PR #24413 opened by James Almer (jamrial) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24413 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24413.patch
When muxing a synthetic timecode (tmcd) track (e.g. via -timecode or -map_metadata 0) for video streams that use very high timescales (such as nanosecond VUI timing from GoPro cameras with a timescale of 500,000,000), mov_create_timecode_track() blindly copied the video track's timescale to the timecode track. Unlike regular video tracks where each packet spans ~1 frame, a tmcd track contains a single sample covering the entire duration of the stream. With a 500 MHz timescale, duration * timescale exceeds INT_MAX after just ~4.29 seconds (2,147,483,647 / 500,000,000 ~= 4.295s). When calculating sample duration in get_cluster_duration(), next_dts overflows INT_MAX and triggers av_assert0(next_dts <= INT_MAX), aborting container finalization. derive the timecode (tmcd) track's timescale directly from the timecode rate across all files: - For drop-frame timecode, use fps * 1000 (e.g. 30,000 for 29.97 DF). - For fractional rates with den == 1001, use tc.rate.num (e.g. 24,000 for 23.976, 30,000 for 29.97 NDF). - Otherwise, use tc.fps (e.g. 24, 25, 30, 50, 60). This eliminates the arbitrary 100,000 timescale threshold and ensures all timecode tracks adhere consistently to standard SMPE/QuickTime timecode timebases with integer frame durations. Authored-by: Daniel Drake <[email protected]> >From 7dcbcacc654d0ddf4f4b09f313f1c9b3c5236932 Mon Sep 17 00:00:00 2001 From: Daniel Drake <[email protected]> Date: Sun, 6 Sep 2026 20:39:11 +0100 Subject: [PATCH 1/2] avformat/movenc: avoid timescale overflow in synthetic tmcd tracks When muxing a synthetic timecode (tmcd) track (e.g. via -timecode or -map_metadata 0) for video streams that use very high timescales (such as nanosecond VUI timing from GoPro cameras with a timescale of 500,000,000), mov_create_timecode_track() blindly copied the video track's timescale to the timecode track. Unlike regular video tracks where each packet spans ~1 frame, a tmcd track contains a single sample covering the entire duration of the stream. With a 500 MHz timescale, duration * timescale exceeds INT_MAX after just ~4.29 seconds (2,147,483,647 / 500,000,000 ~= 4.295s). When calculating sample duration in get_cluster_duration(), next_dts overflows INT_MAX and triggers av_assert0(next_dts <= INT_MAX), aborting container finalization. To maintain backward compatibility with existing muxing behavior and keep FATE test outputs unchanged, source timescales <= 100,000 are still copied directly. (At <= 100,000, single-sample durations can safely span up to ~6 hours without 32-bit overflow, matching the high timebase warning threshold in mov_init). When the source timescale exceeds 100,000, derive an appropriate standard timecode timescale from the timecode rate: - For drop-frame timecode, use fps * 1000 (e.g. 30,000 for 29.97 DF) so frame duration is an integer (1001). - For fractional rates with den == 1001, use tc.rate.num (e.g. 24,000 or 30,000) to yield an integer frame duration of 1001. - For integer rates with den == 1, use tc.rate.num (e.g. 25, 30, 60) for a 1:1 timescale with frame duration 1. - Otherwise fallback to integer fps (or 30). Signed-off-by: Daniel Drake <[email protected]> --- libavformat/movenc.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 4c7868c5f8..7c9e83cc3e 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -7922,7 +7922,16 @@ static int mov_create_timecode_track(AVFormatContext *s, int index, int src_inde return AVERROR(ENOMEM); *track->src_track = src_index; track->nb_src_track = 1; - track->timescale = mov->tracks[src_index].timescale; + if (mov->mode == MODE_ISM || mov->tracks[src_index].timescale <= 100000) + track->timescale = mov->tracks[src_index].timescale; + else if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME) + track->timescale = tc.fps * 1000; + else if (tc.rate.den == 1001 && tc.rate.num > 0 && tc.rate.num <= 100000) + track->timescale = tc.rate.num; + else if (tc.rate.den == 1 && tc.rate.num > 0 && tc.rate.num <= 1000) + track->timescale = tc.rate.num; + else + track->timescale = tc.fps > 0 ? tc.fps : 30; if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME) track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME; -- 2.52.0 >From e9fca153b74956dce8de97b875c5114290a12d43 Mon Sep 17 00:00:00 2001 From: Daniel Drake <[email protected]> Date: Sun, 6 Sep 2026 20:51:01 +0100 Subject: [PATCH 2/2] avformat/movenc: always derive timecode timescale from timecode rate Rather than preserving the heuristic threshold of copying the video track's timescale for <= 100,000 to maintain backward compatibility, cleanly derive the timecode (tmcd) track's timescale directly from the timecode rate across all files: - For drop-frame timecode, use fps * 1000 (e.g. 30,000 for 29.97 DF). - For fractional rates with den == 1001, use tc.rate.num (e.g. 24,000 for 23.976, 30,000 for 29.97 NDF). - Otherwise, use tc.fps (e.g. 24, 25, 30, 50, 60). This eliminates the arbitrary 100,000 timescale threshold and ensures all timecode tracks adhere consistently to standard SMPTE/QuickTime timecode timebases with integer frame durations. Update FATE reference checksums for affected MOV, MP4, and ISMV tests. Signed-off-by: Daniel Drake <[email protected]> --- libavformat/movenc.c | 10 +++------- tests/ref/lavf/ismv | 4 ++-- tests/ref/lavf/mov | 2 +- tests/ref/lavf/mp4 | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 7c9e83cc3e..3e8ccd9272 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -7922,16 +7922,12 @@ static int mov_create_timecode_track(AVFormatContext *s, int index, int src_inde return AVERROR(ENOMEM); *track->src_track = src_index; track->nb_src_track = 1; - if (mov->mode == MODE_ISM || mov->tracks[src_index].timescale <= 100000) - track->timescale = mov->tracks[src_index].timescale; - else if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME) + if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME) track->timescale = tc.fps * 1000; - else if (tc.rate.den == 1001 && tc.rate.num > 0 && tc.rate.num <= 100000) - track->timescale = tc.rate.num; - else if (tc.rate.den == 1 && tc.rate.num > 0 && tc.rate.num <= 1000) + else if (tc.rate.den == 1001) track->timescale = tc.rate.num; else - track->timescale = tc.fps > 0 ? tc.fps : 30; + track->timescale = tc.fps; if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME) track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME; diff --git a/tests/ref/lavf/ismv b/tests/ref/lavf/ismv index 12f5c8c960..86aaa39f1c 100644 --- a/tests/ref/lavf/ismv +++ b/tests/ref/lavf/ismv @@ -1,7 +1,7 @@ -48fb8d7a5d19bd60f3a49ccf4b7d6593 *tests/data/lavf/lavf.ismv +43fac7feb5f11c226e5b409e19c7fdb6 *tests/data/lavf/lavf.ismv 313169 tests/data/lavf/lavf.ismv tests/data/lavf/lavf.ismv CRC=0x9d9a638a -ba53902fb100d5d1d0603114802df3a5 *tests/data/lavf/lavf.ismv +6c2b3ec373d68d028b44fbcf0cae58c2 *tests/data/lavf/lavf.ismv 322247 tests/data/lavf/lavf.ismv tests/data/lavf/lavf.ismv CRC=0xc5569484 3b6023766845b51b075aed474c00f73c *tests/data/lavf/lavf.ismv diff --git a/tests/ref/lavf/mov b/tests/ref/lavf/mov index 0ce413a666..1d3c1e1f2f 100644 --- a/tests/ref/lavf/mov +++ b/tests/ref/lavf/mov @@ -1,4 +1,4 @@ -bcaddabe56a6517cc3e1130e0e0a31fc *tests/data/lavf/lavf.mov +a95d0651532051ce24bda4e9c750d1e5 *tests/data/lavf/lavf.mov 357371 tests/data/lavf/lavf.mov tests/data/lavf/lavf.mov CRC=0xbb2b949b 554d925e6fb9babdaadc474ebe1c5761 *tests/data/lavf/lavf.mov diff --git a/tests/ref/lavf/mp4 b/tests/ref/lavf/mp4 index 89143af524..ec20ab5d55 100644 --- a/tests/ref/lavf/mp4 +++ b/tests/ref/lavf/mp4 @@ -1,4 +1,4 @@ -a6e44724cab1c4b50c49b0fd227b87d3 *tests/data/lavf/lavf.mp4 +2fa2e05e8685530ca952293542d0c783 *tests/data/lavf/lavf.mp4 312477 tests/data/lavf/lavf.mp4 tests/data/lavf/lavf.mp4 CRC=0x9d9a638a e2876a5a13c2e3c5570f114ff9ba0d41 *tests/data/lavf/lavf.mp4 -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
