Skip to content

Commit d6ddd55

Browse files
committed
MP3Decoder: Allow passing any stream-like object
This can sort-of play MP3s from a http request, but the buffering is not good enough to play glitch-free. A new kind of buffer that can read ahead further without blocking is needed.
1 parent c2443f9 commit d6ddd55

File tree

4 files changed

+109
-35
lines changed

4 files changed

+109
-35
lines changed

shared-bindings/audiomp3/MP3Decoder.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "shared/runtime/context_manager_helpers.h"
1111
#include "py/objproperty.h"
1212
#include "py/runtime.h"
13+
#include "py/stream.h"
1314
#include "shared-bindings/audiomp3/MP3Decoder.h"
1415
#include "shared-bindings/util.h"
1516

@@ -68,16 +69,18 @@
6869

6970
static mp_obj_t audiomp3_mp3file_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
7071
mp_arg_check_num(n_args, n_kw, 1, 2, false);
71-
mp_obj_t arg = args[0];
72+
mp_obj_t stream = args[0];
7273

73-
if (mp_obj_is_str(arg)) {
74-
arg = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), arg, MP_ROM_QSTR(MP_QSTR_rb));
74+
if (mp_obj_is_str(stream)) {
75+
stream = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), stream, MP_ROM_QSTR(MP_QSTR_rb));
7576
}
7677

7778
audiomp3_mp3file_obj_t *self = m_new_obj_with_finaliser(audiomp3_mp3file_obj_t);
7879
self->base.type = &audiomp3_mp3file_type;
7980

80-
if (!mp_obj_is_type(arg, &mp_type_fileio)) {
81+
const mp_stream_p_t *stream_p = mp_get_stream_raise(stream, MP_STREAM_OP_READ);
82+
83+
if (stream_p->is_text) {
8184
mp_raise_TypeError(MP_ERROR_TEXT("file must be a file opened in byte mode"));
8285
}
8386
uint8_t *buffer = NULL;
@@ -88,8 +91,7 @@ static mp_obj_t audiomp3_mp3file_make_new(const mp_obj_type_t *type, size_t n_ar
8891
buffer = bufinfo.buf;
8992
buffer_size = bufinfo.len;
9093
}
91-
common_hal_audiomp3_mp3file_construct(self, MP_OBJ_TO_PTR(arg),
92-
buffer, buffer_size);
94+
common_hal_audiomp3_mp3file_construct(self, stream, buffer, buffer_size);
9395

9496
return MP_OBJ_FROM_PTR(self);
9597
}
@@ -131,7 +133,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiomp3_mp3file___exit___obj, 4, 4,
131133
static mp_obj_t audiomp3_mp3file_obj_get_file(mp_obj_t self_in) {
132134
audiomp3_mp3file_obj_t *self = MP_OBJ_TO_PTR(self_in);
133135
check_for_deinit(self);
134-
return self->file;
136+
return self->stream;
135137
}
136138
MP_DEFINE_CONST_FUN_OBJ_1(audiomp3_mp3file_get_file_obj, audiomp3_mp3file_obj_get_file);
137139

shared-bindings/audiomp3/MP3Decoder.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
extern const mp_obj_type_t audiomp3_mp3file_type;
1616

1717
void common_hal_audiomp3_mp3file_construct(audiomp3_mp3file_obj_t *self,
18-
pyb_file_obj_t *file, uint8_t *buffer, size_t buffer_size);
18+
mp_obj_t stream, uint8_t *buffer, size_t buffer_size);
1919

20-
void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t *self, pyb_file_obj_t *file);
20+
void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t *self, mp_obj_t stream);
2121
void common_hal_audiomp3_mp3file_deinit(audiomp3_mp3file_obj_t *self);
2222
bool common_hal_audiomp3_mp3file_deinited(audiomp3_mp3file_obj_t *self);
2323
uint32_t common_hal_audiomp3_mp3file_get_sample_rate(audiomp3_mp3file_obj_t *self);

shared-module/audiomp3/MP3Decoder.c

Lines changed: 97 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,85 @@
77

88
#include "shared-bindings/audiomp3/MP3Decoder.h"
99

10+
#include <math.h>
1011
#include <stdint.h>
1112
#include <string.h>
12-
#include <math.h>
13+
#include <sys/types.h>
14+
#include <unistd.h>
1315

1416
#include "py/mperrno.h"
1517
#include "py/runtime.h"
18+
#include "py/stream.h"
1619

1720
#include "shared-module/audiomp3/MP3Decoder.h"
1821
#include "supervisor/background_callback.h"
1922
#include "lib/mp3/src/mp3common.h"
2023

2124
#define MAX_BUFFER_LEN (MAX_NSAMP * MAX_NGRAN * MAX_NCHAN * sizeof(int16_t))
2225

26+
// (near copy of mp_stream_posix_read, but with changes)
27+
// (circuitpy doesn't enable posix stream routines anyway)
28+
STATIC ssize_t stream_read(void *stream, void *buf, size_t len) {
29+
int errcode;
30+
mp_obj_base_t *o = MP_OBJ_TO_PTR(stream);
31+
const mp_stream_p_t *stream_p = MP_OBJ_TYPE_GET_SLOT(o->type, protocol);
32+
if (!stream_p->read) {
33+
return -EINVAL;
34+
}
35+
mp_uint_t out_sz = stream_p->read(MP_OBJ_FROM_PTR(stream), buf, len, &errcode);
36+
if (out_sz == MP_STREAM_ERROR) {
37+
return -errcode; // CIRCUITPY-CHANGE: returns negative errcode value
38+
} else {
39+
return out_sz;
40+
}
41+
}
42+
43+
// (near copy of mp_stream_posix_read, but with changes)
44+
// (circuitpy doesn't enable posix stream routines anyway)
45+
STATIC ssize_t stream_read_all(void *stream, void *buf, size_t len) {
46+
ssize_t total_read = 0;
47+
while (len) {
48+
ssize_t r = stream_read(stream, buf, len);
49+
if (r <= 0) {
50+
if (total_read) {
51+
break;
52+
}
53+
return r;
54+
}
55+
total_read += r;
56+
buf += r;
57+
len -= r;
58+
}
59+
return total_read;
60+
}
61+
62+
63+
// (near copy of mp_stream_posix_lseek, but with changes)
64+
// (circuitpy doesn't enable posix stream routines anyway)
65+
STATIC off_t stream_lseek(void *stream, off_t offset, int whence) {
66+
int errcode;
67+
const mp_obj_base_t *o = stream;
68+
const mp_stream_p_t *stream_p = MP_OBJ_TYPE_GET_SLOT(o->type, protocol);
69+
if (!stream_p->ioctl) {
70+
return -EINVAL;
71+
}
72+
struct mp_stream_seek_t seek_s;
73+
seek_s.offset = offset;
74+
seek_s.whence = whence;
75+
mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &errcode);
76+
if (res == MP_STREAM_ERROR) {
77+
return -errcode;
78+
}
79+
return seek_s.offset;
80+
}
81+
2382
/** Fill the input buffer unconditionally.
2483
*
2584
* Returns true if the input buffer contains any useful data,
2685
* false otherwise. (The input buffer will be padded to the end with
2786
* 0 bytes, which do not interfere with MP3 decoding)
2887
*
29-
* Raises OSError if f_read fails.
88+
* Raises OSError if stream_read fails.
3089
*
3190
* Sets self->eof if any read of the file returns 0 bytes
3291
*/
@@ -41,20 +100,20 @@ static bool mp3file_update_inbuf_always(audiomp3_mp3file_obj_t *self) {
41100
self->inbuf_length - self->inbuf_offset);
42101
self->inbuf_offset = 0;
43102

44-
UINT to_read = end_of_buffer - new_end_of_data;
45-
UINT bytes_read = 0;
103+
ssize_t to_read = end_of_buffer - new_end_of_data;
46104
memset(new_end_of_data, 0, to_read);
47-
if (f_read(&self->file->fp, new_end_of_data, to_read, &bytes_read) != FR_OK) {
105+
ssize_t r = stream_read_all(self->stream, new_end_of_data, to_read);
106+
if (r < 0) {
48107
self->eof = true;
49-
mp_raise_OSError(MP_EIO);
108+
mp_raise_OSError(-r);
50109
}
51110

52-
if (bytes_read == 0) {
111+
if (r == 0) {
53112
self->eof = true;
54113
}
55114

56-
if (to_read != bytes_read) {
57-
new_end_of_data += bytes_read;
115+
if (to_read != r) {
116+
new_end_of_data += r;
58117
memset(new_end_of_data, 0, end_of_buffer - new_end_of_data);
59118
}
60119

@@ -119,8 +178,17 @@ static void mp3file_skip_id3v2(audiomp3_mp3file_obj_t *self) {
119178
size -= to_consume;
120179

121180
// Next, seek in the file after the header
122-
f_lseek(&self->file->fp, f_tell(&self->file->fp) + size);
123-
return;
181+
if (stream_lseek(self->stream, SEEK_CUR, size) == 0) {
182+
return;
183+
}
184+
185+
// Couldn't seek (might be a socket), so need to actually read and discard all that data
186+
while (size > 0 && !self->eof) {
187+
mp3file_update_inbuf_always(self);
188+
to_consume = MIN(size, BYTES_LEFT(self));
189+
CONSUME(self, to_consume);
190+
size -= to_consume;
191+
}
124192
}
125193

126194
/* If a sync word can be found, advance to it and return true. Otherwise,
@@ -154,7 +222,7 @@ static bool mp3file_get_next_frame_info(audiomp3_mp3file_obj_t *self, MP3FrameIn
154222
}
155223

156224
void common_hal_audiomp3_mp3file_construct(audiomp3_mp3file_obj_t *self,
157-
pyb_file_obj_t *file,
225+
mp_obj_t stream,
158226
uint8_t *buffer,
159227
size_t buffer_size) {
160228
// XXX Adafruit_MP3 uses a 2kB input buffer and two 4kB output buffers.
@@ -202,14 +270,17 @@ void common_hal_audiomp3_mp3file_construct(audiomp3_mp3file_obj_t *self,
202270
}
203271
}
204272

205-
common_hal_audiomp3_mp3file_set_file(self, file);
273+
common_hal_audiomp3_mp3file_set_file(self, stream);
206274
}
207275

208-
void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t *self, pyb_file_obj_t *file) {
276+
void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t *self, mp_obj_t stream) {
209277
background_callback_prevent();
210278

211-
self->file = file;
212-
f_lseek(&self->file->fp, 0);
279+
self->stream = stream;
280+
281+
// Seek the beginning of the stream if possible, but ignore any errors
282+
(void)stream_lseek(self->stream, SEEK_SET, 0);
283+
213284
self->inbuf_offset = self->inbuf_length;
214285
self->eof = 0;
215286
self->other_channel = -1;
@@ -243,7 +314,7 @@ void common_hal_audiomp3_mp3file_deinit(audiomp3_mp3file_obj_t *self) {
243314
self->inbuf = NULL;
244315
self->buffers[0] = NULL;
245316
self->buffers[1] = NULL;
246-
self->file = NULL;
317+
self->stream = mp_const_none;
247318
self->samples_decoded = 0;
248319
}
249320

@@ -277,14 +348,15 @@ void audiomp3_mp3file_reset_buffer(audiomp3_mp3file_obj_t *self,
277348
// We don't reset the buffer index in case we're looping and we have an odd number of buffer
278349
// loads
279350
background_callback_prevent();
280-
f_lseek(&self->file->fp, 0);
281-
self->inbuf_offset = self->inbuf_length;
282-
self->eof = 0;
283-
self->samples_decoded = 0;
284-
self->other_channel = -1;
285-
mp3file_update_inbuf_half(self);
286-
mp3file_skip_id3v2(self);
287-
mp3file_find_sync_word(self);
351+
if (stream_lseek(self->stream, SEEK_SET, 0) == 0) {
352+
self->inbuf_offset = self->inbuf_length;
353+
self->eof = 0;
354+
self->samples_decoded = 0;
355+
self->other_channel = -1;
356+
mp3file_update_inbuf_half(self);
357+
mp3file_skip_id3v2(self);
358+
mp3file_find_sync_word(self);
359+
}
288360
background_callback_allow();
289361
}
290362

shared-module/audiomp3/MP3Decoder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ typedef struct {
2525
uint32_t frame_buffer_size;
2626

2727
uint32_t sample_rate;
28-
pyb_file_obj_t *file;
28+
mp_obj_t stream;
2929

3030
uint8_t buffer_index;
3131
uint8_t channel_count;

0 commit comments

Comments
 (0)