【關於alsa buffer】ALSA程式設計細節分析
二. 程式設計細節
按照上面的流程,其中有許多細節我們可以加以控制,這裡僅僅指出應用程式需要關心的:
1.1 裝置層次
在alsa驅動這一層,目前為止,抽象出了4層裝置:
一是hw:0,0;
二是plughw:0,0;
三是default:0;
四是default。
至於一是清楚了,二和二以上可以做資料轉換,以支援一個動態的範圍,比如你要播放7000hz的東西,那麼就可以用二和二以上的。而你用7000hz作為引數,去設定一,就會報錯。三和四,支援軟體混音。我覺得default:0表示對第一個音效卡軟體混音,default表示對整個系統軟體混音。
這裡提出兩點:
1.1.1 一般為了讓所有的程式都可以發音,為使用更多的預設策略,我們選用三和四,這樣少一些控制權,多一些方便。
1.1.2 對不同的層次的裝置,相同的函式,結果可能是不一樣的。比如,設定Hardware Parameters裡的period和buffer size,這個是對硬體的設定,所以,default和default:0這兩種裝置是不能設定的。
如果直接操作hw:0,0,那麼snd_pcm_writei只能寫如8的倍數的frame,比如16、24等,否則就會剩下一點不寫入而退回,而 default,就可以想寫多少就寫多少,我們也不必要關心裡面具體的策略。
[Loong]:之前都是使用了default,還真沒留意過這些裝置有何區別。
1.2 Hardware Parameters
說明:之所以叫做Hardware Parameters,是因為alsa這一層api是較為底層的,它允許使用者對audio interface和alsa-core兩層都做設定。其中
1.2.1 Sample rate: 取樣率
1.2.2 Sample format: 採用格式
1.2.3 Number of channels: 聲道數
1.2.4 Data access and layout:
簡單點說,在一個period以內(interleaved和non-interleaved是在一個period裡面排)
1.2.5 Interrupt interval:
中斷間隔,就是靠periods決定的,有函式來設定periods,也就是說這個hardware buffer在一次遍歷之內,要中斷多少次,來通知alsa-driver來寫入或讀走資料。比如buffer是8192個frame大,而 period設為4個frame大,那麼比如playback,則每當有4個frame大的hardware buffer空間空出,就會中斷,通知核心(alsa驅動)來寫如資料。這個是影響實時效果的關鍵。一般不用調整。
1.2.6 Buffer size:
hardware buffer的大小,如果alsa整套體系主要靠這個來做緩衝,那麼這個的大小,將影響緩衝效果,但是一般也不調整。
[Loong]:缺少buffer time、peroid time、peroid size等引數說明,這些引數一般情況下都要設定的。
1.3 Software Parameters
1.3.1 snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096)
這個software parameters僅用在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模式,那麼這個函式不必使用。
[Loong]:什麼是interrupt-driven模式?
1.3.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,保證音效卡的立即啟動。
1.4 what to do about xruns
xrun指的是,音效卡period一到,引發一箇中斷,告訴alsa驅動,要填入資料,或讀走資料,但是,問題在於alsa的讀取和寫入操作必須使用者呼叫writei和readi才會發生的,它不會去快取資料。如果上層沒有使用者呼叫writei和readi,那麼就會產生 overrun(錄製時,資料都滿了,還沒被alsa驅動讀走)和underrun(需要資料來播放,alsa驅動卻不寫入資料),統稱為xrun。
這個東西,需要用一些函式來設定,比如snd_pcm_sw_params_set_silence_threshold(),是針對playback 的,就是設定當xxx的情況下,就用silence來寫入hardware buffer。至於xxx情況,以及寫入多少silence,我都不是很清楚,還有,比如xrun到什麼情況下,可以停止這個裝置等等函式。一般情況下用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()來重新準備好裝置,然後再開始下一次讀寫。
1.5 transfer chunk size
這個應該是用不上的,我沒找到文件裡有用這個的。
[Loong]:這個其實是非常重要的,如果snd_pcm_writei/ snd_pcm_readi不是每次寫入chunk size資料的話,那麼放音/錄音不是你所期望的聲音。詳細見:http://alsa-project.org/main/index.php/FramesPeriods