0
点赞
收藏
分享

微信扫一扫

C# wpf 使用d3d直接渲染dxva2解码数据

朱悟能_9ad4 2022-04-17 阅读 37

WPF视频渲染系列

第一章 使用HwndHost渲染视频
第二章 使用d3d渲染视频
第三章 使用d3d渲染dxva2数据(本章)
第四章 使用WriteableBitmap渲染视频
第五章 使用ffmpeg(ffplay)实现播放器


文章目录


前言

使用dxva2解码渲染的方案是有的,通过句柄关联d3d对象的方式直接渲染,性能相当好,但是在wpf中显然不太合适,嵌入hwnd窗口与wpf绘制不兼容,而且对于键盘事件也会有影响,最好的方式还是使用d3d渲染,本文主要讲述如何将dxva2解码的数据不经过转换,直接渲染到wpf的image上。


一、对象说明

1.dxva2解码Surface

对于dxva2解码,以ffmpeg使用dxva2为例,解码后的数据放在AVFrame.data[3]里面,是一个d3d9的Surface对象,里面装载着视频数据,数据格式通常为nv12。
下面是C#代码示例。

Play_VideoDisplay(IntPtr play, IntPtr[] data, int[] linesize, int width, int height, ACDll.ac_pixFormat format)
{
 var surface = Surface.FromPointer<Surface>(data[3]);
  surface.Dispose();
}

2.D3DImage

D3DImage是wpf提供与d3d互操作的对象。通常通过SetBackBuffer设置其背景缓存surface,这个背景surface就可以是d3d9的Surface对象,.AddDirtyRect更新前景Surface。与Image控件的source关联,显示到界面上。

var d3DImage = new D3DImage();
    d3DImage.Lock();
    d3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _textureSurface.NativePointer);
    d3DImage.Unlock();
    img_diplay.Source = d3DImage;

二、如何实现?

1.关联D3DImage

需要渲染数据,必须先有一块缓存用来装载数据,因为dxva2解码出来的Surface对象通常数据是nv12不能直接作为D3DImage的缓存对象,需要建立一个与D3DImage兼容的缓存对象Texture获取其内部的Surface即可。

//获取到surface对象
var surface = Surface.FromPointer<Surface>(data[3]);
//获取surface的device
var device = surface.Device;
//获取device的d3d对象
var d3d = device.Direct3D;
//通过device创建Texture
_texture = new Texture(device, width, height, 1, Usage.RenderTarget, d3d.GetAdapterDisplayMode(0).Format, Pool.Default);
//获取texture的surface
_textureSurface = _texture.GetSurfaceLevel(0);
//将surface设置为D3DImage的背景缓存
_d3DImage = new D3DImage();
_d3DImage.Lock();
_d3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _textureSurface.NativePointer);
_d3DImage.Unlock();
//将D3DImage与界面的Image关联
img_diplay.Source = _d3DImage;

2.渲染

//surface为dxva2解码数据,_textureSurface为D3DImage的背景缓存
device.StretchRectangle(surface, _textureSurface, TextureFilter.Linear);
d3DImage.Lock();
d3DImage.AddDirtyRect(new Int32Rect(0, 0, width, height));
d3DImage.Unlock();

3.完整流程

void Play_VideoDisplay(IntPtr play, IntPtr[] data, int[] linesize, int width, int height, ACDll.ac_pixFormat format)
{
    //获取dxva2解码的surface帧
    var surface = Surface.FromPointer<Surface>(data[3]);
    var device = surface.Device;
    if (_d3DImage == null)
    {
        //获取surface的device
        var d3d = device.Direct3D;
        //通过device创建Texture
        _texture = new Texture(device, width, height, 1, Usage.RenderTarget, d3d.GetAdapterDisplayMode(0).Format, Pool.Default);
        _textureSurface = _texture.GetSurfaceLevel(0);
        _d3DImage = new D3DImage();
        _d3DImage.Lock();
        _d3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _textureSurface.NativePointer);
        _d3DImage.Unlock();
        img_diplay.Source = _d3DImage;
        d3d.Dispose();
    }
    //渲染
    device.StretchRectangle(surface, _textureSurface, TextureFilter.Linear);
    _d3DImage.Lock();
    _d3DImage.AddDirtyRect(new Int32Rect(0, 0, width, height));
    _d3DImage.Unlock();
    //释放引用计数
    surface.Dispose();
    device.Dispose();
}

三、示例代码


四、效果预览

视频框内放置控件:
在这里插入图片描述
i7 核显渲染 hevc 4k 60fps性能:
在这里插入图片描述

五、性能对比

测试视频:hevc 4k 60fps
测试设备:i7 8750h gpu使用核显
数据记录:30秒内取5次值计算均值

渲染方式 cpu使用率(%) gpu使用率(%)
软解渲染 24.52 60.86
dxva2解码句柄渲染 0.78 52.36
dxva2解码D3DImage渲染 1.22 56.04

总结

以上就是今天的内容了,曾有以为dxva2解码渲染最佳性能方式只能通过句柄渲染实现,但是经过发现了通过D3DImage渲染也能够达到非常接近的性能,这样就很好的解决了在wpf中的绘制兼容问题,而且使用方式非常简单。

举报

相关推荐

0 条评论