gstreamer插件指南
https://blog.csdn.net/sinat_28502203/article/details/46010485
GStreamer插件开发指南 (0.10.9.1)
I. 介绍
GStreamer是一个用来创建流媒体应用程序的非常强大和通用的框架。GStreamer框架的许多优点来源于它的模块性 :GStreamer可以无缝地接纳新的插件模块。但是由于模块性和强大的功能通常以极高的复杂性为代价(例如CORBA),编写一个新的插件并不总是一件简单的事。
2.3. 数据(Data),缓冲区(Buffers)和事件(Events)
Chapter 1. 前言
1.1. GStreamer是什么?
GStreamer是一个创建流媒体应用程序的框架。其基本设计思想来自于俄勒冈(Oregon)研究生学院有关视频管道的创意, 同时也借鉴了DirectShow[h1] 的设计思想。
1.2. 谁应该读该指南?
该指南叙述了如何为GStreamer编写新的模块。本指南针对以下几种人:
· 那些想给 GStreamer增加新的数据处理方法的人。例如,可能有人想创建一个新的可见的工具---数据类型转换器,或者新的编码器或者解码器。
· 那些想支持新的输入输出设备的人。例如:某人可能会想增加将数据输出到新的视频输出系统或从一个数码相机或耳机中读取数据的功能。
· 那些想通过任何方法扩展GStreamer的人。你必须事先了解插件系统是如何工作的,这样你才能知道插件系统对其它代码有什么限制。读完了该手册,你也许会因为插件可以做如此多的事而感到惊讶。
1.3. 预备知识
1.4. 该指南的结构
为了帮助你驾驭这份指南,我们将其分为几个大的部分。每一部分致力于一个特定的GStreamer插件开发相关的广泛概念。该指南的所有部分按照以下顺序组织:
· 编写一个插件 ── 介绍插件的结构,并用一个音频滤镜作为演示。
· 高阶滤镜概念 ── GStreamer插件开发的高级特性信息。
下一步,我们将在Chapter 12中讨论GStreamer媒体标识。你将学到怎样定义新的媒体类型及得到GStreamer内部的标准媒体类型列表。
接着,在Chapter 14,将解释GStreamer中的时钟概念。如果你的element中需要同步音视频,你必须知道这些信息。
附录中包含一些不适合在手册的其它章节讲述的信息,这些大部分还没有完成。
· 通过扩展GstBin创建复杂的element,这样可以创建包含其它插件的插件。
· 通过在类型侦测函数(typedetect functions)中加入新的mime类型,可以让你的插件处理全新的媒体类型。
Chapter 2. 基础概念介绍
2.1. 元件和插件
查阅GStreamer库索引来获取GstElement
和GstPlugin
的当前实现细节。
2.2. 衬垫(Pads)
大部分情况下,所有在GStreamer中流经的数据都遵循一个原则。数据从element的一个或多个源衬垫流出,从一个或多个sink衬垫流入。源和sink元件分别只有源和sink衬垫。
查阅GStreamer库索引来获取GstPad
的当前实现细节。
2.3. 数据,缓冲区和事件
GStreamer中的所有数据流被分割成一块一块的,并从一个元件的源衬垫传到另一个元件的sink衬垫。数据就是用来承载一块一块数据的数据结构。
· 一个类型域标识该数据的准确类型(control,content,...)。
· 一个指示当前有多少元件引用缓冲区的引用计数器。当计数器的值为0时,缓冲区将被销毁,内存被释放(更详细的细节看下面)。
当前存在两种数据类型:事件(control)和缓冲区(content)。
2、缓冲区可以包含两个相连接的pad所能处理的任何数据。通常,一个缓冲区包含一块音频或视频数据块,该数据块从一个元件流向(push、pull)另一个元件。
缓冲区同样包含描述缓冲区内容的元数据(metadata)。一些重要的元数据类型有:
事件将在Chapter 19一章中广泛讨论。在那之前,只会涉及流结束(EOS)事件,该事件通常在文件结束时发出,标识流的结束。
查阅GStreamer库索引来获取GstMiniObject
, GstBuffer
和GstEvent
的当前实现细节。
2.3.1. 缓冲区的分配
缓冲区是一块可以存放各种数据的内存。缓冲区的内存一般用malloc()函数分配。这样虽然很方便,但不总是最高效,因为数据经常需要被显式的拷入缓冲区。
2.4. MIME类型(Mimetypes)和属性
2.4.1. 基本类型
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. 编写测试程序
Chapter 3. 构建样板(Boilerplate)
3.1. 获取GStreamer插件模板
shell $ cvs -d:pserver:anoncvs@cvs.freedesktop.org/cvs/gstreamer login
Logging in to :pserver:anoncvs@cvs.freedesktop.org:/cvs/gstreamer
CVS password: [ENTER]
shell $ cvs -z3 -d:pserver:anoncvs@cvs.freedesktop.org:/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
...
3.2. 使用项目戳(Project Stamp)
要使用make_element,首先开启一个终端窗口,进入gst-template/gst-plugin/src目录,然后运行make_element命令。make_element的参数为:
2. 本工具使用的源文件名,缺省使用gstplugin.{c,h}。
shell $ cd gst-template/gst-plugin/src
shell $ ../tools/make_element ExampleFilter
最后一个命令创建了两个文件: gstexamplefilter.c和 gstexamplefilter.h。
3.3. 检查基本代码
Example 3-1. Example Plugin Header File
#include <gst/gst.h>
/* 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);
3.4. GstElementDetails
GstElementDetails结构给出了元件的一个层次类型,一个有可读性的描述,以及作者和版本信息。该结构的成员有:
· 元件作者的名字,可选的跟一个由尖括号括起来的email地址。
static GstElementDetails my_filter_details = {
"An example plugin",
"Example/FirstExample",
"Shows the basic structure of a plugin",
"your name <your.name@your.isp>"
};
元件细节在_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);
}
3.5. GstStaticPadTemplate
一个GstStaticPadTemplate是一个element将(也许)会创建并使用的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")
);
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));
}
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的一章里,我们将看到如何用类型来得到流的准确格式。
3.7. Plugin_init函数
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/"
)
Chapter 4. 指定pad
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;
}
4.1. setcaps函数
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;
}
Chapter 5. 链函数(The chain function)
所有的数据处理在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);
}
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);
}
Chapter 6. 什么是状态(States)?
一个状态用来描述元件实例是否被初始化了,是否已经准备好要传输数据了,以及当前是否在处理数据。GStreamer中一共定义了四个状态:
从现在开始,我们分别简单的用"NULL", "READY", "PAUSED"和"PLAYING"来指代这四个状态。
GST_STATE_NULL
是element的缺省状态。这种状态下,没有分配任何运行时资源,也没有加载任何运行时库,显然此时不能处理数据。
6.1. 管理滤镜状态
如果你使用一个上述有的基类,你基本不需要自己去处理状态的改变。你所要做的所有工作就是重载基类的start()和stop()虚函数(也许会根据基类做不同的调用),基类将会为你做好所有的工作。
一个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;
}
Chapter 7. 增加参数
/* 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;
}
}
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));
[..]
}
Chapter 8. 信号
Chapter 9. 编写测试程序
在连接时,你的测试程序可以使用固定的或过滤的cap来驱动一个特殊数据类型进出你的element。这是检查你的element的多种输入输出类型的一个非常简单而高效方法。
不要忘了在你的插件或测试程序中清理内存。当转变成NULL状态时,你的元件应该清理内存和高速缓存。并且,它应该关闭可能的支持的库的引用。你的程序应该unref ()
管道并确保它不会崩溃。
#include <gst/gst.h>
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 <mp3 filename>\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;
}
至此,你应该已经可以创建具有接收和发送数据能力的基本滤镜 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)
Chapter 10. Caps协商
10.1. Caps协商实例
为了caps协商在非固定连接上能正确工作,pads可以选择性的实现一个函数来告诉同伴elements所支持或喜欢的格式。当上游再协商被触发时,这变得很重要。
10.2. 固定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, <bytes-per-frame>,
"rate", G_TYPE_INT, <samplerate>,
"channels", G_TYPE_INT, <num-channels>, 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协商 ,这将在下面的几个小节中解释。
10.3. 下游caps协商
当一个源衬垫上需要设置一个格式来配置输出格式时就要进行下游协商,但是这个元件运行再协商是因为它的格式是在sink衬垫的caps上配置的,或者是因为它支持多格式输出。实际的再协商要求有些细微的差别。
10.3.1. 对嵌入到输入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;
}
让我们看一个例子,这个元件可以转换流的采样率,这样输入输出采样率就不必一致了:
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);
}
10.3.2. 解析和设置caps
10.4. 上游caps(再)协商
上游协商主要用于再协商一个已经(部分)协商好的管道去支持新的格式。一些实际的例子包括视频窗口大小改变后选择一个不同的视频尺寸,并且视频输出不具有改变大小的能力,或者因为音频频道配置改变了。
· 元件应该实现一个"padalloc"-函数以便再协商的时候可以改变格式。这对于滤镜和转换器也是一样的。
· 元件应该使用gst_pad_alloc_buffer
()
函数分配新的缓冲区。
· 可再协商的元件也应该在它们的源衬垫上实现一个 "setcaps"-函数。
不幸的是,这边所讨论的所有细节还没有完全解决,所以这份文档还未完成。FIXME。
10.5. 实现一个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仓库中的同类型的其它元件看它们是怎样做到你所要做的。
Chapter 11. 不同的调度模式
11.1. 衬垫激活阶段
在接下来的两部分,我们将深入pull-based调度(元件/衬垫驱动管道,元件/衬垫提供随机访问),并且一些特定的场景将会给出。
11.2. 衬垫驱动管道
· 特定的需要控制输入流的音频输入,如Jack sound 服务器。
#include "filter.h"
#include <string.h>
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);
}
11.3. 提供随机访问
· 能在整个管道中提供类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;
}
Chapter 12. 类型和属性
元件在传送数据时有一系列的可能的数据类型集合。事实上,每一个定义的新元件可能会使用一种新的数据结构(虽然:除非至少有一个别的元件能识别这种结构,否则该结构可能一无是处,因为没有元件能和它连接上)。
· 如果要创建一个新类型,先与 GStreamer 开发人员讨论,可以通过这些方式:IRC, 邮件列表。
· 保证了上面这些,当你想要创建一个新类型时,你需要明确指出来,并将它加到已知类型的列表中,这样其它开发人员才能在他们写他们自己的元件时能正确的使用你的类型。
12.1. 创建一个简单的格式
12.2. Typefind函数和Autoplugging
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;
}
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类型 |
描述 |
属性 |
属性类型 |
属性值 |
属性描述 |
还未定义 |
Chapter 13. 请求(Request)和间或(Sometimes)衬垫(pads)
13.1. 间或衬垫
下面的示例代码将会解析一个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);
}
13.2. 请求衬垫
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;
}
Chapter 14. 时钟机制
当播放复合媒体的时候,每个声音样本和图象样本必须在指定的时间按照指定的秩序播放。为此,gstreamer提供了一个同步机制。
14.1. 时间类型
14.2. 时钟
因为时钟返回绝对时间刻度,所以他们通常不被直接使用。相反的,对时钟的引用被存储在任何需要它的元件中,并且gstreamer在内部使用它来计算元件时间。
14.3. 元件和时间之间的数据流
管道开始播放。 源元件通常知道每个样本的播放时间。[1] 首先,源元件发出一个非连续的事件。这个事件载有关于下个样本的相对于当前的时间。这个相对时间是多变的(无规律的),但必须和放入缓冲器的时间戳相符合。在流媒体开始的时候,或是涉及的各个媒体在任何有意义的(阶段),相对时间都是可以预计的。当接收它时,其他的元件调整它们的元件时间的偏移量,使这个时间和写在事件中的时间相匹配。
如果流被查找,那么下个被发出的样本将拥有一个没有根据元件时间校准过的时间戳。因此,源元件必须发出一个非连续的事件。
注释
有时,它是一个知道时间的解析器元件。例如,如果一个管道拥有一个被MPEG解码器元件连接filesrc元件,前者(MPEG解码器元件)知道每个样本的(播放)时间,因为,每个样本何时被播放的信息被嵌入了MPEG的格式里面。在这种情况下,这个元件会被看成源元件来讨论。 |
14.4. 每个元件的责任
让我们澄清,在管道中gstreamer和每个元件之间的契约.
14.4.1. 源元件
为了初始化一个静止的管道中的元件时间,在开始播放之前,源元件必须发出一个非连续的事件。另外,在查找后,必须发送一个非连续的事件,因为下个元件的时间戳和静止的管道的元件时间不匹配。
14.4.2. 接收元件
如果元件为了在一个特定的时间放出样本(实时播放),元件将要求一个clock(时钟),从而实现方法 set_clock
。
此外,在播放每个样本前,如果当前的元件时间小于样本中的时间戳,它将调用 gst_element_wait()
等待,直到当前时间达到(样本时间戳)。[1]
注释
使用某些调度(模式), |
Chapter 15. 支持动态参数
15.1. 初始化
控制器子系统包含在 gstcontroller 库中。你需要在你元件的源代码中包含下面的头部:
...
#include <gst/gst.h>
#include <gst/controller/gstcontroller.h>
...
即使gstcontroller 库可能已经连接到主程序中,你还是需要确保它已经在你的 plugin_init 函数中被初始化:
static gboolean
plugin_init (GstPlugin *plugin)
{
...
/* initialize library */
gst_controller_init (NULL, NULL);
...
}
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));
15.2. 数据处理循环
上一节我们知道如何标记GObject参数为可控。应用程序开发人员能够将可控参数中改变排队。控制器子系统的这种处理方法使得插件能对那些被取出的改变进行处理。这仅通过一步:
gst_object_sync_values(element,timestamp);
这个调用使得所有在给定的时间戳的参数改变值通过适应元件的GObject属性来被激活。具体取决于元件检测到的同步率。
15.2.1. 视频元件的数据处理循环
对于视频处理元件,最好的方式是对每帧进行同步。这意味着程序员可以通过gst_object_sync_values()
调用来进行元件的数据处理,该函数在上部分提到过。
15.2.2. 音频元件的数据处理循环
使用一个特定控制速率的元件需要在同步每n个样本时打破它们的数据处理循环。
Chapter 16. MIDI
Chapter 17. 接口
在前面部分,增加参数章中,我们介绍了控制对象行为的GObject属性的概念。这个东西非常有用,但它有两个明显的缺陷:首先,它太泛泛;其次,它不是动态的。
请记住最重要的一点:接口不是要取代属性。更恰当地讲,接口是属性的 锦上添花。有两方面可以说明这点:首先,属性可以存储在XML文件中。其次属性可以通过命令行(gst-launch)来得到其详细说明。
17.1. 如何实现接口
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 */
}
17.2. URI interface
17.3. 混合器接口
下面的例子显示了一个N-1元件中的混合器接口实现。这里并没有给出混和流的实际处理过程,因为该过程对于本手册而言显得太复杂,也不适合该手册的主题。
#include <gst/mixer/mixer.h>
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;
}
17.4. 谐调器(Tuner)接口
该接口需要 GstImplemensInterface
接口才能正常工作。
下面的例子显示了如何在一个元件中实现谐调器接口。其中并没有给出实际的选择流的过程,因为这与本节内容没有太大关联。
#include <gst/tuner/tuner.h>
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;
}
17.5. Color Balance Interface
17.6. 属性探测(Property Probe)接口
下面是一个针对音频过滤元件的属性探测的例子;它将会为 "silent" 属性探测所有允许的值。实际上,该值是 gboolean 型,因此没有很好的实际意义。再次声明,这只是一个例子。
#include <gst/propertyprobe/propertyprobe.h>
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 ()
函数来处理。
17.7. X Overlay接口
你可能已经猜到主动模式仅意味着发送一个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);
...
}
17.8. Navigation Interface
Chapter 18. 标签(元数据和流信息)
GStreamer中一个读取标签的元件叫 TagGetter
。 一个写标签的元件叫TagSetter
。一个支持上两种操作的元件可以用做标签编辑器来快速改变标签。
18.1. 在流中读取标签
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);
}
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);
[..]
}
18.2. 将标签写入流
下面的例子将会从应用程序和管道收到标签,并将它们混合在一个列表中再将该列表写到输出流中。这段代码实现了标签设置器,这样应用程序能设置标签,并在到来的事件中收到管道标签。
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都等待数据到来(因为标签通常在第一个数据缓存之前到来),一旦数据到来便处理数据。
19.2. 上游事件
最常见的上游事件是定位事件(seek events)和服务质量事件(QoS events)。
· 如果你的元件中有多个sink衬垫。在这种情况下你将不得不决 定通过哪个sink衬垫将事件发出(如果不通过所有的)。
· 如果你需要在本地处理事件。例如在你发送上游导航事件前你 想要转换它,或者你想处理一个QoS事件。
你在事件处理器中所做的操作不是很要紧,但是你必须考虑一些重 要的规则,因为一个元件的糟糕的事件处理器将破坏整个管道的事件处理。这些规则如下:
· 始终使用 gst_pad_event_default
转发你不想处理的上游事件。
· 如果你要根据你收到事件创建一个新的事件,不要忘了用 gst_event_unref来减少你收到的事件的引用。
· 事件处理函数会被假定返回TRUE或FALSE来表示事件是否被处 理了。永远不要简单的返回TRUE或FALSE除非你真的知道你以及处 理了哪个事件。
19.3. 事件汇总
要获取关于事件的更全面的信息以及怎样在各种情况下正确使用它们,请查阅GStreamer设计文档。该小节仅仅给出一个总揽。
19.3.1. 流结束标志 (EOS)
流结束事件没有任何属性,这使得它是GStreamer中最简单的事件之一。它用 gst_event_new_eos()
函数创建。
19.3.2. 冲刷开始
19.3.3. 冲刷结束
冲刷结束事件由驱动管道的元件发送,发送时机是在冲刷开始事件后,它告诉下游元件它们可以再次接收其它事件和缓存。(在第一个缓存通过之前,至少有一个NEWSEGMENT事件)。
如果你的元件还持有流数据的缓存,它应该在接收到FLUSH-STOP事件后清除该缓存。(对于任何时刻其链函数接收到一个有DISCONT标志的缓存也是如此)
flush-stop事件通过 gst_event_new_flush_stop ()
被创建。像EOS事件一样,它也没有属性。
19.3.4. 新的片段
当通告数据流中的新数据片或用新值更新当前片段都会发送一个新片段事件。一个新片段事件总是在第一个缓存被冲刷之前(见上)。 >
新片段事件同样可以用来指示流中的‘陷阱’,例如字幕流可能没有对整个视频流一一对应的数据。这可以通过更新当前片段的起始位置来应付这个缺陷(详情参见设计文档)。
newsegment事件通过函数 gst_event_new_new_segment ()
被创建。该函数的参数情况请参见API参考手册和设计文档。
19.3.5. 定位请求
元件可以通过gst_event_parse_seek()
来解析该事件。
19.3.6. 导航
导航事件通常由视频sink向上游元件发送,它用来通知上游元件鼠标位置。当鼠标点击事件发生或键盘被按下或松开,该事件便会发生。
所有这些信息都在由gst_event_get_structure ()
得到的一个事件结构中。
检查gst-plugins-good中的导航元件是一个得到该事件信息的办法。
19.3.7. 标签(元数据)
元件可以通过函数 gst_event_parse_tag ()
得到事件包含的标签列表从而来解析该事件。
迄今为止,我们已看到了许多可以融入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. 编写一个管理器
Chapter 20. Pre-made基类
20.1. 编写一个sink
· 它要求sink元件只有一个sink衬垫。需要多个sink衬垫的元件不能使用该基类。
· 基类拥有pad的支配权,并且指定了诸如caps协商,数据处理,衬垫分配这些函数。如果这些虚函数接口满足不了你的要求,那么你不能使用该基类。
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 = [..];
[..]
}
· 这种派生的实现方式只需要你意识到预滚动的存在而不需要知道预滚动的技术实现,基类完成了所有的这些工作。
共享代码(因此共享bug的修正),在派生类中只需写少量的代码。
20.1.1. 编写一个音频sink
· 也自动提供了一个时钟,这样其它sink元件(假设音频/视频同时播放)也可以同步。
· 改变一下基类就可以给所有音频sink元件添加新的特性,这样很易于维护。
· 派生类只要实现三个小函数以及一些 GObject
样板代码。
除实现音频基类的虚函数外,派生类也可以(应该)实现 GstBaseSink
的set_caps ()
和 get_caps ()
虚函数以用于协商。
20.1.2. 编写一个视频sink
· 因为预滚动(以及preroll ()
虚函数)的存在,有可能在进入GST_STATE_PAUSED
状态时显示一帧视频。
· 通过给GstVideoSink
,增加新的特性,有可能给视频sink元件增加扩展来影响所有的sink元件,但只需编写一次代码,这是一个巨大的维护收益。
20.2. 编写一个源
Basesrc基类自动为其派生类做一些事,因此派生类不再需要关心这些:
· 自动的衬垫激活处理,以及当我们自己开始一个作业时的作业封装(task-wrapping)。
但GstBaseSrc
可能不会适用于所有情况,它有一些局限:
· 它有且只有一个源衬垫。需要多个源衬垫的元件将不能使用该基类。
· 因为衬垫的所有权属于基类,所以派生类只有在虚函数许可时才可操纵它,你只能使用虚函数所提供的功能。如果你需要更多功能,你不能使用该类。
20.2.1. 写一个音频源
继承GstAudioSrc
基类有许多好处,这是因为GstAudioSrc
有以下功能:
20.3. 编写一个转换元件
因为GstBaseTransform
是基于1-to-1滤镜模型的,它可能不适用于像解码器那样可能需要从流中解析属性的元件。而且它不能用于那些需要多个源或sink衬垫的元件。
Chapter 21. 编写一个分流器(Demuxer)或解析器(Parser)
正如在前面的Caps协商中所提及的,分流器应该使用固定caps,因为它们的数据类型不会改变。
正如在不同的调度模式一章中所讨论的,分流器元件可以有多种模式:
· 它们可以运行自己的任务,充当管道的驱动者。这种模式特别适用于需要随机访问的元件,例如AVI分流器。
· 它们也可以运行在push-based模式下,即上游元件来驱动管道。这种模式适用于那些从网络上过来的流,譬如Ogg。
Chapter 22. 编写一个N-to-1元件或混合器(Muxer)
注意,该类仅能帮你从每个输入中得到缓存再给你拥有最早时间戳的那个缓存。如果你想做一些更复杂的事情,像“只抓取拥有最早时间戳的缓存”或其它类似的事情,你需要自己来处理这些需求。
Chapter 23. 编写一个管理器(Manager)
管理器是增加一个功能或统一其它(一系列)元件功能的元件。管理器通常是一个拥有一个或多个精灵衬垫(ghostpads)的GstBin
。它们内部的元件才是起作用的部分。管理器在一些情行下是有用的,例如:
· 增加支持其它元件的自定义的衬垫的_query ()
或_convert ()
事件处理器。
· 在其它元件的数据处理器(通常是_chain ()
函数)之前或之后增加自定义的数据处理器。
· 将一个或一系列元件嵌入到在外界看来是一个单一的元件的东西中。
[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]不清楚
gstreamer插件指南相关推荐
- gstreamer插件开发指南(一)
翻译自:https://gstreamer.freedesktop.org/documentation/plugin-development/index.html 1 简介 GStreamer是一个非 ...
- 编写一个GStreamer插件
前面章节对GStreamer做了概述,不过我们最终用到主要是插件,下面我们对插件做一个简单介绍,大部分内容都是copy的,并非原创,主要用于学习记录,英文好的可以看官方文档,我和官方校对过,翻译的大体 ...
- gst-crypto GStreamer插件
gst-crypto GStreamer插件 内容 • 1. gst-crypto概述 o 1.1gst-crypto GStreamer插件功能 o 1.2用例范例 • 2. GStreamer插件 ...
- GStreamer插件实列rockchipmpp
尽管这些年arm发展取得了不少的进步,不过对于音视频的编解码仍然心有余力不足,好在芯片厂家在SOC里面提供了硬件加速能力.善于发挥出芯片的能力,才能打造出完美的应用.今天我们一起来探索一下rk3568 ...
- Gstreamer 插件黑名单问题
本文是基于gstreamer-1.0版本来介绍 背景介绍 无论是在PC上还是在开发板上,第一次安装gstreamer-1.0的时候,gst-plugin_scanner会扫描系统内集成的gstream ...
- 【GStreamer 】3-1 gstreamer插件之 videotestsrc 介绍
目录 编辑 1.简介 2.videotestsrc 3.videotestsrc 不同pattern参数测试罗列 3.1 (0): smpte - SMPTE 100% color bars 3.2 ...
- Gstreamer插件教程1.1—介绍(Introduction):前言(Preface)
英文原文:https://gstreamer.freedesktop.org/documentation/plugin-development/introduction/preface.html Gs ...
- GStreamer插件:fakesink (黑洞插件)
fakesink:是一个吞噬任何数据的伪插件(黑洞插件),类似于Linux的/dev/null 伪设备,用于丢弃不需要的数据. 场景一:当需要使用音响设备播放视频的时候,视频流对于音响设备是毫无意义的 ...
- 直接拿来用!VS Code 最强插件指南
作为一款优秀的代码编辑器,Visual Studio Code 中最常用的插件有哪些? 作者 | Adrian Hajdin,JavaScript 开发者 译者 | 虎说 责编 | 屠敏 出品 | C ...
- IDEA——必备插件指南
目录 一.Free-Mybatis-Plugin 二.Lombok 三.jclasslib Bytecode Viewer 一.Free-Mybatis-Plugin 二.Lombok 三.jclas ...
最新文章
- java修饰静态_Java-static修饰符及静态代码块
- Pycharm+Django搭建第一个Python Web程序
- windows 2008 64位oracle11g部署问题(3)之ORA-12514:TNS:监听程序当前无法识别连接描述符中请求的服务...
- git本地仓库关联远端仓库
- MyBatis基本运行环境
- base标签在ie6下的恶心问题
- python多维数据分析_使用python进行数据分析
- 浅谈Java内存模型——JVM
- JavaSE IDEA 使用-面向对象
- IDEA 字符编码转换问题
- 使用 Java 下载FTP文件
- 【uni-app】第三方ui组件推荐引入的方法
- RGB和CMYK配色表
- minigui[基础篇][11]—— 图标
- 视频教程-JavaScript全套课程-JavaScript
- 文字在div中水平居中,垂直居中
- 【整理】学习Android Studio时遇到的错误及解决方法(持续更新)
- 在树莓派3B+上部署Intel NCS2神经网络计算棒
- 前谷歌员工推Cuil 获3300万风投对战老东家
- Qt论坛和博客网址大全