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中音頻驅動移植的方法。