SpeakingRateStream blocks the event loop: per-frame Python STFT loop in `_stft` / `_spectral_flux`
Los mantenedores suelen responder en 1 día
@omerekin ya está trabajando en esto.
Desde el 17/10/2026.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 35/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Estancado
- Stack tecnológico
- python
- Área
- audio-video-rtc, performance
Línea de trabajo
Locate SpeakingRateStream._stft and _spectral_flux, then trace audio draining through _main_task. Run the existing speaking-rate tests and event-loop timing checks, comparing spectral-flux output and loop-blocking measurements before and after the change. Done means the calculation is vectorized without changing results and queued TTS audio no longer causes the measured stalls.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
TranscriptSynchronizer always runs SpeakingRateDetector on the agent's TTS audio, on the job's event loop. SpeakingRateStream._stft computes the STFT one frame at a time in a Python loop (about 80 np.fft.rfft calls per 1 s window at 24 kHz), and _spectral_flux loops over the frames again. Because RoomIO's _ParticipantAudioOutput.capture_frame queues TTS audio without pacing, _main_task drains a whole lump of a reply (10 windows per second of audio) without yielding.
Measured on livekit-agents 1.8.2, numpy 2.5.2, Apple M3 (the code is unchanged in 1.8.8, 2.0.0b1 and main):
- 0.61 ms per window;
_main_taskdraining 20 s of queued 24 kHz audio holds the loop ~123 ms (2 s: 7.6 ms, 5 s: 26 ms, 10 s: 58 ms). - In a local test conversation driven by synthetic speech, with
LIVEKIT_AGENTS_LOOP_BLOCK_WARN_MS=50: 4-5 loop blocks of 59-131 ms per run, a few seconds into each agent turn. With the 200 ms output buffer, stalls that size can be audible, and on smaller cloud machines they'd be roughly twice as long.
The same computation vectorized gives a bit-identical result (480 random cases across 6 sample rates and 4 lengths) and is ~3.8x faster (0.16 ms per window; 20 s of queued audio holds the loop ~38 ms). It replaces _stft + _spectral_flux:
def _spectral_flux(
self, audio: np.ndarray[tuple[int], np.dtype[np.float32]], sample_rate: int
) -> float:
frame_length = int(sample_rate * 0.025) # 25ms
hop_length = frame_length // 2 # 50% overlap
num_frames = (len(audio) - frame_length) // hop_length + 1
if num_frames < 2:
return 0.0
window = np.hanning(frame_length)
scale_factor = 1.0 / np.sqrt(np.sum(window**2))
frames = np.lib.stride_tricks.sliding_window_view(audio, frame_length)[::hop_length]
magnitudes = np.abs(np.fft.rfft(frames * window, axis=1) * scale_factor)
return float(np.mean(np.sum(np.abs(np.diff(magnitudes, axis=0)), axis=1)))
It might also be worth yielding (await asyncio.sleep(0)) between windows in _main_task, so a long lump never holds the loop, whatever the per-window cost.
Happy to open a PR with this if it's useful.
- Lenguaje dominante
- Python
- Estrellas
- 14.6k
- Forks
- 3.9k
- Merge medio
- 3 d 18 h
- PR fusionados (30 d)
- 193
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de livekit/agents
-
Google TTS: default ssml_gender NEUTRAL is rejected for Neural2 voicesPosiblemente ocupada @sarankumar1325 la tomó hace 1 día. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
Assistant markup is stripped from chat history when expressive mode is offPosiblemente ocupada @ShivamSharma43 la tomó hace 13 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
docs: openai.LLM vs openai.responses.LLM for HTTP OpenAI-compat hostsQuizá libre de nuevo @AbarnaaSree la tomó hace 52 días y no hay ningún pull request abierto. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
livekit/agents#7022 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
google realtime (Gemini Live): manual activity detection — barge-in fires on the model's own generation; unclosed transcript-only generation blocks later generate_replyPosiblemente ocupada @akashhguptaa la tomó hoy. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 30/100
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
Los mantenedores suelen responder en 1 día
Todos los issues de livekit/agents
Issues similares
-
[Bug]: `hermes plugins update` clones an entire monorepo twice - `subdir` is resolved but dropped at both catalog-update call sitesPosiblemente ocupada @HootRock la tomó hoy. Abiertocomp/cli P2 type/perf
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
NousResearch/hermes-agent#142417 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
bug enhancement python
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
DewaldOosthuizen/tech-cheat-sheets-and-notes#392 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
Los mantenedores suelen responder en 1 día
-
bug p2
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
amd/gaia#5471 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Restore postgresql_vm_test_charm actions.yaml change (removed in canonical/postgresql-operator#2054)Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
canonical/postgresql-single-kernel-library#390 · 1 comentario ·
Los mantenedores suelen responder en 2 días