前面兩篇我們完成了兩件很重要的事情,第一是建立了編寫插件程式的環境和測試方法,第二是替插件裝好了進出水閥(sinkpad和srcpad)的格式和屬性,格式不合的資料進不來,也出不去。接下來我們要開始放水,讓資料流進這個插件。
gstreamer在處理資料的流動有兩種主要的模式,一個是「推」,一個是「拉」。兩種模式需要實作的routine不同,在對資料的操作(manipulation)上的重點也不一樣,很容易被搞得摸不清方向(其實我到現在還是有很多沒搞懂的地方…)。首先先解釋一下兩者的不同。
「推」模式就是由上游的插件控制資料的大小、流速,向下「推」到下游的插件,所以下游的插件並不會事先知道有多少資料會被送進來,它就必須先準備一個緩衝區來承接資料,然後判斷緩衝區裏的資料是否足夠拆解出一個壓縮單位的資料,夠的話就把資料切割出一個固定大小送給解碼器,剩下的資料要留著和下一筆流進來的資料做連接。
「拉」模式則是需要自己控制資料大小、流速,告訴上游的插件說自己要多少資料,從幾分幾秒開始讀,自己控制速度、大小等等變數,把資料「拉」進來。因爲要流進來的資料量(舉例來說,media-object的size、chunksize、packet size)自己可以控制,就不需要設計一個緩衝區來放資料。
通常,「拉」模式會用在 demuxer,而「推」模式用在其他插件,所以gst-template提供的例子是「推」模式的寫法。_chain()函式就是讓上游插件把資料送進來的接口,當資料開始流動的時候(完成啓動階段(activationstage)後,啓動的部份留待後述。)會直接喚起初始階段時向pad註冊的chain 函式,這個函式的介面(GstPadChainFunction)是已經被定義好的,其中一個變數是GstBuffer的指標,資料就被塞在這個指標所指向的記憶體空間。我們便可以透過註冊進去的函式,取得操作這段資料的handle。
Gstreamer 在處理資料流有四個狀態:Null,Ready, Pause, Playing按順序切換。也就是說,剛開始播放一個檔案時狀態變化是:Null –> Ready –> Pause –>Playing,當播放結束要釋放pipeline的順序就是原路走回去:Playing–> Pause –> Ready –> Null。我們寫的這個mp3dec插件是要把mpegaudio decoder libmad包裝爲 gstreamer插件,所以在開始播放檔案之前必須先把插件初始化(比如說,設定membervariable的初始值,初始化 gstreamer的其他元件等等),當然,也要先初始化libmad。初始化的動作一般來說,應該要放在Null轉到Ready的階段,或 Ready 轉到Pause 的階段,絕對不可能是在Pause轉到Playing的階段,因爲 Pause 和Playing 兩個狀態是切換播放模式用的(如:暫停、快進、Seeking)。
到目前爲止都很抽象,我們走進源碼來看就會好一點。
爲了處理剛提到的狀態切換,我們要註冊一個_change_state()函式。
1: static GstStateChangeReturn
2: gst_mp3dec_change_state(GstElement* element, GstStateChange transition)
3: {
4: GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
5: Gstmp3dec *dec;
6: dec = GST_MP3DEC(element);
7:
8: switch(transition)
9: {
10: case GST_STATE_CHANGE_NULL_TO_READY:
11: mad_frame_init(&dec->frame);
12: mad_stream_init(&dec->stream);
13: mad_synth_init(&dec->synth);
14: break;
15: default:
16: break;
17: }
18:
19: ret = parent_class->change_state(element, transition);
20: if(ret == GST_STATE_CHANGE_FAILURE)
21: return ret;
22:
23: switch(transition)
24: {
25: case GST_STATE_CHANGE_READY_TO_NULL:
26: gst_mp3dec_reset(dec);
27: break;
28: default:
29: break;
30: }
31: return ret;
32: }
33:
34: static void gst_mp3dec_clas_init()
35: {
36: ...
37: gstelement_class->change_state = gst_mp3dec_change_state;
38: ...
39: }
如剛所說,當狀態從 NULL 轉到READY時(GST_STATE_CHANGE_NULL_TO_READY),插件要做初始化,配置記憶體等。反過來當狀態從READY轉到NULL時(GST_STATE_CHANGE_READY_TO_NULL),就要釋放資源。爲了避免當主要的執行續(mainthread)還在運作時,就因爲收到「停止」的指令,從PLAYING切進NULL,把資源都給釋放掉,所以狀態轉換要分成兩個switch-case來處理。
我們可以試著討論一下 pipeline如此處理狀態切換的理由是什麼。想像你手上有一個濾水器,一個水桶的污水和一個乾淨的水壺