首页 > 代码库 > SylixOS音频驱动移植
SylixOS音频驱动移植
1. 适用范围
本文档为实现Nuc970平台音频驱动的方法总结,以此提供一些SylixOS音频驱动移植方法的参考。
2. 原理概述
2.1 Codec编解码芯片
声音信号分为模拟信号和数字信号,Codec编解码芯片主要功能就是实现模拟信号与数字信号的互相转换。
本文调试的Codec型号为NAU8822L,其结构如图 2-1所示。
图 2-1 NAU8822L编解码芯片
其中主要使用到的是以下三个部分:
Mixer 混音器设备,它的作用是将多个信号组合或者叠加在一起。
由输入混音器(input mixer)和输出混音器(output mixer)组成。
ADC 模拟信号转换数字信号单元。
由左路ADC和右路ADC组成。
DAC 数字信号转换模拟信号单元。
由左路DAC和右路DAC组成。
2.2 采样率、量化位数、声道
采样率 每秒从连续信号中提取并组成离散信号的采样个数。
量化位数 模拟量转换成数字量之后的数据位数。
声道 声音录制时的音源数量或回放时相应的扬声器数量。
2.3 I2S总线
I2S(Inter-IC Sound Bus)是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准。I2S有3个主要信号,如表 2-1所示。
表 2-1 i2s信号类型
信号 | 名称 | 功能 |
BCLK | 位时钟 | 数字音频的每一位数据,BCLK都有1个脉冲。 频率 = 2 ×采样频率×量化位数 |
LRCK | 左右声道切换时钟 | 用于切换左右声道的数据,频率等于采样频率。 为"1"表示正在传输的是左声道的数据, 为"0"表示正在传输的是右声道的数据。 |
SDATA | 串行数据 | 用二进制补码表示的音频数据 |
2.4 OSS统一音频接口
2.4.1 声卡模型
SylixOS中使用的是OSS简化后的声卡模型,将声卡抽象为了以下两种设备:
/dev/dsp 实现录音和放音功能。
向该设备写数据即意味着激活声卡上的D/A转换器进行放音,
向该设备读数据则意味着激活声卡上的A/D转换器进行录音。
/dev/mixer 实现混音器调节功能。
对该设备调用ioctl,即可控制声卡上的Mixer功能进行调节。
2.4.2 IO控制
/dev/dsp和/dev/mixer分别提供了ioctl的接口用于对其功能进行配置和操作。
开发时,应该根据OSS的框架以及实际的Codec情况在驱动中实现这些命令相关的功能。
/dev/dsp提供了如表 2-2所示的ioctl命令。
表 2-2 dsp相关ioctl参数
命令 | 说明 |
SNDCTL_DSP_SETTRIGGER | 用于启动播放和录音功能 |
SNDCTL_DSP_GETOSPACE SNDCTL_DSP_GETISPACE | 用于获得输出/输入buffer相关的信息 |
SNDCTL_DSP_GETCAPS | 用于获得Codec相关的属性参数 |
SNDCTL_DSP_SETFRAGMENT | 用于设置Codec和buffer相关的参数 |
…… | …… |
/dev/mixer提供了如表 2-3所示的ioctl命令
表 2-3 mixer相关ioctl参数
命令 | 说明 |
SOUND_MIXER_VOLUME | 用于调节主输出音量 |
SOUND_MIXER_PCM | 用于调节Codec(ADC)输出音量 |
SOUND_MIXER_MIC | 用于调节Mic输入音量 |
…… | …… |
3. 技术实现
3.1 驱动框架
在Nuc970的音频驱动代码中采用了如图 3-1所示的框架。
Device层 用于实现抽象的dsp和mixer设备,提供统一的API接口。
AudioLib层 用于实现Codec和I2S的相关逻辑,供Device层调用,Codec和I2S之间应同步配置。
Hardware层 用于实现具体的Codec和I2S的寄存器访问,和具体硬件相关。
图 3-1音频驱动框架
3.2 调试方法
当喇叭还未有声音产生或录音文件没有数据时,需要借助示波器进行调试。
总体思路是在Codec的输入端和输出端分别测量数字信号和模拟信号,用于确定问题点所在位置。
数字信号下的音频波形即为i2s输出的波形,大致形状如图 3-2所示。
图 3-2 i2s波形
数字信号下的模拟波形大致形状如图 3-3所示。
图 3-3音频模拟信号波形
3.2.1 播放音乐调试
当调试播放功能时,可以参考如图 3-4所示流程,确定问题点所在位置,以此明确需要修改的内容。
图 3-4播放音乐功能调试流程
3.2.2 录制声音调试
当调试录音功能时,可以参考如图 3-5所示流程,确定问题点所在位置,以此明确需要修改的内容。
图 3-5录制声音功能调试流程
3.3 DMA缓冲机制
Nuc970的I2S可以将DMA分块,对于Play和Record使用的DMA都可以按图 3-6所示各自等分成2至8块。
图 3-6 DMA扫描
I2S的DMA本身处于实时刷新状态。以播放为例,DMA不停地从内存搬运数据送往Codec。如果DMA正在读取区域内的数据仍未更新,Codec转换出来的模拟音频信号就会产生杂音。因此,必须保证DMA读取对应内存区域前,其中的数据已经被更新。
Nuc970可以在某分块区域被DMA读取完后产生中断,软件通过读取寄存器可以得知是哪个分块已经被读取完成,此时就可以更新这块刚刚被读取完的分块中的数据,以保证下次DMA读取此分区时其中的数据已经为有效数据,如图 3-7所示。
图 3-7扫描分块结束后产生中断
3.4 播放音乐代码实现
3.4.1 应用代码实现
程序清单 3-1播放音乐应用代码实现
/* * 以只写方式打开dsp设备 */ iDspFd = open("/dev/dsp", O_WRONLY, 0666); if (iDspFd < 0) { printf("failed to open /dev/dsp device!\n"); break; } /* * 设置量化位数 */ iSampleFmt = wavHeader.sPCMBitWidth == 16 ? AFMT_S16_LE : AFMT_S8; stRet = ioctl(iDspFd, SNDCTL_DSP_SETFMT, &iSampleFmt); if (stRet < 0) { printf("failed to set sample format!\n"); close(iDspFd); break; } /* * 设置声道数 */ iChannels = wavHeader.sChannel; stRet = ioctl(iDspFd, SNDCTL_DSP_CHANNELS, &iChannels); if (stRet < 0) { printf("failed to set channels!\n"); close(iDspFd); break; } /* * 设置采样率 */ iSampleRate = wavHeader.lSampleRate; stRet = ioctl(iDspFd, SNDCTL_DSP_SPEED, &iSampleRate); if (stRet < 0) { printf("failed to set sample rate!\n"); close(iDspFd); break; } /* * 打开wav音频文件 */ iFileFd = open(pcFileName, O_RDONLY, 0666); if (iFileFd < 0) { printf("failed to open test audio file %s!\n", pcFileName); close(iDspFd); break; } read(iFileFd, pcBuffer, 0x2E); /* * 循环读取wav音频文件,写入dsp进行播放 */ while((stLen = read(iFileFd, pcBuffer, __OSS_PLAY_BUFFER_LEN)) > 0) { pcPtr = pcBuffer; while (stLen > 0) { stRet = write(iDspFd, pcPtr, stLen); if (stRet < 0) { break; } pcPtr += stRet; stLen -= stRet; } }
3.4.2 驱动代码实现
Nuc970采用了如图 3-8所示的音频驱动代码结构,此代码结构与图 3-1所示驱动框架相对应。
图 3-8代码结构
Audio驱动实现的基本流程如图 3-9所示,开始播放声音时,驱动需要先对Codec进行配置,然后开启DMA传输,此时DMA就在实时读取内存。
当DMA其中一个区域读取完成后,就会产生中断,此时将用户层写入的新数据填充到DMA中即可。
当音频文件播放结束时,停止DMA传输即可。
图 3-9音频驱动流程
4. 总结
本文章结合Nuc970说明了SylixOS中音频驱动移植的方法。
SylixOS音频驱动移植