1

每当我将 AVAudioFile 设置framePosition为当前 framePosition 以外的其他内容时,我都会崩溃:

错误 - 6658

我在 Apple Docs 中查找的是:

kExtAudioFileError_InvalidSeek:写入尝试或偏移量超出范围。

这对我来说很奇怪,因为我可以确认我尝试设置的framePosition位置小于 AVAudioFile'slength并且高于 0,因此帧不应该超出范围。

代码:

  // Start Recording to file
  func startRecording(atTimeInFile: TimeInterval) throws {
    let tapNode: AVAudioNode = mixerNode
    let format = tapNode.outputFormat(forBus: 0)
    let semaphore = DispatchSemaphore(value: 0)
    
    duration = 0
    startTime = atTimeInFile
    
    // Check to see if recording file already has content
    
    let f: AVAudioFile? = {
      if FileManager.default.fileExists(atPath: FileManagerHelper.recordingLocalURL().relativePath) {
        return try? AVAudioFile(forReading: FileManagerHelper.recordingLocalURL())
      } else {
        return nil
      }
    }()
    
    if let f = f {
      // The file we opened for writing already has data written to it
      // Let's load the buffer data in from that the file
      guard let bufferData = AVAudioPCMBuffer(pcmFormat: f.processingFormat, frameCapacity: AVAudioFrameCount(f.length)) else {
        throw NSError(domain: "Failed to load the content of the existing file.", code: -10, userInfo: nil)
      }
      
      bufferData.frameLength = bufferData.frameCapacity
      
      do {
        try f.read(into: bufferData)
      } catch {
        throw NSError(domain: "Failed to read from the content of the existing file.", code: -20, userInfo: nil)
      }
      
      // AVAudioFile uses the Core Audio Format (CAF) to write to disk.
      // So we're using the caf file extension.
      file = try AVAudioFile(forWriting: FileManagerHelper.recordingLocalURL(), settings: format.settings)
      
      do {
        file!.framePosition = 0
        try self.file!.write(from: bufferData)
      } catch {
        throw NSError(domain: "Failed to write from the content of the existing file.", code: -30, userInfo: nil)
      }
    }
    
    else {
      file = try AVAudioFile(forWriting: FileManagerHelper.recordingLocalURL(), settings: format.settings)
    }

    if file!.duration == 0 {
      file!.framePosition = AVAudioFramePosition(0)
    } else {
      let percentThrough = CGFloat(atTimeInFile) / CGFloat(file!.duration)
      let pos = AVAudioFramePosition(percentThrough * CGFloat(file!.length))
      
      file!.framePosition = pos // CRASH OCCURS HERE
    }
    
    tapNode.installTap(onBus: 0, bufferSize: bufferSize, format: format, block: {
      (buffer, _) in
      do {
        buffer.frameLength = 1024 // Tap is now called every 40ms. By default, tap is called every 0.375s
        try self.file!.write(from: buffer)
        self.delegate?.recorderDidRecieveAudioBuffer(self, buffer: buffer)
        semaphore.signal()
      } catch {
        log("Error writing mic data to file.", msgType: .error)
      }
    })

    try engine.start()

    semaphore.wait()
    state = .recording
    startObservingTime()
  }

这是崩溃的完整描述:

    ExtAudioFile.cpp:1084:Seek: about to throw -66568: seek to frame in audio file
2020-11-09 18:01:46.032612-0800 Application[15316:800908] [avae]            AVAEInternal.h:109   [AVAudioFile.mm:515:-[AVAudioFile setFramePosition:]: (ExtAudioFileSeek(_imp->_extAudioFile, pos)): error -66568
2020-11-09 18:01:46.034491-0800 Application[15316:800908] *** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'error -66568'
*** First throw call stack:
(0x187b1c878 0x19c072c50 0x187a22000 0x197aa3e48 0x197b4270c 0x1004fc164 0x10048b43c 0x10048b198 0x10048b254 0x18a010e54 0x18a01a7b8 0x18a0173f4 0x18a01696c 0x18a00aa98 0x18a009e44 0x18a5111b4 0x18a4ea4ec 0x18a574488 0x18a577440 0x18a56e8ec 0x187a9876c 0x187a98668 0x187a97960 0x187a91a8c 0x187a9121c 0x19eb10784 0x18a4ca200 0x18a4cfa74 0x10048f270 0x1877516c0)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'error -66568'
terminating with uncaught exception of type NSException

更新 1:我注意到我可以设置 的 framePosition f,即为阅读而打开的 AVAudioFile,但我仍然无法设置 的fileframePosition ,为使用相同的 URL 编写打开的 AVAudioFile 。

更新 2:我尝试将文件写入一个单独的 URL,而不是通过替换读取它

file = try AVAudioFile(forWriting: FileManagerHelper.recordingLocalURL(), settings: format.settings)

file = try AVAudioFile(forWriting: FileManagerHelper.recordingLocalURL2(), settings: format.settings)

if let f = f街区。

但是,在设置成名位置时,我仍然会出现越界错误。

4

1 回答 1

0

我怀疑这是因为你先写,然后再尝试覆盖。也许尝试读入数据,然后将其写回。

当您创建写入文件以使用相同的格式时,我还添加了,如果它已经定义,以防出现问题。

  // Start Recording to file
  func startRecording(atTimeInFile: TimeInterval) throws {
    let tapNode: AVAudioNode = mixerNode
    let format = tapNode.outputFormat(forBus: 0)
    let semaphore = DispatchSemaphore(value: 0)
    
    duration = 0
    startTime = atTimeInFile

    // Check to see if recording file already has content
    let f: AVAudioFile? = {
      if FileManager.default.fileExists(atPath: FileManagerHelper.recordingLocalURL().relativePath) {
        return try? AVAudioFile(forReading: FileManagerHelper.recordingLocalURL())
      } else {
        return nil
      }
    }()
    
    // AVAudioFile uses the Core Audio Format (CAF) to write to disk.
    // So we're using the caf file extension.
    self.file = try? AVAudioFile(forWriting: FileManagerHelper.recordingLocalURL(),
                                 settings: format.settings,
                                 commonFormat: f?.fileFormat ?? format.commonFormat)

    guard let file = self.file else { throw NSError(domain: "Failed to open file for writing") }

    var pos = AVAudioFramePosition(0)
    if let dur = f?.duration,
       let len = f?.length {
      if dur != 0 {
        let percentThrough = CGFloat(atTimeInFile) / CGFloat(dur)
        pos = AVAudioFramePosition(percentThrough * CGFloat(len))
      }
    }

    tapNode.installTap(onBus: 0,
                       bufferSize: bufferSize,
                       format: format,
                       block: { (buffer, _) in
      do {
        if let fread = f {
          guard let bufferData = AVAudioPCMBuffer(pcmFormat: fread.processingFormat,
                                                  frameCapacity: AVAudioFrameCount(pos)) else {
            throw NSError(domain: "Failed to load the content of the existing file.",
                          code: 0,
                          userInfo: nil)
          }

          // read in the data before
          fread.framePosition = AVFramePosition(0)
          try fread.read(into: bufferData)

          // write it to file
          try file.write(from: bufferData)

          // now write the new data
          buffer.frameLength = 1024 // Tap is now called every 40ms. By default, tap is called every 0.375s
          try file.write(from: buffer)

          // now read in the rest if possible
          if file.framePosition < fread.length {
            guard let afterBufferData = AVAudioPCMBuffer(pcmFormat: fread.processingFormat,
                                                         frameCapacity: AVAudioFrameCount(fread.length - file.framePosition)) else {
              throw NSError(domain: "Failed to load extra content of the existing file.",
                            code: 0,
                            userInfo: nil)
            }

            fread.framePosition = file.framePosition
            try fread.read(into: afterBufferData)
            try file.write(from: afterBufferData)
          }
        } else {
          // now write the new data if there wasn't pre-existing
          buffer.frameLength = 1024 // Tap is now called every 40ms. By default, tap is called every 0.375s
          file.write(from: buffer)
        }

        // end the closure
        self.delegate?.recorderDidRecieveAudioBuffer(self, buffer: buffer)
        semaphore.signal()
      } catch {
        semaphore.signal()
        log("Error writing mic data to file.", msgType: .error)
      }
    })

    try engine.start()

    _ = semaphore.wait()
    state = .recording
    startObservingTime()
  }

此外,如果您每 40 毫秒执行一次,那么这可能会非常慢。我建议重构以读取所有“tap”缓冲区,然后复制读取旧的、写入新的、写入旧的结构。

于 2020-11-10T05:21:49.193 回答