[FFmpeg-devel] Evolution of lavfi's design and API

Nicolas George george at nsup.org
Wed Oct 22 23:45:42 CEST 2014


[ CCing Anton, as most that is written here also apply to libav too, and
this would be a good occasion to try a cross-fork cooperation; if that is
not wanted, please let us know so we can drop the cc. ]

1. Problems with the current design

  1.1. Mixed input-/output-driven model

    Currently, lavfi is designed to work in a mixed input-driven and
    output-driven model. That means the application needs sometimes to add
    input to buffersources and sometimes request output to buffersinks. This
    is a bit of a nuisance, because it requires the application to do it
    properly: adding input on the wrong input or requesting a frame on the
    wrong output will cause extra memory consumption or latency.

    With the libav API, it can not work at all since there is no mechanism
    to determine which input needs a frame in order to proceed.

    The libav API is clearly designed for a more output-driven
    implementation, with FIFOs anywhere to prevent input-driven frames to
    reach unready filters. Unfortunately, since it is impossible from the
    outside to guess what output will get a frame next, that can cause
    frames to accumulate anywhere in the filter graph, eating a lot of
    memory unnecessarily.

    FFmpeg's API has eliminated FIFOs in favour of queues in filters that
    need it, but these queues can not be controlled for unusual filter
    graphs with extreme needs. Also, there still is an implicit FIFO inside
    buffersink.

  1.2. Recursive implementation

    All work in a filter graph is triggered by recursive invocations of the
    filters' methods. It makes debugging harder. It also can lead to large
    stack usage and makes frame- and filter-level multithreading harder to
    implement. It also prevents some diagnosis from working reliably.

  1.3. EOF handling

    Currently, EOF is propagated only through the return value of the
    request_frame() method. That means it only works in an output-driven
    scheme. It also means that it has no timestamp attached to it; this is
    an issue for filters where the duration of the last frame is relevant,
    like vf_fps.

  1.4. Latency

    Some filters need to know the timestamp of the next frame in order to
    know when the current frame will stop and be able to process it:
    overlay, fps are two examples. These filters will introduce a latency of
    one input frame that could otherwise be avoided.

  1.5. Timestamps

    Some filters do not care about timestamps at all. Some check and have a
    proper handling of NOPTS values. Some filters just assume the frames
    will have timestamps, and possibly make extra assumptions on that:
    monotony, consistency, etc. That is an inconsistent mess.

  1.6. Sparse streams

    There is a more severe instance of the latency issue when the input
    comes from an interleaved sparse stream: in that case, waiting for the
    next frame in order to find the end of the current one may require
    demuxing a large chunk of input, in turn provoking a lot of activity on
    other inputs of the graph.

2. Proposed API changes

  To fix/enhance all these issues, I believe a complete rethink of the
  scheduling design of the library is necessary. I propose the following
  changes.

  Note: some of these changes are not 100% related to the issues I raised,
  but looked like a good idea while thinking on an API rework.

  2.1. AVFrame.duration

    Add a duration field to AVFrame; if set, it indicates the duration of
    the frame. Thus, it becomes unnecessary to wait for the next frame to
    know when the current frame stops, reducing the latency.

    Another solution would be to add a dedicated function on buffersrc to
    inject a timestamp for end or activity on a link. That would avoid the
    need of adding a field to AVFrame.

  2.2. Add some fields to AVFilterLink

    AVFilterLink.pts: current timestamp of the link, i.e. end timestamp of
    the last forwarede frame, assuming the duration was correct. This is
    somewhat redundant with the fields in AVFrame, but can carry the
    information even when there is no actual frame.

    AVFilterLink.status: if not 0, gives the return status of trying to pass
    a frame on this link. The typical use would be EOF.

  2.3. AVFilterLink.need_ts

    Add a field to AVFilterLink to specify that the output filter requires
    reliable timestamps on its input. More precisely, specify how reliable
    the timestamps need to be: is the duration necessary? do the timestamps
    need to be monotonic? continuous?

    For audio streams, consistency between timestamps and the number of
    samples may also be tested. For video streams, constant frame rate may
    be enforced, but I am not sure about this one.

    A "fixpts" filter should be provided to allow the user to tweak how the
    timestamps are fixed (change the timestamps to match the duration or
    change the duration to match the timestamps?).

    When no explicit filter is inserted, the framework should do the work of
    fixing them automatically. I am not sure whether that should be done
    directly or by automatically inserting the fixpts filter. The later
    solution is more elegant, but it requires more changes to the framework
    and the filters (because the correctness of the timestamps would need to
    be merged just like formats), so I am rather for the former.

    Note that for a lot of filters, the actual duration or end timestamp is
    not required, only a lower bound for it. For sparse interleaved streams,
    that is very relevant as we may not know the exact time for the next
    frame until we reach it, but we can know it is later than the other
    streams' timestamps minus the interleaving delta.

  2.4. Build FIFOs directly in AVFilterLink

    Instead of automatically insert an additional filter like libav, handle
    the FIFO operation directly in the framework using fields in
    AVFilterLink.

    The main benefit is that the framework can examine the inside of the
    FIFOs to make scheduling decisions. It can also do so to provide the
    user with more accurate diagnostics.

    An extra benefit: the memory pool for the FIFOed frames can more easily
    be shared, across the whole filter graph or the whole application.
    Memory management becomes easier: just take a good heuristics (half the
    RAM?), no need to guess what FIFOs will actually need a lot of memory
    and what FIFOs are just there mostly useless.

    Last but not least, FIFOs now become potential thread communication /
    synchronization points, making filter-level multithreading easier.

    For audio streams, framing (i.e. ensuring all frame have an exact /
    minimum / maximum number of samples) can be merged with FIFOs.

  2.5. Allow status change pseudo-frames inside FIFOs

    To propagate EOF and possibly other status changes (errors) in
    input-driven model, allow FIFOs to contain not only frames but also kind
    of pseudo-frames with a timestamp and metadata attached.

    Depending on the filters, these pseudo-frames may be directly passed to
    the filter's methods, or they may be interpreted by the framework to
    just change fields on the AVFilterLink structure.

  2.6. Change the scheduling logic for filters

    From the outside of a filter with several outputs, it is usually not
    possible to guess what output will get a frame next. Requesting a frame
    on output #0 may cause activity on the filter graph that produce a frame
    on output #1 instead, or possibly on a completely different filter.

    Therefore, having a request_frame() method on all outputs seems
    pointless.

    Instead, use a global AVFilter.activate() method that causes the filter
    to do one step of work if it can. This method is called each time
    something is changed to the filter: new frame on input, output ready,
    status change. It returns as soon at it could do something, either
    producing output and/or consuming input, or nothing if nothing can be
    done.

  2.7. Add fields to AVFilterLink for flow control.

    Add to AVFilterLinks a few field to help filters decide if they need to
    process something, and if relevant in what order. The most obvious idea
    would be AVFilterLink.frames_needed, counting how many frames are
    probably needed on a link before anything can be done. For example, with
    concat, after input has been consumed, the frames_needed fields on the
    current input are set according to the corresponding output.

  2.8. Activate the filters iteratively

    Keep a global (per graph) priority queue of filters that are supposed to
    be ready and call the activate() method on them.

  2.9. AVFrame.stream_id

    Add an integer (or pointer: intptr_t maybe?) field to AVFrame to allow
    passing frames related to distinct streams on the same link. That would
    allow to multiplex all outputs of a graph into a single output, making
    the application simpler.

    Not sure this is really useful or necessary: for the graph outputs, a
    convenience function iterating on all of them and returning the frame
    and the output index separately would do the trick too.

  2.10. buffersrc.callback and buffersink.callback

    Add a callback on both buffersource and buffersink, called respectively
    when a frame is necessary on input and a frame has arrived on output.
    This allows pure input-driven and pure output-driven design to work.

  2.11. Links groups

    Links that carry frames from related interleaved streams should be
    explicitly connected together so that the framework can use the
    information.

    The typical use would be to group all the links from buffersrc that come
    from the same interleaved input file.

    When a frame is passed on a link, all links in the same group(s) that
    are too late (according to an interleaving tolerance that can be set)
    are activated using a dummy frame.

  2.12. FIFOs with compression and external storage

    All FIFOs should be able to off-load some of their memory requirements
    by either compressing the frames (using a lossless or optionally lossy
    codec) and/or storing them on mass storage.

    The options for that should be changeable globally or on a per-link
    basis.

  2.13. AVFrame.owner

    Add a owner field (probably with type "AVObject", i.e. "void *" pointing
    to AVClass *) to AVFrame, and update it whenever the frame is passed
    from one filter to the other. That way, inconsistent ref/unref
    operations can be detected.

Regards,

-- 
  Nicolas George
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <https://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20141022/6e3fc728/attachment.asc>


More information about the ffmpeg-devel mailing list