如何在 AVAudioPlayers 之间交叉淡入淡出

Posted

技术标签:

【中文标题】如何在 AVAudioPlayers 之间交叉淡入淡出【英文标题】:How to cross fade between AVAudioPlayers 【发布时间】:2012-09-15 08:35:41 【问题描述】:

我想使用 AVAudioPlayer 并淡入淡出超过 2 个 mp3 文件。

我有 5 个 mp3 文件和 5 个带有 UIScrollView 类和 pagingEnabled = YES 的页面“myScrollView”。

当用户移动页面时,我想播放每个页面的每首歌曲,音量淡出为上一个 mp3 文件,淡入为下一个 mp3。

请帮忙解决这个问题。

【问题讨论】:

请注意,这些天,您只需使用其中两个 setVolume#fadeDuration 【参考方案1】:

这是一个非常古老的线程,但我只是寻找有关交叉淡入淡出问题的解决方案并找到了它。我是这样解决的:

-(void)crossFadePlayerOne:(AVAudioPlayer *)player1 andPlayerTwo:(AVAudioPlayer *)player2 withCompletion:(void(^)())completion
    if([player1 volume] > 0)
        [player1 setVolume:[player1 volume] - 0.05];
        [player2 setVolume:[player2 volume] + 0.05];

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC),dispatch_get_main_queue(),^
            [self crossFadePlayerOne:player1 andPlayerTwo:player2 withCompletion:completion];
        );

    else
        if(completion)
            completion();
        
    

【讨论】:

【参考方案2】:

这不是交叉淡入淡出操作。 但是,这可以通过使用 NSOperation 的新线程一次淡入/淡出一个,并且可以以任何顺序将淡入/淡出效果添加到线程中。

我发现了由 Andrew Mackenzie-Ross 在 2010 年 11 月 30 日创建的线性渐变对象 MXAudioPlayerFadeOperation。 mackross.net.

MXAudioPlayerFadeOperation.h

#import <Foundation/Foundation.h>

@class AVAudioPlayer;
@interface MXAudioPlayerFadeOperation : NSOperation 
    AVAudioPlayer *_audioPlayer;
    NSTimeInterval _fadeDuration;
    NSTimeInterval _delay;
    float _finishVolume;
    BOOL _pauseAfterFade;
    BOOL _stopAfterFade;
    BOOL _playBeforeFade;


// The AVAudioPlayer that the volume fade will be applied to.
// Retained until the fade is completed.
// Must be set with init method.
@property (nonatomic, strong, readonly) AVAudioPlayer *audioPlayer;

// The duration of the volume fade.
// Default value is 1.0
@property (nonatomic, assign) NSTimeInterval fadeDuration;

// The delay before the volume fade begins.
// Default value is 0.0
@property (nonatomic, assign) NSTimeInterval delay;

// The volume that will be faded to.
// Default value is 0.0
@property (nonatomic, assign) float finishVolume;

// If YES, audio player will be sent a pause message when the fade has completed.
// Default value is NO, however, if finishVolume is 0.0, default is YES
@property (nonatomic, assign) BOOL pauseAfterFade;

// If YES, when the fade has completed the audio player will be sent a stop message.
// Default value is NO.
@property (nonatomic, assign) BOOL stopAfterFade;

// If YES, audio player will be sent a play message after the delay.
// Default value is YES.
@property (nonatomic, assign) BOOL playBeforeFade;

// Init Methods
- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume overDuration:(NSTimeInterval)duration withDelay:(NSTimeInterval)timeDelay;
- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume overDuration:(NSTimeInterval)duration;
- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume;
- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player;

@end

MXAudioPlayerFadeOperation.m

#import "MXAudioPlayerFadeOperation.h"
#import <AVFoundation/AVFoundation.h>

#define SKVolumeChangesPerSecond 15

@interface MXAudioPlayerFadeOperation ()

- (void)beginFadeOperation;
- (void)finishFadeOperation;
@end

@implementation MXAudioPlayerFadeOperation
#pragma mark -
#pragma mark Properties
@synthesize audioPlayer = _audioPlayer;
@synthesize fadeDuration = _fadeDuration;
@synthesize finishVolume = _finishVolume;
@synthesize playBeforeFade = _playBeforeFade;
@synthesize pauseAfterFade = _pauseAfterFade;
@synthesize stopAfterFade = _stopAfterFade;
@synthesize delay = _delay;

#pragma mark -
#pragma mark Accessors
- (AVAudioPlayer *)audioPlayer 
    AVAudioPlayer *result;
    @synchronized(self) 
        result = _audioPlayer;
    
    return result;


- (void)setAudioPlayer:(AVAudioPlayer *)anAudioPlayer 
    @synchronized(self) 
        if (_audioPlayer != anAudioPlayer) 
            _audioPlayer = nil;
            _audioPlayer = anAudioPlayer;
        
    


#pragma mark -
#pragma mark NSOperation
-(id) initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume overDuration:(NSTimeInterval)duration withDelay:(NSTimeInterval)timeDelay 
    if (self = [super init]) 
        self.audioPlayer = player;
        [player prepareToPlay];
        _fadeDuration = duration;
        _finishVolume = volume;
        _playBeforeFade = YES;
        _stopAfterFade = NO;
        _pauseAfterFade = (volume == 0.0) ? YES : NO;
        _delay = timeDelay;
    
    return self;


- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume overDuration:(NSTimeInterval)duration 
    return [self initFadeWithAudioPlayer:player toVolume:volume overDuration:duration withDelay:0.0];


- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume 
    return [self initFadeWithAudioPlayer:player toVolume:volume overDuration:1.0];


- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player 
    return [self initFadeWithAudioPlayer:player toVolume:0.0];


- (id) init 
    NSLog(@"Failed to init class (%@) with AVAudioPlayer instance, use initFadeWithAudioPlayer:",[self class]);
    return nil;


- (void)main 
    @autoreleasepool 

        [NSThread sleepForTimeInterval:_delay];
        if ([self.audioPlayer isKindOfClass:[AVAudioPlayer class]]) 
            [self beginFadeOperation];
        
        else 
            NSLog(@"AudioPlayerFadeOperation began with invalid AVAudioPlayer");
        
    



- (void)beginFadeOperation 
    if (![self.audioPlayer isPlaying] && _playBeforeFade) [self.audioPlayer play];

    if (_fadeDuration != 0.0) 

        NSTimeInterval sleepInterval = (1.0 / SKVolumeChangesPerSecond);
        NSTimeInterval startTime = [[NSDate date] timeIntervalSinceReferenceDate];
        NSTimeInterval now = startTime;

        float startVolume = [self.audioPlayer volume];

        while (now < (startTime + _fadeDuration)) 
            float ratioOfFadeCompleted = (now - startTime)/_fadeDuration;
            float volume = (_finishVolume * ratioOfFadeCompleted) + (startVolume * (1-ratioOfFadeCompleted));
            [self.audioPlayer setVolume:volume];
            [NSThread sleepForTimeInterval:sleepInterval];
            now = [[NSDate date] timeIntervalSinceReferenceDate];
        

        [self.audioPlayer setVolume:_finishVolume];
        [self finishFadeOperation];
    
    else 
        [self.audioPlayer setVolume:_finishVolume];
        [self finishFadeOperation];
    


- (void)finishFadeOperation 
    if ([self.audioPlayer isPlaying] && _pauseAfterFade) [self.audioPlayer pause];
    if ([self.audioPlayer isPlaying] && _stopAfterFade) [self.audioPlayer stop];


@end

带有使用 AudioManagerController 的示例

AudioManagerController.h

/*
 * Hedgewars-ios, a Hedgewars port for iOS devices
 * Copyright (c) 2009-2011 Vittorio Giovara <vittorio.giovara@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * File created on 23/09/2011.
 */


#import <Foundation/Foundation.h>


@interface AudioManagerController : NSObject 



+(void) playBackgroundMusic;
+(void) pauseBackgroundMusic;
+(void) stopBackgroundMusic;

+(void) fadeInBackgroundMusic;
+(void) fadeOutBackgroundMusic;

+(void) playClickSound;
+(void) playBackSound;
+(void) playSelectSound;

+(void) releaseCache;

@end

AudioManagerController.m

/*
 * Hedgewars-iOS, a Hedgewars port for iOS devices
 * Copyright (c) 2009-2011 Vittorio Giovara <vittorio.giovara@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * File created on 23/09/2011.
 */


#import "AudioManagerController.h"
#import "AVFoundation/AVAudioPlayer.h"
#import <AudioToolbox/AudioToolbox.h>
#import "MXAudioPlayerFadeOperation.h"

static AVAudioPlayer *backgroundMusic = nil;
static SystemSoundID clickSound = -1;
static SystemSoundID backSound = -1;
static SystemSoundID selSound = -1;

static NSOperationQueue *audioFaderQueue = nil;
static MXAudioPlayerFadeOperation *fadeIn = nil;
static MXAudioPlayerFadeOperation *fadeOut = nil;


@implementation AudioManagerController

#pragma mark -
#pragma mark background music control
+(void) loadBackgroundMusic 
    NSString *musicString = [[NSBundle mainBundle] pathForResource:@"hwclassic" ofType:@"mp3"];
    backgroundMusic = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:musicString] error:nil];

    backgroundMusic.delegate = nil;
    backgroundMusic.volume = 0;
    backgroundMusic.numberOfLoops = -1;
    [backgroundMusic prepareToPlay];


+(void) playBackgroundMusic 
    if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"music"] boolValue] == NO)
        return;

    if (backgroundMusic == nil)
        [AudioManagerController loadBackgroundMusic];

    backgroundMusic.volume = 0.45f;
    [backgroundMusic play];


+(void) pauseBackgroundMusic 
    [backgroundMusic pause];


+(void) stopBackgroundMusic 
    [backgroundMusic stop];


+(void) fadeOutBackgroundMusic 
    if (audioFaderQueue == nil)
        audioFaderQueue = [[NSOperationQueue alloc] init];
    if (backgroundMusic == nil)
        [AudioManagerController loadBackgroundMusic];
    if (fadeOut == nil)
        fadeOut = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:backgroundMusic toVolume:0.0 overDuration:3.0];

    [audioFaderQueue addOperation:fadeOut];


+(void) fadeInBackgroundMusic 
    if (audioFaderQueue == nil)
        audioFaderQueue = [[NSOperationQueue alloc] init];
    if (backgroundMusic == nil)
        [AudioManagerController loadBackgroundMusic];
    if (fadeIn == nil)
        fadeIn = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:backgroundMusic toVolume:0.45 overDuration:2.0];

    [audioFaderQueue addOperation:fadeIn];


#pragma mark -
#pragma mark sound effects control
+(SystemSoundID) loadSound:(NSString *)snd 
    // get the filename of the sound file:
    NSString *path = [NSString stringWithFormat:@"%@/%@",[[NSBundle mainBundle] resourcePath],snd];

    // declare a system sound id and get a URL for the sound file
    SystemSoundID soundID;
    NSURL *filePath = [NSURL fileURLWithPath:path isDirectory:NO];

    // use audio sevices to create and play the sound
    AudioServicesCreateSystemSoundID((CFURLRef)filePath, &soundID);
    return soundID;


+(void) playClickSound 
    if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"sound"] boolValue] == NO)
        return;

    if (clickSound == -1)
        clickSound = [AudioManagerController loadSound:@"clickSound.wav"];

    AudioServicesPlaySystemSound(clickSound);


+(void) playBackSound 
    if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"sound"] boolValue] == NO)
        return;

    if (backSound == -1)
        backSound = [AudioManagerController loadSound:@"backSound.wav"];

    AudioServicesPlaySystemSound(backSound);


+(void) playSelectSound 
    if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"sound"] boolValue] == NO)
        return;

    if (selSound == -1)
        selSound = [AudioManagerController loadSound:@"selSound.wav"];

    AudioServicesPlaySystemSound(selSound);


#pragma mark -
#pragma mark memory management
+(void) releaseCache 
    [backgroundMusic stop];
    [backgroundMusic release], backgroundMusic = nil;
    [fadeOut release], fadeOut = nil;
    [fadeIn release], fadeIn = nil;
    [audioFaderQueue release], audioFaderQueue = nil;
    AudioServicesDisposeSystemSoundID(clickSound), clickSound = -1;
    AudioServicesDisposeSystemSoundID(backSound), backSound = -1;
    AudioServicesDisposeSystemSoundID(selSound), selSound = -1;
    MSG_MEMCLEAN();


@end

参考:hedgewars

【讨论】:

我用swift重写了MXAudioPlayerFadeOperation gist.github.com/chunkyguy/be6c98b30722dac3af2116c469885a5e

以上是关于如何在 AVAudioPlayers 之间交叉淡入淡出的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL中的交叉淡入淡出场景

AVMutableVideoComposition 中的交叉淡入淡出

Audiokit 交叉淡入淡出声音循环

looper.js 插件 - 交叉淡入淡出动画之间不可见的图像

如何控制两个 UIImage 的无动画之间的淡入淡出?

为啥这个 CSS @keyframes 规则不交叉淡入淡出?