/*---------------------------------------------------------------------------*/
/* SampleHandler.h                                                           */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#import <Accelerate/Accelerate.h>
#import "platform/platform.h"
#import "client/ios/VPX.framework/Headers/vpx/vp8cx.h"
#import "client/ios/VPX.framework/Headers/vpx/vpx_encoder.h"
#import "ios_screen_capture_sender.h"
#import "SampleHandler.h"

@interface SampleHandler ()

@property (nonatomic) int64_t presentationTimestamp;
@property (nonatomic) dword rtpTimestamp;
@property (nonatomic) unsigned fullIntraAck;
@property (nonatomic) size_t width;
@property (nonatomic) size_t height;
@property (nonatomic) byte *scaleBuffer;
@property (nonatomic) byte *YBuffer;
@property (nonatomic) byte *UVBuffer;
@property (nonatomic) vpx_codec_iface_t *vpxEncoderInterface;
@property (nonatomic) vpx_codec_ctx_t *vpxEncoder;
@property (nonatomic) vpx_codec_enc_cfg_t *vpxEncoderCfg;
@property (nonatomic) int vpxFrameIndex;
@property (nonatomic) bool vpxEncoderInitialized;
@property (nonatomic) IosScreenCaptureSender *screenCaptureSender;

@end
@implementation SampleHandler

#define IMAGE_BUFFER_MAX_PLANES     4
#define IMAGE_BUFFER_ALIGN(x)       (((x) + 0x3f) & ~0x3f)
#define IMAGE_BUFFER_ALIGN_PTR(x)   ((x) + ((((byte *) 0) - (x)) & 0x3f))

typedef struct {
    dword timestamp;
    dword duration;
}
TranscodingEncodeInfo;

- (void) broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo
{
    // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
    self.presentationTimestamp = 0;
    self.rtpTimestamp = 0;
    self.fullIntraAck = 0;
    self.width = 0;
    self.height = 0;
    self.scaleBuffer = NULL;
    self.YBuffer = NULL;
    self.UVBuffer = NULL;
    self.vpxEncoderInterface = vpx_codec_vp8_cx();
    self.vpxEncoder = (vpx_codec_ctx_t *)(malloc(sizeof(vpx_codec_ctx_t)));
    self.vpxEncoderCfg = (vpx_codec_enc_cfg_t *)(malloc(sizeof(vpx_codec_enc_cfg_t)));
    self.vpxFrameIndex = 0;
    self.vpxEncoderInitialized = false;
    self.screenCaptureSender = (IosScreenCaptureSender *)(malloc(sizeof(IosScreenCaptureSender)));
    IosScreenCaptureSenderOpen(self.screenCaptureSender);
}

- (void) broadcastPaused
{
    // User has requested to pause the broadcast. Samples will stop being delivered.
}

- (void) broadcastResumed
{
    // User has requested to resume the broadcast. Samples delivery will resume.
}

- (void) broadcastFinished
{
    IosScreenCaptureSenderSend(self.screenCaptureSender, NULL, 0, 0, 0, 0, 0, 0, 0, 1);
    IosScreenCaptureSenderClose(self.screenCaptureSender);
    // User has requested to finish the broadcast.
    if (self.vpxEncoderInitialized) {
        self.vpxEncoderInitialized = false;
        if (vpx_codec_destroy(self.vpxEncoder)) {
            NSLog(@"SampleHandler: vpx_codec_destroy transcoding failed");
        }
    }
    free(self.vpxEncoder);
    self.vpxEncoder = nil;
    free(self.vpxEncoderCfg);
    self.vpxEncoderCfg = nil;
    free(self.screenCaptureSender);
    self.screenCaptureSender = nil;
    if (self.scaleBuffer) {
        free(self.scaleBuffer = NULL);
        self.scaleBuffer = NULL;
    }
    if (self.YBuffer) {
        free(self.YBuffer = NULL);
        self.YBuffer = NULL;
    }
    if (self.UVBuffer) {
        free(self.UVBuffer = NULL);
        self.UVBuffer = NULL;
    }
}

void didTranscodeVPX(SampleHandler *sampleHandler, vpx_image_t *vpxImage)
{
    vpx_codec_iter_t iter = NULL;
    const vpx_codec_cx_pkt_t *pkt = NULL;
    TranscodingEncodeInfo *encodeInfo = (TranscodingEncodeInfo *)(vpxImage->user_priv);
    int width = vpxImage->d_w;
    int height = vpxImage->d_h;
    dword timestamp = encodeInfo->timestamp;
    dword rtpDuration = encodeInfo->duration;

    while ((pkt = vpx_codec_get_cx_data(sampleHandler.vpxEncoder, &iter)) != NULL) {
        if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
            IosScreenCaptureSenderSend(sampleHandler.screenCaptureSender, pkt->data.frame.buf, (dword)(pkt->data.frame.sz), timestamp, (word) rtpDuration, (pkt->data.frame.flags & VPX_FRAME_IS_KEY) ? 1 : 0, (word) width, (word) height, 0, 1);
        }
    }
}

void rotateCopyByte(unsigned width, unsigned height, unsigned srcRowBytes, const byte * src, unsigned targetWidth, unsigned targetHeight, unsigned dstRowBytes, byte * dst, unsigned rotation)
{
    unsigned i, j, k, l, m;

    l = dstRowBytes * targetHeight;
    switch(rotation) {
    case 90:
        k = ((width - targetHeight) / 2) + ((height - targetWidth) / 2) * srcRowBytes;
        m = l;
        for (j = 0; j < targetWidth / 4; j++) {
            for (i = 0; i < targetHeight; i++) {
                m -= dstRowBytes;
                dst[m] = src[k];
                dst[m+1] = src[k + srcRowBytes];
                dst[m+2] = src[k + srcRowBytes + srcRowBytes];
                dst[m+3] = src[k + srcRowBytes + srcRowBytes + srcRowBytes];
                k++;
            }
            k += 4 * srcRowBytes - targetHeight;
            m += l + 4;
        }
        switch (targetWidth & 0x3) {
        case 3:
            for (i = 0; i < targetHeight; i++) {
                m -= dstRowBytes;
                dst[m] = src[k];
                dst[m+1] = src[k + srcRowBytes];
                dst[m+2] = src[k + srcRowBytes + srcRowBytes];
                k++;
            }
            break;
        case 2:
            for (i = 0; i < targetHeight; i++) {
                m -= dstRowBytes;
                dst[m] = src[k];
                dst[m+1] = src[k + srcRowBytes];
                k++;
            }
            break;
        case 1:
            for (i = 0; i < targetHeight; i++) {
                m -= dstRowBytes;
                dst[m] = src[k];
                k++;
            }
            break;
        default:
            break;
        }
        break;
    case 180:
        k = ((height - targetHeight) / 2) * srcRowBytes + ((width - targetWidth) / 2);
        m = targetHeight * dstRowBytes;
        for (j = 0; j < targetHeight; j++) {
            m -= dstRowBytes - targetWidth;
            for (i = 0; i < targetWidth; i++) {
                dst[--m] = src[k++];
            }
            k += srcRowBytes - targetWidth;
        }
        break;
    case 270:
        k = ((width - targetHeight) / 2) + (((height - targetWidth) / 2) + 3) * srcRowBytes;
        m = targetWidth;
        switch (targetWidth & 0x3) {
        case 3:
            for (i = 0; i < targetHeight; i++) {
                dst[m-3] = src[k - srcRowBytes];
                dst[m-2] = src[k - srcRowBytes - srcRowBytes];
                dst[m-1] = src[k - srcRowBytes - srcRowBytes - srcRowBytes];
                k++;
                m += dstRowBytes;
            }
            k += 4 * srcRowBytes - targetHeight;
            m -= l + 3;
            break;
        case 2:
            for (i = 0; i < targetHeight; i++) {
                dst[m-2] = src[k - srcRowBytes - srcRowBytes];
                dst[m-1] = src[k - srcRowBytes - srcRowBytes - srcRowBytes];
                k++;
                m += dstRowBytes;
            }
            k += 4 * srcRowBytes - targetHeight;
            m -= l + 2;
            break;
        case 1:
            for (i = 0; i < targetHeight; i++) {
                dst[m-1] = src[k - srcRowBytes - srcRowBytes - srcRowBytes];
                k++;
                m += dstRowBytes;
            }
            k += 4 * srcRowBytes - targetHeight;
            m -= l + 1;
            break;
        default:
            break;
        }
        for (j = 0; j < targetWidth / 4; j++) {
            for (i = 0; i < targetHeight; i++) {
                dst[m-4] = src[k];
                dst[m-3] = src[k - srcRowBytes];
                dst[m-2] = src[k - srcRowBytes - srcRowBytes];
                dst[m-1] = src[k - srcRowBytes - srcRowBytes - srcRowBytes];
                k++;
                m += dstRowBytes;
            }
            k += 4 * srcRowBytes - targetHeight;
            m -= l + 4;
        }
        break;
    default:
        k = ((height - targetHeight) / 2) * srcRowBytes + ((width - targetWidth) / 2);
        m = 0;
        for (j = 0; j < targetHeight; j++) {
            memcpy(&dst[m], &src[k], targetWidth);
            k += srcRowBytes;
            m += dstRowBytes;
        }
        break;
    }
}

void rotateCopySplit(unsigned width, unsigned height, unsigned srcRowWords, const word * src, unsigned targetWidth, unsigned targetHeight, unsigned dstRowBytes, byte * dst0, byte * dst1, unsigned rotation)
{
    unsigned i, j, k, l, m;

    l = dstRowBytes * targetHeight;
    switch(rotation) {
    case 90:
        k = ((width - targetHeight) / 2) + ((height - targetWidth) / 2) * srcRowWords;
        m = l;
        for (j = 0; j < targetWidth / 4; j++) {
            for (i = 0; i < targetHeight; i++) {
                m -= dstRowBytes;
                dst0[m] = ((const byte *)(&src[k]))[0];
                dst0[m+1] = ((const byte *)(&src[k + srcRowWords]))[0];
                dst0[m+2] = ((const byte *)(&src[k + srcRowWords + srcRowWords]))[0];
                dst0[m+3] = ((const byte *)(&src[k + srcRowWords + srcRowWords + srcRowWords]))[0];
                dst1[m] = ((const byte *)(&src[k]))[1];
                dst1[m+1] = ((const byte *)(&src[k + srcRowWords]))[1];
                dst1[m+2] = ((const byte *)(&src[k + srcRowWords + srcRowWords]))[1];
                dst1[m+3] = ((const byte *)(&src[k + srcRowWords + srcRowWords + srcRowWords]))[1];
                k++;
            }
            k += 4 * srcRowWords - targetHeight;
            m += l + 4;
        }
        switch (targetWidth & 0x3) {
        case 3:
            for (i = 0; i < targetHeight; i++) {
                m -= dstRowBytes;
                dst0[m] = ((const byte *)(&src[k]))[0];
                dst0[m+1] = ((const byte *)(&src[k + srcRowWords]))[0];
                dst0[m+2] = ((const byte *)(&src[k + srcRowWords + srcRowWords]))[0];
                dst1[m] = ((const byte *)(&src[k]))[1];
                dst1[m+1] = ((const byte *)(&src[k + srcRowWords]))[1];
                dst1[m+2] = ((const byte *)(&src[k + srcRowWords + srcRowWords]))[1];
                k++;
            }
            break;
        case 2:
            for (i = 0; i < targetHeight; i++) {
                m -= dstRowBytes;
                dst0[m] = ((const byte *)(&src[k]))[0];
                dst0[m+1] = ((const byte *)(&src[k + srcRowWords]))[0];
                dst1[m] = ((const byte *)(&src[k]))[1];
                dst1[m+1] = ((const byte *)(&src[k + srcRowWords]))[1];
                k++;
            }
            break;
        case 1:
            for (i = 0; i < targetHeight; i++) {
                m -= dstRowBytes;
                dst0[m] = ((const byte *)(&src[k]))[0];
                dst1[m] = ((const byte *)(&src[k]))[1];
                k++;
            }
            break;
        default:
            break;
        }
        break;
    case 180:
        k = ((height - targetHeight) / 2) * srcRowWords + ((width - targetWidth) / 2);
        m = targetHeight * dstRowBytes;
        for (j = 0; j < targetHeight; j++) {
            m -= dstRowBytes - targetWidth;
            for (i = 0; i < targetWidth; i++) {
                --m;
                dst0[m] = ((const byte *)(&src[k]))[0];
                dst1[m] = ((const byte *)(&src[k]))[1];
                k++;
            }
            k += srcRowWords - targetWidth;
        }
        break;
    case 270:
        k = ((width - targetHeight) / 2) + (((height - targetWidth) / 2) + 3) * srcRowWords;
        m = targetWidth;
        switch (targetWidth & 0x3) {
        case 3:
            for (i = 0; i < targetHeight; i++) {
                dst0[m-3] = ((const byte *)(&src[k - srcRowWords]))[0];
                dst0[m-2] = ((const byte *)(&src[k - srcRowWords - srcRowWords]))[0];
                dst0[m-1] = ((const byte *)(&src[k - srcRowWords - srcRowWords - srcRowWords]))[0];
                dst1[m-3] = ((const byte *)(&src[k - srcRowWords]))[1];
                dst1[m-2] = ((const byte *)(&src[k - srcRowWords - srcRowWords]))[1];
                dst1[m-1] = ((const byte *)(&src[k - srcRowWords - srcRowWords - srcRowWords]))[1];
                k++;
                m += dstRowBytes;
            }
            k += 4 * srcRowWords - targetHeight;
            m -= l + 3;
            break;
        case 2:
            for (i = 0; i < targetHeight; i++) {
                dst0[m-2] = ((const byte *)(&src[k - srcRowWords - srcRowWords]))[0];
                dst0[m-1] = ((const byte *)(&src[k - srcRowWords - srcRowWords - srcRowWords]))[0];
                dst1[m-2] = ((const byte *)(&src[k - srcRowWords - srcRowWords]))[1];
                dst1[m-1] = ((const byte *)(&src[k - srcRowWords - srcRowWords - srcRowWords]))[1];
                k++;
                m += dstRowBytes;
            }
            k += 4 * srcRowWords - targetHeight;
            m -= l + 2;
            break;
        case 1:
            for (i = 0; i < targetHeight; i++) {
                dst0[m-1] = ((const byte *)(&src[k - srcRowWords - srcRowWords - srcRowWords]))[0];
                dst1[m-1] = ((const byte *)(&src[k - srcRowWords - srcRowWords - srcRowWords]))[1];
                k++;
                m += dstRowBytes;
            }
            k += 4 * srcRowWords - targetHeight;
            m -= l + 1;
            break;
        default:
            break;
        }
        for (j = 0; j < targetWidth / 4; j++) {
            for (i = 0; i < targetHeight; i++) {
                dst0[m-4] = ((const byte *)(&src[k]))[0];
                dst0[m-3] = ((const byte *)(&src[k - srcRowWords]))[0];
                dst0[m-2] = ((const byte *)(&src[k - srcRowWords - srcRowWords]))[0];
                dst0[m-1] = ((const byte *)(&src[k - srcRowWords - srcRowWords - srcRowWords]))[0];
                dst1[m-4] = ((const byte *)(&src[k]))[1];
                dst1[m-3] = ((const byte *)(&src[k - srcRowWords]))[1];
                dst1[m-2] = ((const byte *)(&src[k - srcRowWords - srcRowWords]))[1];
                dst1[m-1] = ((const byte *)(&src[k - srcRowWords - srcRowWords - srcRowWords]))[1];
                k++;
                m += dstRowBytes;
            }
            k += 4 * srcRowWords - targetHeight;
            m -= l + 4;
        }
        break;
    default:
        k = ((height - targetHeight) / 2) * srcRowWords + ((width - targetWidth) / 2);
        m = 0;
        for (j = 0; j < targetHeight; j++) {
            for (i = 0; i < targetWidth; i++) {
                dst0[m] = ((const byte *)(&src[k]))[0];
                dst1[m] = ((const byte *)(&src[k]))[1];
                k++;
                m++;
            }
            k += srcRowWords - targetWidth;
            m += dstRowBytes - targetWidth;
        }
        break;
    }
}

- (void) processVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
    vImage_Buffer vImageSrc, vImageDst;
    vpx_codec_err_t res;
    vpx_image_t vpxImage;
    TranscodingEncodeInfo encodeInfo;
    unsigned width, height, dstWidth, dstHeight;

    CMTime presentationTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);

    dword d = (dword)((((int64_t)(presentationTimestamp.value - self.presentationTimestamp)) * 90000) / presentationTimestamp.timescale);
    dword e = d % 375;
    if (e != 0) {
        if (e <= 10/*(90000 / presentationTimestamp.timescale) + 1*/) {
            d -= e;
        }
        else if (375 - e <= 10/*(90000 / presentationTimestamp.timescale) + 1*/) {
            d += 375 - e;
        }
    }
    if (d < 100 * 90) {
        return;
    }
    self.rtpTimestamp += d;
    self.presentationTimestamp = presentationTimestamp.value;

    int rotation = 0;
    CFNumberRef sampleOrientation = (CFNumberRef)(CMGetAttachment(sampleBuffer, (__bridge CFStringRef) RPVideoSampleOrientationKey, nil));
    if (sampleOrientation) {
        int imageOrientation;
        if (!CFNumberGetValue(sampleOrientation, kCFNumberSInt32Type, &imageOrientation)) {
            imageOrientation = kCGImagePropertyOrientationUp;
        }
        CFRelease(sampleOrientation);

        switch (imageOrientation) {
        case kCGImagePropertyOrientationLeft:
            rotation = 270;
            break;
        case kCGImagePropertyOrientationRight:
            rotation = 90;
            break;
        case kCGImagePropertyOrientationDown:
            rotation = 180;
            break;
        case kCGImagePropertyOrientationUp:
        default:
            break;
        }
    }

    CVImageBufferRef processBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

    width = (unsigned)(CVPixelBufferGetWidth(processBuffer));
    height = (unsigned)(CVPixelBufferGetHeight(processBuffer));
    if ((rotation == 90) || (rotation == 270)) {
        dstWidth = height / 2;
        dstHeight = width / 2;
    }
    else {
        dstWidth = width / 2;
        dstHeight = height / 2;
    }

    if ((dstWidth != self.width) || (dstHeight != self.height)) {
        self.width = dstWidth;
        self.height = dstHeight;
        if (self.vpxEncoderInitialized) {
            self.vpxEncoderInitialized = false;
            if (vpx_codec_destroy(self.vpxEncoder)) {
                NSLog(@"SampleHandler: vpx_codec_destroy transcoding failed");
            }
        }
        if (self.scaleBuffer) {
            free(self.scaleBuffer = NULL);
            self.scaleBuffer = NULL;
        }
        if (self.YBuffer) {
            free(self.YBuffer = NULL);
            self.YBuffer = NULL;
        }
        if (self.UVBuffer) {
            free(self.UVBuffer = NULL);
            self.UVBuffer = NULL;
        }
        res = vpx_codec_enc_config_default(self.vpxEncoderInterface, self.vpxEncoderCfg, 0);
        if (res != VPX_CODEC_OK) {
            NSLog(@"SampleHandler::Initialize Failed to get default codec config res=%d", res);
            return;
        }
        self.vpxEncoderCfg->g_w = (unsigned) dstWidth;
        self.vpxEncoderCfg->g_h = (unsigned) dstHeight;
        self.scaleBuffer = (byte *)(malloc(IMAGE_BUFFER_ALIGN(width / 2) * (height / 2)));
        self.YBuffer = (byte *)(malloc(IMAGE_BUFFER_ALIGN(dstWidth) * dstHeight));
        self.UVBuffer = (byte *)(malloc(IMAGE_BUFFER_ALIGN(dstWidth / 2) * dstHeight));
        self.vpxEncoderCfg->rc_target_bitrate = 1000 * 1024;
        self.vpxEncoderCfg->g_timebase.num = 1;
        self.vpxEncoderCfg->g_timebase.den = 30;
        self.vpxEncoderCfg->g_pass = VPX_RC_ONE_PASS;
        self.vpxEncoderCfg->g_lag_in_frames = 0;
        self.vpxEncoderCfg->rc_dropframe_thresh = 0;
        self.vpxEncoderCfg->rc_resize_allowed = 0;
        self.vpxEncoderCfg->g_profile = 0;
        self.vpxEncoderCfg->kf_mode = VPX_KF_AUTO;
        self.vpxEncoderCfg->rc_end_usage = VPX_CBR;
        self.vpxEncoderCfg->kf_min_dist = self.vpxEncoderCfg->kf_max_dist = self.vpxEncoderCfg->g_timebase.den * 100; // 90 sec aprox
        self.vpxEncoderCfg->rc_undershoot_pct = 100;
        self.vpxEncoderCfg->rc_overshoot_pct = 15;
        self.vpxEncoderCfg->g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
#if 1 // These parameters reduce the size of the keyframes
        self.vpxEncoderCfg->rc_buf_initial_sz = 800;
        self.vpxEncoderCfg->rc_buf_optimal_sz = 1000;
        self.vpxEncoderCfg->rc_buf_sz = 1200;
#endif
        res = vpx_codec_enc_init(self.vpxEncoder, self.vpxEncoderInterface, self.vpxEncoderCfg, 0);
        if (res != VPX_CODEC_OK) {
            NSLog(@"SampleHandler::Initialize Failed to initialize encoder. Res=%d", res);
            return;
        }
        self.vpxEncoderInitialized = true;
        NSLog(@"SampleHandler::Initialized encoder %ux%u", width, height);
    }

    NSDictionary *attachments = (__bridge NSDictionary *)CVBufferGetAttachments(processBuffer, kCVAttachmentMode_ShouldPropagate);
    NSString *YCbCrMatrix = [attachments objectForKey:(NSString *)kCVImageBufferYCbCrMatrixKey];

    CVPixelBufferLockBaseAddress(processBuffer, 0);

    memset(&vpxImage, 0, sizeof(vpx_image_t));
    vpxImage.fmt = VPX_IMG_FMT_YV12;
    vpxImage.cs = [YCbCrMatrix isEqualToString:(NSString *)kCVImageBufferYCbCrMatrix_ITU_R_601_4] ? VPX_CS_BT_601 :
        ([YCbCrMatrix isEqualToString:(NSString *)kCVImageBufferYCbCrMatrix_ITU_R_709_2] ? VPX_CS_BT_709 : VPX_CS_UNKNOWN);
    vpxImage.range = ((CVPixelBufferGetPixelFormatType(processBuffer) == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) ||
        (CVPixelBufferGetPixelFormatType(processBuffer) == kCVPixelFormatType_420YpCbCr8PlanarFullRange)) ? VPX_CR_FULL_RANGE : VPX_CR_STUDIO_RANGE;
    vpxImage.w = dstWidth;
    vpxImage.h = dstHeight;
    vpxImage.bit_depth = 8;
    vpxImage.d_w = dstWidth;
    vpxImage.d_h = dstHeight;
    vpxImage.r_w = dstWidth;
    vpxImage.r_h = dstHeight;
    vpxImage.x_chroma_shift = 0;
    vpxImage.y_chroma_shift = 0;
    switch (CVPixelBufferGetPixelFormatType(processBuffer)) {
    case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
    case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: // NV12
        vImageSrc.width = width;
        vImageSrc.height = height;
        vImageSrc.data = CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0);
        vImageSrc.rowBytes = CVPixelBufferGetBytesPerRowOfPlane(processBuffer, 0);
        vImageDst.width = width / 2;
        vImageDst.height = height / 2;
        vImageDst.data = self.scaleBuffer;
        vImageDst.rowBytes = IMAGE_BUFFER_ALIGN(width / 2);
        vImageScale_Planar8(&vImageSrc, &vImageDst, nil, kvImageNoFlags);
        rotateCopyByte((unsigned)(vImageDst.width), (unsigned)(vImageDst.height), (unsigned)(vImageDst.rowBytes), vImageDst.data, dstWidth, dstHeight, IMAGE_BUFFER_ALIGN(dstWidth), self.YBuffer, rotation);
        vpxImage.planes[VPX_PLANE_Y] = self.YBuffer;
        vpxImage.stride[VPX_PLANE_Y] = IMAGE_BUFFER_ALIGN(dstWidth);
        vImageSrc.width = width / 2;
        vImageSrc.height = height / 2;
        vImageSrc.data = CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1);
        vImageSrc.rowBytes = CVPixelBufferGetBytesPerRowOfPlane(processBuffer, 1);
        vImageDst.width = width / 4;
        vImageDst.height = height / 4;
        vImageDst.data = self.scaleBuffer;
        vImageDst.rowBytes = IMAGE_BUFFER_ALIGN(width / 2);
        vImageScale_CbCr8(&vImageSrc, &vImageDst, nil, kvImageNoFlags);
        rotateCopySplit((unsigned)(vImageDst.width), (unsigned)(vImageDst.height), ((unsigned)(vImageDst.rowBytes)) / sizeof(word), vImageDst.data, dstWidth / 2, dstHeight / 2, IMAGE_BUFFER_ALIGN(dstWidth / 2), self.UVBuffer, self.UVBuffer + (IMAGE_BUFFER_ALIGN(dstWidth / 2) * (dstHeight / 2)), rotation);
        vpxImage.planes[VPX_PLANE_U] = self.UVBuffer;
        vpxImage.stride[VPX_PLANE_U] = IMAGE_BUFFER_ALIGN(dstWidth / 2);
        vpxImage.planes[VPX_PLANE_V] = self.UVBuffer + (IMAGE_BUFFER_ALIGN(dstWidth / 2) * (dstHeight / 2));
        vpxImage.stride[VPX_PLANE_V] = IMAGE_BUFFER_ALIGN(dstWidth / 2);
        break;
    case kCVPixelFormatType_420YpCbCr8Planar: // I420
    case kCVPixelFormatType_420YpCbCr8PlanarFullRange:
        break;
    default:
        break;
    }
    CVPixelBufferUnlockBaseAddress(processBuffer, 0);

    vpxImage.bps = 8;
    if (self.vpxEncoder) {
        vpxImage.user_priv = &encodeInfo;
        encodeInfo.timestamp = self.rtpTimestamp;
        encodeInfo.duration = 100 * 90;

        vpx_enc_frame_flags_t flags = 0;
        byte b[1];
        int bytesRead = IosScreenCaptureSenderRecv(self.screenCaptureSender, b, sizeof(b));
        if (bytesRead > 0) {
            flags |= VPX_EFLAG_FORCE_KF;
        }
        const vpx_codec_err_t res = vpx_codec_encode(self.vpxEncoder, &vpxImage, self.vpxFrameIndex, 1, flags, VPX_DL_REALTIME);
        if (res != VPX_CODEC_OK) {
            NSLog(@"SampleHandler: vpx_codec_encode transcoding failed %d", res);
        }
        (self.vpxFrameIndex)++;

        didTranscodeVPX(self, &vpxImage);
    }
}

- (void) processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType
{
    switch (sampleBufferType) {
    case RPSampleBufferTypeVideo:
        // Handle video sample buffer
        [self processVideoSampleBuffer:sampleBuffer];
        break;

    case RPSampleBufferTypeAudioApp:
        // Handle audio sample buffer for app audio
        break;

    case RPSampleBufferTypeAudioMic:
        // Handle audio sample buffer for mic audio
        break;
        
    default:
        break;
    }
}

@end

void ns_log(const char *format, ...)
{
    va_list args;

    va_start(args, format);
    NSLogv([NSString stringWithCString:format encoding:NSUTF8StringEncoding], args);
    va_end(args);
}

