这几天在做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 = ,
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[];
InputStream *ist = (InputStream *)s->opaque;
DXVA2Context *ctx = (DXVA2Context *)ist->hwaccel_ctx; EnterCriticalSection(&cs);
//直接渲染
ctx->d3d9device->Clear(, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(, , ), 1.0f, );
ctx->d3d9device->BeginScene();
if (m_pBackBuffer)
{
m_pBackBuffer->Release();
m_pBackBuffer = NULL;
}
ctx->d3d9device->GetBackBuffer(, , 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 ;
}

(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 = ; // 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) == )
{
codecctx->get_buffer2 = ist->hwaccel_get_buffer;
codecctx->get_format = GetHwFormat;
codecctx->thread_safe_callbacks = ; break;
} bAccel = false;
break;
}
default:
bAccel = false;
break;
}

可以看出其中主要就是调用dxva2_init函数。
解码并渲染的代码:

if (pkt.stream_index == videoindex)
{
int got_picture = ; 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, , 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转码使用硬件加速

    需求源于手机拍摄的视频,默认参数码率较大,拍摄的文件体积较大,不便于保存和转发.手机默认拍照的720P视频,默认码率达到4M,实际上转成1M就差不多了.FFmpeg默认的转码是使用软件解码,然后软件编 ...

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

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

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

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

  7. 基于FFmpeg的Dxva2硬解码及Direct3D显示(四)

    初始化硬解码上下文 目录 初始化硬解码上下文 创建解码数据缓冲区 创建IDirectXVideoDecoder视频解码器 设置硬解码上下文 解码回调函数 创建解码数据缓冲区 这一步为了得到 LPDIR ...

  8. Chrome/Chromium HTML5 video 视频播放硬件加速

    Chromium站点上有个大致的框图.描写叙述了Chromium的video在各个平台 - 包含Android - 上是怎样使用硬件资源来做视频编解码加速的: 而依据Android Kitkat上的C ...

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

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

随机推荐

  1. [ASP.NET MVC 小牛之路]04 - 依赖注入(DI)和Ninject

    本人博客已转移至:http://www.exblr.com/liam  为什么需要依赖注入 在[ASP.NET MVC 小牛之路]系列的理解MVC模式文章中,我们提到MVC的一个重要特征是关注点分离( ...

  2. [ASP.NET MVC 小牛之路]09 - Controller 和 Action (1)

    我们知道,在 MVC 中每个请求都会提交到 Controller 进行处理.Controller 是和请求密切相关的,它包含了对请求的逻辑处理,能对 Model 进行操作并选择 View 呈现给用户, ...

  3. EF:根据实体类生成表结构SQL

    根据实体类生成表结构SQL: PM> Enable-Migrations -ProjectName Domain -StartUpProjectName Handler -Force PM> ...

  4. Security8:删除Role 和 User

    数据库的Role 和 User都是基于Specified DB的,在删除这些Principal之前,必须使用Use clause,切换到指定的DB中. sys.database_role_member ...

  5. 网站初步收工---www.dkill.net

    今天10.30左右备案核审成功了,然后一天都在忙部署和一些其他的东西,中途也写了很多文档,遇到很多问题,直接琢磨了N久,暂时发了这么多教程,明天揭露阿里云的各种坑(先用winServer服务器,有时间 ...

  6. SQL Server中使用Check约束提升性能

        在SQL Server中,SQL语句的执行是依赖查询优化器生成的执行计划,而执行计划的好坏直接关乎执行性能.     在查询优化器生成执行计划过程中,需要参考元数据来尽可能生成高效的执行计划, ...

  7. 在SQL Server中将数据导出为XML和Json

        有时候需要一次性将SQL Server中的数据导出给其他部门的也许进行关联或分析,这种需求对于SSIS来说当然是非常简单,但很多时候仅仅需要一次性导出这些数据而建立一个SSIS包就显得小题大做 ...

  8. 一起学微软Power BI系列-官方文档-入门指南(4)Power BI的可视化

    在前面的系列文章中,我们介绍了官方有关获取数据,以及建模的原始文档和基本介绍.今天继续给大家介绍官方文档中,有关可视化的内容.实际上获获取数据和建模更注重业务关系的处理,而可视化则关注对数据的解读.这 ...

  9. Sublime Text3下的markdown插件的安装及配置

    Sublime Text3下的markdown插件的安装及配置 安装准备--安装Package Control 安装MarkdownEditing 安装Markdown Preview或OmniMar ...

  10. Spring笔记--xml配置文件详解

    1:bean的基本属性配置: <!-- id是bean的标识符,必须唯一,如果没有配置id,name默认为标识符 如果配置了id,有配置了name,那么name为别名 name可以设置多个别名, ...