2015-10-20 115 views
0

我试图创建图像阵列的视频,它的工作,但只能在模拟器。看来主要问题是内存使用情况。我已经优化阵列,但它显然是不够的,而我的应用程序崩溃的最后阶段,当它试图将图像转换为帧。巨大的内存消耗,同时图像转换为帧

我说:“阵”,但我真的不使用图像的阵列,我创建他们即时(我是从我的视频源获得正确的框架,并运用它的覆盖图像,之后我将这个图像转换为新视频的帧)。

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之前)。在仪器/分配我可以看到很多JVTLib实例从VideoToolbox(如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) 
} 

我不是在这里释放上下文但似乎我没有做到这一点斯威夫特(Do you need to release CGContextRef in Swift?

更新:谢谢,伙计们

现在的工作。是什么帮助(如果有人有相同的问题):

  1. 不把图像转换成数组(我发布这个问题之前修正了这个,但仍然)

  2. 不要惊慌和autoreleasepool一切。

  3. 尝试优化内存使用无处不在(降低您的内存使用量开始越高,你的内存上限)。

  4. 放处理后的图像变成财产。

+2

图像的分辨率是多少? – heximal

+1

为了强调@十六进制的注释,'UIImage'每个像素需要四个字节。因此,一张1000x1000的图像需要400万字节的RAM,并且这个大小的1000张图像的阵列需要40亿字节(不计算阵列本身的开销)。 – nhgrif

+0

图像的分辨率取决于视频分辨率。我的测试文件是640x360。但是我没有使用数组来创建图像,我正在按需创建每个图像。 – lithium

回答

2

有类似的循环图像动画问题(当创建UIImage对象时,它被缓存 - 这么多的实例给了巨大的内存消耗)。 ARC对于这种情况是不利的,因为持续引用会导致持久的引用,并且不会在具有大量内存排列的循环中释放它。 尝试在autoreleasepool包装你的代码或可考虑手动内存管理:

autoreleasepool { 
    /* code */ 
} 
+0

非常感谢。它现在肯定更好,但问题仍然存在(250 MB而不是400-600 MB)。 – lithium

+0

@lithium试图释放CGReferences?像'CGContextRelease()'? – katleta3000

+0

是的,我试图做到这一点,但有消息错误“CG​​ContextRelease不可用:核心基础对象自动内存管理” – lithium

1

通过它的外观,你写非实时数据的AVAssetWriter。在这种情况下,你要小心,不要让作品的帧速度超过它的编码速度,以免压倒作者。做这件事的最好方法是让作者从你那里获取数据,而不是在作者身上推送数据。这很容易用AVAssetWriterInput.requestMediaDataWhenReadyOnQueue(_:usingBlock:)完成。在文档中有一个简单的例子来说明如何使用这个函数。

在这种拉式风格中,您告诉作者“只要您能处理更多数据,就可以调用这个块”。作者称它,它应该保持添加数据,直到isReadForMoreMediaData成为false。然后该块返回,并且只要作者准备好更多时就会再次被调用。当没有更多帧时,您设置“我现在完成”布尔值。

你应该小心,通过确保任何循环内部有autoreleasepool{}定期排空你的autorelease池,但它看起来像你通常已经这样做。如果你没有,并且你有一个循环,没有autoreleasepool生成大图像,那当然是你的问题。

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

无关方面注意:避免使用get的前缀方法。这具有特定的内存管理含义,与您的使用相反(这意味着结果将传回到指针指针参数中)。 ARC非常聪明,可以避免产生内存错误,但对于知道ARC命名规则的开发人员而言,这很令人困惑。它应该是combinedImageForFrameNumber(_:)而不是getTheCombinedImage(_:)

+0

非常感谢。我已经在使用'requestMediaDataWhenReadyOnQueue'了,所以这里没有问题。我会在katleta3000的建议之后尝试getTheCombinedImage()上的'autoreleasepool {}',现在我的内存使用量是200-250MB,而不是400-600MB。它仍然太多,但它肯定更好。 – lithium

+0

为什么我想在这里使用CGImage而不是UIImage?内存方面更好吗? – lithium

+0

您已经创建了一个'CGImage'('frameRef')。把它包装成'UIImage'只是一种浪费(它肯定不会更高效)。 'UIImage'不会增加很多开销,但在这种情况下没有理由添加任何东西。 –