2011年02月07日

WAVEファイルの再生 第1部



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

  1. マルチメディアファイル入出力関数を使って、WAVEファイルを開きます。
  2. RIFFチャンクに進入して、WAVEチャンクを探します。
  3. fmtチャンクに進入して、fmtチャンクを読み込み、その内容を元にしてWAVEFORMATEX構造体を作成します。
  4. fmtチャンクから退出して、dataチャンクに進入します。
  5. dataチャンクの先頭データ位置を音源データの先頭位置として保存し、ファイル末尾に移動して、実際のファイルサイズを取得します。
  6. 先に作成したWAVEFORMATEX構造体を元にWAVE出力デバイスを開きます。
  7. WAVE出力デバイスからのコールバックイベントをトリガにして、ダブルバッファに音源データを読み込みながら演奏していきます。
  8. ファイル末尾まで読み込むか、演奏停止イベントがあればWAVE出力デバイスを停止してから閉じます。
  9. RIFFチャンクから退出してファイルを閉じます。




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

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


WAVE出力デバイスコールバック関数内で、MM_WOM_DONEメッセージの二重呼び出しを防ぐために使用する、CCriticalSectionクラスを収めたインクルードファイルです。

#include <afxmt.h>


WAVE出力デバイス関数を使いやすくするため、CWaveOutクラスを独自に定義しました。

// CWaveOutクラス

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

//メンバー変数の初期化
void Init();
//WAVE出力デバイスの停止
void Delete();

//WAVE出力デバイスを使って演奏を開始します。
//timePerOut:一回に演奏する時間(_秒)

BOOL Play(const WAVEFORMATEX* pwfxOut,UINT timePerOut);
//演奏中かどうか?
BOOL IsPlaying() const{return (m_hWaveOut!=NULL);};

//演奏を停止します。
void Stop(){Delete();};

//一時停止します。
BOOL Pause();
//一時停止中かどうか?
BOOL IsPausing() const{return m_bPause;};

//派生クラスから音源ストリームを入力します。(オーバーライド)
virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize){return FALSE;};

//WAVEFORMATEX構造体の取得
int GetOutFormat(LPWAVEFORMATEX pwfxOut) const;
//WAVE出力デバイスに引き渡すWAVEFORMATEX構造体を設定します。
void SetOutFormat(const LPWAVEFORMATEX pwfxOut);

//派生クラスから演奏位置を取得します。(オーバーライド)
virtual UINT GetPos() const{return FALSE;};

//音量調節
//pBuffer:音源データが格納されているバッファ
//nBufSize:バッファのバイト数
//pwfx:音源データの演奏形式を格納したWAVEFORMATEX構造体

BOOL DoVolume(LPVOID pBuffer,UINT nBufSize,WAVEFORMATEX* pwfx);
//音量の設定
//LOWORD(dwVolume):左音量, HIWORD(dwVolume):右音量

void SetVolume(DWORD dwVolume){m_dwVolume=dwVolume;};
//音量の取得
//LOWORD(dwVolume):左音量, HIWORD(dwVolume):右音量

DWORD GetVolume() const{return m_dwVolume;};

//メンバー変数のm_byteInBuf(CByteArrayクラス)に格納された音源データを、
//モノラル→擬似ステレオ、ステレオ→モノラル変換して、引数のバッファに格納します。
// pBuffer:加工後の音源データを格納するバッファ
// nBufSize:バッファのバイト数
// indexSample:現在の演奏位置(サンプル数)
// validBytes:音源データの有効バイト数
// pwfxFile:音源データの演奏形式を格納したWAVEFORMATEX構造体

UINT DoStereo(LPVOID pBuffer,UINT nBufSize,UINT indexSample,UINT validBytes,WAVEFORMATEX* pwfxFile);
//擬似ステレオ遅延時間の設定
void SetDelay(int delay){m_nDelayStereo=delay;};
//擬似ステレオ遅延時間の取得
int GetDelay() const{return m_nDelayStereo;};

protected:
//音源データを入力ストリームから読み込む
BOOL ReadSampleData(PWAVEHDR pWaveHdr);
//派生クラスから読み込み用のバッファサイズを取得します。
//(オーバーライドしない場合のバッファサイズは、100_秒演奏するのに必要なサイズとします。)

virtual UINT GetSizeOfLoadBuffer() const{return m_wfxOut.nAvgBytesPerSec*m_timePerOut/1000;};

//WAVE出力デバイスコールバック関数(スタティック関数)
static void __stdcall StaticWaveOutProc(HWAVEOUT hwo,UINT uMsg,DWORD_PTR dwInstance,
DWORD dwParam1,DWORD dwParam2);

//WAVE出力デバイスコールバック関数
void WaveOutProc(HWAVEOUT hwo,UINT uMsg,DWORD dwParam1,DWORD dwParam2);

//オーナーウィンドウにメッセージをポストします。(オーバーライド)
virtual void PostMessage(UINT message,WPARAM wParam,LPARAM lParam);

//WAVE出力デバイスコールバック関数内で、MM_WOM_DONEメッセージの
//二重呼び出しを防ぐために使用するCCriticalSectionクラス

CCriticalSection m_key;

WAVEFORMATEX m_wfxOut; //WAVEFORMATEX構造体
CWnd* m_pWndCallBack; //コールバックするCWndクラスへのポインタ
HWAVEOUT m_hWaveOut; //WAVE出力デバイスのハンドル

DWORD m_timePerOut; //一回に演奏する時間(_秒)
CByteArray m_byteBuf1; //出力音源第一バッファ
CByteArray m_byteBuf2; //出力音源第二バッファ
WAVEHDR m_WaveHdr1; //第一バッファ用WAVEHDR構造体
WAVEHDR m_WaveHdr2; //第二バッファ用WAVEHDR構造体
BOOL m_bStop; //リセット時の2重呼び出しを避けるため
BOOL m_bPause; //一時停止フラグ

//ボリューム
//LOWORD(dwVolume):左音量, HIWORD(dwVolume):右音量

DWORD m_dwVolume;

//擬似ステレオ関連
CByteArray m_byteInBuf; //音源データ取り込み用バッファ
CByteArray m_byteDelay; //遅延バッファ
CByteArray m_byteCache; //遅延バッファに書ききれなかった音源データを、次回分に保存しておくバッファ
int m_nDelayStereo; //ステレオ変換時の遅延時間(_秒)
};


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

//WAVEFORMATEX構造体の取得
inline int CWaveOut::GetOutFormat(LPWAVEFORMATEX pwfxOut) const
{
if (!::IsBadWritePtr(pwfxOut,sizeof(WAVEFORMATEX)))
::CopyMemory(pwfxOut,&m_wfxOut,sizeof(WAVEFORMATEX));
return sizeof(WAVEFORMATEX);
}

//WAVE出力デバイスに引き渡すWAVEFORMATEX構造体を設定します。
inline void CWaveOut::SetOutFormat(const LPWAVEFORMATEX pwfxOut)
{
//入力されたWAVEFORMATEX構造体が有効な場合。
if (!::IsBadReadPtr(pwfxOut,sizeof(WAVEFORMATEX))){
//WAVE出力デバイスを停止します。
Delete();
//入力されたWAVEFORMATEX構造体で再演奏します。
Play(pwfxOut,0);
}
}

//オーナーウィンドウにメッセージをポストします。(オーバーライド)
inline void CWaveOut::PostMessage(UINT message,WPARAM wParam,LPARAM lParam)
{
if (m_pWndCallBack)
m_pWndCallBack->PostMessage(message,wParam,lParam);
}


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

//コンストラクタ
void CWaveOut::CWaveOut(CWnd* pWndCallBack)
{
m_pWndCallBack=pWndCallBack;
m_timePerOut=100; //一回の演奏時間を100msにします。

::ZeroMemory(&m_wfxOut,sizeof(WAVEFORMATEX));
m_dwVolume=0xFFFFFFFF;
m_nDelayStereo=30;

Init();
}

//デストラクタ
void CWaveOut::~CWaveOut()
{
Delete();
}


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

//メンバー変数の初期化
void CWaveOut::Init()
{
m_hWaveOut=0;

::ZeroMemory(&m_wfxOut,sizeof(WAVEFORMATEX));
::ZeroMemory(&m_WaveHdr1,sizeof(WAVEHDR));
::ZeroMemory(&m_WaveHdr2,sizeof(WAVEHDR));
m_bStop=FALSE;
m_bPause=FALSE;
}

//WAVE出力デバイスの停止
void CWaveOut::Delete()
{
//演奏中の場合。
if (m_hWaveOut){
//コールバック関数の2重呼び出しを避けるため、フラグを立てます。
m_bStop=TRUE;
//コールバック関数が動作中の場合、終了するまでの時間待ちです。
::Sleep(10);
//WAVE出力デバイスをリセットします。
::waveOutReset(m_hWaveOut);
//WAVE出力デバイスから、WAVEHDR構造体を切り離します。
::waveOutUnprepareHeader(m_hWaveOut,&m_WaveHdr1, sizeof(WAVEHDR));
::waveOutUnprepareHeader(m_hWaveOut,&m_WaveHdr2, sizeof(WAVEHDR));
//WAVE出力デバイスを閉じます。
::waveOutClose(m_hWaveOut);
}
Init();
}

//WAVE出力デバイスを使って演奏を開始します。
//timePerOut:一回に演奏する時間(_秒)

BOOL CWaveOut::Play(const WAVEFORMATEX* pwfxOut,UINT timePerOut)
{
ASSERT(!::IsBadReadPtr(pwfxOut,sizeof(WAVEFORMATEX)));
if (pwfxOut->wFormatTag!=WAVE_FORMAT_PCM) return FALSE;

//メンバー変数をクリアします。
Delete();

//引数のWAVEFORMATEX構造体をメンバー変数にコピーします。
::CopyMemory(&m_wfxOut,pwfxOut,sizeof(WAVEFORMATEX));

//引数の一回の演奏時間が有効なら、メンバー変数に代入します。
if ((timePerOut>0)&&(timePerOut<10000)) m_timePerOut=timePerOut;
//派生クラスから読み込み用のバッファサイズを取得します。
DWORD dwBufferLength=GetSizeOfLoadBuffer();

//出力音源バッファに、指定サイズのメモリーを確保します。
m_byteBuf1.SetSize(dwBufferLength); //出力音源第一バッファ
m_byteBuf2.SetSize(dwBufferLength); //出力音源第二バッファ

//WAVE出力デバイスを開く
MMRESULT mmr=::waveOutOpen(&m_hWaveOut,WAVE_MAPPER,&m_wfxOut,
(DWORD)(DWORD_PTR)StaticWaveOutProc,(DWORD)(DWORD_PTR)this, CALLBACK_FUNCTION);
if (mmr!= MMSYSERR_NOERROR) return FALSE;

//第一バッファ用WAVEHDR構造体を作成します。
m_WaveHdr1.lpData=(LPSTR)m_byteBuf1.GetData();
m_WaveHdr1.dwBufferLength=dwBufferLength;
m_WaveHdr1.dwLoops=1;
m_WaveHdr1.dwFlags=WHDR_BEGINLOOP|WHDR_ENDLOOP;
::waveOutPrepareHeader(m_hWaveOut,&m_WaveHdr1,sizeof(WAVEHDR));

//第二バッファ用WAVEHDR構造体を作成します。
m_WaveHdr2.lpData=(LPSTR)m_byteBuf2.GetData();
m_WaveHdr2.dwBufferLength=dwBufferLength;
m_WaveHdr2.dwLoops=1;
m_WaveHdr2.dwFlags=WHDR_BEGINLOOP|WHDR_ENDLOOP;
::waveOutPrepareHeader(m_hWaveOut,&m_WaveHdr2,sizeof(WAVEHDR));

//バッファに音源データを読み込んで、
//WAVEHDR構造体をWAVE出力デバイスに送信します。

if (!ReadSampleData(&m_WaveHdr1)) return FALSE;
if (!ReadSampleData(&m_WaveHdr2)) return FALSE;
//第一バッファ
::waveOutWrite(m_hWaveOut,&m_WaveHdr1,sizeof(WAVEHDR));
//第二バッファ
::waveOutWrite(m_hWaveOut,&m_WaveHdr2,sizeof(WAVEHDR));

return TRUE;
}

//一時停止
BOOL CWaveOut::Pause()
{
//一時停止していない状態なら、WAVE出力デバイスを一時停止してから
//フラグを立てます。

if (!m_bPause) {
::waveOutPause(m_hWaveOut);
m_bPause=TRUE;
}
//一時停止の状態なら、WAVE出力デバイスを再スタートさせてから
//フラグを消します。

else {
::waveOutRestart(m_hWaveOut);
m_bPause=FALSE;
}
return m_bPause;
}

//音源データを入力ストリームから読み込む
BOOL CWaveOut::ReadSampleData(PWAVEHDR pWaveHdr)
{
ASSERT(!::IsBadWritePtr(pWaveHdr,sizeof(WAVEHDR)));

LPVOID pBuffer=pWaveHdr->lpData;
DWORD dwBufferLength=pWaveHdr->dwBufferLength;
//出力バッファをゼロクリアします。
::ZeroMemory(pBuffer,dwBufferLength);
//入力ストリーム(オーバーライド関数)から音源データを入力します。
UINT nLength=StreamIn(pBuffer,dwBufferLength);
if (!nLength) return FALSE;
DoVolume(pBuffer,nLength,&m_wfxOut);
return TRUE;
}

//WAVE出力デバイスコールバック関数(スタティック関数)
void __stdcall CWaveOut::StaticWaveOutProc(HWAVEOUT hwo,UINT uMsg,DWORD_PTR dwInstance,
DWORD dwParam1,DWORD dwParam2)
{
CWaveOut* pWaveOut=(CWaveOut*)dwInstance;
//CWaveOutクラスへのポインタが有効なら、
//CWaveOutクラスのWaveOutProc関数を呼び出します。

if (::AfxIsValidAddress(pWaveOut,sizeof(CWaveOut)))
pWaveOut->WaveOutProc(hwo,uMsg,dwParam1,dwParam2);
}

//WAVE出力デバイスコールバック関数(オーバーライド)
void CWaveOut::WaveOutProc(HWAVEOUT hwo,UINT uMsg,DWORD dwParam1,DWORD dwParam2)
{
switch(uMsg){
//WAVE出力デバイスを開いた時。
case MM_WOM_OPEN:
break;
//バッファの音源データの演奏が終了した場合。
case MM_WOM_DONE:
//WAVE出力デバイスを停止する場合は、何もせず終了します。
if (m_bStop) break;
//クリティカルセクションで割り込み不可にします。
m_key.Lock();
//演奏が終了したバッファに音源データを読み込んで、
//WAVEHDR構造体をWAVE出力デバイスに送信します。

if (ReadSampleData((PWAVEHDR)(DWORD_PTR)dwParam1)){
::waveOutWrite(hwo,(PWAVEHDR)(DWORD_PTR)dwParam1,sizeof(WAVEHDR));
//コールバックするCWndクラスへのポインタがあれば、
//スライドバーの位置を通知します。

PostMessage(TBM_SETPOS,TRUE,GetPos());
}
//音源データの最後に達した場合。
else{
//コールバックするCWndクラスへのポインタがあれば、
//MM_WOM_DONEメッセージを送信します。
//※このタスク内ではWAVE出力デバイスを停止できないため、
//PostMessage関数を使用して、別のタスクで停止させます。

PostMessage(MM_WOM_DONE,(WPARAM)hwo,dwParam1);
}
//クリティカルセクションを解除します。
m_key.Unlock();
break;
//WAVE出力デバイスを閉じた時。
case MM_WOM_CLOSE:
//コールバックするCWndクラスへのポインタがあれば、
//MM_WOM_CLOSEメッセージを送信します。

PostMessage(MM_WOM_CLOSE,(WPARAM)hwo,dwParam1);
break;
}
}


WaveOut.h
WaveOut.cpp




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

メールアドレス:

ホームページアドレス:

コメント:

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


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

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