ffmpeg库视频硬解码使用流程
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. 初始化 FFmpegavformat_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
,需适配后续处理流程。