欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

WPF下YUV播放的D3D解决方案

程序员文章站 2023-11-05 12:09:58
在视频媒体播放,监控系统的构建当中,经常会涉及到yuv数据的显示问题。一般的播放控件以及sdk都是通过使用window句柄,利用directdraw直接在窗口上渲染。但是,...

在视频媒体播放,监控系统的构建当中,经常会涉及到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解决方案,希望对大家有所帮助