EternalWindows
MP3 / フォーマットの列挙

MP3からWAVEへの変換を行うにあたって、アプリケーションがどこまで その動作に介入すべきなのかは検討しておくべきことです。 ACM関数のドライバハンドルにNULLが指定されていた場合、 その動作を適切に行うドライバはACMによって選択されますから、 アプリケーションがドライバを明示的に選択する必要性はそれほど多くはありません。 しかし、ドライバが実際に行う適切な操作というのは、 ときとしてアプリケーションに期待に沿わないことがあり、 特にフォーマットの初期化に関しては、安易にドライバを利用するわけにはいきません。 ここでいうフォーマットとは、MP3データをWAVEデータに変換する際に指定するフォーマットで、 WAVEデータの構成がそれを表すフォーマットのチャンネルやサンプリングレートの値に 依存することから、変換後のWAVEフォーマットは前もって指定することになるのです。

変換後のWAVEフォーマットをドライバを介して初期化するといっても、 単にMP3を再生するということにおいては特に問題が発生することはありません。 ただ、特定のWAVEフォーマットに変換したいような場合は、 そのフォーマットがドライバが選択するフォーマットと一致するとは限りませんし、 変換後のWAVEデータのサイズを少しでも抑えたい場合は、 ステレオや16ビットのフォーマットは避けたいところでもあるはずです。 しかし、アプリケーションが自ら希望するフォーマットを指定するといっても、 そのフォーマットへの変換をドライバが成功させるかどうかは分かりませんから、 ドライバがサポートできるフォーマットを列挙し、その中の最適だと思うフォーマットを アプリケーションが選択するのが最も確実だといえるでしょう。 次に示すacmFormatEnumは、指定されたフォーマットから変換できる 変換先フォーマットを列挙します。

MMRESULT acmFormatEnum(
  HACMDRIVER         had,        
  LPACMFORMATDETAILS pafd,       
  ACMFORMATENUMCB    fnCallback, 
  DWORD_PTR          dwInstance, 
  DWORD              fdwEnum     
);

hadは、ドライバハンドルを指定します。 pafdは、ACMFORMATDETAILS構造体のアドレスを指定します。 fnCallbackは、コールバック関数のアドレスを指定します。 dwInstanceは、コールバック関数に渡したいデータを指定します。 fdwEnumは、列挙するフォーマットについてのフラグを指定します。 よく使われる思われる定数を次に示します。

定数 意味
ACM_FORMATENUMF_CONVERT pwfxに指定したフォーマットから変換できるフォーマットを列挙する。 pwfxの一部のメンバに準拠するフォーマットを列挙したい場合は、 たとえば、それがサンプリングレートであるならば、 ACM_FORMATENUMF_NSAMPLESPERSECを指定することになる。
ACM_FORMATENUMF_SUGGEST pwfxに指定したフォーマットから変換できるフォーマットのうち、 ドライバによって推奨されるものが列挙される。 ただし、このフォーマットは必ずしもアプリケーションにとって最善で あるとは限らないため、利用するときには注意しなければならない。 なお、このフラグを指定したときの効果はacmFormatSuggestで得ることも できる。

acmFormatEnumでACMFORMATDETAILS構造体を利用する場合は、 pwfxメンバに変換元となるフォーマットを指定することになります。 これは、基本的にファイルから取得したMPEGLAYER3WAVEFORMAT構造体を指定すれば十分ですが、 cbwfxにはMPEGLAYER3WAVEFORMAT構造体のサイズを指定するわけにはいきません。 確かにこのメンバは文字通り、pwfxメンバのサイズを表すものなのですが、 それはシステムに定義されている最大サイズのフォーマットを下回ってはならないのです。 フォーマットの最大サイズは、acmMetricsがサポートするデータの1つとして提供されています。

MMRESULT acmMetrics(
  HACMOBJ hao,   
  UINT uMetric,  
  LPVOID pMetric 
);

haoは、ACMオブジェクトのハンドルを指定します。 ACMオブジェクトには、HACMDRIVERやHACMDRIVERID、HACMSTREAMなどがありますが、 多くの場合はNULLを指定することができます。 uMetricは、ACMまたはACMオブジェクトから取得したいデータを表すフラグを指定します。 pMetricは、uMetricで表されるデータを取得するためのバッファを指定します。 主なuMetricの定数を次に示します。

定数 意味
ACM_METRIC_COUNT_DRIVERS 有効となっているACMドライバの数が返る。
ACM_METRIC_DRIVER_PRIORITY ドライバの優先順位を変更する。 haoはドライバ識別子のハンドルを指定しなければならない。
ACM_METRIC_MAX_SIZE_FORMAT 最大のWAVEFORMATEX構造体のサイズを取得する。 haoにドライバハンドルやドライバ識別子のハンドルを指定した場合は、 そのドライバに対する最大のWAVEFORMATEX構造体のサイズが返る。

今回のプログラムは、RIFF/WAVE形式のMP3ファイルからMPEGLAYER3WAVEFORMAT構造体を取得し、 それを基にacmFormatEnumで変換先フォーマットを列挙します。

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

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

BOOL CALLBACK acmFormatEnumCallback(HACMDRIVERID hdriverId, LPACMFORMATDETAILS lpFormatDetails, DWORD dwInstance, DWORD dwSupport);
BOOL ReadMP3File(LPTSTR lpszFileName, LPMPEGLAYER3WAVEFORMAT lpmf);
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: {
		ACMFORMATDETAILS     formatDetails;
		MPEGLAYER3WAVEFORMAT mf;
		
		if (!ReadMP3File(TEXT("sample.wav"), &mf))
			return -1;
		
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);	
		
		formatDetails.cbStruct   = sizeof(ACMFORMATDETAILS);
		formatDetails.fdwSupport = 0;
		formatDetails.pwfx       = (LPWAVEFORMATEX)&mf;
		acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &formatDetails.cbwfx);

		acmFormatEnum(NULL, &formatDetails, (ACMFORMATENUMCB)acmFormatEnumCallback, (DWORD)hwndListBox, ACM_FORMATENUMF_CONVERT);//ACM_FORMATENUMF_SUGGEST
	
		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 acmFormatEnumCallback(HACMDRIVERID hdriverId, LPACMFORMATDETAILS lpFormatDetails, DWORD dwInstance, DWORD dwSupport)
{
	SendMessage((HWND)dwInstance, LB_ADDSTRING, 0, (LPARAM)lpFormatDetails->szFormat);

	return TRUE;
}

BOOL ReadMP3File(LPTSTR lpszFileName, LPMPEGLAYER3WAVEFORMAT lpmf)
{
	HMMIO    hmmio;
	MMRESULT mmr;
	MMCKINFO mmckRiff;
	MMCKINFO mmckFmt;
	
	hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ);
	if (hmmio == NULL) {
		MessageBox(NULL, TEXT("ファイルのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}
	
	mmckRiff.fccType = mmioStringToFOURCC(TEXT("WAVE"), 0);
	mmr = mmioDescend(hmmio, &mmckRiff, NULL, MMIO_FINDRIFF);
	if (mmr != MMSYSERR_NOERROR) {
		mmioClose(hmmio, 0);
		return FALSE;
	}
	
	mmckFmt.ckid = mmioStringToFOURCC(TEXT("fmt "), 0);
	mmioDescend(hmmio, &mmckFmt, &mmckRiff, MMIO_FINDCHUNK);
	mmioRead(hmmio, (HPSTR)lpmf, mmckFmt.cksize);
	mmioAscend(hmmio, &mmckFmt, 0);
	if (lpmf->wfx.wFormatTag != WAVE_FORMAT_MPEGLAYER3) {
		MessageBox(NULL, TEXT("RIFF/WAVE形式のMP3ファイルではありません。"), NULL, MB_ICONWARNING);
		mmioClose(hmmio, 0);
		return FALSE;
	}

	mmioAscend(hmmio, &mmckRiff, 0);
	mmioClose(hmmio, 0);

	return TRUE;
}

acmFormatEnumは、変換先フォーマットが存在する限り、 またはコールバック関数がFALSEを返すまでコールバック関数を呼び続けます。 第2引数のlpFormatDetailsのpwfxが変換先フォーマットを表しているため、 必要に応じてこれをコピーすることになるでしょう。 szFormatは文字列化したフォーマットを格納しているため、 この文字列を表示すればフォーマットのチャンネルやビット数を確認することができます。

標準フォーマット

あるフォーマットに対して変換可能なフォーマットを列挙する場合は、 acmFormatEnumを呼び出すことになりますが、任意のフォーマットタグにおいて 任意のドライバが定義している標準フォーマットを列挙したいような場合は、 acmFormatTagDetailsとacmFormatDetailsを巧みに呼び出すことになります。

void ShowStandardFormats(HACMDRIVER hdriver, DWORD dwFormatTag)
{
	DWORD               i;
	WAVEFORMATEX        wf;
	ACMFORMATDETAILS    formatDetails;
	ACMFORMATTAGDETAILS tagDetails;

	tagDetails.cbStruct    = sizeof(ACMFORMATTAGDETAILS);
	tagDetails.fdwSupport  = 0;
	tagDetails.dwFormatTag = dwFormatTag;
	acmFormatTagDetails(hdriver, &tagDetails, ACM_FORMATTAGDETAILSF_FORMATTAG);

	for (i = 0; i < tagDetails.cStandardFormats; i++) {
		formatDetails.cbStruct      = sizeof(ACMFORMATDETAILS);
		formatDetails.dwFormatIndex = i;
		formatDetails.dwFormatTag   = dwFormatTag;
		formatDetails.fdwSupport    = 0;
		formatDetails.pwfx          = &wf;
		acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &formatDetails.cbwfx); 

		acmFormatDetails(hdriver, &formatDetails, ACM_FORMATDETAILSF_INDEX);
		MessageBoxA(NULL, formatDetails.szFormat, "OK", MB_OK);
	}
}

標準フォーマットの数は、ACMFORMATDETAILS構造体のcStandardFormatsメンバに格納されます。 この値が必要なのは、acmFormatDetailsでACM_FORMATDETAILSF_INDEXを指定するからであり、 ACMFORMATDETAILS構造体のdwFormatIndexメンバをインデックスとして利用するためでもあります。 関数が成功すると、pwfxメンバとszFormatメンバから標準フォーマットを参照することができます。



戻る