PR #24342 opened by michaelni
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24342
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24342.patch

Decoding a 4690 frame 720x576 stream single threaded
runs each:
full decode:   2.973 +- 0.014 s
metadata only: 0.058 +- 0.007 s


>From 5f3b824fb389feaedf3b62c078634291a6c2c2f9 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 1 Sep 2026 02:04:15 +0200
Subject: [PATCH 1/2] avcodec: add AVCodecContext.skip_pred

Skipping prediction complements skip_idct and skip_loop_filter: with
both skip_pred and skip_idct discarding a frame, no pixel of that frame
is needed, so a decoder can drop all pixel operations and output the
frame with only its metadata valid. This allows callers to determine
timestamps, frame types and the reorder delay of a stream at a small
fraction of the cost of a full decode.
---
 doc/APIchanges             |  3 +++
 doc/codecs.texi            | 10 +++++++++-
 libavcodec/avcodec.h       | 11 +++++++++++
 libavcodec/options_table.h |  1 +
 libavcodec/pthread_frame.c |  1 +
 libavcodec/version.h       |  2 +-
 6 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/doc/APIchanges b/doc/APIchanges
index 7d878a630c..9cf1f85702 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2026-06-23.
 
 API changes, most recent first:
 
+2026-09-01 - xxxxxxxxxx - lavc 63.10.100 - avcodec.h
+  Add AVCodecContext.skip_pred.
+
 2026-08-28 - xxxxxxxxxx - lavc 63.9.100 - avcodec.h
   Add FF_BUG_H264_DP_NNZ.
 
diff --git a/doc/codecs.texi b/doc/codecs.texi
index e905282f1e..136462e13c 100644
--- a/doc/codecs.texi
+++ b/doc/codecs.texi
@@ -724,13 +724,21 @@ Set max macroblock lagrange factor (VBR).
 
 @item skip_loop_filter @var{integer} (@emph{decoding,video})
 @item skip_idct        @var{integer} (@emph{decoding,video})
+@item skip_pred        @var{integer} (@emph{decoding,video})
 @item skip_frame       @var{integer} (@emph{decoding,video})
 
 Make decoder discard processing depending on the frame type selected
 by the option value.
 
 @option{skip_loop_filter} skips frame loop filtering, @option{skip_idct}
-skips frame IDCT/dequantization, @option{skip_frame} skips decoding.
+skips frame IDCT/dequantization, @option{skip_pred} skips prediction
+(intra prediction and motion compensation), @option{skip_frame} skips
+decoding.
+
+@option{skip_pred} takes effect only together with @option{skip_idct}:
+with both set to @samp{all} the decoder (currently only h264) skips all
+pixel operations and outputs frames with uninitialized pixels whose
+metadata, such as timestamps and frame types, is valid.
 
 Possible values:
 @table @samp
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index cf2257f3c9..55ea151074 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -1936,6 +1936,17 @@ typedef struct AVCodecContext {
      * - decoding: Set by libavcodec
      */
     enum AVAlphaMode alpha_mode;
+
+    /**
+     * Skip prediction (intra prediction and motion compensation) for
+     * selected frames. Decoders honor this only when skip_pred and
+     * skip_idct both discard a frame: all pixel operations of that frame
+     * are then skipped and it is output with valid metadata and
+     * uninitialized pixels.
+     * - encoding: unused
+     * - decoding: Set by user.
+     */
+    enum AVDiscard skip_pred;
 } AVCodecContext;
 
 /**
diff --git a/libavcodec/options_table.h b/libavcodec/options_table.h
index 23fe7d3409..e18d0d4c46 100644
--- a/libavcodec/options_table.h
+++ b/libavcodec/options_table.h
@@ -255,6 +255,7 @@ static const AVOption avcodec_options[] = {
 {"mblmax", "maximum macroblock Lagrange factor (VBR)", OFFSET(mb_lmax), 
AV_OPT_TYPE_INT, {.i64 = FF_QP2LAMBDA * 31 }, 1, FF_LAMBDA_MAX, V|E},
 {"skip_loop_filter", "skip loop filtering process for the selected frames", 
OFFSET(skip_loop_filter), AV_OPT_TYPE_INT, {.i64 = AVDISCARD_DEFAULT }, 
INT_MIN, INT_MAX, V|D, .unit = "avdiscard"},
 {"skip_idct"       , "skip IDCT/dequantization for the selected frames",    
OFFSET(skip_idct),        AV_OPT_TYPE_INT, {.i64 = AVDISCARD_DEFAULT }, 
INT_MIN, INT_MAX, V|D, .unit = "avdiscard"},
+{"skip_pred"       , "skip prediction for the selected frames",             
OFFSET(skip_pred),        AV_OPT_TYPE_INT, {.i64 = AVDISCARD_DEFAULT }, 
INT_MIN, INT_MAX, V|D, .unit = "avdiscard"},
 {"skip_frame"      , "skip decoding for the selected frames",               
OFFSET(skip_frame),       AV_OPT_TYPE_INT, {.i64 = AVDISCARD_DEFAULT }, 
INT_MIN, INT_MAX, V|D, .unit = "avdiscard"},
 {"none"            , "discard no frame",                    0, 
AV_OPT_TYPE_CONST, {.i64 = AVDISCARD_NONE    }, INT_MIN, INT_MAX, V|D, .unit = 
"avdiscard"},
 {"default"         , "discard useless frames",              0, 
AV_OPT_TYPE_CONST, {.i64 = AVDISCARD_DEFAULT }, INT_MIN, INT_MAX, V|D, .unit = 
"avdiscard"},
diff --git a/libavcodec/pthread_frame.c b/libavcodec/pthread_frame.c
index 1814ab2768..5ec9a8a974 100644
--- a/libavcodec/pthread_frame.c
+++ b/libavcodec/pthread_frame.c
@@ -476,6 +476,7 @@ static int update_context_from_user(AVCodecContext *dst, 
const AVCodecContext *s
 
     dst->skip_loop_filter = src->skip_loop_filter;
     dst->skip_idct        = src->skip_idct;
+    dst->skip_pred        = src->skip_pred;
     dst->skip_frame       = src->skip_frame;
 
     dst->frame_num        = src->frame_num;
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 230d5fa13e..80e2ae630d 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -29,7 +29,7 @@
 
 #include "version_major.h"
 
-#define LIBAVCODEC_VERSION_MINOR   9
+#define LIBAVCODEC_VERSION_MINOR  10
 #define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
-- 
2.52.0


>From ebe403eb039e1859fccf1fc9b2fbde886bf8c11f Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 1 Sep 2026 02:04:20 +0200
Subject: [PATCH 2/2] avcodec/h264dec: decode only metadata when prediction and
 IDCT are discarded

Decoding a 4690 frame 720x576 stream single threaded, 12 interleaved
runs each:
full decode:   2.973 +- 0.014 s
metadata only: 0.058 +- 0.007 s
51x faster, p = 5e-37 (Welch)
---
 libavcodec/h264_slice.c | 8 +++++++-
 libavcodec/h264dec.c    | 3 ++-
 libavcodec/h264dec.h    | 7 +++++++
 3 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/libavcodec/h264_slice.c b/libavcodec/h264_slice.c
index 4f4cf3f66b..501e637dae 100644
--- a/libavcodec/h264_slice.c
+++ b/libavcodec/h264_slice.c
@@ -525,6 +525,7 @@ static int h264_frame_start(H264Context *h)
     pic->needs_fg =
         h->sei.common.film_grain_characteristics &&
         h->sei.common.film_grain_characteristics->present &&
+        !ff_h264_skip_all_pixels(h->avctx) &&
         !h->avctx->hwaccel &&
         !(h->avctx->export_side_data & AV_CODEC_EXPORT_DATA_FILM_GRAIN);
 
@@ -1578,7 +1579,7 @@ static int h264_field_start(H264Context *h, const 
H264SliceContext *sl,
                 if (h->short_ref[0]->field_picture)
                     ff_thread_report_progress(&h->short_ref[0]->tf, INT_MAX, 
1);
             } else if (!h->frame_recovered) {
-                if (!h->avctx->hwaccel)
+                if (!h->avctx->hwaccel && !ff_h264_skip_all_pixels(h->avctx))
                     color_frame(h->short_ref[0]->f, c);
                 h->short_ref[0]->gray = 1;
             }
@@ -2886,6 +2887,11 @@ int ff_h264_execute_decode_slices(H264Context *h)
     if (h->avctx->hwaccel || context_count < 1)
         return 0;
 
+    if (ff_h264_skip_all_pixels(avctx)) {
+        h->mb_y = h->mb_height;
+        goto finish;
+    }
+
     av_assert0(context_count && h->slice_ctx[context_count - 1].mb_y < 
h->mb_height);
 
     if (context_count == 1) {
diff --git a/libavcodec/h264dec.c b/libavcodec/h264dec.c
index 0892850fe0..6b9b6981ee 100644
--- a/libavcodec/h264dec.c
+++ b/libavcodec/h264dec.c
@@ -830,7 +830,8 @@ end:
      * past end by one (callers fault) and resync_mb_y != 0
      * causes problems for the first MB line, too.
      */
-    if (!FIELD_PICTURE(h) && h->current_slice && h->enable_er) {
+    if (!FIELD_PICTURE(h) && h->current_slice && h->enable_er &&
+        !ff_h264_skip_all_pixels(h->avctx)) {
 
         H264SliceContext *sl = h->slice_ctx;
         int use_last_pic = h->last_pic_for_ec.f->buf[0] && !sl->ref_count[0];
diff --git a/libavcodec/h264dec.h b/libavcodec/h264dec.h
index 7bce897021..715b16d9c8 100644
--- a/libavcodec/h264dec.h
+++ b/libavcodec/h264dec.h
@@ -683,6 +683,13 @@ static av_always_inline int get_chroma_qp(const PPS *pps, 
int t, int qscale)
     return pps->chroma_qp_table[t][qscale];
 }
 
+static inline int ff_h264_skip_all_pixels(const AVCodecContext *avctx)
+{
+    return avctx->skip_pred >= AVDISCARD_ALL &&
+           avctx->skip_idct >= AVDISCARD_ALL &&
+           !(avctx->flags2 & AV_CODEC_FLAG2_CHUNKS);
+}
+
 int ff_h264_field_end(H264Context *h, H264SliceContext *sl, int in_setup);
 
 int ff_h264_ref_picture(H264Picture *dst, const H264Picture *src);
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to