Repository: RT-Thread/rt-thread
Pull request #10243: [RFC][utest] Add audio driver test framework
Author: login: 1078249029
Labels: name: BSP
name: BSP: Cvitek
description: BSP related with cvitek
name: testcase
name: Component
name: component: drivers/audio
name: component: drivers
Created: 2025-05-03T14:32:12Z | Merged: 2025-05-18T07:48:27Z



## 拉取/合并请求描述：(PR description)

[
<!-- 这段方括号里的内容是您**必须填写并替换掉**的，否则PR不可能被合并。**方括号外面的内容不需要修改，但请仔细阅读。**
The content in this square bracket must be filled in and replaced, otherwise, PR can not be merged. The contents outside square brackets need not be changed, but please read them carefully.

请在这里填写您的PR描述，可以包括以下之一的内容：为什么提交这份PR；解决的问题是什么，你的解决方案是什么；
Please fill in your PR description here, which can include one of the following items: why to submit this PR; what is the problem solved and what is your solution;

并确认并列出已经在什么情况或板卡上进行了测试。
And confirm in which case or board has been tested. -->

#### 为什么提交这份PR (why to submit this PR)
Fixed #10046 

#### 你的解决方案是什么 (what is your solution)

Achieve driver framework by operating memory to simulate audio peripheral drivers. And it could be as a draft standrad for other drivers test framework.

#### 请提供验证的bsp和config (provide the config and bsp) 

<!-- 请填写验证bsp目录下面的目录比如bsp/stm32/stm32l496-st-nucleo

Please provide the path of verfied bsp. Like bsp/stm32/stm32l496-st-nucleo  bsp/ESP32_C3 -->

- BSP:

<!-- 请填写.config 文件中需要改动的config

Please provide the changed config of .config file to how to verify the PR file like CONFIG_BSP_USING_I2C CONFIG_BSP_USING_WDT -->

- .config:

<!-- 请提供自己仓库的PR branch的action的编译链接相关PR文件成功的链接：

Please provide the link of action triggered by your own repo's action  

https://github.com/RT-Thread/rt-thread/actions/workflows/manual_dist.yml -->

- action:

]

<!-- 以下的内容不应该在提交PR时的message修改，修改下述message，PR会被直接关闭。请在提交PR后，浏览器查看PR并对以下检查项逐项check，没问题后逐条在页面上打钩。
The following content must not be changed in the submitted PR message. Otherwise, the PR will be closed immediately. After submitted PR, please use a web browser to visit PR, and check items one by one, and ticked them if no problem. -->

### 当前拉取/合并请求的状态 Intent for your PR

必须选择一项 Choose one (Mandatory):

- [x] 本拉取/合并请求是一个草稿版本 This PR is for a code-review and is intended to get feedback
- [ ] 本拉取/合并请求是一个成熟版本 This PR is mature, and ready to be integrated into the repo

### 代码质量 Code Quality：

我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:

- [ ] 已经仔细查看过代码改动的对比 Already check the difference between PR and old code
- [x] 代码风格正确，包括缩进空格，命名及其他风格 Style guide is adhered to, including spacing, naming and other styles
- [x] 没有垃圾代码，代码尽量精简，不包含`#if 0`代码，不包含已经被注释了的代码 All redundant code is removed and cleaned up
- [x] 所有变更均有原因及合理的，并且不会影响到其他软件组件代码或BSP All modifications are justified and not affect other components or BSP
- [x] 对难懂代码均提供对应的注释 I've commented appropriately where code is tricky
- [ ] 代码是高质量的 Code in this PR is of high quality
- [x] 已经使用[formatting](https://github.com/mysterywolf/formatting) 等源码格式化工具确保格式符合[RT-Thread代码规范](https://github.com/RT-Thread/rt-thread/blob/master/documentation/contribution_guide/coding_style_cn.md) This PR complies with [RT-Thread code specification](https://github.com/RT-Thread/rt-thread/blob/master/documentation/contribution_guide/coding_style_en.md)
- [ ] 如果是新增bsp, 已经添加ci检查到[.github/workflows/bsp_buildings.yml](workflows/bsp_buildings.yml)  详细请参考链接[BSP自查](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/development-guide/bsp-selfcheck/bsp_selfcheck)


Linked issue #10046: [Feature] [doxygen] [audio] A plan to achieve audio components doxygen comments
### Describe problem solved by the proposed feature

实现audio components的注释

### Describe your preferred solution

我打算先把注释写了然后实现utest框架，以后补充components/drivers下的注释都按这个顺序来
目录的group结构方面我打算规划如下：

* Aduio
    * Audio 相关的宏，由于宏类似于AUDIO_SAMP_RATExxK或者AUDIO_DSPxx，因此这里需要放几个同级目录来管理这些宏
    * Audio 相关的函数，除了audio pipe相关的都放这里
* Audio Pipe
    * audio pipe相关的函数
 由于audio pipe宏不是很多，这里就直接放到audio pipe group下面

有的函数写的不好的地方我会在写好注释后另提pr修改，也算梳理模块了

各位觉得怎么样？

### Describe possible alternatives

_No response_
Linked issue comments: user: login: supperthomas
created_at: 2025-02-27T02:54:58Z
body: 很好，有计划在什么平台验证吗？
author_association: MEMBER
user: login: 1078249029
created_at: 2025-02-27T03:01:25Z
body: 有生成doxygen的脚本，我这里可以在线预览
author_association: CONTRIBUTOR
user: login: unicornx
created_at: 2025-02-28T01:51:06Z
body: Q1：对于 audio，我看 markdown 部分是缺失的，是否这次会补上？一个可以参考的网上文章 https://zhuanlan.zhihu.com/p/344383184

Q2：对于 audio 框架，我有个疑问，就是 audio pipe 部分是不是应该不属于 API 部分。因为从 driver 的 user 来看，API 仅仅是定义在 `components/drivers/include/drivers/dev_audio.h` 中的那些。
`components/drivers/audio/dev_audio_pipe.h` 中和 audio pipe 的相关部分是否都是仅在 audio 框架内部使用的? 
对于 audio frameowork 的 client，也就是 audio 驱动, 应该不会直接用到 audio pipe 的接口吧，如果是这样，我觉得就 audio pipe 部分不应该属于 API，也不需要为它定义 group 从而展现在 html 中。当然代码注释依然可以加上，只是不进入 doxygen 的生成 html 部分。

Q3：有关 group 结构，从你的文字描述上看不太清楚，可以给个 tree 描述吗？类似：
```
group_A
|_group_B
  |_group_C
```

Q4：从你的规划上来看，似乎 Audio 下宏和函数还要定义 group? 没有看明白，感觉 group 的概念应该不会用来区分 函数和 宏吧

Q3 和 Q4 也可以提交一个 RFC 的 pr，直接看看代码就清楚了。

Q5：我建议的 pr 步骤如下：
- 先 ***基于现有设计和代码*** 提 pr 修改文档，包括 markdown 的文字部分，API 的 doxygen 注释部分等
- 如果觉得现有代码有问题，可以再提 pr 改代码，相应的提 pr 改文档。如果改动不大，代码和文档的改动可以一个 pr，否则建议分开。
- 再提一个 pr 解决 utest。感觉这个需要设计好如何测试，是否可以引入 pesudo device 辅助测试 API。如果要引入 pesudo device，这个又是一个自动化测试的框架，可能需要在 utest 的 pr 前单独提 pr 来做。

其他未尽事宜，欢迎补充。


author_association: CONTRIBUTOR
user: login: 1078249029
created_at: 2025-02-28T05:17:36Z
body: A1: markdown这部分我还在想，目前我认为最好的还是[rtt的官方文档](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/audio/audio?id=audio-%e8%ae%be%e5%a4%87)，结构和内容也很符合introduction，直接把文档搬过来也是不错的选择
A2: pipe都是在audio内部使用的。我以为需要补充并暴露audio组件所有非static符号的注释的，毕竟写的是文档，理应越全越好，使用的时候也只是查个api。如果需要写驱动可以参考例程或者别人的代码，多余的api直接当成手册了。按您这么说只暴露写驱动和应用层的api也没什么问题......而且风格与其他模块的注释是一样的。感觉挺两难的
A3: 

![Image](https://github.com/user-attachments/assets/d514dcef-0832-4097-a879-4306b1b201a8)

A4: group只区分不同种类的宏，Audio的采样率和码率等配置宏的选项太多了，用group会更规整一些
author_association: CONTRIBUTOR
user: login: unicornx
created_at: 2025-02-28T06:26:59Z
body: > A2: pipe都是在audio内部使用的。我以为需要补充并暴露audio组件所有非static符号的注释的，毕竟写的是文档，理应越全越好，使用的时候也只是查个api。如果需要写驱动可以参考例程或者别人的代码，多余的api直接当成手册了。按您这么说只暴露写驱动和应用层的api也没什么问题......而且风格与其他模块的注释是一样的。感觉挺两难的 A3:

我觉得没什么两难的，“越全越好” 也不是原则，我们的原则就是 “html 上出现的是 API”。所以我觉得对于 `components/drivers/audio/dev_audio_pipe.h` 中的那些结构体和函数，如果不是开放给外部驱动使用的，就不要出现在 html 上。但是 pipe 相关的  .c 和 .h 中依然可以写注释。只要不要 addtogroup 啥的就不会出现在 html 上。所以我看你贴的图上的 Audio Pipe 那个部分是不是应该不出现？

> A4: group只区分不同种类的宏，Audio的采样率和码率等配置宏的选项太多了，用group会更规整一些

我理解你这么做是因为看宏比较多，所以想分一下类，对吧。可以试试，你图片展示的是 treeview 的效果，右边页面上的效果又会是如何？如果你不想提 rfc pr ，其实也可以贴你的 github 开发仓库的 branch 上来，省的贴图。

author_association: CONTRIBUTOR
user: login: 1078249029
created_at: 2025-02-28T08:03:44Z
body: 我把markdown补完就提个rfc吧，但是没有合并到主线生成的文档的视觉效果能看到么？
author_association: CONTRIBUTOR
user: login: unicornx
created_at: 2025-02-28T08:11:51Z
body: > 我把markdown补完就提个rfc吧，但是没有合并到主线生成的文档的视觉效果能看到么？

只要能拿到你的 branch，我自己本地也能看
author_association: CONTRIBUTOR
user: login: supperthomas
created_at: 2025-02-28T08:55:37Z
body: > 我把markdown补完就提个rfc吧，但是没有合并到主线生成的文档的视觉效果能看到么？

可以的，提交PR的时候，会有个产出文件，打开即可看到效果。
author_association: MEMBER
user: login: supperthomas
created_at: 2025-02-28T08:56:23Z
body: ![Image](https://github.com/user-attachments/assets/1f3af67e-07ac-45a6-81dc-d221721bea6a)
author_association: MEMBER

Diff:
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 <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;
+};
\ 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


Reviews:
user: login: Rbb666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T06:24:20Z
user: login: Rbb666
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-12T02:31:42Z
user: login: unicornx
body: 我记得上次开会满老师 @mysterywolf 说建议后面 ut cases 都移到各个模块自己的目录下去，不要放到 examples 下了，本身这个 examples 就很让人迷惑。可以参考 `src/klibc/utest` 这个是满老师最新作的 utest 例子，貌似就是每个模块下自己维护一个 utest 目录然后在里面加测试代码。然后就是模仿  `src/klibc/utest`  下的代码写，包括 SConscript 里要 denpend `RT_UTEST_USING_ALL_CASES` ，还有配置选项名字统一加 'RT_UTEST_TC_USING_' 前缀，参考 'RT_UTEST_TC_USING_KLIBC'。

还有一个问题就是建议在测试代码中以注释的方式写一下测试的设计思想，直接 review 代码一下子不知道你的设计思路理解起来很费劲。这个测试代码需要依赖硬件吗？看注释有模拟的部分，可以说明一下。另外我理解这里测试的对象是 driver audio 的 API，所以 `drv_mic.c` 和 `drv_player.c` 应该是测试的主体，对吧。可以在注释中列一下你覆盖的 API 吗？

代码上，所有全局变量和函数，如果不是 extern 的请全部 static。


state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T02:18:22Z
user: login: unicornx
body: 请每次更新后都和最新的 master 同步一下。

此外，几个问题先提一下，请一并和上面的同步问题一起先改掉。
- commit 的标题中不要出现 RFC 的字样，PR 可以是 RFC。如果 commit 中含有 RFC，一旦 merge 这个 commit 看上去就怪怪的，除非提交人会及时改 commit，但实践上看做不到这个。PR 里写 RFC 或者设置 draft 状态都很方便，以后要改 PR 的标题也可以在网页上直接操作，但是 commit 一旦合入就很难改了。
- commit 的标题前缀我觉得不是 utest，但是应该是属于 components: driver: audio 的因为按照现在的做法，utest 是属于子模块的，不再是单独的 utest，除非是涉及 utest 的 framework 的改动。
- 文件名，我建议有 TC 前缀就够了，不用加 rt 前缀，所有文件默认都是 RTT 的，加 rt 前缀属于多余，而且导致文件名分段太多。另外我比较建议不要大写 TC，文件名全部小写比较好。
- 只在内部使用的全局变量都要 static，好像没有改全。
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T10:27:28Z
user: login: unicornx
body: 参考 <https://github.com/plctlab/plct-rt-thread/blob/notes/0.notes/20241212-github-tips.md#2-how-to-write-git-commit-message> 重新写一下 commit message。
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T07:04:51Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a test framework for audio drivers by simulating audio peripherals using memory operations.  
- Added unit tests for audio player and microphone drivers.  
- Integrated simulation threads to trigger DMA-like behavior.  
- Updated build scripts and configuration options for audio driver tests.

### Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File                                           | Description                                                               |
| ---------------------------------------------- | ------------------------------------------------------------------------- |
| components/drivers/audio/utest/tc_audio_main.c | Added unit tests for audio framework and memory verification logic.       |
| components/drivers/audio/utest/tc_audio_drv_player.c | Added player driver simulation and interrupt thread for DMA behavior.     |
| components/drivers/audio/utest/tc_audio_drv_mic.c | Added microphone driver simulation and interrupt thread for DMA behavior. |
| components/drivers/audio/utest/tc_audio_common.h | New header with shared definitions and constants for driver tests.        |
| components/drivers/audio/utest/SConscript       | Updated SConscript to include audio test cases.                           |
| components/drivers/audio/SConscript             | Modified build script to recursively include SConscript files.            |
| components/drivers/audio/Kconfig                | Added new configuration option for audio driver test cases.               |
</details>



<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**components/drivers/audio/utest/tc_audio_drv_player.c:15**
* [nitpick] Consider renaming 'thread_simulate_intr_create_stacksize' to uppercase (e.g., THREAD_SIMULATE_INTR_CREATE_STACKSIZE) for consistency with other macro names.
```
#define thread_simulate_intr_create_stacksize 1024
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T01:28:42Z
user: login: unicornx
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T02:40:06Z

Review comments:
diff_hunk: @@ -0,0 +1,196 @@
+/*
+ * 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
+ */
+
+#include "utest.h"
+#include "common.h"
+#include <string.h>
+
+static rt_device_t dev_obj; /* Audio device handler */
+
+/* Allocate and initialize memory filled by fill_byte */
+static void *alloc_filled_mem(rt_uint8_t fill_byte, size_t size)
+{
+    void *ptr = malloc(size);
+    if (ptr != NULL)
+    {
+        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 fd = -1;
+    int res = 0;
+    uint8_t *buffer = NULL;
+    void* player_buffer = NULL;struct rt_audio_caps caps = {0};
path: examples/utest/testcases/drivers/audio/audio_tc.c
user: login: Rbb666
body: 注意下代码风格，加个换行吧
created_at: 2025-05-06T06:23:18Z
author_association: MEMBER
path: examples/utest/testcases/drivers/audio/mic_drv.c
user: login: Rbb666
body: 两个文件名称改成drv_xxx是不是更好些？
created_at: 2025-05-06T06:24:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,139 @@
+/*
+* 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_rt_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;
+
+struct mic_device mic_dev;
path: components/drivers/audio/utest/tc_audio_drv_mic.c
user: login: unicornx
body: 为什么不是 static？
created_at: 2025-05-15T06:43:36Z
author_association: CONTRIBUTOR
path: components/drivers/audio/utest/tc_audio_tc.c
user: login: unicornx
body: 这个文件名为啥前后都有 tc，后面那个有什么含义吗？要不要改一个更有意义的名字？
看了你的代码，感觉 `tc_audio_tc.c` 是测试的主程序，譬如叫 `tc_audio_main.c` 或者直接叫 `tc_audio.c`?
created_at: 2025-05-15T06:44:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,139 @@
+/*
+* 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_rt_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;
+
+struct mic_device mic_dev;
+
+rt_uint8_t mic_flag = 0;
path: components/drivers/audio/utest/tc_audio_drv_mic.c
user: login: unicornx
body: 为啥 `mic_flag` 定义在 `tc_audio_drv_mic.c` 里，而 `player_flag` 定义在 `tc_audio_tc.c` 里？
我建议统一定义在 `tc_audio_tc.c` ，甚至我觉得如果 `player_test` 和 `mic_test` 不会同时跑的话，只要一个 flag 就好了, 因为这两个变量的作用应该是一回事且不会同时被使用。

另外我理解 `mic_flag`/`player_flag` 你是想表达一个类似测试过程中状态机的用于同步主程序和驱动之间的作用吧，flag 这个名字我觉得不能很清楚地表达意思，或许叫 step 更好？



created_at: 2025-05-15T06:56:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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_rt_audio_common.h"
path: components/drivers/audio/utest/tc_audio_tc.c
user: login: unicornx
body: 你提交前编译过？
created_at: 2025-05-15T07:00:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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 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;
+    }
+
+    fsm_step = 1;
+    while (1)
+    {
+        if(fsm_step == 2)
+        {
+            break;
+        }
+        rt_thread_mdelay(10);
+    }
+
path: components/drivers/audio/utest/tc_audio_main.c
user: login: Copilot
body: [nitpick] The busy-wait loop here could be improved by using an event-driven mechanism or proper signaling to avoid unnecessary CPU usage during test execution.
```suggestion
    rt_sem_t fsm_sem = rt_sem_create("fsm_sem", 0, RT_IPC_FLAG_FIFO);
    if (fsm_sem == RT_NULL)
    {
        rt_kprintf("Failed to create semaphore\n");
        uassert_true(0);
        goto __exit;
    }

    /* Wait for fsm_step to be updated to 2 */
    rt_sem_take(fsm_sem, RT_WAITING_FOREVER);

    /* Clean up semaphore */
    rt_sem_delete(fsm_sem);
```
created_at: 2025-05-16T01:28:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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);
+    fsm_step = 1;
+
+    while (1)
+    {
+        if(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);
+            fsm_step = 3;
+            break;
+        }
+        if(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)
+{
path: components/drivers/audio/utest/tc_audio_drv_mic.c
user: login: Copilot
body: [nitpick] Consider replacing the busy-wait loop with an event-driven or signaling mechanism to improve efficiency in the test thread.
```suggestion
        rt_sem_take(fsm_step_sem, RT_WAITING_FOREVER); // Wait for state change signal
        if (fsm_step == 2)
        {
            /* Send 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);
            fsm_step = 3;
        }
        else if (fsm_step == 4)
        {
            rt_audio_rx_done(&mic_dev.audio, &mic_dev.rx_fifo[RX_DMA_BLOCK_SIZE], RX_DMA_BLOCK_SIZE);
        }
    }
}

static void thread_simulate_intr_create(void)
{
    fsm_step_sem = rt_sem_create("fsm_step_sem", 0, RT_IPC_FLAG_FIFO); // Create semaphore
    if (fsm_step_sem == RT_NULL)
    {
        rt_kprintf("Error: Failed to create semaphore!\n");
        return;
    }
```
created_at: 2025-05-16T01:28:42Z
author_association: CONTRIBUTOR

PR comments:
user: login: Rbb666
created_at: 2025-05-10T05:16:44Z
body: @1078249029 这个PR准备好了吗？
author_association: MEMBER
user: login: 1078249029
created_at: 2025-05-10T09:21:06Z
body: > @1078249029 这个PR准备好了吗？

准备好了，麻烦您review下。
但是ci过不去，应该是utest组件下没有分配reviewer的问题？
author_association: CONTRIBUTOR
user: login: 1078249029
created_at: 2025-05-12T01:04:12Z
body: @unicornx 老师，上次跟您说的音频测试框架已经写好了，您有空的话也请review？
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-12T02:45:34Z
body: ## 📌 Code Review Assignment

### 🏷️ Tag: bsp_cvitek
**Path:** `bsp/cvitek`  
**Reviewers:** @unicornx   
<details>
<summary><b>Changed Files</b> (Click to expand)</summary>

- bsp/cvitek/cv18xx_aarch64/.config
- bsp/cvitek/cv18xx_aarch64/rtconfig.h
- bsp/cvitek/cv18xx_aarch64/rtconfig.py

</details>

---
### 📊 Current Review Status (Last Updated: 2025-05-15 12:43 UTC)
- ⌛ **@unicornx** Pending Review
---
### 📝 Review Instructions

1. **维护者可以通过单击此处来刷新审查状态:** [🔄 刷新状态](https://github.com/RT-Thread/rt-thread/actions/runs/15045243531)
   **Maintainers can refresh the review status by clicking here:** [🔄 Refresh Status](https://github.com/RT-Thread/rt-thread/actions/runs/15045243531)

2. **确认审核通过后评论 `LGTM/lgtm`**
   **Comment `LGTM/lgtm` after confirming approval**

3. **PR合并前需至少一位维护者确认**
   **PR must be confirmed by at least one maintainer before merging**

> ℹ️ **刷新CI状态操作需要具备仓库写入权限。**
> ℹ️ **Refresh CI status operation requires repository Write permission.**
author_association: NONE
user: login: 1078249029
created_at: 2025-05-15T04:01:17Z
body: 
> * 只在内部使用的全局变量都要 static，好像没有改全。

两个flag变量是用于同步驱动与应用层状态机的状态的，只能放在全局。其他方法例如使用全局信号量或者手动延时的效果都不是很好，前者既麻烦又与其他测试模块有依赖，后者太死板，所以这里就没有改。

author_association: CONTRIBUTOR
user: login: unicornx
created_at: 2025-05-15T07:05:29Z
body: 另外这个 PR 的状态为何还是 Draft，可以改成正常的了。
author_association: CONTRIBUTOR