iOS Xode : AVAudioPlayer is not playing WAV file correctly - ios

I have a WAV file with tones at around 18kHz. The audio is 16-bit PCM mono.
I am using the following function to play the file:
func playSound(name: String) {
let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let path = documents.appending("/").appending(name)
let url = NSURL.fileURL(withPath: path)
do {
player = try AVAudioPlayer(contentsOf: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error {
print(error.localizedDescription)
}
}
When I play it on my iOS 10.2 device I hear a series of tones between 1000Hz to 10000Hz. I've analyzed the rendered audio by capturing it and a frequency plot shows that the original content at 18kHz is there, but there are also tones present between 1000Hz to 10000Hz. When I play the same WAV file with VLC or any other desktop audio player, I don't hear the tones (which is expected since they're located around 18kHz). I suspect that the code above isn't loading the data correctly or that the player isn't properly initialized, so I need a seasoned iOS veteran who can tell me what I'm doing wrong.
Thank you in advance.

Check the following code it might work for you
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
var alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("button-09", ofType: "wav"))
println(alertSound)
// Removed deprecated use of AVAudioSessionDelegate protocol
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}

Related

What is the best way to play a sound with the least amount of delay possible, in Swift/Xcode?

I am experiencing a slight delay between UIButton press and sound output (it seems to average about 0.05 seconds). I am currently implementing AVAudioPlayer. Is there a best way to play a sound instantaneously? I scoured the Internet but can't find a concrete answer.
For reference, my (simplified) code is as follows:
// Get sound url
let soundURL = URL(fileURLWithPath: Bundle.main.path(forResource: file, ofType: type)!)
// Audio Player
var audioPlayer: AVAudioPlayer!
do {
// Create audio player
audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
}
catch let errMsg as NSError {
print("AVAudioPlayer error: \(errMsg.localizedDescription)")
}
// Prepare audio player to play
audioPlayer.prepareToPlay()
//...
#IBAction func btnPressed(_ sender: UIButton) {
audioPlayer.play()
}

iOS: audio files used in app sound harsher than when played outside the app

The audio files for our iOS app sound much worse when played through the app than when played outside the app. The sounds seem harsher and more "ecohy".
Here's our code for playing audio. Are we somehow altering the playback or natural sound of the audio?
Two of the audio files we're using can be found here:
private func createAudioPlayer(filename: String) -> AVAudioPlayer {
// Define file URL
let path = Bundle.main.path(forResource: filename, ofType: nil)
let url = URL(fileURLWithPath: path!)
// Create player
let audioPlayer: AVAudioPlayer!
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
} catch {
audioPlayer = nil
printError("Error creating audio player for \(url): \(error)")
logEvent("Audio Error", userData: nil)
}
// Print status
print("Created audio player for \(filename)")
// Return player
return audioPlayer
}
func play(file: AudioFileEnum) {
if let player = audioPlayers[file] {
if player.isPlaying {
player.pause()
}
player.currentTime = 0
player.play()
} else {
printError("Error finding audio player for \(file)")
}
}
Have you tried setting the volume on the audioPlayer object to something smaller, like 0.05f, and adjusting from there?

Adding sound effects in Spritekit

I'm making a Flappy Bird-type game, and I want to add a sound effect for when a coin is collected. This is what I have:
import SpriteKit
import AVFoundation
class GameScene: SKScene {
var coinSound = NSURL(fileURLWithPath:NSBundle.mainBundle().pathForResource("Super Mario Bros.- Coin Sound Effect", ofType: "mp3")!)
var audioPlayer = AVAudioPlayer()
Later in my didMoveToView:
audioPlayer = AVAudioPlayer(contentsOfURL: coinSound, error: nil)
audioPlayer.prepareToPlay()
For the second part (in my didMoveToView), the first line keeps showing an error:
Call can throw, but it is not marked with 'try' and the error is not handled
How do I fix this?
I know you already have marked an answer but if you re read this question or for future readers its easier to use SKActions to play a short 1 time sound.
AVFoundation is better used for background music.
e.g
class GameScene: SKScene {
// add as class property so sound is preloaded and always ready
let coinSound = SKAction.playSoundFileNamed("NAMEOFSOUNDFILE", waitForCompletion: false)
}
and than when you need to play the sound simply say this
run(coinSound)
Hope this helps
audioPlayer = AVAudioPlayer(contentsOfURL: coinSound, error: nil)
This is no longer the way that errors are handled in Swift. You must use the do-try-catch syntax.
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: coinSound)
audioPlayer.prepareToPlay()
}
catch let error {
// handle error
}
Alternatively, if you don't care to write any error handling code, you can make your audioPlayer variable optional and write a failable try?:
audioPlayer = try? AVAudioPlayer(contentsOfURL: coinSound)
audioPlayer?.prepareToPlay()
This is more closely equivalent to the code you're attempting where you pass nil for the error parameter.
Or if crashing is the appropriate behavior when an error occurs, you could take this approach:
guard let audioPlayer = try? AVAudioPlayer(contentsOfURL: coinSound) else {
fatalError("Failed to initialize the audio player with asset: \(coinSound)")
}
audioPlayer.prepareToPlay()
self.audioPlayer = audioPlayer
Swift 3.0+
For the above guard let example from #nhgrif, this Swift 3.0+ way of doing it:
var coinSound = NSURL(fileURLWithPath:Bundle.main.path(forResource: "Super Mario Bros.- Coin Sound Effect", ofType: "mp3")!)
var audioPlayer = AVAudioPlayer()
func playCoinSound(){
guard let soundToPlay = try? AVAudioPlayer(contentsOf: coinSound as URL) else {
fatalError("Failed to initialize the audio player with asset: \(coinSound)")
}
soundToPlay.prepareToPlay()
// soundToPlay.play() // NO IDEA WHY THIS DOES NOT WORK!!!???
self.audioPlayer = soundToPlay
self.audioPlayer.play() // But this does work... please let me know why???
}
playCoinSound()

Swift - AVAudioPlayer not playing sound

I have a sound called speeding.mp3 and I am trying to make a AVAudioPayer instance with it by doing this in the root of the class:
let speedingAudioPlayer = try! AVAudioPlayer(contentsOfURL: NSBundle.mainBundle().URLForResource("speeding", withExtension: "mp3")!)
Then when I run the app it throws an error but I don't know why this is happening. The speeding.mp3 file is in the Bundle Resources drop down in Build Phases so it should find it fine. If I try to print out the URL in the simulator and paste it into Safari it loads and plays the file like normal. I am importing AVFoundation into the class and the framework into the project.
Use this code snippet to debug your error,
if let URL = NSBundle.mainBundle().URLForResource("speeding", withExtension: "mp3") {
do {
let speedingAudioPlayer = try AVAudioPlayer(contentsOfURL: URL)
} catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("No such file exist")
}
I found another question where the var is placed outside the class because otherwise it would be deallocated by the phone.
AVAudioPlayer Object Sound Not Playing - Swift
import UIKit
import AVFoundation
class CustomAudioPlayer {
var audioPlayer : AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
playSound("sound_file_name")
}
func playSound(soundName: String)
{
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(soundName, ofType: "mp3")!)
do{
audioPlayer = try AVAudioPlayer(contentsOfURL:alertSound)
audioPlayer.prepareToPlay()
audioPlayer.play()
}catch {
print("Error getting the audio file")
}
}
}
Try this code for playing the sound.

How can I allow background music to continue playing while my app still plays its sounds while using Swift

I created an app and I am attempting to allow the user to continue to listen to their music while playing my game, but whenever they hit "play" and the ingame sounds occur it will stop the background music. I am developing on iOS using Swift. Here is a piece of the code that initiates the ingame sounds.
func playSpawnedDot() {
var alertSound: NSURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("spawnDot", ofType: "mp3")!)!
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.prepareToPlay()
if volumeBool {
audioPlayer.play()
}
}
You need to set the AVAudioSession category, with one of the following value: https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioSession_ClassReference/index.html (AVAudioSession Class Reference).
The default value is set to AVAudioSessionCategorySoloAmbient. As you can read :
[...] using this category implies that your app’s audio is nonmixable—activating your session will interrupt any other audio sessions which are also nonmixable. To allow mixing, use the AVAudioSessionCategoryAmbient category instead.
You have to change the category, before you play your sound. To do so :
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
You don't need to call those line each time you play the sound. You might want to do it only once.
Swift 4 version:
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
try? AVAudioSession.sharedInstance().setActive(true)
This is how I do it in Swift 3.0
var songPlayer : AVAudioPlayer?
func SetUpSound() {
if let path = Bundle.main.path(forResource: "TestSound", ofType: "wav") {
let filePath = NSURL(fileURLWithPath:path)
songPlayer = try! AVAudioPlayer.init(contentsOf: filePath as URL)
songPlayer?.numberOfLoops = -1 //logic for infinite loop
songPlayer?.prepareToPlay()
songPlayer?.play()
}
let audioSession = AVAudioSession.sharedInstance()
try!audioSession.setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.duckOthers) //Causes audio from other sessions to be ducked (reduced in volume) while audio from this session plays
}
You can see more of AVAudioSessionCategoryOptions here: https://developer.apple.com/reference/avfoundation/avaudiosessioncategoryoptions
Here's what I am using for Swift 2.0:
let sess = AVAudioSession.sharedInstance()
if sess.otherAudioPlaying {
_ = try? sess.setCategory(AVAudioSessionCategoryAmbient, withOptions: .DuckOthers)
_ = try? sess.setActive(true, withOptions: [])
}
Please note that you can replace .DuckOthers with [] if you don't want to lower background music and instead play on top to it.
Since they can't seem to make up their minds from version to version. Here it is in Swift 5.0
do{
try AVAudioSession.sharedInstance().setCategory(.ambient)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
} catch {
NSLog(error.localizedDescription)
}
lchamp's solution worked perfectly for me, adapted for Objective-C:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
**
Updated for Swift 3.0
**
The name of the sound I am playing is shatter.wav
func shatterSound() {
if let soundURL = Bundle.main.url(forResource: "shatter", withExtension: "wav") {
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL as CFURL, &mySound)
AudioServicesPlaySystemSound(mySound);
}
}
Then where ever you want to play the sound call
shatterSound()
If you want to play an alert sound:
public func playSound(withFileName fileName: String) {
if let soundUrl = Bundle.main.url(forResource: fileName, withExtension: "wav") {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, with:[.duckOthers])
try AVAudioSession.sharedInstance().setActive(true)
var soundId: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundId)
AudioServicesAddSystemSoundCompletion(soundId, nil, nil, { (soundId, clientData) -> Void in
AudioServicesDisposeSystemSoundID(soundId)
do {
// This is to unduck others, make other playing sounds go back up in volume
try AVAudioSession.sharedInstance().setActive(false)
} catch {
DDLogWarn("Failed to set AVAudioSession to inactive. error=\(error)")
}
}, nil)
AudioServicesPlaySystemSound(soundId)
} catch {
DDLogWarn("Failed to create audio player. soundUrl=\(soundUrl) error=\(error)")
}
} else {
DDLogWarn("Sound file not found in app bundle. fileName=\(fileName)")
}
}
And if you want to play music:
import AVFoundation
var audioPlayer:AVAudioPlayer?
public func playSound(withFileName fileName: String) {
if let soundUrl = Bundle.main.url(forResource: fileName, withExtension: "wav") {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, with:[.duckOthers])
try AVAudioSession.sharedInstance().setActive(true)
let player = try AVAudioPlayer(contentsOf: soundUrl)
player.delegate = self
player.prepareToPlay()
DDLogInfo("Playing sound. soundUrl=\(soundUrl)")
player.play()
// ensure the player does not get deleted while playing the sound
self.audioPlayer = player
} catch {
DDLogWarn("Failed to create audio player. soundUrl=\(soundUrl) error=\(error)")
}
} else {
DDLogWarn("Sound file not found in app bundle. fileName=\(fileName)")
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
self.audioPlayer?.stop()
do {
// This is to unduck others, make other playing sounds go back up in volume
try AVAudioSession.sharedInstance().setActive(false)
} catch {
DDLogWarn("Failed to set AVAudioSession inactive. error=\(error)")
}
}
For Swift (Objective-C like this too)
you can use this link for best answer and if you don't have any time for watching 10 minutes the best action is that you just copy below code in your AppDelegate in didFinishLaunchingWithOptions and then select your project's target then go to Capabilities and at last in Background modes check on Audio, AirPlay and Picture in Picture
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayback)
}
catch {
}
}
I didn't think that just setting the AVAudioSession to AVAudioSessionCategoryOptions.duckOthers would work, but it did. Here is my full code to help the rookies like myself.
Swift 4 - Full Example:
var audioPlayer = AVAudioPlayer()
func playSound(sound: String){
let path = Bundle.main.path(forResource: sound, ofType: nil)!
let url = URL(fileURLWithPath: path)
let audioSession = AVAudioSession.sharedInstance()
try!audioSession.setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.duckOthers)
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer.play()
} catch {
print("couldn't load the file")
}
}
I still need to figure out setActive (I was looking at this blog post) but the audio stops ducking when I leave the app, so it works for my app.
Swift 5 Version based on lchamp`s answer.
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient)
try? AVAudioSession.sharedInstance().setActive(true)
This works perfectly for me on iOS 14.4 and Swift 5. It's a bit different answer from others by using the .duckOthers option (and you can also mix directly with sound if you'd like with .mixWithOthers), but it works perfectly while music plays. It will lower the volume of the music, play the "beep" sound, and then raise the music volume back up to normal. It also captures error data using Google Firebase Crashlytics if there is an issue, and tries to raise the volume to normal even on an error.
This code will also work perfectly on the first, and all other, plays of your sound without it stopping the music ever.
func playSound() {
do {
if let path = Bundle.main.path(forResource: "beep", ofType: "mp3") {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: .duckOthers)
try AVAudioSession.sharedInstance().setActive(true)
let filePath = NSURL(fileURLWithPath:path)
songPlayer = try AVAudioPlayer.init(contentsOf: filePath as URL)
songPlayer?.numberOfLoops = 0
songPlayer?.prepareToPlay()
songPlayer?.play()
try AVAudioSession.sharedInstance().setActive(false)
}
} catch (let error) {
try? AVAudioSession.sharedInstance().setActive(false)
Crashlytics.crashlytics().setCustomValue(error.localizedDescription, forKey: "audio_playback_error")
}
}
An important thing the others answers do not have is that you need to call false with the .notifyOthers flag when deactivating:
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
The reason for this is that other apps playing music in the background will know when to turn their audio back on when you deactivate yours. Otherwise your background music won't turn back on if you turned off your session.

Resources