Gstreamer 在處理資料流有四個狀態:Null, Ready, Pause, Playing 按順序切換。也就是說,剛開始播放一個檔案時狀態變化是: Null –> Ready –> Pause –> Playing,當播放結束要釋放 pipeline 的順序就是原路走回去:Playing –> Pause –> Ready –> Null。我們寫的這個 mp3dec 插件是要把 mpeg audio decoder libmad 包裝為 gstreamer 插件,所以在開始播放檔案之前必須先把插件初始化 (比如說,設定 member variable 的初始值,初始化 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: 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),就要釋放資源。為了避免當主要的執行續(main thread)還在運作時,就因為收到「停止」的指令,從 PLAYING 切進 NULL ,把資源都給釋放掉,所以狀態轉換要分成兩個 switch-case 來處理。
我們可以試著討論一下 pipeline 如此處理狀態切換的理由是什麼。想像你手上有一個濾水器,一個水桶的污水和一個乾淨的水壺。當你要開始過濾污水的時候,你會不會先檢查水壺已經正確地接在濾水器的另一端了?要開始把污水往下倒時,會不會先把濾水器的開關打開,會吧?水壺和濾水器都「READY」了以後,才開始把污水往下倒。如果你使用濾水器的方法和我不同,請麻煩接受這個「由下而上READY」的想法,因為這是 gstreamer 在做開關控制的精神。
反過來看,如果要停止濾水,該是怎樣的順序?沒錯,把上面過濾的順序反過來。先停止倒污水,再關閉濾水器,最後才蓋上水壺。這樣的流程要怎麼用程式碼表達呢?
Gstreamer 只提供了一個函式來處理整個 pipeline 開始和結束的動作,在 mp3dec 這個例子中,就是我們註冊進去的 gst_mp3dec_change_state。只有一個函式的話,還要兼顧「開的時候下游先開,關的時候上游先關」的原則,最簡單的做法就是:播放初始時先替自己做初始化,準備好了以後通知上游。播放結束時先通知上游,再釋放自己的資源。所以,就會出現上面那段程式碼的寫法。
當 pipeline 的狀態被切換到 PLAYING 的時候,gstreamer 會開始做 preroll (提取影音資料進緩衝區),此時 _chain() 函式就會被觸發。主要的資料處理工作就是在 _chain() 裡完成,在「拉」模式的情況下,主要的資料處理工作則是在 _loop() 裡完成,以後會說明。因為 _chain() 裡面牽涉到 mpeg audio 解碼的程式,和 libmad 調用的部份、處理緩衝佇列等等比較複雜,將另開篇幅說明。