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]
