Linphone學習之 Oss

來源:互聯網
上載者:User

標籤:使用   os   io   檔案   資料   for   art   問題   

1.OSS簡介

OSS的階層非常簡單,應用程式通過API(定義於 )訪問OSS driver,OSS driver控制音效卡。如所示:

oss結構

音效卡中主要有兩個基本裝置:Mixer和CODEC(ADC/DAC)。Mixer用來控制輸入音量的大小,對應的裝置檔案為/dev /mixer;CODEC用來實現錄音(類比訊號轉變為數字訊號)和播放聲音(數字訊號轉變為類比訊號)的功能,對應的裝置檔案為/dev/dsp。

開發OSS應用程式的一般流程是:

1)包含OSS標頭檔:#include
2)開啟裝置檔案,返迴文件描述符
3)使用ioctl設定裝置的參數,控制裝置的特性
4)對於錄音,從裝置讀(read)
5)對於播放,向裝置寫(write)
6)關閉開啟的裝置

2.緩衝區設定的效能分析

在設定驅動內部的緩衝區時,存在一個矛盾:在音效卡驅動程式中,為了防止抖動的出現,保證播放的效能,設定了內部緩衝區-DMA buffer。在播放時,應用程式通過驅動程式首先將音頻資料從應用程式緩衝區-APP buffer,寫入到DMA buffer。接著,由DMA控制器把DMA buffer中的音頻資料發送到DAC(Digital-Analog Converter)。某些時刻CPU非常的繁忙,比如正在從磁碟讀入資料,或者正在重畫螢幕,沒有時間向DMA buffer放入新的音頻資料。DAC由於沒有輸入新的音頻資料,導致聲音播放的間斷,這就出現了聲音的抖動現象。此時,需要將DMA buffer設定的足夠大,使得DAC始終有資料播放。但是,DMA buffer的增大使得每次從APP buffer拷貝的時間也變長,導致了更大的播放延遲。這對於那些延遲敏感的應用場合,如與使用者有互動的音頻應用程式,就會出現問題。

對於這個矛盾,可以從兩個不同的方面分別著手解決。驅動程式採用多緩衝(Multi-buffering)的方式,即將大的DMA buffer分割成多個小的緩衝區,稱之為fragment,它們的大小相同。驅動程式開始時只需等待兩個fragment滿了就開始播放。這樣可以通過 增加fragment的個數來增加緩衝區的大小,但同時每個fragment被限制在合適的大小,也不影響時延。音頻驅動程式中的多緩衝機制一般會利用底 層DMA控制器的scatter-gather功能。

另一方面,應用程式也可指導驅動程式選擇合適大小的緩衝區,使得在沒有抖動的情況下,時延儘可能的小。特別的,應用程式將驅動程式中的緩衝通過 mmap映射到自己地址空間後,會以自己的方式來處理這些緩衝區(與驅動程式的不一定一致),這時應用程式往往會先根據自己的需要設定驅動程式中內部緩衝 區的大小。

在OSS的ioctl介面中,SNDCTL_DSP_SETFRAGMENT就是用來設定驅動程式內部緩衝區大小。具體的用法如下:

int param;
param = ( 0×0004 « 16) + 0x000a;
if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &param) == -1) {
…error handling…
}

參數param由兩部分組成:低16位為fragment的大小,此處0x000a表示fragment大小為2^0xa,即1024位元組;高16 位為fragment的數量,此處為0×0004,即4個fragement。設定好fragment參數後,通過ioctl的 SNDCTL_DSP_SETFRAGMENT命令調整驅動程式中的緩衝區。

為了給音頻程式的開發人員展示緩衝區配置對播放效果的影響,我們將對緩衝區配置與播放效能的關係進行測試。下面首先介紹測試的環境,包括測試方法的原理和測試結果的含義;接著針對兩種情況進行測試,並解釋測試的結果。

測試環境

測試是在PC機上進行的,具體的測試環境參見下表。

項目 參數
CPU PIII 800
記憶體 256M SDRAM
硬碟 ST 80G UDMA
顯卡 TNT2 m64 16M
音效卡 主板整合(工作在44.1KHz,立體聲,16bit的模式)
核心 Linux kernel 2.4.20(Redhat 9.0)

測試軟體(latencytest)由兩部分組成:音頻播放測試程式、系統運行負載模擬程式。(註:latencytest軟體主要目的是測試核心的時延,但這裡作為對不同緩衝配置進行比較的工具。)

音頻播放測試程式的工作流程見下面的代碼。為了保證音頻播放在調度上的優先性,音頻播放測試程式使用SCHED_FIFO調度策略(通過sched_setscheduler())。

while(1)
{
time1=my_gettime();
通過空迴圈消耗一定的CPU時間
time2=my_gettime();
write(audio_fd,playbuffer,fragmentsize);
time3=my_gettime();
}

my_gettime返回當前的時刻,在每個操作的開始和結束分別記錄下時間,就可以得到操作所花費的時間。audio_fd為開啟音訊裝置的檔案 描述符,playbuffer是應用程式中存放音頻資料的緩衝區,也就是APP buffer,fragmentsize為一個fragment的大小,write操作控制向驅動寫入一個fragment。空迴圈用來類比在播放音頻時 的CPU運算負載,典型的例子是合成器(synthesizer)即時產生波形後,再進行播放(write)。空迴圈消耗的時間長度設定為一個 fragment播放時延的80%。

相關指標的計算方法如下:

1) 一個fragment的播放時延(fragm.latency) = fragment大小/(頻率22)。以fragment大小為512位元組和以上的測試環境為例,一個fragment時延 = 512/(4410022) = 2.90ms[44100表示44.1KHz的採樣頻率,第一個2表示立體聲的兩個聲道,第二個2表示16bit為2個位元組]。
2) 一個fragment的傳輸時延 = 將一個fragment從APP buffer拷貝到DMA buffer的時延。
3) time3-time1 = 一次迴圈持續的時間 = 空迴圈消耗的CPU時間 + 一個fragment的傳輸時延。
4) time2-time1 = 空迴圈消耗的實際CPU時間(cpu latency)。

為了類比真實的系統運行情況,在測試程式播放音頻資料的同時,還運行了一個系統負載。一共設定5種負載情境,按順序分別是:

1) 高強度的圖形輸出(使用x11perf來類比大量的BitBlt操作)
2) 高強度對/proc檔案系統的訪問(使用top,更新頻率為0.01秒)
3) 高強度的磁碟寫(向硬碟寫一個大檔案)
4) 高強度的磁碟拷貝(將一個檔案拷貝到另一個地方)
5) 高強度的磁碟讀(從硬碟讀一個大檔案)

針對不同的系統負載情境,測試分別給出了各自的結果。測試結果以圖形的形式表示,測試結果中圖形的含義留待效能分析時再行解釋。

效能分析

下面,我們分別對兩種緩衝區的配置進行效能比較,

1) 情況1:fragment大小為512位元組,fragment個數為2。 測試結果1(2×512.html)
2) 情況2:fragment大小為2048位元組,fragment個數為4。 測試結果2(4×2048.html)

為了看懂測試結果,需要瞭解測試結果圖形中各種標識的含義:

1) 紅線:全部緩衝區的播放時延。全部緩衝區播放時延 = 一個fragment時延 x fragment的個數。對於測試的第一種情況,全部緩衝區時延 = 2.90ms x 2 = 5.8ms。
2) 白線:實際的調度時延,即一次迴圈的時間(time3-time1)。如果白線越過了紅線,則說明所有的緩衝區中音頻資料播放結束後,應用程式仍然沒有來得及將新的資料放入到緩衝區中,此時會出現聲音的丟失,同時overruns相應的增加1。
3) 綠線:CPU執行空迴圈的時間(即前面的time2-time1)。綠線的標稱值為fragm.latency x 80%。由於播放進程使用SCHED_FIFO調度策略,所以如果綠線所代表的時間變大,則說明出現了匯流排競爭,或者是系統長時間的處於核心中。
4) 黃線:一個fragment播放時延。白線應該接近於黃線。
5) 白色的between +/-1ms:實際的調度時延落入到fragm.latency +/-1ms範圍的比例。
6) 白色的between +/-2ms:實際的調度時延落入到fragm.latency +/-2ms範圍的比例。
7) 綠色的between +/-0.2ms:CPU的空迴圈時延波動+/-0.2ms範圍的比例(即落入到標稱值+/-0.2ms範圍的比例)。
8) 綠色的between +/-0.1ms:CPU的空迴圈時延波動+/-0.1ms範圍的比例(即落入到標稱值+/-0.1ms範圍的比例)。

第一種情況的緩衝區很小,每個fragment只有512位元組,總共的緩衝區大小為2 x 512 = 1024位元組。1024位元組只能播放5.8ms。根據OSS的說明,由於Unix是一個多任務的作業系統,有多個進程共用CPU,播放程式必須要保證選擇 的緩衝區配置要提供足夠的大小,使得當CPU被其它進程使用時(此時不能繼續向音效卡傳送新的音頻資料),不至於出現欠載的現象。欠載是指應用程式提供音頻 資料的速度跟不上音效卡播放的速度,這時播放就會出現暫停或滴答聲。因此,不推薦使用fragment大小小於256位元組的設定。從測試結果中看到,不管使 用那種系統負載,都會出現欠載的現象,特別是在寫硬碟的情況下,一共發生了14次欠載(overruns = 14)。

當然,對於那些即時性要求高的音頻播放程式,希望使用較小的緩衝區,因為只有這樣才能保證較小的時延。在上面的測試結果我們看到了欠載的現象,但 是,這並不完全是緩衝區過小所導致的。實際上,由於Linux核心是不可搶佔的,所以無法確知Linux在核心中停留的時間,因此也就無法保證以確定的速 度調度某個進程,即使現在播放程式使用了SCHED_FIFO調度策略。從這個角度來說,多媒體應用(如音頻播放)對作業系統核心提出了更高的要求。在目 前Linux核心的情況下,較小的調度時延可以通過一些專門的核心補丁(low-latency patch)達到。不過我們相信Linux2.6新核心會有更好的表現。

第二種情況的緩衝區要大得多,總共的緩衝區大小為4 x 2048 = 8192位元組。8192位元組可以播放0.046秒。從測試的圖形來看,結果比較理想,即使在系統負載較重的情況,仍然能夠基本保證播放時延的要求,而且沒有出現一次欠載的現象。

當然,並不是說緩衝區越大越好,如果繼續選擇更大的緩衝區,將會產生比較大的時延,對於即時性要求比較高的音頻流來說,是不能接受的。從測試結果中 可以看到,第二種配置的時延抖動比第一種配置要大得多。不過,在一般情況下,驅動程式會根據硬體的情況,選擇一個預設的緩衝區配置,播放程式通常不需要修 改驅動程式的緩衝區配置,而可以獲得較好的播放效果。

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

如果播放程式寫入的速度超過了DAC的播放速度,DMA buffer就會充滿了音頻資料。應用程式調用write時就會因為沒有閒置DMA buffer而被阻塞,直到DMA buffer出現空閑為止。此時,從某種程度來說,應用程式的推進速度依賴於播放的速度,不同的播放速度就會產生不同的推進速度。因此,有時我們不希望 write被阻塞,這就需要我們能夠知道DMA buffer的使用方式。

for (;;) {
audio_buf_info info;
/ Ask OSS if there is any free space in the buffer. /
if (ioctl(dsp,SNDCTL_DSP_GETOSPACE,&info) != 0) {
perror(“Unable to query buffer space”);
close(dsp);
return 1;
};
/ 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了。

4.DMA buffer的直接存取(mmap)

除了依賴於作業系統核心提供更好的調度效能,音頻播放應用程式也可以採用一些技術以提高音頻播放的即時性。繞過APP buffer,直接存取DMA buffer的mmap方法就是其中之一。

我們知道,將音頻資料輸出到音訊裝置通常使用系統調用write,但是這會帶來效能上的損失,因為要進行一次從使用者空間到核心空間的緩衝區拷貝。這 時,可以考慮利用mmap系統調用,獲得直接存取DMA buffer的能力。DMA控制器不停的掃描DMA buffer,將資料發送到DAC。這有點類似於顯卡對顯存的操作,大家都知道,GUI可以通過mmap將framebuffer(顯存)映射到自己的地 址空間,然後直接操縱顯存。這裡的DMA buffer就是音效卡的framebuffer。

理解mmap方法的最好方法是通過實際的例子, 代碼1(list1.c)。

代碼中有詳細的注釋,這裡只給出一些說明。

PlayerDMA函數的參數samples指向存放音頻資料的緩衝,rate/bits/channels分別說明音頻資料的採樣速率、每次採樣的位元、聲道數。

在開啟/dev/dsp以後,根據/rate/bits/channels參數的要求配置驅動程式。需要注意的是,這些要求並一定能得到滿足,驅動程式要根據自己的情況選擇,因此在配置後,需要重新查詢,擷取驅動程式真正使用的參數值。

在使用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方式。因此,在出現不相容的情況下,應用程式要能夠轉而去使用傳統的方式。

最後,為了能深入的理解mmap的實現原理,我們以某種音效卡驅動程式為例,介紹了其內部mmap函數時具體實現。 代碼2(list2.c)

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)。

5.結束語

當然,除了上面所討論的問題以外,音頻應用的開發還有很多實際的問題需要去面對,比如多路音頻流的合并,各種音頻檔案格式的開啟等等。

OSS音頻介面存在於Linux核心中許多年了,由於在體繫結構上有許多的局限性,在Linux 2.6核心中引入了一種全新的音頻體系和介面——ALSA(Advanced Linux Sound Architecture),它提供了很多比OSS更好的特性,包括完全的thread-safe和SMP-safe,模組化的設計,支援多個音效卡等等。 為了保持和OSS介面的相容性,ALSA還提供了OSS的模擬介面,使得那些為OSS介面開發的大量應用程式仍然能夠在新的ALSA體系下正常的工作。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.