2024年最全论坛热贴 RT-Thread音频驱动开发(一),5214页PDF的进阶架构师学习笔记

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

99
100static struct rt_audio_ops ops =
101{
102    .getcaps     = getcaps,
103    .configure   = configure,
104    .init        = init,
105    .start       = start,
106    .stop        = stop,
107    .transmit    = transmit,
108    .buffer_info = buffer_info,
109};
110
111static int rt_hw_sound_init(void)
112{
113    rt_uint8_t tx_fifo = RT_NULL;
114    static struct temp_sound sound = {0};
115
116    /
 分配 DMA 搬运 buffer /
117    tx_fifo = rt_calloc(1, TX_DMA_FIFO_SIZE);
118    if(tx_fifo == RT_NULL)
119    {
120        return -RT_ENOMEM;
121    }
122
123    sound.tx_fifo = tx_fifo;
124
125    /
 注册声卡放音驱动 */
126    sound.device.ops = &ops;
127    rt_audio_register(&sound.device, “sound0”, RT_DEVICE_FLAG_WRONLY, &sound);
128
129    return RT_EOK;
130}
131INIT_DEVICE_EXPORT(rt_hw_sound_init);`


上面是整个audio驱动的架子,没有如何和硬件相关的代码,但是添加到项目中,是可以在shell中使用list\_device命令看到 sound0 驱动的。如果我们将第一章中的代码配合的话是可以播放 wav 音频,当然由于没有硬件相关代码是不会出声音的。


我们先来分析下这段代码:


1、rt\_hw\_sound\_init 函数是驱动的入口,用于注册audio框架,在这个里面,我们分配了 audio dma 需要的buffer,并将 实现的音频相关的ops注册到sound0音频设备中。调用这个函数后就可以在list\_device中看到sound0驱动了。


2、那么接下来有疑问了struct rt\_audio\_ops ops这个结构体中的几个函数分别是干什么的如何编写。那么笔者给大家慢慢道来!


3、由于 audio 相关的配置和设置的参数比较多,所以这里我们将配置和获取参数分别分成了2个 ops 函数来实现,分别为 getcaps 和 configure。getcaps 用于获取 audio 的能力,例如硬件通道数,当前采样率,采样深度,音量,configure 函数用于实现设置通道数,当前采样率,采样深度,音量。


4、init ops函数,主要用于实现 芯片的 i2s(与外部codec进行音频数据通信) i2c(控制外部codec的采样率,mute脚,当然部分codec内置的是不需要这个的,还有部分比较低端一点的codec也是不会有i2c控制的,这个根据大家外部接的芯片来确定),当然还需要配置 dma 和 dma 中端。还有控制 mute 的gpio引脚。


5、start ops 函数主要是用于启动 dma 和 关mute 相关的处理的。


6、stop ops 函数主要是用于关闭 dma 和 开mute 相关的处理的。


7、transmit 主要是用于触发数据的搬运,为什么说是触发搬运呢?其实上层代码向音频设备写入音频数据并不会直接写入到驱动中,也就是不会直接调用transmit这个底层函数用于将缓冲区的数据传递到 dma 的buffer中,那么transmit会在什么时候调用呢?上面的驱动并不会触发驱动的搬运也就是这个函数,其实我们可以看到 audio 框架中有一个函数 rt\_audio\_tx\_complete(&sound->device); 这个函数就是用于通知搬运的,那么我们再来梳理下这个段逻辑:


●上层应用调用 rt\_device\_write 函数向 audio 写入数据,框架层会将写入的数据缓存到内部的一个buffer(静态内存池中的一个节点,默认配置为2k数据)


●上层写入超过2k的数据会阻塞等待


●第一次使用 rt\_device\_write 会调用 start ops函数启动 dma搬运,在i2s的dma中断(半空和满中断服务函数中)调用 rt\_audio\_tx\_complete 函数


●rt\_audio\_tx\_complete 表示 dma的 数据搬运完毕了,需要填充下一次的音频数据,这个函数会调用 transmit ops,但是如果是i2s dma循环搬运的数据,dma会自动搬运数据,所以并不需要使用 transmit ops来将音频缓冲区的数据 copy 到驱动的dma中,那么transmit 有什么用呢?第一在部分没有dma循环搬运的芯片上我们可以利用这个函数触发下一个dma搬运或者是cpu搬运,第二这个地方可以用来刷cache的!


8、buffer\_info 用于告诉audio框架你的音频驱动缓冲区有多大,有几块,这样上层通过 transmit ops函数的时候就知道给你多少字节数据了!


看了上面的分析我相信你应该了解了基本原理了,和编写方法了。但是这个驱动还是不能出声音,那么我们得想办法实现一个驱动,由于笔者的硬件和大家都不一样,那么小弟想了一个办法。


那就是将音频缓存到文件中,这里我们来做一个虚拟音频驱动,这个驱动并不会出声音,但是会将数据保存层pcm文件。pcm的相关参数和你播放的wav一样这样我们可以用电脑来播放了。这样就避免硬件的差异化。


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi94VGlhRWNxUGJESHBvaWNodjZqaGxYT0wzeXNIVWJrV1pFSlVxQVV6aGNmUTk2Qk5lbjJocVMwaktNQjYwVEp5TjB3UXVDUWljTWtPaWFsR3V6aDhQRmpoekEvNjQwP3d4X2ZtdD1naWY?x-oss-process=image/format,png)


**4. 音频虚拟驱动编写**


还是废话不多说,直接上代码。



1/* 2* File: drv_virtual.c 3* 4* COPYRIGHT (C) 2012-2019, Shanghai Real-Thread Technology Co., Ltd 5*/ 6 7#include "drv_virtual.h" 8#include "dfs.h" 9#include "dfs_posix.h" 10 11#define DBG_TAG "drv_virtual" 12#define DBG_LVL DBG_LOG 13#define DBG_COLOR 14#include <rtdbg.h> 15 16#define TX_DMA_FIFO_SIZE (2048) 17 18struct tina_sound 19{ 20    struct rt_audio_device device; 21    struct rt_audio_configure replay_config; 22    int volume; 23    rt_uint8_t *tx_fifo; 24    int fd; 25    struct rt_thread thread; 26    int endflag; 27}; 28 29static rt_err_t getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps) 30{ 31    rt_err_t ret = RT_EOK; 32    struct tina_sound *sound = RT_NULL; 33 34    RT_ASSERT(audio != RT_NULL); 35    sound = (struct tina_sound *)audio->parent.user_data; (void)sound; 36 37    switch(caps->main_type) 38    { 39    case AUDIO_TYPE_QUERY: 40    { 41        switch (caps->sub_type) 42        { 43        case AUDIO_TYPE_QUERY: 44            caps->udata.mask = AUDIO_TYPE_OUTPUT | AUDIO_TYPE_MIXER; 45            break; 46 47        default: 48            ret = -RT_ERROR; 49            break; 50        } 51 52        break; 53    } 54 55    case AUDIO_TYPE_OUTPUT: 56    { 57        switch(caps->sub_type) 58        { 59        case AUDIO_DSP_PARAM: 60            caps->udata.config.channels   = sound->replay_config.channels; 61            caps->udata.config.samplebits = sound->replay_config.samplebits; 62            caps->udata.config.samplerate = sound->replay_config.samplerate; 63            break; 64 65        default: 66            ret = -RT_ERROR; 67            break; 68        } 69 70        break; 71    } 72 73    case AUDIO_TYPE_MIXER: 74    { 75        switch (caps->sub_type) 76        { 77        case AUDIO_MIXER_QUERY: 78            caps->udata.mask = AUDIO_MIXER_VOLUME | AUDIO_MIXER_LINE; 79            break; 80 81        case AUDIO_MIXER_VOLUME: 82            caps->udata.value = sound->volume; 83            break; 84 85        case AUDIO_MIXER_LINE: 86            break; 87 88        default: 89            ret = -RT_ERROR; 90            break; 91        } 92 93        break; 94    } 95 96    default: 97        ret = -RT_ERROR; 98        break; 99    } 100 101    return ret; 102} 103 104static rt_err_t configure(struct rt_audio_device *audio, struct rt_audio_caps *caps) 105{ 106    rt_err_t ret = RT_EOK; 107    struct tina_sound *sound = RT_NULL; 108 109    RT_ASSERT(audio != RT_NULL); 110    sound = (struct tina_sound *)audio->parent.user_data; (void)sound; 111 112    switch(caps->main_type) 113    { 114    case AUDIO_TYPE_MIXER: 115    { 116        switch(caps->sub_type) 117        { 118        case AUDIO_MIXER_VOLUME: 119        { 120            int volume = caps->udata.value; 121            sound->volume = volume; 122            break; 123        } 124 125        default: 126            ret = -RT_ERROR; 127            break; 128        } 129 130        break; 131    } 132 133    case AUDIO_TYPE_OUTPUT: 134    { 135        switch(caps->sub_type) 136        { 137        case AUDIO_DSP_PARAM: 138        { 139            int samplerate; 140 141            samplerate = caps->udata.config.samplerate; 142            sound->replay_config.samplerate = samplerate; 143            LOG_I("set samplerate = %d", samplerate); 144            break; 145        } 146 147        case AUDIO_DSP_SAMPLERATE: 148        { 149            int samplerate; 150 151            samplerate = caps->udata.config.samplerate; 152            sound->replay_config.samplerate = samplerate; 153            LOG_I("set samplerate = %d", samplerate); 154            break; 155        } 156 157        case AUDIO_DSP_CHANNELS: 158        { 159            break; 160        } 161 162        default: 163            break; 164        } 165 166        break; 167    } 168 169    default: 170        break; 171    } 172 173    return ret; 174} 175 176static void virtualplay(void *p) 177{ 178    struct tina_sound *sound = (struct tina_sound *)p; (void)sound; 179 180    while(1) 181    { 182        /* tick = TX_DMA_FIFO_SIZE/2 * 1000ms / 44100 / 4 ≈ 5.8 */ 183        rt_thread_mdelay(6); 184        rt_audio_tx_complete(&sound->device); 185 186        if(sound->endflag == 1) 187        { 188            break; 189        } 190    } 191} 192 193static int thread_stack[1024] = {0}; 194 195static rt_err_t init(struct rt_audio_device *audio) 196{ 197    struct tina_sound *sound = RT_NULL; 198 199    RT_ASSERT(audio != RT_NULL); 200    sound = (struct tina_sound *)audio->parent.user_data; (void)sound; 201 202    LOG_I("sound init"); 203 204    return RT_EOK; 205} 206 207static rt_err_t start(struct rt_audio_device *audio, int stream) 208{ 209    struct tina_sound *sound = RT_NULL; 210    rt_err_t ret = RT_EOK; 211 212    RT_ASSERT(audio != RT_NULL); 213    sound = (struct tina_sound *)audio->parent.user_data; (void)sound; 214 215    LOG_I("sound start"); 216 217    ret = rt_thread_init(&sound->thread, "virtual", virtualplay, sound, &thread_stack, sizeof(thread_stack), 1, 10); 218    if(ret != RT_EOK) 219    { 220        LOG_E("virtual play thread init failed"); 221        return (-RT_ERROR); 222    } 223    rt_thread_startup(&sound->thread); 224 225    sound->endflag = 0; 226 227    sound->fd = open("/tmp/virtual.pcm", O_CREAT | O_RDWR, 0666); 228 229    return RT_EOK; 230} 231 232static rt_err_t stop(struct rt_audio_device *audio, int stream) 233{ 234    struct tina_sound *sound = RT_NULL; 235 236    RT_ASSERT(audio != RT_NULL); 237    sound = (struct tina_sound *)audio->parent.user_data; (void)sound; 238 239    LOG_I("sound stop");   240 241    sound->endflag = 1; 242 243    close(sound->fd); 244    sound->fd = -1; 245 246    return RT_EOK; 247} 248 249rt_size_t transmit(struct rt_audio_device *audio, const void *wb, void *rb, rt_size_t size) 250{ 251    struct tina_sound *sound = RT_NULL; 252 253    RT_ASSERT(audio != RT_NULL); 254    sound = (struct tina_sound *)audio->parent.user_data; (void)sound; 255 256    return write(sound->fd, wb, size); 257} 258 259static void buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info) 260{ 261    struct tina_sound *sound = RT_NULL; 262 263    RT_ASSERT(audio != RT_NULL); 264    sound = (struct tina_sound *)audio->parent.user_data; 265 266    /** 267     *               TX_FIFO 268     * +----------------+----------------+ 269     * |     block1     |     block2     | 270     * +----------------+----------------+ 271     *  \  block_size  / 272     */ 273    info->buffer      = sound->tx_fifo; 274    info->total_size  = TX_DMA_FIFO_SIZE; 275    info->block_size  = TX_DMA_FIFO_SIZE / 2; 276    info->block_count = 2; 277} 278 279static struct rt_audio_ops ops = 280{ 281    .getcaps     = getcaps, 282    .configure   = configure, 283    .init        = init, 284    .start       = start, 285    .stop        = stop, 286    .transmit    = transmit, 287    .buffer_info = buffer_info, 288}; 289 290static int rt_hw_sound_init(void) 291{ 292    rt_uint8_t *tx_fifo = RT_NULL; 293    static struct tina_sound sound = {0}; 294 295    /* 分配 DMA 搬运 buffer */ 296    tx_fifo = rt_calloc(1, TX_DMA_FIFO_SIZE); 297    if(tx_fifo == RT_NULL) 298    { 299        return -RT_ENOMEM; 300    } 301 302    sound.tx_fifo = tx_fifo; 303 304    /* 配置 DSP 参数 */ 305    { 306        sound.replay_config.samplerate = 44100; 307        sound.replay_config.channels   = 2; 308        sound.replay_config.samplebits = 16; 309        sound.volume                   = 60; 310        sound.fd                       = -1; 311        sound.endflag                  = 0; 312    } 313 314    /* 注册声卡放音驱动 */ 315    sound.device.ops = &ops; 316    rt_audio_register(&sound.device, "sound0", RT_DEVICE_FLAG_WRONLY, &sound); 317 318    return RT_EOK; 319} 320INIT_DEVICE_EXPORT(rt_hw_sound_init);


根据第二部分的分析,相信你也能看懂这部分代码,这个驱动的根本思想是利用 virtualplay 线程模拟 i2s dma进行数据的自动搬运!


最终文件会保存到 `/tmp/virtual.pcm` 中,注意这里有点是 virtualplay 函数延时了6ms是为了模拟dma buffer中 1k 数据搬运(播放)需要消耗的时间,`tick = TX_DMA_FIFO_SIZE/2 * 1000ms / 44100 / 4 ≈ 5.8ms`  所以我们得要求文件写入比较快,这里笔者利用了ramfs来实现文件系统,经过实际测试如果写入sd卡或者flash会非常的慢,所以还是建议使用 ramfs 保证 20Mbytes 以上的大小,当然可以使用 qemu 来测试~


那么小弟就分析到这里,更加多的信息请加入 qq 群: 690181735 讨论,有更多更专业RT-Thread audio相关资料等着你!


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy94VGlhRWNxUGJESHBmOWN1dmxibzlETm9UYzBrM1FPWmliOHBrUXJDQlNPRGRQUzd0QWt2WjZWekd3eUxjdG03Uno1bzVhd0MzUXhJMzB6aFBIeThWU3BnLzY0MD93eF9mbXQ9cG5n?x-oss-process=image/format,png)


**RT-Thread线上/下活动**  



**1、****【**RT-Thread开发者大会报名】深圳站马上开始!2019年RT-Thread开发者大会已经登入了成都、上海,马上将去到我们最后一站深圳,大会内容包含:RT-Thread在中高端智能领域的应用、一站式RTT开发工具、打造IoT极速开发模式等干货演讲,期待您的参与!


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy94VGlhRWNxUGJESHJQaWMwdlEwbGlhTktETUJWYnFkUEg3NWFwTHY4c3JvUm4yT2draGliZDBIOFNyRXhYVWViQzZJenpjUGliYk93d21nRzNTZmhKYjRIa1J3LzY0MD93eF9mbXQ9cG5n?x-oss-process=image/format,png)  



立即报名


2、**【**RT-Thread音频大会】12月14日,LiveVideoStack联合RT-Thread在LiveVideoStackCon音视频技术大会推出嵌入式与音频开发专题会议,本专题将讨论音频设备开发方案、实战经验,涉及智能音箱、TWS耳机等产品的技术实践。


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy94VGlhRWNxUGJESHJIUTJCcWtGbERGdE1raWJpY3dTcjVwOGljVG52THdwY09nMzF2WE9XdmN5WlVJWEEzbDJTYXRYRUdtODhXbUk1WUJIU0lGT0RIZU9iZmcvNjQwP3d4X2ZtdD1wbmc?x-oss-process=image/format,png)


长按识别二维码扫码报名  



**报名链接:**http://sz2019.livevideostack.com/track/106


**#题外话#** 喜欢RT-Thread不要忘了在GitHub上留下你的![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy94VGlhRWNxUGJESG9XZHFEYVhhaWNmTWJSQTNwdjU0RTloMGpNcGswekxsVjRGVndFRFU5S2NjY1ZNRWliOURJUGpsSm9weE5YYndJS3F2bWdOUXFLNkRIUS82NDA_d3hfZm10PXBuZw?x-oss-process=image/format,png)STAR![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy94VGlhRWNxUGJESG9XZHFEYVhhaWNmTWJSQTNwdjU0RTloMGpNcGswekxsVjRGVndFRFU5S2NjY1ZNRWliOURJUGpsSm9weE5YYndJS3F2bWdOUXFLNkRIUS82NDA_d3hfZm10PXBuZw?x-oss-process=image/format,png)哦,你的star对我们来说非常重要!链接地址:https://github.com/RT-Thread/rt-thread


你可以添加微信17775982065为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy94VGlhRWNxUGJESHJZZmxLaFFwVzNNeENRZ05IZWZBaEk2d0tyWWw2cmliS1F0ZGtBTVFRdGtxcktQaWM5RExPVlk0MG85MVU1djlSM2MyVDU4VXBYTXFUUS82NDA_d3hfZm10PWpwZWc?x-oss-process=image/format,png)


**RT-Thread**


  
让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。


长按二维码,关注我们




**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
![img](https://img-blog.csdnimg.cn/img_convert/6a0a242e310352330a958c36ead10d46.png)
![img](https://img-blog.csdnimg.cn/img_convert/8c363febc491d24bd562382ebb7eca03.png)

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**

**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

DA_d3hfZm10PWpwZWc?x-oss-process=image/format,png)


**RT-Thread**


  
让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。


长按二维码,关注我们




**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
[外链图片转存中...(img-iTGBorp6-1715638046641)]
[外链图片转存中...(img-K0SsoFpx-1715638046643)]

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**

**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值