xref: /aosp_15_r20/external/oboe/include/oboe/FullDuplexStream.h (revision 05767d913155b055644481607e6fa1e35e2fe72c)
1 /*
2  * Copyright 2023 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 #ifndef OBOE_FULL_DUPLEX_STREAM_
18 #define OBOE_FULL_DUPLEX_STREAM_
19 
20 #include <cstdint>
21 #include "oboe/Definitions.h"
22 #include "oboe/AudioStream.h"
23 #include "oboe/AudioStreamCallback.h"
24 
25 namespace oboe {
26 
27 /**
28  * FullDuplexStream can be used to synchronize an input and output stream.
29  *
30  * For the builder of the output stream, call setDataCallback() with this object.
31  *
32  * When both streams are ready, onAudioReady() of the output stream will call onBothStreamsReady().
33  * Callers must override onBothStreamsReady().
34  *
35  * To ensure best results, open an output stream before the input stream.
36  * Call inputBuilder.setBufferCapacityInFrames(mOutputStream->getBufferCapacityInFrames() * 2).
37  * Also, call inputBuilder.setSampleRate(mOutputStream->getSampleRate()).
38  *
39  * Callers must call setInputStream() and setOutputStream().
40  * Call start() to start both streams and stop() to stop both streams.
41  * Caller is responsible for closing both streams.
42  *
43  * Callers should handle error callbacks with setErrorCallback() for the output stream.
44  * When an error callback occurs for the output stream, Oboe will stop and close the output stream.
45  * The caller is responsible for stopping and closing the input stream.
46  * The caller should also reopen and restart both streams when the error callback is ErrorDisconnected.
47  * See the LiveEffect sample as an example of this.
48  *
49  */
50 class FullDuplexStream : public AudioStreamDataCallback {
51 public:
FullDuplexStream()52     FullDuplexStream() {}
53     virtual ~FullDuplexStream() = default;
54 
55     /**
56      * Sets the input stream. Calling this is mandatory.
57      *
58      * @param stream the output stream
59      */
setInputStream(AudioStream * stream)60     void setInputStream(AudioStream *stream) {
61         mInputStream = stream;
62     }
63 
64     /**
65      * Gets the input stream
66      *
67      * @return the input stream
68      */
getInputStream()69     AudioStream *getInputStream() {
70         return mInputStream;
71     }
72 
73     /**
74      * Sets the output stream. Calling this is mandatory.
75      *
76      * @param stream the output stream
77      */
setOutputStream(AudioStream * stream)78     void setOutputStream(AudioStream *stream) {
79         mOutputStream = stream;
80     }
81 
82     /**
83      * Gets the output stream
84      *
85      * @return the output stream
86      */
getOutputStream()87     AudioStream *getOutputStream() {
88         return mOutputStream;
89     }
90 
91     /**
92      * Attempts to start both streams. Please call setInputStream() and setOutputStream() before
93      * calling this function.
94      *
95      * @return result of the operation
96      */
start()97     virtual Result start() {
98         mCountCallbacksToDrain = kNumCallbacksToDrain;
99         mCountInputBurstsCushion = mNumInputBurstsCushion;
100         mCountCallbacksToDiscard = kNumCallbacksToDiscard;
101 
102         // Determine maximum size that could possibly be called.
103         int32_t bufferSize = getOutputStream()->getBufferCapacityInFrames()
104                              * getOutputStream()->getChannelCount();
105         if (bufferSize > mBufferSize) {
106             mInputBuffer = std::make_unique<float[]>(bufferSize);
107             mBufferSize = bufferSize;
108         }
109 
110         oboe::Result result = getInputStream()->requestStart();
111         if (result != oboe::Result::OK) {
112             return result;
113         }
114         return getOutputStream()->requestStart();
115     }
116 
117     /**
118      * Stops both streams. Returns Result::OK if neither stream had an error during close.
119      *
120      * @return result of the operation
121      */
stop()122     virtual Result stop() {
123         Result outputResult = Result::OK;
124         Result inputResult = Result::OK;
125         if (getOutputStream()) {
126             outputResult = mOutputStream->requestStop();
127         }
128         if (getInputStream()) {
129             inputResult = mInputStream->requestStop();
130         }
131         if (outputResult != Result::OK) {
132             return outputResult;
133         } else {
134             return inputResult;
135         }
136     }
137 
138     /**
139      * Reads input from the input stream. Callers should not call this directly as this is called
140      * in onAudioReady().
141      *
142      * @param numFrames
143      * @return result of the operation
144      */
readInput(int32_t numFrames)145     virtual ResultWithValue<int32_t> readInput(int32_t numFrames) {
146         return getInputStream()->read(mInputBuffer.get(), numFrames, 0 /* timeout */);
147     }
148 
149     /**
150      * Called when data is available on both streams.
151      * Caller should override this method.
152      * numInputFrames and numOutputFrames may be zero.
153      *
154      * @param inputData buffer containing input data
155      * @param numInputFrames number of input frames
156      * @param outputData a place to put output data
157      * @param numOutputFrames number of output frames
158      * @return DataCallbackResult::Continue or DataCallbackResult::Stop
159      */
160     virtual DataCallbackResult onBothStreamsReady(
161             const void *inputData,
162             int   numInputFrames,
163             void *outputData,
164             int   numOutputFrames
165             ) = 0;
166 
167     /**
168      * Called when the output stream is ready to process audio.
169      * This in return calls onBothStreamsReady() when data is available on both streams.
170      * Callers should call this function when the output stream is ready.
171      * Callers must override onBothStreamsReady().
172      *
173      * @param audioStream pointer to the associated stream
174      * @param audioData a place to put output data
175      * @param numFrames number of frames to be processed
176      * @return DataCallbackResult::Continue or DataCallbackResult::Stop
177      *
178      */
onAudioReady(AudioStream *,void * audioData,int numFrames)179     DataCallbackResult onAudioReady(
180             AudioStream * /*audioStream*/,
181             void *audioData,
182             int numFrames) {
183         DataCallbackResult callbackResult = DataCallbackResult::Continue;
184         int32_t actualFramesRead = 0;
185 
186         // Silence the output.
187         int32_t numBytes = numFrames * getOutputStream()->getBytesPerFrame();
188         memset(audioData, 0 /* value */, numBytes);
189 
190         if (mCountCallbacksToDrain > 0) {
191             // Drain the input.
192             int32_t totalFramesRead = 0;
193             do {
194                 ResultWithValue<int32_t> result = readInput(numFrames);
195                 if (!result) {
196                     // Ignore errors because input stream may not be started yet.
197                     break;
198                 }
199                 actualFramesRead = result.value();
200                 totalFramesRead += actualFramesRead;
201             } while (actualFramesRead > 0);
202             // Only counts if we actually got some data.
203             if (totalFramesRead > 0) {
204                 mCountCallbacksToDrain--;
205             }
206 
207         } else if (mCountInputBurstsCushion > 0) {
208             // Let the input fill up a bit so we are not so close to the write pointer.
209             mCountInputBurstsCushion--;
210 
211         } else if (mCountCallbacksToDiscard > 0) {
212             mCountCallbacksToDiscard--;
213             // Ignore. Allow the input to reach to equilibrium with the output.
214             ResultWithValue<int32_t> resultAvailable = getInputStream()->getAvailableFrames();
215             if (!resultAvailable) {
216                 callbackResult = DataCallbackResult::Stop;
217             } else {
218                 int32_t framesAvailable = resultAvailable.value();
219                 if (framesAvailable >= mMinimumFramesBeforeRead) {
220                     ResultWithValue<int32_t> resultRead = readInput(numFrames);
221                     if (!resultRead) {
222                         callbackResult = DataCallbackResult::Stop;
223                     }
224                 }
225             }
226         } else {
227             int32_t framesRead = 0;
228             ResultWithValue<int32_t> resultAvailable = getInputStream()->getAvailableFrames();
229             if (!resultAvailable) {
230                 callbackResult = DataCallbackResult::Stop;
231             } else {
232                 int32_t framesAvailable = resultAvailable.value();
233                 if (framesAvailable >= mMinimumFramesBeforeRead) {
234                     // Read data into input buffer.
235                     ResultWithValue<int32_t> resultRead = readInput(numFrames);
236                     if (!resultRead) {
237                         callbackResult = DataCallbackResult::Stop;
238                     } else {
239                         framesRead = resultRead.value();
240                     }
241                 }
242             }
243 
244             if (callbackResult == DataCallbackResult::Continue) {
245                 callbackResult = onBothStreamsReady(mInputBuffer.get(), framesRead,
246                                                     audioData, numFrames);
247             }
248         }
249 
250         if (callbackResult == DataCallbackResult::Stop) {
251             getInputStream()->requestStop();
252         }
253 
254         return callbackResult;
255     }
256 
257     /**
258      *
259      * This is a cushion between the DSP and the application processor cursors to prevent collisions.
260      * Typically 0 for latency measurements or 1 for glitch tests.
261      *
262      * @param numBursts number of bursts to leave in the input buffer as a cushion
263      */
setNumInputBurstsCushion(int32_t numBursts)264     void setNumInputBurstsCushion(int32_t numBursts) {
265         mNumInputBurstsCushion = numBursts;
266     }
267 
268     /**
269      * Get the number of bursts left in the input buffer as a cushion.
270      *
271      * @return number of bursts in the input buffer as a cushion
272      */
getNumInputBurstsCushion()273     int32_t getNumInputBurstsCushion() const {
274         return mNumInputBurstsCushion;
275     }
276 
277     /**
278      * Minimum number of frames in the input stream buffer before calling readInput().
279      *
280      * @param numFrames number of bursts in the input buffer as a cushion
281      */
setMinimumFramesBeforeRead(int32_t numFrames)282     void setMinimumFramesBeforeRead(int32_t numFrames) {
283         mMinimumFramesBeforeRead = numFrames;
284     }
285 
286     /**
287      * Gets the minimum number of frames in the input stream buffer before calling readInput().
288      *
289      * @return minimum number of frames before reading
290      */
getMinimumFramesBeforeRead()291     int32_t getMinimumFramesBeforeRead() const {
292         return mMinimumFramesBeforeRead;
293     }
294 
295 private:
296 
297     // TODO add getters and setters
298     static constexpr int32_t kNumCallbacksToDrain   = 20;
299     static constexpr int32_t kNumCallbacksToDiscard = 30;
300 
301     // let input fill back up, usually 0 or 1
302     int32_t mNumInputBurstsCushion =  0;
303     int32_t mMinimumFramesBeforeRead = 0;
304 
305     // We want to reach a state where the input buffer is empty and
306     // the output buffer is full.
307     // These are used in order.
308     // Drain several callback so that input is empty.
309     int32_t              mCountCallbacksToDrain = kNumCallbacksToDrain;
310     // Let the input fill back up slightly so we don't run dry.
311     int32_t              mCountInputBurstsCushion = mNumInputBurstsCushion;
312     // Discard some callbacks so the input and output reach equilibrium.
313     int32_t              mCountCallbacksToDiscard = kNumCallbacksToDiscard;
314 
315     AudioStream   *mInputStream = nullptr;
316     AudioStream   *mOutputStream = nullptr;
317 
318     int32_t              mBufferSize = 0;
319     std::unique_ptr<float[]> mInputBuffer;
320 };
321 
322 } // namespace oboe
323 
324 #endif //OBOE_FULL_DUPLEX_STREAM_
325