在 Swift 中使用 AVAudioEngine 点击麦克风输入
Posted
技术标签:
【中文标题】在 Swift 中使用 AVAudioEngine 点击麦克风输入【英文标题】:Tap Mic Input Using AVAudioEngine in Swift 【发布时间】:2015-01-27 23:54:40 【问题描述】:我对新的 AVAudioEngine 感到非常兴奋。它似乎是围绕音频单元的一个很好的 API 包装器。不幸的是,到目前为止该文档不存在,而且我在让一个简单的图表工作时遇到了问题。
使用以下简单代码设置音频引擎图,从不调用点击块。它模仿了一些在网络上流传的示例代码,尽管这些也不起作用。
let inputNode = audioEngine.inputNode
var error: NSError?
let bus = 0
inputNode.installTapOnBus(bus, bufferSize: 2048, format: inputNode.inputFormatForBus(bus))
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
println("sfdljk")
audioEngine.prepare()
if audioEngine.startAndReturnError(&error)
println("started audio")
else
if let engineStartError = error
println("error starting audio: \(engineStartError.localizedDescription)")
我正在寻找的只是用于分析的原始 pcm 缓冲区。我不需要任何效果或输出。根据 WWDC 演讲“实践中的 502 音频引擎”,此设置应该可以工作。
现在如果你想从输入节点捕获数据,你可以安装一个节点水龙头,我们已经讨论过了。
但这个特定示例的有趣之处在于,如果我只想使用输入节点,比如从麦克风捕获数据并可能对其进行检查、实时分析或将其写入文件,我可以直接在输入节点上安装一个tap。
tap 将完成拉取数据输入节点的工作,将其填充到缓冲区中,然后将其返回给应用程序。
一旦你有了这些数据,你就可以用它做任何你需要做的事情。
以下是我尝试的一些链接:
-
http://hondrouthoughts.blogspot.com/2014/09/avfoundation-audio-monitoring.html
http://jamiebullock.com/post/89243252529/live-coding-audio-with-swift-playgrounds(在 Playground 中的 startAndReturnError SIGABRT)
编辑:这是基于 Thorsten Karrer 建议的实现。不幸的是,它不起作用。
class AudioProcessor
let audioEngine = AVAudioEngine()
init()
let inputNode = audioEngine.inputNode
let bus = 0
var error: NSError?
inputNode.installTapOnBus(bus, bufferSize: 2048, format:inputNode.inputFormatForBus(bus))
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
println("sfdljk")
audioEngine.prepare()
audioEngine.startAndReturnError(nil)
println("started audio")
【问题讨论】:
【参考方案1】:这可能是因为您的 AVAudioEngine 超出范围并由 ARC 发布(“如果您喜欢它,那么您应该在其上添加保留...”)。
以下代码(引擎被移动到一个 ivar 并因此停留)触发水龙头:
class AppDelegate: NSObject, NSApplicationDelegate
let audioEngine = AVAudioEngine()
func applicationDidFinishLaunching(aNotification: NSNotification)
let inputNode = audioEngine.inputNode
let bus = 0
inputNode.installTapOnBus(bus, bufferSize: 2048, format: inputNode.inputFormatForBus(bus))
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
println("sfdljk")
audioEngine.prepare()
audioEngine.startAndReturnError(nil)
(为简洁起见,我删除了错误处理)
【讨论】:
我尝试了这个并用使用的代码编辑了我的问题。您的答案中的代码对您有用吗? Jup,为我工作。我没有在操场上测试过它,但是当从 Xcode 编译运行时它可以工作。连续调用点击块。 遗憾的是,我不得不承认它毕竟是一个未保留的对象。我的 AudioProcessor 类在方法中被实例化,而不是作为类属性。 您为什么选择带有“通知”参数的 appDelegate 方法?默认/事实上的一个是 didFinishLaunchingWithOptions: 原因是在我写答案的时候(2014),Xcode 会给我默认的通知参数。对于手头的问题,无论如何都无所谓。【参考方案2】:更新:我已经实现了一个完整的记录麦克风输入的工作示例,在运行时应用一些效果(混响、延迟、失真),并将所有这些效果保存到输出文件中。
var engine = AVAudioEngine()
var distortion = AVAudioUnitDistortion()
var reverb = AVAudioUnitReverb()
var audioBuffer = AVAudioPCMBuffer()
var outputFile = AVAudioFile()
var delay = AVAudioUnitDelay()
//初始化音频引擎
func initializeAudioEngine()
engine.stop()
engine.reset()
engine = AVAudioEngine()
isRealTime = true
do
try AVAudiosession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
let ioBufferDuration = 128.0 / 44100.0
try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(ioBufferDuration)
catch
assertionFailure("AVAudioSession setup error: \(error)")
let fileUrl = URLFor("/NewRecording.caf")
print(fileUrl)
do
try outputFile = AVAudioFile(forWriting: fileUrl!, settings: engine.mainMixerNode.outputFormatForBus(0).settings)
catch
let input = engine.inputNode!
let format = input.inputFormatForBus(0)
//settings for reverb
reverb.loadFactoryPreset(.MediumChamber)
reverb.wetDryMix = 40 //0-100 range
engine.attachNode(reverb)
delay.delayTime = 0.2 // 0-2 range
engine.attachNode(delay)
//settings for distortion
distortion.loadFactoryPreset(.DrumsBitBrush)
distortion.wetDryMix = 20 //0-100 range
engine.attachNode(distortion)
engine.connect(input, to: reverb, format: format)
engine.connect(reverb, to: distortion, format: format)
engine.connect(distortion, to: delay, format: format)
engine.connect(delay, to: engine.mainMixerNode, format: format)
assert(engine.inputNode != nil)
isReverbOn = false
try! engine.start()
//现在的录音功能:
func startRecording()
let mixer = engine.mainMixerNode
let format = mixer.outputFormatForBus(0)
mixer.installTapOnBus(0, bufferSize: 1024, format: format, block:
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
print(NSString(string: "writing"))
do
try self.outputFile.writeFromBuffer(buffer)
catch
print(NSString(string: "Write failed"));
)
func stopRecording()
engine.mainMixerNode.removeTapOnBus(0)
engine.stop()
我希望这对你有帮助。谢谢!
【讨论】:
这行得通,但是有没有办法让它在没有实时从扬声器发出的效果的情况下工作? @user3344977 我目前正在研究一个我没有成功实施的类似模块。即带有效果的音频文件的离线渲染。这是我关注的帖子:[离线渲染] (***.com/a/15361251/2429443)。希望对你有帮助 这些值从何而来?:let ioBufferDuration = 128.0 / 44100.0 有用的代码。现在我有关于这个 AudioEngine 如何与 AudioUnits 之间的所有连接一起工作的代码。 效果很好。但是,如果添加 AVAudioUnitTimePitch 节点,它会崩溃:(【参考方案3】:以上答案对我不起作用,但以下答案对我有用。我正在混音器节点上安装水龙头。
mMixerNode?.installTapOnBus(0, bufferSize: 4096, format: mMixerNode?.outputFormatForBus(0),
(buffer: AVAudioPCMBuffer!, time:AVAudioTime!) -> Void in
NSLog("tapped")
)
【讨论】:
【参考方案4】:好话题
你好,布罗德尼
在您的主题中,我找到了我的解决方案。这是类似的主题Generate AVAudioPCMBuffer with AVAudioRecorder
参见 Wwdc 2014 502 讲座 - AVAudioEngine 在实践中捕获麦克风 => 在 20 分钟内使用点击代码创建缓冲区 => 在 21 .50 中
这里是 swift 3 代码
@IBAction func button01Pressed(_ sender: Any)
let inputNode = audioEngine.inputNode
let bus = 0
inputNode?.installTap(onBus: bus, bufferSize: 2048, format: inputNode?.inputFormat(forBus: bus))
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
var theLength = Int(buffer.frameLength)
print("theLength = \(theLength)")
var samplesAsDoubles:[Double] = []
for i in 0 ..< Int(buffer.frameLength)
var theSample = Double((buffer.floatChannelData?.pointee[i])!)
samplesAsDoubles.append( theSample )
print("samplesAsDoubles.count = \(samplesAsDoubles.count)")
audioEngine.prepare()
try! audioEngine.start()
停止音频
func stopAudio()
let inputNode = audioEngine.inputNode
let bus = 0
inputNode?.removeTap(onBus: bus)
self.audioEngine.stop()
【讨论】:
我也想出了一个类似的解决方案。事情只会工作一次,然后在 5 分钟内不会再次工作。当您的应用程序将要终止时(我正在从调试器中执行此操作),您需要移除水龙头并停止音频引擎。我还将 AudioSession 设置为非活动状态以上是关于在 Swift 中使用 AVAudioEngine 点击麦克风输入的主要内容,如果未能解决你的问题,请参考以下文章
SWIFT - 是不是可以从 AVAudioEngine 或 AudioPlayerNode 保存音频?如果是,如何?
Swift AVAudioEngine - 如何使本地麦克风静音