Как конвертировать кадры YUV (из OTVideoFrame) в CVPixelBuffer

Мне нужно преобразовать кадры YUV в CVPixelBuffer, которые я получаю от OTVideoFrame Класс

Этот класс предоставляет массив плоскостей в видеокадре, который содержит три элемента для кадра y, u, v, каждый с индексом 0,1,2.

@property (неатомный, сохранить) плоскости NSPointerArray *

и формат из кадр видео

@property (неатомный, сохранить) формат OTVideoFormat *

Содержит такие свойства, как ширина, высота, bytesPerRow и т. Д. Кадра.

Мне нужно добавить фильтр к изображению, которое я получаю в виде OTVideoFrame, я уже пробовал эти ответы:

По этим двум ссылкам есть решения в Objective-C, но я хочу сделать это быстро. Один из ответов во второй ссылке был быстрым, но в нем отсутствует некоторая информация о структуре YUVFrame, на которую ссылается ответ.

Я получаю формат NV12.

Вот что я пытался сделать до сих пор, но не знаю, что делать дальше:

 /**
 * Calcualte the size of each plane from OTVideoFrame.
 *
 * @param frame The frame to render.
 * @return tuple containing three elements for size of each plane
 */
fileprivate func calculatePlaneSize(forFrame frame: OTVideoFrame)
        -> (ySize: Int, uSize: Int, vSize: Int){
            guard let frameFormat = frame.format
                else {
                    return (0, 0 ,0)
            }
            let baseSize = Int(frameFormat.imageWidth * frameFormat.imageHeight) * MemoryLayout<GLubyte>.size
            return (baseSize, baseSize / 4, baseSize / 4)
    }

/**
 * Renders a frame to the video renderer.
 *
 * @param frame The frame to render.
 */
func renderVideoFrame(_ frame: OTVideoFrame) {


    let planeSize = calculatePlaneSize(forFrame: frame)
    let yPlane = UnsafeMutablePointer<GLubyte>.allocate(capacity: planeSize.ySize)
    let uPlane = UnsafeMutablePointer<GLubyte>.allocate(capacity: planeSize.uSize)
    let vPlane = UnsafeMutablePointer<GLubyte>.allocate(capacity: planeSize.vSize)

    memcpy(yPlane, frame.planes?.pointer(at: 0), planeSize.ySize)
    memcpy(uPlane, frame.planes?.pointer(at: 1), planeSize.uSize)
    memcpy(vPlane, frame.planes?.pointer(at: 2), planeSize.vSize)

    let yStride = frame.format!.bytesPerRow.object(at: 0) as! Int
    // multiply chroma strides by 2 as bytesPerRow represents 2x2 subsample
    let uStride = frame.format!.bytesPerRow.object(at: 1) as! Int
    let vStride = frame.format!.bytesPerRow.object(at: 2) as! Int

    let width = frame.format!.imageWidth
    let height = frame.format!.imageHeight

    var pixelBuffer: CVPixelBuffer? = nil
    var err: CVReturn;


    err = CVPixelBufferCreate(kCFAllocatorDefault, Int(width), Int(height), kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, nil, &pixelBuffer)
    if (err != 0) {
        NSLog("Error at CVPixelBufferCreate %d", err)
        fatalError()
    }

}

Руководствуясь этими двумя ссылками, я пытался создать буфер пикселей, но каждый раз на этом этапе я останавливался, потому что преобразование кода Objective-C после этого не похоже на то, что мы имеем в Swift 3.


person Agent Smith    schedule 05.05.2017    source источник


Ответы (2)


Для тех, кто ищет быстрое решение, я сделал с swift Accelerate, используя функцию vImageConvert_AnyToAny(_:_:_:_:_:).

import Foundation
import Accelerate
import UIKit
import OpenTok
class Accelerater{


    var infoYpCbCrToARGB = vImage_YpCbCrToARGB()
    init() {
        _ = configureYpCbCrToARGBInfo()
    }

    func configureYpCbCrToARGBInfo() -> vImage_Error {
        print("Configuring")
        var pixelRange = vImage_YpCbCrPixelRange(Yp_bias: 0,
                                                 CbCr_bias: 128,
                                                 YpRangeMax: 255,
                                                 CbCrRangeMax: 255,
                                                 YpMax: 255,
                                                 YpMin: 1,
                                                 CbCrMax: 255,
                                                 CbCrMin: 0)

        let error = vImageConvert_YpCbCrToARGB_GenerateConversion(
            kvImage_YpCbCrToARGBMatrix_ITU_R_601_4!,
            &pixelRange,
            &infoYpCbCrToARGB,
            kvImage420Yp8_Cb8_Cr8,
            kvImageARGB8888,
            vImage_Flags(kvImagePrintDiagnosticsToConsole))



        print("Configration done \(error)")
        return error
    }

    public func convertFrameVImageYUV(toUIImage frame: OTVideoFrame, flag: Bool) -> UIImage {

        var result: UIImage? = nil
        let width = frame.format?.imageWidth ?? 0
        let height = frame.format?.imageHeight ?? 0
        var pixelBuffer: CVPixelBuffer? = nil
        _ = CVPixelBufferCreate(kCFAllocatorDefault, Int(width), Int(height), kCVPixelFormatType_32BGRA, nil, &pixelBuffer)

        _ = convertFrameVImageYUV(frame, to: pixelBuffer)
        var ciImage: CIImage? = nil
        if let pixelBuffer = pixelBuffer {
            ciImage = CIImage(cvPixelBuffer: pixelBuffer)
        }

        let temporaryContext = CIContext(options: nil)
        var uiImage: CGImage? = nil
        if let ciImage = ciImage {
            uiImage = temporaryContext.createCGImage(ciImage, from: CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(pixelBuffer!), height: CVPixelBufferGetHeight(pixelBuffer!)))
        }

        if let uiImage = uiImage {
            result = UIImage(cgImage: uiImage)
        }
        CVPixelBufferUnlockBaseAddress(pixelBuffer!, [])
        return result!

    }

    func convertFrameVImageYUV(_ frame: OTVideoFrame, to pixelBufferRef: CVPixelBuffer?) -> vImage_Error{
        let start  = CFAbsoluteTimeGetCurrent()
        if pixelBufferRef == nil {
            print("No PixelBuffer refrance found")
            return vImage_Error(kvImageInvalidParameter)
        }

        let width = frame.format?.imageWidth ?? 0
        let height = frame.format?.imageHeight ?? 0
        let subsampledWidth = frame.format!.imageWidth/2
        let subsampledHeight = frame.format!.imageHeight/2
        print("subsample height \(subsampledHeight) \(subsampledWidth)")
        let planeSize = calculatePlaneSize(forFrame: frame)

        print("ysize : \(planeSize.ySize) \(planeSize.uSize) \(planeSize.vSize)")
        let yPlane = UnsafeMutablePointer<GLubyte>.allocate(capacity: planeSize.ySize)
        let uPlane = UnsafeMutablePointer<GLubyte>.allocate(capacity: planeSize.uSize)
        let vPlane = UnsafeMutablePointer<GLubyte>.allocate(capacity: planeSize.vSize)

        memcpy(yPlane, frame.planes?.pointer(at: 0), planeSize.ySize)
        memcpy(uPlane, frame.planes?.pointer(at: 1), planeSize.uSize)
        memcpy(vPlane, frame.planes?.pointer(at: 2), planeSize.vSize)

        let yStride = frame.format!.bytesPerRow.object(at: 0) as! Int
        // multiply chroma strides by 2 as bytesPerRow represents 2x2 subsample
        let uStride = frame.format!.bytesPerRow.object(at: 1) as! Int
        let vStride = frame.format!.bytesPerRow.object(at: 2) as! Int

        var yPlaneBuffer = vImage_Buffer(data: yPlane, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: yStride)

        var uPlaneBuffer = vImage_Buffer(data: uPlane, height: vImagePixelCount(subsampledHeight), width: vImagePixelCount(subsampledWidth), rowBytes: uStride)




        var vPlaneBuffer = vImage_Buffer(data: vPlane, height: vImagePixelCount(subsampledHeight), width: vImagePixelCount(subsampledWidth), rowBytes: vStride)
        CVPixelBufferLockBaseAddress(pixelBufferRef!, .readOnly)
        let pixelBufferData = CVPixelBufferGetBaseAddress(pixelBufferRef!)
        let rowBytes = CVPixelBufferGetBytesPerRow(pixelBufferRef!)
        var destinationImageBuffer = vImage_Buffer()
        destinationImageBuffer.data = pixelBufferData
        destinationImageBuffer.height = vImagePixelCount(height)
        destinationImageBuffer.width = vImagePixelCount(width)
        destinationImageBuffer.rowBytes = rowBytes

        var permuteMap: [UInt8] = [3, 2, 1, 0] // BGRA
        let convertError = vImageConvert_420Yp8_Cb8_Cr8ToARGB8888(&yPlaneBuffer, &uPlaneBuffer, &vPlaneBuffer, &destinationImageBuffer, &infoYpCbCrToARGB, &permuteMap, 255, vImage_Flags(kvImagePrintDiagnosticsToConsole))

        CVPixelBufferUnlockBaseAddress(pixelBufferRef!, [])


        yPlane.deallocate()
        uPlane.deallocate()
        vPlane.deallocate()
        let end = CFAbsoluteTimeGetCurrent()
        print("Decoding time \((end-start)*1000)")
        return convertError

    }
    fileprivate func calculatePlaneSize(forFrame frame: OTVideoFrame)
        -> (ySize: Int, uSize: Int, vSize: Int)
    {
        guard let frameFormat = frame.format
            else {
                return (0, 0 ,0)
        }
        let baseSize = Int(frameFormat.imageWidth * frameFormat.imageHeight) * MemoryLayout<GLubyte>.size
        return (baseSize, baseSize / 4, baseSize / 4)
    }

}

Производительность протестирована на iPhone7, преобразование одного кадра составляет менее миллисекунды.

person Afsar edrisy    schedule 27.09.2019

Вот что у меня сработало (я взял вашу функцию и немного изменил ее):

func createPixelBufferWithVideoFrame(_ frame: OTVideoFrame) -> CVPixelBuffer? {
    if let fLock = frameLock {
        fLock.lock()

        let planeSize = calculatePlaneSize(forFrame: frame)

        let yPlane = UnsafeMutablePointer<GLubyte>.allocate(capacity: planeSize.ySize)
        let uPlane = UnsafeMutablePointer<GLubyte>.allocate(capacity: planeSize.uSize)
        let vPlane = UnsafeMutablePointer<GLubyte>.allocate(capacity: planeSize.vSize)

        memcpy(yPlane, frame.planes?.pointer(at: 0), planeSize.ySize)
        memcpy(uPlane, frame.planes?.pointer(at: 1), planeSize.uSize)
        memcpy(vPlane, frame.planes?.pointer(at: 2), planeSize.vSize)

        let width = frame.format!.imageWidth
        let height = frame.format!.imageHeight

        var pixelBuffer: CVPixelBuffer? = nil
        var err: CVReturn;

        err = CVPixelBufferCreate(kCFAllocatorDefault, Int(width), Int(height), kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, nil, &pixelBuffer)
        if (err != 0) {
            NSLog("Error at CVPixelBufferCreate %d", err)
            return nil
        }

        if let pixelBuffer = pixelBuffer {
            CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
            let yPlaneTo = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
            memcpy(yPlaneTo, yPlane, planeSize.ySize)

            let uvRow: Int = planeSize.uSize*2/Int(width)

            let halfWidth: Int = Int(width)/2

            if let uPlaneTo = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1) {
                let uvPlaneTo = uPlaneTo.bindMemory(to: GLubyte.self, capacity: Int(uvRow*halfWidth*2))

                for i in 0..<uvRow {
                    for j in 0..<halfWidth {
                        let dataIndex: Int = Int(i) * Int(halfWidth) + Int(j)
                        let uIndex: Int = (i * Int(width)) + Int(j) * 2
                        let vIndex: Int = uIndex + 1

                        uvPlaneTo[uIndex] = uPlane[dataIndex]
                        uvPlaneTo[vIndex] = vPlane[dataIndex]

                    }
                }

            }

        }

        fLock.unlock()

        return pixelBuffer
    }
    return nil
}
person Ivan Pavlović    schedule 02.06.2017