Gstreamer插件教程3.2—高级概念(Advanced Concepts):不同的调度模式(Different scheduling modes)

本文详细介绍了GStreamer中两种调度模式:push和pull的工作原理及其应用场景。解释了如何选择和激活不同的调度模式,并通过示例代码展示了如何实现pull模式驱动的数据流以及提供随机访问能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

英文原文: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。
首先,你应该运行调度询问以检查上游elements是否支持pull调度模式。如果支持,你可以激活sink pad为pull模式。在激活模式的函数中,你可以开始一个task。

#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解析器。
以下的例子展示了一个 _get_range()函数是怎么在一个source element上被实现的:
#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)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值