• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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 <fstream>
18 #include <iostream>
19 #include <vector>
20 #include <common/AudioClock.h>
21 
22 #include "util/WaveFileWriter.h"
23 
24 #include "NativeAudioContext.h"
25 
26 using namespace oboe;
27 
convertNativeApiToAudioApi(int nativeApi)28 static oboe::AudioApi convertNativeApiToAudioApi(int nativeApi) {
29     switch (nativeApi) {
30         default:
31         case NATIVE_MODE_UNSPECIFIED:
32             return oboe::AudioApi::Unspecified;
33         case NATIVE_MODE_AAUDIO:
34             return oboe::AudioApi::AAudio;
35         case NATIVE_MODE_OPENSLES:
36             return oboe::AudioApi::OpenSLES;
37     }
38 }
39 
40 class MyOboeOutputStream : public WaveFileOutputStream {
41 public:
write(uint8_t b)42     void write(uint8_t b) override {
43         mData.push_back(b);
44     }
45 
length()46     int32_t length() {
47         return (int32_t) mData.size();
48     }
49 
getData()50     uint8_t *getData() {
51         return mData.data();
52     }
53 
54 private:
55     std::vector<uint8_t> mData;
56 };
57 
58 bool ActivityContext::mUseCallback = true;
59 int  ActivityContext::callbackSize = 0;
60 
getOutputStream()61 std::shared_ptr<oboe::AudioStream> ActivityContext::getOutputStream() {
62     for (auto entry : mOboeStreams) {
63         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
64         if (oboeStream->getDirection() == oboe::Direction::Output) {
65             return oboeStream;
66         }
67     }
68     return nullptr;
69 }
70 
getInputStream()71 std::shared_ptr<oboe::AudioStream> ActivityContext::getInputStream() {
72     for (auto entry : mOboeStreams) {
73         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
74         if (oboeStream != nullptr) {
75             if (oboeStream->getDirection() == oboe::Direction::Input) {
76                 return oboeStream;
77             }
78         }
79     }
80     return nullptr;
81 }
82 
freeStreamIndex(int32_t streamIndex)83 void ActivityContext::freeStreamIndex(int32_t streamIndex) {
84     mOboeStreams[streamIndex].reset();
85     mOboeStreams.erase(streamIndex);
86 }
87 
allocateStreamIndex()88 int32_t ActivityContext::allocateStreamIndex() {
89     return mNextStreamHandle++;
90 }
91 
close(int32_t streamIndex)92 void ActivityContext::close(int32_t streamIndex) {
93     stopBlockingIOThread();
94     std::shared_ptr<oboe::AudioStream> oboeStream = getStream(streamIndex);
95     if (oboeStream != nullptr) {
96         oboeStream->close();
97         LOGD("ActivityContext::%s() delete stream %d ", __func__, streamIndex);
98         freeStreamIndex(streamIndex);
99     }
100 }
101 
isMMapUsed(int32_t streamIndex)102 bool ActivityContext::isMMapUsed(int32_t streamIndex) {
103     std::shared_ptr<oboe::AudioStream> oboeStream = getStream(streamIndex);
104     if (oboeStream == nullptr) return false;
105     if (oboeStream->getAudioApi() != AudioApi::AAudio) return false;
106     return AAudioExtensions::getInstance().isMMapUsed(oboeStream.get());
107 }
108 
pause()109 oboe::Result ActivityContext::pause() {
110     oboe::Result result = oboe::Result::OK;
111     stopBlockingIOThread();
112     for (auto entry : mOboeStreams) {
113         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
114         result = oboeStream->requestPause();
115     }
116     return result;
117 }
118 
stopAllStreams()119 oboe::Result ActivityContext::stopAllStreams() {
120     oboe::Result result = oboe::Result::OK;
121     stopBlockingIOThread();
122     for (auto entry : mOboeStreams) {
123         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
124         result = oboeStream->requestStop();
125     }
126     return result;
127 }
128 
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)129 void ActivityContext::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
130     // We needed the proxy because we did not know the channelCount when we setup the Builder.
131     if (mUseCallback) {
132         LOGD("ActivityContext::open() set callback to use oboeCallbackProxy, callback size = %d",
133              callbackSize);
134         builder.setDataCallback(&oboeCallbackProxy);
135         builder.setFramesPerCallback(callbackSize);
136     }
137 }
138 
open(jint nativeApi,jint sampleRate,jint channelCount,jint format,jint sharingMode,jint performanceMode,jint inputPreset,jint deviceId,jint sessionId,jint framesPerBurst,jboolean channelConversionAllowed,jboolean formatConversionAllowed,jint rateConversionQuality,jboolean isMMap,jboolean isInput)139 int ActivityContext::open(jint nativeApi,
140                           jint sampleRate,
141                           jint channelCount,
142                           jint format,
143                           jint sharingMode,
144                           jint performanceMode,
145                           jint inputPreset,
146                           jint deviceId,
147                           jint sessionId,
148                           jint framesPerBurst,
149                           jboolean channelConversionAllowed,
150                           jboolean formatConversionAllowed,
151                           jint rateConversionQuality,
152                           jboolean isMMap,
153                           jboolean isInput) {
154 
155     oboe::AudioApi audioApi = oboe::AudioApi::Unspecified;
156     switch (nativeApi) {
157         case NATIVE_MODE_UNSPECIFIED:
158         case NATIVE_MODE_AAUDIO:
159         case NATIVE_MODE_OPENSLES:
160             audioApi = convertNativeApiToAudioApi(nativeApi);
161             break;
162         default:
163             return (jint) oboe::Result::ErrorOutOfRange;
164     }
165 
166     int32_t streamIndex = allocateStreamIndex();
167     if (streamIndex < 0) {
168         LOGE("ActivityContext::open() stream array full");
169         return (jint) oboe::Result::ErrorNoFreeHandles;
170     }
171 
172     if (channelCount < 0 || channelCount > 256) {
173         LOGE("ActivityContext::open() channels out of range");
174         return (jint) oboe::Result::ErrorOutOfRange;
175     }
176 
177     // Create an audio stream.
178     oboe::AudioStreamBuilder builder;
179     builder.setChannelCount(channelCount)
180             ->setDirection(isInput ? oboe::Direction::Input : oboe::Direction::Output)
181             ->setSharingMode((oboe::SharingMode) sharingMode)
182             ->setPerformanceMode((oboe::PerformanceMode) performanceMode)
183             ->setInputPreset((oboe::InputPreset)inputPreset)
184             ->setDeviceId(deviceId)
185             ->setSessionId((oboe::SessionId) sessionId)
186             ->setSampleRate(sampleRate)
187             ->setFormat((oboe::AudioFormat) format)
188             ->setChannelConversionAllowed(channelConversionAllowed)
189             ->setFormatConversionAllowed(formatConversionAllowed)
190             ->setSampleRateConversionQuality((oboe::SampleRateConversionQuality) rateConversionQuality)
191             ;
192 
193     configureBuilder(isInput, builder);
194 
195     builder.setAudioApi(audioApi);
196 
197     // Temporarily set the AAudio MMAP policy to disable MMAP or not.
198     bool oldMMapEnabled = AAudioExtensions::getInstance().isMMapEnabled();
199     AAudioExtensions::getInstance().setMMapEnabled(isMMap);
200 
201     // Record time for opening.
202     if (isInput) {
203         mInputOpenedAt = oboe::AudioClock::getNanoseconds();
204     } else {
205         mOutputOpenedAt = oboe::AudioClock::getNanoseconds();
206     }
207     // Open a stream based on the builder settings.
208     std::shared_ptr<oboe::AudioStream> oboeStream;
209     Result result = builder.openStream(oboeStream);
210     AAudioExtensions::getInstance().setMMapEnabled(oldMMapEnabled);
211     if (result != Result::OK) {
212         freeStreamIndex(streamIndex);
213         streamIndex = -1;
214     } else {
215         mOboeStreams[streamIndex] = oboeStream; // save shared_ptr
216 
217         mChannelCount = oboeStream->getChannelCount(); // FIXME store per stream
218         mFramesPerBurst = oboeStream->getFramesPerBurst();
219         mSampleRate = oboeStream->getSampleRate();
220 
221         createRecording();
222 
223         finishOpen(isInput, oboeStream.get());
224     }
225 
226     if (!mUseCallback) {
227         int numSamples = getFramesPerBlock() * mChannelCount;
228         dataBuffer = std::make_unique<float[]>(numSamples);
229     }
230 
231     return (result != Result::OK) ? (int)result : streamIndex;
232 }
233 
start()234 oboe::Result ActivityContext::start() {
235     oboe::Result result = oboe::Result::OK;
236     std::shared_ptr<oboe::AudioStream> inputStream = getInputStream();
237     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
238     if (inputStream == nullptr && outputStream == nullptr) {
239         LOGD("%s() - no streams defined", __func__);
240         return oboe::Result::ErrorInvalidState; // not open
241     }
242 
243     configureForStart();
244 
245     audioStreamGateway.reset();
246     result = startStreams();
247 
248     if (!mUseCallback && result == oboe::Result::OK) {
249         // Instead of using the callback, start a thread that writes the stream.
250         threadEnabled.store(true);
251         dataThread = new std::thread(threadCallback, this);
252     }
253 
254     return result;
255 }
256 
saveWaveFile(const char * filename)257 int32_t  ActivityContext::saveWaveFile(const char *filename) {
258     if (mRecording == nullptr) {
259         LOGW("ActivityContext::saveWaveFile(%s) but no recording!", filename);
260         return -1;
261     }
262     if (mRecording->getSizeInFrames() == 0) {
263         LOGW("ActivityContext::saveWaveFile(%s) but no frames!", filename);
264         return -2;
265     }
266     MyOboeOutputStream outStream;
267     WaveFileWriter writer(&outStream);
268 
269     writer.setFrameRate(mSampleRate);
270     writer.setSamplesPerFrame(mRecording->getChannelCount());
271     writer.setBitsPerSample(24);
272     float buffer[mRecording->getChannelCount()];
273     // Read samples from start to finish.
274     mRecording->rewind();
275     for (int32_t frameIndex = 0; frameIndex < mRecording->getSizeInFrames(); frameIndex++) {
276         mRecording->read(buffer, 1 /* numFrames */);
277         for (int32_t i = 0; i < mRecording->getChannelCount(); i++) {
278             writer.write(buffer[i]);
279         }
280     }
281     writer.close();
282 
283     if (outStream.length() > 0) {
284         auto myfile = std::ofstream(filename, std::ios::out | std::ios::binary);
285         myfile.write((char *) outStream.getData(), outStream.length());
286         myfile.close();
287     }
288 
289     return outStream.length();
290 }
291 
getTimestampLatency(int32_t streamIndex)292 double ActivityContext::getTimestampLatency(int32_t streamIndex) {
293     std::shared_ptr<oboe::AudioStream> oboeStream = getStream(streamIndex);
294     if (oboeStream != nullptr) {
295         auto result = oboeStream->calculateLatencyMillis();
296         return (!result) ? -1.0 : result.value();
297     }
298     return -1.0;
299 }
300 
301 // =================================================================== ActivityTestOutput
close(int32_t streamIndex)302 void ActivityTestOutput::close(int32_t streamIndex) {
303     ActivityContext::close(streamIndex);
304     manyToMulti.reset(nullptr);
305     monoToMulti.reset(nullptr);
306     mSinkFloat.reset();
307     mSinkI16.reset();
308 }
309 
setChannelEnabled(int channelIndex,bool enabled)310 void ActivityTestOutput::setChannelEnabled(int channelIndex, bool enabled) {
311     if (manyToMulti == nullptr) {
312         return;
313     }
314     if (enabled) {
315         switch (mSignalType) {
316             case SignalType::Sine:
317                 sineOscillators[channelIndex].frequency.disconnect();
318                 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
319                 break;
320             case SignalType::Sawtooth:
321                 sawtoothOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
322                 break;
323             case SignalType::FreqSweep:
324                 mLinearShape.output.connect(&sineOscillators[channelIndex].frequency);
325                 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
326                 break;
327             case SignalType::PitchSweep:
328                 mExponentialShape.output.connect(&sineOscillators[channelIndex].frequency);
329                 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
330                 break;
331             default:
332                 break;
333         }
334     } else {
335         manyToMulti->inputs[channelIndex]->disconnect();
336     }
337 }
338 
configureForStart()339 void ActivityTestOutput::configureForStart() {
340     manyToMulti = std::make_unique<ManyToMultiConverter>(mChannelCount);
341 
342     mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
343     mSinkI16 = std::make_unique<SinkI16>(mChannelCount);
344 
345     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
346 
347     mTriangleOscillator.setSampleRate(outputStream->getSampleRate());
348     mTriangleOscillator.frequency.setValue(1.0/kSweepPeriod);
349     mTriangleOscillator.amplitude.setValue(1.0);
350     mTriangleOscillator.setPhase(-1.0);
351 
352     mLinearShape.setMinimum(0.0);
353     mLinearShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
354 
355     mExponentialShape.setMinimum(110.0);
356     mExponentialShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
357 
358     mTriangleOscillator.output.connect(&(mLinearShape.input));
359     mTriangleOscillator.output.connect(&(mExponentialShape.input));
360     {
361         double frequency = 330.0;
362         for (int i = 0; i < mChannelCount; i++) {
363             sineOscillators[i].setSampleRate(outputStream->getSampleRate());
364             sineOscillators[i].frequency.setValue(frequency);
365             frequency *= 4.0 / 3.0; // each sine is at a higher frequency
366             sineOscillators[i].amplitude.setValue(AMPLITUDE_SINE);
367             setChannelEnabled(i, true);
368         }
369     }
370 
371     manyToMulti->output.connect(&(mSinkFloat.get()->input));
372     manyToMulti->output.connect(&(mSinkI16.get()->input));
373 
374     mSinkFloat->pullReset();
375     mSinkI16->pullReset();
376 
377     configureStreamGateway();
378 }
379 
configureStreamGateway()380 void ActivityTestOutput::configureStreamGateway() {
381     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
382     if (outputStream->getFormat() == oboe::AudioFormat::I16) {
383         audioStreamGateway.setAudioSink(mSinkI16);
384     } else if (outputStream->getFormat() == oboe::AudioFormat::Float) {
385         audioStreamGateway.setAudioSink(mSinkFloat);
386     }
387 
388     if (mUseCallback) {
389         oboeCallbackProxy.setCallback(&audioStreamGateway);
390     }
391 }
392 
runBlockingIO()393 void ActivityTestOutput::runBlockingIO() {
394     int32_t framesPerBlock = getFramesPerBlock();
395     oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
396 
397     std::shared_ptr<oboe::AudioStream> oboeStream = getOutputStream();
398     if (oboeStream == nullptr) {
399         LOGE("%s() : no stream found\n", __func__);
400         return;
401     }
402 
403     while (threadEnabled.load()
404            && callbackResult == oboe::DataCallbackResult::Continue) {
405         // generate output by calling the callback
406         callbackResult = audioStreamGateway.onAudioReady(oboeStream.get(),
407                                                          dataBuffer.get(),
408                                                          framesPerBlock);
409 
410         auto result = oboeStream->write(dataBuffer.get(),
411                                         framesPerBlock,
412                                         NANOS_PER_SECOND);
413 
414         if (!result) {
415             LOGE("%s() returned %s\n", __func__, convertToText(result.error()));
416             break;
417         }
418         int32_t framesWritten = result.value();
419         if (framesWritten < framesPerBlock) {
420             LOGE("%s() : write() wrote %d of %d\n", __func__, framesWritten, framesPerBlock);
421             break;
422         }
423     }
424 }
425 
426 // ======================================================================= ActivityTestInput
configureForStart()427 void ActivityTestInput::configureForStart() {
428     mInputAnalyzer.reset();
429     if (mUseCallback) {
430         oboeCallbackProxy.setCallback(&mInputAnalyzer);
431     }
432     mInputAnalyzer.setRecording(mRecording.get());
433 }
434 
runBlockingIO()435 void ActivityTestInput::runBlockingIO() {
436     int32_t framesPerBlock = getFramesPerBlock();
437     oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
438 
439     std::shared_ptr<oboe::AudioStream> oboeStream = getInputStream();
440     if (oboeStream == nullptr) {
441         LOGE("%s() : no stream found\n", __func__);
442         return;
443     }
444 
445     while (threadEnabled.load()
446            && callbackResult == oboe::DataCallbackResult::Continue) {
447 
448         // Avoid glitches by waiting until there is extra data in the FIFO.
449         auto err = oboeStream->waitForAvailableFrames(mMinimumFramesBeforeRead, kNanosPerSecond);
450         if (!err) break;
451 
452         // read from input
453         auto result = oboeStream->read(dataBuffer.get(),
454                                        framesPerBlock,
455                                        NANOS_PER_SECOND);
456         if (!result) {
457             LOGE("%s() : read() returned %s\n", __func__, convertToText(result.error()));
458             break;
459         }
460         int32_t framesRead = result.value();
461         if (framesRead < framesPerBlock) { // timeout?
462             LOGE("%s() : read() read %d of %d\n", __func__, framesRead, framesPerBlock);
463             break;
464         }
465 
466         // analyze input
467         callbackResult = mInputAnalyzer.onAudioReady(oboeStream.get(),
468                                                      dataBuffer.get(),
469                                                      framesRead);
470     }
471 }
472 
stopPlayback()473 oboe::Result ActivityRecording::stopPlayback() {
474     oboe::Result result = oboe::Result::OK;
475     if (playbackStream != nullptr) {
476         result = playbackStream->requestStop();
477         playbackStream->close();
478         mPlayRecordingCallback.setRecording(nullptr);
479         delete playbackStream;
480         playbackStream = nullptr;
481     }
482     return result;
483 }
484 
startPlayback()485 oboe::Result ActivityRecording::startPlayback() {
486     stop();
487     oboe::AudioStreamBuilder builder;
488     builder.setChannelCount(mChannelCount)
489             ->setSampleRate(mSampleRate)
490             ->setFormat(oboe::AudioFormat::Float)
491             ->setCallback(&mPlayRecordingCallback)
492             ->setAudioApi(oboe::AudioApi::OpenSLES);
493     oboe::Result result = builder.openStream(&playbackStream);
494     if (result != oboe::Result::OK) {
495         delete playbackStream;
496         playbackStream = nullptr;
497     } else if (playbackStream != nullptr) {
498         if (mRecording != nullptr) {
499             mRecording->rewind();
500             mPlayRecordingCallback.setRecording(mRecording.get());
501             result = playbackStream->requestStart();
502         }
503     }
504     return result;
505 }
506 
507 // ======================================================================= ActivityTapToTone
configureForStart()508 void ActivityTapToTone::configureForStart() {
509     monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
510 
511     mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
512     mSinkI16 = std::make_unique<SinkI16>(mChannelCount);
513 
514     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
515     sawPingGenerator.setSampleRate(outputStream->getSampleRate());
516     sawPingGenerator.frequency.setValue(FREQUENCY_SAW_PING);
517     sawPingGenerator.amplitude.setValue(AMPLITUDE_SAW_PING);
518 
519     sawPingGenerator.output.connect(&(monoToMulti->input));
520     monoToMulti->output.connect(&(mSinkFloat.get()->input));
521     monoToMulti->output.connect(&(mSinkI16.get()->input));
522 
523     mSinkFloat->pullReset();
524     mSinkI16->pullReset();
525 
526     configureStreamGateway();
527 }
528 
529 // ======================================================================= ActivityRoundTripLatency
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)530 void ActivityFullDuplex::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
531     if (isInput) {
532         // Ideally the output streams should be opened first.
533         std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
534         if (outputStream != nullptr) {
535             // Make sure the capacity is bigger than two bursts.
536             int32_t burst = outputStream->getFramesPerBurst();
537             builder.setBufferCapacityInFrames(2 * burst);
538         }
539     }
540 }
541 
542 // ======================================================================= ActivityEcho
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)543 void ActivityEcho::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
544     ActivityFullDuplex::configureBuilder(isInput, builder);
545 
546     if (mFullDuplexEcho.get() == nullptr) {
547         mFullDuplexEcho = std::make_unique<FullDuplexEcho>();
548     }
549     // only output uses a callback, input is polled
550     if (!isInput) {
551         builder.setCallback(mFullDuplexEcho.get());
552     }
553 }
554 
finishOpen(bool isInput,oboe::AudioStream * oboeStream)555 void ActivityEcho::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
556     if (isInput) {
557         mFullDuplexEcho->setInputStream(oboeStream);
558     } else {
559         mFullDuplexEcho->setOutputStream(oboeStream);
560     }
561 }
562 
563 // ======================================================================= ActivityRoundTripLatency
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)564 void ActivityRoundTripLatency::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
565     ActivityFullDuplex::configureBuilder(isInput, builder);
566 
567     if (mFullDuplexLatency.get() == nullptr) {
568         mFullDuplexLatency = std::make_unique<FullDuplexAnalyzer>(&mEchoAnalyzer);
569     }
570     if (!isInput) {
571         // only output uses a callback, input is polled
572         builder.setCallback(mFullDuplexLatency.get());
573     }
574 }
575 
finishOpen(bool isInput,AudioStream * oboeStream)576 void ActivityRoundTripLatency::finishOpen(bool isInput, AudioStream *oboeStream) {
577     if (isInput) {
578         mFullDuplexLatency->setInputStream(oboeStream);
579         mFullDuplexLatency->setRecording(mRecording.get());
580     } else {
581         mFullDuplexLatency->setOutputStream(oboeStream);
582     }
583 }
584 
585 // ======================================================================= ActivityGlitches
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)586 void ActivityGlitches::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
587     ActivityFullDuplex::configureBuilder(isInput, builder);
588 
589     if (mFullDuplexGlitches.get() == nullptr) {
590         mFullDuplexGlitches = std::make_unique<FullDuplexAnalyzer>(&mGlitchAnalyzer);
591     }
592     if (!isInput) {
593         // only output uses a callback, input is polled
594         builder.setCallback(mFullDuplexGlitches.get());
595     }
596 }
597 
finishOpen(bool isInput,oboe::AudioStream * oboeStream)598 void ActivityGlitches::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
599     if (isInput) {
600         mFullDuplexGlitches->setInputStream(oboeStream);
601         mFullDuplexGlitches->setRecording(mRecording.get());
602     } else {
603         mFullDuplexGlitches->setOutputStream(oboeStream);
604     }
605 }
606 
607 // ======================================================================= ActivityDataPath
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)608 void ActivityDataPath::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
609     ActivityFullDuplex::configureBuilder(isInput, builder);
610 
611     if (mFullDuplexDataPath.get() == nullptr) {
612         mFullDuplexDataPath = std::make_unique<FullDuplexAnalyzer>(&mDataPathAnalyzer);
613     }
614     if (!isInput) {
615         // only output uses a callback, input is polled
616         builder.setCallback(mFullDuplexDataPath.get());
617     }
618 }
619 
finishOpen(bool isInput,oboe::AudioStream * oboeStream)620 void ActivityDataPath::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
621     if (isInput) {
622         mFullDuplexDataPath->setInputStream(oboeStream);
623         mFullDuplexDataPath->setRecording(mRecording.get());
624     } else {
625         mFullDuplexDataPath->setOutputStream(oboeStream);
626     }
627 }
628 
629 // =================================================================== ActivityTestDisconnect
close(int32_t streamIndex)630 void ActivityTestDisconnect::close(int32_t streamIndex) {
631     ActivityContext::close(streamIndex);
632     mSinkFloat.reset();
633 }
634 
configureForStart()635 void ActivityTestDisconnect::configureForStart() {
636     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
637     std::shared_ptr<oboe::AudioStream> inputStream = getInputStream();
638     if (outputStream) {
639         mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
640         sineOscillator = std::make_unique<SineOscillator>();
641         monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
642 
643         sineOscillator->setSampleRate(outputStream->getSampleRate());
644         sineOscillator->frequency.setValue(440.0);
645         sineOscillator->amplitude.setValue(AMPLITUDE_SINE);
646         sineOscillator->output.connect(&(monoToMulti->input));
647 
648         monoToMulti->output.connect(&(mSinkFloat->input));
649         mSinkFloat->pullReset();
650         audioStreamGateway.setAudioSink(mSinkFloat);
651     } else if (inputStream) {
652         audioStreamGateway.setAudioSink(nullptr);
653     }
654     oboeCallbackProxy.setCallback(&audioStreamGateway);
655 }
656 
657