The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

550 lines
20KB

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