瀏覽代碼

components: drivers: audio: Add audio driver test framework

Achieve driver framework by operating memory to simulate audio
peripheral drivers. And it could be as a draft standrad for other
drivers test framework.

Signed-off-by: 1078249029 <1078249029@qq.com>
1078249029 2 周之前
父節點
當前提交
d834899a6f

+ 4 - 0
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

+ 5 - 0
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')

+ 13 - 0
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')

+ 50 - 0
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 <rtthread.h>
+#include <rtdevice.h>
+#include <rttypes.h>
+#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;
+};

+ 136 - 0
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);

+ 150 - 0
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);

+ 309 - 0
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);