天天看点

论坛热贴 | RT-Thread音频驱动开发(一)

今天主要是讲解音频虚拟驱动来分析驱动的编写。但是这篇文章并不会讲解关于 RT-Thread IO Device 框架相关内容,如果有对这部分不太熟悉的人请先看这个链接了解基本概念,RT-Thread I/O 设备模型:https://www.rt-thread.org/document/site/programming-manual/device/device(请复制以上链接至外部浏览器打开)

论坛热贴 | RT-Thread音频驱动开发(一)

1. RT-Thread 音频框架图

RT-Thread的音频分成了4个部分,但是我们只要关心上层提供的api和底层驱动需要实现的ops接口就可以了。

论坛热贴 | RT-Thread音频驱动开发(一)
论坛热贴 | RT-Thread音频驱动开发(一)

2. 如何使用 Audio 驱动

在写驱动之前,我们首先得知道如何测试自己的驱动,所以我们首先需要了解下 RT-Thread 系统中是如何播放音乐的!

这段代码主要是播放 wav(pcm) 的音频。那么我们来分析下上面一段代码,这段播放一段音频数据的主要步骤如下:

1、<code>#define SOUND_DEVICE_NAME "sound0"</code>: 首先定义播放的驱动

2、<code>fd = open(argv[1], O_WRONLY);</code>: 用于打开音频文件,这个没什么分析的3、<code>snd_dev = rt_device_find(SOUND_DEVICE_NAME);</code>: 首先查找 Audio 设备获取设备句柄

4、<code>rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY);</code>: 以只写方式打开 Audio 设备,也就是打开放音设备

5、<code>rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &amp;caps);</code>: 置音频参数信息(采样率、通道等)

6、<code>length = read(fd, buffer, BUFSZ);</code>: 读取音频文件的数据

7、<code>rt_device_write(snd_dev, 0, buffer, length);</code>: 向驱动写入音频文件数据,写入后就会出声音,写入的数据为pcm数据,音频相关格式是步骤5中配置的参数 8、<code>rt_device_close(snd_dev);</code>: 播放完成,关闭设备

这样看起来是不是非常简单,将这段代码添加到你的代码中进行编译下载,就可以了放音乐了,当然只能播放wav格式的音频。

这个时候肯定有大佬已经反应过来了,我bsp连个audio驱动都没有,脑补音乐吗!大佬不要心急,小弟这就给你把驱动慢慢道来~

论坛热贴 | RT-Thread音频驱动开发(一)

3. 编写音频虚拟驱动

上来废话不多说,直接上干货:

上面是整个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(&amp;sound-&gt;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一样这样我们可以用电脑来播放了。这样就避免硬件的差异化。

论坛热贴 | RT-Thread音频驱动开发(一)

4. 音频虚拟驱动编写

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

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

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

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

论坛热贴 | RT-Thread音频驱动开发(一)
论坛热贴 | RT-Thread音频驱动开发(一)

RT-Thread

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

长按二维码,关注我们