好嘞!我给你写一个用ESP32-S3实现的最简易UDP对讲机示例,包含:
- WiFi连接
- I2S采集麦克风音频
- UDP发送采集的音频数据
- UDP接收对方音频数据并播放
- 按键控制“按住说话,松开听”
硬件前提
- 麦克风接ESP32-S3的I2S输入(I2S麦克风或模拟麦克风接ADC也行,这里用I2S麦克风示范)
- 扬声器接I2S或DAC输出(示例用I2S)
- 按键接GPIO(带下拉,按下为低)
代码说明
- 采样率16kHz,16bit单声道
- UDP包大小为256字节PCM数据(128个16bit样本)
- 按键GPIO0,按下开始采集发送,松开接收播放
cpp复制编辑#include <WiFi.h>
#include <WiFiUdp.h>
#include "driver/i2s.h"
#define I2S_NUM (0) // 使用I2S0
#define SAMPLE_RATE (16000)
#define I2S_BUFFER_SIZE (256) // 256字节 = 128个16bit采样
#define UDP_LOCAL_PORT (12345)
#define UDP_REMOTE_PORT (12345)
#define BUTTON_PIN (0) // 按键GPIO
const char* WIFI_SSID = "your_ssid";
const char* WIFI_PASS = "your_password";
const char* REMOTE_IP = "192.168.1.100"; // 对方IP
WiFiUDP udp;
uint16_t i2s_read_buffer[I2S_BUFFER_SIZE / 2];
uint16_t i2s_write_buffer[I2S_BUFFER_SIZE / 2];
void setupWiFi() {
Serial.print("Connecting to WiFi...");
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" connected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
udp.begin(UDP_LOCAL_PORT);
}
void setupI2S() {
// 配置I2S作为麦克风输入
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(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_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = 128,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
i2s_pin_config_t pin_config = {
.bck_io_num = 14, // I2S BCK (bit clock)
.ws_io_num = 15, // I2S WS (word select)
.data_out_num = -1, // 不用输出
.data_in_num = 13 // I2S DATA_IN (麦克风数据)
};
i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM, &pin_config);
i2s_zero_dma_buffer(I2S_NUM);
}
void setupI2SOut() {
// 配置I2S作为扬声器输出
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.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_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = 128,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0
};
i2s_pin_config_t pin_config = {
.bck_io_num = 14,
.ws_io_num = 15,
.data_out_num = 27, // I2S数据输出(扬声器)
.data_in_num = -1
};
i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM, &pin_config);
i2s_zero_dma_buffer(I2S_NUM);
}
void sendAudio() {
size_t bytes_read = 0;
// 从麦克风读数据
i2s_read(I2S_NUM, (void*)i2s_read_buffer, I2S_BUFFER_SIZE, &bytes_read, portMAX_DELAY);
if (bytes_read == I2S_BUFFER_SIZE) {
udp.beginPacket(REMOTE_IP, UDP_REMOTE_PORT);
udp.write((uint8_t*)i2s_read_buffer, I2S_BUFFER_SIZE);
udp.endPacket();
}
}
void receiveAudio() {
int packetSize = udp.parsePacket();
if (packetSize >= I2S_BUFFER_SIZE) {
int len = udp.read((uint8_t*)i2s_write_buffer, I2S_BUFFER_SIZE);
if (len == I2S_BUFFER_SIZE) {
size_t bytes_written = 0;
i2s_write(I2S_NUM, (const void*)i2s_write_buffer, I2S_BUFFER_SIZE, &bytes_written, portMAX_DELAY);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
setupWiFi();
// I2S初始化为输入采集(先)
setupI2S();
Serial.println("Setup complete. Press button to talk.");
}
void loop() {
if (digitalRead(BUTTON_PIN) == LOW) {
// 按键按下,采集并发送音频
sendAudio();
} else {
// 松开按键,接收并播放音频
// 先卸载I2S输入,切换成输出模式
i2s_driver_uninstall(I2S_NUM);
setupI2SOut();
receiveAudio();
// 再切回输入模式,准备下一次按键说话
i2s_driver_uninstall(I2S_NUM);
setupI2S();
}
}
说明和改进
- 代码中
setupI2S
和setupI2SOut
反复切换输入输出,是最简方案,实际可以用双I2S或者更优的音频框架避免切换延迟。 - 麦克风型号和接线请根据实际I2S麦克风调整。
- 音质和延迟需调试缓冲区大小和采样率。
- 可进一步添加音频压缩、VAD和多播。
- 如果用ADC麦克风或内置ADC,采样部分需改写。