ffprobe - getting video info from file chunk

andrzej1_1 picture andrzej1_1 · Sep 6, 2015 · Viewed 10.8k times · Source

On my site users can upload videos, which are encrypted on the fly and stored on another server. I want to store video bitrate, frame rate etc., but I have not direct access to them and I can not just use the following command:

ffprobe -show_streams -i file.mp4

I tried saving last chunk on my server, which contain moov atom, but ffprobe is outputting:

Format mov,mp4,m4a,3gp,3g2,mj2 detected only with low score of 1, misdetection possible!
moov atom not found
C:\file.mp4: Invalid data found when processing input

I checked and truncating at least one byte cause this, although moov atom is intact.

What is the proper way to get video metadata from file fragment containg moov atom? What about getting informations from piece of mdata atom?

Answer

Ronald S. Bultje picture Ronald S. Bultje · Sep 12, 2015

I don't think the ffmpeg parser knows how to find the moov atom of chunked files. It parses (reading or skipping) mp4 files chunk by chunk until it finds the moov atom, and if you're cutting off a part of the beginning, the chunk structure is broken and thus it won't find the moov atom.

One possible solution for you is to detect files that have a moov atom at the end, and move the moov atom to the beginning by remuxing with ffmpeg with the -movflags +faststart (or comparable AVOptions in c/c++ code). Then you can truncate the file after the moov atom and parsing the header will still work.

[edit]

So, for the case of writing a truncated-fragment-aware modification to the mov demuxer (see comments), here's how you would go. First of all, try not to modify mov_read_default(), it's the central recursion engine, and any changes here will likely break most regular functionality. Rather, make changes to mov_read_header() (since you only care about header parsing here, not so much the demuxing of frames). You'll find this code:

if (mov->moov_retry)
    avio_seek(pb, 0, SEEK_SET);
if ((err = mov_read_default(mov, pb, atom)) < 0) {
    av_log(s, AV_LOG_ERROR, "error reading header\n");
    mov_read_close(s);
    return err;
}
} while (pb->seekable && !mov->found_moov && !mov->moov_retry++);
if (!mov->found_moov) {

This is trying to decode the header tree structure, where moov is the upper level atom. In a file, it looks for a sequence like this:

$ hexdump -n 32 -s 41934133 -C somefile.mov 
027fdd35  00 00 3e b4 6d 6f 6f 76  00 00 00 6c 6d 76 68 64  |..>.moov...lmvhd|
027fdd45  00 00 00 00 c9 6b 7b f5  c9 6b 7c 02 00 00 02 58  |.....k{..k|....X|

0x00003eb4 is the size in bytes of the 'moov' atom, and within that is a subatom called 'mvhd' of size 0x0000006c bytes (the tree structure goes on for a while after this). If you set the file pointer to this exact offset when demuxing the file, it will decode correctly:

$ tail -c +41934134 somefile.mov > /tmp/hdr.mov
$ ffprobe  /tmp/hdr.mov
[..]
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f871b002a00] stream 0, offset 0x3f3e: partial file
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f871b002a00] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none(bt709), 1280x720, 10695 kb/s): unspecified pixel format
[..]
    Stream #0:0(und): Video: h264 (avc1 / 0x31637661), none(bt709), 1280x720, 10695 kb/s, 29.97 fps, 29.97 tbr, 600 tbn, 1200 tbc (default)
[..]
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, mono, fltp, 63 kb/s (default)

How you get the file offset to that point is up to you:

  • you can add some code to mov_read_header() to scan the file for 'moov' (0x6d6f6f76) and set the file pointer 4 bytes before that
  • you can, in your code that creates this fragment, scan for moov and chop off the leading garbage before the moov atom before you save that fragment to a file

If you're going to change ffmpeg and put this in a ffmpeg version that you use for other functionality also, I'd encourage you to put this under some kind of option so it's not enabled for default file reading. Otherwise you run the risk that regular mov/mp4 file parsing will no longer work correctly.