• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <fstream>
18 #include <iostream>
19 #include <vector>
20 
21 #include "util/WaveFileWriter.h"
22 
23 #include "NativeAudioContext.h"
24 
25 using namespace oboe;
26 
convertNativeApiToAudioApi(int nativeApi)27 static 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 
39 class MyOboeOutputStream : public WaveFileOutputStream {
40 public:
write(uint8_t b)41     void write(uint8_t b) override {
42         mData.push_back(b);
43     }
44 
length()45     int32_t length() {
46         return (int32_t) mData.size();
47     }
48 
getData()49     uint8_t *getData() {
50         return mData.data();
51     }
52 
53 private:
54     std::vector<uint8_t> mData;
55 };
56 
57 bool ActivityContext::mUseCallback = true;
58 int  ActivityContext::callbackSize = 0;
59 
getOutputStream()60 oboe::AudioStream * ActivityContext::getOutputStream() {
61     for (auto entry : mOboeStreams) {
62         oboe::AudioStream *oboeStream = entry.second.get();
63         if (oboeStream->getDirection() == oboe::Direction::Output) {
64             return oboeStream;
65         }
66     }
67     return nullptr;
68 }
69 
getInputStream()70 oboe::AudioStream * ActivityContext::getInputStream() {
71     for (auto entry : mOboeStreams) {
72         oboe::AudioStream *oboeStream = entry.second.get();
73         if (oboeStream != nullptr) {
74             if (oboeStream->getDirection() == oboe::Direction::Input) {
75                 return oboeStream;
76             }
77         }
78     }
79     return nullptr;
80 }
81 
freeStreamIndex(int32_t streamIndex)82 void ActivityContext::freeStreamIndex(int32_t streamIndex) {
83     mOboeStreams[streamIndex].reset();
84     mOboeStreams.erase(streamIndex);
85 }
86 
allocateStreamIndex()87 int32_t ActivityContext::allocateStreamIndex() {
88     return mNextStreamHandle++;
89 }
90 
close(int32_t streamIndex)91 void ActivityContext::close(int32_t streamIndex) {
92     stopBlockingIOThread();
93     oboe::AudioStream *oboeStream = getStream(streamIndex);
94     if (oboeStream != nullptr) {
95         oboeStream->close();
96         LOGD("ActivityContext::%s() delete stream %d ", __func__, streamIndex);
97         freeStreamIndex(streamIndex);
98     }
99 }
100 
isMMapUsed(int32_t streamIndex)101 bool ActivityContext::isMMapUsed(int32_t streamIndex) {
102     oboe::AudioStream *oboeStream = getStream(streamIndex);
103     if (oboeStream == nullptr) return false;
104     if (oboeStream->getAudioApi() != AudioApi::AAudio) return false;
105     return AAudioExtensions::getInstance().isMMapUsed(oboeStream);
106 }
107 
pause()108 oboe::Result ActivityContext::pause() {
109     oboe::Result result = oboe::Result::OK;
110     stopBlockingIOThread();
111     for (auto entry : mOboeStreams) {
112         oboe::AudioStream *oboeStream = entry.second.get();
113         result = oboeStream->requestPause();
114     }
115     return result;
116 }
117 
stopAllStreams()118 oboe::Result ActivityContext::stopAllStreams() {
119     oboe::Result result = oboe::Result::OK;
120     stopBlockingIOThread();
121     for (auto entry : mOboeStreams) {
122         oboe::AudioStream *oboeStream = entry.second.get();
123         result = oboeStream->requestStop();
124     }
125     return result;
126 }
127 
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)128 void ActivityContext::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
129     // We needed the proxy because we did not know the channelCount when we setup the Builder.
130     if (mUseCallback) {
131         LOGD("ActivityContext::open() set callback to use oboeCallbackProxy, callback size = %d",
132              callbackSize);
133         builder.setCallback(&oboeCallbackProxy);
134         builder.setFramesPerCallback(callbackSize);
135     }
136 }
137 
open(jint nativeApi,jint sampleRate,jint channelCount,jint format,jint sharingMode,jint performanceMode,jint inputPreset,jint deviceId,jint sessionId,jint framesPerBurst,jboolean channelConversionAllowed,jboolean formatConversionAllowed,jint rateConversionQuality,jboolean isMMap,jboolean isInput)138 int ActivityContext::open(jint nativeApi,
139                           jint sampleRate,
140                           jint channelCount,
141                           jint format,
142                           jint sharingMode,
143                           jint performanceMode,
144                           jint inputPreset,
145                           jint deviceId,
146                           jint sessionId,
147                           jint framesPerBurst,
148                           jboolean channelConversionAllowed,
149                           jboolean formatConversionAllowed,
150                           jint rateConversionQuality,
151                           jboolean isMMap,
152                           jboolean isInput) {
153 
154     oboe::AudioApi audioApi = oboe::AudioApi::Unspecified;
155     switch (nativeApi) {
156         case NATIVE_MODE_UNSPECIFIED:
157         case NATIVE_MODE_AAUDIO:
158         case NATIVE_MODE_OPENSLES:
159             audioApi = convertNativeApiToAudioApi(nativeApi);
160             break;
161         default:
162             return (jint) oboe::Result::ErrorOutOfRange;
163     }
164 
165     int32_t streamIndex = allocateStreamIndex();
166     if (streamIndex < 0) {
167         LOGE("ActivityContext::open() stream array full");
168         return (jint) oboe::Result::ErrorNoFreeHandles;
169     }
170 
171     if (channelCount < 0 || channelCount > 256) {
172         LOGE("ActivityContext::open() channels out of range");
173         return (jint) oboe::Result::ErrorOutOfRange;
174     }
175 
176     // Create an audio stream.
177     oboe::AudioStreamBuilder builder;
178     builder.setChannelCount(channelCount)
179             ->setDirection(isInput ? oboe::Direction::Input : oboe::Direction::Output)
180             ->setSharingMode((oboe::SharingMode) sharingMode)
181             ->setPerformanceMode((oboe::PerformanceMode) performanceMode)
182             ->setInputPreset((oboe::InputPreset)inputPreset)
183             ->setDeviceId(deviceId)
184             ->setSessionId((oboe::SessionId) sessionId)
185             ->setSampleRate(sampleRate)
186             ->setFormat((oboe::AudioFormat) format)
187             ->setChannelConversionAllowed(channelConversionAllowed)
188             ->setFormatConversionAllowed(formatConversionAllowed)
189             ->setSampleRateConversionQuality((oboe::SampleRateConversionQuality) rateConversionQuality)
190             ;
191 
192     configureBuilder(isInput, builder);
193 
194     builder.setAudioApi(audioApi);
195 
196     // Temporarily set the AAudio MMAP policy to disable MMAP or not.
197     bool oldMMapEnabled = AAudioExtensions::getInstance().isMMapEnabled();
198     AAudioExtensions::getInstance().setMMapEnabled(isMMap);
199 
200     // Open a stream based on the builder settings.
201     std::shared_ptr<oboe::AudioStream> oboeStream;
202     Result result = builder.openStream(oboeStream);
203     AAudioExtensions::getInstance().setMMapEnabled(oldMMapEnabled);
204     if (result != Result::OK) {
205         freeStreamIndex(streamIndex);
206         streamIndex = -1;
207     } else {
208         mOboeStreams[streamIndex] = oboeStream; // save shared_ptr
209 
210         mChannelCount = oboeStream->getChannelCount(); // FIXME store per stream
211         mFramesPerBurst = oboeStream->getFramesPerBurst();
212         mSampleRate = oboeStream->getSampleRate();
213 
214         createRecording();
215 
216         finishOpen(isInput, oboeStream.get());
217     }
218 
219     if (!mUseCallback) {
220         int numSamples = getFramesPerBlock() * mChannelCount;
221         dataBuffer = std::make_unique<float[]>(numSamples);
222     }
223 
224     return (result != Result::OK) ? (int)result : streamIndex;
225 }
226 
start()227 oboe::Result ActivityContext::start() {
228     oboe::Result result = oboe::Result::OK;
229     oboe::AudioStream *inputStream = getInputStream();
230     oboe::AudioStream *outputStream = getOutputStream();
231     if (inputStream == nullptr && outputStream == nullptr) {
232         LOGD("%s() - no streams defined", __func__);
233         return oboe::Result::ErrorInvalidState; // not open
234     }
235 
236     configureForStart();
237 
238     audioStreamGateway.reset();
239     result = startStreams();
240 
241     if (!mUseCallback && result == oboe::Result::OK) {
242         // Instead of using the callback, start a thread that writes the stream.
243         threadEnabled.store(true);
244         dataThread = new std::thread(threadCallback, this);
245     }
246 
247     return result;
248 }
249 
saveWaveFile(const char * filename)250 int32_t  ActivityContext::saveWaveFile(const char *filename) {
251     if (mRecording == nullptr) {
252         LOGW("ActivityContext::saveWaveFile(%s) but no recording!", filename);
253         return -1;
254     }
255     if (mRecording->getSizeInFrames() == 0) {
256         LOGW("ActivityContext::saveWaveFile(%s) but no frames!", filename);
257         return -2;
258     }
259     MyOboeOutputStream outStream;
260     WaveFileWriter writer(&outStream);
261 
262     writer.setFrameRate(mSampleRate);
263     writer.setSamplesPerFrame(mRecording->getChannelCount());
264     writer.setBitsPerSample(24);
265     float buffer[mRecording->getChannelCount()];
266     // Read samples from start to finish.
267     mRecording->rewind();
268     for (int32_t frameIndex = 0; frameIndex < mRecording->getSizeInFrames(); frameIndex++) {
269         mRecording->read(buffer, 1 /* numFrames */);
270         for (int32_t i = 0; i < mRecording->getChannelCount(); i++) {
271             writer.write(buffer[i]);
272         }
273     }
274     writer.close();
275 
276     if (outStream.length() > 0) {
277         auto myfile = std::ofstream(filename, std::ios::out | std::ios::binary);
278         myfile.write((char *) outStream.getData(), outStream.length());
279         myfile.close();
280     }
281 
282     return outStream.length();
283 }
284 
285 // =================================================================== ActivityTestOutput
close(int32_t streamIndex)286 void ActivityTestOutput::close(int32_t streamIndex) {
287     ActivityContext::close(streamIndex);
288     manyToMulti.reset(nullptr);
289     monoToMulti.reset(nullptr);
290     mSinkFloat.reset();
291     mSinkI16.reset();
292 }
293 
setChannelEnabled(int channelIndex,bool enabled)294 void ActivityTestOutput::setChannelEnabled(int channelIndex, bool enabled) {
295     if (manyToMulti == nullptr) {
296         return;
297     }
298     if (enabled) {
299         switch (mSignalType) {
300             case SignalType::Sine:
301                 sineOscillators[channelIndex].frequency.disconnect();
302                 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
303                 break;
304             case SignalType::Sawtooth:
305                 sawtoothOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
306                 break;
307             case SignalType::FreqSweep:
308                 mLinearShape.output.connect(&sineOscillators[channelIndex].frequency);
309                 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
310                 break;
311             case SignalType::PitchSweep:
312                 mExponentialShape.output.connect(&sineOscillators[channelIndex].frequency);
313                 sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
314                 break;
315             default:
316                 break;
317         }
318     } else {
319         manyToMulti->inputs[channelIndex]->disconnect();
320     }
321 }
322 
configureForStart()323 void ActivityTestOutput::configureForStart() {
324     manyToMulti = std::make_unique<ManyToMultiConverter>(mChannelCount);
325 
326     mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
327     mSinkI16 = std::make_unique<SinkI16>(mChannelCount);
328 
329     oboe::AudioStream *outputStream = getOutputStream();
330 
331     mTriangleOscillator.setSampleRate(outputStream->getSampleRate());
332     mTriangleOscillator.frequency.setValue(1.0/kSweepPeriod);
333     mTriangleOscillator.amplitude.setValue(1.0);
334     mTriangleOscillator.setPhase(-1.0);
335 
336     mLinearShape.setMinimum(0.0);
337     mLinearShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
338 
339     mExponentialShape.setMinimum(110.0);
340     mExponentialShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
341 
342     mTriangleOscillator.output.connect(&(mLinearShape.input));
343     mTriangleOscillator.output.connect(&(mExponentialShape.input));
344     {
345         double frequency = 330.0;
346         for (int i = 0; i < mChannelCount; i++) {
347             sineOscillators[i].setSampleRate(outputStream->getSampleRate());
348             sineOscillators[i].frequency.setValue(frequency);
349             frequency *= 4.0 / 3.0; // each sine is at a higher frequency
350             sineOscillators[i].amplitude.setValue(AMPLITUDE_SINE);
351             setChannelEnabled(i, true);
352         }
353     }
354 
355     manyToMulti->output.connect(&(mSinkFloat.get()->input));
356     manyToMulti->output.connect(&(mSinkI16.get()->input));
357 
358     mSinkFloat->pullReset();
359     mSinkI16->pullReset();
360 
361     configureStreamGateway();
362 }
363 
configureStreamGateway()364 void ActivityTestOutput::configureStreamGateway() {
365     oboe::AudioStream *outputStream = getOutputStream();
366     if (outputStream->getFormat() == oboe::AudioFormat::I16) {
367         audioStreamGateway.setAudioSink(mSinkI16);
368     } else if (outputStream->getFormat() == oboe::AudioFormat::Float) {
369         audioStreamGateway.setAudioSink(mSinkFloat);
370     }
371 
372     if (mUseCallback) {
373         oboeCallbackProxy.setCallback(&audioStreamGateway);
374     }
375 }
376 
runBlockingIO()377 void ActivityTestOutput::runBlockingIO() {
378     int32_t framesPerBlock = getFramesPerBlock();
379     oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
380 
381     oboe::AudioStream *oboeStream = getOutputStream();
382     if (oboeStream == nullptr) {
383         LOGE("%s() : no stream found\n", __func__);
384         return;
385     }
386 
387     while (threadEnabled.load()
388            && callbackResult == oboe::DataCallbackResult::Continue) {
389         // generate output by calling the callback
390         callbackResult = audioStreamGateway.onAudioReady(oboeStream,
391                                                          dataBuffer.get(),
392                                                          framesPerBlock);
393 
394         auto result = oboeStream->write(dataBuffer.get(),
395                                         framesPerBlock,
396                                         NANOS_PER_SECOND);
397 
398         if (!result) {
399             LOGE("%s() returned %s\n", __func__, convertToText(result.error()));
400             break;
401         }
402         int32_t framesWritten = result.value();
403         if (framesWritten < framesPerBlock) {
404             LOGE("%s() : write() wrote %d of %d\n", __func__, framesWritten, framesPerBlock);
405             break;
406         }
407     }
408 }
409 
410 // ======================================================================= ActivityTestInput
configureForStart()411 void ActivityTestInput::configureForStart() {
412     mInputAnalyzer.reset();
413     if (mUseCallback) {
414         oboeCallbackProxy.setCallback(&mInputAnalyzer);
415     }
416     mInputAnalyzer.setRecording(mRecording.get());
417 }
418 
runBlockingIO()419 void ActivityTestInput::runBlockingIO() {
420     int32_t framesPerBlock = getFramesPerBlock();
421     oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
422 
423     oboe::AudioStream *oboeStream = getInputStream();
424     if (oboeStream == nullptr) {
425         LOGE("%s() : no stream found\n", __func__);
426         return;
427     }
428 
429     while (threadEnabled.load()
430            && callbackResult == oboe::DataCallbackResult::Continue) {
431 
432         // Avoid glitches by waiting until there is extra data in the FIFO.
433         auto err = oboeStream->waitForAvailableFrames(mMinimumFramesBeforeRead, kNanosPerSecond);
434         if (!err) break;
435 
436         // read from input
437         auto result = oboeStream->read(dataBuffer.get(),
438                                        framesPerBlock,
439                                        NANOS_PER_SECOND);
440         if (!result) {
441             LOGE("%s() : read() returned %s\n", __func__, convertToText(result.error()));
442             break;
443         }
444         int32_t framesRead = result.value();
445         if (framesRead < framesPerBlock) { // timeout?
446             LOGE("%s() : read() read %d of %d\n", __func__, framesRead, framesPerBlock);
447             break;
448         }
449 
450         // analyze input
451         callbackResult = mInputAnalyzer.onAudioReady(oboeStream,
452                                                      dataBuffer.get(),
453                                                      framesRead);
454     }
455 }
456 
stopPlayback()457 oboe::Result ActivityRecording::stopPlayback() {
458     oboe::Result result = oboe::Result::OK;
459     if (playbackStream != nullptr) {
460         result = playbackStream->requestStop();
461         playbackStream->close();
462         mPlayRecordingCallback.setRecording(nullptr);
463         delete playbackStream;
464         playbackStream = nullptr;
465     }
466     return result;
467 }
468 
startPlayback()469 oboe::Result ActivityRecording::startPlayback() {
470     stop();
471     oboe::AudioStreamBuilder builder;
472     builder.setChannelCount(mChannelCount)
473             ->setSampleRate(mSampleRate)
474             ->setFormat(oboe::AudioFormat::Float)
475             ->setCallback(&mPlayRecordingCallback)
476             ->setAudioApi(oboe::AudioApi::OpenSLES);
477     oboe::Result result = builder.openStream(&playbackStream);
478     if (result != oboe::Result::OK) {
479         delete playbackStream;
480         playbackStream = nullptr;
481     } else if (playbackStream != nullptr) {
482         if (mRecording != nullptr) {
483             mRecording->rewind();
484             mPlayRecordingCallback.setRecording(mRecording.get());
485             result = playbackStream->requestStart();
486         }
487     }
488     return result;
489 }
490 
491 // ======================================================================= ActivityTapToTone
configureForStart()492 void ActivityTapToTone::configureForStart() {
493     monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
494 
495     mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
496     mSinkI16 = std::make_unique<SinkI16>(mChannelCount);
497 
498     oboe::AudioStream *outputStream = getOutputStream();
499     sawPingGenerator.setSampleRate(outputStream->getSampleRate());
500     sawPingGenerator.frequency.setValue(FREQUENCY_SAW_PING);
501     sawPingGenerator.amplitude.setValue(AMPLITUDE_SAW_PING);
502 
503     sawPingGenerator.output.connect(&(monoToMulti->input));
504     monoToMulti->output.connect(&(mSinkFloat.get()->input));
505     monoToMulti->output.connect(&(mSinkI16.get()->input));
506 
507     mSinkFloat->pullReset();
508     mSinkI16->pullReset();
509 
510     sawPingGenerator.setEnabled(false);
511     configureStreamGateway();
512 }
513 
514 // ======================================================================= ActivityRoundTripLatency
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)515 void ActivityFullDuplex::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
516     if (isInput) {
517         // Ideally the output streams should be opened first.
518         oboe::AudioStream *outputStream = getOutputStream();
519         if (outputStream != nullptr) {
520             // Make sure the capacity is bigger than two bursts.
521             int32_t burst = outputStream->getFramesPerBurst();
522             builder.setBufferCapacityInFrames(2 * burst);
523         }
524     }
525 }
526 
527 // ======================================================================= ActivityEcho
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)528 void ActivityEcho::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
529     ActivityFullDuplex::configureBuilder(isInput, builder);
530 
531     if (mFullDuplexEcho.get() == nullptr) {
532         mFullDuplexEcho = std::make_unique<FullDuplexEcho>();
533     }
534     // only output uses a callback, input is polled
535     if (!isInput) {
536         builder.setCallback(mFullDuplexEcho.get());
537     }
538 }
539 
finishOpen(bool isInput,oboe::AudioStream * oboeStream)540 void ActivityEcho::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
541     if (isInput) {
542         mFullDuplexEcho->setInputStream(oboeStream);
543     } else {
544         mFullDuplexEcho->setOutputStream(oboeStream);
545     }
546 }
547 
548 // ======================================================================= ActivityRoundTripLatency
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)549 void ActivityRoundTripLatency::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
550     ActivityFullDuplex::configureBuilder(isInput, builder);
551 
552     if (mFullDuplexLatency.get() == nullptr) {
553         mFullDuplexLatency = std::make_unique<FullDuplexLatency>();
554     }
555     if (!isInput) {
556         // only output uses a callback, input is polled
557         builder.setCallback(mFullDuplexLatency.get());
558     }
559 }
560 
finishOpen(bool isInput,oboe::AudioStream * oboeStream)561 void ActivityRoundTripLatency::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
562     if (isInput) {
563         mFullDuplexLatency->setInputStream(oboeStream);
564         mFullDuplexLatency->setRecording(mRecording.get());
565     } else {
566         mFullDuplexLatency->setOutputStream(oboeStream);
567     }
568 }
569 
570 // ======================================================================= ActivityGlitches
configureBuilder(bool isInput,oboe::AudioStreamBuilder & builder)571 void ActivityGlitches::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
572     ActivityFullDuplex::configureBuilder(isInput, builder);
573 
574     if (mFullDuplexGlitches.get() == nullptr) {
575         mFullDuplexGlitches = std::make_unique<FullDuplexGlitches>();
576     }
577     if (!isInput) {
578         // only output uses a callback, input is polled
579         builder.setCallback(mFullDuplexGlitches.get());
580     }
581 }
582 
finishOpen(bool isInput,oboe::AudioStream * oboeStream)583 void ActivityGlitches::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
584     if (isInput) {
585         mFullDuplexGlitches->setInputStream(oboeStream);
586         mFullDuplexGlitches->setRecording(mRecording.get());
587     } else {
588         mFullDuplexGlitches->setOutputStream(oboeStream);
589     }
590 }
591 
592 
593 // =================================================================== ActivityTestDisconnect
close(int32_t streamIndex)594 void ActivityTestDisconnect::close(int32_t streamIndex) {
595     ActivityContext::close(streamIndex);
596     mSinkFloat.reset();
597 }
598 
configureForStart()599 void ActivityTestDisconnect::configureForStart() {
600     oboe::AudioStream *outputStream = getOutputStream();
601     oboe::AudioStream *inputStream = getInputStream();
602     if (outputStream) {
603         mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
604         sineOscillator = std::make_unique<SineOscillator>();
605         monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
606 
607         sineOscillator->setSampleRate(outputStream->getSampleRate());
608         sineOscillator->frequency.setValue(440.0);
609         sineOscillator->amplitude.setValue(AMPLITUDE_SINE);
610         sineOscillator->output.connect(&(monoToMulti->input));
611 
612         monoToMulti->output.connect(&(mSinkFloat->input));
613         mSinkFloat->pullReset();
614         audioStreamGateway.setAudioSink(mSinkFloat);
615     } else if (inputStream) {
616         audioStreamGateway.setAudioSink(nullptr);
617     }
618     oboeCallbackProxy.setCallback(&audioStreamGateway);
619 }
620 
621