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
~LiveEffectEngine()26 LiveEffectEngine::~LiveEffectEngine() {
27
28 mFullDuplexPass.stop();
29 closeStream(mPlayStream);
30 closeStream(mRecordingStream);
31 }
32
setRecordingDeviceId(int32_t deviceId)33 void LiveEffectEngine::setRecordingDeviceId(int32_t deviceId) {
34 mRecordingDeviceId = deviceId;
35 }
36
setPlaybackDeviceId(int32_t deviceId)37 void LiveEffectEngine::setPlaybackDeviceId(int32_t deviceId) {
38 mPlaybackDeviceId = deviceId;
39 }
40
isAAudioSupported()41 bool LiveEffectEngine::isAAudioSupported() {
42 oboe::AudioStreamBuilder builder;
43 return builder.isAAudioSupported();
44 }
45
setAudioApi(oboe::AudioApi api)46 bool LiveEffectEngine::setAudioApi(oboe::AudioApi api) {
47 if (mIsEffectOn) return false;
48
49 mAudioApi = api;
50 return true;
51 }
52
setEffectOn(bool isOn)53 bool LiveEffectEngine::setEffectOn(bool isOn) {
54 bool success = true;
55 if (isOn != mIsEffectOn) {
56 if (isOn) {
57 success = openStreams() == oboe::Result::OK;
58 if (success) {
59 mFullDuplexPass.start();
60 mIsEffectOn = isOn;
61 }
62 } else {
63 mFullDuplexPass.stop();
64 /*
65 * Note: The order of events is important here.
66 * The playback stream must be closed before the recording stream. If the
67 * recording stream were to be closed first the playback stream's
68 * callback may attempt to read from the recording stream
69 * which would cause the app to crash since the recording stream would be
70 * null.
71 */
72 closeStream(mPlayStream);
73 closeStream(mRecordingStream);
74 mIsEffectOn = isOn;
75 }
76 }
77 return success;
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 mSampleRate = mPlayStream->getSampleRate();
93 }
94 warnIfNotLowLatency(mPlayStream);
95
96 setupRecordingStreamParameters(&inBuilder);
97 result = inBuilder.openStream(mRecordingStream);
98 if (result != oboe::Result::OK) {
99 closeStream(mPlayStream);
100 return result;
101 }
102 warnIfNotLowLatency(mRecordingStream);
103
104 mFullDuplexPass.setInputStream(mRecordingStream.get());
105 mFullDuplexPass.setOutputStream(mPlayStream.get());
106 return result;
107 }
108
109 /**
110 * Sets the stream parameters which are specific to recording,
111 * including the sample rate which is determined from the
112 * playback stream.
113 *
114 * @param builder The recording stream builder
115 */
setupRecordingStreamParameters(oboe::AudioStreamBuilder * builder)116 oboe::AudioStreamBuilder *LiveEffectEngine::setupRecordingStreamParameters(
117 oboe::AudioStreamBuilder *builder) {
118 // This sample uses blocking read() by setting callback to null
119 builder->setCallback(nullptr)
120 ->setDeviceId(mRecordingDeviceId)
121 ->setDirection(oboe::Direction::Input)
122 ->setSampleRate(mSampleRate)
123 ->setChannelCount(mInputChannelCount);
124 return setupCommonStreamParameters(builder);
125 }
126
127 /**
128 * Sets the stream parameters which are specific to playback, including device
129 * id and the dataCallback function, which must be set for low latency
130 * playback.
131 * @param builder The playback stream builder
132 */
setupPlaybackStreamParameters(oboe::AudioStreamBuilder * builder)133 oboe::AudioStreamBuilder *LiveEffectEngine::setupPlaybackStreamParameters(
134 oboe::AudioStreamBuilder *builder) {
135 builder->setCallback(this)
136 ->setDeviceId(mPlaybackDeviceId)
137 ->setDirection(oboe::Direction::Output)
138 ->setChannelCount(mOutputChannelCount);
139
140 return setupCommonStreamParameters(builder);
141 }
142
143 /**
144 * Set the stream parameters which are common to both recording and playback
145 * streams.
146 * @param builder The playback or recording stream builder
147 */
setupCommonStreamParameters(oboe::AudioStreamBuilder * builder)148 oboe::AudioStreamBuilder *LiveEffectEngine::setupCommonStreamParameters(
149 oboe::AudioStreamBuilder *builder) {
150 // We request EXCLUSIVE mode since this will give us the lowest possible
151 // latency.
152 // If EXCLUSIVE mode isn't available the builder will fall back to SHARED
153 // mode.
154 builder->setAudioApi(mAudioApi)
155 ->setFormat(mFormat)
156 ->setSharingMode(oboe::SharingMode::Exclusive)
157 ->setPerformanceMode(oboe::PerformanceMode::LowLatency);
158 return builder;
159 }
160
161
162 /**
163 * Close the stream. AudioStream::close() is a blocking call so
164 * the application does not need to add synchronization between
165 * onAudioReady() function and the thread calling close().
166 * [the closing thread is the UI thread in this sample].
167 * @param stream the stream to close
168 */
closeStream(std::shared_ptr<oboe::AudioStream> & stream)169 void LiveEffectEngine::closeStream(std::shared_ptr<oboe::AudioStream> &stream) {
170 if (stream) {
171 oboe::Result result = stream->stop();
172 if (result != oboe::Result::OK) {
173 LOGW("Error stopping stream: %s", oboe::convertToText(result));
174 }
175 result = stream->close();
176 if (result != oboe::Result::OK) {
177 LOGE("Error closing stream: %s", oboe::convertToText(result));
178 } else {
179 LOGW("Successfully closed streams");
180 }
181 stream.reset();
182 }
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