Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AudioKit 4.3: record audio, render it offline, then play it

I'm trying to record audio, then save offline with AudioKit.renderToFile, then use AKPlayer to play the original recorded audio file.

import UIKit
import AudioKit


class ViewController: UIViewController {

private var recordUrl:URL!
private var isRecording:Bool = false

public var player:AKPlayer!
private let format = AVAudioFormat(commonFormat: .pcmFormatFloat64, sampleRate: 44100, channels: 2, interleaved: true)!

private var amplitudeTracker:AKAmplitudeTracker!
private var boostedMic:AKBooster!
private var mic:AKMicrophone!
private var micMixer:AKMixer!
private var silence:AKBooster!
public var recorder: AKNodeRecorder!

@IBOutlet weak var recordButton: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()
    //self.recordUrl = Bundle.main.url(forResource: "sound", withExtension: "caf")
    //self.startAudioPlayback(url: self.recordUrl!)
    self.recordUrl = self.urlForDocument("record.caf")
}

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

func requestMic(completion: @escaping () -> Void) {
    AVAudioSession.sharedInstance().requestRecordPermission({ (granted: Bool) in
        
        if granted { completion()}
    })
}
public func switchToMicrophone() {
    stopEngine()
    do {
        try AKSettings.setSession(category: .playAndRecord, with: .allowBluetoothA2DP)
    } catch {
        AKLog("Could not set session category.")
    }
    mic = AKMicrophone()
    micMixer = AKMixer(mic)
    boostedMic = AKBooster(micMixer, gain: 5)
    amplitudeTracker = AKAmplitudeTracker(boostedMic)
    silence = AKBooster(amplitudeTracker, gain: 0)
    AudioKit.output = silence
    startEngine()
}

@IBAction func startStopRecording(_ sender: Any) {
    self.isRecording = !self.isRecording
    
    if self.isRecording {
        self.startRecording()
        self.recordButton.setTitle("Stop Recording", for: .normal)
    } else {
        self.stopRecording()
        self.recordButton.setTitle("Start Recording", for: .normal)
    }
}

func startRecording() {
    self.requestMic() {
        self.switchToMicrophone()
        if let url = self.recordUrl {
            do {
            let audioFile = try AKAudioFile(forWriting: url, settings: self.format.settings, commonFormat: .pcmFormatFloat64, interleaved: true)

            self.recorder = try AKNodeRecorder(node: self.micMixer, file: audioFile)

            try self.recorder.reset()
            try self.recorder.record()
            } catch {
                print("error setting up recording", error)
            }
        }
    }
}

func stopRecording() {
    recorder.stop()
    startAudioPlayback(url: self.recordUrl)
}

@IBAction func saveToDisk(_ sender: Any) {
    if let source = self.player, let saveUrl = self.urlForDocument("pitchAudio.caf") {
        do {
            source.stop()
            
            let audioFile = try AKAudioFile(forWriting: saveUrl, settings: self.format.settings, commonFormat: .pcmFormatFloat64, interleaved: true)
            try AudioKit.renderToFile(audioFile, duration: source.duration, prerender: {
                source.play()
            })
            print("audio file rendered")
            
        } catch {
            print("error rendering", error)
        }
        
        // PROBLEM STARTS HERE //
        
        self.startAudioPlayback(url: self.recordUrl)
        
    }
}

public func startAudioPlayback(url:URL) {
    print("loading playback audio", url)
    self.stopEngine()
    
    do {
        try AKSettings.setSession(category: .playback)
        player = AKPlayer.init()
        try player.load(url: url)
    }
    catch {
        print("error setting up audio playback", error)
        return
    }
    
    player.prepare()
    player.isLooping = true
    self.setPitch(pitch: self.getPitch(), saveValue: false)
    AudioKit.output = player
    
    startEngine()
    startPlayer()
}


public func startPlayer() {
    if AudioKit.engine.isRunning { self.player.play() }
    else { print("audio engine not running, can't play") }
}

public func startEngine() {
    if !AudioKit.engine.isRunning {
        print("starting engine")
        do { try AudioKit.start() }
        catch {
            print("error starting audio", error)
        }
    }
}

public func stopEngine(){
    
    if AudioKit.engine.isRunning {
        print("stopping engine")
        do {
            try AudioKit.stop()
        }
        catch {
            print("error stopping audio", error)
        }
    }
    
    //playback doesn't work without this?
    mic = nil
}

@IBAction func changePitch(_ sender: UISlider) {
    self.setPitch(pitch:Double(sender.value))
}

public func getPitch() -> Double {
    return UserDefaults.standard.double(forKey: "pitchFactor")
}

public func setPitch(pitch:Double, saveValue:Bool = true) {
    player.pitch = pitch * 1000.0
    if saveValue {
        UserDefaults.standard.set(pitch, forKey: "pitchFactor")
        UserDefaults.standard.synchronize()
    }
}

func urlForDocument(_ named:String) -> URL? {
    let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
    let url = NSURL(fileURLWithPath: path)
    if let pathComponent = url.appendingPathComponent(named) {
        return pathComponent
    }
    return nil
}

}

The order of calls is switchToMicrophone, startRecording, stopRecording, startAudioPlayback, saveToDisk, and again, startAudioPlayback

Please see the github repo for full code in ViewController.swift

After the renderToFile function, when restarting AudioKit for the player, the following errors occur:


[mcmx] 338: input bus 0 sample rate is 0

[avae] AVAEInternal.h:103:_AVAE_CheckNoErr: [AVAudioEngineGraph.mm:1265:Initialize: (err = AUGraphParser::InitializeActiveNodesInOutputChain(ThisGraph, kOutputChainOptimizedTraversal, *GetOutputNode(), isOutputChainActive)): error -10875

[avae] AVAudioEngine.mm:149:-[AVAudioEngine prepare]: Engine@0x1c4008ae0: could not initialize, error = -10875

[mcmx] 338: input bus 0 sample rate is 0

[avae] AVAEInternal.h:103:_AVAE_CheckNoErr: [AVAudioEngineGraph.mm:1265:Initialize: (err = AUGraphParser::InitializeActiveNodesInOutputChain(ThisGraph, kOutputChainOptimizedTraversal, *GetOutputNode(), isOutputChainActive)): error -10875

error starting audio Error Domain=com.apple.coreaudio.avfaudio Code=-10875 "(null)" UserInfo={failed call=err = AUGraphParser::InitializeActiveNodesInOutputChain(ThisGraph, kOutputChainOptimizedTraversal, *GetOutputNode(), isOutputChainActive)} ***

This all works flawlessly if I take the recording piece out, or the offline render out, but not with both included.

like image 286
hartw Avatar asked Jun 10 '18 23:06

hartw


2 Answers

It might be that the issue is with your order of execution try swapping startAudioPlayback, saveToDisk, so that it first does saveToDisk, and then reads the file back and plays it, startAudioPlayback.

EDIT: So far by playing around with it I believe I have identified the issue. Once you save the file the other tempfile which was the recording is disappearing for some reason. I think that needs to be narrowed down why is that.

Or perhaps to playaround and send the whole saveToDisk method to a background thread without interrupting the currently playing file.

In my spare time I'll try to tweak it a little more and let you know.

EDIT 2: check this https://stackoverflow.com/a/48133092/9497657 if you get nowhere try to post your issue here: https://github.com/audiokit/AudioKit/issues/

also check this tutorial out as well: https://www.raywenderlich.com/145770/audiokit-tutorial-getting-started

also it might be useful to message Aurelius Prochazka as he is a developer of AudioKit who could help.

like image 74
AD Progress Avatar answered Sep 30 '22 06:09

AD Progress


I was able to get it working by combining the recording and playback into a single pipeline:

mixer = AKMixer(mic)
boostedMic = AKBooster(mixer, gain: 5)
amplitudeTracker = AKAmplitudeTracker(boostedMic)
micBooster = AKBooster(amplitudeTracker, gain: 0)

player = AKPlayer()
try? player.load(url: self.recordUrl)
player.prepare()
player.gain = 2.0

outputMixer = AKMixer(micBooster, player)
AudioKit.output = outputMixer
like image 24
hartw Avatar answered Sep 30 '22 06:09

hartw