iOS: Sample rate patch proposal

The iOSAudioIODevice never sets the sample rate given as a parameter in the open function and only supports one sample rate, the one that has been set on the system when the app launched. This causes nondeterministic behavior as the app might run at different sample rate at different times.

I propose this patch that fixes the issue with sample rate not being correctly set in open and also providing more sample rates to choose from.

In open I've added the following code which sets the sample rate given as a parameter in open and updates the list of available sample rates.

...

        fixAudioRouteIfSetToReceiver();
        updateDeviceInfo();
        
        AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareSampleRate, sizeof(sampleRate), &sampleRate);
        updateSampleRates();

...

The updateSampleRate function looks like this. The code assumes that a Array<Float64> sampleRates has been added to iOSAudioIODevice

    void updateSampleRates()
    {
        static const double commonSampleRates[6] = { 8000.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0};
        
        UInt32 size = sizeof (sampleRate);
        AudioSessionGetProperty (kAudioSessionProperty_CurrentHardwareSampleRate, &size, &sampleRate);
        
        sampleRates.clear();
        sampleRates.add(sampleRate);
        
        for(int i = 0; i < 6; ++i)
        {
            if(commonSampleRates[i] == sampleRate)
            {
                continue;
            }
            
            AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareSampleRate, sizeof(commonSampleRates[i]), &commonSampleRates[i]);
            
            Float64 actualSampleRate = 0.0;
            UInt32 size = sizeof (actualSampleRate);
            AudioSessionGetProperty (kAudioSessionProperty_CurrentHardwareSampleRate, &size, &actualSampleRate);
            
            if(actualSampleRate == commonSampleRates[i])
            {
                sampleRates.add(actualSampleRate);
            }
        }
        
        DefaultElementComparator<Float64> comparator;
        sampleRates.sort(comparator);
        
        AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareSampleRate, sizeof(sampleRate), &sampleRate);
        
        size = sizeof (sampleRate);
        AudioSessionGetProperty (kAudioSessionProperty_CurrentHardwareSampleRate, &size, &sampleRate);
    }

From what I can tell from the AudioSession API there is no way to get a list of all the supported sample rates, so I just try some common ones and see if they work.
 

Lastly, I've changed the following 2 functions

    int getNumSampleRates()                 { return jmax(sampleRates.size(),1); }
    double getSampleRate (int index)        { return sampleRates.size() > 0 ? sampleRates[index] : sampleRate; }


I've tested this code on an iPad 2 running iOS 7.0.2,

I'll bet this is what is causing the problem I was talking about in http://www.juce.com/forum/topic/sample-rate-issue-ios-simulator

I'll try out your code when I get home later.

Excellent stuff - thanks!

I've done some work on this now, and have hopefully sorted this out, plus some other fixes to some problems I noticed with the buffer size - let me know if you have any more issues with it!

My iPhone app is crashing now when going to background while playing audio. Commit 4c9b1eff4f6023a0b9cc48fd0354a83893166931 introduced the issue. It crashes regardless of using the builtin speakers or a bluetooth headset.

Besides crashing an assertion is triggered in juce_ios_Audio.cpp, line 244. It has a nice comment:

// This shouldn't ever get triggered, but please let me know if it does!

Well, there you go :-)

How annoying! Ok.. I've added a bit of code to cope with that situation - let me know if anything still fails for you!

Works great now. Thanks!

Still no luck for me when trying to chnge the sample rate on iOS 7 using the new Juce Demo

It seems with iPhone5 and iPhone5s devices the device samplerate will be changed when using the phone app. This means the samplerate will not remain unchanged while an app is running.

The iOS music app is able to set the samplerate back to the original value but I wasn't able to do this using the audio device manager.

So I decided to use a different approach. I resample to whatever samplerate is used by the hardware. My reasoning is that if the property used to set the samplerate on iOS is named kAudioSessionProperty_PreferredHardwareSampleRate nothing will guarantee that the provided value is actually used.

Well, that's kind of true of any device on any platform.. You can request a particular rate, but it's only the rate that the device reports when audioDeviceAboutToStart() is called that you should believe. Presumably that number's correct on iOS?

Yes, that's right. The rate that audioDeviceAboutToStart() reports may be different to what has been requested originally, but the reported rate is correct.

What has changed on iOS is that if your app goes to background on iPhone 5 and iPhone 5s and the audio session has been interrupted by a different app (e.g. the phone app) and then your app comes back to foreground the rate may have changed. After an interruption that is not temporary you have to restart the audio device anyway during which audioDeviceAboutToStart() will report the new rate that is in effect now.