不同采样率、采样数的音频缓冲区如何实现相加混合
不同参数音频帧混合实现方案
核心逻辑
要混合采样率、采样数不一致的音频帧,必须先将所有输入帧的采样率、通道数、时长三个参数统一到和输出帧一致,再逐采样点相加,否则采样点无法对齐,混合结果会出现杂音、时长错乱等问题。
实现步骤
- 确定输出帧的基准参数
- 采样率:根据业务需求选择,常见可选48000(音视频通话、直播场景)或44100(音乐场景),此处以48000为例
- 通道数:统一为所有输入帧的最大通道数,或业务指定值,此处以单声道1为例
- 输出采样数:先计算每个输入帧的时长
时长 = 单通道采样数 / 采样率,输出时长取所有输入帧的最大时长,输出采样数 = 输出时长 * 输出采样率,向上取整即可。
以你给出的示例计算:input1时长 = 1000 / 48000 ≈ 0.0208s
input2时长 = 600 / 44100 ≈ 0.0136s
输出取最大时长0.0208s,对应采样数 = 0.0208 * 48000 = 1000
- 统一所有输入帧的通道数
- 多声道转单声道:每个位置的采样值为所有通道对应采样值的平均值
- 单声道转多声道:将单声道采样值复制到所有通道对应位置即可
- 对所有输入帧做重采样,统一为输出采样率
可以用成熟的开源库(如libsamplerate、FFmpeg swresample)实现,也可以自己实现简单的线性插值重采样,核心逻辑是根据输入输出采样率的比值,计算目标采样点对应的原音频位置,用相邻的两个采样点插值得到新采样值。 - 对齐采样数
重采样后所有输入帧已经是输出采样率,将采样数少于输出采样数的输入帧,在尾部补0,直到和输出采样数一致。 - 逐采样点相加并限幅
两个采样值相加后可能超出音频归一化范围[-1.0, 1.0],需要做限幅处理避免爆音:大于1.0的设为1.0,小于-1.0的设为-1.0。
完整示例代码
#include <stdlib.h> #include <math.h> // 音频帧结构定义 typedef struct { int sample_rate; // 该缓冲区的采样率,通常为48000或44100 int no_channels; // 音频通道数 int no_samples; // 每通道的音频采样数 float* p_data; // 音频数据 } AudioFrame; // 简单线性插值重采样实现(仅支持单声道) float* resample_mono(float* input, int in_sample_rate, int in_samples, int out_sample_rate, int* out_samples) { *out_samples = (int)ceil((double)in_samples * out_sample_rate / in_sample_rate); float* output = (float*)malloc(*out_samples * sizeof(float)); double ratio = (double)in_sample_rate / out_sample_rate; for (int i = 0; i < *out_samples; i++) { double pos = i * ratio; int idx = (int)pos; double frac = pos - idx; if (idx >= in_samples - 1) { output[i] = input[in_samples - 1]; } else { output[i] = input[idx] * (1 - frac) + input[idx + 1] * frac; } } return output; } AudioFrame mix_two_frames(AudioFrame input1, AudioFrame input2) { AudioFrame output; // 1. 确定输出基准参数 output.sample_rate = 48000; output.no_channels = 1; double dur1 = (double)input1.no_samples / input1.sample_rate; double dur2 = (double)input2.no_samples / input2.sample_rate; double max_dur = fmax(dur1, dur2); output.no_samples = (int)ceil(max_dur * output.sample_rate); output.p_data = (float*)calloc(output.no_samples, sizeof(float)); // 初始化为0 // 2. 重采样输入1到输出采样率 int resampled1_samples; float* resampled1 = resample_mono(input1.p_data, input1.sample_rate, input1.no_samples, output.sample_rate, &resampled1_samples); // 写入输出缓冲区 for (int i = 0; i < resampled1_samples && i < output.no_samples; i++) { output.p_data[i] += resampled1[i]; } free(resampled1); // 3. 重采样输入2到输出采样率 int resampled2_samples; float* resampled2 = resample_mono(input2.p_data, input2.sample_rate, input2.no_samples, output.sample_rate, &resampled2_samples); // 写入输出缓冲区 for (int i = 0; i < resampled2_samples && i < output.no_samples; i++) { output.p_data[i] += resampled2[i]; } free(resampled2); // 4. 限幅防止爆音 for (int i = 0; i < output.no_samples; i++) { if (output.p_data[i] > 1.0f) output.p_data[i] = 1.0f; if (output.p_data[i] < -1.0f) output.p_data[i] = -1.0f; } return output; }
内容的提问来源于stack exchange,提问作者cerutti davide
相关产品推荐
相关产品推荐

