Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
AUDIO
【Audio】2. 音频虚拟驱动
发布于 2019-12-02 23:40:51 浏览:3445
订阅该版
[tocm] 之前更新过一篇音频驱动相关的文档,也是当时兴趣来了,那么今天我详细整理下这块,拆分下分析 RT-Thread 音频驱动相关的内容。 今天主要是讲解音频虚拟驱动来分析驱动的编写。但是这篇文章并不会讲解关于 RT-Thread IO Device 框架相关内容,如果有对这部分不太熟悉的人请先看这个链接了解基本概念: [RT-Thread I/O 设备模型](https://www.rt-thread.org/document/site/programming-manual/device/device/) **1. 如何使用 Audio 驱动** 在写驱动之前,我们首先得知道如何测试自己的驱动对吧!所以这里我们首先了解下如何播放音乐! ```c #include
#include
#include
#define BUFSZ 1024 #define SOUND_DEVICE_NAME "sound0" /* Audio 设备名称 */ static rt_device_t snd_dev; /* Audio 设备句柄 */ struct RIFF_HEADER_DEF { char riff_id[4]; // 'R','I','F','F' uint32_t riff_size; char riff_format[4]; // 'W','A','V','E' }; struct WAVE_FORMAT_DEF { uint16_t FormatTag; uint16_t Channels; uint32_t SamplesPerSec; uint32_t AvgBytesPerSec; uint16_t BlockAlign; uint16_t BitsPerSample; }; struct FMT_BLOCK_DEF { char fmt_id[4]; // 'f','m','t',' ' uint32_t fmt_size; struct WAVE_FORMAT_DEF wav_format; }; struct DATA_BLOCK_DEF { char data_id[4]; // 'R','I','F','F' uint32_t data_size; }; struct wav_info { struct RIFF_HEADER_DEF header; struct FMT_BLOCK_DEF fmt_block; struct DATA_BLOCK_DEF data_block; }; int wavplay_sample(int argc, char **argv) { int fd = -1; uint8_t *buffer = NULL; struct wav_info *info = NULL; struct rt_audio_caps caps = {0}; if (argc != 2) { rt_kprintf("Usage:
"); rt_kprintf("wavplay_sample song.wav
"); return 0; } fd = open(argv[1], O_WRONLY); if (fd < 0) { rt_kprintf("open file failed!
"); goto __exit; } buffer = rt_malloc(BUFSZ); if (buffer == RT_NULL) goto __exit; info = (struct wav_info *) rt_malloc(sizeof * info); if (info == RT_NULL) goto __exit; if (read(fd, &(info->header), sizeof(struct RIFF_HEADER_DEF)) <= 0) goto __exit; if (read(fd, &(info->fmt_block), sizeof(struct FMT_BLOCK_DEF)) <= 0) goto __exit; if (read(fd, &(info->data_block), sizeof(struct DATA_BLOCK_DEF)) <= 0) goto __exit; rt_kprintf("wav information:
"); rt_kprintf("samplerate %d
", info->fmt_block.wav_format.SamplesPerSec); rt_kprintf("channel %d
", info->fmt_block.wav_format.Channels); /* 根据设备名称查找 Audio 设备,获取设备句柄 */ snd_dev = rt_device_find(SOUND_DEVICE_NAME); /* 以只写方式打开 Audio 播放设备 */ rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY); /* 设置采样率、通道、采样位数等音频参数信息 */ caps.main_type = AUDIO_TYPE_OUTPUT; /* 输出类型(播放设备 )*/ caps.sub_type = AUDIO_DSP_PARAM; /* 设置所有音频参数信息 */ caps.udata.config.samplerate = info->fmt_block.wav_format.SamplesPerSec; /* 采样率 */ caps.udata.config.channels = info->fmt_block.wav_format.Channels; /* 采样通道 */ caps.udata.config.samplebits = 16; /* 采样位数 */ rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps); while (1) { int length; /* 从文件系统读取 wav 文件的音频数据 */ length = read(fd, buffer, BUFSZ); if (length <= 0) break; /* 向 Audio 设备写入音频数据 */ rt_device_write(snd_dev, 0, buffer, length); } /* 关闭 Audio 设备 */ rt_device_close(snd_dev); __exit: if (fd >= 0) close(fd); if (buffer) rt_free(buffer); if (info) rt_free(info); return 0; } MSH_CMD_EXPORT(wavplay_sample, play wav file); ``` 播放一段音频数据的主要步骤如下: 我们来分析下这段代码,首先从 wavplay_sample 函数中进行分析: - `#define SOUND_DEVICE_NAME "sound0"` 首先定义播放的驱动 - `fd = open(argv[1], O_WRONLY);` 用于打开音频文件,这个没什么分析的 - `snd_dev = rt_device_find(SOUND_DEVICE_NAME);` 首先查找 Audio 设备获取设备句柄 - `rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY);` 以只写方式打开 Audio 设备,也就是打开放音设备 - `rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps);` 设置音频参数信息(采样率、通道等) - `length = read(fd, buffer, BUFSZ);` 解码音频文件的数据 - `rt_device_write(snd_dev, 0, buffer, length);` 写入音频文件数据 - `rt_device_close(snd_dev);` 播放完成,关闭设备 这样看起来是不是非常简单,将这段代码添加到你的代码中进行编译下载,就可以了放音乐了,当然只能播放wav格式的音频。 这个时候肯定有大佬已经反应过来了,你这个驱动都没有放毛线音乐啊!大佬不要心急,小弟这就给你把驱动慢慢道来~ ## 2. 编写音频虚拟驱动 中 [上一篇关于音频的文档中](https://club.rt-thread.org/ask/question/422550.html) 小弟我已经分析了 audio 驱动的框架,这里我们接着这个驱动的架子说下框架如何实现。 ```c #include "drv_sound.h" #include "drv_tina.h" #include "drivers/audio.h" #define DBG_TAG "drv_sound" #define DBG_LVL DBG_LOG #define DBG_COLOR #include
#define TX_DMA_FIFO_SIZE (2048) struct temp_sound { struct rt_audio_device device; struct rt_audio_configure replay_config; int volume; rt_uint8_t *tx_fifo; }; static rt_err_t getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps) { struct temp_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct temp_sound *)audio->parent.user_data; (void)sound; return RT_EOK; } static rt_err_t configure(struct rt_audio_device *audio, struct rt_audio_caps *caps) { struct temp_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct temp_sound *)audio->parent.user_data; (void)sound; return RT_EOK; } static rt_err_t init(struct rt_audio_device *audio) { struct temp_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct temp_sound *)audio->parent.user_data; (void)sound; return RT_EOK; } static rt_err_t start(struct rt_audio_device *audio, int stream) { struct temp_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct temp_sound *)audio->parent.user_data; (void)sound; return RT_EOK; } static rt_err_t stop(struct rt_audio_device *audio, int stream) { struct temp_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct temp_sound *)audio->parent.user_data; (void)sound; return RT_EOK; } rt_size_t transmit(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size) { struct temp_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct temp_sound *)audio->parent.user_data; (void)sound; return size; } static void buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info) { struct temp_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct temp_sound *)audio->parent.user_data; /** * TX_FIFO * +----------------+----------------+ * | block1 | block2 | * +----------------+----------------+ * \ block_size / */ info->buffer = sound->tx_fifo; info->total_size = TX_DMA_FIFO_SIZE; info->block_size = TX_DMA_FIFO_SIZE / 2; info->block_count = 2; } static struct rt_audio_ops ops = { .getcaps = getcaps, .configure = configure, .init = init, .start = start, .stop = stop, .transmit = transmit, .buffer_info = buffer_info, }; static int rt_hw_sound_init(void) { rt_uint8_t *tx_fifo = RT_NULL; static struct temp_sound sound = {0}; /* 分配 DMA 搬运 buffer */ tx_fifo = rt_calloc(1, TX_DMA_FIFO_SIZE); if(tx_fifo == RT_NULL) { return -RT_ENOMEM; } sound.tx_fifo = tx_fifo; /* 注册声卡放音驱动 */ sound.device.ops = &ops; rt_audio_register(&sound.device, "sound0", RT_DEVICE_FLAG_WRONLY, &sound); return RT_EOK; } INIT_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函数的时候就知道给你多少字节数据了! 看了上面的分析我相信你应该了解了基本原理了,和编写方法了。但是这个驱动还是不能出声音,那么我们得想办法实现一个驱动,由于笔者的硬件和大家都不一样,那么笔者想了一个比较sao的办法。 那就是将音频缓存到文件中~,这里我们来做一个虚拟音频驱动,这个驱动并不会出声音,但是会将数据保存层pcm文件。pcm的相关参数和你播放的wav一样这样我们可以用电脑来播放了。这样就避免硬件的差异化 ## 3. 音频虚拟驱动 废话不多说,直接上代码。 ```c /* * File: drv_virtual.c * * COPYRIGHT (C) 2012-2019, Shanghai Real-Thread Technology Co., Ltd */ #include "drv_virtual.h" #include "dfs.h" #include "dfs_posix.h" #define DBG_TAG "drv_virtual" #define DBG_LVL DBG_LOG #define DBG_COLOR #include
#define TX_DMA_FIFO_SIZE (2048) struct tina_sound { struct rt_audio_device device; struct rt_audio_configure replay_config; int volume; rt_uint8_t *tx_fifo; int fd; struct rt_thread thread; int endflag; }; static rt_err_t getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps) { rt_err_t ret = RT_EOK; struct tina_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct tina_sound *)audio->parent.user_data; (void)sound; switch(caps->main_type) { case AUDIO_TYPE_QUERY: { switch (caps->sub_type) { case AUDIO_TYPE_QUERY: caps->udata.mask = AUDIO_TYPE_OUTPUT | AUDIO_TYPE_MIXER; break; default: ret = -RT_ERROR; break; } break; } case AUDIO_TYPE_OUTPUT: { switch(caps->sub_type) { case AUDIO_DSP_PARAM: caps->udata.config.channels = sound->replay_config.channels; caps->udata.config.samplebits = sound->replay_config.samplebits; caps->udata.config.samplerate = sound->replay_config.samplerate; break; default: ret = -RT_ERROR; break; } break; } case AUDIO_TYPE_MIXER: { switch (caps->sub_type) { case AUDIO_MIXER_QUERY: caps->udata.mask = AUDIO_MIXER_VOLUME | AUDIO_MIXER_LINE; break; case AUDIO_MIXER_VOLUME: caps->udata.value = sound->volume; break; case AUDIO_MIXER_LINE: break; default: ret = -RT_ERROR; break; } break; } default: ret = -RT_ERROR; break; } return ret; } static rt_err_t configure(struct rt_audio_device *audio, struct rt_audio_caps *caps) { rt_err_t ret = RT_EOK; struct tina_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct tina_sound *)audio->parent.user_data; (void)sound; switch(caps->main_type) { case AUDIO_TYPE_MIXER: { switch(caps->sub_type) { case AUDIO_MIXER_VOLUME: { int volume = caps->udata.value; sound->volume = volume; break; } default: ret = -RT_ERROR; break; } break; } case AUDIO_TYPE_OUTPUT: { switch(caps->sub_type) { case AUDIO_DSP_PARAM: { int samplerate; samplerate = caps->udata.config.samplerate; sound->replay_config.samplerate = samplerate; LOG_I("set samplerate = %d", samplerate); break; } case AUDIO_DSP_SAMPLERATE: { int samplerate; samplerate = caps->udata.config.samplerate; sound->replay_config.samplerate = samplerate; LOG_I("set samplerate = %d", samplerate); break; } case AUDIO_DSP_CHANNELS: { break; } default: break; } break; } default: break; } return ret; } static void virtualplay(void *p) { struct tina_sound *sound = (struct tina_sound *)p; (void)sound; while(1) { /* tick = TX_DMA_FIFO_SIZE/2 * 1000ms / 44100 / 4 ≈ 5.8 */ rt_thread_mdelay(6); rt_audio_tx_complete(&sound->device); if(sound->endflag == 1) { break; } } } static int thread_stack[1024] = {0}; static rt_err_t init(struct rt_audio_device *audio) { struct tina_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct tina_sound *)audio->parent.user_data; (void)sound; LOG_I("sound init"); return RT_EOK; } static rt_err_t start(struct rt_audio_device *audio, int stream) { struct tina_sound *sound = RT_NULL; rt_err_t ret = RT_EOK; RT_ASSERT(audio != RT_NULL); sound = (struct tina_sound *)audio->parent.user_data; (void)sound; LOG_I("sound start"); ret = rt_thread_init(&sound->thread, "virtual", virtualplay, sound, &thread_stack, sizeof(thread_stack), 1, 10); if(ret != RT_EOK) { LOG_E("virtual play thread init failed"); return (-RT_ERROR); } rt_thread_startup(&sound->thread); sound->endflag = 0; sound->fd = open("/tmp/virtual.pcm", O_CREAT | O_RDWR, 0666); return RT_EOK; } static rt_err_t stop(struct rt_audio_device *audio, int stream) { struct tina_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct tina_sound *)audio->parent.user_data; (void)sound; LOG_I("sound stop"); sound->endflag = 1; close(sound->fd); sound->fd = -1; return RT_EOK; } rt_size_t transmit(struct rt_audio_device *audio, const void *wb, void *rb, rt_size_t size) { struct tina_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct tina_sound *)audio->parent.user_data; (void)sound; return write(sound->fd, wb, size); } static void buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info) { struct tina_sound *sound = RT_NULL; RT_ASSERT(audio != RT_NULL); sound = (struct tina_sound *)audio->parent.user_data; /** * TX_FIFO * +----------------+----------------+ * | block1 | block2 | * +----------------+----------------+ * \ block_size / */ info->buffer = sound->tx_fifo; info->total_size = TX_DMA_FIFO_SIZE; info->block_size = TX_DMA_FIFO_SIZE / 2; info->block_count = 2; } static struct rt_audio_ops ops = { .getcaps = getcaps, .configure = configure, .init = init, .start = start, .stop = stop, .transmit = transmit, .buffer_info = buffer_info, }; static int rt_hw_sound_init(void) { rt_uint8_t *tx_fifo = RT_NULL; static struct tina_sound sound = {0}; /* 分配 DMA 搬运 buffer */ tx_fifo = rt_calloc(1, TX_DMA_FIFO_SIZE); if(tx_fifo == RT_NULL) { return -RT_ENOMEM; } sound.tx_fifo = tx_fifo; /* 配置 DSP 参数 */ { sound.replay_config.samplerate = 44100; sound.replay_config.channels = 2; sound.replay_config.samplebits = 16; sound.volume = 60; sound.fd = -1; sound.endflag = 0; } /* 注册声卡放音驱动 */ sound.device.ops = &ops; rt_audio_register(&sound.device, "sound0", RT_DEVICE_FLAG_WRONLY, &sound); return RT_EOK; } INIT_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 讨论,但是群中讨论出的有结论或者有意义的东西请直觉发帖,构建生态和资料才是更好的发展,否则请别加!!!谢谢配合!!! 后面一篇会分析audio框架代码~,看大家的点赞数了 :lol:lol:lol
查看更多
11
个回答
默认排序
按发布时间排序
WillianChan
2019-12-03
-
干货噢,为什么没有人顶贴?Why nobody ding this tiezi?
Liam
2019-12-03
这家伙很懒,什么也没写!
``` while(1) { rt_kprintf("赞!"); rt_thread_mdelay(1000); } ```
whj467467222
认证专家
2019-12-03
开源,分享,交流,共同进步
光神,你为什么要自称小弟
bigmagic
2019-12-03
这家伙很懒,什么也没写!
比较麻烦的就是音频的pause和resume,还不能有破音。
liu2guang
认证专家
2019-12-03
这家伙很懒,什么也没写!
>比较麻烦的就是音频的pause和resume,还不能有破音。 这个还好把,驱动层没有暂停和恢复的概念,上层不传入数据就是不播放,RT-Thread 的audio框架中会做 dma buffer填充,会填充成0,所以不会出现pop音~
Liam
2019-12-03
这家伙很懒,什么也没写!
大佬有I2S初始化相关的代码可以拿出来参考么?我这边407+wm8978参考bsp429和原来裸机的代码,I2S这里一直没搞好:D
liu2guang
认证专家
2019-12-03
这家伙很懒,什么也没写!
>大佬有I2S初始化相关的代码可以拿出来参考么?我这边407+wm8978参考bsp429和原来裸机的代码,I2S这里一直没 ... 控制wm8978 codec芯片的代码参考`stm32\stm32f429-atk-apollo\board`下的drv_wm8978.c就可以了你的应该也是使用i2c接口,首先保存这个i2c初始化codec芯片成功。 429中的audio使用的sai接口,你换成i2s就可以了,我给你一份参考的代码lpc54114中的i2s: ```c /* * Copyright (c) 2006-2018, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2019-11-17 LiWeiHao First implementation */ #include "drv_sound.h" #include "fsl_common.h" #include "fsl_iocon.h" #include "fsl_dma.h" #include "fsl_i2s.h" #include "fsl_i2s_dma.h" #include "fsl_wm8904.h" #include "fsl_i2c.h" #define TX_FIFO_SIZE (4096) #define I2S_TX I2S1 #define I2S_RX I2S0 #define I2S_DMA_TX 15 #define I2S_DMA_RX 12 #ifndef CODEC_I2C_NAME #define CODEC_I2C_NAME "i2c4" #endif struct sound_device { wm8904_handle_t wm8904_handle; dma_handle_t tx_dma_handle; i2s_dma_handle_t tx_i2s_dma_handle; struct rt_audio_device audio; struct rt_audio_configure replay_config; rt_uint8_t volume; rt_uint8_t *tx_fifo; }; const pll_setup_t pll_setup = { .syspllctrl = SYSCON_SYSPLLCTRL_BANDSEL_MASK | SYSCON_SYSPLLCTRL_SELP(0x1FU) | SYSCON_SYSPLLCTRL_SELI(0x8U), .syspllndec = SYSCON_SYSPLLNDEC_NDEC(0x2DU), .syspllpdec = SYSCON_SYSPLLPDEC_PDEC(0x42U), .syspllssctrl = {SYSCON_SYSPLLSSCTRL0_MDEC(0x34D3U) | SYSCON_SYSPLLSSCTRL0_SEL_EXT_MASK, 0x00000000U}, .pllRate = 24576000U, /* 16 bits * 2 channels * 44.1 kHz * 16 */ .flags = PLL_SETUPFLAG_WAITLOCK }; static struct sound_device snd_dev; void i2s_tx_transfer_callback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData) { struct sound_device *snd_dev = (struct sound_device *)userData; rt_audio_tx_complete(&snd_dev->audio); } static rt_err_t lpc_audio_init(struct rt_audio_device *audio) { i2s_config_t tx_i2s_config; wm8904_config_t wm8904_config; CLOCK_EnableClock(kCLOCK_Iocon); CLOCK_EnableClock(kCLOCK_InputMux); CLOCK_EnableClock(kCLOCK_Gpio0); CLOCK_EnableClock(kCLOCK_Gpio1); CLOCK_AttachClk(kFRO12M_to_SYS_PLL); CLOCK_AttachClk(kSYS_PLL_to_FLEXCOMM7); RESET_PeripheralReset(kFC7_RST_SHIFT_RSTn); CLOCK_SetPLLFreq(&pll_setup); CLOCK_AttachClk(kSYS_PLL_to_MCLK); SYSCON->MCLKDIV = SYSCON_MCLKDIV_DIV(0U); // Flexcomm 7 I2S Tx IOCON_PinMuxSet(IOCON, 1, 12, IOCON_FUNC4 | IOCON_DIGITAL_EN); /* Flexcomm 7 / SCK */ IOCON_PinMuxSet(IOCON, 1, 13, IOCON_FUNC4 | IOCON_DIGITAL_EN); /* Flexcomm 7 / SDA */ IOCON_PinMuxSet(IOCON, 1, 14, IOCON_FUNC4 | IOCON_DIGITAL_EN); /* Flexcomm 7 / WS */ /* MCLK output for I2S */ IOCON_PinMuxSet(IOCON, 1, 17, IOCON_FUNC4 | IOCON_MODE_INACT | IOCON_DIGITAL_EN); SYSCON->MCLKIO = 1U; WM8904_GetDefaultConfig(&wm8904_config); snd_dev.wm8904_handle.i2c = (struct rt_i2c_bus_device *)rt_device_find(CODEC_I2C_NAME); if (WM8904_Init(&snd_dev.wm8904_handle, &wm8904_config) != kStatus_Success) { rt_kprintf("wm8904 init failed\n"); return -RT_ERROR; } WM8904_SetMute(&snd_dev.wm8904_handle, RT_TRUE, RT_TRUE); I2S_TxGetDefaultConfig(&tx_i2s_config); tx_i2s_config.divider = CLOCK_GetPllOutFreq() / 48000U / 16 / 2; I2S_TxInit(I2S_TX, &tx_i2s_config); DMA_Init(DMA0); DMA_EnableChannel(DMA0, I2S_DMA_TX); DMA_SetChannelPriority(DMA0, I2S_DMA_TX, kDMA_ChannelPriority3); DMA_CreateHandle(&snd_dev.tx_dma_handle, DMA0, I2S_DMA_TX); I2S_TxTransferCreateHandleDMA(I2S_TX, &snd_dev.tx_i2s_dma_handle, &snd_dev.tx_dma_handle, i2s_tx_transfer_callback, (void *)&snd_dev); return RT_EOK; } static rt_err_t lpc_audio_start(struct rt_audio_device *audio, int stream) { RT_ASSERT(audio != RT_NULL); if (stream == AUDIO_STREAM_REPLAY) { struct rt_audio_caps caps; caps.main_type = AUDIO_TYPE_MIXER; caps.sub_type = AUDIO_MIXER_VOLUME; audio->ops->getcaps(audio, &caps); audio->ops->configure(audio, &caps); rt_audio_tx_complete(audio); } return RT_EOK; } static rt_err_t lpc_audio_stop(struct rt_audio_device *audio, int stream) { if (stream == AUDIO_STREAM_REPLAY) { WM8904_SetMute(&snd_dev.wm8904_handle, RT_TRUE, RT_TRUE); I2S_TransferAbortDMA(I2S_TX, &snd_dev.tx_i2s_dma_handle); } return RT_EOK; } static rt_err_t lpc_audio_getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps) { rt_err_t result = RT_EOK; struct sound_device *snd_dev; RT_ASSERT(audio != RT_NULL); snd_dev = (struct sound_device *)audio->parent.user_data; switch (caps->main_type) { case AUDIO_TYPE_QUERY: /* qurey the types of hw_codec device */ { switch (caps->sub_type) { case AUDIO_TYPE_QUERY: caps->udata.mask = AUDIO_TYPE_OUTPUT | AUDIO_TYPE_MIXER; break; default: result = -RT_ERROR; break; } break; } case AUDIO_TYPE_OUTPUT: /* Provide capabilities of OUTPUT unit */ { switch (caps->sub_type) { case AUDIO_DSP_PARAM: caps->udata.config.samplerate = snd_dev->replay_config.samplerate; caps->udata.config.channels = snd_dev->replay_config.channels; caps->udata.config.samplebits = snd_dev->replay_config.samplebits; break; case AUDIO_DSP_SAMPLERATE: caps->udata.config.samplerate = snd_dev->replay_config.samplerate; break; case AUDIO_DSP_CHANNELS: caps->udata.config.channels = snd_dev->replay_config.channels; break; case AUDIO_DSP_SAMPLEBITS: caps->udata.config.samplebits = snd_dev->replay_config.samplebits; break; default: result = -RT_ERROR; break; } break; } case AUDIO_TYPE_MIXER: /* report the Mixer Units */ { switch (caps->sub_type) { case AUDIO_MIXER_QUERY: caps->udata.mask = AUDIO_MIXER_VOLUME; break; case AUDIO_MIXER_VOLUME: caps->udata.value = snd_dev->volume; break; default: result = -RT_ERROR; break; } break; } default: result = -RT_ERROR; break; } return result; } static rt_err_t lpc_audio_configure(struct rt_audio_device *audio, struct rt_audio_caps *caps) { rt_err_t result = RT_EOK; struct sound_device *snd_dev = audio->parent.user_data; switch (caps->main_type) { case AUDIO_TYPE_MIXER: { switch (caps->sub_type) { case AUDIO_MIXER_MUTE: { WM8904_SetMute(&snd_dev->wm8904_handle, RT_TRUE, RT_TRUE); snd_dev->volume = 0; break; } case AUDIO_MIXER_VOLUME: { int volume = caps->udata.value / 2; WM8904_SetMute(&snd_dev->wm8904_handle, RT_FALSE, RT_FALSE); WM8904_SetVolume(&snd_dev->wm8904_handle, volume, volume); snd_dev->volume = volume; break; } } break; } case AUDIO_TYPE_OUTPUT: { switch (caps->sub_type) { case AUDIO_DSP_PARAM: { struct rt_audio_configure config = caps->udata.config; i2s_config_t tx_i2s_config; snd_dev->replay_config.channels = config.channels; snd_dev->replay_config.samplebits = config.samplebits; snd_dev->replay_config.samplerate = config.samplerate; I2S_TxGetDefaultConfig(&tx_i2s_config); tx_i2s_config.divider = CLOCK_GetPllOutFreq() / config.samplerate / 16 / 2; I2S_TxInit(I2S_TX, &tx_i2s_config); break; } case AUDIO_DSP_SAMPLERATE: { struct rt_audio_configure config = caps->udata.config; i2s_config_t tx_i2s_config; snd_dev->replay_config.samplerate = config.samplerate; I2S_TxGetDefaultConfig(&tx_i2s_config); tx_i2s_config.divider = CLOCK_GetPllOutFreq() / config.samplerate / 16 / 2; I2S_TxInit(I2S_TX, &tx_i2s_config); break; } default: result = -RT_ERROR; break; } break; } } return result; } static rt_size_t lpc_audio_transmit(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size) { RT_ASSERT(audio != RT_NULL); i2s_transfer_t transfer; transfer.data = (uint8_t *)writeBuf; transfer.dataSize = size; I2S_TxTransferSendDMA(I2S_TX, &snd_dev.tx_i2s_dma_handle, transfer); return RT_EOK; } static void lpc_audio_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_FIFO_SIZE; info->block_size = TX_FIFO_SIZE / 2; info->block_count = 2; } static struct rt_audio_ops audio_ops = { .getcaps = lpc_audio_getcaps, .configure = lpc_audio_configure, .init = lpc_audio_init, .start = lpc_audio_start, .stop = lpc_audio_stop, .transmit = lpc_audio_transmit, .buffer_info = lpc_audio_buffer_info, }; int rt_hw_sound_init(void) { rt_uint8_t *tx_fifo = RT_NULL; tx_fifo = rt_malloc(TX_FIFO_SIZE); if (tx_fifo == NULL) { return -RT_ENOMEM; } snd_dev.tx_fifo = tx_fifo; /* init default configuration */ { snd_dev.replay_config.samplerate = 44100; snd_dev.replay_config.channels = 2; snd_dev.replay_config.samplebits = 16; snd_dev.volume = 30; } snd_dev.audio.ops = &audio_ops; rt_audio_register(&snd_dev.audio, "sound0", RT_DEVICE_FLAG_WRONLY, &snd_dev); return RT_EOK; } INIT_DEVICE_EXPORT(rt_hw_sound_init); ```
liu2guang
认证专家
2019-12-03
这家伙很懒,什么也没写!
>大佬有I2S初始化相关的代码可以拿出来参考么?我这边407+wm8978参考bsp429和原来裸机的代码,I2S这里一直没 ... 不过你没有说你具体的现象和问题是什么,只能给你提供代码参考了,i2s其实就是初始化,配置dma地址,start中启动dma, stop关掉dma,在dma半空中断和满中断中触发音频数据搬运
Liam
2019-12-03
这家伙很懒,什么也没写!
>不过你没有说你具体的现象和问题是什么,只能给你提供代码参考了,i2s其实就是初始化,配置dma地址,star ... 感谢,我的应该是在I2S初始化和设置采样率的一些地方有问题,在使用软件包的wavplayer测试时没有声音,正在查找原因,参照上面lpc54114学习ing,多谢:D
liu2guang
认证专家
2019-12-03
这家伙很懒,什么也没写!
>感谢,我的应该是在I2S初始化和设置采样率的一些地方有问题,在使用软件包的wavplayer测试时没有声音,正 ... 是不是 wm8978 没有关 mute 啊,其实有数据,但是codec不发声音,或者是功放的mute脚不对
撰写答案
登录
注册新账号
关注者
0
被浏览
3.4k
关于作者
liu2guang
这家伙很懒,什么也没写!
提问
14
回答
100
被采纳
4
关注TA
发私信
相关问题
1
I2S WM8978 DMA没中断问题,参考SAI驱动改写
2
audio的replay模式播放时是先发一段0来启动吗?
3
audio的replay模式不能播放长度小于内存块大小的数据吗?
4
关于音频初始化函数感觉有问题:_audio_dev_init
5
柿饼m3模块的音频问题
6
关于rtthread支持的媒体库
7
请问vs1050的音频驱动怎么写啊?
8
RT-Thread Audio - 1. 音频框架的分析
9
1.工具:rt-thread studio ;2.MCU:stm32f407zgt6;3: audio 怎么使用?4.背景:首次使用RT-thread
10
STM32F407ZGT6音频I2S对接audio框架
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
最新文章
1
开源共生 商业共赢 | RT-Thread 2024开发者大会议程正式发布!
2
【24嵌入式设计大赛】基于RT-Thread星火一号的智慧家居系统
3
RT-Thread EtherKit开源以太网硬件正式发布
4
如何在master上的BSP中添加配置yml文件
5
使用百度AI助手辅助编写一个rt-thread下的ONVIF设备发现功能的功能代码
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
20
个答案
2
次被采纳
张世争
11
个答案
2
次被采纳
rv666
9
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
RTT_逍遥
1
篇文章
6
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部