JuceDemo Bug: Audio Related - latest tip (Tue Mar 6 16:03)

Hi. I’d just like to report a bug in the JuceDemo that is probably related to my other ongoing thread on this board.
http://www.rawmaterialsoftware.com/viewtopic.php?f=4&t=8670

THE BUG ONLY OCCURS ON THE DEVICE, NOT FROM WITHIN THE SIMULATOR

To replicate the bug,

1 ) Open the JuceDemo on an iOS device
2 ) Click Demo --> Audio
3 ) Click Demo --> Widgets
4 ) Open buttons tab
5 ) Click the hyperlink
6 ) Let Safari begin to open the rawmaterialsoftware site
7 ) Hit the home button
8 ) Go back to the juce Demo by either…
a) double tapping the home button and bringing the juce demo from the background tasks
b) hit the JuceDemo icon on the home screen

The Demo will then crash. When hooked up to the debugger…

I sometimes get an error here on line 397 of juce_ios_Audio.cpp

…or here on line 235

…or here on line 257

callback->audioDeviceIOCallback ((const float**) inputChannels, numInputChannels, outputChannels, numOutputChannels, (int) numFrames);

Thanks for looking

Bump. Can anyone replicate this?

Had a quick go in the simulator, and can’t replicate it…

No. Works fine in the simulator, agreed. Device only issue.

Hmm. Really not sure what could be going on there…

Aw, snap. I will use my comparatively feeble skills to probe away. Thanks for looking anyway, Jules. I know you’re a busy fella.

Can anyone replicate this on their devices?

FWIW, it doesn’t replicate on either an iPad 2 or an iPad 1 here. Odd that it is crashing in audio when no audio manager is loaded (in widget demo).

Thanks for loading it up and taking a look. Your input is appreciated!

Just tested this on an iPad 2 and it definitely does crash, just like it does on the iPhone 4S (unless I’m going insane)

Did you follow step 2? When the audio demo is loaded, there is a call to initialise in AudioDemoTabComponent.cpp . .

//[UserPreSize]
    deviceManager.initialise (2, 2, 0, true, String::empty, 0);
    //[/UserPreSize]

There is no call to closeAudioDevice () anywhere in the demo and I can’t see any way that the device manager is unloaded until the MainDemoWindow is deleted. The memory usage just totals up as you open each of the demos.

[quote=“billythekid”]
There is no call to closeAudioDevice () anywhere in the demo and I can’t see any way that the device manager is unloaded until the MainDemoWindow is deleted. The memory usage just totals up as you open each of the demos.[/quote]

The DeviceManager is a member of AudioDemoTabComponent. This is instantiated with a call to “createAudioDemo()”. The call is actually something like:

showDemo (createAudioDemo());

showDemo assigns the component pointer to: ScopedPointer currentDemo.

When a new value is assigned to currentDemo, it deletes the prior object (see the ScopedPointer copy and assignment operators).

This is super easy to verify with breakpoints on:

AudioDeviceManager::AudioDeviceManager() and AudioDeviceManager::~AudioDeviceManager

If you are ‘just leaking memory’, then perhaps you inadvertently broke ScopedPointer. The AudioDeviceManager also relies on ScopedPointer members to clean up open audio and midi devices (if any).

Hi again, thanks for the response. I have just freshly cloned juce to make absolutely definitely sure that I didn’t screw anything up inadvertently. I rebuilt the demo and run through the steps described in my OP. About 90% of the time I get a bad access crash in the following section of code when running on a device . . .

    bool createAudioUnit()
    {
        if (audioUnit != 0)
        {
            AudioComponentInstanceDispose (audioUnit); <---------------- HERE!
            audioUnit = 0;
        }

… if it doesn’t happen for you immediately then cycle through the steps another time or two and it should bail out to the debugger.

I put a breakpoint on createAudioUnit() in routingChanged, and this gets hit 3 times before the audio demo correctly loads up.

I put a breakpoint on AudioComponentInstanceDispose (audioUnit) that gets hit the 2nd and 3rd times as you’d expect from the above “if” statement.

I put a breakpoint in AudioDeviceManager::~AudioDeviceManager() and this gets hit successfully when I switch from the audio demo to the widgets demo.

I put a breakpoint on ~IPhoneAudioIODevice() and the close() function gets called just fine setting the audioUnit to zero.

I switch out to safari and then switch back to the app (which opens on the widgets page) and immediately hit my breakpoint on createAudioUnit() and then AudioComponentInstanceDispose (audioUnit). i.e. audioUnit is not null. Continuing causes a crash.

Thanks again for your continued support!

Well, I can only suppose that when safari is launched, the audiounit object that the app is using gets trashed by some external process or an OS bug… I don’t think the code is doing anything wrong, and the OS definitely shouldn’t allow an audio unit object to get into a broken state like that. Not really sure what to suggest!

Just updated device and xcode on one of my machines to latest versions. Similar crashes occurring. You do not need to load Safari to get the crash. All that you need to do is open up the Audio demo, switch to any other demo, hit the home button, re-enter the juce demo and you get a crash.

The really painful thing about debugging this is it periodically causes . . .

Couldn't register com.rawmaterialsoftware.jucedemo with the bootstrap server. Error: unknown error code. This generally means that another instance of this process was already running or is hung in the debugger.

. . . and the only way I can get around this is by restarting the phone each time.

One thing that does bother me at the moment is that nobody else has reported being able to replicate the crash. I can do it using 2 different devices running different versions of iOS, with the juce demo built on 2 different Mac machines, which both run different OS versions, and have different versions of xcode. I think that I’m covering my bases there.

Can anyone else confirm or deny being able to replicate this error on a device? Input would be really helpful.

Just a quick thought . . after probing around, it seems as if interruptionListener gets hit occasionally after I return to the application, even though the audio device has been unloaded. Is it possible that this listener is registered with a broadcaster somewhere and not deregistered when we zero the audioUnit?

Regarding “Couldn’t register…” That is typically a hung process on the device (or simulator). However, it can also indicate cross linked bundles.

As far as the interruption listener, it is registered when the audio session is initialized. The interruption listener should not be called if the session is set to inactive (which is done in close() for the iOS audio device). If it is being invoked when you have no actual device manager constructed, that would be, well, bad. It’s a static member, handed a ‘this’ pointer passed in originally, which is static cast back. Invoking a method from a deleted this pointer is generally a recipe for a crash.

You could use the Logger:: to report set active / set inactive to see if you are mismatched.

Also, you seem to be crashing a lot using audioUnit as a param. You could use Logger to report the value at construction and then again to see what you are passing as a parameter.

No, it doesn’t repro here (I just tried a 4S as well, leaving it on different demos and on different tabs in the audio demo). And I tracked down an iOS audio bug for another user recently (no inputs specified) and didn’t see it then either. However, the primary gotcha in device vs. simulator is memory. Apps typically crash because they keep pointers to things that were purged in response to a low memory delegate.

Thanks yet again for helping me out here

Just to make this super clear, exiting and reopening when leaving it on different tabs of the audio demo is just fine. Exiting and reopening on different demos is also just fine. I only have trouble when I exit and reopen on a different demo after the audio demo has been used.

I have been closing all background tasks before testing. Looking at the memory on iPad, I have 182MB free before running the juce demo and 173.3 MB free while it runs in the background so it seems unlikely unless I’m missing something fundamental. Any way of setting an alarm if such a purge happens? Do you do anything special to prime your devices for development that I might not be doing. It just seems so strange that identical devices running (what we assume to be) identical software are behaving differently :shock:

I will look into this.

Memory resources on iOS are tricky - remember, you thought all the demos were leaking.

You can tell if you get a low memory warning by adding an additional app delegate:

applicationDidReceiveMemoryWarning

You don’t have to do anything, you can just break on it. I have no idea why you are having unusual Audio Session life/scope issues. The general rule of thumb is to keep the iOS Audio Session life as short as possible:

https://developer.apple.com/library/ios/#DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/AdvancedAppTricks/AdvancedAppTricks.html

However, the definitive Audio Session Programming Guide is here:

https://developer.apple.com/library/ios/#DOCUMENTATION/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007875

Edit: No, I don’t do anything special for devices, though I sometimes delete the prior bundle when debugging something.

Thanks again. More strange behavior to report. I have been putting DBG statements into the code and have noticed something really odd.

void interruptionListener (const UInt32 interruptionType) { DBG( "Interruption detected type:" + String((int)interruptionType) )

Does not get hit when I leave the app while running audio demos, but it does get hit leaving the app when running other demos after the audio demo. Is block of code being hit as it was designed to be?

If I comment out all code in the interruptionListener block, then my app does not crash anymore when I exit and reopen on other demos . . . but I’m still obviously very worried that both my devices seem to be behaving differently to everyone (jfitz) else’s. I’m getting to the stage where I am wondering if some strange flag has been set in XCode and I’m about to completely remove XCode and reinstall. Clutching at straws here.

Just got my hands on another 4S and tested the Juce demo, just incase I had some common settings or apps on my own devices that might be doing something strange. The error reproduces exactly like on my own devices.

This time, I put a breakpoint in the interruption listener. I load the Juce demo, go to the audio demo and my DBG macro shows me that the IPhoneAudioIODevice Constructor was called. I navigate to the widgets demo and my other DBG macro shows me that IPhoneAudioIODevice Destructor was called. I hit the home button and return to the juce demo from the home screen and I hit the breakpoint. The phantom IPhoneAudioIODevice is full of crud as you can see.

Ah! Wish you’d said that earlier - that makes it very clear what’s going wrong!

The AudioIODevice object is obviously a dangling pointer, and that’s because my code didn’t deregister the callback when the AudioIODevice get deleted. That sounds like a very easy fix, but just to make things more challenging, Apple don’t provide a function to deregister an interruption listener once you’ve registered it (!!)

But I’ve added some nasty hacks to try to work around this - let me know if it works now!

[quote=“jules”]Ah! Wish you’d said that earlier - that makes it very clear what’s going wrong!

The AudioIODevice object is obviously a dangling pointer, and that’s because my code didn’t deregister the callback when the AudioIODevice get deleted. That sounds like a very easy fix, but just to make things more challenging, Apple don’t provide a function to deregister an interruption listener once you’ve registered it (!!)

But I’ve added some nasty hacks to try to work around this - let me know if it works now![/quote]

That could also explain the difference in crashing. A zombie will only crash if it is already over-written.

I suppose that the problem is fundamentally that the AudioSession is a singleton with the life span of the app. Couldn’t you just add a static delegate pointer to go with the static callback function? The close() member could then set the delegate to nullptr. This is sort of how the objC wrapper works.

That sorts the JUCE demo. Thanks to both of you for your unwavering patience.

I also makes the app with background audio input that I discuss in the other related thread:
http://www.rawmaterialsoftware.com/viewtopic.php?f=4&t=8670

. . . a lot more difficult to crash. I might be able to fix it now though. I will continue conversation on that thread if I come across strange things.

Thanks again