当前位置: 首页 > news >正文

音频重采样(Audio Resampling)实现指南

一、核心原理与架构

1.1 什么是音频重采样?

音频重采样是将音频信号从源采样率(Fs_in)转换为目标采样率(Fs_out)的过程。例如:将48kHz麦克风采集的语音转换为16kHz供语音识别算法使用。

1.2 重采样的数学本质

原始信号 x[n] (Fs_in)  →  插值/抽取  →  新信号 y[m] (Fs_out)

1.3 两种重采样方式

类型 公式 特点 应用场景
整数倍重采样 Fs_out = L × Fs_in 简单高效 MCU实时处理
分数倍重采样 Fs_out = (L/M) × Fs_in 灵活通用 PC/嵌入式通用

二、STM32 嵌入式整数倍重采样(推荐)

2.1 系统架构

麦克风/音频输入↓
抗混叠滤波器(FIR)↓
插值(补零)↓
低通滤波(FIR)↓
抽取↓
输出到DAC/算法

2.2 完整源码实现

(1)头文件(audio_resample.h)

/*** @file audio_resample.h* @brief STM32音频重采样库(整数倍)*/
#ifndef __AUDIO_RESAMPLE_H
#define __AUDIO_RESAMPLE_H#include <stdint.h>
#include <stdbool.h>/* 重采样配置 */
#define RESAMPLE_MAX_FACTOR   8       // 最大插值因子
#define FIR_FILTER_TAPS      33       // FIR滤波器阶数(奇数)
#define AUDIO_BUFFER_SIZE   256       // 音频缓冲区大小/* 重采样状态结构 */
typedef struct {uint8_t interpolation;            // 插值因子 Luint8_t decimation;               // 抽取因子 Mint16_t fir_coeff[FIR_FILTER_TAPS]; // FIR滤波器系数int32_t history[FIR_FILTER_TAPS];   // 历史样本缓存uint8_t history_index;           // 历史缓存索引uint32_t input_samples;           // 输入样本计数uint32_t output_samples;          // 输出样本计数bool initialized;                // 初始化标志
} ResampleState;/* 函数声明 */
void Resample_Init(ResampleState* rs, uint8_t L, uint8_t M);
int16_t Resample_Process(ResampleState* rs, int16_t input_sample);
void Resample_ProcessBlock(ResampleState* rs, int16_t* input, uint16_t input_len,int16_t* output, uint16_t* output_len);
void Resample_Reset(ResampleState* rs);#endif /* __AUDIO_RESAMPLE_H */

(2)FIR滤波器系数生成(audio_resample.c)

/*** @file audio_resample.c* @brief 音频重采样实现(基于FIR滤波)*/
#include "audio_resample.h"
#include <string.h>
#include <math.h>/* 生成低通FIR滤波器系数(Hamming窗) */
static void GenerateFIRCoeff(int16_t* coeff, uint8_t taps, float cutoff)
{float sum = 0.0f;for (int i = 0; i < taps; i++) {float n = (float)i - (taps - 1) / 2.0f;float sinc = (n == 0) ? 1.0f : sinf(2.0f * M_PI * cutoff * n) / (2.0f * M_PI * cutoff * n);float window = 0.54f - 0.46f * cosf(2.0f * M_PI * i / (taps - 1)); // Hamming窗coeff[i] = (int16_t)(sinc * window * 32767.0f);sum += coeff[i];}// 归一化for (int i = 0; i < taps; i++) {coeff[i] = (int16_t)((float)coeff[i] * 32767.0f / sum);}
}/* 初始化重采样器 */
void Resample_Init(ResampleState* rs, uint8_t L, uint8_t M)
{if (rs == NULL || L == 0 || M == 0) return;rs->interpolation = L;rs->decimation = M;// 生成FIR滤波器系数float cutoff = 0.5f / (L > M ? L : M);  // 截止频率GenerateFIRCoeff(rs->fir_coeff, FIR_FILTER_TAPS, cutoff);// 初始化历史缓存memset(rs->history, 0, sizeof(rs->history));rs->history_index = 0;rs->input_samples = 0;rs->output_samples = 0;rs->initialized = true;
}/* 单样本重采样处理 */
int16_t Resample_Process(ResampleState* rs, int16_t input_sample)
{if (!rs->initialized) return 0;int32_t accumulator = 0;int16_t output_sample = 0;// 1. 将输入样本存入历史缓存rs->history[rs->history_index] = input_sample;rs->history_index = (rs->history_index + 1) % FIR_FILTER_TAPS;// 2. 插值(补零)static uint8_t phase = 0;if (phase == 0) {// 3. FIR滤波uint8_t hist_idx = rs->history_index;for (int i = 0; i < FIR_FILTER_TAPS; i++) {hist_idx = (hist_idx == 0) ? FIR_FILTER_TAPS - 1 : hist_idx - 1;accumulator += (int32_t)rs->history[hist_idx] * rs->fir_coeff[i];}output_sample = (int16_t)(accumulator >> 15);  // 右移15位恢复缩放}// 4. 抽取phase++;if (phase >= rs->interpolation) {phase = 0;}rs->input_samples++;if (phase == 0) {rs->output_samples++;return output_sample;}return 0;  // 非输出时刻返回0
}/* 块处理重采样 */
void Resample_ProcessBlock(ResampleState* rs, int16_t* input, uint16_t input_len,int16_t* output, uint16_t* output_len)
{uint16_t out_idx = 0;for (uint16_t i = 0; i < input_len; i++) {int16_t sample = Resample_Process(rs, input[i]);if (sample != 0) {  // 非零表示有效输出if (out_idx < *output_len) {output[out_idx++] = sample;}}}*output_len = out_idx;
}/* 重置重采样器状态 */
void Resample_Reset(ResampleState* rs)
{memset(rs->history, 0, sizeof(rs->history));rs->history_index = 0;rs->input_samples = 0;rs->output_samples = 0;
}

(3)主程序示例(main.c)

/*** @file main.c* @brief STM32音频重采样示例*/
#include "stm32f10x.h"
#include "audio_resample.h"
#include "i2s.h"
#include "dma.h"/* 全局变量 */
ResampleState resampler;
int16_t audio_input[AUDIO_BUFFER_SIZE];
int16_t audio_output[AUDIO_BUFFER_SIZE * 4];  // 4倍缓冲区
uint16_t output_len = 0;int main(void)
{/* 1. 系统初始化 */System_Init();I2S_Init();DMA_Init();/* 2. 初始化重采样器(48kHz → 16kHz,L=1, M=3) */Resample_Init(&resampler, 1, 3);  // 48/3 = 16kHz/* 3. 主循环 */while (1) {/* 3.1 等待I2S接收完成 */if (I2S_ReceiveComplete()) {/* 3.2 获取音频数据 */I2S_GetData(audio_input, AUDIO_BUFFER_SIZE);/* 3.3 重采样处理 */uint16_t max_output = AUDIO_BUFFER_SIZE * 4;Resample_ProcessBlock(&resampler, audio_input, AUDIO_BUFFER_SIZE,audio_output, &max_output);output_len = max_output;/* 3.4 发送处理后的数据到语音识别算法 */Speech_Process(audio_output, output_len);}}
}

三、PC/Linux 分数倍重采样(通用)

3.1 基于线性插值的轻量级实现

适合资源受限的系统,音质尚可。

/*** @file linear_resample.c* @brief 线性插值重采样(分数倍)*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>/* 线性插值重采样 */
uint32_t Linear_Resample(int16_t* input, uint32_t input_len,int16_t* output, uint32_t output_capacity,uint32_t fs_in, uint32_t fs_out)
{if (fs_in == fs_out) {uint32_t copy_len = (input_len < output_capacity) ? input_len : output_capacity;memcpy(output, input, copy_len * sizeof(int16_t));return copy_len;}double ratio = (double)fs_out / fs_in;uint32_t output_len = (uint32_t)(input_len * ratio);if (output_len > output_capacity) {output_len = output_capacity;}for (uint32_t i = 0; i < output_len; i++) {double src_index = i / ratio;uint32_t index_low = (uint32_t)src_index;uint32_t index_high = index_low + 1;double frac = src_index - index_low;if (index_high >= input_len) {output[i] = input[index_low];} else {// 线性插值: y = y0 + (y1 - y0) * fracoutput[i] = (int16_t)(input[index_low] + (input[index_high] - input[index_low]) * frac);}}return output_len;
}/* 测试示例 */
int main()
{int16_t input[10] = {0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000};int16_t output[20];uint32_t out_len = Linear_Resample(input, 10, output, 20, 48000, 16000);printf("Resampled Output (%d samples):\n", out_len);for (uint32_t i = 0; i < out_len; i++) {printf("%d ", output[i]);}printf("\n");return 0;
}

3.2 基于SpeexDSP的高质量重采样

这是工业界常用的高质量重采样库。

/*** @file speex_resample.c* @brief 使用SpeexDSP库的重采样示例*/
#include <speex/speex_resampler.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define FRAME_SIZE 160int main()
{SpeexResamplerState *resampler;int err;spx_uint32_t in_frames = FRAME_SIZE;spx_uint32_t out_frames = FRAME_SIZE;spx_uint32_t in_frames_used, out_frames_generated;short in_buf[FRAME_SIZE];short out_buf[FRAME_SIZE * 2];  // 缓冲区放大// 1. 初始化重采样器(单声道,48kHz → 16kHz)resampler = speex_resampler_init(1, 48000, 16000, 10, &err);if (err != 0) {printf("Resampler init failed: %d\n", err);return -1;}// 2. 设置重采样质量(0-10,越高越好)speex_resampler_set_quality(resampler, 10);// 3. 处理音频数据while (1) {// 读取一帧音频数据if (read_audio_frame(in_buf, FRAME_SIZE) <= 0) break;// 执行重采样speex_resampler_process_int(resampler, 0,in_buf, &in_frames,out_buf, &out_frames);// 输出重采样后的数据write_audio_frame(out_buf, out_frames);}// 4. 清理资源speex_resampler_destroy(resampler);return 0;
}

四、性能优化建议

4.1 算法优化

/* 定点运算优化(避免浮点) */
typedef struct {int32_t fir_coeff_fixed[FIR_FILTER_TAPS];  // Q15格式int64_t accumulator;                       // 64位累加器
} FastResampler;/* SIMD指令优化(ARM NEON) */
#ifdef __ARM_NEON
#include <arm_neon.h>
void FIR_Filter_NEON(int16_t* input, int16_t* coeff, int16_t* output, int len)
{int16x8_t in_vec, coeff_vec;int32x4_t acc_low, acc_high;for (int i = 0; i < len; i += 8) {in_vec = vld1q_s16(input + i);coeff_vec = vld1q_s16(coeff);acc_low = vmull_s16(vget_low_s16(in_vec), vget_low_s16(coeff_vec));acc_high = vmull_s16(vget_high_s16(in_vec), vget_high_s16(coeff_vec));// ... 累加处理}
}
#endif

4.2 内存优化

/* 环形缓冲区替代线性缓冲区 */
typedef struct {int16_t buffer[AUDIO_BUFFER_SIZE];uint16_t write_ptr;uint16_t read_ptr;uint16_t count;
} CircularBuffer;/* 原地重采样(避免额外拷贝) */
void InPlace_Resample(int16_t* buffer, uint32_t len, uint32_t factor)
{// 从后向前处理,避免覆盖未处理的数据for (int i = len - 1; i >= 0; i--) {buffer[i * factor] = buffer[i];for (int j = 1; j < factor; j++) {buffer[i * factor + j] = 0;  // 插值零点}}
}

参考代码 音频重采样 www.youwenfan.com/contentcnu/56438.html

五、测试与验证

5.1 测试代码

/* 重采样质量测试 */
void Test_ResampleQuality(void)
{int16_t sine_wave[1000];int16_t resampled[3000];uint32_t out_len;// 生成1kHz正弦波(48kHz采样率)for (int i = 0; i < 1000; i++) {sine_wave[i] = (int16_t)(32767 * sin(2 * M_PI * 1000 * i / 48000.0));}// 重采样到16kHzResampleState rs;Resample_Init(&rs, 1, 3);  // 48kHz → 16kHzResample_ProcessBlock(&rs, sine_wave, 1000, resampled, &out_len);printf("Input samples: 1000, Output samples: %d\n", out_len);// 验证频率是否正确// 理论上输出应该是333个样本的1kHz正弦波
}

5.2 性能基准

算法 复杂度 延迟 音质 适用场景
最近邻 O(n) 0 快速原型
线性插值 O(n) 嵌入式实时
FIR滤波 O(n×taps) 通用音频
SpeexDSP O(n×log n) 专业音频

六、常见问题解决

6.1 抗混叠处理

/* 重采样前必须加抗混叠滤波器 */
void AntiAlias_Filter(int16_t* input, uint32_t len, uint32_t fs_out)
{// 截止频率设为 fs_out/2float cutoff = fs_out / 2.0f;// 使用FIR或IIR滤波器
}

6.2 相位失真

/* 使用线性相位FIR滤波器避免相位失真 */
#define FIR_PHASE_LINEAR 1
#define FIR_PHASE_MINIMUM 0

6.3 缓冲区溢出

/* 计算安全的输出缓冲区大小 */
uint32_t Safe_Output_Size(uint32_t input_len, uint32_t fs_in, uint32_t fs_out)
{return (uint32_t)ceil((double)input_len * fs_out / fs_in) + 10;
}
http://www.jsqmd.com/news/763621/

相关文章:

  • 别再一个个部署模型了!用Xinference在AutoDL上一次性搞定Embedding、Rerank和Qwen(附完整命令清单)
  • AI 英语伴学 APP的开发
  • 量子网络模拟中的张量网络技术与应用
  • 新手猫粮创业者的避坑指南与成功攻略
  • 【前端(十三)】JavaScript 数组与字符串笔记
  • Mac mini 从零开始:新建隔离用户 + 完整安装 Hermes Agent
  • 别再只会用等号了!C++ vector赋值,swap和assign到底哪个更快?
  • 程序化噪声在游戏开发中的应用:从Perlin到Shader实战
  • Barlow字体超级家族:如何用一个开源字体解决你的多平台设计统一难题
  • 效率提升:用快马ai一键生成winutil多模块工具箱代码框架
  • Golden UPF Flow实战解析:如何用一份UPF搞定RTL到门级的低功耗验证
  • LIDA:基于大语言模型的自然语言数据可视化代码生成工具
  • 5个常见游戏控制器兼容性难题:XOutput如何让旧手柄在现代游戏中重获新生
  • Obsidian BMO Chatbot:在笔记软件中集成AI助手的配置与实战指南
  • 为Alexa注入ChatGPT灵魂:智能语音助手开发实战指南
  • Windows右键菜单管理终极指南:5分钟掌握系统级菜单定制
  • C++链表学习心得
  • 别再死记硬背了!用Multisim仿真带你直观理解运放负反馈的三大魔法(增益、带宽、阻抗)
  • JESD204B同步实战:在Vivado里配置Xilinx IP核时,这几个参数千万别设错
  • 终极窗口控制指南:如何用WindowResizer强制调整任意窗口尺寸
  • 【软考高级架构】论文范文06——论DDD领域驱动设计及其应用
  • Opus 4.7 + GPT-5.5“双核驱动”——2026最强AI编程工作流实测
  • 考研数学救命稻草:一阶和二阶微分方程的通解公式,我帮你整理好了(附880/660真题解法)
  • 数据分析新手福音:告别复杂spss安装,用快马ai轻松入门统计
  • AI编码助手安全技能集成:vt、gakido等工具实战指南
  • 大模型应用开发入门:收藏!Java开发者如何精准转型,HR眼中的认知误区与你的优势
  • 5分钟掌握网盘直链下载:告别限速与强制客户端的神器
  • BIT概率论考情分析
  • MXFP4量化技术提升LLM推理性能与精度
  • 第 3 周 Unit 1:Kotlin Hello World、生日卡与单位转换器