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
Any help or advice would be appreciated.
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.