DEV Community

Cover image for VOIP Calls + Resample + PHP
Spech Shop
Spech Shop

Posted on

VOIP Calls + Resample + PHP

VoIP Real-Time e Áudio 48 kHz no PHP: Guia Hands-On com Swoole

Este guia demonstra como construir um softphone completo com áudio de alta qualidade (48 kHz) usando PHP moderno com Swoole. Vamos explorar VoIP e processamento de áudio em tempo real através de uma implementação prática.

Nota importante sobre arquitetura: O SpechPhone que vamos explorar funciona de um jeito diferente do que você talvez esteja acostumado. Aqui, subimos dois servidores PHP (middleware.php e audio.php) que entregam áudio via RTP no backend + WebSocket (PCM) pro browser — sem passar pelo caminho tradicional do Asterisk/AGI. É uma abordagem mais direta que facilita a compreensão do fluxo completo.
Referência: https://github.com/spechshop/spechphone/blob/volume-dev/README.md

O que você vai construir (de verdade)

Ao final deste guia, você terá nas mãos:

  • Um softphone web funcional que faz SIP/RTP em tempo real, com interface no browser via WebSocket
  • Um pipeline de mídia completo onde o backend recebe RTP, decodifica e envia PCM em chunks pro cliente
  • Uma implementação prática de I/O assíncrono com corrotinas por sessão

E o melhor: você vai entender cada peça desse quebra-cabeça.

1) Entendendo a arquitetura

Vamos começar pelo básico: como tudo isso se encaixa? O SpechPhone é construído sobre PHP + Swoole, trabalhando com mídia em RTP/UDP no backend e PCM via WebSocket direto pro browser. Nada de WebRTC/SRTP/ICE/DTLS aqui — é uma abordagem mais crua e direta, o que facilita muito pra entender o que está acontecendo em cada camada.

Referência: https://github.com/spechshop/spechphone/blob/volume-dev/README.md

Os dois pilares da aplicação:

  • middleware.php: servidor HTTP/WebSocket para interface web e controle de chamadas + servidor UDP para sinalização SIP
  • audio.php: servidor HTTP/WebSocket + UDP que recebe streams RTP decodificados, mixa múltiplos canais de áudio e distribui via WebSocket para os clientes

Cada um tem seu papel bem definido, e você vai ver como eles conversam entre si.

Referência: https://github.com/spechshop/spechphone (seção "Directory Structure" do README)

2) O segredo do Swoole: corrotinas e I/O assíncrono

O conceito fundamental aqui é I/O assíncrono com corrotinas. Independente da linguagem, quando você domina esse padrão, consegue lidar com operações em tempo real de forma eficiente.

O SpechPhone abre uma coroutine dedicada pra cada trunkController, o que significa que você pode ter várias chamadas simultâneas rodando sem bloquear o processo principal. É a aplicação prática de concorrência cooperativa — um padrão que funciona em qualquer ecossistema que o implemente.

Referência: https://github.com/spechshop/spechphone/blob/volume-dev/README.md

Detalhe importante: Estamos falando de Swoole (a extensão/runtime oficial), não OpenSwoole. São projetos diferentes, então fique atento na hora de instalar.

3) Mão na massa: subir o SpechPhone localmente

Hora de colocar a mão no teclado. O README do SpechPhone já entrega tudo mastigado: clone, instale o runtime e suba os servidores. Simples assim.

# deps
sudo apt update && sudo apt install -y openssl

# repo + lib
git clone https://github.com/spechshop/spechphone && cd spechphone
git clone https://github.com/spechshop/libspech

# runtime php otimizado (pcg729)
curl -L https://github.com/spechshop/pcg729/releases/download/current/php -o php
chmod +x ./php
sudo cp php /usr/local/bin/php

# start (em terminais separados)
php middleware.php
php audio.php
Enter fullscreen mode Exit fullscreen mode

Referência do trecho acima: https://github.com/spechshop/spechphone/blob/volume-dev/README.md

Dica prática: Abra dois terminais lado a lado. Deixe os logs rolando e observe como os servidores se comunicam. É bem instrutivo ver o fluxo acontecendo em tempo real.

4) Áudio 48 kHz: onde entra (de verdade) e por que isso importa

Aqui é onde a coisa fica interessante. Quando falamos de telefonia tradicional, você está preso a 8 kHz (aquele som meio "de telefone", sabe?). Mas com Opus a 48 kHz, você tem qualidade de áudio próxima do que ouve em streaming de música. É uma diferença que você sente na primeira chamada.

O SpechPhone trabalha com duas peças que se complementam:

1) Oferta de Opus/48kHz via SDP no trunkController (libspech). É aqui que você diz pro outro lado: "ei, eu falo Opus em alta qualidade":

// Oferecer codec Opus em SDP
$phone->mountLineCodecSDP('opus/48000/2');
Enter fullscreen mode Exit fullscreen mode

Referência do snippet: https://github.com/spechshop/libspech/blob/spech/README.md

2) Decodificação dinâmica + PCM em chunks pro browser. Os pacotes RTP chegam, são processados pela libspech (usando funções nativas via runtime pcg729), e o áudio decodificado é entregue ao controlador WebSocket em PCM puro, pronto pra ser consumido no browser via webkitAudioContext.

Referência: https://github.com/spechshop/spechphone/blob/volume-dev/README.md

Em outras palavras: você negocia Opus, recebe RTP, decodifica e entrega PCM. Simples, direto e poderoso.

5) Um exemplo mínimo de chamada — vendo corrotinas e eventos na prática

Esse é o "hello world" da stack. Aqui você registra no servidor SIP, oferece o codec, reage a eventos (tocando, atendido, desligado) e recebe áudio. Tudo isso em menos de 50 linhas:

<?php
use libspech\Sip\trunkController;

include 'plugins/autoloader.php';

\Swoole\Coroutine\run(function () {
    $username = getenv('SIP_USERNAME');
    $password = getenv('SIP_PASSWORD');
    $domain   = getenv('SIP_DOMAIN');
    $host     = gethostbyname($domain);

    $phone = new trunkController($username, $password, $host, 5060);

    if (!$phone->register(2)) {
        throw new \Exception('Falha no registro');
    }

    // Oferecer codec Opus em SDP (48 kHz)
    $phone->mountLineCodecSDP('opus/48000/2');

    $phone->onRinging(function ($phone) {
        echo "Tocando...\n";
    });

    $phone->onAnswer(function (trunkController $phone) {
        echo "Atendido. Recebendo mídia...\n";
        $phone->receiveMedia();
        \Swoole\Coroutine::sleep(10);
    });

    $phone->onReceiveAudio(function ($pcmData, $peer, trunkController $phone) {
        echo "Recebido: " . strlen($pcmData) . " bytes\n";
    });

    $phone->onHangup(function (trunkController $phone) {
        echo "Chamada finalizada\n";
        $phone->close();
    });

    $phone->call('5511999999999');
});
Enter fullscreen mode Exit fullscreen mode

Referência (onde esse exemplo está documentado): https://github.com/spechshop/libspech/blob/spech/README.md

Referência do exemplo completo: https://github.com/spechshop/libspech/blob/spech/example.php

O que está acontecendo aqui? Você registra no servidor SIP, configura callbacks pra cada evento da chamada (ringing, answer, hangup) e, quando atendido, começa a receber chunks de áudio PCM. Cada callback roda na sua própria coroutine, sem travar nada.

6) Três exercícios práticos (pra você realmente aprender fazendo)

Ler código é legal, mas mexer nele é melhor. Aqui vão três experimentos rápidos pra você sentir como tudo funciona:

1) Troque o codec e compare

2) Instrumente o fluxo de PCM

3) Faça a UI reagir aos eventos

Links principais (pra você não se perder)

Aqui estão todos os recursos que você vai precisar:


E agora? Clone o repo, suba os servidores e faça sua primeira chamada. Os conceitos apresentados aqui são aplicáveis a qualquer stack que implemente I/O assíncrono com corrotinas. Boa sorte! 🚀

Top comments (0)