做短波和卫星语音链路的人,基本都绕不开 2400bps 这个码率。这个数字乍一看小得可怜,但在窄带通信里它是个很经典的门槛:再高就得牺牲信道余量,再低音质又很难保证。前阵子我们团队做了一个单芯片2400bps音频编解码方案,把采集、编码、解码、信道打包全压在一颗 MCU 上,替换掉原来 DSP 加 FPGA 的两片架构。这篇文章就把从算法选型到上板调试的完整过程复盘一遍,适合正在做窄带语音、数字对讲、应急通信底层的工程师,也适合想搞明白“这么低的码率到底怎么把声音传过去”的朋友。
1. 2400bps语音编码到底在解决什么问题
1.1 2400bps有多慢,以及为什么需要这么慢
先做个直观对比:传统电话语音用 G.711 编码,码率 64kbps;手机里的 AMR 语音编码,一般在 12.2kbps 左右工作;而 2400bps 只有前者的 1/27。拿汉字打比方,一个汉字编码后大约占 16bit,2400bps 每秒只能传大约 150 个汉字。但我们要传的不是文字,而是有人情绪、有语气、300Hz 到 3400Hz 连续变化的人声,这就不能靠逐样拍脑袋采样,得靠算法把语音“建模”了再传。
为什么会有这种低码率需求?答案在信道。短波单边带通信一个话路只分到 3kHz 带宽,用 QPSK 这类调制方式跑满也就 2400bps 上下;卫星手持终端发射功率有限,链路预算紧,同样只能压到这么低的业务码率。还有些场景,比如应急通信、深海或矿井窄带链路,信道资源是按字节计的。在这些链路上,能不能保持语音可懂、能达到“能通上话”的目标,比追求高保真重要得多。
1.2 传统编解码器为什么做不到这么低
很多人第一反应是“压缩吗?多压一点不就行了”。真不是。传统波形编码,比如 G.711、G.726,本质上是在尽量忠实地重构声音波形,再低最多压到 16kbps 还能听,到了 4.8kbps 以下,波形信息量根本不够,声音会变得像喉咙里含了沙子一样完全没法听。
2400bps 这个量级必须换思路,走“参数编码”路线,也叫声码器。它不说“我要把声音录下来传过去”,而是说“人说话是靠声带振动激励声道滤波器,我只需要提取激励信号的特征和声道滤波器的参数,把这些参数传过去,接收端重建出来就行”。这套思路来自线性预测编码,也就是 LPC 技术。上世纪八九十年代,军用电台就在用基于 LPC 的声码器,到了后来,MELP、AMBE、Codec2 这些算法又把参数建模做得更精细,才在 2400bps 上做到了可懂度不错、带一定自然感的语音。
1.3 主流2400bps算法怎么选
做方案时我们认真对比了四类常见算法:MELP、MELPe、AMBE 和 Codec2。MELP 是 2400bps 声码器里最经典的标准之一,全称 Mixed Excitation Linear Prediction,混合激励线性预测,它比传统 LPC 多做了“混合激励”建模,能区分清音浊音,保留更多语音细节。MELPe 是它的增强版,进一步加了噪声抑制和低码率扩展。AMBE 是 DVSI 公司的专有算法,很多卫星电话里都在用,音质表现稳定,但要买授权、签协议。Codec2 是开源项目,作者把源码全部公开,也正好有 2400bps 模式,适合先跑通原型。
从实际项目角度看,我建议的路线是:先用 Codec2 的 2400 模式把板级流程、信道接口、端到端时延全部调通,然后再把核心编码器替换成你要量产用的 MELPe 授权算法或者基于 MELP 自研的技术。Codec2 最大的价值在于它给了你一套可读、可编译、可调试的参考实现,非常方便我们理解参数编解码器的工作细节。
| 算法 | 典型码率 | 专利与授权 | 音质(主观) | 资源占用 | 适合场景 |
|---|---|---|---|---|---|
| MELP | 2400bps | 需注意专利,参考实现公开 | 可懂度高,自然感一般 | 中等 | 军用标准、传统窄带终端 |
| MELPe | 2400/1200/600bps | 需授权 | 可懂度高,抗噪声较好 | 中等 | 卫星电话、应急通信 |
| AMBE | 2000-9600bps | DVSI授权 | 较好,听感偏自然 | 较低 | 卫星电话、数字对讲机 |
| Codec2 | 2400/1300/700bps | 开源(LGPL) | 可懂度较好,机械感稍强 | 低 | 原型验证、HAM、低成本产品 |
2. 单芯片方案怎么设计,芯片怎么选
2.1 “单芯片”到底指什么
项目标题里有“单芯片”,这个词比听起来要微妙。纯讲物理芯片,一个系统总还少不了 ADC、DAC 和功放,尤其是给对讲机加语音,模拟前端不能省。所以“单芯片”指的是核心处理路径的单片化:语音采集进来的数字流、编解码算法、信道接口协议全部由一颗 MCU 或 SoC 完成,不再单独挂 DSP、FPGA 或者专用语音芯片。这种架构在成本、功耗、供货上都占优势,但代价是这颗主控芯片的算力必须足够,而且软件层面对实时调度要求变高了。
实现单芯片语音编解码,业内实际上有三条路线。第一条是“纯 MCU 软解”,用一颗带浮点或强定点的 Cortex-M7/M33 级别芯片,硬跑编码算法。第二条是“专用语音编解码芯片”,比如一些内置 AMBE 或 MELP 算法的语音处理芯片,软件几乎不用管,调用接口就行,优点是省心,缺点是算法和码率被锁死,扩展性差。第三条是 FPGA 加软核,比如在 FPGA 里放个小 RISC-V 或 Arm 软核,语音算法跑在上面,但这种方案一般是在系统里本来就有一块 FPGA 时才划算,否则 BOM 成本偏高。我们最终选了第一条,因为方案最灵活,能自己控制算法、帧结构和信道格式,后续要加语音加密也方便。
2.2 算力评估与芯片选型
选芯片前,我先做了个粗略的算力估算。以 MELP 编解码为例,编码器端要做 LPC 分析、基音检测、清浊音判决、傅里叶级数提取,运算量大致在 15 到 30 MIPS 的水平(优化程度不同差异很大),解码器端相对轻,大概 5 到 10 MIPS。这是在 8kHz 采样、每帧 22.5ms 的条件下大概算出来的。内存方面,编码和解码的工作缓冲加起来一般在 16KB 到 32KB 左右,代码量大约 80KB 到 150KB Flash。所以我的结论是:一颗主频 400MHz 级别的 Cortex-M7,比如 STM32H743、GD32H7 这类带 DSP 指令、带浮点的 MCU,跑一路 2400bps 编解码是绰绰有余的;即使是 240MHz 的 M4,如果优化讲究一点也能跑,但余量不高。
有人可能会问,干嘛不用一颗经典音频 DSP,比如 C55x 或者国产的 DSP?客观说传统 DSP 在跑这类算法时功耗确实低,但开发工具链、周边生态和调试手段比 MCU 差不少,招人也难。用 MCU 跑声码器的做法这几年已经越来越主流,关键是 MCU 现在算力上来了,深度学习、语音、变频控制都能扛,技术选型没必要被所谓“专业 DSP”框死。
2.3 系统任务划分与实时性设计
单芯片方案跑编解码,最怕的就是实时性不稳。“有没有算完”比“算得快不快”更重要。我们最终把软件架构分成三层:第一层是中断层,负责音频采样 DMA、信道接收发送的事件;第二层是算法层,跑编码器、解码器两个任务;第三层是控制层,处理外部按键、状态显示、参数设置等低优先级事务。
音频采集我们采用双缓冲 DMA,一块缓冲采着,另一块送去编码。8kHz采样、16bit单声道,22.5ms一帧对应刚好 180 个采样点。编码任务被定时器以帧周期唤醒,每次要从当前双缓冲里把已满的那块数据取走。这里最容易犯的错是让编码任务处理耗时太长,导致下一次 DMA 中断来了还没处理完,频域缓冲就漏采或者覆盖了。解决思路是给编码任务一个独立的信号量,DMA 中断只标记“这一帧满了”,不做任何算法运算,编码任务在后台慢慢算,保证系统的最大中断延迟在微秒级。
3. 核心实现步骤与移植细节
3.1 拿到算法代码后的第一步:定点化
如果用 MCU 软解声码器,大部分代码默认是浮点 C 语言。浮点模型在 PC 上验证没问题,但想要在低功耗 MCU 上稳定跑,一般是把核心算法改写为定点,用 Q15 或 Q31 格式统一表示小数。这个步骤是项目里最细致也最枯燥的工作。比如 LPC 分析中的自相关函数,原始浮点代码里每个系数在 0 到 1 之间,转到定点后需要确定一个合适的 Q 值,既要保精度,又要防溢出,每个模块的 Q 值可能还不一样。
定点化之后的验证不能只看能不能跑通,要看中间数据误差。我们当时的做法是:用同一个语音文件喂给原始浮点代码和定点代码,在每个关键模块入口打印一组特征值,比如 LSP 系数、基音周期、增益、清浊音判决结果,逐帧比对。误差超过一定程度就锁帧号,把那一帧的中间数据 dump 出来定位。整个过程像绣花,建议朋友们千万别图省事直接拿整段代码编出来听音质,许多隐蔽的溢出问题要到特定语速、特定音量下才会爆出来。
3.2 实时性调优和内存优化
代码移植通过后,紧接着是实时性调优。拿我们用的 Cortex-M7 举例,先把编译选项打开 ARMCC 或 GCC 的-O2,然后开启 FPU(如果混用定点就不需要),再把关键循环里的除法、开方、对数这类运算尽量换成查表和 DSP 指令。CMSIS-DSP 里带了一批现成的库函数,比如自相关、矩阵运算、FFT,优化效果非常明显。我印象最深的是 LPC 求解的 Levinson-Durbin 递归,原始代码在 PC 上感觉不到,搬到 MCU 上一帧要占不少时钟周期。后来用定点 Q15 配合 32 位累加器重写了核心循环,再加上编译器自动向量化,整体编码时间从最初的大几十毫秒一帧降到了 10 毫秒以内。
内存优化方面,声码器任务建议全部用静态分配。掉进 heap 碎片坑是嵌入式项目里最不值当的事。我们把编码器和解码器的 buffer 分别独立分配,编解码状态结构体集中放在片内 RAM,利用自定义内存池统一管理,保证运行期间没有动态 malloc。实测下来整体 RAM 占用 28KB 左右,Flash 占用 120KB,对一颗 64KB RAM 的 M7 来说还有富余。
3.3 帧结构与信道接口的对接
编码算法算完一帧,只是生成了几十个 bit 的参数,真正难的反而是怎么把这些 bit 塞进信道。以 MELP 的 2400bps 帧结构为例,在 8kHz 采样下每帧是 180 个采样点,时长 22.5ms,每帧固定生成 54bit。这 54bit 要按重要性分配给线谱频率、增益、基音周期、清浊音、抖动等参数。信道不是绝对可靠的,尤其短波有衰落、突发干扰,可能整帧丢掉或者错几个 bit。所以我们设计信道接口时做了三层保护:第一层在帧头加同步字和帧序号,第二层对关键参数做 CRC 校验,第三层做交织,把连续误码打散,通过纠错码把有限的保护 bit 花在刀刃上。
特别注意时延预算。声码器编码器本身因为帧长和向前看会产生几十毫秒延迟,解码器同理,信道交织加纠错还会再加几十毫秒,链路往返之后,用户会觉得“像在喊对讲机,说完一句话要等半拍”。我们在设计时明确定目标:单向语音处理时延控制在 150ms 以内,其中编解码各约 40ms,信道处理 60ms 左右,尽量保证正常通话的节奏感。这需要在算法帧长和交织深度之间找平衡,交织越深抗突发误码越好,但时延越高,这个取舍要对着实际信道测试数据来做,不能拍脑袋。
4. 实测结果与调试经验
4.1 音质评测:MOS分和主观听感
调通之后我们做了完整的音质评测。测试条件是 8kHz 采样率、2400bps 码率、安静环境下男女声测试语料各 10 句,采用听感评分和 A/B 对比。主观结果和同类算法公开数据基本一致:可懂度很好,对方说“天上人间”和“天上人间”不会听错,发音清楚,但是声音有明显的“机器味”,像过去接收机里带压缩的语音,一听就知道这不是宽带通话。
我特意把 2400bps 和 1200bps 各做了一条测试文件对比。2400bps 保留了足够的韵律信息,能听出说话人的紧张或轻松;降到 1200bps 之后,部分音节的声调信息开始丢失,平静陈述和疑问句容易听混。所以如果产品要求“能辨别语气”,不建议低于 2400bps;如果只要求“命令词可懂”,1200bps 甚至 600bps 都能接受。这个度要在需求阶段就和客户对齐,不然码率选低了,最后音质验收会很尴尬。
4.2 三个踩坑记录
第一个坑是定点化之后偶发爆音。听起来像“噼啪”一声,不是每次都出现,很随机。最后定位到增益参数在极端人声幅度下溢出,限制模块写反了方向,峰值大于上限时被卷到负值,产生一个大的跳变。这个问题的教训是:数值类 bug 必须靠长时间、多样化测试语料去触发,不能只拿两三句标准语音测。
第二个坑是信道丢帧之后解码器自激。模拟信道随机丢 5% 帧时,声音突然变得像引擎轰鸣一样,恢复不了。原因是我们没有做帧错误隐藏,丢帧后解码器把上一次状态继续推,增益和基音发散。后来加了一层简单的错误隐藏:检测到丢帧时,不更新激励参数,用上一帧基音打折保持一个轻音量输出,听起来像短暂“咕哝”一声,但至少不会长时间刺耳。
第三个坑是首帧噪声。开机后第一次解码总会“啪”一下,因为 22.5ms 帧被盗了一部分,解码器状态没建立完整。后来在初始化阶段强制写入一帧静音的编码状态,让解码器先预热,问题就消失了。首帧问题虽然小,但用户开机第一声听到噪音,观感很不好。
4.3 功耗实测与续航估算
做完音质和稳定性,功耗是我们最关心的点。在 3.7V 供电、主频 400MHz、关闭外设、只保留编解码任务的情况下,实测整机(包含 ADC/DAC 和前端)工作电流大约 45mA 左右,其中 MCU 内核占约 20mA,模拟前端约 15mA,信道收发模块另算。如果待机时 MCU 进入睡眠、只保留 RTC 和按键唤醒,系统电流能压到 20uA 以下。按一块 1000mAh 的电池算,持续通话时间大约 22 小时,待机时间可以按年计。对便携终端来说,这个数已经够用,如果想要再把通话功耗降下来,就需要进一步降主频并把部分加速指令用起来,或者切换到更低功耗的 M33 核加 TrustZone。
5. 常见问题排查速查表
项目做完之后,我整理了一张问题排查表,便于研发和测试阶段快速定位问题,这里分享出来。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 声音断续、卡顿 | 编码任务未按时完成,DMA缓冲溢出 | 检查编码一帧耗时,开启编译器优化,降低中断嵌套负担 |
| 偶发爆音 | 定点溢出或限幅错误 | 加大各参数动态范围,逐帧对比中间变量 |
| 声音“鼓引擎” | 解码器丢帧自激 | 增加帧错误隐藏,限制增益和基音变化率 |
| 有声音但语速快慢不稳 | 解码端采样时钟漂移 | 让解码器与信道时钟同源,或用 PLL 校准音频时钟 |
| 开机瞬间噪音或爆音 | 解码器未初始化完成 | 初始化阶段送入一帧静音编码状态 |
| 传输后声音错乱,有时能响有时不能 | 帧同步失锁 | 检查同步字、帧序号、CRC,确认交织去交织方向一致 |
| 语音混叠、像含了水 | ADC 抗混叠滤波不足 | 检查前端低通滤波器,增加数字下采样前滤波 |
| 灵敏度差,弱信号下断断续续 | 信道编码保护不足 | 增加关键位保护,调整交织深度 |
结合这次项目,我也整理了几条适用于单芯片窄带语音设计的经验:一是先把通信链路预算和时延预算做出来再选芯片,千万别一上来就开跑算法;二是定点化过程中每个模块都要有测试向量,不能等系统联调时再回头找错;三是给声码器任务预留至少 30% 的 CPU 余量,因为你不知道后续会不会加加密或者降噪模块;四是音频电路的地线布局要格外小心,2400bps 的低速率不代表模拟前端可以随便布线,噪声一样会毁掉音质。
我个人在实际操作中的体会是,单芯片 2400bps 音频编解码方案的难度不在于“敲代码”,而在于把语音算法、实时系统、信道协议三件事揉在一起考虑。很多时候算法单独验证全部通过,一上信道就出问题,是嵌入式里最磨人的状态。如果你正在做类似的东西,建议先用开源算法把端到端链路完整跑通,再逐步替换核心模块,这样每一步都可控,排查问题时也容易定位。另外每个版本之间要保留完整的测试语音库和自动评分脚本,音质回归测试一定要做到可以随时一键复现,这一点省下来的时间足够你再做两个功能模块。