Render audio file offline using AVAudioEngine
Asked Answered
S

1

5

I want to record audio file and save it by applying some effects. Record is okay and also playing this audio with effect is okay too. The problem is when I try to save such audio offline it produces empty audio file. Here is my code:

let effect = AVAudioUnitTimePitch()
effect.pitch = -300
self.addSomeEffect(effect)

func addSomeEffect(_ effect: AVAudioUnit) {
    try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)

    let format = self.audioFile.processingFormat
    self.audioEngine.stop()
    self.audioEngine.reset()

    self.audioEngine = AVAudioEngine()
    let audioPlayerNode = AVAudioPlayerNode()


    self.audioEngine.attach(audioPlayerNode)
    self.audioEngine.attach(effect)

    self.audioEngine.connect(audioPlayerNode, to: self.audioEngine.mainMixerNode, format: format)
    self.audioEngine.connect(effect, to: self.audioEngine.mainMixerNode, format: format)

    audioPlayerNode.scheduleFile(self.audioFile, at: nil)
    do {
        let maxNumberOfFrames: AVAudioFrameCount = 8096
        try self.audioEngine.enableManualRenderingMode(.offline,
                                                       format: format,
                                                       maximumFrameCount: maxNumberOfFrames)
    } catch {
        fatalError()
    }


    do {
        try audioEngine.start()
        audioPlayerNode.play()
    } catch {

    }

    let outputFile: AVAudioFile
    do {
        let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("temp.m4a")
        if FileManager.default.fileExists(atPath: url.path) {
            try? FileManager.default.removeItem(at: url)
        }

        let recordSettings = self.audioFile.fileFormat.settings

        outputFile = try AVAudioFile(forWriting: url, settings: recordSettings)
    } catch {
        fatalError()
    }

    let buffer = AVAudioPCMBuffer(pcmFormat: self.audioEngine.manualRenderingFormat,
                                  frameCapacity: self.audioEngine.manualRenderingMaximumFrameCount)!

    while self.audioEngine.manualRenderingSampleTime < self.audioFile.length {
        do {
            let framesToRender = min(buffer.frameCapacity,
                                     AVAudioFrameCount(self.audioFile.length - self.audioEngine.manualRenderingSampleTime))
            let status = try self.audioEngine.renderOffline(framesToRender, to: buffer)
            switch status {
            case .success:
                print("Write to file")
                try outputFile.write(from: buffer)
            case .error:
                fatalError()
            default:
                break
            }
        } catch {
            fatalError()
        }
    }
    print("Finish write")


    audioPlayerNode.stop()
    audioEngine.stop()


    self.outputFile = outputFile
    self.audioPlayer = try? AVAudioPlayer(contentsOf: outputFile.url)

}

AVAudioPlayer fails to open file with output url. I looked at the file through the file system and it is empty and can't be played.

Picking different categories for AVAudioSession is not working too.

Thanks for help!

UPDATE

I switched to use .caf file extension in my record in output file and it worked. Any idea why is .m4a is not working?

Sagittal answered 5/9, 2018 at 11:33 Comment(2)
You need to nil outputFile to flush the header and close the m4a file.Portauprince
@RhythmicFistman thanks, it works!Sagittal
P
8

You need to nil outputFile to flush the header and close the m4a file.

Portauprince answered 19/9, 2018 at 8:48 Comment(3)
Sorry, just to be clear, do you meaning setting outputFile to nil at some point after everything has finished rendering?Adkison
That's right. After you've written your last buffer, where you would normally call close you instead set the AVAudioFile to nil.Portauprince
This, by the way, is an extraordinarily stupid API decision in my book. Using a side-effect of object cleanup to close the file is both non-trivial to discover and also non-deterministic. I don't understand why AVAudioFile doesn't have an explicit close() method.Inquisition

© 2022 - 2024 — McMap. All rights reserved.