Xaudio2を使う

#はじめに
Xaudio2を使ってPCMデータ(wav file)を再生する方法を記載します。
MicrosoftのWebPageによるとGameが想定されているようですがここではwav fileを再生してみます。
> XAudio2 is a low-level audio API. It provides a signal processing and mixing
> foundation for games that is similar to its predecessors, DirectSound and XAudio.

# header/lib
コンパイルに必要なheader/libは次の通りです。

```
xaudio2.lib
winmm.lib

#include <xaudio2.h>
#include <mmsystem.h>
```

#wav fileを再生する例
## mmio
mmioを使ってwav fileを操作します。
mmioOpenでtest.wavファイルを開きます。
mmioDescendを使うことでriffのchunk typeを指定してパースします。
"WAVE"を見つけて、次に"fmt "を見つけます。
"fmt "の後ろにはchannel, sampling rateの情報があります。mmioReadで読み込みます。
次に"data"を見つけます。後ろにあるPCMのデータをmmioReadで読み込みます。
読み込んだデータはXaudio2で再生します。ファイル終端になるまでPCMデータを読み込みます。

```cpp
(snip)

wchar_t* file = L"test.wav";
HMMIO mmio = NULL;
MMIOINFO info = { 0 };
mmio = mmioOpen(file, &info, MMIO_READ);
if (!mmio) {
	printf("error mmioOpen\n");
	return -1;
}

MMRESULT mret;
MMCKINFO riff_chunk;
riff_chunk.fccType = mmioFOURCC('W', 'A', 'V', 'E');
mret = mmioDescend(mmio, &riff_chunk, NULL, MMIO_FINDRIFF);
if (mret != MMSYSERR_NOERROR) {
	printf("error mmioDescend(wave) ret=%d\n", mret);
	return -1;
}

MMCKINFO chunk;
/* fmt chunk */
chunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
mret = mmioDescend(mmio, &chunk, &riff_chunk, MMIO_FINDCHUNK);
if (mret != MMSYSERR_NOERROR) {
	printf("error mmioDescend(fmt) ret=%d\n", mret);
	return -1;
}

WAVEFORMATEX format = { 0 };
{
	DWORD size = mmioRead(mmio, (HPSTR)&format, chunk.cksize);
	if (size != chunk.cksize) {
		printf("error mmioRead(fmt) ret=%d\n", mret);
		return -1;
	}

	printf("foramt    =%d\n", format.wFormatTag);
	printf("channel   =%d\n", format.nChannels);
	printf("sampling  =%dHz\n", format.nSamplesPerSec);
	printf("bit/sample=%d\n", format.wBitsPerSample);
	printf("byte/sec  =%d\n", format.nAvgBytesPerSec);
}

(snip)

/* data chunk */
chunk.ckid = mmioFOURCC('d', 'a', 't', 'a');
mret = mmioDescend(mmio, &chunk, &riff_chunk, MMIO_FINDCHUNK);
if (mret != MMSYSERR_NOERROR) {
    printf("error mmioDescend(data) ret=%d\n", mret);
    return -1;
}

voice->Start();

XAUDIO2_BUFFER buffer = { 0 };
const int buf_len = 2;
BYTE** buf = new BYTE*[buf_len];
const int len = format.nAvgBytesPerSec;
for (int i = 0; i < buf_len; i++) {
    buf[i] = new BYTE[len];
}
int buf_cnt = 0;
int size;
size = mmioRead(mmio, (HPSTR)buf[buf_cnt], len);
buffer.AudioBytes = size;
buffer.pAudioData = buf[buf_cnt];

(snip)
```

## Xaudio2
IXAudio2VoiceCallbackのサブクラスを作成します。CreateSourceVoiceにインスタンスを設定して、
PCMデータの補給が必要になったときにOnBufferEndが呼ばれるようにします。この関数でEventにシグナルを送ります。
シグナルを受けるとSubmitSourceBufferでPCMデータをバッファキューに供給します。

処理の流れは次の通りです。

- 初期化
- voice->Start() 再生を開始します。
- ファイルからPCMデータを取得します。
- SubmitSourceBufferでバッファをキューに入れます。
- WaitForSingleObjectでバッファ供給タイミングを待ちます。
- (PCMデータがなくなるまで上3つを繰り返します)
- voice->Stop() 再生を停止します。

以下、ソースコードの抜粋です。

```cpp
class VoiceCallback : public IXAudio2VoiceCallback
{
public:
  HANDLE event;
  VoiceCallback() : event(CreateEvent(NULL, FALSE, FALSE, NULL)) {}
  ~VoiceCallback() { CloseHandle(event); }
  void STDMETHODCALLTYPE OnBufferEnd(void * pBufferContext) { SetEvent(event); }
  (snip)
};

CoInitializeEx(...);

IXAudio2 *audio = NULL;
XAudio2Create(&audio, ...);

IXAudio2MasteringVoice *master = NULL;
audio->CreateMasteringVoice(&maser, ...);

IXAudio2SourceVoice *voice = NULL;
WAVEFORMATEX format = { 0 };
/* set format value from riff info */
VoiceCallback callback;
audio->CreateSourceVoice(&voice, &format, ..., &callback, ...);

voice->Start();

/* read pcm from wav file */
voice->SubmitSourceBuffer(...);

do {
  /* read pcm from wav file */
  voice->SubmitSourceBuffer(...);  
} while(WaitForSingleObject(callback.event, INFINITE) == WAIT_OBJECT_0);

voice->Stop();
```

## buffer / queue
bufferとqueueについて説明します。

ここでbufferと言っているのはXAUDIO2_BUFFERの変数です。
再生対象のPCMデータはbuffer.pAudioDataのメモリ領域です。
queueにはXAUDIO2_BUFFER変数はコピーされますがbuffer.pAudioDataはコピーされません。
したがって再生完了を待ってこの領域を解放する必要があります。

XAudio2は内部にqueueを持っています。
ユーザはSubmitSourceBufferを使ってqueueにbufferを登録します。
登録順に再生されます。

PCMをすべて再生し終わると、queueからbufferを取り出して、
次のbufferのPCMの再生を開始します。このタイミングでOnBufferEndが呼ばれます。
OnBufferEndでSetEventすることでbufferが破棄されたことを検知できるので

- 再生し終わったbuffer.pAudioDataの解放
- 次のbufferの準備

を行います。

bufferを2つ用意することで、

- 使用中のbuffer
- 次のbuffer

を分けることができます。こうすることで音飛なく再生することができます。
逆に、2つ以上のbufferを用意しないとうまく再生できないことがあります。

## sample code全体

```cpp
#include "stdafx.h"

class VoiceCallback : public IXAudio2VoiceCallback
{
public:
	HANDLE event;
	VoiceCallback() : event(CreateEvent(NULL, FALSE, FALSE, NULL)) {}
	~VoiceCallback() { CloseHandle(event); }
	void STDMETHODCALLTYPE OnStreamEnd() { printf("%s\n", __func__); }
	void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() {}
	void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32 SamplesRequired) {}
	void STDMETHODCALLTYPE OnBufferEnd(void * pBufferContext) { SetEvent(event); }
	void STDMETHODCALLTYPE OnBufferStart(void * pBufferContext) {}
	void STDMETHODCALLTYPE OnLoopEnd(void * pBufferContext) { printf("%s\n", __func__); }
	void STDMETHODCALLTYPE OnVoiceError(void * pBufferContext, HRESULT Error) { printf("%s\n", __func__); }
};

int main()
{
	HRESULT ret;

	ret = CoInitializeEx(NULL, COINIT_MULTITHREADED);
	if (FAILED(ret)) {
		printf("error CoInitializeEx ret=%d\n", ret);
		return -1;
	}

	IXAudio2 *audio = NULL;
	ret = XAudio2Create(
		&audio
		// UINT32 Flags = 0,
		// XAUDIO2_PROCESSOR XAudio2Processor = XAUDIO2_DEFAULT_PROCESSOR
	);
	if (FAILED(ret)) {
		printf("error XAudio2Create ret=%d\n", ret);
		return -1;
	}

	IXAudio2MasteringVoice *master = NULL;
	ret = audio->CreateMasteringVoice(
		&master
		// UINT32 InputChannels = XAUDIO2_DEFAULT_CHANNELS,
		// UINT32 InputSampleRate = XAUDIO2_DEFAULT_SAMPLERATE,
		// UINT32 Flags = 0,
		// UINT32 DeviceIndex = 0,
		// const XAUDIO2_EFFECT_CHAIN *pEffectChain = NULL
	);
	if (FAILED(ret)) {
		printf("error CreateMasteringVoice ret=%d\n", ret);
		return -1;
	}

	wchar_t* file = L"test.wav";
	HMMIO mmio = NULL;
	MMIOINFO info = { 0 };
	mmio = mmioOpen(file, &info, MMIO_READ);
	if (!mmio) {
		printf("error mmioOpen\n");
		return -1;
	}

	MMRESULT mret;
	MMCKINFO riff_chunk;
	riff_chunk.fccType = mmioFOURCC('W', 'A', 'V', 'E');
	mret = mmioDescend(mmio, &riff_chunk, NULL, MMIO_FINDRIFF);
	if (mret != MMSYSERR_NOERROR) {
		printf("error mmioDescend(wave) ret=%d\n", mret);
		return -1;
	}

	MMCKINFO chunk;
	/* fmt chunk */
	chunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
	mret = mmioDescend(mmio, &chunk, &riff_chunk, MMIO_FINDCHUNK);
	if (mret != MMSYSERR_NOERROR) {
		printf("error mmioDescend(fmt) ret=%d\n", mret);
		return -1;
	}

	WAVEFORMATEX format = { 0 };
	{
		DWORD size = mmioRead(mmio, (HPSTR)&format, chunk.cksize);
		if (size != chunk.cksize) {
			printf("error mmioRead(fmt) ret=%d\n", mret);
			return -1;
		}

		printf("foramt    =%d\n", format.wFormatTag);
		printf("channel   =%d\n", format.nChannels);
		printf("sampling  =%dHz\n", format.nSamplesPerSec);
		printf("bit/sample=%d\n", format.wBitsPerSample);
		printf("byte/sec  =%d\n", format.nAvgBytesPerSec);
	}

	IXAudio2SourceVoice *voice = NULL;
	VoiceCallback callback;
	ret = audio->CreateSourceVoice(
		&voice,
		&format,
		0,                          // UINT32 Flags = 0,
		XAUDIO2_DEFAULT_FREQ_RATIO, // float MaxFrequencyRatio = XAUDIO2_DEFAULT_FREQ_RATIO,
		&callback                   // IXAudio2VoiceCallback *pCallback = NULL,
		// const XAUDIO2_VOICE_SENDS *pSendList = NULL,
		// const XAUDIO2_EFFECT_CHAIN *pEffectChain = NULL
	);
	if (FAILED(ret)) {
		printf("error CreateSourceVoice ret=%d\n", ret);
		return -1;
	}

	/* data chunk */
	chunk.ckid = mmioFOURCC('d', 'a', 't', 'a');
	mret = mmioDescend(mmio, &chunk, &riff_chunk, MMIO_FINDCHUNK);
	if (mret != MMSYSERR_NOERROR) {
		printf("error mmioDescend(data) ret=%d\n", mret);
		return -1;
	}

	voice->Start();

	XAUDIO2_BUFFER buffer = { 0 };
	const int buf_len = 2;
	BYTE** buf = new BYTE*[buf_len];
	const int len = format.nAvgBytesPerSec;
	for (int i = 0; i < buf_len; i++) {
		buf[i] = new BYTE[len];
	}
	int buf_cnt = 0;
	int size;
	size = mmioRead(mmio, (HPSTR)buf[buf_cnt], len);
	buffer.AudioBytes = size;
	buffer.pAudioData = buf[buf_cnt];
	if (0 < size) {
		ret = voice->SubmitSourceBuffer(&buffer);
		if (FAILED(ret)) {
			printf("error SubmitSourceBuffer ret=%d\n", mret);
			return -1;
		}
	}
	if (buf_len <= ++buf_cnt) buf_cnt = 0;

	do {
		size = mmioRead(mmio, (HPSTR)buf[buf_cnt], len);
		if (size <= 0) {
			break;
		}
		buffer.AudioBytes = size;
		buffer.pAudioData = buf[buf_cnt];
		ret = voice->SubmitSourceBuffer(&buffer);
		if (FAILED(ret)) {
			printf("error SubmitSourceBuffer ret=%d\n", mret);
			return -1;
		}
		if (buf_len <= ++buf_cnt) buf_cnt = 0;
	} while (WaitForSingleObject(callback.event, INFINITE) == WAIT_OBJECT_0);

	voice->Stop();

	/* wait user input */
	getchar();

	/* clean up */
	for (int i = 0; i < buf_len; i++) {
		delete[] buf[i];
	}
	delete[] buf;
	mmioClose(mmio, MMIO_FHOPEN);
	voice->DestroyVoice(); voice = NULL;
	master->DestroyVoice(); master = NULL;
	audio->Release(); audio = NULL;
	CoUninitialize();

	return 0;
}
```

# references
[XAudio2 Introduction](https://msdn.microsoft.com/en-us/library/windows/desktop/ee415813(v=vs.85).aspx)
[XAudio2 APIs](https://msdn.microsoft.com/en-us/library/windows/desktop/hh405049(v=vs.85).aspx)