gstreamer插件开发指南(一)

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

1 简介

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

本指南旨在帮助您了解GStreamer框架,以便您可以开发新插件来扩展现有功能。该指南通过开发一个用C语言编写的示例插件(音频过滤器插件)来解决大多数问题。但是,本指南的后面部分还介绍了编写其他类型插件时涉及的一些问题,本指南的结尾描述了一些GStreamer的Python绑定。

1.1 前言

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

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

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

该框架基于提供各种编解码器和其他功能的插件。插件可以链接并布置在管道中。此管道定义数据流。

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

该指南的适用范围
本指南介绍了如何为GStreamer编写新模块。该指南与几组人群相关:

  • 任何想要添加对GStreamer中处理数据的新方法的支持的人。例如,该组中的某个人可能想要创建新的数据格式转换器,新的可视化工具或新的解码器或编码器。
  • 任何想要添加对新输入和输出设备的支持的人。例如,该组中的人可能希望添加写入新视频输出系统或从数码相机或特殊麦克风读取数据的功能。
  • 任何想要以任何方式扩展GStreamer的人。在理解插件系统对其余代码的约束之前,您需要了解插件系统的工作原理。此外,阅读此内容后,您可能会对使用插件可以完成的工作感到惊讶。

如果您只想使用GStreamer的现有功能,或者您只想使用使用GStreamer的应用程序,则本指南与您无关。如果您只对使用现有插件编写新应用程序感兴趣 - 并且已经有相当多的插件 - 您可能需要查看GStreamer应用程序开发手册。如果您只是想获得GStreamer应用程序的帮助,那么您应该查看该特定应用程序的用户手册。

初读
本指南假设您对GStreamer的基本工作方式有所了解。有关GStreamer编程概念的简要介绍,您可能希望首先阅读GStreamer应用程序开发手册。另请查看GStreamer网站上提供的其他文档。

为了理解本手册,您需要对C语言有基本的了解。由于GStreamer遵循GObject编程模型,本指南还假设您了解GObject编程的基础知识。您可能还想看看Eric Harlow的书“使用GTK +和GDK开发Linux应用程序”。

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

  • 构建插件 - 插件结构简介,使用音频过滤器示例进行说明。

    本部分介绍了构建插件通常需要执行的所有基本步骤,例如使用GStreamer注册元素以及设置基础知识,以便它可以从邻居元素接收数据并将数据发送到邻居元素。讨论首先给出生成基本结构和在构建Boilerplate中注册元素的示例。然后,您将学习如何编写代码以获得在指定pads,link函数和状态是什么时使用的基本过滤器插件。

    之后,我们将展示一些关于如何为应用程序配置元素以及如何在添加属性和信号中进行应用程序元素交互的GObject概念。接下来,您将学习构建一个快速测试应用程序来测试您刚刚在构建测试应用程序中学到的所有知识。我们将在这里介绍基础知识。对于成熟的应用程序开发,您应该查看“应用程序开发手册”。

  • 高级过滤器概念 - 有关GStreamer插件开发的高级功能的信息。
    在了解了基本步骤之后,您应该能够创建具有一些不错功能的音频或视频过滤器插件。但是,GStreamer为插件编写者提供了更多功能。本指南的这一部分包括有关更高级主题的章节,例如调度,GStreamer中的媒体类型定义,时钟,接口和标记。由于这些功能是针对特定目的的,因此您可以按任何顺序阅读它们,其中大多数不需要其他部分的知识。

    第一章,名为不同的调度模式,将解释元素调度的一些基础知识。它不是很深入,但主要是介绍其他事情为何如此起作用。如果您对GStreamer内部感兴趣,请阅读本章。接下来,我们将应用这些知识并讨论另一种类型的数据传输,而不是您在链函数中学到的:不同的调度模式。基于循环的元素可以让您更好地控制输入速率。这在编写复用器时非常有用。

    接下来,我们将在媒体类型和属性中讨论GStreamer中的媒体识别。您将学习如何定义新媒体类型并了解GStreamer中定义的标准媒体类型列表。

    在下一章中,您将学习request和sometimes垫的概念,它们是动态创建的pads,因为应用程序要求它(请求)或因为媒体流需要它(有时)。这将是request和sometimes pad。

    下一章Clocking将解释GStreamer中时钟的概念。当您想知道元素应如何实现音频/视频同步时,您需要此信息。

    接下来的几章将讨论进行应用程序元素交互的高级方法。以前,我们在GObject中了解了添加属性和信号的方法。我们将在支持动态参数中讨论动态参数,这是一种预先定义元素行为的方法。接下来,您将了解Interfaces中的接口。基于GObject的GInterface,接口是应用程序元素交互的特定于目标的方式。最后,您将了解如何在标记(元数据和Streaminfo)中的GStreamer中处理元数据。

    最后一章,活动:寻求,导航等,将讨论GStreamer中事件的概念。事件是进行应用程序元素交互的另一种方式。例如,他们负责寻求。它们也是元素彼此交互的另一种方式,例如让对方知道媒体流不连续性,在管道内转发标签等等。

  • 创建特殊元素类型 - 编写其他插件类型的说明。

    由于本指南的前两部分使用音频过滤器作为示例,因此引入的概念适用于过滤器插件。但是许多概念同样适用于其他插件类型,包括源,接收器和自动插件。本指南的这一部分介绍了处理这些更专业的插件类型时出现的问题。本章首先重点介绍可以使用基类(预制基类)编写的元素,然后在写入解复用器或解析器时编写特殊类型的元素,编写N对1元素或复用器和编写管理器。

  • 附录 - 插件开发人员的更多信息。

    附录包含一些顽固地拒绝在指南的其他部分中完全适应的信息。本节的大部分内容尚未完成。

    本指南的这个介绍部分的其余部分简要概述了GStreamer插件开发中涉及的基本概念。涵盖的主题包括元素和插件,Pads,GstMiniObject,缓冲区和事件以及媒体类型和属性。如果您已熟悉此信息,则可以使用此简短概述来刷新内存,也可以跳至构建插件。

如您所见,有很多东西需要学习,所以让我们开始吧!

  • 通过从GstBin扩展来创建复合元素和复杂元素。这将允许您创建插入其他插件的插件。
  • 将新媒体类型与typedetect函数一起添加到注册表中。这将允许您的插件在全新的媒体类型上运行。

1.2 基础知识

本指南的这一章介绍了GStreamer的基本概念。理解这些概念将有助于您解决扩展GStreamer所涉及的问题。许多这些概念在GStreamer应用程序开发手册中有更详细的解释。这里介绍的基本概念主要用于刷新你的记忆。

Elements 和 Plugins

元素是GStreamer的核心。在插件开发的上下文中,元素是从GstElement类派生的对象。元素在与其他元素链接时提供某种功能:例如,源元素向流提供数据,过滤元素作用于流中的数据。没有元素,GStreamer只是一堆没有任何链接的概念管道配件。GStreamer附带了大量元素,但也可以编写额外的元素。

但是,编写新元素并不完全足够:您需要将元素封装在插件中以使GStreamer能够使用它。插件本质上是一个可加载的代码块,通常称为共享对象文件或动态链接库。单个插件可能包含多个元素的实现,或者只包含一个元素。为简单起见,本指南主要关注包含一个元素的插件。

过滤器是处理数据流的重要元素类型。数据的生产者和消费者分别称为源和宿元素。Bin元素包含其他元素。一种类型的bin负责同步它们包含的元素,以便数据流畅地流动。另一种类型的bin,称为autoplugger元素,自动将其他元素添加到bin并将它们链接在一起,以便它们充当两个任意流类型之间的过滤器。

插件机制在GStreamer中随处可用,即使只使用标准软件包也是如此。一些非常基本的函数驻留在核心库中,所有其他函数都在插件中实现。插件注册表用于在二进制注册表文件中存储插件的详细信息。这样,使用GStreamer的程序不必加载所有插件来确定所需的插件。仅在请求提供的元素时才加载插件。

有关GstElement和GstPlugin的当前实现详细信息,请参阅GStreamer库参考。

Pads

Pads用于协商GStreamer中元素之间的链接和数据流。可以将pads视为元素上的“位置”或“端口”,其中可以与其他元素建立链接,并且数据可以通过该元素流入或流出这些元素。Pads具有特定的数据处理功能:pad可以限制流经它的数据类型。当两个pads的允许数据类型兼容时,仅允许在两个pads之间建立链接。

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

在大多数情况下,GStreamer中的所有数据都通过元素之间的链接流动。数据通过一个或多个源pads流出一个元件,并且元件通过一个或多个pads接收输入数据。源和宿元件分别仅具有source pads和sink pads。

有关GstPad的当前实现详细信息,请参阅GStreamer库参考。

GstMiniObject, Buffers and Events

GStreamer中的所有数据流都被切割成块,这些块从一个元素上的源pads传递到另一个元素上的sink pads。GstMiniObject是用于保存这些数据块的结构。

GstMiniObject包含以下重要类型:

  • 一个确切的类型,指示此GstMiniObject的数据类型(Events,Buffers…)。
  • 引用计数,指示当前持有对miniobject的引用的元素数。当引用计数降至零时,将处理小对象,并且在某种意义上将释放其存储器(有关详细信息,请参见下文)。

对于数据传输,定义了两种类型的GstMiniObject:Events(控制)和Buffers(内容)。

缓冲区可能包含两个链接的pads知道如何处理的任何类型的数据。通常,缓冲区包含从一个元素流向另一个元素的某种音频或视频数据。

缓冲区还包含描述缓冲区内容的元数据。一些重要的元数据类型是:

  • 指向一个或多个GstMemory对象的指针。GstMemory对象是封装内存区域的refcounted对象。
  • 一个时间戳,指示缓冲区中内容的首选显示时间戳。

事件包含有关在两个链接的pads之间流动的流的状态的信息。仅当元素明确支持它们时才会发送事件,否则核心将(尝试)自动处理事件。事件用于指示例如媒体类型,媒体流的结尾或应该刷新缓存。

事件可能包含以下几项:

  • 指示所包含事件类型的子类型。
  • 事件的其他内容取决于特定的事件类型。

活动将在活动中广泛讨论:寻求,导航等。在此之前,将使用的唯一事件是EOS事件,该事件用于指示流末尾(通常是文件结束)。

有关GstMiniObject,GstBuffer和GstEvent的当前实现详细信息,请参阅GStreamer库参考。

Buffer Allocation

缓冲区能够存储几种不同类型的内存块。最通用的缓冲区包含malloc()分配的内存。这些缓冲区虽然方便,但并不总是非常快,因为数据通常需要专门复制到缓冲区中。

许多专用元素创建指向特殊内存的缓冲区。例如,filesrc元素通常将文件映射到应用程序的地址空间(使用mmap()),并创建指向该地址范围的缓冲区。filesrc创建的这些缓冲区与通用缓冲区完全相同,只是它们是只读的。缓冲区释放代码自动确定释放底层内存的正确方法。接收这些缓冲区的下游元素不需要做任何特殊的处理或取消它的事情。

元素可能获得专用缓冲区的另一种方法是通过GstBufferPool或GstAllocator从下游对等方请求它们。元素可以从下游对等元素中请求GstBufferPool或GstAllocator。如果下游能够提供这些对象,则上游可以使用它们来分配缓冲区。在内存分配中查看更多内容。

许多接收器元件具有将数据复制到硬件或直接访问硬件的加速方法。这些元素通常能够为其上游对等体创建GstBufferPool或GstAllocator。一个这样的例子是ximagesink。它创建包含XImages的缓冲区。因此,当上游对等体将数据复制到缓冲区时,它将直接复制到XImage中,使ximagesink能够将图像直接绘制到屏幕上,而不必首先将数据复制到XImage中。

过滤器元素通常可以就地处理缓冲区,也可以在从源缓冲区复制到目标缓冲区时工作。实现这两种算法是最佳的,因为GStreamer框架可以根据需要选择最快的算法。当然,这仅适用于严格的过滤器 - 在源和接收器pads上具有完全相同格式的元素。

Media types and Properties

GStreamer使用类型系统来确保元素之间传递的数据采用可识别的格式。类型系统对于确保在链接元素之间的pads时完全指定格式所需的参数正确匹配也很重要。元素之间建立的每个链接都具有指定的类型和可选的一组属性。在Caps协商中查看有关caps协商的更多信息。

The Basic Types

GStreamer已经支持许多基本媒体类型。以下是GStreamer中用于缓冲区的一些基本类型的表格。该表包含名称(“媒体类型”)以及类型的描述,与类型关联的属性以及每个属性的含义。已定义类型列表中包含受支持类型的完整列表。

2 写插件

您现在已准备好学习如何构建插件。在本指南的这一部分中,您将学习如何应用基本的GStreamer编程概念来编写一个简单的插件。本指南的前几部分没有包含明确的示例代码,可能使事情有点抽象,难以理解。相比之下,本节将通过开发名为“MyFilter”的示例音频过滤器插件来呈现应用程序和代码。

示例滤波器元件将以单个输入pad和单个输出pad。首先,滤波器简单地将媒体和事件数据从其sink pad传递到其source pad而无需修改。但是,在本指南的这一部分结束时,您将学习添加一些更有趣的功能,包括属性和信号处理程序。阅读本指南的下一部分“高级过滤器概念”后,您将能够为插件添加更多功能。

2.1 构建Boilerplate

在本章中,您将学习如何构建新插件的最小代码。从零开始,您将看到如何获取GStreamer模板源。然后,您将学习如何使用一些基本工具来复制和修改模板插件以创建新插件。如果您按照此处的示例进行操作,那么在本章结束时,您将拥有一个可在GStreamer应用程序中编译和使用的音频过滤器插件。

获取GStreamer Plugin Templates

目前有两种方法可以为GStreamer开发一个新的插件:你可以手工编写整个插件,或者你可以复制一个现有的插件模板并编写你需要的插件代码。到目前为止,第二种方法更简单,因此这里不会描述第一种方法。(Errm,也就是说,“它留给了读者阅读。”)

第一步是查看gst-template git模块的副本,以获得一个重要的工具和基本GStreamer插件的源代码模板。要检查gst-template模块,请确保已连接到Internet,并在命令控制台中键入以下命令:

git clone https://gitlab.freedesktop.org/gstreamer/gst-template.git

此命令将检出一系列文件和目录到gst-template中。您将使用的模板位于gst-template / gst-plugin /目录中。您应该查看该目录中的文件,以大致了解插件的源树结构。

如果由于某种原因您无法访问git存储库,您还可以通过gitlab Web界面下载最新版本的快照。

使用项目代码

制作新元素时要做的第一件事是指定一些关于它的基本细节:它的名字是什么,是谁编写的,它是什么版本号等等。我们还需要定义一个对象来表示元素和存储元素需要的数据。这些细节统称为boilerplate。

定义boilerplate的标准方法只是编写一些代码,并填写一些结构。如上一节所述,最简单的方法是复制模板并根据需要添加功能。为了帮助您这样做,./ gst-plugin / tools /目录中有一个工具。此工具make_element是一个命令行应用程序,可为您创建boilerplate代码。

要使用make_element,首先打开一个终端窗口。切换到gst-template / gst-plugin / src目录,然后运行make_element命令。make_element的参数是:

插件的名称以及该工具将使用的源文件。默认情况下,使用gstplugin。

例如,以下命令基于插件模板创建MyFilter插件,并将输出文件放在gst-template / gst-plugin / src目录中:

cd gst-template/gst-plugin/src
../tools/make_element MyFilter

最后一个命令创建两个文件:gstmyfilter.c和gstmyfilter.h。

注意:大写对于插件的名称很重要。请记住,在某些操作系统下,通常在指定目录和文件名时,大小写也很重要。

现在需要调整Makefile.am以使用新文件名并从父目录运行autogen.sh来引导构建环境。之后,可以使用众所周知的make && sudo make install命令构建和安装项目。

注意:请注意,默认情况下autogen.sh和configure会选择/ usr / local作为默认位置。需要将/usr/local/lib/gstreamer-1.0添加到GST_PLUGIN_PATH,以使新插件显示在已从包中安装的gstreamer中。

注意FIXME:这部分略显过时。gst-template仍然可用作最小插件构建系统框架的示例。但是,为了创建元素,最近推荐使用gst-plugins-bad工具gst-element-maker。

Examining the Basic Code

首先,我们将检查您可能放在头文件中的代码(尽管由于代码的接口完全由插件系统定义,并且不依赖于读取头文件,因此这并不重要。)

#include 

/* Definition of structure storing data for this element. */
typedef struct _GstMyFilter {
  GstElement element;

  GstPad *sinkpad, *srcpad;

  gboolean silent;



} GstMyFilter;

/* Standard definition defining a class for this element. */
typedef struct _GstMyFilterClass {
  GstElementClass parent_class;
} GstMyFilterClass;

/* Standard macros for defining types for this element.  */
#define GST_TYPE_MY_FILTER (gst_my_filter_get_type())
#define GST_MY_FILTER(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MY_FILTER,GstMyFilter))
#define GST_MY_FILTER_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MY_FILTER,GstMyFilterClass))
#define GST_IS_MY_FILTER(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MY_FILTER))
#define GST_IS_MY_FILTER_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MY_FILTER))

/* Standard function returning type information. */
GType gst_my_filter_get_type (void);

使用此头文件,您可以使用以下宏在源文件中设置GObject基础,以便适当地调用所有函数:

#include "filter.h"

G_DEFINE_TYPE (GstMyFilter, gst_my_filter, GST_TYPE_ELEMENT);

Element metadata

元素元数据提供额外的元素信息。它配置了gst_element_class_set_metadata或gst_element_class_set_static_metadata,它采用以下参数:

  • 元素的长英文名称。
  • 元素的类型,请参阅GStreamer核心源代码树中的docs / design / draft-klass.txt文档以获取详细信息和示例。
  • 关于元素目的的简要描述。
  • 元素作者的姓名,可选地后跟尖括号中的联系电子邮件地址。

例如:

gst_element_class_set_static_metadata (klass,
  "An example plugin",
  "Example/FirstExample",
  "Shows the basic structure of a plugin",
  "your name ");

在_class_init()函数中,元素详细信息在插件中注册,该函数是GObject系统的一部分。应该在使用GLib注册类型的函数中为此GObject设置_class_init()函数。

static void
gst_my_filter_class_init (GstMyFilterClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

[..]
  gst_element_class_set_static_metadata (element_klass,
    "An example plugin",
    "Example/FirstExample",
    "Shows the basic structure of a plugin",
    "your name ");

}

GstStaticPadTemplate

GstStaticPadTemplate是元素将(或可能)创建和使用的pad的描述。它包含:

  • pad的名字。
  • pad的方向。
  • 存在属性。这表示pad是否总是存在(always pad),仅在某些情况下(sometimes pad)或仅在应用请求这样的垫(request pad)时。
  • 此元素支持的类型(功能)。

例如:

static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
  "sink",
  GST_PAD_SINK,
  GST_PAD_ALWAYS,
  GST_STATIC_CAPS ("ANY")
);

这些pad模板在_class_init()函数期间使用gst_element_class_add_pad_template()进行注册。对于此函数,您需要一个GstPadTemplate句柄,您可以使用gst_static_pad_template_get()从静态pad模板创建该句柄。有关详细信息,请参见下文。

使用gst_pad_new_from_static_template()从元素的_init()函数中的这些静态模板创建Pads。要使用gst_pad_new_from_static_template()从此模板创建新pad,您需要将pad模板声明为全局变量。有关此主题的更多信息,请参阅指定pads。

static GstStaticPadTemplate sink_factory = [..],
    src_factory = [..];

static void
gst_my_filter_class_init (GstMyFilterClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
[..]

  gst_element_class_add_pad_template (element_class,
    gst_static_pad_template_get (&src_factory));
  gst_element_class_add_pad_template (element_class,
    gst_static_pad_template_get (&sink_factory));
}

模板中的最后一个参数是其类型或支持的类型列表。在这个例子中,我们使用’ANY’,这意味着该元素将接受所有输入。在实际情况中,您可以设置媒体类型和可选的一组属性,以确保只有受支持的输入才会进入。此表示应该是以媒体类型开头的字符串,然后是一组逗号分隔属性与他们支持的值。如果音频滤波器支持任何采样率的原始整数16位音频,单声道或立体声,正确的模板将如下所示:

static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
  "sink",
  GST_PAD_SINK,
  GST_PAD_ALWAYS,
  GST_STATIC_CAPS (
    "audio/x-raw, "
      "format = (string) " GST_AUDIO_NE (S16) ", "
      "channels = (int) { 1, 2 }, "
      "rate = (int) [ 8000, 96000 ]"
  )
);

由大括号(“{”和“}”)包围的值是列表,由方括号(“[”和“]”)包围的值是范围。也支持多组类型,并且应以分号(“;”)分隔。稍后,在有关pad的章节中,我们将看到如何使用类型来了解流的确切格式:指定pad。

构造函数

每个元素都有两个用于构造元素的函数。_class_init()函数,用于仅初始化类一次(指定类具有哪些信号,参数和虚函数以及设置全局状态);和_init()函数,用于初始化此类型的特定实例。

plugin_init函数

一旦我们编写了定义插件所有部分的代码,我们就需要编写plugin_init()函数。这是一个特殊的函数,只要加载插件就会调用它,并且应该返回TRUE或FALSE,具体取决于它是否正确加载了初始化任何依赖项。此外,在此函数中,应注册插件中任何受支持的元素类型。

static gboolean
plugin_init (GstPlugin *plugin)
{
  return gst_element_register (plugin, "my_filter",
                   GST_RANK_NONE,
                   GST_TYPE_MY_FILTER);
}

GST_PLUGIN_DEFINE (
  GST_VERSION_MAJOR,
  GST_VERSION_MINOR,
  my_filter,
  "My filter plugin",
  plugin_init,
  VERSION,
  "LGPL",
  "GStreamer",
  "http://gstreamer.net/"
)

请注意,plugin_init()函数返回的信息将缓存在中央注册表中。因此,函数始终返回相同的信息非常重要:例如,它不能基于运行时条件使元素工厂可用。如果一个元素只能在某些条件下工作(例如,如果某个其他进程没有使用声卡),则必须通过无法进入READY状态的元素来反映,而不是插件试图否认存在插件。

2.2 指定pads

如前所述,pad是数据进出元素的端口,这使得它们成为元素创建过程中非常重要的项目。在样板代码中,我们已经看到静态填充模板如何处理使用元素类注册填充模板。在这里,我们将看到如何创建实际元素,使用_event()函数来配置特定格式以及如何注册函数以让数据流过元素。

在元素_init()函数中,您可以使用_class_init()函数中的元素类注册的pad模板创建pad。创建pad后,您必须设置一个_chain()函数指针,该指针将接收并处理sinkpad上的输入数据。您也可以选择设置_event()函数指针和_query()函数指针。或者,pad也可以在循环模式下工作,这意味着它们可以自己提取数据。稍后将详细介绍此主题。之后,您必须使用元素注册pad。这是这样的:

static void
gst_my_filter_init (GstMyFilter *filter)
{
  /* pad through which data comes in to the element */
  filter->sinkpad = gst_pad_new_from_static_template (
    &sink_template, "sink");
  /* pads are configured here with gst_pad_set_*_function () */



  gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);

  /* pad through which data goes out of the element */
  filter->srcpad = gst_pad_new_from_static_template (
    &src_template, "src");
  /* pads are configured here with gst_pad_set_*_function () */



  gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);

  /* properties initial value */
  filter->silent = FALSE;
}

2.3 chain函数

chain函数是进行所有数据处理的函数。在简单过滤器中,_chain()函数主要是线性函数 - 因此对于每个传入缓冲区,一个缓冲区也将传出。下面是一个非常简单的chain函数实现:

static GstFlowReturn gst_my_filter_chain (GstPad    *pad,
                                          GstObject *parent,
                                          GstBuffer *buf);

[..]

static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
  /* configure chain function on the pad before adding
   * the pad to the element */
  gst_pad_set_chain_function (filter->sinkpad,
      gst_my_filter_chain);
[..]
}

static GstFlowReturn
gst_my_filter_chain (GstPad    *pad,
                     GstObject *parent,
             GstBuffer *buf)
{
  GstMyFilter *filter = GST_MY_FILTER (parent);

  if (!filter->silent)
    g_print ("Have data of size %" G_GSIZE_FORMAT" bytes!\n",
        gst_buffer_get_size (buf));

  return gst_pad_push (filter->srcpad, buf);
}

显然,上面没有多大用处。您通常会在那里处理数据,而不是打印数据。但请记住,缓冲区并不总是可写的。

在更高级的元素(进行事件处理的元素)中,您可能还需要另外指定一个事件处理函数,该函数将在发送流事件时调用(例如上限,流末尾,新段,标记等))。

static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
  gst_pad_set_event_function (filter->sinkpad,
      gst_my_filter_sink_event);
[..]
}

static gboolean
gst_my_filter_sink_event (GstPad    *pad,
                  GstObject *parent,
                  GstEvent  *event)
{
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_CAPS:
      /* we should handle the format here */
      break;
    case GST_EVENT_EOS:
      /* end-of-stream, we should close down all stream leftovers here */
      gst_my_filter_stop_processing (filter);
      break;
    default:
      break;
  }

  return gst_pad_event_default (pad, parent, event);
}

static GstFlowReturn
gst_my_filter_chain (GstPad    *pad,
             GstObject *parent,
             GstBuffer *buf)
{
  GstMyFilter *filter = GST_MY_FILTER (parent);
  GstBuffer *outbuf;

  outbuf = gst_my_filter_process_data (filter, buf);
  gst_buffer_unref (buf);
  if (!outbuf) {
    /* something went wrong - signal an error */
    GST_ELEMENT_ERROR (GST_ELEMENT (filter), STREAM, FAILED, (NULL), (NULL));
    return GST_FLOW_ERROR;
  }

  return gst_pad_push (filter->srcpad, outbuf);
}

在某些情况下,元素也可以控制输入数据速率。在这种情况下,您可能想要编写一个所谓的基于循环的元素。源元素(仅具有源pad)也可以是基于get的元素。这些概念将在本指南的高级部分以及专门讨论源pad的部分中进行说明。

2.3 event函数

事件函数会通知您数据流中发生的特殊事件(例如上限,流末尾,新段,标记等)。事件可以在上游和下游传播,因此您可以在sink pad和source pad上接收它们。

下面是一个非常简单的事件函数,我们安装在元素的sink pad上。

static gboolean gst_my_filter_sink_event (GstPad    *pad,
                                          GstObject *parent,
                                          GstEvent  *event);

[..]

static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
  /* configure event function on the pad before adding
   * the pad to the element */
  gst_pad_set_event_function (filter->sinkpad,
      gst_my_filter_sink_event);
[..]
}

static gboolean
gst_my_filter_sink_event (GstPad    *pad,
                  GstObject *parent,
                  GstEvent  *event)
{
  gboolean ret;
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_CAPS:
      /* we should handle the format here */

      /* push the event downstream */
      ret = gst_pad_push_event (filter->srcpad, event);
      break;
    case GST_EVENT_EOS:
      /* end-of-stream, we should close down all stream leftovers here */
      gst_my_filter_stop_processing (filter);

      ret = gst_pad_event_default (pad, parent, event);
      break;
    default:
      /* just call the default handler */
      ret = gst_pad_event_default (pad, parent, event);
      break;
  }
  return ret;
}

为未知事件调用默认事件处理程序gst_pad_event_default()是个好主意。根据事件类型,默认处理程序将转发事件或简单地取消它。CAPS事件默认不转发,因此我们需要在事件处理程序中自己执行此操作。

2.5 查询函数

通过查询函数,您的元素将接收必须回复的查询。这些是诸如位置、持续时间之类的查询,但也包括元素支持的格式和调度模式。查询可以在上游和下游传输,因此您可以在sink pad和source pad上接收它们。

下面是一个非常简单的查询函数,我们在元素的源代码上安装它。

static gboolean gst_my_filter_src_query (GstPad    *pad,
                                         GstObject *parent,
                                         GstQuery  *query);

[..]

static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
  /* configure event function on the pad before adding
   * the pad to the element */
  gst_pad_set_query_function (filter->srcpad,
      gst_my_filter_src_query);
[..]
}

static gboolean
gst_my_filter_src_query (GstPad    *pad,
                 GstObject *parent,
                 GstQuery  *query)
{
  gboolean ret;
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_POSITION:
      /* we should report the current position */
      [...]
      break;
    case GST_QUERY_DURATION:
      /* we should report the duration here */
      [...]
      break;
    case GST_QUERY_CAPS:
      /* we should report the supported caps here */
      [...]
      break;
    default:
      /* just call the default handler */
      ret = gst_pad_query_default (pad, parent, query);
      break;
  }
  return ret;
}

为未知查询调用默认查询处理程序gst_pad_query_default()是个好主意。根据查询类型,默认处理程序将转发查询或只是取消它。

2.6 状态

状态描述元素实例是否已初始化,是否准备好传输数据以及它当前是否正在处理数据。GStreamer中定义了四种状态:

GST_STATE_NULL

GST_STATE_READY

GST_STATE_PAUSED

GST_STATE_PLAYING

从现在开始,它将简称为“NULL”,“READY”,“PAUSED”和“PLAYING”。

GST_STATE_NULL是元素的默认状态。在这种状态下,它没有分配任何运行时资源,它没有加载任何运行时库,它显然不能处理数据。

GST_STATE_READY是元素可以处于的下一个状态。在READY状态中,元素具有分配的所有默认资源(运行时库,运行时内存)。但是,它尚未分配或定义任何特定于流的内容。当从NULL进入READY状态(GST_STATE_CHANGE_NULL_TO_READY)时,元素应该分配任何非流特定的资源,并且应该加载运行时可加载的库(如果有的话)。当反过来时(从READY到NULL,GST_STATE_CHANGE_READY_TO_NULL),元素应该卸载这些库并释放所有分配的资源。这种资源的示例是硬件设备。请注意,文件通常是流,因此应将其视为特定于流的资源;因此,它们不应该在这种状态下分配。

GST_STATE_PAUSED是元素准备接受和处理数据的状态。对于大多数元素,此状态与PLAYING相同。此规则的唯一例外是sink元素。sink元素只接受一个数据缓冲区然后阻塞。此时,管道已“预卷”并准备好立即呈现数据。

GST_STATE_PLAYING是元素可以处于的最高状态。对于大多数元素,此状态与PAUSED完全相同,它们接受并处理带有数据的事件和缓冲区。只有sink元素需要区分PAUSED和PLAYING状态。在PLAYING状态中,sink元素实际上呈现输入数据,例如,将音频输出到声卡或将视频图像渲染到图像接收器。

管理滤波器状态
如果可能的话,您的元素应该派生自一个新的基类(预制基类)。有针对不同类型的源、sink和过滤器/转换元件的现成通用基类。除此之外,还存在音频和视频元素等专用基类。

如果使用基类,则很少需要自己处理状态更改。您所要做的就是覆盖基类的start()和stop()虚函数(根据基类可能会有不同的调用),基类将为您处理所有事情。

但是,如果您不是从现成的基类派生,而是从GstElement或其他不基于基类构建的类派生,那么您很可能必须实现自己的状态更改函数以通知状态更改。如果您的插件是多路分离器或多路复用器,这绝对是必要的,因为还没有用于多路复用器或多路分离器的基类。

可以通过虚函数指针通知元素状态变化。在此函数内部,元素可以初始化元素所需的任何类型的特定数据,并且可以选择无法从一个状态转到另一个状态。

对于未处理的状态更改,请不要g_assert;这由GstElement基类处理。

static GstStateChangeReturn
gst_my_filter_change_state (GstElement *element, GstStateChange transition);

static void
gst_my_filter_class_init (GstMyFilterClass *klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

  element_class->change_state = gst_my_filter_change_state;
}

static GstStateChangeReturn
gst_my_filter_change_state (GstElement *element, GstStateChange transition)
{
  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
  GstMyFilter *filter = GST_MY_FILTER (element);

  switch (transition) {
	case GST_STATE_CHANGE_NULL_TO_READY:
	  if (!gst_my_filter_allocate_memory (filter))
		return GST_STATE_CHANGE_FAILURE;
	  break;
	default:
	  break;
  }

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
  if (ret == GST_STATE_CHANGE_FAILURE)
	return ret;

  switch (transition) {
	case GST_STATE_CHANGE_READY_TO_NULL:
	  gst_my_filter_free_memory (filter);
	  break;
	default:
	  break;
  }

  return ret;
}

请注意,向上(NULL => READY,READY => PAUSED,PAUSED => PLAYING)和向下(PLAYING => PAUSED,PAUSED => READY,READY => NULL)状态更改在两个单独的块中处理,向下状态更改只有在我们链接到父类的状态更改函数之后才会处理。这是为了安全地处理多个线程的并发访问所必需的。

这样做的原因是,在向下状态更改的情况下,您不希望销毁已分配的资源,而插件的link函数(例如)仍然在另一个线程中访问这些资源。您的link函数是否可能正在运行取决于插件的pad状态,并且这些pad的状态与元素的状态紧密相关。Pad状态在GstElement类的状态更改函数中处理,包括正确的锁定,这就是在销毁分配的资源之前必须进行link的原因。

2.7 添加属性

控制元素行为方式的主要和最重要的方法是通过GObject属性。GObject属性在_class_init()函数中定义。该元素可选地实现_get_property()和_set_property()函数。如果应用程序更改或请求属性的值,将通知这些函数,然后可以填写值或采取该属性所需的操作以在内部更改值。

您可能还希望保留一个实例变量,其中包含您在get和set函数中使用的属性的当前配置值。请注意,GObject不会自动将您的实例变量设置为默认值,您必须在元素的_init()函数中执行此操作。

/* properties */
enum {
  PROP_0,
  PROP_SILENT
  /* FILL ME */
};

static void gst_my_filter_set_property  (GObject      *object,
                         guint         prop_id,
                         const GValue *value,
                         GParamSpec   *pspec);
static void gst_my_filter_get_property  (GObject      *object,
                         guint         prop_id,
                         GValue       *value,
                         GParamSpec   *pspec);

static void
gst_my_filter_class_init (GstMyFilterClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  /* define virtual function pointers */
  object_class->set_property = gst_my_filter_set_property;
  object_class->get_property = gst_my_filter_get_property;

  /* define properties */
  g_object_class_install_property (object_class, PROP_SILENT,
    g_param_spec_boolean ("silent", "Silent",
              "Whether to be very verbose or not",
              FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static void
gst_my_filter_set_property (GObject      *object,
                guint         prop_id,
                const GValue *value,
                GParamSpec   *pspec)
{
  GstMyFilter *filter = GST_MY_FILTER (object);

  switch (prop_id) {
    case PROP_SILENT:
      filter->silent = g_value_get_boolean (value);
      g_print ("Silent argument was changed to %s\n",
           filter->silent ? "true" : "false");
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_my_filter_get_property (GObject    *object,
                guint       prop_id,
                GValue     *value,
                GParamSpec *pspec)
{
  GstMyFilter *filter = GST_MY_FILTER (object);

  switch (prop_id) {
    case PROP_SILENT:
      g_value_set_boolean (value, filter->silent);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

以上是如何使用属性的一个非常简单的示例。图形应用程序将使用这些属性,并将显示用户可控制的窗口小部件,可以使用这些窗口小部件更改这些属性。这意味着 - 为了使属性尽可能方便用户 - 您应该在属性的定义中尽可能准确。不仅可以定义有效属性之间的范围(对于整数,浮点数等),还可以在属性定义中使用非常具有描述性(更好的:国际化)的字符串,如果可能,还可以使用枚举和标志而不是整数。GObject文档以非常完整的方式描述了这些内容,但在下面,我们将给出一个简短的示例,说明它的用处。请注意,在这里使用整数可能会完全混淆用户,因为在这种情况下它们没有任何意义。这个例子是从videotestsrc偷来的。

typedef enum {
  GST_VIDEOTESTSRC_SMPTE,
  GST_VIDEOTESTSRC_SNOW,
  GST_VIDEOTESTSRC_BLACK
} GstVideotestsrcPattern;

[..]

#define GST_TYPE_VIDEOTESTSRC_PATTERN (gst_videotestsrc_pattern_get_type ())
static GType
gst_videotestsrc_pattern_get_type (void)
{
  static GType videotestsrc_pattern_type = 0;

  if (!videotestsrc_pattern_type) {
    static GEnumValue pattern_types[] = {
      { GST_VIDEOTESTSRC_SMPTE, "SMPTE 100% color bars",    "smpte" },
      { GST_VIDEOTESTSRC_SNOW,  "Random (television snow)", "snow"  },
      { GST_VIDEOTESTSRC_BLACK, "0% Black",                 "black" },
      { 0, NULL, NULL },
    };

    videotestsrc_pattern_type =
    g_enum_register_static ("GstVideotestsrcPattern",
                pattern_types);
  }

  return videotestsrc_pattern_type;
}

[..]

static void
gst_videotestsrc_class_init (GstvideotestsrcClass *klass)
{
[..]
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PATTERN,
    g_param_spec_enum ("pattern", "Pattern",
               "Type of test pattern to generate",
                       GST_TYPE_VIDEOTESTSRC_PATTERN, GST_VIDEOTESTSRC_SMPTE,
                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
[..]
}

2.8 信号

GObject信号可用于通知应用程序特定于此对象的事件。但请注意,应用程序需要了解信号及其含义,因此,如果您正在寻找应用程序元素交互的通用方法,那么信号可能不是您正在寻找的。然而,在许多情况下,信号非常有用。有关信号的所有内部信息,请参阅GObject文档。

2.9 构建一个测试应用

通常,您需要在尽可能小的设置中测试新编写的插件。通常,gst-launch-1.0是测试插件的第一步。如果您尚未在GStreamer搜索的目录中安装插件,则需要设置插件路径。将GST_PLUGIN_PATH设置为包含插件的目录,或使用命令行选项–gst-plugin-path。如果你的插件基于gst-plugin模板,那么这将看起来像gst-launch-1.0 --gst-plugin-path = $ HOME / gst-template / gst-plugin / src / .libs TESTPIPELINE。但是,您通常需要比gst-launch-1.0提供的更多测试功能,例如搜索、事件、交互性等。编写自己的小型测试程序是实现这一目标的最简单方法。本节解释 - 用几句话 - 如何做到这一点。有关完整的应用程序开发指南,请参阅应用程序开发手册。

首先,您需要通过调用gst_init()来初始化GStreamer核心库。您也可以调用gst_init_get_option_group(),它将返回指向GOptionGroup的指针。然后,您可以使用GOption来处理初始化,这将完成GStreamer初始化。

您可以使用gst_element_factory_make()创建元素,其中第一个参数是您要创建的元素类型,第二个参数是自由格式名称。最后的示例使用简单的文件源 - 解码器 - 声卡输出管道,但如果需要,您可以使用特定的调试元素。例如,可以在管道的中间使用标识元素来充当数据到应用程序的发送器。这可用于检查测试应用程序中的数据是否存在错误或正确性。此外,您可以使用管道末尾的fakesink元素将数据转储到stdout(为了执行此操作,请将dump属性设置为TRUE)。最后,您可以使用valgrind来检查内存错误。

在链接期间,您的测试应用程序可以使用过滤的caps作为向元素或从元素驱动特定类型数据的方法。这是检查元素中多种输入和输出的一种非常简单有效的方法。

请注意,在运行期间,您应该至少侦听总线和/或插件/元素上的“错误”和“eos”消息,以检查是否正确处理了此消息。此外,您应该将事件添加到管道中,并确保您的插件正确处理这些事件(关于时钟,内部缓存等)。

永远不要忘记清理插件或测试应用程序中的内存。当进入NULL状态时,您的元素应该清理已分配的内存和缓存。此外,它应该关闭对可能的支持库的任何引用。您的应用程序应该unref()管道并确保它不会崩溃。

#include 

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 = NULL;
      GError *err = NULL;

      gst_message_parse_error (msg, &err, &debug);

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

      if (debug) {
        g_print ("Debug details: %s\n", debug);
        g_free (debug);
      }

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

  return TRUE;
}

gint
main (gint   argc,
      gchar *argv[])
{
  GstStateChangeReturn ret;
  GstElement *pipeline, *filesrc, *decoder, *filter, *sink;
  GstElement *convert1, *convert2, *resample;
  GMainLoop *loop;
  GstBus *bus;
  guint watch_id;

  /* initialization */
  gst_init (&argc, &argv);
  loop = g_main_loop_new (NULL, FALSE);
  if (argc != 2) {
    g_print ("Usage: %s \n", argv[0]);
    return 01;
  }

  /* create elements */
  pipeline = gst_pipeline_new ("my_pipeline");

  /* watch for messages on the pipeline's bus (note that this will only
   * work like this when a GLib main loop is running) */
  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  watch_id = gst_bus_add_watch (bus, bus_call, loop);
  gst_object_unref (bus);

  filesrc  = gst_element_factory_make ("filesrc", "my_filesource");
  decoder  = gst_element_factory_make ("mad", "my_decoder");

  /* putting an audioconvert element here to convert the output of the
   * decoder into a format that my_filter can handle (we are assuming it
   * will handle any sample rate here though) */
  convert1 = gst_element_factory_make ("audioconvert", "audioconvert1");

  /* use "identity" here for a filter that does nothing */
  filter   = gst_element_factory_make ("my_filter", "my_filter");

  /* there should always be audioconvert and audioresample elements before
   * the audio sink, since the capabilities of the audio sink usually vary
   * depending on the environment (output used, sound card, driver etc.) */
  convert2 = gst_element_factory_make ("audioconvert", "audioconvert2");
  resample = gst_element_factory_make ("audioresample", "audioresample");
  sink     = gst_element_factory_make ("pulsesink", "audiosink");

  if (!sink || !decoder) {
    g_print ("Decoder or output could not be found - check your install\n");
    return -1;
  } else if (!convert1 || !convert2 || !resample) {
    g_print ("Could not create audioconvert or audioresample element, "
             "check your installation\n");
    return -1;
  } else if (!filter) {
    g_print ("Your self-written filter could not be found. Make sure it "
             "is installed correctly in $(libdir)/gstreamer-1.0/ or "
             "~/.gstreamer-1.0/plugins/ and that gst-inspect-1.0 lists it. "
             "If it doesn't, check with 'GST_DEBUG=*:2 gst-inspect-1.0' for "
             "the reason why it is not being loaded.");
    return -1;
  }

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

  gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, convert1, filter,
                    convert2, resample, sink, NULL);

  /* link everything together */
  if (!gst_element_link_many (filesrc, decoder, convert1, filter, convert2,
                              resample, sink, NULL)) {
    g_print ("Failed to link one or more elements!\n");
    return -1;
  }

  /* run */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    GstMessage *msg;

    g_print ("Failed to start up pipeline!\n");

    /* check if there is an error message with details on the bus */
    msg = gst_bus_poll (bus, GST_MESSAGE_ERROR, 0);
    if (msg) {
      GError *err = NULL;

      gst_message_parse_error (msg, &err, NULL);
      g_print ("ERROR: %s\n", err->message);
      g_error_free (err);
      gst_message_unref (msg);
    }
    return -1;
  }

  g_main_loop_run (loop);

  /* clean up */
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);
  g_source_remove (watch_id);
  g_main_loop_unref (loop);

  return 0;
}

你可能感兴趣的:(DeepStream)