一般录音,可以简单使用 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
}