这几天在做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. 项目Alpha冲刺(团队)-第九天冲刺

    格式描述 课程名称:软件工程1916|W(福州大学) 作业要求:项目Alpha冲刺(团队) 团队名称:为了交项目干杯 作业目标:描述第九天冲刺的项目进展.问题困难.心得体会 队员姓名与学号 队员学号 ...

  2. delete properties inside object

  3. 优化mybatis框架中的查询用户记录数的案例

    通过对mybatis框架的中核心接口和类的分析,发现之前写的那个小demo是有问题的.现在对其进行部分优化. 如果存在多个功能的时候,势必会有很多重复的代码,如,创建sqlsession对象,关闭sq ...

  4. [Javascript] Use requestIdleCallback to schedule JavaScript tasks at an optimal time

    JavaScript is single-threaded, which can present some problems when creating an interactive user exp ...

  5. 53、Spark Streaming:输入DStream之Kafka数据源实战

    一.基于Receiver的方式 1.概述 基于Receiver的方式: Receiver是使用Kafka的高层次Consumer API来实现的.receiver从Kafka中获取的数据都是存储在Sp ...

  6. c博客作业—分支,结构顺序

    1展现PTA总分 1 2 2本章学习类容总结 1常量和变量 常量:在运行中其值不变的量被称为常量,常量的类型通常是由书写格式决定,包括整型常量,实数型变量等等. 变量: 在运行中其值可变的量被称为变量 ...

  7. 设置多个className

    有时候我们需要有选择地设置多个className function myComponent(props) { const myClassName = { 'aaa', {'bbb': props.ne ...

  8. Django基础之redirect()

    参数可以是: 一个模型: 将调用模型的get_absolute_url()函数 一个视图, 可以带有函数: 可以使用urlresolvers.reverse来反向解析名称 一个绝对的或相对的URL, ...

  9. nRF51822 硬件复位引脚

    nRF51822 有一个硬件复位引脚和Debug 口SWDIO是共用的,名字叫做nReset. 实现硬件复位是怎样子的: 1.这个引脚引出来, 2.给这个引脚低电平, 3.从低电平拉到高电平,即复位. ...

  10. Spring AOP的实现记录操作日志

    适用场景: 记录接口方法的执行情况,记录相关状态到日志中. 注解类:LogTag.java package com.lichmama.spring.annotation; import java.la ...