1、主动丢帧的应用场景
视频丢帧有被动的情况,例如数据丢失损坏导致的丢帧;也有主动的丢帧,例如:
- 高倍速播放(4倍速、8倍速等),解码全部视频帧可能解码速度跟不上,丢弃一部分也不影响播放渲染效果
- 按一定的间隔截图
- 服务器发送码流出现网络拥塞严重的情况,需要主动丢帧降低拥塞
丢帧可以是解码前,也可以是解码后。视频解码算力开销大,与解码后丢帧相比,能在解码前丢帧可以降低算力开销。
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 丢弃非关键帧
这里值得一提的有两点:
- B帧可以是参考帧,所以FFmpeg区分定义了非参考帧和B帧
- I帧不等于关键帧(IDR帧),所以FFmpeg区分定义了non intra和non key
libavformat demux有主动丢帧能力,但是功能十分有限:
- 只有部分封装格式实现了,其他封装格式忽略discard配置
- 虽然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:为更多编码格式添加实现。
作者:quink
来源:Fun With FFmpeg
原文:https://mp.weixin.qq.com/s/lZ1jzDy2FjMvn8HB8JngQA
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。