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

下面的代码以只读方式打开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

}

}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值