XAudio2BasicStream


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

XAudio2BasicStream


目的:
SDLみたいにリアルタイムにbufferを利用して音声をデバイスへ流し込みたい。
SDLでもいいんだけど、のちのちにBEEPとくっつけたいので。
Macは良くわかってないが、Linuxに関しては、
Beep音を鳴らすモジュールがデフォルトで組み込まれていないので
厄介なのでWin用にしたいというアレです、あれ。


DirectXSDKのデフォルトなら
C:\Program Files\Microsoft DirectX SDK (June 2010)\Samples\C++\XAudio2\XAudio2BasicStream\

にあるソースコードをちょっと読んでみる。

main

CoInitializeEx( NULL, COINIT_MULTITHREADED );
COMの初期化に利用するらしい関数

IXAudio2* pXAudio2 = NULL;
XAudio2へ命令するときのインタフェース。インスタンス。

#ifdef _DEBUG
   flags |= XAUDIO2_DEBUG_ENGINE;
#endif
XAudio2Create( &pXAudio2, flags );
デバッグモードのフラグを指定して初期化可能。

IXAudio2MasteringVoice* pMasteringVoice = NULL;
マスタリングボイスを作る様子。

pXAudio2->CreateMasteringVoice( &pMasteringVoice )
XAudio2のインタフェースを利用してマスタリングボイスを作る。
戻り値にオブジェクトを渡して、
内部で例外呼んだ方が気持ちいいがそれは自分が未熟だからかも。
あと、CPP的じゃないのだろう。

しかしこういう時ってpMasteringVoiceのNULLチェックはしなくていいものか。
WCHAR wavebank[ MAX_PATH ];
FindMediaFileCch( wavebank, MAX_PATH, L"wavebank.xwb" )

xwbって拡張子が。WaveBankってことはDirectMusicのあの音源?
http://blog.iskysoft.com/xact-wave-bank-xwb/

Collection of waves, or sound files, created with XACT (the Microsoft Cross-Platform Audio Creation Tool);
logically grouped into a single file by an audio designer so it can be integrated into a video game.

XACT supports both in-memory banks (typically used for sound effects)
and streaming wave banks (often used for background music);
audio files may be saved in .WAV, .AIFF, XMA, xWMA, and ADPCM formats.

waveファイルの集合でXACTによって作られる。
論理的なグループ付けをされた信号ファイル。
ContentsLoadとかで引っ張ってくるんだろうが、
今は用なしなのでさらっと進む。

Wavebank wb;
wb.Load( wavebank ); 

バンクのロード。

CreateFile( wavebank, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
                               FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL );

Winプログラムをそこそこやらないから良くわかってないんだけど、
これを利用すべきなんだろう。Windowsの場合は。
ロックをかけたりできるはず。
失敗した場合は、HRESULT型としてINVALID_HANDLE_VALUEが返って来る。


待機ループ内

char formatBuff[ 64 ]; 
WAVEFORMATEX *wfx = reinterpret_cast<WAVEFORMATEX*>(&formatBuff);

reinterpet_castはおなじみ、ポインタや参照の関係するキャスト。
キャラクタ型ポインタのformatBufferを無理にWVEFORMATEX*に変える。

wb.GetEntryFormat( i, wfx );
用意できたwfxを利用して、wb.GetEntryFormatを呼ぶ。
wbはwbのファイル。おそらくバンク内からi番目のデータを引っ張ってきて、
formatBuffに格納してるんだと思われる。
DWORD waveOffset = wb.GetEntryOffset( i );
DWORD waveLength = wb.GetEntryLengthInBytes( i );

さらにバンクファイルから情報を引き出し利用。
このバンクから引っ張ってきたデータを渡す部分を見たいのよ。(笑)



StreamingVoiceContext voiceContext;

IXAudio2SourceVoice* pSourceVoice;
if( FAILED( hr = pXAudio2->CreateSourceVoice( &pSourceVoice, wfx, 0, 1.0f, &voiceContext ) ) )
{
  wprintf( L"\nError %#X creating source voice\n", hr );
  exit = true;
  break;
}
pSourceVoice->Start( 0, 0 );

SourceVoiceを作る。

StreamingVoiceContextはIXAudio2VoiceCallbackを継承した構造体。
コールバックを固めたもの。ソースコード冒頭に記述。
これの実態としてvoiceContextという変数を作る。
wfxはさっき作ったバッファ。

pSourceVoiceを作れた。何に利用するかは二の次。

OVERLAPPED ovlCurrentRequest = {0};
ovlCurrentRequest.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

BYTE buffers[MAX_BUFFER_COUNT][STREAMING_BUFFER_SIZE];
DWORD currentDiskReadBuffer = 0;
DWORD currentPosition = 0;

  • こーひぃブレイク-

// Create an overlapped structure and buffers to handle the async I/O
OVERLAPPED ovlCurrentRequest = {0};
ovlCurrentRequest.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

BYTE buffers[MAX_BUFFER_COUNT][STREAMING_BUFFER_SIZE];
DWORD currentDiskReadBuffer = 0;
DWORD currentPosition = 0;


オーバラップ構造体とasyncIOのハンドルを作るとのこと。
asyncってことは非同期IOってことっすかね。

次のコードを見てから戻りたい。
while( currentPosition < waveLength ){
 ...以下の処理...
}

音声再生中はここのwhileが呼ばれる様子。

wprintf( L"." );
if( GetAsyncKeyState( VK_ESCAPE ) ){
  exit = true;

  while( GetAsyncKeyState( VK_ESCAPE ) )
  Sleep( 10 );
  break;
}

ESCAPEキーで終了。なんで押してる間はsleepにしたんだろ。


DWORD cbValid = min( STREAMING_BUFFER_SIZE, waveLength - currentPosition );
ovlCurrentRequest.Offset = waveOffset + currentPosition;
if( !ReadFileEx( hAsync, buffers[ currentDiskReadBuffer ], STREAMING_BUFFER_SIZE, &ovlCurrentRequest,
  NULL ) )
{
  wprintf( L"\nCouldn't start async read: error %#X\n", HRESULT_FROM_WIN32( GetLastError() ) );
  exit = true;
  break;
}
currentPosition += cbValid;

現在位置の計算(追記)とファイルからの読み取り。
ReadFileEx。こんなややこしい記述をみるとcのfopenを使いたくなるが、
それはNGなんだろう。

DWORD cb;
GetOverlappedResult( hAsync, &ovlCurrentRequest, &cb, TRUE );

XAUDIO2_VOICE_STATE state;
 for(; ; )
 {
 pSourceVoice->GetState( &state );
 if( state.BuffersQueued < MAX_BUFFER_COUNT - 1 ) break;

 WaitForSingleObject( voiceContext.hBufferEndEvent, INFINITE );
 }

ここで待機。state.BuffersQueued < MAX_BUFFER_COUNT -1になった場合にbreak;


//
// At this point we have a buffer full of audio and enough room to submit it, so
// let's submit it and get another read request going.
//
XAUDIO2_BUFFER buf = {0};
buf.AudioBytes = cbValid;
buf.pAudioData = buffers[currentDiskReadBuffer];
if( currentPosition >= waveLength ) buf.Flags = XAUDIO2_END_OF_STREAM;

pSourceVoice->SubmitSourceBuffer( &buf );

currentDiskReadBuffer++;
currentDiskReadBuffer %= MAX_BUFFER_COUNT;

問題のシーンか。
pSourceVoice->SubmitSourceBufferを利用して引数のサウンドをデバイスへ渡す。
XAUDIO_BUFFERのFlagsにXAUDIO2_END_OF_STREAMを入れると渡したときにうまく終わってくれる。
http://msdn.microsoft.com/ja-jp/library/bb944086%28v=vs.85%29.aspx
というか推奨されるそうだ。


GPLにするつもりなので、
こんなに辛いのならSDLで打つのも悪くないような気も。

余談,気になるところ

struct StreamingVoiceContext : public IXAudio2VoiceCallback

構造体も継承できるんだすな。Publicなクラスとは思っていたけど、
継承も許されてるとは知らなかった(笑)


STDMETHOD_( void, OnVoiceProcessingPassStart )( UINT32 )

#define
STDMETHOD_(type,method) virtual type STDMETHODCALLTYPE method

つまり、
virtual void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32)


に展開される。
STDMETHODCALLTYPEマクロは通常windows標準のコールの
__stdcall

http://loafer.jp/mixi/diary/class.xsp?2006-11-13-00-21
に展開されるとのこと。


COMカオス過ぎる。業務は避けたいな、これ。

結論

SDLで書く、Beepの部分は別DLLにして、loadlibraryできない場合は擬似Beep.soを呼ぶ方針で。
終わり。