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