FFmpeg是一款强大的多媒体处理工具,支持软件和硬件解码。软件解码利用CPU执行解码过程,适用于各种平台,但可能对性能要求较高。硬件解码则利用GPU或其他专用硬件加速解码,能显著降低CPU负载,提升解码效率和能效。FFmpeg结合这两种解码方式,提供了灵活的多媒体解决方案,适合于视频处理、流媒体和多媒体应用开发。
1、FFmpeg支持多种硬件加速类型,用于编解码视频,以提升性能和效率。以下是FFmpeg支持的主要硬件加速类型:
- NVIDIA NVENC/NVDEC:利用NVIDIA显卡进行视频编码(NVENC)和解码(NVDEC)。
- Intel Quick Sync Video (QSV):利用Intel处理器中的集成图形进行视频加速。
- AMD Video Coding Engine (VCE)和Video Decoding Acceleration (VDA):利用AMD显卡进行视频编码和解码。
- VAAPI (Video Acceleration API):适用于Intel和AMD硬件,通过通用的API接口进行硬件加速。
- VDPAU (Video Decode and Presentation API for Unix):主要用于NVIDIA显卡的硬件解码加速。
- DXVA2 (DirectX Video Acceleration):适用于Windows平台,利用DirectX进行视频加速。
- OpenMAX IL (Open Media Acceleration Integration Layer):用于移动设备和嵌入式系统的视频加速。
- Vulkan:一种跨平台的图形和计算API,也可以用于视频加速。
这些硬件加速类型使FFmpeg在处理高分辨率视频时更加高效,减少了CPU负载,提高了多媒体处理的整体性能。
2、硬件解码流程图(软解流程比起硬解少一些步骤,就不单独画了):
3、代码示例:
Decode.h
#ifndef WINDOWS_FFMPEG_DECODE_H
#define WINDOWS_FFMPEG_DECODE_H
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#include <memory>
class Decode {
public:
Decode();
~Decode();
//初始化软解码
//IN:AVCodecID, AVPixelFormat
int InitSoftDecode(int VideoType, int PixFmt);
//初始化硬解码
//IN:AVFormatContext输入上下文, 硬解类型名称
int InitHardDecode(AVFormatContext* input_ctx, const std::string& HWType);
//解码视频数据
int DecodePacket(AVPacket* packet, AVFrame* frame);
private:
//解码器上下文的get_format函数
static enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts);
//初始化AVBufferRef
static int hw_decoder_init(AVCodecContext *ctx, const enum AVHWDeviceType type);
private:
AVCodecContext* pDecodeCtx_;
AVCodecParserContext* pParseCtx_;
AVStream* pVStream_;
const AVCodec* pCodec_;
static enum AVPixelFormat ePixFmt_;
static AVBufferRef* pDeviceCtx;
bool bHWDecode_;
};
#endif //WINDOWS_FFMPEG_DECODE_H
Decode.cpp
#include "Decode.h"
#include <iostream>
using namespace std;
enum AVPixelFormat Decode::ePixFmt_;
AVBufferRef* Decode::pDeviceCtx;
Decode::Decode() {
}
Decode::~Decode() {
}
enum AVPixelFormat Decode::get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) {
const enum AVPixelFormat *p;
for (p = pix_fmts; *p != -1; p++) {
if (*p == ePixFmt_)
return *p;
}
fprintf(stderr, "Failed to get HW surface format.\n");
return AV_PIX_FMT_NONE;
}
int Decode::hw_decoder_init(AVCodecContext *ctx, const enum AVHWDeviceType type) {
int err = 0;
if ((err = av_hwdevice_ctx_create(&pDeviceCtx, type,
nullptr, nullptr, 0)) < 0) {
fprintf(stderr, "Failed to create specified HW device.\n");
return err;
}
ctx->hw_device_ctx = av_buffer_ref(pDeviceCtx);
return err;
}
int Decode::InitSoftDecode(int VideoType, int PixFmt) {
pCodec_ = avcodec_find_decoder((AVCodecID)VideoType);
if (!pCodec_) {
std::cout<<"avcodec_find_decoder Failed"<<std::endl;
return -1;
}
pParseCtx_ = av_parser_init(pCodec_->id);
if (!pParseCtx_) {
std::cout<<"av_parser_init Failed"<<std::endl;
return -1;
}
pDecodeCtx_ = avcodec_alloc_context3(pCodec_);
if (!pDecodeCtx_) {
std::cout<<"avcodec_alloc_context3 Failed"<<std::endl;
return -1;
}
pDecodeCtx_->pix_fmt = (AVPixelFormat)PixFmt;
if (avcodec_open2(pDecodeCtx_, pCodec_, nullptr) < 0) {
std::cout<<"avcodec_open2 Failed"<<std::endl;
return -1;
}
bHWDecode_ = false;
return 0;
}
int Decode::InitHardDecode(AVFormatContext* input_ctx, const std::string& HWType) {
enum AVHWDeviceType type;
type = av_hwdevice_find_type_by_name(HWType.c_str());
if (type == AV_HWDEVICE_TYPE_NONE) {
std::cout<<"UnKnown HW Device Type"<<std::endl;
while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) {
std::cout<< type <<std::endl;
}
return -1;
}
int video_index = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1 , -1, &pCodec_, 0);
if (video_index < 0) {
cout<<"Cannot find a video stream in the input file"<<endl;
return -1;
}
for (int i = 0; ; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(pCodec_, i);
if (!config) {
cout<<"avcodec_get_hw_config Failed"<<i<<endl;
return -1;
}
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
config->device_type == type) {
ePixFmt_ = config->pix_fmt;
break;
}
}
pDecodeCtx_ = avcodec_alloc_context3(pCodec_);
if (!pDecodeCtx_) {
cout<<"avcodec_alloc_context3 Failed"<<endl;
return -1;
}
pVStream_ = input_ctx->streams[video_index];
if (avcodec_parameters_to_context(pDecodeCtx_, pVStream_->codecpar) < 0) {
cout<<"avcodec_parameters_to_context Failed"<<endl;
return -1;
}
pDecodeCtx_->get_format = get_hw_format;
if (hw_decoder_init(pDecodeCtx_, type) < 0) {
return -1;
}
if (avcodec_open2(pDecodeCtx_, pCodec_, nullptr) < 0) {
cout<<"avcodec_open2 Failed"<<endl;
return -1;
}
bHWDecode_ = true;
return 0;
}
int Decode::DecodePacket(AVPacket* packet, AVFrame* frame) {
//软解码
if (!bHWDecode_) {
int nRet = avcodec_send_packet(pDecodeCtx_, packet); //将AVPacket发送至解码器中
if (nRet < 0) {
cout<<"Error sending a packet for decoding"<<endl;
return -1;
}
nRet = avcodec_receive_frame(pDecodeCtx_, frame); //从解码器中获取被解码后的帧数据AVFrame
if (nRet == AVERROR(EAGAIN) || nRet == AVERROR_EOF)
return 0;
else if (nRet < 0) {
cout<<"Error during decoding"<<endl;
return -1;
}
return 0;
} else { //硬解码
AVFrame* tmpFrame = nullptr, *swFrame = nullptr;
int nRet = avcodec_send_packet(pDecodeCtx_, packet); //将AVPacket发送至解码器中
if (nRet < 0) {
cout<<"Error sending a packet for decoding"<<endl;
av_frame_free(&tmpFrame);
av_frame_free(&swFrame);
return -1;
}
if (!(tmpFrame = av_frame_alloc()) || !(swFrame = av_frame_alloc())) {
cout<<"Can not alloc frame"<<endl;
av_frame_free(&tmpFrame);
av_frame_free(&swFrame);
nRet = AVERROR(ENOMEM);
return -1;
}
nRet = avcodec_receive_frame(pDecodeCtx_, tmpFrame); //从解码器中获取被解码后的帧数据AVFrame
if (nRet == AVERROR(EAGAIN) || nRet == AVERROR_EOF) {
av_frame_free(&tmpFrame);
av_frame_free(&swFrame);
return 0;
} else if (nRet < 0) {
cout<<"Error while decoding"<<endl;
av_frame_free(&tmpFrame);
av_frame_free(&swFrame);
return -1;
}
if (frame->format == ePixFmt_) {
/* 将GPU中的数据 移交到CPU中*/
if (av_hwframe_transfer_data(swFrame, tmpFrame, 0) < 0) {
cout<<"Error transferring the data to system memory"<<endl;
av_frame_free(&tmpFrame);
av_frame_free(&swFrame);
return -1;
}
frame = swFrame;
} else {
frame = tmpFrame;
}
av_frame_free(&tmpFrame);
av_frame_free(&swFrame);
return 0;
}
}
代码仅供参考,因为电脑太旧,硬解没识别出来支持的硬件,简单跟着深入理解FFmpeg写的Demo,有问题欢迎指正。