ESP32开发板PDM录音到FFT分析,声音频率识别实例
PDM麦克风实时采集音频信号,然后对采集到的信号进行FFT分析,识别是否存在特定的频率,比如预设的某个频率音调。用于声音触发或者频率检测应用,比如检测特定音调的门铃或者警报声。需要识别特定频率,比如检测某个频率成分是否存在超过阈值。首先,需要考虑实时处理数据流,实时处理每个数据块。每次从I2S读取一定数量的样本(比如SAMPLE_SIZE),然后对这个数据块进行FFT分析。需要注意的问题包括:实时
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, "系统启动,开始频率检测...");
}
关键实现说明
-
硬件接口配置:
-
使用I2S的PDM模式直接连接数字麦克风
-
配置DMA双缓冲区实现连续音频采集
-
通过
i2s_read()
非阻塞获取音频数据
-
-
实时FFT处理流程:
graph TD A[读取I2S数据] --> B[格式转换] B --> C[加窗处理] C --> D[执行FFT] D --> E[幅度谱计算] E --> F[频率检测] F --> G[触发事件]
-
频率检测优化:
-
局部峰值搜索:仅在目标频率附近±50Hz范围搜索
-
对数转换:使用dB值提高检测灵敏度
-
双重判断:同时满足幅度阈值和频率容差
-
-
性能优化措施:
-
内存对齐优化DSP指令效率
-
使用汉宁窗减少频谱泄漏
-
仅计算前N/2点(奈奎斯特频率)
-
采用DMA零拷贝技术降低CPU负载
-
扩展功能建议
-
多频段检测:
typedef struct { int target_freq; float threshold; bool detected; } freq_band; freq_band bands[] = { {1000, 60.0, false}, {2000, 55.0, false} };
-
动态阈值调整:
#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); }
-
音调持续时间检测:
int detect_counter = 0; #define MIN_DURATION 5 // 最小持续5帧 if(detected) { if(++detect_counter > MIN_DURATION) { ESP_LOGI(TAG, "确认有效音调"); } } else { detect_counter = 0; }
典型应用场景
-
智能家居:门铃音调识别
-
工业检测:设备异常噪声监测
-
安防系统:特定频率报警音识别
-
语音唤醒:关键词检测预处理
实际部署时需注意:
-
根据麦克风灵敏度调整幅度阈值
-
优化FFT大小平衡频率分辨率与实时性
-
添加抗混叠滤波(硬件或软件实现)
-
考虑环境噪声消除算法(如谱减法)
更多推荐
所有评论(0)