【问题标题】:Slow start for AVAudioPlayer the first time a sound is played第一次播放声音时 AVAudioPlayer 启动缓慢
【发布时间】:2010-10-28 09:05:59
【问题描述】:

我正在尝试通过 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 可确保在应用程序的其余部分中无延迟地播放音频。
  • @johnbiesnecker 播放任何音量为 0 的声音,然后所有声音播放调用都应在异步全局队列块内,具有默认或高优先级,不应在主队列中播放声音。
  • 除了@JuanPabloBoero说的,我收到UIApplicationWillEnterForegroundNotification的时候也播放了0音量的声音,不然后台恢复app会有延迟。
  • @Pang 哇,我错过了,非常有趣的一点。

标签: iphone caching audio nsdata avaudioplayer


【解决方案1】:

我不确定,但我怀疑 NSData 对象是懒惰的并按需加载文件的内容。您可以尝试“作弊”,在早期调用[audioData getBytes:someBuffer length:1];,让它在需要之前加载该文件。

【讨论】:

    【解决方案2】:

    延迟似乎与第一次实例化 AVAudioPlayer 有关。如果我加载任何音频,运行 [audioPlayer prepareToPlay] 然后立即释放它,我所有其他音频的加载时间非常接近于难以察觉。所以现在我在 applicationDidFinishLaunching 中执行此操作,其他一切都运行良好。

    我在文档中找不到任何关于此的内容,但似乎确实如此。

    【讨论】:

    • 嗯,我不知道这个。 prepareToPlay 并不完全适合我。 prepareToPlay 有点工作,但由于它似乎是异步加载音频,因此无法准确知道音频何时真正准备好播放。有关我想出的解决方案,请参阅我最近的答案。
    • 每当我第一次将资产加载到其中时,AVQueuePlayer 也会出现延迟。播放器只被初始化一次。似乎是一个常见问题:stackoverflow.com/questions/11171374/…
    • It 非常适合我,现在我的初始化程序在其中一个声音上运行 prepareToPlay() 并且我不再有延迟。它不漂亮,但它有效。
    【解决方案3】:

    答案是

    AVAudioPlayer *audioplayer;
    [audioplayer prepareToPlay];
    

    【讨论】:

    • 实际上,[audioplayer prepareToPlay] 不是答案……我所说的延迟发生在您第一次分配和初始化 AVAudioPlayer 对象时,然后才调用它的任何方法。不过,它只会在您第一次加载时发生。 prepareToPlay 只是预加载音频,但并没有消除我实际询问的延迟。顺便说一句,UIWebView 表现出相同的行为——如果你分配然后立即释放一个,其余的加载非常快。
    【解决方案4】:

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

    [audioplayer start]
    [audioplayer stop]
    self.audioplayer = audioplayer
    

    [audioplayer prepareToPlay] 似乎是一个异步方法,因此如果音频实际上可以播放,您无法确定它何时返回。

    在我的情况下,我调用 start 来实际开始播放 - 这似乎是同步的。然后我立即停止。无论如何,在模拟器中,我没有听到此活动发出任何声音。现在声音“真的”可以播放了,我将局部变量分配给一个成员变量,以便线程外的代码可以访问它。

    我必须说,即使在 iOS 4 上,加载一个长度只有 4 秒的音频文件也需要大约 2 秒,这让我感到有些惊讶......

    【讨论】:

      【解决方案5】:

      我采用了另一种适合我的方法。我在其他地方看到过这种技术(但我现在不记得那是在哪里)。

      简而言之,在执行任何其他操作之前,通过加载并播放一个简短的“空白”声音来预检音响系统。对于我在视图控制器的viewDidLoad 方法中预加载的一个短 mp3 文件的代码看起来像这样,持续时间为 0.1 秒:

         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,或者在“空白 mp3”上进行谷歌搜索以找到并下载其他人已经构建的。

      【讨论】:

      • 在启用 NSZombies 的情况下使用它会显示在 dealloc 后发送给玩家的“impl”消息。我通过不自动释放它来避免这种情况,而是在 audioPlayerDidFinishPlaying:successfully: 事件(在 回调中)中手动向它发送 [player release] 消息
      • 最后的评论,请参阅我对此问题的更新答案以获取源代码解决方案:stackoverflow.com/a/7426406/830899
      【解决方案6】:

      如果您的音频长度小于 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);
      

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

      【讨论】:

      • 这是一个很好的答案。感谢您发布此消息!
      【解决方案7】:

      我为 AVAudioPlayer 编写了一个简单的包装器,它提供了一个实际有效的 prepareToPlay 方法:

      https://github.com/nicklockwood/SoundManager

      它基本上只是扫描您的应用程序中的声音文件,选择一个并以零音量播放。这会初始化音频硬件并允许立即播放下一个声音。

      【讨论】:

        【解决方案8】:

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

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

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

        【讨论】:

          【解决方案9】:

          这是 AVAudioPlayer 的一个简单的 Swift 扩展,它使用了先前答案中提出的在 0 音量处播放和停止的想法。 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()
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-08-20
            • 1970-01-01
            • 1970-01-01
            • 2015-12-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多