GStreamer Basic tutorial 7 Multithreading & Pad Availability

"GStreamer 基本教程7: 媒体格式和Pad可用性"

Posted by Stephen on March 19, 2022

前言

本文是GStreamer学习笔记,也可以看成是对原文的意译。

这些教程描述了理解其余教程所需的GStreamer主题。

GStreamer教程:

基础教程 : GStreamer 介绍

基础教程 1: Hello world!

基础教程 2: GStreamer 概念

基础教程 3: 动态管道

基础教程 4: 时间管理

基础教程 5: GUI工具包集成

基础教程 6: 媒体格式和pad功能

基础教程 7: 多线程和Pad可用性

基础教程 8: 管道短路操作

基础教程 9: 媒体信息收集

基础教程 10: GStreamer工具

基础教程 11: 调试工具

基础教程 12: 流媒体

基础教程 13: 播放速度

基础教程 14: 有用的元素

基础教程 16: 特定平台元素

环境

系统环境

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消耗音频信号并渲染波形,就好像它是(诚然便宜的)示波器一样。我们已经使用了autoaudiosinkand autovideosink

转换元素(audioconvertaudioresamplevideoconvert是保证管道可以链接所必需的。实际上,音频和视频接收器的功能取决于硬件,在设计时您不知道它们是否会匹配由audiotestsrcand生成的 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: 管道短路操作在此基础上,展示如何将数据手动注入和从正在运行的管道中提取。

后记