客户端视频渲染目前最理想的解决方案
很多客户端产品有视频渲染需求。视频渲染有软件渲染和硬件渲染两种方案。
软件渲染一般来说需要将常见的yuv视频流转换成rgb/rgba视频流,再通过GDI绘制到窗口上。软件渲染的优势在于可以方便的和客户端界面做融合,达到更好的界面效果,比如通过alphablend等API实现前景,背景的混合。
然后软件渲染性能存在比较大的瓶颈,现阶段最好的电脑可能也带不动1080P, 30帧的视频。只适合小视频窗口的渲染。
硬件渲染则是使用硬件加速接口,直接将视频渲染到显示器,通常对yuv流的支持都很友好,不需要做格式转换,同时性能会高很多,轻松视频4K视频的高帧率渲染。然而硬件渲染需要将视频流转换成硬件支持的Texture, 并占满整个HWND窗口空间,导致很难和GDI绘制的界面元素融合。
有没有再使其美的办法呢?
先看图:
我一直在问为什么手机上做视频渲染这么简单,视频和其它UI元素可以完美的组合?浏览器好像也可以达到类似的效果。
前段时间我也有尝试使用sdl来实现视频和soui绘制的窗口进行融合渲染(sdlplayer),效果还比较理想。当时的实现还限于当时对视频渲染的认知不够,还是需要为视频创建一个独立的HWND,只是在将视频流绘制到这个HWND之后,再将视频上的UI前景再叠加到视频流上。
这样做基本达到了将视频使用硬件渲染,同时又能和UI前景融合的问题。不够理想在于,它还是需要多一个HWND,而且操作这些前景需要多做一次中转。
事件的转机在一个朋友提的一个问题:SOUI可不可以使用opengl来渲染界面,而绘制流程保持不变?
既然SOUI内部已经将界面效果渲染到了内存位图上了,上显示器为什么一定要用GDI呢?
结合前段时间做sdlplayer的经验,通过简单的将上屏流程抽象出一个IPresenter接口,再使用sdl来实现这个接口,即可以方便的切换到使用sdl来上屏,从而实现上屏过程的硬件加速。
当然,只是抽象出IPresenter还不够。如果一个视频占满整个窗口空间,当然没有问题,界面上的前景绘制好后直接生成一个rgba的sdl_texture,再帖到视频的yuv_texture上面即可。但是如果要达到使用单一的HWND实现视频和UI的融合,则需要支持视频显示在窗口的任意位置。
要解决这个问题,首先需要在Presenter中视频到达的时候,知道这一帧视频需要渲染到什么地方。
这个问题好办,在视频到达的时候,问一下SOUI,哪个控件占的位置是留给视频的即可。
然后,SOUI需要保证这个位置上在绘制视频前不能绘制背景。常规流程下,SOUI在渲染UI元素时是自动将整个窗口的空间刷新的,不区分哪一块是留给视频渲染的。假定窗口有一个白色的背景,那么整个窗口的内存位图就会被白色填充。在将这个内存位图叠加到视频上以后,视频就看不到了。
要解决这个问题,SOUI的方法是:窗口刷新的时候,先将整个画布清空(alpha=0,窗口全透明),再使用clip剪切掉视频占用的位置,保证后面的绘制流程不绘制视频位置。经过这样处理,视频在SOUI给定的位置渲染后,再叠加SOUI的渲染结果,就会出现视频和SOUI融合的情况。
然而这只解决了一个问题,让SOUI绘制不遮挡视频。由于前面将视频渲染位置使用clip来保证不做渲染,视频的前景也同样没有渲染。
为解决这个问题,只需要在做完前面的绘制流程后,再做一次只绘制视频前景的流程即可:清除前面的clip,从视频窗口的dui元素层开始绘制前景(如果有的话)。这样处理后就可以实现视频和UI元素的完美融合。
最后给出源代码:
https://github.com/setoutsoft/soui4js