下面的代码以只读方式打开AVI文件。szFile是打开文件的名字。title[100]用来修改window标题(显示AVI文件信息).。
首先调用AVIFileInit()。初始化AVI文件库(使东西能用)。
打开AVI文件有很多方法.我采用AVIStreamOpenFromFile(...).他能打开AVI文件中单独一个流(AVI文件可以包含多个流).它的参数如下:pavi是接收流句柄的缓冲的指针,szFile是打开文件的名字(包括路径).第三参数是打开的流的类型.在这个工程里,我们只对视频流感兴趣(streamtypeVIDEO).第四参数是0,这表示我们需要第一次读到的视频流(一个AVI文件里会有多个视频流,我们要第一个).OF_READ表示以只读方式打开文件.最后一个参数是一个类标识句柄的指针.说实话,我也不清楚他是干吗的.我让windows自己设定,于是把NULL传过去.
void OpenAVI(LPCSTR szFile)// 打开AVI文件szFile
{
TCHAR title[100];// 包含修改了的window标题
AVIFileInit();// 打开AVI文件库
// 打开AVI流
if (AVIStreamOpenFromFile(&pavi, szFile, streamtypeVIDEO, 0, OF_READ, NULL) !=0)
{
// 打开流时的出错处理
MessageBox (HWND_DESKTOP, "打开AVI流失败", "错误", MB_OK | MB_ICONEXCLAMATION);
}
到目前为止,我们假定文件被正确打开,流被正确定位!然后用AVIStreamInfo(...)从AVI文件里抓取一些信息.
先前我们创建了叫psi的结构体来保存AVI流的信息.下面第一行,我们把AVI信息填入该结构体.从流的宽度(以像素计)到动画的帧速等所有的信息都会存到psi中.那些想要精确控制播放速度的要记住我刚才说的.更多的信息参阅MSDN.
我们通过右边位置减左边位置算出帧宽.这个结果是以像素记的精确的帧宽.至于高度,可以用底边位置减顶边位置得到.这样得到高度的像素值.
然后用AVIStreamLength(...)得到AVI文件最后一帧的序号.AVIStreamLength(...)返回动画最后一帧的序号.结果存在lastframe里.
计算帧速很简单.每秒帧速(fps)= psi.dwRate/psi,dwScale.返回的值应该匹配显示帧的速度(你在AVI动画中右击鼠标可以看到).你会问那么这和mpf有什么关系呢?第一次写这个代码时,我试着用fps来选择动画了正确的帧面.我遇到一个问题...视频放的太快!于是我看了一下视频属性.face2.avi文件有3.36秒长.帧速是29.974fps.视频动画共有91帧.而3.36*29.974 = 100.71.非常奇怪!!
所以我采用一些不同的方法.不是计算帧速,我计算每一帧播放所需时间.AVIStreamSampleToTime()把在动画中的位置转换位你到达该位置所需的时间(毫秒计).所以通过计算到达最后一帧的时间就得到整个动画的播放时间.再拿这个结果除以动画总帧数(lastframe).这样就给出了每帧的显示时间(毫秒计).结果存在mpf(milliseconds per frame)里.你也能通过获取动画中一帧的时间来算每帧的毫秒数,代码为:AVIStreamSampleToTime(pavi,1).两种方法都不错!非常感谢Albert Chaulk提供思路!
我说每帧的毫秒数不精确是因为mpf是一个整型值,所以所有的浮点数都会被取整.
AVIStreamInfo(pavi, &psi, sizeof(psi));// 把流信息读进psi
width=psi.rcFrame.right-psi.rcFrame.left;// 宽度为右边减左边
height=psi.rcFrame.bottom-psi.rcFrame.top;// 高为底边减顶边
lastframe=AVIStreamLength(pavi);// 最后一帧的序号
mpf=AVIStreamSampleToTime(pavi,lastframe)/lastframe;// mpf的不精确值
我们可利用Windows Dib函数去做.
首先要做的是描述我们想要的图像的类型.于是我们要以所需参数填好bmih这个BitmapInfoHeader结构.
首先设定该结构体的大小.再把位平面数设为1.3字节的数据有24比特(RGB).要使图像位256像素宽,256像素高,最后要让数据返回为UNCOMPRESSED(非压缩)的RGB数据(BI_RGB).
CreateDIBSection创建一个可直接写的设备无关位图(dib).如果一切顺利,hBitmap会指向该dib的比特值.hdc是设备上下文(DC)的句柄第二参数是BitmapInfo结构体的指针.该结构体包含了上述dib文件的信息.第三参数(DIB_RGB_COLORS)设定数据是RGB值.data是指向DIB比特值位置的指针的指针(呜,真绕口).第五参数设为NULL,我们的DIB已被分配好内存.末了,最后一个参数可忽略(设为NULL).
引自MSDN:SelecObject函数选一个对象进入设备上下文(DC).
现在我们建好一个能直接写的DIB,yeah:)
bmih.biSize= sizeof (BITMAPINFOHEADER);// BitmapInfoHeader的大小
bmih.biPlanes= 1;// 位平面
bmih.biBitCount= 24;//比特格式(24 Bit, 3 Bytes)
bmih.biWidth= 256; // 宽度(256 Pixels)
bmih.biHeight= 256;// 高度 (256 Pixels)
bmih.biCompression = BI_RGB; // 申请的模式 = RGB
hBitmap = CreateDIBSection (hdc, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&data), NULL, NULL);
SelectObject (hdc, hBitmap);// 选hBitmap进入设备上下文(hdc)
在从AVI中读取帧面前还有几件事要做.接下来使程序做好从AVI文件中解出帧面的准备.用AVIStreamGetFrameOpen(...)函数做这一点.
你能给这个函数传一个结构体作为第二参数(它会返回一个特定的视频格式).糟糕的是,你能改变的唯一数据是返回的图像的宽度和高度.MSDN也提到能传AVIGETFRAMEF_BESTDISPLAYFMT为参数来选择一个最佳显示格式.奇怪的是,我的编译器没有定义这玩艺儿.
如果一切顺利,一个GETFRAME对象被返回(用来读帧数据).有问题的话,提示框会出现在屏幕上告诉你有错误!
pgf=AVIStreamGetFrameOpen(pavi, NULL); // 用要求的模式建PGETFRAME
if (pgf==NULL)
{
// 解帧出错
MessageBox (HWND_DESKTOP, "不能打开AVI帧", "错误", MB_OK | MB_ICONEXCLAMATION);
}
下面的代码把视频宽,高和帧数传给window标题.用函数SetWindowText(...)在window顶部显示标题.以窗口模式运行程序看看以下代码的作用.
// bt标题栏信息(宽 / 高/ 帧数)
wsprintf (title, "NeHe 's AVI Player: Width: %d, Height: %d, Frames: %d ", width, height, lastframe);
SetWindowText(g_window-> hWnd, title);// 修改标题栏
}
下面是有趣的东西...从AVI中抓取一帧,把它转为大小和色深可用的图象.lpbi包含一帧的BitmapInfoHeader信息.我们在下面第二行完成了几件事.先是抓了动画的一帧...我们需要的帧面由这些帧确定.这会让动画走掉这一帧,lpbi会指向这一帧的头信息.
下面是有趣的东西...我们要指向图像数据了.要跳过头信息(lpbi-> biSize).一件事直到写本文时我才意识到:也要跳过任何的色彩信息.所以要跳过biClrUsed*sizeof(RGBQUAD)(译者:我想他是说要跳过调色板信息).做完这一切,我们就得到图像数据的指针了(pdata).
也要把动画的每一帧的大小转为纹理能用的大小,还要把数据转为RGB数据.这用到DrawDibDraw(...).一个大概的解释.我们能直接写设定的DIB图像.那就是DrawDibDraw(...)所做的.第一参数是DrawDib DC的句柄.第二参数是DC的句柄.接下来用左上角(0,0)和右下角(256,256)构成目标矩形.lpbi指向刚读的帧的bitmapinfoheader信息.pdata是刚读的帧的图像数据指针.再把源图象(刚读的帧)的左上角设为(0,0),右下角设为(帧宽,帧高).最后的参数应设为0.这个方法可把任何大小、色深的图像转为256*256*24bit的图像.
void GrabAVIFrame(int frame)// 从流中抓取一帧
{
LPBITMAPINFOHEADER lpbi;// 存位图的头信息
lpbi = (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf, frame);// 从AVI流中得到数据
pdata=(char *)lpbi+lpbi-> biSize+lpbi->biClrUsed * sizeof(RGBQUAD);// 数据指针,由AVIStreamGetFrame返回(跳过头
//信息和色彩信息)
// 把数据转为所需格式
DrawDibDraw (hdd, hdc, 0, 0, 256, 256, lpbi, pdata, 0, 0, width, height, 0);
我们得到动画的每帧数据(红蓝数据颠倒的).为解决这个问题,我们的高速代码flipIt(...).记住,data是指向DIB比特值位置的指针的指针变量.这意味着调用DrawDibDraw后,data指向一个调整过大小(256*256),修改过色深(24bits)的位图数据.
flipIt(data); // 交换红蓝数据
// 更新纹理
。。。。。这里保存或位图
}
接下来的部分当程序退出时调用,我们关掉DrawDib DC,释放占用的资源.然后释放AVI GetFrame资源.最后释放AVI流和文件.
void CloseAVI(void)// 关掉AVI资源
{
DeleteObject(hBitmap);//释放设备无关位图信息
DrawDibClose(hdd); // 关掉DrawDib DC
AVIStreamGetFrameClose(pgf);// 释放AVI GetFrame资源
AVIStreamRelease(pavi);// 释放AVI流
AVIFileExit();// 释放AVI文件
}
void flipIt(void* buffer)// 交换红蓝数据(256x256)
{
void* b = buffer;// 缓冲指针
__asm// 汇编代码
{
mov ecx, 256*256 // 设置计数器
mov ebx, b// ebx存数据指针
label: // 循环标记
mov al,[ebx+0]// 把ebx位置的值赋予al
mov ah,[ebx+2]// 把ebx+2位置的值赋予ah
mov [ebx+2],al// 把al的值存到ebx+2的位置
mov [ebx+0],ah// 把ah的值存到ebx+0的位置
add ebx,3 // 向前走3个字节
dec ecx // 循环计数器减1
jnz label // ecx非0则跳至label
}
}