PR #22987 opened by fran6co URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22987 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22987.patch
>From 3b6bc79b0b6488633aaa76a77e6820d6265ec96c Mon Sep 17 00:00:00 2001 From: Francisco Facioni <[email protected]> Date: Fri, 1 May 2026 17:36:44 +0200 Subject: [PATCH] avcodec/hevc: support field-coded interlaced decoding --- Changelog | 1 + libavcodec/hevc/hevcdec.c | 48 +++++++++++++-- libavcodec/hevc/hevcdec.h | 16 +++++ libavcodec/hevc/refs.c | 98 ++++++++++++++++++++++++++++--- tests/ref/fate/hevc-paired-fields | 8 --- 5 files changed, 150 insertions(+), 21 deletions(-) diff --git a/Changelog b/Changelog index 3f5e38e0c7..20c8fe49df 100644 --- a/Changelog +++ b/Changelog @@ -9,6 +9,7 @@ version <next>: - HE-AAC 960 decoding (DAB+) - transpose_cuda filter - Add AMF Frame Rate Converter (vf_frc_amf) filter +- HEVC field-coded interlaced decoding (software) version 8.1: diff --git a/libavcodec/hevc/hevcdec.c b/libavcodec/hevc/hevcdec.c index 8611909dd3..476701f733 100644 --- a/libavcodec/hevc/hevcdec.c +++ b/libavcodec/hevc/hevcdec.c @@ -334,17 +334,30 @@ static void export_stream_params(HEVCContext *s, const HEVCSPS *sps) const HEVCVPS *vps = sps->vps; const HEVCWindow *ow = &sps->output_window; unsigned int num = 0, den = 0; + /* For field-coded HEVC, SPS height is per-field; combined output doubles. */ + const int field_combined = s->field_coded_stream; + const int dim_factor = field_combined ? 2 : 1; + const int ps = s->sei.picture_timing.picture_struct; avctx->pix_fmt = sps->pix_fmt; avctx->coded_width = sps->width; - avctx->coded_height = sps->height; + avctx->coded_height = sps->height * dim_factor; avctx->width = sps->width - ow->left_offset - ow->right_offset; - avctx->height = sps->height - ow->top_offset - ow->bottom_offset; + avctx->height = (sps->height - ow->top_offset - + ow->bottom_offset) * dim_factor; avctx->has_b_frames = sps->temporal_layer[sps->max_sub_layers - 1].num_reorder_pics; avctx->profile = sps->ptl.general_ptl.profile_idc; avctx->level = sps->ptl.general_ptl.level_idc; - ff_set_sar(avctx, sps->vui.common.sar); + avctx->field_order = !field_combined ? AV_FIELD_PROGRESSIVE : + ps == AV_PICTURE_STRUCTURE_TOP_FIELD ? AV_FIELD_TT : AV_FIELD_BB; + /* Some encoders signal SAR 1:2 on field pictures so legacy decoders + * display them at correct shape; combined output is full-height. */ + if (field_combined && sps->vui.common.sar.num == 1 && sps->vui.common.sar.den == 2) { + ff_set_sar(avctx, (AVRational){ 1, 1 }); + } else { + ff_set_sar(avctx, sps->vui.common.sar); + } if (sps->vui.common.video_signal_type_present_flag) avctx->color_range = sps->vui.common.video_full_range_flag ? AVCOL_RANGE_JPEG @@ -3226,6 +3239,15 @@ static int hevc_frame_start(HEVCContext *s, HEVCLayerContext *l, return AVERROR_INVALIDDATA; } + /* Latch on first field-coded SEI; rest of CSV is then field-coded. */ + if (l == &s->layers[0] && !s->field_coded_stream && + (s->sei.picture_timing.picture_struct == AV_PICTURE_STRUCTURE_TOP_FIELD || + s->sei.picture_timing.picture_struct == AV_PICTURE_STRUCTURE_BOTTOM_FIELD)) { + s->field_coded_stream = 1; + if (l->sps) + export_stream_params(s, l->sps); + } + av_refstruct_replace(&s->pps, pps); if (l->sps != sps) { const HEVCSPS *sps_base = s->layers[0].sps; @@ -3444,6 +3466,9 @@ static int verify_md5(HEVCContext *s, AVFrame *frame) for (i = 0; frame->data[i]; i++) { int width = s->avctx->coded_width; int height = s->avctx->coded_height; + /* SEI MD5 covers a single field; coded_height was doubled for output. */ + if (frame->flags & AV_FRAME_FLAG_INTERLACED) + height /= 2; int w = (i == 1 || i == 2) ? (width >> desc->log2_chroma_w) : width; int h = (i == 1 || i == 2) ? (height >> desc->log2_chroma_h) : height; uint8_t md5[16]; @@ -3912,9 +3937,10 @@ static int hevc_ref_frame(HEVCFrame *dst, const HEVCFrame *src) dst->rpl = av_refstruct_ref(src->rpl); dst->nb_rpl_elems = src->nb_rpl_elems; - dst->poc = src->poc; - dst->ctb_count = src->ctb_count; - dst->flags = src->flags; + dst->poc = src->poc; + dst->ctb_count = src->ctb_count; + dst->flags = src->flags; + dst->second_field = src->second_field; dst->base_layer_frame = src->base_layer_frame; @@ -4055,6 +4081,16 @@ static int hevc_update_thread_context(AVCodecContext *dst, s->eos = s0->eos; s->no_rasl_output_flag = s0->no_rasl_output_flag; + /* field_coded_stream latches on layer 0, so first_field_frame is always + * in layer 0's DPB; translate the index across contexts. */ + s->field_coded_stream = s0->field_coded_stream; + s->first_field_frame = NULL; + if (s0->first_field_frame) { + ptrdiff_t idx = s0->first_field_frame - s0->layers[0].DPB; + if (idx >= 0 && idx < FF_ARRAY_ELEMS(s0->layers[0].DPB)) + s->first_field_frame = &s->layers[0].DPB[idx]; + } + s->is_nalff = s0->is_nalff; s->nal_length_size = s0->nal_length_size; s->layers_active_decode = s0->layers_active_decode; diff --git a/libavcodec/hevc/hevcdec.h b/libavcodec/hevc/hevcdec.h index 8394740c4b..381a5c6550 100644 --- a/libavcodec/hevc/hevcdec.h +++ b/libavcodec/hevc/hevcdec.h @@ -372,6 +372,12 @@ typedef struct HEVCFrame { int ctb_count; int poc; + /** + * Set on the second HEVCFrame of a field pair; its f aliases the first + * field's buffer at a +linesize/2 offset and is not directly output. + */ + int second_field; + const HEVCPPS *pps; ///< RefStruct reference RefPicListTab *rpl; ///< RefStruct reference int nb_rpl_elems; @@ -581,6 +587,16 @@ typedef struct HEVCContext { AVBufferRef *rpu_buf; ///< 0 or 1 Dolby Vision RPUs. DOVIContext dovi_ctx; ///< Dolby Vision decoding context + + /** + * Latched on first field-coded SEI; per H.265 Table D.2 the rest of the + * CSV is then field-coded too. + */ + int field_coded_stream; + /** + * In-progress first field of a pair; NULL when not mid-pair. + */ + HEVCFrame *first_field_frame; } HEVCContext; /** diff --git a/libavcodec/hevc/refs.c b/libavcodec/hevc/refs.c index ccf7258ec4..370ed731bd 100644 --- a/libavcodec/hevc/refs.c +++ b/libavcodec/hevc/refs.c @@ -51,6 +51,8 @@ void ff_hevc_unref_frame(HEVCFrame *frame, int flags) frame->refPicList = NULL; av_refstruct_unref(&frame->hwaccel_picture_private); + + frame->second_field = 0; } } @@ -75,6 +77,7 @@ void ff_hevc_clear_refs(HEVCLayerContext *l) void ff_hevc_flush_dpb(HEVCContext *s) { + s->first_field_frame = NULL; for (int layer = 0; layer < FF_ARRAY_ELEMS(s->layers); layer++) { HEVCLayerContext *l = &s->layers[layer]; for (int i = 0; i < FF_ARRAY_ELEMS(l->DPB); i++) @@ -107,11 +110,29 @@ static int replace_alpha_plane(AVFrame *alpha, AVFrame *base) return AVERROR_BUG; } -static HEVCFrame *alloc_frame(HEVCContext *s, HEVCLayerContext *l) +/* When allow_field_pairing is set and SEI signals a field, alloc_frame either + * (a) allocates a full-height buffer with doubled linesize for the FIRST + * field, or (b) aliases the first field's buffer at +linesize/2 for the + * SECOND. Pairing is bypassed for missing-ref placeholders. */ +static HEVCFrame *alloc_frame(HEVCContext *s, HEVCLayerContext *l, + int allow_field_pairing) { const HEVCVPS *vps = l->sps->vps; const int view_id = vps->view_id[s->cur_layer]; + const int ps = s->sei.picture_timing.picture_struct; + const int is_field = allow_field_pairing && s->field_coded_stream && + (ps == AV_PICTURE_STRUCTURE_TOP_FIELD || + ps == AV_PICTURE_STRUCTURE_BOTTOM_FIELD); + HEVCFrame *first_field = (is_field && s->first_field_frame && + s->first_field_frame->f && + s->first_field_frame->f->buf[0]) ? + s->first_field_frame : NULL; int i, j, ret; + + /* Stale first-field pointer (e.g. error-path unref): start a fresh pair. */ + if (is_field && s->first_field_frame && !first_field) + s->first_field_frame = NULL; + for (i = 0; i < FF_ARRAY_ELEMS(l->DPB); i++) { HEVCFrame *frame = &l->DPB[i]; if (frame->f) @@ -158,9 +179,44 @@ static HEVCFrame *alloc_frame(HEVCContext *s, HEVCLayerContext *l) } } - ret = ff_thread_get_buffer(s->avctx, frame->f, AV_GET_BUFFER_FLAG_REF); - if (ret < 0) - goto fail; + if (first_field) { + /* Second field: alias first field's buffer at +linesize/2 so the + * decoder, walking at doubled stride, fills the alternating rows + * the first field skipped. av_frame_copy_props overwrites any + * side data attached above with the first field's. */ + const AVFrame *src = first_field->f; + AVFrame *dst = frame->f; + if (!src->buf[0]) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + dst->format = src->format; + dst->width = src->width; + dst->height = src->height; + for (j = 0; j < AV_NUM_DATA_POINTERS && src->buf[j]; j++) { + ret = av_buffer_replace(&dst->buf[j], src->buf[j]); + if (ret < 0) + goto fail; + dst->linesize[j] = src->linesize[j]; + dst->data[j] = src->data[j] + src->linesize[j] / 2; + } + ret = av_frame_copy_props(dst, src); + if (ret < 0) + goto fail; + frame->second_field = 1; + s->first_field_frame = NULL; + } else { + /* First field of a pair allocates at full height (avctx dims are + * already doubled) and then doubles linesize. */ + ret = ff_thread_get_buffer(s->avctx, frame->f, AV_GET_BUFFER_FLAG_REF); + if (ret < 0) + goto fail; + if (is_field) { + for (j = 0; j < AV_NUM_DATA_POINTERS && frame->f->linesize[j]; j++) + frame->f->linesize[j] *= 2; + s->first_field_frame = frame; + } + } frame->rpl = av_refstruct_allocz(s->pkt.nb_nals * sizeof(*frame->rpl)); if (!frame->rpl) @@ -223,7 +279,7 @@ int ff_hevc_set_new_ref(HEVCContext *s, HEVCLayerContext *l, int poc) } } - ref = alloc_frame(s, l); + ref = alloc_frame(s, l, 1); if (!ref) return AVERROR(ENOMEM); @@ -248,6 +304,11 @@ int ff_hevc_set_new_ref(HEVCContext *s, HEVCLayerContext *l, int poc) ref->f->crop_right = l->sps->output_window.right_offset; ref->f->crop_top = l->sps->output_window.top_offset; ref->f->crop_bottom = l->sps->output_window.bottom_offset; + /* SPS crop window is per-field; combined output is full-height. */ + if (ref->f->flags & AV_FRAME_FLAG_INTERLACED) { + ref->f->crop_top *= 2; + ref->f->crop_bottom *= 2; + } return 0; } @@ -303,8 +364,31 @@ int ff_hevc_output_frames(HEVCContext *s, HEVCFrame *frame = &s->layers[min_layer].DPB[min_idx]; AVFrame *f = frame->needs_fg ? frame->frame_grain : frame->f; int output = !discard && (layers_active_output & (1 << min_layer)); + const int is_field = !!(f->flags & AV_FRAME_FLAG_INTERLACED); - if (output) { + /* Second-field frame's output is carried by the first field. */ + if (is_field && frame->second_field) + output = 0; + + if (output && is_field) { + /* Halve linesize on a ref of the shared full-height buffer + * to walk the combined frame at natural stride. */ + AVFrame *out = av_frame_alloc(); + if (!out) { + ret = AVERROR(ENOMEM); + } else if ((ret = av_frame_ref(out, f)) >= 0) { + for (int p = 0; p < AV_NUM_DATA_POINTERS && out->linesize[p]; p++) + out->linesize[p] /= 2; + if (frame->flags & HEVC_FRAME_FLAG_CORRUPT) + out->flags |= AV_FRAME_FLAG_CORRUPT; + if (out->duration > 0) + out->duration *= 2; + out->pkt_dts = s->pkt_dts; + ret = av_container_fifo_write(s->output_fifo, out, + AV_CONTAINER_FIFO_FLAG_REF); + } + av_frame_free(&out); + } else if (output) { if (frame->flags & HEVC_FRAME_FLAG_CORRUPT) f->flags |= AV_FRAME_FLAG_CORRUPT; f->pkt_dts = s->pkt_dts; @@ -465,7 +549,7 @@ static HEVCFrame *generate_missing_ref(HEVCContext *s, HEVCLayerContext *l, int HEVCFrame *frame; int i, y; - frame = alloc_frame(s, l); + frame = alloc_frame(s, l, 0); if (!frame) return NULL; diff --git a/tests/ref/fate/hevc-paired-fields b/tests/ref/fate/hevc-paired-fields index f2223e770b..91c7fb2fe6 100644 --- a/tests/ref/fate/hevc-paired-fields +++ b/tests/ref/fate/hevc-paired-fields @@ -4,13 +4,5 @@ top_field_first=1 [/FRAME] [FRAME] interlaced_frame=1 -top_field_first=0 -[/FRAME] -[FRAME] -interlaced_frame=1 top_field_first=1 [/FRAME] -[FRAME] -interlaced_frame=1 -top_field_first=0 -[/FRAME] -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
