简单看看,Audio Kit 录音部分的源代码

1,053 阅读3分钟

一般录音,可以简单使用 AVAudioRecorder,

本文介绍下,Audio Kit 录音部分的源代码

( Audio Kit 最强的是,Midi )

录音调用


    let engine = AudioEngine()

    var recorder: NodeRecorder?
    
    let mixer = Mixer()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let input = engine.input else {
            fatalError()
        }

        do {
            recorder = try NodeRecorder(node: input)
        } catch let err {
            fatalError("\(err)")
        }
        engine.output = mixer
        do {
            try engine.start()
        } catch {
            print("AudioKit did not start! \(error)")
        }
    }

    func toStop(){
        recorder?.stop()
    }
    
    func toRecord(){
        NodeRecorder.removeTempFiles()
        do {
            try recorder?.record()
        } catch let err {
            print(err)
        }

    }

把 AudioEngine 连起来,mixer -> engine's outoput

就可以录音了

这里的录音,采用的是音频数据采样

源代码部分

把 Audio Engine 连通

engine.output = mixer

背后的代码

public var output: Node? {
        didSet {

            if let node = output {
                // 添加上面的 mixer
                avEngine.attach(node.avAudioNode)

                createEngineMixer()
                
                 // 连接上面的 mixer
                mainMixerNode?.addInput(node)
            }

        }
    }

在里面给 engine 的输出,添加一个混音器

inner mixer -> engine's outoput

所以上面使用录音的代码,实际上是

outer mixer -> inner mixer -> engine's outoput

    private func createEngineMixer() {
        guard mainMixerNode == nil else { return }

        let mixer = Mixer()
        avEngine.attach(mixer.avAudioNode)
        avEngine.connect(mixer.avAudioNode, to: avEngine.outputNode, format: Settings.audioFormat)
        mainMixerNode = mixer
    }

录音采样部分

音频文件的格式,有两部分:

  • 文件的封装格式,

  • 和音频数据的编码格式

创建音频文件,URL 尾缀对应封装格式,格式的设置 ( 采样率 )对应编码格式

开始采样

    public func record() throws {
        if isRecording == true {
            print("Warning: already recording")
            return
        }

        if let path = internalAudioFile?.url.path, !FileManager.default.fileExists(atPath: path) {
            // record to new tmp file
            print("path: \(path)")
            if let tmpFile = NodeRecorder.createTempFile() {
                internalAudioFile = try AVAudioFile(forWriting: tmpFile.url,
                                                    settings: tmpFile.fileFormat.settings)
            }
        }
        // 前面是简单的文件处理
        
        // 录音就是,记录音频数据的采样
        
        let bufferLength: AVAudioFrameCount = Settings.recordingBufferLength.samplesCount
        isRecording = true

        // Note: if you install a tap on a bus that already has a tap it will crash your application.
        print("⏺ Recording using format", internalAudioFile?.processingFormat.debugDescription ?? "")

        // note, format should be nil as per the documentation for installTap:
        // "If non-nil, attempts to apply this as the format of the specified output bus. This should
        // only be done when attaching to an output bus which is not connected to another node"
        // In most cases AudioKit nodes will be attached to something else.
        node.avAudioUnitOrNode.installTap(onBus: bus,
                                          bufferSize: bufferLength,
                                          format: recordFormat,
                                          block: process(buffer:time:))
    }
    

有了创建的空白音频文件,

接下来就是,

给文件,填充音频数据

    // 音频数据处理部分
    private func process(buffer: AVAudioPCMBuffer, time: AVAudioTime) {
        guard let internalAudioFile = internalAudioFile else { return }

        do {
            // 写入音频缓冲
            recordBufferDuration = Double(buffer.frameLength) / Settings.sampleRate
            try internalAudioFile.write(from: buffer)

            // allow an optional timed stop
            if durationToRecord != 0 && internalAudioFile.duration >= durationToRecord {
                stop()
            }

        } catch let error as NSError {
            print("Write failed: error -> \(error.localizedDescription)")
        }
    }

我们听见的声音,是连续的,模拟信号

计算机能够办的,是数字信号,

  • 采样率是一秒内,记录多少次,

一秒内记录的次数越多,1 秒记录的信息越丰富

  • 位深是,记录的一次的精度

苹果标准的 Float32, 是把听到的声音,拆分为 6.8*10^38 个级别

每一次记录,采用 6.8*10^38 个等级中的一个

UInt16 ,声音的级别没这么细致,精度相对低,好多值取不到,只能进一步近似

辅助方法,创建音频文件

录音文件,采样的音频文件格式是,caf

AVLinearPCMIsNonInterleaved 选 false, 便于音频数据分析

AVLinearPCMIsNonInterleaved 选 true, 便于音频播放,左右声道的音频数据夹杂在一起

public static func createTempFile() -> AVAudioFile? {
        let filename = UUID().uuidString + ".caf"
        let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(filename)
        var settings = Settings.audioFormat.settings
        settings[AVLinearPCMIsNonInterleaved] = NSNumber(value: false)

        print("Creating temp file at", url)
        guard let tmpFile = try? AVAudioFile(forWriting: url,
                                             settings: settings,
                                             commonFormat: Settings.audioFormat.commonFormat,
                                             interleaved: true) else { return nil }

        tmpFiles.append(url)
        return tmpFile
    }


github repo