2015-10-20 84 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'不會增加很多開銷,但在這種情況下沒有理由添加任何東西。 –