不使用第三方库实现 IIS 麦克风转HTTP

这段代码展示了不使用第三方库实现在一个 HTTP Server, 然后电脑可以通过浏览器打开 http://ip/stream 即可听到 IIS 麦克风拾取到的声音。实验使用MSM261S4030H0的 IIS 音频传感器。

#include <WiFi.h>
#include <WebServer.h>
#include <driver/i2s.h>

// WiFi配置
const char* ssid = "YOURSSID";
const char* password = "YOURPASSWORD";

// I2S配置
#define I2S_MIC_WS  37
#define I2S_MIC_SD  48
#define I2S_MIC_SCK 45
#define SAMPLE_RATE 16000
#define BITS_PER_SAMPLE 32
#define BUFFER_SIZE 1024

WebServer server(80);
int32_t audioBuffer[BUFFER_SIZE];

void setup() {
  Serial.begin(2000000);
  
  // 初始化I2S
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = BUFFER_SIZE,
    .use_apll = false
  };
  
  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_MIC_SCK,
    .ws_io_num = I2S_MIC_WS,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = I2S_MIC_SD
  };
  
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_NUM_0, &pin_config);

  // 连接WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  Serial.println("IP address: " + WiFi.localIP().toString());

  // 设置HTTP路由
  server.on("/stream", HTTP_GET, handleAudioStream);
  server.begin();
}

void loop() {
  server.handleClient();
  
  // 持续读取音频数据
  size_t bytesRead;
  i2s_read(I2S_NUM_0, &audioBuffer, BUFFER_SIZE * sizeof(int32_t), &bytesRead, portMAX_DELAY);
}

void handleAudioStream() {
  // 生成WAV文件头
  uint8_t wavHeader[44];
  generateWavHeader(wavHeader, SAMPLE_RATE, 32);
  
  // 发送HTTP头
  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
  server.send(200, "audio/wav", "");
  
  // 先发送WAV头
  server.sendContent((const char*)wavHeader, sizeof(wavHeader));
  
  // 持续发送音频数据
  while(server.client().connected()) {
    size_t bytesRead;
    i2s_read(I2S_NUM_0, &audioBuffer, BUFFER_SIZE*4, &bytesRead, portMAX_DELAY);
    server.sendContent((const char*)audioBuffer, bytesRead);
  }
}


void generateWavHeader(uint8_t* header, uint32_t sampleRate, uint32_t bitDepth) {
    // RIFF块标识
    header[0] = 'R'; header[1] = 'I'; header[2] = 'F'; header[3] = 'F';
    
    // 文件总大小占位(后续更新)
    header[4] = 0; header[5] = 0; header[6] = 0; header[7] = 0;
    
    // WAVE格式标识
    header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E';
    
    // fmt子块标识
    header[12] = 'f'; header[13] = 'm'; header[14] = 't'; header[15] = ' ';
    
    // fmt块大小(16字节)
    header[16] = 16; header[17] = 0; header[18] = 0; header[19] = 0;
    
    // 音频格式(1=PCM)
    header[20] = 1; header[21] = 0;
    
    // 声道数(1=单声道)
    header[22] = 1; header[23] = 0;
    
    // 采样率
    header[24] = sampleRate & 0xFF;
    header[25] = (sampleRate >> 8) & 0xFF;
    header[26] = (sampleRate >> 16) & 0xFF;
    header[27] = (sampleRate >> 24) & 0xFF;
    
    // 字节率 = 采样率 * 声道数 * 位深度/8
    uint32_t byteRate = sampleRate * (bitDepth/8);
    header[28] = byteRate & 0xFF;
    header[29] = (byteRate >> 8) & 0xFF;
    header[30] = (byteRate >> 16) & 0xFF;
    header[31] = (byteRate >> 24) & 0xFF;
    
    // 块对齐 = 声道数 * 位深度/8
    uint16_t blockAlign = (bitDepth/8);
    header[32] = blockAlign & 0xFF;
    header[33] = (blockAlign >> 8) & 0xFF;
    
    // 位深度
    header[34] = bitDepth & 0xFF;
    header[35] = (bitDepth >> 8) & 0xFF;
    
    // data子块标识
    header[36] = 'd'; header[37] = 'a'; header[38] = 't'; header[39] = 'a';
    
    // data块大小占位(后续更新)
    header[40] = 0; header[41] = 0; header[42] = 0; header[43] = 0;
}

类似的,有时候传感器送出 32Bits 数据,我们需要转为 16Bits输出,编写代码如下:

#include <WiFi.h>
#include <WebServer.h>
#include <driver/i2s.h>

// WiFi配置
const char* ssid = "CMCC-TSR6739";
const char* password = "!!1783az";

// I2S配置
#define I2S_MIC_WS  37
#define I2S_MIC_SD  48
#define I2S_MIC_SCK 45
#define SAMPLE_RATE 16000
#define BITS_PER_SAMPLE 32
#define BUFFER_SIZE 1024

WebServer server(80);
int32_t audioBuffer[BUFFER_SIZE];
int16_t webaudioBuffer[BUFFER_SIZE];

void setup() {
  Serial.begin(2000000);
  
  // 初始化I2S
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = BUFFER_SIZE,
    .use_apll = false
  };
  
  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_MIC_SCK,
    .ws_io_num = I2S_MIC_WS,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = I2S_MIC_SD
  };
  
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_NUM_0, &pin_config);

  // 连接WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  Serial.println("IP address: " + WiFi.localIP().toString());

  // 设置HTTP路由
  server.on("/stream", HTTP_GET, handleAudioStream);
  server.begin();
}

void loop() {
  server.handleClient();
  
  // 持续读取音频数据
  size_t bytesRead;
  i2s_read(I2S_NUM_0, &audioBuffer, BUFFER_SIZE * sizeof(int32_t), &bytesRead, portMAX_DELAY);
}

void handleAudioStream() {
  // 生成WAV文件头
  uint8_t wavHeader[44];
  generateWavHeader(wavHeader, SAMPLE_RATE, 16);
  
  // 发送HTTP头
  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
  server.send(200, "audio/wav", "");
  
  // 先发送WAV头
  server.sendContent((const char*)wavHeader, sizeof(wavHeader));
  
  // 持续发送音频数据
  while(server.client().connected()) {
    size_t bytesRead;
    i2s_read(I2S_NUM_0, &audioBuffer, BUFFER_SIZE*4, &bytesRead, portMAX_DELAY);
    for (int i=0;i<BUFFER_SIZE;i++) {
       int32_t audio_24bit = audioBuffer[i] >> 8;       // 提取高 24 位
       webaudioBuffer[i]=(int16_t)((audio_24bit + 128) >> 8);            // 四舍五入到 16 位
  
    }
    server.sendContent((const char*)webaudioBuffer, bytesRead/2);
  }
}


void generateWavHeader(uint8_t* header, uint32_t sampleRate, uint32_t bitDepth) {
    // RIFF块标识
    header[0] = 'R'; header[1] = 'I'; header[2] = 'F'; header[3] = 'F';
    
    // 文件总大小占位(后续更新)
    header[4] = 0; header[5] = 0; header[6] = 0; header[7] = 0;
    
    // WAVE格式标识
    header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E';
    
    // fmt子块标识
    header[12] = 'f'; header[13] = 'm'; header[14] = 't'; header[15] = ' ';
    
    // fmt块大小(16字节)
    header[16] = 16; header[17] = 0; header[18] = 0; header[19] = 0;
    
    // 音频格式(1=PCM)
    header[20] = 1; header[21] = 0;
    
    // 声道数(1=单声道)
    header[22] = 1; header[23] = 0;
    
    // 采样率
    header[24] = sampleRate & 0xFF;
    header[25] = (sampleRate >> 8) & 0xFF;
    header[26] = (sampleRate >> 16) & 0xFF;
    header[27] = (sampleRate >> 24) & 0xFF;
    
    // 字节率 = 采样率 * 声道数 * 位深度/8
    uint32_t byteRate = sampleRate * (bitDepth/8);
    header[28] = byteRate & 0xFF;
    header[29] = (byteRate >> 8) & 0xFF;
    header[30] = (byteRate >> 16) & 0xFF;
    header[31] = (byteRate >> 24) & 0xFF;
    
    // 块对齐 = 声道数 * 位深度/8
    uint16_t blockAlign = (bitDepth/8);
    header[32] = blockAlign & 0xFF;
    header[33] = (blockAlign >> 8) & 0xFF;
    
    // 位深度
    header[34] = bitDepth & 0xFF;
    header[35] = (bitDepth >> 8) & 0xFF;
    
    // data子块标识
    header[36] = 'd'; header[37] = 'a'; header[38] = 't'; header[39] = 'a';
    
    // data块大小占位(后续更新)
    header[40] = 0; header[41] = 0; header[42] = 0; header[43] = 0;
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注