xref: /aosp_15_r20/external/oboe/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp (revision 05767d913155b055644481607e6fa1e35e2fe72c)
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