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

问题描述

背景: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 正好描述了我的情况!

重新排序流

-map 选项的顺序决定了输出中流的顺序。在这个例子中,输入文件将音频作为流#0 和视频作为流#1(这可能但不寻常)。重新定位视频的示例,使其首先列出,然后是音频:

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

此示例流使用 -c copy 复制(重新复用)以避免重新编码。

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

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,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,to 'myfile.trimmed.flipped.mpg':
  Metadata:
    encoder         : Lavf58.45.100
    Stream #0:0: Video: h264 (High),q=2-31,59.94 tbc
    Stream #0:1: Audio: ac3,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,from 'myfile.trimmed.flipped.mpg':
  Duration: 00:02:41.09,119.88 tbc

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

解决方法

一开始看起来很棘手。我想知道 this old FFmpeg trac ticket 是否可能持有钥匙:

mpeg-ps 中没有流顺序。 您从 ffmpeg 输出顺序中看到的可能只是音频或视频数据包先出现

但这实际上不是问题;但是值得注意的是,当您应该尝试输出 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
(在这种情况下相当于 -map 0:a -map 0:v

结果:交换流; MP3 音频流为 0:0,H.264 视频流为 0:1

Input #0,from 'C:\temp\swapped.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.78.100
  Duration: 00:05:00.30,bitrate: 422 kb/s
  Stream #0:0(und): Audio: mp3 (mp4a / 0x6134706D),180 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
      vendor_id       : [0][0][0][0]
  Stream #0:1(und): Video: h264 (Main) (avc1 / 0x31637661),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
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 SpecifiersAdvanced Options 文档值得收藏。

进一步阅读:

(非常感谢 Gyan 如此细心!)


使用FFprobe识别容器

FFprobe 可以尽力检测真实的容器格式,忽略扩展名。这是我的演示“swapped.mp4”的示例,重命名为 .mpg:

ffprobe -hide_banner -show_error -show_format -i "C:\temp\swapped.mpg"
Input #0,from 'C:\temp\swapped.mpg':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.78.100
  Duration: 00:05:00.30,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,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=isom (ISO Base Media file format)、format_nameformat_long_name 等。

真实我上个月制作的 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),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,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

,

克里斯说的一切都是对的,而且帮助很大。原始的 .MPG 内含 H264 视频来自 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),progressive),1280x720 [SAR 1:1 DAR 16:9],Closed Captions,59.94 fps,59.94 tbr,119.88 tbc
    Stream #0:1[0x1abf]: Audio: ac3 ([129][0][0][0] / 0x0081),5.1(side),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,to 'myfile.trimmed.mp4':
  Metadata:
    encoder         : Lavf58.45.100
    Stream #0:0: Video: h264 (High) (avc1 / 0x31637661),q=2-31,90k tbc
    Stream #0:1: Audio: ac3 (ac-3 / 0x332D6361),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,from 'myfile.trimmed.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.45.100
  Duration: 00:02:40.96,bitrate: 6239 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661),yuv420p(tv),5890 kb/s,119.88 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: ac3 (ac-3 / 0x332D6361),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,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 命令允许选择音频流的新顺序。

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

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

注意:可以映射命令来复制输出中的音频流...