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

2022-11-16 18:11:50 浏览数 (1)

0.前言

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

1.实现效果

明显看到使用硬解码后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 目录–>包含目录:D3Dinclude;include 项目–>属性–>VC 目录–>库目录:D3Dlib;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 目录–>包含目录:

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

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

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

调试发现:dxva2_init 返回值 -22,而源码执行发现返回值0;修改添加:

修改后 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.主要代码

代码语言:javascript复制
    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 inputn");
goto end;
}
/***
* 读取一部分视音频数据并且获得一些相关的信息(AVStream)
* ic:输入的AVFormatContext。
* options:额外的选项,目前没有深入研究过。
**/
ret = avformat_find_stream_info(fmt_ctx, NULL);         ///2.
if (ret < 0)
{
fprintf(stderr, "Could not find stream informationn");
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 filen");
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 decoden");
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.源码下载

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

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/234716.html原文链接:https://javaforall.cn

0 人点赞