[ESP32 오디오 및 비디오 전송] ②I2S를 통해 SPH0645 마이크의 오디오 데이터를 수집하고 서버에 업로드하여 실시간 재생

Tip: 글 작성 후 자동으로 목차가 생성될 수 있으며, 생성 방법은 오른쪽 도움말 문서를 참고하시면 됩니다.


머리말

本文章基于Arduino ESP32 2.07版本,因为2.04版本开始I2S驱动被更改了,所以相同代码可能效果不太同 本文主要参考了:https://atomic14.com/2020/09/12/esp32-audio-input.html


1. I2S 마이크 SPH0645

ESP32에는 외부 마이크에서 데이터를 읽는 여러 가지 방법이 있습니다.

①내장된 ADC(Analog-to-Digital Converter)에서 직접 데이터 읽기
일회성 읽기에는 유용하지만 높은 샘플링 속도에는 적합하지 않습니다. ②I2S를 사용
하여 DMA를 통해 내장 ADC를 읽는 것은 MAX4466 및 MAX9814와 같은
아날로그 마이크에 적합합니다.


SPH0645

2. 사용 단계

1. 연결도

코드는 다음과 같습니다(예제).
여기에 이미지 설명 삽입
(위 사진은 모두 출처: https://diyi0t.com/i2s-sound-tutorial-for-esp32/ )

①비트 클럭 라인

공식 명칭은 "SCK(Continuous Serial Clock)"입니다. 일반적으로 "비트 클럭(BCLK)"이라고 씁니다.

② 워드클록 라인

공식적으로 "단어 선택(WS)". 종종 "LRCLK(Left-Right Clock)" 또는 "FS(Frame Sync)"라고 합니다.

0 = 왼쪽 채널, 1 = 오른쪽 채널

③데이터 케이블

정식 명칭은 "Serial Data(SD)"이지만 SDATA, SDIN, SDOUT, DACDAT, ADCDAT 등으로 불릴 수도 있습니다.

내 배선 다이어그램은 다음과 같습니다.
여기에 이미지 설명 삽입
핀은 다음과 같습니다.

// i2s pins
i2s_pin_config_t i2sPins = {
    
    
    .bck_io_num = GPIO_NUM_15,
    .ws_io_num = GPIO_NUM_16,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = GPIO_NUM_21};

2. 아두이노 메인 파일 코드

프로젝트 코드의 I2S 작업 SPH0645는 다음을 참조합니다: https://github.com/atomic14/esp32-walkie-talkie , 원래 프로젝트는 HTTP 스트림을 사용합니다. 이 예는
다음과 같이 AsyncUDP 기본 파일 코드로 변경됩니다.

#include <Arduino.h>
#include <WiFi.h>

#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"

#include "configuration.h"
#include "I2SMEMSSampler.h"

#include "AsyncUDP.h"

const char * ssid = "your-ssid"; //WiFi名称
const char * password = "password";//wifi密码

IPAddress serverip = IPAddress(192,168,1,108);    //服务端的静态IP地址
uint16_t toport_sound = 8085;              //服务端的开放端口
const int SAMPLE_SIZE = 700;//udp一次最大发送的数据包大小,UDP最大发生有限制

AsyncUDP SoundUDP;


I2SSampler *i2sSampler = NULL;

void InitUDP(IPAddress ip, uint16_t toport_sound){
    
    

  if(SoundUDP.connect(ip, toport_sound)) {
    
    
      Serial.println("UDP connected");
      SoundUDP.onPacket([](AsyncUDPPacket packet) {
    
    
          Serial.print("UDP Packet Type: ");
          Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast");
          Serial.print(", From: ");
          Serial.print(packet.remoteIP());
          Serial.print(":");
          Serial.print(packet.remotePort());
          Serial.print(", To: ");
          Serial.print(packet.localIP());
          Serial.print(":");
          Serial.print(packet.localPort());
          Serial.print(", Length: ");
          Serial.print(packet.length());
          Serial.print(", Data: ");
          Serial.write(packet.data(), packet.length());
          Serial.println();
          //reply to the client
          packet.printf("S2 Got %u bytes of data", packet.length());
      });
      //Send unicast
      //SoundUDP.print("This is S2 Server");
  }  

}

// i2s config for reading from left channel of I2S
i2s_config_t i2sMemsConfigLeftChannel = {
    
    
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 16000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 4,
    .dma_buf_len = 1024,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0};

// i2s pins
i2s_pin_config_t i2sPins = {
    
    
    .bck_io_num = GPIO_NUM_15,
    .ws_io_num = GPIO_NUM_16,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = GPIO_NUM_21};



//      发送音频数据
void sendSoundData(uint8_t *bytes, size_t count, AsyncUDP* UDPClient = &SoundUDP, uint16_t the_port = toport_sound)
{
    
    
  // send them off to the server
  digitalWrite(2, HIGH);
  /*
  httpClient->begin(*wifiClient, url);
  httpClient->addHeader("content-type", "application/octet-stream");
  httpClient->POST(bytes, count);
  httpClient->end();
  */

 //这是向局域网内所有用户广播,若只发给连接的用户,要用UDPClient.write()
  UDPClient->broadcastTo(bytes, count,the_port);
  digitalWrite(2, LOW);
}

// Task to write samples to our server
void i2sMemsWriterTask(void *param)
{
    
    
  I2SSampler *sampler = (I2SSampler *)param;
  int16_t *samples = (int16_t *)malloc(sizeof(uint16_t) * SAMPLE_SIZE);
  if (!samples)
  {
    
    
    Serial.println("Failed to allocate memory for samples");
    return;
  }
  while (true)
  {
    
    
    int samples_read = sampler->read(samples, SAMPLE_SIZE);
    //sendSoundData((uint8_t *)samples, samples_read * sizeof(uint16_t));
    SoundUDP.write((uint8_t *)samples, samples_read * sizeof(uint16_t));
    //SoundUDP.broadcastTo((uint8_t *)samples, samples_read * sizeof(uint16_t),toport_sound);
  }
}

void setup()
{
    
    
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
  Serial.begin(115200);
  //Serial.setDebugOutput(false);
  // launch WiFi
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    
    
        Serial.println("WiFi Failed");
        while(1) {
    
    
            delay(1000);
        }
    }

  // indicator LED
  pinMode(2, OUTPUT);

  InitUDP(serverip,toport_sound);

  // Direct i2s input from INMP441 or the SPH0645
  i2sSampler = new I2SMEMSSampler(I2S_NUM_1, i2sPins, i2sMemsConfigLeftChannel, false);
  i2sSampler->start();


  // set up the i2s sample writer task
  TaskHandle_t i2sMemsWriterTaskHandle;
  xTaskCreatePinnedToCore(i2sMemsWriterTask, "I2S Writer Task", 4096, i2sSampler, 1, &i2sMemsWriterTaskHandle, 1);

  // // start sampling from i2s device
}

void loop()
{
    
    
  // nothing to do here - everything is taken care of by tasks

}

여기에 사용된 URL 네트워크에서 요청한 데이터입니다.

3. 서버는 UDP를 사용하여 오디오 코드를 수신합니다.

import pyaudio
import socket

# 配置参数
chunk = 1400
sample_rate = 16000
duration = 5
udp_port = 8085

p = pyaudio.PyAudio()

# 打开音频流
stream = p.open(format=pyaudio.paInt16,
                channels=1,
                rate=sample_rate,
                output=True)

# 打开UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', udp_port))

# 播放PCM音频数据
while True:
    data, addr = sock.recvfrom(chunk)
    print(f"Received message ")
    stream.write(data)

# 关闭音频流和UDP套接字
stream.stop_stream()
stream.close()
sock.close()
p.terminate()


아두이노 완성 프로그램

https://download.csdn.net/download/loveliveoil/87762126

추천

출처blog.csdn.net/loveliveoil/article/details/130542231