diff --git a/components/drivers/audio/Kconfig b/components/drivers/audio/Kconfig index e903eccc21c..f6cae1ae6ce 100644 --- a/components/drivers/audio/Kconfig +++ b/components/drivers/audio/Kconfig @@ -14,4 +14,8 @@ config RT_USING_AUDIO config RT_AUDIO_RECORD_PIPE_SIZE int "Record pipe size" default 2048 + + config RT_UTEST_USING_AUDIO_DRIVER + bool "Enable rt_audio_api testcase" + default n endif diff --git a/components/drivers/audio/SConscript b/components/drivers/audio/SConscript index 1ea671d5555..7cf8f8118ca 100644 --- a/components/drivers/audio/SConscript +++ b/components/drivers/audio/SConscript @@ -6,4 +6,9 @@ CPPPATH = [cwd] group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_AUDIO'], CPPPATH = CPPPATH) +list = os.listdir(cwd) +for item in list: + if os.path.isfile(os.path.join(cwd, item, 'SConscript')): + group = group + SConscript(os.path.join(item, 'SConscript')) + Return('group') diff --git a/components/drivers/audio/utest/SConscript b/components/drivers/audio/utest/SConscript new file mode 100644 index 00000000000..4a535be5c37 --- /dev/null +++ b/components/drivers/audio/utest/SConscript @@ -0,0 +1,13 @@ +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() +src = [] +CPPPATH = [cwd] + +if GetDepend('RT_UTEST_USING_ALL_CASES') or GetDepend('RT_UTEST_USING_AUDIO_DRIVER'): + src += Glob('tc_*.c') + +group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_AUDIO'], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/drivers/audio/utest/tc_audio_common.h b/components/drivers/audio/utest/tc_audio_common.h new file mode 100644 index 00000000000..fa506969918 --- /dev/null +++ b/components/drivers/audio/utest/tc_audio_common.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006-2025 RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-05-02 wumingzi first version + */ + +#include +#include +#include +#include "utest.h" + +/* DMA buffer of audio player device refresh is triggered only when the amount of transmitted data is + * greater than the size of a single block in the data queue */ +#define TX_DMA_BLOCK_SIZE RT_AUDIO_REPLAY_MP_BLOCK_SIZE +#define TX_DMA_FIFO_SIZE (RT_AUDIO_REPLAY_MP_BLOCK_SIZE * 2) +#define RX_DMA_BLOCK_SIZE RT_AUDIO_RECORD_PIPE_SIZE +#define RX_DMA_FIFO_SIZE (RT_AUDIO_RECORD_PIPE_SIZE * 2) + +#define SOUND_PLAYER_DEVICE_NAME "sound0" +#define SOUND_MIC_DEVICE_NAME "mic0" + +#define PLAYER_SAMPLEBITS 16 +#define PLAYER_SAMPLERATE 16000 +#define PLAYER_CHANNEL 2 +#define PLAYER_VOLUME 30 + +#define MIC_SAMPLEBITS 16 +#define MIC_SAMPLERATE 16000 +#define MIC_CHANNEL 2 +#define MIC_TIME_MS 5000 + +extern rt_uint8_t audio_fsm_step ; + +struct mic_device +{ + struct rt_audio_device audio; + struct rt_audio_configure config; + rt_uint8_t *rx_fifo; +}; +struct sound_device +{ + struct rt_audio_device audio; + struct rt_audio_configure config; + rt_uint8_t volume; + rt_uint8_t *tx_fifo; +}; \ No newline at end of file diff --git a/components/drivers/audio/utest/tc_audio_drv_mic.c b/components/drivers/audio/utest/tc_audio_drv_mic.c new file mode 100644 index 00000000000..65cc0912078 --- /dev/null +++ b/components/drivers/audio/utest/tc_audio_drv_mic.c @@ -0,0 +1,136 @@ +/* +* Copyright (c) 2006-2025 RT-Thread Development Team +* +* SPDX-License-Identifier: Apache-2.0 +* +* Change Logs: +* Date Author Notes +* 2025-05-02 wumingzi First version +*/ + +#include "tc_audio_common.h" + +#define THREAD_PRIORITY 9 +#define THREAD_TIMESLICE 5 +#define thread_simulate_intr_create_stacksize 1024 +static rt_thread_t thread_simulate_intr_handle; +static struct mic_device mic_dev; + +/* Simulate callback function */ +static void thread_simulate_intr(void *parameter) +{ + /* Send the data(0xAA) from DMA buffer to kernel */ + rt_memset((void*)&mic_dev.rx_fifo[0], 0xAA, RX_DMA_BLOCK_SIZE); + rt_audio_rx_done((struct rt_audio_device *)&(mic_dev.audio), mic_dev.rx_fifo, RX_DMA_BLOCK_SIZE); + audio_fsm_step = 1; + + while (1) + { + if(audio_fsm_step == 2) + { + /* Send the the data(0x55) from DMA buffer to kernel */ + rt_memset((void*)&mic_dev.rx_fifo[RX_DMA_BLOCK_SIZE], 0x55, RX_DMA_BLOCK_SIZE); + rt_audio_rx_done(&mic_dev.audio, &mic_dev.rx_fifo[RX_DMA_BLOCK_SIZE], RX_DMA_BLOCK_SIZE); + audio_fsm_step = 3; + break; + } + if(audio_fsm_step == 4) + { + rt_thread_mdelay(10); + rt_audio_rx_done(&mic_dev.audio, &mic_dev.rx_fifo[RX_DMA_BLOCK_SIZE], RX_DMA_BLOCK_SIZE); + break; + } + rt_thread_mdelay(10); + } + + while(1) + { + rt_thread_mdelay(10); + } +} + +static void thread_simulate_intr_create(void) +{ + thread_simulate_intr_handle = rt_thread_create( + "thread_simulate_intr", + thread_simulate_intr, + RT_NULL, + thread_simulate_intr_create_stacksize, + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + + if (thread_simulate_intr_handle == RT_NULL) + { + rt_kprintf("Error: Failed to create thread!\n"); + return; + } + if (rt_thread_startup(thread_simulate_intr_handle) != RT_EOK) + { + rt_kprintf("Error: Failed to start thread!\n"); + thread_simulate_intr_handle = RT_NULL; + } + +} + +static rt_err_t mic_device_init(struct rt_audio_device *audio) +{ + return RT_EOK; +} + +/* Simulate DMA interrupt */ +static rt_err_t mic_device_start(struct rt_audio_device *audio, int stream) +{ + thread_simulate_intr_create(); + return RT_EOK; +} + +static rt_err_t mic_device_stop(struct rt_audio_device *audio, int stream) +{ + if (thread_simulate_intr_handle != RT_NULL) + { + rt_thread_delete(thread_simulate_intr_handle); + thread_simulate_intr_handle = RT_NULL; + } + return RT_EOK; +} + +static rt_err_t mic_device_getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps) +{ + return RT_EOK; +} + +static rt_err_t mic_device_configure(struct rt_audio_device *audio, struct rt_audio_caps *caps) +{ + return RT_EOK; +} + +static struct rt_audio_ops _mic_audio_ops = +{ + .getcaps = mic_device_getcaps, + .configure = mic_device_configure, + .init = mic_device_init, + .start = mic_device_start, + .stop = mic_device_stop, + .transmit = RT_NULL, + .buffer_info = RT_NULL, +}; + +static int rt_hw_mic_init(void) +{ + struct rt_audio_device *audio = &mic_dev.audio; + /* mic default */ + mic_dev.rx_fifo = rt_malloc(RX_DMA_FIFO_SIZE); + if (mic_dev.rx_fifo == RT_NULL) + { + return -RT_ENOMEM; + } + mic_dev.config.channels = MIC_CHANNEL; + mic_dev.config.samplerate = MIC_SAMPLERATE; + mic_dev.config.samplebits = MIC_SAMPLEBITS; + + /* register mic device */ + audio->ops = &_mic_audio_ops; + rt_audio_register(audio, SOUND_MIC_DEVICE_NAME, RT_DEVICE_FLAG_RDONLY, (void *)&mic_dev); + + return RT_EOK; +} +INIT_DEVICE_EXPORT(rt_hw_mic_init); \ No newline at end of file diff --git a/components/drivers/audio/utest/tc_audio_drv_player.c b/components/drivers/audio/utest/tc_audio_drv_player.c new file mode 100644 index 00000000000..049a6b09587 --- /dev/null +++ b/components/drivers/audio/utest/tc_audio_drv_player.c @@ -0,0 +1,150 @@ +/* +* Copyright (c) 2006-2025 RT-Thread Development Team +* +* SPDX-License-Identifier: Apache-2.0 +* +* Change Logs: +* Date Author Notes +* 2025-05-02 wumingzi First version +*/ + +#include "tc_audio_common.h" + +#define THREAD_PRIORITY 9 +#define THREAD_TIMESLICE 5 +#define thread_simulate_intr_create_stacksize 1024 +static rt_thread_t thread_simulate_intr_handle; +static struct sound_device snd_dev; + +static void thread_simulate_intr(void *parameter) +{ + rt_flag_t exec_once = 0; + while(1) + { + if(audio_fsm_step == 1 && exec_once == 0) + { + /* Move the data(0xAA) from kernel to DMA buffer */ + rt_audio_tx_complete(&snd_dev.audio); + audio_fsm_step = 2; + exec_once = 1; + rt_thread_mdelay(10); + } + else if(audio_fsm_step == 2 && exec_once == 1) + { + /* Move the data(0x55) from kernel to DMA buffer */ + rt_audio_tx_complete(&snd_dev.audio); + audio_fsm_step = 3; + rt_thread_mdelay(10); + } + else if(audio_fsm_step == 4) + { + /* rt_device_close will call rt_completion_wait(FOREVER), so we need delay to + * let system run the point */ + rt_thread_mdelay(10); + rt_audio_tx_complete(&snd_dev.audio); + break; + } + rt_thread_mdelay(10); + } + while (1) + { + rt_thread_mdelay(10); + } +} + +static void thread_simulate_intr_create(void) +{ + thread_simulate_intr_handle = rt_thread_create( + "thread_simulate_intr", + thread_simulate_intr, + RT_NULL, + thread_simulate_intr_create_stacksize, + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + + rt_thread_startup(thread_simulate_intr_handle); +} + +static rt_err_t player_device_init(struct rt_audio_device *audio) +{ + return RT_EOK; +} + +/* Simulate DMA interrupt */ +static rt_err_t player_device_start(struct rt_audio_device *audio, int stream) +{ + thread_simulate_intr_create(); + return RT_EOK; +} + +static rt_err_t player_device_stop(struct rt_audio_device *audio, int stream) +{ + rt_thread_delete(thread_simulate_intr_handle); + return RT_EOK; +} + +static rt_err_t player_device_getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps) +{ + return RT_EOK; +} + +static rt_err_t player_device_configure(struct rt_audio_device *audio, struct rt_audio_caps *caps) +{ + return RT_EOK; +} + +static rt_ssize_t player_device_transmit(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size) +{ + return size; +} + +static void player_device_buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info) +{ + RT_ASSERT(audio != RT_NULL); + /** + * TX_FIFO + * +----------------+----------------+ + * | block1 | block2 | + * +----------------+----------------+ + * \ block_size / + */ + info->buffer = snd_dev.tx_fifo; + info->total_size = TX_DMA_FIFO_SIZE; + info->block_size = TX_DMA_BLOCK_SIZE; + info->block_count = RT_AUDIO_REPLAY_MP_BLOCK_COUNT; +} + +static struct rt_audio_ops audio_ops = +{ + .getcaps = player_device_getcaps, + .configure = player_device_configure, + .init = player_device_init, + .start = player_device_start, + .stop = player_device_stop, + .transmit = player_device_transmit, + .buffer_info = player_device_buffer_info, +}; + +static int rt_hw_sound_init(void) +{ + rt_uint8_t *tx_fifo = RT_NULL; + + tx_fifo = rt_malloc(TX_DMA_FIFO_SIZE); + if (tx_fifo == NULL) + { + return -RT_ENOMEM; + } + snd_dev.tx_fifo = tx_fifo; + + /* Init default configuration */ + { + snd_dev.config.samplerate = PLAYER_SAMPLERATE; + snd_dev.config.channels = PLAYER_CHANNEL; + snd_dev.config.samplebits = PLAYER_SAMPLEBITS; + snd_dev.volume = PLAYER_VOLUME; + } + + snd_dev.audio.ops = &audio_ops; + rt_audio_register(&snd_dev.audio, SOUND_PLAYER_DEVICE_NAME, RT_DEVICE_FLAG_WRONLY, &snd_dev); + return RT_EOK; +} +INIT_DEVICE_EXPORT(rt_hw_sound_init); \ No newline at end of file diff --git a/components/drivers/audio/utest/tc_audio_main.c b/components/drivers/audio/utest/tc_audio_main.c new file mode 100644 index 00000000000..969b714f4ae --- /dev/null +++ b/components/drivers/audio/utest/tc_audio_main.c @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2006-2025 RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-05-01 wumingzi first version + */ + +/* The file can test the rt-thread audio driver framework including following api via memory + * simulation. + * + * rt_audio_register + * rt_audio_rx_done + * rt_audio_tx_complete + * + * When audio devices generate or receive new data, the corresponding buffer in device will + * receive date from kernel or surroundings. The same phenomenon will also happen at the + * application level. Thus we can fill memory to simulate the generation of data then track + * and check memory to ensure kernel processing audio data correctly. And this depends on + * implementations of audio drivers. + * + * Therefore, if the player_test testcase failed, it could mean rt_audio_register or + * rt_audio_tx_complete existing bugs. Similarly, if mic_test testcase failed, it could mean + * rt_audio_register or rt_audio_rx_done existing bugs. + */ + +#include "tc_audio_common.h" + +rt_uint8_t audio_fsm_step = 0; + +/* Allocate and initialize memory filled by fill_byte */ +static void *alloc_filled_mem(rt_uint8_t fill_byte, rt_size_t size) +{ + void *ptr = rt_malloc(size); + if (ptr != NULL) + { + rt_memset(ptr, fill_byte, size); + } + return ptr; +} + +/* Check if the memory is filled with fill_byte */ +static rt_err_t check_filled_mem(rt_uint8_t fill_byte, rt_uint8_t *mem, size_t size) +{ + rt_uint8_t *p = mem; + for (size_t i = 0; i < size; ++i) + { + if (*(p+i) != fill_byte) + { + return -RT_ERROR; + } + } + return RT_EOK; +} + +static void player_test(void) +{ + int res = 0; + void* player_buffer = RT_NULL; + rt_device_t dev_obj; + + dev_obj = rt_device_find(SOUND_PLAYER_DEVICE_NAME); + if (dev_obj == RT_NULL) + { + uassert_not_null(dev_obj); + goto __exit; + } + if (dev_obj->type != RT_Device_Class_Sound) + { + LOG_E("Not an audio player device\n"); + goto __exit; + } + + res = rt_device_open(dev_obj, RT_DEVICE_OFLAG_WRONLY); + if (res != RT_EOK) + { + LOG_E("Audio player device failed\n"); + uassert_true(0); + goto __exit; + } + + /* The sampling rate is set by the driver default, so there isn't configuration step */ + + struct rt_audio_device *audio_dev = rt_container_of(dev_obj, struct rt_audio_device, parent); + struct rt_audio_buf_info buf_info = audio_dev->replay->buf_info; + struct sound_device *snd_dev = rt_container_of(audio_dev, struct sound_device, audio); + + player_buffer = alloc_filled_mem(0xAA, TX_DMA_BLOCK_SIZE); + if (player_buffer == RT_NULL) + { + rt_kprintf("Allocate test memory failed\n"); + uassert_true(0); + goto __exit; + } + + if(snd_dev->tx_fifo == RT_NULL) + { + rt_kprintf("snd_dev->tx_fifo == RT_NULL "); + uassert_true(0); + goto __exit; + } + res = rt_device_write(dev_obj, 0, player_buffer, TX_DMA_BLOCK_SIZE); + if (res != RT_EOK && res != TX_DMA_BLOCK_SIZE) + { + rt_kprintf("Failed to write data to the player device, res = %d\n",res); + uassert_true(0); + goto __exit; + } + + audio_fsm_step = 1; + while (1) + { + if(audio_fsm_step == 2) + { + break; + } + rt_thread_mdelay(10); + } + + res = check_filled_mem(0xAA, &buf_info.buffer[0], TX_DMA_BLOCK_SIZE); + if (res != RT_EOK) + { + rt_kprintf("The first memory check failed! Buffer dump\n"); + + for (rt_size_t i = 0; i < TX_DMA_FIFO_SIZE; i++) + { + rt_kprintf("%02X ", buf_info.buffer[i]); + if (i % 16 == 15) rt_kprintf("\n"); + } + rt_kprintf("\n"); + uassert_true(0); + goto __exit; + } + + rt_free(player_buffer); + player_buffer = RT_NULL; + + player_buffer = alloc_filled_mem(0x55, TX_DMA_BLOCK_SIZE); + if (player_buffer == RT_NULL) + { + rt_kprintf("Allocate test memory failed\n"); + uassert_true(0); + goto __exit; + } + + res = rt_device_write(dev_obj, TX_DMA_BLOCK_SIZE, player_buffer, TX_DMA_BLOCK_SIZE); + if (res != RT_EOK && res != TX_DMA_BLOCK_SIZE) + { + rt_kprintf("Failed to write data to the player device, res = %d\n",res); + uassert_true(0); + goto __exit; + } + + audio_fsm_step = 2; + while (res != RT_EOK) + { + if(audio_fsm_step == 3) + { + break; + } + + rt_thread_mdelay(10); + } + + res = check_filled_mem(0x55,&buf_info.buffer[TX_DMA_BLOCK_SIZE], TX_DMA_BLOCK_SIZE); + if (res != RT_EOK) + { + rt_kprintf("The second memory check failed! Buffer dump\n"); + + for (rt_size_t i = 0; i < TX_DMA_FIFO_SIZE; i++) + { + rt_kprintf("%02X ", buf_info.buffer[i]); + if (i % 16 == 15) rt_kprintf("\n"); + } + rt_kprintf("\n"); + uassert_true(0); + goto __exit; + } + +__exit: + + if (player_buffer) + { + rt_free(player_buffer); + player_buffer = RT_NULL; + } + + if (dev_obj != RT_NULL) + { + audio_fsm_step = 4; + rt_device_close(dev_obj); + } +} + +static void mic_test(void) +{ + rt_device_t dev_obj; + rt_uint8_t *mic_buffer = RT_NULL; + rt_ssize_t res = 0; + rt_ssize_t length = 0; + mic_buffer = (rt_uint8_t *)rt_malloc(RX_DMA_BLOCK_SIZE); + if (mic_buffer == RT_NULL) + { + rt_kprintf("The mic_buffer memory allocate failed\n"); + uassert_true(0); + goto __exit; + } + + + dev_obj = rt_device_find(SOUND_MIC_DEVICE_NAME); + if (dev_obj == RT_NULL) + { + LOG_E("Not a mic device\n"); + uassert_true(0); + goto __exit; + } + + res = rt_device_open(dev_obj, RT_DEVICE_OFLAG_RDONLY); + if (res != RT_EOK) + { + LOG_E("Audio player device failed\n"); + uassert_true(0); + goto __exit; + } + + length = rt_device_read(dev_obj, 0, mic_buffer,RX_DMA_BLOCK_SIZE); + if(length < 0) + { + LOG_E("Mic device read err\n"); + } + if(audio_fsm_step == 1) + { + res = check_filled_mem(0xAA, (rt_uint8_t*)(mic_buffer), length); + } + if (res != RT_EOK) + { + LOG_E("The first memory check failed! Buffer dump\n"); + for (rt_size_t i = 0; i < RX_DMA_FIFO_SIZE; i++) + { + rt_kprintf("%02X ",mic_buffer[i]); + if (i % 16 == 15) rt_kprintf("\n"); + } + rt_kprintf("\n"); + uassert_true(0); + goto __exit; + } + audio_fsm_step = 2; + + while (1) + { + if(audio_fsm_step == 3) + { + length = rt_device_read(dev_obj, 0, mic_buffer, RX_DMA_FIFO_SIZE); + if(length < 0) + { + LOG_E("Mic device read err\n"); + } + res = check_filled_mem(0x55, (rt_uint8_t*)(&mic_buffer[0]), length); + + if(res != RT_EOK) + { + LOG_E("The second memory check failed! Buffer dump\n"); + for (rt_size_t i = 0; i < RX_DMA_FIFO_SIZE; i++) + { + rt_kprintf("%02X ",mic_buffer[i]); + if (i % 16 == 15) rt_kprintf("\n"); + } + rt_kprintf("\n"); + uassert_true(0); + goto __exit; + } + + break; + } + rt_thread_mdelay(100); + } + +__exit: + if (mic_buffer) + { + rt_free(mic_buffer); + } + + if (dev_obj != RT_NULL) + { + audio_fsm_step = 4; + rt_device_close(dev_obj); + } +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(player_test); + UTEST_UNIT_RUN(mic_test); +} + +static rt_err_t utest_tc_init(void) +{ + return RT_EOK; +} + +static rt_err_t utest_tc_cleanup(void) +{ + return RT_EOK; +} + +UTEST_TC_EXPORT(testcase, "audio.tc_audio_main", utest_tc_init, utest_tc_cleanup, 10); \ No newline at end of file