ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

EVS编解码器实战:从3GPP协议到DSP移植与调试

EVS编解码器实战:从3GPP协议到DSP移植与调试 简介这份文档面向移动语音通信开发者、VoLTE/VoWiFi终端与芯片方案工程师系统讲解3GPP R12定义的EVS音频编解码器及其工程落地前的准备工作。内容围绕EVS的全频段8kHz至48kHz支持、5.9kbps至128kbps码率范围、ACELP语音编码与MDCT音乐编码的双路策略、DTX/VAD/CNG/SID机制、PLC丢包补偿及JBM抖动缓冲等关键特性展开并梳理TS26.441总览、TS26.442定点参考代码、TS26.444测试序列、TS26.445算法描述等规范的使用重点帮助读者建立从原理到实现的完整认知。资源为1个docx文档压缩包约367KB轻量便于随时查阅。目前已有363人学习适合需要快速掌握EVS编解码流程、明确优化与测试切入点的中高级开发者参考。1. 从一通 VoLTE 电话说起EVS 编解码器到底解决了什么问题你打了一通 VoLTE 电话对方声音清晰得像在耳边说话背景噪声被压得很低偶尔网络抖动也没出现断断续续的机械音——这背后大概率是 EVS 在干活。EVSEnhanced Voice Service是 3GPP 在 2014 年 9 月随 R12 版本标准化的音频编解码器目标很明确对标互联网阵营的 OPUS把移动语音的音质和抗丢包能力拉到一个新水位。它覆盖 8kHz 到 48kHz 全频段码率从 5.9kbps 到 128kbps 可调每帧 20ms语音走 ACELP、音乐走 MDCT 频域编码还自带 DTX/VAD/CNG 和 PLC 丢包补偿。适合谁做手机 Audio DSP 语音通道的、搞 VoLTE/VoWiFi 终端适配的、需要在嵌入式平台上把 EVS 跑通的工程师。如果你只是调个 App 层音频这份内容可能偏底层但如果你想搞清楚“为什么我的 EVS 通话有杂音”“reference code 怎么改成 DSP 能用的”往下看。2. 吃透 3GPP 协议栈从 TS26.441 到 TS26.445 的阅读顺序与取舍2.1 先看哪本、后看哪本别一头扎进 700 页算法描述3GPP 给 EVS 铺了一整套 SPEC从 TS26.441 到 TS26.451但你不是做算法研究的没必要每本都逐字啃。我当初的做法是先翻 TS26.441 总览把 EVS 的框架、术语、模块划分搞清楚大概花半天然后直接跳到 TS26.442 定点参考代码这是后面所有工作的重中之重TS26.444 测试序列放在手边优化过程中每天都要跑TS26.445 算法描述近 700 页只看特性描述相关章节比如 DTX 的 SID 发送机制、JBM 的接口定义纯数学推导部分扫一眼知道在讲什么就行。TS26.443 是浮点参考代码如果你只在定点 DSP 上跑可以暂时放一放。TS26.446 到 TS26.451 涉及 AMR-WB 互操作、编解码器接口等用到再查。提示TS26.442 里的 reference code 是 C 语言写的定点实现权威性没问题但直接编译到 DSP 上跑不动是常态别怀疑代码有 bug先怀疑自己没做优化。2.2 把 reference code 在 Ubuntu 上跑起来生成 encoder/decoder 可执行文件在 PC 上先把 reference code 跑通这是后面所有调试的基础。我一般在 Ubuntu 下操作流程不复杂解压 TS26.442 的代码包进入c-code目录直接make就能生成EVS_coder和EVS_decoder。但要注意原始 makefile 默认可能只编译一部分你需要根据自己平台改一下Makefile里的CFLAGS和TARGET。下面是我常用的编译命令# 进入参考代码目录 cd ./ts26.442/c-code # 清理旧目标文件避免架构不匹配 make clean # 编译生成 encoder 和 decoder指定 32 位模式DSP 通常是 32 位 make all CFLAGS-m32 -O2 -DREFERENCE_CODE # 检查生成的可执行文件 ls -l EVS_coder EVS_decoder逻辑说明make clean先清掉可能残留的 x86_64 目标文件因为后面要往 32 位 DSP 上移植提前用-m32编译能暴露一些指针长度问题。-O2是常规优化-DREFERENCE_CODE是参考代码里用来区分平台宏的不加可能编不过。编译成功后你会得到两个可执行文件一个负责编码一个负责解码。参数说明CFLAGS里的-m32不是必须但如果你后续要对比 DSP 上的行为建议加上。-O2别用-O3参考代码里有些循环依赖顺序激进优化可能导致结果不一致到时候你查几天都查不出来。2.3 用 PCM 文件跑通编解码闭环验证算法可信度编译出可执行文件后拿一段 16kHz 采样、16bit 量化的 PCM 做输入走一遍编码再解码听回放是否和原始 PCM 一致。命令大概是这样# 编码输入 PCM输出 G192 格式码流 ./EVS_coder -i input_16k.pcm -o encoded.g192 -rate 8k -fs 16k # 解码输入 G192 码流输出 PCM ./EVS_decoder -i encoded.g192 -o decoded.pcm -fs 16k # 用 CoolEdit 或 Audacity 对比 input_16k.pcm 和 decoded.pcm逻辑说明-rate 8k指定编码码率 8kbps-fs 16k指定采样率 16kHz这两个参数必须和你的实际场景匹配。编码输出 G192 格式解码器读 G192 还原 PCM。如果解码后的 PCM 听下来和原始文件无异样说明算法实现是可信的如果有杂音或断续先检查 PCM 的字节序和采样率是否写对再检查 G192 文件头是否正确。参数说明-rate可选 5.9k、8k、9.6k、13.2k、16.4k、24.4k、32k、48k、64k、96k、128k具体支持哪些取决于采样率。WB 下支持全码率NB 下只支持部分。-fs可选 8k、16k、32k、48k对应 NB、WB、SWB、FB。3. 从 indices 到码流G192 与 MIME 打包的实操细节3.1 indices 结构长什么样为什么不能直接发编码器输出的是 indices 数组最多 1953 个每个 indices 有两个成员nb_bits表示这个值占多少位value表示具体数值。这些 indices 不能直接扔到网络上发因为对方不知道每个值占多少位也没法解析。所以必须打包成两种标准格式之一G192 或 MIME。G192 是 ITU-T 定义的测试格式每个 Word 两个字节同步头0x6B21表示好帧0x6B20表示坏帧后面跟长度和每个 bit 的展开值1 存0x00810 存0x007F。MIME 格式更紧凑第一个字节是 header低 4 位表示码率 index后面是 pack 后的比特流。3.2 G192 打包调试阶段最好用的格式G192 的好处是肉眼可读每个 bit 都展开成两个字节方便你用十六进制编辑器直接看。打包函数在 reference code 里叫pack_bits_g192()核心逻辑就是遍历 indices把每个 value 的二进制位按顺序写成0x0081或0x007F。下面是我改造后的简化版// 将 indices 打包成 G192 格式每个 bit 展开为 0x0081 或 0x007F void pack_g192(short *indices, int num_indices, unsigned short *out_buf) { int bit_pos 0; out_buf[0] 0x6B21; // good frame 同步头 for (int i 0; i num_indices; i) { int nb nb_bits[i]; int val value[i]; for (int b nb - 1; b 0; b--) { // 从高位到低位依次展开 out_buf[2 bit_pos] (val (1 b)) ? 0x0081 : 0x007F; bit_pos; } } out_buf[1] bit_pos; // 长度字段单位是 bit }逻辑说明out_buf[0]固定写同步头out_buf[1]写总 bit 数后面每个 bit 展开成两个字节。注意bit_pos从 0 开始但写入位置从out_buf[2]开始因为前两个 Word 是头和长度。这个函数在调试时非常有用你可以把编码后的 G192 文件用十六进制编辑器打开对照 TS26.444 的测试序列看每个 bit 是否一致。参数说明indices是编码器输出的数组num_indices是实际使用的 indices 个数out_buf要预先分配足够空间一帧 20ms 在 128kbps 下是 2560 bit展开后需要 2560 个 Word加上头两个 Word至少 2562 个unsigned short。3.3 MIME 打包实际发送用的格式MIME 格式才是真正在网络上传输的因为它紧凑。打包函数叫pack_bits_mime()核心是把每个 value 按nb_bits依次塞进字节流不足 8 位的要跨字节拼接。第一个字节是 header低 4 位是码率 index比如 8kbps 对应 index 2。下面是我在 DSP 上改造后的版本// 将 indices 打包成 MIME 格式输出字节流 void pack_mime(short *indices, int num_indices, unsigned char *out_buf, int rate_index) { int bit_pos 0; out_buf[0] rate_index 0x0F; // header 低 4 位是码率 index for (int i 0; i num_indices; i) { int nb nb_bits[i]; int val value[i]; for (int b nb - 1; b 0; b--) { int byte_idx 1 (bit_pos 3); // 从第 1 字节开始 int bit_idx 7 - (bit_pos 7); if (val (1 b)) { out_buf[byte_idx] | (1 bit_idx); } else { out_buf[byte_idx] ~(1 bit_idx); } bit_pos; } } }逻辑说明out_buf[0]写 headerbit_pos从 0 开始每写一个 bit 就计算它在哪个字节的哪一位。byte_idx从 1 开始因为第 0 字节是 header。注意out_buf在调用前要清零否则或运算会保留旧值。这个函数在 DSP 上跑的时候bit_pos用int没问题但如果你在 16 位平台上跑要改成long防止溢出。参数说明rate_index根据码率查表得到8kbps 是 29.6kbps 是 313.2kbps 是 4以此类推。out_buf大小按1 (总 bit 数 7) / 8计算8kbps 一帧 160 bit需要 1 20 21 字节。4. 避坑与排查EVS 移植和联调中最容易翻车的五个点4.1 解码后 PCM 有杂音或断续现象用 reference code 跑闭环解码后的 PCM 听下来有“滋滋”声或者断断续续。原因最常见的是 PCM 字节序搞反了或者采样率参数写错。EVS 参考代码默认输入是 16bit 小端如果你从 DSP 导出的 PCM 是大端解码出来就是噪声。解决用xxd看一眼 PCM 文件头确认字节序再检查-fs参数是否和实际采样率一致16kHz 的 PCM 用 8kHz 解码出来的声音会变调且带杂音。4.2 G192 文件头写错导致解码器直接报错现象解码器读 G192 文件时提示bad frame或者直接崩溃。原因同步头写成了0x6B20而不是0x6B21或者长度字段写的是字节数而不是 bit 数。解决G192 的同步头只有好帧才用0x6B21坏帧是0x6B20长度字段是总 bit 数不是 Word 数。我当初就是长度写成了 Word 数解码器读了一半就报错查了一下午。4.3 DSP 上跑 reference code 结果和 PC 不一致现象同样的码流PC 上解码正常DSP 上解码出来是噪声或者静音。原因DSP 是 16 位shortPC 是 32 位intreference code 里有些地方用了int做中间变量移植到 DSP 上溢出。解决把所有int改成long或者int32_t特别是pack/unpack里的bit_pos和L_shl之类的移位操作。另外检查 DSP 的编译器是否默认char是无符号的如果是unsigned char和char混用会出问题。4.4 DTX 开启后对方听到“咔咔”声现象通话中一方不说话时另一方听到周期性的“咔咔”声。原因SID 包发送周期配置不当或者 CNG 算法在 DSP 上没跑对。EVS 的 SID 发送周期可配 3 到 100 帧自适应模式下 8 到 50 帧。如果周期太短SID 包太密集对方 CNG 还没生成完就收到下一个就会产生突变噪声。解决先固定周期比如 8 帧发一个 SID确认 CNG 输出正常后再开自适应。另外检查 SID 包的 payload 大小EVS 是 48 字节别沿用 AMR-WB 的 40 字节。4.5 与 CP 联调时上行正常下行无声现象自调时 encoder、pack、unpack、decoder 都 OK但和 CP 联调时对方听不到声音。原因CP 侧发的 EVS 码流 header 里的码率 index 和 Audio DSP 侧配置的不一致或者 CP 根本没发 EVS 码流还是 AMR-WB。解决先在 Audio DSP 侧加日志把收到的码流前几个字节打印出来看 header 低 4 位是不是预期的码率 index。如果不是找 CP 侧确认协商结果如果是但解码无声检查 unpack 后的 indices 是否和 encoder 侧一致重点看nb_bits数组有没有被覆盖。5. 进阶技巧用 loopback 和测试序列把 EVS 调稳自调阶段最有效的手段是 loopback把 pack 后的 MIME 码流直接作为 unpack 的输入绕开 CP验证 Audio DSP 内部的 pack/unpack 闭环。我当初就是这么干的把 pack 后的码流存成文件再用 unpack 读回来解码后用 CoolEdit 听和自己说的话一样说明 unpack 没问题。这个方法能帮你快速定位问题在 Audio DSP 内部还是 CP 侧。另一个习惯是每天保存一个优化版本用 TS26.444 的测试序列跑一遍。测试序列覆盖了各种码率、采样率、丢帧模式跑一遍大概十几分钟。如果发现某个序列的输出和参考不一致立刻回退到上一个版本用git bisect或者手动二分找到是哪次改动引入的。我当初优化 DSP 汇编时有一次改了L_mac的溢出处理结果 13.2kbps 下所有测试序列都偏了回退后发现是舍入模式改了导致累加器结果差 1。从那以后我每次改完汇编都强制走一遍完整测试序列再也不敢只跑几个用例就交差。注意TS26.444 的测试序列文件是二进制格式别用文本编辑器打开用xxd看前几个字节确认同步头。如果你也在手机 Audio DSP 上搞 EVS或者正准备把 reference code 往嵌入式平台上搬这份资源里的 SPEC 和参考代码值得仔细过一遍。希望帮到你。本文还有配套的精品资源点击获取
返回列表