EternalWindows
AVI / 圧縮されたストリームとキャプチャ

前節では、AVIファイルのビデオストリームに無圧縮のフレームを書き込みましたが、 これにはファイルサイズが大きくなるという問題がありました。 これを防ぐためには、フレームを事前に圧縮してから書き込み、 さらに圧縮フォーマットをビデオストリームに設定すればよいのですが、 実はもう少し簡単な方法があります。 それは、予め圧縮されたストリームを作成しておき、 そのストリームに対して無圧縮のフレームを書き込むというものです。 このようにすれば、フレームは圧縮された状態でストリームに格納されるため、 各フレームに対して明示的に圧縮処理を行う必要はなくなります。 圧縮されたストリームは、AVIMakeCompressedStreamで作成することができます。

STDAPI AVIMakeCompressedStream(
  PAVISTREAM * ppsCompressed,
  PAVISTREAM psSource,
  AVICOMPRESSOPTIONS * lpOptions,
  CLSID * pclsidHandler
);

ppsCompressedは、圧縮されたストリームを受け取る変数のアドレスを指定します。 psSourceは、圧縮したいストリームを指定します。 このストリームの中身は空で構いません。 lpOptionsは、圧縮情報を格納したAVICOMPRESSOPTIONS構造体のアドレスを指定します。 pclsidHandlerは、使用するハンドラのCLSIDを指定します。 AVICOMPRESSOPTIONS構造体は、次のように定義されています。

typedef struct { 
  DWORD  fccType;
  DWORD  fccHandler;
  DWORD  dwKeyFrameEvery; 
  DWORD  dwQuality;
  DWORD  dwBytesPerSecond;
  DWORD  dwFlags;
  LPVOID lpFormat;
  DWORD  cbFormat;
  LPVOID lpParms;
  DWORD  cbParms;
  DWORD  dwInterleaveEvery;
} AVICOMPRESSOPTIONS; 

fccTypeは、ストリームの種類を表すFOURCCを指定します。 fccHandlerは、圧縮に使用するコーデックのFOURCCを指定します。 dwKeyFrameEveryは、キーフレームの頻度を指定します。 dwQualityは、圧縮の品質を指定します。 dwBytesPerSecondは、データレートを指定します。 dwFlagsは、使用するメンバを表す定数をします。 AVICOMPRESSF_DATARATEはdwBytesPerSecond、 AVICOMPRESSF_INTERLEAVEはdwInterleaveEvery、 AVICOMPRESSF_KEYFRAMESはdwKeyFrameEveryを表します。 lpFormatは、フォーマットを指定します。 cbFormatは、lpFormatのサイズを指定します。 lpParmsは、ビデオコーデックに渡したいデータを指定します。 cbParmsは、lpParmsのサイズを指定します。 dwInterleaveEveryは、インターリーブの頻度を指定します。

AVICOMPRESSOPTIONS構造体は、圧縮に使用するコーデックのFOURCCを要求するため、 アプリケーションは何らかの方法でこれを取得する必要があります。 次に示すICCompressorChooseを呼び出せば、 ユーザーがダイアログ経由でコーデックを選択することができるため、 その結果を利用することになります。

BOOL ICCompressorChoose(
  HWND hwnd,  
  UINT uiFlags,
  LPVOID pvIn,
  LPVOID lpData,
  PCOMPVARS pc,
  LPSTR lpszTitle
);

hwndは、ダイアログの親ウインドウとするハンドルを指定します。 uiFlagsは、0または定義されている定数を指定します。 pvInは、圧縮を解除する目的でコーデックを選択する場合に指定します。 ここに指定したフォーマットを解凍できるコーデックのみがダイアログに列挙されます。 不要な場合は、NULLを指定して問題ありません。 lpDataは、プレビューウインドウの表示するビデオストリームを指定します。 不要な場合は、NULLを指定することができます。 pcは、選択結果を受け取るCOMPVARS構造体のアドレスを指定します。 lpszTitleは、ダイアログのタイトルを表す文字列を指定します。 NULLの場合は、デフォルトの文字列が使用されます。 uiFlagsに指定できる定数を次に示します。

定数 意味
ICMF_CHOOSE_ALLCOMPRESSORS 全てのコーデックをダイアログのコンボボックスに列挙する。 ICCompressorChooseのpvInにNULLを指定した場合は、この定数を指定する必要はない。
ICMF_CHOOSE_DATARATE データレートを入力するためのエディットボックスとコンボボックスを表示する。
ICMF_CHOOSE_KEYFRAME キーフレームの頻度を入力するためのエディットボックスとコンボボックスを表示する。
ICMF_CHOOSE_PREVIEW イントロ再生ボタンを表示する。lpDataに無圧縮のビデオストリームを指定する必要がある。

ICCompressorChooseを呼び出すためには、COMPVARS構造体のcbSizeを初期化し、 残りのメンバは0で問題ありません。 ただし、ダイアログの既定値を指定したい場合は、dwFlagsにICMF_COMPVARS_VALIDを指定し、 既定値を設定したいメンバを明示的に初期化します。 たとえば、fccHandlerにcomptypeDIBを指定した場合、 既定で無圧縮コーデックが選択されるようになります。 COMPVARS構造体の定義を次に示します。

typedef struct { 
  LONG         cbSize; 
  DWORD        dwFlags; 
  HIC          hic; 
  DWORD        fccType; 
  DWORD        fccHandler; 
  LPBITMAPINFO lpbiIn; 
  LPBITMAPINFO lpbiOut; 
  LPVOID       lpBitsOut; 
  LPVOID       lpBitsPrev; 
  LONG         lFrame; 
  LONG         lKey; 
  LONG         lDataRate; 
  LONG         lQ; 
  LONG         lKeyCount; 
  LPVOID       lpState; 
  LONG         cbState; 
} COMPVARS; 

cbSizeは、構造体のサイズを指定します。 dwFlagsは、0またはICMF_COMPVARS_VALIDを指定します。 hicは、コーデックのハンドルを指定します。 fccTypeは、ICTYPE_VIDEOまたは0を指定します。 fccHandlerは、コーデックを表すFOURCCを指定します。 関数から制御が返った場合、選択されたコーデックのFOURCCを指定します。 lpbiInは、現在使用されていません。 lpbiOutは、出力フォーマットを指定します。 lpBitsOut、lpBitsPrev、lFrameは現在使用されていません。 lKeyは、キーフレームの頻度を指定します。 lDataRateは、データレートを指定します。 lQは、品質を示す1から10000までの値を指定します。 ICQUALITY_DEFAULTを指定した場合は、デフォルトの品質となります。 lKeyCount、lpState、cbStateは現在使用されていません。

ICCompressorChooseを呼び出した場合は、COMPVARS.hicが初期化されることになります。 このような初期化されたメンバを開放するには、ICCompressorFreeを呼び出します。

void ICCompressorFree(
  PCOMPVARS pc
);

pcは、COMPVARS構造体のアドレスを指定します。

今回のプログラムは、デスクトップをキャプチャしてAVIファイルに保存します。

#include <windows.h>
#include <vfw.h>

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

#define ID_START 100
#define ID_END 200
#define ID_COMPRESS 300

struct THREADINFO {
	HWND             hwndTarget;
	BOOL             bExit;
	DWORD            dwRate;
	PAVISTREAM       pavi;
	BITMAPINFOHEADER biIn;
};
typedef struct THREADINFO THREADINFO;
typedef THREADINFO *LPTHREADINFO;

BOOL CreateCompressedStream(LPTSTR lpszFileName, DWORD dwRate, PCOMPVARS pCompvars, LPBITMAPINFOHEADER lpbiIn, PAVIFILE *ppfile, PAVISTREAM *ppaviCompress);
DWORD WINAPI ThreadProc(LPVOID lpParamater);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND       hwndButtonStart = NULL;
	static HWND       hwndButtonStop = NULL;
	static HWND       hwndButtonCompress = NULL;
	static PAVISTREAM pavi = NULL;
	static PAVIFILE   pfile = NULL;
	static COMPVARS   compvars = {0};
	static BOOL       bSetCompressParam = FALSE;
	static HANDLE     hThread = NULL;
	static THREADINFO threadInfo = {0};

	switch (uMsg) {

	case WM_CREATE:
		AVIFileInit();
		hwndButtonStart = CreateWindowEx(0, TEXT("BUTTON"), TEXT("キャプチャ開始"), WS_CHILD | WS_VISIBLE, 10, 20, 130, 30, hwnd, (HMENU)ID_START, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndButtonStop = CreateWindowEx(0, TEXT("BUTTON"), TEXT("キャプチャ終了"), WS_CHILD | WS_VISIBLE, 10, 70, 130, 30, hwnd, (HMENU)ID_END, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndButtonCompress = CreateWindowEx(0, TEXT("BUTTON"), TEXT("圧縮設定"), WS_CHILD | WS_VISIBLE, 10, 120, 130, 30, hwnd, (HMENU)ID_COMPRESS, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		EnableWindow(hwndButtonStop, FALSE);
		RegisterHotKey(hwnd, ID_START, MOD_ALT, 0x53);
		RegisterHotKey(hwnd, ID_END, MOD_ALT, 0x45);
		return 0;

	case WM_COMMAND:
		if (LOWORD(wParam) == ID_START) {
			HWND             hwndTarget;
			RECT             rc;
			DWORD            dwThreadId;
			BITMAPINFOHEADER biIn;
			DWORD            dwRate = 30;

			hwndTarget = GetDesktopWindow();
			if (hwndTarget == NULL) {
				MessageBox(NULL, TEXT("キャプチャするウインドウが見つかりません。"), NULL, MB_ICONWARNING);
				return 0;
			}

			if (!bSetCompressParam) {
				MessageBox(NULL, TEXT("圧縮設定が行われていません。"), NULL, MB_ICONWARNING);
				return 0;
			}

			GetClientRect(hwndTarget, &rc);

			ZeroMemory(&biIn, sizeof(BITMAPINFOHEADER));
			biIn.biSize        = sizeof(BITMAPINFOHEADER);
			biIn.biWidth       = rc.right;
			biIn.biHeight      = rc.bottom;
			biIn.biPlanes      = 1;
			biIn.biBitCount    = 24;
			biIn.biCompression = BI_RGB;
			biIn.biSizeImage   = biIn.biHeight * ((3 * biIn.biWidth + 3) / 4) * 4;

			if (!CreateCompressedStream(TEXT("cap.avi"), dwRate, &compvars, &biIn, &pfile, &pavi))
				return 0;

			threadInfo.hwndTarget = hwndTarget;
			threadInfo.bExit      = FALSE;
			threadInfo.dwRate     = dwRate;
			threadInfo.pavi       = pavi;
			threadInfo.biIn       = biIn;

			hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, &threadInfo, 0, &dwThreadId);
			SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);

			EnableWindow(hwndButtonStart, FALSE);
			EnableWindow(hwndButtonStop, TRUE);
		}
		else if (LOWORD(wParam) == ID_END) {
			if (hThread != NULL) {
				threadInfo.bExit = TRUE;
				WaitForSingleObject(hThread, 1000);
				CloseHandle(hThread);
				threadInfo.bExit = FALSE;
				hThread = NULL;
			}

			if (compvars.hic != NULL) {
				ICCompressorFree(&compvars);
				compvars.hic = NULL;
			}
			
			if (pavi != NULL) {
				AVIStreamRelease(pavi);
				pavi = NULL;
			}

			if (pfile != NULL) {
				AVIFileRelease(pfile);
				pfile = NULL;
			}

			EnableWindow(hwndButtonStart, TRUE);
			EnableWindow(hwndButtonStop, FALSE);
		}
		else if (LOWORD(wParam) == ID_COMPRESS) {
			BOOL bResult;

			ZeroMemory(&compvars, sizeof(COMPVARS));
			compvars.cbSize = sizeof(COMPVARS);
			
			bResult = ICCompressorChoose(hwnd, 0, NULL, NULL, &compvars, NULL);
			if (!bSetCompressParam)
				bSetCompressParam = bResult;
		}
		else
			;
		return 0;
		
	case WM_HOTKEY:
		SendMessage(hwnd, WM_COMMAND, wParam, 0);
		return 0;

	case WM_CLOSE:
		SendMessage(hwnd, WM_COMMAND, ID_END, 0);
		break;

	case WM_DESTROY:
		UnregisterHotKey(hwnd, ID_START);
		UnregisterHotKey(hwnd, ID_END);
		AVIFileExit();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL CreateCompressedStream(LPTSTR lpszFileName, DWORD dwRate, PCOMPVARS pCompvars, LPBITMAPINFOHEADER lpbiIn, PAVIFILE *ppfile, PAVISTREAM *ppaviCompress)
{
	AVISTREAMINFO      si;
	AVICOMPRESSOPTIONS options;
	PAVISTREAM         pavi;
	PAVISTREAM         paviCompress;
	PAVIFILE           pfile;

	if (AVIFileOpen(&pfile, lpszFileName, OF_WRITE | OF_CREATE, NULL) != 0){
		MessageBox(NULL, L"ファイルの作成またはオープンに失敗しました。", NULL, MB_ICONWARNING);
		return FALSE;
	}

	ZeroMemory(&si, sizeof(AVISTREAMINFO));
	si.fccType    = streamtypeVIDEO;
	si.fccHandler = pCompvars->fccHandler;
	si.dwScale    = 1;
	si.dwRate     = dwRate;
	SetRect(&si.rcFrame, 0, 0, lpbiIn->biWidth, lpbiIn->biHeight);

	if (AVIFileCreateStream(pfile, &pavi, &si) != 0) {
		MessageBox(NULL, L"ストリームの作成に失敗しました。", NULL, MB_ICONWARNING);
		AVIFileRelease(pfile);
		return FALSE;
	}

	options.fccType           = streamtypeVIDEO;
	options.fccHandler        = pCompvars->fccHandler;
	options.dwKeyFrameEvery   = pCompvars->lKey;
	options.dwQuality         = pCompvars->lQ;
	options.dwBytesPerSecond  = pCompvars->lDataRate;
	options.dwFlags           = AVICOMPRESSF_DATARATE | AVICOMPRESSF_KEYFRAMES;
	options.lpFormat          = NULL;
	options.cbFormat          = 0;
	options.lpParms           = NULL;
	options.cbParms           = 0;
	options.dwInterleaveEvery = 0;

	if (AVIMakeCompressedStream(&paviCompress, pavi, &options, NULL) != AVIERR_OK) {
		AVIStreamClose(pavi);
		AVIFileRelease(pfile); 
		return FALSE;
	}

	AVIStreamSetFormat(paviCompress, 0, lpbiIn, sizeof(BITMAPINFOHEADER));

	*ppfile = pfile;
	*ppaviCompress = paviCompress;
	AVIStreamClose(pavi);
	
	return TRUE;
}

DWORD WINAPI ThreadProc(LPVOID lpParamater)
{
	int          i;
	HDC          hdc;
	LPTHREADINFO lpThreadInfo = (LPTHREADINFO)lpParamater;
	HDC          hdcMem;
	HBITMAP      hbmpMem, hbmpMemPrev;
	LPVOID       lpBits;
	double       dInterval;
	double       dCurTime, dNextTime;

	hdcMem = CreateCompatibleDC(NULL);
	hbmpMem = CreateDIBSection(NULL, (LPBITMAPINFO)&lpThreadInfo->biIn, DIB_RGB_COLORS, &lpBits, NULL, 0);
	hbmpMemPrev = (HBITMAP)SelectObject(hdcMem, hbmpMem);
	
	dInterval = (double)1000 / lpThreadInfo->dwRate;
	
	for (i = 0; !lpThreadInfo->bExit; i++) {
		dNextTime = (double)timeGetTime();
		dNextTime += dInterval;
	
		hdc = GetDC(lpThreadInfo->hwndTarget);
		BitBlt(hdcMem, 0, 0, lpThreadInfo->biIn.biWidth, lpThreadInfo->biIn.biHeight, hdc, 0, 0, SRCCOPY);
		ReleaseDC(lpThreadInfo->hwndTarget, hdc);
		AVIStreamWrite(lpThreadInfo->pavi, i, 1, lpBits, lpThreadInfo->biIn.biSizeImage, AVIIF_KEYFRAME, NULL, NULL);

		dCurTime = (double)timeGetTime();
		if (dNextTime < dCurTime)
			dNextTime = dCurTime + dInterval;
			
		Sleep((DWORD)(dNextTime - dCurTime));
	}

	SelectObject(hdcMem, hbmpMemPrev);
	DeleteObject(hbmpMem);
	DeleteDC(hdcMem);

	return 0;
}

「キャプチャ開始」というボタンを押すと、デスクトップのキャプチャが開始されます。 ただし、その前に「圧縮設定」というボタンで使用するコーデックを選択しておく必要があります。 キャプチャを開始すると、「キャプチャ終了」というボタンが有効になるため、 キャプチャを終了する場合はこのボタンを押すことになります。 今回のプログラムではホットキーを登録しているため、 キャプチャの開始はAlt + Sで、キャプチャの終了はAlt + Eで行うこともできます。 「圧縮設定」ボタンが押されると次の処理が実行されます。

else if (LOWORD(wParam) == ID_COMPRESS) {
	BOOL bResult;

	ZeroMemory(&compvars, sizeof(COMPVARS));
	compvars.cbSize = sizeof(COMPVARS);
			
	bResult = ICCompressorChoose(hwnd, 0, NULL, NULL, &compvars, NULL);
	if (!bSetCompressParam)
		bSetCompressParam = bResult;
}

cbSizeメンバを初期化し、他のメンバをゼロクリアしていればCOMPVARS構造体の初期化は完了したことになるので、 ICCompressorChooseを呼び出すことができます。 関数が成功すると、選択したコーデックのFOURCCやデータレートがCOMPVARS構造体に格納されることになります。 bSetCompressParamという変数がTRUEになると、「キャプチャ開始」ボタンでキャプチャを開始することができます。

キャプチャを開始するためには、まずキャプチャした画像を書き込むためのストリームが必要になります。 これは、CreateCompressedStreamという自作関数で作成することになっています。 この関数は、ICCompressorChooseで初期化したCOMPVARS構造体やフォーマットを要求し、 ファイルとストリームのハンドルを返します。 フォーマットについては無圧縮の24ビットで明示的に初期化しておくことになります。 CreateCompressedStreamの内部を順に見ていきます。

if (AVIFileOpen(&pfile, lpszFileName, OF_WRITE | OF_CREATE, NULL) != 0){
	MessageBox(NULL, L"ファイルの作成またはオープンに失敗しました。", NULL, MB_ICONWARNING);
	return FALSE;
}

ZeroMemory(&si, sizeof(AVISTREAMINFO));
si.fccType    = streamtypeVIDEO;
si.fccHandler = pCompvars->fccHandler;
si.dwScale    = 1;
si.dwRate     = dwRate;
SetRect(&si.rcFrame, 0, 0, lpbiIn->biWidth, lpbiIn->biHeight);

if (AVIFileCreateStream(pfile, &pavi, &si) != 0) {
	MessageBox(NULL, L"ストリームの作成に失敗しました。", NULL, MB_ICONWARNING);
	AVIFileRelease(pfile);
	return FALSE;
}

まず、AVIFileOpenでファイルを作成または書き込みオープンします。 次に、ファイルに関連するストリームを作成するために、AVIFileCreateStreamを呼び出します。 これにより、このストリームに対する書き込みはファイルに反映されることになりますが、 このままでは無圧縮DIBの書き込みが圧縮されずに保存されることになってしまいます。 よって、AVIMakeCompressedStreamで圧縮されたストリームを作成する必要があります。

options.fccType           = streamtypeVIDEO;
options.fccHandler        = pCompvars->fccHandler;
options.dwKeyFrameEvery   = pCompvars->lKey;
options.dwQuality         = pCompvars->lQ;
options.dwBytesPerSecond  = pCompvars->lDataRate;
options.dwFlags           = AVICOMPRESSF_DATARATE | AVICOMPRESSF_KEYFRAMES;
options.lpFormat          = NULL;
options.cbFormat          = 0;
options.lpParms           = NULL;
options.cbParms           = 0;
options.dwInterleaveEvery = 0;

if (AVIMakeCompressedStream(&paviCompress, pavi, &options, NULL) != AVIERR_OK) {
	AVIStreamClose(pavi);
	AVIFileRelease(pfile); 
	return FALSE;
}

AVIMakeCompressedStreamの第2引数にAVIFileCreateStreamで作成したストリームを指定した場合、 作成される圧縮されたストリームもファイルに関連付けられることになります。 つまり、圧縮されたストリームに対する書き込みがファイルに反映されることになります。 AVICOMPRESSOPTIONS構造体では、ICCompressorChooseで初期化したCOMPVARS構造体のメンバを指定することができます。 dwKeyFrameEveryとdwBytesPerSecondを使用する場合は、 dwFlagsにAVICOMPRESSF_DATARATEとAVICOMPRESSF_KEYFRAMESを指定します。

圧縮されたストリームの作成が完了すれば、キャプチャを開始することができます。 キャプチャ処理はスレッドによって行われており、 渡されるデータにはキャプチャ対象のウインドウや圧縮ストリームなどが含まれます。 キャプチャ対象のウインドウについては、GetDesktopWindowで取得したウインドウになっていますが、 FindWindowで任意のウインドウを対象にするのもよいでしょう。 キャプチャ処理は、次のようになっています。

hdc = GetDC(lpThreadInfo->hwndTarget);
BitBlt(hdcMem, 0, 0, lpThreadInfo->biIn.biWidth, lpThreadInfo->biIn.biHeight, hdc, 0, 0, SRCCOPY);
ReleaseDC(lpThreadInfo->hwndTarget, hdc);
AVIStreamWrite(lpThreadInfo->pavi, i, 1, lpBits, lpThreadInfo->biIn.biSizeImage, AVIIF_KEYFRAME, NULL, NULL);

まず目的のウインドウのクライアント領域をメモリデバイスコンテキストに転送します。 メモリデバイスコンテキストに割り当てられたビットマップがDIBセクションであれば、 ビットイメージに転送内容が反映されることになります。 よって、後はこのビットイメージをAVIStreamWriteに指定すれば、 これが圧縮されてストリームに書き込まれることになります。

フレームシーケンスについて

フレームシーケンスとは、一連のフレームを1つずつ圧縮する流れを意味しています。 この方法を利用すれば、圧縮されたフレームをストリームに保存することができるため、 ファイルサイズを軽くすることができます。 AVIMakeCompressedStreamを利用した方法と比べた場合、 この方法は圧縮フォーマットの設定やフレームの圧縮処理が明示的に必要となりますが、 フレームシーケンスの使い方を理解するためにも、ここで例を示したいと思います。

BITMAPINFOHEADER biIn;
COMPVARS         compvars;
LONG             lSize;
DWORD            dwFlags;
LPVOID           lpBitsCompress;

ZeroMemory(&biIn, sizeof(BITMAPINFOHEADER));
biIn.biSize        = sizeof(BITMAPINFOHEADER);
biIn.biWidth       = nWidth;
biIn.biHeight      = nHeight;
biIn.biPlanes      = 1;
biIn.biBitCount    = 24;
biIn.biCompression = BI_RGB;
biIn.biSizeImage   = biIn.biHeight * ((3 * biIn.biWidth + 3) / 4) * 4;

ZeroMemory(&compvars, sizeof(COMPVARS));
compvars.cbSize = sizeof(COMPVARS);

if (!ICCompressorChoose(NULL, 0, &biIn, NULL, &compvars, NULL))
	return 0;

AVIStreamSetFormat(pavi, 0, compvars.lpbiOut, sizeof(BITMAPINFOHEADER));

ICSeqCompressFrameStart(&compvars,(LPBITMAPINFO)&biIn);

for (...) {
	lSize = biIn.biSizeImage;
	lpBitsCompress = ICSeqCompressFrame(&compvars, 0, lpBits, &dwFlags, &lSize);
	AVIStreamWrite(pavi, i, 1, lpBitsCompress, lSize, dwFlags, NULL, NULL);
}

ICSeqCompressFrameEnd(&compvars);

ICCompressorFree(&compvars);

前提として、nWidthとnHeightにはフレームの幅と高さが格納されているとします。 また、paviはビデオストリームであり、lpBitsはループの度に次のフレームのビットイメージを表すものとします。 まず、圧縮したいデータのフォーマットをbiInに指定します。 圧縮したいデータは無圧縮の状態ですから、 無圧縮を表すフォーマットを指定しすることになります。 続いて、これをICCompressorChooseに指定し、 圧縮に使用するコーデックを選択すると共に、 そのコーデックにおける圧縮フォーマットを取得します。 このフォーマットはcompvars.lpbiOutから参照可能であり、 ICCompressorChooseの呼び出し時にフォーマットを指定しておかなければ、 初期化されません。 圧縮フォーマットは、AVIStreamSetFormatでストリームに設定します。

フレームシーケンスの処理は、ICSeqCompressFrameStartの呼び出しから始まります。 第2引数に指定するのは、圧縮したいデータのフォーマットであり、 圧縮後のフォーマットではない点に注意してください。 ICSeqCompressFrameStartの呼び出しが終了すれば、 ICSeqCompressFrameで圧縮されたフレームを戻り値として取得することができます。 第4引数はキーフレームに圧縮されたどうかを表す値が返るため、 これはAVIStreamWriteの第6引数に指定することができます。 第5引数は圧縮したいデータのサイズを格納した変数のアドレスであり、 関数が成功した場合は圧縮されたデータのサイズが返ります。 よって、AVIStreamWriteの第5引数に指定することができます。 必要な圧縮処理が完了した場合は、ICSeqCompressFrameEndでフレームシーケンスを終了します。

ストリームに圧縮されたフレームを格納する目的で、 フレームシーケンスを利用するのはそこまで効率的とはいえません。 AVIMakeCompressedStreamで圧縮されたストリームを作成し、 無圧縮フレームをそのまま書き込む方が分かりやすいでしょう。 ただし、圧縮したフレームをネットワーク上へ順番に送信したい場合などは、 フレームシーケンスが大いに役に立ちます。



戻る