With the thundering herd problem resolved, there's no more reason to
wait until the FIFO is empty before unblocking the writers.
In practice, this means that in a downstream-throttled scenario, we will
always wake up exactly the decoder that we just read from.
Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <git@haasn.dev>
Instead of multiple producers and consumers all waiting on a single cond
var, resulting in a thundering herd problem, use a separate cond var for
signalling the "queue full" and "queue empty" conditions.
Since the previous commit splits up the universal limit into sublimits per
stream, we can actually go a step further and only wake up the writer whose
stream we just read from.
In practice, this will result in a nice round-robin behavior, without
spending countless context switches waking up idle decoders who will
immediately go back to sleep.
Results in 67x less sys time and 11x fewer context switches in a synthetic
test where 16 streams are feeding into a single filter graph.
Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <git@haasn.dev>
This preserves FIFO properties, but allows a queue with multiple streams
(e.g. a filter graph with many inputs) to have each stream contend for only
its own space, rather than fighting with possibly arbitrarily many other
streams for space inside a single 2-element frame queue.
This does slightly change the semantics of the -thread_queue_size option,
but this is already marked as an expert/debug option and was partially
broken for several years, so I consider the risk of downstream annoyance
to be rather minimal. (Especially as the new behavior results in a larger
queue, which only wastes memory, rather than a smaller queue, which may
starve)
Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <git@haasn.dev>
Exactly what it says on the tin. There is some ambiguity as to whether this
should also prevent reading from *choked*, as opposed to empty queue, but
I think it makes sense to consider them equivalent, as I struggle to think
of a use case where it would be beneficial to allow draining a queue that
was explicitly choked by the upstream (to e.g. prevent further reads).
Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <git@haasn.dev>
Currently, when a demuxer thread is choked, it will avoid queuing more
packets, but any packets already present on the thread queue will still be
processed.
This can be quite wasteful if the choke is due to e.g. decoder not being
needed yet, such as in a filter graph involving concatenation-style filters.
Adding the ability to propagate the choke status to the thread queue directly
allows downstream decoders and filter graphs to avoid unnecessary work and
buffering.
Reduces the effective latency between scheduler updates and changes in the
thread workfload.
The queue needs to track each frame/packet's stream index, this is
achieved by maintaining a parallel AVFifo instance for that purpose.
This is simpler than implementing custom AVContainerFifo callbacks.
This ensures that tq_receive() will always return EOF after all streams
were receive-finished, even though the sending side might not have
closed them yet. This may allow the receiver to avoid manually tracking
which streams it has already closed.