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