Phase 06 - Lesson 14
Detecção de Atividade de Voz e Alternância de Turnos — Silero, Cobra e o Truque do Flush
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Todo agente de voz vive ou morre por duas decisões: o usuário está falando agora e ele terminou? A VAD responde à primeira. A detecção de turno (VAD + carência de silêncio + modelo de endpoint semântico) responde à segunda. Erre qualquer uma delas e seu assistente ou corta o usuário no meio da fala ou nunca para de falar.
Tipo: Build Linguagens: Python Pré-requisitos: Fase 6 · 11 (Áudio em Tempo Real), Fase 6 · 12 (Assistente de Voz) Tempo: ~45 minutos
O Problema
Três decisões distintas que um agente de voz toma a cada bloco de 20 ms:
- Este quadro é fala? — VAD. Binária, por quadro.
- O usuário começou um novo enunciado? — detecção de início.
- O usuário terminou? — end-pointing (fim de turno).
A resposta ingênua (limiar de energia) falha em qualquer ruído — trânsito, teclados, balbúrdia de multidão. A resposta de 2026: Silero VAD (aberta, baseada em deep learning) + um modelo de detecção de turno (endpointing semântico) + uma carência de silêncio calibrada pela VAD.
O Conceito
A cascata de VAD em três camadas
Camada 1: portão de energia. A mais barata. Aplica limiar de RMS em -40 dBFS. Filtra silêncio óbvio, mas dispara em qualquer ruído acima do limiar.
Camada 2: Silero VAD (2020-2026, MIT). 1 milhão de parâmetros. Treinada em mais de 6000 idiomas. Roda em ~1 ms por bloco de 30 ms em uma única thread de CPU. 87,7% de TPR a 5% de FPR. O padrão de código aberto.
Camada 3: detector semântico de turno. O modelo de detecção de turno da LiveKit (2024-2026) ou seu próprio classificador pequeno. Distingue "pausa no meio da frase" de "terminou de falar". Usa contexto linguístico (entonação + palavras recentes), não apenas silêncio.
Parâmetros-chave e seus padrões
- Limiar. A Silero produz uma probabilidade; classifique como fala em > 0.5 (padrão) ou > 0.3 (sensível). Limiar mais baixo = menos cortes da primeira palavra, mais falsos positivos.
- Duração mínima de fala. Rejeite fala mais curta que 250 ms — geralmente tosses ou ruído de cadeira.
- Carência de silêncio (end-pointing). Após a VAD retornar a 0, espere 500-800 ms antes de declarar o fim do turno. Curta demais → interrompe o usuário. Longa demais → parece lenta.
- Buffer de pre-roll. Mantenha 300-500 ms de áudio antes de a VAD disparar. Evita que o "ei" seja cortado.
O truque do flush (Kyutai 2025)
Modelos de STT em streaming têm um atraso de look-ahead (500 ms para o Kyutai STT-1B, 2,5 s para o STT-2.6B). Normalmente você esperaria todo esse tempo após o fim da fala para obter a transcrição. Truque do flush: quando a VAD dispara o fim da fala, envie um sinal de flush ao STT que força a saída imediata. O STT processa a ~4× tempo real, então o buffer de 500 ms termina em ~125 ms.
Ponta a ponta: 125 ms de VAD + flush do STT = latência conversacional.
Comparação de VAD em 2026
| VAD | TPR @ 5% FPR | Latência | Termos |
|---|---|---|---|
| WebRTC VAD (Google, 2013) | 50,0% | 30 ms | BSD |
| Silero VAD (2020-2026) | 87,7% | ~1 ms | MIT |
| Cobra VAD (Picovoice) | 98,9% | ~1 ms | comercial |
| pyannote segmentation | 95% | ~10 ms | tipo MIT |
A Silero é o padrão certo. A Cobra é o upgrade de conformidade / precisão. VAD baseada apenas em energia não tem lugar na produção de 2026.
Construa
Passo 1: o portão de energia
def energy_vad(chunk, threshold_dbfs=-40.0):
rms = (sum(x * x for x in chunk) / len(chunk)) ** 0.5
dbfs = 20.0 * math.log10(max(rms, 1e-10))
return dbfs > threshold_dbfs
Passo 2: Silero VAD em Python
from silero_vad import load_silero_vad, get_speech_timestamps
vad = load_silero_vad()
audio = torch.tensor(waveform_16k, dtype=torch.float32)
segments = get_speech_timestamps(
audio, vad, sampling_rate=16000,
threshold=0.5,
min_speech_duration_ms=250,
min_silence_duration_ms=500,
speech_pad_ms=300,
)
for s in segments:
print(f"{s['start']/16000:.2f}s - {s['end']/16000:.2f}s")
Passo 3: máquina de estados de fim de turno
class TurnDetector:
def __init__(self, silence_hangover_ms=500, min_speech_ms=250):
self.state = "idle"
self.speech_ms = 0
self.silence_ms = 0
self.silence_hangover_ms = silence_hangover_ms
self.min_speech_ms = min_speech_ms
def update(self, is_speech, chunk_ms=20):
if is_speech:
self.speech_ms += chunk_ms
self.silence_ms = 0
if self.state == "idle" and self.speech_ms >= self.min_speech_ms:
self.state = "speaking"
return "START"
else:
self.silence_ms += chunk_ms
if self.state == "speaking" and self.silence_ms >= self.silence_hangover_ms:
self.state = "idle"
self.speech_ms = 0
return "END"
return None
Passo 4: o esqueleto do truque do flush
def flush_on_end(stt_client, audio_buffer):
stt_client.send_audio(audio_buffer)
stt_client.send_flush()
return stt_client.recv_transcript(timeout_ms=150)
O STT (Kyutai, Deepgram, AssemblyAI) precisa suportar flush para que isso funcione. O Whisper em streaming não suporta — ele é baseado em blocos e sempre espera os blocos.
Use
| Situação | Escolha de VAD |
|---|---|
| Aberta, rápida, geral | Silero VAD |
| Call center comercial | Cobra VAD |
| No dispositivo (telefone) | Silero VAD ONNX |
| Pesquisa / diarização | pyannote segmentation |
| Fallback sem dependências | WebRTC VAD (legado) |
| Precisa de qualidade no fim de turno | Silero + detector de turno da LiveKit em camadas |
Regra prática: nunca coloque em produção VAD baseada apenas em energia, a menos que você realmente não tenha outra opção.
Armadilhas
- Limiar fixo. Funciona no silêncio, falha no ruído. Calibre no dispositivo ou troque pela Silero.
- Carência de silêncio curta demais. O agente interrompe no meio da frase. 500-800 ms é o ponto ideal para fala conversacional.
- Carência longa demais. Parece lenta. Faça testes A/B com usuários-alvo.
- Sem buffer de pre-roll. Os primeiros 200-300 ms do áudio do usuário se perdem. Sempre mantenha um pre-roll rolante.
- Ignorar o endpointing semântico. "Hmm, deixa eu pensar..." contém pausas longas. Usuários odeiam ser cortados no meio do pensamento. Use o detector de turno da LiveKit ou similar.
Entregue
Salve como outputs/skill-vad-tuner.md. Escolha o modelo de VAD, o limiar, a carência, o pre-roll e a estratégia de detecção de turno para uma carga de trabalho.
Exercícios
- Fácil. Rode
code/main.py. Ele simula uma sequência de fala + silêncio + fala + tosses e testa três camadas de VAD. - Médio. Instale o
silero-vad, processe uma gravação de 5 min, ajuste o limiar para minimizar tanto os cortes da primeira palavra quanto os disparos falsos. Reporte precisão/recall. - Difícil. Construa um mini detector de turno: Silero VAD + uma MLP de 3 camadas sobre os embeddings das últimas 10 palavras (use sentence-transformers). Treine em um dataset de fim de turno rotulado à mão. Supere a Silero sozinha em 10% de F1.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| VAD | Detector de voz | Binário por quadro: isto é fala? |
| Detecção de turno | End-pointing | VAD + carência de silêncio + endpoint semântico. |
| Carência de silêncio | Espera após a fala | Tempo a esperar antes de declarar o fim do turno; 500-800 ms. |
| Pre-roll | Buffer pré-fala | Mantenha 300-500 ms de áudio antes de a VAD disparar. |
| Truque do flush | Hack da Kyutai | VAD → flush do STT → 125 ms em vez de 500 ms de atraso. |
| Endpoint semântico | "Eles quiseram parar?" | Classificador de ML que olha para as palavras, não apenas o silêncio. |
| TPR @ FPR 5% | Ponto na ROC | Benchmark padrão de VAD; 87,7% para a Silero, 50% para o WebRTC. |
Leitura Complementar
- Silero VAD — a VAD aberta de referência.
- Picovoice Cobra VAD — líder comercial em precisão.
- Kyutai — Unmute + truque do flush — o truque de engenharia sub-200 ms.
- LiveKit — detecção de turno — endpointing semântico em produção.
- WebRTC VAD — a linha de base legada.
- pyannote segmentation — segmentação em nível de diarização.