7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Xaudio2を使う

Last updated at Posted at 2016-10-01

はじめに

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データを読み込みます。

(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() 再生を停止します。

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

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全体

# 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
XAudio2 APIs

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?