EternalWindows
AVI / AVI再生サンプル(VCM編)

今回は、圧縮されたフレームをVCM関数によって明示的に解凍し、 その解凍したフレームをGDI関数で描画します。 圧縮されたフレーム解凍するためには、 まずそのフレームを解凍することのできるコーデックのハンドルが必要となります。 圧縮されたフレームのフォーマットと解凍後のフレームのフォーマットがあれば、 ICLocateで適切なコーデックのハンドルを取得することができます。

HIC ICLocate(
  DWORD fccType,             
  DWORD fccHandler,          
  LPBITMAPINFOHEADER lpbiIn, 
  LPBITMAPINFOHEADER lpbiOut, 
  WORD wFlags                
);

fccTypeは、ICTYPE_VIDEOを指定します。 fccHandlerは、圧縮または解凍に使用するFOURCCを指定します。 解凍を行う場合は、0を指定しても問題ありません。 lpbiInは、圧縮または解凍されたフォーマットを指定します。 lpbiOutは、解凍後または圧縮後のフォーマットを受け取る変数のアドレスを指定します。 wFlagsは、コーデックに実行させるための動作を表す定数を指定します。 ICMODE_COMPRESSならば圧縮であり、ICMODE_DECOMPRESSならば解凍になります。 戻り値は、コーデックのハンドルを指定になります。

コーデックのハンドルと解凍後のフォーマットをがあれば、 ICDecompressで圧縮データを解凍することができます。

DWORD ICDecompress(
  HIC hic,                    
  DWORD dwFlags,              
  LPBITMAPINFOHEADER lpbiFormat,
  LPVOID lpData,              
  LPBITMAPINFOHEADER lpbi,    
  LPVOID lpBits               
);

hicは、コーデックのハンドルを指定します。 dwFlagsは、解凍時における操作を表す定数を指定します。 0を指定しても問題ありません。 lpbiFormatは、圧縮されたデータのフォーマットを指定します。 lpDataは、圧縮されたデータを指定します。 lpbiは、解凍後のフォーマットを指定します。 lpBitsは、解凍後のデータを受け取るバッファを指定します。

今回のプログラムは、VCMによってフレームの解凍処理を行い、 解凍されたフレームを描画します。 前節のコードと比べて変化しているのは、ThreadProcの内部のみです。

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

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

struct THREADINFO {
	HWND       hwnd;
	BOOL       bExit;
	PAVISTREAM pavi;
};
typedef struct THREADINFO THREADINFO;
typedef THREADINFO *LPTHREADINFO;

BOOL GetAudioData(PAVISTREAM pavi, LPWAVEFORMATEX lpwfDest, LPBYTE *lplpDestData, LPDWORD lpdwDestSize);
BOOL DecodeToWave(LPWAVEFORMATEX lpwfSrc, LPBYTE lpSrcData, DWORD dwSrcSize, LPWAVEFORMATEX lpwfDest, LPBYTE *lplpDestData, LPDWORD lpdwDestSize);
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, CW_USEDEFAULT, CW_USEDEFAULT, 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 LPBYTE     lpWaveData = NULL;
	static HWAVEOUT   hwo = NULL;
	static WAVEHDR    wh = {0};
	static HANDLE     hThread = NULL;
	static PAVISTREAM paviVideo = NULL;
	static PAVISTREAM paviAudio = NULL;
	static THREADINFO threadInfo = {0};

	switch (uMsg) {

	case WM_CREATE: {
		DWORD        dwThreadId;
		DWORD        dwDataSize;
		WAVEFORMATEX wf;
		PAVIFILE     pfile;

		AVIFileInit();
		
		if (AVIFileOpen(&pfile, TEXT("sample.avi"), OF_READ, NULL) != 0) {
			MessageBox(NULL, TEXT("AVIファイルが存在しません。"), NULL, MB_ICONWARNING);
			return -1;
		}
		
		AVIFileGetStream(pfile, &paviVideo, streamtypeVIDEO, 0);
		AVIFileGetStream(pfile, &paviAudio, streamtypeAUDIO, 0);

		AVIFileRelease(pfile);

		if (paviVideo != NULL) {
			ICINFO        icinfo;
			AVISTREAMINFO si;

			AVIStreamInfo(paviVideo, &si, sizeof(AVISTREAMINFO));
			
			if (si.fccHandler == comptypeDIB || ICInfo(ICTYPE_VIDEO, si.fccHandler, &icinfo)) {
				threadInfo.hwnd  = hwnd;
				threadInfo.pavi  = paviVideo;
				threadInfo.bExit = FALSE;

				hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, &threadInfo, 0, &dwThreadId);
				SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);
			}
			else {
				TCHAR szBuf[256];
				LPSTR lp = (LPSTR)&si.fccHandler;
				wsprintf(szBuf, TEXT("%c%c%c%c"), lp[0], lp[1], lp[2], lp[3]);
				MessageBox(NULL, szBuf, TEXT("ビデオコーデックが存在しません。"), MB_ICONWARNING);
			}
		}
		
		if (paviAudio != NULL) {
			if (GetAudioData(paviAudio, &wf, &lpWaveData, &dwDataSize)) {
				if (waveOutOpen(&hwo, WAVE_MAPPER, &wf, (DWORD_PTR)hwnd, 0, CALLBACK_WINDOW) == MMSYSERR_NOERROR) {
					wh.lpData         = (LPSTR)lpWaveData;
					wh.dwBufferLength = dwDataSize;
					wh.dwFlags        = 0;

					waveOutPrepareHeader(hwo, &wh, sizeof(WAVEHDR));
					waveOutWrite(hwo, &wh, sizeof(WAVEHDR));
				}
			}
		}

		return 0;
	}

	case MM_WOM_DONE:
		waveOutWrite((HWAVEOUT)wParam, (LPWAVEHDR)lParam, sizeof(WAVEHDR));
		return 0;

	case WM_DESTROY:
		if (hwo != NULL) {
			waveOutReset(hwo);
			waveOutUnprepareHeader(hwo, &wh, sizeof(WAVEHDR));
			waveOutClose(hwo);
		}

		if (lpWaveData != NULL)
			HeapFree(GetProcessHeap(), 0, lpWaveData);

		if (hThread != NULL) {
			threadInfo.bExit = TRUE;
			WaitForSingleObject(hThread, 1000);
			CloseHandle(hThread);
		}
		
		if (paviVideo != NULL)
			AVIStreamRelease(paviVideo);

		if (paviAudio != NULL)
			AVIStreamRelease(paviAudio);

		AVIFileExit();

		PostQuitMessage(0);

		return 0;

	default:
		break;

	}

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


DWORD WINAPI ThreadProc(LPVOID lpParamater)
{
	HDC              hdc;
	HWND             hwnd;
	LONG             i;
	LONG             lStart, lEnd;
	LONG             lSize;
	LPBYTE           lpBitsIn, lpBitsOut;
	HIC              hic;
	PAVISTREAM       pavi;
	LPTHREADINFO     lpThreadInfo = (LPTHREADINFO)lpParamater;
	AVISTREAMINFO    si;
	BITMAPINFOHEADER biIn, biOut;
	double           dInterval;
	double           dCurTime, dNextTime;

	pavi = lpThreadInfo->pavi;
	hwnd = lpThreadInfo->hwnd;
	
	lStart = AVIStreamStart(pavi);
	lEnd  = lStart + AVIStreamLength(pavi);

	AVIStreamInfo(pavi, &si, sizeof(AVISTREAMINFO));
	dInterval = 1000 / ((double)si.dwRate / si.dwScale);
	
	lSize = sizeof(BITMAPINFOHEADER);
	AVIStreamReadFormat(pavi, 0, &biIn, &lSize);
	
	ZeroMemory(&biOut, sizeof(BITMAPINFOHEADER));
	biOut.biSize        = sizeof(BITMAPINFOHEADER);
	biOut.biWidth       = biIn.biWidth;
	biOut.biHeight      = biIn.biHeight;
	biOut.biPlanes      = 1;
	biOut.biBitCount    = 24;
	biOut.biCompression = BI_RGB;
	biOut.biSizeImage   = biOut.biHeight * ((3 * biOut.biWidth + 3) / 4) * 4;

	hic = ICLocate(ICTYPE_VIDEO, 0, &biIn, &biOut, ICMODE_DECOMPRESS);
	if (hic == NULL) {
		MessageBox(NULL, TEXT("デコンプレッサの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	lpBitsIn = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, biIn.biSizeImage);
	lpBitsOut = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, biOut.biSizeImage);

	ICDecompressBegin(hic, &biIn, &biOut);

	for (; !lpThreadInfo->bExit;) {
		for (i = lStart; i < lEnd; i++) {
			dNextTime = (double)timeGetTime();
			dNextTime += dInterval;

			AVIStreamRead(pavi, i, 1, lpBitsIn, biIn.biSizeImage, NULL, NULL);
			ICDecompress(hic, 0, &biIn, lpBitsIn, &biOut, lpBitsOut);

			hdc = GetDC(hwnd);
			StretchDIBits(hdc, 0, 0, biOut.biWidth, biOut.biHeight, 0, 0, biOut.biWidth, biOut.biHeight, lpBitsOut, (LPBITMAPINFO)&biOut, DIB_RGB_COLORS, SRCCOPY);
			ReleaseDC(hwnd, hdc);
			ZeroMemory(lpBitsIn, biIn.biSizeImage);

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

			if (lpThreadInfo->bExit)
				break;
		}
	}
	
	ICDecompressEnd(hic);
	
	HeapFree(GetProcessHeap(), 0, lpBitsIn);
	HeapFree(GetProcessHeap(), 0, lpBitsOut);
	ICClose(hic);

	return 0;
}

BOOL GetAudioData(PAVISTREAM pavi, LPWAVEFORMATEX lpwfDest, LPBYTE *lplpDestData, LPDWORD lpdwDestSize)
{
	LONG           lStart, lEnd;
	LONG           lSize;
	LPWAVEFORMATEX lpwfSrc;
	LPBYTE         lpSrcData;
	HRESULT        hr;
	
	AVIStreamReadFormat(pavi, 0, NULL, &lSize);
	lpwfSrc = (LPWAVEFORMATEX)HeapAlloc(GetProcessHeap(), 0, lSize);
	AVIStreamReadFormat(pavi, 0, lpwfSrc, &lSize);
	
	lStart = AVIStreamStart(pavi);
	lEnd  = lStart + AVIStreamLength(pavi);
	hr = AVIStreamRead(pavi, lStart, lEnd, NULL, 0, &lSize, NULL);
	if (hr != 0) {
		TCHAR szBuf[256];
		wsprintf(szBuf, TEXT("AVIStreamReadの呼び出しに失敗しました。サイズ %d エラー %x"), lSize, hr);
		MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpwfSrc);
		return FALSE;
	}

	lpSrcData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, lSize);
	AVIStreamRead(pavi, lStart, lEnd, lpSrcData, lSize, NULL, NULL);

	if (lpwfSrc->wFormatTag != WAVE_FORMAT_PCM) {
		if (!DecodeToWave(lpwfSrc, lpSrcData, lSize, lpwfDest, lplpDestData, lpdwDestSize)) {
			HeapFree(GetProcessHeap(), 0, lpwfSrc);
			HeapFree(GetProcessHeap(), 0, lpSrcData);
			return FALSE;
		}
		HeapFree(GetProcessHeap(), 0, lpwfSrc);
		HeapFree(GetProcessHeap(), 0, lpSrcData);
	}
	else {
		*lplpDestData = lpSrcData;
		*lpwfDest = *lpwfSrc;
		*lpdwDestSize = lSize;
		HeapFree(GetProcessHeap(), 0, lpwfSrc);
	}
	
	return TRUE;
}

BOOL DecodeToWave(LPWAVEFORMATEX lpwfSrc, LPBYTE lpSrcData, DWORD dwSrcSize, LPWAVEFORMATEX lpwfDest, LPBYTE *lplpDestData, LPDWORD lpdwDestSize)
{
	HACMSTREAM      has;
	ACMSTREAMHEADER ash;
	LPBYTE          lpDestData;
	DWORD           dwDestSize;
	BOOL            bResult;
	
	lpwfDest->wFormatTag = WAVE_FORMAT_PCM;
	acmFormatSuggest(NULL, lpwfSrc, lpwfDest, sizeof(WAVEFORMATEX), ACM_FORMATSUGGESTF_WFORMATTAG);
	
	if (acmStreamOpen(&has, NULL, lpwfSrc, lpwfDest, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME) != 0) {
		MessageBox(NULL, TEXT("変換ストリームのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}

	acmStreamSize(has, dwSrcSize, &dwDestSize, ACM_STREAMSIZEF_SOURCE);
	lpDestData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDestSize);

	ZeroMemory(&ash, sizeof(ACMSTREAMHEADER));
	ash.cbStruct    = sizeof(ACMSTREAMHEADER);
	ash.pbSrc       = lpSrcData;
	ash.cbSrcLength = dwSrcSize;
	ash.pbDst       = lpDestData;
	ash.cbDstLength = dwDestSize;

	acmStreamPrepareHeader(has, &ash, 0);
	bResult = acmStreamConvert(has, &ash, 0) == 0;
	acmStreamUnprepareHeader(has, &ash, 0);
	
	acmStreamClose(has, 0);

	if (bResult) {
		*lplpDestData = lpDestData;
		*lpdwDestSize = ash.cbDstLengthUsed;
	}
	else {
		MessageBox(NULL, TEXT("変換に失敗しました。"), NULL, MB_ICONWARNING);
		*lplpDestData = NULL;
		*lpdwDestSize = 0;
		HeapFree(GetProcessHeap(), 0, lpDestData);
	}

	return bResult;
}

ICLocateに関係するコードは、次のようになっています。

lSize = sizeof(BITMAPINFOHEADER);
AVIStreamReadFormat(pavi, 0, &biIn, &lSize);

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

hic = ICLocate(ICTYPE_VIDEO, 0, &biIn, &biOut, ICMODE_DECOMPRESS);
if (hic == NULL) {
	MessageBox(NULL, TEXT("デコンプレッサの取得に失敗しました。"), NULL, MB_ICONWARNING);
	return 0;
}

lpBitsIn = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, biIn.biSizeImage);
lpBitsOut = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, biOut.biSizeImage);

まず、AVIStreamReadFormatで圧縮されたフレームのフォーマットを取得します。 続いて、解凍後のフォーマットを明示的に初期化します。 解凍後のフォーマットの幅と高さは、圧縮されたフレームのフォーマットと同一で問題ありませんが、 ビット数は24ビットにしておくことになります。 つまり、1ピクセルに3バイト使用する、通常(無圧縮)のDIBのフォーマットを指定します。 biCompressionに指定しているBI_RGBは無圧縮を意味していますが、0を指定しても問題ありません。 フォーマットの初期化が終わればそれをICLocateに指定し、コーデックのハンドルを取得します。 第5引数にICMODE_DECOMPRESSを指定しているため、関数が成功した場合は解凍処理を行えることが保障されます。 圧縮データと解凍データを格納できるだけのメモリは、事前に確保しておくようにします。

ICDecompressを呼び出すためには、ICDecompressBeginを呼び出しておく必要があります。 これは正式にはマクロであり、中身はICSendMessageの呼び出しによって実現されていますが、 今回はこれを利用することにします。 また、ICDecompressを呼び出しが不要になった場合は、ICDecompressEndを呼び出しておくことになります。 これもICDecompressBeginと同じくマクロになっています。 解凍と描画処理に関係するコードは、次のようになっています。

AVIStreamRead(pavi, i, 1, lpBitsIn, biIn.biSizeImage, NULL, NULL);
ICDecompress(hic, 0, &biIn, lpBitsIn, &biOut, lpBitsOut);

hdc = GetDC(hwnd);
StretchDIBits(hdc, 0, 0, biOut.biWidth, biOut.biHeight, 0, 0, biOut.biWidth, biOut.biHeight, lpBitsOut, (LPBITMAPINFO)&biOut, DIB_RGB_COLORS, SRCCOPY);
ReleaseDC(hwnd, hdc);
ZeroMemory(lpBitsIn, biIn.biSizeImage);

AVIStreamReadで取得した圧縮されたフレームをICDecompressの第4引数に指定します。 これにより、第6引数に解凍されたフレームが格納されます。 後は、この解凍されたフレームとフォーマットをGDI関数のStretchDIBitsに指定すれば、 フレームが描画されることになります。


戻る