AVPlayer/AVAudioMix fade-in effect clicks in the beginning
Asked Answered
P

0

6

I'm trying to implement a fade-in effect based on AVPlayer + AVAudioMix + AVAudioMixInputParameters. It basically works except when playing the audio for the first time after starting my app there is a click in the beginning. Subsequent plays work perfect though, but the first-time glitch is pretty stable and reproducible.

My Play button is enabled only after the AVPlayerItem's status is set to ready, so it's impossible to fire a play method while the player is not ready. In fact it doesn't matter how long I wait after loading the audio file and constructing all the objects.

This happens on OS X, I haven't tested it on iOS (yet).

Note that for this test you need an audio file that starts with sound and not silence. Here is my stripped down code without the GUI part (testFadeIn is the entry point):

static AVPlayer* player;
static void* PlayerItemStatusObserverContext = &PlayerItemStatusObserverContext;

- (void)testFadeIn
{
    AVURLAsset* asset = [AVURLAsset.alloc initWithURL:[NSURL fileURLWithPath:@"Helicopter.m4a"] options:@{AVURLAssetPreferPreciseDurationAndTimingKey: @YES}];
    AVPlayerItem* item = [AVPlayerItem playerItemWithAsset:asset];
    player = [AVPlayer playerWithPlayerItem:item];
    [item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:PlayerItemStatusObserverContext];
}

- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
    if (context == PlayerItemStatusObserverContext)
    {
        AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue];
        if (status == AVPlayerStatusReadyToPlay)
        {
            [self applyFadeIn];
            [self performSelector:@selector(play:) withObject:nil afterDelay:1.0];
        }
    }
}

- (void)applyFadeIn
{
    assert(player.currentItem.tracks.firstObject);
    AVMutableAudioMixInputParameters* fadeIn = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:player.currentItem.tracks.firstObject];
    [fadeIn setVolume:0 atTime:kCMTimeZero];
    [fadeIn setVolume:1 atTime:CMTimeMake(2, 1)];
    NSMutableArray* paramsArray = [NSMutableArray new];
    [paramsArray addObject:fadeIn];
    AVMutableAudioMix* audioMix = [AVMutableAudioMix audioMix];
    audioMix.inputParameters = paramsArray;
    player.currentItem.audioMix = audioMix;
}

- (void)play:(id)unused
{
    [player play];
}

Click! What is wrong with this?

Edit:

An obvious workaround that I use at the moment is: when the player reports it's ready, I do a short 100ms playback with volume=0, then restore currentTime and volume and only then I report to the main app that the player is ready. This way there are no clicks. Interestingly, anything less than 100ms still gives the click.

This seems like an issue with something that's being cached by AVFoundation after the first playback. It's neither the tracks, as they are available when I set the fade in params, nor the seek status.

Pinch answered 18/7, 2015 at 2:19 Comment(3)
To anyone also looking for a solution: I gave AVFoundation altogether and switched to The Amazing Audio Library, which does what it does just perfectly. Though TAAE is a bit of an advanced tool for non-trivial audio apps and you do need some basic understanding of Core Audio.Pinch
i would imagine that this is from some threading/buffering issue ... maybe TAAE handles Buffer overruns better ?Scrumptious
@Scrumptious TAAE is fantastic at everything it does, but it is poorly documented and it is conceptually different from the AVPlayer family.Pinch

© 2022 - 2024 — McMap. All rights reserved.