一、概述
早在2015年8月初,FFmpeg
就已经开始逐步支持 macOS 和 iOS 平台的 VideoToolBox
,2017年7月低,FFmpeg 在 example 中新增了一个 hw_decode.c 用于演示对文件的硬解码。以前使用过其软编码(基于 x264
)和软解码,也单独使用 VideoToolBox 进行过硬编码和硬解码。但是距离上次使用 FFmpeg 后其 API 发生了一些改变,VideoToolBox 也支持 HEVC
了,所以重新研究一次。
二、软件准备工作
1、编译 FFmpeg for iOS
对 FFmpeg for iOS 进行交叉编译不是本文重点,在此略去。
对 FFmpeg for iOS 进行编译时,默认是开启了 VideoToolBox 的。如果心里没底,可以先验证一次。
1 | const char *type_name = "videotoolbox"; |
2、安装 FFmpeg for macOS
FFmpeg for macOS 在本文主要用于准备相关视频文件。比如原始 mp4 文件过大需要对其裁剪、音视频分离等。
除了使用官方提供的安装包或自行编译外,也可使用 brew
进行安装,先看有哪些可用选项:
1 | brew options ffmpeg | grep -vE '\s' | tr '\n' ' ' |
如果使用尽可能完整的选项进行安装:
1 | brew install ffmpeg $(brew options ffmpeg | grep -vE '\s' | grep -- '--with-' | tr '\n' ' ') |
安装完成后,如果想回过头看看 brew 使用了哪些选项:
1 | brew info ffmpeg |
如果想看看编译时 configure 了哪些选项,直接执行 ffmpeg 命令即可:
1 | ffmpeg |
如果要看格式更友好的版本,可在执行 ffmpeg 时加上 -buildconf
参数:
1 | ffmpeg -buildconf |
三、视频文件准备工作
1、准备 mp4 文件
随意找一个 WWDC
的视频文件。使用 Mediainfo
查看其信息:
视频文件的音视频的编码分别是 AAC
(LC) 和 AVC
(Main@L3.1) 。
当然,也可以直接使用 ffprobe
命令查看信息:
1 | ffprobe -show_format 503_hd_introducing_heif_and_hevc.mp4 |
2、裁剪 mp4 文件
因为在 iOS 上测试,视频文件作为资源文件,希望小一点,从第10秒开始截取20秒,其余不变:
1 | ffmpeg -i 503_hd_introducing_heif_and_hevc.mp4 -ss 10 -t 20 -codec copy temp.mp4 |
3、裁剪不带 B 帧的 mp4 文件
为了简化,使用 x264 以 ultrafast
模式重编码以确保没有 B 帧。暂时不纠结 AVC profile
和 level
的问题:
1 | ffmpeg -v debug -i 503_hd_introducing_heif_and_hevc.mp4 -vcodec libx264 -preset ultrafast -ss 10 -t 20 temp.mp4 |
ultrafast 模式实际上其中一个参数指定了 B 帧数为 0(其他的如 superfast
不能保证不产生 B 帧),所以可以直接以 x264-params
选项指定::
1 | ffmpeg -v debug -i 503_hd_introducing_heif_and_hevc.mp4 -vcodec libx264 -x264-params bframes=0 -ss 10 -t 20 temp.mp4 |
通过 x264opts
也可以设置:
1 | ffmpeg -v debug -i 503_hd_introducing_heif_and_hevc.mp4 -vcodec libx264 -x264opts "bframes=0" -ss 10 -t 20 temp.mp4 |
我们知道, profile
baseline
不支持 B 帧的,直接设置 profile 也是可以的(可换作 h264_videotoolbox
编码器):
1 | ffmpeg -v debug -i 503_hd_introducing_heif_and_hevc.mp4 -vcodec h264_videotoolbox -profile:v baseline -ss 10 -t 20 temp.mp4 |
使用 FFmpeg 的
-bf
选项貌似参数无效,抽空继续尝试。
如何判断的确没有 B 帧了呢?可以使用 ffprobe 命令查看,如果视频流的 has_b_frames
值为 0 则表示没有 B 帧:
1 | ffprobe -show_streams temp.mp4 |
也可以采用最直接的、读取数据的方式进行验证。或者使用 FFmpeg 解码,然后通过 AVFrame
的 pict_type
字段判断,有个 av_get_picture_type_char
可以直接返回帧类型的字符表示:
1 | AVFrame *frame = ... |
3、提取 H.264 数据
然后再来一份 H.264 数据(-bsf:v h264_mp4toannexb 可省略,使用该参数与否结果都是 Annex B
格式):
1 | ffmpeg -i temp.mp4 -c:v copy -bsf:v h264_mp4toannexb -an temp.h264 |
使用 VLC
播放试试:
在使用
ffmpeg -v debug -i 503_hd_introducing_heif_and_hevc.mp4 -ss 10 -t 40 -codec copy temp.mp4
进行裁剪时,第一个Slice
值为 00000001 09…. ,是单元分割符(0x09 & 0001 1111 = 9),使用ffplay
播放 H.264 文件时提示 sps 和 pps 找不到的错误,但能继续播放;在使用ffmpeg -v debug -i 503_hd_introducing_heif_and_hevc.mp4 -vcodec libx264 -preset ultrafast -ss 10 -t 20 temp.mp4
进行裁剪时,第一个 Slice 值为 00000001 06….,是 SEI (0x06 & 0001 1111 = 6),使用ffplay
播放 H.264 文件正常。可使用16进制编辑器删除非必要数据再尝试播放。另外,想通过第5个字节来判断IPB帧也是不行的。
为了在之后的测试中,能够使用标准 I/O 函数而不是 av_read_frame
函数逐帧读取,比如模拟从 FFmpeg 外部获取数据,可以在准备数据时用 av_read_frame
函数读取帧后写入文件时,先写入帧长度再写入帧数据。
1 | FILE *frame_file = fopen("temp.data", "w"); |
因为没有使用
h264_mp4toannexb
Bitstream Filters
, 所以帧数据的每个 Slice 前面是 Slice 的长度而不是 Start code ( 00000001 )。这并不影响FFmpeg 解码。
参考资料
- https://trac.ffmpeg.org/wiki/HWAccelIntro
- http://ffmpeg.org/ffmpeg-all.html
- https://stackoverflow.com/questions/19456745/libavcodec-libx264-do-not-produce-b-frames
https://stackoverflow.com/questions/15855535/setting-b-frames-in-a-video-with-ffmpeg - https://superuser.com/questions/996949/how-to-set-the-number-of-b-frames-in-ffmpeg-for-h-265-encoding
- https://blog.csdn.net/dangxw_/article/details/50974880
- https://blog.csdn.net/NB_vol_1/article/details/78363559
- https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/decode_video.c
- https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/hw_decode.c
- https://en.wikipedia.org/wiki/YUV
- https://github.com/mypopydev/