EternalWindows
MP3 / WAVEへの変換

変換先となるフォーマットを取得することができれば、 それと変換元のフォーマットを基にすることで実際に変換を行うことができます。 次に示すacmStreamOpenは、変換対象となる2つのフォーマットを変換ストリームと見なし、 そこに具体的な変換方法を示すフラグを設定します。

MMRESULT acmStreamOpen(
  LPHACMSTREAM    phas,       
  HACMDRIVER      had,        
  LPWAVEFORMATEX  pwfxSrc,    
  LPWAVEFORMATEX  pwfxDst,    
  LPWAVEFILTER    pwfltr,     
  DWORD_PTR       dwCallback, 
  DWORD_PTR       dwInstance, 
  DWORD           fdwOpen     
);

phasは、新しく開いたストリームを受け取るハンドルを指定します。 hadは、ACMドライバのハンドルを指定します。 NULLを指定すると、ACMが適切なドライバを選択することになります。 pwfxSrcは、変換元フォーマットのアドレスを指定します。 pwfxDstは、変換先フォーマットのアドレスを指定します。 pwfltrは、フィルタ操作を行うための引数ですが、通常はNULLを指定します。 dwCallbackとdwInstanceは、ストリーム変換を非同期に行う場合に利用することになります。 fdwOpenにACM_STREAMOPENF_ASYNCを指定していない場合は、0を指定してください。

変換ストリームのハンドルを取得すれば、以降の変換に伴う関数には このハンドルを指定するだけでフォーマットやフラグを指定したことになります。 演奏データそのものが含まれていないのは、実際に変換を行うacmStreamConvertが、 データとそのサイズを構造体を通じて明示的に受け取るようになっているからであり、 これにより任意の部分から任意のサイズ分だけ変換できるような柔軟性が生じます。 acmStreamConvertを呼び出すためには、変換先データの推奨サイズをacmStreamSizeしておきます。

MMRESULT acmStreamSize(
  HACMSTREAM has,          
  DWORD cbInput,           
  LPDWORD pdwOutputBytes,  
  DWORD fdwSize            
);

hasは、変換ストリームのハンドルを指定します。 cbInputは、データのサイズを指定します。 fdwSizeにACM_STREAMSIZEF_SOURCEを指定した場合、 cbInputのサイズは変換元データのサイズと解釈され、 pdwOutputBytesに変換先のデータの推奨サイズが返ることになります。

これまでの処理を終えれば、後は変換元と変換先のデータとサイズで ACMSTREAMHEADER構造体を初期化し、acmStreamConvertを呼び出すことになります。 この関数は、ACMSTREAMHEADER構造体のpbDstメンバに変換したデータを格納するため、 事前に取得したサイズで初期化したバッファを指定しておくことになります。

MMRESULT acmStreamConvert(
  HACMSTREAM has,          
  LPACMSTREAMHEADER pash,  
  DWORD fdwConvert         
);

hasは、変換ストリームのハンドルを指定します。 pashは、ACMSTREAMHEADER構造体のアドレスを指定します。 fdwConvertは、変換に関するフラグですが0を指定して構いません。

少し長い話になりましたが、以上がACMによる変換の大まかな流れとなります。 実際には、これ以外にストリームヘッダの準備やストリームのクローズなどの作業が伴いますが、 それらは単なる約束事であり、深い意味は持ちません。 あくまで重要なのは、acmStreamConvertを呼び出すまでに必要な 変換先のフォーマットやデータを格納するバッファは、 全てアプリケーションが用意するという点のみです。

今回のプログラムは、RIFF/WAVE形式のMP3ファイルをACMを介してWAVEに変換し、 その結果をカレントディレクトリにdecode.wavとして出力します。

#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);
void SaveWave(LPTSTR lpszFileName, LPWAVEFORMATEX lpwf, LPBYTE lpData, DWORD dwSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	DWORD                dwMP3Size;
	DWORD                dwWaveSize;
	LPBYTE               lpMP3Data;
	LPBYTE               lpWaveData;
	WAVEFORMATEX         wf;
	MPEGLAYER3WAVEFORMAT mf;

	if (!ReadMP3File(TEXT("sample.wav"), &mf, &lpMP3Data, &dwMP3Size))
		return 0;
	
	if (!DecodeToWave((LPWAVEFORMATEX)&mf, lpMP3Data, dwMP3Size, &wf, &lpWaveData, &dwWaveSize)) {
		HeapFree(GetProcessHeap(), 0, lpMP3Data);
		return 0;
	}
	
	SaveWave(TEXT("decode.wav"), &wf, lpWaveData, dwWaveSize);
	
	HeapFree(GetProcessHeap(), 0, lpMP3Data);
	HeapFree(GetProcessHeap(), 0, lpWaveData);

	MessageBox(NULL, TEXT("変換を終了しました。"), TEXT("OK"), MB_OK);
	
	return 0;
}

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;
}

void SaveWave(LPTSTR lpszFileName, LPWAVEFORMATEX lpwf, LPBYTE lpData, DWORD dwSize)
{
	HMMIO    hmmio;
	MMCKINFO mmckRiff;
	MMCKINFO mmckFmt;
	MMCKINFO mmckData;

	hmmio = mmioOpen(lpszFileName, NULL, MMIO_CREATE | MMIO_WRITE);
	
	mmckRiff.fccType = mmioStringToFOURCC(TEXT("WAVE"), 0);
	mmioCreateChunk(hmmio, &mmckRiff, MMIO_CREATERIFF);

	mmckFmt.ckid = mmioStringToFOURCC(TEXT("fmt "), 0);
	mmioCreateChunk(hmmio, &mmckFmt, 0);
	mmioWrite(hmmio, (char *)lpwf, sizeof(WAVEFORMATEX));
	mmioAscend(hmmio, &mmckFmt, 0);

	mmckData.ckid = mmioStringToFOURCC(TEXT("data"), 0);
	mmioCreateChunk(hmmio, &mmckData, 0);
	mmioWrite(hmmio, (char *)lpData, dwSize);
	mmioAscend(hmmio, &mmckData, 0);
	
	mmioAscend(hmmio, &mmckRiff, 0);
	mmioClose(hmmio, 0);
}

DecodeToWaveが今回の軸となる関数です。 この関数の第1引数から第3引数は変換元のフォーマット、データ、データのサイズとなり、 第4引数から第6引数は変換先のフォーマット、データ、データのサイズとなります。 変換先の引数では、データとサイズの部分に変数のアドレスを指定していますが、 これは変換で得られた値を呼び出し側に返すための仕組みです。 そのため、これらの変数は事前に初期化を必要としません。 変換先フォーマットに関しては本来ならば初期化しておくべきですが、 このプログラムでは簡単のため、その部分をDecodeToWaveの冒頭に含めています。

lpwfDest->wFormatTag = WAVE_FORMAT_PCM;
acmFormatSuggest(NULL, lpwfSrc, lpwfDest, sizeof(WAVEFORMATEX), ACM_FORMATSUGGESTF_WFORMATTAG);	

acmFormatSuggestは、第2引数に指定された変換元フォーマットから 最も適切なフォーマットを推測し、第3引数に格納します。 第5引数に、ACM_FORMATSUGGESTF_WFORMATTAGを指定した場合、 wFormatTagメンバを通じて変換のヒントを与えることになるため、 WAVE_FORMAT_PCMを指定することで、変換先のフォーマットはWAVEフォーマットと解釈できます。 ただし、前節で述べたように、このフォーマットはあくまでドライバが選択したものですから、 アプリケーションが期待するフォーマットと一致するとは限らないことに注意してください。 次に、acmStreamOpenを呼び出すコードを見てみます。

if (acmStreamOpen(&has, NULL, lpwfSrc, lpwfDest, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME) != 0) {
	MessageBox(NULL, TEXT("変換ストリームのオープンに失敗しました。"), NULL, MB_ICONWARNING);
	return FALSE;
}

それぞれ初期化された変換元、及び変換先のフォーマットを第3引数と第4引数に指定します。 第8引数のフラグは、変換を非同期に行うつもりがない場合は、 ACM_STREAMOPENF_NONREALTIMEを指定してください。 この関数が成功すれば、フォーマットの整合性とドライバの存在は保証されますから、 以降の変換に伴う関数の呼び出しは基本的には成功するはずです。 そういう意味でも、この関数の戻り値はきちんと調べておくことは重要です。

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

変換先データの推奨サイズを取得し、そのサイズ分バッファを確保します。 となるのは変換元のバッファのサイズですから、第2引数にそれを指定し、 第4引数にACM_STREAMSIZEF_SOURCEを指定します。

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);

実際に変換を開始するためのコードです。 ACMSTREAMHEADER構造体に変換元と変換先のデータ及びサイズをそれぞれ代入し、 cbStructを構造体のサイズで初期化することになります。 acmStreamConvertの前に呼び出しているacmStreamPrepareHeaderは、 変換元及び変換先のバッファの準備を行い、この準備が行われている場合のみ、 acmStreamConverは成功することになります。 変換が終了した場合は、準備を解除するためにacmStreamUnprepareHeaderを呼び出します。 acmStreamConvertは変換に成功したら0を返します。

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

変換に成功した場合は、lpDestDataを*lplpDestDataに指定することで、データを呼び出し側に返します。 *lpdwDestSizeにはデータのサイズを指定することになりますが、ここはdwDestSizeを指定するべきではありません。 dwDestSizeはバッファの推奨サイズであり、実際に変換に要したサイズはash.cbDstLengthUsedに格納されるからです。 cbDstLengthUsedは推奨サイズより小さくなる場合がありますが、逆に大きくなる場合はないはずです。 変換に失敗した場合は、lpDestDataを忘れずに開放しておきます。

DecodeToWaveが制御を返せば、取得したWAVEフォーマットとWAVEデータで、 通常通りのWAVEプログラミングが行えることになります。 たとえば、今回のようにWAVEファイルを作成することができますし、 waveOutOpenでWAVEデバイスを開いてwaveOutWriteで音を鳴らすこともできます。 このことから分かるように、ある規格で圧縮された音声ファイルを再生するとは、 その圧縮されたデータから如何にWAVEフォーマットとWAVEデータを取得するかであって、 圧縮形式は違えど、目指すゴールは常に同じになるのです。

基本的にACMは、圧縮されたデータの解凍という角度から取り上げられますが、 ACMにはこれ以外にも様々な使い道があります。 そもそも、ACMにおける圧縮/解凍というのは、 正確にはフォーマットタグレベルでの変換ですから、 変化先フォーマットのフォーマットタグを変換元と同一にすれば、 フォーマットの変更のみを行うことができるようになります。 これはつまり、既存のWAVEファイルのチャンネルやサンプリングレードを 自分が希望する値に変更できるということですから、 簡単なリサンプリングツールとして機能しているともいえるでしょう。 特別なドライバが必要でないというところも魅力的です。

WAVEからMP3への変換

今回のようなMP3からWAVEへの変換ではなく、WAVEからMP3への変換にもACMは使用できます。 ドライバの列挙で説明したように、MPEG Layer-3 codecをl3codeca.acmではなくl3codecp.acmに設定している場合は、 変換が可能になります。

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

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

WCHAR g_szDriverName[256];

BOOL ReadWaveFile(LPTSTR lpszFileName, LPWAVEFORMATEX lpwf, LPBYTE *lplpData, LPDWORD lpdwDataSize);
BOOL EncodeToMp3(LPWAVEFORMATEX lpwfSrc, LPBYTE lpSrcData, DWORD dwSrcSize, LPWAVEFORMATEX lpwfDest, LPBYTE *lplpDestData, LPDWORD lpdwDestSize, BOOL bUISelect);
BOOL CALLBACK acmDriverEnumCallback(HACMDRIVERID hdriverId, DWORD dwInstance, DWORD dwSupport);
BOOL WriteRiffWaveMp3File(LPTSTR lpszFileName, LPMPEGLAYER3WAVEFORMAT lpmf, LPBYTE lpMp3Data, DWORD dwDataSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	DWORD                dwWaveSize;
	DWORD                dwMP3Size;
	LPBYTE               lpWaveData = NULL;
	LPBYTE               lpMP3Data = NULL;
	WAVEFORMATEX         wf;
	MPEGLAYER3WAVEFORMAT mf;
	BOOL                 bResult;

	if (!ReadWaveFile(TEXT("sample.wav"), &wf, &lpWaveData, &dwWaveSize))
		return 0;

	bResult = EncodeToMp3(&wf, lpWaveData, dwWaveSize, (LPWAVEFORMATEX)&mf, &lpMP3Data, &dwMP3Size, TRUE);
	if (bResult)
		MessageBox(NULL, TEXT("変換に成功しました"), TEXT("OK"), MB_OK);

	WriteRiffWaveMp3File(TEXT("sample-mp3.wav"), &mf, lpMP3Data, dwMP3Size);

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

	return 0;
}

BOOL EncodeToMp3(LPWAVEFORMATEX lpwfSrc, LPBYTE lpSrcData, DWORD dwSrcSize, LPWAVEFORMATEX lpwfDest, LPBYTE *lplpDestData, LPDWORD lpdwDestSize, BOOL bUISelect)
{
	HACMSTREAM      has;
	ACMSTREAMHEADER ash;
	HACMDRIVER      hdriver;
	HACMDRIVERID    hdriverId;
	LPBYTE          lpDestData;
	DWORD           dwDestSize;
	BOOL            bResult;

	lstrcpyW(g_szDriverName, L"MPEG Layer-3 Codec");
	acmDriverEnum((ACMDRIVERENUMCB)acmDriverEnumCallback, (DWORD)&hdriverId, 0);
	if (hdriverId == 0) {
		MessageBox(NULL, TEXT("ドライバ識別子の取得に失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}
	
	acmDriverOpen(&hdriver, hdriverId, 0);

	if (bUISelect) {
		WAVEFORMATEX    wfEnum;
		ACMFORMATCHOOSE formatChoose;

		wfEnum.wFormatTag = WAVE_FORMAT_MPEGLAYER3;
		wfEnum.nChannels = lpwfSrc->nChannels;
		wfEnum.nSamplesPerSec = lpwfSrc->nSamplesPerSec;

		ZeroMemory(&formatChoose, sizeof(ACMFORMATCHOOSE));
		formatChoose.cbStruct = sizeof(ACMFORMATCHOOSE);
		formatChoose.pwfx     = (LPWAVEFORMATEX)lpwfDest;
		formatChoose.cbwfx    = sizeof(MPEGLAYER3WAVEFORMAT);
		formatChoose.pszTitle = TEXT("MP3フォーマットを選択");
		formatChoose.fdwEnum  = ACM_FORMATENUMF_WFORMATTAG | ACM_FORMATENUMF_NCHANNELS | ACM_FORMATENUMF_NSAMPLESPERSEC;
		formatChoose.pwfxEnum = &wfEnum;

		if (acmFormatChoose(&formatChoose) != 0) {
			MessageBox(NULL, TEXT("フォーマットの選択に失敗しました。"), NULL, MB_ICONWARNING);
			acmDriverClose(hdriver, 0);
			return FALSE;
		}
	}
	else {
		lpwfDest->wFormatTag = WAVE_FORMAT_MPEGLAYER3;
		acmFormatSuggest(hdriver, lpwfSrc, lpwfDest, sizeof(MPEGLAYER3WAVEFORMAT), ACM_FORMATSUGGESTF_WFORMATTAG);
	}

	if (acmStreamOpen(&has, hdriver, lpwfSrc, lpwfDest, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME) != 0) {
		MessageBox(NULL, TEXT("変換ストリームのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		acmDriverClose(hdriver, 0);
		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);
	
	acmDriverClose(hdriver, 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 ReadWaveFile(LPTSTR lpszFileName, LPWAVEFORMATEX lpwf, LPBYTE *lplpData, LPDWORD lpdwDataSize)
{
	HMMIO    hmmio;
	MMCKINFO mmckRiff;
	MMCKINFO mmckFmt;
	MMCKINFO mmckData;
	LPBYTE   lpData;

	hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ);
	if (hmmio == NULL) {
		MessageBox(NULL, TEXT("ファイルのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}
	
	mmckRiff.fccType = mmioStringToFOURCC(TEXT("WAVE"), 0);
	if (mmioDescend(hmmio, &mmckRiff, NULL, MMIO_FINDRIFF) != MMSYSERR_NOERROR) {
		MessageBox(NULL, TEXT("WAVEファイルではありません。"), NULL, MB_ICONWARNING);
		mmioClose(hmmio, 0);
		return FALSE;
	}

	mmckFmt.ckid = mmioStringToFOURCC(TEXT("fmt "), 0);
	if (mmioDescend(hmmio, &mmckFmt, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR) {
		mmioClose(hmmio, 0);
		return FALSE;
	}
	mmioRead(hmmio, (HPSTR)lpwf, mmckFmt.cksize);
	mmioAscend(hmmio, &mmckFmt, 0);
	if (lpwf->wFormatTag != WAVE_FORMAT_PCM) {
		MessageBox(NULL, TEXT("PCMデータではありません。"), NULL, MB_ICONWARNING);
		mmioClose(hmmio, 0);
		return FALSE;
	}

	mmckData.ckid = mmioStringToFOURCC(TEXT("data"), 0);
	if (mmioDescend(hmmio, &mmckData, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR) {
		mmioClose(hmmio, 0);
		return FALSE;
	}
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, mmckData.cksize);
	mmioRead(hmmio, (HPSTR)lpData, mmckData.cksize);
	mmioAscend(hmmio, &mmckData, 0);

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

	*lplpData = lpData;
	*lpdwDataSize = mmckData.cksize;

	return TRUE;
}

BOOL CALLBACK acmDriverEnumCallback(HACMDRIVERID hdriverId, DWORD dwInstance, DWORD dwSupport)
{
	ACMDRIVERDETAILS driverDetails;

	driverDetails.cbStruct = sizeof(ACMDRIVERDETAILS);
	acmDriverDetails(hdriverId, &driverDetails, 0);

	if (lstrcmpW(g_szDriverName, driverDetails.szShortName) == 0) {
		HACMDRIVERID *lphdriverId = (HACMDRIVERID *)dwInstance;
		*lphdriverId = hdriverId;
		return FALSE;
	}

	return TRUE;
}

BOOL WriteRiffWaveMp3File(LPTSTR lpszFileName, LPMPEGLAYER3WAVEFORMAT lpmf, LPBYTE lpMp3Data, DWORD dwDataSize)
{
	HMMIO    hmmio;
	MMCKINFO mmckRiff;
	MMCKINFO mmckFmt;
	MMCKINFO mmckData;
	
	hmmio = mmioOpen(lpszFileName, NULL, MMIO_CREATE | MMIO_WRITE);
	if (hmmio == NULL)
		return FALSE;

	mmckRiff.fccType = mmioStringToFOURCC(TEXT("WAVE"), 0); 
	mmioCreateChunk(hmmio, &mmckRiff, MMIO_CREATERIFF);

	mmckFmt.ckid = mmioStringToFOURCC(TEXT("fmt "), 0);
	mmioCreateChunk(hmmio, &mmckFmt, 0);
	mmioWrite(hmmio, (char *)lpmf, sizeof(MPEGLAYER3WAVEFORMAT));
	mmioAscend(hmmio, &mmckFmt, 0);

	mmckData.ckid = mmioStringToFOURCC(TEXT("data"), 0);
	mmioCreateChunk(hmmio, &mmckData, 0);
	mmioWrite(hmmio, (char *)lpMp3Data, dwDataSize);
	mmioAscend(hmmio, &mmckData, 0);

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

	return TRUE;
}

まず、ReadWaveFileによってWAVEファイルからフォーマットとデータ、サイズをそれぞれ取得します。 EncodeToMp3はこれらを基にMP3フォーマットとデータ、サイズを取得し、 それぞれ第4引数以降に返します。 最終引数は変換後のMP3フォーマットをUI経由で決定するかどうかです。 TRUEの場合はacmFormatChooseでダイアログを表示しますが、 FALSEの場合はacmFormatSuggestで最適なフォーマットをドライバに決定してもらいます。

EncodeToMp3ではまず、エンコードに使用するドライバのハンドルを取得しようとします。 acmStreamOpenの第2引数にNULLを指定すると、変換に使用するドライバがどれになるか分からないため、 確実に一定のドライバで変換を行いたい場合は、そのハンドルが必要になります。 ドライバ名からハンドルを一発で取得できる関数は用意されていないため、 acmDriverEnumでacmDriverEnumCallbackがドライバの数だけ呼ばれるようにし、 その中で目的の名前を持ったドライバ識別子を取得します。 ACMDRIVERDETAILS.を参照すればドライバ名が分かるため、 これが予めドライバ名を格納しておいたg_szDriverNameと一致したならば、 そのときの識別子を返せばよいことになります。

ドライバ識別子を取得したら、acmDriverOpenでドライバハンドルを取得できるため、 これをacmStreamOpenに指定します。 また、フォーマットの推測もそのドライバで行われるよう、 acmFormatSuggestの第2引数にも指定しておきます。 acmFormatChooseにおいて、wfEnum.wFormatTagにWAVE_FORMAT_MPEGLAYER3を指定しているのは、 PCMやADPCMなどのフォーマットを選択できないようにするためです。 また、nChannelsとnSamplesPerSecを初期化しているのは、チャンネルとHzがWAVEと同一でなければ変換が失敗するためです。



戻る