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

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <VideoToolbox/VideoToolbox.h>

#import "platform/platform.h"
#import "platform/IosNamespace.h"
#import "../ios_async/ios_event.h"
#import "sdk/common/ios/ios_forms/FormsConst.h"
#if TARGET_OS_IPHONE
#import "client/ios/VPX.framework/Headers/vpx/vp8dx.h"
#import "client/ios/VPX.framework/Headers/vpx/vpx_decoder.h"
#import "client/ios/VPX.framework/Headers/vpx/vp8cx.h"
#import "client/ios/VPX.framework/Headers/vpx/vpx_encoder.h"
#else
#import "client/macos/VPX.framework/Headers/vpx/vp8dx.h"
#import "client/macos/VPX.framework/Headers/vpx/vpx_decoder.h"
#import "client/macos/VPX.framework/Headers/vpx/vp8cx.h"
#import "client/macos/VPX.framework/Headers/vpx/vpx_encoder.h"
#endif
#import "TranscodingSession.h"

@interface TranscodingSession ()

#define TRANSCODING_RESULT_BUFFERS  4

@property (nonatomic) IosEvent *ios_event;
@property (nonatomic) dispatch_queue_t videoDispatchQueue;
@property (nonatomic) int64_t encodingTimestamp;
@property (nonatomic) int64_t presentationTimestamp;
@property (nonatomic) dword rtpTimestamp;
@property (nonatomic) size_t width;
@property (nonatomic) size_t height;
@property (nonatomic) VTCompressionSessionRef encodingSession;
@property (nonatomic) NSData *sps;
@property (nonatomic) NSData *pps;
@property (nonatomic) vpx_codec_iface_t *vpxDecoderInterface;
@property (nonatomic) vpx_codec_ctx_t *vpxDecoder;
@property (nonatomic) short sequenceDiff;

@property (nonatomic) NSData *decodeSps;
@property (nonatomic) NSData *decodePps;
@property (nonatomic) NSMutableData *UVBuffer;
@property (nonatomic) CMVideoFormatDescriptionRef formatDesc;
@property (nonatomic) VTDecompressionSessionRef decodingSession;
@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) vpx_active_map_t *vpxEncoderMap;
@property (nonatomic) bool vpxEncoderInitialized;
@property (nonatomic) byte *activeMap;
@property (nonatomic) int lenActiveMap;
@property (nonatomic) byte *lastVideoFrame;
@property (nonatomic) int lenLastVideoFrame;
@property (nonatomic) NSMutableArray *resultBuffers;
@property (nonatomic) unsigned resultUsedPos;
@property (nonatomic) unsigned encoderReq;
@property (nonatomic) unsigned encoderAck;

@end
@implementation TranscodingSession

- (id) initWithID:(int)sessionID ios_event:(IosEvent *)ios_event videoDispatchQueue:(dispatch_queue_t)videoDispatchQueue
{
    self = [super init];
    if (self) {
        _sessionID = sessionID;
        _ios_event = ios_event;
        _videoDispatchQueue = videoDispatchQueue;
        self.encodingTimestamp = 0;
        self.presentationTimestamp = 0;
        self.rtpTimestamp = 0;
        self.width = 0;
        self.height = 0;
        self.encodingSession = nil;
        self.sps = nil;
        self.pps = nil;
        self.vpxDecoderInterface = nil;
        self.vpxDecoder = nil;
        self.sequenceDiff = 0;

        self.decodeSps = nil;
        self.decodePps = nil;
        self.formatDesc = nil;
        self.decodingSession = nil;
        self.vpxEncoderInterface = nil;
        self.vpxEncoder = nil;
        self.vpxEncoderCfg = nil;
        self.vpxFrameIndex = 0;
        self.vpxEncoderMap = nil;
        self.vpxEncoderInitialized = false;
        self.lastVideoFrame = nil;
        self.resultBuffers = nil;
        self.resultUsedPos = 0;
        self.encoderReq = 0;
        self.encoderAck = 0;
    }
    return self;
}

#define NAL_UNIT_NON_IDR       1
#define NAL_UNIT_IDR           5
#define NAL_UNIT_SEI           6
#define NAL_UNIT_SPS           7
#define NAL_UNIT_PPS           8
#define NAL_UNIT_DELIMITER     9

#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))
#define AVCC_HEADER_LENGTH          4

typedef struct {
    dword timestamp;
    dword duration;
    short sequenceDiff;
    byte *frame;
    int length;
    int to;
}
TranscodingDecodeInfo;

typedef struct {
    dword timestamp;
    dword duration;
    short sequenceDiff;
    dword flags;
}
TranscodingEncodeInfo;

- (void) onVideoTranscode:(int)timestamp duration:(int)duration sequenceDiff:(short)sequenceDiff data:(NSData *)data length:(int)length width:(int)width height:(int)height stride:(int)stride flags:(int)flags
{
    self.ios_event.enqueue(FORMS_EVENT_VIDEO_TRANSCODE, self.sessionID, timestamp, duration, (int)sequenceDiff, 0, length, data, width, height, stride, flags);
}

void didTranscode(TranscodingSession *transcodingSession, vpx_image_t *vpxImage, int timestamp, int duration, short sequenceDiff, int flags)
{
    NSMutableData *data;
    int width, height, stride, length, j;

    if ((transcodingSession.resultUsedPos >= transcodingSession.resultBuffers.count) || (CFGetRetainCount((__bridge CVImageBufferRef)[transcodingSession.resultBuffers objectAtIndex:transcodingSession.resultUsedPos]) == 1)) {
        width = vpxImage->d_w;
        height = vpxImage->d_h;
        if ((vpxImage->stride[VPX_PLANE_Y] == 2 * vpxImage->stride[VPX_PLANE_U])
         && (vpxImage->stride[VPX_PLANE_Y] == 2 * vpxImage->stride[VPX_PLANE_V])
         && (vpxImage->planes[(vpxImage->fmt & VPX_IMG_FMT_UV_FLIP) ? VPX_PLANE_V : VPX_PLANE_U] == vpxImage->planes[VPX_PLANE_Y] + vpxImage->stride[VPX_PLANE_Y] * vpxImage->d_h)
         && (vpxImage->planes[(vpxImage->fmt & VPX_IMG_FMT_UV_FLIP) ? VPX_PLANE_U : VPX_PLANE_V] == vpxImage->planes[VPX_PLANE_Y] + vpxImage->stride[VPX_PLANE_Y] * vpxImage->d_h + (vpxImage->stride[VPX_PLANE_Y] / 2) * (vpxImage->d_h / 2))) {
            stride = vpxImage->stride[VPX_PLANE_Y];
            length = stride * height + (stride / 2) * height;
            if (transcodingSession.resultUsedPos >= transcodingSession.resultBuffers.count) {
                data = [NSMutableData dataWithCapacity:length];
                data.length = length;
            }
            else {
                data = [transcodingSession.resultBuffers objectAtIndex:transcodingSession.resultUsedPos];
                if (length > data.length) {
                    data = [NSMutableData dataWithCapacity:length];
                    data.length = length;
                    [transcodingSession.resultBuffers replaceObjectAtIndex:transcodingSession.resultUsedPos withObject:data];
                }
            }
            memcpy(data.mutableBytes, vpxImage->planes[VPX_PLANE_Y], length);
        }
        else {
            stride = width;
            length = stride * height + (stride / 2) * height;
            if (transcodingSession.resultUsedPos >= transcodingSession.resultBuffers.count) {
                data = [NSMutableData dataWithCapacity:length];
                data.length = length;
            }
            else {
                data = [transcodingSession.resultBuffers objectAtIndex:transcodingSession.resultUsedPos];
                if (length > data.length) {
                    data = [NSMutableData dataWithCapacity:length];
                    data.length = length;
                    [transcodingSession.resultBuffers replaceObjectAtIndex:transcodingSession.resultUsedPos withObject:data];
                }
            }
            const byte *src = vpxImage->planes[VPX_PLANE_Y];
            byte *dst = data.mutableBytes;
            for (j = 0; j < height; j++) {
                memcpy(dst, src, width);
                src += vpxImage->stride[VPX_PLANE_Y];
                dst += width;
            }
            int u = (vpxImage->fmt & VPX_IMG_FMT_UV_FLIP) ? VPX_PLANE_V : VPX_PLANE_U;
            src = vpxImage->planes[u];
            for (j = 0; j < height / 2; j++) {
                memcpy(dst, src, width / 2);
                src += vpxImage->stride[u];
                dst += width / 2;
            }
            src = vpxImage->planes[VPX_PLANE_U + VPX_PLANE_V - u];
            for (j = 0; j < height / 2; j++) {
                memcpy(dst, src, width / 2);
                src += vpxImage->stride[VPX_PLANE_U + VPX_PLANE_V - u];
                dst += width / 2;
            }
        }
        transcodingSession.resultUsedPos = (transcodingSession.resultUsedPos < TRANSCODING_RESULT_BUFFERS-1) ? transcodingSession.resultUsedPos + 1 : 0;
        dispatch_async(dispatch_get_main_queue(), ^
            { [transcodingSession onVideoTranscode:timestamp duration:duration sequenceDiff:sequenceDiff data:data length:length width:width height:height stride:stride flags:flags]; });
    }
}

void didTranscodeH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer)
{
    OSStatus statusCode;

    TranscodingSession *transcodingSession = (__bridge TranscodingSession *)outputCallbackRefCon;
    if (status != noErr) {
        NSLog(@"didTranscodeH264 status %d", (int)status);
//        CFRelease(sourceFrameRefCon);
        return;
    }
    if (infoFlags & kVTEncodeInfo_FrameDropped) {
        NSLog(@"didTranscodeH264 infoFlags %x frame dropped", (int)infoFlags);
    }
    if (!CMSampleBufferDataIsReady(sampleBuffer)) {
        NSLog(@"didTranscodeH264 data is not ready");
        CFRelease(sourceFrameRefCon);
        return;
    }

    // Check if we have got a key frame first
    dword flags = 0;

    if (!CFDictionaryContainsKey((CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync)) {
        flags |= FORMS_VIDEO_TRANSCODER_KEY_FRAME;
        CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
        // CFDictionaryRef extensionDict = CMFormatDescriptionGetExtensions(format);
        // Get the extensions
        // From the extensions get the dictionary with key "SampleDescriptionExtensionAtoms"
        // From the dict, get the value for the key "avcC"

        size_t sparameterSetSize, sparameterSetCount;
        const uint8_t *sparameterSet;
        statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sparameterSet, &sparameterSetSize, &sparameterSetCount, 0);
        if (statusCode == noErr) {
            // Found sps and now check for pps
            size_t pparameterSetSize, pparameterSetCount;
            const uint8_t *pparameterSet;
            OSStatus statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0);
            if (statusCode == noErr) {
                // Found pps
                transcodingSession.sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
                transcodingSession.pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];
            }
        }
    }

    CMTime presentationTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    transcodingSession.rtpTimestamp += (((int64_t)(presentationTimestamp.value - transcodingSession.presentationTimestamp)) * 90000) / presentationTimestamp.timescale;
    transcodingSession.presentationTimestamp = presentationTimestamp.value;
    CMTime duration = CMSampleBufferGetDuration(sampleBuffer);
    short sequenceDiff = (short)(duration.timescale - 90000);
    dword rtpDuration = (dword)(duration.value);

    CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    size_t length, totalLength;
    NSMutableData *h264Data = nil;
    size_t h264Length = 0;
    byte *dataPointer;
    statusCode = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength, (char **)(&dataPointer));
    if ((statusCode == noErr) && ((transcodingSession.resultUsedPos >= transcodingSession.resultBuffers.count) || (CFGetRetainCount((__bridge CVImageBufferRef)[transcodingSession.resultBuffers objectAtIndex:transcodingSession.resultUsedPos]) == 1))) {
        h264Length = totalLength;
        if (transcodingSession.sps != nil) {
            h264Length += transcodingSession.sps.length + 4;
        }
        if (transcodingSession.pps != nil) {
            h264Length += transcodingSession.pps.length + 4;
        }
        if (transcodingSession.resultUsedPos >= transcodingSession.resultBuffers.count) {
            h264Data = [NSMutableData dataWithCapacity:h264Length];
            h264Data.length = h264Length;
        }
        else {
            h264Data = [transcodingSession.resultBuffers objectAtIndex:transcodingSession.resultUsedPos];
            if (h264Length > h264Data.length) {
                h264Data = [NSMutableData dataWithCapacity:h264Length];
                h264Data.length = h264Length;
                [transcodingSession.resultBuffers replaceObjectAtIndex:transcodingSession.resultUsedPos withObject:h264Data];
            }
        }
        transcodingSession.resultUsedPos = (transcodingSession.resultUsedPos < TRANSCODING_RESULT_BUFFERS-1) ? transcodingSession.resultUsedPos + 1 : 0;
        byte *h264DataPointer = h264Data.mutableBytes;

        h264Length = 0;
        if (transcodingSession.sps) {
            h264DataPointer[h264Length] = 0x00;
            h264DataPointer[h264Length + 1] = 0x00;
            h264DataPointer[h264Length + 2] = 0x00;
            h264DataPointer[h264Length + 3] = 0x01;
            memcpy(&(h264DataPointer[h264Length + AVCC_HEADER_LENGTH]), transcodingSession.sps.bytes, transcodingSession.sps.length);
            h264Length += AVCC_HEADER_LENGTH + transcodingSession.sps.length;
            transcodingSession.sps = nil;
        }
        if (transcodingSession.pps) {
            h264DataPointer[h264Length] = 0x00;
            h264DataPointer[h264Length + 1] = 0x00;
            h264DataPointer[h264Length + 2] = 0x00;
            h264DataPointer[h264Length + 3] = 0x01;
            memcpy(&(h264DataPointer[h264Length + AVCC_HEADER_LENGTH]), transcodingSession.pps.bytes, transcodingSession.pps.length);
            h264Length += AVCC_HEADER_LENGTH + transcodingSession.pps.length;
            transcodingSession.pps = nil;
        }

        size_t bufferOffset = 0;
        while (bufferOffset < totalLength - AVCC_HEADER_LENGTH) {
            // Read the NAL unit length
            uint32_t NALUnitLength = (dataPointer[bufferOffset] << 24) | (dataPointer[bufferOffset + 1] << 16) | (dataPointer[bufferOffset + 2] << 8) | dataPointer[bufferOffset + 3];

            h264DataPointer[h264Length] = 0x00;
            h264DataPointer[h264Length + 1] = 0x00;
            h264DataPointer[h264Length + 2] = 0x00;
            h264DataPointer[h264Length + 3] = 0x01;
            memcpy(&(h264DataPointer[h264Length + AVCC_HEADER_LENGTH]), &(dataPointer[bufferOffset + AVCC_HEADER_LENGTH]), NALUnitLength);
            h264Length += AVCC_HEADER_LENGTH + NALUnitLength;

            // Move to the next NAL unit in the block buffer
            bufferOffset += AVCC_HEADER_LENGTH + NALUnitLength;
        }
    }
    if (h264Data) {
        dword timestamp = transcodingSession.rtpTimestamp;
        CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)sourceFrameRefCon;
        int width = CVPixelBufferGetWidth(pixelBuffer);
        int height = CVPixelBufferGetWidth(pixelBuffer);
        dispatch_async(dispatch_get_main_queue(), ^
            { [transcodingSession onVideoTranscode:timestamp duration:rtpDuration sequenceDiff:sequenceDiff data:h264Data length:h264Length width:width height:height stride:width flags:flags]; });
    }
    CFRelease(sourceFrameRefCon);
}

void transcodingBufferReleaseCallback(void *releaseRefCon, const void *dataPtr, size_t dataSize, size_t numberOfPlanes, const void * _Nullable planeAddresses[])
{
    free(releaseRefCon);
}

- (void) h264Encode:(CVPixelBufferRef)pixelBuffer timestampEx:(int64_t)timestampEx durationEx:(int64_t)durationEx sequenceDiff:(short)sequenceDiff flags:(int)flags
{
    OSStatus statusCode;
    VTEncodeInfoFlags infoFlagsOut;
    // Get the CV Image buffer
    NSDictionary *frameProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:((flags & FORMS_VIDEO_TRANSCODER_KEY_FRAME) != 0)], kVTEncodeFrameOptionKey_ForceKeyFrame, nil];

    // Pass it to the encoder
    statusCode = VTCompressionSessionEncodeFrame(self.encodingSession,
                                                 pixelBuffer,
                                                 CMTimeMake(timestampEx, 90000),
                                                 CMTimeMake(durationEx, 90000 + sequenceDiff),
                                                 (CFDictionaryRef)frameProperties, pixelBuffer, &infoFlagsOut);
    // Check for error
    if (statusCode != noErr) {
        NSLog(@"VTCompressionSessionEncodeFrame transcoding failed with %d", (int)statusCode);
        CFRelease(pixelBuffer);
/*
        // Mark the completion
        VTCompressionSessionCompleteFrames(transcodingSession.encodingSession, kCMTimeInvalid);
        // End the session
        VTCompressionSessionInvalidate(transcodingSession.encodingSession);
        CFRelease(transcodingSession.encodingSession);
        transcodingSession.encodingSession = NULL;
*/
        return;
    }
    if (infoFlagsOut & kVTEncodeInfo_FrameDropped) {
        NSLog(@"h264Encode flagOut %x frame dropped", (int)infoFlagsOut);
    }
}

void doEncodeH264(TranscodingSession *transcodingSession, vpx_image_t *vpxImage, int timestamp, int duration, short sequenceDiff, int flags)
{
    size_t i, j;

    transcodingSession.encodingTimestamp += (int)(timestamp - transcodingSession.encodingTimestamp);
    size_t width = vpxImage->d_w;
    size_t height = vpxImage->d_h;
    OSType pixelFormatType = (vpxImage->range == VPX_CR_FULL_RANGE) ? kCVPixelFormatType_420YpCbCr8BiPlanarFullRange : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
    if ((width != transcodingSession.width) || (height != transcodingSession.height)) {
        transcodingSession.width = width;
        transcodingSession.height = height;
        if (transcodingSession.encodingSession != nil) {
            // Mark the completion
            VTCompressionSessionCompleteFrames(transcodingSession.encodingSession, kCMTimeInvalid);

            // End the session
            VTCompressionSessionInvalidate(transcodingSession.encodingSession);
            CFRelease(transcodingSession.encodingSession);
            transcodingSession.encodingSession = nil;
        }
        // Create the compression session
        VTCompressionSessionRef encodingSession;
        OSStatus status = VTCompressionSessionCreate(NULL, transcodingSession.width, transcodingSession.height,
            kCMVideoCodecType_H264, NULL, NULL, NULL, didTranscodeH264, (__bridge void *)transcodingSession, &encodingSession);
        transcodingSession.encodingSession = encodingSession;
        NSLog(@"H264: VTCompressionSessionCreate transcoding %d %d %d", (int)(transcodingSession.width), (int)(transcodingSession.height), (int)status);
        if (status != noErr) {
            NSLog(@"H264: Unable to create a H264 transcoding session");
        }
        else {
            // Set the properties
            VTSessionSetProperty(transcodingSession.encodingSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
            VTSessionSetProperty(transcodingSession.encodingSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
            VTSessionSetProperty(transcodingSession.encodingSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, (__bridge CFTypeRef)@(10));
            VTSessionSetProperty(transcodingSession.encodingSession, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(600 * 1024));
//            VTSessionSetProperty(transcodingSession.encodingSession, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)@[800 * 1024 / 8, 1]);
            VTSessionSetProperty(transcodingSession.encodingSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_High_AutoLevel);
            VTSessionSetProperty(transcodingSession.encodingSession, kVTCompressionPropertyKey_ExpectedFrameRate, (__bridge CFTypeRef)@(10));
            // Tell the encoder to start encoding
            VTCompressionSessionPrepareToEncodeFrames(transcodingSession.encodingSession);
        }
    }
    size_t numberOfPlanes;
    size_t dataSize;
    void *allocPtr;
    void *dataPtr;

    void *(planeBaseAddress[IMAGE_BUFFER_MAX_PLANES]);
    size_t planeWidth[IMAGE_BUFFER_MAX_PLANES];
    size_t planeHeight[IMAGE_BUFFER_MAX_PLANES];
    size_t planeBytesPerRow[IMAGE_BUFFER_MAX_PLANES];

    switch(pixelFormatType) {
    case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
    case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
        numberOfPlanes = 2;
        dataSize = IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrBiPlanar)) + IMAGE_BUFFER_ALIGN(width) * height + IMAGE_BUFFER_ALIGN(width) * (height / 2);
        allocPtr = malloc(dataSize + IMAGE_BUFFER_ALIGN(1));
        dataPtr = IMAGE_BUFFER_ALIGN_PTR((byte *)allocPtr);
        ((CVPlanarPixelBufferInfo_YCbCrBiPlanar *)dataPtr)->componentInfoY.offset = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrBiPlanar)));
        ((CVPlanarPixelBufferInfo_YCbCrBiPlanar *)dataPtr)->componentInfoY.rowBytes = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(width));
        ((CVPlanarPixelBufferInfo_YCbCrBiPlanar *)dataPtr)->componentInfoCbCr.offset = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrBiPlanar)) + IMAGE_BUFFER_ALIGN(width) * height);
        ((CVPlanarPixelBufferInfo_YCbCrBiPlanar *)dataPtr)->componentInfoCbCr.rowBytes = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(width));

        planeBaseAddress[0] = &(((byte *)dataPtr)[IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrBiPlanar))]);
        planeWidth[0] = width;
        planeHeight[0] = height;
        planeBytesPerRow[0] = IMAGE_BUFFER_ALIGN(width);

        planeBaseAddress[1] = &(((byte *)dataPtr)[IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrBiPlanar)) + IMAGE_BUFFER_ALIGN(width) * height]);
        planeWidth[1] = width / 2;
        planeHeight[1] = height / 2;
        planeBytesPerRow[1] = IMAGE_BUFFER_ALIGN(width);

        {
            const byte *src = vpxImage->planes[VPX_PLANE_Y];
            byte *dst = planeBaseAddress[0];
            for (j = 0; j < height; j++) {
                memcpy(dst, src, width);
                src += vpxImage->stride[VPX_PLANE_Y];
                dst += IMAGE_BUFFER_ALIGN(width);
            }
        }
        switch(vpxImage->fmt) {
        case VPX_IMG_FMT_I420:
        case VPX_IMG_FMT_YV12:
        default: {
            int u = (vpxImage->fmt & VPX_IMG_FMT_UV_FLIP) ? VPX_PLANE_V : VPX_PLANE_U;
            const byte *src0 = vpxImage->planes[u];
            const byte *src1 = vpxImage->planes[VPX_PLANE_U + VPX_PLANE_V - u];
            word *dst = planeBaseAddress[1];
            for (j = 0; j < height / 2; j++) {
                for (i = 0; i < width / 2; i++) {
                    ((byte *)(&dst[i]))[0] = src0[i];
                    ((byte *)(&dst[i]))[1] = src1[i];
                }
                src0 += vpxImage->stride[u];
                src1 += vpxImage->stride[VPX_PLANE_U + VPX_PLANE_V - u];
                dst += IMAGE_BUFFER_ALIGN(width) / sizeof(word);
            }
            break;
            }
        }
        break;

    case kCVPixelFormatType_420YpCbCr8Planar: // I420
    case kCVPixelFormatType_420YpCbCr8PlanarFullRange:
        numberOfPlanes = 3;
        dataSize = IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar)) + IMAGE_BUFFER_ALIGN(width) * height + IMAGE_BUFFER_ALIGN(width / 2) * height;
        allocPtr = malloc(dataSize + IMAGE_BUFFER_ALIGN(1));
        dataPtr = IMAGE_BUFFER_ALIGN_PTR((byte *)allocPtr);
        ((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoY.offset = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar)));
        ((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoY.rowBytes = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(width));
        ((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoCb.offset = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar)) + IMAGE_BUFFER_ALIGN(width) * height);
        ((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoCb.rowBytes = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(width / 2));
        ((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoCr.offset = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar)) + IMAGE_BUFFER_ALIGN(width) * height + IMAGE_BUFFER_ALIGN(width / 2) * (height / 2));
        ((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoCr.rowBytes = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(width / 2));

        planeBaseAddress[0] = &(((byte *)dataPtr)[IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar))]);
        planeWidth[0] = width;
        planeHeight[0] = height;
        planeBytesPerRow[0] = IMAGE_BUFFER_ALIGN(width);

        planeBaseAddress[1] = &(((byte *)dataPtr)[IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar)) + IMAGE_BUFFER_ALIGN(width) * height]);
        planeWidth[1] = width / 2;
        planeHeight[1] = height / 2;
        planeBytesPerRow[1] = IMAGE_BUFFER_ALIGN(width / 2);

        planeBaseAddress[2] = &(((byte *)dataPtr)[IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar)) + IMAGE_BUFFER_ALIGN(width) * height + IMAGE_BUFFER_ALIGN(width / 2) * (height / 2)]);
        planeWidth[2] = width / 2;
        planeHeight[2] = height / 2;
        planeBytesPerRow[2] = IMAGE_BUFFER_ALIGN(width / 2);

        {
            const byte *src = vpxImage->planes[VPX_PLANE_Y];
            byte *dst = planeBaseAddress[0];
            for (j = 0; j < height; j++) {
                memcpy(dst, src, width);
                src += vpxImage->stride[VPX_PLANE_Y];
                dst += IMAGE_BUFFER_ALIGN(width);
            }
        }
        switch(vpxImage->fmt) {
        case VPX_IMG_FMT_I420:
        case VPX_IMG_FMT_YV12:
        default: {
            int u = (vpxImage->fmt & VPX_IMG_FMT_UV_FLIP) ? VPX_PLANE_V : VPX_PLANE_U;
            const byte *src = vpxImage->planes[u];
            byte *dst = planeBaseAddress[1];
            for (j = 0; j < height / 2; j++) {
                memcpy(dst, src, width / 2);
                src += vpxImage->stride[u];
                dst += IMAGE_BUFFER_ALIGN(width / 2);
            }
            src = vpxImage->planes[VPX_PLANE_U + VPX_PLANE_V - u];
            dst = planeBaseAddress[2];
            for (j = 0; j < height / 2; j++) {
                memcpy(dst, src, width / 2);
                src += vpxImage->stride[VPX_PLANE_U + VPX_PLANE_V - u];
                dst += IMAGE_BUFFER_ALIGN(width / 2);
            }
            break;
            }
        }
        break;

    default:
        numberOfPlanes = 1;
        dataSize = IMAGE_BUFFER_ALIGN(numberOfPlanes * sizeof(CVPlanarPixelBufferInfo)) + IMAGE_BUFFER_ALIGN(width) * height;
        allocPtr = malloc(dataSize + IMAGE_BUFFER_ALIGN(1));
        dataPtr = IMAGE_BUFFER_ALIGN_PTR((byte *)allocPtr);

        for (i = 0; i < numberOfPlanes; i++) {
            ((CVPlanarPixelBufferInfo *)dataPtr)->componentInfo[0].offset = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(numberOfPlanes * sizeof(CVPlanarPixelBufferInfo)) + i * IMAGE_BUFFER_ALIGN(width) * height);
            ((CVPlanarPixelBufferInfo *)dataPtr)->componentInfo[0].rowBytes = CFSwapInt32BigToHost(IMAGE_BUFFER_ALIGN(width));

            planeBaseAddress[i] = &(((byte *)dataPtr)[IMAGE_BUFFER_ALIGN(numberOfPlanes * sizeof(CVPlanarPixelBufferInfo)) + i * IMAGE_BUFFER_ALIGN(width) * height]);
            planeWidth[i] = width;
            planeHeight[i] = height;
            planeBytesPerRow[i] = IMAGE_BUFFER_ALIGN(width);
        }
        {
            const byte *src = vpxImage->planes[VPX_PLANE_Y];
            byte *dst = planeBaseAddress[0];
            for (j = 0; j < height; j++) {
                memcpy(dst, src, width);
                src += vpxImage->stride[VPX_PLANE_Y];
                dst += IMAGE_BUFFER_ALIGN(width);
            }
        }
        switch(vpxImage->fmt) {
        case VPX_IMG_FMT_I420:
        case VPX_IMG_FMT_YV12:
        default: {
            int u = (vpxImage->fmt & VPX_IMG_FMT_UV_FLIP) ? VPX_PLANE_V : VPX_PLANE_U;
            const byte *src = vpxImage->planes[u];
            byte *dst = planeBaseAddress[1];
            for (j = 0; j < height / 2; j++) {
                memcpy(dst, src, width / 2);
                src += vpxImage->stride[u];
                dst += IMAGE_BUFFER_ALIGN(width / 2);
            }
            src = vpxImage->planes[VPX_PLANE_U + VPX_PLANE_V - u];
            dst = planeBaseAddress[2];
            for (j = 0; j < height / 2; j++) {
                memcpy(dst, src, width / 2);
                src += vpxImage->stride[VPX_PLANE_U + VPX_PLANE_V - u];
                dst += IMAGE_BUFFER_ALIGN(width / 2);
            }
            break;
            }
        }
        break;
    }

    CVPixelBufferRef pixelBuffer;
    CVReturn result = CVPixelBufferCreateWithPlanarBytes(nil, width, height, pixelFormatType, dataPtr, dataSize,
        numberOfPlanes, planeBaseAddress, planeWidth, planeHeight, planeBytesPerRow, &transcodingBufferReleaseCallback, allocPtr, nil, &pixelBuffer);
    if (result != kCVReturnSuccess) {
        NSLog(@"CVPixelBufferCreateWithPlanarBytes transcoding %d", result);
    }

    // Apply required colorimetric attachments. 
    CVBufferSetAttachment(pixelBuffer, kCVImageBufferColorPrimariesKey, kCVImageBufferColorPrimaries_ITU_R_709_2, kCVAttachmentMode_ShouldPropagate); 
    CVBufferSetAttachment(pixelBuffer, kCVImageBufferTransferFunctionKey, kCVImageBufferTransferFunction_ITU_R_709_2, kCVAttachmentMode_ShouldPropagate); 
    CVBufferSetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey,
        (vpxImage->cs == VPX_CS_BT_601) ? kCVImageBufferYCbCrMatrix_ITU_R_601_4 : kCVImageBufferYCbCrMatrix_ITU_R_709_2, kCVAttachmentMode_ShouldPropagate); 

    int64_t timestampEx = transcodingSession.encodingTimestamp;
    int64_t durationEx = duration;
    dispatch_async(transcodingSession.videoDispatchQueue, ^
        { [transcodingSession h264Encode:pixelBuffer timestampEx:timestampEx durationEx:durationEx sequenceDiff:sequenceDiff flags:flags]; });
}

- (void) encodeH264:(int)timestamp duration:(int)duration sequenceDiff:(short)sequenceDiff frame:(byte *)frame length:(int)length width:(int)width height:(int)height stride:(int)stride flags:(int)flags
{
    vpx_image_t vpxImage;

    memset(&vpxImage, 0, sizeof(vpx_image_t));
    vpxImage.fmt = VPX_IMG_FMT_YV12;
    vpxImage.cs = VPX_CS_BT_601/*[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 = VPX_CR_FULL_RANGE/*((CVPixelBufferGetPixelFormatType(processBuffer) == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) ||
        (CVPixelBufferGetPixelFormatType(processBuffer) == kCVPixelFormatType_420YpCbCr8PlanarFullRange)) ? VPX_CR_FULL_RANGE : VPX_CR_STUDIO_RANGE*/;
    vpxImage.w = width;
    vpxImage.h = height;
    vpxImage.bit_depth = 8;
    vpxImage.d_w = width;
    vpxImage.d_h = height;
    vpxImage.r_w = width;
    vpxImage.r_h = height;
    vpxImage.x_chroma_shift = 0;
    vpxImage.y_chroma_shift = 0;
    vpxImage.planes[VPX_PLANE_Y] = frame;
    vpxImage.stride[VPX_PLANE_Y] = stride;
    vpxImage.planes[VPX_PLANE_U] = frame + stride * height + (stride / 2) * (height / 2);
    vpxImage.stride[VPX_PLANE_U] = stride / 2;
    vpxImage.planes[VPX_PLANE_V] = frame + stride * height;
    vpxImage.stride[VPX_PLANE_V] = stride / 2;

    vpxImage.bps = 8;

    doEncodeH264(self, &vpxImage, timestamp, duration, sequenceDiff, flags);
    free(frame);
}

void didDecodeVPX(TranscodingSession *transcodingSession, vpx_image_t *vpxImage)
{
    TranscodingDecodeInfo *decodeInfo = (TranscodingDecodeInfo *)(vpxImage->user_priv);
    dword flags = (decodeInfo->frame[0] & 0x01) ? FORMS_VIDEO_TRANSCODER_KEY_FRAME : 0;
    int timestamp = decodeInfo->timestamp;
    int duration = decodeInfo->duration;
    short sequenceDiff = decodeInfo->sequenceDiff;

    if (decodeInfo->to == FORMS_WEBCAM_CODER_IYUV) {
        didTranscode(transcodingSession, vpxImage, timestamp, duration, sequenceDiff, flags);
    }
    else {
        doEncodeH264(transcodingSession, vpxImage, timestamp, duration, sequenceDiff, flags);
    }
}

- (void) vpxDecode:(int)timestamp duration:(int)duration sequenceDiff:(short)sequenceDiff frame:(byte *)frame length:(int)length to:(int)to
{
    TranscodingDecodeInfo decodeInfo;
    decodeInfo.timestamp = timestamp;
    decodeInfo.duration = duration;
    decodeInfo.frame = frame;
    decodeInfo.length = length;
    decodeInfo.to = to;

    vpx_codec_iter_t iter = NULL;
    vpx_image_t *img;

    if (length != 0) {
        if (sequenceDiff != 0) {
            self.sequenceDiff += sequenceDiff;
            if (self.sequenceDiff == 0) {
                self.sequenceDiff = 1;
            }
        }
        decodeInfo.sequenceDiff = self.sequenceDiff;
        if (!(frame[0] & 0x01)) {
            self.sequenceDiff = 0;
        }
    }
    if ((length != 0) && (self.sequenceDiff == 0)) {
        if (vpx_codec_decode(self.vpxDecoder, frame, length, &decodeInfo, 0)) {
            NSLog(@"VPX: vpx_codec_decode transcoding failed");
        }
        while ((img = vpx_codec_get_frame(self.vpxDecoder, &iter)) != NULL) {
            didDecodeVPX(self, img);
        }
    }
    else {
        (self.sequenceDiff)++;
        if (self.sequenceDiff == 0) {
            self.sequenceDiff = 1;
        }
    }
    free(frame);
}

void didTranscodeVPX(TranscodingSession *transcodingSession, 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;
    short sequenceDiff = encodeInfo->sequenceDiff;
    NSMutableData *vpxData = nil;

    while ((pkt = vpx_codec_get_cx_data(transcodingSession.vpxEncoder, &iter)) != NULL) {
        if ((pkt->kind == VPX_CODEC_CX_FRAME_PKT) && ((transcodingSession.resultUsedPos >= transcodingSession.resultBuffers.count) || (CFGetRetainCount((__bridge CVImageBufferRef)[transcodingSession.resultBuffers objectAtIndex:transcodingSession.resultUsedPos]) == 1))) {
            dword flags = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) ? FORMS_VIDEO_TRANSCODER_KEY_FRAME : 0;
            int vpxLength = pkt->data.frame.sz;
            if (transcodingSession.resultUsedPos >= transcodingSession.resultBuffers.count) {
                vpxData = [NSMutableData dataWithCapacity:vpxLength];
                vpxData.length = vpxLength;
            }
            else {
                vpxData = [transcodingSession.resultBuffers objectAtIndex:transcodingSession.resultUsedPos];
                if (vpxLength > vpxData.length) {
                    vpxData = [NSMutableData dataWithCapacity:vpxLength];
                    vpxData.length = vpxLength;
                    [transcodingSession.resultBuffers replaceObjectAtIndex:transcodingSession.resultUsedPos withObject:vpxData];
                }
            }
            transcodingSession.resultUsedPos = (transcodingSession.resultUsedPos < TRANSCODING_RESULT_BUFFERS-1) ? transcodingSession.resultUsedPos + 1 : 0;
            byte *vpxDataPointer = vpxData.mutableBytes;
            memcpy(vpxDataPointer, pkt->data.frame.buf, vpxLength);

            dispatch_async(dispatch_get_main_queue(), ^
                { [transcodingSession onVideoTranscode:timestamp duration:rtpDuration sequenceDiff:sequenceDiff data:vpxData length:vpxLength width:width height:height stride:width flags:flags]; });
            vpxData = nil;
        }
    }
}

void CompareVideoFrames(const byte *buf, const byte *lastVideoFrame, unsigned width, unsigned height, byte *activeMap)
{
    unsigned int* pTmp0, * pTmp1;
    unsigned int i, j, k, l;
    unsigned int mapRows = (height + 0xf) >> 4;
    unsigned int mapCols = (width + 0xf) >> 4;
    unsigned int numRows = height >> 4;
    unsigned int numCols = width >> 4;
    unsigned int lasth = height & 0xf;
    unsigned int lastw = width & 0xf;

    memset(activeMap, 0, mapCols * mapRows);

    // Y
    for(k = 0; k < numRows; k++) {
        for(l = 0; l < numCols; l++) {
            pTmp0 = (unsigned int*)((unsigned char*)buf + (k * 16 * width) + l * 16);
            pTmp1 = (unsigned int*)(lastVideoFrame      + (k * 16 * width) + l * 16);
            uint8_t* ptr = activeMap + k * mapCols + l;
            for (i = 0; i < 16 && !(*ptr); i++) {
                unsigned int* pTmp00 = (unsigned int*)((unsigned char*)pTmp0 + (i * width));
                unsigned int* pTmp11 = (unsigned int*)((unsigned char*)pTmp1 + (i * width));
                for (j = 0; j < 16 >> 2; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
        if(lastw) {
            pTmp0 = (unsigned int*)((unsigned char*)buf + (k * 16 * width) + numCols * 16);
            pTmp1 = (unsigned int*)(lastVideoFrame      + (k * 16 * width) + numCols * 16);
            uint8_t* ptr = activeMap + k * mapCols + numCols;
            for (i = 0; i < 16 && !(*ptr); i++) {
                unsigned char* pTmp00 = (unsigned char*)pTmp0 + (i * width);
                unsigned char* pTmp11 = (unsigned char*)pTmp1 + (i * width);
                for (j = 0; j < lastw; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
    }
    if(lasth) {
        for (l = 0; l < numCols; l++) {
            pTmp0 = (unsigned int*)((unsigned char*)buf + (numRows * 16 * width) + l * 16);
            pTmp1 = (unsigned int*)(lastVideoFrame      + (numRows * 16 * width) + l * 16);
            uint8_t* ptr = activeMap + numRows * mapCols + l;
            for (i = 0; i < lasth && !(*ptr); i++) {
                unsigned int* pTmp00 = (unsigned int *)((unsigned char*)pTmp0 + (i * width));
                unsigned int* pTmp11 = (unsigned int *)((unsigned char*)pTmp1 + (i * width));
                for (j = 0; j < 16 >> 2; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
        if (lastw) {
            pTmp0 = (unsigned int*)((unsigned char*)buf + (numRows * 16 * width) + numCols * 16);
            pTmp1 = (unsigned int*)(lastVideoFrame      + (numRows * 16 * width) + numCols * 16);
            uint8_t* ptr = activeMap + numRows * mapCols + numCols;
            for (i = 0; i < lasth && !(*ptr); i++) {
                unsigned char* pTmp00 = (unsigned char*)pTmp0 + (i * width);
                unsigned char* pTmp11 = (unsigned char*)pTmp1 + (i * width);
                for (j = 0; j < lastw; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
    }

    // U
    unsigned char* pCurrent = (unsigned char*)buf         + width * height;
    unsigned char* pLast = (unsigned char*)lastVideoFrame + width * height;
    int uvWidth = width >> 1;
    for (k = 0; k < numRows; k++) {
        for (l = 0; l < numCols; l++) {
            pTmp0 = (unsigned int*)(pCurrent + (k * 8 * uvWidth) + l * 8);
            pTmp1 = (unsigned int*)(pLast    + (k * 8 * uvWidth) + l * 8);
            uint8_t* ptr = activeMap + k * mapCols + l;
            for (i = 0; i < 8 && !(*ptr); i++) {
                unsigned int* pTmp00 = (unsigned int*)((unsigned char*)pTmp0 + (i * uvWidth));
                unsigned int* pTmp11 = (unsigned int*)((unsigned char*)pTmp1 + (i * uvWidth));
                for (j = 0; j < 8 >> 2; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
        if(lastw) {
            pTmp0 = (unsigned int*)(pCurrent + (k * 8 * uvWidth) + numCols * 8);
            pTmp1 = (unsigned int*)(pLast    + (k * 8 * uvWidth) + numCols * 8);
            uint8_t* ptr = activeMap + k * mapCols + numCols;
            for (i = 0; i < 8 && !(*ptr); i++) {
                unsigned char* pTmp00 = (unsigned char*)((unsigned char*)pTmp0 + (i * uvWidth));
                unsigned char* pTmp11 = (unsigned char*)((unsigned char*)pTmp1 + (i * uvWidth));
                for (j = 0; j < lastw>>1; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
    }
    if(lasth) {
        for (l = 0; l < numCols; l++) {
            pTmp0 = (unsigned int*)(pCurrent + (numRows * 8 * uvWidth) + l * 8);
            pTmp1 = (unsigned int*)(pLast    + (numRows * 8 * uvWidth) + l * 8);
            uint8_t* ptr = activeMap + numRows * mapCols + l;
            for (i = 0; i < lasth>>1 && !(*ptr); i++) {
                unsigned int* pTmp00 = (unsigned int*)((unsigned char*)pTmp0 + (i * uvWidth));
                unsigned int* pTmp11 = (unsigned int*)((unsigned char*)pTmp1 + (i * uvWidth));
                for (j = 0; j < 8 >> 2; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
        if (lastw) {
            pTmp0 = (unsigned int*)(pCurrent + (numRows * 8 * uvWidth) + numCols * 8);
            pTmp1 = (unsigned int*)(pLast    + (numRows * 8 * uvWidth) + numCols * 8);
            uint8_t* ptr = activeMap + numRows * mapCols + numCols;
            for (i = 0; i < lasth>>1 && !(*ptr); i++) {
                unsigned char* pTmp00 = (unsigned char*)pTmp0 + (i * uvWidth);
                unsigned char* pTmp11 = (unsigned char*)pTmp1 + (i * uvWidth);
                for (j = 0; j < lastw>>1; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
    }

    // V
    pCurrent = (unsigned char*)buf            + width * height + uvWidth * (height>>1);
    pLast    = (unsigned char*)lastVideoFrame + width * height + uvWidth * (height>>1);
    for (k = 0; k < numRows; k++) {
        for (l = 0; l < numCols; l++) {
            pTmp0 = (unsigned int*)(pCurrent + (k * 8 * uvWidth) + l * 8);
            pTmp1 = (unsigned int*)(pLast    + (k * 8 * uvWidth) + l * 8);
            uint8_t* ptr = activeMap + k * mapCols + l;
            for (i = 0; i < 8 && !(*ptr); i++) {
                unsigned int* pTmp00 = (unsigned int*)((unsigned char*)pTmp0 + (i * uvWidth));
                unsigned int* pTmp11 = (unsigned int*)((unsigned char*)pTmp1 + (i * uvWidth));
                for (j = 0; j < 8 >> 2; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
        if (lastw) {
            pTmp0 = (unsigned int*)(pCurrent + (k * 8 * uvWidth) + numCols * 8);
            pTmp1 = (unsigned int*)(pLast + (k * 8 * uvWidth) + numCols * 8);
            uint8_t* ptr = activeMap + k * mapCols + numCols;
            for (i = 0; i < 8 && !(*ptr); i++) {
                unsigned char* pTmp00 = (unsigned char*)((unsigned char*)pTmp0 + (i * uvWidth));
                unsigned char* pTmp11 = (unsigned char*)((unsigned char*)pTmp1 + (i * uvWidth));
                for (j = 0; j < lastw >> 1; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
    }
    if (lasth) {
        for (l = 0; l < numCols; l++) {
            pTmp0 = (unsigned int*)(pCurrent + (numRows * 8 * uvWidth) + l * 8);
            pTmp1 = (unsigned int*)(pLast + (numRows * 8 * uvWidth) + l * 8);
            uint8_t* ptr = activeMap + numRows * mapCols + l;
            for (i = 0; i < lasth >> 1 && !(*ptr); i++) {
                unsigned int* pTmp00 = (unsigned int*)((unsigned char*)pTmp0 + (i * uvWidth));
                unsigned int* pTmp11 = (unsigned int*)((unsigned char*)pTmp1 + (i * uvWidth));
                for (j = 0; j < 8 >> 2; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
        if (lastw) {
            pTmp0 = (unsigned int*)(pCurrent + (numRows * 8 * uvWidth) + numCols * 8);
            pTmp1 = (unsigned int*)(pLast + (numRows * 8 * uvWidth) + numCols * 8);
            uint8_t* ptr = activeMap + numRows * mapCols + numCols;
            for (i = 0; i < lasth >> 1 && !(*ptr); i++) {
                unsigned char* pTmp00 = (unsigned char*)pTmp0 + (i * uvWidth);
                unsigned char* pTmp11 = (unsigned char*)pTmp1 + (i * uvWidth);
                for (j = 0; j < lastw >> 1; j++) {
                    if (*pTmp00 != *pTmp11) {
                        *ptr = 1;
                        break;
                    }
                    pTmp00++; pTmp11++;
                }
            }
        }
    }
}

- (void) encodeVPX:(int)timestamp duration:(int)duration sequenceDiff:(short)sequenceDiff frame:(byte *)frame length:(int)length width:(int)width height:(int)height stride:(int)stride flags:(int)flags
{
    vpx_codec_err_t res;
    vpx_image_t vpxImage;
    TranscodingEncodeInfo encodeInfo;
    unsigned nextRcMinQuantizer = (flags & FORMS_VIDEO_TRANSCODER_MIN_QUANT_MASK) >> FORMS_VIDEO_TRANSCODER_MIN_QUANT_SHIFT;
    unsigned nextRcMaxQuantizer = (flags & FORMS_VIDEO_TRANSCODER_MAX_QUANT_MASK) >> FORMS_VIDEO_TRANSCODER_MAX_QUANT_SHIFT;
    unsigned targetBitrate = (flags & FORMS_VIDEO_TRANSCODER_TARGET_KBIT_MASK) >> FORMS_VIDEO_TRANSCODER_TARGET_KBIT_SHIFT;
    bool activeMap = ((flags & FORMS_VIDEO_TRANSCODER_ACTIVE_MAP) != 0);
    unsigned l;

    if (!self.vpxEncoder) {
        free(frame);
        return;
    }
    if ((width != self.width) || (height != self.height)) {
        self.width = width;
        self.height = height;
        if (self.vpxEncoderInitialized) {
            self.vpxEncoderInitialized = false;
            if (vpx_codec_destroy(self.vpxEncoder)) {
                NSLog(@"VPX: vpx_codec_destroy transcoding failed");
            }
        }
        res = vpx_codec_enc_config_default(self.vpxEncoderInterface, self.vpxEncoderCfg, 0);
        if (res != VPX_CODEC_OK) {
            NSLog(@"VideoEncoderVPX(%u)::Initialize Failed to get default codec config res=%d", self.sessionID, res);
            free(frame);
            return;
        }
        self.vpxEncoderCfg->g_w = width;
        self.vpxEncoderCfg->g_h = height;

        self.vpxEncoderCfg->g_timebase.num = 1;
        self.vpxEncoderCfg->g_timebase.den = 30;
        self.vpxEncoderCfg->rc_target_bitrate = targetBitrate + (targetBitrate >> 2);
        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;
        if (flags & FORMS_VIDEO_TRANSCODER_APP_SHARING) {
            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 = 500;
            self.vpxEncoderCfg->rc_buf_optimal_sz = 600;
            self.vpxEncoderCfg->rc_buf_sz = 1000;
#endif
        }
        else {
            self.vpxEncoderCfg->rc_end_usage = VPX_VBR;
            self.vpxEncoderCfg->kf_min_dist = self.vpxEncoderCfg->kf_max_dist = self.vpxEncoderCfg->g_timebase.den * 10; // 1 per 10 seconds
            self.vpxEncoderCfg->g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
        }
        self.vpxEncoderCfg->rc_min_quantizer = nextRcMinQuantizer;
        self.vpxEncoderCfg->rc_max_quantizer = nextRcMaxQuantizer;
        res = vpx_codec_enc_init(self.vpxEncoder, self.vpxEncoderInterface, self.vpxEncoderCfg, 0);
        if (res != VPX_CODEC_OK) {
            NSLog(@"VideoEncoderVPX(%u)::Initialize Failed to initialize encoder. Res=%d", self.sessionID, res);
            free(frame);
            return;
        }
        self.vpxEncoderInitialized = true;

        if (flags & FORMS_VIDEO_TRANSCODER_APP_SHARING) {
            res = vpx_codec_control(self.vpxEncoder, VP8E_SET_CPUUSED, 8);
            if (res != VPX_CODEC_OK) {
                NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_CPUUSED %d", self.sessionID, res);
            }
            res = vpx_codec_control(self.vpxEncoder, VP8E_SET_NOISE_SENSITIVITY, 0);  // Off
            if (res != VPX_CODEC_OK) {
                NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_NOISE_SENSITIVITY %d", self.sessionID, res);
            }
            res = vpx_codec_control(self.vpxEncoder, VP8E_SET_STATIC_THRESHOLD, 100);
            if (res != VPX_CODEC_OK) {
                NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_STATIC_THRESHOLD %d", self.sessionID, res);
            }
            res = vpx_codec_control(self.vpxEncoder, VP8E_SET_SCREEN_CONTENT_MODE, 1);
            if (res != VPX_CODEC_OK) {
                NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_SCREEN_CONTENT_MODE %d", self.sessionID, res);
            }
        }
        else {
            if(width > 640) {
                res = vpx_codec_control(self.vpxEncoder, VP8E_SET_CPUUSED, 0);
                if (res != VPX_CODEC_OK) {
                    NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_CPUUSED %d", self.sessionID, res);
                }
                res = vpx_codec_control(self.vpxEncoder, VP8E_SET_NOISE_SENSITIVITY, 0);  // Off
                if (res != VPX_CODEC_OK) {
                    NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_NOISE_SENSITIVITY %d", self.sessionID, res);
                }
            }
            else {
                res = vpx_codec_control(self.vpxEncoder, VP8E_SET_CPUUSED, 0);
                if (res != VPX_CODEC_OK) {
                    NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_CPUUSED %d", self.sessionID, res);
                }
                res = vpx_codec_control(self.vpxEncoder, VP8E_SET_SHARPNESS, 0);
                if (res != VPX_CODEC_OK) {
                    NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_SHARPNESS %d", self.sessionID, res);
                }
                res = vpx_codec_control(self.vpxEncoder, VP8E_SET_NOISE_SENSITIVITY, 4);  // Adaptative
                if (res != VPX_CODEC_OK) {
                    NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_NOISE_SENSITIVITY %d", self.sessionID, res);
                }
            }
            res = vpx_codec_control(self.vpxEncoder, VP8E_SET_STATIC_THRESHOLD, 1);
            if (res != VPX_CODEC_OK) {
                NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_STATIC_THRESHOLD %d", self.sessionID, res);
            }
            res = vpx_codec_control(self.vpxEncoder, VP8E_SET_SCREEN_CONTENT_MODE, 0);
            if (res != VPX_CODEC_OK) {
                NSLog(@"VideoEncoderVPX(%u)::VPX VP8E_SET_SCREEN_CONTENT_MODE %d", self.sessionID, res);
            }
        }
        if (flags & FORMS_VIDEO_TRANSCODER_APP_SHARING) {
            self.vpxEncoderMap->rows = (height + 0xf) >> 4;
            self.vpxEncoderMap->cols = (width + 0xf) >> 4;
            l = 1;
            while (l < self.vpxEncoderMap->rows * self.vpxEncoderMap->cols) {
                l <<= 1;
            }
            if ((self.activeMap == nil) || (l > self.lenActiveMap)) {
                if (self.activeMap != nil) {
                    free(self.activeMap);
                }
                self.activeMap = (byte *)(malloc(l));
                self.lenActiveMap = l;
            }
            self.vpxEncoderMap->active_map = self.activeMap;
            l = 1;
            while (l < height * width) {
                l <<= 1;
            }
            if ((self.lastVideoFrame == nil) || (l > self.lenLastVideoFrame)) {
                if (self.lastVideoFrame != nil) {
                    free(self.lastVideoFrame);
                }
                self.lastVideoFrame = (byte *)(malloc(l));
                self.lenLastVideoFrame = l;
            }
            activeMap = false;
        }
    }

    memset(&vpxImage, 0, sizeof(vpx_image_t));
    vpxImage.fmt = VPX_IMG_FMT_YV12;
    vpxImage.cs = VPX_CS_BT_709/*[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 = VPX_CR_FULL_RANGE/*((CVPixelBufferGetPixelFormatType(processBuffer) == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) ||
        (CVPixelBufferGetPixelFormatType(processBuffer) == kCVPixelFormatType_420YpCbCr8PlanarFullRange)) ? VPX_CR_FULL_RANGE : VPX_CR_STUDIO_RANGE*/;
    vpxImage.w = width;
    vpxImage.h = height;
    vpxImage.bit_depth = 8;
    vpxImage.d_w = width;
    vpxImage.d_h = height;
    vpxImage.r_w = width;
    vpxImage.r_h = height;
    vpxImage.x_chroma_shift = 0;
    vpxImage.y_chroma_shift = 0;
    vpxImage.planes[VPX_PLANE_Y] = frame;
    vpxImage.stride[VPX_PLANE_Y] = stride;
    vpxImage.planes[VPX_PLANE_U] = frame + stride * height;
    vpxImage.stride[VPX_PLANE_U] = stride / 2;
    vpxImage.planes[VPX_PLANE_V] = frame + stride * height + (stride / 2) * (height / 2);
    vpxImage.stride[VPX_PLANE_V] = stride / 2;

    vpxImage.bps = 8;
    vpxImage.user_priv = &encodeInfo;
    encodeInfo.timestamp = (dword)timestamp;
    encodeInfo.duration = (dword)duration;
    encodeInfo.sequenceDiff = sequenceDiff;
    encodeInfo.flags = flags;

    if (flags & FORMS_VIDEO_TRANSCODER_APP_SHARING) {
        if((flags & FORMS_VIDEO_TRANSCODER_KEY_FRAME) || !activeMap) memset(self.vpxEncoderMap->active_map, 1, self.vpxEncoderMap->cols * self.vpxEncoderMap->rows);
        else CompareVideoFrames(frame, self.lastVideoFrame, width, height, self.vpxEncoderMap->active_map);
        res = vpx_codec_control(self.vpxEncoder, VP8E_SET_ACTIVEMAP, self.vpxEncoderMap);
        if (res != VPX_CODEC_OK) {
            NSLog(@"VideoEncoderVPX(%u)::EncodeSample Failed to set active map error=%d", self.sessionID, res);
        }
        memcpy(self.lastVideoFrame, frame, length);

        if ((self.vpxEncoderCfg->rc_min_quantizer != nextRcMinQuantizer || self.vpxEncoderCfg->rc_max_quantizer != nextRcMaxQuantizer)) {
            self.vpxEncoderCfg->rc_min_quantizer = nextRcMinQuantizer;
            self.vpxEncoderCfg->rc_max_quantizer = nextRcMaxQuantizer;
            res = vpx_codec_enc_config_set(self.vpxEncoder, self.vpxEncoderCfg);
            if (res != VPX_CODEC_OK) {
                NSLog(@"VideoEncoderVPX(%u)::EncodeSample vpx_codec_enc_config_set error=%d", self.sessionID, res);
            }
            //else NSLog(@"VideoEncoderVPX(%u)::EncodeSample vpx_codec_enc_config_set quantizer updated=%d,%d %d,%d", self.sessionID, cfg.rc_min_quantizer, nextRcMinQuantizer, cfg.rc_max_quantizer, nextRcMaxQuantizer);
        }
    }

    res = vpx_codec_encode(self.vpxEncoder, &vpxImage, self.vpxFrameIndex, 1,
        (flags & FORMS_VIDEO_TRANSCODER_KEY_FRAME) ? VPX_EFLAG_FORCE_KF : 0, VPX_DL_REALTIME);
    if (res != VPX_CODEC_OK) {
        NSLog(@"vpx_codec_encode encoding failed %d %d %d %d %08x", res, width, height, stride, flags);
    }
    (self.vpxFrameIndex)++;

    didTranscodeVPX(self, &vpxImage);
    free(frame);
}

- (void) vpxEncode:(CVImageBufferRef)processBuffer timestampEx:(int64_t)timestampEx durationEx:(int64_t)durationEx sequenceDiff:(short)sequenceDiff flags:(int)flags to:(int)to
{
    vpx_image_t vpxImage;
    TranscodingEncodeInfo encodeInfo;
    unsigned i, j;

    CVPixelBufferLockBaseAddress(processBuffer, 0);

    if (CVPixelBufferGetDataSize(processBuffer) == 0) {
        NSLog(@"vpxEncode data size 0");
        CVPixelBufferUnlockBaseAddress(processBuffer, 0);
        return;
    }
    NSDictionary *attachments = (__bridge NSDictionary *)CVBufferGetAttachments(processBuffer, kCVAttachmentMode_ShouldPropagate);
    NSString *YCbCrMatrix = [attachments objectForKey:(NSString *)kCVImageBufferYCbCrMatrixKey];

    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 = CVPixelBufferGetWidth(processBuffer);
    vpxImage.h = CVPixelBufferGetHeight(processBuffer);
    vpxImage.bit_depth = 8;
    vpxImage.d_w = CVPixelBufferGetWidth(processBuffer);
    vpxImage.d_h = CVPixelBufferGetHeight(processBuffer);
    vpxImage.r_w = CVPixelBufferGetWidth(processBuffer);
    vpxImage.r_h = CVPixelBufferGetHeight(processBuffer);
    vpxImage.x_chroma_shift = 0;
    vpxImage.y_chroma_shift = 0;
    switch (CVPixelBufferGetPixelFormatType(processBuffer)) {
    case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
    case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: // NV12
        vpxImage.planes[VPX_PLANE_Y] = CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0);
        vpxImage.stride[VPX_PLANE_Y] = (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
            CVPixelBufferGetHeight(processBuffer);
        size_t width = CVPixelBufferGetWidth(processBuffer) / 2;
        size_t height = CVPixelBufferGetHeight(processBuffer) / 2;
        size_t srcRowBytes = (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
            CVPixelBufferGetHeight(processBuffer);
        size_t dstRowBytes = IMAGE_BUFFER_ALIGN(width);
        if (dstRowBytes > srcRowBytes / 2) {
            dstRowBytes = srcRowBytes / 2;
        }
        memcpy(self.UVBuffer.mutableBytes, CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1), srcRowBytes * height);
        const word *src = self.UVBuffer.mutableBytes;
        byte *dst0 = ((byte *)CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1));
        byte *dst1 = dst0 + dstRowBytes * height;
        vpxImage.planes[VPX_PLANE_U] = dst0;
        vpxImage.stride[VPX_PLANE_U] = dstRowBytes;
        vpxImage.planes[VPX_PLANE_V] = dst1;
        vpxImage.stride[VPX_PLANE_V] = dstRowBytes;
        for (j = 0; j < height; j++) {
            for (i = 0; i < width; i++) {
                dst0[i] = ((const byte *)(&src[i]))[0];
                dst1[i] = ((const byte *)(&src[i]))[1];
            }
            src += srcRowBytes / sizeof(word);
            dst0 += dstRowBytes;
            dst1 += dstRowBytes;
        }
        break;
    case kCVPixelFormatType_420YpCbCr8Planar: // I420
    case kCVPixelFormatType_420YpCbCr8PlanarFullRange:
        vpxImage.planes[VPX_PLANE_Y] = CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0);
        vpxImage.stride[VPX_PLANE_Y] = (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
            CVPixelBufferGetHeight(processBuffer);
        vpxImage.planes[VPX_PLANE_U] = CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1);
        vpxImage.stride[VPX_PLANE_U] = (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1)))) /
            (CVPixelBufferGetHeight(processBuffer) / 2);
        vpxImage.planes[VPX_PLANE_V] = CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2);
        vpxImage.stride[VPX_PLANE_V] = (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1)))) /
            (CVPixelBufferGetHeight(processBuffer) / 2);
        break;
    default:
        NSLog(@"vpxEncode unexpected format %u %u/%u", CVPixelBufferGetPixelFormatType(processBuffer), (unsigned)(CVPixelBufferGetWidth(processBuffer)), (unsigned)(CVPixelBufferGetHeight(processBuffer)));
        CVPixelBufferUnlockBaseAddress(processBuffer, 0);
        return;
/*
        vpxImage.planes[VPX_PLANE_Y] = CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0);
        vpxImage.stride[VPX_PLANE_Y] = (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
            CVPixelBufferGetHeightOfPlane(processBuffer, 0);
        vpxImage.planes[VPX_PLANE_U] = CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1);
        vpxImage.stride[VPX_PLANE_U] = (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1)))) /
            CVPixelBufferGetHeightOfPlane(processBuffer, 1);
        vpxImage.planes[VPX_PLANE_V] = CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2);
        vpxImage.stride[VPX_PLANE_V] = (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1)))) /
            CVPixelBufferGetHeightOfPlane(processBuffer, 1);
        break;
*/
    }
    CVPixelBufferUnlockBaseAddress(processBuffer, 0);

    vpxImage.bps = 8;
    if (to == FORMS_WEBCAM_CODER_IYUV) {
        didTranscode(self, &vpxImage, (int)timestampEx, (int)durationEx, sequenceDiff, flags);
    }
    else {
        if (self.vpxEncoder) {
            vpxImage.user_priv = &encodeInfo;
            encodeInfo.timestamp = (dword)timestampEx;
            encodeInfo.duration = (dword)durationEx;
            encodeInfo.sequenceDiff = sequenceDiff;
            encodeInfo.flags = flags;

            const vpx_codec_err_t res = vpx_codec_encode(self.vpxEncoder, &vpxImage, self.vpxFrameIndex, 1,
                (flags & FORMS_VIDEO_TRANSCODER_KEY_FRAME) ? VPX_EFLAG_FORCE_KF : 0, VPX_DL_REALTIME);
            if (res != VPX_CODEC_OK) {
                NSLog(@"vpx_codec_encode transcoding failed %d", res);
            }
            (self.vpxFrameIndex)++;

            didTranscodeVPX(self, &vpxImage);
        }
    }
}

void didDecodeH264(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimestamp, CMTime presentationDuration, int to)
{
    TranscodingSession *transcodingSession = (__bridge TranscodingSession *)decompressionOutputRefCon;

    if (status != noErr)
    {
        NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        NSLog(@"didDecodeH264 status %@", error);
        return;
    }
    if (infoFlags & kVTDecodeInfo_FrameDropped) {
        NSLog(@"didDecodeH264 infoFlags %x frame dropped", (int)infoFlags);
    }
    transcodingSession.rtpTimestamp += (((int64_t)(presentationTimestamp.value - transcodingSession.presentationTimestamp)) * 90000) / presentationTimestamp.timescale;
    transcodingSession.presentationTimestamp = presentationTimestamp.value;
    short sequenceDiff = (short)(presentationDuration.timescale - 90000);
    dword rtpDuration = (dword)(presentationDuration.value);
    dword flags = ((((byte *)sourceFrameRefCon)[4] & 0x1f) == NAL_UNIT_IDR) ? FORMS_VIDEO_TRANSCODER_KEY_FRAME : 0;
    free(sourceFrameRefCon);
    [transcodingSession vpxEncode:imageBuffer timestampEx:transcodingSession.rtpTimestamp durationEx:rtpDuration sequenceDiff:sequenceDiff flags:flags to:to];
}

void didDecodeH264ToVP8(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimestamp, CMTime presentationDuration)
{
    didDecodeH264(decompressionOutputRefCon, sourceFrameRefCon, status, infoFlags, imageBuffer, presentationTimestamp, presentationDuration, FORMS_WEBCAM_CODER_VP8);
}

void didDecodeH264ToRaw(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimestamp, CMTime presentationDuration)
{
    didDecodeH264(decompressionOutputRefCon, sourceFrameRefCon, status, infoFlags, imageBuffer, presentationTimestamp, presentationDuration, FORMS_WEBCAM_CODER_IYUV);
}

/*
void * _Nullable (*AllocateBlock)(void *refcon, size_t sizeInBytes)
{
    return (malloc(sizeInBytes));
}

void (*FreeBlock)(void *refcon, void *doomedMemoryBlock, size_t sizeInBytes)
{
    free(doomedMemoryBlock);
}
*/

- (void) h264Decode:(int)timestamp duration:(int)duration sequenceDiff:(short)sequenceDiff frame:(byte *)frame length:(int)length to:(int)to
{
    OSStatus status;
    unsigned int pos, start, end;
    short decodeSequenceDiff;
    byte nal;

    decodeSequenceDiff = 0;
    if (length != 0) {
        if (sequenceDiff != 0) {
            self.sequenceDiff += sequenceDiff;
            if (self.sequenceDiff == 0) {
                self.sequenceDiff = 1;
            }
        }
        decodeSequenceDiff = self.sequenceDiff;
    }
    if (length != 0) {
        pos = 0;
        do {
            if (frame[pos + 2] > 0x01) {
                pos += 3;
            }
            else if (frame[pos + 1] != 0x00) {
                pos += 2;
            }
            else if ((frame[pos + 2] == 0x00) || (frame[pos] != 0x00)) {
                pos++;
            }
            else {
                break;
            }
        }
        while (true);
        while (pos + 3 < length) {
            pos += 3;
            start = pos;
            do {
                if (frame[pos + 2] > 0x01) {
                    pos += 3;
                }
                else if (frame[pos + 1] != 0x00) {
                    pos += 2;
                }
                else if ((frame[pos + 2] == 0x00) || (frame[pos] != 0x00)) {
                    pos++;
                }
                else {
                    break;
                }
            }
            while (pos + 3 < length);
            end = (pos + 3 < length) ? ((frame[pos - 1] == 0x00) ? pos - 1 : pos) : length;
            nal = frame[start];
            switch (nal & 0x1f) {
            case NAL_UNIT_SPS:
                self.decodeSps = [NSData dataWithBytes:&(frame[start]) length:end - start];
                break;
            case NAL_UNIT_PPS:
                self.decodePps = [NSData dataWithBytes:&(frame[start]) length:end - start];
                break;
            case NAL_UNIT_IDR:
                self.sequenceDiff = 0;
                if ((self.decodeSps != nil) && (self.decodePps != nil)) {
                    if ((self.sps == nil) || (self.decodeSps.length != self.sps.length) || (memcmp(self.decodeSps.bytes, self.sps.bytes, self.sps.length) != 0)
                     || (self.pps == nil) || (self.decodePps.length != self.pps.length) || (memcmp(self.decodePps.bytes, self.pps.bytes, self.pps.length) != 0)) {
                        const byte * const parameterSetPointers[2] = { self.decodeSps.bytes, self.decodePps.bytes };
                        const size_t parameterSetSizes[2] = { self.decodeSps.length, self.decodePps.length };
                        CMVideoFormatDescriptionRef decoderFormatDescription = nil;
                        status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, 4, &decoderFormatDescription);
                        if (status != noErr) {
                            NSLog(@"H264: Unable to create H264 transcoding session parameters %d", (int)status);
                        }
                        else {
                            self.sps = self.decodeSps;
                            self.pps = self.decodePps;
                            self.formatDesc = decoderFormatDescription;
                            if ((self.decodingSession == nil) || !VTDecompressionSessionCanAcceptFormatDescription(self.decodingSession, self.formatDesc)) {
                                const VTDecompressionOutputCallbackRecord callback = { (to == FORMS_WEBCAM_CODER_IYUV) ? didDecodeH264ToRaw : didDecodeH264ToVP8, (__bridge void *)self };
                                CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(self.formatDesc);
                                self.width = dimensions.width;
                                self.height = dimensions.height;

                                // Set the pixel attributes for the destination buffer
                                CFMutableDictionaryRef destinationPixelBufferAttributes = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                                SInt32 destinationPixelType = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
                                CFDictionarySetValue(destinationPixelBufferAttributes, kCVPixelBufferPixelFormatTypeKey, CFNumberCreate(NULL, kCFNumberSInt32Type, &destinationPixelType));
                                CFDictionarySetValue(destinationPixelBufferAttributes, kCVPixelBufferWidthKey, CFNumberCreate(NULL, kCFNumberSInt32Type, &dimensions.width));
                                CFDictionarySetValue(destinationPixelBufferAttributes, kCVPixelBufferHeightKey, CFNumberCreate(NULL, kCFNumberSInt32Type, &dimensions.height));
                                CFDictionarySetValue(destinationPixelBufferAttributes, kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue);

                                // Set the Decoder Parameters
                                CFMutableDictionaryRef decoderParameters = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                                CFDictionarySetValue(decoderParameters, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);

                                if (self.decodingSession != nil) {
                                    VTDecompressionSessionInvalidate(self.decodingSession);
                                    CFRelease(self.decodingSession);
                                    self.decodingSession = nil;
                                }
                                if (self.vpxEncoderInitialized) {
                                    self.vpxEncoderInitialized = false;
                                    if (vpx_codec_destroy(self.vpxEncoder)) {
                                        NSLog(@"VPX: vpx_codec_destroy transcoding failed");
                                    }
                                }
                                // Create the decompression session
                                VTDecompressionSessionRef decodingSession;
                                status = VTDecompressionSessionCreate(NULL, self.formatDesc, decoderParameters, destinationPixelBufferAttributes, &callback, &decodingSession);
                                NSLog(@"H264: VTDecompressionSessionCreate transcoding %d %d %d", (int)(self.width), (int)(self.height), (int)status);
                                self.decodingSession = decodingSession;
                                self.UVBuffer = [NSMutableData dataWithCapacity:IMAGE_BUFFER_ALIGN(dimensions.width / 2) * dimensions.height];

                                vpx_codec_enc_cfg_t vpxEncoder_cfg;
                                if (vpx_codec_enc_config_default(self.vpxEncoderInterface, &vpxEncoder_cfg, 0)) {
                                    NSLog(@"VPX: vpx_codec_enc_config_default transcoding failed");
                                }
                                vpxEncoder_cfg.g_w = dimensions.width;
                                vpxEncoder_cfg.g_h = dimensions.height;
                                vpxEncoder_cfg.rc_target_bitrate = 600 * 1024;
                                if (vpx_codec_enc_init(self.vpxEncoder, self.vpxEncoderInterface, &vpxEncoder_cfg, 0)) {
                                    NSLog(@"VPX: vpx_codec_enc_init transcoding failed");
                                }
                                else {
                                    self.vpxEncoderInitialized = true;
                                }

                                // release the dictionaries
                                CFRelease(destinationPixelBufferAttributes);
                                CFRelease(decoderParameters);

                                // Check the Status
                                if(status != noErr) {
                                    NSLog(@"H264: Unable to create H264 transcoding session %d", (int)status);
                                }
                            }
                        }
                    }
                    self.decodeSps = nil;
                    self.decodePps = nil;
                }
            case NAL_UNIT_NON_IDR:
                if ((self.decodingSession != nil) && (self.sequenceDiff == 0)) {
                    size_t blockSize = end - start + 4;
                    byte *data = (byte *)(malloc(blockSize));
                    data[0] = (byte)((blockSize - 4) >> 24);
                    data[1] = (byte)((blockSize - 4) >> 16);
                    data[2] = (byte)((blockSize - 4) >> 8);
                    data[3] = (byte)(blockSize - 4);
                    memcpy(&(data[4]), &(frame[start]), blockSize - 4);
                    CMBlockBufferRef blockBuffer;
                    status = CMBlockBufferCreateWithMemoryBlock(NULL, data, blockSize, kCFAllocatorNull, NULL, 0, blockSize, 0, &blockBuffer);
                    if(status != noErr) {
                        NSLog(@"H264: Unable to create H264 block buffer %d", (int)status);
                    }
                    else {
                        CMSampleBufferRef sampleBuffer;
                        CMSampleTimingInfo timingInfo;
                        timingInfo.decodeTimeStamp = CMTimeMake(timestamp, 90000);
                        timingInfo.duration = CMTimeMake(duration, 90000 + decodeSequenceDiff);
                        timingInfo.presentationTimeStamp = CMTimeMake(timestamp, 90000);
                        status = CMSampleBufferCreate(kCFAllocatorDefault, blockBuffer, true, NULL, NULL, self.formatDesc, 1, 1, &timingInfo, 1, &blockSize, &sampleBuffer);
                        if(status != noErr) {
                            NSLog(@"H264: Unable to create H264 sample buffer %d", (int)status);
                        }
                        else {
                            CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
                            CFMutableDictionaryRef dict = (CFMutableDictionaryRef)(CFArrayGetValueAtIndex(attachments, 0));
                            CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);

                            VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression;
                            VTDecodeInfoFlags infoFlagsOut;
                            VTDecompressionSessionDecodeFrame(self.decodingSession, sampleBuffer, flags, data, &infoFlagsOut);
                            if (infoFlagsOut & kVTDecodeInfo_FrameDropped) {
                                NSLog(@"h264Decode flagOut %x frame dropped", (int)infoFlagsOut);
                            }

                            CFRelease(sampleBuffer);
                        }
                        CFRelease(blockBuffer);
                    }
                }
                break;
            default:
                break;
            }
        }
    }
    if ((length == 0) || (self.sequenceDiff != 0)) {
        (self.sequenceDiff)++;
        if (self.sequenceDiff == 0) {
            self.sequenceDiff = 1;
        }
    }
    free(frame);
}

- (void) stopTranscode
{
    if (self.encodingSession != nil) {
        // Mark the completion
        VTCompressionSessionCompleteFrames(self.encodingSession, kCMTimeInvalid);

        // End the session
        VTCompressionSessionInvalidate(self.encodingSession);
        CFRelease(self.encodingSession);
        self.encodingSession = nil;
    }
    if (vpx_codec_destroy(self.vpxDecoder)) {
        NSLog(@"VPX: vpx_codec_destroy transcoding failed");
    }
    free(self.vpxDecoder);
    self.vpxDecoder = nil;

    if (self.decodingSession != nil) {
        VTDecompressionSessionInvalidate(self.decodingSession);
        CFRelease(self.decodingSession);
        self.decodingSession = nil;
    }
    if (self.vpxEncoderInitialized) {
        self.vpxEncoderInitialized = false;
        if (vpx_codec_destroy(self.vpxEncoder)) {
            NSLog(@"VPX: vpx_codec_destroy transcoding failed");
        }
    }
    if (self.formatDesc != nil) {
         CFRelease(self.formatDesc);
         self.formatDesc = nil;
    }
    free(self.vpxEncoder);
    self.vpxEncoder = nil;
    free(self.vpxEncoderCfg);
    self.vpxEncoderCfg = nil;
    free(self.vpxEncoderMap);
    self.vpxEncoderMap = nil;
    free(self.activeMap);
    self.activeMap = nil;
    free(self.lastVideoFrame);
    self.lastVideoFrame = nil;
    [self.resultBuffers removeAllObjects];
}

- (void) stop
{
    dispatch_async(self.videoDispatchQueue, ^
        { [self stopTranscode]; });
}

- (void) startTranscode
{
    self.vpxDecoderInterface = vpx_codec_vp8_dx();
    vpx_codec_flags_t flags = 0;
/*
    vpx_codec_caps_t capabilities = vpx_codec_get_caps(self.vpxDecoderInterface);
    NSLog(@"VPX: capabilities %08x", capabilities);
    if (capabilities & VPX_CODEC_CAP_ERROR_CONCEALMENT) {
        flags |= VPX_CODEC_USE_ERROR_CONCEALMENT;
    }
*/
    self.vpxDecoder = (vpx_codec_ctx_t *)(malloc(sizeof(vpx_codec_ctx_t)));
    if (vpx_codec_dec_init(self.vpxDecoder, self.vpxDecoderInterface, NULL, flags)) {
        NSLog(@"VPX: vpx_codec_dec_init transcoding failed");
    }

    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.vpxEncoderMap = (vpx_active_map_t *)(malloc(sizeof(vpx_active_map_t)));

    self.resultBuffers = [[NSMutableArray alloc] init];
    self.resultUsedPos = 0;
}

- (void) start
{
    dispatch_async(self.videoDispatchQueue, ^
        { [self startTranscode]; });
}

- (void) transcode:(int)timestamp duration:(int)duration sequenceDiff:(short)sequenceDiff frame:(byte *)frame length:(int)length width:(int)width height:(int)height stride:(int)stride flags:(int)flags from:(int)from to:(int)to
{
    if (from == FORMS_WEBCAM_CODER_H264) {
        dispatch_async(self.videoDispatchQueue, ^
            { [self h264Decode:timestamp duration:duration sequenceDiff:sequenceDiff frame:frame length:length to:to]; });
    }
    else if (from == FORMS_WEBCAM_CODER_VP8) {
        dispatch_async(self.videoDispatchQueue, ^
            { [self vpxDecode:timestamp duration:duration sequenceDiff:sequenceDiff frame:frame length:length to:to]; });
    }
    else {
        (self.encoderReq)++;
        if (to == FORMS_WEBCAM_CODER_H264) {
            dispatch_async(self.videoDispatchQueue, ^
                {
                    if (((unsigned)(self.encoderReq - self.encoderAck)) < 4) {
                        [self encodeH264:timestamp duration:duration sequenceDiff:sequenceDiff frame:frame length:length width:width height:height stride:stride flags:flags];
                    }
                    else {
                        free(frame);
                    }
                    (self.encoderAck++);
                });
        }
        else {
            dispatch_async(self.videoDispatchQueue, ^
                {
                    if (((unsigned)(self.encoderReq - self.encoderAck)) < 4) {
                        [self encodeVPX:timestamp duration:duration sequenceDiff:sequenceDiff frame:frame length:length width:width height:height stride:stride flags:flags];
                    }
                    else {
                        free(frame);
                    }
                    (self.encoderAck++);
                });
        }
    }
}

@end
