PR #23136 opened by Mikael Magnusson (Mikachu) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23136 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23136.patch
This fixes playing twitch vods longer than 26h30m (very common for gamesdonequick). Adds a helper that unwraps id3 33-bit values based on where we think we should be, disable other things that try to do the same thing but worse. Callers now get a full 64-bit timestamp. Moving the warning hunk in demux.c after the call to compute_pkt_fields avoids spurious warnings when pts wraps before dts. >From 480dd6aa6756081e04f57efb8a28951676cfc038 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson <[email protected]> Date: Mon, 18 May 2026 02:33:12 +0200 Subject: [PATCH] avformat/hls: unwrap id3 33-bit timestamps This fixes playing twitch vods longer than 26h30m (very common for gamesdonequick). Adds a helper that unwraps id3 33-bit values based on where we think we should be, disable other things that try to do the same thing but worse. Callers now get a full 64-bit timestamp. Moving the warning hunk in demux.c after the call to compute_pkt_fields avoids spurious warnings when pts wraps before dts. --- libavformat/demux.c | 20 ++++++++++---------- libavformat/hls.c | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/libavformat/demux.c b/libavformat/demux.c index 555399720c..83adbf8358 100644 --- a/libavformat/demux.c +++ b/libavformat/demux.c @@ -1461,16 +1461,6 @@ static int read_frame_internal(AVFormatContext *s, AVPacket *pkt) sti->need_context_update = 0; } - if (pkt->pts != AV_NOPTS_VALUE && - pkt->dts != AV_NOPTS_VALUE && - pkt->pts < pkt->dts) { - av_log(s, AV_LOG_WARNING, - "Invalid timestamps stream=%d, pts=%s, dts=%s, size=%d\n", - pkt->stream_index, - av_ts2str(pkt->pts), - av_ts2str(pkt->dts), - pkt->size); - } if (s->debug & FF_FDEBUG_TS) av_log(s, AV_LOG_DEBUG, "ff_read_packet stream=%d, pts=%s, dts=%s, size=%d, duration=%"PRId64", flags=%d\n", @@ -1532,6 +1522,16 @@ static int read_frame_internal(AVFormatContext *s, AVPacket *pkt) if (ret >= 0) { AVStream *const st = s->streams[pkt->stream_index]; FFStream *const sti = ffstream(st); + if (pkt->pts != AV_NOPTS_VALUE && + pkt->dts != AV_NOPTS_VALUE && + pkt->pts < pkt->dts) { + av_log(s, AV_LOG_WARNING, + "Invalid timestamps stream=%d, pts=%s, dts=%s, size=%d\n", + pkt->stream_index, + av_ts2str(pkt->pts), + av_ts2str(pkt->dts), + pkt->size); + } int discard_padding = 0; if (sti->first_discard_sample && pkt->pts != AV_NOPTS_VALUE) { int64_t pts = pkt->pts - (is_relative(pkt->pts) ? RELATIVE_TS_BASE : 0); diff --git a/libavformat/hls.c b/libavformat/hls.c index 29dc08ef1f..9f6ce0e546 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -1214,6 +1214,19 @@ static int id3_has_changed_values(struct playlist *pls, AVDictionary *metadata, return 0; } +/* Unwrap a raw 33-bit MPEG-TS timestamp relative to an expected value in the same timebase. */ +static int64_t unwrap_timestamp(int64_t raw, int64_t expected) +{ + int64_t mask = (1LL << 33) - 1; + int64_t base = expected & ~mask; + int64_t candidate = base | (raw & mask); + if (candidate < expected - (1LL << 32)) + candidate += 1LL << 33; + else if (candidate > expected + (1LL << 32)) + candidate -= 1LL << 33; + return candidate; +} + /* Parse ID3 data and handle the found data */ static void handle_id3(AVIOContext *pb, struct playlist *pls) { @@ -2063,7 +2076,7 @@ static int set_stream_info_from_input_stream(AVStream *st, struct playlist *pls, return err; if (pls->is_id3_timestamped) /* custom timestamps via id3 */ - avpriv_set_pts_info(st, 33, 1, MPEG_TIME_BASE); + avpriv_set_pts_info(st, 64, 1, MPEG_TIME_BASE); else avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den); @@ -2264,6 +2277,7 @@ static int hls_read_header(AVFormatContext *s) if (!(pls->ctx = avformat_alloc_context())) return AVERROR(ENOMEM); + pls->ctx->correct_ts_overflow = 0; /* this is handled manually in hls_read_packet */ if (pls->n_segments == 0) continue; @@ -2534,13 +2548,13 @@ static AVRational get_timebase(struct playlist *pls) return pls->ctx->streams[pls->pkt->stream_index]->time_base; } -static int compare_ts_with_wrapdetect(int64_t ts_a, struct playlist *pls_a, - int64_t ts_b, struct playlist *pls_b) +static int compare_ts(int64_t ts_a, struct playlist *pls_a, + int64_t ts_b, struct playlist *pls_b) { int64_t scaled_ts_a = av_rescale_q(ts_a, get_timebase(pls_a), MPEG_TIME_BASE_Q); int64_t scaled_ts_b = av_rescale_q(ts_b, get_timebase(pls_b), MPEG_TIME_BASE_Q); - return av_compare_mod(scaled_ts_a, scaled_ts_b, 1LL << 33); + return (scaled_ts_a > scaled_ts_b) - (scaled_ts_a < scaled_ts_b); } static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) @@ -2575,6 +2589,18 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) fill_timing_for_id3_timestamped_stream(pls); } + if (pls->pkt->dts != AV_NOPTS_VALUE && + c->cur_timestamp != AV_NOPTS_VALUE && + (pls->is_id3_timestamped || + pls->ctx->streams[pls->pkt->stream_index]->pts_wrap_bits == 33)) + { + int64_t expected = av_rescale_q(c->cur_timestamp, + AV_TIME_BASE_Q, get_timebase(pls)); + pls->pkt->dts = unwrap_timestamp(pls->pkt->dts, expected); + if (pls->pkt->pts != AV_NOPTS_VALUE) + pls->pkt->pts = unwrap_timestamp(pls->pkt->pts, expected); + } + if (c->first_timestamp == AV_NOPTS_VALUE && pls->pkt->dts != AV_NOPTS_VALUE) { int64_t seg_idx = pls->cur_seq_no - pls->start_seq_no; @@ -2639,7 +2665,7 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) int64_t mindts = minpls->pkt->dts; if (dts == AV_NOPTS_VALUE || - (mindts != AV_NOPTS_VALUE && compare_ts_with_wrapdetect(dts, pls, mindts, minpls) < 0)) + (mindts != AV_NOPTS_VALUE && compare_ts(dts, pls, mindts, minpls) < 0)) minplaylist = i; } } -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
