AVAudioPlayer首次播放声音时启动缓慢

43

我正在尝试通过iPhone上的AVAudioPlayer播放一个非常短的音频文件(少于2秒),并消除启动延迟。

首先,是代码:

NSString *audioFile = [NSString stringWithFormat:@"%@/%@.caf", [[NSBundle mainBundle] resourcePath], @"audiofile"];
NSData *audioData = [NSData dataWithContentsOfMappedFile:audioFile];

NSError *err;
AVAudioPlayer *audioPlayer = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithData:audioData error:&err];

audioPlayer.delegate = self;
[audioPlayer play];

我还实现了audioPlayerDidFinishPlaying方法,在完成后释放AVAudioPlayer。

第一次播放音频时,有明显的延迟 - 至少2秒钟。然而,之后声音就立即播放了。我猜测罪魁祸首是[NSData dataWithContentsOfMappedFile]最初从闪存中读取需要很长时间,但对于后续的读取速度较快。不过我不确定如何进行测试。

这是否属实?如果是,那么我应该预缓存NSData对象并在低内存条件下积极清除它们吗?


感谢提供的信息。您应该回答问题并接受您的答案。在启动时初始化AVAudioPlayer可以确保在应用程序的其余部分中没有延迟播放音频。 - brianegge
1
@johnbiesnecker 播放任何音频时请将音量设置为0,然后所有的播放音频调用都应该放在一个异步全局队列块中,使用默认或高优先级。请勿在主队列中播放音频。 - Juan Boero
1
除了@JuanPabloBoero所说的之外,当接收到UIApplicationWillEnterForegroundNotification时,我还会播放0音量的声音,否则在应用程序从后台恢复后会有延迟。 - Pang
@Pang 哇,我错过了那个,非常有趣的观点。 - Juan Boero
9个回答

41

延迟似乎与首次实例化AVAudioPlayer有关。如果我加载任何音频,运行[audioPlayer prepareToPlay]然后立即释放它,所有其他音频的加载时间非常接近无感知。所以现在我在applicationDidFinishLaunching中完成这个操作,一切都运行良好。

我在文档中找不到任何相关信息,但它确实是事实。


1
哦,我对这个不太了解。prepareToPlay 对我来说并没有完全起作用。虽然 prepareToPlay 稍微有点用处,但由于它似乎是异步加载音频,所以无法准确知道何时音频真正准备好可以播放。请参见我的最新答案,其中提供了我想出的解决方案。 - Taylor Gautier
每当我首次将资产加载到AVQueuePlayer中时,也会出现延迟。播放器只初始化一次。看起来是一个常见的问题: https://dev59.com/S-o6XIcBkEYKwwoYPSL7 - Oren
它对我来说非常完美,现在我的初始化器在其中一个声音上运行prepareToPlay(),我不再有延迟了。虽然不太好看,但它可用。 - Joel

12

这是我所做的(在一个单独的线程中):

[audioplayer start]
[audioplayer stop]
self.audioplayer = audioplayer
似乎是一个异步方法,因此当它返回时,你无法确定音频是否已准备好播放。 在我的情况下,我调用了start来实际开始播放-这似乎是同步的。然后我立即停止它。在模拟器上,我听不到任何声音从这个活动中传出。现在,声音真正准备好播放了,我将本地变量分配给成员变量,以便线程外的代码可以访问它。
我必须说,即使在iOS 4上,加载一个只有4秒长度的音频文件也需要大约2秒钟,这让我感到有些惊讶...

8

如果您的音频长度不超过30秒,格式为线性PCM或IMA4,并且打包为.caf、.wav或.aiff文件,则可以使用系统声音:

导入AudioToolbox框架

在您的.h文件中创建此变量:

SystemSoundID mySound;

在您的.m文件中,在init方法中实现它:
-(id)init{
if (self) {
    //Get path of VICTORY.WAV <-- the sound file in your bundle
    NSString* soundPath = [[NSBundle mainBundle] pathForResource:@"VICTORY" ofType:@"WAV"];
    //If the file is in the bundle
    if (soundPath) {
        //Create a file URL with this path
        NSURL* soundURL = [NSURL fileURLWithPath:soundPath];

        //Register sound file located at that URL as a system sound
        OSStatus err = AudioServicesCreateSystemSoundID((CFURLRef)soundURL, &mySound);

            if (err != kAudioServicesNoError) {
                NSLog(@"Could not load %@, error code: %ld", soundURL, err);
            }
        }
    }
return self;
}

在您的IBAction方法中,您使用以下代码调用声音:
AudioServicesPlaySystemSound(mySound);

这对我有用,可以在按下按钮时播放非常接近的声音。希望这能帮助你。

这是一个很棒的答案。感谢您发布它! - SushiGrass Jacob

5
我采用了一种适合我的替代方法。我曾在其他地方见过提到这种技术(但我现在不记得是在哪里看到的)。
简而言之,在您执行任何其他操作之前,请通过加载和播放一个短的“空白”声音来进行预检测音频系统。对于我在视图控制器的viewDidLoad方法中预加载的0.1秒持续时间的短mp3文件,代码如下:
   NSError* error = nil;
   NSString* soundfilePath = [[NSBundle mainBundle] pathForResource:@"point1sec" ofType:@"mp3"];
   NSURL* soundfileURL = [NSURL fileURLWithPath:soundfilePath];
   AVAudioPlayer* player = [[[AVAudioPlayer alloc] initWithContentsOfURL:soundfileURL error:&error] autorelease];
   [player play];  

如果你想要自己制作空白的mp3文件,可以创建一个;或者在Google上搜索“空白的mp3文件”来查找并下载其他人已经建立的文件。


启用NSZombies后,使用此功能会在dealloc后向播放器发送“impl”消息。我通过不使用autorelease来避免这种情况,并在audioPlayerDidFinishPlaying:successfully:事件(<AVAudioPlayerDelegate>回调中)手动发送[player release]消息来解决这个问题。 - unsynchronized
根据上一条评论,请查看我对此问题的更新答案,其中包含源代码解决方案:https://dev59.com/WFvUa4cB1Zd3GeqPrkCj#7426406 - unsynchronized

3
这是一个简单的Swift扩展,用于AVAudioPlayer,并使用先静音后播放的想法,在之前的答案中提出。prepareToPlay()对我来说不起作用。
extension AVAudioPlayer {
    private func initDelaylessPlayback() {
        volume = 0
        play()
        stop()
        volume = 1
    }

    convenience init(contentsOfWithoutDelay : URL) throws {
        try self.init(contentsOf: contentsOfWithoutDelay, fileTypeHint: nil)
        initDelaylessPlayback()
    }
}

3

我为AVAudioPlayer编写了一个简单的包装器,提供了一个真正起作用的prepareToPlay方法:

https://github.com/nicklockwood/SoundManager

它基本上只是扫描您的应用程序以查找声音文件,选择一个并将其音量设置为零进行播放。这将初始化音频硬件并允许下一个声音立即播放。

0

另一个解决方法是创建一个短而无声的音频,并在第一次启动播放器时播放它。

  1. 在系统偏好设置中将麦克风级别设置为0。
  2. 打开QuickTime。
  3. 创建新的音频录制(文件>新的音频录制)。
  4. 录制1或2秒钟。
  5. 保存/导出音频文件。(它将具有.m4a扩展名,大小约为2 kb)。
  6. 将文件添加到您的项目并播放它。

如果您不想自己创建新的声音文件,可以在此处下载一个短而无声的音频文件:https://www.dropbox.com/s/3u45x9v72ic70tk/silent.m4a?dl=0


-1

答案是

AVAudioPlayer *audioplayer;
[audioplayer prepareToPlay];

2
实际上,[audioplayer prepareToPlay] 不是答案... 我所说的延迟发生在你分配和初始化 AVAudioPlayer 对象的第一次调用之前,在你调用它的任何方法之前。这只会在你第一次加载一个对象时发生。prepareToPlay 只是预加载音频,但并不能消除我实际上询问的延迟。 顺便说一下,UIWebView 也表现出相同的行为——如果你分配然后立即释放一个,其余的加载非常快。 - John Biesnecker

-1

我不确定,但我怀疑NSData对象会懒加载文件内容。你可以尝试“欺骗”它,通过在早期调用[audioData getBytes:someBuffer length:1];来提前加载该文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接