I’m working on a macOS application using the Zoom Video SDK, and as per the SDK documentation, the user video is received in the ZMVideoSDKYUVRawDataI420
format from onRawDataFrameReceived which is mention on here(Video SDK - macOS - Video) However, the documentation doesn’t explain how to convert this I420 raw data into video frames for display in a view. How to achieve this and show the user video in a view?
I am using following code to convert it into frames
func renderVideoFrame(_ frame: ZMVideoSDKYUVRawDataI420) {
let width = Int(frame.streamWidth)
let height = Int(frame.streamHeight)
let yPlane = frame.yBuffer
let uPlane = frame.uBuffer
let vPlane = frame.vBuffer
guard let rgbData = convertYUVtoRGB(yPlane: yPlane, uPlane: uPlane, vPlane: vPlane, width: width, height: height) else {
return
}
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: rgbData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
if let cgImage = context?.makeImage() {
let nsImage = NSImage(cgImage: cgImage, size: NSSize(width: width, height: height))
DispatchQueue.main.async {
self.viewController.displayImage(nsImage: nsImage)
}
}
}
func convertYUVtoRGB(yPlane: UnsafeMutablePointer<CChar>?, uPlane: UnsafeMutablePointer<CChar>?, vPlane: UnsafeMutablePointer<CChar>?, width: Int, height: Int) -> UnsafeMutablePointer<UInt8>? {
guard let yPlane = yPlane, let uPlane = uPlane, let vPlane = vPlane else {
return nil
}
let rgbSize = width * height * 4
let rgbData = UnsafeMutablePointer<UInt8>.allocate(capacity: rgbSize)
DispatchQueue.concurrentPerform(iterations: height) { y in
for x in stride(from: 0, to: width, by: 2) {
let yIndex = y * width + x
// Calculate chroma indices for the subsampled U and V planes
let chromaX = x / 2
let chromaY = y / 2
let uIndex = chromaY * (width / 2) + chromaX
let vIndex = uIndex
// Fetch U, V values
let U = Int(UInt8(bitPattern: uPlane[uIndex])) - 128
let V = Int(UInt8(bitPattern: vPlane[vIndex])) - 128
// Precompute UV components
let v1 = 5727 * V
let uv1 = -(1617 * U) - (2378 * V)
let u1 = 8324 * U
// Process even pixel
var Y = Int(UInt8(bitPattern: yPlane[yIndex])) << 12
var R = (Y + v1) >> 12
var G = (Y + uv1) >> 12
var B = (Y + u1) >> 12
// Clamp RGB values to valid range [0, 255]
R = min(max(R, 0), 255)
G = min(max(G, 0), 255)
B = min(max(B, 0), 255)
// Store the RGB values in the output buffer
var rgbIndex = yIndex * 4
rgbData[rgbIndex] = UInt8(R)
rgbData[rgbIndex + 1] = UInt8(G)
rgbData[rgbIndex + 2] = UInt8(B)
rgbData[rgbIndex + 3] = 0 // Alpha value
// Process odd pixel
Y = Int(UInt8(bitPattern: yPlane[yIndex + 1])) << 12
R = (Y + v1) >> 12
G = (Y + uv1) >> 12
B = (Y + u1) >> 12
// Clamp RGB values to valid range [0, 255]
R = min(max(R, 0), 255)
G = min(max(G, 0), 255)
B = min(max(B, 0), 255)
// Store the RGB values in the output buffer
rgbIndex = (yIndex + 1) * 4
rgbData[rgbIndex] = UInt8(R)
rgbData[rgbIndex + 1] = UInt8(G)
rgbData[rgbIndex + 2] = UInt8(B)
rgbData[rgbIndex + 3] = 0 // Alpha value
}
}
return rgbData
}