【问题标题】:Tap Mic Input Using AVAudioEngine in Swift在 Swift 中使用 AVAudioEngine 点击麦克风输入
【发布时间】: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 将完成拉取数据输入节点的工作,将其填充到缓冲区中,然后将其返回给应用程序。

一旦你有了这些数据,你就可以用它做任何你需要做的事情。

以下是我尝试的一些链接:

  1. http://hondrouthoughts.blogspot.com/2014/09/avfoundation-audio-monitoring.html
  2. 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")
    }
}

【问题讨论】:

    标签: ios swift avfoundation core-audio ios8.1


    【解决方案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 我目前正在研究一个我没有成功实施的类似模块。即带有效果的音频文件的离线渲染。这是我关注的帖子:[离线渲染] (stackoverflow.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 设置为非活动状态
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多