目标
GStreamer建立的pipeline不需要完全关闭。
有多种方法可以让数据在任何时候送到pipeline中或者从pipeline中取出。
本教程会展示:
如何把外部数据送到pipeline中
如何把数据从pipeline中取出
如何操作这些数据
介绍
有几种方法可以让应用通过pipeline和数据流交互。
本教程讲述了最简单的一种,因为使用了专门为这个而创建的element。
appsrc,专门让应用可以往pipeline里面传入数据的element
(https://www.freedesktop.org/software/gstreamer-sdk/data/docs/2012.5/gst-plugins-base-libs-0.10/gst-plugins-base-libs-appsrc.html#appsrc),
appsink,就正好相反,让应用可以从pipeline中获得数据。
为了避免混淆,我们可以这么来理解,
appsrc是一个普通的source element,不过它的数据都是来自外太空,
appsink是一个普通的sink element,数据从这里出去的就消失不见了。
appsrc和appsink用得非常多,所以他们都自己提供API,你只要连接了gstreamer-app库,那么就可以访问到。
在本教程里,我们会使用一种简单地方法通过信号来实现。
appsrc可以有不同的工作模式:
在pull模式,在需要时向应用请求数据;
在push模式,应用根据自己的节奏把数据推送过来。
而且,在push模式,如果已经有了足够的数据,应用可以在push时被阻塞,或者可以经由enough-data和need-data信号来控制。
本教程中得例子就采用了这种信号控制的方式,其他没有提及的方法可以在appsrc的文档中查阅。
Buffers
通过pipeline传递的大块数据被称为buffers。
因为本例子会制造数据同时也消耗数据,所以我们需要了解GstBuffer。
Source Pads负责制造buffer,这些buffer被sink pad消耗掉。GStreamer在一个个element之间传递这些buffer。
一个buffer只能简单地描述一小片数据,不要认为我们所有的buffer都是一样大小的。
而且,buffer有一个时间戳和有效期,这个就描述了什么时候buffer里的数据需要渲染出来。
时间戳是个非常复杂和精深的话题,但目前这个简单地解释也足够了。
作为一个例子,一个filesrc会提供“ANY”属性的buffers并且没有时间戳信息。
在demux(《GStreamer基础教程03——动态pipeline》)之后,buffers会有一些特定的cap了,比如"video/x-h264",
在解码后,每一个buffer都会包含一帧有原始caps的视频帧(比如:video/x-raw-yuv),
并且有非常明确地时间戳用来指示这一帧在什么时候显示。
教程
本教程是上一篇教程(《GStreamer基础教程07——多线程和Pad的有效性》)在两个方面的扩展:
第一是用appsrc来取代audiotestsrc来生成音频数据;
第二是在tee里新加了一个分支,这样流入audio sink和波形显示的数据同样复制了一份传给appsink。
这个appsink就把信息回传给应用,应用就可以通知用户收到了数据或者做其他更复杂的工作。
一个粗糙的波形发生器
- #include<gst/gst.h>
- #include<string.h>
- #defineCHUNK_SIZE1024/*Amountofbyteswearesendingineachbuffer*/
- #defineSAMPLE_RATE44100/*Samplespersecondwearesending*/
- #defineAUdio_CAPS"audio/x-raw-int,channels=1,rate=%d,signed=(boolean)true,width=16,depth=16,endianness=BYTE_ORDER"
- /*Structuretocontainallourinformation,sowecanpassittocallbacks*/
- typedefstruct_CustomData{
- GstElement*pipeline,*app_source,*tee,*audio_queue,*audio_convert1,*audio_resample,*audio_sink;
- GstElement*video_queue,*audio_convert2,*visual,*video_convert,*video_sink;
- GstElement*app_queue,*app_sink;
- guint64num_samples;/*Numberofsamplesgeneratedsofar(fortimestampgeneration)*/
- gfloata,b,c,d;/*Forwaveformgeneration*/
- guintsourceid;/*TocontroltheGSource*/
- GMainLoop*main_loop;/*GLib'sMainLoop*/
- }CustomData;
- /*ThismethodiscalledbytheidleGSourceinthemainloop,toFeedCHUNK_SIZEbytesintoappsrc.
- *Theidlehandlerisaddedtothemainloopwhenappsrcrequestsustostartsendingdata(need-datasignal)
- *andisremovedwhenappsrchasenoughdata(enough-datasignal).
- */
- staticgbooleanpush_data(CustomData*data){
- GstBuffer*buffer;
- GstFlowReturnret;
- inti;
- gint16*raw;
- gintnum_samples=CHUNK_SIZE/2;/*Becauseeachsampleis16bits*/
- gfloatfreq;
- /*Createanewemptybuffer*/
- buffer=gst_buffer_new_and_alloc(CHUNK_SIZE);
- /*Setitstimestampandduration*/
- GST_BUFFER_TIMESTAMP(buffer)=gst_util_uint64_scale(data->num_samples,GST_SECOND,SAMPLE_RATE);
- GST_BUFFER_DURATION(buffer)=gst_util_uint64_scale(CHUNK_SIZE,SAMPLE_RATE);
- /*Generatesomepsychodelicwaveforms*/
- raw=(gint16*)GST_BUFFER_DATA(buffer);
- data->c+=data->d;
- data->d-=data->c/1000;
- freq=1100+11000*data->d;
- for(i=0;i<num_samples;i++){
- data->a+=data->b;
- data->b-=data->a/freq;
- raw[i]=(gint16)(5500*data->a);
- }
- data->num_samples+=num_samples;
- /*Pushthebufferintotheappsrc*/
- g_signal_emit_by_name(data->app_source,"push-buffer",buffer,&ret);
- /*Freethebuffernowthatwearedonewithit*/
- gst_buffer_unref(buffer);
- if(ret!=GST_FLOW_OK){
- /*Wegotsomeerror,stopsendingdata*/
- returnFALSE;
- returnTRUE;
- }
- /*Thissignalcallbacktriggerswhenappsrcneedsdata.Here,weaddanidlehandler
- *tothemainlooptostartpushingdataintotheappsrc*/
- staticvoidstart_Feed(GstElement*source,guintsize,CustomData*data){
- if(data->sourceid==0){
- g_print("StartFeeding\n");
- data->sourceid=g_idle_add((GSourceFunc)push_data,data);
- /*Thiscallbacktriggerswhenappsrchasenoughdataandwecanstopsending.
- *Weremovetheidlehandlerfromthemainloop*/
- voidstop_Feed(if(data->sourceid!=0){
- g_print("StopFeeding\n");
- g_source_remove(data->sourceid);
- data->sourceid=0;
- /*Theappsinkhasreceivedabuffer*/
- voidnew_buffer(GstElement*sink,153); font-weight:bold; background-color:inherit">GstBuffer*buffer;
- /*Retrievethebuffer*/
- g_signal_emit_by_name(sink,"pull-buffer",&buffer);
- if(buffer){
- /*Theonlythingwedointhisexampleisprinta*toindicateareceivedbuffer*/
- g_print("*");
- /*Thisfunctioniscalledwhenanerrormessageispostedonthebus*/
- voiderror_cb(GstBus*bus,153); font-weight:bold; background-color:inherit">GstMessage*msg,153); font-weight:bold; background-color:inherit">GError*err;
- gchar*debug_info;
- /*Printerrordetailsonthescreen*/
- 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);
- g_main_loop_quit(data->main_loop);
- intmain(intargc,153); font-weight:bold; background-color:inherit">charchar*argv[]){
- CustomDatadata;
- GstPadTemplate*tee_src_pad_template;
- GstPad*tee_audio_pad,*tee_video_pad,*tee_app_pad;
- GstPad*queue_audio_pad,*queue_video_pad,*queue_app_pad;
- gchar*audio_caps_text;
- GstCaps*audio_caps;
- GstBus*bus;
- /*Initializecumstomdatastructure*/
- memset(&data,0,153); font-weight:bold; background-color:inherit">sizeof(data));
- data.b=1; data.d=1;
- /*InitializeGStreamer*/
- gst_init(&argc,&argv);
- /*Createtheelements*/
- data.app_source=gst_element_factory_make("appsrc","audio_source");
- data.tee=gst_element_factory_make("tee","tee");
- data.audio_queue=gst_element_factory_make("queue","audio_queue");
- data.audio_convert1=gst_element_factory_make("audioconvert","audio_convert1");
- data.audio_resample=gst_element_factory_make("audioresample","audio_resample");
- data.audio_sink=gst_element_factory_make("autoaudiosink","audio_sink");
- data.video_queue=gst_element_factory_make("queue","video_queue");
- data.audio_convert2=gst_element_factory_make("audioconvert","audio_convert2");
- data.visual=gst_element_factory_make("wavescope","visual");
- data.video_convert=gst_element_factory_make("ffmpegcolorspace","csp");
- data.video_sink=gst_element_factory_make("autovideosink","video_sink");
- data.app_queue=gst_element_factory_make("queue","app_queue");
- data.app_sink=gst_element_factory_make("appsink","app_sink");
- /*Createtheemptypipeline*/
- data.pipeline=gst_pipeline_new("test-pipeline");
- if(!data.pipeline||!data.app_source||!data.tee||!data.audio_queue||!data.audio_convert1||
- !data.audio_resample||!data.audio_sink||!data.video_queue||!data.audio_convert2||!data.visual||
- !data.video_convert||!data.video_sink||!data.app_queue||!data.app_sink){
- g_printerr("Notallelementscouldbecreated.\n");
- return-1;
- /*Configurewavescope*/
- g_object_set(data.visual,"shader",0,"style",153); font-weight:bold; background-color:inherit">NULL);
- /*Configureappsrc*/
- audio_caps_text=g_strdup_printf(AUdio_CAPS,SAMPLE_RATE);
- audio_caps=gst_caps_from_string(audio_caps_text);
- g_object_set(data.app_source,"caps",audio_caps,153); font-weight:bold; background-color:inherit">NULL);
- g_signal_connect(data.app_source,"need-data",G_CALLBACK(start_Feed),&data);
- g_signal_connect(data.app_source,"enough-data",G_CALLBACK(stop_Feed),&data);
- /*Configureappsink*/
- g_object_set(data.app_sink,"emit-signals",TRUE,248)"> g_signal_connect(data.app_sink,"new-buffer",G_CALLBACK(new_buffer),108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> gst_caps_unref(audio_caps);
- g_free(audio_caps_text);
- /*Linkallelementsthatcanbeautomaticallylinkedbecausetheyhave"Always"pads*/
- gst_bin_add_many(GST_BIN(data.pipeline),data.app_source,data.tee,data.audio_queue,data.audio_convert1,data.audio_resample,
- data.audio_sink,data.video_queue,data.audio_convert2,data.visual,data.video_convert,data.video_sink,data.app_queue,
- data.app_sink,153); font-weight:bold; background-color:inherit">if(gst_element_link_many(data.app_source,153); font-weight:bold; background-color:inherit">NULL)!=TRUE||
- gst_element_link_many(data.audio_queue,data.audio_sink,153); font-weight:bold; background-color:inherit">NULL)!=TRUE||
- gst_element_link_many(data.video_queue,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> gst_element_link_many(data.app_queue,data.app_sink,153); font-weight:bold; background-color:inherit">NULL)!=TRUE){
- g_printerr("Elementscouldnotbelinked.\n");
- gst_object_unref(data.pipeline);
- return-1;
- /*ManuallylinktheTee,whichhas"Request"pads*/
- tee_src_pad_template=gst_element_class_get_pad_template(GST_ELEMENT_GET_CLASS(data.tee),"src%d");
- tee_audio_pad=gst_element_request_pad(data.tee,tee_src_pad_template,153); font-weight:bold; background-color:inherit">NULL,248)"> g_print("Obtainedrequestpad%sforaudiobranch.\n",gst_pad_get_name(tee_audio_pad));
- queue_audio_pad=gst_element_get_static_pad(data.audio_queue,"sink");
- tee_video_pad=gst_element_request_pad(data.tee,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> g_print("Obtainedrequestpad%sforvideobranch.\n",gst_pad_get_name(tee_video_pad));
- queue_video_pad=gst_element_get_static_pad(data.video_queue,"sink");
- tee_app_pad=gst_element_request_pad(data.tee,248)"> g_print("Obtainedrequestpad%sforappbranch.\n",gst_pad_get_name(tee_app_pad));
- queue_app_pad=gst_element_get_static_pad(data.app_queue,153); font-weight:bold; background-color:inherit">if(gst_pad_link(tee_audio_pad,queue_audio_pad)!=GST_PAD_LINK_OK||
- gst_pad_link(tee_video_pad,queue_video_pad)!=GST_PAD_LINK_OK||
- gst_pad_link(tee_app_pad,queue_app_pad)!=GST_PAD_LINK_OK){
- g_printerr("Teecouldnotbelinked\n");
- gst_object_unref(data.pipeline);
- gst_object_unref(queue_audio_pad);
- gst_object_unref(queue_video_pad);
- gst_object_unref(queue_app_pad);
- /*Instructthebustoemitsignalsforeachreceivedmessage,andconnecttotheinterestingsignals*/
- bus=gst_element_get_bus(data.pipeline);
- gst_bus_add_signal_watch(bus);
- g_signal_connect(G_OBJECT(bus),"message::error",(GCallback)error_cb,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> gst_object_unref(bus);
- /*Startplayingthepipeline*/
- gst_element_set_state(data.pipeline,GST_STATE_PLAYING);
- /*CreateaGLibMainLoopandsetittorun*/
- data.main_loop=g_main_loop_new( g_main_loop_run(data.main_loop);
- /*ReleasetherequestpadsfromtheTee,andunrefthem*/
- gst_element_release_request_pad(data.tee,tee_audio_pad);
- gst_element_release_request_pad(data.tee,tee_video_pad);
- gst_object_unref(tee_audio_pad);
- gst_object_unref(tee_video_pad);
- gst_object_unref(tee_app_pad);
- /*Freeresources*/
- gst_element_set_state(data.pipeline,GST_STATE_NULL);
- return0;
- }
工作流程
创建pipeline段的代码就是上一篇的教程中得例子的扩大版。
包括初始或所有的element,连接有Always Pad的element然后手动连接tee element的Request Pad。
下面我们关注一下appsrc和appsink这两个element的配置:
它说明了element准备生成的数据的类型,这样GStreamer就可以检查下游的element看看是否支持。
这个属性必须是一个GstCaps对象,这个对象可以很方便的由gst_caps_from_string()来生成。
然后我们把need-data和enough-data信号和回调连接起来,
这样在appsrc内部的队列里面数据不足或将要满地时候会发送信号,我们就用这些信号来启动/停止我们的信号发生过程。
copy
当然,这个信号的发出需要emit-signals这个信号属性被开启(默认是关闭的)。 启动pipeline,等到消息和最后的清理资源都和以前的没什么区别。让我们关注我们刚刚注册的回调吧。
这个函数会给appsrc输入数据知道内部队列满为止。
一个GLib的idle函数是一个GLib在主循环在“idle”时会调用的方法,也就是说,当时没有更高优先级的任务运行。 这只是appsrc多种发出数据方法中的一个。
特别需要指出的是,buffer不是必须要在主线程中用GLib方法来传递给appsrc的,
你也不是一定要用need-data和enough-data信号来同步appsrc的(据说这样最方便)。
我们记录下g_idle_add()的返回的sourceid,这样后面可以关掉它。
copy