iOS: 如何在不掉帧的情况下播放音频?

iOS: How to play audio without fps drops?

我正在使用 Sprite Kit 并最好使用 Swift 库为 iOS 9+ 开发游戏。

目前我正在使用 Singleton 预加载我的音频文件,每个文件都连接到一个单独的 AVAudioPlayer 实例。

这里有一个简短的代码片段来理解这个想法:

import SpriteKit
import AudioToolbox
import AVFoundation

class AudioEngine {

    static let sharedInstance = AudioEngine()

    internal var sfxPing: AVAudioPlayer

    private init() {

        self.sfxPing = AVAudioPlayer()

        if let path = NSBundle.mainBundle().pathForResource("ping", ofType: "m4a") {
            do {
                let url = NSURL(fileURLWithPath:path)
                sfxPing = try AVAudioPlayer(contentsOfURL: url)
                sfxPing.prepareToPlay()
            } catch {
                print("ERROR: Can't load ping.m4a audio file.")
            }
        }

    }

}

此单例在应用程序启动期间初始化。在游戏循环中,我只需调用以下行来播放特定的音频文件:

AudioEngine.sharedInstance.sfxPing.play()

这基本上是可行的,但是我在 iPad Air 上播放文件并且帧率从 60.0 下降到 56.0 时总是出现故障。

有人知道如何解决 AVAudioPlayer 的这个性能问题吗?

我还留意了第 3 方库,即:

要求:

您能否根据我的要求推荐上述任何库或指出使用上述代码的问题?

更新:

播放短音:

self.runAction(SKAction.playSoundFileNamed("sfx.caf", waitForCompletion: false))

确实提高了帧率。 I exported the audio files with Audiacity to the .caf format (Apple's Core Audio Format). 但在教程中,它们使用“Signed 32-bit PCM”编码导出,这导致我的音频播放受到干扰。使用任何其他编码选项(32-bit floatU-LawA-Law 等)对我来说效果很好。

为什么使用caf格式?因为它是未压缩的,因此与 m4a 等压缩格式相比,它以更少的 CPU 开销更快地加载到内存中。对于在短时间内播放很多的短音效,这是有道理的,并且对于消耗几千字节的短音频文件,磁盘使用不会受到太大影响。对于较大的音频文件,如环境音乐和背景音乐,使用压缩格式 (mp3m4a) 显然是更好的选择。

根据您的问题,如果您为 iOS 9+ 开发游戏,您可以使用新的 iOS 9 库 SKAudioNode (official Apple doc):

var backgroundMusic: SKAudioNode!

例如,您可以将此添加到 didMoveToView():

if let musicURL = NSBundle.mainBundle().URLForResource("music", withExtension: "m4a") {
    backgroundMusic = SKAudioNode(URL: musicURL)
    addChild(backgroundMusic)
}

你也可以用来播放简单的效果:

let beep = SKAudioNode(fileNamed: "beep.wav")
beep.autoplayLooped = false
self.addChild(beep)

最后,如果你想改变音量:

beep.runAction(SKAction.changeVolumeTo(0.4, duration: 0))

更新:

我看到你更新了关于 AVAudioPlayerSKAction 的问题。我已经为我的 iOS8+ 兼容游戏测试了它们。

关于AVAudioPlayer,我个人使用我根据旧的SKTAudio.

制作的自定义库

我看到你的代码,关于 AVAudioPlayer init,我的代码是不同的,因为我使用:

@available(iOS 7.0, *)
    public init(contentsOfURL url: NSURL, fileTypeHint utiString: String?)

我不知道 fileTypeHint 是否有所作为,所以试着告诉我你的测试。

您的代码的优点: 使用基于 AVAudioPlayer 的共享实例音频管理器,您可以控制音量,随时随地使用您的管理器,确保与 iOS8

兼容

关于您的代码的缺点: 每次播放一个声音又想播放另一个声音,以前的就坏了,尤其是你开过背景音乐。

如何解决?根据这个 SO post 没有问题似乎 AVFoundation 仅限于 4 个 AVAudioPlayer 属性实例化,所以你可以这样做:

  • 1) backgroundMusicPlayer: AVAudioPlayer!
  • 2) soundEffectPlayer1: AVAudioPlayer!
  • 3) soundEffectPlayer2: AVAudioPlayer!
  • 4) soundEffectPlayer3: AVAudioPlayer!

你可以构建一个方法来切换 3 个音效,看看是否被占用:

if player.playing

并使用下一个免费播放器。使用此解决方法,您的声音始终可以正确播放,甚至是背景音乐。