参考博客:ffmpeg实现dxva2硬件加速
下载源码:GitHub:https://github.com/Yacov-lu/ffmpeg-DXVA-decode
百度网盘:https://pan.baidu.com/s/1fFm4Ra5ka2bPJeIRig14wA?pwd=qwer
提取码:qwer
该源码下载后,将播放的视频路径(filename)修改为你自己的,便可直接运行。
明显看到使用硬解码后CPU下来了,GPU上去了
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、引入头文件
解决办法:项目-->属性-->VC++目录-->包含目录:$(WindowsSDK_IncludePath);D3D\include;include;$(IncludePath)
参考:warning:4005 DXGI_STATUS_OCCLUDED,宏重定义_superbin的博客-CSDN博客
参考:ffmpeg 的各种声明已被否决,整理_顺其自然~的博客-CSDN博客_被声明为已否决
当我写这博客想复现问题的时候,它居然又不报错了,想不通。。。
调试发现:dxva2_init 返回值 -22,而源码执行发现返回值0;修改添加:
修改后 dxva2_init 返回0 ,便不再执行 sws_getContext 这段代码
打开多媒体数据并且获取一些参数信息
ps:函数调用成功之后处理过的AVFormatContext结构体。
file:打开的视音频流的URL。
fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。
dictionay:附加的一些选项,一般情况下可以设置为NULL。
读取一部分视音频数据并且获得一些相关的信息(AVStream)
ic:输入的AVFormatContext结构体。
options:额外的选项,目前没有深入研究过。
获取音视频对应的流索引(stream_index)及存储编解码器信息的结构体AVCodec
ic:媒体文件句柄
type:流类型:视频、音频、字幕等。
Wanted_stream_nb:用户请求的流号, 或 -1 用于自动选择
related_stream:尝试查找相关的流(例如,在相同的program) 到这个,或者 -1 如果没有
decoder_ret:如果非空,返回解码器选定的流
flags:标志;目前没有定义
返回:音视频对应的流索引(stream_index)
申请AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。
该函数用于将流里面的参数,也就是AVStream里面的参数直接复制到AVCodecContext的上下文当中。
这步骤就是配置硬解码,函数dxva2_init是初始化配置dxva2解码器的入口,配置工作主要就是由它来完成
hwnd:窗口句柄
用于初始化一个视音频编解码器的 AVCodecContext
avctx:需要初始化的 AVCodecContext。
codec:输入的AVCodec。
options:一些选项。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置。
读取码流中的音频若干帧或者视频一帧
s: 文件格式上下文,输入的AVFormatContext
pkt:这个值不能传NULL,必须是一个空间,输出的AVPacket
返回值:return 0 is OK, <0 on error or end of file
发送视频一帧到解码器中。ret=0:成功
从解码器中获取解码的输出数据。ret=0:成功,返回一帧数据
获取数据同时直接渲染
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
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);