Question

Every once in a while, AVAudioPlayer crashes when I call its play method.

My app is a game, with background music and many sound effects. Originally, AVAudioPlayer was causing it to run very slowly. However, as it seems to be the simplest sound API to work with, (especially for a 2D game like mine) I simply decided to play the sounds concurrently using dispatch_async.

This resulted in a considerable improvement, with no noticeable performance toll taken on the main thread of the game. But it crashes the game. Every 5 or 10 minutes, while playing, an exception appears.

I don't understand why this is happening. Is something being released internally within the player before it can be run concurrently? Or is it something else altogether?

EDIT:

This is a snippet of code I use to play sounds when the player collects coins in game. I use 4 AVAudioPlayers so the sounds can overlap:

if([coinCollectPlayer isPlaying]){
                if([coinCollectPlayer2 isPlaying]){
                    if([coinCollectPlayer3 isPlaying]){
                        dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
                            [coinCollectPlayer4 play];});
                    }
                    else dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
                        [coinCollectPlayer3 play];});
                }
                else dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
                    [coinCollectPlayer2 play];});
            }
            else
                dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){[coinCollectPlayer play];});
Was it helpful?

Solution

The reason you're crashing occasionally is that this structure is unsound:

if([coinCollectPlayer isPlaying]){
    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { 
        [coinCollectPlayer play];
    });
}

You are checking isPlaying on the main thread. So how do you know that between then and when you say play on a background thread, you haven't already said play on a background thread? You don't. Think of it like this:

if([coinCollectPlayer isPlaying]){
    // GREAT BIG HOLE HERE YOU CAN DRIVE A TRUCK THROUGH
    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { 
        [coinCollectPlayer play];
    });
}

You need to use a consistent thread for all communication with your audio players, and either you must use locking / synchronize to enforce coherency or you must use a serial queue (which DISPATCH_QUEUE_PRIORITY_DEFAULT is not).

Having said all that, I think what you're doing is just wrong. I don't believe that AVAudioPlayer needs to or should be used on a background thread.

What I suggest you try is to use every AVAudioPlayer once. In other words, to play a sound, make an AVAudioPlayer, play a sound, and throw the whole thing away.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top