From 190dc519cf1210124b369254e2aecd41021b0621 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 8 Nov 2020 22:26:18 +0200 Subject: [PATCH] Make audio module non-blocking. --- src/micropython | 2 +- src/omv/main.c | 5 +- src/omv/py/py_audio.c | 317 +++++++++++++++++---------------------- src/omv/py/py_audio.h | 14 ++ src/omv/py/qstrdefsomv.h | 2 +- 5 files changed, 156 insertions(+), 184 deletions(-) create mode 100644 src/omv/py/py_audio.h diff --git a/src/micropython b/src/micropython index d2880e8d3..a0d393a06 160000 --- a/src/micropython +++ b/src/micropython @@ -1 +1 @@ -Subproject commit d2880e8d39c41ba163ef1cc274578b47d7b1124a +Subproject commit a0d393a0627a7fd4825a3ae8f14c7eb5e2a70de5 diff --git a/src/omv/main.c b/src/omv/main.c index 97ddce45b..d9788cca2 100644 --- a/src/omv/main.c +++ b/src/omv/main.c @@ -70,6 +70,7 @@ #include "py_fir.h" #include "py_tv.h" #include "py_imu.h" +#include "py_audio.h" #include "framebuffer.h" @@ -732,7 +733,9 @@ soft_reset: can_deinit_all(); #endif pyb_thread_deinit(); - + #if MICROPY_PY_AUDIO + py_audio_deinit(); + #endif first_soft_reset = false; goto soft_reset; } diff --git a/src/omv/py/py_audio.c b/src/omv/py/py_audio.c index 4579ae622..16dc67715 100644 --- a/src/omv/py/py_audio.c +++ b/src/omv/py/py_audio.c @@ -9,6 +9,7 @@ * Audio Python module. */ #include +#include "py_audio.h" #include "systick.h" #include "py_assert.h" #include "py_helper.h" @@ -28,7 +29,10 @@ static CRC_HandleTypeDef hcrc; static SAI_HandleTypeDef hsai; static DMA_HandleTypeDef hdma_sai_rx; -static int n_channels = AUDIO_SAI_NBR_CHANNELS; +static mp_obj_array_t *g_pcmbuf = NULL; +static mp_obj_t g_audio_callback = mp_const_none; + +static int g_channels = AUDIO_SAI_NBR_CHANNELS; static PDM_Filter_Handler_t PDM_FilterHandler[2]; static PDM_Filter_Config_t PDM_FilterConfig[2]; @@ -41,6 +45,9 @@ static volatile uint32_t xfer_status = 0; // BDMA can only access D3 SRAM4 memory. uint8_t PDM_BUFFER[PDM_BUFFER_SIZE] __attribute__ ((aligned (32))) __attribute__((section(".d3_sram_buffer"))); +// Pendsv dispatch callback. +static void audio_pendsv_callback(void); + void AUDIO_SAI_DMA_IRQHandler(void) { HAL_DMA_IRQHandler(hsai.hdmarx); @@ -50,26 +57,62 @@ void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai) { xfer_status |= DMA_XFER_HALF; SCB_InvalidateDCache_by_Addr((uint32_t *)(&PDM_BUFFER[0]), PDM_BUFFER_SIZE / 2); + if (g_audio_callback != mp_const_none) { + pendsv_schedule_dispatch(PENDSV_DISPATCH_AUDIO, audio_pendsv_callback); + } } void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) { xfer_status |= DMA_XFER_FULL; SCB_InvalidateDCache_by_Addr((uint32_t *)(&PDM_BUFFER[PDM_BUFFER_SIZE / 2]), PDM_BUFFER_SIZE / 2); + if (g_audio_callback != mp_const_none) { + pendsv_schedule_dispatch(PENDSV_DISPATCH_AUDIO, audio_pendsv_callback); + } +} + +static uint32_t get_decimation_factor(uint32_t decimation) +{ + switch (decimation) { + case 16: return PDM_FILTER_DEC_FACTOR_16; + case 24: return PDM_FILTER_DEC_FACTOR_24; + case 32: return PDM_FILTER_DEC_FACTOR_32; + case 48: return PDM_FILTER_DEC_FACTOR_48; + case 64: return PDM_FILTER_DEC_FACTOR_64; + case 80: return PDM_FILTER_DEC_FACTOR_80; + case 128: return PDM_FILTER_DEC_FACTOR_128; + default: return 0; + } } static mp_obj_t py_audio_init(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - n_channels = py_helper_keyword_int(n_args, args, 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_channels), 2); + // Read Args. + g_channels = py_helper_keyword_int(n_args, args, 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_channels), 2); + uint32_t frequency = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_frequency), 16000); + int gain_db = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_gain_db), 24); + float highpass = py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_highpass), 0.9883f); - if (n_channels != 1 && n_channels != 2) { + // Sanity checks + if (frequency < 16000 || frequency > 128000) { + RAISE_OS_EXCEPTION("Invalid frequency!"); + } + + if (g_channels != 1 && g_channels != 2) { RAISE_OS_EXCEPTION("Invalid number of channels! Expected 1 or 2."); } + uint32_t decimation_factor = AUDIO_SAI_FREQKHZ / (frequency / 1000); + uint32_t decimation_factor_const = get_decimation_factor(decimation_factor); + if (decimation_factor_const == 0) { + RAISE_OS_EXCEPTION("This frequency is not supported!"); + } + uint32_t samples_per_channel = (PDM_BUFFER_SIZE * 8) / (decimation_factor * g_channels * 2); // Half a transfer + hsai.Instance = AUDIO_SAI; hsai.Init.Protocol = SAI_FREE_PROTOCOL; hsai.Init.AudioMode = SAI_MODEMASTER_RX; - hsai.Init.DataSize = (n_channels == 1) ? SAI_DATASIZE_8 : SAI_DATASIZE_16; + hsai.Init.DataSize = (g_channels == 1) ? SAI_DATASIZE_8 : SAI_DATASIZE_16; hsai.Init.FirstBit = SAI_FIRSTBIT_LSB; hsai.Init.ClockStrobing = SAI_CLOCKSTROBING_RISINGEDGE; hsai.Init.Synchro = SAI_ASYNCHRONOUS; @@ -78,7 +121,7 @@ static mp_obj_t py_audio_init(uint n_args, const mp_obj_t *args, mp_map_t *kw_ar hsai.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_1QF; hsai.Init.SynchroExt = SAI_SYNCEXT_DISABLE; hsai.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_MCKDIV; - hsai.Init.MonoStereoMode = (n_channels == 1) ? SAI_MONOMODE: SAI_STEREOMODE; + hsai.Init.MonoStereoMode = (g_channels == 1) ? SAI_MONOMODE: SAI_STEREOMODE; hsai.Init.CompandingMode = SAI_NOCOMPANDING; hsai.Init.TriState = SAI_OUTPUT_RELEASED; @@ -101,8 +144,8 @@ static mp_obj_t py_audio_init(uint n_args, const mp_obj_t *args, mp_map_t *kw_ar hsai.SlotInit.FirstBitOffset = 0; hsai.SlotInit.SlotSize = SAI_SLOTSIZE_DATASIZE; - hsai.SlotInit.SlotNumber = (n_channels == 1) ? 2 : 1; - hsai.SlotInit.SlotActive = (n_channels == 1) ? (SAI_SLOTACTIVE_0 | SAI_SLOTACTIVE_1) : SAI_SLOTACTIVE_0; + hsai.SlotInit.SlotNumber = (g_channels == 1) ? 2 : 1; + hsai.SlotInit.SlotActive = (g_channels == 1) ? (SAI_SLOTACTIVE_0 | SAI_SLOTACTIVE_1) : SAI_SLOTACTIVE_0; // Initialize the SAI HAL_SAI_DeInit(&hsai); @@ -119,8 +162,8 @@ static mp_obj_t py_audio_init(uint n_args, const mp_obj_t *args, mp_map_t *kw_ar hdma_sai_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_sai_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_sai_rx.Init.MemInc = DMA_MINC_ENABLE; - hdma_sai_rx.Init.PeriphDataAlignment = (n_channels == 1) ? DMA_PDATAALIGN_BYTE : DMA_PDATAALIGN_HALFWORD; - hdma_sai_rx.Init.MemDataAlignment = (n_channels == 1) ? DMA_MDATAALIGN_BYTE : DMA_MDATAALIGN_HALFWORD; + hdma_sai_rx.Init.PeriphDataAlignment = (g_channels == 1) ? DMA_PDATAALIGN_BYTE : DMA_PDATAALIGN_HALFWORD; + hdma_sai_rx.Init.MemDataAlignment = (g_channels == 1) ? DMA_MDATAALIGN_BYTE : DMA_MDATAALIGN_HALFWORD; hdma_sai_rx.Init.Mode = DMA_CIRCULAR; hdma_sai_rx.Init.Priority = DMA_PRIORITY_HIGH; hdma_sai_rx.Init.FIFOMode = DMA_FIFOMODE_ENABLE; @@ -151,9 +194,54 @@ static mp_obj_t py_audio_init(uint n_args, const mp_obj_t *args, mp_map_t *kw_ar } __HAL_CRC_DR_RESET(&hcrc); + // Configure PDM filters + for (int i=0; iitems)[i], &PDM_FilterHandler[i]); + } + } else if ((xfer_status & DMA_XFER_FULL)) { // Check for transfer complete. + // Clear buffer state. + xfer_status &= ~(DMA_XFER_FULL); + + // Convert PDM samples to PCM. + for (int i=0; iitems)[i], &PDM_FilterHandler[i]); + } } + // Call user callback + mp_call_function_1(g_audio_callback, MP_OBJ_FROM_PTR(g_pcmbuf)); } -static mp_obj_t py_audio_read_pcm(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +static mp_obj_t py_audio_start_streaming(mp_obj_t callback_obj) { - mp_buffer_info_t pcmbuf; - mp_get_buffer_raise(args[0], &pcmbuf, MP_BUFFER_WRITE); - uint32_t frequency = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_frequency), 16); - int gain_db = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_gain_db), 24); - float highpass = py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_highpass), 0.9883f); + g_audio_callback = callback_obj; - int16_t *output_buffer = (int16_t *) pcmbuf.buf; - uint32_t decimation_factor = AUDIO_SAI_FREQKHZ/frequency; - uint32_t output_samples = ((PDM_BUFFER_SIZE / 2) * 8) / (decimation_factor * n_channels); // Half transfer - size_t typesize = mp_binary_get_size('@', pcmbuf.typecode, NULL); - // Round down the number of samples to even buffer multiples. - uint32_t n_samples = (((pcmbuf.len / typesize) / output_samples) & (~1U)) * output_samples; - - if (typesize != 2) { - // Make sure the buffer is 16-Bits array for PCM samples. - RAISE_OS_EXCEPTION("Wrong data type, expected 16-Bits array!"); - } - - // Configure PDM library - for (int i=0; i= 1000) { - HAL_SAI_DMAStop(&hsai); - RAISE_OS_EXCEPTION("SAI DMA transfer timeout!"); - } - } - - // Clear buffer state. - xfer_status &= ~(DMA_XFER_HALF); - - // Convert PDM samples to PCM. - for (int i=0; i= 1000) { - HAL_SAI_DMAStop(&hsai); - RAISE_OS_EXCEPTION("SAI DMA transfer timeout!"); - } - } - - // Clear buffer state. - xfer_status &= ~(DMA_XFER_FULL); - - // Convert PDM samples to PCM. - for (int i=0; i= 1000) { - HAL_SAI_DMAStop(&hsai); - RAISE_OS_EXCEPTION("SAI DMA transfer timeout!"); - } - } - - // Clear buffer state. - xfer_status &= ~(DMA_XFER_HALF); - - // Convert PDM samples to PCM. - for (int i=0; iitems)[i], &PDM_FilterHandler[i]); - } - - mp_call_function_2(callback, MP_OBJ_FROM_PTR(pcmbuf), arg_obj); - - // Wait for transfer complete. - while ((xfer_status & DMA_XFER_FULL) == 0) { - if ((HAL_GetTick() - start) >= 1000) { - HAL_SAI_DMAStop(&hsai); - RAISE_OS_EXCEPTION("SAI DMA transfer timeout!"); - } - } - - // Clear buffer state. - xfer_status &= ~(DMA_XFER_FULL); - - // Convert PDM samples to PCM. - for (int i=0; iitems)[i], &PDM_FilterHandler[i]); - } - - mp_call_function_2(callback, MP_OBJ_FROM_PTR(pcmbuf), arg_obj); - } - // Stop SAI DMA. HAL_SAI_DMAStop(&hsai); - + g_audio_callback = mp_const_none; return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_audio_init_obj, 0, py_audio_init); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_audio_read_pdm_obj, py_audio_read_pdm); -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_audio_read_pcm_obj, 1, py_audio_read_pcm); -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_audio_start_streaming_obj, 2, py_audio_start_streaming); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_audio_start_streaming_obj, py_audio_start_streaming); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_audio_stop_streaming_obj, py_audio_stop_streaming); -static const mp_map_elem_t globals_dict_table[] = { - { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_audio) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)&py_audio_init_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_read_pcm), (mp_obj_t)&py_audio_read_pcm_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_read_pdm), (mp_obj_t)&py_audio_read_pdm_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_start_streaming), (mp_obj_t)&py_audio_start_streaming_obj }, +static const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audio) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&py_audio_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_pdm), MP_ROM_PTR(&py_audio_read_pdm_obj) }, + { MP_ROM_QSTR(MP_QSTR_start_streaming), MP_ROM_PTR(&py_audio_start_streaming_obj)}, + { MP_ROM_QSTR(MP_QSTR_stop_streaming), MP_ROM_PTR(&py_audio_stop_streaming_obj) }, }; STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); diff --git a/src/omv/py/py_audio.h b/src/omv/py/py_audio.h new file mode 100644 index 000000000..694c9ea6d --- /dev/null +++ b/src/omv/py/py_audio.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2019 Ibrahim Abdelkader + * Copyright (c) 2013-2019 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Audio Python module. + */ +#ifndef __PY_AUDIO_H__ +#define __PY_AUDIO_H__ +void py_audio_deinit(); +#endif // __PY_AUDIO_H__ diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 430838cff..3b8123ec0 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -1256,5 +1256,5 @@ Q(gain_db) Q(highpass) Q(frequency) Q(read_pdm) -Q(read_pcm) Q(start_streaming) +Q(stop_streaming)