GStreamer - Application Development Manual

翻译自:https://gstreamer.freedesktop.org/documentation/application-development/index.html

1 前言

GStreamer是一个非常强大且通用的框架,用于创建流媒体应用程序。GStreamer框架的许多优点来自其模块化:GStreamer可以无缝地合并新的插件模块。但是,由于模块化和功耗通常需要更高的复杂性,因此编写新的应用程序并不总是那么容易。

本指南旨在帮助您了解GStreamer框架,以便您可以基于它开发应用程序。第一章将重点介绍简单音频播放器的开发,并努力帮助您了解GStreamer概念。后面的章节将讨论与媒体播放和其他形式的媒体处理(捕获,编辑等)相关的更高级主题。

2 简介

2.1 谁应该阅读本手册?

本书是从应用程序开发人员的角度来看GStreamer的。它描述了如何使用GStreamer库和工具编写GStreamer应用程序。有关编写插件的说明,我们建议使用插件编写器指南。

另请查看GStreamer网站上提供的其他文档。

2.2 预备知识

为了理解本手册,您需要对C语言有基本的了解。

由于GStreamer遵循GObject编程模型,本指南还假设您了解GObject和glib编程的基础知识。特别是:

  • GObject实例化
  • GObject属性(设置/获取)
  • GObject转换
  • GObject引用/解除引用
  • glib内存管理
  • glib信号和回调
  • glib主循环

2.3 本手册的结构

为了帮助您浏览本指南,它分为几个大部分。每个部分都涉及有关GStreamer应用程序开发的特定主题。本指南的各部分按以下顺序排列:

About GStreamer:概述了GStreamer,其设计原则和基础。

Building an Application:涵盖了GStreamer应用程序编程的基础知识。在本部分的最后,您应该能够使用GStreamer构建自己的音频播放器;

在高级GStreamer概念中,我们将继续研究使GStreamer从竞争对手中脱颖而出的高级课程。我们将讨论使用动态参数和接口的应用程序 - 管道交互,我们将讨论线程和线程管道,调度和时钟(以及同步)。大多数这些主题不仅仅是向您介绍他们的API,而且主要是为了更深入地了解使用GStreamer解决应用程序编程问题并理解他们的概念。

接下来,在GStreamer应用程序的高级接口中,我们将进入GStreamer的高级编程API。您不需要了解前面部分的所有细节来理解这一点,但您仍需要了解基本的GStreamer概念。除其他外,我们将讨论playbin和autopluggers。

最后在附录中,您将找到有关与GNOME,KDE,OS X或Windows集成的一些随机信息,一些调试帮助以及改进和简化GStreamer编程的一般提示。

3 About GStreamer

本部分概述了本书中介绍的技术。

3.1 GStreamer是什么?

GStreamer是一个用于创建流媒体应用程序的框架。基础设计来自俄勒冈研究生院的视频管道,以及DirectShow的一些想法。

GStreamer的开发框架可以编写任何类型的流媒体多媒体应用程序。GStreamer框架旨在简化编写处理音频或视频或两者的应用程序。它不仅限于音频和视频,还可以处理任何类型的数据流。管道设计的开销小于所应用的过滤器引起的开销。这使得GStreamer成为设计高端音频应用程序的良好框架,这些应用程序对延迟提出了很高的要求。

GStreamer最明显的用途之一是使用它来构建媒体播放器。 GStreamer已经包含用于构建媒体播放器的组件,可以支持各种格式,包括MP3,Ogg / Vorbis,MPEG-1/2,AVI,Quicktime,mod等。然而,GStreamer不仅仅是一个媒体播放器。它的主要优点是可插拔组件可以混合并匹配到任意管道中,因此可以编写成熟的视频或音频编辑应用程序。

该框架基于各种具备编解码器和其他功能的插件。插件可以链接并布置在管道中。此管道定义数据流。管道也可以使用GUI编辑器进行编辑并保存为XML,以便可以轻松地制作管道库。

GStreamer核心功能是为插件、数据流和媒体类型处理/协商提供框架。它还提供了使用各种插件编写应用程序的API。

特别地,GStreamer支持:

  • 用于多媒体应用的API;
  • 插件结构;
  • 管道结构;
  • 媒体类型处理/协商的机制;
  • 同步机制;
  • 超过250个插件,提供超过1000个元素;
  • 一系列的工具。

GStreamer的插件可以分为:

  • 处理协议;
  • 源:用于音频和视频(涉及协议插件);
  • 格式:解析器、格式化程序、复用器、分路器、元数据、字幕;
  • 编解码器:编码器和解码器;
  • 过滤器:转换器,混合器,效果等;
  • 接收器:用于音频和视频(涉及协议插件)。

在这里插入图片描述GStreamer被封装为:

  • gstreamer:核心包;
  • gst-plugins-base:一组重要的示例元素;
  • gst-plugins-good:符合LGPL协议的一套优质插件;
  • gst-plugins-ugly:一组可能造成分发问题的高质量插件;
  • gst-plugins-bad:一组需要更高质量化处理的插件;
  • gst-libav:一组包装libav用于解码和编码的插件;
  • 其他几个包。

3.2 设计原则

3.2.1 Clean and powerful

GStreamer提供了一个干净的界面,用于:

  • GStreamer为想要构建媒体管道的应用程序员可以使用一组广泛的强大工具来创建媒体管道,而无需编写任何代码。执行复杂的媒体操作变得非常容易。
  • GStreamer为插件程序员提供了一个简洁的API来创建自包含的插件。已经集成了广泛的调试和跟踪机制。 GStreamer还提供了大量现实生活中的插件,也可作为示例。

3.2.2 面向对象

GStreamer坚持使用GLIB 2.0对象模型GObject。熟悉GLib 2.0或GTK +的程序员会对GStreamer感到满意。

GStreamer使用信号和对象属性的机制。

可以在运行时查询所有对象的各种属性和功能。

GStreamer打算在编程方法上与GTK +类似。这适用于对象模型、对象的所有权、引用计数等。

3.2.3 可扩展

可以使用GObject继承方法扩展所有GStreamer对象。

所有插件都是动态加载的,可以单独扩展和升级。

3.2.4 Allow binary-only plugins

插件是在运行时加载的共享库。由于可以使用GObject属性设置插件的所有属性,因此不需要(实际上没有办法)为插件安装任何头文件。

已经特别注意使插件完全独立。可以在运行时查询插件的所有相关方面。

3.2.5 高性能

高性能基于:

  • 使用GLib的GSlice分配器;
  • 插件之间的轻量级链接。数据可以以最小的开销传输到管道。插件之间的数据传递仅涉及典型管道中的指针解除引用;
  • 提供直接处理目标内存的机制。例如,插件可以直接写入X服务器的共享内存空间。缓冲区还可以指向任意内存,例如声卡的内部硬件缓冲区;
  • refcounting和copy on write可以最大限度地减少memcpy的使用。子缓冲区有效地将缓冲区拆分为可管理的块;
  • 专用流线程,由内核处理调度;
  • 通过使用专门的插件允许硬件加速;
  • 使用插件注册表,以便插件加载可以延迟,直到插件实际使用。

3.2.6 内核和插件有效分离

GStreamer的核心本质上是与媒体无关的。它只知道字节和块,只包含基本元素。 GStreamer的核心功能甚至可以实现低级系统工具,如cp。

所有媒体处理功能都由核心外部的插件提供。这些插件告诉核心如何处理特定类型的媒体。

3.2.7 为编解码器实验提供框架

GStreamer还希望成为一个简单的框架,编解码器开发人员可以在其中试验不同的算法,加速免费的多媒体编解码器的开发,如Xiph.Org基金会(如Theora和Vorbis)开发的那些。

3.3 基础知识

本指南的这一章介绍了GStreamer的基本概念。在阅读本指南的任何其他内容时,理解这些概念非常重要,所有这些概念都假设您理解这些基本概念。

3.3.1 Elements

Elements是GStreamer中最重要的对象类。您通常会创建一个链接在一起的元素链,让数据流过这个元素链。元素具有一个特定功能,可以是从文件读取数据,解码此数据或将此数据输出到声卡(或其他任何内容)通过将几个这样的元素链接在一起,您可以创建一个可以执行特定任务的管道,例如媒体播放或捕获。默认情况下,GStreamer附带了大量元素,使得各种媒体应用程序的开发成为可能。如果需要,您还可以编写新元素。 GStreamer Plugin Writer’s Guide中对该主题进行了大量解释

3.3.2 Pads

Pads是元素的输入和输出,您可以在其中连接其他元素。它们用于协商GStreamer中元素之间的链接和数据流可以将Pad视为元件上的“插头”或“端口”,其可以与其他元件建立链接,并且数据可以通过该元件流入或流出这些元件Pads具有特定的数据处理功能:pad可以限制流经它的数据类型。只有当两个pad允许的数据类型(功能)兼容时,才允许在两个pad之间建立链接。使用称为caps negotiation的过程在pad之间协商数据类型。数据类型由GstCaps描述。

这里的比喻可能会有所帮助。pad类似于物理设备上的插头或插孔。例如,考虑一个由音频放大器、DVD播放器和(静音)视频投影仪组成的家庭影院系统。允许将DVD播放器连接到放大器,因为两个设备都有音频插孔,并且允许将投影仪连接到DVD播放器,因为两个设备都具有兼容的视频插孔。由于投影仪和放大器具有不同类型的插孔,因此可能无法建立投影仪和放大器之间的链接。 GStreamer中的Pad与家庭影院系统中的插孔具有相同的用途。

在大多数情况下,GStreamer中的所有数据都通过元素之间的链接流动。数据通过一个或多个源pad流出一个元件,并且元件通过一个或多个pad接收输入数据。source和sink元件分别仅具有source pad和slink pad。数据通常包括缓冲区(由GstBuffer对象描述)和事件(由GstEvent对象描述)

3.3.3 Bins和pipelines

bin是元素集合的容器。由于bin是元素本身的子类,因此您可以主要控制bin,就像它是一个元素一样,从而为应用程序抽象出很多复杂性。例如,您可以通过更改bin本身的状态来更改bin中所有元素的状态。bins还会转发来自其包含的children的总线消息(例如错误消息,标记消息或EOS消息)。

管道是顶级的bin。它为应用程序提供总线并管理其children的同步。当您将其设置为PAUSED或PLAYING状态时,将启动数据流并进行媒体处理。一旦启动,管道将在一个单独的线程中运行,直到您停止它们或达到数据流的末尾。

在这里插入图片描述

3.3.4 通信

在这里插入图片描述
GStreamer为应用程序和管道之间的通信和数据交换提供了几种机制。

  • buffers是用于在管道中的元素之间传递流数据的对象,其总是从source传输到sink(向下传递);
  • events是在元素之间或从应用程序发送到元素的对象。事件可以向上传递和向下传递。向下传递的事件可以与数据流同步;
  • messages是管道消息总线上的元素拥有的对象,它们将由应用程序保存以供收集。消息可以从发布消息的元素的流线程上下文同步截取,但通常由应用程序从应用程序的主线程异步处理。消息用于以线程安全的方式将诸如错误、标签、状​​态改变、缓冲状态、重定向等信息从元素传输到应用程序;
  • queries允许应用程序从管道请求诸如持续时间或当前回放位置的信息。查询总是同步回答。元素还可以使用查询来从其对等元素请求信息(例如文件大小或持续时间)。它们可以在管道中以两种方式使用,但向上查询更常见。

4 Building an application

在这些章节中,我们将讨论GStreamer的基本概念和最常用的对象,例如elements,pad和buffers。我们将使用这些对象的可视化表示,以便我们可以可视化您将稍后构建的更复杂的管道。您将初步了解GStreamer API,它应该足以构建基本应用程序。稍后在本部分中,您还将学习构建基本的命令行应用程序。

请注意,本部分将介绍GStreamer的低级API和概念。一旦您要构建应用程序,您可能希望使用更高级别的API。这些将在本手册的后面部分讨论。

4.1 Initializing GStreamer

编写GStreamer应用程序时,只需包含gst / gst.h即可访问库函数。除此之外,您还需要初始化GStreamer库。

4.1.1 简单初始化

一个典型的程序会使用代码来初始化GStreamer,如下所示:

#include <stdio.h>
#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。

也可以使用两个NULL参数调用gst_init函数,在这种情况下,GStreamer不会解析任何命令行选项。

4.1.2 GOption接口

您还可以使用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_clear_error (&err);
    g_option_context_free (ctx);
    return 1;
  }
  g_option_context_free (ctx);

  printf ("Run me with --help to see the Application options appended.\n");

  return 0;
}

如此片段所示,您可以使用GOption表定义特定于应用程序的命令行选项,并将此表连同从函数gst_init_get_option_group返回的选项组一起传递给GLib初始化函数。除标准GStreamer选项外,还将解析您的应用程序选项。

4.2 Elements

GStreamer中最重要的对象是GstElement对象。元素是媒体管道的基本构建块。您将使用的所有不同的高级组件都派生自GstElement。每个解码器、编码器、分路器、视频或音频输出实际上都是GstElement。

4.2.1 What are elements?

对于应用程序员来说,elements最好视为为黑盒子。在一端,你可能会放入一些东西,元素用它做一些事情,另一端出现其他东西。例如,对于解码器element,您将放入编码数据,该元素将输出解码数据。在下一章中,您将了解有关元素中数据输入和输出的更多信息,以及如何在应用程序中进行设置。

4.2.1.1 Source elements

source elements生成供管道使用的数据,例如从磁盘或声卡读取。源元素的可视化显示了我们如何可视化源元素。我们总是在元素的右边画一个源 pad。

源元素不接受数据,它们只生成数据。您可以在图中看到这一点,因为它只有一个源pad(在右侧)。源 pad只能生成数据。
在这里插入图片描述

4.2.1.2 滤波器,转换器,分路器,复用器和编解码器

滤波器和类似滤波器的元件具有输入和输出 pad。它们对输入(接收器)pad 上接收的数据进行操作,并在其输出(源)pad上提供数据。这些元件的示例是体积元件(滤波器)、视频缩放器(转换器)、Ogg分路器或Vorbis解码器。

类似滤波器的元件可以具有任意数量的源或接收 pad。例如,视频分路器将具有一个接收 pad和若干(1-N)个源 pad,一个对应于容器格式中包含的每个基本流。另一方面,解码器只有一个源和接收 pad。

在这里插入图片描述
滤波元素的可视化显示了我们如何可视化类似滤波的元素。该特定元素具有一个源和一个sink pad。接收输入数据的sink pad位于元件的左侧;source pad仍在右侧。

在这里插入图片描述
具有多个输出pad的滤波器元件的可视化显示另一个类似滤波器的元件,该元件具有多个输出(源)pad。例如,一个这样的元素的示例可以是用于包含音频和视频的Ogg流的Ogg分路器。一个源pad将包含基本视频流,另一个将包含基本音频流。解复用器通常会在创建新pad时触发信号,程序员可以在信号处理程序中处理新的基本流。

4.2.1.3 Sink element

sink element是media 管道的端点。他们接受数据但不生产任何东西。磁盘写入、声卡重放和视频输出都将由接收器元素实现。 sink元素的可视化显示了sink元素。

在这里插入图片描述

4.2.2 创建一个GstElement

创建元素的最简单方法是使用gst_element_factory_make()。此函数输入新创建元素的工厂名称和元素名称。例如,元素的名称可以在稍后用于查找bin中的元素。该名称也将用于调试输出。您可以传递NULL作为name参数以获取唯一的默认名称。

当您不再需要该元素时,您需要使用gst_object_unref()取消它。这会将元素的引用计数减少1。元素在创建时的引用计数为1。当refcount减少到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实际上是两个函数组合的简写。 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));
  gst_object_unref (GST_OBJECT (factory));

  return 0;
}

4.2.3 使用元素作为GObject

GstElement可以具有使用标准GObject属性实现的多个属性。因此,支持查询、设置和获取属性值以及GParamSpec的常用GObject方法。

每个GstElement都从其父GstObject继承至少一个属性:“name”属性。这是您为函数gst_element_factory_make()或gst_element_factory_create()提供的名称。您可以使用函数gst_object_set_name和gst_object_get_name来获取和设置此属性,或使用GObject属性机制,如下所示。

#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;
}

大多数插件都提供其他属性,以提供有关其配置或配置元素的更多信息。 gst-inspect是查询特定元素属性的有用工具,它还将使用属性自省来简要说明属性的功能以及它支持的参数类型和范围。有关gst-inspect的详细信息,请参阅附录中的gst-inspect。

有关GObject属性的更多信息,我们建议您阅读GObject手册Glib对象系统简介。

GstElement还提供各种GObject信号,可用作灵活的回调机制。在这里,您也可以使用gst-inspect来查看特定元素支持的信号。信号和属性一起是元素和应用程序交互的最基本方式。

4.2.4 更多关于element factory的信息

在上一节中,我们简要介绍了GstElementFactory对象作为创建元素实例的方法。然而,元素工厂的作用远不止于此。元素工厂是从GStreamer注册表中检索的基本类型,它们描述了GStreamer可以创建的所有插件和元素。这意味着元素工厂对自动化元素实例化非常有用,例如自动插件的功能,以及创建可用元素列表。

4.2.4.1 使用工厂获取element的信息

像gst-inspect这样的工具将提供有关元素的一些通用信息,例如编写插件的人、描述性名称(和短名称)、等级和类别。该类别可用于获取可使用此元素工厂创建的元素的类型。类别的示例包括编解码器/解码器/视频(视频解码器),编解码器/编码器/视频(视频编码器),源/视频(视频发生器),接收器/视频(视频输出),以及所有这些也可用于音频。然后,还有Codec / Demuxer和Codec / Muxer等等。 gst-inspect将给出所有工厂的列表,gst-inspect 将列出所有上述信息,还有更多。

#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 ("fakesrc");
  if (!factory) {
    g_print ("You don't have the 'fakesrc' 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_metadata (factory, GST_ELEMENT_METADATA_KLASS),
           gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_DESCRIPTION));

  gst_object_unref (GST_OBJECT (factory));

  return 0;
}

您可以使用gst_registry_pool_feature_list(GST_TYPE_ELEMENT_FACTORY)获取GStreamer知道的所有元素工厂的列表。

4.2.4.2 查看element可以包含哪些类型的pad

也许元素工厂最强大的特性是它们包含元素可以生成的pad的完整描述,以及这些pad的功能(用外行的话说:什么类型的介质可以在这些pad上流动),而不必实际需要将这些插件加载到内存中。这可以用于为编码器提供编解码器选择列表,或者它可以用于媒体播放器的自动插拔目的。目前所有基于GStreamer的媒体播放器和自动插件都以这种方式工作。我们将在下一章了解GstPad和GstCaps时仔细研究这些功能:Pads和capabilities。

4.2.5 链接元素

通过将source元素与零个或多个类似过滤器的元素以及最后一个sink元素相链接,可以设置媒体管道。数据将流经元素。这是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()。您还可以获取对各个pad的引用,并使用各种gst_pad_link_ *()函数链接它们。有关更多详细信息,请参阅API参考。

重要提示:您必须在链接之前向bin或管道添加元素,因为向bin添加元素将断开任何已存在的链接。此外,您不能直接链接不在同一个bin或管道中的元素;如果你想链接不同层次的元素或pad,你需要使用ghost pads(稍后介绍)。

4.2.6 Element状态

创建后,元素实际上不会执行任何操作。您需要更改元素状态以使其执行某些操作。 GStreamer知道四种元素状态,每种状态都具有非常特定的含义。这四个状态是:

  • GST_STATE_NULL:这是默认状态。在此状态下没有分配资源,因此,转换到它将释放所有资源。当refcount达到0并且释放时,该元素必须处于此状态。
  • GST_STATE_READY:在就绪状态中,元素已分配其所有全局资源,即可以保留在流中的资源。您可以考虑打开设备,分配缓冲区等。但是,在此状态下流并没有被打开,因此流位置自动为零。如果先前打开了一个流,则应该在此状态下关闭它,并且应该重置位置、属性等。
  • GST_STATE_PAUSED:在此状态下,元素已打开流,但未主动处理它。允许元素修改流的位置,读取和处理数据等,以便在状态更改为PLAYING后立即准备播放,但不允许播放使时钟运行的数据。总之,PAUSED与PLAYING相同,但没有运行时钟。
    进入PAUSED状态的元素应该准备好尽快转移到PLAYING状态。例如,视频或音频输出将等待数据到达并对其进行排队,以便它们可以在状态改变后立即播放。此外,视频接收器已经可以播放第一帧(因为这还不影响时钟)。 Autopluggers可以使用相同的状态转换来将管道连接在一起。但是,大多数其他元素(如编解码器或过滤器)不需要在此状态下显式执行任何操作。
  • GST_STATE_PLAYING:在PLAYING状态下,一个元素与PAUSED状态完全相同,只是时钟现在运行。

您可以使用函数gst_element_set_state()更改元素的状态。如果将元素设置为另一个状态,GStreamer将在内部遍历所有中间状态。因此,如果您将元素从NULL设置为PLAYING,GStreamer将在内部将元素设置为READY和PAUSED。

当移动到GST_STATE_PLAYING时,管道将自动处理数据。它们不需要以任何形式进行迭代。在内部,GStreamer将启动承担各任务的线程。 GStreamer还将使用GstBus将消息从管道的线程传输到应用程序自己的线程中。详情请见Bus。

当您将bin或管道设置为某个目标状态时,它通常会自动将状态更改传播到bin或管道中的所有元素,因此通常只需要设置顶级管道的状态以启动管道或关闭它。但是,当动态地向已经运行的管道添加元素时,例如,在“pad-added”信号回调中,您需要使用gst_element_set_state()或gst_element_sync_state_with_parent()将其设置为所需的目标状态。

4.3 Bins

bin是容器元素。您可以向bin添加元素。由于bin本身就是一个元素,因此可以以与任何其他元素相同的方式处理bin。因此,整个前一章(元素)也适用于bin。

4.3.1 what are bins

Bins允许您将一组链接元素组合成一个逻辑元素。您不再处理单个元素,而只使用一个元素bin。我们将看到,当您构建复杂的管道时,这非常强大,因为它允许您以较小的块分解管道。

bin还将管理其中包含的元素。它将对元素执行状态更改以及收集和转发总线消息。

在这里插入图片描述
GStreamer程序员可以使用一种特殊类型的bin:
管道:管理所包含元素的同步和总线消息的通用容器。顶层bin必须是管道,因此每个应用至少需要其中一个。

4.3.2 create a bin

以与创建其他元素相同的方式创建bin,即使用元素工厂。还有便利功能(gst_bin_new()和gst_pipeline_new())。要向bin中添加元素或从bin中删除元素,可以使用gst_bin_add()和gst_bin_remove()。请注意,添加元素的bin将获取该元素的所有权。如果你析构了bin,那么该元素将被解除引用。如果从bin中删除元素,它将自动解除引用。

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

[..]

}

查找bin中的元素有各种函数。最常用的是gst_bin_get_by_name()和gst_bin_get_by_interface()。您还可以使用函数gst_bin_iterate_elements()迭代bin包含的所有元素。有关详细信息,请参阅GstBin的API参考。

4.3.3 custom bins

程序员可以创建包含元素的自定义bin来执行特定任务。例如,这允许您使用以下代码行编写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);
[..]
}


(当然,这是一个愚蠢的例子,已经存在一个功能更强大,功能更多的自定义bin:playbin元素。)

可以使用插件或应用程序创建自定义bin。您可以在Plugin Writer指南中找到有关创建自定义bin的更多信息。

这种自定义bin的示例是来自gst-plugins-base的playbin和uridecodebin元素。

4.3.4 Bins manage states of their children

bins管理其包含的所有元素的状态。如果使用gst_element_set_state()将bin(或管道,它是特殊的顶级bin类型)设置为某个目标状态,它将确保其中包含的所有元素也将设置为此状态。这意味着通常只需要设置顶级管道的状态以启动管道或关闭它。

bin将对其所有子节点从sink元素到源元素执行状态更改(从后向前进行状态更改)。这确保了当上游元素进入PAUSED或PLAYING时下游元素已准备好接收数据。类似地,当关闭时,sink元素将首先设置为READY或NULL,这将导致上游元素接收FLUSHING错误并在元素设置为READY或NULL状态之前停止流线程。

但是,请注意,如果将元素添加到已经运行的bin或管道中,例如在“pad-added”信号回调中,其状态不会自动与其添加到的bin或管道的当前状态或目标状态一致。相反,在向已经运行的管道添加元素时,需要使用gst_element_set_state()或gst_element_sync_state_with_parent()将其设置为所需的目标状态。

4.4 总线

总线是一个简单的系统,负责将消息从流线程转发到其自己的线程上下文中的应用程序。总线的优点是应用程序不需要是线程感知就可以使用GStreamer,即使GStreamer本身具有高度线程性。

默认情况下,每个管道都包含一个总线,因此应用程序不需要创建总线或任何东西。应用程序应该做的唯一事情是在总线上设置消息处理程序,类似于对象的信号处理程序。当主循环运行时,将定期检查总线是否有新消息,并且当有任何消息可用时将调用回调。

4.4.1 如何使用总线

使用总线有两种不同的方法:

  • 运行GLib / Gtk +主循环(或定期自己迭代默认的GLib主要上下文)并将某种监视附加到总线上。这样,GLib主循环将检查总线是否有新消息,并在有消息时通知您。
    通常,在这种情况下,您将使用gst_bus_add_watch()或gst_bus_add_signal_watch()。
    要使用总线,请使用gst_bus_add_watch()将消息处理程序附加到管道总线。只要管道向总线发送消息,就会调用此处理程序。在此处理程序中,检查信号类型(请参阅下一节)并执行相应操作。处理程序的返回值应为TRUE以保持处理程序连接到总线,返回FALSE以将其删除。
  • 自己检查总线上的消息。这可以使用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;
  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;
}

重要的是要知道将在mainloop的线程上下文中调用处理程序。这意味着管道和应用程序通过总线之间的交互是异步的,因此不适合某些实时目的,例如音轨之间的交叉淡入淡出,做(理论上)无间隙回放或视频效果。所有这些事情都应该在管道上下文中完成,这是编写GStreamer插件最简单的但它的主要用途非常有用:将消息从管道传递到应用程序。这种方法的优点是GStreamer在内部执行的所有线程都是对应用程序隐藏的,应用程序开发人员根本不必担心线程问题

请注意,如果您使用默认的GLib主循环集成,则可以连接到总线上的“消息”信号,而不是连接监视。这样您就不必在所有可能的消息类型上执行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);

[..]

如果您不使用GLib主循环,则默认情况下不会提供异步消息信号。但是,您可以安装一个自定义同步处理程序,该处理程序唤醒自定义主循环并使用gst_bus_async_signal_func()发出信号。 (有关详细信息,请参阅文档)

4.4.2 消息类型

GStreamer有一些可以通过总线传递的预定义消息类型。但是,这些消息是可扩展的。插件可以定义其他消息,应用程序可以决定为这些消息设置特定代码或忽略它们。强烈建议所有应用程序通过向用户提供可视反馈来至少处理错误消息。

所有消息都有消息源、类型和时间戳。消息源可用于查看哪个element发出了消息。例如,对于某些消息,只有顶级管道发出的消息对大多数应用程序都很有用(例如,对于状态变化通知)。下面列出了所有消息,并简要说明了它们的作用以及如何解析特定消息的内容。

  • Error,warning和information notifications:如果应向用户显示有关管道状态的消息,则element将使用这些通知。错误消息是致命的并将终止数据传递。应该修复错误以恢复管道活动。警告不是致命的,但仍暗示着一个问题。信息消息用于非问题通知。所有这些消息都包含具有主要错误类型和消息的GError,以及可选的调试字符串。两者都可以使用gst_message_parse_error(),_ expand_warning()和_parse_info()来提取。使用后应释放错误和调试字符串。
  • End-of-stream notification:这是在流结束时发出的。管道状态不会改变,但进一步的媒体处理将停滞不前。应用程序可以使用此选项跳到播放列表中的下一首歌曲。在流结束之后,还可以在流中寻找。然后播放将自动继续。此消息没有特定参数。
  • Tags:在流中找到元数据时发出。对于管道,这可以多次发出(例如,一次用于描述性元数据,例如艺术家姓名或歌曲名称,另一次用于流信息,例如采样率和比特率)。应用应在内部缓存元数据。应该使用gst_message_parse_tag()来解析标记列表,当不再需要时,应该调用gst_tag_list_unref()。
  • State-changes:在状态改变成功后发出。 gst_message_parse_state_changed()可用于解析此转换的旧状态和新状态。
  • Buffering:在网络流缓存期间发出的。可以通过从gst_message_get_structure()返回的结构中提取“buffer-percent”属性,从消息中手动提取进度(以百分比表示)。另请参见缓冲。
  • Element messages:这些是特殊消息,对于某些元素是唯一的,通常代表其他功能。元素的文档应详细说明特定元素可能发送的元素消息。作为示例,如果流包含重定向指令,则’qtdemux’QuickTime demuxer元素可以在某些情况下发送’重定向’元素消息。
  • Application-specific messages:可以通过获取消息结构(参见上文)并阅读其字段来提取有关这些信息的任何信息。通常可以安全地忽略这些消息。
    应用程序消息主要用于应用程序内部使用,以防应用程序需要将来自某个线程的信息封送到主线程中。当应用程序使用元素信号时(这些信号将在流线程的上下文中发出),这尤其有用。

4.5 Pads和capabilities

正如我们在Elements中看到的那样,pad是元素与外部世界的接口。数据从一个元素的源pad流到另一个元素的sink pad。某element可以处理的特定媒体类型将由该pad的功能公开。我们将在本章后面讨论更多关于功能的内容(参见pad的功能)。

4.5.1 Pads

pad类型由两个属性定义:其方向和可用性。正如我们之前提到的,GStreamer定义了两个pad方向:source pads和sink pads。该术语是从元素内部定义的:元素在其sink pads上接收数据并在其source pads上生成数据。示意性地,在元件的左侧绘制sink pads,而在元件的右侧绘制source pads。在这些图中,数据从左向右流动。

与pads可用性相比,pads方向非常简单。pads可以具有三种可用性中的任何一种:always,sometimes和on request。这三种类型的含义完全如下所示:always表示pads始终存在,sometimes pad仅在某些情况下存在(并且可以随机消失),on-requests pads在应用程序明确请求时才会出现。

4.5.1.1 动态(也叫做sometimes) pads

创建元素时,某些元素可能没有所有的pads。例如,这可能发生在Ogg demuxer元素上。当元素在Ogg流中检测到这样的流时,该元素将读取Ogg流并为每个包含的基本流(vorbis,theora)创建动态pads。同样,它将在流结束时删除pad。例如,该原理对于解复用器元件非常有用。

运行gst-inspect oggdemux将显示该元素只有一个pad:一个名为“sink”的sink pad。其他pad是“休眠”。您可以在pad模板中看到这一点,因为存在“Exists:Sometimes”属性。根据您播放的Ogg文件的类型,将创建pads。我们将看到,当您要创建动态管道时,这非常重要。您可以将信号处理程序附加到元素,以便在元素从其“sometimes”pad模板之一创建新pad时通知您。以下代码是如何执行此操作的示例:

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

[..]

}

通常仅在“pad-added”回调中向管道添加元素并不罕见。如果这样做,请不要忘记使用gst_element_set_state()或gst_element_sync_state_with_parent()将新添加的元素的状态设置为管道的目标状态。

4.5.1.2 Request Pads

元素也可以有request pads。这些pads不是自动创建的,只是按需创建。这对于多路复用器、聚合器和T形元件非常有用。聚合器是将多个输入流的内容合并为一个输出流的元素。T形元件是相反的:它们是具有一个输入流的元素,并将此流复制到它们的每个输出pad,这些输出pad是根据请求创建的。每当应用程序需要另一个流副本时,它只需从T形元素请求一个新的输出pad。

以下代码显示了如何从“tee”元素请求新的输出pad:

#include <gst/gst.h>
  
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, NULL);
  gst_pad_link (tolink_pad, 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", sinkname, srcname);
  g_free (sinkname);
  g_free (srcname);
}

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模板的名称从元素中获取pad。还可以请求与另一个pad模板兼容的pad。如果要将元素链接到多路复用器元素并且需要请求兼容的pad,这非常有用。方法gst_element_get_compatible_pad()可用于请求兼容的pad,如下一个示例所示。它将从任何输入请求来自Ogg多路复用器的兼容pad。

#include <gst/gst.h>
  
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, NULL);
  gst_pad_link (tolink_pad, 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", sinkname, srcname);
  g_free (sinkname);
  g_free (srcname);
}

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

4.5.2 Pad的capabilities

由于pad在外部世界如何观察元件中起着非常重要的作用,因此实现了一种机制通过使用capabilities描述可以流经或当前正在流过pad的数据。在这里,我们将简要描述哪些功能以及如何使用它们,足以理解这个概念。要深入了解功能以及GStreamer中定义的所有功能列表,请参阅插件编写指南。

capabilities附加到pad模板和pad本身。对于pad模板,它将描述可以通过此模板创建的pad上流式传输的媒体类型。对于pads,它可以是可能的caps列表(通常是pads模板功能的副本),在这种情况下,pads尚未协商,或者它是当前在此pads上流动的媒体类型,在这种情况下pads已经谈判了。

4.5.2.1 解析capabilities

pad的功能使用GstCaps对象描述。在内部,GstCaps将包含一个或多个将描述一种媒体类型的GstStructure。协商的pad将具有仅包含一个结构的功能集。此外,此结构仅包含固定值。对于未协商的pad或pad模板,这些约束不适用。

例如,下面是“vorbisdec”元素功能的转储,您可以通过运行gst-inspect vorbisdec获得。你会看到两个pad:一个源和一个接收pad。这两种pad始终可用,并且都具有附加功能。接收pad将接受vorbis编码的音频数据,媒体类型为“audio / x-vorbis”。源pad用于将原始(解码)音频样本发送到下一个元素,具有原始音频媒体类型(在本例中为“audio / x-raw”)。源pad还将包含音频采样率和通道数量的属性,以及您现在不需要担心的一些属性。

Pad Templates:
  SRC template: 'src'
    Availability: Always
    Capabilities:
      audio/x-raw
                 format: F32LE
                   rate: [ 1, 2147483647 ]
               channels: [ 1, 256 ]

  SINK template: 'sink'
    Availability: Always
    Capabilities:
      audio/x-vorbis
4.5.2.2 属性和取值

属性用于描述功能的额外信息。属性由键(字符串)和值组成。可以使用不同的可能值类型:

  • 基本类型,这几乎可以是任何在Glib中注册的GType。这些属性指示此属性的特定非动态值。例子包括:
    - 整数值(G_TYPE_INT):该属性具有此精确值;
    - 布尔值(G_TYPE_BOOLEAN):属性为TRUE或FALSE。
    - 浮点值(G_TYPE_FLOAT):该属性具有此精确浮点值。
    - 字符串值(G_TYPE_STRING):该属性包含UTF-8字符串。
    - 分数值(GST_TYPE_FRACTION):包含由整数分子和分母表示的分数。
  • Range类型是GStreamer注册的GType,用于指示一系列可能的值。它们用于指示允许的音频采样率值或支持的视频大小。 GStreamer中定义的两种类型是:
    - 整数范围值(GST_TYPE_INT_RANGE):该属性表示可能的整数范围,具有下边界和上边界。例如,“vorbisdec”元素的rate属性可以介于8000和50000之间。
    - 浮点范围值(GST_TYPE_FLOAT_RANGE):该属性表示可能的浮点值范围,具有下边界和上边界。
    - 分数范围值(GST_TYPE_FRACTION_RANGE):该属性表示可能的分数值的范围,具有下边界和上边界。
  • 列表值(GST_TYPE_LIST):该属性可以从此列表中给出的基本值列表中获取任何值。示例:表示支持采样率为44100 Hz且采样率为48000 Hz的caps将使用整数值列表,其中一个值为44100,一个值为48000。
  • 数组值(GST_TYPE_ARRAY):属性是值数组。数组中的每个值也是一个完整的值。数组中的所有值都应该是相同的基本类型。这意味着数组可以包含整数、整数列表、整数范围以及浮点数或字符串相同的任意组合,但它不能同时包含浮点数和整数。示例:对于涉及两个以上通道的音频,需要指定通道布局(对于一个和两个通道音频,通道布局是隐式的,除非在上限中另有说明)。因此,通道布局将是整数枚举值的数组,其中每个枚举值表示扬声器位置。与GST_TYPE_LIST不同,数组中的值将被解释为整体。

4.5.3 使用什么capabilities

capabilities(简称:caps)描述在两个pads之间流动的数据类型,或者一个pad(模板)支持的数据类型。这使它们对各种用途非常有用:

  • Autoplugging:根据其功能自动查找链接到元素的某一个pad。所有自动插件都使用此方法。
  • 兼容性检测:当两个pads相连时,GStreamer可以验证两个pads是否在谈论相同的介质类型。链接两个焊盘并检查它们是否兼容的过程称为“caps协商”。
  • 元数据:通过从pad中读取功能,应用程序可以提供有关在pad上传输的媒体类型的信息,这是有关当前正在播放的流的信息。
  • 过滤:应用程序可以使用capabilities来限制可以在两个pads之间流式传输的媒体类型。例如,应用程序可以使用“过滤的caps”来设置应在两个pads之间流动的特定(固定或非固定)视频大小。您将在本手册后面的“手动添加或删除管道中的数据”中看到过滤caps的示例。您可以通过将capsfilter元素插入管道并设置其“caps”属性来进行caps过滤。Caps过滤器通常放在转换器元件之后,如audioconvert,audioresample,videoconvert或videoscale,以强制这些转换器在特定位置将数据转换为特定的输出格式。
4.5.3.1 元数据的capabilities

pad可以具有附加到其上的一组(即,一个或多个)能力。能力(GstCaps)表示为一个或多个GstStructures的阵列,并且每个GstStructure是字段阵列,其中每个字段由字段名称字符串(例如“宽度”)和类型值(例如G_TYPE_INT或GST_TYPE_INT_RANGE)组成。

请注意,pad的可能功能之间存在明显的差异(即,通常你发现的pad模板的caps,因为它们在gst-inspect中显示),pads的允许caps(可以与pad模板的caps相同或是其子集,取决于对等垫的可能上限)和最后协商的caps(这些描述了流或缓冲区的确切格式,并且只包含一个结构,并且没有像范围或列表那样的可变位,即,他们是固定的caps)。

您可以通过查询一个结构的各个属性来获取一组功能中的属性值。您可以使用gst_caps_get_structure()从封装中获取结构,使用gst_caps_get_size()获取GstCaps中的结构数。

当Caps仅包含一个结构时,Caps称为简单caps;当它们只包含一个结构且没有可变字段类型(如范围或可能值列表)时,将称为固定caps。另外两种特殊类型的caps是ANY caps和empty caps。

以下是如何从一组固定视频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);
}
4.5.3.2 为过滤器创建caps

虽然capabilities主要在插件内部使用来描述pads的媒体类型,但应用程序员通常还必须对capabilities有基本的了解才能与插件对接,尤其是在使用过滤后的caps。当您使用过滤后的caps或固定caps时,您将限制在两个pads之间流动的媒体类型为其支持的媒体类型的子集。您可以在管道中使用capsfilter元素执行此操作。为此,您还需要创建自己的GstCaps。最简单的方法是使用函数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元素,并将其插入您要连接的两个元素之间的bin或管道中(如果您想要断开这些元素,这很重要因为那时你将不得不从capsfilter断开这两个元素)。

在某些情况下,您需要创建一组更精细的功能来过滤两个pads之间的链接。然后,这个函数太简单了,你会想要使用方法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",
             "width", G_TYPE_INT, 384,
             "height", G_TYPE_INT, 288,
             "framerate", GST_TYPE_FRACTION, 25, 1,
             NULL),
      gst_structure_new ("video/x-bayer",
             "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参考。

4.5.4 Ghost Pads

你可以从没有Ghost pads的GstBin元素的可视化中看到bin如何没有自己的pads。这就是“ghost pads”发挥作用的地方。

在这里插入图片描述
ghost pads是来自bin中某些element的pad,也可以直接从bin中访问。将它与UNIX文件系统中的符号链接进行比较。在bin上使用ghost pad,bin也有一个pad,可以透明地用作代码其他部分的元素。

在这里插入图片描述
具有ghost pad的GstBin元素的可视化是ghost pad的表示。elemnet 1的sink pad现在也是bin的pad。因为ghost pad的外观和工作方式与任何其他pad一样,它们可以添加到任何类型的元素,而不仅仅是普通的pads添加到GstBin。

使用函数gst_ghost_pad_new()创建ghost pad:

#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_static_pad (sink, "sink");
  gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad));
  gst_object_unref (GST_OBJECT (pad));

[..]

}


在上面的例子中,bin现在还有一个pad:给定元素的称为“sink”的pad。从这里开始,bin可以用作sink element的替代物。例如,您可以将另一个element链接到bin。

4.6 Buffers 和 Events

流经管道的数据由缓冲区和事件组合而成。缓冲区包含实际的媒体数据。事件包含控制信息,例如搜索信息和流末尾通知。所有这些都会在运行时自动流过管道。本章主要是为了向您解释这个概念;你不需要为此做任何事情。

4.6.1 Buffers

缓冲区包含将流经您创建的管道的数据。源元素通常会创建一个新缓冲区并将其通过pad传递给链中的下一个元素。使用GStreamer基础架构创建媒体管道时,您不必自己处理缓冲区;元素会为你做到这一点。

缓冲区包括:

  • 指向内存对象的指针。内存对象封装了内存中的一个区域。
  • 缓冲区的时间戳。
  • 一个refcount,指示有多少元素正在使用此缓冲区。当没有元素具有对它的引用时,该引用计数将用于销毁缓冲区。
  • 缓冲区标志。

简单的情况是创建缓冲区、分配内存、将数据放入其中,然后传递给下一个元素。该元素读取数据,执行某些操作(如创建新缓冲区并解码),并解除对缓冲区的引用。这导致数据被释放并且缓冲区被破坏。典型的视频或音频解码器就像这样工作。

但是,有更复杂的场景。元素可以就地修改缓冲区,即不分配新的缓冲区。元素也可以写入硬件存储器(例如来自视频捕获源)或从X服务器分配的内存(使用XShm)。缓冲区可以是只读的,依此类推。

4.6.2 Events

Events表示控制信息,它们与缓冲区一起在管道中向上和向下发送。向下传递的事件通知流的其他元素。可能的事件包括搜索、刷新、流末尾通知等。向上传递的事件既用于应用程序元素交互,也用于元素 - 元素交互以请求流状态的更改,例如搜索。对于应用程序,只有向上传递的事件很重要。刚刚解释了向下传递的事件,以便更全面地了解数据概念。

由于大多数应用程序都在寻找时间单位,因此下面的示例也是如此:

static void
seek_to_time (GstElement *element,
          guint64     time_ns)
{
  GstEvent *event;

  event = gst_event_new_seek (1.0, GST_FORMAT_TIME,
                  GST_SEEK_FLAG_NONE,
                  GST_SEEK_METHOD_SET, time_ns,
                  GST_SEEK_TYPE_NONE, G_GUINT64_CONSTANT (0));
  gst_element_send_event (element, event);
}

函数gst_element_seek()是此的快捷方式。这主要是为了展示它是如何工作的。

4.7 第一个应用程序

本章将总结您在前几章中学到的所有内容。它描述了简单GStreamer应用程序的所有方面,包括初始化库,创建元素,在管道中将元素打包在一起并播放此管道。通过这一切,您将能够构建一个简单的Ogg / Vorbis音频播放器。

4.7.1 Hello world

我们将创建一个简单的第一个应用程序,一个简单的Ogg / Vorbis命令行音频播放器。为此,我们将仅使用标准GStreamer组件。播放器将读取命令行中指定的文件。让我们开始吧!

我们在初始化GStreamer中了解到,在您的应用程序中要做的第一件事是通过调用gst_init()来初始化GStreamer。此外,请确保应用程序包含gst / gst.h,以便正确定义所有函数名称和对象。使用#include <gst / gst.h>来做到这一点。

接下来,您将要使用gst_element_factory_make()创建不同的元素。对于Ogg / Vorbis音频播放器,我们需要一个从磁盘读取文件的源元素。GStreamer在名称“filesrc”下包含此元素。接下来,我们需要解析文件并将其解码为原始音频。GStreamer有两个元素:第一个将Ogg流解析为基本流(视频,音频),称为“oggdemux”。第二种是Vorbis音频解码器,它被称为“vorbisdec”。由于“oggdemux”为每个基本流创建动态pad,因此您需要在“oggdemux”元素上设置“pad-added”事件处理程序,就像您在动态(或sometimes)pads中学习的那样,链接Ogg demuxer和Vorbis解码器元素在一起。最后,我们还需要一个音频输出element,我们将使用“autoaudiosink”,它会自动检测您的音频设备。

剩下要做的就是将所有元素添加到容器元素GstPipeline中,并等到我们播放整首歌曲。我们之前已经学会了如何在Bins中向容器bin中添加元素,并且我们已经了解了Element States中的元素状态。我们还将一个消息处理程序附加到管道总线,以便我们可以检索错误并检测流末尾。

现在让我们将所有代码添加到一起,以获得我们的第一个音频播放器:

#include <gst/gst.h>
#include <glib.h>


static gboolean
bus_call (GstBus     *bus,
          GstMessage *msg,
          gpointer    data)
{
  GMainLoop *loop = (GMainLoop *) 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 *error;

      gst_message_parse_error (msg, &error, &debug);
      g_free (debug);

      g_printerr ("Error: %s\n", error->message);
      g_error_free (error);

      g_main_loop_quit (loop);
      break;
    }
    default:
      break;
  }

  return TRUE;
}


static void
on_pad_added (GstElement *element,
              GstPad     *pad,
              gpointer    data)
{
  GstPad *sinkpad;
  GstElement *decoder = (GstElement *) data;

  /* We can now link this pad with the vorbis-decoder sink pad */
  g_print ("Dynamic pad created, linking demuxer/decoder\n");

  sinkpad = gst_element_get_static_pad (decoder, "sink");

  gst_pad_link (pad, sinkpad);

  gst_object_unref (sinkpad);
}



int
main (int   argc,
      char *argv[])
{
  GMainLoop *loop;

  GstElement *pipeline, *source, *demuxer, *decoder, *conv, *sink;
  GstBus *bus;
  guint bus_watch_id;

  /* Initialisation */
  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);


  /* Check input arguments */
  if (argc != 2) {
    g_printerr ("Usage: %s <Ogg/Vorbis filename>\n", argv[0]);
    return -1;
  }


  /* Create gstreamer elements */
  pipeline = gst_pipeline_new ("audio-player");
  source   = gst_element_factory_make ("filesrc",       "file-source");
  demuxer  = gst_element_factory_make ("oggdemux",      "ogg-demuxer");
  decoder  = gst_element_factory_make ("vorbisdec",     "vorbis-decoder");
  conv     = gst_element_factory_make ("audioconvert",  "converter");
  sink     = gst_element_factory_make ("autoaudiosink", "audio-output");

  if (!pipeline || !source || !demuxer || !decoder || !conv || !sink) {
    g_printerr ("One element could not be created. Exiting.\n");
    return -1;
  }

  /* Set up the pipeline */

  /* we set the input filename to the source element */
  g_object_set (G_OBJECT (source), "location", argv[1], NULL);

  /* we add a message handler */
  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);
  gst_object_unref (bus);

  /* we add all elements into the pipeline */
  /* file-source | ogg-demuxer | vorbis-decoder | converter | alsa-output */
  gst_bin_add_many (GST_BIN (pipeline),
                    source, demuxer, decoder, conv, sink, NULL);

  /* we link the elements together */
  /* file-source -> ogg-demuxer ~> vorbis-decoder -> converter -> alsa-output */
  gst_element_link (source, demuxer);
  gst_element_link_many (decoder, conv, sink, NULL);
  g_signal_connect (demuxer, "pad-added", G_CALLBACK (on_pad_added), decoder);

  /* note that the demuxer will be linked to the decoder dynamically.
     The reason is that Ogg may contain various streams (for example
     audio and video). The source pad(s) will be created at run time,
     by the demuxer when it detects the amount and nature of streams.
     Therefore we connect a callback function which will be executed
     when the "pad-added" is emitted.*/


  /* Set the pipeline to "playing" state*/
  g_print ("Now playing: %s\n", argv[1]);
  gst_element_set_state (pipeline, GST_STATE_PLAYING);


  /* Iterate */
  g_print ("Running...\n");
  g_main_loop_run (loop);


  /* Out of the main 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));
  g_source_remove (bus_watch_id);
  g_main_loop_unref (loop);

  return 0;
}

我们现在已经创建了一个完整的管道,我们可以将管道可视化如下:
在这里插入图片描述

4.7.2 编译和运行程序

要编译helloworld示例,请使用:

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

GStreamer使用pkg-config来获取编译此应用程序所需的编译器和链接器标志。

如果您正在运行非标准安装(即,您自己从源代码安装了GStreamer而不是使用预构建的软件包),请确保将PKG_CONFIG_PATH环境变量设置为正确的位置($ libdir / pkgconfig)。

在不太可能的情况下,您使用的是未安装的GStreamer设置(即gst-uninstalled),您将需要使用libtool来构建hello world程序,如下所示:

libtool --mode=link gcc -Wall helloworld.c -o helloworld $(pkg-config --cflags --libs gstreamer-1.0)

您可以使用./helloworld file.ogg运行此示例应用程序。用您喜欢的Ogg / Vorbis文件替换file.ogg。

4.7.3 总结

这是我们的第一个例子。如您所见,设置管道非常低级但功能强大。您将在本手册的后面部分看到如何使用更高级别的接口创建功能更强大的媒体播放器。我们将在GStreamer应用程序的高级接口中讨论所有这些内容。但是,我们首先会更深入地了解更先进的GStreamer内部结构。

从示例中可以清楚地看出,我们可以非常轻松地将“filesrc”元素替换为从网络读取数据的其他元素,或者与桌面环境更好地集成的其他数据源元素。此外,您可以使用其他解码器和解析器/解复用器来支持其他媒体类型。如果您没有运行Linux,可以使用另一个音频接收器,但是Mac OS X,Windows或FreeBSD,或者您可以使用filesink将音频文件写入磁盘而不是播放它们。通过使用音频卡源,您甚至可以进行音频捕获而不是播放。所有这些都表明了GStreamer元素的可重用性,这是它最大的优势。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值