2011年02月16日

WAVEファイルの書き込み



WAVEファイルはWindowsのマルチメディアアプリケーションを収めたRIFF形式Resource Interchange File Format)という形式をとっております。

WAVEファイルの書き込み手順は以下の通りです。

  1. マルチメディアファイル入出力関数を使って、WAVEファイルを開きます。
  2. RIFF/WAVEチャンクを作成して、その中に進入します。
  3. fmtチャンクを作成してその中に進入し、WAVEFORMATEX構造体を書き込んだ後、fmtチャンクから退出します。
  4. factチャンクを作成してその中に進入し、音源データサンプル数を書き込んだ後、factチャンクから退出します。
  5. dataチャンクを作成してその中に進入します。
  6. 音源データを格納するバッファを作成します。
  7. 現在の演奏条件での音源データをバッファに読み込み、ファイルに書き込みます。この作業を音源データが読み込めなくなるまで繰り返します。
  8. RIFFチャンクから退出します。
  9. 実際に書き込んだサンプル数factチャンクに書き込んだサンプル数が違う場合は、factチャンクのサンプル数書き込み位置に戻って書き直します。
  10. メタデータがあれば、ファイル末尾にINFOチャンクを作成し、メタデータを書き込みます。
  11. ファイルを閉じます。





マルチメディアファイル入出力関数を使用するために必要なインクルードファイルとライブラリーです。

#include <Mmsystem.h>
#pragma comment (lib, "winmm.lib")


現在演奏中のファイルに曲名情報などを編集して重ね書きする際に、必要となるテンポラリーファイルのパス名を取得するstdcall関数です。

//テンポラリーファイル名の取得
BOOL __stdcall _GetTempFileName(CString& strTempFileName,LPCTSTR lpPrefixString,LPCTSTR lpszExt)
{
//テンポラリフォルダーのディレクトリパスを取得
DWORD size=::GetTempPath(0,0);
CString strTempPath;
LPTSTR lpszPath=strTempPath.GetBufferSetLength(size);
size=::GetTempPath(size,lpszPath);
if (!size) return FALSE;

//テンポラリファイルを作成し、ファイル名を取得します。
CString strTempFile;
LPTSTR lpsz=strTempFile.GetBufferSetLength(MAX_PATH);
if (!::GetTempFileName(strTempPath,lpPrefixString,0,lpsz)) return FALSE;
::DeleteFile(strTempFile);

//引数の拡張子名が有効な場合は、
//テンポラリーファイル名の拡張子と入れ替えます。

if (lpszExt){
int nExt=strTempFile.Find('.');
if (nExt<0) return FALSE;

strTempFileName=strTempFile.Left(nExt);
if (lpszExt[0]!='.') strTempFileName.AppendChar('.');
strTempFileName.Append(lpszExt);
}
else strTempFileName=strTempFile;

return TRUE;
}


指定されたパス名のファイルをマルチメディアファイル入出力関数で開いて、StreamOut関数を呼び出します。

//現在演奏中の音源データをオーディオファイルに書き込みます。(重ね書き可)
BOOL CWaveFile::SaveFile(LPCTSTR lpszFileName)
{
//書き込み対象のファイルがない場合は、エラー終了します。
if (!IsOpen()) return FALSE;

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

if (!ResetStream()) return FALSE;

//現在演奏中のファイルに上書きする場合。
if (m_strPathName==lpszFileName){
//テンポラリーファイル名の取得
CString strTempFile;
if (!::_GetTempFileName(strTempFile,::PathFindFileName(m_strPathName),
::PathFindExtension(m_strPathName))) return FALSE;
//テンポラリファイルにWaveファイルを書き込みます。
if (!WriteFile(strTempFile)) return FALSE;
//Waveファイルにテンポラリファイルを重ね書きします。
Delete();
BOOL bResult=::CopyFile(strTempFile,m_strPathName,FALSE);
// テンポラリファイルの削除
::DeleteFile(strTempFile);
return TRUE;
}
return WriteFile(lpszFileName);
}

//メタデータを残したままで、ファイルを再度開き直します。(オーバーライド)
BOOL CWaveFile::ResetStream()
{
//INFOチャンクの内容を保存しておきます。
CStringArray strArrayRIFFSafe;
for(UINT i=0;i<GetChunkCount();i++)
strArrayRIFFSafe.Add(GetChunkTagByIndex(i));

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

if (!OpenFile(m_strPathName)) return FALSE;

//保存しておいたINFOチャンクの内容を戻します。
for(UINT i=0;i<GetChunkCount();i++)
SetChunkTextByIndex(i,strArrayRIFFSafe[i]);

return TRUE;
}

//現在演奏中の音源データをWAVEファイルの書き込みます。(重ね書き不可&オーバーライド)
BOOL CWaveFile::WriteFile(LPCTSTR lpszFileName)
{
ASSERT(m_strPathName!=lpszFileName);

//拡張子名の取得
LPTSTR pExt=::PathFindExtension(lpszFileName);
//拡張子名が「.wav」で無い場合は、エラー終了します。
if (::StrCmpNI(pExt,_T(".wav"),4)!=0) return FALSE;

//マルチメディア関数を使ってファイルを開きます。
HMMIO hmmio=::mmioOpen((LPTSTR)lpszFileName,NULL,MMIO_CREATE|MMIO_WRITE);
if (!hmmio) return FALSE;
//Waveストリームへの書き込むStreamOut関数を呼び出します。
BOOL bResult=StreamOut(hmmio);
::mmioClose(hmmio,0);
return bResult;
}


現在演奏中のファイルから音源データを読み込み、ファイルストリームに書き出します。

//Wave音源データをファイルストリームに書き込みます。
BOOL CWaveFile::StreamOut(HMMIO hmmio)
{
//現在演奏中のファイルハンドルがなければ、エラー終了します。
if (!IsOpen()) return FALSE;

//WAVEデータ書き込みのための設定
MMCKINFO mmckRiff,mmckFmt,mmckData,mmckFact;

//RIFF/WAVEチャンクを作成して、その中に進入します。
mmckRiff.fccType=::mmioStringToFOURCC("WAVE", 0);
MMRESULT mmr=::mmioCreateChunk(hmmio,&mmckRiff,MMIO_CREATERIFF);
//WAVEチャンクの作成に失敗したら、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//fmtチャンクを作成して、その中に進入します。
mmckFmt.ckid=::mmioStringToFOURCC("fmt ", 0);
mmr=::mmioCreateChunk(hmmio,&mmckFmt,0);
//fmtチャンクの作成に失敗したら、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//WAVEFORMATEX構造体をファイルに書き込みます。
LONG sizeHeader=sizeof(WAVEFORMAT)+sizeof(WORD);
LONG size=::mmioWrite(hmmio,(const char*)&m_wfx,sizeHeader);
//書き込みに失敗したら、エラー終了します。
if (size<sizeHeader) return FALSE;

//fmtチャンクから退出します。
mmr=::mmioAscend(hmmio,&mmckFmt,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//factチャンクを作成して、その中に進入します。
mmckFact.ckid=::mmioStringToFOURCC("fact", 0);
mmr=::mmioCreateChunk(hmmio,&mmckFact,0);
//factチャンクの作成に失敗したら、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;
//サンプル数書き込み位置を保存します。
LONG mmioPosSamples=::mmioSeek(hmmio,0,SEEK_CUR);
//サンプル数を書き込みます。
DWORD nSamples=GetSamples();
size=::mmioWrite(hmmio,(const char*)&nSamples,sizeof(DWORD));
//書き込みに失敗したら、エラー終了します。
if (size<sizeof(DWORD)) return FALSE;

//factチャンクから退出します。
mmr=::mmioAscend(hmmio,&mmckFact,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//dataチャンクを作成して、その中に進入します。
mmckData.ckid=::mmioStringToFOURCC("data",0);
mmr=::mmioCreateChunk(hmmio,&mmckData,0);
//dataチャンクの作成に失敗したら、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//派生クラスから書き込み用のバッファサイズを取得します。
UINT nBufSize=GetSizeOfSaveBuffer();
//音源データを格納するバッファを作成します。
CByteArray byteArray;
byteArray.SetSize(nBufSize);
//バッファへのポインタを取得します。
PBYTE pBuffer=byteArray.GetData();
//読み込み用のインデックスをゼロにします。
UINT indexSample=0;
//書き込み済みバイト数をゼロにします。
DWORD writeBytes=0;
while(TRUE){
//現在の演奏条件での音源データを取得します。
UINT readBytes=StreamIn(pBuffer,nBufSize,&indexSample);
//最後まで読み込んだら、ループを抜けます。
if (!readBytes) break;
//ファイルに音源データを書き込みます。
size=::mmioWrite(hmmio,(const char*)pBuffer,readBytes);
if (size==-1) return FALSE;
//書き込み済みバイト数をカウントします。
writeBytes+=size;
//オーナーウィンドウがあれば、プログレスバーの位置を通知します。
if (m_pWndOwner) m_pWndOwner->PostMessage(PBM_SETPOS,indexSample,0);
}
//dataチャンクから退出します。
mmr=::mmioAscend(hmmio,&mmckData,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//MP3などの圧縮ファイルからデコードした場合など、
//実際の書き込み済みバイト数とwave音源データバイト数が違う場合。

if (writeBytes!=m_nDataSize){
//dataチャンク末尾位置を保存します。
LONG mmioPosDataEnd=::mmioSeek(hmmio,0,SEEK_CUR);

//サンプル数書き込み位置に戻ります。
::mmioSeek(hmmio,mmioPosSamples,SEEK_SET);

//実際のサンプル数を書き込みます。
nSamples=writeBytes/m_wfx.nBlockAlign;
size=::mmioWrite(hmmio,(const char*)&nSamples,sizeof(DWORD));
//書き込みに失敗したら、エラー終了します。
if (size<sizeof(DWORD)) return FALSE;

//dataチャンク末尾位置に戻ります。
::mmioSeek(hmmio,mmioPosDataEnd,SEEK_SET);
}

//INFOチャンクを新たに作成して、メタデータを書き込みます。
if (!WriteListINFO(hmmio)) return FALSE;

//riffチャンクから退出します。
mmr=::mmioAscend(hmmio,&mmckRiff,0);
if (mmr!=MMSYSERR_NOERROR) return FALSE;
return TRUE;
}


戻り値がTRUEなら、WAVEファイルへの書き込みは成功です。

WaveOut.h
WaveOut.cpp
WaveFile.h
WaveFile.cpp



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

メールアドレス:

ホームページアドレス:

コメント:

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


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

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