详细的GStreamer开发教程

详细的GStreamer开发教程


文章目录

  • 详细的GStreamer开发教程
    • 1. 什么是GStreamer?
    • 2. GStreamer架构
      • 2.1 Media Applications
      • 2.2 Core Framework
      • 2.3 Plugins
    • 3. GStreamer组件
      • 3.1 Element
        • 创建一个 GstElement
      • 3.2 箱柜(bin)
        • 元件的状态
      • 3.3 衬垫(Pad)
        • 元件链接(Pad link)
        • Pad Capability
        • Pad Capability for filtering
        • 精灵衬垫(ghost pad)
      • 动态衬垫( Dynamic pads )
      • 3.4 总线(Bus)
        • 如何使用Bus?
        • Bus Message类型
      • 3.5 最佳实战


本文主要参考GStreamer官方Tutorials:gstreamer Tutorials

1. 什么是GStreamer?

Gstreamer是一个用于开发流媒体应用的开源框架,采用了基于插件(plugin)和管道(pipeline)的体系结构,框架中的所有的功能模块都被实现成可以插拔的组件(component), 并且能够很方便地安装到任意一个管道上。由于所有插件都通过管道机制进行统一的数据交换,因此很容易利用已有的各种插件“组装”出一个功能完善的流媒体应用程序。

由于deepstream是基于gstreamer的,所以要想在deepstream上做拓展,需要对gstreamer有一定的认识。

2. GStreamer架构

GStreamer的核心功能是为插件,数据流和媒体类型处理/协商提供框架。 下图是对基于Gstreamer框架的应用的简单分层:
详细的GStreamer开发教程_第1张图片

2.1 Media Applications

最上面一层为应用,比如gstreamer自带的一些工具(gst-launch,gst-inspect等),以及基于gstreamer封装的库(gst-player,gst-rtsp-server,gst-editing-services等)根据不同场景实现的应用。

2.2 Core Framework

中间一层为Core Framework,主要提供:

  • 上层应用所需接口
  • Plugin的框架
  • Pipline的框架
  • 数据在各个Element间的传输及处理机制
  • 多个媒体流(Streaming)间的同步(比如音视频同步)
  • 其他各种所需的工具库

2.3 Plugins

最下层为各种插件,实现具体的数据处理及音视频输出,应用不需要关注插件的细节,会由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等。

3. GStreamer组件

3.1 Element

对于需要应用 GStreamer 框架的程序员来讲,GstElement 是一个必须理解的概念,因为它是组成管道的基本构件,也是框架中所有可用组件的基础。因此,GStreamer 框架中的大部分函数都会涉及到对 GstElement 对象的操作。

从 GStreamer 自身的观点来看,GstElement 可以描述为一个具有特定属性的黑盒子,它通过连接点(link point)与外界进行交互,向框架中的其余部分表征自己的特性或者功能。 一个Element实现了一个功能(如读取文件,解码,输出等),一个程序需要创建多个element,并按顺序将其串连起来,构成一个完整的pipeline。

按照各自功能上的差异,GstElement 可以细分成如下几类:

  • Source Element 数据源元件 只有输出端,它仅能用来产生供管道消费的数据,而不能对数据做任何处理。一个典型的数据源元件的例子是音频捕获单元,它负责从声卡读取原始的音频数据,然后作为数据源提供给其它模块使用。

详细的GStreamer开发教程_第2张图片

  • Filter Element 过滤器元件 既有输入端又有输出端,它从输入端获得相应的数据,并在经过特殊处理之后传递给输出端。 一个典型的过滤器元件的例子是音频编码单元,它首先从外界获得音频数据,然后根据特定的压缩算法对其进行编码,最后再将编码后的结果提供给其它模块使用。

详细的GStreamer开发教程_第3张图片或者 详细的GStreamer开发教程_第4张图片

  • Sink Element 接收器元件 只有输入端,它仅具有消费数据的能力,是整条媒体管道的终端。一个典型的接收器元件的例子是音频回放单元,它负责将接收到的数据写到声卡上,通常这也是音频处理过程中的最后一个环节。

详细的GStreamer开发教程_第5张图片

下图将有助于你更好地理解数据源元件、过滤器元件和接收器元件三者的区别,同时也不难看出它们是如何相互配合形成管道的:
详细的GStreamer开发教程_第6张图片

创建一个 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;
}

3.2 箱柜(bin)

箱柜(bin)是GStreamer框架中的容器元件,它通常被用来容纳其它的元件对象,但由于其自身也是一个GstElement对象,因此实际上也能够被用来容纳其它的箱柜对象。利用bin可以将需要处理的多个元件组合成一个逻辑元件,你不再需要对箱柜中的元件逐个进行操作,而只需处理一个bin元件即可。

例如:你有很多个元件用来解码视频并对其使用一些效果,要使事情变得简单一些, 你可以把这些元件放进一个bin(就像一个容器)中,以后你就可以使用bin来引用这些元件了。 这样其实bin变成了一个元件,如你的管道是 a ! b ! c ! d ,你可以把他们放进 mybin,这样当你使用mybin时其实是引用了 a ! b ! c ! d

下图描述了箱柜在GStreamer框架中的典型结构:
详细的GStreamer开发教程_第7张图片

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

  • GstPipeline 管道是最常用到的容器,对于一个GStreamer应用程序来讲,其顶层箱柜必须是一条管道。
  • GstThread 线程的作用在于能够提供同步处理能力,如果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状态时并不会释放其占用的资源。

3.3 衬垫(Pad)

衬垫(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的指针。

元件链接(Pad link)

在引入了元件和衬垫的概念之后,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);

Pad Capability

衬垫从某种程度上可以看成是元件的代言人,因为它要负责向外界描述该元件所具有的能力。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;
}

Pad Capability for filtering

如果我想让两个元件之间的数据流变为某种媒体类型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断开这两个元件连接。

精灵衬垫(ghost pad)

如果仔细研究一下箱柜,会发现bin没有属于自己的输入衬垫和输出衬垫,因此显然是无法作为一个逻辑整体与其它元件交互的。为了解决这一问题,GStreamer引入了精灵衬垫(ghost pad)的概念, ghost pad是与bin中某些元件的pad相互连接, 它与UNIX文件系统中的符号链接类似,如下图所示:在bin上使用ghost pad,链接到Element1的sink,这样可以通过操作bin上的ghost pad操作Element1 sink pad。

详细的GStreamer开发教程_第8张图片

具有精灵衬垫(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");

动态衬垫( Dynamic pads )

创建元件时,某些元件可能没有包含全部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 () 将新添加的元素的状态设置为管道的目标状态。

3.4 总线(Bus)

总线是一个简单的系统,负责将消息从管道传递到应用程序。默认情况下,每个管道都包含一个总线,因此应用程序不需要创建总线或任何东西。应用程序唯一要做的就是在总线上设置消息处理程序,这类似于对对象的信号处理程序。当mainloop运行时,将定期检查总线上是否有新消息,并且在有任何消息可用时将调用回调。

如何使用Bus?

有两种总线使用方式:

  • 运行GLib/Gtk+ main loop,并将某种watch连接到总线。这样,GLib main loop将检查总线上是否有新消息,并在有消息时触发信号,此时可以使用回调函数进行处理。通常使用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);

[..]

Bus Message类型

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”属性,可以从消息中手动提取媒体进度(以百分比为单位)。
  • 特定于应用程序的消息:可以通过获取消息结构并读取其字段来提取有关这些消息的任何信息。应用程序消息主要用于应用程序内部使用,以防应用程序需要将信息从某个线程封送到主线程中。

3.5 最佳实战

#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状态,并开始侦听总线中是否有消息(例如ERROREOS)。

回调

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(或管道)中。

你可能感兴趣的:(Gstreamer)