这个demo和想法是参考了大神的文章
https://aijishu.com/a/1060000...
我简单的想法是 和 星辰 这个内核呼应下,星辰大海嘛,这首歌还挺好听的,咱们的目标是星辰大海,目标要大一点 应该能实现。
先说音频播放的接口,一般是I2S,这一点大神的文章里介绍的很详细了,我就不再赘述。我关注的目标是rt-thread上播放音乐这码事,稍微区别下。
rt-thread提供了wav播放的包,名字叫wavplayer,我懒得移植helix的MP3
库了,估计有很多人移植来着,我就不趟了,我的核心是IoT方向,今早收拢战线回归主要战场。
wavplayer的下载链接,当然如果你有空用脚本生成那倒无所谓
https://github.com/RT-Thread-...
或
gitee我没找到...
在keil的工程里添加wavplayer的源码
这里我没有用到record录音的功能,所以这部分c文件就编译屏蔽了
wavplayer需要用到rtt的optparse的相关定义和函数,添加optparse.c及其include的路径到工程
接下来就是rt-thread的audio框架的相关代码了,在component文件夹下
主要是audio.c和audio_pipe.c
嗯,预想实现功能,rtconfig.h的修改是必不可少的,咱们需要打开如下几个宏定义,欸~
一切就绪就是本次文章的重头了,也是我干到夜里2点没干动的代码---drv_sound.c
我先把源文件列在这里,再讲我的思路,整的不好容易 啪啪啪 的破音,破音要么是数据DMA搬运的不连续,要么就是数据有问题,反正多搜索网上的帖子就好。
我代码偷懒了,并没有实现audio频率的设置,音量的设置(板子不支持)等我觉得很烦的功能,懒的搞,能播就行
/*
* Copyright (c) 2020-2021, Bluetrum Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Date Author Notes
* 2020-12-12 greedyhao first implementation
*/
#include <rtthread.h>
#include "rtdevice.h"
#define DBG_TAG "drv.snd_dev"
#define DBG_LVL DBG_ERROR
#include <rtdbg.h>
#include <stdio.h>
#include <stdint.h>
#include "hal_common.h"
#include "hal_rcc.h"
#include "hal_i2s.h"
#include "hal_dma.h"
#include "hal_dma_request.h"
#include "hal_gpio.h"
#include "clock_init.h"
#define SAI_AUDIO_FREQUENCY_48K ((uint32_t)48000u)
#define SAI_AUDIO_FREQUENCY_44K ((uint32_t)44100u)
#define SAI_AUDIO_FREQUENCY_38K ((uint32_t)38000u)
#define TX_FIFO_SIZE (4096*2)
struct sound_device
{
struct rt_audio_device audio;
struct rt_audio_configure replay_config;
rt_uint8_t *tx_fifo;
rt_uint8_t volume;
};
static struct sound_device snd_dev = {0};
#pragma pack (4)
volatile uint8_t g_PlayIndex = 0;
uint8_t g_AudioBuf[TX_FIFO_SIZE] __attribute__((section(".ARM.__at_0x20000000"))) ;
#pragma pack()
/* I2S IRQ. */
void DMA1_CH5_IRQHandler(void)
{
rt_interrupt_enter();
if (0u != (DMA_GetChannelInterruptStatus(DMA1, DMA_REQ_DMA1_SPI2_TX) & DMA_CHN_INT_XFER_DONE) )
{
DMA_ClearChannelInterruptStatus(DMA1, DMA_REQ_DMA1_SPI2_TX, DMA_CHN_INT_XFER_DONE);
DMA_EnableChannel(DMA1, DMA_REQ_DMA1_SPI2_TX, true);
g_PlayIndex = 1;
rt_audio_tx_complete(&snd_dev.audio);
}
if(0u != (DMA_CHN_INT_XFER_HALF_DONE & DMA_GetChannelInterruptStatus(DMA1, DMA_REQ_DMA1_SPI2_TX)) )
{
DMA_ClearChannelInterruptStatus(DMA1, DMA_REQ_DMA1_SPI2_TX, DMA_CHN_INT_XFER_HALF_DONE);
g_PlayIndex = 0;
rt_audio_tx_complete(&snd_dev.audio);
}
rt_interrupt_leave();
}
//apll = 采样率*ADPLL_DIV*512
//audio pll init
void adpll_init(uint8_t out_spr)
{
GPIO_Init_Type gpio_init;
/* SPI2. */
RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_SPI2, true);
RCC_ResetAPB1Periphs(RCC_APB1_PERIPH_SPI2);
/* PD3 - I2S_CK. */
gpio_init.Pins = GPIO_PIN_3;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOD, &gpio_init);
GPIO_PinAFConf(GPIOD, gpio_init.Pins, GPIO_AF_5);
/* PE6 - I2S_SD. */
gpio_init.Pins = GPIO_PIN_6;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOE, &gpio_init);
GPIO_PinAFConf(GPIOE, gpio_init.Pins, GPIO_AF_5);
/* PE4 - I2S_WS. */
gpio_init.Pins = GPIO_PIN_4;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOE, &gpio_init);
GPIO_PinAFConf(GPIOE, gpio_init.Pins, GPIO_AF_5);
/* PE5 - I2S_MCK. */
gpio_init.Pins = GPIO_PIN_5;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOE, &gpio_init);
GPIO_PinAFConf(GPIOE, gpio_init.Pins, GPIO_AF_5);
/* Setup the DMA for I2S RX. */
DMA_Channel_Init_Type dma_channel_init;
dma_channel_init.MemAddr = (uint32_t)(g_AudioBuf);
dma_channel_init.MemAddrIncMode = DMA_AddrIncMode_IncAfterXfer;
dma_channel_init.PeriphAddr = I2S_GetTxDataRegAddr(SPI2); /* use tx data register here. */
dma_channel_init.PeriphAddrIncMode = DMA_AddrIncMode_StayAfterXfer;
dma_channel_init.Priority = DMA_Priority_Highest;
dma_channel_init.XferCount = TX_FIFO_SIZE/2;
dma_channel_init.XferMode = DMA_XferMode_MemoryToPeriph;
dma_channel_init.ReloadMode = DMA_ReloadMode_AutoReload;
dma_channel_init.XferWidth = DMA_XferWidth_16b;
DMA_InitChannel(DMA1, DMA_REQ_DMA1_SPI2_TX, &dma_channel_init);
/* Enable DMA transfer done interrupt. */
DMA_EnableChannelInterrupts(DMA1, DMA_REQ_DMA1_SPI2_TX, DMA_CHN_INT_XFER_DONE, true);
DMA_EnableChannelInterrupts(DMA1, DMA_REQ_DMA1_SPI2_TX, DMA_CHN_INT_XFER_HALF_DONE, true);
NVIC_EnableIRQ(DMA1_CH5_IRQn);
/* Setup the I2S. */
I2S_Master_Init_Type i2s_master_init;
i2s_master_init.ClockFreqHz = CLOCK_APB1_FREQ;
i2s_master_init.SampleRate = SAI_AUDIO_FREQUENCY_44K;
i2s_master_init.DataWidth = I2S_DataWidth_16b;
i2s_master_init.Protocol = I2S_Protocol_PHILIPS;
i2s_master_init.EnableMCLK = true;
i2s_master_init.Polarity = I2S_Polarity_1;
i2s_master_init.XferMode = I2S_XferMode_TxOnly;
I2S_InitMaster(SPI2, &i2s_master_init);
I2S_EnableDMA(SPI2, true);
I2S_Enable(SPI2, true);
DMA_EnableChannel(DMA1, DMA_REQ_DMA1_SPI2_TX, true);
}
static rt_err_t sound_getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps)
{
rt_err_t result = RT_EOK;
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
LOG_D("%s:main_type: %d, sub_type: %d", __FUNCTION__, caps->main_type, caps->sub_type);
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 = saia_volume_get();
break;
default:
result = -RT_ERROR;
break;
}
break;
}
default:
result = -RT_ERROR;
break;
}
return RT_EOK;
}
static rt_err_t sound_configure(struct rt_audio_device *audio, struct rt_audio_caps *caps)
{
rt_err_t result = RT_EOK;
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
switch (caps->main_type)
{
case AUDIO_TYPE_MIXER:
{
switch (caps->sub_type)
{
case AUDIO_MIXER_VOLUME:
{
rt_uint8_t volume = caps->udata.value;
// saia_volume_set(volume);
snd_dev->volume = volume;
LOG_D("set volume %d", volume);
break;
}
case AUDIO_MIXER_EXTEND:
break;
default:
result = -RT_ERROR;
break;
}
break;
}
case AUDIO_TYPE_OUTPUT:
{
switch (caps->sub_type)
{
case AUDIO_DSP_PARAM:
{
/* set samplerate */
// saia_frequency_set(caps->udata.config.samplerate);
/* set channels */
// saia_channels_set(caps->udata.config.channels);
/* save configs */
snd_dev->replay_config.samplerate = caps->udata.config.samplerate;
snd_dev->replay_config.channels = caps->udata.config.channels;
snd_dev->replay_config.samplebits = caps->udata.config.samplebits;
LOG_D("set samplerate %d", snd_dev->replay_config.samplerate);
break;
}
case AUDIO_DSP_SAMPLERATE:
{
// saia_frequency_set(caps->udata.config.samplerate);
snd_dev->replay_config.samplerate = caps->udata.config.samplerate;
LOG_D("set samplerate %d", snd_dev->replay_config.samplerate);
break;
}
case AUDIO_DSP_CHANNELS:
{
// saia_channels_set(caps->udata.config.channels);
snd_dev->replay_config.channels = caps->udata.config.channels;
LOG_D("set channels %d", snd_dev->replay_config.channels);
break;
}
case AUDIO_DSP_SAMPLEBITS:
{
/* not support */
snd_dev->replay_config.samplebits = caps->udata.config.samplebits;
break;
}
default:
result = -RT_ERROR;
break;
}
break;
}
default:
break;
}
return RT_EOK;
}
static rt_err_t sound_init(struct rt_audio_device *audio)
{
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
adpll_init(0);
/* set default params */
// saia_frequency_set(snd_dev->replay_config.samplerate);
//saia_channels_set(snd_dev->replay_config.channels);
//saia_volume_set(snd_dev->volume);
return RT_EOK;
}
static rt_err_t sound_start(struct rt_audio_device *audio, int stream)
{
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
if (stream == AUDIO_STREAM_REPLAY)
{
LOG_D("open sound device");
DMA_EnableChannel(DMA1, DMA_REQ_DMA1_SPI2_TX, true);
}
return RT_EOK;
}
static rt_err_t sound_stop(struct rt_audio_device *audio, int stream)
{
RT_ASSERT(audio != RT_NULL);
if (stream == AUDIO_STREAM_REPLAY)
{
LOG_D("close sound device");
DMA_EnableChannel(DMA1, DMA_REQ_DMA1_SPI2_TX, false);
}
return RT_EOK;
}
rt_size_t sound_transmit(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size)
{
struct sound_device *snd_dev = RT_NULL;
rt_size_t count = 0;
// RT_ASSERT(audio != RT_NULL);
// snd_dev = (struct sound_device *)audio->parent.user_data;
// rt_kprintf("Size %d %d\r\n", size, g_PlayIndex);
if(g_PlayIndex == 0)
{
memcpy(&g_AudioBuf[0], writeBuf, size);
}
else
{
memcpy(&g_AudioBuf[TX_FIFO_SIZE/2], writeBuf, size);
}
return size;
}
static void sound_buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info)
{
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
/**
* 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 ops =
{
.getcaps = sound_getcaps,
.configure = sound_configure,
.init = sound_init,
.start = sound_start,
.stop = sound_stop,
.transmit = sound_transmit,//NULL,//sound_transmit,
.buffer_info = sound_buffer_info,
};
static int rt_hw_sound_init(void)
{
rt_uint8_t *tx_fifo = RT_NULL;
rt_uint8_t *rx_fifo = RT_NULL;
tx_fifo = rt_malloc(TX_FIFO_SIZE);
if(tx_fifo == RT_NULL)
{
rt_kprintf("Sound can alloc tx_fifo\r\n");
return -RT_ENOMEM;
}
rt_memset(&g_AudioBuf[0], 0x00, TX_FIFO_SIZE);
rt_memset(tx_fifo, 0, TX_FIFO_SIZE/2);
snd_dev.tx_fifo = tx_fifo;
/* init default configuration */
{
snd_dev.replay_config.samplerate = SAI_AUDIO_FREQUENCY_44K;
snd_dev.replay_config.channels = 2;
snd_dev.replay_config.samplebits = 16;
snd_dev.volume = 55;
}
/* register snd_dev device */
snd_dev.audio.ops = &ops;
rt_audio_register(&snd_dev.audio, "sound0", RT_DEVICE_FLAG_WRONLY, &snd_dev);
return RT_EOK;
}
INIT_DEVICE_EXPORT(rt_hw_sound_init);
DMA搬运wav的音频数据我遇到了很多问题,最后用一个大的buffer给DMA搬运,把这个buffer拆成两半,DMA使用half transfer interrupt以及transfered interrupt即传输一半给中断,传输完成给中断。
传输一半的时候让wav解码任务读取一部分内容放到buffer前半部分,传输完成通知wav解码任务再读取一部分内容放到buffer的后半部分。类似pingpong buffer 这种机制,才能保证wav播放的连续性。
好吧编译后下载运行看视频的结果,wav播放的命令是wavplayer -s 文件名