1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21 */
22
23 #ifndef JUCE_OBOE_LOG_ENABLED
24 #define JUCE_OBOE_LOG_ENABLED 1
25 #endif
26
27 #if JUCE_OBOE_LOG_ENABLED
28 #define JUCE_OBOE_LOG(x) DBG(x)
29 #else
30 #define JUCE_OBOE_LOG(x) {}
31 #endif
32
33 namespace juce
34 {
35
36 template <typename OboeDataFormat> struct OboeAudioIODeviceBufferHelpers {};
37
38 template <>
39 struct OboeAudioIODeviceBufferHelpers<int16>
40 {
oboeAudioFormatjuce::OboeAudioIODeviceBufferHelpers41 static oboe::AudioFormat oboeAudioFormat() { return oboe::AudioFormat::I16; }
42
bitDepthjuce::OboeAudioIODeviceBufferHelpers43 static constexpr int bitDepth() { return 16; }
44
referAudioBufferDirectlyToOboeIfPossiblejuce::OboeAudioIODeviceBufferHelpers45 static bool referAudioBufferDirectlyToOboeIfPossible (int16*, AudioBuffer<float>&, int) { return false; }
46
convertFromOboejuce::OboeAudioIODeviceBufferHelpers47 static void convertFromOboe (const int16* srcInterleaved, AudioBuffer<float>& audioBuffer, int numSamples)
48 {
49 for (int i = 0; i < audioBuffer.getNumChannels(); ++i)
50 {
51 using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
52 using SrcSampleType = AudioData::Pointer<AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const>;
53
54 DstSampleType dstData (audioBuffer.getWritePointer (i));
55 SrcSampleType srcData (srcInterleaved + i, audioBuffer.getNumChannels());
56 dstData.convertSamples (srcData, numSamples);
57 }
58 }
59
convertToOboejuce::OboeAudioIODeviceBufferHelpers60 static void convertToOboe (const AudioBuffer<float>& audioBuffer, int16* dstInterleaved, int numSamples)
61 {
62 for (int i = 0; i < audioBuffer.getNumChannels(); ++i)
63 {
64 using DstSampleType = AudioData::Pointer<AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst>;
65 using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
66
67 DstSampleType dstData (dstInterleaved + i, audioBuffer.getNumChannels());
68 SrcSampleType srcData (audioBuffer.getReadPointer (i));
69 dstData.convertSamples (srcData, numSamples);
70 }
71 }
72 };
73
74 template <>
75 struct OboeAudioIODeviceBufferHelpers<float>
76 {
oboeAudioFormatjuce::OboeAudioIODeviceBufferHelpers77 static oboe::AudioFormat oboeAudioFormat() { return oboe::AudioFormat::Float; }
78
bitDepthjuce::OboeAudioIODeviceBufferHelpers79 static constexpr int bitDepth() { return 32; }
80
referAudioBufferDirectlyToOboeIfPossiblejuce::OboeAudioIODeviceBufferHelpers81 static bool referAudioBufferDirectlyToOboeIfPossible (float* nativeBuffer, AudioBuffer<float>& audioBuffer, int numSamples)
82 {
83 if (audioBuffer.getNumChannels() == 1)
84 {
85 audioBuffer.setDataToReferTo (&nativeBuffer, 1, numSamples);
86 return true;
87 }
88
89 return false;
90 }
91
convertFromOboejuce::OboeAudioIODeviceBufferHelpers92 static void convertFromOboe (const float* srcInterleaved, AudioBuffer<float>& audioBuffer, int numSamples)
93 {
94 auto numChannels = audioBuffer.getNumChannels();
95
96 if (numChannels > 0)
97 {
98 // No need to convert, we instructed the buffer to point to the src data directly already
99 jassert (audioBuffer.getWritePointer (0) != srcInterleaved);
100
101 for (int i = 0; i < numChannels; ++i)
102 {
103 using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
104 using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const>;
105
106 DstSampleType dstData (audioBuffer.getWritePointer (i));
107 SrcSampleType srcData (srcInterleaved + i, audioBuffer.getNumChannels());
108 dstData.convertSamples (srcData, numSamples);
109 }
110 }
111 }
112
convertToOboejuce::OboeAudioIODeviceBufferHelpers113 static void convertToOboe (const AudioBuffer<float>& audioBuffer, float* dstInterleaved, int numSamples)
114 {
115 auto numChannels = audioBuffer.getNumChannels();
116
117 if (numChannels > 0)
118 {
119 // No need to convert, we instructed the buffer to point to the src data directly already
120 jassert (audioBuffer.getReadPointer (0) != dstInterleaved);
121
122 for (int i = 0; i < numChannels; ++i)
123 {
124 using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst>;
125 using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
126
127 DstSampleType dstData (dstInterleaved + i, audioBuffer.getNumChannels());
128 SrcSampleType srcData (audioBuffer.getReadPointer (i));
129 dstData.convertSamples (srcData, numSamples);
130 }
131 }
132 }
133 };
134
135 template <typename Type>
getOboeString(const Type & value)136 static String getOboeString (const Type& value)
137 {
138 return String (oboe::convertToText (value));
139 }
140
141 //==============================================================================
142 class OboeAudioIODevice : public AudioIODevice
143 {
144 public:
145 //==============================================================================
OboeAudioIODevice(const String & deviceName,int inputDeviceIdToUse,const Array<int> & supportedInputSampleRatesToUse,int maxNumInputChannelsToUse,int outputDeviceIdToUse,const Array<int> & supportedOutputSampleRatesToUse,int maxNumOutputChannelsToUse)146 OboeAudioIODevice (const String& deviceName,
147 int inputDeviceIdToUse,
148 const Array<int>& supportedInputSampleRatesToUse,
149 int maxNumInputChannelsToUse,
150 int outputDeviceIdToUse,
151 const Array<int>& supportedOutputSampleRatesToUse,
152 int maxNumOutputChannelsToUse)
153 : AudioIODevice (deviceName, oboeTypeName),
154 inputDeviceId (inputDeviceIdToUse),
155 supportedInputSampleRates (supportedInputSampleRatesToUse),
156 maxNumInputChannels (maxNumInputChannelsToUse),
157 outputDeviceId (outputDeviceIdToUse),
158 supportedOutputSampleRates (supportedOutputSampleRatesToUse),
159 maxNumOutputChannels (maxNumOutputChannelsToUse)
160 {
161 }
162
~OboeAudioIODevice()163 ~OboeAudioIODevice() override
164 {
165 close();
166 }
167
getOutputChannelNames()168 StringArray getOutputChannelNames() override { return getChannelNames (false); }
getInputChannelNames()169 StringArray getInputChannelNames() override { return getChannelNames (true); }
170
getAvailableSampleRates()171 Array<double> getAvailableSampleRates() override
172 {
173 Array<double> result;
174
175 auto inputSampleRates = getAvailableSampleRates (true);
176 auto outputSampleRates = getAvailableSampleRates (false);
177
178 if (inputDeviceId == -1)
179 {
180 for (auto& sr : outputSampleRates)
181 result.add (sr);
182 }
183 else if (outputDeviceId == -1)
184 {
185 for (auto& sr : inputSampleRates)
186 result.add (sr);
187 }
188 else
189 {
190 // For best performance, the same sample rate should be used for input and output,
191 for (auto& inputSampleRate : inputSampleRates)
192 {
193 if (outputSampleRates.contains (inputSampleRate))
194 result.add (inputSampleRate);
195 }
196 }
197
198 // either invalid device was requested or its input&output don't have compatible sample rate
199 jassert (result.size() > 0);
200 return result;
201 }
202
getAvailableBufferSizes()203 Array<int> getAvailableBufferSizes() override
204 {
205 return AndroidHighPerformanceAudioHelpers::getAvailableBufferSizes (getNativeBufferSize(), getAvailableSampleRates());
206 }
207
open(const BigInteger & inputChannels,const BigInteger & outputChannels,double requestedSampleRate,int bufferSize)208 String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
209 double requestedSampleRate, int bufferSize) override
210 {
211 close();
212
213 lastError.clear();
214
215 sampleRate = (int) (requestedSampleRate > 0 ? requestedSampleRate : AndroidHighPerformanceAudioHelpers::getNativeSampleRate());
216 actualBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;
217
218 // The device may report no max, claiming "no limits". Pick sensible defaults.
219 int maxOutChans = maxNumOutputChannels > 0 ? maxNumOutputChannels : 2;
220 int maxInChans = maxNumInputChannels > 0 ? maxNumInputChannels : 1;
221
222 activeOutputChans = outputChannels;
223 activeOutputChans.setRange (maxOutChans,
224 activeOutputChans.getHighestBit() + 1 - maxOutChans,
225 false);
226
227 activeInputChans = inputChannels;
228 activeInputChans.setRange (maxInChans,
229 activeInputChans.getHighestBit() + 1 - maxInChans,
230 false);
231
232 int numOutputChans = activeOutputChans.countNumberOfSetBits();
233 int numInputChans = activeInputChans.countNumberOfSetBits();
234
235 if (numInputChans > 0 && (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)))
236 {
237 // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio
238 // before trying to open an audio input device. This is not going to work!
239 jassertfalse;
240 lastError = "Error opening Oboe input device: the app was not granted android.permission.RECORD_AUDIO";
241 }
242
243 // At least one output channel should be set!
244 jassert (numOutputChans >= 0);
245
246 session.reset (OboeSessionBase::create (*this,
247 inputDeviceId, outputDeviceId,
248 numInputChans, numOutputChans,
249 sampleRate, actualBufferSize));
250
251 deviceOpen = session != nullptr;
252
253 if (! deviceOpen)
254 lastError = "Failed to create audio session";
255
256 return lastError;
257 }
258
close()259 void close() override { stop(); }
getOutputLatencyInSamples()260 int getOutputLatencyInSamples() override { return session->getOutputLatencyInSamples(); }
getInputLatencyInSamples()261 int getInputLatencyInSamples() override { return session->getInputLatencyInSamples(); }
isOpen()262 bool isOpen() override { return deviceOpen; }
getCurrentBufferSizeSamples()263 int getCurrentBufferSizeSamples() override { return actualBufferSize; }
getCurrentBitDepth()264 int getCurrentBitDepth() override { return session->getCurrentBitDepth(); }
getActiveOutputChannels() const265 BigInteger getActiveOutputChannels() const override { return activeOutputChans; }
getActiveInputChannels() const266 BigInteger getActiveInputChannels() const override { return activeInputChans; }
getLastError()267 String getLastError() override { return lastError; }
isPlaying()268 bool isPlaying() override { return callback.get() != nullptr; }
getXRunCount() const269 int getXRunCount() const noexcept override { return session->getXRunCount(); }
270
getDefaultBufferSize()271 int getDefaultBufferSize() override
272 {
273 return AndroidHighPerformanceAudioHelpers::getDefaultBufferSize (getNativeBufferSize(), getCurrentSampleRate());
274 }
275
getCurrentSampleRate()276 double getCurrentSampleRate() override
277 {
278 return (sampleRate == 0.0 ? AndroidHighPerformanceAudioHelpers::getNativeSampleRate() : sampleRate);
279 }
280
start(AudioIODeviceCallback * newCallback)281 void start (AudioIODeviceCallback* newCallback) override
282 {
283 if (callback.get() != newCallback)
284 {
285 if (newCallback != nullptr)
286 newCallback->audioDeviceAboutToStart (this);
287
288 AudioIODeviceCallback* oldCallback = callback.get();
289
290 if (oldCallback != nullptr)
291 {
292 // already running
293 if (newCallback == nullptr)
294 stop();
295 else
296 setCallback (newCallback);
297
298 oldCallback->audioDeviceStopped();
299 }
300 else
301 {
302 jassert (newCallback != nullptr);
303
304 // session hasn't started yet
305 setCallback (newCallback);
306 running = true;
307
308 session->start();
309 }
310
311 callback = newCallback;
312 }
313 }
314
stop()315 void stop() override
316 {
317 if (session != nullptr)
318 session->stop();
319
320 running = false;
321 setCallback (nullptr);
322 }
323
setAudioPreprocessingEnabled(bool)324 bool setAudioPreprocessingEnabled (bool) override
325 {
326 // Oboe does not expose this setting, yet it may use preprocessing
327 // for older APIs running OpenSL
328 return false;
329 }
330
331 static const char* const oboeTypeName;
332
333 private:
getChannelNames(bool forInput)334 StringArray getChannelNames (bool forInput)
335 {
336 auto& deviceId = forInput ? inputDeviceId : outputDeviceId;
337 auto& numChannels = forInput ? maxNumInputChannels : maxNumOutputChannels;
338
339 // If the device id is unknown (on olders APIs) or if the device claims to
340 // support "any" channel count, use a sensible default
341 if (deviceId == -1 || numChannels == -1)
342 return forInput ? StringArray ("Input") : StringArray ("Left", "Right");
343
344 StringArray names;
345
346 for (int i = 0; i < numChannels; ++i)
347 names.add ("Channel " + String (i + 1));
348
349 return names;
350 }
351
getAvailableSampleRates(bool forInput)352 Array<int> getAvailableSampleRates (bool forInput)
353 {
354 auto& supportedSampleRates = forInput
355 ? supportedInputSampleRates
356 : supportedOutputSampleRates;
357
358 if (! supportedSampleRates.isEmpty())
359 return supportedSampleRates;
360
361 // device claims that it supports "any" sample rate, use
362 // standard ones then
363 return getDefaultSampleRates();
364 }
365
getDefaultSampleRates()366 static Array<int> getDefaultSampleRates()
367 {
368 static const int standardRates[] = { 8000, 11025, 12000, 16000,
369 22050, 24000, 32000, 44100, 48000 };
370
371 Array<int> rates (standardRates, numElementsInArray (standardRates));
372
373 // make sure the native sample rate is part of the list
374 int native = (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate();
375
376 if (native != 0 && ! rates.contains (native))
377 rates.add (native);
378
379 return rates;
380 }
381
getNativeBufferSize()382 static int getNativeBufferSize()
383 {
384 auto bufferSizeHint = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint();
385
386 // providing a callback is required on some devices to get a FAST track, so we pass an
387 // empty one to the temp stream to get the best available buffer size
388 struct DummyCallback : public oboe::AudioStreamCallback
389 {
390 oboe::DataCallbackResult onAudioReady (oboe::AudioStream*, void*, int32_t) override { return oboe::DataCallbackResult::Stop; }
391 };
392
393 DummyCallback callback;
394
395 // NB: Exclusive mode could be rejected if a device is already opened in that mode, so to get
396 // reliable results, only use this function when a device is closed.
397 // We initially try to open a stream with a buffer size returned from
398 // android.media.property.OUTPUT_FRAMES_PER_BUFFER property, but then we verify the actual
399 // size after the stream is open.
400 OboeAudioIODevice::OboeStream tempStream (oboe::kUnspecified,
401 oboe::Direction::Output,
402 oboe::SharingMode::Exclusive,
403 2,
404 getAndroidSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16,
405 (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(),
406 bufferSizeHint,
407 &callback);
408
409 if (auto* nativeStream = tempStream.getNativeStream())
410 return nativeStream->getFramesPerBurst();
411
412 return bufferSizeHint;
413 }
414
setCallback(AudioIODeviceCallback * callbackToUse)415 void setCallback (AudioIODeviceCallback* callbackToUse)
416 {
417 if (! running)
418 {
419 callback.set (callbackToUse);
420 return;
421 }
422
423 // Setting nullptr callback is allowed only when playback is stopped.
424 jassert (callbackToUse != nullptr);
425
426 for (;;)
427 {
428 auto old = callback.get();
429
430 if (old == callbackToUse)
431 break;
432
433 // If old is nullptr, then it means that it's currently being used!
434 if (old != nullptr && callback.compareAndSetBool (callbackToUse, old))
435 break;
436
437 Thread::sleep (1);
438 }
439 }
440
process(const float ** inputChannelData,int numInputChannels,float ** outputChannelData,int numOutputChannels,int32_t numFrames)441 void process (const float** inputChannelData, int numInputChannels,
442 float** outputChannelData, int numOutputChannels, int32_t numFrames)
443 {
444 if (auto* cb = callback.exchange (nullptr))
445 {
446 cb->audioDeviceIOCallback (inputChannelData, numInputChannels,
447 outputChannelData, numOutputChannels, numFrames);
448 callback.set (cb);
449 }
450 else
451 {
452 for (int i = 0; i < numOutputChannels; ++i)
453 zeromem (outputChannelData[i], (size_t) (numFrames) * sizeof (float));
454 }
455 }
456
457 //==============================================================================
458 class OboeStream
459 {
460 public:
OboeStream(int deviceId,oboe::Direction direction,oboe::SharingMode sharingMode,int channelCount,oboe::AudioFormat format,int32 sampleRateIn,int32 bufferSize,oboe::AudioStreamCallback * callbackIn=nullptr)461 OboeStream (int deviceId, oboe::Direction direction,
462 oboe::SharingMode sharingMode,
463 int channelCount, oboe::AudioFormat format,
464 int32 sampleRateIn, int32 bufferSize,
465 oboe::AudioStreamCallback* callbackIn = nullptr)
466 {
467 open (deviceId, direction, sharingMode, channelCount,
468 format, sampleRateIn, bufferSize, callbackIn);
469 }
470
~OboeStream()471 ~OboeStream()
472 {
473 close();
474 delete stream;
475 }
476
openedOk() const477 bool openedOk() const noexcept
478 {
479 return openResult == oboe::Result::OK;
480 }
481
start()482 void start()
483 {
484 jassert (openedOk());
485
486 if (openedOk() && stream != nullptr)
487 {
488 auto expectedState = oboe::StreamState::Starting;
489 auto nextState = oboe::StreamState::Started;
490 int64 timeoutNanos = 1000 * oboe::kNanosPerMillisecond;
491
492 auto startResult = stream->requestStart();
493 JUCE_OBOE_LOG ("Requested Oboe stream start with result: " + getOboeString (startResult));
494
495 startResult = stream->waitForStateChange (expectedState, &nextState, timeoutNanos);
496
497 JUCE_OBOE_LOG ("Starting Oboe stream with result: " + getOboeString (startResult);
498 + "\nUses AAudio = " + String ((int) stream->usesAAudio())
499 + "\nDirection = " + getOboeString (stream->getDirection())
500 + "\nSharingMode = " + getOboeString (stream->getSharingMode())
501 + "\nChannelCount = " + String (stream->getChannelCount())
502 + "\nFormat = " + getOboeString (stream->getFormat())
503 + "\nSampleRate = " + String (stream->getSampleRate())
504 + "\nBufferSizeInFrames = " + String (stream->getBufferSizeInFrames())
505 + "\nBufferCapacityInFrames = " + String (stream->getBufferCapacityInFrames())
506 + "\nFramesPerBurst = " + String (stream->getFramesPerBurst())
507 + "\nFramesPerCallback = " + String (stream->getFramesPerCallback())
508 + "\nBytesPerFrame = " + String (stream->getBytesPerFrame())
509 + "\nBytesPerSample = " + String (stream->getBytesPerSample())
510 + "\nPerformanceMode = " + getOboeString (stream->getPerformanceMode())
511 + "\ngetDeviceId = " + String (stream->getDeviceId()));
512 }
513 }
514
getNativeStream() const515 oboe::AudioStream* getNativeStream() const
516 {
517 jassert (openedOk());
518 return stream;
519 }
520
getXRunCount() const521 int getXRunCount() const
522 {
523 if (stream != nullptr)
524 {
525 auto count = stream->getXRunCount();
526
527 if (count)
528 return count.value();
529
530 JUCE_OBOE_LOG ("Failed to get Xrun count: " + getOboeString (count.error()));
531 }
532
533 return 0;
534 }
535
536 private:
open(int deviceId,oboe::Direction direction,oboe::SharingMode sharingMode,int channelCount,oboe::AudioFormat format,int32 newSampleRate,int32 newBufferSize,oboe::AudioStreamCallback * newCallback=nullptr)537 void open (int deviceId, oboe::Direction direction,
538 oboe::SharingMode sharingMode,
539 int channelCount, oboe::AudioFormat format,
540 int32 newSampleRate, int32 newBufferSize,
541 oboe::AudioStreamCallback* newCallback = nullptr)
542 {
543 oboe::DefaultStreamValues::FramesPerBurst = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint();
544
545 oboe::AudioStreamBuilder builder;
546
547 if (deviceId != -1)
548 builder.setDeviceId (deviceId);
549
550 // Note: letting OS to choose the buffer capacity & frames per callback.
551 builder.setDirection (direction);
552 builder.setSharingMode (sharingMode);
553 builder.setChannelCount (channelCount);
554 builder.setFormat (format);
555 builder.setSampleRate (newSampleRate);
556 builder.setPerformanceMode (oboe::PerformanceMode::LowLatency);
557
558 #if JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK
559 if (newCallback != nullptr)
560 {
561 stabilizedCallback = std::make_unique<oboe::StabilizedCallback> (newCallback);
562 builder.setCallback (stabilizedCallback.get());
563 }
564 #else
565 builder.setCallback (newCallback);
566 #endif
567
568 JUCE_OBOE_LOG (String ("Preparing Oboe stream with params:")
569 + "\nAAudio supported = " + String (int (builder.isAAudioSupported()))
570 + "\nAPI = " + getOboeString (builder.getAudioApi())
571 + "\nDeviceId = " + String (deviceId)
572 + "\nDirection = " + getOboeString (direction)
573 + "\nSharingMode = " + getOboeString (sharingMode)
574 + "\nChannelCount = " + String (channelCount)
575 + "\nFormat = " + getOboeString (format)
576 + "\nSampleRate = " + String (newSampleRate)
577 + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency));
578
579 openResult = builder.openStream (&stream);
580 JUCE_OBOE_LOG ("Building Oboe stream with result: " + getOboeString (openResult)
581 + "\nStream state = " + (stream != nullptr ? getOboeString (stream->getState()) : String ("?")));
582
583 if (stream != nullptr && newBufferSize != 0)
584 {
585 JUCE_OBOE_LOG ("Setting the bufferSizeInFrames to " + String (newBufferSize));
586 stream->setBufferSizeInFrames (newBufferSize);
587 }
588
589 JUCE_OBOE_LOG (String ("Stream details:")
590 + "\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
591 + "\nDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?"))
592 + "\nDirection = " + (stream != nullptr ? getOboeString (stream->getDirection()) : String ("?"))
593 + "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?"))
594 + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
595 + "\nFormat = " + (stream != nullptr ? getOboeString (stream->getFormat()) : String ("?"))
596 + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
597 + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
598 + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
599 + "\nFramesPerBurst = " + (stream != nullptr ? String (stream->getFramesPerBurst()) : String ("?"))
600 + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
601 + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
602 + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
603 + "\nPerformanceMode = " + (stream != nullptr ? getOboeString (stream->getPerformanceMode()) : String ("?")));
604 }
605
close()606 void close()
607 {
608 if (stream != nullptr)
609 {
610 oboe::Result result = stream->close();
611 ignoreUnused (result);
612 JUCE_OBOE_LOG ("Requested Oboe stream close with result: " + getOboeString (result));
613 }
614 }
615
616 oboe::AudioStream* stream = nullptr;
617 #if JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK
618 std::unique_ptr<oboe::StabilizedCallback> stabilizedCallback;
619 #endif
620 oboe::Result openResult;
621 };
622
623 //==============================================================================
624 class OboeSessionBase : protected oboe::AudioStreamCallback
625 {
626 public:
627 static OboeSessionBase* create (OboeAudioIODevice& owner,
628 int inputDeviceId, int outputDeviceId,
629 int numInputChannels, int numOutputChannels,
630 int sampleRate, int bufferSize);
631
632 virtual void start() = 0;
633 virtual void stop() = 0;
634 virtual int getOutputLatencyInSamples() = 0;
635 virtual int getInputLatencyInSamples() = 0;
636
openedOk() const637 bool openedOk() const noexcept
638 {
639 if (inputStream != nullptr && ! inputStream->openedOk())
640 return false;
641
642 return outputStream != nullptr && outputStream->openedOk();
643 }
644
getCurrentBitDepth() const645 int getCurrentBitDepth() const noexcept { return bitDepth; }
646
getXRunCount() const647 int getXRunCount() const
648 {
649 int inputXRunCount = jmax (0, inputStream != nullptr ? inputStream->getXRunCount() : 0);
650 int outputXRunCount = jmax (0, outputStream != nullptr ? outputStream->getXRunCount() : 0);
651
652 return inputXRunCount + outputXRunCount;
653 }
654
655 protected:
OboeSessionBase(OboeAudioIODevice & ownerToUse,int inputDeviceIdToUse,int outputDeviceIdToUse,int numInputChannelsToUse,int numOutputChannelsToUse,int sampleRateToUse,int bufferSizeToUse,oboe::AudioFormat streamFormatToUse,int bitDepthToUse)656 OboeSessionBase (OboeAudioIODevice& ownerToUse,
657 int inputDeviceIdToUse, int outputDeviceIdToUse,
658 int numInputChannelsToUse, int numOutputChannelsToUse,
659 int sampleRateToUse, int bufferSizeToUse,
660 oboe::AudioFormat streamFormatToUse,
661 int bitDepthToUse)
662 : owner (ownerToUse),
663 inputDeviceId (inputDeviceIdToUse),
664 outputDeviceId (outputDeviceIdToUse),
665 numInputChannels (numInputChannelsToUse),
666 numOutputChannels (numOutputChannelsToUse),
667 sampleRate (sampleRateToUse),
668 bufferSize (bufferSizeToUse),
669 streamFormat (streamFormatToUse),
670 bitDepth (bitDepthToUse),
671 outputStream (new OboeStream (outputDeviceId,
672 oboe::Direction::Output,
673 oboe::SharingMode::Exclusive,
674 numOutputChannels,
675 streamFormatToUse,
676 sampleRateToUse,
677 bufferSizeToUse,
678 this))
679 {
680 if (numInputChannels > 0)
681 {
682 inputStream.reset (new OboeStream (inputDeviceId,
683 oboe::Direction::Input,
684 oboe::SharingMode::Exclusive,
685 numInputChannels,
686 streamFormatToUse,
687 sampleRateToUse,
688 bufferSizeToUse,
689 nullptr));
690
691 if (inputStream->openedOk() && outputStream->openedOk())
692 {
693 // Input & output sample rates should match!
694 jassert (inputStream->getNativeStream()->getSampleRate()
695 == outputStream->getNativeStream()->getSampleRate());
696 }
697
698 checkStreamSetup (inputStream.get(), inputDeviceId, numInputChannels,
699 sampleRate, bufferSize, streamFormat);
700 }
701
702 checkStreamSetup (outputStream.get(), outputDeviceId, numOutputChannels,
703 sampleRate, bufferSize, streamFormat);
704 }
705
706 // Not strictly required as these should not change, but recommended by Google anyway
checkStreamSetup(OboeStream * stream,int deviceId,int numChannels,int expectedSampleRate,int expectedBufferSize,oboe::AudioFormat format)707 void checkStreamSetup (OboeStream* stream, int deviceId, int numChannels, int expectedSampleRate,
708 int expectedBufferSize, oboe::AudioFormat format)
709 {
710 if (auto* nativeStream = stream != nullptr ? stream->getNativeStream() : nullptr)
711 {
712 ignoreUnused (deviceId, numChannels, sampleRate, expectedBufferSize);
713 ignoreUnused (streamFormat, bitDepth);
714
715 jassert (numChannels == 0 || numChannels == nativeStream->getChannelCount());
716 jassert (expectedSampleRate == 0 || expectedSampleRate == nativeStream->getSampleRate());
717 jassert (format == nativeStream->getFormat());
718 }
719 }
720
getBufferCapacityInFrames(bool forInput) const721 int getBufferCapacityInFrames (bool forInput) const
722 {
723 auto& ptr = forInput ? inputStream : outputStream;
724
725 if (ptr == nullptr || ! ptr->openedOk())
726 return 0;
727
728 return ptr->getNativeStream()->getBufferCapacityInFrames();
729 }
730
731 OboeAudioIODevice& owner;
732 int inputDeviceId, outputDeviceId;
733 int numInputChannels, numOutputChannels;
734 int sampleRate;
735 int bufferSize;
736 oboe::AudioFormat streamFormat;
737 int bitDepth;
738
739 std::unique_ptr<OboeStream> inputStream, outputStream;
740 };
741
742 //==============================================================================
743 template <typename SampleType>
744 class OboeSessionImpl : public OboeSessionBase
745 {
746 public:
OboeSessionImpl(OboeAudioIODevice & ownerToUse,int inputDeviceIdIn,int outputDeviceIdIn,int numInputChannelsToUse,int numOutputChannelsToUse,int sampleRateToUse,int bufferSizeToUse)747 OboeSessionImpl (OboeAudioIODevice& ownerToUse,
748 int inputDeviceIdIn, int outputDeviceIdIn,
749 int numInputChannelsToUse, int numOutputChannelsToUse,
750 int sampleRateToUse, int bufferSizeToUse)
751 : OboeSessionBase (ownerToUse,
752 inputDeviceIdIn, outputDeviceIdIn,
753 numInputChannelsToUse, numOutputChannelsToUse,
754 sampleRateToUse, bufferSizeToUse,
755 OboeAudioIODeviceBufferHelpers<SampleType>::oboeAudioFormat(),
756 OboeAudioIODeviceBufferHelpers<SampleType>::bitDepth()),
757 inputStreamNativeBuffer (static_cast<size_t> (numInputChannelsToUse * getBufferCapacityInFrames (true))),
758 inputStreamSampleBuffer (numInputChannels, getBufferCapacityInFrames (true)),
759 outputStreamSampleBuffer (numOutputChannels, getBufferCapacityInFrames (false))
760 {
761 }
762
start()763 void start() override
764 {
765 audioCallbackGuard.set (0);
766
767 if (inputStream != nullptr)
768 inputStream->start();
769
770 outputStream->start();
771
772 isInputLatencyDetectionSupported = isLatencyDetectionSupported (inputStream.get());
773 isOutputLatencyDetectionSupported = isLatencyDetectionSupported (outputStream.get());
774 }
775
stop()776 void stop() override
777 {
778 while (! audioCallbackGuard.compareAndSetBool (1, 0))
779 Thread::sleep (1);
780
781 inputStream = nullptr;
782 outputStream = nullptr;
783
784 audioCallbackGuard.set (0);
785 }
786
getOutputLatencyInSamples()787 int getOutputLatencyInSamples() override { return outputLatency; }
getInputLatencyInSamples()788 int getInputLatencyInSamples() override { return inputLatency; }
789
790 private:
isLatencyDetectionSupported(OboeStream * stream)791 bool isLatencyDetectionSupported (OboeStream* stream)
792 {
793 if (stream == nullptr || ! openedOk())
794 return false;
795
796 auto result = stream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC, nullptr, nullptr);
797 return result != oboe::Result::ErrorUnimplemented;
798 }
799
onAudioReady(oboe::AudioStream * stream,void * audioData,int32_t numFrames)800 oboe::DataCallbackResult onAudioReady (oboe::AudioStream* stream, void* audioData, int32_t numFrames) override
801 {
802 if (audioCallbackGuard.compareAndSetBool (1, 0))
803 {
804 if (stream == nullptr)
805 return oboe::DataCallbackResult::Stop;
806
807 // only output stream should be the master stream receiving callbacks
808 jassert (stream->getDirection() == oboe::Direction::Output && stream == outputStream->getNativeStream());
809
810 // Read input from Oboe
811 inputStreamSampleBuffer.clear();
812 inputStreamNativeBuffer.calloc (static_cast<size_t> (numInputChannels * bufferSize));
813
814 if (inputStream != nullptr)
815 {
816 auto* nativeInputStream = inputStream->getNativeStream();
817
818 if (nativeInputStream->getFormat() != oboe::AudioFormat::I16 && nativeInputStream->getFormat() != oboe::AudioFormat::Float)
819 {
820 JUCE_OBOE_LOG ("Unsupported input stream audio format: " + getOboeString (nativeInputStream->getFormat()));
821 jassertfalse;
822 return oboe::DataCallbackResult::Continue;
823 }
824
825 auto result = inputStream->getNativeStream()->read (inputStreamNativeBuffer.getData(), numFrames, 0);
826
827 if (result)
828 {
829 auto referringDirectlyToOboeData = OboeAudioIODeviceBufferHelpers<SampleType>
830 ::referAudioBufferDirectlyToOboeIfPossible (inputStreamNativeBuffer.get(),
831 inputStreamSampleBuffer,
832 result.value());
833
834 if (! referringDirectlyToOboeData)
835 OboeAudioIODeviceBufferHelpers<SampleType>::convertFromOboe (inputStreamNativeBuffer.get(), inputStreamSampleBuffer, result.value());
836 }
837 else
838 {
839 JUCE_OBOE_LOG ("Failed to read from input stream: " + getOboeString (result.error()));
840 }
841
842 if (isInputLatencyDetectionSupported)
843 inputLatency = getLatencyFor (*inputStream);
844 }
845
846 // Setup output buffer
847 auto referringDirectlyToOboeData = OboeAudioIODeviceBufferHelpers<SampleType>
848 ::referAudioBufferDirectlyToOboeIfPossible (static_cast<SampleType*> (audioData),
849 outputStreamSampleBuffer,
850 numFrames);
851
852 if (! referringDirectlyToOboeData)
853 outputStreamSampleBuffer.clear();
854
855 // Process
856 // NB: the number of samples read from the input can potentially differ from numFrames.
857 owner.process (inputStreamSampleBuffer.getArrayOfReadPointers(), numInputChannels,
858 outputStreamSampleBuffer.getArrayOfWritePointers(), numOutputChannels,
859 numFrames);
860
861 // Write output to Oboe
862 if (! referringDirectlyToOboeData)
863 OboeAudioIODeviceBufferHelpers<SampleType>::convertToOboe (outputStreamSampleBuffer, static_cast<SampleType*> (audioData), numFrames);
864
865 if (isOutputLatencyDetectionSupported)
866 outputLatency = getLatencyFor (*outputStream);
867
868 audioCallbackGuard.set (0);
869 }
870
871 return oboe::DataCallbackResult::Continue;
872 }
873
printStreamDebugInfo(oboe::AudioStream * stream)874 void printStreamDebugInfo (oboe::AudioStream* stream)
875 {
876 ignoreUnused (stream);
877
878 JUCE_OBOE_LOG ("\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
879 + "\nDirection = " + (stream != nullptr ? getOboeString (stream->getDirection()) : String ("?"))
880 + "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?"))
881 + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
882 + "\nFormat = " + (stream != nullptr ? getOboeString (stream->getFormat()) : String ("?"))
883 + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
884 + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
885 + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
886 + "\nFramesPerBurst = " + (stream != nullptr ? String (stream->getFramesPerBurst()) : String ("?"))
887 + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
888 + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
889 + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
890 + "\nPerformanceMode = " + (stream != nullptr ? getOboeString (stream->getPerformanceMode()) : String ("?"))
891 + "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?")));
892 }
893
getLatencyFor(OboeStream & stream)894 int getLatencyFor (OboeStream& stream)
895 {
896 auto& nativeStream = *stream.getNativeStream();
897
898 if (auto latency = nativeStream.calculateLatencyMillis())
899 return static_cast<int> ((latency.value() * sampleRate) / 1000);
900
901 // Get the time that a known audio frame was presented.
902 int64_t hardwareFrameIndex = 0;
903 int64_t hardwareFrameHardwareTime = 0;
904
905 auto result = nativeStream.getTimestamp (CLOCK_MONOTONIC,
906 &hardwareFrameIndex,
907 &hardwareFrameHardwareTime);
908
909 if (result != oboe::Result::OK)
910 return 0;
911
912 // Get counter closest to the app.
913 const bool isOutput = nativeStream.getDirection() == oboe::Direction::Output;
914 const int64_t appFrameIndex = isOutput ? nativeStream.getFramesWritten() : nativeStream.getFramesRead();
915
916 // Assume that the next frame will be processed at the current time
917 int64_t appFrameAppTime = getCurrentTimeNanos();
918
919 // Calculate the number of frames between app and hardware
920 int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex;
921
922 // Calculate the time which the next frame will be or was presented
923 int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / sampleRate;
924 int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta;
925
926 // Calculate latency as a difference in time between when the current frame is at the app
927 // and when it is at the hardware.
928 auto latencyNanos = isOutput ? (appFrameHardwareTime - appFrameAppTime) : (appFrameAppTime - appFrameHardwareTime);
929 return static_cast<int> ((latencyNanos * sampleRate) / oboe::kNanosPerSecond);
930 }
931
getCurrentTimeNanos()932 int64_t getCurrentTimeNanos()
933 {
934 timespec time;
935
936 if (clock_gettime (CLOCK_MONOTONIC, &time) < 0)
937 return -1;
938
939 return time.tv_sec * oboe::kNanosPerSecond + time.tv_nsec;
940 }
941
onErrorBeforeClose(oboe::AudioStream * stream,oboe::Result error)942 void onErrorBeforeClose (oboe::AudioStream* stream, oboe::Result error) override
943 {
944 ignoreUnused (error);
945
946 // only output stream should be the master stream receiving callbacks
947 jassert (stream->getDirection() == oboe::Direction::Output);
948
949 JUCE_OBOE_LOG ("Oboe stream onErrorBeforeClose(): " + getOboeString (error));
950 printStreamDebugInfo (stream);
951 }
952
onErrorAfterClose(oboe::AudioStream * stream,oboe::Result error)953 void onErrorAfterClose (oboe::AudioStream* stream, oboe::Result error) override
954 {
955 // only output stream should be the master stream receiving callbacks
956 jassert (stream->getDirection() == oboe::Direction::Output);
957
958 JUCE_OBOE_LOG ("Oboe stream onErrorAfterClose(): " + getOboeString (error));
959
960 if (error == oboe::Result::ErrorDisconnected)
961 {
962 if (streamRestartGuard.compareAndSetBool (1, 0))
963 {
964 // Close, recreate, and start the stream, not much use in current one.
965 // Use default device id, to let the OS pick the best ID (since our was disconnected).
966
967 while (! audioCallbackGuard.compareAndSetBool (1, 0))
968 Thread::sleep (1);
969
970 outputStream = nullptr;
971 outputStream.reset (new OboeStream (oboe::kUnspecified,
972 oboe::Direction::Output,
973 oboe::SharingMode::Exclusive,
974 numOutputChannels,
975 streamFormat,
976 sampleRate,
977 bufferSize,
978 this));
979
980 outputStream->start();
981
982 audioCallbackGuard.set (0);
983 streamRestartGuard.set (0);
984 }
985 }
986 }
987
988 HeapBlock<SampleType> inputStreamNativeBuffer;
989 AudioBuffer<float> inputStreamSampleBuffer,
990 outputStreamSampleBuffer;
991 Atomic<int> audioCallbackGuard { 0 },
992 streamRestartGuard { 0 };
993
994 bool isInputLatencyDetectionSupported = false;
995 int inputLatency = -1;
996
997 bool isOutputLatencyDetectionSupported = false;
998 int outputLatency = -1;
999 };
1000
1001 //==============================================================================
1002 friend class OboeAudioIODeviceType;
1003 friend class OboeRealtimeThread;
1004
1005 //==============================================================================
1006 int actualBufferSize = 0, sampleRate = 0;
1007 bool deviceOpen = false;
1008 String lastError;
1009 BigInteger activeOutputChans, activeInputChans;
1010 Atomic<AudioIODeviceCallback*> callback { nullptr };
1011
1012 int inputDeviceId;
1013 Array<int> supportedInputSampleRates;
1014 int maxNumInputChannels;
1015 int outputDeviceId;
1016 Array<int> supportedOutputSampleRates;
1017 int maxNumOutputChannels;
1018
1019 std::unique_ptr<OboeSessionBase> session;
1020
1021 bool running = false;
1022
1023 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OboeAudioIODevice)
1024 };
1025
1026 //==============================================================================
create(OboeAudioIODevice & owner,int inputDeviceId,int outputDeviceId,int numInputChannels,int numOutputChannels,int sampleRate,int bufferSize)1027 OboeAudioIODevice::OboeSessionBase* OboeAudioIODevice::OboeSessionBase::create (OboeAudioIODevice& owner,
1028 int inputDeviceId,
1029 int outputDeviceId,
1030 int numInputChannels,
1031 int numOutputChannels,
1032 int sampleRate,
1033 int bufferSize)
1034 {
1035
1036 std::unique_ptr<OboeSessionBase> session;
1037 auto sdkVersion = getAndroidSDKVersion();
1038
1039 // SDK versions 21 and higher should natively support floating point...
1040 if (sdkVersion >= 21)
1041 {
1042 session.reset (new OboeSessionImpl<float> (owner, inputDeviceId, outputDeviceId,
1043 numInputChannels, numOutputChannels, sampleRate, bufferSize));
1044
1045 // ...however, some devices lie so re-try without floating point
1046 if (session != nullptr && (! session->openedOk()))
1047 session.reset();
1048 }
1049
1050 if (session == nullptr)
1051 {
1052 session.reset (new OboeSessionImpl<int16> (owner, inputDeviceId, outputDeviceId,
1053 numInputChannels, numOutputChannels, sampleRate, bufferSize));
1054
1055 if (session != nullptr && (! session->openedOk()))
1056 session.reset();
1057 }
1058
1059 return session.release();
1060 }
1061
1062 //==============================================================================
1063 class OboeAudioIODeviceType : public AudioIODeviceType
1064 {
1065 public:
OboeAudioIODeviceType()1066 OboeAudioIODeviceType()
1067 : AudioIODeviceType (OboeAudioIODevice::oboeTypeName)
1068 {
1069 // Not using scanForDevices() to maintain behaviour backwards compatible with older APIs
1070 checkAvailableDevices();
1071 }
1072
1073 //==============================================================================
scanForDevices()1074 void scanForDevices() override {}
1075
getDeviceNames(bool wantInputNames) const1076 StringArray getDeviceNames (bool wantInputNames) const override
1077 {
1078 StringArray names;
1079
1080 for (auto& device : wantInputNames ? inputDevices : outputDevices)
1081 names.add (device.name);
1082
1083 return names;
1084 }
1085
getDefaultDeviceIndex(bool) const1086 int getDefaultDeviceIndex (bool) const override
1087 {
1088 return 0;
1089 }
1090
getIndexOfDevice(AudioIODevice * device,bool asInput) const1091 int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
1092 {
1093 if (auto oboeDevice = static_cast<OboeAudioIODevice*> (device))
1094 {
1095 auto oboeDeviceId = asInput ? oboeDevice->inputDeviceId
1096 : oboeDevice->outputDeviceId;
1097
1098 auto& devices = asInput ? inputDevices : outputDevices;
1099
1100 for (int i = 0; i < devices.size(); ++i)
1101 if (devices.getReference (i).id == oboeDeviceId)
1102 return i;
1103 }
1104
1105 return -1;
1106 }
1107
hasSeparateInputsAndOutputs() const1108 bool hasSeparateInputsAndOutputs() const override { return true; }
1109
createDevice(const String & outputDeviceName,const String & inputDeviceName)1110 AudioIODevice* createDevice (const String& outputDeviceName,
1111 const String& inputDeviceName) override
1112 {
1113 auto outputDeviceInfo = getDeviceInfoForName (outputDeviceName, false);
1114 auto inputDeviceInfo = getDeviceInfoForName (inputDeviceName, true);
1115
1116 if (outputDeviceInfo.id < 0 && inputDeviceInfo.id < 0)
1117 return nullptr;
1118
1119 auto& name = outputDeviceInfo.name.isNotEmpty() ? outputDeviceInfo.name
1120 : inputDeviceInfo.name;
1121
1122 return new OboeAudioIODevice (name,
1123 inputDeviceInfo.id, inputDeviceInfo.sampleRates,
1124 inputDeviceInfo.numChannels,
1125 outputDeviceInfo.id, outputDeviceInfo.sampleRates,
1126 outputDeviceInfo.numChannels);
1127 }
1128
isOboeAvailable()1129 static bool isOboeAvailable()
1130 {
1131 #if JUCE_USE_ANDROID_OBOE
1132 return true;
1133 #else
1134 return false;
1135 #endif
1136 }
1137
1138 private:
checkAvailableDevices()1139 void checkAvailableDevices()
1140 {
1141 auto sampleRates = OboeAudioIODevice::getDefaultSampleRates();
1142
1143 inputDevices .add ({ "System Default (Input)", oboe::kUnspecified, sampleRates, 1 });
1144 outputDevices.add ({ "System Default (Output)", oboe::kUnspecified, sampleRates, 2 });
1145
1146 if (! supportsDevicesInfo())
1147 return;
1148
1149 auto* env = getEnv();
1150
1151 jclass audioManagerClass = env->FindClass ("android/media/AudioManager");
1152
1153 // We should be really entering here only if API supports it.
1154 jassert (audioManagerClass != nullptr);
1155
1156 if (audioManagerClass == nullptr)
1157 return;
1158
1159 auto audioManager = LocalRef<jobject> (env->CallObjectMethod (getAppContext().get(),
1160 AndroidContext.getSystemService,
1161 javaString ("audio").get()));
1162
1163 static jmethodID getDevicesMethod = env->GetMethodID (audioManagerClass, "getDevices",
1164 "(I)[Landroid/media/AudioDeviceInfo;");
1165
1166 static constexpr int allDevices = 3;
1167 auto devices = LocalRef<jobjectArray> ((jobjectArray) env->CallObjectMethod (audioManager,
1168 getDevicesMethod,
1169 allDevices));
1170
1171 const int numDevices = env->GetArrayLength (devices.get());
1172
1173 for (int i = 0; i < numDevices; ++i)
1174 {
1175 auto device = LocalRef<jobject> ((jobject) env->GetObjectArrayElement (devices.get(), i));
1176 addDevice (device, env);
1177 }
1178
1179 JUCE_OBOE_LOG ("-----InputDevices:");
1180
1181 for (auto& device : inputDevices)
1182 {
1183 ignoreUnused (device);
1184
1185 JUCE_OBOE_LOG ("name = " << device.name);
1186 JUCE_OBOE_LOG ("id = " << String (device.id));
1187 JUCE_OBOE_LOG ("sample rates size = " << String (device.sampleRates.size()));
1188 JUCE_OBOE_LOG ("num channels = " + String (device.numChannels));
1189 }
1190
1191 JUCE_OBOE_LOG ("-----OutputDevices:");
1192
1193 for (auto& device : outputDevices)
1194 {
1195 ignoreUnused (device);
1196
1197 JUCE_OBOE_LOG ("name = " << device.name);
1198 JUCE_OBOE_LOG ("id = " << String (device.id));
1199 JUCE_OBOE_LOG ("sample rates size = " << String (device.sampleRates.size()));
1200 JUCE_OBOE_LOG ("num channels = " + String (device.numChannels));
1201 }
1202 }
1203
supportsDevicesInfo() const1204 bool supportsDevicesInfo() const
1205 {
1206 static auto result = getAndroidSDKVersion() >= 23;
1207 return result;
1208 }
1209
addDevice(const LocalRef<jobject> & device,JNIEnv * env)1210 void addDevice (const LocalRef<jobject>& device, JNIEnv* env)
1211 {
1212 auto deviceClass = LocalRef<jclass> ((jclass) env->FindClass ("android/media/AudioDeviceInfo"));
1213
1214 jmethodID getProductNameMethod = env->GetMethodID (deviceClass, "getProductName",
1215 "()Ljava/lang/CharSequence;");
1216
1217 jmethodID getTypeMethod = env->GetMethodID (deviceClass, "getType", "()I");
1218 jmethodID getIdMethod = env->GetMethodID (deviceClass, "getId", "()I");
1219 jmethodID getSampleRatesMethod = env->GetMethodID (deviceClass, "getSampleRates", "()[I");
1220 jmethodID getChannelCountsMethod = env->GetMethodID (deviceClass, "getChannelCounts", "()[I");
1221 jmethodID isSourceMethod = env->GetMethodID (deviceClass, "isSource", "()Z");
1222
1223 auto deviceTypeString = deviceTypeToString (env->CallIntMethod (device, getTypeMethod));
1224
1225 if (deviceTypeString.isEmpty()) // unknown device
1226 return;
1227
1228 auto name = juceString ((jstring) env->CallObjectMethod (device, getProductNameMethod)) + " " + deviceTypeString;
1229 auto id = env->CallIntMethod (device, getIdMethod);
1230
1231 auto jSampleRates = LocalRef<jintArray> ((jintArray) env->CallObjectMethod (device, getSampleRatesMethod));
1232 auto sampleRates = jintArrayToJuceArray (jSampleRates);
1233
1234 auto jChannelCounts = LocalRef<jintArray> ((jintArray) env->CallObjectMethod (device, getChannelCountsMethod));
1235 auto channelCounts = jintArrayToJuceArray (jChannelCounts);
1236 int numChannels = channelCounts.isEmpty() ? -1 : channelCounts.getLast();
1237
1238 auto isInput = env->CallBooleanMethod (device, isSourceMethod);
1239 auto& devices = isInput ? inputDevices : outputDevices;
1240
1241 devices.add ({ name, id, sampleRates, numChannels });
1242 }
1243
deviceTypeToString(int type)1244 static String deviceTypeToString (int type)
1245 {
1246 switch (type)
1247 {
1248 case 0: return {};
1249 case 1: return "built-in earphone speaker";
1250 case 2: return "built-in speaker";
1251 case 3: return "wired headset";
1252 case 4: return "wired headphones";
1253 case 5: return "line analog";
1254 case 6: return "line digital";
1255 case 7: return "Bluetooth device typically used for telephony";
1256 case 8: return "Bluetooth device supporting the A2DP profile";
1257 case 9: return "HDMI";
1258 case 10: return "HDMI audio return channel";
1259 case 11: return "USB device";
1260 case 12: return "USB accessory";
1261 case 13: return "DOCK";
1262 case 14: return "FM";
1263 case 15: return "built-in microphone";
1264 case 16: return "FM tuner";
1265 case 17: return "TV tuner";
1266 case 18: return "telephony";
1267 case 19: return "auxiliary line-level connectors";
1268 case 20: return "IP";
1269 case 21: return "BUS";
1270 case 22: return "USB headset";
1271 case 23: return "hearing aid";
1272 case 24: return "built-in speaker safe";
1273 case 25: return {};
1274 default: jassertfalse; return {}; // type not supported yet, needs to be added!
1275 }
1276 }
1277
jintArrayToJuceArray(const LocalRef<jintArray> & jArray)1278 static Array<int> jintArrayToJuceArray (const LocalRef<jintArray>& jArray)
1279 {
1280 auto* env = getEnv();
1281
1282 jint* jArrayElems = env->GetIntArrayElements (jArray, nullptr);
1283 int numElems = env->GetArrayLength (jArray);
1284
1285 Array<int> juceArray;
1286
1287 for (int s = 0; s < numElems; ++s)
1288 juceArray.add (jArrayElems[s]);
1289
1290 env->ReleaseIntArrayElements (jArray, jArrayElems, 0);
1291 return juceArray;
1292 }
1293
1294 struct DeviceInfo
1295 {
1296 String name;
1297 int id = -1;
1298 Array<int> sampleRates;
1299 int numChannels;
1300 };
1301
getDeviceInfoForName(const String & name,bool isInput)1302 DeviceInfo getDeviceInfoForName (const String& name, bool isInput)
1303 {
1304 if (name.isNotEmpty())
1305 {
1306 for (auto& device : isInput ? inputDevices : outputDevices)
1307 {
1308 if (device.name == name)
1309 return device;
1310 }
1311 }
1312
1313 return {};
1314 }
1315
1316 Array<DeviceInfo> inputDevices, outputDevices;
1317
1318 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OboeAudioIODeviceType)
1319 };
1320
1321 const char* const OboeAudioIODevice::oboeTypeName = "Android Oboe";
1322
isOboeAvailable()1323 bool isOboeAvailable() { return OboeAudioIODeviceType::isOboeAvailable(); }
1324
1325 //==============================================================================
1326 class OboeRealtimeThread : private oboe::AudioStreamCallback
1327 {
1328 using OboeStream = OboeAudioIODevice::OboeStream;
1329
1330 public:
OboeRealtimeThread()1331 OboeRealtimeThread()
1332 : testStream (new OboeStream (oboe::kUnspecified,
1333 oboe::Direction::Output,
1334 oboe::SharingMode::Exclusive,
1335 1,
1336 oboe::AudioFormat::Float,
1337 (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(),
1338 OboeAudioIODevice::getNativeBufferSize(),
1339 this)),
1340 formatUsed (oboe::AudioFormat::Float)
1341 {
1342 // Fallback to I16 stream format if Float has not worked
1343 if (! testStream->openedOk())
1344 {
1345 testStream.reset (new OboeStream (oboe::kUnspecified,
1346 oboe::Direction::Output,
1347 oboe::SharingMode::Exclusive,
1348 1,
1349 oboe::AudioFormat::I16,
1350 (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(),
1351 OboeAudioIODevice::getNativeBufferSize(),
1352 this));
1353
1354 formatUsed = oboe::AudioFormat::I16;
1355 }
1356
1357 parentThreadID = pthread_self();
1358
1359 pthread_cond_init (&threadReady, nullptr);
1360 pthread_mutex_init (&threadReadyMutex, nullptr);
1361 }
1362
isOk() const1363 bool isOk() const
1364 {
1365 return testStream != nullptr && testStream->openedOk();
1366 }
1367
startThread(void * (* entry)(void *),void * userPtr)1368 pthread_t startThread (void*(*entry)(void*), void* userPtr)
1369 {
1370 pthread_mutex_lock (&threadReadyMutex);
1371
1372 threadEntryProc = entry;
1373 threadUserPtr = userPtr;
1374
1375 testStream->start();
1376
1377 pthread_cond_wait (&threadReady, &threadReadyMutex);
1378 pthread_mutex_unlock (&threadReadyMutex);
1379
1380 return realtimeThreadID;
1381 }
1382
onAudioReady(oboe::AudioStream *,void *,int32_t)1383 oboe::DataCallbackResult onAudioReady (oboe::AudioStream*, void*, int32_t) override
1384 {
1385 // When running with OpenSL, the first callback will come on the parent thread.
1386 if (threadEntryProc != nullptr && ! pthread_equal (parentThreadID, pthread_self()))
1387 {
1388 pthread_mutex_lock (&threadReadyMutex);
1389
1390 realtimeThreadID = pthread_self();
1391
1392 pthread_cond_signal (&threadReady);
1393 pthread_mutex_unlock (&threadReadyMutex);
1394
1395 threadEntryProc (threadUserPtr);
1396 threadEntryProc = nullptr;
1397
1398 MessageManager::callAsync ([this]() { delete this; });
1399
1400 return oboe::DataCallbackResult::Stop;
1401 }
1402
1403 return oboe::DataCallbackResult::Continue;
1404 }
1405
onErrorBeforeClose(oboe::AudioStream *,oboe::Result error)1406 void onErrorBeforeClose (oboe::AudioStream*, oboe::Result error) override
1407 {
1408 JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorBeforeClose(): " + getOboeString (error));
1409 ignoreUnused (error);
1410 jassertfalse; // Should never get here!
1411 }
1412
onErrorAfterClose(oboe::AudioStream *,oboe::Result error)1413 void onErrorAfterClose (oboe::AudioStream*, oboe::Result error) override
1414 {
1415 JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorAfterClose(): " + getOboeString (error));
1416 ignoreUnused (error);
1417 jassertfalse; // Should never get here!
1418 }
1419
1420 private:
1421 //==============================================================================
1422 void* (*threadEntryProc) (void*) = nullptr;
1423 void* threadUserPtr = nullptr;
1424
1425 pthread_cond_t threadReady;
1426 pthread_mutex_t threadReadyMutex;
1427 pthread_t parentThreadID, realtimeThreadID;
1428
1429 std::unique_ptr<OboeStream> testStream;
1430 oboe::AudioFormat formatUsed;
1431 };
1432
1433 //==============================================================================
juce_createRealtimeAudioThread(void * (* entry)(void *),void * userPtr)1434 pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr)
1435 {
1436 auto thread = std::make_unique<OboeRealtimeThread>();
1437
1438 if (! thread->isOk())
1439 return {};
1440
1441 auto threadID = thread->startThread (entry, userPtr);
1442
1443 // the thread will de-allocate itself
1444 thread.release();
1445
1446 return threadID;
1447 }
1448
1449 } // namespace juce
1450