[libmaple]Adafruit MP3 native decoding with F103xC/D/E/F/G MCU
victor_pv Sun Mar 25, 2018 4:27 am
The original used a timer to trigger an ISR at the required frequency and loads 1 value a a time in the DAC.
I modified it so it uses the I2S peripheral, and loads a block of data at once to transfer with DMA.
I need to send a PR with modifications to the SDIO library, but SPI with sdfatEx should be fast enough too.
Currently takes ~11mS to decode a frame from a stereo 44100 256Kbps mp3 file. That generates 1152 samples. At that rate the I2S peripheral takes 13mSto play the same number of samples, so that leaves 2mS to read data from the card and anything else you want to do.
With a 22Khz 64Kbps file, it take around 5mS to decode a frame, and 26mS to play it, so that would leave 80% of the CPU time available for something else.
Also since the SDFat library uses DMA, it is possible to use the yield call to do something else while the DMA transfers complete.
The I2S library has several new functions not in the Arduino API, to allow the sketch to access the buffer directly, and to notify the callback function on whether the interrupt was for Half Transfer to Transfer Complete, so the DMA can work in circular mode with no interruption and just call the callback function in the sketch when the top or bottom half of the buffer has been played, so the that half can be reloaded with new data.
This is how the modified code works now:
=========================
Setup() will read the pointer to the I2S buffer and the size (in bytes, so has to be /2 to get the size in int16_t thats the sample size from the mp3 library).
Next it will set a callback function to the I2S library, called when DMA HT or TC interrupts trigger.
After that it starts the I2S peripheral in circular mode, so all that’s left is to reload the data buffer when the ISR is called.
Then calls the play function of the MP3 library. That’s modified so it doesn’t use any timer any more. Instead now checks when the two internal buffers have space for 1 frame (1152 samples), and if so, decodes a frame to the buffer. The MP3 library is repeatedly called in loop(), and just checkes whether a buffer has space for samples, if not, returns, and some other user code could run after it.
When the I2S DMA triggers the ISR, the sketch ISR function will check which half of the buffer is empty, and then call a new function within the mp3 library to copy data from the mp3 buffer to the I2S buffer, and update some variables to indicate when a buffer is fully emptied, then returns.
The loop calls to mp3 keep being called often enough to keep loading new data from the SDcard, and then decoding it to one of the buffers.
I added some pieces of code from the teensy mp3 library to increase the decoding speed a good bit.
The code is here:
https://github.com/victorpv/Adafruit_MP3
Further possible optimizations:
- Use only 1 buffer, and the I2S DMA read from the same buffer the MP3 library writes to. This would eliminate some overhead, and some time spent in copying from one buffer to the other, but the copy doesn’t take that long, so it’s not critical. Main advantage would be to save some RAM.
- Add support to use the DACs. Ideally write a library with an API similar to the I2S one, so sketches work on both
- Further speed optimizations. With 44100Hz stereo files it takes almost all the CPU time decoding.
