Skip to content

Commit f81a3ae

Browse files
committed
MP3Decoder: avoid all blocking reads in background
This gets MP3 playback of a soma.fm stream working for up to a minute at a time, though it's still vulnerable to network glitches. * the buffer can empty, in which case a single block of audio plays repeatedly in max headroom stutter fashion * the server eventually (after 1 to 5 5 minutes) stops getting packets at all. At this point stream playback stops, with the internal error indicating a problem MP3 decoding (which doesn't quite make sense): -9, ERR_MP3_INVALID_HUFFCODES. * other combinations of audiomixer buffer & mp3 buffer might give different results ```py import time import adafruit_connection_manager import adafruit_requests import audiobusio import audiomixer import audiomp3 import board import wifi pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) requests = adafruit_requests.Session(pool, ssl_context) # todo: parse PLS files like https://somafm.com/nossl/dronezone.pls # todo: figure out why https URLs don't work at all (missing select?) # STREAMING_URL = "http://ice2.somafm.com/dronezone-128-mp3" STREAMING_URL = "http://ice4.somafm.com/tikitime-128-mp3" def get_mp3_stream(): if STREAMING_URL.startswith("http:") or STREAMING_URL.startswith("https:"): return requests.get(STREAMING_URL, headers={"connection": "close"}).socket return open(STREAMING_URL, "rb") mixer_buffer_size = 1152 * 2 mp3_buffer = bytearray(32768) with audiobusio.I2SOut( bit_clock=board.D12, word_select=board.D13, data=board.D11 ) as i2s, get_mp3_stream() as stream, audiomp3.MP3Decoder( stream, mp3_buffer ) as sample, audiomixer.Mixer( channel_count=2, sample_rate=44100, buffer_size=mixer_buffer_size ) as m: v = m.voice[0] print(sample) i2s.play(m) v.play(sample, loop=False) while v.playing: time.sleep(0.1) ```
1 parent a5c4a86 commit f81a3ae

File tree

1 file changed

+34
-23
lines changed

1 file changed

+34
-23
lines changed

shared-module/audiomp3/MP3Decoder.c

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ static off_t stream_lseek(void *stream, off_t offset, int whence) {
104104
*
105105
* Sets self->eof if any read of the file returns 0 bytes
106106
*/
107-
static bool mp3file_update_inbuf_always(audiomp3_mp3file_obj_t *self) {
107+
static bool mp3file_update_inbuf_always(audiomp3_mp3file_obj_t *self, bool block_ok) {
108108
if (self->eof || INPUT_BUFFER_SPACE(self->inbuf) == 0) {
109109
return INPUT_BUFFER_AVAILABLE(self->inbuf) > 0;
110110
}
@@ -118,7 +118,7 @@ static bool mp3file_update_inbuf_always(audiomp3_mp3file_obj_t *self) {
118118
self->inbuf.read_off = 0;
119119
}
120120

121-
for (size_t to_read; !self->eof && (to_read = INPUT_BUFFER_SPACE(self->inbuf)) > 0;) {
121+
for (size_t to_read; !self->eof && (to_read = INPUT_BUFFER_SPACE(self->inbuf)) > 0 && (block_ok || stream_readable(self->stream));) {
122122
uint8_t *write_ptr = self->inbuf.buf + self->inbuf.write_off;
123123
ssize_t n_read = stream_read(self->stream, write_ptr, to_read);
124124

@@ -152,8 +152,11 @@ static bool mp3file_update_inbuf_always(audiomp3_mp3file_obj_t *self) {
152152
*/
153153
static void mp3file_update_inbuf_cb(void *self_in) {
154154
audiomp3_mp3file_obj_t *self = self_in;
155+
if (common_hal_audiomp3_mp3file_deinited(self_in)) {
156+
return;
157+
}
155158
if (!self->eof && stream_readable(self->stream)) {
156-
mp3file_update_inbuf_always(self);
159+
mp3file_update_inbuf_always(self, false);
157160
}
158161

159162
#if !defined(MICROPY_UNIX_COVERAGE)
@@ -170,22 +173,22 @@ static void mp3file_update_inbuf_cb(void *self_in) {
170173
*
171174
* Returns the same as mp3file_update_inbuf_always.
172175
*/
173-
static bool mp3file_update_inbuf_half(audiomp3_mp3file_obj_t *self) {
176+
static bool mp3file_update_inbuf_half(audiomp3_mp3file_obj_t *self, bool block_ok) {
174177
// If buffer is over half full, do nothing
175178
if (INPUT_BUFFER_SPACE(self->inbuf) < self->inbuf.size / 2) {
176179
return true;
177180
}
178181

179-
return mp3file_update_inbuf_always(self);
182+
return mp3file_update_inbuf_always(self, block_ok);
180183
}
181184

182185
#define READ_PTR(self) (INPUT_BUFFER_READ_PTR(self->inbuf))
183186
#define BYTES_LEFT(self) (INPUT_BUFFER_AVAILABLE(self->inbuf))
184187
#define CONSUME(self, n) (INPUT_BUFFER_CONSUME(self->inbuf, n))
185188

186189
// http://id3.org/id3v2.3.0
187-
static void mp3file_skip_id3v2(audiomp3_mp3file_obj_t *self) {
188-
mp3file_update_inbuf_half(self);
190+
static void mp3file_skip_id3v2(audiomp3_mp3file_obj_t *self, bool block_ok) {
191+
mp3file_update_inbuf_half(self, block_ok);
189192
if (BYTES_LEFT(self) < 10) {
190193
return;
191194
}
@@ -206,6 +209,9 @@ static void mp3file_skip_id3v2(audiomp3_mp3file_obj_t *self) {
206209
int32_t size = (data[6] << 21) | (data[7] << 14) | (data[8] << 7) | (data[9]);
207210
size += 10; // size excludes the "header" (but not the "extended header")
208211
// First, deduct from size whatever is left in buffer
212+
if (DO_DEBUG) {
213+
mp_printf(&mp_plat_print, "%s:%d id3 size %d\n", __FILE__, __LINE__, size);
214+
}
209215
uint32_t to_consume = MIN(size, BYTES_LEFT(self));
210216
CONSUME(self, to_consume);
211217
size -= to_consume;
@@ -217,7 +223,7 @@ static void mp3file_skip_id3v2(audiomp3_mp3file_obj_t *self) {
217223

218224
// Couldn't seek (might be a socket), so need to actually read and discard all that data
219225
while (size > 0 && !self->eof) {
220-
mp3file_update_inbuf_always(self);
226+
mp3file_update_inbuf_always(self, true);
221227
to_consume = MIN(size, BYTES_LEFT(self));
222228
CONSUME(self, to_consume);
223229
size -= to_consume;
@@ -227,29 +233,29 @@ static void mp3file_skip_id3v2(audiomp3_mp3file_obj_t *self) {
227233
/* If a sync word can be found, advance to it and return true. Otherwise,
228234
* return false.
229235
*/
230-
static bool mp3file_find_sync_word(audiomp3_mp3file_obj_t *self) {
236+
static bool mp3file_find_sync_word(audiomp3_mp3file_obj_t *self, bool block_ok) {
231237
do {
232-
mp3file_update_inbuf_half(self);
238+
mp3file_update_inbuf_half(self, block_ok);
233239
int offset = MP3FindSyncWord(READ_PTR(self), BYTES_LEFT(self));
234240
if (offset >= 0) {
235241
CONSUME(self, offset);
236-
mp3file_update_inbuf_half(self);
242+
mp3file_update_inbuf_half(self, block_ok);
237243
return true;
238244
}
239245
CONSUME(self, MAX(0, BYTES_LEFT(self) - 16));
240246
} while (!self->eof);
241247
return false;
242248
}
243249

244-
static bool mp3file_get_next_frame_info(audiomp3_mp3file_obj_t *self, MP3FrameInfo *fi) {
250+
static bool mp3file_get_next_frame_info(audiomp3_mp3file_obj_t *self, MP3FrameInfo *fi, bool block_ok) {
245251
int err;
246252
do {
247253
err = MP3GetNextFrameInfo(self->decoder, fi, READ_PTR(self));
248254
if (err == ERR_MP3_NONE) {
249255
break;
250256
}
251257
CONSUME(self, 1);
252-
mp3file_find_sync_word(self);
258+
mp3file_find_sync_word(self, block_ok);
253259
} while (!self->eof);
254260
return err == ERR_MP3_NONE;
255261
}
@@ -328,8 +334,8 @@ void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t *self, mp_obj_t
328334
INPUT_BUFFER_CLEAR(self->inbuf);
329335
self->eof = 0;
330336
self->other_channel = -1;
331-
mp3file_update_inbuf_half(self);
332-
mp3file_find_sync_word(self);
337+
mp3file_update_inbuf_half(self, true);
338+
mp3file_find_sync_word(self, true);
333339
// It **SHOULD** not be necessary to do this; the buffer should be filled
334340
// with fresh content before it is returned by get_buffer(). The fact that
335341
// this is necessary to avoid a glitch at the start of playback of a second
@@ -338,7 +344,7 @@ void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t *self, mp_obj_t
338344
memset(self->pcm_buffer[0], 0, MAX_BUFFER_LEN);
339345
memset(self->pcm_buffer[1], 0, MAX_BUFFER_LEN);
340346
MP3FrameInfo fi;
341-
bool result = mp3file_get_next_frame_info(self, &fi);
347+
bool result = mp3file_get_next_frame_info(self, &fi, true);
342348
background_callback_allow();
343349
if (!result) {
344350
mp_raise_msg(&mp_type_RuntimeError,
@@ -353,7 +359,9 @@ void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t *self, mp_obj_t
353359
}
354360

355361
void common_hal_audiomp3_mp3file_deinit(audiomp3_mp3file_obj_t *self) {
356-
MP3FreeDecoder(self->decoder);
362+
if (self->decoder) {
363+
MP3FreeDecoder(self->decoder);
364+
}
357365
self->decoder = NULL;
358366
self->inbuf.buf = NULL;
359367
self->pcm_buffer[0] = NULL;
@@ -397,8 +405,8 @@ void audiomp3_mp3file_reset_buffer(audiomp3_mp3file_obj_t *self,
397405
self->eof = 0;
398406
self->samples_decoded = 0;
399407
self->other_channel = -1;
400-
mp3file_skip_id3v2(self);
401-
mp3file_find_sync_word(self);
408+
mp3file_skip_id3v2(self, false);
409+
mp3file_find_sync_word(self, false);
402410
}
403411
background_callback_allow();
404412
}
@@ -439,8 +447,8 @@ audioio_get_buffer_result_t audiomp3_mp3file_get_buffer(audiomp3_mp3file_obj_t *
439447
int16_t *buffer = (int16_t *)(void *)self->pcm_buffer[self->buffer_index];
440448
*bufptr = (uint8_t *)buffer;
441449

442-
mp3file_skip_id3v2(self);
443-
if (!mp3file_find_sync_word(self)) {
450+
mp3file_skip_id3v2(self, false);
451+
if (!mp3file_find_sync_word(self, false)) {
444452
*buffer_length = 0;
445453
return self->eof ? GET_BUFFER_DONE : GET_BUFFER_ERROR;
446454
}
@@ -464,8 +472,8 @@ audioio_get_buffer_result_t audiomp3_mp3file_get_buffer(audiomp3_mp3file_obj_t *
464472

465473
self->samples_decoded += frame_buffer_size_bytes / sizeof(int16_t);
466474

467-
mp3file_skip_id3v2(self);
468-
int result = mp3file_find_sync_word(self) ? GET_BUFFER_MORE_DATA : GET_BUFFER_DONE;
475+
mp3file_skip_id3v2(self, false);
476+
int result = mp3file_find_sync_word(self, false) ? GET_BUFFER_MORE_DATA : GET_BUFFER_DONE;
469477

470478
if (DO_DEBUG) {
471479
mp_printf(&mp_plat_print, "%s:%d result=%d\n", __FILE__, __LINE__, result);
@@ -477,6 +485,9 @@ audioio_get_buffer_result_t audiomp3_mp3file_get_buffer(audiomp3_mp3file_obj_t *
477485
self);
478486
}
479487

488+
if (DO_DEBUG) {
489+
mp_printf(&mp_plat_print, "post-decode avail=%d eof=%d\n", (int)INPUT_BUFFER_AVAILABLE(self->inbuf), self->eof);
490+
}
480491
return result;
481492
}
482493

0 commit comments

Comments
 (0)