Forwarded from RT-Thread Community Developer liu2guang
Today I'm going to introduce using audio virtual drivers to analyze driver programming.
1. How to use Audio Driver
Before writing a driver, we'll first need to know how to test the driver? So let's start learning how to play the music.
#include
#include
#include
#define BUFSZ 1024
#define SOUND_DEVICE_NAME "sound0" /* Audio Device Name */
static rt_device_t snd_dev; /* Audio Device Handle */
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);
/* Find the Audio device by device name and get the device handle */
snd_dev = rt_device_find(SOUND_DEVICE_NAME);
/* Turn on the Audio playback device in a write-only manner */
rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY);
/* Set audio parameter information such as sample rate, channel, sample number of bits, and so on */
caps.main_type = AUDIO_TYPE_OUTPUT; /* Output type(Player device)*/
caps.sub_type = AUDIO_DSP_PARAM; /* Set all audio parameter information */
caps.udata.config.samplerate = info->fmt_block.wav_format.SamplesPerSec; /* sample rate */
caps.udata.config.channels = info->fmt_block.wav_format.Channels; /* Sample channel */
caps.udata.config.samplebits = 16; /* Sample bit */
rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps);
while (1)
{
int length;
/* Read the audio data for the wav file in file system */
length = read(fd, buffer, BUFSZ);
if (length <= 0)
break;
/* Write audio data to the Audio device */
rt_device_write(snd_dev, 0, buffer, length);
}
/* Turn of Audio device */
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);
The main steps for playing the audio data are as follows:
Let's analyze this code, starting with wavplay_sample function:
(*)#define SOUND_DEVICE_NAME "sound0" First define the play driver
(*)fd = open(argv[1], O_WRONLY); Used for opening audio file
(*)snd_dev = rt_device_find(SOUND_DEVICE_NAME); First look for the Audio device to get the device handle
(*)rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY); Turn on the Audio device in a write-only manner, which is the sound device
(*)rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps); Set audio parameter information (sample rate, channel, etc.)
(*)length = read(fd, buffer, BUFSZ); Decode the data for the audio file
(*)rt_device_write(snd_dev, 0, buffer, length); Write in audio file data
(*)rt_device_close(snd_dev); Play is complete, turn off the device
Add those codes to your code compile and download it, then it starts playing music, of course, only wwav-format audio. Next, let's add the driver.
2. Write Audio Virtual Driver
In last post, I have analyzed the audio-driven frame, and here we continue to introduce how to implement the driver framework.
#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};
/* Assign DMA, Move buffer */
tx_fifo = rt_calloc(1, TX_DMA_FIFO_SIZE);
if(tx_fifo == RT_NULL)
{
return -RT_ENOMEM;
}
sound.tx_fifo = tx_fifo;
/* Register the sound card playing driver */
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);
Above is the entire audio-driver framework but without hardware-related code, add it to the project and use the list_device command in the shell can see the sound0 driver. If we combine the code in the last post, it is available to play wav audio, but since there is no hardware-related code so it won't play the sound now.
Let's analyze the code first:
transmit is mainly used to trigger the handling of data, why it is used to trigger the handling? Because the upper-level code writes the audio data to the audio device will not be written directly to the driver, so it won't directly call transmit function used to handle buffer data to the dma buffer, then when transmit will be call? We can see that the audio framework has a function rt_audio_tx_complete(&sound->device) that used to notify the handling, let's analyze this:
After reading the above analysis you get to know its basic principles and writing methods. But this driver still can't make a sound, so we have to find a way to achieve a driver, because the my hardware is special so I find a specific solution.
To cache the audio into the file, and here we make a virtual audio driver , this driver won't make a sound, but it saves the data into pcm file. The parameters of pcm are the same as the wav so we can play them on our computer even if the hardware is different.
3. Audio Virtual Driver
/*
* 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};
/* Assign DMA move buffer */
tx_fifo = rt_calloc(1, TX_DMA_FIFO_SIZE);
if(tx_fifo == RT_NULL)
{
return -RT_ENOMEM;
}
sound.tx_fifo = tx_fifo;
/* Configure DSP parameter */
{
sound.replay_config.samplerate = 44100;
sound.replay_config.channels = 2;
sound.replay_config.samplebits = 16;
sound.volume = 60;
sound.fd = -1;
sound.endflag = 0;
}
/* Register the sound card playing driver */
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);
Based on the second part of the analysis, you might understand the code, this driver is taking the advantage of using the virtualplay thread to simulate i2s dma for automatically handling data.
The final file is saved to /tmp/virtual.pcm, note that the virtualplay function has 6ms delay in order to simulate the time it takes to handle (play) 1KB of data in the dma buffer, tick = TX_DMA_FIFO_SIZE/2 * 1000ms / 44100 / 4 ≈ 5.8ms. So we have to write the file fast, I was using ramfs to implement the file system, it takes time to write sd card or flash, so I would recommended you to use ramfs, and make sure that the size of ramfs has more than 20Mbytes. Also, you can give a try with QEMU.