混音本质是加法,但需防溢出、统一采样率/位深/声道数,并严格检查缓冲区边界与数据有效性。

混音就是加法,但要注意溢出
直接把两个 int16_t 音频样本相加,大概率爆音——因为 32767 + 32767 = 65534,超出 int16_t 范围(−32768~32767)。不处理溢出,输出就全是 clipped 噪声。
常见做法是先转成 int32_t 相加,再做饱和截断(saturation):
int16_t mix_sample(int16_t a, int16_t b) {
int32_t sum = (int32_t)a + (int32_t)b;
if (sum > 32767) return 32767;
if (sum
- 别用
std::clamp(C++17),它在嵌入式或旧编译器上可能不可用 - 避免除法或浮点运算——实时混音要快,整数运算最稳
- 如果混多个声道(比如 4 路输入),建议每加一路都做一次截断,而不是最后才截,否则中间值更容易溢出
为什么要归一化?什么时候可以跳过
两路音量相近的 PCM 数据直接相加,结果平均幅度会翻倍,人耳听起来更响,还容易触发后续环节(如 DAC 或播放器)的削波。所以多数场景下需要归一化。
归一化不是简单除以路数(比如除以 2),而是按能量或峰值动态调整:
- 峰值归一:找到所有输入样本的最大绝对值
max_abs,然后整体乘以0.5f / max_abs(假设两路)——但这是离线处理,不适合流式 - 流式常用固定衰减:每路输入先乘 0.5(即右移 1 位),再相加,等效于平均。对
int16_t就是(a >> 1) + (b >> 1) - 如果已知输入电平很低(比如都 ≤ ±16000),可跳过归一化,直接加后截断
混音时采样率和格式必须一致
混音前没对齐采样率或位深,结果必然是杂音或崩溃。这不是算法问题,是数据前提。
- 不同采样率(如 44.1kHz 和 48kHz)必须重采样——别自己写,用
libsamplerate或soxr - 位深不一致(如
int16_t和float32)要先统一转换:float 到 int16 是clamp(round(x * 32767.0f), -32768, 32767) - 声道数也要对齐:单声道混双声道?得决定是左+右、左+左,还是复制后混——没约定就会左右不平衡
实际混音循环里别忽略缓冲区边界
音频常以 buffer(如 1024 样本)为单位处理,混音循环写错一个索引,轻则破音,重则越界读写。
- 确保所有输入 buffer 长度一致,或用
std::min({len_a, len_b, ...})取最小长度做有效混音长度 - 别假设 buffer 是连续内存——如果是 ring buffer 或分段 DMA 缓冲,得用偏移量算真实地址
- 用
size_t做索引变量,避免有符号整数负溢出(比如i--到 −1 后变成极大正数)
真正麻烦的从来不是加法本身,而是数据来源是否可信、边界是否被检查、溢出是否被覆盖——这些地方出错,波形看起来“差不多”,但监听一两分钟就会听出毛刺或间歇失真。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











