EternalWindows
MP3 / Id3v2の再生

標準MP3ファイルには、id3タグと呼ばれる曲情報が含まれています。 特にid3v2は、id3v1と比べて扱えるジャンルやタグサイズが豊富であり、 現在の主流となっています。 id3v2タグはファイルの先頭に、タグヘッダという形の10バイトで存在し、 次のような情報を含んでいます。

データ バイト数 説明
ファイル識別子 3 id3v2タグが存在するかどうかを表す指標。 常にID3という文字列が格納されている。
バージョン 2 id3v2のバージョン。 1バイト目がメジャーバージョンとなり、2バイト目が改訂番号となる。
フラグ 1 拡張ヘッダやフッタが存在するかどうか表すビット群。
サイズ 4 id3v2タグのサイズ(ただし、タグヘッダのサイズは除く)。 各バイトの最上位ビットは常に1にならなければならばいため、 事実上有効なのは28ビット、即ち256メガバイトとなる。

タグヘッダの最初の3バイトは常にID3となるため、 この値を確認すればId3v2形式かどうかを見極めることが可能です。 ID3である場合、現在ヘッダの先頭を指しているポインタをサイズ分を加算すれば、 ポインタはMP3データを指すことになるでしょう。 タグサイズの取得について補足するため、次に1つの例を示します。

10000011

これは0x83という値を2進数で記したものですが、タグサイズにおける各1バイトには、 最上位ビットが常に0でなければならないという決まりがありました。 仮にタグサイズが0x83だった場合、上記のように最上位ビットが1であってはなりませんから、 次のように1ビット次のバイトへ移行して格納されることになっています。

00000001 00000011

確かに、第1バイトの最上位ビットが0になっていることが確認できます。 ただし、ここで注意しなければならないのは、上記の値は最上位ビットの調整のため、 もはや本来の0x83という値を維持していませんから、実際にタグサイズを利用する際には、 各バイトを適切にシフトし、論理和で結合する必要がでてきます。

MP3データを解析する上でフレームヘッダ、Id3v2タグ以外に、 CBR(Constant Bit Rate)とVBR(Variable Bit Rate)の違いも理解しておく必要があります。 前節で述べたように、MP3データはフレームで構成されているわけですが、 全てのフレームのビットレートが同一である場合はCBR形式と呼ばれます。 一方、フレームによってビットレートが異なる場合は、VBA形式になります。 このCBRとVBRの違いは、次の処理で影響が出ることを確認しています。

acmStreamSize(has, dwSrcSize, &dwDestSize, ACM_STREAMSIZEF_SOURCE);

acmStreamSizeは、変換元データ(今回の場合MP3データ)のサイズから、 変換先データ(今回の場合WAVEデータ)のサイズを予測して返す関数でした。 CBRの場合はこの関数は適切なサイズを返しますが、 VBRの場合は本来望むサイズよりも小さいサイズを返すことがあるようです。 このため、VBR形式のことを考えて、 acmStreamSizeを使わずにWAVEデータのサイズを取得する方法を考える必要があります。

WAVEデータのサイズというのは、曲の長さ(秒数)が分かれば求められるようになっています。 1秒間の音のサイズというのは、WAVEFORMATEX構造体のnAvgBytesPerSecですから、 たとえば曲が5秒ちょうどならば、この値に5を掛ければよいのです。 それでは、曲の長さをどう求めるかですが、これもMP3データに含まれるフレーム数が分かれば可能になっています。 まず、MP3データにおける1フレームは1152サンプルで構成されていました。 1秒間の音に必要となるサンプル数はWAVEFORMATEX構造体のnSamplesPerSecから分かりますから、 これで1152を割り、その結果にフレーム数を掛ければ曲の長さが分かります。

MP3データに含まれるフレーム数を取得するのは、それほど難解ではありません。 フレームは常にフレームヘッダを持つわけですから、 データの中でフレームヘッダが見つかった回数がフレームの総数になりますす。 ただし、曲が長い場合はこのループ処理に時間がかかるため、 そうしたことを考慮してXINGヘッダと呼ばれるものがあります。 このヘッダは、次のような形式になっています。

データ バイト数 説明
ヘッダ識別子 4 XINGヘッダが存在するかどうかを表す指標。 常にXingという文字列が格納されている。
フラグ 4 0x0001が含まれている場合はフレーム数が後続に格納されていることを意味する。 同じ要領で0x0002が含まれれている場合はファイルサイズ、0x0004の場合はTOCエントリ、 0x0008の場合はQuality Indicatorが格納されていることを意味する。
フレーム数 4 MP3データに含まれるフレームの総数。
ファイルサイズ 4 バイト単位のサイズ
TOCエントリ 100 特定の時間にシークするために必要な情報。
Quality indicator 4 品質を示す情報。

注目すべきなのは、フラグに0x0001という値が含まれる場合です。 この際にはヘッダにフレーム数が格納されていますから、 それを取得することでMP3データをループする必要がなくなります。

今回のプログラムは、id3v1及びid3v2の標準MP3ファイルを再生します。 WindowProcの構造はRIFF/WAVE形式のときとほぼ同一であり、 取得したMP3データをACMを介してWAVEデータに変換する点も同一です。

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

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

struct ID3V2HEADER {
	BYTE id[3];
	BYTE version[2];
	BYTE flag;
	BYTE size[4];
};
typedef struct ID3V2HEADER ID3V2HEADER;
typedef struct ID3V2HEADER *LPID3V2HEADER;

struct FRAMEHEADER {
	BYTE  version;
	DWORD dwBitRate;
	DWORD dwSampleRate;
	BYTE  padding;
	BYTE  channel;
	WORD  wFrameSize;
};
typedef struct FRAMEHEADER FRAMEHEADER;
typedef struct FRAMEHEADER *LPFRAMEHEADER;

BOOL GetFrameHeader(LPBYTE lpData, LPFRAMEHEADER lpfh);
void GetMp3Format(LPFRAMEHEADER lpfh, LPMPEGLAYER3WAVEFORMAT lpmf);
DWORD GetDecodeSize(LPBYTE lpMP3Data, DWORD dwMP3Size, LPWAVEFORMATEX lpwf);
BOOL IsId3v2(LPBYTE lpData, DWORD dwDataSize, LPDWORD lpdwTagSize);
BOOL DecodeToWave(LPWAVEFORMATEX lpwfSrc, LPBYTE lpSrcData, DWORD dwSrcSize, LPWAVEFORMATEX lpwfDest, LPBYTE *lplpDestData, LPDWORD lpdwDestSize);
BOOL ReadMP3File(LPTSTR lpszFile, 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 = NULL;
		WAVEFORMATEX         wf;
		MPEGLAYER3WAVEFORMAT mf;

		if (!ReadMP3File(TEXT("sample.mp3"), &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 GetFrameHeader(LPBYTE lpData, LPFRAMEHEADER lpfh)
{
	BYTE  index;
	BYTE  version;
	BYTE  channel;
	BYTE  padding;
	WORD  wFrameSize;
	DWORD dwBitRate;
	DWORD dwSampleRate;
	DWORD dwBitTableLayer3[][16] = {
		{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0},
		{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}
	};
	DWORD dwSampleTable[][3] = {
		{44100, 48000, 32000},
		{22050, 24000, 16000}
	};

	if (lpData[0] != 0xff || lpData[1] >> 5 != 0x07)
		return FALSE;
	
	switch (lpData[1] >> 3 & 0x03) {

	case 3:
		version = 1;
		break;

	case 2:
		version = 2;
		break;

	default:
		return FALSE;

	}

	if ((lpData[1] >> 1 & 0x03) != 1)
		return FALSE;

	index     = lpData[2] >> 4;
	dwBitRate = dwBitTableLayer3[version - 1][index];
	
	index        = lpData[2] >> 2 & 0x03;
	dwSampleRate = dwSampleTable[version - 1][index];

	padding = lpData[2] >> 1 & 0x01;
	channel = (lpData[3] >> 6) == 3 ? 1 : 2;
	
	wFrameSize = (WORD)((1152 * dwBitRate * 1000 / dwSampleRate) / 8) + padding;

	lpfh->version      = version;
	lpfh->dwBitRate    = dwBitRate;
	lpfh->dwSampleRate = dwSampleRate;
	lpfh->padding      = padding;
	lpfh->channel      = channel;
	lpfh->wFrameSize   = wFrameSize;

	return TRUE;
}

void GetMp3Format(LPFRAMEHEADER lpfh, LPMPEGLAYER3WAVEFORMAT lpmf)
{
	lpmf->wfx.wFormatTag      = WAVE_FORMAT_MPEGLAYER3;
	lpmf->wfx.nChannels       = lpfh->channel;
	lpmf->wfx.nSamplesPerSec  = lpfh->dwSampleRate;
	lpmf->wfx.nAvgBytesPerSec = (lpfh->dwBitRate * 1000) / 8;
	lpmf->wfx.nBlockAlign     = 1;
	lpmf->wfx.wBitsPerSample  = 0;
	lpmf->wfx.cbSize          = MPEGLAYER3_WFX_EXTRA_BYTES;

	lpmf->wID             = MPEGLAYER3_ID_MPEG;
	lpmf->fdwFlags        = lpfh->padding ? MPEGLAYER3_FLAG_PADDING_ON : MPEGLAYER3_FLAG_PADDING_OFF;
	lpmf->nBlockSize      = lpfh->wFrameSize;
	lpmf->nFramesPerBlock = 1;
	lpmf->nCodecDelay     = 0x571;
}


DWORD GetDecodeSize(LPBYTE lpMP3Data, DWORD dwMP3Size, LPWAVEFORMATEX lpwf)
{
	DWORD       dwFrameCount;
	DWORD       dwDecodeSize;
	LPBYTE      lp;
	DWORD       dwOffset;
	DWORD       dwFlags;
	FRAMEHEADER frameHeader;
	double      dSecond;

	GetFrameHeader(lpMP3Data, &frameHeader);

	if (frameHeader.channel == 1)
		dwOffset = 17;
	else
		dwOffset = 31;
	
	lp = lpMP3Data + dwOffset + 4;
	if (lp[0] == 'X' || lp[1] == 'i' || lp[2] == 'n' || lp[3] == 'g') {
		lp += 4;
		dwFlags = lp[3] | (lp[2] << 8) | (lp[1] << 16) | (lp[0] << 24);
		if (dwFlags & 0x0001) {
			lp += 4;
			dwFrameCount = lp[3] | (lp[2] << 8) | (lp[1] << 16) | (lp[0] << 24);
		}
		else {
			dwFrameCount = 0;
		}
	}
	else {
		dwFrameCount = 0;
		dwOffset = 0;
		while (dwMP3Size > dwOffset) {
			if (GetFrameHeader(lpMP3Data + dwOffset, &frameHeader)) {
				dwFrameCount++;
				dwOffset += frameHeader.wFrameSize;
			}
			else {
				dwOffset++;
			}
		}
	}

	dSecond = (dwFrameCount * (double)(1152 / (double)lpwf->nSamplesPerSec));
	dwDecodeSize = (DWORD)(lpwf->nAvgBytesPerSec * dSecond);

	return dwDecodeSize;
}

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

	dwDestSize = GetDecodeSize(lpSrcData, dwSrcSize, lpwfDest);
	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 IsId3v2(LPBYTE lpData, DWORD dwDataSize, LPDWORD lpdwTagSize)
{
	BOOL          bResult;
	LPBYTE        lp;
	LPID3V2HEADER lpHeader = (LPID3V2HEADER)lpData;

	if (lpHeader->id[0] == 'I' && lpHeader->id[1] == 'D' && lpHeader->id[2] == '3') {
		*lpdwTagSize  = ((lpHeader->size[0] << 21) | (lpHeader->size[1] << 14) | (lpHeader->size[2] << 7) | (lpHeader->size[3])) + 10; 
		bResult = TRUE;
	}
	else {
		lp = (lpData + dwDataSize) - 128;
		if (lp[0] == 'T' && lp[1] == 'A' && lp[2] == 'G')
			*lpdwTagSize = 128;
		else
			*lpdwTagSize = 0;
		bResult = FALSE;
	}

	return bResult;
}

BOOL ReadMP3File(LPTSTR lpszFileName, LPMPEGLAYER3WAVEFORMAT lpmf, LPBYTE *lplpData, LPDWORD lpdwSize)
{
	HMMIO       hmmio;
	LPBYTE      lpData;
	LPBYTE      lpMp3Data;
	DWORD       dwSize;
	DWORD       dwTagSize;
	FRAMEHEADER frameHeader;
	
	hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ);
	if (hmmio == NULL) {
		MessageBox(NULL, TEXT("ファイルのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}

	dwSize = mmioSeek(hmmio, 0, SEEK_END);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	mmioSeek(hmmio, 0, SEEK_SET);
	mmioRead(hmmio, (HPSTR)lpData, dwSize);	
	mmioClose(hmmio, 0);

	if (IsId3v2(lpData, dwSize, &dwTagSize)) {
		dwSize -= dwTagSize;
		lpMp3Data = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
		CopyMemory(lpMp3Data, (LPBYTE)lpData + dwTagSize, dwSize);
		HeapFree(GetProcessHeap(), 0, lpData);
	}
	else {
		dwSize -= dwTagSize;
		lpMp3Data = lpData;
	}

	if (!GetFrameHeader(lpMp3Data, &frameHeader)) {
		HeapFree(GetProcessHeap(), 0, lpMp3Data);
		return FALSE;
	}
	
	GetMp3Format(&frameHeader, lpmf);
	
	*lplpData = lpMp3Data;
	*lpdwSize = dwSize;

	return TRUE;
}

ReadMP3Fileでは、まずファイル全体をロードしています。 次にIsId3v2でId3v2であるかを調べると共に、 タグのサイズを取得します。

BOOL IsId3v2(LPBYTE lpData, DWORD dwDataSize, LPDWORD lpdwTagSize)
{
	BOOL          bResult;
	LPBYTE        lp;
	LPID3V2HEADER lpHeader = (LPID3V2HEADER)lpData;

	if (lpHeader->id[0] == 'I' && lpHeader->id[1] == 'D' && lpHeader->id[2] == '3') {
		*lpdwTagSize  = ((lpHeader->size[0] << 21) | (lpHeader->size[1] << 14) | (lpHeader->size[2] << 7) | (lpHeader->size[3])) + 10; 
		bResult = TRUE;
	}
	else {
		lp = (lpData + dwDataSize) - 128;
		if (lp[0] == 'T' && lp[1] == 'A' && lp[2] == 'G')
			*lpdwTagSize = 128;
		else
			*lpdwTagSize = 0;
		bResult = FALSE;
	}

	return bResult;
}

lpHeaderの型であるID3V2HEADER構造体はタグヘッダを表しているため、 先頭メンバのidを参照してID3という文字を調べることができます。 タグサイズの初期化では、先に述べたように各バイトをシフトして論理和で結合し、 そこにタグヘッダのサイズを加算することで、完全なタグサイズを取得できます。 else文はファイルデータの先頭がID3でないときに処理されますから、 id3v1のためのコードだということが分かります。 この形式ではファイルの先頭からMP3データが格納されており、 id3v1タグはファイルの最後から128バイトの位置に存在します。 よって、その位置をlpという変数で表し、 先頭3バイトがTAGという文字列であるかを調べています。

IsId3v2の後には次のコードが実行されます。

if (IsId3v2(lpData, dwSize, &dwTagSize)) {
	dwSize -= dwTagSize;
	lpMp3Data = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CopyMemory(lpMp3Data, (LPBYTE)lpData + dwTagSize, dwSize);
	HeapFree(GetProcessHeap(), 0, lpData);
}
else {
	dwSize -= dwTagSize;
	lpMp3Data = lpData;
}

lpMp3Dataには、MP3データの先頭を格納したいと思っています。 Id3v2の場合はファイルの先頭にタグヘッダがありますから、 lpMp3Data = lpData + dwTagSizeとすればよいように思えますが、 それではメモリを解放する際に、タグヘッダの部分が含まれないことになります。 よって、純粋にMP3データを含むバッファを確保し、 そこにMP3データをコピーするようにしています。 dwSizeにタグサイズが含まれてはならないため、dwTagSize分だけ引くことになります。

DecodeToWaveではacmStreamSizeの代わりにGetDecodeSizeを呼び出していますが、 この関数がMP3データからWAVEデータのサイズします。 まず、次の処理に注目します。

GetFrameHeader(lpMP3Data, &frameHeader);

if (frameHeader.channel == 1)
	dwOffset = 17;
else
	dwOffset = 31;

lp = lpMP3Data + dwOffset + 4;
if (lp[0] == 'X' || lp[1] == 'i' || lp[2] == 'n' || lp[3] == 'g') {

MP3データの先頭からXINGヘッダまでのオフセットは、 フレームヘッダのチャンネルによって決まるため、 GetFrameHeaderでフレームヘッダを取得しています。 dwOffsetを加算することでXINGヘッダの先頭を求めるわけですが、 このときに+4をしているのはフレームヘッダの領域をカットするためです。 ヘッダの指標となるXingが見つかった場合の処理は、次のようになっています。

lp += 4;
dwFlags = lp[3] | (lp[2] << 8) | (lp[1] << 16) | (lp[0] << 24);
if (dwFlags & 0x0001) {
	lp += 4;
	dwFrameCount = lp[3] | (lp[2] << 8) | (lp[1] << 16) | (lp[0] << 24);
}
else {
	dwFrameCount = 0;
}

まず、指標のサイズ分だけlpを進めます。 次に、フラグの値を取得し、0x0001が含まれるかどうかを調べます。 dwFlagsへの代入に*((LPDWORD)lp)としていないのは、データがビッグエンディアン形式で格納されているからです。 0x0001が含まれている場合はフレーム数が格納されているわけですから、 フレームフラグ分だけlpを進めて実際にフレームの数を取得します。 続いて、XINGヘッダがないときの処理を見ていきます。

dwOffset = 0;
while (dwMP3Size > dwOffset) {
	if (GetFrameHeader(lpMP3Data + dwOffset, &frameHeader)) {
		dwFrameCount++;
		dwOffset += frameHeader.wFrameSize;
	}
	else {
		dwOffset++;
	}
}

GetFrameHeaderを繰り返すことで、フレームの数を更新していきます。 フレームが見つからない場合は1つオフセットを進めますが、 見つかった場合はフレーム分のサイズを足すことができます。 ループは、オフセットの値がMP3データのサイズより低い限り可能です。 最後に、取得したフレーム数からWAVEデータのサイズを求める処理を確認します。

dSecond = (dwFrameCount * (double)(1152 / (double)lpwf->nSamplesPerSec));
dwDecodeSize = (DWORD)(lpwf->nAvgBytesPerSec * dSecond);

フレーム数から曲の長さを求めるための計算式は既に述べた通りです。 そして、これをnAvgBytesPerSecに掛ければ、dSecond秒再生するために必要なサイズが求まります。


戻る