標籤:des style http io os 使用 ar for strong
轉自: http://www.ibm.com/developerworks/cn/linux/l-audio/
Linux音頻編程指南
雖然目前Linux的優勢主要體現在網路服務方面,但事實上同樣也有著非常豐富的媒體功能,本文就是以多媒體應用中最基本的聲音為對象,介紹如何在Linux平台下開發實際的音頻應用程式,同時還給出了一些常用的音頻編程架構。
1 評論:
肖文鵬 ([email protected]), 自由軟體愛好者
2004 年 2 月 01 日
一、數字音頻
音頻訊號是一種連續變化的類比訊號,但電腦只能處理和記錄二進位的數字訊號,由自然音源得到的音頻訊號必須經過一定的變換,成為數字音頻訊號之後,才能送到電腦中作進一步的處理。
數 字音頻系統通過將聲波的波型轉換成一系列位元據,來實現對原始聲音的重現,實現這一步驟的裝置常被稱為模/數轉換器(A/D)。A/D轉換器以每秒鐘 上萬次的速率對聲波進行採樣,每個採樣點都記錄下了原始類比聲波在某一時刻的狀態,通常稱之為樣本(sample),而每一秒鐘所採樣的數目則稱為採樣頻 率,通過將一串聯續的樣本串連起來,就可以在電腦中描述一段聲音了。對於採樣過程中的每一個樣本來說,數字音頻系統會分配一定儲存位來記錄聲波的振幅, 一般稱之為採樣分辯率或者採樣精度,採樣精度越高,聲音還原時就會越細膩。
數字音頻涉及到的概念非常多,對於在Linux下進行音頻編程的 程式員來說,最重要的是理解聲音數字化的兩個關鍵步驟:採樣和量化。採樣就是每隔一定時間就讀一次聲音訊號的幅度,而量化則是將採樣得到的聲音訊號幅度轉 換為數字值,從本質上講,採樣是時間上的數字化,而量化則是幅度上的數字化。下面介紹幾個在進行音頻編程時經常需要用到的技術指標:
- 採樣頻率
採樣頻率是指將類比聲音波形進行數字化時,每秒鐘抽取聲波幅度樣本的次數。採樣頻率的選擇應該遵循奈奎斯特(Harry Nyquist)採樣理論:如果對某一類比訊號進行採樣,則採樣後可還原的最高訊號頻率只有採樣頻率的一半,或者說只要採樣頻率高於輸入訊號最高頻率的兩倍,就能從採樣訊號系列重構原始訊號。正常人聽覺的頻率範圍大約在20Hz~20kHz之間,根據奈奎斯特採樣理論,為了保證聲音不失真,採樣頻率應該在40kHz左右。常用的音頻採樣頻率有8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等,如果採用更高的採樣頻率,還可以達到DVD的音質。
- 量化位元
量化位元是對類比音頻訊號的幅度進行數字化,它決定了類比訊號數字化以後的動態範圍,常用的有8位、12位和16位。量化位越高,訊號的動態範圍越大,數字化後的音頻訊號就越可能接近原始訊號,但所需要的存貯空間也越大。
- 聲道數
聲道數是反映音頻數字化品質的另一個重要因素,它有單聲道和雙聲道之分。雙聲道又稱為立體聲,在硬體中有兩條線路,音質和音色都要優於單聲道,但數字化後佔據的儲存空間的大小要比單聲道多一倍。
回頁首
二、音效卡驅動
出於對安全性方面的考慮,Linux下的應用程式無法直接對音效卡這類硬體裝置進行操作,而是必須通過核心提供的驅動程式才能完成。在Linux上進行音頻編程的本質就是要藉助於驅動程式,來完成對音效卡的各種操作。
對硬體的控制涉及到寄存器中各個位元位的操作,通常這是與裝置直接相關並且對時序的要求非常嚴格,如果這些工作都交由應用程式員來負責,那麼對音效卡的編程將變得異常複雜而困難起來,驅動程式的作用正是要屏蔽硬體的這些底層細節,從而簡化應用程式的編寫。目前Linux下常用的音效卡驅動程式主要有兩種:OSS和ALSA。
最早出現在Linux上的音頻編程介面是OSS(Open Sound System),它由一套完整的核心驅動程式模組組成,可以為絕大多數音效卡提供統一的編程介面。OSS出現的曆史相對較長,這些核心模組中的一部分(OSS/Free)是與Linux核心源碼共同免費發布的,另外一些則以二進位的形式由4Front Technologies公司提供。由於得到了商業公司的鼎力支援,OSS已經成為在Linux下進行音頻編程的事實標準,支援OSS的應用程式能夠在絕大多數音效卡上工作良好。
雖然OSS已經非常成熟,但它畢竟是一個沒有完全開放原始碼的商業產品,ALSA(Advanced Linux Sound Architecture)恰好彌補了這一空白,它是在Linux下進行音頻編程時另一個可供選擇的音效卡驅動程式。ALSA除了像OSS那樣提供了一組核心驅動程式模組之外,還專門為簡化應用程式的編寫提供了相應的函數庫,與OSS提供的基於ioctl的原始編程介面相比,ALSA函數庫使用起來要更加方便一些。ALSA的主要特點有:
- 支援多種音效卡裝置
- 模組化的核心驅動程式
- 支援SMP和多線程
- 提供應用開發函數庫
- 相容OSS應用程式
ALSA和OSS最大的不同之處在於ALSA是由志願者維護的自由項目,而OSS則是由公司提供的商業產品,因此在對硬體的適應程度上OSS要優於ALSA,它能夠支援的音效卡種類更多。ALSA雖然不及OSS運用得廣泛,但卻具有更加友好的編程介面,並且完全相容於OSS,對應用程式員來講無疑是一個更佳的選擇。
回頁首
三、編程介面
如何對各種音訊裝置進行操作是在Linux上進行音頻編程的關鍵,通過核心提供的一組系統調用,應用程式能夠訪問音效卡驅動程式提供的各種音訊裝置介面,這是在Linux下進行音頻編程最簡單也是最直接的方法。
3.1 訪問音訊裝置
無論是OSS還是ALSA,都是以核心驅動程式的形式運行在Linux核心空間中的,應用程式要想訪問音效卡這一硬體裝置,必須藉助於Linux核心所提供的系統調用(system call)。從程式員的角度來說,對音效卡的操作在很大程度上等同於對磁碟檔案的操作:首先使用open系統調用建立起與硬體間的聯絡,此時返回的檔案描述符將作為隨後操作的標識;接著使用read系統調用從裝置接收資料,或者使用write系統調用向裝置寫入資料,而其它所有不符合讀/寫這一基本模式的操作都可以由ioctl系統調用來完成;最後,使用close系統調用告訴Linux核心不會再對該裝置做進一步的處理。
- open系統調用
系統調用open可以獲得對音效卡的訪問權,同時還能為隨後的系統調用做好準備,其函數原型如下所示:int open(const char *pathname, int flags, int mode);
參數pathname是將要被開啟的裝置檔案的名稱,對於音效卡來講一般是/dev/dsp。參數flags用來指明應該以什麼方式開啟裝置檔案,它可以是O_RDONLY、O_WRONLY或者O_RDWR,分別表示以唯讀、唯寫或者讀寫的方式開啟裝置檔案;參數mode通常是可選的,它只有在指定的裝置檔案不存在時才會用到,指明新建立的檔案應該具有怎樣的許可權。
如果open系統調用能夠成功完成,它將返回一個正整數作為檔案標識符,在隨後的系統調用中需要用到該標識符。如果open系統調用失敗,它將返回-1,同時還會設定全域變數errno,指明是什麼原因導致了錯誤的發生。
- read系統調用
系統調用read用來從音效卡讀取資料,其函數原型如下所示:int read(int fd, char *buf, size_t count);
參數fd是裝置檔案的標識符,它是通過之前的open系統調用獲得的;參數buf是指向緩衝區的字元指標,它用來儲存從音效卡獲得的資料;參數count則用來限定從音效卡獲得的最大位元組數。如果read系統調用成功完成,它將返回從音效卡實際讀取的位元組數,通常情況會比count的值要小一些;如果read系統調用失敗,它將返回-1,同時還會設定全域變數errno,來指明是什麼原因導致了錯誤的發生。
- write系統調用
系統調用write用來向音效卡寫入資料,其函數原型如下所示:size_t write(int fd, const char *buf, size_t count);
系統調用write和系統調用read在很大程度是類似的,差別只在於write是向音效卡寫入資料,而read則是從音效卡讀入資料。參數fd同樣是裝置檔案的標識符,它也是通過之前的open系統調用獲得的;參數buf是指向緩衝區的字元指標,它儲存著即將向音效卡寫入的資料;參數count則用來限定向音效卡寫入的最大位元組數。
如果write系統調用成功完成,它將返迴向音效卡實際寫入的位元組數;如果read系統調用失敗,它將返回-1,同時還會設定全域變數errno,來指明是什麼原因導致了錯誤的發生。無論是read還是write,一旦調用之後Linux核心就會阻塞當前應用程式,直到資料成功地從音效卡讀出或者寫入為止。
- ioctl系統調用
系統調用ioctl可以對音效卡進行控制,凡是對裝置檔案的操作不符合讀/寫基本模式的,都是通過ioctl來完成的,它可以影響裝置的行為,或者返回裝置的狀態,其函數原型如下所示:
int ioctl(int fd, int request, ...);
參數fd是裝置檔案的標識符,它是在裝置開啟時獲得的;如果裝置比較複雜,那麼對它的控制請求相應地也會有很多種,參數request的目的就是用來區分不同的控制請求;通常說來,在對裝置進行控制時還需要有其它參數,這要根據不同的控制請求才能確定,並且可能是與硬體裝置直接相關的。
- close系統調用
當應用程式使用完音效卡之後,需要用close系統調用將其關閉,以便及時釋放佔用的硬體資源,其函數原型如下所示:int close(int fd);
參數fd是裝置檔案的標識符,它是在裝置開啟時獲得的。一旦應用程式調用了close系統調用,Linux核心就會釋放與之相關的各種資源,因此建議在不需要的時候盡量及時關閉已經開啟的裝置。
3.2 音訊裝置檔案
對於Linux應用程式員來講,音頻編程介面實際上就是一組音訊裝置檔案,通過它們可以從音效卡讀取資料,或者向音效卡寫入資料,並且能夠對音效卡進行控制,設定採樣頻率和聲道數目等等。
- /dev/sndstat
裝置檔案/dev/sndstat是音效卡驅動程式提供的最簡單的介面,通常它是一個唯讀檔案,作用也僅僅只限於彙報音效卡的目前狀態。一般說來,/dev/sndstat是提供給終端使用者來檢測音效卡的,不宜用於程式當中,因為所有的資訊都可以通過ioctl系統調用來獲得。Linux提供的cat命令可以很方便地從/dev/sndstat獲得音效卡的目前狀態:[[email protected] sound]$ cat /dev/sndstat
- /dev/dsp
音效卡驅動程式提供的/dev/dsp是用於數字採樣(sampling)和數字錄音(recording)的裝置檔案,它對於Linux下的音頻編程來講非常重要:向該裝置寫資料即意味著啟用音效卡上的D/A轉換器進行放音,而向該裝置讀資料則意味著啟用音效卡上的A/D轉換器進行錄音。目前許多音效卡都提供有多個數字採樣裝置,它們在Linux下可以通過/dev/dsp1等裝置檔案進行訪問。
DSP是數位訊號處理器(Digital Signal Processor)的簡稱,它是用來進行數字訊號處理的特殊晶片,音效卡使用它來實現類比訊號和數字訊號的轉換。音效卡中的DSP裝置實際上包含兩個組成部分:在以唯讀方式開啟時,能夠使用A/D轉換器進行聲音的輸入;而在以唯寫方式開啟時,則能夠使用D/A轉換器進行聲音的輸出。嚴格說來,Linux下的應用程式要麼以唯讀方式開啟/dev/dsp輸入聲音,要麼以唯寫方式開啟/dev/dsp輸出聲音,但事實上某些音效卡驅動程式仍允許以讀寫的方式開啟/dev/dsp,以便同時進行聲音的輸入和輸出,這對於某些應用場合(如IP電話)來講是非常關鍵的。
在從DSP裝置讀取資料時,從音效卡輸入的類比訊號經過A/D轉換器變成數字採樣後的樣本(sample),儲存在音效卡驅動程式的核心緩衝區中,當應用程式通過read系統調用從音效卡讀取資料時,儲存在核心緩衝區中的數字採樣結果將被複製到應用程式所指定的使用者緩衝區中。需要指出的是,音效卡採樣頻率是由核心中的驅動程式所決定的,而不取決於應用程式從音效卡讀取資料的速度。如果應用程式讀取資料的速度過慢,以致低於音效卡的採樣頻率,那麼多餘的資料將會被丟棄;如果讀取資料的速度過快,以致高於音效卡的採樣頻率,那麼音效卡驅動程式將會阻塞那些請求資料的應用程式,直到新的資料到來為止。
在向DSP裝置寫入資料時,數字訊號會經過D/A轉換器變成類比訊號,然後產生出聲音。應用程式寫入資料的速度同樣應該與音效卡的採樣頻率相匹配,否則過慢的話會產生聲音暫停或者停頓的現象,過快的話又會被核心中的音效卡驅動程式阻塞,直到硬體有能力處理新的資料為止。與其它裝置有所不同,音效卡通常不會支援非阻塞(non-blocking)的I/O操作。
無論是從音效卡讀取資料,或是向音效卡寫入資料,事實上都具有特定的格式(format),預設為8位無符號資料、單聲道、8KHz採樣率,如果預設值無法達到要求,可以通過ioctl系統調用來改變它們。通常說來,在應用程式中開啟裝置檔案/dev/dsp之後,接下去就應該為其設定恰當的格式,然後才能從音效卡讀取或者寫入資料。
- /dev/audio
/dev/audio類似於/dev/dsp,它相容於Sun工作站上的音訊裝置,使用的是mu-law編碼方式。如果音效卡驅動程式提供了對/dev/audio的支援,那麼在Linux上就可以通過cat命令,來播放在Sun工作站上用mu-law進行編碼的音頻檔案:[[email protected] sound]$ cat audio.au > /dev/audio
由於裝置檔案/dev/audio主要出於對相容性的考慮,所以在新開發的應用程式中最好不要嘗試用它,而應該以/dev/dsp進行替代。對於應用程式來說,同一時刻只能使用/dev/audio或者/dev/dsp其中之一,因為它們是相同硬體的不同軟體介面。
- /dev/mixer
在音效卡的硬體電路中,混音器(mixer)是一個很重要的組成部分,它的作用是將多個訊號組合或者疊加在一起,對於不同的音效卡來說,其混音器的作用可能各不相同。運行在Linux核心中的音效卡驅動程式一般都會提供/dev/mixer這一裝置檔案,它是應用程式對混音器進行操作的軟體介面。混音器電路通常由兩個部分組成:輸入混音器(input mixer)和輸出混音器(output mixer)。
輸入混音器負責從多個不同的訊號源接收類比訊號,這些訊號源有時也被稱為混音通道或者混音裝置。類比訊號通過增益控制器和由軟體控制的音量大小器後,在不同的混音通道中進行層級(level)調製,然後被送到輸入混音器中進行聲音的合成。混音器上的電子開關可以控制哪些通道中有訊號與混音器相連,有些音效卡只允許串連一個混音通道作為錄音的音源,而有些音效卡則允許對混音通道做任意的串連。經過輸入混音器處理後的訊號仍然為類比訊號,它們將被送到A/D轉換器進行數字化處理。
輸出混音器的工作原理與輸入混音器類似,同樣也有多個訊號源與混音器相連,並且事先都經過了感度調整。當輸出混音器對所有的類比訊號進行了混合之後,通常還會有一個總控感度調整器來控制輸出聲音的大小,此外還有一些音調控制器來調節輸出聲音的音調。經過輸出混音器處理後的訊號也是類比訊號,它們最終會被送給喇叭或者其它的類比輸出裝置。對混音器的編程包括如何設定增益控制器的層級,以及怎樣在不同的音源間進行切換,這些操作通常來講是不連續的,而且不會像錄音或者放音那樣需要佔用大量的電腦資源。由於混音器的操作不符合典型的讀/寫操作模式,因此除了open和close兩個系統調用之外,大部分的操作都是通過ioctl系統調用來完成的。與/dev/dsp不同,/dev/mixer允許多個應用程式同時訪問,並且混音器的設定值會一直保持到對應的裝置檔案被關閉為止。
為了簡化應用程式的設計,Linux上的音效卡驅動程式大多都支援將混音器的ioctl操作直接應用到聲音裝置上,也就是說如果已經開啟了/dev/dsp,那麼就不用再開啟/dev/mixer來對混音器進行操作,而是可以直接用開啟/dev/dsp時得到的檔案標識符來設定混音器。
- /dev/sequencer
目前大多數音效卡驅動程式還會提供/dev/sequencer這一裝置檔案,用來對音效卡內建的波表合成器進行操作,或者對MIDI匯流排上的樂器進行控制,一般只用於電腦音樂軟體中。
回頁首
四、應用程式框架
在Linux下進行音頻編程時,重點在於如何正確地操作音效卡驅動程式所提供的各種裝置檔案,由於涉及到的概念和因素比較多,所以遵循一個通用的架構無疑將有助於簡化應用程式的設計。
4.1 DSP編程
對音效卡進行編程時首先要做的是開啟與之對應的硬體裝置,這是藉助於open系統調用來完成的,並且一般情況下使用的是/dev/dsp檔案。採用何種模式對音效卡進行操作也必須在開啟裝置時指定,對於不支援全雙工系統的音效卡來說,應該使用唯讀或者唯寫的方式開啟,只有那些支援全雙工系統的音效卡,才能以讀寫的方式開啟,並且還要依賴於驅動程式的具體實現。Linux允許應用程式多次開啟或者關閉與音效卡對應的裝置檔案,從而能夠很方便地在放音狀態和錄音狀態之間進行切換,建議在進行音頻編程時只要有可能就盡量使用唯讀或者唯寫的方式開啟裝置檔案,因為這樣不僅能夠充分利用音效卡的硬體資源,而且還有利於驅動程式的最佳化。下面的代碼示範了如何以唯寫方式開啟音效卡進行放音(playback)操作:
int handle = open("/dev/dsp", O_WRONLY);if (handle == -1) {perror("open /dev/dsp");return -1;}
運行在Linux核心中的音效卡驅動程式專門維護了一個緩衝區,其大小會影響到放音和錄音時的效果,使用ioctl系統調 用可以對它的尺寸進行恰當的設定。調節驅動程式中緩衝區大小的操作不是必須的,如果沒有特殊的要求,一般採用預設的緩衝區大小也就可以了。但需要注意的 是,緩衝區大小的設定通常應緊跟在裝置檔案開啟之後,這是因為對音效卡的其它操作有可能會導致驅動程式無法再修改其緩衝區的大小。下面的代碼示範了怎樣設定 音效卡驅動程式中的核心緩衝區的大小:
int setting = 0xnnnnssss;int result = ioctl(handle, SNDCTL_DSP_SETFRAGMENT, &setting);if (result == -1) {perror("ioctl buffer size");return -1;}// 檢查設定值的正確性
在設定緩衝區大小時,參數setting實際上由兩部分組成,其低16位標明緩衝區的尺寸,相應 的計算公式為buffer_size = 2^ssss,即若參數setting低16位的值為16,那麼相應的緩衝區的大小會被設定為65536位元組。參數setting的高16位則用來標明分 片(fragment)的最大序號,它的取值範圍從2一直到0x7FFF,其中0x7FFF表示沒有任何限制。
接下來要做的是設定音效卡工作時的聲道(channel)數目,根據硬體裝置和驅動程式的具體情況,可以將其設定為0(單聲道,mono)或者1(立體聲,stereo)。下面的代碼示範了應該怎樣設定聲道數目:
int channels = 0; // 0=mono 1=stereoint result = ioctl(handle, SNDCTL_DSP_STEREO, &channels);if ( result == -1 ) {perror("ioctl channel number");return -1;}if (channels != 0) {// 只支援立體聲}
採樣格式和採樣頻率是在進行音頻編程時需要考慮的另一個問題,音效卡支援的所有採樣格式可以在標頭檔soundcard.h中找到,而通過ioctl系統調用則可以很方便地更改當前所使用的採樣格式。下面的代碼示範了如何設定音效卡的採樣格式:
int format = AFMT_U8;int result = ioctl(handle, SNDCTL_DSP_SETFMT, &format);if ( result == -1 ) {perror("ioctl sample format");return -1;}// 檢查設定值的正確性
音效卡採樣頻率的設定也非常容易,只需在調用ioctl時將第二個參數的值設定為 SNDCTL_DSP_SPEED,同時在第三個參數中指定採樣頻率的數值就行了。對於大多數音效卡來說,其支援的採樣頻率範圍一般為5kHz到 44.1kHz或者48kHz,但並不意味著該範圍內的所有頻率都會被硬體支援,在Linux下進行音頻編程時最常用到的幾種採樣頻率是11025Hz、 16000Hz、22050Hz、32000Hz和44100Hz。下面的代碼示範了如何設定音效卡的採樣頻率:
int rate = 22050;int result = ioctl(handle, SNDCTL_DSP_SPEED, &rate);if ( result == -1 ) {perror("ioctl sample format");return -1;}// 檢查設定值的正確性
4.2 Mixer編程
音效卡上的混音器由多個混音通道組成,它們可以通過驅動程式提供的裝置檔案/dev/mixer進行編程。對混音器的操作是通過ioctl系統調用來完成的,並且所有控制命令都由SOUND_MIXER或者MIXER開頭,表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輸入 |
表1 混音器命令
對音效卡的輸入增益和輸出增益進行調節是混音器的一個主要作用,目前大部分音效卡採用的是8位或者16位的增益控制器,但作為程式 員來講並不需要關心這些,因為音效卡驅動程式會負責將它們變換成百分比的形式,也就是說無論是輸入增益還是輸出增益,其取值範圍都是從0到100。在進行混 音器編程時,可以使用SOUND_MIXER_READ宏來讀取混音通道的增益大小,例如在擷取麥克風的輸入增益時,可以使用如下的代碼:
int vol;ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);printf("Mic gain is at %d %%\n", vol);
對於只有一個混音通道的單聲道裝置來說, 返回的增益大小儲存在低位位元組中。而對於支援多個混音通道的雙聲道裝置來說,返回的增益大小實際上包括兩個部分,分別代表左、右兩個聲道的值,其中低位字 節儲存左聲道的音量,而高位位元組則儲存右聲道的音量。下面的代碼可以從傳回值中依次提取左右聲道的增益大小:
int left, right;left = vol & 0xff;right = (vol & 0xff00) >> 8;printf("Left gain is %d %%, Right gain is %d %%\n", left, right);
類似地,如果想設定混音通道的增益大小,則可以通過SOUND_MIXER_WRITE宏來實現,此時遵循的原則與擷取增益值時的原則基本相同,例如下面的語句可以用來設定麥克風的輸入增益:
vol = (right << 8) + left;ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);
在 編寫實用的音頻程式時,混音器是在涉及到相容性時需要重點考慮的一個對象,這是因為不同的音效卡所提供的混音器資源是有所區別的。音效卡驅動程式提供了多個 ioctl系統調用來獲得混音器的資訊,它們通常返回一個整型的位元遮罩(bitmask),其中每一位分別代表一個特定的混音通道,如果相應的位為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");
如果進一步還想知道其是否是一個有效錄音源,則可以使用如下語句:
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可以查詢出當前正在使用的錄音源,同一時刻能夠使用幾個錄音源是由音效卡硬體決定 的。類似地,使用SOUND_MIXER_WRITE_RECSRC可以設定音效卡當前使用的錄音源,例如下面的代碼可以將CD輸入作為音效卡的錄音源使用:
devmask = SOUND_MIXER_CD;ioctl(fd, SOUND_MIXER_WRITE_DEVMASK, &devmask);
此外,所有的混音通道都有單聲道和雙聲道的區別,如果需要知道哪些混音通道提供了對立體聲的支援,可以通過SOUND_MIXER_READ_STEREODEVS來獲得。
4.3 音頻錄放架構
下面給出一個利用音效卡上的DSP裝置進行聲音錄製和回放的基本架構,它的功能是先錄製幾秒種音頻資料,將其存放在記憶體緩衝區中,然後再進行回放,其所有的功能都是通過讀寫/dev/dsp裝置檔案來完成的:
/* * sound.c */#include <unistd.h>#include <fcntl.h>#include <sys/types.h>#include <sys/ioctl.h>#include <stdlib.h>#include <stdio.h>#include <linux/soundcard.h>#define LENGTH 3 /* 儲存秒數 */#define RATE 8000 /* 採樣頻率 */#define SIZE 8 /* 量化位元 */#define CHANNELS 1 /* 聲道數目 *//* 用於儲存數字音頻資料的記憶體緩衝區 */unsigned char buf[LENGTH*RATE*SIZE*CHANNELS/8];int main(){ int fd;/* 聲音裝置的檔案描述符 */ int arg;/* 用於ioctl調用的參數 */ int status; /* 系統調用的傳回值 */ /* 開啟聲音裝置 */ fd = open("/dev/dsp", O_RDWR); if (fd < 0) { perror("open of /dev/dsp failed"); exit(1); } /* 設定採樣時的量化位元 */ arg = SIZE; status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg); if (status == -1) perror("SOUND_PCM_WRITE_BITS ioctl failed"); if (arg != SIZE) perror("unable to set sample size"); /* 設定採樣時的聲道數目 */ arg = CHANNELS; status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg); if (status == -1) perror("SOUND_PCM_WRITE_CHANNELS ioctl failed"); if (arg != CHANNELS) perror("unable to set number of channels"); /* 設定採樣時的採樣頻率 */ arg = RATE; status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg); if (status == -1) perror("SOUND_PCM_WRITE_WRITE ioctl failed"); /* 迴圈,直到按下Control-C */ while (1) { printf("Say something:\n"); status = read(fd, buf, sizeof(buf)); /* 錄音 */ if (status != sizeof(buf)) perror("read wrong number of bytes"); printf("You said:\n"); status = write(fd, buf, sizeof(buf)); /* 回放 */ if (status != sizeof(buf)) perror("wrote wrong number of bytes"); /* 在繼續錄音前等待回放結束 */ status = ioctl(fd, SOUND_PCM_SYNC, 0); if (status == -1) perror("SOUND_PCM_SYNC ioctl failed"); }}
4.4 混音器架構
下面再給出一個對混音器進行編程的基本架構,利用它可以對各種混音通道的增益進行調節,其所有的功能都是通過讀寫/dev/mixer裝置檔案來完成的:
/* * mixer.c */#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <sys/ioctl.h>#include <fcntl.h>#include <linux/soundcard.h>/* 用來儲存所有可用混音裝置的名稱 */const char *sound_device_names[] = SOUND_DEVICE_NAMES;int fd; /* 混音裝置所對應的檔案描述符 */int devmask, stereodevs; /* 混音器資訊對應的位元影像掩碼 */char *name;/* 顯示命令的使用方法及所有可用的混音裝置 */void usage(){ int i; fprintf(stderr, "usage: %s <device> <left-gain%%> <right-gain%%>\n" " %s <device> <gain%%>\n\n" "Where <device> is one of:\n", name, name); for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++) if ((1 << i) & devmask) /* 只顯示有效混音裝置 */ fprintf(stderr, "%s ", sound_device_names[i]); fprintf(stderr, "\n"); exit(1);}int main(int argc, char *argv[]){ int left, right, level; /* 增益設定 */ int status; /* 系統調用的傳回值 */ int device; /* 選用的混音裝置 */ char *dev; /* 混音裝置的名稱 */ int i; name = argv[0]; /* 以唯讀方式開啟混音裝置 */ fd = open("/dev/mixer", O_RDONLY); if (fd == -1) { perror("unable to open /dev/mixer"); exit(1); } /* 獲得所需要的資訊 */ status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask); if (status == -1) perror("SOUND_MIXER_READ_DEVMASK ioctl failed"); status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs); if (status == -1) perror("SOUND_MIXER_READ_STEREODEVS ioctl failed"); /* 檢查使用者輸入 */ if (argc != 3 && argc != 4) usage(); /* 儲存使用者輸入的混音器名稱 */ dev = argv[1]; /* 確定即將用到的混音裝置 */ for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++) if (((1 << i) & devmask) && !strcmp(dev, sound_device_names[i])) break; if (i == SOUND_MIXER_NRDEVICES) { /* 沒有找到匹配項 */ fprintf(stderr, "%s is not a valid mixer device\n", dev); usage(); } /* 尋找到有效混音裝置 */ device = i; /* 擷取增益值 */ if (argc == 4) { /* 左、右聲道均給定 */ left = atoi(argv[2]); right = atoi(argv[3]); } else { /* 左、右聲道設為相等 */ left = atoi(argv[2]); right = atoi(argv[2]); } /* 對非立體聲裝置給出警告資訊 */ if ((left != right) && !((1 << i) & stereodevs)) { fprintf(stderr, "warning: %s is not a stereo device\n", dev); } /* 將兩個聲道的值合到同一變數中 */ level = (right << 8) + left; /* 設定增益 */ status = ioctl(fd, MIXER_WRITE(device), &level); if (status == -1) { perror("MIXER_WRITE ioctl failed"); exit(1); } /* 獲得從驅動返回的左右聲道的增益 */ left = level & 0xff; right = (level & 0xff00) >> 8; /* 顯示實際設定的增益 */ fprintf(stderr, "%s gain set to %d%% / %d%%\n", dev, left, right); /* 關閉混音裝置 */ close(fd); return 0;}
編譯好上面的程式之後,先不帶任何參數執行一遍,此時會列出音效卡上所有可用的混音通道:
[[email protected] sound]$ ./mixerusage: ./mixer <device> <left-gain%> <right-gain%> ./mixer <device> <gain%> Where <device> is one of:vol pcm speaker line mic cd igain line1 phin video
之後就可以很方便地設定各個混音通道的增益大小了,例如下面的命令就能夠將CD輸入的左、右聲道的增益分別設定為80%和90%:
[[email protected] sound]$ ./mixer cd 80 90cd gain set to 80% / 90%
回頁首
五、小結
隨 著Linux平台下多媒體應用的逐漸深入,需要用到數字音訊場合必將越來越廣泛。雖然數字音頻牽涉到的概念非常多,但在Linux下進行最基本的音頻編 程卻並不十分複雜,關鍵是掌握如何與OSS或者ALSA這類音效卡驅動程式進行互動,以及如何充分利用它們提供的各種功能,熟悉一些最基本的音頻編程架構和 模式對初學者來講大有裨益。
參考資料
- 1. OSS是Linux上最早出現的音效卡驅動程式,http://www.opensound.com是它的核心網站,從中可以瞭解到許多與OSS相關的資訊。
- 2. ALSA是目前廣泛使用的Linux音效卡驅動程式,並且提供了一些庫函數來簡化音頻程式的編寫,在其官方網站http://www.alsa-project.org/上可以瞭解到ALSA的許多資訊,並能夠下載到最新的驅動程式和工具軟體。
- 3. Ken C. Pohlmann著,蘇菲譯,數字音頻原理與應用(第四合版),北京:電子工業出版社,2002
- 4. 鐘玉琢等編著,多媒體技術及其應用,北京:機械工業出版社,2003
Linux音頻編程指南(轉)