EternalWindows
AVI / ビデオコーデックの確認

ストリームに格納されているデータが圧縮されている場合、それを解凍する必要が生じます。 たとえば、前節のオーディオストリームではACMを通じて解凍処理を行いましたが、 こうした解凍処理は必ずしも成功するとは限りません。 圧縮されたデータを解凍するには、その圧縮形式を理解できるコーデックが存在しなければならないからです。 つまり、ある環境からダウンロードしたAVIファイルが、 システムに既定で存在しないコーデックを利用して圧縮されている場合、 ダウンロード先のPCではそのAVIファイルを再生できない可能性があります。 特にビデオコーデックは、圧縮率の優れたものがインターネット上で公開されていることもあり、 システムに既定で存在しないコーデックが利用されている可能性は非常に高いといえます。 こうしたことから、特定のコーデックがシステムに存在するかを調べる技術は重要といえます。

アプリケーションからビデオコーデックにアクセスするためには、 VCMの関数を呼び出すことになります。 次に示すICOpenを呼び出せば、指定したコーデックのハンドルを取得することができます。

HIC ICOpen(
  DWORD fccType,    
  DWORD fccHandler, 
  UINT wMode        
);

fccTypeは、ICTYPE_VIDEOを指定します。 fccHandlerは、オープンしたいコーデックのFORCCを指定します。 コーデックをどのように使用するかを表す定数を指定します。 戻り値は、コーデックを表すハンドルになります。 これがNULLである場合は、指定したコーデックがシステムに存在しないことを意味します。

コーデックに関する情報は、ICGetInfoで取得することができます。

LRESULT ICGetInfo(
  HIC hic,           
  ICINFO * lpicinfo, 
  DWORD cb           
);

hicは、コーデックのハンドルを指定します。 lpicinfoは、ICINFO構造体のアドレスを指定します。 cbは、lpicinfoのサイズを指定します。 ICINFO構造体は、次のように定義されています。

typedef struct { 
  DWORD dwSize; 
  DWORD fccType; 
  DWORD fccHandler; 
  DWORD dwFlags; 
  DWORD dwVersion; 
  DWORD dwVersionICM; 
  WCHAR szName[16]; 
  WCHAR szDescription[128]; 
  WCHAR szDriver[128]; 
} ICINFO; 

dwSizeは、構造体のサイズが格納されます。 fccTypeは、ICTYPE_VIDEOが格納されます。 fccHandlerは、コーデックのFOURCCが格納されます。 dwFlagsは、コーデックがサポートする機能を表す定数が格納されます。 dwVersionは、コーデックのバージョンが格納されます。 dwVersionICMは、ICVERSIONが格納されます。 szNameは、コーデックの短い名前が格納されます。 szDescriptionは、コーデックの長い名前が格納されます。 szDriverは、コーデックのファイルパスが格納されます。

不要になったコーデックのハンドルは、ICCloseで解放することになります。

LRESULT ICClose(
  HIC hic
);

hicは、コーデックのハンドルを指定します。

ICOpenはコーデックの存在を確認するために呼び出すことができますが、 確認後にコーデックのハンドルを必要としないのであれば、 ICInfoによる確認の方が効率的ともいえます。

BOOL ICInfo(
  DWORD fccType,    
  DWORD fccHandler, 
  ICINFO * lpicinfo 
);

fccTypeは、ICTYPE_VIDEOを指定します。 fccHandlerは、コーデックのFOURCCを指定します。 lpicinfoは、ICINFO構造体のアドレスを指定します。 ICInfoで取得したICINFO構造体では、 szNameとszDescriptionが初期化されないことになっています。

今回のプログラムは、AVIファイルから映像の圧縮に使用されたコーデックのFORCCを取得し、 そのコーデックが存在するかをICOpenで調べます。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HIC           hic;
	ICINFO        icinfo;
	PAVISTREAM    pavi;
	AVISTREAMINFO si;

	AVIFileInit();
	
	if (AVIStreamOpenFromFile(&pavi, TEXT("sample.avi"), streamtypeVIDEO, 0, OF_READ, NULL) != 0) {
		AVIFileExit();
		return 0;
	}

	AVIStreamInfo(pavi, &si, sizeof(AVISTREAMINFO));

	hic = ICOpen(ICTYPE_VIDEO, si.fccHandler, ICMODE_QUERY);
	if (hic == NULL) {
		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);
		AVIStreamRelease(pavi);
		AVIFileExit();
		return 0;
	}

	ICGetInfo(hic, &icinfo, sizeof(ICINFO));
	MessageBoxW(NULL, icinfo.szDescription, L"OK", MB_OK);

	ICClose(hic);
	AVIStreamRelease(pavi);
	AVIFileExit();
	
	return 0;
}

映像の圧縮に使用されたコーデックを特定するためには、ストリームのハンドルが必要となるため、 AVIStreamOpenFromFileを呼び出すことになります。 今回は、ビデオストリームのハンドルが必要になるため、 第3引数にはstreamtypeVIDEOを指定します。 ストリームのハンドルを取得すれば、AVIStreamInfoを呼び出すことでAVISTREAMINFO.fccHandlerから 圧縮に使用されたコーデックを確認することができます。 よって、これをICOpenの第2引数に指定します。

hic = ICOpen(ICTYPE_VIDEO, si.fccHandler, ICMODE_QUERY);
if (hic == NULL) {
	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);
	AVIStreamRelease(pavi);
	AVIFileExit();
	return 0;
}

ICOpenの戻り値がNULLである場合、指定されたコーデックがシステムに存在しないことを意味しています。 よって、この場合はそのコーデックのFOURCCを表示するようにしています。 こうすることで、ユーザーはFOURCCを基にインターネット上で検索し、 必要なコーデックを何であるかを特定することができます。 ハンドルを取得した場合は、ICGetInfoで情報を取得するようにしているため、 ICOpenの第3引数にはICMODE_QUERYを指定しています。

ビデオコーデックの列挙

システムに存在するビデオコーデックは、ICInfoで列挙することができます。 この関数の第2引数にゼロベースのインデックスを指定すれば、 初期化されたICINFO構造体が返るため、関数が失敗するまでインデックスを加算して 呼び出せばよいことになります。

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

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

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   hwndListBoxLeft = NULL;
	static HWND   hwndListBoxRight = NULL;
	static FOURCC fcc[100] = {0};
	
	switch (uMsg) {

	case WM_CREATE: {
		int    i;
		HIC    hic;
		ICINFO icinfo;

		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		for (i = 0; i < 100; i++) {
			if (!ICInfo(ICTYPE_VIDEO, i, &icinfo))
				break;

			hic = ICOpen(ICTYPE_VIDEO, icinfo.fccHandler, ICMODE_QUERY);
			ICGetInfo(hic, &icinfo, sizeof(icinfo));
			ICClose(hic);
			
			fcc[i] = icinfo.fccHandler;

			SendMessageW(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)icinfo.szName);
		}

		return 0;
	}
	
	case WM_COMMAND: {
		int    nIndex;
		HIC    hic;
		ICINFO icinfo;
		LPSTR  lp;
		TCHAR  szBuf[256];
		
		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;
		
		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);

		nIndex = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);

		hic = ICOpen(ICTYPE_VIDEO, fcc[nIndex], ICMODE_QUERY);
		ICGetInfo(hic, &icinfo, sizeof(icinfo));
		ICClose(hic);

		lp = (LPSTR)&fcc[nIndex];
		wsprintf(szBuf, TEXT("FOURCC : %c%c%c%c"), lp[0], lp[1], lp[2], lp[3]);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)icinfo.szDescription);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)icinfo.szDriver);
		
		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 3, 0, LOWORD(lParam) - LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

想定するビデオコーデックの数は100として、for文を実行します。 ICInfoが成功した場合、ICGetInfoでICINFO構造体を取得していますが、 これは既にICInfoで取得したのではないでしょうか。 実はICInfoでは、szNameとszDescriptionが初期化されないことになっているため、 これらを参照する場合はICGetInfoを呼び出す必要があるのです。 WM_COMMANDでは静的変数に保存されたFORCCから、ICGetInfoを呼び出します。



戻る