经过修改setpts代码实现调整视频部分的播放速率。 完整代码可参考: https://andy-zhangtao.github.io/ffmpeg-examples/git
在前面提到了PTS/DTS/Timestamp的关系,播放器在渲染视频时就是根据PTS来肯定渲染和展现时间点的。 根据这个原理,咱们就能够经过调整帧的PTS时间来实现视频加速/降速播放。github
咱们都知道,当帧速率(frame rate)大于24时,也就是1秒播放24帧时,咱们的视觉就会看到流程的视频。 在帧总量不变的状况下,若是将1/24变为1/48,那么在相同时间内多播放了一倍的帧,对于咱们的视觉来讲,就感受播放速度加快了(由于本该20秒才能播放完的帧,在10秒内就播放完了,就至关加速了一倍)。同理,若是将1/24调整为1/12,就会看到慢动做。shell
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。这样就实现了加速播放。code
同理,若是是2*PTS
就是降速播放。视频
setpts
只能实现所有加速或者所有减速。 由于在其内部实现中,对每一个帧都应用相同的计算规则,因此要么都调整要么都不调整。若是要实现局部调整,按照通用的解决方案,只能先切割视频,而后对单独视频进行加速/降速处理,而后再将视频链接起来。继承
但若是咱们适当调整PTS值,也能够实现部分调整的效果。get
假设存在一段30s的视频,帧分布以下:it
+------------------------------------------------------------------+ | 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秒时渲染的帧)以下:io
+------------------------------------------------------------------+ | 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
中的代码。