65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 GStreamer 处理多媒体内容

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2024 年 9 月 1 日

CPOL

20分钟阅读

viewsIcon

3843

downloadIcon

62

本文介绍了 GStreamer 工具集,并解释了如何编写读取和处理多媒体内容的应用程序。

随着多媒体设备的普及,构建能够读取和处理其内容的应用程序变得越来越必要。有许多可用的编程库,但 GStreamer 是少数可以处理各种音频和视频数据的开源工具集之一。它是免费提供的,可在多个操作系统上运行,并提供令人印象深刻的性能和广泛的文档。

在我看来,使用 GStreamer 只有两个缺点。首先,它的数据结构(pad、caps、bus、element)与我以前遇到的任何东西都不同。其次,它是用 C 语言编写的,因此您必须处理诸如 gst_bus_timed_pop_filtered 之类的冗长函数名。

本文的目标是介绍 GStreamer 的用法和功能。它首先解释了如何安装软件包,然后展示了如何从命令行执行操作。最后一部分介绍了 GStreamer 的基本数据结构,并演示了如何使用它们。

1. 安装

GStreamer 的主站点是 gstreamer.freedesktop.org。安装过程取决于您的操作系统,您可以通过访问 GStreamer 站点并执行四个步骤来查找说明:

  1. 点击左侧的“文档”按钮。
  2. 点击中心的“开始”按钮。
  3. 打开左上角的“安装 GStreamer”选项。
  4. 点击与您的操作系统对应的链接。

安装 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 应用程序的目标是创建和执行管道。在管道描述方面,有四点需要注意:

  1. 管道描述由“元素”组成,这些元素标识应如何生成、处理和使用媒体。
  2. 元素之间用感叹号 (!) 分隔,感叹号代表元素之间的链接。
  3. 元素的属性可以通过在其名称后加上 name=value 对来设置,其中 name 是属性的名称,value 是所需的值。
  4. 在几乎所有情况下,元素都用 GStreamer“插件”的名称来标识。

考虑到这一点,让我们看一下前面的命令。管道描述包含四个名为 audiotestsrcaudioconvertlamemp3encfilesink 的元素。audiotestsrc 元素有一个名为 freq 的属性,filesink 元素有一个名为 location 的属性。

GStreamer 插件是执行管道中操作的库。这四个元素中的每一个都标识一个插件,当 gst-launch-1.0 执行时,它会调用每个插件来执行其操作。

大多数插件以某种方式处理流数据,但有些插件从源读取数据或生成新数据。这些是“源插件”,在示例命令中,audiotestsrc 是一个生成音频数据的源插件。同样,“接收器插件”消耗或存储流数据。在前面的示例中,filesink 插件将音频数据存储到名为 audio.mp3 的文件中。

filesink 插件是 GStreamer 附带的许多插件之一。这些是“核心插件”,表 1 列出了 filesink 和其他几个核心插件。

表 1:核心插件(节选)
插件名称 分类 描述
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 包提供了大量插件,分为多个类别,包括 audioconvertencodingplaybackplayback 类别中的插件特别有用,表 2 列出了其中许多插件。

表 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

运行此命令时,请记住管道加载、缓冲和播放视频可能需要时间。此外,请注意管道所经历的不同状态,例如 PAUSEDPLAYING

此时,您应该了解 GStreamer 的目的是执行由元素组成的管道。gst-launch-1.0 实用程序非常适合简单操作,但不适用于复杂媒体处理。要以编程方式创建管道,您需要了解 GStreamer API 及其数据结构。

3. GStreamer API

GStreamer 是用 C 语言编写的,它不是一种面向对象的语言。尽管如此,官方文档仍将 GStreamer 的数据结构称为“”。文档还描述了一个从 GObject 开始的“继承层次结构”。例如,文档指出 GstElementGstObject 的子类,GstObjectGInitiallyOwned 的子类,GInitiallyOwnedGObject 的子类。

在 GStreamer 中,继承基于“聚合”。如果结构 X 是结构 Y 的子类,则结构 X 的第一个字段是结构 Y 的实例。例如,GstElement 的第一个字段是 GstObject 的实例,GstObject 的第一个字段是 GInitiallyOwned 的实例。

如果您熟悉传统的面向对象编程,这会显得奇怪和不自然。但是,如果您想了解 GStreamer 的 API,接受这一点至关重要。图 1 展示了 GStreamer 最重要类的继承层次结构。

图 1:继承层次结构中的重要类

在此图中,顶部的两个结构以 G 开头,下面的结构以 Gst 开头。这是因为 GObjectGInitiallyUnowned 由 GLib 提供,而其他结构由 GStreamer 提供。同样,以 g_ 开头的函数执行 GLib 操作,以 gst_ 开头的函数执行 GStreamer 操作。

本节将介绍此图中的阴影结构:GObjectGstObjectGstElementGstBinGstPipelineGstBusGstPad。一旦您了解这些结构的功能,您就可以轻松使用它们来创建和执行管道。

3.1 GObject

GObject 结构是 GLib/GStreamer 层次结构中的基本结构。对于本文,您只需要了解两点:

  1. GObject 存储名为“属性”的名称-值对。
  2. 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 终止符。为了演示,以下代码设置名为 gobjGObject*namecolor 属性:

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,但有四个值得了解的函数:

  1. gst_init(int argc, char** argv) - 初始化 GStreamer 环境
  2. gst_object_unref(GstObject* obj) - 释放 GstObject
  3. gst_object_set_name(GstObject* obj, const gchar* name) - 为对象分配名称
  4. gst_object_get_name(GstObject* obj) - 返回对象的名称

应用程序需要在任何其他 GStreamer 函数之前调用 gst_init。这可以接受传递给应用程序的命令行参数(argcargv)。

许多应用程序将声明一个 GstObject 指针变量并通过调用函数来初始化该变量。为了释放内存,应用程序应该为每个已初始化的变量调用 gst_object_unref

3.3 GstElement

媒体处理管道中的每个阶段都由一个 GstElement 表示。本节讨论此重要数据结构的三个方面:

  1. GstElement 的字段
  2. 创建 GstElement 的函数
  3. 访问 GstElement 属性的函数

阅读本节时,您不需要记住每个字段和函数。但您应该学习足够多的知识,以便熟悉 GstElement 的使用方式。

3.3.1 GstElement 字段

要了解 GstElement 的功能,最好熟悉其字段。表 3 列出了其中许多字段及其数据类型。

表 3:GstElement 字段
字段 数据类型 描述
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 函数接受一个名称并返回相应的元素工厂。例如,以下代码访问名为 videotestsrcGstElementFactory

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 列出了许多可用函数并提供了每个函数的描述。

表 4:GstElement 字段函数
函数 描述
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 及其属性相关的函数。

表 5:GstPad 函数
函数 描述
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 终止符。例如,以下代码在 elemAelemB 之间以及 elemBelemC 之间建立连接。

gst_element_link_many(elemA, elemB, elemC, NULL);

除了 pad 函数之外,GStreamer 还提供了与 pad capabilities 相关的函数。它们的名称以 gst_caps_ 开头,表 6 列出了其中八个函数。

表 6:GstCaps 函数
函数 描述
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 结构及其消息队列相关的函数。

表 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 列出了此类型的前十个值。

表 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 所示,GstBinGstElement 的子结构,GstPipelineGstBin 的子结构。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 中的函数之一来访问管道的字段。

表 9:GstPipeline 函数
函数 描述
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_addgst_bin_add_many。为了演示,以下代码从 pipeline 创建一个 GstBin 并添加三个元素:elemAelemBelemC

gst_bin_add_many(GST_BIN(pipeline), elemA, elemB, elemC, NULL);

与许多 GStreamer 函数一样,gst_bin_add_many 的最后一个参数设置为 NULL。这会终止添加到管道的元素列表。

4. 编写应用程序

既然您熟悉了 GStreamer 的主要数据结构,是时候看看代码了。本文提供了两个示例源文件:

  1. simple_playback.c - 创建一个播放 GStreamer 站点视频的管道
  2. 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 来播放管道的媒体。

大部分代码都配置管道,以便在发生任何错误时提供消息。这需要三个步骤:

  1. 使用 gst_element_get_bus 访问管道的总线。
  2. 配置总线,以便在发生错误条件或流结束 (EOS) 条件时提供消息。
  3. 如果任一条件发生,打印适当的文本。

设置错误处理后,应用程序会释放管道的总线。然后它将管道的状态设置为 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 日提交。

© . All rights reserved.