2011年04月20日

MP3ファイルの再生 第2部



CWaveFile::OpenFile関数から呼び出された後、MP3ファイルを開いて、CMP3File::ReadHeaderを呼び出します。

//MP3ファイルを開きます。(オーバーライド)
// lpszFileName:オープンするファイル名。

BOOL CMP3File::ReadFile(LPCTSTR lpszFileName)
{
//CDDAFile::ReadFile関数を呼び出し、CD-DA若しくはWAVEファイルとしてファイルを読み込みます。
//読み込みに成功した場合は、正常終了します。

if (CDDAFile::ReadFile(lpszFileName)) return TRUE;

//ファイルのオープンに失敗した場合は、エラー終了します。
CFile* infile=new CFile;
if (!infile->Open(lpszFileName,CFile::modeRead|CFile::shareDenyNone)){
delete infile;
return FALSE;
}

//ヘッダー読み込みに失敗した場合は、エラー終了します。
if (!ReadHeader(infile)){
infile->Close();
delete infile;
return FALSE;
}

//CFileクラスへのポインタを保存します。
m_infile=infile;
return TRUE;
}


MP3ファイルフレームヘッダからMPEGLAYER3WAVEFORMAT構造体を作成し、この構造体を元にACMストリームをオープンし、デコードと同時に演奏するための準備としてACMストリームヘッダを作成しておきます。

//MP3ヘッダーの読み込み
BOOL CMP3File::ReadHeader(CFile* infile)
{
ASSERT(infile->IsKindOf(RUNTIME_CLASS(CFile)));

//読み飛ばし位置は、現在のファイル位置とします。
m_dwSkipOffset=(DWORD)infile->GetPosition();
//ファイルサイズを取得します。
DWORD fileSize=(DWORD)(DWORD_PTR)infile->GetLength();

//フレームヘッダーを探します。
BYTE header[128];
FRAMEHEADER frameHeader={0};
while(TRUE){
//読み飛ばし位置に移動してから10バイト読みます。
infile->Seek(m_dwSkipOffset,CFile::begin);
if (infile->Read(header,10)!=10) return FALSE;
//フレームヘッダーがあった場合。
if (_ParseFrameHeader((PBYTE)&header,&frameHeader)){
//ファイルポインタ
DWORD offset=m_dwSkipOffset+frameHeader.frameSize;
//フレームサイズが正しいかどうか調べます。
FRAMEHEADER fh={0};
//読み飛ばし位置に移動してから4バイト読みます。
infile->Seek(offset,CFile::begin);
if (infile->Read(header,4)!=4) break;
//フレームヘッダーかどうか調べます。
if (_ParseFrameHeader((PBYTE)&header,&fh)) break;
m_dwSkipOffset++;
}
//"ID3"タグがある場合。
else if (::memcmp(header,"ID3",3)==0){
//ID3V2タグサイズを算出します。
DWORD sizeTag=((header[6]&0x7F)<<21)|((header[7]&0x7F)<<14)|
((header[8]&0x7F)<< 7)|(header[9]&0x7F);
//読み飛ばし位置を算出します。
m_dwSkipOffset+=(sizeTag+10);
//バッファに生データを保存します。
BYTE* pBuffer=NULL;
switch(header[3]){
case 2: //ID3v2.2タグの場合
if (!m_byteID3v22.GetSize()){
m_byteID3v22.SetSize(sizeTag+10);
pBuffer=m_byteID3v22.GetData();
m_ID3v22Version=(header[3]<<8)|(header[4]);
}
break;
case 3: //ID3v2.3タグの場合
if (!m_byteID3v23.GetSize()){
m_byteID3v23.SetSize(sizeTag+10);
pBuffer=m_byteID3v23.GetData();
m_ID3v23Version=(header[3]<<8)|(header[4]);
}
break;
case 4: //ID3v2.4タグの場合
if (!m_byteID3v24.GetSize()){
m_byteID3v24.SetSize(sizeTag+10);
pBuffer=m_byteID3v24.GetData();
m_ID3v24Version=(header[3]<<8)|(header[4]);
}
break;
default:
pBuffer=NULL;
}
//ID3v2タグバッファが確保されている場合は、
//ID3v2タグの内容をバッファに読み込みます。

if (pBuffer){
::CopyMemory(pBuffer,&header,10);
UINT size=infile->Read(pBuffer+10,sizeTag);
if (size<sizeTag) return FALSE;
}
}
else{
//フレームヘッダーではなく、ID3v2タグでもない場合は、
//読み飛ばし位置を1バイト進めてから再び読み直します。

m_dwSkipOffset++;
//1メガバイト以上検索してもフレームヘッダーが見つからなかった場合は、エラー終了します。
if (m_dwSkipOffset>0x100000) return FALSE;
}
}

//フレームヘッダーから得られた値を、それぞれメンバー変数に保存します。
m_iVersion =frameHeader.version; //MPEGバージョン番号
m_iLayer =frameHeader.layer; //レイヤー番号
m_iBitRate =frameHeader.bitRate; //ビットレート
m_sizeFrame =frameHeader.frameSize; //MP3ブロックサイズ

//MPEGLAYER3WAVEFORMAT構造体を作成します。
MPEGLAYER3WAVEFORMAT mp3wf={0};

mp3wf.wfx.wFormatTag =WAVE_FORMAT_MPEGLAYER3;
mp3wf.wfx.nChannels =frameHeader.channel;
mp3wf.wfx.nSamplesPerSec =frameHeader.sampleRate;
mp3wf.wfx.nAvgBytesPerSec =(frameHeader.bitRate*1000)/8;
mp3wf.wfx.nBlockAlign =1;
mp3wf.wfx.wBitsPerSample =0;
mp3wf.wfx.cbSize =MPEGLAYER3_WFX_EXTRA_BYTES;

mp3wf.wID =MPEGLAYER3_ID_MPEG;
mp3wf.fdwFlags =(frameHeader.padding)?MPEGLAYER3_FLAG_PADDING_ON:MPEGLAYER3_FLAG_PADDING_OFF;
mp3wf.nBlockSize =frameHeader.frameSize;
mp3wf.nFramesPerBlock =1;
mp3wf.nCodecDelay =1393;

//Xingヘッダーがあるか調べます。
m_uFlagBitRate=CBR;
DWORD offset=(frameHeader.version==1)?
((frameHeader.channel==1)?17:32):((frameHeader.channel==1)?9:17);
infile->Seek(m_dwSkipOffset+offset+4,CFile::begin);
if (infile->Read(header,4)!=4) return FALSE;

//Xingヘッダーがある場合
if ((::memcmp(header,"Xing",4)==0)||
(::memcmp(header,"Info",4)==0))
{
m_uFlagBitRate=VBR;
if (infile->Read(header,4)!=4) return FALSE;
DWORD dwflags=(((((header[0]<<8)|header[1])<<8)|header[2])<<8)|header[3];
if (dwflags&1){//フレーム数の取得
if (infile->Read(header,4)!=4) return FALSE;
m_nFrames=(((((header[0]<<8)|header[1])<<8)|header[2])<<8)|header[3];
}
if (dwflags&2){//データ総数
if (infile->Read(header,4)!=4) return FALSE;
m_dwStreamSize=(((((header[0]<<8)|header[1])<<8)|header[2])<<8)|header[3];
}
if (dwflags&4){//TOCエントリー
if (infile->Read(m_vbrTOC,100)!=100) return FALSE;
}
if (dwflags&8){//VBR品質((高)0〜100(低))
if (infile->Read(header,4)!=4) return FALSE;
m_vbrQuality=(((((header[0]<<8)|header[1])<<8)|header[2])<<8)|header[3];
}
}

//すべてのフレームを調べて、フレーム総数と平均ビットレートを算出します。
int nFrames=0;
//ビットレートの合計
double ttlBitRate=0.0;
//データ総数
DWORD dwStreamSize=0;
//VBRではない場合は、既に読み込んだ第一フレームの分も加算します。
if (m_uFlagBitRate!=VBR){
nFrames++;
dwStreamSize+=m_sizeFrame;
}
//検索するファイル位置を算出します。
offset=m_dwSkipOffset+m_sizeFrame;

//すべてのフレームについて調べます。
while(TRUE){
//次のフレームの先頭位置を探します。
BOOL bFind=FALSE;
while(TRUE){
//読み飛ばし位置に移動してから4バイト読みます。
infile->Seek(offset,CFile::begin);
if (infile->Read(header,4)!=4) break;
//フレームヘッダーかどうか調べます。
if (_ParseFrameHeader((PBYTE)&header,&frameHeader)){
bFind=TRUE;
break;
}
//見つからない場合は、ファイルポインタを1つ進めます。
offset++;
}
if (!bFind) break;
//第一フレームのビットレートと違えば、ABRにします。
if ((m_uFlagBitRate==CBR)&&(m_iBitRate!=frameHeader.bitRate))
m_uFlagBitRate=ABR;

ttlBitRate+=(double)frameHeader.bitRate;
offset+=frameHeader.frameSize;
dwStreamSize+=frameHeader.frameSize;
nFrames++;
}
//平均ビットレートを算出します。
m_iAveBitRate=(int)(ttlBitRate/(double)nFrames);
if ((m_uFlagBitRate!=VBR)||(!m_nFrames)) m_nFrames=nFrames;
if (!m_dwStreamSize) m_dwStreamSize=dwStreamSize;

//ID3v1タグがあるか調べます。
//ファイル末尾から128バイト戻った位置にシークします。

infile->Seek(fileSize-128,CFile::begin);

UINT size=infile->Read(header,128);
if (size<128) return FALSE;

//"TAG"がある場合。(ID3v1タグ)
if (::memcmp(header,"TAG",3)==0){
//バージョン番号を保存します。
m_id3v1Version=0x100|((header[125]=='\0')?1:0);
//バッファに生データを保存します。
m_byteId3v1.SetSize(128);
::CopyMemory(m_byteId3v1.GetData(),&header,128);
}

//MP3ストリームサイズは、ファイル長さから読み飛ばし位置と、
//ID3v1タグがあれば、そのサイズを差し引きます。

DWORD dwFileStreamSize=fileSize-m_dwSkipOffset-m_byteId3v1.GetSize();
if (dwFileStreamSize<m_dwStreamSize) m_dwStreamSize=dwFileStreamSize;

//ACMドライバーを開きます。
MMRESULT mmr=::acmDriverOpen(&m_hDriver,m_hDriverIdDec,0);
if (mmr!=MMSYSERR_NOERROR) m_hDriver=NULL;

//MPEGLAYER3WAVEFORMAT構造体からWAVEFORMATEX構造体を取得します。
m_wfx.wFormatTag=WAVE_FORMAT_PCM;
m_wfx.cbSize=sizeof(WAVEFORMATEX);
mmr=::acmFormatSuggest(m_hDriver,&mp3wf.wfx,&m_wfx,sizeof(WAVEFORMATEX),ACM_FORMATSUGGESTF_WFORMATTAG);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//ACMストリームをオープンします。
mmr=::acmStreamOpen(&m_hAcmStream,m_hDriver,&mp3wf.wfx,&m_wfx,NULL,0,0,ACM_STREAMOPENF_NONREALTIME);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//フレームサイズに相当するデコード後のデータサイズを取得します。
DWORD sizeFrameDecoded;
::acmStreamSize(m_hAcmStream,m_sizeFrame,&sizeFrameDecoded,ACM_STREAMSIZEF_SOURCE);

//MP3ストリームサイズからデコード後の総データサイズを取得します。
::acmStreamSize(m_hAcmStream,m_dwStreamSize,(LPDWORD)&m_nDataSize,ACM_STREAMSIZEF_SOURCE);
//フレームサイズに相当するデコード後のデータサイズの平均値を算出します。
m_sizeAveFrameDecoded=ROUND((double)m_sizeFrame*(double)m_nDataSize/(double)m_dwStreamSize);

//作業用バッファを確保します。
DWORD dwSrcSize=m_sizeFrame; //転送元バッファサイズ
DWORD dwDstSize=sizeFrameDecoded; //転送先バッファサイズ
//転送先バッファサイズは4608バイト以上になるようにします。(libmpg123対応)
if (dwDstSize<4608) dwDstSize=4608*2;

m_byteSrcBuf.SetSize(dwSrcSize); //転送元バッファ
m_byteDstBuf.SetSize(dwDstSize); //転送先バッファ

//ACMストリームヘッダーを作成します。
::ZeroMemory(&m_AcmStreamHeader,sizeof(ACMSTREAMHEADER));
m_AcmStreamHeader.cbStruct =sizeof(ACMSTREAMHEADER);
m_AcmStreamHeader.pbSrc =m_byteSrcBuf.GetData();
m_AcmStreamHeader.cbSrcLength=(DWORD)(DWORD_PTR)m_byteSrcBuf.GetSize();
m_AcmStreamHeader.pbDst =m_byteDstBuf.GetData();
m_AcmStreamHeader.cbDstLength=(DWORD)(DWORD_PTR)m_byteDstBuf.GetSize();

::acmStreamPrepareHeader(m_hAcmStream,&m_AcmStreamHeader,0);

//現在のファイル位置を、読み飛ばし位置に移動します。
infile->Seek(m_dwSkipOffset,CFile::begin);

//第一フレームをドライバーに読み込ませます。
size=infile->Read(m_AcmStreamHeader.pbSrc,m_AcmStreamHeader.cbSrcLength);
if (!size) return FALSE;
//実際に読み込んだサイズを有効なサイズとします。
m_AcmStreamHeader.cbSrcLengthUsed=size;
//Mp3データからWave音源データに変換します。
mmr=::acmStreamConvert(m_hAcmStream,&m_AcmStreamHeader,ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START);
//デコード済みデータがある場合。
if ((mmr==MMSYSERR_NOERROR)&&(m_AcmStreamHeader.cbDstLengthUsed>0)){
//キャッシュに、デコード済みデータをコピーします。
m_byteCache.SetSize(m_AcmStreamHeader.cbDstLengthUsed);
::CopyMemory(m_byteCache.GetData(),m_AcmStreamHeader.pbDst,m_AcmStreamHeader.cbDstLengthUsed);
}
else
//キャッシュを初期化します。
m_byteCache.SetSize(0);

//指定の演奏位置を、読み込んだMp3データに相当する音源データ分進めます。
DWORD dwPosRecent=(DWORD)infile->GetPosition();
UINT indexFrame=(dwPosRecent-m_dwSkipOffset)/m_sizeFrame;
m_indexSample=(m_sizeAveFrameDecoded*indexFrame)/m_wfx.nBlockAlign;

//MPEGLAYER3WAVEFORMAT構造体を保存しておきます。
SetMpegLayer3WaveFormat(0,&mp3wf);

return TRUE;
}


MP3ファイル再生時に音源データ読み込みのために、基底クラスから呼び出されるオーバーライド関数です。

//MP3からWave音源に変換してストリーム読み込み
UINT CMP3File::StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample)
{
ASSERT(!::IsBadWritePtr(pBuffer,nBufSize));
ASSERT(AfxIsValidAddress(pIndexSample,sizeof(UINT)));

//ACMストリームハンドルが無効な場合は、CDDAFile::StreamIn関数を呼び出します。
if (!IsMP3File()) return CDDAFile::StreamIn(pBuffer,nBufSize,pIndexSample);

//現在位置と指定位置を算出します。
DWORD dwPosRecent=(DWORD)m_infile->GetPosition();
UINT indexFrame=ROUND((double)(*pIndexSample*m_wfx.nBlockAlign)/(double)m_sizeAveFrameDecoded);
DWORD dwPosRead=m_dwSkipOffset+(m_sizeFrame*indexFrame);

DWORD fdwConvert=ACM_STREAMCONVERTF_BLOCKALIGN;
if (indexFrame==0) fdwConvert|=ACM_STREAMCONVERTF_START;
//現在演奏中で現在位置と指定位置が100_秒以上違えば、指定位置にシークします。
if ((IsPlaying())&&
((DWORD)abs((int)(dwPosRead-dwPosRecent))>(m_wfx.nAvgBytesPerSec/10))){
//前回の残りの音源データ数を0にします。
m_byteCache.SetSize(0);

//フレームヘッダーを探します。
BYTE header[4];
FRAMEHEADER frameHeader;
while(TRUE){
//読み飛ばし位置に移動してから4バイト読みます。
m_infile->Seek(dwPosRead,CFile::begin);
if (m_infile->Read(header,4)!=4) return FALSE;
//フレームヘッダーかどうか調べます。
if (_ParseFrameHeader((PBYTE)&header,&frameHeader)) break;
//フレームヘッダーではない場合は、
//読み飛ばし位置を1バイト進めてから再び読み直します。

dwPosRead++;
}
//探し出したフレームヘッダーの先頭位置に移動します。
m_infile->Seek(dwPosRead,CFile::begin);
}

//キャッシュ内のデータサイズを取得します。
UINT nCaches=(UINT)(UINT_PTR)m_byteCache.GetSize();
//MP3ストリーム最後尾の位置
DWORD dwFileEnd=m_dwStreamSize+m_dwSkipOffset;

//キャッシュ内のデータサイズがバッファサイズに満たない場合。
while(nCaches<nBufSize){

UINT size=m_infile->Read(m_AcmStreamHeader.pbSrc,m_AcmStreamHeader.cbSrcLength);
if (!size) break;
if (size!=m_AcmStreamHeader.cbSrcLength)
fdwConvert|=ACM_STREAMCONVERTF_END;

//実際に読み込んだサイズを有効なサイズとします。
m_AcmStreamHeader.cbSrcLengthUsed=size;
//現在のファイル位置を取得します。
DWORD dwPos=(DWORD)m_infile->GetPosition();
//ID3v1タグを読み込んだ場合はその分を差し引きます。
if (dwPos>dwFileEnd){
DWORD dwTaiDust=dwPos-dwFileEnd;
if (m_AcmStreamHeader.cbSrcLengthUsed<dwTaiDust) break;
else m_AcmStreamHeader.cbSrcLengthUsed-=dwTaiDust;
fdwConvert|=ACM_STREAMCONVERTF_END;
}
//MP3データからWave音源データに変換します。
MMRESULT mmr=::acmStreamConvert(m_hAcmStream,&m_AcmStreamHeader,fdwConvert);

fdwConvert=ACM_STREAMCONVERTF_BLOCKALIGN;

//lameACM対応
#define MP3_NEED_MORE 0x200

if (mmr==MP3_NEED_MORE) continue;
//デコードエラーの場合は、エラー終了します。
else if (mmr!=MMSYSERR_NOERROR) return FALSE;
//デコード済みデータがある場合。
else if (m_AcmStreamHeader.cbDstLengthUsed>0){
//キャッシュの末尾に、デコード済みデータを追加します。
nCaches=(UINT)(UINT_PTR)m_byteCache.GetSize();
m_byteCache.SetSize(nCaches+m_AcmStreamHeader.cbDstLengthUsed);
::CopyMemory(m_byteCache.GetData()+nCaches,m_AcmStreamHeader.pbDst,
m_AcmStreamHeader.cbDstLengthUsed);
nCaches=(UINT)(UINT_PTR)m_byteCache.GetSize();
}
}
//バッファに取り込むサイズを算定して、キャッシュからデータをコピーします。
UINT readBytes=min(nBufSize,nCaches);
::CopyMemory(pBuffer,m_byteCache.GetData(),readBytes);

//キャッシュ内のデータの内、バッファに取り込んだデータを削除してから、
//残りのデータを左シフトします。

m_byteCache.RemoveAt(0,readBytes);

//指定の演奏位置を、読み込んだMP3データに相当する音源データ分進めます。
dwPosRecent=(DWORD)m_infile->GetPosition();
indexFrame=(dwPosRecent-m_dwSkipOffset)/m_sizeFrame;
*pIndexSample=(m_sizeAveFrameDecoded*indexFrame)/m_wfx.nBlockAlign;
//読み込んだバイト数を返して終了します。
return readBytes;
}


CMP3Fileクラスのインスタンスを作成した後、CWaveFile::OpenFile関数でMP3ファイルを開きます。
エラーがなければCWaveFile::Play関数で演奏を開始します。演奏を中断したり演奏終了後、WAVE出力デバイスを閉じる場合は、CWaveFile::Stop関数を呼び出して下さい。
入力したMP3ファイルが再生されれば成功です。

WaveOut.h
WaveOut.cpp
WaveFile.h
WaveFile.cpp
CDDAFile.h
CDDAFile.cpp
MP3File.h
MP3File.cpp



CWaveFile::OpenFile関数から呼び出された後、MP3ファイルを開いて、CMP3File::ReadHeaderを呼び出します。

//MP3ファイルを開きます。(オーバーライド)
// lpszFileName:オープンするファイル名。

BOOL CMP3File::ReadFile(LPCTSTR lpszFileName)
{
//CDDAFile::ReadFile関数を呼び出し、CD-DA若しくはWAVEファイルとしてファイルを読み込みます。
//読み込みに成功した場合は、正常終了します。

if (CDDAFile::ReadFile(lpszFileName)) return TRUE;

//ファイルのオープンに失敗した場合は、エラー終了します。
CFile* infile=new CFile;
if (!infile->Open(lpszFileName,CFile::modeRead|CFile::shareDenyNone)){
delete infile;
return FALSE;
}

//ヘッダー読み込みに失敗した場合は、エラー終了します。
if (!ReadHeader(infile)){
infile->Close();
delete infile;
return FALSE;
}

//CFileクラスへのポインタを保存します。
m_infile=infile;
return TRUE;
}


MP3ファイルフレームヘッダからMPEGLAYER3WAVEFORMAT構造体を作成し、この構造体を元にACMストリームをオープンし、デコードと同時に演奏するための準備としてACMストリームヘッダを作成しておきます。

//MP3ヘッダーの読み込み
BOOL CMP3File::ReadHeader(CFile* infile)
{
ASSERT(infile->IsKindOf(RUNTIME_CLASS(CFile)));

//読み飛ばし位置は、現在のファイル位置とします。
m_dwSkipOffset=(DWORD)infile->GetPosition();
//ファイルサイズを取得します。
DWORD fileSize=(DWORD)(DWORD_PTR)infile->GetLength();

//フレームヘッダーを探します。
BYTE header[128];
FRAMEHEADER frameHeader={0};
while(TRUE){
//読み飛ばし位置に移動してから10バイト読みます。
infile->Seek(m_dwSkipOffset,CFile::begin);
if (infile->Read(header,10)!=10) return FALSE;
//フレームヘッダーがあった場合。
if (_ParseFrameHeader((PBYTE)&header,&frameHeader)){
//ファイルポインタ
DWORD offset=m_dwSkipOffset+frameHeader.frameSize;
//フレームサイズが正しいかどうか調べます。
FRAMEHEADER fh={0};
//読み飛ばし位置に移動してから4バイト読みます。
infile->Seek(offset,CFile::begin);
if (infile->Read(header,4)!=4) break;
//フレームヘッダーかどうか調べます。
if (_ParseFrameHeader((PBYTE)&header,&fh)) break;
m_dwSkipOffset++;
}
//"ID3"タグがある場合。
else if (::memcmp(header,"ID3",3)==0){
//ID3V2タグサイズを算出します。
DWORD sizeTag=((header[6]&0x7F)<<21)|((header[7]&0x7F)<<14)|
((header[8]&0x7F)<< 7)|(header[9]&0x7F);
//読み飛ばし位置を算出します。
m_dwSkipOffset+=(sizeTag+10);
//バッファに生データを保存します。
BYTE* pBuffer=NULL;
switch(header[3]){
case 2: //ID3v2.2タグの場合
if (!m_byteID3v22.GetSize()){
m_byteID3v22.SetSize(sizeTag+10);
pBuffer=m_byteID3v22.GetData();
m_ID3v22Version=(header[3]<<8)|(header[4]);
}
break;
case 3: //ID3v2.3タグの場合
if (!m_byteID3v23.GetSize()){
m_byteID3v23.SetSize(sizeTag+10);
pBuffer=m_byteID3v23.GetData();
m_ID3v23Version=(header[3]<<8)|(header[4]);
}
break;
case 4: //ID3v2.4タグの場合
if (!m_byteID3v24.GetSize()){
m_byteID3v24.SetSize(sizeTag+10);
pBuffer=m_byteID3v24.GetData();
m_ID3v24Version=(header[3]<<8)|(header[4]);
}
break;
default:
pBuffer=NULL;
}
//ID3v2タグバッファが確保されている場合は、
//ID3v2タグの内容をバッファに読み込みます。

if (pBuffer){
::CopyMemory(pBuffer,&header,10);
UINT size=infile->Read(pBuffer+10,sizeTag);
if (size<sizeTag) return FALSE;
}
}
else{
//フレームヘッダーではなく、ID3v2タグでもない場合は、
//読み飛ばし位置を1バイト進めてから再び読み直します。

m_dwSkipOffset++;
//1メガバイト以上検索してもフレームヘッダーが見つからなかった場合は、エラー終了します。
if (m_dwSkipOffset>0x100000) return FALSE;
}
}

//フレームヘッダーから得られた値を、それぞれメンバー変数に保存します。
m_iVersion =frameHeader.version; //MPEGバージョン番号
m_iLayer =frameHeader.layer; //レイヤー番号
m_iBitRate =frameHeader.bitRate; //ビットレート
m_sizeFrame =frameHeader.frameSize; //MP3ブロックサイズ

//MPEGLAYER3WAVEFORMAT構造体を作成します。
MPEGLAYER3WAVEFORMAT mp3wf={0};

mp3wf.wfx.wFormatTag =WAVE_FORMAT_MPEGLAYER3;
mp3wf.wfx.nChannels =frameHeader.channel;
mp3wf.wfx.nSamplesPerSec =frameHeader.sampleRate;
mp3wf.wfx.nAvgBytesPerSec =(frameHeader.bitRate*1000)/8;
mp3wf.wfx.nBlockAlign =1;
mp3wf.wfx.wBitsPerSample =0;
mp3wf.wfx.cbSize =MPEGLAYER3_WFX_EXTRA_BYTES;

mp3wf.wID =MPEGLAYER3_ID_MPEG;
mp3wf.fdwFlags =(frameHeader.padding)?MPEGLAYER3_FLAG_PADDING_ON:MPEGLAYER3_FLAG_PADDING_OFF;
mp3wf.nBlockSize =frameHeader.frameSize;
mp3wf.nFramesPerBlock =1;
mp3wf.nCodecDelay =1393;

//Xingヘッダーがあるか調べます。
m_uFlagBitRate=CBR;
DWORD offset=(frameHeader.version==1)?
((frameHeader.channel==1)?17:32):((frameHeader.channel==1)?9:17);
infile->Seek(m_dwSkipOffset+offset+4,CFile::begin);
if (infile->Read(header,4)!=4) return FALSE;

//Xingヘッダーがある場合
if ((::memcmp(header,"Xing",4)==0)||
(::memcmp(header,"Info",4)==0))
{
m_uFlagBitRate=VBR;
if (infile->Read(header,4)!=4) return FALSE;
DWORD dwflags=(((((header[0]<<8)|header[1])<<8)|header[2])<<8)|header[3];
if (dwflags&1){//フレーム数の取得
if (infile->Read(header,4)!=4) return FALSE;
m_nFrames=(((((header[0]<<8)|header[1])<<8)|header[2])<<8)|header[3];
}
if (dwflags&2){//データ総数
if (infile->Read(header,4)!=4) return FALSE;
m_dwStreamSize=(((((header[0]<<8)|header[1])<<8)|header[2])<<8)|header[3];
}
if (dwflags&4){//TOCエントリー
if (infile->Read(m_vbrTOC,100)!=100) return FALSE;
}
if (dwflags&8){//VBR品質((高)0〜100(低))
if (infile->Read(header,4)!=4) return FALSE;
m_vbrQuality=(((((header[0]<<8)|header[1])<<8)|header[2])<<8)|header[3];
}
}

//すべてのフレームを調べて、フレーム総数と平均ビットレートを算出します。
int nFrames=0;
//ビットレートの合計
double ttlBitRate=0.0;
//データ総数
DWORD dwStreamSize=0;
//VBRではない場合は、既に読み込んだ第一フレームの分も加算します。
if (m_uFlagBitRate!=VBR){
nFrames++;
dwStreamSize+=m_sizeFrame;
}
//検索するファイル位置を算出します。
offset=m_dwSkipOffset+m_sizeFrame;

//すべてのフレームについて調べます。
while(TRUE){
//次のフレームの先頭位置を探します。
BOOL bFind=FALSE;
while(TRUE){
//読み飛ばし位置に移動してから4バイト読みます。
infile->Seek(offset,CFile::begin);
if (infile->Read(header,4)!=4) break;
//フレームヘッダーかどうか調べます。
if (_ParseFrameHeader((PBYTE)&header,&frameHeader)){
bFind=TRUE;
break;
}
//見つからない場合は、ファイルポインタを1つ進めます。
offset++;
}
if (!bFind) break;
//第一フレームのビットレートと違えば、ABRにします。
if ((m_uFlagBitRate==CBR)&&(m_iBitRate!=frameHeader.bitRate))
m_uFlagBitRate=ABR;

ttlBitRate+=(double)frameHeader.bitRate;
offset+=frameHeader.frameSize;
dwStreamSize+=frameHeader.frameSize;
nFrames++;
}
//平均ビットレートを算出します。
m_iAveBitRate=(int)(ttlBitRate/(double)nFrames);
if ((m_uFlagBitRate!=VBR)||(!m_nFrames)) m_nFrames=nFrames;
if (!m_dwStreamSize) m_dwStreamSize=dwStreamSize;

//ID3v1タグがあるか調べます。
//ファイル末尾から128バイト戻った位置にシークします。

infile->Seek(fileSize-128,CFile::begin);

UINT size=infile->Read(header,128);
if (size<128) return FALSE;

//"TAG"がある場合。(ID3v1タグ)
if (::memcmp(header,"TAG",3)==0){
//バージョン番号を保存します。
m_id3v1Version=0x100|((header[125]=='\0')?1:0);
//バッファに生データを保存します。
m_byteId3v1.SetSize(128);
::CopyMemory(m_byteId3v1.GetData(),&header,128);
}

//MP3ストリームサイズは、ファイル長さから読み飛ばし位置と、
//ID3v1タグがあれば、そのサイズを差し引きます。

DWORD dwFileStreamSize=fileSize-m_dwSkipOffset-m_byteId3v1.GetSize();
if (dwFileStreamSize<m_dwStreamSize) m_dwStreamSize=dwFileStreamSize;

//ACMドライバーを開きます。
MMRESULT mmr=::acmDriverOpen(&m_hDriver,m_hDriverIdDec,0);
if (mmr!=MMSYSERR_NOERROR) m_hDriver=NULL;

//MPEGLAYER3WAVEFORMAT構造体からWAVEFORMATEX構造体を取得します。
m_wfx.wFormatTag=WAVE_FORMAT_PCM;
m_wfx.cbSize=sizeof(WAVEFORMATEX);
mmr=::acmFormatSuggest(m_hDriver,&mp3wf.wfx,&m_wfx,sizeof(WAVEFORMATEX),ACM_FORMATSUGGESTF_WFORMATTAG);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//ACMストリームをオープンします。
mmr=::acmStreamOpen(&m_hAcmStream,m_hDriver,&mp3wf.wfx,&m_wfx,NULL,0,0,ACM_STREAMOPENF_NONREALTIME);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//フレームサイズに相当するデコード後のデータサイズを取得します。
DWORD sizeFrameDecoded;
::acmStreamSize(m_hAcmStream,m_sizeFrame,&sizeFrameDecoded,ACM_STREAMSIZEF_SOURCE);

//MP3ストリームサイズからデコード後の総データサイズを取得します。
::acmStreamSize(m_hAcmStream,m_dwStreamSize,(LPDWORD)&m_nDataSize,ACM_STREAMSIZEF_SOURCE);
//フレームサイズに相当するデコード後のデータサイズの平均値を算出します。
m_sizeAveFrameDecoded=ROUND((double)m_sizeFrame*(double)m_nDataSize/(double)m_dwStreamSize);

//作業用バッファを確保します。
DWORD dwSrcSize=m_sizeFrame; //転送元バッファサイズ
DWORD dwDstSize=sizeFrameDecoded; //転送先バッファサイズ
//転送先バッファサイズは4608バイト以上になるようにします。(libmpg123対応)
if (dwDstSize<4608) dwDstSize=4608*2;

m_byteSrcBuf.SetSize(dwSrcSize); //転送元バッファ
m_byteDstBuf.SetSize(dwDstSize); //転送先バッファ

//ACMストリームヘッダーを作成します。
::ZeroMemory(&m_AcmStreamHeader,sizeof(ACMSTREAMHEADER));
m_AcmStreamHeader.cbStruct =sizeof(ACMSTREAMHEADER);
m_AcmStreamHeader.pbSrc =m_byteSrcBuf.GetData();
m_AcmStreamHeader.cbSrcLength=(DWORD)(DWORD_PTR)m_byteSrcBuf.GetSize();
m_AcmStreamHeader.pbDst =m_byteDstBuf.GetData();
m_AcmStreamHeader.cbDstLength=(DWORD)(DWORD_PTR)m_byteDstBuf.GetSize();

::acmStreamPrepareHeader(m_hAcmStream,&m_AcmStreamHeader,0);

//現在のファイル位置を、読み飛ばし位置に移動します。
infile->Seek(m_dwSkipOffset,CFile::begin);

//第一フレームをドライバーに読み込ませます。
size=infile->Read(m_AcmStreamHeader.pbSrc,m_AcmStreamHeader.cbSrcLength);
if (!size) return FALSE;
//実際に読み込んだサイズを有効なサイズとします。
m_AcmStreamHeader.cbSrcLengthUsed=size;
//Mp3データからWave音源データに変換します。
mmr=::acmStreamConvert(m_hAcmStream,&m_AcmStreamHeader,ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START);
//デコード済みデータがある場合。
if ((mmr==MMSYSERR_NOERROR)&&(m_AcmStreamHeader.cbDstLengthUsed>0)){
//キャッシュに、デコード済みデータをコピーします。
m_byteCache.SetSize(m_AcmStreamHeader.cbDstLengthUsed);
::CopyMemory(m_byteCache.GetData(),m_AcmStreamHeader.pbDst,m_AcmStreamHeader.cbDstLengthUsed);
}
else
//キャッシュを初期化します。
m_byteCache.SetSize(0);

//指定の演奏位置を、読み込んだMp3データに相当する音源データ分進めます。
DWORD dwPosRecent=(DWORD)infile->GetPosition();
UINT indexFrame=(dwPosRecent-m_dwSkipOffset)/m_sizeFrame;
m_indexSample=(m_sizeAveFrameDecoded*indexFrame)/m_wfx.nBlockAlign;

//MPEGLAYER3WAVEFORMAT構造体を保存しておきます。
SetMpegLayer3WaveFormat(0,&mp3wf);

return TRUE;
}


MP3ファイル再生時に音源データ読み込みのために、基底クラスから呼び出されるオーバーライド関数です。

//MP3からWave音源に変換してストリーム読み込み
UINT CMP3File::StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample)
{
ASSERT(!::IsBadWritePtr(pBuffer,nBufSize));
ASSERT(AfxIsValidAddress(pIndexSample,sizeof(UINT)));

//ACMストリームハンドルが無効な場合は、CDDAFile::StreamIn関数を呼び出します。
if (!IsMP3File()) return CDDAFile::StreamIn(pBuffer,nBufSize,pIndexSample);

//現在位置と指定位置を算出します。
DWORD dwPosRecent=(DWORD)m_infile->GetPosition();
UINT indexFrame=ROUND((double)(*pIndexSample*m_wfx.nBlockAlign)/(double)m_sizeAveFrameDecoded);
DWORD dwPosRead=m_dwSkipOffset+(m_sizeFrame*indexFrame);

DWORD fdwConvert=ACM_STREAMCONVERTF_BLOCKALIGN;
if (indexFrame==0) fdwConvert|=ACM_STREAMCONVERTF_START;
//現在演奏中で現在位置と指定位置が100_秒以上違えば、指定位置にシークします。
if ((IsPlaying())&&
((DWORD)abs((int)(dwPosRead-dwPosRecent))>(m_wfx.nAvgBytesPerSec/10))){
//前回の残りの音源データ数を0にします。
m_byteCache.SetSize(0);

//フレームヘッダーを探します。
BYTE header[4];
FRAMEHEADER frameHeader;
while(TRUE){
//読み飛ばし位置に移動してから4バイト読みます。
m_infile->Seek(dwPosRead,CFile::begin);
if (m_infile->Read(header,4)!=4) return FALSE;
//フレームヘッダーかどうか調べます。
if (_ParseFrameHeader((PBYTE)&header,&frameHeader)) break;
//フレームヘッダーではない場合は、
//読み飛ばし位置を1バイト進めてから再び読み直します。

dwPosRead++;
}
//探し出したフレームヘッダーの先頭位置に移動します。
m_infile->Seek(dwPosRead,CFile::begin);
}

//キャッシュ内のデータサイズを取得します。
UINT nCaches=(UINT)(UINT_PTR)m_byteCache.GetSize();
//MP3ストリーム最後尾の位置
DWORD dwFileEnd=m_dwStreamSize+m_dwSkipOffset;

//キャッシュ内のデータサイズがバッファサイズに満たない場合。
while(nCaches<nBufSize){

UINT size=m_infile->Read(m_AcmStreamHeader.pbSrc,m_AcmStreamHeader.cbSrcLength);
if (!size) break;
if (size!=m_AcmStreamHeader.cbSrcLength)
fdwConvert|=ACM_STREAMCONVERTF_END;

//実際に読み込んだサイズを有効なサイズとします。
m_AcmStreamHeader.cbSrcLengthUsed=size;
//現在のファイル位置を取得します。
DWORD dwPos=(DWORD)m_infile->GetPosition();
//ID3v1タグを読み込んだ場合はその分を差し引きます。
if (dwPos>dwFileEnd){
DWORD dwTaiDust=dwPos-dwFileEnd;
if (m_AcmStreamHeader.cbSrcLengthUsed<dwTaiDust) break;
else m_AcmStreamHeader.cbSrcLengthUsed-=dwTaiDust;
fdwConvert|=ACM_STREAMCONVERTF_END;
}
//MP3データからWave音源データに変換します。
MMRESULT mmr=::acmStreamConvert(m_hAcmStream,&m_AcmStreamHeader,fdwConvert);

fdwConvert=ACM_STREAMCONVERTF_BLOCKALIGN;

//lameACM対応
#define MP3_NEED_MORE 0x200

if (mmr==MP3_NEED_MORE) continue;
//デコードエラーの場合は、エラー終了します。
else if (mmr!=MMSYSERR_NOERROR) return FALSE;
//デコード済みデータがある場合。
else if (m_AcmStreamHeader.cbDstLengthUsed>0){
//キャッシュの末尾に、デコード済みデータを追加します。
nCaches=(UINT)(UINT_PTR)m_byteCache.GetSize();
m_byteCache.SetSize(nCaches+m_AcmStreamHeader.cbDstLengthUsed);
::CopyMemory(m_byteCache.GetData()+nCaches,m_AcmStreamHeader.pbDst,
m_AcmStreamHeader.cbDstLengthUsed);
nCaches=(UINT)(UINT_PTR)m_byteCache.GetSize();
}
}
//バッファに取り込むサイズを算定して、キャッシュからデータをコピーします。
UINT readBytes=min(nBufSize,nCaches);
::CopyMemory(pBuffer,m_byteCache.GetData(),readBytes);

//キャッシュ内のデータの内、バッファに取り込んだデータを削除してから、
//残りのデータを左シフトします。

m_byteCache.RemoveAt(0,readBytes);

//指定の演奏位置を、読み込んだMP3データに相当する音源データ分進めます。
dwPosRecent=(DWORD)m_infile->GetPosition();
indexFrame=(dwPosRecent-m_dwSkipOffset)/m_sizeFrame;
*pIndexSample=(m_sizeAveFrameDecoded*indexFrame)/m_wfx.nBlockAlign;
//読み込んだバイト数を返して終了します。
return readBytes;
}


CMP3Fileクラスのインスタンスを作成した後、CWaveFile::OpenFile関数でMP3ファイルを開きます。
エラーがなければCWaveFile::Play関数で演奏を開始します。演奏を中断したり演奏終了後、WAVE出力デバイスを閉じる場合は、CWaveFile::Stop関数を呼び出して下さい。
入力したMP3ファイルが再生されれば成功です。

WaveOut.h
WaveOut.cpp
WaveFile.h
WaveFile.cpp
CDDAFile.h
CDDAFile.cpp
MP3File.h
MP3File.cpp



posted by ひろし at 00:57| Comment(0) | MP3ファイルフォーマット | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。