2016-10-08 4 views
1

Я создал процесс создания видео «слайд-шоу» из коллекций фотографий и изображений в приложении, которое я создаю. Процесс функционирует корректно, но создает ненужные большие файлы, учитывая, что любые фотографии, включенные в повторение видео, не изменяются на 100-150 кадров. Я включил любое сжатие, которое я могу найти в AVFoundation, которое в основном применяет внутрикадровые методы и пыталось найти больше информации о межкадровом сжатии в AVFoundation. К сожалению, есть только несколько ссылок, которые я смог найти, и ничего, что позволило мне заставить его работать.Способы сжатия межкадрового видео в AVFoundation

Я надеюсь, что кто-то может направить меня в правильном направлении. Код видеогенератора приведен ниже. Я не включил код для извлечения и подготовки отдельных кадров (называемый ниже как self.getFrame()), поскольку он, кажется, работает нормально и становится довольно сложным, поскольку он обрабатывает фотографии, видео, добавляет заголовки и делает переходы , Для повторных кадров он возвращает структуру с изображением кадра и счетчиком количества включенных кадров вывода.

 // 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 <requestMediaDataWhenReadyOnQueue> 

     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: {() -> 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 < nextFrame!.noDisplays) && (waitCount < 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<CVPixelBuffer?>.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") 
          } 
        } 
       } 
      } 

Заранее благодарим за любые предложения.

ответ

2

Похоже, вы

  1. добавив кучу избыточных кадров видео,
  2. трудясь под заблуждением: это видео-файлы должны иметь постоянную частоту кадров, что является высоким, например, 30 кадров в секунду.

Если, например, вы показываете слайд-шоу из 3-х изображений над продолжительностью 15 секунд, после чего вы нужен только выход 3 изображений с предъявлением отметками времени 0s, 5с, 10с и с assetWriter.endSession(atSourceTime:) 15 с, а не 15 с * 30 FPS = 450 кадров.

Другими словами, ваша частота кадров слишком высокая - за лучшие деньги сжатия межфреймовой можно купить, снизить частоту кадров до голого минимального числа кадров, вам нужно, и все будет хорошо *.

* Я видел некоторые видео услуги/проигрыватели давятся необычно низкие частоты кадров,
поэтому вам может понадобиться минимальный фреймрейт и некоторые избыточные кадры, например, 1frame/5s, ymmv

+0

Спасибо, иногда легко закрепить на сложном ответе на то, что вы пропускаете простые. Я уменьшил количество кадров, используя временные метки, чтобы пропускать повторяющиеся кадры, но не мог отказаться от общей частоты кадров, так как я выполняю кросс-затухание между изображениями с помощью CG для генерации кадров с наложением предыдущего изображения, затухающего более чем на 25 кадров , Чтобы быть плавным, мне нравится это на 30FPS. Он по-прежнему становится большим со 100 + изображениями и слайдами заголовков, но не так плохо, как раньше. –