FC2ブログ

ホーム > MP3ファイルフォーマット > MP3ファイルの書き込み 第1部

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


現在演奏中のファイルをMP3ファイルに保存します。

//MP3ファイルの書き込み
BOOL CMP3File::SaveFile(LPCTSTR lpszFileName)
{
//書き込み対象のファイルがない場合は、FALSEを戻して終了します。
if (!IsOpen()) return FALSE;
//ID3タグの内容を保存しておきます。
CByteArray byteId3v1Safe;
CByteArray byteId3v22Safe;
CByteArray byteId3v23Safe;
CByteArray byteId3v24Safe;

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

//ドライバー内に残ったデコードデータの残りが
//保存ファイルデータに混じらないようにするため、
//念のため現在演奏中のファイルを、再度開き直しておきます。

if (!OpenFile(m_strPathName)) return FALSE;

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

//拡張子名の取得
LPTSTR pExt=::PathFindExtension(lpszFileName);
//拡張子名が「.mp3」若しくは「.mpeg」で無い場合は、
//CDDAFile::SaveFile関数を呼び出します。

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

//現在演奏中のファイルがMP3ファイルの場合。
if (IsMp3File()){
//現在読み込んでいるMP3ファイルに書き込む場合。
if (m_strPathName==lpszFileName)
{
//テンポラリフォルダーのディレクトリパスを取得します。
DWORD sizeTempPath=::GetTempPath(0,0);
CString strTempPath;
LPTSTR lpszPath=strTempPath.GetBufferSetLength(sizeTempPath);
sizeTempPath=::GetTempPath(sizeTempPath,lpszPath);
if (!sizeTempPath) return FALSE;

//テンポラリファイルを作成し、ファイル名を取得します。
CStringA strPathA=(CStringA)strTempPath;
CStringA strPrefix=(CStringA)m_infile->GetFileName();
CStringA strTempFile;
LPTSTR lpszFile=(LPTSTR)strTempFile.GetBufferSetLength(sizeTempPath+16);
if (!::GetTempFileName((LPCTSTR)strPathA.GetBuffer(),(LPCTSTR)strPrefix.GetBuffer(),0,lpszFile))
return FALSE;

//テンポラリファイルにMP3ファイルを書き込みます。
if (!SaveMp3File(strTempFile.GetBuffer())) return FALSE;

//Mp3ファイルにテンポラリファイルを重ね書きします。
Delete();
::CopyFile(strTempFile.GetBuffer(),strPath.GetBuffer(),FALSE);

// テンポラリファイルの削除
::remove(strTempFile);
return TRUE;
}
}
//別のファイルに書き込む場合。
if (!SaveMp3File(lpszFileName)) return FALSE;
return TRUE;
}

//MP3ファイル(id3tag)の書き込み
BOOL CMP3File::SaveMp3File(LPCTSTR lpszFileName)
{
if (!IsOpen()) return FALSE;

//ファイルを開きます。
CFile outfile;
if (!outfile.Open(lpszFileName,CFile::modeCreate|CFile::modeWrite)) 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_mp3StreamSize){
UINT sizeToRead=min(sizeBuf,m_mp3StreamSize-readSize);
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;
}

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


CAcmDriverDlgクラスで取得したドライバーIDとフォーマットを引数にして、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;
UINT readTotalSize=0;
//変換フラグの初期化
DWORD fdwConvert=ACM_STREAMCONVERTF_START|ACM_STREAMCONVERTF_BLOCKALIGN;

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,fdwConvert);
if (mmr!=MMSYSERR_NOERROR) return FALSE;
//ファイルに音源データを書き込みます。
outfile->Write(AcmStreamHeader.pbDst,AcmStreamHeader.cbDstLengthUsed);
readTotalSize+=readBytes;
//オーナーウィンドウがあれば、プログレスバーの位置を通知します。
if (m_pWndOwner) m_pWndOwner->PostMessage(PBM_SETPOS,readTotalSize,0);
fdwConvert=ACM_STREAMCONVERTF_BLOCKALIGN;
}

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

return TRUE;
}



<<LAME ACMのインストールページの先頭MP3ファイルの書き込み 第2部>>


スポンサーサイト



コメント: 0

この記事へのコメント
ブログ著者にのみ知らせます。

Trackback+Pingback: 0

TrackBack URL for this entry
http://hiroshi0945.blog75.fc2.com/tb.php/64-37149626
Listed below are links to weblogs that reference
MP3ファイルの書き込み 第1部 from マルチメディアファイルフォーマット

Home > MP3ファイルフォーマット > MP3ファイルの書き込み 第1部

タグクラウド
ブロとも申請フォーム

この人とブロともになる

ブロとも一覧

このページの先頭へ戻る