mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-30 12:10:37 +00:00
89 lines
5.6 KiB
Text
89 lines
5.6 KiB
Text
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.
|