您的位置:首页 > 其它

使用windows API函数读取AVI视频文件

2011-11-30 21:07 253 查看
下面的代码以只读方式打开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

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: