マルチメディアAPI(MME)利用してマイクから録音する
(その2)
ダイアログの[録音]ボタンを押されたときに、外部入力(マイク)からの録音を開始します。
[録音]ボタンは、録音開始時と一時停止からの録音復帰時に押されますので、入出力デバイス等の二重オープンを防止する為に、入力デバイスの有無でデバイスのオープン済みかどうかを判定します。
(よって、何れかのデバイスでオープンに失敗したときは、オープンに成功した他のデバイスもクローズするようにしておきます。)
録音開始までの手順は以下のようになります。
- 音声データを記録する為にWAVファイルをオープンします。
- サウンド入力デバイスをオープンします。
- データブロックを確保して、サウンド入力デバイスにデータブロックを関連付けます。
- サウンド出力デバイスをオープンします。
- サウンド入力バッファをリセットします。
- サウンド入力バッファにサンプリングした音声データを格納するバッファをキューイングします。
- マイクからの音声データサンプリング(サウンド入力処理)を開始します。
最後に録音の一時停止中を判定する為に録画中フラグをセットしておきます。
サンプルコードを以下に示します。
/****************************************************************************/
/*!
* @brief 外部音声の録音を開始する.
*
* @param [in] hDlg ダイアログのウインドウ・ハンドル.
*
* @retval TRUE = OK. / FALSE = NG.
*/
static BOOL RecSound( HWND hDlg )
{
if( !hWI ){
//! オーディオ・データ・フォーマットを設定する.
WAVEFORMATEX wf;
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.nChannels = 2;
wf.wBitsPerSample = 16;
wf.nBlockAlign = 4;
wf.nSamplesPerSec = SMPFRQ;
wf.nAvgBytesPerSec = SMPFRQ * wf.nBlockAlign;
wf.cbSize = 0;
//! ダイアログから再生するWAVファイル名を取得する.
GetWindowText( GetDlgItem( hDlg, IDC_FILENAME ), FileName, sizeof(FileName) - 1 );
//! 保存するWAVファイルを新規作成でオープンする.
int result = SaveOpenWaveFile( FileName, &wf, FALSE );
if( result == -2 ){
//! 同一のファイル名がある場合、上書きするかどうかを問い合わせる.
if( MessageBox( hDlg, _T("既にファイルが存在しています。上書きしますか?"), _T("WAVファイルの保存"), MB_YESNO ) == IDYES ){
//! 上書きモードでファイルをオープンする.
result = SaveOpenWaveFile( FileName, &wf, TRUE );
}else{
return FALSE;
}
}
if( result != 0 ){
MessageBox( hDlg, _T("ファイルをオープンできません"), _T("WAVファイルの保存"), MB_OK );
return FALSE;
}
//! サウンド入力デバイスをオープンする.
UINT num = ComboBox_GetCurSel( GetDlgItem( hDlg, IDC_INPUT_DEVICE ) );
if( !OpenSoundInputDevice( hDlg, num, &wf ) ){
return FALSE;
}
//! サウンド出力デバイスをオープンする.
num = ComboBox_GetCurSel( GetDlgItem( hDlg, IDC_OUTPUT_DEVICE ) );
if( !OpenSoundOutDevice( hDlg, num, &wf ) ){
return FALSE;
}
//! ボリューム設定をダイアログの設定にあわせる.
VolumeControl( GetDlgItem( hDlg, IDC_VOLUME ) );
//! サウンド入力バッファをリセットする.
waveInReset( hWI );
//! データブロックをサウンド入力デバイスにキューイングする.
for( DWORD i = 0; i < 2; i++ ){
InHdr[i].dwBytesRecorded = 0;
waveInAddBuffer( hWI, &InHdr[i], sizeof(WAVEHDR) );
}
//! サウンド入力処理を開始する.
waveInStart( hWI );
}
//! 録画中フラグをセットする.
RecFlag = TRUE;
EnableWindow( GetDlgItem( hDlg, IDC_REC ), 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 );
EnableWindow( GetDlgItem( hDlg, IDC_INPUT_DEVICE ), FALSE );
return TRUE;
}
/****************************************************************************/
/*!
* @brief サウンド入力デバイスをオープンする.
*
* @param [in] hDlg ダイアログのウインドウ・ハンドル.
* @param [in] num オープンするデバイス番号.
* @param [in] wf オーディオ情報構造体のポインタ.
*
* @retval TRUE = OK. / FALSE = NG.
*/
static BOOL OpenSoundInputDevice( HWND hDlg, UINT num, WAVEFORMATEX* wf )
{
//! 入力デバイスをオープンする.
if( waveInOpen( &hWI, num, wf, (DWORD)hDlg, 0, CALLBACK_WINDOW ) != MMSYSERR_NOERROR ){
return FALSE;
}
//! データブロックを入力デバイスに登録する.
memset( InHdr, 0, sizeof(InHdr) );
for( int i = 0; i < 2; i++ ){
InHdr[i].lpData = new char[BUFSIZE];
InHdr[i].dwBufferLength = BUFSIZE;
if( waveInPrepareHeader( hWI, &InHdr[i], sizeof(WAVEHDR) ) != MMSYSERR_NOERROR ){
return FALSE;
}
}
return TRUE;
}
waveInOpen()を呼び出して、サウンド入力デバイスをオープンします。
MMRESULT waveInOpen(
LPHWAVEIN phwi,
UINT uDeviceID,
LPWAVEFORMATEX pwfx,
DWORD dwCallback,
DWORD dwCallbackInstance,
DWORD fdwOpen )
引数 | 説明 |
---|---|
phwi | オープンしたサウンド入力デバイスのハンドルを格納する変数へのポインタ |
uDeviceID |
オープンするサウンド入力デバイスの識別子 |
pwfx | WAVEFORMATEX構造体のポインタ |
dwCallback | サウンド入力デバイスからのMM_WIM_DATAメッセージを受け取るウインドウのハンドル |
dwCallbackInstance | fdwOpen に CALLBACK_WINDOW を指定するので 0 を設定 |
fdwOpen | サウンド入力デバイスからの通知をウインドウ・メッセージで受け取る方法にするのでCALLBACK_WINDOWを指定 |
waveInOpen()からの戻り値がMMSYSERR_NOERRORであれば、デバイスのオープンは成功です。
入力するサウンドデータのデータ形式(WAVEFORMATEX構造体)は以下のとおりです。
メンバ名 | 説明 |
---|---|
wFormatTag |
オーディオデータのデータ形式 |
nChannels |
オーディオデータのチャンネル数 |
wBitsPerSample | オーディオデータのビット数 |
nBlockAlign |
1サンプルあたりのバイト数 チャンネル数(nChannels)とデータビット長(wBitsPerSample)により、以下のようになる
- 2チャンネル16ビットの場合、4バイト nBlockAlign = nChannels * ((wBitsPerSample + 7) >> 3)となる |
nSamplesPerSec | サンプリング周波数(1秒間あたりのサンプル数) |
nAvgBytesPerSec |
1秒間あたりのサウンドデータのバイト数 |
cbSize |
拡張データのバイト数の指定。 |
waveInPrepareHeader()を呼び出して、入力するサウンドデータを格納するデータブロックを、オープンしたサウンド入力デバイスで使用できるようにします。
MMRESULT waveInPrepareHeader( HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh )
引数 | 説明 |
---|---|
hwi | サウンド入力デバイスのハンドル |
pwh | 初期化するWAVEHDR構造体のポインタ |
cbwh | WAVEHDR構造体のバイト数 |
waveInPrepareHeader()からの戻り値がMMSYSERR_NOERRORであれば、データブロックの利用が可能です。
メンバ名 | 説明 |
---|---|
lpData | データバッファのポインタ |
dwBufferLength | データバッファのサイズ |
dwBytesRecorded | サンプリングした音声データのバイト数 |
dwUser | ユーザーが自由に使用できるデータ領域 |
dwFlags | データブロックの状態を示す為のフラグ |
dwLoops | サウンド入力時は使用しない |
lpNext | ドライバが使用する領域 |
使用するデータブロックはwaveInUnprepareHeader()によってクリーンアップされるまでは有効な変数でないといけませんので、静的変数で確保します。
サウンド入力が開始されると、キューイングされているデータブロックにサンプリングした音声データを格納していきます。
データブロックのサイズ分の音声データの格納が完了すると、アプリケーションに対してデータ入力完了のコールバックを行います。
このときデバイスは、次にキューイングされているデータブロックがあれば、そのデータブロックから継続してサンプリングした音声データを格納していきます。
キューイングされているデータブロックが無い場合、次のデータブロックがキューイングされるまでサンプリングを中断してしまいます。
よって、連続してデータ入力を行いたい場合、データブロックは2つ以上準備します。
waveInReset()を呼び出して、サウンド入力処理のリセットを行います。
MMRESULT waveInReset( HWAVEIN hwi )
waveInReset()からの戻り値がMMSYSERR_NOERRORであれば、成功です。
waveInAddBuffer()を呼び出して、データブロックをサウンド入力デバイスのデータ・キューにキューイングします。
MMRESULT waveInAddBuffer( HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh )
引数 | 説明 |
---|---|
hwi | サウンド入力デバイスのハンドル |
pwh | キューイングするデータブロック(WAVEHDR構造体)のポインタ |
cbwh | WAVEHDR構造体のバイト数 |
waveInAddBuffer()からの戻り値がMMSYSERR_NOERRORであれば、データブロックがキューイングされます。
音声データのサンプリング開始は、サウンド出力のようにデータブロックをデバイスにキューイングしただけでは開始されません。
waveInStart()を呼び出して、音声データのサンプリング開始します。
MMRESULT waveInStart( HWAVEIN hwi )
waveInStart()からの戻り値がMMSYSERR_NOERRORであれば、音声データのサンプリングが開始されます。