■ 概要
ERI第4水準フォーマットのMIOファイルは、MIODynamicPlayer クラスで再生することが出来ます。
MIODynamicPlayer クラスは、ERI ファイルの中に含まれる音声ストリームだけを取り出し、展開する機能を有したクラスです。
音声ストリームは、動的にファイルから読み込まれ、動的に再生されます。
MIODynamicPlayer クラスは派生しないでも利用できますが、派生して使うのが一般的です。
■ 主なメンバ関数
MIODynamicPlayer クラスの主なメンバ関数を以下に説明します。
- Open 関数
EFileObject へのポインタを渡し、ファイルの中に含まれる音声ストリームへのインターフェースを開きます。
- GetWaveBufferFrom 関数
音声ストリームの中の特定の位置へシークする時にこの関数を使います。
- IsNextDataRewound 関数
音声ストリームの中で、次のデータが、音声ストリームの先頭かどうかを判定します。
この関数は、音声ストリームが終端に到達したかどうかを判定するために使います。
- GetNextWaveBuffer 関数
音声ストリームの中で、次のデータブロックを取得します。
- AllocateWaveBuffer 関数
音声データを格納するためのバッファを確保するための関数です。
特別なメモリアロケーションを供給したい場合には、派生したクラスでこの関数をオーバーライドします。
- DeleteWaveBuffer 関数
音声データを格納するために AllocateWaveBuffer 関数で確保されたメモリを破棄するための関数です。
特別なメモリアロケーションを供給したい場合には、派生したクラスでこの関数をオーバーライドします。
- GetChannelCount 関数
音声データのチャネル数を取得します。
- GetFrequency 関数
音声データのサンプリング周波数を取得します。
- GetBitsPerSample 関数
音声データのビット深度を取得します。
- GetTotalSampleCount 関数
音声ストリームに含まれる全サンプル数を取得します。
■ サンプルプログラム
では、具体的な例として、ERINA-Library に同梱の mioplayer を例にとってプログラムを見ていきましょう。mioplayobj.h を見てください。
ここでは、MIODynamicPlayer クラスから、MIOPlayObject クラスを派生させています。
このクラスは、音声出力デバイスをオブジェクトに内包し、ファイルから再生した音声データを音声出力デバイスに出力します。では、具体的な実装を見てみましょう。
mioplayobj.cpp を見てください。MIOPlayObject::Open 関数は、ファイル名を受け取りファイルを開きます。
この関数は、まず EReadFile オブジェクトでファイルを開いています。次に MIODynamicPlayer::Open を読み出しています。if ( !m_file.Open( pszMioFile ) )音声ファイルのオープンに成功したら、次は音声出力デバイスを開いています。
{
return "ファイルを開けませんでした。" ;
}
if ( !MIODynamicPlayer::Open( &m_file ) )
{
return "不正なMIOファイルです。" ;
}WAVEFORMATEX wfx ;音声出力デバイスを開く前に、スレッドを作成していますが、これは、音声ストリーミング用のスレッドです。
wfx.wFormatTag = WAVE_FORMAT_PCM ;
wfx.nChannels = (WORD) GetChannelCount( ) ;
wfx.nSamplesPerSec = GetFrequency( ) ;
wfx.wBitsPerSample = (WORD) GetBitsPerSample( ) ;
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8 ;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign ;
wfx.cbSize = 0 ;
//
MMRESULT mmr = ::waveOutOpen
( &m_hWaveOut, WAVE_MAPPER, &wfx,
(DWORD) &MIOPlayObject::waveOutProc,
(DWORD) this, CALLBACK_FUNCTION ) ;
音声出力デバイスのコールバック関数に MIOPlayerObject::waveOutProc を指定していますが、この関数では以下の様に、スレッドに音声データの再生終了を通知しています。if ( uMsg == WOM_DONE )音声ストリーミング用スレッドでは、PostThreadMessage でポストされたメッセージを受け取り、次の音声データを音声出力デバイスに出力しています。
{
WAVEHDR * pwh = (WAVEHDR*) dwParam1 ;
::PostThreadMessage
( m_idThread, WOM_DONE, 0, (LPARAM) pwh ) ;
}
MIOPlayObject::StreamingThreadProc 関数を見てみましょう。else if ( msg.message == WOM_DONE )まず、データブロック再生終了の通知を受けると、そのデータを破棄しています。
{
//
// 過去のデータを破棄する
bool fFirstBuf = false ;
WAVEHDR * pwh = (WAVEHDR*) msg.lParam ;
::waveOutUnprepareHeader( m_hWaveOut, pwh, sizeof(WAVEHDR) ) ;
if ( m_pFirstOut != NULL )
{
if ( (BYTE*) pwh->lpData ==
((BYTE*)m_pFirstOut) + m_dwPlayOffset )
{
DeleteWaveBuffer( m_pFirstOut ) ;
m_pFirstOut = NULL ;
::SetEvent( m_hReleased ) ;
fFirstBuf = true ;
::InterlockedDecrement( (LPLONG) &m_nOutBufCount ) ;
}
}
if ( !fFirstBuf )
{
DeleteWaveBuffer( (void*) pwh->lpData ) ;
::InterlockedDecrement( (LPLONG) &m_nOutBufCount ) ;
}
delete pwh ;
//
// 次のデータを取得する
if ( m_fPlaying )
{
if ( !IsNextDataRewound( ) )
{
DWORD dwBytes ;
void * ptrWaveBuf = GetNextWaveBuffer( dwBytes ) ;
if ( ptrWaveBuf != NULL )
{
pwh = new WAVEHDR ;
memset( pwh, 0, sizeof(WAVEHDR) ) ;
pwh->lpData = (LPSTR) ptrWaveBuf ;
pwh->dwBufferLength = dwBytes ;
::waveOutPrepareHeader
( m_hWaveOut, pwh, sizeof(WAVEHDR) ) ;
::waveOutWrite
( m_hWaveOut, pwh, sizeof(WAVEHDR) ) ;
::InterlockedIncrement( (LPLONG) &m_nOutBufCount ) ;
}
}
}
//
if ( m_nOutBufCount <= 0 )
{
OnEndPlaying( ) ;
}
}
これは、waveOutUnprepareHeader 関数を呼び出して、バッファを解放できるように設定してから、DeleteWaveBuffer 関数でデータを解放しています。
m_pFirstOut メンバと比較しているのは、シークした時の先頭バッファかどうかを判定するためです。次に、音声ストリームの中で、次の音声データを取得し、出力しています。
この時、IsNextDataReqound 関数で、ストリームの終端かどうかもチェックしています。
GetNextWaveBuffer 関数が成功すれば、音声出力デバイスに出力すると言う簡単なものです。