音频能推上去、连接也建立了,但转写回来是空串或乱码,多数情况不是网络和密钥的问题,而是推流前的音频参数与接收端约定对不上:编码或容器解不开,采样率与声道数被重采样成不可用状态,或者分片切得太碎,每一片都不带可识别的语音上下文。接入 MAI-Transcribe-2-Streaming 之前,建议先按下面四项把输入条件固定下来,再动业务代码。
判断方向:先怀疑输入音频本身,再怀疑网络与鉴权。操作上,用 ffprobe 或客户端日志确认 codec_name、sample_rate、channels 三个字段与接入要求一致,不一致就在推流前本地转码;分片保持在能容纳一句完整语音的长度。验证方式是拿一段 5~10 秒的单句音频跑端到端,看返回文本是否非空且内容对得上。边界在于:具体支持的编码、采样率与分片上限,以你所接入环境的文档和返回码为准,本文给的是通用排查顺序。
确认推流编码与容器格式是否被接收端接受
流式转写接口对输入格式的容忍度通常比离线接口低。离线场景上传一个 mp3,服务端可以自己解码;流式场景是按帧持续喂数据,解码器往往只认少数几种编码,甚至只认不带文件头的裸 PCM。所以第一步不是调参数,而是把“我推的到底是什么”确认清楚。
常见的几种组合可以先对照:裸 PCM(s16le,16bit 小端,无文件头)最常见,但需要随连接声明采样率和声道;WAV 是 PCM 加了 44 字节头,有的接口会把这个头当成音频样本导致开头一段噪声;Ogg/Opus 体积小、延迟低,适合带宽受限的推流;AAC(ADTS)和 MP3 属于带帧同步字的压缩流,能否直接喂取决于接收端解码器;WebM 这类容器在流式场景里通常不建议直接推,需要先解封装。
检查命令建议固定成一条,随手就能跑:
ffprobe -v error -show_entries stream=codec_name,codec_type,sample_rate,channels,sample_fmt \
-show_entries format=format_name,duration \
-of default=noprint_wrappers=1 input.mp3
如果 ffprobe 直接报 unknown format,说明你拿到的是裸流,要手动指定参数再解析,例如 ffprobe -f s16le -ar 16000 -ac 1 out.pcm。另一个快速判断是看文件头字节:xxd -l 64 out.bin,开头出现 RIFF 说明是 WAV,出现 OggS 是 Ogg,出现 ID3 或 0xFFFB 是 MP3,什么都没有基本就是裸 PCM。
核对采样率与声道数
采样率不匹配的典型表现不是报错,而是“有结果但很离谱”:语速被拉快拉慢、音调变尖、识别成完全无关的句子。声道数不对则更隐蔽,双声道立体声被当成单声道交错读取,左右声道样本混在一起,听起来正常但转写为空或乱码。转写类服务多数以 16000 Hz 单声道为基准,部分场景接受 8000 Hz 或 48000 Hz,但不保证质量,需要结合你接入环境的说明确认。
| 参数 | 常见取值 | 怎么确认 | 不一致的典型表现 |
|---|---|---|---|
| 采样率 | 16000 Hz | ffprobe 的 sample_rate 字段 | 语速异常、整句识别错位 |
| 声道数 | 1(单声道) | ffprobe 的 channels 字段 | 转写为空、内容乱码 |
| 采样位深/格式 | 16bit 有符号(s16 / s16le) | sample_fmt 字段 | 持续白噪声、音量爆表 |
| 帧长 | 20 ms 一帧较常见 | 客户端切帧逻辑 | 丢帧、断句频繁 |
如果源音频不满足,建议在推流前统一转一次,而不是把原始文件直接推上去赌接收端能兼容:
# 裸 PCM:16kHz / 单声道 / 16bit 小端
ffmpeg -i input.mp3 -vn -ac 1 -ar 16000 -sample_fmt s16 -f s16le out.pcm
# 需要带容器头时
ffmpeg -i input.mp3 -vn -ac 1 -ar 16000 -c:a pcm_s16le out.wav
ffmpeg -i input.mp3 -vn -ac 1 -ar 16000 -c:a libopus -b:a 24k out.ogg
转码后再用 ffprobe 复查一遍,确认输出的 sample_rate 和 channels 确实是目标值,不要只看命令没报错就认为成功了。
检查分片大小与推送间隔
分片是流式转写里最容易被忽略的一项。分片太大,首字结果出得慢,端到端看着像超时;分片太小,每片只有几十毫秒,模型拿不到连续上下文,容易出现空结果、断词或重复。一个保守的起点是按 100~500 毫秒一片推送,帧长维持 20 毫秒左右,并且在两片之间维持稳定的发送节奏,不要一口气把整段音频灌进去再等回复。
# 本地切分,便于按片推送和复现
ffmpeg -i input.wav -f segment -segment_time 0.5 -c copy chunk_%03d.wav
判断分片是否合理,靠日志而不是靠感觉。建议每次推送记一行结构化日志,至少包含这些字段:
{"session_id":"s-01","chunk_index":7,"chunk_bytes":16000,
"duration_ms":500,"codec":"pcm_s16le","sample_rate":16000,
"channels":1,"send_ts":0,"ack_ts":0,"text_len":0}
重点看三处:chunk_bytes 与 duration_ms 是否成比例(不成比例说明采样参数算错了);ack_ts 与 send_ts 的差值是否持续变大(差值递增通常是接收端处理不过来);text_len 是否长时间为 0 而中间偶尔冒出一两个字符(上下文断在分片边界上)。
用短音频做端到端连通验证
在接业务逻辑之前,先拿一段尽量简单的素材跑通一次:5~10 秒、单人、无背景音乐、开头不要留空白、内容是你自己知道的一段话。这段音频的作用是把“链路通不通”和“格式对不对”两个问题分开。
- 本地用 ffprobe 确认素材是目标编码、16000 Hz、单声道。
- 按上一步的转码命令生成 out.pcm,并确认文件大小约等于 采样率 × 2 字节 × 秒数。
- 按固定分片推完整段,不重试、不丢片,记录每片的日志。
- 比对返回文本与原始内容,检查是否完整、有没有整段为空。
预期输出是:每一片或每个结果事件都带非空文本,拼起来的句子与素材内容基本一致,首字延迟稳定在一个可接受的范围内,且没有明显的重复或截断。
如果验证失败,建议按这个顺序定位,不要跳步:先确认素材本地能正常解码播放;再看编码与容器是否在接收端支持范围内;然后核对采样率与声道数;接着检查分片长度与推送间隔是否触发上下文断裂;最后才回头看会话参数、鉴权头和返回码。这个顺序的好处是每一步都能用一个命令或一行日志证伪,不会在“感觉是网络问题”上耗时间。