[XAudio2]ソースボイスを使って簡単に再生してみる

前回作ったSingletonクラス+これからの書き方に捕捉。

  • マルチスレッドに完全対応できてません
  • get_instanceが参照なのは、ポインタでアクセスするのが微妙だったからです
  • 自作するクラス、関数群の名前空間は基本的にmy_audioとしておきます

捕捉も程々に、今回から本題。
全てのオーディオ再生の起点となる、ソースボイスを作成して音を流します。

その前に、XAudio2自体の説明がしづらくなるので、
PCMファイルローダインターフェイスを宣言しておきましょう。
XAudio2が再生できる形式(PCMデータ)でオーディオデータをデコードするクラスです。

// pcm_loader.h
#ifndef __PCM_LOADER_H__
#define __PCM_LOADER_H__

#include <string>
#include <exception>
#include <boost/cstdint.hpp>
#include <boost/shared_ptr.hpp>
#include <xaudio2.h>

namespace my_audio
{

class pcm_loader
{
public:
	virtual ~pcm_loader() {};

	virtual void            get_wfx( WAVEFORMATEX& wfx ) const=0;
	virtual boost::uint32_t get_wavesize() const=0;
	virtual boost::uint32_t get_segment( void* buffer, boost::uint32_t buffsize )=0;
	virtual boost::uint32_t seek( int flag, boost::uint32_t offset )=0;

	typedef boost::shared_ptr<pcm_loader> value_type;
	static pcm_loader::value_type create_instance( const std::string& filepath );
};

class loader_exception : public std::exception {};

};

#endif

get_wfxがWAVEFORMATEX構造体のデータを取得
get_wavesizeがPCMファイルのデータ部のサイズを取得
get_segmentがbufferに対してbuffsize分のデータを格納
seekがflagからoffset分移動
create_instanceは読み込むファイルのパスから実装クラスを作成して返却
loader_exceptionは、create_instanceで投げられる可能性のある例外クラスです
…となります。
上のクラスをオーディオファイル読み込みに使っていきます。
そのうちWAVEファイルやOggVorbisファイルのローダをこのクラスを継承して実装する方法を紹介する予定です。

今回は、main関数に全て書き出してみましょう。

#include <boost/scoped_array.hpp>
#include <stdio.h>
#include "xaudio2_singleton.h"  // 前回作ったSingletonクラスの宣言
#include "pcm_loader.h"

using namespace my_audio;
using namespace std;
using namespace boost;

int main()
{
	xaudio2_singleton& xaudio2_ = xaudio2_singleton::get_instance();
	if( FAILED(xaudio2_.initialize()) ){ return -1; };

	// オーディオファイルのロード
	string filepath( "test.wav" );
	pcm_loader::value_type loader;
	try {
		loader = pcm_loader::create_instance( filepath );
	} catch( loader_exception ) {
		xaudio2_.release();
		return -2;
	}

	// ソースボイスの作成
	IXAudio2SourceVoice* source_voice_;
	WAVEFORMATEX wfx;
	loader->get_wfx( wfx );
	HRESULT hr;
	hr = xaudio2_.get_engine()->CreateSourceVoice( &source_voice_, &wfx );
	if( FAILED(hr) )
	{
		xaudio2_.release();
		return -3;
	}
	source_voice_->Start();	// ソースボイスの開始

	// バッファを作成して読み出し
	const uint32_t WaveSize = loader->get_wavesize();
	scoped_array<BYTE> buffer;
	try {
		buffer.reset( new BYTE[ WaveSize ] );
	} catch( std::bad_alloc ) {
		source_voice_->DestroyVoice();
		xaudio2_.release();
		return -4;
	}
	loader->get_segment( buffer.get(), WaveSize );

	// 無限ループ再生
	XAUDIO2_BUFFER xa2_buff = {NULL};
	xa2_buff.AudioBytes     = WaveSize;
	xa2_buff.pAudioData     = buffer.get();
	xa2_buff.Flags          = XAUDIO2_END_OF_STREAM;
	xa2_buff.LoopCount      = XAUDIO2_LOOP_INFINITE;
	source_voice_->SubmitSourceBuffer( &xa2_buff );

	// キー入力待ち(待ち状態の間ずっと再生)
	getchar();

	// 全て削除
	source_voice_->DestroyVoice();
	xaudio2_.release();
	return 0;
}

恐らくこうなると思います。
WAVEFORMATEX構造体のデータさえ手に入れば、それを取得して、IXAudio2::CreateSourceVoiceメソッドでインスタンスを生成するだけ。ソースボイスの初期化自体は実質2、3行程度ですね。
後は、バッファを作成してデータを全て読み出し、XAUDIO2_BUFFER構造体に再生するデータのサイズ、バッファへの先頭ポインタ、再生フラグ(これは指定してもしなくても大丈夫なようです。デバッグエンジンだとWarningがでます。)を設定して、IXAudio2SourceVoice::SubmitSourceBufferメソッドで再生します。
今回はXAUDIO2_LOOP_INFINITEを設定して、無限ループ再生にしました。待機中ずっと音楽が再生され続けます。
(余談ですが、バッファへの先頭ポインタを示すXAUDIO2_BUFFER::pAudioDataメンバがconst BYTE*なのが凄く気になります。個人的にはconst void*にしてくれてると良かったのですが…。)
削除するときは、必ずソースボイス(とサブミックスボイス)→マスターボイス→エンジン→COMの順番になるので、ソースボイスを削除してから、xaudio2_singleton::releaseメソッドを呼び出します。
(そうしないとデバッグエンジンでBreak Pointが発動して止まります。リリースエンジンだとエンジンが削除されるまで全てのボイスのインスタンスがメモリ上に残ったままになるようです。)
ローダとバッファはshared_ptr(scoped_array)なので勝手に削除されますから大丈夫ですね。
エラーチェックも極力最小限にしましたが、作りこむ場合はインターフェイスの戻り値などをしっかり見ていく必要があると思います。

これで、一応メモリ上に全てのデータを展開して再生できたわけですが、PCMデータとなると3分弱のもので恐らく30〜40MB近くのデータサイズになり、メモリ圧迫の原因になりますし、bad_alloc例外が投げられる可能性がとても高いでしょう。
なので、この方法は効果音など、データサイズがそこまで大きくないものに対して使い、BGMであればストリーミング再生を取る必要があります。
次の記事ではboost.threadを用いてストリーミング再生を簡単に実装してみたいと思います。