这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » SeeedStudioXIAOESP32-S3Sense声音定位二

共2条 1/1 1 跳转至

SeeedStudioXIAOESP32-S3Sense声音定位二

高工
2026-06-18 22:40:13     打赏

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

image.png

时延估计与角度解算算法设计

2.1 远场模型下的几何关系

基于前述选定的远场模型,由于声波以平面波形式传播,到达两个麦克风的路径可视为平行线。设两麦克风间距为 d,声速为 c,入射角为 θ(以麦克风阵列法线为基准),则声波到达两麦克风的时间差(TDOA)为 τ。

其几何关系公式如下:

image.png

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

image.png

高精度时延估计算法(GCC-PHAT)

要获得准确的 τ是实现定位的关键。由于原始音频信号包含噪声且波形相似度难以直接肉眼分辨,本项目采用广义互相关(Generalized Cross-Correlation, GCC)算法,特别是相位变换(PHAT)加权法,来计算两个信号 x1和 x2之间的时延。

该方法的原理是通过寻找两个信号在不同相对时延下的相似度峰值,峰值所在的位置即为时延 τ。

image.pngimage.png

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

image.png

代码编写。麦克风使用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,可根据实际调整
}


实现效果

image.pngimage.png




高工
2026-06-20 00:04:02     打赏
2楼

你这玩的有点高端了,直接上基于GSC的声源定位了


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]