Add SIMD audio processing interface and implementations

- Created a new SIMD interface header and source files for audio processing functions.
- Implemented functions for filling buffers, mixing audio, applying gain, calculating RMS and peak values, normalizing audio, converting stereo to mono, limiting audio, fading audio, and a simple equalizer.
- Added SSE-specific implementations for the audio processing functions to leverage SIMD for performance improvements.
- Updated CMakeLists.txt files to include new libraries and link dependencies for the SIMD interface and SSE implementations.
- Introduced a static test helper library for unit testing with Google Test framework.
This commit is contained in:
2025-11-14 23:27:55 +08:00
parent a96b6ce25c
commit 886b6843e6
53 changed files with 4326 additions and 7305 deletions

View File

@@ -0,0 +1,526 @@
/**
* @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 <cmath>
#include <algorithm>
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<double>(src[i]) * static_cast<double>(src[i]);
}
// 步骤3-4: 求平均值并开平方根
return static_cast<float>(std::sqrt(sum_squares / static_cast<double>(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
* 当信号超过阈值时,立即降低增益,防止削波
* - 释放时间Release50ms
* 当信号低于阈值时,缓慢恢复增益,避免"呼吸"效应
*
* 释放系数计算:
* 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<float>(i) / static_cast<float>(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<float>(fade_out_offset) / static_cast<float>(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; // 保存高频滤波器状态
}
}
}