mov_flush_fragment()'s peek loop populates track->track_duration via
ff_interleaved_peek() for every track whose end_reliable is unset. The
result is consumed by get_cluster_duration() for the current flush, and
later by ff_mov_write_packet()'s "first packet of new fragment" override
(cluster[entry].dts = start_dts + track_duration).

Under av_interleaved_write_frame()'s multi-packet emission loop the
trigger packet has already been removed from the interleave queue when
the flush runs, so ff_interleaved_peek() returns the *following* same-
stream packet rather than the current trigger. For tracks with entry == 0
the peeked future dts is not used by get_cluster_duration (no moof is
written), but it is consumed later by the override and mis-aligns
cluster[0].dts.

With frag_every_frame and two interleaved tracks where one is faster,
this produces pairs of consecutive moof+mdat fragments sharing the same
tfdt, the earlier moof carrying tfhd.default_sample_duration=0 on the
higher-rate track when pkt->duration is unset (typical for live RTMP
without a probed avg_frame_rate). Logs show the paired warnings:

  Packet duration: -<frame_dur> / dts: <DTS> is out of range
  pts has no value

Restrict the peek to tracks with entry > 0 so the future-dts value is
only ever used for the moof being written in this flush and never
lingers in track_duration to drive a later override.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
Signed-off-by: Teodor Atroshenko <[email protected]>
---
 libavformat/movenc.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 8157b5e277..1f2c1134d8 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -6540,9 +6540,21 @@ static int mov_flush_fragment(AVFormatContext *s, int force)
     // of fragments was triggered automatically by an AVPacket, we
     // already have reliable info for the end of that track, but other
     // tracks may need to be filled in.
+    //
+    // Only do this for tracks that actually have a queued sample
+    // (track->entry > 0) and will therefore emit a moof in this flush.
+    // For tracks with no queued sample, the peeked packet's dts is
+    // typically *not* the next-to-be-added packet for that track --
+    // under av_interleaved_write_frame()'s multi-packet emission loop the
+    // current trigger packet has already been removed from the queue, so
+    // ff_interleaved_peek() returns the *following* same-stream packet.
+    // Storing that future dts into track_duration would later mis-drive
+    // ff_mov_write_packet()'s "first packet of new fragment" override,
+    // producing duplicate-tfdt fragments with default_sample_duration=0
+    // under frag_every_frame on streams whose pkt->duration is unset.
     for (i = 0; i < mov->nb_streams; i++) {
         MOVTrack *track = &mov->tracks[i];
-        if (!track->end_reliable) {
+        if (track->entry > 0 && !track->end_reliable) {
             const AVPacket *pkt = ff_interleaved_peek(s, i);
             if (pkt) {
                 int64_t offset, dts, pts;
--
2.45.2.windows.1

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

Reply via email to