6.1.5. 设计指南¶
6.1.5.1. 源码说明¶
源代码位于:
- bsp/artinchip/drv/audio/drv_audio.c: Playback的driver层驱动 
- bsp/artinchip/drv/audio/drv_dmic.c: DMIC的driver层驱动 
- bsp/artinchip/drv/audio/drv_amic.c: AMIC的driver层驱动 
- bsp/artinchip/hal/audio/hal_audio.c: audio模块的hal层驱动 
- bsp/artinchip/include/hal/hal_audio.h: audio模块的hal层头文件 
- bsp/artinchip/include/hal/hal_audio_reg.h: audio模块的hal层头文件,用于寄存器定义 
6.1.5.2. 模块架构设计¶
6.1.5.2.1. RT-Thread audio框架¶
RT-Thread定义了一套音频框架,driver层的驱动就是为了对接该音频框架。该框架在录音端和播放端采用了两种不同的机制,现对录音端和播放端的框架进行简单说明。
6.1.5.2.1.1. 播放端框架¶
 
如上图所示,应用层读取的音频数据写入到内存池的block中,并用数据队列对内存池的block数据进行管理。从数据队列中依次取出音频数据,写入到audio buffer中,audio buffer在原理上是一个环形缓冲区,最后通过DMA将音频数据写入到硬件的TXFIFO中。所以,playback端的driver层驱动主要是负责audio buffer环形缓冲区的管理,及时写入音频数据,并用DMA搬运到audio硬件。
6.1.5.2.1.2. 录音端框架¶
 
如上图所示,RT-Thread audio框架在录音端的设计与播放端不同。在录音端框架虚拟了一个pipe设备,其实就是一个ringbuffer,读写pipe设备就是对ringbuffer的读写。driver层驱动负责管理一个RX buffer,RX buffer在逻辑上也是一个环形缓冲区。DMA负责将MIC端接收到的数据搬运到RX buffer,再通过写pipe设备将音频数据写入到pipe的ringbuffer中。应用层代码则通过 rt_device_read 每次从pipe设备中读取音频数据,再将音频数据写入到wav文件。
6.1.5.3. 关键流程设计¶
6.1.5.3.1. 初始化流程¶
- 初始化audio模块时钟频率 
- 释放reset和clock信号 
- 注册音频设备,playback端注册为 - sound0设备,DMIC注册为- dmic0设备,AMIC注册为- amic0设备
6.1.5.3.2. playback流程¶
6.1.5.3.2.1. init流程¶
- 初始化DMA传输的起始地址,buf_len以及period_len 
- 注册hal层的回调函数 
audio模块使用DMA传输音频数据,DMA采用环形链表形式,依次将音频数据传送到硬件。所以需要配置DMA传输时的起始地址(即TX buffer地址)以及buf_len,period_len。在driver层驱动,将buf_len配置为period_len的4倍,DMA每传输period_len长度的数据,触发一次DMA中断,通知CPU向TX buffer中写入数据。
6.1.5.3.2.2. start流程¶
- 根据menuconfig配置音频通路 
- 填充TX buffer 
- 设置DMA传输的参数,调用 - hal_audio_playback_start开始音频数据传输
- 使能PA 
为保证DMA传输音频数据的连续性,需要在DMA开始传输前,先向TX buffer中填充数据。在playback的driver层驱动,是将TX buffer填充满后,才开始DMA的传输。
6.1.5.3.2.3. DMA中断流程¶
DMA每传输完period_len长度的数据后,触发一次DMA中断,然后通过DMA回调函数的逐级调用,最终调用 rt_audio_tx_complete 对TX buffer进行填充,每次填充period_len长度的音频数据。
6.1.5.3.3. record流程¶
6.1.5.3.3.1. init流程¶
- 初始化DMA传输的起始地址,buf_len以及period_len 
- 注册hal层的回调函数 
audio模块使用DMA传输音频数据,DMA采用环形链表形式,依次将音频数据传送到硬件。所以需要配置DMA传输时的起始地址(即RX buffer地址)以及buf_len,period_len。在driver层驱动,将buf_len配置为period_len的2倍,DMA每传输period_len长度的数据,触发一次DMA中断,通知CPU向pipe设备写入数据。
6.1.5.3.3.2. start流程¶
按照RT-Thread audio的框架,在执行 rt_device_open 时,就会调用start流程,开始音频的录制,然后再通过 rt_device_control 设置音频的格式(采样率,通道数等)。按照这个流程,最开始可能会录制一段不符合设置的音频格式的数据,这显然是不合理的。所以,在driver层的驱动实现中,start流程并未做任何处理,而是在设置完音频格式后才开始音频的录制。
6.1.5.3.3.3. DMA中断流程¶
DMA每传输完period_len长度的数据后,触发一次DMA中断,然后通过DMA回调函数的逐级调用,最终调用 rt_audio_rx_done ,将RX buffer的数据写入到pipe设备,每次写入period_len长度的音频数据。
6.1.5.4. 数据结构设计¶
6.1.5.4.1. hal层数据结构¶
struct aic_audio_buf_info
{
    void *buf;
    uint32_t buf_len;
    uint32_t period_len;
};
struct aic_audio_transfer_info
{
    struct aic_dma_chan *dma_chan;
    struct aic_audio_buf_info buf_info;
    int transfer_type;
};
struct aic_audio_ctrl
{
    unsigned long reg_base;
    uint32_t irq_num;
    uint32_t clk_id;
    struct aic_audio_transfer_info tx_info;   //TX buffer的参数
    struct aic_audio_transfer_info dmic_info; //DMIC RX buffer的参数
    struct aic_audio_transfer_info amic_info; //AMIC RX buffer的参数
    audio_callback callback;
    void *arg;
    struct aic_audio_config config;
};
6.1.5.4.2. driver层数据结构¶
struct aic_audio
{
    struct rt_audio_device audio;
    aic_audio_ctrl codec;
    rt_uint8_t volume;      //playback音量
    char *pa_name;          //PA引脚的名字
    unsigned int gpio_pa;   //PA引脚的IO口号
};
struct aic_dmic
{
    struct rt_audio_device audio;
    aic_audio_ctrl codec;
    rt_uint8_t volume;
    uint8_t index;
};
6.1.5.5. 接口设计¶
6.1.5.5.1. driver层接口设计¶
driver层将audio定义为 sound0 , dmic0 , amic0 三个设备,三个设备在driver层的接口基本相同,下面以playback端的接口进行说明。
6.1.5.5.1.1. drv_audio_init¶
| 函数原型 | rt_err_t drv_audio_init(struct rt_audio_device *audio) | 
|---|---|
| 功能说明 | playback端的初始化函数 | 
| 参数定义 | audio:指向playback设备的指针 | 
| 返回值 | RT_EOK:执行成功 | 
| 注意事项 | 
6.1.5.5.1.2. drv_audio_buffer_info¶
| 函数原型 | void drv_audio_buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info) | 
|---|---|
| 功能说明 | 获取playback端的TX buffer参数 | 
| 参数定义 | audio:指向playback设备的指针 info:用于获取TX buffer参数的指针 | 
| 返回值 | 无 | 
| 注意事项 | 
6.1.5.5.1.3. drv_audio_start¶
| 函数原型 | rt_err_t drv_audio_start(struct rt_audio_device *audio, int stream) | 
|---|---|
| 功能说明 | 开始playback端播放 | 
| 参数定义 | audio:指向playback设备的指针 stream:音频数据流方向 | 
| 返回值 | RT_EOK:执行成功 -RT_EINVAL:参数非法 | 
| 注意事项 | 
6.1.5.5.1.4. drv_audio_stop¶
| 函数原型 | rt_err_t drv_audio_stop(struct rt_audio_device *audio, int stream) | 
|---|---|
| 功能说明 | 结束playback端播放 | 
| 参数定义 | audio:指向playback设备的指针 stream:音频数据流方向 | 
| 返回值 | RT_EOK:执行成功 -RT_EINVAL:参数非法 | 
| 注意事项 | 
6.1.5.5.1.5. drv_audio_pause¶
| 函数原型 | rt_err_t drv_audio_pause(struct rt_audio_device *audio, int enable) | 
|---|---|
| 功能说明 | 暂停/恢复playback端播放 | 
| 参数定义 | audio:指向playback设备的指针 enable:playback端暂停和恢复播放使能位(0为恢复,非0为暂停) | 
| 返回值 | RT_EOK:执行成功 | 
| 注意事项 | 
6.1.5.5.1.6. drv_audio_configure¶
| 函数原型 | rt_err_t drv_audio_configure(struct rt_audio_device *audio, struct rt_audio_caps *caps) | 
|---|---|
| 功能原型 | 音频设备配置接口,用于配置采样格式,采样率,通道数等接口 | 
| 参数定义 | audio:指向playback设备的指针 caps:指向配置参数的指针 | 
| 返回值 | RT_EOK:执行成功 -RT_ERROR:参数不支持 | 
| 注意事项 | 
6.1.5.5.1.7. drv_audio_getcaps¶
| 函数原型 | rt_err_t drv_audio_getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps) | 
|---|---|
| 功能说明 | 获取音频设备的参数 | 
| 参数定义 | audio:指向playback设备的指针 caps:指向配置参数的指针 | 
| 返回值 | RT_EOK:执行成功 -RT_ERROR:参数不支持 | 
| 注意事项 | 
6.1.5.5.1.8. drv_audio_get_playback_avail¶
| 函数原型 | rt_size_t drv_audio_get_playback_avail(struct rt_audio_device *audio) | 
|---|---|
| 功能说明 | 获取playback端缓存的数据大小 | 
| 参数定义 | audio:指向playback设备的指针 | 
| 返回值 | 缓存数据的大小 | 
| 注意事项 | 
6.1.5.5.1.9. hal层接口设计¶
hal层接口也是分playback、DMIC、AMIC三部分进行设计,下面以playback端的接口进行说明。
6.1.5.5.1.10. hal_audio_set_samplerate¶
| 函数原型 | void hal_audio_set_samplerate(aic_audio_ctrl *codec, uint32_t samplerate) | 
|---|---|
| 功能说明 | 设置采样率 | 
| 参数定义 | codec:指向aic_audio_ctrl的指针 samplerate:采样率 | 
| 返回值 | 无 | 
| 注意事项 | 
6.1.5.5.1.11. hal_audio_set_playback_channel¶
| 函数原型 | void hal_audio_set_playback_channel(aic_audio_ctrl *codec, uint32_t ch) | 
|---|---|
| 功能说明 | 设置playback端的通道数 | 
| 参数定义 | codec:指向aic_audio_ctrl的指针 ch:通道数 | 
| 返回值 | 无 | 
| 注意事项 | 
6.1.5.5.1.12. hal_audio_set_playback_by_spk0¶
| 函数原型 | void hal_audio_set_playback_by_spk0(aic_audio_ctrl *codec) | 
|---|---|
| 功能说明 | 配置SPK0音频通路 | 
| 参数定义 | codec:指向aic_audio_ctrl的指针 | 
| 返回值 | 无 | 
| 注意事项 | 
6.1.5.5.1.13. hal_audio_set_playback_by_spk1¶
| 函数原型 | void hal_audio_set_playback_by_spk1(aic_audio_ctrl *codec) | 
|---|---|
| 功能说明 | 配置SPK1音频通路 | 
| 参数定义 | codec:指向aic_audio_ctrl的指针 | 
| 返回值 | 无 | 
| 注意事项 | 
6.1.5.5.1.14. hal_audio_playback_start¶
| 函数原型 | void hal_audio_playback_start(aic_audio_ctrl *codec) | 
|---|---|
| 功能说明 | 开始播放 | 
| 参数定义 | codec:指向aic_audio_ctrl的指针 | 
| 返回值 | 无 | 
| 注意事项 | 
6.1.5.5.1.15. hal_audio_playback_stop¶
| 函数原型 | void hal_audio_playback_stop(aic_audio_ctrl *codec) | 
|---|---|
| 功能说明 | 结束播放 | 
| 参数定义 | codec:指向aic_audio_ctrl的指针 | 
| 返回值 | 无 | 
| 注意事项 |