【问题标题】:AVAudioPlayer fade volume outAVAudioPlayer 淡出音量
【发布时间】:2010-11-16 00:56:11
【问题描述】:

我有一个 AVAudioPlayer 播放一些音频(呃!)

当用户按下按钮时会启动音频。 当他们释放它时,我希望音频淡出。

我正在使用界面生成器...所以我试图在“内部修饰”上连接一个功能,该功能在 1 秒内淡出音频然后停止。

有什么想法吗?

谢谢

【问题讨论】:

  • 请注意 setVolume#fadeDuration: 现在存在 - 所以不需要这个非常古老的问题!
  • 请注意,对于 AVPlayer(不是 AVAudioPlayer),有这种方法:gist.github.com/artem-sherbachuk/…
  • @Fattie 我试过了,但它在 Swift 5 中不起作用。我不确定我是否做错了什么。

标签: iphone avaudioplayer volume


【解决方案1】:

这是我的做法:

-(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;
    }
}

11 年后:请注意setVolume#fadeDuration 现在存在!

【讨论】:

  • 为什么还没有将其标记为答案?已经快 4 年了!
  • 从经验中讲:确保您有 AudioToolbox,而不仅仅是在“Link Binary With Libraries”下添加了 AVFoundation,否则您会费尽心思试图弄清楚为什么音频实际上不是即使您的 NSLogs 显示此方法正在被调用并且音量正在变化,也会逐渐消失。否则,是的,100% 这个。
  • 我为此做了一个分类。感谢您的帖子。
  • 有谁知道如何在 Swift 中做到这一点?
  • @skyguy 这是同一件事,但在 swift stackoverflow.com/a/28810111/2383604
【解决方案2】:

Swift 有一个 AVAudioPlayer 方法可用于淡出,该方法自 iOS 10.0 起包含在内:

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

【讨论】:

  • 音量设置为 0 后是否会停止音频?还是仍在播放但音量为 0?
  • 它不会停止音频播放器。
  • 很遗憾,AVPlayer 没有这样的功能:/
【解决方案3】:

我使用 NSOperation 子类解决了这个问题,因此音量衰减不会阻塞主线程。它还允许淡入淡出排队和忘记。这对于播放带有淡入和淡出效果的单发声音特别有用,因为它们在最后一个淡入淡出完成后被解除分配。

// 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];

有关 MXAudioPlayerFadeOperation 类代码,请参阅this post

【讨论】:

【解决方案4】:

我最终将一些答案组合在一起,并将其转换为 Swift,最终以这种方法结束:

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
    }
}

【讨论】:

  • 非常有帮助。谢谢。我将 dispatchTime 变量和 dispatch_after 函数替换为: DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { }
【解决方案5】:

这些都是很好的答案,但是它们不涉及指定衰减率(或将对数曲线应用于衰减,这有时是可取的),或指定从您正在衰减的单位减少的 dB 数到。

这是我的一个应用程序中的一个例外,删除了一些与此问题无关的“花里胡哨”。

享受吧!

#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】:

    swift 3 的扩展灵感来自投票最多的答案。对于那些喜欢复制粘贴的人:)

    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
            }
        }
    }
    

    【讨论】:

    • 虽然这确实有效(而且我喜欢所有的复制粘贴:)),真的,如果你想要完美,只需使用显示链接并以这种方式平滑它:/(而不是 0.1 块)
    • 请注意 setVolume#fadeDuration 现在存在,所以这都是学术性的,除非您使用的是 AVPlayer :/
    【解决方案7】:

    我在 Swift 中编写了一个辅助类,用于淡入和淡出 AvAudioPlayer。您可以使用对数音量函数获得更渐变的效果。

    let player = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
    
    let fader = iiFaderForAvAudioPlayer(player: player)
    fader.fadeIn()
    fader.fadeOut()
    

    这里是一个演示应用程序:https://github.com/evgenyneu/sound-fader-ios

    【讨论】:

      【解决方案8】:

      斯威夫特 3

      我喜欢 Ambroise Collon 的答案,所以我投了赞成票,但 Swift 是静态类型的,所以 performSelector: 方法将被淘汰,也许替代方案可以调度异步(在这个版本中,我还添加了目的地音量作为参数)

      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
              }
          }
      }
      

      【讨论】:

        【解决方案9】:

        在我看来,这似乎是对 NSOperationQueue 的一种使用。

        因此这是我的解决方案:

        -(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】:

        这个怎么样:(如果传入的时间是负数则淡出声音,否则淡入)

        - (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解决方案:

          此处评分最高的答案很棒,但由于 0.1 的音量步长太大,它会产生口吃效果。使用 0.01 可以获得更平滑的淡入淡出效果。

          使用此代码,您可以指定希望淡化过渡持续多长时间

          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)
          }
          

          您可以使用fadeTime 常量调整时间。

          【讨论】:

          • 你不能,真的,这样做 - 使用显示链接。 (实际上要容易得多)。请注意 setVolume#fadeDuration 现在存在,所以这都是学术性的。
          【解决方案12】:

          在 Objective-C 中试试这个:

          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];
          

          【讨论】:

            【解决方案13】:

            A very good solution 可以从 objc.io 的 Nick Lockwood 处获得此问题。它利用 Core Animation 对音量变化进行计时,如果需要将 UI 动画与音量变化(包括交互式动画)同步,它会派上用场。

            【讨论】:

            • 虽然理论上这可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。
            猜你喜欢
            • 1970-01-01
            • 2012-09-22
            • 1970-01-01
            • 1970-01-01
            • 2017-11-07
            • 2013-09-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多