GStreamer Tutorial 中文翻译:Basic tutorial 7: Multithreading and Pad Availability

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
pipeline

source是一个人造的声音信号(一个连续的音调),它被一个tee元素(该元素将它从source pad接收的所有东西传输给它的sink pad)分割。一个分支发送声音信号到声卡,另一个则呈现波浪状的视频并将其显示在屏幕上。
正如上图所示,每个queue创造了一个新线程,所以pipeline上运行着三个线程。为了同步,有多个sinkpipeline通常是多线程的。因为sink经常会阻塞并等待其它所有的sink都准备好才继续执行,如果它们在同一个线程中运行,那么会阻塞在第一个sink,无法继续执行。

请求pads(Request pads)

在基础例程3中(Basic tutorial 3: Dynamic pipelines)我们接触过uridecodebin元素,它一开始就没有pads并且它放置在数据开始传输和元素开始接收数据前。这被称为Sometimes Pads(理解为有时需要使用的pad),与之相对的是恒存在或者一直需要的pads,被称为Always pads
第三种padRequest 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通过声音信号产生波形,类似示波器。autoaudiosinkautovideosink在前面的例子中接触过。
转换元素(audioconvertaudioresamplevideoconvert)是用来保证pipeline可以正常链接。实际上,audio sinkvideo sink的能力(capabilities )取决于硬件,在构建pipeline的时候你并不知道它们是否与audiotestsrcwavescope所产生的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()释放teepads,但它还需要使用gst_object_unref()来重置引用。

结论

这个例程展示了:

  • 如何利用queue元素使得pipeline的某部分运行在另一个线程;
  • 什么是Request Pad以及如何使用gst_element_request_pad_simple(), gst_pad_link()gst_element_release_request_pad()将元素和Request Pad链接;
  • 如何利用tee元素在不同分支使用同一个流。

下一篇教程将在本教程的基础上构建,展示如何将数据手动地注入到运行中的pipeline中并从中提取数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值