Tuesday, September 06, 2005

 

Some notes on AVI Manipulate - 2

1.
From http://dev.csdn.net/article/44/44105.shtm

当前,在Windows 平台下开发视频应用程序一般采用两种方式:一种是基于视频采集卡所附带的二次软件开发包SDK(Software development kit)进行。这种方式的优点是应用方便,容易上手,缺点是对硬件的依赖性较强,灵活性差,且功能参差不齐,不能充分满足各种视频应用程序的开发需要;

另一种方式是基于VFW(Video for Windows)进行的。VFW 是Microsoft公司为开发Windows平台下的视频应用程序提供的软件工具包,提供了一系列应用程序编程接口(API),用户可以通过它们很方便地实现视频捕获[1]、视频编辑及视频播放等通用功能,还可利用回调函数开发更复杂的视频应用程序。它的特点是播放视频时不需要专用的硬件设备,而且应用灵活,可以满足视频应用程序开发的需要。Windows操作系统自身就携带了VFW,系统安装时,会自动安装VFW的相关组件。VC++自4.0以来就支持VFW,大大简化了视频应用程序的开发。目前,PC机上多媒体应用程序的视频部分,大都是利用VFW API开发的。

1 VFW 的体系结构

VFW以消息驱动方式对视频设备进行存取,可以很方便地控制设备数据流的工作过程。目前,大多数的视频采集卡驱动程序都支持VFW接口,它主要包括多个动态连接库,通过这些组件间的协调合作,来完成视频的捕获、视频压缩及播放功能。VFW体系结构如图1所示。

1)VICAP.DLL:主要实现视频捕获功能,包含了用于视频捕获的函数,为音像交错AVI (Audio video interleaved)格式文件和视频、音频设备程序提供一个高级接口。

2)MSVIDEO.DLL:能够将视频捕获窗口与获驱动设备连接起来,支持ICM视频编码服务。

3)MCIAVI.DRV:包含MCI(Media control interface)命令解释器,实现回放功能。

4)AVIFILE.DLL:提供对AVI文件的读写操作等文件管理功能。

5)ICM ( Installable compression manager ):即压缩管理器,提供对存储在AVI文件中视频图像数据的压缩、解压缩服务。

6)ACM ( Audio Compression Manager ):即音频压缩管理器,提供实时音频压缩及解压缩功能。

Capture Application

Playback Application

Edit Application

AVICAP.DLL

AVICAP

MSVIDEO.DLL

MCIWnd

ACM

MCIAVI.DRV

MCI Command

Interpreter

MSVIDEO.DLL

Video in ICM

Channel

AVIFILE.DLL

File / Stream

Handler

MSVIDEO.DLL

Drawdib ICM

Capture Application

Playback Application

Edit Application

AVICAP.DLL

AVICAP

MSVIDEO.DLL

MCIWnd

ACM

MCIAVI.DRV

MCI Command

Interpreter

MSVIDEO.DLL

Video in ICM

Channel

AVIFILE.DLL

File / Stream

Handler

MSVIDEO.DLL

Drawdib ICM

Capture Application

Playback Application

Edit Application

AVICAP.DLL





AVICAP

MSVIDEO.DLL





MCIWnd

ACM

MCIAVI.DRV





MCI Command

Interpreter

MSVIDEO.DLL





Video in ICM

Channel



AVIFILE.DLL



File / Stream

Handler



MSVIDEO.DLL



Drawdib ICM


2 视频捕获

视频数据的实时采集,主要通过AVICAP模块中的消息、宏函数、结构以及回调函数来完成。视频捕获的一般过程如下:

2.1 建立捕获窗口

利用AVICAP 组件函数 capCreateCaptureWindow() 建立视频捕获窗口,它是所有捕获工作及设置的基础,其主要功能包括:① 动态地同视频和音频输入器连接或断开;② 设置视频捕获速率;③ 提供视频源、视频格式以及是否采用视频压缩的对话框;④ 设置视频采集的显示模式为Overlay或为Preview; ⑤ 实时获取每一帧视频数据;⑥ 将一视频流和音频流捕获并保存到一个AVI文件中; ⑦ 捕获某一帧数字视频数据,并将单帧图像以DIB格式保存;⑧ 指定捕获数据的文件名,并能将捕获的内容拷贝到另一文件。

2.2 登记回调函数[2]

登记回调函数用来实现用户的一些特殊需要。在以一些实时监控系统或视频会议系统中,需要将数据流在写入磁盘以前就必须加以处理,达到实时功效。应用程序可用捕获窗来登记回调函数,以便及时处理以下情况:捕获窗状态改变、出错、使用视频或音频缓存、放弃控制权等,相应的回调函数分别为 capStatusCallback(), capErrorCallback(), capVideoStreamCallback(), capWaveStreamCallback(),capYieldCallback()。

2.3 获取捕获窗口的缺省设置

通过宏capCaptureGetSetup(hWndCap,&m_Parms,sizeof(m_Parms))来完成。

2.4 设置捕获窗口的相关参数

通过宏capCaptureSetSetup(hWndCap,&m_Parms,sizeof(m_Parms))来完成。

2.5 连接捕获窗口与视频捕获卡

通过宏capDriveConnect(hWndCap,0)来完成。

2.6 获取采集设备的功能和状态

通过宏capDriverGetCaps(hWndCap,&m_CapDrvCap,sizeof(CAPDRIVERCAPS))来获取

视频设备的能力,通过宏capGetStatus(hWndCap,&m_CapStatus,sizeof(m_CapStatus))

来获取视频设备的状态。

2.7 设置捕获窗口显示模式

视频显示有Overlay(叠加)和Preview(预览)两种模式。在叠加模式下,捕获视频数据布展系统资源,显示速度快,视频采集格式为YUV格式,可通过capOverlay(hWndCap,TRUE)来设置;预览模式下要占用系统资源,视频由系统调用GDI函数在捕获窗显示,显示速度慢,它支持RGB视频格式。

2.8 捕获图像到缓存或文件并作相应处理

若要对采集数据进行实时处理,则应利用回调机制,由capSetCallbackOnFrame(hWndCap, FrameCall-

backProc)完成单帧视频采集;由capSetCallbackOnVideoStream(hWndCap, VideoCallbackProc)完成视频流采集。如果要保存采集数据,则可调用capCaptureSequence(hWnd);要指定文件名,可调用capFileSetCap-

ture(hwnd, Filename)。

2.9 终止视频捕获 断开与视频采集设备的连接

调用capCatureStop(hWndCap)停止采集,调用capDriverDisconnect(hWndCap), 断开视频窗口与捕获驱动程序的连接。

3 视频编辑和播放

利用VFW,不仅可以实现视频流的实时采集,还提供了编辑和播放功能,主要通过AVIFILE、ICM、ACM、MCIWnd 等组件之间的协作来完成。

1) AVIFileInit();//初始化;

2) AVIFileOpen(); //打开一个AVI文件并获文件的句柄;

3) AVIFileInfo(); //获取文件的相关信息,如图像的Width和Height等;

4) AVIFileGetStream(); //建立一个指向需要访问的数据流的指针;

5) AVIStreamInfo(); //获取存储数据流信息的AVISTREAMINFO结构;

6) AVIStreamRead(); //读取数据流中的原始数据, 对AVI文件进行所需的编辑处理;

7) AVIStreamRelease(); //释放指向视频流的指针;

8) AVIFileRelease();AVIFileExit(); //释放AVI文件。

若数据是压缩过的,则用AVIStreamGetFrameOpen(),AVIStreamGetFrame()和AVIStreamGetFrameClose()来操作,可以完成对视频流的逐帧分解。

3.2 视频播放

对于实现视频流的播放,VFW提供了MCIWnd窗口类[4],主要用于创建视频播放区,控制并修改MCI窗口当前加载媒体的属性。一个由函数、消息和宏组成的库与MCIWnd相关联,通过它们可以进行AVI文件操作,很方便地使应用程序完成视频播放功能。

1)MCIWndCreate(); //注册MCIWnd窗口类,创建MCIWnd窗口,并指定窗口风格;

2)AVIFileInit(); //初始化;

3) AVIFileOpen(); //打开AVI文件;

4) AVIFileGetStream(); //获得视频流;

5)运用相关函数进行各种播放任务:MCIWndPlay()正向播放AVI文件内容,MCIWndPlayReverse()反向播放,MCIWndResume() 恢复播放,MCIWndPlayPause()暂停播放,MCIWndStop()停止播放等等。

6) AVIStreamRelease(); //释放视频流;

7)AVIFileRease();AVIFileExit(); //断开与AVI文件的连接,释放视频源。

由以上步骤可以看出,视频播放是视频编辑其中的一种操作。

2.
from http://act.it.sohu.com/book/chapter.php?id=24&volume=6&chapter=1

第6章 VFW软件开发包


6.1 VFW简介/6.2 AVI文件流操作(1)


VFW(Video for Windows)是Microsoft推出的关于数字视频的一个软件开发包,VFM的核心是AVI文件标准。AVI(Audio Video Interleave)文件中的视频数据帧交错存放。围绕AVI文件,VFW推出了一整套完整的视频采集、压缩、解压、回放和编辑的应用程序编程接口(API)。由于AVI文件格式推出较早且在数字视频技术中有广泛的应用,所以VFM仍有很大的实用价值,且有进一步发展之势。

在VFM的基本结构中,本章重点介绍以下模块的调用:

(1)AFIFILE.dll:支持标准多媒体I/O函数及宏调用。

(2)Avfcap.dll:它提供视频采集功能与外接硬件(如摄像头)打交道。

(3)VCM视频压缩管理器:它负责视频压缩/解码,是为应用程序调用底层压缩驱动程序而设计的。

(4)MSVIDE.dll:在视图中画出视频数据图像。

在VC++开发环境中调用VFW与使用其他开发包没有什么不同,只是需要将VFW32.lib文件加入工程中,但需要注意的是在开发视频捕捉与压缩管理程序时需要其他软硬件设置,具体情况将在以下章节中叙述。另外,在VC++安装目录下提供了一些小的工具程序,可以用于测试硬件设备性能,如果读者要更深入的研究,可以参阅开发文档。

6.2 AVI文件流操作

VFW为AVI文件提供了丰富的处理函数和宏定义,AVI文件的特点在于它是典型的数据流文件,它由视频、音频流和文本流组成。所以对AVI文件的处理主要是处理文件流。

本节我们将依次结合具体实例讲述一下常用操作:

(1)打开/关闭文件流;

(2)读/写数据操作;

(3)在文件中定位;

(4)用剪贴板编辑数据流。

有关AVI文件处理的详细信息可在MSDN中的SDK/GRAPHICS AND MULTIMEDIA SERVERS/VEDIO FOR WINDOWS中找到。

6.2.1 打开/关闭文件流

首先要用AVIFILEINIT函数初始化AVIFILE库。下面的例程演示了打开和关闭一个AVI文件并关联的过程。

BOOL LoadAVIFileThenClose(LPCSTR strFileName, HWND hwnd)

// LoadAVIFile - loads AVIFile and opens an AVI file.

//

// szfile - filename

// hwnd - window handle

//

{

LONG hr;



PAVIFILE pfile;



BOOL success;



AVIFileInit(); // opens AVIFile library



success = AVIFileOpen(&pfile, strFileName, OF_SHARE_DENY_NONE , 0L);



//Returns zero if successful or an error otherwise

if (success != 0)

{

ErrMsg("Unable to open %s", szFile);

return success;

}



// Place functions here that interact with the open file.

AVIFileRelease(pfile); // closes the file



AVIFileExit(); // releases AVIFile library

return success;

}


注意:AVIFILEPOPEN中的第三个参数为打开方式,表示程序可与其他线程读写该文件。

6.2.2 读写文件数据

下面我们演示如何打开文件数据流并写数据。首先用OpenAllAVIFileStream例程打开文件用的所有数据流,打开数据流的句柄放入gapavi[i]中。gapavi[i]设为全局变量,其类型为Pavistream类型。为了进一步操作数据流,首先得到数据流的信息。

下面的GetStreamTypes例程从数据流中得到数据类型。

6.2 AVI文件流操作(2)


在以下读写数据流的操作中,我们从一个数据流中拷贝数据,并压缩,然后把压缩数据写到另一个新建的文件中。

void SaveSmall(PAVISTREAM ps, LPSTR lpFilename)

// SaveSmall - copies a stream of data from one file, compresses

// the stream, and writes the compressed stream to a new file. //

// ps stream interface pointer

// lpFilename - new AVI file to build

//

{

PAVIFILE pf;

PAVISTREAM psSmall;

HRESULT hr;

AVISTREAMINFO strhdr;

BITMAPINFOHEADER bi;

BITMAPINFOHEADER biNew;

LONG lStreamSize;

LPVOID lpOld;

LPVOID lpNew;

// Determine the size of the format data using

// AVIStreamFormatSize.

AVIStreamFormatSize(ps, 0, &lStreamSize);



if (lStreamSize > sizeof(bi)) // Format too large?



return;



lStreamSize = sizeof(bi);



hr = AVIStreamReadFormat(ps, 0, &bi, &lStreamSize); // Read format



if (bi.biCompression != BI_RGB) // Wrong compression format?



return;



hr = AVIStreamInfo(ps, &strhdr, sizeof(strhdr));

// Create new AVI file using AVIFileOpen.



hr = AVIFileOpen(&pf, lpFilename, OF_WRITE | OF_CREATE, NULL);



if (hr != 0) return;



// Set parameters for the new stream.

biNew = bi;

biNew.biWidth /= 2;

biNew.biHeight /= 2;

biNew.biSizeImage =

((((UINT)biNew.biBitCount * biNew.biWidt + 31)&~31) / 8) * biNew.biHeight;



SetRect(&strhdr.rcFrame, 0, 0, (int) biNew.biWidth,

(int) biNew.biHeight);



// Create a stream using AVIFileCreateStream.

hr = AVIFileCreateStream(pf, &psSmall, &strhdr);



if (hr != 0)

{ //Stream created OK? If not, close file.

AVIFileRelease(pf);

return;

}



// Set format of new stream using AVIStreamSetFormat.

hr = AVIStreamSetFormat(psSmall, 0, &biNew, sizeof(biNew));

if (hr != 0)

{

AVIStreamRelease(psSmall);

AVIFileRelease(pf);

return;

}



// Allocate memory for the bitmaps.

lpOld = GlobalAllocPtr(GMEM_MOVEABLE, bi.biSizeImage);

lpNew = GlobalAllocPtr(GMEM_MOVEABLE, biNew.biSizeImage);



// Read the stream data using AVIStreamRead.

for (lStreamSize = AVIStreamStart(ps); lStreamSize <

AVIStreamEnd(ps); lStreamSize++)

{

hr = AVIStreamRead(ps, lStreamSize, 1, lpOld, bi.biSizeImage,

NULL, NULL);

// Place error check here.





// Compress the data.

CompressDIB(&bi, lpOld, &biNew, lpNew);



// Save the compressed data using AVIStreamWrite.

hr = AVIStreamWrite(psSmall, lStreamSize, 1, lpNew,

biNew.biSizeImage, AVIIF_KEYFRAME, NULL, NULL);

}

// Close the stream and file.

AVIStreamRelease(psSmall);

AVIFileRelease(pf);



//free the memory that have been malloced

GlobalFreePtr (lpOld);

GlobalFreePtr (lpNew);

}


注意:在操作完成后,要释放已分配的内存,另外要检验每个函数调用后的返回结果。需要的话,要根据不同结果设置处理代码。

6.2 AVI文件流操作(3)


6.2.3 利用剪贴板编辑文件

利用剪贴板编辑文件流是VFW的一大特色。接下来的例子中,我们从一组数据流中剪切、复制和删除数据。把剪切和拷贝下来的数据合并到一个新的文件中,最后把它放到剪贴板上。VFW提供了EditStreamClose和EditStreamCut来支持这些功能。(剪贴板是Windows操作系统内定义的一块全局内存。Windows API有专用于针对剪贴板的操作例程,利用它还可以实现不同线程的数据通信,具体资料可参见MSDN中的Visual C++ Documention。)

// Global variables

// gcpavi - count of streams in an AVI file

// gapavi[] - array of stream-interface pointers, used as data source

// gapaviSel[] - stream-interface pointers of edited streams

// galSelStart[] - edit starting point in each stream

// galSelLen[] - length of edit to make in each stream

// gapaviTemp[] - array of stream-interface pointers put on clipboard //

// Comment:

// The editable streams in gapaviSel have been

// initialized with CreateEditableStream.



case MENU_CUT:



case MENU_COPY:



case MENU_DELETE:

{

PAVIFILE pf;

int i;

// Walk list of selections and make streams out of each section.

gcpaviSel = 0; // index counter for destination streams

for (i = 0; i < gcpavi; i++)

{

if (galSelStart[i] != -1)

{

if (wParam == MENU_COPY)

EditStreamCopy(gapavi[i], &galSelStart[i],

&galSelLen[i], &gapaviSel[gcpaviSel++]);

else

{

EditStreamCut(gapavi[i], &galSelStart[i],

&galSelLen[i], &gapaviSel[gcpaviSel++]);

}

}

}

. . . . . .

// Put on the clipboard if segment is not deleted.



if (gcpaviSel && wParam != MENU_DELETE)

{

PAVISTREAM gapaviTemp[MAXNUMSTREAMS];

int i;

// Clone the edited streams, so that if the user does

// more editing, the clipboard won't change.



for (i = 0; i < gcpaviSel; i++)

{

gapaviTemp[i] = NULL;

EditStreamClone(gapaviSel[i], &gapaviTemp[i]);

// Place error check here.

}

// Create a file from the streams and put on clipboard.

AVIMakeFileFromStreams(&pf, gcpaviSel, gapaviTemp);

AVIPutFileOnClipboard(pf);

// Release clone streams.

for (i = 0; i < gcpaviSel; i++)

{

AVIStreamRelease(gapaviTemp[i]);

}

// Release file put on clipboard.

AVIFileRelease(pf);

}

// Release streams created.

for (i = 0; i < gcpaviSel; i++)

AVIStreamRelease(gapaviSel[i]);

}


6.2.4 应用实例

下面将介绍的代码被封装到一个叫做CWriteAvi的类中,主要提供两个对象方法:

(1)CAVIFile(LPCTSTR lpszFileName,int xdim=-1,int ydim=-1):初始化成员以便打开AVI文件库。

6.2 AVI文件流操作(4)

(2)Bool AddFrame(CBitmap& bmp):构建一帧位图数据并将其按一定格式压缩后,写入一个打开的或新建的AVI数据流中。这个例子有助于大家深入了解AVIFile函数操作。读者可以在自己的应用程序中引用这个类,并进一步修改完善。

//*********************************************************

// WriteAvi.h

//*********************************************************

#include



#define TEXT_HEIGHT 20

#define AVIIF_KEYFRAME 0x00000010L // this frame is a key frame.

#define BUFSIZE 260



class CAVIFile : public CObject

{

public:

CAVIFile(LPCTSTR lpszFileName, int xdim =-1, int ydim =-1);

virtual ~CAVIFile();



virtual bool AddFrame(CBitmap& bmp);

CString GetFName() const {return FName;};

virtual bool IsOK() const {return bOK;};



private:

CString FName;

int xDim;

int yDim;



AVISTREAMINFO strhdr;

PAVIFILE pfile;

PAVISTREAM ps;

PAVISTREAM psCompressed;

PAVISTREAM psText;

AVICOMPRESSOPTIONS opts;

AVICOMPRESSOPTIONS FAR * aopts[1];

DWORD dwTextFormat;

char szText[BUFSIZE];

int nFrames;

bool bOK;};



//******************************************

//writevi.cpp

//******************************************

#include “stdafx.h”

#include

#include



#include

#include #include

"writeavi.h"

static HANDLE MakeDib( HBITMAP hbitmap, UINT bits )

{ HANDLE hdib ;

HDC hdc ;

BITMAP bitmap ;

UINT wLineLen ;

DWORD dwSize ;

DWORD wColSize ;

LPBITMAPINFOHEADER lpbi ;

LPBYTE lpBits ;

//

//The GetObject function obtains information about a specified

//graphics object. Depending on the graphics object,the function

//places a filled-in BITMAP structure into a specified buffer.

//



GetObject(hbitmap,sizeof(BITMAP),&bitmap) ;



//

// DWORD align the width of the DIB

// Figure out the size of the colour table

// Calculate the size of the DIB

//

wLineLen = (bitmap.bmWidth*bits+31)/32 * 4;

wColSize = sizeof(RGBQUAD)*((bits <= 8) ? 1<
dwSize = sizeof(BITMAPINFOHEADER) + wColSize +

(DWORD)(UINT)wLineLen*(DWORD)(UINT)bitmap.bmHeight;



//

// Allocate room for a DIB and set the LPBI fields

//

hdib = GlobalAlloc(GHND,dwSize);

if (!hdib) return hdib ;



lpbi = (LPBITMAPINFOHEADER)GlobalLock(hdib) ;



lpbi->biSize = sizeof(BITMAPINFOHEADER) ;

lpbi->biWidth = bitmap.bmWidth ;

lpbi->biHeight = bitmap.bmHeight ;

lpbi->biPlanes = 1 ;

lpbi->biBitCount = (WORD) bits ;

lpbi->biCompression = BI_RGB ;

lpbi->biSizeImage = dwSize - sizeof(BITMAPINFOHEADER) - wColSize ;

lpbi->biXPelsPerMeter = 0 ;

lpbi->biYPelsPerMeter = 0 ;

lpbi->biClrUsed = (bits <= 8) ? 1<
lpbi->biClrImportant = 0 ;



//

// Get the bits from the bitmap and stuff them after the LPBI

//

lpBits = (LPBYTE)(lpbi+1)+wColSize ;



hdc = CreateCompatibleDC(NULL) ;



GetDIBits(hdc,hbitmap,0,bitmap.bmHeight,lpBits,(LPBITMAPINFO)lpbi, DIB_RGB_COLORS);



// Fix this if GetDIBits messed it up....

lpbi->biClrUsed = (bits <= 8) ? 1<


DeleteDC(hdc) ;

GlobalUnlock(hdib);



return hdib ;

}



CAVIFile::CAVIFile(LPCTSTR lpszFileName, int xdim, int ydim)

: FName(lpszFileName),

xDim(xdim), yDim(ydim), bOK(true), nFrames(0)

//

//Initial class members and

//open the AVI library

//

{

pfile = NULL;

ps = NULL;

psCompressed = NULL;

psText = NULL;

aopts[0] = &opts;

WORD wVer = HIWORD(VideoForWindowsVersion());

if (wVer < 0x010A)

{

// oops, we are too old, blow out of here

AfxMessageBox("the Version is too old");

bOK = false;

}

else

{

AVIFileInit();

}

}



CAVIFile::~CAVIFile()

//

//Disorgnazie the Object ,close the streams opened,

//close the AVIFile library

//

{

if (ps)

AVIStreamClose(ps);



if (psCompressed)

AVIStreamClose(psCompressed);



if (psText)

AVIStreamClose(psText);



if (pfile)

AVIFileClose(pfile);



WORD wVer = HIWORD(VideoForWindowsVersion());

if (wVer >= 0x010A)

{

AVIFileExit();

}

}



bool CAVIFile::AddFrame(CBitmap& bmp)

{

HRESULT hr;

char szMessage[BUFSIZE];



if (!bOK)

return false;

LPBITMAPINFOHEADER alpbi = (LPBITMAPINFOHEADER)GlobalLock (MakeDib(bmp, 8));



if (alpbi == NULL) return false;



if (xDim>=0 && xDim != alpbi->biWidth)

{

GlobalFreePtr(alpbi);

return false;

}

if (yDim>=0 && yDim != alpbi->biHeight)

{

GlobalFreePtr(alpbi);

return false;

}



xDim = alpbi->biWidth;

yDim = alpbi->biHeight;



if (nFrames == 0)

{

hr = AVIFileOpen(&pfile, // returned file pointer

FName, // file name

OF_WRITE | OF_CREATE // mode to open file with

|OF_READ,

NULL); // use handler determined

// from file extension....

` if (hr != AVIERR_OK)

{

GlobalFreePtr(alpbi);

bOK = false;

return false;

}



//Fill the strhdr with zero

_fmemset(&strhdr, 0, sizeof(strhdr));

//

//Four-character code of the compressor handler that will

//compresses this video stream when it is saved.

//

strhdr.fccType = streamtypeVIDEO;// stream type

strhdr.fccHandler = mmioFOURCC(''M'',''S'',''V'',''C''));

//

//Dividing dwRate by dwScale gives the playback rate

//in number of samples per second.

//

strhdr.dwScale = 1;

strhdr.dwRate = 15; // 15 fps

strhdr.dwSuggestedBufferSize = alpbi->biSizeImage;



//SetRect assigns the left, top, right,

//and bottom arguments to the appropriate

//members of the RECT structure &strhdr.rcFrame

//with (0,0,alpbi->biWidth,alpbi->biHeight.

//

// rectangle for stream

SetRect(&strhdr.rcFrame, 0, 0, (int)alpbi->biWidth,

(int) alpbi->biHeight);



// And create the stream;

hr = AVIFileCreateStream(pfile, // file pointer

&ps, // returned stream pointer

&strhdr); // stream header

if (hr != AVIERR_OK)

{

GlobalFreePtr(alpbi);

bOK = false;

return false;

}

//Fill the opts with zero

_fmemset(&opts, 0, sizeof(opts));


if (!AVISaveOptions(NULL, 0, 1, &ps,

(LPAVICOMPRESSOPTIONS FAR *)&aopts))

{

GlobalFreePtr(alpbi);

bOK = false;

return false;

}



hr = AVIMakeCompressedStream(&psCompressed, ps, &opts, NULL);

if (hr != AVIERR_OK)

{

GlobalFreePtr(alpbi);

bOK = false;

return false;

}



hr = AVIStreamSetFormat(psCompressed, 0,

alpbi, // stream format

alpbi->biSize + // format size

alpbi->biClrUsed * sizeof(RGBQUAD));

if (hr != AVIERR_OK)

{

GlobalFreePtr(alpbi);

bOK = false;

return false;

}



// Fill in the stream header for the text stream……

// The text stream is in 60ths of a second……



_fmemset(&strhdr, 0, sizeof(strhdr));

strhdr.fccType = streamtypeTEXT;

strhdr.fccHandler = mmioFOURCC(''D'', ''R'', ''A'', ''W'');

strhdr.dwScale = 1;

strhdr.dwRate = 60;

strhdr.dwSuggestedBufferSize = sizeof(szText);



SetRect(&strhdr.rcFrame, 0, (int) alpbi->biHeight,

(int) alpbi->biWidth, (int) alpbi->biHeight + TEXT_HEIGHT);



//……and create the stream.

hr = AVIFileCreateStream(pfile, &psText, &strhdr);

if (hr != AVIERR_OK)

{

GlobalFreePtr(alpbi);

bOK = false;

return false;

}



dwTextFormat = sizeof(dwTextFormat);

hr = AVIStreamSetFormat(psText, 0, &dwTextFormat, sizeof (dwTextFormat));

if (hr != AVIERR_OK)

{

GlobalFreePtr(alpbi);

bOK = false;

return false;

}



}



hr = AVIStreamWrite(psCompressed, // stream pointer

nFrames * 10, // time of this frame

1, // number to write

(LPBYTE) alpbi + // pointer to data

alpbi->biSize +

alpbi->biClrUsed * sizeof(RGBQUAD),

alpbi->biSizeImage, // size of this frame

AVIIF_KEYFRAME, // flags....

NULL,

NULL);

if (hr != AVIERR_OK)

{

GlobalFreePtr(alpbi);

bOK = false;

return false;

}



// Make some text to put in the file ...

//LoadString(hInstance, IDS_TEXTFORMAT, szMessage, BUFSIZE );



strcpy(szMessage, "This is frame #%d");



int iLen = wsprintf(szText, szMessage, (int)(nFrames + 1));



// ... and write it as well.

hr = AVIStreamWrite(psText,

nFrames * 40,

1,

szText,

iLen + 1,

AVIIF_KEYFRAME,

NULL,

NULL);

if (hr != AVIERR_OK)

{

GlobalFreePtr(alpbi);

bOK = false;

return false;

}



GlobalFreePtr(alpbi);

nFrames++;

return true;

}

6.3 用DrawDib绘制图像(1)


在整个VFW体系中,DrawDib模块用于在视频终端(如显示器)上回放视频数据,这里的数据可以是摄像头直接采集的,也可以是位图文件。DrawDib的函数DIBs(Device_Independent Bitmaps)支持高性能图像显示,其色彩深度包括8位、16位、24位、32位。另外DrawDib直接写入视频缓冲区,不依赖Windows GDI函数。

6.3.1 DrawDib的性能特点

总的来说,DrawDib函数提供图像显示、图像缩放以及色彩变换等功能,另外还提供图像解压这一数据流功能。在使用过程中要注意,DrawDib只能显示使用DIB_RGB_COLORS格式的图像,并只用SRCOPY方式显示位图,对于其他情况要考虑用StretchDIBits函数。但另一方面,DrawDib支持16色VGA显示以及256色SVGA显卡,并有高质量的色彩适配功能(如能在16色显卡上显示256色图像)。同样值得一提的是,DrawDib可以显示任何Windows压缩标准(如RELE、411YVV、INDEO等)的图像。

6.3.2 DrawDib的函数

应用程序可调用DrawDib函数来建立并管理一个DrawDib Dc(Device Contxt),显示或更新图像,操作调色板,以及关闭DrawDib DC。另外,DrawDib也支持计时功能,这一功能只用于调试版的应用程序,用以测定显示特性。

表6.1列出了几个函数及其主要功能。

表6.1 DrawDib函数

函 数
解 释

库操作:


DrawDibOpen
打开DrawDib库, 创建DrawDib DC

DrawDibClose
关闭DrawDib DC

DrawDibProfileDisplay
获取当前系统处理DrawDib类函数的模式

图像显示:


DrawDibDraw
绘制 DIB 图像

DrawDibGetBuffer
获得解压图像的缓冲区

DrawDibUpdate
显示缓冲区的最后一幅图像

图像序列:


DrawDibBegin
改变一个DrawDib DC 或重新初始化一个DrawDib DC




续表

函 数
解 释

DrawDibEnd
清除对DrawDib DC所作的修改

DrawDibStart
为视频流回放准备DrawDib DC

DrawDibStop
示范用于视频流回放的资源

调色板:


DrawDibRealize
根据DC 设置调色板

DrawDibSetPalette
设置显示DIB 的调色板

DrawDibGetPalette
获得调色板

DrawDibChangPalette
改变调色板

DrawDib的时间调试:


DrawDibTime
获得显示操作的时间信息


6.3.3 DrawDib的运行方式

首先在做任何操作之前,我们要调用DrawDibOpen来装载动态链接库(dll),分配内存资源,创建一个DrawDib DC。它返回一个HDRAWDIB类型的对象指针,以供后面的函数使用,该指针指向已创建的DC对象。当一切操作完成后,相应地用DrawDibClose函数释放。为应用程序,可以同时创建多个DC对象,特别是当你要同时绘制多个位图时,但最好给不同的DC赋予不同的特征值,以便用来识别或设置不同功能。我们也可以在同一个视图中建立2个DC:一个用来显示位图全貌,另一个放大显示其中一部分。

为了方便运行,函数需要了解显卡适配器及其驱动程序的有关信息。可使用函数DrawDibProfileDisplay,根据它的返回值来判断系统对位图格式的支持情况。

6.3.4 位图文件格式

要装载一张位图,首先要了解它的格式。图6.1描述了一个位图文件的结构(不管它是在内存中还是在磁盘上)。


图6.1 位图文件的结构

一般来讲,一个位图文件有三部分组成:一个BITMAPINFOHEADER结构、一个颜色表以及图形数据主体。描述位图文件性质以及文件格式的信息写在文件头结构中。如果一个位图拥有颜色表,则图像数据由首字节编入颜色表索引中,但如果是256色图像,则没有颜色表,它们用RGB(RED,GREEN,BLUE)三色值表示色彩,这样的文件体积大,但效果好。

6.3.5 使用Palette增强图像

DrawDib中调用调色板的函数已列在表6.1中。可以把调色板想像成Windows管理色彩显示的重要部分。一个调色板实体的数据结构定义如下:

typedef struct tagPALETTEENTRY

{

BYTE peRed;

BYTE peGreen;

BYTE peBlue;

BYTE peFlags;

} PALETTEENTRY;


最后一位PeFlags定义了调色板实体的使用方式,其余三个元素定义了各种基本颜色的显示深度,我们称以上调色板为系统调色板。另外,还有一种调色板叫做逻辑调色板LOGPALETTE,其结构定义如下:

typedef struct tagLOGPALETTE

{

WORD palVersion;

WORD palNumEntries;

PALETTEENTRY palPalEntry[1];

} LOGPALETTE;


但一般程序并不需要设定逻辑调色板。

在有关调色板编程中,应用程序提供两个消息调用接口:

(1)WM_QVERYNEWPALETTE(无参数)

它用来在一个视图窗口获得“焦点”(处于激活状态)时,给窗口一个实现逻辑调色板的机会。这时可以暂时使窗口无效,好让DrawDibDraw重绘视图。

(2)WM_PALETTECHANGED:参数hwndpalchg

它用来在激活窗口实现了逻辑调色板(从而改变了系统调色板)之后,通知所有打开的窗口,更新他们的调色板。但注意,如果这个窗口在此时实现它自己的调色板,那么这个窗口线程就会陷入死循环。可以用消息参数hwndpalchg(它代表引起系统调色板变化的那个窗口的句柄)判断是否可以调用DrawDibRealize。

6.3 用DrawDib绘制图像(2)


下面一段程序简单描述了消息的处理方法:

case WM_PALETTECHANGED:



if ((HWND)wParam == hwnd) //if it is me that cause the

//system palette changed,

break; //doing nothing;otherwise

//execute the programme below;



case WM_QUERYNEWPALETTE:

{

hdc = GetDC(hwnd);



f = DrawDibRealize(hdd, hdc, FALSE) ;



ReleaseDC(hwnd, hdc);



if (f)

InvalidateRect(hwnd, NULL, TRUE);

break;

hdc = GetDC(hwnd);

}


激活调色板的过程很简单,以下程序用DrawDibRealize、DrawDibChangPalette和DrawDibDraw函数实现了这一过程。

//allow palette changes, specify the

//DDF_ANIMATE flag in the call to DrawDibBegin



DrawDibBegin(hdd, ……, DDF_ANIMATE);



DrawDibRealize(hdd, hdc, fBackground);



DrawDibDraw(hdd, hdc, ……, DDF_SAME_DRAW|DDF_SAME_HDC);



//set the color table values from the palette entries

//by using DrawDibChangePalette.

// lppe is an address of thePALETTEENTRY array containing the new colors,

DrawDibChangePalette(hDD, iStart, iLen, lppe);



//do you own job. . .

ReleaseDC(hwnd, hdc);


总之,用调色板可以方便对我们的位图资源实施颜色变幻,可以追求自己喜欢的色调。

6.14 视频捕捉(1)


熟悉视频电话和NetMeeting的人对电脑的视频捕捉能力都有印象。实际使用的视频捕捉设备包括各种摄像头或数码照相机,它们都可以把外部的光信号变成数字信号并通过接口传给计算机,进行储存或回放。视频捕捉的应用很广泛,可用于日常生活,如制作家庭录相;也可用于远程监控(被采集的图像可通过网络传送);甚至可在装有摄像头的电脑自控系统上实现视觉功能。

VFW为开发人员提供了较丰富的接口函数调用,它适用于开发USB(Universal Serial Bus)接口的摄像设备。本节主要介绍视频捕捉AVICap的功能及调用方法并编制一个简单的程序来使用小型的USB类摄像头。

6.14.1 开发AVICap应用程序的软硬件配置

首先对于主机来说,要有与采集设备相连接的接口或插槽。有些设备可直接接到机箱上的接口上(如USB摄像头),而有专用设备可能要占据一个PCI插槽。

在软件方面,操作系统必须安装该设备的驱动程序。设备厂家通常都会提供自己设备程序。但注意,安装驱动程序会占用系统资源(如内存、I/O中断等)。

在安装完驱动程序后,要确认设备是否可正常工作。如果设备供应商没有提供一个测试程序(一般会有),在Windows 98或NT DDK中src\videocap\tools里有个vidcap32程序,它可用来检验用户的设备安装是否正常。

6.14.2 AVICap主要功能

AVICap的主要功能是通过一个采集窗口(Capture Windows)来提供的。在概念上讲,采集窗口类似于一些标准控件,例如按钮、列表框等。各功能函数的调用都需要采集窗口的句柄,AVICap的消息也是传递给采集窗口的。通过系统窗口可使用以下基本功能:

(1)设置系统参数,如采集速率(frame/s)。

(2)实时地获取视频数据,可用Preview模式对输入视频进行实时显示,可用Capture功能把采到的数据写入文件中。

(3)动态地控制数据流功能:可以动态地连接或断开设备;可以在采集过程中切换采集状态;可通过视/音频格式对话框与压缩驱动程序对话框设置各项参数。

(4)暴露了采集到的数据体,为用户对数据进行更深处理(如使用调色板)提供了机会。

6.14.3 Captune Window的操作方法

通过上一节,我们已对Captune Windows有了初步认识。下面介绍关于Captune Windows的操作方法。这里主要讲三个方面:

(1)建立采集窗口;

(2)连接一个采集设备;

(3)获取窗口状态。

首先,建立采集窗口是进行一切工作的前提。使用CapCreateCaptuneWindows函数创建一个采集窗口。采集窗口使用WS_CHILD| WS_VISIBLE风格,下面一个例子演示了采集窗口的参数配置。

hWndC = capCreateCaptureWindow

(

(LPSTR) "My Capture Window", // window name if pop-up

WS_CHILD | WS_VISIBLE, // window style

0, 0, 160, 120, // window position and dimensions

(HWND) hwndParent,

(int) nID /* child ID */)


这个函数通常在程序初始化时调用,并且同一个程序可创建多个采集窗口,以用于连接多个硬件设备。

接下来要做的是与一个采集设备取得联系。CapDriuerConnect和WM_CAP_DRIVER_CONNECT消息都可实现这一功能:

fOK = SendMessage (hWndC, WM_CAP_DRIVER_CONNECT, 0, 0L);

//

// Or, use the macro to connect to the MSVIDEO driver:

// fOK = capDriverConnect(hWndC, 0);

//

// Place code to set up and capture video here.

// capDriverDisconnect (hWndC);


使用CapDriverDisConnect可断开连接。

如果想得到有关设备的详细信息,可以使用CapGetDriverDescription得到设备描述符,使用CapDriverGetCaps宏得到设备性能:

char szDeviceName[80];

char szDeviceVersion[80];

CAPDRIVERCAPS CapDrvCaps;



for (wIndex = 0; wIndex < 10; wIndex++)

{

if (capGetDriverDescription (wIndex, szDeviceName,

sizeof (szDeviceName), szDeviceVersion,

sizeof (szDeviceVersion))

{

// Append name to list of installed capture drivers

// and then let the user select a driver to use.

}

}

SendMessage (hWndC, WM_CAP_DRIVER_GET_CAPS,

sizeof (CAPDRIVERCAPS), (LONG) (LPVOID) &CapDrvCaps);

// Or, use the macro to retrieve the driver capabilities.

// capDriverGetCaps(hWndC, &CapDrvCaps, sizeof (CAPDRIVERCAPS));


窗口状态被定义在一个叫做CAPSTATUS的数据结构中,它包含了大量的有用信息,必须动态地更新,以满足应用程序的各种操作要求,可以通过CapGetStatus或发WM_CAP_GET_STATUS消息得到CAPSTATUS的指针:

CAPSTATUS CapStatus;



capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));



SendMessage (hWndC, WM_CAP_GET_STATUS ,

&CapStatus, sizeof (CAPSTATUS));

//

//read or write information by the handle

//of CapStatus……………


当一个采集窗口建成后,下面要做的是设置回调函数(CallBackFunctions)。

6.14.4 回调函数简介

回调函数应在程序初始化时制定。当应用程序向设备,更确切地说,向设备驱动程序发出读写命令后,回调函数被定时或不定时地用来完成相应的通信功能,如视频流回调函数定时地从USB摄像头取回一帧数据以便回放。应用程序可以在采集窗口中注册以下四种回调函数,它们的名字可由应用程序自己制定。

6.14 视频捕捉(2)


(1)状态回调函数:取回采集状态信息,包含在文本字符串里。

statusCallbackProc: status callback function

// hWnd: capture window handle

// nID: status code for the current status

// lpStatusText: status text string for the current status //



LRESULT PASCAL StatusCallbackProc(HWND hWnd, int nID, LPSTR lpStatusText)

{

if (!ghWndMain) return FALSE;

if (nID == 0)

{

// Clear old status messages.

SetWindowText(ghWndMain, (LPSTR) gachAppName);

return (LRESULT) TRUE;

}

}


(2)错误回调函数:可以把错误信息包含在文本字符串里。

// ErrorCallbackProc: error callback function

// hWnd: capture window handle

// nErrID: error code for the encountered error

// lpErrorText: error text string for the encountered error



LRESULT PASCAL ErrorCallbackProc(HWND hWnd, int nErrID, LPSTR lpErrorText)

{

if (!ghWndMain) return FALSE;

if (nErrID == 0) // Starting a new major function.

return TRUE; // Clear out old errors.



// Show the error identifier and text.

wsprintf(gachBuffer, "Error# %d", nErrID);

MessageBox(hWnd, lpErrorText, gachBuffer,

MB_OK | MB_ICONEXCLAMATION);



return (LRESULT) TRUE;

}


(3)单帧采集回调函数,参数lpVHdr指向视频数据块的头信息。

// FrameCallbackProc: frame callback function

// hWnd: capture window handle

// lpVHdr: pointer to struct containing captured

// frame information //

LRESULT PASCAL FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr)

{

if (!ghWndMain) return FALSE;



wsprintf(gachBuffer, "Preview frame# %ld ", gdwFrameNum++);

SetWindowText(ghWndMain, (LPSTR)gachBuffer);



return (LRESULT) TRUE ;



}


VIDEOHDR结构体的定义如下:

typedef struct videohdr_tag {

LPBYTE lpData; /* pointer to locked data buffer */

DWORD dwBufferLength; /* Length of data buffer */

DWORD dwBytesUsed; /* Bytes actually used */

DWORD dwTimeCaptured; /* Milliseconds from start of stream */

DWORD dwUser; /* for client's use */

DWORD dwFlags; /* assorted flags (see defines) */

DWORD dwReserved[4]; /* reserved for driver */

} VIDEOHDR, NEAR *PVIDEOHDR, FAR * LPVIDEOHDR;


(4)视频流回调函数:它的参数与单帧采集回调函数的相同,但它用于连续读取视频数据。在PreView模式下,此函数以设定的采集速率返回视频数据,形成视频流,它的原型为:

LRESULT CALLBACK capVideoStreamCallback( HWND hWnd, LPVIDEOHDR lpVHdr );


回调函数可由应用程序根据需要动态地设定或删除,方法是使用宏:

BOOL CapSetCallbackOnXXX (hwnd, tpProc)


其中参数hwnd表示采集窗口的句柄,fpProc表示回调函数的指针,如果fpProc为NULL,则会删除前一个设定的回调函数。

下面一段代码演示了在程序初始时设定回调函数的过程:

case WM_CREATE: {

char achDeviceName[80] ;

char achDeviceVersion[100] ;

char achBuffer[100] ;

WORD wDriverCount = 0 ;

WORD wIndex ;

WORD wError ;

HMENU hMenu ;



// Create a capture window using the capCreateCaptureWindow macro.

ghWndCap = capCreateCaptureWindow((LPSTR)"Capture Window",

WS_CHILD | WS_VISIBLE, 0, 0, 160, 120, (HWND) hWnd, (int) 0);



// Register the error callback function using the

// capSetCallbackOnError macro.

capSetCallbackOnError(ghWndCap, fpErrorCallback);

// Register the status callback function using the

// capSetCallbackOnStatus macro.

capSetCallbackOnStatus(ghWndCap, fpStatusCallback);



// Register the video-stream callback function using the

// capSetCallbackOnVideoStream macro.

capSetCallbackOnVideoStream(ghWndCap, fpVideoCallback);

// Register the frame callback function using the

// capSetCallbackOnFrame macro.

capSetCallbackOnFrame(ghWndCap, fpFrameCallback);



// Connect to a capture driver

break;

}

case WM_CLOSE:

{

// Use the capSetCallbackOnFrame macro to

// disable the frame callback. Similar calls exist for the other

// callback functions.

capSetCallbackOnFrame(hWndC, NULL);



break;

}

6.14 视频捕捉(3)


6.14.5 视频预览与采集

在前面工作的基础上,可以让采集系统真正地工作起来,让采到的视频数据显示出来或保存在文件中。

所谓预览状态,就是在显示器上显示连续或单帧图像。在预览状态时,可以调整显示模式,调节颜色,或调试外设(如可调节摄像头焦距)。在满意后,就可以进入采集状态,设定保存文件,把数据记录在文件中。

预览操作比较简单,下面一段代码首先设定预览速率,单位为帧/毫秒:

capPreviewRate(hWndC, 66); // possible 15 frames per second

capPreview(hWndC, TRUE); // starts preview

capPreview(hWnd, FALSE); // disables preview


其实CapPreview只是初始化了预览状态,然后它开始调用VidoCallbuekProc回调函数。得到视频数据后,应用程序可用DrawDib把图像画到视图框内。如果不做处理,则下一帧数据将覆盖前一帧数据。

Capture Data操作要麻烦些。首先要设置采集参数。AVICap用CAPTUREPARMS结构记录采集参数,如果不愿意设置,则可以接受缺省值。

CAPTUREPARMS capParam;

char szNewName[] = "NEWFILE.AVI";



if( capCaptureGetSetup( hwnd, sizof(CAPTUREPARMS), &capParam ))

{

//

// chang the setting of CAPTUREPARMS……

//

if(capCaptureSetSetup( hwnd, &CapParm, sizeof(CAPTUREPARMS) ))

{

capFileSetCaptureFile( hwnd, szNewName );

capCaptureSequence( hwnd );

}

}


6.14.6 采集设置对话框

AVICap在采集过程中可随时对视频数据源、视频格式和压缩格式进行设置。AVICap为用户提供了高层的接口。可以直接显示设置对话框,操作与编程都很方便。下面介绍几个常用的函数。

BOOL capDlgVideoSource(hwnd);


使用该函数可弹出视频源对话框,如图6.2和6.3所示。


图6.2 视频源对话框-PCAM Color


图6.3 视频源对话框-捕获来源

BOOL capDlgVideoFormat(hwnd);


该函数可显示视频格式对话框,它可设置分辨率和像素深度,如图6.14所示。


图6.14 视频格式对话框

BOOL capDlgVideoCompression(hwnd);


该函数用于显示压缩驱动程序对话框。可以选择一个已安装的压缩驱动程序,如图6.5所示。


图6.5 压缩驱动程序对话框

6.5 视频压缩管理器/6.6 视频捕捉程序示例(1)


通过AVICap所采集的原始数据容量很大,如果直接保存或实时地从网上传输,需要占用很大的磁盘空间和网络带宽。数据压缩在很多时候是必不可少的工作,许多软件商为用户提供了Windows下的多种压缩驱动程序。VCM视频压缩管理器的设计目的是为用户提供调用压缩驱动程序的统一接口,以方便用户编程。本节将简要地介绍VCM的功能,以便为实际编程打下基础。

6.5.1 VCM的功能

如上所述,VCM处于应用程序与驱动程序之间,它本身具有独立性,给用户提供编程接口,与网络中的代理服务器在概念上相似。图6.6表达了VCM在系统中的关系。



图6.6 VCM在系统中的关系

通过VCM,应用程序的编程要简单,而且可以适用于各种驱动程序,从而增强了可移植性。当应用程序VCM时,VCM将其转换成对应的消息,然后通过ICSemMessage将消息发到合适的压缩或解压驱动程序,同时VCM收到从驱动程序返回的信息,将其转交给应用程序处理。

通过调用VCM,可以实现如下功能:

(1)定位、打开或安装压缩/解压驱动程序。

(2)配置压缩/解压驱动程序或获得压缩/解压驱动程序的配置信息。

(3)压缩、解压或显示视频数据。

使用VCM的函数和宏可以实现以上功能,但读者可能已注意到,在关于DrawDib的

例程中,我们已使用了压缩功能。可以说,通过DrawDib实现数据压缩要更简单些。

6.5.2 VCM驱动注册

Windows利用注册表保存和检索VCM驱动程序信息。每种驱动程序都用两个四字符的代码表示,形如XXXX.YYYY,其中前四个字符由系统定义,用于表示驱动程序的类别,见表6.2。

表6.2 4字符的驱动程序类别标识

4 字 符 串
描 述

“VIDC”
压缩/解压驱动程序

“VIDS”
视频流显示驱动程序

“TXTS”
文本流显示驱动程序

“AUDS”
音频流处理程序


第二个四字符串由每个驱动程序自己制定。一般地说,第二个四字符串应与驱动程序能处理的数据类型相符。

当用户打开一个VCM驱动程序时,用户应先确定要处理的数据类型。VCM将试图打开相应的驱动程序,如果失败,则在系统注册表中搜索,寻找与前4个字符相匹配的驱动程序,即找到一个能处理这种数据的驱动程序。



<< Home

This page is powered by Blogger. Isn't yours?