wiredy
wiredy

Reputation: 61

Pixel Buffer will not append to AVAssetWriterInput on device

I'm trying to take an image and convert it into a video file in Swift. I was able to get the code to work in the iOS simulator, but it won't work on an actual device. It errors out when I try to append the pixel buffer to the AVAssetWriterInput. I was wondering if anyone here has run into the same issue previously. Here's the code:

func createVideoFromImage(img: CGImageRef) -> Void {

    var error: NSError?
    let frameSize = CGSizeMake(CGFloat(CGImageGetWidth(img)), CGFloat(CGImageGetHeight(img)))

    let fileName = "\(uniqueString()).m4v"
    let assetWriter = AVAssetWriter(URL: fileUrl(fileName), fileType: AVFileTypeAppleM4V, error: &error)

    let outputSettings = [
        AVVideoCodecKey: AVVideoCodecH264,
        AVVideoWidthKey: frameSize.width,
        AVVideoHeightKey: frameSize.height
    ]

    let assetWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: outputSettings)

    let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterInput, sourcePixelBufferAttributes: nil)

    if assetWriter.canAddInput(assetWriterInput) {
        assetWriter.addInput(assetWriterInput)
    }

    assetWriter.startWriting()
    assetWriter.startSessionAtSourceTime(kCMTimeZero)

    let buffer:CVPixelBufferRef = pixelBufferFromCGImage(img, frameSize: frameSize)

    if pixelBufferAdaptor.assetWriterInput.readyForMoreMediaData {
        let frameTime = CMTimeMakeWithSeconds(0, 30)
        let pixelBufferAppend = pixelBufferAdaptor.appendPixelBuffer(buffer, withPresentationTime: frameTime)

        if pixelBufferAppend {
            assetWriterInput.markAsFinished()

            assetWriter.finishWritingWithCompletionHandler { () -> Void in
                switch assetWriter.status {
                case AVAssetWriterStatus.Failed:
                    println("Error: \(assetWriter.error)")
                default:
                    let path = self.fileUrl(fileName).path!
                    let content = NSFileManager.defaultManager().contentsAtPath(path)
                    println("Video: \(path) \(content?.length)")
                }
            }
        } else {
            println("failed to append pixel buffer")
        }
    }


}

func pixelBufferFromCGImage (img: CGImageRef, frameSize: CGSize) -> CVPixelBufferRef {

    let options = [
        "kCVPixelBufferCGImageCompatibilityKey": true,
        "kCVPixelBufferCGBitmapContextCompatibilityKey": true
    ]

    var pixelBufferPointer = UnsafeMutablePointer<Unmanaged<CVPixelBuffer>?>.alloc(1)

    let buffered:CVReturn = CVPixelBufferCreate(kCFAllocatorDefault, UInt(frameSize.width), UInt(frameSize.height), OSType(kCVPixelFormatType_32ARGB), options, pixelBufferPointer)

    let lockBaseAddress = CVPixelBufferLockBaseAddress(pixelBufferPointer.memory?.takeUnretainedValue(), 0)
    var pixelData:UnsafeMutablePointer<(Void)> = CVPixelBufferGetBaseAddress(pixelBufferPointer.memory?.takeUnretainedValue())

    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.NoneSkipFirst.rawValue)
    let space:CGColorSpace = CGColorSpaceCreateDeviceRGB()

    var context:CGContextRef = CGBitmapContextCreate(pixelData, UInt(frameSize.width), UInt(frameSize.height), 8, CVPixelBufferGetBytesPerRow(pixelBufferPointer.memory?.takeUnretainedValue()), space, bitmapInfo)

    CGContextDrawImage(context, CGRectMake(0, 0, frameSize.width, frameSize.height), img)

    CVPixelBufferUnlockBaseAddress(pixelBufferPointer.memory?.takeUnretainedValue(), 0)

    return pixelBufferPointer.memory!.takeUnretainedValue()
}

pixelBufferAppend returns false! Any help would be greatly appreciated. I have been scratching my head on this all night!

Upvotes: 3

Views: 2003

Answers (1)

wiredy
wiredy

Reputation: 61

I was finally able to figure what was going on. The device could only support encoding frames up to 1920x1080px - reduced the image size and it worked. Change frameSize to be a variable and use the following code:

if frameSize.width > maxImageSize.width || frameSize.height > maxImageSize.height {
    let scale = min((maxImageSize.width/frameSize.width), (maxImageSize.height/frameSize.height))
    frameSize = CGSizeMake((scale * frameSize.width), (scale * frameSize.height))
}

Upvotes: 1

Related Questions