如何在 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 之间交叉淡入淡出的主要内容,如果未能解决你的问题,请参考以下文章
AVMutableVideoComposition 中的交叉淡入淡出