| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library.
 -    Copyright (c) 2018 - ROLI Ltd.
 - 
 -    JUCE is an open source library subject to commercial or open-source
 -    licensing.
 - 
 -    The code included in this file is provided under the terms of the ISC license
 -    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
 -    To use, copy, modify, and/or distribute this software for any purpose with or
 -    without fee is hereby granted provided that the above copyright notice and
 -    this permission notice appear in all copies.
 - 
 -    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 -    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 -    DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - #ifndef JUCE_OBOE_LOG_ENABLED
 -  #define JUCE_OBOE_LOG_ENABLED 1
 - #endif
 - 
 - #if JUCE_OBOE_LOG_ENABLED
 -  #define JUCE_OBOE_LOG(x) DBG(x)
 - #else
 -  #define JUCE_OBOE_LOG(x) {}
 - #endif
 - 
 - namespace juce
 - {
 - 
 - template <typename OboeDataFormat>  struct OboeAudioIODeviceBufferHelpers {};
 - 
 - template<>
 - struct OboeAudioIODeviceBufferHelpers<int16>
 - {
 -     static oboe::AudioFormat oboeAudioFormat() { return oboe::AudioFormat::I16; }
 - 
 -     static constexpr int bitDepth() { return 16; }
 - 
 -     static void referAudioBufferDirectlyToOboeIfPossible (int16*, AudioBuffer<float>&, int) {}
 - 
 -     static void convertFromOboe (const int16* srcInterleaved, AudioBuffer<float>& audioBuffer, int numSamples)
 -     {
 -         for (int i = 0; i < audioBuffer.getNumChannels(); ++i)
 -         {
 -             using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
 -             using SrcSampleType = AudioData::Pointer<AudioData::Int16,   AudioData::NativeEndian, AudioData::Interleaved,    AudioData::Const>;
 - 
 -             DstSampleType dstData (audioBuffer.getWritePointer (i));
 -             SrcSampleType srcData (srcInterleaved + i, audioBuffer.getNumChannels());
 -             dstData.convertSamples (srcData, numSamples);
 -         }
 -     }
 - 
 -     static void convertToOboe (const AudioBuffer<float>& audioBuffer, int16* dstInterleaved, int numSamples)
 -     {
 -         for (int i = 0; i < audioBuffer.getNumChannels(); ++i)
 -         {
 -             using DstSampleType = AudioData::Pointer<AudioData::Int16,   AudioData::NativeEndian, AudioData::Interleaved,    AudioData::NonConst>;
 -             using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
 - 
 -             DstSampleType dstData (dstInterleaved + i, audioBuffer.getNumChannels());
 -             SrcSampleType srcData (audioBuffer.getReadPointer (i));
 -             dstData.convertSamples (srcData, numSamples);
 -         }
 -     }
 - };
 - 
 - template<>
 - struct OboeAudioIODeviceBufferHelpers<float>
 - {
 -     static oboe::AudioFormat oboeAudioFormat() { return oboe::AudioFormat::Float; }
 - 
 -     static constexpr int bitDepth() { return 32; }
 - 
 -     static void referAudioBufferDirectlyToOboeIfPossible (float* nativeBuffer, AudioBuffer<float>& audioBuffer, int numSamples)
 -     {
 -         if (audioBuffer.getNumChannels() == 1)
 -             audioBuffer.setDataToReferTo (&nativeBuffer, 1, numSamples);
 -     }
 - 
 -     static void convertFromOboe (const float* srcInterleaved, AudioBuffer<float>& audioBuffer, int numSamples)
 -     {
 -         // No need to convert, we instructed the buffer to point to the src data directly already
 -         if (audioBuffer.getNumChannels() == 1)
 -         {
 -             jassert (audioBuffer.getWritePointer (0) == srcInterleaved);
 -             return;
 -         }
 - 
 -         for (int i = 0; i < audioBuffer.getNumChannels(); ++i)
 -         {
 -             using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
 -             using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved,    AudioData::Const>;
 - 
 -             DstSampleType dstData (audioBuffer.getWritePointer (i));
 -             SrcSampleType srcData (srcInterleaved + i, audioBuffer.getNumChannels());
 -             dstData.convertSamples (srcData, numSamples);
 -         }
 -     }
 - 
 -     static void convertToOboe (const AudioBuffer<float>& audioBuffer, float* dstInterleaved, int numSamples)
 -     {
 -         // No need to convert, we instructed the buffer to point to the src data directly already
 -         if (audioBuffer.getNumChannels() == 1)
 -         {
 -             jassert (audioBuffer.getReadPointer (0) == dstInterleaved);
 -             return;
 -         }
 - 
 -         for (int i = 0; i < audioBuffer.getNumChannels(); ++i)
 -         {
 -             using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved,    AudioData::NonConst>;
 -             using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
 - 
 -             DstSampleType dstData (dstInterleaved + i, audioBuffer.getNumChannels());
 -             SrcSampleType srcData (audioBuffer.getReadPointer (i));
 -             dstData.convertSamples (srcData, numSamples);
 -         }
 -     }
 - };
 - 
 - //==============================================================================
 - class OboeAudioIODevice  : public AudioIODevice
 - {
 - public:
 -     //==============================================================================
 -     OboeAudioIODevice (const String& deviceName,
 -                        int inputDeviceIdToUse,
 -                        const Array<int>& supportedInputSampleRatesToUse,
 -                        int maxNumInputChannelsToUse,
 -                        int outputDeviceIdToUse,
 -                        const Array<int>& supportedOutputSampleRatesToUse,
 -                        int maxNumOutputChannelsToUse)
 -         : AudioIODevice (deviceName, oboeTypeName),
 -           inputDeviceId (inputDeviceIdToUse),
 -           supportedInputSampleRates (supportedInputSampleRatesToUse),
 -           maxNumInputChannels (maxNumInputChannelsToUse),
 -           outputDeviceId (outputDeviceIdToUse),
 -           supportedOutputSampleRates (supportedOutputSampleRatesToUse),
 -           maxNumOutputChannels (maxNumOutputChannelsToUse)
 -     {
 -         // At least an input or an output has to be supported by the device!
 -         jassert (inputDeviceId != -1 || outputDeviceId != -1);
 -     }
 - 
 -     ~OboeAudioIODevice()
 -     {
 -         close();
 -     }
 - 
 -     StringArray getOutputChannelNames() override    { return getChannelNames (false); }
 -     StringArray getInputChannelNames() override     { return getChannelNames (true); }
 - 
 -     Array<double> getAvailableSampleRates() override
 -     {
 -         Array<double> result;
 - 
 -         auto inputSampleRates  = getAvailableSampleRates (true);
 -         auto outputSampleRates = getAvailableSampleRates (false);
 - 
 -         if (inputDeviceId == -1)
 -         {
 -             for (auto& sr : outputSampleRates)
 -                 result.add (sr);
 -         }
 -         else if (outputDeviceId == -1)
 -         {
 -             for (auto& sr : inputSampleRates)
 -                 result.add (sr);
 -         }
 -         else
 -         {
 -             // For best performance, the same sample rate should be used for input and output,
 -             for (auto& inputSampleRate : inputSampleRates)
 -             {
 -                 if (outputSampleRates.contains (inputSampleRate))
 -                     result.add (inputSampleRate);
 -             }
 -         }
 - 
 -         // either invalid device was requested or its input&output don't have compatible sample rate
 -         jassert (result.size() > 0);
 -         return result;
 -     }
 - 
 -     Array<int> getAvailableBufferSizes() override
 -     {
 -         // we need to offer the lowest possible buffer size which
 -         // is the native buffer size
 -         const int defaultNumMultiples = 8;
 -         const int nativeBufferSize = getNativeBufferSize();
 -         Array<int> bufferSizes;
 - 
 -         for (int i = 1; i < defaultNumMultiples; ++i)
 -             bufferSizes.add (i * nativeBufferSize);
 - 
 -         return bufferSizes;
 -     }
 - 
 -     String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
 -                  double requestedSampleRate, int bufferSize) override
 -     {
 -         close();
 - 
 -         lastError.clear();
 -         sampleRate = (int) requestedSampleRate;
 - 
 -         actualBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;
 - 
 -         // The device may report no max, claiming "no limits". Pick sensible defaults.
 -         int maxOutChans = maxNumOutputChannels > 0 ? maxNumOutputChannels : 2;
 -         int maxInChans  = maxNumInputChannels  > 0 ? maxNumInputChannels : 1;
 - 
 -         activeOutputChans = outputChannels;
 -         activeOutputChans.setRange (maxOutChans,
 -                                     activeOutputChans.getHighestBit() + 1 - maxOutChans,
 -                                     false);
 - 
 -         activeInputChans = inputChannels;
 -         activeInputChans.setRange (maxInChans,
 -                                    activeInputChans.getHighestBit() + 1 - maxInChans,
 -                                    false);
 - 
 -         int numOutputChans = activeOutputChans.countNumberOfSetBits();
 -         int numInputChans = activeInputChans.countNumberOfSetBits();
 - 
 -         if (numInputChans > 0 && (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)))
 -         {
 -             // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio
 -             // before trying to open an audio input device. This is not going to work!
 -             jassertfalse;
 -             lastError = "Error opening Oboe input device: the app was not granted android.permission.RECORD_AUDIO";
 -         }
 - 
 -         // At least one output channel should be set!
 -         jassert (numOutputChans >= 0);
 - 
 -         session.reset (OboeSessionBase::create (*this,
 -                                                 inputDeviceId, outputDeviceId,
 -                                                 numInputChans, numOutputChans,
 -                                                 sampleRate, actualBufferSize));
 - 
 -         deviceOpen = session != nullptr;
 - 
 -         if (! deviceOpen)
 -             lastError = "Failed to create audio session";
 - 
 -         return lastError;
 -     }
 - 
 -     void close() override                               { stop(); }
 -     int getOutputLatencyInSamples() override            { return session->getOutputLatencyInSamples(); }
 -     int getInputLatencyInSamples() override             { return session->getInputLatencyInSamples(); }
 -     bool isOpen() override                              { return deviceOpen; }
 -     int getCurrentBufferSizeSamples() override          { return actualBufferSize; }
 -     int getCurrentBitDepth() override                   { return session->getCurrentBitDepth(); }
 -     BigInteger getActiveOutputChannels() const override { return activeOutputChans; }
 -     BigInteger getActiveInputChannels() const override  { return activeInputChans; }
 -     String getLastError() override                      { return lastError; }
 -     bool isPlaying() override                           { return callback.get() != nullptr; }
 -     int getXRunCount() const noexcept override          { return session->getXRunCount(); }
 - 
 -     int getDefaultBufferSize() override
 -     {
 -         // Only on a Pro-Audio device will we set the lowest possible buffer size
 -         // by default. We need to be more conservative on other devices
 -         // as they may be low-latency, but still have a crappy CPU.
 -         return (isProAudioDevice() ? 1 : 6)
 -                  * defaultBufferSizeIsMultipleOfNative * getNativeBufferSize();
 -     }
 - 
 -     double getCurrentSampleRate() override
 -     {
 -         return (sampleRate == 0.0 ? getNativeSampleRate() : sampleRate);
 -     }
 - 
 -     void start (AudioIODeviceCallback* newCallback) override
 -     {
 -         if (callback.get() != newCallback)
 -         {
 -             if (newCallback != nullptr)
 -                 newCallback->audioDeviceAboutToStart (this);
 - 
 -             AudioIODeviceCallback* oldCallback = callback.get();
 - 
 -             if (oldCallback != nullptr)
 -             {
 -                 // already running
 -                 if (newCallback == nullptr)
 -                     stop();
 -                 else
 -                     setCallback (newCallback);
 - 
 -                 oldCallback->audioDeviceStopped();
 -             }
 -             else
 -             {
 -                 jassert (newCallback != nullptr);
 - 
 -                 // session hasn't started yet
 -                 setCallback (newCallback);
 -                 running = true;
 - 
 -                 session->start();
 -             }
 - 
 -             callback = newCallback;
 -         }
 -     }
 - 
 -     void stop() override
 -     {
 -         if (session != nullptr)
 -             session->stop();
 - 
 -         running = false;
 -         setCallback (nullptr);
 -     }
 - 
 -     bool setAudioPreprocessingEnabled (bool) override
 -     {
 -         // Oboe does not expose this setting, yet it may use preprocessing
 -         // for older APIs running OpenSL
 -         return false;
 -     }
 - 
 -     static const char* const oboeTypeName;
 - 
 - private:
 -     StringArray getChannelNames (bool forInput)
 -     {
 -         auto& deviceId = forInput ? inputDeviceId : outputDeviceId;
 -         auto& numChannels = forInput ? maxNumInputChannels : maxNumOutputChannels;
 - 
 -         // If the device id is unknown (on olders APIs) or if the device claims to
 -         // support "any" channel count, use a sensible default
 -         if (deviceId == -1 || numChannels == -1)
 -             return forInput ? StringArray ("Input") : StringArray ("Left", "Right");
 - 
 -         StringArray names;
 - 
 -         for (int i = 0; i < numChannels; ++i)
 -             names.add ("Channel " + String (i + 1));
 - 
 -         return names;
 -     }
 - 
 -     Array<int> getAvailableSampleRates (bool forInput)
 -     {
 -         auto& supportedSampleRates = forInput
 -             ? supportedInputSampleRates
 -             : supportedOutputSampleRates;
 - 
 -         if (! supportedSampleRates.isEmpty())
 -             return supportedSampleRates;
 - 
 -         // device claims that it supports "any" sample rate, use
 -         // standard ones then
 -         return getDefaultSampleRates();
 -     }
 - 
 -     static Array<int> getDefaultSampleRates()
 -     {
 -         static const int standardRates[] = { 8000, 11025, 12000, 16000,
 -                                             22050, 24000, 32000, 44100, 48000 };
 -         Array<int> rates (standardRates, numElementsInArray (standardRates));
 - 
 -         // make sure the native sample rate is part of the list
 -         int native = (int) getNativeSampleRate();
 - 
 -         if (native != 0 && ! rates.contains (native))
 -             rates.add (native);
 - 
 -         return rates;
 -     }
 - 
 -     void setCallback (AudioIODeviceCallback* callbackToUse)
 -     {
 -         if (! running)
 -         {
 -             callback.set (callbackToUse);
 -             return;
 -         }
 - 
 -         // Setting nullptr callback is allowed only when playback is stopped.
 -         jassert (callbackToUse != nullptr);
 - 
 -         while (true)
 -         {
 -             AudioIODeviceCallback* old = callback.get();
 - 
 -             if (old == callbackToUse)
 -                 break;
 - 
 -             // If old is nullptr, then it means that it's currently being used!
 -             if (old != nullptr && callback.compareAndSetBool (callbackToUse, old))
 -                 break;
 - 
 -             Thread::sleep (1);
 -         }
 -     }
 - 
 -     void process (const float** inputChannelData, int numInputChannels,
 -                   float** outputChannelData, int numOutputChannels, int32_t numFrames)
 -     {
 -         if (AudioIODeviceCallback* cb = callback.exchange (nullptr))
 -         {
 -             cb->audioDeviceIOCallback (inputChannelData, numInputChannels,
 -                                        outputChannelData, numOutputChannels, numFrames);
 -             callback.set (cb);
 -         }
 -         else
 -         {
 -             for (int i = 0; i < numOutputChannels; ++i)
 -                 zeromem (outputChannelData[i], sizeof (float) * static_cast<size_t> (numFrames));
 -         }
 -     }
 - 
 -     //==============================================================================
 -     class OboeStream
 -     {
 -     public:
 -         OboeStream (int deviceId, oboe::Direction direction,
 -                     oboe::SharingMode sharingMode,
 -                     int channelCount, oboe::AudioFormat format,
 -                     int32 sampleRate, int32 bufferSize,
 -                     oboe::AudioStreamCallback* callback = nullptr)
 -         {
 -             open (deviceId, direction, sharingMode, channelCount,
 -                   format, sampleRate, bufferSize, callback);
 -         }
 - 
 -         ~OboeStream()
 -         {
 -             // AudioStreamCallback can only be deleted when stream has been closed
 -             close();
 -         }
 - 
 -         bool openedOk() const noexcept
 -         {
 -             return openResult == oboe::Result::OK;
 -         }
 - 
 -         void start()
 -         {
 -             jassert (openedOk());
 - 
 -             if (openedOk() && stream != nullptr)
 -             {
 -                 auto expectedState = oboe::StreamState::Starting;
 -                 auto nextState = oboe::StreamState::Started;
 -                 int64 timeoutNanos = 1000 * oboe::kNanosPerMillisecond;
 - 
 -                 auto startResult = stream->requestStart();
 -                 JUCE_OBOE_LOG ("Requested Oboe stream start with result: " + String (oboe::convertToText (startResult)));
 - 
 -                 startResult = stream->waitForStateChange (expectedState, &nextState, timeoutNanos);
 -                 JUCE_OBOE_LOG ("Starting Oboe stream with result: " + String (oboe::convertToText (startResult));
 -                      + "\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
 -                      + "\nDirection = " + (stream != nullptr ? String (oboe::convertToText (stream->getDirection())) : String ("?"))
 -                      + "\nSharingMode = " + (stream != nullptr ? String (oboe::convertToText (stream->getSharingMode())) : String ("?"))
 -                      + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
 -                      + "\nFormat = " + (stream != nullptr ? String (oboe::convertToText (stream->getFormat())) : String ("?"))
 -                      + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
 -                      + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
 -                      + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
 -                      + "\nFramesPerBurst = " + (stream != nullptr ? String (stream->getFramesPerBurst()) : String ("?"))
 -                      + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
 -                      + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
 -                      + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
 -                      + "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency))
 -                      + "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?")));
 -             }
 -         }
 - 
 -         oboe::AudioStream* getNativeStream()
 -         {
 -             jassert (openedOk());
 - 
 -             return stream;
 -         }
 - 
 -         int getXRunCount() const
 -         {
 -             return stream != nullptr ? stream->getXRunCount() : 0;
 -         }
 - 
 -     private:
 -         void open (int deviceId, oboe::Direction direction,
 -                    oboe::SharingMode sharingMode,
 -                    int channelCount, oboe::AudioFormat format,
 -                    int32 sampleRate, int32 bufferSize,
 -                    oboe::AudioStreamCallback* callback = nullptr)
 -         {
 -             oboe::AudioStreamBuilder builder;
 - 
 -             if (deviceId != -1)
 -                 builder.setDeviceId (deviceId);
 - 
 -             static int defaultFramesPerBurst = getDefaultFramesPerBurst();
 - 
 -             // Note: letting OS to choose the buffer capacity & frames per callback.
 -             builder.setDirection (direction);
 -             builder.setSharingMode (sharingMode);
 -             builder.setChannelCount (channelCount);
 -             builder.setFormat (format);
 -             builder.setSampleRate (sampleRate);
 -             builder.setDefaultFramesPerBurst ((int32) defaultFramesPerBurst);
 -             builder.setPerformanceMode (oboe::PerformanceMode::LowLatency);
 -             builder.setCallback (callback);
 - 
 -             JUCE_OBOE_LOG (String ("Preparing Oboe stream with params:")
 -                  + "\nAAudio supported = " + String (int (builder.isAAudioSupported()))
 -                  + "\nAPI = " + String (oboe::convertToText (builder.getAudioApi()))
 -                  + "\nDeviceId = " + String (deviceId)
 -                  + "\nDirection = " + String (oboe::convertToText (direction))
 -                  + "\nSharingMode = " + String (oboe::convertToText (sharingMode))
 -                  + "\nChannelCount = " + String (channelCount)
 -                  + "\nFormat = " + String (oboe::convertToText (format))
 -                  + "\nSampleRate = " + String (sampleRate)
 -                  + "\nBufferSizeInFrames = " + String (bufferSize)
 -                  + "\nFramesPerBurst = " + String (defaultFramesPerBurst)
 -                  + "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency)));
 - 
 -             openResult = builder.openStream (&stream);
 -             JUCE_OBOE_LOG ("Building Oboe stream with result: " + String (oboe::convertToText (openResult))
 -                  + "\nStream state = " + (stream != nullptr ? String (oboe::convertToText (stream->getState())) : String ("?")));
 - 
 -             if (stream != nullptr)
 -                 stream->setBufferSizeInFrames (bufferSize);
 - 
 -             JUCE_OBOE_LOG (String ("Stream details:")
 -                  + "\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
 -                  + "\nDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?"))
 -                  + "\nDirection = " + (stream != nullptr ? String (oboe::convertToText (stream->getDirection())) : String ("?"))
 -                  + "\nSharingMode = " + (stream != nullptr ? String (oboe::convertToText (stream->getSharingMode())) : String ("?"))
 -                  + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
 -                  + "\nFormat = " + (stream != nullptr ? String (oboe::convertToText (stream->getFormat())) : String ("?"))
 -                  + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
 -                  + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
 -                  + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
 -                  + "\nFramesPerBurst = " + (stream != nullptr ? String (stream->getFramesPerBurst()) : String ("?"))
 -                  + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
 -                  + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
 -                  + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
 -                  + "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency)));
 -         }
 - 
 -         void close()
 -         {
 -             if (stream != nullptr)
 -             {
 -                 oboe::Result result = stream->close();
 -                 JUCE_OBOE_LOG ("Requested Oboe stream close with result: " + String (oboe::convertToText (result)));
 -             }
 -         }
 - 
 -         int getDefaultFramesPerBurst() const
 -         {
 -             // NB: this function only works for inbuilt speakers and headphones
 -             auto* env = getEnv();
 - 
 -             auto audioManager = LocalRef<jobject> (env->CallObjectMethod (android.activity,
 -                                                                           JuceAppActivity.getSystemService,
 -                                                                           javaString ("audio").get()));
 - 
 -             auto propertyJavaString = javaString ("android.media.property.OUTPUT_FRAMES_PER_BUFFER");
 - 
 -             auto framesPerBurstString = LocalRef<jstring> ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty,
 -                                                                                                         propertyJavaString.get()));
 - 
 -             return framesPerBurstString != 0 ? env->CallStaticIntMethod (JavaInteger, JavaInteger.parseInt, framesPerBurstString.get(), 10) : 192;
 -         }
 - 
 -         oboe::AudioStream* stream = nullptr;
 -         oboe::Result openResult;
 -     };
 - 
 -     //==============================================================================
 -     class OboeSessionBase   : protected oboe::AudioStreamCallback
 -     {
 -     public:
 -         static OboeSessionBase* create (OboeAudioIODevice& owner,
 -                                         int inputDeviceId, int outputDeviceId,
 -                                         int numInputChannels, int numOutputChannels,
 -                                         int sampleRate, int bufferSize);
 - 
 -         virtual void start() = 0;
 -         virtual void stop() = 0;
 -         virtual int getOutputLatencyInSamples() = 0;
 -         virtual int getInputLatencyInSamples() = 0;
 - 
 -         bool openedOk() const noexcept
 -         {
 -             if (inputStream != nullptr && ! inputStream->openedOk())
 -                 return false;
 - 
 -             return outputStream != nullptr && outputStream->openedOk();
 -         }
 - 
 -         int getCurrentBitDepth() const noexcept { return bitDepth; }
 - 
 -         int getXRunCount() const
 -         {
 -             int inputXRunCount  = jmax (0, inputStream  != nullptr ? inputStream->getXRunCount() : 0);
 -             int outputXRunCount = jmax (0, outputStream != nullptr ? outputStream->getXRunCount() : 0);
 - 
 -             return inputXRunCount + outputXRunCount;
 -         }
 - 
 -     protected:
 -         OboeSessionBase (OboeAudioIODevice& ownerToUse,
 -                          int inputDeviceIdToUse, int outputDeviceIdToUse,
 -                          int numInputChannelsToUse, int numOutputChannelsToUse,
 -                          int sampleRateToUse, int bufferSizeToUse,
 -                          oboe::AudioFormat streamFormatToUse,
 -                          int bitDepthToUse)
 -             : owner (ownerToUse),
 -               inputDeviceId (inputDeviceIdToUse),
 -               outputDeviceId (outputDeviceIdToUse),
 -               numInputChannels (numInputChannelsToUse),
 -               numOutputChannels (numOutputChannelsToUse),
 -               sampleRate (sampleRateToUse),
 -               bufferSize (bufferSizeToUse),
 -               streamFormat (streamFormatToUse),
 -               bitDepth (bitDepthToUse),
 -               outputStream (new OboeStream (outputDeviceId,
 -                                             oboe::Direction::Output,
 -                                             oboe::SharingMode::Exclusive,
 -                                             numOutputChannels,
 -                                             streamFormatToUse,
 -                                             sampleRateToUse,
 -                                             bufferSizeToUse,
 -                                             this))
 -         {
 -             if (numInputChannels > 0)
 -             {
 -                 inputStream.reset (new OboeStream (inputDeviceId,
 -                                                    oboe::Direction::Input,
 -                                                    oboe::SharingMode::Exclusive,
 -                                                    numInputChannels,
 -                                                    streamFormatToUse,
 -                                                    sampleRateToUse,
 -                                                    bufferSizeToUse,
 -                                                    nullptr));
 - 
 -                 if (inputStream->openedOk() && outputStream->openedOk())
 -                 {
 -                     // Input & output sample rates should match!
 -                     jassert (inputStream->getNativeStream()->getSampleRate()
 -                                == outputStream->getNativeStream()->getSampleRate());
 -                 }
 - 
 -                 checkStreamSetup (inputStream.get(), inputDeviceId, numInputChannels,
 -                                   sampleRate, bufferSize, streamFormat);
 -             }
 - 
 -             checkStreamSetup (outputStream.get(), outputDeviceId, numOutputChannels,
 -                               sampleRate, bufferSize, streamFormat);
 -         }
 - 
 -         // Not strictly required as these should not change, but recommended by Google anyway
 -         void checkStreamSetup (OboeStream* stream, int deviceId, int numChannels, int sampleRate,
 -                                int bufferSize, oboe::AudioFormat format)
 -         {
 -             auto* nativeStream = stream != nullptr ? stream->getNativeStream() : nullptr;
 - 
 -             if (nativeStream != nullptr)
 -             {
 -                 ignoreUnused (deviceId, numChannels, sampleRate, bufferSize);
 -                 ignoreUnused (streamFormat, bitDepth);
 - 
 -                 jassert (numChannels = nativeStream->getChannelCount());
 -                 jassert (sampleRate == nativeStream->getSampleRate());
 -                 jassert (format == nativeStream->getFormat());
 - 
 -                 if (nativeStream->usesAAudio())
 -                     jassert (bufferSize == nativeStream->getBufferSizeInFrames());
 -             }
 -         }
 - 
 -         int getBufferCapacityInFrames (bool forInput) const
 -         {
 -             auto& ptr = forInput ? inputStream : outputStream;
 - 
 -             if (ptr == nullptr || ! ptr->openedOk())
 -                 return 0;
 - 
 -             return ptr->getNativeStream()->getBufferCapacityInFrames();
 -         }
 - 
 -         OboeAudioIODevice& owner;
 -         int inputDeviceId, outputDeviceId;
 -         int numInputChannels, numOutputChannels;
 -         int sampleRate;
 -         int bufferSize;
 -         oboe::AudioFormat streamFormat;
 -         int bitDepth;
 - 
 -         std::unique_ptr<OboeStream> inputStream, outputStream;
 -     };
 - 
 -     //==============================================================================
 -     template <typename SampleType>
 -     class OboeSessionImpl   : public OboeSessionBase
 -     {
 -     public:
 -         OboeSessionImpl (OboeAudioIODevice& ownerToUse,
 -                          int inputDeviceId, int outputDeviceId,
 -                          int numInputChannelsToUse, int numOutputChannelsToUse,
 -                          int sampleRateToUse, int bufferSizeToUse)
 -             : OboeSessionBase (ownerToUse,
 -                                inputDeviceId, outputDeviceId,
 -                                numInputChannelsToUse, numOutputChannelsToUse,
 -                                sampleRateToUse, bufferSizeToUse,
 -                                OboeAudioIODeviceBufferHelpers<SampleType>::oboeAudioFormat(),
 -                                OboeAudioIODeviceBufferHelpers<SampleType>::bitDepth()),
 -               inputStreamNativeBuffer (static_cast<size_t> (numInputChannelsToUse * getBufferCapacityInFrames (true))),
 -               inputStreamSampleBuffer (numInputChannels, getBufferCapacityInFrames (true)),
 -               outputStreamSampleBuffer (numOutputChannels, getBufferCapacityInFrames (false))
 -         {
 -         }
 - 
 -         void start() override
 -         {
 -             audioCallbackGuard.set (0);
 - 
 -             if (inputStream != nullptr)
 -                 inputStream->start();
 - 
 -             outputStream->start();
 - 
 -             checkIsOutputLatencyDetectionSupported();
 -         }
 - 
 -         void stop() override
 -         {
 -             while (! audioCallbackGuard.compareAndSetBool (1, 0))
 -                 Thread::sleep (1);
 - 
 -             inputStream  = nullptr;
 -             outputStream = nullptr;
 - 
 -             audioCallbackGuard.set (0);
 -         }
 - 
 -         int getOutputLatencyInSamples() override    { return outputLatency; }
 -         int getInputLatencyInSamples() override     { return -1; }
 - 
 -     private:
 -         void checkIsOutputLatencyDetectionSupported()
 -         {
 -             if (! openedOk())
 -             {
 -                 isOutputLatencyDetectionSupported = false;
 -                 return;
 -             }
 - 
 -             auto result = outputStream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC, 0, 0);
 -             isOutputLatencyDetectionSupported = result != oboe::Result::ErrorUnimplemented;
 -         }
 - 
 -         oboe::DataCallbackResult onAudioReady (oboe::AudioStream* stream, void* audioData, int32_t numFrames) override
 -         {
 -             attachAndroidJNI();
 - 
 -             if (audioCallbackGuard.compareAndSetBool (1, 0))
 -             {
 -                 if (stream == nullptr)
 -                     return oboe::DataCallbackResult::Stop;
 - 
 -                 // only output stream should be the master stream receiving callbacks
 -                 jassert (stream->getDirection() == oboe::Direction::Output && stream == outputStream->getNativeStream());
 - 
 -                 //-----------------
 -                 // Read input from Oboe
 -                 inputStreamSampleBuffer.clear();
 -                 inputStreamNativeBuffer.calloc (static_cast<size_t> (numInputChannels * bufferSize));
 - 
 -                 if (inputStream != nullptr)
 -                 {
 -                     auto* nativeInputStream = inputStream->getNativeStream();
 - 
 -                     if (nativeInputStream->getFormat() != oboe::AudioFormat::I16 && nativeInputStream->getFormat() != oboe::AudioFormat::Float)
 -                     {
 -                         JUCE_OBOE_LOG ("Unsupported input stream audio format: " + String (oboe::convertToText (nativeInputStream->getFormat())));
 -                         jassertfalse;
 -                         return oboe::DataCallbackResult::Continue;
 -                     }
 - 
 -                     auto result = inputStream->getNativeStream()->read (inputStreamNativeBuffer.getData(), numFrames, 0);
 - 
 -                     if (result)
 -                     {
 -                         OboeAudioIODeviceBufferHelpers<SampleType>::referAudioBufferDirectlyToOboeIfPossible (inputStreamNativeBuffer.get(),
 -                                                                                                               inputStreamSampleBuffer,
 -                                                                                                               result.value());
 - 
 -                         OboeAudioIODeviceBufferHelpers<SampleType>::convertFromOboe (inputStreamNativeBuffer.get(), inputStreamSampleBuffer, result.value());
 -                     }
 -                     else
 -                     {
 -                         // Failed to read from input stream.
 -                         jassertfalse;
 -                     }
 -                 }
 - 
 -                 //-----------------
 -                 // Setup output buffer
 -                 outputStreamSampleBuffer.clear();
 - 
 -                 OboeAudioIODeviceBufferHelpers<SampleType>::referAudioBufferDirectlyToOboeIfPossible (static_cast<SampleType*> (audioData),
 -                                                                                                       outputStreamSampleBuffer,
 -                                                                                                       numFrames);
 - 
 -                 //-----------------
 -                 // Process
 -                 // NB: the number of samples read from the input can potentially differ from numFrames.
 -                 owner.process (inputStreamSampleBuffer.getArrayOfReadPointers(), numInputChannels,
 -                                outputStreamSampleBuffer.getArrayOfWritePointers(), numOutputChannels,
 -                                numFrames);
 - 
 -                 //-----------------
 -                 // Write output to Oboe
 -                 OboeAudioIODeviceBufferHelpers<SampleType>::convertToOboe (outputStreamSampleBuffer, static_cast<SampleType*> (audioData), numFrames);
 - 
 -                 if (isOutputLatencyDetectionSupported)
 -                     calculateOutputLatency();
 - 
 -                 audioCallbackGuard.set (0);
 -             }
 - 
 -             return oboe::DataCallbackResult::Continue;
 -         }
 - 
 -         void printStreamDebugInfo (oboe::AudioStream* stream)
 -         {
 -             ignoreUnused (stream);
 - 
 -             JUCE_OBOE_LOG ("\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
 -                  + "\nDirection = " + (stream != nullptr ? String (oboe::convertToText (stream->getDirection())) : String ("?"))
 -                  + "\nSharingMode = " + (stream != nullptr ? String (oboe::convertToText (stream->getSharingMode())) : String ("?"))
 -                  + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
 -                  + "\nFormat = " + (stream != nullptr ? String (oboe::convertToText (stream->getFormat())) : String ("?"))
 -                  + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
 -                  + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
 -                  + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
 -                  + "\nFramesPerBurst = " + (stream != nullptr ? String (stream->getFramesPerBurst()) : String ("?"))
 -                  + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
 -                  + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
 -                  + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
 -                  + "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency))
 -                  + "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?")));
 -         }
 - 
 -         void calculateOutputLatency()
 -         {
 -             // Sadly, Oboe uses non-portable int64_t (a.k.a. long on LP64 and long long on LLP64)
 -             int64_t lastWrittenAndPresentedFrameIndex = 0;
 -             int64_t lastFramePresentationTimeNanos = 0;
 - 
 -             auto result = outputStream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC,
 -                                                                          &lastWrittenAndPresentedFrameIndex,
 -                                                                          &lastFramePresentationTimeNanos);
 - 
 -             if (result != oboe::Result::OK)
 -                 return;
 - 
 -             int64_t currentNumFramesWritten = outputStream->getNativeStream()->getFramesWritten();
 -             int64_t framesDelta = currentNumFramesWritten - lastWrittenAndPresentedFrameIndex;
 -             int64_t timeDeltaNanos = framesDelta * oboe::kNanosPerSecond / sampleRate;
 - 
 -             int64_t nextPresentationTimeNanos = lastFramePresentationTimeNanos + timeDeltaNanos;
 -             int64_t nextFrameWriteTimeNanos = getCurrentTimeNanos();
 - 
 -             if (nextFrameWriteTimeNanos < 0)
 -                 return;
 - 
 -             outputLatency = (int) ((nextPresentationTimeNanos - nextFrameWriteTimeNanos) * sampleRate / oboe::kNanosPerSecond);
 -         }
 - 
 -         int64_t getCurrentTimeNanos()
 -         {
 -             timespec time;
 - 
 -             if (clock_gettime (CLOCK_MONOTONIC, &time) < 0)
 -                 return -1;
 - 
 -             return time.tv_sec * oboe::kNanosPerSecond + time.tv_nsec;
 -         }
 - 
 -         void onErrorBeforeClose (oboe::AudioStream* stream, oboe::Result error) override
 -         {
 -             // only output stream should be the master stream receiving callbacks
 -             jassert (stream->getDirection() == oboe::Direction::Output);
 - 
 -             JUCE_OBOE_LOG ("Oboe stream onErrorBeforeClose(): " + String (oboe::convertToText (error)));
 -             printStreamDebugInfo (stream);
 -         }
 - 
 -         void onErrorAfterClose (oboe::AudioStream* stream, oboe::Result error) override
 -         {
 -             // only output stream should be the master stream receiving callbacks
 -             jassert (stream->getDirection() == oboe::Direction::Output);
 - 
 -             JUCE_OBOE_LOG ("Oboe stream onErrorAfterClose(): " + String (oboe::convertToText (error)));
 - 
 -             if (error == oboe::Result::ErrorDisconnected)
 -             {
 -                 if (streamRestartGuard.compareAndSetBool (1, 0))
 -                 {
 -                     // Close, recreate, and start the stream, not much use in current one.
 -                     // Use default device id, to let the OS pick the best ID (since our was disconnected).
 - 
 -                     while (! audioCallbackGuard.compareAndSetBool (1, 0))
 -                         Thread::sleep (1);
 - 
 -                     outputStream = nullptr;
 -                     outputStream.reset (new OboeStream (-1,
 -                                                         oboe::Direction::Output,
 -                                                         oboe::SharingMode::Exclusive,
 -                                                         numOutputChannels,
 -                                                         streamFormat,
 -                                                         sampleRate,
 -                                                         bufferSize,
 -                                                         this));
 - 
 -                     outputStream->start();
 - 
 -                     audioCallbackGuard.set (0);
 -                     streamRestartGuard.set (0);
 -                 }
 -             }
 -         }
 - 
 -         HeapBlock<SampleType> inputStreamNativeBuffer;
 -         AudioBuffer<float> inputStreamSampleBuffer,
 -                            outputStreamSampleBuffer;
 -         Atomic<int> audioCallbackGuard { 0 },
 -                     streamRestartGuard { 0 };
 - 
 -         bool isOutputLatencyDetectionSupported = true;
 -         int outputLatency = -1;
 -     };
 - 
 -     //==============================================================================
 -     friend class OboeAudioIODeviceType;
 -     friend class OboeRealtimeThread;
 - 
 -     //==============================================================================
 -     int actualBufferSize = 0, sampleRate = 0;
 -     bool deviceOpen = false;
 -     String lastError;
 -     BigInteger activeOutputChans, activeInputChans;
 -     Atomic<AudioIODeviceCallback*> callback { nullptr };
 - 
 -     int inputDeviceId;
 -     Array<int> supportedInputSampleRates;
 -     int maxNumInputChannels;
 -     int outputDeviceId;
 -     Array<int> supportedOutputSampleRates;
 -     int maxNumOutputChannels;
 - 
 -     std::unique_ptr<OboeSessionBase> session;
 - 
 -     bool running = false;
 - 
 -     enum
 -     {
 -         // These at the moment correspond to OpenSL settings.
 -         bufferSizeMultForLowLatency = 4,
 -         bufferSizeMultForSlowAudio = 8,
 -         defaultBufferSizeIsMultipleOfNative = 1
 -     };
 - 
 -     //==============================================================================
 -     static String audioManagerGetProperty (const String& property)
 -     {
 -         const LocalRef<jstring> jProperty (javaString (property));
 -         const LocalRef<jstring> text ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty,
 -                                                                                    jProperty.get()));
 -         if (text.get() != 0)
 -             return juceString (text);
 - 
 -         return {};
 -     }
 - 
 -     static bool androidHasSystemFeature (const String& property)
 -     {
 -         const LocalRef<jstring> jProperty (javaString (property));
 -         return android.activity.callBooleanMethod (JuceAppActivity.hasSystemFeature, jProperty.get());
 -     }
 - 
 -     static double getNativeSampleRate()
 -     {
 -         return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue();
 -     }
 - 
 -     static int getNativeBufferSize()
 -     {
 -         auto val = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue();
 -         return val > 0 ? val : 512;
 -     }
 - 
 -     static bool isProAudioDevice()
 -     {
 -         return androidHasSystemFeature ("android.hardware.audio.pro");
 -     }
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OboeAudioIODevice)
 - };
 - 
 - //==============================================================================
 - OboeAudioIODevice::OboeSessionBase* OboeAudioIODevice::OboeSessionBase::create (OboeAudioIODevice& owner,
 -                                                                                 int inputDeviceId,
 -                                                                                 int outputDeviceId,
 -                                                                                 int numInputChannels,
 -                                                                                 int numOutputChannels,
 -                                                                                 int sampleRate,
 -                                                                                 int bufferSize)
 - {
 - 
 -     std::unique_ptr<OboeSessionBase> session;
 -     auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT);
 - 
 -     // SDK versions 21 and higher should natively support floating point...
 -     if (sdkVersion >= 21)
 -     {
 -         session.reset (new OboeSessionImpl<float> (owner, inputDeviceId, outputDeviceId,
 -                                                    numInputChannels, numOutputChannels, sampleRate, bufferSize));
 - 
 -         // ...however, some devices lie so re-try without floating point
 -         if (session != nullptr && (! session->openedOk()))
 -             session.reset();
 -     }
 - 
 -     if (session == nullptr)
 -     {
 -         session.reset (new OboeSessionImpl<int16> (owner, inputDeviceId, outputDeviceId,
 -                                                    numInputChannels, numOutputChannels, sampleRate, bufferSize));
 - 
 -         if (session != nullptr && (! session->openedOk()))
 -             session.reset();
 -     }
 - 
 -     return session.release();
 - }
 - 
 - //==============================================================================
 - class OboeAudioIODeviceType  : public AudioIODeviceType
 - {
 - public:
 -     OboeAudioIODeviceType()
 -         : AudioIODeviceType (OboeAudioIODevice::oboeTypeName)
 -     {
 -         // Not using scanForDevices() to maintain behaviour backwards compatible with older APIs
 -         checkAvailableDevices();
 -     }
 - 
 -     //==============================================================================
 -     void scanForDevices() override {}
 - 
 -     StringArray getDeviceNames (bool wantInputNames) const override
 -     {
 -         if (inputDevices.isEmpty() && outputDevices.isEmpty())
 -             return StringArray (OboeAudioIODevice::oboeTypeName);
 - 
 -         StringArray names;
 - 
 -         for (auto& device : wantInputNames ? inputDevices : outputDevices)
 -             names.add (device.name);
 - 
 -         return names;
 -     }
 - 
 -     int getDefaultDeviceIndex (bool forInput) const override
 -     {
 -         // No need to create a stream when only one default device is created.
 -         if (! supportsDevicesInfo())
 -             return 0;
 - 
 -         if (forInput && (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)))
 -             return 0;
 - 
 -         // Create stream with a default device ID and query the stream for its device ID
 -         using OboeStream = OboeAudioIODevice::OboeStream;
 - 
 -         OboeStream tempStream (-1,
 -                                forInput ? oboe::Direction::Input : oboe::Direction::Output,
 -                                oboe::SharingMode::Shared,
 -                                forInput ? 1 : 2,
 -                                getSdkVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16,
 -                                (int) OboeAudioIODevice::getNativeSampleRate(),
 -                                OboeAudioIODevice::getNativeBufferSize(),
 -                                nullptr);
 - 
 -         if (auto* nativeStream = tempStream.getNativeStream())
 -         {
 -             auto& devices = forInput ? inputDevices : outputDevices;
 - 
 -             for (int i = 0; i < devices.size(); ++i)
 -                 if (devices.getReference (i).id == nativeStream->getDeviceId())
 -                     return i;
 -         }
 - 
 -         return 0;
 -     }
 - 
 -     int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
 -     {
 -         if (device == nullptr)
 -             return -1;
 - 
 -         auto* oboeDevice = static_cast<OboeAudioIODevice*> (device);
 -         auto oboeDeviceId = asInput ? oboeDevice->inputDeviceId
 -                                     : oboeDevice->outputDeviceId;
 - 
 -         auto& devices = asInput ? inputDevices : outputDevices;
 - 
 -         for (int i = 0; i < devices.size(); ++i)
 -             if (devices.getReference (i).id == oboeDeviceId)
 -                 return i;
 - 
 -         return -1;
 -     }
 - 
 -     bool hasSeparateInputsAndOutputs() const override  { return true; }
 - 
 -     AudioIODevice* createDevice (const String& outputDeviceName,
 -                                  const String& inputDeviceName) override
 -     {
 -         auto outputDeviceInfo = getDeviceInfoForName (outputDeviceName, false);
 -         auto inputDeviceInfo  = getDeviceInfoForName (inputDeviceName, true);
 - 
 -         if (outputDeviceInfo.name.isEmpty() && inputDeviceInfo.name.isEmpty())
 -         {
 -             // Invalid device name passed. It must be one of the names returned by getDeviceNames().
 -             jassertfalse;
 -             return nullptr;
 -         }
 - 
 -         auto& name = outputDeviceInfo.name.isNotEmpty() ? outputDeviceInfo.name
 -                                                         : inputDeviceInfo.name;
 - 
 -         return new OboeAudioIODevice (name,
 -                                       inputDeviceInfo.id, inputDeviceInfo.sampleRates,
 -                                       inputDeviceInfo.numChannels,
 -                                       outputDeviceInfo.id, outputDeviceInfo.sampleRates,
 -                                       outputDeviceInfo.numChannels);
 -     }
 - 
 -     static bool isOboeAvailable()
 -     {
 -        #if JUCE_USE_ANDROID_OBOE
 -         return true;
 -        #else
 -         return false;
 -        #endif
 -     }
 - 
 -  private:
 -     void checkAvailableDevices()
 -     {
 -         if (! supportsDevicesInfo())
 -         {
 -             auto sampleRates = OboeAudioIODevice::getDefaultSampleRates();
 - 
 -             inputDevices .add ({ OboeAudioIODevice::oboeTypeName, -1, sampleRates, 1 });
 -             outputDevices.add ({ OboeAudioIODevice::oboeTypeName, -1, sampleRates, 2 });
 - 
 -             return;
 -         }
 - 
 -         auto* env = getEnv();
 - 
 -         jclass audioManagerClass = env->FindClass ("android/media/AudioManager");
 - 
 -         // We should be really entering here only if API supports it.
 -         jassert (audioManagerClass != 0);
 - 
 -         if (audioManagerClass == 0)
 -             return;
 - 
 -         auto audioManager = LocalRef<jobject> (env->CallObjectMethod (android.activity,
 -                                                                       JuceAppActivity.getSystemService,
 -                                                                       javaString ("audio").get()));
 - 
 -         static jmethodID getDevicesMethod = env->GetMethodID (audioManagerClass, "getDevices",
 -                                                               "(I)[Landroid/media/AudioDeviceInfo;");
 - 
 -         static constexpr int allDevices = 3;
 -         auto devices = LocalRef<jobjectArray> ((jobjectArray) env->CallObjectMethod (audioManager,
 -                                                                                      getDevicesMethod,
 -                                                                                      allDevices));
 - 
 -         const int numDevices = env->GetArrayLength (devices.get());
 - 
 -         for (int i = 0; i < numDevices; ++i)
 -         {
 -             auto device = LocalRef<jobject> ((jobject) env->GetObjectArrayElement (devices.get(), i));
 -             addDevice (device, env);
 -         }
 - 
 -         JUCE_OBOE_LOG ("-----InputDevices:");
 - 
 -         for (auto& device : inputDevices)
 -         {
 -             JUCE_OBOE_LOG ("name = " << device.name);
 -             JUCE_OBOE_LOG ("id = " << String (device.id));
 -             JUCE_OBOE_LOG ("sample rates size = " << String (device.sampleRates.size()));
 -             JUCE_OBOE_LOG ("num channels = " + String (device.numChannels));
 -         }
 - 
 -         JUCE_OBOE_LOG ("-----OutputDevices:");
 - 
 -         for (auto& device : outputDevices)
 -         {
 -             JUCE_OBOE_LOG ("name = " << device.name);
 -             JUCE_OBOE_LOG ("id = " << String (device.id));
 -             JUCE_OBOE_LOG ("sample rates size = " << String (device.sampleRates.size()));
 -             JUCE_OBOE_LOG ("num channels = " + String (device.numChannels));
 -         }
 -     }
 - 
 -     bool supportsDevicesInfo() const
 -     {
 -         static auto result = getSdkVersion() >= 23;
 -         return result;
 -     }
 - 
 -     int getSdkVersion() const
 -     {
 -         static auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT);
 -         return sdkVersion;
 -     }
 - 
 -     void addDevice (const LocalRef<jobject>& device, JNIEnv* env)
 -     {
 -         auto deviceClass = LocalRef<jclass> ((jclass) env->FindClass ("android/media/AudioDeviceInfo"));
 - 
 -         jmethodID getProductNameMethod = env->GetMethodID (deviceClass, "getProductName",
 -                                                            "()Ljava/lang/CharSequence;");
 - 
 -         jmethodID getTypeMethod          = env->GetMethodID (deviceClass, "getType", "()I");
 -         jmethodID getIdMethod            = env->GetMethodID (deviceClass, "getId", "()I");
 -         jmethodID getSampleRatesMethod   = env->GetMethodID (deviceClass, "getSampleRates", "()[I");
 -         jmethodID getChannelCountsMethod = env->GetMethodID (deviceClass, "getChannelCounts", "()[I");
 -         jmethodID isSourceMethod         = env->GetMethodID (deviceClass, "isSource", "()Z");
 - 
 -         auto name = juceString ((jstring) env->CallObjectMethod (device, getProductNameMethod));
 -         name << deviceTypeToString (env->CallIntMethod (device, getTypeMethod));
 -         int id = env->CallIntMethod (device, getIdMethod);
 - 
 -         auto jSampleRates = LocalRef<jintArray> ((jintArray) env->CallObjectMethod (device, getSampleRatesMethod));
 -         auto sampleRates = jintArrayToJuceArray (jSampleRates);
 - 
 -         auto jChannelCounts = LocalRef<jintArray> ((jintArray) env->CallObjectMethod (device, getChannelCountsMethod));
 -         auto channelCounts = jintArrayToJuceArray (jChannelCounts);
 -         int numChannels = channelCounts.isEmpty() ? -1 : channelCounts.getLast();
 - 
 -         bool isInput  = env->CallBooleanMethod (device, isSourceMethod);
 -         auto& devices = isInput ? inputDevices : outputDevices;
 - 
 -         devices.add ({ name, id, sampleRates, numChannels });
 -     }
 - 
 -     static const char* deviceTypeToString (int type)
 -     {
 -         switch (type)
 -         {
 -             case 0:   return "";
 -             case 1:   return " built-in earphone speaker";
 -             case 2:   return " built-in speaker";
 -             case 3:   return " wired headset";
 -             case 4:   return " wired headphones";
 -             case 5:   return " line analog";
 -             case 6:   return " line digital";
 -             case 7:   return " Bluetooth device typically used for telephony";
 -             case 8:   return " Bluetooth device supporting the A2DP profile";
 -             case 9:   return " HDMI";
 -             case 10:  return " HDMI audio return channel";
 -             case 11:  return " USB device";
 -             case 12:  return " USB accessory";
 -             case 13:  return " DOCK";
 -             case 14:  return " FM";
 -             case 15:  return " built-in microphone";
 -             case 16:  return " FM tuner";
 -             case 17:  return " TV tuner";
 -             case 18:  return " telephony";
 -             case 19:  return " auxiliary line-level connectors";
 -             case 20:  return " IP";
 -             case 21:  return " BUS";
 -             case 22:  return " USB headset";
 -             default:  jassertfalse; return ""; // type not supported yet, needs to be added!
 -         }
 -     }
 - 
 -     static Array<int> jintArrayToJuceArray (const LocalRef<jintArray>& jArray)
 -     {
 -         auto* env = getEnv();
 - 
 -         jint* jArrayElems = env->GetIntArrayElements (jArray, 0);
 -         int numElems = env->GetArrayLength (jArray);
 - 
 -         Array<int> juceArray;
 - 
 -         for (int s = 0; s < numElems; ++s)
 -             juceArray.add (jArrayElems[s]);
 - 
 -         env->ReleaseIntArrayElements (jArray, jArrayElems, 0);
 -         return juceArray;
 -     }
 - 
 -     struct DeviceInfo
 -     {
 -         String name;
 -         int id;
 -         Array<int> sampleRates;
 -         int numChannels;
 -     };
 - 
 -     DeviceInfo getDeviceInfoForName (const String& name, bool isInput)
 -     {
 -         if (name.isEmpty())
 -             return {};
 - 
 -         for (auto& device : isInput ? inputDevices : outputDevices)
 -             if (device.name == name)
 -                 return device;
 - 
 -         return {};
 -     }
 - 
 -     Array<DeviceInfo> inputDevices, outputDevices;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OboeAudioIODeviceType)
 - };
 - 
 - const char* const OboeAudioIODevice::oboeTypeName = "Android Oboe";
 - 
 - 
 - //==============================================================================
 - bool isOboeAvailable()  { return OboeAudioIODeviceType::isOboeAvailable(); }
 - 
 - AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe()
 - {
 -     return isOboeAvailable() ? new OboeAudioIODeviceType() : nullptr;
 - }
 - 
 - //==============================================================================
 - class OboeRealtimeThread    : private oboe::AudioStreamCallback
 - {
 -     using OboeStream = OboeAudioIODevice::OboeStream;
 - 
 - public:
 -     OboeRealtimeThread()
 -         : testStream (new OboeStream (-1,
 -                                       oboe::Direction::Output,
 -                                       oboe::SharingMode::Exclusive,
 -                                       1,
 -                                       oboe::AudioFormat::Float,
 -                                       (int) OboeAudioIODevice::getNativeSampleRate(),
 -                                       OboeAudioIODevice::getNativeBufferSize(),
 -                                       this)),
 -           formatUsed (oboe::AudioFormat::Float)
 -     {
 -         // Fallback to I16 stream format if Float has not worked
 -         if (! testStream->openedOk())
 -         {
 -             testStream.reset (new OboeStream (-1,
 -                                               oboe::Direction::Output,
 -                                               oboe::SharingMode::Exclusive,
 -                                               1,
 -                                               oboe::AudioFormat::I16,
 -                                               (int) OboeAudioIODevice::getNativeSampleRate(),
 -                                               OboeAudioIODevice::getNativeBufferSize(),
 -                                               this));
 - 
 -             formatUsed = oboe::AudioFormat::I16;
 -         }
 - 
 -         parentThreadID = pthread_self();
 - 
 -         pthread_cond_init (&threadReady, nullptr);
 -         pthread_mutex_init (&threadReadyMutex, nullptr);
 -     }
 - 
 -     bool isOk() const
 -     {
 -         return testStream != nullptr && testStream->openedOk();
 -     }
 - 
 -     pthread_t startThread (void* (*entry) (void*), void* userPtr)
 -     {
 -         pthread_mutex_lock (&threadReadyMutex);
 - 
 -         threadEntryProc = entry;
 -         threadUserPtr  = userPtr;
 - 
 -         testStream->start();
 - 
 -         pthread_cond_wait (&threadReady, &threadReadyMutex);
 -         pthread_mutex_unlock (&threadReadyMutex);
 - 
 -         return realtimeThreadID;
 -     }
 - 
 -     oboe::DataCallbackResult onAudioReady (oboe::AudioStream*, void*, int32_t) override
 -     {
 -         // When running with OpenSL, the first callback will come on the parent thread.
 -         if (threadEntryProc != nullptr && ! pthread_equal (parentThreadID, pthread_self()))
 -         {
 -             pthread_mutex_lock (&threadReadyMutex);
 - 
 -             realtimeThreadID = pthread_self();
 - 
 -             pthread_cond_signal (&threadReady);
 -             pthread_mutex_unlock (&threadReadyMutex);
 - 
 -             threadEntryProc (threadUserPtr);
 -             threadEntryProc = nullptr;
 - 
 -             MessageManager::callAsync ([this] () { delete this; });
 - 
 -             return oboe::DataCallbackResult::Stop;
 -         }
 - 
 -         return oboe::DataCallbackResult::Continue;
 -     }
 - 
 -     void onErrorBeforeClose (oboe::AudioStream*, oboe::Result error) override
 -     {
 -         JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorBeforeClose(): " + String (oboe::convertToText (error)));
 -     }
 - 
 -     void onErrorAfterClose (oboe::AudioStream* stream, oboe::Result error) override
 -     {
 -         JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorAfterClose(): " + String (oboe::convertToText (error)));
 - 
 -         if (error == oboe::Result::ErrorDisconnected)
 -         {
 -             testStream.reset();
 -             testStream.reset (new OboeStream (-1,
 -                                               oboe::Direction::Output,
 -                                               oboe::SharingMode::Exclusive,
 -                                               1,
 -                                               formatUsed,
 -                                               (int) OboeAudioIODevice::getNativeSampleRate(),
 -                                               OboeAudioIODevice::getNativeBufferSize(),
 -                                               this));
 -             testStream->start();
 -         }
 -     }
 - 
 - private:
 -     //=============================================================================
 -     void* (*threadEntryProc) (void*) = nullptr;
 -     void* threadUserPtr = nullptr;
 - 
 -     pthread_cond_t  threadReady;
 -     pthread_mutex_t threadReadyMutex;
 -     pthread_t       parentThreadID, realtimeThreadID;
 - 
 -     std::unique_ptr<OboeStream> testStream;
 -     oboe::AudioFormat formatUsed;
 - };
 - 
 - pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr)
 - {
 -     std::unique_ptr<OboeRealtimeThread> thread (new OboeRealtimeThread());
 - 
 -     if (! thread->isOk())
 -         return {};
 - 
 -     auto threadID = thread->startThread (entry, userPtr);
 - 
 -     // the thread will de-allocate itself
 -     thread.release();
 - 
 -     return threadID;
 - }
 - 
 - } // namespace juce
 
 
  |