2011年02月07日

WAVEファイルの再生 第2部



前章WAVEファイルの再生 第1部の続きです。




ファイル保存時に保存経過情報などを、コールバックするための関数ポインタです。

typedef BOOL (__stdcall *SAVECALLBACK)(int percent,LPCTSTR lpszMessage,LPVOID lpParam);


WAVEファイルから音源データを時系列で読み込み、演奏管理するためのCWaveFileクラスを独自に定義しました。

// CWaveFileクラス

class CWaveFile : public CWaveOut
{
public:
//コンストラクタ
CWaveFile(CWnd* pWndCallBack=NULL);
//デストラクタ
virtual ~CWaveFile();

//メンバー変数の初期化
void Init();
//演奏の停止して、読み込み中のファイルを閉じます。(オーバーライド)
void Delete(BOOL bDeleteBaseClass=FALSE);

//Waveファイルがオープンされているかどうか?(オーバーライド)
virtual BOOL IsWaveFile() const{return (m_hmmio!=0);};
//読み込み中のデバイスハンドルが有効か?(オーバーライド)
virtual BOOL IsOpen() const{return IsWaveFile();};

//オーディオファイルを開きます。
BOOL OpenFile(LPCTSTR lpszFileName,BOOL bDeleteMemberParam=TRUE);

//Wave音源ストリームを読み込みます。(オーバーライド)
// pIndexSample:現在の演奏位置変数へのポインタ

virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample);
//基底クラスに音源ストリームを引渡します。(オーバーライド)
virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize);

//現在演奏中の音源データをオーディオファイルに書き込みます。(重ね書き可)
BOOL SaveFile(LPCTSTR lpszFileName,SAVECALLBACK lpfnCallBack,LPVOID lpParam);
//現在演奏中の音源データをWAVEファイルの書き込みます。(重ね書き不可&オーバーライド)
virtual BOOL WriteFile(LPCTSTR lpszFileName);

//現在演奏中のファイルを重ね書きします。
BOOL OverWriteFile(){return SaveFile(m_strPathName,NULL,NULL);};

//演奏を開始します。
// timePerOut:一回に演奏する時間(_秒)

BOOL Play(UINT timePerOut=100){return CWaveOut::Play(&m_wfx,timePerOut);};
//演奏を停止します。
void Stop(UINT indexSample=-1);

//演奏位置を設定します。(オーバーライド)
virtual void SetPos(UINT nPos);
//演奏位置を取得します。(オーバーライド)
virtual UINT GetPos() const{return m_indexSample;};
//音源データのバイト数を取得します。
UINT GetLength() const{return m_nDataSize;};
//演奏予定の音源データ数を取得します。
UINT GetSamples() const{return (m_nDataSize)?m_nDataSize/m_wfx.nBlockAlign:0;};
//指定の演奏時間から演奏位置を設定します。
void SetPastTime(UINT nTime){m_indexSample=(UINT)(((double)nTime*(double)m_wfx.nSamplesPerSec/1000.0)+0.5);};
//現在の演奏時間を取得します。
UINT GetPastTime() const{return (UINT)((double)m_indexSample*1000.0/(double)m_wfx.nSamplesPerSec+0.5);};
//総演奏予定時間を取得します。(オーバーライド)
virtual UINT GetTotalTime() const{return (UINT)((double)GetSamples() const*1000.0/(double)m_wfx.nSamplesPerSec+0.5);};

//ファイルのWAVEFORMATEX構造体を取得します。
int GetWaveFormat(LPWAVEFORMATEX pwfx=NULL) const;

protected:
//WAVEファイルヘッダーを読み込み、WAVEFORMATEX構造体を取得します。
BOOL ReadHeader(HMMIO hmmio);

//Wave音源データをファイルストリームに書き込みます。(オーバーライド)
virtual BOOL WaveStreamOut(HMMIO hmmio);


//派生クラスから書き込み用のバッファサイズを取得します。(オーバーライド)
//(オーバーライドしない場合、Waveファイルの音源データを読み込むバッファサイズは、
//100_秒演奏するのに必要なサイズとします。)

virtual UINT GetSizeOfSaveBuffer() const{return m_wfx.nAvgBytesPerSec/10;};

//ファイル保存時に、メンバー変数に指定のあるコールバック関数を呼び出します。
// 戻り値=TRUE:保存継続、[FALSE]:保存中止。

BOOL SaveCallBack(int percent,LPCTSTR lpszMessage=NULL);

//データストリームをリセットします。(オーバーライド)
virtual void ResetStream(){Stop(0);};

//INFOチャンク内のメタデータを読み込みます。
BOOL ReadListINFO(HMMIO hmmio);
//INFOチャンクを新たに作成して、メタデータを書き込みます。
BOOL WriteListINFO(HMMIO hmmio);

HMMIO m_hmmio; //読み込みファイルハンドル
MMCKINFO m_mmckRiff; //現在読み込み中のRIFFチャンクのMMCKINFO構造体
MMCKINFO m_mmckData; //現在読み込み中のdataチャンクのMMCKINFO構造体
LONG m_posStart; //音源データの開始位置
WAVEFORMATEX m_wfx; //ファイルのWAVEFORMATEX構造体

UINT m_indexSample; //現在の演奏位置
UINT m_nDataSize; //wave音源データバイト数

CString m_strPathName; //現在演奏中のパス名

//ファイル保存時に呼び出される、コールバック関数へのポインタと引数を保存します。
SAVECALLBACK m_lpfnSaveCallBack;
LPVOID m_lpSaveParam;

CString m_strPostMsg; //コールバック関数にポストする文字列を一時格納します。
CString m_strFileType; //ファイル形式文字列を格納します。(例:"WAV")

public:
//現在演奏中のパス名の取得
LPCTSTR GetPathName() const{return m_strPathName;};
//現在演奏中のファイル形式文字列を取得します。
LPCTSTR GetFileType() const{return m_strFileType;};
/*
INFOチャンクタグ一覧

[ 0]:メディア元 ISRC
[ 1]:作成日 ICRD
[ 2]:アルバム名 IPRD
[ 3]:曲名 INAM
[ 4]:ジャンル IGNR
[ 5]:コメント ICMT
[ 6]:アーティスト IART
[ 7]:著作権代理人 ICMS
[ 8]:著作権情報 ICOP
[ 9]:録音エンジニア IENG
[10]:ソフトウェア ISFT
[11]:キーワード IKEY
[12]:エンコード技術者 ITCH
[13]:タイトル ISBJ
[14]:トラック番号 ITRK
*/

#define LIST_INFO_CHUNK_COUNT 15

protected:
//INFOチャンク文字列格納用CStringクラス
CString m_strChunkText[LIST_INFO_CHUNK_COUNT];
public:
//INFOチャンク文字列数の取得
UINT GetChunkCount() const{return LIST_INFO_CHUNK_COUNT;};
//タグ文字列の取得
LPCSTR GetChunkTagByIndex(UINT index) const;
//INFOチャンク文字列の取得
LPCTSTR GetChunkTextByIndex(UINT index) const;
//INFOチャンク文字列の検索
LPCTSTR FindChunkTextByTag(LPCSTR lpszTag) const;
//INFOチャンク文字列の設定
void SetChunkTextByTag(LPCSTR lpszTag,LPCTSTR lpszText);
//INFOチャンク文字列の設定
void SetChunkTextByIndex(UINT index,LPCTSTR lpszText);
//タグの詳細記述の取得
LPCTSTR ChunkDescription(LPCSTR lpszTag) const;
//INFOチャンク文字列の削除
void DeleteChunkByTag(LPCSTR lpszTag);
//INFOチャンク文字列の削除
void DeleteChunkByIndex(UINT index);
//すべてのINFOチャンク文字列の削除
void DeleteAllINFOChunk();
};



CWaveFileクラスのインライン関数です。

//演奏を停止します。
inline void CWaveFile::Stop(UINT indexSample)
{
//WAVE出力デバイスを停止します。
CWaveOut::Stop();
//引数が-1以外の場合にのみ、指定の演奏位置に移動します。
if (indexSample<-1) SetPos(indexSample);
}

//演奏位置を設定します。
inline void CWaveFile::SetPos(UINT nPos)
{
UINT nSamples=GetSamples();
m_indexSample=(nPos<nSamples)? nPos:nSamples;
}

//ファイルのWAVEFORMATEX構造体と、WAVE出力デバイスに引き渡すWAVEFORMATEX構造体を取得します。
inline int CWaveFile::GetWaveFormat(LPWAVEFORMATEX pwfx) const
{
//第一引数が有効な場合、第一引数の示す位置に
//ファイルのWAVEFORMATEX構造体をコピーします。

if (!::IsBadWritePtr(pwfx,sizeof(WAVEFORMATEX)))
::CopyMemory(pwfx,&m_wfx,sizeof(WAVEFORMATEX));
return sizeof(WAVEFORMATEX);
}


CWaveFileクラスのコンストラクタ/デストラクタです。

//コンストラクタ
CWaveFile::CWaveFile(CWnd* pWndCallBack)
:CWaveOut(pWndCallBack)
{
Init();
}

//デストラクタ
CWaveFile::~CWaveFile()
{
Delete(FALSE);
}


CWaveFileクラスのメンバ関数です。

//メンバ変数の初期化
void CWaveFile::Init()
{
m_hmmio=0;
::ZeroMemory(&m_mmckRiff,sizeof(MMCKINFO));
::ZeroMemory(&m_mmckData,sizeof(MMCKINFO));
::ZeroMemory(&m_wfx,sizeof(WAVEFORMATEX));
m_posStart=0;
m_indexSample=0;
m_nDataSize=0;

m_lpfnSaveCallBack=NULL;
m_lpSaveParam=NULL;

m_strPathName=_T("");
m_strPostMsg=_T("");
m_strFileType=_T("");

DeleteAllINFOChunk();
}

//演奏の停止して、読み込み中のファイルを閉じます。
void CWaveFile::Delete(BOOL bDeleteBaseClass)
{
//WAVE出力デバイスを停止します。
Stop();
//ファイルを読み込み中の場合。
if (m_hmmio){
//データチャンクから抜け出して、ファイルを閉じます。
if (m_posStart){
::mmioAscend(m_hmmio,&m_mmckData,0);
::mmioAscend(m_hmmio,&m_mmckRiff,0);
}
::mmioClose(m_hmmio,0);
}
//メンバ変数の初期化
Init();
}



マルチメディアファイル入出力関数を使ってWAVEファイルを開き、CWaveFile::ReadHeader関数を呼び出します。

//オーディオファイルを開きます。
BOOL CWaveFile::OpenFile(LPCTSTR lpszFileName,BOOL bDeleteMemberParam)
{
if (bDeleteMemberParam) Delete(TRUE);

//マルチメディア関数を使ってファイルを開きます。
HMMIO hmmio=::mmioOpen((LPTSTR)lpszFileName,NULL,MMIO_READ|MMIO_DENYNONE);
if (!hmmio) return FALSE;

//WAVEファイルヘッダーを読み込みます。
if (!ReadHeader(hmmio)){
::mmioClose(hmmio,0);
return FALSE;
}

//ファイルハンドルを保存します。
m_hmmio=hmmio;
//ファイルのパス名を保存しておきます。
m_strPathName=lpszFileName;
//ファイル形式を書き込んでおきます。
m_strFileType=_T("WAV");
return TRUE;
}


WAVEファイルからWAVEFORMATEX構造体を取得し、演奏のための準備をします。

//WAVEファイルヘッダーを読み込み、WAVEFORMATEX構造体を取得します。
BOOL CWaveFile::ReadHeader(HMMIO hmmio)
{
ASSERT(hmmio);

//WAVEファイルかのチェック
//RIFFチャンクに進入してWAVEチャンクを探します。

m_mmckRiff.fccType=::mmioStringToFOURCC("WAVE", 0);
MMRESULT mmr=::mmioDescend(hmmio,&m_mmckRiff,NULL,MMIO_FINDRIFF);
//WAVEチャンクがなければ、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//INFOチャンク内のメタデータを読み込みます。
ReadListINFO(hmmio);

//fmtチャンクに進入します。
MMCKINFO mmckFmt;
mmckFmt.ckid=::mmioStringToFOURCC("fmt ",0);
mmr=::mmioDescend(hmmio,&mmckFmt,NULL,MMIO_FINDCHUNK);
//進入に失敗したら、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;
//fmtチャンクを読み込む。
CByteArray byteFmt;
byteFmt.SetSize(mmckFmt.cksize);
LPWAVEFORMATEX pwfxSrc=(LPWAVEFORMATEX)byteFmt.GetData();

LONG size=::mmioRead(hmmio,(HPSTR)pwfxSrc,mmckFmt.cksize);
if (size!=mmckFmt.cksize) return FALSE;
::CopyMemory(&m_wfx,pwfxSrc,sizeof(WAVEFORMATEX));

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

//現在のファイルポインタの位置を保存します。
LONG mmioPos=::mmioSeek(hmmio,0,SEEK_CUR);
//factチャンクに進入します。
MMCKINFO mmckFact;
mmckFact.ckid=::mmioStringToFOURCC("fact ",0);
mmr=::mmioDescend(hmmio,&mmckFact,NULL,MMIO_FINDCHUNK);
//進入に成功した場合。
if (mmr==MMSYSERR_NOERROR){
//factチャンクを読み込む。
DWORD nSamples;
LONG size=::mmioRead(hmmio,(HPSTR)&nSamples,mmckFact.cksize);
if (size!=mmckFact.cksize) return FALSE;
m_nDataSize=nSamples*m_wfx.nBlockAlign;
//factチャンクから退出します。
if (::mmioAscend(hmmio,&mmckFact,0)!=MMSYSERR_NOERROR) return FALSE;
}
//進入に失敗した場合。
else {
m_nDataSize=0;
//元のファイルポインタの位置に戻します。
::mmioSeek(hmmio,mmioPos,SEEK_SET);
}

//dataチャンクに進入します。
m_mmckData.ckid=::mmioStringToFOURCC("data",0);
mmr=::mmioDescend(hmmio,&m_mmckData,NULL,MMIO_FINDCHUNK);
//進入に失敗したら、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;

//音源データの先頭位置を保存します。
m_posStart=::mmioSeek(hmmio,0,SEEK_CUR);
//factチャンクがなかった場合。
if ((!m_nDataSize)||(m_nDataSize>m_mmckData.cksize)){
//音源データサイズにdataチャンクサイズを代入します。
m_nDataSize=m_mmckData.cksize;
//ファイル末尾に移動して、実際の音源データサイズを取得します。
LONG posEnd=::mmioSeek(hmmio,0,SEEK_END);
UINT sizeToEnd=posEnd-m_posStart;
//音源データサイズが不正な値の場合。
if ((!m_nDataSize)||(m_nDataSize>sizeToEnd))
//ファイル末尾までの実際の音源データサイズを代入します。
m_nDataSize=sizeToEnd;
}
return TRUE;
}


演奏中に基底クラスCWaveOutクラスからバッファへの音源データ読み込み要求があるたびに呼び出される、WAVEストリーム読み込み関数です。

//基底クラスに音源ストリームを引渡します。(オーバーライド)
UINT CWaveFile::StreamIn(LPVOID pBuffer,UINT nBufSize)
{
ASSERT(!::IsBadWritePtr(pBuffer,nBufSize));

UINT readBytes=0;
//ファイルとWAVE出力デバイスのWAVEFORMATEX構造体要素のうち、
//サンプル当りのバイト数、1秒あたりのサンプル数、サンプル当りのビット数が
//いずれも等しければ、読み込んだ音源をそのまま返して関数を終了します。

if ((m_wfxOut.nBlockAlign==m_wfx.nBlockAlign)&&
(m_wfxOut.nSamplesPerSec==m_wfx.nSamplesPerSec)&&
(m_wfxOut.wBitsPerSample==m_wfx.wBitsPerSample))
{
readBytes=StreamIn(pBuffer,nBufSize,&m_indexSample);
}
else
{
//入力バッファのサイズから、加工前の入力バイト数を算出します。
UINT nSamples=nBufSize/m_wfxOut.nBlockAlign;
UINT inputBytes=nSamples*m_wfx.nBlockAlign;

//入力用バッファ(CByteArrayクラス)のサイズが入力バイト数より小さい場合、
//入力用バッファのサイズを入力バイト数に合わせます。

if ((UINT)m_byteInBuf.GetSize()<inputBytes)
m_byteInBuf.SetSize(inputBytes);
//入力用バッファへのポインタを取得します。
PBYTE pInBuf=m_byteInBuf.GetData();
//入力用バッファをゼロクリアします。
::ZeroMemory(pInBuf,inputBytes);
UINT indexSafe=m_indexSample;
UINT sizeBytes=StreamIn(pInBuf,inputBytes,&m_indexSample);
//音源データに擬似ステレオ加工を施します。
readBytes=DoStereo(pBuffer,nBufSize,indexSafe,sizeBytes,&m_wfx);
}
return readBytes;
}

//音源ストリームの読み込み(オーバーライド)
// pIndexSample://現在の演奏位置変数へのポインタ

UINT CWaveFile::StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample)
{
ASSERT(!::IsBadWritePtr(pBuffer,nBufSize));
ASSERT(m_hmmio);
ASSERT(m_posStart);
ASSERT(::AfxIsValidAddress(pIndexSample,sizeof(UINT)));

//指定の演奏位置からファイル読み込み位置を算出して、
//その位置にシークします。

LONG offset=m_posStart+*pIndexSample*m_wfx.nBlockAlign;
LONG pos=::mmioSeek(m_hmmio,offset,SEEK_SET);
//シークに失敗したら、エラー終了します。
if (pos!=offset) return FALSE;

//データチャンク外にシークにしていたら、エラー終了します。
LONG endOfDataChunk=m_posStart+m_nDataSize;
if (pos>=endOfDataChunk) return FALSE;

//バッファに取り込むサイズを算定します。
LONG sizeToRead=min(nBufSize,(UINT)(endOfDataChunk-pos));

//dataチャンクの音源データを読み込みます。
LONG readBytes=::mmioRead(m_hmmio,(HPSTR)pBuffer,sizeToRead);
//読み込みに失敗したら、エラー終了します。
if (readBytes<m_wfx.nBlockAlign) return FALSE;

//指定の演奏位置を読み込んだ分だけ進めます。
*pIndexSample+=readBytes/m_wfx.nBlockAlign;
//読み込んだバイト数を返して終了します。
return readBytes;
}


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

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




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

メールアドレス:

ホームページアドレス:

コメント:

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


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

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