先检查音频输入:WAV、裸 PCM 与采样率

同一段录音,文件路径输入正常,换成数组或字节后却出现异常?在调整模型、热词或 VAD 之前,先核对输入的格式、采样率、声道与数值范围。一个短小、只读的文件检查器,就能帮助你排除“空文件”“截断数据”和“把声道数算进时长”等基础问题。

例如,一个 16 kHz 双声道文件有 32000 个采样帧,时长是 2 秒,不是 4 秒。每帧含两个声道的采样值;把左右声道直接展平成单声道序列,会改变时间关系。另一个常见误区是把原本 8 kHz 的数据标记为 16 kHz:修改采样率标签没有生成新的采样点,不能代替重采样。

本文检查器仅面向本地、未压缩、16 位整数 PCM WAV,使用 Python 标准库,不加载 FunASR 或模型、不联网、不修改文件。它不是通用音频解码器,不支持 MP3、浮点 WAV 或任意裸 PCM,也不验证语音内容、识别准确率或生产服务。测试中的音频是人工构造的二进制样本,不是真实语音质量评测。

1. 先区分容器、字节与数组

WAV 是容器。文件包含格式信息与音频数据,不能把整个文件的字节都当作采样值。下面的示例使用 wave 解析容器,再读取数据帧。

裸 PCM 没有自描述的完整容器。处理前需要从数据来源确认采样率、声道数、位宽、符号和字节序。仅凭扩展名或字节长度不能可靠推断这些信息。不要把未知 PCM 强行加一个 WAV 头就认定修复完成。

数组有形状和数值范围。有符号 PCM16 的范围是 -32768 到 32767;转换成浮点 dtype 不等于缩放幅度。按 32768 归一化后,负端可到 -1,正端略小于 1。二维数组还需要明确哪一维是声道,不能只靠 reshape。具体支持方式应以所用版本的输入实现为准。

2. 运行只读的 PCM16 WAV 检查器

将下面代码作为独立的 inspect_wav.py 脚本运行,传入本地 WAV 路径。它逐块读取数据,报告采样帧数、时长和所有声道的绝对峰值,同时拒绝空数据、非 PCM16 和截断文件。

import json
import struct
import sys
import wave


def inspect_pcm16_wav(path):
    with wave.open(str(path), "rb") as audio:
        channels = audio.getnchannels()
        rate = audio.getframerate()
        declared = audio.getnframes()
        if audio.getcomptype() != "NONE" or audio.getsampwidth() != 2:
            raise ValueError("Expected uncompressed PCM16 WAV")
        if channels < 1 or rate < 1 or declared < 1:
            raise ValueError("Empty or invalid audio")
        frame_bytes = channels * 2
        chunk_frames = max(1, 65536 // frame_bytes)
        frames = 0
        peak = 0
        while True:
            raw = audio.readframes(chunk_frames)
            if not raw:
                break
            if len(raw) % frame_bytes:
                raise ValueError("Truncated sample or channel frame")
            frames += len(raw) // frame_bytes
            peak = max(peak, max(abs(value[0])
                                 for value in struct.iter_unpack("<h", raw)))
        if frames != declared:
            raise ValueError("Audio data is shorter than its WAV header")
        return {
            "channels": channels,
            "sample_rate": rate,
            "frames": frames,
            "duration_seconds": frames / rate,
            "peak_abs": peak / 32768.0,
        }


if __name__ == "__main__":
    print(json.dumps(inspect_pcm16_wav(sys.argv[1]), indent=2))
python inspect_wav.py /path/to/input.wav

duration_seconds 使用帧数除以采样率,不再除以或乘以声道数。peak_abs 是全部声道上的最大绝对采样值,不是响度、信噪比或语音概率。非空的全零文件会返回 0;这和没有任何数据帧的空文件不同。峰值达到 1 值得检查,但单个极值本身不能证明整段录音发生了削波。

3. 怎样使用检查结果

4. 版本与测试边界

#3899 的输入修复集成保留了原作者的提交,修复 NumPy PCM16 的缩放和二维浮点输入的声道处理。可核对固定提交的输入加载实现。截至本文发布,修复已合入 main;已验收的根包仍是 FunASR 1.4.26,不能据此宣称安装 1.4.26 已包含这次 main 修复。本文检查器不依赖该修复,也不会修改你的安装。

随文测试直接执行中英文正文及构建页面里的同一段代码,覆盖单/双声道、8/16 kHz、PCM16 正负边界、非空静音、空文件、8 位输入、截断采样、截断整帧、非 WAV、跨读取块的末尾峰值和命令行入口。它检查已覆盖的输入契约,不是任意损坏文件的安全审计,也不涵盖所有 WAV 编码扩展。

文件检查之后,再用有权处理的真实录音检查文字、时间戳、漏句和说话人结果。分享问题时附上脱敏的格式信息、版本与最小复现,避免公开私有音频、转录或绝对路径。

下一步使用会议转录验收清单,把输入检查与实际转录结果对应起来。