C++ ffmpeg+dxva2实现硬解码「建议收藏」

C++ ffmpeg+dxva2实现硬解码「建议收藏」0.前言参考博客:ffmpeg实现dxva2硬件加速下载源码:GitHub:https://github.com/Yacov-lu/ffmpeg-DXVA-decode百度网盘:https://pan.baidu.com/s/1fFm4Ra5ka2bPJeIRig14wA?pwd=qwer提取码:qwer该源码下载后,将播放的视频路径(filename)修改为你自己的,便可直接运行。1…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

0.前言

参考博客:ffmpeg实现dxva2硬件加速
下载源码:GitHub:https://github.com/Yacov-lu/ffmpeg-DXVA-decode
                  百度网盘:https://pan.baidu.com/s/1fFm4Ra5ka2bPJeIRig14wA?pwd=qwer 
                                   提取码:qwer
该源码下载后,将播放的视频路径(filename)修改为你自己的,便可直接运行。

1.实现效果

C++ ffmpeg+dxva2实现硬解码「建议收藏」

C++ ffmpeg+dxva2实现硬解码「建议收藏」

 明显看到使用硬解码后CPU下来了,GPU上去了

2.实际使用步骤

2.1、新建程序并配置

1、重新创建一个MFC的程序 MFCApplication2(基于对话框):
        将源码中的文件夹《D3D》《include》《lib》、文件《D3DVidRender.h》《D3DVidRender.cpp》《ffmpeg_dxva2.h》《ffmpeg_dxva2.cpp》、还有《Debug》文件中的《avcodec-57.dll》《avdevice-57.dll》《avfilter-6.dll》《avformat-57.dll》《avutil-55.dll》《postproc-54.dll》《swresample-2.dll》《swscale-4.dll》拷贝到新建的程序文件中。
2、配置项目环境:
        项目–>属性–>VC++目录–>包含目录:D3D\include;include
        项目–>属性–>VC++目录–>库目录:D3D\lib;lib
        项目–>属性–>链接器–>输入–>附加依赖项:avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;swscale.lib;swresample.lib;SDL2.lib;SDL2main.lib;postproc.lib
3、编写步骤:
        3.1、在对话框中拖了一个PictureControl控件【想让视频在该控件中展示】
        3.2、添加一个全局变量:HWND g_hwWnd;
                在OnInitDialog方法中:g_hwWnd1 = GetDlgItem(IDC_STATIC)->m_hWnd;
        3.3、将源码中的 “GetHwFormat”,“ThreadProc” 两个方法拷贝到CMFCApplication2Dlg.cpp文件中
        3.4、引入头文件

2.2、生成时出现问题

2.2.1、问题1:error C2061: 语法错误: 标识符“DXGI_JPEG_AC_HUFFMAN_TABLE”

解决办法:项目–>属性–>VC++目录–>包含目录:$(WindowsSDK_IncludePath);D3D\include;include;$(IncludePath)
参考:warning:4005 DXGI_STATUS_OCCLUDED,宏重定义_superbin的博客-CSDN博客

2.2.2、问题2:’avcodec_decode_video2′: 被声明为已否决 

参考:ffmpeg 的各种声明已被否决,整理_顺其自然~的博客-CSDN博客_被声明为已否决
当我写这博客想复现问题的时候,它居然又不报错了,想不通。。。

2.2.3、问题3:执行到sws_getContext异常退出

调试发现:dxva2_init 返回值 -22,而源码执行发现返回值0;修改添加:C++ ffmpeg+dxva2实现硬解码「建议收藏」

修改后 dxva2_init 返回0 ,便不再执行 sws_getContext 这段代码

2.3、本程序解码流程

2.3.1、int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);

打开多媒体数据并且获取一些参数信息
ps:函数调用成功之后处理过的AVFormatContext结构体。
file:打开的视音频流的URL。
fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。
dictionay:附加的一些选项,一般情况下可以设置为NULL。

2.3.2、int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

读取一部分视音频数据并且获得一些相关的信息(AVStream)
ic:输入的AVFormatContext结构体。
options:额外的选项,目前没有深入研究过。

2.3.3、int av_find_best_stream(AVFormatContext *ic,
                        enum AVMediaType type,
                        int wanted_stream_nb,
                        int related_stream,
                        AVCodec **decoder_ret,
                        int flags);

获取音视频对应的流索引(stream_index)及存储编解码器信息的结构体AVCodec
ic:媒体文件句柄
type:流类型:视频、音频、字幕等。
Wanted_stream_nb:用户请求的流号, 或 -1 用于自动选择
related_stream:尝试查找相关的流(例如,在相同的program) 到这个,或者 -1 如果没有
decoder_ret:如果非空,返回解码器选定的流
flags:标志;目前没有定义
返回:音视频对应的流索引(stream_index)

2.3.4、AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

申请AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。

2.3.5、int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par);

该函数用于将流里面的参数,也就是AVStream里面的参数直接复制到AVCodecContext的上下文当中。

2.3.6、int dxva2_init(AVCodecContext *s, HWND hwnd);

这步骤就是配置硬解码,函数dxva2_init是初始化配置dxva2解码器的入口,配置工作主要就是由它来完成
hwnd:窗口句柄

2.3.7、int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

用于初始化一个视音频编解码器的 AVCodecContext
avctx:需要初始化的 AVCodecContext。
codec:输入的AVCodec。
options:一些选项。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置。

2.3.8、int av_read_frame(AVFormatContext *s, AVPacket *pkt);

读取码流中的音频若干帧或者视频一帧
s: 文件格式上下文,输入的AVFormatContext
pkt:这个值不能传NULL,必须是一个空间,输出的AVPacket
返回值:return 0 is OK, <0 on error or end of file

2.3.9、int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

发送视频一帧到解码器中。ret=0:成功

2.3.10、int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

从解码器中获取解码的输出数据。ret=0:成功,返回一帧数据

2.3.11、int dxva2_retrieve_data_call(AVCodecContext *s, AVFrame *frame);

获取数据同时直接渲染

2.3.12、释放资源

av_packet_unref(AVPacket *pkt);   参考讲解:不释放会造成内存泄露
avcodec_flush_buffers(AVCodecContext *avctx);    在再次解码之前,必须重新编码。
avcodec_close(AVCodecContext *avctx);    参考讲解:关闭编码器
avcodec_free_context(AVCodecContext **avctx); 释放解码器上下文,包含了avcodec_close()
av_frame_free(AVFrame **frame);   参考讲解:不释放会造成内存泄露
avformat_close_input(AVFormatContext **s); 参考讲解:关闭AVFormatContext

3.主要代码 

    char* url = "D:\\GoogleDownload\\video-h265.mkv";
    int ret;
    /******************获取码流参数信息******************/
    AVFormatContext* fmt_ctx = NULL;    //包含码流参数较多的结构体
   
    /***
    * 打开多媒体数据并且获取一些参数信息
    * ps:函数调用成功之后处理过的AVFormatContext结构体。
    * file:打开的视音频流的URL。
    * fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。
    * dictionay:附加的一些选项,一般情况下可以设置为NULL。
    **/
    ret = avformat_open_input(&fmt_ctx, url, NULL, NULL);   ///1.
    if (ret < 0)
    {
        fprintf(stderr, "Could not open input\n");
        goto end;
    }
    /***
    * 读取一部分视音频数据并且获得一些相关的信息(AVStream)
    * ic:输入的AVFormatContext。
    * options:额外的选项,目前没有深入研究过。
    **/
    ret = avformat_find_stream_info(fmt_ctx, NULL);         ///2.
    if (ret < 0)
    {
        fprintf(stderr, "Could not find stream information\n");
        goto end;
    }


    AVCodec* pCodec = NULL;             // 存储编解码器的结构体
    /***
    * 获取音视频对应的流索引(stream_index)
    **/
    ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0);    ///3.
    if (ret < 0)
    {
        fprintf(stderr, "Cannot find a video stream in the input file\n");
        goto end;
    }
    int video_stream = ret;             // 视频对应流索引

   

    ///***************获取解码器并解码*************************/
    if (pCodec == NULL)
        pCodec = avcodec_find_decoder(fmt_ctx->streams[video_stream]->codecpar->codec_id);// 通过ID号查找解码器


    AVCodecContext* pCodecCtx = NULL;   // 解码器上下文       
    pCodecCtx = avcodec_alloc_context3(pCodec);           ///4.                                // 配置解码器,申请AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。
    avcodec_parameters_to_context(pCodecCtx, fmt_ctx->streams[video_stream]->codecpar);   ///5.// 该函数用于将流里面的参数,也就是AVStream里面的参数直接复制到AVCodecContext的上下文当中。
  

    配置硬解码    
    switch (pCodec->id)
    {
        case AV_CODEC_ID_MPEG2VIDEO:
        case AV_CODEC_ID_H264:
        case AV_CODEC_ID_VC1:
        case AV_CODEC_ID_WMV3:
        case AV_CODEC_ID_HEVC:
        case AV_CODEC_ID_VP9:
        {
            pCodecCtx->thread_count = 1;  // Multithreading is apparently not compatible with hardware decoding
            InputStream* ist = new InputStream();
            ist->hwaccel_id = HWACCEL_AUTO;
            ist->active_hwaccel_id = HWACCEL_AUTO;
            ist->hwaccel_device = "dxva2";
            ist->dec = pCodec;
            ist->dec_ctx = pCodecCtx;         

            pCodecCtx->coded_width = pCodecCtx->width;
            pCodecCtx->coded_height = pCodecCtx->height;

            pCodecCtx->opaque = ist;
            if (dxva2_init(pCodecCtx, g_hwWnd1) == 0)    ///6.
            {
                pCodecCtx->get_buffer2 = ist->hwaccel_get_buffer;
                pCodecCtx->get_format = GetHwFormat;
                pCodecCtx->thread_safe_callbacks = 1;

                break;
            }

            break;
        }
        default:
            break;
    }
    
    ret = avcodec_open2(pCodecCtx, pCodec, NULL);           ///7.                         // 初始化一个视音频编解码器的AVCodecContext
    if (ret < 0)
    {
        fprintf(stderr, "Cannot open decode\n");
        goto end;
    }

    AVPacket packet;                    // 解码前的音频或者视频数据
    AVFrame* frame = av_frame_alloc();  // 用来存储解码后的(或原始)音频或视频数据
                                        // 必须由av_frame_alloc()分配内存,同时必须由av_frame_free()释放
    while (m_threadLoop)    //循环读取
    {    

        if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)   //读取码流中的音频若干帧或者视频一帧
        {
            //av_read_frame 会产生 6MB 的堆内存。
            //如果不进行 av_packet_unref,则会导致内存泄漏。
            //即使是同一个栈变量 pkt,即使出了这个栈变量的作用范围、这个栈变量被系统收回,那些每次产生的 6MB 堆内存们,也不会被收回。
           
            break;
        }
        if (video_stream == packet.stream_index)
        {
            ret = avcodec_send_packet(pCodecCtx, &packet); //发送视频一帧到解码器中
            if (ret < 0)
            {
                av_packet_unref(&packet);           // 将缓存空间的引用计数-1,并将Packet中的其他字段设为初始值。如果引用计数为0,自动的释放缓存空间。
                avcodec_flush_buffers(pCodecCtx);   // 清空内部缓存的帧数据
                continue;
            }

            while (ret >= 0)
            {
                ret = avcodec_receive_frame(pCodecCtx, frame);    // 从解码器中获取解码的输出数据。ret=0:成功,返回一帧数据
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) // AVERROR(EAGAIN):当前输出无效,用户必须发送新的输入,AVERROR_EOF:解码器已经完全刷新,当前没有多余的帧可以输出
                {
                    break;
                } 
                else if (ret < 0)                                 // 对应其他的解码错误
                {
                    break;
                }


                //获取数据同时渲染
                dxva2_retrieve_data_call(pCodecCtx, frame);
                
                Sleep(30u);
                av_packet_unref(&packet);

            }
        }
        av_packet_unref(&packet);
    }

    av_packet_unref(&packet);

    avcodec_flush_buffers(pCodecCtx);
    avcodec_close(pCodecCtx);  //close,如果为rk3399的硬件编解码,则需要等待MPP_Buff释放完成后再关闭?是否需要这样不知道

end:
    av_frame_free(&frame);
    avformat_close_input(&fmt_ctx);
    avcodec_free_context(&pCodecCtx);

Jetbrains全家桶1年46,售后保障稳定

4.源码下载

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:http://www.javaforall.cn/234716.html原文链接:http://www.javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 硬件设计——外围电路(电源电路)[通俗易懂]

    硬件设计——外围电路(电源电路)[通俗易懂]引言 当我们设计一个完整的电路而言,我们除了要知道我们要设计的主芯片电路,如FPGA,DSP,还要知道一些外围电路,如电源电路,复位电路、晶振电路等等。这篇文章我们先来讲解一下对于如何设计一个电源电路。 正文 首先我们查询主芯片的datesheet,根据datesheet,可知主芯片采用多大的电压才能正常工作,然后我们根据其设计电源电路。每个电子设备都有一个供给能量的电源电路。电源电路有整流电源、逆变电源和变频器三种。常见的家用电器中多数要用到直流电源。直流电源的最简单的供电方法是用电池。..

  • 大数据采集平台之ZDH_SERVER部署

    大数据采集平台之ZDH_SERVER部署目录项目源码下载源码打包部署运行项目源码数据采集平台管理端https://github.com/zhaoyachao/zdh_web数据采集平台服务https://github.com/zhaoyachao/zdh_serverweb端在线查看http://zycblog.cn:8081/login用户名:zyc密码:123456界面只是为了参考功能,底层的数据采集服务需要自己下载zdh_server部署,服务器资源有限,请手下留情如.

  • 321_MediaType Media Type 是什么

    321_MediaType Media Type 是什么MediaType是什么MediaType在网络协议的消息头里面叫做Content-Type使用两部分的标识符来确定一个类型所以我们用的时候其实就是为了表明我们传的东西是什么类型比如application/json:JSON格式的数据,在RFC4627中定义application/javascript:JavaScript,在RFC4329中定义但是…

  • Centos下添加用户到用户组

    Centos下添加用户到用户组

    2021年10月23日
  • eclipse代码补全、代码提示及防空格自动补全

    eclipse代码补全、代码提示及防空格自动补全最近学了下eclipse编写java代码时可以自动提示并且解决了空格自动补全的苦恼问题,现在会了这个感觉很好,决定给大家分享下。打开eclipse依次点击Window–&gt;Perferences–&gt;Java–&gt;Editor–&gt;ContentAssist,  在【AutoactivationtriggersforJava:】选项后的文本框中会看…

  • GB28181服务器_GB28181收费吗

    GB28181服务器_GB28181收费吗CarEye开发GB28181服务器有将近两年时间了,早期我们用纯C++开发了一个GB28181视频服务期,对外的接口是基于MQ协议的。这样开发出来的服务器主要有几个问题。1.SIP服务器和流媒体服务器是绑定在一个进程中的,因为没有分离,造成了视频处理和SIP服务器只能在一台服务器上运行,既不能打到GB28181协议的构架要求。也造成无法使用负载均衡的功能。2.对外接口采用了MQ通信方式。虽然MQ消息处理实时,对一些数据处理,如报警,对讲等。但MQ本身是重量级构建,不方便一些应用场景快速构建

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号