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.

580 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. class iOSAudioIODevice : public AudioIODevice
  18. {
  19. public:
  20. iOSAudioIODevice (const String& deviceName)
  21. : AudioIODevice (deviceName, "Audio"),
  22. actualBufferSize (0),
  23. isRunning (false),
  24. audioUnit (0),
  25. callback (nullptr),
  26. floatData (1, 2)
  27. {
  28. getSessionHolder().activeDevices.add (this);
  29. numInputChannels = 2;
  30. numOutputChannels = 2;
  31. preferredBufferSize = 0;
  32. updateDeviceInfo();
  33. }
  34. ~iOSAudioIODevice()
  35. {
  36. getSessionHolder().activeDevices.removeFirstMatchingValue (this);
  37. close();
  38. }
  39. StringArray getOutputChannelNames()
  40. {
  41. StringArray s;
  42. s.add ("Left");
  43. s.add ("Right");
  44. return s;
  45. }
  46. StringArray getInputChannelNames()
  47. {
  48. StringArray s;
  49. if (audioInputIsAvailable)
  50. {
  51. s.add ("Left");
  52. s.add ("Right");
  53. }
  54. return s;
  55. }
  56. int getNumSampleRates() { return jmax (1, sampleRates.size()); }
  57. double getSampleRate (int index) { return sampleRates.size() > 0 ? sampleRates [index] : sampleRate; }
  58. int getNumBufferSizesAvailable() { return 6; }
  59. int getBufferSizeSamples (int index) { return 1 << (jlimit (0, 5, index) + 6); }
  60. int getDefaultBufferSize() { return 1024; }
  61. String open (const BigInteger& inputChannelsWanted,
  62. const BigInteger& outputChannelsWanted,
  63. double targetSampleRate, int bufferSize)
  64. {
  65. close();
  66. lastError = String::empty;
  67. preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;
  68. // xxx set up channel mapping
  69. activeOutputChans = outputChannelsWanted;
  70. activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
  71. numOutputChannels = activeOutputChans.countNumberOfSetBits();
  72. monoOutputChannelNumber = activeOutputChans.findNextSetBit (0);
  73. activeInputChans = inputChannelsWanted;
  74. activeInputChans.setRange (2, activeInputChans.getHighestBit(), false);
  75. numInputChannels = activeInputChans.countNumberOfSetBits();
  76. monoInputChannelNumber = activeInputChans.findNextSetBit (0);
  77. AudioSessionSetActive (true);
  78. if (numInputChannels > 0 && audioInputIsAvailable)
  79. {
  80. setSessionUInt32Property (kAudioSessionProperty_AudioCategory, kAudioSessionCategory_PlayAndRecord);
  81. setSessionUInt32Property (kAudioSessionProperty_OverrideCategoryEnableBluetoothInput, 1);
  82. }
  83. else
  84. {
  85. setSessionUInt32Property (kAudioSessionProperty_AudioCategory, kAudioSessionCategory_MediaPlayback);
  86. }
  87. AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this);
  88. fixAudioRouteIfSetToReceiver();
  89. updateDeviceInfo();
  90. setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, targetSampleRate);
  91. updateSampleRates();
  92. setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareIOBufferDuration, preferredBufferSize / sampleRate);
  93. updateCurrentBufferSize();
  94. prepareFloatBuffers (actualBufferSize);
  95. isRunning = true;
  96. routingChanged (nullptr); // creates and starts the AU
  97. lastError = audioUnit != 0 ? "" : "Couldn't open the device";
  98. return lastError;
  99. }
  100. void close()
  101. {
  102. if (isRunning)
  103. {
  104. isRunning = false;
  105. setSessionUInt32Property (kAudioSessionProperty_AudioCategory, kAudioSessionCategory_MediaPlayback);
  106. AudioSessionRemovePropertyListenerWithUserData (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this);
  107. AudioSessionSetActive (false);
  108. if (audioUnit != 0)
  109. {
  110. AudioComponentInstanceDispose (audioUnit);
  111. audioUnit = 0;
  112. }
  113. }
  114. }
  115. bool isOpen() { return isRunning; }
  116. int getCurrentBufferSizeSamples() { return actualBufferSize; }
  117. double getCurrentSampleRate() { return sampleRate; }
  118. int getCurrentBitDepth() { return 16; }
  119. BigInteger getActiveOutputChannels() const { return activeOutputChans; }
  120. BigInteger getActiveInputChannels() const { return activeInputChans; }
  121. int getOutputLatencyInSamples() { return 0; } //xxx
  122. int getInputLatencyInSamples() { return 0; } //xxx
  123. void start (AudioIODeviceCallback* newCallback)
  124. {
  125. if (isRunning && callback != newCallback)
  126. {
  127. if (newCallback != nullptr)
  128. newCallback->audioDeviceAboutToStart (this);
  129. const ScopedLock sl (callbackLock);
  130. callback = newCallback;
  131. }
  132. }
  133. void stop()
  134. {
  135. if (isRunning)
  136. {
  137. AudioIODeviceCallback* lastCallback;
  138. {
  139. const ScopedLock sl (callbackLock);
  140. lastCallback = callback;
  141. callback = nullptr;
  142. }
  143. if (lastCallback != nullptr)
  144. lastCallback->audioDeviceStopped();
  145. }
  146. }
  147. bool isPlaying() { return isRunning && callback != nullptr; }
  148. String getLastError() { return lastError; }
  149. private:
  150. //==================================================================================================
  151. CriticalSection callbackLock;
  152. Float64 sampleRate;
  153. Array<Float64> sampleRates;
  154. int numInputChannels, numOutputChannels;
  155. int preferredBufferSize, actualBufferSize;
  156. bool isRunning;
  157. String lastError;
  158. AudioStreamBasicDescription format;
  159. AudioUnit audioUnit;
  160. UInt32 audioInputIsAvailable;
  161. AudioIODeviceCallback* callback;
  162. BigInteger activeOutputChans, activeInputChans;
  163. AudioSampleBuffer floatData;
  164. float* inputChannels[3];
  165. float* outputChannels[3];
  166. bool monoInputChannelNumber, monoOutputChannelNumber;
  167. void prepareFloatBuffers (int bufferSize)
  168. {
  169. if (numInputChannels + numOutputChannels > 0)
  170. {
  171. floatData.setSize (numInputChannels + numOutputChannels, bufferSize);
  172. zeromem (inputChannels, sizeof (inputChannels));
  173. zeromem (outputChannels, sizeof (outputChannels));
  174. for (int i = 0; i < numInputChannels; ++i)
  175. inputChannels[i] = floatData.getSampleData (i);
  176. for (int i = 0; i < numOutputChannels; ++i)
  177. outputChannels[i] = floatData.getSampleData (i + numInputChannels);
  178. }
  179. }
  180. //==================================================================================================
  181. OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
  182. const UInt32 numFrames, AudioBufferList* data)
  183. {
  184. OSStatus err = noErr;
  185. if (audioInputIsAvailable && numInputChannels > 0)
  186. err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);
  187. const ScopedLock sl (callbackLock);
  188. if (callback != nullptr)
  189. {
  190. // This shouldn't ever get triggered, but please let me know if it does!
  191. jassert ((int) numFrames <= floatData.getNumSamples());
  192. if (audioInputIsAvailable && numInputChannels > 0)
  193. {
  194. short* shortData = (short*) data->mBuffers[0].mData;
  195. if (numInputChannels >= 2)
  196. {
  197. for (UInt32 i = 0; i < numFrames; ++i)
  198. {
  199. inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
  200. inputChannels[1][i] = *shortData++ * (1.0f / 32768.0f);
  201. }
  202. }
  203. else
  204. {
  205. if (monoInputChannelNumber > 0)
  206. ++shortData;
  207. for (UInt32 i = 0; i < numFrames; ++i)
  208. {
  209. inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
  210. ++shortData;
  211. }
  212. }
  213. }
  214. else
  215. {
  216. for (int i = numInputChannels; --i >= 0;)
  217. zeromem (inputChannels[i], sizeof (float) * numFrames);
  218. }
  219. callback->audioDeviceIOCallback ((const float**) inputChannels, numInputChannels,
  220. outputChannels, numOutputChannels, (int) numFrames);
  221. short* shortData = (short*) data->mBuffers[0].mData;
  222. int n = 0;
  223. if (numOutputChannels >= 2)
  224. {
  225. for (UInt32 i = 0; i < numFrames; ++i)
  226. {
  227. shortData [n++] = (short) (outputChannels[0][i] * 32767.0f);
  228. shortData [n++] = (short) (outputChannels[1][i] * 32767.0f);
  229. }
  230. }
  231. else if (numOutputChannels == 1)
  232. {
  233. for (UInt32 i = 0; i < numFrames; ++i)
  234. {
  235. const short s = (short) (outputChannels[monoOutputChannelNumber][i] * 32767.0f);
  236. shortData [n++] = s;
  237. shortData [n++] = s;
  238. }
  239. }
  240. else
  241. {
  242. zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
  243. }
  244. }
  245. else
  246. {
  247. zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
  248. }
  249. return err;
  250. }
  251. void updateDeviceInfo()
  252. {
  253. getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, sampleRate);
  254. getSessionProperty (kAudioSessionProperty_AudioInputAvailable, audioInputIsAvailable);
  255. }
  256. void updateSampleRates()
  257. {
  258. getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, sampleRate);
  259. sampleRates.clear();
  260. sampleRates.add (sampleRate);
  261. const int commonSampleRates[] = { 8000, 16000, 22050, 32000, 44100, 48000 };
  262. for (int i = 0; i < numElementsInArray (commonSampleRates); ++i)
  263. {
  264. Float64 rate = (Float64) commonSampleRates[i];
  265. if (rate != sampleRate)
  266. {
  267. setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, rate);
  268. Float64 actualSampleRate = 0.0;
  269. getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, actualSampleRate);
  270. if (actualSampleRate == rate)
  271. sampleRates.add (actualSampleRate);
  272. }
  273. }
  274. DefaultElementComparator<Float64> comparator;
  275. sampleRates.sort (comparator);
  276. setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, sampleRate);
  277. getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, sampleRate);
  278. }
  279. void updateCurrentBufferSize()
  280. {
  281. Float32 bufferDuration = sampleRate > 0 ? (Float32) (preferredBufferSize / sampleRate) : 0.0f;
  282. getSessionProperty (kAudioSessionProperty_CurrentHardwareIOBufferDuration, bufferDuration);
  283. actualBufferSize = (int) (sampleRate * bufferDuration + 0.5);
  284. }
  285. void routingChanged (const void* propertyValue)
  286. {
  287. if (! isRunning)
  288. return;
  289. if (propertyValue != nullptr)
  290. {
  291. CFDictionaryRef routeChangeDictionary = (CFDictionaryRef) propertyValue;
  292. CFNumberRef routeChangeReasonRef = (CFNumberRef) CFDictionaryGetValue (routeChangeDictionary,
  293. CFSTR (kAudioSession_AudioRouteChangeKey_Reason));
  294. SInt32 routeChangeReason;
  295. CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason);
  296. if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable)
  297. {
  298. const ScopedLock sl (callbackLock);
  299. if (callback != nullptr)
  300. callback->audioDeviceError ("Old device unavailable");
  301. }
  302. }
  303. updateDeviceInfo();
  304. createAudioUnit();
  305. AudioSessionSetActive (true);
  306. if (audioUnit != 0)
  307. {
  308. UInt32 formatSize = sizeof (format);
  309. AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize);
  310. updateCurrentBufferSize();
  311. AudioOutputUnitStart (audioUnit);
  312. }
  313. }
  314. //==================================================================================================
  315. struct AudioSessionHolder
  316. {
  317. AudioSessionHolder()
  318. {
  319. AudioSessionInitialize (0, 0, interruptionListenerCallback, this);
  320. }
  321. static void interruptionListenerCallback (void* client, UInt32 interruptionType)
  322. {
  323. const Array <iOSAudioIODevice*>& activeDevices = static_cast <AudioSessionHolder*> (client)->activeDevices;
  324. for (int i = activeDevices.size(); --i >= 0;)
  325. activeDevices.getUnchecked(i)->interruptionListener (interruptionType);
  326. }
  327. Array <iOSAudioIODevice*> activeDevices;
  328. };
  329. static AudioSessionHolder& getSessionHolder()
  330. {
  331. static AudioSessionHolder audioSessionHolder;
  332. return audioSessionHolder;
  333. }
  334. void interruptionListener (const UInt32 interruptionType)
  335. {
  336. if (interruptionType == kAudioSessionBeginInterruption)
  337. {
  338. isRunning = false;
  339. AudioOutputUnitStop (audioUnit);
  340. AudioSessionSetActive (false);
  341. const ScopedLock sl (callbackLock);
  342. if (callback != nullptr)
  343. callback->audioDeviceError ("iOS audio session interruption");
  344. }
  345. if (interruptionType == kAudioSessionEndInterruption)
  346. {
  347. isRunning = true;
  348. AudioSessionSetActive (true);
  349. AudioOutputUnitStart (audioUnit);
  350. }
  351. }
  352. //==================================================================================================
  353. static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
  354. UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data)
  355. {
  356. return static_cast <iOSAudioIODevice*> (client)->process (flags, time, numFrames, data);
  357. }
  358. static void routingChangedStatic (void* client, AudioSessionPropertyID, UInt32 /*inDataSize*/, const void* propertyValue)
  359. {
  360. static_cast <iOSAudioIODevice*> (client)->routingChanged (propertyValue);
  361. }
  362. //==================================================================================================
  363. void resetFormat (const int numChannels) noexcept
  364. {
  365. zerostruct (format);
  366. format.mFormatID = kAudioFormatLinearPCM;
  367. format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
  368. format.mBitsPerChannel = 8 * sizeof (short);
  369. format.mChannelsPerFrame = (UInt32) numChannels;
  370. format.mFramesPerPacket = 1;
  371. format.mBytesPerFrame = format.mBytesPerPacket = (UInt32) numChannels * sizeof (short);
  372. }
  373. bool createAudioUnit()
  374. {
  375. if (audioUnit != 0)
  376. {
  377. AudioComponentInstanceDispose (audioUnit);
  378. audioUnit = 0;
  379. }
  380. resetFormat (2);
  381. AudioComponentDescription desc;
  382. desc.componentType = kAudioUnitType_Output;
  383. desc.componentSubType = kAudioUnitSubType_RemoteIO;
  384. desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  385. desc.componentFlags = 0;
  386. desc.componentFlagsMask = 0;
  387. AudioComponent comp = AudioComponentFindNext (0, &desc);
  388. AudioComponentInstanceNew (comp, &audioUnit);
  389. if (audioUnit == 0)
  390. return false;
  391. if (numInputChannels > 0)
  392. {
  393. const UInt32 one = 1;
  394. AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one));
  395. }
  396. {
  397. AudioChannelLayout layout;
  398. layout.mChannelBitmap = 0;
  399. layout.mNumberChannelDescriptions = 0;
  400. layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
  401. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof (layout));
  402. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, &layout, sizeof (layout));
  403. }
  404. {
  405. AURenderCallbackStruct inputProc;
  406. inputProc.inputProc = processStatic;
  407. inputProc.inputProcRefCon = this;
  408. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc));
  409. }
  410. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format));
  411. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format));
  412. AudioUnitInitialize (audioUnit);
  413. return true;
  414. }
  415. // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it
  416. // to make it loud. Needed because by default when using an input + output, the output is kept quiet.
  417. static void fixAudioRouteIfSetToReceiver()
  418. {
  419. CFStringRef audioRoute = 0;
  420. if (getSessionProperty (kAudioSessionProperty_AudioRoute, audioRoute) == noErr)
  421. {
  422. NSString* route = (NSString*) audioRoute;
  423. //DBG ("audio route: " + nsStringToJuce (route));
  424. if ([route hasPrefix: @"Receiver"])
  425. setSessionUInt32Property (kAudioSessionProperty_OverrideAudioRoute, kAudioSessionOverrideAudioRoute_Speaker);
  426. CFRelease (audioRoute);
  427. }
  428. }
  429. template <typename Type>
  430. static OSStatus getSessionProperty (AudioSessionPropertyID propID, Type& result) noexcept
  431. {
  432. UInt32 valueSize = sizeof (result);
  433. return AudioSessionGetProperty (propID, &valueSize, &result);
  434. }
  435. static void setSessionUInt32Property (AudioSessionPropertyID propID, UInt32 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v); }
  436. static void setSessionFloat64Property (AudioSessionPropertyID propID, Float64 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v); }
  437. JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice)
  438. };
  439. //==============================================================================
  440. class iOSAudioIODeviceType : public AudioIODeviceType
  441. {
  442. public:
  443. iOSAudioIODeviceType() : AudioIODeviceType ("iOS Audio") {}
  444. void scanForDevices() {}
  445. StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray ("iOS Audio"); }
  446. int getDefaultDeviceIndex (bool /*forInput*/) const { return 0; }
  447. int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const { return d != nullptr ? 0 : -1; }
  448. bool hasSeparateInputsAndOutputs() const { return false; }
  449. AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName)
  450. {
  451. if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
  452. return new iOSAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
  453. : inputDeviceName);
  454. return nullptr;
  455. }
  456. private:
  457. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType)
  458. };
  459. //==============================================================================
  460. AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio()
  461. {
  462. return new iOSAudioIODeviceType();
  463. }