FFmpeg中的主动丢帧功能

1、主动丢帧的应用场景

视频丢帧有被动的情况,例如数据丢失损坏导致的丢帧;也有主动的丢帧,例如:

  1. 高倍速播放(4倍速、8倍速等),解码全部视频帧可能解码速度跟不上,丢弃一部分也不影响播放渲染效果

  2. 按一定的间隔截图

  3. 服务器发送码流出现网络拥塞严重的情况,需要主动丢帧降低拥塞

丢帧可以是解码前,也可以是解码后。视频解码算力开销大,与解码后丢帧相比,能在解码前丢帧可以降低算力开销。

2、FFmpeg解封装丢帧

FFmpeg libavformat解封装可以配置主动丢帧,配置的地方在AVStream::discard

enum AVDiscard discard; ///< Selects which packets can be discarded at will and do not need to be demuxed.

enum AVDiscard的定义:

/**
 * @ingroup lavc_decoding
 */
enum AVDiscard{
    /* We leave some space between them for extensions (drop some
     * keyframes for intra-only or drop just some bidir frames). */
    AVDISCARD_NONE    =-16, ///< discard nothing
    AVDISCARD_DEFAULT =  0, ///< discard useless packets like 0 size packets in avi
    AVDISCARD_NONREF  =  8, ///< discard all non reference
    AVDISCARD_BIDIR   = 16, ///< discard all bidirectional frames
    AVDISCARD_NONINTRA= 24, ///< discard all non intra frames
    AVDISCARD_NONKEY  = 32, ///< discard all frames except keyframes
    AVDISCARD_ALL     = 48, ///< discard all
};
  • AVDISCARD_NONREF  丢弃非参考帧

  • AVDISCARD_NONREF  丢弃B帧

  • AVDISCARD_NONINTRA 丢弃非I帧

  • AVDISCARD_NONKEY  丢弃非关键帧

这里值得一提的有两点:

  1. B帧可以是参考帧,所以FFmpeg区分定义了非参考帧和B帧

  2. I帧不等于关键帧(IDR帧),所以FFmpeg区分定义了non intra和non key

libavformat demux有主动丢帧能力,但是功能十分有限:

  1. 只有部分封装格式实现了,其他封装格式忽略discard配置

  2. 虽然enum AVDiscard有多个等级区分,但是封装格式因解析层度过浅,往往只能区分关键帧和非关键帧,没法进行精细的丢帧控制

怎样实现更精细的丢帧逻辑控制呢?我们往下看。

3、FFmpeg解码器丢帧

FFmpeg的解码器支持丢帧配置。看AVCodecContext的三个字段:

/**


     * Skip loop filtering for selected frames.


     * - encoding: unused


     * - decoding: Set by user.


     */


    enum AVDiscard skip_loop_filter;






    /**


     * Skip IDCT/dequantization for selected frames.


     * - encoding: unused


     * - decoding: Set by user.


     */


    enum AVDiscard skip_idct;




    /**


     * Skip decoding for selected frames.


     * - encoding: unused


     * - decoding: Set by user.


     */


    enum AVDiscard skip_frame;
  • skip_loop_filter是跳过视频解码的一个环节:in-loop filter

  • skip_idct是跳过反离散余弦变换,一些老的编码格式有实现

  • skip_frame是按帧类型或者说帧的特性,来丢弃一些帧不解码

我们这里主要关注skip_frame。skip_frame是由解码器实现的,在解析到一定深度之后,根据skip_frame的配置,判断是否丢弃当前正在处理的视频帧,不做进一步的解码


越早做决策,越早放弃,节省的算力越多。所以实现方式上,当具备了所需的信息之后,解码器会尽早做丢帧处理,能解析完NALU header就丢帧的,那就不要再解析slice header了。

与libavformat demuxer丢帧相比,libavcodec decoder丢帧实现了精细控制。但是解码丢帧也有一个缺点:没法用在非解码的场景。例如服务器码流转发/分发遇到网络拥塞时,要按需丢弃视频数据包,解码丢帧无意义。

FFmpeg能否实现像demuxer一样不解码、像decoder一样精细控制丢帧逻辑呢?借助libavcodec cbs框架和bitstream filter的能力,我实现了这个功能。

4、FFmpeg bitstream filter 丢帧

libavcodec/cbs*(coded bitstream)是一套libavcodec内部使用的、多种编码格式通用的、码流解析修改合成工具,提供统一的接口,实现代码复用。一些新的parser是在cbs之上实现的,一些bsf(bitstream filter)也是借助cbs实现的。

cbs原来没有丢帧的功能,所以我们第一步是加上响应的API,见patch 1/6(https://patchwork.ffmpeg.org/project/ffmpeg/list/?series=8947&state=*)。


对于H.264、H.265来说,cbs能够解析到slice header,拥有足够的信息做丢弃判断。cbs H.264 H.265丢帧逻辑实现见patch 2/6,3/6。H.264、H.265判断IDR、I、B、非参考帧等的逻辑这里就不介绍了,感兴趣的可以看下代码。

cbs是FFmpeg内部的功能,不具有对外的API。怎样对外暴露新加的丢帧功能呢?我选择用filter_units bsf 实现,见patch 4/6。filter_units bsf原来是按照NALU的类型做丢帧的一个filter(比如你可以用它来过滤掉SEI),我新增的功能是按照enum AVDiscard来丢NALU。

patch 5/6是代码格式调整(FFmpeg因为用邮件做patch review,功能实现和代码格式调整往往要分开提交),patch 6/6是添加测试。

通过命令行的使用示例:

./ffmpeg -i /tmp/test.mp4 -c:v copy -bsf:v filter_units=discard=nonref /tmp/abc.mp4

输入的test.mp4的帧类型

GOP: IPPBPBPBPBPBPBPBPBPBPBPBPBPPBPBPBPBPBPBPBPBPBPBPBPPBBPBBPBPBPBPBPBPBPBPBPBPBPBPBBPBPBPBPBPBPBPBPBPBPBPPBPBPBPBPBPBPBPBPBPBPPPPPP 128 CLOSED
GOP: IBPPPBPBPPPPBPPBPPBPPPBPPPBPPPPPBBPBPBPPBPPBPBPBPBPBPPBPBPBBPBPBPBPBPBPBPBPBPBPBPBPBPBPBPBPBPBPPBPPBPPPPPP 106 CLOSED
GOP: IPPPPBPBPBPPBPBPP 17 CLOSED

输出的abc.mp4的帧类型

GOP: IPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP 68 CLOSED
GOP: IPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP 65 CLOSED
GOP: IPPPPPPPPPPP 12 CLOSED

在实现这个功能的时候,我特意考虑了根据网络情况动态调整丢帧的情况,所以允许filter_units的discard配置随时可变,不需要重复的创建和销毁filter_units的实例。当然,ffmpeg命令无法展示这个功能,只有通过bsf API调用才能实现动态配置。

此外还考虑了一些细节,比如只丢弃视频帧数据,保留其他的NALU,典型场景是保留SEI。

还有一个有趣的应用场景,当你使用第三方解码器,特别是硬件解码的时候,第三方解码器没实现丢帧能力,你可以用filter_units做主动丢帧。视频播放特别是倍速播放会很有用处。

TODO:为更多编码格式添加实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值