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