EternalWindows
MP3 / フォーマットタグ

あるドライバが特定の音声方式を実装しているかどうかは、 それを示すフォーマットタグがサポートされているかどうかで決定します。 既にご存知の通り、マルチメディアAPIの世界における音声方式の表現はWAVE_FORMAT_XXXという形のフォーマットタグによって行われ、 これをドライバがサポートしているかどうか問い合わせることになります。 次に示すacmFormatTagDetailsは、フォーマットタグの詳細を受け取ると共に、 関数の成否でフォーマットタグがサポートされているどうかを特定できます。

MMRESULT acmFormatTagDetails(
  HACMDRIVER had,               
  LPACMFORMATTAGDETAILS paftd,  
  DWORD fdwDetails              
);

hadは、ドライバハンドルを指定します。 paftdは、フォーマットタグの詳細を表すACMFORMATTAGDETAILS構造体のアドレスを指定します。 fdwDetailsは、詳細の取得方法を示すフラグを指定します。

次のコードは、MP3のドライバがインストールされているかどうかを調べる例を示しています。 任意のドライバがMP3のドライバであるかを調べる場合は、 acmFormatTagDetailsの第1引数にドライバハンドルを指定してください。

ACMFORMATTAGDETAILS tagDetails;

tagDetails.cbStruct    = sizeof(ACMFORMATTAGDETAILS);
tagDetails.fdwSupport  = 0;
tagDetails.dwFormatTag = WAVE_FORMAT_MPEGLAYER3;

if (acmFormatTagDetails(NULL, &tagDetails, ACM_FORMATTAGDETAILSF_FORMATTAG) == 0)
	MessageBox(NULL, tagDetails.szFormatTag, TEXT("OK"), MB_OK);
else
	MessageBox(NULL, TEXT("このフォーマットタグはサポートされていません。"), NULL, MB_ICONWARNING);

acmFormatTagDetailsを呼び出すには、ACMFORMATTAGDETAILS構造体を適切に初期化します。 cbStructは構造体のサイズを格納し、fdwSupportには0を代入します。 acmFormatTagDetailsの第3引数にACM_FORMATTAGDETAILSF_FORMATTAGを指定した場合、 dwFormatTagメンバ経由でフォーマットタグを指定することになるため、 ここにサポートされているかどうかを確認したいフォーマットタグを指定することになります。 acmFormatTagDetailsは成功時にszFormatTagメンバを初期化することになっているため、 文字列化したフォーマットを取得するために呼び出すのも候補といえます。

ACMを利用するプログラムでは、ドライバを表すのに2つ型を使うことになります。 1つは、ドライバ識別子のハンドルと呼ばれるHACMDRIVERID型で、 ACM内におけるドライバの統計的な情報へのアクセスに利用されます。 もう1つは、ドライバハンドルと呼ばれるHACMDRIVER型で、 こちらはドライバとの通信を行うために利用されることになります。 ドライバとの通信というのは、たとえばドライバがサポートしているフォーマットや、 フォーマットタグなどを問い合わすことで、もしハンドルにNULLが指定されていた場合は、 ACMが最適なドライバを選択してくれることになっています。 この機能によりアプリケーションは、目的の操作をサポートするドライバを明示的に取得する 必要がなくなるのですが、個々のドライバの能力を予め知っておくためにも、 ドライバハンドルを利用したプログラムに慣れておいても損はありません。 次に示すacmDriverOpenは、ドライバ識別子のハンドルからドライバハンドルを取得します。

MMRESULT acmDriverOpen(
  LPHACMDRIVER phad,  
  HACMDRIVERID hadid, 
  DWORD fdwOpen       
);

phadは、ドライバハンドルのアドレスを指定します。 関数が成功すると、このアドレスにドライバハンドルが格納されます。 hadidは、ドライバ識別子のハンドルを指定します。 fdwOpenは予約されているため、0を指定することになります。 ちなみに、ドライバハンドルからドライバハンドルの識別子を取得する場合は、 acmDriverIDを呼び出すことになります。

オープンしたドライバハンドルは、使い終えたらacmDriverCloseでクローズします。

MMRESULT acmDriverClose(
  HACMDRIVER had, 
  DWORD fdwClose  
);

hadは、ドライバハンドルを指定します。 fdwCloseは予約されているため、0を指定することになります。

今回のプログラムは、2つのリストボックスにドライバの情報を表示します。 左のリストボックスには、前節同様にインストールされているドライバの名前となり、 そこで選択したドライバの詳細が右のリストボックスに表示されます。 また、ドライバによってはときとして独自のアイコンを持つことがあるため、 リストボックスの垂直位置を意図的にずらすことで、アイコンの表示領域を作っています。

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

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

HACMDRIVERID g_hdriverIds[100] = {NULL};

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  hwndListBoxLeft = NULL;
	static HWND  hwndListBoxRight = NULL;
	static HICON hicon = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		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);
		
		acmDriverEnum((ACMDRIVERENUMCB)acmDriverEnumCallback, (DWORD)hwndListBoxLeft, 0);
	
		return 0;
	}

	case WM_COMMAND: {
		int                 nIndex;
		RECT                rcIcon;
		DWORD               i, dwPriority;
		TCHAR               szBuf[256];
		HACMDRIVER          hdriver;
		ACMDRIVERDETAILS    driverDetails;
		ACMFORMATTAGDETAILS tagDetails;

		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;

		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);

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

		driverDetails.cbStruct = sizeof(ACMDRIVERDETAILS);
		acmDriverDetails(g_hdriverIds[nIndex], &driverDetails, 0);
		
		hicon = driverDetails.hicon;
		SetRect(&rcIcon, 0, 0, 32, 32);
		InvalidateRect(hwnd, &rcIcon, TRUE);
		
		wsprintf(szBuf, TEXT("Long Name : %s"), driverDetails.szLongName);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, TEXT("Copyright : %s"), driverDetails.szCopyright);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, TEXT("Licensing : %s"), driverDetails.szLicensing);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, TEXT("Feature : %s"), driverDetails.szFeatures);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, TEXT("Manufacturer identifier : %d"), driverDetails.wMid);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, TEXT("Product identifier : %d"), driverDetails.wPid);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, TEXT("Version : major %d, minor %d, build %d"), ((driverDetails.vdwDriver & 0xFF000000) >> 24), ((driverDetails.vdwDriver & 0x00FF0000) >> 16), driverDetails.vdwDriver & 0x0000FFFF);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		acmMetrics((HACMOBJ)g_hdriverIds[nIndex], ACM_METRIC_DRIVER_PRIORITY, &dwPriority);
		wsprintf(szBuf, TEXT("Priority : %d"), dwPriority);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)TEXT(""));

		wsprintf(szBuf, TEXT("Number of format tags support : %d"), driverDetails.cFormatTags);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		acmDriverOpen(&hdriver, g_hdriverIds[nIndex], 0);
		for (i = 0; i < driverDetails.cFormatTags; i++) {
			tagDetails.cbStruct         = sizeof(ACMFORMATTAGDETAILS);
			tagDetails.dwFormatTagIndex = i;
			tagDetails.dwFormatTag      = WAVE_FORMAT_UNKNOWN;
			tagDetails.fdwSupport       = 0;
			acmFormatTagDetails(hdriver, &tagDetails, ACM_FORMATTAGDETAILSF_INDEX);

			wsprintf(szBuf, TEXT("%#04x %s"), tagDetails.dwFormatTag, tagDetails.szFormatTag);
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}
		acmDriverClose(hdriver, 0);

		return 0;
	}
	
	case WM_PAINT: {
		HDC         hdc;
		PAINTSTRUCT ps;

		hdc = BeginPaint(hwnd, &ps);

		DrawIcon(hdc, 0, 0, hicon);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_SIZE: {
		int y  = 32;
		int cy = HIWORD(lParam) - y;

		MoveWindow(hwndListBoxLeft, 0, y, LOWORD(lParam) / 3, cy, TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 3, y, LOWORD(lParam) - LOWORD(lParam) / 3, cy, 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)
{
	static int       i = 0;
	ACMDRIVERDETAILS driverDetails;
	
	driverDetails.cbStruct = sizeof(ACMDRIVERDETAILS);
	acmDriverDetails(hdriverId, &driverDetails, 0);
	
	g_hdriverIds[i++] = hdriverId;

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

	return TRUE;
}

MPEG Layer-3 Codecを選択すると、 右のリストボックスにWAVE_FORMAT_MPEGLAYER3(0x55として定義)が表示されることが確認できます。 これはつまり、このドライバがMP3をサポートすることを意味します。 ただし、この場合のサポートというのは、必ずしも圧縮/解凍の両方が可能であることを意味するわけではありません。 実はMPEG Layer-3 Codecを名乗るドライバはシステムに2つ存在しており、 そのうち片方のl3codeca.acmはデータの解凍しか実現できないことになっています。 Windowsの既定の設定では、l3codeca.acmが使用されることになっているため、 圧縮も実現したい場合は下記レジストキーを通じて、設定を変更する必要があります。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Drivers32

上記キーのmsacm.l3acmというエントリには、既定でl3codeca.acmが設定されていますが、 これをl3codecp.acmに変更します。 Windows Vista以降ならば、既定でこのドライバはシステムに存在すると思われます。 今回のプログラムをl3codeca.acmで実行した場合と、 l3codecp.acmで実行した場合を見比べてみましょう。

l3codeca.acmの場合 l3codecp.acmの場合

左図は、レジストリの設定がl3codeca.acmになっているときのものですが、 Long Nameにdecode onlyとあるように、データの解凍しか実現できません。 一方、レジストリの設定がl3codecp.acmになっている場合は、 データの解凍が可能になります。

今回は、取得したドライバ識別子のハンドルをWindowProcで利用することになるため、 コールバック関数でハンドルを保存するためのhdriverIdsを初期化しています。 この配列の要素数が100なのは、事前にドライバの数が分からないためであり、 動的にメモリを確保したいときには次の方法でドライバの数を取得するとよいでしょう。

DWORD dwMetric;

acmMetrics(NULL, ACM_METRIC_COUNT_DRIVERS, (LPVOID)&dwMetric); // dwMetricにドライバの数が格納される

acmMetricsは、ACMまたはドライバから情報を取得する関数で、 第2引数にACM_METRIC_COUNT_DRIVERSを指定した場合は、ドライバの数が返ります。

WM_COMMANDでは、右のリストボックスを更新する処理を行うことになります。 基本的には、acmDriverDetailsで初期化したACMDRIVERDETAILS構造体のメンバを表示するだけですが、 vdwDriverメンバの部分は少々複雑なことになっています。 このメンバはドライバのバージョンを示すもので、個々のバイトそれぞれに意味があります。

0xXXXXXXXXXX = majorXX = minorXXXX = build

よって、論理積で適切な部分だけを残してシフトする必要があります。

各ドライバには、優先順位というものが設定されています。 優先順位が高いドライバは、明示的にドライバ識別子やハンドルを指定しなかった場合に、 優先的に参照されることになります。 優先順位の値はACMDRIVERDETAILS構造体に含まれていませんが、 次のようにして取得可能です。

acmMetrics((HACMOBJ)g_hdriverIds[nIndex], ACM_METRIC_DRIVER_PRIORITY, (LPVOID)&dwPriority);

acmMetricsの第2引数にACM_METRIC_DRIVER_PRIORITYを指定すれば、 第1引数で識別されるドライバの優先順位を取得できます。 優先順位を変更したい場合は、acmDriverPriorityを呼び出します。

最後にフォーマットタグを取得する部分を見てみます。

acmDriverOpen(&hdriver, hdriverIds[nSel], 0);
for (i = 0; i < driverDetails.cFormatTags; i++) {
	tagDetails.cbStruct         = sizeof(ACMFORMATTAGDETAILS);
	tagDetails.dwFormatTagIndex = i;
	tagDetails.dwFormatTag      = WAVE_FORMAT_UNKNOWN;
	tagDetails.fdwSupport       = 0;

	acmFormatTagDetails(hdriver, &tagDetails, ACM_FORMATTAGDETAILSF_INDEX);

	SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)tagDetails.szFormatTag);
}
acmDriverClose(hdriver, 0);

ACMDRIVERDETAILS構造体には、ドライバがサポートしているフォーマットタグ自体は含まれていませんが、 フォーマットタグの数はcFormatTagsに格納されています。 acmFormatTagDetailsの第3引数にACM_FORMATTAGDETAILSF_INDEXを指定した場合、 cFormatTags-1までの値をフォーマットタグのインデックスとして第1引数のドライバに問い合わせることができるので、 dwFormatTagIndexにインデックスを代入します。 dwFormatTagは、取得できるフォーマットタグが分からないためWAVE_FORMAT_UNKNOWNを指定し、 関数成功時には、指定したインデックスに対するフォーマットタグが格納されることになります。

フォーマットタグの列挙

acmFormatTagDetailsとACM_FORMATTAGDETAILSF_INDEXのペアを利用すれば、 ドライバがサポートしているフォーマットタグを列挙できますが、 フォーマットタグの列挙はこれ以外にもacmFormatTagEnumを呼び出す方法があります。 この関数のドライバハンドルの引数にNULLを指定した場合、 ドライバ全体においてサポートされているフォーマットタグを列挙することができるため、 どれだけのフォーマットタグが利用可能なのかを知る目安となります。 次に、acmFormatTagEnumの呼び出しの例を示します。

ACMFORMATTAGDETAILS tagDetails;

tagDetails.cbStruct   = sizeof(ACMFORMATTAGDETAILS);
tagDetails.fdwSupport = 0;
acmFormatTagEnum(NULL, &tagDetails, (ACMFORMATTAGENUMCB)acmFormatTagEnumCallback, (DWORD)hwndListBox, 0);

ACMFORMATTAGDETAILS構造体は、上記の2つのメンバは必ず初期化しなければなりません。 acmFormatTagEnumの第4引数はコールバック関数に渡すための値で、第5引数は常に0となります。 続いて、コールバック関数の実装例を示します。

BOOL CALLBACK acmFormatTagEnumCallback(HACMDRIVERID hdriverId, LPACMFORMATTAGDETAILS lpTagDetails, DWORD dwInstance, DWORD dwSupport)
{
	TCHAR szBuf[256];

	wsprintf(szBuf, TEXT("%#04x %s"), lpTagDetails->dwFormatTag, lpTagDetails->szFormatTag);
	SendMessage((HWND)dwInstance, LB_ADDSTRING, 0, (LPARAM)szBuf);
	
	return TRUE;
}

このコールバック関数の第1引数は、ドライバを特定するということにおいては、 あまり利用できないことに注意してください。 acmFormatTagEnumの第1引数にNULLを指定してした場合は、 あるフォーマットタグをサポートするドライバのうち、 最も適切なものがACMによって選択されるため、 実際に列挙されたフォーマットタグをサポートするドライバは、 第1引数で示されるドライバ以外にも存在する可能性があるのです。



戻る