本实验中 AVI 文件读取的示例比较简单,流程如下
通过调用几个库函数实现每一帧的读取。这里不去分析其复杂的文件头结构,仅说明如何读取AVI文件中的视频信息。整个过程中使用的几个函数和数据类型如下表
存储的数据 | 数据类型 | 读取方式 |
---|---|---|
文件指针 aviFile | IAVIFile* (或PAVIFile) | AVIFileOpen |
数据流 aviStream | IAVIStream* (或PAVIStream) | AVIFileGetStream |
数据流中的信息 strInfo | AVISTREAMINFO | AVIStreamInfo |
视频流中的信息 info_h | BITMAPINFOHEADER | AVIStreamReadFormat |
帧指针 pgf | PGETFRAME | AVIStreamGetFrameOpen |
一帧画面的指针 frameData | BITMAPINFO* | AVIStreamGetFrame |
帧的颜色数据 colorData | unsigned char* | frameData->bmiColors |
//--------main_avi2yuv.cpp--------
#include <windows.h>
#include <vfw.h>
//其它头文件等略过
...
char* aviFileName = argv[1];
char* yuvFileName = argv[2];
FILE* yuvFile = fopen(yuvFileName, "wb");
//对输出文件打开的检查略过
...
AVIFileInit();//初始化AVI类库
IAVIFile* aviFile;//AVI文件指针
IAVIStream* aviStream;//AVI数据流指针
//打开视频文件和输入数据流
if (AVIFileOpen(&aviFile, aviFileName, OF_READ, NULL))
...
if (AVIFileGetStream(aviFile, &aviStream, streamtypeVIDEO, 0))
...
读取AVI文件时要使用库 vfw.h ,这个库针对需要用到视频的程序提供了接口。使用时不仅要包含这个库,还要对项目属性进行设置。打开项目属性页->配置属性->链接器->输入->附加依赖项,加入 vfw32.lib,否则编译时会报错。如图1所示。
创建好这些变量之后就可以使用 AVIFileOpen 函数打开文件了。使用这些函数的时候注意查看函数原型,参数是传递变量本身还是地址值,如图2。
打开文件之后,使用 AVIFileGetStream 函数,将第三个参数设为 streamtypeVIDEO 以打开视频流。这些函数如果没有正确地读取文件信息会返回非 0 值,判断一下是否成功读取从而方便调试,if 语句内部即不成功时的输出等略过,后面的代码也采取这样的方式。
另外需要说明一点的是,AVIFileOpen 函数中第二个参数这里使用了 char* 类型,为了能让这个函数正确读取这个字符串而不报错,需要打开项目属性页->配置属性->常规->字符集,选择“使用多字节字符集”,如图3所示。
//读取流的格式和信息
AVISTREAMINFO strInfo;//AVI数据流信息
BITMAPINFOHEADER info_h;//BMP信息头
LONG lStreamSize = sizeof(info_h);//数据流的大小
if (AVIStreamReadFormat(aviStream, 0, &info_h, &lStreamSize))
...
if (AVIStreamInfo(aviStream, &strInfo, sizeof(strInfo)))
...
//读取流的起始位置、长度和宽高
int start = AVIStreamStart(aviStream);
int len = AVIStreamLength(aviStream);
int width = info_h.biWidth;
int height = info_h.biHeight;
printf("The input file is %d x %d,%d frames @ %d Hz.\n",
width, height, len, strInfo.dwRate / strInfo.dwScale);
打开了视频流之后,就要从视频流中获取视频的一些基本信息。视频流信息使用 AVISTREAMINFO 类型。使用 BMP 文件的结构保存每一帧的信息,因此定义一个 BITMAPINFOHEADER 类型的信息头。从视频流中通过 AVIStreamStart 和 AVIStreamLength 函数获取视频流的起始位置和长度。
//为了简单,设每一帧都是24位R8G8B8的图像
info_h.biBitCount = 24;
info_h.biClrImportant = 0;
info_h.biClrUsed = 0;
info_h.biCompression = BI_RGB;
info_h.biHeight = height;
info_h.biPlanes = 1;
info_h.biSize = sizeof(info_h);
info_h.biWidth = width;
info_h.biXPelsPerMeter = 0;
info_h.biYPelsPerMeter = 0;
info_h.biSizeImage = width*height * 3;
AVI 文件的量化位数也像 BMP 那样有 8、16、24、32 位等,这里仅仅对常见的 24 位的 AVI 文件进行读取操作。
//得到帧指针
PGETFRAME pgf = AVIStreamGetFrameOpen(aviStream, &info_h);
//缓冲区建立略过
...
for (i = 0; i < len; i++)//len为视频流的长度,也就是有多少帧视频
{
BITMAPINFO* frameData = (BITMAPINFO*)AVIStreamGetFrame(pgf, i + start);//从视频流中取出一帧画面
if (frameData == NULL)
printf("Frame %d of %d failed.\n", i, len);//如果画面为空则提示
unsigned char* colorData = (unsigned char*)frameData->bmiColors;//每一帧中的颜色数据
if (frameData->bmiHeader.biWidth != width || frameData->bmiHeader.biHeight != height)//检查宽高是否一致
...
if (frameData->bmiHeader.biCompression != BI_RGB)//检查是否为RGB类型
...
使用 AVIStreamGetFrameOpen 函数从视频流中得到指向起始帧的指针 pgf,然后使用 AVIStreamGetFrame 函数循环地取出每一帧画面,帧数据中的 bmiColors 属性为颜色数据。
//由颜色指针获取RGB值
for (j = 0; j < height*width; j++)
{
*(rgbBuf + 3 * j) = *(colorData + 3 * j);//B
*(rgbBuf + 3 * j + 1) = *(colorData + 3 * j + 1);//G
*(rgbBuf + 3 * j + 2) = *(colorData + 3 * j + 2);//R
}
//RGB2YUV
if (RGB2YUV(width, height, rgbBuf, yBuf, uBuf, vBuf, 0))
//对YUV文件中超出16~240范围的值进行处理,写入文件,关闭缓冲区等略过
...
}
AVIFileRelease(aviFile);//释放AVI文件指针
...
得到颜色数据之后就可以依次获取帧中每一个像素的R,G,B值,写入到 rgbBuf 缓冲区中,再使用实验一中的 RGB2YUV 函数转成 YUV 数据,写入到文件中。最后的实验结果如下
AVI文件 | YUV文件的第一帧 | YUV文件的最后一帧 |
---|---|---|