FunASR 实时语音识别total_chunk_num括号语法修复指南(Issue #2720)
【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR
导读
本文围绕 FunASR 开源语音识别工具包中实时(流式)语音识别示例里的一处括号配对语法错误展开,逐层讲解问题现场、正确的total_chunk_num分块计算公式、表达式求值顺序,以及该计算在流式 ASR 推理链路中的实际作用。读完本文,你将能够识别并修复仓库内所有受影响的示例文件,理解chunk_stride与chunk_size的对应关系,并能独立验证修复后的分块推理是否正常工作。
问题现场:Issue #2720 报告的语法错误
在 FunASR 的实时语音识别示例中,计算音频分块总数total_chunk_num时存在一处括号不配对的语法错误:
# INCORRECT(存在语法错误的写法) total_chunk_num = int(len(speech)-1)/chunk_stride+1)该行存在左右括号不匹配:int(只写了一个左括号,却在表达式末尾多了一个右括号)。Python 解释器在解析这一行时会直接抛出SyntaxError,导致整个实时语音识别脚本无法运行。
正确写法如下:
# CORRECT(修复括号后的写法) total_chunk_num = int((len(speech)-1)/chunk_stride+1)修复的本质是在int(之后补上第二个左括号,将除法运算(len(speech)-1)/chunk_stride+1整体包在int(...)内部,再进行取整转换。
表达式求值顺序:为什么要多一层括号
修复后的表达式保证算术运算按以下顺序执行:
- 计算
len(speech)-1,得到音频总采样点数减 1; - 将结果除以
chunk_stride(单个分块的采样点数),得到理论分块数; - 加 1(向上取整的兜底,确保最后不足一个分块的尾部音频也能被覆盖);
- 用
int(...)把结果转换为整数。
向上取整的数学含义
音频总长度不一定能被chunk_stride整除,例如一段 1000 个采样点的音频,若chunk_stride=960,则(1000-1)/960+1 ≈ 2.04,取整后得到 2,即需要 2 个分块:第一个分块取speech[0:960],第二个分块取剩余的speech[960:1000]。-1的作用是处理恰好整除的边界情况,避免多算一个空分块。
若保持错误的写法,Python 会先执行int(len(speech)-1)(先对长度减 1 后的值取整,这一步通常没问题),随后再执行/chunk_stride+1),此时末尾的)没有与之匹配的左括号,直接触发SyntaxError——脚本在任何generate调用之前就崩溃了。
该计算在流式推理链路中的真实作用
total_chunk_num不是孤立的一行代码,它是 FunASR 流式 ASR「分块滑动推理」循环的入口条件。以 paraformer_streaming/demo.py 为例,完整的流式推理流程为:
from funasr import AutoModel import soundfile, os chunk_size = [0, 10, 5] # [0, 10, 5] 600ms, [0, 8, 4] 480ms encoder_chunk_look_back = 4 # encoder 自注意力回看的分块数 decoder_chunk_look_back = 1 # decoder 交叉注意力回看的分块数 model = AutoModel(model="iic/speech_paraformer_asr_nat-zh-cn-16k-common-vocab8404-online") wav_file = os.path.join(model.model_path, "example/asr_example.wav") speech, sample_rate = soundfile.read(wav_file) chunk_stride = chunk_size[1] * 960 # 600ms cache = {} total_chunk_num = int((len(speech) - 1) / chunk_stride + 1) for i in range(total_chunk_num): speech_chunk = speech[i * chunk_stride : (i + 1) * chunk_stride] is_final = i == total_chunk_num - 1 res = model.generate( input=speech_chunk, cache=cache, is_final=is_final, chunk_size=chunk_size, encoder_chunk_look_back=encoder_chunk_look_back, decoder_chunk_look_back=decoder_chunk_look_back, ) print(res)关键点说明:
chunk_stride = chunk_size[1] * 960:16kHz 采样率下 60ms 对应 960 个采样点。chunk_size=[0,10,5]表示上屏实时出字粒度为10*60=600ms,未来信息(lookahead)为5*60=300ms,因此chunk_stride = 10*960 = 9600个采样点,即每个分块 600ms 音频。cache字典:跨分块携带编码器/解码器的隐状态,实现真正的流式增量推理,而不是每次重新处理整段音频。is_final标志:最后一个分块必须设置is_final=True,强制模型输出最后一个字的识别结果。
这一循环模式同样出现在 scama/demo.py(SCAMA 流式模型)与 fsmn_vad_streaming/demo.py(流式 VAD)中,是 FunASR 流式推理的统一范式。
底层实现印证
分块语义在模型实现中有直接对应的逻辑。在 paraformer_streaming/model.py 中:
chunk_size = kwargs.get("chunk_size", [0, 10, 5]) chunk_stride_samples = int(chunk_size[1] * 960) # 600ms模型内部同样以chunk_size[1] * 960计算采样点级别的步长,随后对传入的audio_sample按chunk_stride_samples切块,并在最后不足一个分块且is_final=True时通过cache["encoder"]["tail_chunk"] = True触发尾部强制输出(见 model.py)。由此可见,示例脚本中的total_chunk_num与模型内部的分块逻辑完全一致,二者共同保证了「一次一个 600ms 分块、逐步增量输出」的流式行为。
受影响的文件与修复清单
原文档指出,包含该total_chunk_num计算、需要检查修复的文件包括:
- examples/industrial_data_pretraining/scama/demo.py(约第 34 行)
examples/wenetSpeech/realtime_demo.py(如存在)runtime/triton_gpu/client/speech_client.py(如适用)
结合当前仓库的实际检索结果,仓库中所有包含total_chunk_num的示例与文档均已采用正确的括号配对写法,例如:
- paraformer_streaming/demo.py:
total_chunk_num = int((len(speech) - 1) / chunk_stride + 1) - scama/demo.py:
total_chunk_num = int(len((speech) - 1) / chunk_stride + 1) - fsmn_vad_streaming/demo.py:
total_chunk_num = int(len((speech) - 1) / chunk_stride + 1)
注意:
int(len((speech) - 1) / ...)与int((len(speech) - 1) / ...)在len(speech)为整数时求值结果一致,都属于合法且等价的写法。真正需要警惕的只有int(len(speech)-1)/chunk_stride+1)这种多出右括号的错误形式。
- paraformer/README.md、paraformer/README_zh.md、paraformer_streaming/README_zh.md、paraformer-zh-spk/README_zh.md 等教程文档中同样为正确写法。
若在你的本地分支中发现旧版本代码仍带有错误的括号写法,请统一替换为:
total_chunk_num = int((len(speech)-1)/chunk_stride+1) # CORRECT修复后的验证方法
应用修复后,建议按以下步骤验证:
- 静态检查:运行
python -m py_compile <demo.py>或直接执行脚本,确认不再抛出SyntaxError。 - 分块数正确性:用一个已知长度的音频手工核对,例如 16kHz 音频、
chunk_stride=9600(600ms)时,10 秒音频应得到int((160000-1)/9600+1) = 17个分块;确认打印的循环次数与预期一致。 - 端到端流式推理:运行
examples/industrial_data_pretraining/paraformer_streaming/demo.py,观察每个分块逐步输出识别文本,且最后一个分块(is_final=True)能完整输出句尾内容,无报错。 - 流式 VAD 验证:运行 fsmn_vad_streaming/demo.py,确认输出为
[[beg, end]]形式的时间区间(单位 ms),且存在[[beg, -1]]、[[-1, end]]等边界输出形态。
常见误区与排查建议
- 不要把
-1挪到int()外面:int(len(speech)-1)/chunk_stride+1虽然语法合法,但会把取整结果转成浮点数再参与除法,返回float而非int,随后range(total_chunk_num)会因非整数参数报TypeError。正确做法是让整个除法+加 1 的表达式处于int()内部。 - 括号风格要统一:仓库内同时存在
int(len((speech)-1)/...)与int((len(speech)-1)/...)两种写法,二者等价,迁移代码时不必强制改写,但应避免产生括号不配对的新变体。 chunk_stride的单位换算:ASR 示例中960是 16kHz 下 60ms 的采样点数;流式 VAD 示例(chunk_size=200ms)则用int(chunk_size * sample_rate / 1000)计算步长(见 fsmn_vad_streaming/demo.py)。修改sample_rate或chunk_size时必须同步核对步长换算。
小结
total_chunk_num的一处括号错误看似微小,却会让所有实时语音识别示例在运行前直接崩溃,是典型的「一行代码阻塞整条链路」问题。本文给出的修复方案以int((len(speech)-1)/chunk_stride+1)为最终形式,并解释了其背后的向上取整语义、chunk_stride与chunk_size的换算关系,以及该表达式在 paraformer_streaming/model.py 底层实现中的对应逻辑。按照本文的修复清单与验证步骤,即可让流式 ASR、流式 VAD 等实时示例恢复可用,同时规避float与TypeError等衍生陷阱。
【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考