Introduction

I was looking for a MP3 Encoder Library for Arduino, but unfortunately did not find anything. So I decided to take this up and make LAME available as Arduino Library.

LAME is a high quality MPEG Audio Layer III (MP3) encoder licensed under the LGPL. It is considered as one of the best MP3 encoder at mid-high bitrates and at VBR.

I used the latest current release version 3.100 which can be downloaded from Sourceforge as starting point. The conversion to the Arduino Library format was quite easy: I just needed to put all relevant code under the src directory. Arduino does not provide any dynamic code configuration, so I added the config.h file where the configuration can be driven with simple #defines.

A Simple Example

I also added as simple Arduino style API and a basic example and was pleased that everything seemed to compile and run on the Desktop with my Arduino Emulator:

#include "MP3EncoderLAME.h"
#include <stdlib.h>  // for rand

using namespace liblame;

void dataCallback(uint8_t *mp3_data, size_t len) {
    Serial.print("mp3 generated with ");
    Serial.print(len);
    Serial.println(" bytes");
}

MP3EncoderLAME mp3(dataCallback);
AudioInfo info;
int16_t buffer[512];

void setup() {
    Serial.begin(115200);

    info.channels = 1;
    info.sample_rate = 44100;
    mp3.begin(info);
}

void loop() {
    Serial.println("writing 512 samples of random data");
    for (int j=0;j<512;j++){
        buffer[j] = (rand() % 100) - 50;         
    }
    mp3.write(buffer, 512*sizeof(int16_t));
}

An Ugly Surprise

The problems started, when I tried to run the example on an ESP32. I just got crashes w/o any stack trace which left me no possibility to investigate what the issue was. The first thing I did, was to add some tracing output at the beginning of the methods and some additional logging related to memory allocations and deallocations. This allowed me to identify the location of the crashes. You can activate this by setting USE_DEBUG and USE_DEBUG_ALLOC to 1.

The first issue I addressed, was a failing heap allocation of a big amount of memory, by splitting it up. This feature can be activated by setting USE_MEMORY_HACK to 1. The code got further now, but was still crashing. After analyzing the situation, I realized that the issue came from the fact that there are quite a few methods which allocate big arrays on the stack and the available stack was just not big enough.

So, first I tried to move these arrays out of the local memory into the free store, but I ended up with the situation, that the compiled program did not fit into a simple ESP32 any more. I decided to use the heap and release the memory again when the method was left. This feature can be activated with USE_STACK_HACK 1.

Finally I was double checking the allocation requirements in the encoding loop: We get 2 allocations and 2 frees of heap memory of the same size – so there is no risk of memory heap defragmentation.

Performance

In the examples you find a sketch which measures the encoding speed. It pretty much depends on the processor, the number of channels, the sample_rate and the quality. For random generated noise on 1 channel, a sample rate of 44100 and the quality of 1, I am getting an encoding rate of around 45 kHz on a ESP32!

When you activate the USE_FAST_LOG, the speed increases to 54 kHz at the cost of available RAM and using the USE_FAST_LOG_CONST which stores the values in Flash Memory, you still get 53 kHz!

Conclusions

I am happy to announce that the examples are running on an ESP32 now w/o problems!

The project can be found on Github.


7 Comments

Lennard · 23. October 2024 at 18:54

Hi Phil and everyone,

when I try the example in GitHub, my ESP gives me the following error

[Error] lame.c : 2792 – calloc(1,85840) -> 0x0
available MALLOC_CAP_8BIT: 110580 / MALLOC_CAP_32BIT: 110580 / MALLOC_CAP_SPIRAM: 0

Any suggestions on how I can get the mp3 encoder running?
I am using an ESP32 WROOM-32D.
I find it werid that calloc is called with a a value lower that the CAP but still fails. And I know too little to fix this.

    pschatzmann · 23. October 2024 at 22:08

    Just look at the issue in the project!
    This question has already been answered…

valter · 3. August 2023 at 0:28

Hi, do you have some basic exemple with arduino streaming instead of callback function? I want to read data from i mic and send it along over the web after compress it in mp3 codec or aac. Thank you very much in advance.

Bromium · 10. January 2023 at 22:30

“Unfortunately the available memory on Microcontrollers is quite restricted and we do not get very far by storing a (uncompressed) WAV file e.g. in program/flesh memory, so I started to look into compressed audio formats.”

I am at a bit of a loss here. Why bother with flash or SRAM memory when you can easily have a SD card device on the micro? A WAV file can easily be 100 MB. Even when compressed, it will not fit on an ESP32.

Thanks,
Bromium

    pschatzmann · 11. January 2023 at 3:40

    This is about choice: If your project justifies a SD drive that’s a great solution!
    However, I think it is a cool option e.g. to be able to build a Text to Speech solution with prerecorded audio w/o even using an SD drive…

Alex · 13. October 2021 at 10:14

Hi, On ESP32 Arduino core v1.6 – worked, on core v2.0 – get error :
“[Error] C:\Users\Documents\Arduino\libraries\arduino-liblame\src\liblame\lame.c : 2740 – calloc(1,38808) -> 0 [available: 172067]”

Leave a Reply

Avatar placeholder

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