论坛热贴 RT-Thread音频驱动开发(一),2024物联网嵌入式开发开发现状分析

113 阅读11分钟

72    if (info == RT_NULL)
73        goto __exit;
74
75    if (read(fd, &(info->header), sizeof(struct RIFF_HEADER_DEF)) <= 0)
76        goto __exit;
77    if (read(fd, &(info->fmt_block),  sizeof(struct FMT_BLOCK_DEF)) <= 0)
78        goto __exit;
79    if (read(fd, &(info->data_block), sizeof(struct DATA_BLOCK_DEF)) <= 0)
80        goto __exit;
81
82    rt_kprintf("wav information:\n");
83    rt_kprintf("samplerate %d\n", info->fmt_block.wav_format.SamplesPerSec);
84    rt_kprintf("channel %d\n", info->fmt_block.wav_format.Channels);
85
86    /* 根据设备名称查找 Audio 设备,获取设备句柄 /
87    snd_dev = rt_device_find(SOUND_DEVICE_NAME);
88
89    /
 以只写方式打开 Audio 播放设备 /
90    rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY);
91
92    /
 设置采样率、通道、采样位数等音频参数信息 /
93    caps.main_type               = AUDIO_TYPE_OUTPUT;                           /
 输出类型(播放设备 )/
94    caps.sub_type                = AUDIO_DSP_PARAM;                             /
 设置所有音频参数信息 /
95    caps.udata.config.samplerate = info->fmt_block.wav_format.SamplesPerSec;    /
 采样率 /
96    caps.udata.config.channels   = info->fmt_block.wav_format.Channels;         /
 采样通道 /
97    caps.udata.config.samplebits = 16;                                          /
 采样位数 /
98    rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps);
99
100    while (1)
101    {
102        int length;
103
104        /
 从文件系统读取 wav 文件的音频数据 /
105        length = read(fd, buffer, BUFSZ);
106
107        if (length <= 0)
108            break;
109
110        /
 向 Audio 设备写入音频数据 /
111        rt_device_write(snd_dev, 0, buffer, length);
112    }
113
114    /
 关闭 Audio 设备 */
115    rt_device_close(snd_dev);
116
117__exit:
118
119    if (fd >= 0)
120        close(fd);
121
122    if (buffer)
123        rt_free(buffer);
124
125    if (info)
126        rt_free(info);
127
128    return 0;
129}
130MSH_CMD_EXPORT(wavplay_sample,  play wav file);`


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


1、`#define SOUND_DEVICE_NAME "sound0"`: 首先定义播放的驱动


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


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


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


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


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


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


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


![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/a3a899073702429fa4a894b72a61fef9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1771259289&x-signature=twgTbDkLQPunfk6ipYkan7Ptfgg%3D)


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


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




1#include "drv_sound.h" 2#include "drv_tina.h" 3#include "drivers/audio.h" 4 5#define DBG_TAG "drv_sound" 6#define DBG_LVL DBG_LOG 7#define DBG_COLOR 8#include <rtdbg.h> 9 10#define TX_DMA_FIFO_SIZE (2048) 11 12struct temp_sound 13{ 14    struct rt_audio_device device; 15    struct rt_audio_configure replay_config; 16    int volume; 17    rt_uint8_t *tx_fifo; 18}; 19 20static rt_err_t getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps) 21{ 22    struct temp_sound *sound = RT_NULL; 23 24    RT_ASSERT(audio != RT_NULL); 25    sound = (struct temp_sound *)audio->parent.user_data; (void)sound; 26 27    return RT_EOK; 28} 29 30static rt_err_t configure(struct rt_audio_device *audio, struct rt_audio_caps *caps) 31{ 32    struct temp_sound *sound = RT_NULL; 33 34    RT_ASSERT(audio != RT_NULL); 35    sound = (struct temp_sound *)audio->parent.user_data; (void)sound; 36 37    return RT_EOK; 38} 39 40static rt_err_t init(struct rt_audio_device *audio) 41{ 42    struct temp_sound *sound = RT_NULL; 43 44    RT_ASSERT(audio != RT_NULL); 45    sound = (struct temp_sound *)audio->parent.user_data; (void)sound; 46 47    return RT_EOK; 48} 49 50static rt_err_t start(struct rt_audio_device *audio, int stream) 51{ 52    struct temp_sound *sound = RT_NULL; 53 54    RT_ASSERT(audio != RT_NULL); 55    sound = (struct temp_sound *)audio->parent.user_data; (void)sound; 56 57    return RT_EOK; 58} 59 60static rt_err_t stop(struct rt_audio_device *audio, int stream) 61{ 62    struct temp_sound *sound = RT_NULL; 63 64    RT_ASSERT(audio != RT_NULL); 65    sound = (struct temp_sound *)audio->parent.user_data; (void)sound;   66 67    return RT_EOK; 68} 69 70rt_size_t transmit(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size) 71{ 72    struct temp_sound *sound = RT_NULL; 73 74    RT_ASSERT(audio != RT_NULL); 75    sound = (struct temp_sound *)audio->parent.user_data; (void)sound; 76 77    return size; 78} 79 80static void buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info) 81{ 82    struct temp_sound *sound = RT_NULL; 83 84    RT_ASSERT(audio != RT_NULL); 85    sound = (struct temp_sound *)audio->parent.user_data; 86 87    /** 88     *               TX_FIFO 89     * +----------------+----------------+ 90     * |     block1     |     block2     | 91     * +----------------+----------------+ 92     *  \  block_size  / 93     */ 94    info->buffer      = sound->tx_fifo; 95    info->total_size  = TX_DMA_FIFO_SIZE; 96    info->block_size  = TX_DMA_FIFO_SIZE / 2; 97    info->block_count = 2; 98} 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 音频,当然由于没有硬件相关代码是不会出声音的。


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/a3a899073702429fa4a894b72a61fef9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1771259289&x-signature=twgTbDkLQPunfk6ipYkan7Ptfgg%3D)


**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;

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

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

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

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