Files
Alicho/src/simd/misc/audio_processing/scalar_audio_processing_func.cpp
nanako 886b6843e6 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.
2025-11-14 23:27:55 +08:00

527 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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; // 保存高频滤波器状态
}
}
}