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]