Change framerate in ffmpeg without reencoding
Solution 1:
Here's the method using current versions of FFmpeg. It relies on the concat demuxer not rescaling the PTS of inputs after the first file, but simply applying a fixed offset. Let's say you have a 30 fps stream with a timescale of 15360
(typical of FFmpeg output). That means frame 0
has PTS 0
and frame 30
has PTS 15360
. This would become a 45 fps stream if we could change the timescale to 23040
without affecting the PTS values.
Essentially, that's what the method below does.
1. Identify the source properties.
Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1171 kb/s,
30 fps, 30 tbr, 15360 tbn (default)
You want to note the source properties, especially resolution and tbn
.
2a. (Optional) Change the timescale to something convenient, to make calculations simpler.
ffmpeg -i in.mp4 -c copy -an -video_track_timescale 30 in-v30.mp4
This gets us
Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1171 kb/s, \
30 fps, 30 tbr, 30 tbn (default
If you do this step, the new timescale should be equal or an integral multiple of the original framerate.
2b. Calculate the timescale needed, so that for target framerate x
, PTS of frame # x
in the source should have the same value as the new tbn
.
If you carried out step 2a, this is very easy and it's simply the new framerate. So, for target fps 45
, new tbn
should be 45
.
3. Generate dummy video.
ffmpeg -f lavfi -i color=s=1280x720:r=45:d=1 -profile:v main -video_track_timescale 45 0.mp4
All properties should be same like resolution, H.264 profile, pixel format, refs count..etc for best results.
4 Concat the videos.
First make a text file
file '0.mp4'
file 'in-v30.mp4'
Then, the concat
ffmpeg -f concat -i list.txt -c copy -video_track_timescale 45 45fps.mp4
The output file will have the 2nd video playing at 45 fps.
5. Now, cleave off the dummy preroll
ffmpeg -ss 1.1 -i 45fps.mp4 -c copy -avoid_negative_ts make_zero in45.mp4
and you have
Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1757 kb/s, \
45 fps, 45 tbr, 11520 tbn (default)
I did say this was convoluted!
Solution 2:
Use -itsscale
on the input video to achieve an effective framerate change. It works fine with -vcodec copy
.
Solution 3:
ffmpeg -itsscale 1.0427083 -i input.mp4 -codec copy output.mp4
This correctly slows down a 25 fps mp4 created by Handbrake from a PAL DVD source to 23.974 fps. The original show is NTSC. The audio stays in sync all the way through the now 47 minutes running time. It is very fast as no decoding/encoding is being done. However, there are audio glitches (dropouts) at roughly 3-second intervals throughout. Same result with vcodec
substituted for codec, except that while video is not re-encoded, the audio is re-encoded at half the original bitrate and still has the dropout glitches.
ffmpeg -itsscale 1.0427083 -i input.mp4 -vcodec copy -filter:a "atempo=0.959041" output.mp4
This eliminates the audio dropouts, but does re-encode audio. That's much faster than re-encoding video. The remaining drawback is that it defaults to half the original audio bit rate. Need to figure out how to set the audio bit rate for the re-encode.