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.
3 Comments
Geoff · 15. March 2024 at 4:44
Hi Phil,
I’ve been looking through your site. You’ve done a lot of great work – well done.
I’m planning to build a multi-channel music player. Minimum requirements are 8 channels, 24-bit samples at 96 kHz. Aspiration is 8 channels, 32-bit samples at 192 kHz. The music is encoded as FLAC and stored in files on my network. I plan to use a separate hardware DAC, so I only need to decode FLAC to something the DAC can use , probably PCM. I may be able. to feed the PCM directly to my Oppo Bluray player but this would use HDMI.
I can’t imagine a single ESP32 has enough CPU power but perhaps several could. I can see challenges synchonising the output data. What do you might be a sensible approach? Appreciate your thoughts.
,Geoff.
pschatzmann · 15. March 2024 at 6:33
I never tried something like this, so you will need to experiment to find the limits.
However I currently would not see how you could output 8 channels: I2S has only 2 channels and the ESP32 has only 2 I2S Ports. I suggest that you open a discussion on Github since this is not the right place for this: https://github.com/pschatzmann/arduino-audio-tools/discussions
Geoff · 16. March 2024 at 1:01
Thanks Phil,
I2S constraint noted.
I have opened a thread on github and cross posted.
Best, Geoff.