/*---------------------------------------------------------------------------*/
/* ActiveCamera.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"
#import "client/ios/include/libyuv/convert_from_argb.h"
#if TARGET_OS_IPHONE
#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/vp8cx.h"
#import "client/macos/VPX.framework/Headers/vpx/vpx_encoder.h"
#endif
#import "ActiveCamera.h"

extern void IVideoFormatConversionRGB32ToYUV(unsigned char* dest, int destStride, const byte* src, int srcStride, int width, int height);
extern void IVideoFormatConversionARGBToYUV(unsigned char* dest, int destStride, const byte* src, int srcStride, int width, int height);
extern void IVideoFormatConversionRGBAToYUV(unsigned char* dest, int destStride, const byte* src, int srcStride, int width, int height);
extern void IVideoFormatConversionYUY2ToYUV(unsigned char* dest, int destStride, const byte* src, int srcStride, int width, int height);

@interface CaptureSessionPreset : NSObject

@property (nonatomic) AVCaptureSessionPreset preset;
@property (nonatomic) int width;
@property (nonatomic) int height;

- (id) initWithPreset:(AVCaptureSessionPreset)preset width:(int)width height:(int)height;

@end
@implementation CaptureSessionPreset

- (id) initWithPreset:(AVCaptureSessionPreset)preset width:(int)width height:(int)height
{
    self = [super init];
    if (self) {
        _preset = preset;
        _width = width;
        _height = height;
    }
    return self;
}

@end
@interface ActiveCamera ()

#define PROCESS_SAMPLE_BUFFERS  4

@property (nonatomic) IosEvent *ios_event;
@property (nonatomic) dispatch_queue_t videoDispatchQueue;
@property (nonatomic) int previewCoder;
@property (nonatomic) int webcamCoder;
@property (nonatomic) unsigned rotation;
@property (nonatomic) unsigned nextRotation;
@property (nonatomic) AVCaptureSession *session;
@property (nonatomic) bool sessionRunning;
@property (nonatomic) AVCaptureDeviceInput *deviceInput;
@property (nonatomic) AVCaptureVideoDataOutput *dataOutput;
@property (nonatomic) int64_t presentationTimestamp;
@property (nonatomic) dword rtpTimestamp;
@property (nonatomic) unsigned fullIntraRequest;
@property (nonatomic) unsigned fullIntraAck;
@property (nonatomic) NSMutableArray *processBuffers;
@property (nonatomic) unsigned processUsedPos;
@property (nonatomic) bool blankOut;
@property (nonatomic) NSMutableArray *captureSessionPresets;

@end
@implementation ActiveCamera

- (id) initWithUniqueID:(NSString *)uniqueID ios_event:(IosEvent *)ios_event videoDispatchQueue:(dispatch_queue_t)videoDispatchQueue
{
    self = [super init];
    if (self) {
        _uniqueID = uniqueID;
        _ios_event = ios_event;
        _videoDispatchQueue = videoDispatchQueue;
        self.previewCoder = FORMS_WEBCAM_CODER_H264;
        self.webcamCoder = FORMS_WEBCAM_CODER_VP8;
        self.startFlag = false;
        self.rotation = 0;
        self.nextRotation = 0;
        self.session = nil;
        self.sessionRunning = false;
        self.deviceInput = nil;
        self.dataOutput = nil;
        self.presentationTimestamp = 0;
        self.rtpTimestamp = 0;
        self.fullIntraRequest = 0;
        self.fullIntraAck = 0;
        self.processBuffers = nil;
        self.processUsedPos = 0;
        self.blankOut = false;
        self.context = nil;
        self.hdVideo = false;
        self.captureSessionPresets = [[NSMutableArray alloc] init];
        [self.captureSessionPresets addObject:[[CaptureSessionPreset alloc] initWithPreset:AVCaptureSessionPreset352x288 width:352 height:288]];
        [self.captureSessionPresets addObject:[[CaptureSessionPreset alloc] initWithPreset:AVCaptureSessionPreset640x480 width:640 height:480]];
        [self.captureSessionPresets addObject:[[CaptureSessionPreset alloc] initWithPreset:AVCaptureSessionPreset1280x720 width:1280 height:720]];
#if TARGET_OS_IPHONE
        [self.captureSessionPresets addObject:[[CaptureSessionPreset alloc] initWithPreset:AVCaptureSessionPreset1920x1080 width:1920 height:1080]];
#endif
    }
    return self;
}

typedef struct {
    OSType osType;
    const char *name;
}
OSTypeNameMap;

#define NAME_MAP_ENTRY(x) { x, #x }

const char *OSTypeGetName(const OSTypeNameMap *osTypeNameMap, OSType osType)
{
    int i;
    i = 0;
    while ((osTypeNameMap[i].name != nil) && (osTypeNameMap[i].osType != osType)) {
        i++;
    }
    return ((osTypeNameMap[i].name != nil) ? osTypeNameMap[i].name : "?");
}

static OSTypeNameMap pixelFormatTypeNameMap[] = {
    NAME_MAP_ENTRY(kCVPixelFormatType_16Gray),
    NAME_MAP_ENTRY(kCVPixelFormatType_16LE555),
    NAME_MAP_ENTRY(kCVPixelFormatType_16LE565),
    NAME_MAP_ENTRY(kCVPixelFormatType_16BE565),
    NAME_MAP_ENTRY(kCVPixelFormatType_16BE555),
    NAME_MAP_ENTRY(kCVPixelFormatType_16LE5551),
    NAME_MAP_ENTRY(kCVPixelFormatType_1Monochrome),
    NAME_MAP_ENTRY(kCVPixelFormatType_128RGBAFloat),
//    NAME_MAP_ENTRY(kCVPixelFormatType_14Bayer_BGGR),
//    NAME_MAP_ENTRY(kCVPixelFormatType_14Bayer_RGGB),
//    NAME_MAP_ENTRY(kCVPixelFormatType_14Bayer_GBRG),
//    NAME_MAP_ENTRY(kCVPixelFormatType_14Bayer_GRBG),
    NAME_MAP_ENTRY(kCVPixelFormatType_1IndexedGray_WhiteIsZero),
    NAME_MAP_ENTRY(kCVPixelFormatType_24BGR),
    NAME_MAP_ENTRY(kCVPixelFormatType_24RGB),
    NAME_MAP_ENTRY(kCVPixelFormatType_2Indexed),
    NAME_MAP_ENTRY(kCVPixelFormatType_2IndexedGray_WhiteIsZero),
    NAME_MAP_ENTRY(kCVPixelFormatType_30RGB),
    NAME_MAP_ENTRY(kCVPixelFormatType_32BGRA),
    NAME_MAP_ENTRY(kCVPixelFormatType_32ABGR),
    NAME_MAP_ENTRY(kCVPixelFormatType_32ARGB),
    NAME_MAP_ENTRY(kCVPixelFormatType_32RGBA),
    NAME_MAP_ENTRY(kCVPixelFormatType_32AlphaGray),
//    NAME_MAP_ENTRY(kCVPixelFormatType_30RGBLEPackedWideGamut),
    NAME_MAP_ENTRY(kCVPixelFormatType_420YpCbCr8Planar),
    NAME_MAP_ENTRY(kCVPixelFormatType_420YpCbCr8PlanarFullRange),
    NAME_MAP_ENTRY(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange),
    NAME_MAP_ENTRY(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange),
//    NAME_MAP_ENTRY(kCVPixelFormatType_420YpCbCr10BiPlanarFullRange),
//    NAME_MAP_ENTRY(kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange),
    NAME_MAP_ENTRY(kCVPixelFormatType_422YpCbCr8),
    NAME_MAP_ENTRY(kCVPixelFormatType_422YpCbCr16),
    NAME_MAP_ENTRY(kCVPixelFormatType_422YpCbCr10),
    NAME_MAP_ENTRY(kCVPixelFormatType_422YpCbCr8_yuvs),
    NAME_MAP_ENTRY(kCVPixelFormatType_422YpCbCr8FullRange),
    NAME_MAP_ENTRY(kCVPixelFormatType_422YpCbCr_4A_8BiPlanar),
//    NAME_MAP_ENTRY(kCVPixelFormatType_422YpCbCr10BiPlanarFullRange),
//    NAME_MAP_ENTRY(kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange),
    NAME_MAP_ENTRY(kCVPixelFormatType_444YpCbCr8),
    NAME_MAP_ENTRY(kCVPixelFormatType_444YpCbCr10),
//    NAME_MAP_ENTRY(kCVPixelFormatType_444YpCbCr10BiPlanarFullRange),
//    NAME_MAP_ENTRY(kCVPixelFormatType_444YpCbCr10BiPlanarVideoRange),
    NAME_MAP_ENTRY(kCVPixelFormatType_4444YpCbCrA8),
    NAME_MAP_ENTRY(kCVPixelFormatType_4444AYpCbCr8),
    NAME_MAP_ENTRY(kCVPixelFormatType_4444YpCbCrA8R),
    NAME_MAP_ENTRY(kCVPixelFormatType_4444AYpCbCr16),
    NAME_MAP_ENTRY(kCVPixelFormatType_48RGB),
    NAME_MAP_ENTRY(kCVPixelFormatType_4Indexed),
    NAME_MAP_ENTRY(kCVPixelFormatType_4IndexedGray_WhiteIsZero),
    NAME_MAP_ENTRY(kCVPixelFormatType_64ARGB),
    NAME_MAP_ENTRY(kCVPixelFormatType_64RGBAHalf),
    NAME_MAP_ENTRY(kCVPixelFormatType_8Indexed),
    NAME_MAP_ENTRY(kCVPixelFormatType_8IndexedGray_WhiteIsZero),
//    NAME_MAP_ENTRY(kCVPixelFormatType_ARGB2101010LEPacked),
//    NAME_MAP_ENTRY(kCVPixelFormatType_DepthFloat16),
//    NAME_MAP_ENTRY(kCVPixelFormatType_DepthFloat32),
//    NAME_MAP_ENTRY(kCVPixelFormatType_DisparityFloat16),
//    NAME_MAP_ENTRY(kCVPixelFormatType_DisparityFloat32),
    NAME_MAP_ENTRY(kCVPixelFormatType_OneComponent8),
    NAME_MAP_ENTRY(kCVPixelFormatType_OneComponent16Half),
    NAME_MAP_ENTRY(kCVPixelFormatType_OneComponent32Float),
    NAME_MAP_ENTRY(kCVPixelFormatType_TwoComponent8),
    NAME_MAP_ENTRY(kCVPixelFormatType_TwoComponent16Half),
    NAME_MAP_ENTRY(kCVPixelFormatType_TwoComponent32Float),
    { 0, nil },
};

#pragma mark KVO and Notifications

- (void) videoAddObservers
{
    [self.session addObserver:self forKeyPath:@"running" options:NSKeyValueObservingOptionNew context:(__bridge void *)self];

#if TARGET_OS_IPHONE
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(subjectAreaDidChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:self.deviceInput.device];
#endif
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:self.session];

    /*
     A session can only run when the app is full screen. It will be interrupted
     in a multi-app layout, introduced in iOS 9, see also the documentation of
     AVCaptureSessionInterruptionReason. Add observers to handle these session
     interruptions and show a preview is paused message. See the documentation
     of AVCaptureSessionWasInterruptedNotification for other interruption reasons.
    */
    if ([NSClassFromString(@"AVCaptureDevice") respondsToSelector:@selector(authorizationStatusForMediaType:)]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionWasInterrupted:) name:AVCaptureSessionWasInterruptedNotification object:self.session];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionInterruptionEnded:) name:AVCaptureSessionInterruptionEndedNotification object:self.session];
    }
}

- (void) videoRemoveObservers
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];

    [self.session removeObserver:self forKeyPath:@"running" context:(__bridge void *)self];

#if TARGET_OS_IPHONE
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:self.deviceInput.device];
#endif
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionRuntimeErrorNotification object:self.session];

    if ([NSClassFromString(@"AVCaptureDevice") respondsToSelector:@selector(authorizationStatusForMediaType:)]) {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionWasInterruptedNotification object:self.session];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionInterruptionEndedNotification object:self.session];
    }
}

- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
    if ([keyPath isEqualToString:@"running"]) {
        NSLog(@"Capture session %@ is running: %d", self.uniqueID, [change[NSKeyValueChangeNewKey] boolValue]);
    }
}

- (void) subjectAreaDidChange:(NSNotification*)notification
{
    NSLog(@"Capture session %@ subjectAreaDidChange", self.uniqueID);
}

- (void) sessionRuntimeError:(NSNotification*)notification
{
    NSError* error = notification.userInfo[AVCaptureSessionErrorKey];
    NSLog(@"Capture session runtime error: %@", error);

#if TARGET_OS_IPHONE
    // If media services were reset, and the last start succeeded, restart the session.
    if (error.code == AVErrorMediaServicesWereReset) {
        dispatch_async(self.videoDispatchQueue, ^{
            ActiveCamera *__weak weakSelf = self;
            if (weakSelf.deviceInput != nil) {
                [weakSelf.session startRunning];
            }
        });
    }
#endif
}

- (void) sessionWasInterrupted:(NSNotification*)notification
{
#if TARGET_OS_IPHONE
    /*
     In some scenarios we want to enable the user to resume the session running.
     For example, if music playback is initiated via control center while
     using AVCam, then the user can let AVCam resume
     the session running, which will stop music playback. Note that stopping
     music playback in control center will not automatically resume the session
     running. Also note that it is not always possible to resume, see -[resumeInterruptedSession:].
    */
    AVCaptureSessionInterruptionReason reason = [notification.userInfo[AVCaptureSessionInterruptionReasonKey] integerValue];
    NSLog(@"Capture session was interrupted with reason %ld", (long)reason);

    if (reason == AVCaptureSessionInterruptionReasonVideoDeviceInUseByAnotherClient) {
        NSLog(@"Session %@ stopped running due to use by another app.", self.uniqueID);
    }
    else if (reason == AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableWithMultipleForegroundApps) {
        NSLog(@"Session %@ stopped running due to sharing with another app.", self.uniqueID);
    }
#else
    NSLog(@"Capture session was interrupted");
#endif
}

- (void) sessionInterruptionEnded:(NSNotification*)notification
{
    NSLog(@"Capture session %@ interruption ended", self.uniqueID);
}

- (unsigned) cameraDisplayOrientation
{
#if TARGET_OS_IPHONE
    UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
    AVCaptureDevice *videoDevice = [AVCaptureDevice deviceWithUniqueID:self.uniqueID];
    switch (interfaceOrientation) {
    case UIInterfaceOrientationPortraitUpsideDown: return 90;
    case UIInterfaceOrientationLandscapeLeft: return ((videoDevice.position == AVCaptureDevicePositionFront) ? 0 : 180);
    case UIInterfaceOrientationLandscapeRight: return ((videoDevice.position == AVCaptureDevicePositionFront) ? 180 : 0);
    default: return 270;
    }
#else
    return 0;
#endif
}

- (bool) updateRotation
{
    unsigned r = [self cameraDisplayOrientation];
    if (r != self.nextRotation) {
        self.nextRotation = r;
        return true;
    }
    return false;
}

#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 {
    void *activeCamera;
    CVImageBufferRef processBuffer;
}
ActiveCameraSampleInfo;

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;
    }
}

static size_t bestAligned(size_t a, size_t b)
{
    return (((b ^ (b - 1)) > (a ^ (a - 1))) ? b : a);
}

static void printDictionaryEntry (const void *key, const void *value, void *context) {
    NSLog(@"captureOutput %@ [%@]=%@", (__bridge NSString *)context, (__bridge NSString *)key, (__bridge NSString *)value);
}

- (void) dumpImageBuffer:(CVImageBufferRef)imageBuffer
{
    size_t i;
    
    OSType pixelFormatType = CVPixelBufferGetPixelFormatType(imageBuffer);

    size_t numberOfPlanes = CVPixelBufferGetPlaneCount(imageBuffer);
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
    size_t width = CVPixelBufferGetWidth(imageBuffer);
    size_t height = CVPixelBufferGetHeight(imageBuffer);
    void *dataPtr = CVPixelBufferGetBaseAddress(imageBuffer);
    size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
    NSLog(@"captureOutput %d %d %dx%d %d %p %d %s", CVPixelBufferIsPlanar(imageBuffer), (int)numberOfPlanes, (int)width, (int)height, (int)bytesPerRow, dataPtr, (int)dataSize, OSTypeGetName(pixelFormatTypeNameMap, pixelFormatType));

    switch (pixelFormatType) {
    case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
    case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: // NV12
        NSLog(@"captureOutput %x %u/%u %x %u/%u",
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrBiPlanar *)dataPtr)->componentInfoY.offset),
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrBiPlanar *)dataPtr)->componentInfoY.rowBytes),
            (int)(bestAligned((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)))) /
                CVPixelBufferGetHeightOfPlane(imageBuffer, 0), CVPixelBufferGetWidthOfPlane(imageBuffer, 0))),
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrBiPlanar *)dataPtr)->componentInfoCbCr.offset),
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrBiPlanar *)dataPtr)->componentInfoCbCr.rowBytes),
            (int)(bestAligned((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)))) /
                CVPixelBufferGetHeightOfPlane(imageBuffer, 0), CVPixelBufferGetWidthOfPlane(imageBuffer, 0))));
        break;
    case kCVPixelFormatType_420YpCbCr8Planar: // I420
    case kCVPixelFormatType_420YpCbCr8PlanarFullRange:
        NSLog(@"captureOutput %x %u/%u %x %u/%u %x %u/%u",
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoY.offset),
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoY.rowBytes),
            (int)(bestAligned((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)))) /
                CVPixelBufferGetHeightOfPlane(imageBuffer, 0), CVPixelBufferGetWidthOfPlane(imageBuffer, 0))),
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoCb.offset),
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoCb.rowBytes),
            (int)(bestAligned((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1)))) /
                CVPixelBufferGetHeightOfPlane(imageBuffer, 1), CVPixelBufferGetWidthOfPlane(imageBuffer, 1))),
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoCr.offset),
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo_YCbCrPlanar *)dataPtr)->componentInfoCr.rowBytes),
            (int)(bestAligned((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1)))) /
                CVPixelBufferGetHeightOfPlane(imageBuffer, 1), CVPixelBufferGetWidthOfPlane(imageBuffer, 1))));
        break;
    default:
        i = 0;
        while (i < numberOfPlanes) {
            NSLog(@"captureOutput [%d] %x %u/%u", (int)i,
                CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo *)dataPtr)->componentInfo[i].offset),
                CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo *)dataPtr)->componentInfo[i].rowBytes),
                (int)((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i+1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i)))) /
                    CVPixelBufferGetHeightOfPlane(imageBuffer, i)));
            i++;
        }
        NSLog(@"captureOutput [%d] %x %u/%u", (int)i,
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo *)dataPtr)->componentInfo[i].offset),
            CFSwapInt32BigToHost(((CVPlanarPixelBufferInfo *)dataPtr)->componentInfo[i].rowBytes),
            (int)((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i-1)))) /
                CVPixelBufferGetHeightOfPlane(imageBuffer, i-1)));
        break;
    }

    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];
    for (i = 0; i < numberOfPlanes; i++) {
        planeBaseAddress[i] = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i);
        planeWidth[i] = CVPixelBufferGetWidthOfPlane(imageBuffer, i);
        planeHeight[i] = CVPixelBufferGetHeightOfPlane(imageBuffer, i);
        planeBytesPerRow[i] = CVPixelBufferGetHeightOfPlane(imageBuffer, i);
        NSLog(@"captureOutput [%d] %dx%d %d %p", (int)i, (int)planeWidth[i], (int)planeHeight[i], (int)planeBytesPerRow[i], planeBaseAddress[i]);
    }

    CFDictionaryRef attachments = CVBufferGetAttachments(imageBuffer, kCVAttachmentMode_ShouldNotPropagate);
    CFDictionaryApplyFunction(attachments, printDictionaryEntry, (__bridge void *)(@"NotP"));
    attachments = CVBufferGetAttachments(imageBuffer, kCVAttachmentMode_ShouldPropagate);
    CFDictionaryApplyFunction(attachments, printDictionaryEntry, (__bridge void *)(@"Prop"));
}

- (void) onWebcamSample:(CVImageBufferRef)processBuffer timestamp:(int)timestamp
{
    size_t width, height;
    int length, stride, video_format;

    CVPixelBufferLockBaseAddress(processBuffer, 0);

    width = CVPixelBufferGetWidth(processBuffer);
    height = CVPixelBufferGetHeight(processBuffer);
    switch (CVPixelBufferGetPixelFormatType(processBuffer)) {
    default:
        video_format = FORMS_VIDEO_FORMAT_IYUV;
        break;
    }
    length = CVPixelBufferGetDataSize(processBuffer) - (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0))) - ((byte *)(CVPixelBufferGetBaseAddress(processBuffer)))) - sizeof(ActiveCameraSampleInfo);
    ((ActiveCameraSampleInfo *)(&((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))[length]))->activeCamera = (__bridge ActiveCameraSampleInfo *) self;
    ((ActiveCameraSampleInfo *)(&((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))[length]))->processBuffer = processBuffer;
    NSData *data = [[NSData alloc] initWithBytesNoCopy:CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0) length:length deallocator:^ void (void *bytes, NSUInteger length)
        {
//            ActiveCamera *activeCamera = (__bridge ActiveCamera *)(((ActiveCameraSampleInfo *)(&((byte *)bytes)[length]))->activeCamera);
            CFRelease(((ActiveCameraSampleInfo *)(&((byte *)bytes)[length]))->processBuffer);
        }];
    stride = (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
        CVPixelBufferGetHeightOfPlane(processBuffer, 0),

    CVPixelBufferUnlockBaseAddress(processBuffer, 0);

    self.ios_event.enqueue(FORMS_EVENT_WEBCAM_SAMPLE, self.uniqueID, timestamp,
        0, length, data,
        width,
        height,
        stride,
        video_format);
}

static CVReturn processBufferCreate(size_t width, size_t height, OSType processFormatType, CVPixelBufferRef *pixelBufferPtr)
{
    size_t i, 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(processFormatType) {
    case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
    case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
    case kCVPixelFormatType_420YpCbCr8Planar: // I420
    case kCVPixelFormatType_420YpCbCr8PlanarFullRange:
    case kCVPixelFormatType_422YpCbCr8_yuvs:
    case kCVPixelFormatType_422YpCbCr8:
    case kCVPixelFormatType_32ARGB:
    case kCVPixelFormatType_32RGBA:
    case kCVPixelFormatType_32BGRA:
        numberOfPlanes = 3;
        dataSize = IMAGE_BUFFER_ALIGN(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar)) + IMAGE_BUFFER_ALIGN(width) * height + (IMAGE_BUFFER_ALIGN(width) / 2) * height + sizeof(ActiveCameraSampleInfo);
        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;
        break;
    default:
        numberOfPlanes = 1;
        dataSize = IMAGE_BUFFER_ALIGN(numberOfPlanes * sizeof(CVPlanarPixelBufferInfo)) + IMAGE_BUFFER_ALIGN(width) * height + sizeof(ActiveCameraSampleInfo);
        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);
        }
        break;
    }

    CVReturn result = CVPixelBufferCreateWithPlanarBytes(nil, width, height, processFormatType, dataPtr, dataSize,
        numberOfPlanes, planeBaseAddress, planeWidth, planeHeight, planeBytesPerRow, &processBufferReleaseCallback, allocPtr, nil, pixelBufferPtr);
    if (result != kCVReturnSuccess) {
        return result;
    }

    // Apply required colorimetric attachments. 
    CVBufferSetAttachment(*pixelBufferPtr, kCVImageBufferColorPrimariesKey, kCVImageBufferColorPrimaries_ITU_R_709_2, kCVAttachmentMode_ShouldPropagate); 
    CVBufferSetAttachment(*pixelBufferPtr, kCVImageBufferTransferFunctionKey, kCVImageBufferTransferFunction_ITU_R_709_2, kCVAttachmentMode_ShouldPropagate); 
    CVBufferSetAttachment(*pixelBufferPtr, kCVImageBufferYCbCrMatrixKey, kCVImageBufferYCbCrMatrix_ITU_R_601_4, kCVAttachmentMode_ShouldPropagate); 

    return kCVReturnSuccess;
}

- (void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    size_t i;

    if ((self.processUsedPos >= self.processBuffers.count) || (CFGetRetainCount((__bridge CVImageBufferRef)[self.processBuffers objectAtIndex:self.processUsedPos]) == 1)) {
        // Get the CV Image buffer
/*
        CMVideoFormatDescriptionRef format = (CMVideoFormatDescriptionRef)(CMSampleBufferGetFormatDescription(sampleBuffer));
        CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(format);
        NSLog(@"captureOutput %dx%d", (int)(dimensions.width), (int)(dimensions.height));
*/
        CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

        CVPixelBufferLockBaseAddress(imageBuffer, 0);

        size_t width = ((self.rotation == 90) || (self.rotation == 270)) ? CVPixelBufferGetHeightOfPlane(imageBuffer, 0) : CVPixelBufferGetWidthOfPlane(imageBuffer, 0);
        size_t height = ((self.rotation == 90) || (self.rotation == 270)) ? CVPixelBufferGetWidthOfPlane(imageBuffer, 0) : CVPixelBufferGetHeightOfPlane(imageBuffer, 0);
        if (self.preferredWidth * 9 == self.preferredHeight * 16) {
            width &= ~((16 * 4) - 1);
            height = (width >> 4) * 9;
        }
        else if (self.preferredWidth * 3 == self.preferredHeight * 4) {
            width &= ~((4 * 16) - 1);
            height = (width >> 2) * 3;
        }
        else {
            if ((self.rotation == 90) || (self.rotation == 270)) {
                height /= 2;
            }
        }
        OSType pixelFormatType = CVPixelBufferGetPixelFormatType(imageBuffer);
        OSType processFormatType = (pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) ? kCVPixelFormatType_420YpCbCr8PlanarFullRange :
            ((pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) ? kCVPixelFormatType_420YpCbCr8Planar : pixelFormatType);

        if (self.processUsedPos >= self.processBuffers.count) {
            CVPixelBufferRef pixelBuffer;
            CVReturn result = processBufferCreate(width, height, processFormatType, &pixelBuffer);
            if (result != kCVReturnSuccess) {
                NSLog(@"CVPixelBufferCreateWithPlanarBytes %d", result);
            }
            [self.processBuffers addObject:CFBridgingRelease(pixelBuffer)];
        }

        CVImageBufferRef processBuffer = (__bridge CVImageBufferRef)[self.processBuffers objectAtIndex:self.processUsedPos];
        self.processUsedPos = (self.processUsedPos < PROCESS_SAMPLE_BUFFERS-1) ? self.processUsedPos + 1 : 0;

        CVPixelBufferLockBaseAddress(processBuffer, 0);

        if ((processFormatType != CVPixelBufferGetPixelFormatType(processBuffer)) || (width != CVPixelBufferGetWidthOfPlane(processBuffer, 0)) || (height != CVPixelBufferGetHeightOfPlane(processBuffer, 0))) {
            CVPixelBufferUnlockBaseAddress(processBuffer, 0);

            CVPixelBufferRef pixelBuffer;
            CVReturn result = processBufferCreate(width, height, processFormatType, &pixelBuffer);
            if (result != kCVReturnSuccess) {
                NSLog(@"CVPixelBufferCreateWithPlanarBytes %d", result);
            }
            [self.processBuffers replaceObjectAtIndex:((self.processUsedPos != 0) ? self.processUsedPos - 1 : PROCESS_SAMPLE_BUFFERS-1) withObject:CFBridgingRelease(pixelBuffer)];
            processBuffer = (CVImageBufferRef) pixelBuffer;

            CVPixelBufferLockBaseAddress(processBuffer, 0);
        }

//        [self dumpImageBuffer:imageBuffer];
//        [self dumpImageBuffer:processBuffer];

        if (self.blankOut) {
            switch (CVPixelBufferGetPixelFormatType(imageBuffer)) {
            case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
            case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: // NV12
            case kCVPixelFormatType_420YpCbCr8Planar: // I420
            case kCVPixelFormatType_420YpCbCr8PlanarFullRange:
            case kCVPixelFormatType_422YpCbCr8_yuvs:
            case kCVPixelFormatType_422YpCbCr8:
            case kCVPixelFormatType_32ARGB:
            case kCVPixelFormatType_32RGBA:
            case kCVPixelFormatType_32BGRA:
                memset(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0), 0x00,
                    ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0))));
                memset(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1), 0x80,
                    ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))));
                memset(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2), 0x80,
                    ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))));
                break;
            default:
                i = 0;
                while (i < CVPixelBufferGetPlaneCount(imageBuffer) - 1) {
                    memset(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i), 0x00,
                        ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i+1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i))));
                    i++;
                }
                memset(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i), 0x00,
                    ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i-1))));
                break;
            }
        }
        else {
            switch (CVPixelBufferGetPixelFormatType(imageBuffer)) {
            case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
            case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: // NV12
                rotateCopyByte(CVPixelBufferGetWidthOfPlane(imageBuffer, 0),
                    CVPixelBufferGetHeightOfPlane(imageBuffer, 0),
                    bestAligned((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(imageBuffer, 0), CVPixelBufferGetWidthOfPlane(imageBuffer, 0)),
                    CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0),
                    CVPixelBufferGetWidthOfPlane(processBuffer, 0),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 0),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 0),
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0),
                    self.rotation);
                rotateCopySplit(CVPixelBufferGetWidthOfPlane(imageBuffer, 1),
                    CVPixelBufferGetHeightOfPlane(imageBuffer, 1),
                    bestAligned(((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(imageBuffer, 0)) / sizeof(word), CVPixelBufferGetWidthOfPlane(imageBuffer, 0) / sizeof(word)),
                    CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1),
                    CVPixelBufferGetWidthOfPlane(processBuffer, 1),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 1),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 1),
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1),
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2),
                    self.rotation);
                break;
            case kCVPixelFormatType_420YpCbCr8Planar: // I420
            case kCVPixelFormatType_420YpCbCr8PlanarFullRange:
                rotateCopyByte(CVPixelBufferGetWidthOfPlane(imageBuffer, 0),
                    CVPixelBufferGetHeightOfPlane(imageBuffer, 0),
                    bestAligned((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(imageBuffer, 0), CVPixelBufferGetWidthOfPlane(imageBuffer, 0)),
                    CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0),
                    CVPixelBufferGetWidthOfPlane(processBuffer, 0),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 0),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 0),
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0),
                    self.rotation);
                rotateCopyByte(CVPixelBufferGetWidthOfPlane(imageBuffer, 1),
                    CVPixelBufferGetHeightOfPlane(imageBuffer, 1),
                    bestAligned((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1)))) /
                        CVPixelBufferGetHeightOfPlane(imageBuffer, 1), CVPixelBufferGetWidthOfPlane(imageBuffer, 1)),
                    CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1),
                    CVPixelBufferGetWidthOfPlane(processBuffer, 1),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 1),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 1),
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1),
                    self.rotation);
                rotateCopyByte(CVPixelBufferGetWidthOfPlane(imageBuffer, 2),
                    CVPixelBufferGetHeightOfPlane(imageBuffer, 2),
                    bestAligned((((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1)))) /
                        CVPixelBufferGetHeightOfPlane(imageBuffer, 1), CVPixelBufferGetWidthOfPlane(imageBuffer, 1)),
                    CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 2),
                    CVPixelBufferGetWidthOfPlane(processBuffer, 2),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 2),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 1),
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2),
                    self.rotation);
                break;
            case kCVPixelFormatType_422YpCbCr8_yuvs:
                IVideoFormatConversionYUY2ToYUV(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 0),
                    ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0))) + ((CVPixelBufferGetHeightOfPlane(imageBuffer, 0) - CVPixelBufferGetHeightOfPlane(processBuffer, 0)) / 2) * CVPixelBufferGetWidthOfPlane(imageBuffer, 0) * sizeof(word),
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0) << 1,
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 0));
                break;
            case kCVPixelFormatType_32ARGB:
                IVideoFormatConversionARGBToYUV(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 0),
                    ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0))) + ((CVPixelBufferGetHeightOfPlane(imageBuffer, 0) - CVPixelBufferGetHeightOfPlane(processBuffer, 0)) / 2) * CVPixelBufferGetWidthOfPlane(imageBuffer, 0) * sizeof(dword),
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0) << 2,
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 0));
                break;
            case kCVPixelFormatType_32RGBA:
                IVideoFormatConversionRGBAToYUV(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 0),
                    ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0))) + ((CVPixelBufferGetHeightOfPlane(imageBuffer, 0) - CVPixelBufferGetHeightOfPlane(processBuffer, 0)) / 2) * CVPixelBufferGetWidthOfPlane(imageBuffer, 0) * sizeof(dword),
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0) << 2,
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 0));
                break;
            case kCVPixelFormatType_32BGRA:
#if TARGET_OS_IPHONE
                IVideoFormatConversionRGB32ToYUV(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 0),
                    ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0))) + ((CVPixelBufferGetHeightOfPlane(imageBuffer, 0) - CVPixelBufferGetHeightOfPlane(processBuffer, 0)) / 2) * CVPixelBufferGetWidthOfPlane(imageBuffer, 0) * sizeof(dword),
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0) << 2,
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 0));
#else
                // libyuv library
                ARGBToI420(((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0))) + ((CVPixelBufferGetHeightOfPlane(imageBuffer, 0) - CVPixelBufferGetHeightOfPlane(processBuffer, 0)) / 2) * CVPixelBufferGetWidthOfPlane(imageBuffer, 0) * sizeof(dword),
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0) << 2,
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 0)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 0),
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 1),
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 2))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, 1)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, 1),
                    CVPixelBufferGetWidthOfPlane(imageBuffer, 0),
                    CVPixelBufferGetHeightOfPlane(processBuffer, 0));
#endif
                break;
            default:
                i = 0;
                while (i < CVPixelBufferGetPlaneCount(imageBuffer) - 1) {
                    rotateCopyByte(CVPixelBufferGetWidthOfPlane(imageBuffer, i),
                        CVPixelBufferGetHeightOfPlane(imageBuffer, i),
                        (((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i+1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i)))) /
                            CVPixelBufferGetHeightOfPlane(imageBuffer, i),
                        CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i),
                        CVPixelBufferGetWidthOfPlane(processBuffer, i),
                        CVPixelBufferGetHeightOfPlane(processBuffer, i),
                        (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i+1))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i)))) /
                            CVPixelBufferGetHeightOfPlane(processBuffer, i),
                        CVPixelBufferGetBaseAddressOfPlane(processBuffer, i),
                        self.rotation);
                    i++;
                }
                rotateCopyByte(CVPixelBufferGetWidthOfPlane(imageBuffer, i),
                    CVPixelBufferGetHeightOfPlane(imageBuffer, i),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i-1)))) /
                        CVPixelBufferGetHeightOfPlane(imageBuffer, i-1),
                    CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i),
                    CVPixelBufferGetWidthOfPlane(processBuffer, i),
                    CVPixelBufferGetHeightOfPlane(processBuffer, i),
                    (((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i))) - ((byte *)(CVPixelBufferGetBaseAddressOfPlane(processBuffer, i-1)))) /
                        CVPixelBufferGetHeightOfPlane(processBuffer, i-1),
                    CVPixelBufferGetBaseAddressOfPlane(processBuffer, i),
                    self.rotation);
                break;
            }
        }
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
        CVPixelBufferUnlockBaseAddress(processBuffer, 0);
        CFRetain(processBuffer);

        CMTime presentationTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);

        dword d = (((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;
            }
        }
        self.rtpTimestamp += d;
        self.presentationTimestamp = presentationTimestamp.value;
        dword timestamp = self.rtpTimestamp;

        dispatch_async(dispatch_get_main_queue(), ^
            { [self onWebcamSample:processBuffer timestamp:timestamp]; });
    }
    else {
        NSLog(@"CaptureOutput:no process buffer");
    }
}

- (void) captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    NSLog(@"CaptureOutput:didDropSampleBuffer %@",
        (__bridge NSString *)(CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_DroppedFrameReason, nil)));
}

#pragma mark Session Management

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

- (bool) start
{
    int i, j, l, w, bestWidth, desiredFormat, lowerRateRange, upperRateRange;
    CMTime upperFrameDuration, lowerFrameDuration, frameDuration;
    char traceLine[1024];
    CaptureSessionPreset *c;
    NSError *error = nil;

    // Create the AVCaptureSession.
    if (!self.deviceInput) {
        self.session = [[AVCaptureSession alloc] init];

        [self.session beginConfiguration];

        desiredFormat = -1;
        bestWidth = 0;
        self.rotation = self.nextRotation;
        w = (self.preferredWidth != 0) ? self.preferredWidth : (((self.rotation == 90) || (self.rotation == 270)) ? (self.hdVideo ? 720 : 480) : (self.hdVideo ? 1280 : 640));
        for (i = 0; i < self.captureSessionPresets.count; i++) {
            c = (CaptureSessionPreset *)[self.captureSessionPresets objectAtIndex:i];
            if ([self.session canSetSessionPreset:c.preset]) {
                if ((self.rotation == 90) || (self.rotation == 270)) {
                    if ((desiredFormat == -1) || (((c.height >= w) ? c.height - w : w - c.height) < ((bestWidth >= w) ? bestWidth - w : w - bestWidth))) {
                        desiredFormat = i;
                        bestWidth = c.height;
                    }
                }
                else {
                    if ((desiredFormat == -1) || (((c.width >= w) ? c.width - w : w - c.width) < ((bestWidth >= w) ? bestWidth - w : w - bestWidth))) {
                        desiredFormat = i;
                        bestWidth = c.width;
                    }
                }
            }
        }
        self.session.sessionPreset = ((CaptureSessionPreset *)[self.captureSessionPresets objectAtIndex:desiredFormat]).preset;
        NSLog(@"CaptureSessionPreset %d %d %d %d %dx%d", self.rotation, self.preferredWidth, w, bestWidth,
            ((CaptureSessionPreset *)[self.captureSessionPresets objectAtIndex:desiredFormat]).width,
            ((CaptureSessionPreset *)[self.captureSessionPresets objectAtIndex:desiredFormat]).height);
        if (self.preferredWidth == 0) {
            w = bestWidth;
        }
        
        AVCaptureDevice *videoDevice = [AVCaptureDevice deviceWithUniqueID:self.uniqueID];
        desiredFormat = -1;
        bestWidth = 0;
        for (i = 0; i < videoDevice.formats.count; i++) {
            AVCaptureDeviceFormat *format = [videoDevice.formats objectAtIndex:i];
            if ([format.mediaType isEqualToString:AVMediaTypeVideo]) {
                CMFormatDescriptionRef formatDescription = format.formatDescription;
                CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
                if ((self.rotation == 90) || (self.rotation == 270)) {
                    if ((desiredFormat == -1) || (((dimensions.height >= w) ? dimensions.height - w : w - dimensions.height) < ((bestWidth >= w) ? bestWidth - w : w - bestWidth))) {
                        desiredFormat = i;
                        bestWidth = dimensions.height;
                    }
                }
                else {
                    if ((desiredFormat == -1) || (((dimensions.width >= w) ? dimensions.width - w : w - dimensions.width) < ((bestWidth >= w) ? bestWidth - w : w - bestWidth))) {
                        desiredFormat = i;
                        bestWidth = dimensions.width;
                    }
                }
                l = 0;
                for (j = 0; j < format.videoSupportedFrameRateRanges.count; j++) {
                    l += _sprintf(&traceLine[l], " %d-%d",
                        (int)((((AVFrameRateRange *)[format.videoSupportedFrameRateRanges objectAtIndex:j]).minFrameDuration.timescale + ((AVFrameRateRange *)[format.videoSupportedFrameRateRanges objectAtIndex:j]).minFrameDuration.value / 2) /
                            ((AVFrameRateRange *)[format.videoSupportedFrameRateRanges objectAtIndex:j]).minFrameDuration.value),
                        (int)((((AVFrameRateRange *)[format.videoSupportedFrameRateRanges objectAtIndex:j]).maxFrameDuration.timescale + ((AVFrameRateRange *)[format.videoSupportedFrameRateRanges objectAtIndex:j]).maxFrameDuration.value / 2) /
                            ((AVFrameRateRange *)[format.videoSupportedFrameRateRanges objectAtIndex:j]).maxFrameDuration.value));
                }
                NSLog(@"SupportedPreviewSizes[%d] %dx%d %d%s", i, dimensions.width, dimensions.height, desiredFormat, traceLine);
            }
        }

        // Add video input.
        self.deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
        if (self.deviceInput == nil) {
            NSLog(@"Could not create video device input: %@", error);
            [self.session commitConfiguration];
            return false;
        }
        if (![self.session canAddInput:self.deviceInput]) {
            NSLog(@"Could not add video device input to the session");
            [self.session removeInput:self.deviceInput];
            self.deviceInput = nil;
            [self.session commitConfiguration];
            return false;
        }
        [self.session addInput:self.deviceInput];

        self.dataOutput = [[AVCaptureVideoDataOutput alloc] init];
        [self.session addOutput:self.dataOutput];
        for (i = 0; i < self.dataOutput.availableVideoCVPixelFormatTypes.count; i++) {
            NSLog(@"SupportedPreviewFormats[%d] %s", i, OSTypeGetName(pixelFormatTypeNameMap, ((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue));
        }
/*
        for (i = 0; i < self.dataOutput.availableVideoCodecTypes.count; i++) {
            NSLog(@"SupportedCodec[%d] %@", i, [self.dataOutput.availableVideoCodecTypes objectAtIndex:i]);
        }
*/
        
        AVCaptureDeviceFormat *format = [videoDevice.formats objectAtIndex:desiredFormat];
#if TARGET_OS_IPHONE
        i = 0;
        while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
         && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_420YpCbCr8PlanarFullRange)) {
            i++;
        }
        if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
            while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
             && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)) {
                i++;
            }
            if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
                while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
                 && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_420YpCbCr8Planar)) {
                    i++;
                }
                if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
                    while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
                     && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)) {
                        i++;
                    }
                }
            }
        }
        self.dataOutput.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : (i < self.dataOutput.availableVideoCVPixelFormatTypes.count) ?
            [self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i] : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
        self.dataOutput.alwaysDiscardsLateVideoFrames = true;
#else
        i = 0;
        while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
         && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_420YpCbCr8PlanarFullRange)) {
            i++;
        }
        if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
            i = 0;
            while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
             && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)) {
                i++;
            }
            if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
                i = 0;
                while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
                 && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_420YpCbCr8Planar)) {
                    i++;
                }
                if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
                    i = 0;
                    while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
                     && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)) {
                        i++;
                    }
                    if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
                        i = 0;
                        while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
                         && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_422YpCbCr8_yuvs)) {
                            i++;
                        }
                        if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
                            i = 0;
                            while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
                             && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_32BGRA)) {
                                i++;
                            }
                            if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
                                i = 0;
                                while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
                                 && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_32RGBA)) {
                                    i++;
                                }
                                if (i == self.dataOutput.availableVideoCVPixelFormatTypes.count) {
                                    i = 0;
                                    while ((i < self.dataOutput.availableVideoCVPixelFormatTypes.count)
                                     && (((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue != kCVPixelFormatType_32ABGR)) {
                                        i++;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        CMFormatDescriptionRef formatDescription = format.formatDescription;
        CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
        NSDictionary *outputSettings;
        if(i < self.dataOutput.availableVideoCVPixelFormatTypes.count) {
            outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithDouble:dimensions.width], (id)kCVPixelBufferWidthKey,
                                [NSNumber numberWithDouble:dimensions.height], (id)kCVPixelBufferHeightKey,
                                [NSNumber numberWithInt:((NSNumber *)[self.dataOutput.availableVideoCVPixelFormatTypes objectAtIndex:i]).intValue], (id)kCVPixelBufferPixelFormatTypeKey,
                                nil];
        }
        else {
            outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithDouble:dimensions.width], (id)kCVPixelBufferWidthKey,
                                [NSNumber numberWithDouble:dimensions.height], (id)kCVPixelBufferHeightKey,
                                nil];
        }
        [self.dataOutput setVideoSettings:outputSettings];
#endif
        [self.dataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];

        lowerRateRange = -1;
        upperRateRange = -1;
        lowerFrameDuration = CMTimeMake(0, 1);
        upperFrameDuration = CMTimeMake(1, 1);
        i = 0;
        while ((i < format.videoSupportedFrameRateRanges.count) && (CMTimeCompare(upperFrameDuration, CMTimeMake(1, 10)) != 0)) {
            AVFrameRateRange *r = [format.videoSupportedFrameRateRanges objectAtIndex:i];
            NSLog(@"SupportedPreviewFrameRates[%d] %d/%ld-%d/%ld", i, r.maxFrameDuration.timescale, (long)(r.maxFrameDuration.value), r.minFrameDuration.timescale, (long)(r.minFrameDuration.value));
            if ((CMTimeCompare(r.minFrameDuration, CMTimeMake(1, 10)) <= 0)
             && (CMTimeCompare(r.maxFrameDuration, CMTimeMake(1, 10)) >= 0)) {
                lowerRateRange = i;
                upperRateRange = i;
                lowerFrameDuration = CMTimeMake(1, 10);
                upperFrameDuration = CMTimeMake(1, 10);
            }
            else if ((CMTimeCompare(r.minFrameDuration, CMTimeMake(1, 10)) >= 0)
             && ((upperRateRange == -1)
              || (CMTimeCompare(r.minFrameDuration, upperFrameDuration) < 0))) {
                upperRateRange = i;
                upperFrameDuration = r.minFrameDuration;
            }
            else if ((CMTimeCompare(r.maxFrameDuration, CMTimeMake(1, 10)) <= 0)
             && ((lowerRateRange == -1)
              || (CMTimeCompare(r.maxFrameDuration, lowerFrameDuration) > 0))) {
                lowerRateRange = i;
                lowerFrameDuration = r.maxFrameDuration;
            }
            i++;
        }
        frameDuration = lowerFrameDuration;
        if ((lowerRateRange != -1) || (upperRateRange != -1)) {
            if (upperRateRange != -1) {
                frameDuration = upperFrameDuration;
            }
            if ([videoDevice lockForConfiguration:nil]) {
                videoDevice.activeFormat = format;
                videoDevice.activeVideoMinFrameDuration = frameDuration;
                videoDevice.activeVideoMaxFrameDuration = frameDuration;
                [videoDevice unlockForConfiguration];
            }
        }

        [self.session commitConfiguration];

        NSLog(@"Camera parameters {width=%d, height=%d, frameRate=%d,%d,%d/%ld, image-format=%s, rotation=%d}",
              ((NSNumber *)(self.dataOutput.videoSettings[(NSString *)kCVPixelBufferWidthKey])).intValue,
              ((NSNumber *)(self.dataOutput.videoSettings[(NSString *)kCVPixelBufferHeightKey])).intValue,
            lowerRateRange, upperRateRange, frameDuration.timescale, (long)(frameDuration.value),
            OSTypeGetName(pixelFormatTypeNameMap, ((NSNumber *)(self.dataOutput.videoSettings[(NSString *)kCVPixelBufferPixelFormatTypeKey])).intValue),
            self.rotation);

        if (self.processBuffers != nil) {
            [self.processBuffers removeAllObjects];
        }
        else {
            self.processBuffers = [[NSMutableArray alloc] init];
        }
        self.processUsedPos = 0;

        [self.session startRunning];

        [self videoAddObservers];
    }
    return true;
}

- (void) stop
{
    if (self.deviceInput) {
        [self.session beginConfiguration];
        
        [self.session removeInput:self.deviceInput];

        [self.session removeOutput:self.dataOutput];
        [self.dataOutput setSampleBufferDelegate:nil queue:nil];

        [self.session commitConfiguration];

        [self.session stopRunning];

        [self videoRemoveObservers];

        self.deviceInput = nil;
        self.dataOutput = nil;
    }
}

- (void) requestFullIntra
{
    (self.fullIntraRequest)++;
}

- (void) blankOutVideo:(bool)blank
{
    self.blankOut = blank;
}

- (void) changePreviewCoder:(int)coder
{
    self.previewCoder = coder;
}

- (void) changeWebcamCoder:(int)coder
{
    self.webcamCoder = coder;
}

@end
