EternalWindows
MP3 / ドライバの列挙

前節では、RIFF/WAVE形式のMP3ファイルのfmt チャンクからMP3のフォーマットを取得しました。 次にすべきことは、dataチャンクからデータを取得することですが、 このデータがMP3の規格に基づいて圧縮されていることを忘れてはいけません。 Windowsにおける最も低水準な音声再生の関数は、waveOutで始まる関数群です。 この関数が受け取るのは圧縮されていない生の波形データだけであるため、 MP3に限らず、OGGやその他の規格で圧縮されたデータを直接指定することはできません。 つまり、圧縮されたデータを再生するとは、まずそのデータを適切に解凍し、 その後にwaveOutの関数を呼び出すことを意味しているのです。

多くのアプリケーションは、データの圧縮/解凍ためのアルゴリズムを独自に記述するよりも、 効率性を求めてコーデック(compressor/decompressor)の利用を好んでいます。 コーデックとは、ある音声や動画の規格における圧縮/解凍のためのプログラムのことで、 たとえば、システムにMP3のコーデックがインストールされているのであれば、 そのコーデックにMP3のデータを指定するだけで、データの解凍を実現することができます。 これにより、アプリケーションは圧縮/解凍ためのアルゴリズムに煩わせられることがなく、 特定の規格をサポートするコーデックを選択するだけで済むことになります。

コーデックは主として、オーディオコーデックとビデオコーデックに分かれますが、 このうちオーディオコーデックは、ACM(Audio Compression Manager)によって管理されています。 ACMはmsacm32.dllとして存在し、ACMドライバ(オーディオコーデック)の列挙や詳細の取得、 さらにドライバハンドルによって各ドライバを統一的に扱うための関数を提供し、 アプリケーションからドライバへの中継として働きます。 今回はまず、システムにインストールされているドライバを列挙し、 ACMの関数の使い方に慣れることにしましょう。 次に示すacmDriverEnumは、ドライバを列挙します。

MMRESULT acmDriverEnum(
  ACMDRIVERENUMCB fnCallback,  
  DWORD_PTR       dwInstance,  
  DWORD           fdwEnum      
);

ACMの関数はacmというプレフィックスを持ち、関数の成功時には0を返すようになっています。 fnCallbackは、アプリケーション定義コールバック関数のアドレスを指定します。 このコールバック関数はドライバが存在するまで、またはFALSEを返すまで呼ばれ続け、 関数の引数からドライバ識別子のハンドルを得ることができます。 dwInstanceは、コールバック関数の第2引数に渡したいデータを指定します。 fdwEnumは、ドライバの列挙に関するフラグを指定します。 次の2つの定数が定義されています。

定数 意味
ACM_DRIVERENUMF_DISABLED 無効になっているドライバも含めて列挙する。 ドライバの無効はacmDriverPriorityにACM_DRIVERPRIORITYF_DISABLEを 指定することで可能で、列挙したドライバが実際に無効になっているかどうかは、 コールバック関数のfdwSupportにACMDRIVERDETAILS_SUPPORTF_DISABLEDが 含まれているかどうかで調べることができる。
ACM_DRIVERENUMF_NOLOCAL グローバルドライバのみを列挙する。 アプリケーションがacmDriverAddで追加したドライバはローカルドライバとして扱われ、 他のアプリケーションは利用することができない。

続いて、コールバック関数のプロトタイプについて確認します。 この関数名は、acmDriverEnumCallbackとなっていますが、 実際には自由に命名することができます。

BOOL ACMDRIVERENUMCB acmDriverEnumCallback(
  HACMDRIVERID hadid,     
  DWORD_PTR    dwInstance,
  DWORD        fdwSupport 
);

hadidは、ドライバ識別子のハンドルが格納されています。 dwInstanceは、acmDriverEnumの第2引数に指定した値が格納されます。 fdwSupportはドライバサポートフラグで、 多くのドライバはACMDRIVERDETAILS_SUPPORTF_CODECというフラグのみを含んでいます。 このフラグは、2つの異なるフォーマットタグ間の変換をサポートするという意味です。

ドライバ識別子のハンドルを取得したら、それを基に様々な操作を行うことができます。 たとえば、acmDriverDetailsはドライバに関する詳細を取得します。

MMRESULT acmDriverDetails(
  HACMDRIVERID hadid,       
  LPACMDRIVERDETAILS padd,  
  DWORD fdwDetails          
);

hadidは、ドライバ識別子のハンドルを指定します。 paddは、ACMDRIVERDETAILS構造体のアドレスを指定します。 fdwDetailsは、予約されているため0を指定します。

今回のプログラムは、acmDriverEnumで列挙したドライバの識別子から acmDriverDetailsを呼び出してドライバ名を取得し、それをリストボックスへ順に追加します。

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

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

BOOL CALLBACK acmDriverEnumCallback(HACMDRIVERID hdriverId, DWORD dwInstance, DWORD dwSupport);
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:
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);	
		acmDriverEnum((ACMDRIVERENUMCB)acmDriverEnumCallback, (DWORD)hwndListBox, 0);
		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 CALLBACK acmDriverEnumCallback(HACMDRIVERID hdriverId, DWORD dwInstance, DWORD dwSupport)
{
	ACMDRIVERDETAILS driverDetails;
	
	driverDetails.cbStruct = sizeof(ACMDRIVERDETAILS);
	acmDriverDetails(hdriverId, &driverDetails, 0);

	SendMessage((HWND)dwInstance, LB_ADDSTRING, 0, (LPARAM)driverDetails.szShortName);

	return TRUE;
}

ACMの関数を呼び出すには、msacm32.libのリンクとmsacm.hのインクルードが必要です。 このヘッダーファイルはmmreg.hを事前にインクルードしていないとエラーを出すため、 mmreg.hもインクルードすることになります。 また、ACMで利用する構造体の中にはメンバとして文字列を含むものがありますが、 これはヘッダーファイル内でANSIとUNICODEの変換が適切に行われています。 マイクロソフトのリファレンスでchar型の文字列として定義されていても、 実際にはUNICODEとしてコンパイルしても文字列は扱うことができるので注意してください。 以上の事を踏まえたうえで、ACMのプログラミングに入りたいと思います。

ドライバ識別子ハンドルが手に入るのがコールバック関数であることから、 基本的にドライバの操作はコールバック関数で行うことになります。 このため、必要となるデータはコールバック関数に渡しておかなければなりません。 次のコードは、WM_CREATEの一部です。

acmDriverEnum((ACMDRIVERENUMCB)acmDriverEnumCallback, (DWORD)hwndListBox, 0);

コールバック関数であるacmDriverEnumCallbackでは、リストボックスに文字列を追加しますから、 acmDriverEnumの第2引数にはリストボックスのハンドルを指定します。 列挙するドライバを制限するつもりがない場合は第3引数は0で構いません。

BOOL CALLBACK acmDriverEnumCallback(HACMDRIVERID hdriverId, DWORD dwInstance, DWORD dwSupport)
{
	ACMDRIVERDETAILS driverDetails;
	
	driverDetails.cbStruct = sizeof(ACMDRIVERDETAILS);
	acmDriverDetails(hdriverId, &driverDetails, 0);

	SendMessage((HWND)dwInstance, LB_ADDSTRING, 0, (LPARAM)driverDetails.szShortName);

	return TRUE;
}

acmDriverDetailsを呼び出す場合は、予めACMDRIVERDETAILS構造体のcbStructメンバを 構造体のサイズで初期化しておくことになります。 szShortNameメンバはドライバの名前を簡単に記述した文字列が格納されており、 ここではこれをリストボックスに追加しています。 ACMDRIVERDETAILS構造体の他のメンバについては次節で扱います。


戻る