1. 程式人生 > >【關於alsa buffer】ALSA程式設計細節分析

【關於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兩層都做設定。其中

對alsa-core設定,叫做Software Parameters,而對audio interface的設定叫做Hardware Parameters。(當然要設定hardware parameters,也肯定是通過alsa驅動來完成,只不過哪些引數是指導硬體的,哪些是指導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裡面排)

,資料是按照channel1排完了再排channel2呢,還是一個frame一個frame的來排(frame在alsa裡指的是一次取樣時間內,兩個channel的資料放一塊兒就是一個frame)。預設是第二種。

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