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

Ios 将图像转换为帧时会消耗大量内存,ios,swift,memory,Ios,Swift,Memory,我正在尝试从一系列图像中创建视频,它正在工作,但只在模拟器中。看来主要的问题是内存使用。我已经对阵列进行了优化,但这显然是不够的,我的应用程序在最后一个阶段尝试将图像转换为帧时崩溃了 我说的是“数组”,但我并不是真的使用数组来制作图像,我是在动态地创建它们(我从视频源中获取正确的帧,并将覆盖图像应用到其中,然后我将此图像转换为新视频的帧) 从记忆的角度来看,这个功能似乎不是很好(也许在其他方面也是如此)。似乎我没有在这里发布应该发布的内容,但是发布了什么 当我不使用此功能时,我的记忆状况要好得多

我正在尝试从一系列图像中创建视频,它正在工作,但只在模拟器中。看来主要的问题是内存使用。我已经对阵列进行了优化,但这显然是不够的,我的应用程序在最后一个阶段尝试将图像转换为帧时崩溃了

我说的是“数组”,但我并不是真的使用数组来制作图像,我是在动态地创建它们(我从视频源中获取正确的帧,并将覆盖图像应用到其中,然后我将此图像转换为新视频的帧)

从记忆的角度来看,这个功能似乎不是很好(也许在其他方面也是如此)。似乎我没有在这里发布应该发布的内容,但是发布了什么

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

此外,我认为这是不够的,因为180MB的内存使用量仍然太高,在转换成视频后,我的内存使用量大约为100-110MB(而不是之前的55-60MB)。在Instruments/Allocations中,我可以从VideoToolbox中看到很多JVLIB实例(比如JVLIB_101510(JVLIB_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()中这样做

更新:谢谢大家

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

  • 不要将图像放入数组(我在发布问题之前解决了这个问题,但仍然)

  • 不要惊慌,自动释放所有东西

  • 尝试在任何地方优化内存使用率(开始时内存使用率越低,内存上限越高)

  • 将处理后的图像放入属性


  • 在循环图像动画方面也有类似的问题(当UIImage对象被创建时,它会被缓存——如此多的实例消耗了大量内存)。ARC在这种情况下是不好的,因为它会随着时间的推移保存引用,并且不会以具有大量内存排列的周期释放引用。 尝试在
    autoreleasepool
    中包装代码,或查看手动内存管理:

    autoreleasepool {
      /* code */ 
    }
    

    在循环图像动画方面也有类似的问题(当UIImage对象被创建时,它会被缓存——如此多的实例消耗了大量内存)。ARC在这种情况下是不好的,因为它会随着时间的推移保存引用,并且不会以具有大量内存排列的周期释放引用。 尝试在
    autoreleasepool
    中包装代码,或查看手动内存管理:

    autoreleasepool {
      /* code */ 
    }
    

    从外观上看,您正在将非实时数据写入
    AVAssetWriter
    。在这种情况下,您需要小心,不要让编写器因提供帧的速度快于编写帧的速度而不知所措。最好的方法是让编写器从您那里提取数据,而不是向编写器推送数据。这是使用
    AVAssetWriterInput.requestMediaDataWhenRepayonQueue(\uu:usingBlock:)
    最容易做到的。文档中有一个简单的示例说明如何使用此功能

    在这种拉式风格中,您告诉编写器“只要您可以处理更多数据,就可以调用一个块。”编写器调用它,它应该不断添加数据,直到
    isReadForMoreMediaData
    变为false。然后,该块返回,并将在写入程序准备进行更多操作时再次调用。当没有更多帧时,设置“我现在完成”布尔值

    通过确保任何循环中都有
    autoreleasepool{}
    ,您应该小心地定期排空autorelease池,但看起来您通常已经在这样做了。如果你没有,并且你有一个循环,在没有自动释放池的情况下生成大图像,那么这肯定是你的问题

    作为减少内存流失的一个小好处,您可能应该在属性中缓存任何静态对象,如
    rgbColorSpace
    。您也可能不需要生成
    UIImage
    来绘制像素缓冲区。您只需使用
    CGContextDrawImage
    直接绘制
    CGImage
    。(并可能从
    getTheTempImage
    返回
    CGImage


    不相关的旁注:避免使用
    get
    作为前缀方法。它有一个特定的内存管理含义,这与您的用法相反(它意味着结果将在指针指向指针的参数中传回)。ARC足够聪明,可以避免创建内存错误,但对于了解ARC命名规则的开发人员来说,它很容易混淆。与其
    getTheCombinedImage(:)
    它应该是
    combinedImageForFrameNumber(:)

    从外观上看,您正在将非实时数据写入
    AVAssetWriter
    。在这种情况下,您需要小心,不要让编写器因提供帧的速度快于编写帧的速度而不知所措。最好的方法是让编写器从您那里提取数据,而不是向编写器推送数据。这是使用
    AVAssetWriterInput.requestMediaDataWhenRepayonQueue(\uu:usingBlock:)
    最容易做到的。文档中有一个简单的示例说明如何使用此功能

    在这种拉式风格中,您告诉编写器“只要您可以处理更多数据,就可以调用一个块。”编写器调用它,它应该不断添加数据,直到
    isReadForMoreMediaData
    变为false。然后,该块返回,并将在写入程序准备进行更多操作时再次调用。当没有更多帧时,设置“我现在完成”布尔值

    通过确保任何循环中都有
    autoreleasepool{}
    ,您应该小心地定期排空autorelease池,但看起来您通常已经在这样做了。如果你没有,并且你有一个循环,在没有自动释放池的情况下生成大图像,那么这肯定是你的问题

    作为