Skip to content

VideoDecoder output guarantees #900

Open

Description

@bartadaniel

My use case requires me to decode multiple video streams at the same time. The source files can be of extremely high bitrate, 4K videos, so I would like to be mindful of how I use the GPU.

My root issue is that I cannot find any guarantee of when I might receive a frame or when I need to send more data to the decoder. If I investigate the packets and check the P or B frames, I see no reason why a packet would not be sent to the output. When I call flush I receive frames in the output perfectly.

Based on that experience, I thought I could decode GOPs, then call flush. In terms of decoding the bare minimum it's far from optimal, but at least it gives me some guarantee of when I can receive a frame. However, this is not trivial to do either since I work with libav under the hood, what I consider a keyframe might not be accepted by WebCodecs. Same issue as described here. The idea of flush without invalidating whatever inner state it has seemed like a good idea to me.

The answer to my question could be the optimizeForLatency, as in the specification it seems to do exactly what I want. However, as I read the threads here, I am now confused on what it does and what it doesn't. As @dalecurtis mentioned here, in Chromium, this flag does not affect flush directly, just omits using threading which can hold back frames. According to another thread though, I got the impression this only applies to software decoders, not hardware-accelerated ones. Does this have an effect in hw-accelerated decoding? If so, what are the performance drawbacks to consider?

Very different in nature but what I love about FFmpeg/libav is that this is very trivial to do:

int ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
if (ret == AVERROR(EAGAIN)) {
  // I know the decoder needs more frames before it can give me something
}

To summarize, my question is how can I have a guarantee that I passed enough packets (or how can I know for sure that I didn't ) to the VideoDecoder to surely have an output? Ideally, I want to decode the smallest amount possible for a guaranteed output.

Activity

  1. dalecurtis commented on Jul 24, 2025

    @dalecurtis
    Contributor

    Generally speaking you're meant to use the dequeue events / queue size and target the minimum number of outstanding frames you need. E.g., something like:

    var active_outputs = []
    
    function maybeScheduleDecode() {
      if (active_outputs.size() >= TARGET_FRAME_COUNT) {
        return;
      }
      decoder.decode(getNextPacket());
    }
    
    decoder.ondeque = maybeScheduleDecode;
    maybeScheduleDecode();
    
    // ...
    
    // Later when releasing a frame:
    active_outputs[0].close();
    active_outputs.shift();
    maybeScheduleDecode();
    

    There's also an attribute if you don't want to use events:

    while (this.frameBuffer.length < FRAME_BUFFER_TARGET_SIZE &&

  2. bartadaniel commented on Jul 25, 2025

    @bartadaniel
    Author

    Thank you @dalecurtis. That's a good point, the decodeQueueSize allows me not to decode too much ahead of time.

    Still, I'm hoping to optimize more. This is an example of an h264 stream, where I want to extract a thumbnail, so in this case, getting one frame is perfectly enough for me. Here, I'm showing the presentation timestamp of what I decode, when the decoder signals dequeue and when I receive a decoded frame.

    decoding keyframe 2.07 
    dequeue
    decoding non-keyframe 2 
    dequeue
    decoding non-keyframe 2.04 
    dequeue
    decoding non-keyframe 2.17 
    dequeue
    decoding non-keyframe 2.1 
    dequeue
    decoding non-keyframe 2.14 
    dequeue
    decoding non-keyframe 2.27 
    dequeue
    decoding non-keyframe 2.2 
    dequeue
    decoding non-keyframe 2.24 
    dequeue
    decoding non-keyframe 2.37 
    dequeue
    decoded 2
    

    I don't understand why I receive the first frame so late and decoding so many frames has a definite impact on performance. If I call flush after the keyframe the problem is solved for this video, but that method breaks somewhere else. For interlaced videos, libav can give me the first half of the frame marked as keyframe and the second as a non-keyframe. This might be a framework issue, and I can try to peek into the stream to have a strong guess on what to expect from the VideoDecoder after calling flush. This is just one example (there might be other) why I find it hard to optimize here.

    Having a way to know if the decoder would give me something in case of a flush would be a game-changer here. If not possible, understanding why I need to give the decoder so many frames would also be helpful.

  3. dalecurtis commented on Jul 28, 2025

    @dalecurtis
    Contributor

    At present there's not a better way. Even the user agent can't always know if the hardware framework (E.g., MediaCodec, MediaFoundation, AVFoundation, etc) will want more frames beyond what is required by the bitstream (see see #732).

    Providing inputs until you get an output one per dequeue is the only surefire approach at the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions