Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Audio Session "Ducking" Broken in iOS 4...?

I've an app which uses the MPAudioPlayerController to access the iPod music library, and an AVAudioPlayer to overlay audio on top of the music. I've used this documentation as a guide. Specifically:

Finally, you can enhance a category to automatically lower the volume of other audio when your audio is playing. This could be used, for example, in an exercise application. Say the user is exercising along to their iPod when your application wants to overlay a verbal message—for instance, “You’ve been rowing for 10 minutes.” To ensure that the message from your application is intelligible, apply the kAudioSessionProperty_OtherMixableAudioShouldDuck property to the audio session. When ducking takes place, all other audio on the device—apart from phone audio—lowers in volume.

But I'm not seeing this behavior. In fact, what I see (or hear, rather) is that if I setup the AudioSession with kAudioSessionProperty_OtherMixableAudioShouldDuck set to true, the MPAudioPlayerController initial volume gets reduced, and if I then call pause (and then play again) on the MPAudioPlayerController the volume level gets increased to "normal" levels. Playing the AVAudioPlayer does not have any affect on the audio level...

So I've set up a simple test case to reproduce this.

In a ViewController header:

#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>

@interface MusicPlayerVolumeTestViewController : UIViewController <AVAudioPlayerDelegate>
{
    UIButton *musicButton;
    UIButton *soundButton;
    AVAudioPlayer *audioPlayer;
    MPMusicPlayerController *musicPlayerController;
}
@property (nonatomic, retain) IBOutlet UIButton *musicButton;
@property (nonatomic, retain) IBOutlet UIButton *soundButton;
@property (nonatomic, retain) MPMusicPlayerController *musicPlayerController;

- (IBAction)musicAction;
- (IBAction)soundAction;

@end

and in the implementation:

- (void)viewDidLoad
{
    [super viewDidLoad];

    //Setup our Audio Session
    OSStatus status = AudioSessionInitialize(NULL, NULL, NULL, NULL);    
    //We want our audio to play if the screen is locked or the mute switch is on
    UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
    status = AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (sessionCategory), &sessionCategory);
    //We want our audio to mix with other app's audio
    UInt32 shouldMix = true;
    status = AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof (shouldMix), &shouldMix);
    //Enable "ducking" of the iPod volume level while our sounds are playing
    UInt32 shouldDuck = true;
    AudioSessionSetProperty(kAudioSessionProperty_OtherMixableAudioShouldDuck, sizeof(shouldDuck), &shouldDuck);
    //Activate our audio session
    AudioSessionSetActive(YES);

    //Setup the Music Player to access the iPod music library
    self.musicPlayerController = [MPMusicPlayerController applicationMusicPlayer];
    [self.musicPlayerController setShuffleMode: MPMusicShuffleModeSongs];
    [self.musicPlayerController setRepeatMode: MPMusicRepeatModeNone];
    [self.musicPlayerController setQueueWithQuery:[MPMediaQuery songsQuery]];

    //Setup a AVAudioPlayer sound to overlay against the Music Player audio
    NSURL *soundURL = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"overlay" ofType:@"mp3"]];
    NSError *error = nil;
    audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error: &error];
    if (!audioPlayer)
    {
        NSLog(@"Could not create audio effect player: %@", [error localizedDescription]);
    }
    [audioPlayer prepareToPlay];
}

- (IBAction)musicAction
{
    if (self.musicPlayerController.playbackState == MPMusicPlaybackStatePlaying)
    {
        [self.musicPlayerController pause];
    }
    else if (self.musicPlayerController.playbackState == MPMusicPlaybackStateStopped
          || self.musicPlayerController.playbackState == MPMusicPlaybackStatePaused)
    {
        [self.musicPlayerController play];
    }
}

- (IBAction)soundAction
{
    if (audioPlayer.playing)
    {
        [audioPlayer pause];
    }
    else
    {
        [audioPlayer play];
    }
}

I've wired up a couple UIButtons. One for the musicAction (used for playing/pausing the MPMusicPlayerController) and one for the soundAction (used for playing/pausing the AVAudioPlayer).

As mentioned, If I tap the musicAction button, the music plays, but at a reduced volume level, and if I tap the soundAction button, the overlay plays, but has no affect on the volume of the MPMusicPlayerController. And, more bug-like, is that when I pause and then play the MPMusicPlayerController the volume of the music increases to the level it would have been if I did not setup the AudioSession.

I'm interested to know if anyone else has had this experience, and if so if you've found a work around (or can tell me that I'm doing something wrong). Otherwise, I guess I'm off to Radar.

Many thanks,

Levi

like image 875
levigroker Avatar asked Jul 20 '10 19:07

levigroker


2 Answers

I used this post when I was having a similar issue and had trouble getting it to work consistently. It would work for a while and then just get stuck "ducked". I spent a lot of time researching and debugging this and finally just called Apple. They told me to look at the breadcrumb sample code. I followed that example and everything worked fine.

Here is Apple's sample code:

http://developer.apple.com/library/ios/#samplecode/Breadcrumb/Introduction/Intro.html

The support/developer at Apple also said to watch the order of how and when they set the session properties. That apparently was the trick. After reading a lot of posts here and elsewhere, I had settings that conflicted with each other. Starting from scratch and following the bread crumb example made it work. I have not had an issue since.

I posted the same answer here:

How to unduck AVAudioSession

To me this answer was borderline trade secret since it was so difficult to get working and there were working apps in the market. For me, seeing a working example, which was pretty much just the sound ducking and unducking was worth its weight in gold.

Also, I mentioned to Apple that this was a known issue and they did not agree. They were not aware of any issue here. Sure enough if you follow the breadcrumb example it works with out any strange session deactivate and reactive, etc., etc.

like image 160
zaphodtx Avatar answered Oct 21 '22 07:10

zaphodtx


Ducking is not automatically related to your playing sounds in any way. When you turn on ducking, background sound ducks. When you turn off ducking and deactivate your audio session, background sound comes back to its original level.

So don't turn on ducking in your viewDidLoad. Don't turn it on until you are actually about to play the sound. That causes the background sound to duck. When your sound is done playing, turn ducking back off and toggle your audio session inactive and active again. That causes the background sound to unduck:

UInt32 duck = 0;
AudioSessionSetProperty(kAudioSessionProperty_OtherMixableAudioShouldDuck, 
                        sizeof(duck), &duck);
AudioSessionSetActive(false);
AudioSessionSetActive(true);
like image 3
matt Avatar answered Oct 21 '22 05:10

matt