当前位置: 移动技术网 > IT编程>开发语言>.net > WPF下YUV播放的D3D解决方案

WPF下YUV播放的D3D解决方案

2017年12月08日  | 移动技术网IT编程  | 我要评论

人参娘二区,饭笙网,jssplit

在视频媒体播放,监控系统的构建当中,经常会涉及到yuv数据的显示问题。一般的播放控件以及sdk都是通过使用window句柄,利用directdraw直接在窗口上渲染。但是,如果用户界面是使用wpf开发的时候,通常只能通过winformhost在wpf界面中嵌入winform来完成。但这么做会遇到aerospace的问题,即winform的控件永远浮在wpf的最上层,任何wpf元素都会被盖住,同时缩放和拖动的时候都会造成很差的用户体验。原因是由于wpf和winform使用了不同的渲染技术。

要在wpf中完美的支持yuv数据的显示,通常的解决方式是使用先把yuv数据转换成wpf可以支持的rgb数据,然后利用类似于writeablebitmap的控件,把他展现在wpf上。这么做的主要问题是在做rgb转换的时候,需要消耗大量的cpu, 效率比较低。一种优化方式是使用ffmpeg里的swscale或者intel的ipp库,这些库经过了一定的优化,可以有限度的使用硬件加速。下面为一个使用writablebitmap的例子。

writeablebitmap imagesource = new writeablebitmap(videowidth, videoheight, 
 dpi_x, dpi_y, system.windows.media.pixelformats.bgr32, null); 
... 
int rgbsize = width * height * 4; // bgr32 
intptr rgbptr = marshal.allochglobal(rgbsize); 
yv12torgb(yv12ptr, rgbptr, width, height); 
// 更新图像 
imagesource.lock(); 
interop.memcpy(this.imagesource.backbuffer, rgbptr, rgbsize); 
imagesource.adddirtyrect(this.imagesourcerect); 
imagesource.unlock(); 
marshal.freehglobal(rgbptr); 

另一种解决方法是使用d3dimage作为wpf与显卡的桥梁。我们可以借助d3dimage,直接将d3d渲染过后的部分送到wpf中显示。一个参考就是vmr9在wpf中的应用。vmr9是微软提供的directshow的render。经过仔细参考了wpfmediatookit中vmr9相关的代码后,其核心的思想就是在初始化directshow构建vmr9渲染器时,让其输出一个d3d9surface,d3dimage将使用该surface作为backbuffer。当有新的视频帧在该surface渲染完成后,vmr9将发送一个事件通知。收到通知后,d3dimage刷新一下backbuffer即可。下面代码展现了核心思想部分。

private videomixingrenderer9 createrenderer() { 
 var result = new videomixingrenderer9(); 
 var cfg = result as ivmrfilterconfig9; 
 cfg.setnumberofstreams(1); 
 cfg.setrenderingmode(vmr9mode.renderless); 
 var notify = result as ivmrsurfaceallocatornotify9; 
 var allocator = new vmr9allocator(); 
 notify.advisesurfaceallocator(m_userid, allocator); 
 allocator.advisenotify(notify); 
 // 在构建vmr9 render时,注册新视频帧渲染完成事件 
 allocator.newallocatorframe += new action(allocator_newallocatorframe); 
 // 注册接收新d3dsurface被创建的事件 
 allocator.newallocatorsurface += new newallocatorsurfacedelegate(allocator_newallocatorsurface); 
 return result; 
 } 
 void allocator_newallocatorsurface(object sender, intptr psurface) 
 { 
  // 为了方便理解,只保留核心部分。省略改写了其他部分 
  ... 
  // 将psurface设置为d3dimage的backbuffer 
  this.m_d3dimage.lock(); 
  this.m_d3dimage.setbackbuffer(d3dresourcetype.idirect3dsurface9, psurface); 
  this.m_d3dimage.unlock(); 
  ... 
 } 
 void allocator_newallocatorframe() 
 { 
  ... 
  // 重绘 
  this.m_d3dimage.lock(); 
  this.m_d3dimage.adddirtyrect(new int32rect(0, /* left */ 
    0, /* top */ 
    this.m_d3dimage.pixelwidth, /* width */ 
    this.m_d3dimage.pixelheight /* height */)); 
  this.m_d3dimage.unlock(); 
  ... 
 } 

由此,只要是使用directshow的视频播放就可以借助vmr9在wpf上完美显示。但很多时候,directshow不能解决所有问题。例如在做交互式视频优化处理或是视频叠加的时候, 采用固定滤镜流水线的directshow很难满足要求。有的时候还是需要便捷的直接渲染的方式。

由vmr9的例子我们可以看出,产生出一个d3d9surface并在上面渲染是其中的关键。那么剩下的问题就是如何把yuv数据渲染到d3d9surface。

d3d没有直接支持yuv图像格式。因此需要我们想办法让d3d能够渲染yuv数据。在用c#改写的过程当中,我突然发现d3d已经提供了更简单的方法帮助我们实现yuv到rgb颜色空间的转换,而且是通过显卡硬件直接支持。效率相当的高。主要原理就是借助d3ddevice的strentchrectangle方法。

public void stretchrectangle( 
 surface sourcesurface, 
 rectangle sourcerectangle, 
 surface destsurface, 
 rectangle destrectangle, 
 texturefilter filter 
); 

strentchrectangle方法的主要功能是将一个surface上的某个区域的内容拷贝到另一个surface的指定区域中。在copy的过程当中,只要是显卡直接支持的格式,如yv12,yuy2等等, 都会自动的进行d3d pixelformat的转换!因此,我们只需要创建一个指定好pixelformat的d3d offscreenplainsurface, 把原始数据填充进去,调用strentchrectangle向目标surface拷贝,我们就得到了想要的surface。剩下的事情就交给d3dimage了。下面是例子代码的核心部分

public void render(intptr imgbuffer) 
{ 
 lock (this.renderlock) 
 { 
  // 将图像数据填充进offscreen surface 
  this.fillbuffer(imgbuffer); 
  // 调用strentchrectangle把原始图像数据copy到texturesurface中         
  this.stretchsurface(); 
  // 执行渲染操作 
  this.createscene(); 
 } 
 // 通知d3dimage刷新图像 
 this.invalidateimage(); 
} 

以上所述是小编给大家介绍的wpf下yuv播放的d3d解决方案,希望对大家有所帮助

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网