title: Recording gapless audio with AVAssetWriter
body: <p>I'm trying to record segments of audio and recombine them without producing a gap in audio.</p>
  
  <p>The eventual goal is to also have video, but I've found that audio itself creates gaps when combined with <code>ffmpeg -f concat -i list.txt -c copy out.mp4</code></p>
  
  <p>If I put the audio in an HLS playlist, there are also gaps, so I don't think this is unique to ffmpeg. </p>
  
  <p>The idea is that samples come in continuously, and my controller routes samples to the proper <code>AVAssetWriter</code>. How do I eliminate gaps in audio?</p>
  
  <pre><code>import Foundation
  import UIKit
  import AVFoundation
  
  class StreamController: UIViewController, AVCaptureAudioDataOutputSampleBufferDelegate, AVCaptureVideoDataOutputSampleBufferDelegate {
      var closingAudioInput: AVAssetWriterInput?
      var closingAssetWriter: AVAssetWriter?
  
      var currentAudioInput: AVAssetWriterInput?
      var currentAssetWriter: AVAssetWriter?
  
      var nextAudioInput: AVAssetWriterInput?
      var nextAssetWriter: AVAssetWriter?
  
      var videoHelper: VideoHelper?
  
      var startTime: NSTimeInterval = 0
      let closeAssetQueue: dispatch_queue_t = dispatch_queue_create("closeAssetQueue", nil);
  
      override func viewDidLoad() {
          super.viewDidLoad()
          startTime = NSDate().timeIntervalSince1970
          createSegmentWriter()
          videoHelper = VideoHelper()
          videoHelper!.delegate = self
          videoHelper!.startSession()
          NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "createSegmentWriter", userInfo: nil, repeats: true)
      }
  
      func createSegmentWriter() {
          print("Creating segment writer at t=\(NSDate().timeIntervalSince1970 - self.startTime)")
          let outputPath = OutputFileNameHelper.instance.pathForOutput()
          OutputFileNameHelper.instance.incrementSegmentIndex()
          try? NSFileManager.defaultManager().removeItemAtPath(outputPath)
          nextAssetWriter = try! AVAssetWriter(URL: NSURL(fileURLWithPath: outputPath), fileType: AVFileTypeMPEG4)
          nextAssetWriter!.shouldOptimizeForNetworkUse = true
  
          let audioSettings: [String:AnyObject] = EncodingSettings.AUDIO
          nextAudioInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: audioSettings)
          nextAudioInput!.expectsMediaDataInRealTime = true
          nextAssetWriter?.addInput(nextAudioInput!)
  
          nextAssetWriter!.startWriting()
      }
  
      func closeWriterIfNecessary() {
          if closing &amp;&amp; audioFinished {
              closing = false
              audioFinished = false
              let outputFile = closingAssetWriter?.outputURL.pathComponents?.last
              closingAssetWriter?.finishWritingWithCompletionHandler() {
                  let delta = NSDate().timeIntervalSince1970 - self.startTime
                  print("segment \(outputFile!) finished at t=\(delta)")
              }
              self.closingAudioInput = nil
              self.closingAssetWriter = nil
          }
      }
  
      var audioFinished = false
      var closing = false
  
      func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBufferRef, fromConnection connection: AVCaptureConnection!) {
          if let nextWriter = nextAssetWriter {
              if nextWriter.status.rawValue != 0 {
                  if (currentAssetWriter != nil) {
                      closing = true
                  }
  
                  var sampleTiming: CMSampleTimingInfo = kCMTimingInfoInvalid
                  CMSampleBufferGetSampleTimingInfo(sampleBuffer, 0, &amp;sampleTiming)
  
                  print("Switching asset writers at t=\(NSDate().timeIntervalSince1970 - self.startTime)")
                  closingAssetWriter = currentAssetWriter
                  closingAudioInput = currentAudioInput
  
                  currentAssetWriter = nextAssetWriter
                  currentAudioInput = nextAudioInput
  
                  nextAssetWriter = nil
                  nextAudioInput = nil
  
                  currentAssetWriter?.startSessionAtSourceTime(sampleTiming.presentationTimeStamp)
              }
          }
  
          if let _ = captureOutput as? AVCaptureVideoDataOutput {
          } else if let _ = captureOutput as? AVCaptureAudioDataOutput {
              captureAudioSample(sampleBuffer)
          }
  
          dispatch_async(closeAssetQueue) {
              self.closeWriterIfNecessary()
          }
      }
  
      func printTimingInfo(sampleBuffer: CMSampleBufferRef, prefix: String) {
          var sampleTiming: CMSampleTimingInfo = kCMTimingInfoInvalid
          CMSampleBufferGetSampleTimingInfo(sampleBuffer, 0, &amp;sampleTiming)
          let presentationTime = Double(sampleTiming.presentationTimeStamp.value) / Double(sampleTiming.presentationTimeStamp.timescale)
          print("\(prefix):\(presentationTime)")
      }
  
      func captureAudioSample(sampleBuffer: CMSampleBufferRef) {
          printTimingInfo(sampleBuffer, prefix: "A")
          if (closing &amp;&amp; !audioFinished) {
              if closingAudioInput?.readyForMoreMediaData == true {
                  closingAudioInput?.appendSampleBuffer(sampleBuffer)
              }
              closingAudioInput?.markAsFinished()
              audioFinished = true
          } else {
              if currentAudioInput?.readyForMoreMediaData == true {
                  currentAudioInput?.appendSampleBuffer(sampleBuffer)
              }
          }
      }
  }
  </code></pre>