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>
- /*Structuretocontainallourinformation,sowecanpassittocallbacks*/
- typedefstruct_CustomData{
- GstElement*pipeline;
- GstElement*source;
- GstElement*convert;
- GstElement*sink;
- }CustomData;
- /*Handlerforthepad-addedsignal*/
- staticvoidpad_added_handler(GstElement*src,GstPad*pad,153); font-weight:bold; background-color:inherit">CustomData*data);
- intmain(intargc,153); font-weight:bold; background-color:inherit">charchar*argv[]){
- CustomDatadata;
- GstBus*bus;
- GstMessage*msg;
- GstStateChangeReturnret;
- gbooleanterminate=FALSE;
- /*InitializeGStreamer*/
- gst_init(&argc,&argv);
- /*Createtheelements*/
- data.source=gst_element_factory_make("uridecodebin","source");
- data.convert=gst_element_factory_make("audioconvert","convert");
- data.sink=gst_element_factory_make("autoaudiosink","sink");
- /*Createtheemptypipeline*/
- data.pipeline=gst_pipeline_new("test-pipeline");
- if(!data.pipeline||!data.source||!data.convert||!data.sink){
- g_printerr("Notallelementscouldbecreated.\n");
- return-1;
- }
- /*Buildthepipeline.NotethatweareNOTlinkingthesourceatthis
- *point.Wewilldoitlater.*/
- gst_bin_add_many(GST_BIN(data.pipeline),data.source,data.convert,data.sink,153); font-weight:bold; background-color:inherit">NULL);
- if(!gst_element_link(data.convert,data.sink)){
- g_printerr("Elementscouldnotbelinked.\n");
- gst_object_unref(data.pipeline);
- return-1;
- }
- /*SettheURItoplay*/
- g_object_set(data.source,"uri","http://docs.gstreamer.com/media/sintel_trailer-480p.webm",0); background-color:inherit">/*Connecttothepad-addedsignal*/
- g_signal_connect(data.source,"pad-added",G_CALLBACK(pad_added_handler),&data);
- /*Startplaying*/
- ret=gst_element_set_state(data.pipeline,GST_STATE_PLAYING);
- if(ret==GST_STATE_CHANGE_FAILURE){
- g_printerr("Unabletosetthepipelinetotheplayingstate.\n");
- /*Listentothebus*/
- 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);
- /*Parsemessage*/
- if(msg!=NULL){
- GError*err;
- gchar*debug_info;
- switch(GST_MESSAGE_TYPE(msg)){
- caseGST_MESSAGE_ERROR:
- gst_message_parse_error(msg,&err,&debug_info);
- g_printerr("Errorreceivedfromelement%s:%s\n",GST_OBJECT_NAME(msg->src),err->message);
- g_printerr("Debugginginformation:%s\n",debug_info?debug_info:"none");
- g_clear_error(&err);
- g_free(debug_info);
- terminate=TRUE;
- break;
- GST_MESSAGE_EOS:
- g_print("End-Of-Streamreached.\n");
- GST_MESSAGE_STATE_CHANGED:
- /*Weareonlyinterestedinstate-changedmessagesfromthepipeline*/
- if(GST_MESSAGE_SRC(msg)==GST_OBJECT(data.pipeline)){
- GstStateold_state,new_state,pending_state;
- gst_message_parse_state_changed(msg,&old_state,&new_state,&pending_state);
- g_print("Pipelinestatechangedfrom%sto%s:\n",
- gst_element_state_get_name(old_state),gst_element_state_get_name(new_state));
- break;
- default:
- /*Weshouldnotreachhere*/
- g_printerr("Unexpectedmessagereceived.\n");
- gst_message_unref(msg);
- }while(!terminate);
- /*Freeresources*/
- gst_object_unref(bus);
- gst_element_set_state(data.pipeline,GST_STATE_NULL);
- gst_object_unref(data.pipeline);
- return0;
- /*Thisfunctionwillbecalledbythepad-addedsignal*/
- GstPad*new_pad,153); font-weight:bold; background-color:inherit">CustomData*data){
- GstPad*sink_pad=gst_element_get_static_pad(data->convert,248)"> GstPadLinkReturnret;
- GstCaps*new_pad_caps=NULL;
- GstStructure*new_pad_struct=NULL;
- constgchar*new_pad_type= g_print("Receivednewpad'%s'from'%s':\n",GST_PAD_NAME(new_pad),GST_ELEMENT_NAME(src));
- /*Ifourconverterisalreadylinked,wehavenothingtodohere*/
- if(gst_pad_is_linked(sink_pad)){
- g_print("Wearealreadylinked.Ignoring.\n");
- gotoexit;
- /*Checkthenewpad'stype*/
- 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("Ithastype'%s'whichisnotrawaudio.Ignoring.\n",new_pad_type);
- gotoexit;
- /*Attemptthelink*/
- ret=gst_pad_link(new_pad,sink_pad);
- if(GST_PAD_LINK_Failed(ret)){
- g_print("Typeis'%s'butlinkFailed.\n",new_pad_type);
- else{
- g_print("Linksucceeded(type'%s').\n",248)"> exit:
- /*Unreferencethenewpad'scaps,ifwegotthem*/
- if(new_pad_caps!=NULL)
- gst_caps_unref(new_pad_caps);
- /*Unreferencethesinkpad*/
- gst_object_unref(sink_pad);
- }
工作流程
我们像前面一样创建一个个element。
uridecodebin自己会在内部初始化必要的element,然后把一个URI变成一个原始音视频流输出,它差不多做了playbin2的一半工作。
因为它自己带着demuxer,所以它的source pad没有初始化,我们等会会用到。
audioconvert在不同的音频格式转换时很有用。这里用这个element是为了确保应用的平台无关性。
autoaudiosink和上一篇教程里面的autovideosink是非常相似的,只是操作的时音频流。
这个element的输出就是直接送往声卡的音频流。
注意,我们没有把source连接起来——因为这个时候还没有source pad。
我们把转换element和sink element连接起来后暂时就放在那里,等待后面在处理。
信号
信号是通过名字来区分的,每个GObject都有它自己的信号。
在这段代码里面,我们使用
g_signal_connect()
方法把
“pad-added”信号
和我们的源(uridecodebin)联系了起来,并且注册了一个回调函数。
GStreamer把&data这个指针的内容传给回调函数,这样CustomData这个数据结构中的数据也就传递了过去。
这个信号是有GstElement产生的,可以在相关的文档中找到或者用gst-inspect方法来查到。
我们现在准备开始运行了!
和前面的教程一样,把pipeline置成PLAYING状态,然后开始监听ERROR或者EOS。
回调函数
当我们的source element最后获得足够的数据时,它就会自动生成source pad,并且触发“pad-added”信号。
这样我们的回调就会被调用了:
在这个例子中,就是uridecodebin,也是我们唯一注册的一个信号。
new_pad是加到src上的pad。通常来说,是我们需要连接的pad。
data指针是跟随信号一起过来的参数,在这个例子中,我们传递的时CustomData指针。
copy
这个pad是我们希望和new_pad连接的pad。
在前面的教程里,我们是用element和element连接的,让GStreamer自己来选择合适的pad。
在这里,我们是手动的把两个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开始的,就意味着不是一个解码的音频数据,也就不是我们所需要的,反之,就是我们需要连接的:
就像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,时钟在运行数据 |
你必须经过READY和PAUSED状态。
如果你把pipeline设到PLAYING状态,GStreamer自动会经过中间状态的过渡。
虽然每个element都会把它的消息放到总线上,但我们只监听pipeline本身的。 绝大多数应用都是在PLAYING状态开始播放, 然后跳转到PAUSE状态来提供暂停功能, 最后在退出时退到NULL状态。