多媒体框架GStreamer

前言

注:原文出处多媒体框架GStreamer

GStreamer is a library for constructing graphs of media-handling components. The applications it supports range from simple Ogg/Vorbis playback, audio/video streaming to complex audio (mixing) and video (non-linear editing) processing.

GStreamer是一个轻便的多媒体框架,在TX1上默认并未提供V4L2驱动,需要手动安装,而且这个驱动是基于GStreamer的。此外,在TX1上支持的H.264和H.265硬解码也是通过GStreamer的。

基本概念

Pipeline

GStreamer是通过管道和插件实现各种功能或构建各种流媒体应用的。
管道的概念在shell中已经很常见了,不过GStreamer中的管道符号不是|而是!。GStreamer中将很多元素串联起来,类似管道的命令,将前者输出作为后者输入。
如:

1
gst-launch-1.0 filesrc location=a.mp3 ! decodebin ! audioconvert ! alsasink

其中,每个元素说明如下:

  • filesrc: 从本地磁盘加载了一个文件,使用该元素时你设置了location 属性指向该文件。
  • decodebin: 使用该元素从filesrc解码。会自动检测文件的类型并在后台构造一些GStreamer元素来解码。
  • audioconvert: 音频转换元素。
  • alsasink: 将音频使用ALSA传递给声卡。

Element

元素是GStreamer内置可使用在管道上的命令,GStreamer通过各种元素完成任务。可以使用gst-inspect-1.0命令来查看可使用的元素。当创建Pipeline时,需要为各个Element设置各种属性。
GStreamer将GstElement细分成如下几类:

  • Source Element: 数据源元素。只有输出端,它仅能用来产生供管道消费的数据,而不能对数据做任何处理。一个典型的数据源元素的例子是音频捕获单元,它负责从声卡读取原始的音频数据,然后作为数据源提供给其它模块使用。
  • Filter Element: 过滤器元素。既有输入端又有输出端,它从输入端获得相应的数据,并在经过特殊处理之后传递给输出端。一个典型的过滤器元素的例子是音频编码单元,它首先从外界获得音频数据,然后根据特定的压缩算法对其进行编码,最后再将编码后的结果提供给其它模块使用。
  • Sink Element: 接收器元素。只有输入端,它仅具有消费数据的能力,是整条媒体管道的终端。一个典型的接收器元素的例子是音频回放单元,它负责将接收到的数据写到声卡上,通常这也是音频处理过程中的最后一个环节。

Pad

一般元素都有输入输出接口,即pad。可将各个元素视作黑盒,则一般元素都有一个输入pad和输出pad,即过滤器元素。输入pad称为sink,输出pad称为src。管道命令模型基本如下:

1
[src] ! [sink src] ! [sink src] ! [sink]

最左边的元素只有一个src pad用来提供信息(如filesrc)。接下来的几个元素接收信息并做一些处理,因此他们有sink和src pad(例如decodebin和audiocovert),最后一个元素只接收信息(例如alsasink)。使用gst-inspect-1.0命令查看一个元素的详细信息时,就可以看到该元素的pad信息。

注意可能与平时大家认为的概念有些不同的是,src pad是用来发送数据的端点,即数据的 输出端;而sink pad是用来接收数据的端点,即数据的输入端。

一般来说,src pad只能连接到sink pad。但ghost pad两端就要连接相同类型的pad。

Cap

每个元素的cap表示该元素可以接收什么样的信息,类似于V4L2中的cap。

Bin

GStreamer中的bin类似于c中的结构体,是可以存放多个元素的容器。例如管道是a ! b ! c ! d,可以把他们放进mybin,这样当使用mybin时其实是引用了a ! b ! c ! d。

Ghost pad

从名字上来看,ghost pad即特殊的pad。

当你创建了一个bin并在里面放置了很多元素时,该bin变成了你自定义的元素,该元素按顺序调用里面的元素。要做到这样,bin很自然地需要它自己的pad,它自己的pad会挂接到bin里面元素的pad上,这就是 ghost pad了。当你创建一个bin时,你创建了ghost pad并告诉他们要去挂接里面哪一个元素。

Message

Message是Pipeline用向外来主动报告自己的运行状态。Message会被发送到一个消息队列,即Pipeline的Bus。应用程序则是从Bus中获取message,并进行处理。

Event

Event是Pipeline用来通信的机制,分为上行、下行和双向Event。在定义了某个事件的响应后,也可以由应用程序直接向该插件(bin)发送事件。通过Event可以控制整个Pipeline的运行状态。

上行事件: 由sink插件向source插件方向传输。包括:

1
2
GST_EVENT_QOS
GST_EVENT_SEEK

下行事件: 由source插件向sink插件方向传输。包括:

1
2
GST_EVENT_EOS
GST_EVENT_NEWSEGMENT

双行事件:包括:

1
2
GST_EVENT_FLUSH_START
GST_EVENT_FLUSH_STOP

Signal

Signal是应用控制某一插件的运行状态,可以看做Glib对象的一个属性,属于同步操作,和Linux中的系统信号有差别。通过Signal可以让某个插件做一些对插件本身变量的操作,比如增加或删除一些维护信息等。

GStreamer程序处理

可以使用c或Python,Python中需要import gi,c中使用include <gst/gst.h>等头文件。
TX1中的GStreamer头文件可以在/usr/include/GStreamer-1.0目录下找到。

元素处理

Elements是具有一定功能的基本单元,主要组成如下:

1
2
3
4
udpsrc: 接受UDP数据(source产生数据)
rtph264depay: H264的RTP拆包(filter)。(rtp+h264包)->(h264包)
mpegtsmux: 打ts包(filter)。(h264包)->(ts包)
filesink: 将数据存入文件(sink消耗数据)

在应用程序中创建GstElement对象的方法是借助于工厂对象GstElementFactory。由于GStreamer框架提供了多种类型的GstElement对象,因此对应地提供了多种类型的GstElementFactory对象,它们是通过特定的工厂名称来进行区分的。
如:

1
2
GstElementFactory *factory;
factory = gst_element_factory_find ("mad");

该代码通过gst_element_factory_find()函数创建了一个名为mad的工厂对象。之后就可以通过gst_element_factory_create()函数来创建特定的GstElement对象了。

1
2
GstElement *element;
element = gst_element_factory_create (factory, "decoder");

函数gst_element_factory_create()在调用时有两个参数,分别是需要用到的工厂对象,以及即将创建的元素名。元素名可以用查询的办法获得,也可以通过传入空指针(NULL)来生成工厂对象的默认元素。

GStreamer使用了与GObject相同的机制来对属性(property)进行管理,包括查询(query)、设置(set)和读取(get)等。所有的GstElement对象都需要从其父对象GstObject那里继承名称(name)这一最基本的属性,这是因为像gst_element_factory_make()和gst_element_factory_create()这样的函数在创建工厂对象和元素对象时都会用到名称属性,通过调用gst_object_set_name()和gst_object_get_name()函数可以设置和读取GstElement对象的名称属性。

Pad处理

成功创建GstElement对象之后,可以通过gst_element_get_pad()获得该元素的指定pad。例如,下面的代码将返回element元素中名为src的pad:

1
2
GstPad *srcpad;
srcpad = gst_element_get_pad (element, "src");

需要的话也可以通过gst_element_get_pad_list()函数来查询指定元素中的所有pad。例如,下面的代码将输出elemen元素中所有pad的名称:

1
2
3
4
5
6
7
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);
}

与元素一样,pad的名称也能够动态设置或者读取,这是通过调用gst_pad_get_name()和gst_pad_set_name()函数来完成的。所有元素的pad都可以细分成输入pad和输出pad两种,其中输入pad只能接收数据但不能产生数据,而输出pad则正好相反。函数gst_pad_get_direction()可以获得指定pad的类型。GStreamer框架中的所有pad都必然依附于某个元素之上,调用gst_pad_get_parent()可以获得指定pad所属的元素,该函数的返回值是一个指向GstElement的指针。 Pad从某种程度上可以看成是元素的代言人,因为它要负责向外界描述该元素所具有的能力。GStreamer框架提供了统一的机制来让pad描述元素所具有的能力(capability),这是借助结构体_GstCaps来实现的:

1
2
3
4
5
6
7
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 */
};

GStreamer框架中的每个pad都可能对应于多个能力描述,它们能够通过函数gst_pad_get_caps()来获得。例如,下面的代码将输出pad中所有能力描述的名称及其MIME类型:

1
2
3
4
5
6
7
8
9
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;
}

Bin处理

在GStreamer应用程序中使用的bin主要有两种类型:

  • GstPipeline 管道是最常用到的容器,对于一个GStreamer应用程序来讲,其顶层bin必须是一条管道。
  • GstThread 线程的作用在于能够提供同步处理能力,如果GStreamer应用程序需要进行严格的音视频同步,一般都需要用到这种类型的bin。

GStreamer框架提供了两种方法来创建bin:一种是借助工厂方法,另一种则是使用特定的函数。下面的代码示范了如何使用工厂方法创建线程对象,以及如何使用特定函数来创建管道对象:

1
2
3
4
5
GstElement *thread, *pipeline;
// 创建线程对象,同时为其指定唯一的名称。
thread = gst_element_factory_make ("thread", NULL);
// 根据给出的名称,创建一个特定的管道对象。
pipeline = gst_pipeline_new ("pipeline_name");

Bin成功创建之后,就可以调用gst_bin_add()函数将已经存在的元素添加到其中来了:

1
2
3
4
5
GstElement *element;
GstElement *bin;
bin = gst_bin_new ("bin_name");
element = gst_element_factory_make ("mpg123", "decoder");
gst_bin_add (GST_BIN (bin), element);

从bin中找到特定的元素可以借助gst_bin_get_by_name()函数实现:

1
2
GstElement *element;
element = gst_bin_get_by_name (GST_BIN (bin), "decoder");

由于GStreamer框架中的一个bin能够添加到另一个bin中,因此有可能会出现嵌套bin的情况,gst_bin_get_by_name()函数在查找元素时会对嵌套的bin作递归查找。元素有添加到bin中后,在需要的时候还可以从中移出,通过调用gst_bin_remove()函数来完成:

1
2
GstElement *element;
gst_bin_remove (GST_BIN (bin), element);

Ghost pad

具有ghost pad的bin在行为上与元素是完全相同的,所有元素具有的属性它都具有,所有针对元素能够进行的操作也同样能够针对bin进行,因此在GStreamer应用程序中能够像使用元素一样使用这类bin。通常通过以下方式为bin添加一个ghost pad:

1
2
3
4
5
6
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");

元素连接

GStreamer框架中的元素是通过各自的pad连接起来的,如下所示:

1
2
3
4
5
6
7
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);

如果需要建立起连接的元素都只有一个输入pad和一个输出pad,那么更简单的做法是调用gst_element_link()函数直接在它们之间建立起连接,或者调用gst_element_unlink()函数断开它们之间的连接:

1
2
3
4
// 连接
gst_element_link (element1, element2);
// 断开
gst_element_unlink (element1, element2);

元素状态

每个元素一般会有四种状态:

标识 状态
NULL 这是所有元素的默认状态,表明它刚刚创建,还没有开始做任何事情。
READY 表明元素已经做好准备,随时可以开始处理流程。
PAUSED 表明元素因某种原因暂时停止处理数据。
PLAYING 表明元素正在进行数据处理。

所有的元素都从NULL状态开始,依次经历NULL、READY、PAUSED、PLAYING等状态间的转换。元素当前所处的状态可以通过调用gst_element_set_state()函数进行切换:

1
2
3
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状态时并不会释放其占用的资源。

Probe

应用程序可以通过探针(Probe)来探测某个插件的pad中流过的数据,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*******Callback handler when probe date received***********/
static gboolean
callback_have_data(GstPad *padsrc, GstBuffer *buffer, gpointer data){
gint iBufSize = 0;
gchar* pBuffer = NULL;

iBufSize = GST_BUFFER_SIZE(buffer);
pBuffer = (gchar*)GST_BUFFER_DATA(buffer);

static gint numBuf = 0;
g_print("\rBUF %d Size=%d ", numBuf++, iBufSize);
return TRUE;
}
GstPad *m_pad_concert_src = gst_element_get_static_pad(m_gst_convert, "src");
gst_pad_add_buffer_probe(m_pad_concert_src, G_CALLBACK(callback_have_data), NULL);
gst_object_unref(m_pad_concert_src);

该代码在m_pad_concert插件的src pad加一个探针,每当有buf到达时,就调用callback_have_data(),这里这个函数只是打印一下buf的大小,统计一下buf流过的个数。

例子

一个简单的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <gst/gst.h>
int main(int argc, char *argv[]) {
GstElement *pipeline;
GstBus *bus;
GstMessage *msg;
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Build the pipeline */
pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
/* Start playing */
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* Wait until error or EOS */
bus = gst_element_get_bus (pipeline);
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Free resources */
if (msg != NULL)
gst_message_unref (msg);
gst_object_unref (bus);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
return 0;
}

然后使用gcc进行编译,编译时需加上参数pkg-config –cflags –libs gstreamer-0.10。
其中,gst_init (&argc, &argv)用于初始化所有内部结构,检查插件是否可用,以及执行用于GStreamer的命令参数。
pipeline = gst_parse_launch (“playbin2 uri=http://docs.GStreamer.com/media/sintel_trailer-480p.webm", NULL)中,gst_parse_launch函数接受一个管道的文本表示并把它变成一个实际的管道。
playbin2是充当source和sink一个特殊的元素(element),并且能够实现整个管道。gst_element_set_state (pipeline, GST_STATE_PLAYING)函数设置Pipeline状态为播放状态。
gst_element_get_bus (pipeline); 函数获取管道的总线,gst_bus_timed_pop_filtered函数将阻塞,直到收到错误或EOS(End-Of-Stream)。

感谢你的阅读,如果文章对你有帮助,可以请作者喝杯茶!