| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library.
 -    Copyright (c) 2022 - Raw Material Software Limited
 - 
 -    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 bool referAudioBufferDirectlyToOboeIfPossible (int16*, AudioBuffer<float>&, int)  { return false; }
 - 
 -     using NativeInt16   = AudioData::Format<AudioData::Int16, AudioData::NativeEndian>;
 -     using NativeFloat32 = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>;
 - 
 -     static void convertFromOboe (const int16* srcInterleaved, AudioBuffer<float>& audioBuffer, int numSamples)
 -     {
 -         const auto numChannels = audioBuffer.getNumChannels();
 - 
 -         AudioData::deinterleaveSamples (AudioData::InterleavedSource<NativeInt16>    { reinterpret_cast<const uint16*> (srcInterleaved), numChannels },
 -                                         AudioData::NonInterleavedDest<NativeFloat32> { audioBuffer.getArrayOfWritePointers(),            numChannels },
 -                                         numSamples);
 -     }
 - 
 -     static void convertToOboe (const AudioBuffer<float>& audioBuffer, int16* dstInterleaved, int numSamples)
 -     {
 -         const auto numChannels = audioBuffer.getNumChannels();
 - 
 -         AudioData::interleaveSamples (AudioData::NonInterleavedSource<NativeFloat32> { audioBuffer.getArrayOfReadPointers(),       numChannels },
 -                                       AudioData::InterleavedDest<NativeInt16>        { reinterpret_cast<uint16*> (dstInterleaved), numChannels },
 -                                       numSamples);
 -     }
 - };
 - 
 - template <>
 - struct OboeAudioIODeviceBufferHelpers<float>
 - {
 -     static oboe::AudioFormat oboeAudioFormat() { return oboe::AudioFormat::Float; }
 - 
 -     static constexpr int bitDepth() { return 32; }
 - 
 -     static bool referAudioBufferDirectlyToOboeIfPossible (float* nativeBuffer, AudioBuffer<float>& audioBuffer, int numSamples)
 -     {
 -         if (audioBuffer.getNumChannels() == 1)
 -         {
 -             audioBuffer.setDataToReferTo (&nativeBuffer, 1, numSamples);
 -             return true;
 -         }
 - 
 -         return false;
 -     }
 - 
 -     using Format = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>;
 - 
 -     static void convertFromOboe (const float* srcInterleaved, AudioBuffer<float>& audioBuffer, int numSamples)
 -     {
 -         auto numChannels = audioBuffer.getNumChannels();
 - 
 -         if (numChannels > 0)
 -         {
 -             // No need to convert, we instructed the buffer to point to the src data directly already
 -             jassert (audioBuffer.getWritePointer (0) != srcInterleaved);
 - 
 -             AudioData::deinterleaveSamples (AudioData::InterleavedSource<Format>  { srcInterleaved,                        numChannels },
 -                                             AudioData::NonInterleavedDest<Format> { audioBuffer.getArrayOfWritePointers(), numChannels },
 -                                             numSamples);
 -         }
 -     }
 - 
 -     static void convertToOboe (const AudioBuffer<float>& audioBuffer, float* dstInterleaved, int numSamples)
 -     {
 -         auto numChannels = audioBuffer.getNumChannels();
 - 
 -         if (numChannels > 0)
 -         {
 -             // No need to convert, we instructed the buffer to point to the src data directly already
 -             jassert (audioBuffer.getReadPointer (0) != dstInterleaved);
 - 
 -             AudioData::interleaveSamples (AudioData::NonInterleavedSource<Format> { audioBuffer.getArrayOfReadPointers(), numChannels },
 -                                           AudioData::InterleavedDest<Format>      { dstInterleaved,                       numChannels },
 -                                           numSamples);
 -         }
 -     }
 - };
 - 
 - template <typename Type>
 - static String getOboeString (const Type& value)
 - {
 -     return String (oboe::convertToText (value));
 - }
 - 
 - //==============================================================================
 - 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)
 -     {
 -     }
 - 
 -     ~OboeAudioIODevice() override
 -     {
 -         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
 -     {
 -         return AndroidHighPerformanceAudioHelpers::getAvailableBufferSizes (getNativeBufferSize(), getAvailableSampleRates());
 -     }
 - 
 -     String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
 -                  double requestedSampleRate, int bufferSize) override
 -     {
 -         close();
 - 
 -         lastError.clear();
 - 
 -         sampleRate = (int) (requestedSampleRate > 0 ? requestedSampleRate : AndroidHighPerformanceAudioHelpers::getNativeSampleRate());
 -         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
 -     {
 -         return AndroidHighPerformanceAudioHelpers::getDefaultBufferSize (getNativeBufferSize(), getCurrentSampleRate());
 -     }
 - 
 -     double getCurrentSampleRate() override
 -     {
 -         return (sampleRate == 0.0 ? AndroidHighPerformanceAudioHelpers::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) AndroidHighPerformanceAudioHelpers::getNativeSampleRate();
 - 
 -         if (native != 0 && ! rates.contains (native))
 -             rates.add (native);
 - 
 -         return rates;
 -     }
 - 
 -     static int getNativeBufferSize()
 -     {
 -         auto bufferSizeHint = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint();
 - 
 -         // providing a callback is required on some devices to get a FAST track, so we pass an
 -         // empty one to the temp stream to get the best available buffer size
 -         struct DummyCallback  : public oboe::AudioStreamCallback
 -         {
 -             oboe::DataCallbackResult onAudioReady (oboe::AudioStream*, void*, int32_t) override  { return oboe::DataCallbackResult::Stop; }
 -         };
 - 
 -         DummyCallback callback;
 - 
 -         // NB: Exclusive mode could be rejected if a device is already opened in that mode, so to get
 -         //     reliable results, only use this function when a device is closed.
 -         //     We initially try to open a stream with a buffer size returned from
 -         //     android.media.property.OUTPUT_FRAMES_PER_BUFFER property, but then we verify the actual
 -         //     size after the stream is open.
 -         OboeAudioIODevice::OboeStream tempStream (oboe::kUnspecified,
 -                                                   oboe::Direction::Output,
 -                                                   oboe::SharingMode::Exclusive,
 -                                                   2,
 -                                                   getAndroidSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16,
 -                                                   (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(),
 -                                                   bufferSizeHint,
 -                                                   &callback);
 - 
 -         if (auto* nativeStream = tempStream.getNativeStream())
 -             return nativeStream->getFramesPerBurst();
 - 
 -         return bufferSizeHint;
 -     }
 - 
 -     void setCallback (AudioIODeviceCallback* callbackToUse)
 -     {
 -         if (! running)
 -         {
 -             callback.set (callbackToUse);
 -             return;
 -         }
 - 
 -         // Setting nullptr callback is allowed only when playback is stopped.
 -         jassert (callbackToUse != nullptr);
 - 
 -         for (;;)
 -         {
 -             auto 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* const* inputChannelData, int numInputChannels,
 -                   float* const* outputChannelData, int numOutputChannels, int32_t numFrames)
 -     {
 -         if (auto* cb = callback.exchange (nullptr))
 -         {
 -             cb->audioDeviceIOCallbackWithContext (inputChannelData,
 -                                                   numInputChannels,
 -                                                   outputChannelData,
 -                                                   numOutputChannels,
 -                                                   numFrames,
 -                                                   {});
 -             callback.set (cb);
 -         }
 -         else
 -         {
 -             for (int i = 0; i < numOutputChannels; ++i)
 -                 zeromem (outputChannelData[i], (size_t) (numFrames) * sizeof (float));
 -         }
 -     }
 - 
 -     //==============================================================================
 -     class OboeStream
 -     {
 -     public:
 -         OboeStream (int deviceId, oboe::Direction direction,
 -                     oboe::SharingMode sharingMode,
 -                     int channelCount, oboe::AudioFormat format,
 -                     int32 sampleRateIn, int32 bufferSize,
 -                     oboe::AudioStreamCallback* callbackIn = nullptr)
 -         {
 -             open (deviceId, direction, sharingMode, channelCount,
 -                   format, sampleRateIn, bufferSize, callbackIn);
 -         }
 - 
 -         ~OboeStream()
 -         {
 -             close();
 -             delete stream;
 -         }
 - 
 -         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: " + getOboeString (startResult));
 - 
 -                 startResult = stream->waitForStateChange (expectedState, &nextState, timeoutNanos);
 - 
 -                 JUCE_OBOE_LOG ("Starting Oboe stream with result: " + getOboeString (startResult);
 -                                  + "\nUses AAudio = " + String ((int) stream->usesAAudio())
 -                                  + "\nDirection = " + getOboeString (stream->getDirection())
 -                                  + "\nSharingMode = " + getOboeString (stream->getSharingMode())
 -                                  + "\nChannelCount = " + String (stream->getChannelCount())
 -                                  + "\nFormat = " + getOboeString (stream->getFormat())
 -                                  + "\nSampleRate = " + String (stream->getSampleRate())
 -                                  + "\nBufferSizeInFrames = " + String (stream->getBufferSizeInFrames())
 -                                  + "\nBufferCapacityInFrames = " + String (stream->getBufferCapacityInFrames())
 -                                  + "\nFramesPerBurst = " + String (stream->getFramesPerBurst())
 -                                  + "\nFramesPerCallback = " + String (stream->getFramesPerCallback())
 -                                  + "\nBytesPerFrame = " + String (stream->getBytesPerFrame())
 -                                  + "\nBytesPerSample = " + String (stream->getBytesPerSample())
 -                                  + "\nPerformanceMode = " + getOboeString (stream->getPerformanceMode())
 -                                  + "\ngetDeviceId = " + String (stream->getDeviceId()));
 -             }
 -         }
 - 
 -         oboe::AudioStream* getNativeStream() const
 -         {
 -             jassert (openedOk());
 -             return stream;
 -         }
 - 
 -         int getXRunCount() const
 -         {
 -             if (stream != nullptr)
 -             {
 -                 auto count = stream->getXRunCount();
 - 
 -                 if (count)
 -                     return count.value();
 - 
 -                 JUCE_OBOE_LOG ("Failed to get Xrun count: " + getOboeString (count.error()));
 -             }
 - 
 -             return 0;
 -         }
 - 
 -     private:
 -         void open (int deviceId, oboe::Direction direction,
 -                    oboe::SharingMode sharingMode,
 -                    int channelCount, oboe::AudioFormat format,
 -                    int32 newSampleRate, int32 newBufferSize,
 -                    oboe::AudioStreamCallback* newCallback = nullptr)
 -         {
 -             oboe::DefaultStreamValues::FramesPerBurst = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint();
 - 
 -             oboe::AudioStreamBuilder builder;
 - 
 -             if (deviceId != -1)
 -                 builder.setDeviceId (deviceId);
 - 
 -             // 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 (newSampleRate);
 -             builder.setPerformanceMode (oboe::PerformanceMode::LowLatency);
 - 
 -            #if JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK
 -             if (newCallback != nullptr)
 -             {
 -                 stabilizedCallback = std::make_unique<oboe::StabilizedCallback> (newCallback);
 -                 builder.setCallback (stabilizedCallback.get());
 -             }
 -            #else
 -             builder.setCallback (newCallback);
 -            #endif
 - 
 -             JUCE_OBOE_LOG (String ("Preparing Oboe stream with params:")
 -                  + "\nAAudio supported = " + String (int (builder.isAAudioSupported()))
 -                  + "\nAPI = " + getOboeString (builder.getAudioApi())
 -                  + "\nDeviceId = " + String (deviceId)
 -                  + "\nDirection = " + getOboeString (direction)
 -                  + "\nSharingMode = " + getOboeString (sharingMode)
 -                  + "\nChannelCount = " + String (channelCount)
 -                  + "\nFormat = " + getOboeString (format)
 -                  + "\nSampleRate = " + String (newSampleRate)
 -                  + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency));
 - 
 -             openResult = builder.openStream (&stream);
 -             JUCE_OBOE_LOG ("Building Oboe stream with result: " + getOboeString (openResult)
 -                  + "\nStream state = " + (stream != nullptr ? getOboeString (stream->getState()) : String ("?")));
 - 
 -             if (stream != nullptr && newBufferSize != 0)
 -             {
 -                 JUCE_OBOE_LOG ("Setting the bufferSizeInFrames to " + String (newBufferSize));
 -                 stream->setBufferSizeInFrames (newBufferSize);
 -             }
 - 
 -             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 ? getOboeString (stream->getDirection()) : String ("?"))
 -                  + "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?"))
 -                  + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
 -                  + "\nFormat = " + (stream != nullptr ? getOboeString (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 = " + (stream != nullptr ? getOboeString (stream->getPerformanceMode()) : String ("?")));
 -         }
 - 
 -         void close()
 -         {
 -             if (stream != nullptr)
 -             {
 -                 [[maybe_unused]] oboe::Result result = stream->close();
 -                 JUCE_OBOE_LOG ("Requested Oboe stream close with result: " + getOboeString (result));
 -             }
 -         }
 - 
 -         oboe::AudioStream* stream = nullptr;
 -        #if JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK
 -         std::unique_ptr<oboe::StabilizedCallback> stabilizedCallback;
 -        #endif
 -         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,
 -                                [[maybe_unused]] int deviceId,
 -                                [[maybe_unused]] int numChannels,
 -                                [[maybe_unused]] int expectedSampleRate,
 -                                [[maybe_unused]] int expectedBufferSize,
 -                                oboe::AudioFormat format)
 -         {
 -             if ([[maybe_unused]] auto* nativeStream = stream != nullptr ? stream->getNativeStream() : nullptr)
 -             {
 -                 jassert (numChannels == 0 || numChannels == nativeStream->getChannelCount());
 -                 jassert (expectedSampleRate == 0 || expectedSampleRate == nativeStream->getSampleRate());
 -                 jassert (format == nativeStream->getFormat());
 -             }
 -         }
 - 
 -         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 inputDeviceIdIn, int outputDeviceIdIn,
 -                          int numInputChannelsToUse, int numOutputChannelsToUse,
 -                          int sampleRateToUse, int bufferSizeToUse)
 -             : OboeSessionBase (ownerToUse,
 -                                inputDeviceIdIn, outputDeviceIdIn,
 -                                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();
 - 
 -             isInputLatencyDetectionSupported  = isLatencyDetectionSupported (inputStream.get());
 -             isOutputLatencyDetectionSupported = isLatencyDetectionSupported (outputStream.get());
 -         }
 - 
 -         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 inputLatency; }
 - 
 -     private:
 -         bool isLatencyDetectionSupported (OboeStream* stream)
 -         {
 -             if (stream == nullptr || ! openedOk())
 -                 return false;
 - 
 -             auto result = stream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC, nullptr, nullptr);
 -             return result != oboe::Result::ErrorUnimplemented;
 -         }
 - 
 -         oboe::DataCallbackResult onAudioReady (oboe::AudioStream* stream, void* audioData, int32_t numFrames) override
 -         {
 -             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: " + getOboeString (nativeInputStream->getFormat()));
 -                         jassertfalse;
 -                         return oboe::DataCallbackResult::Continue;
 -                     }
 - 
 -                     auto result = inputStream->getNativeStream()->read (inputStreamNativeBuffer.getData(), numFrames, 0);
 - 
 -                     if (result)
 -                     {
 -                         auto referringDirectlyToOboeData = OboeAudioIODeviceBufferHelpers<SampleType>
 -                                                              ::referAudioBufferDirectlyToOboeIfPossible (inputStreamNativeBuffer.get(),
 -                                                                                                          inputStreamSampleBuffer,
 -                                                                                                          result.value());
 - 
 -                         if (! referringDirectlyToOboeData)
 -                             OboeAudioIODeviceBufferHelpers<SampleType>::convertFromOboe (inputStreamNativeBuffer.get(), inputStreamSampleBuffer, result.value());
 -                     }
 -                     else
 -                     {
 -                         JUCE_OBOE_LOG ("Failed to read from input stream: " + getOboeString (result.error()));
 -                     }
 - 
 -                     if (isInputLatencyDetectionSupported)
 -                         inputLatency = getLatencyFor (*inputStream);
 -                 }
 - 
 -                 // Setup output buffer
 -                 auto referringDirectlyToOboeData = OboeAudioIODeviceBufferHelpers<SampleType>
 -                                                      ::referAudioBufferDirectlyToOboeIfPossible (static_cast<SampleType*> (audioData),
 -                                                                                                  outputStreamSampleBuffer,
 -                                                                                                  numFrames);
 - 
 -                 if (! referringDirectlyToOboeData)
 -                     outputStreamSampleBuffer.clear();
 - 
 -                 // 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
 -                 if (! referringDirectlyToOboeData)
 -                     OboeAudioIODeviceBufferHelpers<SampleType>::convertToOboe (outputStreamSampleBuffer, static_cast<SampleType*> (audioData), numFrames);
 - 
 -                 if (isOutputLatencyDetectionSupported)
 -                     outputLatency = getLatencyFor (*outputStream);
 - 
 -                 audioCallbackGuard.set (0);
 -             }
 - 
 -             return oboe::DataCallbackResult::Continue;
 -         }
 - 
 -         void printStreamDebugInfo ([[maybe_unused]] oboe::AudioStream* stream)
 -         {
 -             JUCE_OBOE_LOG ("\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
 -                  + "\nDirection = " + (stream != nullptr ? getOboeString (stream->getDirection()) : String ("?"))
 -                  + "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?"))
 -                  + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
 -                  + "\nFormat = " + (stream != nullptr ? getOboeString (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 = " + (stream != nullptr ? getOboeString (stream->getPerformanceMode()) : String ("?"))
 -                  + "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?")));
 -         }
 - 
 -         int getLatencyFor (OboeStream& stream)
 -         {
 -             auto& nativeStream = *stream.getNativeStream();
 - 
 -             if (auto latency = nativeStream.calculateLatencyMillis())
 -                 return static_cast<int> ((latency.value() * sampleRate) / 1000);
 - 
 -             // Get the time that a known audio frame was presented.
 -             int64_t hardwareFrameIndex = 0;
 -             int64_t hardwareFrameHardwareTime = 0;
 - 
 -             auto result = nativeStream.getTimestamp (CLOCK_MONOTONIC,
 -                                                      &hardwareFrameIndex,
 -                                                      &hardwareFrameHardwareTime);
 - 
 -             if (result != oboe::Result::OK)
 -                 return 0;
 - 
 -             // Get counter closest to the app.
 -             const bool isOutput = nativeStream.getDirection() == oboe::Direction::Output;
 -             const int64_t appFrameIndex = isOutput ? nativeStream.getFramesWritten() : nativeStream.getFramesRead();
 - 
 -             // Assume that the next frame will be processed at the current time
 -             int64_t appFrameAppTime = getCurrentTimeNanos();
 - 
 -             // Calculate the number of frames between app and hardware
 -             int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex;
 - 
 -             // Calculate the time which the next frame will be or was presented
 -             int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / sampleRate;
 -             int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta;
 - 
 -             // Calculate latency as a difference in time between when the current frame is at the app
 -             // and when it is at the hardware.
 -             auto latencyNanos = isOutput ? (appFrameHardwareTime - appFrameAppTime) : (appFrameAppTime - appFrameHardwareTime);
 -             return static_cast<int> ((latencyNanos  * 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, [[maybe_unused]] 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(): " + getOboeString (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(): " + getOboeString (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 (oboe::kUnspecified,
 -                                                         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 isInputLatencyDetectionSupported = false;
 -         int inputLatency = -1;
 - 
 -         bool isOutputLatencyDetectionSupported = false;
 -         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;
 - 
 -     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 = getAndroidSDKVersion();
 - 
 -     // 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
 -     {
 -         StringArray names;
 - 
 -         for (auto& device : wantInputNames ? inputDevices : outputDevices)
 -             names.add (device.name);
 - 
 -         return names;
 -     }
 - 
 -     int getDefaultDeviceIndex (bool) const override
 -     {
 -         return 0;
 -     }
 - 
 -     int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
 -     {
 -         if (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.id < 0 && inputDeviceInfo.id < 0)
 -             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()
 -     {
 -         auto sampleRates = OboeAudioIODevice::getDefaultSampleRates();
 - 
 -         inputDevices .add ({ "System Default (Input)",  oboe::kUnspecified, sampleRates, 1 });
 -         outputDevices.add ({ "System Default (Output)", oboe::kUnspecified, sampleRates, 2 });
 - 
 -         if (! supportsDevicesInfo())
 -             return;
 - 
 -         auto* env = getEnv();
 - 
 -         jclass audioManagerClass = env->FindClass ("android/media/AudioManager");
 - 
 -         // We should be really entering here only if API supports it.
 -         jassert (audioManagerClass != nullptr);
 - 
 -         if (audioManagerClass == nullptr)
 -             return;
 - 
 -         auto audioManager = LocalRef<jobject> (env->CallObjectMethod (getAppContext().get(),
 -                                                                       AndroidContext.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 ([[maybe_unused]] 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 ([[maybe_unused]] 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 = getAndroidSDKVersion() >= 23;
 -         return result;
 -     }
 - 
 -     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 deviceTypeString = deviceTypeToString (env->CallIntMethod (device, getTypeMethod));
 - 
 -         if (deviceTypeString.isEmpty()) // unknown device
 -             return;
 - 
 -         auto name = juceString ((jstring) env->CallObjectMethod (device, getProductNameMethod)) + " " + deviceTypeString;
 -         auto 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();
 - 
 -         auto isInput  = env->CallBooleanMethod (device, isSourceMethod);
 -         auto& devices = isInput ? inputDevices : outputDevices;
 - 
 -         devices.add ({ name, id, sampleRates, numChannels });
 -     }
 - 
 -     static String 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";
 -             case 23:  return "hearing aid";
 -             case 24:  return "built-in speaker safe";
 -             case 25:  return "remote submix";
 -             case 26:  return "BLE headset";
 -             case 27:  return "BLE speaker";
 -             case 28:  return "echo reference";
 -             case 29:  return "HDMI eARC";
 -             case 30:  return "BLE broadcast";
 -             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, nullptr);
 -         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 = -1;
 -         Array<int> sampleRates;
 -         int numChannels;
 -     };
 - 
 -     DeviceInfo getDeviceInfoForName (const String& name, bool isInput)
 -     {
 -         if (name.isNotEmpty())
 -         {
 -             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(); }
 - 
 - //==============================================================================
 - class OboeRealtimeThread    : private oboe::AudioStreamCallback
 - {
 -     using OboeStream = OboeAudioIODevice::OboeStream;
 - 
 - public:
 -     OboeRealtimeThread()
 -         : testStream (new OboeStream (oboe::kUnspecified,
 -                                       oboe::Direction::Output,
 -                                       oboe::SharingMode::Exclusive,
 -                                       1,
 -                                       oboe::AudioFormat::Float,
 -                                       (int) AndroidHighPerformanceAudioHelpers::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 (oboe::kUnspecified,
 -                                               oboe::Direction::Output,
 -                                               oboe::SharingMode::Exclusive,
 -                                               1,
 -                                               oboe::AudioFormat::I16,
 -                                               (int) AndroidHighPerformanceAudioHelpers::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*, [[maybe_unused]] oboe::Result error) override
 -     {
 -         JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorBeforeClose(): " + getOboeString (error));
 -         jassertfalse;  // Should never get here!
 -     }
 - 
 -     void onErrorAfterClose (oboe::AudioStream*, [[maybe_unused]] oboe::Result error) override
 -     {
 -         JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorAfterClose(): " + getOboeString (error));
 -         jassertfalse;  // Should never get here!
 -     }
 - 
 - 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;
 - };
 - 
 - //==============================================================================
 - RealtimeThreadFactory getAndroidRealtimeThreadFactory()
 - {
 -     return [] (void* (*entry) (void*), void* userPtr) -> pthread_t
 -     {
 -         auto thread = std::make_unique<OboeRealtimeThread>();
 - 
 -         if (! thread->isOk())
 -             return {};
 - 
 -         auto threadID = thread->startThread (entry, userPtr);
 - 
 -         // the thread will de-allocate itself
 -         thread.release();
 - 
 -         return threadID;
 -     };
 - }
 - 
 - } // namespace juce
 
 
  |