本项目中麦克风之间距离为5.4cm,日常听到的声音都处于100 Hz ~ 3000 Hz之间,音源基本都超过10cm以上,所以选用远场模型。即将音源到两个麦克风之间视作平行线。

时延估计与角度解算算法设计
2.1 远场模型下的几何关系
基于前述选定的远场模型,由于声波以平面波形式传播,到达两个麦克风的路径可视为平行线。设两麦克风间距为 d,声速为 c,入射角为 θ(以麦克风阵列法线为基准),则声波到达两麦克风的时间差(TDOA)为 τ。
其几何关系公式如下:

通过反解该公式,即可由时间差求得声源方位角:

高精度时延估计算法(GCC-PHAT)
要获得准确的 τ是实现定位的关键。由于原始音频信号包含噪声且波形相似度难以直接肉眼分辨,本项目采用广义互相关(Generalized Cross-Correlation, GCC)算法,特别是相位变换(PHAT)加权法,来计算两个信号 x1和 x2之间的时延。
该方法的原理是通过寻找两个信号在不同相对时延下的相似度峰值,峰值所在的位置即为时延 τ。


按以上原理进行项目开发。开发工具使用Vscode+esp-idf5.5,使用了espressif/esp-dsp开源库(这是一个乐鑫推出的高性能数字信号处理库)。声音信号使用16000采样率、16bit的深度。两个麦克风之间的距离是0.054米,声音速度使用固定值340米/秒。

代码编写。麦克风使用I2S进行连接,需要留意,要使用立体声进行初始化。
void init_microphone(void)
{
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));
i2s_pdm_rx_config_t pdm_rx_cfg = {
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
/* The default mono slot is the left slot (whose 'select pin' of the PDM microphone is pulled down) */
// .slot_cfg = I2S_PDM_RX_SLOT_PCM_FMT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.slot_cfg = I2S_PDM_RX_SLOT_PCM_FMT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
.gpio_cfg = {
.clk = PDM_CLK_PIN,
.din = PDM_DATA_PIN,
.invert_flags = {
.clk_inv = false,
},
},
};
ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle, &pdm_rx_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
}系统采用 独立音频采集进程 负责麦克风的连续采样。该进程通过 I2S 接口以 16 kHz 采样率、16 bit 位宽 从两颗 PDM 麦克风中读取音频数据。ESP32‑S3 的 I2S 外设将双通道数据以 交错(Interleaved)方式 存储,即在一个采样周期内,左声道(Left)与右声道(Right)依次排列,形成 LRLRLR…的数据结构。
在每一帧音频数据到达后,采集进程首先进行 左右声道分离:
将交错的 16 位 PCM 数据解析为两个独立的缓冲区,分别存放左、右麦克风的信号。随后,系统将这两个单通道时域信号送入 傅里叶变换模块(FFT),完成从时域到频域的转换,为后续的时延估计与声源定位提供频域特征支持。
while (1)
{
/* Read i2s data */
if (i2s_channel_read(rx_handle, (char *)i2s_readraw_buff, sizeof(i2s_readraw_buff), NULL, 1000) == ESP_OK)
{
// 计算总声音强度 RMS
current_volume_db = calculateVolumeRMS(i2s_readraw_buff, I2S_BUFF_SIZE);
for (int i = 0; i < I2S_BUFF_SIZE; i += 2)
{
x1[i] = i2s_readraw_buff[i];
x1[i + 1] = 0.0;
x2[i] = i2s_readraw_buff[i + 1];
x2[i + 1] = 0.0;
}
vol_angle = angleEstimation(x1, x2, INPUT_ARRAY_SIZE, SAMPLE_RATE);
if (vol_angle >= 0 && vol_angle <= 180 && current_volume_db > -25)
{
angle = (int)vol_angle;
printf("angle: %d vol_db=%.2f\n", angle, current_volume_db);
}
}
else
{
printf("Read Task: i2s read failed\n");
}
vTaskDelay(pdMS_TO_TICKS(10));
}声音强度
float calculateVolumeRMS(int16_t *buffer, int size)
{
// 第一步:计算直流偏置(平均值)
float sum = 0.0f;
for (int i = 0; i < size; i++)
{
sum += buffer[i];
}
float dc_offset = sum / size;
// 第二步:去除 DC 偏置后计算 RMS
float ac_sum = 0.0f;
for (int i = 0; i < size; i++)
{
float sample = buffer[i] - dc_offset;
ac_sum += sample * sample;
}
float rms = sqrtf(ac_sum / size);
// 第三步:转换为分贝(使用更合适的参考值)
if (rms < 1.0f)
return -100.0f;
return 20.0f * log10f(rms / 1000.0f); // 参考值改为 1000,可根据实际调整
}实现效果



我要赚赏金
