Stuttering mp3 stream on ESP32 — how to optimize or increase buffer? #1930
-
I am currently working on a CD player / mini receiver project based around ESP32. As part of that I want to receive internet radio streams, and this library is supposedly perfect for this. However when I try to stream a tiny 128kbps radio in mp3, or a 96kbps AAC, the playback stutters like crazy every 1-2 seconds. When playing a 192kbps mp3 from LittleFS, this does not happen and the sound is perfectly correct. Just in case I also tried turning off all my tasks that are related to UI or HID and it was still stuttering. Similarly with all my UI tasks running the LittleFS playback was all clear. Is there any way I could bump the buffer size to a few megabytes? I have 4 MB of PSRAM and 4 MB of HIMEM if that is also usable. If possible I would prefer to use the whole 4MB of HIMEM as a buffer, but even a 2MB buffer on PSRAM would be massively helpful I think. Here is the snippets of the code. There is no audio related code outside of those except for the router route setup which I included. I2S Output Setup ...
if(next == ROUTE_INTERNAL_CPU) {
spdif_teardown_muting_hax();
_spdif->set_enabled(false);
auto cfg = _i2s->defaultConfig(TX_MODE);
cfg.copyFrom(cpuOutputParams);
cfg.pin_bck = i2s_pins.bck;
cfg.pin_data = i2s_pins.data;
cfg.pin_mck = i2s_pins.mck;
cfg.pin_ws = i2s_pins.lrck;
_i2s->begin(cfg);
_resampler->setAudioInfoOut(cfg);
_resampler->setOutput(*_i2s);
_resampler->begin();
set_mute_internal(false);
}
... The decoder plugs into the converter stream: this is to help if any stream uses a different sample rate from 48000/16: AudioStream * AudioRouter::get_output_port() {
return _resampler;
} When using LittleFS and all sounds perfect: const char *startFilePath="/";
const char* ext="mp3";
AudioSourceLittleFS fileSrc(startFilePath, ext);
...
InternetRadioMode::InternetRadioMode(const PlatformSharedResources res):
urlStream(),
source(urlStream, urls, "audio/mp3"), //<- disregard this for now
codec(),
player(),
sndTask("IRASND", 10000, 5, 1),
station_buttons({}), // todo
chgSource(Button(res.keypad, (1 << 0))),
moreStations(Button(res.keypad, (1 << 7))),
Mode(res) {
rootView = new InternetRadioView({{0, 0}, {160, 32}});
}
void InternetRadioMode::setup() {
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
resources.router->activate_route(Platform::AudioRoute::ROUTE_INTERNAL_CPU);
player.setAudioSource(fileSrc);
player.setBufferSize(4096); // adding this had no effect
player.setDecoder(codec);
player.setOutput(*resources.router->get_output_port());
player.addNotifyAudioChange(resources.router->get_output_port());
player.setMetadataCallback(printMetaData);
player.begin();
sndTask.begin([this]() { player.copy(); });
// now sound starts playing and it's perfectly correct
} When using the WiFi stream: const char *urls[] = {
"http://icecast.ndr.de/ndr/ndr1wellenord/kiel/mp3/128/stream.mp3"
};
...
InternetRadioMode::InternetRadioMode(const PlatformSharedResources res):
urlStream(),
source(urlStream, urls, "audio/mp3"), //<- now we use this
codec(),
player(),
sndTask("IRASND", 10000, 5, 1),
station_buttons({}), // todo
chgSource(Button(res.keypad, (1 << 0))),
moreStations(Button(res.keypad, (1 << 7))),
Mode(res) {
rootView = new InternetRadioView({{0, 0}, {160, 32}});
}
void InternetRadioMode::setup() {
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
resources.router->activate_route(Platform::AudioRoute::ROUTE_INTERNAL_CPU);
player.setAudioSource(source);
player.setBufferSize(4096); // adding this had no effect
urlStream.setReadBufferSize(2 * 1024 * 1024); // adding this had no effect either
urlStream.setPowerSave(false); // this is the default but just in case -- still, no effect
player.setDecoder(codec);
player.setOutput(*resources.router->get_output_port());
player.addNotifyAudioChange(resources.router->get_output_port());
player.setMetadataCallback(printMetaData);
player.begin();
sndTask.begin([this]() { player.copy(); });
} I presume the buffering is the issue since I can listen to the same stream perfectly on my PC. Thanks in advance for your help P.S. For some reason using audio also kills JTAG... but not sure if it's an ESP thing or this library's issue. |
Beta Was this translation helpful? Give feedback.
Replies: 8 comments 24 replies
-
Just in case I did a speed test against the same stream: void InternetRadioMode::setup() {
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
MeasuringStream measure(50, &Serial);
StreamCopy copier(measure, urlStream);
urlStream.begin("http://icecast.ndr.de/ndr/ndr1wellenord/kiel/mp3/128/stream.mp3");
while(1) copier.copy();
...
} Result:
Looking at the numbers in the very first section of Phil's post — seems like it's average for a radio station? (I mean, 16000 bytes per second is literally 128kbps which is what this station broadcasts at) So probably the issue is not in the connection speed? |
Beta Was this translation helpful? Give feedback.
-
The problem is partially remedied by an enormously oversized buffer at the output side, as well as disabling the resampling — the stutter becomes fairly rare (once every multiple seconds rather than once every 1-2 seconds):
But eventually something in the codec shits itself and the system crashes, probably for the lack of RAM:
|
Beta Was this translation helpful? Give feedback.
-
I would suggest to check first with this simple sketch, if you can reproduce the issue with your url. If this is playing fine, then the issue must be somewhere in your logic and e.g. your playback task is slowed down too much. To address the issue with the metadata, I suggest that you filter it out e.g. with a MetaDataFilterDecoder or a MetaDataFilter in your chain. It would be interesting if you could debug this error, so that we can try to prevent this in the future: The findTag method currently has already some checks to prevent this and I am not sure what would be missing... You can also try to use one of the Buffered URL Streams (which buffers the encoded data) that I initially implemented, but never needed because the regular URLStream was always good enough... |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Try to update again: I tested with the following sketch #include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
#include "AudioTools/AudioCodecs/CodecAACHelix.h"
#include "AudioTools/AudioLibs/AudioBoardStream.h"
URLStream url("ssid","password");
AudioBoardStream i2s(AudioKitEs8388V1);
MultiDecoder multi;
MP3DecoderHelix mp3;
AACDecoderHelix aac;
WAVDecoder wav;
EncodedAudioStream dec(&i2s, &multi); // Decoding stream
StreamCopy copier(dec, url); // copy url to decoder
void setup(){
Serial.begin(115200);
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);
multi.addDecoder(mp3, "audio/mpeg");
multi.addDecoder(aac, "audio/aac");
multi.addDecoder(wav, "audio/vnd.wave");
// setup i2s
auto config = i2s.defaultConfig(TX_MODE);
// you could define e.g your pins and change other settings
//config.pin_ws = 10;
//config.pin_bck = 11;
//config.pin_data = 12;
//config.mode = I2S_STD_FORMAT;
i2s.begin(config);
// setup I2S based on sampling rate provided by decoder
dec.begin();
// mp3 radio
url.begin("http://stream.srg-ssr.ch/m/rsj/mp3_128","audio/mpeg");
}
void loop(){
copier.copy();
} |
Beta Was this translation helpful? Give feedback.
-
Hmm, so for the time being I decided to work on refactoring the code to not use AudioPlayer and instead rely on just a StreamCopy. Surprisingly, the stutter is back — but this time it's local. I tried enabling the logs for the thing, and what I noticed is:
Every time the MetaDataICY string is logged, the audio stutters, all other times it's fine. Could it be that the Player is using a different way to parse metadata which isn't causing such lag? If I change it to a normal URLStreamBuffered, it doesn't lag anymore, but of course doesn't give metadata too. But I still can't grasp why it worked via AudioPlayer and with a simple StreamCopy it broke. UPD: I fixed it by specifying a buffer size for the ICY stream: |
Beta Was this translation helpful? Give feedback.
-
On the latest I'm still getting occasional stack corruption with some mp3 and most AAC stations. Here is how I play now to keep the UI thread unblocked: sndTask = new Task("IRASND", 30000, 15, 1);
sndTask->begin([this]() {
decoder.addNotifyAudioChange(*resources.router->get_output_port());
urlStream.setMetadataCallback(_update_meta_global);
copier.resize(24 * 1024);
decoder.begin();
urlStream.begin("http://icecast.ndr.de/ndr/ndr1wellenord/kiel/mp3/128/stream.mp3", "audio/mpeg");
rootView->set_loading(false);
int hwmark = 9999999;
while(1) {
copier.copy();
int x = uxTaskGetStackHighWaterMark(NULL);
if(x < hwmark) {
hwmark = x;
ESP_LOGW(LOG_TAG, "High water mark of audio task is now %i", hwmark);
}
}
}); What happens, sometimes after a minute, sometimes near instantly:
|
Beta Was this translation helpful? Give feedback.
I would suggest to check first with this simple sketch, if you can reproduce the issue with your url.
If this is playing fine, then the issue must be somewhere in your logic and e.g. your playback task is slowed down too much.
To address the issue with the metadata, I suggest that you filter it out e.g. with a MetaDataFilterDecoder or a MetaDataFilter in your chain. It would be interesting if you could debug this error, so that we can try to prevent this in the future: The findTag method currently has already some checks to prevent this and I am not sure what would be missing...
You can also try to use one of the Buffered URL Streams (which buffers the encoded data) that I initially imple…