英文原文:https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/scheduling.html
一个pad的调度模式定义了数据是如何从source pad中接收及发送至sink pad。Gstreamer能够运行两种调度模式,分别称为push和pull模式。Gstreamer支持element能够拥有可以任何方式调度的pad,一个element中的pads并不需要以相同的调度模式运行。
目前为止,我们只讨论了_chain()函数运行element,如,在sink pad上设置了chain函数的element将push数据到它的source pad中。我们称这种模式为push模式,因为一个同级的element将在一个source pad上调用gst_pad_push()函数,它能触发我们的_chain()函数被调用,这就使得我们的element在一个source pad上push出一个buffer。当上游开始数据流某处push出一个buffer时,反过来,下游的element的_chain()函数会被调用,此时,element开始调度,
在我们解释pull模式之前,让我们先来理解怎样选择不同的调度模式及在一个pad上激活。
The pad activation stage
在element从READY状态转变到PAUSED状态期间,一个element的pads将会被激活。这个首先发生在element的source pads上,然后再发生在sink pads上。Gstreamer调用一个pad的_activate()函数。默认情况下,这个函数会通过调用gst_pad_activate_mode()函数(传GST_PAD_MODE_PUSH这个参数)激活这个pad为push模式。覆写一个pad的_activate()函数以决定使用哪种调度模式是可以的。通过覆写_activate()函数,你会了解一个pad被激活会是处于何种调度模式。
Gstreamer允许一个element拥有不同的pads,使其可以以不同的调度模式运行。这种容许产生了许多不同的使用案例。以下是一些典型使用案例的总览。
- 如果一个element的所有pads被激活以push模式调度,此element整体都以push模式调度。对于source element,这意味着它必须开始一个task,此task会在source pad上push出buffers到下游的elements。下游的elements将使用sink pad的_chain()函数接收来自上游element所push出的数据,并通过此函数将数据push到当前element的source pad上。此调度模式的前提是每一个sink pad会使用gst_pad_set_chain_function()函数设置一个chain函数及所有下游elements都运行相同的模式。
- 可选择地,当element的source pads依然运行着push模式时,其sink pads通过运行pull模式,可以作为pipeline后的驱动力。为了成为pipeline的驱动力,当这些pads被激活时,需开始一个GstTask。这个task是一个线程,它将调用一个element中定义的函数。当这个函数被调用,它将在所有sink pad上随机访问到数据(通过gst_pad_pull_range()函数),然后将数据push到source pads上,这意味着此element控制着整个pipeline的数据流。此模式的前提是所有下游elements能够以push模式工作,以及所有上游elements以pull模式工作(见下)。
- 最后,一个element中的所有pads可被激活为pull模式。然而,相比于上面的情况,这并不意味着它们将自己开始一个task。而是意味着,它们是下游element pull的对象,它们必须在其_get_range()函数中提供随机访问数据的能力。要求是,此pad需使用gst_pad_set_getrange_function()函数设置一个_get_range()函数。同样的,如果element拥有任意的sink pads,所有这些pads(以及它们的对等体)也需要运行pull访问模式。
当一个sink element被激活为pull模式时,它应该在它的sink pad上开始一个任务,此任务调用gst_pad_pull_range()函数。它只能当上游收到调度询问并回复其支持GST_PAD_MODE_PULL调度模式时,才能如此做。
在接下来的两个小节,我们将更进一步地了解pull调度模式(elements/pads驱动pipeline,以及elements/pads提供随机访问),以及给出一些特殊用例。
Pads driving the pipeline
element的sink pads以pull模式工作,source pads以push模式工作(或element同有source pad,即它是一个sink element),pads开始一个task,此task驱动pipeline的数据流。在这个task函数里,你随机访问了所有的sink pads,并将数据push到source pads上。这对于不同类型的element是很有用的:
- 分流器、解析器和某些类型的解码器中,输入的数据是未被解析的(如MPEG-audio或视频流),由于从它们的输入中,更倾向于字节型的访问。然而,如果可能的话,这个element也应该准备好运行push模式。
- 特定类型的音频输出。此类输出要求控制它们的输入数据流,例如Jack sound server。
#include "filter.h"
#include <string.h>
static gboolean gst_my_filter_activate (GstPad * pad,
GstObject * parent);
static gboolean gst_my_filter_activate_mode (GstPad * pad,
GstObject * parent,
GstPadMode mode,
gboolean active);
static void gst_my_filter_loop (GstMyFilter * filter);
G_DEFINE_TYPE (GstMyFilter, gst_my_filter, GST_TYPE_ELEMENT);
static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
gst_pad_set_activate_function (filter->sinkpad, gst_my_filter_activate);
gst_pad_set_activatemode_function (filter->sinkpad,
gst_my_filter_activate_mode);
[..]
}
[..]
static gboolean
gst_my_filter_activate (GstPad * pad, GstObject * parent)
{
GstQuery *query;
gboolean pull_mode;
/* first check what upstream scheduling is supported */
query = gst_query_new_scheduling ();
if (!gst_pad_peer_query (pad, query)) {
gst_query_unref (query);
goto activate_push;
}
/* see if pull-mode is supported */
pull_mode = gst_query_has_scheduling_mode_with_flags (query,
GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE);
gst_query_unref (query);
if (!pull_mode)
goto activate_push;
/* now we can activate in pull-mode. GStreamer will also
* activate the upstream peer in pull-mode */
return gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, TRUE);
activate_push:
{
/* something not right, we fallback to push-mode */
return gst_pad_activate_mode (pad, GST_PAD_MODE_PUSH, TRUE);
}
}
static gboolean
gst_my_filter_activate_pull (GstPad * pad,
GstObject * parent,
GstPadMode mode,
gboolean active)
{
gboolean res;
GstMyFilter *filter = GST_MY_FILTER (parent);
switch (mode) {
case GST_PAD_MODE_PUSH:
res = TRUE;
break;
case GST_PAD_MODE_PULL:
if (active) {
filter->offset = 0;
res = gst_pad_start_task (pad,
(GstTaskFunction) gst_my_filter_loop, filter, NULL);
} else {
res = gst_pad_stop_task (pad);
}
break;
default:
/* unknown scheduling mode */
res = FALSE;
break;
}
return res;
}
一旦开始,你的task对输入和输出拥有绝对的控制权。最简单的一个task函数例子是读取输入并push数据到它的source pad上。这个并不总是有用的,但相对于老的push模式,它提供了更多的灵活性。
#define BLOCKSIZE 2048
static void
gst_my_filter_loop (GstMyFilter * filter)
{
GstFlowReturn ret;
guint64 len;
GstFormat fmt = GST_FORMAT_BYTES;
GstBuffer *buf = NULL;
if (!gst_pad_query_duration (filter->sinkpad, fmt, &len)) {
GST_DEBUG_OBJECT (filter, "failed to query duration, pausing");
goto stop;
}
if (filter->offset >= len) {
GST_DEBUG_OBJECT (filter, "at end of input, sending EOS, pausing");
gst_pad_push_event (filter->srcpad, gst_event_new_eos ());
goto stop;
}
/* now, read BLOCKSIZE bytes from byte offset filter->offset */
ret = gst_pad_pull_range (filter->sinkpad, filter->offset,
BLOCKSIZE, &buf);
if (ret != GST_FLOW_OK) {
GST_DEBUG_OBJECT (filter, "pull_range failed: %s", gst_flow_get_name (ret));
goto stop;
}
/* now push buffer downstream */
ret = gst_pad_push (filter->srcpad, buf);
buf = NULL; /* gst_pad_push() took ownership of buffer */
if (ret != GST_FLOW_OK) {
GST_DEBUG_OBJECT (filter, "pad_push failed: %s", gst_flow_get_name (ret));
goto stop;
}
/* everything is fine, increase offset and wait for us to be called again */
filter->offset += BLOCKSIZE;
return;
stop:
GST_DEBUG_OBJECT (filter, "pausing task");
gst_pad_pause_task (filter->sinkpad);
}
Providing random access
在前面的章节中,我们已经讨论了elements(或pads)是如何在它们的task中被激活来驱动pipeline,此时它们的sink pad必须是pull模式。这意味着,所有链接到这些pads的pads都需要被激活为pull模式。source pads被激活为pull模式需要实现一个_get_range()函数,并使用gst_pad_set_getrange_function()函数设置此函数。这样,当同级的pad调用gst_pad_pull_range()函数获取一些数据时,此函数将被调用。element此时对查询进入响应并提供需求的数据。多个elements能够实现随机访问:
- 提供来自任意位置且低延迟的数据的数据源,如一个文件源。
- 在整个pipeline上提供pull模式的filter
- 解析器通过跳过这些输入的一小部分能够很简单地提供这些,所以,解析器本质上是将收到的request逐个向前传送而不包含任何自身的处理。例子包含标签读取器(如ID3)或单个输出解析器,如一个WAVE解析器。
#include "filter.h"
static GstFlowReturn
gst_my_filter_get_range (GstPad * pad,
GstObject * parent,
guint64 offset,
guint length,
GstBuffer ** buf);
G_DEFINE_TYPE (GstMyFilter, gst_my_filter, GST_TYPE_ELEMENT);
static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
gst_pad_set_getrange_function (filter->srcpad,
gst_my_filter_get_range);
[..]
}
static GstFlowReturn
gst_my_filter_get_range (GstPad * pad,
GstObject * parent,
guint64 offset,
guint length,
GstBuffer ** buf)
{
GstMyFilter *filter = GST_MY_FILTER (parent);
[.. here, you would fill *buf ..]
return GST_FLOW_OK;
}
许多elements在理论上可以进入随机访问,实际上却可能总被激活为push模式,这是因为没有下游的element能够开始其自身的task。因此,实际上,这些elements应该同时实现一个
_get_range()函数和一个
_chain()函数(对于filter和parser)并通过提供
_activate_*()函数以准备开始它们自身的task(对于source elements)。