原文链接:
http://blog.csdn.net/sakulafly/article/details/20936067
本教程介绍pipeline的一种新的创建方式——在运行中创建,而不是在运行前一次性的创建结束。
介绍
在这篇教程里的pipeline并非在运行前就全部创建结束的。
放松一下,这样做没有任何问题。如果我们不进行更深入的处理,那么数据在到达pipeline的末尾时就直接丢弃了,当然,我们肯定会进行深入处理的。。。
在这个例子中,我们会打开一个已经包含了音视频的文件(container file)。
负责打开这样的容器文件的element叫做demuxer,我们常见的容器格式包括MKV、QT、MOV、Ogg还有ASF、WMV、WMA等等。
在一个容器中可能包含多个流(比如:一路视频,两路音频),
demuxer会把他们分离开来,然后从不同的输出口送出来。
这样在pipeline里面的不同的分支可以处理不同的数据。
在GStreamer里面有个术语描述这样的接口——pad(GstPad)。
Pad分成
sink pad——数据从这里进入一个element
source pad——数据从这里流出element。
很自然的,(这也是这两种pad命名的来源)
source element仅包含source pad,
sink element仅包含sink pad,
而过滤器两种pad都包含。
一个demuxer包含一个sink pad和多个source pad,数据从sink pad输入然后每个流都有一个source pad。
为了完整起见,给出一张示意图,图中有一个demuxer和两个分支,一个处理音频一个处理视频。
请注意,这不是本教程pipeline的示意图。
这里主要复杂在demuxer在没有看到容器文件之前无法确定需要做的工作,不能生成对应的内容。
也就是说,demuxer开始时是没有source pad给其他element连接用的。
解决方法是
只管建立pipeline,让source和demuxer连接起来,然后开始运行。
当demuxer接收到数据之后它就有了足够的信息生成source pad。
这时我们就可以继续把其他部分和demuxer新生成的pad连接起来,生成一个完整的pipeline。
简单起见,在这个例子中,我们仅仅连接音频的pad而不处理视频的pad。
动态Hello World
- #include <gst/gst.h>
-
-
- typedef struct _CustomData {
- GstElement *pipeline;
- GstElement *source;
- GstElement *convert;
- GstElement *sink;
- } CustomData;
-
-
- static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);
-
- int main(int argc, charchar *argv[]) {
- CustomData data;
- GstBus *bus;
- GstMessage *msg;
- GstStateChangeReturn ret;
- gboolean terminate = FALSE;
-
-
- gst_init (&argc, &argv);
-
-
- data.source = gst_element_factory_make ("uridecodebin", "source");
- data.convert = gst_element_factory_make ("audioconvert", "convert");
- data.sink = gst_element_factory_make ("autoaudiosink", "sink");
-
-
- data.pipeline = gst_pipeline_new ("test-pipeline");
-
- if (!data.pipeline || !data.source || !data.convert || !data.sink) {
- g_printerr ("Not all elements could be created.\n");
- return -1;
- }
-
-
-
- gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert , data.sink, NULL);
- if (!gst_element_link (data.convert, data.sink)) {
- g_printerr ("Elements could not be linked.\n");
- gst_object_unref (data.pipeline);
- return -1;
- }
-
-
- g_object_set (data.source, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);
-
-
- g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
-
-
- ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
- if (ret == GST_STATE_CHANGE_FAILURE) {
- g_printerr ("Unable to set the pipeline to the playing state.\n");
- gst_object_unref (data.pipeline);
- return -1;
- }
-
-
- bus = gst_element_get_bus (data.pipeline);
- do {
- msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
- GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
-
-
- 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:
-
- if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
- GstState old_state, new_state, pending_state;
- gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
- g_print ("Pipeline state changed from %s to %s:\n",
- gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
- }
- break;
- default:
-
- g_printerr ("Unexpected message received.\n");
- break;
- }
- gst_message_unref (msg);
- }
- } while (!terminate);
-
-
- gst_object_unref (bus);
- gst_element_set_state (data.pipeline, GST_STATE_NULL);
- gst_object_unref (data.pipeline);
- return 0;
- }
-
-
- static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
- GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
- GstPadLinkReturn ret;
- GstCaps *new_pad_caps = NULL;
- GstStructure *new_pad_struct = NULL;
- const gchar *new_pad_type = NULL;
-
- g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src));
-
-
- if (gst_pad_is_linked (sink_pad)) {
- g_print (" We are already linked. Ignoring.\n");
- goto exit;
- }
-
-
- new_pad_caps = gst_pad_get_caps (new_pad);
- new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
- new_pad_type = gst_structure_get_name (new_pad_struct);
- if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
- g_print (" It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
- goto exit;
- }
-
-
- ret = gst_pad_link (new_pad, sink_pad);
- if (GST_PAD_LINK_FAILED (ret)) {
- g_print (" Type is '%s' but link failed.\n", new_pad_type);
- } else {
- g_print (" Link succeeded (type '%s').\n", new_pad_type);
- }
-
- exit:
-
- if (new_pad_caps != NULL)
- gst_caps_unref (new_pad_caps);
-
-
- gst_object_unref (sink_pad);
- }
工作流程
-
- typedef struct _CustomData {
- GstElement *pipeline;
- GstElement *source;
- GstElement *convert;
- GstElement *sink;
- } CustomData;
在这里我们存下了所有需要的局部变量,因为本教程中会有回调函数,所以我们把所有的数据组织成一个struct,这样比较方便调用。
-
- static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);
这是一个声明,后面会使用这个API。
-
- data.source = gst_element_factory_make ("uridecodebin", "source");
- data.convert = gst_element_factory_make ("audioconvert", "convert");
- data.sink = gst_element_factory_make ("autoaudiosink", "sink");
我们像前面一样创建一个个element。
uridecodebin自己会在内部初始化必要的element,然后把一个URI变成一个原始音视频流输出,它差不多做了playbin2的一半工作。
因为它自己带着demuxer,所以它的source pad没有初始化,我们等会会用到。
audioconvert在不同的音频格式转换时很有用。这里用这个element是为了确保应用的平台无关性。
autoaudiosink和上一篇教程里面的autovideosink是非常相似的,只是操作的时音频流。
这个element的输出就是直接送往声卡的音频流。
- if (!gst_element_link (data.convert, data.sink)) {
- g_printerr ("Elements could not be linked.\n");
- gst_object_unref (data.pipeline);
- return -1;
- }
这里我们把转换element和sink element连接起来,
注意,我们没有把source连接起来——因为这个时候还没有source pad。
我们把转换element和sink element连接起来后暂时就放在那里,等待后面在处理。
-
- g_object_set (data.source, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);
和我们在上一篇教程一样,我们把URI通过设置属性的方法设置好。
信号
-
- g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
GSignal是GStreamer的一个重要部分。它会让你在你感兴趣的事情发生时收到通知。
信号是通过名字来区分的,每个GObject都有它自己的信号。
在这段代码里面,我们使用
g_signal_connect()
方法把
“pad-added”信号
和我们的源(uridecodebin)联系了起来,并且注册了一个回调函数。
GStreamer把&data这个指针的内容传给回调函数,这样CustomData这个数据结构中的数据也就传递了过去。
这个信号是有GstElement产生的,可以在相关的文档中找到或者用gst-inspect方法来查到。
我们现在准备开始运行了!
和前面的教程一样,把pipeline置成PLAYING状态,然后开始监听ERROR或者EOS。
回调函数
当我们的source element最后获得足够的数据时,它就会自动生成source pad,并且触发“pad-added”信号。
这样我们的回调就会被调用了:
- static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
src是触发这个信号的GstElement。
在这个例子中,就是uridecodebin,也是我们唯一注册的一个信号。
new_pad是加到src上的pad。通常来说,是我们需要连接的pad。
data指针是跟随信号一起过来的参数,在这个例子中,我们传递的时CustomData指针。
- GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
从CustomData我们可以获得转换element对象,然后使用gst_element_get_static_pad()方法可以获得sink pad。
这个pad是我们希望和new_pad连接的pad。
在前面的教程里,我们是用element和element连接的,让GStreamer自己来选择合适的pad。
在这里,我们是手动的把两个pad直接连接起来。
-
- if (gst_pad_is_linked (sink_pad)) {
- g_print (" We are already linked. Ignoring.\n");
- goto exit;
- }
uridecodebin会自动创建许多的pad,对于每一个pad,这个回调函数都会被调用。
上面这段代码可以防止连接多次。
-
- new_pad_caps = gst_pad_get_caps (new_pad);
- new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
- new_pad_type = gst_structure_get_name (new_pad_struct);
- if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
- g_print (" It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
- goto exit;
- }
因为我们只处理生成的audio数据,所以我们要检查new pad输出的数据类型。
我们前面创建了一部分处理音频的pipeline(convert+sink),没有生成处理视频的部分,所以我们只能处理音频数据。
gst_pad_get_caps()方法会获得pad的capability(也就是pad支持的数据类型),是被封装起来的GstCaps结构。
一个pad可以有多个capability,GstCaps可以包含多个GstStructure,每个都描述了一个不同的capability。
在这个例子中,我们知道我们的pad需要的capability是声音,我们使用gst_caps_get_structure()方法来获得GstStructure。
最后,我们用gst_structure_get_name()方法来获得structure的名字——最主要的描述部分。
如果名字不是由audio/x-raw开始的,就意味着不是一个解码的音频数据,也就不是我们所需要的,反之,就是我们需要连接的:
-
- ret = gst_pad_link (new_pad, sink_pad);
- if (GST_PAD_LINK_FAILED (ret)) {
- g_print (" Type is '%s' but link failed.\n", new_pad_type);
- } else {
- g_print (" Link succeeded (type '%s').\n", new_pad_type);
- }
gst_pad_link()方法会把两个pad连接起来。
就像gst_element_link()这个方法一样,连接必须是从source到sink,连接的两个pad必须在同一个bin里面。
到这儿我们的任务就完成了,当一个合适的pad出现后,它会和后面的audio处理部分相连,然后继续运行直到ERROR或者EOS。
下面,我们再介绍一点点GStreamer状态的概念。
状态
我们介绍过不把pipeline置成PLAYING状态,播放是不会开始的。
这里我们继续介绍一下其他的几种状态,在GStreamer里面有4种状态:
NULL |
NULL状态或者初始化状态 |
READY |
element已经READY或者PAUSED |
PAUSED |
element已经PAUSED,准备接受数据 |
PLAYING |
element在PLAYING,时钟在运行数据 |
状态迁移只能在相邻的状态里迁移,也就是说,你不能从NULL一下跳到PLAYING。
你必须经过READY和PAUSED状态。
如果你把pipeline设到PLAYING状态,GStreamer自动会经过中间状态的过渡。
- case GST_MESSAGE_STATE_CHANGED:
-
- if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
- GstState old_state, new_state, pending_state;
- gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
- g_print ("Pipeline state changed from %s to %s:\n",
- gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
- }
- break;
我们增加这段代码来监听总线上状态变化的情况,并且打印出相应的内容。
虽然每个element都会把它的消息放到总线上,但我们只监听pipeline本身的。
绝大多数应用都是在PLAYING状态开始播放,
然后跳转到PAUSE状态来提供暂停功能,
最后在退出时退到NULL状态。