ffmpeg frame rate weirdness

Dear Lazyweb, can you explain to me why concatenating two 30fps video files results in a 29.72fps video file, and how to avoid it?

% ffmpeg -i example1.ts 2>&1 | grep fps
Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(progressive), 640x360 [SAR 1:1 DAR 16:9], 30 fps, 30 tbr, 90k tbn, 60 tbc

% ffmpeg -i example2.ts 2>&1 | grep fps
Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(progressive), 640x360 [SAR 1:1 DAR 16:9], 30 fps, 30 tbr, 90k tbn, 60 tbc

% (echo file example1.ts; echo file example2.ts) > list.txt
% ffmpeg -hide_banner -v 16 -f concat -i list.txt -codec copy example3.mp4

% ffmpeg -i example3.mp4 2>&1 | grep fps
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1:1 DAR 16:9], 493 kb/s, 29.72 fps, 30 tbr, 90k tbn, 60 tbc (default)

Transcoding it gets me 30fps, but since both started at 30fps, why doesn't copying work?

% ffmpeg -hide_banner -v 16 -f concat -i list.txt -c:v libx264 -profile:v high -crf 28 -pix_fmt yuv420p -acodec libfdk_aac -b:a 192k -r 30 -movflags faststart example4.mp4
% ffmpeg -i example4.mp4 2>&1 | grep fps

Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1:1 DAR 16:9], 449 kb/s, 30 fps, 30 tbr, 15360 tbn, 60 tbc (default)

Previously.

Tags: , , , ,

23 Responses:

  1. J. Peterson says:

    I was thinking, "huh, it converted it to the NTSC rate", but nope, that's 29.970 fps.

  2. Pedro says:

    The many ways in which video encodes can be subtly borked is truly infinite. My best guess is that the input files are not actually exactly 30fps. mediainfo reports "Frame rate mode : Variable" for both, which can hide all kinds of weirdness.

    The -i flag is only reporting metadata, not actually going over every frame. My first guess is that when you tell ffmpeg to concatenate the files (which does require going over every frame) it realizes the average FPS is actually 29.72, so that's what it writes to the output metadata.

    Note that your last command is going to produce a 30 fps file no matter what, because you're forcing it with "-r 30".

    If the end result looks correct it should be ok just ignore the problem. If you really need to force the output to 30fps without reencoding, one of the answers from here should work.

    • jwz says:

      Well, obviously I wouldn't even be fucking around with this if everything was fine. So we've got these projectors, and they have USB ports, and appear to support ExFAT, so I can put a 12GB file on it, and yet it doesn't play. It would be more convenient to just plug a flash drive into the projector, instead of needing to plug a whole computer into the HDMI port instead. So now we play whack-a-mole to see why it doesn't like the file. And, "Hey, that frame rate, that seems weird", was the first thing that jumped out.

      • Pedro says:

        Ah. The HW decoders they put in those kinds of device have all sorts of limitations.

        If I was in that position I would start by finding an encode setting that works (keep the choice of codecs conservative, mp4 container, H264 main profile, level 3.1, that sort of thing). Once you find it, don't even try to use original files, reencode everything to the known good settings. You'll save more time that way in the end.

        • jwz says:

          I thought I was using maximally-compatible encoder settings... I used the same ones as above.

          • Owen W. says:

            It might want 29.97, not 30. It might want interlaced! It might not support AAC? Try removing the sound so you're just troubleshooting the video. Try loading a bunch of short 10 second clips with different settings (reduced size, reduced bitrate, etc), until something works.

      • Gordon says:

        The builtin decoder in the projector is going to be some low effort low cost trash.
        What about the bottom end of the plug a whole computer in approach and grab some rasberry pi's?

        • jwz says:

          Sure, but that still costs a hundred bucks (pi, enclosure, power supply, cable). Whereas thumb drives basically fall out of the sky.

      • Matt M says:

        Which model of projector is it? Some of these things are just plain defective.

  3. Andrew says:

    You can do it with:


    $ (echo file example1.ts; echo duration 3.96666666666666s; echo file example2.ts; echo duration 3.96666666666666s) > list.txt
    $ ffmpeg -f concat -i list.txt -codec copy out.mp4 -y && ffprobe out.mp4
    ...
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1:1 DAR 16:9], 497 kb/s, 30 fps, 30 tbr, 90k tbn, 60 tbc (default)

    The output of ffprobe -show_entries root -print_format json example1.ts shows exact frame counts and timings of those frames.

    Your input videos have 119 frames each, at 1/30 fps that’s approximately 3.96666666666666s.

    What’s throwing off the timings for concat is that the audio packets do not line up exactly with the video frames. example1.ts and example2.ts have 3.989333s and 4.053333s of audio, respectively. If you strip the audio from them, your original concat command produces the expected frame rate. No guarantees that it’ll work on your projector though.

    • jwz says:

      Huh. I have no idea why the audio and video aren't the same length. I'm not doing anything with audio. I trimmed those out of the original files with:

      ffmpeg -y -hide_banner -v 16 -ss 60 -i 'Plug It In.mov' \
      -t 4 -c:v libx264 -profile:v high -crf 28 -pix_fmt yuv420p -acodec libfdk_aac -b:a 192k -r 30 -movflags faststart \
      -vf 'crop=720:405, scale=640:360, setsar=1:1' \
      example1.ts

      ffmpeg -y -hide_banner -v 16 -ss 99 -i 'Electric Barbarella.mp4' \
      -t 4 -c:v libx264 -profile:v high -crf 28 -pix_fmt yuv420p -acodec libfdk_aac -b:a 192k -r 30 -movflags faststart \
      -vf 'crop=714:401, scale=640:360, setsar=1:1' \
      example2.ts

      • Andrew says:

        The audio in the input files is slightly longer than the video because aac is encoding blocks of 1024 samples, and the 44100Hz sample rate isn’t a multiple of that. This is news to me too, that essentially all video clips with compressed audio have slightly different audio and video lengths. To get them to line up exactly, with these parameters, you'd need a multiple of lcm(1024, 44100/30) / 44100Hz = 17+2/30s.

        When you transcoded, ffmpeg added an extra 5 video frames to make the video just slightly longer than the audio.

        Does example1.ts play on your projector?

        • jwz says:

          Crazy. I wonder if -shortest would stop this, or does that only apply when there are multiple input files?

          • Andrew says:

            I tried that and a few other options without noticing anything obviously different...

            How about giving ffmpeg a detailed list of what segments you want from which videos as a complex filtergraph, and let it encode everything in one go from the original sources such as Plug It In.mov?

            Here’s an example of that. Of course you should write a program to generate this filterscript. In this sample, the only special thing is adjusting the volume of a single clip, but you could add funky things like audio crossfades, or video blending at transitions.


            ffmpeg -filter_complex '
            movie=1.mp4,
            trim=start=1s:duration=1s,
            setpts=PTS-STARTPTS,
            crop=320:240
            [movie1-video];

            amovie=1.mp4,
            atrim=start=1s:duration=1s,
            asetpts=PTS-STARTPTS
            [movie1-audio];

            movie=2.mp4,
            trim=start=2s:duration=3s,
            setpts=PTS-STARTPTS,
            crop=320:240
            [movie2-video];

            amovie=2.mp4,
            atrim=start=2s:duration=3s,
            asetpts=PTS-STARTPTS
            [movie2-audio];

            movie=3.mp4,
            trim=start=5s:duration=1s,
            setpts=PTS-STARTPTS,
            crop=320:240
            [movie3-video];

            amovie=3.mp4,
            atrim=start=5s:duration=1s,
            asetpts=PTS-STARTPTS,
            volume=0.01
            [movie3-audio];

            [movie1-video][movie1-audio]
            [movie2-video][movie2-audio]
            [movie3-video][movie3-audio]
            concat=n=3:v=1:a=1
            [outv][outa]

            ' -map '[outv]' -map '[outa]' -vcodec libx264 out.mp4 -y

            The video frame timing output from this is perfect for me, with nothing added or dropped or out of sync.

            • jwz says:

              That does seem to solve the frame rate weirdness, thanks!

              Couple of downsides, though.

              • The old way let me cache clips, so it was slightly faster. If the same clip ended up in the output video multiple times, I only had to encode it once. One notable example of that is the "static" clips, which, though they are short, are now being encoded hundreds of times.

              • I wonder whether "trim=start=" is seeking in the file the slow way or the fast way: that weird thing where putting "-ss" before "-i" is waaaayyyy faster than putting it after.

              • I realized that I need to add a "--max-size" option, since we have some devices that can't open files larger than 4GB, so I want to be able to say "6 hours or 4GB, whichever comes first." You can't just truncate MP4 files. But, maybe I worked around it? I'm writing to a TS file; noting when it passes the max size; aborting the encoding; truncating the file; and then doing "--codec copy" to an MP4 file, and that seems to work so far. Maybe.

            • jwz says:

              Well, I spoke too soon. open3: exec: Argument list too long

              Is there some way to provide the -filter_complex arguments from a file?

              • Carlos says:

                -filter_complex_script filename

              • Andrew says:

                I’m glad this is working for you.

                -filter_complex_script will read from a file.

                And yes, testing by trying to extract a 1-second clip 45 minutes into a longer video shows that this is doing it the slow way.

                Try

                movie=x1.mkv:seek_point=2700,trim=duration=1s,setpts=PTS-STARTPTS

                and
                amovie=x1.mkv:seek_point=2700,atrim=duration=1s,asetpts=PTS-STARTPTS

                instead. I hope that doesn’t break playback.

                Also I haven’t tried it but there is a -fs option:

                Set the file size limit, expressed in bytes. No further chunk of
                bytes is written after the limit is exceeded. The size of the
                output file is slightly more than the requested file size.

                which presumably stops writing frames when it hits the limit, but still has to write container trailers, so maybe try 3.9GiB ?

                • jwz says:

                  And now we move on to... "Error initializing complex filters. Too many open files"

                  • Andrew says:

                    Sounds like a ulimit issue. Are you on a mac? The default on recent versions is only 256. Try ulimit -n 10240 first.

                  • jwz says:

                    It needs more than that, and:
                    sh-3.2$ ulimit -n 12000
                    sh-3.2$ ulimit -n 12001
                    sh: ulimit: open files: cannot modify limit: Operation not permitted

                    It's absurd that ffmpeg is holding all the files open. It cannot possibly need more than 6 of those file handles at once.

                  • Andrew says:

                    Looks like the hard limit on my laptop is 49152, you can temporarily get that in a non-root process with sudo sh -c "ulimit -n 49152 && exec su $LOGNAME -c 'ffmpeg --help'".

                    It’s possible that it’s keeping all the files open because at any point you could have [a]sendcmd filters send seek commands to the movie sources to rewind and reuse them. When you open the files at the start, ffmpeg has no idea what you’re going to do with them. Maybe you’re going to start with just one video playing, blend it with a second after one second, and keep adding one video per second to see how many music videos need to be playing all at once before everything’s completely unrecognizable. Related: https://www.youtube.com/watch?v=icruGcSsPp0

        • Owen W says:

          the more I learn more about lossy video encoding the sadder I get

Leave a Reply

Your email address will not be published. But if you provide a fake email address, I will likely assume that you are a troll, and not publish your comment.

You may use these HTML tags and attributes: <a href="" title=""> <b> <blockquote cite=""> <code> <em> <i> <s> <strike> <strong> <img src="" width="" height="" style=""> <iframe src="" class=""> <video src="" class="" controls="" loop="" muted="" autoplay="" playsinline=""> <div class=""> <blink> <tt> <u>, or *italics*.

  • Previously