音频重采样(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)
c 复制代码
/**
  * @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;            // 插值因子 L
    uint8_t decimation;               // 抽取因子 M
    int16_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)
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)
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 基于线性插值的轻量级实现

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

c 复制代码
/**
  * @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) * frac
            output[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的高质量重采样

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

c 复制代码
/**
  * @file speex_resample.c
  * @brief 使用SpeexDSP库的重采样示例
  */
#include <speex/speex_resampler.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FRAME_SIZE 160

int 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 算法优化

c 复制代码
/* 定点运算优化(避免浮点) */
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 内存优化

c 复制代码
/* 环形缓冲区替代线性缓冲区 */
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/contentcsu/56438.html

五、测试与验证

5.1 测试代码

c 复制代码
/* 重采样质量测试 */
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));
    }
    
    // 重采样到16kHz
    ResampleState rs;
    Resample_Init(&rs, 1, 3);  // 48kHz → 16kHz
    Resample_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 抗混叠处理

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

6.2 相位失真

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

6.3 缓冲区溢出

c 复制代码
/* 计算安全的输出缓冲区大小 */
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;
}
相关推荐
byte轻骑兵2 小时前
【LE Audio】CAP精讲[3]: 角色能力清单拆解,CAP支持要求全流程解析
人工智能·音视频·le audio·低功耗音频·蓝牙通话
EasyDSS14 小时前
私有化音视频系统/视频直播点播/音视频点播EasyDSS构建智慧校园视频智能服务新体系
音视频
我是发哥哈15 小时前
跨AI模型生成视频的五大维度对比:选型避坑指南
大数据·人工智能·学习·机器学习·chatgpt·音视频
墨染倾城殇19 小时前
蓝牙 5.3 双模一体,面向车载、智能音箱及多场景的经典音频与LE Audio应用
音视频·智能音箱·le audio·蓝牙双模·蓝牙5.3
Gc9umsbL120 小时前
从FLAC到WAV:whisper.cpp中的FFmpeg音频预处理全解析
ffmpeg·whisper·音视频
CWM-183125336391 天前
东芝TDS5B212MX/TDS5C212MX最高支持64G的高速切换开关芯片DP2.0/PCIE6.0/USB4等接口二切一
音视频
ZC跨境爬虫1 天前
跟着 MDN 学 HTML day_27:(深入理解 HTML 属性反射机制)
前端·javascript·ui·html·音视频·媒体
EasyDSS1 天前
视频直播点播/高清点播/音视频点播/云点播/云直播EasyDSS构建社交娱乐全链路视频解决方案
音视频·娱乐
fengfuyao9851 天前
STM32 ADC音频采样与FFT频谱分析实现
stm32·嵌入式硬件·音视频