一. 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空了(寫)時, 就會引發中斷.