Bug: model.generate(input=bytes) hangs for minutes on raw PCM chunks that trigger audio container false-positive
#3739 · open · 1 comments
View on GitHub ↗
## 🐛 Bug Report: `model.generate(input=bytes)` 在处理流式原始 PCM 时长时间 hang
### 版本
- **FunASR**: 1.4.0
- **Python**: 3.10.12
- **PyTorch**: 1.12.0
- **OS**: Centos7.9
### 场景
FSMN VAD 模型做流式推理:每 200ms 收到一个 int16 PCM chunk(6400 字节,16kHz mono),调用 `AutoModel.generate()`。
### 调用方式
```python
from funasr import AutoModel
model = AutoModel(model="iic/speech_fsmn_vad_zh-cn-16k-common-pytorch", disable_update=True)
# 每 200ms 一帧,pcm_chunk 是原始 int16 PCM bytes,6400 字节(16kHz x 0.2s x 2)
cache = {}
for pcm_chunk in pcm_chunks:
result = model.generate(
input=pcm_chunk, # bytes, 原始 int16 PCM, 6400 字节
cache=cache,
chunk_size=200,
disable_pbar=True,
)
```
### 现象
服务稳定运行数周后,某次处理中 `generate()` 调用**hang 约三分钟**不返回。期间该路会话的所有后续音频帧无法处理。三分钟后抛出异常:
```
Failed to decode container-formatted audio bytes.
Verify that the input is a complete supported audio file and that torchaudio,
soundfile, or ffmpeg is available.
```
通常 `generate()` 对一个 200ms chunk 的推理只需 10-20ms。hang 的持续时间远超正常推理时间。
### 补充信息
- FunASR 版本 1.4.0(`pip show funasr` 输出)
- 输入是原始 int16 PCM bytes,不是 WAV/MP3 等容器格式文件
- 该错误低频出现(数周一次),未找到稳定复现方式
---
> 以下是 AI(Claude)辅助分析的推测,供参考:
### AI 推测的可能原因
1. **`_is_audio_container` 误判**:`load_utils.py` 中 `load_bytes()` 对所有 `bytes` 输入调用 `_is_audio_container()` 进行容器格式检测。200ms int16 PCM chunk(6400 字节)有一定概率命中 MPEG 帧头假阳性(`data[0] == 0xFF and (data[1] & 0xE0) == 0xE0`),随后 `_has_consecutive_mp3_frames()` 在随机 PCM 数据中有非零概率通过三层帧头校验。
2. **误判后 torchaudio 陷入长时间错误恢复**:一旦判定为容器格式,`load_bytes()` 调用 `load_audio_text_image_video(BytesIO(input), fs=16000)`,torchaudio 被喂入一个 6400 字节的"假 MP3 文件",内部错误恢复路径反复尝试重新同步,持续数分钟后才放弃。
(以上推测基于对 FunASR 1.4.1 `funasr/utils/load_utils.py` 和 `funasr/auto/auto_model.py` 源码的阅读,尚未通过构造特定 PCM 数据复现验证。)
Comments
感谢把实测现象与 AI 推测分开说明。先提供一个适合你这条 **16 kHz、单声道、signed int16 little-endian 原始 PCM** 输入链路的绕过方法:把每帧显式转换为归一化 `float32` 波形,再交给 `generate()`,不要让无头 PCM 字节参与文件格式自动探测。
保留原来的 `model`、每会话的 `cache` 和流结束处理,仅替换传入的数据:
```python
import numpy as np
# pcm_chunk: one complete PCM chunk, 6400 bytes for your 200 ms input
if len(pcm_chunk) % 2:
raise ValueError("PCM16 chunk must contain complete 2-byte samples")
speech = np.frombuffer(pcm_chunk, dtype="<i2").astype(np.float32) / 32768.0
result = model.generate(
input=speech,
cache=cache,
chunk_size=200,
disable_pbar=True,
)
```
这段只解释已知格式,**不做重采样**。不要用于 WAV/MP3 文件字节、其他位深/字节序或多声道数据;网络分片若切断了一个采样点,应先在收包层拼完整。不要每帧重建 `cache`;会话结束仍按原有流式 VAD 的 `is_final=True` 流程收尾。
维护侧已核对并做了无模型验证:
- [当前 `prepare_data_iterator`](https://github.com/modelscope/FunASR/blob/e9d002b54d0ba0592797d13d6ffd742c7d41ca75/funasr/auto/auto_model.py#L451-L519) 对直接传入的 `bytes` 调用 `load_bytes()`,而 NumPy 波形不经过这个探测入口。
- 用一个 6400 字节的**合成** PCM-compatible 序列构造了 MPEG 帧头特征,确认它能进入容器解码分支。此处把解码调用替换为立即抛出受控异常的边界探针,**没有复现或声称复现真实解码器的三分钟 hang**。
- 对同一序列执行上面的转换,再经过实际的数据准备和音频加载函数,得到 3200 个 float32 样本,数值逐项保持一致,torchaudio/soundfile/ffmpeg 的解码入口均未调用;另有 int16 正负边界值校验。仓库已有音频字节测试 14 项通过。
因此,自动探测发生歧义在原理上可以构造,但还不能据此断言你那次卡顿发生在 torchaudio;[当前文件读取实现](https://github.com/modelscope/FunASR/blob/e9d002b54d0ba0592797d13d6ffd742c7d41ca75/funasr/utils/load_utils.py#L116-L146) 还有 soundfile/ffmpeg 后备路径。这些验证针对当前源码,不是你的 FunASR 1.4.0 / torch 1.12.0 环境复现。
请先用显式波形输入观察是否还会出现同类停顿。进一步定位需要完整异常链(含 `__cause__`)、实际导入的 `funasr.__file__`、torchaudio/soundfile 版本及 ffmpeg 版本;若能在受控复现中取得卡顿时的线程栈,也很有帮助。路径可打码,初步排查不需要公开业务录音。先保留 issue,区分“已验证的输入绕过路径”和“仍待定位的原始卡顿”。
---
源码进展(2026-10-02):[PR #3751](https://github.com/modelscope/FunASR/pull/3751) 已合入 main。对于已知的单声道、signed int16 little-endian 原始 PCM,现在可以显式传入 `input_format="pcm_s16le"`,绕过容器自动探测。对你这条 16 kHz 输入链路,在原来的调用中增加 `input_format="pcm_s16le", fs=16000` 即可;保留每会话的 `cache`、分块参数和结束处理。
**这是源码新增能力,尚未作为新的 PyPI 版本发布。** 不要假定原有 FunASR 1.4.0 已支持这个参数;旧安装继续使用上面的 NumPy 波形绕过方法。
已验证显式 PCM 路径不调用容器探测/解码器,并补上 VAD 非 16 kHz 输入及批量输入的采样率传递回归。两条托管 NumPy CI 均执行 93 项测试,0 失败、0 跳过;验证不涉及真实模型推理。**原始三分钟卡顿仍未复现或确定根因**,因此保留本 issue,等待原场景的观察结果和诊断信息。