【问题标题】:Synchronize AVAudioPlayerNode and start of recording AVAudioEngine同步 AVAudioPlayerNode 并开始录制 AVAudioEngine
【发布时间】:2021-04-01 18:07:41
【问题描述】:

我正在使用 AVAudioEngine 来播放和录制音频。对于我的用例,我需要在开始录制音频时播放声音。目前,我的录音似乎是在播放声音之前开始的。如何让声音和录音同时开始?理想情况下,我希望同时开始录音和播放声音,而不是在后期处理中同步它们。

这是我目前的代码:

class Recorder {
  enum RecordingState {
    case recording, paused, stopped
  }
  
  private var engine: AVAudioEngine!
  private var mixerNode: AVAudioMixerNode!
  private var state: RecordingState = .stopped
    
    

  private var audioPlayer = AVAudioPlayerNode()
  
  init() {
    setupSession()
    setupEngine()
    
  }
    
    
  fileprivate func setupSession() {
      let session = AVAudioSession.sharedInstance()
    try? session.setCategory(.playAndRecord, options: [.mixWithOthers, .defaultToSpeaker])
      try? session.setActive(true, options: .notifyOthersOnDeactivation)
   }
    
    fileprivate func setupEngine() {
      engine = AVAudioEngine()
      mixerNode = AVAudioMixerNode()

      // Set volume to 0 to avoid audio feedback while recording.
      mixerNode.volume = 0

      engine.attach(mixerNode)

    engine.attach(audioPlayer)
        
      makeConnections()

      // Prepare the engine in advance, in order for the system to allocate the necessary resources.
      engine.prepare()
    }

    
    fileprivate func makeConnections() {
       
      let inputNode = engine.inputNode
      let inputFormat = inputNode.outputFormat(forBus: 0)
        print("Input Sample Rate: \(inputFormat.sampleRate)")
      engine.connect(inputNode, to: mixerNode, format: inputFormat)
      
      let mainMixerNode = engine.mainMixerNode
      let mixerFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: inputFormat.sampleRate, channels: 1, interleaved: false)
    
      engine.connect(mixerNode, to: mainMixerNode, format: mixerFormat)

      let path = Bundle.main.path(forResource: "effect1.wav", ofType:nil)!
      let url = URL(fileURLWithPath: path)
      let file = try! AVAudioFile(forReading: url)
      audioPlayer.scheduleFile(file, at: nil)
      engine.connect(audioPlayer, to: mainMixerNode, format: nil)
        
        }
    
    
    //MARK: Start Recording Function
    func startRecording() throws {
        print("Start Recording!")
      let tapNode: AVAudioNode = mixerNode
      let format = tapNode.outputFormat(forBus: 0)

      let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        
      // AVAudioFile uses the Core Audio Format (CAF) to write to disk.
      // So we're using the caf file extension.
        let file = try AVAudioFile(forWriting: documentURL.appendingPathComponent("recording.caf"), settings: format.settings)
       
      tapNode.installTap(onBus: 0, bufferSize: 4096, format: format, block: {
        (buffer, time) in

        try? file.write(from: buffer)
        print(buffer.description)
        print(buffer.stride)
       
        //Do Stuff
        print("Doing Stuff")
      })
    
      
      try engine.start()
        audioPlayer.play()
      state = .recording
    }
    
    
    //MARK: Other recording functions
    func resumeRecording() throws {
      try engine.start()
      state = .recording
    }

    func pauseRecording() {
      engine.pause()
      state = .paused
    }

    func stopRecording() {
      // Remove existing taps on nodes
      mixerNode.removeTap(onBus: 0)
      
      engine.stop()
      state = .stopped
    }
    

    
    
}

【问题讨论】:

  • 不幸的是(也许令人惊讶)我认为没有一种准确的方法可以在不测量延迟的情况下确定延迟
  • @sbooth 我如何测量延迟?
  • 理想的方式是使用从设备输出到输入的环回电缆。您将测试信号发送到输出,记录它发送的时间,然后在输入中检测相同的信号。时间差是往返延迟。有多种方法可以使用 AVAudioEngine 进行估计,但在我的实验中,没有一个是样本准确的。
  • 嗯...我可以这样做,但如果用户降低音量会怎样?这将使检测输出信号变得困难。如果您有办法使用AVAudioEngine 估算往返延迟,请分享它们作为答案。估计总比没有好!

标签: ios swift avfoundation avaudioengine


【解决方案1】:

您是否尝试过在安装水龙头之前启动播放器?

// Stop the player to be sure the engine.start calls the prepare function
audioPlayer.stop()
try engine.start()
audioPlayer.play()
state = .recording

tapNode.installTap(onBus: 0, bufferSize: 4096, format: format, block: {
        (buffer, time) in
        try? file.write(from: buffer)
      })

在这种情况下,如果您的录音有点晚了,不妨尝试使用player.outputPresentationLatency 进行补偿。 根据文档,这是一个最大值。这意味着时间可能会稍逊一筹。我希望它值得一试。

print(player.outputPresentationLatency)
// 0.009999999776482582

let nanoseconds = Int(player.outputPresentationLatency * pow(10,9))
let dispatchTimeInterval = DispatchTimeInterval.nanoseconds(nanoseconds)
            
player.play()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + dispatchTimeInterval) {
    self.installTap()
    self.state = .recording
}

【讨论】:

  • 当我尝试实施您的解决方案时,我收到错误 'Value of type 'AVAudioPlayerNode' has no member 'prepareToPlay'。请记住,audioPlayer 的类型为 AVAudioPlayerNode
  • 延迟也必须尽可能小,因为我可能会使用样本数来计算某些声音或模式在我的音频中出现的时间。
  • 糟糕。对不起。 AudioEngine.prepare 做同样的事情。我纠正我的答案。在回答之前我会尝试一些测试,因为奇怪的是准备功能还不够。也许使用回调
  • 我已经编辑了我的答案(喝完咖啡之后)。我会对结果非常感兴趣,因为我正在开发一个可能需要此功能和准确性的应用程序。不幸的是,我现在没有时间推动实验。请让我更新。 ;)
  • 这几天我很忙,所以没能做太多的测试。然而,我最初的发现表明,在点击之前启动播放器几乎没有什么作用。我很确定我以前试过这个。由于某种原因,我不得不删除audioPlayer.stop() 线才能播放声音。如果您知道它为什么这样做,请告诉我。至于延迟,我还没有研究那么多,但初步测试表明outputPresentationLatency 不是问题的唯一原因。我很快就会回来:)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-14
  • 1970-01-01
  • 2017-11-29
  • 2018-06-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多