はじめに
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;
}