gstreamer/docs/random/omega/sched/chains

90 lines
5.6 KiB
Text
Raw Normal View History

A new concept in scheduling is that of chains of elements that need to be schedule separately, even in the same set of
managed elements (which is the set of elements that the Bin in question [a pipeline or thread] is responsible for).
An example would by anywhere you have a non-blocking queue in place for buffering. This kind of element might be
useful in cases where the scheduling on a buffer level is tight enough that deadlocks might occur.
The scheduler will find chains by traversing the pipeline through the list of managed elements. A chain boundary is
anywhere you have a 'DECOUPLED' element. A DECOUPLED element is one where there is no direct correlation between the
activities of the various pads. A source fits this description, although the normal single-pad source is the
degenerate case. A queue more properly fits the bill, since pushing a buffer at the sink pad doesn't trigger anything
on the src pad, and vice versa. A multi-src async source is probably the best example, since you want to leave the
scheduling up to the elements connected to it.
Anyway, first the simple case:
fakesrc -> fakesink
Both of them should probably have the DECOUPLED bit set, at least to be true to the nature of the actual fake elements.
These two end up being a chain, and scheduling has to be set up for the chain. There are no cothreaded elements in the
chain, which means it's relatively easy. The goal is to find a single entry into the chain, which can be called in a
loop to get things done. Since the fakesrc is DECOUPLED, and we'd be messing with the source pad, it has lower
priority than a DECOUPLED sink pad, so the fakesrc's sink pad is the ideal entry into the chain. This can be
simplified into saying that the fakesink is the entry.
In the end, the code to do this boils down to:
buf = gst_pad_pull (fakesink->sinkpad);
gst_pad_push (fakesink->sinkpad, buf);
Because of the way things are no implemented for scheduling, turning it around and making the source the entry has no
effect as far as the efficiency. That's because _get no longer directly calls gst_pad_push(), so we have to do it
outside. No big deal, it boils down to the same thing I think, modulo a cache-line of stack (i.e. one or two fewer
this way).
If we put an identity in the middle:
fakesrc -> identity -> fakesink
then we have the same thing, except that there's now an element that isn't DECOUPLED, so it gets higher priority. That
means the identity is now the entry, and when we push the buffer into its chain function, the fakesink gets called.
Now, we can make this much more complex, with the following elementary echo meta-filter:
|=====| -> delay1 -> |=====|
| | | |
-> queue -> | tee | -> delay2 -> | mix | -> queue ->
| | | |
|=====| -> delay3 -> |=====|
The tee takes a buffer in and spits three out, delay shifts the timestamps around and possibly reframes things to be
friendly. mix takes the three buffers and simply sums them (they're all audio). The tee element takes one buffer in
and promptly spits three out, one after another. Delay takes an element and immediately spits out a buffer (it
zero-pads at the beginning [the duration of the delay] for the sake of argument). Mix in this case is chained, but
assumes that buffer will arrive in order. On the last chain, it does a push of the newly mixed audio buffer.
The queues are both DECOUPLED, so they have lower weight. That leaves a bunch of other elements sitting there ripe for
entry. But if we were to take delay1, what would happen? Well we can't, since there's no _get function on the tee's
src pads.
This just re-enforces the idea that the left-most (closest to the source, for you right-to-left people) element should
get to be the entry. But what if we have multiple left-most elements?:
-> queue -> eq1 -> |=====|
| mix | -> queue
-> queue -> eq2 -> |=====|
If eq1 is the only entry, we call pull on the queue, then chain over to mix. Mix then doesn't do anything with it,
since it's waiting for another buffer before doing anything. That means we have to do the same with eq2, and have it
chain to mix, at which point mix will do its magic and chain out to the right-hand side. Figure out to actually use
both entries is hard, because the idea at this point is that there's only a single entry to a chain.
Does this mean that we should make mix a DECOUPLED element? That would fix it to some extent, giving us three chains
in the above case. Each eq chain would be driven by the eq element, pulling from the queue and pushing into the mixer.
The mixer -> queue chain is problematic, because there is no possibly entry. The mixer side has no _get function
(since the push always happens upon the receipt of a buffer from the second sink pad), which means that those two
pads have no possible entrance.
Cothreads make this case much easier, since the mix element would drive things, forcing the eq elements to pull and
process buffers in order as needed. It may be that the best option in the case where there are any multi-sinkpad
elements is to turn them into cothreads.
Now, on to cothreaded cases. The simplest possible is to turn all the elements into cothreads. I may punt on this and
do just that for the moment, but there's still the question of what to do at the ends of the chain, where the DECOUPLED
elements are. The easiest is to simply always make then chained, so there's never any worry about who owns the
cothread context for the element, simply because there never will be one.
fakesrc -> queue -> @identity -> fakesink
We just set it up so both ends of the queue are chained, and all is well.