代码之家  ›  专栏  ›  技术社区  ›  hartw

音频工具包4.3:录制音频,使其脱机,然后播放

  •  5
  • hartw  · 技术社区  · 6 年前

    我正在尝试录制音频,然后脱机保存 AudioKit.renderToFile 然后使用 AKPlayer 播放原始录制的音频文件。

    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
    }
    
    }
    

    通话顺序是 switchToMicrophone , startRecording , stopRecording , startAudioPlayback , saveToDisk 再一次, 开始播放

    请看 github repo 完整代码输入 ViewController.swift

    renderToFile 功能,重新启动播放机的AudioKit时,会出现以下错误:


    [MCMX]338:输入总线0采样率为0

    [avae]avaeinternal.h:103:“avae”checknoerr:“avaudioenginegraph.mm:1265:初始化:(err=augraphparser::initializeActiveNoudisNoutputChain(此图,koutputChainOptimizedTraversal,*getOutputNode(),isoutputChainActive)):错误-10875

    [avae]avaudioengine.mm:149:-[avaudioengine prepare]:引擎@0x1C4008AE0:无法初始化,错误=-10875

    [MCMX]338:输入总线0采样率为0

    [avae]avaeinternal.h:103:“avae”checknoerr:“avaudioenginegraph.mm:1265:初始化:(err=augraphparser::initializeActiveNoudisNoutputChain(此图,koutputChainOptimizedTraversal,*getOutputNode(),isoutputChainActive)):错误-10875

    启动audio error domain=com.apple.coreaudio.avfaudio code=-10875“(null)”userinfo={failed call=err=augraphparser::initializeActiveNoudisNoutputChain(thisGraph,koutputChainOptimizedTraversal,*getOutputNode(),isoutputChainActive)}***

    如果我取出录音片段,或者离线渲染,但不包括两者,这一切都是完美的。

    2 回复  |  直到 6 年前
        1
  •  1
  •   AD Progress    6 年前

    可能是执行顺序有问题,请尝试交换startaudioplayback和savetodisk,以便它首先执行savetodisk,然后再读取文件并播放startaudioplayback。

    编辑:到目前为止,我认为我已经发现了这个问题。一旦保存了文件,另一个记录文件就因为某种原因消失了。我想应该缩小范围,为什么?

    或者在不中断当前正在播放的文件的情况下播放并将整个saveToDisk方法发送到后台线程。

    在我的业余时间,我会尽量调整一下,让你知道。

    编辑2: 检查这个 https://stackoverflow.com/a/48133092/9497657 如果你找不到地方,请在这里发布你的问题: https://github.com/audiokit/AudioKit/issues/

    也可以查看本教程: https://www.raywenderlich.com/145770/audiokit-tutorial-getting-started

    此外,这可能是有用的信息奥雷利乌斯普罗查斯卡,因为他是一个开发音频工具包谁可以帮助。

        2
  •  1
  •   hartw    6 年前

    我能够通过把录音和回放结合成一条管道来实现它:

    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