SD_MMC edit file header

Hi! I wanted to ask if it is possible to do the following:

I'm using an esp32 s3 n16r8, with a SDIO interface for the microSD.
And I need to :

  1. Create a .wav file.
  2. Write a placeholder header.
  3. Save the audio data.
  4. Close the file.
  5. Reopen it later.
  6. Calculate its size.
  7. Edit the header with the correct file size.

So far, I haven't been able to reopen the file for editing.
The limitation is that I am using the SD_MMC library.

Any help would be greatly appreciated!

My sketch is something like this:

  #include "esp_sleep.h"         // Sleep mode.
  #include "ESP32Time.h"         // RTC.
  #include "WiFi.h"              // RTC update.
  #include "SD_MMC.h"            // SD MMC mode.
  #include "driver/i2s_std.h"    // I2S STD driver.
  #include "driver/gpio.h"       // I2S STD driver?
  #include "SdFat.h"
//

// SD MMC #defines.
  #define MMC_D2 4  
  #define MMC_D3 5
  #define MMC_CMD 6
  #define MMC_CLK 7
  #define MMC_D0 15
  #define MMC_D1 16

 // some code ......

  wavGenerator(audioFileName.c_str(), true); // Creates wav file with temporary header.
  
  duration = 2*60*1000; /*debugging*/
  while (millis() - startTime < duration)
  {
    I2S_read();
    SD_write();

    if (cycleCount >= MAX_CICLE_COUNT)
    {
      cycleCount = 0;
      Serial.printf("Start. - ");
      Serial.printf("Ciclo : %d .  -  ", cycleCount);
      Serial.printf("Tiempo ciclo : %d . \n", millis() - cycleTime);
      cycleTime = millis();
    }
    cycleCount++;
  }
  wavGenerator(audioFileName.c_str(), false); // Saves proper wav header.
// more code...

The 2nd time wavGenerator is called, it should calculate the size of the file and update the header. take into account that the recording time is unknown before beggining the recording. The file could be 1, 2 up to 10 hours long (16 bit 44.1khz).

void wavGenerator(const char *filename, bool create) // Generates .wav files with proper header.
{
  // If create == true -> writes a temporary header at the beginning of the file.
  // If create == false -> updates the header with the correct data size.

  struct WavHeader // Header for .wav files.
  {
    char ChunkID[4] = {'R', 'I', 'F', 'F'};
    uint32_t ChunkSize;
    char Format[4] = {'W', 'A', 'V', 'E'};
    char Subchunk1ID[4] = {'f', 'm', 't', ' '};
    uint32_t Subchunk1Size = 16; // PCM
    uint16_t AudioFormat = 1; // PCM
    uint16_t NumChannels = 1; // Mono
    uint32_t SampleRate = SAMPLERATE;
    uint32_t ByteRate = SAMPLERATE * 1 * 16 / 8;
    uint16_t BlockAlign = 1 * 16 / 8;
    uint16_t BitsPerSample = 16;
    char Subchunk2ID[4] = {'d', 'a', 't', 'a'};
    uint32_t Subchunk2Size;
  } header;

  if (!SD_MMC.begin("/sdcard", true, SDMMC_SPEED)) return;

  File file = SD_MMC.open(filename, FILE_WRITE);
  if (!file) return;

  header.Subchunk2Size = create ? 0 : file.size() - sizeof(WavHeader);
  header.ChunkSize = header.Subchunk2Size + 36;

  file.seek(0);
  file.write((uint8_t*)&header, sizeof(header));
  file.flush();
  file.close();
  SD_MMC.end();

  Serial.println(create ? "Header creado." : "Header actualizado.");
  Serial.println(filename);
}

The problem is i dont know if file.seek(0) is properly used.ยก
If I use file_append it just appends the data to the end of the file.
file_write deletes all the data and saves just the header.

Two basic rules

  • Opening in write mode always destroys an existing file.
  • When open in append mode, writes always turn into appends, regardless of the current position

Instead of FILE_WRITE, there is no macro for what you need, which is read extended mode: "r+". The tricky part with that is if the file doesn't exist, it will just fail and not create it. There is no single mode that will create if necessary or open in read/write instead of append.

Since you have a bool create argument, that can control whether to open with FILE_WRITE or "r+"

More info on this in a post/thread a few months ago.

Many many thanks @kenb4. the sketch now does work properly.

void initWAV(const char *filename, bool create) // Generates .wav files with proper header.
{
  // If create == true -> writes a temporary header at the beginning of the file.
  // If create == false -> updates the header with the correct data size.

  struct WavHeader 
  {
    char ChunkID[4] = {'R', 'I', 'F', 'F'};
    uint32_t ChunkSize;
    char Format[4] = {'W', 'A', 'V', 'E'};
    char Subchunk1ID[4] = {'f', 'm', 't', ' '};
    uint32_t Subchunk1Size = 16; // PCM
    uint16_t AudioFormat = 1; // PCM
    uint16_t NumChannels = 1; // Mono
    uint32_t SampleRate = SAMPLERATE;
    uint32_t ByteRate = SAMPLERATE * 1 * 16 / 8;
    uint16_t BlockAlign = 1 * 16 / 8;
    uint16_t BitsPerSample = 16;
    char Subchunk2ID[4] = {'d', 'a', 't', 'a'};
    uint32_t Subchunk2Size;
  } header;

  if (!SD_MMC.begin("/sdcard", true, SDMMC_SPEED)) return;

  // create == true -> FILE_WRITE creates file.
  // create == false -> "r+" updates header.
  File file = SD_MMC.open(filename, create ? FILE_WRITE : "r+" );
  if (!file) return;

  header.Subchunk2Size = create ? 0 : file.size() - sizeof(WavHeader);
  header.ChunkSize = header.Subchunk2Size + 36;

  file.seek(0);
  file.write((uint8_t*)&header, sizeof(header));
  file.flush();
  file.close();
  SD_MMC.end();

  Serial.println(create ? "Header created." : "Header updated.");
}