summaryrefslogtreecommitdiff
path: root/pwg-scheduling.md
blob: 9ac72992acbdb05db3ebd55363eae9b7a35e64f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
---
title: Different scheduling modes
...

# Different scheduling modes

The scheduling mode of a pad defines how data is retrieved from (source)
or given to (sink) pads. GStreamer can operate in two scheduling mode,
called push- and pull-mode. GStreamer supports elements with pads in any
of the scheduling modes where not all pads need to be operating in the
same mode.

So far, we have only discussed `_chain ()`-operating elements, i.e.
elements that have a chain-function set on their sink pad and push
buffers on their source pad(s). We call this the push-mode because a
peer element will use `gst_pad_push ()` on a srcpad, which will cause
our `_chain ()`-function to be called, which in turn causes our element
to push out a buffer on the source pad. The initiative to start the
dataflow happens somewhere upstream when it pushes out a buffer and all
downstream elements get scheduled when their `_chain ()`-functions are
called in turn.

Before we explain pull-mode scheduling, let's first understand how the
different scheduling modes are selected and activated on a pad.

## The pad activation stage

During the element state change of READY-\>PAUSED, the pads of an
element will be activated. This happens first on the source pads and
then on the sink pads of the element. GStreamer calls the `_activate ()`
of a pad. By default this function will activate the pad in push-mode by
calling `gst_pad_activate_mode ()` with the GST\_PAD\_MODE\_PUSH
scheduling mode. It is possible to override the `_activate ()` of a pad
and decide on a different scheduling mode. You can know in what
scheduling mode a pad is activated by overriding the `_activate_mode
()`-function.

GStreamer allows the different pads of an element to operate in
different scheduling modes. This allows for many different possible
use-cases. What follows is an overview of some typical use-cases.

  - If all pads of an element are activated in push-mode scheduling, the
    element as a whole is operating in push-mode. For source elements
    this means that they will have to start a task that pushes out
    buffers on the source pad to the downstream elements. Downstream
    elements will have data pushed to them by upstream elements using
    the sinkpads `_chain ()`-function which will push out buffers on the
    source pads. Prerequisites for this scheduling mode are that a
    chain-function was set for each sinkpad using
    `gst_pad_set_chain_function ()` and that all downstream elements
    operate in the same mode.

  - Alternatively, sinkpads can be the driving force behind a pipeline
    by operating in pull-mode, while the sourcepads of the element still
    operate in push-mode. In order to be the driving force, those pads
    start a `GstTask` when they are activated. This task is a thread,
    which will call a function specified by the element. When called,
    this function will have random data access (through
    `gst_pad_pull_range ()`) over all sinkpads, and can push data over
    the sourcepads, which effectively means that this element controls
    data flow in the pipeline. Prerequisites for this mode are that all
    downstream elements can act in push mode, and that all upstream
    elements operate in pull-mode (see below).
    
    Source pads can be activated in PULL mode by a downstream element
    when they return GST\_PAD\_MODE\_PULL from the
    GST\_QUERY\_SCHEDULING query. Prerequisites for this scheduling mode
    are that a getrange-function was set for the source pad using
    `gst_pad_set_getrange_function ()`.

  - Lastly, all pads in an element can be activated in PULL-mode.
    However, contrary to the above, this does not mean that they start a
    task on their own. Rather, it means that they are pull slave for the
    downstream element, and have to provide random data access to it
    from their `_get_range ()`-function. Requirements are that the a
    `_get_range
                                            ()`-function was set on this pad using the function
    `gst_pad_set_getrange_function ()`. Also, if the element has any
    sinkpads, all those pads (and thereby their peers) need to operate
    in PULL access mode, too.
    
    When a sink element is activated in PULL mode, it should start a
    task that calls `gst_pad_pull_range ()` on its sinkpad. It can only
    do this when the upstream SCHEDULING query returns support for the
    GST\_PAD\_MODE\_PULL scheduling mode.

In the next two sections, we will go closer into pull-mode scheduling
(elements/pads driving the pipeline, and elements/pads providing random
access), and some specific use cases will be given.

## Pads driving the pipeline

Sinkpads operating in pull-mode, with the sourcepads operating in
push-mode (or it has no sourcepads when it is a sink), can start a task
that will drive the pipeline data flow. Within this task function, you
have random access over all of the sinkpads, and push data over the
sourcepads. This can come in useful for several different kinds of
elements:

  - Demuxers, parsers and certain kinds of decoders where data comes in
    unparsed (such as MPEG-audio or video streams), since those will
    prefer byte-exact (random) access from their input. If possible,
    however, such elements should be prepared to operate in push-mode
    mode, too.

  - Certain kind of audio outputs, which require control over their
    input data flow, such as the Jack sound server.

First you need to perform a SCHEDULING query to check if the upstream
element(s) support pull-mode scheduling. If that is possible, you can
activate the sinkpad in pull-mode. Inside the activate\_mode function
you can then start the task.

``` c
#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;
}
    
```

Once started, your task has full control over input and output. The most
simple case of a task function is one that reads input and pushes that
over its source pad. It's not all that useful, but provides some more
flexibility than the old push-mode case that we've been looking at so
far.

``` c
    #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

In the previous section, we have talked about how elements (or pads)
that are activated to drive the pipeline using their own task, must use
pull-mode scheduling on their sinkpads. This means that all pads linked
to those pads need to be activated in pull-mode. Source pads activated
in pull-mode must implement a `_get_range ()`-function set using
`gst_pad_set_getrange_function ()`, and that function will be called
when the peer pad requests some data with `gst_pad_pull_range ()`. The
element is then responsible for seeking to the right offset and
providing the requested data. Several elements can implement random
access:

  - Data sources, such as a file source, that can provide data from any
    offset with reasonable low latency.

  - Filters that would like to provide a pull-mode scheduling over the
    whole pipeline.

  - Parsers who can easily provide this by skipping a small part of
    their input and are thus essentially "forwarding" getrange requests
    literally without any own processing involved. Examples include tag
    readers (e.g. ID3) or single output parsers, such as a WAVE parser.

The following example will show how a `_get_range
()`-function can be implemented in a 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;
    }

In practice, many elements that could theoretically do random access,
may in practice often be activated in push-mode scheduling anyway, since
there is no downstream element able to start its own task. Therefore, in
practice, those elements should implement both a `_get_range
()`-function and a `_chain
()`-function (for filters and parsers) or a `_get_range
()`-function and be prepared to start their own task by providing
`_activate_* ()`-functions (for source elements).