1. 程式人生 > >linux audio device driver音訊裝置驅動

linux audio device driver音訊裝置驅動

第十七章 Linux 音訊裝置驅動 本章導讀 在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); 17.4.2 PCM 裝置 每個音效卡最多可以有4 個PCM 例項,1 個PCM 例項對應1 個裝置檔案。PCM 例項由PCM 放音和錄音流組成, 而每個PCM 流又由1 個或多個PCM 子流組成。有的音效卡支援多重放音功能,例如,emu10k1 包含1 個32 個 立體聲子流的PCM 放音裝置。 1、PCM 例項構造 int snd_pcm_new(struct snd_card *card, char *id, int device, int playback_count, int capture_count, struct snd_pcm ** rpcm); 第1 個引數是card 指標,第2 個是標識字串,第3 個是PCM 裝置索引(0 表示第1 個PCM 裝置),第4 和第5 個分別為放音和錄音裝置的子流數。當存在多個子流時,需要恰當地處理open()、close()和其它 函式。在每個回撥函式中,可以通過snd_pcm_substream 的number 成員得知目前操作的究竟是哪個子流, 如: struct snd_pcm_substream *substream; int index = substream->number; 一種習慣的做法是在驅動中定義1 個PCM“建構函式”,負責PCM 例項的建立,如程式碼清單17.7。 程式碼清單17.7 PCM 裝置“建構函式” 1 static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip) 2 { 3 struct snd_pcm *pcm; 4 int err; 5 //建立PCM 例項 6 if ((err = snd_pcm_new(chip->card, "xxx Chip", 0, 1, 1, &pcm)) < 0) 7 return err; 8 pcm->private_data = chip; //置pcm->private_data 為晶片特定資料 9 strcpy(pcm->name, "xxx Chip"); 10 chip->pcm = pcm; 11 ... 12 return 0; 13 } 2、設定PCM 操作 void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops); 第1 個引數是snd_pcm 的指標,第2 個引數是SNDRV_PCM_STREAM_PLAYBACK 或SNDRV_PCM_STREAM_CAPTURE, 而第3 個引數是PCM 操作結構體snd_pcm_ops,這個結構體的定義如程式碼清單17.8。 程式碼清單17.8 snd_pcm_ops 結構體 1 struct snd_pcm_ops 2 { 3 int (*open)(struct snd_pcm_substream *substream);//開啟 4 int (*close)(struct snd_pcm_substream *substream);//關閉 5 int (*ioctl)(struct snd_pcm_substream * substream, 6 unsigned int cmd, void *arg);//io 控制 7 int (*hw_params)(struct snd_pcm_substream *substream, 8 struct snd_pcm_hw_params *params);//硬體引數 9 int (*hw_free)(struct snd_pcm_substream *substream); //資源釋放 10 int (*prepare)(struct snd_pcm_substream *substream);//準備 11 //在PCM 被開始、停止或暫停時呼叫 12 int (*trigger)(struct snd_pcm_substream *substream, int cmd); 13 snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);// 當前緩衝區的硬體位置 14 //緩衝區拷貝 15 int (*copy)(struct snd_pcm_substream *substream, int channel, 16 snd_pcm_uframes_t pos, 17 void __user *buf, snd_pcm_uframes_t count); 18 int (*silence)(struct snd_pcm_substream *substream, int channel, 19 snd_pcm_uframes_t pos, snd_pcm_uframes_t count); 20 struct page *(*page)(struct snd_pcm_substream *substream, 21 unsigned long offset); 22 int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma); 23 int (*ack)(struct snd_pcm_substream *substream); 24 }; snd_pcm_ops 中的所有操作都需事先通過snd_pcm_substream_chip()獲得xxxchip 指標,例如: int xxx() { struct xxxchip *chip = snd_pcm_substream_chip(substream); ... } 當1 個PCM 子流被開啟時,snd_pcm_ops 中的open()函式將被呼叫,在這個函式中,至少需要初始化 runtime->hw 欄位,程式碼清單17.9 給出了open()函式的範例。 程式碼清單17.9 snd_pcm_ops 結構體中open()函式 1 static int snd_xxx_open(struct snd_pcm_substream *substream) 2 { 3 //從子流獲得xxxchip 指標 4 struct xxxchip *chip = snd_pcm_substream_chip(substream); 5 //獲得PCM 執行時資訊指標 6 struct snd_pcm_runtime *runtime = substream->runtime; 7 ... 8 //初始化runtime->hw 9 runtime->hw = snd_xxxchip_playback_hw; 10 return 0; 11 } 上述程式碼中的snd_xxxchip_playback_hw 是預先定義的硬體描述。在open()函式中,可以分配1 段私有數 據。如果硬體配置需要更多的限制,也需設定硬體限制。 當PCM 子流被關閉時,close()函式將被呼叫。如果open()函式中分配了私有資料,則在close()函式中應 該釋放substream 的私有資料,程式碼清單17.10 給出了close()函式的範例。 程式碼清單17.10 snd_pcm_ops 結構體中close()函式 1 static int snd_xxx_close(struct snd_pcm_substream *substream) 2 { 3 //釋放子流私有資料 4 kfree(substream->runtime->private_data); 5 //... 6 } 驅動中通常可以給snd_pcm_ops 的ioctl()成員函式傳遞通用的snd_pcm_lib_ioctl()函式。 snd_pcm_ops 的hw_params()成員函式將在應用程式設定硬體引數(PCM 子流的週期大小、緩衝區大小和格 式等)的時候被呼叫,它的形式如下: static int snd_xxx_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *hw_params); 在這個函式中,將完成大量硬體設定,甚至包括緩衝區分配,這時可呼叫如下輔助函式: snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); 僅當DMA 緩衝區已被預先分配的情況下,上述呼叫才可成立。 與hw_params()對應的函式是hw_free(),它釋放由hw_params()分配的資源,例如,通過如下呼叫釋放 snd_pcm_lib_malloc_pages()緩衝區: snd_pcm_lib_free_pages(substream); 當PCM 被“準備”時,prepare()函式將被呼叫,在其中可以設定取樣率、格式等。prepare()函式與 hw_params()函式的不同在於對prepare()的呼叫發生在snd_pcm_prepare()每次被呼叫的時候。prepare() 的形式如下: static int snd_xxx_prepare(struct snd_pcm_substream *substream); trigger()成員函式在PCM 被開始、停止或暫停時呼叫,函式的形式如下: static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd); cmd 引數定義了具體的行為, 在trigger() 成員函式中至少要處理SNDRV_PCM_TRIGGER_START 和 SNDRV_PCM_TRIGGER_STOP 命令,如果PCM 支援暫停,還應處理SNDRV_PCM_TRIGGER_PAUSE_PUSH 和 SNDRV_PCM_TRIGGER_PAUSE_RELEASE 命令。如果裝置支援掛起/恢復,當能量管理狀態發生變化時將處理 SNDRV_PCM_TRIGGER_SUSPEND 和SNDRV_PCM_TRIGGER_RESUME 這2 個命令。注意trigger()函式是原子的, 中途不能睡眠。程式碼清單17.11 給出了1 個trigger()函式的範例。 程式碼清單17.11 snd_pcm_ops 結構體中trigger()函式 1 static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd) 2 { 3 switch (cmd) 4 { 5 case SNDRV_PCM_TRIGGER_START: 6 // 開啟PCM 引擎 7 break; 8 case SNDRV_PCM_TRIGGER_STOP: 9 // 停止PCM 引擎 10 break; 11 ...//其它命令 12 default: 13 return - EINVAL; 14 } 15 } pointer()函式用於PCM 中間層查詢目前緩衝區的硬體位置,該函式以幀的形式返回0~buffer_size – 1 的位置(ALSA 0.5.x 中為位元組形式),此函式也是原子的。 copy()和silence()函式一般可以省略,但是,當硬體緩衝區不處於常規記憶體中時需要。例如,一些裝置 有自己的不能被對映的硬體緩衝區,這種情況下,我們不得不將資料從記憶體緩衝區拷貝到硬體緩衝區。例 外,當記憶體緩衝區在物理和虛擬地址上都不連續時,這2 個函式也必須被實現。 3、分配緩衝區 分配緩衝區的最簡單方法是呼叫如下函式: int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm, int type, void *data, size_t size, size_t max); type 引數是緩衝區的型別,包含SNDRV_DMA_TYPE_UNKNOWN(未知)、SNDRV_DMA_TYPE_CONTINUOUS(連續 的非DMA 記憶體)、SNDRV_DMA_TYPE_DEV (連續的通用裝置),SNDRV_DMA_TYPE_DEV_SG(通用裝置SG-buffer) 和SNDRV_DMA_TYPE_SBUS(連續的SBUS)。如下程式碼將分配64KB 的緩衝區: snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),64*1024, 64*1024); 4、設定標誌 在構造PCM 例項、設定操作集並分配緩衝區之後,如果有需要,應設定PCM 的資訊標誌,例如,如果PCM 裝置只支援半雙工,則這樣定義標誌: pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; 5、PCM 例項析構 PCM 例項的“解構函式”並非是必須的,因為PCM 例項會被PCM 中間層程式碼自動釋放,如果驅動中分配了 一些特別的記憶體空間,則必須定義“解構函式”,程式碼清單17.x 給出了PCM“解構函式”與對應的“構造 函式”,“解構函式”會釋放“建構函式”中建立的xxx_private_pcm_data。 程式碼清單17.12 PCM 裝置“解構函式” 1 static void xxxchip_pcm_free(struct snd_pcm *pcm) 2 { 3 /* 從pcm 例項得到chip */ 4 struct xxxchip *chip = snd_pcm_chip(pcm); 5 /* 釋放自定義用途的記憶體 */ 6 kfree(chip->xxx_private_pcm_data); 7 ... 8 } 9 10 static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip) 11 { 12 struct snd_pcm *pcm; 13 ... 14 /* 分配自定義用途的記憶體 */ 15 chip->xxx_private_pcm_data = kmalloc(...); 16 pcm->private_data = chip; 17 /* 設定“解構函式” */ 18 pcm->private_free = xxxchip_pcm_free; 19 ... 20 } 上述程式碼第4 行的snd_pcm_chip()從PCM 例項指標獲得xxxchip 指標,實際上它就是返回第16 行給PCM 例項賦予的xxxchip 指標。 6、PCM 資訊執行時指標 當PCM 子流被開啟後,PCM 執行時例項(定義為結構體snd_pcm_runtime,如程式碼清單17.13)將被分配給 這個子流,這個指標通過substream->runtime 獲得。執行時指標包含各種各樣的資訊:hw_params 及 sw_params 配置的拷貝、緩衝區指標、mmap 記錄、自旋鎖等,幾乎要控制PCM 的所有資訊均能從中取得。 程式碼清單17.13 snd_pcm_runtime 結構體 1 struct snd_pcm_runtime 2 { 3 /* 狀態 */ 4 struct snd_pcm_substream *trigger_master; 5 snd_timestamp_t trigger_tstamp; /* 觸發時間戳 */ 6 int overrange; 7 snd_pcm_uframes_t avail_max; 8 snd_pcm_uframes_t hw_ptr_base; /* 緩衝區復位時的位置 */ 9 snd_pcm_uframes_t hw_ptr_interrupt; /* 中斷時的位置*/ 10 /* 硬體引數 */ 11 snd_pcm_access_t access; /* 存取模式 */ 12 snd_pcm_format_t format; /* SNDRV_PCM_FORMAT_* */ 13 snd_pcm_subformat_t subformat; /* 子格式 */ 14 unsigned int rate; /* rate in Hz */ 15 unsigned int channels; /* 通道 */ 16 snd_pcm_uframes_t period_size; /* 週期大小 */ 17 unsigned int periods; /* 週期數 */ 18 snd_pcm_uframes_t buffer_size; /* 緩衝區大小 */ 19 unsigned int tick_time; /* tick time */ 20 snd_pcm_uframes_t min_align; /* 格式對應的最小對齊*/ 21 size_t byte_align; 22 unsigned int frame_bits; 23 unsigned int sample_bits; 24 unsigned int info; 25 unsigned int rate_num; 26 unsigned int rate_den; 27 /* 軟體引數 */ 28 struct timespec tstamp_mode; /* mmap 時間戳被更新*/ 29 unsigned int period_step; 30 unsigned int sleep_min; /* 睡眠的最小節拍 */ 31 snd_pcm_uframes_t xfer_align; 32 snd_pcm_uframes_t start_threshold; 33 snd_pcm_uframes_t stop_threshold; 34 snd_pcm_uframes_t silence_threshold; /* Silence 填充閾值 */ 35 snd_pcm_uframes_t silence_size; /* Silence 填充大小 */ 36 snd_pcm_uframes_t boundary; 37 snd_pcm_uframes_t silenced_start; 38 snd_pcm_uframes_t silenced_size; 39 snd_pcm_sync_id_t sync; /* 硬體同步ID */ 40 /* mmap */ 41 volatile struct snd_pcm_mmap_status *status; 42 volatile struct snd_pcm_mmap_control *control; 43 atomic_t mmap_count; 44 /* 鎖/排程 */ 45 spinlock_t lock; 46 wait_queue_head_t sleep; 47 struct timer_list tick_timer; 48 struct fasync_struct *fasync; 49 /* 私有段 */ 50 void *private_data; 51 void(*private_free)(struct snd_pcm_runtime *runtime); 52 /* 硬體描述 */ 53 struct snd_pcm_hardware hw; 54 struct snd_pcm_hw_constraints hw_constraints; 55 /* 中斷回撥函式 */ 56 void(*transfer_ack_begin)(struct snd_pcm_substream*substream); 57 void(*transfer_ack_end)(struct snd_pcm_substream *substream); 58 /* 定時器 */ 59 unsigned int timer_resolution; /* timer resolution */ 60 /* DMA */ 61 unsigned char *dma_area; /* DMA 區域*/ 62 dma_addr_t dma_addr; /* 匯流排實體地址*/ 64 size_t dma_bytes; /* DMA 區域大小 */ 65 struct snd_dma_buffer *dma_buffer_p; /* 被分配的緩衝區 */ 66 #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) 67 /* OSS 資訊 */ 68 struct snd_pcm_oss_runtime oss; 69 #endif 70 }; snd_pcm_runtime 中的大多數記錄對被音效卡驅動操作集中的函式是隻讀的,僅僅PCM 中間層可更新或修改 這些資訊,但是硬體描述、中斷回撥函式、DMA 緩衝區資訊和私有資料是例外的。 下面解釋snd_pcm_runtime 結構體中的幾個重要成員: • 硬體描述 硬體描述(snd_pcm_hardware 結構體)包含了基本硬體配置的定義,需要在open()函式中賦值。runtime 例項儲存的是硬體描述的拷貝而非指標,這意味著在open()函式中可以修改被拷貝的描述(runtime->hw), 例如: struct snd_pcm_runtime *runtime = substream->runtime; ... runtime->hw = snd_xxchip_playback_hw; /* “大眾”硬體描述 */ /* 特定的硬體描述 */ if (chip->model == VERY_OLD_ONE) runtime->hw.channels_max = 1; snd_pcm_hardware 結構體的定義如程式碼清單17.14。 程式碼清單17.14 snd_pcm_hardware 結構體 1 struct snd_pcm_hardware 2 { 3 unsigned int info; /* SNDRV_PCM_INFO_* / 4 u64 formats; /* SNDRV_PCM_FMTBIT_* */ 5 unsigned int rates; /* SNDRV_PCM_RATE_* */ 6 unsigned int rate_min; /* 最小取樣率 */ 7 unsigned int rate_max; /* 最大采樣率 */ 8 unsigned int channels_min; /* 最小的通道數 */ 9 unsigned int channels_max; /* 最大的通道數 */ 10 size_t buffer_bytes_max; /* 最大緩衝區大小 */ 11 size_t period_bytes_min; /* 最小週期大小 */ 12 size_t period_bytes_max; /* 最大奏曲大小 */ 13 unsigned int periods_min; /* 最小週期數 */ 14 unsigned int periods_max; /* 最大週期數 */ 15 size_t fifo_size; /* FIFO 位元組數 */ 16 }; snd_pcm_hardware 結構體中的info 欄位標識PCM 裝置的型別和能力,形式為SNDRV_PCM_INFO_XXX。info 欄位至少需要定義是否支援mmap,當支援時,應設定SNDRV_PCM_INFO_MMAP 標誌;當硬體支援interleaved 或non-interleaved 格式,應設定SNDRV_PCM_INFO_INTERLEAVED 或SNDRV_PCM_INFO_NONINTERLEAVED 標誌, 如果都支援,則二者都可設定;MMAP_VALID 和BLOCK_TRANSFER 標誌針對OSS mmap,只有mmap 被真正支援 時,才可設定MMAP_VALID;SNDRV_PCM_INFO_PAUSE 意味著裝置可支援暫停操作,而SNDRV_PCM_INFO_RESUME 意味著裝置可支援掛起/恢復操作;當PCM 子流能被同步,如同步放音和錄音流的start/stop,可設定 SNDRV_PCM_INFO_SYNC_START 標誌。 formats 包含PCM 裝置支援的格式,形式為SNDRV_PCM_FMTBIT_XXX,如果裝置支援多種模式,應將各種模 式標誌進行“或”操作。 rates 包含了PCM 裝置支援的取樣率,形式如SNDRV_PCM_RATE_XXX,如果支援連續的取樣率,則傳遞 CONTINUOUS。 rate_min 和rate_max 分別定義了最大和最小的取樣率,注意要與rates 欄位相符。 channel_min 和channel_max 定義了最大和最小的通道數量。 buffer_bytes_max 定義最大的緩衝區大小,注意沒有buffer_bytes_min 欄位,這是因為它可以通過最小 的週期大小和最小的週期數量計算出來。 period 資訊與OSS 中的fragment 對應,定義了PCM 中斷產生的週期。更小的週期大小意味著更多的中斷, 在錄音時,週期大小定義了輸入延遲,在放音時,整個緩衝區大小對應著輸出延遲。 PCM 可被應用程式通過alsa-lib 傳送hw_params 來配置,配置資訊將儲存在執行時例項中。對緩衝區和周 期大小的配置以幀形式儲存,而frames_to_bytes()和 bytes_to_frames()可完成幀和位元組的轉換,如: period_bytes = frames_to_bytes(runtime, runtime->period_size); • DMA 緩衝區資訊 包含dma_area(邏輯地址)、dma_addr(實體地址)、dma_bytes(緩衝區大小)和dma_private(被ALSA DMA 分配器使用)。可以由snd_pcm_lib_malloc_pages()實現,ALSA 中間層會設定DMA 緩衝區資訊的相關 欄位,這種情況下,驅動中不能再寫這些資訊,只能讀取。也就是說,如果使用標準的緩衝區分配函式 snd_pcm_lib_malloc_pages()分配緩衝區,則我們不需要自己維護DMA 緩衝區資訊。如果緩衝區由自己分 配,則需在hw_params()函式中管理緩衝區資訊,至少需管理dma_bytes 和dma_addr,如果支援mmap,則 必須管理dma_area,對dma_private 的管理視情況而定。 • 執行狀態 通過runtime->status 可以獲得執行狀態,它是snd_pcm_mmap_status 結構體的指標,例如,通過 runtime->status->hw_ptr 可以獲得目前的DMA 硬體指標。此外,通過runtime->control 可以獲得DMA 應 用指標,它指向snd_pcm_mmap_control 結構體指標。 • 私有資料 驅動中可以為子流分配一段記憶體並賦值給runtime->private_data,注意不要與pcm->private_data 混淆, 後者一般指向xxxchip,而前者是在PCM 裝置的open()函式中分配的動態資料,如: static int snd_xxx_open(struct snd_pcm_substream *substream) { struct xxx_pcm_data *data; .... data = kmalloc(sizeof(*data), GFP_KERNEL); substream->runtime->private_data = data; //賦值runtime->private_data .... } • 中斷回撥函式: transfer_ack_begin()和transfer_ack_end()函式分別在snd_pcm_period_elapsed()的開始和結束時被 呼叫。 根據以上分析,程式碼清單17.15 給出了一個完整的PCM 裝置介面模板。 程式碼清單17.15 PCM 裝置介面模板 1 #include <sound/pcm.h> 2 .... 3 /* 放音裝置硬體定義 */ 4 static struct snd_pcm_hardware snd_xxxchip_playback_hw = 5 { 6 .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | 7 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), 8 .formats = SNDRV_PCM_FMTBIT_S16_LE, 9 .rates = SNDRV_PCM_RATE_8000_48000, 10 .rate_min = 8000, 11 .rate_max = 48000, 12 .channels_min = 2, 13 .channels_max = 2, 14 .buffer_bytes_max = 32768, 15 .period_bytes_min = 4096, 16 .period_bytes_max = 32768, 17 .periods_min = 1, 18 .periods_max = 1024, 19 }; 20 21 /* 錄音裝置硬體定義 */ 22 static struct snd_pcm_hardware snd_xxxchip_capture_hw = 23 { 24 .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | 25 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), 26 .formats = SNDRV_PCM_FMTBIT_S16_LE, 27 .rates = SNDRV_PCM_RATE_8000_48000, 28 .rate_min = 8000, 29 .rate_max = 48000, 30 .channels_min = 2, 31 .channels_max = 2, 32 .buffer_bytes_max = 32768, 33 .period_bytes_min = 4096, 34 .period_bytes_max = 32768, 35 .periods_min = 1, 36 .periods_max = 1024, 37 }; 38 39 /* 放音:開啟函式 */ 40 static int snd_xxxchip_playback_open(struct snd_pcm_substream*substream) 41 { 42 struct xxxchip *chip = snd_pcm_substream_chip(substream); 43 struct snd_pcm_runtime *runtime = substream->runtime; 44 runtime->hw = snd_xxxchip_playback_hw; 45 ... // 硬體初始化程式碼 46 return 0; 47 } 48 49 /* 放音:關閉函式 */ 50 static int snd_xxxchip_playback_close(struct snd_pcm_substream*substream) 51 { 52 struct xxxchip *chip = snd_pcm_substream_chip(substream); 53 // 硬體相關的程式碼 54 return 0; 55 } 56 57 /* 錄音:開啟函式 */ 58 static int snd_xxxchip_capture_open(struct snd_pcm_substream*substream) 59 { 60 struct xxxchip *chip = snd_pcm_substream_chip(substream); 61 struct snd_pcm_runtime *runtime = substream->runtime; 62 runtime->hw = snd_xxxchip_capture_hw; 63 ... // 硬體初始化程式碼 64 return 0; 65 } 66 67 /* 錄音:關閉函式 */ 68 static int snd_xxxchip_capture_close(struct snd_pcm_substream*substream) 69 { 70 struct xxxchip *chip = snd_pcm_substream_chip(substream); 71 ... // 硬體相關的程式碼 72 return 0; 73 } 74 /* hw_params 函式 */ 75 static int snd_xxxchip_pcm_hw_params(struct snd_pcm_substream*substream, struct 76 snd_pcm_hw_params *hw_params) 77 { 78 return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); 79 } 80 /* hw_free 函式 */ 81 static int snd_xxxchip_pcm_hw_free(struct snd_pcm_substream*substream) 82 { 83 return snd_pcm_lib_free_pages(substream); 84 } 85 /* prepare 函式 */ 86 static int snd_xxxchip_pcm_prepare(struct snd_pcm_substream*substream) 87 { 88 struct xxxchip *chip = snd_pcm_substream_chip(substream); 89 struct snd_pcm_runtime *runtime = substream->runtime; 90 /* 根據目前的配置資訊設定硬體 91 * 例如: 92 */ 93 xxxchip_set_sample_format(chip, runtime->format); 94 xxxchip_set_sample_rate(chip, runtime->rate); 95 xxxchip_set_channels(chip, runtime->channels); 96 xxxchip_set_dma_setup(chip, runtime->dma_addr, chip->buffer_size, chip 97 ->period_size); 98 return 0; 99 } 100 /* trigger 函式 */ 101 static int snd_xxxchip_pcm_trigger(struct snd_pcm_substream*substream, int cmd) 102 { 103 switch (cmd) 104 { 105 case SNDRV_PCM_TRIGGER_START: 106 // do something to start the PCM engine 107 break; 108 case SNDRV_PCM_TRIGGER_STOP: 109 // do something to stop the PCM engine 110 break; 111 default: 112 return - EINVAL; 113 } 114 } 115 116 /* pointer 函式 */ 117 static snd_pcm_uframes_t snd_xxxchip_pcm_pointer(struct snd_pcm_substream 118 *substream) 119 { 120 struct xxxchip *chip = snd_pcm_substream_chip(substream); 121 unsigned int current_ptr; 122 /*獲得當前的硬體指標*/ 123 current_ptr = xxxchip_get_hw_pointer(chip); 124 return current_ptr; 125 } 126 /* 放音裝置操作集 */ 127 static struct snd_pcm_ops snd_xxxchip_playback_ops = 128 { 129 .open = snd_xxxchip_playback_open, 130 .close = snd_xxxchip_playback_close, 131 .ioctl = snd_pcm_lib_ioctl, 132 .hw_params = snd_xxxchip_pcm_hw_params, 133 .hw_free = snd_xxxchip_pcm_hw_free, 134 .prepare = snd_xxxchip_pcm_prepare, 135 .trigger = snd_xxxchip_pcm_trigger, 136 .pointer = snd_xxxchip_pcm_pointer, 137 }; 138 /* 錄音裝置操作集 */ 139 static struct snd_pcm_ops snd_xxxchip_capture_ops = 140 { 141 .open = snd_xxxchip_capture_open, 142 .close = snd_xxxchip_capture_close, 143 .ioctl = snd_pcm_lib_ioctl, 144 .hw_params = snd_xxxchip_pcm_hw_params, 145 .hw_free = snd_xxxchip_pcm_hw_free, 146 .prepare = snd_xxxchip_pcm_prepare, 147 .trigger = snd_xxxchip_pcm_trigger, 148 .pointer = snd_xxxchip_pcm_pointer, 149 }; 150 151 /* 建立1 個PCM 裝置 */ 152 static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip) 153 { 154 struct snd_pcm *pcm; 155 int err; 156 if ((err = snd_pcm_new(chip->card, "xxx Chip", 0, 1, 1, &pcm)) < 0) 157 return err; 158 pcm->private_data = chip; 159 strcpy(pcm->name, "xxx Chip"); 160 chip->pcm = pcm; 161 /* 設定操作集 */ 162 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_xxxchip_playback_ops); 163 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_xxxchip_capture_ops); 164 /* 分配緩衝區 */ 165 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, 166 snd_dma_pci_data(chip - > pci), 64 *1024, 64 *1024); 167 return 0; 168 } 17.4.3 控制介面 1、control 控制介面對於許多開關(switch)和調節器(slider)而言應用相當廣泛,它能從使用者空間被存取。control 的最主要用途是mixer,所有的mixer 元素基於control 核心API 實現,在ALSA 中,control 用snd_kcontrol 結構體描述。 ALSA 有一個定義很好的AC97 控制模組,對於僅支援AC97 的晶片而言,不必實現本節的內容。 建立1 個新的control 至少需要實現snd_kcontrol_new 中的info()、get()和put()這3 個成員函式, snd_kcontrol_new 結構體的定義如程式碼清單17.16。 程式碼清單17.16 snd_kcontrol_new 結構體 1 struct snd_kcontrol_new 2 { 3 snd_ctl_elem_iface_t iface; /*介面ID,SNDRV_CTL_ELEM_IFACE_XXX */ 4 unsigned int device; /* 裝置號 */ 5 unsigned int subdevice; /* 子流(子裝置)號 */ 6 unsigned char *name; /* 名稱(ASCII 格式) */ 7 unsigned int index; /* 索引 */ 8 unsigned int access; /* 訪問許可權 */ 9 unsigned int count; /* 享用元素的數量 */ 10 snd_kcontrol_info_t *info; 11 snd_kcontrol_get_t *get; 12 snd_kcontrol_put_t *put; 13 unsigned long private_value; 14 }; iface 欄位定義了control 的型別,形式為SNDRV_CTL_ELEM_IFACE_XXX,通常是MIXER,對於不屬於mixer 的全域性控制,使用CARD。如果關聯於某類裝置,則使用HWDEP、 PCM、RAWMIDI、TIMER 或SEQUENCER。 name 是名稱標識字串,control 的名稱非常重要,因為control 的作用由名稱來區分。對於名稱相同的 control,則使用index 區分。name 定義的標準是“SOURCE DIRECTION FUNCTION”即“源 方向 功能”, SOURCE 定義了control 的源,如“Master”、“PCM”、“CD”和“Line”,方向則為“Playback”、 “Capture”、“Bypass Playback”或“Bypass Capture”,如果方向省略,意味著playback 和capture 雙向,第3 個引數可以是“Switch”、“Volume”和“Route”等。 “SOURCE DIRECTION FUNCTION”格式的名稱例子如Master Capture Switch、PCM Playback Volume。 下面幾種control 的命名不採用“SOURCE DIRECTION FUNCTION”格式,屬於例外: • 全域性控制 “Capture Source”、 “Capture Switch”和“Capture Volume”用於全域性錄音源、輸入開關和錄音音量 控制;“Playback Switch”、“Playback Volume”用於全域性輸出開關和音量控制。 • 音調控制 音調控制名稱的形式為“Tone Control – XXX”,例如“Tone Control – Switch”、“Tone Control – Bas”和“Tone Control – Center”。 • 3D 控制 3D 控制名稱的形式為“3D Control – XXX”,例如“3D Control – Switch”、“3D Control – Center” 和“3D Control – Space”。 • 麥克風增益(Mic boost) 麥克風增益被設定為“Mic Boost”或“Mic Boost (6dB)”。 snd_kcontrol_new 結構體的access 欄位是訪問控制權限,形式如SNDRV_CTL_ELEM_ACCESS_XXX。 SNDRV_CTL_ELEM_ACCESS_READ 意味著只讀,這時put()函式不必實現;SNDRV_CTL_ELEM_ACCESS_WRITE 意 味著只寫,這時get()函式不必實現。若control 值頻繁變化,則需定義VOLATILE 標誌。當control 處於 非啟用狀態時,應設定INACTIVE 標誌。 private_value 欄位包含1 個長整型值,可以通過它給info()、get()和put()函式傳遞引數。 2、info()函式 snd_kcontrol_new 結構體中的info()函式用於獲得該control 的詳細資訊,該函式必須填充傳遞給它的第 2 個引數snd_ctl_elem_info 結構體,info()函式的形式如下: static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); snd_ctl_elem_info 結構體的定義如程式碼清單17.17。 程式碼清單17.17 snd_ctl_elem_info 結構體 1 struct snd_ctl_elem_info 2 { 3 struct snd_ctl_elem_id id; /* W: 元素ID */ 4 snd_ctl_elem_type_t type; /* R: 值型別 - SNDRV_CTL_ELEM_TYPE_* */ 5 unsigned int access; /* R: 值訪問許可權(位掩碼) - SNDRV_CTL_ELEM_ACCESS_* */ 6 unsigned int count; /* 值的計數 */ 7 pid_t owner; /* 該control 的擁有者PID */ 8 union 9 { 10 struct 11 { 12 long min; /* R: 最小值 */ 13 long max; /* R: 最大值 */ 14 long step; /* R: 值步進 (0 可變的) */ 15 } integer; 16 struct 17 { 18 long long min; /* R: 最小值 */ 19 long long max; /* R: 最大值 */ 20 long long step; /* R: 值步進 (0 可變的) */ 21 } integer64; 22 struct 23 { 24 unsigned int items; /* R: 專案數 */ 25 unsigned int item; /* W: 專案號 */ 26 char name[64]; /* R: 值名稱 */ 27 } enumerated; /* 列舉 */ 28 unsigned char reserved[128]; 29 } 30 value; 31 union 32 { 33 unsigned short d[4]; 34 unsigned short *d_ptr; 35 } dimen; 36 unsigned char reserved[64-4 * sizeof(unsigned short)]; 37 }; snd_ctl_elem_info 結構體的type 欄位定義了control 的型別,包括BOOLEAN、INTEGER、ENUMERATED、BYTES、 IEC958 和INTEGER64。count 欄位定義了這個control 中包含的元素的數量,例如1 個立體聲音量control 的count = 2。value 是1 個聯合體,其所儲存的值的具體型別依賴於type。程式碼清單17.18 給出了1 個 info()函式填充snd_ctl_elem_info 結構體的範例。 程式碼清單17.18 snd_ctl_elem_info 結構體中info()函式範例 1 static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct 2 snd_ctl_elem_info *uinfo) 3 { 4 uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;//型別為BOOLEAN 5 uinfo->count = 1;//數量為1 6 uinfo->value.integer.min = 0;//最小值為0 7 uinfo->value.integer.max = 1;//最大值為1 8 return 0; 9 } 列舉型別和其它型別略有不同,對列舉型別,應為目前專案索引設定名稱字串,如程式碼清單17.19。 程式碼清單17.19 填充snd_ctl_elem_info 結構體中列舉型別值 1 static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct 2 snd_ctl_elem_info *uinfo) 3 { 4 //值名稱字串 5 static char *texts[4] = 6 { 7 "First", "Second", "Third", "Fourth" 8 }; 9 uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;//列舉型別 10 uinfo->count = 1;//數量為1 11 uinfo->value.enumerated.items = 4;//專案數量為1 12 //超過3 的專案號改為3 13 if (uinfo->value.enumerated.item > 3) 14 uinfo->value.enumerated.item = 3; 15 //為目前專案索引拷貝名稱字串 16 strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); 17 return 0; 18 } 3、get()函式 get()函式用於得到control 的目前值並返回使用者空間,程式碼清單17.20 給出了get()函式的範例。 程式碼清單17.20 snd_ctl_elem_info 結構體中get()函式範例 1 static int snd_xxxctl_get(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 //從xxxchip 獲得值並寫入snd_ctl_elem_value 7 ucontrol->value.integer.value[0] = get_some_value(chip); 8 return 0; 9 } get()函式的第2 個引數的型別為snd_ctl_elem_value,其定義如程式碼清單10.21。snd_ctl_elem_value 結構體的內部也包含1 個由integer、integer64、enumerated 等組成的值聯合體,它的具體型別依賴於 control 的型別和info()函式。 程式碼清單17.21 snd_ctl_elem_value 結構體 1 struct snd_ctl_elem_value 2 { 3 struct snd_ctl_elem_id id; /* W: 元素ID */ 4 unsigned int indirect: 1; /* W: 使用間接指標(xxx_ptr 成員) */ 5 //值聯合體 6 union 7 { 8 union 9 { 10 long value[128]; 11 long *value_ptr; 12 } integer; 13 union 14 { 15 long long value[64]; 16 long long *value_ptr; 17 } integer64; 18 union 19 { 20 unsigned int item[128]; 21 unsigned int *item_ptr; 22 } enumerated; 23 union 24 { 25 unsigned char data[512]; 26 unsigned char *data_ptr; 27 } bytes; 28 struct snd_aes_iec958 iec958; 29 } 30 value; /* 只讀 */ 31 struct timespec tstamp; 32 unsigned char reserved[128-sizeof(struct timespec)]; 33 }; 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_kc ontrol 新增到對應的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 結構體,其定義