FFmpeg 与 VideoToolBox(3):VideoToolBox 硬解 H.264

一、概述

FFmpeg 的硬解和软解的流程大部分是一致的,关键点在于开启硬解模式,以及解码成功后从 GPU 中将数据提取出来。

二、解码过程

1、测试视频文件

为了尽可能简单,使用《FFmpeg 与 VideoToolBox(1):准备工作》准备的 temp.data 文件,其有如下特点:
(1) 非标准的;
(2) 有封装格式又足够简单的,每一帧数据有个长度头(4字节);
(3) 知道文件包含的是 H.264 数据;
(4) SPSPPS 数据已经在 IDR 帧中。

2、解码过程描述

解码过程对应的 FFmpeg API,可以用伪代码表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
fopen() // 打开文件
avcodec_find_decoder(AV_CODEC_ID_H264) // 以 AV_CODEC_ID_H264 初始化并返回解码器 AVCodec
avcodec_alloc_context3() // 初始化并返回一个解码器上下文 AVCodecContext 对象。
// 硬件配置开始
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX // VideoToolBox 硬件设备类型,也可通过 av_hwdevice_find_type_by_name("videotoolbox") 获取
av_hwdevice_ctx_create() // 通过硬件设备类型,创建硬件设备上下文,数据类型为 AVBufferRef
AVCodecContext->hw_device_ctx = av_buffer_ref() // 硬件上下文拷贝一份然后赋值给解码器上下文的 hw_device_ctx 字段
AVCodecContext->get_format = get_hw_format // get_hw_format 是一个函数,其返回 AV_PIX_FMT_VIDEOTOOLBOX 而不是 AV_PIX_FMT_YUV420P 之类的。
// 硬件配置结束
while(true) // 逐帧读取,返回的是 AVPacket。对于视频,如果读取成功,1 Packet 总数包含 1 Frame,对于音频暂时不表。
{
fread() // 读取4字节的帧头
fread() // 根据帧头标示的帧长度读取帧
// 将帧数据放入一个 AVPacket 结构中
avcodec_send_packet() // 将包含 1 Frame 数据 1 Packet 传入解码器
while(true)
{
avcodec_receive_frame() // 返回 AVFrame
av_hwframe_transfer_data() // 将数据从 GPU 中将数据提取出来, GPU 中的格式是 AV_PIX_FMT_VIDEOTOOLBOX ,提取出来后是 AV_PIX_FMT_NV12
}
}

// 传入 null 的 AVPacket 再次调用 avcodec_send_packet,以确保解码器中的数据全部取出
avcodec_send_packet()
while(true)
{
avcodec_receive_frame()
av_hwframe_transfer_data()
}

// 资源释放操作

参考资料

https://trac.ffmpeg.org/wiki/HWAccelIntro
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/