audiocore: Add async support
Rename the exising audiocore C module to _audiocore and create a new Micropython wrapper module audiocore. This makes it easier to implement async methods. Add interrupt support to _audiocore that notifies core0 whenever data has been consumed from the MP3 bitstream buffer. Use this interrupt and an asyncio.ThreadSafeFlag to implement audiocore.async_put which will play back the provided buffer, allowing other async tasks to run while waiting for space in the bitstream buffer.
This commit is contained in:
@@ -33,6 +33,7 @@ function(make_unity_test )
|
||||
target_link_libraries(${MAKE_UNITY_TEST_NAME} PUBLIC unity::framework)
|
||||
target_include_directories(${MAKE_UNITY_TEST_NAME}
|
||||
PRIVATE ${MAKE_UNITY_TEST_INCLUDES})
|
||||
target_compile_definitions(${MAKE_UNITY_TEST_NAME} PRIVATE UNIT_TESTING)
|
||||
|
||||
add_test(NAME ${MAKE_UNITY_TEST_NAME}
|
||||
COMMAND sh -c "$<TARGET_FILE:${MAKE_UNITY_TEST_NAME}> > ${PROJECT_BINARY_DIR}/reports/${MAKE_UNITY_TEST_NAME}.testresults"
|
||||
|
||||
@@ -11,3 +11,5 @@ require("aiorepl")
|
||||
|
||||
module("mfrc522.py", "../../lib/micropython-mfrc522/")
|
||||
module("microdot.py", "../../lib/microdot/src/microdot/")
|
||||
|
||||
module("audiocore.py", "../../src/audiocore")
|
||||
|
||||
@@ -14,6 +14,26 @@ void __time_critical_func(volume_adjust)(int16_t *buf, size_t samples, uint16_t
|
||||
}
|
||||
}
|
||||
|
||||
static void __time_critical_func(send_fifo_response)(uint32_t data)
|
||||
{
|
||||
multicore_fifo_push_blocking(AUDIOCORE_FIFO_DATA_FLAG | data);
|
||||
}
|
||||
|
||||
#ifndef UNIT_TESTING
|
||||
static void __time_critical_func(send_consume_notify)(void)
|
||||
{
|
||||
if (multicore_fifo_wready())
|
||||
sio_hw->fifo_wr = 0;
|
||||
}
|
||||
#else
|
||||
// Don't do optimization with raw HW access in unit test environment
|
||||
static void send_consume_notify(void)
|
||||
{
|
||||
if (multicore_fifo_wready())
|
||||
multicore_fifo_push_blocking(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
void __time_critical_func(core1_main)(void)
|
||||
{
|
||||
uint32_t ret = 0;
|
||||
@@ -27,7 +47,7 @@ void __time_critical_func(core1_main)(void)
|
||||
goto out_i2s;
|
||||
}
|
||||
|
||||
multicore_fifo_push_blocking(0);
|
||||
send_fifo_response(0);
|
||||
uint32_t current_volume = AUDIOCORE_MAX_VOLUME >> 4;
|
||||
bool flushing = false;
|
||||
while (running) {
|
||||
@@ -42,14 +62,18 @@ void __time_critical_func(core1_main)(void)
|
||||
case AUDIOCORE_CMD_SET_VOLUME: {
|
||||
const uint32_t new_volume = multicore_fifo_pop_blocking();
|
||||
if (new_volume > AUDIOCORE_MAX_VOLUME) {
|
||||
multicore_fifo_push_blocking(1);
|
||||
send_fifo_response(1);
|
||||
} else {
|
||||
current_volume = new_volume;
|
||||
multicore_fifo_push_blocking(0);
|
||||
send_fifo_response(0);
|
||||
}
|
||||
} break;
|
||||
case AUDIOCORE_CMD_FLUSH:
|
||||
flushing = true;
|
||||
if (playing) {
|
||||
flushing = true;
|
||||
} else {
|
||||
send_fifo_response(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -65,6 +89,7 @@ void __time_critical_func(core1_main)(void)
|
||||
}
|
||||
volume_adjust((int16_t *)buf, 2304, current_volume);
|
||||
i2s_commit_buf(buf);
|
||||
send_consume_notify();
|
||||
continue;
|
||||
}
|
||||
/* mp3_decode returned false: not enough data in buffer */
|
||||
@@ -73,7 +98,9 @@ void __time_critical_func(core1_main)(void)
|
||||
i2s_stop();
|
||||
playing = false;
|
||||
flushing = false;
|
||||
multicore_fifo_push_blocking(0);
|
||||
send_fifo_response(0);
|
||||
} else {
|
||||
send_consume_notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,5 +111,5 @@ void __time_critical_func(core1_main)(void)
|
||||
out_i2s:
|
||||
i2s_deinit();
|
||||
out:
|
||||
multicore_fifo_push_blocking(ret);
|
||||
send_fifo_response(ret);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@ void __time_critical_func(volume_adjust)(int16_t *buf, size_t samples, uint16_t
|
||||
|
||||
void core1_main(void);
|
||||
|
||||
// For data sent from core1 to core0, signals that this is a return value from some function call
|
||||
// Otherwise it it just a trigger to wake core0 and the data can be discarded.
|
||||
#define AUDIOCORE_FIFO_DATA_FLAG 0x80000000
|
||||
|
||||
// SHUTDOWN - no arguments - return 0
|
||||
#define AUDIOCORE_CMD_SHUTDOWN 0xdeadc0de
|
||||
|
||||
|
||||
37
software/src/audiocore/audiocore.py
Normal file
37
software/src/audiocore/audiocore.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import _audiocore
|
||||
from asyncio import ThreadSafeFlag
|
||||
|
||||
|
||||
class Audiocore:
|
||||
def __init__(self, pin, sideset):
|
||||
self.notify = ThreadSafeFlag()
|
||||
self._audiocore = _audiocore.Audiocore(pin, sideset, self._interrupt)
|
||||
|
||||
def __del__(self):
|
||||
self._audiocore.deinit()
|
||||
|
||||
def _interrupt(self, _):
|
||||
self.notify.set()
|
||||
|
||||
def flush(self):
|
||||
self._audiocore.flush()
|
||||
|
||||
def set_volume(self, volume):
|
||||
self._audiocore.set_volume(volume)
|
||||
|
||||
def put(self, buffer, blocking=False):
|
||||
pos = 0
|
||||
while True:
|
||||
(copied, buf_space, underruns) = self._audiocore.put(buffer[pos:])
|
||||
pos += copied
|
||||
if pos >= len(buffer) or not blocking:
|
||||
return (pos, buf_space, underruns)
|
||||
|
||||
async def async_put(self, buffer):
|
||||
pos = 0
|
||||
while True:
|
||||
(copied, buf_space, underruns) = self._audiocore.put(buffer[pos:])
|
||||
pos += copied
|
||||
if pos >= len(buffer):
|
||||
return (pos, buf_space, underruns)
|
||||
await self.notify.wait()
|
||||
@@ -102,6 +102,7 @@ void i2s_stop(void)
|
||||
} while (have_data);
|
||||
const long flags = save_and_disable_interrupts();
|
||||
i2s_context.playback_active = false;
|
||||
shared_context.underruns = 0;
|
||||
restore_interrupts(flags);
|
||||
// Workaround rp2040 E13
|
||||
dma_channel_set_irq1_enabled(i2s_context.dma_ch, false);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// Include MicroPython API.
|
||||
#include "py/mperrno.h"
|
||||
#include "py/runtime.h"
|
||||
#include "shared/runtime/mpirq.h"
|
||||
|
||||
// This module is RP2 specific
|
||||
#include "mphalport.h"
|
||||
@@ -18,9 +19,41 @@ static bool initialized = false;
|
||||
|
||||
struct audiocore_obj {
|
||||
mp_obj_base_t base;
|
||||
mp_irq_obj_t *irq_obj;
|
||||
uint32_t fifo_read_value;
|
||||
};
|
||||
const mp_obj_type_t audiocore_type;
|
||||
|
||||
static struct audiocore_obj *the_audiocore_obj = NULL;
|
||||
|
||||
static void __time_critical_func(fifo_isr)(void)
|
||||
{
|
||||
if (!multicore_fifo_rvalid())
|
||||
return;
|
||||
const uint32_t val = sio_hw->fifo_rd;
|
||||
if (!the_audiocore_obj)
|
||||
return;
|
||||
if (val & AUDIOCORE_FIFO_DATA_FLAG)
|
||||
the_audiocore_obj->fifo_read_value = val;
|
||||
if (the_audiocore_obj->irq_obj)
|
||||
mp_irq_handler(the_audiocore_obj->irq_obj);
|
||||
}
|
||||
|
||||
static const mp_irq_methods_t audiocore_irq_methods = {};
|
||||
|
||||
static uint32_t get_fifo_read_value_blocking(struct audiocore_obj *obj)
|
||||
{
|
||||
while (true) {
|
||||
const long flags = save_and_disable_interrupts();
|
||||
const uint32_t value = obj->fifo_read_value;
|
||||
obj->fifo_read_value = 0;
|
||||
restore_interrupts(flags);
|
||||
if (value & AUDIOCORE_FIFO_DATA_FLAG)
|
||||
return value & ~AUDIOCORE_FIFO_DATA_FLAG;
|
||||
__wfi();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* audiocore.Context.deinit(self)
|
||||
*
|
||||
@@ -30,8 +63,10 @@ static mp_obj_t audiocore_deinit(mp_obj_t self_in)
|
||||
{
|
||||
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
|
||||
multicore_fifo_push_blocking(AUDIOCORE_CMD_SHUTDOWN);
|
||||
multicore_fifo_pop_blocking();
|
||||
(void)self;
|
||||
get_fifo_read_value_blocking(self);
|
||||
irq_set_enabled(SIO_IRQ_PROC0, false);
|
||||
irq_remove_handler(SIO_IRQ_PROC0, &fifo_isr);
|
||||
the_audiocore_obj = NULL;
|
||||
initialized = false;
|
||||
return mp_const_none;
|
||||
}
|
||||
@@ -86,10 +121,9 @@ static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_put_obj, audiocore_put);
|
||||
static mp_obj_t audiocore_flush(mp_obj_t self_in)
|
||||
{
|
||||
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
|
||||
(void)self;
|
||||
multicore_fifo_push_blocking(AUDIOCORE_CMD_FLUSH);
|
||||
__sev();
|
||||
multicore_fifo_pop_blocking();
|
||||
get_fifo_read_value_blocking(self);
|
||||
return mp_const_none;
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_flush_obj, audiocore_flush);
|
||||
@@ -105,14 +139,13 @@ static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_flush_obj, audiocore_flush);
|
||||
static mp_obj_t audiocore_set_volume(mp_obj_t self_in, mp_obj_t volume_obj)
|
||||
{
|
||||
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
|
||||
(void)self;
|
||||
const int volume = mp_obj_get_int(volume_obj);
|
||||
if (volume < 0 || volume > 255)
|
||||
mp_raise_ValueError("volume out of range");
|
||||
multicore_fifo_push_blocking(AUDIOCORE_CMD_SET_VOLUME);
|
||||
multicore_fifo_push_blocking(AUDIOCORE_MAX_VOLUME * volume / 255);
|
||||
__sev();
|
||||
const uint32_t ret = multicore_fifo_pop_blocking();
|
||||
const uint32_t ret = get_fifo_read_value_blocking(self);
|
||||
if (ret != 0)
|
||||
mp_raise_OSError(MP_EINVAL);
|
||||
return mp_const_none;
|
||||
@@ -132,10 +165,11 @@ static uint32_t __scratch_y("core1_stack") core1_stack[1024];
|
||||
*/
|
||||
static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
|
||||
{
|
||||
enum { ARG_pin, ARG_sideset };
|
||||
enum { ARG_pin, ARG_sideset, ARG_handler };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
|
||||
{MP_QSTR_sideset, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
|
||||
{MP_QSTR_handler, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
|
||||
};
|
||||
if (initialized)
|
||||
mp_raise_OSError(MP_EBUSY);
|
||||
@@ -150,6 +184,16 @@ static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_ob
|
||||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
const mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(args[ARG_pin].u_obj);
|
||||
const mp_hal_pin_obj_t sideset_pin = mp_hal_get_pin_obj(args[ARG_sideset].u_obj);
|
||||
if (args[ARG_handler].u_obj != MP_OBJ_NULL) {
|
||||
obj->irq_obj = mp_irq_new(&audiocore_irq_methods, MP_OBJ_FROM_PTR(obj));
|
||||
obj->irq_obj->handler = args[ARG_handler].u_obj;
|
||||
obj->irq_obj->ishard = false;
|
||||
} else {
|
||||
obj->irq_obj = NULL;
|
||||
}
|
||||
the_audiocore_obj = obj;
|
||||
irq_set_exclusive_handler(SIO_IRQ_PROC0, &fifo_isr);
|
||||
irq_set_enabled(SIO_IRQ_PROC0, true);
|
||||
shared_context.mp3_buffer_write = shared_context.mp3_buffer_read = shared_context.underruns = 0;
|
||||
memset(shared_context.mp3_buffer, 0, MP3_BUFFER_PREAREA + MP3_BUFFER_SIZE);
|
||||
multicore_reset_core1();
|
||||
@@ -157,9 +201,12 @@ static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_ob
|
||||
shared_context.sideset_base = sideset_pin;
|
||||
initialized = true;
|
||||
multicore_launch_core1_with_stack(&core1_main, core1_stack, sizeof(core1_stack));
|
||||
uint32_t result = multicore_fifo_pop_blocking();
|
||||
uint32_t result = get_fifo_read_value_blocking(obj);
|
||||
if (result != 0) {
|
||||
multicore_reset_core1();
|
||||
irq_set_enabled(SIO_IRQ_PROC0, false);
|
||||
irq_remove_handler(SIO_IRQ_PROC0, &fifo_isr);
|
||||
the_audiocore_obj = NULL;
|
||||
initialized = false;
|
||||
mp_raise_OSError(result);
|
||||
}
|
||||
@@ -187,7 +234,7 @@ MP_DEFINE_CONST_OBJ_TYPE(audiocore_type, MP_QSTR_Audiocore, MP_TYPE_FLAG_NONE, l
|
||||
make_new, &audiocore_make_new);
|
||||
|
||||
static const mp_rom_map_elem_t audiocore_module_globals_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiocore)},
|
||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__audiocore)},
|
||||
{MP_ROM_QSTR(MP_QSTR_Audiocore), MP_ROM_PTR(&audiocore_type)},
|
||||
};
|
||||
static MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table);
|
||||
@@ -196,4 +243,4 @@ const mp_obj_module_t audiocore_cmodule = {
|
||||
.base = {&mp_type_module},
|
||||
.globals = (mp_obj_dict_t *)&audiocore_module_globals,
|
||||
};
|
||||
MP_REGISTER_MODULE(MP_QSTR_audiocore, audiocore_cmodule);
|
||||
MP_REGISTER_MODULE(MP_QSTR__audiocore, audiocore_cmodule);
|
||||
|
||||
@@ -55,17 +55,18 @@ unsigned multicore_fifo_pop_blocking(void)
|
||||
}
|
||||
|
||||
bool multicore_fifo_rvalid(void) { return multicore_fifo_pop_blocking_cb; }
|
||||
bool multicore_fifo_wready(void) { return true; }
|
||||
|
||||
void test_audiocore_handles_i2sinit_failure(void)
|
||||
{
|
||||
i2s_init_return = false;
|
||||
core1_main();
|
||||
TEST_ASSERT_EQUAL(multicore_fifo_push_last, MP_EIO);
|
||||
TEST_ASSERT_EQUAL(AUDIOCORE_FIFO_DATA_FLAG | MP_EIO, multicore_fifo_push_last);
|
||||
}
|
||||
|
||||
unsigned audiocore_init_deinit_pop_cb(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL(0, multicore_fifo_push_last);
|
||||
TEST_ASSERT_EQUAL(AUDIOCORE_FIFO_DATA_FLAG | 0, multicore_fifo_push_last);
|
||||
TEST_ASSERT_TRUE(i2s_initialized);
|
||||
return AUDIOCORE_CMD_SHUTDOWN;
|
||||
}
|
||||
@@ -76,7 +77,7 @@ void test_audiocore_init_deinit(void)
|
||||
i2s_init_return = true;
|
||||
|
||||
core1_main();
|
||||
TEST_ASSERT_EQUAL(0, multicore_fifo_push_last);
|
||||
TEST_ASSERT_EQUAL(AUDIOCORE_FIFO_DATA_FLAG | 0, multicore_fifo_push_last);
|
||||
TEST_ASSERT_FALSE(i2s_initialized);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,21 +43,13 @@ async def rainbow(np, period=10):
|
||||
|
||||
|
||||
async def play_mp3(audiocore, mp3file):
|
||||
_, avail, _ = audioctx.put(b'')
|
||||
known_underruns = 0
|
||||
while True:
|
||||
data = mp3file.read(avail)
|
||||
if avail > 0 and len(data) == 0:
|
||||
data = mp3file.read(4096)
|
||||
if len(data) == 0:
|
||||
# End of file
|
||||
break
|
||||
pos = 0
|
||||
while pos < len(data):
|
||||
pushed, avail, underruns = audioctx.put(data[pos:])
|
||||
if pushed == 0:
|
||||
await asyncio.sleep_ms(0)
|
||||
else:
|
||||
await asyncio.sleep_ms(0)
|
||||
pos += pushed
|
||||
_, _, underruns = await audioctx.async_put(data)
|
||||
if underruns > known_underruns:
|
||||
print(f"{underruns:x}")
|
||||
known_underruns = underruns
|
||||
@@ -66,6 +58,7 @@ async def play_mp3(audiocore, mp3file):
|
||||
|
||||
|
||||
async def play_mp3s(audiocore, mp3files):
|
||||
audiocore.set_volume(64)
|
||||
for name in mp3files:
|
||||
print(b'Playing ' + name)
|
||||
with open(name, "rb") as testfile:
|
||||
|
||||
@@ -11,6 +11,7 @@ typedef struct spin_lock spin_lock_t;
|
||||
void multicore_fifo_push_blocking(unsigned val);
|
||||
unsigned multicore_fifo_pop_blocking(void);
|
||||
bool multicore_fifo_rvalid(void);
|
||||
bool multicore_fifo_wready(void);
|
||||
|
||||
#define __time_critical_func(x) x
|
||||
#define __wfe()
|
||||
|
||||
Reference in New Issue
Block a user