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
- iOS中将多张图片合成为可导出可播放的视频文件(Swift 3)
- iOS多张图片合成一个视频文件
- iOS多张图片合成一个视频文件
- iOS多张图片合成一个视频文件
- iOS多张图片合成一个视频文件
- iOS开发图片合成,多张图片合成一张图片
- IOS两张图片合成为一张图片
- IOS两张图片合成为一张图片
- iOS 多张图片合成视频
- IOS多张图片合成一个视频
- IOS多张图片合成一个视频
- IOS多张图片合成一个视频
- IOS多张图片合成一个视频
- iOS多张图片合成一个视频
- IOS多张图片合成一个视频
- iOS多张图片合成一个视频
- JS代码,多张图片的渐变。可展开,可缩小。
- iOS多张图片上传多线程处理方法(可获取最后一张上传状态后的信号)
- 【JavaEE—Hibernate】一级缓存以及事务操作
- CSS:display属性
- 传统Ajax的工作流程(以检测新用户id是否可用为例)
- IllegalAccessException: Class A can not access a member of class B 的一种原因分析与解决
- R语言学习五
- iOS中将多张图片合成为可导出可播放的视频文件(Swift 3)
- LintCode - 539.移动零
- 研究方向社交网络——楼天城
- 图像识别(9)——UVC预览+曝光滑动调节
- 设计模式之代理模式(静态代理)
- Django学习之数据库的链接详解
- Java基础 构造函数
- XML解析之Pull、Sax方式
- C语言的循环