This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

commit 0e00e1b3d8c940cba30c084af045859ec8b44ff5
Author:     guy-with-a-why <[email protected]>
AuthorDate: Fri Aug 28 10:25:04 2026 +0100
Commit:     Timo Rothenpieler <[email protected]>
CommitDate: Mon Aug 31 18:13:21 2026 +0000

    avcodec/h264: decode slice data partitions B and C
    
    Syntax elements of category 2 stay in partition A, category 3 (intra
    residual) moves to partition B and category 4 (inter residual) to
    partition C, so CAVLC has to read the residual of each macroblock from
    the partition that matches its type. A partition that never arrived is
    reported instead of being read from A.
    
    B and C are taken from the NAL units immediately following their A and
    matched to it by slice_id. Arbitrary slice order may separate them
    (7.4.1.2.5); that is not supported, and such a stream reports the
    missing partition instead. Attaching runs while the slice is queued,
    before any slice is executed, so no partition can arrive too late to
    be used. ff_h264_queue_decode_slice() reports which context it queued,
    as a slice opening a second field is moved into slot zero once the
    pending field has been decoded.
    
    Attaching and routing land together on purpose: enabling the partition
    A path on its own would decode partitioned streams incorrectly instead
    of reporting them unsupported. hwaccel formats are not offered for a
    partitioned picture and CODEC_FLAG2_CHUNKS is refused, as both assume
    one self-contained slice NAL.
    
    Signed-off-by: guy-with-a-why <[email protected]>
---
 libavcodec/h264_cavlc.c | 32 +++++++++++++++++++++----
 libavcodec/h264_slice.c | 63 +++++++++++++++++++++++++++++++++++++++++++++----
 libavcodec/h264dec.c    | 53 ++++++++++++++++++++++++++++++++++++++---
 libavcodec/h264dec.h    | 12 +++++++++-
 4 files changed, 147 insertions(+), 13 deletions(-)

diff --git a/libavcodec/h264_cavlc.c b/libavcodec/h264_cavlc.c
index 09f7b42ba0..d3737166ad 100644
--- a/libavcodec/h264_cavlc.c
+++ b/libavcodec/h264_cavlc.c
@@ -662,6 +662,23 @@ int decode_luma_residual(const H264Context *h, 
H264SliceContext *sl,
     }
 }
 
+/* Residual is category 3 (intra) or 4 (inter), so it comes from partition B
+ * or C. NULL if that partition was not received. */
+static GetBitContext *mb_residual_gb(const H264Context *h, H264SliceContext 
*sl,
+                                     unsigned mb_type)
+{
+    int intra = IS_INTRA(mb_type);
+
+    if (!sl->data_partitioning)
+        return &sl->gb;
+    if (intra ? sl->dpb_available : sl->dpc_available)
+        return intra ? &sl->gb_dpb : &sl->gb_dpc;
+
+    av_log(h->avctx, AV_LOG_ERROR, "Missing slice data partition %c\n",
+           intra ? 'B' : 'C');
+    return NULL;
+}
+
 int ff_h264_decode_mb_cavlc(const H264Context *h, H264SliceContext *sl)
 {
     int mb_xy;
@@ -742,14 +759,18 @@ decode_intra_mb:
     if(IS_INTRA_PCM(mb_type)){
         const int mb_size = ff_h264_mb_sizes[h->ps.sps->chroma_format_idc] *
                             h->ps.sps->bit_depth_luma;
+        GetBitContext *gb = mb_residual_gb(h, sl, mb_type); // samples are 
category 3
+
+        if (!gb)
+            return AVERROR_INVALIDDATA;
 
         // We assume these blocks are very rare so we do not optimize it.
-        sl->intra_pcm_ptr = align_get_bits(&sl->gb);
-        if (get_bits_left(&sl->gb) < mb_size) {
+        sl->intra_pcm_ptr = align_get_bits(gb);
+        if (get_bits_left(gb) < mb_size) {
             av_log(h->avctx, AV_LOG_ERROR, "Not enough data for an intra PCM 
block.\n");
             return AVERROR_INVALIDDATA;
         }
-        skip_bits_long(&sl->gb, mb_size);
+        skip_bits_long(gb, mb_size);
 
         // In deblocking, the quantizer is 0
         h->cur_pic.qscale_table[mb_xy] = 0;
@@ -1067,10 +1088,13 @@ decode_intra_mb:
         int i4x4, i8x8, chroma_idx;
         int dquant;
         int ret;
-        GetBitContext *gb = &sl->gb;
+        GetBitContext *gb = mb_residual_gb(h, sl, mb_type);
         const uint8_t *scan, *scan8x8;
         const int max_qp = 51 + 6 * (h->ps.sps->bit_depth_luma - 8);
 
+        if (!gb)
+            return AVERROR_INVALIDDATA;
+
         dquant= get_se_golomb(&sl->gb);
 
         sl->qscale += (unsigned)dquant;
diff --git a/libavcodec/h264_slice.c b/libavcodec/h264_slice.c
index 3a94781219..4f4cf3f66b 100644
--- a/libavcodec/h264_slice.c
+++ b/libavcodec/h264_slice.c
@@ -784,7 +784,8 @@ static void init_scan_tables(H264Context *h)
     }
 }
 
-static enum AVPixelFormat get_pixel_format(H264Context *h, int force_callback)
+static enum AVPixelFormat get_pixel_format(H264Context *h, int force_callback,
+                                           int data_partitioning)
 {
 #define HWACCEL_MAX (CONFIG_H264_DXVA2_HWACCEL + \
                      (CONFIG_H264_D3D11VA_HWACCEL * 2) + \
@@ -920,6 +921,12 @@ static enum AVPixelFormat get_pixel_format(H264Context *h, 
int force_callback)
         return AVERROR_INVALIDDATA;
     }
 
+    /* hwaccels take one self-contained slice NAL, not three */
+    if (data_partitioning) {
+        pix_fmts[0] = fmt[-1];
+        fmt         = pix_fmts + 1;
+    }
+
     *fmt = AV_PIX_FMT_NONE;
 
     for (int i = 0; pix_fmts[i] != AV_PIX_FMT_NONE; i++)
@@ -1095,7 +1102,8 @@ static int h264_init_ps(H264Context *h, const 
H264SliceContext *sl, int first_sl
                      || h->mb_height != sps->mb_height
                     ));
     if (h->avctx->pix_fmt == AV_PIX_FMT_NONE
-        || (non_j_pixfmt(h->avctx->pix_fmt) != 
non_j_pixfmt(get_pixel_format(h, 0))))
+        || (non_j_pixfmt(h->avctx->pix_fmt) !=
+            non_j_pixfmt(get_pixel_format(h, 0, sl->data_partitioning))))
         must_reinit = 1;
 
     if (first_slice && av_cmp_q(sps->vui.sar, h->avctx->sample_aspect_ratio))
@@ -1158,7 +1166,8 @@ static int h264_init_ps(H264Context *h, const 
H264SliceContext *sl, int first_sl
         if (flush_changes)
             ff_h264_flush_change(h);
 
-        if ((ret = get_pixel_format(h, must_reinit || needs_reinit)) < 0)
+        if ((ret = get_pixel_format(h, must_reinit || needs_reinit,
+                                    sl->data_partitioning)) < 0)
             return ret;
         h->avctx->pix_fmt = ret;
 
@@ -2103,13 +2112,56 @@ static int h264_parse_slice_id(const H264Context *h, 
H264SliceContext *sl)
     return 0;
 }
 
-int ff_h264_queue_decode_slice(H264Context *h, const H2645NAL *nal)
+int ff_h264_attach_slice_partition(const H264Context *h, H264SliceContext *sl,
+                                   const H2645NAL *nal)
+{
+    const PPS *pps = h->ps.pps_list[sl->pps_id];
+    GetBitContext gb = nal->gb;
+    int redundant_pic_cnt = 0;
+    unsigned slice_id;
+
+    if (!sl->data_partitioning)
+        return AVERROR_INVALIDDATA;
+
+    slice_id = get_ue_golomb_long(&gb);
+    if (pps->sps->residual_color_transform_flag)
+        skip_bits(&gb, 2);                  // colour_plane_id
+    if (pps->redundant_pic_cnt_present)
+        redundant_pic_cnt = get_ue_golomb(&gb);
+
+    if (get_bits_left(&gb) < 0) {
+        av_log(h->avctx, AV_LOG_ERROR, "Truncated slice data partition\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    /* 7.4.2.9.2: B and C repeat the slice_id and redundant_pic_cnt of their 
A. */
+    if (slice_id != sl->slice_id || redundant_pic_cnt != 
sl->redundant_pic_count) {
+        av_log(h->avctx, AV_LOG_WARNING, "Slice data partition %c does not "
+               "match the preceding partition A\n",
+               nal->type == H264_NAL_DPB ? 'B' : 'C');
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (nal->type == H264_NAL_DPB) {
+        sl->gb_dpb        = gb;
+        sl->dpb_available = 1;
+    } else {
+        sl->gb_dpc        = gb;
+        sl->dpc_available = 1;
+    }
+
+    return 0;
+}
+
+int ff_h264_queue_decode_slice(H264Context *h, const H2645NAL *nal,
+                               H264SliceContext **queued)
 {
     H264SliceContext *sl = h->slice_ctx + h->nb_slice_ctx_queued;
     int first_slice = sl == h->slice_ctx && !h->current_slice;
     int ret;
 
-    sl->gb = nal->gb;
+    *queued = NULL;
+    sl->gb  = nal->gb;
 
     sl->data_partitioning = 0;
     sl->dpb_available     = 0;
@@ -2234,6 +2286,7 @@ int ff_h264_queue_decode_slice(H264Context *h, const 
H2645NAL *nal)
         return ret;
 
     h->nb_slice_ctx_queued++;
+    *queued = sl;
 
     return 0;
 }
diff --git a/libavcodec/h264dec.c b/libavcodec/h264dec.c
index b78b7989ea..3e583ef6d2 100644
--- a/libavcodec/h264dec.c
+++ b/libavcodec/h264dec.c
@@ -580,12 +580,35 @@ static void debug_green_metadata(const 
H264SEIGreenMetaData *gm, void *logctx)
     }
 }
 
+/**
+ * Attach the partitions B and C immediately following the partition A at idx.
+ * Arbitrary slice order may separate them (7.4.1.2.5); that is not supported.
+ *
+ * @return index of the last NAL absorbed, or idx if there were none.
+ */
+static int h264_attach_partitions(const H264Context *h, H264SliceContext *sl,
+                                  int idx)
+{
+    while (idx + 1 < h->pkt.nb_nals) {
+        const H2645NAL *nal = &h->pkt.nals[idx + 1];
+
+        if (nal->type != H264_NAL_DPB && nal->type != H264_NAL_DPC)
+            break;
+        if (ff_h264_attach_slice_partition(h, sl, nal) < 0)
+            break;
+        idx++;
+    }
+
+    return idx;
+}
+
 static int decode_nal_units(H264Context *h, AVBufferRef *buf_ref,
                             const uint8_t *buf, int buf_size)
 {
     AVCodecContext *const avctx = h->avctx;
     int nals_needed = 0; ///< number of NALs that need decoding before the 
next frame thread starts
     int idr_cleared=0;
+    int dp_attached_to = -1; ///< index of the last partition B/C claimed
     int i, ret = 0;
 
     h->has_slice = 0;
@@ -621,6 +644,7 @@ static int decode_nal_units(H264Context *h, AVBufferRef 
*buf_ref,
 
     for (i = 0; i < h->pkt.nb_nals; i++) {
         H2645NAL *nal = &h->pkt.nals[i];
+        H264SliceContext *queued;
         int max_slice_ctx, err;
 
         if (avctx->skip_frame >= AVDISCARD_NONREF &&
@@ -647,14 +671,34 @@ static int decode_nal_units(H264Context *h, AVBufferRef 
*buf_ref,
             h->has_recovery_point = 1;
             av_fallthrough;
         case H264_NAL_SLICE:
+        case H264_NAL_DPA:
             h->has_slice = 1;
 
-            if ((err = ff_h264_queue_decode_slice(h, nal))) {
+            if (nal->type == H264_NAL_DPA) {
+                /* hwaccels take one self-contained slice NAL, not three */
+                if (avctx->hwaccel) {
+                    avpriv_request_sample(avctx, "hardware accelerated data 
partitioning");
+                    ret = AVERROR_PATCHWELCOME;
+                    goto end;
+                }
+                /* the lookahead needs all three partitions in one packet */
+                if (avctx->flags2 & AV_CODEC_FLAG2_CHUNKS) {
+                    av_log(avctx, AV_LOG_ERROR, "Decoding in chunks is not "
+                           "supported for partitioned slices\n");
+                    ret = AVERROR(ENOSYS);
+                    goto end;
+                }
+            }
+
+            if ((err = ff_h264_queue_decode_slice(h, nal, &queued))) {
                 H264SliceContext *sl = h->slice_ctx + h->nb_slice_ctx_queued;
                 sl->ref_count[0] = sl->ref_count[1] = 0;
                 break;
             }
 
+            if (nal->type == H264_NAL_DPA && queued)
+                dp_attached_to = h264_attach_partitions(h, queued, i);
+
             if (h->current_slice == 1) {
                 if (avctx->active_thread_type & FF_THREAD_FRAME &&
                     i >= nals_needed && !h->setup_finished && h->cur_pic_ptr) {
@@ -679,10 +723,13 @@ static int decode_nal_units(H264Context *h, AVBufferRef 
*buf_ref,
                     goto end;
             }
             break;
-        case H264_NAL_DPA:
         case H264_NAL_DPB:
         case H264_NAL_DPC:
-            avpriv_request_sample(avctx, "data partitioning");
+            /* not claimed by the lookahead above, so it has no partition A */
+            if (i > dp_attached_to)
+                av_log(avctx, AV_LOG_WARNING, "Ignoring slice data partition "
+                       "%c without a matching partition A\n",
+                       nal->type == H264_NAL_DPB ? 'B' : 'C');
             break;
         case H264_NAL_SEI:
             if (h->setup_finished) {
diff --git a/libavcodec/h264dec.h b/libavcodec/h264dec.h
index 4b3e423741..7bce897021 100644
--- a/libavcodec/h264dec.h
+++ b/libavcodec/h264dec.h
@@ -698,8 +698,18 @@ void ff_h264_draw_horiz_band(const H264Context *h, 
H264SliceContext *sl, int y,
  *
  * Parse the slice header, starting a new field/frame if necessary. If any
  * slices are queued for the previous field, they are decoded.
+ *
+ * @param queued set to the queued context, or NULL if the slice was discarded
  */
-int ff_h264_queue_decode_slice(H264Context *h, const H2645NAL *nal);
+int ff_h264_queue_decode_slice(H264Context *h, const H2645NAL *nal,
+                               H264SliceContext **queued);
+
+/**
+ * Attach a slice data partition B or C to the slice started by partition A.
+ */
+int ff_h264_attach_slice_partition(const H264Context *h, H264SliceContext *sl,
+                                   const H2645NAL *nal);
+
 int ff_h264_execute_decode_slices(H264Context *h);
 int ff_h264_update_thread_context(AVCodecContext *dst,
                                   const AVCodecContext *src);

-- 
To stop receiving notification emails like this one, please contact
[email protected].
_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to