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 »

1390. Four Divisors

Problem Statement Given an integer array nums, return the sum of divisors of the integers in that array that have exactly four divisors. If there is no such in...