Проблема преобразования массива изображений в видео - PullRequest
0 голосов
/ 19 июня 2019

В этом приложении, над которым я работаю, я пытаюсь преобразовать довольно большой массив из примерно 30 изображений в видео.

Мой код для преобразования основан на этом коде, который явидел по этому вопросу: Создание видео из массива UIImage с различными анимациями перехода

Проблема, которую я видел довольно часто при попытках сделать этот тип преобразования, заключается в том, чтобы иметь какое-топроблема с выводом AVAssetsWriter, но мне кажется, что это нормально.

Я не знаю, может ли это быть проблемой, но если я проверяю, что pixelBufferPool равен нулю, прежде чем я запускаю videoWriter, он говорит, что он нулевой, но после запуска он говорит, что не нулевой.

Вот мой код для конвертации:

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
    }

И вот как я вызываю функцию:

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()
                }                
            })

Итак, это возвращает мне не воспроизводимое видео,если я пытаюсь воспроизвести его, я получаю только плеер iOS с полосой, пересекающей символ воспроизведения, и колесиком рядом с полосой времени, постоянно вращающейся.Мне также нужны данные файла, чтобы загрузить видео, чтобы при попытке получить его оно было пустым.

Ответы [ 2 ]

0 голосов
/ 22 июня 2019

Таким образом, я смог создать рабочее решение, объединяющее некоторые методы, которые можно найти в ссылках, предоставленных Робином Стюартом.Стоит отметить, что использование их, как будто они не работают для меня, только когда я сделал некоторые изменения, это наконец сработало.Может быть, это что-то делает, что большинство из них в Swift 3, и я использую Swift 4.2.

Вот мое решение:

func writeImagesAsMovie(_ allImages: [UIImage], videoPath: String, videoSize: CGSize, videoFPS: Int32, completion: @escaping (Bool) -> ()) -> Bool{

        guard let assetWriter = try? AVAssetWriter(outputURL: URL(string: videoPath)!, 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 assetWriter.canApply(outputSettings: outputSettings, forMediaType: AVMediaType.video) else {
            fatalError("Negative : Can't apply the Output settings...")
        }
        let writerInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: outputSettings)


        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: writerInput, sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)


        if assetWriter.canAdd(writerInput) {
            assetWriter.add(writerInput)
        }
        // Start writing session
        if assetWriter.startWriting() {
            assetWriter.startSession(atSourceTime: CMTime.zero)

            // -- Create queue for <requestMediaDataWhenReadyOnQueue>
            assert(pixelBufferAdaptor.pixelBufferPool != nil)
            let mediaQueue = DispatchQueue(label: "mediaInputQueue", attributes: [])

            // -- Set video parameters
            let frameDuration = CMTimeMake(value: 1, timescale: videoFPS)
            var frameCount = 0

            // -- Add images to video
            let numImages = allImages.count
            writerInput.requestMediaDataWhenReady(on: mediaQueue, using: { () -> Void in
                // Append unadded images to video but only while input ready
                while (writerInput.isReadyForMoreMediaData && frameCount < numImages) {
                    let lastFrameTime = CMTimeMake(value: Int64(frameCount), timescale: videoFPS)
                    let presentationTime = frameCount == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration)

                    if !self.appendPixelBufferForImageAtURL(allImages[frameCount], pixelBufferAdaptor: pixelBufferAdaptor, presentationTime: presentationTime) {
                        print("Error converting images to video: AVAssetWriterInputPixelBufferAdapter failed to append pixel buffer")
                        return
                    }

                    frameCount += 1
                }

                // No more images to add? End video.
                if (frameCount >= numImages) {
                    writerInput.markAsFinished()
                    assetWriter.finishWriting {
                        if (assetWriter.error != nil) {
                            print("Error converting images to video: \(assetWriter.error)")
                        } else {
                            print("Converted images to movie @ \(videoPath)")
                            completion(true)
                        }
                    }
                }
            })
        }

        return true
    }


    func createAssetWriter(_ path: String, size: CGSize) -> AVAssetWriter? {
        // Convert <path> to NSURL object
        let pathURL = URL(fileURLWithPath: path)

        // Return new asset writer or nil
        do {
            // Create asset writer
            let newWriter = try AVAssetWriter(outputURL: pathURL, fileType: AVFileType.mp4)

            // Define settings for video input
            let videoSettings: [String : AnyObject] = [
                AVVideoCodecKey  : AVVideoCodecType.h264 as AnyObject,
                AVVideoWidthKey  : size.width as AnyObject,
                AVVideoHeightKey : size.height as AnyObject,
            ]

            // Add video input to writer
            let assetWriterVideoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
            newWriter.add(assetWriterVideoInput)

            // Return writer
            print("Created asset writer for \(size.width)x\(size.height) video")
            return newWriter
        } catch {
            print("Error creating asset writer: \(error)")
            return nil
        }
    }


    func appendPixelBufferForImageAtURL(_ image: UIImage, pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor, presentationTime: CMTime) -> Bool {
        var appendSucceeded = false

        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 {
                    fillPixelBufferFromImage(image, pixelBuffer: pixelBuffer)
                    appendSucceeded = pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: presentationTime)
                    pixelBufferPointer.deinitialize()
                } else {
                    NSLog("Error: Failed to allocate pixel buffer from pool")
                }

                pixelBufferPointer.deallocate(capacity: 1)
            }
        }

        return appendSucceeded
    }


    func fillPixelBufferFromImage(_ image: UIImage, pixelBuffer: CVPixelBuffer) {
        CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))

        let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer)
        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()

        // Create CGBitmapContext
        let context = CGContext(
            data: pixelData,
            width: Int(image.size.width),
            height: Int(image.size.height),
            bitsPerComponent: 8,
            bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),
            space: rgbColorSpace,
            bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
            )!

        // Draw image into context
        let drawCGRect = CGRect(x:0, y:0, width:image.size.width, height:image.size.height)
        var drawRect = NSCoder.string(for: drawCGRect);
        let ciImage = CIImage(image: image)
        let cgImage = convertCIImageToCGImage(inputImage: ciImage!)
        context.draw(cgImage!, in: CGRect(x: 0.0,y: 0.0,width: image.size.width,height: image.size.height))

        CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
    }

    func convertCIImageToCGImage(inputImage: CIImage) -> CGImage! {
        let context = CIContext(options: nil)
        if context != nil {
            return context.createCGImage(inputImage, from: inputImage.extent)
        }
        return nil
    }

}
0 голосов
/ 20 июня 2019

В AVFoundation существует множество причин, по которым все может пойти не так. Я бы порекомендовал более внимательно следовать хорошо написанной справочной реализации. Вот некоторые из них, которые я нашел полезными:

...