EternalWindows
AVI / AVIファイルの作成

AVIファイルを作成するための主な作業は、新しくストリームを作成し、 そのストリームに対してサンプルを書き込むことです。 ストリームとファイルが関連付けられていれば、 ストリームへの書き込みはファイルの書き込みに反映されます。 次に示すAVIFileCreateStreamは、ファイルに関連付けられた新しいストリームを作成します。

STDAPI AVIFileCreateStream(
  PAVIFILE pfile,    
  PAVISTREAM * ppavi,
  AVISTREAMINFO * psi
);

pfileは、ファイルインターフェースを指定します。 これは、AVIFileOpenで取得することになります。 ppaviは、新しく作成されたストリームを受け取る変数のアドレスを指定します。 psiは、ストリームの情報を格納したAVISTREAMINFO構造体のアドレスを指定します。

ストリームにサンプルを書き込むためには、 そのサンプルのサイズや圧縮されているかどうかを表すフォーマットを設定しておく必要があります。 これには、AVIStreamSetFormatを呼び出します。

STDAPI AVIStreamSetFormat(
  PAVISTREAM pavi,
  LONG lPos,      
  LPVOID lpFormat,
  LONG cbFormat   
);

paviは、ストリームのハンドルを指定します。 lPosは、フォーマットを設定する位置を指定します。 これは、0で問題ないと思われます。 lpFormatは、設定するフォーマットを格納したバッファを指定します。 cbFormatは、lpFormatのサイズを指定します。

ストリームにサンプルを書き込むには、AVIStreamWriteを呼び出します。

STDAPI AVIStreamWrite(
  PAVISTREAM pavi,     
  LONG lStart,         
  LONG lSamples,       
  LPVOID lpBuffer,     
  LONG cbBuffer,       
  DWORD dwFlags,       
  LONG * plSampWritten,
  LONG * plBytesWritten
);

paviは、ストリームのハンドルを指定します。 lStartは、サンプルの書き込み位置を指定します。 lSamplesは、書き込みたいサンプルのサイズを指定します lpBufferは、書き込みたいサンプルを格納したバッファを指定します。 cbBufferは、lpBufferのサイズを指定します。 dwFlagsは、0またはAVIIF_KEYFRAMEを指定します。 キーフレームとは、これから書き込むフレームが先行データに依存しないフレームのことです。 plSampWrittenは、書き込まれたサンプル数を受け取る変数のアドレスを指定します。 不要な場合はNULLを指定することができます。 plBytesWrittenは、書き込まれたバイト数を受け取る変数のアドレスを指定します。 不要な場合はNULLを指定することができます。

今回のプログラムは、無圧縮のAVIファイルを作成します。

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

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

void WriteStream(PAVISTREAM pavi, LPAVISTREAMINFO lpsi);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	PAVIFILE      pfile;
	PAVISTREAM    pavi;
	AVISTREAMINFO si;
	
	AVIFileInit();
	
	if (AVIFileOpen(&pfile, TEXT("video.avi"), OF_CREATE | OF_WRITE, NULL) != 0) {
		MessageBox(NULL, TEXT("ファイルの作成または書き込みオープンに失敗しました。"), TEXT("OK"), MB_OK);
		AVIFileExit();
		return 0;
	}

	ZeroMemory(&si, sizeof(AVISTREAMINFO));
	si.fccType    = streamtypeVIDEO;
	si.fccHandler = comptypeDIB;
	si.dwScale    = 1;
	si.dwRate     = 2;
	si.dwLength   = 10;
	si.dwQuality  = (DWORD)-1;
	SetRect(&si.rcFrame, 0, 0, 32, 32);

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

	WriteStream(pavi, &si);
	MessageBox(NULL, TEXT("書き込みを終了しました。"), TEXT("OK"), MB_OK);
	
	AVIStreamRelease(pavi);
	AVIFileRelease(pfile);
	AVIFileExit();
	
	return 0;
}

void WriteStream(PAVISTREAM pavi, LPAVISTREAMINFO lpsi)
{
	HDC              hdcMem;
	HBITMAP          hbmpMem, hbmpMemPrev;
	DWORD            i;
	TCHAR            szBuf[256];
	LPVOID           lpBits;
	BITMAPINFOHEADER bmiHeader;

	ZeroMemory(&bmiHeader, sizeof(BITMAPINFOHEADER));
	bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
	bmiHeader.biWidth       = lpsi->rcFrame.right;
	bmiHeader.biHeight      = lpsi->rcFrame.bottom;
	bmiHeader.biPlanes      = 1;
	bmiHeader.biBitCount    = 24;
	bmiHeader.biCompression = BI_RGB;
	bmiHeader.biSizeImage   = bmiHeader.biHeight * ((3 * bmiHeader.biWidth + 3) / 4) * 4;

	AVIStreamSetFormat(pavi, 0, &bmiHeader, sizeof(BITMAPINFOHEADER));

	hdcMem = CreateCompatibleDC(NULL);
	hbmpMem = CreateDIBSection(NULL, (LPBITMAPINFO)&bmiHeader, DIB_RGB_COLORS, &lpBits, NULL, 0);
	hbmpMemPrev = (HBITMAP)SelectObject(hdcMem, hbmpMem);

	SetBkMode(hdcMem, TRANSPARENT);

	for (i = 0; i < lpsi->dwLength; i++) {
		wsprintf(szBuf, TEXT("%d"), i);
		FillRect(hdcMem, &lpsi->rcFrame, (HBRUSH)GetStockObject(GRAY_BRUSH));
		DrawText(hdcMem, szBuf, -1, &lpsi->rcFrame, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
		AVIStreamWrite(pavi, i, 1, lpBits, bmiHeader.biSizeImage, AVIIF_KEYFRAME, NULL, NULL);
	}
	
	SelectObject(hdcMem, hbmpMemPrev);
	DeleteObject(hbmpMem);
	DeleteDC(hdcMem);
}

AVIFileOpenにOF_CREATEとOF_WRITEを指定して、ファイルハンドルを取得します。 これでAVIFileCreateStreamを呼び出すことができますが、 そのためにはストリームの情報を表すAVISTREAMINFO構造体を初期化しておく必要があります。

ZeroMemory(&si, sizeof(AVISTREAMINFO));
si.fccType    = streamtypeVIDEO;
si.fccHandler = comptypeDIB; // mmioFOURCC('D', 'I', 'B', ' ')としてもよい
si.dwScale    = 1;
si.dwRate     = 2;
si.dwLength   = 10;
si.dwQuality  = (DWORD)-1;
SetRect(&si.rcFrame, 0, 0, 32, 32);

fccTypeがstreamtypeVIDEOになっていることから、ビデオストリームを作成することになります。 fccHandlerには使用するコーデックのFOURCCを指定しますが、ここではcomptypeDIBを指定します。 これは、無圧縮の状態でサンプルを書き込むという意味です。 dwRateをdwScaleで割った値が2であることから、 このAVIファイルは1秒間に2回フレームを描画することになります。 dwLengthが10であることからストリームの格納されるフレームは10個であり、 この中の2個を1秒間で表示することになるため、再生時間は5秒ということになります。 dwQualityに-1を指定した場合は、デフォルトの品質になります。 ビデオストリームの場合は、rcFrameにフレームのサイズを設定します。

今回のプログラムが作成するAVIファイルには、0から9までの数字を持ったフレームが順に格納されます。 数字を表したフレームを作成する手順としては、 まずメモリデバイスコンテキストを作成し、これにDIBセクションを割り当てます。 こうした場合、メモリデバイスコンテキストへの書き込みがDIBセクションのビットイメージに反映されるため、 これをAVIStreamWriteに指定することができます。

ZeroMemory(&bmiHeader, sizeof(BITMAPINFOHEADER));
bmiHeader.biSize       = sizeof(BITMAPINFOHEADER);
bmiHeader.biWidth      = lpsi->rcFrame.right;
bmiHeader.biHeight     = lpsi->rcFrame.bottom;
bmiHeader.biPlanes     = 1;
bmiHeader.biBitCount   = 24;
bmiHeaderbiCompression = BI_RGB;
bmiHeader.biSizeImage  = bmiHeader.biHeight * ((3 * bmiHeader.biWidth + 3) / 4) * 4;

AVIStreamSetFormat(pavi, 0, &bmiHeader, sizeof(BITMAPINFOHEADER));

hdcMem = CreateCompatibleDC(NULL);
hbmpMem = CreateDIBSection(NULL, (LPBITMAPINFO)&bmiHeader, DIB_RGB_COLORS, &lpBits, NULL, 0);
hbmpMemPrev = (HBITMAP)SelectObject(hdcMem, hbmpMem);

SetBkMode(hdcMem, TRANSPARENT);

for (i = 0; i < lpsi->dwLength; i++) {
	wsprintf(szBuf, TEXT("%d"), i);
	FillRect(hdcMem, &lpsi->rcFrame, (HBRUSH)GetStockObject(GRAY_BRUSH));
	DrawText(hdcMem, szBuf, -1, &lpsi->rcFrame, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	AVIStreamWrite(pavi, i, 1, lpBits, bmiHeader.biSizeImage, AVIIF_KEYFRAME, NULL, NULL);
}

まず、無圧縮を表すBITMAPINFOHEADER構造体を初期化し、 これをAVIStreamSetFormatでフォーマットとして設定します。 続いて、メモリデバイスコンテキストとDIBセクションの作成及び割り当てを済まし、 SetBkModeでテキストの透過描画を有効にします。 そして後は、フレームの数だけ背景の塗りつぶしとテキストの描画を行い、 AVIStreamWriteを呼び出せばよいことになります。

ストリームの混合

ビデオストリームだけを格納したAVIファイルに、 WAVEファイルなどの音声を新たに追加したいという要望はよくあると思われます。 ビデオストリームとオーディオストリームの両方があれば、 AVISaveVを呼び出して2つを混ぜ合わした新しいAVIファイルを作成することができます。

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

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

BOOL SaveAviFile(LPTSTR lpszFileName, PAVISTREAM paviVideo, PAVISTREAM paviAudio);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	PAVISTREAM paviVideo;
	PAVISTREAM paviAudio;
	
	AVIFileInit();
	
	if (AVIStreamOpenFromFile(&paviVideo, TEXT("video.avi"), streamtypeVIDEO, 0, OF_READ, NULL) != 0) {
		MessageBox(NULL, TEXT("ファイルまたはビデオストリームが存在しません。"), TEXT("OK"), MB_OK);
		AVIFileExit();
		return 0;
	}

	if (AVIStreamOpenFromFile(&paviAudio, TEXT("audio.wav"), streamtypeAUDIO, 0, OF_READ, NULL) != 0) {
		MessageBox(NULL, TEXT("ファイルまたはオーディオストリームが存在しません。"), TEXT("OK"), MB_OK);
		AVIStreamRelease(paviVideo);
		AVIFileExit();
		return 0;
	}
	
	if (SaveAviFile(TEXT("new.avi"), paviVideo, paviAudio))
		MessageBox(NULL, TEXT("AVIファイルを作成しました。"), TEXT("OK"), MB_OK);

	AVIStreamRelease(paviAudio);
	AVIStreamRelease(paviVideo);
	AVIFileExit();
	
	return 0;
}

BOOL SaveAviFile(LPTSTR lpszFileName, PAVISTREAM paviVideo, PAVISTREAM paviAudio)
{
	int                  i;
	PAVISTREAM           pavis[2];
	AVICOMPRESSOPTIONS   options[2];
	LPAVICOMPRESSOPTIONS lpOptions[2];

	for (i = 0; i < 2; i++) {
		ZeroMemory(&options[i], sizeof(AVICOMPRESSOPTIONS));
		lpOptions[i] = &options[i];
	}

	pavis[0] = paviVideo;
	pavis[1] = paviAudio;
	if (!AVISaveOptions(NULL, 0, 2, pavis, lpOptions))
		return FALSE;

	if (AVISaveV(lpszFileName, NULL, NULL, 2, pavis, lpOptions) != AVIERR_OK) {
		MessageBox(NULL, TEXT("AVIファイルの作成に失敗しました"), NULL, MB_ICONWARNING);
		AVISaveOptionsFree(2, lpOptions);
		return FALSE;
	}

	AVISaveOptionsFree(2, lpOptions);

	return TRUE;
}

1つ目のAVIStreamOpenFromFileでは既存のAVIファイルからビデオストリームを取得し、 2つ目のAVIStreamOpenFromFileでは既存のWAVEファイルからオーディオストリームを取得しています。 後者の方法が成立する点は覚えておきたいところです。 取得した2つのストリームはSaveAviFileという自作関数に指定され、 関数内部で必要な配列を定義しています。 AVISaveOptionsではダイアログが表示されるため、 ビデオストリームやオーディオストリームを圧縮して保存することもできます。



戻る