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