|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2016 - ROLI Ltd.
-
- Permission is granted to use this software under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license/
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
- TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
- FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
- OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
- USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
- OF THIS SOFTWARE.
-
- -----------------------------------------------------------------------------
-
- To release a closed-source product which uses other parts of JUCE not
- licensed under the ISC terms, commercial licenses are available: visit
- www.juce.com for more information.
-
- ==============================================================================
- */
-
- class iOSAudioIODevice;
-
- static const char* const iOSAudioDeviceName = "iOS Audio";
-
- //==============================================================================
- struct AudioSessionHolder
- {
- AudioSessionHolder();
- ~AudioSessionHolder();
-
- void handleStatusChange (bool enabled, const char* reason) const;
- void handleRouteChange (const char* reason) const;
-
- Array<iOSAudioIODevice*> activeDevices;
-
- id nativeSession;
- };
-
- static const char* getRoutingChangeReason (AVAudioSessionRouteChangeReason reason) noexcept
- {
- switch (reason)
- {
- case AVAudioSessionRouteChangeReasonNewDeviceAvailable: return "New device available";
- case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: return "Old device unavailable";
- case AVAudioSessionRouteChangeReasonCategoryChange: return "Category change";
- case AVAudioSessionRouteChangeReasonOverride: return "Override";
- case AVAudioSessionRouteChangeReasonWakeFromSleep: return "Wake from sleep";
- case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: return "No suitable route for category";
- case AVAudioSessionRouteChangeReasonRouteConfigurationChange: return "Route configuration change";
- case AVAudioSessionRouteChangeReasonUnknown:
- default: return "Unknown";
- }
- }
-
- bool getNotificationValueForKey (NSNotification* notification, NSString* key, NSUInteger& value) noexcept
- {
- if (notification != nil)
- {
- if (NSDictionary* userInfo = [notification userInfo])
- {
- if (NSNumber* number = [userInfo objectForKey: key])
- {
- value = [number unsignedIntegerValue];
- return true;
- }
- }
- }
-
- jassertfalse;
- return false;
- }
-
- } // juce namespace
-
- //==============================================================================
- @interface iOSAudioSessionNative : NSObject
- {
- @private
- juce::AudioSessionHolder* audioSessionHolder;
- };
-
- - (id) init: (juce::AudioSessionHolder*) holder;
- - (void) dealloc;
-
- - (void) audioSessionChangedInterruptionType: (NSNotification*) notification;
- - (void) handleMediaServicesReset;
- - (void) handleMediaServicesLost;
- - (void) handleRouteChange: (NSNotification*) notification;
- @end
-
- @implementation iOSAudioSessionNative
-
- - (id) init: (juce::AudioSessionHolder*) holder
- {
- self = [super init];
-
- if (self != nil)
- {
- audioSessionHolder = holder;
-
- auto session = [AVAudioSession sharedInstance];
- auto centre = [NSNotificationCenter defaultCenter];
-
- [centre addObserver: self
- selector: @selector (audioSessionChangedInterruptionType:)
- name: AVAudioSessionInterruptionNotification
- object: session];
-
- [centre addObserver: self
- selector: @selector (handleMediaServicesLost)
- name: AVAudioSessionMediaServicesWereLostNotification
- object: session];
-
- [centre addObserver: self
- selector: @selector (handleMediaServicesReset)
- name: AVAudioSessionMediaServicesWereResetNotification
- object: session];
-
- [centre addObserver: self
- selector: @selector (handleRouteChange:)
- name: AVAudioSessionRouteChangeNotification
- object: session];
- }
- else
- {
- jassertfalse;
- }
-
- return self;
- }
-
- - (void) dealloc
- {
- [[NSNotificationCenter defaultCenter] removeObserver: self];
- [super dealloc];
- }
-
- - (void) audioSessionChangedInterruptionType: (NSNotification*) notification
- {
- NSUInteger value;
-
- if (juce::getNotificationValueForKey (notification, AVAudioSessionInterruptionTypeKey, value))
- {
- switch ((AVAudioSessionInterruptionType) value)
- {
- case AVAudioSessionInterruptionTypeBegan:
- audioSessionHolder->handleStatusChange (false, "AVAudioSessionInterruptionTypeBegan");
- break;
-
- case AVAudioSessionInterruptionTypeEnded:
- audioSessionHolder->handleStatusChange (true, "AVAudioSessionInterruptionTypeEnded");
- break;
-
- // No default so the code doesn't compile if this enum is extended.
- }
- }
- }
-
- - (void) handleMediaServicesReset
- {
- audioSessionHolder->handleStatusChange (true, "AVAudioSessionMediaServicesWereResetNotification");
- }
-
- - (void) handleMediaServicesLost
- {
- audioSessionHolder->handleStatusChange (false, "AVAudioSessionMediaServicesWereLostNotification");
- }
-
- - (void) handleRouteChange: (NSNotification*) notification
- {
- NSUInteger value;
-
- if (juce::getNotificationValueForKey (notification, AVAudioSessionRouteChangeReasonKey, value))
- audioSessionHolder->handleRouteChange (juce::getRoutingChangeReason ((AVAudioSessionRouteChangeReason) value));
- }
-
- @end
-
- //==============================================================================
- namespace juce {
-
- #ifndef JUCE_IOS_AUDIO_LOGGING
- #define JUCE_IOS_AUDIO_LOGGING 0
- #endif
-
- #if JUCE_IOS_AUDIO_LOGGING
- #define JUCE_IOS_AUDIO_LOG(x) DBG(x)
- #else
- #define JUCE_IOS_AUDIO_LOG(x)
- #endif
-
- static void logNSError (NSError* e)
- {
- if (e != nil)
- {
- JUCE_IOS_AUDIO_LOG ("iOS Audio error: " << [e.localizedDescription UTF8String]);
- jassertfalse;
- }
- }
-
- #define JUCE_NSERROR_CHECK(X) { NSError* error = nil; X; logNSError (error); }
-
- #if JUCE_MODULE_AVAILABLE_juce_graphics
- #include <juce_graphics/native/juce_mac_CoreGraphicsHelpers.h>
- #endif
-
- //==============================================================================
- class iOSAudioIODevice::Pimpl : public AudioPlayHead
- {
- public:
- Pimpl (iOSAudioIODevice& ioDevice)
- : owner (ioDevice)
- {
- sessionHolder->activeDevices.add (&owner);
- }
-
- ~Pimpl()
- {
- sessionHolder->activeDevices.removeFirstMatchingValue (&owner);
- owner.close();
- }
-
- static void setAudioSessionActive (bool enabled)
- {
- JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setActive: enabled
- error: &error]);
- }
-
- static double trySampleRate (double rate)
- {
- auto session = [AVAudioSession sharedInstance];
- JUCE_NSERROR_CHECK ([session setPreferredSampleRate: rate
- error: &error]);
- return session.sampleRate;
- }
-
- Array<double> getAvailableSampleRates()
- {
- Array<double> rates;
-
- // Important: the supported audio sample rates change on the iPhone 6S
- // depending on whether the headphones are plugged in or not!
- setAudioSessionActive (true);
-
- AudioUnitRemovePropertyListenerWithUserData (audioUnit,
- kAudioUnitProperty_StreamFormat,
- handleStreamFormatChangeCallback,
- this);
-
- const double lowestRate = trySampleRate (4000);
- const double highestRate = trySampleRate (192000);
-
- for (double rate = lowestRate; rate <= highestRate; rate += 1000)
- {
- const double supportedRate = trySampleRate (rate);
-
- rates.addIfNotAlreadyThere (supportedRate);
- rate = jmax (rate, supportedRate);
- }
-
- trySampleRate (owner.getCurrentSampleRate());
-
- AudioUnitAddPropertyListener (audioUnit,
- kAudioUnitProperty_StreamFormat,
- handleStreamFormatChangeCallback,
- this);
-
- for (auto r : rates)
- {
- ignoreUnused (r);
- JUCE_IOS_AUDIO_LOG ("available rate = " + String (r, 0) + "Hz");
- }
-
- return rates;
- }
-
- Array<int> getAvailableBufferSizes()
- {
- Array<int> r;
-
- for (int i = 6; i < 13; ++i)
- r.add (1 << i);
-
- return r;
- }
-
- String open (const BigInteger& inputChannelsWanted,
- const BigInteger& outputChannelsWanted,
- double targetSampleRate, int bufferSize)
- {
- close();
-
- owner.lastError.clear();
- owner.preferredBufferSize = bufferSize <= 0 ? owner.getDefaultBufferSize() : bufferSize;
-
- // xxx set up channel mapping
-
- owner.activeOutputChans = outputChannelsWanted;
- owner.activeOutputChans.setRange (2, owner.activeOutputChans.getHighestBit(), false);
- owner.numOutputChannels = owner.activeOutputChans.countNumberOfSetBits();
- monoOutputChannelNumber = owner.activeOutputChans.findNextSetBit (0);
-
- owner.activeInputChans = inputChannelsWanted;
- owner.activeInputChans.setRange (2, owner.activeInputChans.getHighestBit(), false);
- owner.numInputChannels = owner.activeInputChans.countNumberOfSetBits();
- monoInputChannelNumber = owner.activeInputChans.findNextSetBit (0);
-
- setAudioSessionActive (true);
-
- // Set the session category & options:
- auto session = [AVAudioSession sharedInstance];
-
- const bool useInputs = (owner.numInputChannels > 0 && owner.audioInputIsAvailable);
-
- NSString* category = (useInputs ? AVAudioSessionCategoryPlayAndRecord : AVAudioSessionCategoryPlayback);
-
- NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers; // Alternatively AVAudioSessionCategoryOptionDuckOthers
- if (useInputs) // These options are only valid for category = PlayAndRecord
- options |= (AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth);
-
- JUCE_NSERROR_CHECK ([session setCategory: category
- withOptions: options
- error: &error]);
-
- fixAudioRouteIfSetToReceiver();
-
- // Set the sample rate
- trySampleRate (targetSampleRate);
- owner.updateSampleRateAndAudioInput();
- updateCurrentBufferSize();
-
- prepareFloatBuffers (owner.actualBufferSize);
-
- owner.isRunning = true;
- handleRouteChange ("Started AudioUnit");
-
- owner.lastError = (audioUnit != 0 ? "" : "Couldn't open the device");
-
- setAudioSessionActive (true);
-
- return owner.lastError;
- }
-
- void close()
- {
- if (owner.isRunning)
- {
- owner.isRunning = false;
-
- if (audioUnit != 0)
- {
- AudioOutputUnitStart (audioUnit);
- AudioComponentInstanceDispose (audioUnit);
- audioUnit = 0;
- }
-
- setAudioSessionActive (false);
- }
- }
-
- void start (AudioIODeviceCallback* newCallback)
- {
- if (owner.isRunning && owner.callback != newCallback)
- {
- if (newCallback != nullptr)
- newCallback->audioDeviceAboutToStart (&owner);
-
- const ScopedLock sl (callbackLock);
- owner.callback = newCallback;
- }
- }
-
- void stop()
- {
- if (owner.isRunning)
- {
- AudioIODeviceCallback* lastCallback;
-
- {
- const ScopedLock sl (callbackLock);
- lastCallback = owner.callback;
- owner.callback = nullptr;
- }
-
- if (lastCallback != nullptr)
- lastCallback->audioDeviceStopped();
- }
- }
-
- bool setAudioPreprocessingEnabled (bool enable)
- {
- auto session = [AVAudioSession sharedInstance];
-
- NSString* mode = (enable ? AVAudioSessionModeMeasurement
- : AVAudioSessionModeDefault);
-
- JUCE_NSERROR_CHECK ([session setMode: mode
- error: &error]);
-
- return session.mode == mode;
- }
-
- //==============================================================================
- bool canControlTransport() override { return owner.interAppAudioConnected; }
-
- void transportPlay (bool shouldSartPlaying) override
- {
- if (! canControlTransport())
- return;
-
- HostCallbackInfo callbackInfo;
- fillHostCallbackInfo (callbackInfo);
-
- Boolean hostIsPlaying = NO;
- OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
- &hostIsPlaying,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL);
- jassert (err == noErr);
-
- if (hostIsPlaying != shouldSartPlaying)
- handleAudioTransportEvent (kAudioUnitRemoteControlEvent_TogglePlayPause);
- }
-
- void transportRecord (bool shouldStartRecording) override
- {
- if (! canControlTransport())
- return;
-
- HostCallbackInfo callbackInfo;
- fillHostCallbackInfo (callbackInfo);
-
- Boolean hostIsRecording = NO;
- OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
- NULL,
- &hostIsRecording,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL);
- jassert (err == noErr);
-
- if (hostIsRecording != shouldStartRecording)
- handleAudioTransportEvent (kAudioUnitRemoteControlEvent_ToggleRecord);
- }
-
- void transportRewind() override
- {
- if (canControlTransport())
- handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind);
- }
-
- bool getCurrentPosition (CurrentPositionInfo& result) override
- {
- if (! canControlTransport())
- return false;
-
- zerostruct (result);
-
- HostCallbackInfo callbackInfo;
- fillHostCallbackInfo (callbackInfo);
-
- if (callbackInfo.hostUserData == nullptr)
- return false;
-
- Boolean hostIsPlaying = NO;
- Boolean hostIsRecording = NO;
- Float64 hostCurrentSampleInTimeLine = 0;
- Boolean hostIsCycling = NO;
- Float64 hostCycleStartBeat = 0;
- Float64 hostCycleEndBeat = 0;
- OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
- &hostIsPlaying,
- &hostIsRecording,
- NULL,
- &hostCurrentSampleInTimeLine,
- &hostIsCycling,
- &hostCycleStartBeat,
- &hostCycleEndBeat);
- if (err == kAUGraphErr_CannotDoInCurrentContext)
- return false;
-
- jassert (err == noErr);
-
- result.timeInSamples = (int64) hostCurrentSampleInTimeLine;
- result.isPlaying = hostIsPlaying;
- result.isRecording = hostIsRecording;
- result.isLooping = hostIsCycling;
- result.ppqLoopStart = hostCycleStartBeat;
- result.ppqLoopEnd = hostCycleEndBeat;
-
- result.timeInSeconds = result.timeInSamples / owner.sampleRate;
-
- Float64 hostBeat = 0;
- Float64 hostTempo = 0;
- err = callbackInfo.beatAndTempoProc (callbackInfo.hostUserData,
- &hostBeat,
- &hostTempo);
- jassert (err == noErr);
-
- result.ppqPosition = hostBeat;
- result.bpm = hostTempo;
-
- Float32 hostTimeSigNumerator = 0;
- UInt32 hostTimeSigDenominator = 0;
- Float64 hostCurrentMeasureDownBeat = 0;
- err = callbackInfo.musicalTimeLocationProc (callbackInfo.hostUserData,
- NULL,
- &hostTimeSigNumerator,
- &hostTimeSigDenominator,
- &hostCurrentMeasureDownBeat);
- jassert (err == noErr);
-
- result.ppqPositionOfLastBarStart = hostCurrentMeasureDownBeat;
- result.timeSigNumerator = (int) hostTimeSigNumerator;
- result.timeSigDenominator = (int) hostTimeSigDenominator;
-
- result.frameRate = AudioPlayHead::fpsUnknown;
-
- return true;
- }
-
- //==============================================================================
- #if JUCE_MODULE_AVAILABLE_juce_gui_basics
- Image getIcon (int size)
- {
- if (owner.interAppAudioConnected)
- {
- UIImage* hostUIImage = AudioOutputUnitGetHostIcon (audioUnit, size);
- if (hostUIImage != nullptr)
- return juce_createImageFromUIImage (hostUIImage);
- }
- return Image();
- }
- #endif
-
- void switchApplication()
- {
- if (! owner.interAppAudioConnected)
- return;
-
- CFURLRef hostUrl;
- UInt32 dataSize = sizeof (hostUrl);
- OSStatus err = AudioUnitGetProperty(audioUnit,
- kAudioUnitProperty_PeerURL,
- kAudioUnitScope_Global,
- 0,
- &hostUrl,
- &dataSize);
- if (err == noErr)
- [[UIApplication sharedApplication] openURL:(NSURL*)hostUrl];
- }
-
- //==============================================================================
- void invokeAudioDeviceErrorCallback (const String& reason)
- {
- const ScopedLock sl (callbackLock);
-
- if (owner.callback != nullptr)
- owner.callback->audioDeviceError (reason);
- }
-
- void handleStatusChange (bool enabled, const char* reason)
- {
- const ScopedLock myScopedLock (callbackLock);
-
- JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason);
-
- owner.isRunning = enabled;
- setAudioSessionActive (enabled);
-
- if (enabled)
- AudioOutputUnitStart (audioUnit);
- else
- AudioOutputUnitStop (audioUnit);
-
- if (! enabled)
- invokeAudioDeviceErrorCallback (reason);
- }
-
- void handleRouteChange (const char* reason)
- {
- const ScopedLock myScopedLock (callbackLock);
-
- JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason);
-
- fixAudioRouteIfSetToReceiver();
-
- if (owner.isRunning)
- {
- invokeAudioDeviceErrorCallback (reason);
- owner.updateSampleRateAndAudioInput();
- updateCurrentBufferSize();
- createAudioUnit();
-
- setAudioSessionActive (true);
-
- if (audioUnit != 0)
- {
- UInt32 formatSize = sizeof (format);
- AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize);
- AudioOutputUnitStart (audioUnit);
- }
-
- if (owner.callback != nullptr)
- {
- owner.callback->audioDeviceStopped();
- owner.callback->audioDeviceAboutToStart (&owner);
- }
- }
- }
-
- void handleAudioUnitPropertyChange (AudioUnit,
- AudioUnitPropertyID propertyID,
- AudioUnitScope,
- AudioUnitElement)
- {
- const ScopedLock myScopedLock (callbackLock);
-
- switch (propertyID)
- {
- case kAudioUnitProperty_IsInterAppConnected: return handleInterAppAudioConnectionChange();
- default: return;
- }
- }
-
- void handleInterAppAudioConnectionChange()
- {
- UInt32 connected;
- UInt32 dataSize = sizeof (connected);
- OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_IsInterAppConnected,
- kAudioUnitScope_Global, 0, &connected, &dataSize);
- jassert (err == noErr);
-
- JUCE_IOS_AUDIO_LOG ("handleInterAppAudioConnectionChange: " << connected ? "connected"
- : "disconnected");
-
- if (connected != owner.interAppAudioConnected)
- {
- const ScopedLock myScopedLock (callbackLock);
-
- owner.interAppAudioConnected = connected;
-
- UIApplicationState appstate = [UIApplication sharedApplication].applicationState;
- bool inForeground = (appstate != UIApplicationStateBackground);
-
- if (owner.interAppAudioConnected || inForeground)
- {
- setAudioSessionActive (true);
- AudioOutputUnitStart (audioUnit);
-
- if (owner.callback != nullptr)
- owner.callback->audioDeviceAboutToStart (&owner);
- }
- else if (! inForeground)
- {
- AudioOutputUnitStop (audioUnit);
- setAudioSessionActive (false);
- }
- }
- }
-
- private:
- //==============================================================================
- iOSAudioIODevice& owner;
- SharedResourcePointer<AudioSessionHolder> sessionHolder;
- CriticalSection callbackLock;
-
- AudioStreamBasicDescription format;
- AudioUnit audioUnit {};
-
- AudioSampleBuffer floatData;
- float* inputChannels[3];
- float* outputChannels[3];
- bool monoInputChannelNumber, monoOutputChannelNumber;
-
- void prepareFloatBuffers (int bufferSize)
- {
- if (owner.numInputChannels + owner.numOutputChannels > 0)
- {
- floatData.setSize (owner.numInputChannels + owner.numOutputChannels, bufferSize);
- zeromem (inputChannels, sizeof (inputChannels));
- zeromem (outputChannels, sizeof (outputChannels));
-
- for (int i = 0; i < owner.numInputChannels; ++i)
- inputChannels[i] = floatData.getWritePointer (i);
-
- for (int i = 0; i < owner.numOutputChannels; ++i)
- outputChannels[i] = floatData.getWritePointer (i + owner.numInputChannels);
- }
- }
-
- //==============================================================================
- OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
- const UInt32 numFrames, AudioBufferList* data)
- {
- OSStatus err = noErr;
-
- if (owner.audioInputIsAvailable && owner.numInputChannels > 0)
- err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);
-
- const ScopedTryLock stl (callbackLock);
-
- if (stl.isLocked() && owner.callback != nullptr)
- {
- if ((int) numFrames > floatData.getNumSamples())
- prepareFloatBuffers ((int) numFrames);
-
- if (owner.audioInputIsAvailable && owner.numInputChannels > 0)
- {
- short* shortData = (short*) data->mBuffers[0].mData;
-
- if (owner.numInputChannels >= 2)
- {
- for (UInt32 i = 0; i < numFrames; ++i)
- {
- inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
- inputChannels[1][i] = *shortData++ * (1.0f / 32768.0f);
- }
- }
- else
- {
- if (monoInputChannelNumber > 0)
- ++shortData;
-
- for (UInt32 i = 0; i < numFrames; ++i)
- {
- inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
- ++shortData;
- }
- }
- }
- else
- {
- for (int i = owner.numInputChannels; --i >= 0;)
- zeromem (inputChannels[i], sizeof (float) * numFrames);
- }
-
- owner.callback->audioDeviceIOCallback ((const float**) inputChannels, owner.numInputChannels,
- outputChannels, owner.numOutputChannels, (int) numFrames);
-
- short* const shortData = (short*) data->mBuffers[0].mData;
- int n = 0;
-
- if (owner.numOutputChannels >= 2)
- {
- for (UInt32 i = 0; i < numFrames; ++i)
- {
- shortData [n++] = (short) (outputChannels[0][i] * 32767.0f);
- shortData [n++] = (short) (outputChannels[1][i] * 32767.0f);
- }
- }
- else if (owner.numOutputChannels == 1)
- {
- for (UInt32 i = 0; i < numFrames; ++i)
- {
- const short s = (short) (outputChannels[monoOutputChannelNumber][i] * 32767.0f);
- shortData [n++] = s;
- shortData [n++] = s;
- }
- }
- else
- {
- zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
- }
- }
- else
- {
- zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
- }
-
- return err;
- }
-
- void updateCurrentBufferSize()
- {
- NSTimeInterval bufferDuration = owner.sampleRate > 0 ? (NSTimeInterval) ((owner.preferredBufferSize + 1) / owner.sampleRate) : 0.0;
-
- JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setPreferredIOBufferDuration: bufferDuration
- error: &error]);
- owner.updateSampleRateAndAudioInput();
- }
-
- //==============================================================================
- static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
- UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data)
- {
-
- return static_cast<Pimpl*> (client)->process (flags, time, numFrames, data);
- }
-
- //==============================================================================
- void resetFormat (const int numChannels) noexcept
- {
- zerostruct (format);
- format.mFormatID = kAudioFormatLinearPCM;
- format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
- format.mBitsPerChannel = 8 * sizeof (short);
- format.mChannelsPerFrame = (UInt32) numChannels;
- format.mFramesPerPacket = 1;
- format.mBytesPerFrame = format.mBytesPerPacket = (UInt32) numChannels * sizeof (short);
- }
-
- bool createAudioUnit()
- {
- if (audioUnit != 0)
- {
- AudioComponentInstanceDispose (audioUnit);
- audioUnit = 0;
- }
-
- resetFormat (2);
-
- AudioComponentDescription desc;
- desc.componentType = kAudioUnitType_Output;
- desc.componentSubType = kAudioUnitSubType_RemoteIO;
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
-
- AudioComponent comp = AudioComponentFindNext (0, &desc);
- AudioComponentInstanceNew (comp, &audioUnit);
-
- if (audioUnit == 0)
- return false;
-
- #if JucePlugin_Enable_IAA
- AudioComponentDescription appDesc;
- appDesc.componentType = JucePlugin_IAAType;
- appDesc.componentSubType = JucePlugin_IAASubType;
- appDesc.componentManufacturer = JucePlugin_ManufacturerCode;
- appDesc.componentFlags = 0;
- appDesc.componentFlagsMask = 0;
- OSStatus err = AudioOutputUnitPublish (&appDesc,
- CFSTR(JucePlugin_IAAName),
- JucePlugin_VersionCode,
- audioUnit);
-
- // This assert will be hit if the Inter-App Audio entitlement has not
- // been enabled, or the description being published with
- // AudioOutputUnitPublish is different from any in the AudioComponents
- // array in this application's .plist file.
- jassert (err == noErr);
-
- err = AudioUnitAddPropertyListener(audioUnit,
- kAudioUnitProperty_IsInterAppConnected,
- audioUnitPropertyChangeDispatcher,
- this);
- jassert (err == noErr);
- #endif
-
- if (owner.numInputChannels > 0)
- {
- const UInt32 one = 1;
- AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one));
- }
-
- {
- AudioChannelLayout layout;
- layout.mChannelBitmap = 0;
- layout.mNumberChannelDescriptions = 0;
- layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
- AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof (layout));
- AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, &layout, sizeof (layout));
- }
-
- {
- AURenderCallbackStruct inputProc;
- inputProc.inputProc = processStatic;
- inputProc.inputProcRefCon = this;
- AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc));
- }
-
- AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format));
- AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format));
-
- UInt32 framesPerSlice;
- UInt32 dataSize = sizeof (framesPerSlice);
-
- AudioUnitInitialize (audioUnit);
-
- updateCurrentBufferSize();
-
- if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
- kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr
- && dataSize == sizeof (framesPerSlice) && static_cast<int> (framesPerSlice) != owner.actualBufferSize)
- {
- prepareFloatBuffers (static_cast<int> (framesPerSlice));
- }
-
- AudioUnitAddPropertyListener (audioUnit, kAudioUnitProperty_StreamFormat, handleStreamFormatChangeCallback, this);
-
- return true;
- }
-
- void fillHostCallbackInfo (HostCallbackInfo& callbackInfo)
- {
- zerostruct (callbackInfo);
- UInt32 dataSize = sizeof (HostCallbackInfo);
- OSStatus err = AudioUnitGetProperty (audioUnit,
- kAudioUnitProperty_HostCallbacks,
- kAudioUnitScope_Global,
- 0,
- &callbackInfo,
- &dataSize);
- jassert (err == noErr);
- }
-
- void handleAudioTransportEvent (AudioUnitRemoteControlEvent event)
- {
- OSStatus err = AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_RemoteControlToHost,
- kAudioUnitScope_Global, 0, &event, sizeof (event));
- jassert (err == noErr);
- }
-
- // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it
- // to make it loud. Needed because by default when using an input + output, the output is kept quiet.
- static void fixAudioRouteIfSetToReceiver()
- {
- auto session = [AVAudioSession sharedInstance];
- auto route = session.currentRoute;
-
- for (AVAudioSessionPortDescription* port in route.inputs)
- {
- ignoreUnused (port);
- JUCE_IOS_AUDIO_LOG ("AVAudioSession: input: " << [port.description UTF8String]);
- }
-
- for (AVAudioSessionPortDescription* port in route.outputs)
- {
- JUCE_IOS_AUDIO_LOG ("AVAudioSession: output: " << [port.description UTF8String]);
-
- if ([port.portName isEqualToString: @"Receiver"])
- {
- JUCE_NSERROR_CHECK ([session overrideOutputAudioPort: AVAudioSessionPortOverrideSpeaker
- error: &error]);
- setAudioSessionActive (true);
- }
- }
- }
-
- void handleStreamFormatChange()
- {
- AudioStreamBasicDescription desc;
- zerostruct (desc);
- UInt32 dataSize = sizeof (desc);
- AudioUnitGetProperty(audioUnit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- 0,
- &desc,
- &dataSize);
- if (desc.mSampleRate != owner.getCurrentSampleRate())
- {
- owner.updateSampleRateAndAudioInput();
- const ScopedLock sl (callbackLock);
- if (owner.callback != nullptr)
- {
- owner.callback->audioDeviceStopped();
- owner.callback->audioDeviceAboutToStart (&owner);
- }
- }
- }
-
- static void handleStreamFormatChangeCallback (void* device,
- AudioUnit,
- AudioUnitPropertyID,
- AudioUnitScope scope,
- AudioUnitElement element)
- {
- if (scope == kAudioUnitScope_Output && element == 0)
- static_cast<Pimpl*> (device)->handleStreamFormatChange();
- }
-
- static void audioUnitPropertyChangeDispatcher (void* data, AudioUnit unit, AudioUnitPropertyID propertyID,
- AudioUnitScope scope, AudioUnitElement element)
- {
- Pimpl* device = (Pimpl*)data;
- device->handleAudioUnitPropertyChange (unit, propertyID, scope, element);
- }
-
- void handleMidiMessage (MidiMessage msg)
- {
- if (owner.messageCollector != nullptr)
- owner.messageCollector->addMessageToQueue (msg);
- }
-
- static void midiEventCallback (void *client, UInt32 status, UInt32 data1, UInt32 data2, UInt32)
- {
- return static_cast<Pimpl*> (client)->handleMidiMessage (MidiMessage ((int) status,
- (int) data1,
- (int) data2,
- Time::getMillisecondCounter() / 1000.0));
- }
-
- JUCE_DECLARE_NON_COPYABLE (Pimpl)
- };
-
-
- //==============================================================================
- iOSAudioIODevice::iOSAudioIODevice (const String& deviceName)
- : AudioIODevice (deviceName, iOSAudioDeviceName),
- #if TARGET_IPHONE_SIMULATOR
- defaultBufferSize (512),
- #else
- defaultBufferSize (256),
- #endif
- sampleRate (0), numInputChannels (2), numOutputChannels (2),
- preferredBufferSize (0), actualBufferSize (0), isRunning (false),
- audioInputIsAvailable (false), interAppAudioConnected (false),
- callback (nullptr), messageCollector (nullptr),
- pimpl (new Pimpl (*this))
- {
- updateSampleRateAndAudioInput();
- }
-
- //==============================================================================
- int iOSAudioIODevice::getOutputLatencyInSamples() { return roundToInt (sampleRate * [AVAudioSession sharedInstance].outputLatency); }
- int iOSAudioIODevice::getInputLatencyInSamples() { return roundToInt (sampleRate * [AVAudioSession sharedInstance].inputLatency); }
-
- //==============================================================================
- AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const { return pimpl; }
- void iOSAudioIODevice::close() { pimpl->close(); }
- void iOSAudioIODevice::start (AudioIODeviceCallback* callbackToUse) { pimpl->start (callbackToUse); }
- void iOSAudioIODevice::stop() { pimpl->stop(); }
- Array<double> iOSAudioIODevice::getAvailableSampleRates() { return pimpl->getAvailableSampleRates(); }
- Array<int> iOSAudioIODevice::getAvailableBufferSizes() { return pimpl->getAvailableBufferSizes(); }
- bool iOSAudioIODevice::setAudioPreprocessingEnabled (bool enabled) { return pimpl->setAudioPreprocessingEnabled (enabled); }
- void iOSAudioIODevice::switchApplication() { return pimpl->switchApplication(); }
-
- //==============================================================================
- void iOSAudioIODevice::handleStatusChange (bool enabled, const char* reason) { pimpl->handleStatusChange (enabled, reason); }
- void iOSAudioIODevice::handleRouteChange (const char* reason) { pimpl->handleRouteChange (reason); }
-
- #if JUCE_MODULE_AVAILABLE_juce_gui_basics
- Image iOSAudioIODevice::getIcon (int size) { return pimpl->getIcon (size); }
- #endif
-
- //==============================================================================
- String iOSAudioIODevice::open (const BigInteger& inChans, const BigInteger& outChans, double requestedSampleRate, int requestedBufferSize)
- {
- return pimpl->open (inChans, outChans, requestedSampleRate, requestedBufferSize);
- }
-
- void iOSAudioIODevice::updateSampleRateAndAudioInput()
- {
- auto session = [AVAudioSession sharedInstance];
- sampleRate = session.sampleRate;
- audioInputIsAvailable = session.isInputAvailable;
- actualBufferSize = roundToInt (sampleRate * session.IOBufferDuration);
-
- JUCE_IOS_AUDIO_LOG ("AVAudioSession: sampleRate: " << sampleRate
- << " Hz, audioInputAvailable: " << (int) audioInputIsAvailable
- << ", buffer size: " << actualBufferSize);
- }
-
- //==============================================================================
- class iOSAudioIODeviceType : public AudioIODeviceType
- {
- public:
- iOSAudioIODeviceType() : AudioIODeviceType (iOSAudioDeviceName) {}
-
- void scanForDevices() {}
- StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray (iOSAudioDeviceName); }
- int getDefaultDeviceIndex (bool /*forInput*/) const { return 0; }
- int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const { return d != nullptr ? 0 : -1; }
- bool hasSeparateInputsAndOutputs() const { return false; }
-
- AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName)
- {
- if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
- return new iOSAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName : inputDeviceName);
-
- return nullptr;
- }
-
- private:
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType)
- };
-
- //==============================================================================
- AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio()
- {
- return new iOSAudioIODeviceType();
- }
-
- //==============================================================================
- AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; }
- AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; }
-
- void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) const
- {
- for (auto device: activeDevices)
- device->handleStatusChange (enabled, reason);
- }
-
- void AudioSessionHolder::handleRouteChange (const char* reason) const
- {
- struct RouteChangeMessage : public CallbackMessage
- {
- RouteChangeMessage (Array<iOSAudioIODevice*> devs, const char* r)
- : devices (devs), changeReason (r)
- {
- }
-
- void messageCallback() override
- {
- for (auto device: devices)
- device->handleRouteChange (changeReason);
- }
-
- Array<iOSAudioIODevice*> devices;
- const char* changeReason;
- };
-
- (new RouteChangeMessage (activeDevices, reason))->post();
- }
-
- #undef JUCE_NSERROR_CHECK
|