pad定義
pad相當於element的接口,各個element就是通過pad連接進行傳輸數據,同時pad會通過caps限制特定的數據類型通過,只有當兩個pad的caps數據類型一致時纔可以建立連接。那麼pad在element又是怎麼創建以及使用的呢,下面一起來分析一下。
在理解pad的定義之前,我們先來看看,pad都有那些信息。
Pad Templates:
SINK template: 'sink' ------>sink pad:數據流入
Availability: Always ------>pad時效性:永久型
Capabilities: ------>pad支持的caps
video/quicktime
video/mj2
audio/x-m4a
application/x-3gp
SRC template: 'video_%u' ------>src pad:數據流出
Availability: Sometimes ------>pad時效性:隨機型
Capabilities:
ANY
SRC template: 'audio_%u'
Availability: Sometimes
Capabilities:
ANY
SRC template: 'subtitle_%u'
Availability: Sometimes
Capabilities:
ANY
從上面可以看到,每個pad,都會有以下屬性:padname、direction、presence、caps。
- padname:pad名稱
- direction:pad的輸入輸出方向,有src和sink兩種
- presence:pad的時效性,有永久型GST_PAD_ALWAYS、隨機型GST_PAD_SOMETIMES、請求型GST_PAD_REQUEST,請求型的僅在
gst_element_request_pad()
調用,隨機型的則是會根據不同的輸入數據使用不同的pad - caps:pad支持的功能
那麼,一般情況我們是如何定義一個pad呢,我們以qtdemux的sink pad爲例進行說明。在qtdemux.c可以看到以下定義:
static GstStaticPadTemplate gst_qtdemux_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/quicktime; video/mj2; audio/x-m4a; "
"application/x-3gp")
);
通過上面我們定義了一個靜態的pad模板,而GST_STATIC_PAD_TEMPLATE
宏展開只是簡單的花括號{}。那麼,在這裏定義了pad模板,哪裏使用,與element綁定呢?往下看。
在qtdemux的class_init()函數中,可以看到以下代碼:
static void
gst_qtdemux_class_init (GstQTDemuxClass * klass)
{
gobject_class = (GObjectClass *) klass;
...
/* 就是在這裏完成相應的pad添加 */
gst_element_class_add_static_pad_template (gstelement_class,
&gst_qtdemux_sink_template);
...
}
其實在gst_element_class_add_static_pad_template()
函數中,也是通過調用gst_element_class_add_pad_template()
完成相應的pad添加。pad具體的添加流程,又是怎樣的呢,在GstElementClass中,有一個GList *padtemplates
,所以,在gst_element_class_add_pad_template()函數中就是簡單的將pad添加到padtemplates
,這樣就完成了elementClass的pad添加。
同時,在element的實例初始化函數init中,也會通過gst_element_add_pad()
函數添加pad到element。
static void
gst_qtdemux_init (GstQTDemux * qtdemux)
{
...
gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), qtdemux->sinkpad);
...
}
在gst_element_add_pad()函數中,將會檢查pad的name是否已經在該element存在,如何沒有,將會把pad的parent設置爲該element,同時根據pad的direction類型添加到element->srcpads或者element->sinkpads,最終都會保存到element->pads。
pad連接
gst_pad_link_unchecked跟gst_pad_link的區別?一個是不檢查屬性,一個檢查屬性。
GST_OBJECT_LOCK (srcpad); 多線程的情況,要加鎖。
在上面我們已經說過,pad相當與element的接口,那麼element間的連接,實質上就是pad間的連接,caps適配,那麼這個連接流程又是怎樣的呢,讓我們來一起探討一下。
在應用程序中,可以通過gboolean gst_element_link (GstElement * src, GstElement * dest)
函數完成element連接,最終會調用到gst_element_link_pads_full (src, srcpadname, dest, destpadname, GST_PAD_LINK_CHECK_DEFAULT)
。那麼,下面我們來分析一下gst_element_link_pads_full()函數。
因爲gst_element_link_pads_full()函數代碼量有點大,就不上代碼,分析一下關鍵函數調用。
gboolean
gst_element_link_pads_full (GstElement * src, const gchar * srcpadname,
GstElement * dest, const gchar * destpadname, GstPadLinkCheck flags)
{
/* 一般的,srcpadname爲NULL,所以會獲取src的所有pads,這個就是在實例初始化時添加的,
1. 如果srcpadname不爲空,則會根據相應的pad name獲取pad */
srcpads = GST_ELEMENT_PADS (src);
srcpad = srcpads ? GST_PAD_CAST (srcpads->data) : NULL;
...
/* 同理的,dest進行相應的操作 */
destpads = GST_ELEMENT_PADS (dest);
destpad = destpads ? GST_PAD_CAST (destpads->data) : NULL;
...
/* 接下來將會檢查src pads中,是否有pad是還沒有連接的,沒有連接的,將會與dest的pads進行逐一適配,
2. 如果循環src的發現都沒有,又將會循環dest的pad,進行相同的操作,
3. 最終都沒有合適的pad相連,將會查看,是否存在請求型pad,再進行嘗試連接 */
do {
...
if ((GST_PAD_DIRECTION (srcpad) == GST_PAD_SRC) &&
(GST_PAD_PEER (srcpad) == NULL)) {
/* 獲取可以相連的pad */
temp = gst_element_get_compatible_pad (dest, srcpad, NULL);
...
/* 進行pad連接操作 */
if (temp && pad_link_maybe_ghosting (srcpad, temp, flags)) {
...
}
...
}
...
}while (srcpads);
...
}
在gst_element_get_compatible_pad(GstElement * element, GstPad * pad, GstCaps * caps)
中,主要完成以下操作:
1. 將會通過gst_element_iterate_sink_pads(element)
或者gst_element_iterate_src_pads (element)
函數element相應的pad;
2. 通過temp = gst_pad_query_caps (pad, NULL)
得到pad的所有caps;
3. 同樣的,通過temp = gst_pad_query_caps (current, NULL)
得到element pad的所有caps;
4. 通過compatible = gst_caps_can_intersect (temp, intersection)
檢查,pad的caps與element pad caps數據類型是否一致;
5. 如果第4步返回的是一致,則從element中找到了可以與pad連接的element pad,返回element pad;
6. 如果循環之後都沒有找到,將會嘗試請求型的pad,最終都沒有將會返回NULL。
在調用gst_element_get_compatible_pad()函數之後,得到dest中可以與srcpad相連的destpad,接下來將會通過pad_link_maybe_ghosting (srcpad, temp, flags)
函數進行pad連接,詳細操作如下:
1. 先通過prepare_link_maybe_ghosting()
函數檢查pad的element是否存在同一個parent,需要存在同一個parent纔可以進行下一步連接,也正是這個函數限制了,element需要在同一個pipeline纔可以link;
2. 通過gst_pad_link_full (src, sink, flags)
link src pad和sink pad;
a. 通過parent發送GST_STRUCTURE_CHANGE_TYPE_PAD_LINK消息,這裏發送的消息,bin(pipelin)將會接收到該消息;
gst_element_post_message (parent,
gst_message_new_structure_change (GST_OBJECT_CAST (sinkpad),
GST_STRUCTURE_CHANGE_TYPE_PAD_LINK, parent, TRUE));
b. 通過檢查pad可用之後,將會通過GST_PAD_PEER設置srcpad和sinkpad,此時相當於已經link;
c. 通過schedule_events (srcpad, sinkpad)
函數檢查srcpad、sinkpad是否有不一樣的event,sink不存在的event都需要做好標記received = FALSE;
d. 通過g_signal_emit()
函數發送gst_pad_signals[PAD_LINKED]
信號,完成連接,這裏發送的信號,又是在實例初始化時,設置各自的pad的接收設置,部分pad並沒有設置接收函數,採用默認的,也將是接收就釋放;
e. 最後通過gst_pad_send_event (srcpad, gst_event_new_reconfigure ())
發送相應的event,srcpad發送的事件,將會在srcpad的event_function處理,這個函數看看相應的element有沒有重載,如果沒有,基本上就是接收到事件就進行事件釋放;
在gst_pad_link_full()
函數完成pad link之後,回到gst_element_link_pads_full()
函數,在完成link之後,其實就是釋放之前申請的object,返回link成功,至此,element link完成。上面介紹的,是通過dest element連接srcpad,如果這一步沒有成功的link,又將會進行src element連接destpad,如果以上兩步都沒有完成相應的element link,則是按照之前說的,嘗試通過請求型的pad進行連接,由於過程都類似,就不再進行分析。
PUSH
ement間的數據的傳輸都是通過pad的,那麼,究竟是如何進行數據傳遞的呢,下面我們來看看。
pad具有兩種模式,分別是PUSH和PULL。PUSH模式,就是由上游element控制傳輸數據的大小與速度,將數據推送到下游element,所以下游的element一般都會設置一個緩衝區來接收數據,PUSH模式一般是通過gst_pad_push (GstPad * pad, GstBuffer * buffer)函數完成數據傳遞的操作;而PULL模式呢,它就是由下游的element告訴上游element需要的數據量,PULL模式通過gst_pad_pull_range (GstPad * pad, guint64 offset, guint size, GstBuffer ** buffer)函數完成數據的獲取。但是實際究竟是如何完成的呢,繼續看代碼。
在element處理完數據之後,需要將數據傳遞給下游,那麼,就是通過gst_pad_push()函數完成這個操作,看看它的具體實現,而gst_pad_push()函數也是通過調用gst_pad_push_data (pad, GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH, buffer)函數完成操作的。 在gst_pad_push_data()函數中,在檢查pad的狀態、模式是否正確之後,通過宏定義GST_PAD_PEER取得pad的下游element sink pad,然後調用gst_pad_chain_data_unchecked (peer, type, data),最後,將會在該函數中,將數據push到下游。
static inline GstFlowReturn
gst_pad_chain_data_unchecked (GstPad * pad, GstPadProbeType type, void *data)
{
...
{
GstPadChainFunction chainfunc;
/* 通過宏GST_PAD_CHAINFUNC獲取chainfunc */
if (G_UNLIKELY ((chainfunc = GST_PAD_CHAINFUNC (pad)) == NULL))
goto no_function;
/* 調用chainfunc函數 */
ret = chainfunc (pad, parent, GST_BUFFER_CAST (data));
}
...
}
具體的是怎麼傳輸數據的呢,GST_PAD_CHAINFUNC獲取到的是什麼函數呢,需要注意的,該函數的pad是peer喔,也就是說,這個是下游的sink pad,通過GST_PAD_CHAINFUNC獲取到的,就是下游element在類實例初始化init函數通過宏gst_pad_set_chain_function設置的GstPadChainFunction。看到這裏,不知道大家明白了沒,PUSH模式,實質就是在上游element的src pad中,通過GST_PAD_PEER獲取到下游element的sink pad,然後調用下游sink pad的chain函數,這樣來達到數據傳遞,簡單來說就是函數指針鏈表,從上往下逐個調用,直至最後,調用之後再逐個返回,push數據完成。