深入理解FFmpeg--软/硬件解码流程

FFmpeg是一款强大的多媒体处理工具,支持软件和硬件解码。软件解码利用CPU执行解码过程,适用于各种平台,但可能对性能要求较高。硬件解码则利用GPU或其他专用硬件加速解码,能显著降低CPU负载,提升解码效率和能效。FFmpeg结合这两种解码方式,提供了灵活的多媒体解决方案,适合于视频处理、流媒体和多媒体应用开发。

1、FFmpeg支持多种硬件加速类型,用于编解码视频,以提升性能和效率。以下是FFmpeg支持的主要硬件加速类型:

  1. NVIDIA NVENC/NVDEC:利用NVIDIA显卡进行视频编码(NVENC)和解码(NVDEC)。
  2. Intel Quick Sync Video (QSV):利用Intel处理器中的集成图形进行视频加速。
  3. AMD Video Coding Engine (VCE)和Video Decoding Acceleration (VDA):利用AMD显卡进行视频编码和解码。
  4. VAAPI (Video Acceleration API):适用于Intel和AMD硬件,通过通用的API接口进行硬件加速。
  5. VDPAU (Video Decode and Presentation API for Unix):主要用于NVIDIA显卡的硬件解码加速。
  6. DXVA2 (DirectX Video Acceleration):适用于Windows平台,利用DirectX进行视频加速。
  7. OpenMAX IL (Open Media Acceleration Integration Layer):用于移动设备和嵌入式系统的视频加速。
  8. Vulkan:一种跨平台的图形和计算API,也可以用于视频加速。

这些硬件加速类型使FFmpeg在处理高分辨率视频时更加高效,减少了CPU负载,提高了多媒体处理的整体性能。

2、硬件解码流程图(软解流程比起硬解少一些步骤,就不单独画了):

3、代码示例:

Decode.h

cpp 复制代码
#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

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,有问题欢迎指正。

相关推荐
岁月小龙9 小时前
如何让ffmpeg运行时从当前目录加载库,而不是从/lib64
ffmpeg·origin·ffprobe·rpath
行者记2 天前
ffmpeg命令——从wireshark包中的rtp包中分离h264
测试工具·ffmpeg·wireshark
EasyCVR2 天前
国标GB28181视频平台EasyCVR私有化视频平台工地防盗视频监控系统方案
运维·科技·ffmpeg·音视频·1024程序员节·监控视频接入
hypoqqq2 天前
使用ffmpeg播放rtsp视频流
ffmpeg
cuijiecheng20182 天前
音视频入门基础:FLV专题(24)——FFmpeg源码中,获取FLV文件视频信息的实现
ffmpeg·音视频
QMCY_jason2 天前
黑豹X2 armbian 编译rkmpp ffmpeg 实现CPU视频转码
ffmpeg
苍天饶过谁?2 天前
SDL基本使用
ffmpeg
HZ355722 天前
ffmpeg视频解码
ffmpeg·音视频
runing_an_min2 天前
windows运行ffmpeg的脚本报错:av_ts2str、av_ts2timestr、av_err2str => E0029 C4576
c++·windows·ffmpeg·e0029
EelBarb2 天前
ffmpeg:视频字幕嵌入(GPU加速)
ffmpeg·音视频