【问题标题】:How can I specify the format of AVAudioEngine Mic-Input?如何指定 AVAudioEngine Mic-Input 的格式?
【发布时间】:2016-02-02 17:18:42
【问题描述】:

我想使用AVAudioEngine 和用户麦克风录制一些音频。我已经有一个工作示例,但不知道如何指定我想要的输出格式...

我的要求是我需要AVAudioPCMBuffer,正如我所说的那样......

我需要添加一个单独的节点来进行一些转码吗?我找不到太多关于该问题的文档/示例...

在音频方面,我也是个菜鸟。我知道我想要 NSData 包含最大采样率为 16000 的 PCM-16bit(8000 会更好)

这是我的工作示例:

private var audioEngine = AVAudioEngine()

func startRecording() {

  let format = audioEngine.inputNode!.inputFormatForBus(bus)

  audioEngine.inputNode!.installTapOnBus(bus, bufferSize: 1024, format: format) { (buffer: AVAudioPCMBuffer, time:AVAudioTime) -> Void in

     let audioFormat = PCMBuffer.format
     print("\(audioFormat)")
  }

  audioEngine.prepare()
  do {
     try audioEngine.start()
  } catch { /* Imagine some super awesome error handling here */ }
}

如果我将格式更改为让'说

let format = AVAudioFormat(commonFormat: AVAudioCommonFormat.PCMFormatInt16, sampleRate: 8000.0, channels: 1, interleaved: false)

then if 会产生一个错误,说采样率需要与 hwInput 相同...

非常感谢任何帮助!!!

编辑:我刚刚找到AVAudioConverter,但我还需要兼容iOS8...

【问题讨论】:

    标签: ios avfoundation core-audio avaudioengine


    【解决方案1】:

    你不能改变输入节点的配置,尝试用你想要的格式创建一个混音器节点,将它附加到引擎,然后将它连接到输入节点,然后将mainMixer连接到你刚刚创建的节点.现在你可以在这个节点上安装一个 tap 来获取 PCM 数据。

    请注意,由于某些奇怪的原因,您对采样率没有太多选择!至少在 iOS 9.1 上不行,使用标准 11025、22050 或 44100。任何其他采样率都会失败!

    【讨论】:

    • 看起来好像要使 8000 Hz 工作,您必须在 AVAudioSession.sharedInstance() 上调用 session.setPreferredSampleRate(8000)。它实际上会将采样率更改为 16000,但随后 Mixer 可以将其重新采样到 8kHz。
    • 如果 sampleRate 不匹配仍然会导致崩溃
    【解决方案2】:

    您不能直接在输入或输出节点上更改音频格式。对于麦克风,格式始终为 44KHz、1 通道、32 位。为此,您需要在两者之间插入一个混音器。然后在连接inputNode > changeformatMixer > mainEngineMixer的时候,可以指定你想要的格式细节。

    类似:

    var inputNode = audioEngine.inputNode
    var downMixer = AVAudioMixerNode()
    
    //I think you the engine's I/O nodes are already attached to itself by default, so we attach only the downMixer here:
    audioEngine.attachNode(downMixer)
    
    //You can tap the downMixer to intercept the audio and do something with it:
    downMixer.installTapOnBus(0, bufferSize: 2048, format: downMixer.outputFormatForBus(0), block:  //originally 1024
                { (buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
                    print(NSString(string: "downMixer Tap"))
                    do{
                        print("Downmixer Tap Format: "+self.downMixer.outputFormatForBus(0).description)//buffer.audioBufferList.debugDescription)
    
            })
    
    //let's get the input audio format right as it is
    let format = inputNode.inputFormatForBus(0)
    //I initialize a 16KHz format I need:
    let format16KHzMono = AVAudioFormat.init(commonFormat: AVAudioCommonFormat.PCMFormatInt16, sampleRate: 11050.0, channels: 1, interleaved: true)
    
    //connect the nodes inside the engine:
    //INPUT NODE --format-> downMixer --16Kformat--> mainMixer
    //as you can see I m downsampling the default 44khz we get in the input to the 16Khz I want 
    audioEngine.connect(inputNode, to: downMixer, format: format)//use default input format
    audioEngine.connect(downMixer, to: audioEngine.outputNode, format: format16KHzMono)//use new audio format
    //run the engine
    audioEngine.prepare()
    try! audioEngine.start()
    

    不过,我建议使用开放式框架,例如 EZAudio。

    【讨论】:

    • 您的mainMixerNode 未使用。在您的示例中,您正在连接到 outputNode。为什么?
    • 这是 iOS 的限制,您会看到一堆格式,但实际上只有 2 或 3 种有效。 @ErikAigner 我
    • @ErikAigner (迟到总比没有好) mainMixerNode 根本没有使用,(我的错,这是一个过时的代码行,我更新了我的答案)。这里的主角是downMixer节点,它的下采样输出连接到音频引擎的outputNode。
    • @Josh 我用那个代码块在扬声器上听到我自己的声音。有什么办法可以预防吗?
    • @Daedelus 我没有这个问题,请注意拼写错误或交叉变量名。
    【解决方案3】:

    我发现唯一能改变采样率的是

    AVAudioSettings.sharedInstance().setPreferredSampleRate(...)
    

    您可以点击 engine.inputNode 并使用输入节点的输出格式:

    engine.inputNode.installTap(onBus: 0, bufferSize: 2048,
                                format: engine.inputNode.outputFormat(forBus: 0))
    

    不幸的是,虽然 8000、12000、16000、22050、44100 似乎都有效,但无法保证您将获得所需的采样率。

    以下操作无效:

    1. 在分接 engine.inputNode 中设置我的自定义格式。 (例外)
    2. 使用我的自定义格式添加混音器并点击它。 (例外)
    3. 添加一个混音器,将其与 inputNode 的格式连接,将混音器连接到具有我自定义格式的主混音器,然后删除 outputNode 的输入,以免将音频发送到扬声器并获得即时反馈。 (有效,但全为零)
    4. 在 AVAudioEngine 中根本不使用我的自定义格式,而是使用 AVAudioConverter 从我的 Tap 中的硬件速率转换。 (缓冲区的长度没有设置,无法判断结果是否正确)

    这适用于 iOS 12.3.1。

    【讨论】:

    【解决方案4】:

    如果您只需要更改采样率和通道,我建议使用行级 API。您不需要使用混音器或转换器。在这里您可以找到有关低级录音的 Apple 文档。如果需要,您将能够转换为 Objective-C 类并添加协议。

    Audio Queue Services Programming Guide

    【讨论】:

      【解决方案5】:

      为了改变输入节点的采样率,你必须先将输入节点连接到一个混合器节点,并在参数中指定一个新的格式。

      let input = avAudioEngine.inputNode
      let mainMixer = avAudioEngine.mainMixerNode
      let newAudioFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100, channels: 1, interleaved: true)
      avAudioEngine.connect(input, to: mainMixer, format: newAudioFormat)
      

      现在您可以使用 newAudioFormat 在输入节点上调用 installTap 函数。

      还有一点要指出的是,自从 iPhone12 新推出以来,输入节点的默认采样率已经不再是 44100。已经升级到48000了。

      【讨论】:

      【解决方案6】:

      如果您的目标只是最终获得包含所需格式音频的 AVAudioPCMBuffers,您可以使用 AVAudioConverter 转换在 tap 块中返回的缓冲区。这样一来,您实际上就不需要知道或关心 inputNode 的格式是什么。

      class MyBufferRecorder {
          
          private let audioEngine:AVAudioEngine = AVAudioEngine()
          private var inputNode:AVAudioInputNode!
          private let audioQueue:DispatchQueue = DispatchQueue(label: "Audio Queue 5000")
          private var isRecording:Bool = false
          
          func startRecording() {
              
              if (isRecording) {
                  return
              }
              isRecording = true
              
              // must convert (unknown until runtime) input format to our desired output format
              inputNode = audioEngine.inputNode
              let inputFormat:AVAudioFormat! = inputNode.outputFormat(forBus: 0)
          
              // 9600 is somewhat arbitrary... min seems to be 4800, max 19200... it doesn't matter what we set
              // because we don't re-use this value -- we query the buffer returned in the tap block for it's true length.
              // Using [weak self] in the tap block is probably a better idea, but it results in weird warnings for now
              inputNode.installTap(onBus: 0, bufferSize: AVAudioFrameCount(9600), format: inputFormat) { (buffer, time) in
                  
                  // not sure if this is necessary
                  if (!self.isRecording) {
                      print("\nDEBUG - rejecting callback, not recording")
                      return }
                  
                  // not really sure if/why this needs to be async
                  self.audioQueue.async {
      
                      // Convert recorded buffer to our preferred format
                      
                      let convertedPCMBuffer = AudioUtils.convertPCMBuffer(bufferToConvert: buffer, fromFormat: inputFormat, toFormat: AudioUtils.desiredFormat)
                  
                      // do something with converted buffer
                  }
              }
              do {
                  // important not to start engine before installing tap
                  try audioEngine.start()
              } catch {
                  print("\nDEBUG - couldn't start engine!")
                  return
              }
              
          }
          
          func stopRecording() {
              print("\nDEBUG - recording stopped")
              isRecording = false
              inputNode.removeTap(onBus: 0)
              audioEngine.stop()
          }
          
      }
      

      单独的类:

      import Foundation
      import AVFoundation
      
      // assumes we want 16bit, mono, 44100hz
      // change to what you want
      class AudioUtils {
          
          static let desiredFormat:AVAudioFormat! = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: Double(44100), channels: 1, interleaved: false)
          
          // PCM <--> PCM
          static func convertPCMBuffer(bufferToConvert: AVAudioPCMBuffer, fromFormat: AVAudioFormat, toFormat: AVAudioFormat) -> AVAudioPCMBuffer {
              
              let convertedPCMBuffer = AVAudioPCMBuffer(pcmFormat: toFormat, frameCapacity: AVAudioFrameCount(bufferToConvert.frameLength))
              var error: NSError? = nil
              
              let inputBlock:AVAudioConverterInputBlock = {inNumPackets, outStatus in
                  outStatus.pointee = AVAudioConverterInputStatus.haveData
                  return bufferToConvert
              }
              let formatConverter:AVAudioConverter = AVAudioConverter(from:fromFormat, to: toFormat)!
              formatConverter.convert(to: convertedPCMBuffer!, error: &error, withInputFrom: inputBlock)
              
              if error != nil {
                  print("\nDEBUG - " + error!.localizedDescription)
              }
              
              return convertedPCMBuffer!
              
          }
      }
      

      这绝不是生产就绪代码——我也在学习 IOS 音频...所以请告诉我该代码中发生的任何错误、最佳实践或危险事情,我会保留这个答案已更新。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-04
        • 2018-03-04
        • 2014-08-15
        • 2011-06-30
        相关资源
        最近更新 更多