サイトアイコン GAMEWORKS LAB

XAudio2でサウンドの再生

サウンドの再生

前回の記事で『XAudio2の初期化』を行いました。

次はサウンドの再生部分を作っていきたいと思います。
XAudio2で個々のサウンドを制御するのはソースボイス(SourceVoice)が行います。

 

ソースボイスの作成

XAudio2の初期化と同じくソースボイスも作成は簡単です。
ただし、ソースボイスでサウンドを再生するには別途波形データの情報が必要となります。

どちらかと言えばこちらで苦労することが多くなってきます。

今回、先ずはソースボイスの初期化から順番に説明していきます。

SourceVoiceの作成
IXAudio2SourceVoice* pSourceVoice = nullptr;
// wfexは後述する波形データのフォーマット情報となります
pXAudio->CreateSourceVoice(&pSourceVoice, &wfex);


これだけで作成できます。
ただ、サウンドデータのフォーマット(wfex)が分からなければ作成ができません。

ソースボイスを作成するには前提としてサウンドデータのフォーマットを知っておく必要があります。
しかし、フォーマットと言ってもサウンドには定まったフォーマットがある訳ではありません。

なので事前にサウンドデータを読み込んでのフォーマットを取得しておく必要があります。

 

Waveファイルの読み込み

次はWave(.wav)ファイルを使った形で説明していきます。
今回は基本的なファイルフォーマットにのみ対応した例外無視の実装をしていきます。

Waveを読むために、先ずは各情報を読み取るための構造体を用意します。

// チャンクデータの基本構造 
struct Chunk
{
	char	id[4]; // チャンク毎のID
	int32_t	size;  // チャンクサイズ
};

// RIFFヘッダー
struct RiffHeader
{
	Chunk	chunk;   // "RIFF"
	char	type[4]; // "WAVE"
};

// FMTチャンク
struct FormatChunk
{
	Chunk		chunk; // "fmt "
	WAVEFORMAT	fmt;   // 波形フォーマット
};


こんな感じです。
この3つがあれば簡単にWAVEが読み込めてしまいます。

WaveファイルはChunkと呼ばれる形式で管理されています。
Chunkの基本フォーマットは下記です。

id チャンクの識別子として4文字
size データ部のサイズ
データ部 各チャンク毎のデータ

Waveこのフォーマットで並べられたデータとなっています。

先ず先頭にRIFFチャンク

id “RIFF”
size データ部のサイズ
データ部 チャンクデータ一覧

このようにRIFFチャンクは全体のチャンクを内包したチャンクとなります。
sizeにはidとsizeの8バイトを除いたファイルサイズが入っています。

次にFormatチャンクです。
波形のフォーマットが格納されています。

id “fmt “
size データ部のサイズ
WAVEFORMAT 波形のフォーマット

この部分を元にSourceVoiceで再生するフォーマットを取得します。

最後にDataチャンクです。
ここに本体となる波形データが格納されています。

id “data”
size データ部のサイズ
data 波形データ

この波形データをSourceVoiceに流し込むことで音が鳴るようになります。

ではこれらを読み込んで再生する処理を見ていきましょう。

 

SourceVoiceでの再生

再生するためには先ほどの説明を元に読み込みを行う必要があります。

Waveファイルの読み込み
FILE* file = NULL;
// Waveファイルを開く
if (fopen_s(&file, filename, "rb") != 0) {
	return nullptr;
}

// RIFFヘッダーの読み込み
RiffHeader riff;
fread(&riff, sizeof(riff), 1, file);

// Formatチャンクの読み込み
FormatChunk format;
fread(&format, sizeof(format), 1, file);

// Dataチャンクの読み込み
Chunk data;
fread(&data, sizeof(data), 1, file);

// Dataチャンクのデータ部(波形データ)の読み込み
char* pBuffer = (char*)malloc(data.size);
fread(pBuffer, data.size, 1, file);

fclose(file);


これでWaveフォーマットの各チャンクを元に読み込みが行えます。
※特殊なフォーマットのWaveは読み込めません

Waveが読み込めたら後はSourceVoiceを作るだけです。

SourceVoiceの生成
WAVEFORMATEX wfex{};
// 波形フォーマットの設定
memcpy(&wfex, &format.fmt, sizeof(format.fmt));
// 1サンプル辺りのバッファサイズを算出します
wfex.wBitsPerSample = format.fmt.nBlockAlign * 8 / format.fmt.nChannels;

IXAudio2SourceVoice* pSourceVoice = nullptr;
// 波形フォーマットを元にSourceVoiceの生成
if (FAILED(pXAudio->CreateSourceVoice(&pSourceVoice, &wfex))) {
	free(pBuffer);
	return nullptr;
}


SourceVoiceに必要なフォーマットはFormatチャンクに含まれていた情報です。
あとはこれを元に1サンプル辺りのバッファサイズを追加すれば出来上がりです。

SourceVoicceの再生
XAUDIO2_BUFFER buf{};
// 再生する波形データの設定
buf.pAudioData = (BYTE*)pBuffer;
buf.Flags = XAUDIO2_END_OF_STREAM;
buf.AudioBytes = data.size;
// 波形データの再生
sourceVoice->SubmitSourceBuffer(&buf);
sourceVoice->Start();


これで波形データの再生が行えます。
では一連の流れをつなげて関数化してみましょう。

 

Waveファイルの再生関数

再生まで一気に行うPlayWave関数の例です。

IXAudio2SourceVoice* PlayWave(const char* filename)
{
	FILE* file = NULL;
	if (fopen_s(&file, filename, "rb") != 0) {
		return nullptr;
	}
	// RIFFヘッダーの読み込み
	RiffHeader riff;
	fread(&riff, sizeof(riff), 1, file);

	// Formatチャンクの読み込み
	FormatChunk format;
	fread(&format, sizeof(format), 1, file);

	// Dataチャンクの読み込み
	Chunk data;
	fread(&data, sizeof(data), 1, file);

	// Dataチャンクのデータ部(波形データ)の読み込み
	char* pBuffer = (char*)malloc(data.size);
	fread(pBuffer, data.size, 1, file);

	fclose(file);

	WAVEFORMATEX wfex{};
	// 波形フォーマットの設定
	memcpy(&wfex, &format.fmt, sizeof(format.fmt));
	wfex.wBitsPerSample = format.fmt.nBlockAlign * 8 / format.fmt.nChannels;

	// 波形フォーマットを元にSourceVoiceの生成
	IXAudio2SourceVoice* pSourceVoice = nullptr;
	if (FAILED(pXAudio->CreateSourceVoice(&pSourceVoice, &wfex))) {
		free(pBuffer);
		return nullptr;
	}

	// 再生する波形データの設定
	XAUDIO2_BUFFER buf{};
	buf.pAudioData = (BYTE*)pBuffer;
	buf.Flags = XAUDIO2_END_OF_STREAM;
	buf.AudioBytes = data.size;

	// 波形データの再生
	pSourceVoice->SubmitSourceBuffer(&buf);
	pSourceVoice->Start();

	return pSourceVoice;
}


今回の関数では再生まで全て一連の流れにしていますが、もちろん読み込みだけ行って後で再生するといった使い方も可能です。

今回の場合はSourceVoiceを戻り値として返しているので、呼び出し側で止めたい歳にStopを呼ぶ形となります。

削除の方法はMasteringVoiceと同じでDestroyVoiceを呼び出します。

pSourceVoice->DestroyVoice();
pSourceVoice = nullptr;


これでXAudio2を使用した再生は完了です。
Waveファイルのフォーマットさえ分かれば、難しい処理もないので、DirectSoundなどと比べても楽に再生できるようになったんではないでしょうか!

モバイルバージョンを終了