/** * Copyright 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include "HelloOboeEngine.h" #include "SoundGenerator.h" /** * Main audio engine for the HelloOboe sample. It is responsible for: * * - Creating a callback object which is supplied when constructing the audio stream, and will be * called when the stream starts * - Restarting the stream when user-controllable properties (Audio API, channel count etc) are * changed, and when the stream is disconnected (e.g. when headphones are attached) * - Calculating the audio latency of the stream * */ HelloOboeEngine::HelloOboeEngine() : mLatencyCallback(std::make_unique()), mErrorCallback(std::make_unique(*this)) { } double HelloOboeEngine::getCurrentOutputLatencyMillis() { if (!mIsLatencyDetectionSupported) return -1.0; std::lock_guard lock(mLock); if (!mStream) return -1.0; // Get the time that a known audio frame was presented for playing auto result = mStream->getTimestamp(CLOCK_MONOTONIC); double outputLatencyMillis = -1; const int64_t kNanosPerMillisecond = 1000000; if (result == oboe::Result::OK) { oboe::FrameTimestamp playedFrame = result.value(); // Get the write index for the next audio frame int64_t writeIndex = mStream->getFramesWritten(); // Calculate the number of frames between our known frame and the write index int64_t frameIndexDelta = writeIndex - playedFrame.position; // Calculate the time which the next frame will be presented int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / (mStream->getSampleRate()); int64_t nextFramePresentationTime = playedFrame.timestamp + frameTimeDelta; // Assume that the next frame will be written at the current time using namespace std::chrono; int64_t nextFrameWriteTime = duration_cast(steady_clock::now().time_since_epoch()).count(); // Calculate the latency outputLatencyMillis = static_cast(nextFramePresentationTime - nextFrameWriteTime) / kNanosPerMillisecond; } else { LOGE("Error calculating latency: %s", oboe::convertToText(result.error())); } return outputLatencyMillis; } void HelloOboeEngine::setBufferSizeInBursts(int32_t numBursts) { std::lock_guard lock(mLock); if (!mStream) return; mLatencyCallback->setBufferTuneEnabled(numBursts == kBufferSizeAutomatic); auto result = mStream->setBufferSizeInFrames( numBursts * mStream->getFramesPerBurst()); if (result) { LOGD("Buffer size successfully changed to %d", result.value()); } else { LOGW("Buffer size could not be changed, %d", result.error()); } } void HelloOboeEngine::setAudioApi(oboe::AudioApi audioApi) { mAudioApi = audioApi; reopenStream(); } void HelloOboeEngine::setChannelCount(int channelCount) { mChannelCount = channelCount; reopenStream(); } void HelloOboeEngine::setDeviceId(int32_t deviceId) { mDeviceId = deviceId; if (reopenStream() != oboe::Result::OK) { LOGW("Open stream failed, forcing deviceId to Unspecified"); mDeviceId = oboe::Unspecified; } } bool HelloOboeEngine::isLatencyDetectionSupported() { return mIsLatencyDetectionSupported; } void HelloOboeEngine::tap(bool isDown) { mAudioSource->tap(isDown); } oboe::Result HelloOboeEngine::createPlaybackStream() { oboe::AudioStreamBuilder builder; return builder.setSharingMode(oboe::SharingMode::Exclusive) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setFormat(oboe::AudioFormat::Float) ->setDataCallback(mLatencyCallback.get()) ->setErrorCallback(mErrorCallback.get()) ->setAudioApi(mAudioApi) ->setChannelCount(mChannelCount) ->setDeviceId(mDeviceId) ->openStream(mStream); } void HelloOboeEngine::restart() { // The stream will have already been closed by the error callback. mLatencyCallback->reset(); start(); } oboe::Result HelloOboeEngine::start() { std::lock_guard lock(mLock); auto result = createPlaybackStream(); if (result == oboe::Result::OK){ mAudioSource = std::make_shared(mStream->getSampleRate(), mStream->getChannelCount()); mLatencyCallback->setSource(std::dynamic_pointer_cast(mAudioSource)); mStream->start(); mIsLatencyDetectionSupported = (mStream->getTimestamp((CLOCK_MONOTONIC)) != oboe::Result::ErrorUnimplemented); LOGD("Stream opened: AudioAPI = %d, channelCount = %d, deviceID = %d", mStream->getAudioApi(), mStream->getChannelCount(), mStream->getDeviceId()); } else { LOGE("Error creating playback stream. Error: %s", oboe::convertToText(result)); mIsLatencyDetectionSupported = false; } return result; } void HelloOboeEngine::stop() { // Stop, close and delete in case not already closed. std::lock_guard lock(mLock); if (mStream) { mStream->stop(); mStream->close(); mStream.reset(); } } oboe::Result HelloOboeEngine::reopenStream() { stop(); return start(); }