VoIP Real-Time e Áudio 48 kHz no PHP: Guia Hands-On com Swoole
Nota de arquitetura: o fluxo documentado do SpechPhone sobe dois serviços PHP (middleware.php e audio.php) e entrega áudio por RTP (backend) + WebSocket (PCM pro browser) — sem depender do caminho clássico de Asterisk/AGI (no sentido de: não existe “passo” de AGI na execução descrita no README).
Referência: https://github.com/spechshop/spechph...-dev/README.md
O que você vai montar (na prática)
1) Arquitetura em 60 segundos (2 processos, um objetivo)
O próprio README descreve o projeto como PHP + Swoole, com mídia em RTP/UDP no backend e PCM via WebSocket pro browser (sem WebRTC/SRTP/ICE/DTLS).
Referência: https://github.com/spechshop/spechph...-dev/README.md
Papéis (documentados no README):
2) “Ok, mas cadê o Swoole nessa história?”
A parte “cala-boca” aqui é simples: corrotinas. O README afirma que o SpechPhone abre uma coroutine dedicada por trunkController e que isso permite várias chamadas simultâneas sem bloquear o processo principal.
Referência: https://github.com/spechshop/spechph...-dev/README.md
Se você já ouviu “PHP não aguenta real-time”, o ponto é: a crítica geralmente mira no PHP “bloqueante”; aqui a pilha gira em I/O assíncrono com Swoole.
Importante: aqui é Swoole (extensão/runtime Swoole), não OpenSwoole.
3) Mão na massa: subir o SpechPhone localmente
O README do SpechPhone já entrega o caminho “clone → runtime → start”:
# 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/...ad/current/php -o php
chmod +x ./php
sudo cp php /usr/local/bin/php
# start (em terminais separados)
php middleware.php
php audio.php
Referência do trecho acima: https://github.com/spechshop/spechph...-dev/README.md
4) Áudio 48 kHz: onde entra (de verdade) e por que chama atenção
Tem duas peças documentadas que se complementam:
1) Oferta de Opus/48kHz via SDP no trunkController (libspech). O README mostra explicitamente:
// Oferecer codec Opus em SDP
$phone->mountLineCodecSDP('opus/48000/2');
Referência do snippet: https://github.com/spechshop/libspec...pech/README.md
2) Decodificação dinâmica + PCM em chunks pro browser. O README do SpechPhone descreve que os pacotes RTP chegam, são processados pela libspech (com funções nativas via runtime pcg729) e o áudio decodificado é entregue ao controlador WebSocket em PCM, pra ser consumido no browser via webkitAudioContext.
Referência: https://github.com/spechshop/spechph...-dev/README.md
Ou seja: “48 kHz” aparece naturalmente quando você negocia Opus e trata a mídia como mídia de verdade (não só “telecom 8 kHz”).
5) Um exemplo mínimo de chamada (libspech) — com corrotinas e eventos
Esse exemplo (do README do libspech) é o “hello world” da stack: registrar, oferecer codec, reagir a eventos e receber áudio.
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');
});
Referência (onde esse exemplo está documentado): https://github.com/spechshop/libspec...pech/README.md
Referência do exemplo completo: https://github.com/spechshop/libspec...ch/example.php
6) Três exercícios rápidos (pra você “entrar no código”, não só ler)
1) Troque o codec ofertado
2) Instrumente o fluxo de PCM
3) Faça o UI “sentir” o estado
Links principais (pra você não se perder no labirinto)
More...
Nota de arquitetura: o fluxo documentado do SpechPhone sobe dois serviços PHP (middleware.php e audio.php) e entrega áudio por RTP (backend) + WebSocket (PCM pro browser) — sem depender do caminho clássico de Asterisk/AGI (no sentido de: não existe “passo” de AGI na execução descrita no README).
Referência: https://github.com/spechshop/spechph...-dev/README.md
O que você vai montar (na prática)
- Um softphone web que faz SIP/RTP em tempo real, com UI no browser via WebSocket.
- Um pipeline de mídia onde o backend recebe RTP, decodifica e manda PCM em chunks pro cliente.
- Um “ponto de prova” bem direto de por que Swoole muda o jogo em PHP (I/O assíncrono + corrotinas por sessão).
1) Arquitetura em 60 segundos (2 processos, um objetivo)
O próprio README descreve o projeto como PHP + Swoole, com mídia em RTP/UDP no backend e PCM via WebSocket pro browser (sem WebRTC/SRTP/ICE/DTLS).
Referência: https://github.com/spechshop/spechph...-dev/README.md
Papéis (documentados no README):
- middleware.php: servidor HTTP/WS (UI + controle)
- audio.php: servidor de áudio (mix/stream)
Referência: https://github.com/spechshop/spechphone (seção “Directory Structure” do README)
2) “Ok, mas cadê o Swoole nessa história?”
A parte “cala-boca” aqui é simples: corrotinas. O README afirma que o SpechPhone abre uma coroutine dedicada por trunkController e que isso permite várias chamadas simultâneas sem bloquear o processo principal.
Referência: https://github.com/spechshop/spechph...-dev/README.md
Se você já ouviu “PHP não aguenta real-time”, o ponto é: a crítica geralmente mira no PHP “bloqueante”; aqui a pilha gira em I/O assíncrono com Swoole.
Importante: aqui é Swoole (extensão/runtime Swoole), não OpenSwoole.
O README do SpechPhone já entrega o caminho “clone → runtime → start”:
# 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/...ad/current/php -o php
chmod +x ./php
sudo cp php /usr/local/bin/php
# start (em terminais separados)
php middleware.php
php audio.php
Referência do trecho acima: https://github.com/spechshop/spechph...-dev/README.md
4) Áudio 48 kHz: onde entra (de verdade) e por que chama atenção
Tem duas peças documentadas que se complementam:
1) Oferta de Opus/48kHz via SDP no trunkController (libspech). O README mostra explicitamente:
// Oferecer codec Opus em SDP
$phone->mountLineCodecSDP('opus/48000/2');
Referência do snippet: https://github.com/spechshop/libspec...pech/README.md
2) Decodificação dinâmica + PCM em chunks pro browser. O README do SpechPhone descreve que os pacotes RTP chegam, são processados pela libspech (com funções nativas via runtime pcg729) e o áudio decodificado é entregue ao controlador WebSocket em PCM, pra ser consumido no browser via webkitAudioContext.
Referência: https://github.com/spechshop/spechph...-dev/README.md
Ou seja: “48 kHz” aparece naturalmente quando você negocia Opus e trata a mídia como mídia de verdade (não só “telecom 8 kHz”).
5) Um exemplo mínimo de chamada (libspech) — com corrotinas e eventos
Esse exemplo (do README do libspech) é o “hello world” da stack: registrar, oferecer codec, reagir a eventos e receber áudio.
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');
});
Referência (onde esse exemplo está documentado): https://github.com/spechshop/libspec...pech/README.md
Referência do exemplo completo: https://github.com/spechshop/libspec...ch/example.php
6) Três exercícios rápidos (pra você “entrar no código”, não só ler)
1) Troque o codec ofertado
- Mude opus/48000/2 para L16/8000 (o README do libspech também menciona L16/8000) e compare o que você recebe em onReceiveAudio.
- Referência: https://github.com/spechshop/libspec...pech/README.md
2) Instrumente o fluxo de PCM
- Deixe o log de strlen($pcmData) rodando e observe padrões (tamanho, frequência, jitter aparente).
- Referência: https://github.com/spechshop/libspec...pech/README.md
3) Faça o UI “sentir” o estado
- O SpechPhone descreve um cliente “thin” via WebSocket; faça o frontend reagir a “ringing/answered/hangup” do backend (comece só logando em tela).
- Referência: https://github.com/spechshop/spechph...-dev/README.md
Links principais (pra você não se perder no labirinto)
- SpechPhone (repo): https://github.com/spechshop/spechphone
- SpechPhone (README, branch volume-dev): https://github.com/spechshop/spechph...-dev/README.md
- libspech (repo): https://github.com/spechshop/libspech
- libspech (README, branch spech): https://github.com/spechshop/libspec...pech/README.md
- libspech (example.php): https://github.com/spechshop/libspec...ch/example.php
- pcg729 (runtime release “current/php”): https://github.com/spechshop/pcg729/...es/tag/current
More...