EternalWindows
AVI / AVI再生サンプル

前節までで、オーディオストリームの再生とビデオストリームの再生は実現できました。 後は、これらを同時に再生すれば、AVIファイルを再生を再生していることになります。 2つのストリームを扱う場合は、AVIStreamOpenFromFileを呼び出すよりも、 AVIFileOpenでファイルインターフェースを取得しておいたほうがよいでしょう。 そうすることで、ファイルの確認を2回行わずに済みます。 AVIFileOpenは、次のように定義されています。

STDAPI AVIFileOpen(
  PAVIFILE * ppfile,   
  LPCTSTR szFile,      
  UINT mode,           
  CLSID * pclsidHandler
);

ppfileは、ファイルインターフェースを受け取る変数のアドレスを指定します。 ファイルインターフェースは、ファイルのハンドルのようなものと考えて問題ありません。 szFileは、オープンしたいファイル名を指定します。 modeは、アクセスモードを指定します。 pclsidHandlerは、使用するハンドラのCLSIDを指定します。 NULLを指定すると、システムによって適切なハンドラが決定されます。

ファイルインターフェースを取得すれば、AVIFileGetStreamでストリームを取得することができます。

STDAPI AVIFileGetStream(
  PAVIFILE pfile,    
  PAVISTREAM * ppavi,
  DWORD fccType,     
  LONG lParam        
);

pfileは、ファイルインターフェースを指定します。 ppaviは、ストリームを受け取る変数のアドレスを指定します。 fccTypeは、取得するストリームの種類を表すFOURCCを指定します。 lParamは、0で問題ありません。

不要になったファイルインターフェースは、AVIFileReleaseで開放することになります。

STDAPI_(ULONG) AVIFileRelease(
  PAVIFILE pfile
);

pfileは、ファイルインターフェースを指定します。

今回のプログラムは、オーディオストリームとビデオストリームを共に再生します。 基本的には、両者の処理が合わさっただけの内容です。

#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           lpBits = NULL;
	HDRAWDIB         hdd;
	PAVISTREAM       pavi;
	LPTHREADINFO     lpThreadInfo = (LPTHREADINFO)lpParamater;
	AVISTREAMINFO    si;
	BITMAPINFOHEADER bmiHeader;
	double           dInterval;
	double           dCurTime, dNextTime;

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

	lSize = sizeof(BITMAPINFOHEADER);
	AVIStreamReadFormat(pavi, 0, &bmiHeader, &lSize);
	lpBits = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, bmiHeader.biSizeImage);

	AVIStreamInfo(pavi, &si, sizeof(AVISTREAMINFO));
	dInterval = 1000 / ((double)si.dwRate / si.dwScale);

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

			AVIStreamRead(pavi, i, 1, lpBits, bmiHeader.biSizeImage, NULL, NULL);
			hdc = GetDC(hwnd);
			DrawDibDraw(hdd, hdc, 0, 0, -1, -1, &bmiHeader, lpBits, 0, 0, -1, -1, 0);
			ReleaseDC(hwnd, hdc);
			ZeroMemory(lpBits, bmiHeader.biSizeImage);

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

			if (lpThreadInfo->bExit)
				break;
		}
	}

	HeapFree(GetProcessHeap(), 0, lpBits);
	DrawDibClose(hdd);

	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;
}

AVIFileOpenでファイルインターフェースのハンドルを取得し、 AVIFileGetStreamでビデオストリームとオーディオストリームのハンドルを取得します。 その後、ビデオストリームが存在するならばそれを再生する処理を行い、 オーディオストリームが存在するならばそれを再生する処理を行います。 よって、今回のプログラムでは、オーディオストリームは再生されるけれども、 ビデオストリームは再生されないといった事も生じます。

再生時間の取得

ビデオストリームやオーディオストリームの再生時間を取得するには、 AVIStreamSampleToTimeという関数が役に立ちます。 この関数は、第2引数に指定したサンプルの番号から、 そのサンプルを再生するのに必要な時間をミリ秒単位で返します。

DWORD dwSecond;
TCHAR szBuf[256];

dwSecond = AVIStreamSampleToTime(pavi, i) / 1000;
wsprintf(szBuf, TEXT("%02d : %02d"), dwSecond / 60, dwSecond % 60);
SetWindowText(hwnd, szBuf);

このようなコードを今回のThreadProcに追加した場合、 ウインドウのタイトルに現在の再生時間が表示されることになります。 全体を再生するのに必要な時間を取得したい場合は、 第2引数にAVIStreamLengthの戻り値を指定します。



戻る