This is an automated email from the git hooks/post-receive script.
Git pushed a commit to branch master
in repository ffmpeg.
The following commit(s) were added to refs/heads/master by this push:
new b57a3d028d avformat/mov: set video packet durations in presentation
order
b57a3d028d is described below
commit b57a3d028dbefc95f5f4bb70f2b2f5f8debb9a0c
Author: panboxiaosa <[email protected]>
AuthorDate: Tue Jul 28 11:47:36 2026 +0800
Commit: James Almer <[email protected]>
CommitDate: Wed Jul 29 23:49:33 2026 +0000
avformat/mov: set video packet durations in presentation order
STTS sample deltas follow decode order, while AVPacket.duration is
defined as the interval to the next PTS in presentation order. Assigning
the deltas directly therefore produces incorrect packet durations for
VFR video with reordered frames.
After index construction and edit-list processing, sort samples by PTS
and set every duration with a following PTS from adjacent presentation
timestamps. This reorders STTS durations where possible and derives
intervals produced by the STTS/CTTS combination when necessary. Keep the
original timing table if allocation fails or timestamps are invalid.
Add FATE coverage for the official VFR H.264 sample and for a generated
MPEG-4 case whose presentation intervals are not a permutation of its
STTS deltas. Update the HEVC dts2pts CRA reference for the corrected
presentation-order durations.
Signed-off-by: panboxiaosa <[email protected]>
---
libavformat/mov.c | 109 ++++++++++++++++++++++++
tests/fate/mov.mak | 21 +++++
tests/ref/fate/hevc-bsf-dts2pts-cra | 20 ++---
tests/ref/fate/mov-vfr-bframes-derived-duration | 3 +
tests/ref/fate/mov-vfr-bframes-duration | 12 +++
5 files changed, 155 insertions(+), 10 deletions(-)
diff --git a/libavformat/mov.c b/libavformat/mov.c
index bd5f456032..46a947e26a 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -28,6 +28,7 @@
#include <inttypes.h>
#include <limits.h>
#include <stdint.h>
+#include <stdlib.h>
#include "libavutil/attributes.h"
#include "libavutil/bprint.h"
@@ -5269,6 +5270,109 @@ static void mov_build_index(MOVContext *mov, AVStream
*st)
mov_estimate_video_delay(mov, st);
}
+typedef struct MOVPresentationSample {
+ int index;
+ int64_t pts;
+} MOVPresentationSample;
+
+static int mov_compare_presentation_samples(const void *a, const void *b)
+{
+ const MOVPresentationSample *sa = a;
+ const MOVPresentationSample *sb = b;
+
+ if (sa->pts != sb->pts)
+ return (sa->pts > sb->pts) - (sa->pts < sb->pts);
+ return (sa->index > sb->index) - (sa->index < sb->index);
+}
+
+/*
+ * Set sample durations from adjacent presentation timestamps.
+ */
+static void mov_update_sample_durations(MOVContext *mov, AVStream *st)
+{
+ MOVStreamContext *sc = st->priv_data;
+ FFStream *const sti = ffstream(st);
+ MOVPresentationSample *samples = NULL;
+ MOVTimeToSample *tts_data = NULL;
+ unsigned int tts_index = 0, tts_sample = 0;
+ int count = sti->nb_index_entries;
+
+ /* A single STTS entry describes a fixed sample delta. */
+ if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO ||
+ !sc->ctts_count || sc->stts_count < 2 ||
+ !sc->tts_data || count < 2 ||
+ count >= UINT_MAX / sizeof(*tts_data))
+ return;
+
+ samples = av_malloc_array(count, sizeof(*samples));
+ tts_data = av_malloc_array(count, sizeof(*tts_data));
+ if (!samples || !tts_data)
+ goto fail;
+
+ for (int i = 0; i < count; i++) {
+ int64_t dts, offset;
+
+ if (tts_index >= sc->tts_count || !sc->tts_data[tts_index].count)
+ goto fail;
+
+ tts_data[i] = sc->tts_data[tts_index];
+ tts_data[i].count = 1;
+
+ dts = sti->index_entries[i].timestamp;
+ offset = (int64_t)sc->dts_shift + tts_data[i].offset;
+ if (dts == AV_NOPTS_VALUE ||
+ (offset > 0 && dts > INT64_MAX - offset) ||
+ (offset < 0 && dts < INT64_MIN - offset))
+ goto fail;
+
+ samples[i].index = i;
+ samples[i].pts = dts + offset;
+
+ if (++tts_sample == sc->tts_data[tts_index].count) {
+ tts_index++;
+ tts_sample = 0;
+ }
+ }
+ if (tts_index != sc->tts_count || tts_sample)
+ goto fail;
+
+ qsort(samples, count, sizeof(*samples), mov_compare_presentation_samples);
+
+ for (int i = 0; i + 1 < count; i++) {
+ uint64_t duration;
+
+ if (samples[i].pts >= samples[i + 1].pts)
+ goto fail;
+
+ /*
+ * In VFR streams with reordered frames, STTS deltas follow decode
+ * order while AVPacket.duration follows presentation order. CTTS
+ * may produce presentation intervals that cannot be obtained by
+ * merely permuting the STTS deltas, so derive each known duration
+ * from adjacent PTS.
+ */
+ duration = (uint64_t)samples[i + 1].pts - samples[i].pts;
+ if (!duration || duration > UINT_MAX)
+ goto fail;
+
+ tts_data[samples[i].index].duration = (unsigned int)duration;
+ }
+
+ av_log(mov->fc, AV_LOG_DEBUG,
+ "Updated sample durations in presentation order for stream %d\n",
+ st->index);
+
+ av_freep(&sc->tts_data);
+ sc->tts_data = tts_data;
+ sc->tts_count = count;
+ sc->tts_allocated_size = count * sizeof(*tts_data);
+ tts_data = NULL;
+
+fail:
+ av_free(samples);
+ av_free(tts_data);
+}
+
static int test_same_origin(const char *src, const char *ref) {
char src_proto[64];
char ref_proto[64];
@@ -5497,6 +5601,11 @@ static int mov_read_trak(MOVContext *c, AVIOContext *pb,
MOVAtom atom)
}
mov_build_index(c, st);
+ /*
+ * Fragment samples are appended later by mov_read_trun() and are not
+ * covered by this non-fragmented track update.
+ */
+ mov_update_sample_durations(c, st);
#if CONFIG_IAMFDEC
if (sc->iamf) {
diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index 1629796f77..5c399f4020 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -44,6 +44,8 @@ FATE_MOV_FFPROBE-$(call FRAMEMD5, MOV, MPEG4, H264_PARSER) +=
fate-mov-mp4-exten
FATE_MOV_FFPROBE-$(call DEMDEC, MOV, HEVC) += fate-mov-dovi-hvce-mp4-read
+FATE_MOV_FFPROBE-$(call DEMDEC, MOV, H264) += fate-mov-vfr-bframes-duration
+
FATE_MOV_FASTSTART = fate-mov-faststart-4gb-overflow \
FATE_SAMPLES_FFMPEG += $(FATE_MOV-yes) $(FATE_MOV_REMUX-yes)
@@ -148,6 +150,8 @@ fate-mov-spherical-mono: CMD = run
ffprobe$(PROGSSUF)$(EXESUF) -show_entries str
fate-mov-dovi-hvce-mp4-read: CMD = run ffprobe$(PROGSSUF)$(EXESUF)
-show_entries stream_side_data_list -select_streams v -v 0
$(TARGET_SAMPLES)/mov/dovi-p7-hvce.mp4
+fate-mov-vfr-bframes-duration: CMD = run ffprobe$(PROGSSUF)$(EXESUF)
-show_packets -show_entries packet=pts,dts,duration -print_format compact
-select_streams v -v 0 $(TARGET_SAMPLES)/mov/vfr-7-12-1-sequence.mp4
+
fate-mov-gpmf-remux: CMD = md5 -i
$(TARGET_SAMPLES)/mov/fake-gp-media-with-real-gpmf.mp4 -map 0 -c copy -fflags
+bitexact -f mp4
fate-mov-gpmf-remux: CMP = oneline
fate-mov-gpmf-remux: REF = e919915c5cd22c849e2aba281ddaf0c8
@@ -302,6 +306,23 @@ fate-mov-vfr: CMD = md5 -filter_complex
testsrc=size=2x2:duration=1,setpts=N*N:s
fate-mov-vfr: CMP = oneline
fate-mov-vfr: REF = 1558b4a9398d8635783c93f84eb5a60d
+FATE_MOV_FFMPEG_FFPROBE-$(call ALLYES, COLOR_FILTER SETPTS_FILTER
MPEG4_ENCODER \
+ MOV_MUXER MOV_DEMUXER FILE_PROTOCOL)
\
+ += fate-mov-vfr-bframes-derived-duration
+
+# Create VFR B-frames whose presentation durations are not a permutation of
+# the STTS sample deltas.
+tests/data/mov-vfr-bframes-derived-duration.mov: TAG = GEN
+tests/data/mov-vfr-bframes-derived-duration.mov: ffmpeg$(PROGSSUF)$(EXESUF) |
tests/data
+ $(M)$(TARGET_EXEC) $(TARGET_PATH)/$< -nostdin -v error \
+ -filter_complex "color=c=black:s=2x2:r=1,setpts=N+N*N" \
+ -frames:v 3 -fps_mode vfr -c:v mpeg4 -bf 2 -q:v 2 -threads 1 \
+ -flags +bitexact -fflags +bitexact \
+ -f mov $(TARGET_PATH)/$@ -y
+
+fate-mov-vfr-bframes-derived-duration:
tests/data/mov-vfr-bframes-derived-duration.mov
+fate-mov-vfr-bframes-derived-duration: CMD = run ffprobe$(PROGSSUF)$(EXESUF)
-show_packets -show_entries packet=pts,dts,duration -print_format compact
-select_streams v -v 0
$(TARGET_PATH)/tests/data/mov-vfr-bframes-derived-duration.mov
+
FATE_MOV_FFMPEG_FFPROBE-$(call TRANSCODE, FLAC, MP4 MOV, WAV_DEMUXER
PCM_S16LE_DECODER) += fate-mov-mp4-iamf-stereo
fate-mov-mp4-iamf-stereo: tests/data/asynth-44100-2.wav
tests/data/streamgroups/audio_element-stereo
tests/data/streamgroups/mix_presentation-stereo
fate-mov-mp4-iamf-stereo: SRC = $(TARGET_PATH)/tests/data/asynth-44100-2.wav
diff --git a/tests/ref/fate/hevc-bsf-dts2pts-cra
b/tests/ref/fate/hevc-bsf-dts2pts-cra
index cd70cde3a6..95849eb485 100644
--- a/tests/ref/fate/hevc-bsf-dts2pts-cra
+++ b/tests/ref/fate/hevc-bsf-dts2pts-cra
@@ -46,7 +46,7 @@
0, 1632000, 1776000, 48000, 949, 0xdfaed80a, F=0x0
0, 1680000, 1728000, 48000, 283, 0x81f686c2, F=0x0
0, 1728000, 1824000, 48000, 274, 0x81138813, F=0x0
-0, 1776000, 2256000, 48000, 4940, 0x92098d44, F=0x0
+0, 1776000, 2256000, 1, 4940, 0x92098d44, F=0x0
0, 1824000, 2064000, 48000, 1583, 0x25a61e94, F=0x0
0, 1872000, 1968000, 48000, 909, 0x41fdb7da, F=0x0
0, 1920000, 1920000, 48000, 251, 0xe68f7697, F=0x0
@@ -54,35 +54,35 @@
0, 2016000, 2160000, 48000, 892, 0x0d2ab3bc, F=0x0
0, 2064000, 2112000, 48000, 238, 0x45827561, F=0x0
0, 2112000, 2208000, 48000, 281, 0x2a3a8e61, F=0x0
-0, 2160000, 2256010, 48000, 4629, 0xf2e0fb0f, F=0x0
-0, 2208000, 2256005, 48000, 1453, 0x6ae5dc98, F=0x0
-0, 2256000, 2256002, 1, 869, 0x3982ae69, F=0x0
+0, 2160000, 2256010, 1, 4629, 0xf2e0fb0f, F=0x0
+0, 2208000, 2256005, 1, 1453, 0x6ae5dc98, F=0x0
+0, 2256000, 2256002, 2, 869, 0x3982ae69, F=0x0
0, 2256001, 2256001, 1, 282, 0xd9e28960, F=0x0
-0, 2256002, 2256004, 2, 259, 0x253a809d, F=0x0
+0, 2256002, 2256004, 1, 259, 0x253a809d, F=0x0
0, 2256004, 2256007, 1, 835, 0x83499f30, F=0x0
0, 2256005, 2256006, 1, 255, 0xa77b7690, F=0x0
-0, 2256006, 2256008, 1, 242, 0x83977ccf, F=0x0
+0, 2256006, 2256008, 2, 242, 0x83977ccf, F=0x0
0, 2256007, 2256019, 1, 5082, 0xba55ee51, F=0x0
0, 2256008, 2256014, 2, 1393, 0xc998b442, F=0x0
0, 2256010, 2256012, 1, 742, 0x91ab75d2, F=0x0
0, 2256011, 2256011, 1, 229, 0xfa326d98, F=0x0
0, 2256012, 2256013, 1, 275, 0x49c38226, F=0x0
0, 2256013, 2256017, 1, 869, 0xdd05acc4, F=0x0
-0, 2256014, 2256016, 2, 293, 0xcc9e904f, F=0x0
+0, 2256014, 2256016, 1, 293, 0xcc9e904f, F=0x0
0, 2256016, 2256018, 1, 334, 0x212aa4b1, F=0x0
0, 2256017, 2256029, 1, 8539, 0xcccc9eb1
0, 2256018, 2256024, 1, 1593, 0x5a351a68, F=0x0
0, 2256019, 2256022, 1, 1042, 0xb77d00cc, F=0x0
0, 2256020, 2256020, 2, 302, 0xbcdb9750, F=0x0
0, 2256022, 2256023, 1, 336, 0xc7b0a55d, F=0x0
-0, 2256023, 2256026, 1, 875, 0x7e31b046, F=0x0
+0, 2256023, 2256026, 2, 875, 0x7e31b046, F=0x0
0, 2256024, 2256025, 1, 401, 0xb473bca8, F=0x0
0, 2256025, 2256028, 1, 246, 0x43357263, F=0x0
0, 2256026, 2256038, 2, 3254, 0x8be44a2d, F=0x0
0, 2256028, 2256034, 1, 1151, 0x29d52d14, F=0x0
0, 2256029, 2256031, 1, 733, 0x33606982, F=0x0
0, 2256030, 2256030, 1, 234, 0xb70a79ff, F=0x0
-0, 2256031, 2256032, 1, 228, 0x86916848, F=0x0
-0, 2256032, 2256036, 2, 689, 0xcca34b40, F=0x0
+0, 2256031, 2256032, 2, 228, 0x86916848, F=0x0
+0, 2256032, 2256036, 1, 689, 0xcca34b40, F=0x0
0, 2256034, 2256035, 1, 223, 0xa96f6e31, F=0x0
0, 2256035, 2256037, 1, 241, 0x7ac17531, F=0x0
diff --git a/tests/ref/fate/mov-vfr-bframes-derived-duration
b/tests/ref/fate/mov-vfr-bframes-derived-duration
new file mode 100644
index 0000000000..9f62978780
--- /dev/null
+++ b/tests/ref/fate/mov-vfr-bframes-derived-duration
@@ -0,0 +1,3 @@
+packet|pts=0|dts=-32768|duration=32768
+packet|pts=98304|dts=0|duration=32768
+packet|pts=32768|dts=32768|duration=65536
diff --git a/tests/ref/fate/mov-vfr-bframes-duration
b/tests/ref/fate/mov-vfr-bframes-duration
new file mode 100644
index 0000000000..18f54b7e63
--- /dev/null
+++ b/tests/ref/fate/mov-vfr-bframes-duration
@@ -0,0 +1,12 @@
+packet|pts=0|dts=-1710000|duration=630000
+packet|pts=3510000|dts=-1080000|duration=90000
+packet|pts=1710000|dts=0|duration=90000
+packet|pts=630000|dts=630000|duration=1080000
+packet|pts=1800000|dts=1710000|duration=630000
+packet|pts=2430000|dts=1800000|duration=1080000
+packet|pts=5400000|dts=2430000|duration=630000
+packet|pts=4230000|dts=3510000|duration=1080000
+packet|pts=3600000|dts=3600000|duration=630000
+packet|pts=5310000|dts=4230000|duration=90000
+packet|pts=6030000|dts=5310000|duration=1080000
+packet|pts=7110000|dts=5400000|duration=90000
_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]