iOS中将多张图片合成为可导出可播放的视频文件(Swift 3)

来源:互联网 发布:江西财经大学网络平台 编辑:程序博客网 时间:2024/04/30 01:54

最近在做一个东西的时候,需要把一张或者多张图片合成为一个视频文件,并加入到视频轨道中进行播放或者导出,而不单单是把图片当做水印加到现有的视频上,做的时候首先考虑到的当然是AVAssetWriter,但是在做的时候还是遇到了很多问题,首先我用的是swift 3,翻遍了StackOverFlow也没有找到合适的,当然也有点偷懒的意思,想直接搜来完事儿。

先上个链接,思路和方法姿势全都有了,http://stackoverflow.com/questions/3741323/how-do-i-export-uiimage-array-as-a-movie?noredirect=1&lq=1,具体的步骤及涉及到点还请点看来看下,当然如果是为了省事儿,下面我也会直接给出demo,还合适你拿去就好。

第一次做的时候,因为里面的很多接口及操作都是异步的方式,但是当在测试的时候,发现我现有流程的代码中用需要和其他流程的代码同步,所以又改成了同步的方式。下面就直接贴代码了:

异步的方式:

////  DOVImagesToVideoAsync.swift//  ImagesToVideo////  Created by Wulei on 2016/12/24.//  Copyright © 2016年 wulei. All rights reserved.//import Foundationimport AVFoundationimport UIKittypealias DOVMovieMakerCompletion = (URL) -> Voidfileprivate typealias DOVMovieMakerUIImageExtractor = (AnyObject) -> UIImage?class DOVImageToVideoAsync: NSObject{        //MARK: Private Properties        private var assetWriter:AVAssetWriter!    private var writeInput:AVAssetWriterInput!    private var bufferAdapter:AVAssetWriterInputPixelBufferAdaptor!    private var videoSettings:[String : Any]!    private var frameTime:CMTime!    private var fileURL:URL!    private var completionBlock: DOVMovieMakerCompletion?        //MARK: Class Method        class func videoSettings(codec:String, width:Int, height:Int) -> [String: Any]{        if(Int(width) % 16 != 0){            print("warning: video settings width must be divisible by 16")        }                let videoSettings:[String: Any] = [AVVideoCodecKey: AVVideoCodecH264,                                           AVVideoWidthKey: width,                                           AVVideoHeightKey: height]                return videoSettings    }        //MARK: Public methods        init(videoSettings: [String: Any]) {        super.init()                let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)        var tempPath:String        repeat{            let random = arc4random()            tempPath = paths[0] + "/\(random).mp4"        }while(FileManager.default.fileExists(atPath: tempPath))        //        let tempPath = paths[0] + "/exprotvideo.mp4"        //        if(FileManager.default.fileExists(atPath: tempPath)){        //            guard (try? FileManager.default.removeItem(atPath: tempPath)) != nil else {        //                print("remove path failed")        //                return        //            }        //        }                self.fileURL = URL(fileURLWithPath: tempPath)        self.assetWriter = try! AVAssetWriter(url: self.fileURL, fileType: AVFileTypeQuickTimeMovie)                self.videoSettings = videoSettings        self.writeInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings)        assert(self.assetWriter.canAdd(self.writeInput), "add failed")                self.assetWriter.add(self.writeInput)        let bufferAttributes:[String: Any] = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32ARGB)]        self.bufferAdapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: self.writeInput, sourcePixelBufferAttributes: bufferAttributes)        self.frameTime = CMTimeMake(1, 1)    }        func createMovieFrom(urls: [URL], withCompletion: @escaping DOVMovieMakerCompletion){        self.createMovieFromSource(images: urls as [AnyObject], extractor:{(inputObject:AnyObject) ->UIImage? in            return UIImage(data: try! Data(contentsOf: inputObject as! URL))}, withCompletion: withCompletion)    }        func createMovieFrom(images: [UIImage], withCompletion: @escaping DOVMovieMakerCompletion){        self.createMovieFromSource(images: images, extractor: {(inputObject:AnyObject) -> UIImage? in            return inputObject as? UIImage}, withCompletion: withCompletion)    }        //MARK: Private methods        private func createMovieFromSource(images: [AnyObject], extractor: @escaping DOVMovieMakerUIImageExtractor, withCompletion: @escaping CXEMovieMakerCompletion){        self.completionBlock = withCompletion                self.assetWriter.startWriting()        self.assetWriter.startSession(atSourceTime: kCMTimeZero)                let mediaInputQueue = DispatchQueue(label: "mediaInputQueue")        var i = 0        let frameNumber = images.count        self.writeInput.requestMediaDataWhenReady(on: mediaInputQueue){            while(true){                if(i >= frameNumber){                    break                }                                if (self.writeInput.isReadyForMoreMediaData){                    var sampleBuffer:CVPixelBuffer?                    autoreleasepool{                        let img = extractor(images[i])                        if img == nil{                            i += 1                            print("Warning: counld not extract one of the frames")                            //                            continue                        }                        sampleBuffer = self.newPixelBufferFrom(cgImage: img!.cgImage!)                    }                    if (sampleBuffer != nil){                        if(i == 0){                            self.bufferAdapter.append(sampleBuffer!, withPresentationTime: kCMTimeZero)                        }else{                            let value = i - 1                            let lastTime = CMTimeMake(Int64(value), self.frameTime.timescale)                            let presentTime = CMTimeAdd(lastTime, self.frameTime)                            self.bufferAdapter.append(sampleBuffer!, withPresentationTime: presentTime)                        }                        i = i + 1                    }                }            }            self.writeInput.markAsFinished()            self.assetWriter.finishWriting {                DispatchQueue.main.sync {                    self.completionBlock!(self.fileURL)                }            }        }    }        private func newPixelBufferFrom(cgImage:CGImage) -> CVPixelBuffer?{        let options:[String: Any] = [kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true]        var pxbuffer:CVPixelBuffer?        let frameWidth = self.videoSettings[AVVideoWidthKey] as! Int        let frameHeight = self.videoSettings[AVVideoHeightKey] as! Int                let status = CVPixelBufferCreate(kCFAllocatorDefault, frameWidth, frameHeight, kCVPixelFormatType_32ARGB, options as CFDictionary?, &pxbuffer)        assert(status == kCVReturnSuccess && pxbuffer != nil, "newPixelBuffer failed")                CVPixelBufferLockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0))        let pxdata = CVPixelBufferGetBaseAddress(pxbuffer!)        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()        let context = CGContext(data: pxdata, width: frameWidth, height: frameHeight, bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pxbuffer!), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue)        assert(context != nil, "context is nil")                context!.concatenate(CGAffineTransform.identity)        context!.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))        CVPixelBufferUnlockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0))        return pxbuffer    }}

同步的方式:

////  DOVImagesToVideoSync.swift//  ImagesToVideo////  Created by Wulei on 2016/12/24.//  Copyright © 2016年 wulei. All rights reserved.//import Foundationimport AVFoundationimport UIKitfileprivate typealias DOVMovieMakerUIImageExtractor = (AnyObject) -> UIImage?class DOVImageToVideoSync: NSObject{        //MARK: Private Properties        private var assetWriter:AVAssetWriter!    private var writeInput:AVAssetWriterInput!    private var bufferAdapter:AVAssetWriterInputPixelBufferAdaptor!    private var videoSettings:[String : Any]!    private var frameTime:CMTime!    private var fileURL:URL!        //MARK: Class Method        class func videoSettings(codec:String, width:Int, height:Int) -> [String: Any]{        if(Int(width) % 16 != 0){            print("warning: video settings width must be divisible by 16")        }                let videoSettings:[String: Any] = [AVVideoCodecKey: AVVideoCodecH264,                                           AVVideoWidthKey: width,                                           AVVideoHeightKey: height]                return videoSettings    }        //MARK: Public methods        init(videoSettings: [String: Any]) {        super.init()                let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)        var tempPath:String        repeat{            let random = arc4random()            tempPath = paths[0] + "/\(random).mp4"        }while(FileManager.default.fileExists(atPath: tempPath))                self.fileURL = URL(fileURLWithPath: tempPath)        self.assetWriter = try! AVAssetWriter(url: self.fileURL, fileType: AVFileTypeQuickTimeMovie)                self.videoSettings = videoSettings        self.writeInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings)        assert(self.assetWriter.canAdd(self.writeInput), "add failed")                self.assetWriter.add(self.writeInput)        let bufferAttributes:[String: Any] = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32ARGB)]        self.bufferAdapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: self.writeInput, sourcePixelBufferAttributes: bufferAttributes)        self.frameTime = CMTimeMake(600, 600)    }        func createMovieFrom(url: URL, duration:Int) -> URL{        var urls = [URL]()        var index = duration        while(index > 0){            urls.append(url)            index -= 1        }        return self.createMovieFromSource(images: urls as [AnyObject], extractor:{(inputObject:AnyObject) ->UIImage? in            return UIImage(data: try! Data(contentsOf: inputObject as! URL))})    }        func createMovieFrom(image: UIImage, duration:Int) -> URL{        var images = [UIImage]()        var index = duration        while(index > 0){            images.append(image)            index -= 1        }        return self.createMovieFromSource(images: images, extractor: {(inputObject:AnyObject) -> UIImage? in            return inputObject as? UIImage})    }        //MARK: Private methods        private func createMovieFromSource(images: [AnyObject], extractor: @escaping DOVMovieMakerUIImageExtractor) -> URL{                self.assetWriter.startWriting()        //        self.assetWriter.startSession(atSourceTime: kCMTimeZero)        let zeroTime = CMTimeMake(Int64(0),self.frameTime.timescale)        self.assetWriter.startSession(atSourceTime: zeroTime)                var i = 0        let frameNumber = images.count                while !self.writeInput.isReadyForMoreMediaData {}                while(true){            if(i >= frameNumber){                break            }                        if (self.writeInput.isReadyForMoreMediaData){                var sampleBuffer:CVPixelBuffer?                autoreleasepool{                    let img = extractor(images[i])                    if img == nil{                        i += 1                        print("Warning: counld not extract one of the frames")                        //                            continue                    }                    sampleBuffer = self.newPixelBufferFrom(cgImage: img!.cgImage!)                }                if (sampleBuffer != nil){                    if(i == 0){                        self.bufferAdapter.append(sampleBuffer!, withPresentationTime: kCMTimeZero)                    }else{                        let value = i - 1                        let lastTime = CMTimeMake(Int64(value), self.frameTime.timescale)                        let presentTime = CMTimeAdd(lastTime, self.frameTime)                        self.bufferAdapter.append(sampleBuffer!, withPresentationTime: presentTime)                    }                    i = i + 1                }            }        }        self.writeInput.markAsFinished()        self.assetWriter.finishWriting {}                var isSuccess:Bool = false        while(!isSuccess){            switch self.assetWriter.status {            case .completed:                isSuccess = true                print("completed")            case .writing:                sleep(1)                print("writing")            case .failed:                isSuccess = true                print("failed")            case .cancelled:                isSuccess = true                print("cancelled")            default:                isSuccess = true                print("unknown")            }        }        return self.fileURL    }        private func newPixelBufferFrom(cgImage:CGImage) -> CVPixelBuffer?{        let options:[String: Any] = [kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true]        var pxbuffer:CVPixelBuffer?        let frameWidth = self.videoSettings[AVVideoWidthKey] as! Int        let frameHeight = self.videoSettings[AVVideoHeightKey] as! Int                let status = CVPixelBufferCreate(kCFAllocatorDefault, frameWidth, frameHeight, kCVPixelFormatType_32ARGB, options as CFDictionary?, &pxbuffer)        assert(status == kCVReturnSuccess && pxbuffer != nil, "newPixelBuffer failed")                CVPixelBufferLockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0))        let pxdata = CVPixelBufferGetBaseAddress(pxbuffer!)        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()        let context = CGContext(data: pxdata, width: frameWidth, height: frameHeight, bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pxbuffer!), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue)        assert(context != nil, "context is nil")                context!.concatenate(CGAffineTransform.identity)        context!.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))        CVPixelBufferUnlockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0))        return pxbuffer    }}

运行结果截图:


Demo程序:https://github.com/Willib/ImagesToVideo

0 0