录制时裁剪音频文件的方法?

A way to crop AudioFile while recording?

我正在编写一个先进先出的录音应用程序,它使用 AudioQueue 缓冲最多 2.5 分钟的音频。我已经弄明白了大部分,但我在尝试裁剪音频数据时遇到了障碍。

我见过有人用 AVAssetExportSession 这样做,但每次调用 AudioQueueInputCallback 时导出新曲目似乎性能不佳。

如果有人有更好的主意,我不会以任何方式使用 AVAssestExportSession

这是我正在写的地方,希望能执行裁剪。

 var beforeSeconds = TimeInterval() // find the current estimated duration (not reliable)
    var propertySize = UInt32(MemoryLayout.size(ofValue: beforeSeconds))
    var osStatus = AudioFileGetProperty(audioRecorder.recordFile!, kAudioFilePropertyEstimatedDuration, &propertySize, &beforeSeconds)

    if numPackets > 0 {
      AudioFileWritePackets(audioRecorder.recordFile!, // write to disk
                                       false,
                                       buffer.mAudioDataByteSize,
                                       packetDescriptions,
                                       audioRecorder.recordPacket,
                                       &numPackets,
                                       buffer.mAudioData)
      audioRecorder.recordPacket += Int64(numPackets) // up the packet index

      var afterSeconds = TimeInterval() // find the after write estimated duration (not reliable)
      var propertySize = UInt32(MemoryLayout.size(ofValue: afterSeconds))
      var osStatus = AudioFileGetProperty(audioRecorder.recordFile!, kAudioFilePropertyEstimatedDuration, &propertySize, &afterSeconds)
      assert(osStatus == noErr, "couldn't get record time")

      if afterSeconds >= 150.0 {
        print("hit max buffer!")
        audioRecorder.onBufferMax?(afterSeconds - beforeSeconds)
      }
    }

这里是执行回调的地方

func onBufferMax(_ difference: Double){
    let asset = AVAsset(url: tempFilePath)
    let duration = CMTimeGetSeconds(asset.duration)
    guard duration >= 150.0 else { return }

    guard let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else {
      print("exporter init failed")
      return }


    exporter.outputURL = getDocumentsDirectory().appendingPathComponent("buffered.caf") // helper function that calls the FileManager
    exporter.outputFileType = AVFileTypeAppleM4A

    let startTime = CMTimeMake(Int64(difference), 1)
    let endTime = CMTimeMake(Int64(WYNDRConstants.maxTimeInterval + difference), 1)

    exporter.timeRange = CMTimeRangeFromTimeToTime(startTime, endTime)
    exporter.exportAsynchronously(completionHandler: {
      switch exporter.status {
      case .failed:
        print("failed to export")
      case .cancelled:
        print("canceled export")
      default:
        print("export successful")
      }
    })
  }

环形缓冲区是一种有用的结构,可用于在内存或磁盘上存储最近 n 秒的音频。这是一个将音频存储在内存中的简单解决方案,以传统的 UIViewController 格式呈现。

N.B 2.5 分钟的 44.1kHz 音频存储为浮点数需要大约 26MB 的 RAM,这对于移动设备来说太重了。

import AVFoundation

class ViewController: UIViewController {
    let engine = AVAudioEngine()

    var requiredSamples: AVAudioFrameCount = 0
    var ringBuffer: [AVAudioPCMBuffer] = []
    var ringBufferSizeInSamples: AVAudioFrameCount = 0

    func startRecording() {
        let input = engine.inputNode!

        let bus = 0
        let inputFormat = input.inputFormat(forBus: bus)

        requiredSamples = AVAudioFrameCount(inputFormat.sampleRate * 2.5 * 60)

        input.installTap(onBus: bus, bufferSize: 512, format: inputFormat) { (buffer, time) -> Void in
            self.appendAudioBuffer(buffer)
        }

        try! engine.start()
    }

    func appendAudioBuffer(_ buffer: AVAudioPCMBuffer) {
        ringBuffer.append(buffer)
        ringBufferSizeInSamples += buffer.frameLength

        // throw away old buffers if ring buffer gets too large
        if let firstBuffer = ringBuffer.first {
            if ringBufferSizeInSamples - firstBuffer.frameLength >= requiredSamples {
                ringBuffer.remove(at: 0)
                ringBufferSizeInSamples -= firstBuffer.frameLength
            }
        }
    }

    func stopRecording() {
        engine.stop()

        let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("foo.m4a")
        let settings: [String : Any] = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC)]

        // write ring buffer to file.
        let file = try! AVAudioFile(forWriting: url, settings: settings)
        for buffer in ringBuffer {
            try! file.write(from: buffer)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // example usage
        startRecording()

        DispatchQueue.main.asyncAfter(deadline: .now() + 4*60) {
            print("stopping")
            self.stopRecording()
        }
    }
}