2

这是我现在正在使用的代码,它不起作用(当我按下调用此方法的按钮时没有任何反应)。以前,我有一个用于 audioPlayer 的属性并且它可以工作(下面的所有 audioPlayer 显然都是 self.audioPlayer)。问题是当我尝试播放两次声音时,它会结束第一个声音的播放。

这不好,因为我正在制作音板并希望声音能够重叠。我以为我可以让 audioPlayer 成为局部变量而不是属性,一切都会好起来的,但现在声音根本不起作用,我不知道为什么。在我为 AVAudioPlayer 找到的所有教程中,都创建了一个属性,但没有人解释原因。如果这不起作用,我有什么替代方法可以发出可以重叠的声音?

- (void)loadSound:(NSString *)sound ofType:(NSString *)type withDelegate:(BOOL)delegate {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
                                     pathForResource:sound
                                     ofType:type]];

AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (delegate) audioPlayer.delegate = self;
[audioPlayer prepareToPlay];

[audioPlayer play];
}
4

2 回答 2

8

您需要属性或 ivar 的原因是因为它提供了强大的参考。使用 ARC 时,任何没有指向它的强指针的对象都是释放的公平游戏,事实上这就是您所看到的。

你也是正确的,一个AVAudioPlayer强指针一次只允许一个音频播放器被引用。

如果您选择继续使用,解决方案AVAudioPlayer是使用某种集合对象来持有对所有播放器实例的强引用。您可以使用NSMutableArray如下所示的:

编辑我稍微调整了代码,所以播放声音的方法需要一个NSString soundName参数。

@synthesize audioPlayers = _audioPlayers;
-(NSMutableArray *)audioPlayers{
    if (!_audioPlayers){
        _audioPlayers = [[NSMutableArray alloc] init];
    }
    return _audioPlayers;
}
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    [self.audioPlayers removeObject:player];
}
-(void)playSoundNamed:(NSString *)soundName{
    NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
                                         pathForResource:soundName
                                         ofType:@"wav"]];
    AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
    if (audioPlayer){
        [audioPlayer setDelegate:self];
        [audioPlayer prepareToPlay];
        [audioPlayer play];
        [self.audioPlayers addObject:audioPlayer];
    }
}

通常,AVAudioPlayer对于音效/音板应用程序来说,这是过度杀伤力。对于快速的声音“下降”,您可能会找到音频工具箱框架,如我对这个问题的回答中所述。

通过查看 System Sound 类参考,您似乎一次只能播放一种声音。

它一次只能播放一个SystemSoundID。例如,如果您有 soundOne 和 soundTwo。您可以在 soundTwo 播放时播放 soundOne,但一次不能播放多个声音的一个实例。

能够播放可以重叠的声音同时仍然有效地利用代码和内存量的最佳方式是什么?

最好的是意见。

如果您需要同时播放相同声音的两个实例,那么我会说此答案中发布的代码将是要使用的代码。由于同一声音的每个重叠实例都需要创建一个新资源,这样的代码audioPlayerDidFinishPlaying:更易于管理(内存可以很容易地回收)。

如果相同声音的重叠实例不会破坏交易,那么我认为仅使用AudioServicesCreateSystemSoundID()为每个声音创建一个实例会更有效。

我绝对不会尝试SystemSoundID通过每次按下按钮来管理 s 的创建和处置。那会很快出错。在那种情况下AVAudioPlayer,仅在可维护性方面显然是赢家。

于 2012-11-21T04:36:43.597 回答
5

我假设您正在使用 ARC。音频播放器不起作用的原因是因为对象正在被释放,然后在方法终止AVAudioPlayer后随后被销毁。loadSound:这是由于 ARC 的对象管理而发生的。在ARC之前,代码:

AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (delegate) audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
[audioPlayer play];

会按预期播放声音。但是,在方法终止AVAudioPlayer后,该对象仍将存在很长时间。loadSound:这意味着每次播放声音时,都会泄漏内存。

关于属性的一些事情

引入属性是为了减少开发人员必须编写和维护的代码量。在属性之前,开发人员必须为每个实例变量手动编写 setter 和 getter。这是很多冗余代码。属性的一个幸运的副作用是它们处理了为基于对象的实例变量编写 setter/getter 所需的大量内存管理代码。这意味着许多开发人员开始专门使用属性,即使对于不需要公开的变量也是如此。

由于 ARC 为您处理所有内存管理细节,因此属性应仅用于其原始目的,从而减少冗余代码的数量。传统的 iVars 默认会被强引用,这意味着一个简单的赋值比如: title = [NSString stringWithFormat:@"hello"]; 本质上和代码一样: self.title = [NSString stringWithFormat:@"hello"];

好的,回到你的问题。

如果要AVAudioPlayer在方法中创建实例,则loadSound:需要对每个AVAudioPlayer实例保持强引用,否则 ARC 会破坏它。我建议将新创建的AVAudioPlayer对象添加到NSMutableArray数组中。如果采用AVAudioPlayerDelegate协议,则可以实现该audioPlayerDidFinishPlaying:successfully:方法。您可以在其中从数组中删除AVAudioPlayer对象,让 ARC 知道可以销毁该对象。

于 2012-11-21T04:59:13.163 回答