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]

Reply via email to