首页 > 编程语言 >C++ ffmpeg硬件解码的实现方法

C++ ffmpeg硬件解码的实现方法

时间:2023-05-18 12:11:26浏览次数:52  
标签:ffmpeg 解码 decoder ctx ret C++ stderr av frame

什么是硬件解码

普通解码是利用cpu去解码也就是软件解码 硬件解码就是利用gpu去解码

为什么要使用硬件解码

首先最大的好处 快硬解播放出来的视频较为流畅,并且能够延长移动设备播放视频的时间; 而软解由于软解加大CPU工作负荷,会占用过多的移动CPU资源,如果CPU能力不足,则软件也将受到影响 最主要就是一个字 快

怎样使用硬件解码

ffmpeg内部为我们提供了友好的接口去实现硬件解码

注意事项

ffmpeg内部有很多编解码器 并不是所有的编解码器都支持硬件解码 并且就算支持硬件解码的编解码器也不一定能支持你的显卡 也就是说在使用硬件解码时我们首先要去判断这个解码器是否支持在这个平台对这个显卡进行硬件编解码 不然是无法使用的

对显卡厂家SDK进行封装和集成,实现部分的硬件编解码

其次在ffmpeg中软件编解码器可以实现相关硬解加速。如在h264解码器中可以使用cuda 加速,qsv加速,dxva2 加速,d3d11va加速,opencl加速等。cuda qsv等就是不同公司推出的针对gpu编程的工具包

AV_CODEC_ID_H264;代表是h264编解码器。而name代表某一个编码器或解码器。通常我们使用avcodec_find_decoder(ID)和avcodec_find_encoder(ID)来解码器和编码器。默认采用的软件编解码。如果我们需要使用硬件编解码,采用avcodec_find_encoder_by_name(name)和avcodec_find_decoder_by_name(name)来指定编码器。其他代码流程与软件编解码一致。

1 2 3 4 5 6 //codec = avcodec_find_decoder(AV_CODEC_ID_H264); codec = avcodec_find_decoder_by_name("h264_cuvid"); if (!codec) {     fprintf(stderr, "Codec not found\n");     exit(1); }

通过id找到的可能并不是你预期中的编解码器 通过name找到的一定是你想要的

下面是ffmpeg官方的硬件解码例子 我加上了中文注释方便理解

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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 #include <stdio.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/pixdesc.h> #include <libavutil/hwcontext.h> #include <libavutil/opt.h> #include <libavutil/avassert.h> #include <libavutil/imgutils.h> static AVBufferRef *hw_device_ctx = NULL; static enum AVPixelFormat hw_pix_fmt; static FILE *output_file = NULL; static int hw_decoder_init(AVCodecContext *ctx, const enum AVHWDeviceType type) {     int err = 0;     //创建硬件设备信息上下文     if ((err = av_hwdevice_ctx_create(&hw_device_ctx, type,         NULL, NULL, 0)) < 0) {         fprintf(stderr, "Failed to create specified HW device.\n");         return err;     }     //绑定编解码器上下文和硬件设备信息上下文     ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);     return err; } static enum AVPixelFormat get_hw_format(AVCodecContext *ctx,     const enum AVPixelFormat *pix_fmts) {     const enum AVPixelFormat *p;     for (p = pix_fmts; *p != -1; p++) {         if (*p == hw_pix_fmt)             return *p;     }     fprintf(stderr, "Failed to get HW surface format.\n");     return AV_PIX_FMT_NONE; } static int decode_write(AVCodecContext *avctx, AVPacket *packet) {     AVFrame *frame = NULL, *sw_frame = NULL;     AVFrame *tmp_frame = NULL;     uint8_t *buffer = NULL;     int size;     int ret = 0;     ret = avcodec_send_packet(avctx, packet);     if (ret < 0) {         fprintf(stderr, "Error during decoding\n");         return ret;     }     while (1) {         if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc())) {             fprintf(stderr, "Can not alloc frame\n");             ret = AVERROR(ENOMEM);             goto fail;         }         ret = avcodec_receive_frame(avctx, frame);         if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {             av_frame_free(&frame);             av_frame_free(&sw_frame);             return 0;         }         else if (ret < 0) {             fprintf(stderr, "Error while decoding\n");             goto fail;         }         if (frame->format == hw_pix_fmt) {             /* retrieve data from GPU to CPU */             if ((ret = av_hwframe_transfer_data(sw_frame, frame, 0)) < 0) {                 fprintf(stderr, "Error transferring the data to system memory\n");                 goto fail;             }             tmp_frame = sw_frame;         }         else             tmp_frame = frame;         size = av_image_get_buffer_size(tmp_frame->format, tmp_frame->width,             tmp_frame->height, 1);         buffer = av_malloc(size);         if (!buffer) {             fprintf(stderr, "Can not alloc buffer\n");             ret = AVERROR(ENOMEM);             goto fail;         }         ret = av_image_copy_to_buffer(buffer, size,             (const uint8_t * const *)tmp_frame->data,             (const int *)tmp_frame->linesize, tmp_frame->format,             tmp_frame->width, tmp_frame->height, 1);         if (ret < 0) {             fprintf(stderr, "Can not copy image to buffer\n");             goto fail;         }         if ((ret = fwrite(buffer, 1, size, output_file)) < 0) {             fprintf(stderr, "Failed to dump raw data.\n");             goto fail;         }     fail:         av_frame_free(&frame);         av_frame_free(&sw_frame);         av_freep(&buffer);         if (ret < 0)             return ret;     } } int main(int argc, char *argv[]) {     AVFormatContext *input_ctx = NULL;     int video_stream, ret;     AVStream *video = NULL;     AVCodecContext *decoder_ctx = NULL;     AVCodec *decoder = NULL;     AVPacket packet;     enum AVHWDeviceType type;     int i;     if (argc < 4) {         fprintf(stderr, "Usage: %s <device type> <input file> <output file>\n", argv[0]);         return -1;     }     //通过你传入的名字来找到对应的硬件解码类型     type = av_hwdevice_find_type_by_name(argv[1]);     if (type == AV_HWDEVICE_TYPE_NONE) {         fprintf(stderr, "Device type %s is not supported.\n", argv[1]);         fprintf(stderr, "Available device types:");         while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)             fprintf(stderr, " %s", av_hwdevice_get_type_name(type));         fprintf(stderr, "\n");         return -1;     }     /* 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;     }     video_stream = ret;     //去遍历所有编解码器支持的硬件解码配置 如果和之前你指定的是一样的 那么就可以继续执行了 不然就找不到     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;         }     }     if (!(decoder_ctx = avcodec_alloc_context3(decoder)))         return AVERROR(ENOMEM);     video = input_ctx->streams[video_stream];     if (avcodec_parameters_to_context(decoder_ctx, video->codecpar) < 0)         return -1;     //填入回调函数 通过这个函数 编解码器能够知道显卡支持的像素格式     decoder_ctx->get_format = get_hw_format;     if (hw_decoder_init(decoder_ctx, type) < 0)         return -1;    //绑定完成后 打开编解码器     if ((ret = avcodec_open2(decoder_ctx, decoder, NULL)) < 0) {         fprintf(stderr, "Failed to open codec for stream #%u\n", video_stream);         return -1;     }     /* open the file to dump raw data */     output_file = fopen(argv[3], "w+");     /* actual decoding and dump the raw data */     while (ret >= 0) {         if ((ret = av_read_frame(input_ctx, &packet)) < 0)             break;         if (video_stream == packet.stream_index)             ret = decode_write(decoder_ctx, &packet);         av_packet_unref(&packet);     }     /* flush the decoder */     packet.data = NULL;     packet.size = 0;     ret = decode_write(decoder_ctx, &packet);     av_packet_unref(&packet);     if (output_file)         fclose(output_file);     avcodec_free_context(&decoder_ctx);     avformat_close_input(&input_ctx);     av_buffer_unref(&hw_device_ctx);     return 0; }

关键函数解析

enum AVHWDeviceType av_hwdevice_find_type_by_name(const char *name);

通过传入的参数查找对应的硬件类型 其中 AVHWDeviceType值如下

const AVCodecHWConfig *avcodec_get_hw_config(const AVCodec *codec, int index);

拿到编解码器支持的硬件配置比如硬件支持的像素格式等等

1 2 3 4 5 6 7 8 9 10 11 static enum AVPixelFormat get_hw_format(AVCodecContext *ctx,     const enum AVPixelFormat *pix_fmts) {     const enum AVPixelFormat *p;     for (p = pix_fmts; *p != -1; p++) {         if (*p == hw_pix_fmt)             return *p;     }     fprintf(stderr, "Failed to get HW surface format.\n");     return AV_PIX_FMT_NONE; }

这是一个回调函数,它的作用就是告诉解码器codec自己的目标像素格式是什么。在上一步骤获取到了硬解码器codec可以支持的目标格式之后,就通过这个回调函数告知给codec

  • fmt是这个解码器codec支持的像素格式,且按照质量优劣进行排序;
  • 如果没有特别的需要,这个步骤是可以省略的。内部默认会使用“native”的格式。

int av_hwdevice_ctx_create(AVBufferRef **pdevice_ref, enum AVHWDeviceType type, const char *device, AVDictionary *opts, int flags)

这个函数的作用是,创建硬件设备相关的上下文信息AVHWDeviceContext,包括分配内存资源、对硬件设备进行初始化。

准备好硬件设备上下文AVHWDeviceContext后,需要把这个信息绑定到AVCodecContext,就可以像软解一样的流程执行解码操作了。绑定操作如下

注意这里硬件设备信息上下文是通过AVBuffer来存储的引用 并不是实体

int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags)

这个函数是负责在cpu内存和硬件内存(原文是hw surface)之间做数据交换的。也就是说,它不但可以把数据从硬件surface上搬回系统内存,反向操作也支持;甚至可以直接在硬件内存之间做数据交互。

标签:ffmpeg,解码,decoder,ctx,ret,C++,stderr,av,frame
From: https://www.cnblogs.com/kn-zheng/p/17411539.html

相关文章

  • c++程序流程结构
    c++程序流程结构c++中支持最基本的三种流程结构:顺序结构、选择结构、循环结构顺序结构:程序按顺序执行,不会发生跳转。选择结构:根据条件是否满足,有选择的执行相应的功能。循环结构:根据条件是否满足,循环多次执行某代码块。if语句:作用:执行满足条件的语句单行if语句:语法:if(条件){ 条件......
  • 初识c++
    c++开发环境搭建初识c++第一个c++程序c++程序框架:实例:#include<iostream>usingnamespacestd;intmain(){ system("pause"); return0;}//#include代表预处理指令iostream中声明了程序所需要的的输入和输出操作的有关信息#include<iostream>//usingnamespace针对命名......
  • MinGW32编译ffmpeg+libsrt
    MinGW编译带srt库的ffmpeg前言MinGW安装CMAKE安装VisualStudio2017安装支持windows的线程库SRT库的编译openssl的安装SRT编译MinGW下的SDL和opensslopensslSDLffmpeg编译PKG-CONFIG配置ffmpeg前言国内关于SRT协议的资料,几乎为0,没什么人用srt协议去编译过ffmpeg,而且这东西在Wind......
  • c++ ffmpeg 推送rtsp码_编译ffmpeg以获得极佳性能
    背景Gemfield最近尝试使用python封装的ffmpeg库(PyAV)来进行mp4文件、rtmp协议及其它协议的decode,具体来说就是将mp4文件(或者rtmp协议的数据,下同)进行demux并逐帧decode。然而在这期间发现了一些decode的性能问题。这些问题概括起来就是2点:python封装的ffmpeg是否能够利用到多核CPU的......
  • 【C++】多态(下)
    @TOC1.单继承中的虚函数表整体代码#include<iostream>usingnamespacestd;classBase{public:virtualvoidFunc1(){cout<<"Base::Func1()"<<endl;}virtualvoidFunc2(){cout<<"Base::Func......
  • c++打卡第二十九天
    模板编程对于模板编程,写template<typenameT>一、函数模板编程1、编辑模板表明返回值T或者无返回值+函数名(T&变量) 2、例题描述请使用模板参数设计实现双倍功能函数,函数功能要求实现返回值为输入参数的两倍,函数参数应能适应整型、浮点型、双精度型等各种类型,返回值类型与......
  • 输入输出流(C++)
    一、问题描述定义一个Dog类,包括体重和年龄两个数据成员及其成员函数,声明一个实例dog1,体重5,年龄10,使用I/O流把dog1的状态写入磁盘文件。再声明一个实例dog2,通过读取文件dog1的状态赋给dog2。分别用文本方式和二进制方式操作文件。二、代码实现1#include<fstream>2#includ......
  • 2654. 使数组所有元素变成 1 的最少操作次数(c++,gcd性质)
    题目链接:2654.使数组所有元素变成1的最少操作次数方法一:计算最短的gcd为1的子数组解题思路本题目标:使得所有的数组元素都变为\(1\),通过求相邻元素\(gcd\)将其赋值给一方的方式;思路:若想操作数最少,那么就是不为\(1\)的数\(x\)和1求\(gcd\),即\(x=gcd(x,1)\),......
  • c++打卡练习(33)
    歌星大赛,十个评委打分,去掉一个最高分,去掉一个最低分,求剩下的八个评分的平均分,作为选手的最终分数流程图:伪代码:源代码:#include<iostream>usingnamespacestd;intmain(){ inta[10],b[8]; inti,j,k,t,sum=0,Ave,max,min; cout<<"输入十个正整数"<<endl; for(i=0;i<10;i++){ ......
  • C++调用python过程+Anaconda使用arcpy包踩的坑
    C++调python(python文件包含第三方库):工具:VS2017QT5插件PycharmAnaconda1.下载Anaconda,配置一个虚拟环境2.将这个环境里的DLLs和Lib包以及相应py文件,放至C++项目生成.exe文件同级目录下 3.将include和libs放在项目某文件夹下,在VS里添加附加包含目录、附加库目录和附加依赖......