GStreamer Tutorial 7中文翻译
文章目录
前言
由于工作原因,用的GStreamer的图像解码库,所以记录GStreamer Tutorial 的中文翻译和个人的理解以便学习。若有不足请多指教。侵删。
Basic tutorial 7: Multithreading and Pad Availability
目的
GStreamer自动处理多线程,但在一些情况下,你需要手动对线程进行解耦。本教程展示了如何做到这一点,此外,完成了关于Pad可用性的阐述。更确切地说,本文件解释:
- 如何为
pipeline
的某些部分创建新的执行线程; - 什么是Pad的可用性;
- 如何重复流。
简介
多线程
GStreamer 是一个多线程架构。这意味着在架构内部它按需要创建和销毁线程,例如,从应用线程中解耦Streaming
(流)。此外,插件也可以对它们自有的进程自由的创建线程,例如,一个视频解码器可以创建4个线程来完全利用4核CPU。
除此之外,在构建pipeline时,一个应用可以显式地指定某个分支(pipeline
的一部分)运行在一个另一个的线程上(例如,使音频和视频解码器同时工作)。
这是通过queue
(队列)元素完成。其工作方式如下。sink pad
将数据压入队列并返回控制。在另一个线程,数据被取出并使用。queue
元素也被用于数据缓存,将在后面的流处理例程中展示。queue
的大小可由属性控制。
pipeline 示例
这个示例创建了如下的pipeline
:
source
是一个人造的声音信号(一个连续的音调),它被一个tee
元素(该元素将它从source pad
接收的所有东西传输给它的sink pad
)分割。一个分支发送声音信号到声卡,另一个则呈现波浪状的视频并将其显示在屏幕上。
正如上图所示,每个queue
创造了一个新线程,所以pipeline
上运行着三个线程。为了同步,有多个sink
的pipeline
通常是多线程的。因为sink
经常会阻塞并等待其它所有的sink
都准备好才继续执行,如果它们在同一个线程中运行,那么会阻塞在第一个sink
,无法继续执行。
请求pads(Request pads)
在基础例程3中(Basic tutorial 3: Dynamic pipelines)我们接触过uridecodebin
元素,它一开始就没有pads
并且它放置在数据开始传输和元素开始接收数据前。这被称为Sometimes Pads
(理解为有时需要使用的pad
),与之相对的是恒存在或者一直需要的pads
,被称为Always pads
;
第三种pad
是Request pad
,它按需创造。最典型的例子是tee
元素,它有一个sink pad
并且没有初始的source pads
,它需要请求pads
并添加它们。通过这个方式,一个输入的流可以被复制任意多次(因为tee
只是将收到的流传输给sink pads
,当它请求多个sink pads
时,就能复制流)。但缺点就是将Request pads
和其他元素链接并不像Always pads
那样自动链接,正如代码走读中所示。
此外,在运行或者暂停状态下,请求(释放)pads
你需要注意额外的事项(pad
阻塞),这在本例中没有提到。在空和准备状态下请求(释放)pads
是安全的。
简单的多线程示例
将代码复制到basic-tutorial-7.c
中,或在你的GStreamer路径中找到它。
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;
}
代码走读
/* 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
和autovideosink
在前面的例子中接触过。
转换元素(audioconvert
,audioresample
和videoconvert
)是用来保证pipeline
可以正常链接。实际上,audio sink
和video sink
的能力(capabilities )取决于硬件,在构建pipeline
的时候你并不知道它们是否与audiotestsrc
和wavescope
所产生的Caps
相匹配。但是,如果匹配,这些元素将在"pass-through"(直通)模式下工作,无需修改信号,对性能的影响很小。
/* Configure elements */
g_object_set (audio_source, "freq", 215.0f, NULL);
g_object_set (visual, "shader", 0, "style", 1, NULL);
为更好的演示而进行的小调整:audiotestsrc
的频率特性控制了波的频率(215Hz使波在窗口中看起来几乎是静止的),这种风格和用于波长镜的着色器使波连续。使用基础教程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;
}
这部分代码将所有元素添加到pipeline
中并链接可以自动链接的部分(Always Pads
)。至于Request Pads
,实际上可以使用gst_element_link_many()
自动链接,但仍需事后手动释放Request Pads
。由于一开始是自动链接的,所以很容易遗忘释放的步骤,因此,正如下个代码块所示,我们手动创建和释放Request Pads
。
/* Manually link the Tee, which has "Request" pads */
tee_audio_pad = gst_element_get_request_pad(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_get_request_pad(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 pad
,需要通过向元素请求来获得它们。一个元素可以产生多种Request pads
,因此在请求时,需要指定目标Pad
的模板名称。在文档中,对于tee
元素,它有两个模板pads
,“sink”(用于sink pad
)和"src_%u"(用于Request pad
)。我们使用gst_element_request_pad_simple()
从tee
请求两个pad
(用于音频和视频分支)。
然后我们从下游的需要和Request pad
链接的元素获取pads
。这些通常是Always pads
,我们用 gst_element_get_static_pad()
来获取。
Request pad
对应gst_element_request_pad_simple()
方法。由于tee
没有source pad
,所以需要对应输出创建音频和视频的Request pad
Always pads
对应gst_element_get_static_pad()
方法。获取与Request pad
相连的Always pads
的信息。
最后,我们使用gst_pad_link()
链接pads
。这个函数也在gst_element_link()
和gst_element_link_many()
内部使用。
我们所获取的sink pads
需要使用gst_object_unref()
函数释放。Request pads
将在程序的末尾,我们不用它时被释放。
然后,我们和之前一样设置pipeline
为播放状态,并阻塞直到错误信息或者流结束信息产生。剩下的就是释放请求的Request 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
的pads
,但它还需要使用gst_object_unref()
来重置引用。
结论
这个例程展示了:
- 如何利用
queue
元素使得pipeline
的某部分运行在另一个线程; - 什么是
Request Pad
以及如何使用gst_element_request_pad_simple()
,gst_pad_link()
和gst_element_release_request_pad()
将元素和Request Pad
链接; - 如何利用
tee
元素在不同分支使用同一个流。
下一篇教程将在本教程的基础上构建,展示如何将数据手动地注入到运行中的pipeline
中并从中提取数据。