Today's microcontrollers are pretty powerful, but without careful coding, you're likely to have glitches in your MP3 playback. Here are some tips for projects that use MP3Decoder in CircuitPython.
Construct your audio playback object
Depending on your board, this might be:
Construct MP3Decoder objects just once
MP3 decoding takes a lot of RAM. To reduce RAM fragmentation, create your MP3Decoder
object or objects near the start of your program. In order to change the MP3 file that your decoder object plays, use the open()
method or assign the file
property.
When creating an MP3Decoder object, you must always specify an MP3 file. This can even be a silent MP3 file that just has a valid MP3 header. This file is used to set the initial properties of the audio stream such as bit rate and channel count.
import audiomp3 mp3_decoder = audiomp3.MP3Decoder("/silence.mp3")
Using a buffer for MP3 streams
Optionally, you can specify a buffer when creating an MP3Decoder
. This is usually not necessary when playing a stream from the CIRCUITPY drive or SD card, but it can reduce stuttering when playing from a network stream. This buffer is used to store two frames of decoded data (9216 bytes) and not-yet-decoded MP3 data (the remainder).
For a 128kbit/s stream, a total buffer size of 16384 bytes (16KiB) can buffer about 1/2 second of encoded MP3 data. This is a good value to start with.
import audiomp3 mp3_buffer = bytearray(16384) mp3_decoder = audiomp3.MP3Decoder("/silence.mp3", mp3_buffer)
Tune AudioMixer buffers for MP3 mixing
MP3 data is generally organized in frames of 1152 samples. For stereo 16-bit audio, this means 1 frame is 4*1152=4608 bytes. The best performance is obtained when the AudioMixer buffer size is a small multiple of the MP3 frame size:
import audiomixer mixer_buffer_size = (1152 * 4) * 4 mixer = audiomixer.Mixer( channel_count=2, sample_rate=44100, buffer_size=mixer_buffer_size )
adafruit_requests settings for MP3 streaming
For MP3 streaming you must make your request with the Connection: close header, specify the stream=True
positional argument, and use the socket
property of the returned request object. You must also employ the with ...:
syntax to properly manage the socket resource:
with requests.get("http://ice2.somafm.com/dronezone-128-mp3", headers={"connection": "close", stream=True) as response: mp3decoder.file = response speaker.play(mp3decoder) while speaker.playing: time.sleep(.1)
It's possible to use this pair of functions to treat local files and remote streams consistently with one another, and do all resource management correctly:
def get_mp3_stream(location): if location.startswith("http:") or location.startswith("https:"): return requests.get( location, headers={"connection": "close"}, stream=True, ) return open(location, "rb") def set_stream(decoder, obj): decoder.file = getattr(obj, 'socket', obj) with get_mp3_stream("http://ice2.somafm.com/dronezone-128-mp3") as request: set_stream(decoder, request)
Don't call open()
or assign file
while an mp3 is playing
This isn't allowed, but due to technical limitations CircuitPython can't currently raise an exception in this case.
Don't use play(loop=True)
with a stream
Looping is allowed for files, but not streams. Due to technical limitations CircuitPython can't currently raise an exception in this case.
Avoid allocating memory during playback
Operations such as creating lists, tuples and dicts allocate memory. Any time memory is allocated, there's a possibility that garbage collection will be required. Garbage collection pauses not only the execution of the CircuitPython code, but also stops the background process that allows more audio data to be generated. Especially on microcontrollers with PSRAM, the length of time taken by garbage collection can be longer than the audio buffer size, leading to gaps or stutters in audio playback.
Schedule when you update your display
Set your display to manual refresh and call refresh when needed. When a background display update is in progress, the background process that allows more audio data to be generated cannot occur, because only one background task can occur at a time.
Design your user interface so that only a small part of the display updates during playback.
Page last edited July 08, 2024
Text editor powered by tinymce.