如何解决使用 AVAssetWriter 将图像转换为视频期间的 iOS 快速内存问题
我已经为此做了很多搜索并做了很多实验,但我没有得到任何合适的解决方案。
我尝试将 UIImages 转换为视频。我有 250 多个图像阵列,我尝试将这些图像转换为 60FPS 的视频。
我将渲染代码放在 autoreleasepool 方法中并添加了一些其他代码也添加了 autoreleasepool 但没有效果。
代码。
import AVFoundation
import UIKit
import Photos
import AVKit
var tempurl = ""
struct RenderSettings {
var width: CGFloat = UIScreen.main.bounds.width * UIScreen.main.scale
var height: CGFloat = UIScreen.main.bounds.width * UIScreen.main.scale
var fps: Int32 = 60 //frames per second
var avCodecKey = AVVideoCodecType.h264
var videoFilename = "ImagetoVideo"
var videoFilenameExt = "mp4"
var size: CGSize {
return CGSize(width: width,height: height)
}
var outputURL: URL {
let fileManager = FileManager.default
if let tmpDirURL = try? fileManager.url(for: .cachesDirectory,in: .userDomainMask,appropriateFor: nil,create: true) {
return tmpDirURL.appendingPathComponent(videoFilename).appendingPathExtension(videoFilenameExt) as URL
}
fatalError("URLForDirectory() Failed")
}
}
class VideoWriter {
let renderSettings: RenderSettings
var videoWriter: AVAssetWriter!
var videoWriterInput: AVAssetWriterInput!
var pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor!
var isReadyForData: Bool {
return videoWriterInput?.isReadyForMoreMediaData ?? false
}
class func pixelBufferFromImage(image: UIImage,pixelBufferPool: CVPixelBufferPool,size: CGSize) -> CVPixelBuffer {
autoreleasepool {
var pixelBufferOut: CVPixelBuffer?
let status = CVPixelBufferPoolCreatePixelBuffer(kcfAllocatorDefault,pixelBufferPool,&pixelBufferOut)
if status != kCVReturnSuccess {
fatalError("CVPixelBufferPoolCreatePixelBuffer() Failed")
}
let pixelBuffer = pixelBufferOut!
CVPixelBufferLockBaseAddress(pixelBuffer,[])
let data = CVPixelBufferGetBaseAddress(pixelBuffer)
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: data,width: Int(size.width),height: Int(size.height),bitsPerComponent: 8,bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),space: rgbColorSpace,bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
context!.clear(CGRect(x: 0,y: 0,width: size.width,height: size.height))
let horizontalRatio = size.width / image.size.width
let verticalRatio = size.height / image.size.height
let aspectRatio = min(horizontalRatio,verticalRatio) // ScaleAspectFit
let newSize = CGSize(width: image.size.width * aspectRatio,height: image.size.height * aspectRatio)
let x = newSize.width < size.width ? (size.width - newSize.width) / 2 : 0
let y = newSize.height < size.height ? (size.height - newSize.height) / 2 : 0
context!.concatenate(CGAffineTransform.identity)
context!.draw(image.cgImage!,in: CGRect(x: x,y: y,width: newSize.width,height: newSize.height))
CVPixelBufferUnlockBaseAddress(pixelBuffer,[])
return pixelBuffer
}
}
init(renderSettings: RenderSettings) {
self.renderSettings = renderSettings
}
func start() {
let avOutputSettings: [String: AnyObject] = [
AVVideoCodecKey: renderSettings.avCodecKey as AnyObject,AVVideoWidthKey: NSNumber(value: Float(renderSettings.width)),AVVideoHeightKey: NSNumber(value: Float(renderSettings.height))
]
func createPixelBufferAdaptor() {
let sourcePixelBufferAttributesDictionary = [
kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: kCVPixelFormatType_32ARGB),kCVPixelBufferWidthKey as String: NSNumber(value: Float(renderSettings.width)),kCVPixelBufferHeightKey as String: NSNumber(value: Float(renderSettings.height))
]
pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput,sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)
}
func createAssetWriter(outputURL: URL) -> AVAssetWriter {
guard let assetWriter = try? AVAssetWriter(outputURL: outputURL,fileType: AVFileType.mp4) else {
fatalError("AVAssetWriter() Failed")
}
guard assetWriter.canApply(outputSettings: avOutputSettings,forMediaType: AVMediaType.video) else {
fatalError("canApplyOutputSettings() Failed")
}
return assetWriter
}
videoWriter = createAssetWriter(outputURL: renderSettings.outputURL)
videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video,outputSettings: avOutputSettings)
if videoWriter.canAdd(videoWriterInput) {
videoWriter.add(videoWriterInput)
}
else {
fatalError("canAddinput() returned false")
}
createPixelBufferAdaptor()
if videoWriter.startWriting() == false {
fatalError("startWriting() Failed")
}
videoWriter.startSession(atSourceTime: CMTime.zero)
precondition(pixelBufferAdaptor.pixelBufferPool != nil,"nil pixelBufferPool")
}
func render(appendPixelBuffers: @escaping (VideoWriter)->Bool,completion: @escaping ()->Void) {
autoreleasepool {
precondition(videoWriter != nil,"Call start() to initialze the writer")
let queue = dispatchQueue(label: "mediaInputQueue")
videoWriterInput.requestMediaDataWhenReady(on: queue) {
let isFinished = appendPixelBuffers(self)
if isFinished {
self.videoWriterInput.markAsFinished()
self.videoWriter.finishWriting() {
dispatchQueue.main.async {
completion()
}
}
}
}
}
}
func addImage(image: UIImage,withPresentationTime presentationTime: CMTime) -> Bool {
autoreleasepool {
precondition(pixelBufferAdaptor != nil,"Call start() to initialze the writer")
let pixelBuffer = VideoWriter.pixelBufferFromImage(image: image,pixelBufferPool: pixelBufferAdaptor.pixelBufferPool!,size: renderSettings.size)
return pixelBufferAdaptor.append(pixelBuffer,withPresentationTime: presentationTime)
}
}
}
class ImageAnimator {
static let kTimescale: Int32 = 600
let settings: RenderSettings
let videoWriter: VideoWriter
var images: [UIImage]!
var frameNum = 0
class func removeFileAtURL(fileURL: URL) {
do {
try FileManager.default.removeItem(atPath: fileURL.path)
}
catch _ as NSError {
//
}
}
init(renderSettings: RenderSettings,imagearr: [UIImage]) {
settings = renderSettings
videoWriter = VideoWriter(renderSettings: settings)
images = imagearr
}
func render(completion: @escaping ()->Void) {
// The VideoWriter will fail if a file exists at the URL,so clear it out first.
ImageAnimator.removeFileAtURL(fileURL: settings.outputURL)
videoWriter.start()
videoWriter.render(appendPixelBuffers: appendPixelBuffers) {
let s: String = self.settings.outputURL.path
tempurl = s
completion()
}
}
func appendPixelBuffers(writer: VideoWriter) -> Bool {
let frameDuration = CMTimeMake(value: Int64(ImageAnimator.kTimescale / settings.fps),timescale: ImageAnimator.kTimescale)
while !images.isEmpty {
if writer.isReadyForData == false {
return false
}
let image = images.removeFirst()
let presentationTime = CMTimeMultiply(frameDuration,multiplier: Int32(frameNum))
let success = videoWriter.addImage(image: image,withPresentationTime: presentationTime)
if success == false {
fatalError("addImage() Failed")
}
frameNum=frameNum+1
}
return true
}
}
内存使用:
@objc public class Recorder: NSObject {
public var view : UIView?
var displayLink : CAdisplayLink?
var referenceDate : NSDate?
var imageArray = [UIImage]()
public func start() {
self.imageArray.removeAll()
if (view == nil) {
NSException(name: NSExceptionName(rawValue: "No view set"),reason: "You must set a view before calling start.",userInfo: nil).raise()
}else {
displayLink = CAdisplayLink(target: self,selector: #selector(self.handledisplayLink(displayLink:)))
displayLink!.add(to: RunLoop.main,forMode: RunLoop.Mode.common)
referenceDate = NSDate()
}
}
@objc func handledisplayLink(displayLink : CAdisplayLink) {
if (view != nil) {
createImageFromView(captureView: view!)
}
}
func createImageFromView(captureView : UIView) {
UIGraphicsBeginImageContextWithOptions(captureView.bounds.size,false,0)
captureView.drawHierarchy(in: captureView.bounds,afterScreenUpdates: false)
let image = UIGraphicsGetimageFromCurrentimageContext();
if let img = image {
self.imageArray.append(img)
}
UIGraphicsEndImageContext();
}
public func stop(completion: @escaping (_ saveURL: String) -> Void) {
displayLink?.invalidate()
let seconds = referenceDate?.timeIntervalSinceNow
if (seconds != nil) {
print("Image Count : \(self.imageArray.count)")
dispatchQueue.main.async {
let settings = RenderSettings()
let imageAnimator = ImageAnimator(renderSettings: settings,imagearr: self.imageArray)
imageAnimator.render() {
let u: String = tempurl
completion(u)
//self.saveVideoInPhotos()
}
}
}
}
}
提前致谢
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。