まず、気づくのはDirectXを彷彿させるインターフェース型のAPIであることです。多くのメソッドはSLresultで結果を返し、Destroyでリソースを解放して終了します。素直に書くと冗長なコードになりそうですが、まず最初にDirectX開発者にはおなじみの手法を導入します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
template <class T> void SafeDestroy(T& p) | |
{ | |
if (p) { | |
(*p)->Destroy(p); | |
p = nullptr; | |
} | |
} | |
SLresult _slHandleError(const char* func, int line, const char* command, SLresult r) | |
{ | |
if (r != SL_RESULT_SUCCESS) { | |
const char *err = nullptr; | |
switch (r) { | |
#define E(er) case er: err = #er; break | |
E(SL_RESULT_PRECONDITIONS_VIOLATED); | |
E(SL_RESULT_PARAMETER_INVALID); | |
E(SL_RESULT_MEMORY_FAILURE); | |
E(SL_RESULT_RESOURCE_ERROR); | |
E(SL_RESULT_RESOURCE_LOST); | |
E(SL_RESULT_BUFFER_INSUFFICIENT); | |
E(SL_RESULT_CONTENT_CORRUPTED); | |
E(SL_RESULT_CONTENT_UNSUPPORTED); | |
#undef E | |
default: | |
aflog("%s(%d): err=%d %s\n", func, line, r, command); | |
return r; | |
} | |
aflog("%s(%d): %s %s\n", func, line, err, command); | |
} | |
return r; | |
} | |
#define SLHandleError(command) _afHandleSLError(__FUNCTION__, __LINE__, #command, command) | |
#define SLCall(obj,func,...) afHandleSLError((*obj)->func(obj, __VA_ARGS__)) |
SLHandleErrorというのは、各種メソッドがエラーコードを返したらソースコード上の行数と関数呼び出しの概要をログに出力するためのマクロです。Direct3D9のサンプルでは"V"という一文字のマクロが多用されていましたが、それです。
SLCallは、インターフェース呼び出しをすっきり書きたいがために書いたマクロです。これを使ってエンジンの初期化と終了を書いてみます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SL { | |
SLObjectItf engineObject = nullptr; | |
SLEngineItf engineEngine = nullptr; | |
SLObjectItf outputMixObject = nullptr; | |
public: | |
SL() { | |
SLHandleError(slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr)); | |
SLCall(engineObject, Realize, SL_BOOLEAN_FALSE); | |
SLCall(engineObject, GetInterface, SL_IID_ENGINE, &engineEngine); | |
SLInterfaceID ids = SL_IID_ENVIRONMENTALREVERB; | |
SLboolean req = SL_BOOLEAN_FALSE; | |
SLCall(engineEngine, CreateOutputMix, &outputMixObject, 1, &ids, &req); | |
SLCall(outputMixObject, Realize, SL_BOOLEAN_FALSE); | |
} | |
~SL() { | |
SafeDestroy(outputMixObject); | |
SafeDestroy(engineObject); | |
engineEngine = nullptr; | |
} | |
SLEngineItf GetEngine(){ return engineEngine; } | |
SLObjectItf GetOutputMixObject() { return outputMixObject; } | |
}; | |
static SL sl; |
DirectXから来ると戸惑うのが、SLObjectItfとその他インターフェースに分かれていることです。OpenSLでは全てのインスタンスの実体はSLObjectItfであって、何らかのメソッド呼び出しが必要になったらSLObjectItfから追加のインターフェースを取得します。また、SLObjectItfをDestroyするとSLObjectItfから取得したインターフェースも一緒に無効になります。
engineEngineがSafeDestroyではなくnullptrを代入しているのは、engineObjectをSafeDestroyした時に一緒に消滅しているからです。
次は、WAVファイルをロードして"audio player"を作ってみます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct WaveFormatEx { | |
uint16_t tag, channels; | |
uint32_t samplesPerSecond, averageBytesPerSecond; | |
uint16_t blockAlign, bitsPerSample; | |
}; | |
struct WaveContext | |
{ | |
SLObjectItf playerObject; | |
SLPlayItf playerPlay; | |
SLAndroidSimpleBufferQueueItf playerBufferQueue; | |
void *fileImg; | |
int enqueuedSize; | |
bool loop; | |
}; | |
void Voice::Create(const char* fileName) | |
{ | |
context = new WaveContext; | |
memset(context, 0, sizeof(*context)); | |
bool result = false; | |
result = !!(context->fileImg = LoadFile(fileName)); | |
assert(result); | |
const WaveFormatEx* wfx = (WaveFormatEx*)RiffFindChunk(context->fileImg, "fmt "); | |
assert(wfx); | |
SLDataLocator_AndroidSimpleBufferQueue q = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; | |
SLDataFormat_PCM f = {SL_DATAFORMAT_PCM, wfx->channels, wfx->samplesPerSecond * 1000, wfx->bitsPerSample, wfx->bitsPerSample, wfx->channels == 2 ? SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT : SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN}; | |
SLDataSource src = {&q, &f}; | |
SLDataLocator_OutputMix m = {SL_DATALOCATOR_OUTPUTMIX, sl.GetOutputMixObject()}; | |
SLDataSink sink = {&m, nullptr}; | |
SLInterfaceID ids = SL_IID_ANDROIDSIMPLEBUFFERQUEUE; | |
SLboolean req = SL_BOOLEAN_TRUE; | |
SLCall(sl.GetEngine(), CreateAudioPlayer, &context->playerObject, &src, &sink, 1, &ids, &req); | |
SLCall(context->playerObject, Realize, SL_BOOLEAN_FALSE); | |
SLCall(context->playerObject, GetInterface, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &context->playerBufferQueue); | |
SLCall(context->playerObject, GetInterface, SL_IID_PLAY, &context->playerPlay); | |
} |
次はWAVファイルの再生部です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void Voice::Play(bool loop) | |
{ | |
if (!IsReady()) { | |
return; | |
} | |
auto playback = [](SLAndroidSimpleBufferQueueItf q, void* context_) { | |
WaveContext* context = (WaveContext*)context_; | |
int totalSize; | |
const void* buf = RiffFindChunk(context->fileImg, "data", &totalSize); | |
SLCall(q, Enqueue, (char*)buf, totalSize); | |
}; | |
auto doNothing = [](SLAndroidSimpleBufferQueueItf, void*) {}; | |
SLCall(context->playerPlay, SetPlayState, SL_PLAYSTATE_STOPPED); | |
SLCall(context->playerBufferQueue, RegisterCallback, loop ? playback : doNothing, context); | |
SLCall(context->playerPlay, SetPlayState, SL_PLAYSTATE_PLAYING); | |
playback(context->playerBufferQueue, context); | |
} |
これで、OpenSLの初期化からWAVファイルの再生までができました。ところで、この方法だとActivityが非アクティブになっても音が鳴り続けるという問題があります。この解決は次回書きます。
No comments:
Post a Comment