使用 GStreamer 处理多媒体内容





5.00/5 (1投票)
本文介绍了 GStreamer 工具集,并解释了如何编写读取和处理多媒体内容的应用程序。
随着多媒体设备的普及,构建能够读取和处理其内容的应用程序变得越来越必要。有许多可用的编程库,但 GStreamer 是少数可以处理各种音频和视频数据的开源工具集之一。它是免费提供的,可在多个操作系统上运行,并提供令人印象深刻的性能和广泛的文档。
在我看来,使用 GStreamer 只有两个缺点。首先,它的数据结构(pad、caps、bus、element)与我以前遇到的任何东西都不同。其次,它是用 C 语言编写的,因此您必须处理诸如 gst_bus_timed_pop_filtered
之类的冗长函数名。
本文的目标是介绍 GStreamer 的用法和功能。它首先解释了如何安装软件包,然后展示了如何从命令行执行操作。最后一部分介绍了 GStreamer 的基本数据结构,并演示了如何使用它们。
1. 安装
GStreamer 的主站点是 gstreamer.freedesktop.org。安装过程取决于您的操作系统,您可以通过访问 GStreamer 站点并执行四个步骤来查找说明:
- 点击左侧的“文档”按钮。
- 点击中心的“开始”按钮。
- 打开左上角的“安装 GStreamer”选项。
- 点击与您的操作系统对应的链接。
安装 GStreamer 后,您将拥有几个新的库、头文件和可执行文件。本文的大部分内容都涉及库和头文件,但对于新手,我建议从可执行文件开始。
2. 命令行中的 GStreamer
如果您从未使用过 GStreamer,我建议您在编写代码之前先使用其命令行工具。它们易于使用,一旦您熟悉它们,您将对 GStreamer 有更好的理解。
在我的 Linux 系统上,GStreamer 将其可执行文件安装在 /usr/bin 中。在我的 Windows 系统上,它们位于 C:\gstreamer\1.0\msvc_x86_64\bin 中。在继续之前,最好确保您可以从命令提示符访问这些实用程序(特别是 gst-launch-1.0
),这可能需要更新您的 PATH 环境变量。
要开始,打开命令提示符并执行以下命令:
gst-launch-1.0 audiotestsrc freq=200 ! audioconvert ! lamemp3enc ! filesink location=audio.mp3
这可能需要很长时间,因此请在几秒钟后按 Ctrl-c。命令完成后,您将在当前目录中找到一个名为 audio.mp3 的文件。如果您播放它,您将听到一个对应于 200 Hz 正弦波的低音调。
这并不令人兴奋,但是一旦您了解了命令的结构,您就会很好地理解 GStreamer。gst-launch-1.0
后面的文本称为“管道描述”,因为它描述了应如何处理媒体(在本例中为音频数据)。当您使用管道描述执行 gst-launch-1.0
时,它会创建一个“管道”并执行它。
本质上,GStreamer 应用程序的目标是创建和执行管道。在管道描述方面,有四点需要注意:
- 管道描述由“元素”组成,这些元素标识应如何生成、处理和使用媒体。
- 元素之间用感叹号 (!) 分隔,感叹号代表元素之间的链接。
- 元素的属性可以通过在其名称后加上
name=value
对来设置,其中name
是属性的名称,value
是所需的值。 - 在几乎所有情况下,元素都用 GStreamer“插件”的名称来标识。
考虑到这一点,让我们看一下前面的命令。管道描述包含四个名为 audiotestsrc
、audioconvert
、lamemp3enc
和 filesink
的元素。audiotestsrc
元素有一个名为 freq
的属性,filesink
元素有一个名为 location
的属性。
GStreamer 插件是执行管道中操作的库。这四个元素中的每一个都标识一个插件,当 gst-launch-1.0
执行时,它会调用每个插件来执行其操作。
大多数插件以某种方式处理流数据,但有些插件从源读取数据或生成新数据。这些是“源插件”,在示例命令中,audiotestsrc
是一个生成音频数据的源插件。同样,“接收器插件”消耗或存储流数据。在前面的示例中,filesink 插件将音频数据存储到名为 audio.mp3 的文件中。
filesink
插件是 GStreamer 附带的许多插件之一。这些是“核心插件”,表 1 列出了 filesink
和其他几个核心插件。
插件名称 | 分类 | 描述 |
---|---|---|
filesrc | 源/文件 | 从文件读取数据 |
filesink | 接收器/文件 | 将数据写入文件 |
dataurisrc | 源 | 从 URI 提供数据 |
fdsrc | 源/文件 | 从文件描述符读取数据 |
fdsink | 接收器/文件 | 将数据写入文件描述符 |
fakesrc | 源 | 虚假数据源 |
fakesink | 接收器 | 虚假数据接收器 |
funnel | Generic | N 对 1 流连接 |
标识 | Generic | 不修改地传递数据 |
input-selector | Generic | N 对 1 输入流选择器 |
output-selector | Generic | N 对 1 输出流选择器 |
queue | Generic | 简单数据队列 |
queue2 | Generic | 简单数据队列 |
tee | Generic | 1 对 N 流分流器 |
typefind | Generic | 打印流的媒体类型 |
除了核心插件之外,您还可以安装四个开源 GStreamer 插件集合:
- gst-plugins-base - 可靠、高质量、文档齐全且维护良好
- gst-plugins-good - 质量好且维护良好
- gst-plugins-bad - 质量和/或维护存在缺陷
- gst-plugins-ugly - 可能因许可问题而存在分发问题
gst-plugins-base 包提供了大量插件,分为多个类别,包括 audioconvert、encoding 和 playback。playback 类别中的插件特别有用,表 2 列出了其中许多插件。
插件名称 | 分类 | 描述 |
---|---|---|
decodebin | 通用/容器/解码器 | 将数据解码为原始媒体 |
decodebin3 | 通用/容器/解码器 | 将数据解码为原始媒体 |
playbin | 通用/容器/播放器 | 从 URI 播放媒体 |
playbin3 | 通用/容器/播放器 | 从 URI 播放媒体 |
playsink | 通用/容器/接收器 | 多流的便利接收器 |
subtitleoverlay | 视频/叠加 | 在视频上叠加字幕 |
uridecodebin | 通用/容器/解码器 | 将 URI 解码为原始媒体 |
uridecodebin3 | 通用/容器/解码器 | 将 URI 解码为原始媒体 |
urisourcebin | 通用/容器/源 | 从 URI 下载和缓冲数据 |
其中,playbin
因其能够从 URI 播放流媒体而广受欢迎。为了演示,以下命令从免费的 GStreamer 站点播放 WebM 视频文件:
gst-launch-1.0 playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm
运行此命令时,请记住管道加载、缓冲和播放视频可能需要时间。此外,请注意管道所经历的不同状态,例如 PAUSED
和 PLAYING
。
此时,您应该了解 GStreamer 的目的是执行由元素组成的管道。gst-launch-1.0
实用程序非常适合简单操作,但不适用于复杂媒体处理。要以编程方式创建管道,您需要了解 GStreamer API 及其数据结构。
3. GStreamer API
GStreamer 是用 C 语言编写的,它不是一种面向对象的语言。尽管如此,官方文档仍将 GStreamer 的数据结构称为“类”。文档还描述了一个从 GObject
开始的“继承层次结构”。例如,文档指出 GstElement
是 GstObject
的子类,GstObject
是 GInitiallyOwned
的子类,GInitiallyOwned
是 GObject
的子类。
在 GStreamer 中,继承基于“聚合”。如果结构 X 是结构 Y 的子类,则结构 X 的第一个字段是结构 Y 的实例。例如,GstElement
的第一个字段是 GstObject
的实例,GstObject
的第一个字段是 GInitiallyOwned
的实例。
如果您熟悉传统的面向对象编程,这会显得奇怪和不自然。但是,如果您想了解 GStreamer 的 API,接受这一点至关重要。图 1 展示了 GStreamer 最重要类的继承层次结构。
图 1:继承层次结构中的重要类
在此图中,顶部的两个结构以 G
开头,下面的结构以 Gst
开头。这是因为 GObject
和 GInitiallyUnowned
由 GLib 提供,而其他结构由 GStreamer 提供。同样,以 g_
开头的函数执行 GLib 操作,以 gst_
开头的函数执行 GStreamer 操作。
本节将介绍此图中的阴影结构:GObject
、GstObject
、GstElement
、GstBin
、GstPipeline
、GstBus
和 GstPad
。一旦您了解这些结构的功能,您就可以轻松使用它们来创建和执行管道。
3.1 GObject
GObject
结构是 GLib/GStreamer 层次结构中的基本结构。对于本文,您只需要了解两点:
GObject
存储名为“属性”的名称-值对。GObject
可以发出可由处理程序接收的信号。
关于属性,GLib 提供了两个函数来读取和修改 GObject
的属性:
g_object_get(GObject* obj, const gchar* name, void* val, NULL)
- 将obj
中名为name
的属性复制到val
引用的内存中g_object_set(GObject* obj, const gchar* name, any val, NULL)
- 设置对象的一个或多个属性
在这些签名中,每个函数只接受一个名称/值对。可以追加额外的对,但最后一个参数必须设置为 NULL
终止符。为了演示,以下代码设置名为 gobj
的 GObject*
的 name
和 color
属性:
g_object_set(gobj, "name", "RedObject", "color", "red", NULL);
GStreamer 应用程序通常通过调用以下函数来访问有关结构的信息:
g_signal_emit_by_name(GObject* obj, const gchar* signal, gint index, void* data)
除了存储常规属性之外,许多 GStreamer 结构还在名为 GstTagList
的结构中存储名为“标签”的元数据。当应用程序调用 g_signal_emit_by_name
并带上结构的标签列表的名称时,最后一个参数将指向返回的 GstTagList
。
3.2 GstObject
GstObject
结构是 GStreamer 层次结构的基础结构。应用程序通常不直接访问 GstObject
,但有四个值得了解的函数:
gst_init(int argc, char** argv)
- 初始化 GStreamer 环境gst_object_unref(GstObject* obj)
- 释放GstObject
gst_object_set_name(GstObject* obj, const gchar* name)
- 为对象分配名称gst_object_get_name(GstObject* obj)
- 返回对象的名称
应用程序需要在任何其他 GStreamer 函数之前调用 gst_init
。这可以接受传递给应用程序的命令行参数(argc
和 argv
)。
许多应用程序将声明一个 GstObject
指针变量并通过调用函数来初始化该变量。为了释放内存,应用程序应该为每个已初始化的变量调用 gst_object_unref
。
3.3 GstElement
媒体处理管道中的每个阶段都由一个 GstElement
表示。本节讨论此重要数据结构的三个方面:
GstElement
的字段- 创建
GstElement
的函数 - 访问
GstElement
属性的函数
阅读本节时,您不需要记住每个字段和函数。但您应该学习足够多的知识,以便熟悉 GstElement
的使用方式。
3.3.1 GstElement 字段
要了解 GstElement
的功能,最好熟悉其字段。表 3 列出了其中许多字段及其数据类型。
字段 | 数据类型 | 描述 |
---|---|---|
object | GstObject | 元素的“超类” |
contexts | GList* | 元素的处理任务 |
current_state | GstState | 元素的当前状态 |
next_state | GstState | 元素的下一个状态 |
target_state | GstState | 元素的最终状态 |
srcpads | GList* | 元素的源 pad |
numsrcpads | guint16 | 源 pad 的数量 |
sinkpads | GList* | 元素的接收 pad |
numsinkpads | guint16 | 接收 pad 的数量 |
bus | GstBus* | 连接的总线 |
时钟 | GstClock* | 元素的时钟 |
start_time | GstClockTime | 自上次 PAUSE 状态以来的时间 |
base_time | GstClockTimeDiff | PLAY 状态之前的时刻 |
第一个字段是 GstObject
,它是 GstElement
的超类。下一个字段 contexts
存储元素要执行的操作。第三个字段 current_state
标识元素的处理状态,它可以取以下五个值之一:
GST_STATE_NULL
- 元素的初始状态GST_STATE_READY
- 元素准备进入暂停状态GST_STATE_PAUSE
- 元素准备接受和处理数据GST_STATE_PLAYING
- 元素正在播放数据GST_STATE_VOID_PENDING
- 元素没有待定状态
元素总是先进入 PAUSE
状态,然后才进入 PLAYING
状态。这使得测量播放时间变得简单。
应用程序可以通过元素的 GstBus
访问有关元素的信息。这对于响应错误特别有用,我将很快讨论 GstBus
结构。
3.3.2 创建元素
GStreamer 的元素工厂使创建不同类型的元素变得容易。gst_element_factory_find
函数接受一个名称并返回相应的元素工厂。例如,以下代码访问名为 videotestsrc
的 GstElementFactory
:
factory = gst_element_factory_find("videotestsrc");
应用程序可以通过调用 gst_element_factory_make
从元素工厂创建新的 GstElement
,该函数接受工厂的 ID 和要分配给 GstElement
的名称。为了演示,以下代码从名为 videotestsrc
的工厂创建一个名为 elem
的元素。由于其名称设置为 NULL
,GStreamer 将为其分配一个唯一的名称。
elem = gst_element_factory_make("videotestsrc", NULL);
在这些函数中,参数通常是插件的名称,这意味着新元素是 GStreamer 插件的实例。这将在本文末尾的示例代码中阐明。
3.3.3 访问字段
GStreamer 应用程序通常不直接访问 GstElement
的字段。相反,它们调用读取和修改这些字段的函数。表 4 列出了许多可用函数并提供了每个函数的描述。
函数 | 描述 |
---|---|
gst_element_get_state(GstElement*, GstState*, GstState*, GstClockTime) | 读取元素的当前/待定状态 |
gst_element_set_state(GstElement*, GstState) | 设置元素的当前状态 |
gst_element_get_static_pad(GstElement*, const gchar*) | 返回具有给定名称的 pad |
gst_element_iterate_pads(GstElement*) | 返回元素 pad 的迭代器 |
gst_element_add_pad(GstElement*, GstPad*) | 将给定 pad 添加到元素 |
gst_element_remove_pad(GstElement*, GstPad*) | 从元素中移除给定 pad |
gst_element_get_bus(GstElement*) | 返回元素的总线 |
gst_element_get_clock(GstElement*) | 返回元素的时钟 |
gst_element_set_clock(GstElement*, GstClock*) | 设置元素的时钟 |
gst_element_get_start_time(GstElement*) | 返回自上次暂停状态以来的时间 |
gst_element_get_current_clock_time( GstElement*, GstClockTime) | 返回当前时钟时间 |
大多数这些函数都易于理解。请记住,一个元素可以有任意数量的 pad,但总是从无开始。它总是有一个用于传输数据的总线,但该总线可能不会被使用。
3.4 GstPad 和 Capabilities
在管道描述中,感叹号用于将一个元素连接到另一个元素。这对于简单拓扑很好,但 GStreamer 允许在元素之间创建多个连接,并且在低级别,这些连接是使用“pad”建立的。一个元素可以有零个或多个提供数据(“源 pad”)的 pad 和零个或多个接收数据(“接收 pad”)的 pad。
每个 pad 都有一组“capabilities”(“caps”),用于标识它可以传输的数据的性质。一个 pad 可能具有传输特定格式视频流的能力,而另一个 pad 可能具有传输特定格式音频数据的能力。
在代码中,pad 由 GstPad
实例表示,pad 的 capabilities 集包含在 GstCaps
实例中。表 5 列出了许多与 pad 及其属性相关的函数。
函数 | 描述 |
---|---|
gst_pad_new(const gchar*, GstPadDirection) | 创建一个新的 pad |
gst_pad_new_from_template(GstPadTemplate*, const gchar*) | 从模板创建一个新的 pad |
gst_pad_get_name(GstPad*) | 返回 pad 的名称 |
gst_pad_get_direction(GstPad*) | 返回 pad 的方向 |
gst_pad_get_parent(GstPad*) | 返回包含 pad 的元素 |
gst_pad_set_active(GstPad*, gboolean) | 将 pad 设置为活动或非活动状态 |
gst_pad_get_current_caps(GstPad*) | 返回 pad 的 capabilities |
gst_pad_link(GstPad*, GstPad*) | 在两个 pad 之间创建链接 |
gst_pad_unlink(GstPad*, GstPad*) | 移除两个 pad 之间的链接 |
gst_pad_is_linked(GstPad*) | 标识 pad 是否已链接 |
第一个函数 gst_pad_new
使用给定的名称和方向创建一个新的 GstPad
。第二个参数是 GstPadDirection
,它可以取以下三个值之一:
GST_PAD_UNKNOWN
- pad 的方向未知GST_PAD_SRC
- pad 发送数据GST_PAD_SINK
- pad 接收数据
在 pad 可以传输数据之前,需要将其激活。这可以通过调用 gst_pad_set_active
函数来完成。大多数应用程序通过将元素的状态设置为 RUNNING
来激活元素的 pad。
gst_pad_get_current_caps
函数返回一个包含 pad capabilities 的 GstCaps
实例。如果不同元素中的 pad 具有一个或多个共同的 capabilities,应用程序可以通过调用 gst_pad_link
在它们之间创建链接。
创建两个元素之间链接的另一种方法是使用指向要链接的元素的指针调用 gst_element_link
。调用此函数时,GStreamer 将查找具有相似 capabilities 的未链接 pad 并在它们之间创建链接。另一个用于链接元素的函数如下所示:
gst_element_link_filtered(GstElement* src, GstElement* dest, GstCaps* filter)
如果两个元素具有在 filter
参数中给出的具有相似 capabilities 的未链接 pad,则此函数会在它们之间创建链接。如果链接已建立,则返回 true
,否则返回 false
。
应用程序可以通过调用 gst_element_link_many
在多个元素之间创建链接。此函数接受一系列元素,后跟 NULL
终止符。例如,以下代码在 elemA
和 elemB
之间以及 elemB
和 elemC
之间建立连接。
gst_element_link_many(elemA, elemB, elemC, NULL);
除了 pad 函数之外,GStreamer 还提供了与 pad capabilities 相关的函数。它们的名称以 gst_caps_
开头,表 6 列出了其中八个函数。
函数 | 描述 |
---|---|
gst_caps_new_empty() | 创建新的空 GstCaps |
gst_caps_new_empty_simple(const char*) | 使用给定媒体类型创建新的 GstCaps |
gst_caps_new_simple(const char*, const char*, ...) | 使用给定 GstStructure 创建新的 GstCaps |
gst_caps_get_size(const GstCaps*) | 返回 GstCaps 中的结构数量 |
gst_caps_get_structure(const GstCaps*, guint index) | 返回给定索引处的 GstStructure |
gst_caps_remove_structure(GstCaps*, guint index) | 移除给定索引处的 GstStructure |
gst_caps_append(GstCaps*, GstCaps*) | 将一个 GstCaps 追加到另一个 GstCaps |
gst_caps_unref(GstCaps*) | 释放 GstCaps 内存 |
GstCaps
的每个元素都是一个 GstStructure
,它是一个名称/值对的通用容器。当应用程序使用 gst_caps_new_simple
等函数创建新的 GstCaps
时,它需要提供 GstStructure
中的名称/值对。例如,以下代码创建一个具有处理视频能力的 GstCaps
:
GstCaps *caps = gst_caps_new_simple("video/x-raw",
"format", G_TYPE_STRING, "I420",
"framerate", GST_TYPE_FRACTION, 25, 1,
"pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
"width", G_TYPE_INT, 320,
"height", G_TYPE_INT, 240,
NULL);
由于此代码,GstCaps
包含一个 GStructure
,表示以 1:1 像素宽高比、宽度 320 和高度 240 显示原始视频的能力。
除了定义结构之外,应用程序还可以使用 gst_caps_get_structure
访问结构,并使用 gst_caps_remove_structure
移除结构。当应用程序完成处理 GstCaps
时,它应该调用 gst_caps_unref
来释放其内存。
3.5 GstBus 和消息
每个 GstElement
都有一个 GstBus
,使应用程序能够从元素中提取信息。了解 pad 和 bus 之间的区别很重要——pad 在元素之间传输多媒体数据,而 bus 将状态消息从元素传输到应用程序。
GstBus
将其消息存储在队列中,每个消息都是 GstMessage
结构的一个实例。表 7 列出了与 GstBus
结构及其消息队列相关的函数。
函数 | 描述 |
---|---|
gst_bus_post(GstBus*, GstMessage*) | 将消息放入总线队列 |
gst_bus_peek(GstBus*) | 检索第一条消息而不改变队列 |
gst_bus_pop(GstBus*) | 检索第一条消息,从队列中移除 |
gst_bus_timed_pop(GstBus*, GstClockTime) | 检索/移除具有超时的第一条消息 |
gst_bus_pop_filtered(GstBus*, GstMessageType) | 根据类型检索并移除第一条消息 |
gst_bus_timed_pop_filtered(GstBus*, GstClockTime, GstMessageType) | 根据类型检索/移除具有超时的第一条消息 |
gst_bus_post
将消息放入队列,但这很少使用。相反,应用程序通过调用其中一个 pop 函数等待错误消息。gst_bus_pop
立即返回,如果队列不为空则返回第一条消息,如果队列为空则返回 NULL
。相反,gst_bus_timed_pop
在第二个参数给定的时间量之后返回。
gst_bus_pop_filtered
仅在消息属于第二个参数标识的类型或类型时才返回消息。这可以设置为 GstMessageType
枚举类型的一个值或其 OR 组合。表 8 列出了此类型的前十个值。
消息类型 | 值 | 描述 |
---|---|---|
GST_MESSAGE_UNKNOWN | 0 | 未定义消息 |
GST_MESSAGE_EOS | 1 | 管道已到达流的末尾 |
GST_MESSAGE_ERROR | 2 | 错误消息 |
GST_MESSAGE_WARNING | 4 | 警告消息 |
GST_MESSAGE_INFO | 8 | 信息消息 |
GST_MESSAGE_TAG | 16 | 标签消息 |
GST_MESSAGE_BUFFERING | 32 | 管道正在缓冲 |
GST_MESSAGE_STATE_CHANGED | 64 | 状态已更改 |
GST_MESSAGE_STATE_DIRTY | 128 | 元素更改了状态 |
GST_MESSAGE_STEP_DONE | 256 | 步进操作完成 |
应用程序通常会等待错误情况(GST_MESSAGE_ERROR
)或流的结束(GST_MESSAGE_EOS
)。这可以通过以下方式调用 gst_bus_timed_pop_filtered
函数来实现:
GstMessage* msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
第二个参数 GST_CLOCK_TIME_NONE
表示无限时间。由于此参数,该函数会无限期地等待错误消息或流结束消息。这两种类型的组合表示为 GST_MESSAGE_ERROR | GST_MESSAGE_EOS
。
3.6 GstBin 和 GstPipeline
如图 1 所示,GstBin
是 GstElement
的子结构,GstPipeline
是 GstBin
的子结构。GstBin
是一个可以包含其他元素的元素,而 GstPipeline
表示整个管道。这两种结构的函数都相当简单。
应用程序可以通过调用 gst_bin_new
并提供新结构的名称来创建一个空 bin。然后它可以通过调用 gst_bin_add
添加一个元素,或者通过调用 gst_bin_add_many
添加多个元素。应用程序可以通过调用 gst_bin_remove
从 bin 中移除一个元素。可以通过调用 gst_bin_get_by_name
按名称检索 bin 中的元素。
应用程序可以通过调用 gst_pipeline_new
并提供管道的名称来创建一个空的管道结构。但更常见的是调用 gst_parse_launch
,它接受管道描述和指向内存的指针以存储错误 (GError**
)。它返回一个具有描述中给定特征的 GstPipeline
。
例如,以下代码通过使用 filesrc
插件访问名为 image.svg 的文件来创建管道:
pipeline = gst_parse_launch("filesrc location=image.svg", NULL);
一旦应用程序创建了 GstPipeline
,它就可以通过调用表 9 中的函数之一来访问管道的字段。
函数 | 描述 |
---|---|
gst_pipeline_get_bus(GstPipeline*) | 返回管道的总线 |
gst_pipeline_get_clock(GstPipeline*) | 返回管道的时钟 |
gst_pipeline_set_clock(GstPipeline*, GstClock*) | 设置与管道关联的时钟 |
gst_pipeline_get_delay(GstPipeline*) | 检索管道的延迟 |
gst_pipeline_set_delay(GstPipeline*, GstClockTime) | 设置管道的延迟 |
gst_pipeline_get_latency(GstPipeline*) | 检索管道的延迟 |
gst_pipeline_set_latency(GstPipeline*, GstClockTime) | 设置管道处理的延迟 |
要向 GstPipeline
添加元素,应用程序可以使用 GST_BIN
宏将其转换为 GstBin
。然后它调用 gst_bin_add
或 gst_bin_add_many
。为了演示,以下代码从 pipeline
创建一个 GstBin
并添加三个元素:elemA
、elemB
和 elemC
:
gst_bin_add_many(GST_BIN(pipeline), elemA, elemB, elemC, NULL);
与许多 GStreamer 函数一样,gst_bin_add_many
的最后一个参数设置为 NULL
。这会终止添加到管道的元素列表。
4. 编写应用程序
既然您熟悉了 GStreamer 的主要数据结构,是时候看看代码了。本文提供了两个示例源文件:
- simple_playback.c - 创建一个播放 GStreamer 站点视频的管道
- audio_file.c - 通过连接元素创建新的 MP3 文件
要编译这些文件,您需要安装 GStreamer。如果您有 GCC 可用,可以使用以下命令编译第一个源文件:
gcc simple_playback.c -o simple_playback `pkg-config --cflags --libs gstreamer-1.0`
在此命令中,pkg-config
是一个辅助工具,用于生成使用 GStreamer 编译所需的适当标志。这种类型的命令可以与所有三个示例源文件一起使用。
4.1 简单播放示例
第一个源文件 simple_playback.c 中的代码通过管道描述创建并执行一个管道,该管道显示 GStreamer 站点上的视频 (sintel_trailer-480p.webm)。其代码如下:
int main(int argc, char *argv[]) {
GstElement *pipeline;
GstBus *bus;
GstMessage *msg;
/* Initialize GStreamer */
gst_init(&argc, &argv);
/* Create the pipeline */
pipeline =
gst_parse_launch("playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);
/* Start playing */
gst_element_set_state(pipeline, GST_STATE_PLAYING);
/* Handle errors through bus */
bus = gst_element_get_bus(pipeline);
msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
if (msg != NULL) {
GError *err;
gchar *dbg;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error(msg, &err, &dbg);
g_printerr("Error from element %s: %s\n",
GST_OBJECT_NAME(msg->src), err->message);
g_printerr("Debug: %s\n", dbg ? dbg : "none");
g_clear_error(&err);
g_free(dbg);
break;
case GST_MESSAGE_EOS:
g_print("End-Of-Stream\n");
break;
default:
g_printerr("Unexpected message\n");
break;
}
gst_message_unref(msg);
}
/* Deallocate structures */
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
return 0;
}
在用 gst_init
初始化 GStreamer 操作后,此代码通过使用管道描述调用 gst_parse_launch
来创建管道。然后它通过调用 gst_element_set_state
并传入 GST_STATE_PLAYING
来播放管道的媒体。
大部分代码都配置管道,以便在发生任何错误时提供消息。这需要三个步骤:
- 使用
gst_element_get_bus
访问管道的总线。 - 配置总线,以便在发生错误条件或流结束 (EOS) 条件时提供消息。
- 如果任一条件发生,打印适当的文本。
设置错误处理后,应用程序会释放管道的总线。然后它将管道的状态设置为 GST_STATE_NULL
,并使用 gst_object_unref
释放管道。
4.2 音频文件示例
在本文的开头,我介绍了一个基本的管道描述,它创建一个包含 200 Hz 音调的 MP3 文件。管道的文本如下:
audiotestsrc freq=200 ! audioconvert ! lamemp3enc ! filesink location=audio.mp3
audio_file.c 示例通过创建和连接元素来实现相同的结果。此文件中的代码如下:
int main(int argc, char *argv[]) {
GstElement *pipeline, *src, *convert, *encode, *sink;
GstBus *bus;
GstMessage *msg;
/* Initialize GStreamer */
gst_init(&argc, &argv);
/* Create the elements */
src = gst_element_factory_make("audiotestsrc", NULL);
convert = gst_element_factory_make("audioconvert", NULL);
encode = gst_element_factory_make("lamemp3enc", NULL);
sink = gst_element_factory_make("filesink", NULL);
/* Create pipeline */
pipeline = gst_pipeline_new("pipeline");
/* Add elements to pipeline */
gst_bin_add_many(GST_BIN(pipeline), src, convert, encode, sink, NULL);
gst_element_link_many(src, convert, encode, sink, NULL);
/* Set element properties */
g_object_set(G_OBJECT(src), "freq", 200.0f, NULL);
g_object_set(G_OBJECT(sink), "location", "audio.mp3", NULL);
/* Start playing */
gst_element_set_state(pipeline, GST_STATE_PLAYING);
/* Handle errors through bus */
bus = gst_element_get_bus(pipeline);
msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
if (msg != NULL) {
GError *err;
gchar *dbg;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error(msg, &err, &dbg);
g_printerr("Error from element %s: %s\n",
GST_OBJECT_NAME(msg->src), err->message);
g_printerr("Debug: %s\n", dbg ? dbg : "none");
g_clear_error(&err);
g_free(dbg);
break;
case GST_MESSAGE_EOS:
g_print("End-Of-Stream\n");
break;
default:
g_printerr("Unexpected message\n");
break;
}
gst_message_unref(msg);
}
/* Deallocate structures */
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
return 0;
}
初始化 GStreamer 后,代码调用 gst_element_factory_make
为管道中的每个元素创建一个 GstElement
。然后它使用 gst_pipeline_new
创建一个空管道,并使用 gst_bin_add_many
将元素添加到管道。添加元素后,代码通过调用 gst_element_link_many
在它们之间创建连接。
代码调用 g_object_set
两次来设置元素属性。第一次调用将源元素 (src
) 的频率 (freq
) 设置为 200 Hz。第二次调用将接收元素 (sink
) 的文件名 (location
) 设置为 audio.mp3
。
在管道填充了连接的元素后,源代码通过调用 gst_element_set_state
并传入 GST_STATE_PLAYING
来执行管道。然后它使用前面讨论的相同代码配置错误处理。
当您运行可执行文件时,它将创建 audio.mp3 文件并继续向其中写入数据。按 Ctrl-c 停止该进程。
历史
本文最初于 2024 年 9 月 1 日提交。