最近做项目遇到一个问题,iOS相机视频默认输出是yuv格式,然而图像处理类算法都是以rgb为输入,所以需要将yuv格式转为rgb格式,然后我用的是swift语言,在这里记录一下
yuv是一种图片储存格式,跟RGB格式类似。yuv中,y表示亮度,单独只有y数据就可以形成一张图片,只不过这张图片是灰色的。u和v表示色差(u和v也被称为:Cb-蓝色差,Cr-红色差), yuv比rgb数据占用字节数少,而且传输过程中可以y\u\v三种分别传输,所以最常见的是在直播推流类应用,yuv会比较常见。
多的不谈了,代码先跟上。
extension CVPixelBuffer {
    public func toBGRA() throws -> CVPixelBuffer? {
        let pixelBuffer = self
        /// Check format
        let pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer)
        guard pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange || pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange else { return pixelBuffer }
//        guard pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange else { return pixelBuffer }
        /// Split plane
        let yImage = pixelBuffer.with({ VImage(pixelBuffer: $0, plane: 0, pixelFormat: pixelFormat) })!
        let cbcrImage = pixelBuffer.with({ VImage(pixelBuffer: $0, plane: 1, pixelFormat: pixelFormat) })!
        /// Create output pixelBuffer
        let outPixelBuffer = CVPixelBuffer.make(width: yImage.width, height: yImage.height, format: kCVPixelFormatType_32BGRA)!
        /// Convert yuv to argb
        var argbImage = outPixelBuffer.with({ VImage(pixelBuffer: $0, pixelFormat: pixelFormat) })!
        try argbImage.draw(yBuffer: yImage.buffer, cbcrBuffer: cbcrImage.buffer)
        /// Convert argb to bgra
        argbImage.permute(channelMap: [3, 2, 1, 0])
//        argbImage.permute(channelMap: [0, 1, 2, 3])
        return outPixelBuffer
    }
}
struct VImage {
    let width: Int
    let height: Int
    let bytesPerRow: Int
    var buffer: vImage_Buffer
    let pixelFotmat: OSType
    init?(pixelBuffer: CVPixelBuffer, plane: Int, pixelFormat: OSType) {
        guard let rawBuffer = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, plane) else { return nil }
        self.width = CVPixelBufferGetWidthOfPlane(pixelBuffer, plane)
        self.height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane)
        self.bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, plane)
        self.buffer = vImage_Buffer(
            data: UnsafeMutableRawPointer(mutating: rawBuffer),
            height: vImagePixelCount(height),
            width: vImagePixelCount(width),
            rowBytes: bytesPerRow
        )
        self.pixelFotmat = pixelFormat
    }
    init?(pixelBuffer: CVPixelBuffer, pixelFormat: OSType) {
        guard let rawBuffer = CVPixelBufferGetBaseAddress(pixelBuffer) else { return nil }
        self.width = CVPixelBufferGetWidth(pixelBuffer)
        self.height = CVPixelBufferGetHeight(pixelBuffer)
        self.bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
        self.buffer = vImage_Buffer(
            data: UnsafeMutableRawPointer(mutating: rawBuffer),
            height: vImagePixelCount(height),
            width: vImagePixelCount(width),
            rowBytes: bytesPerRow
        )
        self.pixelFotmat = pixelFormat
    }
    mutating func draw(yBuffer: vImage_Buffer, cbcrBuffer: vImage_Buffer) throws {
        try buffer.draw(yBuffer: yBuffer, cbcrBuffer: cbcrBuffer, pixelFormat: self.pixelFotmat)
    }
    mutating func permute(channelMap: [UInt8]) {
        buffer.permute(channelMap: channelMap)
    }
}
extension CVPixelBuffer {
    func with<T>(_ closure: ((_ pixelBuffer: CVPixelBuffer) -> T)) -> T {
        CVPixelBufferLockBaseAddress(self, .readOnly)
        let result = closure(self)
        CVPixelBufferUnlockBaseAddress(self, .readOnly)
        return result
    }
    static func make(width: Int, height: Int, format: OSType) -> CVPixelBuffer? {
        var pixelBuffer: CVPixelBuffer? = nil
        CVPixelBufferCreate(kCFAllocatorDefault,
                            width,
                            height,
                            format,
                            nil,
                            &pixelBuffer)
        return pixelBuffer
    }
}
extension vImage_Buffer {
    mutating func draw(yBuffer: vImage_Buffer, cbcrBuffer: vImage_Buffer, pixelFormat: OSType) throws {
        var yBuffer = yBuffer
        var cbcrBuffer = cbcrBuffer
        var conversionMatrix: vImage_YpCbCrToARGB
        if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
             conversionMatrix = {
                var pixelRange = vImage_YpCbCrPixelRange(Yp_bias: 0, CbCr_bias: 128, YpRangeMax: 255, CbCrRangeMax: 255, YpMax: 255, YpMin: 1, CbCrMax: 255, CbCrMin: 0)
                var matrix = vImage_YpCbCrToARGB()
                vImageConvert_YpCbCrToARGB_GenerateConversion(kvImage_YpCbCrToARGBMatrix_ITU_R_709_2, &pixelRange, &matrix, kvImage420Yp8_CbCr8, kvImageARGB8888, UInt32(kvImageNoFlags))
                return matrix
            }()
        } else if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
            conversionMatrix = {
                var pixelRange = vImage_YpCbCrPixelRange(Yp_bias: 16, CbCr_bias: 128, YpRangeMax: 235, CbCrRangeMax: 240, YpMax: 235, YpMin: 16, CbCrMax: 240, CbCrMin: 16)
                var matrix = vImage_YpCbCrToARGB()
                vImageConvert_YpCbCrToARGB_GenerateConversion(kvImage_YpCbCrToARGBMatrix_ITU_R_709_2, &pixelRange, &matrix, kvImage420Yp8_CbCr8, kvImageARGB8888, UInt32(kvImageNoFlags))
                return matrix
            }()
        } else {
            fatalError()
        }
        let error = vImageConvert_420Yp8_CbCr8ToARGB8888(&yBuffer, &cbcrBuffer, &self, &conversionMatrix, nil, 255, UInt32(kvImageNoFlags))
        if error != kvImageNoError {
            fatalError()
        }
    }
    mutating func permute(channelMap: [UInt8]) {
        vImagePermuteChannels_ARGB8888(&self, &self, channelMap, 0)
    }
}我这里是转的BGRA格式,用的是CVPixelBuffer,从CMSampleBuffer获取CVPixelBuffer也很简单,
let pixelBuffer: CVPixelBuffer? = CMSampleBufferGetImageBuffer(buffer)
guard let imagePixelBuffer = pixelBuffer else {
    // 异常处理
}然后直接调用就好了
let bgraImagePixelBuffer = try? pixelBuffer.toBGRA()
guard let oPixelBuffer = bgraImagePixelBuffer else {
    // 异常处理
}