Linux音頻設備驅動

Linux音頻設備驅動 (2012-02-23 10:03)


在Linux中,先後出現了音頻設備的兩種框架OSS和ALSA,本節將在介紹數字音頻設備及音頻設備硬件接口的基礎上,展現OSS和ALSA驅動的結構。
17.1~17.2節講解了音頻設備及PCM、IIS和AC97硬件接口。
17.3節闡述了Linux OSS音頻設備驅動的組成、mixer接口、dsp接口及用戶空間編程方法。
17.4節闡述了Linux ALSA音頻設備驅動的組成、card和組件管理、PCM設備、control接口、AC97 API及用戶空間編程方法。
17.5節以S3C2410通過IIS接口外接UDA1341編解碼器的實例講解了OSS驅動。
17.6節以PXA255通過AC97接口外接AC97 編解碼器的實例講解了ALSA驅動。
17.1數字音頻設備
目前,手機、PDA、MP3等許多嵌入式設備中包含了數字音頻設備,一個典型的數字音頻系統的電路組成如圖17.1所示。圖17.1中的嵌入式微控制器 /DSP中集成了PCM、IIS或AC97音頻接口,通過這些接口連接外部的音頻編解碼器即可實現聲音的AD和DA轉換,圖中的功放完成模擬信號的放大功能。

圖17.1 典型的數字音頻系統電路
音頻編解碼器是數字音頻系統的核心,衡量它的指標主要有:
? 採樣頻率
採樣的過程就是將通常的模擬音頻信號的電信號轉換成二進制碼0和1的過程,這些0和1便構成了數字音頻文件。如圖17.2中的正弦曲線代表原始音頻曲線,方格代表採樣後得到的結果,二者越吻合說明採樣結果越好。
採樣頻率是每秒鐘的採樣次數,我們常說的 44.1kHz 採樣頻率就是每秒鐘採樣44100 次。理論上採樣頻率越高,轉換精度越高,目前主流的採樣頻率是48kHz。
? 量化精度
量化精度是指對採樣數據分析的精度,比如24bit量化精度就是是將標準電平信號按照2的24次方進行分析,也就是說將圖17.2中的縱座標等分爲224等分。量化精度越高,聲音就越逼真。

圖17.2 數字音頻採樣
17.2音頻設備硬件接口
17.2.1 PCM接口
針對不同的數字音頻子系統,出現了幾種微處理器或DSP與音頻器件間用於數字轉換的接口。
最簡單的音頻接口是PCM(脈衝編碼調製)接口,該接口由時鐘脈衝(BCLK)、幀同步信號(FS)及接收數據(DR)和發送數據(DX)組成。在FS信號的上升沿,數據傳輸從MSB(Most Significant Bit)字開始,FS頻率等於採樣率。FS信號之後開始數據字的傳輸,單個的數據位按順序進行傳輸,1個時鐘週期傳輸1個數據字。發送MSB時,信號的等級首先降到最低,以避免在不同終端的接口使用不同的數據方案時造成MSB的丟失。
PCM接口很容易實現,原則上能夠支持任何數據方案和任何採樣率,但需要每個音頻通道獲得一個獨立的數據隊列。
17.2.2 IIS接口
IIS 接口(Inter-IC Sound)在20世紀80年代首先被飛利浦用於消費音頻,並在一個稱爲LRCLK(Left/Right CLOCK)的信號機制中經過多路轉換,將兩路音頻信號變成單一的數據隊列。當LRCLK爲高時,左聲道數據被傳輸;LRCLK爲低時,右聲道數據被傳輸。與PCM相比,IIS更適合於立體聲系統。對於多通道系統,在同樣的BCLK和LRCLK條件下,並行執行幾個數據隊列也是可能的。
17.2.3 AC97接口
AC'97(Audio Codec 1997)是以Intel爲首的五個PC廠商Intel、Creative Labs、NS、Analog Device與Yamaha共同提出的規格標準。與PCM和IIS不同,AC'97不只是一種數據格式,用於音頻編碼的內部架構規格,它還具有控制功能。 AC'97採用AC-Link與外部的編解碼器相連,AC-Link接口包括位時鐘(BITCLK)、同步信號校正(SYNC)和從編碼到處理器及從處理器中解碼(SDATDIN與SDATAOUT)的數據隊列。AC'97數據幀以SYNC脈衝開始,包括12個20位時間段(時間段爲標準中定義的不同的目的服務)及16位“tag”段,共計256個數據序列。例如,時間段“1”和“2”用於訪問編碼的控制寄存器,而時間段“3”和“4”分別負載左、右兩個音頻通道。“tag”段表示其他段中哪一個包含有效數據。把幀分成時間段使傳輸控制信號和音頻數據僅通過4根線到達9個音頻通道或轉換成其他數據流成爲可能。與具有分離控制接口的IIS方案相比,AC'97明顯減少了整體管腳數。一般來說,AC'97 編解碼器採用TQFP48封裝,如圖17.3所示。

圖17.3 AC97 Codec芯片
PCM、IIS和AC97各有其優點和應用範圍,例如在CD、MD、MP3隨身聽多采用IIS接口,移動電話會採用PCM接口,具有音頻功能的PDA則多使用和PC一樣的AC'97編碼格式。
17.3 Linux OSS音頻設備驅動
17.3.1 OSS驅動的組成
OSS標準中有2個最基本的音頻設備:mixer(混音器)和DSP(數字信號處理器)。
在聲卡的硬件電路中,mixer是一個很重要的組成部分,它的作用是將多個信號組合或者疊加在一起,對於不同的聲卡來說,其混音器的作用可能各不相同。OSS驅動中,/dev/mixer設備文件是應用程序對mixer進行操作的軟件接口。
混音器電路通常由兩個部分組成:輸入混音器(input mixer)和輸出混音器(output mixer)。輸入混音器負責從多個不同的信號源接收模擬信號,這些信號源有時也被稱爲混音通道或者混音設備。模擬信號通過增益控制器和由軟件控制的音量調節器後,在不同的混音通道中進行級別(level)調製,然後被送到輸入混音器中進行聲音的合成。混音器上的電子開關可以控制哪些通道中有信號與混音器相連,有些聲卡只允許連接一個混音通道作爲錄音的音源,而有些聲卡則允許對混音通道做任意的連接。經過輸入混音器處理後的信號仍然爲模擬信號,它們將被送到A/D轉換器進行數字化處理。
輸出混音器的工作原理與輸入混音器類似,同樣也有多個信號源與混音器相連,並且事先都經過了增益調節。當輸出混音器對所有的模擬信號進行了混合之後,通常還會有一個總控增益調節器來控制輸出聲音的大小,此外還有一些音調控制器來調節輸出聲音的音調。經過輸出混音器處理後的信號也是模擬信號,它們最終會被送給喇叭或者其它的模擬輸出設備。對混音器的編程包括如何設置增益控制器的級別,以及怎樣在不同的音源間進行切換,這些操作通常來講是不連續的,而且不會像錄音或者放音那樣需要佔用大量的計算機資源。由於混音器的操作不符合典型的讀/寫操作模式,因此除了 open()和close()兩個系統調用之外,大部分的操作都是通過ioctl()系統調用來完成的。與/dev/dsp不同,/dev/mixer允許多個應用程序同時訪問,並且混音器的設置值會一直保持到對應的設備文件被關閉爲止。
DSP也稱爲編解碼器,實現錄音(錄音)和放音(播放),其對應的設備文件是/dev/dsp或/dev/sound/dsp。OSS聲卡驅動程序提供的 /dev/dsp是用於數字採樣和數字錄音的設備文件,向該設備寫數據即意味着激活聲卡上的D/A轉換器進行放音,而向該設備讀數據則意味着激活聲卡上的 A/D轉換器進行錄音。
在從DSP設備讀取數據時,從聲卡輸入的模擬信號經過A/D轉換器變成數字採樣後的樣本,保存在聲卡驅動程序的內核緩衝區中,當應用程序通過 read()系統調用從聲卡讀取數據時,保存在內核緩衝區中的數字採樣結果將被複制到應用程序所指定的用戶緩衝區中。需要指出的是,聲卡採樣頻率是由內核中的驅動程序所決定的,而不取決於應用程序從聲卡讀取數據的速度。如果應用程序讀取數據的速度過慢,以致低於聲卡的採樣頻率,那麼多餘的數據將會被丟棄(即overflow);如果讀取數據的速度過快,以致高於聲卡的採樣頻率,那麼聲卡驅動程序將會阻塞那些請求數據的應用程序,直到新的數據到來爲止。
在向DSP設備寫入數據時,數字信號會經過D/A轉換器變成模擬信號,然後產生出聲音。應用程序寫入數據的速度應該至少等於聲卡的採樣頻率,過慢會產生聲音暫停或者停頓的現象(即underflow)。如果用戶寫入過快的話,它會被內核中的聲卡驅動程序阻塞,直到硬件有能力處理新的數據爲止。
與其它設備有所不同,聲卡通常不需要支持非阻塞(non-blocking)的I/O操作。即便內核OSS驅動提供了非阻塞的I/O支持,用戶空間也不宜採用。
無論是從聲卡讀取數據,或是向聲卡寫入數據,事實上都具有特定的格式(format),如無符號8位、單聲道、8KHz採樣率,如果默認值無法達到要求,可以通過ioctl()系統調用來改變它們。通常說來,在應用程序中打開設備文件/dev/dsp之後,接下去就應該爲其設置恰當的格式,然後才能從聲卡讀取或者寫入數據。
17.3.2 mixer接口
int register_sound_mixer(struct file_operations *fops, int dev);
上述函數用於註冊1個混音器,第1個參數fops即是文件操作接口,第2個參數dev是設備編號,如果填入-1,則系統自動分配1個設備編號。mixer 是 1個典型的字符設備,因此編碼的主要工作是實現file_operations中的open()、ioctl()等函數。
mixer接口file_operations中的最重要函數是ioctl(),它實現混音器的不同IO控制命令,代碼清單17.1給出了1個ioctl()的範例。
代碼清單17.1 mixer()接口ioctl()函數範例
1 static int mixdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
2 {
3    ...
4    switch (cmd)
5    {
6      case SOUND_MIXER_READ_MIC:
7        ...
8      case SOUND_MIXER_WRITE_MIC:
9        ...
10     case SOUND_MIXER_WRITE_RECSRC:
11       ...
12     case SOUND_MIXER_WRITE_MUTE:
13       ...
14   }
15   //其它命令
16   return mixer_ioctl(codec, cmd, arg);
17 }
17.3.3 DSP接口
int register_sound_dsp(struct file_operations *fops, int dev);
上述函數與register_sound_mixer()類似,它用於註冊1個dsp設備,第1個參數fops即是文件操作接口,第2個參數dev是設備編號,如果填入-1,則系統自動分配1個設備編號。dsp也是1個典型的字符設備,因此編碼的主要工作是實現file_operations中的 read()、write()、ioctl()等函數。
dsp接口file_operations中的read()和write()函數非常重要,read()函數從音頻控制器中獲取錄音數據到緩衝區並拷貝到用戶空間,write()函數從用戶空間拷貝音頻數據到內核空間緩衝區並最終發送到音頻控制器。
dsp接口file_operations中的ioctl()函數處理對採樣率、量化精度、DMA緩衝區塊大小等參數設置IO控制命令的處理。
在數據從緩衝區拷貝到音頻控制器的過程中,通常會使用DMA,DMA對聲卡而言非常重要。例如,在放音時,驅動設置完DMA控制器的源數據地址(內存中 DMA緩衝區)、目的地址(音頻控制器FIFO)和DMA的數據長度,DMA控制器會自動發送緩衝區的數據填充FIFO,直到發送完相應的數據長度後才中斷一次。
在OSS驅動中,建立存放音頻數據的環形緩衝區(ring buffer)通常是值得推薦的方法。此外,在OSS驅動中,一般會將1個較大的DMA緩衝區分成若干個大小相同的塊(這些塊也被稱爲“段”,即 fragment),驅動程序使用DMA每次在聲音緩衝區和聲卡之間搬移一個fragment。在用戶空間,可以使用ioctl()系統調用來調整塊的大小和個數。
除了read()、write()和ioctl()外,dsp接口的poll()函數通常也需要被實現,以向用戶反饋目前能否讀寫DMA緩衝區。
在OSS驅動初始化過程中,會調用register_sound_dsp()和register_sound_mixer()註冊dsp和mixer設備;在模塊卸載的時候,會調用如代碼清單17.2。
代碼清單17.2 OSS驅動初始化註冊dsp和mixer設備
1 static int xxx_init(void)
2 {
3    struct xxx_state *s = &xxx_state;
4    ...
5    //註冊dsp設備
6    if ((audio_dev_dsp = register_sound_dsp(&xxx_audio_fops, - 1)) < 0)
7      goto err_dev1;
8    //設備mixer設備
9    if ((audio_dev_mixer = register_sound_mixer(&xxx_mixer_fops, - 1)) < 0)
10     goto err_dev2;
11   ...
12 }
13
14 void __exit xxx_exit(void)
15 {
16 //註銷dsp和mixer設備接口
17 unregister_sound_dsp(audio_dev_dsp);
18 unregister_sound_mixer(audio_dev_mixer);
19 ...
20 }
根據17.3.2和17.3.3節的分析,可以畫出一個Linux OSS驅動結構的簡圖,如圖17.4所示。

圖17.4 Linux OSS驅動結構
17.3.4 OSS用戶空間編程
1、DSP編程
對OSS驅動聲卡的編程使用Linux文件接口函數,如圖17.5,DSP接口的操作一般包括如下幾個步驟:
① 打開設備文件/dev/dsp。
採用何種模式對聲卡進行操作也必須在打開設備時指定,對於不支持全雙工的聲卡來說,應該使用只讀或者只寫的方式打開,只有那些支持全雙工的聲卡,才能以讀寫的方式打開,這還依賴於驅動程序的具體實現。Linux允許應用程序多次打開或者關閉與聲卡對應的設備文件,從而能夠很方便地在放音狀態和錄音狀態之間進行切換。
② 如果有需要,設置緩衝區大小。
運行在Linux內核中的聲卡驅動程序專門維護了一個緩衝區,其大小會影響到放音和錄音時的效果,使用ioctl()系統調用可以對它的尺寸進行恰當的設置。調節驅動程序中緩衝區大小的操作不是必須的,如果沒有特殊的要求,一般採用默認的緩衝區大小也就可以了。如果想設置緩衝區的大小,則通常應緊跟在設備文件打開之後,這是因爲對聲卡的其它操作有可能會導致驅動程序無法再修改其緩衝區的大小。
③ 設置聲道(channel)數量。
根據硬件設備和驅動程序的具體情況,可以設置爲單聲道或者立體聲。
④ 設置採樣格式和採樣頻率
採樣格式包括AFMT_U8(無符號8位)、AFMT_S8(有符號8位)、AFMT_U16_LE(小端模式,無符號16位)、 AFMT_U16_BE(大端模式,無符號16位)、AFMT_MPEG、AFMT_AC3等。使用SNDCTL_DSP_SETFMT IO控制命令可以設置採樣格式。
對於大多數聲卡來說,其支持的採樣頻率範圍一般爲5kHz到44.1kHz或者48kHz,但並不意味着該範圍內的所有連續頻率都會被硬件支持,在 Linux下進行音頻編程時最常用到的幾種採樣頻率是11025Hz、16000Hz、22050Hz、32000Hz 和44100Hz。使用SNDCTL_DSP_SPEED IO控制命令可以設置採樣頻率。
⑤ 讀寫/dev/dsp實現播放或錄音。

圖17.5 OSS dsp接口用戶空間操作流程
代碼清單17.3的程序實現了利用/dev/dsp接口進行聲音錄製和播放的過程,它的功能是先錄製幾秒鐘音頻數據,將其存放在內存緩衝區中,然後再進行放音。
代碼清單17.3 OSS DSP接口應用編程範例
1 #include <unistd.h>
2 #include <fcntl.h>
3 #include <sys/types.h>
4 #include <sys/ioctl.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <linux/soundcard.h>
8 #define LENGTH 3    /* 存儲秒數 */
9 #define RATE 8000   /* 採樣頻率 */
10 #define SIZE 8      /* 量化位數 */
11 #define CHANNELS 1 /* 聲道數目 */
12 /* 用於保存數字音頻數據的內存緩衝區 */
13 unsigned char buf[LENGTH *RATE * SIZE * CHANNELS / 8];
14 int main()
15 {
16   int fd; /* 聲音設備的文件描述符 */
17   int arg; /* 用於ioctl調用的參數 */
18   int status; /* 系統調用的返回值 */
19   /* 打開聲音設備 */
20   fd = open("/dev/dsp", O_RDWR);
21   if (fd < 0)
22   {
23     perror("open of /dev/dsp failed");
24     exit(1);
25   }
26   /* 設置採樣時的量化位數 */
27   arg = SIZE;
28   status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);
29   if (status == - 1)
30     perror("SOUND_PCM_WRITE_BITS ioctl failed");
31   if (arg != SIZE)
32     perror("unable to set sample size");
33   /* 設置採樣時的通道數目 */
34   arg = CHANNELS;
35   status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg);
36   if (status == - 1)
37     perror("SOUND_PCM_WRITE_CHANNELS ioctl failed");
38   if (arg != CHANNELS)
39     perror("unable to set number of channels");
40   /* 設置採樣率 */
41   arg = RATE;
42   status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg);
43   if (status == - 1)
44     perror("SOUND_PCM_WRITE_WRITE ioctl failed");
45   /* 循環,直到按下Control-C */
46   while (1)
47   {
48     printf("Say something:/n");
49     status = read(fd, buf, sizeof(buf)); /* 錄音 */
50     if (status != sizeof(buf))
51       perror("read wrong number of bytes");
52     printf("You said:/n");
53     status = write(fd, buf, sizeof(buf)); /* 放音 */
54     if (status != sizeof(buf))
55       perror("wrote wrong number of bytes");
56     /* 在繼續錄音前等待放音結束 */
57     status = ioctl(fd, SOUND_PCM_SYNC, 0);
58     if (status == - 1)
59       perror("SOUND_PCM_SYNC ioctl failed");
60   }
61 }
2、mixer編程
聲卡上的混音器由多個混音通道組成,它們可以通過驅動程序提供的設備文件/dev/mixer進行編程。對混音器的操作一般都通過ioctl()系統調用來完成,所有控制命令都以SOUND_MIXER或者MIXER開頭,表17.1列出了常用的混音器控制命令。
表17.1 混音器常用命令
命 令 作 用
SOUND_MIXER_VOLUME 主音量調節
SOUND_MIXER_BASS 低音控制
SOUND_MIXER_TREBLE 高音控制
SOUND_MIXER_SYNTH FM合成器
SOUND_MIXER_PCM 主D/A轉換器
SOUND_MIXER_SPEAKER PC喇叭
SOUND_MIXER_LINE 音頻線輸入
SOUND_MIXER_MIC 麥克風輸入
SOUND_MIXER_CD CD輸入
SOUND_MIXER_IMIX 放音音量
SOUND_MIXER_ALTPCM 從D/A 轉換器
SOUND_MIXER_RECLEV 錄音音量
SOUND_MIXER_IGAIN 輸入增益
SOUND_MIXER_OGAIN 輸出增益
SOUND_MIXER_LINE1 聲卡的第1輸入
SOUND_MIXER_LINE2 聲卡的第2輸入
SOUND_MIXER_LINE3 聲卡的第3輸入
對聲卡的輸入增益和輸出增益進行調節是混音器的一個主要作用,目前大部分聲卡採用的是8位或者16位的增益控制器,聲卡驅動程序會將它們變換成百分比的形式,也就是說無論是輸入增益還是輸出增益,其取值範圍都是從0到100。
? SOUND_MIXER_READ宏
在進行混音器編程時,可以使用 SOUND_MIXER_READ宏來讀取混音通道的增益大小,例如如下代碼可以獲得麥克風的輸入增益:
ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);
對於只有一個混音通道的單聲道設備來說,返回的增益大小保存在低位字節中。而對於支持多個混音通道的雙聲道設備來說,返回的增益大小實際上包括兩個部分,分別代表左、右兩個聲道的值,其中低位字節保存左聲道的音量,而高位字節則保存右聲道的音量。下面的代碼可以從返回值中依次提取左右聲道的增益大小:
int left, right;
left = vol & 0xff;
right = (vol & 0xff00) >> 8;
? SOUND_MIXER_WRITE宏
如果想設置混音通道的增益大小,則可以通過SOUND_MIXER_WRITE宏來實現,例如下面的語句可以用來設置麥克風的輸入增益:
vol = (right << 8) + left;
ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);
? 查詢Mixer信息
聲卡驅動程序提供了多個ioctl()系統調用來獲得混音器的信息,它們通常返回一個整型的位掩碼,其中每一位分別代表一個特定的混音通道,如果相應的位爲1,則說明與之對應的混音通道是可用的。
通過 SOUND_MIXER_READ_DEVMASK返回的位掩碼查詢出能夠被聲卡支持的每一個混音通道,而通過 SOUND_MIXER_READ_RECMAS返回的位掩碼則可以查詢出能夠被當作錄音源的每一個通道。例如,如下代碼可用來檢查CD輸入是否是一個有效的混音通道:
ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
if (devmask & SOUND_MIXER_CD)
printf("The CD input is supported");
如下代碼可用來檢查CD輸入是否是一個有效的錄音源:
ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask);
if (recmask & SOUND_MIXER_CD)
printf("The CD input can be a recording source");
大多數聲卡提供了多個錄音源,通過 SOUND_MIXER_READ_RECSRC可以查詢出當前正在使用的錄音源,同一時刻可使用2個或2個以上的錄音源,具體由聲卡硬件本身決定。相應地,使用 SOUND_MIXER_WRITE_RECSRC可以設置聲卡當前使用的錄音源,如下代碼可以將CD輸入作爲聲卡的錄音源使用:
devmask = SOUND_MIXER_CD;
ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &devmask);
此外,所有的混音通道都有單聲道和雙聲道的區別,如果需要知道哪些混音通道提供了對立體聲的支持,可以通過SOUND_MIXER_READ_STEREODEVS來獲得。
代碼清單17.4的程序實現了利用/dev/mixer接口對混音器進行編程的過程,該程序可對各種混音通道的增益進行調節。
代碼清單17.4 OSS mixer接口應用編程範例
1   #include <unistd.h>
2   #include <stdlib.h>
3   #include <stdio.h>
4   #include <sys/ioctl.h>
5   #include <fcntl.h>
6   #include <linux/soundcard.h>
7   /* 用來存儲所有可用混音設備的名稱 */
8   const char *sound_device_names[] = SOUND_DEVICE_NAMES;
9   int fd; /* 混音設備所對應的文件描述符 */
10 int devmask, stereodevs; /* 混音器信息對應的bit掩碼 */
11 char *name;
12 /* 顯示命令的使用方法及所有可用的混音設備 */
13 void usage()
14 {
15    int i;
16    fprintf(stderr, "usage: %s <device> <left-gain%%> <right-gain%%>/n"
17      "%s <device> <gain%%>/n/n""Where <device> is one of:/n", name, name);
18    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
19      if ((1 << i) &devmask)
20     /* 只顯示有效的混音設備 */
21        fprintf(stderr, "%s ", sound_device_names[i]);
22    fprintf(stderr, "/n");
23    exit(1);
24 }
25
26 int main(int argc, char *argv[])
27 {
28    int left, right, level; /* 增益設置 */
29    int status; /* 系統調用的返回值 */
30    int device; /* 選用的混音設備 */
31    char *dev; /* 混音設備的名稱 */
32    int i;
33    name = argv[0];
34    /* 以只讀方式打開混音設備 */
35    fd = open("/dev/mixer", O_RDONLY);
36    if (fd == - 1)
37    {
38      perror("unable to open /dev/mixer");
39      exit(1);
40    }
41
42    /* 獲得所需要的信息 */
43    status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
44    if (status == - 1)
45      perror("SOUND_MIXER_READ_DEVMASK ioctl failed");
46    status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs);
47    if (status == - 1)
48      perror("SOUND_MIXER_READ_STEREODEVS ioctl failed");
49    /* 檢查用戶輸入 */
50    if (argc != 3 && argc != 4)
51      usage();
52    /* 保存用戶輸入的混音器名稱 */
53    dev = argv[1];
54    /* 確定即將用到的混音設備 */
55    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
56      if (((1 << i) &devmask) && !strcmp(dev, sound_device_names[i]))
57        break;
58    if (i == SOUND_MIXER_NRDEVICES)
59    {
60       /* 沒有找到匹配項 */
61      fprintf(stderr, "%s is not a valid mixer device/n", dev);
62      usage();
63    }
64    /* 查找到有效的混音設備 */
65    device = i;
66    /* 獲取增益值 */
67    if (argc == 4)
68    {
69      /* 左、右聲道均給定 */
70      left = atoi(argv[2]);
71      right = atoi(argv[3]);
72    }
73    else
74    {
75      /* 左、右聲道設爲相等 */
76      left = atoi(argv[2]);
77      right = atoi(argv[2]);
78    }
79
80    /* 對非立體聲設備給出警告信息 */
81    if ((left != right) && !((1 << i) &stereodevs))
82    {
83      fprintf(stderr, "warning: %s is not a stereo device/n", dev);
84    }
85
86    /* 將兩個聲道的值合到同一變量中 */
87    level = (right << 8) + left;
88
89    /* 設置增益 */
90    status = ioctl(fd, MIXER_WRITE(device), &level);
91    if (status == - 1)
92    {
93      perror("MIXER_WRITE ioctl failed");
94      exit(1);
95    }
96    /* 獲得從驅動返回的左右聲道的增益 */
97    left = level &0xff;
98    right = (level &0xff00) >> 8;
99    /* 顯示實際設置的增益 */
100   fprintf(stderr, "%s gain set to %d%% / %d%%/n", dev, left, right);
101   /* 關閉混音設備 */
102   close(fd);
103   return 0;
104 }
編譯上述程序爲可執行文件mixer,執行./mixer <device> <left-gain%> <right-gain%>或./mixer <device> <gain%>可設置增益,device可以是vol、pcm、speaker、line、mic、cd、igain、line1、 phin、video。
17.4 Linux ALSA音頻設備驅動
17.4.1 ALSA的組成
雖然OSS已經非常成熟,但它畢竟是一個沒有完全開放源代碼的商業產品,而ALSA (Advanced Linux Sound Architecture)恰好彌補了這一空白,它符合GPL,是在Linux下進行音頻編程時另一種可供選擇的聲卡驅動體系結構,其官方網站爲 http://www.alsa-project.org/。ALSA除了像OSS那樣提供了一組內核驅動程序模塊之外,還專門爲簡化應用程序的編寫提供了相應的函數庫,與OSS提供的基於ioctl的原始編程接口相比,ALSA函數庫使用起來要更加方便一些。ALSA的主要特點有:
? 支持多種聲卡設備
? 模塊化的內核驅動程序
? 支持SMP和多線程
? 提供應用開發函數庫(alsa-lib)以簡化應用程序開發
? 支持OSS API,兼容OSS應用程序
ALSA 具有更加友好的編程接口,並且完全兼容於OSS,對應用程序員來講無疑是一個更佳的選擇。ALSA系統包括驅動包alsa-driver、開發包 alsa-libs、開發包插件alsa-libplugins、設置管理工具包alsa-utils、其他聲音相關處理小程序包alsa-tools、特殊音頻固件支持包alsa- firmware、OSS接口兼容模擬層工具alsa-oss共7個子項目,其中只有驅動包是必需的。
alsa- driver指內核驅動程序,包括硬件相關的代碼和一些公共代碼,非常龐大,代碼總量達數十萬行;alsa-libs指用戶空間的函數庫,提供給應用程序使用,應用程序應包含頭文件asoundlib.h,並使用共享庫libasound.so;alsa-utils包含一些基於ALSA的用於控制聲卡的應用程序,如alsaconf(偵測系統中聲卡並寫一個適合的ALSA配置文件)、alsactl(控制ALSA聲卡驅動的高級設置)、 alsamixer(基於ncurses的混音器程序)、amidi(用於讀寫ALSA RawMIDI)、amixer(ALSA聲卡混音器的命令行控制)、aplay(基於命令行的聲音文件播放)、arecord(基於命令行的聲音文件錄製)等。
目前ALSA內核提供給用戶空間的接口有:
? 信息接口(Information Interface,/proc/asound)
? 控制接口(Control Interface,/dev/snd/controlCX)
? 混音器接口(Mixer Interface,/dev/snd/mixerCXDX)
? PCM接口(PCM Interface,/dev/snd/pcmCXDX)
? Raw迷笛接口(Raw MIDI Interface,/dev/snd/midiCXDX)
? 音序器接口(Sequencer Interface,/dev/snd/seq)
? 定時器接口(Timer Interface,/dev/snd/timer)
和OSS類似,上述接口也以文件的方式被提供,不同的是這些接口被提供給alsa-lib使用,而不是直接給應用程序使用的。應用程序最好使用alsa-lib,或者更高級的接口,比如jack提供的接口。
圖17.6給出了ALSA聲卡驅動與用戶空間體系結構的簡圖,從中可以看出ALSA內核驅動與用戶空間庫及OSS之間的關係。

圖17.6 ALSA體系結構
17.4.1 card和組件管理
對於每個聲卡而言,必須創建1個“card”實例。card是聲卡的“總部”,它管理這個聲卡上的所有設備(組件),如PCM、mixers、MIDI、synthesizer等。因此,card和組件是ALSA聲卡驅動中的主要組成元素。
1、創建card
struct snd_card *snd_card_new(int idx, const char *xid,
    struct module *module, int extra_size);
idx是card索引號、xid是標識字符串、module一般爲THIS_MODULE,extra_size是要分配的額外數據的大小,分配的extra_size大小的內存將作爲card->private_data。
2、創建組件
int snd_device_new(struct snd_card *card, snd_device_type_t type,
     void *device_data, struct snd_device_ops *ops);
當 card被創建後,設備(組件)能夠被創建並關聯於該card。第1個參數是snd_card_new()創建的card指針,第2個參數type 指的是device-level即設備類型,形式爲SNDRV_DEV_XXX,包括SNDRV_DEV_CODEC、 SNDRV_DEV_CONTROL、SNDRV_DEV_PCM、SNDRV_DEV_RAWMIDI等,用戶自定義設備的device-level是 SNDRV_DEV_LOWLEVEL,ops參數是1個函數集(定義爲snd_device_ops結構體)的指針,device_data是設備數據指針,注意函數snd_device_new()本身不會分配設備數據的內存,因此應事先分配。
3、組件釋放
每個ALSA預定義的組件在構造時需調用snd_device_new(),而每個組件的析構方法則在函數集中被包含。對於PCM、AC97此類預定義組件,我們不需關心它們的析構,而對於自定義的組件,則需要填充snd_device_ops中的析構函數指針dev_free,這樣,當 snd_card_free()被調用時,組件將自動被釋放。
4、芯片特定的數據(Chip-Specific Data)
芯片特定的數據一般以struct xxxchip結構體形式組織,這個結構體中包含芯片相關的I/O端口地址、資源指針、中斷號等,其意義等同於字符設備驅動中的 file->private_data。定義芯片特定的數據主要有2種方法,一種方法是將sizeof(struct xxxchip)傳入snd_card_new()的extra_size參數,它將自動成員snd_card的private_data成員,如代碼清單17.5;另一種方法是在snd_card_new()傳入給extra_size參數0,再分配sizeof(struct xxxchip)的內存,將分配內存的地址傳入snd_device_new()的device_data的參數,如代碼清單17.6。
代碼清單17.5 創建芯片特定的數據方法1
1 struct xxxchip //芯片特定的數據結構體
2 {
3    ...
4 };
5 card = snd_card_new(index, id, THIS_MODULE, sizeof(struct
6 xxxchip)); //創建聲卡並申請xxx_chi內存作爲card-> private_data
7 struct xxxchip *chip = card->private_data;
代碼清單17.6 創建芯片特定的數據方法2
1 struct snd_card *card;
2 struct xxxchip *chip;
3 //使用0作爲第4個參數,並動態分配xxx_chip的內存:
4 card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
5 ...
6 chip = kzalloc(sizeof(*chip), GFP_KERNEL);
7 //在xxxchip結構體中,應該包括聲卡指針:
8 struct xxxchip
9 {
10   struct snd_card *card;
11   ...
12 };
13 //並將其card成員賦值爲snd_card_new()創建的card指針:
14 chip->card = card;
15 static struct snd_device_ops ops =
16 {
17   .dev_free = snd_xxx_chip_dev_free, //組件析構
18 };
19 ...
20 //創建自定義組件
21 snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
22 //在析構函數中釋放xxxchip內存
23 static int snd_xxx_chip_dev_free(struct snd_device *device)
24 {
25   return snd_xxx_chip_free(device->device_data); //釋放
26 }
5、註冊/釋放聲卡
當snd_card被準備好以後,可使用snd_card_register()函數註冊這個聲卡:
int snd_card_register(struct snd_card *card)
對應的snd_card_free()完成相反的功能:
int snd_card_free(struct snd_card *card);

4、put()函數
put()用於從用戶空間寫入值,如果值被改變,該函數返回1,否則返回0;如果發生錯誤,該函數返回1個錯誤碼。代碼清單17.22給出了1個put()函數的範例。
代碼清單17.22 snd_ctl_elem_info結構體中put()函數範例
1 static int snd_xxxctl_put(struct snd_kcontrol *kcontrol, struct
2    snd_ctl_elem_value *ucontrol)
3 {
4    //從snd_kcontrol獲得xxxchip指針
5    struct xxxchip *chip = snd_kcontrol_chip(kcontrol);
6    int changed = 0;//缺省返回值爲0
7    //值被改變
8    if (chip->current_value != ucontrol->value.integer.value[0])
9    {
10     change_current_value(chip, ucontrol->value.integer.value[0]);
11     changed = 1;//返回值爲1
12   }
13   return changed;
14 }
對於get()和put()函數而言,如果control有多於1個元素,即count>1,則每個元素都需要被返回或寫入。
5、構造control
當所有事情準備好後,我們需要創建1個control,調用snd_ctl_add()和snd_ctl_new1()這2個函數來完成,這2個函數的原型爲:
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol);

struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
      void *private_data);
snd_ctl_new1()函數用於創建1個snd_kcontrol並返回其指針,snd_ctl_add()函數用於將創建的snd_kcontrol添加到對應的card中。
6、變更通知
如果驅動中需要在中斷服務程序中改變或更新1個control,可以調用snd_ctl_notify()函數,此函數原型爲:
void snd_ctl_notify(struct snd_card *card, unsigned int mask, struct snd_ctl_elem_id *id);
該函數的第2個參數爲事件掩碼(event-mask),第3個參數爲該通知的control元素id指針。
例如,如下語句定義的事件掩碼SNDRV_CTL_EVENT_MASK_VALUE意味着control值的改變被通知:
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id_pointer);
17.4.4 AC97 API接口
ALSA AC97編解碼層被很好地定義,利用它,驅動工程師只需編寫少量底層的控制函數。
1、AC97實例構造
爲了創建1個AC97實例,首先需要調用snd_ac97_bus()函數構建AC97總線及其操作,這個函數的原型爲:
int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops *ops,
   void *private_data, struct snd_ac97_bus **rbus);
該函數的第3個參數ops是1個snd_ac97_bus_ops結構體,其定義如代碼清單17.23。
代碼清單17.23 snd_ac97_bus_ops結構體
1 struct snd_ac97_bus_ops
2 {
3    void(*reset)(struct snd_ac97 *ac97); //復位函數
4    //寫入函數
5    void(*write)(struct snd_ac97 *ac97, unsigned short reg, unsigned short val);
6    //讀取函數
7    unsigned short(*read)(struct snd_ac97 *ac97, unsigned short reg);
8    void(*wait)(struct snd_ac97 *ac97);
9    void(*init)(struct snd_ac97 *ac97);
10 };
接下來,調用snd_ac97_mixer()函數註冊混音器,這個函數的原型爲:
int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97);
代碼清單17.24演示了AC97實例的創建過程。
代碼清單17.24 AC97實例的創建過程範例
1 struct snd_ac97_bus *bus;
2 //AC97總線操作
3 static struct snd_ac97_bus_ops ops =
4 {
5    .write = snd_mychip_ac97_write,
6    .read = snd_mychip_ac97_read,
7 };
8 //AC97總線與操作創建
9 snd_ac97_bus(card, 0, &ops, NULL, &bus);
10 //AC97模板
11 struct snd_ac97_template ac97;
12 int err;
13 memset(&ac97, 0, sizeof(ac97));
14 ac97.private_data = chip;//私有數據
15 //註冊混音器
16 snd_ac97_mixer(bus, &ac97, &chip->ac97);
上述代碼第1行的snd_ac97_bus結構體指針bus的指針被傳入第9行的snd_ac97_bus()函數並被賦值,chip->ac97的指針被傳入第16行的snd_ac97_mixer()並被賦值,chip->ac97將成員新創建AC97實例的指針。
如果1個聲卡上包含多個編解碼器,這種情況下,需要多次調用snd_ac97_mixer()並對snd_ac97的num成員(編解碼器序號)賦予相應的序號。驅動中可以爲不同的編解碼器編寫不同的snd_ac97_bus_ops成員函數中,或者只是在相同的一套成員函數中通過ac97.num獲得序號後再區分進行具體的操作。
2、snd_ac97_bus_ops成員函數
snd_ac97_bus_ops結構體中的read()和write()成員函數完成底層的硬件訪問,reset()函數用於復位編解碼器,wait()函數用於編解碼器標準初始化過程中的特定等待,如果芯片要求額外的等待時間,應實現這個函數,init()用於完成編解碼器附加的初始化。代碼清單17.25給出了read()和write()函數的範例。
代碼清單17.25 snd_ac97_bus_ops結構體中read()和write()函數範例
1 static unsigned short snd_xxxchip_ac97_read(struct snd_ac97 *ac97, unsigned
2    short reg)
3 {
4    struct xxxchip *chip = ac97->private_data;
5    ...
6    return the_register_value; //返回寄存器值
7 }
8
9 static void snd_xxxchip_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
10   unsigned short val)
11 {
12   struct xxxchip *chip = ac97->private_data;
13   ...
14   // 將被給的寄存器值寫入codec
15 }
3、修改寄存器
如果需要在驅動中訪問編解碼器,可使用如下函數:
void snd_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);

int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);

int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value);

unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg);
snd_ac97_update()與void snd_ac97_write()的區別在於前者在值已經設置的情況下不會再設置,而後者則會再寫一次。snd_ac97_update_bits()用於更新寄存器的某些位,由mask決定。
除此之外,還有1個函數可用於設置採樣率:
int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate);
這個函數的第2個參數reg可以是AC97_PCM_MIC_ADC_RATE、AC97_PCM_FRONT_DAC_RATE、 AC97_PCM_LR_ADC_RATE和AC97_SPDIF,對於AC97_SPDIF而言,寄存器並非真地被改變了,只是相應的IEC958狀態位將被更新。
4、時鐘調整
在一些芯片上,編解碼器的時鐘不是48000而是使用PCI時鐘以節省1個晶體,在這種情況下,我們應該改變bus->clock爲相應的值,例如intel8x0和es1968包含時鐘的自動測量函數。
5、proc文件
ALSA AC97接口會創建如/proc/asound/card0/codec97#0/ac97#0-0和ac97#0-0+regs這樣的proc文件,通過這些文件可以察看編解碼器目前的狀態和寄存器。
如果1個chip上有多個codecs,可多次調用snd_ac97_mixer()。
17.4.5 ALSA用戶空間編程
ALSA驅動的聲卡在用戶空間不宜直接使用文件接口,而應使用alsa-lib,代碼清單17.26給出了基於ALSA音頻驅動的最簡單的放音應用程序。
代碼清單17.26 ALSA用戶空間放音程序
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <alsa/asoundlib.h>
4
5 main(int argc, char *argv[])
6 {
7    int i;
8    int err;
9    short buf[128];
10   snd_pcm_t *playback_handle;   //PCM設備句柄
11   snd_pcm_hw_params_t *hw_params; //硬件信息和PCM流配置
12   //打開PCM,最後1個參數爲0意味着標準配置
13   if ((err = snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)
14     ) < 0)
15   {
16     fprintf(stderr, "cannot open audio device %s (%s)/n", argv[1], snd_strerror
17       (err));
18     exit(1);
19   }
20   //分配snd_pcm_hw_params_t結構體
21   if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
22   {
23     fprintf(stderr, "cannot allocate hardware parameter structure (%s)/n",
24       snd_strerror(err));
25     exit(1);
26   }
27   //初始化hw_params
28   if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0)
29   {
30     fprintf(stderr, "cannot initialize hardware parameter structure (%s)/n",
31       snd_strerror(err));
32     exit(1);
33   }
34   //初始化訪問權限
35   if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params,
36     SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
37   {
38     fprintf(stderr, "cannot set access type (%s)/n", snd_strerror(err));
39     exit(1);
40   }
41   //初始化採樣格式
42   if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params,
43     SND_PCM_FORMAT_S16_LE)) < 0)
44   {
45     fprintf(stderr, "cannot set sample format (%s)/n", snd_strerror(err));
46     exit(1);
47   }
48   //設置採樣率,如果硬件不支持我們設置的採樣率,將使用最接近的
49   if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, 44100,
50     0)) < 0)
51   {
52     fprintf(stderr, "cannot set sample rate (%s)/n", snd_strerror(err));
53     exit(1);
54   }
55   //設置通道數量
56   if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) < 0)
57   {
58     fprintf(stderr, "cannot set channel count (%s)/n", snd_strerror(err));
59     exit(1);
60   }
61   //設置hw_params
62   if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0)
63   {
64     fprintf(stderr, "cannot set parameters (%s)/n", snd_strerror(err));
65     exit(1);
66   }
67   //釋放分配的snd_pcm_hw_params_t結構體
68   snd_pcm_hw_params_free(hw_params);
69   //完成硬件參數設置,使設備準備好
70   if ((err = snd_pcm_prepare(playback_handle)) < 0)
71   {
72     fprintf(stderr, "cannot prepare audio interface for use (%s)/n",
73       snd_strerror(err));
74     exit(1);
75   }
76
77   for (i = 0; i < 10; ++i)
78   {
79     //寫音頻數據到PCM設備
80     if ((err = snd_pcm_writei(playback_handle, buf, 128)) != 128)
81     {
82       fprintf(stderr, "write to audio interface failed (%s)/n", snd_strerror
83         (err));
84       exit(1);
85     }
86   }
87   //關閉PCM設備句柄
88   snd_pcm_close(playback_handle);
89   exit(0);
90 }
由上述代碼可以看出,ALSA用戶空間編程的流程與17.3.4節給出的OSS驅動用戶空間編程的流程基本是一致的,都經過了“打開――設置參數――讀寫音頻數據”的過程,不同在於OSS打開的是設備文件,設置參數使用的是Linux ioctl()系統調用,讀寫音頻數據使用的是Linux read()、write()文件API,而ALSA則全部使用alsa-lib中的API。
把上述代碼第80行的snd_pcm_writei()函數替換爲snd_pcm_readi()就編程了1個最簡單的錄音程序。
代碼清單17.27的程序打開1個音頻接口,配置它爲立體聲、16位、44.1khz採樣和基於interleave的讀寫。它阻塞等待直接接口準備好接收放音數據,這時候將數據拷貝到緩衝區。這種設計方法使得程序很容易移植到類似JACK、LADSPA、Coreaudio、VST等callback機制驅動的系統。
代碼清單17.27 ALSA用戶空間放音程序(基於“中斷”)
1   #include <stdio.h>
2   #include <stdlib.h>
3   #include <errno.h>
4   #include <poll.h>
5   #include <alsa/asoundlib.h>
6
7   snd_pcm_t *playback_handle;
8   short buf[4096];
9
10 int playback_callback(snd_pcm_sframes_t nframes)
11 {
12    int err;
13    printf("playback callback called with %u frames/n", nframes);
14    /* 填充緩衝區 */
15    if ((err = snd_pcm_writei(playback_handle, buf, nframes)) < 0)
16    {
17      fprintf(stderr, "write failed (%s)/n", snd_strerror(err));
18    }
19
20    return err;
21 }
22
23 main(int argc, char *argv[])
24 {
25
26    snd_pcm_hw_params_t *hw_params;
27    snd_pcm_sw_params_t *sw_params;
28    snd_pcm_sframes_t frames_to_deliver;
29    int nfds;
30    int err;
31    struct pollfd *pfds;
32
33    if ((err = snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)
34      ) < 0)
35    {
36      fprintf(stderr, "cannot open audio device %s (%s)/n", argv[1], snd_strerror
37        (err));
38      exit(1);
39    }
40
41    if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
42    {
43      fprintf(stderr, "cannot allocate hardware parameter structure (%s)/n",
44        snd_strerror(err));
45      exit(1);
46    }
47
48    if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0)
49    {
50      fprintf(stderr, "cannot initialize hardware parameter structure (%s)/n",
51        snd_strerror(err));
52      exit(1);
53    }
54
55    if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params,
56      SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
57    {
58      fprintf(stderr, "cannot set access type (%s)/n", snd_strerror(err));
59      exit(1);
60    }
61
62    if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params,
63      SND_PCM_FORMAT_S16_LE)) < 0)
64    {
65      fprintf(stderr, "cannot set sample format (%s)/n", snd_strerror(err));
66      exit(1);
67    }
68
69    if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, 44100,
70      0)) < 0)
71    {
72      fprintf(stderr, "cannot set sample rate (%s)/n", snd_strerror(err));
73      exit(1);
74    }
75
76    if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) < 0)
77    {
78      fprintf(stderr, "cannot set channel count (%s)/n", snd_strerror(err));
79      exit(1);
80    }
81
82    if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0)
83    {
84      fprintf(stderr, "cannot set parameters (%s)/n", snd_strerror(err));
85      exit(1);
86    }
87
88    snd_pcm_hw_params_free(hw_params);
89
90    /* 告訴ALSA當4096個以上幀可以傳遞時喚醒我們 */
91    if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0)
92    {
93      fprintf(stderr, "cannot allocate software parameters structure (%s)/n",
94        snd_strerror(err));
95      exit(1);
96    }
97    if ((err = snd_pcm_sw_params_current(playback_handle, sw_params)) < 0)
98    {
99      fprintf(stderr, "cannot initialize software parameters structure (%s)/n",
100       snd_strerror(err));
101     exit(1);
102   }
103   /* 設置4096幀傳遞一次數據 */
104   if ((err = snd_pcm_sw_params_set_avail_min(playback_handle, sw_params, 4096))
105     < 0)
106   {
107     fprintf(stderr, "cannot set minimum available count (%s)/n", snd_strerror
108       (err));
109     exit(1);
110   }
111   /* 一旦有數據就開始播放 */
112   if ((err = snd_pcm_sw_params_set_start_threshold(playback_handle, sw_params,
113     0U)) < 0)
114   {
115     fprintf(stderr, "cannot set start mode (%s)/n", snd_strerror(err));
116     exit(1);
117   }
118   if ((err = snd_pcm_sw_params(playback_handle, sw_params)) < 0)
119   {
120     fprintf(stderr, "cannot set software parameters (%s)/n", snd_strerror(err));
121     exit(1);
122   }
123
124   /* 每4096幀接口將中斷內核,ALSA將很快喚醒本程序 */
125
126   if ((err = snd_pcm_prepare(playback_handle)) < 0)
127   {
128     fprintf(stderr, "cannot prepare audio interface for use (%s)/n",
129       snd_strerror(err));
130     exit(1);
131   }
132
133   while (1)
134   {
135
136     /* 等待,直到接口準備好傳遞數據,或者1秒超時發生 */
137     if ((err = snd_pcm_wait(playback_handle, 1000)) < 0)
138     {
139       fprintf(stderr, "poll failed (%s)/n", strerror(errno));
140       break;
141     }
142
143     /* 查出有多少空間可放置playback數據 */
144     if ((frames_to_deliver = snd_pcm_avail_update(playback_handle)) < 0)
145     {
146       if (frames_to_deliver == - EPIPE)
147       {
148         fprintf(stderr, "an xrun occured/n");
149         break;
150       }
151       else
152       {
153         fprintf(stderr, "unknown ALSA avail update return value (%d)/n",
154           frames_to_deliver);
155         break;
156       }
157     }
158
159     frames_to_deliver = frames_to_deliver > 4096 ? 4096 : frames_to_deliver;
160
161     /* 傳遞數據 */
162     if (playback_callback(frames_to_deliver) != frames_to_deliver)
163     {
164       fprintf(stderr, "playback callback failed/n");
165       break;
166     }
167   }
168
169   snd_pcm_close(playback_handle);
170   exit(0);
171 }
17.5實例1:S3C2410+UDA1341 OSS驅動
17.5.1 S3C2410與UDA1341接口硬件描述
如圖17.7,S3C2410處理器內置了IIS總線接口,S3C2410的IIS總線時鐘信號SCK與Philip公司的UDA1341的BCK連接,字段選擇連接於WS引腳。UDA1341提供兩個音頻通道,分別用於輸入和輸出,對應的引腳連接:IIS總線的音頻輸出IISSDO對應於UDA1341的音頻輸入;IIS總線的音頻輸入IISSDI對應於UDA1341的音頻輸出。UDA1341的L3接口相當於一個混音器控制接口,可以用來控制輸入/輸出音頻信號的音量大小、低音等。L3接口的引腳L3MODE、L3DATA、L3CLOCK分別連接到S3C2410的3個GPIO來控制。

   
圖17.7 S3C2410與UDA1341 IIS接口連接
Philips 公司的UDA1341支持IIS總線數據格式,採用位元流轉換技術進行信號處理,完成聲音信號的模數轉換,具有可編程增益放大器和數字自動增益控制器,其低功耗、低電壓的特點使其非常適合用於MD/CD、筆記本電腦等便攜式設備。UDA1341對外提供2組音頻信號輸入接口,每組包括左右2個聲道。

圖17.8 UDA1341 內部結構
如圖17.8所示,2組音頻輸入在UDA1341內部的處理存在很大差別:第一組音頻信號輸入後經過1個0 dB/6 dB開關後採樣送入數字混音器:第二組音頻信號輸入後先經過可編程增益放大器(PGA),然後再進行採樣,採樣後的數據要再經過數字自動增益控制器(AGC)送入數字混音器。設計硬件電路時選用第二組輸入音頻信號,這樣可以通過軟件的方法實現對系統輸入音量大小的調節。顯然選用第二組可以通過L3總線接口控制AGC來實現。另外,選擇通道2還可以通過PGA對從MIC輸入的信號進行片內放大。
S3C2410與UDA1341之間的IIS接口有3種工作方式:
• 正常傳輸模式。該模式下使用IISCON寄存器對FIFO進行控制,CPU通過輪詢方式訪問FIFO寄存器,以完成對FIFO緩存傳輸或接收的處理。
• DMA模式。通過設置IISFCON寄存器使IIS接口工作於這種模式。在該模式下,FIFO寄存器組的控制權掌握在DMA控制器上,當FIFO滿時,由DMA控制器對FIFO中的數據進行處理。DMA模式的選擇由IISCON寄存器的第4和第5位控制。
• 傳輸/接收模式。該模式下,IIS數據線將通過雙通道DMA同時接收和發送音頻數據。在OSS驅動中,將使用此模式。
17.5.2註冊dsp和mixer接口
如代碼清單17.28,在UDA1341 OSS驅動的模塊加載函數中,將完成如下工作:
• 初始化IIS接口硬件,設置L3總線對應的GPIO。
• 申請用於音頻數據傳輸的DMA通道。
• 初始化UDA1341到恰當的工作模式。
• 註冊dsp和mixer接口。
代碼清單17.28 UDA1341 OSS驅動模塊加載函數
1 //音頻(dsp)文件操作
2 static struct file_operations smdk2410_audio_fops =
3 {
4    llseek: smdk2410_audio_llseek,
5    write: smdk2410_audio_write,
6    read: smdk2410_audio_read,
7    poll: smdk2410_audio_poll,
8    ioctl: smdk2410_audio_ioctl,
9    open: smdk2410_audio_open,
10   release: smdk2410_audio_release
11 };
12 //混音器文件操作
13 static struct file_operations smdk2410_mixer_fops =
14 {
15   ioctl: smdk2410_mixer_ioctl,
16   open: smdk2410_mixer_open,
17   release: smdk2410_mixer_release
18 };
19
20 int __init s3c2410_uda1341_init(void)                                       
21 {                                                                           
22   unsigned long flags;                                                      
23                                                                             
24   local_irq_save(flags);                                                    
25                                                                             
26   /* 設置IIS接口引腳GPIO */                                                 
27                                                                             
28   set_gpio_ctrl(GPIO_L3CLOCK); // GPB 4: L3CLOCK, 輸出                      
29   set_gpio_ctrl(GPIO_L3DATA); // GPB 3: L3DATA, 輸出                       
30   set_gpio_ctrl(GPIO_L3MODE); // GPB 2: L3MODE, 輸出                       
31                                                                             
32                                                                             
33   set_gpio_ctrl(GPIO_E3 | GPIO_PULLUP_EN | GPIO_MODE_IISSDI); //GPE 3: IISSDI
34   set_gpio_ctrl(GPIO_E0 | GPIO_PULLUP_EN | GPIO_MODE_IISSDI); //GPE 0: IISLRCK
35   set_gpio_ctrl(GPIO_E1 | GPIO_PULLUP_EN | GPIO_MODE_IISSCLK); //GPE 1:IISSCLK
36   set_gpio_ctrl(GPIO_E2 | GPIO_PULLUP_EN | GPIO_MODE_CDCLK); //GPE 2: CDCLK
37   set_gpio_ctrl(GPIO_E4 | GPIO_PULLUP_EN | GPIO_MODE_IISSDO); //GPE 4: IISSDO
38                                                                             
39   local_irq_restore(flags);                                                 
40                                                                             
41   init_uda1341();                                                           
42                                                                             
43   /* 輸出流採樣DMA通道2 */                                                  
44   output_stream.dma_ch = DMA_CH2;                                           
45                                                                             
46   if (audio_init_dma(&output_stream, "UDA1341 out"))                        
47   {                                                                         
48     audio_clear_dma(&output_stream);                                        
49     printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels/n");
50     return - EBUSY;                                                        
51   }                                                                         
52   /* 輸入流採樣DMA通道1 */                                                  
53   input_stream.dma_ch = DMA_CH1;                                            
54                                                                             
55   if (audio_init_dma(&input_stream, "UDA1341 in"))                          
56   {                                                                         
57     audio_clear_dma(&input_stream);                                         
58     printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels/n");
59     return - EBUSY;                                                        
60   }                                                                         
61                                                                             
62   /* 註冊dsp和mixer設備接口 */                                              
63   audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, - 1);           
64   audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, - 1);       
65                                                                             
66   printk(AUDIO_NAME_VERBOSE " initialized/n");                              
67                                                                             
68   return 0;                                                                 
69 }   
UDA1341 OSS驅動的模塊卸載函數中,將完成與模塊加載函數相反的工作,如代碼清單17.29。
代碼清單17.29 UDA1341 OSS驅動模塊卸載函數
1 void __exit s3c2410_uda1341_exit(void)
2 {
3   //註銷dsp和mixer設備接口
4   unregister_sound_dsp(audio_dev_dsp);
5   unregister_sound_mixer(audio_dev_mixer);
6  
7   //註銷DMA通道
8   audio_clear_dma(&output_stream);
9   audio_clear_dma(&input_stream); /* input */
10 printk(AUDIO_NAME_VERBOSE " unloaded/n");
11 }
17.5.3 mixer接口IO控制函數
UDA1341 OSS驅動的ioctl()函數處理多個mixer命令,如SOUND_MIXER_INFO、 SOUND_MIXER_READ_STEREODEVS、SOUND_MIXER_WRITE_VOLUME等,用於獲得或設置音量和增益等信息,如代碼清單17.30所示。
代碼清單17.30 UDA1341 OSS驅動ioctl()函數
1 static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file,
2    unsigned int cmd, unsigned long arg)
3 {
4    int ret;
5    long val = 0;
6
7    switch (cmd)
8    {
9      case SOUND_MIXER_INFO:   //獲得mixer信息
10       {
11         mixer_info info;
12         strncpy(info.id, "UDA1341", sizeof(info.id));
13         strncpy(info.name, "Philips UDA1341", sizeof(info.name));
14         info.modify_counter = audio_mix_modcnt;
15         return copy_to_user((void*)arg, &info, sizeof(info));
16       }
17
18     case SOUND_OLD_MIXER_INFO:
19       {
20         _old_mixer_info info;
21         strncpy(info.id, "UDA1341", sizeof(info.id));
22         strncpy(info.name, "Philips UDA1341", sizeof(info.name));
23         return copy_to_user((void*)arg, &info, sizeof(info));
24       }
25
26     case SOUND_MIXER_READ_STEREODEVS://獲取設備對立體聲的支持
27       return put_user(0, (long*)arg);
28
29     case SOUND_MIXER_READ_CAPS: //獲取聲卡能力
30       val = SOUND_CAP_EXCL_INPUT;
31       return put_user(val, (long*)arg);
32
33     case SOUND_MIXER_WRITE_VOLUME:   //設置音量
34       ret = get_user(val, (long*)arg);
35       if (ret)
36         return ret;
37       uda1341_volume = 63-(((val &0xff) + 1) *63) / 100;
38       uda1341_l3_address(UDA1341_REG_DATA0);
39       uda1341_l3_data(uda1341_volume);
40       break;
41
42     case SOUND_MIXER_READ_VOLUME:   //獲取音量
43       val = ((63-uda1341_volume) *100) / 63;
44       val |= val << 8;
45       return put_user(val, (long*)arg);
46
47     case SOUND_MIXER_READ_IGAIN:   //獲得增益
48       val = ((31-mixer_igain) *100) / 31;
49       return put_user(val, (int*)arg);
50
51     case SOUND_MIXER_WRITE_IGAIN: //設置增益
52       ret = get_user(val, (int*)arg);
53       if (ret)
54         return ret;
55       mixer_igain = 31-(val *31 / 100);
56       /* 使用mixer增益通道1 */
57       uda1341_l3_address(UDA1341_REG_DATA0);
58       uda1341_l3_data(EXTADDR(EXT0));
59       uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain)));
60       break;
61
62     default:
63       DPRINTK("mixer ioctl %u unknown/n", cmd);
64       return - ENOSYS;
65   }
66
67   audio_mix_modcnt++;
68   return 0;
69 }    
17.5.4 dsp接口音頻數據傳輸
OSS聲卡驅動中,dsp接口的讀寫函數是核心中的核心,直接對應着錄音和放音的流程。
OSS 的讀函數存在一個與普通字符設備驅動讀函數不同的地方,那就是一般而言,對於普通字符設備驅動,如果用戶要求讀count個字節,而實際上只有 count1字節可獲得(count1< count)時,它會將這count1字節拷貝給用戶後即返回count1;而dsp接口的讀函數會分次拷貝,如果第1次不能滿足,它會等待第2次,直到 “count1 + count2 + ... = count”爲止再返回count。這種設計是合理的,因爲OSS驅動應該負責音頻數據的流量控制。代碼清單17.31給出了UDA1341 OSS驅動的讀函數實現。
代碼清單17.31 UDA1341 OSS驅動的讀函數
1 static ssize_t smdk2410_audio_read(struct file *file, char *buffer, size_t
2    count, loff_t *ppos)
3 {
4    const char *buffer0 = buffer;
5    audio_stream_t *s = &input_stream; //得到數據區的指針
6    int chunksize, ret = 0;
7
8    DPRINTK("audio_read: count=%d/n", count);
9
10   if (ppos != &file->f_pos)
11     return - ESPIPE;
12
13   if (!s->buffers)
14   {
15     int i;
16
17     if (audio_setup_buf(s))
18       return - ENOMEM;
19     //依次從緩存區讀取數據
20     for (i = 0; i < s->nbfrags; i++)
21     {
22       audio_buf_t *b = s->buf;
23       down(&b->sem);
24       s3c2410_dma_queue_buffer(s->dma_ch, (void*)b, b->dma_addr, s->fragsize,
25         DMA_BUF_RD);
26       NEXT_BUF(s, buf);
27     }
28   }
29
30   //滿足用戶的所有讀需求
31   while (count > 0)
32   {
33     audio_buf_t *b = s->buf;
34
35     if (file->f_flags &O_NONBLOCK) //非阻塞
36     {
37       ret = - EAGAIN;
38       if (down_trylock(&b->sem))
39         break;
40     }
41     else
42     {
43       ret = - ERESTARTSYS;
44       if (down_interruptible(&b->sem))
45         break;
46     }
47
48     chunksize = b->size;
49     //從緩存區讀取數據
50     if (chunksize > count)
51       chunksize = count;
52     DPRINTK("read %d from %d/n", chunksize, s->buf_idx);
53     if (copy_to_user(buffer, b->start + s->fragsize - b->size, //調用拷貝函數
54     chunksize))
55     {
56       up(&b->sem);
57       return - EFAULT;
58     }
59     b->size -= chunksize;
60
61     buffer += chunksize;
62     count -= chunksize; //已經給用戶拷貝了一部分,count減少
63     if (b->size > 0)
64     {
65       up(&b->sem);
66       break;
67     }
68     //將緩存區釋放
69     s3c2410_dma_queue_buffer(s->dma_ch, (void*)b, b->dma_addr, s->fragsize,
70       DMA_BUF_RD);
71
72     NEXT_BUF(s, buf);
73   }
74
75   if ((buffer - buffer0))
76     ret = buffer - buffer0;
77
78   return ret;
79 }     
OSS驅動dsp接口的寫函數與讀函數類似,一般來說,它也應該滿足用戶的所有寫需求後再返回,如代碼清單17.32。
代碼清單17.32 UDA1341 OSS驅動的寫函數
1 static ssize_t smdk2410_audio_write(struct file *file, const char *buffer,
2    size_t count, loff_t *ppos)
3 {
4    const char *buffer0 = buffer;
5    audio_stream_t *s = &output_stream;
6    int chunksize, ret = 0;
7
8    DPRINTK("audio_write : start count=%d/n", count);
9
10   switch (file->f_flags &O_ACCMODE)
11   {
12     case O_WRONLY: //只寫
13     case O_RDWR: //讀寫
14       break;
15     default: //只讀不合法
16       return - EPERM;
17   }
18   //設置DMA緩衝區
19   if (!s->buffers && audio_setup_buf(s))
20     return - ENOMEM;
21
22   count &= ~0x03;
23
24   while (count > 0) //直到滿足用戶的所有寫需求
25   {
26     audio_buf_t *b = s->buf;
27     //非阻塞訪問
28     if (file->f_flags &O_NONBLOCK)
29     {
30       ret = - EAGAIN;
31       if (down_trylock(&b->sem))
32         break;
33     }
34     else
35     {
36       ret = - ERESTARTSYS;
37       if (down_interruptible(&b->sem))
38         break;
39     }
40     //從用戶空間拷貝音頻數據
41     if (audio_channels == 2)
42     {
43       chunksize = s->fragsize - b->size;
44       if (chunksize > count)
45         chunksize = count;
46       DPRINTK("write %d to %d/n", chunksize, s->buf_idx);
47       if (copy_from_user(b->start + b->size, buffer, chunksize))
48       {
49         up(&b->sem);
50         return - EFAULT;
51       }
52       b->size += chunksize;
53     }
54     else
55     {
56       chunksize = (s->fragsize - b->size) >> 1;
57
58       if (chunksize > count)
59         chunksize = count;
60       DPRINTK("write %d to %d/n", chunksize *2, s->buf_idx);
61       if (copy_from_user_mono_stereo(b->start + b->size, buffer, chunksize))
62       {
63         up(&b->sem);
64         return - EFAULT;
65       }
66
67       b->size += chunksize * 2;
68     }
69
70     buffer += chunksize;
71     count -= chunksize; //已經從用戶拷貝了一部分,count減少
72     if (b->size < s->fragsize)
73     {
74       up(&b->sem);
75       break;
76     }
77     //發起DMA操作
78     s3c2410_dma_queue_buffer(s->dma_ch, (void*)b, b->dma_addr, b->size,
79       DMA_BUF_WR);
80     b->size = 0;
81     NEXT_BUF(s, buf);
82   }
83
84   if ((buffer - buffer0))
85     ret = buffer - buffer0;
86
87   DPRINTK("audio_write : end count=%d/n/n", ret);
88
89   return ret;
90 }
17.6實例2:SA1100+ UDA1341 ALSA驅動
17.6.1 card註冊與註銷
同樣是UDA1341芯片,如果以ALSA體系結構來實現它的驅動,會和OSS大不一樣。如17.4.1節所言,在模塊初始化和卸載的時候,需要註冊和註銷card,另外在模塊加載的時候,也會註冊mixer和pcm組件,如代碼清單17.33。
代碼清單17.33 UDA1341 ALSA驅動模塊初始化與卸載
1 static int __init sa11xx_uda1341_probe(struct platform_device *devptr)
2 {
3   int err;
4   struct snd_card *card;
5   struct sa11xx_uda1341 *chip;
6
7   /* 新建card */
8   card = snd_card_new(-1, id, THIS_MODULE, sizeof(struct sa11xx_uda1341));
9   if (card == NULL)
10   return -ENOMEM;
11
12 chip = card->private_data;
13 spin_lock_init(&chip->s[0].dma_lock);
14 spin_lock_init(&chip->s[1].dma_lock);
15
16 card->private_free = snd_sa11xx_uda1341_free;//card私有數據釋放
17 chip->card = card;
18 chip->samplerate = AUDIO_RATE_DEFAULT;
19
20 // 註冊control(mixer)接口
21 if ((err = snd_chip_uda1341_mixer_new(card, &chip->uda1341)))
22   goto nodev;
23
24 // 註冊PCM接口
25 if ((err = snd_card_sa11xx_uda1341_pcm(chip, 0)) < 0)
26   goto nodev;
27       
28 strcpy(card->driver, "UDA1341");
29 strcpy(card->shortname, "H3600 UDA1341TS");
30 sprintf(card->longname, "Compaq iPAQ H3600 with Philips UDA1341TS");
31       
32 snd_card_set_dev(card, &devptr->dev);
33   //註冊card
34 if ((err = snd_card_register(card)) == 0) {
35   printk( KERN_INFO "iPAQ audio support initialized/n" );
36   platform_set_drvdata(devptr, card);
37   return 0;
38 }
39       
40 nodev:
41 snd_card_free(card);
42 return err;
43 }   
44
45 static int __devexit sa11xx_uda1341_remove(struct platform_device *devptr)
46 {
47 //釋放card
48 snd_card_free(platform_get_drvdata(devptr));
49 platform_set_drvdata(devptr, NULL);
50 return 0;
51 }
17.6.2 PCM設備的實現
PCM組件直接對應着ALSA驅動的錄音和放音,從17.4.2節的描述可知,驅動從需要定義對應相應的snd_pcm_hardware結構體進行PCM設備硬件描述,如代碼清單17.34。
代碼清單17.34 UDA1341 ALSA驅動PCM接口snd_pcm_hardware結構體
1 static struct snd_pcm_hardware snd_sa11xx_uda1341_capture =
2 {
3   .info   = (SNDRV_PCM_INFO_INTERLEAVED |
4         SNDRV_PCM_INFO_BLOCK_TRANSFER |
5         SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
6         SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
7   .formats = SNDRV_PCM_FMTBIT_S16_LE,
8   .rates   = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |/
9         SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |/
10        SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |/
11        SNDRV_PCM_RATE_KNOT),
12 .rate_min = 8000,
13 .rate_max = 48000,
14 .channels_min = 2,
15 .channels_max = 2,
16 .buffer_bytes_max = 64*1024,
17 .period_bytes_min = 64,
18 .period_bytes_max = DMA_BUF_SIZE,
19 .periods_min = 2,
20 .periods_max = 255,
21 .fifo_size = 0,
22 };
23
24 static struct snd_pcm_hardware snd_sa11xx_uda1341_playback =
25 {
26 .info   = (SNDRV_PCM_INFO_INTERLEAVED |
27        SNDRV_PCM_INFO_BLOCK_TRANSFER |
28        SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
29        SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
30 .formats = SNDRV_PCM_FMTBIT_S16_LE,
31 .rates   = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |/
32              SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |/
33        SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |/
34        SNDRV_PCM_RATE_KNOT),
35 .rate_min = 8000,
36 .rate_max = 48000,
37 .channels_min = 2,
38 .channels_max = 2,
39 .buffer_bytes_max = 64*1024,
40 .period_bytes_min = 64,
41 .period_bytes_max = DMA_BUF_SIZE,
42 .periods_min = 2,
43 .periods_max = 255,
44 .fifo_size = 0,
45 };
PCM接口的主要函數被封裝在snd_pcm_ops結構體內,UDA1341 ALSA驅動對snd_pcm_ops結構體的定義如代碼清單17.35。
代碼清單17.35 UDA1341 ALSA驅動PCM接口snd_pcm_ops結構體
1 static struct snd_pcm_ops snd_card_sa11xx_uda1341_playback_ops =
2 {
3   .open   = snd_card_sa11xx_uda1341_open,
4   .close   = snd_card_sa11xx_uda1341_close,
5   .ioctl   = snd_pcm_lib_ioctl,
6   .hw_params         = snd_sa11xx_uda1341_hw_params,
7   .hw_free         = snd_sa11xx_uda1341_hw_free,
8   .prepare = snd_sa11xx_uda1341_prepare,
9   .trigger = snd_sa11xx_uda1341_trigger,
10 .pointer = snd_sa11xx_uda1341_pointer,
11 };
12
13 static struct snd_pcm_ops snd_card_sa11xx_uda1341_capture_ops =
14 {
15 .open   = snd_card_sa11xx_uda1341_open,
16 .close   = snd_card_sa11xx_uda1341_close,
17 .ioctl   = snd_pcm_lib_ioctl,
18 .hw_params         = snd_sa11xx_uda1341_hw_params,
19 .hw_free         = snd_sa11xx_uda1341_hw_free,
20 .prepare = snd_sa11xx_uda1341_prepare,
21 .trigger = snd_sa11xx_uda1341_trigger,
22 .pointer = snd_sa11xx_uda1341_pointer,
23 };
代碼清單17.33第25行調用的snd_card_sa11xx_uda1341_pcm()即是PCM組件的“構造函數”,其實現如代碼清單17.36。
代碼清單17.36 UDA1341 ALSA驅動PCM組件構造函數
1 static int __init snd_card_sa11xx_uda1341_pcm(struct sa11xx_uda1341 *sa11xx_uda1341, int device)
2 {
3   struct snd_pcm *pcm;
4   int err;
5   /* 新建pcm設備,playback和capture都爲1個 */
6   if ((err = snd_pcm_new(sa11xx_uda1341->card, "UDA1341 PCM", device, 1, 1, &pcm)) < 0)
7    return err;
8
9   /* 建立初始緩衝區並設置dma_type爲isa */
10 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
11            snd_dma_isa_data(),
12            64*1024, 64*1024);
13 /* 設置pcm的操作 */
14 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, snd_card_sa11xx_uda1341_playback_ops);
15 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_sa11xx_uda1341_capture_ops);
16 pcm->private_data = sa11xx_uda1341;
17 pcm->info_flags = 0;
18 strcpy(pcm->name, "UDA1341 PCM");
19
20 sa11xx_uda1341_audio_init(sa11xx_uda1341);
21
22 /* 設置DMA控制器 */
23 audio_dma_request(&sa11xx_uda1341->s[SNDRV_PCM_STREAM_PLAYBACK], audio_dma_callback);
24 audio_dma_request(&sa11xx_uda1341->s[SNDRV_PCM_STREAM_CAPTURE], audio_dma_callback);
25
26 sa11xx_uda1341->pcm = pcm;
27
28 return 0;
29 }
在snd_pcm_ops結構體的打開成員函數中,需要根據具體的子流賦值snd_pcm_runtime的hw,如代碼清單17.37。
代碼清單17.37 UDA1341 ALSA驅動snd_pcm_ops結構體open/close成員函數
1 static int snd_card_sa11xx_uda1341_open(struct snd_pcm_substream *substream)
2 {
3   struct sa11xx_uda1341 *chip = snd_pcm_substream_chip(substream);
4   struct snd_pcm_runtime *runtime = substream->runtime;
5   int stream_id = substream->pstr->stream;
6   int err;
7
8   chip->s[stream_id].stream = substream;
9
10 if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) //播放子流
11   runtime->hw = snd_sa11xx_uda1341_playback;
12 else    //錄音子流
13   runtime->hw = snd_sa11xx_uda1341_capture;
14 if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
15   return err;
16 if ((err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,&hw_constraints_rates)) < 0)
17   return err;
18       
19 return 0;
20 }
21
22 static int snd_card_sa11xx_uda1341_close(struct snd_pcm_substream *substream)
23 {
24 struct sa11xx_uda1341 *chip = snd_pcm_substream_chip(substream);
25
26 chip->s[substream->pstr->stream].stream = NULL;
27 return 0;
28 }
在snd_pcm_ops結構體的trigger()成員函數中,控制播放和錄音的啓/停、掛起/恢復,如代碼清單17.38。
代碼清單17.38 UDA1341 ALSA驅動snd_pcm_ops結構體trigger成員函數
1   static int snd_sa11xx_uda1341_trigger(struct snd_pcm_substream *substream, int
2     cmd)
3   {
4     struct sa11xx_uda1341 *chip = snd_pcm_substream_chip(substream);
5     int stream_id = substream->pstr->stream;
6     struct audio_stream *s = &chip->s[stream_id];
7     struct audio_stream *s1 = &chip->s[stream_id ^ 1];
8     int err = 0;
9
10    /* 注意本地中斷已經被中間層代碼禁止 */
11    spin_lock(&s->dma_lock);
12    switch (cmd)
13    {
14      case SNDRV_PCM_TRIGGER_START://開啓PCM
15        if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active) //開啓錄音,不在播放
16        {
17          s1->tx_spin = 1;
18          audio_process_dma(s1);
19        }
20        else
21        {
22          s->tx_spin = 0;
23        }
24
25        /* 被請求的流啓動 */
26        s->active = 1;
27        audio_process_dma(s);
28        break;
29      case SNDRV_PCM_TRIGGER_STOP:
30        /* 被請求的流關閉 */
31        audio_stop_dma(s);
32        if (stream_id == SNDRV_PCM_STREAM_PLAYBACK && s1->active) //在錄音時開啓播放
33        {
34          s->tx_spin = 1;
35          audio_process_dma(s); //啓動DMA
36        }
37        else
38        {
39          if (s1->tx_spin)
40          {
41            s1->tx_spin = 0;
42            audio_stop_dma(s1); //停止DMA
43          }
44        }
45
46        break;
47      case SNDRV_PCM_TRIGGER_SUSPEND: //掛起
48        s->active = 0;
49        #ifdef HH_VERSION
50          sa1100_dma_stop(s->dmach); //停止DMA
51        #endif
52        s->old_offset = audio_get_dma_pos(s) + 1;
53        #ifdef HH_VERSION
54          sa1100_dma_flush_all(s->dmach);
55        #endif
56        s->periods = 0;
57        break;
58      case SNDRV_PCM_TRIGGER_RESUME:   //恢復
59        s->active = 1;
60        s->tx_spin = 0;
61        audio_process_dma(s); //開啓DMA
62        if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active)
63        {
64          s1->tx_spin = 1;
65          audio_process_dma(s1);
66        }
67        break;
68      case SNDRV_PCM_TRIGGER_PAUSE_PUSH:   //暫停
69        #ifdef HH_VERSION
70          sa1100_dma_stop(s->dmach); //停止DMA
71        #endif
72        s->active = 0;
73        if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
74        {
75          if (s1->active)
76          {
77            s->tx_spin = 1;
78            s->old_offset = audio_get_dma_pos(s) + 1;
79            #ifdef HH_VERSION   
80              sa1100_dma_flush_all(s->dmach);
81            #endif
82            audio_process_dma(s); //開啓DMA
83          }
84        }
85        else
86        {
87          if (s1->tx_spin)
88          {
89            s1->tx_spin = 0;
90            #ifdef HH_VERSION   
91              sa1100_dma_flush_all(s1->dmach);
92            #endif
93          }
94        }
95        break;
96      case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:   //暫停釋放
97        s->active = 1;
98        if (s->old_offset)
99        {
100         s->tx_spin = 0;
101         audio_process_dma(s);
102         break;
103       }
104       if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active)
105       {
106         s1->tx_spin = 1;
107         audio_process_dma(s1);
108       }
109       #ifdef HH_VERSION
110         sa1100_dma_resume(s->dmach);
111       #endif
112       break;
113     default:
114       err = - EINVAL;
115       break;
116   }
117   spin_unlock(&s->dma_lock);
118   return err;
119 }
snd_pcm_ops結構體中其它的hw_params()、prepare()、pointer()等成員函數實現較爲簡單,這裏不再贅述。
17.6.3 控制接口的實現
代碼清單17.33第21行調用的snd_chip_uda1341_mixer_new()可以認爲是UDA1341 ALSA驅動mixer控制組件的“構造函數”,其中會創建的控制元素的定義如代碼清單17.39,包括一些枚舉和單值元素。
代碼清單17.39 UDA1341 ALSA驅動控制接口snd_kcontrol_new結構體
1 #define UDA1341_SINGLE(xname, where, reg, shift, mask, invert) /
2 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_uda1341_info_single, /
3    .get = snd_uda1341_get_single, .put = snd_uda1341_put_single, /
4    .private_value = where | (reg << 5) | (shift << 9) | (mask << 12) | (invert << 18) /
5 }
6
7 #define UDA1341_ENUM(xname, where, reg, shift, mask, invert) /
8 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_uda1341_info_enum, /
9    .get = snd_uda1341_get_enum, .put = snd_uda1341_put_enum, /
10   .private_value = where | (reg << 5) | (shift << 9) | (mask << 12) | (invert << 18) /
11 }
12
13 static struct snd_kcontrol_new snd_uda1341_controls[] =
14 {
15 UDA1341_SINGLE("Master Playback Switch", CMD_MUTE, data0_2, 2, 1, 1),
16 UDA1341_SINGLE("Master Playback Volume", CMD_VOLUME, data0_0, 0, 63, 1),
17
18 UDA1341_SINGLE("Bass Playback Volume", CMD_BASS, data0_1, 2, 15, 0),
19 UDA1341_SINGLE("Treble Playback Volume", CMD_TREBBLE, data0_1, 0, 3, 0),
20
21 UDA1341_SINGLE("Input Gain Switch", CMD_IGAIN, stat1, 5, 1, 0),
22 UDA1341_SINGLE("Output Gain Switch", CMD_OGAIN, stat1, 6, 1, 0),
23
24 UDA1341_SINGLE("Mixer Gain Channel 1 Volume", CMD_CH1, ext0, 0, 31, 1),
25 UDA1341_SINGLE("Mixer Gain Channel 2 Volume", CMD_CH2, ext1, 0, 31, 1),
26
27 UDA1341_SINGLE("Mic Sensitivity Volume", CMD_MIC, ext2, 2, 7, 0),
28
29 UDA1341_SINGLE("AGC Output Level", CMD_AGC_LEVEL, ext6, 0, 3, 0),
30 UDA1341_SINGLE("AGC Time Constant", CMD_AGC_TIME, ext6, 2, 7, 0),
31 UDA1341_SINGLE("AGC Time Constant Switch", CMD_AGC, ext4, 4, 1, 0),
32
33 UDA1341_SINGLE("DAC Power", CMD_DAC, stat1, 0, 1, 0),
34 UDA1341_SINGLE("ADC Power", CMD_ADC, stat1, 1, 1, 0),
35
36 UDA1341_ENUM("Peak detection", CMD_PEAK, data0_2, 5, 1, 0),
37 UDA1341_ENUM("De-emphasis", CMD_DEEMP, data0_2, 3, 3, 0),
38 UDA1341_ENUM("Mixer mode", CMD_MIXER, ext2, 0, 3, 0),
39 UDA1341_ENUM("Filter mode", CMD_FILTER, data0_2, 0, 3, 0),
40
41 UDA1341_2REGS("Gain Input Amplifier Gain (channel 2)", CMD_IG, ext4, ext5, 0, 0, 3, 31, 0),
42 };
從上述代碼中宏UDA1341_SINGLE和UDA1341_ENUM的定義可知,單值元素的info()、get()、put()成員函數分別爲 snd_uda1341_info_single()、snd_uda1341_get_single()和 snd_uda1341_put_single();枚舉元素的info()、get()、put()成員函數分別爲 snd_uda1341_info_enum()、snd_uda1341_get_enum()、和snd_uda1341_put_enum()。作爲例子,代碼清單17.40給出了單值元素相關函數的實現。
代碼清單17.40 UDA1341 ALSA驅動控制接口單值元素info/get/put函數
1 static int snd_uda1341_info_single(struct snd_kcontrol *kcontrol, struct
2    snd_ctl_elem_info *uinfo)
3 {
4    int mask = (kcontrol->private_value >> 12) &63;
5
6    uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN :
7      SNDRV_CTL_ELEM_TYPE_INTEGER;
8    uinfo->count = 1; //數量爲1
9    uinfo->value.integer.min = 0; //最小值
10   uinfo->value.integer.max = mask; //最大值
11   return 0;
12 }
13
14 static int snd_uda1341_get_single(struct snd_kcontrol *kcontrol, struct
15   snd_ctl_elem_value *ucontrol)
16 {
17   struct l3_client *clnt = snd_kcontrol_chip(kcontrol);
18   struct uda1341 *uda = clnt->driver_data;
19   int where = kcontrol->private_value &31;
20   int mask = (kcontrol->private_value >> 12) &63;
21   int invert = (kcontrol->private_value >> 18) &1;
22
23   ucontrol->value.integer.value[0] = uda->cfg[where]; //返回給ucontrol
24   if (invert) //如果反轉
25     ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
26
27   return 0;
28 }
29
30 static int snd_uda1341_put_single(struct snd_kcontrol *kcontrol, struct
31   snd_ctl_elem_value *ucontrol)
32 {
33   struct l3_client *clnt = snd_kcontrol_chip(kcontrol);
34   struct uda1341 *uda = clnt->driver_data;
35   int where = kcontrol->private_value &31;
36   int reg = (kcontrol->private_value >> 5) &15;
37   int shift = (kcontrol->private_value >> 9) &7;
38   int mask = (kcontrol->private_value >> 12) &63;
39   int invert = (kcontrol->private_value >> 18) &1;
40   unsigned short val;
41
42   val = (ucontrol->value.integer.value[0] &mask);//從ucontrol獲得值
43   if (invert) //如果反轉
44     val = mask - val;
45
46   uda->cfg[where] = val;
47   return snd_uda1341_update_bits(clnt, reg, mask, shift, val, FLUSH);//更新位
48 }
17.7實例3:PXA255+AC97 ALSA驅動
Intel 公司的XScale PXA255是一款基於ARM5TE內核技術的嵌入式處理器。它提供了符合AC97 rev2.0標準的AC97控制單元(ACUNIT)和音頻控制連接(AC-Link)。ACUNIT就是CODEC控制器,它通過AC-Link連接和控制AC97 CODEC芯片,例如Philips公司出品的一款符合AC97標準的多功能CODEC芯片UCB1400。它不僅是一枚CODEC芯片,還集成了觸摸和能量管理兩個功能模塊,在嵌入式系統中應用廣泛。
AC-Link 連接了ACUNIT 和UCB1400Codec,只要對ACUNIT寄存器操作就可以實現同UCBI400之間的數據傳輸。通過ACUNIT還可讀寫CODEC內部寄存器,實現對音頻採樣和混音處理的控制。當然這些讀寫操作也是經由AC-Link傳輸的。
Intel Xscale PXA255提供了16個DMA通道,可以很方便的爲外圍設備提供數據傳送。ACUNIT也爲CODEC的立體聲輸入輸出和話筒輸入提供了獨立的16 bit數據通道。每個通道都有一個專門的FIFO。
由於它是一個標準的AC97設備,因此,其驅動的控制部分實現可以說是非常的簡單,按照17.4.4節的要求,需要實現AC97 codec寄存器讀寫的硬件級函數pxa2xx_ac97_read()和pxa2xx_ac97_write(),並在模塊初始化時註冊相關的AC97 組件,如代碼清單17.41。
代碼清單17.41 PXA255連接AC97 codec ALSA驅動控制組件
1   static unsigned short pxa2xx_ac97_read(struct snd_ac97 *ac97, unsigned short
2     reg)
3   {
4     unsigned short val = - 1;
5     volatile u32 *reg_addr;
6
7     down(&car_mutex);
8
9     /* 設置首/次codec空間 */
10    reg_addr = (ac97->num &1) ? &SAC_REG_BASE: &PAC_REG_BASE;
11    reg_addr += (reg >> 1);
12
13    /* 通過ac97 link讀 */
14    GSR = GSR_CDONE | GSR_SDONE;
15    gsr_bits = 0;
16    val = *reg_addr;
17    if (reg == AC97_GPIO_STATUS)
18      goto out;
19    if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) &GSR_SDONE, 1) <= 0
20      && !((GSR | gsr_bits) &GSR_SDONE))
21    //等待
22    {
23      printk(KERN_ERR "%s: read error (ac97_reg=%d GSR=%#lx)/n",
24        __FUNCTION__,reg, GSR | gsr_bits);
25      val = - 1;
26      goto out;
27    }
28
29    /* 置數據有效 */
30    GSR = GSR_CDONE | GSR_SDONE;
31    gsr_bits = 0;
32    val = *reg_addr;
33    /* 但是我們已經開啓另一個週期... */
34    wait_event_timeout(gsr_wq, (GSR | gsr_bits) &GSR_SDONE, 1);
35
36    out: up(&car_mutex);
37    return val;
38 }
39
40 static void pxa2xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
41    unsigned short val)
42 {
43    volatile u32 *reg_addr;
44
45    down(&car_mutex);
46
47    /*設置首/次codec空間*/
48    reg_addr = (ac97->num &1) ? &SAC_REG_BASE: &PAC_REG_BASE;
49    reg_addr += (reg >> 1);
50
51    GSR = GSR_CDONE | GSR_SDONE;
52    gsr_bits = 0;
53    *reg_addr = val; //通過ac97 link寫
54    if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) &GSR_CDONE, 1) <= 0
55      && !((GSR | gsr_bits) &GSR_CDONE))
56      printk(KERN_ERR "%s: write error (ac97_reg=%d GSR=%#lx)/n",
57        __FUNCTION__,reg, GSR | gsr_bits);
58
59    up(&car_mutex);
60 }
61
62 static int pxa2xx_ac97_probe(struct platform_device *dev)
63 {
64    struct snd_card *card;
65    struct snd_ac97_bus *ac97_bus;
66    struct snd_ac97_template ac97_template;
67    int ret;
68
69    ret = - ENOMEM;
70    /* 新建card */
71    card = snd_card_new(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0);
72    if (!card)
73      goto err;
74    card->dev = &dev->dev;
75    strncpy(card->driver, dev->dev.driver->name, sizeof(card->driver));
76  
77    /* 構造pcm組件 */
78    ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm);
79    if (ret)
80      goto err;
81
82    /* 申請中斷 */
83    ret = request_irq(IRQ_AC97, pxa2xx_ac97_irq, 0, "AC97", NULL);
84    if (ret < 0)
85      goto err;
86
87    ...
88
89    /* 初始化ac97 bus */
90    ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus);
91    if (ret)
92      goto err;
93    memset(&ac97_template, 0, sizeof(ac97_template));
94    ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97);
95    if (ret)
96      goto err;
97    ...
98  
99    /* 註冊card */
100   ret = snd_card_register(card);
101   if (ret == 0)
102   {
103     platform_set_drvdata(dev, card);
104     return 0;
105   }
106
107   err: if (card)
108     snd_card_free(card);
109   ...
110
111   returns ret;
112 }
17.8總結
音頻設備接口包括PCM、IIS和AC97幾種,分別適用於不同的應用場合。針對音頻設備,Linux內核中包含了2類音頻設備驅動框架,OSS和 ALSA,前者包含dsp和mixer字符設備接口,在用戶空間的編程中,完全使用文件操作;後者以card和組件(pcm、mixer等)爲主線,在用戶空間的編程中不使用文件接口而使用alsalib。
在音頻設備驅動中,幾乎必須使用DMA,而DMA的緩衝區會被分割成一個一個的段,每次 DMA操作進行其中的一段。OSS驅動的阻塞讀寫具有流控能力,在用戶空間不需要進行流量方面的定時工作,但是它需要及時的寫(播放)和讀(錄音),以免出現緩衝區的underflow或overflow。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章