作者:fengcc 原创文章 转载请注明出处


GStreamer 是一个基于流水线的多媒体框架,基于 GObject,以 C 语言写成。

凭借 GStreamer,程序员可以很容易地创建各种多媒体功能组件,包括简单的音频回放,音频和视频播放,录音,流媒体和音频编辑。基于流水线设计,可以创建诸如视频编辑器、流媒体广播和媒体播放器等等的很多多媒体应用。


GstTask/GstTaskPool — streaming threads

Gstreamer 将GstElementGstPad中关于数据流处理的线程封装成 GstTask,并提供gst_task_start(),gst_task_pause(),gst_task_stop()等接口,使数据流的处理更加方便。例如:GstPad通常会启动一个GstTask从另一个 pad 上拉数据或者推数据到另一个 pad。

下面根据它提供的几个接口来详细介绍GstTask内部的实现。


(1)创建任务

GstTask *
gst_task_new (GstTaskFunction func,
gpointer user_data,
GDestroyNotify notify);

创建一个任务,该任务稍后将会在新线程里重复调用func函数,以user_data 为参数。注意,此时还没有创建或者启动一个新的线程。

GstTaskgst_task_class_init() 函数中会调用gst_task_pool_get_default()函数,该函数代码如下:

GstTaskPool *
gst_task_pool_get_default (void)
{
static GstTaskPool *pool = NULL; if (g_once_init_enter (&pool)) { //整个程序周期只会进入一次
GstTaskPool *_pool = gst_task_pool_new (); gst_task_pool_prepare (_pool, NULL);
g_once_init_leave (&pool, _pool);
} return gst_object_ref (pool);
}

它利用g_once_init_enter()/g_once_init_leave()操作来确保整个程序的运行周期中只会创建一个默认GstTaskPool结构,GstTaskPool结构是对 glib 线程池的封装。之后再调用gst_task_pool_prepare()函数,该函数会调用GstTaskPooldefault_prepare()函数来创建一个默认的 glib 线程池,使用的是上面提到的g_thread_pool_new()接口。

传递给g_thread_pool_new()的第一个参数为default_func函数:

static void
default_func (TaskData * tdata, GstTaskPool * pool)
{
GstTaskPoolFunction func;
gpointer user_data; func = tdata->func;
user_data = tdata->user_data;
g_slice_free (TaskData, tdata); func (user_data); //运行自定义的任务
}

它的做法很简单,拆解传进来的参数,调用相应的函数,从而让线程池中的每个线程运行自定义的任务。

当我们调用gst_task_new()函数创建一个新的任务时,会触发GstTaskgst_task_init()函数,该函数会将GstTask结构体中的GstTaskPool *pool变量指向刚才创建的默认的GstTaskPool。所以,整个程序中所有的GstTask 依赖的都是同一个GstTaskPool结构,即所有的GstTask线程都运行在同一个线程池中。


(2)启动任务

gboolean
gst_task_start (GstTask *task);

或:

gboolean
gst_task_set_state (GstTask *task,
GstTaskState state);

对于GstTask的任务,有三种状态:

  • GST_TASK_STARTED:任务已经启动,正在相应的线程中运行。
  • GST_TASK_STOPPED:任务已经停止,此时还没有启动相应的线程,或者线程已经运行结束退出。
  • GST_TASK_PAUSED:任务暂停。任务对应的线程没有退出,处于暂停状态,其实就是让线程阻塞在某个条件变量上。

gst_task_start()实际上就是调用gst_task_set_state (task, GST_TASK_STARTED),将任务设置为开始状态。

gst_task_set_state()会先记录任务的原始状态为old,再将任务设置为新的状态,然后根据old,执行相关的操作,关键代码如下:

/* if the state changed, do our thing */
old = GET_TASK_STATE (task);
if (old != state) {
SET_TASK_STATE (task, state); switch (old) {
case GST_TASK_STOPPED:
/* If the task already has a thread scheduled we don't have to do
* anything. */
if (G_UNLIKELY (!task->running) &&
(!task->priv->scheduleable || (task->priv->should_schedule
&& state == GST_TASK_STARTED)))
res = start_task (task);
break;
case GST_TASK_PAUSED:
/* when we are paused, signal to go to the new state */
GST_TASK_SIGNAL (task);
break;
case GST_TASK_STARTED:
/* if we were started, we'll go to the new state after the next
* iteration. */
break;
}
}
  • 如果旧状态是GST_TASK_STOPPED,则新状态肯定是 GST_TASK_STARTED,则调用start_task函数,该函数稍候会解释。
  • 如果旧状态是GST_TASK_PAUSED,则增加相应的条件变量,这样,任务对应的线程就会结束在此条件变量的阻塞,从而完成状态转换。
  • 如果旧状态是GST_TASK_STARTED,这个注释的解释没有看懂,还要继续琢磨一下,抱歉。

start_task()函数中,以gst_task_func()函数为参数调用gst_task_pool_push()gst_task_pool_push()是对default_push()函数的封装。default_push()函数代码如下:

static gpointer
default_push (GstTaskPool * pool, GstTaskPoolFunction func,
gpointer user_data, GError ** error)
{
TaskData *tdata; tdata = g_slice_new (TaskData);
tdata->func = func;
tdata->user_data = user_data; GST_OBJECT_LOCK (pool);
if (pool->pool)
g_thread_pool_push (pool->pool, tdata, error);
else {
g_slice_free (TaskData, tdata);
}
GST_OBJECT_UNLOCK (pool); return NULL;
}

funcuser_data封装成TaskData结构体,作为参数调用上面提到的g_thread_pool_push()函数将任务插入到线程池的任务列表中。TaskData结构体的拆解在上面提到的default_func()函数中,线程池中的每个线程启动时都会调用该函数拆解参数,然后线程便切换到gst_task_func()函数运行。

注意:这里的funcgst_task_func()函数,user_data是任务对应的GstTask结构体。而使用gst_task_new()创建任务时传入的自定义函数和参数是分别保存在GstTasktask->functask->user_data成员变量中,将会在gst_task_func()中采用task->func (task->user_data)的方式调用。

最后,每个线程中运行的其实都是gst_task_func()函数,该函数关键代码如下:

while (G_LIKELY (GET_TASK_STATE (task) != GST_TASK_STOPPED)) {
GST_OBJECT_LOCK (task); if (G_UNLIKELY (priv->scheduleable
&& GST_TASK_STATE (task) == GST_TASK_PAUSED)) {
GST_OBJECT_UNLOCK (task);
break;
} while (G_UNLIKELY (!priv->scheduleable
&& GST_TASK_STATE (task) == GST_TASK_PAUSED)) {
g_rec_mutex_unlock (lock); GST_TASK_SIGNAL (task);
GST_INFO_OBJECT (task, "Task going to paused");
GST_TASK_WAIT (task);
GST_INFO_OBJECT (task, "Task resume from paused");
GST_OBJECT_UNLOCK (task);
/* locking order.. */
g_rec_mutex_lock (lock);
GST_OBJECT_LOCK (task);
} if (G_UNLIKELY (GET_TASK_STATE (task) == GST_TASK_STOPPED)) {
GST_OBJECT_UNLOCK (task);
break;
} else {
GST_OBJECT_UNLOCK (task);
} // 调用用户自定义的函数。
task->func (task->user_data); if (priv->scheduleable)
break;
}

如果任务被设置成GST_TASK_STOPPED状态,则退出循环,结束线程运行。若任务为暂停状态,则在对应的条件变量上阻塞,否则,就一直循环调用用户自定义函数处理数据流。

Gstreamer 数据流线程(GstTask / GstTaskPool)分析的更多相关文章

  1. JAVA线程池的分析和使用

    1. 引言 合理利用线程池能够带来三个好处.第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗.第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行.第三:提 ...

  2. [转]ThreadPoolExecutor线程池的分析和使用

    1. 引言 合理利用线程池能够带来三个好处. 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. 第 ...

  3. Java 线程池原理分析

    1.简介 线程池可以简单看做是一组线程的集合,通过使用线程池,我们可以方便的复用线程,避免了频繁创建和销毁线程所带来的开销.在应用上,线程池可应用在后端相关服务中.比如 Web 服务器,数据库服务器等 ...

  4. 线程组ThreadGroup分析详解 多线程中篇(三)

    线程组,顾名思义,就是线程的组,逻辑类似项目组,用于管理项目成员,线程组就是用来管理线程. 每个线程都会有一个线程组,如果没有设置将会有些默认的初始化设置 而在java中线程组则是使用类ThreadG ...

  5. ThreadPoolExecutor线程池的分析和使用

    1. 引言 合理利用线程池能够带来三个好处. 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. 第 ...

  6. Linux 线程实现机制分析 Linux 线程模型的比较:LinuxThreads 和 NPTL

    Linux 线程实现机制分析 Linux 线程实现机制分析  Linux 线程模型的比较:LinuxThreads 和 NPTL http://www.ibm.com/developerworks/c ...

  7. 聊聊并发(三)Java线程池的分析和使用

    1.    引言 合理利用线程池能够带来三个好处.第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗.第二:提高响应速度.当任务到达时,任务可以不需要的等到线程创建就能立即执行. ...

  8. java并发包&线程池原理分析&锁的深度化

          java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的, ...

  9. 【Java线程与内存分析工具】VisualVM与MAT简明教程

    目录 前言 VisualVM 安装与配置 本地使用 远程监控 MAT 使用场景 安装与配置 获得堆转储文件 分析堆转储文件 窥探对象内存值 堆转储文件对比分析 总结 前言 本文将简要介绍Java线程与 ...

随机推荐

  1. CTSC && APIO 总结

    先说CTSC吧,第一试其实不难但是下意识觉得CTSC不就只能骗分吗,然后就全上暴力了.然而第二题再一次看漏了条件,即答案总和小于等于1e6.第三题现在回想起来要不然就是没有思考,要不然就是没学过数学, ...

  2. real-time application

    http://www.hanselman.com/blog/InstallingAndRunningNodejsApplicationsWithinIISOnWindowsAreYouMad.aspx ...

  3. 用MATLAB画函数的曲线

    用MATLAB画函数曲线 2013年8月11日 命令funtool 这是单变量函数分析的交互界面,比较方便,特别适用于y=f(x)型,即y与x分开的函数形式.见下图

  4. TeeChart的最小步长和最大步长

    /// <summary> /// 坐标轴的最大步长 /// </summary> public double MajorStep { get { return axis.Ca ...

  5. Drainage Ditches(Dinic最大流)

    http://poj.org/problem?id=1273 用Dinic求最大流的模板题,注意会有重边. 邻接矩阵建图 #include<stdio.h> #include<str ...

  6. JavaScript---网络编程(8)-DHTML技术演示(1)

    DHTML技术使用的基本思路: 1. 用标签封装数据-html范畴 2. 定义样式-css范畴 3. 明确事件源.事件和要处理的节点-dom范畴 4. 明确具体的操作方式,其实就是事件的处理内容(过程 ...

  7. linux 命令拾遗

    man ascii 这个命令会打印出八进制.十六进制和十进制的ASCII码表. xxd xxd可以生成所给与文件的十六进制拷贝,也可以将编辑好的十六进制拷贝还原成二进制格式.它也可以将十六进制拷贝输出 ...

  8. GPU 的硬件基本概念,Cuda和Opencl名词关系对应

    GPU 的硬件基本概念 Nvidia的版本: 实际上在 nVidia 的 GPU 里,最基本的处理单元是所谓的 SP(Streaming Processor),而一颗 nVidia 的 GPU 里,会 ...

  9. Linux下动态调整LVM文件系统大小

    LINUX下可以通过LVM动态调整一个已挂载的文件系统大小 LV可以根据需求增大或减小,但是LV改变大小以后,在LV中的文件系统也需要相应的改变大小.这个概念非常重要,如果没有相应的调整LV中文件系统 ...

  10. Shell简介:什么是Shell,Shell命令的两种执行方式

    Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的.Shell既是一种命令语言,又是一种程序设计语言.作为命令语言,它交互式地解释 ...