Search code examples
swift2avfoundationavassetwritercvpixelbuffer

iOS CVPixelBufferCreate leaking memory in swift 2


I'm trying to convert an image into a video, and the right way seems to use a AVAssetWriter with a AVAssetWriterInputPixelBufferAdaptor, and it works well, but it leaks memory.

When I convert the CGImage to a CVPixelBuffer, I call CVPixelBufferCreate, which never frees it's memory.

func CGImageToPixelBuffer(image: CGImageRef, frameSize: CGSize) -> CVPixelBuffer {

    // stupid CFDictionary stuff
    let keys: [CFStringRef] = [kCVPixelBufferCGImageCompatibilityKey, kCVPixelBufferCGBitmapContextCompatibilityKey]
    let values: [CFTypeRef] = [kCFBooleanTrue, kCFBooleanTrue]
    let keysPointer = UnsafeMutablePointer<UnsafePointer<Void>>.alloc(1)
    let valuesPointer =  UnsafeMutablePointer<UnsafePointer<Void>>.alloc(1)
    keysPointer.initialize(keys)
    valuesPointer.initialize(values)
    let options = CFDictionaryCreate(kCFAllocatorDefault, keysPointer, valuesPointer, keys.count,
        UnsafePointer<CFDictionaryKeyCallBacks>(), UnsafePointer<CFDictionaryValueCallBacks>())

    let buffer = UnsafeMutablePointer<CVPixelBuffer?>.alloc(1)

    // here's the leak >:[
    let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(frameSize.width), Int(frameSize.height),
        kCVPixelFormatType_32ARGB, options, buffer)

    CVPixelBufferLockBaseAddress(pixelBuffer.memory!, 0);
    let bufferData = CVPixelBufferGetBaseAddress(buffer.memory!);

    let rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    let context = CGBitmapContextCreate(bufferData, Int(frameSize.width),
        Int(frameSize.height), 8, 4*Int(frameSize.width), rgbColorSpace,
        CGImageAlphaInfo.NoneSkipFirst.rawValue);

    CGContextDrawImage(context, CGRectMake(0, 0,
        CGFloat(CGImageGetWidth(image)),
        CGFloat(CGImageGetHeight(image))), image);

    CVPixelBufferUnlockBaseAddress(pixelBuffer.memory!, 0);
    return buffer.memory!
}

And here's the code which calls CGImageToPixelBuffer

func saveImageAsVideoFile(path: NSURL, image: UIImage, duration: Double) {
    let writer = try! AVAssetWriter(URL: path, fileType: AVFileTypeQuickTimeMovie)

    let videoSettings: NSDictionary = [
        AVVideoCodecKey : AVVideoCodecH264,
        AVVideoWidthKey : NSNumber(integer: Int(image.size.width)),
        AVVideoHeightKey : NSNumber(integer: Int(image.size.height))
    ]
    let input = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings as? [String : AnyObject])
    let inputAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: input, sourcePixelBufferAttributes: nil)
    input.expectsMediaDataInRealTime = false
    writer.addInput(input)
    writer.startWriting()
    writer.startSessionAtSourceTime(kCMTimeZero)

    // leak starts here
    let buffer = CGImageToPixelBuffer(image.CGImage!, frameSize: image.size)

    // append 30 frames to AVAssetWriter thing
    for i in 0..<30
        if input.readyForMoreMediaData {
            inputAdaptor.appendPixelBuffer(buffer, withPresentationTime: CMTimeMake(i, 30))
        }
    }

    //CVPixelBufferRelease(buffer) ??

    input.markAsFinished()
    writer.finishWritingWithCompletionHandler({ () -> Void in
        print("yay")
    })
}

CVPixelBufferRelease is not available in swift 2

CVPixelBufferCreate doesn't return a unmanaged pointer in swift 2, so I can't use this guys code

I've tried manually calling destroy and dealloc on the unsafepointer, to no avail.

every time it's called it increases the memory usage, and will crash the device if called enough memory profile

Any help or advice would be appreciated.


Solution

  • Your let buffer = UnsafeMutablePointer<CVPixelBuffer?>.alloc(1) isn't balanced by a dealloc, however adding that doesn't seem to fix the leak. Simply using a CVPixelBuffer? does though, and seems simpler:

    var buffer: CVPixelBuffer?
    
    // TODO: handle status != noErr
    let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(frameSize.width), Int(frameSize.height),
        kCVPixelFormatType_32ARGB, options, &buffer)
    
    CVPixelBufferLockBaseAddress(buffer!, 0);
    let bufferData = CVPixelBufferGetBaseAddress(buffer!);
    
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    let context = CGBitmapContextCreate(bufferData, Int(frameSize.width),
        Int(frameSize.height), 8, 4*Int(frameSize.width), rgbColorSpace,
        CGImageAlphaInfo.NoneSkipFirst.rawValue);
    
    CGContextDrawImage(context, CGRectMake(0, 0,
        CGFloat(CGImageGetWidth(image)),
        CGFloat(CGImageGetHeight(image))), image);
    
    CVPixelBufferUnlockBaseAddress(buffer!, 0);
    

    You should also handle status != noErr and unwrap the optional buffer sooner, to avoid all those buffer!s.