audio
wav
wave
Xaudio2
PCM
More than 1 year has passed since last update.


はじめに

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