ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PESQ语音质量评估工具:源码编译、参数配置与批处理避坑指南

PESQ语音质量评估工具:源码编译、参数配置与批处理避坑指南

简介:PESQ(感知语音质量评估)是ITU-T P.862标准推荐的客观语音质量评价工具,常用于通信系统、音频编码及语音增强的效果检验。这份源码包面向音频算法工程师与语音处理研究人员,提供可直接编译的C/C++源码、头文件及Windows批处理脚本,并附带编译教程,帮助解决PESQ在本地环境构建与命令行调用中的常见问题。压缩包共88个文件,约5.53MB,其中包含5个C源码、3个头文件、2个bat编译脚本,以及66个用于测试的wav参考/待测音频,另有P.862标准PDF和P862E说明文档,便于对照原理理解输出分数。资源还配有使用说明txt与源码编译教程,读者可按步骤生成pesq可执行程序,利用wav样本验证不同模式(如窄带/宽带)下的评分流程。已有655人学习下载,适合需要快速上手PESQ的开发者参考。

1. 语音质量评估绕不开的 PESQ:从 ITU-T P.862 标准到可执行文件

做语音质量评估的工程师,手里要是没有 PESQ 这个工具,很多活根本没法干。它是 ITU-T P.862 标准定义的客观语音质量评估方法,核心思想是用心理声学模型模拟人耳听觉,对参考语音和处理后的语音打分,输出 -0.5 到 4.5 之间的 MOS 分。无论是评估 VoIP 通话质量、对比音频编解码器性能,还是验证降噪算法的效果,PESQ 几乎是事实标准。但问题是,官方发布的往往不是现成的可执行文件,而是一堆 C 源码和编译脚本,拿到手要先自己编译才能用。这份资源的核心价值就在这:把 P.862 源码包和配套的编译教程放在一起,省去到处找旧版本代码和折腾编译环境的麻烦。

2. 源码包内幕:P.862 文件结构、版本陷阱与 .DS_Store 干扰项

2.1 压缩包里到底有什么:逐文件拆解

解开 T-REC-P.862-200102.zip 之后,里面不是只有一堆 .c 和 .h 文件,还有一些容易让人困惑的东西。我过了一遍文件清单,按实际用途分类如下:

文件/目录类型作用
T-REC-P.862-200102目录标准文本目录,内含 PDF 版规范
P862E.doc文档ITU-T P.862 的附录 E 说明文档
P.862.pdf标准原文完整的 P.862 建议书,含算法数学描述
源码编译教程.txt教程针对该源码的编译步骤说明,含命令
.DS_Store系统文件macOS Finder 生成的元数据文件,无用途

需要注意 .DS_Store 的存在意味着这个压缩包在 macOS 环境打包过,里面的源码大概率是 Unix/Linux 风格换行符,这直接影响后续在 Windows 上的编译。另外,P862E.doc 是后期修订的附录文件,它和 P.862.pdf 的区别在于,附录 E 补充了窄带模式下某些边界情况的处理建议,但这些修订并未全部体现在源码里,读文档时要留意版本差异。

2.2 版本识别:200102 是源代码的定版时间戳

目录名里的 200102 对应 2001 年 2 月,这是该版代码的提交时间。P.862 标准后续有修订版本(比如 P.862.1、P.862.2),但基础算法以这版源码为准。判断源码版本有个更直接的方法:看代码里#define VERSION或char version[]这类字符串常量。我见过有人在网上下载了名字相近的包,里面混入 P.862.2 的补丁代码,结果编译出的工具行为不一致。如果拿到代码后准备二次开发,先确认版本字符串再动手改,这是最基本的习惯。

3. 编译流程全解:Windows(MinGW)、Linux(GCC)与 macOS 环境配置

3.1 Linux 下用 GCC 直接编:最简单也最容易出问题

PESQ 源码主体是纯 C,在 Linux 上编译理论上最简单。先把代码解压,进入源码目录,执行:

cd pesq-archive/T-REC-P.862-200102/P862_Software ls -la make

如果目录下存在 Makefile,直接make就能生成可执行文件pesq。但是这段流程里最大的问题在于:源码里某些文件是只读权限(从 zip 解压或者从光盘拷出来时常见的权限位保留),make可能会报Permission denied。

我一般会先做一个预处理:

chmod -R u+w . make clean make

其中chmod -R u+w .是对当前目录下所有文件添加写权限,避免只读文件导致的目标文件无法生成;make clean是为了清掉上一次可能的失败产物,让编译重新来过。如果make报错说找不到头文件(如dsp.h),大概率是因为 Makefile 里写的源文件路径与解压后的实际目录结构不一致,需要修改 Makefile 里的SRC_DIR或INCLUDE_DIR变量。

3.2 Windows 上用 MinGW 编:要改的不是代码,是 Makefile

在 Windows 上最顺的路径是用 MinGW-w64 模拟 Linux 环境,编译前先确认 gcc 在 PATH 中:

gcc --version

如果没有输出,说明环境变量没配置好,需要手动把 MinGW 安装目录下的bin路径追加到 PATH 中。接下来进入源码目录,先看一眼 Makefile 的关键行:

CC = gcc CFLAGS = -O2 -Wall -I. OBJS = pesq.o dsp.o ...

Windows 下最容易挂的两处:一是-I.表示当前目录下的头文件路径,如果你把源码放在带空格的路径里(例如C:\Users\My Documents\pesq),编译时会出现找不到头文件的问题;二是 Makefile 里的换行符如果是 Unix 的 LF,而你用了记事本编辑过,变成了 CRLF,MinGW 的 make 会直接报错missing separator。

我习惯把源码复制到一个无空格的纯英文路径下编译,例如C:\pesq\src。路径里带中文或者空格,在 make 阶段十有八九会翻车。接下来执行:

mingw32-make.exe

如果你的 MinGW 安装里没有mingw32-make.exe而是只有make.exe,则替换命令名即可。编译成功后,目录下会多出pesq.exe,此时可以执行:

./pesq.exe -h

看到用法输出,说明编译成功。

3.3 macOS 上编译:.DS_Store 带来的隐藏问题

上面提到压缩包里有.DS_Store文件,这在 macOS 上解压会产生额外的元数据文件。用tar或命令行解压时,_MACOSX目录里也常有._pesq_main.c这类 AppleDouble 文件。如果解压时没有设置COPYFILE_DISABLE=1,这些隐藏文件会伴随源码一起落地,但不会干扰编译——因为 Makefile 用的是通配符加上明确的 .c 文件名,不会把这些隐藏文件编进去。真正要留意的是终端里执行make时,如果代码目录不在/usr/local/include能搜到的范围,需要在 CFLAGS 里加上-I./,或者直接用绝对路径指定头文件目录:

make CFLAGS="-O2 -I\$(pwd)"

这里$(pwd)在 shell 里会被替换成当前目录的绝对路径,确保 dsp.h、pesq.h 等头文件能被编译器找到。macOS 自带的 clang 对 C99 的支持很好,老版本 P.862 源码里没有用到 C11 特性,所以 clang 编译几乎无障碍。

4. 命令行参数与输出解析:mode、文件格式、采样率边界与常见报错

4.1 核心参数:-w 与默认窄带的区别

PESQ 命令行签名是pesq [options] ref_file deg_file。第二个参数mode控制是否启用宽带模式。窄带模式(默认)电话带宽 300 Hz 到 3400 Hz,采样率要求为 8 kHz;宽带模式下采样率要求为 16 kHz。实际使用中需要区分的有三点:

  • -w参数对应宽带,必须放在文件路径前面
  • 如果不加-w,即窄带模式,内部会对输入信号做带通滤波处理
  • 宽窄带不能随意混用,不然输出的 MOS 分数与预期场景不符

典型调用示例:

pesq -w ref_wideband.wav deg_wideband.wav

输出的结果以P.862 Prediction (Raw MOS, MOS-LQO):形式呈现,其中Raw MOS是算法内部未做任何映射的原始分,MOS-LQO是经过映射后的主观分,后者更贴近实际用户体验调查结果。

4.2 输入文件的隐藏要求:格式与电平

PESQ 代码对输入文件有强约束:需要波形文件(.wav),最好为 PCM 16 bit 单体(mono),采样率必须匹配模式。代码内部对齐到内部计算采样率后,再执行感知模型的分帧处理。如果给的是立体声文件,常见做法是在读文件阶段就报错:

Error: input file has 2 channels, expected 1

解决办法是用 SoX 或 ffmpeg 转成单声道:

ffmpeg -i stereo.wav -ac 1 mono.wav

另一个隐含的坑是电平问题。PESQ 内部的响度对齐算法假设输入信号电平在正常语音范围,如果文件是数字静音或者接近满幅削波,输出的 MOS 会异常低。我给客户做验收测试时遇到过极端情况:原文件是 16 bit 正弦波,处理文件是静音,PESQ 的分数是 1.0 左右,此时不是算法坏了,而是输入本来就没有可比性。这种情况可以在用 PESQ 之前先检查波形文件的 RMS 电平,一般用 ffmpeg 的volumedetect滤镜:

ffmpeg -i ref.wav -af volumedetect -f null -

看mean_volume是否在 -25 dB 到 -15 dB 之间,偏离太远就需要先做增益归一化,否则 PESQ 的分值会失真。

4.3 输出分数区间解读:-0.5 到 4.5 的物理含义

PESQ 的原始输出理论上限是 4.5,下限是 -0.5。实际经验值如下:

分数区间主观感受常见典型场景
4.0 - 4.5几乎无感知损伤G.722 宽带编码、无损传输
3.5 - 4.0轻微可感知但不烦人中等码率 Opus、AMR-WB 高码率
3.0 - 3.5可感知损伤但可接受普通移动通信场景
2.0 - 3.0损伤明显,聆听疲劳低码率编码或严重丢包
< 2.0质量差,几乎不可用严重网络损伤或非语音文件

理解这个区间比记数字更重要:PESQ 的分数不是线性的,从 3.5 提到 4.0 所需的质量改进幅度远小于从 2.5 提到 3.0。做编解码器对比时,如果两版差距在 0.2 以内,大概率是算法差异不明显,不建议直接按分数差异做决策。

4.4 无法逃避的常见报错与对应解法

报错一:

Error: Unsupported sample rate.

原因:输入文件采样率不是 8 kHz 或者 16 kHz。解决:用 ffmpeg 重采样后再跑。

ffmpeg -i input.wav -ar 8000 -ac 1 ref_8k.wav

报错二:

Error: Could not open file.

原因:文件路径错误,PESQ 对路径中的特殊字符支持极差(例如中文路径在某些版本里会出现问题)。解决:把 wav 文件和 pesq 可执行文件放同一目录,用相对路径调用。

报错三:

Bad 'MagicNumber' in header.

原因:输入文件不是标准 RIFF/WAVE 头格式(可能是 RAW PCM 文件或者 WAV 头被破坏)。解决:用 sox 重新封装:

sox -t raw -r 8000 -e signed -b 16 raw.pcm wrapper.wav

5. 避坑全记录:跨平台编译与批处理中的 5 个真实踩坑点

5.1 现象:make 报missing separator或syntax error

原因:Makefile 的换行符是 Windows CRLF,Linux 下 make 无法解析。这是在 Windows 上用记事本编辑过 Makefile 后最常见的翻车现场。

解决:执行sed -i 's/\r$//' Makefile把回车符去掉,再用make重新编译。

5.2 现象:编译通过但执行时报段错误(Segmentation fault)

原因:命令行参数中文件数量不对,或者 mode 参数位置放错。PESQ 源码里的main()对 argc 有明确检查,但如果参数是宽窄带模式字符串且未匹配任何分支,代码可能走到未定义分支。

解决:严格按pesq ref.wav deg.wav顺序调用,mode 参数放在两个文件之前,不要写成pesq ref.wav deg.wav -w。

5.3 现象:批处理脚本里连续跑几十个文件,突然某个文件报assertion failed

原因:输入文件时长差异过大。PESQ 要求两段文件长度一致,如果 deg 文件比 ref 文件短了超过一个内部帧的长度,代码内部的assert会直接中止进程。

解决:在批处理脚本中先比较两个 wav 的时长,时长不一致时先做末尾补零(trim)再调用 pesq。我一般是预处理成统一时长:

sox ref.wav ref_pad.wav pad 0 1

这个命令在末尾补一秒静音,如果 deg 文件更短也能跑通,但要在后续计算中扣除静音段的偏移。

5.4 现象:Windows 上双击生成的 pesq.exe 闪退

原因:命令行参数为空时,程序退出但终端窗口关闭。这不是程序 bug,而是 PESQ 需要在命令行终端中运行。

解决:在 cmd 或 PowerShell 中运行,或者写一个批处理文件封装参数:

@echo off pesq.exe -w ref.wav deg.wav pause

加一行pause就能看到输出结果。

5.5 现象:Linux 编出的 pesq 拿到另一台机器上跑提示cannot execute binary file

原因:架构不匹配。比如在一台 x86_64 机器上编译,拷到 ARM 板子上运行,二进制指令集不同。

解决:在目标平台上重新编译,或者用file命令确认二进制格式:

file pesq

输出会显示ELF 64-bit LSB executable, x86-64这类信息,和目标板子的uname -m输出对照一下即可找出问题。

6. 输出到批量评估工作流:用脚本封装 PESQ 并实现 MOS 分数自动归档

当手头有几十对语音文件待评估时,逐条手敲命令显然不现实。这里给出一个可扩展的批处理框架,把 PESQ 包成自动化流程。

把待测文件放在refs/与degs/两个目录,文件名一一对应,下面这个 bash 脚本负责循环评估并输出 CSV 格式结果:

#!/bin/bash # 批处理 PESQ 评估脚本 # 用法: ./run_pesq.sh ref_dir deg_dir output.csv REF_DIR=$1 DEG_DIR=$2 OUT_CSV=$3 echo "filename,raw_mos,mos_lqo" > "$OUT_CSV" for ref_wav in "$REF_DIR"/*.wav do fname=$(basename "$ref_wav") deg_wav="$DEG_DIR/$fname" if [ ! -f "$deg_wav" ]; then echo "跳过缺失文件: $deg_wav" continue fi # 先统一采样率和声道 tmp_ref="/tmp/${fname%.wav}_ref.wav" tmp_deg="/tmp/${fname%.wav}_deg.wav" ffmpeg -y -i "$ref_wav" -ar 16000 -ac 1 "$tmp_ref" > /dev/null 2>&1 ffmpeg -y -i "$deg_wav" -ar 16000 -ac 1 "$tmp_deg" > /dev/null 2>&1 # 跑 PESQ,提取输出行中的数值 output=$(./pesq -w "$tmp_ref" "$tmp_deg" 2>/dev/null | grep "Prediction") raw_mos=$(echo "$output" | awk -F'[(,]' '{print $2}') mos_lqo=$(echo "$output" | awk -F'[(,]' '{print $3}') echo "$fname,$raw_mos,$mos_lqo" >> "$OUT_CSV" done echo "评估完成,结果写入 $OUT_CSV"

脚本逻辑说明:外层循环遍历参考文件,用basename提取文件名与待测目录中的文件对应。两个ffmpeg命令负责把任意采样率、任意声道的 wav 强制转成 16 kHz 单声道,避免 PESQ 因格式问题中途退出。awk -F'[(,]'按括号和逗号切分输出行,例如输出P.862 Prediction (Raw MOS, MOS-LQO): 3.456 3.721,此时$2是 3.456,$3是 3.721,分别对应原始 MOS 和映射后的 MOS-LQO。最后一行写回 CSV。注意脚本里grep "Prediction"是区分 PESQ 输出与错误输出,错误输出走 stderr,已被重定向丢弃。

这套脚本跑完后,整个目录的评估数据直接落成表格,再导入 Excel 或者 Python 分析。需要扩展功能时,可以在 CSV 里加一列用于记录每组文件的编码参数,方便后续按条件筛选。

我自己用 PESQ 这几年最大的教训是:永远不要盲目信任第一次跑出的分数。每次批量评估前,先挑一对已知质量差异的文件做 smoke test,确认无参考与待测文件的路径映射没有错位。从那以后,我每次整理语音质量测试数据,都强制走一遍「格式统一 → 时长对齐 → 批处理脚本 → 抽检输出」的流程,至今没再出过大面积数据错乱的事故。希望这份源码和编译教程也能让你的 PESQ 之旅少走弯路。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进