blob: 2f2e7e5aba932c9c559daf00b8c6fa17ab82c4dd [file] [log] [blame]
Phil Burk0433d8f2018-11-21 16:41:25 -08001/*
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
Phil Burk327361c2019-04-13 14:45:27 -070017#include <fstream>
18#include <iostream>
19#include <vector>
20
21#include "util/WaveFileWriter.h"
22
Phil Burk0433d8f2018-11-21 16:41:25 -080023#include "NativeAudioContext.h"
24
Phil Burkbbde5bb2019-09-02 16:00:18 -070025using namespace oboe;
26
Phil Burk6c16ce92019-03-05 16:07:38 -080027static oboe::AudioApi convertNativeApiToAudioApi(int nativeApi) {
28 switch (nativeApi) {
29 default:
30 case NATIVE_MODE_UNSPECIFIED:
31 return oboe::AudioApi::Unspecified;
32 case NATIVE_MODE_AAUDIO:
33 return oboe::AudioApi::AAudio;
34 case NATIVE_MODE_OPENSLES:
35 return oboe::AudioApi::OpenSLES;
36 }
37}
38
Phil Burk9f3a33f2019-04-17 16:03:14 -070039class MyOboeOutputStream : public WaveFileOutputStream {
40public:
41 void write(uint8_t b) override {
42 mData.push_back(b);
43 }
44
45 int32_t length() {
46 return (int32_t) mData.size();
47 }
48
49 uint8_t *getData() {
50 return mData.data();
51 }
52
53private:
54 std::vector<uint8_t> mData;
55};
56
Phil Burkbd76d6a2019-06-01 08:48:03 -070057bool ActivityContext::mUseCallback = true;
Phil Burk0af43bb2019-03-12 14:11:38 -070058int ActivityContext::callbackSize = 0;
59
60oboe::AudioStream * ActivityContext::getOutputStream() {
Phil Burk21de29d2019-04-03 12:06:30 -070061 for (auto entry : mOboeStreams) {
62 oboe::AudioStream *oboeStream = entry.second;
63 if (oboeStream->getDirection() == oboe::Direction::Output) {
64 return oboeStream;
Phil Burk0af43bb2019-03-12 14:11:38 -070065 }
66 }
67 return nullptr;
Phil Burke5985ed2018-12-06 11:38:30 -080068}
69
Phil Burk0af43bb2019-03-12 14:11:38 -070070oboe::AudioStream * ActivityContext::getInputStream() {
Phil Burk21de29d2019-04-03 12:06:30 -070071 for (auto entry : mOboeStreams) {
72 oboe::AudioStream *oboeStream = entry.second;
Phil Burk0af43bb2019-03-12 14:11:38 -070073 if (oboeStream != nullptr) {
74 if (oboeStream->getDirection() == oboe::Direction::Input) {
75 return oboeStream;
76 }
77 }
78 }
79 return nullptr;
80}
81
82void ActivityContext::freeStreamIndex(int32_t streamIndex) {
Phil Burk21de29d2019-04-03 12:06:30 -070083 mOboeStreams.erase(streamIndex);
Phil Burk0af43bb2019-03-12 14:11:38 -070084}
85
86int32_t ActivityContext::allocateStreamIndex() {
Phil Burk21de29d2019-04-03 12:06:30 -070087 return mNextStreamHandle++;
Phil Burk0af43bb2019-03-12 14:11:38 -070088}
89
90void ActivityContext::close(int32_t streamIndex) {
Phil Burk0433d8f2018-11-21 16:41:25 -080091 stopBlockingIOThread();
Phil Burk21de29d2019-04-03 12:06:30 -070092 oboe::AudioStream *oboeStream = getStream(streamIndex);
93 if (oboeStream != nullptr) {
94 oboeStream->close();
Phil Burk6c16ce92019-03-05 16:07:38 -080095 freeStreamIndex(streamIndex);
Phil Burk59273982019-08-29 09:10:37 -070096 LOGD("ActivityContext::%s() delete stream %d ", __func__, streamIndex);
97 delete oboeStream;
Phil Burk0433d8f2018-11-21 16:41:25 -080098 }
Phil Burk0433d8f2018-11-21 16:41:25 -080099}
100
Phil Burk0af43bb2019-03-12 14:11:38 -0700101bool ActivityContext::isMMapUsed(int32_t streamIndex) {
Phil Burk6c16ce92019-03-05 16:07:38 -0800102 oboe::AudioStream *oboeStream = getStream(streamIndex);
Phil Burk2beb2c72019-09-05 18:51:14 -0700103 if (oboeStream == nullptr) return false;
104 if (oboeStream->getAudioApi() != AudioApi::AAudio) return false;
105 return AAudioExtensions::getInstance().isMMapUsed(oboeStream);
Phil Burk0433d8f2018-11-21 16:41:25 -0800106}
107
Phil Burk0af43bb2019-03-12 14:11:38 -0700108oboe::Result ActivityContext::pause() {
Phil Burk59273982019-08-29 09:10:37 -0700109 LOGD("ActivityContext::%s() called", __func__);
Phil Burk0af43bb2019-03-12 14:11:38 -0700110 oboe::Result result = oboe::Result::OK;
111 stopBlockingIOThread();
Phil Burk21de29d2019-04-03 12:06:30 -0700112 for (auto entry : mOboeStreams) {
113 oboe::AudioStream *oboeStream = entry.second;
114 result = oboeStream->requestPause();
115 printScheduler();
Phil Burk0af43bb2019-03-12 14:11:38 -0700116 }
117 return result;
Phil Burk0433d8f2018-11-21 16:41:25 -0800118}
119
Phil Burk0af43bb2019-03-12 14:11:38 -0700120oboe::Result ActivityContext::stopAllStreams() {
121 oboe::Result result = oboe::Result::OK;
122 stopBlockingIOThread();
Phil Burk21de29d2019-04-03 12:06:30 -0700123 LOGD("ActivityContext::stopAllStreams() called");
124 for (auto entry : mOboeStreams) {
125 LOGD("ActivityContext::stopAllStreams() handle = %d, stream %p",
126 entry.first, entry.second);
127 oboe::AudioStream *oboeStream = entry.second;
128 result = oboeStream->requestStop();
129 printScheduler();
Phil Burk0af43bb2019-03-12 14:11:38 -0700130 }
131 return result;
132}
133
134
135void ActivityContext::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
136 // We needed the proxy because we did not know the channelCount when we setup the Builder.
Phil Burkbd76d6a2019-06-01 08:48:03 -0700137 if (mUseCallback) {
Phil Burk0af43bb2019-03-12 14:11:38 -0700138 LOGD("ActivityContext::open() set callback to use oboeCallbackProxy, size = %d",
139 callbackSize);
140 builder.setCallback(&oboeCallbackProxy);
141 builder.setFramesPerCallback(callbackSize);
142 }
143}
144
Phil Burk2beb2c72019-09-05 18:51:14 -0700145int ActivityContext::open(jint nativeApi,
146 jint sampleRate,
147 jint channelCount,
148 jint format,
149 jint sharingMode,
150 jint performanceMode,
151 jint deviceId,
152 jint sessionId,
153 jint framesPerBurst,
154 jboolean channelConversionAllowed,
155 jboolean formatConversionAllowed,
156 jint rateConversionQuality,
157 jboolean isMMap,
158 jboolean isInput) {
Phil Burk0af43bb2019-03-12 14:11:38 -0700159
160 oboe::AudioApi audioApi = oboe::AudioApi::Unspecified;
161 switch (nativeApi) {
162 case NATIVE_MODE_UNSPECIFIED:
163 case NATIVE_MODE_AAUDIO:
164 case NATIVE_MODE_OPENSLES:
165 audioApi = convertNativeApiToAudioApi(nativeApi);
166 break;
167 default:
168 return (jint) oboe::Result::ErrorOutOfRange;
169 }
170
171 int32_t streamIndex = allocateStreamIndex();
172 if (streamIndex < 0) {
173 LOGE("ActivityContext::open() stream array full");
174 return (jint) oboe::Result::ErrorNoFreeHandles;
175 }
176
177 if (channelCount < 0 || channelCount > 256) {
178 LOGE("ActivityContext::open() channels out of range");
179 return (jint) oboe::Result::ErrorOutOfRange;
180 }
181
182 // Create an audio output stream.
Phil Burk0af43bb2019-03-12 14:11:38 -0700183 oboe::AudioStreamBuilder builder;
184 builder.setChannelCount(channelCount)
185 ->setDirection(isInput ? oboe::Direction::Input : oboe::Direction::Output)
186 ->setSharingMode((oboe::SharingMode) sharingMode)
187 ->setPerformanceMode((oboe::PerformanceMode) performanceMode)
188 ->setDeviceId(deviceId)
189 ->setSessionId((oboe::SessionId) sessionId)
190 ->setSampleRate(sampleRate)
Phil Burk301b0662019-06-06 11:36:30 -0700191 ->setFormat((oboe::AudioFormat) format)
192 ->setChannelConversionAllowed(channelConversionAllowed)
Phil Burk59273982019-08-29 09:10:37 -0700193 ->setFormatConversionAllowed(formatConversionAllowed)
Phil Burk301b0662019-06-06 11:36:30 -0700194 ->setSampleRateConversionQuality((oboe::SampleRateConversionQuality) rateConversionQuality)
195 ;
Phil Burk0af43bb2019-03-12 14:11:38 -0700196
197 configureBuilder(isInput, builder);
198
199 if (audioApi == oboe::AudioApi::OpenSLES) {
200 builder.setFramesPerCallback(framesPerBurst);
201 }
202 builder.setAudioApi(audioApi);
203
Phil Burk2beb2c72019-09-05 18:51:14 -0700204 // Temporarily set the AAudio MMAP policy to disable MMAP or not.
205 bool oldMMapEnabled = AAudioExtensions::getInstance().isMMapEnabled();
206 AAudioExtensions::getInstance().setMMapEnabled(isMMap);
207
Phil Burk0af43bb2019-03-12 14:11:38 -0700208 // Open a stream based on the builder settings.
209 oboe::AudioStream *oboeStream = nullptr;
210 oboe::Result result = builder.openStream(&oboeStream);
211 LOGD("ActivityContext::open() builder.openStream() returned %d", result);
Phil Burk2beb2c72019-09-05 18:51:14 -0700212 AAudioExtensions::getInstance().setMMapEnabled(oldMMapEnabled);
Phil Burk0af43bb2019-03-12 14:11:38 -0700213 if (result != oboe::Result::OK) {
214 delete oboeStream;
215 oboeStream = nullptr;
216 freeStreamIndex(streamIndex);
217 streamIndex = -1;
218 } else {
219 mOboeStreams[streamIndex] = oboeStream;
220
221 mChannelCount = oboeStream->getChannelCount(); // FIXME store per stream
222 mFramesPerBurst = oboeStream->getFramesPerBurst();
223 mSampleRate = oboeStream->getSampleRate();
224
Phil Burk81ca4742019-04-24 07:53:36 -0700225 createRecording();
226
Phil Burk0af43bb2019-03-12 14:11:38 -0700227 finishOpen(isInput, oboeStream);
228 }
229
Phil Burk2beb2c72019-09-05 18:51:14 -0700230
Phil Burkbd76d6a2019-06-01 08:48:03 -0700231 if (!mUseCallback) {
Phil Burk0af43bb2019-03-12 14:11:38 -0700232 int numSamples = getFramesPerBlock() * mChannelCount;
233 dataBuffer = std::make_unique<float[]>(numSamples);
234 }
235
Phil Burk9f3a33f2019-04-17 16:03:14 -0700236
Phil Burk0af43bb2019-03-12 14:11:38 -0700237 return ((int)result < 0) ? (int)result : streamIndex;
238}
239
240oboe::Result ActivityContext::start() {
241 LOGD("ActivityContext: %s() called", __func__);
242 oboe::Result result = oboe::Result::OK;
243 oboe::AudioStream *inputStream = getInputStream();
244 oboe::AudioStream *outputStream = getOutputStream();
245 if (inputStream == nullptr && outputStream == nullptr) {
246 LOGD("%s() - no streams defined", __func__);
247 return oboe::Result::ErrorInvalidState; // not open
248 }
249
250 stop();
251
252 configureForStart();
253
254 result = startStreams();
255
Phil Burkbd76d6a2019-06-01 08:48:03 -0700256 if (!mUseCallback && result == oboe::Result::OK) {
Phil Burk0af43bb2019-03-12 14:11:38 -0700257 LOGD("start thread for blocking I/O");
258 // Instead of using the callback, start a thread that writes the stream.
259 threadEnabled.store(true);
260 dataThread = new std::thread(threadCallback, this);
261 }
262
263 return result;
264}
265
Phil Burk9f3a33f2019-04-17 16:03:14 -0700266int32_t ActivityContext::saveWaveFile(const char *filename) {
267 if (mRecording == nullptr) {
Phil Burk57a35eb2019-07-31 10:46:18 -0700268 LOGW("ActivityContext::saveWaveFile(%s) but no recording!", filename);
Phil Burk9f3a33f2019-04-17 16:03:14 -0700269 return -1;
270 }
Phil Burk57a35eb2019-07-31 10:46:18 -0700271 if (mRecording->getSizeInFrames() == 0) {
272 LOGW("ActivityContext::saveWaveFile(%s) but no frames!", filename);
273 return -2;
274 }
Phil Burk9f3a33f2019-04-17 16:03:14 -0700275 MyOboeOutputStream outStream;
276 WaveFileWriter writer(&outStream);
277
278 writer.setFrameRate(mSampleRate);
279 writer.setSamplesPerFrame(mRecording->getChannelCount());
280 writer.setBitsPerSample(24);
Phil Burk81ca4742019-04-24 07:53:36 -0700281 float buffer[mRecording->getChannelCount()];
Phil Burk9f3a33f2019-04-17 16:03:14 -0700282 // Read samples from start to finish.
283 mRecording->rewind();
Phil Burk81ca4742019-04-24 07:53:36 -0700284 for (int32_t frameIndex = 0; frameIndex < mRecording->getSizeInFrames(); frameIndex++) {
285 mRecording->read(buffer, 1 /* numFrames */);
286 for (int32_t i = 0; i < mRecording->getChannelCount(); i++) {
287 writer.write(buffer[i]);
288 }
Phil Burk9f3a33f2019-04-17 16:03:14 -0700289 }
290 writer.close();
291
Phil Burk57a35eb2019-07-31 10:46:18 -0700292 if (outStream.length() > 0) {
293 auto myfile = std::ofstream(filename, std::ios::out | std::ios::binary);
294 myfile.write((char *) outStream.getData(), outStream.length());
295 myfile.close();
296 }
Phil Burk9f3a33f2019-04-17 16:03:14 -0700297
298 return outStream.length();
299}
300
Phil Burk0af43bb2019-03-12 14:11:38 -0700301// =================================================================== ActivityTestOutput
302void ActivityTestOutput::close(int32_t streamIndex) {
303 ActivityContext::close(streamIndex);
304 manyToMulti.reset(nullptr);
305 monoToMulti.reset(nullptr);
306 mSinkFloat.reset();
307 mSinkI16.reset();
308}
309
Phil Burk0af43bb2019-03-12 14:11:38 -0700310void ActivityTestOutput::setChannelEnabled(int channelIndex, bool enabled) {
Phil Burke5985ed2018-12-06 11:38:30 -0800311 if (manyToMulti == nullptr) {
312 return;
313 }
314 if (enabled) {
Phil Burk0a784e62019-07-20 12:43:15 -0700315 switch (mSignalType) {
316 case SignalType::Sine:
317 sineOscillators[channelIndex].frequency.disconnect();
Phil Burk0817a2b2019-01-03 14:50:18 -0800318 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
319 break;
Phil Burk0a784e62019-07-20 12:43:15 -0700320 case SignalType::Sawtooth:
Phil Burk0817a2b2019-01-03 14:50:18 -0800321 sawtoothOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
322 break;
Phil Burk0a784e62019-07-20 12:43:15 -0700323 case SignalType::FreqSweep:
324 mLinearShape.output.connect(&sineOscillators[channelIndex].frequency);
325 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
326 break;
327 case SignalType::PitchSweep:
328 mExponentialShape.output.connect(&sineOscillators[channelIndex].frequency);
329 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
330 break;
Phil Burk0817a2b2019-01-03 14:50:18 -0800331 default:
332 break;
333 }
Phil Burke5985ed2018-12-06 11:38:30 -0800334 } else {
335 manyToMulti->inputs[channelIndex]->disconnect();
336 }
337}
338
Phil Burk0af43bb2019-03-12 14:11:38 -0700339void ActivityTestOutput::configureForStart() {
Phil Burk71d97e92019-03-08 12:50:01 -0800340 manyToMulti = std::make_unique<ManyToMultiConverter>(mChannelCount);
Phil Burk71d97e92019-03-08 12:50:01 -0800341
342 mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
343 mSinkI16 = std::make_unique<SinkI16>(mChannelCount);
344
Phil Burk71d97e92019-03-08 12:50:01 -0800345 oboe::AudioStream *outputStream = getOutputStream();
Phil Burk0a784e62019-07-20 12:43:15 -0700346
347 mTriangleOscillator.setSampleRate(outputStream->getSampleRate());
348 mTriangleOscillator.frequency.setValue(1.0/kSweepPeriod);
349 mTriangleOscillator.amplitude.setValue(1.0);
350 mTriangleOscillator.setPhase(-1.0);
351
352 mLinearShape.setMinimum(0.0);
353 mLinearShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
354
355 mExponentialShape.setMinimum(110.0);
356 mExponentialShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
357
358 mTriangleOscillator.output.connect(&(mLinearShape.input));
359 mTriangleOscillator.output.connect(&(mExponentialShape.input));
Phil Burk0af43bb2019-03-12 14:11:38 -0700360 {
Phil Burk0a784e62019-07-20 12:43:15 -0700361 double frequency = 330.0;
Phil Burk0af43bb2019-03-12 14:11:38 -0700362 for (int i = 0; i < mChannelCount; i++) {
363 sineOscillators[i].setSampleRate(outputStream->getSampleRate());
364 sineOscillators[i].frequency.setValue(frequency);
365 frequency *= 4.0 / 3.0; // each sine is at a higher frequency
366 sineOscillators[i].amplitude.setValue(AMPLITUDE_SINE);
Phil Burk0a784e62019-07-20 12:43:15 -0700367 setChannelEnabled(i, true);
Phil Burk0af43bb2019-03-12 14:11:38 -0700368 }
Phil Burk71d97e92019-03-08 12:50:01 -0800369 }
370
Phil Burk0af43bb2019-03-12 14:11:38 -0700371 manyToMulti->output.connect(&(mSinkFloat.get()->input));
372 manyToMulti->output.connect(&(mSinkI16.get()->input));
Phil Burk71d97e92019-03-08 12:50:01 -0800373
Phil Burkf67a97f2019-05-30 12:48:17 -0700374 // Clear framePosition in sine oscillators.
375 mSinkFloat->pullReset();
376 mSinkI16->pullReset();
377
Phil Burk0af43bb2019-03-12 14:11:38 -0700378 configureStreamGateway();
Phil Burk71d97e92019-03-08 12:50:01 -0800379}
380
Phil Burk0af43bb2019-03-12 14:11:38 -0700381void ActivityTestOutput::configureStreamGateway() {
382 oboe::AudioStream *outputStream = getOutputStream();
383 if (outputStream->getFormat() == oboe::AudioFormat::I16) {
384 audioStreamGateway.setAudioSink(mSinkI16);
385 } else if (outputStream->getFormat() == oboe::AudioFormat::Float) {
386 audioStreamGateway.setAudioSink(mSinkFloat);
387 }
388
Phil Burkbd76d6a2019-06-01 08:48:03 -0700389 if (mUseCallback) {
Phil Burk0af43bb2019-03-12 14:11:38 -0700390 oboeCallbackProxy.setCallback(&audioStreamGateway);
391 }
392
393 // Set starting size of buffer.
394 constexpr int kDefaultNumBursts = 2; // "double buffer"
395 int32_t numBursts = kDefaultNumBursts;
396 // callbackSize is used for both callbacks and blocking write
397 numBursts = (callbackSize <= mFramesPerBurst)
398 ? kDefaultNumBursts
399 : ((callbackSize * kDefaultNumBursts) + mFramesPerBurst - 1)
400 / mFramesPerBurst;
401 outputStream->setBufferSizeInFrames(numBursts * mFramesPerBurst);
402
403}
404
405void ActivityTestOutput::runBlockingIO() {
Phil Burk0433d8f2018-11-21 16:41:25 -0800406 int32_t framesPerBlock = getFramesPerBlock();
407 oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
Phil Burk6c16ce92019-03-05 16:07:38 -0800408
Phil Burk0af43bb2019-03-12 14:11:38 -0700409 oboe::AudioStream *oboeStream = getOutputStream();
Phil Burk6c16ce92019-03-05 16:07:38 -0800410 if (oboeStream == nullptr) {
411 LOGE("%s() : no stream found\n", __func__);
412 return;
413 }
414
Phil Burk0433d8f2018-11-21 16:41:25 -0800415 while (threadEnabled.load()
416 && callbackResult == oboe::DataCallbackResult::Continue) {
Phil Burk0af43bb2019-03-12 14:11:38 -0700417 // generate output by calling the callback
418 callbackResult = audioStreamGateway.onAudioReady(oboeStream,
Phil Burk0433d8f2018-11-21 16:41:25 -0800419 dataBuffer.get(),
Phil Burk0af43bb2019-03-12 14:11:38 -0700420 framesPerBlock);
Phil Burk0433d8f2018-11-21 16:41:25 -0800421
Phil Burk0af43bb2019-03-12 14:11:38 -0700422 auto result = oboeStream->write(dataBuffer.get(),
423 framesPerBlock,
424 NANOS_PER_SECOND);
Phil Burk0433d8f2018-11-21 16:41:25 -0800425
Phil Burk0af43bb2019-03-12 14:11:38 -0700426 if (!result) {
427 LOGE("%s() returned %s\n", __func__, convertToText(result.error()));
428 break;
429 }
430 int32_t framesWritten = result.value();
431 if (framesWritten < framesPerBlock) {
432 LOGE("%s() : write() wrote %d of %d\n", __func__, framesWritten, framesPerBlock);
433 break;
Phil Burk0433d8f2018-11-21 16:41:25 -0800434 }
435 }
Phil Burk0af43bb2019-03-12 14:11:38 -0700436}
Phil Burk0433d8f2018-11-21 16:41:25 -0800437
Phil Burk0af43bb2019-03-12 14:11:38 -0700438// ======================================================================= ActivityTestInput
439void ActivityTestInput::configureForStart() {
440 mInputAnalyzer.reset();
Phil Burkbd76d6a2019-06-01 08:48:03 -0700441 if (mUseCallback) {
Phil Burk0af43bb2019-03-12 14:11:38 -0700442 oboeCallbackProxy.setCallback(&mInputAnalyzer);
443 }
Phil Burk0af43bb2019-03-12 14:11:38 -0700444 mInputAnalyzer.setRecording(mRecording.get());
445}
446
447void ActivityTestInput::runBlockingIO() {
448 int32_t framesPerBlock = getFramesPerBlock();
449 oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
450
451 oboe::AudioStream *oboeStream = getInputStream();
452 if (oboeStream == nullptr) {
453 LOGE("%s() : no stream found\n", __func__);
454 return;
455 }
456
457 while (threadEnabled.load()
458 && callbackResult == oboe::DataCallbackResult::Continue) {
Phil Burkbbde5bb2019-09-02 16:00:18 -0700459
460 // Avoid glitches by waiting until there is extra data in the FIFO.
461 auto err = oboeStream->waitForAvailableFrames(mMinimumFramesBeforeRead, kNanosPerSecond);
462 if (!err) break;
463
Phil Burk0af43bb2019-03-12 14:11:38 -0700464 // read from input
465 auto result = oboeStream->read(dataBuffer.get(),
466 framesPerBlock,
467 NANOS_PER_SECOND);
468 if (!result) {
469 LOGE("%s() : read() returned %s\n", __func__, convertToText(result.error()));
470 break;
471 }
472 int32_t framesRead = result.value();
473 if (framesRead < framesPerBlock) { // timeout?
474 LOGE("%s() : read() read %d of %d\n", __func__, framesRead, framesPerBlock);
475 break;
476 }
477
478 // analyze input
479 callbackResult = mInputAnalyzer.onAudioReady(oboeStream,
480 dataBuffer.get(),
481 framesRead);
482 }
483}
484
485oboe::Result ActivityRecording::stopPlayback() {
486 LOGD("ActivityRecording::%s() called", __func__);
487 oboe::Result result = oboe::Result::OK;
488 if (playbackStream != nullptr) {
489 result = playbackStream->requestStop();
490 playbackStream->close();
491 mPlayRecordingCallback.setRecording(nullptr);
492 delete playbackStream;
493 playbackStream = nullptr;
494 }
495 return result;
496}
497
498oboe::Result ActivityRecording::startPlayback() {
499 stop();
Phil Burkbd76d6a2019-06-01 08:48:03 -0700500 LOGD("ActivityRecording::%s() called, mSampleRate = %d", __func__, mSampleRate);
Phil Burk0af43bb2019-03-12 14:11:38 -0700501 oboe::AudioStreamBuilder builder;
502 builder.setChannelCount(mChannelCount)
503 ->setSampleRate(mSampleRate)
504 ->setFormat(oboe::AudioFormat::Float)
505 ->setCallback(&mPlayRecordingCallback)
506 ->setAudioApi(oboe::AudioApi::OpenSLES);
507 oboe::Result result = builder.openStream(&playbackStream);
508 LOGD("ActivityRecording::%s() openStream() returned %d", __func__, result);
509 if (result != oboe::Result::OK) {
510 delete playbackStream;
511 playbackStream = nullptr;
512 } else if (playbackStream != nullptr) {
513 if (mRecording != nullptr) {
514 mRecording->rewind();
515 mPlayRecordingCallback.setRecording(mRecording.get());
516 result = playbackStream->requestStart();
517 }
518 }
519 return result;
520}
521
Phil Burk0af43bb2019-03-12 14:11:38 -0700522// ======================================================================= ActivityTapToTone
523void ActivityTapToTone::configureForStart() {
524 monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
525
526 mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
527 mSinkI16 = std::make_unique<SinkI16>(mChannelCount);
528
529 oboe::AudioStream *outputStream = getOutputStream();
530 sawPingGenerator.setSampleRate(outputStream->getSampleRate());
531 sawPingGenerator.frequency.setValue(FREQUENCY_SAW_PING);
532 sawPingGenerator.amplitude.setValue(AMPLITUDE_SAW_PING);
533
534 sawPingGenerator.output.connect(&(monoToMulti->input));
535 monoToMulti->output.connect(&(mSinkFloat.get()->input));
536 monoToMulti->output.connect(&(mSinkI16.get()->input));
537
538 sawPingGenerator.setEnabled(false);
539 configureStreamGateway();
540}
541
Phil Burk42c9f8a2019-03-20 14:19:30 -0700542// ======================================================================= ActivityRoundTripLatency
543void ActivityFullDuplex::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
544 if (isInput) {
545 // Ideally the output streams should be opened first.
546 oboe::AudioStream *outputStream = getOutputStream();
547 if (outputStream != nullptr) {
548 // Make sure the capacity is bigger than two bursts.
549 int32_t burst = outputStream->getFramesPerBurst();
550 builder.setBufferCapacityInFrames(2 * burst);
551 }
552 }
553}
554
Phil Burk0af43bb2019-03-12 14:11:38 -0700555// ======================================================================= ActivityEcho
556void ActivityEcho::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
Phil Burk42c9f8a2019-03-20 14:19:30 -0700557 ActivityFullDuplex::configureBuilder(isInput, builder);
558
Phil Burk0af43bb2019-03-12 14:11:38 -0700559 if (mFullDuplexEcho.get() == nullptr) {
560 mFullDuplexEcho = std::make_unique<FullDuplexEcho>();
561 }
562 // only output uses a callback, input is polled
563 if (!isInput) {
564 builder.setCallback(mFullDuplexEcho.get());
565 }
566}
567
568void ActivityEcho::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
569 if (isInput) {
570 mFullDuplexEcho->setInputStream(oboeStream);
571 } else {
572 mFullDuplexEcho->setOutputStream(oboeStream);
573 }
Phil Burk0433d8f2018-11-21 16:41:25 -0800574}
Phil Burk80d83d82019-03-15 12:03:37 -0700575
576// ======================================================================= ActivityRoundTripLatency
577void ActivityRoundTripLatency::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
Phil Burk42c9f8a2019-03-20 14:19:30 -0700578 ActivityFullDuplex::configureBuilder(isInput, builder);
579
Phil Burk80d83d82019-03-15 12:03:37 -0700580 if (mFullDuplexLatency.get() == nullptr) {
581 mFullDuplexLatency = std::make_unique<FullDuplexLatency>();
582 }
Phil Burk80d83d82019-03-15 12:03:37 -0700583 if (!isInput) {
Phil Burk42c9f8a2019-03-20 14:19:30 -0700584 // only output uses a callback, input is polled
Phil Burk80d83d82019-03-15 12:03:37 -0700585 builder.setCallback(mFullDuplexLatency.get());
586 }
587}
588
589void ActivityRoundTripLatency::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
590 if (isInput) {
591 mFullDuplexLatency->setInputStream(oboeStream);
Phil Burk57a35eb2019-07-31 10:46:18 -0700592 mFullDuplexLatency->setRecording(mRecording.get());
Phil Burk80d83d82019-03-15 12:03:37 -0700593 } else {
594 mFullDuplexLatency->setOutputStream(oboeStream);
595 }
596}
Phil Burk7386a832019-03-21 16:07:27 -0700597
598// ======================================================================= ActivityGlitches
599void ActivityGlitches::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
600 ActivityFullDuplex::configureBuilder(isInput, builder);
601
602 if (mFullDuplexGlitches.get() == nullptr) {
603 mFullDuplexGlitches = std::make_unique<FullDuplexGlitches>();
604 }
605 if (!isInput) {
606 // only output uses a callback, input is polled
607 builder.setCallback(mFullDuplexGlitches.get());
608 }
609}
610
611void ActivityGlitches::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
612 if (isInput) {
613 mFullDuplexGlitches->setInputStream(oboeStream);
Phil Burk81ca4742019-04-24 07:53:36 -0700614 mFullDuplexGlitches->setRecording(mRecording.get());
Phil Burk7386a832019-03-21 16:07:27 -0700615 } else {
616 mFullDuplexGlitches->setOutputStream(oboeStream);
617 }
618}