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