将图像转换为帧时会消耗大量内存

时间:2015-10-20 12:08:42

标签: ios swift memory

我正在尝试从图像阵列创建视频,它正在运行但仅在模拟器中。似乎主要问题是内存使用情况。我已经优化了数组,但显然还不够,我的应用程序在最后一个阶段崩溃,当它试图将图像转换为帧时。

我说'数组'但是我并没有真正使用数组用于图像,我正在动态创建它们(我从我的视频源获取正确的帧并将叠加图像应用于其中,然后,我将此图像转换为新视频的帧。)

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
}

似乎这个功能在内存方面不是很好(也许,在其他方面也是如此)。似乎我没有在这里发布什么应该发布但是什么?

当我不使用此功能时,我的记忆状况要好得多:

screenshot from Instruments

另外,我认为这还不够,因为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?

更新:谢谢,伙计们

现在有效。有什么帮助(如果有人有同样的问题):

  1. 不要将图像放入数组(我在发布问题之前修复了此问题,但仍然如此)

  2. 不要惊慌,自动释放所有内容。

  3. 尝试在任何地方优化内存使用量(启动内存使用率越低,内存上限越高)。

  4. 将已处理的图片放入属性。

2 个答案:

答案 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 */ 
}