Выборка I2S-данных и сохранение на SD-карту как wav воспроизводит исходный звук?
Меня беспокоит сохранение аудиоданных с 2-х I2S MEMS-микрофонов на SD-карту с ESP32 WROOM, и, похоже, мне это не повезло ...
Моя идея: путем опроса флага прерывания (активируется всякий раз, когда буфер DMA заполнен), предлагаемого espressif I2S-API, я вызываю вызов I2S_read и сохраняю полученные данные в больший буфер. Со временем этот буфер заполняется и отправляется на SD-карту, где wav-заголовок уже ожидает данные. Информация о длине в заголовке обрабатывается последней задачей записи, которая получает свою информацию путем вычитания функции millis() времени ОС при нажатии кнопки записи и другого вызова millis() при нажатии кнопки опять таки.
мои ресурсы:
- ESP32 WROOM
- 2x МЭМС-микрофона ADAFRUIT SPH0645 (стерео I2S)
- Адаптер ADAFRUIT для карты micro-SD (SPI)
- кнопка внешнего прерывания
- платформаIO
- freeRTOS
- частота дискретизации 44100 Гц
- битовая глубина 32 бит
- стерео
Моя проблема: помимо потери данных и другого шума в моих данных, меня беспокоит еще одна проблема: мои файлы представляют собой звук, ускоренный в два раза быстрее, но не в два раза выше, что означает, что это не должно быть просто проблема с wav-заголовком. Глядя на заголовок в формате HxD, можно увидеть правильные числа в заголовке, так что моя выборка имеет фундаментальный недостаток, которого я не вижу. Как я уже сказал, выборка полна шума, но это можно исправить с помощью умных настроек буфера. Однако вопрос времени не в этом. Может ли кто-нибудь определить в этом основную проблему? Я уверен, что это просто где-то меняют число, но на мой взгляд все надо рассчитывать ...
что я пробовал: изменение частоты дискретизации или переключение на моно действительно меняет высоту тона, но скорость, кажется, изменяется линейно с этим, всегда вдвое превышая предполагаемую, что указывает на то, что это действительно не проблема заголовка. Даже после полной переделки кода с суперцикла на основанный на задачах, единственная ошибка, которая не исчезла, - это проблема двойной скорости, что для меня ОЧЕНЬ странно.
мой код:
эта задача читает и сохраняет данные в больших массивах для дальнейшей обработки
void readI2STask(void* param){
//DEBUG
digitalWrite(WHITE_LED_PIN, LOW);
//init I2S
I2S_Init(I2S_MODE_RX, I2S_BITS_PER_SAMPLE_32BIT);
//fix for SPH645-specific timing-error:
REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9));
REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT);
//index for globally accessible array
uint16_t bigBufferIndex = 0;
while(!powerOff_flag){
i2s_event_t evt;
//poll for ready-flag in queue
if (xQueueReceive(_i2sSampleQueue, &evt, portMAX_DELAY) == pdPASS){
if (evt.type == I2S_EVENT_RX_DONE){
size_t bytesRead = 0;
do{
uint8_t i2sData[SMALL_BUF_LEN];
i2s_read(I2S_NUM_0, i2sData, SMALL_BUF_LEN, &bytesRead, 10);
int32_t* samples = (int32_t*)i2sData;
for (int i = 0; i < bytesRead / 4; i++){
SDBufMem1[bigBufferIndex] = samples[i];
bigBufferIndex++;
if (bigBufferIndex == SD_BUF_LEN_32BIT){
std::swap(SDBufMem1, SDBufMem2);
bigBufferIndex = 0;
bufFlag = (!bufFlag); //shows other functions which buffer is ready
if(recButton_flag){
xTaskNotify(_sendToSDTask, 1, eIncrement);
}
else{
//xTaskNotify(_LCD_displayVUTask, 1, eIncrement);
}
}
}
} while (bytesRead > 0);
}
}
}
}
эта задача сохраняет доступные данные на SD
void sendToSDTask(void* param){
vTaskSuspend(_LCD_displayVUTask);
static char filename[] = "/rec000.wav";
const int headerSize = 512;
//default file-time = 1 second
const int waveDataSize = 2 * 44100 * 4;
byte header[headerSize];
File file;
const TickType_t xMaxBlockTime = pdMS_TO_TICKS(100);
//create RIFF-WAV-Header
CreateWavHeader(header, waveDataSize);
//find valid name
findName(filename);
//open/make file on SD card with given name
file = SD.open(filename, FILE_WRITE);
if(!file){
//TODO: exception handler for abrupt disconnection of SD-card before recording
}
//induce wav-header into start of file
file.write(header, headerSize);
//red means recording!
digitalWrite(RED_LED_PIN, HIGH);
unsigned long startTime = millis();
unsigned long endTime;
while (recButton_flag)
{
//wait for notofication from 16k array
uint32_t ulNotificationValue = ulTaskNotifyTake(pdTRUE, xMaxBlockTime);
if (ulNotificationValue > 0)
{
if(bufFlag){
//1st buffer ready
file.write((const uint8_t*)SDBufMem1, (size_t)(SD_BUF_LEN_32BIT));
}
else{
//2nd buffer ready
file.write((const uint8_t*)SDBufMem2, (size_t)(SD_BUF_LEN_32BIT));
}
}
}
//calculate recording-lenght and save it to header at the start of the file
endTime = millis();
digitalWrite(RED_LED_PIN, LOW);
CreateWavHeader(header, ((endTime-startTime) * 2* 4 * 44100 / 1000));
file.seek(0);
file.write(header, headerSize);
file.close();
vTaskResume(_LCD_displayVUTask);
vTaskDelete(NULL);
}
WAV-заголовок-Creater
void CreateWavHeader(byte* header, int waveDataSize){
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
unsigned int fileSizeMinus8 = waveDataSize + 512 - 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; // fmt lenght = 16byte
header[17] = 0x00; //
header[18] = 0x00; //
header[19] = 0x00; //
header[20] = 0x01; // format tag = 1 = PCM
header[21] = 0x00; //
header[22] = 0x02; // channels = 2 = stereo
header[23] = 0x00; //
header[24] = 0x44; // sampling rate = 44100
header[25] = 0xAC; //
header[26] = 0x00; //
header[27] = 0x00; //
header[28] = 0x20; // Byte/sec = 44100 * 2 * 4 = 352800
header[29] = 0x62; //
header[30] = 0x05; //
header[31] = 0x00; //
header[32] = 0x08; // block align (n channels = 2 * (bits/sample of one channel = 32 + 7)/8) = 8
header[33] = 0x00; //
header[34] = 0x20; // bits/sample = 32
header[35] = 0x00; //
header[36] = 'p'; //new padding sub-chunk. Increases SD-performance since it writes in 512 byte sized blocks naturally
header[37] = 'a'; //
header[38] = 'd'; //
header[39] = 'd'; //
header[40] = 0xCC; //padlen is rest of header - 512 = 460
header[41] = 0x01; //
header[42] = 0x00; //
header[43] = 0x00; //
for(int i = 44; i < 504; i++){
header[i] = 0x00;
}
header[504] = 'd'; //data sub-chunk
header[505] = 'a'; //
header[506] = 't'; //
header[507] = 'a'; //
header[508] = (byte)(waveDataSize & 0xFF);
header[509] = (byte)((waveDataSize >> 8) & 0xFF);
header[510] = (byte)((waveDataSize >> 16) & 0xFF);
header[511] = (byte)((waveDataSize >> 24) & 0xFF);
//real samples get appended here
}
функция инициализации I2S
void I2S_Init(i2s_mode_t MODE, i2s_bits_per_sample_t BPS)
{
//general config of I2S
//const: 44100 sample rate, stereo
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | MODE),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = BPS,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_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
};
//pin format, adjust in i2s.h if needed:
i2s_pin_config_t pin_config = {
.bck_io_num = PIN_I2S_BCLK,
.ws_io_num = PIN_I2S_LRC,
};
//i tried putting this into statement above, threw an error (?)
pin_config.data_in_num = PIN_I2S_DIN;
//init I2S-driver/peripherals
i2s_driver_install(I2S_NUM_0, &i2s_config, 8, &_i2sSampleQueue);
i2s_set_pin(I2S_NUM_0, &pin_config);
i2s_set_clk(I2S_NUM_0, SAMPLE_RATE, BPS, I2S_CHANNEL_STEREO);
}
спасибо за любую помощь, очень ценим.~ Джейк