Skip to content

Instantly share code, notes, and snippets.

@JBurkeKF
Created February 20, 2020 18:33
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JBurkeKF/a2b4007c797c02209a836f322177a862 to your computer and use it in GitHub Desktop.
Save JBurkeKF/a2b4007c797c02209a836f322177a862 to your computer and use it in GitHub Desktop.
Copy a WebRTC I420Frame, convert it to a UIImage (requires CocoaPods GoogleWebRTC and libyuv-iOS)
//
// I420Frame.h
//
// Created by Joel Burke on 2/7/17.
// Copyright © 2017 Kittehface Software. All rights reserved.
//
// Requires GoogleWebRTC CocoaPod
// Requires libyuv-iOS CocoaPod
#ifndef I420Frame_h
#define I420Frame_h
#import <Foundation/NSObject.h>
#import <UIKit/UIKit.h>
#import <WebRTC/RTCVideoFrame.h>
@interface I420Frame : NSObject
@property NSUInteger width;
@property NSUInteger height;
@property CFAbsoluteTime frameTimeS;
- (instancetype)initWithRTCFrame:(RTCVideoFrame *)frame atTime:(CFAbsoluteTime)timeS;
- (instancetype)initWithI420Frame:(I420Frame *)frame;
- (void)copyRTCFrame:(RTCVideoFrame *)frame atTime:(CFAbsoluteTime)timeS;
- (UIImage *)getUIImage;
@end
#endif /* I420Frame_h */
//
// I420Frame.m
//
// Created by Joel Burke on 2/7/17.
// Copyright © 2017 Kittehface Software. All rights reserved.
//
// Requires GoogleWebRTC CocoaPod
// Requires libyuv-iOS CocoaPod
#import "I420Frame.h"
#import <libyuv.h>
#import <WebRTC/RTCVideoFrameBuffer.h>
#import <WebRTC/RTCI420Buffer.h>
#import <WebRTC/RTCYUVPlanarBuffer.h>
@interface I420Frame()
@property NSUInteger chromaWidth;
@property NSUInteger chromaHeight;
@property uint8_t* yPlane;
@property uint8_t* uPlane;
@property uint8_t* vPlane;
@property NSInteger yPitch;
@property NSInteger uPitch;
@property NSInteger vPitch;
@property RTCVideoRotation rotation;
@end
@implementation I420Frame
@synthesize width = _width;
@synthesize height = _height;
@synthesize frameTimeS = _frameTimeS;
@synthesize chromaWidth = _chromaWidth;
@synthesize chromaHeight = _chromaHeight;
@synthesize yPlane = _yPlane;
@synthesize uPlane = _uPlane;
@synthesize vPlane = _vPlane;
@synthesize yPitch = _yPitch;
@synthesize uPitch = _uPitch;
@synthesize vPitch = _vPitch;
@synthesize rotation = _rotation;
- (instancetype)initWithRTCFrame:(RTCVideoFrame *)frame atTime:(CFAbsoluteTime)timeS {
if (self = [super init]) {
_width = 0;
_height = 0;
_chromaWidth = 0;
_chromaHeight = 0;
_yPlane = nil;
_uPlane = nil;
_vPlane = nil;
_rotation = RTCVideoRotation_0;
[self copyRTCFrame:frame atTime:timeS];
}
return self;
}
- (instancetype)initWithI420Frame:(I420Frame *)frame {
if (self = [super init]) {
_width = 0;
_height = 0;
_chromaWidth = 0;
_chromaHeight = 0;
_yPlane = nil;
_uPlane = nil;
_vPlane = nil;
_rotation = RTCVideoRotation_0;
if (frame) {
[frame copyToI420Frame:self];
}
}
return self;
}
- (void)dealloc {
if (_yPlane) {
free(_yPlane);
}
if (_uPlane) {
free(_uPlane);
}
if (_vPlane) {
free(_vPlane);
}
}
- (void)copyRTCFrame:(RTCVideoFrame *)videoFrame atTime:(CFAbsoluteTime)timeS {
@synchronized (self) {
id<RTCYUVPlanarBuffer> frame = nil;
if (videoFrame) {
RTCVideoFrame *i420VideoFrame = [videoFrame newI420VideoFrame];
if (i420VideoFrame) {
id<RTCVideoFrameBuffer> buffer = [i420VideoFrame buffer];
if (buffer) {
id<RTCI420Buffer> i420Buffer = [buffer toI420];
if (i420Buffer) {
frame = (id<RTCYUVPlanarBuffer>)i420Buffer;
}
}
}
}
if (frame) {
NSUInteger newWidth = [frame width];
NSUInteger newHeight = [frame height];
NSUInteger newChromaWidth = [frame chromaWidth];
NSUInteger newChromaHeight = [frame chromaHeight];
const uint8_t* frameYPlane = [frame dataY];
const uint8_t* frameUPlane = [frame dataU];
const uint8_t* frameVPlane = [frame dataV];
if (!frameYPlane || !frameUPlane || !frameVPlane) {
if (_yPlane) {
free(_yPlane);
_yPlane = nil;
}
if (_uPlane) {
free(_uPlane);
_uPlane = nil;
}
if (_vPlane) {
free(_vPlane);
_vPlane = nil;
}
_yPitch = [frame strideY];
_uPitch = [frame strideU];
_vPitch = [frame strideV];
} else {
if (!_yPlane || _width * _height != newWidth * newHeight) {
if (_yPlane) {
free(_yPlane);
}
_yPlane = malloc(newWidth * newHeight * sizeof(uint8_t));
}
if (!_uPlane || _chromaWidth * _chromaHeight != newChromaWidth * newChromaHeight) {
if (_uPlane) {
free(_uPlane);
}
_uPlane = malloc(newChromaWidth * newChromaHeight * sizeof(uint8_t));
}
if (!_vPlane || _chromaWidth * _chromaHeight != newChromaWidth * newChromaHeight) {
if (_vPlane) {
free(_vPlane);
}
_vPlane = malloc(newChromaWidth * newChromaHeight * sizeof(uint8_t));
}
_yPitch = [frame strideY];
_uPitch = [frame strideU];
_vPitch = [frame strideV];
libyuv:I420Copy(frameYPlane, (int) _yPitch, frameUPlane, (int) _uPitch, frameVPlane, (int) _vPitch, _yPlane, (int) newWidth, _uPlane, (int) newChromaWidth, _vPlane, (int) newChromaWidth, (int) newWidth, (int) newHeight);
}
_width = newWidth;
_height = newHeight;
_frameTimeS = timeS;
_chromaWidth = newChromaWidth;
_chromaHeight = newChromaHeight;
_rotation = [videoFrame rotation];
}
}
}
- (void)copyToI420Frame:(I420Frame *)frame {
@synchronized (self) {
if (frame) {
if (_yPlane && _uPlane && _vPlane) {
if (!frame.yPlane || frame.width * frame.height != _width * _height) {
if (frame.yPlane) {
free(frame.yPlane);
}
frame.yPlane = malloc(_width * _height * sizeof(uint8_t));
}
if (!frame.uPlane || frame.chromaWidth * frame.chromaHeight != _chromaWidth * _chromaHeight) {
if (frame.uPlane) {
free(frame.uPlane);
}
frame.uPlane = malloc(_chromaWidth * _chromaHeight * sizeof(uint8_t));
}
if (!frame.vPlane || frame.chromaWidth * frame.chromaHeight != _chromaWidth * _chromaHeight) {
if (frame.vPlane) {
free(frame.vPlane);
}
frame.vPlane = malloc(_chromaWidth * _chromaHeight * sizeof(uint8_t));
}
memcpy(frame.yPlane, _yPlane, _width * _height * sizeof(uint8_t));
memcpy(frame.uPlane, _uPlane, _chromaWidth * _chromaHeight * sizeof(uint8_t));
memcpy(frame.vPlane, _vPlane, _chromaWidth * _chromaHeight * sizeof(uint8_t));
} else {
if (frame.yPlane) {
free(frame.yPlane);
frame.yPlane = nil;
}
if (frame.uPlane) {
free(frame.uPlane);
frame.uPlane = nil;
}
if (frame.vPlane) {
free(frame.vPlane);
frame.vPlane = nil;
}
}
frame.width = _width;
frame.height = _height;
frame.frameTimeS = _frameTimeS;
frame.chromaWidth = _chromaWidth;
frame.chromaHeight = _chromaHeight;
frame.yPitch = _yPitch;
frame.uPitch = _uPitch;
frame.vPitch = _vPitch;
frame.rotation = _rotation;
}
}
}
- (UIImage *)getUIImage {
@synchronized (self) {
NSUInteger size = _width * _height * 4;
uint8_t *buffer = malloc(size);
if ([self convertRGBA:buffer withSize:size]) {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext = CGBitmapContextCreate(buffer, _width, _height, 8, _width * 4, colorSpace, kCGImageAlphaNoneSkipLast);
CFRelease(colorSpace);
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
UIImageOrientation orientation = UIImageOrientationUp;
switch (_rotation) {
case RTCVideoRotation_0: orientation = UIImageOrientationUp; break;
case RTCVideoRotation_90: orientation = UIImageOrientationRight; break;
case RTCVideoRotation_180: orientation = UIImageOrientationDown; break;
case RTCVideoRotation_270: orientation = UIImageOrientationLeft; break;
}
UIImage *image = [UIImage imageWithCGImage:cgImage scale:1.0 orientation:orientation];
CFRelease(cgImage);
CFRelease(bitmapContext);
free(buffer);
return image;
} else {
free(buffer);
}
}
return nil;
}
- (BOOL)convertRGBA:(uint8_t *)buffer withSize:(NSUInteger)size {
if (buffer && _yPlane && _uPlane && _vPlane) {
if (_width * _height * 4 == size) {
libyuv:I420ToABGR(_yPlane, (int) _yPitch, _uPlane, (int) _uPitch, _vPlane, (int) _vPitch, buffer, (int) _width * 4, (int) _width, (int) _height);
return YES;
}
}
return NO;
}
@end
@JBurkeKF
Copy link
Author

The Java version is YuvFrame.java.

Copy link

ghost commented Feb 21, 2020

<3

@fukemy
Copy link

fukemy commented Dec 29, 2022

Hi, can u tell me the difference between your code and my code:

    let pixelBufferr = frame.buffer as! RTCCVPixelBuffer
    let pixelBufferRef = pixelBufferr.pixelBuffer
    let ciImage = CIImage(cvPixelBuffer: pixelBufferRef)
    let curImage = UIImage(ciImage: ciImage)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment