摘要

  GStreamer框架会自动处理多线程的逻辑,但在某些情况下,我们仍然需要根据实际的情况自己将部分Pipeline在单独的线程中执行,本文将介绍如何处理这种情况。

GStreamer多线程

  GStreamer框架是一个支持多线程的框架,线程会根据Pipeline的需要自动创建和销毁,例如,将媒体流与应用线程解耦,应用线程不会被GStreamer的处理阻塞。而且,GStreamer的插件还可以创建自己所需的线程用于媒体的处理,例如:在一个4核的CPU上,视频解码插件可以创建4个线程来最大化利用CPU资源。
  此外,在创建Pipeline时,我们还可以指定某个Pipeline的分支在不同的线程中执行(例如,使audio、video同时在不同的线程中进行解码)。这是通过queue Element来实现的,queue的sink pad仅仅将数据放入队列,另外一个线程从队列中取出数据,并传递到下一个Element。queue通常也被用于作为数据缓冲,缓冲区大小可以通过queue的属性进行配置。

  在上面的示例Pipeline中,souce是audiotestsrc,会产生一个相应的audio信号,然后使用tee Element将数据分为两路,一路被用于播放,通过声卡输出,另一路被用于转换为视频波形,用于输出到屏幕。
示例图中的红色阴影部分表示位于同一个线程中,queue会创建单独的线程,所以上面的Pipeline使用了3个线程完成相应的功能。拥有多个sink的Pipeline通常需要多个线程,因为在多个sync间进行同步的时候,sink会阻塞当前所在线程直到所等待的事件发生。

示例代码

示例代码将创建上图所示的Pipeline。

#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 -;
} /* Configure elements */
g_object_set (audio_source, "freq", 215.0f, NULL);
g_object_set (visual, "shader", , "style", , 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 -;
} /* 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 -;
}
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 ;
}

保存以上代码,执行下列编译命令即可得到可执行程序:

gcc basic-tutorial-.c -o basic-tutorial- `pkg-config --cflags --libs gstreamer-1.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");

  首先创建所需的Element:audiotestsrc会产生测试的音频波形数据。wavescope 会将输入的音频数据转换为波形图像。audioconvert,audioresample,videoconvert保证了Pipeline中各个Element之间的数据可以互相兼容,使得Pipeline能够被正确的link起来,如果不需要对数据进行转换,这些Element会直接将数据发送到下一个Element,这种情况下的性能影响可以忽略不计。

/* Configure elements */
g_object_set (audio_source, "freq", 215.0f, NULL);
g_object_set (visual, "shader", , "style", , NULL);

  这里修改相应Element的参数,使得输出结果更直观。“freq”会设置audiotestsrc输出波形的频率为215Hz,设置“shader”和“style”使得波形更加连续。其他的参数可以通过gst-inspect查看。

/* 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 -;
}

  这里我们使用gst_element_link_many 将多个Element连接起来,需要注意的是,这里我们只连接了拥有Always Pad的Eelement。虽然gst_element_link_many() 能够在内部处理Request Pad的情况,但我们仍然需要单独释放Request Pad,如果直接使用此函数连接所有的Element,这样容易忘记释放Request Pad。因此我们使用下面的代码单独处理Request Pad。

/* 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 -;
}
gst_object_unref (queue_audio_pad);
gst_object_unref (queue_video_pad);

  为了能够连接到Request Pad,我们需要主动的向Element取得相应的Pad。由于一个Element可以提供不同的Request Pad,所以我们需要指定所需的“Pad Template”,Element提供的Pad Template可以通过gst-inspect查看。从下面的结果可以发现,tee提供了2种类型的模板, ”sink“ 和“src_%u"。

$ gst-inspect-1.0  tee
...
Pad Templates:
SRC template: 'src_%u'
Availability: On request
Has request_new_pad() function: gst_tee_request_new_pad
Capabilities:
ANY SINK template: 'sink'
Availability: Always
Capabilities:
ANY
...

  由于我们这里需要的是2个Source Pad,所以我们通过gst_element_get_request_pad (tee, "src_%u")获取两个Request Pad分别用于audio和video。queue的Sink Pad是Alwasy Pad,所以我们直接使用gst_element_get_static_pad 获取其Sink Pad。最后再通过gst_pad_link()将其连接起来,在gst_element_link()和gst_element_link_many()内部也是使用此函数连接两个Element的Pad。

需要注意的是,我们通过Element获取到的Pad的引用计数会自动增加,因此我们需要调用gst_object_unref()释放相关的引用,对于Request Pad,我们需要在Pipeline执行完成后进行释放。

/* 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);

除了播放完成后正常的资源释放外,我们还要对Request进行释放,需要首先调用gst_element_release_request_pad(),最后再释放相应的对象。

总结

我们在本文中了解了:

  • 如何通过queue让Pipeline运行在多个线程上。
  • 如何通过gst_element_get_request_pad(), gst_pad_link() gst_element_release_request_pad() 对Request Pad进行操作。
  • 如何使用tee将一路媒体数据分为多路。

引用

https://gstreamer.freedesktop.org/documentation/tutorials/basic/multithreading-and-pad-availability.html?gi-language=c

作者:John.Leng
本文版权归作者所有,欢迎转载。商业转载请联系作者获得授权,非商业转载请在文章页面明显位置给出原文连接.

GStreamer基础教程08 - 多线程的更多相关文章

  1. 【GStreamer开发】GStreamer基础教程08——pipeline的快捷访问

    目标 GStreamer建立的pipeline不需要完全关闭.有多种方法可以让数据在任何时候送到pipeline中或者从pipeline中取出.本教程会展示: 如何把外部数据送到pipeline中 如 ...

  2. 【GStreamer开发】GStreamer基础教程07——多线程和Pad的有效性

    目标 GStreamer会自动处理多线程这部分,但在有些情况下,你需要手动对线程做解耦.本教程会教你怎样才能做到这一点,另外也展示了Pad的有效性.主要内容包括: 如何针对部分的pipeline建立一 ...

  3. GStreamer基础教程09 - Appsrc及Appsink

    摘要 在我们前面的文章中,我们的Pipline都是使用GStreamer自带的插件去产生/消费数据.在实际的情况中,我们的数据源可能没有相应的gstreamer插件,但我们又需要将数据发送到GStre ...

  4. 【GStreamer开发】GStreamer基础教程14——常用的element

    目标 本教程给出了一系列开发中常用的element.它们包括大杂烩般的eleemnt(比如playbin2)以及一些调试时很有用的element. 简单来说,下面用gst-launch这个工具给出一个 ...

  5. 【GStreamer开发】GStreamer基础教程10——GStreamer工具

    目标 GStreamer提供了一系列方便使用的工具.这篇教程里不牵涉任何代码,但还是会讲一些有用的内容: 如何在命令行下建立一个pipeline--完全不使用C 如何找出一个element的Capab ...

  6. GStreamer基础教程02 - 基本概念

    摘要 在 Gstreamer基础教程01 - Hello World中,我们介绍了如何快速的通过一个字符串创建一个简单的pipeline.为了能够更好的控制pipline中的element,我们需要单 ...

  7. 【GStreamer开发】GStreamer基础教程05——集成GUI工具

    目标 本教程展示了如何在GStreamer集成一个GUI(比如:GTK+).最基本的原则是GStreamer处理多媒体的播放而GUI处理和用户的交互. 在这个教程里面,我们可以学到: 如何告诉GStr ...

  8. Java基础教程:多线程基础(1)——基础操作

    Java:多线程基础(1) 实现多线程的两种方式 1.继承Thread类 public class myThread extends Thread { /** * 继承Thread类,重写RUN方法. ...

  9. Java基础教程:多线程基础(4)——Lock的使用

    Java基础教程:多线程基础(4)——Lock的使用 快速开始 Java 5中Lock对象的也能实现同步的效果,而且在使用上更加方便. 本节重点的2个知识点是:ReentrantLock类的使用和Re ...

随机推荐

  1. PythonI/O进阶学习笔记_3.1面向对象编程_python的多态和鸭子类型

    前言: 与第一篇的面向对象内容不同的是,第一篇中的面向对象更多的是与类.对象结合起来的概念粗浅理解,就是在编程历史中诞生的一种思想方法. 这篇的面向对象编程,更多落实到在语言设计实现中,是如何体现面向 ...

  2. HDU 6319

    题意略. 思路:倒着使用单调队列,大的放在前,小的放在后. 详见代码: #include<bits/stdc++.h> using namespace std; typedef long ...

  3. three.js实现球体地球城市模拟迁徙

    概况如下:1.SphereGeometry实现自转的地球:2.THREE.ImageUtils.loadTexture加载地图贴图材质:3.THREE.Math.degToRad,Math.sin,M ...

  4. 配置springboot项目使用外部tomcat

    配置springboot项目使用外部tomcat 1.在pom文件中添加依赖 <!--使用自带的tomcat--> <dependency> <groupId>or ...

  5. nsq源码分析

    nsq的源码比较简单,值得一读,特别是golang开发人员,下面重点介绍nsqd,看完这篇文章希望你能对消息队列的原理和实现有一定的了解. nsqd是一个守护进程,负责接收,排队,投递消息给客户端,并 ...

  6. String类的intern()方法,随常量池发生的变化

    JVM的知识这里总结的很详细:https://github.com/doocs/jvm/blob/master/README.md,因此在本博客也不会再对其中的东西重复总结了. intern的作用 简 ...

  7. Spring boot 全局配置 properties或者yml文件报错

    主要问题是没有扫描到配置文件 在pom文件里面<build>    </build>中加上以下代码就可以保证能扫描到了 <resources> <resour ...

  8. DataReader转换

    public static partial class Extension { private static ConcurrentDictionary<Type, ConcurrentDicti ...

  9. 【Offer】[21] 【调整数组顺序使奇数位于偶数前面】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,(偶数之间和奇数 ...

  10. vuex-class用法

    vuex-class可以包装vuex的写法,使代码简化 Installation $ npm install --save vuex-class Example import Vue from 'vu ...