I started to work on a FLAC CODEC for my Arduino Audio Tools.

FLAC stands for Free Lossless Audio Codec, an audio format similar to MP3, but lossless, meaning that audio is compressed in FLAC without any loss in quality. This is similar to how Zip works, except with FLAC you will get much better compression because it is designed specifically for audio, and you can play back compressed FLAC files in your favorite player (or your car or home stereo, see supported devices) just like you would an MP3 file.

FLAC stands out as the fastest and most widely supported lossless audio codec, and the only one that at once is non-proprietary, is unencumbered by patents, has an open-source reference implementation, has a well documented format and API, and has several other independent implementations.

Decoding

My solution builds upon the adapted LibFLAC Library that I am providing as Arduino Library. LibFLAC provides a simple streaming API where we just need to provide a callback method for providing the input data and one for processing the output data. This is implemented in the FLACDecoder class: On the input side we can just read from an Arduino stream, but on the output side I needed to massage the data a bit to make sure that it has the right format.

Arduino Sketch

An example Arduino Sketch – which is reading a file via the internet – would look as follows:

#include "AudioTools.h"
#include "AudioCodecs/CodecFLAC.h"

const char* ssid = "ssid";
const char* pwd = "password";
URLStream url(ssid, pwd);
FLACDecoder dec;
I2SStream i2s;

void setup() {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Info);  

  i2s.begin(i2s.defaultConfig(TX_MODE));

  url.begin("http://www.lindberg.no/hires/test/2L-145_01_stereo_01.cd.flac");
  dec.setOutputStream(i2s);
  dec.setInputStream(url);
  dec.begin();
}

void loop() {
  dec.copy();
}

So we just need to provide the input and output as streams and can shovel the data in the loop by calling copy(). The potentially updated example code can be found on Github.

Decoding Conclusion

The Streaming Interface is very memory efficient, but it is different then the regular approach which is based on the EncodedAudioStream and a StreamCopy classes. I tried to provide this regular processing model as well, but it is very memory inefficient because we need to keep a huge amount of audio data in the buffer to keep the decoder happy. So the approach which was shown above is definitely the preferred one.

I was testing the decoder with CD quality on an ESP32, but this just sees to be a little bit too slow and the sound is slightly breaking up. So I expect that with a smaller sampling rate we should be fine or we can just use files as data source instead.

Encoding

The implementation of the FLACEncoder class was straight forward and there were only few negative surprises: like the number of samples are actually the number of frames and that the block size is driving the memory allocation. I finally using 512 byte as default value for the number of frames to keep the memory allocation small.

A test encoding sketch can be found on Github.

Dependencies


0 Comments

Leave a Reply

Avatar placeholder

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