AVAudioPlayer fade volume out

2019-01-21 00:09发布

问题:

I have an AVAudioPlayer playing some audio (duh!)

The audio is initiated when the user presses a button. When they release it I want the audio to fade out.

I am using Interface builder...so I am trying to hook up a function on "touch up inside" that fades the audio out over 1 sec then stops.

Any ideas?

Thanks

回答1:

Here's how I'm doing it:

-(void)doVolumeFade
{  
    if (self.player.volume > 0.1) {
        self.player.volume = self.player.volume - 0.1;
        [self performSelector:@selector(doVolumeFade) withObject:nil afterDelay:0.1];       
     } else {
        // Stop and get the sound ready for playing again
        [self.player stop];
        self.player.currentTime = 0;
        [self.player prepareToPlay];
        self.player.volume = 1.0;
    }
}


回答2:

Swift has an AVAudioPlayer method you can use for fading out which was included as of iOS 10.0:

var audioPlayer = AVAudioPlayer()
...
audioPlayer.setVolume(0, fadeDuration: 3)


回答3:

I tackled this problem using an NSOperation subclass so fading the volume doesn't block the main thread. It also allows fades to be queued and and forgotten about. This is especially useful for playing one shot sounds with fade-in and fade-out effects as they are dealloced after the last fade is completed.

// Example of MXAudioPlayerFadeOperation in NSOperationQueue 
 NSOperationQueue *audioFaderQueue = [[NSOperationQueue alloc] init];
  [audioFaderQueue setMaxConcurrentOperationCount:1]; // Execute fades serially.

  NSString *filePath = [[NSBundle mainBundle] pathForResource:@"bg" ofType:@"mp3"]; // path to bg.mp3
  AVAudioPlayer *player = [[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:filePath] error:NULL] autorelease];
  [player setNumberOfLoops:-1];
  [player setVolume:0.0];

  // Note that delay is delay after last fade due to the Operation Queue working serially.
  MXAudioPlayerFadeOperation *fadeIn = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:1.0 overDuration:3.0];
  [fadeIn setDelay:2.0];
  MXAudioPlayerFadeOperation *fadeDown = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:0.1 overDuration:3.0];
  [fadeDown setDelay:0.0];
  MXAudioPlayerFadeOperation *fadeUp = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:1.0 overDuration:4.0];
  [fadeUp setDelay:0.0];
  MXAudioPlayerFadeOperation *fadeOut = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:0.0 overDuration:3.0];
  [fadeOut setDelay:2.0];

  [audioFaderQueue addOperation:fadeIn]; // 2.0s - 5.0s
  [audioFaderQueue addOperation:fadeDown]; // 5.0s - 8.0s
  [audioFaderQueue addOperation:fadeUp]; // 8.0s - 12.0s
  [audioFaderQueue addOperation:fadeOut]; // 14.0s - 17.0s

  [fadeIn release];
  [fadeDown release];
  [fadeUp release];
  [fadeOut release];

For MXAudioPlayerFadeOperation class code see this post.



回答4:

I ended up combining some of the answer together and converted it to Swift ending up in this method:

func fadeVolumeAndPause(){
    if self.player?.volume > 0.1 {
        self.player?.volume = self.player!.volume - 0.1

        var dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
        dispatch_after(dispatchTime, dispatch_get_main_queue(), {
            self.fadeVolumeAndPause()
        })

    } else {
        self.player?.pause()
        self.player?.volume = 1.0
    }
}


回答5:

These are all good answers, however they don't deal with specifying the rate of fade (or applying a logarithmic curve to the fade, which is sometimes desirable), or specifying the number of dB's reduction from unity that you are fading to.

this is an except from one of my apps, with a few "bells and whistles" removed, that are not relevant to this question.

enjoy!

#define linearToDecibels(linear) (MIN(10,MAX(-100,20.0 * log10(linear))))
#define decibelsToLinear(decibels) (pow (10, (0.05 * decibels)))

#define fadeInfoId(n) [fadeInfo objectForKey:@#n]
#define fadeInfoObject(NSObject,n) ((NSObject*) fadeInfoId(n))
#define fadeInfoFloat(n) [fadeInfoId(n) floatValue]
#define useFadeInfoObject(n) * n = fadeInfoId(n)
#define useFadeInfoFloat(n) n = fadeInfoFloat(n)
#define setFadeInfoId(n,x) [fadeInfo setObject:x forKey:@#n]
#define setFadeInfoFloat(n,x) setFadeInfoId(n,[NSNumber numberWithFloat:x])
#define setFadeInfoFlag(n) setFadeInfoId(n,[NSNumber numberWithBool:YES])

#define saveFadeInfoId(n) setFadeInfoId(n,n)
#define saveFadeInfoFloat(n) setFadeInfoFloat(n,n)

#define fadeAVAudioPlayer_default           nil
#define fadeAVAudioPlayer_linearFade        @"linearFade"
#define fadeAVAudioPlayer_fadeToStop        @"fadeToStop"
#define fadeAVAudioPlayer_linearFadeToStop  @"linearFadeToStop"





-(void) fadeAVAudioPlayerTimerEvent:(NSTimer *) timer {
    NSMutableDictionary *fadeInfo = timer.userInfo;
    NSTimeInterval elapsed = 0 - [fadeInfoObject(NSDate,startTime) timeIntervalSinceNow];
    NSTimeInterval useFadeInfoFloat(fadeTime);
    float          useFadeInfoFloat(fadeToLevel);
    AVAudioPlayer  useFadeInfoObject(player);
    double linear;
    if (elapsed>fadeTime) {

        if (fadeInfoId(stopPlaybackAtFadeTime)) {
            [player stop];
            linear = fadeInfoFloat(fadeFromLevel);

        } else {

            linear = fadeToLevel;
        }
        [timer invalidate];
        [fadeInfo release];

    } else {


        if (fadeInfoId(linearCurve)) {
            float useFadeInfoFloat(fadeFromLevel);
            float fadeDelta = fadeToLevel-fadeFromLevel;
            linear = fadeFromLevel + (fadeDelta * (elapsed/fadeTime));
        } else {
            float useFadeInfoFloat(fadeToDB);
            float useFadeInfoFloat(fadeFromDB);

            float fadeDelta = fadeToDB-fadeFromDB;
            float decibels = fadeFromDB + (fadeDelta * (elapsed/fadeTime));
            linear = decibelsToLinear(decibels);
        }       
    }

    [player setVolume: linear];

    //[self displayFaderLevelForMedia:player];
    //[self updateMediaVolumeLabel:player];
}


-(void) fadeAVAudioPlayerLinear:(AVAudioPlayer *)player over:(NSTimeInterval) fadeTime fadeToLevel:(float) fadeToLevel fadeMode:(NSString*)fadeMode {
    NSMutableDictionary *fadeInfo = [[NSMutableDictionary alloc ]init];
    saveFadeInfoId(player);
    float fadeFromLevel = player.volume;// to optimize macros put value in var, so we don't call method 3 times.
    float fadeFromDB = linearToDecibels(fadeFromLevel);
    float fadeToDB   = linearToDecibels(fadeToLevel);

    saveFadeInfoFloat(fadeFromLevel);
    saveFadeInfoFloat(fadeToLevel);
    saveFadeInfoFloat(fadeToDB);
    saveFadeInfoFloat(fadeFromDB);
    saveFadeInfoFloat(fadeTime);

    setFadeInfoId(startTime,[NSDate date]);
    if([fadeMode isEqualToString:fadeAVAudioPlayer_fadeToStop]||[fadeMode isEqualToString:fadeAVAudioPlayer_linearFadeToStop]){ 
        setFadeInfoFlag(stopPlaybackAtFadeTime);
    }
    if([fadeMode isEqualToString:fadeAVAudioPlayer_linearFade]||[fadeMode isEqualToString:fadeAVAudioPlayer_linearFadeToStop]){ 
        setFadeInfoFlag(linearCurve);
    }

    [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(fadeAVAudioPlayerTimerEvent:) userInfo:fadeInfo repeats:YES];
}

-(void) fadeAVAudioPlayer:(AVAudioPlayer *)player over:(NSTimeInterval) fadeTime fadeToDB:(float) fadeToDB fadeMode:(NSString*)fadeMode {
    [self fadeAVAudioPlayerLinear:player over:fadeTime fadeToLevel:decibelsToLinear(fadeToDB) fadeMode:fadeMode ];
}

-(void) fadeoutAVAudioPlayer:(AVAudioPlayer *)player {
    [self fadeAVAudioPlayerLinear:player over:5.0 fadeToLevel:0 fadeMode:fadeAVAudioPlayer_default];
}

-(void) fadeinAVAudioPlayer:(AVAudioPlayer *)player {
    [self fadeAVAudioPlayerLinear:player over:5.0 fadeToLevel:0 fadeMode:fadeAVAudioPlayer_default];
}


回答6:

I wrote a helper class in Swift for fading AvAudioPlayer in and out. You can use logarithmic volume function for more gradual fading effect.

let player = AVAudioPlayer(contentsOfURL: soundURL, error: nil)

let fader = iiFaderForAvAudioPlayer(player: player)
fader.fadeIn()
fader.fadeOut()

Here a demo app: https://github.com/evgenyneu/sound-fader-ios



回答7:

Swift 3

I like Ambroise Collon answer's , so i voted up but Swift is statically typed so the performSelector: methods are to fall by the wayside, maybe an alternative could be dispatch async (in this version I've added also the destination volume as parameter)

func dispatchDelay(delay:Double, closure:@escaping ()->()) {
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay, execute: closure)
}

extension AVAudioPlayer {
    func fadeOut(vol:Float) {
        if volume > vol {
            //print("vol is : \(vol) and volume is: \(volume)")
            dispatchDelay(delay: 0.1, closure: {
                [weak self] in
                guard let strongSelf = self else { return }
                strongSelf.volume -= 0.01
                strongSelf.fadeOut(vol: vol)
            })
        } else {
            volume = vol
        }
    }
    func fadeIn(vol:Float) {
        if volume < vol {
            dispatchDelay(delay: 0.1, closure: {
                [weak self] in
                guard let strongSelf = self else { return }
                strongSelf.volume += 0.01
                strongSelf.fadeIn(vol: vol)
            })
        } else {
            volume = vol
        }
    }
}


回答8:

An extension for swift 3 inspired from the most voted answer. For those of you who like copy-pasting :)

extension AVAudioPlayer {
    func fadeOut() {
        if volume > 0.1 {
            // Fade
            volume -= 0.1
            perform(#selector(fadeOut), with: nil, afterDelay: 0.1)
        } else {
            // Stop and get the sound ready for playing again
            stop()
            prepareToPlay()
            volume = 1
        }
    }
}


回答9:

This seems to me to be a descent use of an NSOperationQueue.

Hence here is my solution:

-(void) fadeIn
{
    if (self.currentPlayer.volume >= 1.0f) return;
    else {
        self.currentPlayer.volume+=0.10;
        __weak typeof (self) weakSelf = self;
        [NSThread sleepForTimeInterval:0.2f];
        [self.fadingQueue addOperationWithBlock:^{
            NSLog(@"fading in %.2f", self.currentPlayer.volume);
            [weakSelf fadeIn];
        }];
    }
}
-(void) fadeOut
{
    if (self.currentPlayer.volume <= 0.0f) return;
    else {
        self.currentPlayer.volume -=0.1;
        __weak typeof (self) weakSelf = self;
        [NSThread sleepForTimeInterval:0.2f];
        [self.fadingQueue addOperationWithBlock:^{
            NSLog(@"fading out %.2f", self.currentPlayer.volume);
            [weakSelf fadeOut];
        }];
    }
}


回答10:

How about this: (if time passed in is negative then fade out the sound, otherwise fade in)

- (void) fadeInOutVolumeOverTime: (NSNumber *)time
{
#define fade_out_steps  0.1
    float           theVolume = player.volume;
    NSTimeInterval  theTime = [time doubleValue];
    int             sign = (theTime >= 0) ? 1 : -1;

// before we call this, if we are fading out, we save the volume
// so that we can restore back to that level in the fade in
    if ((sign == 1) &&
            ((theVolume >= savedVolume) ||
                            (theTime == 0))) {
        player.volume = savedVolume;
    }
    else if ((sign == -1) && (theVolume <= 0)) {
        NSLog(@"fading");
        [player pause];
        [self performSelector:@selector(fadeInOutVolumeOverTime:) withObject:[NSNumber numberWithDouble:0] afterDelay:1.0];

    }
    else {
        theTime *= fade_out_steps;
        player.volume = theVolume + fade_out_steps * sign;
        [self performSelector:@selector(fadeInOutVolumeOverTime:) withObject:time afterDelay:fabs(theTime)];
    }
}


回答11:

Swift solution:

The top rated answer here is great but it gives a stuttering effect as the volume step of 0.1 is too much. Using 0.01 gives a smoother fade effect to hear.

Using this code you can specify how long you want the fade transition to last.

let fadeVolumeStep: Float = 0.01

let fadeTime = 0.5 // Fade time in seconds

var fadeVolumeStepTime: Double {
     return fadeTime / Double(1.0 / fadeVolumeStep)
}

func fadeOut() {
    guard let player = self.player else {
        return
    }

    if !player.playing { return }

    func fadeOutPlayer() {
        if player.volume > fadeVolumeStep {
            player.volume -= fadeVolumeStep
            delay(time: fadeVolumeStepTime, closure: {
                fadeOutPlayer()
            })
        } else {
            player.stop()
            player.currentTime = 0
            player.prepareToPlay()
        }
    }

    fadeOutPlayer()
}

func fadeIn() {
    guard let player = self.player else {
        return
    }

    if player.playing { return }
    player.volume = 0
    player.play()

    func fadeInPlayer() {
        if player.volume <= 1 - fadeVolumeStep {
            player.volume += fadeVolumeStep
            delay(time: fadeVolumeStepTime, closure: {
                fadeInPlayer()
            })
        } else {
            player.volume = 1
        }
    }

    fadeInPlayer()
}

func delay(time delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

you can adjust the time using fadeTime constant.



回答12:

In Objective-C try this:

NSURL *trackURL = [[NSURL alloc]initFileURLWithPath:@"path to audio track"];

// fade duration in seconds
NSTimeInterval fadeDuration = 0.3;

// duration of the audio track in seconds
NSTimeInterval audioTrackDuration = 5.0; 

AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc]  initWithContentsOfURL:trackURL error:nil];
[audioPlayer play];

// first we set the volume to 1 - highest
[audioPlayer setVolume:1.0]; 

// then to 0 - lowest
[musicAudioPlayer setVolume:0 fadeDuration:audioTrackDuration - fadeDuration];