I2Sデジタルマイクで録音、BTシリアルで受信してWAVファイルにしてSpeechRecognition

先日、とあるイベントでESP32に接続された謎のI2S接続のデジタルマイクを制御することになった。
色々解説サイトやGithubを漁ったが、ちゃんとWAVファイル化して音声解析に食わせられるものがなかったので自作。

ESP32側はメモリがないため、DMA転送された音声データをほとんどキャッシュできない。
かなり力技であるがBTシリアルで送信してPC側で録音データを組み立てる案でやってみた。

SPH0645LM4HなどのI2Sデジタルマイクは結構癖がある使い方。
録音データは16kHz/32bitとのこと。本当はデバイスが２つないとステレオにならない模様なのでステレオ録音してLチャンネルの録音データのみ取り出した。
音量が小さいのをビットシフトで調整して０点オフセットかけて取りあえず聴ける。

PC側でWAVファイルとして聞く場合、16kHz/16bitのフォーマットでないとちゃんと聞けなかった。ここにドはまり。
32bitを16bitに削って16kHz/16bitモノラルとして、下記コードで実際録音してみたが、しゃべり内容は十分聞き取れるレベルであった。SpeechRecognitionでも解析OK！

ESP32で動作するコードです。ESP32ライブラリは1.0.6推奨。
WAVヘッダは手打ちで書いてますが、PC側アプリでつけた方が良いです。

pin config通りにデジタルマイクを配線し、ESP32起動したら待ち時間内にPythonアプリを起動してください。

```i2s_mic.ino.cpp
#include "driver/i2s.h"

#include "BluetoothSerial.h"
BluetoothSerial SerialBT;

#define CONFIG_EXAMPLE_REC_TIME (5)
#define CONFIG_EXAMPLE_SAMPLE_RATE (16000)
#define NUM_CHANNELS        (1)
#define SAMPLE_SIZE         (I2S_BITS_PER_SAMPLE_32BIT * 1024)
#define BYTE_RATE           ((CONFIG_EXAMPLE_SAMPLE_RATE * (I2S_BITS_PER_SAMPLE_32BIT / 8)) * NUM_CHANNELS)

/////////////////////////////////////////////////////////////////////
// デジタルマイク用I2S
const i2s_port_t   I2S_PORT   = I2S_NUM_1;
const i2s_config_t i2s_config = {
   .mode                =i2s_mode_t(I2S_MODE_MASTER|I2S_MODE_RX),
   .sample_rate         =CONFIG_EXAMPLE_SAMPLE_RATE,  // サンプリングレート
   .bits_per_sample     =I2S_BITS_PER_SAMPLE_32BIT,   // 32bit
   .channel_format      =I2S_CHANNEL_FMT_RIGHT_LEFT, // ステレオ
   //.channel_format      =I2S_CHANNEL_FMT_ONLY_LEFT,   // only left
   .communication_format=I2S_COMM_FORMAT_PCM,         // PCM
   .intr_alloc_flags    =ESP_INTR_FLAG_LEVEL1,        // Interrupt level 1
   .dma_buf_count       =4,                          // number of buffers
   .dma_buf_len         =32                         //  samples per buffer
   };

const i2s_pin_config_t pin_config = {
   .bck_io_num   = 16, // BCLK
   .ws_io_num    = 17, // LRCL
   .data_out_num = -1, // マイクでは使わない
   .data_in_num  = 26  // DOUT
   };
const static int val_shift = 13;    // ボリューム調整
const static int val_offset= 14000; // 0点補正


// 16kHz/16bitのWAVとして再生できるように
void CreateWavHeader(byte* header, int waveDataSize){
  header[0] = 'R';
  header[1] = 'I';
  header[2] = 'F';
  header[3] = 'F';
  unsigned int fileSizeMinus8 = waveDataSize + 44 - 8;
  header[4] = (byte)(fileSizeMinus8 & 0xFF);
  header[5] = (byte)((fileSizeMinus8 >> 8) & 0xFF);
  header[6] = (byte)((fileSizeMinus8 >> 16) & 0xFF);
  header[7] = (byte)((fileSizeMinus8 >> 24) & 0xFF);
  header[8] = 'W';
  header[9] = 'A';
  header[10] = 'V';
  header[11] = 'E';
  header[12] = 'f';
  header[13] = 'm';
  header[14] = 't';
  header[15] = ' ';
  header[16] = 0x10;  // linear PCM
  header[17] = 0x00;
  header[18] = 0x00;
  header[19] = 0x00;
  header[20] = 0x01;  // linear PCM
  header[21] = 0x00;
  header[22] = 0x01;  // monoral
  header[23] = 0x00;
  header[24] = 0x80;  // sampling rate 16000(0x3E80)
  header[25] = 0x3E;
  header[26] = 0x00;
  header[27] = 0x00;
  header[28] = 0x00;  // Byte/sec = 16kHz x 16bit x monoral 16000*2*1 = 32000(7D00)
  header[29] = 0x7D;
  header[30] = 0x00;
  header[31] = 0x00;
  header[32] = 0x02;  // 16bit monoral
  header[33] = 0x00;
  header[34] = 0x10;  // 16bit(WAVは8か16)
  header[35] = 0x00;
  header[36] = 'd';
  header[37] = 'a';
  header[38] = 't';
  header[39] = 'a';
  header[40] = (byte)(waveDataSize & 0xFF);
  header[41] = (byte)((waveDataSize >> 8) & 0xFF);
  header[42] = (byte)((waveDataSize >> 16) & 0xFF);
  header[43] = (byte)((waveDataSize >> 24) & 0xFF);
}

void setup() {
  Serial.begin(115200);
  Serial.println("Configuring I2S...");
  SerialBT.begin("ESP32test");

  // デジタルマイク
  esp_err_t _err;
  _err = i2s_driver_install(I2S_PORT,&i2s_config, 0, NULL);
  if (_err != ESP_OK) {
    Serial.printf("Failed installing driver: %d\n", _err);
    while (true);
  }
  _err = i2s_set_pin(I2S_PORT, &pin_config);
  if (_err != ESP_OK) {
    Serial.printf("Failed setting pin: %d\n", _err);
    while (true);
  }
  Serial.println("I2S driver installed.");

  // 起動したらPythonアプリが録音待ちになるまで待つ
  Serial.printf("Count down...");
  uint32_t delaytime = 15;
  for (int i=0; i<delaytime; i++){
    Serial.printf("%d,", delaytime-i);
    delay(1000); 
  }
  Serial.printf("Start REC!\n");


  // WAVヘッダを送信
  uint32_t rec_size = BYTE_RATE * CONFIG_EXAMPLE_REC_TIME;
  const char wav_header[44]={0};
  CreateWavHeader((byte*) wav_header, rec_size);
  SerialBT.write((const uint8_t *)&wav_header, sizeof(wav_header));

  record_wav(CONFIG_EXAMPLE_REC_TIME);

  Serial.printf("End REC!\n");
}

void record_wav(uint32_t rec_time)
{
  int i2s_read_len = SAMPLE_SIZE;
  size_t bytes_read;// ここに読み込んだ長さ
  char* i2s_read_buff = (char*) calloc(i2s_read_len, sizeof(char));

  // 空読み
  i2s_read(I2S_PORT, (void*) i2s_read_buff, i2s_read_len, &bytes_read, portMAX_DELAY);
  i2s_read(I2S_PORT, (void*) i2s_read_buff, i2s_read_len, &bytes_read, portMAX_DELAY);

  int data_wr_size = 0;
  uint32_t record_size = BYTE_RATE * rec_time;
  int32_t   _samples[2];	// I2SのDMAからは32bit x LRチャンネル分得る
  while (data_wr_size < record_size)
  {
    int16_t _vL[128]; // ★WAVは16bit
    for(int i=0; i < 128; i++){
      i2s_read(I2S_PORT, (void*) _samples, 8, &bytes_read, portMAX_DELAY);	// DMA転送は4byte(32bit)で得る
      _vL[i] = (_samples[1]>>val_shift)+val_offset; // 音量と左右位置調整　(★ここで4byte→2byteに落とす)
    }
    SerialBT.write((const uint8_t*)_vL, 256);
    data_wr_size += 256;
  }
  Serial.println(data_wr_size);
}

void loop() {
}
```

```data_recv.py
import serial
import speech_recognition as sr

# BTシリアルなので普通のシリアルポートとして見えます
com = serial.Serial('COM7', 115200)

f = open('test1.wav', 'wb')
s = ""
read_byte = 256
end_size = 320000
datanum = 0
while True:
    data = com.read(read_byte)
    f.write(data)
    print(data)
    datanum = datanum + read_byte
    if datanum >= end_size:
        print("end recv.")
        break

f.close()

com.close()

# WAVファイルから音声解析
r = sr.Recognizer()
with sr.AudioFile("test1.wav") as source:
    audio_d = r.listen(source)

    print(r.recognize_google(audio_d, language='ja-JP'))

```