2011年11月18日

MP3ファイルの書き込み 第1部



ACM(Audio Compression Manager)を介してWAVE音源データMP3ストリームにエンコードしてMP3ファイルに書き込む手順は、以下の通りです。

  1. MP3エンコードに必要なLAME ACMをインストールします。
  2. プログラムから、目的のACMドライバーを探し出します。
  3. ACMドライバーを開いて使用可能なエンコードフォーマットの中から、目的の仕様を満たすMPEGLAYER3WAVEFORMAT構造体を一つ選択します。
  4. エンコードに特別な仕様を要求しない場合は、システムが推奨するACMドライバーMPEGLAYER3WAVEFORMAT構造体を取得します。
  5. WAVE音源データ読み込み用のバッファサイズから、MP3ストリームを一時格納するための作業用バッファサイズを取得します。
  6. 作業用バッファを確保して、ACMストリームヘッダーを作成します。
  7. 読み込んだWAVE音源データMP3ストリームにエンコードして、MP3ファイルに書き込みます。
  8. WAVE音源データの読み込みが終了したら、ACMストリームヘッダーを削除してから、ACMストリームをクローズします。
  9. 独自にACMドライバーを選択してオープンしている場合は、ACMドライバーをクローズします。





ACMドライバーとそのドライバーがサポートしているフォーマットの中から、目的に一致したフォーマットの選択を簡便にするためのSTDCALL関数です。

//ドライバー列挙のコールバック関数
BOOL __stdcall _acmDriverEnumCallback(HACMDRIVERID hdriverId,DWORD dwInstance,DWORD dwSupport)
{
CObArray* plistHadid=(CObArray*)(DWORD_PTR)dwInstance;
ASSERT(AfxIsValidAddress(plistHadid,sizeof(CObArray)));
plistHadid->Add((CObject*)hdriverId);
return TRUE;
}

//ドライバーIDリストの取得
BOOL __stdcall _GetAcmDriverIDList(CObArray& listDriverId)
{
MMRESULT mmr=::acmDriverEnum(_acmDriverEnumCallback,(DWORD)(DWORD_PTR)&listDriverId,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;
return TRUE;
}

//ドライバーIDの検索(ドライバー名で検索)
HACMDRIVERID __stdcall _FindAcmDriverID(LPCTSTR lpszDriverName)
{
if (!lpszDriverName) return FALSE;

//ドライバーIDリストの取得
CObArray listHadid;
if (!_GetAcmDriverIDList(listHadid)) return FALSE;
int nCount=(int)listHadid.GetCount();
if (!nCount) return FALSE;
for (int i=0;i<nCount;i++){
//ACMDRIVERDETAILS構造体を取得して、ドライバー名を比較します。
ACMDRIVERDETAILS add={0};
add.cbStruct=sizeof(ACMDRIVERDETAILS);
HACMDRIVERID hadid=(HACMDRIVERID)listHadid.GetAt(i);
MMRESULT mmr=::acmDriverDetails(hadid,&add,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;
if ((::StrCmpI(add.szShortName,lpszDriverName)==0)||
(::StrCmpI(add.szLongName,lpszDriverName)==0))
return hadid;
}
return FALSE;
}

//指定ドライバーIDの指定フォーマットタグ内で、
//指定フォーマット番号の、WAVEFORMATEX構造体を取得します。

BOOL __stdcall _GetAcmDriverWaveFormat(HACMDRIVERID hadid,WORD wFormatTag,DWORD dwFormatIndex,
LPWAVEFORMATEX pwfx,int iFormatSize)
{
//ドライバーIDの詳細データの取得。
ACMDRIVERDETAILS add={0};
add.cbStruct=sizeof(ACMDRIVERDETAILS);
MMRESULT mmr=::acmDriverDetails(hadid,&add,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//選択されたドライバーを開きます。
HACMDRIVER hDriver=NULL;
::acmDriverOpen(&hDriver,hadid,0);

//サポートしているフォーマットタグの詳細を調べます。
for(UINT i=0;i<add.cFormatTags;i++){
ACMFORMATTAGDETAILS aftd={0};
aftd.cbStruct=sizeof(ACMFORMATTAGDETAILS);
aftd.dwFormatTagIndex=i;
aftd.dwFormatTag =WAVE_FORMAT_UNKNOWN;
aftd.fdwSupport =0;
::acmFormatTagDetails(hDriver,&aftd,ACM_FORMATTAGDETAILSF_INDEX);
//選択されたフォーマットタグの場合。
if ((wFormatTag==aftd.dwFormatTag)&&(dwFormatIndex<aftd.cStandardFormats)){
ACMFORMATDETAILS afd={0};
afd.cbStruct =sizeof(ACMFORMATDETAILS);
afd.dwFormatIndex =dwFormatIndex;
afd.dwFormatTag =aftd.dwFormatTag;
afd.fdwSupport =0;
afd.pwfx =pwfx;
afd.cbwfx =iFormatSize;
::acmFormatDetails(hDriver,&afd,ACM_FORMATDETAILSF_INDEX);
}
}
::acmDriverClose(hDriver, 0);
return TRUE;
}

//システムの推奨するドライバーIDの取得
HACMDRIVERID __stdcall _GetDriverIDSuggest(LPWAVEFORMATEX pwfxSrc,LPWAVEFORMATEX pwfxDst,DWORD cbwfxDst)
{
//システムの推奨するフォーマットを取得します。
MMRESULT mmr=::acmFormatSuggest(NULL,pwfxSrc,pwfxDst,cbwfxDst,ACM_FORMATSUGGESTF_WFORMATTAG);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//ACMストリームをオープンします。
HACMSTREAM has;
mmr=::acmStreamOpen(&has,NULL,pwfxSrc,pwfxDst,NULL,0,0,ACM_STREAMOPENF_NONREALTIME);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//ACMストリームからドライバーIDを取得します。
HACMDRIVERID hadid=NULL;
mmr=::acmDriverID((HACMOBJ)has,&hadid,0);

//ACMストリームをクローズします。
::acmStreamClose(has,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;
return hadid;
}


CMP3Fileクラス内に保持するACMドライバーIDMPEGLAYER3WAVEFORMAT構造体を扱う為のメンバー関数です。

//現在システムによって推奨されているドライバーIDの取得
void CMP3File::GetDriverIDSuggest(HACMDRIVERID* phadidEnc,HACMDRIVERID* phadidDec)
{
MPEGLAYER3WAVEFORMAT mp3wf;

//エンコード用ドライバーIDの取得
if (AfxIsValidAddress(phadidEnc,sizeof(HACMDRIVERID))){
::ZeroMemory(&mp3wf,sizeof(MPEGLAYER3WAVEFORMAT));
mp3wf.wfx.wFormatTag=WAVE_FORMAT_MPEGLAYER3;
mp3wf.wfx.cbSize =MPEGLAYER3_WFX_EXTRA_BYTES;
HACMDRIVERID hadid=_GetDriverIDSuggest(&m_wfx,&mp3wf.wfx,sizeof(MPEGLAYER3WAVEFORMAT));
//ドライバーIDの取得に成功したら、引数に格納します。
if (hadid) *phadidEnc=hadid;
}
//デコード用ドライバーIDの取得
if (AfxIsValidAddress(phadidDec,sizeof(HACMDRIVERID))){
HACMDRIVERID hadid=NULL;
//現在演奏中なら、ACMストリームからドライバーIDを取得します。
if (m_hAcmStream) {
MMRESULT mmr=::acmDriverID((HACMOBJ)m_hAcmStream,&hadid,0);
if (mmr!=MMSYSERR_NOERROR) hadid=NULL;
}
if (!hadid){
//MPEGLAYER3WAVEFORMAT構造体を作成します。
::ZeroMemory(&mp3wf,sizeof(MPEGLAYER3WAVEFORMAT));
GetMpegLayer3WaveFormat(0,&mp3wf);

WAVEFORMATEX wfx={0};
wfx.wFormatTag=WAVE_FORMAT_PCM;
wfx.cbSize=sizeof(WAVEFORMATEX);
hadid=_GetDriverIDSuggest(&mp3wf.wfx,&m_wfx,sizeof(WAVEFORMATEX));
}
//ドライバーIDの取得に成功したら、引数に格納します。
if (hadid) *phadidDec=hadid;
}
}

//ドライバーIDの取得
void CMP3File::GetDriverID(HACMDRIVERID* phadidEnc,HACMDRIVERID* phadidDec)
{
if (AfxIsValidAddress(phadidEnc,sizeof(HACMDRIVERID))){
if (!m_hDriverIdEnc) GetDriverIDSuggest(&m_hDriverIdEnc,0);
*phadidEnc=m_hDriverIdEnc;
}
if (AfxIsValidAddress(phadidDec,sizeof(HACMDRIVERID))){
if (!m_hDriverIdDec) GetDriverIDSuggest(0,&m_hDriverIdDec);
*phadidDec=m_hDriverIdDec;
}
}

//MPEGLAYER3WAVEFORMAT構造体の作成
BOOL CMP3File::FillMpegLayer3WaveFormat(MPEGLAYER3WAVEFORMAT* pmp3wf)
{
ASSERT(AfxIsValidAddress(pmp3wf,sizeof(MPEGLAYER3WAVEFORMAT),TRUE));
if(!AfxIsValidAddress(pmp3wf,sizeof(MPEGLAYER3WAVEFORMAT),TRUE)) return FALSE;

int samplesPerSec=(m_wfx.wFormatTag==WAVE_FORMAT_PCM)?m_wfx.nSamplesPerSec:44100;
int channels=(m_wfx.wFormatTag==WAVE_FORMAT_PCM)?m_wfx.nChannels:2;
int bitRate=(m_iBitRate)?m_iBitRate:128;
::ZeroMemory(pmp3wf,sizeof(MPEGLAYER3WAVEFORMAT));
pmp3wf->wfx.wFormatTag =WAVE_FORMAT_MPEGLAYER3;
pmp3wf->wfx.nChannels =channels;
pmp3wf->wfx.nSamplesPerSec =samplesPerSec;
pmp3wf->wfx.nAvgBytesPerSec =(bitRate*1000)/8;
pmp3wf->wfx.nBlockAlign =1;
pmp3wf->wfx.wBitsPerSample =0;
pmp3wf->wfx.cbSize =MPEGLAYER3_WFX_EXTRA_BYTES;

pmp3wf->wID =MPEGLAYER3_ID_MPEG;
pmp3wf->fdwFlags =MPEGLAYER3_FLAG_PADDING_OFF;
pmp3wf->nBlockSize =(1152/8)*bitRate*1000/samplesPerSec;
pmp3wf->nFramesPerBlock =1;
pmp3wf->nCodecDelay =1393;
return TRUE;
}

//MPEGLAYER3WAVEFORMAT構造体の取得
BOOL CMP3File::GetMpegLayer3WaveFormat(MPEGLAYER3WAVEFORMAT* pmp3wfEnc,
MPEGLAYER3WAVEFORMAT* pmp3wfDec)
{
if (AfxIsValidAddress(pmp3wfEnc,sizeof(MPEGLAYER3WAVEFORMAT),TRUE)){
if ((m_mp3wfEnc.wfx.wFormatTag==WAVE_FORMAT_MPEGLAYER3)&&
(m_mp3wfEnc.wfx.cbSize==MPEGLAYER3_WFX_EXTRA_BYTES))
::CopyMemory(pmp3wfEnc,&m_mp3wfEnc,sizeof(MPEGLAYER3WAVEFORMAT));
else if((m_mp3wfDec.wfx.wFormatTag==WAVE_FORMAT_MPEGLAYER3)&&
(m_mp3wfDec.wfx.cbSize==MPEGLAYER3_WFX_EXTRA_BYTES))
{
::CopyMemory(&m_mp3wfEnc,&m_mp3wfDec,sizeof(MPEGLAYER3WAVEFORMAT));
::CopyMemory(pmp3wfEnc,&m_mp3wfEnc,sizeof(MPEGLAYER3WAVEFORMAT));
}
else return FillMpegLayer3WaveFormat(pmp3wfEnc);
}
else if (AfxIsValidAddress(pmp3wfDec,sizeof(MPEGLAYER3WAVEFORMAT),TRUE)){
if ((m_mp3wfDec.wfx.wFormatTag==WAVE_FORMAT_MPEGLAYER3)&&
(m_mp3wfDec.wfx.cbSize==MPEGLAYER3_WFX_EXTRA_BYTES))
::CopyMemory(pmp3wfDec,&m_mp3wfDec,sizeof(MPEGLAYER3WAVEFORMAT));
else return FillMpegLayer3WaveFormat(pmp3wfDec);
}
return TRUE;
}


CWaveFile::SaveFile関数から呼び出されるCWaveFile::ResetStream関数にオーバーライドして、メタデータを残したままデコーダーストリームリセットします。

//メタデータを残したままで、ファイルを再度開き直します。(オーバーライド)
BOOL CMP3File::ResetStream()
{
//ID3タグの内容を保存しておきます。
CByteArray byteId3v1Safe,byteId3v22Safe;
CByteArray byteId3v23Safe,byteId3v24Safe;

byteId3v1Safe.Copy(m_byteID3v1);
byteId3v22Safe.Copy(m_byteID3v22);
byteId3v23Safe.Copy(m_byteID3v22);
byteId3v24Safe.Copy(m_byteID3v24);

if (!CWaveFile::ResetStream()) return FALSE;

//保存しておいたID3タグの内容を戻します。
m_byteID3v1.Copy(byteId3v1Safe);
m_byteID3v22.Copy(byteId3v22Safe);
m_byteID3v22.Copy(byteId3v23Safe);
m_byteID3v24.Copy(byteId3v24Safe);

return TRUE;
}


CWaveFile::SaveFile関数から呼び出されるCWaveFile::WriteFile関数にオーバーライドして、現在演奏中のファイルをMP3ファイルに保存します。

//MP3ファイルの書き込み(オーバーライド)
BOOL CMP3File::WriteFile(LPCTSTR lpszFileName)
{
//拡張子名の取得
LPTSTR pExt=::PathFindExtension(lpszFileName);
//拡張子名が「.mp3」若しくは「.mpeg」で無い場合は、
//CWaveFile::WriteFile関数を呼び出します。

if ((::StrCmpNI(pExt,_T(".mp3"),4)!=0)&&
(::StrCmpNI(pExt,_T(".mpeg"),5)!=0))
{
return CWaveFile::WriteFile(lpszFileName);
}

//ファイルを開きます。
CFile outfile;
if (!outfile.Open(lpszFileName,CFile::modeCreate|CFile::modeReadWrite)) return FALSE;

//ID3v2.2タグがあれば、ファイルに書き込みます。
INT_PTR v22size=m_byteID3v22.GetSize();
BYTE* v22data=m_byteID3v22.GetData();
if ((v22data)&&(v22size)) outfile.Write(v22data,(UINT)v22size);

//ID3v2.3タグがあれば、ファイルに書き込みます。
INT_PTR v23size=m_byteID3v23.GetSize();
BYTE* v23data=m_byteID3v23.GetData();
if ((v23data)&&(v23size)) outfile.Write(v23data,(UINT)v23size);

//ID3v2.4タグがあれば、ファイルに書き込みます。
INT_PTR v24size=m_byteID3v24.GetSize();
BYTE* v24data=m_byteID3v24.GetData();
if ((v24data)&&(v24size)) outfile.Write(v24data,(UINT)v24size);

//MP3ファイルの場合は、MP3データの内容をそのまま書き写します。
if (IsMP3File())
{
//MP3データを書き込む為のバッファを作成します。
CByteArray array;
int sizeBuf=GetSizeOfSaveBuffer();
array.SetSize(sizeBuf);
BYTE* buffer=array.GetData();

//MP3データを書き込みます。
UINT readSize=0;
m_infile->Seek(m_dwSkipOffset,CFile::begin);
while(readSize<m_dwStreamSize){
UINT sizeToRead=min(sizeBuf,m_dwStreamSize-readSize);
UINT readBytes=m_infile->Read(buffer,sizeToRead);
if (!readBytes) break;
outfile.Write(buffer,readBytes);
readSize+=readBytes;
}
}
//それ以外の場合は、エンコードし直します。
else if (!StreamOut(&outfile,m_hDriverIdEnc,&m_mp3wfEnc)){
outfile.Close();
::DeleteFile(lpszFileName);
return FALSE;
}

//最終フレームヘッダー位置を探します。
ULONGLONG endOfFile=outfile.GetPosition();
ULONGLONG ptrFile=endOfFile-4;

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

ptrFile--;
}

UINT sizeFrame=(UINT)(ULONGLONG)(endOfFile-ptrFile);
//実際の最終フレームサイズが足りない場合。
if (sizeFrame<(UINT)fh.frameSize){
//足りないサイズ分、0xFFを書き足します。
outfile.SeekToEnd();
CByteArray byteBuf;
UINT sizePadding=fh.frameSize-sizeFrame;
byteBuf.SetSize(sizePadding);
PBYTE lpBuf=byteBuf.GetData();
::memset(lpBuf,0xFF,sizePadding);
outfile.Write(lpBuf,sizePadding);
}
//実際の最終フレームサイズが多い場合。
else if (sizeFrame>(UINT)fh.frameSize)
//フレームの末尾に移動します。
outfile.Seek(ptrFile+fh.frameSize,CFile::begin);
//正常な場合。
else
//ファイル末尾に移動します。
outfile.SeekToEnd();

//ID3v1タグを書き込みます。
INT_PTR v1size=m_byteID3v1.GetSize();
BYTE* v1data=m_byteID3v1.GetData();
if ((v1data)&&(v1size)){
ASSERT(v1size==128);
outfile.Write(v1data,(UINT)v1size);
}
outfile.Close();

return TRUE;
}


ドライバーIDリストを取得する_GetAcmDriverIDList関数、ドライバーIDを検索する_FindAcmDriverID関数、指定ドライバーID/フォーマットタグ内で、指定フォーマット番号のWAVEFORMATEX構造体を取得する_GetAcmDriverWaveFormat関数等を使って得られたACMドライバーIDMPEGLAYER3WAVEFORMAT構造体を引数にして、WAVE音源データMP3データストリームエンコードします。

//Mp3ストリームの書き込み
BOOL CMP3File::StreamOut(CFile* outfile,HACMDRIVERID hadid,MPEGLAYER3WAVEFORMAT* pmp3wf)
{
MMRESULT mmr=0;

//引数のドライバーIDが有効な値の場合は、このドライバーIDからドライバーを開きます。
HACMDRIVER hDriver=NULL;
if (hadid){
mmr=::acmDriverOpen(&hDriver,hadid,0);
if (mmr!=MMSYSERR_NOERROR) hDriver=NULL;
}

//MPEGLAYER3WAVEFORMAT構造体の取得
MPEGLAYER3WAVEFORMAT mp3wf={0};
if ((AfxIsValidAddress(pmp3wf,sizeof(MPEGLAYER3WAVEFORMAT)))&&
(pmp3wf->wfx.wFormatTag==WAVE_FORMAT_MPEGLAYER3))
::CopyMemory(&mp3wf,pmp3wf,sizeof(MPEGLAYER3WAVEFORMAT));
else{
mp3wf.wfx.wFormatTag = WAVE_FORMAT_MPEGLAYER3;
mp3wf.wfx.cbSize = MPEGLAYER3_WFX_EXTRA_BYTES;

mmr=::acmFormatSuggest(hDriver,&m_wfx,&mp3wf.wfx,sizeof(MPEGLAYER3WAVEFORMAT),ACM_FORMATSUGGESTF_WFORMATTAG);
if (mmr!=MMSYSERR_NOERROR) return FALSE;
}

HACMSTREAM hStream;
mmr=::acmStreamOpen(&hStream,hDriver,&m_wfx,&mp3wf.wfx,NULL,NULL,0,ACM_STREAMOPENF_NONREALTIME);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

DWORD dwSrcSize=GetSizeOfSaveBuffer();
//音源データを格納するバッファを作成します。
CByteArray byteSrcWave;
byteSrcWave.SetSize(dwSrcSize);
//バッファへのポインタを取得します。
PBYTE pSrcWave=byteSrcWave.GetData();

DWORD dwDstSize=0;
//出力バッファのサイズを取得します。
mmr=::acmStreamSize(hStream,dwSrcSize,&dwDstSize,ACM_STREAMSIZEF_SOURCE);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//エンコードデータを格納するバッファを作成します。
CByteArray byteDstMp3;
byteDstMp3.SetSize(dwDstSize);
//バッファへのポインタを取得します。
PBYTE pDstMp3=byteDstMp3.GetData();

//ストリームヘッダーの作成
ACMSTREAMHEADER AcmStreamHeader ={0};
AcmStreamHeader.cbStruct =sizeof(ACMSTREAMHEADER);
AcmStreamHeader.fdwStatus =0;
AcmStreamHeader.pbSrc =pSrcWave;
AcmStreamHeader.cbSrcLength =dwSrcSize;
AcmStreamHeader.cbSrcLengthUsed =dwSrcSize;
AcmStreamHeader.pbDst =pDstMp3;
AcmStreamHeader.cbDstLength =(DWORD)dwDstSize;
AcmStreamHeader.cbDstLengthUsed =0;
mmr=::acmStreamPrepareHeader(hStream,&AcmStreamHeader,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//読み込み用のインデックスをゼロにします。
UINT indexSample=0;
while(TRUE){
//バッファを初期化します。
::ZeroMemory(AcmStreamHeader.pbSrc,AcmStreamHeader.cbSrcLength);
::ZeroMemory(AcmStreamHeader.pbDst,AcmStreamHeader.cbDstLength);
//現在の演奏条件での音源データを取得します。
UINT readBytes=StreamIn(AcmStreamHeader.pbSrc,AcmStreamHeader.cbSrcLength,&indexSample);
AcmStreamHeader.cbSrcLengthUsed=readBytes;
//最後まで読み込んだら、ループを抜けます。
if (!readBytes) break;
//最後のフレームの末尾を読み終えたら、払い出しフラグを立てます。
if (readBytes!=AcmStreamHeader.cbSrcLength) fdwConvert|=ACM_STREAMCONVERTF_END;
//Wave音源データからMp3データに変換します。
mmr=::acmStreamConvert(hStream,&AcmStreamHeader,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;
//ファイルに音源データを書き込みます。
outfile->Write(AcmStreamHeader.pbDst,AcmStreamHeader.cbDstLengthUsed);
//オーナーウィンドウがあれば、プログレスバーの位置を通知します。
if (m_pWndOwner) m_pWndOwner->PostMessage(PBM_SETPOS,indexSample,0);
fdwConvert=ACM_STREAMCONVERTF_BLOCKALIGN;
}

//ACMストリームヘッダーを削除します。
::acmStreamUnprepareHeader(hStream,&AcmStreamHeader,0);
//ACMストリームをクローズします。
::acmStreamClose(hStream,0);
//ACMドライバーハンドルがあればクローズします。
if (hDriver) ::acmDriverClose(hDriver,0);

return TRUE;
}



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



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

メールアドレス:

ホームページアドレス:

コメント:

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


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

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