[PR] avformat/oggdec: keep timestamps monotonic across a chain (PR #23987)

Romain Beauxis via ffmpeg-devel <[email protected]> Sun, 02 Aug 2026 19:16:53 -0000
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178569821379.59.16995331196588308187@29965ddac10e>
PR #23987 opened by Romain Beauxis (toots)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23987
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23987.patch

Granule positions restart at zero in every link, and ogg_replace_stream()
dropped the timestamp the previous link had reached, so a packet taking
its timestamp from its own page came out placed within its link rather
than within the file.

Carry that timestamp forward as the offset the new link starts at, and
apply it where timestamps are handed out, since codec parsers derive
lastpts from the raw granule and parseopus.c reads a value of exactly
zero as the mark of a link having begun.



>From bdbd98afbb00746d1fa0270c416d5cc89567cf9a Mon Sep 17 00:00:00 2001
From: Romain Beauxis <[email protected]>
Date: Wed, 29 Jul 2026 08:57:43 -0500
Subject: [PATCH] avformat/oggdec: keep timestamps monotonic across a chain

Granule positions restart at zero in every link, and ogg_replace_stream()
dropped the timestamp the previous link had reached, so a packet taking
its timestamp from its own page came out placed within its link rather
than within the file.

Carry that timestamp forward as the offset the new link starts at, and
apply it where timestamps are handed out, since codec parsers derive
lastpts from the raw granule and parseopus.c reads a value of exactly
zero as the mark of a link having begun.
---
 libavformat/oggdec.c | 21 +++++++++++++++++----
 libavformat/oggdec.h |  1 +
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/libavformat/oggdec.c b/libavformat/oggdec.c
index 48279f2928..ba92754eae 100644
--- a/libavformat/oggdec.c
+++ b/libavformat/oggdec.c
@@ -239,6 +239,10 @@ static int ogg_replace_stream(AVFormatContext *s, uint32_t serial, char *magic,
     os->serial  = serial;
     os->codec   = codec;
     os->serial  = serial;
+    /* Granule positions restart at zero in every link, so what the link that
+     * just ended reached is where the new one begins. */
+    if (os->lastpts != AV_NOPTS_VALUE)
+        os->link_offset += os->lastpts;
     os->lastpts = 0;
     os->lastdts = 0;
     os->flags   = 0;
@@ -782,24 +786,33 @@ static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts)
     struct ogg *ogg       = s->priv_data;
     struct ogg_stream *os = ogg->streams + idx;
     int64_t pts           = AV_NOPTS_VALUE;
+    /* Shifting here, where timestamps are handed out, rather than where they
+     * are stored: codec parsers derive os->lastpts from the raw granule
+     * themselves, and parseopus.c reads a value of exactly zero as the mark of
+     * a link having begun. */
+    int64_t offset        = os->link_offset;
 
     if (dts)
         *dts = AV_NOPTS_VALUE;
 
     if (os->lastpts != AV_NOPTS_VALUE) {
-        pts         = os->lastpts;
+        pts         = os->lastpts + offset;
         os->lastpts = AV_NOPTS_VALUE;
     }
     if (os->lastdts != AV_NOPTS_VALUE) {
         if (dts)
-            *dts = os->lastdts;
+            *dts = os->lastdts + offset;
         os->lastdts = AV_NOPTS_VALUE;
     }
     if (os->page_end) {
         if (os->granule != -1LL) {
-            if (os->codec && os->codec->granule_is_start)
+            if (os->codec && os->codec->granule_is_start) {
                 pts = ogg_gptopts(s, idx, os->granule, dts);
-            else
+                if (pts != AV_NOPTS_VALUE)
+                    pts += offset;
+                if (dts && *dts != AV_NOPTS_VALUE)
+                    *dts += offset;
+            } else
                 os->lastpts = ogg_gptopts(s, idx, os->granule, &os->lastdts);
             os->granule = -1LL;
         }
diff --git a/libavformat/oggdec.h b/libavformat/oggdec.h
index 72f3d40c5a..337b625605 100644
--- a/libavformat/oggdec.h
+++ b/libavformat/oggdec.h
@@ -77,6 +77,7 @@ struct ogg_stream {
     uint64_t start_granule;
     int64_t lastpts;
     int64_t lastdts;
+    int64_t link_offset;    ///< start of the current link on the file timeline
     int64_t sync_pos;   ///< file offset of the first page needed to reconstruct the current packet
     int64_t page_pos;   ///< file offset of the current page
     int flags;
-- 
2.52.0

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