<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: spechshop </title>
    <description>The latest articles on DEV Community by spechshop  (@spechshop).</description>
    <link>https://dev.to/spechshop</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2142329%2F882bfa61-415c-490c-aa52-5ca4aac10662.png</url>
      <title>DEV Community: spechshop </title>
      <link>https://dev.to/spechshop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/spechshop"/>
    <language>en</language>
    <item>
      <title>SpechPhone: softphone com chamadas inbound, chat SIP e bridge RTP ↔ PCM em PHP + Swoole na Web</title>
      <dc:creator>spechshop </dc:creator>
      <pubDate>Fri, 08 May 2026 09:04:16 +0000</pubDate>
      <link>https://dev.to/spechshop/spechphone-agora-com-chamadas-inbound-chat-sip-e-bridge-rtp-pcm-em-php-swoole-1429</link>
      <guid>https://dev.to/spechshop/spechphone-agora-com-chamadas-inbound-chat-sip-e-bridge-rtp-pcm-em-php-swoole-1429</guid>
      <description>&lt;p&gt;Olá, comunidade!&lt;/p&gt;

&lt;p&gt;Há um tempo eu publiquei aqui no DEV um artigo apresentando o &lt;strong&gt;SpechPhone&lt;/strong&gt;, um softphone SIP web open-source feito com &lt;strong&gt;PHP&lt;/strong&gt;, &lt;strong&gt;Swoole&lt;/strong&gt; e &lt;strong&gt;WebSockets&lt;/strong&gt;, com uma proposta meio fora da curva:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;criar um softphone web sem depender de WebRTC.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Na primeira versão, o foco estava em provar que era possível fazer chamadas SIP pelo navegador usando um backend PHP assíncrono, controlando sinalização SIP, áudio PCM e transporte em tempo real com Swoole.&lt;/p&gt;

&lt;p&gt;Agora o projeto deu um salto importante.&lt;/p&gt;

&lt;p&gt;O SpechPhone deixou de ser apenas uma demonstração de discagem web e passou a caminhar para uma arquitetura mais completa de softphone SIP, com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recebimento de chamadas inbound;&lt;/li&gt;
&lt;li&gt;controle de estado em tempo real;&lt;/li&gt;
&lt;li&gt;bridge de mídia RTP ↔ PCM;&lt;/li&gt;
&lt;li&gt;suporte a SIP MESSAGE;&lt;/li&gt;
&lt;li&gt;interface de chat integrada;&lt;/li&gt;
&lt;li&gt;melhorias no áudio via WebSocket;&lt;/li&gt;
&lt;li&gt;controle de volume dinâmico;&lt;/li&gt;
&lt;li&gt;arquitetura mais limpa para chamadas ativas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E o mais importante: ainda sem WebRTC no caminho da mídia.&lt;/p&gt;

&lt;p&gt;Repositório:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/spechshop/spechphone" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  O que mudou nesta nova fase?
&lt;/h2&gt;

&lt;p&gt;A primeira versão do SpechPhone já conseguia iniciar chamadas, registrar conta SIP, configurar codec e transportar áudio entre o navegador e o backend.&lt;/p&gt;

&lt;p&gt;Mas ainda faltava uma parte essencial para um softphone de verdade:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;receber chamadas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nesta atualização, a branch &lt;code&gt;inbound&lt;/code&gt; introduz um fluxo mais completo para chamadas recebidas, incluindo tratamento de &lt;code&gt;INVITE&lt;/code&gt;, &lt;code&gt;ACK&lt;/code&gt;, &lt;code&gt;CANCEL&lt;/code&gt; e &lt;code&gt;BYE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Isso muda bastante a natureza do projeto.&lt;/p&gt;

&lt;p&gt;Antes ele era um discador web experimental.&lt;/p&gt;

&lt;p&gt;Agora ele começa a se comportar como um endpoint SIP real.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recebimento de chamadas inbound
&lt;/h2&gt;

&lt;p&gt;O fluxo inbound funciona mais ou menos assim:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O usuário configura e registra sua conta SIP no SpechPhone.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;server.php&lt;/code&gt; escuta SIP via UDP na porta &lt;code&gt;4000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Quando chega um &lt;code&gt;INVITE&lt;/code&gt;, o backend identifica o usuário de destino.&lt;/li&gt;
&lt;li&gt;O navegador recebe um evento em tempo real informando a chamada recebida.&lt;/li&gt;
&lt;li&gt;Ao aceitar, o servidor responde com &lt;code&gt;200 OK&lt;/code&gt; contendo SDP local.&lt;/li&gt;
&lt;li&gt;A mídia RTP é ligada à ponte interna RTP ↔ PCM.&lt;/li&gt;
&lt;li&gt;O áudio chega ao navegador via WebSocket.&lt;/li&gt;
&lt;li&gt;Ao desligar, &lt;code&gt;BYE&lt;/code&gt;/&lt;code&gt;CANCEL&lt;/code&gt; limpam o estado da chamada.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Em termos práticos, o SpechPhone agora consegue tocar no navegador quando uma chamada SIP chega.&lt;/p&gt;

&lt;p&gt;Pequeno detalhe técnico: isso parece simples escrito em oito linhas, mas no mundo SIP cada header tem autoestima própria. Se errar &lt;code&gt;To-tag&lt;/code&gt;, &lt;code&gt;Call-ID&lt;/code&gt;, &lt;code&gt;CSeq&lt;/code&gt; ou rota reversa, o softphone vira decoração digital.&lt;/p&gt;




&lt;h2&gt;
  
  
  CallState: estado compartilhado com Swoole Table
&lt;/h2&gt;

&lt;p&gt;Uma das mudanças importantes foi a introdução de um gerenciador de estado para chamadas.&lt;/p&gt;

&lt;p&gt;O SpechPhone agora usa estruturas em memória com &lt;strong&gt;Swoole Table&lt;/strong&gt; para rastrear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;chamadas recebidas;&lt;/li&gt;
&lt;li&gt;chamadas ativas;&lt;/li&gt;
&lt;li&gt;bindings SIP;&lt;/li&gt;
&lt;li&gt;usuário SIP associado ao navegador;&lt;/li&gt;
&lt;li&gt;IP/porta RTP remoto;&lt;/li&gt;
&lt;li&gt;codec negociado;&lt;/li&gt;
&lt;li&gt;worker dono da chamada;&lt;/li&gt;
&lt;li&gt;status da sessão.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A ideia é simples: em um servidor assíncrono, principalmente com múltiplos workers, não dá para depender só de variável local solta no vento.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;CallState&lt;/code&gt; vira uma espécie de “quadro branco compartilhado” do runtime.&lt;/p&gt;

&lt;p&gt;Ele mantém informações como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;CallState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$incomingCalls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;CallState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$activeCalls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;CallState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sipBindings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso permite que handlers diferentes saibam se existe uma chamada tocando, se ela já foi aceita, qual usuário pertence àquela chamada e como encerrar tudo corretamente.&lt;/p&gt;

&lt;p&gt;Esse tipo de controle é essencial para eventos como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aceitar chamada;&lt;/li&gt;
&lt;li&gt;rejeitar chamada;&lt;/li&gt;
&lt;li&gt;desligar;&lt;/li&gt;
&lt;li&gt;receber &lt;code&gt;BYE&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;lidar com timeout RTP;&lt;/li&gt;
&lt;li&gt;sincronizar múltiplas abas abertas no navegador.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Bridge RTP ↔ PCM: o coração da mídia
&lt;/h2&gt;

&lt;p&gt;A parte mais interessante continua sendo a mídia.&lt;/p&gt;

&lt;p&gt;O SpechPhone não envia RTP diretamente para o navegador e não depende do WebRTC para negociar ICE, STUN, TURN ou DTLS-SRTP.&lt;/p&gt;

&lt;p&gt;A arquitetura segue outro caminho:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SIP/RTP peer
    ↓
RTP UDP
    ↓
PHP + Swoole + libspech
    ↓
PCM interno
    ↓
WebSocket de áudio
    ↓
Browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o caminho inverso também existe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microfone do navegador
    ↓
PCM via WebSocket
    ↓
audio.php
    ↓
UDP interno
    ↓
encode para codec SIP
    ↓
RTP para o peer remoto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A classe &lt;code&gt;CallMediaBridge&lt;/code&gt; centraliza boa parte desse fluxo.&lt;/p&gt;

&lt;p&gt;Ela cria um socket UDP local para conversar com o &lt;code&gt;audio.php&lt;/code&gt;, registra callbacks para receber PCM decodificado e inicia corrotinas para o caminho:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RTP → decode → PCM → navegador;&lt;/li&gt;
&lt;li&gt;navegador → PCM → encode → RTP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essa separação deixa a arquitetura mais clara: o plano SIP não precisa carregar toda a responsabilidade da mídia nas costas.&lt;/p&gt;




&lt;h2&gt;
  
  
  audio.php como servidor dedicado de áudio
&lt;/h2&gt;

&lt;p&gt;Outra decisão arquitetural importante foi manter o &lt;code&gt;audio.php&lt;/code&gt; como servidor dedicado para a ponte de áudio.&lt;/p&gt;

&lt;p&gt;Ele atua como ponto intermediário entre:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebSocket do navegador;&lt;/li&gt;
&lt;li&gt;UDP interno;&lt;/li&gt;
&lt;li&gt;chamadas ativas;&lt;/li&gt;
&lt;li&gt;buffers PCM;&lt;/li&gt;
&lt;li&gt;frequência de áudio;&lt;/li&gt;
&lt;li&gt;controle de reconexão.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Na prática, isso ajuda a isolar a mídia da sinalização.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;server.php&lt;/code&gt; cuida do HTTP/WSS/SIP/control plane.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;audio.php&lt;/code&gt; cuida da parte mais ingrata: áudio em tempo real.&lt;/p&gt;

&lt;p&gt;É aquela divisão saudável: um cuida do drama SIP, o outro cuida do chiado existencial do PCM.&lt;/p&gt;




&lt;h2&gt;
  
  
  Codecs suportados
&lt;/h2&gt;

&lt;p&gt;O SpechPhone continua com foco em controle direto dos codecs.&lt;/p&gt;

&lt;p&gt;A depender do ambiente e das extensões disponíveis, o projeto trabalha com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PCMA;&lt;/li&gt;
&lt;li&gt;PCMU;&lt;/li&gt;
&lt;li&gt;G.729;&lt;/li&gt;
&lt;li&gt;L16;&lt;/li&gt;
&lt;li&gt;Opus;&lt;/li&gt;
&lt;li&gt;telephone-event / DTMF RFC2833.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A negociação usa SDP remoto para escolher o codec compatível e gerar uma resposta local.&lt;/p&gt;

&lt;p&gt;A vantagem dessa abordagem é ter controle total do pipeline.&lt;/p&gt;

&lt;p&gt;O navegador não precisa suportar o codec SIP do tronco. Ele pode trabalhar com PCM, enquanto o backend faz a conversão necessária para o mundo VoIP.&lt;/p&gt;

&lt;p&gt;Isso abre espaço para integrações mais avançadas no futuro, como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gravação;&lt;/li&gt;
&lt;li&gt;transcrição;&lt;/li&gt;
&lt;li&gt;VAD;&lt;/li&gt;
&lt;li&gt;agente de voz;&lt;/li&gt;
&lt;li&gt;análise de áudio;&lt;/li&gt;
&lt;li&gt;filtros;&lt;/li&gt;
&lt;li&gt;mixagem;&lt;/li&gt;
&lt;li&gt;monitoramento de volume;&lt;/li&gt;
&lt;li&gt;detecção de fala.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SIP MESSAGE e chat integrado
&lt;/h2&gt;

&lt;p&gt;Outra novidade forte é o suporte a mensagens SIP.&lt;/p&gt;

&lt;p&gt;O SpechPhone agora tem um caminho integrado para envio e recebimento de mensagens de texto usando &lt;code&gt;MESSAGE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No lado do backend, existe um armazenamento simples em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/data/spechphone/messages.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ele mantém:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conversas;&lt;/li&gt;
&lt;li&gt;histórico;&lt;/li&gt;
&lt;li&gt;mensagens não lidas;&lt;/li&gt;
&lt;li&gt;participantes;&lt;/li&gt;
&lt;li&gt;última mensagem;&lt;/li&gt;
&lt;li&gt;eventos em tempo real.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quando uma nova mensagem chega, o backend pode empurrar um evento WebSocket para o cliente conectado.&lt;/p&gt;

&lt;p&gt;Isso transforma o SpechPhone em algo além de um discador.&lt;/p&gt;

&lt;p&gt;Ele começa a virar uma interface SIP web completa: chamada + mensagem + presença futura + controle de conta.&lt;/p&gt;

&lt;p&gt;A interface de mensagens ainda é simples, mas já cria uma base interessante para comunicação em tempo real dentro do próprio navegador.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interface mais modular
&lt;/h2&gt;

&lt;p&gt;A UI também evoluiu.&lt;/p&gt;

&lt;p&gt;Agora o projeto organiza melhor as áreas principais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;discador;&lt;/li&gt;
&lt;li&gt;chamadas;&lt;/li&gt;
&lt;li&gt;mensagens;&lt;/li&gt;
&lt;li&gt;configurações de áudio;&lt;/li&gt;
&lt;li&gt;configuração SIP;&lt;/li&gt;
&lt;li&gt;notificações;&lt;/li&gt;
&lt;li&gt;timers;&lt;/li&gt;
&lt;li&gt;medidores de sinal;&lt;/li&gt;
&lt;li&gt;barra de chamada ativa.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Algumas capturas da nova fase:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5l32bsebl6k42hhjb3gz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5l32bsebl6k42hhjb3gz.jpeg" alt="In Call" width="800" height="352"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38ry065fu8b9a29igimh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38ry065fu8b9a29igimh.jpeg" alt="Keyboard" width="800" height="352"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkk8beydomu7dls7gbody.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkk8beydomu7dls7gbody.jpeg" alt="Messages" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Melhorias recentes no áudio
&lt;/h2&gt;

&lt;p&gt;As versões recentes também trouxeram melhorias práticas no áudio e na experiência da chamada:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;controle de volume dinâmico com &lt;code&gt;GainNode&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;sliders visuais para microfone e retorno;&lt;/li&gt;
&lt;li&gt;persistência de configurações em &lt;code&gt;localStorage&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;melhorias no buffer PCM;&lt;/li&gt;
&lt;li&gt;validação de codec/frequência;&lt;/li&gt;
&lt;li&gt;controle de reconexão;&lt;/li&gt;
&lt;li&gt;logs mais ricos no client e no server;&lt;/li&gt;
&lt;li&gt;limpeza de conexões antigas;&lt;/li&gt;
&lt;li&gt;redução de inconsistências em chamadas WebSocket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essas mudanças são menos chamativas que “agora recebe chamada”, mas são justamente o tipo de ajuste que separa demo bonita de ferramenta utilizável.&lt;/p&gt;

&lt;p&gt;Áudio em tempo real é cruel: um buffer mal-humorado e o usuário já acha que está falando de dentro de uma lata de sardinha.&lt;/p&gt;


&lt;h2&gt;
  
  
  Por que não WebRTC?
&lt;/h2&gt;

&lt;p&gt;Essa pergunta sempre aparece.&lt;/p&gt;

&lt;p&gt;O SpechPhone não é uma tentativa de dizer que WebRTC é ruim.&lt;/p&gt;

&lt;p&gt;WebRTC é excelente.&lt;/p&gt;

&lt;p&gt;Mas ele também traz uma pilha grande:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ICE;&lt;/li&gt;
&lt;li&gt;STUN;&lt;/li&gt;
&lt;li&gt;TURN;&lt;/li&gt;
&lt;li&gt;SDP próprio;&lt;/li&gt;
&lt;li&gt;codecs controlados pelo browser;&lt;/li&gt;
&lt;li&gt;políticas de mídia;&lt;/li&gt;
&lt;li&gt;NAT traversal;&lt;/li&gt;
&lt;li&gt;comportamento diferente entre navegadores;&lt;/li&gt;
&lt;li&gt;dependências que nem sempre fazem sentido em uma infraestrutura SIP já existente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O SpechPhone segue outra filosofia:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;se o servidor já fala SIP e RTP, então o navegador pode ser apenas a interface, e o backend controla a mídia.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Isso dá mais trabalho?&lt;/p&gt;

&lt;p&gt;Sim.&lt;/p&gt;

&lt;p&gt;Dá mais controle?&lt;/p&gt;

&lt;p&gt;Muito.&lt;/p&gt;

&lt;p&gt;É uma abordagem para quem quer entender e controlar a pilha inteira, do &lt;code&gt;INVITE&lt;/code&gt; ao pacote RTP.&lt;/p&gt;


&lt;h2&gt;
  
  
  Arquitetura resumida
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
 ├── Interface Web
 ├── WebSocket de controle
 └── WebSocket de áudio PCM
          │
          ▼
server.php
 ├── HTTPS/WSS
 ├── SIP UDP :4000
 ├── roteamento de eventos
 ├── controle de chamadas
 ├── CallState / Swoole Tables
 └── integração com libspech
          │
          ▼
audio.php
 ├── WebSocket de áudio
 ├── UDP interno :9600
 ├── buffers PCM
 ├── relay de áudio
 └── ponte com chamadas ativas
          │
          ▼
SIP/RTP peer
 ├── Asterisk
 ├── FreeSWITCH
 ├── OpenSIPS
 ├── trunk SIP
 └── provedor VoIP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Instalação básica
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/spechshop/spechphone
&lt;span class="nb"&gt;cd &lt;/span&gt;spechphone

git clone https://github.com/spechshop/libspech

wget https://github.com/spechshop/pcg729/releases/download/PCG729/php
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;php /usr/local/bin/php
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/php

git submodule update &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt;

&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Depois configure sua chave:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SPECH_VAULT_KEY_HEX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sua_chave_aqui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E rode os servidores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php server.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em outro terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php audio.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Status do projeto
&lt;/h2&gt;

&lt;p&gt;O SpechPhone ainda está em desenvolvimento ativo.&lt;/p&gt;

&lt;p&gt;A branch &lt;code&gt;inbound&lt;/code&gt; já é altamente funcional, mas ainda deve ser tratada como experimental para produção.&lt;/p&gt;

&lt;p&gt;O foco agora é endurecer a base:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;melhorar estabilidade inbound;&lt;/li&gt;
&lt;li&gt;refinar tratamento de NAT;&lt;/li&gt;
&lt;li&gt;lapidar encerramento de chamadas;&lt;/li&gt;
&lt;li&gt;melhorar compatibilidade com PBXs;&lt;/li&gt;
&lt;li&gt;evoluir o chat SIP;&lt;/li&gt;
&lt;li&gt;preparar o terreno para recursos de mídia mais avançados.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Contribuições
&lt;/h2&gt;

&lt;p&gt;O projeto é open-source e qualquer contribuição é bem-vinda.&lt;/p&gt;

&lt;p&gt;Você pode ajudar com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testes com Asterisk, FreeSWITCH, OpenSIPS e provedores SIP;&lt;/li&gt;
&lt;li&gt;issues com logs reais;&lt;/li&gt;
&lt;li&gt;melhorias na UI;&lt;/li&gt;
&lt;li&gt;ajustes de compatibilidade SIP;&lt;/li&gt;
&lt;li&gt;melhorias no buffer de áudio;&lt;/li&gt;
&lt;li&gt;documentação;&lt;/li&gt;
&lt;li&gt;exemplos de configuração;&lt;/li&gt;
&lt;li&gt;testes de codecs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repositório:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/spechshop/spechphone" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Se o projeto te interessar, uma estrela no GitHub também ajuda bastante.&lt;br&gt;&lt;br&gt;
Open-source vive de código, café e pequenas doses de validação social. Principalmente café.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;O SpechPhone começou como uma provocação técnica:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;será que dá para fazer um softphone SIP web em PHP, com Swoole, sem WebRTC?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A resposta inicial foi: sim, dá.&lt;/p&gt;

&lt;p&gt;Agora a nova pergunta é outra:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dá para transformar isso em um softphone web SIP realmente utilizável?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Com chamadas inbound, bridge RTP ↔ PCM, controle de estado, áudio em tempo real e SIP MESSAGE integrado, o projeto chegou em uma fase muito mais interessante.&lt;/p&gt;

&lt;p&gt;Ainda tem chão pela frente, mas a base ficou mais séria.&lt;/p&gt;

&lt;p&gt;E, sinceramente, ver PHP segurando SIP, RTP, PCM, WebSocket e chat em tempo real no mesmo ecossistema é aquele tipo de insanidade técnica que dá gosto de construir.&lt;/p&gt;

&lt;p&gt;Até a próxima atualização.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>sip</category>
      <category>softphone</category>
      <category>php</category>
    </item>
    <item>
      <title>SpechPhone: Softphone SIP Web com PHP (Swoole) e WebSockets 🚀</title>
      <dc:creator>spechshop </dc:creator>
      <pubDate>Tue, 06 Jan 2026 10:01:01 +0000</pubDate>
      <link>https://dev.to/spechshop/spechphone-softphone-sip-web-com-php-swoole-e-websockets-527o</link>
      <guid>https://dev.to/spechshop/spechphone-softphone-sip-web-com-php-swoole-e-websockets-527o</guid>
      <description>&lt;p&gt;Olá, comunidade! 👋&lt;/p&gt;

&lt;p&gt;Estou animado para compartilhar o status atual do &lt;strong&gt;SpechPhone&lt;/strong&gt;, um softphone SIP web open-source que desafia o convencional ao não utilizar WebRTC. Em vez disso, utilizamos o poder do &lt;strong&gt;PHP com Swoole&lt;/strong&gt; no backend para processamento de mídia em tempo real e &lt;strong&gt;WebSockets&lt;/strong&gt; para o transporte de áudio PCM.&lt;/p&gt;

&lt;p&gt;O projeto está evoluindo rapidamente e gostaria de mostrar como está a interface e as funcionalidades disponíveis hoje.&lt;/p&gt;

&lt;h2&gt;
  
  
  📱 Interface Principal (Dialer)
&lt;/h2&gt;

&lt;p&gt;A interface de chamada foi projetada para ser intuitiva e responsiva. Temos um discador completo com feedback visual, controles de volume independentes para microfone e retorno de áudio, além de indicadores de nível de sinal (VU meters).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqogrpz83qevxyt1t871u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqogrpz83qevxyt1t871u.png" alt="Interface de Chamada" width="800" height="388"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Tela principal: Discador, controles de chamada e monitoramento de áudio.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bastidores Técnicos:&lt;/strong&gt;&lt;br&gt;
O front-end interage com handlers específicos para gerenciar o estado da chamada:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;startCall.php&lt;/code&gt;&lt;/strong&gt;: Ao discar, este handler cria uma corrotina Swoole dedicada que gerencia toda a sinalização SIP (INVITE) e aloca portas UDP dinâmicas para o RTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;checkCall.php&lt;/code&gt;&lt;/strong&gt;: Atua como um &lt;em&gt;heartbeat&lt;/em&gt;, sincronizando o status da UI (timer, botões) com a corrotina no backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dtmf.php&lt;/code&gt;&lt;/strong&gt;: O envio de tons utiliza o padrão RFC 2833, injetando eventos diretamente no stream RTP via PHP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;hangUpCall.php&lt;/code&gt;&lt;/strong&gt;: Garante o encerramento limpo (BYE) e a liberação imediata dos recursos de áudio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📄 plugins/Message/handlers/startCall.php:44&lt;/p&gt;


&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Server&lt;/span&gt; &lt;span class="nv"&gt;$socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$fd&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?bool&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
   &lt;span class="nv"&gt;$vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\spechphoneVault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/data/spechphone/devices.vault'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SPECH_VAULT_KEY_HEX'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🎧 Suporte Avançado a Codecs
&lt;/h2&gt;

&lt;p&gt;Uma das grandes vantagens da nossa arquitetura é o controle total sobre a transcodificação. O SpechPhone suporta nativamente uma grande variedade de codecs, processados diretamente no backend PHP.&lt;/p&gt;

&lt;p&gt;Na aba de Áudio, é possível visualizar e selecionar os codecs disponíveis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;G.729 &amp;amp; Opus:&lt;/strong&gt; Para alta compressão e qualidade.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;PCMA/PCMU:&lt;/strong&gt; Padrões clássicos G.711.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Wideband:&lt;/strong&gt; G.722, Speex, L16 (até 48kHz).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;GSM &amp;amp; iLBC:&lt;/strong&gt; Para cenários de banda estreita.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe0jzgd7n9c7gaqhekaxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe0jzgd7n9c7gaqhekaxz.png" alt="Página de seleção de codecs" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Painel de Codecs: No final todos se tornarão PCM devolvido para o browser.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bastidores Técnicos:&lt;/strong&gt;&lt;br&gt;
A mágica acontece na integração entre o &lt;code&gt;startCall.php&lt;/code&gt; e o servidor de áudio &lt;code&gt;audio.php&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline de Áudio (Sem WebRTC)&lt;/strong&gt;: O navegador envia/recebe PCM raw via WebSocket (porta 8888) para o &lt;code&gt;audio.php&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bridge UDP&lt;/strong&gt;: O &lt;code&gt;audio.php&lt;/code&gt; encaminha esse áudio via UDP local (porta 9600) para a corrotina da chamada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transcoding Nativo&lt;/strong&gt;: O PHP recebe o PCM, aplica a codificação selecionada (ex: comprime para G.729 ou Opus) usando a biblioteca &lt;code&gt;libspech&lt;/code&gt; e envia para o tronco SIP. O processo inverso (decodificação) ocorre na chegada dos pacotes RTP, garantindo compatibilidade universal sem depender de codecs do browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📄 audio.php:54&lt;/p&gt;


&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$udp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Swoole\Coroutine\Socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SOCK_DGRAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$udp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🎧 Servidor UDP aguardando pacotes em 9600...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ⚙️ Configuração SIP Simplificada
&lt;/h2&gt;

&lt;p&gt;Conectar-se ao seu servidor VoIP (Asterisk, FreeSWITCH, Opensips) é simples. A tela de configurações permite definir o servidor, credenciais de autenticação e o codec preferencial para o tronco SIP.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69dyr3zcq6q3dwir7esj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69dyr3zcq6q3dwir7esj.png" alt="Configurações SIP" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Configuração da conta SIP e preferências de conexão.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bastidores Técnicos:&lt;/strong&gt;&lt;br&gt;
A segurança e a validação são prioridades no handler &lt;strong&gt;&lt;code&gt;saveConfig.php&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validação Ativa&lt;/strong&gt;: Antes de salvar, o sistema instancia um &lt;code&gt;trunkController&lt;/code&gt; e tenta realizar um registro real (SIP REGISTER) no servidor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback Imediato&lt;/strong&gt;: Se as credenciais estiverem erradas, o usuário é notificado na hora, evitando configurações "quebradas".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistência Segura&lt;/strong&gt;: Apenas após o sucesso (200 OK), os dados são criptografados e armazenados no &lt;code&gt;devices.vault&lt;/code&gt;. O handler &lt;strong&gt;&lt;code&gt;register.php&lt;/code&gt;&lt;/strong&gt; pode então ser usado para manter a sessão ativa (keep-alive).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📄 plugins/Message/handlers/saveConfig.php:103&lt;/p&gt;


&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$trunkController&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$socket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
       &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'notify'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;'data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
           &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'bg-danger text-white'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Registro falhou, verifique as credenciais fornecidas"&lt;/span&gt;
       &lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="p"&gt;]));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;




&lt;p&gt;O projeto está em fase &lt;strong&gt;Beta&lt;/strong&gt; e o desenvolvimento continua ativo, focado agora em aprimorar a estabilidade e implementar o recebimento de chamadas.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Confira o código e contribua no GitHub:&lt;/strong&gt; &lt;a href="https://github.com/spechshop/spechphone" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fiquem ligados para mais novidades! 🚀&lt;/p&gt;

</description>
      <category>voip</category>
      <category>sip</category>
      <category>softphone</category>
      <category>php</category>
    </item>
    <item>
      <title>VOIP Calls + Resample + PHP</title>
      <dc:creator>spechshop </dc:creator>
      <pubDate>Sun, 04 Jan 2026 04:17:39 +0000</pubDate>
      <link>https://dev.to/spechshop/voip-calls-resample-php-ifg</link>
      <guid>https://dev.to/spechshop/voip-calls-resample-php-ifg</guid>
      <description>&lt;h2&gt;
  
  
  VoIP Real-Time e Áudio 48 kHz no PHP: Guia Hands-On com Swoole
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  O que você vai construir (de verdade)
&lt;/h2&gt;

&lt;p&gt;Ao final deste guia, você terá nas mãos:&lt;/p&gt;

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

&lt;p&gt;E o melhor: você vai entender cada peça desse quebra-cabeça.&lt;/p&gt;

&lt;h2&gt;
  
  
  1) Entendendo a arquitetura
&lt;/h2&gt;

&lt;p&gt;Vamos começar pelo básico: como tudo isso se encaixa? O SpechPhone é construído sobre &lt;strong&gt;PHP + Swoole&lt;/strong&gt;, trabalhando com &lt;strong&gt;mídia em RTP/UDP&lt;/strong&gt; no backend e &lt;strong&gt;PCM via WebSocket&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Referência: &lt;a href="https://github.com/spechshop/spechphone/blob/volume-dev/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone/blob/volume-dev/README.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Os dois pilares da aplicação:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Cada um tem seu papel bem definido, e você vai ver como eles conversam entre si.&lt;/p&gt;

&lt;p&gt;Referência: &lt;a href="https://github.com/spechshop/spechphone" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone&lt;/a&gt; (seção "Directory Structure" do README)&lt;/p&gt;

&lt;h2&gt;
  
  
  2) O segredo do Swoole: corrotinas e I/O assíncrono
&lt;/h2&gt;

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

&lt;p&gt;O SpechPhone abre uma coroutine dedicada pra cada &lt;code&gt;trunkController&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;Referência: &lt;a href="https://github.com/spechshop/spechphone/blob/volume-dev/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone/blob/volume-dev/README.md&lt;/a&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  3) Mão na massa: subir o SpechPhone localmente
&lt;/h2&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# deps&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; openssl

&lt;span class="c"&gt;# repo + lib&lt;/span&gt;
git clone https://github.com/spechshop/spechphone &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;spechphone
git clone https://github.com/spechshop/libspech

&lt;span class="c"&gt;# runtime php otimizado (pcg729)&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://github.com/spechshop/pcg729/releases/download/current/php &lt;span class="nt"&gt;-o&lt;/span&gt; php
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./php
&lt;span class="nb"&gt;sudo cp &lt;/span&gt;php /usr/local/bin/php

&lt;span class="c"&gt;# start (em terminais separados)&lt;/span&gt;
php middleware.php
php audio.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Referência do trecho acima: &lt;a href="https://github.com/spechshop/spechphone/blob/volume-dev/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone/blob/volume-dev/README.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dica prática:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  4) Áudio 48 kHz: onde entra (de verdade) e por que isso importa
&lt;/h2&gt;

&lt;p&gt;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ê &lt;strong&gt;sente&lt;/strong&gt; na primeira chamada.&lt;/p&gt;

&lt;p&gt;O SpechPhone trabalha com duas peças que se complementam:&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Oferecer codec Opus em SDP&lt;/span&gt;
&lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mountLineCodecSDP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'opus/48000/2'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Referência do snippet: &lt;a href="https://github.com/spechshop/libspech/blob/spech/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/libspech/blob/spech/README.md&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Referência: &lt;a href="https://github.com/spechshop/spechphone/blob/volume-dev/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone/blob/volume-dev/README.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Em outras palavras: você negocia Opus, recebe RTP, decodifica e entrega PCM. Simples, direto e poderoso.&lt;/p&gt;

&lt;h2&gt;
  
  
  5) Um exemplo mínimo de chamada — vendo corrotinas e eventos na prática
&lt;/h2&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="n"&gt;libspech\Sip\trunkController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'plugins/autoloader.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;\Swoole\Coroutine\run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SIP_USERNAME'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SIP_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$domain&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SIP_DOMAIN'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$host&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gethostbyname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;trunkController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5060&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Falha no registro'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Oferecer codec Opus em SDP (48 kHz)&lt;/span&gt;
    &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mountLineCodecSDP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'opus/48000/2'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;onRinging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Tocando...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;onAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;trunkController&lt;/span&gt; &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Atendido. Recebendo mídia...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;receiveMedia&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;\Swoole\Coroutine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;onReceiveAudio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pcmData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;trunkController&lt;/span&gt; &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Recebido: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pcmData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;" bytes&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;onHangup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;trunkController&lt;/span&gt; &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Chamada finalizada&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'5511999999999'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Referência (onde esse exemplo está documentado): &lt;a href="https://github.com/spechshop/libspech/blob/spech/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/libspech/blob/spech/README.md&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Referência do exemplo completo: &lt;a href="https://github.com/spechshop/libspech/blob/spech/example.php" rel="noopener noreferrer"&gt;https://github.com/spechshop/libspech/blob/spech/example.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que está acontecendo aqui?&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  6) Três exercícios práticos (pra você realmente aprender fazendo)
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;1) Troque o codec e compare&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mude &lt;code&gt;opus/48000/2&lt;/code&gt; para &lt;code&gt;L16/8000&lt;/code&gt; (o README do libspech também menciona L16/8000) e compare o que você recebe em &lt;code&gt;onReceiveAudio&lt;/code&gt;. Você vai notar diferença no tamanho dos chunks e na qualidade do áudio.&lt;/li&gt;
&lt;li&gt;Referência: &lt;a href="https://github.com/spechshop/libspech/blob/spech/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/libspech/blob/spech/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2) Instrumente o fluxo de PCM&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deixe o log de &lt;code&gt;strlen($pcmData)&lt;/code&gt; rodando e observe os padrões: tamanho dos chunks, frequência de chegada, variações (jitter aparente). É fascinante ver como o áudio flui em tempo real.&lt;/li&gt;
&lt;li&gt;Referência: &lt;a href="https://github.com/spechshop/libspech/blob/spech/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/libspech/blob/spech/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3) Faça a UI reagir aos eventos&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O SpechPhone usa um cliente "thin" via WebSocket. Faça o frontend reagir aos eventos "ringing/answered/hangup" que vêm do backend. Comece simples: apenas logando na tela. Depois você pode adicionar animações, indicadores visuais, etc.&lt;/li&gt;
&lt;li&gt;Referência: &lt;a href="https://github.com/spechshop/spechphone/blob/volume-dev/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone/blob/volume-dev/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links principais (pra você não se perder)
&lt;/h2&gt;

&lt;p&gt;Aqui estão todos os recursos que você vai precisar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SpechPhone (repo): &lt;a href="https://github.com/spechshop/spechphone" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SpechPhone (README, branch volume-dev): &lt;a href="https://github.com/spechshop/spechphone/blob/volume-dev/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/spechphone/blob/volume-dev/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;libspech (repo): &lt;a href="https://github.com/spechshop/libspech" rel="noopener noreferrer"&gt;https://github.com/spechshop/libspech&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;libspech (README, branch spech): &lt;a href="https://github.com/spechshop/libspech/blob/spech/README.md" rel="noopener noreferrer"&gt;https://github.com/spechshop/libspech/blob/spech/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;libspech (example.php): &lt;a href="https://github.com/spechshop/libspech/blob/spech/example.php" rel="noopener noreferrer"&gt;https://github.com/spechshop/libspech/blob/spech/example.php&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;pcg729 (runtime release "current/php"): &lt;a href="https://github.com/spechshop/pcg729/releases/tag/current" rel="noopener noreferrer"&gt;https://github.com/spechshop/pcg729/releases/tag/current&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;E agora?&lt;/strong&gt; 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! 🚀&lt;br&gt;
&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>voip</category>
      <category>opensource</category>
      <category>rtp</category>
    </item>
  </channel>
</rss>
