oss編程

一、概述

1.聲卡

聲卡有三個基本功能:一是音樂合成發音功能;二是混音器(Mixer)功能和數字聲音效果處理器(DSP)功能;三是模擬聲音信號的輸入和輸出功能。

混音器的作用是將來自音樂合成器、CD-ROM、話筒輸入(MIC)等不同來源的聲音組合在一起再輸出,混音器是每種聲音卡都有的。

模擬聲音輸入輸出功能 主要是A/D、D/A轉換。

2.MP3格式

MP3其中的VBR,ABR,CBR及相關解釋 VBR(Variable Bitrate)動態比特率。也就是沒有固定的比特率,壓縮軟件在壓縮時根據音頻數據即時確定使用什麼

比特率。將一首歌的復 雜部分用高Bitrate編碼,簡單部分用低Bitrate編碼。 

ABR(Average Bitrate)平均比特率,是VBR的一種插值參數。Lame針對CBR不佳的文件體積比和VBR生成文件大小不定的特點獨創了這種編碼模式。

ABR也 被稱爲“Safe VBR”,它是在指定的平均Bitrate內,以每50幀(30幀約1秒)爲一段,低頻和不敏感頻率使用相對低的流量,高頻和大動態表現時使用高

流量。舉 例來說,當指定用192kbps ABR對一段wav文件進行編碼時,Lame會將該文件的85%用192kbps固定編碼,然後對剩餘15%進行動態優化:複雜部

分用高於192kbps 來編碼、簡單部分用低於192kbps來編碼。與192kbps CBR相比,192kbps ABR在文件大小上相差不多,音質卻提高不少。ABR編碼在速度

上是VBR編碼的2到3倍,在128-256kbps範圍內質量要好於CBR。可以做爲 VBR和CBR的一種折衷選擇。

CBR(Constant Bitrate),常數比特率,指文件從頭到尾都是一種位速率。相對於VBR和ABR來講,它壓縮出來的文件體積很大,但音質卻不會有明顯的提高。

 對MP3來說Bitrate是最重要的因素,它用來表示每秒鐘的音頻數據佔用了多少個bit(bit per second,簡稱bps)。這個值越高,音質就越好。

當音樂CD的數據被讀到電腦後,他的存儲將成爲一大難題,我們可以來算一下,44100Hz採樣率16bit位率的立體聲數據數據一秒鐘有多少?

44100Hz * (16 / 8)Byte * 2Channel = 176400字節,也就是0.17M,一分種就是10.5M,一般CD可以存70多分鐘的音樂,那麼一張CD算下來就是700多M,

對於一般百餘G的硬盤空間,這還是比較難以承受的,所以很多年以前有人就提出了有損壓縮的辦法,採用了類似於快速傅利葉變換的算法,對數據進行處理,可以

把WAVE壓縮到很小,一秒鐘100-300K,這樣就很好的解決了存儲的問題,但這是以犧牲了一部分音質爲代價的。

二.DSP(數字音頻設備)

數字音頻系統通過將聲波的波型轉換成一系列二進制數據,來實現對原始聲音的重現,實現這一步驟的設備常被稱爲模/數轉換器(A/D)。A/D轉換器

以每秒鐘上萬次的速率對聲波進行採樣,每個採樣點都記錄下了原始模擬聲波在某一時刻的狀態,通常稱之爲樣本(sample),而每一秒鐘所採樣的數

目則稱爲採樣頻率,通過將一串連續的樣本連接起來,就可以在計算機中描述一段聲音了。

數字聲音效果處理器是對數字化的聲音信號進行處理以獲得所需要的音響效果(混響、延時、合唱等).

用於表示聲卡性能的兩個參數是採樣率和模擬量轉換成數字量之後的數據位數(簡稱量化位數)。採樣率決定了頻率響應範圍,對聲音進行採樣的三種標準

以及採樣頻率分別爲:語音效果(11 kHz)、音樂效果(22 kHz)、高保真效果(44.1 kHz),目前聲卡的最高採樣率爲44.1KHz。

量化位數決定了音樂的動態範圍,量化位數有8位和16位兩種。8位聲卡的聲音從最低音到最高音只有256個級別,16位聲卡有65536個高低音級別。

聲道數 :反映音頻數字化質量的另一個重要因素,雙聲道又稱爲立體聲,在硬件中有兩條線路,音質和音色都要優於單聲道。

 位速說明 Kbps 表示 “每秒千字節數”,因此數值越大表示數據越多。如果您想把製作的 VCD 放在 DVD 播放器上播放,那麼視頻必須是 1150 Kbps,

音頻必須是 224 Kbps。

DSP:數字音頻設備(有時也稱codec,PCM,ADC/DAC設備),播放或錄製數字化的聲音。它的指標主要有:採樣速率(電話爲8K,DVD爲96K)、channel

數目(單聲道,立體聲)、採樣分辨率(8-bit,16-bit)。對該設備操作時應注意一次寫入數據塊的大小,如果數據塊過大會引起設備的block操作。 

/dev/dsp與/dev/audio之間的區別在於採樣的編碼不同,/dev/audio使用μ律編碼,/dev/dsp使用8-bit(無符號)線性編碼,/dev/dspW使用16-bit(有符號)線

形編碼。/dev/audio主要是爲了與SunOS兼容,所以儘量不要使用。

在從DSP設備讀取數據時,從聲卡輸入的模擬信號經過A/D轉換器變成數字採樣後的樣本(sample),保存在聲卡驅動程序的內核緩衝區中,當應用程序通過read

系統調用從聲卡讀取數據時,內核緩衝區內容被複制用戶緩衝區中。

聲卡採樣頻率是由內核中的驅動程序所決定的。如果應用程序讀取數據的速度過慢,以致低於聲卡的採樣頻率,那麼多餘的數據將會被丟棄;如果讀取數據的速度過

快,以致高於聲卡的採樣頻率,那麼聲卡驅動程序將會阻塞那些請求數據的應用程序,直到新的數據到來爲止。

在向DSP設備寫入數據時,數字信號會經過D/A轉換器變成模擬信號,然後產生出聲音。應用程序寫入數據的速度同樣應該與聲卡的採樣頻率相匹配,否則過慢的話

會產生聲音暫停或者停頓的現象,過快的話又會被內核中的聲卡驅動程序阻塞,直到硬件有能力處理新的數據爲止。聲卡通常不會支持非阻塞的I/O操作。

無論是從聲卡讀取數據,或是向聲卡寫入數據,事實上都具有特定的格式(format),默認爲8位無符號數據、單聲道、8KHz採樣率,如果默認值無法達到要求,

可以通過ioctl系統調用來改變它們。

1. 錄音

int len = read(audio_fd, audio_buffer, count);

count爲錄音數據的字節個數(建議爲2的指數),但不能超過audio_buffer的大小。從讀字節的個數可以精確的測量時間,例如

8kHZ 16-bit stereo的速率爲8000*2*2=32000bytes/second,這是知道何時停止錄音的唯一方法。

注意:用戶始終要讀/寫一個完整的採樣。例如一個16-bit的立體聲模式下,每個採樣有4個字節,所以應用程序每次必須讀/寫4的倍數個字節。

還要注意讀/寫時的字節順序。

2. 設置參數

設置採樣格式

int format;

format = AFMT_S16_LE;

ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format) ;

在設置採樣格式之前,可以先測試設備能夠支持那些採樣格式,方法如下:

int mask;

ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &mask) ;

if (mask & AFMT_MPEG) {

/* 本設備支持MPEG採樣格式 ... */}

/*設置採樣時的量化位數*/

arg = SIZE;

status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);

/* 在繼續錄音前等待回放結束 */

status = ioctl(fd, SOUND_PCM_SYNC, 0); 

設置聲卡工作時的聲道(channel)數目

int channels = 0; // 0=mono 1=stereo

int result = ioctl(handle, SNDCTL_DSP_STEREO, &channels);

採樣格式和採樣頻率是在進行音頻編程時需要考慮的另一個問題,聲卡支持的所有采樣格式可以在頭文件soundcard.h中找到.

int format = AFMT_U8;

int result = ioctl(handle, SNDCTL_DSP_SETFMT, &format);

聲卡採樣頻率的設置。下面的代碼示範瞭如何設置聲卡的採樣頻率:

int rate = 22050;

int result = ioctl(handle, SNDCTL_DSP_SPEED, &rate);

44100hz

Sample rate of standard Red Book audio CDs.

88000hz

Sample rate of SACD high definition audio discs/downloads. It is rare that your motherboard will support this sample rate.

96000hz

Sample rate of most high definition audio downloads. If your motherboard is an AC'97 motherboard, this is likely to be your highest bitrate.

192000hz

Sample rate of BluRay, and some (very few) high definition downloads. Support for external audio receiver equipment is limited to high end 

audio. Not all motherboards support this. An example of a motherboard chipset that would support this includes HD Audio. 

設置通道數目

int channels = 2; /* 1=mono, 2=stereo */

ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &channels) ;

音頻設備通過分頻的方法產生需要的採樣時鐘,因此不可能產生所有的頻率。

/* 用於保存數字音頻數據的內存緩衝區 */

unsigned char buf[LENGTH*RATE*SIZE*CHANNELS/8];

Telephone Quality8 KHz or 11.025 KHz

Radio Quality22.05 KHz

CD Quality44.1KHz 

DVD Quality98KHz

暫停

用兩個bits去控制是否暫停,一個bit控制錄音,一個bit控制撥音。bit1表正常錄撥音,bit0表暫停錄撥音。

PCM_ENABLE_OUTPUT

PCM_ENABLE_INPUT

int enable_bits = ~ PCM_ENABLE_OUTPUT;

ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, & enable_bits);

3.聲卡緩衝區

聲卡驅動專門維護了一個緩衝區,其大小會影響到放音和錄音時的效果。調節驅動程序中緩衝區大小的操作不是必須的,如果沒有特殊的要求,一般採用默認的緩衝區

大小也就可以了。但需要注意的是,緩衝區大小的設置通常應緊跟在設備文件

打開之後,這是因爲對聲卡的其它操作有可能會導致驅動程序無法再修改其緩衝區的大小。

int result = ioctl(handle, SNDCTL_DSP_SETFRAGMENT, &(int)0xx*);

在設置緩衝區大小時,參數setting實際上由兩部分組成,其低16位標明緩衝區的尺寸,相應的計算公式爲buffer_size = 2^ssss,即若參數setting低16

位的值爲16,那麼相應的緩衝區的大小會被設置爲65536字節。參數setting的高16位則用來標明分片(fragment)的最大序號,它的取值範圍從2一直

到0x7FFF,其中0x7FFF表示沒有任何限制。

OSS卻是採用multi-buffering,因為考慮real time performance一般情況下,一開始程式會等到一個buffer都滿了之後才開始撥音,如果buffer size太大,

才會有一段時間的延遲,對於real time的程式來說是不可行的,因此OSSbuffer分成一堆小的fragment,而程式開始時只須等兩個fragment滿了就開

始撥音。一般的情況下我們並不需要去考慮fragment size的大小,OSS會計算出程式的data rate自動幫我們調整bufferfragment的大小。

Multi-buffering

audio CD quality : 172 KB/sec  à small buffer is likely to fail à

increase buffer size à latency increase à OSS solution : fragment the buffer (multi-buffering)

Small  fragment size  improves real-time performance.

Determine Buffering Parameters

Get Fragment Size:

int frag_size;

ioctl(audio_fd, SNDCTL_DSP_GETBLKSIZE, &frag_size)

Set Fragment Size:

int arg = 0xMMMMSSSS;

ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &arg)

fragment size = 2 ^SSSS  bytes ( if SSSS = 0008, fragment size = 2 ^8 = 256 )

fragment numbers = 0x MMMM

Ø Synchronization Issues

Get Played or recorded data bytes from the beginning of opening the device file:

count_info info;

ioctl(audio_fd, SNDCTL_DSP_GETIPTR, &info);

ioctl(audio_fd, SNDCTL_DSP_GETOPTR, &info);

struct count_info {

int bytes;    //number of bytes processed since opening the device

int blocks;  // number of fragment processed since previous call to ioctl

int ptr;   // offset of current play/record position from beginning of buffer

};

played_time=info.bytes / bytes_per_sec

now_frame = played_time * frame_per_sec

使用SNDCTL_DSP_GETOPTR / SNDCTL_DSP_GETIPTR的缺點是不夠精確,例如如果錄音時data  rate很快,而I/O不能及時將buffer的資料送給程

式,則會有一部份的data被覆蓋(overrun),利用SNDCTL_DSP_GETIPTR所得到的byte所算出的時間則並不正確。而且錯誤會不斷的累積,算出的時

間會越來越不準。

4.緩衝區設置的性能分析

驅動程序採用多緩衝(Multi-buffering)的方式,即將大的DMA buffer分割成多個小的緩衝區,稱之爲fragment,它們的大小相同。驅動程序開始時只需等待兩個fragment滿了就開始播放。這樣

可以通過增加fragment的個數來增加緩衝區的大小,但同時每個fragment被限制在合適的大小,也不影響時延。音頻驅動程序中的多緩衝機制一般會利用底層DMA控制器的scatter-gather功能。

在OSS的ioctl接口中,SNDCTL_DSP_SETFRAGMENT就是用來設置驅動程序內部緩衝區大小。

int param = ( 0x0004 << 16) + 0x000a;

ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, ) ;

參數param由兩部分組成:低16位爲fragment的大小,此處0x000a表示fragment大小爲2^0xa,即1024字節;高16 位爲fragment的數量,此處爲0x0004,即4個fragement。設置好fragment參數後

,通過ioctl的 SNDCTL_DSP_SETFRAGMENT命令調整驅動程序中的緩衝區。

第一種情況的緩衝區很小,每個fragment只有512字節,總共的緩衝區大小爲2 x 512 = 1024字節。1024字節只能播放5.8ms。不推薦使用fragment大小小於256字節的設置。從測試結果中看到

,不管使用那種系統負載,

第二種情況的緩衝區要大得多,總共的緩衝區大小爲4 x 2048 = 8192字節。8192字節可以播放0.046秒。從測試的圖形來看,結果比較理想,即使在系統負載較重的情況,仍然能夠基本保證

播放時延的要求,而且沒有出現一次欠載的現象。

並不是說緩衝區越大越好,如果繼續選擇更大的緩衝區,將會產生比較大的時延,對於實時性要求比較高的音頻流來說,是不能接受的。在一般情況下,驅動程序會根據硬件的情況,選擇

一個缺省的緩衝區配置,播放程序通常不需要修改驅動程序的緩衝區配置,而可以獲得較好的播放效果。

5.非阻塞寫(non-blocking write)

如果播放程序寫入的速度超過了DAC的播放速度,DMA buffer就會充滿了音頻數據。應用程序調用write時就會因爲沒有空閒的DMA buffer而被阻塞,直到DMA buffer出現空閒爲止。此時,

從某種程度來說,應用程序的推進速度依賴於播放的速度,不同的播放速度就會產生不同的推進速度。有時我們不希望 write被阻塞,這就需要我們能夠知道DMA buffer的使用情況。

audio_buf_info info

;/* Ask OSS if there is any free space in the buffer. */

(ioctl(dsp,SNDCTL_DSP_GETOSPACE,&info);

/* Any empty fragments? */

if (info.fragments > 0) break;

/* Not enough free space in the buffer. Waste time. */

usleep(100);

以上的代碼不停的查詢驅動程序中是否有空的fragment(SNDCTL_DSP_GETOSPACE),如果沒有,則進入睡眠(usleep(100))。如果有空閒的fragment(info.fragments > 0),則退出循環,接

着就可以進行非阻塞的write了。

6.DMA buffer的直接訪問(mmap)

將音頻數據輸出到音頻設備通常使用系統調用write,但是這會帶來性能上的損失,因爲要進行一次從用戶空間到內核空間的緩衝區拷貝。這時,可以考慮利用mmap系統調用,獲得直接訪問

DMA buffer的能力。DMA控制器不停的掃描DMA buffer,將數據發送到DAC。這有點類似於顯卡對顯存的操作,大家都知道,GUI可以通過mmap將framebuffer(顯存)映射到自己的地址空間,

然後直接操縱顯存。這裏的DMA buffer就是聲卡的framebuffer。

在使用mmap之前,要查看驅動程序是否支持這種模式(SNDCTL_DSP_GETCAPS)。使用SNDCTL_DSP_GETOSPACE得知驅動選擇的framgment大小和個數,就可以計算出全部DMA buffer的

大小dmabuffer_size。

mmap將dmabuffer_size大小的DMA buffer映射到調用進程的地址空間,DMA buffer在應用進程的起始地址爲dmabuffer。以後就可以直接使用指針dmabuffer訪問DMA buffer了。這裏需要對mmap

中的參數做些解釋。

音頻驅動程序針對播放和錄音分別有各自的緩衝區,mmap不能同時映射這兩組緩衝,具體選擇映射哪個緩衝取決於mmap的prot參數。 PROT_READ選擇輸入(錄音)緩衝,PROT_WRITE選

擇輸出(播放)緩衝,代碼中使用了PROT_WRITE|PROT_READ,也是選擇輸出緩衝。(這是BSD系統的要求,如果只有PROT_WRITE,那麼每次對緩衝的訪問都會出現segmentation/bus error)。

一旦DMA buffer被mmap後,就不能再通過read/write接口來控制驅動程序了。只能通過SNDCTL_DSP_SETTRIGGER打開DAC的使能位,當然,先要關閉使能位。

DMA一旦啓動後,就會週而復始的掃描DMA buffer。當然我們總是希望提前爲DMA準備好新的數據,使得DMA的播放始終連續。因此,PlayerDMA函數將mmap後的DMA buffer分割成前後兩塊

,中間設置一個界限。當DMA掃描前面一塊時,就填充後面一塊。一旦DMA越過了界限,就去填充前面一塊。

使用mmap的問題是,不是所有的聲卡驅動程序都支持mmap方式。因此,在出現不兼容的情況下,應用程序要能夠轉而去使用傳統的方式。

audio_mmap()是實現mmap接口的函數,它首先根據mmap調用的prot參數(vma->vm_flags),選擇合適的緩衝(輸入還是輸出);vma->vm_end - vma->vm_start爲需要映射到應用進程地址空

間的大小,必須和DMA buffer的大小(s->fragsize * s->nbfrags)一致;如果DMA buffer還沒有建立,則調用audio_setup_buf(s)建立;接着對所有的fragment,從映射起始地址開始(vma->vm_start)

,建立實際物理地址與映射的虛擬地址之間的對應關係(remap_page_range)。最後設置mmap標誌(s->mapped = 1)。

三、Mixer編程

混音器電路通常由兩個部分組成:input mixer和ouput mixer.

輸入混音器負責從多個不同的信號源接收模擬信號,這些信號源有時也被稱爲混音通道或者混音設備。模擬信號通過增益控制器和由軟件控制的音量調節器後,在不同的混音通道中進

行級別(level)調製,然後被送到輸入混音器中進行聲音的合成。混音器上的電子開關可以控制哪些通道中有信號與混音器相連,有些聲卡只允許連接一個混音通道作爲錄音的音源,

而有些聲卡則允許對混音通道做任意的連接。經過輸入混音器處理後的信號仍然爲模擬信號,它們將被送到A/D轉換器進行數字化處理。 

 用來控制多個輸入、輸出的音量,也控制輸入(microphone,line-in,CD)之間的切換。對Mixer的控制,包括調節音量(volume)、選擇錄音音源(microphone,line-in)、

查詢mixer的功能和狀態,選擇mixer的錄音通道。主要是對聲卡進行設置,比如設置speaker、mic和midi的音量等等。這些設置主要是通過ioctl進行,比如可以用

SNDCTL_DSP_SETPLAYVOL設置speaker的音量,用SNDCTL_DSP_SETRECVOL設置mic的音量,用SNDCTL_DSP_SET_PLAYTGT選擇輸出的speaker(比如機身的speaker或者earphone)。

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輸入

soundcard.h中已定義的channel數量。

1.調節音量

對聲卡的輸入增益和輸出增益進行調節是混音器的一個主要作用,大部分聲卡採用的是8位或者16位的增益控制器。

應用程序通過ioctl的SOUND_MIXER_READ和SOUND_MIXER_WIRTE功能號來讀取/設置音量。在OSS中,音量的大小範圍在0-100之間。例如在獲取麥克風的輸入增益時使用方法如下:

int vol;

if (ioctl(mixer_fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol) == -1) {

/* 訪問了沒有定義的mixer通道... */

SOUND_MIXER_MIC是通道參數,表示讀microphone通道的音量,結果放置在vol中。如果通道是立體聲,那麼vol的最低有效字節爲左聲道的音量值,接着的字節爲右聲道的音量

值,另外的兩個字節不用。如果通道是單聲道,vol中左聲道與右聲道具有相同的值。

對於只有一個混音通道的單聲道設備來說,返回的增益大小保存在低位字節中。而對於支持多個混音通道的雙聲道設備來說,返回的增益大小實際上包括兩個部分,分別代表左、右兩

個聲道的值,其中低位字節保存左聲道的音量,而高位字節則保存右聲道的音量。下面的代碼可以從返回值中依次提取左右聲道的增益大小:

int left, right;

left = vol & 0xff;

right = (vol & 0xff00) >> 8;

如想設置混音通道的增益大小,則可以通過SOUND_MIXER_WRITE宏來實現下面的語句可以用來設置麥克風的輸入增益:

vol = (right << 8) + left;

ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);

2.查詢mixer的能力

int mask;

if (ioctl(mixer_fd, SOUND_MIXER_READ_xxxx, &mask) == -1) {

/* Mixer 的沒有此能力... */

}

SOUND_MIXER_READ_xxxx 中的xxxx代表具體要查詢的內容,比如檢查可用的mixer通道用SOUND_MIXER_READ_DEVMASK;

檢查可用的錄音設備,用SOUND_MIXER_READ_RECMASK;檢查單聲道/立體聲,用SOUND_MIXER_READ_STEREODEVS;

檢查mixer的一般能力,用SOUND_MIXER_READ_CAPS等等。

所有通道的查詢的結果都放在mask中,所以要區分出特定通道的狀況,使用mask& (1 << channel_no)。

3.選擇mixer的錄音通道

首先可以通過SOUND_MIXER_READ_RECMASK檢查可用的錄音通道,然後通過SOUND_MIXER_WRITE_RECSRC選擇錄音通道。可以隨時通過SOUND_MIXER_READ_RECSRC查詢當前聲

卡中已經被選擇的錄音通道。在使用mixer之前,首先通過API的查詢功能檢查聲卡的能力。

聲卡驅動程序提供了多個ioctl系統調用來獲得混音器的信息,它們通常返回一個整型的位掩碼(bitmask),其中每一位分別代表一個特定的混音通道,如果相應的位爲1,則說明與之對應的混

音通道是可用的。例如通過SOUND_MIXER_READ_DEVMASK返回的位掩碼,可以查詢出能夠被聲卡支持的每一個混音通道,而通過SOUND_MIXER_READ_RECMAS返回的位掩碼,則可以查

詢出能夠被當作錄音源的每一個通道。下面的代碼可以用來檢查CD輸入是否是一個有效的混音通道:

ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);

如果進一步還想知道其是否是一個有效的錄音源,則可以使用如下語句:

ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask);

大多數聲卡提供多個錄音源,通過SOUND_MIXER_READ_RECSRC可以查詢出當前正在使用的錄音源,同一時刻能夠使用幾個錄音源是由聲卡硬件決定的。使用SOUND_MIXER_WRITE_RECSRC

可以設置聲卡當前使用的錄音源,例將CD輸入作爲聲卡的錄音源使用:

devmask = SOUND_MIXER_CD;

ioctl(fd, SOUND_MIXER_WRITE_DEVMASK, &devmask);

所有的混音通道都有單聲道和雙聲道的區別,如果需要知道哪些混音通道提供了對立體聲的支持,可以通過SOUND_MIXER_READ_STEREODEVS來獲得。

三.MIDI

MIDI是數字樂器接口的國際標準,它定義了電子音樂設備與計算機的通訊接口,規定了使用數字編碼來描述音樂樂譜的規範。常見的MIDI設備有電子琴等。計算機中以MID爲擴展名的文件稱爲

MIDI文件,其中存放的是對MIDI設備的命令,即每個音符的頻率、音量、通道號等指示信息。最後播出的聲音是由MIDI設備根據這些信息產生的。MIDI聲音可以用於配音。

MIDI與聲波形式的聲音不同,MIDI技術不是對聲波進行編碼,而是把MIDI樂器上產生的每一活動編碼記錄下來存儲在MIDI文件中,放在MIDI消息中。MIDI 傳輸的不是聲音信號, 而是音符、控制參

數等指令, 它指示MIDI 設備要做什麼,怎麼做, 如演奏哪個音符、多大音量等。它們被統一表示成MIDI 消息(MIDI Message) .MIDI技術的優點是可以節省大量的存儲空間,並可方便地配樂。

/dev/dmmidi:是MIDI設備(MIDI device)的原始接口,它提供直接TTY方式訪問MIDI端口,主要是給一個特殊應用程序使用。(不知道它與/dev/midi的差別到底在哪裏。)

MIDI接口是爲了連接舞臺上的synthesizer(合成器,通過一些預先定義好的波形來合成聲音,有時用在遊戲中聲音效果的產生)、鍵盤、道具、燈光控制器的一種串行接口。該文件是MIDI總

線端口的比較底層的接口,它的工作方式很像一個TTY (character terminal),所有發送給它的數據立即傳遞到MIDI端口。

MIDI是Musical Instrument Digital Interface的縮寫。說白了,MIDI只一個通信協議,用來在樂器之間進行通信的,讓所有的樂器都說同一種語言。這種通信通常是通過一個高速串口來實現

的,速率爲31250bps,8bits的數據,外加一個起始位和一個停止位。下面簡單介紹一下這個協議的內容:

MIDI的消息由狀態(Status,或者稱爲命令更好)和數據兩部分組成,狀態由一個字節表示,所有狀態值的最高位都爲1,即大於等於128。數據由多個字節表示,數據長度視狀態而定,但所有數

據的最高位都爲0,即小於128。狀態的8bits,又分爲兩個4bits,高的4bits代表狀態的類型,低四位代表通道。MIDI的狀態有:

8 = Note Off

9 = Note On

A = AfterTouch (ie, key pressure)

B = Control Change

C = Program (patch) change

D = Channel Pressure

E = Pitch Wheel

F = System Exclusive

Note On: 它是一個抽象的動作,當演奏者按下鋼琴的鍵,拉動小提琴的弦或者撥動吉它的弦,這個動作稱爲note on。

Note Off: 它是一個抽象的動作,當演奏者放開鋼琴的鍵,停止拉動小提琴或者手指離開吉它的弦,這個動作稱爲note off。

AfterTouch: 同是按鍵動作,力度的差異產生效果也不一樣,即使在保持按鍵的過程中,壓力也會有變化,這由AfterTouch狀態來調整。

Control Change:用來對MIDI設備進行設置,比如設置音量和立體聲平衡值等等,它有128種取值(0-127)。

Program (patch) change:一個Program 一般與一種樂器對應,比如Piano、 Guitar和Trumpet,要換樂器就用這個狀態。

Channel Pressure: 和AfterTouch的功能類似,但它不只影響一個note,而是影響一個通道,通常用來設置默認值。

Pitch Wheel:設置Pitch Wheel的值,好像是設置樂器的基準音調吧,不太懂。

 播放midi

unsigned char note_on[] = { 0xc0, 0, /* Program change */0x90, 60, 60}; /* Note on */

unsigned char note_off[] = { 0x80, 60, 60 }; /* Note off */

int fd = open  "/dev/midi" , O_WRONLY, 0));

write (fd, note_on, sizeof (note_on)) ;

sleep (1); /* Delay one second */

write (fd, note_off, sizeof (note_off)) ;

四.其他設備

1. /dev/sndstat

這主要是用於debug的,cat /dev/sndstat可以輸出一些OSS驅動程序檢測的設備信息,這些信息是給人讀的而不是程序使用的。

2./dev/oss/sblive0/pcm0是第一個聲卡。 

關於PCM的

PCM是Pulse code modulation的縮寫,它是對波形最直接的編碼方式。Sampling rate:從模擬信號到數字信號,即從連續信號到離散信號的轉換都是通過離散採樣完成的,Sampling rate

就是每秒種採樣的個數。根據香農採樣定理,要保證信號不失真,Sampling rate要大於信號最高頻率的兩倍。我們知道人的耳朵能聽到的頻率範圍是20hz – 20khz,所以Sampling rate達到

40k就夠了,再多了也只是浪費。但是有時爲了節省帶寬和存儲資源,可以降低Sampling rate而損失聲音的質量,所以我們常常見到小於40k採樣率的聲音數據。Sample size:用來量化一個

採樣的幅度,一般爲8 bits、16 bits和24 bits。8 bits只有早期的聲卡支持,而24 bits只有專業的聲卡才支持,我們用的一般都是16 bits的。Number of channels:聲音通道個數,單聲道爲

一個,立體聲爲兩個,還有更多的(如8個聲道的7.1格式)。一般來說,每個聲道都來源於一個獨立的mic,所以聲道多效果會更好(更真實),當然代價也更大。Frame: Frame是指包含了所

有通道的一次採樣數據,比如對於16bits的雙聲道來說,一個frame的大小爲4個字節(2 * 16)。

/dev/dsp_spdifout:Default digital audio(默認的數字信號) (S/PDIF) output device

3. /dev/sequencer

主要是給電子(MIDI)音樂應用程序使用的,也利用它來實現遊戲中的音效。通過該文件可以訪問聲卡內部和外部(即子卡)中的聲音合成(synthesizer)設備。聲音合

成(synthesizer)設備的功能是把MIDI轉成波型數據,一般通過調頻(FM)和波表(wavetable)來實現。

4. /dev/music

該文件類似於/dev/sequencer,可以以同樣的方式處理聲音合成(synthesizer)設備和MIDI設備(可能是指MIDI鍵盤吧),而且具有更好的設備無關性,但是不能像

/dev/sequencer那可以精確的控制單個音符。

5. /dev/dmfm

該文件是調頻合成器(FM synthesizers)的原始接口,通過它可以訪問FM的一些底層寄存器。

五.FFMPEG

ffmpeg提取音頻播放器總結;音頻播放器過程如下所示:打開文件--分析文件格式--打開對應解碼器--讀取一音頻幀--解碼音頻幀--音頻數據寫入音頻設備--循環讀取音頻幀--

再解碼。。。如此循環下去;

整個播放器實現原理詳細說明爲,採用ffmpeg提供的API函數先用av_open_input_file打開音頻文件,分析文件得到格式信息,然後識別格式,並查找對應的解碼器,再得到針對此音頻文件的解碼

器之後,用av_read_frame從音頻文件中讀取一幀,然後對其用 avcodec_decode_audio函數進行解碼,在將解碼之後的PCM音頻數據直接寫到audio設備(/dev/dsp)上,根據linux音頻設備的原

理,此時你就應該聽到悅耳的歌聲了;1、不同音頻文件格式對音頻壓縮率不同,導致對於同一個音頻包,你解壓出來的音頻數據大小也是不一樣的,這一點無需驚奇,但是對於這些解壓出來

的音頻數據,一定要保證全部寫到聲卡當中去,這樣才能夠作爲你能聽到悅耳歌聲的基礎. 有一些寫音頻數據的太快的原故;2、在確認瞭解碼後的數據是完整的之後,可以將數據寫入到音頻

設備當中了(/dev/dsp),這裏很關鍵的一點就是要對音頻設備進行設置,否則你也聽不到你想聽到的聲音:

(對音頻設備的設置主要是四個方面,這不代表其他方面不設置哦:

設置採樣率

ioctl (fd, SNDCTL_DSP_SPEED, &(pCodecCtx->sample_rate));

設置音頻聲道數(這個很好理解,一般都是立體聲了)

// set channels;

i = pCodecCtx->channels;

ioctl (fd, SNDCTL_DSP_CHANNELS, &i);

這裏需要說明的一點是,如果是立體聲,則此處i應該等於2,而不是1.

設置量化位數(這個量化位數是指對聲音的振幅進行採樣的位數,以前一般是8位,現在以16位居多,更高位數對於普通用戶用不着,只能在專業音樂中才有價值)

i = AFMT_S16_LE; 

(16位,小端存儲,也即intel的倒序數據存儲)

ioctl (fd, SNDCTL_DSP_SETFMT, &i);

設置音頻驅動級緩存

i = (0x0004 << 16) + 0x000b; 

// four 2kb buffer;

你看着對應改就行了(這裏是四個2kb緩存)

ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &i);

這裏也可以不設置,用系統默認自定義也可;

Q:播放音頻和pts有關係麼?需要他來調整播放的快慢麼?就像視頻那樣?

A:基本沒有關係,至少我目前沒有用到這個咚咚,pts應該實在視頻當中採用到的,pts是顯示時間戳,dts是解碼時間戳;對於寫音頻數據,系統,或者更準確的說驅動會自動調整,寫得快,

他會阻塞你的寫,寫的慢?增加緩存可以解決慢的問題;

Q:如何調試音頻播放器?

這裏需要注意兩點,一點是你要保證解碼後的數據確實是PCM數據;

第二點是你要確定數據準確無誤,全部寫入音頻文件;有關這兩點你可以分別調試;

第一點,可以將解碼後的數據寫入一個文件當中,然後利用一些音頻分析軟件(能夠分析PCM數據),播放即可,看你解碼的數據是否正確,完整;這裏向大家推薦windows下的cooledit軟件,

不用找註冊碼,反正能試用,沒問題,功能絕對夠用,而且分析聲音頻播非常形象,鄭重推薦.

下面將這個音頻播放器的源代碼貼出來,以便大家互相學習;

我的編譯環境是硬件:普通pc機;

// manipulations for file//

 open file file_name and return the file descriptor;

int fd = open (file_name, mode);

// set the properties of audio device with pCodecCtx;

/* 設置適當的參數,使得聲音設備工作正常 */

/* 詳細情況請參考Linux關於聲卡編程的文檔 */

i = 0;

ioctl (fd, SNDCTL_DSP_RESET, &i);

i = 0;

ioctl (fd, SNDCTL_DSP_SYNC, &i);

i = 1;

ioctl (fd, SNDCTL_DSP_NONBLOCK, &i);

// set sample rate;

i = pCodecCtx->sample_rate;

ioctl (fd, SNDCTL_DSP_SPEED, &i);

// set channels;

i = pCodecCtx->channels;

ioctl (fd, SNDCTL_DSP_CHANNELS, &i)) ;

// set bit format;

i = AFMT_S16_LE;

ioctl (fd, SNDCTL_DSP_SETFMT, &i) ;

// set application buffer size;

i = 1;

ioctl (fd, SNDCTL_DSP_PROFILE, &i);

// set the sched priority

// 這是爲了提高音頻優先級;不曉得起作用沒;

int policy = SCHED_FIFO;

sched_setscheduler(0, policy, NULL);

int write_buf_size = 4196;

int written_size;

while (av_read_frame (pFormatCtx, &packet) >= 0)

{

// Is this a packet from the audio stream?

// 判斷是否音頻幀;

if (packet.stream_index == audioStream)

{

// Decode audio frame

// 解碼音頻數據爲pcm數據;

len = avcodec_decode_audio (pCodecCtx, (int16_t *)decompressed_audio_buf, &decompressed_audio_buf_size, // it is the decompressed frame in BYTES 解碼後的數據大小,字節爲單位;packet.data, packet.size );

// 重點是這一部分,使用oss播放的代碼,之前的數據寫是否完整的問題就是出在這裏,或者是前面的set_audio函數設置不正確;

// audio_buf_info info;

p_decompressed_audio_buf = decompressed_audio_buf;while ( decompressed_audio_buf_size > 0 )

{

// 解碼後數據不爲零,則播放之,爲零,則;

written_size = write(fd, p_decompressed_audio_buf, decompressed_audio_buf_size);

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