1 /**
2 * Copyright 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18 #include <inttypes.h>
19 #include <memory>
20
21 #include <Oscillator.h>
22
23 #include "HelloOboeEngine.h"
24 #include "SoundGenerator.h"
25
26
27 /**
28 * Main audio engine for the HelloOboe sample. It is responsible for:
29 *
30 * - Creating a callback object which is supplied when constructing the audio stream, and will be
31 * called when the stream starts
32 * - Restarting the stream when user-controllable properties (Audio API, channel count etc) are
33 * changed, and when the stream is disconnected (e.g. when headphones are attached)
34 * - Calculating the audio latency of the stream
35 *
36 */
HelloOboeEngine()37 HelloOboeEngine::HelloOboeEngine()
38 : mLatencyCallback(std::make_unique<LatencyTuningCallback>()),
39 mErrorCallback(std::make_unique<DefaultErrorCallback>(*this)) {
40 }
41
getCurrentOutputLatencyMillis()42 double HelloOboeEngine::getCurrentOutputLatencyMillis() {
43 if (!mIsLatencyDetectionSupported) return -1.0;
44
45 std::lock_guard<std::mutex> lock(mLock);
46 if (!mStream) return -1.0;
47
48 // Get the time that a known audio frame was presented for playing
49 auto result = mStream->getTimestamp(CLOCK_MONOTONIC);
50 double outputLatencyMillis = -1;
51 const int64_t kNanosPerMillisecond = 1000000;
52 if (result == oboe::Result::OK) {
53 oboe::FrameTimestamp playedFrame = result.value();
54 // Get the write index for the next audio frame
55 int64_t writeIndex = mStream->getFramesWritten();
56 // Calculate the number of frames between our known frame and the write index
57 int64_t frameIndexDelta = writeIndex - playedFrame.position;
58 // Calculate the time which the next frame will be presented
59 int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / (mStream->getSampleRate());
60 int64_t nextFramePresentationTime = playedFrame.timestamp + frameTimeDelta;
61 // Assume that the next frame will be written at the current time
62 using namespace std::chrono;
63 int64_t nextFrameWriteTime =
64 duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count();
65 // Calculate the latency
66 outputLatencyMillis = static_cast<double>(nextFramePresentationTime - nextFrameWriteTime)
67 / kNanosPerMillisecond;
68 } else {
69 LOGE("Error calculating latency: %s", oboe::convertToText(result.error()));
70 }
71 return outputLatencyMillis;
72 }
73
setBufferSizeInBursts(int32_t numBursts)74 void HelloOboeEngine::setBufferSizeInBursts(int32_t numBursts) {
75 std::lock_guard<std::mutex> lock(mLock);
76 if (!mStream) return;
77
78 mLatencyCallback->setBufferTuneEnabled(numBursts == kBufferSizeAutomatic);
79 auto result = mStream->setBufferSizeInFrames(
80 numBursts * mStream->getFramesPerBurst());
81 if (result) {
82 LOGD("Buffer size successfully changed to %d", result.value());
83 } else {
84 LOGW("Buffer size could not be changed, %d", result.error());
85 }
86 }
87
setAudioApi(oboe::AudioApi audioApi)88 void HelloOboeEngine::setAudioApi(oboe::AudioApi audioApi) {
89 mAudioApi = audioApi;
90 reopenStream();
91 }
92
setChannelCount(int channelCount)93 void HelloOboeEngine::setChannelCount(int channelCount) {
94 mChannelCount = channelCount;
95 reopenStream();
96 }
97
setDeviceId(int32_t deviceId)98 void HelloOboeEngine::setDeviceId(int32_t deviceId) {
99 mDeviceId = deviceId;
100 if (reopenStream() != oboe::Result::OK) {
101 LOGW("Open stream failed, forcing deviceId to Unspecified");
102 mDeviceId = oboe::Unspecified;
103 }
104 }
105
isLatencyDetectionSupported()106 bool HelloOboeEngine::isLatencyDetectionSupported() {
107 return mIsLatencyDetectionSupported;
108 }
109
tap(bool isDown)110 void HelloOboeEngine::tap(bool isDown) {
111 mAudioSource->tap(isDown);
112 }
113
createPlaybackStream()114 oboe::Result HelloOboeEngine::createPlaybackStream() {
115 oboe::AudioStreamBuilder builder;
116 return builder.setSharingMode(oboe::SharingMode::Exclusive)
117 ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
118 ->setFormat(oboe::AudioFormat::Float)
119 ->setDataCallback(mLatencyCallback.get())
120 ->setErrorCallback(mErrorCallback.get())
121 ->setAudioApi(mAudioApi)
122 ->setChannelCount(mChannelCount)
123 ->setDeviceId(mDeviceId)
124 ->openStream(mStream);
125 }
126
restart()127 void HelloOboeEngine::restart() {
128 // The stream will have already been closed by the error callback.
129 mLatencyCallback->reset();
130 start();
131 }
132
start()133 oboe::Result HelloOboeEngine::start() {
134 std::lock_guard<std::mutex> lock(mLock);
135
136 auto result = createPlaybackStream();
137 if (result == oboe::Result::OK){
138 mAudioSource = std::make_shared<SoundGenerator>(mStream->getSampleRate(),
139 mStream->getChannelCount());
140 mLatencyCallback->setSource(std::dynamic_pointer_cast<IRenderableAudio>(mAudioSource));
141 mStream->start();
142 mIsLatencyDetectionSupported = (mStream->getTimestamp((CLOCK_MONOTONIC)) !=
143 oboe::Result::ErrorUnimplemented);
144
145 LOGD("Stream opened: AudioAPI = %d, channelCount = %d, deviceID = %d",
146 mStream->getAudioApi(),
147 mStream->getChannelCount(),
148 mStream->getDeviceId());
149 } else {
150 LOGE("Error creating playback stream. Error: %s", oboe::convertToText(result));
151 mIsLatencyDetectionSupported = false;
152 }
153 return result;
154 }
155
stop()156 void HelloOboeEngine::stop() {
157 // Stop, close and delete in case not already closed.
158 std::lock_guard<std::mutex> lock(mLock);
159 if (mStream) {
160 mStream->stop();
161 mStream->close();
162 mStream.reset();
163 }
164 }
165
reopenStream()166 oboe::Result HelloOboeEngine::reopenStream() {
167 stop();
168 return start();
169 }
170