快速实现3D场景中的视频回放
快速实现3D场景中的视频回放
记得在刚开始学习D3D时,曾看到D3D的一个例子程序,一个人(模型)在走,身旁是一堵一堵的电视墙,而里面都在播放同一段视频。程序中同时展现出3D和视频画面给我留下了深深印象。在后来的工作中,却主要是做音视频方面程序开发,直到最近比较闲,又看到论坛里有这方面的贴子,便琢磨,能不能把它做出来呢。通过几天的努力后,终于实现了其基本功能,做出了一个Demo。将一些感受写下来,欢迎大家讨论,借以抛砖引玉!
一、问题
我是在DirectX中实现的,用到了DirectShow和Direct3D。直观地想,就是要把视频解码出来的每一帧图片,及时送给D3D,作为一张纹理渲染出来,这样当每一帧的处理足够快时,便在3D场景中看到流畅的视频回放。但是问题就出来了:
1、DShow的视频回放,是拉模式的,图片流何时到达,并不是由Renderer决定的,只有当有新的帧到达时,才被调用处理。而D3D中每一帧的渲染,是由自己“主动”处理的,这样就有视频解码与D3D渲染步调一致的问题。
2、DShow解码出来的图片格式,要与D3D中纹理格式兼容,或者进行必要的转换。
二、解决方式
先说第一个问题,要在视频解码得到新的一帧时,就即时在3D场景中渲染出来,是很不现实的事情,因为此时3D渲染部分很可能正在处理别的渲染对象,难以马上进行视频图像的渲染,而且这样的程序结构在DirectX中的实现也是有难度的。那么换个角度,从D3D这边来说,当它需要渲染视频图像时,就要得到最新的一帧,这是可以做到的。使用队列缓冲,将解码后的视频图像缓冲一段时间,然后当D3D需要时,从缓冲中取出一帧,这样就可满足要求,缓冲队列中的数据属于临界资源,在存入/取出时必须做到线程同步。在我的程序中,也是采用后一种方式。事实证明,这种方式是行之有效的。
缓冲队列中的首指针指向待存入帧,尾指针指向待取出帧,当从Renderer获取数据存入一帧后,首指针后移一位,若后移后首指针与尾指针相同,表明存入的速度大于取出的速度,图像没来得取出就被新的帧填充掉了,即D3D的渲染速度小于图片流的生成速度,这可能是D3D渲染的负担太重或硬件配置太差造成的。而当D3D取出一帧图像渲染后,尾指针后移一位,若后移后尾指针与首指针相同,表明取出的速度大于存入的速度,Renderer还没存入新的图像数据,D3D就又来取了,这可能是因为D3D中的渲染场景很简单或者是硬件配置非常好。对后一种情况,在我的程序中,让尾指针保持不动,因为此时的结果是D3D将同一视频画面渲染了两次或更多,但并不影响视频回放原有的帧速率。
再说第二个问题,在做的过程中发现,其实这个问题是比较好解决,按照相相应的视频媒体格式和纹理格式,拷过去就可以了。比如视频图像格式是MEDIASUBTYPE_GRGB24,纹理格式是D3DFMT_A8R8G8B8,这是两种常见的格式。*pb1和*pb2分别代表两个数据区域的首地址,那么操作就是:
//循环体…
*pb2++ = *pb1++;
*pb2++ = *pb1++;
*pb2++ = *pb1++;
pb2++;
//…
其它格式依此视情况而处理。这样开头提出的两个问题都能得到解决。
三、实现中的一些问题
在编码中,发现程序结构很重要,因为这个程序要实现的核心功能其实就是两个应用架构间的数据流动,其它问题都是两个架构内部的问题,所以清晰而良好的程序结构是非常重要的!
在DShow中,需要写一个Windowless Video Renderer来获取数据,为程序处理方便,这个Filter我用new的方式生成,这样只需要定义一个全局的缓冲队列对象,就可实现DShow和D3D间的数据访问。
D3D和DShow这两个DirectX的成员,是可以在一起良好工作的,最终程序编绎、链接成功,只是链接时还是出现了一堆警告,估计是与我的DShow库有关,因为以前我将其重新编绎过。
四、关于提高性能
提高性能,重点在于优化从视频图像转换到D3D纹理的处理过程。
优化拷贝算法,甚至对循环体采用汇编是一个办法。但后来又想到,每一帧图像都做拷贝处理,是比较消耗资源的,视频图像的数据区域其实是由DShow中的IMediaSample接口来访问的,D3D中的纹理是由IDirect3DTexture9接口来访问的,它们各自有自己的数据区域,如果可以确保两个接口支持相同的格式,让它们共用同一块数据区域的话,就可以省下拷贝这个过程了,只需要传递指针即可。而这有赖于DirectX SDK能否支持这样的功能!如果这样的话,除开D3D和DShow框架本身的开销外,不会再有其它的开销!只是现在还是设想。
五、总结
我的程序,队列缓冲中经常发生的是,尾指针追上头指针的情况,这说明,要实现视频流畅回放,是很有保证的。D3D渲染的速度相对于视频回放的速度,如果快了,那么就多次渲染同一画面,如果慢了,就跳过一些画面,这就是这个程序所反映的,中间没有作影响FPS的处理,仅用一个全局对象来管理队列缓冲,所以叫做“快速实现”!
限于篇幅,这里主要讨论思路,细节问题就不赘述了。
题外话
这是一个D3D与DShow相结合的程序,我的编程学习从D3D开始,写这个程序,是对自己一个总结,也可以算一点心理慰藉吧!不管以后还会不会与D3D打交道,还是要把这些写下来,希望通过这个程序,又是一个新的开始……
Cyrys 2009-2-17
[解决办法]
学习了,正在弄这块,不过是用xvid,受益匪浅
[解决办法]
顶一个,把视频和3d联系起来,做出来的效果可够炫了,一个3D的播放器,在Linux下面有立体桌面,播放视频的时候视频可以分割到两个平面形成3d效果。
[解决办法]
问个问题:那个vedio render的基本构架是怎么样的,实现了哪几个接口,它是怎么做到音视频同步的。