摘要
在上一篇文章中,我們介紹了如何將多個element連接起來構造一個pipline,進行數據傳輸。那么GStreamer是通過何種方式保證element之間能正常的進行數據傳輸?今天就將介紹GStreamer是如何利用Pad來控制數據的傳輸。
Pad
我們知道,pad是element之間的數據的接口,一個src pad只能與一個sink pad相連。每個element可以通過pad過濾數據,接收自己支持的數據類型。Pad通過Pad Capabilities(簡稱為Pad Caps)來描述支持的數據類型。例如:
- 表示分辨率為300x200,幀率為30fps的RGB視頻的Caps:
“video/x-raw,format=RGB,width=300,height=200,framerate=30/1”
- 表示采樣位寬為16位,采樣率44.1kHz,雙通道PCM音頻的Caps:
“audio/x-raw,format=S16LE,rate=44100,channels=2”
- 或者直接描述編碼數據格式Voribis,VP8:
“audio/x-vorbis” "video/x-vp8"
一個Pad可以支持多種類型的Caps(比如一個video sink可以同時支持RGB或YUV格式的數據),同時可以指定Caps支持的數據范圍(比如一個audio sink可以支持1~48k的采樣率)。但是,在一個Pipeline中,Pad之間所傳輸的數據類型必須是唯一的。GStreamer在進行element連接時,會通過協商(negotiation)的方式選擇一個雙方都支持的類型。
因此,為了能使兩個Element能夠正確的連接,雙方的Pad Caps之間必須有交集,從而在協商階段選擇相同的數據類型,這就是Pad Caps的主要作用。在實際使用中,我們可以通過gst-inspect工具查看Element所支持的Pad Caps,從而才能知道在連接出錯時如何處理。
Pad Templates(模板)
我們曾使用gst_element_factory_make()接口創建Element,這個接口內部也會先創建一個Element 工廠,再通過工廠方法創建一個Element。由於大部分Element都需要創建類似的Pad,於是GStreame定義了Pad Template,Pad Template被包含中Element工廠中,在創建Element時,用於快速創建Pad。
Pad Template包含了一個Pad所能支持的所有Caps。通過Pad Template,我們可以快速的判斷兩個pad是否能夠連接(比如兩個elements都只提供了sink template,這樣的element之間是無法連接的,這樣就沒必要進一步判斷Pad Caps)。
由於Pad Template屬於Element工廠,所以我們可以直接使用gst-inspect查看其屬性,但Element實際的Pad會根據Element所處的不同狀態來進行實例化,具體的Pad Caps會在協商后才會被確定。
Pad Templates Capabilities例子
我們看一個 “gst-inspect-1.0 alsasink”的例子(不同平台會有差異):
Pad Templates: SINK template: 'sink' Availability: Always Capabilities: audio/x-raw format: S16LE layout: interleaved rate: [ 1, 48000 ] channels: [ 1, 2 ] audio/x-ac3 framed: true
alsasink只提供了一個sink template,可以創建sink pad,並且是一直存在的。支持兩種類型的音頻數據:16位的PCM(audio/x-raw),采樣率1~48k,1-2通道和AC3(audio/x-ac3)的幀數據。
再看一個 “gst-inspect-1.0 videotestsrc”的例子:
Pad Templates: SRC template: 'src' Availability: Always Capabilities: video/x-raw format: { I420, YV12, YUY2, UYVY, AYUV, RGBx, BGRx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, Y41B, Y42B, YVYU, Y444, v210, v216, NV12, NV21, NV16, NV24, GRAY8, GRAY16_BE, GRAY16_LE, v308, RGB16, BGR16, RGB15, BGR15, UYVP, A420, RGB8P, YUV9, YVU9, IYU1, ARGB64, AYUV64, r210, I420_10LE, I420_10BE, I422_10LE, I422_10BE, Y444_10LE, Y444_10BE, GBR, GBR_10LE, GBR_10BE } width: [ 1, 2147483647 ] height: [ 1, 2147483647 ] framerate: [ 0/1, 2147483647/1 ] video/x-bayer format: { bggr, rggb, grbg, gbrg } width: [ 1, 2147483647 ] height: [ 1, 2147483647 ] framerate: [ 0/1, 2147483647/1 ]
videotestsrc只提供了一個src template用於創建src pad,pad支持多種格式,可以通過參數指定輸出的數據類型或Caps Filter指定。
Pad Availability(有效性)
上面的例子中顯示的Pad Template都是一直存在的(Availability: Always),創建的Pad也是一直有效的。但有些Element會根據輸入數據以及后續的Element動態增加或刪除Pad,因此GStreamer提供了3種Pad有效性的狀態:Always,Sometimes,On request。
Always Pad
在element被初始化后就存在的pad,被稱為always pad或static pad。
Sometimes Pad
根據輸入數據的不同而產生的pad,被稱為sometimes pad,常見於各種文件格式解析器。例如用於解析mp4文件的qtdemux:"gst-inspect-1.0 qtdemux"
Pad Templates: SINK template: 'sink' Availability: Always Capabilities: video/quicktime video/mj2 audio/x-m4a application/x-3gp SRC template: 'video_%u' Availability: Sometimes Capabilities: ANY SRC template: 'audio_%u' Availability: Sometimes Capabilities: ANY SRC template: 'subtitle_%u' Availability: Sometimes Capabilities: ANY
只有我們從mp4文件中讀取數據時,我們才能知道這個文件中包含多少音頻,視頻,字幕,所以這些src pad都是sometimes pad。
Request Pad
按需創建的pad被稱為request pad,常見於合並或生成多路數據。例如,用於1到N轉換的tee:"gst-inspect-1.0 tee"
Pad Templates: ... SRC template: 'src_%u' Availability: On request Has request_new_pad() function: gst_tee_request_new_pad Capabilities: ANY
當我們需要將同一路視頻流同時進行顯示和存儲,這時候我們就需要用到tee,在創建tee element的時候,我們不知道pipeline需要多少個src pad,需要后續element來請求一個src pad。
示例代碼
GStreamer提供了gst-inspect工具來查看element所提供的Pad Templates,但無法查看element在不同狀態時其Pad所支持的數據類型,通過下面的代碼,我們可以看到Pad Caps在不同狀態下的變化。
#include <gst/gst.h> /* Functions below print the Capabilities in a human-friendly format */ static gboolean print_field (GQuark field, const GValue * value, gpointer pfx) { gchar *str = gst_value_serialize (value); g_print ("%s %15s: %s\n", (gchar *) pfx, g_quark_to_string (field), str); g_free (str); return TRUE; } static void print_caps (const GstCaps * caps, const gchar * pfx) { guint i; g_return_if_fail (caps != NULL); if (gst_caps_is_any (caps)) { g_print ("%sANY\n", pfx); return; } if (gst_caps_is_empty (caps)) { g_print ("%sEMPTY\n", pfx); return; } for (i = 0; i < gst_caps_get_size (caps); i++) { GstStructure *structure = gst_caps_get_structure (caps, i); g_print ("%s%s\n", pfx, gst_structure_get_name (structure)); gst_structure_foreach (structure, print_field, (gpointer) pfx); } } /* Prints information about a Pad Template, including its Capabilities */ static void print_pad_templates_information (GstElementFactory * factory) { const GList *pads; GstStaticPadTemplate *padtemplate; g_print ("Pad Templates for %s:\n", gst_element_factory_get_longname (factory)); if (!gst_element_factory_get_num_pad_templates (factory)) { g_print (" none\n"); return; } pads = gst_element_factory_get_static_pad_templates (factory); while (pads) { padtemplate = pads->data; pads = g_list_next (pads); if (padtemplate->direction == GST_PAD_SRC) g_print (" SRC template: '%s'\n", padtemplate->name_template); else if (padtemplate->direction == GST_PAD_SINK) g_print (" SINK template: '%s'\n", padtemplate->name_template); else g_print (" UNKNOWN!!! template: '%s'\n", padtemplate->name_template); if (padtemplate->presence == GST_PAD_ALWAYS) g_print (" Availability: Always\n"); else if (padtemplate->presence == GST_PAD_SOMETIMES) g_print (" Availability: Sometimes\n"); else if (padtemplate->presence == GST_PAD_REQUEST) g_print (" Availability: On request\n"); else g_print (" Availability: UNKNOWN!!!\n"); if (padtemplate->static_caps.string) { GstCaps *caps; g_print (" Capabilities:\n"); caps = gst_static_caps_get (&padtemplate->static_caps); print_caps (caps, " "); gst_caps_unref (caps); } g_print ("\n"); } } /* Shows the CURRENT capabilities of the requested pad in the given element */ static void print_pad_capabilities (GstElement *element, gchar *pad_name) { GstPad *pad = NULL; GstCaps *caps = NULL; /* Retrieve pad */ pad = gst_element_get_static_pad (element, pad_name); if (!pad) { g_printerr ("Could not retrieve pad '%s'\n", pad_name); return; } /* Retrieve negotiated caps (or acceptable caps if negotiation is not finished yet) */ caps = gst_pad_get_current_caps (pad); if (!caps) caps = gst_pad_query_caps (pad, NULL); /* Print and free */ g_print ("Caps for the %s pad:\n", pad_name); print_caps (caps, " "); gst_caps_unref (caps); gst_object_unref (pad); } int main(int argc, char *argv[]) { GstElement *pipeline, *source, *sink; GstElementFactory *source_factory, *sink_factory; GstBus *bus; GstMessage *msg; GstStateChangeReturn ret; gboolean terminate = FALSE; /* Initialize GStreamer */ gst_init (&argc, &argv); /* Create the element factories */ source_factory = gst_element_factory_find ("audiotestsrc"); sink_factory = gst_element_factory_find ("autoaudiosink"); if (!source_factory || !sink_factory) { g_printerr ("Not all element factories could be created.\n"); return -1; } /* Print information about the pad templates of these factories */ print_pad_templates_information (source_factory); print_pad_templates_information (sink_factory); /* Ask the factories to instantiate actual elements */ source = gst_element_factory_create (source_factory, "source"); sink = gst_element_factory_create (sink_factory, "sink"); /* Create the empty pipeline */ pipeline = gst_pipeline_new ("test-pipeline"); if (!pipeline || !source || !sink) { g_printerr ("Not all elements could be created.\n"); return -1; } /* Build the pipeline */ gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL); if (gst_element_link (source, sink) != TRUE) { g_printerr ("Elements could not be linked.\n"); gst_object_unref (pipeline); return -1; } /* Print initial negotiated caps (in NULL state) */ g_print ("In NULL state:\n"); print_pad_capabilities (sink, "sink"); /* Start playing */ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr ("Unable to set the pipeline to the playing state (check the bus for error messages).\n"); } /* Wait until error, EOS or State Change */ bus = gst_element_get_bus (pipeline); do { msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_STATE_CHANGED); /* Parse message */ if (msg != NULL) { GError *err; gchar *debug_info; switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_ERROR: gst_message_parse_error (msg, &err, &debug_info); g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); g_clear_error (&err); g_free (debug_info); terminate = TRUE; break; case GST_MESSAGE_EOS: g_print ("End-Of-Stream reached.\n"); terminate = TRUE; break; case GST_MESSAGE_STATE_CHANGED: /* We are only interested in state-changed messages from the pipeline */ if (GST_MESSAGE_SRC (msg) == GST_OBJECT (pipeline)) { GstState old_state, new_state, pending_state; gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); g_print ("\nPipeline state changed from %s to %s:\n", gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); /* Print the current capabilities of the sink element */ print_pad_capabilities (sink, "sink"); } break; default: /* We should not reach here because we only asked for ERRORs, EOS and STATE_CHANGED */ g_printerr ("Unexpected message received.\n"); break; } gst_message_unref (msg); } } while (!terminate); /* Free resources */ gst_object_unref (bus); gst_element_set_state (pipeline, GST_STATE_NULL); gst_object_unref (pipeline); gst_object_unref (source_factory); gst_object_unref (sink_factory); return 0; }
將源碼保存為basic-tutorial-3.c,執行下列命令可得到編譯結果:
$ gcc basic-tutorial-3.c -o basic-tutorial-3 `pkg-config --cflags --libs gstreamer-1.0`
源碼分析
輸出可讀信息
print_field, print_caps and print_pad_templates_information實現類似功能,打印GStreamer的數據結構,可以查看相應GStreamer GstCaps 接口了解更多信息。
/* Shows the CURRENT capabilities of the requested pad in the given element */ static void print_pad_capabilities (GstElement *element, gchar *pad_name) { GstPad *pad = NULL; GstCaps *caps = NULL; /* Retrieve pad */ pad = gst_element_get_static_pad (element, pad_name); if (!pad) { g_printerr ("Could not retrieve pad '%s'\n", pad_name); return; } /* Retrieve negotiated caps (or acceptable caps if negotiation is not finished yet) */ caps = gst_pad_get_current_caps (pad); if (!caps) caps = gst_pad_query_caps (pad, NULL); /* Print and free */ g_print ("Caps for the %s pad:\n", pad_name); print_caps (caps, " "); gst_caps_unref (caps); gst_object_unref (pad); }
因為我們使用的source和sink都具有static(always)pad,所以這里使用gst_element_get_static_pad()獲取Pad, 其他情況可以使用gst_element_foreach_pad()或gst_element_iterate_pads()獲取動態創建的Pad。
接着使用gst_pad_get_current_caps()獲取pad當前的caps,根據不同的element狀態會有不同的結果,甚至可能不存在caps。如果沒有,我們通過gst_pad_query_caps()獲取當前可以支持的caps,當element處於NULL狀態時,這個caps為Pad Template所支持的caps,其值可隨狀態變化而變化。
獲取Element工廠
/* Create the element factories */ source_factory = gst_element_factory_find ("audiotestsrc"); sink_factory = gst_element_factory_find ("autoaudiosink"); if (!source_factory || !sink_factory) { g_printerr ("Not all element factories could be created.\n"); return -1; } /* Print information about the pad templates of these factories */ print_pad_templates_information (source_factory); print_pad_templates_information (sink_factory); /* Ask the factories to instantiate actual elements */ source = gst_element_factory_create (source_factory, "source"); sink = gst_element_factory_create (sink_factory, "sink");
在使用gst_element_factory_make()接口創建element時,應用不需要關心element工廠。在這里,由於Pad Template數據Element工程,因此我們首先根據工廠名創建了相應工廠實例(GstElementFactory ),再由其獲取Pad Template以及創建element。
此處使用gst_element_factory_find()查找"audiotestsrc"工廠,再通過gst_element_factory_create()創建source element。以前使用的gst_element_factory_make()是gst_element_factory_find() + gst_element_factory_create()的簡化版。
處理State-Changed消息
Pipeline的創建過程與其他示例相同,此例新增了狀態變化的處理。
case GST_MESSAGE_STATE_CHANGED: /* We are only interested in state-changed messages from the pipeline */ if (GST_MESSAGE_SRC (msg) == GST_OBJECT (pipeline)) { GstState old_state, new_state, pending_state; gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); g_print ("\nPipeline state changed from %s to %s:\n", gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); /* Print the current capabilities of the sink element */ print_pad_capabilities (sink, "sink"); } break;
因為我們在gst_bus_timed_pop_filtered()中加入了GST_MESSAGE_STATE_CHANGED,所以我們會收到狀態變化的消息。在狀態變化時,輸出sink element的pad caps中當前狀態的信息。
輸出分析
Pad Templates for Audio test source: SRC template: 'src' Availability: Always Capabilities: audio/x-raw format: { S16LE, S32LE, F32LE, F64LE } layout: interleaved rate: [ 1, 2147483647 ] channels: [ 1, 2 ] Pad Templates for Auto audio sink: SINK template: 'sink' Availability: Always Capabilities: ANY
首先是“audiotestsrc”和“autoaudiosink”的pad templates信息,這個與gst-inspect的輸出相同。
In NULL state: Caps for the sink pad: ANY
NULL狀態為Element的初始化狀態,此時,“autoaudiosink”的sink pad caps與Pad Template相同,支持所有的格式。
Pipeline state changed from NULL to READY: Caps for the sink pad: audio/x-raw format: { S16LE, S16BE, F32LE, F32BE, S32LE, S32BE, S24LE, S24BE, S24_32LE, S24_32BE, U8 } layout: interleaved rate: [ 1, 2147483647 ] channels: [ 1, 32 ] audio/x-alaw rate: [ 1, 2147483647 ] channels: [ 1, 32 ] audio/x-mulaw rate: [ 1, 2147483647 ] channels: [ 1, 32 ]
狀態從NULL轉到READY時,GStreamer會獲取音頻輸出設備所支持的所有類型,這里可以看到sink pad caps列出了輸出設備所能支持的類型。
Pipeline state changed from READY to PAUSED: Caps for the sink pad: audio/x-raw format: S16LE layout: interleaved rate: 44100 channels: 1 Pipeline state changed from PAUSED to PLAYING: Caps for the sink pad: audio/x-raw format: S16LE layout: interleaved rate: 44100 channels: 1
狀態從READY轉到PAUSED時,GStreamer會協商一個所有element都支持的類型。當進入PLAYING狀態時,sink會采用協商后的類型進行數據傳輸。
總結
在本教程中,我們掌握了:
- 什么是Pad Capabilities 和 Pad Template Capabilities。
- Pad有效性的類別。
- 如何通過gst_pad_get_current_caps() 和 gst_pad_query_caps()獲取當前的caps。
- Pad Capabilities在element不同狀態下的變化。
- 如何使用gst-inspect工具查看element的Pad Caps。
引用
https://gstreamer.freedesktop.org/documentation/tutorials/basic/media-formats-and-pad-capabilities.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/tutorials/basic/multithreading-and-pad-availability.html
https://gstreamer.freedesktop.org/documentation/gstreamer/gstcaps.html?gi-language=c