EternalWindows
マルチメディア入力 / AVIの読み取り

映像と音声を格納したAVIファイルは、RIFF形式を採用しています。 つまり、マルチメディアファイル入出力関数に適切なチャンクIDを指定して、 映像や音声に関する情報を取得することができます。 しかし、多くの場合、AVIファイルからの情報の取得は、 VFW(Video for Windows)のAVIFile関数が使用されています。 その理由は、AVIファイルのチャンク構造が非常に複雑であり、 データが分割されて格納されているからであると思われます。 次に、AVIファイルのチャンク構造を示します。

RIFFチャンクの中には、LISTチャンクが2つ存在します。 片方のチャンクのリストタイプはhdrlであり、 もう片方のチャンクのリストタイプはmoviです。 前者のチャンクには主にヘッダー情報とフォーマット情報が格納されており、 たとえば、avihチャンクにはMainAVIHeader構造体が格納されています。 さらに前者のチャンクの中には、リストタイプがstrlであるLISTチャンクが2つ存在し、 片方が映像の情報を格納し、もう片方が音声の情報を格納しています。 内部に存在するチャンクのID(strhなど)は両者共通となっています。

リストタイプがmoviであるリストチャンクには、映像データと音声データが格納されています。 00dcチャンクは1つの映像データを表し、このチャンクは映像の数だけ存在します。 また、01wbチャンクは1秒間に音声データを表し、このチャンクは秒の数だけ存在します。 00や01といった数字はストリームの番号であり、 ビデオストリームのデータを格納するチャンクは00、 オーディオストリームのデータを格納するチャンクは01となります。 dcという文字列は、圧縮されたDIBデータが格納されていることを意味し、 これが無圧縮のDIBデータであるばらば、dbという文字列になります。 wbという文字列は、WAVEデータが格納されていることを意味しますが、 MP3のような圧縮されたデータが格納されている場合もwbとなります。 ちなみに、音声フォーマットはWAVEFORMAT構造体と記述していますが、 この構造体が単一で格納されているわけではない点には注意してください。 無圧縮のWAVEデータならばフォーマットはPCMWAVEFORMAT構造体であり、 MP3による圧縮ならばMPEGLAYER3WAVEFORMAT構造体が格納されています。

上記した各チャンクにマルチメディアファイル入出力関数でアクセスするとなった場合、 当然ながらそれらを格納するチャンクのチャンクIDを理解しておく必要があります。 また、目的のチャンクまでディセンドとアセンドを何回も行うことになり、 コードも複雑になってしまいます。 一方、VFWのAVIFile関数ではこうした操作を必要とせずに目的の情報を取得できるため、 できる限りこれらの関数を使用した方がよいと思われます。 ただし、MainAVIHeader構造体とAVIStreamHeader構造体はAVIFile関数で取得できないため、 これらの構造体が必要な場合はマルチメディアファイル入出力関数を呼び出すことになります。

今回のプログラムは、AVIファイルに格納されている各種構造体を取得し、そのメンバの値を表示します。 VFWの関数を呼び出していませんが、必要な構造体と定義を参照する関係上、 vfw.hをインクルードしています。

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

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

BOOL GetAviHeader(LPTSTR lpszFileName, MainAVIHeader *lpAviHeader, AVIStreamHeader *lpVideoStreamHeader, LPBITMAPINFO lpbm, AVIStreamHeader *lpAudioStreamHeader, LPWAVEFORMAT lpwf);
BOOL GetStreamHeader(HMMIO hmmio, AVIStreamHeader *lpStreamHeader, LPVOID lpFormat);
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 HWND hwndListBox = NULL;
	
	switch (uMsg) {

	case WM_CREATE: {
		TCHAR           szBuf[256];
		LPSTR           lp;
		MainAVIHeader   aviHeader;
		AVIStreamHeader videoStreamHeader;
		AVIStreamHeader audioStreamHeader;
		BITMAPINFO      bmInfo;
		WAVEFORMAT      wf;

		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		if (!GetAviHeader(TEXT("sample.avi"), &aviHeader, &videoStreamHeader, &bmInfo, &audioStreamHeader, &wf))
			return -1;

		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("AVI情報"));
		wsprintf(szBuf, TEXT("dwMicroSecPerFrame : %d"), aviHeader.dwMicroSecPerFrame);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		wsprintf(szBuf, TEXT("dwMaxBytesPerSec : %d"), aviHeader.dwMaxBytesPerSec);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		wsprintf(szBuf, TEXT("dwWidth : %d, dwHeight : %d"), aviHeader.dwWidth, aviHeader.dwHeight);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("ビデオストリーム"));
		lp = (LPSTR)&videoStreamHeader.fccHandler;
		wsprintf(szBuf, TEXT("fccHandler : '%c' '%c' '%c' '%c'"), lp[0], lp[1], lp[2], lp[3]);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		wsprintf(szBuf, TEXT("dwRate : %d, dwScale : %d"), videoStreamHeader.dwRate, videoStreamHeader.dwScale);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		wsprintf(szBuf, TEXT("biBitCount : %d"), bmInfo.bmiHeader.biBitCount);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("オーディオストリーム"));
		lp = (LPSTR)&audioStreamHeader.fccHandler;
		wsprintf(szBuf, TEXT("fccHandler : '%c' '%c' '%c' '%c'"), lp[0], lp[1], lp[2], lp[3]);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		wsprintf(szBuf, TEXT("wFormatTag : %#x"), wf.wFormatTag);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		wsprintf(szBuf, TEXT("nSamplesPerSec : %d"), wf.nSamplesPerSec);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		wsprintf(szBuf, TEXT("nChannels : %d"), wf.nChannels);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		
		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

BOOL GetAviHeader(LPTSTR lpszFileName, MainAVIHeader *lpAviHeader, AVIStreamHeader *lpVideoStreamHeader, LPBITMAPINFO lpbmInfo, AVIStreamHeader *lpAudioStreamHeader, LPWAVEFORMAT lpwf)
{
	HMMIO    hmmio;
	MMCKINFO mmckRiff;
	MMCKINFO mmckList;
	MMCKINFO mmckAvih;
	
	hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ);
	if (hmmio == NULL) {
		MessageBox(NULL, TEXT("ファイルのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}
	
	mmckRiff.fccType = formtypeAVI;
	if (mmioDescend(hmmio, &mmckRiff, NULL, MMIO_FINDRIFF) != MMSYSERR_NOERROR) {
		MessageBox(NULL, TEXT("AVIファイルではありません。"), NULL, MB_ICONWARNING);
		mmioClose(hmmio, 0);
		return FALSE;
	}

	mmckList.fccType = listtypeAVIHEADER;
	if (mmioDescend(hmmio, &mmckList, NULL, MMIO_FINDLIST) != MMSYSERR_NOERROR) {
		mmioClose(hmmio, 0);
		return FALSE;
	}

	if (lpAviHeader != NULL) {
		mmckAvih.ckid = ckidAVIMAINHDR;
		if (mmioDescend(hmmio, &mmckAvih, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR) {
			mmioClose(hmmio, 0);
			return FALSE;
		}

		mmioRead(hmmio, (HPSTR)lpAviHeader, mmckAvih.cksize);
		mmioAscend(hmmio, &mmckAvih, 0);
	}

	if (!GetStreamHeader(hmmio, lpVideoStreamHeader, lpbmInfo)) {
		mmioClose(hmmio, 0);
		return FALSE;
	}
	
	if (!GetStreamHeader(hmmio, lpAudioStreamHeader, lpwf)) {
		mmioClose(hmmio, 0);
		return FALSE;
	}
	
	mmioAscend(hmmio, &mmckList, 0);
	mmioAscend(hmmio, &mmckRiff, 0);

	return TRUE;
}

BOOL GetStreamHeader(HMMIO hmmio, AVIStreamHeader *lpStreamHeader, LPVOID lpFormat)
{
	MMCKINFO mmckList;
	MMCKINFO mmckStrh;
	MMCKINFO mmckStrf;

	mmckList.fccType = listtypeSTREAMHEADER;
	if (mmioDescend(hmmio, &mmckList, NULL, MMIO_FINDLIST) != MMSYSERR_NOERROR)
		return FALSE;

	if (lpStreamHeader != NULL) {
		mmckStrh.ckid = ckidSTREAMHEADER;
		if (mmioDescend(hmmio, &mmckStrh, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR) {
			mmioAscend(hmmio, &mmckList, 0);
			return FALSE;
		}

		mmioRead(hmmio, (HPSTR)lpStreamHeader, mmckStrh.cksize);
		mmioAscend(hmmio, &mmckStrh, 0);
	}

	if (lpFormat != NULL) {
		mmckStrf.ckid = ckidSTREAMFORMAT;
		if (mmioDescend(hmmio, &mmckStrf, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR) {
			mmioAscend(hmmio, &mmckList, 0);
			return FALSE;
		}

		mmioRead(hmmio, (HPSTR)lpFormat, mmckStrf.cksize);
		mmioAscend(hmmio, &mmckStrf, 0);
	}

	mmioAscend(hmmio, &mmckList, 0);

	return TRUE;
}

自作関数のGetAviHeaderは、マルチメディアファイル入出力関数を使用してMainAVIHeader構造体を取得します。 また、ビデオストリームとオーディオストリーム用のAVIStreamHeaderを取得すると共に、 それぞれのフォーマットも取得します。 重要な部分を順に見ていきます。

mmckRiff.fccType = formtypeAVI;
if (mmioDescend(hmmio, &mmckRiff, NULL, MMIO_FINDRIFF) != MMSYSERR_NOERROR) {
	MessageBox(NULL, TEXT("AVIファイルではありません。"), NULL, MB_ICONWARNING);
	mmioClose(hmmio, 0);
	return FALSE;
}

mmckList.fccType = listtypeAVIHEADER;
if (mmioDescend(hmmio, &mmckList, NULL, MMIO_FINDLIST) != MMSYSERR_NOERROR) {
	mmioClose(hmmio, 0);
	return FALSE;
}

まずは、RIFFチャンクにディセンドしなければならないため、 mmioDescendにMMIO_FINDRIFFを指定します。 また、フォームタイプにはAVIファイルであることを示すformtypeAVIを指定します。 続いて、LISTチャンクにデイセンドするため、mmioDescendにMMIO_FINDLISTを指定します。 このチャンクのリストタイプはhdrlであるため、それを示すlisttypeAVIHEADERを指定します。 LISTチャンクの下には、avihチャンクが存在します。

if (lpAviHeader != NULL) {
	mmckAvih.ckid = ckidAVIMAINHDR;
	if (mmioDescend(hmmio, &mmckAvih, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR) {
		mmioClose(hmmio, 0);
		return FALSE;
	}

	mmioRead(hmmio, (HPSTR)lpAviHeader, mmckAvih.cksize);
	mmioAscend(hmmio, &mmckAvih, 0);
}

チャンクIDにavihを示すckidAVIMAINHDRを指定し、mmioDescendを呼び出します。 RIFFチャンクやLISTチャンクにデイセンドするわけではないので、 MMIO_FINDCHUNKを指定しています。 デイセンドに成功したら、mmioReadでMainAVIHeader構造体のデータを読み取ることができます。 次に、AVIStreamHeaderを読み取る必要があるため、 現在のavihチャンクからアセンドします。

AVIStreamHeader構造体は、リストタイプstrlのLISTチャンクに存在しています。 これは、ビデオストリームとオーディオストリーム共に共通します。 よって取得処理が同一処理となるため、GetStreamHeaderという関数を用意し、 ビデオストリーム用とオーディオストリーム用でそれぞれ呼び出すことにしています。 GetStreamHeaderの内部は、次のようになっています。

mmckList.fccType = listtypeSTREAMHEADER;
if (mmioDescend(hmmio, &mmckList, NULL, MMIO_FINDLIST) != MMSYSERR_NOERROR)
	return FALSE;

if (lpStreamHeader != NULL) {
	mmckStrh.ckid = ckidSTREAMHEADER;
	if (mmioDescend(hmmio, &mmckStrh, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR) {
		mmioAscend(hmmio, &mmckList, 0);
		return FALSE;
	}

	mmioRead(hmmio, (HPSTR)lpStreamHeader, mmckStrh.cksize);
	mmioAscend(hmmio, &mmckStrh, 0);
}

if (lpFormat != NULL) {
	mmckStrf.ckid = ckidSTREAMFORMAT;
	if (mmioDescend(hmmio, &mmckStrf, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR) {
		mmioAscend(hmmio, &mmckList, 0);
		return FALSE;
	}

	mmioRead(hmmio, (HPSTR)lpFormat, mmckStrf.cksize);
	mmioAscend(hmmio, &mmckStrf, 0);
}

まず、strlを示すlisttypeSTREAMHEADERを指定してLISTチャンクにディセンドします。 AVIStreamHeader構造体は、strhチャンクに格納されているため、 ckidSTREAMHEADERを指定してこのチャンクへディセンドし、mmioReadでデータを読み取ります。 フォーマットはLISTチャンクの下のstrfチャンクに存在するため、 mmioAscendによってLISTチャンクへと戻り、 ckidSTREAMFORMATを指定してstrfチャンクへディセンドします。


戻る