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
isAAudioSupported()34 bool LiveEffectEngine::isAAudioSupported() {
35 oboe::AudioStreamBuilder builder;
36 return builder.isAAudioSupported();
37 }
38
setAudioApi(oboe::AudioApi api)39 bool LiveEffectEngine::setAudioApi(oboe::AudioApi api) {
40 if (mIsEffectOn) return false;
41
42 mAudioApi = api;
43 return true;
44 }
45
setEffectOn(bool isOn)46 bool LiveEffectEngine::setEffectOn(bool isOn) {
47 bool success = true;
48 if (isOn != mIsEffectOn) {
49 if (isOn) {
50 success = openStreams() == oboe::Result::OK;
51 if (success) {
52 mFullDuplexPass.start();
53 mIsEffectOn = isOn;
54 }
55 } else {
56 mFullDuplexPass.stop();
57 closeStreams();
58 mIsEffectOn = isOn;
59 }
60 }
61 return success;
62 }
63
closeStreams()64 void LiveEffectEngine::closeStreams() {
65 /*
66 * Note: The order of events is important here.
67 * The playback stream must be closed before the recording stream. If the
68 * recording stream were to be closed first the playback stream's
69 * callback may attempt to read from the recording stream
70 * which would cause the app to crash since the recording stream would be
71 * null.
72 */
73 closeStream(mPlayStream);
74 mFullDuplexPass.setOutputStream(nullptr);
75
76 closeStream(mRecordingStream);
77 mFullDuplexPass.setInputStream(nullptr);
78 }
79
openStreams()80 oboe::Result LiveEffectEngine::openStreams() {
81 // Note: The order of stream creation is important. We create the playback
82 // stream first, then use properties from the playback stream
83 // (e.g. sample rate) to create the recording stream. By matching the
84 // properties we should get the lowest latency path
85 oboe::AudioStreamBuilder inBuilder, outBuilder;
86 setupPlaybackStreamParameters(&outBuilder);
87 oboe::Result result = outBuilder.openStream(mPlayStream);
88 if (result != oboe::Result::OK) {
89 mSampleRate = oboe::kUnspecified;
90 return result;
91 } else {
92 // The input stream needs to run at the same sample rate as the output.
93 mSampleRate = mPlayStream->getSampleRate();
94 }
95 warnIfNotLowLatency(mPlayStream);
96
97 setupRecordingStreamParameters(&inBuilder, mSampleRate);
98 result = inBuilder.openStream(mRecordingStream);
99 if (result != oboe::Result::OK) {
100 closeStream(mPlayStream);
101 return result;
102 }
103 warnIfNotLowLatency(mRecordingStream);
104
105 mFullDuplexPass.setInputStream(mRecordingStream);
106 mFullDuplexPass.setOutputStream(mPlayStream);
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 ->setSharingMode(oboe::SharingMode::Exclusive)
159 ->setPerformanceMode(oboe::PerformanceMode::LowLatency);
160 return builder;
161 }
162
163 /**
164 * Close the stream. AudioStream::close() is a blocking call so
165 * the application does not need to add synchronization between
166 * onAudioReady() function and the thread calling close().
167 * [the closing thread is the UI thread in this sample].
168 * @param stream the stream to close
169 */
closeStream(std::shared_ptr<oboe::AudioStream> & stream)170 void LiveEffectEngine::closeStream(std::shared_ptr<oboe::AudioStream> &stream) {
171 if (stream) {
172 oboe::Result result = stream->stop();
173 if (result != oboe::Result::OK) {
174 LOGW("Error stopping stream: %s", oboe::convertToText(result));
175 }
176 result = stream->close();
177 if (result != oboe::Result::OK) {
178 LOGE("Error closing stream: %s", oboe::convertToText(result));
179 } else {
180 LOGW("Successfully closed streams");
181 }
182 stream.reset();
183 }
184 }
185
186 /**
187 * Warn in logcat if non-low latency stream is created
188 * @param stream: newly created stream
189 *
190 */
warnIfNotLowLatency(std::shared_ptr<oboe::AudioStream> & stream)191 void LiveEffectEngine::warnIfNotLowLatency(std::shared_ptr<oboe::AudioStream> &stream) {
192 if (stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency) {
193 LOGW(
194 "Stream is NOT low latency."
195 "Check your requested format, sample rate and channel count");
196 }
197 }
198
199 /**
200 * Handles playback stream's audio request. In this sample, we simply block-read
201 * from the record stream for the required samples.
202 *
203 * @param oboeStream: the playback stream that requesting additional samples
204 * @param audioData: the buffer to load audio samples for playback stream
205 * @param numFrames: number of frames to load to audioData buffer
206 * @return: DataCallbackResult::Continue.
207 */
onAudioReady(oboe::AudioStream * oboeStream,void * audioData,int32_t numFrames)208 oboe::DataCallbackResult LiveEffectEngine::onAudioReady(
209 oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) {
210 return mFullDuplexPass.onAudioReady(oboeStream, audioData, numFrames);
211 }
212
213 /**
214 * Oboe notifies the application for "about to close the stream".
215 *
216 * @param oboeStream: the stream to close
217 * @param error: oboe's reason for closing the stream
218 */
onErrorBeforeClose(oboe::AudioStream * oboeStream,oboe::Result error)219 void LiveEffectEngine::onErrorBeforeClose(oboe::AudioStream *oboeStream,
220 oboe::Result error) {
221 LOGE("%s stream Error before close: %s",
222 oboe::convertToText(oboeStream->getDirection()),
223 oboe::convertToText(error));
224 }
225
226 /**
227 * Oboe notifies application that "the stream is closed"
228 *
229 * @param oboeStream
230 * @param error
231 */
onErrorAfterClose(oboe::AudioStream * oboeStream,oboe::Result error)232 void LiveEffectEngine::onErrorAfterClose(oboe::AudioStream *oboeStream,
233 oboe::Result error) {
234 LOGE("%s stream Error after close: %s",
235 oboe::convertToText(oboeStream->getDirection()),
236 oboe::convertToText(error));
237 }
238