3

I have configured I2S to reproduce files with an ESP32 and a MAX98357A from and SD card. The problem is that it reproduces the files way too fast, like they are sped up * 100. Here is my (simplified) code:

#define I2S_DOUT 25
#define I2S_BCLK 27
#define I2S_LRC 26

void setup() {
// Start SD card and sqlite
  SPI.begin();
  if (!SD.begin()) {
    Serial.println("SD card not found");
    while (1) {};
  }
// Configure I2S
  i2s_config_t i2s_config = {
      .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
      .sample_rate = 44100,
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
      .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
      .communication_format = I2S_COMM_FORMAT_I2S_MSB,
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
      .dma_buf_count = 2,
      .dma_buf_len = 1024,
      .tx_desc_auto_clear = true,
  };

  i2s_pin_config_t pin_config = {
      .bck_io_num = I2S_BCLK,
      .ws_io_num = I2S_LRC,
      .data_out_num = I2S_DOUT,
      .data_in_num = -1,
  };

  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_NUM_0, &pin_config);
  i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN);
  i2s_set_sample_rates(I2S_NUM_0, 44100);
}

void loop() {
// Play the audio file
  string filename = "/audios/" + text + ".wav";
  File file = SD.open(filename.c_str());
  if (!file) {
    Serial.println("Failed to open file for reading");
    return;
  }

  const size_t buffer_size = 512;
  uint8_t buffer[buffer_size];
  size_t bytes_read;
  size_t bytes_written;
  while (file.available() && (bytes_read = file.read(buffer, buffer_size)) > 0) {
    i2s_write(I2S_NUM_0, buffer, bytes_read, &bytes_written, portMAX_DELAY);
    if (bytes_read == 0) break;
  }
  file.close();
}

How do I configure it correctly?

1 Answer 1

2

So I figured it out (a somewhat long time ago) but forgot to post here hahaha.

Basically, the i2s_config_t was not properly configured. Here's a corrected version:

i2s_config_t i2s_config = {
      .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
      .sample_rate = 44100,
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
      .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
      .communication_format = I2S_COMM_FORMAT_I2S_MSB,
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
      .dma_buf_count = 8,
      .dma_buf_len = 1024,
      .use_apll = false,
      .tx_desc_auto_clear = true,
  };

If you compare, the .channel_format configuration changes from I2S_CHANNEL_FMT_RIGHT_LEFT to I2S_CHANNEL_FMT_ONLY_LEFT. This is important because when using RIGHT_LEFT and only using one amplifier, 2 sound outputs were sent but only one amp was receiving them, causing the sound to play at twice the speed. Now, the .dma_buf_count was increased to 8, and added the .use_apll config, because I saw it somewhere else, I don't really know what it does hahaha.

There are other things.
In the setup for the i2s communication, I didn't include

i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN);
i2s_set_sample_rates(I2S_NUM_0, 44100);

And created a new function, play_sound.

Here's the complete code:

// sound.cpp

#include <sound.h>

bool parseWavHeader(File &file, WavHeader &header) {
  if (file.read() != 'R' || file.read() != 'I' || file.read() != 'F' || file.read() != 'F') {
    Serial.println("Invalid WAV file");
    return false;
  }

  file.seek(22);
  header.num_channels = file.read() | (file.read() << 8);
  header.sample_rate = file.read() | (file.read() << 8) | (file.read() << 16) | (file.read() << 24);
  file.seek(34);
  header.bits_per_sample = file.read() | (file.read() << 8);
  file.seek(44);  // Skip to the audio data
  return true;
}

void setup_sound() {
  i2s_config_t i2s_config = {
      .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
      .sample_rate = 44100,
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
      .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
      .communication_format = I2S_COMM_FORMAT_I2S_MSB,
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
      .dma_buf_count = 8,
      .dma_buf_len = 1024,
      .use_apll = false,
      .tx_desc_auto_clear = true,
  };

  i2s_pin_config_t pin_config = {
      .bck_io_num = I2S_BCLK,
      .ws_io_num = I2S_LRC,
      .data_out_num = I2S_DOUT,
      .data_in_num = -1,
  };

  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_NUM_0, &pin_config);
}

void play_sound(std::string text, fs::FS &SD) {
  std::string filename = "/audios/" + text + ".wav";
  File file = SD.open(filename.c_str());
  if (!file) {
    Serial.println("Failed to open file for reading");
    while (1) delay(1);
  }

  WavHeader header;
  if (!parseWavHeader(file, header)) {
    Serial.println("Invalid WAV file");
    return;
  }

  i2s_set_sample_rates(I2S_NUM_0, header.sample_rate);

  const size_t buffer_size = 512;
  uint8_t buffer[buffer_size];
  size_t bytes_read;
  size_t bytes_written;
  while (file.available() && (bytes_read = file.read(buffer, buffer_size)) > 0) {
    i2s_write(I2S_NUM_0, buffer, bytes_read, &bytes_written, portMAX_DELAY);
    if (bytes_read == 0) break;
  }

  file.close();
}
// sound.h

#ifndef SOUND_H
#define SOUND_H

#include <SD.h>
#include <driver/i2s.h>

#define I2S_DOUT 25
#define I2S_BCLK 33
#define I2S_LRC 26

struct WavHeader {
  uint32_t sample_rate;
  uint16_t bits_per_sample;
  uint16_t num_channels;
};

bool parseWavHeader(File &file, WavHeader &header);
void setup_sound();
void play_sound(std::string text, fs::FS &SD);

#endif  // SOUND_H

Hope it helps you not go through the struggle I went through!

1
  • good job. the another solution is to change the .sample_rate = 16000, can play well. for the file saved at same sample_rate.
    – laoadam
    Commented Dec 8, 2023 at 15:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.