7

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.

Community
  • 1
  • 1
hartw
  • 356
  • 3
  • 17
  • 2
    "Please see the github repo" You'll get better help, I suspect, if you include the relevant code in your question. – matt Jun 15 '18 at 16:19
  • Thanks matt -- wasn't sure if a wall of code would be distracting, but I'll add that in now – hartw Jun 15 '18 at 16:20

2 Answers2

2

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.

AD Progress
  • 4,190
  • 1
  • 14
  • 33
  • The problem is that I need to play the audio before and after saving to disk. The `renderToFile` [function](https://github.com/AudioKit/AudioKit/blob/3919bf3bc149fb56b82c6e4a1002a940ba763b3f/AudioKit/Common/Internals/AVAudioEngine%2BExtensions.swift) stops the audio engine, so it must be restarted after. I suspect the problem lies somewhere in there. – hartw Jun 21 '18 at 16:04
  • how about a way around this. Save the file first then play it and if it will be rejected for some reason then just implement a delete function. From my experience if you save something it easier to manipulate than if it is temporarily saved in memory. – AD Progress Jun 21 '18 at 16:08
  • 1
    I am just running your app on my phone I will try to locate the issue but there is a double call somewhere where it is nit needed – AD Progress Jun 21 '18 at 16:38
  • thank you ... I'm ok using any other solutions to save the file, but please note that the pitch of the sound can be changed before saving it, thus allowing the user to hear what it will sound like before they save it – hartw Jun 21 '18 at 18:01
  • what I have noticed that the file is getting saved anyway without using the AudioKit.renderToFile method. Now my question is that the saveToFile method supposed to save the newly pitched file?? And then go back to the previously played file? The problem seems to be with deinitialization of audio engine I am still playing around with it I'll let you know if I come up with anything. – AD Progress Jun 21 '18 at 18:22
  • exactly, so there should be two files saved at the end -- the original, and the pitch-modified version. It should replay the original after the pitch modified version is saved. – hartw Jun 21 '18 at 21:34
  • Added some extra info to my answer. – AD Progress Jun 22 '18 at 18:34
  • Thanks for the tip, I’ll see if it leads to any solutions —I awarded you the bounty – hartw Jun 22 '18 at 20:20
  • I am happy that I could help. I found another bug. Not every time when I start the app I can record a sound sometimes it is the second or third time I can record one, I also dug – AD Progress Jun 23 '18 at 10:10
2

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
hartw
  • 356
  • 3
  • 17