我正在尝试从图像阵列创建视频,它正在运行但仅在模拟器中。似乎主要问题是内存使用情况。我已经优化了数组,但显然还不够,我的应用程序在最后一个阶段崩溃,当它试图将图像转换为帧时。
我说'数组'但是我并没有真正使用数组用于图像,我正在动态创建它们(我从我的视频源获取正确的帧并将叠加图像应用于其中,然后,我将此图像转换为新视频的帧。)
func getTheCombinedImage(frameNumber: Int)->UIImage {
let videoURLAsset = videoAsset as! AVURLAsset
let generator:AVAssetImageGenerator = AVAssetImageGenerator(asset: videoURLAsset)
generator.requestedTimeToleranceBefore = kCMTimeZero
generator.requestedTimeToleranceAfter = kCMTimeZero
var actualTime : CMTime = CMTimeMake(0, 0)
let duration:CMTime = CMTimeMake(Int64(frameNumber), Int32(30))
let frameRef:CGImageRef = try! generator.copyCGImageAtTime(duration, actualTime: &actualTime)
let sourceImage:UIImage = UIImage(CGImage: frameRef)
let tempImage:UIImage = getTheTempImage(frameNumber)
UIGraphicsBeginImageContext(sourceImage.size)
sourceImage.drawInRect(CGRect(x: 0, y: 0, width: sourceImage.size.width, height: sourceImage.size.height), blendMode: CGBlendMode.Normal, alpha: 1.0)
tempImage.drawInRect(CGRect(x: 0, y: 0, width: tempImage.size.width, height: tempImage.size.height), blendMode: CGBlendMode.Normal, alpha: 1.0)
let combinedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return combinedImage
}
似乎这个功能在内存方面不是很好(也许,在其他方面也是如此)。似乎我没有在这里发布什么应该发布但是什么?
当我不使用此功能时,我的记忆状况要好得多:
另外,我认为这还不够,因为180MB的内存使用率仍然太高,在转换为视频后,我的内存使用率水平就像100-110 MB(而不是之前的55-60 MB)。在 Instruments / Allocations 中,我可以看到很多来自VideoToolbox的JVTLib实例(比如 JVTLib_101510(JVTLib_101496 ,int)*) - 我认为它们是转换和所必需的_CVPixelBufferStandardMemoryLayout 实例 - 我认为这些是由我创建的,显然我也没有在这里发布一些东西。
func appendPixelBufferForImageAtURL(image: UIImage, pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor, presentationTime: CMTime) -> Bool {
var appendSucceeded = true
autoreleasepool {
var pixelBuffer: CVPixelBuffer? = nil
let status: CVReturn = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferAdaptor.pixelBufferPool!, &pixelBuffer)
if let pixelBuffer = pixelBuffer where status == 0 {
let managedPixelBuffer = pixelBuffer
fillPixelBufferFromImage(image, pixelBuffer: managedPixelBuffer)
appendSucceeded = pixelBufferAdaptor.appendPixelBuffer(pixelBuffer, withPresentationTime: presentationTime)
} else {
NSLog("error: Failed to allocate pixel buffer from pool")
}
}
return appendSucceeded
}
我不明白这里有什么问题。
func fillPixelBufferFromImage(image: UIImage, pixelBuffer: CVPixelBufferRef) {
let imageData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage))
CVPixelBufferLockBaseAddress(pixelBuffer, 0)
let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer)
let bitmapInfo = CGImageAlphaInfo.PremultipliedFirst.rawValue
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGBitmapContextCreate(
pixelData,
Int(self.externalInputSize.width),
Int(self.externalInputSize.height),
8,
CVPixelBufferGetBytesPerRow(pixelBuffer),
rgbColorSpace,
bitmapInfo
)
let imageDataProvider = CGDataProviderCreateWithCFData(imageData)
CGContextDrawImage(context, CGRectMake(0, 0, self.externalInputSize.width, self.externalInputSize.height), image.CGImage)
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0)
}
我不是在这里发布上下文,但似乎我不必在Swift中执行此操作(Do you need to release CGContextRef in Swift?)
更新:谢谢,伙计们
现在有效。有什么帮助(如果有人有同样的问题):
不要将图像放入数组(我在发布问题之前修复了此问题,但仍然如此)
不要惊慌,自动释放所有内容。
尝试在任何地方优化内存使用量(启动内存使用率越低,内存上限越高)。
将已处理的图片放入属性。
答案 0 :(得分:4)
从外观上看,您将非实时数据写入AVAssetWriter
。在这种情况下,你要小心不要让编写器的帧速度超过编码速度。执行此操作的最佳方法是让作者从您那里提取数据,而不是在编写器上推送数据。使用AVAssetWriterInput.requestMediaDataWhenReadyOnQueue(_:usingBlock:)
最容易做到这一点。在文档中有一个关于如何使用此函数的简单示例。
在这种拉式中,你告诉作者“只要你能处理更多数据,就可以调用这个块。”作者称之为,它应该继续添加数据,直到isReadForMoreMediaData
变为假。然后块返回,并且只要编写器准备好再执行一次,它将再次被调用。当没有更多帧时,你设置一个“我现在完成”的布尔值。
您应该小心定期排空自动释放池,确保其中的任何循环都有autoreleasepool{}
,但看起来您通常已经这样做了。如果你不是,并且你有一个循环,在没有自动释放池的情况下生成大图像,那那肯定是你的问题。
作为减少内存流失的一个小好处,您应该在属性中缓存任何静态对象,例如rgbColorSpace
。您也可能不需要生成UIImage
以便绘制像素缓冲区。您可以使用CGContextDrawImage
直接绘制CGImage
。 (可能会从CGImage
返回getTheTempImage
。)
不相关的附注:避免使用get
前缀方法。这具有特定的内存管理意义,这与您的使用相反(这意味着结果将在指向指针的参数中传回)。 ARC非常聪明,可以避免产生内存错误,但对于了解ARC命名规则的开发人员来说却很困惑。而不是getTheCombinedImage(_:)
它应该是combinedImageForFrameNumber(_:)
。
答案 1 :(得分:2)
与循环图像动画有类似的问题(当创建UIImage对象时,它被缓存 - 这么多实例给了大量的内存消耗)。 ARC对于这种情况是不好的,因为保持引用超时并且不会在具有大量内存布置的循环中释放它。
尝试将代码包装在autoreleasepool
中或查看手动内存管理:
autoreleasepool {
/* code */
}