Phase 06 - Lesson 11

Procesamiento de Audio en Tiempo Real

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

Los pipelines por lotes procesan un archivo. Los pipelines en tiempo real procesan los proximos 20 milisegundos antes de que lleguen los siguientes 20. Toda IA conversacional, estudio de transmision y bot de telefonia vive o muere por este presupuesto de latencia.

Tipo: Build Lenguajes: Python, Rust Requisitos previos: Fase 6 · 02 (Espectrogramas), Fase 6 · 04 (ASR), Fase 6 · 07 (TTS) Tiempo: ~75 minutos

El Problema

Quieres un asistente de voz que se sienta vivo. La latencia humana de alternancia de turnos en una conversacion es de ~230 ms (del silencio a la respuesta). Cualquier cosa por encima de 500 ms suena robotica; por encima de 1500 ms parece rota. El presupuesto para un bucle completo de oir → entender → responder → hablar en 2026 es:

Etapa Presupuesto
Microfono → buffer 20 ms
VAD 10 ms
ASR (streaming) 150 ms
LLM (primer token) 100 ms
TTS (primer chunk) 100 ms
Renderizar → altavoz 20 ms
Total ~400 ms

Moshi (Kyutai, 2024) marco 200 ms en full-duplex. GPT-4o-realtime (2024) marca ~320 ms. Los pipelines en cascada en 2022 entregaban a 2500 ms. La mejora de 10x vino de tres tecnicas: (1) streaming en todas partes, (2) pipelining asincrono con resultados parciales, (3) generacion interrumpible.

El Concepto

Pipeline de audio en streaming con ring buffer, gate de VAD e interrupcion

Frame / chunk / ventana. El audio en tiempo real fluye como bloques de tamano fijo. Eleccion comun: 20 ms (320 muestras a 16 kHz). Todo lo que sigue debe seguir el ritmo de esta cadencia.

Ring buffer. Buffer circular de tamano fijo. El hilo productor escribe nuevos frames, el hilo consumidor lee. Evita asignaciones en el camino critico. Tamano ≈ latencia-maxima × tasa-de-muestreo; un ring de 2 segundos a 16 kHz = 32.000 muestras.

VAD (Deteccion de Actividad de Voz). Bloquea el trabajo posterior cuando nadie esta hablando. Silero VAD 4.0 (2024) corre en <1 ms por frame de 30 ms en CPU. webrtcvad es la alternativa mas antigua.

ASR en streaming. Modelos que emiten transcripciones parciales a medida que llega el audio. Parakeet-CTC-0.6B en modo streaming (NeMo, 2024) logra 2-5% de WER con 320 ms de latencia. Whisper-Streaming (Macháček et al., 2023) fragmenta Whisper para casi-streaming con latencia de ~2 s.

Interrupcion. Cuando el usuario habla mientras el asistente esta hablando, debes (a) detectar el barge-in, (b) detener el TTS, (c) descartar el resto de la salida del LLM. Todo dentro de 100 ms, o el usuario percibe un asistente sordo.

Transporte WebRTC Opus. Frames de 20 ms, 48 kHz, bitrate adaptativo de 8-128 kbps. Estandar para navegador y movil. LiveKit, Daily.co y Pion son los stacks de 2026 para construir apps de voz.

Jitter buffer. Los paquetes de red llegan fuera de orden / tarde. El jitter buffer reordena y suaviza; demasiado pequeno → huecos audibles, demasiado grande → latencia. 60-80 ms es lo tipico.

Trampas comunes

  • Contencion de hilos. El GIL de Python + modelos pesados pueden dejar sin recursos al hilo de audio. Usa una biblioteca de audio con callback en C (sounddevice, PortAudio) y manten a Python fuera del camino critico.
  • Latencia de conversion de tasa de muestreo. Remuestrear dentro del pipeline anade 5-20 ms. Remuestrea por adelantado o usa un remuestreador de latencia cero (PolyPhase, soxr_hq).
  • Priming del TTS. Incluso TTS rapidos como Kokoro tienen un calentamiento de 100-200 ms en la primera solicitud. Cachea el modelo y caliéntalo con una ejecucion ficticia antes del primer turno real.
  • Cancelacion de eco. Sin AEC, la salida del TTS reentra al microfono y dispara el ASR con la propia voz del bot. WebRTC AEC3 es el estandar open-source.

Construyelo

Paso 1: ring buffer

import collections

class RingBuffer:
    def __init__(self, capacity):
        self.buf = collections.deque(maxlen=capacity)
    def write(self, frame):
        self.buf.extend(frame)
    def read(self, n):
        return [self.buf.popleft() for _ in range(min(n, len(self.buf)))]
    def level(self):
        return len(self.buf)

La capacidad determina la latencia maxima de buffering. 32.000 muestras a 16 kHz = 2 s.

Paso 2: gate de VAD

def simple_energy_vad(frame, threshold=0.01):
    return sum(x * x for x in frame) / len(frame) > threshold ** 2

Reemplazalo por Silero VAD en produccion:

import torch
vad, _ = torch.hub.load("snakers4/silero-vad", "silero_vad")
is_speech = vad(torch.tensor(frame), 16000).item() > 0.5

Paso 3: ASR en streaming

# Parakeet-CTC-0.6B streaming via NeMo
from nemo.collections.asr.models import EncDecCTCModelBPE
asr = EncDecCTCModelBPE.from_pretrained("nvidia/parakeet-ctc-0.6b")
# chunk_ms=320 ms, look_ahead_ms=80 ms
for chunk in audio_stream():
    partial_text = asr.transcribe_streaming(chunk)
    print(partial_text, end="\r")

Paso 4: manejador de interrupcion

class Dialog:
    def __init__(self):
        self.tts_task = None

    def on_user_speech(self, frame):
        if self.tts_task and not self.tts_task.done():
            self.tts_task.cancel()   # barge-in
        # then feed to streaming ASR

    def on_final_user_utterance(self, text):
        self.tts_task = asyncio.create_task(self.reply(text))

    async def reply(self, text):
        async for tts_chunk in llm_then_tts(text):
            speaker.write(tts_chunk)

Depende de I/O asincrono y de streaming de TTS cancelable. El peerconnection.stop() de WebRTC sobre la pista de audio es la forma canonica.

Usalo

El stack de 2026:

Capa Eleccion
Transporte LiveKit (WebRTC) o Pion (Go)
VAD Silero VAD 4.0
ASR en streaming Parakeet-CTC-0.6B o Whisper-Streaming
LLM primer token Groq, Cerebras, vLLM-streaming
TTS en streaming Kokoro o ElevenLabs Turbo v2.5
Cancelacion de eco WebRTC AEC3
Nativo de extremo a extremo OpenAI Realtime API o Moshi

Errores Frecuentes

  • Bufferizar 500 ms por seguridad. El buffer es tu piso de latencia. Reducelo.
  • No fijar hilos. Callback de audio en un hilo de prioridad menor que la de la UI = glitches bajo carga.
  • Chunks de TTS demasiado pequenos. Chunks por debajo de 200 ms hacen audibles los artefactos del vocoder. Chunks de 320 ms son el punto ideal.
  • Sin jitter buffer. Las redes reales tienen jitter; sin suavizado obtienes chasquidos.
  • Manejo de errores de un solo intento. Los pipelines de audio deben ser a prueba de fallos. Una excepcion mata la sesion.

Entregalo

Guardalo como outputs/skill-realtime-designer.md. Disena un pipeline de audio en tiempo real con presupuestos de latencia concretos por etapa.

Ejercicios

  1. Facil. Ejecuta code/main.py. Simula un ring buffer + VAD de energia; imprime las latencias por etapa para un stream falso de 10 segundos.
  2. Medio. Usando sounddevice, construye un bucle de passthrough que procese tu microfono en frames de 20 ms e imprima el estado del VAD en cada frame.
  3. Dificil. Construye una prueba de eco full duplex con aiortc: navegador → WebRTC → Python → WebRTC → navegador. Mide la latencia glass-to-glass con un pulso de 1 kHz.

Terminos Clave

Termino Lo que dice la gente Lo que realmente significa
Ring buffer La cola circular FIFO de tamano fijo, lock-free (o con lock SPSC) para frames de audio.
VAD Gate de silencio Modelo o heuristica que marca habla vs no-habla.
ASR en streaming STT en tiempo real Emite texto parcial a medida que llega el audio; lookahead acotado.
Jitter buffer Suavizador de red Cola que reordena paquetes fuera de orden; 60-80 ms tipico.
AEC Cancelacion de eco Resta el camino de retroalimentacion del altavoz al microfono.
Barge-in Interrupcion del usuario El sistema detecta el habla del usuario en mitad del TTS; debe cancelar la reproduccion.
Full duplex Simultaneo en ambos sentidos El usuario y el bot pueden hablar al mismo tiempo; Moshi es full duplex.

Lecturas Adicionales

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).