/** * @file scalar_audio_processing_func.cpp * @brief 音频处理函数的标量(非SIMD)实现 * * 本文件提供所有音频处理函数的标量实现版本,具有以下特点: * * 1. **平台通用性**: * - 不依赖任何特定的SIMD指令集 * - 可在任何支持C++的平台上编译运行 * - 作为SIMD实现的参考基准和后备方案 * * 2. **代码可读性**: * - 算法逻辑清晰直观 * - 易于理解和维护 * - 便于验证SIMD实现的正确性 * * 3. **性能特点**: * - 单次处理一个样本(无并行化) * - 编译器可能进行自动向量化 * - 适合小批量数据或不支持SIMD的平台 * * 4. **精度考虑**: * - 累加操作使用double提高精度(如RMS计算) * - 避免浮点精度损失 * - 保证计算结果的准确性 */ #include "scalar_audio_processing_func.h" #include #include namespace scalar_audio_processing_func { /** * @brief 音频混合函数(标量实现) * * 将两路音频信号逐样本相加,实现音频混合效果。 * * 算法原理: * output[i] = input1[i] + input2[i] * * 这是最基础的音频混合方式,适用于: * - 多轨混音 * - 音频叠加 * - 效果器链处理 * * 注意事项: * - 需要调用方确保输出不会溢出(超过±1.0范围) * - 如需避免削波,应先对输入信号进行适当的衰减 * - 本函数不做削波保护,以保持性能 * * @param src1 第一路输入音频数据 * @param src2 第二路输入音频数据 * @param dst 输出音频数据(可以与src1或src2相同,实现原地处理) * @param num_samples 样本数量 */ void mix_audio(const float* src1, const float* src2, float* dst, size_t num_samples) { // 逐样本相加,简单直接 // 现代编译器可能会自动向量化此循环(需要-O2或更高优化级别) for (size_t i = 0; i < num_samples; ++i) { dst[i] = src1[i] + src2[i]; } } /** * @brief 音量增益调节函数(标量实现) * * 对音频信号应用线性增益(音量调节)。 * * 算法原理: * output[i] = input[i] * gain * * 增益参数说明: * - gain = 1.0: 保持原音量不变 * - gain > 1.0: 放大音量(如2.0表示音量翻倍,+6dB) * - 0 < gain < 1.0: 衰减音量(如0.5表示音量减半,-6dB) * - gain = 0.0: 静音 * - gain < 0.0: 反相(改变相位) * * 应用场景: * - 音量调节 * - 淡入淡出效果的基础 * - 归一化处理 * - 混音时的声道平衡 * * @param src 输入音频数据 * @param dst 输出音频数据(可与src相同) * @param gain 增益系数 * @param num_samples 样本数量 */ void apply_gain(const float* src, float* dst, float gain, size_t num_samples) { // 逐样本应用增益 // 这是最简单的音频处理操作之一 for (size_t i = 0; i < num_samples; ++i) { dst[i] = src[i] * gain; } } /** * @brief 计算音频RMS电平(均方根值) * * RMS (Root Mean Square) 是衡量音频平均响度的重要指标。 * * 数学公式: * RMS = sqrt( (1/N) * Σ(x[i]²) ) * 其中: * - N: 样本总数 * - x[i]: 第i个样本值 * - Σ: 求和符号 * * 算法步骤: * 1. 对每个样本求平方 * 2. 累加所有平方值 * 3. 除以样本数(求平均) * 4. 对结果开平方根 * * 精度考虑: * - 使用double进行累加,避免float精度损失 * - 大量小数值累加时,float可能损失精度 * - 最终结果转回float以匹配音频数据类型 * * 应用场景: * - 音量表显示 * - 自动增益控制(AGC) * - 音频分析 * - 压缩器阈值计算 * * @param src 输入音频数据 * @param num_samples 样本数量 * @return RMS值(通常在0.0到1.0之间,但可能超过1.0) */ float calculate_rms(const float* src, size_t num_samples) { double sum_squares = 0.0; // 使用double提高累加精度 // 步骤1-2: 逐样本求平方并累加 for (size_t i = 0; i < num_samples; ++i) { // 转换为double再相乘,避免精度损失 sum_squares += static_cast(src[i]) * static_cast(src[i]); } // 步骤3-4: 求平均值并开平方根 return static_cast(std::sqrt(sum_squares / static_cast(num_samples))); } /** * @brief 计算音频峰值电平 * * 峰值检测用于找出音频信号中的最大幅度值。 * * 算法原理: * peak = max(|x[0]|, |x[1]|, ..., |x[N-1]|) * * 算法步骤: * 1. 初始化峰值为0 * 2. 遍历所有样本 * 3. 对每个样本取绝对值 * 4. 如果绝对值大于当前峰值,更新峰值 * 5. 返回最终峰值 * * 为什么使用绝对值: * - 音频样本可能为负值(如-0.8) * - 负值的幅度同样重要 * - 峰值表示最大偏离零点的距离 * * 应用场景: * - 防止削波(确保不超过±1.0) * - 峰值表显示 * - 归一化处理的依据 * - 限幅器/压缩器的输入分析 * * 性能特点: * - 单次遍历,O(N)时间复杂度 * - 不使用排序,效率高 * * @param src 输入音频数据 * @param num_samples 样本数量 * @return 峰值(0.0到可能大于1.0的正数) */ float calculate_peak(const float* src, size_t num_samples) { float peak = 0.0f; // 初始峰值为0 // 遍历所有样本,寻找最大绝对值 for (size_t i = 0; i < num_samples; ++i) { float abs_sample = std::fabs(src[i]); // 取绝对值 if (abs_sample > peak) { peak = abs_sample; // 更新峰值 } } return peak; } /** * @brief 音频归一化函数(标量实现) * * 归一化是将音频信号缩放到指定的峰值电平。 * * 算法原理: * 1. 找出当前音频的峰值 current_peak * 2. 计算增益系数 gain = target_peak / current_peak * 3. 对所有样本应用此增益 * * 数学公式: * output[i] = input[i] * (target_peak / current_peak) * * 边界情况处理: * 1. num_samples == 0: 无数据,直接返回 * 2. target_peak <= 0: 无效目标,直接返回 * 3. current_peak < 1e-10: 信号过弱(近乎静音),输出为0 * * 为什么检测1e-10: * - 避免除以接近零的数(导致极大增益) * - 过小的信号主要是噪声,放大无意义 * - 1e-10在浮点数范围内足够小 * * 应用场景: * - 音频电平标准化 * - 音量响度统一 * - 防止削波同时最大化响度 * - 音频制作的母带处理 * * @param src 输入音频数据 * @param dst 输出音频数据 * @param target_peak 目标峰值(通常为0.99或1.0) * @param num_samples 样本数量 */ void normalize_audio(const float* src, float* dst, float target_peak, size_t num_samples) { // 边界情况1-2: 参数有效性检查 if (num_samples == 0 || target_peak <= 0.0f) { return; } // 步骤1: 计算当前音频的峰值 const float current_peak = calculate_peak(src, num_samples); // 边界情况3: 信号过弱,输出静音 if (current_peak < 1e-10f) { for (size_t i = 0; i < num_samples; ++i) { dst[i] = 0.0f; } return; } // 步骤2: 计算归一化增益因子 // gain = target / current,使得 current * gain = target const float gain_factor = target_peak / current_peak; // 步骤3: 对所有样本应用增益 for (size_t i = 0; i < num_samples; ++i) { dst[i] = src[i] * gain_factor; } } /** * @brief 立体声到单声道转换(标量实现) * * 将立体声(双声道)音频转换为单声道音频。 * * 数据布局: * - 输入立体声格式:[L0, R0, L1, R1, L2, R2, ...] * 其中L=左声道,R=右声道,数字=样本索引 * - 输出单声道格式:[M0, M1, M2, ...] * 其中M=混合后的单声道 * * 算法原理: * mono[i] = (left[i] + right[i]) / 2 * * 为什么除以2: * - 保持音量电平一致 * - 防止相加后溢出 * - 符合音频工程标准 * * 索引计算: * - 第i个立体声样本对占用位置:[2*i, 2*i+1] * - stereo_src[i*2] 是左声道 * - stereo_src[i*2+1] 是右声道 * * 应用场景: * - 降低音频数据量(单声道占用空间减半) * - 兼容单声道设备 * - 音频分析(如语音识别) * - 节省带宽 * * 注意事项: * - 会丢失立体声的空间信息 * - 不可逆转换 * * @param stereo_src 立体声输入数据(交错格式:L,R,L,R...) * @param mono_dst 单声道输出数据 * @param num_stereo_samples 立体声样本对的数量(不是总样本数) */ void stereo_to_mono(const float* stereo_src, float* mono_dst, size_t num_stereo_samples) { // 边界情况处理 if (num_stereo_samples == 0) { return; } // 对每个立体声样本对(左声道,右声道),计算其平均值作为单声道样本 for (size_t i = 0; i < num_stereo_samples; ++i) { const float left = stereo_src[i * 2]; // 左声道位于偶数索引 const float right = stereo_src[i * 2 + 1]; // 右声道位于奇数索引 mono_dst[i] = (left + right) * 0.5f; // 取平均值 } } /** * @brief 音频限幅器(动态范围压缩) * * 限幅器用于防止音频信号超过指定阈值,避免削波失真。 * * 工作原理: * 1. 检测每个样本的幅度 * 2. 如果超过阈值,计算所需的衰减增益 * 3. 平滑应用增益变化(避免咔嗒声) * 4. 输出限幅后的样本 * * 包络跟随器特性: * - 攻击时间(Attack):立即响应(0ms) * 当信号超过阈值时,立即降低增益,防止削波 * - 释放时间(Release):50ms * 当信号低于阈值时,缓慢恢复增益,避免"呼吸"效应 * * 释放系数计算: * release_coeff = exp(-1 / (release_time * sample_rate)) * 这是一阶低通滤波器的时间常数 * * 增益更新公式: * - 攻击:current_gain = target_gain(立即) * - 释放:current_gain = target_gain + (current_gain - target_gain) * release_coeff * 这是指数平滑,使增益变化更自然 * * 状态保持: * - limiter_state保存上一次处理的增益值 * - 确保连续音频块之间的增益平滑过渡 * - 如果为nullptr,则从1.0(无衰减)开始 * * 应用场景: * - 防止输出削波 * - 保护扬声器/耳机 * - 音频制作的母带处理 * - 实时音频处理 * * @param src 输入音频数据 * @param dst 输出音频数据 * @param threshold 阈值(通常为0.99或1.0) * @param limiter_state 限幅器状态(保存增益值,可为nullptr) * @param sample_rate 采样率(用于计算时间常数) * @param num_samples 样本数量 */ void limit_audio(const float* src, float* dst, float threshold, float* limiter_state, float sample_rate, size_t num_samples) { // 边界情况处理 if (num_samples == 0 || threshold <= 0.0f) { return; } // 释放时间参数(与SIMD实现保持一致) constexpr float release_time = 0.05f; // 50ms释放时间 // 计算一阶低通滤波器的释放系数 const float release_coeff = std::exp(-1.0f / (release_time * sample_rate)); // 初始化限幅器状态 // 如果有状态值则继续上次的处理,否则从1.0(无衰减)开始 float current_gain = limiter_state != nullptr ? *limiter_state : 1.0f; // 逐样本处理 for (size_t i = 0; i < num_samples; ++i) { float sample = src[i]; float abs_sample = std::fabs(sample); // 取绝对值 // 计算所需的增益以将信号限制在阈值内 // 如果 |sample| > threshold,则 gain = threshold / |sample| < 1.0 // 否则不需要衰减,gain = 1.0 float target_gain = abs_sample > threshold ? threshold / abs_sample : 1.0f; // 平滑增益变化(包络跟随器) if (target_gain < current_gain) { // 攻击阶段:立即降低增益(快速响应) current_gain = target_gain; } else { // 释放阶段:缓慢恢复增益(指数平滑) // 新增益 = 目标增益 + (当前增益 - 目标增益) * 释放系数 current_gain = target_gain + (current_gain - target_gain) * release_coeff; } // 应用计算出的增益 dst[i] = sample * current_gain; } // 保存状态以供下次调用使用(实现连续处理) if (limiter_state != nullptr) { *limiter_state = current_gain; } } /** * @brief 音频淡入淡出效果(标量实现) * * 在音频开头应用淡入效果,结尾应用淡出效果,避免突然的音频切换。 * * 音频分段: * 1. 淡入段:[0, fade_in_samples),增益从0线性增加到1 * 2. 中间段:[fade_in_samples, num_samples - fade_out_samples),保持原样 * 3. 淡出段:[num_samples - fade_out_samples, num_samples),增益从1线性减少到0 * * 应用场景:音频片段拼接、音乐播放器淡入淡出、视频编辑音频过渡 * * @param src 输入音频数据 * @param dst 输出音频数据 * @param fade_in_samples 淡入样本数 * @param fade_out_samples 淡出样本数 * @param num_samples 总样本数 */ void fade_audio(const float* src, float* dst, size_t fade_in_samples, size_t fade_out_samples, size_t num_samples) { // 边界情况处理 if (num_samples == 0) { return; } // 第一段:处理淡入部分 if (fade_in_samples > 0) { // 淡入不能超过总样本数 for (size_t i = 0; i < std::min(fade_in_samples, num_samples); ++i) { // 计算线性淡入增益:0 -> 1 const float gain = static_cast(i) / static_cast(fade_in_samples); dst[i] = src[i] * gain; } } // 第二段:处理中间部分(直接复制) const size_t middle_start = fade_in_samples; const size_t middle_end = num_samples > fade_out_samples ? num_samples - fade_out_samples : 0; if (middle_end > middle_start) { for (size_t i = middle_start; i < middle_end; ++i) { dst[i] = src[i]; } } // 第三段:处理淡出部分 if (fade_out_samples > 0 && num_samples > fade_out_samples) { const size_t fade_out_start = num_samples - fade_out_samples - 1; for (size_t i = fade_out_start; i < num_samples; ++i) { // 计算线性淡出增益:1 -> 0 const size_t fade_out_offset = i - fade_out_start; const float gain = 1.0f - static_cast(fade_out_offset) / static_cast(fade_out_samples); dst[i] = src[i] * gain; } } } /** * @brief 简单三段均衡器(标量实现) * * 实现简化的三段参数均衡器,分别控制低频、中频、高频增益。 * * 频段划分策略: * 1. 低频段:通过低通滤波器提取(约500Hz以下) * 2. 高频段:通过高通滤波器提取(约5kHz以上) * 3. 中频段:原始信号减去低频和高频 * * 一阶IIR滤波器:output = cutoff * input + (1 - cutoff) * state * - low_cutoff = 0.02: 低通系数 * - high_cutoff = 0.1: 高通系数 * - mid_factor = 0.7: 中频能量平衡系数 * * 状态变量(eq_state: * - eq_state[0]: 低频滤波器状态 * - eq_state[1]: 高频滤波器状态 * - 用于IIR滤波器连续性,确保音频块间平滑过渡 * * 应用场景:音质调整、频率平衡、音色调节 * * @param src 输入音频数据 * @param dst 输出音频数据 * @param low_gain 低频增益系数 * @param mid_gain 中频增益系数 * @param high_gain 高频增益系数 * @param eq_state EQ状态数组(2个float,可为nullptr) * @param num_samples 样本数量 */ void simple_eq(const float* src, float* dst, float low_gain, float mid_gain, float high_gain, float* eq_state, size_t num_samples) { // 边界情况处理 if (num_samples == 0) { return; } // 简化的频率分割系数(与SIMD实现保持一致) constexpr float low_cutoff = 0.02f; // 低通滤波器系数(约500Hz) constexpr float high_cutoff = 0.1f; // 高通滤波器系数(约5kHz) constexpr float mid_factor = 0.7f; // 中频能量调整系数 // 初始化EQ状态(IIR波器的记忆) float low_state = eq_state != nullptr ? eq_state[0] : 0.0f; // 低频滤波器状态 float high_state = eq_state != nullptr ? eq_state[1] : 0.0f; // 高频滤波器状态 // 逐样本处理 for (size_t i = 0; i < num_samples; ++i) { float input = src[i]; // 步骤1: 低通滤波器(提取低频) // 一阶IIR低通:new = a*input + (1-a)*old float low_output = low_cutoff * input + (1.0f - low_cutoff) * low_state; low_state = low_output; // 更新状态 // 步骤2: 高通滤波器(从剩余信号提取高频) float high_input = input - low_output; // 减去低频 float high_output = high_cutoff * high_input + (1.0f - high_cutoff) * high_state; high_state = high_output; // 更新状态 // 步骤3: 中频提取(原始信号减去低频和高频) float mid_output = (input - low_output - high_output) * mid_factor; // 步骤4: 混合三段并应用各自的增益 dst[i] = low_output * low_gain + mid_output * mid_gain + high_output * high_gain; } // 保存状态以供下次调用使用 if (eq_state != nullptr) { eq_state[0] = low_state; // 保存低频滤波器状态 eq_state[1] = high_state; // 保存高频滤波器状态 } } }