GStreamer 基本教程3: 动态管道
转载自vinming.github.io
前言
本文是GStreamer学习笔记,也可以看成是对原文的意译。
这些教程描述了理解其余教程所需的GStreamer主题。
GStreamer教程:
环境
系统环境
Distributor ID: Ubuntu Description: Ubuntu 18.04.4 LTS Release: 18.04 Codename: bionic Linux version : 5.3.0-46-generic ( buildd@lcy01-amd64-013 ) Gcc version: 7.5.0 ( Ubuntu 7.5.0-3ubuntu1~18.04 )
软件信息
version : GStreamer-1.0
正文
1. 目标
本教程展示了使用 GStreamer 所需的其余基本概念,它允许在信息可用时“即时”构建管道,而不是在应用程序开始时定义单一管道。
完成本教程后,您将具备开始 播放教程!here
所需的知识。这里学习的要点是:
- 如何在链接元素时获得更好的控制。
- 如何收到有趣事件的通知,以便您及时做出反应。
- 元素可以处于的各种状态。
2. 介绍
如您将看到的,本教程中的管道在设置为播放状态之前并未完全构建。还行吧。如果我们不采取进一步的行动,数据将到达管道的末端,管道将产生错误消息并停止。但我们将采取进一步的行动……
在这个例子中,我们打开一个多路复用(或复用)的文件,也就是说,音频和视频一起存储在一个容器文件中。负责打开此类容器的元素称为 demuxers,容器格式的一些示例包括 Matroska (MKV)、Quick Time (QT、MOV)、Ogg 或 Advanced Systems Format (ASF、WMV、WMA)。
如果一个容器嵌入了多个流(例如,一个视频和两个音轨),解复用器将分离它们并通过不同的输出端口公开它们。这样就可以在管道中创建不同的分支,处理不同类型的数据。
GStreamer 元素相互通信的端口称为 pad ( GstPad
)。存在sink pads,数据通过它进入一个元素,还有source pads,数据通过它离开一个元素。很自然地,源元素仅包含源垫,接收器元素仅包含接收垫,过滤器元素包含两者。
一个分路器包含一个接收器垫,多路复用的数据通过它到达,以及多个源垫,一个用于容器中找到的每个流:
为了完整起见,这里有一个简化的管道,其中包含一个解复用器和两个分支,一个用于音频,一个用于视频。这 不是将在此示例中构建的管道:
处理 demuxer 时的主要复杂性在于,它们无法生成任何信息,直到它们接收到一些数据并且有机会查看容器以查看里面的内容。也就是说,解复用器从没有其他元素可以链接到的开始,因此流水线必须在它们处终止。
解决方案是构建从源到解复用器的管道,并将其设置为运行(播放)。当解复用器收到足够的信息以了解容器中流的数量和种类时,它将开始创建源垫。这是我们完成管道构建并将其连接到新添加的解复用器垫的正确时间。
为简单起见,在此示例中,我们将仅链接到音频垫并忽略视频。
3. 动态Hello world
basic-tutorial-3.c
#include <gst/gst.h> /* Structure to contain all our information, so we can pass it to callbacks */ typedef struct _CustomData { GstElement *pipeline; GstElement *source; GstElement *convert; GstElement *resample; GstElement *sink; } CustomData; /* Handler for the pad-added signal */ static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data); int main(int argc, char *argv[]) { CustomData data; GstBus *bus; GstMessage *msg; GstStateChangeReturn ret; gboolean terminate = FALSE; /* Initialize GStreamer */ gst_init (&argc, &argv); /* Create the elements */ data.source = gst_element_factory_make ("uridecodebin", "source"); data.convert = gst_element_factory_make ("audioconvert", "convert"); data.resample = gst_element_factory_make ("audioresample", "resample"); data.sink = gst_element_factory_make ("autoaudiosink", "sink"); /* Create the empty pipeline */ data.pipeline = gst_pipeline_new ("test-pipeline"); if (!data.pipeline || !data.source || !data.convert || !data.resample || !data.sink) { g_printerr ("Not all elements could be created.\n"); return -1; } /* Build the pipeline. Note that we are NOT linking the source at this * point. We will do it later. */ gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert, data.resample, data.sink, NULL); if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) { g_printerr ("Elements could not be linked.\n"); gst_object_unref (data.pipeline); return -1; } /* Set the URI to play */ g_object_set (data.source, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); /* Connect to the pad-added signal */ g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data); /* Start playing */ ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr ("Unable to set the pipeline to the playing state.\n"); gst_object_unref (data.pipeline); return -1; } /* Listen to the bus */ bus = gst_element_get_bus (data.pipeline); do { msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS); /* Parse message */ if (msg != NULL) { GError *err; gchar *debug_info; switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_ERROR: gst_message_parse_error (msg, &err, &debug_info); g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); g_clear_error (&err); g_free (debug_info); terminate = TRUE; break; case GST_MESSAGE_EOS: g_print ("End-Of-Stream reached.\n"); terminate = TRUE; break; case GST_MESSAGE_STATE_CHANGED: /* We are only interested in state-changed messages from the pipeline */ if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) { GstState old_state, new_state, pending_state; gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); g_print ("Pipeline state changed from %s to %s:\n", gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); } break; default: /* We should not reach here */ g_printerr ("Unexpected message received.\n"); break; } gst_message_unref (msg); } } while (!terminate); /* Free resources */ gst_object_unref (bus); gst_element_set_state (data.pipeline, GST_STATE_NULL); gst_object_unref (data.pipeline); return 0; } /* This function will be called by the pad-added signal */ static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) { GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink"); GstPadLinkReturn ret; GstCaps *new_pad_caps = NULL; GstStructure *new_pad_struct = NULL; const gchar *new_pad_type = NULL; g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src)); /* If our converter is already linked, we have nothing to do here */ if (gst_pad_is_linked (sink_pad)) { g_print ("We are already linked. Ignoring.\n"); goto exit; } /* Check the new pad's type */ new_pad_caps = gst_pad_get_current_caps (new_pad); new_pad_struct = gst_caps_get_structure (new_pad_caps, 0); new_pad_type = gst_structure_get_name (new_pad_struct); if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) { g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type); goto exit; } /* Attempt the link */ ret = gst_pad_link (new_pad, sink_pad); if (GST_PAD_LINK_FAILED (ret)) { g_print ("Type is '%s' but link failed.\n", new_pad_type); } else { g_print ("Link succeeded (type '%s').\n", new_pad_type); } exit: /* Unreference the new pad's caps, if we got them */ if (new_pad_caps != NULL) gst_caps_unref (new_pad_caps); /* Unreference the sink pad */ gst_object_unref (sink_pad); }
编译运行
$ gcc basic-tutorial-3.c -o basic-tutorial-3 `pkg-config --cflags --libs gstreamer-1.0` $ ./basic-tutorial-3.c Pipeline state changed from NULL to READY: Received new pad 'src_0' from 'source': It has type 'video/x-raw' which is not raw audio. Ignoring. Received new pad 'src_1' from 'source': Link succeeded (type 'audio/x-raw'). Pipeline state changed from READY to PAUSED: Pipeline state changed from PAUSED to PLAYING: End-Of-Stream reached.
4. 演练
/* Structure to contain all our information, so we can pass it to callbacks */ typedef struct _CustomData { GstElement *pipeline; GstElement *source; GstElement *convert; GstElement *resample; GstElement *sink; } CustomData;
到目前为止,我们已经将我们需要的所有信息(GstElement
基本上是指向 s 的指针)保存为局部变量。由于本教程(以及大多数实际应用程序)涉及回调,我们将把所有数据分组到一个结构中以便于处理。
/* Handler for the pad-added signal */ static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);
这是一个前向引用,以后会用到
/* Create the elements */ data.source = gst_element_factory_make ("uridecodebin", "source"); data.convert = gst_element_factory_make ("audioconvert", "convert"); data.resample = gst_element_factory_make ("audioresample", "resample"); data.sink = gst_element_factory_make ("autoaudiosink", "sink");
我们像往常一样创建元素基础教程 14: 有用的元素。uridecodebin
将在内部实例化所有必要的元素(源、解复用器和解码器)以将 URI 转换为原始音频和/或视频流。它完成了一半的工作playbin
。由于它包含解复用器,它的源焊盘最初不可用,我们需要即时链接到它们。
audioconvert
对于在不同音频格式之间进行转换很有用,确保此示例适用于任何平台,因为音频解码器生成的格式可能与音频接收器期望的格式不同。
audioresample
对于在不同的音频采样率之间进行转换很有用,同样确保此示例适用于任何平台,因为音频解码器产生的音频采样率可能不是音频接收器支持的采样率。
autoaudiosink
相当于在上一个教程中看到的autovideosink
音频。它将音频流渲染到声卡。
if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) { g_printerr ("Elements could not be linked.\n"); gst_object_unref (data.pipeline); return -1; }
这里我们链接元素转换器、重采样和接收器,但我们不将它们与源链接,因为此时它不包含源焊盘。我们只是让这个分支(转换器+接收器)不链接,直到稍后。
/* Set the URI to play */ g_object_set (data.source, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
我们将文件的 URI 设置为通过属性播放,就像我们在上一个教程中所做的那样。
4.1 信号
/* Connect to the pad-added signal */ g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
GSignals
是 GStreamer 中的一个关键点。它们允许您在发生有趣的事情时收到通知(通过回调)。信号由名称标识,每个GObject
信号都有自己的信号。
在这一行中,我们附加uridecodebin
到源(一个元素)的“pad- added”信号。为此,我们使用g_signal_connect()
并提供要使用的回调函数 ( pad_added_handler
) 和数据指针。GStreamer 对这个数据指针不做任何事情,它只是将它转发给回调,以便我们可以与它共享信息。在这种情况下,我们传递一个指向CustomData
我们专门为此目的构建的结构的指针。
生成的信号GstElement
可以在其文档中找到,也可以使用基础教程 10: GStreamer工具gst-inspect-1.0
中描述的工具。
我们现在准备出发了!只需将管道设置为PLAYING
状态并开始监听总线以获取有趣的消息(如ERROR
或EOS
),就像在之前的教程中一样。
4.2 回调
当我们的源元素最终有足够的信息开始产生数据时,它将创建源焊盘,并触发“焊盘添加”信号。此时我们的回调将被调用:
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data)
4.3 GStreamer状态
当我们说直到您将管道带入状态时才会开始播放时,我们已经谈到了一些PLAYING
状态。我们将在这里介绍其余的状态及其含义。GStreamer 中有 4 个状态:
状态 | 描述 |
---|---|
NULL |
元素的 NULL 状态或初始状态。 |
READY |
元素已准备好进入 PAUSED。 |
PAUSED |
元素已暂停,它已准备好接受和处理数据。然而,接收器元素只接受一个缓冲区然后阻塞。 |
PLAYING |
元素正在播放,时钟正在运行,数据正在流动。 |
你只能在相邻的之间移动,也就是说,你不能从NULL
to PLAYING
,你必须经过中间READY
和PAUSED
状态。
但是,如果您将管道设置为PLAYING
,GStreamer 将为您进行中间转换。
case GST_MESSAGE_STATE_CHANGED: /* We are only interested in state-changed messages from the pipeline */ if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) { GstState old_state, new_state, pending_state; gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); g_print ("Pipeline state changed from %s to %s:\n", gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); } break;
我们添加了这段代码来监听关于状态变化的总线消息并将它们打印在屏幕上以帮助您理解转换。每个元素都将有关其当前状态的消息放在总线上,因此我们将它们过滤掉,只收听来自管道的消息。
大多数应用程序只需要担心PLAYING
开始播放,然后PAUSED
执行暂停,然后返回NULL
程序退出以释放所有资源。
5. 练习
对于许多程序员来说,动态焊盘链接传统上一直是一个难题。autovideosink
通过实例化一个(可能videoconvert
在前面带有一个)并在出现正确的 pad 时将其链接到解复用器来证明您已经掌握了它。提示:您已经在屏幕上打印了视频板的类型。
您现在应该看到(并听到)与基础教程 1: Hello world!. 在您使用的那个教程中playbin
,这是一个方便的元素,可以自动为您处理所有解复用和填充链接。大多数播放教程都专门用于playbin
.
6. 结论
在本小节中,学习了:
- 如何使用事件通知GSingals
- 如何连接多个GstPad而不是他们的父元素
- GStreamer元素的各种状态
结合了这些项目构建一个动态管道,它不是在程序开始时定义好的,而是在有关媒体的信息可用时创建的。
在下一小节基础教程 4: 时间管理中了解如何执行搜索和时间相关查询,或者转到播放教程,并获取有关该playbin
元素的更多见解
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2017-03-08 sendto函数的坑