本文主要参考GStreamer官方Tutorials:gstreamer Tutorials
Gstreamer是一个用于开发流媒体应用的开源框架,采用了基于插件(plugin)和管道(pipeline)的体系结构,框架中的所有的功能模块都被实现成可以插拔的组件(component), 并且能够很方便地安装到任意一个管道上。由于所有插件都通过管道机制进行统一的数据交换,因此很容易利用已有的各种插件“组装”出一个功能完善的流媒体应用程序。
由于deepstream是基于gstreamer的,所以要想在deepstream上做拓展,需要对gstreamer有一定的认识。
GStreamer的核心功能是为插件,数据流和媒体类型处理/协商提供框架。 下图是对基于Gstreamer框架的应用的简单分层:
最上面一层为应用,比如gstreamer自带的一些工具(gst-launch,gst-inspect等),以及基于gstreamer封装的库(gst-player,gst-rtsp-server,gst-editing-services等)根据不同场景实现的应用。
中间一层为Core Framework,主要提供:
上层应用所需接口
Plugin的框架
Pipline的框架
数据在各个Element间的传输及处理机制
多个媒体流(Streaming)间的同步(比如音视频同步)
其他各种所需的工具库
最下层为各种插件,实现具体的数据处理及音视频输出,应用不需要关注插件的细节,会由Core Framework层负责插件的加载及管理。主要分类为:
Protocols
:负责各种协议的处理,file,http,rtsp等。Sources
:负责数据源的处理,alsa,v4l2,tcp/udp等。Formats
:负责媒体容器的处理,avi,mp4,ogg等。Codecs
:负责媒体的编解码,mp3,vorbis等。Filters
:负责媒体流的处理,converters,mixers,effects等。Sinks
:负责媒体流输出到指定设备或目的地,alsa,xvideo,tcp/udp等。对于需要应用 GStreamer 框架的程序员来讲,GstElement 是一个必须理解的概念,因为它是组成管道的基本构件,也是框架中所有可用组件的基础。因此,GStreamer 框架中的大部分函数都会涉及到对 GstElement 对象的操作。
从 GStreamer 自身的观点来看,GstElement 可以描述为一个具有特定属性的黑盒子,它通过连接点(link point)与外界进行交互,向框架中的其余部分表征自己的特性或者功能。 一个Element实现了一个功能(如读取文件,解码,输出等),一个程序需要创建多个element,并按顺序将其串连起来,构成一个完整的pipeline。
按照各自功能上的差异,GstElement 可以细分成如下几类:
下图将有助于你更好地理解数据源元件、过滤器元件和接收器元件三者的区别,同时也不难看出它们是如何相互配合形成管道的:
要想在应用程序中创建GstElement对象,办法是借助于工厂对象GstElementFactory
。由于GStreamer框架提供了多种类型的GstElement对象,因此对应地提供了多种类型的GstElementFactory对象,它们是通过特定的工厂名称来进行区分的。例如,下面的代码通过gst_element_factory_find()函数获得了一个名为mad的工厂对象,它之后可以用来创建与之对应的MP3解码器元件:
GstElementFactory *factory;
factory = gst_element_factory_find ("mad");
成功获得工厂对象之后,接下来就可以通过gst_element_factory_create()函数来创建特定的GstElement对象了,该函数在调用时有两个参数,分别是需要用到的工厂对象,以及即将创建的元件名称。元件名称可以用查询的办法获得,也可以通过传入空指针(NULL)来生成工厂对象的默认元件。下面的代码示范了如何利用已经获得的工厂对象,来创建名为decoder的MP3解码器元件:
GstElement *element;
element = gst_element_factory_create (factory, "decoder");
此外,创建元件的最简单方法是使用 gst_element_factory_make()
。 gst_element_factory_make
实际上是两个功能组合的简写,此函数为新创建的元件采用工厂名称和元件名称。
element = gst_element_factory_make ("mad", "decoder");
if (!element) {
g_print ("Failed to create element of type 'mad'\n");
return -1;
}
当创建的GstElement不再使用的时候,还必须调用gst_element_unref()
函数释放其占用的内存资源,gst_element_unref()
会将元件的引用计数减少1。元件在创建时的引用计数为1。当refcount减少到0时,元件将被完全销毁。
gst_element_unref (element);
GStreamer使用了与GObject相同的机制来对属性(property)进行管理,包括查询(query)、设置(set)和读取(get)等。所有的GstElement对象都从其父对象GstObject那里继承了名称(name)这一最基本的属性,这是因为像gst_element_factory_make()
和gst_element_factory_create()
这样的函数在创建元件对象时都会用到名称name属性,通过调用gst_object_set_name()和gst_object_get_name()函数可以设置和读取GstElement对象的名称属性。
一个完整地创建GstElement的例子如下所示。
#include
int main (int argc, char *argv[])
{
GstElementFactory *factory;
GstElement * element;
/* init GStreamer */
gst_init (&argc, &argv);
/* create element, method #2 */
factory = gst_element_factory_find ("fakesrc");
if (!factory) {
g_print ("Failed to find factory of type 'fakesrc'\n");
return -1;
}
element = gst_element_factory_create (factory, "source");
if (!element) {
g_print ("Failed to create element, even though its factory exists!\n");
return -1;
}
/* get name */
g_object_get (G_OBJECT (element), "name", &name, NULL);
g_print ("The name of the element is '%s'.\n", name);
g_free (name);
gst_object_unref (GST_OBJECT (element));
gst_object_unref (GST_OBJECT (factory));
return 0;
}
箱柜(bin)是GStreamer框架中的容器元件,它通常被用来容纳其它的元件对象,但由于其自身也是一个GstElement对象,因此实际上也能够被用来容纳其它的箱柜对象。利用bin可以将需要处理的多个元件组合成一个逻辑元件,你不再需要对箱柜中的元件逐个进行操作,而只需处理一个bin元件即可。
例如:你有很多个元件用来解码视频并对其使用一些效果,要使事情变得简单一些, 你可以把这些元件放进一个bin(就像一个容器)中,以后你就可以使用bin来引用这些元件了。 这样其实bin变成了一个元件,如你的管道是 a ! b ! c ! d
,你可以把他们放进 mybin,这样当你使用mybin时其实是引用了 a ! b ! c ! d
。
在GStreamer应用程序中使用的箱柜主要有两种类型:
GStreamer框架提供了两种方法来创建箱柜:一种是借助工厂方法,另一种则是使用特定的函数。下面的代码示范了如何使用工厂方法创建线程对象,以及如何使用特定函数来创建管道对象:
GstElement *thread, *pipeline;
// 创建线程对象,同时为其指定唯一的名称。
thread = gst_element_factory_make ("thread", NULL);
// 根据给出的名称,创建一个特定的管道对象。
pipeline = gst_pipeline_new ("pipeline_name");
箱柜成功创建之后,就可以调用gst_bin_add()
函数将已经存在的元件添加到其中来了:
GstElement *element;
GstElement *bin;
bin = gst_bin_new ("bin_name");
element = gst_element_factory_make ("mad", "decoder");
gst_bin_add (GST_BIN (bin), element);
而要从箱柜中找到特定的元件也很容易,可以借助gst_bin_get_by_name()
函数实现:
GstElement *element;
element = gst_bin_get_by_name (GST_BIN (bin), "decoder");
由于GStreamer框架中的一个箱柜能够添加到另一个箱柜之中,因此有可能会出现箱柜嵌套的情况,gst_bin_get_by_name()
函数在查找元件时会对嵌套的箱柜作递归查找。元件有添加到箱柜之中以后,在需要的时候还可以从中移出,这是通过调用gst_bin_remove()
函数来完成的:
GstElement *element;
gst_bin_remove (GST_BIN (bin), element);
请注意,您添加元件的箱柜将获得该元件的所有权,如果销毁箱柜,则该元件将被取消引用,如果从箱柜容器中删除元件,则将自动取消引用该元件。
下面是创建pipeline Bin容器的代码示例:
#include
int main (int argc, char *argv[])
{
GstElement *bin, *pipeline, *source, *sink;
/* init */
gst_init (&argc, &argv);
/* create */
pipeline = gst_pipeline_new ("my_pipeline");
bin = gst_bin_new ("my_bin");
source = gst_element_factory_make ("fakesrc", "source");
sink = gst_element_factory_make ("fakesink", "sink");
/* First add the elements to the bin */
gst_bin_add_many (GST_BIN (bin), source, sink, NULL);
/* add the bin to the pipeline */
gst_bin_add (GST_BIN (pipeline), bin);
/* link the elements */
gst_element_link (source, sink);
[...]
}
当GStreamer框架中的元件通过管道连接好之后,它们就开始了各自的处理流程,期间一般会经历多次状态切换,其中每个元件在特定时刻将处于如下四种状态之一:
GST_STATE_NULL
:这是默认状态。在这种状态下没有分配资源,因此,转换到此状态将释放所有资源。当元件的引用计数达到0并被释放时,该元件必须处于此状态。GST_STATE_READY
:在就绪状态下,元件已分配了其所有全局资源,即可以保留在流中的资源。你可以考虑打开设备,分配缓冲区等。但是在这种状态下不会打开流,因此流位置自动为零。如果先前已打开流,则应在此状态下将其关闭,并应重置位置,属性等。GST_STATE_PAUSED
:在此状态下,元件已打开流,但未对其进行处理。此时元件可以修改流的位置,读取和处理数据,状态一旦更改为PLAYING,即可开始播放。总之,PAUSED与PLAYING相同,只是PAUSED没有运行时钟。GST_STATE_PLAYING
:在该PLAYING
状态下,与该PAUSED
状态下完全相同,只是时钟现在运行。所有的元件都从NULL状态开始,依次经历NULL、READY、PAUSED、PLAYING等状态间的转换。元件当前所处的状态可以通过调用gst_element_set_state()
函数进行切换:
GstElement *bin;
/* 创建元件,并将其连接成箱柜bin */
gst_element_set_state (bin, GST_STATE_PLAYING);
默认情况下,管道及其包含的所有元件在创建之后将处于NULL状态,此时它们不会进行任何操作。当管道使用完毕之后,不要忘记重新将管道的状态切换回NULL状态,让其中包含的所有元件能够有机会释放它们正在占用的资源。
管道真正的处理流程是从第一次将其切换到READY状态时开始的,此时管道及其包含的所有元件将做好相应的初始化工作,来为即将执行的数据处理过程做好准备。对于一个典型的元件来讲,处于READY状态时需要执行的操作包括打开媒体文件和音频设备等,或者试图与位于远端的媒体服务器建立起连接。
处于READY状态的管道一旦切换到PLAYING状态,需要处理的多媒体数据就开始在整个管道中流动,并依次被管道中包含的各个元件进行处理,从而最终实现管道预先定义好的某种多媒体功能。GStreamer框架也允许将管道直接从NULL状态切换到PLAYING状态,而不必经过中间的READY状态。
正处于播放状态的管道能够随时切换到PAUSED状态,暂时停止管道中所有数据的流动,并能够在需要的时候再次切换回PLAYING状态。如果需要插入或者更改管道中的某个元件,必须先将其切换到PAUSED或者NULL状态,元件在处于PAUSED状态时并不会释放其占用的资源。
衬垫(pad)是GStreamer框架引入的另外一个基本概念,它指的是元件(element)与外界的连接通道, 衬垫pad是元件的输入和输出,它们用于协商GStreamer中元件之间的链接和数据流。可以将pad视为元件上的“插头”或“端口”,与其他元件建立链接后,数据可以通过pad流入或流出其他元件。
绝大多数元件有一个输入pad(sink)和一个输出pad(src)。 因此,我们上面的管道看起来是这样的:
[src] ! [sink src] ! [sink]
最左边的元件只有一个src pad用来提供数据,接下来的元件接收信息并做一些处理后,传给下一个元件,因此他们有sink和src pad,最后一个元件sink pad只接收数据。
下面看一下如何获取元件的衬垫pad。
成功创建GstElement对象之后,可以通过gst_element_get_pad()
获得该元件的指定衬垫。例如,下面的代码将返回element元件中名为src的衬垫:
GstPad *srcpad;
srcpad = gst_element_get_pad (element, "src");
如果需要的话也可以通过gst_element_get_pad_list()
函数,来查询指定元件中的所有衬垫。例如,下面的代码将输出element元件中所有衬垫的名称:
GList *pads;
pads = gst_element_get_pad_list (element);
while (pads) {
GstPad *pad = GST_PAD (pads->data);
g_print ("pad name is: %s\n", gst_pad_get_name (pad));
pads = g_list_next (pads);
}
与元件一样,衬垫的名称也能够动态设置或者读取,这是通过调用gst_pad_get_name ()
和gst_pad_set_name()
函数来完成的。
所有元件的衬垫都可以细分成输入衬垫(sink)和输出衬垫(src)两种,其中输入衬垫只能接收数据但不能产生数据,而输出衬垫则正好相反,只能产生数据但不能接收数据。GStreamer框架中的所有衬垫都必然依附于某个元件之上,调用gst_pad_get_parent()
可以获得指定衬垫所属的元件,该函数的返回值是一个指向GstElement的指针。
在引入了元件和衬垫的概念之后,GStreamer对多媒体数据的处理过程就变得非常清晰了:通过将不同元件的衬垫依次连接起来构成一条媒体处理管道,使数据在流经管道的过程能够被各个元件正常处理,最终实现特定的多媒体功能。
GStreamer框架中的元件是通过各自的衬垫连接起来的,下面的代码示范了如何将两个元件通过衬垫连接起来,以及如何在需要的时候断开它们之间的连接:
GstPad *srcpad, *sinkpad;
srcpad = gst_element_get_pad (element1, "src");
sinpad = gst_element_get_pad (element2, "sink");
// 连接
gst_pad_link (srcpad, sinkpad);
// 断开
gst_pad_unlink (srcpad, sinkpad);
如果需要建立起连接的元件都只有一个输入衬垫和一个输出衬垫,那么更简单的做法是调用gst_element_link()函数直接在它们之间建立起连接,或者调用gst_element_unlink()函数断开它们之间的连接:
// 连接
gst_element_link (element1, element2);
// 断开
gst_element_unlink (element1, element2);
衬垫从某种程度上可以看成是元件的代言人,因为它要负责向外界描述该元件所具有的能力。GStreamer框架提供了统一的机制来让衬垫描述元件所具有的能力(Capability)。Capabilities简称caps,它描绘了两个pad之间可以支持的数据类型,具体是通过数据结构_GstCaps来实现的:
struct _GstCaps {
gchar *name; /* the name of this caps */
guint16 id; /* type id (major type) */
guint refcount; /* caps are refcounted */
GstProps *properties; /* properties for this capability */
GstCaps *next; /* caps can be chained together */
};
当你使用 gst-inspect-1.0
命令查看一个元件的详细信息时,就会列出该元件的pad信息。
以下是对mad元件的能力描述,不难看出该元件中实际包含sink和src两个衬垫,并且每个衬垫都带有特定的功能信息。名为sink的衬垫是mad元件的输入端,它能够接受MIME类型为audio/mp3的媒体数据,此外还具有layer、bitrate和framed三种属性。名为src的衬垫是mad元件的输出端,它负责产生MIME类型为audio/raw媒体数据,此外还具有format、depth、rate和channels等多种属性。
Pads:
SINK template: ’sink’
Availability: Always
Capabilities:
’mad_sink’:
MIME type: ’audio/mp3’:
SRC template: ’src’
Availability: Always
Capabilities:
’mad_src’:
MIME type: ’audio/raw’:
format: String: int
endianness: Integer: 1234
width: Integer: 16
depth: Integer: 16
channels: Integer range: 1 - 2
law: Integer: 0
signed: Boolean: TRUE
rate: Integer range: 11025 - 48000
准确地说,GStreamer框架中的每个衬垫都可能对应于多个能力描述,它们能够通过函数gst_pad_get_caps()
来获得。例如,下面的代码将输出pad衬垫中所有能力描述的名称及其MIME类型:
GstCaps *caps;
caps = gst_pad_get_caps (pad);
g_print ("pad name is: %s\n", gst_pad_get_name (pad));
while (caps) {
g_print (" Capability name is %s, MIME type is %s\n",
gst_caps_get_name (cap),
gst_caps_get_mime (cap));
caps = caps->next;
}
如果我想让两个元件之间的数据流变为某种媒体类型video/x-raw
,并限制帧率等状态,可以通过Caps filter实现,最简单的方法是使用函数gst_caps_new_simple()
。
static gboolean
link_elements_with_filter (GstElement *element1, GstElement *element2)
{
gboolean link_ok;
GstCaps *caps;
caps = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, "I420",
"width", G_TYPE_INT, 384,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, 25, 1,
NULL);
link_ok = gst_element_link_filtered (element1, element2, caps);
gst_caps_unref (caps);
if (!link_ok) {
g_warning ("Failed to link element1 and element2!");
}
return link_ok;
}
这将迫使两个元件之间的数据流变为某种视频格式(宽度,高度和帧速率等)。如果在所涉及元件的上下文中无法实现链接,则链接将失败。
注意:使用gst_element_link_filtered()
它时,它将自动为您创建一个capsfilter
元件,并将其插入到您要连接的两个元件之间的容器或管道中,如果您要断开这些元件的连接,就必须从capsfilter断开这两个元件连接。
如果仔细研究一下箱柜,会发现bin没有属于自己的输入衬垫和输出衬垫,因此显然是无法作为一个逻辑整体与其它元件交互的。为了解决这一问题,GStreamer引入了精灵衬垫(ghost pad)的概念, ghost pad是与bin中某些元件的pad相互连接, 它与UNIX文件系统中的符号链接类似,如下图所示:在bin上使用ghost pad,链接到Element1的sink,这样可以通过操作bin上的ghost pad操作Element1 sink pad。
具有精灵衬垫(ghost pad)的箱柜在行为上与元件是完全相同的,所有元件具有的属性它都具有,所有针对元件能够进行的操作也同样能够针对箱柜进行,因此在GStreamer应用程序中能够像使用元件一样使用这类箱柜。
下面的代码示范了如何为箱柜添加一个精灵衬垫:
GstElement *bin;
GstElement *element;
element = gst_element_factory_create ("mad", "decoder");
bin = gst_bin_new ("bin_name");
gst_bin_add (GST_BIN (bin), element);
gst_element_add_ghost_pad (bin, gst_element_get_pad (element, "sink"), "sink");
创建元件时,某些元件可能没有包含全部pad。例如,创建ogg demuxer元件时会发生src pad不会立即创建的情况。这是因为此时demuxer并不知道输入流经过demux后需要输出什么类型的流?输出几路流?因此demuxer的src pad不会立即创建。动态衬垫会根据输入文件的类型,自动创建衬垫pad。
当demuxer元件检测到ogg流时,它会读取ogg流并根据ogg流的信息为每个流动态创建pad。当ogg流结束时,demuxer元件将会删除动态src pad。
我们使用gst-inspect oggdemux
命令来查看oggdemux的信息,结果显示该元件只有一个衬垫:“一个sink垫”。其他衬垫都是“ dormant(休眠)”状态。
你可以在oggdemux的src pad caps中看到此内容,其中有一个“Exists: Sometimes”的属性,表示该pad是并不总是存在的。
#include
static void cb_new_pad (GstElement *element, GstPad *pad, gpointer data)
{
gchar *name;
name = gst_pad_get_name (pad);
g_print ("A new pad %s was created\n", name);
g_free (name);
/* here, you would setup a new pad link for the newly created pad */
[..]
}
int main (int argc, char *argv[])
{
GstElement *pipeline, *source, *demux;
GMainLoop *loop;
/* init */
gst_init (&argc, &argv);
/* create elements */
pipeline = gst_pipeline_new ("my_pipeline");
source = gst_element_factory_make ("filesrc", "source");
g_object_set (source, "location", argv[1], NULL);
demux = gst_element_factory_make ("oggdemux", "demuxer");
/* you would normally check that the elements were created properly */
/* put together a pipeline */
gst_bin_add_many (GST_BIN (pipeline), source, demux, NULL);
gst_element_link_pads (source, "src", demux, "sink");
/* listen for newly created pads */
g_signal_connect (demux, "pad-added", G_CALLBACK (cb_new_pad), NULL);
/* start the pipeline */
gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (loop);
[..]
}
这里通过g_signal_connect
监听"pad-added"信号,并使用G_CALLBACK (cb_new_pad)回调处理监听到的信号。最后不要忘记使用gst_element_set_state ()
或gst_element_sync_state_with_parent ()
将新添加的元素的状态设置为管道的目标状态。
总线是一个简单的系统,负责将消息从管道传递到应用程序。默认情况下,每个管道都包含一个总线,因此应用程序不需要创建总线或任何东西。应用程序唯一要做的就是在总线上设置消息处理程序,这类似于对对象的信号处理程序。当mainloop运行时,将定期检查总线上是否有新消息,并且在有任何消息可用时将调用回调。
有两种总线使用方式:
gst_bus_add_watch ()
或 gst_bus_add_signal_watch ()
来监听Bus,并将消息处理程序附加到管道的总线上,每当管道向总线发出消息时,都将调用此处理程序。gst_bus_peek ()
和/或完成此操作 gst_bus_poll ()
。#include
static GMainLoop *loop;
static gboolean
my_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
{
g_print ("Got %s message\n", GST_MESSAGE_TYPE_NAME (message));
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ERROR:{
GError *err;
gchar *debug;
gst_message_parse_error (message, &err, &debug);
g_print ("Error: %s\n", err->message);
g_error_free (err);
g_free (debug);
g_main_loop_quit (loop);
break;
}
case GST_MESSAGE_EOS:
/* end-of-stream */
g_main_loop_quit (loop);
break;
default:
/* unhandled message */
break;
}
/* we want to be notified again the next time there is a message
* on the bus, so returning TRUE (FALSE means we want to stop watching
* for messages on the bus and our callback should not be called again)
*/
return TRUE;
}
gint main (gint argc, gchar * argv[])
{
GstElement *pipeline;
GstBus *bus;
guint bus_watch_id;
/* init */
gst_init (&argc, &argv);
/* create pipeline, add handler */
pipeline = gst_pipeline_new ("my_pipeline");
/* adds a watch for new message on our pipeline's message bus to
* the default GLib main context, which is the main context that our
* GLib main loop is attached to below
*/
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
bus_watch_id = gst_bus_add_watch (bus, my_bus_callback, NULL);
gst_object_unref (bus);
/* [...] */
/* create a mainloop that runs/iterates the default GLib main context
* (context NULL), in other words: makes the context check if anything
* it watches for has happened. When a message has been posted on the
* bus, the default main context will automatically call our
* my_bus_callback() function to notify us of that message.
* The main loop will be run until someone calls g_main_loop_quit()
*/
loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (loop);
/* clean up */
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
g_source_remove (bus_watch_id);
g_main_loop_unref (loop);
return 0;
}
请注意,如果你使用gst_bus_add_signal_watch()
,则可以连接总线上的“消息”信号,而不必switch()
处理所有可能的消息类型。只需以 message::
的形式连接到你需要处理的信号。
上面的代码段也可以写成:
GstBus *bus;
[..]
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message::error", G_CALLBACK (cb_message_error), NULL);
g_signal_connect (bus, "message::eos", G_CALLBACK (cb_message_eos), NULL);
[..]
GStreamer具有一些可以通过总线传递的预定义消息类型。 所有消息都有消息源,类型和时间戳。消息源可以被用于查看哪些元件发出的消息。
以下是所有消息的列表,并简要说明了它们的作用以及如何解析消息内容。
gst_message_parse_error()
,gst_message_parse_warning ()
和gst_message_parse_info ()
来解析这些消息。使用后应释放错误和调试这些消息对象。gst_message_parse_tag()
被用于解析tag列表,gst_tag_list_unref ()
当不再需要时应进行释放。gst_message_parse_state_changed ()
可用于解析旧状态和新状态的转变。gst_message_get_structure()
函数从返回的结构中提取“ buffer-percent”属性,可以从消息中手动提取媒体进度(以百分比为单位)。#include
/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
GstElement *pipeline;
GstElement *source;
GstElement *convert;
GstElement *sink;
} CustomData;
/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);
int main(int argc, char *argv[]) {
CustomData data;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret;
gboolean terminate = FALSE;
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Create the elements */
data.source = gst_element_factory_make ("uridecodebin", "source");
data.convert = gst_element_factory_make ("audioconvert", "convert");
data.sink = gst_element_factory_make ("autoaudiosink", "sink");
/* Create the empty pipeline */
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;
}
/* Build the pipeline. Note that we are NOT linking the source at this
* point. We will do it later. */
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;
}
/* Set the URI to play */
g_object_set (data.source, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
/* Connect to the pad-added signal */
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
/* Start playing */
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;
}
/* Listen to the bus */
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);
/* 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 (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:
/* We should not reach here */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}
} while (!terminate);
/* Free resources */
gst_object_unref (bus);
gst_element_set_state (data.pipeline, GST_STATE_NULL);
gst_object_unref (data.pipeline);
return 0;
}
/* This function will be called by the pad-added signal */
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 our converter is already linked, we have nothing to do here */
if (gst_pad_is_linked (sink_pad)) {
g_print ("We are already linked. Ignoring.\n");
goto exit;
}
/* Check the new pad's type */
new_pad_caps = gst_pad_get_current_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;
}
/* Attempt the link */
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:
/* Unreference the new pad's caps, if we got them */
if (new_pad_caps != NULL)
gst_caps_unref (new_pad_caps);
/* Unreference the sink pad */
gst_object_unref (sink_pad);
}
编译方法:
gcc basic-tutorial-3.c -o basic-tutorial-3 `pkg-config --cflags --libs gstreamer-1.0`
代码注释:
/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
GstElement *pipeline;
GstElement *source;
GstElement *convert;
GstElement *sink;
} CustomData;
由于本教程(以及大多数实际应用程序)都涉及回调,因此我们将所有数据放在一个结构体中,以便于传递处理。
/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);
这是信号处理函数,将在信号触发后调用,此处为函数预定义。
/* Create the elements */
data.source = gst_element_factory_make ("uridecodebin", "source");
data.convert = gst_element_factory_make ("audioconvert", "convert");
data.sink = gst_element_factory_make ("autoaudiosink", "sink");
创建元素。uridecodebin
将在内部实例化所有必要的元素(source,demuxer和decode),以将URI转换为原始音频或视频流。由于它包含demuxer,因此其src pad最初不可用,我们需要动态创建src pad。
audioconvert
用于转换不同的音频格式。
autoaudiosink
用于将音频流呈现到声卡。
if (!gst_element_link (data.convert, data.sink)) {
g_printerr ("Elements could not be linked.\n");
gst_object_unref (data.pipeline);
return -1;
}
在这里,我们将audioconvert
元素链接到autoaudiosink
元素,但是我们不将它们与uridecodebin
链接,因为此时uridecodebin
不包含src pad。
/* Set the URI to play */
g_object_set (data.source, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
我们将文件的URI设置为通过属性播放。
信号处理
/* Connect to the pad-added signal */
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
GSignals
是GStreamer的一个关键点,它们使你可以在发生某些事情的时候(通过回调)得到通知并进行处理。
在这一行中,通过g_signal_connect
监听"pad-added"信号,并提供要使用的回调函数pad_added_handler
和数据指针。GStreamer对此数据指针不执行任何操作,它只是将其转发给回调,因此我们可以与其共享信息。在这种情况下,我们传递一个指向CustomData
的专门构建的指针。
GstElement
生成的信号可以在其文档中找到,也可以使用gst-inspect-1.0
找到。
/* Start playing */
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;
}
/* Listen to the bus */
bus = gst_element_get_bus (data.pipeline);
将管道设置为PLAYING
状态,并开始侦听总线中是否有消息(例如ERROR
或EOS
)。
回调
当uridecodebin
元素最终有足够的流信息时,它将创建src pad,并触发“pad-added”信号。此时将执行回调函数。
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
src
是触发信号的GstElement
,在此示例中,它只能是uridecodebin
。信号处理程序的第一个参数始终是触发它的对象。
new_pad
是刚刚添加到src
元素中的GstPad
,通常这是我们要链接的pad。
data
是我们在连接信号时提供的指针。在此示例中,我们使用它传递CustomData
指针。
GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
从CustomData
中,我们提取出convert元素,然后使用gst_element_get_static_pad ()
检索其sink pad。我们需要把uridecodebin
新生成的new_pad
(src pad)和audioconvert
的sink_pad链接到一起。
/* If our converter is already linked, we have nothing to do here */
if (gst_pad_is_linked (sink_pad)) {
g_print ("We are already linked. Ignoring.\n");
goto exit;
}
uridecodebin
会尝试创建多个pad,并且对于每个pad,都会调用此回调。一旦我们已经链接成功,上面的代码将阻止继续链接。
/* Check the new pad's type */
new_pad_caps = gst_pad_get_current_caps (new_pad, NULL);
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;
}
现在,我们将检查新创建的src pad将要输出的数据类型,我们仅对产生音频的pad感兴趣,因此使用g_str_has_prefix (new_pad_type, “audio/x-raw”)进行过滤。
gst_pad_get_current_caps()
检索pad 的当前capabilities(即其当前输出的数据类型)。gst_caps_get_structure()
检索new_pad_caps的GstStructure
结构体数据 。gst_structure_get_name()
得到new pad的类型。
如果名称不是audio/x-raw
,则该名称不是经过解码的音频pad,因此我们忽略它。否则,尝试链接pad:
/* Attempt the link */
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()`一样,必须从src链接到sink,并且两个pad都必须位于同一bin(或管道)中。