【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

时间:2021-03-16 07:56:23

第3章. 基础概念介绍

本章将介绍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, &micro, &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.形象化的源元件
【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

源元件不接收数据,仅产生数据。

你可从上图中明白这一点,因为上图仅有一个源衬垫(右端),同样的, 源衬垫也仅产生数据(对外部而言)。

5.1.2. 过滤器(filters)、转换器(convertors)、分流器(demuxers)、整流器(muxers)以及编解码器(codecs)

过滤器(Filters)以及类过滤元件(Filter-like elements)都同时拥有输入和输出衬垫。

他们对从输入衬垫得到的数据进行操作,然后将数据提供给输出衬垫。

音量元件(filter)、视频转换器 (convertor)、Ogg分流器或者 Vorbis 解码器都是这种类型的元件。

类过滤元件可以拥有任意个的源衬垫或者接收衬垫。

像一个视频分流器可能有一个接收衬垫以及多个(1-N)源衬垫,每个接收衬垫对应一种元数据流 (elementary stream)。

相反地,解码器只有一个源衬垫及一个接收衬垫。

图 5-2. 形象化的过滤元件
【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

图5-2 形象化了类过滤元件。

这个特殊的元件同时拥有源端和接收端。

接收输入数据的接收衬垫在元件的左端,源衬垫在右端。

图 5-3. 形象化的拥有多个输出的过滤元件
【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

图5-3 显示了另一种了类过滤元件。它有多个输出衬垫(source pad)。

Ogg分流器是个很好的实例。因为Ogg流包含了视频和音频。

一个源衬垫可能包含视频元数据流,另一个则包含音频元数据流。

当一个新的衬垫被创 建时,分流器通常会产生一个信号。程序员可以在信号处理事件中处理新的元数据流。

5.1.3.接收元件

接收元件是媒体管道的末端,它接收数据但不产生任何数据。

写磁盘、利用声卡播放声音以及视频输出等都是由接收元件实现的。

图 5-4 显示了接收元件。
【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

图 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个链接的元件

 【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

通过链接这三个元件,我们创建了一条简单的元件链。

元件链中源元件("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. 形象化的箱柜
【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

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);

[..]

    

如果你没有使用GLib主循环,默认的消息信号将会无效,

然而,你可以导出一个小助手给集成提供你使用的主循环,启动产生总线信号。 

(详细见 documentation ) 

7.2. 消息类型(Message types)

GStreamer有几种由总线传递的预定义消息类型,这些消息都是可扩展的。

插件可以定义另外的一些消息,应用程序可以有这些消息的绝对代码或者忽略它们。

强烈推荐应用程序至少要处理错误消息并直接的反馈给用户。

所有的消息都有一个消息源、类型和时间戳。

这个消息源能被用来判断由哪个元件发出消息。

例如,在众多的消息中,应用程序只对上层的管道发出的消息感兴趣(如状态变换的提示)。

下面列出所有的消息种类、代表的意义,以及如何解析具体消息的内容。

  . 错误、警告和消息提示:

    它们被各个元件用来在必要的时候告知用户现在管道的状态。

    错误信息表明有致命的错误并且终止数据传送。

    错误应该被修复,这样才能继续管道的工作。

    警告并不是致命的,但是暗示有问题存在。

    消息提示用来告知非错误的信息。

    这些消息含有一个带有主要的错误类型和消息的GError,和一个任选的调试字符串。

    这两项都可以用gst_message_parse_error (), _parse_warning () 以及 _parse_info ()三个函数来提取其信息。

    当使用完毕后,错误和修正字符串都将被释放。

  . 数据流结束(End-of-stream)提示:

    当数据流结束的时候,该消息被发送。管道的状态不会改变,但是之后的媒体操作将会停止。

    应用程序可以通过收到这一消息来跳到播放列表的下一首歌。

    在数据流结束提示出现之后,仍然可以通过向后搜索来回到以前数据流前面的位置。

    之后的播放工作将会自动的继续执行。这个消息没有特殊的参数。

  . 标签(Tags):

    当元数据在数据流中被找到的时候,此消息被发送。

    一个管道可以发出多个Tag(如元数据的描述里有艺术家、歌曲名,另外的例子如流的信息采样率和比特率)。

    应用程序应该将元数据存储在缓存里。函

    数gst_message_parse_tag () 被用来解析tag的列表,当该列表不再使用的时候,函数gst_tag_list_free () 释放其相应的tag。

  . 状态转换(State-changes):

    当状态成功的转换时发送该消息。函数gst_message_parse_state_changed ()可以用来解析转换中的新旧状态。

  . 缓冲(Buffering):

    当缓冲网络数据流时此消息被发送。你可以通过函数gst_message_get_structure ()的返回值,来解析"buffer-percent" 属性,

    从而手动的得到缓冲进度(该缓冲进度以百分比的形式表示)。

  . 元件消息(Element messages):

    它是一组特殊的消息,用以标识一个特定元件。这样一组特殊的消息通常表述了一些额外的信息。

    元件的信息应该被详细的描述,因为这样一些元件信息将被作为消息而发送给其他元件。

    例如:'qtdemux' QuickTime 整流器(demuxer)应该把'redirect'信息保存于该元件信息当中,

    以便在某种特殊情况下将'redirect'元件信息发送出去。

  . Application-specific消息:

    我们可以将取得的消息结构解析出来,从而得到有关Application-specific消息的任何信息。

    通常这些信息是能够被安全地忽略。

应用程序消息主要用于内部,以备从一些线程排列信息到主线程应用的需求。

这些在使用元件信号的应用中非常实用(这些信号在数据流线程的上下文被发射)。 

第8章. 衬垫(Pads)及其功能

如我们在Elements一章中看到的那样,衬垫(Pads)是元件对 外的接口。

数据流从一个元件的源衬垫(source pad)到另一个元件的接收衬垫(sink pad)。

衬垫的功能(capabilities)决定了一个元件所能处理的媒体类型。

在这章的后续讲解中,我们将对衬垫的功能做更详细的说明。 (见第8.2节).

8.1. 衬垫(Pads)

一个衬垫的类型由2个特性决定:

  . 它的数据导向(direction)以及它的时效性(availability)。

正如我们先前提到 的,Gstreamer定义了2种衬垫的数据导向:源衬垫以及接收衬垫。

衬垫的数据导向这个术语是从元件内部的角度给予定义的: 元件通过它们的接收衬垫接收数据,通过它们的源衬垫输出数据。

如果通过一张图来形象地表述,接收衬垫画在元件的左侧,而源衬垫画在元件的右侧,数据从左向右流动。 [1]

衬垫的时效性比衬垫的数据导向复杂得多。一个衬垫可以拥有三种类型的时效性: 

  . 永久型(always)、随机型(sometimes)、请求型(on request)。

三种时效性的意义顾名思义: 永久型的衬垫一直会存在,

随机型的衬垫只在某种特定的条件下才存在(会随机消失的衬垫也属于随机型),

请求型的衬垫只在应用程序明确发出请求时才出现。

8.1.1. 动态(随机)衬垫

一些元件在其被创建时不会立刻产生所有它将用到的衬垫。

例如在一个Ogg demuxer的元件中可能发生这种情况。这个元件将会读取Ogg流,

每当它在Ogg流中检测到一些元数据流时(例如vorbis,theora ),它会为每个元数据流创建动态衬垫。

同样,它也会在流终止时删除该衬垫。动态衬垫在demuxer这种元件中可以起到很大的作用。

运行gst-inspect oggdemux只会显示出一个衬垫在元件中: 

一个名字叫作'sink'的接收衬垫,其它的衬垫都处于'休眠'中,

你可以从衬垫模板(pad template)中的"Exists: Sometimes"的属性看到这些信息。

衬垫会根据你所播放的Ogg文件的类型而产生,认识到这点 对于你创建一个动态管道特别重要。

当元件通过它的随机型(sometimes)衬垫模板创建了一个随机型(sometimes)的衬垫的时侯,

你可以通过对该元件绑定一个信号处理器(signal handler),通过它来得知衬垫被创建。下面一段代码演示了如何这样做:

名叫'sink'的接收衬垫,其它的衬垫都处于'休眠'中,显而易见这是衬垫”有时存在”的特性。

衬垫会根据你所播放的Ogg文件的类型而产生,这点在你 准备创建一个动态管道时显得特别重要,

当元件创建了一个”有时存在”的衬垫时,你可以通过对该元件触发一个信号处理器(signal handler) 来得知衬垫被创建。

下面一段代码演示了如何这样做:

#include <gst/gst.h>

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);

[..]

}

      

8.1.2. 请求衬垫

元件同样可以拥有请求衬垫(request pads)。这种衬垫不是自动被创建,而是根据请求被创建的。

这在多路复用(multiplexers)类型的元件中有很大的用处。例如 aggregators以及tee元件。

Aggregators 元件可以把多个输入流合并成一个输出流; 

tee元件正好相反,它只有一个输入流,然后根据请求把数据流发送到不同的输出衬垫

。只要应用程序需要另一份数据流,它可以简单的从tee元件请求到一个 输出衬垫。

下面一段代码演示了怎样在一个”tee”元件请求一个新的输出衬垫:

static void

some_function (GstElement *tee)

{

  GstPad * pad;

  gchar *name;

  pad = gst_element_get_request_pad (tee, "src%d");

  name = gst_pad_get_name (pad);

  g_print ("A new pad %s was created\n", name);

  g_free (name);

  /* here, you would link the pad */

[..]

  /* and, after doing that, free our reference */

  gst_object_unref (GST_OBJECT (pad));

}

      

gst_element_get_request_pad()方法可以从一个元件中得到一个衬垫,这个衬垫基于衬垫模板的名字(pad template)。

同样可以请求一个同其它衬垫模板兼容的衬垫,这点在某些情况下非常重要。

比如当你想要将一个元件连接到一个多路复用型的元件时,你就需要请求一个带兼容性的衬垫。

gst_element_get_compatible_pad()方法可以得到一个带兼容性的衬垫。

下面一段代码将从一个基于Ogg的带多输入衬垫的元件中请求一个带兼容性的衬垫。

static void

link_to_multiplexer (GstPad     *tolink_pad,

    GstElement *mux)

{

  GstPad *pad;

  gchar *srcname, *sinkname;

  srcname = gst_pad_get_name (tolink_pad);

  pad = gst_element_get_compatible_pad (mux, tolink_pad);

  gst_pad_link (tolinkpad, pad);

  sinkname = gst_pad_get_name (pad);

  gst_object_unref (GST_OBJECT (pad));

  g_print ("A new pad %s was created and linked to %s\n", srcname, sinkname);

  g_free (sinkname);

  g_free (srcname);

}

      

注[1]事实上,数据流可以逆流(upstream)地在一个元件中从源衬垫到接收衬垫。

但数据总是从一个元件的源衬垫到另一个元件的接收衬垫。

8.2.衬垫(Pads)的性能

由于衬垫对于一个元件起了非常重要的作用,因此就有了一个术语来描述能够通过衬垫或当前通过衬垫的数据流。

这个术语就是功能 (capabilities)。在这里,我们将简要介绍什么是衬垫的功能以及怎么使用它们。

这些足以使我们对这个概念有个大 致的了解。

如果想要对衬垫的功能有更深入的了解,并知道在GStreamer 中定义的所有的衬垫的功能,

请参考插件开发手册 Plugin Writers Guide。

衬垫的功能(capabilities)是与衬垫模板(pad templates)以及衬垫实例相关联的。

对于衬垫模板,衬垫的功能(capabilities)描述的是:

  . 当通过该衬垫模板创建了一个衬垫后,该衬垫允许通过的媒体类型。

对于衬垫实例,功能可以描述所有可能通过该衬垫的媒体类型(通常是该衬垫实例所属的衬垫模板的功能的一份拷贝),

或者是当前已经通过该衬垫的流媒体类型。前一种情况,该衬垫实例还未有任何数据流在其中通过。

8.2.1. 分解功能

衬垫的功能通过GstCaps 对象来进行描述。一个GstCaps对象会包含一个或多个 GstStructure。

一个 GstStructure描述一种媒体类型。

一个被数据流通过的衬垫(negotiated pad)存在功能集(capabilities set),每种功能只包含一个GstStructure结构。

结构中只包含固定的值。但上述约束并不对尚未有数据流通过的衬垫(unnegotiated pads)或衬垫模板有效。

下面给出了一个例子,你可以通过运行 gst-inspect vorbisdec 看到"vorbisdec" 元件的一些功能。

你可能会看到2个衬垫: 源衬垫和接收衬垫,2个衬垫的时效性都是永久型,并且每个衬垫都有相应的功能描述。

接收衬垫将会接收vorbis编码的音频数据,其 mime-type显示为"audio/x-vorbis"。

源衬垫可以将解码后的音频数据采样(raw audio samples)发送给下一个元件,其 mime-type显示为为"audio/x-raw-int"。

源衬垫的功能描述中还包含了一些其它的特性: 音频采样率(audio samplerate)、声道数、以及一些你可能并不太关心的信息。

Pad Templates:

  SRC template: 'src'

    Availability: Always

    Capabilities:

      audio/x-raw-float

                   rate: [ 8000, 50000 ]

               channels: [ 1, 2 ]

             endianness: 1234

                  width: 32

          buffer-frames: 0

 

  SINK template: 'sink'

    Availability: Always

    Capabilities:

      audio/x-vorbis

      

8.2.2. 特性与值

特性(Properties)用来描述功能中的额外信息(注:除数据流类型之外的信息)。

一条特性由一个关键字和一个值组 成。下面是一些值的类型:

基本类型,几乎涵盖了Glib 中的所有GType类型。这些类型为每条特性(Properties)指明了一个明确,非动态的值。

例子如下所示:

  . 整型(G_TYPE_INT): 明确的数值(相对范围值)。

  . 布尔类型:(G_TYPE_BOOLEAN):  TRUE 或FALSE。

  . 浮点类型:(G_TYPE_FLOAT): 明确的浮点数。

  . 字符串类型:(G_TYPE_STRING): UTF-8 编码的字符串。

  . 分数类型:(GST_TYPE_FRACTION): 由整数做分子分母的分数。

  . 范围类型(Range types):由GStreamer注册的且属于GTypes的一种数据类型。

    它指明了一个范围值。范围类型通常被用来指示支持的音频采样率范围或者支持 的视频文件大小范围。

    GStreamer 中又有2种不同类型的范围值。

  . 整型范围值(GST_TYPE_INT_RANGE): 用最大和最小边界值指明了一个整型数值范围。

    举例来说: "vorbisdec"元件的采样率范围为8000-50000。

  . 浮点范围值(GST_TYPE_FLOAT_RANGE): 用最大和最小边界值指明了一个浮点数值范围。

  . 分数范围值(GST_TYPE_FRACTION_RANGE): 用最大和最小边界值指明了一个分数数值范围。

  . 列表类型(GST_TYPE_LIST):可以在给定的列表中取任何一个值。

    示例:某个衬垫的功能如果想要表示其支持采样率为44100Hz以及48000Hz的数据,

    它可以用一个包含44100和48000的列表 数据类型。

  . 数组类型(GST_TYPE_ARRAY): 一组数据。数组中的每个元素都是特性的全值(full value)。

    数组中的元素必须是同样的数据类型。这意味着一个数组可以包含任意的整数,整型的列表,整型范围的组合。

    对于浮点数与字符串类型也是如此, 但一个数组不能同时包含整数与浮点数。

    示例: 对于一个多于两个声道的音频文件,其声道布局(channel layout)需要被特别指明。

    (对于单声道和双声道的音频文件除非明确指明在特性中指明其声道数,否则按默认处理)。

    因此声道布局应该用一个枚举数组类 型来存储。每个枚举值代表了一个喇叭位置。

    与 GST_TYPE_LIST类型不一样的是,数组类型是作为一个整体来看待的。

8.3.衬垫(Pads)性能的用途

衬垫的功能(Capabilities)(简称 caps)描述了两个衬垫之间的数据流类型,或者它们所支持的数据流类型。功能主要用于以下用途:

  . 自动填充(Autoplugging): 

    根据元件的功能自动找到可连接的元件。所有的自动充填器(autopluggers)都采用的这种方法。

  . 兼容性检测(Compatibility detection): 

    当两个个衬垫连接时,GStreamer 会验证它们是否采用的同样的数据流格式进行交互。

    连接并验证两个衬垫是否兼容的过程叫”功能谈判”(caps negotiation)。

  . 元数据(Metadata): 通过读取衬垫的功能(capabilities),应用程序能够提供有关当前流经衬垫的正在播放的媒体类型信息。

    而这个信息我们叫做元数据(Metadata)。

  . 过滤(Filtering): 应用程序可以通过衬垫的功能(capabilities)来给两个交互的衬垫之间的媒体类型加以限制,

    这些被限制的媒体类型的集合应该是两个交互的衬垫共同支持的格式集的子集。

    举例来说:应用程序可以使用"filtered caps"指明两个 交互的衬垫所支持的视频大小(固定或不固定)。

    在本手册的后面部分 Section 18.2,你可以看到一个使用带过滤功能(filtered caps)衬垫的例子。

    你可以往你的管道中插入一个 capsfilter元件,并设置其衬垫的功能(capabilities)属性,

    从而实现衬垫的功能(capabilities)的过滤。功能过滤器(caps filters)一般放在一些转换元件后面,

    将数据在特定的位置强制转换成特定的输出格式。

    

这些转换元件有: audioconvert、audioresample、ffmpegcolorspace 和 videoscale。

8.3.1. 使用衬垫的功能(capabilities)来操作元数据

一个衬垫能够有多个功能。功能(GstCaps)可以用一个包含一个或多个 GstStructures 的数组来表示。

每个GstStructures由一个名字字符串(比如说 "width")和相应的值(类型可能为G_TYPE_INT或GST_TYPE_INT_RANGE)构成。

值得注意的是,这里有三种不同的衬垫的功能(capabilities)需要区分:

衬垫的可能功能(possible capabilities)(通常是通过对衬垫模板使用gst-inspect 得到),

衬垫的允许功能(allowed caps)(它是衬垫模板的功能的子集,具体取决于每对交互衬垫的可能功能), 

衬垫的最后协商功能(lastly negotiated caps)(准确的流或缓存格式,只包含一个结构,以及没有像范围值或列表值这种不定变量)。

你可以通过查询每个功能的结构得到一个衬垫功能集的所有功能。你可以通过gst_caps_get_structure()得到一个功能的 GstStructure,

通过gst_caps_get_size()得到一个GstCaps对象中的GstStructure数量。

简易衬垫的功能(capabilities)(simple caps )是指仅有一个GstStructure,

固定衬垫的功能(capabilities)(fixed caps)指其仅有一个GstStructure,且没有可变的数据类型(像范围或列表等)。

另外还有两种特殊的功能 - 任意衬垫的功能(capabilities)(ANY caps)和空衬垫的功能(capabilities)(empty caps)。

下面的例子演示了如何从一个固定的视频功能提取出宽度和高度信息:

static void

read_video_props (GstCaps *caps)

{

  gint width, height;

  const GstStructure *str;

  g_return_if_fail (gst_caps_is_fixed (caps));

  str = gst_caps_get_structure (caps, 0);

  if (!gst_structure_get_int (str, "width", &width) ||

      !gst_structure_get_int (str, "height", &height)) {

    g_print ("No width/height available\n");

    return;

  }

  g_print ("The video size of this set of capabilities is %dx%d\n",

  width, height);

}

      

8.3.2. 功能(capabilities)应用于过滤器

由于衬垫的功能(capabilities)常被包含于插件(plugin)中,且用来描述衬垫支持的媒体类型,

所以程序员在为了在插件(plugin)间 进行交互时,尤其是使用过滤功能(filtered caps)时,

通常需要对衬垫功能有着基本的理解。当你使用过滤功能(filtered caps)或固定功能(fixation)时,

你就对交互的衬垫间所允许的媒体类型做了限制,限制其为交互的衬垫所支持的媒体类型的一个子集。

你可以通过 在管道中使用capsfilter元件实现上述功能,而为了做这些,你需要创建你自己的GstCaps。

这里我们给出最容易的方法是,你可以通过gst_caps_new_simple()函数来创建你自己的GstCaps。

static gboolean

link_elements_with_filter (GstElement *element1, GstElement *element2)

{

  gboolean link_ok;

  GstCaps *caps;

  caps = gst_caps_new_simple ("video/x-raw-yuv",

       "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'),

     "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()时,Gstreamer会自动创建一个capsfilter元件,将其加入到你的箱柜或管道中, 

并插入到你想要交互的两个元件间。(当你想要断开两个元件的连接时,你需要注意到这一点)。

在某些情况下,当你想要在两个衬垫间创建一个更精确的带过滤连接的功能集时,

你可以用到一个更精简的函数- gst_caps_new_full (): 

static gboolean

link_elements_with_filter (GstElement *element1, GstElement *element2)

{

  gboolean link_ok;

  GstCaps *caps;

                                                                                

  caps = gst_caps_new_full (

      gst_structure_new ("video/x-raw-yuv",

"width", G_TYPE_INT, 384,

"height", G_TYPE_INT, 288,

"framerate", GST_TYPE_FRACTION, 25, 1,

NULL),

      gst_structure_new ("video/x-raw-rgb",

"width", G_TYPE_INT, 384,

"height", G_TYPE_INT, 288,

"framerate", GST_TYPE_FRACTION, 25, 1,

NULL),

      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;

}

      

关于GstStructure以及GstCaps的详细API信息可以参考API参考手册。

8.4. 精灵衬垫(Ghost pads)

你可以从图 8-1看 到,箱柜没有一个属于它自己的衬垫,这就是”精灵衬垫”的由来。

图8-1. 没有使用精灵衬垫的GstBin元件

 【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

精灵衬垫来自于箱柜中某些元件,它同样可以在该箱柜中被直接访问。精灵衬垫与UNIX文件系统中的符号链接很类似。

使用箱柜,你可以在你的代码中将 箱柜当作一个普通元件来使用。

图 8-2. 使用了精灵衬垫的GstBin元件

 【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

图 8-2 显示了一个精灵衬垫。最左边元件的接收衬垫同样也是整个箱柜的精灵衬垫。

由于精灵衬垫看起来与其它衬垫没什么区别,而且与其它衬垫有着类似的功能。

所以它 们可以加到任何一种元件上,而不仅仅是GstBin。

通过函数gst_ghost_pad_new ()可以创建一个ghostpad :

#include <gst/gst.h>

int

main (int   argc,

      char *argv[])

{

  GstElement *bin, *sink;

  GstPad *pad;

  /* init */

  gst_init (&argc, &argv);

  /* create element, add to bin */

  sink = gst_element_factory_make ("fakesink", "sink");

  bin = gst_bin_new ("mybin");

  gst_bin_add (GST_BIN (bin), sink);

  /* add ghostpad */

  pad = gst_element_get_pad (sink, "sink");

  gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad));

  gst_object_unref (GST_OBJECT (pad));

[..]

}

    

上面的例子中,箱柜不仅有精灵衬垫,而且还存在一个带名叫”sink”的接收衬垫的元件。

因此这个箱柜可以作为那个元件的替代者。你可以将其它的元 件与这个箱柜进行连接。 

第9章. 缓冲区(Buffers)和事件(Events)

管道的数据流由一组缓冲区和事件组成,缓冲区包括实际的管道数据,事件包括控制信息,如寻找信息和流的终止信号。

所有这些数据流在运行的时候自动的流过管道。这一章将主要为你阐述这些概念。

9.1. 缓冲区(Buffers)

缓冲区包含了你创建的管道里的数据流。通常一个源元件会创建一个新的缓冲区,

同时元件还将会把缓冲区的数据传递给下一个元件。当使用GStreamer底层构造来创建一个媒体管道的时候,

你不需要自己来处理缓冲区,元件将会为你处理这些缓冲区。

一个缓冲区主要由以下一个组成:

  . 指向某块内存的指针

  . 内存的大小

  . 缓冲区的时间戳

  . 一个引用计数,指出了缓冲区所使用的元件数。没有元件可引用的时候,这个引用将用于销毁缓冲区。

这里有一个简单的例子,我们先创建了一个缓冲区,然后为这个缓冲区分配内存,然后将数据存放在缓冲区中,并传递至下一个元件。

该元件读取数据,处理某些事件(像创建一个新的缓冲区并进行解码),对该缓冲区解引用,这将造成数据空闲,

导致缓冲区被销毁。典型的音频和视频解码器就是这样工作的。

尽管如此,还有一些更为复杂的设定,元件会适当的修改缓冲区,也就是说,不会分配一个新的缓冲区。

元件也可以写入硬件内存(如视频捕获源)或是使用XShm从X-server分配内存。缓冲区只能读,等等。

9.2. 事件(Events)

事件是一系列控制粒子,随着缓冲区被发送到管道的上游和下游。

下游事件通知流状态相同的元件,可能的事件包括中断,flush,流的终止信号等等。

在应用程序与元件之间的交互以及事件与事件之间的交互中,上游事件被用于改变管道中数据流的状态,如查找。

对于应用程序来说,上游事件非常重要,下游事件则是为了说明获取更加完善的数据概念上的图像。

由于大多数应用程序以时间为单位查找,下面的例子实现了同样的功能:

static void

seek_to_time (GstElement *element,

     guint64     time_ns)

{

  GstEvent *event;

  event = gst_event_new_seek (GST_SEEK_METHOD_SET |

     GST_FORMAT_TIME,

     time_ns);

  gst_element_send_event (element, event);

}

    

以上代码主要是说明其具体的工作原理,快捷算法是一个函数 gst_element_seek ()。 

第10章. 你的第一个应用程序

在这一章中将会对先前的章节做个总结。它通过一个小程序来讲述GStreamer 的各个方面:

初始化库,创建元件,将元件打包进管道,播放管道中的数据内容。

通过这些步骤,你将能够自己开发一个简易的,支持Ogg/Vorbis格式的音频播放器。

10.1. 第一个Hello world程序

我们现在开始创建第一个简易的应用程序 – 一个基于命令行并支持Ogg/Vorbis格式的音频播放器。

我们只需要使用标准的 Gstreamer的组件(components)就能够开发出这个程序。

它通过命令行来指定播放的文件。让我们开始这段旅程:

如在 第4章中学到的那样,

第一件事情是要通过gst_init()函数来初始化 GStreamer库。确保程序包含了 gst/gst.h 头文件,

这样GStreamer库中的对象和函数才能够被正确地定义。你可以通过#include <gst/gst.h> 指令来包含 gst/gst.h 头文件。

然后你可以通过函数gst_element_factory_make ()来创建不同的元件。

对于Ogg/Vorbis音频播放器,我们需要一个源元件从磁盘读取文件。 GStreamer 中有一个”filesrc”的元件可以胜任此事。

其次我们需要一些东西来解析从磁盘读取的文件。 GStreamer 中有两个元件可以分别来解析Ogg/Vorbis文件。

第一个将 Ogg 数据流解析成元数据流的元件叫”oggdemux”。第二个是 Vorbis 音频解码器,通常称为”vorbisdec”。

由于”oggdemux”为每个元数据流动态创建衬垫,所以你得为”oggdemux”元件设置"pad- added" 的事件处理函数。

像8.1.1部分讲 解的那样,"pad-added" 的事件处理函数可以用来将 Ogg 解码元件和 Vorbis 解码元件连接起来。

最后,我们还需要一个音频输出元件 - “alsasink”。它会将数据传送给 ALSA 音频设备。

万事俱备,只欠东风。我们需要把所有的元件都包含到一个容器元件中 -  GstPipeline,

然后在这个管道中一直轮循,直到我们播放完整的歌曲。我们在第6章中 学习过如何将元件包含进容器元件,

在5.6部分了解过元件的状 态信息。我们同样需要在管道总线上加消息处理来处理错误信息和检测流结束标志。 

现在给出我们第一个音频播放器的所有代码:

#include <gst/gst.h>

/*

 * Global objects are usually a bad thing. For the purpose of this

 * example, we will use them, however.

 */

GstElement *pipeline, *source, *parser, *decoder, *conv, *sink;

static gboolean

bus_call (GstBus     *bus,

 GstMessage *msg,

 gpointer    data)

{

  GMainLoop *loop = data;

  switch (GST_MESSAGE_TYPE (msg)) {

    case GST_MESSAGE_EOS:

      g_print ("End-of-stream\n");

      g_main_loop_quit (loop);

      break;

    case GST_MESSAGE_ERROR: {

      gchar *debug;

      GError *err;

      gst_message_parse_error (msg, &err, &debug);

      g_free (debug);

      g_print ("Error: %s\n", err->message);

      g_error_free (err);

      g_main_loop_quit (loop);

      break;

    }

    default:

      break;

  }

  return TRUE;

}

static void

new_pad (GstElement *element,

GstPad     *pad,

gpointer    data)

{

  GstPad *sinkpad;

  /* We can now link this pad with the audio decoder */

  g_print ("Dynamic pad created, linking parser/decoder\n");

  sinkpad = gst_element_get_pad (decoder, "sink");

  gst_pad_link (pad, sinkpad);

  gst_object_unref (sinkpad);

}

int

main (int   argc,

      char *argv[])

{

  GMainLoop *loop;

  GstBus *bus;

  /* initialize GStreamer */

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* check input arguments */

  if (argc != 2) {

    g_print ("Usage: %s <Ogg/Vorbis filename>\n", argv[0]);

    return -1;

  }

  /* create elements */

  pipeline = gst_pipeline_new ("audio-player");

  source = gst_element_factory_make ("filesrc", "file-source");

  parser = gst_element_factory_make ("oggdemux", "ogg-parser");

  decoder = gst_element_factory_make ("vorbisdec", "vorbis-decoder");

  conv = gst_element_factory_make ("audioconvert", "converter");

  sink = gst_element_factory_make ("alsasink", "alsa-output");

  if (!pipeline || !source || !parser || !decoder || !conv || !sink) {

    g_print ("One element could not be created\n");

    return -1;

  }

  /* set filename property on the file source. Also add a message

   * handler. */

  g_object_set (G_OBJECT (source), "location", argv[1], NULL);

  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));

  gst_bus_add_watch (bus, bus_call, loop);

  gst_object_unref (bus);

  /* put all elements in a bin */

  gst_bin_add_many (GST_BIN (pipeline),

   source, parser, decoder, conv, sink, NULL);

  /* link together - note that we cannot link the parser and

   * decoder yet, becuse the parser uses dynamic pads. For that,

   * we set a pad-added signal handler. */

  gst_element_link (source, parser);

  gst_element_link_many (decoder, conv, sink, NULL);

  g_signal_connect (parser, "pad-added", G_CALLBACK (new_pad), NULL);

  /* Now set to playing and iterate. */

  g_print ("Setting to PLAYING\n");

  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  g_print ("Running\n");

  g_main_loop_run (loop);

  /* clean up nicely */

  g_print ("Returned, stopping playback\n");

  gst_element_set_state (pipeline, GST_STATE_NULL);

  g_print ("Deleting pipeline\n");

  gst_object_unref (GST_OBJECT (pipeline));

  return 0;

}

    

我们现在创建了一个完整的管道。我们可以用下面的图来形象地描述这个管道:

图10-1. "hello world"管道

 【miscellaneous】 GStreamer应用开发手册学习笔记之基础概念介绍

10.2. 编译运行helloworld.c

通过命令 

$ gcc -Wall $(pkg-config --cflags --libs gstreamer-0.10) helloworld.c -o helloworld 

来编译例子helloworld。

编译这个应用程序,GStreamer需要使用pkg-config来 得到编译器和连接标志。

如果你的GStreamer不是以默认的方式安装,请确保环境变量 PKG_CONFIG_PATH 设置正确($libdir/pkgconfig)。

如果环境变量设置不对,应用程序不会通过编译。

你可以通过./helloworld file.ogg命令来 运行例子。用你自己的Ogg/Vorbis 文件来代替 file.ogg 运行上述命令。

10.3. 结论

对我们的第一个例子做个总结。像你看到的那样,我们是通过非常底层(low-level)的API来建立的管道,这样也非常有效。

在这份手册的后面 部分,你可以看到通过使用一些高层(higher-level)的API

可以花费比这个例子更少的代码来建立一个有着更强大功能的媒体播放器。

我们将在 GStreamer应用程序开发手册(0.10.9.1)的第四部分讨 论这个话题。

我们现在需要对 GStreamer 的内部机制有更深的了解。

在这个例子中,我们可以很容易的用其它的元件来代替"filesrc"元件。

比如从网络读取数据的元件,或者从其它任何能够更好的将数据与你桌面环 境整和在一起的元件。

同样你可以使用其它的解码器来支持其它的媒体类型。如果你的程序不是运行在Linux上,

而是运行在 Mac OS X,Windows 或 FreeBSD 上,你可以使用不同的音频接收元件。 

甚至你可以使用filesink将数据写到磁盘上而不是将它们播放出来。 

所有这些,都显示了 GStreamer 元件的一个巨大优势 - 可重用性(reusability)。