Search code examples
audiovideoffmpeg

Why can't I reorder the streams in my mpg container with ffmpeg?


Background: Could having audio as stream 0 and video as stream 1 explain why my MPG will play on OSX QuickTime Player, but not Win10 Movies & TV?
I've got an mpg file with audio as stream 0 and video as stream 1.
It plays fine under OSX QT Player, but not under Win10's default app.
For lack of a better idea, I'm assuming the unusual stream ordering is my problem, and I'm trying to fix it with ffmpeg. What luck! https://trac.ffmpeg.org/wiki/Map describes exactly my case!

Re-order streams

The order of your -map options determines the order of the streams in the output. In this example the input file has audio as stream #0 and video as stream #1 (which is possible but unusual). Example to re-position video so it is listed first, followed by the audio:

ffmpeg -i input.mp4 -map 0:v -map 0:a -c copy output.mp4

This example stream copies (re-mux) with -c copy to avoid re-encoding.

I use exactly that command, but the flipping doesn't seem to work, like so:

ffprobe -hide_banner myfile.trimmed.mpg
[h264 @ 000001b965b569c0] Increasing reorder buffer to 2
Input #0, mpeg, from 'myfile.trimmed.mpg':
  Duration: 00:02:41.09, start: 0.500000, bitrate: 6255 kb/s
    Stream #0:0[0x80]: Audio: ac3, 48000 Hz, 5.1(side), fltp, 384 kb/s
    Stream #0:1[0x1e2]: Video: h264 (High), yuv420p(tv, progressive), 1280x720 [SAR 1:1 DAR 16:9], Closed Captions, 59.94 fps, 59.94 tbr, 90k tbn, 119.88 tbc
ffmpeg -hide_banner -i myfile.trimmed.mpg -map 0:v -map 0:a -c copy myfile.trimmed.flipped.mpg
[h264 @ 000001fa0ee94680] Increasing reorder buffer to 2
Input #0, mpeg, from 'myfile.trimmed.mpg':
  Duration: 00:02:41.09, start: 0.500000, bitrate: 6255 kb/s
    Stream #0:0[0x80]: Audio: ac3, 48000 Hz, 5.1(side), fltp, 384 kb/s
    Stream #0:1[0x1e2]: Video: h264 (High), yuv420p(tv, progressive), 1280x720 [SAR 1:1 DAR 16:9], Closed Captions, 59.94 fps, 59.94 tbr, 90k tbn, 119.88 tbc
[mpeg @ 000001fa0ee88dc0] VBV buffer size not set, using default size of 230KB
If you want the mpeg file to be compliant to some specification
Like DVD, VCD or others, make sure you set the correct buffer size
[mpeg @ 000001fa0ee88dc0] ac3 in MPEG-1 system streams is not widely supported, consider using the vob or the dvd muxer to force a MPEG-2 program stream.
Output #0, mpeg, to 'myfile.trimmed.flipped.mpg':
  Metadata:
    encoder         : Lavf58.45.100
    Stream #0:0: Video: h264 (High), yuv420p(tv, progressive), 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 59.94 fps, 59.94 tbr, 90k tbn, 59.94 tbc
    Stream #0:1: Audio: ac3, 48000 Hz, 5.1(side), fltp, 384 kb/s
Stream mapping:
  Stream #0:1 -> #0:0 (copy)
  Stream #0:0 -> #0:1 (copy)
Press [q] to stop, [?] for help
frame= 9570 fps=0.0 q=-1.0 Lsize=  123008kB time=00:02:40.95 bitrate=6260.6kbits/s speed= 518x
video:114772kB audio:7545kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.565047%
ffprobe -hide_banner myfile.trimmed.flipped.mpg
[h264 @ 0000021edcf36ac0] Increasing reorder buffer to 2
Input #0, mpeg, from 'myfile.trimmed.flipped.mpg':
  Duration: 00:02:41.09, start: 0.500000, bitrate: 6255 kb/s
    Stream #0:0[0x80]: Audio: ac3, 48000 Hz, 5.1(side), fltp, 384 kb/s
    Stream #0:1[0x1e2]: Video: h264 (High), yuv420p(tv, progressive), 1280x720 [SAR 1:1 DAR 16:9], Closed Captions, 59.94 fps, 59.94 tbr, 90k tbn, 119.88 tbc

What, what?! The command output looks like it did exactly what I asked, but the resulting file has the same stream ordering as the original file. What am I missing?
One possible clue: It looks like the audio stream starts before the video stream. The smallest pkt_pts_time I see in the audio stream is 00:00:00.500000, while the smallest I see in the video stream is 0:00:01.912967. Could that matter?


Solution

  • Tricky one this seemed at first. I wondered if this old FFmpeg trac ticket might hold the key:

    There is no stream order in mpeg-ps. what you see from ffmpeg output order is likely just if a audio or video packet comes first

    But that's not actually the problem; however it is worth noting that your file has a .mpg extension, when you should be trying to output an MP4 or MKV. ".mpg" is only valid if it contains legacy MPEG-1 and MPEG-2 formats. H.264 or AAC elementary streams are invalid.

    If you've not created this file yourself, it's either a mislabelled container (e.g. MKV or MP4), or someone has bizarrely muxed the streams to .mpg. Note how FFmpeg warns you of the incompatible codec during your stream reorder attempt.

    MPEG-PS is a packetised format, so there's no elementary streams as such. If it's genuinely an MPEG-PS file, it may be that an audio sample appears first. Either way, you should abandon using .mpg for your formats.

    See the end of this answer for how you can use FFprobe to fairly accurately identify the actual container format.

    I had another think, and finally a neuron reminded me about how the -map output follows the order of assignment.

    An important thing to note is that -map 0:v -map 0:a doesn't quite work how you might expect it with files containing more than one of a stream type, as that syntax matches all applicable streams of that type.


    Gyan has clarified that if you have a file with exactly one video and one audio stream, -map 0:v -map 0:a will function equivalently to -map 0:1 -map 0:0.

    If you want to use the 0:a syntax, if you have more than one audio for example you must address them individually, otherwise FFmpeg will group them when reordering. -map 0:a will move both audios; -map 0:a:0 will move just the first audio.

    The alternative, remembering to always check stream order in every file you manipulate, is to specify absolute stream numbers in the order you wish to have them in the output. So, -map 0:1 -map 0:0 if your video is the second of two streams in the source file.

    For files with one video and one audio stream, you can use either method.


    Tests

    I created an .MP4 file containing one H.264 video as stream 0:0 and one MP3 audio as stream 0:1.

    Original file:

    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'C:\temp\video-audio.mp4':
      Metadata:
        major_brand     : isom
        minor_version   : 512
        compatible_brands: isomiso2avc1mp41
        encoder         : Lavf58.78.100
      Duration: 00:05:00.30, start: 0.000000, bitrate: 422 kb/s
      Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080 [SAR 1:1 DAR 16:9], 421 kb/s, 23.98 fps, 23.98 tbr, 11988 tbn, 47.95 tbc (default)
        Metadata:
          handler_name    : GPAC ISO Video Handler
          vendor_id       : [0][0][0][0]
      Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 180 kb/s (default)
        Metadata:
          handler_name    : SoundHandler
          vendor_id       : [0][0][0][0]
    

    Then I fed it back to FFmpeg:

    ffmpeg -i C:\temp\video-audio.mp4 -map 0:1 -map 0:0 -c copy C:\temp\swapped.mp4
    (equivalent to -map 0:a -map 0:v in this case)

    Result: swapped streams; MP3 audio stream is 0:0, H.264 video stream is 0:1

    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'C:\temp\swapped.mp4':
      Metadata:
        major_brand     : isom
        minor_version   : 512
        compatible_brands: isomiso2avc1mp41
        encoder         : Lavf58.78.100
      Duration: 00:05:00.30, start: 0.000000, bitrate: 422 kb/s
      Stream #0:0(und): Audio: mp3 (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 180 kb/s (default)
        Metadata:
          handler_name    : SoundHandler
          vendor_id       : [0][0][0][0]
      Stream #0:1(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080 [SAR 1:1 DAR 16:9], 421 kb/s, 23.98 fps, 23.98 tbr, 11988 tbn, 47.95 tbc (default)
        Metadata:
          handler_name    : GPAC ISO Video Handler
          vendor_id       : [0][0][0][0]
    

    This appears to accomplish what you want :-)

    ffmpeg -i INPUTFILE -map 0:1 -map 0:0 -c copy OUTPUTFILE.mp4 or
    ffmpeg -i INPUTFILE -map 0:v -map 0:a -c copy OUTPUTFILE.mp4

    Reminder:

    • For files with one video and one audio stream, you can use either method above.
    • For files with multiple audios or videos, determine the stream index and specify using the more granular 0:v:0, 0:a:0 syntax.

    ffmpeg -i INPUTFILE shows a file's numeric stream IDs, and this may be quicker for the odd file. However, numeric references skips the sanity check of specifying only audio, video, subtitles etc. when reordering.

    FFmpeg's Stream Specifiers and Advanced Options documentation is worth bookmarking.

    Further reading:

    (big thanks to Gyan for being so observant!)


    Using FFprobe to identify container

    FFprobe can do a best-effort detection of the real container format, ignoring the extension. Here's an example of my demo "swapped.mp4", renamed to .mpg:

    ffprobe -hide_banner -show_error -show_format -i "C:\temp\swapped.mpg"
    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'C:\temp\swapped.mpg':
      Metadata:
        major_brand     : isom
        minor_version   : 512
        compatible_brands: isomiso2avc1mp41
        encoder         : Lavf58.78.100
      Duration: 00:05:00.30, start: 0.000000, bitrate: 422 kb/s
      Stream #0:0(und): Audio: mp3 (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 180 kb/s (default)
        Metadata:
          handler_name    : SoundHandler
          vendor_id       : [0][0][0][0]
      Stream #0:1(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080 [SAR 1:1 DAR 16:9], 421 kb/s, 23.98 fps, 23.98 tbr, 11988 tbn, 47.95 tbc (default)
        Metadata:
          handler_name    : GPAC ISO Video Handler
          vendor_id       : [0][0][0][0]
    [FORMAT]
    filename=C:\temp\swapped.mpg
    nb_streams=2
    nb_programs=0
    format_name=mov,mp4,m4a,3gp,3g2,mj2
    format_long_name=QuickTime / MOV
    start_time=0.000000
    duration=300.301000
    size=15847817
    bit_rate=422184
    probe_score=100
    TAG:major_brand=isom
    TAG:minor_version=512
    TAG:compatible_brands=isomiso2avc1mp41
    TAG:encoder=Lavf58.78.100
    [/FORMAT]
    

    Note major_brand=isom (ISO Base Media file format), format_name, format_long_name and so on.

    A true MPEG-2 video I produced last month (a DVD rip) outputs this:

    ffprobe -hide_banner -show_error -show_format -i "C:\temp\opera.mpg"
    Input #0, mpeg, from 'C:\temp\opera.mpg':
      Duration: 02:15:23.71, start: 66240.530111, bitrate: 4194 kb/s
      Stream #0:0[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, smpte170m, top first), 720x576 [SAR 64:45 DAR 16:9], 25 fps, 25 tbr, 90k tbn, 50 tbc
        Side data:
          cpb: bitrate max/min/avg: 7000000/0/0 buffer size: 1835008 vbv_delay: N/A
      Stream #0:1[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 192 kb/s
    [FORMAT]
    filename=C:\temp\opera.mpg
    nb_streams=2
    nb_programs=0
    format_name=mpeg
    format_long_name=MPEG-PS (MPEG-2 Program Stream)
    start_time=66240.530111
    duration=8123.712000
    size=4259503237
    bit_rate=4194637
    probe_score=26
    [/FORMAT]
    

    FFprobe correctly reports format_name as mpeg and format_long_name as MPEG-PS.