GStreamer插件开发指南 (0.10.9.1)
I. 介绍
GStreamer是一个用来创建流媒体应用程序的非常强大和通用的框架。GStreamer框架的许多优点来源于它的模块性 :GStreamer可以无缝地接纳新的插件模块。但是由于模块性和强大的功能通常以极高的复杂性为代价(例如CORBA),编写一个新的插件并不总是一件简单的事。
本手册旨在帮助你了解GStreamer的框架0.10.9.1版)以便你能够开发新的插件来扩展当前的功能。本手册通过用C语言开发一个示例插件──一个音频过滤器──来定位大多数问题。然而,接下来的部分会通过编写其它类型的插件引入另一些问题,在本手册的结尾介绍了一些GStreamer的Python绑定。
目录
1. 前言
1.1. GStreamer是什么?
1.2. 谁应该读该指南?
1.3. 预备知识
1.4. 该指南的结构
2. 基本知识
2.1. 元件(Elements)和插件(Plugins)
2.2. 衬垫(Pads)
2.3. 数据(Data),缓冲区(Buffers)和事件(Events)
2.3.1. 缓冲区分配
2.4. MIME类型(Mimetypes)和属性
2.4.1. 基本类型
GStreamer是一个创建流媒体应用程序的框架。其基本设计思想来自于俄勒冈(Oregon)研究生学院有关视频管道的创意, 同时也借鉴了DirectShow[h1] 的设计思想。
GStreamer的开发框架使它有可能被用来编写任何类型的流媒体应用程序。基于GStreamer的程序开发框架使得编写任意类型的流媒体应用程序成为了可能。在编写处理音频、视频或者两者皆有的应用程序时, GStreamer可以让你的工作变得简单。GStreamer并不受限于音频和视频处理, 它能够处理任意类型的数据流。管道的设计对于一般应用的滤镜(filter)绰绰有余。这使得GStreamer成为一个优秀的框架,它甚至可以用来设计出对延时有很高要求的高端音频应用程序。
GStreamer最显著的用途是在构建一个播放器上。GStreamer已经支持很多格式的文件了, 包括:MP3, Ogg/Vorbis, MPEG-1/2, AVI, Quicktime, mod等等。从这个角度看,GStreamer更象是一个播放器。但是它主要的优点确是在于: 它的可插入组件能够很方便的接入到任意的管道当中。这个优点使得利用GStreamer编写一个万能的可编辑音视频应用程序成为可能。
GStreamer框架是基于插件的, 有些插件中提供了各种各样的多媒体数字信号编解码器,也有些提供了其他的功能。所有的插件都能够被链接到任意的已经定义了的数据流管道中。GStreamer的管道能够被GUI编辑器编辑, 能够以XML文件来保存。这样的设计使得管道程序库的消耗变得非常少。
GStreamer核心库函数是一个处理插件、数据流和媒体操作的框架。 GStreamer核心库还提供了一个API, 这个API是开放给程序员使用的---当程序员需要使用其他的插件来编写他所需要的应用程序的时候可以使用它。
该指南叙述了如何为GStreamer编写新的模块。本指南针对以下几种人:
· 那些想给 GStreamer增加新的数据处理方法的人。例如,可能有人想创建一个新的可见的工具---数据类型转换器,或者新的编码器或者解码器。
· 那些想支持新的输入输出设备的人。例如:某人可能会想增加将数据输出到新的视频输出系统或从一个数码相机或耳机中读取数据的功能。
· 那些想通过任何方法扩展GStreamer的人。你必须事先了解插件系统是如何工作的,这样你才能知道插件系统对其它代码有什么限制。读完了该手册,你也许会因为插件可以做如此多的事而感到惊讶。
如果你只是想使用GStreamer的已有功能,或者只想使用那些基于GStreamer程序,那么你可以不用再看下去了。如果你只关注与使用现有的插件去便写新的程序──现在有许多这样的插件──你也许想看的是《GStreamer应用程序开发手册》如果你只是想获得那些基于GStreamer的程序的帮助,那么你应该查看那些程序的用户手册。
该指南假定你已经稍微了解了GStreamer的基本工作原理。你也许希望首先阅读《GStreamer应用程序开发手册》,以便获得对GStreamer编程概念的了解。另外,不要忘了查看GStreamer网站上的已有文档。
为了理解这份手册,你必须对C语言有一个基本的了解。因为GStreamer是基于GObject编程模型,所以本指南假定你也已经对GObject编程有了一个基本的了解。你或许也想看一下Eric Harlow's的《Developing Linux Applications with GTK+ and GDK》一书。
为了帮助你驾驭这份指南,我们将其分为几个大的部分。每一部分致力于一个特定的GStreamer插件开发相关的广泛概念。该指南的所有部分按照以下顺序组织:
· 编写一个插件 ── 介绍插件的结构,并用一个音频滤镜作为演示。
这一部分涵盖了编写一个插件的所有基本步骤,例如将element注册到GStreamer并进行一些初步设置以便能和相邻的element互传数据。讨论以一个示例程序开始,该程序创建一些基本的结构并在构建样板一节中注册一个element。接着,你将在Chapter 4, Chapter 5和Chapter 6中学会怎样通过一段代码得到一个基本的滤镜插件。
随后,在增加参数和Chapter 8中,我们将演示如何使用GObject使element在程序中可配置,以及怎样使你的程序和element交互。下一步,你将学习如何快速的创建一个测试程序来测试你在Chapter 9所学的所有知识。这里只涉及一些应用程序开发的基本知识,要想对此有一个全面的了解,请查看应用程序开发手册。
· 高阶滤镜概念 ── GStreamer插件开发的高级特性信息。
通过对初步知识的学习,你应该能够创建一个有很好的特性的音频或视频过滤器插件了。然而,GStreamer为插件编写者提供了更多的东西。这一部分的章节包含了一些更高级的主题。例如调度,GStreamer的类型定义,时钟,接口和标签。因为这些特性是针对特定应用的(purpose-specific),你可以按任何顺序阅读它们,它们大多不依赖于其他章节的知识。
在该部分的第一章不同的调度模式 我们将解释一些element调度的基本知识。这部分不会很深入,只是一种介绍性的阐述为什么那些东西会那样工作。如果你对GStreamer的内部感兴趣,请阅读这一章。接下来,我们将这一知识运用于另一种类型的数据传输Chapter 5:不同的调度模式基于环路的(Loop-based)元件将让你能对其输入速率有更多的控制。这些在编写muxers或demuxers时是很有用的。
下一步,我们将在Chapter 12中讨论GStreamer媒体标识。你将学到怎样定义新的媒体类型及得到GStreamer内部的标准媒体类型列表。
在下一章,你将学习request-pads和sometimes-pads的概念,这些pad都是动态创建的, 或者是因为应用程序的需要(request),或者是因为媒体类型的要求(sometimes),这些都将在Chapter 13中叙述。
接着,在Chapter 14,将解释GStreamer中的时钟概念。如果你的element中需要同步音视频,你必须知道这些信息。
接下来的几章,我们将讨论应用程序和element进行交互的高级方法。之前,我们在添加参数 和Chapter 8中学会了如何通过GObject的方法来完成这一功能。我们将在Chapter 15中讨论动态参数,这是一种预先定义element行为的方法。下一步,我们会在Chapter 17中学习到接口的方方面面。接口是个非常特别的东西-应用级元件的特定方式,基于GObject的 GInterface之上。最后,你将在Chapter 18中学到GStreamer是怎样处理元数据的。
在最后一章,Chapter 19中,将讨论GStreamer的事件机制。一方面,事件也是应用程序和element间交互的方法,例如定位事件。另一方面,事件也是element间进行交互的一种方法,例如,element可以通过事件传递媒体流断开的状况,在管道内转发标签等等。
· 创建特殊类型的元件 ── 编写其它类型的插件
由于前两部分以一个音频过滤器为例,所介绍的概念可以运用到过滤器插件的开发中。同时许多概念也可以运用到sources,sinks和autopluggers等其它类型的插件中。这一部分讨论编写其他特定类型的插件会碰到的问题。本章开始将焦点集中在可以使用基类(Pre-made base classes)编写的 element上。接下来,在 写一个Demuxer或Parser, 写一个N-to-1元件或Muxer和写一个管理器中,讨论编写特殊的元件。
· 附录 ── 关于插件开发的进一步信息
附录中包含一些不适合在手册的其它章节讲述的信息,这些大部分还没有完成。
手册的剩余部分总揽了GStreamer插件开发中的基本概念,涵盖的内容包括元件(Elements)和插件(Plugins), 衬垫, 数据(Data),缓冲区(Buffers)和事件(Events)和 类型和属性。如果你已经熟悉了对这一部分,你可以将之当作一次回顾,或者直接跳到编写一个插件。
正如你所见,我们有许多的东西需要学习,让我们就此开始吧!
· 通过扩展GstBin创建复杂的element,这样可以创建包含其它插件的插件。
· 通过在类型侦测函数(typedetect functions)中加入新的mime类型,可以让你的插件处理全新的媒体类型。
本章介绍GStreamer的基本概念。掌握了这些基本概念将有助于你解决在扩展GStreamer时遇到的问题。关于这些概念的更详尽的解释请参考 GStreamer应用程序开发指南;这些基本概念放在这儿主要是为了帮助你回顾一遍。
元件是GStreamer的核心。在插件的开发中,一个元件就是继承于 GstElement
的一个对象。元件在与其他元件连接时提供了如下一些功能 :例如,一个源元件为一个流提供数据,一个滤镜元件对流中的数据进行操作。没有了元件,GStreamer只是一堆概念性的管道,没有任何东西可供连接 。GStreamer已经自带了一大堆元件,但是我们仍然可以编写额外的元件。
然而,仅仅写一个新的元件并不够,为了使GStreamer能够使用它,你必须将元件封装到一个插件中。一个插件是一块可以加载的代码,通常被称为共享对象文件(shared object file)或动态链接库(dynamically linked library)。一个插件中可以包含一个或若干element。为简单起见,本手册主要涉及只包含一个element的插件。
滤镜是一类处理流数据的重要插件。数据的生产者和消费者分别被称为source和sink元件。 箱柜(Bin)元件可以包含其它的元件。箱柜的主要职责是调度它包含的元件并使得数据流更平滑。热插拔(autoplugger)元件是另一种箱柜,它可以动态的加载其它元件,并将它们连接起了形成一个可以处理两个任意流的滤镜。
GStreamerr充斥着插件的概念,即使你只使用到一些标准的包。核心库中只有少量基本函数,其它所有的功能都由插件来实现。一个XML文件被用来保存所有注册的插件的详细信息。 这样,使用GStreamer的程序可以只在需要时加载插件,而不必事先全部加载。插件也只在需要它们的元件时才被加载。
查阅GStreamer库索引来获取GstElement
和GstPlugin
的当前实现细节。
在GStreamer中,衬垫是用来在元件间协商连接和数据流的。衬垫可以看作元件间互相连接的 “接口”,数据流通过这些接口流入流出元件。Pad具有特殊的数据处理能力:衬垫可以限制通过它的数据类型。只有当两个衬垫允许通过的数据类型兼容时才可以将它们连接起来。
也许打一个比方可以有助于理解这些概念。衬垫类似于物理设备上的a plug or jack。想象一个包含功放,DVD播放器和一个视频投影仪器的家庭影院系统。将投影仪和DVD播放器相连是允许的,因为这两个设备具有兼容的video jacks。而要将投影仪和功放连起来也许就行不通了,因为它们之间的jack不同。GStreamer中的衬垫具有和家庭影院系统中的jack相同的功能。
大部分情况下,所有在GStreamer中流经的数据都遵循一个原则。数据从element的一个或多个源衬垫流出,从一个或多个sink衬垫流入。源和sink元件分别只有源和sink衬垫。
查阅GStreamer库索引来获取GstPad
的当前实现细节。
GStreamer中的所有数据流被分割成一块一块的,并从一个元件的源衬垫传到另一个元件的sink衬垫。数据就是用来承载一块一块数据的数据结构。
1、数据包含以下的重要组成部分:
· 一个类型域标识该数据的准确类型(control,content,...)。
· 一个指示当前有多少元件引用缓冲区的引用计数器。当计数器的值为0时,缓冲区将被销毁,内存被释放(更详细的细节看下面)。
当前存在两种数据类型:事件(control)和缓冲区(content)。
2、缓冲区可以包含两个相连接的pad所能处理的任何数据。通常,一个缓冲区包含一块音频或视频数据块,该数据块从一个元件流向(push、pull)另一个元件。
缓冲区同样包含描述缓冲区内容的元数据(metadata)。一些重要的元数据类型有:
· 一个指向缓冲区数据的指针。
· 一个标识缓冲区数据大小的整型变量。
· 一个指示缓冲区的最佳显示时间的时间戳。
3、事件包含两个相连的衬垫间的流的状态的信息。只有元件显式地支持事件时,事件才会被发送至管道中的bus,否则核心层将(尝试)自动处理事件。举例来说,事件会被用来表示一个时钟中断、媒体流的结束或高速缓冲区(cache)需要刷新。
事件结构可能会包含如下的成员:
· 一个用来标明事件类型的子类型。
· 事件类型相关的其他部分。
事件将在Chapter 19一章中广泛讨论。在那之前,只会涉及流结束(EOS)事件,该事件通常在文件结束时发出,标识流的结束。
查阅GStreamer库索引来获取GstMiniObject
, GstBuffer
和GstEvent
的当前实现细节。
缓冲区是一块可以存放各种数据的内存。缓冲区的内存一般用malloc()函数分配。这样虽然很方便,但不总是最高效,因为数据经常需要被显式的拷入缓冲区。
有些特殊的元件创建指向特殊内存的缓冲区。例如,filesrc 元件通常会(使用 mmap())将一个文件映射到应用程序的地址空间,并创建指向那个地址范围的缓冲区。这些由filesrc创建的缓冲区具有和其它通用的缓冲区一样的行为,唯一的区别是它们是只读的。释放缓冲区的代码将自动检测内存类型并使用正确的方法释放内存。
另一种可能得到特殊缓冲区的途径是向下游同伙(downstream peer)发出请求。这样得到的缓冲区称为downstream-allocated缓冲区。元件可以请求一个连接到源衬垫的同伙创建一个指定大小的空缓冲区。如果下游元件可以创建一个正确大小的特殊缓冲区,它将会这样做。否则,GStreamer将会自动创建一个通用缓冲区。接着,请求缓冲区的元件就可以将数据拷入缓冲区,并将缓冲区push给创建它的源衬垫。
许多sink元件的将数据拷到硬件的函数都经过了优化,或者可以直接操作硬件。这些元件为它们的上游伙伴创建downstream-allocated缓冲区是很平常的事。其中一例是ximagesink。它创建包含XImage的缓冲区,因此当一个上游伙伴将数据拷入缓冲区时,数据被直接拷入XImage,这样ximagesink可以直接将图象画到屏幕上而不用先将数据拷到一个 XImage中。
滤镜元件通常有机会可以直接作用于缓冲区,或者在将数据从源缓冲区拷入目标缓冲区时发生作用。最佳方案是两种算法都予以实现,因为GStreamer框架会在可能的时候选择最快的算法。自然地,这只在元件的源和sink衬垫完全一致的情况下才有效果。
GStreamer使用一个类型系统来保障流经元件的数据格式是可识别的。当连接元件中的衬垫时,类型系统对于确保特定的参数有着非常重要的作用,这些参数对正连接的元件间的衬垫的格式匹配有着特定作用。元件间的每一个连接有一个指定的类型和可选的属性集。
GStreamer已经支持许多基本的媒体类型。下表是GStreamer中的缓冲区所使用的一些基本类型。表中包含了类型的名字("mime type")和对类型的描述,类型相关的属性,以及每个属性的意义。已定义类型列表一节中列出了所有支持的类型。
Table 2-1. Table of Example Types
Mime 类型 |
描述 |
属性 |
属性类型 |
属性值 |
属性描述 |
audio/* |
所有音频类型 |
rate |
整型 |
大于0 |
数据的采样率,每秒的样本数(每个声道)。 |
channels |
整型 |
大于0 |
音频数据的声道数。 |
||
audio/x-raw-int |
未结构化的及未压缩的原始整型音频数据. |
endianness |
整型 |
G_BIG_ENDIAN (1234) or G_LITTLE_ENDIAN (4321) |
样本的字节序列。值G_LITTLE_ENDIAN (4321) 意味着"little-endian" (字节序列是"低位字节优先"). 值G_BIG_ENDIAN (1234) 意味着"big-endian" (字节序列是"高位字节优先"). |
signed |
布尔型 |
TRUE或FALSE |
整型样本值是否带符号。带符号的样本值用一个位来指示符号位(正或负)。不带符号的样本值总为正。 |
||
width |
整型 |
大于0 |
每个样本的最大位数。 |
||
depth |
整型 |
大于0 |
每个样本所使用的位数。该值小于等于width。如果depth小于width,从低位开始算被使用的位数。举例来说,一个32的width,24的depth,意味着每个样本存储在32位的字节(4个字节)中,但只有低24位被使用了。 |
||
audio/mpeg |
使用MPEG音频算法压缩过的音频数据。 |
mpegversion |
整型 |
1, 2或4 |
压缩数据的MPEG-版本。值1表示MPEG-1, -2 和-2.5 layer 1, 2或3。值2和4表示MPEG-AAC 音频压缩算法。 |
framed |
布尔型 |
0或1 |
true值表示每个缓存只包含一帧。false 值表示缓存数和帧数并不是1:1。 |
||
layer |
整型 |
1, 2,或3 |
用来压缩数据的压缩策略层次。 (only if mpegversion=1). |
||
bitrate |
整型 |
大于0 |
位率,每秒的位数。对于VBR (variable bitrate) MPEG 数据,这是个平均位率。 |
||
audio/x-vorbis |
Vorbis 音频数据 |
|
|
|
对这种类型的数据通常没有特定的属性。 |
II. 构建插件
现在可以学习如何编写一个插件了。在这一部分,你将学习如何利用GStreamer 的基本编程概念来写一个简单的插件。也许前一部分没有涉及代码,导致事情变的有点抽象和难于理解。相比而言,这一部分将出现程序和代码,我们将开发一个名为"ExampleFilter".
示例过滤器元件的开发将以一个单一的输入衬垫和输出衬垫开始。起初,过滤器只是简单的将媒体和事件数据从它的sink pad传送到它的source pad而不作任何改动。但在这一部分的最后,你将学到如何给它添加一些包括属性和信号处理等有趣的功能。并且,读完下一部分,即Advanced Filter Concepts后,你将可以给你的插件添加更多的功能。
你可以在GStreamer目录下的 examples/pwg/examplefilter/中找到着一部分所使用的示例代码。
目录
3. 构建样板(Boilerplate)
3.1. 获取GStreamer插件模板
3.2. 使用项目戳(Project Stamp)
3.3. 检查基本代码
3.4. GstElementDetails
3.5. GstStaticPadTemplate
3.6. 构造函数
3.7. Plugin_init函数
4. 指定衬垫(pads)
4.1. Setcaps-函数
5. 链函数(The chain function)
6. 什么是状态?
6.1. 管理滤镜的状态
7. 添加参数
8. 信号
9. 编写测试程序
本章你将学习如何用少量的代码创建一个新的插件。你将看到如何从零开始得到GStreamer的模板代码。随后你将学习怎样通过使用一些基本工具拷贝修改模板插件来生成一个新的插件。如果你照着示例做,到本章结束,你将得到一个可在GStreamer程序中编译使用的音频过滤插件。
当前有两种方法可以用来开发一个新的GStreamer插件:你可以手写全部的代码,或者你可以拷贝一份现成的插件模板然后添加你需要的代码。迄今为止,第二种方法是比较简单的,因此,第一种方法将不再提及(这是留给读者的一个作业)。
第一步,从CVS模块中取出 gst-template的一份拷贝,其中包含了一个重要的工具以及GStreamer基本插件的源代码模板。为了顺利得到 gst-template模块,确保你已经连到网络上,然后在终端中执行如下命令:
shell $ cvs -d:pserver:[email protected]/cvs/gstreamer login
Logging in to :pserver:[email protected]:/cvs/gstreamer
CVS password: [ENTER]
shell $ cvs -z3 -d:pserver:[email protected]:/cvs/gstreamer co gst-template
U gst-template/README
U gst-template/gst-app/AUTHORS
U gst-template/gst-app/ChangeLog
U gst-template/gst-app/Makefile.am
U gst-template/gst-app/NEWS
U gst-template/gst-app/README
U gst-template/gst-app/autogen.sh
U gst-template/gst-app/configure.ac
U gst-template/gst-app/src/Makefile.am
...
敲入第一个命令后,你必须按回车键登陆CVS服务器。(你或许必须登陆两次。)第二个命令将check out出一系列文件,并放到./gst-template目录下。你要使用的模板将位于./gst-template/gst-plugin/目录下。你应该先浏览一下该目录中的文件,并对插件的源码树结构有一个总体的了解。
编写一个新的元件所要做的第一件事为它指定一些基本信息:它的名字,作者,版本号等等。我们还必须定义一个对象来表示 element,并存放元件需要的数据。所有的这些统称为样板文件(boilerplate).
定义样板文件的标准方法是:写一些简单的代码,并填入一些结构。正如前一部分所说,实现这一目的的最简单方法拷贝一个模板然后根据你的需要增加一些功能。在./gst-plugins/tools/录下有个工具会对你有所帮助。这个名为make_element的程序是一个快速的命令行工具。
要使用make_element,首先开启一个终端窗口,进入gst-template/gst-plugin/src目录,然后运行make_element命令。make_element的参数为:
1. 插件的名称。
2. 本工具使用的源文件名,缺省使用gstplugin.{c,h}。
注意,插件名称的大小写很重要。在一些操作系统中,路径名的大小写同样很重要。例如:以下的基于插件模板创建一个ExampleFilter插件,并将所有输出文件放入gst-template/gst-plugin/src目录下:
shell $ cd gst-template/gst-plugin/src
shell $ ../tools/make_element ExampleFilter
最后一个命令创建了两个文件: gstexamplefilter.c和 gstexamplefilter.h。
首先我们将检查你要放入头文件中的代码(即使代码的所有接口都在插件系统中定义,不用太关心头文件里的内容,那不是很紧要的。)下面的代码可以在examples/pwg/examplefilter/boiler/gstexamplefilter.h中找到。
Example 3-1. Example Plugin Header File
#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(obj) \
(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"
GST_BOILERPLATE (GstMyFilter, gst_my_filter, GstElement, GST_TYPE_ELEMENT);
GstElementDetails结构给出了元件的一个层次类型,一个有可读性的描述,以及作者和版本信息。该结构的成员有:
· 插件的一个长英文名。
· 在一个层次树中的元件的类型。层次树定义为:指定一个顶层类型,后跟一个“/”,再跟下一层的类型,依次进行下去。类型的定义应该根据遍布在该文档中的指导方针进行。(FIXME:写下这些方针,并给出一个好的索引)
· 对元件要实现目标的简短描述。
· 元件作者的名字,可选的跟一个由尖括号括起来的email地址。
例如:
static GstElementDetails my_filter_details = {
"An example plugin",
"Example/FirstExample",
"Shows the basic structure of a plugin",
"your name "
};
元件细节在_base_init ()
函数中被注册到插件中,作为GObject系统的一部分。你应该在用Glib注册类型 的函数中来设置该GObject的_base_init ()
函数。
static void
gst_my_filter_base_init (gpointer klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
static GstElementDetails my_filter_details = {
[..]
};
[..]
gst_element_class_set_details (element_class, &my_filter_details);
}
一个GstStaticPadTemplate是一个element将(也许)会创建并使用的pad的描述。它包含:
· Pad的一个简短的名字。
· Pad的方向。
· 现有属性。这表示pad是否总是存在(一个“always” pad),只在一些情况下存在(一个“sometimes” pad),或只在程序请求时存在(一个“request” pad)。
· 该element支持的类型(capabilities)。
例如:
static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
"sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("ANY")
);
那些pad模板在 _base_init ()
函数中被注册。在_init ()
函数中用 gst_pad_new_from_template ()
根据模板来创建pad。该模板可以使用 gst_element_class_get_pad_template ()
函数从element类中得到。更详细的信息见下面。要想用gst_pad_new_from_template ()
,函数从模板中创建一个新的pad,你必须将模板定义成全局变量。更多详情请参考第四章。
static GstStaticPadTemplate sink_factory = [..],
src_factory = [..];
static void
gst_my_filter_base_init (gpointer 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’,意思是这个element接受所有输入格式。在一个实用的插件中,你会设置一个 MIME类型,以及一个可选的属性集合来保证只有支持的输入格式可以通过。参数的格式是一个以MIME类型开头,后跟一个以逗号分隔的所支持的属性集。一个支持16位整型原始音频,环绕或立体声的音频过滤器看起来会是这样:
static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
"sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
"audio/x-raw-int, "
"width = (int) 16, "
"depth = (int) 16, "
"endianness = (int) BYTE_ORDER, "
"channels = (int) { 1, 2 }, "
"rate = (int) [ 8000, 96000 ]"
)
);
花括号里的值是一个列表,方括号里的值是一个范围。同时支持多个类型集,它们之间应该用分号隔开。以后在讲pad的一章里,我们将看到如何用类型来得到流的准确格式。
一旦我们写完所有定义插件的代码,接下来就需要完成plugin_init[h2] ()函数。这是一个特殊的函数,它在插件被加载时就立即被调用,而且它应该根据自己是否被加载并初始化,以及所有的依赖关系是否正确而返回TURE或FALSE。而且在这个函数中必须注册插件支持的所有element类型。
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()的返回信息将被缓存在一个中心注册表中。因此,务必保证该函数每次的返回信息是一致的:例如,不要根据运行时条件来决定element的工厂方法是否可用。如果一个element只能在特定条件下工作(例如,如果声卡没有被另一个进程使用),这必须处理为element不能进入READY状态,而不是试图不让element出现。
正如前文所说,pad是element间传输数据的通道,因此在创建element时它们显得非常重要。我们在样板文件代码中看到了静态pad模板如何在元件类中注册pad模板。在此,我们将看到怎样创建真实的element,用一个_setcaps ()
函数配置一个特殊的类型以及怎样注册函数以便让数据流经element。
你在_base_init ()
函数中将pad模板注册到element中,然后在_init ()
函数中从pad模板中创建pad。创建完pad后,你必须设置_setcaps ()
和 optionally a _getcaps ()
函数指针,其中后者是可选的。另外,你还必须设置_chain ()
函数指针。 或者,pad也可以在一个循环模式中操作,也就是说它们能自己主动拉(pull)数据。这方面的话题以后会讲述的更多。此后,你必须将pad注册进element,就像这样:
static gboolean gst_my_filter_setcaps (GstPad *pad,
GstCaps *caps);
static GstFlowReturn gst_my_filter_chain (GstPad *pad,
GstBuffer *buf);
static void
gst_my_filter_init (GstMyFilter *filter, GstMyFilterClass *filter_klass)
{
GstElementClass *klass = GST_ELEMENT_CLASS (filter_klass);
/* pad through which data comes in to the element */
filter->sinkpad[93] = gst_pad_new_from_template (
gst_element_class_get_pad_template (klass, "sink"), "sink");
gst_pad_set_setcaps_function (filter->sinkpad[94] , gst_my_filter_setcaps);
gst_pad_set_chain_function (filter->sinkpad, gst_my_filter_chain);
gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
/* pad through which data goes out of the element */
filter->srcpad[95] = gst_pad_new_from_template (
gst_element_class_get_pad_template (klass, "src"), "src");
gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
/* properties initial value */
filter->silent = FALSE;
}
_setcaps ()
函数在cap协商时被调用,cap协商在cap协商中详细讨论。这是相连接的pad决定流经它们之间的流的类型的一个过程。第12章有完整的类型定义列表。一个_link ()
函数接受一个指向定义了建议的流媒体类型的GstCaps
结构的指针, 并且可以返回"yes" (TRUE
)或"no" (FALSE
)。如果element给出一个积极的响应,那种流类型将在pad上使用。示例如下:
static gboolean
gst_my_filter_setcaps (GstPad *pad,
GstCaps *caps)
{
GstStructure *structure = gst_caps_get_structure (caps, 0);
GstMyFilter *filter = GST_MY_FILTER (GST_OBJECT_PARENT (pad));
const gchar *mime;
/* Since we're an audio filter, we want to handle raw audio
* and from that audio type, we need to get the samplerate and
* number of channels. */
mime = gst_structure_get_name (structure);
if (strcmp (mime, "audio/x-raw-int") != 0) {
GST_WARNING ("Wrong mimetype %s provided, we only support %s",
mime, "audio/x-raw-int");
return FALSE;
}
/* we're a filter and don't touch the properties of the data.
* That means we can set the given caps unmodified on the next
* element, and use that negotiation return value as ours. */
if (!gst_pad_set_caps (filter->srcpad, caps))
return FALSE;
/* Capsnego succeeded, get the stream properties for internal
* usage and return success. */
gst_structure_get_int (structure, "rate", &filter->samplerate);
gst_structure_get_int (structure, "channels", &filter->channels);
g_print ("Caps negotiation succeeded with %d Hz @ %d channels\n",
filter->samplerate, filter->channels);
return TRUE;
}
在此,我们检查所给cap的mime类型。通常,你不需要在你自己的插件或element中这样做,因为核心库已经替你做了。我们只是简单的来示范如何得到一堆cap的mime类型。在内部,类型存储在GstStructure
结构中。一个GstCaps
仅仅是0个或多个结构或类型的封装而已。你还可以从这个结构中重新获取流的属性,就像上面代码中gst_structure_get_int ()
函数的功能。
如果你的_link ()
函数不需要做任何特殊的操作(例如,它只是转发cap),你可以将其设为gst_pad_proxy_link ()
。这是核心库提供的一个连接转发函数。它在诸如identity
的element中是很有用的。
所有的数据处理在Chain函数中进行。在简单滤镜中,_chain ()
函数多为线性函数 ── 因此每有一个输入缓冲区,便会有一个输出缓冲区。下面是chain函数的一个非常简单的实现:
static GstFlowReturn
gst_my_filter_chain (GstPad *pad,
GstBuffer *buf)
{
GstMyFilter *filter = GST_MY_FILTER (GST_OBJECT_PARENT (pad));
if (!filter->silent)
g_print ("Have data of size %u bytes!\n", GST_BUFFER_SIZE (buf));
return gst_pad_push (filter->srcpad, buf);
}
显然,上面的函数并没有做任何有用的事。你通常会在这儿处理数据而不是将这些数据打印出来。记住,缓冲区并不总是可写的。在更加高级的element中(那些进行事件处理的),你也许会要指定一个事件处理函数,当流事件发出时(例如流结束,连接中断,tags,等等)该函数会被调用。
static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
gst_pad_set_event_function (filter->sinkpad,
gst_my_filter_event);
[..]
}
static gboolean
gst_my_filter_event (GstPad *pad,
GstEvent *event)
{
GstMyFilter *filter = GST_MY_FILTER (GST_OBJECT_PARENT (pad));
switch (GST_EVENT_TYPE (event)) {
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, event);
}
static GstFlowReturn
gst_my_filter_chain (GstPad *pad,
GstBuffer *buf)
{
GstMyFilter *filter = GST_MY_FILTER (gst_pad_get_parent (pad));
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);
}
在某些情况下,也许element对数据输入的速率有控制权也是有好处的。在那种情况下,你也许要写一个所谓的基于循环(loop-based)的元件。Source elements(只含有source pads的elements)也可以是get-based elements。这些概念将在本指南的高阶部分以及专门讨论source pad的章节讲述。
一个状态用来描述元件实例是否被初始化了,是否已经准备好要传输数据了,以及当前是否在处理数据。GStreamer中一共定义了四个状态:
· GST_STATE_NULL
· GST_STATE_READY
· GST_STATE_PAUSED
· GST_STATE_PLAYING
从现在开始,我们分别简单的用"NULL", "READY", "PAUSED"和"PLAYING"来指代这四个状态。
GST_STATE_NULL
是element的缺省状态。这种状态下,没有分配任何运行时资源,也没有加载任何运行时库,显然此时不能处理数据。
GST_STATE_READY
是下element的下一个状态。在READY状态下,一个element拥有所有的缺省资源(运行时库,运行时内存)。然而,所有流相关的东西还没有被分配或定义。当从NULL状态过渡到READY状态时(GST_STATE_CHANGE_NULL_TO_READY
),一个element应该分配所有的非流相关的资源以及加载所有运行时库(如果有的话)。反过来,(从READY到NULL状态GST_STATE_CHANGE_READY_TO_NULL
),一个element应该卸载这些库并释放所有分配的资源。硬件设备就是这种资源的一个例子。注意,文件通常是流,应当被视为流相关的资源,因此不应该在该状态下分配。
GST_STATE_PAUSED
下element已准备好接受并处理数据。对多数element来说,这个状态和PLAYING状态是一样的。唯一的例外是sink elements。sink elements只接受一个buffer然后阻塞。在这种情况下管道处于'prerolled'并且准备好可以立即将数据画出。
GST_STATE_PLAYING
是element的最高状态。对多数element来说,该状态和PAUSED状态是完全一样的,它们接受并处理事件和缓冲区数据。只有sink elements需要区分PAUSED和PALAYING状态。在PLAYING状态下,sink elemnts才真正将到达的数据输出(render),例如,将音频输出到声卡或将视频画面输出到image sink上。
如果可能,你的element应该从一个新的基类(Pre-made base classes)中继承。sources,sinks和滤镜/转换element都有已写好的通用基类。除此以外,存在一些音频,视频和其它element的专门的基类。
如果你使用一个上述有的基类,你基本不需要自己去处理状态的改变。你所要做的所有工作就是重载基类的start()和stop()虚函数(也许会根据基类做不同的调用),基类将会为你做好所有的工作。
然而,如果你不继承已有的基类,但是继承GstElement或其它不是基于基类的类,你很可能要自己实现状态变迁函数以便在状态变化时得到通知。如果你的插件是解码器或编码器,这些是无可避免的,因为当前还没有解码器或编码器的基类。
一个element可以通过一个虚函数指针来得到状态变化的通知。在这个函数中,element可以初始化任何element所需的特定的数据,而且在从一个状态到另一个状态时可以出现转换失败。
不要在未处理的状态变化中使用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)的状态变迁在两个分开的块中处理,而且反向的状态变化只在我们已经连接到(chained up to)父类的状态变迁函数时才被处理。为了保证多线程同时访问的安全性这样做是必须的。
这样做的原因[96] 是,例如反向状态变迁时当你的插件的chain函数仍然在其它线程中访问那些资源时你不必销毁所有已分配的资源。你的chain函数是否要运行取决于你的插件的pad的状态,而那些pad的状态是何element的状态紧密相连的。pad的状态是在GstElement函数的状态变迁函数中处理的,包括恰当的锁操作,这就是为什么在销毁分配的资源之前联锁(chain up)很重要的原因。
最主要也是最重要的控制element行为的方法是通过设置GObject的属性。GObject的属性在_class_init ()
函数中定义。Element可以实现一个_get_property ()
和一个 _set_property ()
函数。如果一个应用程序改变或请求一个属性值,这些函数将会被调用,在这些函数中可以改变这些值或根据那些属性的要求在内部改变这些值。
/* properties */
enum {
ARG_0,
ARG_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 properties */
g_object_class_install_property (object_class, ARG_SILENT,
g_param_spec_boolean ("silent", "Silent",
"Whether to be very verbose or not",
FALSE, G_PARAM_READWRITE));
/* define virtual function pointers */
object_class->set_property = gst_my_filter_set_property;
object_class->get_property = gst_my_filter_get_property;
}
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 ARG_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 ARG_SILENT:
g_value_set_boolean (value, filter->silent);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
上面是一个演示如何使用参数的简单例子。图形程序,例如GStreamer编辑器,将使用这些属性并显示一个控件,用户可以通过该控件来改变这个属性。因为这些属性足够的用户友好,你应该精确的定义属性。不仅仅在于定义一个属性的有效范围,还在于你要选用具有描述性的字符串来定义这个属性,如果可能,尽量使用枚举或标志来代替整形数。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", "SMPTE 100% color bars" },
{ GST_VIDEOTESTSRC_SNOW, "snow", "Random (television snow)" },
{ GST_VIDEOTESTSRC_BLACK, "black", "0% 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), ARG_TYPE,
g_param_spec_enum ("pattern", "Pattern",
"Type of test pattern to generate",
GST_TYPE_VIDEOTESTSRC_PATTERN, 1, G_PARAM_READWRITE));
[..]
}
GObject信号可以用来通知程序该对象的特定事件的到来。然而要注意,应用程序必须要知道信号及其意义,因此如果你在寻找一个应用程序和element间的通用的交互方法,信号也许不是你所要的。然而,在许多情况下信号是很有用的。信号的内部详情请参考GObject文档。
你经常会想在你对新写的插件做了最少的设置的时候来测试它。通常,用gst-launch进行测试是一个很好的开始。但是,你经常需要测试更多的特性,如seeking,事件,交互等等,但这些gst-lanch都没有提供。达成这一目标的最简单方法是自己写一个小的测试程序。本章用很短的篇幅来解释这一切是如何进行的。完整的应用程序开发指南请参考Application Development Manual。
一开始,你需要调用gst_init ()
函数初始化GStreamer核心库。另一种可选途径是调用 gst_init_with_popt_tables ()
函数,该函数返回一个指向popt表的指针。接着你可以用libpopt来处理参数表,这会完成GStreamer的初始化。
你可以用gst_element_factory_make ()
来创建element,这个函数的第一个参数是你要创建的element的类型,第二个参数是一个随意的名称。最后的例子使用了一个简单的文件源―解码器―声卡输出的管道,但是如果必需的话你可以使用特殊的调试element。例如,一个identity
element可以用在管道中间来充当数据到应用程序的传达者。这种方法可以检查出你的测试程序中的错误数据。另外你可以将fakesink
element放到管道的最后将你的数据输出到标准输出(为了实现这个目的,请将dump
属性设置为TRUE)。最后,你可以使用efence
element (就是一个Eletric Fence内存调试器封装而成的element)来检查你的内存错误。
在连接时,你的测试程序可以使用固定的或过滤的cap来驱动一个特殊数据类型进出你的element。这是检查你的element的多种输入输出类型的一个非常简单而高效方法。
管道是通过gst_bin_iterate ()
函数来运行的。值得注意的是,在运行时你至少要连接管道或元件的 "error"和"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;
GError *err;
gst_message_parse_error (msg, &err, &debug);
g_free (debug);
g_print ("Error: %s\n", err->message);
g_error_free (err);
g_main_loop_quit (loop);
break;
}
default:
break;
}
return TRUE;
}
gint
main (gint argc,
gchar *argv[])
{
GstElement *pipeline, *filesrc, *decoder, *filter, *sink;
GMainLoop *loop;
/* 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");
gst_bus_add_watch (gst_pipeline_get_bus (GST_PIPELINE (pipeline)),
bus_call, loop);
filesrc = gst_element_factory_make ("filesrc", "my_filesource");
decoder = gst_element_factory_make ("mad", "my_decoder");
filter = gst_element_factory_make ("my_filter", "my_filter");
sink = gst_element_factory_make ("osssink", "audiosink");
if (!sink || !decoder) {
g_print ("Decoder or output could not be found - check your install\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-0.9/ and that "
"you've ran gst-register-0.9 to register it. Check availability "
"of the plugin afterwards using \"gst-inspect-0.9 my_filter\"");
return -1;
}
g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL);
/* link everything together */
gst_element_link_many (filesrc, decoder, filter, sink, NULL);
gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, filter, sink, NULL);
/* run */
gst_element_set_state (pipeline, GST_STATE_PLAYING);
g_main_loop_run (loop);
/* clean up */
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (GST_OBJECT (pipeline));
return 0;
}
III. 高阶滤镜概念
至此,你应该已经可以创建具有接收和发送数据能力的基本滤镜 element。这是一个代表GStreamer的简单模型。但是GStreamer可以做其它更多的事!在这一章中,将讨论各种高阶话题,如调度,特殊pad类型,时钟,事件,接口,tagging等等。这些主题使得我们可以更容易的利用GStreamer来写应用程序。
目 录
10. Caps协商(negotiation)
10.1. Caps协商实例
10.2. 固定caps
10.3. 下游caps协商
10.3.1. Negotiating caps embedded in input caps
10.3.2. Parsing and setting caps
10.4. 上游caps(再)协商
10.5. 实现一个getcaps函数
11. 不同的调度模式
11.1. 衬垫(Pad)激活时机
11.2. 衬垫(Pads)驱动管道(pipeline)
11.3. 提供随机访问
12. 类型和属性
12.1. 创建一个简单的格式来测试
12.2. Typefind函数和Autoplugging
12.3. 已定义类型列表
13. 请求(Request)和间或(Sometimes)衬垫(pads)
13.1. 间或衬垫
13.2. 请求衬垫
14. 时钟机制(Clocking)
14.1. 时间格式
14.2. 时钟(Clocks)
14.3. 在元件(elements)和时间里的数据流
14.4. 每个元件的义务
14.4.1. 源元件
14.4.2. Sink元件
15. 支持动态参数
15.1. 开始
15.2. 数据处理循环
15.2.1. The Data Processing Loop for Video Elements
15.2.2. The Data Processing Loop for Audio Elements
16. MIDI
17. 接口
17.1. 怎样实现接口
17.2. URI接口
17.3. Mixer接口
17.4. Tuner接口
17.5. 颜色平衡(Color Balance)接口
17.6. 属性探测(Property Probe)接口
17.7. X Overlay接口
17.8. 导航(Navigation)接口
18. 标签(元数据和流信息)
18.1. 从流中读取标签
18.2. 将标签写入流
19. 事件:定位,导航及更多
19.1. 下游事件
19.2. 上游事件
19.3. 事件汇总
19.3.1. End of Stream (EOS)
19.3.2. Flush Start
19.3.3. Flush Stop
19.3.4. New Segment
19.3.5. Seek Request
19.3.6. Navigation
19.3.7. Tag (metadata)
Caps协商是element为在它们的pads上流化一个特定的多媒体格式而配置自己的过程。因为不同类型的element对它们能够协商的媒体格式有不同的要求,因此这个协商过程必须通用且正确实现所有的实例。
在这一章里,我们将从一个管道的角度来讨论下游协商(downstream negotiation)和上游协商(upstream negotiation), 包括管道中各种element的职责。另外我们将介绍固定caps的概念。
我们以一个文件源连接到一个demuxer,然后连接到一个具有caps滤镜的转换器,最后连接到一个音频输出的管道为例。当开始有数据流时,demuxer将解析文件头(例如ogg头),并且发出通告,例如说,ogg文件中有一个Vorbis流。注意,它将为Vorbis基本流创建一个输出pad并为它设置Vorbis-caps。最后,它会加载这个pad。至此,这个pad就准备好可以流化数据了,因此ogg demuxer到此也完成了。这个pad不能再协商,因为数据流的类型已经嵌入到数据中了。
Vorbis解码器将对它的sinkpad中流入的Vorbis头和Vorbis数据进行解码。现在,一些解码器可以有能力输出多种格式,例如同时输出16位整形和浮点型。而另一些解码器可能只能解码出一种特定的格式,例如只有(32位)浮点音频。两种情况都决定了在一个解码器element中应该如何实现caps协商。一种情况下,可以使用固定caps,前面已经做到了。然而另一种情况下,你必须在element中实现再协商的功能,这将允许在将来改变数据的格式。我们将在本章的其它小节更深入的讨论这个。
例如,滤镜可以被应用程序用来对一个管道上的通道作强制配置(5.1/环绕或2.0/立体声),以便用户可以享受来自所有的扬声器的声音。在这个例子中,音频sink是一个标准的ALSA输出element(alsasink)。转换器element支持所有格式的输入到所有格式的输出,滤镜将确保只有特定的想要的通道配置流通过这个连接(由用户的通道配置设置决定)。在管道运行时改变这个设置,一些element必须在管道运行时重协商。这是通过上游caps协商来完成的。这些也将在下面的章节中详细讨论。
为了caps协商在非固定连接上能正确工作,pads可以选择性的实现一个函数来告诉同伴elements所支持或喜欢的格式。当上游再协商被触发时,这变得很重要。
只有当数据真正的流经它们的pad时下游element才会被告知。这是因为在数据流中caps是被绑定到缓冲区的。因此当vorbis解码器在它的source pad上设置一个caps(用来配置输出格式)时,转换器还没有得到通知。只有当解码器将一个缓冲区从它的source pad传递给转换器时,转换器才会得到通知。就在在调用chain-函数之前 ,GStreamer将检查以前所协商的格式是否仍能运用在这个缓冲区上。如果不行,它将调用转换器的setcaps-函数来配置以适应新的类型。此后才会调用转换器的chain函数。
进行caps协商的最简单方法是在衬垫上设置一个固定caps。一旦设定了固定caps,衬垫就不能在外界再进行协商。对衬垫进行再配置的唯一方法就是让衬垫的所有元件为衬垫设置一个新的固定caps。固定caps是衬垫的一个可设置的属性,在创建衬垫的时候进行:
[..]
pad = gst_pad_new_from_template (..);
gst_pad_use_fixed_caps (pad);
[..]
随后调用 gst_pad_set_caps ()
函数来设置衬垫的固定caps。
[..]
caps = gst_caps_new_simple ("audio/x-raw-float",
"width", G_TYPE_INT, 32,
"endianness", G_TYPE_INT, G_BYTE_ORDER,
"buffer-frames", G_TYPE_INT, ,
"rate", G_TYPE_INT, ,
"channels", G_TYPE_INT, , NULL);
if (!gst_pad_set_caps (pad, caps)) {
GST_ELEMENT_ERROR (element, CORE, NEGOTIATION, (NULL),
("Some debug information here"));
return GST_FLOW_ERROR;
}
[..]
通常,可以(在它们的源衬垫上)设置固定caps的元件都是不可再协商的元件。这类元件的例子有:
· 一个typefinder,因为类型发现是实际数据流的一部分因此可以不用再协商。
· 几乎所有的demuxers,因为包含的基本数据流都在头文件中定义,因此也不可再协商。
· 一些解码器,格式嵌入在数据流中而不是peercaps的一部分,并且解码器本身也不可重配置。
所有其它的需要对格式进行再配置的元件应该实现完全的caps协商 ,这将在下面的几个小节中解释。
当一个源衬垫上需要设置一个格式来配置输出格式时就要进行下游协商,但是这个元件运行再协商是因为它的格式是在sink衬垫的caps上配置的,或者是因为它支持多格式输出。实际的再协商要求有些细微的差别。
许多元件,特别是特效和转换器,可以解析它们的输入caps的流格式,并且当即就可决定输出格式。当再协商发生时,一些元件可能仅仅只需要向后转发再协商(详情见后面)。对于那些元件来说,所有(下游)caps协商可以在_setcaps ()
的函数中进行。这个函数在一个缓冲区被push到一个衬垫上时被调用,但是这个缓冲区上的格式和前面协商的格式是不一致的(或者,同样的,至此还没有协商格式)。
在_setcaps ()
-函数中,元件可以转发caps到下游的一个元件,并且如果那个衬垫也接受这个格式,元件就可以从caps中解析相关参数并在内部对自己进行配置。传到这个函数的caps总是模板caps的一个子集,因此不必进行广泛的安全检查。下面的例子应该可以清晰的指示出这种函数该如何实现:
static gboolean
gst_my_filter_setcaps (GstPad *pad,
GstCaps *caps)
{
GstMyFilter *filter = GST_MY_FILTER (GST_OBJECT_PARENT (pad));
GstStructure *s;
/* forward-negotiate */
if (!gst_pad_set_caps (filter->srcpad, caps))
return FALSE;
/* negotiation succeeded, so now configure ourselves */
s = gst_caps_get_structure (caps, 0);
gst_structure_get_int (s, "rate", &filter->samplerate);
gst_structure_get_int (s, "channels", &filter->channels);
return TRUE;
}
实际中也可能会出现滤镜可以改变流格式的情况。在那种情况下,它将协商一个新的格式。显然,元件应该首先试图配置成“全通(pass-through)”,意思是它不会改变流的格式。然而,如果这种配置失败了,元件应该在它的源衬垫上调用gst_pad_get_allowed_caps ()
函数来得到一个支持的输出格式列表,并选取第一个。那个函数的返回值保证是模板caps的一个子集。
让我们看一个例子,这个元件可以转换流的采样率,这样输入输出采样率就不必一致了:
static gboolean
gst_my_filter_setcaps (GstPad *pad,
GstCaps *caps)
{
GstMyFilter *filter = GST_MY_FILTER (GST_OBJECT_PARENT (pad));
if (gst_pad_set_caps (filter->sinkpad, caps)) {
filter->passthrough = TRUE;
} else {
GstCaps *othercaps, *newcaps;
GstStructure *s = gst_caps_get_structure (caps, 0), *others;
/* no passthrough, setup internal conversion */
gst_structure_get_int (s, "channels", &filter->channels);
othercaps = gst_pad_get_allowed_caps (filter->srcpad);
others = gst_caps_get_structure (othercaps, 0);
gst_structure_set (others,
"channels", G_TYPE_INT, filter->channels, NULL);
/* now, the samplerate value can optionally have multiple values, so
* we "fixate" it, which means that one fixed value is chosen */
newcaps = gst_caps_copy_nth (othercaps, 0);
gst_caps_unref (othercaps);
gst_pad_fixate_caps (filter->srcpad, newcaps);
if (!gst_pad_set_caps (filter->srcpad, newcaps))
return FALSE;
/* we are now set up, configure internally */
filter->passthrough = FALSE;
gst_structure_get_int (s, "rate", &filter->from_samplerate);
others = gst_caps_get_structure (newcaps, 0);
gst_structure_get_int (others, "rate", &filter->to_samplerate);
}
return TRUE;
}
static GstFlowReturn
gst_my_filter_chain (GstPad *pad,
GstBuffer *buf)
{
GstMyFilter *filter = GST_MY_FILTER (GST_OBJECT_PARENT (pad));
GstBuffer *out;
/* push on if in passthrough mode */
if (filter->passthrough)
return gst_pad_push (filter->srcpad, buf);
/* convert, push */
out = gst_my_filter_convert (filter, buf);
gst_buffer_unref (buf);
return gst_pad_push (filter->srcpad, out);
}
其它元件,譬如某些解码器,不能从它们的输入解析caps,仅仅因为输入格式还不包含需要知道输出格式的信息;更确切的说,数据头也需要被解析。许多情况下,固定caps就足够了,但在另一些情况下,特别是当解码器是可重新协商时,使用完全的caps协商也是可能的。
幸运的是,需要这样做的代码和Negotiating caps embedded in input caps中的最后一个示例代码非常类似,区别是caps是在_chain ()
-函数不是_setcaps ()
-函数中选择的。剩下的,至于从源衬垫获取所有允许的caps,固定化之类,是完全一样的。但将在下一小节讨论的再协商对这些元件来说是很不同的。
上游协商主要用于再协商一个已经(部分)协商好的管道去支持新的格式。一些实际的例子包括视频窗口大小改变后选择一个不同的视频尺寸,并且视频输出不具有改变大小的能力,或者因为音频频道配置改变了。
上游caps再协商是在gst_pad_alloc_buffer ()
-函数中进行的。这里的思想是一个元件向下游请求一个缓冲区的时候必须指定那个缓冲区的类型。如果再协商发生了,这个类型将不再被使用,并且下游元件将在所提供的缓冲区上设置一个新的caps。然后元件应该重配置自己来push缓冲区。一旦缓冲区被push了,源衬垫的setcaps函数将立即被调用。
在此意识到不同的元件具有不同的职责是很重要的:
· 元件应该实现一个"padalloc"-函数以便再协商的时候可以改变格式。这对于滤镜和转换器也是一样的。
· 元件应该使用 gst_pad_alloc_buffer
()
函数分配新的缓冲区。
· 可再协商的元件也应该在它们的源衬垫上实现一个 "setcaps"-函数。
不幸的是,这边所讨论的所有细节还没有完全解决,所以这份文档还未完成。FIXME。
当一个对等元件(peer element)想要知道这个元件所支持的类型及首选的顺序时_getcaps ()
-就会被调用。返回值应该是这个元件所支持的所有格式,考虑对对等元件进一步的上游和下游事件的限制, 这些返回值按优先级排序,优先级最高的最先返回。
static GstCaps *
gst_my_filter_getcaps (GstPad *pad)
{
GstMyFilter *filter = GST_MY_FILTER (GST_OBJECT_PARENT (pad));
GstPad *otherpad = (pad == filter->srcpad) ? filter->sinkpad :
filter->srcpad;
GstCaps *othercaps = gst_pad_get_allowed_caps (otherpad), *caps;
gint i;
/* We support *any* samplerate, indifferent from the samplerate
* supported by the linked elements on both sides. */
for (i = 0; i < gst_caps_get_size (othercaps); i++) {
GstStructure *structure = gst_caps_get_structure (othercaps, i);
gst_structure_remove_field (structure, "rate");
}
caps = gst_caps_intersect (othercaps, gst_pad_get_pad_template_caps (pad));
gst_caps_unref (othercaps);
return caps;
}
使用你阅读本章所掌握的所有知识,你应该可以编写一个可以正确进行caps协商的元件了。如果有疑问,参考一下我们CVS仓库中的同类型的其它元件看它们是怎样做到你所要做的。
简单说来,调度就是一种确保在处理数据并为下一个元件准备数据的过程中,每个元件都会被调用一次的方法。类似的,内核有一个进程调度器,在某种程度上你的大脑也是一个复杂的调度器。然而,随机地调用元件的链函数并不能满足所有的需求,因此你将了解GStreamer中的调度器会比这个要复杂一点。然而,作为开始,这是一幅很好的图画。
迄今为止,我们只讨论了_chain ()
-操作元件,即那些在sink衬垫上设置了一个链函数并将缓冲区退到源衬垫的元件。然而,衬垫(或元件)也可以在另两种调度模式下工作。本章中,我们将讨论那些调度模式是什么,他们怎么被激活以及在什么情况下有用。另外两种调度模式是随机访问(基于_getrange ()
)和任务驱动(意即该元件是是管道的驱动力)模式。
GStreamer决定各种元件工作在何种模式下的阶段称为衬垫激活阶段。在这一阶段,GStreamer将查询调度能力(即查看每个特定的元件/衬垫能够在何种模式下工作)及决定管道结构的最佳调度模式。接着,每个衬垫会被通知所指定的调度模式,随后管道将开始运行。
衬垫可以被指定三种模式,每种模式都依赖一些先决条件。衬垫需要为每种调度模式实现一个通知函数 (gst_pad_set_activatepull_function ()
和 gst_pad_set_activatepush_function ()
) 。同时,pull-base模式的sinkpads应该在该通知函数中开始和终止它们的任务。
· 如果一个元件的所有衬垫都被分派做基于 "push"的调度,这意味着数据会通过sinkpads _chain ()
函数从上流的元件推过来。该调度模式的先决条件是每个sinkpad都通过 gst_pad_set_chain_function ()
被设置了链函数,并且所有的下游元件都处于同一个模式。衬垫被分配为按照sink到source的顺序完成基于push的调度模式,而在同一个元件里,则是按照先sourcepad然后sinkpad的顺序。只有接收元件的sinkpad被激活为push-based调度模式,该接收元件才能处于这种模式。源元件不能是基于链的。
· 或者,另外一种情况,当元件的sourcepads仍然运行在基于push模式下的时候,sinkpads能在基于 "pull"的管道中被驱动。为了能够成功驱动,这些衬垫在它们被激活时会运行 GstTask
。该任务会起一个线程,该线程供元件调用特定的函数。一旦该函数被调用,它会上随机访问所有的sinkpads(通过 gst_pad_get_range ()
) ,并把数据push给sourcepads,这个过程简明地说明了元件在管道中处理数据流的场景。这个模式的先决条件是所有的下游元件可以运行在链模式下,所有的上游元件允许随机访问(见下)。如果源元件的sourcepads被激活为push-based方式,源元件可以运行在该模式之下。接收元件的sinkpads被激活为pull-mode方式,接收元件可以运行在该模式之下。
· 最后,元件中的所有衬垫都被分派为pull模式。但与上面相反的是,这并不意味着元件会在自己的线程中开始任务。更准确的说,这意味着它们是下游元件的pull“奴隶”,并通过 _get_range ()
函数提供随机数据访问。该情形的先决条件是 _get_range ()
函数通过gst_pad_set_getrange_function ()
在该衬垫上被设置。同样,如果该元件有任何的sinkpads,所有的衬垫(也是处于同等地位的) 也需要运行在随机访问模式下。注意,这些元件应该自己处理激活的事情,GStreamer不会做这些事情。
在接下来的两部分,我们将深入pull-based调度(元件/衬垫驱动管道,元件/衬垫提供随机访问),并且一些特定的场景将会给出。
一个元件的Sinkpads在pull-based模式中运行,同时该元件的sourcepads运行在pull-based模式(或者该元件没有sourcepads), 这样Sinkpads就能运行一个任务来驱动管道数据流。在该任务中,元件能对其所有的Sinkpads提供随机访问,把数据推给它们的sourcepads。这在下面几种不同的元件中都能起到重要作用:
· Demuxers, parsers以及能处理特定的未被解析的数据的decoders (像MPEG-audio或视频流),因为这些元件比较关注字节级(随机的)的输入。如果有可能,不论怎样,这些元件同时都应该准备运行在链(chain)模式之下。
· 特定的需要控制输入流的音频输入,如Jack sound 服务器。
为了启动任务,你需要在激活函数中创建它。
#include "filter.h"
#include
static gboolean gst_my_filter_activate (GstPad * pad);
static gboolean gst_my_filter_activate_pull (GstPad * pad,
gboolean active);
static void gst_my_filter_loop (GstMyFilter * filter);
GST_BOILERPLATE (GstMyFilter, gst_my_filter, GstElement, GST_TYPE_ELEMENT);
static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
gst_pad_set_activate_function (filter->sinkpad, gst_my_filter_activate);
gst_pad_set_activatepull_function (filter->sinkpad,
gst_my_filter_activate_pull);
[..]
}
[..]
static gboolean
gst_my_filter_activate (GstPad * pad)
{
if (gst_pad_check_pull_range (pad)) {
return gst_pad_activate_pull (pad, TRUE);
} else {
return FALSE;
}
}
static gboolean
gst_my_filter_activate_pull (GstPad *pad,
gboolean active)
{
GstMyFilter *filter = GST_MY_FILTER (GST_OBJECT_PARENT (pad));
if (active) {
filter->offset = 0;
return gst_pad_start_task (pad,
(GstTaskFunction) gst_my_filter_loop, filter);
} else {
return gst_pad_stop_task (pad);
}
}
一旦启动了任务,你的任务能完全控制输入输出。最简单的情形是任务函数从输入读取数据,再将数据推给它的源衬垫。任务并不是只能做这样的事情,它还提供了比之老的基于链式模式更多的弹性。
#define BLOCKSIZE 2048
static void
gst_my_filter_loop (GstMyFilter * filter)
{
GstFlowReturn ret;
guint64 len;
GstFormat fmt = GST_FORMAT_BYTES;
GstBuffer *buf = NULL;
if (!gst_pad_query_duration (filter->sinkpad, &fmt, &len)) {
GST_DEBUG_OBJECT (filter, "failed to query duration, pausing");
goto stop;
}
if (filter->offset >= len) {
GST_DEBUG_OBJECT (filter, "at end of input, sending EOS, pausing");
gst_pad_push_event (filter->srcpad, gst_event_new_eos ());
goto stop;
}
/* now, read BLOCKSIZE bytes from byte offset filter->offset */
ret = gst_pad_pull_range (filter->sinkpad, filter->offset,
BLOCKSIZE, &buf);
if (ret != GST_FLOW_OK) {
GST_DEBUG_OBJECT (filter, "pull_range failed: %s", gst_flow_get_name (ret));
goto stop;
}
/* now push buffer downstream */
ret = gst_pad_push (filter->srcpad, buf);
buf = NULL; /* gst_pad_push() took ownership of buffer */
if (ret != GST_FLOW_OK) {
GST_DEBUG_OBJECT (filter, "pad_push failed: %s", gst_flow_get_name (ret));
goto stop;
}
/* everything is fine, increase offset and wait for us to be called again */
filter->offset += BLOCKSIZE;
return;
stop:
GST_DEBUG_OBJECT (filter, "pausing task");
gst_pad_pause_task (filter->sinkpad);
}
在前面的部分,我们讨论了元件(或衬垫)如何驱动管道,使用它们自己的任务,对它们的sinkpads进行随机访问。这些意味着所有连接到这些衬垫(递归地)元件需要提供随机访问方法。请求随机访问通过函数 gst_pad_pull_range ()
,它请求一个特定大小和偏移的缓存。实现了随机访问的源衬垫将有 _get_range ()
方法,通过 gst_pad_set_getrange_function ()
来设置,这个函数会在一对衬垫请求数据时被调用。该元件随后将会寻找正确的偏移,并提供被请求的数据。一些元件可以实现随机访问:
· 数据源,比如文件源,能够以任意合理的偏移量提供数据。
· 能在整个管道中提供类pull-base调度模式的过滤器。注意,分派了随机访问调度的元件需要自己对它们的上游同伴的调度模式负责! GStreamer 不会为你做那些事情。
· 那些能通过跳过一小部分输入而达到随机访问目的解析器。本质上来讲,逐个地“向前”随机访问请求不需要包含自己的处理过程。例如标签读取器(像ID3)或单个输出解析器,例如WAVE解析器。
下面的例子展示了_get_range ()
函数如何在源元件中被实现的:
#include "filter.h"
static GstFlowReturn
gst_my_filter_get_range (GstPad * pad,
guint64 offset,
guint length,
GstBuffer ** buf);
GST_BOILERPLATE (GstMyFilter, gst_my_filter, GstElement, GST_TYPE_ELEMENT);
static void
gst_my_filter_init (GstMyFilter * filter)
{
GstElementClass *klass = GST_ELEMENT_GET_CLASS (filter);
filter->srcpad = gst_pad_new_from_template (
gst_element_class_get_pad_template (klass, "src"), "src");
gst_pad_set_getrange_function (filter->srcpad,
gst_my_filter_get_range);
gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
[..]
}
static gboolean
gst_my_filter_get_range (GstPad * pad,
guint64 offset,
guint length,
GstBuffer ** buf)
{
GstMyFilter *filter = GST_MY_FILTER (GST_OBJECT_PARENT (pad));
[.. here, you would fill *buf ..]
return GST_FLOW_OK;
}
实践中,很多的元件理论上可以实现随机访问,具体执行起来则或多或少取决于push-base调度。因为没有一个下游元件能开始它自己的任务。因此,实践中,这些元件应该实现 _get_range ()
函数和 _chain ()
函数 (针对过滤器和解析器) 或者 _get_range ()
函数及准备通过 _activate_* ()
函数开始它自己的任务 (针对源元件), 所以 GStreamer可以选择一个最优的模式,并让它实际运做起来。
元件在传送数据时有一系列的可能的数据类型集合。事实上,每一个定义的新元件可能会使用一种新的数据结构(虽然:除非至少有一个别的元件能识别这种结构,否则该结构可能一无是处,因为没有元件能和它连接上)。
为了让类型切实可用,以及使得像自动加载器(autoplugger)能工作起来,有必要使得所有元件认同一种类型定义,以及每个类型需要一些属性。 GStreamer 框架本身仅提供了识别类型和参数的能力,并没有固定住类型和参数的意义,也没有给新类型强加一个标准。这些都是基于策略的考虑,而不是技术系统的强制要求。
到目前为止,策略还并不复杂:
· 不要创建一个已经存在的类型。
· 如果要创建一个新类型,先与 GStreamer 开发人员讨论,可以通过这些方式:IRC, 邮件列表。
· 确保新格式的名字不会和业已存在的格式名字冲突,并且不要取一个太笼统的名字。例如:"audio/compressed"就是一个指示使用mp3编码压缩的音频数据的笼统名字。使用"audio/mp3" 也许是个更合适的名字,或者是使用"audio/compressed"作为名字,但存在一些属性来指明压缩类型。
· 保证了上面这些,当你想要创建一个新类型时,你需要明确指出来,并将它加到已知类型的列表中,这样其它开发人员才能在他们写他们自己的元件时能正确的使用你的类型。
你如果需要一种新格式,该格式未在已定义的类型列表定义,对于mime命名,属性等你将要遵守一些通用的准则。一个mimetype理想地由IANA定义;另外的,它应该以type/x-name形式存在,其中type是一种mimetype种类(audio, video, ...),name应该是该特定类型的特定称呼。音频和视频mimitype应该试图支持所有通用的 audio/video 属性(见列表),并使用它自己的特有属性。想要知道哪些属性将是能被用到的,参考列表。
花点时间去研究你新建类型的属性集合。匆忙是使不得的。同样,多实验也是个不错的方式。经验告诉我们,理论上深思熟虑的类型是好的,但它仍需要实践来检验它是否达到了它预期的需求。确保你的属性名字不会和其它已存在的类型的属性名字冲突。如果它们重合了,确保它们都指示同样的事情:不同类型拥有同样的属性名字是不允许的。
当仅定义 一个类型时,我们不需要这部分的类容。为了能识别随机数据文件以及回放,我们需要一种方式来识别它们的类型。为了这个目的, "typefinding" 便被引进。类型检测是一个检测数据流类型的过程。类型检测包括两个阶段:第一,这有个未限制数目的函数集,我们叫它类型检测函数,它能在输入流中识别出一种或更多的类型。然后,存在一个小的引擎来注册和调用这些函数。这是类型检测的核心。利用这类型检测核心,你可以写一个自动加载器,它可通过该类型检测系统来为输入流动态地建立一条管道。在此,我们仅关注类型检测函数。
类型检测函数通常位于 gst-plugins/gst/typefind/gsttypefindfunctions.c中,除非有什么其它合理的原因(像库依赖等)才将它放到其它地方。这样集中化的原因是为了减少自动加载插件的数目,这些加载的插件是用来检测媒体的类型。下面的例子说明了如何识别AVI文件,该文件以一个"RIFF" 标签开始,然后是该文件的大小,再是一个 "AVI " 标签:
static void
gst_my_typefind_function (GstTypeFind *tf,
gpointer data)
{
guint8 *data = gst_type_find_peek (tf, 0, 12);
if (data &&
GUINT32_FROM_LE (&((guint32 *) data)[0]) == GST_MAKE_FOURCC ('R','I','F','F') &&
GUINT32_FROM_LE (&((guint32 *) data)[2]) == GST_MAKE_FOURCC ('A','V','I',' ')) {
gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM,
gst_caps_new_simple ("video/x-msvideo", NULL));
}
}
static gboolean
plugin_init (GstPlugin *plugin)
{
static gchar *exts[] = { "avi", NULL };
if (!gst_type_find_register (plugin, "", GST_RANK_PRIMARY,
gst_my_typefind_function, exts,
gst_caps_new_simple ("video/x-msvideo",
NULL), NULL))
return FALSE;
}
注意, gst-plugins/gst/typefind/gsttypefindfunctions.c 使用了一些有意义的宏来减少代码量。当你想要提交一个使用了其它类型检测函数的类型检测代码时,正确认识这些宏所代表的意义。
自动加载在应用程序开发手册中有过详细讨论。
12.3. 已定义类型列表
下面是所有在GStreamer中被定义的类型的列表。为了阅读的方便,它们被分为音频,视频,容器,字幕以及其它类型几个子表。每个子表可能对应一个针对该表的注释。在每个类型的定义中,我们尽力遵守着由 IANA 定义的类型规则。
直接跳转到各个子表:
注意,很多的属性都没有被强制要求,而是可选属性。这意味着大多数的这些属性可以从容器头中抽取出来,但如果容器头没有包含这些属性,它们同样可以通过解析流文件头部或流的内容而得到。推荐策略是,你创建的元件应该提供一个数据,通过解析该数据就知道该元件中数据的类容而不是其它元件中数据的类容。举例来说:AVI头部提供了一个音频流的采样率(samplerate)信息。MPEG系统流则没有该信息。这意味着一个AVI流分流器会给MPEG音频流提供一个采样率属性,但MPEG流分流器则不会。一个需要这个信息的解码器会要求流解析器在流中得到该信息。
Table 12-1. 音频类型表
Mime类型 |
描述 |
属性 |
属性类型 |
属性值 |
属性描述 |
所有音频类型 |
|||||
audio/* |
所有音频类型 |
rate |
integer |
大于0 |
数据的采样率,每秒的样本数(每个声道)。 |
channels |
integer |
大于0 |
音频数据的声道数。 |
||
所有原始音频类型 |
|||||
audio/x-raw-int |
未结构化的并没有压缩的原始定整数音频数据。 |
endianness |
integer |
G_BIG_ENDIAN (1234)或G_LITTLE_ENDIAN (4321) |
样本的字节序。 值G_LITTLE_ENDIAN (4321)意味着 "little-endian" (字节序是 "低位字节优先")。值G_BIG_ENDIAN (1234)意味着 "big-endian" (字节序是"高位字节优先"). |
signed |
boolean |
TRUE或FALSE |
整型样本值是否带符号。 带符号的样本值用一个位来指示符号位(正或负)。不带符号的样本值总为正。 |
||
width |
integer |
大于0 |
每个样本的最大位数。 |
||
depth |
integer |
大于0 |
每个样本所使用的位数。该值小于等于width。如果depth小于width,从低位开始算被使用的位数。举例来说,一个32的width,24的depth,意味着每个样本存储在32位的字节(4个字节)中,但只有低24位被使用了。 |
||
audio/x-raw-float |
未结构化的并没有压缩的原始浮点音频数据。 |
endianness |
integer |
G_BIG_ENDIAN (1234)或G_LITTLE_ENDIAN (4321) |
样本的字节序。 值G_LITTLE_ENDIAN (4321)意味着 "little-endian" (字节序是"低位字节优先")。值G_BIG_ENDIAN (1234)意味着"big-endian" (字节序是"高位字节优先"). |
width |
integer |
大于0 |
每个样本的最大位数。 |
|
|
所有被编码的音频类型 |
|||||
audio/x-ac3 |
AC-3或A52音频流 |
|
|
|
当前没有特定的属性。 |
audio/x-adpcm |
ADPCM音频流 |
layout |
string |
"quicktime", "dvi", "microsoft"或"4xm". |
layout定义了流中样本的包.在ADPCM中,大多数格式存储了每个声道的多样本.这个样本的数目不同于每个格式,因此是不同的layout.大多数时候我们不会使用这个变量,并使用更描述性的东西,这里只对它做个介绍. |
block_align |
integer |
任意整数值 |
一块内存大小 |
||
audio/x-cinepak |
Cinepak (Quicktime) 流中提供的音频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-dv |
数字视频流中提供的音频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-flac |
Free Lossless Audio codec (FLAC). |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-gsm |
GSM编码格式的数据 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-alaw |
A-Law Audio. |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-mulaw |
Mu-Law Audio. |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-mace |
MACE音频(在Quicktime中使用). |
maceversion |
integer |
3或6 |
当前流的MACE音频编码算法版本号. |
audio/mpeg |
使用MPEG音频编码格式压缩的音频数据 |
mpegversion |
integer |
1, 2或4 |
编码数据的MPEG版本号. 1代表MPEG-1, -2 和-2.5 layer 1, 2或3. 2和4代表MPEG-AAC音频编码算法. |
framed |
boolean |
0或1 |
true值表示每个缓存只包含一帧。false 值表示缓存数和帧数并不是1:1。 |
||
layer |
integer |
1, 2,或3 |
用来压缩数据的压缩策略层次。 (only if mpegversion=1). |
||
bitrate |
integer |
大于0 |
位率,每秒的位数。对于VBR (variable bitrate) MPEG 数据,这是个平均位率。 |
||
audio/x-qdm2 |
QDM version 2编码格式的数据 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-pn-realaudio |
Realmedia音频数据 |
raversion |
integer |
1或2 |
对数据进行编码的实时音频编码算法的版本.1代表14k4流, 2代表28k8流. |
audio/x-speex |
Speex编码格式的数据 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-vorbis |
Vorbis音频数据 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-wma |
Windows媒体类型 |
wmaversion |
integer |
1,2或3 |
对数据流进行编码的实时WMA编码算法的版本号. |
audio/x-paris |
Ensoniq PARIS audio |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-svx |
Amiga IFF / SVX8 / SV16 audio |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-nist |
Sphere NIST audio |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-voc |
Sound Blaster VOC audio |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-ircam |
Berkeley/IRCAM/CARL audio |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
audio/x-w64 |
Sonic Foundry's 64 bit RIFF/WAV |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
Table 12-2. 视频类型表
Mime类型 |
描述 |
属性 |
属性类型 |
属性值 |
属性描述 |
所有video类型 |
|||||
video/* |
所有video类型 |
width |
integer |
大于0 |
视频图象的宽度 |
height |
integer |
大于0 |
视频图象的高度 |
||
framerate |
fraction |
大于或等于0 |
每秒的平均帧率.注意该属性不保证在 任意 条件下都能接近该值.如果你需要一个固定的帧率,请使用提供固定帧率的元件.(像 "videodrop"). 0 意味着可变帧率. |
||
所有原始视频数据类型 |
|||||
video/x-raw-yuv |
YUV (or Y'Cb'Cr)视频格式 |
format |
fourcc |
YUY2, YVYU, UYVY, Y41P, IYU2, Y42B, YV12, I420, Y41B, YUV9, YVU9, Y800 |
视频的布局.具体定义请参考 FourCC definition site YUY2, YVYU and UYVY是4:2:2的像素比例(packed-pixel), Y41P是4:1:1的像素比例, IYU2是4:4:4的像素比例. Y42B是4:2:2 planar, YV12 和I420是4:2:0 planar, Y41B是4:1:1 planar,YUV9和YVU9是4:1:0 planar. Y800 仅包含Y-samples(黑/白). |
video/x-raw-rgb |
Red-Green-Blue (RBG) video. |
bpp |
integer |
大于0 |
每个像素的位数.通常是16, 24或32. |
depth |
integer |
大于0 |
每个像素被R/G/B使用的位数.通常是15, 16或24. |
||
endianness |
integer |
G_BIG_ENDIAN (1234)或G_LITTLE_ENDIAN (4321) |
样本的字节序。 值G_LITTLE_ENDIAN (4321)意味着 "little-endian" (字节序是"字节序是"). 值G_BIG_ENDIAN (1234)意味着 "big-endian" (字节序是"高位字节优先""). 对于24/32的bpp, 通常是big-endian, because the byte order can be given in both.?? |
||
red_mask, green_mask和blue_mask |
integer |
任意值 |
用来覆盖每个样本中所有被使用的位的掩码。掩码应该以特定的endianness形式给出。这意味着对于24/32的bpp,掩码可能和主机字节序相反(如果你使用计算机中的字节序是little-endian的)。 |
||
所有encoded video类型 |
|||||
video/x-3ivx |
3ivx视频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/x-divx |
DivX视频 |
divxversion |
integer |
3, 4或5 |
对流进行DivX编码的算法版本号. |
video/x-dx |
数字视频 |
systemstream |
boolean |
FALSE |
标识该数据流不是 一个系统级的容器流。 |
video/x-ffv |
FFMpeg视频 |
ffvversion |
integer |
1 |
对流进行FFMpeg视频编码的算法版本号. |
video/x-h263 |
H-263视频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/x-h264 |
H-264视频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/x-huffyuv |
Huffyuv视频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/x-indeo |
Indeo视频 |
indeoversion |
integer |
3 |
对流进行Indeo编码的算法版本号. |
video/x-jpeg |
Motion-JPEG视频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性.注意video/x-jpeg只针对于Motion-JPEG图象(YUY2 色彩空间). 基于RGB色彩空间的JPEG 图象请参考image/jpeg (JPEG 图象). |
video/mpeg |
MPEG视频 |
mpegversion |
integer |
1, 2或4 |
对流进行MPEG编码的算法版本号.注意3ivx, XviD, DivX和"标准" ISO MPEG-4它们之间mime类型的区别。我们对这些做到都很熟悉 不是 一件容易的事情。但我们还没有其它的办法来解决这个问题。 |
systemstream |
boolean |
FALSE |
标识该数据流不是一个系统级的容器流。 |
||
video/x-msmpeg |
Microsoft MPEG-4 video deviations. |
msmpegversion |
integer |
41, 42或43 |
对流进行类MS-MPEG-4编码的算法版本号。值41表示MS MPEG 4.1, 42表示4.2,43表示4.3. |
video/x-msvideocodec |
Microsoft Video 1 (oldish codec). |
msvideoversion |
integer |
1 |
编码的算法版本号,总是1. |
video/x-pn-realvideo |
Realmedia视频 |
rmversion |
integer |
1, 2或3 |
对流进行Real视频编码的算法版本号。 |
video/x-rle |
RLE animation format. |
layout |
string |
"microsoft"或"quicktime" |
Microsoft AVI容器中的RLE格式与Apple的Quicktime容器中的RLE格式有着不同的字节布局。该属性保存了布局的轨迹。 |
depth |
integer |
1 to 64 |
被使用的调色板中的位深。这意味着该格式的调色板定义了2的n次幂的颜色深度。 |
||
palette_data |
GstBuffer |
|
该格式的中存储调色板(native-endian RGBA)的缓存。缓存大小为 4*2^depth。 |
||
video/x-svq |
Sorensen视频 |
svqversion |
integer |
1或3 |
对流进行Sorensen编码的算法版本号。 |
video/x-tarkin |
Tarkin视频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/x-theora |
Theora视频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/x-vp3 |
VP-3 视频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性.注意VP-3和Theora之间mime类型的区别,这不是一个非常必要的区分。这点或许会被改进。 |
video/x-wmv |
Windows媒体视频 |
wmvversion |
integer |
1,2或3 |
对流进行WMV编码的算法版本号。 |
video/x-xvid |
XviD视频 |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
所有image类型 |
|||||
image/jpeg |
Joint Picture Expert Group Image. |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性.注意image/jpeg仅针对基于RGB-颜色空间的JPEG图象; 基于YUY2-颜色空间的JPEG图象对应于video/x-jpeg ("Motion JPEG"). |
image/png |
Portable Network Graphics Image. |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
Table 12-3. 容器类型表
Mime类型 |
描述 |
属性 |
属性类型 |
属性值 |
属性描述 |
video/x-ms-asf |
Advanced Streaming Format (ASF). |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/x-msvideo |
AVI. |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/x-dv |
Digital Video. |
systemstream |
boolean |
TRUE |
标明这是一个容器系统流而不是一个基本的视频流。 |
video/x-matroska |
Matroska. |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/mpeg |
Motion Pictures Expert Group System Stream. |
systemstream |
boolean |
TRUE |
标明这是一个容器系统流而不是一个基本的视频流。 |
application/ogg |
Ogg. |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/quicktime |
Quicktime. |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
video/x-pn-realvideo |
Digital Video. |
systemstream |
boolean |
TRUE |
标明这是一个容器系统流而不是一个基本的视频流。 |
audio/x-wav |
WAV. |
|
|
|
当前没有为该类型定义任何属性,它目前也不需要任何属性. |
Table 12-4. 字幕类型表
Mime类型 |
描述 |
属性 |
属性类型 |
属性值 |
属性描述 |
|
|
|
|
|
还未定义 |
Table 12-5. 其它类型表
Mime类型 |
描述 |
属性 |
属性类型 |
属性值 |
属性描述 |
|
|
|
|
|
还未定义 |
到目前为止,我们仅处理了一些一直可用的衬垫。但有时候,有些衬垫只在某些情形下产生,或仅当应用程序请求某些衬垫时才会产生。前一种情况叫 间或; 后一种情况叫 请求 衬垫。衬垫的有效性(availability)(一直,间或,请求)可以在衬垫模板中看到。本章将讨论其中两中,它们的有效时机,它们如何被创建,它们什么时候被移除。
一个 "间或" 衬垫是指在某些特定情形下所产生的衬垫,而不是所有的情况下都会发生。这主要取决于流的内容:分流器主要解析流数据头,分辨出该流中所嵌入的基本流种类,(视频,音频,字幕等)然后为每种基本流类型创建一个间或衬垫。系统对每个基本流总是创建多于一个的衬垫。唯一的限制是每个新创建的衬垫都有一个唯一的名字。间或衬垫在流数据被移除时同样被移除。(像从PAUSED 转换到 READY 状态)你 不应该 在流到达EOS时才来移除衬垫。因为一些衬垫可能重活跃在管道中,并在到达流终点时回朔回去。流数据在EOS后应该仍然有效,至少到流数据被移除后。在上面的任何情形下,元件总是衬垫的所有者。
下面的示例代码将会解析一个txt文件,该文件的第一行是一个数字,随后的行都以一个数字开头(0到n-1),这些数字标识了数据被发送时的衬垫。
3
0: foo
1: bar
0: boo
2: bye
代码解析这个文件并创建动态"间或" 衬垫,如下所示:
typedef struct _GstMyFilter {
[..]
gboolean firstrun;
GList *srcpadlist;
} GstMyFilter;
static void
gst_my_filter_base_init (GstMyFilterClass *klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
static GstStaticPadTemplate src_factory =
GST_STATIC_PAD_TEMPLATE (
"src_%02d",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("ANY")
);
[..]
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&src_factory));
[..]
}
static void
gst_my_filter_init (GstMyFilter *filter)
{
[..]
filter->firstrun = TRUE;
filter->srcpadlist = NULL;
}
/*
* Get one line of data - without newline.
*/
static GstBuffer *
gst_my_filter_getline (GstMyFilter *filter)
{
guint8 *data;
gint n, num;
/* max. line length is 512 characters - for safety */
for (n = 0; n < 512; n++) {
num = gst_bytestream_peek_bytes (filter->bs, &data, n + 1);
if (num != n + 1)
return NULL;
/* newline? */
if (data[n] == '\n') {
GstBuffer *buf = gst_buffer_new_and_alloc (n + 1);
gst_bytestream_peek_bytes (filter->bs, &data, n);
memcpy (GST_BUFFER_DATA (buf), data, n);
GST_BUFFER_DATA (buf)[n] = '\0';
gst_bytestream_flush_fast (filter->bs, n + 1);
return buf;
}
}
}
static void
gst_my_filter_loopfunc (GstElement *element)
{
GstMyFilter *filter = GST_MY_FILTER (element);
GstBuffer *buf;
GstPad *pad;
gint num, n;
/* parse header */
if (filter->firstrun) {
GstElementClass *klass;
GstPadTemplate *templ;
gchar *padname;
if (!(buf = gst_my_filter_getline (filter))) {
gst_element_error (element, STREAM, READ, (NULL),
("Stream contains no header"));
return;
}
num = atoi (GST_BUFFER_DATA (buf));
gst_buffer_unref (buf);
/* for each of the streams, create a pad */
klass = GST_ELEMENT_GET_CLASS (filter);
templ = gst_element_class_get_pad_template (klass, "src_%02d");
for (n = 0; n < num; n++) {
padname = g_strdup_printf ("src_%02d", n);
pad = gst_pad_new_from_template (templ, padname);
g_free (padname);
/* here, you would set _getcaps () and _link () functions */
gst_element_add_pad (element, pad);
filter->srcpadlist = g_list_append (filter->srcpadlist, pad);
}
}
/* and now, simply parse each line and push over */
if (!(buf = gst_my_filter_getline (filter))) {
GstEvent *event = gst_event_new (GST_EVENT_EOS);
GList *padlist;
for (padlist = srcpadlist;
padlist != NULL; padlist = g_list_next (padlist)) {
pad = GST_PAD (padlist->data);
gst_event_ref (event);
gst_pad_push (pad, GST_DATA (event));
}
gst_event_unref (event);
gst_element_set_eos (element);
return;
}
/* parse stream number and go beyond the ':' in the data */
num = atoi (GST_BUFFER_DATA (buf));
if (num >= 0 && num < g_list_length (filter->srcpadlist)) {
pad = GST_PAD (g_list_nth_data (filter->srcpadlist, num);
/* magic buffer parsing foo */
for (n = 0; GST_BUFFER_DATA (buf)[n] != ':' &&
GST_BUFFER_DATA (buf)[n] != '\0'; n++) ;
if (GST_BUFFER_DATA (buf)[n] != '\0') {
GstBuffer *sub;
/* create subbuffer that starts right past the space. The reason
* that we don't just forward the data pointer is because the
* pointer is no longer the start of an allocated block of memory,
* but just a pointer to a position somewhere in the middle of it.
* That cannot be freed upon disposal, so we'd either crash or have
* a memleak. Creating a subbuffer is a simple way to solve that. */
sub = gst_buffer_create_sub (buf, n + 1, GST_BUFFER_SIZE (buf) - n - 1);
gst_pad_push (pad, GST_DATA (sub));
}
}
gst_buffer_unref (buf);
}
注意,我们使用了很多的检查代码来确保文件类容的有效性。这主要基于两点考虑:第一,文件可能有错误,这样我们可以防止程序崩溃。第二,也是最重要的一个原因 - 在极端情况下 - 文件可能是带有恶意的来触发插件的未定义的行为,这可能导致安全问题。一直类型的衬垫则是假定文件不一定是友好的。
"请求" 衬垫和间或衬垫非常类似,除了请求是应外部元件的要求,而间或是内部自发的行为。这个概念常用在混音器,混音器对每个要被放置到外部流的基本流都会产生一个接收衬垫。请求衬垫也常用在有着不定数目的输入输出衬垫的元件,好比 tee
(多输出), switch
或aggregator
(同样是多输入) 元件。在写这份文档的时候,我还不大清楚谁应该负责清理创建的请求衬垫以及如何或什么时候来做这些事情。下面是一个基于请求衬垫的aggregator简单例子
static GstPad * gst_my_filter_request_new_pad (GstElement *element,
GstPadTemplate *templ,
const gchar *name);
static void
gst_my_filter_base_init (GstMyFilterClass *klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
"sink_%d",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("ANY")
);
[..]
gst_element_class_add_pad_template (klass,
gst_static_pad_template_get (&sink_factory));
}
static void
gst_my_filter_class_init (GstMyFilterClass *klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
[..]
element_class->request_new_pad = gst_my_filter_request_new_pad;
}
static GstPad *
gst_my_filter_request_new_pad (GstElement *element,
GstPadTemplate *templ,
const gchar *name)
{
GstPad *pad;
GstMyFilterInputContext *context;
context = g_new0 (GstMyFilterInputContext, 1);
pad = gst_pad_new_from_template (templ, name);
gst_element_set_private_data (pad, context);
/* normally, you would set _link () and _getcaps () functions here */
gst_element_add_pad (element, pad);
return pad;
}
当播放复合媒体的时候,每个声音样本和图象样本必须在指定的时间按照指定的秩序播放。为此,gstreamer提供了一个同步机制。
在gstreamer中,有两种时间。 时钟时间 是绝对时间。相对而言, 元件时间 是相对时间,通常相对于当前的流媒体开始(译注:播放的时刻)。元件时间代表了一个时刻。在这个时刻,此元件正在处理一个媒体采样。元件时间可以通过时钟时间加上一个偏移量来计算获得。
gstreamer可以使用(译注:2种)不同时钟. 虽然系统时间可以作为时钟,但是声卡和其他(硬件)设备提供了更好的时间源.为此,一些元件提供了一个时钟. 那些提供时钟的元件里实现了 get_clock
方法。
因为时钟返回绝对时间刻度,所以他们通常不被直接使用。相反的,对时钟的引用被存储在任何需要它的元件中,并且gstreamer在内部使用它来计算元件时间。
现在,我们将看到,在不同的状态下的时间信息往来于管道中。
管道开始播放。 源元件通常知道每个样本的播放时间。 [1] 首先,源元件发出一个非连续的事件。这个事件载有关于下个样本的相对于当前的时间。这个相对时间是多变的(无规律的),但必须和放入缓冲器的时间戳相符合。在流媒体开始的时候,或是涉及的各个媒体在任何有意义的(阶段),相对时间都是可以预计的。当接收它时,其他的元件调整它们的元件时间的偏移量,使这个时间和写在事件中的时间相匹配。
然后,源元件发出(存储在)缓冲中的样本。这个元件在每个缓冲里面放置一个时间戳来说明(保存在缓冲里面的)样本何时被播放。当一个buffer到达了下个元件的sink pad的时候,这个元件比较当前的元件时间和这个buffer的时间戳。如果时间戳高于或等于(元件时间),它就播放buffer,否则它就调用gst_element_wait()
来等待,直到放置了(符合条件的)时间的buffer到来。
如果流被查找,那么下个被发出的样本将拥有一个没有根据元件时间校准过的时间戳。因此,源元件必须发出一个非连续的事件。
[1] |
有时,它是一个知道时间的解析器元件。例如,如果一个管道拥有一个被MPEG解码器元件连接filesrc元件,前者(MPEG解码器元件)知道每个样本的(播放)时间,因为,每个样本何时被播放的信息被嵌入了MPEG的格式里面。在这种情况下,这个元件会被看成源元件来讨论。 |
让我们澄清,在管道中gstreamer和每个元件之间的契约.
源元件(或提供时间概念的格式解析器,例如MPEG的,可参考前文)必须放置时间戳在它们分发的每个buffer里。起源时间是随机的,但必须和分发非连续事件的时间相匹配(见下文).不过,源流媒体的起源是可预料的。
为了初始化一个静止的管道中的元件时间,在开始播放之前,源元件必须发出一个非连续的事件。另外,在查找后,必须发送一个非连续的事件,因为下个元件的时间戳和静止的管道的元件时间不匹配。
如果元件为了在一个特定的时间放出样本(实时播放),元件将要求一个clock(时钟),从而实现方法 set_clock
。
此外,在播放每个样本前,如果当前的元件时间小于样本中的时间戳,它将调用 gst_element_wait()
等待,直到当前时间达到(样本时间戳)。 [1]
[1] |
使用某些调度(模式), |
有时候,对象属性并不足以来控制一些能影响你元件行为的参数。当发生这种情况时,你可以把这些参数标记为可控的(beeing Controllable)。应用程序在对象存活期间能使用控制器子系统来动态调整属性的值。
控制器子系统包含在 gstcontroller 库中。你需要在你元件的源代码中包含下面的头部:
...
#include
#include
...
即使gstcontroller 库可能已经连接到主程序中,你还是需要确保它已经在你的 plugin_init 函数中被初始化:
static gboolean
plugin_init (GstPlugin *plugin)
{
...
/* initialize library */
gst_controller_init (NULL, NULL);
...
}
让所有的GObject参数都是实时可控是没有意义的。因此下一步是标记可控参数。通过特殊标志 GST_PARAM_CONTROLLABLE
可以达到这个目的。该设置是在 _class_init
方法中建立GObject参数的时候。
g_object_class_install_property (gobject_class, PROP_FREQ,
g_param_spec_double ("freq", "Frequency", "Frequency of test signal",
0.0, 20000.0, 440.0,
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
上一节我们知道如何标记GObject参数为可控。应用程序开发人员能够将可控参数中改变排队。控制器子系统的这种处理方法使得插件能对那些被取出的改变进行处理。这仅通过一步:
gst_object_sync_values(element,timestamp);
这个调用使得所有在给定的时间戳的参数改变值通过适应元件的GObject属性来被激活。具体取决于元件检测到的同步率。
对于视频处理元件,最好的方式是对每帧进行同步。这意味着程序员可以通过gst_object_sync_values()
调用来进行元件的数据处理,该函数在上部分提到过。
音频元件的处理比视频元件的处理复杂。关键点是音频有很高的频率。例如对于PAL视频,每秒处理25帧,但对于标准音频,每秒将会有44100个样本。同步可控参数对于那么多数量的样本起不了什么作用。最简单的解决办法是使用一个每缓存处理同步。这使得控制速率取决于缓存大小。
使用一个特定控制速率的元件需要在同步每n个样本时打破它们的数据处理循环。
WRITEME
在前面部分,增加参数章中,我们介绍了控制对象行为的GObject属性的概念。这个东西非常有用,但它有两个明显的缺陷:首先,它太泛泛;其次,它不是动态的。
第一个缺陷涉及到用来建立控制元件的自定义最终用户接口的问题。一些属性比之其它属性而言,更加重要。一些整型属性适合在spin-button widget中体现出来,而另一些可能适合在slider widget中体现出来。不过这种事情不可能发生,因为UI在应用程序中没有实际意义。一个表示位率属性的UI widget同一个表示视频大小的UI widget是同种类型,它们都是 GParamSpec
类型。另一个问题是,那些如参数组、函数组或参数联合的东西都是不太现实的。
第二个问题是这些参数都不是动态加载的。在很多情形下,一个属性的允许值并不是固定的,具体取决于运行时的情况。举例来说,源元件video4linux中的TV卡的输入,它的名字就是当我们打开设备时,从内核驱动中得到的,这个场景也仅发生在元件进入READY状态的时候。这也意味着我们不能通过创建一个枚举属性类型来把这些值显示给用户。
这些问题的解决方法是为每个常用控制创建一个特定的控制类型。我们需要一个接口的概念来达到这个目的。接口的基础是glib中的 GTypeInterface
类型。对于上面我们认为能有用处的情形,我们会为每种情形创建一个接口,该接口由元件自己去实现。我们同样创建一个针对 GTypeInterface
(本身也是静态类型)的小扩展来方便我们查询基于运行时参数的接口可用性。该扩展叫 GstImplementsInterface
。
请记住最重要的一点:接口不是要取代属性。更恰当地讲,接口是属性的 锦上添花。有两方面可以说明这点:首先,属性可以存储在XML文件中。其次属性可以通过命令行(gst-launch)来得到其详细说明。
建议在你元件的 _get_type ()
来实现接口。你可以在注册完元件类型本身后,注册一个或多个接口。一些接口可能依赖于其它接口,或只能在特定的元件类型中被注册。当使用元件时,当使用接口发生错误时,你会得到通知:程序会以一个失败的声明(assertion)退出,该声明解释了出错的地方。在GStreamer的其它情形中,唯一的依赖关系是一些接口是 GstImplementsInterface
。每个接口,当其依赖于这个扩展时,我们都会明确地指出来。如果确实存在这种依赖,你需要在注册所有你想要支持的接口前先为那种接口注册。下面的例子解释了如何为一个没有依赖的简单接口实现。对于 GstImplementsInterface
的解释,参考下部分混音器接口: Mixer Interface.
static void gst_my_filter_some_interface_init (GstSomeInterface *iface);
GType
gst_my_filter_get_type (void)
{
static GType my_filter_type = 0;
if (!my_filter_type) {
static const GTypeInfo my_filter_info = {
sizeof (GstMyFilterClass),
(GBaseInitFunc) gst_my_filter_base_init,
NULL,
(GClassInitFunc) gst_my_filter_class_init,
NULL,
NULL,
sizeof (GstMyFilter),
0,
(GInstanceInitFunc) gst_my_filter_init
};
static const GInterfaceInfo some_interface_info = {
(GInterfaceInitFunc) gst_my_filter_some_interface_init,
NULL,
NULL
};
my_filter_type =
g_type_register_static (GST_TYPE_MY_FILTER,
"GstMyFilter",
&my_filter_info, 0);
g_type_add_interface_static (my_filter_type,
GST_TYPE_SOME_INTERFACE,
&some_interface_info);
}
return my_filter_type;
}
static void
gst_my_filter_some_interface_init (GstSomeInterface *iface)
{
/* here, you would set virtual function pointers in the interface */
}
WRITEME
混合器接口的主要目的是提供一个简单但强大的API给应用程序以便达到对音频硬件混音/音量控制。很多的声卡拥有硬件混音器,音量可以通过混合器被改变,音量可以被减弱,输入能通过混合它们的内容而被修改,这些输入是应用程序从设备上读取的数据(在我们的场景中:音频源插件)。混合器接口是一种控制这些东西的方式。混合器接口同样能用于软件级的音量控制(像"音量"元件)。该接口的最终目标是允许扩展硬件级的音量控制应用程序和控制音频音量和输入/输出设置。
混合器接口需要通过 GstImplementsInterface
接口才能被元件实现。下面的例子展示了两个方面的内容,因此也包含了一个关于 GstImplementsInterface
的例子(另一方面是混合器接口)。在这个接口中,它需要设置一个支持 supported ()
函数的函数指针。如果你没有设置,该函数会默认返回FALSE (默认实现) ,并且混合器接口的实现也不会工作。对于混合器接口而言,唯一需要的函数是 list_tracks ()
。所有其它位于混合器接口中的函数指针都是可选的,尽管强烈建议至少为get_volume ()
和set_volume ()
设置函数指针。该接口的API参考手册中说明了每个函数的功能,因此我们在此仅实现上面几个函数。
下面的例子显示了一个N-1元件中的混合器接口实现。这里并没有给出混和流的实际处理过程,因为该过程对于本手册而言显得太复杂,也不适合该手册的主题。
#include
typedef struct _GstMyFilter {
[..]
gint volume;
GList *tracks;
} GstMyFilter;
static void gst_my_filter_implements_interface_init (GstImplementsInterfaceClass *iface);
static void gst_my_filter_mixer_interface_init (GstMixerClass *iface);
GType
gst_my_filter_get_type (void)
{
[..]
static const GInterfaceInfo implements_interface_info = {
(GInterfaceInitFunc) gst_my_filter_implements_interface_init,
NULL,
NULL
};
static const GInterfaceInfo mixer_interface_info = {
(GInterfaceInitFunc) gst_my_filter_mixer_interface_init,
NULL,
NULL
};
[..]
g_type_add_interface_static (my_filter_type,
GST_TYPE_IMPLEMENTS_INTERFACE,
&implements_interface_info);
g_type_add_interface_static (my_filter_type,
GST_TYPE_MIXER,
&mixer_interface_info);
[..]
}
static void
gst_my_filter_init (GstMyFilter *filter)
{
GstMixerTrack *track = NULL;
[..]
filter->volume = 100;
filter->tracks = NULL;
track = g_object_new (GST_TYPE_MIXER_TRACK, NULL);
track->label = g_strdup ("MyTrack");
track->num_channels = 1;
track->min_volume = 0;
track->max_volume = 100;
track->flags = GST_MIXER_TRACK_SOFTWARE;
filter->tracks = g_list_append (filter->tracks, track);
}
static gboolean
gst_my_filter_interface_supported (GstImplementsInterface *iface,
GType iface_type)
{
g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE);
/* for the sake of this example, we'll always support it. However, normally,
* you would check whether the device you've opened supports mixers. */
return TRUE;
}
static void
gst_my_filter_implements_interface_init (GstImplementsInterfaceClass *iface)
{
iface->supported = gst_my_filter_interface_supported;
}
/*
* This function returns the list of support tracks (inputs, outputs)
* on this element instance. Elements usually build this list during
* _init () or when going from NULL to READY.
*/
static const GList *
gst_my_filter_mixer_list_tracks (GstMixer *mixer)
{
GstMyFilter *filter = GST_MY_FILTER (mixer);
return filter->tracks;
}
/*
* Set volume. volumes is an array of size track->num_channels, and
* each value in the array gives the wanted volume for one channel
* on the track.
*/
static void
gst_my_filter_mixer_set_volume (GstMixer *mixer,
GstMixerTrack *track,
gint *volumes)
{
GstMyFilter *filter = GST_MY_FILTER (mixer);
filter->volume = volumes[0];
g_print ("Volume set to %d\n", filter->volume);
}
static void
gst_my_filter_mixer_get_volume (GstMixer *mixer,
GstMixerTrack *track,
gint *volumes)
{
GstMyFilter *filter = GST_MY_FILTER (mixer);
volumes[0] = filter->volume;
}
static void
gst_my_filter_mixer_interface_init (GstMixerClass *iface)
{
/* the mixer interface requires a definition of the mixer type:
* hardware or software? */
GST_MIXER_TYPE (iface) = GST_MIXER_SOFTWARE;
/* virtual function pointers */
iface->list_tracks = gst_my_filter_mixer_list_tracks;
iface->set_volume = gst_my_filter_mixer_set_volume;
iface->get_volume = gst_my_filter_mixer_get_volume;
}
混和器接口是立足于音频的。但是只要设置不同的软件标志,混和器可以在N-1元件中把各种流连接(不是聚集)成一种输出流。概念上讲,这也是混合(mixing)。你可以使用元件工厂的"种类" 来表示你元件的类型。在软件元件中,混合器随机处理流,你可以不用实现_get_volume ()
或_set_volume ()
函数。更准确的说,你只需要实现_set_record ()
来开关输出流中的轨道信息。为了确保实现的混合器(mixer-implementing)元件处于一种特定的状态,检查元件工厂的种类。
作为通过混合所有的流把N个流连接成一个输出的混合器接口相对的谐调器(tuner)接口而言, 它也是作用于N-1的元件,但不是混合所有输入流,它是选择一个流然后把这个流中的数据推给输出流。该接口会丢弃其它未被选中的输入流。存在一个标志来标识当前元件是软件级谐调器(这是纯软件的实现,N个sink衬垫,1个源衬垫)还是硬件级谐调器,这是只有一个源衬垫,整个选择该被处理的流是由硬件完成。软件级的可以用在像 switch这样的元件中。硬件级的可以用在有频道(channel)选择的元件中,像视频源元件中(v4lsrc, v4l2src,等)。如果你使用了一个特定的元件类型,通过元件工厂的"种类"来确定你使用的元件类型。注意该接口本身是高度类似于视频应用的(analog-video-centric)。
该接口需要 GstImplemensInterface
接口才能正常工作。
下面的例子显示了如何在一个元件中实现谐调器接口。其中并没有给出实际的选择流的过程,因为这与本节内容没有太大关联。
#include
typedef struct _GstMyFilter {
[..]
gint active_input;
GList *channels;
} GstMyFilter;
static void gst_my_filter_implements_interface_init (GstImplementsInterfaceClass *iface);
static void gst_my_filter_tuner_interface_init (GstTunerClass *iface);
GType
gst_my_filter_get_type (void)
{
[..]
static const GInterfaceInfo implements_interface_info = {
(GInterfaceInitFunc) gst_my_filter_implements_interface_init,
NULL,
NULL
};
static const GInterfaceInfo tuner_interface_info = {
(GInterfaceInitFunc) gst_my_filter_tuner_interface_init,
NULL,
NULL
};
[..]
g_type_add_interface_static (my_filter_type,
GST_TYPE_IMPLEMENTS_INTERFACE,
&implements_interface_info);
g_type_add_interface_static (my_filter_type,
GST_TYPE_TUNER,
&tunerr_interface_info);
[..]
}
static void
gst_my_filter_init (GstMyFilter *filter)
{
GstTunerChannel *channel = NULL;
[..]
filter->active_input = 0;
filter->channels = NULL;
channel = g_object_new (GST_TYPE_TUNER_CHANNEL, NULL);
channel->label = g_strdup ("MyChannel");
channel->flags = GST_TUNER_CHANNEL_INPUT;
filter->channels = g_list_append (filter->channels, channel);
}
static gboolean
gst_my_filter_interface_supported (GstImplementsInterface *iface,
GType iface_type)
{
g_return_val_if_fail (iface_type == GST_TYPE_TUNER, FALSE);
/* for the sake of this example, we'll always support it. However, normally,
* you would check whether the device you've opened supports tuning. */
return TRUE;
}
static void
gst_my_filter_implements_interface_init (GstImplementsInterfaceClass *iface)
{
iface->supported = gst_my_filter_interface_supported;
}
static const GList *
gst_my_filter_tuner_list_channels (GstTuner *tuner)
{
GstMyFilter *filter = GST_MY_FILTER (tuner);
return filter->channels;
}
static GstTunerChannel *
gst_my_filter_tuner_get_channel (GstTuner *tuner)
{
GstMyFilter *filter = GST_MY_FILTER (tuner);
return g_list_nth_data (filter->channels,
filter->active_input);
}
static void
gst_my_filter_tuner_set_channel (GstTuner *tuner,
GstTunerChannel *channel)
{
GstMyFilter *filter = GST_MY_FILTER (tuner);
filter->active_input = g_list_index (filter->channels, channel);
g_assert (filter->active_input >= 0);
}
static void
gst_my_filter_tuner_interface_init (GstTunerClass *iface)
{
iface->list_channels = gst_my_filter_tuner_list_channels;
iface->get_channel = gst_my_filter_tuner_get_channel;
iface->set_channel = gst_my_filter_tuner_set_channel;
}
正如前所说,谐调器高度类似于视频应用(video-centric)。它的特点是作用于所选择的每个输入或输出,对于每个输入,如果该输入支持频率协调(frequency-tuning),它的作用是选择一个正在协调的频率。类似地,如果输入支持信号强度获取(signal-strength-acquiring),它同样也能做类似的事情。频率协调常用在广播或电视(cable-TV tuning)。信号强度是一个识别信号的标识,并且可以为用户一个视觉反馈或在自动探测信号中被用到。与前面类似,它还能作用于规范选择,这对类视频元件非常有作用。
WRITEME
属性探测是一种问题的通用解决办法,该问题是存在一个枚举中的属性的值都是静态的。我们在 Adding Arguments中给出过枚举。属性探测试图完成一个和枚举列表类似的问题:给出一个限度,对每个属性显式列出允许的值。在枚举列表和属性探测之间有两个不同之处。首先,枚举仅允许字符串作为值;属性探测可以为所有的值类型有效。其次,所有有效的探测列表的内容可能在元件的生命周期内被改变。而枚举列表的的内容是静态的。目前,属性探测多用于设备的检测(像检测OSS元件,Video4linux元件等)。但理论上,属性探测可以对任何的属性都有效。
属性探测在 GValueArray
中存储了一个允许的(或被推荐)的列表,然后返回给用户。 NULL
也是一个有效的返回值。属性探测的过程实际上被分为两个部分:首先是探测属性并创建一个 GValueArray
,然后是得到当前的 GValueArray
。之所以分为两个部分是因为探测的过程可能很长(几秒种)。同样,这个简单的接口在元件中执行。对于应用程序而言,存在一个函数封装了上两步。关于这部分的更详细的信息,参阅API参考手册中 GstPropertyProbe
接口部分。
下面是一个针对音频过滤元件的属性探测的例子;它将会为 "silent" 属性探测所有允许的值。实际上,该值是 gboolean 型,因此没有很好的实际意义。再次声明,这只是一个例子。
#include
static void gst_my_filter_probe_interface_init (GstPropertyProbeInterface *iface);
GType
gst_my_filter_get_type (void)
{
[..]
static const GInterfaceInfo probe_interface_info = {
(GInterfaceInitFunc) gst_my_filter_probe_interface_init,
NULL,
NULL
};
[..]
g_type_add_interface_static (my_filter_type,
GST_TYPE_PROPERTY_PROBE,
&probe_interface_info);
[..]
}
static const GList *
gst_my_filter_probe_get_properties (GstPropertyProbe *probe)
{
GObjectClass *klass = G_OBJECT_GET_CLASS (probe);
static GList *props = NULL;
if (!props) {
GParamSpec *pspec;
pspec = g_object_class_find_property (klass, "silent");
props = g_list_append (props, pspec);
}
return props;
}
static gboolean
gst_my_filter_probe_needs_probe (GstPropertyProbe *probe,
guint prop_id,
const GParamSpec *pspec)
{
gboolean res = FALSE;
switch (prop_id) {
case ARG_SILENT:
res = FALSE;
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
break;
}
return res;
}
static void
gst_my_filter_probe_probe_property (GstPropertyProbe *probe,
guint prop_id,
const GParamSpec *pspec)
{
switch (prop_id) {
case ARG_SILENT:
/* don't need to do much here... */
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
break;
}
}
static GValueArray *
gst_my_filter_get_silent_values (GstMyFilter *filter)
{
GValueArray *array = g_value_array_new (2);
GValue value = { 0 };
g_value_init (&value, G_TYPE_BOOLEAN);
/* add TRUE */
g_value_set_boolean (&value, TRUE);
g_value_array_append (array, &value);
/* add FALSE */
g_value_set_boolean (&value, FALSE);
g_value_array_append (array, &value);
g_value_unset (&value);
return array;
}
static GValueArray *
gst_my_filter_probe_get_values (GstPropertyProbe *probe,
guint prop_id,
const GParamSpec *pspec)
{
GstMyFilter *filter = GST_MY_FILTER (probe);
GValueArray *array = NULL;
switch (prop_id) {
case ARG_SILENT:
array = gst_my_filter_get_silent_values (filter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
break;
}
return array;
}
static void
gst_my_filter_probe_interface_init (GstPropertyProbeInterface *iface)
{
iface->get_properties = gst_my_filter_probe_get_properties;
iface->needs_probe = gst_my_filter_probe_needs_probe;
iface->probe_property = gst_my_filter_probe_probe_property;
iface->get_values = gst_my_filter_probe_get_values;
}
你不需要提供任何的方法来实现getting或setting值。这些都会通过 GObject
_set_property ()
和_get_property ()
函数来处理。
一个X Overlay是XFree86中可拖拽的视频输出。实现了这个接口的元件可以在X11窗口中拖拽视频图象。通过这个接口,应用程序可以以2种方式来于实现了该接口的插件。第一种是被动模式,插件自己创建并销毁X11窗口。第二种是主动模式,应用程序处理X11窗口的创建,然后告诉插件在哪可以输出视频。让我们对这些模式来一些更深入的认识...
X11窗口中画出视频输出的插件需要在某个阶段保留那个窗口。被动模式仅意味着在那个阶段没有窗口提供给插件,所以插件自己创建窗口。这种情况下,插件有责任在其不再需要该窗口时销毁它。同时它告知应用程序窗口已经被创建。这样应用程序才能使用该窗口。这些通过have_xwindow_id
信号来完成,该信号通过 gst_x_overlay_got_xwindow_id
方法从插件中发射出。
你可能已经猜到主动模式仅意味着发送一个X11窗口给插件,这样视频可以在那输出。这些通过 gst_x_overlay_set_xwindow_id
方法来完成。
可以在任何时刻从一种模式转换到另一种模式,这样实现了该接口的插件可以处理任何的情形。这只有2种模式让插件写入器来实现,它们可能和下面的代码类似:
static void
gst_my_filter_set_xwindow_id (GstXOverlay *overlay, XID xwindow_id)
{
GstMyFilter *my_filter = GST_MY_FILTER (overlay);
if (my_filter->window)
gst_my_filter_destroy_window (my_filter->window);
my_filter->window = xwindow_id;
}
static void
gst_my_filter_get_desired_size (GstXOverlay *overlay,
guint *width, guint *height)
{
GstMyFilter *my_filter = GST_MY_FILTER (overlay);
*width = my_filter->width;
*height = my_filter->height;
}
static void
gst_my_filter_xoverlay_init (GstXOverlayClass *iface)
{
iface->set_xwindow_id = gst_my_filter_set_xwindow_id;
iface->get_desired_size = gst_my_filter_get_desired_size;
}
你同样需要使用接口方法在需要时发射信号,例如在衬垫连接函数中,通过该函数,你可以知道视频的几何信息,并可能创建窗口。
static MyFilterWindow *
gst_my_filter_window_create (GstMyFilter *my_filter, gint width, gint height)
{
MyFilterWindow *window = g_new (MyFilterWindow, 1);
...
gst_x_overlay_got_xwindow_id (GST_X_OVERLAY (my_filter), window->win);
}
static GstPadLinkReturn
gst_my_filter_sink_link (GstPad *pad, const GstCaps *caps)
{
GstMyFilter *my_filter = GST_MY_FILTER (overlay);
gint width, height;
gboolean ret;
...
ret = gst_structure_get_int (structure, "width", &width);
ret &= gst_structure_get_int (structure, "height", &height);
if (!ret) return GST_PAD_LINK_REFUSED;
if (!my_filter->window)
my_filter->window = gst_my_filter_create_window (my_filter, width, height);
gst_x_overlay_got_desired_size (GST_X_OVERLAY (my_filter),
width, height);
...
}
WRITEME
标签是存储在流中的一些非流内容的信息,它们是用来 描述 流的内容。大多数媒体容器格式在某种方式上支持标记(tagging)。Ogg通过VorbisComment来支持标签,MP3使用ID3,AVI以及WAV使用RIFF的INFO列表块等。GStreamer为元件从流中读取标签并解析给使用者提供了一个通用方式。标签(至少是元数据)将会是管道中流的一部分。这样的结果是,只要输入输出元件都支持标记,文件从一种格式转换到另一种格式将会自动保存标签。
标签在GStreamer中被分为两类。即使应用程序并不需要了解这个细节,但多知道一些也无妨。第一种叫 元数据,第二种叫流信息。元数据是一种描述非技术方面的流内容的标签。它们可以不需要完全重编码流而被改变。例如 "作者r", "标题" or "相册"。但是容器格式仍然需要被标签能重写。另一方面,流信息是一种描述流内容中技术性的内容。为了改变它们,需要对流重编码。例如 "codec" or "bitrate"。注意,一些容器格式(像ID3)在文件容器中存储了不同的流信息标签作为元数据。这意味着它们能被改变,因此它们也不再匹配文件的内容。但是叫它们元数据是因为 技术上讲,它们能不重编码整个流而被改变,即使这种改变是无效的。拥有这种元数据标签的文件将会有两次同样的标签:一次是元数据,一次是流信息。
GStreamer中一个读取标签的元件叫 TagGetter
。 一个写标签的元件叫TagSetter
。一个支持上两种操作的元件可以用做标签编辑器来快速改变标签。
对标签而言,最基本的对象是GstTagList
。一个从流中读取标签的元件应该创建一个空的标签列表,然后往其中填入单个的值。空的标签列表可以通过 gst_tag_list_new ()
来创建。然后元件使用 gst_tag_list_add_values ()
来填充列表。注意,一个元件可能以字符串的形式来读取元数据,但存储时不一定是以字符串格式。确保使用 gst_value_transform ()
来保证你的数据为合理类型。读取完数据后,应用程序通过调用 gst_element_found_tags ()
来通知一个新的标签列表的产生。该标签同样是数据流的一部分,所以它将被发送给所有的源衬垫。函数 gst_event_new_tag ()
由标签列表创建一个事件。该事件可以通过 gst_pad_push ()
传递给所有的源衬垫。 仅有一个源衬垫的简单元件能通过 gst_element_found_tags_for_pad ()
将上面的步骤融合在一起。
下面的例子将会解析一个文件,并将文件中的数据作为元数据/标签来解析而不是作为实际内容。它会解析像"name:value"的行,其中name是元数据的类型(标题,作者,...),value是元数据的值。 _getline ()
是在 Sometimes pads中提到过的。
static void
gst_my_filter_loopfunc (GstElement *element)
{
GstMyFilter *filter = GST_MY_FILTER (element);
GstBuffer *buf;
GstTagList *taglist = gst_tag_list_new ();
/* get each line and parse as metadata */
while ((buf = gst_my_filter_getline (filter))) {
gchar *line = GST_BUFFER_DATA (buf), *colon_pos, *type = NULL;a
/* get the position of the ':' and go beyond it */
if (!(colon_pos = strchr (line, ':')))
goto next:
/* get the string before that as type of metadata */
type = g_strndup (line, colon_pos - line);
/* content is one character beyond the ':' */
colon_pos = &colon_pos[1];
if (*colon_pos == '\0')
goto next;
/* get the metadata category, it's value type, store it in that
* type and add it to the taglist. */
if (gst_tag_exists (type)) {
GValue from = { 0 }, to = { 0 };
GType to_type;
to_type = gst_tag_get_type (type);
g_value_init (&from, G_TYPE_STRING);
g_value_set_string (&from, colon_pos);
g_value_init (&to, to_type);
g_value_transform (&from, &to);
g_value_unset (&from);
gst_tag_list_add_values (taglist, GST_TAG_MERGE_APPEND,
type, &to, NULL);
g_value_unset (&to);
}
next:
g_free (type);
gst_buffer_unref (buf);
}
/* signal metadata */
gst_element_found_tags_for_pad (element, filter->srcpad, 0, taglist);
gst_tag_list_free (taglist);
/* send EOS */
gst_pad_send_event (filter->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS)));
gst_element_set_eos (element);
}
当前我们假定内核已经 明了 mime类型 (gst_tag_exists ()
)。你可以通过 gst_tag_register ()
在列表中增加一个新标签。如果你认为该标签不仅在你当前的元件中有用处,在其它地方也能起到作用,那么建议你把该标签加入到 gsttag.c中。当然这取决于你的想法。如果你只想在你自己的元件中使用该标签,你可以很容易地在你的类初始化函数中注册该标签,首选 _class_init ()
。
static void
gst_my_filter_class_init (GstMyFilterClass *klass)
{
[..]
gst_tag_register ("my_tag_name", GST_TAG_FLAG_META,
G_TYPE_STRING,
_("my own tag"),
_("a tag that is specific to my own element"),
NULL);
[..]
}
标签写入器做着与标签读取器相反的工作。标签写入器仅将元数据标签写入帐号,因为只有一种类型的标签会被写入流。标签写入器能以三种方式接收到标签:内部的,程序,管道。内部标签是被元件自己读取的标签,这意味着在这种情况下,标签写入器也是一个标签读取器。程序标签是一种通过TagSetter接口(这仅是一个层次)而被提供的标签。管道标签是在管道内部提供给元件的标签。元件收到通过 GST_EVENT_TAG
事件收到标签,这意味着标签写入器能自动感知事件。标签写入器有责任将三种标签混合成一个列表中,并将它们写到输出流中。
下面的例子将会从应用程序和管道收到标签,并将它们混合在一个列表中再将该列表写到输出流中。这段代码实现了标签设置器,这样应用程序能设置标签,并在到来的事件中收到管道标签。
GType
gst_my_filter_get_type (void)
{
[..]
static const GInterfaceInfo tag_setter_info = {
NULL,
NULL,
NULL
};
[..]
g_type_add_interface_static (my_filter_type,
GST_TYPE_TAG_SETTER,
&tag_setter_info);
[..]
}
static void
gst_my_filter_init (GstMyFilter *filter)
{
GST_FLAG_SET (filter, GST_ELEMENT_EVENT_AWARE);
[..]
}
/*
* Write one tag.
*/
static void
gst_my_filter_write_tag (const GstTagList *taglist,
const gchar *tagname,
gpointer data)
{
GstMyFilter *filter = GST_MY_FILTER (data);
GstBuffer *buffer;
guint num_values = gst_tag_list_get_tag_size (list, tag_name), n;
const GValue *from;
GValue to = { 0 };
g_value_init (&to, G_TYPE_STRING);
for (n = 0; n < num_values; n++) {
from = gst_tag_list_get_value_index (taglist, tagname, n);
g_value_transform (from, &to);
buf = gst_buffer_new ();
GST_BUFFER_DATA (buf) = g_strdup_printf ("%s:%s", tagname,
g_value_get_string (&to));
GST_BUFFER_SIZE (buf) = strlen (GST_BUFFER_DATA (buf));
gst_pad_push (filter->srcpad, GST_DATA (buf));
}
g_value_unset (&to);
}
static void
gst_my_filter_loopfunc (GstElement *element)
{
GstMyFilter *filter = GST_MY_FILTER (element);
GstTagSetter *tagsetter = GST_TAG_SETTER (element);
GstData *data;
GstEvent *event;
gboolean eos = FALSE;
GstTagList *taglist = gst_tag_list_new ();
while (!eos) {
data = gst_pad_pull (filter->sinkpad);
/* We're not very much interested in data right now */
if (GST_IS_BUFFER (data))
gst_buffer_unref (GST_BUFFER (data));
event = GST_EVENT (data);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_TAG:
gst_tag_list_insert (taglist, gst_event_tag_get_list (event),
GST_TAG_MERGE_PREPEND);
gst_event_unref (event);
break;
case GST_EVENT_EOS:
eos = TRUE;
gst_event_unref (event);
break;
default:
gst_pad_event_default (filter->sinkpad, event);
break;
}
}
/* merge tags with the ones retrieved from the application */
if ((gst_tag_setter_get_tag_list (tagsetter)) {
gst_tag_list_insert (taglist,
gst_tag_setter_get_tag_list (tagsetter),
gst_tag_setter_get_tag_merge_mode (tagsetter));
}
/* write tags */
gst_tag_list_foreach (taglist, gst_my_filter_write_tag, filter);
/* signal EOS */
gst_pad_push (filter->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS)));
gst_element_set_eos (element);
}
注意,通常元件不能在处理标签前读取整个流。更恰当地说,它会在每个sinkpad都等待数据到来(因为标签通常在第一个数据缓存之前到来),一旦数据到来便处理数据。
上游事件是由管道中下游的某个元件创建的(例如:一个视频sink 也许会创建一个导航事件来通知上游元件当前的鼠标位置)。这也许在应用程序发出请求时也会间接的发生,例如当应用程序向管道发出一个 定位请求时该请求将被传递到sink元件,然后sink元件会创建一个上游 定位事件。
最常见的上游事件是定位事件(seek events)和服务质量事件(QoS events)。
一个上游事件可以用 gst_pad_send_event
函数来发送。这个函数只是简单的调用该衬垫的缺省事件处理器。衬垫的缺省 事件处理器是gst_pad_event_default
,它主要是将事件发送给对等衬垫。因此上游事件总是到达你的元件的源衬垫 并被缺省事件处理器处理,除非你重载处理器来自己处理。在一些特别的 情况下你必须这样做:
· 如果你的元件中有多个sink衬垫。在这种情况下你将不得不决 定通过哪个sink衬垫将事件发出(如果不通过所有的)。
· 如果你需要在本地处理事件。例如在你发送上游导航事件前你 想要转换它,或者你想处理一个QoS事件。
你在事件处理器中所做的操作不是很要紧,但是你必须考虑一些重 要的规则,因为一个元件的糟糕的事件处理器将破坏整个管道的事件处理。这些规则如下:
· 始终使用 gst_pad_event_default
转发你不想处理的上游事件。
· 如果你要根据你收到事件创建一个新的事件,不要忘了用 gst_event_unref来减少你收到的事件的引用。
· 事件处理函数会被假定返回TRUE或FALSE来表示事件是否被处 理了。永远不要简单的返回TRUE或FALSE除非你真的知道你以及处 理了哪个事件。
· 记住,事件处理器有可能在流化线程外的另一个线程里被调用 ,因此确保你在所有的地方以及在那些使用 gst_pad_get_parent()
函数得到你的元件的函数的开头恰当的使用了锁(并且在函数的结尾使用函数gst_object_unref ()
释放它。)。
在这一章列出一个所有当前使用的已定义的事件,以及如何使用它们的说明。你可以使用GST_EVENT_TYPE宏来检测一个特定事件的类型(如果你需要一个字符串以便于调试,使用GST_EVENT_TYPE_NAME)。
在这一章,我们将讨论如下的事件:
· 流结束(EOS)
· 冲刷开始
· 冲刷结束
· 新的片断
· 定位请求
· 导航
· 标签(元数据)
要获取关于事件的更全面的信息以及怎样在各种情况下正确使用它们,请查阅GStreamer设计文档。该小节仅仅给出一个总揽。
当一个元件发送完数据流时,流结束事件就会被发送。元件收到该事件(从上游,因此从它的sink衬垫接受的)通常只是处理一些缓冲数据(如果有的话)然后将事件向下游发送。gst_pad_event_default ()
会做所有这些工作,因此大多数元件不需要支持该事件。那些在流结束需要显式地关闭一个资源的元件以及N到1的元件是一个例外。注意,流自身不是一个在收到流结束事件时需要关闭的资源。应用程序也许会在流结束之前往回定位然后继续播放。
流结束事件没有任何属性,这使得它是GStreamer中最简单的事件之一。它用 gst_event_new_eos()
函数创建。
值得一提的是只有那些驱动整个管道的元件才应该发出流结束事件。如果你的元件是基于链的(chain-based),它不会驱动整个管道。基于链的元件应该只是在流(或片断)结束时从它们的链函数返回GST_FLOW_UNEXPECTED,驱动管道的上游元件将负责发送流结束事件(或者给bus发送一个SEGMENT_DONE消息,这取决于操作模式)。如果你在实现你自己的源元件,你也不必手动的发送一个流结束事件,你应该只是在创建函数中返回GST_FLOW_UNEXPECTED(假设你的元件继承自GstBaseSrc或GstPushSrc)。
如果管道中所有缓存都为空时,冲刷事件将会发送给下游元件。"队列" 元件在接收到该事件后将会清空它们的内部缓存列表。例如,文件接收元件(比如 "filesink") 在接收到该事件后将会冲刷掉kernel-to-disk缓存(fdatasync ()
或fflush ()
) 通常地,接收到该事件的元件仅会将该事件往前发,因为大多数的过滤器和类过滤元件都没有内部缓存数据。 gst_pad_event_default ()
可以传递该事件,对大多数的元件而言,使用默认时间处理方法来向前传递该事件已经足矣。
从管道中刷新所有数据有一定的边界效应,该事件会使所有的衬垫拒收数据直到收到Flush Stop 信号,这期间它阻塞了流线程。(试图写入数据的元件会得到一个WRONG_STATE信号,并中止处理数据。).
冲刷开始时间通过 gst_event_new_flush_start ()
创建。和EOS事件类似,该事件没有属性。该事件仅由驱动管道的元件产生,像运行在push模式下的源元件或pull模式的分流器/解码器。
冲刷结束事件由驱动管道的元件发送,发送时机是在冲刷开始事件后,它告诉下游元件它们可以再次接收其它事件和缓存。(在第一个缓存通过之前,至少有一个NEWSEGMENT事件)。
如果你的元件还持有流数据的缓存,它应该在接收到FLUSH-STOP事件后清除该缓存。(对于任何时刻其链函数接收到一个有DISCONT标志的缓存也是如此)
flush-stop事件通过 gst_event_new_flush_stop ()
被创建。像EOS事件一样,它也没有属性。
当通告数据流中的新数据片或用新值更新当前片段都会发送一个新片段事件。一个新片段事件总是在第一个缓存被冲刷之前(见上)。 >
第一个新片段事件由驱动管道的元件创建,像运行在push模式下的源元件或pull模式的分流器/解码器。这个新片段事件在管道中旅行,并在旅途中被转换格式。(例如一个解码器,可能收到一个BYTES格式的new-segment事件,它可能将该时间转换为基于平均位率的TIMES格式的new-segment事件)。
新片段事件同样可以用来指示流中的‘陷阱’,例如字幕流可能没有对整个视频流一一对应的数据。这可以通过更新当前片段的起始位置来应付这个缺陷(详情参见设计文档)。
根据媒体类型,该事件可以简单地通过 gst_pad_event_default ()
向前传递,或被解析并被修改再传递给下游元件。后种情况的应用有分流器,它有着字节到时间(byte-to-time)转换的概念。分流器的输入是基于字节的,所以新到的事件有着字节单元的偏移 (GST_FORMAT_BYTES
)。但是,下游元件期望得到以时间单元为便移的新片段事件,以便用该事件来更新管道时钟。因此,分流器及类似元件不会直接向下游元件发送事件,而是解析收到的事件,释放掉该事件结构,并发送一个新的newsegment事件给下游元件(使用时间单元 GST_FORMAT_TIME
)。
newsegment事件通过函数 gst_event_new_new_segment ()
被创建。该函数的参数情况请参见API参考手册和设计文档。
元件解析该事件,通过gst_event_parse_new_segment_full()解析事件详情。元件可以找到相应的GstSegment API来明了当前数据片段(例如它们想要使用该片来裁剪输出)。
请求定位意味着为元件请求一个新的流位置。这个新位置可以通过不同的格式来设置。(时间,字节或 "默认单位" [标识视频帧的术语,音频样本的独立声道等])。定位可以以文件尾,文件头或当前位置为参照,通常为往上游元件方向。(下游定位通过发送参合了适当偏移量的NEWSEGMENT事件给支持该信号的元件,像filesink)。
元件接收到定位事件后,根据元件的类型,可以仅向上传递该事件(filters, decoders),或改变事件的格式再向上传递(分流器),或通过改变内部流资源的文件指针来处理该事件(文件资源,pull模式中驱动管道的分流器或解码器) 或其它行为。
定位事件通过特定格式的位置信息(时间,字节,单元)被建立。它们通过函数 gst_event_new_seek ()
来被创建。注意大多数的插件不支持从流尾部或当前位置开始定位。一个没有驱动管道的元件发送了一个定位请求,这不能被认为定位成功或者做了定位请求的事情。这种情形下,该元件应该是收到一个NEWSEGMENT事件后才被认为做了定位请求的事情。
元件可以通过gst_event_parse_seek()
来解析该事件。
导航事件通常由视频sink向上游元件发送,它用来通知上游元件鼠标位置。当鼠标点击事件发生或键盘被按下或松开,该事件便会发生。
所有这些信息都在由gst_event_get_structure ()
得到的一个事件结构中。
检查gst-plugins-good中的导航元件是一个得到该事件信息的办法。
标签事件被发送往下游,其用来标识流数据中的需要被解析的标签。目前主要用该事件来保护在流格式转换期间不改变标签信息。标签在 18章中有过广泛的阐述。更多的事件将简单地通过调用 gst_pad_event_default ()
来向前传递。
标签事件通过 gst_event_new_tag ()
来创建,但更多的元件是使用 gst_element_found_tags ()
或gst_element_found_tags_for_pad ()
,这两个函数做的事情类似:往总线上发送一个消息,发送一个标签事件给下游。所有这些函数需要一个自主的标签列表作为参数。
元件可以通过函数 gst_event_parse_tag ()
得到事件包含的标签列表从而来解析该事件。
IV. 创建特殊类型的元件
迄今为止,我们已看到了许多可以融入GStreamer元件的特性。它们中的大多数都相当的底层且涉及到GStreamer内部的工作机制。幸运的是, GStreamer包含了一些易于使用的接口来创建这样的插件。为此,我们将更深入的考查GStreamer提供的基础类(源,sinks,以及转换元件)。我们也将更深入的着眼于那些对特定代码没有要求的类型的元件,譬如调度交互(scheduling-interaction)或数据流通,但这些都要求特定的管道控制(例如N到1元件和管理器)。
目 录
20. Pre-made基类
20.1. 编写一个sink
20.1.1. 编写一个音频sink
20.1.2. 编写一个视频sink
20.2. 编写一个源
20.2.1. 编写一个音频源
20.3. 编写一个转换元件
21. 编写一个分流器或解析器
22. 编写一个N-to-1元件或混合器(Muxer)
23. 编写一个管理器
迄今,我们已经从底层看到了如何创建任何类型的GStreamer元件。现在,我们假设你想要创建一个简单的音频sink,譬如说其工作方式和"esdsink"一样,或者你想创建一个滤镜来使音量正常。这些插件都是概念性的因为它们并没有做任何特殊的事,这应该比实现一个调度器函数和复杂的caps协商来的简单。为此,GStreamer提供了一些基类来简化一些元件的创建。本章将讨论这些基类。
Sinks是GStreamer中的一类特殊元件。这是因为sink元件必须关注预滚动(preroll), 这是一个数据准备的过程,当元件切入 GST_STATE_PAUSED
状态后缓冲区必须已准备好。这样该元件就可以在进入GST_STATE_PLAYING
状态后立即开始处理数据,而不用再花费时间来初始化输出或设置解码器;所有的一切都已在进入GST_STATE_PAUSED
状态前做好了。
然而,预滚动是一个复杂的过程,它要求许多元件都要具备一段相同的代码。因此,sink元件可以继承 GstBaseSink
基类,该基类已经自动实现了预滚动以及其它一些实用的函数。继承的类只需实现一系列虚函数就可以自动工作了。
然而,GstBaseSink
对元件做了一些限制:
· 它要求sink元件只有一个sink衬垫。需要多个sink衬垫的元件不能使用该基类。
· 基类拥有pad的支配权,并且指定了诸如caps协商,数据处理,衬垫分配这些函数。如果这些虚函数接口满足不了你的要求,那么你不能使用该基类。
· 通过实现pad_allocate ()
函数,上游元件有可能使用特殊内存,譬如只有sink元件可以分配的X服务器端的内存,或者内核中用mmap ()
映射的一段硬件内存。注意,在几乎所有情况下,你将必须继承GstBuffer
对象,这样当缓冲区被销毁时你自己的函数集才会被调用。
Sink元件可以使用GObject
的继承机制从GstBaseSink
类中继承,或者使用方便的GST_BOILERPLATE ()
宏:
GST_BOILERPLATE_FULL (GstMySink, gst_my_sink, GstBaseSink, GST_TYPE_BASE_SINK);
[..]
static void
gst_my_sink_class_init (GstMySinkClass * klass)
{
klass->set_caps = [..];
klass->render = [..];
[..]
}
继承GstBaseSink
类有众多优点:
· 这种派生的实现方式只需要你意识到预滚动的存在而不需要知道预滚动的技术实现,基类完成了所有的这些工作。
共享代码(因此共享bug的修正),在派生类中只需写少量的代码。
同样存在一些特殊的音频和视频的特殊基类,让我们看一下。
事实上,音频sink元件的实现只是通用sink元件的一个特例。你可以从两个音频基类中选择继承,这取决于你的需求: GstBaseAudiosink
和GstAudioSink
。Baseaudiosink提供了关于同步和调度处理的完整控制,这是通过在继承函数中使用其提供的环缓冲实现的。Audiosink基类是baseaudiosink的一个派生类,实现了一个标准的环缓冲来进行缺省同步并提供了一个标准的音频采样时钟。从该基类派生的类仅仅需要提供一个_open ()
,_close ()
和_write ()
函数的其其它可选函数的实现。这应该可以满足许多声音服务器以及大多接口的需要了。要求更苛刻的音频系统,譬如Jack,将会要实现GstBaseAudioSink
基类。
GstBaseAusioSink
只有很少甚至没有任何限制,这应该可以满足任何实现的要求,但是这比较难实现。另一方面,GstAudioSink
只适合那些只要简单的实现open ()
/ close ()
/ write ()
接口的系统(事实上大多数系统都这样),但是它的实现非常简单。第二个基类的好处有很多:
· 不需要在派生类中写任何代码就可以做到自动同步。
· 也自动提供了一个时钟,这样其它sink元件(假设音频/视频同时播放)也可以同步。
· 改变一下基类就可以给所有音频sink元件添加新的特性,这样很易于维护。
· 派生类只要实现三个小函数以及一些 GObject
样板代码。
除实现音频基类的虚函数外,派生类也可以(应该)实现 GstBaseSink
的set_caps ()
和 get_caps ()
虚函数以用于协商。
编写视频sink元件可以使用 GstVideoSink
基类,该类继承自GstBaseSink
类。当前它没做任何事但增加了另一个编译依赖,因此所有的派生类将必须实现所有的基本sink虚函数。当这些都成功完成后,这些对用户体验视频sink元件会有一些积极效果:
· 因为预滚动(以及preroll ()
虚函数)的存在,有可能在进入GST_STATE_PAUSED
状态时显示一帧视频。
· 通过给GstVideoSink
,增加新的特性,有可能给视频sink元件增加扩展来影响所有的sink元件,但只需编写一次代码,这是一个巨大的维护收益。
在前些部分,特别是提供随机存取一节中,我们已经了解了一些元件可以提供随机访问。这大多发生在一些需要从一个随机位置读取数据的源元件,譬如文件源。然而,其它源插件更像一个实时源插件,譬如照相机源,一个声卡源等等;它们不可定位也不提供精确到字节的访问。对于所有这些实例,GStreamer提供两个基类: GstBaseSrc
用于提供基本的源的功能,以及 GstPushSrc
,它不是一个精确到字节的源基类。Pushsource也继承自basesource,所以它具有basesource的所有功能。
Basesrc基类自动为其派生类做一些事,因此派生类不再需要关心这些:
· 自动对所有子类实现固定GstBaseSrc
。
· 自动的衬垫激活处理,以及当我们自己开始一个作业时的作业封装(task-wrapping)。
但GstBaseSrc
可能不会适用于所有情况,它有一些局限:
· 它有且只有一个源衬垫。需要多个源衬垫的元件将不能使用该基类。
· 因为衬垫的所有权属于基类,所以派生类只有在虚函数许可时才可操纵它,你只能使用虚函数所提供的功能。如果你需要更多功能,你不能使用该类。
有可能使用特殊内存,例如X服务器的内存指针或用mmap ()
映射的内存区域,作为create()
虚函数返回的数据指针。大多数情况下,你需要继承 GstBuffer
这样当缓冲区销毁时你自己的函数集才会被调用。
音频源只是pushsource的一个特例。音频源是那些读取音频的东西,譬如一个读取声音服务器的源,一个内核接口(例如ALSA)或一个测试的声音/信号发生器。GStreamer提供了两个基类,和写一个音频sink中描述的很相似;一个基于环缓冲,需要派生类自己处理调度,同步等问题。另一个叫做GstAudioSrc
,它基于 GstBaseAudioSrc
,提供了一个简单的 open ()
,close ()
和 read ()
接口,它相当易于实现并且能够满足大多声音服务器源和音频接口(例如ALSA或OSS)的需求。
继承GstAudioSrc
基类有许多好处,这是因为GstAudioSrc
有以下功能:
· 同步并提供一个时钟。
· 可以增加新的特性并且它们会自动运用到派生类上。
GStreamer提供的第三个基类是 GstBaseTransform
类。这个基类用于像滤镜一样只有一个源和一个sink衬垫的元件,譬如音量调节,音频重采样,音频格式转换等等。已经存在相当多的记录说明此类元件该如何正确的分配转发,传递,处理缓冲区。该类已替你做好了这些,因此你只需进行实际的处理工作。
因为GstBaseTransform
是基于1-to-1滤镜模型的,它可能不适用于像解码器那样可能需要从流中解析属性的元件。而且它不能用于那些需要多个源或sink衬垫的元件。
分流器是一种1-to-N(一个sink衬垫,多个源衬垫)的元件,需要特别谨慎。它们的职责是对原始的未解析的数据打上时间戳生成初步的视频或音频流,你可以对其做许多优化,但也可能做错许多事。这里会提及一些错误做法并提供一些通常的解决方案。解析器是只有一个源衬垫的分流器。它们也只是将流分解成缓冲区,而不改变数据。
正如在前面的Caps协商中所提及的,分流器应该使用固定caps,因为它们的数据类型不会改变。
正如在不同的调度模式一章中所讨论的,分流器元件可以有多种模式:
· 它们可以运行自己的任务,充当管道的驱动者。这种模式特别适用于需要随机访问的元件,例如AVI分流器。
· 它们也可以运行在push-based模式下,即上游元件来驱动管道。这种模式适用于那些从网络上过来的流,譬如Ogg。
另外,带有一个输出的音频解析器理论上也可以做成随机访问的模式。即使大多数情况下你的元件只接受一种模式也可以完成简单的回放,为了能和各种各样的应用程序,譬如编辑器,配合工作,它也可能要求实现多种模式。而且如果你实现了多种模式性能可能会得到提升。参看Different scheduling modes来了解一个元件如何接受多种调度模式。
之前的 13章和 不同的调度模式中都已提及了N-to-1元件。关于N-to-1元件最值得一提的是它的每一个衬垫都是都是push-based且运行在他自己的线程里,N-to-1元件通过时间戳来同步这些流。在其自己的线程中,N-to-1元件通过时间戳来同步所有的流。这意味着所有的流都期待一个能提供最早下一个时间戳的缓存。当流通过一个缓存时,最早下一个时间戳被计算,然后退回到先前的位置,直到所有的流都结束。在Gstreamer中有个可以做此事的基类 GstCollectPads
。
注意,该类仅能帮你从每个输入中得到缓存再给你拥有最早时间戳的那个缓存。如果你想做一些更复杂的事情,像“只抓取拥有最早时间戳的缓存”或其它类似的事情,你需要自己来处理这些需求。
管理器是增加一个功能或统一其它(一系列)元件功能的元件。管理器通常是一个拥有一个或多个精灵衬垫(ghostpads)的GstBin
。它们内部的元件才是起作用的部分。管理器在一些情行下是有用的,例如:
· 增加支持其它元件的拥有自定义的事件处理器的私有事件。
· 增加支持其它元件的自定义的衬垫的_query ()
或_convert ()
事件处理器。
· 在其它元件的数据处理器(通常是_chain ()
函数)之前或之后增加自定义的数据处理器。
· 将一个或一系列元件嵌入到在外界看来是一个单一的元件的东西中。
创建一个管理器是很简单的。你可以继承 GstBin
,大多数情况下,你可以将所需的元件放入_init ()
,也可以在此设置精灵衬垫。如果你需要自定义数据处理器,你可以使用信号或包含另一个你可以控制的元件。
[h1]DirectShow是微软公司在ActiveMovie和Video for Windows的基础上推出的新一代基于COM(Component Object Model)的流媒体处理的开发包,与DirectX开发包一起发布。
[h2]plugin_init [h2]是一个特殊的函数,它在插件被加载时就立即被调用,而且它应该根据自己是否被加载并初始化,以及所有的依赖关系是否正确而返回TURE或FALSE。而且在这个函数中必须注册插件支持的所有element类型。
[93]数据流入的pad
[94]在pad上做数据的控制
[95]数据流出的pad
[96]不清楚