为了对 avi 进行读写,微软提供了一套 API ,总共 50 个函数,他们的用途主要有两类,一个是 avi 文件的操作,一类是数据流 streams 的操作。 1 、打开和关闭文件 AVIFileOpen , AVIFileAddRef , AVIFileRelease 2 、从文件中读取文件信息 通过 AVIFileInfo 可以获取 avi 文件的一些信息,这个函数返回一个 AVIFILEINFO 结构,通过 AVIFileReadData 可以用来获取 AVIFileInfo 函数得不到的信息。这些信息也许不包含在文件的头部,比如拥有 file 的公司和个人的名称。 3 、写入文件信息 可以通过 AVIFileWriteData 函数来写入文件的一些额外信息。 4 、打开和关闭一个流 打开一个数据流就跟打开文件一样,你可以通过 AVIFileGetStream 函数来打开一个数据流,这个函数创建了一个流的接口,然后在该接口中保存了一个句柄。 如果你想操作文件的某一个单独的流,你可以采用 AVIStreamOpenFromFile 函数,这个函数综合了 AVIFileOpen 和 AVIFileGetStream 函数。 如果你想操作文件中的多个数据流,你就要首先 AVIFileOpen ,然后 AVIFileGetStream 。 可以通过 AVIStreamAddRef 来增加 stream 接口的引用。 通过 AVIStreamRelease 函数来关闭数据流。这个函数用来减少 streams 的引用计数,当计数减少为 0 时,删除。 5 、从流中读取数据和信息 AVIStreamInfo 函数可以获取数据的一些信息,该函数返回一个 AVISTREAMINFO 结构,该结构包含了数据的类型压缩方法,建议的 buffersize ,回放的 rate ,以及一些 description 。 如果数据流还有一些其它的额外的信息,你可以通过 AVIStreamReadData 函数来获取。应用程序分配一个 内存 ,传递给这个函数,然后这个函数会通过这个内存返回数据流的信息,额外的信息可能包括数据流的压缩和解压缩的方法,你可以通过 AVIStreamDataSize 宏来回去需要申请内存块的大小。 可以通过 AVIStreamReadFormat 函数获取数据流的格式信息。这个函数通过指定的内存返回数据流的格式信息,比如对于视频流,这个 buffer 包含了一个 BIMAPINFO 结构,对于音频流,内存块包含了 WAVEFORMATEX 或者 PCMAVEFORMAT 结构。你可以通过给 AVIStreamReadFormat 传递一个空 buffer 就可以获取 buffer 的大小。也可以通过 AVIStreamFormatSize 宏。 可以通过 AVIStreamRead 函数来返回多媒体的数据。这个函数将数据复制到应用程序提供的内存中,对于视频流,这个函数返回图像祯,对于音频流,这个函数返回音频的 sample 数据。可以通过给 AVIStreamRead 传递一个 NULL 的 buffer 来获取需要的 buffer 的大小。也可以通过 AVIStreamSampleSize 宏来获取 buffer 的大小。 有些 AVI 数据流句柄可能需要在启动数据流的前要做一下准备工作,此时,我们可以调用 AVIStreamBeginStreaming 函数来告知 AVI 数据流 handle 来申请分配它需要的一些资源。在完毕后,调用 AVIStreamEndStreamming 函数来释放资源。 6 、操作压缩的视频数据 如果你要演示一祯或者几祯压缩视频图像时,你可以调用 AVIStreamRead 函数,将获取的数据传递给 DrawDib 函数来显示图像。这些函数可以显示压缩和未压缩的图像。 AVIFile 也提供了一个函数 AVIStreamGetFrameOpen ,来获取未压缩的视频祯,这个函数创建了内存来获取未压缩的数据。也可以通过 AVIStreamGetFrame 函数来解压缩一个单独的视频祯。这个函数可以解压缩某一祯图像,然后将数据以一个 BIMAPINFOHEADER 结构返回。当你调用完 AVIStreamGetFrame 函数后,要调用 AVIStreamGetFrameClose 函数释放上一个函数申请的资源。 7 、根据已存在的数据流创建文件 创建一个包含多个数据流的文件的方法就是整合多个数据流,将其写入一个新文件。这些数据流可以是内存中的数据,也可以是存在于另一个文件中。 我们可以用 AVISave 这个函数来 build 一个文件。这个函数可以创建一个文件,并且将指定的多个数据流按照指定的顺序写入文件,你也可以通过 AVISaveV 函数来创建一个新的文件,这个函数的功能和 AVISave 的功能一样,主要区别是 AVISaveV 采用的数据流数组,而 AVISave 是单个的数据流,多次保存。 我们可以调用 AVISaveOptions 函数来显示一个对话框,可以让用户来选择压缩方式。 我们可以在调用 AVISave 和 AVISaveV 函数时指定一个回调函数,用来显示 avi 文件的生成进度,可以让用户随时地取消生成 avi 文件。 我们可以调用 GetSaveFileNamePreview 函数来显示保存的对话框让用户选择保存的文件名。 通过 AVIMakeFileFromStreams 函数我们可以创建一个虚拟的文件句柄,其他的 avi 函数可以通过这个虚拟的文件句柄来操作文件中的数据流,操作完毕要记得调用 AVIFileRelease 释放。
8 、向文件写入一个数据流 我们可以通过 AVIFileCreateStream 函数来在一个新文件或者已经存在的文件中创建一个数据流。这个函数根据 AVISTREAMINFO 结构定义了新的数据流,并为新的数据流创建一个接口,返回接口的指针。 在写入新的数据前,一定要指定流的格式信息,通过 AVIStreamSetFormat 函数,当设置一个视频流的时候,一定要使用 BIMAPINFO 结构来设置,音频就用 WAVEFORMAT 。 然后我们就可以通过 AVIStreamWrite 函数将我们的多媒体数据写入数据流了。这个函数将应用程序提供的内存数据复制到指定的流。缺省的 avi handler 将数据写入流的最后。 如果你有其他额外的信息需要写入流,你可以调用 AVIFileWriteData 或者 AVIStreamWriteData ,最后记得在完成数据写入后,要调用 AVIStreamRelease 。 9 、数据流中的祯的位置 寻找起始祯: 可以通过 AVIStreamStart 函数来获取第一祯包含的 sample number 。也可以通过 AVIStreamInfo 函数来获取这个信息,这个函数的 AVISTREAMINFO 结构中包含了 dwStart ,可以通过 AVIStreamStartTime 宏来获取第一个 sample 。 可以通过 AVIStreamLength 函数来获取流的长度。这个函数返回流中的 sample 的数目。也可以通过 AVIStreamInfo 函数来获取这些信息,可以通过 AVIStreamLengthTime 宏来获取流的长度,毫秒。 在视频流中,一个 sample 对应着一祯图像,所以,有时这些 sample 中没有视频数据,如果你调用 AVIStreamRead 函数来数据,可能返回 NULL ,也可以通过 AVIStreamFindSample 通过指定 FIND_ANY 标志来查找指定的 sample 。 查找关键祯 通过 AVIStreamFindSample 函数查找符合要寻找的 sample ,然后可以通过下面的宏判断是否关键祯。 在 time 和 sample 间互相切换。 AVIStreamSampleToTime 这个函数可以将 smaple 转换成毫秒。对于视频,这个值代表的是这个祯开始播放的时间。 在了解了上面的知识后,我们对 avi 的文件结构以及如何操作 avi 文件心里就明白了,下面我们可以开始我们的编程了。我们要做两件事情: 1 、如何将一组静态的 bmp 位图合成一个 avi 的视频文件; 2 、如何将一个未压缩的 avi 文件解析成一幅幅位图。 示例程序界面如下:
下面的函数演示了如何将一个文件夹下面的所有 bmp 文件都保存为一个 avi 文件,函数的第一个参数是要生成的 AVI 的文件名,第二个参数是存放 bmp 文件的文件夹名,这个函数会枚举该文件夹下的所有 bmp 文件,合成一个 AVI 文件。
void Cbmp2aviDlg::AVItoBmp(CString strAVIFileName, CString strBmpDir) { // TODO: 在此添加控件通知处理程序代码 AVIFileInit(); PAVIFILE avi; int res=AVIFileOpen(&avi, strAVIFileName, OF_READ, NULL); int n = GetLastError(); if (res!=AVIERR_OK) { //an error occures if (avi!=NULL) AVIFileRelease(avi); return ; } AVIFILEINFO avi_info; AVIFileInfo(avi, &avi_info, sizeof(AVIFILEINFO)); PAVISTREAM pStream; res=AVIFileGetStream(avi, &pStream, streamtypeVIDEO /*video stream*/, 0 /*first stream*/); if (res!=AVIERR_OK) { if (pStream!=NULL) AVIStreamRelease(pStream); AVIFileExit(); return ; } //do some task with the stream int iNumFrames; int iFirstFrame; iFirstFrame=AVIStreamStart(pStream); if (iFirstFrame==-1) { //Error getteing the frame inside the stream if (pStream!=NULL) AVIStreamRelease(pStream); AVIFileExit(); return ; } iNumFrames=AVIStreamLength(pStream); if (iNumFrames==-1) { //Error getteing the number of frames inside the stream if (pStream!=NULL) AVIStreamRelease(pStream); AVIFileExit(); return ; } //getting bitmap from frame BITMAPINFOHEADER bih; ZeroMemory(&bih, sizeof(BITMAPINFOHEADER)); bih.biBitCount=24; //24 bit per pixel bih.biClrImportant=0; bih.biClrUsed = 0; bih.biCompression = BI_RGB; bih.biPlanes = 1; bih.biSize = 40; bih.biXPelsPerMeter = 0; bih.biYPelsPerMeter = 0; //calculate total size of RGBQUAD scanlines (DWORD aligned) bih.biSizeImage = (((bih.biWidth * 3) + 3) & 0xFFFC) * bih.biHeight ; PGETFRAME pFrame; pFrame=AVIStreamGetFrameOpen(pStream, NULL ); AVISTREAMINFO streaminfo; AVIStreamInfo(pStream,&streaminfo,sizeof(AVISTREAMINFO)); //Get the first frame BITMAPINFOHEADER bih2; long lsize = sizeof(bih2); int index=0; for (int i=iFirstFrame; i<iNumFrames; i++) { index= i-iFirstFrame; BYTE* pDIB = (BYTE*) AVIStreamGetFrame(pFrame, index); // AVIStreamReadFormat(pStream,index,&bih2,&lsize); BITMAPFILEHEADER stFileHdr; BYTE* Bits=new BYTE[bih2.biSizeImage]; AVIStreamRead(pStream,index,1,Bits,bih2.biSizeImage,NULL,NULL); //RtlMoveMemory(Bits, pDIB + sizeof(BITMAPINFOHEADER), bih2.biSizeImage); bih2.biClrUsed =0; stFileHdr.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER); stFileHdr.bfSize=sizeof(BITMAPFILEHEADER); stFileHdr.bfType=0x4d42; CString FileName; FileName.Format("Frame-%05d.bmp", index); CString strtemp = strBmpDir; strtemp += "//"; strtemp += FileName; FILE* fp=_tfopen(strtemp ,_T("wb")); fwrite(&stFileHdr,1,sizeof(BITMAPFILEHEADER),fp); fwrite(&bih2,1,sizeof(BITMAPINFOHEADER),fp); int ff = fwrite(Bits,1,bih2.biSizeImage,fp); int e = GetLastError(); fclose(fp); / delete Bits; //CreateFromPackedDIBPointer(pDIB, index); } AVIStreamGetFrameClose(pFrame); //close the stream after finishing the task if (pStream!=NULL) AVIStreamRelease(pStream); AVIFileExit(); }
下面的这个函数演示了如何将 AVI 文件中的每一桢图像单独取出来,保存为 bmp 文件。函数的头一个参数是 avi 文件名,第二个参数是存放 bmp 文件的文件夹。
// 生成 avi void Cbmp2aviDlg::BMPtoAVI(CString szAVIName, CString strBmpDir) { CFileFind finder; strBmpDir += _T("//*.*"); AVIFileInit(); AVISTREAMINFO strhdr; PAVIFILE pfile; PAVISTREAM ps; int nFrames =0; HRESULT hr; BOOL bFind = finder.FindFile(strBmpDir); while(bFind) { bFind = finder.FindNextFile(); if(!finder.IsDots() && !finder.IsDirectory()) { CString str = finder.GetFilePath(); FILE *fp = fopen(str,"rb"); BITMAPFILEHEADER bmpFileHdr; BITMAPINFOHEADER bmpInfoHdr; fseek( fp,0,SEEK_SET); fread(&bmpFileHdr,sizeof(BITMAPFILEHEADER),1, fp); fread(&bmpInfoHdr,sizeof(BITMAPINFOHEADER),1, fp); BYTE *tmp_buf = NULL; if(nFrames ==0 ) { AVIFileOpen(&pfile,szAviName,OF_WRITE | OF_CREATE,NULL); _fmemset(&strhdr, 0, sizeof(strhdr)); strhdr.fccType = streamtypeVIDEO;// stream type strhdr.fccHandler = 0; strhdr.dwScale = 1; strhdr.dwRate = 15; // 15 fps strhdr.dwSuggestedBufferSize = bmpInfoHdr.biSizeImage ; SetRect(&strhdr.rcFrame, 0, 0, bmpInfoHdr.biWidth, bmpInfoHdr.biHeight); // And create the stream; hr = AVIFileCreateStream(pfile,&ps,&strhdr); // hr = AVIStreamSetFormat(ps,nFrames,&bmpInfoHdr,sizeof(bmpInfoHdr)); } tmp_buf = new BYTE[bmpInfoHdr.biWidth * bmpInfoHdr.biHeight * 3]; fread(tmp_buf, 1, bmpInfoHdr.biWidth * bmpInfoHdr.biHeight * 3, fp); hr = AVIStreamSetFormat(ps,nFrames,&bmpInfoHdr,sizeof(bmpInfoHdr)); hr = AVIStreamWrite(ps, // stream pointer nFrames , // time of this frame 1, // number to write (LPBYTE) tmp_buf, bmpInfoHdr.biSizeImage , // size of this frame AVIIF_KEYFRAME, // flags.... NULL, NULL); nFrames ++; fclose(fp); } } AVIStreamClose(ps); if(pfile != NULL) AVIFileRelease(pfile); AVIFileExit(); }
结束语: 以上代码在 vc 6.0 和 windows xp 平台调试通过。这两个函数你可以直接在你的程序中使用,更详细的代码可以参见随着本文附上的示例源码。这里我要指出的是,这个 AVI 文件和 bmp 互相转换过程中, avi 中的视频数据都是存放的是没有压缩的数据,如果你要分解 AVI 文件是经过压缩编码,比如, DVSD , MPEG4 编码,首先你要采用相应的解码器对视频数据解码,然后将解码过的数据保存为 bmp 文件。好了,关于 avi 文件的介绍就到这里结束了。