UIImage, созданный из CMSampleBuffer, не сохранен и вызывает EXC_BAD_ACCESS

Я строю UIImage из CMSampleBuffer. Из основного потока я вызываю функцию для доступа к данным пикселей в CMSampleBuffer и преобразую плоскости YCbCr в растровое изображение ABGR, которое я обертываю в UIImage. Я вызываю функцию из основного потока с помощью:

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
    })
})

Это поддерживает отзывчивость пользовательского интерфейса и основного потока, как я надеюсь. Функция, обрабатывающая буфер:

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 для возвращенного изображения, ничего в пользовательском интерфейсе не изменится:

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

Если я пытаюсь получить доступ к изображению любым другим способом (например, попытаться сохранить его в Parse), код вылетает с EXC_BAD_ACCESS. Опять же, если я сохраню изображение в Parse в вышеупомянутой функции, оно появится в базе данных бэкэнда, как и ожидалось.

Я также попытался вызвать функцию обработки без отправки в глобальные и основные очереди, вызвав функцию напрямую. Результаты всегда одинаковы.

Я считаю, что это потому, что изображение не сохраняется. Я попытался определить как изображение, так и контекст CGImage на уровне класса и файла, но ни один из них не изменил результат. Я думал, что это будет поддерживать ссылку, но это, очевидно, нет. Я достаточно новичок в Swift, так что я явно не понимаю, как работает ARC в этом случае.

Я также считаю, что было несколько раз при отладке с использованием Quick Look из функции, что при первом нажатии Quick Look было "недоступно"... но ожидание несколько секунд и повторное нажатие приводит к появлению изображения. Возможно ли, что данные становятся доступными дольше? Возможно, GPU->CPU? Если это так, как я могу проверить / задержать, чтобы избежать сбоя?

Как мне сохранить ссылку? Есть ли лучший способ обработки изображения, созданного из CMSampleBuffer?

1 ответ

Проблема в том, как создается CGImage. С помощью dataProvider а также CGImageCreate это конкретный вопрос:

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)!

Рабочий раствор с использованием CGBitmapContextGetData а также CGBitmapContextCreateImage следующим образом:

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
}
Другие вопросы по тегам