【实验三】多媒体文件的读写和转换

实验三、多媒体文件的读写和转换

一、概述

       本次实验的目的是学会根据相关标准来解析一个多媒体文件(如AVI)。

       我认为本次的实验有两个重要的地方。其一是要通过相关资料了解AVI文件的结构以及相关的知识;而其二是要学会如何使用微软开发平台提供的AVI操作相关的API函数。

       其实,读得懂示例程序并会些封装好的函数就能很轻松的完成本次实验。但是相比于简单用用函数,我觉得好好了解一番AVI背后的故事会更有意义。

二、实验涉及的基本原理

       1.涉及原理汇总

       涉及原理有:AVI是什么,AVI封装格式以及微软提供的AVIFile Function的简单的用法。

       2.AVI是什么?

       首先,AVI是一种封装结构,与之类似的还有我们数字电视中提到的MPEG-2中的TS,它们都不是一种压缩算法。也就是说,如果你没有相应的解码器,即使解开了AVI的封装,你可能也没办法提取出RGB或YUV的分量。这也是为什么有一些从网上下载下来的AVI文件我们没办法直接解压缩的原因。例如,我用格式工厂这个软件压制了网上一个AVI文件,但是即使能读出视频流也没办法解压缩。用VS2013自带的二进制编辑器打开这个AVI文件,可以发现其fccHandler的位置为0x64697678(ASCII码为xvid),即应使用xvid解码器将其解码。想解码也很简单,这时候去网上安装一个相应的xvid解码器就OK了,操作系统会自己去调用这个解码器。之所以操作系统能够自动的去调用这个解码器也相当有趣,其中有一个叫VCM(Video Compression Manager)的东西作为应用程序与解码器之间的媒介。


       进一步地,我简单从初学者的角度来谈谈我对AVI的理解。AVI文件从物理上可以分为信息块,数据块,索引块,但实际操作的时候可能并不是这样理解的。在用AVIFile Function操作文件的过程中,我认为从抽象的层面上AVI文件应该理解为是文件层,视频流或音频流层以及数据层。我们用的函数更多是在流这一层操作,而实际的数据的走向是被封装而不易见的。这些流可能不是未经压缩的RGB分量,甚至可能连视频都不是而是音频甚至其他的。

       再说句题外话,AVI这种封装格式某种程度上说是有弊端的。例如,它的索引块在文件的最后面,这不利于网络传输。这是AVI被认为落后的一点,而数字电视中运用的TS格式从一开始便考虑到了网络传输的情况。

       3.AVI封装格式

       一个AVI文件在内存中究竟是怎么样保存的呢?首先分成了三块,如下图所示:


       其中文件头的结构如下:


       而信息块的结构如下:


       了解了信息块的结构,我们就能很轻易的获取待处理AVI文件的所有信息了。如此便能,通过AVIFile  Function系列的函数我们就能很顺利的去操作AVI文件了。

       想要更加深入的了解的同学,我推荐大家去阅读一下以下这两篇博客,这两篇博客解释得比我更加清楚~

       AVI格式详细解析     

       AVI文件格式图表描述

       有条件的还可以去网上查一查很多早期的期刊文章,其中也有不少关于AVI封装格式的介绍。

       [1]黄东军,贺宏遵. AVI文件结构的实例分析[J]. 企业技术开发,2008,(03):3-6.

       [2]徐殿武. AVI文件格式及其应用研究[J]. 现代电子技术,2008,(02):119-122.

       4.微软提供的AVIFile Function的简单的用法

       在微软的MSDN(Microsoft Developer Network)中即可在线查到所有相关函数的用途和注释。

       AVIFile Reference

       AVIFile Function

三、实验的流程分析

       1.实验大体的流程

       (1)用AVIFileInit()函数来初始化相关的库(对应的应用AVIFileExit()来释放这个库);

       (2)用AVIFileOpen()函数打开一个AVI文件(对应的应用AVIFileRelease()来释放);

       (3)用AVIFileGetStream()函数来获得一个Stream(这个Stream需要用AVIStreamRelease()来释放);

       (4)用AVIStreamGetFrameOpen()函数来准备一个GetFrame对象,以准备对Stream中的数据解压缩()(对应的,应用AVIStreamGetFrameClose()来释放掉);

       (5)用AVISteamGetFrame()函数来获得已解压缩的一帧图像数据;

      2.流程图


四、关键代码及分析

       1.关键代码概述

       我觉得较为关键的一步便是使用AVIStreamReadFormat()获得了待处理视频流的位图信息后,根据自己的需求,设置解压缩参数,并通过初始化一个新的BITMAPINFOHEADER结构体bih_wanted的方式去解压缩出需要的一帧图像。

       2.bih_wanted

//initialize bih_wanted and buffer
	width = bih.biWidth;
	height = bih.biHeight;
	size = width*height;
	bih_wanted = bih;
	bih_wanted.biBitCount = 24;
	bih_wanted.biClrImportant = 0;
	bih_wanted.biClrUsed = 0;
	bih_wanted.biCompression = BI_RGB;
	bih_wanted.biHeight = height;
	bih_wanted.biPlanes = 1;
	bih_wanted.biSize = sizeof(bih_wanted);
	bih_wanted.biWidth = width;
	bih_wanted.biXPelsPerMeter = 0;
	bih_wanted.biYPelsPerMeter = 0;

       这一部分代码会根据原本视频流中的图像信息初始化解码后的图像信息,初始化为宽高不变,图像深度为24bit的RGB形式。

       3.解压缩并转换为YUV形式输出

//process the frame
	/*prepare to get the decompressed frame we want*/
	pFrame = AVIStreamGetFrameOpen(pStream,&bih_wanted);
	if (pFrame == NULL)
	{
		printf("we cann't get the frame.\n");
	}
	/*process all frames*/
	for (i = 0; i < streamLth;i++)
	{
		/*get a decompressed frame data*/
		frameData = (BYTE*)AVIStreamGetFrame(pFrame,i+streamSrt);
		bmpInfo = (BITMAPINFO*) frameData;
		colorData = (BYTE*)bmpInfo->bmiColors;
		
		/*put the color data into rgbBuf*/
		for (j = 0; j < size * 3;j++)
		{
			rgbBuf[j] = colorData[j];
		}

		/*convert rgb to yuv and write it into yuvFile*/
		RGB2YUV(width, height, rgbBuf,yBuf,uBuf,vBuf,flip);
		for (j = 0; j < size; j++)
		{
			if (yBuf[j] < 16) yBuf[j] = 16;
			if (yBuf[j] > 235) yBuf[j] = 235;
		}
		for (j = 0; j < size / 4; j++)
		{
			if (uBuf[j] < 16) uBuf[j] = 16;
			if (uBuf[j] > 240) uBuf[j] = 240;
			if (vBuf[j] < 16) vBuf[j] = 16;
			if (vBuf[j] > 240) vBuf[j] = 240;
		}
		fwrite(yBuf, 1, size, yuvFile);
		fwrite(uBuf, 1, size/4, yuvFile);
		fwrite(vBuf, 1, size/4, yuvFile);
	}

       值得注意的是在AVIStreamGetFrameOpen()这段函数中便会对图像准备解压缩,如果没有安装对应的解码器将无法得到pFrame对象。

五、实验结果

       本次实验我用了3种不同压缩方式的avi文件

       4000k_1.yuv是一个4000k码率的1920x1080的主观实验测试序列,是用ffmpeg以copy的形式进行封装的。使用的是I420编解码器。

       clock_1.avi是老师发的示例程序,使用MSVC编解码器的。

       而0.avi则是从视频网站上下载的,使用xvid编解码器。尤其令人注意的是,0.avi在我安装xvid解码器前并不能完成解压缩,在AVIStreamGetFrameOpen()这一步便会报错退出。在PC上安装上下载的xvid编解码器后便能正常解压缩了。

       xvid这个编解码器是一个开源的MPEG-4编解码器,其诞生的背后还有很多很多引人入胜的小故事。


六、结论

       网上有相当多类似的示例程序而且善用MSDN,想要实现将AVI格式视频提取YUV分量保存这一功能问题还是不大的。

       但是做完了本次实验我还是有些没搞明白的问题。数据块里面是怎么存放视音频流的?索引块里面的信息又要如何使用呢?如果没有AVIFile Function系列的函数,我该怎么去读出我想要的东西?

       源代码如下:

【注】需要将VS2013的默认的Unicode字符集修改

【注】需要将之前的rgb2yuv.h 与 rgb2yuv.cpp包含到工程项目中

#pragma comment(lib,"vfw32.lib ") 

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<windows.h>
#include<vfw.h>
#include"rgb2yuv.h"

int main(int argc,char** argv)
{
//definition
	/*file*/
	PAVIFILE paviFile = NULL;
	FILE* yuvFile = NULL;
	char* yuvFileName = NULL;
	/*struct*/
	PAVISTREAM pStream = NULL;
	PGETFRAME pFrame = NULL;
	BITMAPINFOHEADER bih = {NULL};
	BITMAPINFOHEADER bih_wanted = {NULL};
	BITMAPINFO* bmpInfo = {NULL};
	/*variable*/
	BYTE* frameData = NULL;
	BYTE* colorData = NULL;
	LONG bihSize = NULL;
	int streamSrt = NULL;
	int streamLth = NULL;
	int width, height,size = NULL;
	int flip = FALSE;
	/*buffer*/
	unsigned char *yBuf,*uBuf,*vBuf = NULL;
	unsigned char *rgbBuf = NULL;
	/*counter*/
	int i,j,res = NULL;

//process command line
	yuvFileName = argv[2];

//process the library
	/*open library*/
	AVIFileInit();

//process the files
	/*open yuvfile*/
	yuvFile = fopen(yuvFileName,"wb");

	/*open avifile*/
	res = AVIFileOpen(&paviFile, argv[1], OF_READ, NULL);
	if (res != 0)
	{
		printf("we cann't open the aviFile.\n");
	}

//process the stream
	/*get the video stream*/
	res = AVIFileGetStream(paviFile, &pStream, streamtypeVIDEO, 0);
	if (res != 0)
	{
		printf("we cann't get the stream.\n");
	}

	/*get the stream format*/
	bihSize = sizeof(bih);
	AVIStreamReadFormat(pStream,0,&bih,&bihSize);
	streamSrt = AVIStreamStart(pStream);
	streamLth = AVIStreamLength(pStream);

	printf("startposition = %d;\n length = %d\n",streamSrt,streamLth);
	printf("width = %d;\nheight = %d;\n",bih.biWidth,bih.biHeight);
	printf("bitCount = %d\n",bih.biBitCount);

//initialize bih_wanted and buffer
	width = bih.biWidth;
	height = bih.biHeight;
	size = width*height;
	bih_wanted = bih;
	bih_wanted.biBitCount = 24;
	bih_wanted.biClrImportant = 0;
	bih_wanted.biClrUsed = 0;
	bih_wanted.biCompression = BI_RGB;
	bih_wanted.biHeight = height;
	bih_wanted.biPlanes = 1;
	bih_wanted.biSize = sizeof(bih_wanted);
	bih_wanted.biWidth = width;
	bih_wanted.biXPelsPerMeter = 0;
	bih_wanted.biYPelsPerMeter = 0;
	/*malloc the buffer*/
	yBuf = (unsigned char*)malloc(size*sizeof(unsigned char));
	uBuf = (unsigned char*)malloc(size / 4 * sizeof(unsigned char));
	vBuf = (unsigned char*)malloc(size / 4 * sizeof(unsigned char));
	rgbBuf = (unsigned char*)malloc(3*size*sizeof(unsigned char));

	

//process the frame
	/*prepare to get the decompressed frame we want*/
	pFrame = AVIStreamGetFrameOpen(pStream,&bih_wanted);
	if (pFrame == NULL)
	{
		printf("we cann't get the frame.\n");
	}
	/*process all frames*/
	for (i = 0; i < streamLth;i++)
	{
		/*get a decompressed frame data*/
		frameData = (BYTE*)AVIStreamGetFrame(pFrame,i+streamSrt);
		bmpInfo = (BITMAPINFO*) frameData;
		colorData = (BYTE*)bmpInfo->bmiColors;
		
		/*put the color data into rgbBuf*/
		for (j = 0; j < size * 3;j++)
		{
			rgbBuf[j] = colorData[j];
		}

		/*convert rgb to yuv and write it into yuvFile*/
		RGB2YUV(width, height, rgbBuf,yBuf,uBuf,vBuf,flip);
		for (j = 0; j < size; j++)
		{
			if (yBuf[j] < 16) yBuf[j] = 16;
			if (yBuf[j] > 235) yBuf[j] = 235;
		}
		for (j = 0; j < size / 4; j++)
		{
			if (uBuf[j] < 16) uBuf[j] = 16;
			if (uBuf[j] > 240) uBuf[j] = 240;
			if (vBuf[j] < 16) vBuf[j] = 16;
			if (vBuf[j] > 240) vBuf[j] = 240;
		}
		fwrite(yBuf, 1, size, yuvFile);
		fwrite(uBuf, 1, size/4, yuvFile);
		fwrite(vBuf, 1, size/4, yuvFile);
	}
	

//clean up
	AVIStreamGetFrameClose(pFrame);
	free(vBuf);
	free(uBuf);
	free(yBuf);
	AVIStreamRelease(pStream);
	AVIFileRelease(paviFile);
	fclose(yuvFile);
	AVIFileExit();
	system("pause");
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值