将AVAudioPCMBuffer转换为另一个AVAudioPCMBuffer

时间:2018-02-16 16:51:14

标签: avaudioengine audiokit avaudiofile

我正在尝试使用AVAudioPCMBuffer将确定的AVAudioPCMBuffer(44.1khz,1ch,float32,不交错)转换为另一个AVAudioConverter(16khz,1ch,int16,不交错)用AVAudioFile写下来。 我的代码使用库AudioKit和点击AKLazyTap根据此来源在每个确定的时间获取缓冲区:

https://github.com/AudioKit/AudioKit/tree/master/AudioKit/Common/Taps/Lazy%20Tap

这是我的实施:

lazy var downAudioFormat: AVAudioFormat = {
  let avAudioChannelLayout = AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_Mono)!
  return AVAudioFormat(
      commonFormat: .pcmFormatInt16,
      sampleRate: 16000,
      interleaved: false,
      channelLayout: avAudioChannelLayout)
}()

//...
AKSettings.sampleRate = 44100
AKSettings.numberOfChannels = AVAudioChannelCount(1)
AKSettings.ioBufferDuration = 0.002
AKSettings.defaultToSpeaker = true

//...
let mic = AKMicrophone()
let originalAudioFormat: AVAudioFormat = mic.avAudioNode.outputFormat(forBus: 0) //41.100, 1ch, float32...
let inputFrameCapacity = AVAudioFrameCount(1024)
//I don't think this is correct, the audio is getting chopped... 
//How to calculate it correctly?
let outputFrameCapacity = AVAudioFrameCount(512)

guard let inputBuffer = AVAudioPCMBuffer(
  pcmFormat: originalAudioFormat,
  frameCapacity: inputFrameCapacity) else {
  fatalError()
}

// Your timer should fire equal to or faster than your buffer duration
bufferTimer = Timer.scheduledTimer(
  withTimeInterval: AKSettings.ioBufferDuration/2,
  repeats: true) { [weak self] _ in

  guard let unwrappedSelf = self else {
    return
  }

  unwrappedSelf.lazyTap?.fillNextBuffer(inputBuffer, timeStamp: nil)

  // This is important, since we're polling for samples, sometimes
  //it's empty, and sometimes it will be double what it was the last call.
  if inputBuffer.frameLength == 0 {
    return
  }

  //This converter is only create once, as the AVAudioFile. Ignore this code I call a function instead.
  let converter = AVAudioConverter(from: originalAudioFormat, to: downAudioFormat)
  converter.sampleRateConverterAlgorithm = AVSampleRateConverterAlgorithm_Normal
  converter.sampleRateConverterQuality = .min
  converter.bitRateStrategy = AVAudioBitRateStrategy_Constant

  guard let outputBuffer = AVAudioPCMBuffer(
      pcmFormat: converter.outputFormat,
      frameCapacity: outputFrameCapacity) else {
    print("Failed to create new buffer")
    return
  }

  let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
    outStatus.pointee = AVAudioConverterInputStatus.haveData
    return inputBuffer
  }

  var error: NSError?
  let status: AVAudioConverterOutputStatus = converter.convert(
      to: outputBuffer,
      error: &error,
      withInputFrom: inputBlock)

  switch status {
  case .error:
    if let unwrappedError: NSError = error {
      print(unwrappedError)
    }
    return
  default: break
  }

  //Only created once, instead of this code my code uses a function to verify if the AVAudioFile has been created, ignore it.
  outputAVAudioFile = try AVAudioFile(
    forWriting: unwrappedCacheFilePath,
    settings: format.settings,
    commonFormat: format.commonFormat,
    interleaved: false)

  do {
    try outputAVAudioFile?.write(from: avAudioPCMBuffer)
  } catch {
    print(error)
  }

}

(请注意,AVAudioConverterAVAudioFile正在重复使用,初始化并不能代表我的代码上的实际实现,只是为了简化并使其更简单理解。)

frameCapacity上的outputBuffer: AVAudioPCMBuffer设置为512,音频会被切断。有没有办法为这个缓冲区发现正确的frameCapacity

使用Swift 4和AudioKit 4.1编写。

非常感谢!

1 个答案:

答案 0 :(得分:1)

我设法解决了这个问题,在Tap上安装了inputNode,如下所示:

lazy var downAudioFormat: AVAudioFormat = {
  let avAudioChannelLayout = AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_Mono)!
  return AVAudioFormat(
      commonFormat: .pcmFormatInt16,
      sampleRate: SAMPLE_RATE,
      interleaved: true,
      channelLayout: avAudioChannelLayout)
}()

private func addBufferListener(_ avAudioNode: AVAudioNode) {

  let originalAudioFormat: AVAudioFormat = avAudioNode.inputFormat(forBus: 0)
  let downSampleRate: Double = downAudioFormat.sampleRate
  let ratio: Float = Float(originalAudioFormat.sampleRate)/Float(downSampleRate)
  let converter: AVAudioConverter = buildConverter(originalAudioFormat)

  avAudioNode.installTap(
      onBus: 0,
      bufferSize: AVAudioFrameCount(downSampleRate * 2),
      format: originalAudioFormat,
      block: { (buffer: AVAudioPCMBuffer!, _ : AVAudioTime!) -> Void in

        let capacity = UInt32(Float(buffer.frameCapacity)/ratio)

        guard let outputBuffer = AVAudioPCMBuffer(
            pcmFormat: self.downAudioFormat,
            frameCapacity: capacity) else {
          print("Failed to create new buffer")
          return
        }

        let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
          outStatus.pointee = AVAudioConverterInputStatus.haveData
          return buffer
        }

        var error: NSError?
        let status: AVAudioConverterOutputStatus = converter.convert(
            to: outputBuffer,
            error: &error,
            withInputFrom: inputBlock)

        switch status {
        case .error:
          if let unwrappedError: NSError = error {
            print("Error \(unwrappedError)"))
          }
          return
        default: break
        }

        self.delegate?.flushAudioBuffer(outputBuffer)

  })

}