接著編輯 gstmp3dec.c (這個檔案也會不斷的修改),尋找 GstStaticPadTemplate ,會找到已經被自動產生的兩個 pad:sink_factory 和 src_factory 。還不知道 pad 是什麼沒關係,先想像它是插件的「開口」就好;上一篇文章我們有提到所謂的 pipeline 的箭頭是有方向性的,資料從源頭 (檔案、網路…等) 讀取出來後,從讀取的插件開始(即:file-source),到播送的插件出去(即:audio-sink 和 video-sink)。
透過插件的「開口」,資料才能在插件之間流動,就像濾水器的進水閥和出水閥,控制流進流出的水量、速度等等。不過 gstreamer 的水閥比較複雜一點,它必須再去判斷多媒體資料流的屬性,動態地決定輸入的多媒體檔案要用哪一個濾水器來承接。在這裡水閥就是GstPad ,而標示水閥的「屬性」就是 GstCaps。 進水閥我們稱為「sink pad」,出水閥我們稱為「source pad」,所以按上圖來看,file-source 沒有「安裝」「sink pad」是因為他在進水的那一條路是透過系統的 file I/O 來處理,不屬於 gstreamer pad 的範疇;同樣的 audio-sink 和 video-sink 沒有「安裝」「source pad」是因為在播放聲音和影像的部份是透過系統的 A/V renderer。而在中間的插件們,最基本的型態是一個進水(後稱 sinkpad )一個出水(後稱 srcpad ),像 decoder ;而 demuxer 要把 audio/video (或更多,視封裝格式而定) 資料拆開給各自的解碼器,就會有一個 sinkpad ,多個 srcpad ,因為責任重大,demuxer 寫起來也比較複雜。
解釋完插件和 pad、caps 之間的關係後,我們先透過程式去設定 mp3dec 的屬性。為求簡單,我們照抄 mad 的屬性就好,所以 sink_factory 和 src_factory 會改成如下
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/mpeg, / mpegversion=1, / layer=[1,3], / rate={8000,11025,12000,16000,22050,24000,32000,44100,48000},/ channels=[1,2]") ); static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw-int, / endianness=1234, / signed=true, / width=32, / depth=32, / rate={8000,11025,12000,16000,22050,24000,32000,44100,48000},/ channels=[1,2]") );重編後再用 gst-inspect 檢查一下就會發現在 Pad Templates 裡所描述 sinkpad 和 srcpad 的屬性都更新了,看的出來 mp3dec 接受的輸入格式是 mpeg1 audio layer3 的資料流,輸出 pcm 。設定這些屬性的目的就跟前述一樣,讓 gstreamer 在自動產生 pipeline 的時候可以按照我們設定的格式找到正確的插件來處理資料。(想像一下濾水器的進入出入閥標示著這個是濾工業用水、那個是濾農業用水、另一個是濾家庭用水,口徑多少、每單位吃水量多少…等等等,如此就算濾水器的功能一樣,而相對應的口徑、水量不符合,gstreamer 也不會接錯。)
然而,這邊設定的 caps 只是一個樣板,告訴上下插件輸入和輸出資料的格式及相關屬性的「範圍」,做為建立 pipeline 時參考的依據,當檔案開始播放時,真正的資料流的格式、屬性要等解碼完才知道。換言之,caps 的設定不一定是在 template 裡寫死就好,有時要另外動態產生運行時對應的 caps 並指派給 pad ( 包括 sinkpad 和 srcpad )。
在處理 sinkpad 和 srcpad 的程式都還沒寫之前就先設定 caps 其實並沒有具體的功能,但我覺得這樣解釋比較不會搞不清楚或混淆 caps 的目的和重要性。
當 caps 被設定好後,我們再來執行看看前面執行過的指令
gst-launch filesrc location=/path/to/file.mp3 ! mad ! mp3dec ! alsasink
有沒有發現結果不一樣了?此時音樂不會播,程式直接中斷並吐出一行字:
WARNING: erroneous pipeline: could not link mad0 to mp3dec0
原因很簡單,就是 gstreamer 發現 mad 的輸出閥 (srcpad) 和 mp3dec 的輸入閥 (sinkpad) 的 caps 不符合。所以跑都不跑就直接跳掉了。
3.推動 gstreamer plugin 的第三步
前面兩篇我們完成了兩件很重要的事情,第一是建立了編寫插件程式的環境和測試方法,第二是替插件裝好了進出水閥 (sinkpad 和 srcpad) 的格式和屬性,格式不合的資料進不來,也出不去。接下來我們要開始放水,讓資料流進這個插件。
gstreamer 在處理資料的流動有兩種主要的模式,一個是「推」,一個是「拉」。兩種模式需要實作的 routine 不同,在對資料的操作 (manipulation) 上的重點也不一樣,很容易被搞得摸不清方向(其實我到現在還是有很多沒搞懂的地方…)。首先先解釋一下兩者的不同。
「推」模式就是由上游的插件控制資料的大小、流速,向下「推」到下游的插件,所以下游的插件並不會事先知道有多少資料會被送進來,它就必須先準備一個緩衝區來承接資料,然後判斷緩衝區裡的資料是否足夠拆解出一個壓縮單位的資料,夠的話就把資料切割出一個固定大小送給解碼器,剩下的資料要留著和下一筆流進來的資料做連接。
「拉」模式則是需要自己控制資料大小、流速,告訴上游的插件說自己要多少資料,從幾分幾秒開始讀,自己控制速度、大小等等變數,把資料「拉」進來。因為要流進來的資料量 (舉例來說,media-object 的 size、chunk size、packet size) 自己可以控制,就不需要設計一個緩衝區來放資料。
通常,「拉」模式會用在 demuxer,而「推」模式用在其他插件,所以 gst-template 提供的例子是「推」模式的寫法。_chain() 函式就是讓上游插件把資料送進來的接口,當資料開始流動的時候 (完成啟動階段(activation stage)後,啟動的部份留待後述。) 會直接喚起初始階段時向 pad 註冊的 chain 函式,這個函式的介面 (GstPadChainFunction) 是已經被定義好的,其中一個變數是 GstBuffer 的指標,資料就被塞在這個指標所指向的記憶體空間。我們便可以透過註冊進去的函式,取得操作這段資料的 handle 。