FFmpeg 的硬解码(Hardware Decoding)通过调用 GPU 或专用硬件的编解码能力实现,能显著降低 CPU 占用率。
一、FFmpeg 支持的硬件解码类型
FFmpeg 原生支持多种硬件加速类型,具体由 AVHWDeviceType
定义,包括:
-
NVIDIA CUDA/NVDEC:基于 NVIDIA 显卡的解码。
-
**Intel Quick Sync Video (QSV)**:Intel 集成显卡的硬件加速。
-
VAAPI:适用于 Intel/AMD 硬件的通用视频加速 API。
-
VideoToolbox:macOS/iOS 平台的硬解码。
-
MediaCodec :Android 平台的硬解码(需 FFmpeg 编译时启用)。
enum AVHWDeviceType { AV_HWDEVICE_TYPE_NONE, AV_HWDEVICE_TYPE_VDPAU, AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_VAAPI, AV_HWDEVICE_TYPE_DXVA2, AV_HWDEVICE_TYPE_QSV, AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DRM, AV_HWDEVICE_TYPE_OPENCL, AV_HWDEVICE_TYPE_MEDIACODEC, };
av_hwdevice_find_type_by_name:根据名称查找对应的AVHWDeviceType。支持的名称如下所示。
static const char *const hw_type_names[] = {
[AV_HWDEVICE_TYPE_CUDA] = "cuda",
[AV_HWDEVICE_TYPE_DRM] = "drm",
[AV_HWDEVICE_TYPE_DXVA2] = "dxva2",
[AV_HWDEVICE_TYPE_D3D11VA] = "d3d11va",
[AV_HWDEVICE_TYPE_OPENCL] = "opencl",
[AV_HWDEVICE_TYPE_QSV] = "qsv",
[AV_HWDEVICE_TYPE_VAAPI] = "vaapi",
[AV_HWDEVICE_TYPE_VDPAU] = "vdpau",
[AV_HWDEVICE_TYPE_VIDEOTOOLBOX] = "videotoolbox",
[AV_HWDEVICE_TYPE_MEDIACODEC] = "mediacodec",
};
与硬解相关的函数:
avcodec_get_hw_config:用于获取编解码器支持的硬件配置AVCodecHWConfig。这里用于获取硬件支持的像素格式。
av_hwdevice_ctx_create:av_hwdevice_ctx_create创建硬件设备相关的上下文信息AVHWDeviceContext和对硬件设备进行初始化。
decoder_ctx->get_format = get_hw_format ,get_hw_format是向AVCodecContext注册的一个函数,用于协商支持的像素格式。
av_hwframe_transfer_data:拷贝数据到一个硬件的surface,或者从一个硬件surface拷贝数据,也就是GPU和CPU之间数据拷贝。这里用于GPU拷贝到CPU。GPU解码后数据格式默认类型是从硬件读取,CUDA可能是AV_PIX_FMT_NV12;而CPU解码后的数据一般是YUV数据,比如AV_PIX_FMT_YUV420P。
av_find_best_stream:查找最佳媒体流(如视频、音频、字幕等)的函数。
enum AVPixelFormat hw_pix_fmt;
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_CUDA;
AVCodec *decoder = NULL;
/* open the input file */
if (avformat_open_input(&input_ctx, argv[2], NULL, NULL) != 0) {
fprintf(stderr, "Cannot open input file '%s'\n", argv[2]);
return -1;
}
if (avformat_find_stream_info(input_ctx, NULL) < 0) {
fprintf(stderr, "Cannot find input stream information.\n");
return -1;
}
/* find the video stream information */
ret = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
if (ret < 0) {
fprintf(stderr, "Cannot find a video stream in the input file\n");
return -1;
}
for (i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
if (!config) {
fprintf(stderr, "Decoder %s does not support device type %s.\n",
decoder->name, av_hwdevice_get_type_name(type));
return -1;
}
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
config->device_type == type) {
hw_pix_fmt = config->pix_fmt;
break;
}
}
二、FFmpeg 硬件解码器名称及对应编码格式
FFmpeg 支持的硬件解码器名称与编码格式关联紧密,需根据具体硬件平台(如 NVIDIA、Intel、AMD)及接口协议(如 CUDA、VAAPI、QSV)选择适配方案。以下是主流编码格式对应的硬件解码器名称示例:
2.1、视频编码格式与硬件解码器
-
H.264/AVC
h264_cuvid
(NVIDIA CUDA加速)h264_qsv
(Intel Quick Sync Video)h264_vaapi
(跨平台开源接口)h264_amf
(AMD Advanced Media Framework)
-
H.265/HEVC
hevc_cuvid
(NVIDIA)hevc_qsv
(Intel)hevc_vaapi
(通用接口)hevc_amf
(AMD)
-
VP8/VP9
vp8_cuvid
(NVIDIA)vp9_vaapi
(通用接口)vp9_qsv
(Intel)
-
AV1
av1_qsv
(Intel)av1_vaapi
(通用接口)
2.2、音频编码格式与硬件解码器
-
AAC
- 硬件解码依赖平台驱动支持(如 Intel HD Audio),FFmpeg 中通常通过系统接口调用,无独立硬解名称。
-
MP3/Opus
- 硬解支持较少,多采用软件解码。
三**、硬解码实现流程**
1. 初始化硬件设备
- 获取硬件设备类型
通过av_hwdevice_find_type_by_name
或枚举类型确定目标硬解码设备。 - 创建硬件设备上下文
使用av_hwdevice_ctx_create
初始化硬件设备上下文(hw_device_ctx
)。
2. 配置解码器
- 查找支持硬解码的编解码器
例如 H.264 硬解需查找h264_cuvid
(NVIDIA)或h264_mediacodec
(Android)等解码器。 - 设置解码器参数
在AVCodecContext
中指定hw_device_ctx
,关联硬件设备上下文。
3. 解码数据
- 发送数据包
调用avcodec_send_packet
将压缩数据送入解码器。 - 接收解码帧
通过avcodec_receive_frame
获取解码后的帧数据,硬件解码的帧通常存储在 GPU 内存中。
4. 处理解码数据
- 内存映射与格式转换
若需 CPU 访问解码数据,需使用av_hwframe_transfer_data
将帧从 GPU 内存复制到 CPU 内存。
四**、代码示例**
3.1 实现硬件解码(以 NVIDIA CUDA/NVDEC 为例)的完整示例代码。
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>
int main(int argc, char *argv[]) {
AVFormatContext *fmt_ctx = NULL;
AVCodecContext *codec_ctx = NULL;
const AVCodec *codec = NULL;
AVBufferRef *hw_device_ctx = NULL;
AVPacket *pkt = NULL;
AVFrame *hw_frame = NULL, *sw_frame = NULL;
int video_stream_idx = -1;
// 1. 初始化 FFmpeg
avformat_network_init();
// 2. 打开输入文件
if (avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL) < 0) {
fprintf(stderr, "无法打开输入文件\n");
return -1;
}
// 3. 查找视频流索引
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
fprintf(stderr, "无法获取流信息\n");
goto cleanup;
}
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_idx = i;
break;
}
}
if (video_stream_idx == -1) {
fprintf(stderr, "未找到视频流\n");
goto cleanup;
}
// 4. 初始化硬件设备 (CUDA)
if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0) < 0) {
fprintf(stderr, "无法创建 CUDA 硬件设备\n");
goto cleanup;
}
// 5. 配置硬件解码器
codec = avcodec_find_decoder_by_name("h264_cuvid"); // NVIDIA 硬解解码器
if (!codec) {
fprintf(stderr, "未找到支持的硬解解码器\n");
goto cleanup;
}
codec_ctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_idx]->codecpar);
codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); // 关联硬件设备
// 6. 打开解码器
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
fprintf(stderr, "无法打开硬解解码器\n");
goto cleanup;
}
// 7. 初始化数据包和帧
pkt = av_packet_alloc();
hw_frame = av_frame_alloc();
sw_frame = av_frame_alloc();
// 8. 解码循环
while (av_read_frame(fmt_ctx, pkt) >= 0) {
if (pkt->stream_index == video_stream_idx) {
// 发送数据包到解码器
if (avcodec_send_packet(codec_ctx, pkt) < 0) {
fprintf(stderr, "发送数据包失败\n");
continue;
}
// 接收解码后的帧
while (avcodec_receive_frame(codec_ctx, hw_frame) == 0) {
// 检查是否为硬件帧
if (hw_frame->format == AV_PIX_FMT_CUDA) {
// 将 GPU 内存数据复制到 CPU 内存
if (av_hwframe_transfer_data(sw_frame, hw_frame, 0) < 0) {
fprintf(stderr, "GPU→CPU 内存拷贝失败\n");
continue;
}
// 在此处理 sw_frame(YUV420 数据)
// 例如:保存到文件、渲染、转码等
printf("解码一帧:宽度=%d, 高度=%d\n", sw_frame->width, sw_frame->height);
}
av_frame_unref(hw_frame);
av_frame_unref(sw_frame);
}
}
av_packet_unref(pkt);
}
cleanup:
// 9. 释放资源
av_frame_free(&hw_frame);
av_frame_free(&sw_frame);
av_packet_free(&pkt);
avcodec_free_context(&codec_ctx);
av_buffer_unref(&hw_device_ctx);
avformat_close_input(&fmt_ctx);
avformat_network_deinit();
return 0;
}
说明:
AV_PIX_FMT_CUDA等像素格式对比。
格式 | 存储位置 | 典型用途 | 性能优势 |
---|---|---|---|
AV_PIX_FMT_CUDA |
GPU 显存 | 硬解码、全流程 GPU 处理 | 零拷贝、低延迟 |
AV_PIX_FMT_NV12 |
CPU 内存 | 软解码、跨设备处理 | 兼容性强,但需拷贝 |
AV_PIX_FMT_RGB24 |
CPU 内存 | 图像显示、算法输入 | 通用性强,但带宽占用高 |
五**、注意事项**
- 编译配置
启用硬解码需在 FFmpeg 编译时添加对应选项(如--enable-cuda --enable-cuvid
--enable-nonfree)。 - 平台差异
- Windows:常用 DXVA2 或 NVIDIA CUDA6。
- Android:需启用
--enable-mediacodec
并关联MediaCodec
API。
- 兼容性回退
硬解码失败时需切换至软解(如h264
解码器)。 - 硬件类型选择
若需使用其他硬件(如 Intel QSV 或 VAAPI):
1)解码器名称改为 h264_qsv 或 h264_vaapi。
2)修改 AV_HWDEVICE_TYPE_CUDA 为 AV_HWDEVICE_TYPE_QSV 或 AV_HWDEVICE_TYPE_VAAPI。 - 滤镜处理 :通过
libavfilter
实现缩放、裁剪、水印等操作。 - 封装格式转换 :使用
avformat_write_header()
和av_write_frame()
实现转封装(如 MP4 转 TS)。
六**、性能优化**
- 减少内存拷贝:直接在 GPU 内存中处理数据(如 OpenGL 渲染)。
- 帧格式限制 :硬解码输出格式通常为
NV12
或YUV420P
,需适配后续处理流程。