1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - 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 namespace juce
24 {
25
26 class iOSAudioIODevice;
27
28 static const char* const iOSAudioDeviceName = "iOS Audio";
29
30 //==============================================================================
31 struct AudioSessionHolder
32 {
33 AudioSessionHolder();
34 ~AudioSessionHolder();
35
36 void handleStatusChange (bool enabled, const char* reason) const;
37 void handleRouteChange (AVAudioSessionRouteChangeReason reason);
38
39 Array<iOSAudioIODevice::Pimpl*> activeDevices;
40 Array<iOSAudioIODeviceType*> activeDeviceTypes;
41
42 id nativeSession;
43 };
44
getRoutingChangeReason(AVAudioSessionRouteChangeReason reason)45 static const char* getRoutingChangeReason (AVAudioSessionRouteChangeReason reason) noexcept
46 {
47 switch (reason)
48 {
49 case AVAudioSessionRouteChangeReasonNewDeviceAvailable: return "New device available";
50 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: return "Old device unavailable";
51 case AVAudioSessionRouteChangeReasonCategoryChange: return "Category change";
52 case AVAudioSessionRouteChangeReasonOverride: return "Override";
53 case AVAudioSessionRouteChangeReasonWakeFromSleep: return "Wake from sleep";
54 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: return "No suitable route for category";
55 case AVAudioSessionRouteChangeReasonRouteConfigurationChange: return "Route configuration change";
56 case AVAudioSessionRouteChangeReasonUnknown:
57 default: return "Unknown";
58 }
59 }
60
getNotificationValueForKey(NSNotification * notification,NSString * key,NSUInteger & value)61 bool getNotificationValueForKey (NSNotification* notification, NSString* key, NSUInteger& value) noexcept
62 {
63 if (notification != nil)
64 {
65 if (NSDictionary* userInfo = [notification userInfo])
66 {
67 if (NSNumber* number = [userInfo objectForKey: key])
68 {
69 value = [number unsignedIntegerValue];
70 return true;
71 }
72 }
73 }
74
75 jassertfalse;
76 return false;
77 }
78
79 } // juce namespace
80
81 //==============================================================================
82 @interface iOSAudioSessionNative : NSObject
83 {
84 @private
85 juce::AudioSessionHolder* audioSessionHolder;
86 };
87
88 - (id) init: (juce::AudioSessionHolder*) holder;
89 - (void) dealloc;
90
91 - (void) audioSessionChangedInterruptionType: (NSNotification*) notification;
92 - (void) handleMediaServicesReset;
93 - (void) handleMediaServicesLost;
94 - (void) handleRouteChange: (NSNotification*) notification;
95 @end
96
97 @implementation iOSAudioSessionNative
98
99 - (id) init: (juce::AudioSessionHolder*) holder
100 {
101 self = [super init];
102
103 if (self != nil)
104 {
105 audioSessionHolder = holder;
106
107 auto session = [AVAudioSession sharedInstance];
108 auto centre = [NSNotificationCenter defaultCenter];
109
110 [centre addObserver: self
111 selector: @selector (audioSessionChangedInterruptionType:)
112 name: AVAudioSessionInterruptionNotification
113 object: session];
114
115 [centre addObserver: self
116 selector: @selector (handleMediaServicesLost)
117 name: AVAudioSessionMediaServicesWereLostNotification
118 object: session];
119
120 [centre addObserver: self
121 selector: @selector (handleMediaServicesReset)
122 name: AVAudioSessionMediaServicesWereResetNotification
123 object: session];
124
125 [centre addObserver: self
126 selector: @selector (handleRouteChange:)
127 name: AVAudioSessionRouteChangeNotification
128 object: session];
129 }
130 else
131 {
132 jassertfalse;
133 }
134
135 return self;
136 }
137
138 - (void) dealloc
139 {
140 [[NSNotificationCenter defaultCenter] removeObserver: self];
141 [super dealloc];
142 }
143
144 - (void) audioSessionChangedInterruptionType: (NSNotification*) notification
145 {
146 NSUInteger value;
147
148 if (juce::getNotificationValueForKey (notification, AVAudioSessionInterruptionTypeKey, value))
149 {
150 switch ((AVAudioSessionInterruptionType) value)
151 {
152 case AVAudioSessionInterruptionTypeBegan:
153 audioSessionHolder->handleStatusChange (false, "AVAudioSessionInterruptionTypeBegan");
154 break;
155
156 case AVAudioSessionInterruptionTypeEnded:
157 audioSessionHolder->handleStatusChange (true, "AVAudioSessionInterruptionTypeEnded");
158 break;
159
160 // No default so the code doesn't compile if this enum is extended.
161 }
162 }
163 }
164
165 - (void) handleMediaServicesReset
166 {
167 audioSessionHolder->handleStatusChange (true, "AVAudioSessionMediaServicesWereResetNotification");
168 }
169
170 - (void) handleMediaServicesLost
171 {
172 audioSessionHolder->handleStatusChange (false, "AVAudioSessionMediaServicesWereLostNotification");
173 }
174
175 - (void) handleRouteChange: (NSNotification*) notification
176 {
177 NSUInteger value;
178
179 if (juce::getNotificationValueForKey (notification, AVAudioSessionRouteChangeReasonKey, value))
180 audioSessionHolder->handleRouteChange ((AVAudioSessionRouteChangeReason) value);
181 }
182
183 @end
184
185 //==============================================================================
186 #if JUCE_MODULE_AVAILABLE_juce_graphics
187 #include <juce_graphics/native/juce_mac_CoreGraphicsHelpers.h>
188 #endif
189
190 namespace juce {
191
192 #ifndef JUCE_IOS_AUDIO_LOGGING
193 #define JUCE_IOS_AUDIO_LOGGING 0
194 #endif
195
196 #if JUCE_IOS_AUDIO_LOGGING
197 #define JUCE_IOS_AUDIO_LOG(x) DBG(x)
198 #else
199 #define JUCE_IOS_AUDIO_LOG(x)
200 #endif
201
logNSError(NSError * e)202 static void logNSError (NSError* e)
203 {
204 if (e != nil)
205 {
206 JUCE_IOS_AUDIO_LOG ("iOS Audio error: " << [e.localizedDescription UTF8String]);
207 jassertfalse;
208 }
209 }
210
211 #define JUCE_NSERROR_CHECK(X) { NSError* error = nil; X; logNSError (error); }
212
213 //==============================================================================
214 class iOSAudioIODeviceType : public AudioIODeviceType,
215 public AsyncUpdater
216 {
217 public:
218 iOSAudioIODeviceType();
219 ~iOSAudioIODeviceType() override;
220
221 void scanForDevices() override;
222 StringArray getDeviceNames (bool) const override;
223 int getDefaultDeviceIndex (bool) const override;
224 int getIndexOfDevice (AudioIODevice*, bool) const override;
225 bool hasSeparateInputsAndOutputs() const override;
226 AudioIODevice* createDevice (const String&, const String&) override;
227
228 private:
229 void handleRouteChange (AVAudioSessionRouteChangeReason);
230
231 void handleAsyncUpdate() override;
232
233 friend struct AudioSessionHolder;
234 friend struct iOSAudioIODevice::Pimpl;
235
236 SharedResourcePointer<AudioSessionHolder> sessionHolder;
237
238 JUCE_DECLARE_WEAK_REFERENCEABLE (iOSAudioIODeviceType)
239 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType)
240 };
241
242 //==============================================================================
243 struct iOSAudioIODevice::Pimpl : public AudioPlayHead,
244 public AsyncUpdater
245 {
Pimpljuce::iOSAudioIODevice::Pimpl246 Pimpl (iOSAudioIODeviceType* ioDeviceType, iOSAudioIODevice& ioDevice)
247 : deviceType (ioDeviceType),
248 owner (ioDevice)
249 {
250 JUCE_IOS_AUDIO_LOG ("Creating iOS audio device");
251
252 // We need to activate the audio session here to obtain the available sample rates and buffer sizes,
253 // but if we don't set a category first then background audio will always be stopped. This category
254 // may be changed later.
255 setAudioSessionCategory (AVAudioSessionCategoryPlayAndRecord);
256
257 setAudioSessionActive (true);
258 updateHardwareInfo();
259 channelData.reconfigure ({}, {});
260 setAudioSessionActive (false);
261
262 sessionHolder->activeDevices.add (this);
263 }
264
~Pimpljuce::iOSAudioIODevice::Pimpl265 ~Pimpl() override
266 {
267 sessionHolder->activeDevices.removeFirstMatchingValue (this);
268
269 close();
270 }
271
setAudioSessionCategoryjuce::iOSAudioIODevice::Pimpl272 static void setAudioSessionCategory (NSString* category)
273 {
274 NSUInteger options = 0;
275
276 #if ! JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS
277 options |= AVAudioSessionCategoryOptionMixWithOthers; // Alternatively AVAudioSessionCategoryOptionDuckOthers
278 #endif
279
280 if (category == AVAudioSessionCategoryPlayAndRecord)
281 options |= (AVAudioSessionCategoryOptionDefaultToSpeaker
282 | AVAudioSessionCategoryOptionAllowBluetooth
283 | AVAudioSessionCategoryOptionAllowBluetoothA2DP);
284
285 JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setCategory: category
286 withOptions: options
287 error: &error]);
288 }
289
setAudioSessionActivejuce::iOSAudioIODevice::Pimpl290 static void setAudioSessionActive (bool enabled)
291 {
292 JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setActive: enabled
293 error: &error]);
294 }
295
getBufferSizejuce::iOSAudioIODevice::Pimpl296 int getBufferSize (const double currentSampleRate)
297 {
298 return roundToInt (currentSampleRate * [AVAudioSession sharedInstance].IOBufferDuration);
299 }
300
tryBufferSizejuce::iOSAudioIODevice::Pimpl301 int tryBufferSize (const double currentSampleRate, const int newBufferSize)
302 {
303 NSTimeInterval bufferDuration = currentSampleRate > 0 ? (NSTimeInterval) ((newBufferSize + 1) / currentSampleRate) : 0.0;
304
305 auto session = [AVAudioSession sharedInstance];
306 JUCE_NSERROR_CHECK ([session setPreferredIOBufferDuration: bufferDuration
307 error: &error]);
308
309 return getBufferSize (currentSampleRate);
310 }
311
updateAvailableBufferSizesjuce::iOSAudioIODevice::Pimpl312 void updateAvailableBufferSizes()
313 {
314 availableBufferSizes.clear();
315
316 auto newBufferSize = tryBufferSize (sampleRate, 64);
317 jassert (newBufferSize > 0);
318
319 const auto longestBufferSize = tryBufferSize (sampleRate, 4096);
320
321 while (newBufferSize <= longestBufferSize)
322 {
323 availableBufferSizes.add (newBufferSize);
324 newBufferSize *= 2;
325 }
326
327 // Sometimes the largest supported buffer size is not a power of 2
328 availableBufferSizes.addIfNotAlreadyThere (longestBufferSize);
329
330 bufferSize = tryBufferSize (sampleRate, bufferSize);
331
332 #if JUCE_IOS_AUDIO_LOGGING
333 {
334 String info ("Available buffer sizes:");
335
336 for (auto size : availableBufferSizes)
337 info << " " << size;
338
339 JUCE_IOS_AUDIO_LOG (info);
340 }
341 #endif
342
343 JUCE_IOS_AUDIO_LOG ("Buffer size after detecting available buffer sizes: " << bufferSize);
344 }
345
trySampleRatejuce::iOSAudioIODevice::Pimpl346 double trySampleRate (double rate)
347 {
348 auto session = [AVAudioSession sharedInstance];
349 JUCE_NSERROR_CHECK ([session setPreferredSampleRate: rate
350 error: &error]);
351
352 return session.sampleRate;
353 }
354
355 // Important: the supported audio sample rates change on the iPhone 6S
356 // depending on whether the headphones are plugged in or not!
updateAvailableSampleRatesjuce::iOSAudioIODevice::Pimpl357 void updateAvailableSampleRates()
358 {
359 availableSampleRates.clear();
360
361 AudioUnitRemovePropertyListenerWithUserData (audioUnit,
362 kAudioUnitProperty_StreamFormat,
363 dispatchAudioUnitPropertyChange,
364 this);
365
366 const double lowestRate = trySampleRate (4000);
367 availableSampleRates.add (lowestRate);
368 const double highestRate = trySampleRate (192000);
369
370 JUCE_IOS_AUDIO_LOG ("Lowest supported sample rate: " << lowestRate);
371 JUCE_IOS_AUDIO_LOG ("Highest supported sample rate: " << highestRate);
372
373 for (double rate = lowestRate + 1000; rate < highestRate; rate += 1000)
374 {
375 const double supportedRate = trySampleRate (rate);
376 JUCE_IOS_AUDIO_LOG ("Trying a sample rate of " << rate << ", got " << supportedRate);
377 availableSampleRates.addIfNotAlreadyThere (supportedRate);
378 rate = jmax (rate, supportedRate);
379 }
380
381 availableSampleRates.addIfNotAlreadyThere (highestRate);
382
383 // Restore the original values.
384 sampleRate = trySampleRate (sampleRate);
385 bufferSize = tryBufferSize (sampleRate, bufferSize);
386
387 AudioUnitAddPropertyListener (audioUnit,
388 kAudioUnitProperty_StreamFormat,
389 dispatchAudioUnitPropertyChange,
390 this);
391
392 // Check the current stream format in case things have changed whilst we
393 // were going through the sample rates
394 handleStreamFormatChange();
395
396 #if JUCE_IOS_AUDIO_LOGGING
397 {
398 String info ("Available sample rates:");
399
400 for (auto rate : availableSampleRates)
401 info << " " << rate;
402
403 JUCE_IOS_AUDIO_LOG (info);
404 }
405 #endif
406
407 JUCE_IOS_AUDIO_LOG ("Sample rate after detecting available sample rates: " << sampleRate);
408 }
409
updateHardwareInfojuce::iOSAudioIODevice::Pimpl410 void updateHardwareInfo (bool forceUpdate = false)
411 {
412 if (! forceUpdate && ! hardwareInfoNeedsUpdating.compareAndSetBool (false, true))
413 return;
414
415 JUCE_IOS_AUDIO_LOG ("Updating hardware info");
416
417 updateAvailableSampleRates();
418 updateAvailableBufferSizes();
419
420 if (deviceType != nullptr)
421 deviceType->callDeviceChangeListeners();
422 }
423
setTargetSampleRateAndBufferSizejuce::iOSAudioIODevice::Pimpl424 void setTargetSampleRateAndBufferSize()
425 {
426 JUCE_IOS_AUDIO_LOG ("Setting target sample rate: " << targetSampleRate);
427 sampleRate = trySampleRate (targetSampleRate);
428 JUCE_IOS_AUDIO_LOG ("Actual sample rate: " << sampleRate);
429
430 JUCE_IOS_AUDIO_LOG ("Setting target buffer size: " << targetBufferSize);
431 bufferSize = tryBufferSize (sampleRate, targetBufferSize);
432 JUCE_IOS_AUDIO_LOG ("Actual buffer size: " << bufferSize);
433 }
434
openjuce::iOSAudioIODevice::Pimpl435 String open (const BigInteger& inputChannelsWanted,
436 const BigInteger& outputChannelsWanted,
437 double sampleRateWanted, int bufferSizeWanted)
438 {
439 close();
440
441 firstHostTime = true;
442 lastNumFrames = 0;
443 xrun = 0;
444 lastError.clear();
445
446 requestedInputChannels = inputChannelsWanted;
447 requestedOutputChannels = outputChannelsWanted;
448 targetSampleRate = sampleRateWanted;
449 targetBufferSize = bufferSizeWanted > 0 ? bufferSizeWanted : defaultBufferSize;
450
451 JUCE_IOS_AUDIO_LOG ("Opening audio device:"
452 << " inputChannelsWanted: " << requestedInputChannels .toString (2)
453 << ", outputChannelsWanted: " << requestedOutputChannels.toString (2)
454 << ", targetSampleRate: " << targetSampleRate
455 << ", targetBufferSize: " << targetBufferSize);
456
457 setAudioSessionActive (true);
458 setAudioSessionCategory (requestedInputChannels > 0 ? AVAudioSessionCategoryPlayAndRecord
459 : AVAudioSessionCategoryPlayback);
460 channelData.reconfigure (requestedInputChannels, requestedOutputChannels);
461 updateHardwareInfo (true);
462 setTargetSampleRateAndBufferSize();
463 fixAudioRouteIfSetToReceiver();
464
465 isRunning = true;
466
467 if (! createAudioUnit())
468 {
469 lastError = "Couldn't open the device";
470 return lastError;
471 }
472
473 const ScopedLock sl (callbackLock);
474
475 AudioOutputUnitStart (audioUnit);
476
477 if (callback != nullptr)
478 callback->audioDeviceAboutToStart (&owner);
479
480 return lastError;
481 }
482
closejuce::iOSAudioIODevice::Pimpl483 void close()
484 {
485 stop();
486
487 if (isRunning)
488 {
489 isRunning = false;
490
491 if (audioUnit != nullptr)
492 {
493 AudioOutputUnitStart (audioUnit);
494 AudioComponentInstanceDispose (audioUnit);
495 audioUnit = nullptr;
496 }
497
498 setAudioSessionActive (false);
499 }
500 }
501
startjuce::iOSAudioIODevice::Pimpl502 void start (AudioIODeviceCallback* newCallback)
503 {
504 if (isRunning && callback != newCallback)
505 {
506 if (newCallback != nullptr)
507 newCallback->audioDeviceAboutToStart (&owner);
508
509 const ScopedLock sl (callbackLock);
510 callback = newCallback;
511 }
512 }
513
stopjuce::iOSAudioIODevice::Pimpl514 void stop()
515 {
516 if (isRunning)
517 {
518 AudioIODeviceCallback* lastCallback;
519
520 {
521 const ScopedLock sl (callbackLock);
522 lastCallback = callback;
523 callback = nullptr;
524 }
525
526 if (lastCallback != nullptr)
527 lastCallback->audioDeviceStopped();
528 }
529 }
530
setAudioPreprocessingEnabledjuce::iOSAudioIODevice::Pimpl531 bool setAudioPreprocessingEnabled (bool enable)
532 {
533 auto session = [AVAudioSession sharedInstance];
534
535 NSString* mode = (enable ? AVAudioSessionModeDefault
536 : AVAudioSessionModeMeasurement);
537
538 JUCE_NSERROR_CHECK ([session setMode: mode
539 error: &error]);
540
541 return session.mode == mode;
542 }
543
544 //==============================================================================
canControlTransportjuce::iOSAudioIODevice::Pimpl545 bool canControlTransport() override { return interAppAudioConnected; }
546
transportPlayjuce::iOSAudioIODevice::Pimpl547 void transportPlay (bool shouldSartPlaying) override
548 {
549 if (! canControlTransport())
550 return;
551
552 HostCallbackInfo callbackInfo;
553 fillHostCallbackInfo (callbackInfo);
554
555 Boolean hostIsPlaying = NO;
556 OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
557 &hostIsPlaying,
558 nullptr,
559 nullptr,
560 nullptr,
561 nullptr,
562 nullptr,
563 nullptr);
564
565 ignoreUnused (err);
566 jassert (err == noErr);
567
568 if (hostIsPlaying != shouldSartPlaying)
569 handleAudioTransportEvent (kAudioUnitRemoteControlEvent_TogglePlayPause);
570 }
571
transportRecordjuce::iOSAudioIODevice::Pimpl572 void transportRecord (bool shouldStartRecording) override
573 {
574 if (! canControlTransport())
575 return;
576
577 HostCallbackInfo callbackInfo;
578 fillHostCallbackInfo (callbackInfo);
579
580 Boolean hostIsRecording = NO;
581 OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
582 nullptr,
583 &hostIsRecording,
584 nullptr,
585 nullptr,
586 nullptr,
587 nullptr,
588 nullptr);
589 ignoreUnused (err);
590 jassert (err == noErr);
591
592 if (hostIsRecording != shouldStartRecording)
593 handleAudioTransportEvent (kAudioUnitRemoteControlEvent_ToggleRecord);
594 }
595
transportRewindjuce::iOSAudioIODevice::Pimpl596 void transportRewind() override
597 {
598 if (canControlTransport())
599 handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind);
600 }
601
getCurrentPositionjuce::iOSAudioIODevice::Pimpl602 bool getCurrentPosition (CurrentPositionInfo& result) override
603 {
604 if (! canControlTransport())
605 return false;
606
607 zerostruct (result);
608
609 HostCallbackInfo callbackInfo;
610 fillHostCallbackInfo (callbackInfo);
611
612 if (callbackInfo.hostUserData == nullptr)
613 return false;
614
615 Boolean hostIsPlaying = NO;
616 Boolean hostIsRecording = NO;
617 Float64 hostCurrentSampleInTimeLine = 0;
618 Boolean hostIsCycling = NO;
619 Float64 hostCycleStartBeat = 0;
620 Float64 hostCycleEndBeat = 0;
621 OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
622 &hostIsPlaying,
623 &hostIsRecording,
624 nullptr,
625 &hostCurrentSampleInTimeLine,
626 &hostIsCycling,
627 &hostCycleStartBeat,
628 &hostCycleEndBeat);
629 if (err == kAUGraphErr_CannotDoInCurrentContext)
630 return false;
631
632 jassert (err == noErr);
633
634 result.timeInSamples = (int64) hostCurrentSampleInTimeLine;
635 result.isPlaying = hostIsPlaying;
636 result.isRecording = hostIsRecording;
637 result.isLooping = hostIsCycling;
638 result.ppqLoopStart = hostCycleStartBeat;
639 result.ppqLoopEnd = hostCycleEndBeat;
640
641 result.timeInSeconds = result.timeInSamples / sampleRate;
642
643 Float64 hostBeat = 0;
644 Float64 hostTempo = 0;
645 err = callbackInfo.beatAndTempoProc (callbackInfo.hostUserData,
646 &hostBeat,
647 &hostTempo);
648 jassert (err == noErr);
649
650 result.ppqPosition = hostBeat;
651 result.bpm = hostTempo;
652
653 Float32 hostTimeSigNumerator = 0;
654 UInt32 hostTimeSigDenominator = 0;
655 Float64 hostCurrentMeasureDownBeat = 0;
656 err = callbackInfo.musicalTimeLocationProc (callbackInfo.hostUserData,
657 nullptr,
658 &hostTimeSigNumerator,
659 &hostTimeSigDenominator,
660 &hostCurrentMeasureDownBeat);
661 jassert (err == noErr);
662
663 result.ppqPositionOfLastBarStart = hostCurrentMeasureDownBeat;
664 result.timeSigNumerator = (int) hostTimeSigNumerator;
665 result.timeSigDenominator = (int) hostTimeSigDenominator;
666
667 result.frameRate = AudioPlayHead::fpsUnknown;
668
669 return true;
670 }
671
672 //==============================================================================
673 #if JUCE_MODULE_AVAILABLE_juce_graphics
getIconjuce::iOSAudioIODevice::Pimpl674 Image getIcon (int size)
675 {
676 if (interAppAudioConnected)
677 {
678 UIImage* hostUIImage = AudioOutputUnitGetHostIcon (audioUnit, size);
679 if (hostUIImage != nullptr)
680 return juce_createImageFromUIImage (hostUIImage);
681 }
682 return Image();
683 }
684 #endif
685
switchApplicationjuce::iOSAudioIODevice::Pimpl686 void switchApplication()
687 {
688 if (! interAppAudioConnected)
689 return;
690
691 CFURLRef hostUrl;
692 UInt32 dataSize = sizeof (hostUrl);
693 OSStatus err = AudioUnitGetProperty(audioUnit,
694 kAudioUnitProperty_PeerURL,
695 kAudioUnitScope_Global,
696 0,
697 &hostUrl,
698 &dataSize);
699 if (err == noErr)
700 {
701 #if (! defined __IPHONE_10_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0)
702 [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl];
703 #else
704 [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl options: @{} completionHandler: nil];
705 #endif
706 }
707 }
708
709 //==============================================================================
invokeAudioDeviceErrorCallbackjuce::iOSAudioIODevice::Pimpl710 void invokeAudioDeviceErrorCallback (const String& reason)
711 {
712 const ScopedLock sl (callbackLock);
713
714 if (callback != nullptr)
715 callback->audioDeviceError (reason);
716 }
717
handleStatusChangejuce::iOSAudioIODevice::Pimpl718 void handleStatusChange (bool enabled, const char* reason)
719 {
720 const ScopedLock myScopedLock (callbackLock);
721
722 JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason);
723
724 isRunning = enabled;
725 setAudioSessionActive (enabled);
726
727 if (enabled)
728 AudioOutputUnitStart (audioUnit);
729 else
730 AudioOutputUnitStop (audioUnit);
731
732 if (! enabled)
733 invokeAudioDeviceErrorCallback (reason);
734 }
735
handleRouteChangejuce::iOSAudioIODevice::Pimpl736 void handleRouteChange (AVAudioSessionRouteChangeReason reason)
737 {
738 const ScopedLock myScopedLock (callbackLock);
739
740 const String reasonString (getRoutingChangeReason (reason));
741 JUCE_IOS_AUDIO_LOG ("handleRouteChange: " << reasonString);
742
743 if (isRunning)
744 invokeAudioDeviceErrorCallback (reasonString);
745
746 switch (reason)
747 {
748 case AVAudioSessionRouteChangeReasonCategoryChange:
749 case AVAudioSessionRouteChangeReasonOverride:
750 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
751 break;
752 case AVAudioSessionRouteChangeReasonUnknown:
753 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
754 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
755 case AVAudioSessionRouteChangeReasonWakeFromSleep:
756 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
757 {
758 hardwareInfoNeedsUpdating = true;
759 triggerAsyncUpdate();
760 break;
761 }
762
763 // No default so the code doesn't compile if this enum is extended.
764 }
765 }
766
handleAudioUnitPropertyChangejuce::iOSAudioIODevice::Pimpl767 void handleAudioUnitPropertyChange (AudioUnit,
768 AudioUnitPropertyID propertyID,
769 AudioUnitScope scope,
770 AudioUnitElement element)
771 {
772 ignoreUnused (scope);
773 ignoreUnused (element);
774 JUCE_IOS_AUDIO_LOG ("handleAudioUnitPropertyChange: propertyID: " << String (propertyID)
775 << " scope: " << String (scope)
776 << " element: " << String (element));
777
778 switch (propertyID)
779 {
780 case kAudioUnitProperty_IsInterAppConnected:
781 handleInterAppAudioConnectionChange();
782 return;
783 case kAudioUnitProperty_StreamFormat:
784 handleStreamFormatChange();
785 return;
786 default:
787 jassertfalse;
788 }
789 }
790
handleInterAppAudioConnectionChangejuce::iOSAudioIODevice::Pimpl791 void handleInterAppAudioConnectionChange()
792 {
793 UInt32 connected;
794 UInt32 dataSize = sizeof (connected);
795 OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_IsInterAppConnected,
796 kAudioUnitScope_Global, 0, &connected, &dataSize);
797 ignoreUnused (err);
798 jassert (err == noErr);
799
800 JUCE_IOS_AUDIO_LOG ("handleInterAppAudioConnectionChange: " << (connected ? "connected"
801 : "disconnected"));
802
803 if (connected != interAppAudioConnected)
804 {
805 const ScopedLock myScopedLock (callbackLock);
806
807 interAppAudioConnected = connected;
808
809 UIApplicationState appstate = [UIApplication sharedApplication].applicationState;
810 bool inForeground = (appstate != UIApplicationStateBackground);
811
812 if (interAppAudioConnected || inForeground)
813 {
814 setAudioSessionActive (true);
815 AudioOutputUnitStart (audioUnit);
816
817 if (callback != nullptr)
818 callback->audioDeviceAboutToStart (&owner);
819 }
820 else if (! inForeground)
821 {
822 AudioOutputUnitStop (audioUnit);
823 setAudioSessionActive (false);
824
825 if (callback != nullptr)
826 callback->audioDeviceStopped();
827 }
828 }
829 }
830
831 //==============================================================================
processjuce::iOSAudioIODevice::Pimpl832 OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
833 const UInt32 numFrames, AudioBufferList* data)
834 {
835 OSStatus err = noErr;
836
837 recordXruns (time, numFrames);
838
839 const bool useInput = channelData.areInputChannelsAvailable();
840
841 if (useInput)
842 err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);
843
844 const auto channelDataSize = sizeof (float) * numFrames;
845
846 const ScopedTryLock stl (callbackLock);
847
848 if (stl.isLocked() && callback != nullptr)
849 {
850 if ((int) numFrames > channelData.getFloatBufferSize())
851 channelData.setFloatBufferSize ((int) numFrames);
852
853 float** const inputData = channelData.audioData.getArrayOfWritePointers();
854 float** const outputData = inputData + channelData.inputs->numActiveChannels;
855
856 if (useInput)
857 {
858 for (int c = 0; c < channelData.inputs->numActiveChannels; ++c)
859 {
860 auto channelIndex = channelData.inputs->activeChannelIndices[c];
861 memcpy (inputData[c], (float*) data->mBuffers[channelIndex].mData, channelDataSize);
862 }
863 }
864 else
865 {
866 for (int c = 0; c < channelData.inputs->numActiveChannels; ++c)
867 zeromem (inputData[c], channelDataSize);
868 }
869
870 callback->audioDeviceIOCallback ((const float**) inputData, channelData.inputs ->numActiveChannels,
871 outputData, channelData.outputs->numActiveChannels,
872 (int) numFrames);
873
874 for (int c = 0; c < channelData.outputs->numActiveChannels; ++c)
875 {
876 auto channelIndex = channelData.outputs->activeChannelIndices[c];
877 memcpy (data->mBuffers[channelIndex].mData, outputData[c], channelDataSize);
878 }
879
880 for (auto c : channelData.outputs->inactiveChannelIndices)
881 zeromem (data->mBuffers[c].mData, channelDataSize);
882 }
883 else
884 {
885 for (uint32 c = 0; c < data->mNumberBuffers; ++c)
886 zeromem (data->mBuffers[c].mData, channelDataSize);
887 }
888
889 return err;
890 }
891
recordXrunsjuce::iOSAudioIODevice::Pimpl892 void recordXruns (const AudioTimeStamp* time, UInt32 numFrames)
893 {
894 if (time != nullptr && (time->mFlags & kAudioTimeStampSampleTimeValid) != 0)
895 {
896 if (! firstHostTime)
897 {
898 if ((time->mSampleTime - lastSampleTime) != lastNumFrames)
899 xrun++;
900 }
901 else
902 firstHostTime = false;
903
904 lastSampleTime = time->mSampleTime;
905 }
906 else
907 firstHostTime = true;
908
909 lastNumFrames = numFrames;
910 }
911
912 //==============================================================================
processStaticjuce::iOSAudioIODevice::Pimpl913 static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
914 UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data)
915 {
916 return static_cast<Pimpl*> (client)->process (flags, time, numFrames, data);
917 }
918
919 //==============================================================================
createAudioUnitjuce::iOSAudioIODevice::Pimpl920 bool createAudioUnit()
921 {
922 JUCE_IOS_AUDIO_LOG ("Creating the audio unit");
923
924 if (audioUnit != nullptr)
925 {
926 AudioComponentInstanceDispose (audioUnit);
927 audioUnit = nullptr;
928 }
929
930 AudioComponentDescription desc;
931 desc.componentType = kAudioUnitType_Output;
932 desc.componentSubType = kAudioUnitSubType_RemoteIO;
933 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
934 desc.componentFlags = 0;
935 desc.componentFlagsMask = 0;
936
937 AudioComponent comp = AudioComponentFindNext (nullptr, &desc);
938 AudioComponentInstanceNew (comp, &audioUnit);
939
940 if (audioUnit == nullptr)
941 return false;
942
943 #if JucePlugin_Enable_IAA
944 AudioComponentDescription appDesc;
945 appDesc.componentType = JucePlugin_IAAType;
946 appDesc.componentSubType = JucePlugin_IAASubType;
947 appDesc.componentManufacturer = JucePlugin_ManufacturerCode;
948 appDesc.componentFlags = 0;
949 appDesc.componentFlagsMask = 0;
950 OSStatus err = AudioOutputUnitPublish (&appDesc,
951 CFSTR(JucePlugin_IAAName),
952 JucePlugin_VersionCode,
953 audioUnit);
954
955 // This assert will be hit if the Inter-App Audio entitlement has not
956 // been enabled, or the description being published with
957 // AudioOutputUnitPublish is different from any in the AudioComponents
958 // array in this application's .plist file.
959 jassert (err == noErr);
960
961 err = AudioUnitAddPropertyListener (audioUnit,
962 kAudioUnitProperty_IsInterAppConnected,
963 dispatchAudioUnitPropertyChange,
964 this);
965 jassert (err == noErr);
966
967 AudioOutputUnitMIDICallbacks midiCallbacks;
968 midiCallbacks.userData = this;
969 midiCallbacks.MIDIEventProc = midiEventCallback;
970 midiCallbacks.MIDISysExProc = midiSysExCallback;
971 err = AudioUnitSetProperty (audioUnit,
972 kAudioOutputUnitProperty_MIDICallbacks,
973 kAudioUnitScope_Global,
974 0,
975 &midiCallbacks,
976 sizeof (midiCallbacks));
977 jassert (err == noErr);
978 #endif
979
980 if (channelData.areInputChannelsAvailable())
981 {
982 const UInt32 one = 1;
983 AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one));
984 }
985
986 {
987 AURenderCallbackStruct inputProc;
988 inputProc.inputProc = processStatic;
989 inputProc.inputProcRefCon = this;
990 AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc));
991 }
992
993 {
994 AudioStreamBasicDescription format;
995 zerostruct (format);
996 format.mSampleRate = sampleRate;
997 format.mFormatID = kAudioFormatLinearPCM;
998 format.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
999 format.mBitsPerChannel = 8 * sizeof (float);
1000 format.mFramesPerPacket = 1;
1001 format.mChannelsPerFrame = (UInt32) jmax (channelData.inputs->numHardwareChannels, channelData.outputs->numHardwareChannels);
1002 format.mBytesPerFrame = format.mBytesPerPacket = sizeof (float);
1003
1004 AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format));
1005 AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format));
1006 }
1007
1008 AudioUnitInitialize (audioUnit);
1009
1010 {
1011 // Querying the kAudioUnitProperty_MaximumFramesPerSlice property after calling AudioUnitInitialize
1012 // seems to be more reliable than calling it before.
1013 UInt32 framesPerSlice, dataSize = sizeof (framesPerSlice);
1014
1015 if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
1016 kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr
1017 && dataSize == sizeof (framesPerSlice)
1018 && static_cast<int> (framesPerSlice) != bufferSize)
1019 {
1020 JUCE_IOS_AUDIO_LOG ("Internal buffer size: " << String (framesPerSlice));
1021 channelData.setFloatBufferSize (static_cast<int> (framesPerSlice));
1022 }
1023 }
1024
1025 AudioUnitAddPropertyListener (audioUnit, kAudioUnitProperty_StreamFormat, dispatchAudioUnitPropertyChange, this);
1026
1027 return true;
1028 }
1029
fillHostCallbackInfojuce::iOSAudioIODevice::Pimpl1030 void fillHostCallbackInfo (HostCallbackInfo& callbackInfo)
1031 {
1032 zerostruct (callbackInfo);
1033 UInt32 dataSize = sizeof (HostCallbackInfo);
1034 OSStatus err = AudioUnitGetProperty (audioUnit,
1035 kAudioUnitProperty_HostCallbacks,
1036 kAudioUnitScope_Global,
1037 0,
1038 &callbackInfo,
1039 &dataSize);
1040 ignoreUnused (err);
1041 jassert (err == noErr);
1042 }
1043
handleAudioTransportEventjuce::iOSAudioIODevice::Pimpl1044 void handleAudioTransportEvent (AudioUnitRemoteControlEvent event)
1045 {
1046 OSStatus err = AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_RemoteControlToHost,
1047 kAudioUnitScope_Global, 0, &event, sizeof (event));
1048 ignoreUnused (err);
1049 jassert (err == noErr);
1050 }
1051
1052 // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it
1053 // to make it loud. Needed because by default when using an input + output, the output is kept quiet.
fixAudioRouteIfSetToReceiverjuce::iOSAudioIODevice::Pimpl1054 static void fixAudioRouteIfSetToReceiver()
1055 {
1056 auto session = [AVAudioSession sharedInstance];
1057 auto route = session.currentRoute;
1058
1059 for (AVAudioSessionPortDescription* port in route.outputs)
1060 {
1061 if ([port.portName isEqualToString: @"Receiver"])
1062 {
1063 JUCE_NSERROR_CHECK ([session overrideOutputAudioPort: AVAudioSessionPortOverrideSpeaker
1064 error: &error]);
1065 setAudioSessionActive (true);
1066 }
1067 }
1068 }
1069
restartjuce::iOSAudioIODevice::Pimpl1070 void restart()
1071 {
1072 const ScopedLock sl (callbackLock);
1073
1074 updateHardwareInfo();
1075 setTargetSampleRateAndBufferSize();
1076
1077 if (isRunning)
1078 {
1079 if (audioUnit != nullptr)
1080 {
1081 AudioComponentInstanceDispose (audioUnit);
1082 audioUnit = nullptr;
1083
1084 if (callback != nullptr)
1085 callback->audioDeviceStopped();
1086 }
1087
1088 channelData.reconfigure (requestedInputChannels, requestedOutputChannels);
1089
1090 createAudioUnit();
1091
1092 if (audioUnit != nullptr)
1093 {
1094 isRunning = true;
1095
1096 if (callback != nullptr)
1097 callback->audioDeviceAboutToStart (&owner);
1098
1099 AudioOutputUnitStart (audioUnit);
1100 }
1101 }
1102 }
1103
handleAsyncUpdatejuce::iOSAudioIODevice::Pimpl1104 void handleAsyncUpdate() override
1105 {
1106 restart();
1107 }
1108
handleStreamFormatChangejuce::iOSAudioIODevice::Pimpl1109 void handleStreamFormatChange()
1110 {
1111 AudioStreamBasicDescription desc;
1112 zerostruct (desc);
1113 UInt32 dataSize = sizeof (desc);
1114 AudioUnitGetProperty (audioUnit,
1115 kAudioUnitProperty_StreamFormat,
1116 kAudioUnitScope_Output,
1117 0,
1118 &desc,
1119 &dataSize);
1120
1121 if (desc.mSampleRate != 0 && desc.mSampleRate != sampleRate)
1122 {
1123 JUCE_IOS_AUDIO_LOG ("Stream format has changed: Sample rate " << desc.mSampleRate);
1124 triggerAsyncUpdate();
1125 }
1126 }
1127
dispatchAudioUnitPropertyChangejuce::iOSAudioIODevice::Pimpl1128 static void dispatchAudioUnitPropertyChange (void* data, AudioUnit unit, AudioUnitPropertyID propertyID,
1129 AudioUnitScope scope, AudioUnitElement element)
1130 {
1131 static_cast<Pimpl*> (data)->handleAudioUnitPropertyChange (unit, propertyID, scope, element);
1132 }
1133
getTimestampForMIDIjuce::iOSAudioIODevice::Pimpl1134 static double getTimestampForMIDI()
1135 {
1136 return Time::getMillisecondCounter() / 1000.0;
1137 }
1138
midiEventCallbackjuce::iOSAudioIODevice::Pimpl1139 static void midiEventCallback (void *client, UInt32 status, UInt32 data1, UInt32 data2, UInt32)
1140 {
1141 return static_cast<Pimpl*> (client)->handleMidiMessage (MidiMessage ((int) status,
1142 (int) data1,
1143 (int) data2,
1144 getTimestampForMIDI()));
1145 }
1146
midiSysExCallbackjuce::iOSAudioIODevice::Pimpl1147 static void midiSysExCallback (void *client, const UInt8 *data, UInt32 length)
1148 {
1149 return static_cast<Pimpl*> (client)->handleMidiMessage (MidiMessage (data, (int) length, getTimestampForMIDI()));
1150 }
1151
handleMidiMessagejuce::iOSAudioIODevice::Pimpl1152 void handleMidiMessage (MidiMessage msg)
1153 {
1154 if (messageCollector != nullptr)
1155 messageCollector->addMessageToQueue (msg);
1156 }
1157
1158 struct IOChannelData
1159 {
1160 class IOChannelConfig
1161 {
1162 public:
IOChannelConfig(const bool isInput,const BigInteger requiredChannels)1163 IOChannelConfig (const bool isInput, const BigInteger requiredChannels)
1164 : hardwareChannelNames (getHardwareChannelNames (isInput)),
1165 numHardwareChannels (hardwareChannelNames.size()),
1166 areChannelsAccessible ((! isInput) || [AVAudioSession sharedInstance].isInputAvailable),
1167 activeChannels (limitRequiredChannelsToHardware (numHardwareChannels, requiredChannels)),
1168 numActiveChannels (activeChannels.countNumberOfSetBits()),
1169 activeChannelIndices (getActiveChannelIndices (activeChannels)),
1170 inactiveChannelIndices (getInactiveChannelIndices (activeChannelIndices, numHardwareChannels))
1171 {
1172 #if JUCE_IOS_AUDIO_LOGGING
1173 {
1174 String info;
1175
1176 info << "Number of hardware channels: " << numHardwareChannels
1177 << ", Hardware channel names:";
1178
1179 for (auto& name : hardwareChannelNames)
1180 info << " \"" << name << "\"";
1181
1182 info << ", Are channels available: " << (areChannelsAccessible ? "yes" : "no")
1183 << ", Active channel indices:";
1184
1185 for (auto i : activeChannelIndices)
1186 info << " " << i;
1187
1188 info << ", Inactive channel indices:";
1189
1190 for (auto i : inactiveChannelIndices)
1191 info << " " << i;
1192
1193 JUCE_IOS_AUDIO_LOG ((isInput ? "Input" : "Output") << " channel configuration: {" << info << "}");
1194 }
1195 #endif
1196 }
1197
1198 const StringArray hardwareChannelNames;
1199 const int numHardwareChannels;
1200 const bool areChannelsAccessible;
1201
1202 const BigInteger activeChannels;
1203 const int numActiveChannels;
1204
1205 const Array<int> activeChannelIndices, inactiveChannelIndices;
1206
1207 private:
getHardwareChannelNames(const bool isInput)1208 static StringArray getHardwareChannelNames (const bool isInput)
1209 {
1210 StringArray result;
1211
1212 auto route = [AVAudioSession sharedInstance].currentRoute;
1213
1214 for (AVAudioSessionPortDescription* port in (isInput ? route.inputs : route.outputs))
1215 {
1216 for (AVAudioSessionChannelDescription* desc in port.channels)
1217 result.add (nsStringToJuce (desc.channelName));
1218 }
1219
1220 // A fallback for the iOS simulator and older iOS versions
1221 if (result.isEmpty())
1222 return { "Left", "Right" };
1223
1224 return result;
1225 }
1226
limitRequiredChannelsToHardware(const int numHardwareChannelsAvailable,BigInteger requiredChannels)1227 static BigInteger limitRequiredChannelsToHardware (const int numHardwareChannelsAvailable,
1228 BigInteger requiredChannels)
1229 {
1230 requiredChannels.setRange (numHardwareChannelsAvailable,
1231 requiredChannels.getHighestBit() + 1,
1232 false);
1233
1234 return requiredChannels;
1235 }
1236
getActiveChannelIndices(const BigInteger activeChannelsToIndex)1237 static Array<int> getActiveChannelIndices (const BigInteger activeChannelsToIndex)
1238 {
1239 Array<int> result;
1240
1241 auto index = activeChannelsToIndex.findNextSetBit (0);
1242
1243 while (index != -1)
1244 {
1245 result.add (index);
1246 index = activeChannelsToIndex.findNextSetBit (++index);
1247 }
1248
1249 return result;
1250 }
1251
getInactiveChannelIndices(const Array<int> & activeIndices,int numChannels)1252 static Array<int> getInactiveChannelIndices (const Array<int>& activeIndices, int numChannels)
1253 {
1254 Array<int> result;
1255
1256 auto nextActiveChannel = activeIndices.begin();
1257
1258 for (int i = 0; i < numChannels; ++i)
1259 if (nextActiveChannel != activeIndices.end() && i == *nextActiveChannel)
1260 ++nextActiveChannel;
1261 else
1262 result.add (i);
1263
1264 return result;
1265 }
1266 };
1267
reconfigurejuce::iOSAudioIODevice::Pimpl::IOChannelData1268 void reconfigure (const BigInteger requiredInputChannels,
1269 const BigInteger requiredOutputChannels)
1270 {
1271 inputs .reset (new IOChannelConfig (true, requiredInputChannels));
1272 outputs.reset (new IOChannelConfig (false, requiredOutputChannels));
1273
1274 audioData.setSize (inputs->numActiveChannels + outputs->numActiveChannels,
1275 audioData.getNumSamples());
1276 }
1277
getFloatBufferSizejuce::iOSAudioIODevice::Pimpl::IOChannelData1278 int getFloatBufferSize() const
1279 {
1280 return audioData.getNumSamples();
1281 }
1282
setFloatBufferSizejuce::iOSAudioIODevice::Pimpl::IOChannelData1283 void setFloatBufferSize (const int newSize)
1284 {
1285 audioData.setSize (audioData.getNumChannels(), newSize);
1286 }
1287
areInputChannelsAvailablejuce::iOSAudioIODevice::Pimpl::IOChannelData1288 bool areInputChannelsAvailable() const
1289 {
1290 return inputs->areChannelsAccessible && inputs->numActiveChannels > 0;
1291 }
1292
1293 std::unique_ptr<IOChannelConfig> inputs;
1294 std::unique_ptr<IOChannelConfig> outputs;
1295
1296 AudioBuffer<float> audioData { 0, 0 };
1297 };
1298
1299 IOChannelData channelData;
1300
1301 BigInteger requestedInputChannels, requestedOutputChannels;
1302
1303 bool isRunning = false;
1304
1305 AudioIODeviceCallback* callback = nullptr;
1306
1307 String lastError;
1308
1309 #if TARGET_IPHONE_SIMULATOR
1310 static constexpr int defaultBufferSize = 512;
1311 #else
1312 static constexpr int defaultBufferSize = 256;
1313 #endif
1314 int targetBufferSize = defaultBufferSize, bufferSize = targetBufferSize;
1315
1316 double targetSampleRate = 44100.0, sampleRate = targetSampleRate;
1317
1318 Array<double> availableSampleRates;
1319 Array<int> availableBufferSizes;
1320
1321 bool interAppAudioConnected = false;
1322
1323 MidiMessageCollector* messageCollector = nullptr;
1324
1325 WeakReference<iOSAudioIODeviceType> deviceType;
1326 iOSAudioIODevice& owner;
1327
1328 CriticalSection callbackLock;
1329
1330 Atomic<bool> hardwareInfoNeedsUpdating { true };
1331
1332 AudioUnit audioUnit {};
1333
1334 SharedResourcePointer<AudioSessionHolder> sessionHolder;
1335
1336 bool firstHostTime;
1337 Float64 lastSampleTime;
1338 unsigned int lastNumFrames;
1339 int xrun;
1340
1341 JUCE_DECLARE_NON_COPYABLE (Pimpl)
1342 };
1343
1344 //==============================================================================
iOSAudioIODevice(iOSAudioIODeviceType * ioDeviceType,const String &,const String &)1345 iOSAudioIODevice::iOSAudioIODevice (iOSAudioIODeviceType* ioDeviceType, const String&, const String&)
1346 : AudioIODevice (iOSAudioDeviceName, iOSAudioDeviceName),
1347 pimpl (new Pimpl (ioDeviceType, *this))
1348 {
1349 }
1350
1351 //==============================================================================
open(const BigInteger & inChans,const BigInteger & outChans,double requestedSampleRate,int requestedBufferSize)1352 String iOSAudioIODevice::open (const BigInteger& inChans, const BigInteger& outChans,
1353 double requestedSampleRate, int requestedBufferSize)
1354 {
1355 return pimpl->open (inChans, outChans, requestedSampleRate, requestedBufferSize);
1356 }
1357
close()1358 void iOSAudioIODevice::close() { pimpl->close(); }
1359
start(AudioIODeviceCallback * callbackToUse)1360 void iOSAudioIODevice::start (AudioIODeviceCallback* callbackToUse) { pimpl->start (callbackToUse); }
stop()1361 void iOSAudioIODevice::stop() { pimpl->stop(); }
1362
getAvailableSampleRates()1363 Array<double> iOSAudioIODevice::getAvailableSampleRates() { return pimpl->availableSampleRates; }
getAvailableBufferSizes()1364 Array<int> iOSAudioIODevice::getAvailableBufferSizes() { return pimpl->availableBufferSizes; }
1365
setAudioPreprocessingEnabled(bool enabled)1366 bool iOSAudioIODevice::setAudioPreprocessingEnabled (bool enabled) { return pimpl->setAudioPreprocessingEnabled (enabled); }
1367
isPlaying()1368 bool iOSAudioIODevice::isPlaying() { return pimpl->isRunning && pimpl->callback != nullptr; }
isOpen()1369 bool iOSAudioIODevice::isOpen() { return pimpl->isRunning; }
getLastError()1370 String iOSAudioIODevice::getLastError() { return pimpl->lastError; }
1371
getOutputChannelNames()1372 StringArray iOSAudioIODevice::getOutputChannelNames() { return pimpl->channelData.outputs->hardwareChannelNames; }
getInputChannelNames()1373 StringArray iOSAudioIODevice::getInputChannelNames() { return pimpl->channelData.inputs->areChannelsAccessible ? pimpl->channelData.inputs->hardwareChannelNames : StringArray(); }
1374
getDefaultBufferSize()1375 int iOSAudioIODevice::getDefaultBufferSize() { return pimpl->defaultBufferSize; }
getCurrentBufferSizeSamples()1376 int iOSAudioIODevice::getCurrentBufferSizeSamples() { return pimpl->bufferSize; }
1377
getCurrentSampleRate()1378 double iOSAudioIODevice::getCurrentSampleRate() { return pimpl->sampleRate; }
1379
getCurrentBitDepth()1380 int iOSAudioIODevice::getCurrentBitDepth() { return 16; }
1381
getActiveInputChannels() const1382 BigInteger iOSAudioIODevice::getActiveInputChannels() const { return pimpl->channelData.inputs->activeChannels; }
getActiveOutputChannels() const1383 BigInteger iOSAudioIODevice::getActiveOutputChannels() const { return pimpl->channelData.outputs->activeChannels; }
1384
getInputLatencyInSamples()1385 int iOSAudioIODevice::getInputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].inputLatency); }
getOutputLatencyInSamples()1386 int iOSAudioIODevice::getOutputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].outputLatency); }
getXRunCount() const1387 int iOSAudioIODevice::getXRunCount() const noexcept { return pimpl->xrun; }
1388
setMidiMessageCollector(MidiMessageCollector * collector)1389 void iOSAudioIODevice::setMidiMessageCollector (MidiMessageCollector* collector) { pimpl->messageCollector = collector; }
getAudioPlayHead() const1390 AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const { return pimpl.get(); }
1391
isInterAppAudioConnected() const1392 bool iOSAudioIODevice::isInterAppAudioConnected() const { return pimpl->interAppAudioConnected; }
1393 #if JUCE_MODULE_AVAILABLE_juce_graphics
getIcon(int size)1394 Image iOSAudioIODevice::getIcon (int size) { return pimpl->getIcon (size); }
1395 #endif
switchApplication()1396 void iOSAudioIODevice::switchApplication() { return pimpl->switchApplication(); }
1397
1398 //==============================================================================
iOSAudioIODeviceType()1399 iOSAudioIODeviceType::iOSAudioIODeviceType()
1400 : AudioIODeviceType (iOSAudioDeviceName)
1401 {
1402 sessionHolder->activeDeviceTypes.add (this);
1403 }
1404
~iOSAudioIODeviceType()1405 iOSAudioIODeviceType::~iOSAudioIODeviceType()
1406 {
1407 sessionHolder->activeDeviceTypes.removeFirstMatchingValue (this);
1408 }
1409
1410 // The list of devices is updated automatically
scanForDevices()1411 void iOSAudioIODeviceType::scanForDevices() {}
getDeviceNames(bool) const1412 StringArray iOSAudioIODeviceType::getDeviceNames (bool) const { return { iOSAudioDeviceName }; }
getDefaultDeviceIndex(bool) const1413 int iOSAudioIODeviceType::getDefaultDeviceIndex (bool) const { return 0; }
getIndexOfDevice(AudioIODevice *,bool) const1414 int iOSAudioIODeviceType::getIndexOfDevice (AudioIODevice*, bool) const { return 0; }
hasSeparateInputsAndOutputs() const1415 bool iOSAudioIODeviceType::hasSeparateInputsAndOutputs() const { return false; }
1416
createDevice(const String & outputDeviceName,const String & inputDeviceName)1417 AudioIODevice* iOSAudioIODeviceType::createDevice (const String& outputDeviceName, const String& inputDeviceName)
1418 {
1419 return new iOSAudioIODevice (this, outputDeviceName, inputDeviceName);
1420 }
1421
handleRouteChange(AVAudioSessionRouteChangeReason)1422 void iOSAudioIODeviceType::handleRouteChange (AVAudioSessionRouteChangeReason)
1423 {
1424 triggerAsyncUpdate();
1425 }
1426
handleAsyncUpdate()1427 void iOSAudioIODeviceType::handleAsyncUpdate()
1428 {
1429 callDeviceChangeListeners();
1430 }
1431
1432 //==============================================================================
createAudioIODeviceType_iOSAudio()1433 AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio()
1434 {
1435 return new iOSAudioIODeviceType();
1436 }
1437
1438 //==============================================================================
AudioSessionHolder()1439 AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; }
~AudioSessionHolder()1440 AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; }
1441
handleStatusChange(bool enabled,const char * reason) const1442 void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) const
1443 {
1444 for (auto device: activeDevices)
1445 device->handleStatusChange (enabled, reason);
1446 }
1447
handleRouteChange(AVAudioSessionRouteChangeReason reason)1448 void AudioSessionHolder::handleRouteChange (AVAudioSessionRouteChangeReason reason)
1449 {
1450 for (auto device: activeDevices)
1451 device->handleRouteChange (reason);
1452
1453 for (auto deviceType: activeDeviceTypes)
1454 deviceType->handleRouteChange (reason);
1455 }
1456
1457 #undef JUCE_NSERROR_CHECK
1458
1459 } // namespace juce
1460