マルチメディアAPI(MME)を利用してWAVファイルを再生する
(その2)
ダイアログの[再生]ボタンを押されたときに、WAVファイルの再生を開始します。
WAVファイルがオープンされていない場合、WAVファイルをオープンします。
次にオープンしたWAVファイルの情報を元にサウンド出力デバイスのオープンを行います。
最後にWAVファイルから再生するオーディオデータを読み込んでサウンド出力デバイスに投入します。
これでWAVファイルの再生が開始されます。
サンプルコードを以下に示します。
/****************************************************************************/
/*!
* @brief WAVファイルを再生する.
*
* @param [in] hDlg ダイアログのウインドウ・ハンドル.
*
* @retval TRUE = OK. / FALSE = NG.
*/
static BOOL PlaySoundOutput( HWND hDlg )
{
//! WAVファイルをオープンしていない場合、オープンする.
if( !IsOpenWaveFile() ){
//! -1.ダイアログから再生するWAVファイル名を取得する.
GetWindowText( GetDlgItem( hDlg, IDC_FILENAME ),
FileName, sizeof(FileName) - 1 );
//! -2.再生するWAVファイル名をオープンする.
if( !ReadOpenWaveFile( FileName ) ){
MessageBox( hDlg, _T("ファイルをオープンできません"),
_T("WAVファイルの再生"), MB_OK );
return FALSE;
}
}
if( !hWO ){
//! オープンしたWAVファイルのオーディオ・フィーマットを取得する.
WAVEFORMATEX wf;
GetAudioFormat( &wf );
//! サウンド出力デバイスをオープンする.
UINT num = ComboBox_GetCurSel( GetDlgItem( hDlg, IDC_OUTPUT_DEVICE ) );
if( OpenSoundOutDevice( hDlg, num, &wf ) ){
//! WAVファイルの読み込み用バッファを確保する.
if( !DataBuff ){
DataSize = wf.nAvgBytesPerSec >> 2; // 1sec / 4 = 250ms.
DataBuff = new BYTE[DataSize];
}
//! ボリューム設定をダイアログの設定にあわせる.
VolumeControl( GetDlgItem( hDlg, IDC_VOLUME ) );
}
}
//! WAVファイルを読み込んで、サウンド出力を開始する.
for( DWORD i = 0; i < 2; i++ ){
//! -1.データブロックをクリアする.
memset( OutHdr[i].lpData, 0, DataSize );
//! -2.WAVファイルからデータブロック分のデータを読み込む.
ReadWaveFile( OutHdr[i].lpData, DataSize );
//! -3.読み込んだオーディオ・データを出力デバイスに投入する.
OutHdr[i].dwBufferLength = DataSize;
waveOutWrite( hWO, &OutHdr[i], sizeof(WAVEHDR) );
}
EnableWindow( GetDlgItem( hDlg, IDC_PLAY ), FALSE );
EnableWindow( GetDlgItem( hDlg, IDC_PAUSE ), TRUE );
EnableWindow( GetDlgItem( hDlg, IDC_STOP ), TRUE );
EnableWindow( GetDlgItem( hDlg, IDC_FILENAME ), FALSE );
EnableWindow( GetDlgItem( hDlg, IDC_FILE_SELECT ), FALSE );
EnableWindow( GetDlgItem( hDlg, IDC_OUTPUT_DEVICE ), FALSE );
return TRUE;
}
/****************************************************************************/
/*!
* @brief サウンド出力デバイスをオープンする.
*
* @param [in] hDlg ダイアログのウインドウ・ハンドル.
* @param [in] num オープンするデバイス番号.
* @param [in] wf 再生するWAVファイルのオーディオ情報構造体のポインタ.
*
* @retval TRUE = OK. / FALSE = NG.
*/
static BOOL OpenSoundOutDevice( HWND hDlg, UINT num, WAVEFORMATEX* wf )
{
//! 出力デバイスをオープンする.
if( waveOutOpen( &hWO, num, wf, (DWORD)hDlg, 0, CALLBACK_WINDOW )
!= MMSYSERR_NOERROR ){
return FALSE;
}
//! データブロックを出力デバイスに登録する.
memset( OutHdr, 0, sizeof(OutHdr) );
for( int i = 0; i < 2; i++ ){
OutHdr[i].lpData = new char[wf->nAvgBytesPerSec];
if( waveOutPrepareHeader( hWO, &OutHdr[i], sizeof(WAVEHDR) )
!= MMSYSERR_NOERROR ){
return FALSE;
}
}
return TRUE;
}
waveOutOpen()を呼び出して、サウンド出力デバイスをオープンします。
MMRESULT waveOutOpen(
LPHWAVEOUT phwo,
UINT uDeviceID,
LPWAVEFORMATEX pwfx,
DWORD dwCallback,
DWORD dwCallbackInstance,
DWORD fdwOpen )
引数 | 説明 |
---|---|
phwo |
オープンしたサウンド出力デバイスのハンドルを格納する変数へのポインタ |
uDeviceID |
オープンするサウンド出力デバイスの識別子 |
pwfx | WAVEFORMATEX構造体のポインタ |
dwCallback | サウンド出力デバイスからのWM_WOM_DONEメッセージを受け取るウインドウのハンドル |
dwCallbackInstance | fdwOpen に CALLBACK_WINDOW を指定するので 0 を設定 |
fdwOpen | サウンド出力デバイスからの通知をウインドウ・メッセージで受け取る方法にするのでCALLBACK_WINDOWを指定 |
waveOutOpen()からの戻り値がMMSYSERR_NOERRORであれば、デバイスのオープンは成功です。
出力するサウンドデータのデータ形式(WAVEFORMATEX構造体)は以下のとおりです。
メンバ名 | 説明 |
---|---|
wFormatTag |
オーディオデータのデータ形式 |
nChannels |
オーディオデータのチャンネル数 |
wBitsPerSample | オーディオデータのビット数 |
nBlockAlign |
1サンプルあたりのバイト数 チャンネル数(nChannels)とデータビット長(wBitsPerSample)により、以下のようになる
- 2チャンネル16ビットの場合、4バイト nBlockAlign = nChannels * ((wBitsPerSample + 7) >> 3)となる |
nSamplesPerSec | サンプリング周波数(1秒間あたりのサンプル数) |
nAvgBytesPerSec |
1秒間あたりのサウンドデータのバイト数 |
cbSize |
拡張データのバイト数の指定。 |
waveOutPrepareHeader()を呼び出して、出力するサウンドデータを格納するデータブロックを、オープンしたサウンド出力デバイスで使用できるようにします。
MMRESULT waveOutPrepareHeader( HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh )
引数 | 説明 |
---|---|
hwo | サウンド出力デバイスのハンドル |
pwh | 初期化するWAVEHDR構造体のポインタ |
cbwh | WAVEHDR構造体のバイト数 |
waveOutPrepareHeader()からの戻り値がMMSYSERR_NOERRORであれば、データブロックの利用が可能です。
メンバ名 | 説明 |
---|---|
lpData | データバッファのポインタ |
dwBufferLength | データバッファ中のサウンド出力するデータのバイト数 |
dwBytesRecorded | サウンド出力時は使用しない |
dwUser | ユーザーが自由に使用できるデータ領域 |
dwFlags | データブロックの状態を示す為のフラグ |
dwLoops | ループ再生時のループ回数 |
lpNext | ドライバが使用する領域 |
使用するデータブロックはwaveOutUnprepareHeader()によってクリーンアップされるまでは有効な変数でないといけません。
(要するにデータブロックは、静的変数として確保しておくという事です。)
サウンド出力が開始されると、キューイングされているデータブロックから出力するサウンドデータを取り出して、
指定されたデータバイト数のデータ出力を行います。
デバイスはデータ出力完了後に、アプリケーションに対してデータ出力完了のコールバックを行います。
このときデバイスは、次にキューイングされているデータブロックがあれば、そのデータブロックから継続してデータ出力を行います。
キューイングされているデータブロックが無い場合、次のデータブロックがキューイングされるまでデータ出力を中断してしまいます。
よって、連続してデータ出力を行いたい場合、データブロックは2つ以上準備します。