通过修改 setpts 代码实现调整视频部分的播放速率. 完整代码可参考: https://andy-zhangtao.github.io/ffmpeg-examples/
在前面提到了 PTS/DTS/Timestamp 的关系, 播放器在渲染视频时就是根据 PTS 来确定渲染和展示时间点的. 根据这个原理, 我们就可以通过调整帧的 PTS 时间来实现视频加速 / 降速播放.
加速 / 降速的原理
我们都知道, 当帧速率 (frame rate) 大于 24 时, 也就是 1 秒播放 24 帧时, 我们的视觉就会看到流程的视频. 在帧总量不变的情况下, 如果将 1/24 变为 1/48, 那么在相同时间内多播放了一倍的帧, 对于我们的视觉来说, 就感觉播放速度加快了(因为本该 20 秒才能播放完的帧, 在 10 秒内就播放完了, 就相当加速了一倍). 同理, 如果将 1/24 调整为 1/12, 就会看到慢动作.
FFMPEG 提供了 setpts 滤镜可以实现调整 pts 的效果. 典型的用法如下:
FFMPEG -i ~/tmp/trailer.mp4 -filter:v "setpts=0.5*PTS" output.mp4
0.5*PTS 表示将帧的 PTS 值乘以 0.5 后作为新的 PTS 值. 比如说: 帧 A 当前的 PTS 是 4000(根据以前的知识, 根据 PTS 和 Time_base 可以计算出渲染的时间点). 假设对应的时间点是: 00:00:05, 现在将 PTS 调整为 0.5*PTS 就变成了 2000, 那么对应的渲染时间点就变成了: 00:00:02.5. 这样就实现了加速播放.
同理, 如果是 2*PTS 就是降速播放.
局部调整
setpts 只能实现全部加速或者全部减速. 因为在其内部实现中, 对每个帧都应用相同的计算规则, 所以要么都调整要么都不调整. 如果要实现局部调整, 按照通用的解决方案, 只能先切割视频, 然后对单独视频进行加速 / 降速处理, 然后再将视频连接起来.
但如果我们适当调整 PTS 值, 也可以实现部分调整的效果.
问题分析
假设存在一段 30s 的视频, 帧分布如下:
- +------------------------------------------------------------------+
- | F1 F2 F3 F4 F5 F6 F7 |
- | |--------------|--------------|--------------|---> |
- |Time 0 10 20 30 |
- |PTS 0 100 200 250 300 350 400 |
- +------------------------------------------------------------------+
F1 - F7 表示 7 个 I 帧 (30 秒包含的帧比这个多多了, 这里是为了方便描述问题). 假设我们需要加速前 15 秒(后 15 秒播放速率不变) 的视频, 那么需要调整 F1 到 F4(F4 是第 15 秒时渲染的帧)如下:
- +------------------------------------------------------------------+
- | F1 F2 F3 F4 F5 F6 F7 |
- | |--------------|--------------|--------------|---> |
- |Time 0 10 20 30 |
- |PTS 0 100 200 250 300 350 400 |
- +------------------------------------------------------------------+
这样调整看似没问题, 但仔细分析会发现在 10s-20s 之间会出现天窗, 这是因为这段时间内的 PTS 没有任何帧需要渲染, 直到第 20 秒的时候, 才会开始继续渲染 F5 帧. 显然这样不满足实际应用需求.
而发生问题的关键在于将 F2-F4 调整 PTS 之后, 也需要实时调整 F5-F7 的 PTS. 也就是正确的帧分布应该是下面的样子:
- +------------------------------------------------------------------+
- | F1 F2 F3 F4 F5 F6 F7 |
- | |--------------|--------------|--------------|---> |
- |Time 0 10 20 30 |
- |PTS 0 100 200 250 300 350 400 |
- +------------------------------------------------------------------+
F1-F4 以一个速率播放, 而 F5-F7 以另外一个速率播放. 这样就实现了部分加速的效果.
代码实现
为了简化编码难度, 我们以 setpts 的代码为基础进行修改. 在 setpts 代码中修改 pts 的代码是下面部分:
- d = av_expr_eval(setpts->expr, setpts->var_values, NULL);
- frame->pts = D2TS(d);
d 是根据规则 (0.5*PTS) 计算出来的 pts 值. 然后将新的 PTS 赋值给当前帧, 而后继续后面的编码处理.
所以在这里, 我们做一些判断, 为了简化其它无关步骤, 先假设只修改前 5 秒的视频, 所以需要先判断当前帧是否需要修改:
(frame->pts * av_q2d(inlink->time_base)) <5.0
通过 pts*time_base 可以计算出当前时间点, 通过这个判断, 可以得出是否需要修改此帧的 PTS 值. 如果需要修改, 那么仍然通过 frame->pts = D2TS(d)来调整. 而处理不需要修改的帧才是重点,
按照上图所示意的 PTS,F5 应该继承 F4 调整前的 PTS 值, 所以需要在调整 F4 之前需要保存旧的 PTS. 所以是下面的伪代码:
保存 Old PTS
if (当前时间 <0.5) {
计算新的 PTS 并赋值给当前帧
}else{
当前帧使用上个帧的 PTS
}
将伪代码实现后如下:
- oldPts = frame->pts;
- if ((frame->pts * av_q2d(inlink->time_base)) <5.0) {
- frame->pts = D2TS(d);
- setpts->_pts = frame->pts;
- } else {
- frame->pts = setpts->_pts;
- }
- setpts->_pts++;
完整代码可参考 isetpts 中的代码.
来源: https://www.cnblogs.com/vikings-blog/p/11540425.html