Linux音頻編程指南

雖然目前Linux的優勢主要體現在網絡服務方面,但事實上同樣也有着非常豐富的媒體功能,本文就是以多媒體應用中最基本的聲音爲對象,介紹如何在Linux平臺下開發實際的音頻應用程序,同時還給出了一些常用的音頻編程框架。

一、數字音頻

音頻信號是一種連續變化的模擬信號,但計算機只能處理和記錄二進制的數字信號,由自然音源得到的音頻信號必須經過一定的變換,成爲數字音頻信號之後,才能送到計算機中作進一步的處理。

數 字音頻系統通過將聲波的波型轉換成一系列二進制數據,來實現對原始聲音的重現,實現這一步驟的設備常被稱爲模/數轉換器(A/D)。A/D轉換器以每秒鐘 上萬次的速率對聲波進行採樣,每個採樣點都記錄下了原始模擬聲波在某一時刻的狀態,通常稱之爲樣本(sample),而每一秒鐘所採樣的數目則稱爲採樣頻 率,通過將一串連續的樣本連接起來,就可以在計算機中描述一段聲音了。對於採樣過程中的每一個樣本來說,數字音頻系統會分配一定存儲位來記錄聲波的振幅, 一般稱之爲採樣分辯率或者採樣精度,採樣精度越高,聲音還原時就會越細膩。

數字音頻涉及到的概念非常多,對於在Linux下 進行音頻編程的程序員來說,最重要的是理解聲音數字化的兩個關鍵步驟:採樣和量化。採樣就是每隔一定時間就讀一次聲音信號的幅度,而量化則是將採樣得到的 聲音信號幅度轉換爲數字值,從本質上講,採樣是時間上的數字化,而量化則是幅度上的數字化。下面介紹幾個在進行音頻編程時經常需要用到的技術指標:

  1. 採樣頻率
    採樣頻率是指將模擬聲音波形進行數字化時,每秒鐘抽取聲波幅度樣本的次數。採樣頻率的選擇應該遵循奈奎斯特(Harry Nyquist)採樣理論:如果對某一模擬信號進行採樣,則採樣後可還原的最高信號頻率只有採樣頻率的一半,或者說只要採樣頻率高於輸入信號最高頻率的兩 倍,就能從採樣信號系列重構原始信號。正常人聽覺的頻率範圍大約在20Hz~20kHz之間,根據奈奎斯特採樣理論,爲了保證聲音不失真,採樣頻率應該在 40kHz左右。常用的音頻採樣頻率有8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等,如 果採用更高的採樣頻率,還可以達到DVD的音質。
  2. 量化位數
    量化位數是對模擬音頻信號的幅度進行數字化,它決定了模擬信號數字化以後的動態範圍,常用的有8位、12位和16位。量化位越高,信號的動態範圍越大,數字化後的音頻信號就越可能接近原始信號,但所需要的存貯空間也越大。
  3. 聲道數
    聲道數是反映音頻數字化質量的另一個重要因素,它有單聲道和雙聲道之分。雙聲道又稱爲立體聲,在硬件中有兩條線路,音質和音色都要優於單聲道,但數字化後佔據的存儲空間的大小要比單聲道多一倍。




回頁首


二、聲卡驅動

出於對安全性方面的考慮,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獲得聲卡的當前狀態: [xiaowp@linuxgam 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進行編碼的音頻文件:
    [xiaowp@linuxgam 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=stereo
int 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_SYNTHFM合成器
SOUND_MIXER_PCM主D/A轉換器
SOUND_MIXER_SPEAKERPC喇叭
SOUND_MIXER_LINE音頻線輸入
SOUND_MIXER_MIC麥克風輸入
SOUND_MIXER_CDCD輸入
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;
}

編譯好上面的程序之後,先不帶任何參數執行一遍,此時會列出聲卡上所有可用的混音通道:

[xiaowp@linuxgam sound]$ ./mixer
usage: ./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%:

[xiaowp@linuxgam sound]$ ./mixer cd 80 90
cd 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和Python。你可以通過 [email protected]與他取得聯繫。

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