这几天在做dxva2硬件加速,找不到什么资料,翻译了一下微软的两篇相关文档。这是第二篇,记录用ffmpeg实现dxva2。

第一篇翻译的Direct3D device manager,链接:http://www.cnblogs.com/betterwgo/p/6124588.html

  第二篇翻译的在DirectShow中支持DXVA 2.0,链接:http://www.cnblogs.com/betterwgo/p/6125351.html

  在做dxva2的过程中,参考了许多网上的代码,这些代码又多参考VLC和ffmpeg的例子。

1.ffmpeg支持dxva2硬件加速的格式

  当前我所使用的ffmpeg的版本是3.2,支持dxva2硬件加速的有以下几种文件格式: AV_CODEC_ID_MPEG2VIDEO、AV_CODEC_ID_H264、AV_CODEC_ID_VC1、AV_CODEC_ID_WMV3、AV_CODEC_ID_HEVC、AV_CODEC_ID_VP9。ffmpeg识别为这几种格式的文件都可以尝试使用dxva2做硬件加速。但这并不代表是这几种格式的文件就一定支持dxva2硬件加速,因为我就遇到了一个AV_CODEC_ID_HEVC文件在初始化配置dxva2的过程中会失败,PotPlayer在播放这个文件时也不能用dxva2硬件加速。
2.一些要注意的地方

  (1)ffmpeg只实现了dxva2硬件解码的内容。我所翻译的第一篇、第二篇文章的那部分内容除了解码部分,都要由用户自己去实现。这一块颇有一点复杂,不过不用担心,VLC和ffmpeg都有例子可以参考。这一部分的内容需要对以上两篇翻译的内容有所了解才能比较好的理解代码的逻辑。

  (2)要想真正看到硬件加速的效果,解码后的数据不建议再copy到内存中用CPU进行处理。我一开始就是因为拷贝到吧解码后的数据又copy回内存导致不仅gpu的使用率看不到明显变化,而且CPU的使用率相对于不使用dxva2反而提高了。后来我修改为把解码后的数据直接显示出来,GPU使用率一下子就上去了,CPU使用率也降下来了。
3.关键代码

  由于网上已经有从ffmpeg的例子中分离出来的配置dxva2解码器的代码,所以具体实现起来也相当简单。

(1)头文件ffmpeg_dxva2.h

/*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ #ifndef FFMPEG_DXVA2_H
#define FFMPEG_DXVA2_H //#include "windows.h" extern "C"{
#include "libavcodec/avcodec.h"
#include "libavutil/pixfmt.h"
#include "libavutil/rational.h"
} enum HWAccelID {
HWACCEL_NONE = 0,
HWACCEL_AUTO,
HWACCEL_VDPAU,
HWACCEL_DXVA2,
HWACCEL_VDA,
HWACCEL_VIDEOTOOLBOX,
HWACCEL_QSV,
}; typedef struct AVStream AVStream;
typedef struct AVCodecContext AVCodecContext;
typedef struct AVCodec AVCodec;
typedef struct AVFrame AVFrame;
typedef struct AVDictionary AVDictionary; typedef struct InputStream {
int file_index;
AVStream *st;
int discard; /* true if stream data should be discarded */
int user_set_discard;
int decoding_needed; /* non zero if the packets must be decoded in 'raw_fifo', see DECODING_FOR_* */
#define DECODING_FOR_OST 1
#define DECODING_FOR_FILTER 2 AVCodecContext *dec_ctx;
AVCodec *dec;
AVFrame *decoded_frame;
AVFrame *filter_frame; /* a ref of decoded_frame, to be sent to filters */ int64_t start; /* time when read started */
/* predicted dts of the next packet read for this stream or (when there are
* several frames in a packet) of the next frame in current packet (in AV_TIME_BASE units) */
int64_t next_dts;
int64_t dts; ///< dts of the last packet read for this stream (in AV_TIME_BASE units) int64_t next_pts; ///< synthetic pts for the next decode frame (in AV_TIME_BASE units)
int64_t pts; ///< current pts of the decoded frame (in AV_TIME_BASE units)
int wrap_correction_done; int64_t filter_in_rescale_delta_last; int64_t min_pts; /* pts with the smallest value in a current stream */
int64_t max_pts; /* pts with the higher value in a current stream */
int64_t nb_samples; /* number of samples in the last decoded audio frame before looping */ double ts_scale;
int saw_first_ts;
int showed_multi_packet_warning;
AVDictionary *decoder_opts;
AVRational framerate; /* framerate forced with -r */
int top_field_first;
int guess_layout_max; int autorotate;
int resample_height;
int resample_width;
int resample_pix_fmt; int resample_sample_fmt;
int resample_sample_rate;
int resample_channels;
uint64_t resample_channel_layout; int fix_sub_duration;
struct { /* previous decoded subtitle and related variables */
int got_output;
int ret;
AVSubtitle subtitle;
} prev_sub; struct sub2video {
int64_t last_pts;
int64_t end_pts;
AVFrame *frame;
int w, h;
} sub2video; int dr1; /* decoded data from this stream goes into all those filters
* currently video and audio only */
//InputFilter **filters;
//int nb_filters; //int reinit_filters; /* hwaccel options */
enum HWAccelID hwaccel_id;
char *hwaccel_device; /* hwaccel context */
enum HWAccelID active_hwaccel_id;
void *hwaccel_ctx;
void(*hwaccel_uninit)(AVCodecContext *s);
int(*hwaccel_get_buffer)(AVCodecContext *s, AVFrame *frame, int flags);
int(*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame);
enum AVPixelFormat hwaccel_pix_fmt;
enum AVPixelFormat hwaccel_retrieved_pix_fmt; /* stats */
// combined size of all the packets read
uint64_t data_size;
/* number of packets successfully read for this stream */
uint64_t nb_packets;
// number of frames/samples retrieved from the decoder
uint64_t frames_decoded;
uint64_t samples_decoded;
} InputStream; int dxva2_init(AVCodecContext *s, HWND hwnd);
int dxva2_retrieve_data_call(AVCodecContext *s, AVFrame *frame); #endif /* FFMPEG_DXVA2_H */

  以上代码其实是从ffmpeg中抽出来的。HWAccelID为硬件加速器的ID,在初始化配置解码器的时候会用到,我们实际用的是HWACCEL_DXVA2。InputStream这个结构体水很深,包含了一些在初始化配置中会用到的数据,还包含了一些函数指针,注意这些函数指针的使用。我要说的其实是以下两个函数:

int dxva2_init(AVCodecContext *s, HWND hwnd);
int dxva2_retrieve_data_call(AVCodecContext *s, AVFrame *frame);

  函数dxva2_init是初始化配置dxva2解码器的入口,配置工作主要就是由它来完成。在文章最后我会上传整个工程的源码。前两篇翻译的文章的内容几乎都是为它服务的,我上传的源码中的ffmpeg_dxva2.cpp主要就是为了做这一部分工作,当然dxva2_retrieve_data_call也包含在了其中。要想看懂dxva2_init函数的逻辑,你最好看看前面两篇翻译的内容,另外你还需要懂一点D3D渲染的基本知识。

  函数dxva2_retrieve_data_call用来获得解码后的数据的。如我前面所说,如果不必要,最后不要再把它copy出来,直接用D3D绘制出来就行了,把数据从GPU再copy到内存中会极大的降低GPU的使用率,在我的试验中这样做完全没达到GPU加速的目的,反而是CPU的使用率增高了。所以你在我上传的源码中看到的是直接绘制数据。

static int dxva2_retrieve_data(AVCodecContext *s, AVFrame *frame)
{
LPDIRECT3DSURFACE9 surface = (LPDIRECT3DSURFACE9)frame->data[3];
InputStream *ist = (InputStream *)s->opaque;
DXVA2Context *ctx = (DXVA2Context *)ist->hwaccel_ctx; EnterCriticalSection(&cs);
//直接渲染
ctx->d3d9device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
ctx->d3d9device->BeginScene();
if (m_pBackBuffer)
{
m_pBackBuffer->Release();
m_pBackBuffer = NULL;
}
ctx->d3d9device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &m_pBackBuffer);
GetClientRect(d3dpp.hDeviceWindow, &m_rtViewport);
ctx->d3d9device->StretchRect(surface, NULL, m_pBackBuffer, &m_rtViewport, D3DTEXF_LINEAR);
ctx->d3d9device->EndScene();
ctx->d3d9device->Present(NULL, NULL, NULL, NULL); LeaveCriticalSection(&cs); return 0;
}

(2)实现

  有了ffmpeg_dxva2.h和ffmpeg_dxva2.cpp这两个文件后实现起来就非常简单了。

 主流程中配置dxva2部分的代码:

switch (codec->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:
{
codecctx->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 = codec;
ist->dec_ctx = codecctx; codecctx->opaque = ist;
if (dxva2_init(codecctx, hWnd) == 0)
{
codecctx->get_buffer2 = ist->hwaccel_get_buffer;
codecctx->get_format = GetHwFormat;
codecctx->thread_safe_callbacks = 1; break;
} bAccel = false;
break;
}
default:
bAccel = false;
break;
}

可以看出其中主要就是调用dxva2_init函数。

解码并渲染的代码:

if (pkt.stream_index == videoindex)
{
int got_picture = 0; DWORD t_start = GetTickCount();
int bytes_used = avcodec_decode_video2(codecctx, picture, &got_picture, &pkt);
if (got_picture)
{
if (bAccel)
{
//获取数据同时渲染
dxva2_retrieve_data_call(codecctx, picture); DWORD t_end = GetTickCount();
printf("dxva2 time using: %lu\n", t_end - t_start);
}
else
{
//非dxva2情形
if (img_convert_ctx &&pFrameBGR && out_buffer)
{
//转换数据并渲染
sws_scale(img_convert_ctx, (const uint8_t* const*)picture->data, picture->linesize, 0, codecctx->height, pFrameBGR->data, pFrameBGR->linesize);
m_D3DVidRender.Render_YUV(out_buffer, picture->width, picture->height); DWORD t_end = GetTickCount();
printf("normal time using: %lu\n", t_end - t_start);
}
} count++;
} av_packet_unref(&pkt);
}

在dxva2_init函数中其实已经对D3D的渲染进行了配置,所以只需要穿进去窗口句柄,然后调用dxva2_retrieve_data_call函数就可以直接把数据绘制在句柄所对应得窗口上。

源码:http://download.csdn.net/download/qq_33892166/9698473

工程基于VS2013,需要对ffmpeg有一定了解,对D3D也要有一定的了解。注意在代码中修改要播放的视频的路径,否则控制台退出不正常,VS会卡死的,我也是刚发现有这个问题。最后自己修改一下控制台的代码。直接把调出控制台的代码注释掉也可以正常运行,不过就看不到调试信息了。

--------------------------------------------------------------2017.3.5 更新---------------------------------------------

本次更新只为解答一个问的人比较多的问题:如何在CPU占用不变的情况下,把硬解的数据再copy回显卡?

我的建议:最好不要这样做。如果你要对硬解出来的数据做进一步处理,我建议直接在显卡上进行,GPU并行计算对于做某些图像处理速度比CPU快好多。如果你渲染用的OpenGL,你可以用GLSL写shader;如果你渲染用的D3D,你可以用HLSL写shader;如果你渲染用的D3D11,compute shader将有可能完成你要求得更为复杂的图像处理(compute shader没用过,只看了点介绍,可能不确实);如果你能用CUDA,不要犹豫;如果你不能用CUDA,我推荐你OpenCL。如果以上都不行,我知识面也有限,也没有什么好办法。因为硬解出来的数据很大,从显存copy到内存必然很耗时间和CPU,而且从时间消耗上来看,从显存copy回内存的时间比硬解本身所花费的时间大好多,所以依我狭窄的知识面来看,如果你一定要copy回内存,我建议你干脆放弃硬解。

【视频开发】ffmpeg实现dxva2硬件加速的更多相关文章

  1. ffmpeg实现dxva2硬件加速

    这几天在做dxva2硬件加速,找不到什么资料,翻译了一下微软的两篇相关文档.这是第二篇,记录用ffmpeg实现dxva2. 第一篇翻译的Direct3D device manager,链接:http: ...

  2. 使用C#+FFmpeg+DirectX+dxva2硬件解码播放h264流

    本文门槛较高,因此行文看起来会乱一些,如果你看到某处能会心一笑请马上联系我开始摆龙门阵 如果你跟随这篇文章实现了播放器,那你会得到一个高效率,低cpu占用(单路720p视频解码播放占用1%左右cpu) ...

  3. FFmpeg再学习 -- 硬件加速编解码

    为了搞硬件加速编解码,用了一周时间来看 CUDA,接下来开始加以总结. 一.什么是 CUDA (1)首先需要了解一下,什么是 CUDA. 参看:百度百科 -- CUDA 参看:CUDA基础介绍 参看: ...

  4. 音视频开发-FFmpeg

    音视频开发是个非常复杂的,庞大的开发话题,初涉其中,先看一下结合 OEIP(开源项目) 新增例子. 可以打开flv,mp4类型文件,以及rtmp协议音视频数据,声音的播放使用SDL. 把采集的麦/声卡 ...

  5. 是否应该开启WebView硬件加速?

    android webview 在3.0+后显示flash要启用硬件加速,开启硬件加速也能够使绘制更加平滑. 开启硬件加速是在manifest中加入: android:hardwareAccelera ...

  6. 【并行计算-CUDA开发】【视频开发】ffmpeg Nvidia硬件加速总结

    2017年5月25日 0. 概述 FFmpeg可通过Nvidia的GPU进行加速,其中高层接口是通过Video Codec SDK来实现GPU资源的调用.Video Codec SDK包含完整的的高性 ...

  7. 【视频开发】【CUDA开发】FFMPEG硬件加速-nvidia方案

    1.目标 <1>显卡性能参数: <2>方案可行性: 2.平台信息 2.1.查看当前显卡信息 命令:  lspci |grep VGA  信息:  01:00.0 VGA com ...

  8. 【ARM-Linux开发】【CUDA开发】【视频开发】关于Linux下利用GPU对视频进行硬件加速转码的方案

    最近一直在研究Linux下利用GPU进行硬件加速转码的方案,折腾了很久,至今没有找到比较理想的硬加速转码方案.似乎网上讨论这一方案的文章也特别少,这个过程中也进行了各种尝试,遇到很多具体问题,以下便对 ...

  9. 【并行计算与CUDA开发】英伟达硬件加速编解码

    硬件加速 并行计算 OpenCL OpenCL API VS SDK 英伟达硬件编解码方案 基于 OpenCL 的 API 自己写一个编解码器 使用 SDK 中的编解码接口 使用编码器对于 OpenC ...

随机推荐

  1. switch表达式可使用的类型

    在java中switch后的表达式的类型只能为以下几种:byte.short.char.int(在Java1.6中是这样),在java1.7后支持了对string的判断.

  2. Python 生成 JWT(json web token) 及 解析方式

    一.关于 jwt 的原理及概念可以自行在网络上搜索了解一下,这里推荐一篇写的比较好的博客 深入了解Json Web Token之概念篇 另附 JWT 的官方文档: https://jwt.io/int ...

  3. 《团队作业第三、四周》五阿哥小组Scrum 冲刺阶段---Day4

    <团队作业第三.四周>五阿哥小组Scrum 冲刺阶段---Day3 一.项目燃尽图 二.项目进展 20182310周烔今日进展: 主要任务一览:聊天软件主界面 20182330魏冰妍今日进 ...

  4. python - django 实现文件下载功能

    使用 Django 搭建的网站中添加一个可以从服务器中下载文件的功能 (此处演示一个从网站中下载API文档的例子供参考) # 一.url  urlpatterns = [ # 下载 API 接口文档 ...

  5. LeetCode 732. My Calendar III

    原题链接在这里:https://leetcode.com/problems/my-calendar-iii/ 题目: Implement a MyCalendarThree class to stor ...

  6. 对生成对抗网络GANs原理、实现过程、应用场景的理解(附代码),另附:深度学习大神文章列表

    https://blog.csdn.net/love666666shen/article/details/75522489 https://blog.csdn.net/yangdelong/artic ...

  7. 机器学习---三种线性算法的比较(线性回归,感知机,逻辑回归)(Machine Learning Linear Regression Perceptron Logistic Regression Comparison)

    最小二乘线性回归,感知机,逻辑回归的比较:   最小二乘线性回归 Least Squares Linear Regression 感知机 Perceptron 二分类逻辑回归 Binary Logis ...

  8. 洛谷P3147 262144

    题目 此题数据范围小的话可以用区间\(DP\),但是该题目的数据范围并不能用区间DP来求解,因此我们考虑优化\(DP\). 每个数的生成一定是由这两个区间 考虑区间DP的弊端是并不知道每个数生成的区间 ...

  9. DACL原理.控制文件的访问权限(文件,注册表.目录.等任何带有安全属性的对象.)

    目录 一丶简介 1.DACL是什么. 2.如何创建一个自己控制的文件. 3.SDDL是个什么鬼. 二丶 编写SDDL 控制的文件 一丶简介 1.DACL是什么. DACL称为自主访问的控制列表.是应用 ...

  10. CSS — BEM 命名规范

    推荐阅读: https://juejin.im/post/5b925e616fb9a05cdd2ce70d 1 什么是 BEM 命名规范 Bem 是块(block).元素(element).修饰符(m ...