好嘞!我给你写一个用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,采样部分需改写。