Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_ios_Audio.cpp 44KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. The code included in this file is provided under the terms of the ISC license
  8. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  9. To use, copy, modify, and/or distribute this software for any purpose with or
  10. without fee is hereby granted provided that the above copyright notice and
  11. this permission notice appear in all copies.
  12. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  13. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  14. DISCLAIMED.
  15. ==============================================================================
  16. */
  17. namespace juce
  18. {
  19. class iOSAudioIODevice;
  20. static const char* const iOSAudioDeviceName = "iOS Audio";
  21. //==============================================================================
  22. struct AudioSessionHolder : public AsyncUpdater
  23. {
  24. AudioSessionHolder();
  25. ~AudioSessionHolder();
  26. void handleAsyncUpdate() override;
  27. void handleStatusChange (bool enabled, const char* reason) const;
  28. void handleRouteChange (const char* reason);
  29. CriticalSection routeChangeLock;
  30. String lastRouteChangeReason;
  31. Array<iOSAudioIODevice*> activeDevices;
  32. id nativeSession;
  33. };
  34. static const char* getRoutingChangeReason (AVAudioSessionRouteChangeReason reason) noexcept
  35. {
  36. switch (reason)
  37. {
  38. case AVAudioSessionRouteChangeReasonNewDeviceAvailable: return "New device available";
  39. case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: return "Old device unavailable";
  40. case AVAudioSessionRouteChangeReasonCategoryChange: return "Category change";
  41. case AVAudioSessionRouteChangeReasonOverride: return "Override";
  42. case AVAudioSessionRouteChangeReasonWakeFromSleep: return "Wake from sleep";
  43. case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: return "No suitable route for category";
  44. case AVAudioSessionRouteChangeReasonRouteConfigurationChange: return "Route configuration change";
  45. case AVAudioSessionRouteChangeReasonUnknown:
  46. default: return "Unknown";
  47. }
  48. }
  49. bool getNotificationValueForKey (NSNotification* notification, NSString* key, NSUInteger& value) noexcept
  50. {
  51. if (notification != nil)
  52. {
  53. if (NSDictionary* userInfo = [notification userInfo])
  54. {
  55. if (NSNumber* number = [userInfo objectForKey: key])
  56. {
  57. value = [number unsignedIntegerValue];
  58. return true;
  59. }
  60. }
  61. }
  62. jassertfalse;
  63. return false;
  64. }
  65. } // juce namespace
  66. //==============================================================================
  67. @interface iOSAudioSessionNative : NSObject
  68. {
  69. @private
  70. juce::AudioSessionHolder* audioSessionHolder;
  71. };
  72. - (id) init: (juce::AudioSessionHolder*) holder;
  73. - (void) dealloc;
  74. - (void) audioSessionChangedInterruptionType: (NSNotification*) notification;
  75. - (void) handleMediaServicesReset;
  76. - (void) handleMediaServicesLost;
  77. - (void) handleRouteChange: (NSNotification*) notification;
  78. @end
  79. @implementation iOSAudioSessionNative
  80. - (id) init: (juce::AudioSessionHolder*) holder
  81. {
  82. self = [super init];
  83. if (self != nil)
  84. {
  85. audioSessionHolder = holder;
  86. auto session = [AVAudioSession sharedInstance];
  87. auto centre = [NSNotificationCenter defaultCenter];
  88. [centre addObserver: self
  89. selector: @selector (audioSessionChangedInterruptionType:)
  90. name: AVAudioSessionInterruptionNotification
  91. object: session];
  92. [centre addObserver: self
  93. selector: @selector (handleMediaServicesLost)
  94. name: AVAudioSessionMediaServicesWereLostNotification
  95. object: session];
  96. [centre addObserver: self
  97. selector: @selector (handleMediaServicesReset)
  98. name: AVAudioSessionMediaServicesWereResetNotification
  99. object: session];
  100. [centre addObserver: self
  101. selector: @selector (handleRouteChange:)
  102. name: AVAudioSessionRouteChangeNotification
  103. object: session];
  104. }
  105. else
  106. {
  107. jassertfalse;
  108. }
  109. return self;
  110. }
  111. - (void) dealloc
  112. {
  113. [[NSNotificationCenter defaultCenter] removeObserver: self];
  114. [super dealloc];
  115. }
  116. - (void) audioSessionChangedInterruptionType: (NSNotification*) notification
  117. {
  118. NSUInteger value;
  119. if (juce::getNotificationValueForKey (notification, AVAudioSessionInterruptionTypeKey, value))
  120. {
  121. switch ((AVAudioSessionInterruptionType) value)
  122. {
  123. case AVAudioSessionInterruptionTypeBegan:
  124. audioSessionHolder->handleStatusChange (false, "AVAudioSessionInterruptionTypeBegan");
  125. break;
  126. case AVAudioSessionInterruptionTypeEnded:
  127. audioSessionHolder->handleStatusChange (true, "AVAudioSessionInterruptionTypeEnded");
  128. break;
  129. // No default so the code doesn't compile if this enum is extended.
  130. }
  131. }
  132. }
  133. - (void) handleMediaServicesReset
  134. {
  135. audioSessionHolder->handleStatusChange (true, "AVAudioSessionMediaServicesWereResetNotification");
  136. }
  137. - (void) handleMediaServicesLost
  138. {
  139. audioSessionHolder->handleStatusChange (false, "AVAudioSessionMediaServicesWereLostNotification");
  140. }
  141. - (void) handleRouteChange: (NSNotification*) notification
  142. {
  143. NSUInteger value;
  144. if (juce::getNotificationValueForKey (notification, AVAudioSessionRouteChangeReasonKey, value))
  145. audioSessionHolder->handleRouteChange (juce::getRoutingChangeReason ((AVAudioSessionRouteChangeReason) value));
  146. }
  147. @end
  148. //==============================================================================
  149. #if JUCE_MODULE_AVAILABLE_juce_graphics
  150. #include <juce_graphics/native/juce_mac_CoreGraphicsHelpers.h>
  151. #endif
  152. namespace juce {
  153. #ifndef JUCE_IOS_AUDIO_LOGGING
  154. #define JUCE_IOS_AUDIO_LOGGING 0
  155. #endif
  156. #if JUCE_IOS_AUDIO_LOGGING
  157. #define JUCE_IOS_AUDIO_LOG(x) DBG(x)
  158. #else
  159. #define JUCE_IOS_AUDIO_LOG(x)
  160. #endif
  161. static void logNSError (NSError* e)
  162. {
  163. if (e != nil)
  164. {
  165. JUCE_IOS_AUDIO_LOG ("iOS Audio error: " << [e.localizedDescription UTF8String]);
  166. jassertfalse;
  167. }
  168. }
  169. #define JUCE_NSERROR_CHECK(X) { NSError* error = nil; X; logNSError (error); }
  170. //==============================================================================
  171. struct iOSAudioIODevice::Pimpl : public AudioPlayHead,
  172. public AsyncUpdater
  173. {
  174. Pimpl (iOSAudioIODevice& ioDevice)
  175. : owner (ioDevice)
  176. {
  177. sessionHolder->activeDevices.add (&owner);
  178. updateSampleRateAndAudioInput();
  179. }
  180. ~Pimpl()
  181. {
  182. sessionHolder->activeDevices.removeFirstMatchingValue (&owner);
  183. close();
  184. }
  185. static void setAudioSessionActive (bool enabled)
  186. {
  187. JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setActive: enabled
  188. error: &error]);
  189. }
  190. static double trySampleRate (double rate)
  191. {
  192. auto session = [AVAudioSession sharedInstance];
  193. JUCE_NSERROR_CHECK ([session setPreferredSampleRate: rate
  194. error: &error]);
  195. return session.sampleRate;
  196. }
  197. Array<double> getAvailableSampleRates()
  198. {
  199. const ScopedLock sl (callbackLock);
  200. Array<double> rates;
  201. // Important: the supported audio sample rates change on the iPhone 6S
  202. // depending on whether the headphones are plugged in or not!
  203. setAudioSessionActive (true);
  204. AudioUnitRemovePropertyListenerWithUserData (audioUnit,
  205. kAudioUnitProperty_StreamFormat,
  206. dispatchAudioUnitPropertyChange,
  207. this);
  208. const double lowestRate = trySampleRate (4000);
  209. const double highestRate = trySampleRate (192000);
  210. for (double rate = lowestRate; rate <= highestRate; rate += 1000)
  211. {
  212. const double supportedRate = trySampleRate (rate);
  213. if (rates.addIfNotAlreadyThere (supportedRate))
  214. JUCE_IOS_AUDIO_LOG ("available rate = " + String (supportedRate, 0) + "Hz");
  215. rate = jmax (rate, supportedRate);
  216. }
  217. trySampleRate (sampleRate);
  218. updateCurrentBufferSize();
  219. AudioUnitAddPropertyListener (audioUnit,
  220. kAudioUnitProperty_StreamFormat,
  221. dispatchAudioUnitPropertyChange,
  222. this);
  223. return rates;
  224. }
  225. Array<int> getAvailableBufferSizes()
  226. {
  227. Array<int> r;
  228. for (int i = 6; i < 13; ++i)
  229. r.add (1 << i);
  230. return r;
  231. }
  232. void updateSampleRateAndAudioInput()
  233. {
  234. auto session = [AVAudioSession sharedInstance];
  235. sampleRate = session.sampleRate;
  236. audioInputIsAvailable = session.isInputAvailable;
  237. actualBufferSize = roundToInt (sampleRate * session.IOBufferDuration);
  238. JUCE_IOS_AUDIO_LOG ("AVAudioSession: sampleRate: " << sampleRate
  239. << " Hz, audioInputAvailable: " << (int) audioInputIsAvailable
  240. << ", buffer size: " << actualBufferSize);
  241. }
  242. String open (const BigInteger& inputChannelsWanted,
  243. const BigInteger& outputChannelsWanted,
  244. double targetSampleRate, int bufferSize)
  245. {
  246. close();
  247. firstHostTime = true;
  248. lastNumFrames = 0;
  249. xrun = 0;
  250. lastError.clear();
  251. preferredBufferSize = bufferSize <= 0 ? defaultBufferSize : bufferSize;
  252. // xxx set up channel mapping
  253. activeOutputChans = outputChannelsWanted;
  254. activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
  255. numOutputChannels = activeOutputChans.countNumberOfSetBits();
  256. monoOutputChannelNumber = activeOutputChans.findNextSetBit (0);
  257. activeInputChans = inputChannelsWanted;
  258. activeInputChans.setRange (2, activeInputChans.getHighestBit(), false);
  259. numInputChannels = activeInputChans.countNumberOfSetBits();
  260. monoInputChannelNumber = activeInputChans.findNextSetBit (0);
  261. setAudioSessionActive (true);
  262. // Set the session category & options:
  263. auto session = [AVAudioSession sharedInstance];
  264. const bool useInputs = (numInputChannels > 0 && audioInputIsAvailable);
  265. NSString* category = (useInputs ? AVAudioSessionCategoryPlayAndRecord : AVAudioSessionCategoryPlayback);
  266. NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers; // Alternatively AVAudioSessionCategoryOptionDuckOthers
  267. if (useInputs) // These options are only valid for category = PlayAndRecord
  268. options |= (AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth);
  269. JUCE_NSERROR_CHECK ([session setCategory: category
  270. withOptions: options
  271. error: &error]);
  272. fixAudioRouteIfSetToReceiver();
  273. // Set the sample rate
  274. trySampleRate (targetSampleRate);
  275. updateSampleRateAndAudioInput();
  276. updateCurrentBufferSize();
  277. prepareFloatBuffers (actualBufferSize);
  278. isRunning = true;
  279. handleRouteChange ("Started AudioUnit");
  280. lastError = (audioUnit != 0 ? "" : "Couldn't open the device");
  281. setAudioSessionActive (true);
  282. return lastError;
  283. }
  284. void close()
  285. {
  286. if (isRunning)
  287. {
  288. isRunning = false;
  289. if (audioUnit != 0)
  290. {
  291. AudioOutputUnitStart (audioUnit);
  292. AudioComponentInstanceDispose (audioUnit);
  293. audioUnit = 0;
  294. }
  295. setAudioSessionActive (false);
  296. }
  297. }
  298. void start (AudioIODeviceCallback* newCallback)
  299. {
  300. if (isRunning && callback != newCallback)
  301. {
  302. if (newCallback != nullptr)
  303. newCallback->audioDeviceAboutToStart (&owner);
  304. const ScopedLock sl (callbackLock);
  305. callback = newCallback;
  306. }
  307. }
  308. void stop()
  309. {
  310. if (isRunning)
  311. {
  312. AudioIODeviceCallback* lastCallback;
  313. {
  314. const ScopedLock sl (callbackLock);
  315. lastCallback = callback;
  316. callback = nullptr;
  317. }
  318. if (lastCallback != nullptr)
  319. lastCallback->audioDeviceStopped();
  320. }
  321. }
  322. bool setAudioPreprocessingEnabled (bool enable)
  323. {
  324. auto session = [AVAudioSession sharedInstance];
  325. NSString* mode = (enable ? AVAudioSessionModeMeasurement
  326. : AVAudioSessionModeDefault);
  327. JUCE_NSERROR_CHECK ([session setMode: mode
  328. error: &error]);
  329. return session.mode == mode;
  330. }
  331. //==============================================================================
  332. bool canControlTransport() override { return interAppAudioConnected; }
  333. void transportPlay (bool shouldSartPlaying) override
  334. {
  335. if (! canControlTransport())
  336. return;
  337. HostCallbackInfo callbackInfo;
  338. fillHostCallbackInfo (callbackInfo);
  339. Boolean hostIsPlaying = NO;
  340. OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
  341. &hostIsPlaying,
  342. nullptr,
  343. nullptr,
  344. nullptr,
  345. nullptr,
  346. nullptr,
  347. nullptr);
  348. ignoreUnused (err);
  349. jassert (err == noErr);
  350. if (hostIsPlaying != shouldSartPlaying)
  351. handleAudioTransportEvent (kAudioUnitRemoteControlEvent_TogglePlayPause);
  352. }
  353. void transportRecord (bool shouldStartRecording) override
  354. {
  355. if (! canControlTransport())
  356. return;
  357. HostCallbackInfo callbackInfo;
  358. fillHostCallbackInfo (callbackInfo);
  359. Boolean hostIsRecording = NO;
  360. OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
  361. nullptr,
  362. &hostIsRecording,
  363. nullptr,
  364. nullptr,
  365. nullptr,
  366. nullptr,
  367. nullptr);
  368. ignoreUnused (err);
  369. jassert (err == noErr);
  370. if (hostIsRecording != shouldStartRecording)
  371. handleAudioTransportEvent (kAudioUnitRemoteControlEvent_ToggleRecord);
  372. }
  373. void transportRewind() override
  374. {
  375. if (canControlTransport())
  376. handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind);
  377. }
  378. bool getCurrentPosition (CurrentPositionInfo& result) override
  379. {
  380. if (! canControlTransport())
  381. return false;
  382. zerostruct (result);
  383. HostCallbackInfo callbackInfo;
  384. fillHostCallbackInfo (callbackInfo);
  385. if (callbackInfo.hostUserData == nullptr)
  386. return false;
  387. Boolean hostIsPlaying = NO;
  388. Boolean hostIsRecording = NO;
  389. Float64 hostCurrentSampleInTimeLine = 0;
  390. Boolean hostIsCycling = NO;
  391. Float64 hostCycleStartBeat = 0;
  392. Float64 hostCycleEndBeat = 0;
  393. OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
  394. &hostIsPlaying,
  395. &hostIsRecording,
  396. nullptr,
  397. &hostCurrentSampleInTimeLine,
  398. &hostIsCycling,
  399. &hostCycleStartBeat,
  400. &hostCycleEndBeat);
  401. if (err == kAUGraphErr_CannotDoInCurrentContext)
  402. return false;
  403. jassert (err == noErr);
  404. result.timeInSamples = (int64) hostCurrentSampleInTimeLine;
  405. result.isPlaying = hostIsPlaying;
  406. result.isRecording = hostIsRecording;
  407. result.isLooping = hostIsCycling;
  408. result.ppqLoopStart = hostCycleStartBeat;
  409. result.ppqLoopEnd = hostCycleEndBeat;
  410. result.timeInSeconds = result.timeInSamples / sampleRate;
  411. Float64 hostBeat = 0;
  412. Float64 hostTempo = 0;
  413. err = callbackInfo.beatAndTempoProc (callbackInfo.hostUserData,
  414. &hostBeat,
  415. &hostTempo);
  416. jassert (err == noErr);
  417. result.ppqPosition = hostBeat;
  418. result.bpm = hostTempo;
  419. Float32 hostTimeSigNumerator = 0;
  420. UInt32 hostTimeSigDenominator = 0;
  421. Float64 hostCurrentMeasureDownBeat = 0;
  422. err = callbackInfo.musicalTimeLocationProc (callbackInfo.hostUserData,
  423. nullptr,
  424. &hostTimeSigNumerator,
  425. &hostTimeSigDenominator,
  426. &hostCurrentMeasureDownBeat);
  427. jassert (err == noErr);
  428. result.ppqPositionOfLastBarStart = hostCurrentMeasureDownBeat;
  429. result.timeSigNumerator = (int) hostTimeSigNumerator;
  430. result.timeSigDenominator = (int) hostTimeSigDenominator;
  431. result.frameRate = AudioPlayHead::fpsUnknown;
  432. return true;
  433. }
  434. //==============================================================================
  435. #if JUCE_MODULE_AVAILABLE_juce_graphics
  436. Image getIcon (int size)
  437. {
  438. if (interAppAudioConnected)
  439. {
  440. UIImage* hostUIImage = AudioOutputUnitGetHostIcon (audioUnit, size);
  441. if (hostUIImage != nullptr)
  442. return juce_createImageFromUIImage (hostUIImage);
  443. }
  444. return Image();
  445. }
  446. #endif
  447. void switchApplication()
  448. {
  449. if (! interAppAudioConnected)
  450. return;
  451. CFURLRef hostUrl;
  452. UInt32 dataSize = sizeof (hostUrl);
  453. OSStatus err = AudioUnitGetProperty(audioUnit,
  454. kAudioUnitProperty_PeerURL,
  455. kAudioUnitScope_Global,
  456. 0,
  457. &hostUrl,
  458. &dataSize);
  459. if (err == noErr)
  460. {
  461. #if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_10_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0)
  462. [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl];
  463. #else
  464. [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl options: @{} completionHandler: nil];
  465. #endif
  466. }
  467. }
  468. //==============================================================================
  469. void invokeAudioDeviceErrorCallback (const String& reason)
  470. {
  471. const ScopedLock sl (callbackLock);
  472. if (callback != nullptr)
  473. callback->audioDeviceError (reason);
  474. }
  475. void handleStatusChange (bool enabled, const char* reason)
  476. {
  477. const ScopedLock myScopedLock (callbackLock);
  478. JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason);
  479. isRunning = enabled;
  480. setAudioSessionActive (enabled);
  481. if (enabled)
  482. AudioOutputUnitStart (audioUnit);
  483. else
  484. AudioOutputUnitStop (audioUnit);
  485. if (! enabled)
  486. invokeAudioDeviceErrorCallback (reason);
  487. }
  488. void handleRouteChange (const char* reason)
  489. {
  490. const ScopedLock myScopedLock (callbackLock);
  491. JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason);
  492. fixAudioRouteIfSetToReceiver();
  493. if (isRunning)
  494. invokeAudioDeviceErrorCallback (reason);
  495. restart();
  496. }
  497. void handleAudioUnitPropertyChange (AudioUnit,
  498. AudioUnitPropertyID propertyID,
  499. AudioUnitScope scope,
  500. AudioUnitElement element)
  501. {
  502. JUCE_IOS_AUDIO_LOG ("handleAudioUnitPropertyChange: propertyID: " << String (propertyID)
  503. << " scope: " << String (scope)
  504. << " element: " << String (element));
  505. switch (propertyID)
  506. {
  507. case kAudioUnitProperty_IsInterAppConnected:
  508. handleInterAppAudioConnectionChange();
  509. return;
  510. case kAudioUnitProperty_StreamFormat:
  511. if (scope == kAudioUnitScope_Output && element == 0)
  512. handleStreamFormatChange();
  513. return;
  514. default:
  515. jassertfalse;
  516. return;
  517. }
  518. }
  519. void handleInterAppAudioConnectionChange()
  520. {
  521. UInt32 connected;
  522. UInt32 dataSize = sizeof (connected);
  523. OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_IsInterAppConnected,
  524. kAudioUnitScope_Global, 0, &connected, &dataSize);
  525. ignoreUnused (err);
  526. jassert (err == noErr);
  527. JUCE_IOS_AUDIO_LOG ("handleInterAppAudioConnectionChange: " << (connected ? "connected"
  528. : "disconnected"));
  529. if (connected != interAppAudioConnected)
  530. {
  531. const ScopedLock myScopedLock (callbackLock);
  532. interAppAudioConnected = connected;
  533. UIApplicationState appstate = [UIApplication sharedApplication].applicationState;
  534. bool inForeground = (appstate != UIApplicationStateBackground);
  535. if (interAppAudioConnected || inForeground)
  536. {
  537. setAudioSessionActive (true);
  538. AudioOutputUnitStart (audioUnit);
  539. if (callback != nullptr)
  540. callback->audioDeviceAboutToStart (&owner);
  541. }
  542. else if (! inForeground)
  543. {
  544. AudioOutputUnitStop (audioUnit);
  545. setAudioSessionActive (false);
  546. }
  547. }
  548. }
  549. //==============================================================================
  550. void prepareFloatBuffers (int bufferSize)
  551. {
  552. if (numInputChannels + numOutputChannels > 0)
  553. {
  554. floatData.setSize (numInputChannels + numOutputChannels, bufferSize);
  555. zeromem (inputChannels, sizeof (inputChannels));
  556. zeromem (outputChannels, sizeof (outputChannels));
  557. for (int i = 0; i < numInputChannels; ++i)
  558. inputChannels[i] = floatData.getWritePointer (i);
  559. for (int i = 0; i < numOutputChannels; ++i)
  560. outputChannels[i] = floatData.getWritePointer (i + numInputChannels);
  561. }
  562. }
  563. //==============================================================================
  564. OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
  565. const UInt32 numFrames, AudioBufferList* data)
  566. {
  567. OSStatus err = noErr;
  568. recordXruns (time, numFrames);
  569. if (audioInputIsAvailable && numInputChannels > 0)
  570. err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);
  571. const ScopedTryLock stl (callbackLock);
  572. if (stl.isLocked() && callback != nullptr)
  573. {
  574. if ((int) numFrames > floatData.getNumSamples())
  575. prepareFloatBuffers ((int) numFrames);
  576. if (audioInputIsAvailable && numInputChannels > 0)
  577. {
  578. short* shortData = (short*) data->mBuffers[0].mData;
  579. if (numInputChannels >= 2)
  580. {
  581. for (UInt32 i = 0; i < numFrames; ++i)
  582. {
  583. inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
  584. inputChannels[1][i] = *shortData++ * (1.0f / 32768.0f);
  585. }
  586. }
  587. else
  588. {
  589. if (monoInputChannelNumber > 0)
  590. ++shortData;
  591. for (UInt32 i = 0; i < numFrames; ++i)
  592. {
  593. inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
  594. ++shortData;
  595. }
  596. }
  597. }
  598. else
  599. {
  600. for (int i = numInputChannels; --i >= 0;)
  601. zeromem (inputChannels[i], sizeof (float) * numFrames);
  602. }
  603. callback->audioDeviceIOCallback ((const float**) inputChannels, numInputChannels,
  604. outputChannels, numOutputChannels, (int) numFrames);
  605. short* const shortData = (short*) data->mBuffers[0].mData;
  606. int n = 0;
  607. if (numOutputChannels >= 2)
  608. {
  609. for (UInt32 i = 0; i < numFrames; ++i)
  610. {
  611. shortData [n++] = (short) (outputChannels[0][i] * 32767.0f);
  612. shortData [n++] = (short) (outputChannels[1][i] * 32767.0f);
  613. }
  614. }
  615. else if (numOutputChannels == 1)
  616. {
  617. for (UInt32 i = 0; i < numFrames; ++i)
  618. {
  619. const short s = (short) (outputChannels[monoOutputChannelNumber][i] * 32767.0f);
  620. shortData [n++] = s;
  621. shortData [n++] = s;
  622. }
  623. }
  624. else
  625. {
  626. zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
  627. }
  628. }
  629. else
  630. {
  631. zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
  632. }
  633. return err;
  634. }
  635. void updateCurrentBufferSize()
  636. {
  637. NSTimeInterval bufferDuration = sampleRate > 0 ? (NSTimeInterval) ((preferredBufferSize + 1) / sampleRate) : 0.0;
  638. JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setPreferredIOBufferDuration: bufferDuration
  639. error: &error]);
  640. updateSampleRateAndAudioInput();
  641. }
  642. void recordXruns (const AudioTimeStamp* time, UInt32 numFrames)
  643. {
  644. if (time != nullptr && (time->mFlags & kAudioTimeStampSampleTimeValid) != 0)
  645. {
  646. if (! firstHostTime)
  647. {
  648. if ((time->mSampleTime - lastSampleTime) != lastNumFrames)
  649. xrun++;
  650. }
  651. else
  652. firstHostTime = false;
  653. lastSampleTime = time->mSampleTime;
  654. }
  655. else
  656. firstHostTime = true;
  657. lastNumFrames = numFrames;
  658. }
  659. //==============================================================================
  660. static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
  661. UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data)
  662. {
  663. return static_cast<Pimpl*> (client)->process (flags, time, numFrames, data);
  664. }
  665. //==============================================================================
  666. void resetFormat (const int numChannels) noexcept
  667. {
  668. zerostruct (format);
  669. format.mFormatID = kAudioFormatLinearPCM;
  670. format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
  671. format.mBitsPerChannel = 8 * sizeof (short);
  672. format.mChannelsPerFrame = (UInt32) numChannels;
  673. format.mFramesPerPacket = 1;
  674. format.mBytesPerFrame = format.mBytesPerPacket = (UInt32) numChannels * sizeof (short);
  675. }
  676. bool createAudioUnit()
  677. {
  678. if (audioUnit != 0)
  679. {
  680. AudioComponentInstanceDispose (audioUnit);
  681. audioUnit = 0;
  682. }
  683. resetFormat (2);
  684. AudioComponentDescription desc;
  685. desc.componentType = kAudioUnitType_Output;
  686. desc.componentSubType = kAudioUnitSubType_RemoteIO;
  687. desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  688. desc.componentFlags = 0;
  689. desc.componentFlagsMask = 0;
  690. AudioComponent comp = AudioComponentFindNext (0, &desc);
  691. AudioComponentInstanceNew (comp, &audioUnit);
  692. if (audioUnit == 0)
  693. return false;
  694. #if JucePlugin_Enable_IAA
  695. AudioComponentDescription appDesc;
  696. appDesc.componentType = JucePlugin_IAAType;
  697. appDesc.componentSubType = JucePlugin_IAASubType;
  698. appDesc.componentManufacturer = JucePlugin_ManufacturerCode;
  699. appDesc.componentFlags = 0;
  700. appDesc.componentFlagsMask = 0;
  701. OSStatus err = AudioOutputUnitPublish (&appDesc,
  702. CFSTR(JucePlugin_IAAName),
  703. JucePlugin_VersionCode,
  704. audioUnit);
  705. // This assert will be hit if the Inter-App Audio entitlement has not
  706. // been enabled, or the description being published with
  707. // AudioOutputUnitPublish is different from any in the AudioComponents
  708. // array in this application's .plist file.
  709. jassert (err == noErr);
  710. err = AudioUnitAddPropertyListener (audioUnit,
  711. kAudioUnitProperty_IsInterAppConnected,
  712. dispatchAudioUnitPropertyChange,
  713. this);
  714. jassert (err == noErr);
  715. #endif
  716. if (numInputChannels > 0)
  717. {
  718. const UInt32 one = 1;
  719. AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one));
  720. }
  721. {
  722. AudioChannelLayout layout;
  723. layout.mChannelBitmap = 0;
  724. layout.mNumberChannelDescriptions = 0;
  725. layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
  726. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof (layout));
  727. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, &layout, sizeof (layout));
  728. }
  729. {
  730. AURenderCallbackStruct inputProc;
  731. inputProc.inputProc = processStatic;
  732. inputProc.inputProcRefCon = this;
  733. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc));
  734. }
  735. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format));
  736. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format));
  737. UInt32 framesPerSlice;
  738. UInt32 dataSize = sizeof (framesPerSlice);
  739. AudioUnitInitialize (audioUnit);
  740. updateCurrentBufferSize();
  741. if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
  742. kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr
  743. && dataSize == sizeof (framesPerSlice) && static_cast<int> (framesPerSlice) != actualBufferSize)
  744. {
  745. prepareFloatBuffers (static_cast<int> (framesPerSlice));
  746. }
  747. AudioUnitAddPropertyListener (audioUnit, kAudioUnitProperty_StreamFormat, dispatchAudioUnitPropertyChange, this);
  748. return true;
  749. }
  750. void fillHostCallbackInfo (HostCallbackInfo& callbackInfo)
  751. {
  752. zerostruct (callbackInfo);
  753. UInt32 dataSize = sizeof (HostCallbackInfo);
  754. OSStatus err = AudioUnitGetProperty (audioUnit,
  755. kAudioUnitProperty_HostCallbacks,
  756. kAudioUnitScope_Global,
  757. 0,
  758. &callbackInfo,
  759. &dataSize);
  760. ignoreUnused (err);
  761. jassert (err == noErr);
  762. }
  763. void handleAudioTransportEvent (AudioUnitRemoteControlEvent event)
  764. {
  765. OSStatus err = AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_RemoteControlToHost,
  766. kAudioUnitScope_Global, 0, &event, sizeof (event));
  767. ignoreUnused (err);
  768. jassert (err == noErr);
  769. }
  770. // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it
  771. // to make it loud. Needed because by default when using an input + output, the output is kept quiet.
  772. static void fixAudioRouteIfSetToReceiver()
  773. {
  774. auto session = [AVAudioSession sharedInstance];
  775. auto route = session.currentRoute;
  776. for (AVAudioSessionPortDescription* port in route.inputs)
  777. {
  778. ignoreUnused (port);
  779. JUCE_IOS_AUDIO_LOG ("AVAudioSession: input: " << [port.description UTF8String]);
  780. }
  781. for (AVAudioSessionPortDescription* port in route.outputs)
  782. {
  783. JUCE_IOS_AUDIO_LOG ("AVAudioSession: output: " << [port.description UTF8String]);
  784. if ([port.portName isEqualToString: @"Receiver"])
  785. {
  786. JUCE_NSERROR_CHECK ([session overrideOutputAudioPort: AVAudioSessionPortOverrideSpeaker
  787. error: &error]);
  788. setAudioSessionActive (true);
  789. }
  790. }
  791. }
  792. void restart()
  793. {
  794. if (isRunning)
  795. {
  796. updateSampleRateAndAudioInput();
  797. updateCurrentBufferSize();
  798. createAudioUnit();
  799. setAudioSessionActive (true);
  800. if (audioUnit != 0)
  801. {
  802. UInt32 formatSize = sizeof (format);
  803. AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize);
  804. AudioOutputUnitStart (audioUnit);
  805. }
  806. if (callback != nullptr)
  807. {
  808. callback->audioDeviceStopped();
  809. callback->audioDeviceAboutToStart (&owner);
  810. }
  811. }
  812. }
  813. void handleAsyncUpdate() override
  814. {
  815. restart();
  816. }
  817. void handleStreamFormatChange()
  818. {
  819. AudioStreamBasicDescription desc;
  820. zerostruct (desc);
  821. UInt32 dataSize = sizeof (desc);
  822. AudioUnitGetProperty(audioUnit,
  823. kAudioUnitProperty_StreamFormat,
  824. kAudioUnitScope_Output,
  825. 0,
  826. &desc,
  827. &dataSize);
  828. if (desc.mSampleRate != sampleRate)
  829. {
  830. JUCE_IOS_AUDIO_LOG ("handleStreamFormatChange: sample rate " << desc.mSampleRate);
  831. triggerAsyncUpdate();
  832. }
  833. }
  834. static void dispatchAudioUnitPropertyChange (void* data, AudioUnit unit, AudioUnitPropertyID propertyID,
  835. AudioUnitScope scope, AudioUnitElement element)
  836. {
  837. static_cast<Pimpl*> (data)->handleAudioUnitPropertyChange (unit, propertyID, scope, element);
  838. }
  839. void handleMidiMessage (MidiMessage msg)
  840. {
  841. if (messageCollector != nullptr)
  842. messageCollector->addMessageToQueue (msg);
  843. }
  844. static void midiEventCallback (void *client, UInt32 status, UInt32 data1, UInt32 data2, UInt32)
  845. {
  846. return static_cast<Pimpl*> (client)->handleMidiMessage (MidiMessage ((int) status,
  847. (int) data1,
  848. (int) data2,
  849. Time::getMillisecondCounter() / 1000.0));
  850. }
  851. bool isRunning = false;
  852. AudioIODeviceCallback* callback = nullptr;
  853. String lastError;
  854. bool audioInputIsAvailable = false;
  855. const int defaultBufferSize =
  856. #if TARGET_IPHONE_SIMULATOR
  857. 512;
  858. #else
  859. 256;
  860. #endif
  861. double sampleRate = 0;
  862. int numInputChannels = 2, numOutputChannels = 2;
  863. int preferredBufferSize = 0, actualBufferSize = 0;
  864. bool interAppAudioConnected = false;
  865. BigInteger activeOutputChans, activeInputChans;
  866. MidiMessageCollector* messageCollector = nullptr;
  867. iOSAudioIODevice& owner;
  868. SharedResourcePointer<AudioSessionHolder> sessionHolder;
  869. CriticalSection callbackLock;
  870. AudioStreamBasicDescription format;
  871. AudioUnit audioUnit {};
  872. AudioSampleBuffer floatData;
  873. float* inputChannels[3];
  874. float* outputChannels[3];
  875. bool monoInputChannelNumber, monoOutputChannelNumber;
  876. bool firstHostTime;
  877. Float64 lastSampleTime;
  878. unsigned int lastNumFrames;
  879. int xrun;
  880. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  881. };
  882. //==============================================================================
  883. iOSAudioIODevice::iOSAudioIODevice (const String& deviceName)
  884. : AudioIODevice (deviceName, iOSAudioDeviceName),
  885. pimpl (new Pimpl (*this))
  886. {}
  887. //==============================================================================
  888. String iOSAudioIODevice::open (const BigInteger& inChans, const BigInteger& outChans,
  889. double requestedSampleRate, int requestedBufferSize)
  890. {
  891. return pimpl->open (inChans, outChans, requestedSampleRate, requestedBufferSize);
  892. }
  893. void iOSAudioIODevice::close() { pimpl->close(); }
  894. void iOSAudioIODevice::start (AudioIODeviceCallback* callbackToUse) { pimpl->start (callbackToUse); }
  895. void iOSAudioIODevice::stop() { pimpl->stop(); }
  896. Array<double> iOSAudioIODevice::getAvailableSampleRates() { return pimpl->getAvailableSampleRates(); }
  897. Array<int> iOSAudioIODevice::getAvailableBufferSizes() { return pimpl->getAvailableBufferSizes(); }
  898. bool iOSAudioIODevice::setAudioPreprocessingEnabled (bool enabled) { return pimpl->setAudioPreprocessingEnabled (enabled); }
  899. bool iOSAudioIODevice::isPlaying() { return pimpl->isRunning && pimpl->callback != nullptr; }
  900. bool iOSAudioIODevice::isOpen() { return pimpl->isRunning; }
  901. String iOSAudioIODevice::getLastError() { return pimpl->lastError; }
  902. StringArray iOSAudioIODevice::getOutputChannelNames() { return { "Left", "Right" }; }
  903. StringArray iOSAudioIODevice::getInputChannelNames() { return pimpl->audioInputIsAvailable ? getOutputChannelNames() : StringArray(); }
  904. int iOSAudioIODevice::getDefaultBufferSize() { return pimpl->defaultBufferSize; }
  905. int iOSAudioIODevice::getCurrentBufferSizeSamples() { return pimpl->actualBufferSize; }
  906. double iOSAudioIODevice::getCurrentSampleRate() { return pimpl->sampleRate; }
  907. int iOSAudioIODevice::getCurrentBitDepth() { return 16; }
  908. BigInteger iOSAudioIODevice::getActiveOutputChannels() const { return pimpl->activeOutputChans; }
  909. BigInteger iOSAudioIODevice::getActiveInputChannels() const { return pimpl->activeInputChans; }
  910. int iOSAudioIODevice::getOutputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].outputLatency); }
  911. int iOSAudioIODevice::getInputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].inputLatency); }
  912. int iOSAudioIODevice::getXRunCount() const noexcept { return pimpl->xrun; }
  913. void iOSAudioIODevice::setMidiMessageCollector (MidiMessageCollector* collector) { pimpl->messageCollector = collector; }
  914. AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const { return pimpl; }
  915. bool iOSAudioIODevice::isInterAppAudioConnected() const { return pimpl->interAppAudioConnected; }
  916. #if JUCE_MODULE_AVAILABLE_juce_graphics
  917. Image iOSAudioIODevice::getIcon (int size) { return pimpl->getIcon (size); }
  918. #endif
  919. void iOSAudioIODevice::switchApplication() { return pimpl->switchApplication(); }
  920. //==============================================================================
  921. struct iOSAudioIODeviceType : public AudioIODeviceType
  922. {
  923. iOSAudioIODeviceType() : AudioIODeviceType (iOSAudioDeviceName) {}
  924. void scanForDevices() {}
  925. StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray (iOSAudioDeviceName); }
  926. int getDefaultDeviceIndex (bool /*forInput*/) const { return 0; }
  927. int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const { return d != nullptr ? 0 : -1; }
  928. bool hasSeparateInputsAndOutputs() const { return false; }
  929. AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName)
  930. {
  931. if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
  932. return new iOSAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName : inputDeviceName);
  933. return nullptr;
  934. }
  935. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType)
  936. };
  937. //==============================================================================
  938. AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio()
  939. {
  940. return new iOSAudioIODeviceType();
  941. }
  942. //==============================================================================
  943. AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; }
  944. AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; }
  945. void AudioSessionHolder::handleAsyncUpdate()
  946. {
  947. const ScopedLock sl (routeChangeLock);
  948. for (auto device: activeDevices)
  949. device->pimpl->handleRouteChange (lastRouteChangeReason.toRawUTF8());
  950. }
  951. void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) const
  952. {
  953. for (auto device: activeDevices)
  954. device->pimpl->handleStatusChange (enabled, reason);
  955. }
  956. void AudioSessionHolder::handleRouteChange (const char* reason)
  957. {
  958. const ScopedLock sl (routeChangeLock);
  959. lastRouteChangeReason = reason;
  960. triggerAsyncUpdate();
  961. }
  962. #undef JUCE_NSERROR_CHECK
  963. } // namespace juce