录制时播放AudioFile的方法是什么?

时间:2017-05-30 20:40:56

标签: ios swift core-audio audioqueue caf

我正在编写一个先进先出录制应用,使用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")
      }
    })
  }

1 个答案:

答案 0 :(得分:3)

环形缓冲区是一种有用的结构,用于在内存或磁盘上存储最近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()
        }
    }
}