Skip to content

Commit cbcdff6

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 b75db44 commit cbcdff6

File tree

2 files changed

+45
-11
lines changed

2 files changed

+45
-11
lines changed

shared-module/audiomp3/MP3Decoder.c

Lines changed: 43 additions & 11 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_always(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,34 @@ STATIC bool mp3file_update_inbuf(audiomp3_mp3file_obj_t* self) {
8785
return self->inbuf_offset < self->inbuf_length;
8886
}
8987

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

94112
// http://id3.org/d3v2.3.0
95113
// http://id3.org/id3v2.3.0
96114
STATIC void mp3file_skip_id3v2(audiomp3_mp3file_obj_t* self) {
97-
mp3file_update_inbuf(self);
115+
mp3file_update_inbuf_half(self);
98116
if (BYTES_LEFT(self) < 10) {
99117
return;
100118
}
@@ -129,11 +147,11 @@ STATIC void mp3file_skip_id3v2(audiomp3_mp3file_obj_t* self) {
129147
*/
130148
STATIC bool mp3file_find_sync_word(audiomp3_mp3file_obj_t* self) {
131149
do {
132-
mp3file_update_inbuf(self);
150+
mp3file_update_inbuf_half(self);
133151
int offset = MP3FindSyncWord(READ_PTR(self), BYTES_LEFT(self));
134152
if (offset >= 0) {
135153
CONSUME(self, offset);
136-
mp3file_update_inbuf(self);
154+
mp3file_update_inbuf_half(self);
137155
return true;
138156
}
139157
CONSUME(self, MAX(0, BYTES_LEFT(self) - 16));
@@ -209,12 +227,14 @@ void common_hal_audiomp3_mp3file_construct(audiomp3_mp3file_obj_t* self,
209227
}
210228

211229
void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t* self, pyb_file_obj_t* file) {
230+
background_callback_begin_critical_section();
231+
212232
self->file = file;
213233
f_lseek(&self->file->fp, 0);
214234
self->inbuf_offset = self->inbuf_length;
215235
self->eof = 0;
216236
self->other_channel = -1;
217-
mp3file_update_inbuf(self);
237+
mp3file_update_inbuf_half(self);
218238
mp3file_find_sync_word(self);
219239
// It **SHOULD** not be necessary to do this; the buffer should be filled
220240
// with fresh content before it is returned by get_buffer(). The fact that
@@ -224,7 +244,9 @@ void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t* self, pyb_file
224244
memset(self->buffers[0], 0, MAX_BUFFER_LEN);
225245
memset(self->buffers[1], 0, MAX_BUFFER_LEN);
226246
MP3FrameInfo fi;
227-
if(!mp3file_get_next_frame_info(self, &fi)) {
247+
bool result = mp3file_get_next_frame_info(self, &fi);
248+
background_callback_end_critical_section();
249+
if(!result) {
228250
mp_raise_msg(&mp_type_RuntimeError,
229251
translate("Failed to parse MP3 file"));
230252
}
@@ -277,13 +299,15 @@ void audiomp3_mp3file_reset_buffer(audiomp3_mp3file_obj_t* self,
277299
}
278300
// We don't reset the buffer index in case we're looping and we have an odd number of buffer
279301
// loads
302+
background_callback_begin_critical_section();
280303
f_lseek(&self->file->fp, 0);
281304
self->inbuf_offset = self->inbuf_length;
282305
self->eof = 0;
283306
self->other_channel = -1;
284-
mp3file_update_inbuf(self);
307+
mp3file_update_inbuf_half(self);
285308
mp3file_skip_id3v2(self);
286309
mp3file_find_sync_word(self);
310+
background_callback_end_critical_section();
287311
}
288312

289313
audioio_get_buffer_result_t audiomp3_mp3file_get_buffer(audiomp3_mp3file_obj_t* self,
@@ -321,6 +345,14 @@ audioio_get_buffer_result_t audiomp3_mp3file_get_buffer(audiomp3_mp3file_obj_t*
321345
uint8_t *inbuf = READ_PTR(self);
322346
int err = MP3Decode(self->decoder, &inbuf, &bytes_left, buffer, 0);
323347
CONSUME(self, BYTES_LEFT(self) - bytes_left);
348+
349+
if (self->inbuf_offset >= 512) {
350+
background_callback_add(
351+
&self->inbuf_fill_cb,
352+
mp3file_update_inbuf_cb,
353+
self);
354+
}
355+
324356
if (err) {
325357
return GET_BUFFER_DONE;
326358
}

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)