前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >新手学习FFmpeg - 调用API调整视频局部速率

新手学习FFmpeg - 调用API调整视频局部速率

作者头像
随机来个数
发布2019-09-19 11:24:27
1.7K0
发布2019-09-19 11:24:27
举报
文章被收录于专栏:写代码的海盗

通过修改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的效果。 典型的用法如下:

代码语言:javascript
复制
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的视频,帧分布如下:

代码语言:javascript
复制
        +------------------------------------------------------------------+
        |       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秒时渲染的帧)如下:

代码语言:javascript
复制
        +------------------------------------------------------------------+
        |       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。 也就是正确的帧分布应该是下面的样子:

代码语言:javascript
复制
        +------------------------------------------------------------------+
        |       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的代码是下面部分:

代码语言:javascript
复制
d = av_expr_eval(setpts->expr, setpts->var_values, NULL);
frame->pts = D2TS(d);

d是根据规则(0.5*PTS)计算出来的pts值. 然后将新的PTS赋值给当前帧,而后继续后面的编码处理。

所以在这里,我们做一些判断,为了简化其它无关步骤,先假设只修改前5秒的视频,所以需要先判断当前帧是否需要修改:

代码语言:javascript
复制
(frame->pts * av_q2d(inlink->time_base)) < 5.0

通过pts*time_base可以计算出当前时间点,通过这个判断,可以得出是否需要修改此帧的PTS值。 如果需要修改,那么仍然通过frame->pts = D2TS(d)来调整。 而处理不需要修改的帧才是重点,

按照上图所示意的PTS,F5应该继承F4调整前的PTS值,所以需要在调整F4之前需要保存旧的PTS。所以是下面的伪代码:

代码语言:javascript
复制
    保存Old PTS
    if (当前时间 < 0.5) {
        计算新的PTS并赋值给当前帧
    }else{
        当前帧使用上个帧的PTS
    }

将伪代码实现后如下:

代码语言:javascript
复制
    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中的代码。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-09-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 加速/降速的原理
  • 局部调整
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档