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