通过uac 协议 与 ESP32-S3 通讯控制 麦克风和扬声器 简单介绍
·
硬件配置
方案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扬声器+麦克风
更多推荐
所有评论(0)