The Learning of OpenCV
					

Here contains just some personal notes for learning OpenCV and inevitably most parts of them are from their sample codes.
Here is the first test of adaptive-threshold. 
#include <cv.h>
#include <highgui.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <windows.h>
#include <tchar.h>
#include <time.h>

#pragma comment(lib, "cvd.lib")
#pragma comment(lib, "cxcored.lib")
#pragma comment(lib, "highguid.lib")


void myContour(LPTSTR szFileName)
{
	const int Max_Threshold = 2;
	const int MyStep = 255;
	const char* pszWndName[] = {"pSrcImage", "cvPyrDown", "cvPyrUp", "cvCopy", "pDst", "contour", "clone"};
	IplImage* pSrcImage = NULL;
	CvMemStorage* pMem = NULL;
	CvSize sz;
	IplImage* pImage = NULL;
	IplImage* pSrc, *pDst = NULL;
	IplImage* pPyramid = NULL;
	CvSeq* pHeader = NULL;
	IplImage* pContour = NULL;
	IplImage* pEmpty = NULL;
	IplImage* pClone = NULL;

	int i, j;

	pImage = cvLoadImage(szFileName, 1);

	pMem = cvCreateMemStorage(0);

	sz.width = (pImage->width&-2);
	sz.height = (pImage->height&-2);

	pSrcImage = cvCreateImage(sz, 8, 3);

	cvSetImageROI(pImage, cvRect(0, 0, sz.width, sz.height));

	cvCopy(pImage, pSrcImage, 0);

	pPyramid = cvCreateImage(cvSize(sz.width/2, sz.height/2), 8, 3);

	pSrc = cvCreateImage(sz, 8, 1);

	pDst = cvCreateImage(sz, 8, 1);

	//cvSetImageROI(pImage, cvRect(0,0,sz.width, sz.height));

	for (i = 0; i < sizeof(pszWndName)/sizeof(const char*); i ++)
	{
		cvNamedWindow(pszWndName[i], 1);
	}

	cvShowImage(pszWndName[0], pSrcImage);

	cvPyrDown(pSrcImage, pPyramid, 7);

	cvShowImage(pszWndName[1], pPyramid);

	cvPyrUp(pPyramid, pSrcImage, 7);

	cvShowImage(pszWndName[2], pSrcImage);

	//cvWaitKey(0);

	cvSetImageROI(pSrcImage, cvRect(0, 0, sz.width, sz.height));
	for (i = 0; i < 3;  i ++)
	{
		cvSetImageCOI(pSrcImage, i + 1);
		cvCopy(pSrcImage, pSrc, 0);

		cvShowImage(pszWndName[3], pSrc);

		for (j = 1; j < Max_Threshold; j ++)
		{
			switch (j)
			{
			case 0:
				cvCanny(pSrc, pDst, 200, 255, 5);				
				// dilate canny output to remove potential
				// holes between edge segments 
				cvDilate(pDst, pDst, 0, 1 );
				break;
			case 1:
				
				//
				cvAdaptiveThreshold(pSrc, pDst, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 7, 2);
				break;
			default:
				cvThreshold(pSrc, pDst, (j+1)*MyStep/Max_Threshold, MyStep, CV_THRESH_BINARY);
				break;

			}


			pClone = cvCloneImage(pDst);
			pContour = cvCreateImage(cvSize(pDst->width, pDst->height), 8, 3);
			pEmpty = cvCreateImage(cvSize(pDst->width, pDst->height), 8, 3);


			for (int k = 0; k < 4;  k ++)
			{
				cvFindContours(pClone, pMem, &pHeader, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
				
				if (pHeader)
				{
					if (k == 0)
					{
						cvDrawContours(pEmpty, pHeader, cvScalarAll(0), cvScalarAll(0), 100, 1);
					}
					//cvDrawContours(pContour, pHeader, cvScalarAll(0), cvScalarAll(0), 100, 1);
					cvDrawContours(pClone, pHeader, cvScalarAll(0), cvScalarAll(0), 100, 1);
				}
			}
		
			if (pHeader)
			{
				cvDrawContours(pContour, pHeader, cvScalarAll(0), cvScalarAll(0), 100, 1);
				//cvDrawContours(pClone, pHeader, cvScalarAll(0), cvScalarAll(0), 100, 1);
			}	

		

			cvShowImage(pszWndName[5], pContour);
			cvShowImage(pszWndName[4], pDst);
			cvShowImage(pszWndName[6], pEmpty);
		
			cvWaitKey(0);
		}
	}
	cvReleaseImage(&pSrcImage);
	cvReleaseImage(&pImage);
	cvReleaseImage(&pSrc);
	cvReleaseImage(&pDst);
	cvReleaseImage(&pPyramid);
	cvReleaseImage(&pContour);
	cvReleaseImage(&pClone);
	cvReleaseImage(&pEmpty);


	cvDestroyAllWindows();
}

typedef int (*HandleFileCallBack)(char*dir, char* fileName, void* userData);

int myContourCallback(LPTSTR dir, LPTSTR fileName, void* userData)
{
	TCHAR szNameFile[MAX_PATH];
	LPTSTR ptr = NULL;
	static int counter = 0;
	if (rand()% 3 ==0)
	{
		if ((ptr = _tcsrchr(fileName, _T('.')))!= NULL)
		{
			if (_tcsicmp(ptr, _T(".jpg"))==0)
			{
				_stprintf(szNameFile, "%s\\%s", dir, fileName);
				myContour(szNameFile);
			}
		}
	}
	return 0;
}

int genericFind(TCHAR* dir, HandleFileCallBack handleFileCallBack, void* userData)
{
	HANDLE handle;
	int counter = 0, temp;
	TCHAR curFileName[MAX_PATH];
	TCHAR wildFileName[MAX_PATH];
	
	WIN32_FIND_DATA ffd;

	_sntprintf(wildFileName, MAX_PATH, _T("%s\\*.*"), dir);
	handle=FindFirstFile(wildFileName, &ffd);
	if (handle==INVALID_HANDLE_VALUE)
	{
		_tprintf(_T("findfirst failed of error code =%d\n"), GetLastError());
		exit(19);
	}
	do
	{	
		if (_tcsicmp(ffd.cFileName, _T("."))!=0 && _tcsicmp(ffd.cFileName, _T(".."))!=0)
		{
			_stprintf(curFileName, _T("%s\\%s"), dir, ffd.cFileName);
			if  (GetFileAttributes(curFileName)&FILE_ATTRIBUTE_DIRECTORY)
			{
				counter += genericFind(curFileName, handleFileCallBack, userData);
			}
			else
			{		
				// check extention name
				if ((temp = handleFileCallBack(dir, ffd.cFileName, userData)) == -1)
				{
					counter = -1;
					break;
					
				}
				else
				{
					counter += temp;
				}
			}
		}
	}
	while (FindNextFile(handle, &ffd));	
	
	FindClose(handle);
	return counter;
}


int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
	//CommandLineToArgvW(GetCommandLine(), &argc);
	srand(time(0));
	genericFind("f:\\downloaded\\ladytang", myContourCallback, NULL);
	return 0;

}
 
还是谷歌拼音好用。摘录了一下资料
http://blog.ednchina.com/FIB/20481/category.aspx
ASF学习笔记
VIA:  http://hi.baidu.com/koko200147/blog/item/cfea4af4008a44ddf3d38526.html

 

设置(Profile)

一个设置是一个ASF的配置(configuration)的描述数据集合。一个设置必须至少包含一个流的配置设置。

流信息
设置中的流信息包含流的比特率(bit rate),缓冲窗口和媒体属性的设置。视频和音频的流信息准确描述了文件中的媒体配置,包括压缩数据使用的编码和解码器(如果有的话)。

一个设置也包含很多创建ASF文件时使用的ASF的特性,这包括互斥、媒体优先级、带宽共享和数据单位扩展。

每次写文件时必须提供设置。你可以调用IWMWriter::SetProfile指定一个设置。

设置有三种形式,应用程序中设置对象包含的数据,XML文件,或者ASF文件头。

设置对象
可以用设置管理器创建空设置对象,然后从现有数据载入设置

XML文件
具有PRX扩展名.注意Windows Media 9 Series 中没有原来的系统设置(system profiles)也不再使用,而作为这种形式存在。保存自定义设置时必须保存成这种文件。

ASF文件头
ASF读者创建一个设置对象,然后从ASF文件头载入格式信息。但是修改文件头不会影响文件的内容。可以重新对文件编码来完成格式的修改。

使用设置编辑器
除了用Windows Media Format SDK之外,还可以用Windows Media Encoder 9 Series中包含的设置编辑器创建设置。在应用程序中使用IWMProfileManager::LoadProfileByData载入预定义的设置。但是,启用“视频大小:和输入相同”这个选项将设置视频的大小为0;Windows Media Encoder 9 Series可以识别并且处理这种情况,但是Windows Media Format SDK的写入对象不会自动处理,所以应用程序必须并且处理这种情况.

下面是一个XML格式的配置

<profile version="589824" storageformat="1" name="ICW" description="ICW Stream">
// 73647561-0000-0010-8000-00AA00389B71 'auds' == WMMEDIATYPE_Audio
<streamconfig majortype="{73647561-0000-0010-8000-00AA00389B71}" streamnumber="1" streamname="Audio Stream" inputname="Audio804" bitrate="1411200" bufferwindow="-1" reliabletransport="0" decodercomplexity="" rfc1766langid="zh-cn">
   // 00000001-0000-0010-8000-00AA00389B71            WMMEDIASUBTYPE_PCM
   <wmmediatype subtype="{00000001-0000-0010-8000-00AA00389B71}" bfixedsizesamples="1" btemporalcompression="0" lsamplesize="4">
    <waveformatex wFormatTag="1" nChannels="2" nSamplesPerSec="44100" nAvgBytesPerSec="176400" nBlockAlign="4" wBitsPerSample="16" />
   </wmmediatype>
</streamconfig>
// 73647561-0000-0010-8000-00AA00389B71 'auds' == WMMEDIATYPE_Audio
<streamconfig majortype="{73646976-0000-0010-8000-00AA00389B71}" streamnumber="2" streamname="Video Stream" inputname="Video804" bitrate="4000" bufferwindow="1000" reliabletransport="0" decodercomplexity="AU" rfc1766langid="zh-cn">
   <videomediaprops maxkeyframespacing="80000000" quality="35" />
   // 56555949-0000-0010-8000-00AA00389B71 'YV12' == MEDIASUBTYPE_IYUV
   <wmmediatype subtype="{56555949-0000-0010-8000-00AA00389B71}" bfixedsizesamples="1" btemporalcompression="0" lsamplesize="0">
    <videoinfoheader dwbitrate="4000" dwbiterrorrate="0" avgtimeperframe="1000000">
     <rcsource left="0" top="0" right="0" bottom="0" />
     <rctarget left="0" top="0" right="0" bottom="0" />
     <bitmapinfoheader biwidth="0" biheight="0" biplanes="1" bibitcount="12" bicompression="IYUV" bisizeimage="0" bixpelspermeter="0" biypelspermeter="0" biclrused="0" biclrimportant="0" />
    </videoinfoheader>
   </wmmediatype>
</streamconfig>
// 73636d64-0000-0010-8000-00AA00389B71 'scmd' == WMMEDIATYPE_Script
<streamconfig majortype="{73636D64-0000-0010-8000-00AA00389B71}" streamnumber="3" streamname="Script Stream" inputname="Script804" bitrate="2560" bufferwindow="-1" reliabletransport="0" decodercomplexity="" rfc1766langid="zh-cn">
   <wmmediatype subtype="{00000000-0000-0000-0000-000000000000}" bfixedsizesamples="0" btemporalcompression="0" lsamplesize="0">
   // 82f38a70-c29f-11d1-97ad-00a0c95ea850        WMSCRIPTTYPE_TwoStrings
   <WMSCRIPTFORMAT scripttype="{82F38A70-C29F-11D1-97AD-00A0C95EA850}" />
   </wmmediatype>
</streamconfig>
</profile>

媒体采样(Media Sample)
媒体采样,或者采样,是一块数字媒体数据。采样是Windows Media Format SDK可以读写的数据的最小单位。采样内容由采样相关的媒体类型指出。对于视频,每个采样表示一个桢,每个单独采样中包含的数据量由创建ASF时指定的设置设置。
采样可以包含未压缩的数据,或者压缩过的数据,这时被称为流采样。创建ASF时,采样被传递给写入对象,写入对象使用相关的编码器压缩数据,并且写入ASF文件的数据段。播放时,读出对象读出压缩的数据,解压数据,并且提供未压缩格式的数据。
采样被封装在Windows Media Format SDK的自动分配的缓冲区对象中。需要的时候,你也可以自己分配缓冲区对象,使用它的读写特性。
这里的采样并非音频采样。通常,音频采样质量用每秒录制的采样数据数量表示,例如CD质量是44,100采样/秒,或者44.1 kHz。

输入,流和输出
输入对象是你用于写入文件的任何数字媒体流,必须是可以支持的格式。支持很多标准RGB和YUV作为视频输入格式,PCM作为音频输入格式。如果编码器不支持某种输入格式,那么写入对象会初始化一个辅助对象,转换输入流到可以支持的格式,例如调整色深转换、缩放,调整声音质量、采样率和频道数目。某些情况下,压缩格式的食品和音频可用于输入。输入也可以是其他格式,例如文字,脚本命令,图像,或者任意文件数据。

输出是读取对象传递给应用程序,提供用户体验的数据。一个输出等同于一个流。如果你使用互斥属性,那么所有互斥数据共享一个输出。

一个流是一个ASF文件中包含的数据。一个流的生命期中只有一种压缩设置。一个简单的ASF具有两种流:视频和音频。更加复杂的ASF文件可以包含两路音频和多路视频。音频可以有同样的压缩设置,但是内容不同,例如不同语言的讲解;视频可以有同样的内容,但是具有不同的压缩比例。格式是在设置对象中指定的。

某些输入可以是压缩过的,这时读取对象必须以流编号依次访问数据,而不是按输出顺序访问数据。

编号
流具有从1开始的编号,这是在设置中指定的。同时,流具有一个索引以在设置中枚举流。这两个数字并不相关,例如输入1并不一定是编号为1的流,编号为1的流并不一定是输入1,等等。

格式
每种媒体类型的全部信息。每个格式有一个主类型,例如音频或视频,并且可能有一个子类型。格式包含依赖于主类型的不同信息。视频和音频格式比其他格式需要更多信息。

输入格式
描述你传递给写入对象的数字媒体类型。如果ASF文件中的流是用编码器压缩,那么编码器只支持某些输入格式。使用Windows Media 音频和视频编码器时,可以使用写入对象枚举支持的输入格式。写到文件时,你有责任选择一个匹配输入媒体的输入格式。
某些格式不必匹配编码器指明的输入格式,编码器可以自行转换数据到需要的格式。

流格式
ASF文件中的数据保存形式。在设置中描述,可以符合或不符合输入、输出格式(例如使用了某种编码/解码器)。可能必须获得编码/解码器信息之后,才可以设置流格式

输出格式。
描述你传递给读出对象的数字媒体类型。如果ASF文件中的流是用编码器压缩,那么编码器只支持某些输出格式。使用Windows Media 音频和视频编码器时,可以使用读出对象枚举支持的输出格式。读出文件时,你有责任选择一个匹配输出媒体的输出格式。
某些格式不必匹配编码器指明的输出格式,编码器可以自行转换数据到需要的格式。

比特率(Bit Rate)
每秒传递给ASF的数据的数量,以位/秒(bps)或者千位/秒(kbps)为单位。经常与带宽混淆,带宽也以bps或者kbps为单位。
如果用户的带宽小于ASF的比特率,那么播放可能中断。通常,带宽不足会导致跳过某些采样,或者更多的数据缓冲时间。
每个ASF文件创建时被指定一个比特率,它基于文件中流的数量。不同的流可以有不同的比特率。比特率可以是常数(压缩的数据可以以基本同样的速度被传输)或者可变(保留压缩的数据质量,即使可能造成突发数据溢出)。
同一个内容可以被压缩成多个比特率不同的流,然后你可以配置他们为互斥的。这个属性叫多比特率(multiple bit rate), 或者MBR.

元数据
描述ASF文件或者文件内容的信息,位于文件头。元数据的项称为属性。每一个属性由名字和值组成。全局常数用于标识属性,例如ASF 文件的标题被保存在 g_wszWMTitle 属性中。在Windows Media Format SDK 中定义了最常用的内建属性,但是你也可以定义自己的属性。由于其他开发者可能和你是用同样的名字,所以可能造成冲突。
一些全局属性可以被修改,例如g_wszWMSeekable属性(文档是否可以从任意点被读取)
一些属性纯粹用于信息用途,并且必须被设置,例如g_wszWMAuthor属性(作者)
属性可以被应用到整个文件或者单独的流。
你可以用Windows Media Format SDK编辑MP3文件的元数据,但是必须使用ID3-compliant属性保留与其他MP3应用程序的兼容性。

媒体时间
自第一个采样开始的时间计量方式,单位和SDK其他时间的单位一样,是100纳秒。它使得文件中不同的流可以被同步。你写入的每一个采样都必须有媒体时间。ASF文件数据段中每一个数据对象都有媒体时间。每一个输出的数据也都有媒体时间。

缓冲
读取对象打开流文件时从文件头的信息决定缓冲区大小。实际比特率是变化的,但是平均值应该是设置中指定的值。

缓冲窗口是以可以缓冲的数据时间长度来衡量的。例如,32Kbps的流,3秒的缓冲窗口,意味着缓冲区大小为 12,000字节(32000*3/8)。解码器限制了这个数值,所以缓冲窗口的平均比特率不大于流的比特率。
通常在设置中指定这个值,写入对象处理剩下的部分。写入压缩数据到流时,必须自己确定写入的速度不会超出这个值

ASF文件中的段
一个ASF文件中的段以对象的方式组织起来。一共有三种顶层对象,必须有的头对象(Head),数据对象(Data),以及可选的索引对象(Index)。

每个对象都以全球唯一标志(GUID)和大小开始。这些数字使得文件读者可以解析这些信息,并且载入到相应的对象。因为这些GUID,底层的对象可以以任何顺序排列,并且仍然可以被识别。这使得一个不完整的ASF文件仍然可被正确读取,只要有一个完整的文件头和至少一个数据对象。某些对象,例如流属性对象,可能有多个示例。

头对象包含文件的描述信息,同时是唯一的顶层对象容器。

数据对象以包的格式存储流数据。数据对象还具有文件ID和包总个数属性,但是对于流格式,包总个数属性没有意义。

每一个数据包包含发送时间和持续时间。这使得读者可以发现流传输的中断。
数据包的数据被封装到载荷(payloads)中。一个载荷可以包含一个或者多个媒体对象(media objects),媒体对象的一个例子是视频流的一个桢。大的媒体对象,例如视频流的一个关键桢,可能被扩展到多个载荷,甚至多个包。为了跟踪对象的片断,每个对象的段具有从0到255的编号。
除了数据之外,载荷也具有以毫秒为单位的时间戳。
所有的包具有头对象中指定的统一的大小。当一个包包含的数据少于指定大小时,用数据("padding" data )填充不足部分。

索引对象包含时间《-》关键桢的配对,以更有效地在文件中定位。因为它处于文件末尾,实时媒体不能访问这个对象。

使用回调方法
一些Windows Media Format SDK的接口的方法是异步执行的,很多这样的方法使用回调方法和应用程序通讯。

使用OnStatus回调
在Windows Media Format SDK中,IWMStatusCallback::OnStatus 被很多对象调用。OnStatus接收SDK操作状态的变化。每种对象可能有不同的方式连接到IWMStatusCallback。

使用事件进行同步调用
1 使用Platform SDK的API CreateEvent创建一个事件对象
2 实现回调函数,,捕获事件,并且调用SetEvent函数标记事件对象
3 在应用程序中调用WaitForSingleObject 、监视事件对象。如果你是在为Windows程序编写代码,你必须创建一个消息循环对用户操作做出相应。

使用上下文参数
Windows Media Format SDK的一些回调函数具有pvContext参数,这个值是你在异步操作启动时传递给对象的。
通常,多个对象使用同一个回调时传递对象指针作为这个参数。

使用设置
设置的主要目的是描述其中的对象,以及对象之间的关系。不管是否使用编码/解码器,某些流需要配置才可以工作。流的配置信息可以用IWMCodecInfo3 接口的方法获得,但是不要手动配置一个使用了Windows Media编码/解码器的流。
创建/编辑设置的步骤
1 创建空设置,或者打开旧设置
2 配置每个流,如果需要的话,使用从编码/解码器获得的数据
3 配置互斥(可选)
4 配置带宽共享(可选)
5 配置优先级(可选)

设计设置

选择编码方式
1-pass Constant Bit Rate (CBR) 直播的唯一选择。以预定的码流率编码,并且质量最低。
2-pass CBR 文件形式的流媒体,长度固定,质量比1-pass Constant Bit Rate (CBR)好
1-pass Variable Bit Rate (VBR) 需要指定质量时使用,本地播放或者下载后播放
2-pass VBR – unconstrained 需要指定带宽时使用,但是真实带宽占用可以偏离指定带宽,本地播放或者下载后播放
2-pass VBR – constrained 需要指定带宽时使用,但是真实带宽占用不能大于指定带宽,本地播放或者下载后播放

码流率
除了数据之外,分包也要占用一定的带宽。如果流包含数据单位扩展,那么这将大大增加流的码流率。
同时,除了应用程序之外的任何连接都和应用程序共享网络带宽,所以不能认为应用程序可以完全使用客户的网络带宽。

配置流
如果流是视频/音频,使用Windows Media编码/解码器,那么你必须使用IWMCodecInfo3的方法从编码/解码器获得流配置对象。
如果流是其他类型,使用IWMProfile::CreateNewStream.创建一个新的流配置对象。
每个流配置都必须设置名字、连接名和流序号(从1到63)。
可能会修改使用Windows Media编码/解码器的two-pass VBR 音频流的VBR设置。视频流无需修改配置。
根据类型配置其他类型流。所有的这种流需要设置比特率和缓冲窗口。
使用IWMProfile::AddStream. 将流添加到媒体。
大部分设置可以通过IWMMediaProps访问。这些设置保存在WM_MEDIA_TYPE 结构中。对于音频和视频,WM_MEDIA_TYPE结构指针指向媒体特定的更多信息,通常是WAVEFORMATEX 或者WMVIDEOINFOHEADER结构。视频有第三个结构BITMAPINFOHEADER描述了视频的桢。

从编码/解码器获得流配置信息
使用Windows Media编码/解码器的视频/音频流需要从编码/解码器获得流配置信息。尽管你可以自行设置这些配置,从编码/解码器获得流配置信息使得数据是准确的。除非文档推荐,否则不要修改获得的流配置信息。
可以从设置管理器的IWMCodecInfo, IWMCodecInfo2, 和IWMCodecInfo3接口获得信息。

枚举安装的编码/解码器
编码/解码器的编号从0开始,音频和视频的编码/解码器有独立的编号。

枚举编码/解码器支持的格式

配置音频流
不要手动修改获得的配置的质量设置,而应该用IWMPropertyVault接口修改。
音频流的缓冲窗口不应该设置得比视频流的缓冲窗口大,否则会造成播放不同步。通常,音频流的缓冲窗口是1.5-3秒,视频流的缓冲窗口是3-5秒。

配置视频流
除非是RGB24数据,否则大小应该是4的倍数,否则会有非法格式/非法配置等错误。

配置屏幕流
和视频流一样,但是如果复杂度设置为0,那么IWMVideoMediaProps::SetQuality设置的质量会被忽略。

图像流
包含JPEG形式的图像数据。

视频流的定位性能
可以使用IWMVideoMediaProps::SetMaxKeyFrameSpacing设置关键桢间隔。增加关键桢数目会降低视频质量。

未压缩的音视频格式
不能用于流,必须手动设置带宽,缓冲窗口应该设为0

配置其他流
通常,这种流只需要比特率和缓冲窗口和WM_MEDIA_TYPE 中的媒体主类型设置。但是某些类型的流还需要其他设置

脚本流
WM_MEDIA_TYPE的成员formattype 要设置为WMFORMAT_Script,指明pbFormat成员指向一个WMSCRIPTFORMAT 结构。
只有一种脚本媒体类型,WMSCRIPTTYPE_TwoStrings。

文件传输流
每个采样需要一个数据单位扩展,你需要实现一个数据单位扩展系统。
调用IWMStreamConfig2::AddDataUnitExtension添加数据单位扩展到流。
hr = pStreamConfig2->AddDataUnitExtension(CLSID_WMTPropertyFileName,
                                          -1, NULL, 0);

网页流
WM_MEDIA_TYPE.majortype WMMEDIATYPE_Filetransfer.
WM_MEDIA_TYPE.subtype WMMEDIASUBTYPE_WebStream.
WM_MEDIA_TYPE.bFixedSizeSamples False.
WM_MEDIA_TYPE.bTemporalCompression True.
WM_MEDIA_TYPE.lSampleSize 0.
WM_MEDIA_TYPE.formattype WMFORMAT_WebStream.
WM_MEDIA_TYPE.pUnk NULL.
WM_MEDIA_TYPE.cbFormat sizeof(WMT_WEBSTREAM_FORMAT).
WM_MEDIA_TYPE.pbFormat 一个配置好的WMT_WEBSTREAM_FORMAT结构的指针.
WMT_WEBSTREAM_FORMAT.cbSampleHeaderFixedData sizeof(WMT_WEBSTREAM_SAMPLE_HEADER).
WMT_WEBSTREAM_FORMAT.wVersion 1.
WMT_WEBSTREAM_FORMAT.wreserved 0.

文本流
媒体类型WMMEDIATYPE_TEXT

计算比特率和缓冲窗口
简单的办法是设置为数据长度/时间.但是图像和文件流可能突发数据很多,但是有很多空闲时间.缓冲窗口必须设置得足够大.需要的时候,可以适当增加这些值.

变码流率流

数据单位扩展

保存/重新使用配置
不要手动更改PRX文件。看起来很小的改变会使得配置无效。

互斥

流优先级

带宽共享

包大小

写ASF文件
使用IWMWriter::SetProfile对写入对象进行设置。但是,设置了之后,对设置对象的修改不会自动反映到写入对象,除非再次调用IWMWriter::SetProfile。
设置写入对象会复位全部头属性,所以必须在设置之后再修改这些属性。

输入

设置对象中的每个连接有一个输入号。除非配置中有互斥流,否则每个流有一个连接。互斥流共享连接。
写入流时需要用输入号来区别每个流,所以必须用连接名字来判断每个流的输入号。

枚举输入格式
SDK可以对输入进行预处理来判断输入的格式是否支持。

设置输入格式
找到符合数据的输入格式之后,可以调用IWMWriter::SetInputProps让它可以被写入对象使用。对于视频流,必须设置桢的大小。

其他类型的流和预压缩流
其他类型的流无需设置。
预压缩流需要设置输入格式为NULL。这个设置必须在BeginWriting之前完成。同时需要调用IWMHeaderInfo3::AddCodecInfo设置预压缩流的格式。

BeginWriting之前,还可以用IWMWriterAdvanced2::SetInputSetting设置和流无关的设置。

元数据
使用写入对象的IWMHeaderInfo 或者IWMHeaderInfo2接口访问元数据。必须在IWMWriter::BeginWriting之前完成元数据的写入。
注意,如果创建了写入对象而没有释放,然后再创建写入对象,一些元数据会被复制到新的对象中。

写入采样
写入采样之前要调用IWMWriter::BeginWriting.
1 用IWMWriter::AllocateSample分配缓冲区,并且获得其INSSBuffer接口
2 用INSSBuffer::GetBuffer获得缓冲区地址
3 复制数据到缓冲区中
4 用INSSBuffer::SetLength设置复制的数据长度
5 把缓冲区、输入编号和媒体时间传递给IWMWriter::WriteSample方法。音频数据持续时间是一样的,所以可以简单地在现有时间上加上一个常数。对于视频,需要根据桢率计算媒体时间。
WriteSample是异步调用,在下一次WriteSample调用之前可能没有结束。所以要在每次写入采样之前调用AllocateSample获取缓冲区对象。
所有采样写完之后,调用IWMWriter::EndWriting完成写入操作。
流数据应该几乎同时结束,否则某些流数据可能丢失。

写入压缩采样
使用IWMWriterAdvanced::WriteStreamSample 替代IWMWriter::WriteSample

写入图像采样
必须用IWMWriterAdvanced2::SetInputSetting设置图像质量g_wszJPEGCompressionQuality,范围从1到100。图像采样压缩比通常很大,所以要使用尝试的方法设置缓冲窗口大小。

强制关键桢
使用INSSBuffer3::SetProperty设置缓冲区对象的WM_SampleExtensionGUID_OutputCleanPoint为TRUE。

读取

输出

默认方式下,每个采样有一个输出编号,对应于ASF文件中的一个流。读取者打开ASF文件时,为每个流赋予一个编号。通常对每个流都有一个输出。但是对于互斥的流,每一组互斥流只有一个输出。多码流率文件的情况,或者程序自行选择流的情况下,输出对应的流是由读取者决定的。
因为流的连接名字并未保留在文件中,读取者为每个流创建一个简单的连接名字,就是输出号的字符形式,例如"1","2","3"等等。
每个输出有由编码器决定的一个或者多个支持的输出格式,打开时默认是从媒体的子类型获得默认输出格式。

使用异步方式读取ASF文件
1 实现IWMReaderCallback,处理读取者的消息,OnStatus处理状态消息,OnSample处理解压过的数据
2 让读取者打开一个文件,为每个流设置一个输出号
3 从读取者获得输出格式信息
4 让读取者开始播放,采样在指定的媒体时间传递给OnSample,直到读取者被停止或者达到文件末尾
5 数据到达时,程序负责播放采样
6 播放结束之后,让读取者关闭。
如果采样是预压缩的,那么需要实现的是IWMReaderCallbackAdvanced::OnStreamSample 。IWMReaderCallbackAdvanced::OnStreamSample几乎和OnSample完全一样,除了它基于流编号而不是输出编号之外。在开始回放之前,获得读取者对象的IWMReaderAdvanced接口,为每个预压缩流流调用 IWMReaderAdvanced::SetReceiveStreamSamples.

定位
一个ASF文件必须被适当的配置才可以定位到指定时间。默认情况下只有音频的文件可以定位,但是包含视频的文件需要有索引才可以。如果你不确定文件的创建方式,你可以调用用IWMHeaderInfo::GetAttributeByName,传递g_wszWMSeekable来获得是否可定位信息。
调用IWMReader::Start可以定位到指定时间。

[开发经验]

选择编码器
Windows Media
尽管在低码流率下的效果令人满意,但是编码时系统资源占用过高,同时在高码流率的情况下效果不甚理想。

Windows Media Video 9
Windows Media Video 9 Screen
对于格式比较挑剔,例如视频的规格必须是按双字对齐的。对于长时间的多媒体编码,有阶段性的质量变化(一段时间内桢率高,过一段时间桢率低)
Windows Media Audio 9
Windows Media Audio 9 Professional
系统资源占用过高致使采样不足的话会造成音调的变化,效果不可忍受。

自定义编码器
多种数据混合编码,避免了同步问题,但是不能单独为一种数据指定码流率和优先级等信息

系统分类: 软件开发
用户分类: 技术文收藏
标签: 无标签
来源: 转贴
发表评论 阅读全文(513) | 回复(0)

1

关于投票
Wma Tag 读写类
VIA : 

http://linle.ycool.com/post.1118133.html

 

文件结构示意图

点击看大图

格式的简单说明:
 

        如图1,每一个WMA文件,它的头16个字节是固定的,为十六进制的“30 26 B2 75 8E 66 CF 11 A6 D9 00 AA 00 62 CE 6C”,用来标识这个是否为WMA文件。接下来的8个字节为一个整数,表示整个WMA文件头部的大小,这个头部里面包含了Tag信息等所有非音频信息,头部后面的是音频信息,我们在这里就不深入了解了。那个整数接下来的6个字节还没搞清楚是什么用的,不过不影响我们对Tag信息的读写。

       也就是说从文件开始偏移量为31开始,里面存放了很多帧,有我们需要的标准Tag信息,扩展Tag信息,WMA文件控制信息等等。每个帧不是等长的,但是帧头是固定的24个字节,其中前16字节是用来标识这个帧的名字,后8个字节是用来表示这个帧(包括帧头)的大小。这一点和MP3文件的ID3V2信息比较像。

        由于我们只需要读写Tag信息,而Tag信息又分别保存在两个帧里,分别为标准Tag帧和扩展Tag帧,所有我们只需要处理这两个帧,其他帧完全可以根据获得的帧长度来跳过。

       如图2,标准Tag帧只包含歌曲标题,艺术家,版权,备注四个内容。它的帧名是十六进制的“33 26 B2 75 8E 66 CF 11 A6 D9 00 AA 00 62 CE 6C”,在24个字节的帧头后紧跟着5个分别为2个字节的整数,前四个分别表示歌曲标题,艺术家,版权,备注的大小,第五个还不清楚是什么用的,大部分情况下是不使用的,即它的大小为0的。

        在这10个字节后,这四个信息的内容就按顺序存放了。记住,在WMA文件里,所有的文字都是按Unicode宽字符的编码方式储存的,而且每个字符串后面都又一个0结束字符的。

        如图3,再看扩展Tag帧,这里就比较麻烦了,里面包含的信息的个数是不确定的,每个信息也是按照像帧一样的方式组织起来的。扩展Tag帧的帧名是十六进制的“40 A4 D0 D2 07 E3 D2 11 97 F0 00 A0 C9 5E A8 50”,在24字节的帧头后先有一个两个字节的整数表示这个帧里一共有的扩展信息个数(ExNo)。

        如图4,每一个扩展信息包含扩展信息名字和对应的值。先有一个两个字节的整数来表示扩展名字信息的大小,接着是扩展信息,然后有一个两个字节的整数标志(Flag),这个后面再讲。然后又是一个两个字节的整数,表示值的大小。接着就是这个值。

       当扩展信息名字为WMFSDKVersion时,这个值表示的是这个WMA文件的版本;当扩展信息名字为WM/AlbumTitle时,这个值代表的就是专辑名;当扩展信息名字为WM/Genre时,这个值代表的就是流派;同理,很容易从扩展信息的名字看出这个值的用途的。这些扩展信息的名字和值几乎都是用Unicode的字符串来存储的,到现在为止只发现对下面两个情况例外。(关于所有扩展信息的名字可以从很多地方查到,比如SDK帮助,MSDN)

       下面再来看看那个标志Flag,这个基本上是为没什么用的(通常值为0),只对WM/TrackNumber和WM/Track这两个扩展信息名字有用,当Flag为3的时候后面的值(也就是曲目信息)是以4个字节的整数的形式表示,当Flag为0的时候,曲目信息是以普通的字符串形式表示的。

 

 

系统分类: 软件开发
用户分类: 技术文收藏
标签: Wma Tag 读写类
来源: 转贴
发表评论 阅读全文(1104) | 回复(0)

1

关于投票
利用VC++实现AVI文件的合成和分解
出处: 天极开发

摘要:本文详细的解析了AVI文件的存储结构,介绍了微软提供的用来操作AVI文件的一组API使用方法,并通过例子代码,演示了如何将一组静态Bmp图片合成一个avi视频文件以及如何将一个avi视频文件解析保存为一系列的bmp图像文件。

  关键词:avi文件 bmp图像 vc

  AVI是音频视频交错(Audio Video Interleaved)的英文缩写,它是Microsoft公司开发的一种符合RIFF文件规范的数字音频与视频文件格式,原先用于Microsoft Video for Windows (简称VFW)环境,现在已被Windows 95/98、OS/2等多数操作系统直接支持。AVI格式允许视频和音频交错在一起同步播放,支持256色和RLE压缩,但AVI文件并未限定压缩标准,因此,AVI文件格式只是作为控制界面上的标准,不具有兼容性,用不同压缩算法生成的AVI文件,必须使用相应的解压缩算法才能播放出来。常用的AVI播放驱动程序,主要是Microsoft Video for Windows或Windows 95/98中的Video 1,以及Intel公司的Indeo Video。

  在介绍AVI文件前,我们要先来看看RIFF文件结构。AVI文件采用的是RIFF文件结构方式,RIFF(Resource Interchange File Format,资源互换文件格式)是微软公司定义的一种用于管理windows环境中多媒体数据的文件格式,波形音频wave,MIDI和数字视频AVI 都采用这种格式存储。构造RIFF文件的基本单元叫做数据块(Chunk),每个数据块包含3个部分,

  1、4字节的数据块标记(或者叫做数据块的ID)

  2、数据块的大小

  3、数据

  整个RIFF文件可以看成一个数据块,其数据块ID为RIFF,称为RIFF块。一个RIFF文件中只允许存在一个RIFF块。RIFF块中包含一系列的子块,其中有一种字块的ID为"LIST",称为LIST,LIST块中可以再包含一系列的子块,但除了LIST块外的其他所有的子块都不能再包含子块。

  RIFF和LIST块分别比普通的数据块多一个被称为形式类型(Form Type)和列表类型(List Type)的数据域,其组成如下:

  1、4字节的数据块标记(Chunk ID)

  2、数据块的大小

  3、4字节的形式类型或者列表类型

  4、数据

  下面我们看看AVI文件的结构。AVI文件是目前使用的最复杂的RIFF文件,它能同时存储同步表现的音频视频数据。AVI的RIFF块的形式类型是AVI,它包含3个子块,如下所述:

  1、信息块,一个ID为"hdrl"的LIST块,定义AVI文件的数据格式。

  2、数据块,一个ID为 "movi"的LIST块,包含AVI的音视频序列数据。

  3、索引块,ID为 "idxl"的子块,定义 "movi"LIST块的索引数据,是可选块。

  AVI文件的结构如下图所示,下面将具体介绍AVI文件的各子块构造。

  1、信息块,信息块包含两个子块,即一个ID为 avih 的子块和一个ID 为 strl 的LIST块。

 

 


  "avih"子块的内容可由如下的结构定义:

 

 

typedef struct
{
 DWORD dwMicroSecPerFrame ; //显示每桢所需的时间ns,定义avi的显示速率
 DWORD dwMaxBytesPerSec; // 最大的数据传输率
 DWORD dwPaddingGranularity; //记录块的长度需为此值的倍数,通常是2048
 DWORD dwFlages; //AVI文件的特殊属性,如是否包含索引块,音视频数据是否交叉存储
 DWORD dwTotalFrame; //文件中的总桢数
 DWORD dwInitialFrames; //说明在开始播放前需要多少桢
 DWORD dwStreams; //文件中包含的数据流种类
 DWORD dwSuggestedBufferSize; //建议使用的缓冲区的大小,
 //通常为存储一桢图像以及同步声音所需要的数据之和
 DWORD dwWidth; //图像宽
 DWORD dwHeight; //图像高
 DWORD dwReserved[4]; //保留值
}MainAVIHeader;


  "strl" LIST块用于记录AVI数据流,每一种数据流都在该LIST块中占有3个子块,他们的ID分别是"strh","strf", "strd";
"strh"子块由如下结构定义。

 

 

typedef struct
{
 FOURCC fccType; //4字节,表示数据流的种类 vids 表示视频数据流
 //auds 音频数据流
 FOURCC fccHandler;//4字节 ,表示数据流解压缩的驱动程序代号
 DWORD dwFlags; //数据流属性
 WORD wPriority; //此数据流的播放优先级
 WORD wLanguage; //音频的语言代号
 DWORD dwInitalFrames;//说明在开始播放前需要多少桢
 DWORD dwScale; //数据量,视频每桢的大小或者音频的采样大小
 DWORD dwRate; //dwScale /dwRate = 每秒的采样数
 DWORD dwStart; //数据流开始播放的位置,以dwScale为单位
 DWORD dwLength; //数据流的数据量,以dwScale为单位
 DWORD dwSuggestedBufferSize; //建议缓冲区的大小
 DWORD dwQuality; //解压缩质量参数,值越大,质量越好
 DWORD dwSampleSize; //音频的采样大小
 RECT rcFrame; //视频图像所占的矩形
}AVIStreamHeader;


  "strf"子块紧跟在"strh"子块之后,其结构视"strh"子块的类型而定,如下所述;如果 strh子块是视频数据流,则 strf子块的内容是一个与windows设备无关位图的BIMAPINFO结构,如下:

 

 

typedef struct tagBITMAPINFO
{
 BITMAPINFOHEADER bmiHeader;
 RGBQUAD bmiColors[1]; //颜色表
}BITMAPINFO;

typedef struct tagBITMAPINFOHEADER
{
 DWORD biSize;
 LONG biWidth;
 LONG biHeight;
 WORD biPlanes;
 WORD biBitCount;
 DWORD biCompression;
 DWORD biSizeImage;
 LONG biXPelsPerMeter;
 LONG biYPelsPerMeter;
 DWORD biClrUsed;
 DWORD biClrImportant;
}BITMAPINFOHEADER;


  如果 strh子块是音频数据流,则strf子块的内容是一个WAVEFORMAT结构,如下:

 

 

typedef struct
{
 WORD wFormatTag;
 WORD nChannels; //声道数
 DWORD nSamplesPerSec; //采样率
 DWORD nAvgBytesPerSec; //WAVE声音中每秒的数据量
 WORD nBlockAlign; //数据块的对齐标志
 WORD biSize; //此结构的大小
}WAVEFORMAT


  "strd"子块紧跟在strf子块后,存储供压缩驱动程序使用的参数,不一定存在,也没有固定的结构。

  "strl" LIST块定义的AVI数据流依次将 "hdrl " LIST 块中的数据流头结构与"movi" LIST块中的数据联系在一起,第一个数据流头结构用于数据流0,第二个用于数据流1,依次类推。

  数据块中存储视频和音频数据流,数据可直接存于 "movi" LIST块中。数据块中音视频数据按不同的字块存放,其结构如下所述,

  音频字块
    "##wb"
    Wave 数据流
  视频子块中存储DIB数据,又分为压缩或者未压缩DIB,
    "##db"
    RGB数据流
    "##dc"
  压缩的图像数据流

  看到了吧,avi文件的图像数据可以是压缩的,和非压缩格式的。对于压缩格式来说,也可采用不同的编码,也许你曾经遇到有些avi没法识别,就是因为编码方式不一样,如果没有相应的解码,你就没法识别视频数据。AVI的编码方式有很多种,比较常见的有 mpeg2,mpeg4,divx等。
 

索引块,索引快包含数据块在文件中的位置索引,能提高avi文件的读写速度,其中存放着一组AVIINDEXENTRY结构数据。如下,这个块并不是必需的,也许不存在。

 

typedef struct
{
 DWORD ckid; //记录数据块中子块的标记
 DWORD dwFlags; //表示chid所指子块的属性
 DWORD dwChunkOffset; //子块的相对位置
 DWORD dwChunkLength; //子块长度
};

  现在我相信你肯定会对AVI的文件结构已经很清楚了,在介绍完了AVI文件结构后,我们就来看看如何对avi文件进行读写了,为了对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文件的介绍就到这里结束了.

 

 

系统分类: 软件开发
用户分类: 技术文收藏
标签: 无标签
来源: 转贴
发表评论 阅读全文(531) | 回复(0)

1

关于投票
ACM采样频率转换
原作者姓名 陆其明
文章原始出处 http://hqtech.nease.net


在音频的处理中,采样频率的转换是经常碰到的问题,比如输入44.1k,要求输出48k,或者相反从48k转换到44.1k。表面上看来,只是增加或减少采样点而已。其实不然。如果只是简单地从时间域上进行采样点的增减,必然导致原有波形的改变,从而声音失真,严重的时候更是不堪入耳。
正确的方法,应该是对输入的数据进行FFT变换到频域,然后再进行转化。这是一个比较繁琐的过程。那么,有没有更简单一点的方法呢?答案是肯定的。微软提供了一套ACM的API函数可以帮我们的忙。熟悉DirectShow Filter的朋友更加知道,在SDK中提供的Filter中就有一个叫ACM Wrapper的,其实它就是微软对ACM API函数的包装。可以说,ACM Wrapper Filter是ACM API在DirectShow环境中应用形式。
美中不足的是,经过 ACM Wrapper Filter进行采样频率转化后,由于浮点运算的误差,有可能会导致数据的丢失。每次转化的一点点丢失,如果再经过时间上的累加,音频数据会丢得越来越多。由于微软的DirectShow是基于Playback模式的一套架构,时间戳上显示的数据丢失对于人耳根本微不足道。所以仅从播放的角度上来说,这个“问题”是很难被察觉的。如果你要使用经过ACM Wrapper Filter转化后的数据跟视频流合成,那么,你生成的文件很有可能在半个小时或更长的一段时间后出现音视频的不同步现象。
解决的办法有两种,一种是自己开发一个In-place-transform的Filter。这个Filter紧跟着接到ACM Wrapper Filter的后面,对进来的每一个Sample检查时间戳,如果累加的音频丢失“时间”超过一个采样点的时间,则马上补上一个采样点的数据。另外一种解决方法,就是干脆使用ACM API函数写一个自己的ACM Wrapper Filter。这样,就可以直接在ACM Wrapper内部监视数据的丢失。
下面我们就来看一下ACM API的使用。请先确认包含了以下头文件:mmreg.h, mmsytem.h, msacm.h;以及连接了以下库文件:msacm32.lib, winmm.lib。在进行采样频率转换之前,首先要使用acmStreamOpen函数打开一个转化流,以及对输入输出数据类型的设置。示例代码如下:
bool CConversionStream::OpenStream(void)
{
    DWORD maxSize = 0;
    MMRESULT mmr = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &maxSize);
    bool pass = (mmr == MMSYSERR_NOERROR);
    if (pass)
    {
        LPWAVEFORMATEX sourceFormat = (LPWAVEFORMATEX) new char [maxSize];
        LPWAVEFORMATEX destFormat   = (LPWAVEFORMATEX) new char [maxSize];
        memset(sourceFormat, 0, maxSize);
        memset(destFormat, 0, maxSize);
        sourceFormat->wFormatTag = WAVE_FORMAT_PCM;
        sourceFormat->nChannels  = 2;
        sourceFormat->nSamplesPerSec = 44100;
        sourceFormat->wBitsPerSample = 16;
        sourceFormat->cbSize = 0;
        sourceFormat->nBlockAlign     = 4;
        sourceFormat->nAvgBytesPerSec = 44100 * 4;

        destFormat->wFormatTag = WAVE_FORMAT_PCM;
        destFormat->nChannels  = 2;
        destFormat->nSamplesPerSec = 48000;
        destFormat->wBitsPerSample = 16;
        destFormat->cbSize = 0;
        destFormat->nBlockAlign     = 4;
        destFormat->nAvgBytesPerSec = 48000 * 4;

        mmr = acmStreamOpen(&mStreamHandler, NULL, sourceFormat, destFormat, NULL, 0, 0, 0);
        pass = (mmr == MMSYSERR_NOERROR);
        delete[] sourceFormat;
        delete[] destFormat;
    }
    return pass;
}
实际的数据转化也很简单。首先要建立一个ACM header,并对其进行设置,如果输入数据的缓冲及数据长度,输出数据的缓冲及缓冲大小。之后务必调用acmStreamPrepareHeader函数对这个ACM header进行初始化。然后就调用acmStreamConvert进行数据转换。最后不要忘记调用 acmStreamUnprepareHeader。
bool CConversionStream::DoConverting(unsigned char * inSourceBuffer, long inSourceLength,
                                     unsigned char * outDestBuffer, long * ioDestLength)
{
    memset(mAcmheader, 0, sizeof(ACMSTREAMHEADER));
    DWORD suggestedDestSize = 0;
    acmStreamSize(mStreamHandler, inSourceLength, &suggestedDestSize, ACM_STREAMSIZEF_SOURCE);
    ASSERT(suggestedDestSize <= *ioDestLength);

    // Build ACM header on buffer
    mAcmheader->cbStruct    = sizeof(ACMSTREAMHEADER);
    mAcmheader->cbSrcLength = inSourceLength;
    mAcmheader->pbSrc       = inSourceBuffer;
    mAcmheader->cbDstLength = *ioDestLength;
    mAcmheader->pbDst       = outDestBuffer;

    // Prepare the buffer for ACM
    MMRESULT mmr = acmStreamPrepareHeader(mStreamHandler, mAcmheader, 0);
    bool pass = (mmr == MMSYSERR_NOERROR);
    if (pass)
    {
        mmr  = acmStreamConvert(mStreamHandler, mAcmheader, ACM_STREAMCONVERTF_BLOCKALIGN);
        pass = (mmr == MMSYSERR_NOERROR);
    }
    *ioDestLength = mAcmheader->cbDstLengthUsed;
    ASSERT(mAcmheader->cbSrcLengthUsed == mAcmheader->cbSrcLength);
    // Unprepare ACM header
    acmStreamUnprepareHeader(mStreamHandler, mAcmheader,0);
    return pass;
}
就这么简单!轻轻松松,实现了音频的采样频率转换。最后,当所有数据都已经转换完毕,不要忘了调用acmStreamClose函数关闭转化流。

系统分类: 软件开发
用户分类: 技术文收藏
标签: 音频 转码
来源: 转贴
发表评论 阅读全文(1222) | 回复(0)

1

关于投票
关于Video Renderer和Overlay Mixer
  • 文章来源: http://hqtech.nease.net
  • 原文作者: 陆其明

    大家知道,Video Renderer (VR)是接收RGB/YUV裸数据,然后在显示器上显示的Filter。为提高计算机画图性能,根据你计算机显卡的能力,VR会优先使用 DirectDraw以及Overlay表面;如果这些特性得不到显卡的支持,VR会使用GDI函数进行画图。在上级Filter连接到VR时,VR总是先要求当前显示器设置的色彩位数的RGB格式,如你的机器设置的是24位彩色,则VR首先要求连接的Media type为RGB24。如果你的显卡支持YUV Overlay表面,那么在Filter Graph运行起来的时候,VR会动态改变已经连接的Media type,要求上级Filter输出一种合适的YUV格式。VR Filter上实现了IVideoWindow接口,Filter Graph Manager主要通过这个接口来控制视频窗口。

    那么,Overlay Mixer又是怎么回事呢?简单地说,Overlay Mixer就是能够将几路视频流合成输出的Filter。这个Filter是特地为DVD回放(DVD有Sub-picture或line-21数据需要叠加显示)或广播视频流(含有line-21数据)而设计的。同时,它还支持硬件解码器使用Video Port Extensions,就是绕过PCI总线,将硬件解码出来的数据直接送给显卡显示。这个Filter同样优先使用显卡的DirectDraw能力,而且必须要有Overlay表面。Overlay Mixer有一个输出Pin,输出的Media type是:MEDIATYPE_VIDEO,MEDIASUBTYPE_ Overlay;后面一般连上一个Video Renderer。当Filter Graph运行时,实际的图像显示工作由Overlay Mixer完成,而Video Renderer只是做一个视频窗口的管理工作。还有另外一个更常见的Filter:Overlay Mixer 2。这个Filter跟Overlay Mixer功能上是一样的,只是两个Filter支持的Format type不同和Merit值不同而已。

    Overlay Mixer使用Color keying来实现几路视频的合成:它将Color key和sub-picture(或line-21)数据送到主表面,将主视频数据送到Overlay表面;显卡然后将两个表面的数据合成,送到帧缓存(Frame buffer)中进行显示。典型的情况,Overlay Mixer使用三个Input pin:Pin 0输入主视频数据,Pin 1和Pin 2输入sub-picture数据和line-21数据。Overlay Mixer在内部根据Pin 0输入的数据来创建Overlay表面。Overlay Mixer向上一般连接的是Video Decoder。如果这是个Software decoder,则Pin 0上的数据传输使用标准的IMemInputPin接口;如果使用了硬件加速,则Pin 0上必须使用IAMVideoAccelerator接口。(注意这两种接口是不能同时使用的!)如果上一级Filter是硬件解码器的包装 Filter,使用VP pin输出,则解码器与Overlay Mixer使用IVPConfig和IVPNotify接口对通讯,以协调工作。Overlay Mixer不支持1394或USB接口的采集设备。Overlay Mixer向下一般连的是Video Renderer。这时Video Renderer只是一个视频窗口管理器。两个Filter通过IOverlay和IOverlayNotify接口对进行通讯,以协调工作。(Video Renderer的Input pin有两种连接方式:VR直接做图像显示时,则使用IMemInputPin接口接收视频流数据;Overlay Mixer做图像显示时,则VR使用IOverlay接口与上一级Filter进行通讯,Overlay Mixer与VR之间没有视频数据的传输。注意这两种接口是不会同时使用的!)

    大家看到了,其实Video Renderer与Overlay Mixer有一部分功能是重复的。Video Renderer是最早设计的,设计之初,很多应用情况没有考虑进去;于是,就用Overlay Mixer来“打补丁”。现在,我们为什么不把两部分功能整合一下呢?微软也正是这么做了!在Windows XP(家庭版和专业版)中,新出现了一个Filter(注册的名字也叫“Video Renderer”,但两个Filter的CLSID是不同的,Merit值也不一样),替代了原来默认的Video Renderer。这个新的Filter,称之为Video Mixing Renderer Filter 7 (VMR-7),因为它内部使用了DirectDraw 7的技术。可以这么说,VMR是Windows平台上新一代的Video Renderer。值得注意的是,这个Filter仅在Windows XP里集成,在其他任何DirectX发布包里都得不到这个Filter。VMR-7的大致功能如下:支持最多16路输入流的alpha混合;支持在合成图像显示之前得到对其访问权;支持插入第三方开发的Video Effects和Transitions组件功能等等。还有,VMR连接时不要求RGB的Media type,因为它任何情况下都不会使用GDI函数来画图。

    随着DirectX 9的发布,又会出现一个新的Video Renderer,称之为VMR-9。这个Filter使用了Direct3D 9的技术。VMR-9与VMR-7是两个不同的Filter。VMR-9的性能更加强劲。值得注意的是,为了保持向下兼容,VMR-9的Merit值并不高,它不作为系统默认的Video Renderer;如果你的应用程序只需要很少的视频显示控制,建议还是使用各自平台默认的Video Renderer。

    下面是关于一些Video Renderer使用的常见问题,可供参考:
    1. 写基于DirectShow的应用程序,肯定会用到Filter Graph Manager的IVideoWindow接口。Filter Graph Manager上的这个接口,实际实现于Video Renderer上。需要特别注意的是,必须在Video Renderer连接成功后才能调用这个接口的方法,否则方法调用总会失败。
    2. 通过IVideoWindow::put_FullScreenMode实现全屏模式。对于一些新的显卡,VR能够对图像直接拉伸后再显示(性能不会损失很大);但如果显卡本身性能不佳,Filter Graph Manager会自动将VR替换为Full Screen Renderer Filter。事实上,当用户调用该接口函数要求切换到全屏模式时,Filter Graph Manager的控制逻辑为:优先使用在Filter Graph中直接支持全屏模式的Video Renderer(通过IVideoWindow::get_FullScreen Mode判断);否则,使用一个对图像缩放到全屏,性能损失不是很大的Video Renderer;再则,使用Full Screen Renderer Filter替换;以上尝试都失败,则选择Filter Graph中任意一个支持IVideoWindow接口的Video Renderer。除了一些比较老的显卡,一般第二步尝试就能成功。
    3. 通过IBasicVideo::GetCurrentImage得到当前的图像数据。对于一般的Video Renderer来说,使用这个接口函数是不可靠的。因为如果Video Renderer使用了DirectDraw加速,这个函数调用会失败;而且调用这个函数,Video Renderer必须处于Pause状态。而对于VMR,则完全没有如上这些限制。所以,在使用Video Renderer的情况下,想得到整个视频流中的某一帧的图像,建议写一个In-place-trans filter,插入到Video Renderer的前面,很简单就能实现。
    4. 有时候,从一个Decoder的Output pin Render出去,会自动接上Overlay Mixer 2这个Filter?或者自己写的Decoder,怎么样让它连接到Overlay Mixer 2?这主要是Decoder的Output pin支持的Media type使用的Format type的原因。需要注意的是:Overlay Mixer 2仅支持Format_VIDEOINFO2,Overlay Mixer虽然同时支持Format_VIDEOINFO和Format_VIDEOINFO2,但它的Merit值为 MERIT_DO_NOT_USE,不会被自动加入Filter Graph中

  • 系统分类: 软件开发
    用户分类: 技术文收藏
    标签: 无标签
    来源: 转贴
    发表评论 阅读全文(553) | 回复(0)

    0

    关于投票
    浅析DirectShow音视频同步解决完整方案
    浅析DirectShow音视频同步解决完整方案

    文/陆其明

      多媒体处理,不可避免地要解决音视频的同步问题。DirectShow是怎么来实现的呢?我们一起来学习一下。

      大家知道,DirectShow结构最核心的部分是Filter Graph Manager:向下控制Graph中的所有Filter,向上对应用程序提供编程接口。其中,Filter Graph Manager实现的很重要一个功能,就是同步音视频的处理。简单地说,就是选一个公共的参考时钟,并并且要求给个Sample都打上时间戳,Video Renderer或Audio Renderer根据Sample的时间戳来控制播放。如果到达Renderer的Sample晚了,则加快Sample的播放;如果早了,则 Renderer等待,一直到Sample时间戳的开始时间再开始播放。这个控制过程还引入一个叫Quality Control的反馈机制。

      下面,我们来看一下参考时钟(Reference Clock)。所有Filter都参照于同一个时钟,才能统一步调。DirectShow引入了两种时钟时间:Reference time和Stream time。前者是从参考时钟返回的绝对时间(IReferenceClock::GetTime),数值本身的意义取决于参考时钟的内部实现,利用价值不大;后者是两次从参考时钟读取的数值的差值,实际应用于Filter Graph内部的同步。Stream time在Filter Graph不同状态的取值为:

      1. Filter Graph运行时,取值为当前参考时钟时间减去Filter Graph启动时的时间(启动时间是通过调用Filter上的IMediaFilter::Run来设置的);

      2. Filter Graph暂停时,保持为暂停那一刻的Stream time;

      3. 执行完一次Seek操作后,复位至零;

      4. Filter Graph停止时,取值不确定。

      那么,参考时钟究竟是什么东西呢?其实,它只是一个实现了IReferenceClock接口的对象。也就是说,任何一个实现了 IReferenceClock接口的对象都可以成为参考时钟。在Filter Graph中,这个对象一般就是一个Filter。(在GraphEdit中,实现了参考时钟的Filter上会显示一个时钟的图标;如果同一个 Graph中有多个Fiter实现了参考时钟,当前被Filter Graph Manager使用的那个会高亮度显示。)而且大多数情况下,参考时钟是由Audio Renderer这个Filter提供的,因为声卡上本身带有了硬件定时器资源。接下来的问题是,如果Filter Graph中有多个对象实现了IReferenceClock接口,Filter Graph Manager是如何做出选择的呢?默认的算法如下:

      1. 如果应用程序设置了一个参考时钟,则直接使用这个参考时钟。(应用程序通过IMediaFilter:: SetSyncSource设置参考时钟,参数即为参考时钟;如果参数值为NULL,表示Filter Graph不使用参考时钟,以最快的速度处理Sample;可以调用IFilterGraph:: SetDefaultSyncSource来恢复Filter Graph Manager默认的参考时钟。值得注意的是,这时候的IMediaFilter接口应该从Filter Graph Manager上获得,而不是枚举Graph中所有的Filter并分别调用Filter上的这个接口方法。)

      2. 如果Graph中有支持IReferenceClock接口的Live Source,则选择这个Live Source。

      3. 如果Graph中没有Live Source,则从Renderer依次往上选择一个实现IReferenceClock接口的Filter。如果连接着的Filter都不能提供参考时钟,则再从没有连接的Filter中选择。这一步算法中还有一个优先情况,就是如果Filter Graph中含有一个Audio Render的链路,则直接选择Audio Renderer这个Filter(原因上面已经提及)。

      4. 如果以上方法都找不到一个适合的Filter,则选取系统参考时钟。(System Reference Clock,通过CoCreateInstance创建,CLSID为CLSID_SystemClock。)

      我们再来看一下Sample的时间戳(Time Stamp)。需要注意的是,每个Sample上可以设置两种时间戳:IMediaSample::SetTime和 IMediaSample::SetMediaTime。我们通常讲到时间戳,一般是指前者,它又叫Presentation time,Renderer正是根据这个时间戳来控制播放;而后者对于Filter来说不是必须的,Media time有没有用取决于你的实现,比如你给每个发出去的Sample依次打上递增的序号,在后面的Filter接收时就可以判断传输的过程中是否有 Sample丢失。我们再看一下IMediaSample::SetTime的参数,两个参数类型都是REFERENCE_TIME,千万不要误解这里的时间是Reference time,其实它们用的是Stream time。还有一点,就是并不是所有的Sample都要求打上时间戳。对于一些压缩数据,时间戳是很难打的,而且意义也不是很大(不过压缩数据经过 Decoder出来之后到达Renderer之前,一般都会打好时间戳了)。时间戳包括两个时间,开始时间和结束时间。当Renderer接收到一个 Sample时,一般会将Sample的开始时间和当前的Stream time作比较,如果Sample来晚了或者没有时间戳,则马上播放这个Sample;如果Sample来得早了,则通过调用参考时钟的 IReferenceClock::AdviseTime等待Sample的开始时间到达后再将这个Sample播放。Sample上的时间戳一般由 Source Filter或Parser Filter来设置,设置的方法有如下几种情况:

      1. 文件回放(File playback):第一个Sample的时间戳从0开始打起,后面Sample的时间戳根据Sample有效数据的长度和回放速率来定。

      2. 音视频捕捉(Video and audio capture):原则上,采集到的每一个Sample的开始时间都打上采集时刻的Stream time。对于视频帧,Preview pin出来的Sample是个例外,因为如果按上述方法打时间戳的话,每个Sample通过Filter链路传输,最后到达Video Renderer的时候都将是迟到的;Video Renderer通过Quality Control反馈给Source Filter,会导致Source Filter丢帧。所以,Preview pin出来的Sample都不打时间戳。对于音频采集,需要注意的是,Audio Capture Filter与声卡驱动程序两者各自使用了不同的缓存,采集的数据是定时从驱动程序缓存拷贝到Filter的缓存的,这里面有一定时间的消耗。

      3. 合成(Mux Filters):取决于Mux后输出的数据类型,可以打时间戳,也可以不打时间戳。

      大家可以看到,Sample的时间戳对于保证音视频同步是很重要的。Video Renderer和Audio Renderer作为音视频同步的最终执行者,需要做很多工作。我们或许要开发其它各种类型的Filter,但一般这两个Filter是不用再开发的。一是因为Renderer Filter本身的复杂性,二是因为微软会对这两个Filter不断升级,集成DirectX中其它模块的最新技术(如DirectSound、 DirectDraw、Direct3D等)。

      最后,我们再来仔细看一下Live Source的情况。Live Source又叫Push source,包括Video /Audio Capture Filter、网络广播接收器等。Filter Graph Manager是如何知道一个Filter是Live Source的呢?通过如下任何一个条件判断:

      1. 调用Filter上的IAMFilterMiscFlags::GetMiscFlags返回有AM_FILTER_MISC_FLAGS_IS_SOURCE标记,并且至少有一个Output pin实现了IAMPushSource接口。

      2. Filter实现了IKsPropertySet接口,并且有一个Capture output pin(Pin的类型为PIN_CATEGORY_CAPTURE)。

      Live Source对于音视频同步的影响主要是以下两个方面:Latency和Rate Matching。Latency是指Filter处理一个Sample花费的时间,对于Live Source来说,主要取决于使用缓存的大小,比如采集30fps的视频一般采集完一帧后才将数据以一个Sample发送出去,则这个Filter的 Latency为33ms,而Audio一般缓存500ms后才发送一个Sample,则它的Latency就为500ms。这样的话,Audio与 Video到达Renderer就会偏差470ms,造成音视频的不同步。默认情况下,Filter Graph Manager是不会对这种情况进行调整的。当然,应用程序可以通过IAMPushSource接口来进行Latency的补偿,方法是调用 IAMGraphStreams::SyncUsingStreamOffset函数。Filter Graph Manager的实现如下:对所有实现IAMPushSource接口的Filter调用IAMLatency::GetLatency得到各个 Source的Latency值,记下所有Latency值中的最大值,然后调用IAMPushSource::SetStreamOffset对各个 Source设置偏移值。

      这样,在Source Filter产生Sample时,打的时间戳就会加上这个偏移量。Rate Matching问题的引入,主要是由于Renderer Filter和Source Filter使用的是不同的参考时钟。这种情况下,Renderer对数据的播放要么太快,要么太慢。另外,一般Live Source不能控制输出数据的速率,所以必须在Renderer上进行播放速率的匹配。因为人的听觉敏感度要大于视觉敏感度,所以微软目前只在 Audio Renderer上实现了Rate Matching。实现Rate Matching的算法是比较复杂的,这里就不再赘述。

      看到这里,大家应该对DirectShow是如何解决音视频同步问题的方案有一点眉目了吧。深层次的研究,还需要更多的测试、Base class源码阅读,以及DirectShow相关控制机制的理解,比如Quality Control Management等。

    系统分类: 汽车电子
    用户分类: 技术文收藏
    标签: 无标签
    来源: 转贴
    发表评论 阅读全文(732) | 回复(0)

    1

    关于投票
    Windows CE OAL层的结构与开发
    摘要:Windows CE的OAL层是一个复杂的函数集。它的复杂性不但体现在包含函数数目繁多,而且体现在很多函数的硬件相关性非常大。

     

        引 言    

        Windows CE是微软针对嵌入式领域推出的一款全新的操作系统。之所以说它是一款全新的操作系统,是因为尽管Windows CE的UI非常接近其它的桌面版Windows操作系统,但是它的内核完全是重新写的,并不是任何一款桌面版Windows的精简版本。 Windows CE是一种支持多种CPU架构的操作系统,这其中包括ARM、x86、MIPS和SHx,极大地减轻了0EM开发过程中移植操作系统的工作量。   

        操作系统移植包含两个层面上的工作:一个层面是CPU级的,另一个层面是板级的。CPU级的移植通常由微软或芯片制造商来完成;板级移植则是由OEM来完成的。0AL正是0EM完成这一系统移植的工作核心!

        1 .OAL   

        OAL的全称是OEM Adaption Layer,即原始设备制造商适配层。从逻辑结构上看,它位于操作系统的内核与硬件之间,是连接系统与硬件的枢纽;从功能上看,OAL颇似桌面机上的BIOS,具有初始化设备、引导操作系统以及抽象硬件功能等作用。与B10S不同的是,0AL隶属于操作系统,是操作系统的一部分。从存在方式上,讲OAL是一组函数的集合体,这些函数体现出0AL的功能,如图1所示。

     

    图1 Win CE 系统结构

        2 .最小化的OAL

        OAL层的首要任务是加载内核。OAL层中为内核的启动作种种铺垫的函数的集合构成最小OAL层。我们可以由此深入0AL层,如图2所示。

     

    图2 OS启动顺序

        首先来看一下OS的启动顺序。    

        ①CPU执行引导向量,跳转到硬件初始化代码,即Startup函数;    

        ②在start up函数完成最小硬件环境初始化后跳转到KernelStart函数(当CPU为x86架构时为Kernel Initial-ize函数),来对内核进行初始化;    

        ③Kernelstart函数调用OEMInitDebugSerial完成对调试串口的初始化,调用0EMInit函数来完成硬件初始化工作以及设置时钟、中断,调用OEMGetExtensionDRAM函数来判断是否还有另外一块DRAM。    

        至此,内核加载完毕。由此可见,OS启动的重中之重是Startup函数的正确加载。  

        2.1 Startup 

        Startup阶段的特点是Kernel还没有加载起来,调试工作比较困难。StartuP函数的两大核心任务分别是把CPU初始化到一已知状态和调用内核初始化函数来初始化内核。以下是Startup函数中通常包含的内容:①把处理器置为监控模式;②禁止CPU的IRQ和FIQ输入:③禁止内存管理单元 MMU和指令、数据Cache; ④刷新指令和数据Cache、TLB、清空写buffr; ⑤确定启动的原因一hard reset,wake from sleep, GPIO reset,Watchdog reset,eboot handoff; ⑥根据目标板需要配置GPIO,比如连接LED的GPIO; ⑦配置内存管理器,设置刷新频率,使能时钟; ⑧配置中断控制器;⑨初始化实时时钟(RTC)为0,使能实时时钟:⑩设置电源管理寄存器;⑾打开所有板级时钟和片内外部时钟;⑿取得 OEMAddressTable的物理基地址并把它存在r0中;⒀跳转到KernelStart。    

        Bootloader和OAL中均包含Startup函数。它的功能大致相同,都是要初始化最小硬件环境。Bootloader是在为自己的执行准备硬件环境,OAL则是为kernel的执行准备硬件环境。由于这两种硬件环境要求基本相同,所以它们的代码也有很大部分可以相互借鉴。但应该明白,Bootloader与OAL在物理上是独立的,它们并不是同一段代码。而且,如果可以确定这一硬件部分Bootloader已经初始化过,则在 OAL中不必重复。当然,前提是每次加载都要经过Bootloader这一环节。最典型的例子就是x86 OAL中的Startup,见例程:
    Naked_Startup()
    {_asm
     {
      cli
      jmp KernelInitialize
     }
    }
    StartuP执行完毕后,跳转至Kerne1Start/Kemellnitialize(X86下)。

        2.2 Kernel Start

        Kernel Start主要完成内核的最小初始化并且通过调用OEMInit函数来完成板级硬件初始化。以下是ARM内核初始化过程:① 初始化一级页表;②使能MMU和cache;③为每种工作模式使能栈(stack);     ④重新定位内核;⑤执行串口调试函数;⑥调用OEMInit;⑦初始化内存;⑧执行其它初始化。    

        KernelStart中用到的三个函数OEMInit()、OEMInitDebugSerial()和OEMGetExtensionDRAM() 中,OEMInit()硬件相关性较大,也最重要。(1)OEMlnit() 0EMInit的最小任务是初始化其它硬件和注册系统时钟。通常OEMInit应该完成以下工作。①通过设置以下值来设置中断映射表一 SYSINTR→IRQ和IRQ→SYSINTR。②在中断映射表中设置静态中断映射。③设置KITL,但在最小化的OAL层中通常不包括KITL。④用 Init Clock配置系统定时器、实时时钟、时钟。⑤确定系统时钟的中断源。⑥初始化内核时间粒度为1ms。⑦配置中断控制器或可编程中断控制器(PICS)。 ⑧提供调试用LED指示灯。⑨置pWriteDedugLED=OEMWriteDedugLED。⑩调用HookInterrupt函数来注册中断服务例程ISRs,以下示例说明如何处理TIMERISR硬件中断5的中断服务例程:
    void OEMInit(void)
    {
    ...
    HookInterrupt(5,TIMERISR);//注意定时器中断
    ...
    }

        (11)  清楚中断掩码,防止初始化内核有中断申请。


      

      (2)串口调试函数

        有限的调试手段是0S移植人员经常遇到的难题。串口调试函数虽然不像以太网口调试函数那样功能强大,但仍然要比LED指示灯或数码管要直观得多,是调试 OAL层代码不可或缺的一组工具。这个函数组由四个函数组成,分别是0EMI nitDebugSerial()、OEMReadDebugByte()、OEMWriteDebugByte()和 OEMWriteDebugString()。   

        ◇OEMInitDebugSerial()用于配置串口;   

        ◇OEMReadDebugByte0和OEMWriteDebugByte()用于向串口读写一个字节;   

        ◇OEMWriteDebugString()用于向串口写一个调试用字符串。    

         KernelStart中调用的是OEMInitDebugSerial(),完成串口初始化,为串口调试工作作好准备。   

        (3)OEMGetExtensionDRAM()

        在最简最小化OAL层函数中,OEMGetExtensionDRAM()并不是一个必需的函数。OEMGetExtensionDRAM()的主要功能是查询是否存在另外一片DRAM.如果目标板上只有一片DRAM,则该函数返回FALSE。但在KernelStart通常都包含此函数。至此,最小的 OAL层已经完毕,kernel的最基本的功能可以正常使用。骨架搭起,第一阶段的任务告一段落,但是很多非常重要的功能还不完整,还不能做到物尽其用。于是需要进一步加强OAL层的功能。这种做法也是OAL层开发通常使用的方法。先完成基本功能,在基本功能确保正确无误后,逐渐加入其它功能。循序渐进,即使出错也很容易找到出错的地方,便于排查。   

        3 .加强OAL

        第二阶段主要目的是充分利用板上硬件资源和加强调试手段。主要包括中断、KITL、以太网口调试函数和OEMIOControl四方面内容。我们把包含这四方面内容的OAL层称为加强OAL。   

        3.1 中 断

        外设硬件与CPU的数据交换基本上是异步进行的、最常用的中断形式。CE的中断处理顺序如图3所示。由图3可知,CE的中断实际上是由两部分ISR和 IST组成的。其中IST包含在驱动程序中,而ISR包含在OAL层中。所以,要想支持一个硬件,首先必须从0AL层为其作好准备。这个准备用两步完成。

     

    图3 中断响应顺序

     

         ①创建中断标识符。下面代码节选自SAMSUNG2410的oalintr.h。中断映射表通常位于\Platform\
    \INC。#define SYSINTR USB (SYSlNTR FIRMWARE+11) #define SYSINTR USBD (SYSlNTR_FIRMWARE+12)    

          ②创建并注册ISR。ISR的主要任务是返回中断标识符。ISR代码通常位于\Platform\\KERNEL\HAL下。 下面代码节选自SAMSUNG2410的armint.c。
        if(IntPendval==INTSRC_ADC){
        s2410INT.>r1INTSUBMSKI=BIT_SUB_TC;
        s2410INT_>rINTMSK |=BIT_ADC;
        s2410INT_>rSRCPND |=BIT_ADC;
        s2410INT_>rINTPND =BIT_ADC;
        return(SYSINTR_TOUCH);
        }

    在中断处理中,还有三个函数也起着至关重要的作用。它是OEMInterruptEnable()、OEMInterruptDisable()和OEMInterruptDone()。    

         ◇OEMInterruptEnable()用于执行允许设备产生中断的硬件操作;    

         ◇OEMInterruptDisable()禁止设备发出中断申请;    

         ◇OEMInterruptDone()中断处理结束。    

         3.2 以太网口调试函数

         以太网口调试函数与串口调试函数相比,具有更快的速度。    

         ◇OEMEthInit 初始化以太网调试口;    

         ◇OEMEthEnableInts开以太网适配器中断;    

         ◇OEMEthDisableInts关以太网适配器中断;    

         ◇OEMEthISR 以太网适配器中断服务例程;    

         ◇OEMEthGetFrame从以太网调试口收数据;    

         ◇OEMEthSendFrame从以太网调试口发数据;    

         ◇OEMEthQueryClientlnfo获取平台相关信息;   

         ◇OEMEthGetSecs 返回从某一特定时间开始的计时值。本函数用于处理超时。   

         3.3 KITL 

         KITL全称为Kernel Independent TransportLayer。它的主要用途是提供更方便的调试手段,如图4所示。KITL出现在Windows CE.net之后,把软件传输协议与硬件传输层隔离开。KITL使得开发者不必了解硬件传输层如何与软件协议层接口。    

         以下是应该在OEMInit函数中加入的KITL初始化代码。    


         ①初始化所有PCI桥和设备,枚举它们并且给它们分配资源,然后使能,使他们能正常工作。注:此条适于有KITL网络接口卡(NIC)和NIC桥的情况。    

         ② 对相关总线进行初始化,使得CPU能够正确识别NIC。    

         ③通过调用KitlInit函数来初始化KITL。这部分代码可参照其它平台,代码文件为Halkitl.c。    

         ④执行0EMKitlInit函数,进行相关的硬件初始化工作。搜索是否存在KITL 网口、串口或并口连接。    

        ⑤执行完OEMKitlInit后,把Kitl.1ib和Kitleth.1ib包含入平台资源文件\\Kernel\Buildexe\Kernkitl,以便把KITL打包进内核。有关KITL的其它函数请参考微软MSDN。

     

    图4  KITL功能结构

          3.4 OEMIOControl

        OEMIOContr01在OAL层是一个非常重要的函数,应用程序是通过调用KernelIoContrOI来调用OEMl0Control的。内核对许多硬件平台信息的获得都要通过对它的调用来实现。此外,0EMl0Contr0I还是用户模式应用代码到内核模式OAL代码之间的转换入口。这就是说,用在用户模式下通过调用0EMl0Control可以获得内核模式的权力。0EMIOControl函数原型如下:
        BOOL OEMIoControl(...)
       {switch(dwloCtrolCode)
       {caseIOCTL_HAL_SET_DEVICE_INFO;
       case10CTL_HAL_REBOOT;
       ……
       default;
       return FALSE;
       }
       return TURE;
       }

        硬件资源利用和调试手段的加强大大丰富了OAL的功能,但是嵌入式系统通常会面临的功耗问题和由于网络功能的日益普及而带来的安全性问题并没有涉及到。       

     

     

     4. 完整OAL

        完整OAL是指在加强OAL的基础上扩充了功耗和安全性验证的OAL。所以这一阶段的主要工作集中在电源管理与模块认证两部分。   

        4.1 电源管理

        OAL层的电源管理与驱动程序的电源管理颇为不同。一种设备驱动程序仅负责某种特定的设备,如果可能,则把这种设备置为省电模式,当形势需要时再把设备置为满载荷模式。OAL层的电源管理则是负责整个系统功耗管理。例如,调度器在下一个25ms没有线程要运行时,系统将被置为省电模式。   

        电源管理函数响应关闭系统和使系统空闲的系统调用。这些系统调用可能是软触发也可能是硬触发。以下两个函数是须在OAL层中实现的电源管理函数:    

        ◇0EMIdle一一把设备置为空闲状态,此时系统处于低功耗状态;    
     
        ◇0EMPoweroff一一把设备置为断电状态;    

        ◇OEMPowerOff和OEMIdle的程序代码可在如下目录中参照例程%_WINCER00T%\Platform\\Kerlael\Hal。   

        4.2 模块认证

        自从Windows CE 3.0以来,在RAM中加载和运行模块前,内核可以对其进行授权核查。对于在ROM中运行的模块则不需要此过程。模块认证实际上是在被加载的模块后添加一数字签名,只有当系统用公开密钥验证数字签名通过后,该模块才可以被加载到RAM中运行。这样系统可以阻止或限制一些模块的运行,达到系统安全的目的。要达到以上目的须完成以下两个函数:    

        ◇OEMCertifyModuleInit,用于初始化验证过程,每验 证一个模块调用一次;    

        ◇OEMCertifyM0dule,用于验证数字签名。    

        为了支持这两个函数,在OEMInit函数中须分配两个全局变量pOEMLoadInit和p0EMLoadModule,用来存放这两个函数的地址。   

        结语

        Windows CE的OAL层是一个复杂的函数集。它的复杂性不但体现在包含函数数目繁多,而且体现在很多函数的硬件相关性非常大。本文并没有详细讲解每个OAL层函数,而是就一些通常会遇到的OAL层函数进行层层划分;在说明OAL层的功能和结构的同时,提出开发OAL的一种方法和思路。

     

    来源:21IC中国电子网

    系统分类: 嵌入式
    用户分类: 技术文收藏
    标签: wince OAL
    来源: 转贴
    发表评论 阅读全文(543) | 回复(0)

    1

    关于投票
    Windows CE 内存管理
    本文译自 Douglas Boling 的 《Programming Microsoft Windows CE.NET 3rd Ed》原文版权归原作者所有,译文版权归个人所有。离我第一篇blog 译文发布已有半年多,其实这些文章我在2003年就已经翻译完成,一直没空贴上来,但在CSDN论坛上看到许多人询问我关于内存的问题,觉得有必要将最新的经典文章带给大家,虽然该书没有中文版,但是仍然建议大家有空去买本原著,虽然亚马逊有点贵30多美金,但还是值得的。本文仅是初稿,不足之处请大家指正。译者:罗俊(nbcool) 原著:Douglas Boling。

    内存管理

           如果你在写Windows CE 程序中遇到的最重要的问题,那一定是内存问题。一个WinCE 系统可能只有4MB RAM,这相对于个人电脑来说是十分少的,因为个人电脑的标准配置已经到了128MB 甚至更多。事实上,运行WinCE 的机器的内存十分缺乏,以至于有时候有必要在写程序的时候为节约内存而牺牲程序的整体性能。

           幸运的是,尽管WinCE系统的内存很小,但可用来管理内存的函数却十分完善。WinCE实现了Microsoft Windows XPMicrosoft Windows Me中可用到的几乎全部的Win32内存管理APIWinCE支持虚拟内存(virtual memory)分配,本地(local)和分离(separate)的堆(heaps),甚至还有(memory-mapped files)内存映射文件。

           Windows XP一样,WinCE支持一个带有应用程序间内存保护功能的32位平面地址空间,但是WinCE是被设计来应用于不同场合,所以它底层的内存结构不同于Windows XP。这些不同能够影响到你如何设计一个WinCE 应用程序。在这一章中,我将讲述最基础的WinCE内存结构。我也将讲述包括WinCE中可用的内存分配方式中的不同点以及如何使用这些不同的内存类型来最小化你的程序的内存占有率。

    内存基础

           对所有的电脑来说,系统地运行一个WinCE,需要ROM(只读存储器)和RAM(随机存储器)。但不论如何,在WinCE系统中,ROMRAM的使用还是稍微有些不同于个人电脑环境。

    关于RAM

           RAMWinCE 系统中被分为两个区域:第一个是程序的存储区(program memory),也叫做系统堆(system heap)。第二个是对象存储区(object store)。这个对象存储区有点像一个永久的虚拟RAM磁盘。不同于PC上的旧式的虚拟RAM磁盘,对象存储区保留存储的文件甚至当系统被关闭以后。(脚注)这种安排的原因是WinCE 系统,例如Pocket PC代表性地具有一个主电池和一个备用电池。当用户更换主电池的时候,备用电池的工作是提供电源给RAM以便维持文件在对象存储区的存储。当用户按了重启键之后,WinCE核心就开始寻找在关闭系统前建立的对象存储区,如果找到的话就将继续使用它。

           RAM中的另一个区域则用作程序存储区。程序存储区有点像个人电脑中的RAM,它为正在运行的应用程序保存堆和栈的内容。在对象存储区和程序存储区之间的分界线是可以通过移动它来改变的,用户可以在控制面板中找到改变这条分界线的设置。在可用内存降低的(low-memory)条件下,系统将会弹出对话框询问用户是否要将对象存储区RAM划分一些给程序存储区RAM以满足要运行的应用程序的需求。

    关于ROM

           在个人电脑中,ROM是用来存储BIOS(基本输入输出系统)并且只有64128KB。在WinCE系统中,ROM大小可以从4MB32MB并且存放整个操作系统以及和系统捆绑在一起的应用程序。在这种情况下,ROMWinCE系统中就好像一个只读的硬盘。

           在一个WinCE系统中,存储在ROM之上的程序能够以现场执行(Execute in PlaceXIP)的方式运行。换句话说,程序可以直接从ROM中执行而不必先加载到RAM中再执行。这种能力对小型系统来说,使之在两个方面具有巨大的优势。代码直接从ROM中执行意味着程序代码不会占据更有价值的RAM。同样,程序在执行前也不必先复制到RAM中,这样就只需要很少的时间来启动一个应用程序。不在ROM中,但是被包含在对象存储区(译者注:上文将对象存储区比作永久的RAM磁盘,故此处要说明,只有Intel力推的nor flash memroy类型才能以XIP方式执行,ROM其实也是一种nor flash memory类型)或闪存卡(Flash memory storage card)中的程序将不能以现场方式执行,它们将被复制到RAM中再执行。

    关于虚拟内存

           WinCE 实现了系统的虚拟内存管理,在一个虚拟内存系统中,应用程序主要处理这个分离(译者注:物理上可能分离,但系统将它们联系起来),虚拟的地址空间,因此并不涉及到由硬件管理的物理内存。操作系统使用微处理器的内存管理单元来处理虚拟地址和物理地址间的实时转换。

           这种虚拟内存方法的优势能从MS-DOS系统复杂的地址空间看出来。一旦请求的RAM超过最初PC设计的640-KB限制,程序设计者将不得不作出像扩展内存一样的计划以便增加可用内存的数量。OS/2 1.x(译者注:IBM研制的操作系统)和Windows 3.0采用了一种基于段(segment-based)的虚拟内存系统来解决问题。应用程序使用虚拟内存不需要知道实际物理内存的位置,只要有内存可用就行。在这些系统中,虚拟内存以一种段的方式被实现了,即可移动的内存块(译者注:段其实就是内存分块)大小从16字节到64KB64-KB的限制并不是由于段本身原因,而是由于Intel 80286的特性所致,这就是Windows3.xOS/21.x的分段式虚拟内存系统结构。

    分页存储

    Intel 80386支持的段大小已经超过64KB,但是MicrosoftIBM开始设计OS/2 2.0,他们选择了一种不同的虚拟内存系统,随后也被386所支持,这就是分页式虚拟内存系统。在一个分页存储的系统中,最小的可被微处理器管理的单元是页(page)。对于Windows NTOS/2 2.0系统来说,页大小都被设置为386处理器默认的4096字节。当一个应用程序存取一个页的时候,微处理器将转换该页的虚拟内存地址到实际的ROMRAM中的物理页(译者注:这就是实现了地址映射和转换,将虚拟的和实际的存储单元一一对应),这一页同时被标记以便其他程序对该页的访问将被排斥。操作系统决定虚拟内存页是否有效,如果有效,将做一个物理内存页到虚拟页的映射。

    WinCE实现了一个和其他Win32操作系统类似的分页式虚拟内存系统。在WinCE中,一页的大小可以从1024字节到4096字节,基于微处理器的不同而不同。这和Windows XP不同,Windows XP页面尺寸是Intel微处理器所支持的4096字节。对WinCE所支持的CPU类型来说,有486IntelStrong-ARM,和Hitachi SH4 都是是用了4096-byte 的页面。NEC 4100Windows CE 3.0中使用了4-KB的页面尺寸但是在较早期的开放式系统版本中使用了1-KB的页面大小。

    虚拟内存页可以处在三种状态:自由(free),保留(reserved),或被提交(committed),)。自由页就像它的名称一样,自由并且可被分配。保留页是虚拟地址已经被保留,并且不能被操作系统或进程中的其他线程重新分配。保留页不能用在别处,但是它同样不能被当前程序使用,因为它没有被映射到物理内存。要想执行映射,它必须被提交,一个提交页能被应用程序保留,并且直接映射到物理地址。

    所有我刚才讲述的内容对有经验的Win32 程序员们来说是些陈旧的知识。对Windows CE 程序员来说最重要的东西是学习Windows CE 是如何改变这些因素的。当Windows CE 实现了大部分和它的老大哥Win32一样的内存API集的时候,Windows CE下面的基础结构将影响到上面的程序。在分开来看Window CE 应用程序的内存结构之前,让我们先来看看一些提供系统内存全局状态的函数。

    查询系统的内存

           如果一个应用程序知道系统当前的内存状态,它将可以较好地管理可用到的资源。WinCE实现了Win32GetSystemInfoGlobalMemoryStatus函数,GetSystemInfo函数原型如下:

    VOID GetSystemInfo (LPSYSTEM_INFO lpSystemInfo);

    它传递了一个指针给SYSTEM_INFO结构,定义如下

     

     

    wProcessorArchitecture参数表示系统微处理器的架构。它的值是定义在Winnt.h中,例如PROCESSOR_ARCHITECTURE_INTELWindows CE扩展了这些常数,包括PROCESSOR_ARCHITECTURE_ARMPROCESSOR_ARCHITECTURE_SHx,等等。增加的常数包括像Win32操作系统支持的网络CPUnet CPU)。跳过一些参数,我们看dwProcessorType参数,它来自于特定的微处理器类型。常数有Hitachi SHx架构中的PROCESSOR_HITACHI_SH3PROCESSOR_HITACHI_SH4。最后两个参数,wProcessorLevelwProcessorRevision,指明了CPU类型的特征。wProcessorLevel参数类似于dwProcessorType参数,它一个指定的微处理器系列中被定义了,dwProcessorRevision告诉你模式(model)和芯片的步进级别(stepping level)。

     

    typedef struct {

        WORD wProcessorArchitecture;

        WORD wReserved;

        DWORD  dwPageSize;

        LPVOID lpMinimumApplicationAddress;

        LPVOID lpMaximumApplicationAddress;

        DWORD  dwActiveProcessorMask;

        DWORD  dwNumberOfProcessors;

        DWORD  dwProcessorType;

        DWORD  dwAllocationGranularity;

        WORD  wProcessorLevel; 

        WORD  wProcessorRevision;

    } SYSTEM_INFO;

     

           dwPageSize参数说明了微处理器页面的大小,以字节为单位。知道这个值,将会在你直接处理虚拟内存API的时候带来方便,在此我只作简短说明。lpMinimumApplication­AddresslpMaximumApplicationAddress参数说明了应用程序可用到的最小和最大的虚拟内存地址。dwActiveProcessorMaskdwNumberOfProcessors参数显示被Window XP系统支持的多个处理器数量。因为Windows CE 只支持一个处理器,所以你可以忽略这个参数。dwAllocationGranularity参数说明了一个完整的虚拟内存区域分配的界限。像Windows XPWindows CE 规定虚拟区为64-KB的界限(译者注:作者此处64-KB的意思是即使你只分配一个字节的内存,系统也将会保留64-KB的虚拟地址空间给它,这个值一般是由硬件代码实现的,但是不同硬件可能不同值)。

           第二个方便的检测系统状态的函数如下:

    void GlobalMemoryStatus(LPMEMORYSTATUS lpmst);

    它返回一个MEMORYSTATUS结构,定义为

    typedef struct { 

        DWORD dwLength; 

        DWORD dwMemoryLoad; 

        DWORD dwTotalPhys; 

        DWORD dwAvailPhys; 

        DWORD dwTotalPageFile; 

        DWORD dwAvailPageFile; 

        DWORD dwTotalVirtual; 

        DWORD dwAvailVirtual; 

    } MEMORYSTATUS;

        dwLength参数在调用这个函数之前必须初始化。dwMemoryLoad参数是一个不确定的值;这是一个可用的一般性的参数指示了当前系统的内存使用情况(译者注:该参数是一个近似的百分比值,指明了物理内存的使用情况)。dwTotalPhysdwAvailPhys参数指明了RAM有多少页被分配给了程序存储区RAM,和还有多少可用(译者注:实际上是以字节为单位)。这些值不包括分配对象存储区的RAM

           dwTotalPageFiledwAvailPageFile参数在Windows XP下和Windows Me下指明了当前页面文件(paging file)的状态。因为Windows CE不支持页面文件,所以这些参数总是0dwTotalVirtualdwAvailVirtual参数指明了总共的和可用的可被应用程序存取的虚拟内存页的数量(译者注:参数都是以字节为单位,而不是指页数,dwAvailVirtual是指未保留和未提交的内存)。

           通过GlobalMemoryStatus返回的信息可以验证Windows CE内存结构,通过在有32MBRAMHP iPaq Pocket PC上调用函数,返回值如下:

    dwMemoryLoad       0x18          (24)

    dwTotalPhys        0x011ac000    (18,530,304)

    dwAvailPhys        0x00B66000    (11,952,128)

    dwTotalPageFile    0

    dwAvailPageFile    0

    dwTotalVirtual     0x02000000    (33,554,432)

    dwAvailVirtual     0x01e10000    (31,522,816)

     

        dwTotalPhys参数表明了系统的32MB RAM,分配了18.5MB给程序存储区RAM,其中12MB仍然可用。注意这对应用程序来说,并不是通过这次调用,就知道了另外14MBRAM是分配给对象存储区的。要检测分配给对象存储区的RAM的大小,要使用GetStoreInformation

           dwTotalPageFiledwAvailPageFile参数是0,表明页面文件不被Windows CE所支持。dwTotalVirtual参数十分有趣,因为它显示了Windows CE 强制给程序的32-MB的虚拟内存限制。其间,dwAvailVirtual参数显示了只使用了32MB虚拟内存的一小部分(译者注:即33,554,432-31,522,816=2,031,616)。

    应用程序的地址空间

    尽管和Windows XP的应用程序设计类似,但Windows CE应用程序地址空间有一个巨大的不同影响到应用程序。在Windows CE之下,一个应用程序被限制在虚拟内存空间中它自己的32MB slot(槽)和 32MB slot 1中,slot 1用来加载基于XIPDLL(译者注:Windows CE将虚拟地址空间分为33slot,每个slot 32MB,序号从0-32 ,附插图c-1,c-2)。当系统只有4MB RAM的时候,分配给应用程序32MB的虚拟地址空间看起来是比较合理的,Win32的程序员在使用这个2-GB的虚拟地址空间的时候,必须记住对Windows CE应用程序的虚拟地址空间限制。

    7-1展示了一个应用程序的64-MB虚拟地址空间,其中包括32MB用于XIPDLL空间。

     

    7-1     Windows CE的内存映射图

     

           要注意的是应用程序是以一个64-KB的内存区域开始从0x10000映射,记得最低的64KB地址空间是由Windows为所有应用程序保留的。文件映象包括代码,静态数据段和资源段。在实际过程中,当应用程序启动时代码页(code pages)不会载入进来,只有在需要该页面被载入的时候,代码才被载入进来。

           只读静态数据段(read-only static data segment)和可读写静态数据区(read/write static data areas)只占很少的页面。这些段都是排列在一起的。如同代码一样,只有当这些数据段被应用程序读或者写的时候才会提交给RAM。应用程序的资源将被载入到一些分离的页面中,这些资源是只读的,并且只有当它们被应用程序获取的时候才会分页进入RAM

           应用程序的栈(stack)被映射到资源段之上。栈的段位置很容易被找到,因为它提交的页就在被保留的区域的尾部。栈的表现是从高地址到低地址增长(译者注:即从高到低填满地址)。如果该应用程序有超过一个线程,那么应用程序的地址空间就会保留超过一个的栈的段。

           紧接着栈的就是本地堆(local heap)。引导程序保留了大量的页,大约有几百K交给heap来使用,但是只提交满足mallocnewLocalAlloc函数调用分配的内存(译者注:这里是说,被分配多少内存才可以提交多少内存,没被分配的不能用作提交)。从本地堆的保留页尾部到non-XIP DLL开始的部分剩余保留页面将被映射为自由的保留空间,如果RAM允许,应用程序可以提交这些保留页。Non-XIP DLLs 就是不能被在ROM中现场执行的DLL将被从上至下载入到32MB的地址空间。Non-XIP DLLs 包括那些被压缩存储在ROM中的DLL。被压缩的ROM 中的文件在被载入到RAM中执行前必须先解压缩。

           被保留给XIP DLLs32MB 应用程序地址空间的较高位置。Windows CE 映射这些XIP DLLs的代码进入这个空间(译者注:即较高空间),而可读写段被映射进较低位置。从Windows CE 4.2开始,在ROM中的纯资源的DLL将被载入到应用程序64MB空间之外的虚拟内存空间。

     

    脚注

    PocketPC这样的移动式系统中,当用户按下关闭按钮时系统将不会被真正的关闭,系统进入一种低功耗的挂起状态。

     

     

    VIA

    系统分类: 嵌入式
    用户分类: 技术文收藏
    标签: winCE 内存管理
    来源: 转贴
    发表评论 阅读全文(363) | 回复(0)

    1

    关于投票
    什么是 NAND Flash
    NOR和NAND是现在市场上两种主要的非易失闪存技术。Intel于1988年首先开发出NOR
    flash技术,彻底改变了原先由EPROM和EEPROM一统
    天下的局面。紧接着,1989年,东芝公司发表了NAND flash结构,强调降低每比特的成
    本,更高的性能,并且象磁盘一样可以通过接口
    轻松升级。但是经过了十多年之后,仍然有相当多的硬件工程师分不清NOR和NAND闪存。

      相"flash存储器"经常可以与相"NOR存储器"互换使用。许多业内人士也搞不清
    楚NAND闪存技术相对于NOR技术的优越之处,因
    为大多数情况下闪存只是用来存储少量的代码,这时NOR闪存更适合一些。而NAND则是高
    数据存储密度的理想解决方案。
      NOR的特点是芯片内执行(XIP, eXecute In Place),这样应用程序可以直接在flash
    闪存内运行,不必再把代码读到系统RAM中。
    NOR的传输效率很高,在1~4MB的小容量时具有很高的成本效益,但是很低的写入和擦除
    速度大大影响了它的性能。
      NAND结构能提供极高的单元密度,可以达到高存储密度,并且写入和擦除的速度也
    很快。应用NAND的困难在于flash的管理和需要
    特殊的系统接口。

    性能比较
      flash闪存是非易失存储器,可以对称为块的存储器单元块进行擦写和再编程。任何
    flash器件的写入操作只能在空或已擦除的单元
    内进行,所以大多数情况下,在进行写入操作之前必须先执行擦除。NAND器件执行擦除
    操作是十分简单的,而NOR则要求在进行擦除前
    先要将目标块内所有的位都写为0。
      由于擦除NOR器件时是以64~128KB的块进行的,执行一个写入/擦除操作的时间为5s
    ,与此相反,擦除NAND器件是以8~32KB的块进
    行的,执行相同的操作最多只需要4ms。
      执行擦除时块尺寸的不同进一步拉大了NOR和NADN之间的性能差距,统计表明,对于
    给定的一套写入操作(尤其是更新小文件时),
    更多的擦除操作必须在基于NOR的单元中进行。这样,当选择存储解决方案时,设计师必
    须权衡以下的各项因素。
      ● NOR的读速度比NAND稍快一些。
      ● NAND的写入速度比NOR快很多。
      ● NAND的4ms擦除速度远比NOR的5s快。
      ● 大多数写入操作需要先进行擦除操作。
      ● NAND的擦除单元更小,相应的擦除电路更少。

    接口差别
      NOR flash带有SRAM接口,有足够的地址引脚来寻址,可以很容易地存取其内部的每
    一个字节。
      NAND器件使用复杂的I/O口来串行地存取数据,各个产品或厂商的方法可能各不相同
    。8个引脚用来传送控制、地址和数据信息。
      NAND读和写操作采用512字节的块,这一点有点像硬盘管理此类操作,很自然地,基
    于NAND的存储器就可以取代硬盘或其他块设备。

    容量和成本
      NAND flash的单元尺寸几乎是NOR器件的一半,由于生产过程更为简单,NAND结构可
    以在给定的模具尺寸内提供更高的容量,也就
    相应地降低了价格。
      NOR flash占据了容量为1~16MB闪存市场的大部分,而NAND flash只是用在8~128M
    B的产品当中,这也说明NOR主要应用在代码存
    储介质中,NAND适合于数据存储,NAND在CompactFlash、Secure Digital、PC Cards和M
    MC存储卡市场上所占份额最大。

    可靠性和耐用性
      采用flahs介质时一个需要重点考虑的问题是可靠性。对于需要扩展MTBF的系统来说
    ,Flash是非常合适的存储方案。可以从寿命
    (耐用性)、位交换和坏块处理三个方面来比较NOR和NAND的可靠性。
      寿命(耐用性)
      在NAND闪存中每个块的最大擦写次数是一百万次,而NOR的擦写次数是十万次。NAND
    存储器除了具有10比1的块擦除周期优势,典型
    的NAND块尺寸要比NOR器件小8倍,每个NAND存储器块在给定的时间内的删除次数要少一
    些。
      位交换
      所有flash器件都受位交换现象的困扰。在某些情况下(很少见,NAND发生的次数要
    比NOR多),一个比特位会发生反转或被报告反转
    了。
      一位的变化可能不很明显,但是如果发生在一个关键文件上,这个小小的故障可能
    导致系统停机。如果只是报告有问题,多读几次
    就可能解决了。
      当然,如果这个位真的改变了,就必须采用错误探测/错误更正(EDC/ECC)算法。位
    反转的问题更多见于NAND闪存,NAND的供应商建
    议使用NAND闪存的时候,同时使用EDC/ECC算法。
      这个问题对于用NAND存储多媒体信息时倒不是致命的。当然,如果用本地存储设备
    来存储操作系统、配置文件或其他敏感信息时,
    必须使用EDC/ECC系统以确保可靠性。
      坏块处理
      NAND器件中的坏块是随机分布的。以前也曾有过消除坏块的努力,但发现成品率太
    低,代价太高,根本不划算。
      NAND器件需要对介质进行初始化扫描以发现坏块,并将坏块标记为不可用。在已制
    成的器件中,如果通过可靠的方法不能进行这项
    处理,将导致高故障率。

    易于使用
      可以非常直接地使用基于NOR的闪存,可以像其他存储器那样连接,并可以在上面直
    接运行代码。
      由于需要I/O接口,NAND要复杂得多。各种NAND器件的存取方法因厂家而异。
      在使用NAND器件时,必须先写入驱动程序,才能继续执行其他操作。向NAND器件写
    入信息需要相当的技巧,因为设计师绝不能向坏
    块写入,这就意味着在NAND器件上自始至终都必须进行虚拟映射。

    软件支持
      当讨论软件支持的时候,应该区别基本的读/写/擦操作和高一级的用于磁盘仿真和
    闪存管理算法的软件,包括性能优化。
      在NOR器件上运行代码不需要任何的软件支持,在NAND器件上进行同样操作时,通常
    需要驱动程序,也就是内存技术驱动程序(MTD
    ),NAND和NOR器件在进行写入和擦除操作时都需要MTD。
      使用NOR器件时所需要的MTD要相对少一些,许多厂商都提供用于NOR器件的更高级软
    件,这其中包括M-System的TrueFFS驱动,该驱
    动被Wind River System、Microsoft、QNX Software System、Symbian和Intel等厂商所
    采用。
      驱动还用于对DiskOnChip产品进行仿真和NAND闪存的管理,包括纠错、坏块处理和
    损耗平衡。

     

                            back.gif (341 bytes)       up.gif (335 bytes)         next.gif (337 bytes)