title: Ways to do inter-frame video compression in AVFoundation
body: <p>I've created a process to generate video "slideshows" from collections of photographs and images in an application that I'm building.  The process is functioning correctly, but creates unnecessarily large files given that any photographs included in the video repeat for 100 to 150 frames unchanged.  I've included whatever compression I can find in AVFoundation, which mostly applies intra-frame techniques and tried to find more information on inter-frame compression in AVFoundation.  Unfortunately, there are only a few references that I've been able to find and nothing that has let me get it to work.</p>
  
  <p>I'm hoping that someone can steer me in the right direction.  The code for the video generator is included below.  I've not included the code for fetching and preparing the individual frames (called below as self.getFrame()) since that seems to be working fine and gets quite complex since it handles photos, videos, adding title frames, and doing fade transitions.  For repeated frames, it returns a structure with the frame image and a counter for the number of output frames to include.</p>
  
  <pre><code>        // Create a new AVAssetWriter Instance that will build the video
  
          assetWriter = createAssetWriter(path: filePathNew, size: videoSize!)
          guard assetWriter != nil else
          {
              print("Error converting images to video: AVAssetWriter not created.")
              inProcess = false
              return
          }
  
          let writerInput = assetWriter!.inputs.filter{ $0.mediaType == AVMediaTypeVideo }.first!
  
          let sourceBufferAttributes : [String : AnyObject] = [
              kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_32ARGB) as AnyObject,
              kCVPixelBufferWidthKey as String : videoSize!.width as AnyObject,
              kCVPixelBufferHeightKey as String : videoSize!.height as AnyObject,
              AVVideoMaxKeyFrameIntervalKey as String : 50 as AnyObject,
              AVVideoCompressionPropertiesKey as String : [
                  AVVideoAverageBitRateKey: 725000,
                  AVVideoProfileLevelKey: AVVideoProfileLevelH264Baseline30,
                  ] as AnyObject
          ]
  
          let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: writerInput, sourcePixelBufferAttributes: sourceBufferAttributes)
  
          // Start the writing session
  
          assetWriter!.startWriting()
  
          assetWriter!.startSession(atSourceTime: kCMTimeZero)
  
          if (pixelBufferAdaptor.pixelBufferPool == nil) {
              print("Error converting images to video: pixelBufferPool nil after starting session")
              inProcess = false
              return
          }
  
          // -- Create queue for &lt;requestMediaDataWhenReadyOnQueue&gt;
  
          let mediaQueue = DispatchQueue(label: "mediaInputQueue")
  
          // Initialize run time values
  
          var presentationTime = kCMTimeZero
          var done = false
          var nextFrame: FramePack?                // The FramePack struct has the frame to output, noDisplays - the number of times that it will be output
                                                   // and an isLast flag that is true when it's the final frame
  
          writerInput.requestMediaDataWhenReady(on: mediaQueue, using: { () -&gt; Void in    // Keeps invoking the block to get input until call markAsFinished
  
              nextFrame = self.getFrame()          // Get the next frame to be added to the output with its associated values
              let imageCGOut = nextFrame!.frame    // The frame to output
              if nextFrame!.isLast { done = true } // Identifies the last frame so can drop through to markAsFinished() below
  
              var frames = 0                       // Counts how often we've output this frame
              var waitCount = 0                    // Used to avoid an infinite loop if there's trouble with writer.Input
  
              while (frames &lt; nextFrame!.noDisplays) &amp;&amp; (waitCount &lt; 1000000)  // Need to wait for writerInput to be ready - count deals with potential hung writer
              {
                  waitCount += 1
                  if waitCount == 1000000     // Have seen it go into 100s of thousands and succeed
                  {
                      print("Exceeded waitCount limit while attempting to output slideshow frame.")
                      self.inProcess = false
                      return
                  }
  
                  if (writerInput.isReadyForMoreMediaData)
                  {
                      waitCount = 0
                      frames += 1
  
                      autoreleasepool
                          {
                              if  let pixelBufferPool = pixelBufferAdaptor.pixelBufferPool
                              {
                                  let pixelBufferPointer = UnsafeMutablePointer&lt;CVPixelBuffer?&gt;.allocate(capacity: 1)
                                  let status: CVReturn = CVPixelBufferPoolCreatePixelBuffer(
                                      kCFAllocatorDefault,
                                      pixelBufferPool,
                                      pixelBufferPointer
                                  )
  
                                  if let pixelBuffer = pixelBufferPointer.pointee, status == 0
                                  {
                                      CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
                                      let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer)
                                      let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
  
                                      // Set up a context for rendering using the PixelBuffer allocated above as the target
  
                                      let context = CGContext(
                                          data: pixelData,
                                          width: Int(self.videoWidth),
                                          height: Int(self.videoHeight),
                                          bitsPerComponent: 8,
                                          bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),
                                          space: rgbColorSpace,
                                          bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
                                      )
  
                                      // Draw the image into the PixelBuffer used for the context
  
                                      context?.draw(imageCGOut, in: CGRect(x: 0.0,y: 0.0,width: 1280, height: 720))
  
                                      // Append the image (frame) from the context pixelBuffer onto the video file
  
                                      _ = pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: presentationTime)
                                      presentationTime = presentationTime + CMTimeMake(1, videoFPS)
  
                                      // We're done with the PixelBuffer, so unlock it
  
                                      CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
                                  }
  
                                  pixelBufferPointer.deinitialize()
                                  pixelBufferPointer.deallocate(capacity: 1)
  
                              } else {
                                  NSLog("Error: Failed to allocate pixel buffer from pool")
                              }
                      }
                  }
              }
  </code></pre>
  
  <p>Thanks in advance for any suggestions.</p>