PDM麦克风实时采集音频信号,然后对采集到的信号进行FFT分析,识别是否存在特定的频率,比如预设的某个频率音调。用于声音触发或者频率检测应用,比如检测特定音调的门铃或者警报声。需要识别特定频率,比如检测某个频率成分是否存在超过阈值。

首先,需要考虑实时处理数据流,实时处理每个数据块。每次从I2S读取一定数量的样本(比如SAMPLE_SIZE),然后对这个数据块进行FFT分析。

需要注意的问题包括:实时处理的延迟、FFT计算的开销、如何高效处理数据流,以及如何从时域的PDM数据转换到频域进行分析。

接下来,确定FFT的参数。由于PDM麦克风的采样率可能较高,比如16kHz,FFT的大小需要合理选择。比如,使用1024点的FFT,这样频率分辨率为16000/1024≈15.625Hz。这样,要检测的特定频率(比如1kHz)对应的bin为1000/15.625≈64。需要计算该bin附近的幅值是否超过阈值。

在代码结构上,需要将录音部分的循环从写入文件改为进行FFT处理。每次读取一个数据块,进行加窗、FFT、计算功率谱,然后检查目标频率的幅值。

需要处理的数据类型是int16_t的原始音频数据,而FFT函数可能需要浮点数输入。所以需要先将int16_t的样本转换为浮点数,并进行加窗处理(比如汉宁窗),然后进行FFT。

此外,FFT的结果是复数,需要计算每个频率点的幅值,并找到目标频率附近的峰值。同时,为了减少计算量,只需要计算到Nyquist频率(即N/2点)。

操作的步骤:

1. 初始化I2S麦克风和FFT库。

2. 在循环中读取音频数据块。

3. 将int16_t转换为float,并加窗。

4. 执行FFT。

5. 计算功率谱,找到目标频率附近的幅值。

6. 如果幅值超过阈值,触发相应动作(比如打印检测到频率)。

以下是结合PDM麦克风录音与FFT频率识别的完整C语言实现代码,包含详细注释:

#include "esp_dsp.h"
#include "driver/i2s.h"
#include "esp_log.h"

static const char* TAG = "FREQ_DETECT";

/*======= FFT配置 =======*/
#define FFT_SIZE        1024          // FFT点数(需为2的幂)
#define SAMPLE_RATE     16000         // 采样率16kHz
#define TARGET_FREQ     1000          // 目标检测频率1kHz
#define FREQ_TOLERANCE  50            // 频率允许误差±50Hz
#define MAG_THRESHOLD   60.0          // 幅度阈值(dB)

// 对齐内存分配(DSP优化需要)
__attribute__((aligned(16)))
float fft_input[FFT_SIZE*2];          // FFT输入数组(实部+虚部)
__attribute__((aligned(16)))
float fft_output[FFT_SIZE];           // FFT幅度输出
__attribute__((aligned(16)))
float window[FFT_SIZE];               // 窗函数系数

/*======= I2S配置 =======*/
#define I2S_CHANNEL     I2S_NUM_0     // 使用I2S0
#define I2S_BUF_SIZE    1024          // DMA缓冲区大小

// PDM麦克风引脚配置(根据实际硬件修改)
#define I2S_BCK_PIN     32
#define I2S_WS_PIN      25
#define I2S_DATA_PIN    33

/*======= 频率检测函数 =======*/
bool detect_target_frequency(float *spectrum) {
    float max_mag = 0;
    int peak_bin = 0;
    const float freq_resolution = (float)SAMPLE_RATE / FFT_SIZE;
    
    // 计算目标频率对应的bin范围
    int target_bin = TARGET_FREQ / freq_resolution;
    int bin_start = (TARGET_FREQ - FREQ_TOLERANCE) / freq_resolution;
    int bin_end = (TARGET_FREQ + FREQ_TOLERANCE) / freq_resolution;
    
    // 搜索区间内的最大幅值
    for(int i=bin_start; i<=bin_end && i<FFT_SIZE/2; i++) {
        if(spectrum[i] > max_mag) {
            max_mag = spectrum[i];
            peak_bin = i;
        }
    }
    
    // 计算实际频率和幅度
    float peak_freq = peak_bin * freq_resolution;
    float peak_db = 20 * log10f(max_mag); // 转换为dB
    
    ESP_LOGI(TAG, "Peak: %.1fHz, %.1fdB", peak_freq, peak_db);
    
    return (peak_db > MAG_THRESHOLD) && 
           (fabsf(peak_freq - TARGET_FREQ) < FREQ_TOLERANCE);
}

/*======= FFT初始化 =======*/
void init_fft() {
    esp_err_t ret = dsps_fft2r_init_fc32(NULL, FFT_SIZE);
    if(ret != ESP_OK) {
        ESP_LOGE(TAG, "FFT初始化失败: %d", ret);
        abort();
    }
    
    // 生成汉宁窗
    dsps_wind_hann_f32(window, FFT_SIZE);
    ESP_LOGI(TAG, "FFT初始化完成");
}

/*======= 音频处理任务 =======*/
void audio_process_task() {
    int16_t raw_samples[FFT_SIZE]; // 原始音频缓冲区
    
    while(1) {
        size_t bytes_read = 0;
        
        // 1. 从I2S读取一帧数据
        i2s_read(I2S_CHANNEL, raw_samples, sizeof(raw_samples), &bytes_read, portMAX_DELAY);
        
        // 2. 转换为浮点并加窗
        for(int i=0; i<FFT_SIZE; i++) {
            fft_input[i*2] = (float)raw_samples[i] * window[i]; // 实部
            fft_input[i*2+1] = 0;                               // 虚部
        }
        
        // 3. 执行FFT
        dsps_fft2r_fc32(fft_input, FFT_SIZE);       // 原位计算
        dsps_bit_rev_fc32(fft_input, FFT_SIZE);     // 位反转
        dsps_cplx2real_fc32(fft_input, FFT_SIZE);   // 转换为实数格式
        
        // 4. 计算幅度谱
        for(int i=0; i<FFT_SIZE/2; i++) {
            float real = fft_input[i*2];
            float imag = fft_input[i*2+1];
            fft_output[i] = sqrtf(real*real + imag*imag);
        }
        
        // 5. 检测目标频率
        if(detect_target_frequency(fft_output)) {
            ESP_LOGW(TAG, "检测到目标频率%dHz!", TARGET_FREQ);
        }
        
        vTaskDelay(pdMS_TO_TICKS(10)); // 适当延迟
    }
}

/*======= I2S初始化 =======*/
void init_i2s() {
    i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM,
        .sample_rate = SAMPLE_RATE,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count = 4,
        .dma_buf_len = I2S_BUF_SIZE,
    };
    
    i2s_pin_config_t pin_config = {
        .bck_io_num = I2S_BCK_PIN,
        .ws_io_num = I2S_WS_PIN,
        .data_out_num = -1,
        .data_in_num = I2S_DATA_PIN
    };
    
    i2s_driver_install(I2S_CHANNEL, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_CHANNEL, &pin_config);
    ESP_LOGI(TAG, "I2S初始化完成");
}

void app_main() {
    // 初始化硬件
    init_i2s();
    init_fft();
    
    // 创建音频处理任务
    xTaskCreate(audio_process_task, "audio_process", 4096, NULL, 5, NULL);
    
    ESP_LOGI(TAG, "系统启动,开始频率检测...");
}

关键实现说明

  1. 硬件接口配置

    • 使用I2S的PDM模式直接连接数字麦克风

    • 配置DMA双缓冲区实现连续音频采集

    • 通过i2s_read()非阻塞获取音频数据

  2. 实时FFT处理流程

    graph TD
    A[读取I2S数据] --> B[格式转换]
    B --> C[加窗处理]
    C --> D[执行FFT]
    D --> E[幅度谱计算]
    E --> F[频率检测]
    F --> G[触发事件]
  3. 频率检测优化

    • 局部峰值搜索:仅在目标频率附近±50Hz范围搜索

    • 对数转换:使用dB值提高检测灵敏度

    • 双重判断:同时满足幅度阈值和频率容差

  4. 性能优化措施

    • 内存对齐优化DSP指令效率

    • 使用汉宁窗减少频谱泄漏

    • 仅计算前N/2点(奈奎斯特频率)

    • 采用DMA零拷贝技术降低CPU负载

扩展功能建议

  1. 多频段检测

    typedef struct {
        int target_freq;
        float threshold;
        bool detected;
    } freq_band;
    
    freq_band bands[] = {
        {1000, 60.0, false},
        {2000, 55.0, false}
    };
  2. 动态阈值调整

    #define NOISE_FLOOR_SAMPLES 50  // 取前50个bin作为噪声基底
    float calc_noise_floor(float *spectrum) {
        float sum = 0;
        for(int i=0; i<NOISE_FLOOR_SAMPLES; i++) {
            sum += spectrum[i];
        }
        return 20 * log10f(sum / NOISE_FLOOR_SAMPLES);
    }
  3. 音调持续时间检测

    int detect_counter = 0;
    #define MIN_DURATION 5  // 最小持续5帧
    if(detected) {
        if(++detect_counter > MIN_DURATION) {
            ESP_LOGI(TAG, "确认有效音调");
        }
    } else {
        detect_counter = 0;
    }

典型应用场景

  1. 智能家居:门铃音调识别

  2. 工业检测:设备异常噪声监测

  3. 安防系统:特定频率报警音识别

  4. 语音唤醒:关键词检测预处理

实际部署时需注意:

  • 根据麦克风灵敏度调整幅度阈值

  • 优化FFT大小平衡频率分辨率与实时性

  • 添加抗混叠滤波(硬件或软件实现)

  • 考虑环境噪声消除算法(如谱减法)

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐