Нужна помощь по i2s esp32

Тема в разделе "ESP8266, ESP32", создана пользователем Dreamlord, 5 мар 2023.

  1. Dreamlord

    Dreamlord Нуб

    Приветствую всех. Впервые столкнулся с проблемой , которую не смог решить гуглом и чтением существующих тем на форумах. Итак делаю проект барабанного триггерного модуля , который воспроизводит звук бас-бочки ( звук в формате wav сохранен в памяти программ ) по сигналу с триггера ( как это выглядит на практике можно посмотреть тут :

    . На данный момент проект существует на платформе Teensy 3.2 + Teensy Audio Board и работает прекрасно . Решил перенести на ESP32 в связке с ЦАП pcm5102 . Поскольку для ESP32 нет аудио-библиотеки , которая бы работала подобно Teensy Audio Library пришлось нужную функцию написать самому. Вернее адаптировать под себя найденный пример на просторах интернета. Cуть проста - инициализируется интерфейс I2S , которому в loop() передается порциями данные аудио из wav файла , и когда все данные переданы воспроизведение останавливается сбросом флага. Так вот каждый раз уже после вывода всех аудио данных звучит довольно ощутимый щелчек ( аудио файл и снимок из cubase ниже ) , как при отключении усилителя обычно происходит. Так же и в начале есть подобный эффект , но гораздо менее ощутимый и слышеш только в наушниках ( семпл бас-бочки имеет плавное нарастание и спад громкости в начале и конце соотв. так что этот артефакт результат не самого аудио файла , а именно аппаратной или программной части ESP32 ) . Так вот у меня вопрос - чем это вызванно и как с этим бороться ? В документации по I2S на сайте Espressif я не нашел функции I2S.Mute() или что нибудь подобное . Кроме того попутный вопрос - как правильно "наложить " воспроизведение двух таких семплов друг на друга со смещением по времени . Т.е. допустим у меня уже запустилось воспроизведение семпла один раз и пока оно длится приходит второй сигнал от триггера и нужно запустить еще раз вопроизведение этого же семпла . Изначально я думал в этой ситуации при повторном срабатывании триггера просто возвращать указатель на аудио-данные wav файла в начало и делать так кадый раз пока не перестанут идти сигналы с триггера и файл просто не доиграет до конца. Но теперь мне кажется , что это не лучшая идея т.к. резкое прерывание воспроизведения будет вызывать подобные искажения с щелчками или еще чем похуже. Спасибо заранее за помощь.
    [​IMG]

    Звуковой пример , записан с выхода ЦАП в линейный вход звуковой карты :
    https://on.soundcloud.com/drGnZ

    Код (C++):

    [== C++ ==]


    //------------------------------------------------------------------------------------------------------------------------

        #include "driver/i2s.h"                    
        #include "Kick.h"                 // файл wav              
    //------------------------------------------------------------------------------------------------------------------------

    //------------------------------------------------------------------------------------------------------------------------

         
        bool             isPlaing = true ;             // флаг состояния воспроизведения ( играет/не играет )
        static const i2s_port_t i2s_num = I2S_NUM_0;   // i2s номер порта
        unsigned const char* TheData;                  // указатель на данные wav файла
        uint32_t DataIdx=0;                            // значение смещения указателя "TheData" в процессе передачи порции данных интерфейсу  I2S
        const  uint8_t* nameWav ;  
        struct WavHeader_Struct
     
        {
          //   RIFF Section  
          char RIFFSectionID[4];      // "RIFF"
          uint32_t Size;              // Size of entire file less 8
          char RiffFormat[4];         // "WAVE"
       
          //   Format Section  
          char FormatSectionID[4];    // letters "fmt"
          uint32_t FormatSize;        // Size of format section less 8
          uint16_t FormatID;          // 1=uncompressed PCM
          uint16_t NumChannels;       // 1=mono,2=stereo
          uint32_t SampleRate;        // 44100, 16000, 8000 etc.
          uint32_t ByteRate;          // =SampleRate * Channels * (BitsPerSample/8)
          uint16_t BlockAlign;        // =Channels * (BitsPerSample/8)
          uint16_t BitsPerSample;     // 8,16,24 or 32
     
          // Data Section
          char DataSectionID[4];      // The letters "data"
          uint32_t DataSize;          // Size of the data that follows
        }WavHeader;
     
    //------------------------------------------------------------------------------------------------------------------------
    uint32_t currenttime ;
    uint32_t looptime ;
    //------------------------------------------------------------------------------------------------------------------------
    // I2S configuration structures

    static const i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
        .sample_rate = 44100,                            // Note, this will be changed later
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,       // high interrupt priority
        .dma_buf_count = 8,                             // 8 buffers
        .dma_buf_len = 1024,                            // 1K per buffer, so 8K of buffer space
        .use_apll=0,
        .tx_desc_auto_clear= true,
        .fixed_mclk=-1  
    };

    // These are the physical wiring connections to our I2S decoder board/chip from the esp32, there are other connections
    // required for the chips mentioned at the top (but not to the ESP32), please visit the page mentioned at the top for
    // further information regarding these other connections.

    static const i2s_pin_config_t pin_config = {
        .bck_io_num = 26,                                 // The bit clock connectiom, goes to pin 27 of ESP32
        .ws_io_num = 25,                                  // Word select, also known as word select or left right clock
        .data_out_num = 27,                               // Data out from the ESP32, connect to DIN on 38357A
        .data_in_num = I2S_PIN_NO_CHANGE                  // we are not interested in I2S data into the ESP32
    };

    //------------------------------------------------------------------------------------------------------------------------


    void setup() {

            memcpy(&WavHeader,&rawData,44);                           // копируем "заголовочную" часть wav файла в структуру
            i2s_driver_install(i2s_num, &i2s_config, 0, NULL);        // Инициализация I2S
            i2s_set_pin(i2s_num, &pin_config);                        // Назначаем пины I2S
            i2s_set_sample_rates(i2s_num, WavHeader.SampleRate);
                                                                   
            TheData=rawData;                                          
            nameWav=TheData+44;                                       // смешаем указатель на начало аудио-данных wav файла
     
    }

    void loop()
    {
          playWav();
        }

     
    void playWav(void){
         size_t BytesWritten;

         while(DataIdx!=WavHeader.DataSize && isPlaing )
         {
                i2s_write(i2s_num,TheData+DataIdx,4,&BytesWritten,1);     //  выводим данные на I2S , если звуковые данные являются представлением двух байт на каждный канал ,
                                                                         //   мы отдаем интерфейсу четыре байта за один раз
                DataIdx+=4;                                             // смещение указателя на четыре байта в массиве данных wav
     
       
     
           if (DataIdx>=WavHeader.DataSize){                         // если вывели все данные
                                                                     // возвращаем указатель в началао массива данных
           DataIdx=0;
       
            isPlaing=false;                                         // сбрасываем флаг для остановки воспроизведения . Если не сбрасывать , файл будет воспроизводиться зацикленно

         
           }
         }
       }