Friday, December 25, 2015

OpenSL ESでActivityが非アクティブになった時に音を停止する簡単な方法

前回OpenSL ESでサウンドを再生しましたが、Activityが非アクティブになっても音が鳴り続ける問題がありました。それに対しての簡単な対処法として、バッファを細かく分けてEnqueueしてみました。Enqueueは32768バイト単位でしています。(サイズは適当です)

void Voice::Play(bool loop)
{
if (!IsReady()) {
return;
}
auto playback = [](SLAndroidSimpleBufferQueueItf q, void* context_) {
double now = GetTime();
if (now - systemMisc.GetLastUpdateTime() >= 0.5) {
return;
}
WaveContext* context = (WaveContext*)context_;
int totalSize;
const void* buf = RiffFindChunk(context->fileImg, "data", &totalSize);
int enqueued = context->enqueuedSize;
if (enqueued >= totalSize) {
if (!context->loop) {
return;
}
enqueued = 0;
}
int toEnqueue = std::min(totalSize - enqueued, 32768);
enqueued += toEnqueue;
context->enqueuedSize = enqueued;
SLCall(q, Enqueue, (char*)buf + enqueued - toEnqueue, toEnqueue);
};
SLCall(context->playerPlay, SetPlayState, SL_PLAYSTATE_STOPPED);
SLCall(context->playerBufferQueue, RegisterCallback, playback, context);
context->enqueuedSize = 0;
context->loop = loop;
SLCall(context->playerPlay, SetPlayState, SL_PLAYSTATE_PLAYING);
playback(context->playerBufferQueue, context);
}
最終アップデートからの経過時間が0.5秒を超えたらコールバック無視します。この方法の長所は、コールバック一つでループ再生と非アクティブになった時の音停止を同時に実装でき、コードが簡潔になります。Activityから非アクティブの通知を受け取る必要もありません。短所としては、アップデートループで長い処理をすると音が切れてしまうことです。

また、停止した音を再開する処理を盛り込んでいません。ここは、ゲーム毎の実装依存が多いと思いあえてしませんでした。例えば多くの場合は再開すべき音はBGMだけであったりするでしょう。

AndroidのOpenSL ESはコールバックは別スレッドから来るので特に気をつける必要があります。Android特有の実装についてはNDKのdocs/Additional_library_docs/opensles/index.html に説明があるので、一度目を通しておくと良さそうです。

No comments:

Post a Comment