In my last blog, I described you I added M4A support to my Arduino Audio Tools project and how to use it.

As highlited we need to access and store the stsz size tabe to play back the audio: a typical size table contains aournd 40 – 80 kbytes, but for long audio this can get much bigger: This is not an amount of data, that you would like to have to store on a Microcontroller.

In this document I present a couple of solution approaches that avoid the need to store this amount of data in RAM

Avoiding the Container Class

My existing container classes use a streaming-based approach: you feed data in small chunks, which makes the processing logic independent of the data source type. To support M4A playback more efficiently, I introduced a new class:

M4AAudioFileDemuxer

This class acts as a demuxer for M4A/MP4 files, designed to extract audio data directly from a file without buffering the sample size table in RAM.

  • It locates the mdat and stsz boxes using the MP4Parser.

  • You assign a MultiDecoder in the constructor.

  • You start processing by calling begin(File file).

  • It offers a copy() method to extract frames by reading the sample sizes directly from the stsz box in the file.

Because the sample sizes are read on-the-fly from the file, this solution is very memory-efficient. Data is passed directly to the decoder or an optional frame_callback..

Buffers

Initially, I was storing the size table in a Vector of uint32_t. In order to half the memory requirements, I changed the data type to uint16_t: this is really sufficient for audio! In the next step, I replaced the Vector with a BaseBuffer, so that we can use any supported Buffer type and add it with the setSampleSizesBuffer() method.

Here are some of the most relevant buffer classes:

RingBufferFile

The RingBufferFile is a file backed ring buffer. Instead of storing the data in RAM we just store it in a separate file.


// Define global variables
MultiDecoder multi_decoder;
ContainerM4A dec_m4a(multi_decoder);
File buffer_file;
RingBufferFile<File,stsz_sample_size_t> file_buffer(0);

// in setup call
buffer_file = SD.open("/home/pschatzmann/tmp.tmp", O_RDWR | O_CREAT);
file_buffer.begin(buffer_file);
dec_m4a.setSampleSizesBuffer(file_buffer);

RedisBuffer

This implementation uses a Redis list as a circular buffer. Data is buffered locally and pushed/popped in batches to minimize network overhead.

  • Uses RPUSH for writing and LPOP for reading.

  • Built on the Arduino Client API.

  • Supports automatic Redis key expiration.

  • The local batch size is configurable.

  • Works well for streaming scenarios.

.

// Define global variables
MultiDecoder multi_decoder;
ContainerM4A dec_m4a(multi_decoder);
WiFiClient client;
RedisBuffer<stsz_sample_size_t> redis(client,"m4a-buffer1",0, 1024, 0);

// in setup call
WiFi.begin("ssid","pwd");
while ( WiFi.status() != WL_CONNECTED) {
   Serial.print(".");
}
if (!client.connect(IPAddress(192,168,1,10),6379)){
  Serial.println("redis error");
  stop();
}
dec_m4a.setSampleSizesBuffer(redis);

M4AFileSampleSizeBuffer

This class reuses the M4AAudioFileDemuxer functionality and mimics a buffer interface. It reads sample sizes directly from an M4A file during playback without storing them in memory.

  • Works only with file-based sources (not streams).

  • Integrates with AudioPlayer:it registers a setOnStreamChangeCallback() to automatically handle file changes.


// Define global variables MultiDecoder multi_decoder; AudioPlayer player(source, out, multi_decoder); ContainerM4A dec_m4a(multi_decoder); M4AFileSampleSizeBuffer sizes_buffer(player, dec_m4a); // in setup call dec_m4a.setSampleSizesBuffer(sizes_buffer);

Conclusion

These buffering strategies offer a lot of flexibility for optimizing your sketches to support M4A audio playback. Depending on your use case—whether you’re streaming, using files, or dealing with long audio content—you can choose the most appropriate approach to minimize RAM usage and maximize performance.

 

Categories: ArduinoMachine Sound

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *