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:
2025-03-15 22:43:22 +01:00
parent cc2bf8a84b
commit 931571bd0a
10 changed files with 144 additions and 30 deletions

View File

@@ -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"

View File

@@ -11,3 +11,5 @@ require("aiorepl")
module("mfrc522.py", "../../lib/micropython-mfrc522/")
module("microdot.py", "../../lib/microdot/src/microdot/")
module("audiocore.py", "../../src/audiocore")

View File

@@ -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);
}

View File

@@ -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

View 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()

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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:

View File

@@ -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()