avcodec: allow bypassing frame threading with an optional flag

Normally, this function tries to make sure all threads are saturated with
work to do before returning any frames; and will continue requesting packets
until that is the case.

However, this significantly slows down initial decoding latency when only
requesting a single frame (to e.g. configure the filter graph), and also
wastes a lot of unnecessary memory in the event that the user does not intend
to decode more frames until later.

By introducing a new `flags` paramater and a new flag
`AV_CODEC_RECEIVE_FRAME_FLAG_SYNCHRONOUS` to go along with it, we can allow
users to temporarily bypass this logic.
This commit is contained in:
Niklas Haas 2025-12-05 19:41:36 +01:00
parent 077864dfd6
commit 5e56937b74
7 changed files with 44 additions and 12 deletions

View file

@ -2,6 +2,10 @@ The last version increases of all libraries were on 2025-03-28
API changes, most recent first:
2025-12-xx - xxxxxxxxxx - lavc 62.22.100 - avcodec.h
Add avcodec_receive_frame2().
Add AV_CODEC_RECEIVE_FRAME_FLAG_SYNCHRONOUS.
2025-09-xx - xxxxxxxxxx - lavfi 11.10.100 - buffersrc.h
Add av_buffersrc_get_status().

View file

@ -705,7 +705,8 @@ int avcodec_is_open(AVCodecContext *s)
return !!s->internal;
}
int attribute_align_arg avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
int attribute_align_arg avcodec_receive_frame2(AVCodecContext *avctx,
AVFrame *frame, unsigned flags)
{
av_frame_unref(frame);
@ -713,10 +714,15 @@ int attribute_align_arg avcodec_receive_frame(AVCodecContext *avctx, AVFrame *fr
return AVERROR(EINVAL);
if (ff_codec_is_decoder(avctx->codec))
return ff_decode_receive_frame(avctx, frame);
return ff_decode_receive_frame(avctx, frame, flags);
return ff_encode_receive_frame(avctx, frame);
}
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
return avcodec_receive_frame2(avctx, frame, 0);
}
#define WRAP_CONFIG(allowed_type, field, var, field_type, sentinel_check) \
do { \
if (codec->type != (allowed_type)) \

View file

@ -415,6 +415,14 @@ typedef struct RcOverride{
*/
#define AV_GET_ENCODE_BUFFER_FLAG_REF (1 << 0)
/**
* The decoder will bypass frame threading and return the next frame as soon as
* possible. Note that this may deliver frames earlier than the advertised
* `AVCodecContext.delay`. No effect when frame threading is disabled, or on
* encoding.
*/
#define AV_CODEC_RECEIVE_FRAME_FLAG_SYNCHRONOUS (1 << 0)
/**
* main external API structure.
* New fields can be added to the end with minor version bumps.
@ -2360,6 +2368,7 @@ int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
* frame (depending on the decoder type) allocated by the
* codec. Note that the function will always call
* av_frame_unref(frame) before doing anything else.
* @param flags Combination of AV_CODEC_RECEIVE_FRAME_FLAG_* flags.
*
* @retval 0 success, a frame was returned
* @retval AVERROR(EAGAIN) output is not available in this state - user must
@ -2370,6 +2379,11 @@ int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
* @ref AV_CODEC_FLAG_RECON_FRAME flag enabled
* @retval "other negative error code" legitimate decoding errors
*/
int avcodec_receive_frame2(AVCodecContext *avctx, AVFrame *frame, unsigned flags);
/**
* Alias for `avcodec_receive_frame2(avctx, frame, 0)`.
*/
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
/**

View file

@ -45,7 +45,8 @@ extern const SideDataMap ff_sd_global_map[];
/**
* avcodec_receive_frame() implementation for decoders.
*/
int ff_decode_receive_frame(struct AVCodecContext *avctx, struct AVFrame *frame);
int ff_decode_receive_frame(struct AVCodecContext *avctx, struct AVFrame *frame,
unsigned flags);
/**
* avcodec_receive_frame() implementation for encoders.
@ -91,9 +92,10 @@ void ff_thread_flush(struct AVCodecContext *avctx);
* Submit available packets for decoding to worker threads, return a
* decoded frame if available. Returns AVERROR(EAGAIN) if none is available.
*
* Parameters are the same as FFCodec.receive_frame.
* Parameters are the same as FFCodec.receive_frame, plus flags.
*/
int ff_thread_receive_frame(struct AVCodecContext *avctx, AVFrame *frame);
int ff_thread_receive_frame(struct AVCodecContext *avctx, AVFrame *frame,
unsigned flags);
/**
* Do the actual decoding and obtain a decoded frame from the decoder, if

View file

@ -645,14 +645,15 @@ int ff_decode_receive_frame_internal(AVCodecContext *avctx, AVFrame *frame)
return ret;
}
static int decode_receive_frame_internal(AVCodecContext *avctx, AVFrame *frame)
static int decode_receive_frame_internal(AVCodecContext *avctx, AVFrame *frame,
unsigned flags)
{
AVCodecInternal *avci = avctx->internal;
DecodeContext *dc = decode_ctx(avci);
int ret, ok;
if (avctx->active_thread_type & FF_THREAD_FRAME)
ret = ff_thread_receive_frame(avctx, frame);
ret = ff_thread_receive_frame(avctx, frame, flags);
else
ret = ff_decode_receive_frame_internal(avctx, frame);
@ -730,7 +731,7 @@ int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacke
dc->draining_started = 1;
if (!avci->buffer_frame->buf[0] && !dc->draining_started) {
ret = decode_receive_frame_internal(avctx, avci->buffer_frame);
ret = decode_receive_frame_internal(avctx, avci->buffer_frame, 0);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return ret;
}
@ -792,7 +793,7 @@ fail:
return AVERROR_BUG;
}
int ff_decode_receive_frame(AVCodecContext *avctx, AVFrame *frame)
int ff_decode_receive_frame(AVCodecContext *avctx, AVFrame *frame, unsigned flags)
{
AVCodecInternal *avci = avctx->internal;
int ret;
@ -800,7 +801,7 @@ int ff_decode_receive_frame(AVCodecContext *avctx, AVFrame *frame)
if (avci->buffer_frame->buf[0]) {
av_frame_move_ref(frame, avci->buffer_frame);
} else {
ret = decode_receive_frame_internal(avctx, frame);
ret = decode_receive_frame_internal(avctx, frame, flags);
if (ret < 0)
return ret;
}

View file

@ -559,7 +559,7 @@ static int submit_packet(PerThreadContext *p, AVCodecContext *user_avctx,
return 0;
}
int ff_thread_receive_frame(AVCodecContext *avctx, AVFrame *frame)
int ff_thread_receive_frame(AVCodecContext *avctx, AVFrame *frame, unsigned flags)
{
FrameThreadContext *fctx = avctx->internal->thread_ctx;
int ret = 0;
@ -572,6 +572,10 @@ int ff_thread_receive_frame(AVCodecContext *avctx, AVFrame *frame)
while (!fctx->df.nb_f && !fctx->result) {
PerThreadContext *p;
if (fctx->next_decoding != fctx->next_finished &&
(flags & AV_CODEC_RECEIVE_FRAME_FLAG_SYNCHRONOUS))
goto wait_for_result;
/* get a packet to be submitted to the next thread */
av_packet_unref(fctx->next_pkt);
ret = ff_decode_get_packet(avctx, fctx->next_pkt);
@ -588,6 +592,7 @@ int ff_thread_receive_frame(AVCodecContext *avctx, AVFrame *frame)
!avctx->internal->draining)
continue;
wait_for_result:
p = &fctx->threads[fctx->next_finished];
fctx->next_finished = (fctx->next_finished + 1) % avctx->thread_count;

View file

@ -29,7 +29,7 @@
#include "version_major.h"
#define LIBAVCODEC_VERSION_MINOR 21
#define LIBAVCODEC_VERSION_MINOR 22
#define LIBAVCODEC_VERSION_MICRO 100
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \