本章将介绍GStreamer的基本概念。
理解这些概念对于你后续的学习非常重要,因为后续深入的讲解我们都假定你已经完全理解了这些概念。
3.1. 元件(Elements)
元件(element)是GStreamer中最重要的概念。
你可以通过创建一系列的元件(Elements),并把它们连接起来,从而让数据流在这个被连接的各个元件(Elements)之间传输。
每个元件(Elements)都有一个特殊的函数接口,对于有些元件(Elements)的函数接口它们是用于能够读取文件的数据,解码文件数据的。
而有些元件(Elements)的函数接口只是输出相应的数据到具体的设备上(例如,声卡设备)。
你可以将若干个元件(Elements)连接在一起,从而创建一个管道(pipeline)来完成一个特殊的任务,
例如,媒体播放或者录音。GStreamer已经默认安装了很多有用的元件(Elements),
通过使用这些元件(Elements)你能够构建一个具有多种功能的应用程序。
当然,如果你需要的话,你可以自己编写一个新的元件(Elements)。
对于如何编写元件(Elements)的话题在GStreamer Plugin Writer's Guide中有详细的说明。
3.2. 箱柜(Bins)和管道(pipelines)
箱柜(Bins)是一个可以装载元件(element)的容器。
管道(pipelines)是箱柜(Bins)的一个特殊的子类型,管道(pipelines)可以操作包含在它自身内部的所有元件(element)。
因为箱柜(Bins)本身又是元件(element)的子集,所以你能够象操作普通元件(element)一样的操作一个箱柜(Bins),
通过这种方法可以降低你的应用程序的复杂度。你可以改变一个箱柜(Bins)的状态来改变箱柜(Bins)内部所有元件(element)的状态。
箱柜(Bins)可以发送总线消息(bus messages)给它的 子集元件(element)(这些消息包括:错误消息(error messages),
标签消息(tag messages),EOS消息(EOS messages))。
管道(pipeline)是高级的箱柜(Bins)。当你设定管道的暂停或者播放状态的时候,数据流将开始流动,
并且媒体数据处理也开始处理。一旦开始,管道将在一个 单独的线程中运行,直到被停止或者数据流播放完毕。
3.3. 衬垫(Pads)
衬垫(Pads)在GStreamer中被用于多个元件的链接,从而让数据流能在这样的链接中流动。
一个衬垫(Pads)可以被看作是一个元件(element)插座或者端口,元件(element)之间的链接就是依靠着衬垫(Pads)。
衬垫(Pads)有处理特殊数据的能力:一个衬垫(Pads)能够限制数据流类型的通过。链接成功的条件是:
只有在两个衬垫(Pads)允许通过的数据类型一致的时候才被建立。
数据类型的设定使用了一个叫做caps negotiation的方法。数据类型被为一个GstCaps变量所描述。
下面的这个比喻可能对你理解衬垫(Pads)有所帮助。一个衬垫(Pads)很象一个物理设备上的插头。
例如一个家庭影院系统。一个家庭影院系统由一个功放(amplifier),一个DVD机,还有一个无声的视频投影组成。
我们需要连接DVD机到功放(amplifier),因为两个设备都有音频插口;
我们还需要连接投影机到DVD机上,因为 两个设备都有视频处理插口。
但我们很难将投影机与功放(amplifier)连接起来,因为他们之间处理的是不同的 插口。
GStreamer衬垫(Pads)的作用跟家庭影院系统中的插口是一样的。
对于大部分情况,所有的数据流都是在链接好的元素之间流动。
数据向元件(element)以外流出可以通过一个或者多个 source 衬垫(Pads),
元件(element)接受数据是通过一个或者多个sink 衬垫(Pads)来完成的。
Source元件(element)和sink元件(element)分别有且仅有一个 sink 衬垫(Pads)或者source 衬垫(Pads)。
数据在这里代表的是缓冲区(buffers) (GstBuffer对象描述了数据的缓冲区(buffers)的信息)和事件(events)
(GstEvent对象描述了数据的事件(events)信息)。
II.构建一个应用程序
在这一部分,我们将讨论GStreamer中的一些基本概念以及一些常用的对 象,像元件、衬垫和缓存等。
我们给这些对象以一种形象化的描述,相信这样会对我们在后边学习到如何构建一条管道时大有帮助。
首先你会对GStreamer的API有个粗略的认识,用这些API来构建一个基于元件的应用程序已经绰绰有余。
然后你 会学习到如何构建一个简单的基于命令行的应用程序。
注意:在这部分我们会了解一些底层(low-level)的API以及GStreamer的 一些概念。
如果你立马想构建一个应用程序,你可能会使用一些高层(higher-level)的API,它们会在这手册的后部分被提到。
第4章. 初始化GStreamer
当你准备写一个GStreamer应用程序时,你仅需要通过包含头文件gst/gst.h 来访问库函数。
除此之外,不要忘记初始化GStreamer库。
4.1. 简易初始化
在GStreamer库被使用前,主应用程序中应该先调用函数gst_init,
这个函数将会对GStreamer库做一些必要的初始化工作,
同时 也能够对GStreamer的命令行参数进行解析。
一个典型的初始化GStreamer库的代码 [1] 如下所示:
例4-1. 初始化GStreamer
#include <gst/gst.h>
int
main (int argc,
char *argv[])
{
const gchar *nano_str;
guint major, minor, micro, nano;
gst_init (&argc, &argv);
gst_version (&major, &minor, µ, &nano);
if (nano == 1)
nano_str = "(CVS)";
else if (nano == 2)
nano_str = "(Prerelease)";
else
nano_str = "";
printf ("This program is linked against GStreamer %d.%d.%d %s\n",
major, minor, micro, nano_str);
return 0;
}
你可以使用GST_VERSION_MAJOR, GST_VERSION_MINOR以及GST_VERSION_MICRO 三个宏得到你的GStreamer版本信息,
或者使用函数gst_version得到当前你所调用的程序库的版本信息。
目前GStreamer使用了一种 保证主要版本和次要版本中API-/以及ABI兼容的策略。
当命令行参数不需要被GStreamer解析的时候,你可以在调用函数gst_init时使用2个NULL参数。
注[1] 这个例子中的代码可以直接提取出来,并在GStreamer的examples/manual目录下可以找到。
4.2. 使用GOption接口来初始化
你同样可以使用GOption表来初始化你的参数。例子如下:
例4-2. 使用GOption接口来初始化
#include <gst/gst.h>
int
main (int argc,
char *argv[])
{
gboolean silent = FALSE;
gchar *savefile = NULL;
GOptionContext *ctx;
GError *err = NULL;
GOptionEntry entries[] = {
{ "silent", 's', 0, G_OPTION_ARG_NONE, &silent,
"do not output status information", NULL },
{ "output", 'o', 0, G_OPTION_ARG_STRING, &savefile,
"save xml representation of pipeline to FILE and exit", "FILE" },
{ NULL }
};
ctx = g_option_context_new ("- Your application");
g_option_context_add_main_entries (ctx, entries, NULL);
g_option_context_add_group (ctx, gst_init_get_option_group ());
if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
g_print ("Failed to initialize: %s\n", err->message);
g_error_free (err);
return 1;
}
printf ("Run me with --help to see the Application options appended.\n");
return 0;
}
如例子中的代码所示,你可以通过 GOption 表来定义你的命令行选项。
将表与由gst_init_get_option_group函数返回的选项组一同传给GLib初始化函数。
通过使用GOption表来初始化GSreamer,你的程序还可以解析除标准GStreamer选项以外的命令行选项.
第5章. 元件(Element)
对程序员来说,GStreamer 中最重要的一个概念就是 GstElement 对象。
元件是构建一个媒体管道的基本块。所有上层(high-level)部件都源自GstElement对象。
任何一个解码器编码器、分离器、视频/音频输出部件实际上都是一个 GstElement对象。
5.1. 什么是元件?
对程序员来说,元件就像一个黑盒子。
你从元件的一端输入数据,元件对数据进行一些处理,然后数据从元件的另一段输出。
拿一个解码元件来说,你输入一 些有特定编码的数据,元件会输出相应的解码数据。
在下一章 (Pads and capabilities),你将学习到更多关于对元件进行数据输入输出的知识,
以及如何在你的程序中实现数据的输入输出。
5.1.1.源元件
源元件(Source elements)为管道产生数据,比如从磁盘或者声卡读取数据。
图 5-1 形象化的源元件。我们总是将源衬垫(source pad)画在元件的右端。
图 5-1.形象化的源元件
源元件不接收数据,仅产生数据。
你可从上图中明白这一点,因为上图仅有一个源衬垫(右端),同样的, 源衬垫也仅产生数据(对外部而言)。
5.1.2. 过滤器(filters)、转换器(convertors)、分流器(demuxers)、整流器(muxers)以及编解码器(codecs)
过滤器(Filters)以及类过滤元件(Filter-like elements)都同时拥有输入和输出衬垫。
他们对从输入衬垫得到的数据进行操作,然后将数据提供给输出衬垫。
音量元件(filter)、视频转换器 (convertor)、Ogg分流器或者 Vorbis 解码器都是这种类型的元件。
类过滤元件可以拥有任意个的源衬垫或者接收衬垫。
像一个视频分流器可能有一个接收衬垫以及多个(1-N)源衬垫,每个接收衬垫对应一种元数据流 (elementary stream)。
相反地,解码器只有一个源衬垫及一个接收衬垫。
图 5-2. 形象化的过滤元件
图5-2 形象化了类过滤元件。
这个特殊的元件同时拥有源端和接收端。
接收输入数据的接收衬垫在元件的左端,源衬垫在右端。
图 5-3. 形象化的拥有多个输出的过滤元件
图5-3 显示了另一种了类过滤元件。它有多个输出衬垫(source pad)。
Ogg分流器是个很好的实例。因为Ogg流包含了视频和音频。
一个源衬垫可能包含视频元数据流,另一个则包含音频元数据流。
当一个新的衬垫被创 建时,分流器通常会产生一个信号。程序员可以在信号处理事件中处理新的元数据流。
5.1.3.接收元件
接收元件是媒体管道的末端,它接收数据但不产生任何数据。
写磁盘、利用声卡播放声音以及视频输出等都是由接收元件实现的。
图 5-4 显示了接收元件。
图 5-4. 形象化的接收元件
5.2. 创建一个GstElement对象
创建一个元件的最简单的方法是通过函数gst_element_factory_make ()。
这个函数使用一个已存在的工厂对象名和一个新的元件名来创建元件。
创建完之后, 你可以用新的元件名在箱柜(bin)中查询得到这个元件。
这个名字同样可以用来调试程序的输 出。你可以通过传递 NULL 来得到一个默认的具有唯一性的名字。
当你不再需要一个元件时,你需要使用 gst_object_unref ()来对它进行解引用。
这会将一个元件的引用数减少1。任何一个元件在创建时,其引用记数为1。当其引用记数为0时,该元件会被销毁。
下面的例子[1] 显示了如果通过一个fakesrc工厂对象来创建一个名叫source的元件。
程序会检查元件是否创建成功。检查完毕后,程序会销毁元件。
#include <gst/gst.h>
int
main (int argc,
char *argv[])
{
GstElement *element;
/* init GStreamer */
gst_init (&argc, &argv);
/* create element */
element = gst_element_factory_make ("fakesrc", "source");
if (!element) {
g_print ("Failed to create element of type 'fakesrc'\n");
return -1;
}
gst_object_unref (GST_OBJECT (element));
return 0;
}
gst_element_factory_make 是2个函数的速记。
一个GstElement 对象由工厂对象创建而来。
为了创建一个元件,你需要使用一个唯一的工厂对象名字来访问一个 GstElementFactory对象。
gst_element_factory_find ()就 是做了这样的事。
下面的代码段创建了一个工厂对象,这个工厂对象被用来创建一个fakesrc元件 —— 伪装的数据源。
函数 gst_element_factory_create() 将会使用元件工厂并根据给定的名字来创建一个元件。
#include <gst/gst.h>
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;
}
gst_object_unref (GST_OBJECT (element));
return 0;
}
注[1] 这个例子中的代码可以直接提取出来,并在GStreamer的examples/manual目录下可以找到。
5.3. 使用元件作为GObject 对象
GstElement的属性大多通过标准的 GObject 对象实现的。
使用 GObject 的方法可以对GstElement实行查询、设置、获取属性的值。同样 GParamSpecs 也被支持。
每个 GstElement 都从其基类 GstObject 继承了至少一个“名字”属性。
这个名字属性将在函数gst_element_factory_make ()或者函数gst_element_factory_create ()中使用到。
你可通过函数 gst_object_set_name 设置该属性,通过 gst_object_get_name 得到一个对象的名字属性。
你也可以通过下面的方法来得到一个对象的名字属性。
#include <gst/gst.h>
int
main (int argc,
char *argv[])
{
GstElement *element;
gchar *name;
/* init GStreamer */
gst_init (&argc, &argv);
/* create element */
element = gst_element_factory_make ("fakesrc", "source");
/* 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));
return 0;
}
大多数的插件(plugins)都提供了一些额外的方法,这些方法给程序员提供了更多的关于该元件的注册信息或配置信息。
gst-inspect 是一个用来查询特定元件特性(properties)的实用工具。
它也提供了诸如函数简短介绍,参数的类型及其支持的范围等信息。关于 gst-inspect 更详细的信息请参考附录。
关于GObject特性更详细的信息,我们推荐你去阅读 GObject手册 以及 Glib 对象系统介绍.
GstElement对象同样提供了许多的 GObject 信号方法来实现一个灵活的回调机制。
你同样可以使用 gst-inspect来检查一个特定元件所支持的信号。
总之,信号和特性是元件与应用程序交互的最基本的方式。
5.4. 深入了解元件工厂
在前面的部分,我们简要介绍过 GstElementFactory 可以用来创建一个元件的实例,
但是工厂元件不仅仅只能做这件事,工厂元件作为在 GStreamer 注册系统中的一个基本类型,
它可以描述所有的插件(plugins)以及由GStreamer创建的元件。这意味着工厂元件可以应用于一些自动元件实例,
像自动插件(autopluggers); 或者创建一个可用元件列表,像管道对应用程序的类似操作(像GStreamer Editor) 。
5.4.1.通过元 件工厂得到元件的信息
像gst-inspect 这样的工具可以给出一个元件的概要:
插件(plugin)的作者、
描述性的元件名称(或者简称)、
元件的等级(rank)
以及元件的类别(category)。
类别可以用来得到一个元件的类型,这个类型是在使用工厂元件创建该元件时做创建的。
例如类别可以是 Codec/Decoder/Video(视频解码器)、Source/Video(视频发生器)、Sink/Video(视频输出器)。
音频也有类似的类别。同样还存在 Codec/Demuxer和Codec/Muxer,甚至更多的类别。
Gst-inspect将会列出当前所有的工厂对象,gst-inspect <factory-name> 将会列出特定工厂对象的所有概要信息。
#include <gst/gst.h>
int
main (int argc,
char *argv[])
{
GstElementFactory *factory;
/* init GStreamer */
gst_init (&argc, &argv);
/* get factory */
factory = gst_element_factory_find ("audiotestsrc");
if (!factory) {
g_print ("You don't have the 'audiotestsrc' element installed!\n");
return -1;
}
/* display information */
g_print ("The '%s' element is a member of the category %s.\n"
"Description: %s\n",
gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)),
gst_element_factory_get_klass (factory),
gst_element_factory_get_description (factory));
return 0;
}
你可以通过gst_registry_pool_feature_list (GST_TYPE_ELEMENT_FACTORY)得到所有在GStreamer中注册过的工厂元件。
5.4.2. 找出元件所包含的衬垫
工厂元件最有用处的功能可能是它包含了对元件所能产生的衬垫的一个详细描述,
以及这些衬垫的功能(以行外话讲: 就是指这些衬垫所支持的媒体类型),
而得到 这些信息是不需要将所有的插件(plugins)都装载到内存中。
这可用来给一个编码器提供一个编码列表,或在多媒体播放器自动加载插件时发挥作用。
目前 所有基于 GStreamer 的多媒体播放器以及自动加载器(autoplugger)都是以上述方式工作。
当我们在下一章:衬垫与功能( Pads and capabilities)中学习到 GstPad 与 GstCaps 时,会对上面的特性有个更清晰的了解。
5.5. 链接元件
通过将一个源元件,零个或多个类过滤元件,和一个接收元件链接在一起,你可以建立起一条媒体管道。
数据将在这些元件间流过。这是 GStreamer 中处理媒体的基本概念。
图5-5 用3个链接的元件形象化了媒体管道。
图5-5.形象化3个链接的元件
通过链接这三个元件,我们创建了一条简单的元件链。
元件链中源元件("element1")的输出将会是类过滤元件 ("element2")的输入。
类过滤元件将会对数据进行某些操作,
然后将数据输出给最终的接收元件("element3")。
把上述过程想象成一个简单的 Ogg/Vorbis 音频解码器。
源元件从磁盘读取文件。
第二个元件就是Ogg/Vorbis 音频解码器
。最终的接收元件是你的声卡,它用来播放经过解码的音频数据。
我们将在该手册的后部分用一个简单的图来构建这个 Ogg/Vorbis 播放器。
上述的过程用代码表示为:
#include <gst/gst.h>
int
main (int argc,
char *argv[])
{
GstElement *pipeline;
GstElement *source, *filter, *sink;
/* init */
gst_init (&argc, &argv);
/* create pipeline */
pipeline = gst_pipeline_new ("my-pipeline");
/* create elements */
source = gst_element_factory_make ("fakesrc", "source");
filter = gst_element_factory_make ("identity", "filter");
sink = gst_element_factory_make ("fakesink", "sink");
/* must add elements to pipeline before linking them */
gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL);
/* link */
if (!gst_element_link_many (source, filter, sink, NULL)) {
g_warning ("Failed to link elements!");
}
[..]
}
对于一些特定的链接行为,可以通过函数gst_element_link () 以及 gst_element_link_pads()来实现。
你可以使用不同的gst_pad_link_* ()函数来得到单个衬垫的引用并将它们链接起来。
更详细的信息请参考API手册。
注意:在链接不同的元件之前,你需要确保这些元件都被加在同一个箱柜中,
因为将一个元件加载到一个箱柜中会破坏该元件已存在的一些链接关系。
同时,你不能直接链接不在同一箱柜或管道中的元件。
如果你想要连接处于不同层次中的元件或衬垫,你将使用到精灵衬垫(关于精灵衬垫更多的信息将在后续章节中讲到) 。
5.6. 元件状态
一个元件在被创建后,它不会执行任何操作。所以你需要改变元件的状态,使得它能够做某些事情。
Gstreamer中,元件有四种状态,每种状态都有其特定的意义。
这四种状态为:
. GST_STATE_NULL: 默认状态。该状态将会回收所有被该元件占用的资源。
. GST_STATE_READY: 准备状态。
元件会得到所有所需的全局资源,这些全局资源将被通过该元件的数据流所使用。
例如打开设备、分配缓存等。但在这种状态下,数据流仍未开始被处 理,
所以数据流的位置信息应该自动置0。
如果数据流先前被打开过,它应该被关闭,并且其位置信息、特性信息应该被重新置为初始状态。
. GST_STATE_PAUSED: 在这种状态下,元件已经对流开始了处理,但此刻暂停了处理。
因此该状态下元件可以修改流的位置信息,读取或者处理流数据,
以及一旦状态变为 PLAYING,流可以重放数据流。这种情况下,时钟是禁止运行的。
总之, PAUSED 状态除了不能运行时钟外,其它与 PLAYING 状态一模一样。
处于 PAUSED 状态的元件会很快变换到 PLAYING 状态。
举例来说,视频或音频输出元件会等待数据的到来,并将它们压入队列。
一旦状态改变,元件就会处理接收到的数据。同样,视频接收元件能够播放数据的第 一帧。
(因为这并不会影响时钟)。自动加载器(Autopluggers)可以对已经加载进管道的插件进行这种状态转换。
其它更多的像codecs或者 filters这种元件不需要在这个状态上做任何事情。
. GST_STATE_PLAYING: PLAYING 状态除了当前运行时钟外,其它与 PAUSED 状态一模一样。
你可以通过函数gst_element_set_state()来改变一个元件的状态。
你如果显式地改变一个元件的状态,GStreamer可能会 使它在内部经过一些中间状态。
例如你将一个元件从 NULL 状态设置为 PLAYING 状态,
GStreamer在其内部会使得元件经历过 READY 以及 PAUSED 状态。
当处于GST_STATE_PLAYING 状态,管道会自动处理数据。
它们不需要任何形式的迭代。 GStreamer 会开启一个新的线程来处理数据。
GStreamer 同样可以使用 GstBus在管道线程和应用程序现成间交互信息。
详情请参考 第 7 章。
第6章. 箱柜(Bins)
箱柜是一种容器元件。你可以往箱柜中添加元件。由于箱柜本身也是一种元件,所以你可以像普通元件一样 操作箱柜。
因此,先前关于元件(Elements) 那章的内容同样可以应用于箱柜。
6.1.什么是箱柜
箱柜允许你将一组有链接的元件组合成一个大的逻辑元件。你不再需要对单个元件进行操作,而仅仅操作箱柜。
当你在构建一个复杂的 管道时,你会发现箱柜的巨大优势,因为它允许你将复杂的管道分解成一些小块。
箱柜同样可以对包含在其中的元件进行管理。
它会计算数据怎样流入箱柜,并对流入的数据流制定一个最佳的计划(generate an optimal plan)。
计划制定(Plan generation)是GStreamer中最复杂的步骤之一。你可从16.2部分更详细地了解这个部分。
图6-1. 形象化的箱柜
GStreamer程序员经常会用到的一个特殊的箱柜:
管道:是一种允许对所包含的元件进行安排(scheduling)的普通容器。
顶层(toplevel)箱柜必须为一个管道。因 此每个GStreamer应用程序都至少需要一个管道。
当应用程序启动后,管道会自动运行在后台线程中。
6.2. 创建箱柜
你可以通过使用创建其他元件的方法来创建一个箱柜,如使用元件工厂等。
当然也有一些更便利的函数来创建箱柜— (gst_bin_new()和 gst_pipeline_new ())。
你可以使用gst_bin_add()往箱柜中增加元件,使用gst_bin_remove()移除箱柜中的元件。
当你往箱 柜中增加一个元件后,箱柜会对该元件产生一个所属关系;
当你销毁一个箱柜后,箱柜中的元件同样被销毁 (dereferenced);
当你将一个元件从箱柜移除后,该元件会被自动销毁(dereferenced)。
#include <gst/gst.h>
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_pipeline_new ("my_bin");
source = gst_element_factory_make ("fakesrc", "source");
sink = gst_element_factory_make ("fakesink", "sink");
/* set up pipeline */
gst_bin_add_many (GST_BIN (bin), source, sink, NULL);
gst_bin_add (GST_BIN (pipeline), bin);
gst_element_link (source, sink);
[..]
}
有多种方法来查询一个箱柜中的元件。你可以通过函数gst_bin_get_list()得到一个箱柜中所有元件的一个列表。
详细信息请参考API手册 GstBin 部分。
6.3.自定义箱柜
程序员可以自定义能执行特定任务的箱柜。例如,你可以参照下面的代码写一个 Ogg/Vorbis 解码器。
int
main (int argc,
char *argv[])
{
GstElement *player;
/* init */
gst_init (&argc, &argv);
/* create player */
player = gst_element_factory_make ("oggvorbisplayer", "player");
/* set the source audio file */
g_object_set (player, "location", "helloworld.ogg", NULL);
/* start playback */
gst_element_set_state (GST_ELEMENT (player), GST_STATE_PLAYING);
[..]
}
自定义的箱柜可以同插件或XML解释器一起被创建。
你可从 Plugin Writers Guide得到更多关于创建自定义箱柜的信息。
gst-plugins-base中的playbin与decodebin元件都是自定义箱柜的例子。
第7章. 总线(Bus)
总线是一个简单的系统,它采用自己的线程机制将一个管道线程的消息分发到一个应用程序当中。
总线的优势是:当使用GStreamer的时候,应用程序不需要线程识别,即便GStreamer已经被加载了多个线程。
每一个管道默认包含一个总线,所以应用程序不需要再创建总线。
应用程序只需要在总线上设置一个类似于对象的信号处理器的消息处理器。
当主循环运行的时候,总线将会轮询这个消息处理器是否有新的消息,当消息被采集到后,总线将呼叫相应的回调函数来完成任务。
7.1. 如何使用一个总线(Bus)
使用总线有两种方法,如下:
. 运行GLib/Gtk+ 主循环 (你也可以自己运行默认的GLib的主循环),然后使用侦听器对总线进行侦听。
使用这种方法,GLib的主循环将轮询总线上是否存在新的消息,当存在新的消息的时候,总线会马上通知你。
在这种情况下,你会用到gst_bus_add_watch () / gst_bus_add_signal_watch ()两个函数。
当使用总线时,设置消息处理器到管道的总线上可以使用gst_bus_add_watch ()。
来创建一个消息处理器来侦听管道。每当管道发出一个消息到总线,这个消息处理器就会被触发,
消息处理器则开始检测消息信号类型(见下章)从而决定哪些事件将被处理。
当处理器从总线删除某个消息的时候,其返回值应为TRUE。
. 自己侦听总线消息,使用gst_bus_peek () 和/或 gst_bus_poll () 就可以实现。
#include <gst/gst.h>
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;
/* 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));
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_element_unref (pipeline);
gst_main_loop_unref (loop)
return 0;
}
理解消息处理器在主循环的线程context被调用是相当重要的,因为在总线上管道和应用程序之间的交互是异步,
所以上述方法无法适用于实时情况,比如音频轨道、无间隔播放(理论上的)、视频效果之间的交叉混合。
如果需要满足实时要求,实现上述功能,你就需要编写一个GStreamer插件来实现在管道中直接触发回调。
而对于一些初级的应用来说,使用从管道传递消息给应用程序的方法来实现应用程序与管道的交互,还是非常有用的。
这种方法的好处是GStreamer内部所有的线程将被应用程序隐藏,而开发人员也不必去担心线程问题。
注意:如果你使用了默认的GLib主循环来实现管道与应用程序的交互,建议你可以将“消息”信号链接到总线上,
而不必在管道上使用侦听器,这样对于所有可能的消息类型,你就不需用switch(),
只要连接到所需要的信号格式为"message::<type>",其中<Type>是一种消息类型(见下一节对消息类型的详细解释)
上面的代码段也可以这样写:
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);