linux系統(tǒng)數(shù)字音頻設備的OSS和ALSA驅動結構分析(完整代碼)
在Linux中,先后出現(xiàn)了音頻設備的兩種框架OSS和ALSA,本節(jié)將在介紹數(shù)字音頻設備及音頻設備硬件接口的基礎上,展現(xiàn)OSS和ALSA驅動的結構。
17.1~17.2節(jié)講解了音頻設備及PCM、IIS和AC97硬件接口。
17.3節(jié)闡述了Linux OSS音頻設備驅動的組成、mixer接口、dsp接口及用戶空間編程方法。
17.4節(jié)闡述了Linux ALSA音頻設備驅動的組成、card和組件管理、PCM設備、control接口、AC97 API及用戶空間編程方法。
17.5節(jié)以S3C2410通過IIS接口外接UDA1341編解碼器的實例講解了OSS驅動。
17.6節(jié)以PXA255通過AC97接口外接AC97 編解碼器的實例講解了ALSA驅動。
17.1數(shù)字音頻設備
目前,手機、PDA、MP3等許多嵌入式設備中包含了數(shù)字音頻設備,一個典型的數(shù)字音頻系統(tǒng)的電路組成如圖17.1所示。圖17.1中的嵌入式微控制器 /DSP中集成了PCM、IIS或AC97音頻接口,通過這些接口連接外部的音頻編解碼器即可實現(xiàn)聲音的AD和DA轉換,圖中的功放完成模擬信號的放大功能。
圖17.1 典型的數(shù)字音頻系統(tǒng)電路
音頻編解碼器是數(shù)字音頻系統(tǒng)的核心,衡量它的指標主要有:
采樣頻率
采樣的過程就是將通常的模擬音頻信號的電信號轉換成二進制碼0和1的過程,這些0和1便構成了數(shù)字音頻文件。如圖17.2中的正弦曲線代表原始音頻曲線,方格代表采樣后得到的結果,二者越吻合說明采樣結果越好。
采樣頻率是每秒鐘的采樣次數(shù),我們常說的 44.1kHz 采樣頻率就是每秒鐘采樣44100 次。理論上采樣頻率越高,轉換精度越高,目前主流的采樣頻率是48kHz。
量化精度
量化精度是指對采樣數(shù)據(jù)分析的精度,比如24bit量化精度就是是將標準電平信號按照2的24次方進行分析,量化精度越高,聲音就越逼真。
17.2音頻設備硬件接口
17.2.1 PCM接口
針對不同的數(shù)字音頻子系統(tǒng),出現(xiàn)了幾種微處理器或DSP與音頻器件間用于數(shù)字轉換的接口。
最簡單的音頻接口是PCM(脈沖編碼調制)接口,該接口由時鐘脈沖(BCLK)、幀同步信號(FS)及接收數(shù)據(jù)(DR)和發(fā)送數(shù)據(jù)(DX)組成。在FS信號的上升沿,數(shù)據(jù)傳輸從MSB(Most Significant Bit)字開始,F(xiàn)S頻率等于采樣率。FS信號之后開始數(shù)據(jù)字的傳輸,單個的數(shù)據(jù)位按順序進行傳輸,1個時鐘周期傳輸1個數(shù)據(jù)字。發(fā)送MSB時,信號的等級首先降到最低,以避免在不同終端的接口使用不同的數(shù)據(jù)方案時造成MSB的丟失。
PCM接口很容易實現(xiàn),原則上能夠支持任何數(shù)據(jù)方案和任何采樣率,但需要每個音頻通道獲得一個獨立的數(shù)據(jù)隊列。
17.2.2 IIS接口
IIS 接口(Inter-IC Sound)在20世紀80年代首先被飛利浦用于消費音頻,并在一個稱為LRCLK(Left/Right CLOCK)的信號機制中經(jīng)過多路轉換,將兩路音頻信號變成單一的數(shù)據(jù)隊列。當LRCLK為高時,左聲道數(shù)據(jù)被傳輸;LRCLK為低時,右聲道數(shù)據(jù)被傳輸。與PCM相比,IIS更適合于立體聲系統(tǒng)。對于多通道系統(tǒng),在同樣的BCLK和LRCLK條件下,并行執(zhí)行幾個數(shù)據(jù)隊列也是可能的。
17.2.3 AC97接口
AC‘97(Audio Codec 1997)是以Intel為首的五個PC廠商Intel、CreaTIve Labs、NS、Analog Device與Yamaha共同提出的規(guī)格標準。與PCM和IIS不同,AC’97不只是一種數(shù)據(jù)格式,用于音頻編碼的內部架構規(guī)格,它還具有控制功能。 AC‘97采用AC-Link與外部的編解碼器相連,AC-Link接口包括位時鐘(BITCLK)、同步信號校正(SYNC)和從編碼到處理器及從處理器中解碼(SDATDIN與SDATAOUT)的數(shù)據(jù)隊列。AC’97數(shù)據(jù)幀以SYNC脈沖開始,包括12個20位時間段(時間段為標準中定義的不同的目的服務)及16位“tag”段,共計256個數(shù)據(jù)序列。例如,時間段“1”和“2”用于訪問編碼的控制寄存器,而時間段“3”和“4”分別負載左、右兩個音頻通道。“tag”段表示其他段中哪一個包含有效數(shù)據(jù)。把幀分成時間段使傳輸控制信號和音頻數(shù)據(jù)僅通過4根線到達9個音頻通道或轉換成其他數(shù)據(jù)流成為可能。與具有分離控制接口的IIS方案相比,AC‘97明顯減少了整體管腳數(shù)。一般來說,AC’97 編解碼器采用TQFP48封裝。
PCM、IIS和AC97各有其優(yōu)點和應用范圍,例如在CD、MD、MP3隨身聽多采用IIS接口,移動電話會采用PCM接口,具有音頻功能的PDA則多使用和PC一樣的AC‘97編碼格式。
17.3 Linux OSS音頻設備驅動
17.3.1 OSS驅動的組成
OSS標準中有2個最基本的音頻設備:mixer(混音器)和DSP(數(shù)字信號處理器)。
在聲卡的硬件電路中,mixer是一個很重要的組成部分,它的作用是將多個信號組合或者疊加在一起,對于不同的聲卡來說,其混音器的作用可能各不相同。OSS驅動中,/dev/mixer設備文件是應用程序對mixer進行操作的軟件接口。
混音器電路通常由兩個部分組成:輸入混音器(input mixer)和輸出混音器(output mixer)。輸入混音器負責從多個不同的信號源接收模擬信號,這些信號源有時也被稱為混音通道或者混音設備。模擬信號通過增益控制器和由軟件控制的音量調節(jié)器后,在不同的混音通道中進行級別(level)調制,然后被送到輸入混音器中進行聲音的合成?;煲羝魃系碾娮娱_關可以控制哪些通道中有信號與混音器相連,有些聲卡只允許連接一個混音通道作為錄音的音源,而有些聲卡則允許對混音通道做任意的連接。經(jīng)過輸入混音器處理后的信號仍然為模擬信號,它們將被送到A/D轉換器進行數(shù)字化處理。
輸出混音器的工作原理與輸入混音器類似,同樣也有多個信號源與混音器相連,并且事先都經(jīng)過了增益調節(jié)。當輸出混音器對所有的模擬信號進行了混合之后,通常還會有一個總控增益調節(jié)器來控制輸出聲音的大小,此外還有一些音調控制器來調節(jié)輸出聲音的音調。經(jīng)過輸出混音器處理后的信號也是模擬信號,它們最終會被送給喇叭或者其它的模擬輸出設備。對混音器的編程包括如何設置增益控制器的級別,以及怎樣在不同的音源間進行切換,這些操作通常來講是不連續(xù)的,而且不會像錄音或者放音那樣需要占用大量的計算機資源。由于混音器的操作不符合典型的讀/寫操作模式,因此除了 open()和close()兩個系統(tǒng)調用之外,大部分的操作都是通過ioctl()系統(tǒng)調用來完成的。與/dev/dsp不同,/dev/mixer允許多個應用程序同時訪問,并且混音器的設置值會一直保持到對應的設備文件被關閉為止。
DSP也稱為編解碼器,實現(xiàn)錄音(錄音)和放音(播放),其對應的設備文件是/dev/dsp或/dev/sound/dsp。OSS聲卡驅動程序提供的 /dev/dsp是用于數(shù)字采樣和數(shù)字錄音的設備文件,向該設備寫數(shù)據(jù)即意味著激活聲卡上的D/A轉換器進行放音,而向該設備讀數(shù)據(jù)則意味著激活聲卡上的 A/D轉換器進行錄音。
在從DSP設備讀取數(shù)據(jù)時,從聲卡輸入的模擬信號經(jīng)過A/D轉換器變成數(shù)字采樣后的樣本,保存在聲卡驅動程序的內核緩沖區(qū)中,當應用程序通過 read()系統(tǒng)調用從聲卡讀取數(shù)據(jù)時,保存在內核緩沖區(qū)中的數(shù)字采樣結果將被復制到應用程序所指定的用戶緩沖區(qū)中。需要指出的是,聲卡采樣頻率是由內核中的驅動程序所決定的,而不取決于應用程序從聲卡讀取數(shù)據(jù)的速度。如果應用程序讀取數(shù)據(jù)的速度過慢,以致低于聲卡的采樣頻率,那么多余的數(shù)據(jù)將會被丟棄(即overflow);如果讀取數(shù)據(jù)的速度過快,以致高于聲卡的采樣頻率,那么聲卡驅動程序將會阻塞那些請求數(shù)據(jù)的應用程序,直到新的數(shù)據(jù)到來為止。
在向DSP設備寫入數(shù)據(jù)時,數(shù)字信號會經(jīng)過D/A轉換器變成模擬信號,然后產生出聲音。應用程序寫入數(shù)據(jù)的速度應該至少等于聲卡的采樣頻率,過慢會產生聲音暫?;蛘咄nD的現(xiàn)象(即underflow)。如果用戶寫入過快的話,它會被內核中的聲卡驅動程序阻塞,直到硬件有能力處理新的數(shù)據(jù)為止。
與其它設備有所不同,聲卡通常不需要支持非阻塞(non-blocking)的I/O操作。即便內核OSS驅動提供了非阻塞的I/O支持,用戶空間也不宜采用。
無論是從聲卡讀取數(shù)據(jù),或是向聲卡寫入數(shù)據(jù),事實上都具有特定的格式(format),如無符號8位、單聲道、8KHz采樣率,如果默認值無法達到要求,可以通過ioctl()系統(tǒng)調用來改變它們。通常說來,在應用程序中打開設備文件/dev/dsp之后,接下去就應該為其設置恰當?shù)母袷?,然后才能從聲卡讀取或者寫入數(shù)據(jù)。
17.3.2 mixer接口
int register_sound_mixer(struct file_operaTIons *fops, int dev);
上述函數(shù)用于注冊1個混音器,第1個參數(shù)fops即是文件操作接口,第2個參數(shù)dev是設備編號,如果填入-1,則系統(tǒng)自動分配1個設備編號。mixer 是 1個典型的字符設備,因此編碼的主要工作是實現(xiàn)file_operaTIons中的open()、ioctl()等函數(shù)。
mixer接口file_operaTIons中的最重要函數(shù)是ioctl(),它實現(xiàn)混音器的不同IO控制命令,代碼清單17.1給出了1個ioctl()的范例。
代碼清單17.1 mixer()接口ioctl()函數(shù)范例
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);
上述函數(shù)與register_sound_mixer()類似,它用于注冊1個dsp設備,第1個參數(shù)fops即是文件操作接口,第2個參數(shù)dev是設備編號,如果填入-1,則系統(tǒng)自動分配1個設備編號。dsp也是1個典型的字符設備,因此編碼的主要工作是實現(xiàn)file_operations中的 read()、write()、ioctl()等函數(shù)。
dsp接口file_operations中的read()和write()函數(shù)非常重要,read()函數(shù)從音頻控制器中獲取錄音數(shù)據(jù)到緩沖區(qū)并拷貝到用戶空間,write()函數(shù)從用戶空間拷貝音頻數(shù)據(jù)到內核空間緩沖區(qū)并最終發(fā)送到音頻控制器。
dsp接口file_operations中的ioctl()函數(shù)處理對采樣率、量化精度、DMA緩沖區(qū)塊大小等參數(shù)設置IO控制命令的處理。
在數(shù)據(jù)從緩沖區(qū)拷貝到音頻控制器的過程中,通常會使用DMA,DMA對聲卡而言非常重要。例如,在放音時,驅動設置完DMA控制器的源數(shù)據(jù)地址(內存中 DMA緩沖區(qū))、目的地址(音頻控制器FIFO)和DMA的數(shù)據(jù)長度,DMA控制器會自動發(fā)送緩沖區(qū)的數(shù)據(jù)填充FIFO,直到發(fā)送完相應的數(shù)據(jù)長度后才中斷一次。
在OSS驅動中,建立存放音頻數(shù)據(jù)的環(huán)形緩沖區(qū)(ring buffer)通常是值得推薦的方法。此外,在OSS驅動中,一般會將1個較大的DMA緩沖區(qū)分成若干個大小相同的塊(這些塊也被稱為“段”,即 fragment),驅動程序使用DMA每次在聲音緩沖區(qū)和聲卡之間搬移一個fragment。在用戶空間,可以使用ioctl()系統(tǒng)調用來調整塊的大小和個數(shù)。
除了read()、write()和ioctl()外,dsp接口的poll()函數(shù)通常也需要被實現(xiàn),以向用戶反饋目前能否讀寫DMA緩沖區(qū)。
在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 }
根據(jù)17.3.2和17.3.3節(jié)的分析,可以畫出一個Linux OSS驅動結構的簡圖。
17.3.4 OSS用戶空間編程
1、DSP編程
對OSS驅動聲卡的編程使用Linux文件接口函數(shù),如圖17.5,DSP接口的操作一般包括如下幾個步驟:
① 打開設備文件/dev/dsp。
采用何種模式對聲卡進行操作也必須在打開設備時指定,對于不支持全雙工的聲卡來說,應該使用只讀或者只寫的方式打開,只有那些支持全雙工的聲卡,才能以讀寫的方式打開,這還依賴于驅動程序的具體實現(xiàn)。Linux允許應用程序多次打開或者關閉與聲卡對應的設備文件,從而能夠很方便地在放音狀態(tài)和錄音狀態(tài)之間進行切換。
② 如果有需要,設置緩沖區(qū)大小。
運行在Linux內核中的聲卡驅動程序專門維護了一個緩沖區(qū),其大小會影響到放音和錄音時的效果,使用ioctl()系統(tǒng)調用可以對它的尺寸進行恰當?shù)脑O置。調節(jié)驅動程序中緩沖區(qū)大小的操作不是必須的,如果沒有特殊的要求,一般采用默認的緩沖區(qū)大小也就可以了。如果想設置緩沖區(qū)的大小,則通常應緊跟在設備文件打開之后,這是因為對聲卡的其它操作有可能會導致驅動程序無法再修改其緩沖區(qū)的大小。
③ 設置聲道(channel)數(shù)量。
根據(jù)硬件設備和驅動程序的具體情況,可以設置為單聲道或者立體聲。
④ 設置采樣格式和采樣頻率
采樣格式包括AFMT_U8(無符號8位)、AFMT_S8(有符號8位)、AFMT_U16_LE(小端模式,無符號16位)、 AFMT_U16_BE(大端模式,無符號16位)、AFMT_MPEG、AFMT_AC3等。使用SNDCTL_DSP_SETFMT IO控制命令可以設置采樣格式。
對于大多數(shù)聲卡來說,其支持的采樣頻率范圍一般為5kHz到44.1kHz或者48kHz,但并不意味著該范圍內的所有連續(xù)頻率都會被硬件支持,在 Linux下進行音頻編程時最常用到的幾種采樣頻率是11025Hz、16000Hz、22050Hz、32000Hz 和44100Hz。使用SNDCTL_DSP_SPEED IO控制命令可以設置采樣頻率。
⑤ 讀寫/dev/dsp實現(xiàn)播放或錄音。
代碼清單17.3的程序實現(xiàn)了利用/dev/dsp接口進行聲音錄制和播放的過程,它的功能是先錄制幾秒鐘音頻數(shù)據(jù),將其存放在內存緩沖區(qū)中,然后再進行放音。
代碼清單17.3 OSS DSP接口應用編程范例
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #define LENGTH 3 /* 存儲秒數(shù) */
9 #define RATE 8000 /* 采樣頻率 */
10 #define SIZE 8 /* 量化位數(shù) */
11 #define CHANNELS 1 /* 聲道數(shù)目 */
12 /* 用于保存數(shù)字音頻數(shù)據(jù)的內存緩沖區(qū) */
13 unsigned char buf[LENGTH *RATE * SIZE * CHANNELS / 8];
14 int main()
15 {
16 int fd; /* 聲音設備的文件描述符 */
17 int arg; /* 用于ioctl調用的參數(shù) */
18 int status; /* 系統(tǒng)調用的返回值 */
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 /* 設置采樣時的量化位數(shù) */
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 /* 設置采樣時的通道數(shù)目 */
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 /* 循環(huán),直到按下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 /* 在繼續(xù)錄音前等待放音結束 */
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()系統(tǒng)調用來完成,所有控制命令都以SOUND_MIXER或者MIXER開頭,表17.1列出了常用的混音器控制命令。
表17.1 混音器常用命令
命 令 作 用
SOUND_MIXER_VOLUME 主音量調節(jié)
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輸入
對聲卡的輸入增益和輸出增益進行調節(jié)是混音器的一個主要作用,目前大部分聲卡采用的是8位或者16位的增益控制器,聲卡驅動程序會將它們變換成百分比的形式,也就是說無論是輸入增益還是輸出增益,其取值范圍都是從0到100。
? SOUND_MIXER_READ宏
在進行混音器編程時,可以使用 SOUND_MIXER_READ宏來讀取混音通道的增益大小,例如如下代碼可以獲得麥克風的輸入增益:
ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);
對于只有一個混音通道的單聲道設備來說,返回的增益大小保存在低位字節(jié)中。而對于支持多個混音通道的雙聲道設備來說,返回的增益大小實際上包括兩個部分,分別代表左、右兩個聲道的值,其中低位字節(jié)保存左聲道的音量,而高位字節(jié)則保存右聲道的音量。下面的代碼可以從返回值中依次提取左右聲道的增益大小:
int left, right;
left = vol & 0xff;
right = (vol & 0xff00) 》》 8;
? SOUND_MIXER_WRITE宏
如果想設置混音通道的增益大小,則可以通過SOUND_MIXER_WRITE宏來實現(xiàn),例如下面的語句可以用來設置麥克風的輸入增益:
vol = (right 《《 8) + left;
ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);
? 查詢Mixer信息
聲卡驅動程序提供了多個ioctl()系統(tǒng)調用來獲得混音器的信息,它們通常返回一個整型的位掩碼,其中每一位分別代表一個特定的混音通道,如果相應的位為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”);
大多數(shù)聲卡提供了多個錄音源,通過 SOUND_MIXER_READ_RECSRC可以查詢出當前正在使用的錄音源,同一時刻可使用2個或2個以上的錄音源,具體由聲卡硬件本身決定。相應地,使用 SOUND_MIXER_WRITE_RECSRC可以設置聲卡當前使用的錄音源,如下代碼可以將CD輸入作為聲卡的錄音源使用:
devmask = SOUND_MIXER_CD;
ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &devmask);
此外,所有的混音通道都有單聲道和雙聲道的區(qū)別,如果需要知道哪些混音通道提供了對立體聲的支持,可以通過SOUND_MIXER_READ_STEREODEVS來獲得。
代碼清單17.4的程序實現(xiàn)了利用/dev/mixer接口對混音器進行編程的過程,該程序可對各種混音通道的增益進行調節(jié)。
代碼清單17.4 OSS mixer接口應用編程范例
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
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 /n”
17 “%s /n/n”“Where 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; /* 系統(tǒng)調用的返回值 */
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 }
編譯上述程序為可執(zhí)行文件mixer,執(zhí)行。/mixer 或。/mixer 可設置增益,device可以是vol、pcm、speaker、line、mic、cd、igain、line1、 phin、video。
17.4 Linux ALSA音頻設備驅動
17.4.1 ALSA的組成
雖然OSS已經(jīng)非常成熟,但它畢竟是一個沒有完全開放源代碼的商業(yè)產品,而ALSA (Advanced Linux Sound Architecture)恰好彌補了這一空白,它符合GPL,是在Linux下進行音頻編程時另一種可供選擇的聲卡驅動體系結構,其官方網(wǎng)站為 http://www.alsa-project.org/。ALSA除了像OSS那樣提供了一組內核驅動程序模塊之外,還專門為簡化應用程序的編寫提供了相應的函數(shù)庫,與OSS提供的基于ioctl的原始編程接口相比,ALSA函數(shù)庫使用起來要更加方便一些。ALSA的主要特點有:
? 支持多種聲卡設備
? 模塊化的內核驅動程序
? 支持SMP和多線程
? 提供應用開發(fā)函數(shù)庫(alsa-lib)以簡化應用程序開發(fā)
? 支持OSS API,兼容OSS應用程序
ALSA 具有更加友好的編程接口,并且完全兼容于OSS,對應用程序員來講無疑是一個更佳的選擇。ALSA系統(tǒng)包括驅動包alsa-driver、開發(fā)包 alsa-libs、開發(fā)包插件alsa-libplugins、設置管理工具包alsa-utils、其他聲音相關處理小程序包alsa-tools、特殊音頻固件支持包alsa- firmware、OSS接口兼容模擬層工具alsa-oss共7個子項目,其中只有驅動包是必需的。
alsa- driver指內核驅動程序,包括硬件相關的代碼和一些公共代碼,非常龐大,代碼總量達數(shù)十萬行;alsa-libs指用戶空間的函數(shù)庫,提供給應用程序使用,應用程序應包含頭文件asoundlib.h,并使用共享庫libasound.so;alsa-utils包含一些基于ALSA的用于控制聲卡的應用程序,如alsaconf(偵測系統(tǒng)中聲卡并寫一個適合的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.4.1 card和組件管理
對于每個聲卡而言,必須創(chuàng)建1個“card”實例。card是聲卡的“總部”,它管理這個聲卡上的所有設備(組件),如PCM、mixers、MIDI、synthesizer等。因此,card和組件是ALSA聲卡驅動中的主要組成元素。
1、創(chuàng)建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是要分配的額外數(shù)據(jù)的大小,分配的extra_size大小的內存將作為card-》private_data。
2、創(chuàng)建組件
int snd_device_new(struct snd_card *card, snd_device_type_t type,
void *device_data, struct snd_device_ops *ops);
當 card被創(chuàng)建后,設備(組件)能夠被創(chuàng)建并關聯(lián)于該card。第1個參數(shù)是snd_card_new()創(chuàng)建的card指針,第2個參數(shù)type 指的是device-level即設備類型,形式為SNDRV_DEV_XXX,包括SNDRV_DEV_CODEC、 SNDRV_DEV_CONTROL、SNDRV_DEV_PCM、SNDRV_DEV_RAWMIDI等,用戶自定義設備的device-level是 SNDRV_DEV_LOWLEVEL,ops參數(shù)是1個函數(shù)集(定義為snd_device_ops結構體)的指針,device_data是設備數(shù)據(jù)指針,注意函數(shù)snd_device_new()本身不會分配設備數(shù)據(jù)的內存,因此應事先分配。
3、組件釋放
每個ALSA預定義的組件在構造時需調用snd_device_new(),而每個組件的析構方法則在函數(shù)集中被包含。對于PCM、AC97此類預定義組件,我們不需關心它們的析構,而對于自定義的組件,則需要填充snd_device_ops中的析構函數(shù)指針dev_free,這樣,當 snd_card_free()被調用時,組件將自動被釋放。
4、芯片特定的數(shù)據(jù)(Chip-Specific Data)
芯片特定的數(shù)據(jù)一般以struct xxxchip結構體形式組織,這個結構體中包含芯片相關的I/O端口地址、資源指針、中斷號等,其意義等同于字符設備驅動中的 file-》private_data。定義芯片特定的數(shù)據(jù)主要有2種方法,一種方法是將sizeof(struct xxxchip)傳入snd_card_new()的extra_size參數(shù),它將自動成員snd_card的private_data成員,如代碼清單17.5;另一種方法是在snd_card_new()傳入給extra_size參數(shù)0,再分配sizeof(struct xxxchip)的內存,將分配內存的地址傳入snd_device_new()的device_data的參數(shù),如代碼清單17.6。
代碼清單17.5 創(chuàng)建芯片特定的數(shù)據(jù)方法1
1 struct xxxchip //芯片特定的數(shù)據(jù)結構體
2 {
3 。。。
4 };
5 card = snd_card_new(index, id, THIS_MODULE, sizeof(struct
6 xxxchip)); //創(chuàng)建聲卡并申請xxx_chi內存作為card-》 private_data
7 struct xxxchip *chip = card-》private_data;
代碼清單17.6 創(chuàng)建芯片特定的數(shù)據(jù)方法2
1 struct snd_card *card;
2 struct xxxchip *chip;
3 //使用0作為第4個參數(shù),并動態(tài)分配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()創(chuàng)建的card指針:
14 chip-》card = card;
15 static struct snd_device_ops ops =
16 {
17 .dev_free = snd_xxx_chip_dev_free, //組件析構
18 };
19 。。。
20 //創(chuàng)建自定義組件
21 snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
22 //在析構函數(shù)中釋放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()函數(shù)注冊這個聲卡:
int snd_card_register(struct snd_card *card)
對應的snd_card_free()完成相反的功能:
int snd_card_free(struct snd_card *card);
4、put()函數(shù)
put()用于從用戶空間寫入值,如果值被改變,該函數(shù)返回1,否則返回0;如果發(fā)生錯誤,該函數(shù)返回1個錯誤碼。代碼清單17.22給出了1個put()函數(shù)的范例。
代碼清單17.22 snd_ctl_elem_info結構體中put()函數(shù)范例
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()函數(shù)而言,如果control有多于1個元素,即count》1,則每個元素都需要被返回或寫入。
5、構造control
當所有事情準備好后,我們需要創(chuàng)建1個control,調用snd_ctl_add()和snd_ctl_new1()這2個函數(shù)來完成,這2個函數(shù)的原型為:
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()函數(shù)用于創(chuàng)建1個snd_kcontrol并返回其指針,snd_ctl_add()函數(shù)用于將創(chuàng)建的snd_kcontrol添加到對應的card中。
6、變更通知
如果驅動中需要在中斷服務程序中改變或更新1個control,可以調用snd_ctl_notify()函數(shù),此函數(shù)原型為:
void snd_ctl_notify(struct snd_card *card, unsigned int mask, struct snd_ctl_elem_id *id);
該函數(shù)的第2個參數(shù)為事件掩碼(event-mask),第3個參數(shù)為該通知的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編解碼層被很好地定義,利用它,驅動工程師只需編寫少量底層的控制函數(shù)。
1、AC97實例構造
為了創(chuàng)建1個AC97實例,首先需要調用snd_ac97_bus()函數(shù)構建AC97總線及其操作,這個函數(shù)的原型為:
int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops *ops,
void *private_data, struct snd_ac97_bus **rbus);
該函數(shù)的第3個參數(shù)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); //復位函數(shù)
4 //寫入函數(shù)
5 void(*write)(struct snd_ac97 *ac97, unsigned short reg, unsigned short val);
6 //讀取函數(shù)
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()函數(shù)注冊混音器,這個函數(shù)的原型為:
int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97);
代碼清單17.24演示了AC97實例的創(chuàng)建過程。
代碼清單17.24 AC97實例的創(chuàng)建過程范例
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總線與操作創(chuàng)建
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;//私有數(shù)據(jù)
15 //注冊混音器
16 snd_ac97_mixer(bus, &ac97, &chip-》ac97);
上述代碼第1行的snd_ac97_bus結構體指針bus的指針被傳入第9行的snd_ac97_bus()函數(shù)并被賦值,chip-》ac97的指針被傳入第16行的snd_ac97_mixer()并被賦值,chip-》ac97將成員新創(chuàng)建AC97實例的指針。
如果1個聲卡上包含多個編解碼器,這種情況下,需要多次調用snd_ac97_mixer()并對snd_ac97的num成員(編解碼器序號)賦予相應的序號。驅動中可以為不同的編解碼器編寫不同的snd_ac97_bus_ops成員函數(shù)中,或者只是在相同的一套成員函數(shù)中通過ac97.num獲得序號后再區(qū)分進行具體的操作。
2、snd_ac97_bus_ops成員函數(shù)
snd_ac97_bus_ops結構體中的read()和write()成員函數(shù)完成底層的硬件訪問,reset()函數(shù)用于復位編解碼器,wait()函數(shù)用于編解碼器標準初始化過程中的特定等待,如果芯片要求額外的等待時間,應實現(xiàn)這個函數(shù),init()用于完成編解碼器附加的初始化。代碼清單17.25給出了read()和write()函數(shù)的范例。
代碼清單17.25 snd_ac97_bus_ops結構體中read()和write()函數(shù)范例
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、修改寄存器
如果需要在驅動中訪問編解碼器,可使用如下函數(shù):
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()的區(qū)別在于前者在值已經(jīng)設置的情況下不會再設置,而后者則會再寫一次。snd_ac97_update_bits()用于更新寄存器的某些位,由mask決定。
除此之外,還有1個函數(shù)可用于設置采樣率:
int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate);
這個函數(shù)的第2個參數(shù)reg可以是AC97_PCM_MIC_ADC_RATE、AC97_PCM_FRONT_DAC_RATE、 AC97_PCM_LR_ADC_RATE和AC97_SPDIF,對于AC97_SPDIF而言,寄存器并非真地被改變了,只是相應的IEC958狀態(tài)位將被更新。
4、時鐘調整
在一些芯片上,編解碼器的時鐘不是48000而是使用PCI時鐘以節(jié)省1個晶體,在這種情況下,我們應該改變bus-》clock為相應的值,例如intel8x0和es1968包含時鐘的自動測量函數(shù)。
5、proc文件
ALSA AC97接口會創(chuàng)建如/proc/asound/card0/codec97#0/ac97#0-0和ac97#0-0+regs這樣的proc文件,通過這些文件可以察看編解碼器目前的狀態(tài)和寄存器。
如果1個chip上有多個codecs,可多次調用snd_ac97_mixer()。
17.4.5 ALSA用戶空間編程
ALSA驅動的聲卡在用戶空間不宜直接使用文件接口,而應使用alsa-lib,代碼清單17.26給出了基于ALSA音頻驅動的最簡單的放音應用程序。
代碼清單17.26 ALSA用戶空間放音程序
1 #include
2 #include
3 #include
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個參數(shù)為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 //設置通道數(shù)量
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 //完成硬件參數(shù)設置,使設備準備好
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 //寫音頻數(shù)據(jù)到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節(jié)給出的OSS驅動用戶空間編程的流程基本是一致的,都經(jīng)過了“打開――設置參數(shù)――讀寫音頻數(shù)據(jù)”的過程,不同在于OSS打開的是設備文件,設置參數(shù)使用的是Linux ioctl()系統(tǒng)調用,讀寫音頻數(shù)據(jù)使用的是Linux read()、write()文件API,而ALSA則全部使用alsa-lib中的API。
把上述代碼第80行的snd_pcm_writei()函數(shù)替換為snd_pcm_readi()就編程了1個最簡單的錄音程序。
代碼清單17.27的程序打開1個音頻接口,配置它為立體聲、16位、44.1khz采樣和基于interleave的讀寫。它阻塞等待直接接口準備好接收放音數(shù)據(jù),這時候將數(shù)據(jù)拷貝到緩沖區(qū)。這種設計方法使得程序很容易移植到類似JACK、LADSPA、Coreaudio、VST等callback機制驅動的系統(tǒng)。
代碼清單17.27 ALSA用戶空間放音程序(基于“中斷”)
1 #include
2 #include
3 #include
4 #include
5 #include
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 /* 填充緩沖區(qū) */
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幀傳遞一次數(shù)據(jù) */
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 /* 一旦有數(shù)據(jù)就開始播放 */
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 /* 等待,直到接口準備好傳遞數(shù)據(jù),或者1秒超時發(fā)生 */
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數(shù)據(jù) */
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 /* 傳遞數(shù)據(jù) */
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來控制。
Philips 公司的UDA1341支持IIS總線數(shù)據(jù)格式,采用位元流轉換技術進行信號處理,完成聲音信號的模數(shù)轉換,具有可編程增益放大器和數(shù)字自動增益控制器,其低功耗、低電壓的特點使其非常適合用于MD/CD、筆記本電腦等便攜式設備。UDA1341對外提供2組音頻信號輸入接口,每組包括左右2個聲道。
圖17.8 UDA1341 內部結構
如圖17.8所示,2組音頻輸入在UDA1341內部的處理存在很大差別:第一組音頻信號輸入后經(jīng)過1個0 dB/6 dB開關后采樣送入數(shù)字混音器:第二組音頻信號輸入后先經(jīng)過可編程增益放大器(PGA),然后再進行采樣,采樣后的數(shù)據(jù)要再經(jīng)過數(shù)字自動增益控制器(AGC)送入數(shù)字混音器。設計硬件電路時選用第二組輸入音頻信號,這樣可以通過軟件的方法實現(xiàn)對系統(tǒng)輸入音量大小的調節(jié)。顯然選用第二組可以通過L3總線接口控制AGC來實現(xiàn)。另外,選擇通道2還可以通過PGA對從MIC輸入的信號進行片內放大。
S3C2410與UDA1341之間的IIS接口有3種工作方式:
• 正常傳輸模式。該模式下使用IISCON寄存器對FIFO進行控制,CPU通過輪詢方式訪問FIFO寄存器,以完成對FIFO緩存?zhèn)鬏敾蚪邮盏奶幚怼?/p>
• DMA模式。通過設置IISFCON寄存器使IIS接口工作于這種模式。在該模式下,F(xiàn)IFO寄存器組的控制權掌握在DMA控制器上,當FIFO滿時,由DMA控制器對FIFO中的數(shù)據(jù)進行處理。DMA模式的選擇由IISCON寄存器的第4和第5位控制。
• 傳輸/接收模式。該模式下,IIS數(shù)據(jù)線將通過雙通道DMA同時接收和發(fā)送音頻數(shù)據(jù)。在OSS驅動中,將使用此模式。
17.5.2注冊dsp和mixer接口
如代碼清單17.28,在UDA1341 OSS驅動的模塊加載函數(shù)中,將完成如下工作:
• 初始化IIS接口硬件,設置L3總線對應的GPIO。
• 申請用于音頻數(shù)據(jù)傳輸?shù)腄MA通道。
• 初始化UDA1341到恰當?shù)墓ぷ髂J健?/p>
• 注冊dsp和mixer接口。
代碼清單17.28 UDA1341 OSS驅動模塊加載函數(shù)
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驅動的模塊卸載函數(shù)中,將完成與模塊加載函數(shù)相反的工作,如代碼清單17.29。
代碼清單17.29 UDA1341 OSS驅動模塊卸載函數(shù)
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控制函數(shù)
UDA1341 OSS驅動的ioctl()函數(shù)處理多個mixer命令,如SOUND_MIXER_INFO、 SOUND_MIXER_READ_STEREODEVS、SOUND_MIXER_WRITE_VOLUME等,用于獲得或設置音量和增益等信息,如代碼清單17.30所示。
代碼清單17.30 UDA1341 OSS驅動ioctl()函數(shù)
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接口音頻數(shù)據(jù)傳輸
OSS聲卡驅動中,dsp接口的讀寫函數(shù)是核心中的核心,直接對應著錄音和放音的流程。
OSS 的讀函數(shù)存在一個與普通字符設備驅動讀函數(shù)不同的地方,那就是一般而言,對于普通字符設備驅動,如果用戶要求讀count個字節(jié),而實際上只有 count1字節(jié)可獲得(count1《 count)時,它會將這count1字節(jié)拷貝給用戶后即返回count1;而dsp接口的讀函數(shù)會分次拷貝,如果第1次不能滿足,它會等待第2次,直到 “count1 + count2 + 。。。 = count”為止再返回count。這種設計是合理的,因為OSS驅動應該負責音頻數(shù)據(jù)的流量控制。代碼清單17.31給出了UDA1341 OSS驅動的讀函數(shù)實現(xiàn)。
代碼清單17.31 UDA1341 OSS驅動的讀函數(shù)
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; //得到數(shù)據(jù)區(qū)的指針
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 //依次從緩存區(qū)讀取數(shù)據(jù)
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 //從緩存區(qū)讀取數(shù)據(jù)
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, //調用拷貝函數(shù)
54 chunksize))
55 {
56 up(&b-》sem);
57 return - EFAULT;
58 }
59 b-》size -= chunksize;
60
61 buffer += chunksize;
62 count -= chunksize; //已經(jīng)給用戶拷貝了一部分,count減少
63 if (b-》size 》 0)
64 {
65 up(&b-》sem);
66 break;
67 }
68 //將緩存區(qū)釋放
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接口的寫函數(shù)與讀函數(shù)類似,一般來說,它也應該滿足用戶的所有寫需求后再返回,如代碼清單17.32。
代碼清單17.32 UDA1341 OSS驅動的寫函數(shù)
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緩沖區(qū)
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 //從用戶空間拷貝音頻數(shù)據(jù)
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; //已經(jīng)從用戶拷貝了一部分,count減少
72 if (b-》size 《 s-》fragsize)
73 {
74 up(&b-》sem);
75 break;
76 }
77 //發(fā)起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體系結構來實現(xiàn)它的驅動,會和OSS大不一樣。如17.4.1節(jié)所言,在模塊初始化和卸載的時候,需要注冊和注銷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私有數(shù)據(jù)釋放
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設備的實現(xiàn)
PCM組件直接對應著ALSA驅動的錄音和放音,從17.4.2節(jié)的描述可知,驅動從需要定義對應相應的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接口的主要函數(shù)被封裝在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組件的“構造函數(shù)”,其實現(xiàn)如代碼清單17.36。
代碼清單17.36 UDA1341 ALSA驅動PCM組件構造函數(shù)
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 /* 建立初始緩沖區(qū)并設置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結構體的打開成員函數(shù)中,需要根據(jù)具體的子流賦值snd_pcm_runtime的hw,如代碼清單17.37。
代碼清單17.37 UDA1341 ALSA驅動snd_pcm_ops結構體open/close成員函數(shù)
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()成員函數(shù)中,控制播放和錄音的啟/停、掛起/恢復,如代碼清單17.38。
代碼清單17.38 UDA1341 ALSA驅動snd_pcm_ops結構體trigger成員函數(shù)
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 /* 注意本地中斷已經(jīng)被中間層代碼禁止 */
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()等成員函數(shù)實現(xiàn)較為簡單,這里不再贅述。
17.6.3 控制接口的實現(xiàn)
代碼清單17.33第21行調用的snd_chip_uda1341_mixer_new()可以認為是UDA1341 ALSA驅動mixer控制組件的“構造函數(shù)”,其中會創(chuàng)建的控制元素的定義如代碼清單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()成員函數(shù)分別為 snd_uda1341_info_single()、snd_uda1341_get_single()和 snd_uda1341_put_single();枚舉元素的info()、get()、put()成員函數(shù)分別為 snd_uda1341_info_enum()、snd_uda1341_get_enum()、和snd_uda1341_put_enum()。作為例子,代碼清單17.40給出了單值元素相關函數(shù)的實現(xiàn)。
代碼清單17.40 UDA1341 ALSA驅動控制接口單值元素info/get/put函數(shù)
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; //數(shù)量為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芯片,還集成了觸摸和能量管理兩個功能模塊,在嵌入式系統(tǒng)中應用廣泛。
AC-Link 連接了ACUNIT 和UCB1400Codec,只要對ACUNIT寄存器操作就可以實現(xiàn)同UCBI400之間的數(shù)據(jù)傳輸。通過ACUNIT還可讀寫CODEC內部寄存器,實現(xiàn)對音頻采樣和混音處理的控制。當然這些讀寫操作也是經(jīng)由AC-Link傳輸?shù)摹?/p>
Intel Xscale PXA255提供了16個DMA通道,可以很方便的為外圍設備提供數(shù)據(jù)傳送。ACUNIT也為CODEC的立體聲輸入輸出和話筒輸入提供了獨立的16 bit數(shù)據(jù)通道。每個通道都有一個專門的FIFO。
由于它是一個標準的AC97設備,因此,其驅動的控制部分實現(xiàn)可以說是非常的簡單,按照17.4.4節(jié)的要求,需要實現(xiàn)AC97 codec寄存器讀寫的硬件級函數(shù)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 /* 置數(shù)據(jù)有效 */
30 GSR = GSR_CDONE | GSR_SDONE;
31 gsr_bits = 0;
32 val = *reg_addr;
33 /* 但是我們已經(jīng)開啟另一個周期。。。 */
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的緩沖區(qū)會被分割成一個一個的段,每次 DMA操作進行其中的一段。OSS驅動的阻塞讀寫具有流控能力,在用戶空間不需要進行流量方面的定時工作,但是它需要及時的寫(播放)和讀(錄音),以免出現(xiàn)緩沖區(qū)的underflow或overflow。