Crash when repeating a sound with AudioEngine in Swift
Asked Answered
C

4

5

I'm trying to play sounds with different effects. In a previous viewController I record a sound, then in the next screen, it can be played with the effects. First time it works ok but the second time it crashes with error as follows:

2015-08-07 13:00:45.900 Pitch Perfect[9643:1121173] 13:00:45.900 ERROR: AVAudioEngine.mm:253: AttachNode: required condition is false: !nodeimpl->HasEngineImpl() 2015-08-07 13:00:45.953 Pitch Perfect[9643:1121173] Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: !nodeimpl->HasEngineImpl()'

import UIKit
import AVFoundation

class PlaySoundsViewController: UIViewController, AVAudioPlayerDelegate {

    var receivedAudio:RecordedAudio!
    var audioPlayer: AVAudioPlayer!
    var disabledButton:UIButton!
    var firstTime:Bool = true

    var audioEngine:AVAudioEngine!
    var audioFile:AVAudioFile!
    var audioPlayerNode:AVAudioPlayerNode!

    var audioStopped:Bool!
    var typeOfSound:IntegerLiteralType!


    @IBOutlet weak var stopButton: UIButton!

    @IBOutlet weak var reverbButton: UIButton!

    @IBOutlet weak var echoButton: UIButton!

    @IBOutlet weak var darthButton: UIButton!

    @IBOutlet weak var chipmonkButton: UIButton!

    @IBOutlet weak var snailButton: UIButton!

    @IBOutlet weak var rabbitButton: UIButton!



    override func viewDidLoad() {
        super.viewDidLoad()

        audioPlayer = AVAudioPlayer(contentsOfURL: receivedAudio.filePathUrl, error: nil)
        audioPlayer.enableRate=true
        audioPlayer.delegate=self

        var session = AVAudioSession.sharedInstance()
        session.setCategory(AVAudioSessionCategoryPlayback, error: nil)
        audioPlayerNode=AVAudioPlayerNode();
        audioEngine = AVAudioEngine()
        audioFile = AVAudioFile(forReading: receivedAudio.filePathUrl, error: nil)
        audioStopped=true;


    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func playAnimalSound(animal: String) {
        if audioStopped==false {
            resetAudio(typeOfSound)
        }
        typeOfSound = 1
        audioPlayer.currentTime=0

        switch animal {
            case "snail":
                audioPlayer.rate=0.5
            case "rabbit":
                 audioPlayer.rate=2.0
        default:
            showMessage("Sound not found. How can it be?")
        }

        audioPlayer.play()
        stopButton.hidden=false
        stopButton.enabled=true

    }



    @IBAction func playSnailSound(sender: UIButton) {
        highlightButton(sender)
       playAnimalSound("snail")
    }

    @IBAction func playRabbitSound(sender: UIButton) {
        highlightButton(sender)
        playAnimalSound("rabbit")
    }

    func soundEnded() {
        stopButton.hidden=true
        disabledButton.enabled=true;
        if(audioEngine.running) {
            audioEngine.stop()
            audioEngine.reset();
        }

    }


    func playAudioWithVariablePitch(pitch: Float, type: String) {
        if audioStopped==false {
             resetAudio(typeOfSound)
        }

        audioEngine.attachNode(audioPlayerNode)


        switch type {
        case "normal":
             typeOfSound = 2
            var changePitchEffect = AVAudioUnitTimePitch()
            changePitchEffect.pitch = pitch
            audioEngine.attachNode(changePitchEffect)

            audioEngine.connect(audioPlayerNode, to: changePitchEffect, format: nil)
            audioEngine.connect(changePitchEffect, to: audioEngine.outputNode, format: nil)

        case "reverb":
             typeOfSound = 3
            var changeReverbEffect = AVAudioUnitReverb()
            changeReverbEffect.loadFactoryPreset(AVAudioUnitReverbPreset(rawValue: 4)!)
            changeReverbEffect.wetDryMix=50;
            audioEngine.attachNode(changeReverbEffect)

            audioEngine.connect(audioPlayerNode, to: changeReverbEffect, format: nil)
            audioEngine.connect(changeReverbEffect, to: audioEngine.outputNode, format: nil)

        case "delay":
             typeOfSound = 3
            var changeDelayEffect = AVAudioUnitDelay()
            audioEngine.attachNode(changeDelayEffect)

            audioEngine.connect(audioPlayerNode, to: changeDelayEffect, format: nil)
            audioEngine.connect(changeDelayEffect, to: audioEngine.outputNode, format: nil)
        default:
            showMessage("oops, there was an internal problem. Never mind")

        }


       audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: soundEnded)
        audioEngine.startAndReturnError(nil)
        stopButton.hidden=false
        stopButton.enabled=true
        audioPlayerNode.play()


    }



    func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool) {
        if flag {
            stopButton.hidden=true
            disabledButton.enabled=true;
            audioStopped=true
            println("I hid stopButton and enabled the disabled one")

        }

    }



    @IBAction func playDelaySound(sender: UIButton) {
        highlightButton(sender)
        playAudioWithVariablePitch(0, type: "delay")

    }


    @IBAction func playReverbSound(sender: UIButton) {
        highlightButton(sender)
        playAudioWithVariablePitch(0, type: "reverb")

    }

    @IBAction func playChipmunkSound(sender: UIButton) {
        highlightButton(sender)
        playAudioWithVariablePitch(1000.0, type: "normal")

    }

    @IBAction func playDarthVaderSound(sender: UIButton) {
        highlightButton(sender)
        playAudioWithVariablePitch(-900.0, type: "normal")

    }

    @IBAction func stopPlaying(sender: UIButton) {
        resetAudio(typeOfSound)
        stopButton.hidden=true
        stopButton.enabled=false;
        disabledButton.enabled=true;
    }

    func highlightButton(button: UIButton) {
        if firstTime {
            firstTime=false
        } else {
            disabledButton.enabled=true;
        }
        button.enabled=false;
        disabledButton=button;

    }

    func resetAudio(type: IntegerLiteralType) {
        switch type {
        case 1 :
            audioPlayer.stop()
            println("case 1")
        case 2 :
            println("case 2")
            if audioEngine.running {
                audioEngine.stop()
            }
            audioEngine.reset()
        case 3 :
             audioEngine.stop()

        default:
            break
        }

        audioStopped=true;

    }

    func showMessage(msg: String) {
        var message=UIAlertView(title: "Alert", message: msg, delegate: nil, cancelButtonTitle: "ok I won't panic")
    }


}

Does anybody know why it crashes? I have researched the AVAudioEngine, AVAudioPlayer and AVAudioPlayerNode classes with no results.

Thanks

Cambric answered 7/8, 2015 at 11:10 Comment(0)
K
13

I know this is an old issue, but I didn't see the correct answer above.

The reason why it crashes is actually outlined in the error message:

AttachNode: required condition is false: !nodeimpl->HasEngineImpl()

In other words, when attaching a node it is mandatory that that node is not already attached to an engine (!nodeimpl->HasEngineImpl()).

The solution is to remove the node using audioEngine.detachNode before attempting to add it again.

Kliber answered 23/11, 2016 at 14:4 Comment(0)
C
3

Finally the crashed was caused by initializing the audioPlayerNode and the audioEngine objects in the viewDidLoad function. They need to be instantiated every time you use them, apparently, or maybe after being stopped and reset. Placing those lines in the beginning of the playAudioWithVariablePitch function directly instead of in the viewDidLoad function, solved the crash problem. I still have a problem with the playback of the pitched, reverb and echo sounds. They get cut before it's due and I still don't know why. It has to do with the completionHandler of the audioPlayerNode.scheduleFile method.

Cambric answered 7/8, 2015 at 18:6 Comment(0)
N
1

In my case, my timer was calling again and again and this code was written inside my timer

self.recognitionTask?.finish()
node.removeTap(onBus: 0)
self.request.endAudio()

self.recognitionTask = nil
//Maybe this line was causing the issue
self.audioEngine.stop()

So if you already have stopped the request, removed the tap and stopped the engine, then these lines should not be called again.

Hope this helps some one

Necklace answered 17/8, 2018 at 11:3 Comment(0)
G
0

It looks like you are resetting the engine after playing the variable pitch effect.

func soundEnded() {
    stopButton.hidden=true
    disabledButton.enabled=true;
    if(audioEngine.running) {
        audioEngine.stop()
        audioEngine.reset();
    }
}

audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: soundEnded)
audioEngine.startAndReturnError(nil)
stopButton.hidden=false
stopButton.enabled=true
audioPlayerNode.play()

So the engine hasnt been setup again with the nodes added and the chain linked. When you try to play the playerNode, it crashes.

Guard answered 7/8, 2015 at 11:21 Comment(1)
It still crashes after commenting all autoEngine.reset() lines.Cambric

© 2022 - 2024 — McMap. All rights reserved.