ivan123 ivan123 - 3 months ago 44
Swift Question

Record audio with added effects

I've read lots of post and tried lots of different approach and finaly come up to actually working solution which is the fallowing func from other stack-overflow post to play, add effect and save it to file, it plays added effect and saves it but the problem is that the saved caf. file is not readable. I saw that it creates the file on main directory but cant play it back. Any idea what causes the problem will be greatly appreciated

func playAudio(pitch : Float, rate: Float, reverb: Float, echo: Float) {
// Initialize variables
audioEngine = AVAudioEngine()
audioPlayerNode = AVAudioPlayerNode()
audioEngine.attachNode(audioPlayerNode)

// Setting the pitch
let pitchEffect = AVAudioUnitTimePitch()
pitchEffect.pitch = pitch
audioEngine.attachNode(pitchEffect)

// Setting the platback-rate
let playbackRateEffect = AVAudioUnitVarispeed()
playbackRateEffect.rate = rate
audioEngine.attachNode(playbackRateEffect)

// Setting the reverb effect
let reverbEffect = AVAudioUnitReverb()
reverbEffect.loadFactoryPreset(AVAudioUnitReverbPreset.Cathedral)
reverbEffect.wetDryMix = reverb
audioEngine.attachNode(reverbEffect)

// Setting the echo effect on a specific interval
let echoEffect = AVAudioUnitDelay()
echoEffect.delayTime = NSTimeInterval(echo)
audioEngine.attachNode(echoEffect)

// Chain all these up, ending with the output
audioEngine.connect(audioPlayerNode, to: playbackRateEffect, format: nil)
audioEngine.connect(playbackRateEffect, to: pitchEffect, format: nil)
audioEngine.connect(pitchEffect, to: reverbEffect, format: nil)
audioEngine.connect(reverbEffect, to: echoEffect, format: nil)
audioEngine.connect(echoEffect, to: audioEngine.mainMixerNode, format: nil)


// Good practice to stop before starting
audioPlayerNode.stop()

// Play the audio file
if(audioEngine != nil){
audioEngine?.stop()
}

audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: {
print("Complete")

})

try! audioEngine.start()


let dirPaths: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0]
let tmpFileUrl: NSURL = NSURL.fileURLWithPath(dirPaths.stringByAppendingPathComponent("dddeffefsdctedSoundf23f13.caf"))
filteredOutputURL = tmpFileUrl

do{
print(dirPaths)
print(tmpFileUrl)

self.newAudio = try! AVAudioFile(forWriting: tmpFileUrl, settings:[
AVFormatIDKey: NSNumber(unsignedInt:kAudioFormatAppleLossless),
AVEncoderAudioQualityKey : AVAudioQuality.Medium.rawValue,
AVEncoderBitRateKey : 12800,
AVNumberOfChannelsKey: 2,
AVSampleRateKey : 44100.0
])

audioEngine.mainMixerNode.installTapOnBus(0, bufferSize: 2048, format: audioEngine.mainMixerNode.inputFormatForBus(0)) {
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in

print(self.newAudio.length)
print("=====================")
print(self.audioFile.length)
print("**************************")
if (self.newAudio.length) < (self.audioFile.length){//Let us know when to stop saving the file, otherwise saving infinitely

do{
//print(buffer)
try self.newAudio.writeFromBuffer(buffer)
}catch _{
print("Problem Writing Buffer")
}
}else{
self.audioEngine.mainMixerNode.removeTapOnBus(0)//if we dont remove it, will keep on tapping infinitely

}

}
}catch _{
print("Problem")
}

audioPlayerNode.play()

Answer

You need to flush and close the file audio file, so that the caf file is properly written out.

Seeing AVAudioFile doesn't have explicit methods for doing that, your only hope appears to be setting newAudio to nil after you've finished and hoping that it is done during AVAudioFile's dealloc:

self.audioEngine.mainMixerNode.removeTapOnBus(0)
                print("finish?")
self.newAudio = nil   // hopefully flush & close, if there are no other strong references
Comments