So I am trying to convert a quite large array of images, of around 30 images to a video in this app I am working on.
My code to do the conversion is based on this code that I saw on this question: Making video from UIImage array with different transition animations
A problem that I saw that is quite frequent in the attempts to do this type of conversion is to have some kind of problem with the output of AVAssetsWriter, but that seems to be ok for me.
I don't know if this can be the problem but if I check pixelBufferPool is null before I start videoWriter it says it is null, but after it starts it says is not null.
This is my code to make the conversion:
var outputSize = CGSize(width: 1920, height: 1280)
let imagesPerSecond: TimeInterval = 0.3 //each image will be stay for 3 secs
var selectedPhotosArray = [UIImage()]
let audioIsEnabled: Bool = false //if your video has no sound
var asset: AVAsset!
var videoCriado : Bool = false
var publicId : String?
var videoPlayer : AVPlayer?
func buildVideoFromImageArray(imageArrayToVideoURL: URL, completion: @escaping (AVPlayer) -> ()) {
removeFileAtURLIfExists(url: imageArrayToVideoURL as NSURL)
guard let videoWriter = try? AVAssetWriter(outputURL: imageArrayToVideoURL as URL, fileType: AVFileType.mp4) else {
fatalError("AVVideoCodecType.h264 error")
}
let outputSettings = [AVVideoCodecKey : AVVideoCodecType.h264, AVVideoWidthKey : NSNumber(value: Float(outputSize.width)), AVVideoHeightKey : NSNumber(value: Float(outputSize.height))] as [String : Any]
guard videoWriter.canApply(outputSettings: outputSettings, forMediaType: AVMediaType.video) else {
fatalError("Negative : Can't apply the Output settings...")
}
let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: outputSettings)
print(videoWriter.status.rawValue)
print(videoWriter.outputURL)
let sourcePixelBufferAttributesDictionary = [kCVPixelBufferPixelFormatTypeKey as String : NSNumber(value: kCVPixelFormatType_32ARGB), kCVPixelBufferWidthKey as String: NSNumber(value: Float(outputSize.width)), kCVPixelBufferHeightKey as String: NSNumber(value: Float(outputSize.height))]
let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput, sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)
if videoWriter.canAdd(videoWriterInput) {
videoWriter.add(videoWriterInput)
}
if videoWriter.startWriting() {
print(videoWriter.status.rawValue)
let zeroTime = CMTimeMake(value: Int64(imagesPerSecond),timescale: Int32(1))
videoWriter.startSession(atSourceTime: zeroTime)
assert(pixelBufferAdaptor.pixelBufferPool != nil)
let media_queue = DispatchQueue(label: "mediaInputQueue")
videoWriterInput.requestMediaDataWhenReady(on: media_queue, using: { () -> Void in
let fps: Int32 = 1
let framePerSecond: Int64 = Int64(self.imagesPerSecond)
let frameDuration = CMTimeMake(value: Int64(self.imagesPerSecond), timescale: fps)
var frameCount: Int64 = 0
var appendSucceeded = true
while (!self.selectedPhotosArray.isEmpty) {
if (videoWriterInput.isReadyForMoreMediaData) {
let nextPhoto = self.selectedPhotosArray.remove(at: 0)
let lastFrameTime = CMTimeMake(value: frameCount * framePerSecond, timescale: fps)
let presentationTime = frameCount == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration)
var pixelBuffer: CVPixelBuffer? = nil
let status: CVReturn = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferAdaptor.pixelBufferPool!, &pixelBuffer)
if let pixelBuffer = pixelBuffer, status == 0 {
let managedPixelBuffer = pixelBuffer
CVPixelBufferLockBaseAddress(managedPixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
let data = CVPixelBufferGetBaseAddress(managedPixelBuffer)
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: data, width: Int(self.outputSize.width), height: Int(self.outputSize.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(managedPixelBuffer), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
context!.clear(CGRect(x: 0, y: 0, width: CGFloat(self.outputSize.width), height: CGFloat(self.outputSize.height)))
let horizontalRatio = CGFloat(self.outputSize.width) / nextPhoto.size.width
let verticalRatio = CGFloat(self.outputSize.height) / nextPhoto.size.height
//let aspectRatio = max(horizontalRatio, verticalRatio) // ScaleAspectFill
let aspectRatio = min(horizontalRatio, verticalRatio) // ScaleAspectFit
let newSize: CGSize = CGSize(width: nextPhoto.size.width * aspectRatio, height: nextPhoto.size.height * aspectRatio)
let x = newSize.width < self.outputSize.width ? (self.outputSize.width - newSize.width) / 2 : 0
let y = newSize.height < self.outputSize.height ? (self.outputSize.height - newSize.height) / 2 : 0
context?.draw(nextPhoto.cgImage!, in: CGRect(x: x, y: y, width: newSize.width, height: newSize.height))
CVPixelBufferUnlockBaseAddress(managedPixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
appendSucceeded = pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: presentationTime)
} else {
print("Failed to allocate pixel buffer")
appendSucceeded = false
}
}
if !appendSucceeded {
break
}
frameCount += 1
}
videoWriterInput.markAsFinished()
videoWriter.finishWriting { () -> Void in
print("-----video1 url = \(imageArrayToVideoURL)")
self.asset = AVAsset(url: imageArrayToVideoURL)
self.videoPlayer = AVPlayer(url: imageArrayToVideoURL)
//self.videoCriado = true
//self.resultUrl = self.exportVideoWithAnimation()
completion(self.videoPlayer!)
//self.exportVideoWithAnimation()
}
})
}
//return asset
}
And this is how I am calling the function:
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentsURL.appendingPathComponent("\(public_id!.description).mp4")
//let videoSize = CGSize(width: moldura.larguraMoldura, height: moldura.alturaMoldura)
imageToVideo.selectedPhotosArray = fotosBoomerangArray
let sizeVideo = CGSize(width: moldura.larguraMoldura, height: moldura.alturaMoldura)
imageToVideo.outputSize = sizeVideo
imageToVideo.buildVideoFromImageArray(imageArrayToVideoURL: fileURL, completion: {
(video) in
DispatchQueue.main.async {
self.videoPlayer = video
self.irParaPreview()
}
})
So what this is returning to me is a not playable video, if I try to play it I get just the iOS player with a bar cross over the play symbol and a wheel next to the time bar continuously spinning. I also need the data of the file to upload the video, that when I try to get it it is null.