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