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.

860 lines
30KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2016 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license/
  7. Permission to use, copy, modify, and/or distribute this software for any
  8. purpose with or without fee is hereby granted, provided that the above
  9. copyright notice and this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
  11. TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  12. FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
  13. OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
  14. USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
  15. TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  16. OF THIS SOFTWARE.
  17. -----------------------------------------------------------------------------
  18. To release a closed-source product which uses other parts of JUCE not
  19. licensed under the ISC terms, commercial licenses are available: visit
  20. www.juce.com for more information.
  21. ==============================================================================
  22. */
  23. class iOSAudioIODevice;
  24. static const char* const iOSAudioDeviceName = "iOS Audio";
  25. //==============================================================================
  26. struct AudioSessionHolder
  27. {
  28. AudioSessionHolder();
  29. ~AudioSessionHolder();
  30. void handleStatusChange (bool enabled, const char* reason) const;
  31. void handleRouteChange (const char* reason) const;
  32. Array<iOSAudioIODevice*> activeDevices;
  33. id nativeSession;
  34. };
  35. static const char* getRoutingChangeReason (AVAudioSessionRouteChangeReason reason) noexcept
  36. {
  37. switch (reason)
  38. {
  39. case AVAudioSessionRouteChangeReasonNewDeviceAvailable: return "New device available";
  40. case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: return "Old device unavailable";
  41. case AVAudioSessionRouteChangeReasonCategoryChange: return "Category change";
  42. case AVAudioSessionRouteChangeReasonOverride: return "Override";
  43. case AVAudioSessionRouteChangeReasonWakeFromSleep: return "Wake from sleep";
  44. case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: return "No suitable route for category";
  45. case AVAudioSessionRouteChangeReasonRouteConfigurationChange: return "Route configuration change";
  46. case AVAudioSessionRouteChangeReasonUnknown:
  47. default: return "Unknown";
  48. }
  49. }
  50. bool getNotificationValueForKey (NSNotification* notification, NSString* key, NSUInteger& value) noexcept
  51. {
  52. if (notification != nil)
  53. {
  54. if (NSDictionary* userInfo = [notification userInfo])
  55. {
  56. if (NSNumber* number = [userInfo objectForKey: key])
  57. {
  58. value = [number unsignedIntegerValue];
  59. return true;
  60. }
  61. }
  62. }
  63. jassertfalse;
  64. return false;
  65. }
  66. } // juce namespace
  67. //==============================================================================
  68. @interface iOSAudioSessionNative : NSObject
  69. {
  70. @private
  71. juce::AudioSessionHolder* audioSessionHolder;
  72. };
  73. - (id) init: (juce::AudioSessionHolder*) holder;
  74. - (void) dealloc;
  75. - (void) audioSessionChangedInterruptionType: (NSNotification*) notification;
  76. - (void) handleMediaServicesReset;
  77. - (void) handleMediaServicesLost;
  78. - (void) handleRouteChange: (NSNotification*) notification;
  79. @end
  80. @implementation iOSAudioSessionNative
  81. - (id) init: (juce::AudioSessionHolder*) holder
  82. {
  83. self = [super init];
  84. if (self != nil)
  85. {
  86. audioSessionHolder = holder;
  87. auto session = [AVAudioSession sharedInstance];
  88. auto centre = [NSNotificationCenter defaultCenter];
  89. [centre addObserver: self
  90. selector: @selector (audioSessionChangedInterruptionType:)
  91. name: AVAudioSessionInterruptionNotification
  92. object: session];
  93. [centre addObserver: self
  94. selector: @selector (handleMediaServicesLost)
  95. name: AVAudioSessionMediaServicesWereLostNotification
  96. object: session];
  97. [centre addObserver: self
  98. selector: @selector (handleMediaServicesReset)
  99. name: AVAudioSessionMediaServicesWereResetNotification
  100. object: session];
  101. [centre addObserver: self
  102. selector: @selector (handleRouteChange:)
  103. name: AVAudioSessionRouteChangeNotification
  104. object: session];
  105. }
  106. else
  107. {
  108. jassertfalse;
  109. }
  110. return self;
  111. }
  112. - (void) dealloc
  113. {
  114. [[NSNotificationCenter defaultCenter] removeObserver: self];
  115. [super dealloc];
  116. }
  117. - (void) audioSessionChangedInterruptionType: (NSNotification*) notification
  118. {
  119. NSUInteger value;
  120. if (juce::getNotificationValueForKey (notification, AVAudioSessionInterruptionTypeKey, value))
  121. {
  122. switch ((AVAudioSessionInterruptionType) value)
  123. {
  124. case AVAudioSessionInterruptionTypeBegan:
  125. audioSessionHolder->handleStatusChange (false, "AVAudioSessionInterruptionTypeBegan");
  126. break;
  127. case AVAudioSessionInterruptionTypeEnded:
  128. audioSessionHolder->handleStatusChange (true, "AVAudioSessionInterruptionTypeEnded");
  129. break;
  130. // No default so the code doesn't compile if this enum is extended.
  131. }
  132. }
  133. }
  134. - (void) handleMediaServicesReset
  135. {
  136. audioSessionHolder->handleStatusChange (true, "AVAudioSessionMediaServicesWereResetNotification");
  137. }
  138. - (void) handleMediaServicesLost
  139. {
  140. audioSessionHolder->handleStatusChange (false, "AVAudioSessionMediaServicesWereLostNotification");
  141. }
  142. - (void) handleRouteChange: (NSNotification*) notification
  143. {
  144. NSUInteger value;
  145. if (juce::getNotificationValueForKey (notification, AVAudioSessionRouteChangeReasonKey, value))
  146. audioSessionHolder->handleRouteChange (juce::getRoutingChangeReason ((AVAudioSessionRouteChangeReason) value));
  147. }
  148. @end
  149. //==============================================================================
  150. namespace juce {
  151. #ifndef JUCE_IOS_AUDIO_LOGGING
  152. #define JUCE_IOS_AUDIO_LOGGING 0
  153. #endif
  154. #if JUCE_IOS_AUDIO_LOGGING
  155. #define JUCE_IOS_AUDIO_LOG(x) DBG(x)
  156. #else
  157. #define JUCE_IOS_AUDIO_LOG(x)
  158. #endif
  159. static void logNSError (NSError* e)
  160. {
  161. if (e != nil)
  162. {
  163. JUCE_IOS_AUDIO_LOG ("iOS Audio error: " << [e.localizedDescription UTF8String]);
  164. jassertfalse;
  165. }
  166. }
  167. #define JUCE_NSERROR_CHECK(X) { NSError* error = nil; X; logNSError (error); }
  168. //==============================================================================
  169. class iOSAudioIODevice : public AudioIODevice
  170. {
  171. public:
  172. iOSAudioIODevice (const String& deviceName)
  173. : AudioIODevice (deviceName, iOSAudioDeviceName)
  174. {
  175. sessionHolder->activeDevices.add (this);
  176. updateSampleRateAndAudioInput();
  177. }
  178. ~iOSAudioIODevice()
  179. {
  180. sessionHolder->activeDevices.removeFirstMatchingValue (this);
  181. close();
  182. }
  183. StringArray getOutputChannelNames() override
  184. {
  185. return { "Left", "Right" };
  186. }
  187. StringArray getInputChannelNames() override
  188. {
  189. if (audioInputIsAvailable)
  190. return { "Left", "Right" };
  191. return {};
  192. }
  193. static void setAudioSessionActive (bool enabled)
  194. {
  195. JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setActive: enabled
  196. error: &error]);
  197. }
  198. static double trySampleRate (double rate)
  199. {
  200. auto session = [AVAudioSession sharedInstance];
  201. JUCE_NSERROR_CHECK ([session setPreferredSampleRate: rate
  202. error: &error]);
  203. return session.sampleRate;
  204. }
  205. Array<double> getAvailableSampleRates() override
  206. {
  207. Array<double> rates;
  208. // Important: the supported audio sample rates change on the iPhone 6S
  209. // depending on whether the headphones are plugged in or not!
  210. setAudioSessionActive (true);
  211. AudioUnitRemovePropertyListenerWithUserData (audioUnit,
  212. kAudioUnitProperty_StreamFormat,
  213. handleStreamFormatChangeCallback,
  214. this);
  215. const double lowestRate = trySampleRate (4000);
  216. const double highestRate = trySampleRate (192000);
  217. for (double rate = lowestRate; rate <= highestRate; rate += 1000)
  218. {
  219. const double supportedRate = trySampleRate (rate);
  220. rates.addIfNotAlreadyThere (supportedRate);
  221. rate = jmax (rate, supportedRate);
  222. }
  223. trySampleRate (getCurrentSampleRate());
  224. AudioUnitAddPropertyListener (audioUnit,
  225. kAudioUnitProperty_StreamFormat,
  226. handleStreamFormatChangeCallback,
  227. this);
  228. for (auto r : rates)
  229. {
  230. ignoreUnused (r);
  231. JUCE_IOS_AUDIO_LOG ("available rate = " + String (r, 0) + "Hz");
  232. }
  233. return rates;
  234. }
  235. Array<int> getAvailableBufferSizes() override
  236. {
  237. Array<int> r;
  238. for (int i = 6; i < 13; ++i)
  239. r.add (1 << i);
  240. return r;
  241. }
  242. int getDefaultBufferSize() override
  243. {
  244. #if TARGET_IPHONE_SIMULATOR
  245. return 512;
  246. #else
  247. return 256;
  248. #endif
  249. }
  250. String open (const BigInteger& inputChannelsWanted,
  251. const BigInteger& outputChannelsWanted,
  252. double targetSampleRate, int bufferSize) override
  253. {
  254. close();
  255. lastError.clear();
  256. preferredBufferSize = bufferSize <= 0 ? getDefaultBufferSize()
  257. : bufferSize;
  258. // xxx set up channel mapping
  259. activeOutputChans = outputChannelsWanted;
  260. activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
  261. numOutputChannels = activeOutputChans.countNumberOfSetBits();
  262. monoOutputChannelNumber = activeOutputChans.findNextSetBit (0);
  263. activeInputChans = inputChannelsWanted;
  264. activeInputChans.setRange (2, activeInputChans.getHighestBit(), false);
  265. numInputChannels = activeInputChans.countNumberOfSetBits();
  266. monoInputChannelNumber = activeInputChans.findNextSetBit (0);
  267. setAudioSessionActive (true);
  268. // Set the session category & options:
  269. auto session = [AVAudioSession sharedInstance];
  270. const bool useInputs = (numInputChannels > 0 && audioInputIsAvailable);
  271. NSString* category = (useInputs ? AVAudioSessionCategoryPlayAndRecord : AVAudioSessionCategoryPlayback);
  272. NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers; // Alternatively AVAudioSessionCategoryOptionDuckOthers
  273. if (useInputs) // These options are only valid for category = PlayAndRecord
  274. options |= (AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth);
  275. JUCE_NSERROR_CHECK ([session setCategory: category
  276. withOptions: options
  277. error: &error]);
  278. fixAudioRouteIfSetToReceiver();
  279. // Set the sample rate
  280. trySampleRate (targetSampleRate);
  281. updateSampleRateAndAudioInput();
  282. updateCurrentBufferSize();
  283. prepareFloatBuffers (actualBufferSize);
  284. isRunning = true;
  285. handleRouteChange ("Started AudioUnit");
  286. lastError = (audioUnit != 0 ? "" : "Couldn't open the device");
  287. setAudioSessionActive (true);
  288. return lastError;
  289. }
  290. void close() override
  291. {
  292. if (isRunning)
  293. {
  294. isRunning = false;
  295. if (audioUnit != 0)
  296. {
  297. AudioOutputUnitStart (audioUnit);
  298. AudioComponentInstanceDispose (audioUnit);
  299. audioUnit = 0;
  300. }
  301. setAudioSessionActive (false);
  302. }
  303. }
  304. bool isOpen() override { return isRunning; }
  305. int getCurrentBufferSizeSamples() override { return actualBufferSize; }
  306. double getCurrentSampleRate() override { return sampleRate; }
  307. int getCurrentBitDepth() override { return 16; }
  308. BigInteger getActiveOutputChannels() const override { return activeOutputChans; }
  309. BigInteger getActiveInputChannels() const override { return activeInputChans; }
  310. int getOutputLatencyInSamples() override { return roundToInt (getCurrentSampleRate() * [AVAudioSession sharedInstance].outputLatency); }
  311. int getInputLatencyInSamples() override { return roundToInt (getCurrentSampleRate() * [AVAudioSession sharedInstance].inputLatency); }
  312. void start (AudioIODeviceCallback* newCallback) override
  313. {
  314. if (isRunning && callback != newCallback)
  315. {
  316. if (newCallback != nullptr)
  317. newCallback->audioDeviceAboutToStart (this);
  318. const ScopedLock sl (callbackLock);
  319. callback = newCallback;
  320. }
  321. }
  322. void stop() override
  323. {
  324. if (isRunning)
  325. {
  326. AudioIODeviceCallback* lastCallback;
  327. {
  328. const ScopedLock sl (callbackLock);
  329. lastCallback = callback;
  330. callback = nullptr;
  331. }
  332. if (lastCallback != nullptr)
  333. lastCallback->audioDeviceStopped();
  334. }
  335. }
  336. bool isPlaying() override { return isRunning && callback != nullptr; }
  337. String getLastError() override { return lastError; }
  338. bool setAudioPreprocessingEnabled (bool enable) override
  339. {
  340. auto session = [AVAudioSession sharedInstance];
  341. NSString* mode = (enable ? AVAudioSessionModeMeasurement
  342. : AVAudioSessionModeDefault);
  343. JUCE_NSERROR_CHECK ([session setMode: mode
  344. error: &error]);
  345. return session.mode == mode;
  346. }
  347. void invokeAudioDeviceErrorCallback (const String& reason)
  348. {
  349. const ScopedLock sl (callbackLock);
  350. if (callback != nullptr)
  351. callback->audioDeviceError (reason);
  352. }
  353. void handleStatusChange (bool enabled, const char* reason)
  354. {
  355. const ScopedLock myScopedLock (callbackLock);
  356. JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason);
  357. isRunning = enabled;
  358. setAudioSessionActive (enabled);
  359. if (enabled)
  360. AudioOutputUnitStart (audioUnit);
  361. else
  362. AudioOutputUnitStop (audioUnit);
  363. if (! enabled)
  364. invokeAudioDeviceErrorCallback (reason);
  365. }
  366. void handleRouteChange (const char* reason)
  367. {
  368. const ScopedLock myScopedLock (callbackLock);
  369. JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason);
  370. fixAudioRouteIfSetToReceiver();
  371. if (isRunning)
  372. {
  373. invokeAudioDeviceErrorCallback (reason);
  374. updateSampleRateAndAudioInput();
  375. updateCurrentBufferSize();
  376. createAudioUnit();
  377. setAudioSessionActive (true);
  378. if (audioUnit != 0)
  379. {
  380. UInt32 formatSize = sizeof (format);
  381. AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize);
  382. AudioOutputUnitStart (audioUnit);
  383. }
  384. if (callback != nullptr)
  385. {
  386. callback->audioDeviceStopped();
  387. callback->audioDeviceAboutToStart (this);
  388. }
  389. }
  390. }
  391. private:
  392. //==============================================================================
  393. SharedResourcePointer<AudioSessionHolder> sessionHolder;
  394. CriticalSection callbackLock;
  395. NSTimeInterval sampleRate = 0;
  396. int numInputChannels = 2, numOutputChannels = 2;
  397. int preferredBufferSize = 0, actualBufferSize = 0;
  398. bool isRunning = false;
  399. String lastError;
  400. AudioStreamBasicDescription format;
  401. AudioUnit audioUnit {};
  402. bool audioInputIsAvailable = false;
  403. AudioIODeviceCallback* callback = nullptr;
  404. BigInteger activeOutputChans, activeInputChans;
  405. AudioSampleBuffer floatData;
  406. float* inputChannels[3];
  407. float* outputChannels[3];
  408. bool monoInputChannelNumber, monoOutputChannelNumber;
  409. void prepareFloatBuffers (int bufferSize)
  410. {
  411. if (numInputChannels + numOutputChannels > 0)
  412. {
  413. floatData.setSize (numInputChannels + numOutputChannels, bufferSize);
  414. zeromem (inputChannels, sizeof (inputChannels));
  415. zeromem (outputChannels, sizeof (outputChannels));
  416. for (int i = 0; i < numInputChannels; ++i)
  417. inputChannels[i] = floatData.getWritePointer (i);
  418. for (int i = 0; i < numOutputChannels; ++i)
  419. outputChannels[i] = floatData.getWritePointer (i + numInputChannels);
  420. }
  421. }
  422. //==============================================================================
  423. OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
  424. const UInt32 numFrames, AudioBufferList* data)
  425. {
  426. OSStatus err = noErr;
  427. if (audioInputIsAvailable && numInputChannels > 0)
  428. err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);
  429. const ScopedTryLock stl (callbackLock);
  430. if (stl.isLocked() && callback != nullptr)
  431. {
  432. if ((int) numFrames > floatData.getNumSamples())
  433. prepareFloatBuffers ((int) numFrames);
  434. if (audioInputIsAvailable && numInputChannels > 0)
  435. {
  436. short* shortData = (short*) data->mBuffers[0].mData;
  437. if (numInputChannels >= 2)
  438. {
  439. for (UInt32 i = 0; i < numFrames; ++i)
  440. {
  441. inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
  442. inputChannels[1][i] = *shortData++ * (1.0f / 32768.0f);
  443. }
  444. }
  445. else
  446. {
  447. if (monoInputChannelNumber > 0)
  448. ++shortData;
  449. for (UInt32 i = 0; i < numFrames; ++i)
  450. {
  451. inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
  452. ++shortData;
  453. }
  454. }
  455. }
  456. else
  457. {
  458. for (int i = numInputChannels; --i >= 0;)
  459. zeromem (inputChannels[i], sizeof (float) * numFrames);
  460. }
  461. callback->audioDeviceIOCallback ((const float**) inputChannels, numInputChannels,
  462. outputChannels, numOutputChannels, (int) numFrames);
  463. short* const shortData = (short*) data->mBuffers[0].mData;
  464. int n = 0;
  465. if (numOutputChannels >= 2)
  466. {
  467. for (UInt32 i = 0; i < numFrames; ++i)
  468. {
  469. shortData [n++] = (short) (outputChannels[0][i] * 32767.0f);
  470. shortData [n++] = (short) (outputChannels[1][i] * 32767.0f);
  471. }
  472. }
  473. else if (numOutputChannels == 1)
  474. {
  475. for (UInt32 i = 0; i < numFrames; ++i)
  476. {
  477. const short s = (short) (outputChannels[monoOutputChannelNumber][i] * 32767.0f);
  478. shortData [n++] = s;
  479. shortData [n++] = s;
  480. }
  481. }
  482. else
  483. {
  484. zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
  485. }
  486. }
  487. else
  488. {
  489. zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
  490. }
  491. return err;
  492. }
  493. void updateSampleRateAndAudioInput()
  494. {
  495. auto session = [AVAudioSession sharedInstance];
  496. sampleRate = session.sampleRate;
  497. audioInputIsAvailable = session.isInputAvailable;
  498. actualBufferSize = roundToInt (sampleRate * session.IOBufferDuration);
  499. JUCE_IOS_AUDIO_LOG ("AVAudioSession: sampleRate: " << sampleRate
  500. << " Hz, audioInputAvailable: " << (int) audioInputIsAvailable
  501. << ", buffer size: " << actualBufferSize);
  502. }
  503. void updateCurrentBufferSize()
  504. {
  505. NSTimeInterval bufferDuration = sampleRate > 0 ? (NSTimeInterval) ((preferredBufferSize + 1) / sampleRate) : 0.0;
  506. JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setPreferredIOBufferDuration: bufferDuration
  507. error: &error]);
  508. updateSampleRateAndAudioInput();
  509. }
  510. //==============================================================================
  511. static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
  512. UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data)
  513. {
  514. return static_cast<iOSAudioIODevice*> (client)->process (flags, time, numFrames, data);
  515. }
  516. //==============================================================================
  517. void resetFormat (const int numChannels) noexcept
  518. {
  519. zerostruct (format);
  520. format.mFormatID = kAudioFormatLinearPCM;
  521. format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
  522. format.mBitsPerChannel = 8 * sizeof (short);
  523. format.mChannelsPerFrame = (UInt32) numChannels;
  524. format.mFramesPerPacket = 1;
  525. format.mBytesPerFrame = format.mBytesPerPacket = (UInt32) numChannels * sizeof (short);
  526. }
  527. bool createAudioUnit()
  528. {
  529. if (audioUnit != 0)
  530. {
  531. AudioComponentInstanceDispose (audioUnit);
  532. audioUnit = 0;
  533. }
  534. resetFormat (2);
  535. AudioComponentDescription desc;
  536. desc.componentType = kAudioUnitType_Output;
  537. desc.componentSubType = kAudioUnitSubType_RemoteIO;
  538. desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  539. desc.componentFlags = 0;
  540. desc.componentFlagsMask = 0;
  541. AudioComponent comp = AudioComponentFindNext (0, &desc);
  542. AudioComponentInstanceNew (comp, &audioUnit);
  543. if (audioUnit == 0)
  544. return false;
  545. if (numInputChannels > 0)
  546. {
  547. const UInt32 one = 1;
  548. AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one));
  549. }
  550. {
  551. AudioChannelLayout layout;
  552. layout.mChannelBitmap = 0;
  553. layout.mNumberChannelDescriptions = 0;
  554. layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
  555. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof (layout));
  556. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, &layout, sizeof (layout));
  557. }
  558. {
  559. AURenderCallbackStruct inputProc;
  560. inputProc.inputProc = processStatic;
  561. inputProc.inputProcRefCon = this;
  562. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc));
  563. }
  564. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format));
  565. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format));
  566. UInt32 framesPerSlice;
  567. UInt32 dataSize = sizeof (framesPerSlice);
  568. AudioUnitInitialize (audioUnit);
  569. updateCurrentBufferSize();
  570. if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
  571. kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr
  572. && dataSize == sizeof (framesPerSlice) && static_cast<int> (framesPerSlice) != actualBufferSize)
  573. {
  574. prepareFloatBuffers (static_cast<int> (framesPerSlice));
  575. }
  576. AudioUnitAddPropertyListener (audioUnit, kAudioUnitProperty_StreamFormat, handleStreamFormatChangeCallback, this);
  577. return true;
  578. }
  579. // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it
  580. // to make it loud. Needed because by default when using an input + output, the output is kept quiet.
  581. static void fixAudioRouteIfSetToReceiver()
  582. {
  583. auto session = [AVAudioSession sharedInstance];
  584. auto route = session.currentRoute;
  585. for (AVAudioSessionPortDescription* port in route.inputs)
  586. {
  587. ignoreUnused (port);
  588. JUCE_IOS_AUDIO_LOG ("AVAudioSession: input: " << [port.description UTF8String]);
  589. }
  590. for (AVAudioSessionPortDescription* port in route.outputs)
  591. {
  592. JUCE_IOS_AUDIO_LOG ("AVAudioSession: output: " << [port.description UTF8String]);
  593. if ([port.portName isEqualToString: @"Receiver"])
  594. {
  595. JUCE_NSERROR_CHECK ([session overrideOutputAudioPort: AVAudioSessionPortOverrideSpeaker
  596. error: &error]);
  597. setAudioSessionActive (true);
  598. }
  599. }
  600. }
  601. void handleStreamFormatChange()
  602. {
  603. AudioStreamBasicDescription desc;
  604. zerostruct (desc);
  605. UInt32 dataSize = sizeof (desc);
  606. AudioUnitGetProperty(audioUnit,
  607. kAudioUnitProperty_StreamFormat,
  608. kAudioUnitScope_Output,
  609. 0,
  610. &desc,
  611. &dataSize);
  612. if (desc.mSampleRate != getCurrentSampleRate())
  613. {
  614. updateSampleRateAndAudioInput();
  615. const ScopedLock sl (callbackLock);
  616. if (callback != nullptr)
  617. {
  618. callback->audioDeviceStopped();
  619. callback->audioDeviceAboutToStart (this);
  620. }
  621. }
  622. }
  623. static void handleStreamFormatChangeCallback (void* device,
  624. AudioUnit,
  625. AudioUnitPropertyID,
  626. AudioUnitScope scope,
  627. AudioUnitElement element)
  628. {
  629. if (scope == kAudioUnitScope_Output && element == 0)
  630. static_cast<iOSAudioIODevice*> (device)->handleStreamFormatChange();
  631. }
  632. JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice)
  633. };
  634. //==============================================================================
  635. class iOSAudioIODeviceType : public AudioIODeviceType
  636. {
  637. public:
  638. iOSAudioIODeviceType() : AudioIODeviceType (iOSAudioDeviceName) {}
  639. void scanForDevices() {}
  640. StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray (iOSAudioDeviceName); }
  641. int getDefaultDeviceIndex (bool /*forInput*/) const { return 0; }
  642. int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const { return d != nullptr ? 0 : -1; }
  643. bool hasSeparateInputsAndOutputs() const { return false; }
  644. AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName)
  645. {
  646. if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
  647. return new iOSAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName : inputDeviceName);
  648. return nullptr;
  649. }
  650. private:
  651. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType)
  652. };
  653. //==============================================================================
  654. AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio()
  655. {
  656. return new iOSAudioIODeviceType();
  657. }
  658. //==============================================================================
  659. AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; }
  660. AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; }
  661. void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) const
  662. {
  663. for (auto device: activeDevices)
  664. device->handleStatusChange (enabled, reason);
  665. }
  666. void AudioSessionHolder::handleRouteChange (const char* reason) const
  667. {
  668. struct RouteChangeMessage : public CallbackMessage
  669. {
  670. RouteChangeMessage (Array<iOSAudioIODevice*> devs, const char* r)
  671. : devices (devs), changeReason (r)
  672. {
  673. }
  674. void messageCallback() override
  675. {
  676. for (auto device: devices)
  677. device->handleRouteChange (changeReason);
  678. }
  679. Array<iOSAudioIODevice*> devices;
  680. const char* changeReason;
  681. };
  682. (new RouteChangeMessage (activeDevices, reason))->post();
  683. }
  684. #undef JUCE_NSERROR_CHECK