轉自: http://www.ibm.com/developerworks/cn/linux/l-audio/linux
雖然目前Linux的優點主要體如今網絡服務方面,但事實上一樣也有着很是豐富的媒體功能,本文就是以多媒體應用中最基本的聲音爲對象,介紹如何在Linux平臺下開發實際的音頻應用程序,同時還給出了一些經常使用的音頻編程框架。git
1 評論: 程序員
音頻信號是一種連續變化的模擬信號,但計算機只能處理和記錄二進制的數字信號,由天然音源獲得的音頻信號必須通過必定的變換,成爲數字音頻信號以後,才能送到計算機中做進一步的處理。網絡
數 字音頻系統經過將聲波的波型轉換成一系列二進制數據,來實現對原始聲音的重現,實現這一步驟的設備常被稱爲模/數轉換器(A/D)。A/D轉換器以每秒鐘 上萬次的速率對聲波進行採樣,每一個採樣點都記錄下了原始模擬聲波在某一時刻的狀態,一般稱之爲樣本(sample),而每一秒鐘所採樣的數目則稱爲採樣頻 率,經過將一串連續的樣本鏈接起來,就能夠在計算機中描述一段聲音了。對於採樣過程當中的每個樣原本說,數字音頻系統會分配必定存儲位來記錄聲波的振幅, 通常稱之爲採樣分辯率或者採樣精度,採樣精度越高,聲音還原時就會越細膩。多線程
數字音頻涉及到的概念很是多,對於在Linux下進行音頻編程的 程序員來講,最重要的是理解聲音數字化的兩個關鍵步驟:採樣和量化。採樣就是每隔必定時間就讀一次聲音信號的幅度,而量化則是將採樣獲得的聲音信號幅度轉 換爲數字值,從本質上講,採樣是時間上的數字化,而量化則是幅度上的數字化。下面介紹幾個在進行音頻編程時常常須要用到的技術指標:app
回頁首框架
出於對安全性方面的考慮,Linux下的應用程序沒法直接對聲卡這類硬件設備進行操做,而是必須經過內核提供的驅動程序才能完成。在Linux上進行音頻編程的本質就是要藉助於驅動程序,來完成對聲卡的各類操做。ide
對 硬件的控制涉及到寄存器中各個比特位的操做,一般這是與設備直接相關而且對時序的要求很是嚴格,若是這些工做都交由應用程序員來負責,那麼對聲卡的編程將 變得異常複雜而困難起來,驅動程序的做用正是要屏蔽硬件的這些底層細節,從而簡化應用程序的編寫。目前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的主要特色有:
ALSA 和OSS最大的不一樣之處在於ALSA是由志願者維護的自由項目,而OSS則是由公司提供的商業產品,所以在對硬件的適應程度上OSS要優於ALSA,它能 夠支持的聲卡種類更多。ALSA雖然不及OSS運用得普遍,但卻具備更加友好的編程接口,而且徹底兼容於OSS,對應用程序員來說無疑是一個更佳的選擇。
如何對各類音頻設備進行操做是在Linux上進行音頻編程的關鍵,經過內核提供的一組系統調用,應用程序可以訪問聲卡驅動程序提供的各類音頻設備接口,這是在Linux下進行音頻編程最簡單也是最直接的方法。
無 論是OSS仍是ALSA,都是之內核驅動程序的形式運行在Linux內核空間中的,應用程序要想訪問聲卡這一硬件設備,必須藉助於Linux內核所提供的 系統調用(system call)。從程序員的角度來講,對聲卡的操做在很大程度上等同於對磁盤文件的操做:首先使用open系統調用創建起與硬件間的聯繫,此時返回的文件描述 符將做爲隨後操做的標識;接着使用read系統調用從設備接收數據,或者使用write系統調用向設備寫入數據,而其它全部不符合讀/寫這一基本模式的操 做均可以由ioctl系統調用來完成;最後,使用close系統調用告訴Linux內核不會再對該設備作進一步的處理。
int open(const char *pathname, int flags, int mode);
int read(int fd, char *buf, size_t count);
size_t write(int fd, const char *buf, size_t count);
int ioctl(int fd, int request, ...);
int close(int fd);
對於Linux應用程序員來說,音頻編程接口實際上就是一組音頻設備文件,經過它們能夠從聲卡讀取數據,或者向聲卡寫入數據,而且可以對聲卡進行控制,設置採樣頻率和聲道數目等等。
聲卡驅動程序提供的/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以後,接下去就應該爲其設置恰 當的格式,而後才能從聲卡讀取或者寫入數據。
[xiaowp@linuxgam sound]$ cat audio.au > /dev/audio
在Linux下進行音頻編程時,重點在於如何正確地操做聲卡驅動程序所提供的各類設備文件,因爲涉及到的概念和因素比較多,因此遵循一個通用的框架無疑將有助於簡化應用程序的設計。
對 聲卡進行編程時首先要作的是打開與之對應的硬件設備,這是藉助於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; } // 檢查設置值的正確性
聲卡上的混音器由多個混音通道組成,它們能夠經過驅動程序提供的設備文件/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來得到。
下面給出一個利用聲卡上的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"); } }
下面再給出一個對混音器進行編程的基本框架,利用它能夠對各類混音通道的增益進行調節,其全部的功能都是經過讀寫/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這類聲卡驅動程序進行交互,以及如何充分利用它們提供的各類功能,熟悉一些最基本的音頻編程框架和 模式對初學者來說大有裨益。