从CMSampleBuffer构建的UIImage未保留并导致EXC_BAD_ACCESS

时间:2016-01-30 03:35:32

标签: ios swift uiimageview uiimage core-media

我正在从CMSampleBuffer构建UIImage。在主线程中,我调用一个函数来访问CMSampleBuffer中的像素数据,并将YCbCr平面转换为我在UIImage中包装的ABGR位图。我用主线程调用函数:

let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
    dispatch_async(dispatch_get_global_queue(priority, 0), {() -> Void in
    let image = self.imageFromSampleBuffer(frame)
    dispatch_async(dispatch_get_main_queue(), {() -> Void in
        self.testView.image = image
        self.testView.hidden = false
    })
})

这样可以保持UI和主线程的响应能力。处理缓冲区的函数是:

func imageFromSampleBuffer(sampleBuffer: CMSampleBuffer) -> UIImage {

    let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
    CVPixelBufferLockBaseAddress(pixelBuffer, 0)
    let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
    let chromaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1)

    let width = CVPixelBufferGetWidth(pixelBuffer)
    let height = CVPixelBufferGetHeight(pixelBuffer)

    let lumaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
    let chromaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1)
    let lumaBuffer = UnsafeMutablePointer<UInt8>(lumaBaseAddress)
    let chromaBuffer = UnsafeMutablePointer<UInt8>(chromaBaseAddress)

    var rgbaImage = [UInt8](count: 4*width*height, repeatedValue: 0)
    for var x = 0; x < width; x++ {
        for var y = 0; y < height; y++ {
            let lumaIndex = x+y*lumaBytesPerRow
            let chromaIndex = (y/2)*chromaBytesPerRow+(x/2)*2
            let yp = lumaBuffer[lumaIndex]
            let cb = chromaBuffer[chromaIndex]
            let cr = chromaBuffer[chromaIndex+1]

            let ri = Double(yp)                                + 1.402   * (Double(cr) - 128)
            let gi = Double(yp) - 0.34414 * (Double(cb) - 128) - 0.71414 * (Double(cr) - 128)
            let bi = Double(yp) + 1.772   * (Double(cb) - 128)

            let r = UInt8(min(max(ri,0), 255))
            let g = UInt8(min(max(gi,0), 255))
            let b = UInt8(min(max(bi,0), 255))
            rgbaImage[(x + y * width) * 4] = b
            rgbaImage[(x + y * width) * 4 + 1] = g
            rgbaImage[(x + y * width) * 4 + 2] = r
            rgbaImage[(x + y * width) * 4 + 3] = 255
        }
    }

    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let dataProvider: CGDataProviderRef = CGDataProviderCreateWithData(nil, rgbaImage, 4 * width * height, nil)!
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.NoneSkipFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue)
    let cgImage = CGImageCreate(width, height, 8, 32, width * 4, colorSpace!, bitmapInfo, dataProvider, nil, true, CGColorRenderingIntent.RenderingIntentDefault)!
    let image = UIImage(CGImage: cgImage)
    CVPixelBufferUnlockBaseAddress(pixelBuffer,0)

    return image
}

如果我在函数返回之前放置断点,我可以使用“快速查看”并查看图像(这是我期望的)。但是,一旦函数返回,我就无法在其他地方使用image,Quick Look总是会失败。如果我尝试将UIImageView设置为返回的图像,则UI中的任何内容都不会更改:

testView.image = image \\The UIImageView does not update.

如果我尝试以任何其他方式访问图像(例如,尝试将其保存到Parse),则代码会因EXC_BAD_ACCESS而崩溃。同样,如果我在上述函数中将图像保存到Parse,它会按预期显示在后端数据库中。

我还尝试通过直接调用函数调用处理函数而不调度到全局队列和主队列。结果总是一样的。

我相信这是因为图像没有保留。我已经尝试在类和文件级别定义图像和CGImage上下文,但都没有改变结果。我认为这会保留一个参考,但显然没有。我对Swift足够新,我显然不明白ARC在这种情况下是如何工作的。

我也相信在我第一次点击快速查看功能时使用Quick Look进行调试时有几次“不可用”...但等待几秒钟再次点击会导致图像出现。是否可能需要更长时间才能提供数据?也许GPU-> CPU?如果是这样,我如何检查/延迟以避免崩溃?

如何维护参考?有没有更好的方法来处理从CMSampleBuffer创建的图像?

1 个答案:

答案 0 :(得分:0)

问题在于创建CGImage的方式。使用dataProviderCGImageCreate是具体问题:

let dataProvider = CGDataProviderCreateWithData(nil, rgbaImage, 4 * width * height, nil)!
let cgImage = CGImageCreate(width, height, 8, 32, width * 4, colorSpace!, bitmapInfo, dataProvider, nil, true, CGColorRenderingIntent.RenderingIntentDefault)!

使用CGBitmapContextGetDataCGBitmapContextCreateImage的工作解决方案如下:

func imageFromSampleBuffer(sampleBuffer: CMSampleBuffer) -> UIImage? {

    let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
    CVPixelBufferLockBaseAddress(pixelBuffer, 0)
    let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
    let chromaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1)

    let width = CVPixelBufferGetWidth(pixelBuffer)
    let height = CVPixelBufferGetHeight(pixelBuffer)

    let lumaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
    let chromaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1)
    let lumaBuffer = UnsafeMutablePointer<UInt8>(lumaBaseAddress)
    let chromaBuffer = UnsafeMutablePointer<UInt8>(chromaBaseAddress)

    let contextBytesPerRow = Int(width) * 4
    let contextByteCount = contextBytesPerRow * Int(height)
    let colorSpace = CGColorSpaceCreateDeviceRGB()

    let bitmapData = malloc(contextByteCount)
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue)

    let context = CGBitmapContextCreate(bitmapData, width, height, 8, contextBytesPerRow, colorSpace, bitmapInfo.rawValue)

    let data = CGBitmapContextGetData(context)
    let rgbaImage = UnsafeMutablePointer<UInt8>(data)
    for var x = 0; x < width; x++ {
        for var y = 0; y < height; y++ {
            let lumaIndex = x+y*lumaBytesPerRow
            let chromaIndex = (y/2)*chromaBytesPerRow+(x/2)*2
            let yp = lumaBuffer[lumaIndex]
            let cb = chromaBuffer[chromaIndex]
            let cr = chromaBuffer[chromaIndex+1]

            let ri = Double(yp)                                + 1.402   * (Double(cr) - 128)
            let gi = Double(yp) - 0.34414 * (Double(cb) - 128) - 0.71414 * (Double(cr) - 128)
            let bi = Double(yp) + 1.772   * (Double(cb) - 128)

            let r = UInt8(min(max(ri,0), 255))
            let g = UInt8(min(max(gi,0), 255))
            let b = UInt8(min(max(bi,0), 255))
            rgbaImage[(x + y * width) * 4] = b
            rgbaImage[(x + y * width) * 4 + 1] = g
            rgbaImage[(x + y * width) * 4 + 2] = r
            rgbaImage[(x + y * width) * 4 + 3] = 255
        }
    }
    let quartzImage = CGBitmapContextCreateImage(context)
    CVPixelBufferUnlockBaseAddress(pixelBuffer,0)
    let image = UIImage(CGImage: quartzImage!, scale: CGFloat(1.0), orientation: UIImageOrientation.Right)

    return (image)
    // frontCameraImageOrientation = UIImageOrientation.LeftMirrored
    // backCameraImageOrientation = UIImageOrientation.Right
}