xref: /aosp_15_r20/external/oboe/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp (revision 05767d913155b055644481607e6fa1e35e2fe72c)
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 // Set to 1 for debugging race condition #1180 with mAAudioStream.
18 // See also AudioStreamAAudio.cpp in Oboe.
19 // This was left in the code so that we could test the fix again easily in the future.
20 // We could not trigger the race condition without adding these get calls and the sleeps.
21 #define DEBUG_CLOSE_RACE 0
22 
23 #include <fstream>
24 #include <iostream>
25 #if DEBUG_CLOSE_RACE
26 #include <thread>
27 #endif // DEBUG_CLOSE_RACE
28 #include <vector>
29 #include <common/AudioClock.h>
30 
31 #include <common/AudioClock.h>
32 #include "util/WaveFileWriter.h"
33 
34 #include "NativeAudioContext.h"
35 
36 using namespace oboe;
37 
convertNativeApiToAudioApi(int nativeApi)38 static oboe::AudioApi convertNativeApiToAudioApi(int nativeApi) {
39     switch (nativeApi) {
40         default:
41         case NATIVE_MODE_UNSPECIFIED:
42             return oboe::AudioApi::Unspecified;
43         case NATIVE_MODE_AAUDIO:
44             return oboe::AudioApi::AAudio;
45         case NATIVE_MODE_OPENSLES:
46             return oboe::AudioApi::OpenSLES;
47     }
48 }
49 
50 class MyOboeOutputStream : public WaveFileOutputStream {
51 public:
write(uint8_t b)52     void write(uint8_t b) override {
53         mData.push_back(b);
54     }
55 
length()56     int32_t length() {
57         return (int32_t) mData.size();
58     }
59 
getData()60     uint8_t *getData() {
61         return mData.data();
62     }
63 
64 private:
65     std::vector<uint8_t> mData;
66 };
67 
68 bool ActivityContext::mUseCallback = true;
69 int  ActivityContext::callbackSize = 0;
70 
getOutputStream()71 std::shared_ptr<oboe::AudioStream> ActivityContext::getOutputStream() {
72     for (auto entry : mOboeStreams) {
73         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
74         if (oboeStream->getDirection() == oboe::Direction::Output) {
75             return oboeStream;
76         }
77     }
78     return nullptr;
79 }
80 
getInputStream()81 std::shared_ptr<oboe::AudioStream> ActivityContext::getInputStream() {
82     for (auto entry : mOboeStreams) {
83         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
84         if (oboeStream != nullptr) {
85             if (oboeStream->getDirection() == oboe::Direction::Input) {
86                 return oboeStream;
87             }
88         }
89     }
90     return nullptr;
91 }
92 
freeStreamIndex(int32_t streamIndex)93 void ActivityContext::freeStreamIndex(int32_t streamIndex) {
94     mOboeStreams[streamIndex].reset();
95     mOboeStreams.erase(streamIndex);
96 }
97 
allocateStreamIndex()98 int32_t ActivityContext::allocateStreamIndex() {
99     return mNextStreamHandle++;
100 }
101 
release()102 oboe::Result ActivityContext::release() {
103     oboe::Result result = oboe::Result::OK;
104     stopBlockingIOThread();
105     for (auto entry : mOboeStreams) {
106         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
107         result = oboeStream->release();
108     }
109     return result;
110 }
111 
close(int32_t streamIndex)112 void ActivityContext::close(int32_t streamIndex) {
113     stopBlockingIOThread();
114     std::shared_ptr<oboe::AudioStream> oboeStream = getStream(streamIndex);
115     if (oboeStream != nullptr) {
116         oboeStream->close();
117         LOGD("ActivityContext::%s() delete stream %d ", __func__, streamIndex);
118         freeStreamIndex(streamIndex);
119     }
120 }
121 
isMMapUsed(int32_t streamIndex)122 bool ActivityContext::isMMapUsed(int32_t streamIndex) {
123     std::shared_ptr<oboe::AudioStream> oboeStream = getStream(streamIndex);
124     if (oboeStream == nullptr) return false;
125     if (oboeStream->getAudioApi() != AudioApi::AAudio) return false;
126     return AAudioExtensions::getInstance().isMMapUsed(oboeStream.get());
127 }
128 
pause()129 oboe::Result ActivityContext::pause() {
130     oboe::Result result = oboe::Result::OK;
131     stopBlockingIOThread();
132     for (auto entry : mOboeStreams) {
133         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
134         result = oboeStream->requestPause();
135     }
136     return result;
137 }
138 
stopAllStreams()139 oboe::Result ActivityContext::stopAllStreams() {
140     oboe::Result result = oboe::Result::OK;
141     stopBlockingIOThread();
142     for (auto entry : mOboeStreams) {
143         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
144         result = oboeStream->requestStop();
145     }
146     return result;
147 }
148 
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)149 void ActivityContext::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
150     // We needed the proxy because we did not know the channelCount when we setup the Builder.
151     if (mUseCallback) {
152         builder.setDataCallback(&oboeCallbackProxy);
153     }
154 }
155 
open(jint nativeApi,jint sampleRate,jint channelCount,jint channelMask,jint format,jint sharingMode,jint performanceMode,jint inputPreset,jint usage,jint contentType,jint bufferCapacityInFrames,jint deviceId,jint sessionId,jboolean channelConversionAllowed,jboolean formatConversionAllowed,jint rateConversionQuality,jboolean isMMap,jboolean isInput)156 int ActivityContext::open(jint nativeApi,
157                           jint sampleRate,
158                           jint channelCount,
159                           jint channelMask,
160                           jint format,
161                           jint sharingMode,
162                           jint performanceMode,
163                           jint inputPreset,
164                           jint usage,
165                           jint contentType,
166                           jint bufferCapacityInFrames,
167                           jint deviceId,
168                           jint sessionId,
169                           jboolean channelConversionAllowed,
170                           jboolean formatConversionAllowed,
171                           jint rateConversionQuality,
172                           jboolean isMMap,
173                           jboolean isInput) {
174 
175     oboe::AudioApi audioApi = oboe::AudioApi::Unspecified;
176     switch (nativeApi) {
177         case NATIVE_MODE_UNSPECIFIED:
178         case NATIVE_MODE_AAUDIO:
179         case NATIVE_MODE_OPENSLES:
180             audioApi = convertNativeApiToAudioApi(nativeApi);
181             break;
182         default:
183             return (jint) oboe::Result::ErrorOutOfRange;
184     }
185 
186     int32_t streamIndex = allocateStreamIndex();
187     if (streamIndex < 0) {
188         LOGE("ActivityContext::open() stream array full");
189         return (jint) oboe::Result::ErrorNoFreeHandles;
190     }
191 
192     if (channelCount < 0 || channelCount > 256) {
193         LOGE("ActivityContext::open() channels out of range");
194         return (jint) oboe::Result::ErrorOutOfRange;
195     }
196 
197     // Create an audio stream.
198     oboe::AudioStreamBuilder builder;
199     builder.setChannelCount(channelCount)
200             ->setDirection(isInput ? oboe::Direction::Input : oboe::Direction::Output)
201             ->setSharingMode((oboe::SharingMode) sharingMode)
202             ->setPerformanceMode((oboe::PerformanceMode) performanceMode)
203             ->setInputPreset((oboe::InputPreset)inputPreset)
204             ->setUsage((oboe::Usage)usage)
205             ->setContentType((oboe::ContentType)contentType)
206             ->setBufferCapacityInFrames(bufferCapacityInFrames)
207             ->setDeviceId(deviceId)
208             ->setSessionId((oboe::SessionId) sessionId)
209             ->setSampleRate(sampleRate)
210             ->setFormat((oboe::AudioFormat) format)
211             ->setChannelConversionAllowed(channelConversionAllowed)
212             ->setFormatConversionAllowed(formatConversionAllowed)
213             ->setSampleRateConversionQuality((oboe::SampleRateConversionQuality) rateConversionQuality)
214             ;
215     if (channelMask != (jint) oboe::ChannelMask::Unspecified) {
216         // Set channel mask when it is specified.
217         builder.setChannelMask((oboe::ChannelMask) channelMask);
218     }
219     if (mUseCallback) {
220         builder.setFramesPerCallback(callbackSize);
221     }
222     configureBuilder(isInput, builder);
223 
224     builder.setAudioApi(audioApi);
225 
226     // Temporarily set the AAudio MMAP policy to disable MMAP or not.
227     bool oldMMapEnabled = AAudioExtensions::getInstance().isMMapEnabled();
228     AAudioExtensions::getInstance().setMMapEnabled(isMMap);
229 
230     // Record time for opening.
231     if (isInput) {
232         mInputOpenedAt = oboe::AudioClock::getNanoseconds();
233     } else {
234         mOutputOpenedAt = oboe::AudioClock::getNanoseconds();
235     }
236     // Open a stream based on the builder settings.
237     std::shared_ptr<oboe::AudioStream> oboeStream;
238     Result result = builder.openStream(oboeStream);
239     AAudioExtensions::getInstance().setMMapEnabled(oldMMapEnabled);
240     if (result != Result::OK) {
241         freeStreamIndex(streamIndex);
242         streamIndex = -1;
243     } else {
244         mOboeStreams[streamIndex] = oboeStream; // save shared_ptr
245 
246         mChannelCount = oboeStream->getChannelCount(); // FIXME store per stream
247         mFramesPerBurst = oboeStream->getFramesPerBurst();
248         mSampleRate = oboeStream->getSampleRate();
249 
250         createRecording();
251 
252         finishOpen(isInput, oboeStream.get());
253     }
254 
255     if (!mUseCallback) {
256         int numSamples = getFramesPerBlock() * mChannelCount;
257         dataBuffer = std::make_unique<float[]>(numSamples);
258     }
259 
260     if (result != Result::OK) {
261         return (int) result;
262     } else {
263         configureAfterOpen();
264         return streamIndex;
265     }
266 }
267 
start()268 oboe::Result ActivityContext::start() {
269     oboe::Result result = oboe::Result::OK;
270     std::shared_ptr<oboe::AudioStream> inputStream = getInputStream();
271     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
272     if (inputStream == nullptr && outputStream == nullptr) {
273         LOGD("%s() - no streams defined", __func__);
274         return oboe::Result::ErrorInvalidState; // not open
275     }
276 
277     audioStreamGateway.reset();
278     result = startStreams();
279 
280     if (!mUseCallback && result == oboe::Result::OK) {
281         // Instead of using the callback, start a thread that writes the stream.
282         threadEnabled.store(true);
283         dataThread = new std::thread(threadCallback, this);
284     }
285 
286 #if DEBUG_CLOSE_RACE
287     // Also put a sleep for 400 msec in AudioStreamAAudio::updateFramesRead().
288     if (outputStream != nullptr) {
289         std::thread raceDebugger([outputStream]() {
290             while (outputStream->getState() != StreamState::Closed) {
291                 int64_t framesRead = outputStream->getFramesRead();
292                 LOGD("raceDebugger, framesRead = %d, state = %d",
293                      (int) framesRead, (int) outputStream->getState());
294             }
295         });
296         raceDebugger.detach();
297     }
298 #endif // DEBUG_CLOSE_RACE
299 
300     return result;
301 }
302 
flush()303 oboe::Result ActivityContext::flush() {
304     oboe::Result result = oboe::Result::OK;
305     for (auto entry : mOboeStreams) {
306         std::shared_ptr<oboe::AudioStream> oboeStream = entry.second;
307         result = oboeStream->requestFlush();
308     }
309     return result;
310 }
311 
saveWaveFile(const char * filename)312 int32_t  ActivityContext::saveWaveFile(const char *filename) {
313     if (mRecording == nullptr) {
314         LOGW("ActivityContext::saveWaveFile(%s) but no recording!", filename);
315         return -1;
316     }
317     if (mRecording->getSizeInFrames() == 0) {
318         LOGW("ActivityContext::saveWaveFile(%s) but no frames!", filename);
319         return -2;
320     }
321     MyOboeOutputStream outStream;
322     WaveFileWriter writer(&outStream);
323 
324     writer.setFrameRate(mSampleRate);
325     writer.setSamplesPerFrame(mRecording->getChannelCount());
326     writer.setBitsPerSample(24);
327     float buffer[mRecording->getChannelCount()];
328     // Read samples from start to finish.
329     mRecording->rewind();
330     for (int32_t frameIndex = 0; frameIndex < mRecording->getSizeInFrames(); frameIndex++) {
331         mRecording->read(buffer, 1 /* numFrames */);
332         for (int32_t i = 0; i < mRecording->getChannelCount(); i++) {
333             writer.write(buffer[i]);
334         }
335     }
336     writer.close();
337 
338     if (outStream.length() > 0) {
339         auto myfile = std::ofstream(filename, std::ios::out | std::ios::binary);
340         myfile.write((char *) outStream.getData(), outStream.length());
341         myfile.close();
342     }
343 
344     return outStream.length();
345 }
346 
getTimestampLatency(int32_t streamIndex)347 double ActivityContext::getTimestampLatency(int32_t streamIndex) {
348     std::shared_ptr<oboe::AudioStream> oboeStream = getStream(streamIndex);
349     if (oboeStream != nullptr) {
350         auto result = oboeStream->calculateLatencyMillis();
351         return (!result) ? -1.0 : result.value();
352     }
353     return -1.0;
354 }
355 
356 // =================================================================== ActivityTestOutput
close(int32_t streamIndex)357 void ActivityTestOutput::close(int32_t streamIndex) {
358     ActivityContext::close(streamIndex);
359     manyToMulti.reset(nullptr);
360     monoToMulti.reset(nullptr);
361     mVolumeRamp.reset();
362     mSinkFloat.reset();
363     mSinkI16.reset();
364     mSinkI24.reset();
365     mSinkI32.reset();
366 }
367 
setChannelEnabled(int channelIndex,bool enabled)368 void ActivityTestOutput::setChannelEnabled(int channelIndex, bool enabled) {
369     if (manyToMulti == nullptr) {
370         return;
371     }
372     if (enabled) {
373         switch (mSignalType) {
374             case SignalType::Sine:
375                 sineOscillators[channelIndex].frequency.disconnect();
376                 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
377                 break;
378             case SignalType::Sawtooth:
379                 sawtoothOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
380                 break;
381             case SignalType::FreqSweep:
382                 mLinearShape.output.connect(&sineOscillators[channelIndex].frequency);
383                 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
384                 break;
385             case SignalType::PitchSweep:
386                 mExponentialShape.output.connect(&sineOscillators[channelIndex].frequency);
387                 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
388                 break;
389             case SignalType::WhiteNoise:
390                 mWhiteNoise.output.connect(manyToMulti->inputs[channelIndex].get());
391                 break;
392             default:
393                 break;
394         }
395     } else {
396         manyToMulti->inputs[channelIndex]->disconnect();
397     }
398 }
399 
configureAfterOpen()400 void ActivityTestOutput::configureAfterOpen() {
401     manyToMulti = std::make_unique<ManyToMultiConverter>(mChannelCount);
402 
403     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
404 
405     mVolumeRamp = std::make_shared<RampLinear>(mChannelCount);
406     mVolumeRamp->setLengthInFrames(kRampMSec * outputStream->getSampleRate() /
407             MILLISECONDS_PER_SECOND);
408     mVolumeRamp->setTarget(mAmplitude);
409 
410     mSinkFloat = std::make_shared<SinkFloat>(mChannelCount);
411     mSinkI16 = std::make_shared<SinkI16>(mChannelCount);
412     mSinkI24 = std::make_shared<SinkI24>(mChannelCount);
413     mSinkI32 = std::make_shared<SinkI32>(mChannelCount);
414 
415     mTriangleOscillator.setSampleRate(outputStream->getSampleRate());
416     mTriangleOscillator.frequency.setValue(1.0/kSweepPeriod);
417     mTriangleOscillator.amplitude.setValue(1.0);
418     mTriangleOscillator.setPhase(-1.0);
419 
420     mLinearShape.setMinimum(0.0);
421     mLinearShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
422 
423     mExponentialShape.setMinimum(110.0);
424     mExponentialShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
425 
426     mTriangleOscillator.output.connect(&(mLinearShape.input));
427     mTriangleOscillator.output.connect(&(mExponentialShape.input));
428     {
429         double frequency = 330.0;
430         for (int i = 0; i < mChannelCount; i++) {
431             sineOscillators[i].setSampleRate(outputStream->getSampleRate());
432             sineOscillators[i].frequency.setValue(frequency);
433             sineOscillators[i].amplitude.setValue(AMPLITUDE_SINE);
434             sawtoothOscillators[i].setSampleRate(outputStream->getSampleRate());
435             sawtoothOscillators[i].frequency.setValue(frequency);
436             sawtoothOscillators[i].amplitude.setValue(AMPLITUDE_SAWTOOTH);
437 
438             frequency *= 4.0 / 3.0; // each wave is at a higher frequency
439             setChannelEnabled(i, true);
440         }
441     }
442 
443     mWhiteNoise.amplitude.setValue(0.5);
444 
445     manyToMulti->output.connect(&(mVolumeRamp.get()->input));
446 
447     mVolumeRamp->output.connect(&(mSinkFloat.get()->input));
448     mVolumeRamp->output.connect(&(mSinkI16.get()->input));
449     mVolumeRamp->output.connect(&(mSinkI24.get()->input));
450     mVolumeRamp->output.connect(&(mSinkI32.get()->input));
451 
452     mSinkFloat->pullReset();
453     mSinkI16->pullReset();
454     mSinkI24->pullReset();
455     mSinkI32->pullReset();
456 
457     configureStreamGateway();
458 }
459 
configureStreamGateway()460 void ActivityTestOutput::configureStreamGateway() {
461     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
462     if (outputStream->getFormat() == oboe::AudioFormat::I16) {
463         audioStreamGateway.setAudioSink(mSinkI16);
464     } else if (outputStream->getFormat() == oboe::AudioFormat::I24) {
465         audioStreamGateway.setAudioSink(mSinkI24);
466     } else if (outputStream->getFormat() == oboe::AudioFormat::I32) {
467         audioStreamGateway.setAudioSink(mSinkI32);
468     } else if (outputStream->getFormat() == oboe::AudioFormat::Float) {
469         audioStreamGateway.setAudioSink(mSinkFloat);
470     }
471 
472     if (mUseCallback) {
473         oboeCallbackProxy.setDataCallback(&audioStreamGateway);
474     }
475 }
476 
runBlockingIO()477 void ActivityTestOutput::runBlockingIO() {
478     int32_t framesPerBlock = getFramesPerBlock();
479     oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
480 
481     std::shared_ptr<oboe::AudioStream> oboeStream = getOutputStream();
482     if (oboeStream == nullptr) {
483         LOGE("%s() : no stream found\n", __func__);
484         return;
485     }
486 
487     while (threadEnabled.load()
488            && callbackResult == oboe::DataCallbackResult::Continue) {
489         // generate output by calling the callback
490         callbackResult = audioStreamGateway.onAudioReady(oboeStream.get(),
491                                                          dataBuffer.get(),
492                                                          framesPerBlock);
493 
494         auto result = oboeStream->write(dataBuffer.get(),
495                                         framesPerBlock,
496                                         NANOS_PER_SECOND);
497 
498         if (!result) {
499             LOGE("%s() returned %s\n", __func__, convertToText(result.error()));
500             break;
501         }
502         int32_t framesWritten = result.value();
503         if (framesWritten < framesPerBlock) {
504             LOGE("%s() : write() wrote %d of %d\n", __func__, framesWritten, framesPerBlock);
505             break;
506         }
507     }
508 }
509 
startStreams()510 oboe::Result ActivityTestOutput::startStreams() {
511     mSinkFloat->pullReset();
512     mSinkI16->pullReset();
513     mSinkI24->pullReset();
514     mSinkI32->pullReset();
515     if (mVolumeRamp != nullptr) {
516         mVolumeRamp->setTarget(mAmplitude);
517     }
518     return getOutputStream()->start();
519 }
520 
521 // ======================================================================= ActivityTestInput
configureAfterOpen()522 void ActivityTestInput::configureAfterOpen() {
523     mInputAnalyzer.reset();
524     if (mUseCallback) {
525         oboeCallbackProxy.setDataCallback(&mInputAnalyzer);
526     }
527     mInputAnalyzer.setRecording(mRecording.get());
528 }
529 
runBlockingIO()530 void ActivityTestInput::runBlockingIO() {
531     int32_t framesPerBlock = getFramesPerBlock();
532     oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
533 
534     std::shared_ptr<oboe::AudioStream> oboeStream = getInputStream();
535     if (oboeStream == nullptr) {
536         LOGE("%s() : no stream found\n", __func__);
537         return;
538     }
539 
540     while (threadEnabled.load()
541            && callbackResult == oboe::DataCallbackResult::Continue) {
542 
543         // Avoid glitches by waiting until there is extra data in the FIFO.
544         auto err = oboeStream->waitForAvailableFrames(mMinimumFramesBeforeRead, kNanosPerSecond);
545         if (!err) break;
546 
547         // read from input
548         auto result = oboeStream->read(dataBuffer.get(),
549                                        framesPerBlock,
550                                        NANOS_PER_SECOND);
551         if (!result) {
552             LOGE("%s() : read() returned %s\n", __func__, convertToText(result.error()));
553             break;
554         }
555         int32_t framesRead = result.value();
556         if (framesRead < framesPerBlock) { // timeout?
557             LOGE("%s() : read() read %d of %d\n", __func__, framesRead, framesPerBlock);
558             break;
559         }
560 
561         // analyze input
562         callbackResult = mInputAnalyzer.onAudioReady(oboeStream.get(),
563                                                      dataBuffer.get(),
564                                                      framesRead);
565     }
566 }
567 
stopPlayback()568 oboe::Result ActivityRecording::stopPlayback() {
569     oboe::Result result = oboe::Result::OK;
570     if (playbackStream != nullptr) {
571         result = playbackStream->requestStop();
572         playbackStream->close();
573         mPlayRecordingCallback.setRecording(nullptr);
574         delete playbackStream;
575         playbackStream = nullptr;
576     }
577     return result;
578 }
579 
startPlayback()580 oboe::Result ActivityRecording::startPlayback() {
581     stop();
582     oboe::AudioStreamBuilder builder;
583     builder.setChannelCount(mChannelCount)
584             ->setSampleRate(mSampleRate)
585             ->setFormat(oboe::AudioFormat::Float)
586             ->setCallback(&mPlayRecordingCallback);
587     oboe::Result result = builder.openStream(&playbackStream);
588     if (result != oboe::Result::OK) {
589         delete playbackStream;
590         playbackStream = nullptr;
591     } else if (playbackStream != nullptr) {
592         if (mRecording != nullptr) {
593             mRecording->rewind();
594             mPlayRecordingCallback.setRecording(mRecording.get());
595             result = playbackStream->requestStart();
596         }
597     }
598     return result;
599 }
600 
601 // ======================================================================= ActivityTapToTone
configureAfterOpen()602 void ActivityTapToTone::configureAfterOpen() {
603     monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
604 
605     mSinkFloat = std::make_shared<SinkFloat>(mChannelCount);
606     mSinkI16 = std::make_shared<SinkI16>(mChannelCount);
607     mSinkI24 = std::make_shared<SinkI24>(mChannelCount);
608     mSinkI32 = std::make_shared<SinkI32>(mChannelCount);
609 
610     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
611     sawPingGenerator.setSampleRate(outputStream->getSampleRate());
612     sawPingGenerator.frequency.setValue(FREQUENCY_SAW_PING);
613     sawPingGenerator.amplitude.setValue(AMPLITUDE_SAW_PING);
614 
615     sawPingGenerator.output.connect(&(monoToMulti->input));
616     monoToMulti->output.connect(&(mSinkFloat.get()->input));
617     monoToMulti->output.connect(&(mSinkI16.get()->input));
618     monoToMulti->output.connect(&(mSinkI24.get()->input));
619     monoToMulti->output.connect(&(mSinkI32.get()->input));
620 
621     mSinkFloat->pullReset();
622     mSinkI16->pullReset();
623     mSinkI24->pullReset();
624     mSinkI32->pullReset();
625 
626     configureStreamGateway();
627 }
628 
629 // ======================================================================= ActivityFullDuplex
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)630 void ActivityFullDuplex::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
631     if (isInput) {
632         // Ideally the output streams should be opened first.
633         std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
634         if (outputStream != nullptr) {
635             // The input and output buffers will run in sync with input empty
636             // and output full. So set the input capacity to match the output.
637             builder.setBufferCapacityInFrames(outputStream->getBufferCapacityInFrames());
638         }
639     }
640 }
641 
642 // ======================================================================= ActivityEcho
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)643 void ActivityEcho::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
644     ActivityFullDuplex::configureBuilder(isInput, builder);
645 
646     if (mFullDuplexEcho.get() == nullptr) {
647         mFullDuplexEcho = std::make_unique<FullDuplexEcho>();
648     }
649     // only output uses a callback, input is polled
650     if (!isInput) {
651         builder.setCallback((oboe::AudioStreamCallback *) &oboeCallbackProxy);
652         oboeCallbackProxy.setDataCallback(mFullDuplexEcho.get());
653     }
654 }
655 
finishOpen(bool isInput,oboe::AudioStream * oboeStream)656 void ActivityEcho::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
657     if (isInput) {
658         mFullDuplexEcho->setInputStream(oboeStream);
659     } else {
660         mFullDuplexEcho->setOutputStream(oboeStream);
661     }
662 }
663 
664 // ======================================================================= ActivityRoundTripLatency
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)665 void ActivityRoundTripLatency::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
666     ActivityFullDuplex::configureBuilder(isInput, builder);
667 
668     if (mFullDuplexLatency.get() == nullptr) {
669         mFullDuplexLatency = std::make_unique<FullDuplexAnalyzer>(mLatencyAnalyzer.get());
670     }
671     if (!isInput) {
672         // only output uses a callback, input is polled
673         builder.setCallback((oboe::AudioStreamCallback *) &oboeCallbackProxy);
674         oboeCallbackProxy.setDataCallback(mFullDuplexLatency.get());
675     }
676 }
677 
finishOpen(bool isInput,AudioStream * oboeStream)678 void ActivityRoundTripLatency::finishOpen(bool isInput, AudioStream *oboeStream) {
679     if (isInput) {
680         mFullDuplexLatency->setInputStream(oboeStream);
681         mFullDuplexLatency->setRecording(mRecording.get());
682     } else {
683         mFullDuplexLatency->setOutputStream(oboeStream);
684     }
685 }
686 
687 // The timestamp latency is the difference between the input
688 // and output times for a specific frame.
689 // Start with the position and time from an input timestamp.
690 // Map the input position to the corresponding position in output
691 // and calculate its time.
692 // Use the difference between framesWritten and framesRead to
693 // convert input positions to output positions.
measureTimestampLatency()694 jdouble ActivityRoundTripLatency::measureTimestampLatency() {
695     if (!mFullDuplexLatency->isWriteReadDeltaValid()) return -1.0;
696 
697     int64_t writeReadDelta = mFullDuplexLatency->getWriteReadDelta();
698     auto inputTimestampResult = mFullDuplexLatency->getInputStream()->getTimestamp(CLOCK_MONOTONIC);
699     if (!inputTimestampResult) return -1.0;
700     auto outputTimestampResult = mFullDuplexLatency->getOutputStream()->getTimestamp(CLOCK_MONOTONIC);
701     if (!outputTimestampResult) return -1.0;
702 
703     int64_t inputPosition = inputTimestampResult.value().position;
704     int64_t inputTimeNanos = inputTimestampResult.value().timestamp;
705     int64_t ouputPosition = outputTimestampResult.value().position;
706     int64_t outputTimeNanos = outputTimestampResult.value().timestamp;
707 
708     // Map input frame position to the corresponding output frame.
709     int64_t mappedPosition = inputPosition + writeReadDelta;
710     // Calculate when that frame will play.
711     int32_t sampleRate = mFullDuplexLatency->getOutputStream()->getSampleRate();
712     int64_t mappedTimeNanos = outputTimeNanos + ((mappedPosition - ouputPosition) * 1e9) / sampleRate;
713 
714     // Latency is the difference in time between when a frame was recorded and
715     // when its corresponding echo was played.
716     return (mappedTimeNanos - inputTimeNanos) * 1.0e-6; // convert nanos to millis
717 }
718 
719 // ======================================================================= ActivityGlitches
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)720 void ActivityGlitches::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
721     ActivityFullDuplex::configureBuilder(isInput, builder);
722 
723     if (mFullDuplexGlitches.get() == nullptr) {
724         mFullDuplexGlitches = std::make_unique<FullDuplexAnalyzer>(&mGlitchAnalyzer);
725     }
726     if (!isInput) {
727         // only output uses a callback, input is polled
728         builder.setCallback((oboe::AudioStreamCallback *) &oboeCallbackProxy);
729         oboeCallbackProxy.setDataCallback(mFullDuplexGlitches.get());
730     }
731 }
732 
finishOpen(bool isInput,oboe::AudioStream * oboeStream)733 void ActivityGlitches::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
734     if (isInput) {
735         mFullDuplexGlitches->setInputStream(oboeStream);
736         mFullDuplexGlitches->setRecording(mRecording.get());
737     } else {
738         mFullDuplexGlitches->setOutputStream(oboeStream);
739     }
740 }
741 
742 // ======================================================================= ActivityDataPath
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)743 void ActivityDataPath::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
744     ActivityFullDuplex::configureBuilder(isInput, builder);
745 
746     if (mFullDuplexDataPath.get() == nullptr) {
747         mFullDuplexDataPath = std::make_unique<FullDuplexAnalyzer>(&mDataPathAnalyzer);
748     }
749     if (!isInput) {
750         // only output uses a callback, input is polled
751         builder.setCallback((oboe::AudioStreamCallback *) &oboeCallbackProxy);
752         oboeCallbackProxy.setDataCallback(mFullDuplexDataPath.get());
753     }
754 }
755 
finishOpen(bool isInput,oboe::AudioStream * oboeStream)756 void ActivityDataPath::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
757     if (isInput) {
758         mFullDuplexDataPath->setInputStream(oboeStream);
759         mFullDuplexDataPath->setRecording(mRecording.get());
760     } else {
761         mFullDuplexDataPath->setOutputStream(oboeStream);
762     }
763 }
764 
765 // =================================================================== ActivityTestDisconnect
close(int32_t streamIndex)766 void ActivityTestDisconnect::close(int32_t streamIndex) {
767     ActivityContext::close(streamIndex);
768     mSinkFloat.reset();
769 }
770 
configureAfterOpen()771 void ActivityTestDisconnect::configureAfterOpen() {
772     std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
773     std::shared_ptr<oboe::AudioStream> inputStream = getInputStream();
774     if (outputStream) {
775         mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
776         sineOscillator = std::make_unique<SineOscillator>();
777         monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
778 
779         sineOscillator->setSampleRate(outputStream->getSampleRate());
780         sineOscillator->frequency.setValue(440.0);
781         sineOscillator->amplitude.setValue(AMPLITUDE_SINE);
782         sineOscillator->output.connect(&(monoToMulti->input));
783 
784         monoToMulti->output.connect(&(mSinkFloat->input));
785         mSinkFloat->pullReset();
786         audioStreamGateway.setAudioSink(mSinkFloat);
787     } else if (inputStream) {
788         audioStreamGateway.setAudioSink(nullptr);
789     }
790     oboeCallbackProxy.setDataCallback(&audioStreamGateway);
791 }
792 
793