音声再生プログラム
 
 

■ 概要

 ERI第4水準フォーマットのMIOファイルは、MIODynamicPlayer クラスで再生することが出来ます。
 MIODynamicPlayer クラスは、ERI ファイルの中に含まれる音声ストリームだけを取り出し、展開する機能を有したクラスです。
 音声ストリームは、動的にファイルから読み込まれ、動的に再生されます。
 MIODynamicPlayer クラスは派生しないでも利用できますが、派生して使うのが一般的です。
 

■ 主なメンバ関数

 MIODynamicPlayer クラスの主なメンバ関数を以下に説明します。
 
 

■ サンプルプログラム

 では、具体的な例として、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 )
{
    WAVEHDR * pwh = (WAVEHDR*) dwParam1 ;
    ::PostThreadMessage
        ( m_idThread, WOM_DONE, 0, (LPARAM) pwh ) ;
}
 音声ストリーミング用スレッドでは、PostThreadMessage でポストされたメッセージを受け取り、次の音声データを音声出力デバイスに出力しています。
 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 関数が成功すれば、音声出力デバイスに出力すると言う簡単なものです。

 
 

戻る