音频混音原理(MIXer)

发布时间:2026/6/29 18:04:17
音频混音原理(MIXer) 混音本质就是多路独立音频信号经过音量、声像、均衡、压缩、效果器处理后合并为单 / 双 / 多声道输出信号解决声音冲突、塑造空间层次、平衡响度最终得到清晰好听的成品音频。一、基础原理混音就是两个信号的音频叠加比如两路混音就是两个信号叠加可以通过模拟信号叠加和数字信号叠加实现。模拟波形叠加混音输出波形 波形A波形B。 波形叠加存在一个问题 同向波形相加会音量变大异向会相互抵消。数字音频叠加混音输出采样 音频1采样音频2采样。在数字音频中所有采样点数值直接相加会超过设备最大电平就会削波失真刺耳破音因此混音首要目标就是控制总电平不超限。综上可以看出不论是模拟波形直接叠加和数字音频直接叠加都存在一定的问题只要解决了相关的问题就可以实现混音。如果是专业研究可以研究这两个方向我作为程序员暂时只考虑数字混音的实现。因此本文主要以数字混音通过CPU的浮点计算进行实现。二、数字混音数字混音需要把所有的音频转换为PCM 离散采样数据由 CPU/FPGA 执行浮点数学运算完成增益、声像、滤波、动态处理、信号路由、多轨求和最终输出混合后的 PCM 流。2.1 前置条件2.1.1 PCM格式要求混音的音频文件必须采样率一样这样才能保证单位时间点的数组长度一样。采样位数和声道尽量一致如果不一致只能以最大的作为输出参数。采样率必须一样方便计算采样位数不一样也可以进行混音但是计算会非常麻烦建议转为一样的采样位数后再混音。声道数不一样也可以进行混音也是计算要进行处理建议同声道数的混音。本文章的前置条件全部为 一样才进行混音不考虑特殊场景。2.1.2 核心算法设同一时刻轨道 1、轨道2……轨道n的 采样值增益分别为单轨缩放公式增益立体声平衡系数,多轨道求和为 浮点域线性叠加,三、以webrtc的为例源码在webrtc的frame_combiner.cc文件中3.1 浮点域线性叠加void MixToFloatFrame(const std::vectorAudioFrame* mix_list, size_t samples_per_channel, size_t number_of_channels, MixingBuffer* mixing_buffer) { RTC_DCHECK_LE(samples_per_channel, FrameCombiner::kMaximumChannelSize); RTC_DCHECK_LE(number_of_channels, FrameCombiner::kMaximumNumberOfChannels); // Clear the mixing buffer. for (auto one_channel_buffer : *mixing_buffer) { std::fill(one_channel_buffer.begin(), one_channel_buffer.end(), 0.f); } // Convert to FloatS16 and mix. for (size_t i 0; i mix_list.size(); i) { const AudioFrame* const frame mix_list[i]; for (size_t j 0; j std::min(number_of_channels, FrameCombiner::kMaximumNumberOfChannels); j) { for (size_t k 0; k std::min(samples_per_channel, FrameCombiner::kMaximumChannelSize); k) { (*mixing_buffer)[j][k] frame-data()[number_of_channels * k j]; } } } }1采用浮点运算避免溢出• 如果在 int16 域直接相加两个 20000 相加就会变成 40000超过 32767 导致溢出Wrap-around产生巨大的爆音。• 使用 float 累加可以容纳非常大的中间值float 有巨大的动态范围。2为限幅度做准备• 累加后的浮点数据可能远远超过 int16 的范围。• 后续的 RunLimiter 函数会分析这个浮点缓冲区的峰值。如果峰值过高它会动态调整增益Gain平滑地降低整体音量而不是硬截断Hard Clipping。3.2 限幅控制(防止削波/Clipping)限幅控制有两种方式一种是动态调整增益一种是采用固定的值进行 控制。3.2.1 动态调整增益void Limiter::Process(AudioFrameViewfloat signal) { const auto level_estimate level_estimator_.ComputeLevel(signal); RTC_DCHECK_EQ(level_estimate.size() 1, scaling_factors_.size()); scaling_factors_[0] last_scaling_factor_; std::transform(level_estimate.begin(), level_estimate.end(), scaling_factors_.begin() 1, [this](float x) { return interp_gain_curve_.LookUpGainToApply(x); }); const size_t samples_per_channel signal.samples_per_channel(); RTC_DCHECK_LE(samples_per_channel, kMaximalNumberOfSamplesPerChannel); auto per_sample_scaling_factors rtc::ArrayViewfloat( per_sample_scaling_factors_[0], samples_per_channel); ComputePerSampleSubframeFactors(scaling_factors_, samples_per_channel, per_sample_scaling_factors); ScaleSamples(per_sample_scaling_factors, signal); last_scaling_factor_ scaling_factors_.back(); // Dump data for debug. apm_data_dumper_-DumpRaw(agc2_gain_curve_applier_scaling_factors, samples_per_channel, per_sample_scaling_factors_.data()); }webrtc的实现为1. 看: 估计当前声音有多大。level_estimator进行音频估计level_estimator_电平估计器将音频帧划分为多个子帧Sub-frames通常为 4 个并计算每个子帧的峰值电平或 RMS 电平。2. 算: 根据声音大小决定需要衰减多少查增益曲线。scaling_factors_ 计算子帧增益系数对于当前帧的每个子帧电平 x调用 interp_gain_curve_.LookUpGainToApply(x) 查找映射表3. 平滑: 在样本级别平滑过渡增益特别是快速响应突发的大音量Attack。ComputePerSampleSubframeFactors 计算逐样本增益插值我们不能突然在子帧边界跳变增益例如前 1/4 帧增益为 1.0后 3/4 突然变为 0.5这会产生严重的失真。我们需要为每一个样本计算一个平滑过渡的增益值。Attack Handling阶段特殊处理• 如果检测到电平突然升高scaling_factors[0] scaling_factors[1]即需要快速衰减以防止削波第一个子帧会使用非线性插值幂函数见文件顶部的 InterpolateFirstSubframe。• 原因: 线性插值在攻击阶段可能不够快导致初始样本仍然削波。幂函数插值能更迅速地降低增益优先保证不削波尽管这可能稍微牺牲一点固定增益的有效性。4. 做: 将增益应用到每个样本并确保不溢出。• 即使经过限幅由于浮点精度或极端情况结果仍可能略微超出 int16 的范围-1.0 到 1.0 对应的浮点值。• SafeClamp 确保最终输出严格限制在 [-1.0, 1.0] 范围内防止后续转换为 int16 时发生溢出。