SpeakingRateStream blocks the event loop: per-frame Python STFT loop in `_stft` / `_spectral_flux`
维护者通常 1 天内回复
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 35/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 停滞
- 技术栈
- python
调研方向
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.
由索引模型根据 Issue 内容生成。
描述
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.
- 主要语言
- Python
- 星标
- 14.6k
- 派生
- 3.9k
- 平均合并
- 3 天
- 30 天内合并 PR
- 262
环境准备
这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
livekit/agents 的其他 Issue
-
enhancement
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 62/100
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 78/100
维护者通常 1 天内回复
-
ci: unit tests pin Python 3.12 while the package advertises 3.14 support可能重新可做 @huong8373tt-beep 于 9 天前认领,目前没有进行中的 PR。 未关闭
难度 2/5 1-3 小时 新手友好度 74/100
维护者通常 1 天内回复
-
Assistant markup is stripped from chat history when expressive mode is off可能已有人在做 @ShivamSharma43 于 30 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
相似的 Issue
-
ChatBedrockConverse changes the keys of JSON tool results to camelCase (`order_id` becomes `orderId`)可能已有人在做 @geyazheng 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
langchain-ai/langchain-aws#1469 ·
维护者通常 1 天内回复
-
GCToggler leaves collection enabled if the body re-enables it可能已有人在做 @Gambit-Checkmate 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 7 天内回复
-
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
doronz88/pymobiledevice3#2176 ·
维护者通常 1 天内回复
-
b4b usability
难度 2/5 1-3 小时 新手友好度 68/100
维护者通常 1 天内回复