Gstreamer 工具使用(二)

前面兩篇我們完成了兩件很重要的事情,第一是建立了編寫插件程式的環境和測試方法,第二是替插件裝好了進出水閥(sinkpadsrcpad)的格式和屬性,格式不合的資料進不來,也出不去。接下來我們要開始放水,讓資料流進這個插件。

gstreamer在處理資料的流動有兩種主要的模式,一個是「推」,一個是「拉」。兩種模式需要實作的routine不同,在對資料的操作(manipulation)上的重點也不一樣,很容易被搞得摸不清方向(其實我到現在還是有很多沒搞懂的地方…)。首先先解釋一下兩者的不同。

「推」模式就是由上游的插件控制資料的大小、流速,向下「推」到下游的插件,所以下游的插件並不會事先知道有多少資料會被送進來,它就必須先準備一個緩衝區來承接資料,然後判斷緩衝區裏的資料是否足夠拆解出一個壓縮單位的資料,夠的話就把資料切割出一個固定大小送給解碼器,剩下的資料要留著和下一筆流進來的資料做連接。

「拉」模式則是需要自己控制資料大小、流速,告訴上游的插件說自己要多少資料,從幾分幾秒開始讀,自己控制速度、大小等等變數,把資料「拉」進來。因爲要流進來的資料量(舉例來說,media-objectsizechunksizepacket 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如此處理狀態切換的理由是什麼。想像你手上有一個濾水器,一個水桶的污水和一個乾淨的水壺


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章