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

When the caller does not provide valid any valid timestamp (both PTS and DTS 
read NOPTS), use the picture timing SEI of each AU to determine the PTS and DTS 
of each pic.

HEVC streams with timing information are now muxed correctly in containers like 
mp4, and it fixes the deprecation warning about the unset timestamps for this 
format.

The piece of code is mostly the same as #25780. It could be moved to a shared 
code file. Suggestions welcome.


>From affd065cfa4fd09d966f8160001c4d1024ff3d2e Mon Sep 17 00:00:00 2001
From: cubicibo <[email protected]>
Date: Fri, 6 Nov 2026 13:40:58 +0100
Subject: [PATCH] lavc hevc: add timestamps fallback to PicTiming SEI.

When the caller does not provide valid any valid timestamp (neither PTS or DTS)
use the picture timing SEI of each AU to determine the PTS and DTS of each pic.

HEVC streams with timing information are now muxed correctly in containers like 
mp4.
---
 libavcodec/hevc/parser.c | 61 +++++++++++++++++++++++++++++++++++++++-
 libavcodec/hevc/sei.c    | 33 ++++++++++++++++++++--
 libavcodec/hevc/sei.h    | 19 ++++++++++++-
 3 files changed, 109 insertions(+), 4 deletions(-)

diff --git a/libavcodec/hevc/parser.c b/libavcodec/hevc/parser.c
index 47a1ac70d00..e2eaef0acf9 100644
--- a/libavcodec/hevc/parser.c
+++ b/libavcodec/hevc/parser.c
@@ -45,6 +45,8 @@ typedef struct HEVCParserContext {
     HEVCParamSets ps;
     HEVCSEI sei;
 
+    int64_t reference_dts;
+
     int is_avc;
     int nal_length_size;
     int parsed_extradata;
@@ -335,14 +337,70 @@ static int hevc_parse(AVCodecParserContext *s, 
AVCodecContext *avctx,
 
     is_dummy_buf &= (dummy_buf == buf);
 
-    if (!is_dummy_buf)
+    if (!is_dummy_buf) {
         parse_nal_units(s, buf, buf_size, avctx);
 
+        AVRational time_base = { 0, 1 };
+
+        if (avctx->framerate.num)
+            time_base = av_inv_q(avctx->framerate);
+        if (ctx->sei.picture_timing.au_cpb_removal_delay_minus1 >= 0) {
+            s->dts_sync_point    = ctx->sei.buffering_period.present;
+            s->dts_ref_dts_delta = 
ctx->sei.picture_timing.au_cpb_removal_delay_minus1 + 1;
+            s->pts_dts_delta     = 
ctx->sei.picture_timing.pic_dpb_output_delay;
+        } else {
+            s->dts_sync_point    = INT_MIN;
+            s->dts_ref_dts_delta = INT_MIN;
+            s->pts_dts_delta     = INT_MIN;
+        }
+
+        if (s->dts_sync_point >= 0) {
+            int64_t den = time_base.den * (int64_t)avctx->pkt_timebase.num;
+            if (den > 0) {
+                int64_t num = time_base.num * (int64_t)avctx->pkt_timebase.den;
+                if (s->dts != AV_NOPTS_VALUE) {
+                    // got DTS from the stream, update reference timestamp
+                    ctx->reference_dts = av_sat_sub64(s->dts, 
av_rescale(s->dts_ref_dts_delta, num, den));
+                } else if (s->pts != AV_NOPTS_VALUE) {
+                    // container only has a PTS, guess DTS
+                    s->dts = av_sat_sub64(s->pts, av_rescale(s->pts_dts_delta, 
num, den));
+                    ctx->reference_dts = av_sat_sub64(s->dts, 
av_rescale(s->dts_ref_dts_delta, num, den));
+                } else if (ctx->reference_dts != AV_NOPTS_VALUE) {
+                    // compute DTS based on reference timestamp
+                    s->dts = av_sat_add64(ctx->reference_dts, 
av_rescale(s->dts_ref_dts_delta, num, den));
+                } else if (s->dts_sync_point > 0) {
+                    // both pts and dts are unset but we have a buffering 
period: initialize
+                    // the initial_cpb_removal_delay is ignored
+                    ctx->reference_dts = 0;
+                    s->dts = 0;
+                }
+
+                if (ctx->reference_dts != AV_NOPTS_VALUE && s->pts == 
AV_NOPTS_VALUE) {
+                    int64_t pts_dts_delta = av_rescale(s->pts_dts_delta, num, 
den);
+                    uint64_t pts = (uint64_t)s->dts + pts_dts_delta;
+                    if (pts == av_sat_add64(s->dts, pts_dts_delta))
+                        s->pts = pts;
+                }
+
+                if (s->dts_sync_point > 0)
+                    ctx->reference_dts = s->dts; // new reference
+            }
+        }
+    }
+
     *poutbuf      = buf;
     *poutbuf_size = buf_size;
     return next;
 }
 
+static int hevc_parser_init(AVCodecParserContext *s)
+{
+    HEVCParserContext *ctx = s->priv_data;
+    ctx->reference_dts = AV_NOPTS_VALUE;
+
+    return 0;
+}
+
 static void hevc_parser_close(AVCodecParserContext *s)
 {
     HEVCParserContext *ctx = s->priv_data;
@@ -357,6 +415,7 @@ static void hevc_parser_close(AVCodecParserContext *s)
 const FFCodecParser ff_hevc_parser = {
     PARSER_CODEC_LIST(AV_CODEC_ID_HEVC),
     .priv_data_size = sizeof(HEVCParserContext),
+    .init           = hevc_parser_init,
     .parse          = hevc_parse,
     .close          = hevc_parser_close,
 };
diff --git a/libavcodec/hevc/sei.c b/libavcodec/hevc/sei.c
index 83c726a217b..f1d5866a410 100644
--- a/libavcodec/hevc/sei.c
+++ b/libavcodec/hevc/sei.c
@@ -49,11 +49,29 @@ static int 
decode_nal_sei_decoded_picture_hash(HEVCSEIPictureHash *s,
     return 0;
 }
 
+static int decode_nal_sei_buffering_period(HEVCSEI *s, GetBitContext *gb,
+                                           const HEVCParamSets *ps, void 
*logctx)
+{
+    HEVCSEIBufferingPeriod *h = &s->buffering_period;
+
+    uint32_t bp_sps_id = get_ue_golomb_31(gb);
+    if (bp_sps_id >= HEVC_MAX_SPS_COUNT)
+        return AVERROR_INVALIDDATA;
+
+    /* The BP SPS must match the active parameter set in the AU.
+     * if the stream has no APS, recover from the BP value */
+    s->active_parameter_sets.active_seq_parameter_set_id = bp_sps_id;
+
+    h->bp_seq_parameter_set_id = bp_sps_id;
+    h->present = 1;
+    return 0;
+}
+
 static int decode_nal_sei_pic_timing(HEVCSEI *s, GetBitContext *gb,
                                      const HEVCParamSets *ps, void *logctx)
 {
     HEVCSEIPictureTiming *h = &s->picture_timing;
-    const HEVCSPS *sps = ps->sps_list[s->active_seq_parameter_set_id];
+    const HEVCSPS *sps = 
ps->sps_list[s->active_parameter_sets.active_seq_parameter_set_id];
 
     if (!sps)
         return AVERROR_INVALIDDATA;
@@ -74,6 +92,13 @@ static int decode_nal_sei_pic_timing(HEVCSEI *s, 
GetBitContext *gb,
             av_log(logctx, AV_LOG_DEBUG, "Frame/Field Tripling\n");
             h->picture_struct = HEVC_SEI_PIC_STRUCT_FRAME_TRIPLING;
         }
+        skip_bits(gb, 3); /* source_scan_type u(2), duplicate_flag u(1) */
+    }
+    /* CpbDpbDelaysPresentFlag */
+    if (sps->hdr.nal_hrd_parameters_present_flag || 
sps->hdr.vcl_hrd_parameters_present_flag) {
+        h->au_cpb_removal_delay_minus1 = get_bits_long(gb,
+                                                       1 + 
sps->hdr.au_cpb_removal_delay_length_minus1);
+        h->pic_dpb_output_delay = get_bits_long(gb, 1 + 
sps->hdr.dpb_output_delay_length_minus1);
     }
 
     return 0;
@@ -99,6 +124,8 @@ static int decode_nal_sei_active_parameter_sets(HEVCSEI *s, 
GetBitContext *gb, v
     int num_sps_ids_minus1;
     unsigned active_seq_parameter_set_id;
 
+    HEVCSEIActivePS *h = &s->active_parameter_sets;
+
     get_bits(gb, 4); // active_video_parameter_set_id
     get_bits(gb, 1); // self_contained_cvs_flag
     get_bits(gb, 1); // num_sps_ids_minus1
@@ -114,8 +141,8 @@ static int decode_nal_sei_active_parameter_sets(HEVCSEI *s, 
GetBitContext *gb, v
         av_log(logctx, AV_LOG_ERROR, "active_parameter_set_id %d invalid\n", 
active_seq_parameter_set_id);
         return AVERROR_INVALIDDATA;
     }
-    s->active_seq_parameter_set_id = active_seq_parameter_set_id;
 
+    h->active_seq_parameter_set_id = active_seq_parameter_set_id;
     return 0;
 }
 
@@ -229,6 +256,8 @@ static int decode_nal_sei_prefix(GetBitContext *gb, 
GetByteContext *gbyte,
     switch (type) {
     case 256:  // Mismatched value from HM 8.1
         return decode_nal_sei_decoded_picture_hash(&s->picture_hash, gbyte);
+    case SEI_TYPE_BUFFERING_PERIOD:
+        return decode_nal_sei_buffering_period(s, gb,  ps, logctx);
     case SEI_TYPE_PIC_TIMING:
         return decode_nal_sei_pic_timing(s, gb, ps, logctx);
     case SEI_TYPE_RECOVERY_POINT:
diff --git a/libavcodec/hevc/sei.h b/libavcodec/hevc/sei.h
index 59bd2b45f87..e68e9703072 100644
--- a/libavcodec/hevc/sei.h
+++ b/libavcodec/hevc/sei.h
@@ -50,8 +50,16 @@ typedef struct HEVCSEIFramePacking {
     int current_frame_is_frame0_flag;
 } HEVCSEIFramePacking;
 
+typedef struct HEVCSEIBufferingPeriod {
+    int present;
+    int bp_seq_parameter_set_id;
+} HEVCSEIBufferingPeriod;
+
 typedef struct HEVCSEIPictureTiming {
+    int present;
     int picture_struct;
+    int au_cpb_removal_delay_minus1;
+    int pic_dpb_output_delay;
 } HEVCSEIPictureTiming;
 
 typedef struct HEVCSEIAlternativeTransfer {
@@ -103,11 +111,16 @@ typedef struct HEVCSEIRecoveryPoint {
     uint8_t has_recovery_poc;
 } HEVCSEIRecoveryPoint;
 
+typedef struct HEVCSEIActivePS {
+    int active_seq_parameter_set_id;
+} HEVCSEIActivePS;
+
 typedef struct HEVCSEI {
     H2645SEI common;
     HEVCSEIPictureHash picture_hash;
+    HEVCSEIBufferingPeriod buffering_period;
     HEVCSEIPictureTiming picture_timing;
-    int active_seq_parameter_set_id;
+    HEVCSEIActivePS active_parameter_sets;
     HEVCSEITimeCode timecode;
     HEVCSEITDRDI tdrdi;
     HEVCSEIRecoveryPoint recovery_point;
@@ -127,6 +140,10 @@ int ff_hevc_decode_nal_sei(GetBitContext *gb, void 
*logctx, HEVCSEI *s,
  */
 static inline void ff_hevc_reset_sei(HEVCSEI *sei)
 {
+    sei->picture_timing.present = 0;
+    /* optionally present in PicTiming, defaults to < 0 */
+    sei->picture_timing.au_cpb_removal_delay_minus1 = -1;
+    sei->buffering_period.present = 0;
     sei->timecode.present = 0;
     sei->tdrdi.present = 0;
     ff_h2645_sei_reset(&sei->common);
-- 
2.54.0

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

Reply via email to