添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
玩足球的包子  ·  How to trim a video ...·  1 周前    · 
幸福的书签  ·  How to Rotate Videos ...·  1 周前    · 
讲道义的水桶  ·  How to remove ...·  1 周前    · 
礼貌的机器人  ·  FFmpeg: ...·  1 周前    · 
发怒的花卷  ·  Kindle ...·  2 月前    · 
犯傻的铅笔  ·  Lambda-11 - Dustloop Wiki·  4 月前    · 
含蓄的眼镜  ·  scroll · Xiaomi Vela ...·  5 月前    · 
[mov,mp4,m4a,3gp,3g2,mj2 @ 000001f4670fe840] Processing st: 0, edit list 0 - media time: -1, duration: 39960
[mov,mp4,m4a,3gp,3g2,mj2 @ 000001f4670fe840] Processing st: 0, edit list 1 - media time: 3003, duration: 2345400
[mov,mp4,m4a,3gp,3g2,mj2 @ 000001f4670fe840] Processing st: 1, edit list 0 - media time: 88160, duration: 1270704

不过,大多数播放器对 edit list 都没有很好的支持就是了。这个视频用本地播放器播放,虽然音画都同步,行为也不太一样:

关于 ffmpeg 两种 seeking 如果不了解可以先看看 官方 wiki 补课(有些内容稍显过时,问题不大)。简单来说,如果用 input seeking (也就是把相关参数填写在 -i 前面),会在读取该 input 的时候直接 seek 到这个位置,不会解码前面的内容,速度基本是瞬时的;如果用 output seeking (把相关参数填写到 -i 后面),则需要解码整个视频至此处,需要很长的时间,而且对于我这种前面有11小时内容的更是不现实。

注意这里解释一下我在网上经常看到有人误解的地方:对于 正常的视频 ,无论用哪个 seeking,出来的结果都是 精确的 :并不是说只有 output seeking 才能精确到毫秒级时间戳。当你 transode 的时候,即使你用的是 input seeking,而且切割点不在关键帧上,ffmpeg也会自动先 seek 到上一个关键帧然后解码到你需要的帧处(这个行为由 -accurate_seek 这个来控制,默认是 开启 的,可以用 -noaccurate_seek 关闭),再进行 transcode。当然,如果是 stream copy (本文的议题),则就只能 seek 到最近的关键帧了,这个无论是用 input seeking 还是 output seeking 都是一样的, -accurate_seek 这个开关对于 stream copy 也是完全没有任何效果的。

所以我用 input seeking 来 -ss 的原因也很好理解了。至于为什么 -t 反而要用 output seeking,则是为了规避 ffmpeg 当年 seek MEPG-TS 格式的一个bug:当年如果在 input 同时用 -ss 和 -to/-t,会出现并不会在指定的 duration 或者结束时间戳结束的问题。不过 这个 bug 在我汇报后 过了几个月 已经修复了 ,其实现在已经没有必要再这样,不过既然没有副作用就先不改了(另外注意,不要 -ss input seeking 但是 -to output seeking。当视频读取到 output 侧后,其时间戳会被重置,所以你的 -to 是从 ss 处重新开始计算的而不是原始视频的时间戳。对 -t 则没有区别)。

另外对于一般视频,其音频部分的 packets 可以理解为每个都是“关键帧”,也就是任意可分的。只有视频会有 GOP 的概念只能切割到关键帧,不能在任意 packet 处无损切割。再加上一般音频的包本来就比视频包短(本文中此视频音频包长度只有20ms),基本可以认为能任意位置无损切割了。

出问题的 input 简介

让我们回到正题。这个问题一言以蔽之,主要产生于 MPEG-TS 这个格式的问题上,尤其是 m3u8 直播时产出的 segment 的文件里。其实这个问题只需要取此次直播的任意一个 segment 即可实现,所以问题和我们后期 binary 合并过多个 segment 没有关系。我们以 index_4_6992.ts (下文重命名为 raw.ts)为例,其长度是6s。我们先按顺序罗列下他的所有 packets:

Stream video start time: 4.033333 Stream audio start time: 1.4 Earliest video packet pts time: 4.033333 Earliest audio packet pts time: 1.4 Stream video start time: 2.633 Stream audio start time: 0.0 Earliest video packet pts time: 2.633 Earliest audio packet pts time: 0.0
ffmpeg -ss 00:00:03 -i raw.ts -c:v libx264 -c:a aac input_seeking_encode.ts -y
ffmpeg -ss 00:00:03 -i raw.ts -c:v libx264 -c:a aac input_seeking_encode_tomp4.mp4 -y
ffmpeg -ss 00:00:03 -i raw.ts -c:v libx264 -c:a aac input_seeking_encode_tomkv.mkv -y
>v start input_seeking_encode.ts
Format start time: 1.4
Stream video start time: 2.422333
Stream audio start time: 1.4
Earliest video packet pts time: 2.422333
Earliest audio packet pts time: 1.4
>v start input_seeking_encode_tomp4.mp4
Format start time: 0.0
Stream video start time: 0.0
Stream audio start time: 0.0
Earliest video packet pts time: 0.0
Earliest audio packet pts time: -0.021333
>v start input_seeking_encode_tomkv.mkv 
Format start time: -0.021
Stream video start time: 1.001
Stream audio start time: -0.021
Earliest video packet pts time: 1.001
Earliest audio packet pts time: -0.021

可以看到,现在变成这样:

Stream video start time: 1.001 Stream audio start time: 0.0 Earliest video packet pts time: 1.001 Earliest audio packet pts time: -0.021333

这样子了。

ffmpeg -i raw.ts -ss 00:00:03 -c copy output_seeking_copy.ts -y
ffmpeg -i raw.ts -ss 00:00:03 -c copy output_seeking_copy_tomp4.mp4 -y
ffmpeg -i raw.ts -ss 00:00:03 -c copy output_seeking_copy_tomkv.mkv -y
output_seeking_copy.ts
Format start time: 1.413333
Stream video start time: 2.404
Stream audio start time: 1.413333
Earliest video packet pts time: 2.404
Earliest audio packet pts time: 1.413333
output_seeking_copy_tomp4.mp4
Format start time: 0.013
Stream video start time: 1.004
Stream audio start time: 0.013
Earliest video packet pts time: 1.004
Earliest audio packet pts time: 0.013
start output_seeking_copy_tomkv.mkv
Format start time: 0.013
Stream video start time: 1.004
Stream audio start time: 0.013
Earliest video packet pts time: 1.004
Earliest audio packet pts time: 0.013

视频和音频起点不一致的问题依然存在,但是现在所有格式都会是固定的从原视频 audio 3秒处、视频4秒处(第三个GOP处)开始。这里对于MP4,即使不加 -avoid_negative_ts make_non_negative 也不会出现负的PTS了(output seeking 原理所致,TS 是重新计算的),所以 Chromium 也可以正确播放。

ffmpeg -i raw.ts -ss 00:00:03 -c:v libx264 -c:a aac output_seeking_encode.ts -y
ffmpeg -i raw.ts -ss 00:00:03 -c:v libx264 -c:a aac output_seeking_encode_tomp4.mp4 -y
ffmpeg -i raw.ts -ss 00:00:03 -c:v libx264 -c:a aac output_seeking_encode_tomkv.mkv -y
>v start output_seeking_encode.ts
Format start time: 1.4454
Stream video start time: 1.466733
Stream audio start time: 1.4454
Earliest video packet pts time: 1.466733
Earliest audio packet pts time: 1.4454
>v start output_seeking_encode_tomp4.mp4
Format start time: 0.0
Stream video start time: 0.0
Stream audio start time: 0.0
Earliest video packet pts time: 0.0
Earliest audio packet pts time: -0.021333
>v start output_seeking_encode_tomkv.mkv
Format start time: -0.021
Stream video start time: 0.0
Stream audio start time: -0.021
Earliest video packet pts time: 0.0
Earliest audio packet pts time: -0.021

这些视频不但时间戳都正常,实际观看也可以确认,确实是视频音频同时开始,没有重复帧等问题。

Workaround

这里先强调一下,上面切出来的这些“有问题”的文件一个是 AV 开始点不同的问题,一个是MP4容器特有的 edit list 导致部分播放器无法正常播放的问题。第二个问题如上所述可以通过切成别的格式、加 avoid_negative_ts 解决,甚至你切出来的MP4再重新封装一次也行( -ignore_editlist 1 加到 input option);但是第一个问题则是实打实的缺少那些 packets,是切了之后就救不回来的。

这个问题最简单或者说唯一的解决办法其实就是重新 remux 一下原视频,无论是用 ffmpeg 还是 mkvmerge,无论是 remux 成 mkv 还是 mp4(可别再 remux 成ts),都会重新生成 PTS/DTS 且重新对 packets 进行排序,从而会出来一个你随便切也不会切出问题的 input。例如我们简单地用 ffmpeg -i raw.ts -c copy raw.mp4 ,再去 inspect 这个 raw.mp4:

” 对于 mkv 格式,偶尔会出现相邻的两个 packet 没有完全连续,而是有 1~2个 time base 的间隔现象 ”

这个会不会是因为 timebase 精度太低呢,注意到 mkv 的 tbn 是 1k, ts 和 mp4 都是 90k.

显然 29.97 avgfps 更容易整除 90k tbn 而不是 1k tbn…

来自 libavformat/matroskaenc.c:3430
// ms precision is the de-facto standard timescale for mkv files
avpriv_set_pts_info(st, 64, 1, 1000);

看来这个 timescale 精度是 hard-wired 进去的了

越查越停不下来…

在 libavformat/mov.c: 第3919行:
// Audio decoders like AAC need need a decoder delay samples previous to the current sample,
// to correctly decode this frame. Hence for audio we seek to a frame 1 sec. before the
// edit_list_media_time to cover the decoder delay.

也许这就是为什么audio总会往前多切一秒?investigating…

うしろゆびさされ組 麻倉もも