I have a Simon-like memory game where a sequence of tones is played and the user attempts to repeat the tones. The problem is that I get a horrible crackly, popping sound from time to time. It could be after just a few notes or as many as twenty. The sounds are all wav files that are two seconds in length. I cycle through ten players so that none of the sounds are clipped. (I've tried up to 50 but that didn't help anything.) I have also tried implementing audioPlayerDidFinishPlaying so that I could stop the player once the sound is complete but that didn't help either. And finally, I added prepareToPlay() - sadly, no difference.
One interesting issue that may or may not be related is that after a sequence of about 15 notes, the audio stops working all together. The app continues like normal, but without any sound.
Here is the audio portion of the code:
func playSound(_ soundID: Int) {
var soundName = ""
switch soundID {
case 0:
soundName = "beep1"
case 1:
soundName = "beep2"
case 2:
soundName = "beep3"
case 3:
soundName = "beep4"
case 4:
soundName = "swish3"
default:
soundName = "clank"
}
guard let url = Bundle.main.url(forResource: soundName, withExtension: "wav") else { return }
do {
buttonSound.insert(try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue), at: playerIndex)
buttonSound[playerIndex].prepareToPlay()
buttonSound[playerIndex].delegate = self
if playerIndex < 10 {
buttonSound[playerIndex].play()
playerIndex += 1
} else {
buttonSound[playerIndex].play()
playerIndex = 0
}
} catch {
// error
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if flag {
player.stop()
}
}
/******** UPDATE *********/
I moved the AVAudioPlayer creation to its own class based on an outdated example I ran across...somewhere. It solved the audio completely cutting out after 15 tones problem, however, the cracks and pops are still there! I also tried rerecording the sounds with no luck.
Here is the class for creating AVAudioPlayers as needed:
import UIKit
import AVFoundation
private var players: [AVAudioPlayer] = []
class AVAudioPlayerPool: NSObject {
class func playerWithURL(url: URL) -> AVAudioPlayer? {
let availablePlayers = players.filter { (player) -> Bool in
return player.isPlaying == false && player.url == url
}
if let playerToUse = availablePlayers.first {
playerToUse.prepareToPlay()
return playerToUse
}
do {
let newPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue)
players.append(newPlayer)
newPlayer.prepareToPlay()
return newPlayer
} catch {
return nil
}
}
}
Playing a sound is simple:
func playSound(_ soundID: Int) {
var soundName = ""
switch soundID {
case 0:
soundName = "tt1"
case 1:
soundName = "tt2"
case 2:
soundName = "tt3"
case 3:
soundName = "tt4"
case 4:
soundName = "swish3"
default:
soundName = "clank"
}
guard let url = Bundle.main.url(forResource: soundName, withExtension: "wav") else { return }
let player = AVAudioPlayerPool.playerWithURL(url: url)
player?.play()
}
If anyone has any thoughts - even if it's just the next step you might try...
In my opinion, your main problem is that the sound loading is done inside the same function supposed to play the sound itself. Conceptually is wrong, in order to be able to play the sound as fast as you can, you should load once and for all your sounds outside the "play" function. That function should just play, and that's it.
We've just faced the problem in our team. In our case distorted sound bug can be reproduced only if the app is running on a device with debugger attached. Once we detach the app from debugger crackles disappear.
It also looks like the problem first appeared in new Xcode 9.2 (9C40b).
After many hours of experimentation, I'm going to say that the problem was caused by the sound I had chosen - a big, hollow, square wave synth sound with reverb. I first tried reducing the level thinking that might help. Next I tried all sorts of different sounds, but everything crackled. I think that had more to do with the way I was assigning players. Once I fixed the player problem, I didn't think to go back and try different sounds again. Until, I did...and it works!
Related
I have an app being used by people to receive orders with it needing to make a continuous sound until staff attend to it. It was working for two months then just started crashing a lot. For whatever reason, it runs fine on an iPad but not on iPhones running a recent operating system.
When this bit of code gets called it crashes:
guard let path = Bundle.main.path(forResource: "alert.mp3", ofType: nil) else { return }
let url = URL(fileURLWithPath: path)
do {
self.alertSoundEffect = try AVAudioPlayer(contentsOf: url)
} catch let err {
print("err: \(err)")
}
DispatchQueue.main.async {
self.alertSoundEffect.numberOfLoops = -1
self.alertSoundEffect.prepareToPlay()
self.alertSoundEffect.play()
}
The fix online to declare the alertSoundEffect variable like this:
private var alertSoundEffect : AVAudioPlayer!
has not worked at all.
I tried moving everything but the line:
self.alertSoundEffect.play()
to viewDidLoad as I thought maybe that code couldn't get called more than once, but it didn't help.
Specifically, the compiler highlights this line when it crashes:
self.alertSoundEffect = try AVAudioPlayer(contentsOf: url)
I tried using try AVAudioPlayer where it takes a Data object as a parameter or with including the type of audio file to be played, but that did not change anything.
When I try the AVAudioPlayer's delegate and declare it like this:
self.alertSoundEffect.delegate = self
right before the first lines of code I shared above Xcode highlights this line instead when it reliably crashes.
What else should I try?
I suppose your path is wrong.
Try this:
guard let path = Bundle.main.path(forResource: "alert", ofType: "mp3") else { return }
Also, if your audio file is short, like less than 30s, then try not to call self.alertSoundEffect.prepareToPlay(). Just call self.alertSoundEffect.play() right away.
Since iOS 13, this was causing a bug in my app, since I have notification sounds which are 3-10 seconds long.
If you initialise your AVAudioPlayer like var wrongMusicPlayer: AVAudioPlayer = AVAudioPlayer() OR wrongMusicPlayer = AVAudioPlayer() in any method then please remove it and just Declare like var wrongMusicPlayer: AVAudioPlayer!.
iOS 13.1 Crash in AVAudio Player
// My code is below
do{
file = try AKAudioFile(readFileName: "Sound1.mp3", baseDir: .resources)
// file = try AKAudioFile(forReading: SingletonClass.sharedInstance.recordedURLs[SingletonClass.sharedInstance.recordedURL]!)
// AKSettings.defaultToSpeaker = true
}
catch {
}
do {
player = try AKAudioPlayer(file : file)
}
catch {
}
let lfoAmplitude = 1_000
let lfoRate = 1.0 / 3.428
_ = 0.9
//filter section effect below
filterSectionEffect = AKOperationEffect(tracker) { input, _ in
let lfo = AKOperation.sineWave(frequency: lfoRate, amplitude: lfoAmplitude)
return input.moogLadderFilter(cutoffFrequency: lfo + cutoffFrequency,
resonance: resonance)
}
Audiokit.output = filterSectionEffect
Audiokit.start()
And whenever I play the audio using a button with code player.play , the audio gets played properly. And if I connect the headphones, it gets played properly as well but as soon as I disconnect the headphones, I see the error:
It happens in same way for both wired as well as bluetooth headphones.
My app with stuck because of this issue only that too happens only with AKOperationEffect. Any help would be appreciated.
The comment from Kunal Verma that this is fixed is correct, but just for completeness here is the commit that fixed it.
https://github.com/AudioKit/AudioKit/commit/ffac4acbe93553764f6095011e9bf5d71fdc88c2
All of my other audio files play perfectly through the audio player. Most of the files are less than 5 seconds long, and the longest file that still plays is 23 seconds long. For some reason my 1:15 long file "sortSong" is completely silent when it is called.
Here is the code for my audio player:
import Foundation
import AVFoundation
class AudioPlayer {
static let sharedInstance = AudioPlayer()
enum Sound: String {
case sortSongPlay = "sortSong"
case centerButtonRelease = "centerButtonRelease"
case buttonTap = "tapSound"
static var all: [Sound] {
return [.centerButtonRelease, .buttonTap, sortSongPlay]
}
var url: URL {
return URL.init(fileURLWithPath: Bundle.main.path(forResource: self.rawValue, ofType: "mp3")!)
}
}
private var soundMap = [String: SystemSoundID]()
init() {
for sound in Sound.all {
let soundUrl = sound.url
var soundId: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundId);
soundMap[sound.rawValue] = soundId
}
}
func play(sound: Sound) {
AudioServicesPlaySystemSound(soundMap[sound.rawValue]!)
}
}
The sound is played when this function is called in my view controller.
func successfulSort() {
AudioPlayer.sharedInstance.play(sound: .sortSongPlay)
rotateSortImageOne()
rotateSortImageTwo()
}
This is the action that calls the successfulSort func
if inputText == "hello" && seedArray == [1, 4, 1] {
successfulSort()
}
If I simply change the case sortSongPlay to = "shortSortSong" (the 23 second version) it plays just fine.
All of my sound files have their target memberships checked for this project file, and all of the files have the correct path. The audio will play in the interface builder if I press the play button. There are no compiler errors or warnings, the app never crashes, the audio for sortSong simply isn't playing when it is called in the app.
This is a link containing examples I have tried on my project. The first two sounds play silently in the app while the shorter sounds all play perfectly. https://soundcloud.com/spiffystache
What is causing this to be silent?
You should put a correct title on your question.
Your code does not use AVAudioPlayer, but uses System Sound Services.
The documentation clearly states its limitation:
You can use System Sound Services to play short (30 seconds or
shorter) sounds.
I have never checked if its limitation is exactly 30 seconds, but it matches the description in your question.
Consider using AVAudioPlayer (as in the title) to play longer sound.
I am doing following stuff to play a sound on button tap. But here, I guess it loads sound file every time I tap on button.
if let soundURL = NSBundle.mainBundle().URLForResource("notification", withExtension: "mp3") {
var mySound: SystemSoundID = 0;
AudioServicesCreateSystemSoundID(soundURL, &mySound);
AudioServicesPlaySystemSound(mySound);
}
What I am trying to do is to load above code once in AppDelegate and call below code from any other VC :
let systemSoundID: SystemSoundID = 0;
AudioServicesPlaySystemSound(systemSoundID);
each time I want a sound.
But it causes an error in console showing
Failure to setup sound, err = -50.
Any Solution?
Rather than add to AppDelegate, it may be neater to add a separate class. This works for me:
import AudioToolbox
class PlaySound {
static private var mySound:SystemSoundID = {
// Do it like this so mySound is initialised only when it is first used
var aSound:SystemSoundID = 1000 // a default sound in case we forget the sound file
if let soundURL = NSBundle.mainBundle().URLForResource("Detection", withExtension: "wav") {
AudioServicesCreateSystemSoundID(soundURL, &aSound)
print("Initialised aSound:\(aSound)")
} else {
print("You have forgotten to add your sound file to the app.")
}
return aSound // this value is put into mySound when mySound is first used.
}()
static func play() { AudioServicesPlaySystemSound(mySound) } // play the sound
static func prepare() -> SystemSoundID { return mySound } // call this to preload sound to avoid any delay on first use, if you want
}
Then whenever I need the sound, I write
PlaySound.play()
and that only sets up the sound once, the first time it is used. The print above shows just when, and how many times, it is initialized. If you want to avoid any possibility of delay in setting up the sound when it is first used, you can call
PlaySound.prepare()
in AppDelegate when the app is launched.
In iOS 8/Xcode 6 I had a function that included a sound effect. It no longer works in iOS 9 after changing the code multiple times. This is what I've tried:
Original:
let bangSoundEffect = SKAction.playSoundFileNamed("Bang.mp3", waitForCompletion: false)
runAction(bangSoundEffect)
Other attempt:
self.runAction(SKAction.playSoundFileNamed("Bang.mp3", waitForCompletion: false))
Also:
func playRocketExplosionSound(filename: String) {
let url = NSBundle.mainBundle().URLForResource(
filename, withExtension: nil)
if (url == nil) {
print("Could not find file: \(filename)")
return }
var error: NSError? = nil
do {
backgroundMusicPlayer =
try AVAudioPlayer(contentsOfURL: url!)
} catch let error1 as NSError {
error = error1
backgroundMusicPlayer = nil
}
if backgroundMusicPlayer == nil {
print("Could not create audio player: \(error!)")
return}
backgroundMusicPlayer.numberOfLoops = 1
backgroundMusicPlayer.prepareToPlay()
backgroundMusicPlayer.play() }
playRocketExplosionSound("Bang.mp3")
I'm pulling my hair out. I'm using the same code in a different scene for another sound effect and it works fine!! What's going wrong?
I've noticed that the sound effect begins to play sometimes in the simulator, however it doesn't complete and throws this error:
2015-09-24 19:12:14.554 APPNAME[4982:270835] 19:12:14.553 ERROR: 177: timed out after 0.012s (735 736); mMajorChangePending=0
It doesn't work at all on actual devices.
What is the problem? :'(
Possible problem with MP3 file
The problem is most likely connected with the MP3 file you're using. The code works for other sounds, this suggests that the MP3 file might be corrupted and AVAudioPlayer fails with decoding it. You can try re-encode this file and see if the problem persists. Or, even better, converting it to WAV.
Using WAVs
General rule of the thumb when creating short sound effects for games, is to use WAV unless you really feel you need the trim the fat.
Top-notch games are going for top-of-the-line production quality, so they record and produce assets uncompressed 24bit/48kHz. Titles with slightly lesser ambitions might record and produce in 16/44.1, which is the official standard for CD quality audio.
This has at least two benefits. One is that the sound has a better quality. Second one, the CPU does not have to decode the file to play it.
Corrupt data file | AVAudioPlayer out of scope
1. Corrupt data file
This will ensure you have found the file:
var backgroundMusicPlayer: AVAudioPlayer? = nil
if let url = Bundle.main.url(
forResource: "Bang", withExtension: "mp3") {
do {
try backgroundMusicPlayer = AVAudioPlayer(contentsOf: url)
backgroundMusicPlayer!.play()
} catch {}
}
return nil
2. AVAudioPlayer out of scope
The variable retaining backgroundMusicPlayer must not go out of scope before play() has completed and returns. This is generally achieved by using a class variable:
var backgroundMusicPlayer: AVAudioPlayer? = nil
Don't do this: the following sound will play for, at best, outOfScopeDelay due to the local scope of var audioPlayer.
let outOfScopeDelay = 0.5
do {
var audioPlayer:AVAudioPlayer! // Incorrectly scoped variable
try audioPlayer = AVAudioPlayer(contentsOf: audioRecorder.url)
audioPlayer.play()
Thread.sleep(forTimeInterval: outOfScopeDelay)
} catch {}
► Find this solution on GitHub and additional details on Swift Recipes.
try this:
dispatch_async(dispatch_get_main_queue(), {
(self.playRocketExplosionSound("Bang.mp3")
})
it's no longer safe to play audio in child thread under iOS 9.