1 /**
2 * Copyright 2018 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 #include <cassert>
18 #include <logging_macros.h>
19
20 #include "LiveEffectEngine.h"
21
LiveEffectEngine()22 LiveEffectEngine::LiveEffectEngine() {
23 assert(mOutputChannelCount == mInputChannelCount);
24 }
25
setRecordingDeviceId(int32_t deviceId)26 void LiveEffectEngine::setRecordingDeviceId(int32_t deviceId) {
27 mRecordingDeviceId = deviceId;
28 }
29
setPlaybackDeviceId(int32_t deviceId)30 void LiveEffectEngine::setPlaybackDeviceId(int32_t deviceId) {
31 mPlaybackDeviceId = deviceId;
32 }
33
isAAudioRecommended()34 bool LiveEffectEngine::isAAudioRecommended() {
35 return oboe::AudioStreamBuilder::isAAudioRecommended();
36 }
37
setAudioApi(oboe::AudioApi api)38 bool LiveEffectEngine::setAudioApi(oboe::AudioApi api) {
39 if (mIsEffectOn) return false;
40 mAudioApi = api;
41 return true;
42 }
43
setEffectOn(bool isOn)44 bool LiveEffectEngine::setEffectOn(bool isOn) {
45 bool success = true;
46 if (isOn != mIsEffectOn) {
47 if (isOn) {
48 success = openStreams() == oboe::Result::OK;
49 if (success) {
50 mFullDuplexPass.start();
51 mIsEffectOn = isOn;
52 }
53 } else {
54 mFullDuplexPass.stop();
55 closeStreams();
56 mIsEffectOn = isOn;
57 }
58 }
59 return success;
60 }
61
closeStreams()62 void LiveEffectEngine::closeStreams() {
63 /*
64 * Note: The order of events is important here.
65 * The playback stream must be closed before the recording stream. If the
66 * recording stream were to be closed first the playback stream's
67 * callback may attempt to read from the recording stream
68 * which would cause the app to crash since the recording stream would be
69 * null.
70 */
71 closeStream(mPlayStream);
72 mFullDuplexPass.setOutputStream(nullptr);
73
74 closeStream(mRecordingStream);
75 mFullDuplexPass.setInputStream(nullptr);
76 }
77
openStreams()78 oboe::Result LiveEffectEngine::openStreams() {
79 // Note: The order of stream creation is important. We create the playback
80 // stream first, then use properties from the playback stream
81 // (e.g. sample rate) to create the recording stream. By matching the
82 // properties we should get the lowest latency path
83 oboe::AudioStreamBuilder inBuilder, outBuilder;
84 setupPlaybackStreamParameters(&outBuilder);
85 oboe::Result result = outBuilder.openStream(mPlayStream);
86 if (result != oboe::Result::OK) {
87 LOGE("Failed to open output stream. Error %s", oboe::convertToText(result));
88 mSampleRate = oboe::kUnspecified;
89 return result;
90 } else {
91 // The input stream needs to run at the same sample rate as the output.
92 mSampleRate = mPlayStream->getSampleRate();
93 }
94 warnIfNotLowLatency(mPlayStream);
95
96 setupRecordingStreamParameters(&inBuilder, mSampleRate);
97 result = inBuilder.openStream(mRecordingStream);
98 if (result != oboe::Result::OK) {
99 LOGE("Failed to open input stream. Error %s", oboe::convertToText(result));
100 closeStream(mPlayStream);
101 return result;
102 }
103 warnIfNotLowLatency(mRecordingStream);
104
105 mFullDuplexPass.setInputStream(mRecordingStream.get());
106 mFullDuplexPass.setOutputStream(mPlayStream.get());
107 return result;
108 }
109
110 /**
111 * Sets the stream parameters which are specific to recording,
112 * including the sample rate which is determined from the
113 * playback stream.
114 *
115 * @param builder The recording stream builder
116 * @param sampleRate The desired sample rate of the recording stream
117 */
setupRecordingStreamParameters(oboe::AudioStreamBuilder * builder,int32_t sampleRate)118 oboe::AudioStreamBuilder *LiveEffectEngine::setupRecordingStreamParameters(
119 oboe::AudioStreamBuilder *builder, int32_t sampleRate) {
120 // This sample uses blocking read() because we don't specify a callback
121 builder->setDeviceId(mRecordingDeviceId)
122 ->setDirection(oboe::Direction::Input)
123 ->setSampleRate(sampleRate)
124 ->setChannelCount(mInputChannelCount);
125 return setupCommonStreamParameters(builder);
126 }
127
128 /**
129 * Sets the stream parameters which are specific to playback, including device
130 * id and the dataCallback function, which must be set for low latency
131 * playback.
132 * @param builder The playback stream builder
133 */
setupPlaybackStreamParameters(oboe::AudioStreamBuilder * builder)134 oboe::AudioStreamBuilder *LiveEffectEngine::setupPlaybackStreamParameters(
135 oboe::AudioStreamBuilder *builder) {
136 builder->setDataCallback(this)
137 ->setErrorCallback(this)
138 ->setDeviceId(mPlaybackDeviceId)
139 ->setDirection(oboe::Direction::Output)
140 ->setChannelCount(mOutputChannelCount);
141
142 return setupCommonStreamParameters(builder);
143 }
144
145 /**
146 * Set the stream parameters which are common to both recording and playback
147 * streams.
148 * @param builder The playback or recording stream builder
149 */
setupCommonStreamParameters(oboe::AudioStreamBuilder * builder)150 oboe::AudioStreamBuilder *LiveEffectEngine::setupCommonStreamParameters(
151 oboe::AudioStreamBuilder *builder) {
152 // We request EXCLUSIVE mode since this will give us the lowest possible
153 // latency.
154 // If EXCLUSIVE mode isn't available the builder will fall back to SHARED
155 // mode.
156 builder->setAudioApi(mAudioApi)
157 ->setFormat(mFormat)
158 ->setFormatConversionAllowed(true)
159 ->setSharingMode(oboe::SharingMode::Exclusive)
160 ->setPerformanceMode(oboe::PerformanceMode::LowLatency);
161 return builder;
162 }
163
164 /**
165 * Close the stream. AudioStream::close() is a blocking call so
166 * the application does not need to add synchronization between
167 * onAudioReady() function and the thread calling close().
168 * [the closing thread is the UI thread in this sample].
169 * @param stream the stream to close
170 */
closeStream(std::shared_ptr<oboe::AudioStream> & stream)171 void LiveEffectEngine::closeStream(std::shared_ptr<oboe::AudioStream> &stream) {
172 if (stream) {
173 oboe::Result result = stream->stop();
174 if (result != oboe::Result::OK) {
175 LOGW("Error stopping stream: %s", oboe::convertToText(result));
176 }
177 result = stream->close();
178 if (result != oboe::Result::OK) {
179 LOGE("Error closing stream: %s", oboe::convertToText(result));
180 } else {
181 LOGW("Successfully closed streams");
182 }
183 stream.reset();
184 }
185 }
186
187 /**
188 * Warn in logcat if non-low latency stream is created
189 * @param stream: newly created stream
190 *
191 */
warnIfNotLowLatency(std::shared_ptr<oboe::AudioStream> & stream)192 void LiveEffectEngine::warnIfNotLowLatency(std::shared_ptr<oboe::AudioStream> &stream) {
193 if (stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency) {
194 LOGW(
195 "Stream is NOT low latency."
196 "Check your requested format, sample rate and channel count");
197 }
198 }
199
200 /**
201 * Handles playback stream's audio request. In this sample, we simply block-read
202 * from the record stream for the required samples.
203 *
204 * @param oboeStream: the playback stream that requesting additional samples
205 * @param audioData: the buffer to load audio samples for playback stream
206 * @param numFrames: number of frames to load to audioData buffer
207 * @return: DataCallbackResult::Continue.
208 */
onAudioReady(oboe::AudioStream * oboeStream,void * audioData,int32_t numFrames)209 oboe::DataCallbackResult LiveEffectEngine::onAudioReady(
210 oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) {
211 return mFullDuplexPass.onAudioReady(oboeStream, audioData, numFrames);
212 }
213
214 /**
215 * Oboe notifies the application for "about to close the stream".
216 *
217 * @param oboeStream: the stream to close
218 * @param error: oboe's reason for closing the stream
219 */
onErrorBeforeClose(oboe::AudioStream * oboeStream,oboe::Result error)220 void LiveEffectEngine::onErrorBeforeClose(oboe::AudioStream *oboeStream,
221 oboe::Result error) {
222 LOGE("%s stream Error before close: %s",
223 oboe::convertToText(oboeStream->getDirection()),
224 oboe::convertToText(error));
225 }
226
227 /**
228 * Oboe notifies application that "the stream is closed"
229 *
230 * @param oboeStream
231 * @param error
232 */
onErrorAfterClose(oboe::AudioStream * oboeStream,oboe::Result error)233 void LiveEffectEngine::onErrorAfterClose(oboe::AudioStream *oboeStream,
234 oboe::Result error) {
235 LOGE("%s stream Error after close: %s",
236 oboe::convertToText(oboeStream->getDirection()),
237 oboe::convertToText(error));
238
239 // Stop the Full Duplex stream.
240 // Since the error callback occurs only for the output stream, close the input stream.
241 mFullDuplexPass.stop();
242 mFullDuplexPass.setOutputStream(nullptr);
243 closeStream(mRecordingStream);
244 mFullDuplexPass.setInputStream(nullptr);
245
246 // Restart the stream if the error is a disconnect.
247 if (error == oboe::Result::ErrorDisconnected) {
248 LOGI("Restarting AudioStream");
249 oboe::Result result = openStreams();
250 if (result == oboe::Result::OK) {
251 mFullDuplexPass.start();
252 }
253 }
254 }
255