硬件配置

方案1:I2S麦克风 + I2S扬声器(推荐)⭐⭐⭐⭐⭐

ESP32-S3
├─ I2S0 (扬声器输出)
│   ├─ GPIO4  → BCLK
│   ├─ GPIO5  → WS (LRCLK)
│   └─ GPIO18 → DOUT
│
└─ I2S1 (麦克风输入)
    ├─ GPIO6  → BCLK
    ├─ GPIO7  → WS (LRCLK)
    └─ GPIO8  → DIN

常用模块:

  • 扬声器:MAX98357A (I2S DAC)
  • 麦克风:INMP441 (I2S MEMS麦克风)

方案2:模拟麦克风 + I2S扬声器

ESP32-S3
├─ ADC (GPIO1) ← MAX4466麦克风放大器
└─ I2S0 → 扬声器

USB复合设备实现

USB描述符:同时支持扬声器+麦克风

// 配置为USB Audio复合设备(扬声器 + 麦克风)

#define AUDIO_SAMPLE_RATE    48000
#define AUDIO_CHANNELS       2

enum {
    ITF_NUM_AUDIO_CONTROL = 0,
    ITF_NUM_AUDIO_STREAMING_SPK,   // 扬声器(输出)
    ITF_NUM_AUDIO_STREAMING_MIC,   // 麦克风(输入)
    ITF_NUM_TOTAL
};

// 复合描述符配置
uint8_t const desc_configuration[] = {
    // 配置描述符
    TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 
                          TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),

    // 音频控制接口(公共)
    TUD_AUDIO_HEADSET_STEREO_DESCRIPTOR(
        ITF_NUM_AUDIO_CONTROL,
        0,                              // 字符串索引
        0x01,                           // EP OUT (扬声器)
        0x81,                           // EP IN  (麦克风)
        CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX,
        CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX
    )
};

完整代码实现

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "tusb.h"
#include "esp_log.h"

static const char *TAG = "USB_AUDIO";

// I2S句柄
i2s_chan_handle_t tx_handle;  // 扬声器
i2s_chan_handle_t rx_handle;  // 麦克风

// 音频缓冲区
#define BUFFER_SIZE 512
static int16_t spk_buffer[BUFFER_SIZE];  // 扬声器缓冲
static int16_t mic_buffer[BUFFER_SIZE];  // 麦克风缓冲

//--------------------------------------------------------------------+
// I2S 初始化
//--------------------------------------------------------------------+

void init_i2s_speaker(void) {
    // I2S TX (扬声器)
    i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
    i2s_new_channel(&tx_chan_cfg, &tx_handle, NULL);
    
    i2s_std_config_t tx_std_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(
            I2S_DATA_BIT_WIDTH_16BIT, 
            I2S_SLOT_MODE_STEREO
        ),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,
            .bclk = GPIO_NUM_4,
            .ws   = GPIO_NUM_5,
            .dout = GPIO_NUM_18,
            .din  = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };
    
    i2s_channel_init_std_mode(tx_handle, &tx_std_cfg);
    i2s_channel_enable(tx_handle);
    
    ESP_LOGI(TAG, "I2S Speaker initialized");
}

void init_i2s_microphone(void) {
    // I2S RX (麦克风)
    i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);
    i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle);
    
    i2s_std_config_t rx_std_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(
            I2S_DATA_BIT_WIDTH_16BIT, 
            I2S_SLOT_MODE_STEREO
        ),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,
            .bclk = GPIO_NUM_6,
            .ws   = GPIO_NUM_7,
            .dout = I2S_GPIO_UNUSED,
            .din  = GPIO_NUM_8,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };
    
    i2s_channel_init_std_mode(rx_handle, &rx_std_cfg);
    i2s_channel_enable(rx_handle);
    
    ESP_LOGI(TAG, "I2S Microphone initialized");
}

//--------------------------------------------------------------------+
// USB音频回调 - 扬声器(接收PC音频)
//--------------------------------------------------------------------+

bool tud_audio_rx_done_pre_read_cb(uint8_t rhport, uint16_t n_bytes_received, 
                                     uint8_t func_id, uint8_t ep_out, 
                                     uint8_t cur_alt_setting)
{
    (void)rhport;
    (void)func_id;
    (void)ep_out;
    (void)cur_alt_setting;
    
    // 从USB读取音频数据(PC → ESP32)
    uint16_t bytes_read = tud_audio_read(spk_buffer, n_bytes_received);
    
    if (bytes_read > 0) {
        // 发送到I2S扬声器
        size_t bytes_written;
        i2s_channel_write(tx_handle, spk_buffer, bytes_read, 
                         &bytes_written, pdMS_TO_TICKS(10));
        
        ESP_LOGD(TAG, "Speaker: USB→I2S %d bytes", bytes_read);
    }
    
    return true;
}

//--------------------------------------------------------------------+
// 麦克风任务 - 发送音频到PC
//--------------------------------------------------------------------+

void microphone_task(void *param) {
    size_t bytes_read;
    
    while (1) {
        // 从I2S麦克风读取数据
        esp_err_t ret = i2s_channel_read(rx_handle, mic_buffer, 
                                          sizeof(mic_buffer), 
                                          &bytes_read, 
                                          pdMS_TO_TICKS(100));
        
        if (ret == ESP_OK && bytes_read > 0) {
            // 发送到USB (ESP32 → PC)
            if (tud_mounted() && tud_audio_mounted()) {
                uint16_t written = tud_audio_write(mic_buffer, bytes_read);
                
                if (written > 0) {
                    ESP_LOGD(TAG, "Mic: I2S→USB %d bytes", written);
                } else {
                    ESP_LOGW(TAG, "USB buffer full, dropping mic data");
                }
            }
        }
        
        vTaskDelay(1);  // 让出CPU
    }
}

//--------------------------------------------------------------------+
// USB回调
//--------------------------------------------------------------------+

void tud_mount_cb(void) {
    ESP_LOGI(TAG, "✓ USB Device mounted");
}

void tud_umount_cb(void) {
    ESP_LOGW(TAG, "✗ USB Device unmounted");
}

bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request) {
    uint8_t const itf = tu_u16_low(p_request->wIndex);
    uint8_t const alt = tu_u16_low(p_request->wValue);
    
    ESP_LOGI(TAG, "Audio Interface %d set to alt %d", itf, alt);
    
    return true;
}

// 音频控制请求(音量、静音等)
bool tud_audio_set_req_cb(uint8_t rhport, tusb_control_request_t const * p_request,
                           uint8_t *pBuff)
{
    uint8_t channelNum = TU_U16_LOW(p_request->wValue);
    uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue);
    uint8_t ep = TU_U16_LOW(p_request->wIndex);
    
    ESP_LOGI(TAG, "Audio Control: EP=%d, Channel=%d, Ctrl=%d", 
             ep, channelNum, ctrlSel);
    
    // 处理音量控制
    if (ctrlSel == AUDIO_FU_CTRL_VOLUME) {
        int16_t volume = (int16_t)((pBuff[1] << 8) | pBuff[0]);
        ESP_LOGI(TAG, "Volume set to: %d dB", volume / 256);
        
        // TODO: 调整I2S音量或外部放大器
    }
    
    // 处理静音控制
    if (ctrlSel == AUDIO_FU_CTRL_MUTE) {
        bool mute = pBuff[0];
        ESP_LOGI(TAG, "Mute: %s", mute ? "ON" : "OFF");
        
        // TODO: 静音处理
    }
    
    return true;
}

//--------------------------------------------------------------------+
// USB任务
//--------------------------------------------------------------------+

void usb_device_task(void *param) {
    tusb_init();
    
    while (1) {
        tud_task();
        vTaskDelay(1);
    }
}

//--------------------------------------------------------------------+
// 主函数
//--------------------------------------------------------------------+

void app_main(void) {
    ESP_LOGI(TAG, "ESP32-S3 USB Audio (Speaker + Microphone)");
    
    // 初始化I2S
    init_i2s_speaker();
    init_i2s_microphone();
    
    // 创建任务
    xTaskCreate(usb_device_task, "usb", 4096, NULL, 5, NULL);
    xTaskCreate(microphone_task, "mic", 4096, NULL, 4, NULL);
    
    ESP_LOGI(TAG, "System started");
}

TinyUSB配置

tusb_config.h

#ifndef _TUSB_CONFIG_H_
#define _TUSB_CONFIG_H_

// 音频类配置
#define CFG_TUD_AUDIO               1

// 音频功能数量(1个复合功能:扬声器+麦克风)
#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN   TUD_AUDIO_HEADSET_STEREO_DESC_LEN

// 扬声器配置(USB OUT → ESP32)
#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX          2   // 立体声
#define CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE_RX         48000
#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX  2   // 16bit
#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX          ((48 + 1) * 2 * 2)  // 48samples * 2ch * 2bytes
#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ       (CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX * 4)

// 麦克风配置(ESP32 → USB IN)
#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX          2   // 立体声
#define CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE_TX         48000
#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX  2   // 16bit
#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX           ((48 + 1) * 2 * 2)
#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ        (CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX * 2)

// 启用反馈端点(用于音频同步)
#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP            1

#endif

Windows识别效果

插入ESP32-S3后,Windows会显示:

设备管理器 → 声音、视频和游戏控制器
  └─ USB Audio Device
      ├─ 扬声器 (USB Audio Device)  ← 播放设备
      └─ 麦克风 (USB Audio Device)  ← 录制设备

同时使用示例:

场景1:视频会议
  ├─ Zoom音频输出 → ESP32扬声器
  └─ Zoom音频输入 ← ESP32麦克风

场景2:录音+播放监听
  ├─ Audacity录音 ← ESP32麦克风
  └─ Media Player播放 → ESP32扬声器

高级控制功能

1. 音量控制

#include "driver/ledc.h"

// 使用PWM控制放大器音量
void set_speaker_volume(uint8_t volume) {
    // volume: 0-100
    uint32_t duty = (volume * 8191) / 100;  // 13bit分辨率
    ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty);
    ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
    
    ESP_LOGI(TAG, "Speaker volume: %d%%", volume);
}

// 在USB控制回调中调用
bool tud_audio_set_req_cb(...) {
    if (ctrlSel == AUDIO_FU_CTRL_VOLUME) {
        int16_t vol_db = (int16_t)((pBuff[1] << 8) | pBuff[0]);
        // 转换dB到百分比(近似)
        uint8_t vol_percent = (vol_db + 6400) * 100 / 6400;
        set_speaker_volume(vol_percent);
    }
    return true;
}

2. 静音控制

bool speaker_muted = false;

void set_speaker_mute(bool mute) {
    speaker_muted = mute;
    
    if (mute) {
        // 方案1:停止I2S输出
        i2s_channel_disable(tx_handle);
        
        // 方案2:发送静音数据
        // memset(spk_buffer, 0, sizeof(spk_buffer));
    } else {
        i2s_channel_enable(tx_handle);
    }
    
    ESP_LOGI(TAG, "Speaker %s", mute ? "MUTED" : "UNMUTED");
}

3. 麦克风增益控制

int16_t mic_gain = 100;  // 增益百分比

void apply_mic_gain(int16_t *buffer, size_t samples) {
    for (size_t i = 0; i < samples; i++) {
        int32_t val = buffer[i] * mic_gain / 100;
        
        // 限幅防止溢出
        if (val > 32767) val = 32767;
        if (val < -32768) val = -32768;
        
        buffer[i] = (int16_t)val;
    }
}

// 在麦克风任务中使用
void microphone_task(void *param) {
    while (1) {
        i2s_channel_read(rx_handle, mic_buffer, sizeof(mic_buffer), &bytes_read, 100);
        
        // 应用增益
        apply_mic_gain(mic_buffer, bytes_read / 2);
        
        tud_audio_write(mic_buffer, bytes_read);
    }
}

4. 回声消除(简单版)

#define ECHO_DELAY_MS 20
#define ECHO_BUFFER_SIZE (48000 * 2 * 2 * ECHO_DELAY_MS / 1000)

static int16_t echo_buffer[ECHO_BUFFER_SIZE];
static size_t echo_idx = 0;

void simple_echo_cancel(int16_t *mic_data, size_t samples) {
    for (size_t i = 0; i < samples; i++) {
        // 从麦克风数据中减去延迟的扬声器数据
        mic_data[i] -= echo_buffer[echo_idx] / 4;  // 衰减系数
        
        // 更新回声缓冲
        echo_idx = (echo_idx + 1) % ECHO_BUFFER_SIZE;
    }
}

// 在扬声器回调中保存数据
bool tud_audio_rx_done_pre_read_cb(...) {
    tud_audio_read(spk_buffer, n_bytes_received);
    
    // 保存到回声缓冲
    for (size_t i = 0; i < bytes_read / 2; i++) {
        echo_buffer[echo_idx] = spk_buffer[i];
        echo_idx = (echo_idx + 1) % ECHO_BUFFER_SIZE;
    }
    
    i2s_channel_write(tx_handle, spk_buffer, bytes_read, &bytes_written, 10);
    return true;
}

调试技巧

1. 测试扬声器

// 生成1kHz测试音
void test_speaker(void) {
    int16_t test_tone[96];  // 1ms @ 48kHz
    
    for (int i = 0; i < 48; i++) {
        float t = (float)i / 48.0f;
        int16_t val = (int16_t)(sin(2.0f * 3.14159f * t) * 10000);
        test_tone[i*2] = val;
        test_tone[i*2+1] = val;
    }
    
    // 循环播放
    while (1) {
        size_t written;
        i2s_channel_write(tx_handle, test_tone, sizeof(test_tone), &written, 100);
        vTaskDelay(1);
    }
}

2. 测试麦克风

// 监控麦克风电平
void monitor_mic_level(void) {
    size_t bytes_read;
    int16_t buffer[96];
    
    while (1) {
        i2s_channel_read(rx_handle, buffer, sizeof(buffer), &bytes_read, 100);
        
        // 计算RMS电平
        int64_t sum = 0;
        for (int i = 0; i < 48; i++) {
            sum += (int64_t)buffer[i*2] * buffer[i*2];
        }
        int32_t rms = (int32_t)sqrt(sum / 48);
        
        ESP_LOGI(TAG, "Mic Level: %ld (%.1f%%)", rms, rms * 100.0f / 32768);
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

3. USB数据流监控

// 统计数据流量
static uint32_t spk_total = 0, mic_total = 0;

void stats_task(void *param) {
    while (1) {
        ESP_LOGI(TAG, "Stats: Speaker RX=%lu KB, Mic TX=%lu KB", 
                 spk_total / 1024, mic_total / 1024);
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

// 在回调中累计
bool tud_audio_rx_done_pre_read_cb(...) {
    spk_total += bytes_read;
    // ...
}

void microphone_task(...) {
    mic_total += written;
    // ...
}

性能优化

延迟优化

// 减小缓冲区大小(降低延迟,但增加丢包风险)
#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ  (192 * 2)  // 仅2ms缓冲

// 使用DMA直接传输
i2s_channel_write(tx_handle, buffer, size, &written, 0);  // 不等待

CPU负载优化

// 使用FreeRTOS直接通知代替队列
TaskHandle_t audio_task_handle;

void IRAM_ATTR i2s_rx_callback(void *arg) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    vTaskNotifyGiveFromISR(audio_task_handle, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

总结

ESP32-S3完全可以同时控制麦克风和扬声器

功能实现方式难度
扬声器输出I2S TX + USB Audio OUT⭐⭐⭐
麦克风输入I2S RX + USB Audio IN⭐⭐⭐
音量控制USB Audio控制请求 + PWM⭐⭐
静音控制停止I2S或发送零数据
回声消除软件算法⭐⭐⭐⭐

实际应用:

  • 🎧 USB耳机
  • 📞 USB会议电话
  • 🎤 USB录音设备
  • 🔊 USB扬声器+麦克风

更多推荐