基本思路
测试时可使用和视频画面同大小的全屏四边形Rectangle2D,该rect使用动态纹理材质。
渲染时按帧率动态替换该材质的纹理单元为当前帧图像
视频读取
读取每帧视频画面我使用的是OpenCV,类似如下:
CvCapture* mCapture = cvCreateFileCapture(mFileName.c_str());
int totalFrames = (int)cvGetCaptureProperty(mCapture, CV_CAP_PROP_FRAME_COUNT);
int fps = cvGetCaptureProperty(mCapture, CV_CAP_PROP_FPS);
IplImage* frame = cvQueryFrame(mCapture);
frame即为捕捉到的画面,其中最需要的三个属性:
width 图像宽
height 图像高
imageData RGB格式的数据区
你可以使用任意数据源,最终的Ogre处理都是一样的
第一次尝试
首先想到的是直接使用已有的材质,通过卸载、加载纹理单元的方式:
//卸载原纹理
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName("testMat");
Ogre::TexturePtr textureOld = mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->_getTexturePtr(0);
textureOld->unload();//可选
Ogre::TextureManager::getSingleton().remove(static_cast<Ogre::ResourcePtr>(textureOld));
//加载新纹理
Ogre::DataStreamPtr pDataStream(new Ogre::MemoryDataStream(frame->imageData, frame->width * frame->height * 3, false, true));
Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().loadRawData("testTexture",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, pDataStream, frame->width, frame->height,
Ogre::PF_R8G8B8, Ogre::TEX_TYPE_2D);
mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("testTexture");
因为我们有原始内存数据frame->imageData,因此可以使用内存数据流,并调用loadRawData方法加载纹理
按理说这种方法是很快的,因此只需担心能不能正确的画面
运行之后,画面是显示出来的,但灰突突的,没有原先的光泽度
想来大概是需要启用gamma矫正,即纹理创建改为:
Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().loadRawData("testTexture",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, pDataStream, frame->width, frame->height,
Ogre::PF_R8G8B8, Ogre::TEX_TYPE_2D, 0, 1.0f, true);
最后三个参数,分别设置mipmap为0, 采用硬件gamma校正(如果用软件校正,即将1.0f改为2.2,将会惊人的慢)
这一次,图像完全和原始视频相同了,但是帧率非常的低
改进
因为每次切换画面使用的纹理格式、大小都完全相同
显然可以考虑直接替换已有纹理单元的内存数据区,而不是先销毁再创建,代码如下:
Ogre::PixelBox box(frame->width, frame->height, 1, Ogre::PF_R8G8B8, frame->imageData);
texture->getBuffer(0, 0)->blitFromMemory(box);
这一次想来应该是足够快了,然而结果仍不理想
跟踪下去,发现blitFromMemory 类似实现最终都调用了D3DXLoadSurfaceFromMemory,性能的瓶颈都出在这里
因为已经是D3D API,这条路也就不能继续走下去了
另外虽然HardwarePixelBuffer的接口声明了writeData接口,但并没有实现,也是不能使用
看来只剩下一种办法,通过lock复制数据,希望可以成功了:
void* dst = texture->getBuffer(0, 0)->lock(Ogre::HardwareBuffer::HBL_DISCARD);
memcpy(dst, frame->imageData, frame->width * frame->height * 3);
texture->getBuffer(0, 0)->unlock();
这么做发现帧速确实上去了,但图像变成了重影的灰度图,显然是因为内存格式不一致
好在看显示的图像高度好像正好是原来的3/4
考虑我们用的是RGB格式,也许这是因为硬件使用4字节RGBA存储像素的原因
于是修改代码为:
char* dst =(char*)( texture->getBuffer(0, 0)->lock(Ogre::HardwareBuffer::HBL_DISCARD));
char* src = frame->imageData;
for(int i=0; i<frame->width; ++i)
{
for(int j=0; j<frame->height; ++j)
{
*dst++=*src++;
*dst++=*src++;
*dst++=*src++;
*dst++=0; //填充
}
}
texture->getBuffer(0, 0)->unlock();
啊,这一次终于成功了
进一步改进
使用的默认纹理单元时,缓冲区用途默认为TU_DEFAULT
对于需要快速刷新的场景应使用TU_DYNAMIC_WRITE_ONLY_DISCARDABLE
因此可以将纹理的创建改写为:
texture = Ogre::TextureManager::getSingleton().createManual(textureName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, frame->width, frame->height, 0,
Ogre::PF_R8G8B8, Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE, 0, true);
这可以进一步提高性能