- 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.
527 lines
17 KiB
C++
527 lines
17 KiB
C++
/**
|
||
* @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)
|
||
* 当信号超过阈值时,立即降低增益,防止削波
|
||
* - 释放时间(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<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; // 保存高频滤波器状态
|
||
}
|
||
}
|
||
}
|