今日は、先日登壇した
DroidKaigi
にて紹介したOboeライブラリを使用したサンプルコードを読んでいきます。
Oboeとは
Android
NDKの高性能オーディオ
API
を使用する
C++
ライブラリです。
ちなみに、Oboeはハイパフォーマンスなゲームオーディオとして
Android
Game
SDK
に組み込まれています
*1
。
なぜOboeを使う必要があるのか
Oboe は
Android
デ
バイス
の 99 % 以上で最低レイテンシを提供している実績があります。
Voicyの
Android
アプリでは現状、喫緊でOboeを採用する必要性は正直ないです。
ですが、収録前の音声読み込み時間やSEの再生までの
レイテンシー
, 端末依存による音質の違いなど、音質やパフォーマンスチューニングの余地はたくさんあり、ナレッジを蓄積していきた考えです。
C++
で書く必要がありますが、コード量は少なく大変使いやすいコードベースです。また英語ではありますが、手厚いコミュニティサポートもあります。加えて、昨今のカラオケや
音ゲー
など音を扱うアプリがOboeを採用する動きがあります。
*2
基本的な実装
オーディオストリームを作成する
ストリームの構築を行います。
AAudioStream で表現される「オーディオ ストリーム」に対して読み書きすることにより、オーディオ データをやり取りします。
bool AudioEngine::start() {
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
// 浮動小数点数の音声フォーマット
AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT);
// 出力はモノラル
AAudioStreamBuilder_setChannelCount(streamBuilder, 1);
// 低レイテンシのパフォーマンス モードを設定
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
// AAudioStream_writeよりデータ コールバック関数のアプローチのほうが低レイテンシ アプリに適している。
// データ コールバック関数は、ストリームに音声データが必要になるたびに、優先度の高いスレッドから呼び出されます。
// これで dataCallback 関数の準備ができたので、start() メソッドからそれを使用するようストリームに指示することも簡単に行えます
// (:: は、関数がグローバル名前空間にあることを示します)。
AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this);
// AAUDIO_OK 以外の結果が出た場合は、出力を Android Studio の Android Monitor ウィンドウ
// に記録し、false を返します。
aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream_);
if (result != AAUDIO_OK) {
return false;
// すべての設定が完了したので、ストリームを開始して、音声データの使用とデータ コールバックのトリガーを開始できます。
result = AAudioStream_requestStart(stream_);
if (result != AAUDIO_OK) {
__android_log_print(ANDROID_LOG_ERROR, "AudioEngine", "Error starting stream %s",
AAudio_convertResultToText(result));
return false;
AAudioStreamBuilder_delete(streamBuilder);
return true;
音ゲーのミニマムをつくる
Oboeのgithubリポジトリにはいくつかサンプルコードが含まれていて、今回はその中でも簡単な音楽ゲーム [RhythmGame]のコードを参照しながらなにやってるかみていきます。
どんなゲーム?
音に合わせてタイミングよく画面タップするゲームです。
4拍子のトラックが連続的にループします。
ゲーム開始時に、すぐに音楽が再生され、最初の小節で3拍で手拍子の音が流れます。
ユーザーは、2小節目が始まったら画面をタップして、最初に流れた1小節目と同じタイミングで3回拍手を繰り返します。
タップするたびに、拍手音が鳴ります。タイミングよくタップすると、画面が緑色に点滅します。
タップするタイミングが早すぎるとオレンジ色に、遅すぎると紫色に点滅します。
複数のサウンドを同時に再生するには、それらをミックスする必要があります。
このサンプルにはミキサーオブジェクトが提供されています。
// 拍手音のデータソースとプレーヤーを作成する
std::shared_ptr<AAssetDataSource> mClapSource {
AAssetDataSource::newFromCompressedAsset(mAssetManager, "CLAP.mp3")
if (mClapSource == nullptr){
LOGE("Could not load source data for clap sound");
return false;
mClap = std::make_unique<Player>(mClapSource);
// BGMのデータソースとプレーヤーを作成します。
std::shared_ptr<AAssetDataSource> backingTrackSource {
AAssetDataSource::newFromCompressedAsset(mAssetManager, "FUNKY_HOUSE.mp3")
if (backingTrackSource == nullptr){
LOGE("Could not load source data for backing track");
return false;
mBackingTrack = std::make_unique<Player>(backingTrackSource);
mBackingTrack->setPlaying(true);
mBackingTrack->setLooping(true);
// 両方のプレーヤーをミキサーに加える
mMixer.addTrack(mClap.get());
mMixer.addTrack(mBackingTrack.get());
return true;
音とタップイベントを正確に同期させる
AudioStreamクラスがあります。
onAudioReadyが呼ばれるたびに、BGMのオーディオフレームが(ミキサーを通して)オーディオストリームにレンダリングされます。
書き込まれたフレームの数を数えることで、正確な再生時間と、拍手音を再生するタイミングをえることができます。
これを踏まえて、拍手イベントを正確なタイミングで再生する方法を説明します。
現在再生しているオーディオフレームを曲の位置にミリ秒単位で変換します。
この曲の位置で拍手をする必要があるかどうかをチェックする。必要であれば、再生するといった制御を行います。
DataCallbackResult Game::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
float *outputBuffer = static_cast<float *>(audioData);
int64_t nextClapEventMs;
for (int i = 0; i < numFrames; ++i) {
mSongPositionMs = convertFramesToMillis(
mCurrentFrame,
mAudioStream->getSampleRate());
if (mClapEvents.peek(nextClapEventMs) && mSongPositionMs >= nextClapEventMs){
mClap->setPlaying(true);
mClapEvents.pop(nextClapEventMs);
mMixer.renderAudio(outputBuffer+(oboeStream->getChannelCount()*i), 1);
mCurrentFrame++;
mLastUpdateTime = nowUptimeMillis();
return DataCallbackResult::Continue;
画面上のUIとオーディオを同期させる
Game.cppの、scheduleSongEventsというメソッドで、拍手イベントを待ち受けます。
void Game::scheduleSongEvents() {
mClapEvents.push(0);
mClapEvents.push(500);
mClapEvents.push(1000);
音声の再生や録音は以下のCodelabから学べます。
https://developer.android.com/codelabs/making-waves-2-sampler?hl=ja#6