• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Делюсь опытом Запись аудио с микрофона

unreg

Member
Ну вот при содействии форумчан удалось таки записать звук с микрофона. ESP32-wrover-b, Микрофон на плате max9814 (OUT подключен к GPIO36 (SENSOR VP)), и microSD-карта class 10 8Гб. Звук пишется довольно качественно для голоса, длина записи может регулироваться конечным размером аудиоданных :rolleyes: не стал заморачиваться с кнопками, цель была не в этом. Формат файла 8 бит, 8КГц.
Скетч:
Код:
#include <Arduino.h>
#include <driver/adc.h>

#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define AUDIO_BUFFER_MAX 51200 //эмпирически выбрал. склеек не слышно. буфер успевает записаться на SD пока другой заполняется новыми аудио данными

uint8_t audioBuffer[AUDIO_BUFFER_MAX];

uint8_t audioBufferTmp[AUDIO_BUFFER_MAX];

uint32_t bufferPointer = 0;

bool transmitNow = false;
bool recordStarting = false;

hw_timer_t * timer = NULL; // our timer
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

fs::File fContent;

int touch_sensor_value = 0;
const int VALUE_THRESHOLD = 70;

volatile bool audio_buffer_ready = false;
int interruptCounter = 0;
size_t audio_data_size = 0;

byte  header[44] = {};

bool startRecording = true;

void setup() {

    Serial.begin(115200);
    if (!SD.begin()) {
        Serial.println("Card Mount Failed");
        startRecording = false;
        return;
    }
    startRecording = true;
    if (SD.exists("/file.wav"))
        SD.remove("/file.wav");
   
    Serial.println("Ready");

    adc1_config_width(ADC_WIDTH_12Bit);
    adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_11db);

    fContent = SD.open("/file.wav", FILE_WRITE);
    fContent.write((const byte *)header, sizeof(header));
    Serial.println("Rec started file.wav");

    timer = timerBegin(0, 80, true); 
    timerAttachInterrupt(timer, &onTimer, true); 
    timerAlarmWrite(timer, 125, true);
    timerAlarmEnable(timer);
}

void IRAM_ATTR onTimer() {

    portENTER_CRITICAL_ISR(&timerMux);
    uint16_t sample = adc1_get_raw(ADC1_CHANNEL_0);
    uint8_t value = map(sample, 0, 4095, 0, 255);
    audioBuffer[bufferPointer] = value;
    bufferPointer++;
    if (bufferPointer == AUDIO_BUFFER_MAX)
    {
        memcpy(audioBufferTmp, audioBuffer, AUDIO_BUFFER_MAX);
        bufferPointer = 0;
        audio_buffer_ready = true;
    }
    portEXIT_CRITICAL_ISR(&timerMux);
}

void loop() {
    if (!startRecording)
        return;
    if (audio_buffer_ready)
    {
        audio_buffer_ready = false;
        audio_data_size += fContent.write((const uint8_t *)audioBufferTmp, sizeof(audioBufferTmp));
    }
   
    if (audio_data_size > 500000) //500000 это тот самый размер аудиоданных (~1 мин)
    {
        startRecording = false;
        timerAlarmDisable(timer);
        CreateWavHeader(header, audio_data_size);
        fContent.seek(0);
        fContent.write((const byte *)header, sizeof(header));
        fContent.close();
        SD.end(); //корректно отключаем SD, я уже одну убил
        Serial.println("Done!");
        Serial.print("Audio Data size:");
        Serial.println(audio_data_size, DEC);
    }
   
}

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; 
    header[17] = 0x00;
    header[18] = 0x00;
    header[19] = 0x00;
    header[20] = 0x01;
    header[21] = 0x00;
    header[22] = 0x01;
    header[23] = 0x00;
    header[24] = 0x40;
    header[25] = 0x1F;
    header[26] = 0x00;
    header[27] = 0x00;
    header[28] = 0x40;
    header[29] = 0x1F;
    header[30] = 0x00;
    header[31] = 0x00;
    header[32] = 0x01;
    header[33] = 0x00;
    header[34] = 0x08;
    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);
}

uint16_t changeEndianess(uint16_t val)
{
    return (val << 8) | ((val >> 8) & 0x00ff);
}
Прошу предложения, замечания и 12-ти битный wav-файл (они вообще бывают и проигрываются стандартными плеерами?). Не допер как собрать чанк формата и не нашел конвертера который бы мог сделать 12 бит, а так хоть хексом подгляжу.
 

nikolz

Well-known member
Ну вот при содействии форумчан удалось таки записать звук с микрофона. ESP32-wrover-b, Микрофон на плате max9814 (OUT подключен к GPIO36 (SENSOR VP)), и microSD-карта class 10 8Гб. Звук пишется довольно качественно для голоса, длина записи может регулироваться конечным размером аудиоданных :rolleyes: не стал заморачиваться с кнопками, цель была не в этом. Формат файла 8 бит, 8КГц.
Скетч:
Код:
#include <Arduino.h>
#include <driver/adc.h>

#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define AUDIO_BUFFER_MAX 51200 //эмпирически выбрал. склеек не слышно. буфер успевает записаться на SD пока другой заполняется новыми аудио данными

uint8_t audioBuffer[AUDIO_BUFFER_MAX];

uint8_t audioBufferTmp[AUDIO_BUFFER_MAX];

uint32_t bufferPointer = 0;

bool transmitNow = false;
bool recordStarting = false;

hw_timer_t * timer = NULL; // our timer
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

fs::File fContent;

int touch_sensor_value = 0;
const int VALUE_THRESHOLD = 70;

volatile bool audio_buffer_ready = false;
int interruptCounter = 0;
size_t audio_data_size = 0;

byte  header[44] = {};

bool startRecording = true;

void setup() {

    Serial.begin(115200);
    if (!SD.begin()) {
        Serial.println("Card Mount Failed");
        startRecording = false;
        return;
    }
    startRecording = true;
    if (SD.exists("/file.wav"))
        SD.remove("/file.wav");
  
    Serial.println("Ready");

    adc1_config_width(ADC_WIDTH_12Bit);
    adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_11db);

    fContent = SD.open("/file.wav", FILE_WRITE);
    fContent.write((const byte *)header, sizeof(header));
    Serial.println("Rec started file.wav");

    timer = timerBegin(0, 80, true);
    timerAttachInterrupt(timer, &onTimer, true);
    timerAlarmWrite(timer, 125, true);
    timerAlarmEnable(timer);
}

void IRAM_ATTR onTimer() {

    portENTER_CRITICAL_ISR(&timerMux);
    uint16_t sample = adc1_get_raw(ADC1_CHANNEL_0);
    uint8_t value = map(sample, 0, 4095, 0, 255);
    audioBuffer[bufferPointer] = value;
    bufferPointer++;
    if (bufferPointer == AUDIO_BUFFER_MAX)
    {
        memcpy(audioBufferTmp, audioBuffer, AUDIO_BUFFER_MAX);
        bufferPointer = 0;
        audio_buffer_ready = true;
    }
    portEXIT_CRITICAL_ISR(&timerMux);
}

void loop() {
    if (!startRecording)
        return;
    if (audio_buffer_ready)
    {
        audio_buffer_ready = false;
        audio_data_size += fContent.write((const uint8_t *)audioBufferTmp, sizeof(audioBufferTmp));
    }
  
    if (audio_data_size > 500000) //500000 это тот самый размер аудиоданных (~1 мин)
    {
        startRecording = false;
        timerAlarmDisable(timer);
        CreateWavHeader(header, audio_data_size);
        fContent.seek(0);
        fContent.write((const byte *)header, sizeof(header));
        fContent.close();
        SD.end(); //корректно отключаем SD, я уже одну убил
        Serial.println("Done!");
        Serial.print("Audio Data size:");
        Serial.println(audio_data_size, DEC);
    }
  
}

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;
    header[17] = 0x00;
    header[18] = 0x00;
    header[19] = 0x00;
    header[20] = 0x01;
    header[21] = 0x00;
    header[22] = 0x01;
    header[23] = 0x00;
    header[24] = 0x40;
    header[25] = 0x1F;
    header[26] = 0x00;
    header[27] = 0x00;
    header[28] = 0x40;
    header[29] = 0x1F;
    header[30] = 0x00;
    header[31] = 0x00;
    header[32] = 0x01;
    header[33] = 0x00;
    header[34] = 0x08;
    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);
}

uint16_t changeEndianess(uint16_t val)
{
    return (val << 8) | ((val >> 8) & 0x00ff);
}
Прошу предложения, замечания и 12-ти битный wav-файл (они вообще бывают и проигрываются стандартными плеерами?). Не допер как собрать чанк формата и не нашел конвертера который бы мог сделать 12 бит, а так хоть хексом подгляжу.
вопрос уточните.
Вы же принимаете 12 бит.
укажите что у вас буфер 16 битный и пишите в него.
или что-то другое надо?
 

unreg

Member
Да, принимаю12 бит, точнее 4 байта инт со значимыми 12 битами, потом масштабирую на 8. это потому что не получается проиграть полученный 12битный. То ли я не правильно заголовок составляю, то ли в принципе 12 бит не играются тем же виндоус плеером. Дело в том что 12 бит понятное дело это 2 байта. выравниваются по левому краю, плюс младший байт со старшим переставляются при записи в файл. Я все это проделывал, но плеер не распознает формат, чую заголовок формирую не правильно и подглядеть некуда
 

unreg

Member
качал разные конвертеры так ни олин из них 12 ьитный вав делать не могет
 

nikolz

Well-known member
качал разные конвертеры так ни олин из них 12 ьитный вав делать не могет
что мешает делать 16 бит? т е 12 бит помещаете в 16 бит.будет громкость меньше.
если надо громкость повысить то 12 бит двигаете влево на 4 бита и пишите в 16 бит.
 

unreg

Member
а заголовок в чанке формат тоже под 16 бит? там есть два байта под bitPerSample, ну и другие поля зависящие от этого значения...Предлагаете заголовок под 16 бит собрать? Определяться будет как 16 бит а пофакту 12. Надо попробовать
 

nikolz

Well-known member
а заголовок в чанке формат тоже под 16 бит? там есть два байта под bitPerSample, ну и другие поля зависящие от этого значения...Предлагаете заголовок под 16 бит собрать? Определяться будет как 16 бит а пофакту 12. Надо попробовать
чем отличается 12 бит от 16 бит? тем что сигнал в 12 битах в 16 раз слабее (вместо крика будет шопот).
другое дело что если будете воспроизводить через ШИМ то качество будет что с 12 что с 16 что с 8 практически одинаковое
 

unreg

Member
да просто хотелось стандарта придерживаться. воспроизводить буду через цап. заказал td1387
 

nikolz

Well-known member
да просто хотелось стандарта придерживаться. воспроизводить буду через цап. заказал td1387
вы же с микрофона вводите голос. качество будет не очень
если воспроизводите сами то зачем вам заголовки
или вы сжимаете данные?
тогда тем более зачем 16 бит.
надо изначально определиться вам с качеством и разрядностью чтобы избыточность не гонять впустую.
 

nikolz

Well-known member
чтобы не тратить память надо определить буфер в байтах
и упаковывать два отсчета в три байта.
 

unreg

Member
Воспроизводить я буду не только то что с микрофона, но и качественные короткие сэмплы. Устройство многофункциональное по задумке. А на счет чистого потока это прямо мысли читаете :) тем более что реализовал уже такое через встроеный цап, битности не хватает
 

nikolz

Well-known member
Воспроизводить я буду не только то что с микрофона, но и качественные короткие сэмплы. Устройство многофункциональное по задумке. А на счет чистого потока это прямо мысли читаете :) тем более что реализовал уже такое через встроеный цап, битности не хватает
а как вы установили, что битности не хватает?
я например делал говорящий прибор и гонял 8 и 12 бит через ШИМ и ЦАП результат получился одинаковый.
 

unreg

Member
я так понял лучше чем качество в 8 бит там и не получить. поэтому наверное одинаково и получалось
 

unreg

Member
а ну поэтому я цап 16 бит i2s заказал. чтоб уж гонять так гонять)) 8 бит встроеного для моих целей не хватает
 

unreg

Member
да мне не только мои 12 гонять, мне надо будет нормальные качественные данные проигрывать 16/44. микрофон лишь часть задумки и для него хватит и 12
 
Сверху Снизу