FC2ブログ

ホーム > WAVEファイルフォーマット > WAVEファイルの再生 第1部

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* pWndOwner=NULL);
//デストラクタ
virtual ~CWaveOut();

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

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

BOOL Play(const WAVEFORMATEX* pwfx,UINT timePerOut);

//演奏状態
//演奏中かどうか

BOOL IsActivate(){return (m_hWaveOut!=NULL);};
//一時停止する
BOOL Pause();
//一時停止中かどうか
BOOL IsPaused(){return m_bPause;};

//音源ストリームの入力(オーバーライド
virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize){return FALSE;};

//WAVEFORMATEX構造体の取得
void GetWaveFormatEx(WAVEFORMATEX* pwfx);

//音量調節
//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(){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(){return m_nDelayStereo;};

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

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

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

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

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

CCriticalSection m_key;

WAVEFORMATEX m_wfx; //WAVEFORMATEX構造体
CWnd* m_pWndOwner; //コールバックする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_byteDelta; //遅延バッファに書ききれなかった音源データを、次回分に保存しておくバッファ
int m_nDelayStereo; //ステレオ変換時の遅延時間(㍉秒)
};


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

//WAVEFORMATEX構造体の取得
inline void CWaveOut::GetWaveFormatEx(WAVEFORMATEX* pwfx)
{
if (AfxIsValidAddress(pwfx,sizeof(WAVEFORMATEX)))
::CopyMemory(pwfx,&m_wfx,sizeof(WAVEFORMATEX));
}


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

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

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

Init();
}

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


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

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

::ZeroMemory(&m_wfx,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* pwfx,UINT timePerOut)
{
ASSERT(AfxIsValidAddress(pwfx,sizeof(WAVEFORMATEX),0));
if (pwfx->cbSize!=sizeof(WAVEFORMATEX)) return FALSE;

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

//引数のWAVEFORMATEX構造体をメンバー変数にコピーします。
::CopyMemory(&m_wfx,pwfx,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_wfx,
(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(AfxIsValidAddress(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_wfx);
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クラスへのポインタがあれば、
//スライドバーの位置を通知します。

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

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

if (m_pWndOwner) m_pWndOwner->PostMessage(MM_WOM_CLOSE,(WPARAM)hwo,dwParam1);
break;
}
}


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

// CWaveFileクラス

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

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

//読み込み中のデバイスハンドルが有効か?
virtual BOOL IsOpen(){return (m_hmmioIn!=0);};
//WAVEファイルを開く
virtual BOOL OpenFile(LPCTSTR lpszFileName);
//WAVEファイルヘッダーを読み込み、WAVEFORMATEX構造体を取得します。
BOOL ReadHeader(HMMIO hmmio);

//Waveストリームの読み込み
//pIndexSample://現在の演奏位置変数へのポインタ

virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample);
//音源ストリームの入力(CWaveOutクラスのStreamIn関数をオーバーライドします。)
virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize);

//WAVEファイルの書き込み
BOOL SaveFile(LPCTSTR lpszFileName);
//Waveストリームへの書き込み
BOOL StreamOut(HMMIO hmmio);

//演奏を開始します。
//※インテルCORE i7-920の場合、100㍉秒で問題なく動作しましたが
//ペンティアム4の場合は500㍉秒位にしないと音が安定しませんでした。

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

//演奏位置を設定します。
void SetPos(UINT nPos){m_indexSample=(nPos<GetSavedSamples())? nPos:GetSavedSamples();};
//演奏位置を取得します。
UINT GetPos(){return m_indexSample;};
//音源データのバイト数を取得します。
UINT GetLength(){return m_nDataSize;};
//実際にファイルに書かれている音源データ数を取得します。
UINT GetSavedSamples(){return (m_nDataSaved)?m_nDataSaved/m_wfx.nBlockAlign:0;};
//演奏予定の音源データ数を取得します。
UINT GetSamples(){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(){return (UINT)((double)m_indexSample*1000.0/(double)m_wfx.nSamplesPerSec+0.5);};
//総演奏予定時間を取得します。
virtual UINT GetTotalTime(){return (UINT)((double)GetSamples()*1000.0/(double)m_wfx.nSamplesPerSec+0.5);};

//ファイルのWAVEFORMATEX構造体と、WAVE出力デバイスに引き渡すWAVEFORMATEX構造体を取得します。
void GetWaveFormatEx(WAVEFORMATEX* pwfxFile,WAVEFORMATEX* pwfxOut);
//WAVE出力デバイスに引き渡すWAVEFORMATEX構造体を設定します。
void SetWaveFormatEx(const WAVEFORMATEX* pwfxOut);

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

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

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

UINT m_indexSample; //現在の演奏位置
UINT m_nDataSize; //演奏予定のデータバイト数
UINT m_nDataSaved; //実際にファイルに書かれているデータバイト数

CString m_strPathName; //現在演奏中のパス名
public:
//現在演奏中のパス名の取得
LPCTSTR GetPathName(){return m_strPathName;};
};


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

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

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

if (AfxIsValidAddress(pwfxFile,sizeof(WAVEFORMATEX)))
::CopyMemory(pwfxFile,&m_wfx,sizeof(WAVEFORMATEX));

//第二引数が有効な場合、第二引数の示す位置に
//WAVE出力デバイスに引き渡すWAVEFORMATEX構造体をコピーします。

if (AfxIsValidAddress(pwfxOut,sizeof(WAVEFORMATEX)))
CWaveOut::GetWaveFormatEx(pwfxOut);
}

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


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

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

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



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

//メンバ変数の初期化
void CWaveFile::Init()
{
m_hmmioIn=0;
::ZeroMemory(&m_ckRiffIn,sizeof(MMCKINFO));
::ZeroMemory(&m_ckDataIn,sizeof(MMCKINFO));
::ZeroMemory(&m_wfx,sizeof(WAVEFORMATEX));
m_posStart=0;
m_indexSample=0;
m_nDataSize=0;
m_nDataSaved=0;
}

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



<<GIFファイルの書き込み 第3部ページの先頭WAVEファイルの再生 第2部>>



スポンサーサイト



コメント: 0

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

Trackback+Pingback: 0

TrackBack URL for this entry
http://hiroshi0945.blog75.fc2.com/tb.php/45-b4a2de84
Listed below are links to weblogs that reference
WAVEファイルの再生 第1部 from マルチメディアファイルフォーマット

Home > WAVEファイルフォーマット > WAVEファイルの再生 第1部

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

この人とブロともになる

ブロとも一覧

このページの先頭へ戻る