一. alsa展現的三層結構:
(1)audio interface:
audio interface就是音效卡,它含有hardware buffer,注意,這個hardware buffer是在音效卡裡面,不是記憶體。
(2)computer:
這個指的是電腦的核心和驅動(驅動由alsa提供),當(1)的audio interfacce引發中斷,核心會捕捉到,再把處理移交alsa。
(3)application:
這個就是你寫的程式,你開闢一個buffer,比如playback,就交給alsa來play。
在上面的架構下,流程如下:
(1)playback:
application開闢一個buffer,填上資料,調用alsa介面,alsa把buffer資料複製到其驅動的空間,再把資料交給 hardware buffer。
(2)record:
同playback,相似的。
二. 細節:
按照上面的流程,其中有許多細節我們可以加以控制,這裡僅僅指出應用程式需要關心的:
1) 操作的裝置:
在alsa驅動這一層,目前為止,抽象出了4層裝置:
一是如hw:0,0,二是plughw:0,0,三是default:0,四是default至於 一是清楚了,二和二以上可以做資料轉換,以支援一個動態範圍,比如你要播放7000hz的東西,那麼就可以用二和二以上的。而你用7000hz作為參 數,去設定一,就會報錯。三和四,支援軟體混音。我覺得default:0表示對第一個音效卡軟體混音,default表示對整個系統軟體混音,由於我沒有 多音效卡,所以沒法實驗四的效果。
這裡提出兩點:
(1)一般為了讓所有的程式都可以發音,為使用更多的預設策略,我們選用三和四,這樣少一些控制權,多一些方便。
(2)對不同的層次的裝置,相同的函數,結果可能是不一樣的。
比如,設定Hardware Parameters裡的period和buffer size,這個是對硬體的設定,所以,default和default:0這兩種裝置是不能設定的。
比如,如果直接操作hw:0,0,那麼snd_pcm_writei只能寫如8的倍數的frame,比如16,24,否則就會剩下一點不寫入而退回,而 default,就可以想寫多少就寫多少,我們也不必要關心裏面具體的策略。
2) Hardware Parameters:
說明:之所以叫做Hardware Parameters,是因為alsa這一層API是較為底層的,它允許使用者對上面提到的三層結構的audio interface和computer兩層都做設定。其中對computer,也就是alsa驅動這一層的設定,叫做Software Parameters,而對audio interface(音效卡)的設定叫做Hardware Parameters。(當然,要設定hardware parameters,也肯定是通過alsa驅動來完成,只不過哪些參數是指導硬體的,哪些是指導alsa驅動的,分開設定了)
(1)Sample rate: 不用說了(這些,對於default裝置也能設的,上面已經說了)
(2)Sample format: 不用說了
(3)Number of channels: 不用說了
(4)Data access and layout:簡單點,就是說,在一個period以內,資料是按照channel1排完了再排channel2呢,還是一個frame一個frame的 來排(frame在alsa裡指的是一次採樣時間內,兩個channel的資料放一塊兒就是一個frame)。預設是第二種。
(5)Interrupt interval:中斷間隔,就是靠periods決定的,有函數來設定periods,也就是說這個hardware buffer在一次遍曆之內,要中斷多少次,來通知核心(最終是到alsa驅動)來寫入或讀走資料。比如buffer是8192個frame大,而 period設為4個frame大,那麼比如playback,則每當有4個frame大的hardware buffer空間空出,就會中斷,通知核心(alsa驅動)來寫入資料。這個是影響即時效果的關鍵!!!但是,我觀察的,我的電腦的預設period就是
4個frame,按16位元組,雙通道來算的話,也就是16個位元組!所以,預設就很即時了!!一般的即時程式已經夠用了!!一般不用調整。
(6)Buffer size:就是hardware buffer的大小,如果alsa整套體系主要靠這個來做緩衝,那麼這個的大小,將影響緩衝效果,但是一般也不調整。
3) Software Parameters:
(1)snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096)
這個僅用在interrupt-driven模式。這個模式是alsa驅動層的,不是硬體的interrupt。它的意思是,使用者使用 snd_pcm_wait()時,這個實際封裝的是系統的poll調用,表示使用者在等待,那麼在等待什麼呢?對於playback來講,就是等待下面的聲 卡的hardware buffer裡有一定數量的空間,可以放入新的資料了,對於record來講,就是等待下面音效卡新採集的資料達到了一定數量了。這個一定數量,就是用 snd_pcm_sw_params_set_avail_min來設定。單位是frame。實際運作,沒讀驅動代碼,不是很清楚,可能是alsa驅動根
據使用者設的這個參數,來設定Hardware Parameters裡面的period,也可能是不改變硬體的period,每次硬體中斷還是copy到自己的空間,然後資料積累到一定數量再 interrupt應用程式,使之從wait()出來。我不知道,也不必深究。
這種模式的使用,需要使用者在snd_pcm_wait()出來以後,調用一個平常的wirtei或readi函數,來寫入或讀取那個“一定數量”的資料。 如果使用者不用interrupt-driven模式,那麼這個函數不必使用。
(2)snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U)
這個函數指導什麼時候開啟audio interface的AD/DA,就是什麼時候啟動音效卡。
對於playback,假設第三個參數設為320,那麼就是說,當使用者調用writei,寫入的資料,將暫時存在alsa驅動空間裡,當這個資料量達到 320幀時,alsa驅動才開始將資料寫入hardware buffer,並啟動DA轉換。對於record,當使用者調用readi,這個資料量達到320幀時,alsa驅動才開始啟動AD轉換,捕捉資料。我一般 把它設為0,我沒試過非0,如果是非0, 我想第一次的writei和readi一定得夠數量才行,否則裝置不啟動。
這個對即時效果是需要的,將第三個參數設定為0,保證音效卡的立即啟動。
(3)what to do about xruns:
xrun指的是,音效卡period一過,引發一個中斷,告訴alsa驅動,要填入資料,或讀走資料,但是,問題在於,alsa的讀取和寫入操作,好象是必 須使用者調用writei和readi才會發生的,它不會去快取資料!!!,所以如果上層沒有使用者調用writei和readi,那麼就會產生 overrun(錄製時,資料都滿了,還沒被alsa驅動讀走)和underrun(需要資料來播放,alsa驅動卻不寫入資料),統稱為xrun。 我對它的理解是,不是一個period引發的中斷就叫做xrun,而是當整個hardware
buffer都被寫滿了(record時)或空了(play時),這個時候的中斷下的情況才指的是xrun。無所謂了,怎麼立解都行,不影響編程:)
這個東西,需要用一些函數來設定,比如snd_pcm_sw_params_set_silence_threshold(),是針對playback 的,就是設定當xxx的情況下,就用silence來寫入hardware buffer。至於xxx情況,以及寫入多少silence,我都不是很清楚,還有,比如xrun到什麼情況下,可以停止這個裝置等等函數。這個(3)的 涉及的參數,我都沒試過,一般情況下,就用alsa驅動的預設的xrun處理策略吧,等以後出了錯誤再說,而且例子裡也沒有提到。
但是,關於xrun,在編程時,最好這樣寫:
while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
snd_pcm_prepare(pcm_handle);
fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>\n");
}
就是說,如果這次讀/寫距離上次讀/寫,時間可能過長,那麼這次去讀/寫的時候,device已經xrun了,在不知道alsa驅動對xrun的預設策略 的情況下,最好調用snd_pcm_prepare()來重新準備好裝置,然後再開始下一次讀寫。我想,prepare()意思,可能相當於複位,不很清 楚。我想,windows下的那套API下的驅動,一定是已經有了一套對xrun的處理策略,使用者根本沒有介面可以調整它,我想它的策略比如 playback遇到underrun,也就是填入silence罷了。
(4)transfer chunk size:
這個應該是用不上的,我沒找到文檔裡有用這個的。
三. 編碼模式:
TODO LIST:以後把下面兩點補上:
(1)一般讀寫入模式:
(2)interrupt-driven模式:作者推薦這個模式,我決定用這個模式來做吧。
我推測的原因:它非常清晰的告訴了使用者,你需要讀取資料或寫入資料了! 這樣允許使用者即時的作出操作:比如現在讓使用者從wait()出來,使用者知道需要比如寫入資料了,它可以決定寫入真實的資料,或者寫如silence,或者 其他。而用一般讀寫入模式,你不會即時知道下層的需求!!所以,相當於你只能在你下一次讀/寫的時候,判斷有沒有xrun,其他你什麼都做不了:) (並且在即時性要求不高的情況下,設定一個較大的interrupt間隔,真箇alsa的效率會高一點呵呵。而且這種interrupt模式,可以使得代 碼簡單改下就可以用到其他採用interrupt的系統上,作者這米說的)
TODO: 再好好想想這個模式,其實我不確定實際使用上有什麼好?
四. 和windows API的比較:
windows audio介面的那些需要加入的buffer,屬於應用程式的buffer,windows靠使用者添加buffer queue來設定緩衝,而alsa並不提供介面讓使用者佈建應用程式層的緩衝區,緩衝區的作用,就是減弱或消除資料流或使用者操作偶爾過快或過慢造成的影響,所以 alsa是一定要有緩衝區的,我不知道是否,hardware buffer獨自承擔著alsa裡面的buffer角色?
alsa的writei和readi好象可以改成
TODO: 根據實際的編程結果,從效果看看,這樣做是否效果也很好。
五. 其他:
對音效卡的編程就是對兩個裝置進行指導的過程, 下面分3點敘述:
1. 關於Mixer編程.
我瞭解到的音效卡,有三個主要部分:
(1)mixer
(2)dsp(ad,da)
(3)buffer
可以認為它們按上面(1)(2)(3)的順序聯結起來, 而buffer和電腦匯流排間接相連.
目前認為的原理是這樣的:
(1)圖1表現了一個mixer, 圖2是很多個mixer, 但實際上, 音效卡裡至少有兩個mixer, 一個叫做input mixer, 一個叫做output mixer. input mixer接收外來類比訊號, output mixer接收dsp給它的類比訊號. 需要知道的是, mixer的輸入和輸出都是類比訊號, 輸入和輸出的線路也叫做混音通道, 我覺得這是物理線路的範疇, 這也就是為什麼一個程式調整mixer, 會影響另一個程式的原因, 因為我覺得它調整的是物理參數. 圖中藍色的方框就是可調節增益的地方(總之,
我認為它們是可供軟體調節的硬體線路參數)
(2)一個mixer的輸出可以作為另一個mixer的輸入. 比如考慮這樣一種情景, 怎樣混合CD和自己的歌聲? 我估計就是CD的數字訊號, 先到buffer, 經dsp的D/A轉換, 輸入"輸出混音器"的一個混音通道(source line), 再從destination line出來後, 不送往speaker, 而是送往"輸入混音器"混音通道, 這樣和人聲(從"輸入混音器"的microphone混音通道進入)一起混音, 再從destination line出來, 送往dsp做A/D轉換, 最後送往buffer.
而調節各個source line/destination line的增益, 以及route souce line和destination line就是mixer做的, 也是軟體可以調節的.
至於系統mixer面板上的master和pcm的區別, 我想pcm是dsp輸出的類比訊號, 也就是outmixer的source line的增益吧, 而master也許是outmixer的destination line的增益吧.這個其實找不到具體的音效卡結構圖是很難搞清楚的.
2. 關於dsp編程.
這個最好理解為是上述mixer體系中的一條預設的流程.對於使用者來講,dsp裝置就是完成基本的播放和錄音功能.
而使用者當然應該指導dsp完成工作, 就象指導mxier一樣. 使用者給dsp的指導參數主要是format(frame/s和bits/frame)和channel數, 要不然dsp怎樣能做D/A或A/D轉換呢.
3. 關於buffer.
涉及到的buffer有三個, 一是音效卡的buffer, 二是驅動的kernel buffer, 三是使用者指定的user buffer. 資料就是這樣一個接一個複製上來的,或一個一個複製下去的, 複製的訊號是音效卡的interrupt訊號. 當buffer滿了(讀)或buffer空了(寫)時, 就會引發中斷.