3

I am in the process of developing a game for iOS 9+ using Sprite Kit and preferably using Swift libraries.

Currently I'm using a Singleton where I preload my audio files, each connected to a separate instance of AVAudioPlayer.

Here's a short code-snipped to get the idea:

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.")
            }
        }

    }

}

This Singleton is initialised during app start-up. In the game-loop I then just call the following line to play a specific audio file:

AudioEngine.sharedInstance.sfxPing.play()

This basically works, but I always get glitches when a file is played and the frame rate drops from 60.0 to 56.0 on my iPad Air.

Someone any idea how to fix this performance issue with AVAudioPlayer ?

I also watched out for 3rd party libraries, namely:

Requirements:

  • Play a lot of very short samples (like shots, hits, etc..)
  • Play some motor effects (thus pitching would be nice)
  • Play some background / ambient sound in a loop
  • NO nasty glitches / frame rate drops !

Could you recommend any of the above mentioned libraries for my requirements or point out the problems using the above code ?

UPDATE:

Playing short sounds with:

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

does indeed improve the frame rate. I exported the audio files with Audiacity to the .caf format (Apple's Core Audio Format). But in the tutorial, they export with "Signed 32-bit PCM" encoding which led to disturbed audio playback in my case. Using any of the other encoding options (32-bit float, U-Law, A-Law, etc..) worked fine for me.

Why using caf format? Because it's uncompressed and thus loaded faster into memory with less CPU overhead compared to compressed formats like m4a. For short sound effects played a lot in short intervals, this makes sense and disk usage is not affected much for short audio files consuming few kilobytes. For bigger audio files, like ambient and background music, using compressed formats (mp3, m4a) is obviously the better choice.

salocinx
  • 3,715
  • 8
  • 61
  • 110
  • http://stackoverflow.com/questions/25646583/massive-fps-drops-when-avaudioplayer-ist-triggered-repeatedly-sprite-kit this might help :) – Konsy Jul 20 '16 at 14:36
  • @Konsy: thanks for your comment. I know this link, since I posted an answer by myself ;-) but my problem is not solvable by looping. this is good for sound effects like a motor or a rocket thrust, but my sound effects are played repeatedly without a precise pattern like shots fired by the user.. any further ideas and/or recommendations to the listed frameworks in the question ? – salocinx Jul 20 '16 at 15:19
  • How about using Sprite Kit's methods? Like `SKAction.playSoundFileNamed`. I didn't test myself but I suppose it's optimized for not killing the frame rate. Could be worth trying. – Eric Aya Jul 20 '16 at 15:27
  • @Eric D: It looks like that with `SKAction.playSoundFileNamed` you don't even have control about the volume... But I'll give it a try to check the performance. – salocinx Jul 20 '16 at 15:48
  • @salocinx Were you able to find a fix for repeatedly played sound effects like shots fired by the user? – Giorgio May 13 '20 at 12:56
  • 1
    @Giorgio Sorry, I cannot remember. Long ago since then. Aren‘t these issues fixed in the newer SDKs meanwhile?? – salocinx May 13 '20 at 13:33
  • @salocinx Well I’m able to play repeating sound by running a constant which holds SKAction.playSoundFileNamed... but it just cuts out some of the time so it’s not reliable. – Giorgio May 13 '20 at 13:46
  • 1
    @Giorgio I am really sorry I can't help you. Too long ago. I only remember that I also had to implement all kind of nasty hacks. I believe storing the play length of the effects in seconds and then blocking my play() function as long as playing the file before playing another... Stuff like that :-/ – salocinx May 13 '20 at 14:19
  • @salocinx No problem. Thanks for trying to help! – Giorgio May 13 '20 at 14:44

1 Answers1

3

According to your question, if you develop a game for iOS 9+, you can use the new iOS 9 library SKAudioNode (official Apple doc):

var backgroundMusic: SKAudioNode!

For example you can add this to didMoveToView():

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

You can also use to play a simple effect:

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

Finally, if you want to change the volume:

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

Update:

I see you have update your question speaking about AVAudioPlayer and SKAction. I've tested both of them for my iOS8+ compatible games.

About AVAudioPlayer, I personally use a custom library maked by me based from the old SKTAudio.

I see your code, about AVAudioPlayer init, and my code is different because I use:

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

I don't know if fileTypeHint make the difference, so try and fill me about your test.

Advantages about your code: With a shared instance audio manager based to AVAudioPlayer you can control volume, use your manager wherever you want, ensure compatibility with iOS8

Disadvantages about your code: Everytime you play a sound and you want to play another sound, the previous is broken, especially if you have launch a background music.

How to solve? According with this SO post to work well without issues seems AVFoundation is limited to 4 AVAudioPlayer properties instantiables, so you can do this:

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

You could build a method that switch through the 3 soundEffect to see if is occupied:

if player.playing

and use the next free player. With this workaround you have always your sound played correctly, even your background music.

Community
  • 1
  • 1
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • Thank you very much for your answer. I'll check out this approach immediately and if I don't run into performance issues and if it satisfies my requirements entirely, I will check your answer as solution. – salocinx Jul 20 '16 at 18:54
  • 1
    I checked your answer as solution and updated my original question. Thanks. – salocinx Jul 27 '16 at 10:10
  • Ok , you have choice the same way I adopt because my game work from iOS 8+, I will try to give you an answer as soon.. – Alessandro Ornano Jul 27 '16 at 10:15
  • Okay great - thanks. I just updated the question, please check it out under "update" ... – salocinx Jul 27 '16 at 10:16
  • Right, I've update my answer , let me know informations about your new tests – Alessandro Ornano Jul 27 '16 at 10:35
  • 1
    Thanks for the update. I need to work on other parts of my game with higher priority for the moment, since my partner can not work on the game maps otherwise. But this audio issue will come back on the table, for sure... I will report my findings to this thread as soon as I get back to this topic. – salocinx Jul 27 '16 at 17:28