VOIP Calls + Resample + PHP

Published: (January 3, 2026 at 11:17 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

Visão geral

Este guia demonstra como usar Swoole para implementar chamadas VoIP em tempo real com áudio de 48 kHz em PHP. O projeto SpechPhone fornece um softphone web que realiza SIP/RTP no backend e entrega áudio PCM ao navegador via WebSocket, sem depender de Asterisk/AGI.

Arquitetura

  • middleware.php – servidor HTTP/WebSocket que fornece a UI e controla a chamada.
  • audio.php – servidor de áudio responsável por mixar e fazer o streaming dos pacotes de áudio.

O fluxo de mídia funciona da seguinte forma:

  1. O backend recebe pacotes RTP.
  2. A biblioteca libspech decodifica os pacotes (usando o runtime pcg729).
  3. O áudio decodificado é enviado em chunks PCM ao cliente WebSocket, onde é reproduzido com AudioContext do navegador.

A chave para o desempenho em tempo real é o uso de corrotinas do Swoole, que permitem múltiplas chamadas simultâneas sem bloquear o processo principal.

Pré‑requisitos e instalação

# dependências do sistema
sudo apt update && sudo apt install -y openssl

# clonar repositórios
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

Em terminais separados, inicie os dois serviços:

php middleware.php
php audio.php

Importante: o projeto utiliza a extensão Swoole (não OpenSwoole).

Oferta de codec Opus / 48 kHz

O trunkController pode oferecer diferentes codecs via SDP. Para usar Opus a 48 kHz:

$phone->mountLineCodecSDP('opus/48000/2');

Isso faz com que o fluxo RTP seja negociado com 48 kHz, permitindo áudio de alta qualidade.

Exemplo completo em PHP

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');
});

O exemplo está documentado em:

Experimentos sugeridos

1. Trocar o codec ofertado

Altere a linha de oferta de codec para L16/8000 e compare a quantidade de bytes recebidos em onReceiveAudio.

$phone->mountLineCodecSDP('L16/8000');

2. Instrumentar o fluxo PCM

Registre o tamanho (strlen) de cada chunk de PCM recebido e observe padrões de tamanho, frequência e jitter.

3. Atualizar a UI

Implemente um cliente “thin” via WebSocket que reage aos eventos ringing, answered e hangup, exibindo mensagens simples na tela.

  • SpechPhone (repo)
  • SpechPhone README (branch volume-dev)
  • libspech (repo)
  • libspech README (branch spech)
  • libspech example.php
  • pcg729 runtime release
Back to Blog

Related posts

Read more »