【Audio】2. 音频虚拟驱动

发布于 2019-12-02 23:40:51
    本帖最后由 liu2guang 于 2019-12-2 23:45 编辑


之前更新过一篇音频驱动相关的文档,也是当时兴趣来了,那么今天我详细整理下这块,拆分下分析 RT-Thread 音频驱动相关的内容。

今天主要是讲解音频虚拟驱动来分析驱动的编写。但是这篇文章并不会讲解关于 RT-Thread IO Device 框架相关内容,如果有对这部分不太熟悉的人请先看这个链接了解基本概念:

RT-Thread I/O 设备模型

1. 如何使用 Audio 驱动

在写驱动之前,我们首先得知道如何测试自己的驱动对吧!所以这里我们首先了解下如何播放音乐!

#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:\n");
rt_kprintf("wavplay_sample song.wav\n");
return 0;
}

fd = open(argv[1], O_WRONLY);
if (fd < 0)
{
rt_kprintf("open file failed!\n");
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:\n");
rt_kprintf("samplerate %d\n", info->fmt_block.wav_format.SamplesPerSec);
rt_kprintf("channel %d\n", 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. 编写音频虚拟驱动

上一篇关于音频的文档中 小弟我已经分析了 audio 驱动的框架,这里我们接着这个驱动的架子说下框架如何实现。
#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. 音频虚拟驱动

废话不多说,直接上代码。
/*
* 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



查看更多

关注者
0
被浏览
1.5k
11 个回答
WillianChan
WillianChan 2019-12-03
干货噢,为什么没有人顶贴?Why nobody ding this tiezi?
Liam
Liam 2019-12-03
while(1)
{
rt_kprintf("赞!");
rt_thread_mdelay(1000);
}
whj467467222
whj467467222 认证专家 2019-12-03
光神,你为什么要自称小弟
bigmagic
bigmagic 2019-12-03
比较麻烦的就是音频的pause和resume,还不能有破音。
liu2guang
liu2guang 认证专家 2019-12-03
    本帖最后由 liu2guang 于 2019-12-3 11:59 编辑


bigmagic 发表于 2019-12-3 11:55
比较麻烦的就是音频的pause和resume,还不能有破音。

这个还好把,驱动层没有暂停和恢复的概念,上层不传入数据就是不播放,RT-Thread 的audio框架中会做 dma buffer填充,会填充成0,所以不会出现pop音~
Liam
Liam 2019-12-03
大佬有I2S初始化相关的代码可以拿出来参考么?我这边407+wm8978参考bsp429和原来裸机的代码,I2S这里一直没搞好:D
liu2guang
liu2guang 认证专家 2019-12-03
Liam 发表于 2019-12-3 14:56
大佬有I2S初始化相关的代码可以拿出来参考么?我这边407+wm8978参考bsp429和原来裸机的代码,I2S这里一直没 ...


控制wm8978 codec芯片的代码参考stm32\stm32f429-atk-apollo\board下的drv_wm8978.c就可以了你的应该也是使用i2c接口,首先保存这个i2c初始化codec芯片成功。

429中的audio使用的sai接口,你换成i2s就可以了,我给你一份参考的代码lpc54114中的i2s:
/*
* 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
liu2guang 认证专家 2019-12-03
Liam 发表于 2019-12-3 14:56
大佬有I2S初始化相关的代码可以拿出来参考么?我这边407+wm8978参考bsp429和原来裸机的代码,I2S这里一直没 ...


不过你没有说你具体的现象和问题是什么,只能给你提供代码参考了,i2s其实就是初始化,配置dma地址,start中启动dma, stop关掉dma,在dma半空中断和满中断中触发音频数据搬运
Liam
Liam 2019-12-03
liu2guang 发表于 2019-12-3 16:03
不过你没有说你具体的现象和问题是什么,只能给你提供代码参考了,i2s其实就是初始化,配置dma地址,star ...


感谢,我的应该是在I2S初始化和设置采样率的一些地方有问题,在使用软件包的wavplayer测试时没有声音,正在查找原因,参照上面lpc54114学习ing,多谢:D
liu2guang
liu2guang 认证专家 2019-12-03
Liam 发表于 2019-12-3 16:23
感谢,我的应该是在I2S初始化和设置采样率的一些地方有问题,在使用软件包的wavplayer测试时没有声音,正 ...


是不是 wm8978 没有关 mute 啊,其实有数据,但是codec不发声音,或者是功放的mute脚不对
Liam
Liam 2019-12-03
liu2guang 发表于 2019-12-3 16:35
是不是 wm8978 没有关 mute 啊,其实有数据,但是codec不发声音,或者是功放的mute脚不对 ...


还在找原因,把I2S这里排查完了再检查一下wm8978的寄存器配置。硬件应用上也有一些不同,去掉了out1/2的电容,用out3/4当做地
QQ图片20191203170135.png
pjdu
pjdu 2020-06-26
如果不用I2S,直接用DAC接功放,驱动要怎么实现呢

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览