前言
本文是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 自动处理多线程,但在某些情况下,您可能需要手动解耦线程。本教程展示了如何做到这一点,此外,还完成了关于 Pad Availability 的阐述。更准确地说,本文档解释了:
- 如何为管道的某些部分创建新的执行线程
- 什么是pad可用性
- 如何复制流
2. 介绍
2.1 多线程
GStreamer 是一个多线程框架。这意味着,在内部,它会根据需要创建和销毁线程,例如,将流与应用程序线程分离。此外,插件也可以自由地为自己的处理创建线程,例如,视频解码器可以创建 4 个线程来充分利用具有 4 个内核的 CPU。
最重要的是,在构建管道时,应用程序可以明确指定分支(管道的一部分)在不同的线程上运行(例如,让音频和视频解码器同时执行)。
这是使用queue
元素完成的,其工作原理如下。sink pad 只是将数据排入队列并返回控制权。在不同的线程上,数据出列并推送到下游。该元素还用于缓冲,如后面的流式教程中所示。队列的大小可以通过属性来控制。
2.2 示例管道
此示例构建以下管道:
源是合成音频信号(连续音调),它使用一个tee
元素进行拆分(它通过其源垫发送它通过其接收垫接收的所有内容)。然后一个分支将信号发送到声卡,另一个分支渲染波形视频并将其发送到屏幕。
如图所示,队列创建了一个新线程,因此该管道在 3 个线程中运行。具有多个接收器的管道通常需要多线程,因为要同步,接收器通常会阻塞执行,直到所有其他接收器都准备好,并且如果只有一个线程被第一个接收器阻塞,它们将无法准备好。
2.3 请求pads
在基础教程 3: 动态管道中,我们看到了一个元素 ( uridecodebin
),它一开始没有填充,它们出现在数据开始流动并且元素了解媒体时。这些称为“sometimes Pads”,与始终可用并称为“Always Pads”的常规垫形成对比。
第三种 pad 是Request Pad,它是按需创建的。经典的例子是tee
元素,它有一个 sink pad 而没有初始 source pad:需要请求然后 tee
添加它们。通过这种方式,输入流可以被复制任意次数。缺点是使用 Request Pads 链接元素不像链接 Always Pads 那样自动,如本示例的演练所示。
PLAYING
此外,要在or状态下请求(或释放)焊盘PAUSED
,您需要采取额外的注意事项(pads阻塞),这在本教程中没有描述。NULL
不过,在or READY
状态下请求(或释放)pad 是安全的。
3. 简单的多线程示例
basic-tutorial-7.c
#include <gst/gst.h>
int main(int argc, char *argv[]) {
GstElement *pipeline, *audio_source, *tee, *audio_queue, *audio_convert, *audio_resample, *audio_sink;
GstElement *video_queue, *visual, *video_convert, *video_sink;
GstBus *bus;
GstMessage *msg;
GstPad *tee_audio_pad, *tee_video_pad;
GstPad *queue_audio_pad, *queue_video_pad;
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Create the elements */
audio_source = gst_element_factory_make ("audiotestsrc", "audio_source");
tee = gst_element_factory_make ("tee", "tee");
audio_queue = gst_element_factory_make ("queue", "audio_queue");
audio_convert = gst_element_factory_make ("audioconvert", "audio_convert");
audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
video_queue = gst_element_factory_make ("queue", "video_queue");
visual = gst_element_factory_make ("wavescope", "visual");
video_convert = gst_element_factory_make ("videoconvert", "csp");
video_sink = gst_element_factory_make ("autovideosink", "video_sink");
/* Create the empty pipeline */
pipeline = gst_pipeline_new ("test-pipeline");
if (!pipeline || !audio_source || !tee || !audio_queue || !audio_convert || !audio_resample || !audio_sink ||
!video_queue || !visual || !video_convert || !video_sink) {
g_printerr ("Not all elements could be created.\n");
return -1;
}
/* Configure elements */
g_object_set (audio_source, "freq", 215.0f, NULL);
g_object_set (visual, "shader", 0, "style", 1, NULL);
/* Link all elements that can be automatically linked because they have "Always" pads */
gst_bin_add_many (GST_BIN (pipeline), audio_source, tee, audio_queue, audio_convert, audio_resample, audio_sink,
video_queue, visual, video_convert, video_sink, NULL);
if (gst_element_link_many (audio_source, tee, NULL) != TRUE ||
gst_element_link_many (audio_queue, audio_convert, audio_resample, audio_sink, NULL) != TRUE ||
gst_element_link_many (video_queue, visual, video_convert, video_sink, NULL) != TRUE) {
g_printerr ("Elements could not be linked.\n");
gst_object_unref (pipeline);
return -1;
}
/* Manually link the Tee, which has "Request" pads */
tee_audio_pad = gst_element_request_pad_simple (tee, "src_%u");
g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad));
queue_audio_pad = gst_element_get_static_pad (audio_queue, "sink");
tee_video_pad = gst_element_request_pad_simple (tee, "src_%u");
g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad));
queue_video_pad = gst_element_get_static_pad (video_queue, "sink");
if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK) {
g_printerr ("Tee could not be linked.\n");
gst_object_unref (pipeline);
return -1;
}
gst_object_unref (queue_audio_pad);
gst_object_unref (queue_video_pad);
/* Start playing the pipeline */
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* Wait until error or EOS */
bus = gst_element_get_bus (pipeline);
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Release the request pads from the Tee, and unref them */
gst_element_release_request_pad (tee, tee_audio_pad);
gst_element_release_request_pad (tee, tee_video_pad);
gst_object_unref (tee_audio_pad);
gst_object_unref (tee_video_pad);
/* Free resources */
if (msg != NULL)
gst_message_unref (msg);
gst_object_unref (bus);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
return 0;
}
编译运行
gcc basic-tutorial-7.c -o basic-tutorial-7 `pkg-config --cflags --libs gstreamer-1.0`
./basic-tutorial-7
可能遇到的问题
$ gcc basic-tutorial-7.c -o basic-tutorial-7 `pkg-config --cflags --libs gstreamer-1.0`
basic-tutorial-7.c: In function ‘main’:
basic-tutorial-7.c:54:21: warning: implicit declaration of function ‘gst_element_request_pad_simple’; did you mean ‘gst_element_request_pad’? [-Wimplicit-function-declaration]
tee_audio_pad = gst_element_request_pad_simple(tee, "src_%u");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gst_element_request_pad
basic-tutorial-7.c:54:19: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
tee_audio_pad = gst_element_request_pad_simple(tee, "src_%u");
^
basic-tutorial-7.c:57:19: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
tee_video_pad = gst_element_request_pad_simple(tee, "src_%u");
^
/tmp/cc5QgxcJ.o: In function `main':
basic-tutorial-7.c:(.text+0x322): undefined reference to `gst_element_request_pad_simple'
basic-tutorial-7.c:(.text+0x377): undefined reference to `gst_element_request_pad_simple'
collect2: error: ld returned 1 exit status
解决方案:将gst_element_request_pad_simple
替换成gst_element_get_request_pad
参考:gst_element_request_pad_simple
Note that this function was introduced in GStreamer 1.20 in order to provide a better name to gst_element_get_request_pad. Prior to 1.20, users should use gst_element_get_request_pad which provides the same functionality.
4. 演练
/* Create the elements */
audio_source = gst_element_factory_make ("audiotestsrc", "audio_source");
tee = gst_element_factory_make ("tee", "tee");
audio_queue = gst_element_factory_make ("queue", "audio_queue");
audio_convert = gst_element_factory_make ("audioconvert", "audio_convert");
audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
video_queue = gst_element_factory_make ("queue", "video_queue");
visual = gst_element_factory_make ("wavescope", "visual");
video_convert = gst_element_factory_make ("videoconvert", "video_convert");
video_sink = gst_element_factory_make ("autovideosink", "video_sink");
上图中的所有元素都在这里实例化:
audiotestsrc
产生合成音调。wavescope
消耗音频信号并渲染波形,就好像它是(诚然便宜的)示波器一样。我们已经使用了autoaudiosink
and autovideosink
。
转换元素(audioconvert
和audioresample
) videoconvert
是保证管道可以链接所必需的。实际上,音频和视频接收器的功能取决于硬件,在设计时您不知道它们是否会匹配由audiotestsrc
and生成的 Caps wavescope
。但是,如果 Caps 匹配,这些元素会以“直通”模式运行,不会修改信号,对性能的影响可以忽略不计。
/* Configure elements */
g_object_set (audio_source, "freq", 215.0f, NULL);
g_object_set (visual, "shader", 0, "style", 1, NULL);
为更好的演示而进行的小调整: audiotestsrc
控制波的频率的“freq”属性(215Hz 使波在窗口中看起来几乎是静止的),这种风格和着色 wavescope
器使波连续。使用基础教程 10: GStreamer工具gst-inspect-1.0
中描述的工具来学习这些元素的所有属性。
/* Link all elements that can be automatically linked because they have "Always" pads */
gst_bin_add_many (GST_BIN (pipeline), audio_source, tee, audio_queue, audio_convert, audio_sink,
video_queue, visual, video_convert, video_sink, NULL);
if (gst_element_link_many (audio_source, tee, NULL) != TRUE ||
gst_element_link_many (audio_queue, audio_convert, audio_sink, NULL) != TRUE ||
gst_element_link_many (video_queue, visual, video_convert, video_sink, NULL) != TRUE) {
g_printerr ("Elements could not be linked.\n");
gst_object_unref (pipeline);
return -1;
}
此代码块将所有元素添加到管道,然后链接可以自动链接的元素(如注释所述,带有 Always Pads 的元素)。
gst_element_link_many()
实际上可以将元素与request pad联系起来。它在内部请求 Pad,因此您不必担心链接的元素具有 Always 或 Request Pad。看起来很奇怪,这实际上很不方便,因为你之后仍然需要释放请求的 Pad,而且如果 Pad 是由自动请求的gst_element_link_many()
,很容易忘记。
通过始终手动请求request pads来避免麻烦,如下一个代码块所示。
/* Manually link the Tee, which has "Request" pads */
tee_audio_pad = gst_element_request_pad_simple (tee, "src_%u");
g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad));
queue_audio_pad = gst_element_get_static_pad (audio_queue, "sink");
tee_video_pad = gst_element_request_pad_simple (tee, "src_%u");
g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad));
queue_video_pad = gst_element_get_static_pad (video_queue, "sink");
if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK) {
g_printerr ("Tee could not be linked.\n");
gst_object_unref (pipeline);
return -1;
}
gst_object_unref (queue_audio_pad);
gst_object_unref (queue_video_pad);
要链接request pads,需要通过将它们“请求”到元素来获得它们。一个元素可能能够产生不同类型的请求垫,因此,在请求它们时,必须提供所需的垫模板名称。在该tee
元素的文档中,我们看到它有两个名为“sink”(用于其接收器垫)和“src_%u”(用于请求垫)的垫模板。我们要求使用gst_element_request_pad_simple()
.
然后,我们从需要链接这些请求垫的下游元素获取垫。这些是普通的 Always Pads,所以我们使用gst_element_get_static_pad()
.
最后,我们将焊盘与gst_pad_link()
. 这是内部使用的gst_element_link()
功能gst_element_link_many()
。
我们获得的水槽垫需要与 gst_object_unref()
. Request Pads 将在我们不再需要它们时释放,在程序结束时。
然后我们将管道设置为像往常一样播放,并等到产生错误消息或 EOS。剩下要做的就是清理请求的 Pads:
/* Release the request pads from the Tee, and unref them */
gst_element_release_request_pad (tee, tee_audio_pad);
gst_element_release_request_pad (tee, tee_video_pad);
gst_object_unref (tee_audio_pad);
gst_object_unref (tee_video_pad);
gst_element_release_request_pad()
从 释放焊盘tee
,但仍需要使用 . 取消引用(释放)它gst_object_unref()
。
5. 结论
本教程学习了:
- 如何使用queue元素使管道的某些部分在不同的线程运行
- 什么是请求pad和怎么将元素和请求pad连接起来,使用gst_element_request_pad_simple()和gst_pad_link()和gst_element_release_request_pad()。
- 如何使用tee元素在不同的分支中提供相同流
下节基础教程 8: 管道短路操作在此基础上,展示如何将数据手动注入和从正在运行的管道中提取。