为什么我不能用 ffmpeg 重新排序我的 mpg 容器中的流?

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

背景: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?
我有一个 mpg 文件,音频为流 0,视频为流 1。
在OSX QT Player下可以正常播放,但在Win10的默认应用下就不行了。
由于没有更好的主意,我假设不正常的流排序是我的问题,我正在尝试使用 ffmpeg 修复它。 真幸运! https://trac.ffmpeg.org/wiki/Map 完全符合我的情况!

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.

我完全使用那个命令,但是翻转似乎不起作用,像这样:

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

什么,什么?! 命令输出看起来完全符合我的要求,但生成的文件与原始文件具有相同的流顺序。我错过了什么?
一个可能的线索:看起来音频流在视频流之前开始。我在音频流中看到的最小 pkt_pts_time 是 00:00:00.500000,而我在视频流中看到的最小 0:00:01.912967。 这有关系吗?

起初看起来很棘手。我想知道 this old FFmpeg trac ticket 是否可以保存密钥:

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

但这实际上不是问题;但是值得注意的是,当您应该尝试输出 MP4 或 MKV 时,您的文件具有 .mpg 扩展名。 ".mpg" is only valid if it contains legacy MPEG-1 and MPEG-2 formats。 H.264 或 AAC 基本流无效。

如果您没有自己创建此文件,则它可能是容器标签错误(例如 MKV 或 MP4),或者有人奇怪地将流多路复用为 .mpg。请注意 FFmpeg 如何在您的流重新排序尝试期间警告您不兼容的编解码器。

MPEG-PS 是一种打包格式,因此没有基本流本身。如果它确实是 MPEG-PS 文件,则可能是音频样本首先出现。无论哪种方式,您都应该放弃使用 .mpg 作为您的格式。

查看此答案的末尾,了解如何使用 FFprobe 相当准确地识别实际容器格式。

我又想了想,最后一个神经元提醒我 -map 输出是如何遵循赋值顺序的。

需要注意的重要一点是,-map 0:v -map 0:a 对于包含多个流类型的文件并不像您期望的那样有效,因为该语法匹配该类型的所有适用流。


Gyan 已阐明,如果您的文件恰好包含一个视频流和一个音频流,-map 0:v -map 0:a 的功能等同于 -map 0:1 -map 0:0

如果你想使用 0:a 语法,如果你有多个音频,你必须单独处理它们,否则 FFmpeg 会在重新排序时将它们分组。 -map 0:a 将移动两个音频; -map 0:a:0 将只移动第一个音频。

另一种方法是,记住始终检查您操作的每个文件中的流顺序,即按照您希望它们在输出中的顺序指定绝对流编号。因此,-map 0:1 -map 0:0 如果您的视频是源文件中两个流中的第二个。

对于一个视频和一个音频流的文件,您可以使用任一方法。


测试

我创建了一个 .MP4 文件,其中包含一个 H.264 视频流 0:0 和一个 MP3 音频流 0:1。

原文件:

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]

然后反馈给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)

结果:交换流; MP3 音频流为 0:0,H.264 视频流为 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]

这似乎可以完成您想要的:-)

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

提醒:

  • 对于一个视频和一个音频流的文件,您可以使用上述任一方法。
  • 对于包含多个音频或视频的文件,确定流索引并使用更精细的 0:v:00:a:0 语法指定。

ffmpeg -i INPUTFILE 显示文件的数字流 ID,这对于奇数文件可能更快。但是,数字引用在重新排序时会跳过仅指定音频、视频、字幕等的健全性检查。

FFmpeg's Stream Specifiers and Advanced Options 文档值得收藏。

延伸阅读:

(非常感谢 Gyan 的观察力!)


使用FFprobe识别容器

FFprobe 可以 best-effort 检测真实的容器格式,忽略扩展名。这是我的演示示例“swapped.mp4”,重命名为 .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]

注意major_brand=isomISO Base Media file format),format_nameformat_long_name等等。

我上个月制作的 true MPEG-2 视频(DVD 翻录)输出:

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 正确地将 format_name 报告为 mpeg,将 format_long_name 报告为 MPEG-PS

Chris 说的都对,帮了大忙。带有H264视频的原始.MPG来自TivoDecoder;我从来没有想过质疑那个容器是否对那个视频格式有效,当 QT 愿意播放它时,我认为 ffprobe 警告不是什么大问题。 :)

重新混合到 MP4 容器给了我一些东西 Windows 可以播放。

ffmpeg -hide_banner -ss 00:17:24 -i myfile.mpg -t 00:02:40 -c copy myfile.trimmed.mp4
[h264 @ 000002d5d73f4040] Increasing reorder buffer to 2
[mpegts @ 000002d5d73edbc0] PES packet size mismatch
[mpegts @ 000002d5d73edbc0] Packet corrupt (stream = 1, dts = 8467425232).
[mpegts @ 000002d5d73edbc0] Could not find codec parameters for stream 2 (Unknown: none ([151][0][0][0] / 0x0097)): unknown codec
Consider increasing the value for the 'analyzeduration' and 'probesize' options
[mpegts @ 000002d5d73edbc0] PES packet size mismatch
[mpegts @ 000002d5d73edbc0] Packet corrupt (stream = 1, dts = 8467425232).
Input #0, mpegts, from 'myfile.mpg':
  Duration: 00:30:00.63, start: 92282.982578, bitrate: 6249 kb/s
  Program 1
    Stream #0:0[0x1aab]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(tv, progressive), 1280x720 [SAR 1:1 DAR 16:9], Closed Captions, 59.94 fps, 59.94 tbr, 90k tbn, 119.88 tbc
    Stream #0:1[0x1abf]: Audio: ac3 ([129][0][0][0] / 0x0081), 48000 Hz, 5.1(side), fltp, 384 kb/s
    Stream #0:2[0x1ac1]: Unknown: none ([151][0][0][0] / 0x0097)
[mp4 @ 000002d5d7e90540] track 1: codec frame size is not set
Output #0, mp4, to 'myfile.trimmed.mp4':
  Metadata:
    encoder         : Lavf58.45.100
    Stream #0:0: Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, progressive), 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 59.94 fps, 59.94 tbr, 90k tbn, 90k tbc
    Stream #0:1: Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 384 kb/s
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
  Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
frame= 9570 fps=0.0 q=-1.0 Lsize=  122587kB time=00:02:39.99 bitrate=6276.6kbits/s speed= 462x
video:114772kB audio:7545kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.220873%
ffprobe -hide_banner -show_error -show_format -i myfile.trimmed.mp4
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'myfile.trimmed.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.45.100
  Duration: 00:02:40.96, start: 0.000000, bitrate: 6239 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv), 1280x720 [SAR 1:1 DAR 16:9], Closed Captions, 5890 kb/s, 59.94 fps, 59.94 tbr, 90k tbn, 119.88 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 384 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
    Side data:
      audio service type: main
[FORMAT]
filename=myfile.trimmed.mp4
nb_streams=2
nb_programs=0
format_name=mov,mp4,m4a,3gp,3g2,mj2
format_long_name=QuickTime / MOV
start_time=0.000000
duration=160.960000
size=125529106
bit_rate=6239021
probe_score=100
TAG:major_brand=isom
TAG:minor_version=512
TAG:compatible_brands=isomiso2avc1mp41
TAG:encoder=Lavf58.45.100
[/FORMAT]

我遇到了视频和音频严重不同步的新问题,但我会把它作为一个单独的问题来问。

这非常棘手,但可以使用 ffmpeg 工具重新排序音频流。 如果您知道源流的编号,或者它们的 ID,只需按您想要的顺序映射它们: ffmpeg -i "..." -map 0:a:0,0:1 -map 0:a:1,0:1 ... 或使用 -map i:0xbbbb -map i:0xaaaa 。 .. 即 -map 命令允许 select 音频流的新顺序。

更棘手的是,如果您想根据语言对它们重新排序:假设您的源包含英语、法语和西班牙语(以及可能的其他)音轨,并且您想将它们排序为法语、英语、西班牙语、以及以下任何其他语言。因此,基于 -map 命令,您可以执行以下操作:ffmpeg -i "..." -map 0:V,0:0 -map 0:m:language:fre? 0:m:language:eng? 0:m:language:spa? -地图 0:a? -地图-0:s? ....参数的一些解释:首先将真实视频流(大写V)映射为目标流0(您可以更改它),然后使用元数据的语言法语搜索流,如果存在(末尾的问号)放作为第一个音频流,对其他请求的语言重复相同的操作,但在完成映射任何其他流之前,以防没有任何您请求的语言或不会丢失一些源音频流,最后(在某些情况下非常重要例)删除字幕映射,因为字幕的元数据与音频流的元数据相同,如果有法语字幕流但没有法语音频流,结果将是字幕流映射但没有音频流 - 这取决于您的要求当然。

不要忘记使用 -map 命令会阻止自动将所有流映射到目标,这就是为什么将“-map 0:v”放在命令中的原因,数据流和下载流也是如此也。任何音频流末尾的问号 selection 允许缺少确切的音频流,因此 ffmpeg 命令不会因错误而停止。

注意:映射命令可能会在输出中复制您的音频流...