Skip to content

Commit bbd9436

Browse files
committed
MP3Decoder: take advantage of background callback
Before this, the mp3 file would be read into the in-memory buffer only when new samples were actually needed. This meant that the time to read mp3 content always counted against the ~22ms audio buffer length. Now, when there's at least 1 full disk block of free space in the input buffer, we can request that the buffer be filled _after_ returning from audiomp3_mp3file_get_buffer and actually filling the DMA pointers. In this way, the time taken for reading MP3 data from flash/SD is less likely to cause an underrun of audio DMA. The existing calls to fill the inbuf remain, but in most cases during streaming these become no-ops because the buffer will be over half full.
1 parent cf46432 commit bbd9436

File tree

2 files changed

+41
-10
lines changed

2 files changed

+41
-10
lines changed

shared-module/audiomp3/MP3Decoder.c

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@
3636

3737
#include "shared-module/audiomp3/MP3Decoder.h"
3838
#include "supervisor/shared/translate.h"
39+
#include "supervisor/background_callback.h"
3940
#include "lib/mp3/src/mp3common.h"
4041

4142
#define MAX_BUFFER_LEN (MAX_NSAMP * MAX_NGRAN * MAX_NCHAN * sizeof(int16_t))
4243

43-
/** Fill the input buffer if it is less than half full.
44+
/** Fill the input buffer unconditionally.
4445
*
4546
* Returns true if the input buffer contains any useful data,
4647
* false otherwise. (The input buffer will be padded to the end with
@@ -50,10 +51,7 @@
5051
*
5152
* Sets self->eof if any read of the file returns 0 bytes
5253
*/
53-
STATIC bool mp3file_update_inbuf(audiomp3_mp3file_obj_t* self) {
54-
// If buffer is over half full, do nothing
55-
if (self->inbuf_offset < self->inbuf_length/2) return true;
56-
54+
STATIC bool mp3file_update_inbuf_unconditional(audiomp3_mp3file_obj_t* self) {
5755
// If we didn't previously reach the end of file, we can try reading now
5856
if (!self->eof) {
5957

@@ -87,14 +85,37 @@ STATIC bool mp3file_update_inbuf(audiomp3_mp3file_obj_t* self) {
8785
return self->inbuf_offset < self->inbuf_length;
8886
}
8987

88+
STATIC int bg_updates;
89+
90+
/** Update the inbuf from a background callback.
91+
*
92+
* This variant is introduced so that at the site of the
93+
* add_background_callback_core call, the prototype matches.
94+
*/
95+
STATIC void mp3file_update_inbuf_cb(void* self) {
96+
bg_updates ++;
97+
mp3file_update_inbuf_unconditional(self);
98+
}
99+
100+
/** Fill the input buffer if it is less than half full.
101+
*
102+
* Returns the same as mp3file_update_inbuf_unconditional.
103+
*/
104+
STATIC bool mp3file_update_inbuf_half(audiomp3_mp3file_obj_t* self) {
105+
// If buffer is over half full, do nothing
106+
if (self->inbuf_offset < self->inbuf_length/2) return true;
107+
108+
return mp3file_update_inbuf_unconditional(self);
109+
}
110+
90111
#define READ_PTR(self) (self->inbuf + self->inbuf_offset)
91112
#define BYTES_LEFT(self) (self->inbuf_length - self->inbuf_offset)
92113
#define CONSUME(self, n) (self->inbuf_offset += n)
93114

94115
// http://id3.org/d3v2.3.0
95116
// http://id3.org/id3v2.3.0
96117
STATIC void mp3file_skip_id3v2(audiomp3_mp3file_obj_t* self) {
97-
mp3file_update_inbuf(self);
118+
mp3file_update_inbuf_half(self);
98119
if (BYTES_LEFT(self) < 10) {
99120
return;
100121
}
@@ -129,11 +150,11 @@ STATIC void mp3file_skip_id3v2(audiomp3_mp3file_obj_t* self) {
129150
*/
130151
STATIC bool mp3file_find_sync_word(audiomp3_mp3file_obj_t* self) {
131152
do {
132-
mp3file_update_inbuf(self);
153+
mp3file_update_inbuf_half(self);
133154
int offset = MP3FindSyncWord(READ_PTR(self), BYTES_LEFT(self));
134155
if (offset >= 0) {
135156
CONSUME(self, offset);
136-
mp3file_update_inbuf(self);
157+
mp3file_update_inbuf_half(self);
137158
return true;
138159
}
139160
CONSUME(self, MAX(0, BYTES_LEFT(self) - 16));
@@ -214,7 +235,7 @@ void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t* self, pyb_file
214235
self->inbuf_offset = self->inbuf_length;
215236
self->eof = 0;
216237
self->other_channel = -1;
217-
mp3file_update_inbuf(self);
238+
mp3file_update_inbuf_half(self);
218239
mp3file_find_sync_word(self);
219240
// It **SHOULD** not be necessary to do this; the buffer should be filled
220241
// with fresh content before it is returned by get_buffer(). The fact that
@@ -281,7 +302,7 @@ void audiomp3_mp3file_reset_buffer(audiomp3_mp3file_obj_t* self,
281302
self->inbuf_offset = self->inbuf_length;
282303
self->eof = 0;
283304
self->other_channel = -1;
284-
mp3file_update_inbuf(self);
305+
mp3file_update_inbuf_half(self);
285306
mp3file_skip_id3v2(self);
286307
mp3file_find_sync_word(self);
287308
}
@@ -321,6 +342,14 @@ audioio_get_buffer_result_t audiomp3_mp3file_get_buffer(audiomp3_mp3file_obj_t*
321342
uint8_t *inbuf = READ_PTR(self);
322343
int err = MP3Decode(self->decoder, &inbuf, &bytes_left, buffer, 0);
323344
CONSUME(self, BYTES_LEFT(self) - bytes_left);
345+
346+
if (self->inbuf_offset >= 512) {
347+
background_callback_add(
348+
&self->inbuf_fill_cb,
349+
mp3file_update_inbuf_cb,
350+
self);
351+
}
352+
324353
if (err) {
325354
return GET_BUFFER_DONE;
326355
}

shared-module/audiomp3/MP3Decoder.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#ifndef MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_MP3FILE_H
2929
#define MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_MP3FILE_H
3030

31+
#include "supervisor/background_callback.h"
3132
#include "extmod/vfs_fat.h"
3233
#include "py/obj.h"
3334

@@ -36,6 +37,7 @@
3637
typedef struct {
3738
mp_obj_base_t base;
3839
struct _MP3DecInfo *decoder;
40+
background_callback_t inbuf_fill_cb;
3941
uint8_t* inbuf;
4042
uint32_t inbuf_length;
4143
uint32_t inbuf_offset;

0 commit comments

Comments
 (0)