EternalWindows
MP3 / MP3の再生

マルチメディアアプリケーションでは、WAVEフォーマットとWAVEデータさえ取得できれば、 もはやそれが何らかの変換処理を経たかどうかは重要な意味を持ちません。 通常のWAVEプログラミング同様に、WAVEフォーマットをwaveOutOpenに指定し、 WAVEデータをwaveOutWriteに指定すれば、それで音は再生されることになるでしょう。 ユーザーからはあたかもMP3を再生しているように思えますが、 実際にはM3データを変換したWAVEデータを再生しているのです。

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

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

BOOL DecodeToWave(LPWAVEFORMATEX lpwfSrc, LPBYTE lpSrcData, DWORD dwSrcSize, LPWAVEFORMATEX lpwfDest, LPBYTE *lplpDestData, LPDWORD lpdwDestSize);
BOOL ReadMP3File(LPTSTR lpszFileName, LPMPEGLAYER3WAVEFORMAT lpmf, LPBYTE *lplpData, LPDWORD lpdwSize);
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 LPBYTE   lpWaveData = NULL;
	static HWAVEOUT hwo = NULL;
	static WAVEHDR  wh = {0};

	switch (uMsg) {

	case WM_CREATE: {
		DWORD                dwMP3Size;
		DWORD                dwWaveSize;
		LPBYTE               lpMP3Data;
		LPBYTE               lpWaveData;
		WAVEFORMATEX         wf;
		MPEGLAYER3WAVEFORMAT mf;

		if (!ReadMP3File(TEXT("sample.wav"), &mf, &lpMP3Data, &dwMP3Size))
			return -1;

		if (!DecodeToWave((LPWAVEFORMATEX)&mf, lpMP3Data, dwMP3Size, &wf, &lpWaveData, &dwWaveSize)) {
			HeapFree(GetProcessHeap(), 0, lpMP3Data);
			return -1;
		}
		
		HeapFree(GetProcessHeap(), 0, lpMP3Data);

		if (waveOutOpen(&hwo, WAVE_MAPPER, &wf, (DWORD_PTR)hwnd, 0, CALLBACK_WINDOW) != MMSYSERR_NOERROR)
			return -1;
		
		wh.lpData         = (LPSTR)lpWaveData;
		wh.dwBufferLength = dwWaveSize;
		wh.dwFlags        = 0;

		waveOutPrepareHeader(hwo, &wh, sizeof(WAVEHDR));
		waveOutWrite(hwo, &wh, sizeof(WAVEHDR));
		
		return 0;
	}
	
	case MM_WOM_DONE:
		waveOutWrite((HWAVEOUT)wParam, (LPWAVEHDR)lParam, sizeof(WAVEHDR));
		return 0;

	case WM_DESTROY:
		if (hwo != NULL) {
			waveOutReset(hwo);
			waveOutUnprepareHeader(hwo, &wh, sizeof(WAVEHDR));
			waveOutClose(hwo);
		}

		if (lpWaveData != NULL)
			HeapFree(GetProcessHeap(), 0, lpWaveData);

		PostQuitMessage(0);

		return 0;

	default:
		break;

	}

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

BOOL DecodeToWave(LPWAVEFORMATEX lpwfSrc, LPBYTE lpSrcData, DWORD dwSrcSize, LPWAVEFORMATEX lpwfDest, LPBYTE *lplpDestData, LPDWORD lpdwDestSize)
{
	HACMSTREAM      has;
	ACMSTREAMHEADER ash;
	LPBYTE          lpDestData;
	DWORD           dwDestSize;
	BOOL            bResult;
	
	lpwfDest->wFormatTag = WAVE_FORMAT_PCM;
	acmFormatSuggest(NULL, lpwfSrc, lpwfDest, sizeof(WAVEFORMATEX), ACM_FORMATSUGGESTF_WFORMATTAG);
	
	if (acmStreamOpen(&has, NULL, lpwfSrc, lpwfDest, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME) != 0) {
		MessageBox(NULL, TEXT("変換ストリームのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}

	acmStreamSize(has, dwSrcSize, &dwDestSize, ACM_STREAMSIZEF_SOURCE);
	lpDestData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDestSize);

	ZeroMemory(&ash, sizeof(ACMSTREAMHEADER));
	ash.cbStruct    = sizeof(ACMSTREAMHEADER);
	ash.pbSrc       = lpSrcData;
	ash.cbSrcLength = dwSrcSize;
	ash.pbDst       = lpDestData;
	ash.cbDstLength = dwDestSize;

	acmStreamPrepareHeader(has, &ash, 0);
	bResult = acmStreamConvert(has, &ash, 0) == 0;
	acmStreamUnprepareHeader(has, &ash, 0);
	
	acmStreamClose(has, 0);

	if (bResult) {
		*lplpDestData = lpDestData;
		*lpdwDestSize = ash.cbDstLengthUsed;
	}
	else {
		MessageBox(NULL, TEXT("変換に失敗しました。"), NULL, MB_ICONWARNING);
		*lplpDestData = NULL;
		*lpdwDestSize = 0;
		HeapFree(GetProcessHeap(), 0, lpDestData);
	}

	return bResult;
}

BOOL ReadMP3File(LPTSTR lpszFileName, LPMPEGLAYER3WAVEFORMAT lpmf, LPBYTE *lplpData, LPDWORD lpdwSize)
{
	HMMIO    hmmio;
	MMRESULT mmr;
	MMCKINFO mmckRiff;
	MMCKINFO mmckFmt;
	MMCKINFO mmckData;
	
	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;
	}

	mmckData.ckid = mmioStringToFOURCC(TEXT("data"), 0);
	mmioDescend(hmmio, &mmckData, &mmckRiff, MMIO_FINDCHUNK);
	*lplpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, mmckData.cksize);
	mmioRead(hmmio, (HPSTR)*lplpData, mmckData.cksize);
	mmioAscend(hmmio, &mmckData, 0);

	mmioAscend(hmmio, &mmckRiff, 0);
	mmioClose(hmmio, 0);
	
	*lpdwSize = mmckData.cksize;

	return TRUE;
}

DecodeToWaveでWAVEフォーマットとWAVEデータを取得し、 不要となったMP3データを開放すれば、後は通常のWAVEプログラミングと同一となります。 このプログラムにストリーミング再生や波形データの編集などの機能拡張を加えるなら、 それはもはやWAVEプログラミングの一部であり、MP3とは別の問題といえるでしょう。 多種多様な音声形式を再生する場合は、ACMや圧縮アルゴリズムを理解する以外に、 WAVEプログラミングに対する深い知識も必要だといえます。


戻る