AudioKit 下采样音频
AudioKit down sample audio
我有一些现有代码使用 AVAudioEngine
从麦克风获取输入,对其进行下采样并将其写入 AVAudioFile
internal func setupNodeChain() {
guard let audioEngine = audioEngine else { return } // Fatal error ?
let engineInputNode = audioEngine.inputNode
let bus = 0
let engineInputNodeFormat = engineInputNode.outputFormat(forBus: bus)
// This attempts to down sample the audio from the microphone
let downSampleMixerNode = AVAudioMixerNode()
let mixerOutputFormat = AVAudioFormat(standardFormatWithSampleRate: 8000, channels: 1)
// Input -> (volume) -> down sample -> (volume) -> Output
let inputVolumeMixerNode = AVAudioMixerNode()
inputVolumeMixerNode.volume = Float(10 * microphoneVolume)
audioEngine.attach(inputVolumeMixerNode)
audioEngine.attach(downSampleMixerNode)
self.downSampleMixerNode = downSampleMixerNode
self.inputVolumeMixerNode = inputVolumeMixerNode
let silenceNode = AVAudioMixerNode()
silenceNode.outputVolume = 0
self.silenceNode = silenceNode
audioEngine.connect(engineInputNode, to: inputVolumeMixerNode, format: engineInputNodeFormat)
audioEngine.connect(inputVolumeMixerNode, to: downSampleMixerNode, format: engineInputNodeFormat)
// Try and stop the microphone audio from going through to the speaker
audioEngine.attach(silenceNode)
audioEngine.connect(downSampleMixerNode, to: silenceNode, format: mixerOutputFormat)
audioEngine.connect(silenceNode, to: audioEngine.outputNode, format: mixerOutputFormat)
downSampleMixerNode.installTap(onBus: bus, bufferSize: 1024 * 16, format: mixerOutputFormat) { (buffer: AVAudioPCMBuffer, time: AVAudioTime) in
guard let tap = self.audioTap else { return }
// Write buffer to AVAudioFile
tap.drip(buffer: buffer, time: time)
}
}
这主要是可行的,但我正在研究用 AudioKit 替换它,但我遇到了问题,我不知道如何创建一种机制来对从麦克风到录音机的音频进行下采样.
AKSettings.enableEchoCancellation = true
AKSettings.allowAirPlay = true
AKSettings.useBluetooth = true
do {
try AKSettings.setSession(category: .playAndRecord,
with: [
.allowBluetoothA2DP,
])
AKSettings.defaultToSpeaker = true
let audioFile = try self.makeAudioFile(named: "Recording")
let mixerOutputFormat = AVAudioFormat(standardFormatWithSampleRate: 8000, channels: 1)!
let microphone = AKMicrophone()
let microphoneBooster = AKBooster(microphone)
microphoneBooster.gain = 0
let recorder = try AKNodeRecorder(node: microphoneBooster)
//recorder.recordFormat = mixerOutputFormat
let silence = AKMixer(microphoneBooster)
silence.volume = 0
self.microphone = microphone
self.microphoneBooster = microphoneBooster
self.recorder = recorder
self.silence = silence
AKManager.output = silence
log(debug: "Start")
try AKManager.start()
log(debug: "Record")
try recorder.record()
DispatchQueue.main.async {
self.state = .recording
self.plot?.node = microphone
self.callButton.setImage(#imageLiteral(resourceName: "EndCall"), for: [])
}
} catch let error {
log(error: "Failed to establish play and record session: \(error)")
}
所以,问题是 - 我将如何创建一个“下采样”node/workflow,这将 link 麦克风到具有“默认”格式的“节点”和link 链中下一个节点的“节点”与所需 AVAudioFormat
?
麦克风 -> 下采样(默认格式)
下采样->下一个节点(目标格式)->记录器
基本上,我必须创建自己的“tap”来挖掘数据
首先,我有一个“转换器”。这基本上是将来自另一个混音器的音频(通过“tap”)转换为目标格式并将其写入音频文件
class TapConverter: NodeTapperDelegate {
let audioConfig: AudioConfig
internal var inputFormat: AVAudioFormat?
internal var converter: AVAudioConverter?
var onError: ((Error) -> Void)?
init(audioConfig: AudioConfig) {
self.audioConfig = audioConfig
}
func open(format: AVAudioFormat) throws {
inputFormat = format
converter = AVAudioConverter(from: format, to: audioConfig.audioFormat)
}
func drip(buffer: AVAudioPCMBuffer, time: AVAudioTime) {
guard let converter = converter else {
return
}
guard let inputFormat = inputFormat else {
return
}
let inputBufferSize = inputFormat.sampleRate
let sampleRateRatio = inputBufferSize / audioConfig.audioFormat.sampleRate
let capacity = Int(Double(buffer.frameCapacity) / sampleRateRatio)
let bufferPCM16 = AVAudioPCMBuffer(pcmFormat: audioConfig.audioFormat, frameCapacity: AVAudioFrameCount(capacity))!
var error: NSError? = nil
converter.convert(to: bufferPCM16, error: &error) { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer
}
if let error = error {
// Handle error in someway
} else {
let audioFile = audioConfig.audioFile
do {
log(debug: "Write buffer")
try audioFile.write(from: bufferPCM16)
} catch let error {
log(error: "Failed to write buffer to audio file: \(error)")
onError?(error)
}
}
}
func close() {
converter = nil
inputFormat = nil
// we close the audio file
}
}
AudioConfig
只是一个基本的占位符,它包含写入的 audioFile
(必须已经创建)和目标 AVAudioFormat
struct AudioConfig {
let url: URL
let audioFile: AVAudioFile
let audioFormat: AVAudioFormat
}
创建可能类似于...
let settings: [String: Any] = [
AVFormatIDKey: NSNumber(value: kAudioFormatMPEG4AAC),
AVSampleRateKey: NSNumber(value: 8000),
AVNumberOfChannelsKey: NSNumber(value: 1),
AVEncoderBitRatePerChannelKey: NSNumber(value: 16),
AVEncoderAudioQualityKey: NSNumber(value: AVAudioQuality.min.rawValue)
]
let audioFile = try AVAudioFile(forWriting: sourceURL, settings: settings)
let audioConfig = AudioConfig(url: sourceURL, audioFile: audioFile, audioFormat: audioFormat)
从那里,我需要一种方法来点击节点(获取它的数据)并将其传递到我的转换器,为此,我使用了类似...
import Foundation
import AudioKit
protocol NodeTapperDelegate: class {
func open(format: AVAudioFormat) throws
func drip(buffer: AVAudioPCMBuffer, time: AVAudioTime)
func close()
}
class NodeTapper: NSObject {
// MARK: - Properties
// The node we record from
private(set) var node: AKNode?
/// True if we are recording.
@objc private(set) dynamic var isTapping = false
/// The bus to install the recording tap on. Default is 0.
private var bus: Int = 0
/// Used for fixing recordings being truncated
private var recordBufferDuration: Double = 16_384 / AKSettings.sampleRate
weak var delegate: NodeTapperDelegate?
// MARK: - Initialization
/// Initialize the node recorder
///
/// Recording buffer size is defaulted to be AKSettings.bufferLength
/// You can set a different value by setting an AKSettings.recordingBufferLength
///
/// - Parameters:
/// - node: Node to record from
/// - bus: Integer index of the bus to use
///
@objc init(node: AKNode? = AKManager.output,
bus: Int = 0) throws {
self.bus = bus
self.node = node
}
// MARK: - Methods
/// Start recording
@objc func start() throws {
if isTapping == true {
return
}
guard let node = node else {
return
}
guard let delegate = delegate else {
return
}
let bufferLength: AVAudioFrameCount = AKSettings.recordingBufferLength.samplesCount
isTapping = true
// Note: if you install a tap on a bus that already has a tap it will crash your application.
let nodeFormat = node.avAudioNode.outputFormat(forBus: 0)
try delegate.open(format: nodeFormat)
// 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: nil, // Might need to the input node's format :/
block: process(buffer:time:))
}
private func process(buffer: AVAudioPCMBuffer, time: AVAudioTime) {
guard let sink = delegate else { return }
sink.drip(buffer: buffer, time: time)
}
/// Stop recording
@objc func stop() {
if isTapping == false {
return
}
isTapping = false
if AKSettings.fixTruncatedRecordings {
// delay before stopping so the recording is not truncated.
let delay = UInt32(recordBufferDuration * 1_000_000)
usleep(delay)
}
node?.avAudioUnitOrNode.removeTap(onBus: bus)
delegate?.close()
}
}
然后,以某种方式将其完全绑定
let microphone = AKMicrophone()
microphone?.volume = 10 * volume
let monoToStereo = AKStereoFieldLimiter(microphone, amount: 1)
let microphoneMixer = AKMixer(monoToStereo)
// This is where we're converting the audio from
// the microphone and dripping it into the audio file
let converter = TapConverter(audioConfig: audioConfig)
// handleError is basically just a func in this case
converter.onError = handleError
// Here we tap the mixer/node and output to the converter
let tapper = try NodeTapper(node: microphoneMixer)
tapper.delegate = converter
// Silence the output from the microphone, so it's not
// fed back into the microphone
let silence = AKMixer(microphoneMixer)
silence.volume = 0
self.microphoneMixer = microphoneMixer
self.converter = converter
self.tapper = tapper
self.microphone = microphone
self.silence = silence
AKManager.output = silence
log(debug: "Start")
try AKManager.start()
log(debug: "Record")
try tapper.start()
其中大部分来自网络上不同帖子的不同想法的碎片,那么它是最佳选择吗?我不知道,但它做了我需要它做的事情
我有一些现有代码使用 AVAudioEngine
从麦克风获取输入,对其进行下采样并将其写入 AVAudioFile
internal func setupNodeChain() {
guard let audioEngine = audioEngine else { return } // Fatal error ?
let engineInputNode = audioEngine.inputNode
let bus = 0
let engineInputNodeFormat = engineInputNode.outputFormat(forBus: bus)
// This attempts to down sample the audio from the microphone
let downSampleMixerNode = AVAudioMixerNode()
let mixerOutputFormat = AVAudioFormat(standardFormatWithSampleRate: 8000, channels: 1)
// Input -> (volume) -> down sample -> (volume) -> Output
let inputVolumeMixerNode = AVAudioMixerNode()
inputVolumeMixerNode.volume = Float(10 * microphoneVolume)
audioEngine.attach(inputVolumeMixerNode)
audioEngine.attach(downSampleMixerNode)
self.downSampleMixerNode = downSampleMixerNode
self.inputVolumeMixerNode = inputVolumeMixerNode
let silenceNode = AVAudioMixerNode()
silenceNode.outputVolume = 0
self.silenceNode = silenceNode
audioEngine.connect(engineInputNode, to: inputVolumeMixerNode, format: engineInputNodeFormat)
audioEngine.connect(inputVolumeMixerNode, to: downSampleMixerNode, format: engineInputNodeFormat)
// Try and stop the microphone audio from going through to the speaker
audioEngine.attach(silenceNode)
audioEngine.connect(downSampleMixerNode, to: silenceNode, format: mixerOutputFormat)
audioEngine.connect(silenceNode, to: audioEngine.outputNode, format: mixerOutputFormat)
downSampleMixerNode.installTap(onBus: bus, bufferSize: 1024 * 16, format: mixerOutputFormat) { (buffer: AVAudioPCMBuffer, time: AVAudioTime) in
guard let tap = self.audioTap else { return }
// Write buffer to AVAudioFile
tap.drip(buffer: buffer, time: time)
}
}
这主要是可行的,但我正在研究用 AudioKit 替换它,但我遇到了问题,我不知道如何创建一种机制来对从麦克风到录音机的音频进行下采样.
AKSettings.enableEchoCancellation = true
AKSettings.allowAirPlay = true
AKSettings.useBluetooth = true
do {
try AKSettings.setSession(category: .playAndRecord,
with: [
.allowBluetoothA2DP,
])
AKSettings.defaultToSpeaker = true
let audioFile = try self.makeAudioFile(named: "Recording")
let mixerOutputFormat = AVAudioFormat(standardFormatWithSampleRate: 8000, channels: 1)!
let microphone = AKMicrophone()
let microphoneBooster = AKBooster(microphone)
microphoneBooster.gain = 0
let recorder = try AKNodeRecorder(node: microphoneBooster)
//recorder.recordFormat = mixerOutputFormat
let silence = AKMixer(microphoneBooster)
silence.volume = 0
self.microphone = microphone
self.microphoneBooster = microphoneBooster
self.recorder = recorder
self.silence = silence
AKManager.output = silence
log(debug: "Start")
try AKManager.start()
log(debug: "Record")
try recorder.record()
DispatchQueue.main.async {
self.state = .recording
self.plot?.node = microphone
self.callButton.setImage(#imageLiteral(resourceName: "EndCall"), for: [])
}
} catch let error {
log(error: "Failed to establish play and record session: \(error)")
}
所以,问题是 - 我将如何创建一个“下采样”node/workflow,这将 link 麦克风到具有“默认”格式的“节点”和link 链中下一个节点的“节点”与所需 AVAudioFormat
?
麦克风 -> 下采样(默认格式)
下采样->下一个节点(目标格式)->记录器
基本上,我必须创建自己的“tap”来挖掘数据
首先,我有一个“转换器”。这基本上是将来自另一个混音器的音频(通过“tap”)转换为目标格式并将其写入音频文件
class TapConverter: NodeTapperDelegate {
let audioConfig: AudioConfig
internal var inputFormat: AVAudioFormat?
internal var converter: AVAudioConverter?
var onError: ((Error) -> Void)?
init(audioConfig: AudioConfig) {
self.audioConfig = audioConfig
}
func open(format: AVAudioFormat) throws {
inputFormat = format
converter = AVAudioConverter(from: format, to: audioConfig.audioFormat)
}
func drip(buffer: AVAudioPCMBuffer, time: AVAudioTime) {
guard let converter = converter else {
return
}
guard let inputFormat = inputFormat else {
return
}
let inputBufferSize = inputFormat.sampleRate
let sampleRateRatio = inputBufferSize / audioConfig.audioFormat.sampleRate
let capacity = Int(Double(buffer.frameCapacity) / sampleRateRatio)
let bufferPCM16 = AVAudioPCMBuffer(pcmFormat: audioConfig.audioFormat, frameCapacity: AVAudioFrameCount(capacity))!
var error: NSError? = nil
converter.convert(to: bufferPCM16, error: &error) { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer
}
if let error = error {
// Handle error in someway
} else {
let audioFile = audioConfig.audioFile
do {
log(debug: "Write buffer")
try audioFile.write(from: bufferPCM16)
} catch let error {
log(error: "Failed to write buffer to audio file: \(error)")
onError?(error)
}
}
}
func close() {
converter = nil
inputFormat = nil
// we close the audio file
}
}
AudioConfig
只是一个基本的占位符,它包含写入的 audioFile
(必须已经创建)和目标 AVAudioFormat
struct AudioConfig {
let url: URL
let audioFile: AVAudioFile
let audioFormat: AVAudioFormat
}
创建可能类似于...
let settings: [String: Any] = [
AVFormatIDKey: NSNumber(value: kAudioFormatMPEG4AAC),
AVSampleRateKey: NSNumber(value: 8000),
AVNumberOfChannelsKey: NSNumber(value: 1),
AVEncoderBitRatePerChannelKey: NSNumber(value: 16),
AVEncoderAudioQualityKey: NSNumber(value: AVAudioQuality.min.rawValue)
]
let audioFile = try AVAudioFile(forWriting: sourceURL, settings: settings)
let audioConfig = AudioConfig(url: sourceURL, audioFile: audioFile, audioFormat: audioFormat)
从那里,我需要一种方法来点击节点(获取它的数据)并将其传递到我的转换器,为此,我使用了类似...
import Foundation
import AudioKit
protocol NodeTapperDelegate: class {
func open(format: AVAudioFormat) throws
func drip(buffer: AVAudioPCMBuffer, time: AVAudioTime)
func close()
}
class NodeTapper: NSObject {
// MARK: - Properties
// The node we record from
private(set) var node: AKNode?
/// True if we are recording.
@objc private(set) dynamic var isTapping = false
/// The bus to install the recording tap on. Default is 0.
private var bus: Int = 0
/// Used for fixing recordings being truncated
private var recordBufferDuration: Double = 16_384 / AKSettings.sampleRate
weak var delegate: NodeTapperDelegate?
// MARK: - Initialization
/// Initialize the node recorder
///
/// Recording buffer size is defaulted to be AKSettings.bufferLength
/// You can set a different value by setting an AKSettings.recordingBufferLength
///
/// - Parameters:
/// - node: Node to record from
/// - bus: Integer index of the bus to use
///
@objc init(node: AKNode? = AKManager.output,
bus: Int = 0) throws {
self.bus = bus
self.node = node
}
// MARK: - Methods
/// Start recording
@objc func start() throws {
if isTapping == true {
return
}
guard let node = node else {
return
}
guard let delegate = delegate else {
return
}
let bufferLength: AVAudioFrameCount = AKSettings.recordingBufferLength.samplesCount
isTapping = true
// Note: if you install a tap on a bus that already has a tap it will crash your application.
let nodeFormat = node.avAudioNode.outputFormat(forBus: 0)
try delegate.open(format: nodeFormat)
// 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: nil, // Might need to the input node's format :/
block: process(buffer:time:))
}
private func process(buffer: AVAudioPCMBuffer, time: AVAudioTime) {
guard let sink = delegate else { return }
sink.drip(buffer: buffer, time: time)
}
/// Stop recording
@objc func stop() {
if isTapping == false {
return
}
isTapping = false
if AKSettings.fixTruncatedRecordings {
// delay before stopping so the recording is not truncated.
let delay = UInt32(recordBufferDuration * 1_000_000)
usleep(delay)
}
node?.avAudioUnitOrNode.removeTap(onBus: bus)
delegate?.close()
}
}
然后,以某种方式将其完全绑定
let microphone = AKMicrophone()
microphone?.volume = 10 * volume
let monoToStereo = AKStereoFieldLimiter(microphone, amount: 1)
let microphoneMixer = AKMixer(monoToStereo)
// This is where we're converting the audio from
// the microphone and dripping it into the audio file
let converter = TapConverter(audioConfig: audioConfig)
// handleError is basically just a func in this case
converter.onError = handleError
// Here we tap the mixer/node and output to the converter
let tapper = try NodeTapper(node: microphoneMixer)
tapper.delegate = converter
// Silence the output from the microphone, so it's not
// fed back into the microphone
let silence = AKMixer(microphoneMixer)
silence.volume = 0
self.microphoneMixer = microphoneMixer
self.converter = converter
self.tapper = tapper
self.microphone = microphone
self.silence = silence
AKManager.output = silence
log(debug: "Start")
try AKManager.start()
log(debug: "Record")
try tapper.start()
其中大部分来自网络上不同帖子的不同想法的碎片,那么它是最佳选择吗?我不知道,但它做了我需要它做的事情