//
//  CaptureView.m
//  AudioCapture
//
//  Created by magewell on 2025/9/25.
//  Copyright © 2025 magewell. All rights reserved.
//


#import "MWCaptureView.h"
#import <mach/mach.h>
#import <mach/mach_time.h>
#import <mach-o/dyld.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <queue>
#import "APITest.h"
#import "MWCaptureUtil.h"
#import "CaptureFormatInfo.h"

static void releasePixelBufferFrame(PixelBufferFrame *frame, void *param) {
    MWCaptureView *viewCtrl = (__bridge MWCaptureView*)param;
    
    if (frame && frame->pixelBuffer) {
        [[viewCtrl capturePixelFrameLock] lock];
        ((std::queue<CVPixelBufferRef> *)[viewCtrl capturePixelFrameQueue])->push(frame->pixelBuffer);
        [[viewCtrl capturePixelFrameLock] unlock];
    }
}



static void* onVideoCaptureThreadProc(void *param) {
    MWCaptureView *viewCtrl = (__bridge MWCaptureView*)param;
    
    [viewCtrl onVideoCaptureProc];
    
    return NULL;
}

static void* onAudioCaptureThreadProc(void *param) {
    MWCaptureView *viewCtrl = (__bridge MWCaptureView*)param;
    
    [viewCtrl onAudioCaptureProc];
    
    return NULL;
}

static void* onVideoEncodeThreadProc(void *param) {
    MWCaptureView *viewCtrl = (__bridge MWCaptureView*)param;
    
    [viewCtrl onVideoEncodeProc];
    
    return NULL;
}

static void onVTBoxEncodeCallback(void * user_ptr, const uint8_t* nalus[], uint32_t nalu_lens[],uint32_t nalu_num, mw_venc_frame_info_t *p_frame_info) {
    MWCaptureView *viewCtrl = (__bridge MWCaptureView*)user_ptr;
    
    [viewCtrl onVTBoxEncodeFrame:nalus nalu_lens:nalu_lens nalu_num:nalu_num frame_info:p_frame_info];
}

static void onAacEncodeCallback(uint8_t *frame, size_t frame_size, uint64_t timestamp, uint32_t seq, void *param) {
    MWCaptureView *viewCtrl = (__bridge MWCaptureView*)param;
    [viewCtrl onAacEncodeFrame:frame size:frame_size timestamp:timestamp];
}

#define MAX_VIDEO_ENCODE_BUFFER_FRAMES 6
#define MAX_VIDEO_CAPTURE_BUFFER_FRAMES (MAX_VIDEO_ENCODE_BUFFER_FRAMES + 2)

@interface MWCaptureView ()
{
    
}
@end

@implementation MWCaptureView

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    
    // Drawing code here.
}

- (id)initWithFrame:(NSRect)frameRect lineNumber:(int)lineNumber
{
    if (self = [super initWithFrame:frameRect]) {
        
        self.lineNumber = lineNumber;
        self.statusLabel = [[NSTextField alloc] initWithFrame:CGRectMake(0, 0, frameRect.size.width, 20)];
        self.statusLabel.textColor = [NSColor blackColor];
        self.statusLabel.layer.backgroundColor = [NSColor whiteColor].CGColor;
        [self addSubview:self.statusLabel];
        self.statusLabel.enabled = NO;
        self.statusLabel.translatesAutoresizingMaskIntoConstraints = NO;

        NSLayoutConstraint *constraintWidth = [NSLayoutConstraint constraintWithItem:self.statusLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
        [self addConstraint:constraintWidth];
        
        self.viewEnable = 1;
        [self performSelector:@selector(createPreviewLayer) withObject:nil afterDelay:0];
        
        NSMenu *mainMenu = [NSApp mainMenu];
        
        NSMenuItem *mainMenuDeviceItem = [[NSMenuItem alloc] init];
        [mainMenu addItem:mainMenuDeviceItem];
        
        self.deviceMenu = [[NSMenu alloc] initWithTitle:[NSString stringWithFormat:@"Device(Line %d)",lineNumber]];
        [mainMenuDeviceItem setSubmenu:self.deviceMenu];
        
        NSMenuItem *mainMenuResolutionsItem = [[NSMenuItem alloc] init];
        [mainMenu addItem:mainMenuResolutionsItem];
        self.resolutionsMenu = [[NSMenu alloc] initWithTitle:@"Resolution"];
        [mainMenuResolutionsItem setSubmenu:self.resolutionsMenu];
        
        NSMenuItem *mainFPSItem = [[NSMenuItem alloc] init];
        [mainMenu addItem:mainFPSItem];
        self.fpsMenu = [[NSMenu alloc] initWithTitle:@"FPS"];
        [mainFPSItem setSubmenu:self.fpsMenu];
        
        NSMenuItem *mainFourccItem = [[NSMenuItem alloc] init];
        [mainMenu addItem:mainFourccItem];
        self.fourccMenu = [[NSMenu alloc] initWithTitle:@"Color Format"];
        [mainFourccItem setSubmenu:self.fourccMenu];
        
        [self initParameters];
        
        [self initDeviceMenu];
        
        if (self.deviceMenu.numberOfItems > 0) {
            [self performSelector:@selector(switchDevice:) withObject:[self.deviceMenu itemAtIndex:0] afterDelay:0];
        }
        
        self.statusTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target: self selector:@selector(statusTimerProc:) userInfo:nil repeats:YES];
    }
    return self;
}


- (void)initParameters
{
    self.width         = 1920;
    self.height        = 1080;
    self.frameDuration = 166667;
    self.fourcc        = MWFOURCC_NV12;
//    self.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
    self.pixelFormat = fourcc_to_CVPixelFormat(self.fourcc);

    self.samplerate    = 48000;
    self.audioChannels = 2;
    self.bitsPerSample = 16;

    self.audioRender   = NULL;

    self.hChannel             = NULL;
    self.capturePixelFrameLock = [[NSLock alloc] init];
    self.capturePixelFrameQueue = new std::queue<CVPixelBufferRef>();
    self.running              = false;
    self.videoCaptureThreadId = 0;
    self.audioCaptureThreadId = 0;
    self.captureFps      = 0.0;
    self.lastTimestamp   = 0;
    self.videoFrameCount = 0;
    
    self.bitRate = 4 * 1000;
    self.h265Enable = false;
    self.vtEnc = NULL;
    self.vtEncLock = [[NSLock alloc] init];
    self.encPixelFrameQueue = new std::queue<std::shared_ptr<PixelBufferFrame> >();
    self.encoding = false;
    self.videoEncodeThreadId = 0;
    self.encodeFps           = 0.0;
    self.lastEncodeTimestamp = 0;
    self.encodeFrameCount    = 0;
    
    self.aacEnc = NULL;
    self.aacEncLock = [[NSLock alloc] init];
    
    self.mp4Record = NULL;
    self.gotVideoTrack = false;
    self.gotAudioTrack = false;
    self.videoBufferFrame = NULL;
    self.videoBufferFrameSize = 0;
    self.startRecordTime = 0;
}
#pragma mark -
- (void)initDeviceMenu {
    int count = MWGetChannelCount();
    for (int i = 0; i < count; i++) {
        NSString *itemTitle = nil;
        
        MWCAP_CHANNEL_INFO mci = { 0 };
        MWGetChannelInfoByIndex(i, &mci);
        if (strcmp(mci.szFamilyName, "Pro Capture") == 0) {
            itemTitle = [NSString stringWithFormat:@"%02d-%d %s", mci.byBoardIndex, mci.byChannelIndex, mci.szProductName];
            NSMenuItem *deviceItem = [self.deviceMenu addItemWithTitle:itemTitle action:@selector(switchDevice:) keyEquivalent:@""];
            [deviceItem setTag:i];
            [deviceItem setTarget:self];
        }
    }
}

- (void)initResolutionsMenu:(HCHANNEL) hChannel
{
    [self.resolutionsMenu removeAllItems];
    self.suportCaptureFormatList = [[NSMutableArray alloc] init];
    if (!hChannel) {
        return;
    }
    BOOL resolutionFound = NO;
    MWCAP_VIDEO_RESOLUTION resolutions[256] = { 0 };
    MWCAP_VIDEO_RESOLUTION_LIST resList = { sizeof(resolutions)/sizeof(resolutions[0]), resolutions};
    MWGetVideoCaptureSupportListResolution(hChannel, &resList);
    
    NSMenuItem *autoItem = [self.resolutionsMenu addItemWithTitle:@"continuousAutoSwitch" action:@selector(switchCaptureFormat:) keyEquivalent:@""];
    [autoItem setTarget:self];
    [autoItem setTag:-1];
    
    for(int i=0; i<resList.nListSize; i++) {
        printf("INFO: Line%d support resolution[%d x %d]\n", self.lineNumber, resolutions[i].cx, resolutions[i].cy);
        CaptureFormatInfo *info = [[CaptureFormatInfo alloc] initWithWidth:resolutions[i].cx height:resolutions[i].cy];
        [self.suportCaptureFormatList addObject:info];
        NSString *itemTitle = [NSString stringWithFormat:@"%d * %d", resolutions[i].cx, resolutions[i].cy];
        NSMenuItem *resolutionItem = [self.resolutionsMenu addItemWithTitle:itemTitle action:@selector(switchCaptureFormat:) keyEquivalent:@""];
        [resolutionItem setTarget:self];
        [resolutionItem setTag:i];
        if (resolutions[i].cx == self.width && resolutions[i].cy == self.height) {
            [resolutionItem setState:NSControlStateValueOn];
            self.selectedResolutionItem = resolutionItem;
            resolutionFound = YES;
        } else {
            [resolutionItem setState:NSControlStateValueOff];
        }
    }
}

- (void)initFpsMenu
{
    [self.fpsMenu removeAllItems];
    for(int i=0; i<3; i++) {
        int fps = 15*pow(2, i);
        NSString *itemTitle = [NSString stringWithFormat:@"%d", fps];
        NSMenuItem *fpsItem = [self.fpsMenu addItemWithTitle:itemTitle action:@selector(switchFPS:) keyEquivalent:@""];
        [fpsItem setTarget:self];
        int frameDuration = (int32_t)(round)(10000000/(float)fps);
        [fpsItem setTag:frameDuration];

        if (self.frameDuration == frameDuration) {
            [fpsItem setState:NSControlStateValueOn];
            self.selectedFpsItem = fpsItem;
        } else {
            [fpsItem setState:NSControlStateValueOff];
        }
        printf("INFO: Line %d support FPS[%d]\n", self.lineNumber, fps);

    }
}

- (void)initColorFormatMenu:(HCHANNEL)hChannel
{
    if (!hChannel) {
        return;
    }
    [self.fourccMenu removeAllItems];
    DWORD *pColorFourcc = (DWORD *)malloc(sizeof(DWORD)*10);
    int nCount = 10;
    int ret = MWGetVideoCaptureSupportColorFormat(hChannel, pColorFourcc, &nCount);
    if (ret == MW_SUCCEEDED) {
        for (int i=0; i <nCount && i<10 ; i++) {
            DWORD colorFourcc = pColorFourcc[i];
            char strFourcc[5] = { 0 };
            *(uint32_t*)strFourcc = colorFourcc;
            NSString *itemTitle = [NSString stringWithFormat:@"%s", strFourcc];
            NSMenuItem *fourccItem = [self.fourccMenu addItemWithTitle:itemTitle action:@selector(switchFourcc:) keyEquivalent:@""];
            [fourccItem setTarget:self];
            [fourccItem setTag:colorFourcc];

            if (self.fourcc == colorFourcc) {
                [fourccItem setState:NSControlStateValueOn];
                self.selectedFourccItem= fourccItem;
            } else {
                [fourccItem setState:NSControlStateValueOff];
            }
            printf("INFO: Line %d support fourcc[%s]\n", self.lineNumber, strFourcc);
        }
    }
    free(pColorFourcc);
}

#pragma mark -
- (void)startCapture:(NSMenuItem *)deviceItem {
    //stop recording first
    [self stopCapture];
    
    int index = (int)[deviceItem tag];
    char wPath[256] = {0};
    MWGetDevicePath(index, wPath);
    self.hChannel = MWOpenChannelByPath(wPath);
    if (self.hChannel == NULL) {
        printf("ERROR: Open channel failed !\n");
        return;
    }
    
    self.inUseDevicePath = [NSString stringWithCString:wPath encoding:NSUTF8StringEncoding];
    self.running = true;
    if (!self.selectedDeviceItem || self.selectedDeviceItem != deviceItem) {
        //Audio Node menu
        [self initResolutionsMenu:self.hChannel];
        [self initFpsMenu];
        [self initColorFormatMenu:self.hChannel];
    }
    
    [self modifyAudioConfig];
    
    MWAudioRenderCreate(self.samplerate, self.audioChannels, self.bitsPerSample, &_audioRender);
    MWAudioRenderStart(self.audioRender);
    
    if (self.videoCaptureThreadId == 0) {
        pthread_t tid = 0;
        if (0 == pthread_create(&tid, NULL, onVideoCaptureThreadProc,  (__bridge void*)(self))) {
            self.videoCaptureThreadId = tid;
        }
    }
    
        
    if (self.audioCaptureThreadId == 0) {
        pthread_t tid = 0;
        if (0 == pthread_create(&tid, NULL, onAudioCaptureThreadProc,  (__bridge void*)(self))) {
           self.audioCaptureThreadId = tid;
        }
    }

    self.startRecordItem.enabled = true;
    self.stopRecordItem.enabled = false;
}

- (void)stopCapture {
    self.running = false;
    if (self.audioCaptureThreadId) {
        
        pthread_join(self.audioCaptureThreadId, NULL);
        self.audioCaptureThreadId = 0;
    }
    
    if (self.audioRender) {
        MWAudioRenderStop(self.audioRender);
        MWAudioRenderDestroy(self.audioRender);
        self.audioRender = NULL;
    }
    
    if (self.videoCaptureThreadId) {
        pthread_join(self.videoCaptureThreadId, NULL);
        self.videoCaptureThreadId = 0;
    }
    
    if (self.hChannel) {
        MWCloseChannel(self.hChannel);
        self.hChannel = NULL;
    }
    
    self.captureFps      = 0.0;
    self.lastTimestamp   = 0;
    self.videoFrameCount = 0;
    self.previousPts = 0;
    self.delayVideoFrameCount = 0;
    self.dropVideoFrameCount = 0;
}

- (void)startRecord:(NSString *)fileName {
    if (NULL == self.mp4Record) {
           self->_mp4Record = mw_mp4_open([fileName UTF8String]);
           
           if (NULL == self.videoBufferFrame) {
               self.videoBufferFrameSize = self.width * self.height * 3/2 + 512;
               self.videoBufferFrame = (uint8_t*) malloc(self.videoBufferFrameSize);
           }
           
           self.startRecordTime = getTimestamp();
       }
       
       {
           self.encoding = true;
           if (self.videoEncodeThreadId == 0) {
               pthread_t tid = 0;
               if (0 == pthread_create(&tid, NULL, onVideoEncodeThreadProc,  (__bridge void*)(self))) {
                   self.videoEncodeThreadId = tid;
               }
           }
           
           [self.vtEncLock lock];
           
           if (NULL == self.vtEnc) {
               printf("start video encode\n");
               
               mw_venc_param_t param = {
                   .code_type = self.h265Enable ? MW_VENC_CODE_TYPE_H265 : MW_VENC_CODE_TYPE_H264,
                   .fourcc = MW_VENC_FOURCC_NV12,
                   .targetusage = MW_VENC_TARGETUSAGE_BEST_SPEED,
                   .rate_control = {
                       .mode = MW_VENC_RATECONTROL_VBR,
                       .target_bitrate = self.bitRate,
                       .max_bitrate = 0
                   },
                   .width = (int32_t)self.width,
                   .height = (int32_t)self.height,
                   .fps = {
                       .num = (int32_t)(round)(10000000/(float)self.frameDuration),
                       .den = 1
                   },
                   .slice_num = 1,
                   .gop_pic_size = (int32_t)(round)(10000000/(float)self.frameDuration),
                   .gop_ref_size = 1,
                   .profile = self.h265Enable ? MW_VENC_PROFILE_H265_MAIN : MW_VENC_PROFILE_H264_BASELINE,
                   .level = MW_VENC_LEVEL_UNKNOWN,
                   .intel_async_depth = 0,
               };
               
               self.vtEnc = mw_venc_create(&param, NULL, onVTBoxEncodeCallback, (__bridge void*)self);
               if (NULL == self.vtEnc) {
                   NSAlert *alert = [[NSAlert alloc] init];
                   [alert setMessageText:@"AVCapture"];
                   [alert setInformativeText:@"Failed to create video encoder!"];
                   [alert setAlertStyle:NSAlertStyleWarning];
                   
                   if (alert) {
                       [alert runModal];
                   }
               }
               
               self.startRecordItem.enabled = !self.startRecordItem.enabled;
               self.stopRecordItem.enabled = !self.startRecordItem.enabled;
           }
           
           [self.vtEncLock unlock];
       }
       
       {
           [self.aacEncLock lock];
           
           if (NULL == self.aacEnc) {
               printf("start audio encode\n");
               
               self.aacEnc = new FAACEncoder();
               self.aacEnc->open(self.samplerate, self.audioChannels);
               self.aacEnc->setCallback(onAacEncodeCallback, (__bridge void*)self);
           }
           
           [self.aacEncLock unlock];
       }
}

- (void)stopRecord {
    self.encoding = false;
    if (self.videoEncodeThreadId) {
        pthread_join(self.videoEncodeThreadId, NULL);
        self.videoEncodeThreadId = 0;
    }
    
    [self.vtEncLock lock];
    if (self.vtEnc) {
        printf("stopping video encode\n");
        
        mw_venc_destory(self.vtEnc);
        self.vtEnc = NULL;
        
        printf("stopped video encode\n");
    }
    [self.vtEncLock unlock];
    
    [self.aacEncLock lock];
    if (self.aacEnc) {
        printf("stop audio encode\n");
        
        self.aacEnc->close();
        delete self.aacEnc;
        self.aacEnc = NULL;
    }
    [self.aacEncLock unlock];
    
    self.encodeFps           = 0.0;
    self.lastEncodeTimestamp = 0;
    self.encodeFrameCount    = 0;
    
    if (self.mp4Record) {
        mw_mp4_close(self.mp4Record);
        self.mp4Record = NULL;
        
        self.startRecordItem.enabled = !self.startRecordItem.enabled;
        self.stopRecordItem.enabled = !self.startRecordItem.enabled;
    }
    self.gotVideoTrack = false;
    self.gotAudioTrack = false;
    
    if (self.videoBufferFrame) {
        free(self.videoBufferFrame);
        self.videoBufferFrame = NULL;
    }
    self.videoBufferFrameSize = 0;
    self.startRecordTime = 0;
}
- (BOOL)isCapturing
{
    return YES;
}
- (BOOL)isRecording
{
    return self.encoding;
}

- (void)createPreviewLayer {
    if (!self.videoLayer) {
        self.videoLayer = [[AVSampleBufferDisplayLayer alloc] init];
        self.videoLayer.frame = CGRectMake(0, self.statusLabel.frame.size.height,
                                           self.frame.size.width, self.frame.size.height - self.statusLabel.frame.size.height);
        self.videoLayer.bounds = CGRectMake(0, self.statusLabel.frame.size.height,
                                            self.bounds.size.width, self.bounds.size.height - self.statusLabel.frame.size.height);
        self.videoLayer.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
        self.videoLayer.videoGravity = AVLayerVideoGravityResizeAspect;
        self.videoLayer.backgroundColor = [NSColor blackColor].CGColor;
        [[self layer] addSublayer:self.videoLayer];
        
//        self.window.delegate = self;
    }
}

- (void)destory
{
    
    [self stopRecord];
    [self stopCapture];
    if (self.statusTimer) {
        [self.statusTimer invalidate];
    }
    
    std::queue<std::shared_ptr<PixelBufferFrame> > *endQueue = (std::queue<std::shared_ptr<PixelBufferFrame> > *)self.encPixelFrameQueue;
    delete endQueue;
    self.encPixelFrameQueue = NULL;
    
    std::queue<CVPixelBufferRef> *captureQueue = (std::queue<CVPixelBufferRef> *)self.capturePixelFrameQueue;
    delete captureQueue;
    self.capturePixelFrameQueue = NULL;
    
//    MWCaptureExitInstance();
}


//- (void)viewDidDisappear {
//}

//- (void)setRepresentedObject:(id)representedObject {
//    [super setRepresentedObject:representedObject];
//
//    // Update the view, if already loaded.
//}

- (void) statusTimerProc: (NSTimer *) timer {
    char strFourcc[5] = { 0 };
    *(uint32_t*)strFourcc = self.fourcc;
    [self.statusLabel setStringValue:[NSString stringWithFormat:@" %d x %d,    %s,    Cap:%.2f, Drop:%lld Stutter:%lld,    Enc: %.2f ", self.width, self.height, strFourcc, self.captureFps, self.dropVideoFrameCount, self.delayVideoFrameCount, self.encodeFps]];
}

- (void)modifyAudioConfig
{
    MWCAP_AUDIO_SIGNAL_STATUS aStatus;
    BOOL readAudioSsamplerate = NO;
    MW_RESULT xr = MWGetAudioSignalStatus(self.hChannel, &aStatus);
    if (xr == MW_SUCCEEDED) {
        if (aStatus.wChannelValid != 0){
            self.samplerate = aStatus.dwSampleRate;
            readAudioSsamplerate = YES;
        }
    }
    NSLog(@"Read audio signal status:%@, samplerate:%d",readAudioSsamplerate ? @"success" : @"failed", self.samplerate);
}

#pragma mark -
- (void)switchDevice:(NSMenuItem *)menuItem
{
    {
        NSAlert *alert = nil;
        [self.vtEncLock lock];
        if (self.vtEnc) {
            alert = [[NSAlert alloc] init];
            [alert setMessageText:@"AVCapture"];
            [alert setInformativeText:@"Please stop recording first"];
            [alert setAlertStyle:NSAlertStyleWarning];
        }
        [self.vtEncLock unlock];
        
        if (alert) {
            [alert runModal];
            return;
        }
    }
    
    int index = (int)[menuItem tag];
    char wPath[256] = {0};
    MWGetDevicePath(index, wPath);
    NSString *devicePath = [NSString stringWithCString:wPath encoding:NSUTF8StringEncoding];
    BOOL inUse = NO;
    if (self.delegate && [self.delegate respondsToSelector:@selector(isDeviceInUse:)])
    {
        inUse = [self.delegate isDeviceInUse:devicePath];
    }
    
    if (inUse && self.inUseDevicePath) {
        NSLog(@"Prohibit using the same channel multiple times in the same process.");
        // 创建 NSAlert 实例
        NSAlert *alert = [[NSAlert alloc] init];
        // 设置标题和信息文本
        [alert setMessageText:@"Warning"];
        [alert setInformativeText:@"Prohibit using the same channel multiple times in the same process."];
        // 添加按钮
        [alert addButtonWithTitle:@"Done"];
        // 显示警告框
        [alert runModal];
        return;
    }
    
    while (inUse && !self.inUseDevicePath && self.deviceMenu.numberOfItems > 1) {
        //first load, find the next device
        index++;
        if (index >= self.deviceMenu.numberOfItems) {
            index = 0;
        }
        char wNextPath[256] = {0};
        MWGetDevicePath(index, wNextPath);
        NSString *nextDevicePath = [NSString stringWithCString:wNextPath encoding:NSUTF8StringEncoding];
        BOOL nextInUse = NO;
        if (self.delegate && [self.delegate respondsToSelector:@selector(isDeviceInUse:)])
        {
            nextInUse = [self.delegate isDeviceInUse:nextDevicePath];
        }
        if (!nextInUse) {
            menuItem = [self.deviceMenu itemAtIndex:index];
            break;
        }
    }
    
    [self stopContinuousAutoSwitch];
    
    if (self.selectedDeviceItem) {
        [self.selectedDeviceItem setState:NSControlStateValueOff];
    }
    
    self.width         = 1920;
    self.height        = 1080;
    self.frameDuration = 166667;
    self.fourcc        = MWFOURCC_NV12;
//    self.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
    self.pixelFormat = fourcc_to_CVPixelFormat(self.fourcc);
    self.captureFps      = 0.0;
    self.lastTimestamp   = 0;
    self.videoFrameCount = 0;
    
    [self startCapture:menuItem];
    
    [menuItem setState:NSControlStateValueOn];
    self.selectedDeviceItem = menuItem;
}

- (void)switchCaptureFormat:(NSMenuItem *)menuItem
{
    {
        NSAlert *alert = nil;
        [self.vtEncLock lock];
        if (self.vtEnc) {
            alert = [[NSAlert alloc] init];
            [alert setMessageText:@"AVCapture"];
            [alert setInformativeText:@"Please stop recording first"];
            [alert setAlertStyle:NSWarningAlertStyle];
        }
        [self.vtEncLock unlock];
        
        if (alert) {
            [alert runModal];
            return;
        }
    }
    int index = (int)[menuItem tag];
    if (index == -1) {
        [self startContinuousAutoSwitch];
    } else {
        [self stopContinuousAutoSwitch];
        if (index >= [self.suportCaptureFormatList count]) {
            return;
        }
        [self stopCapture];
        CaptureFormatInfo *info = [self.suportCaptureFormatList objectAtIndex:index];
        self.width = info.width;
        self.height = info.height;
        self.currentCaptrueFormatIndex = index;
        [self startCapture:self.selectedDeviceItem];
    }
    if (self.selectedResolutionItem) {
        [self.selectedResolutionItem setState:NSControlStateValueOff];
        self.selectedResolutionItem = NULL;
    }
    
    [menuItem setState:NSControlStateValueOn];
    self.selectedResolutionItem = menuItem;
}

- (void)switchFPS:(NSMenuItem *)menuItem
{
    {
        NSAlert *alert = nil;
        [self.vtEncLock lock];
        if (self.vtEnc) {
            alert = [[NSAlert alloc] init];
            [alert setMessageText:@"AVCapture"];
            [alert setInformativeText:@"Please stop recording first"];
            [alert setAlertStyle:NSAlertStyleWarning];
        }
        [self.vtEncLock unlock];
        
        if (alert) {
            [alert runModal];
            return;
        }
    }
    int tag = (int)[menuItem tag];
    [self stopCapture];
    self.frameDuration = tag;
    [self startCapture:self.selectedDeviceItem];

    if (self.selectedFpsItem) {
        [self.selectedFpsItem setState:NSControlStateValueOff];
        self.selectedFpsItem = NULL;
    }
    
    [menuItem setState:NSControlStateValueOn];
    self.selectedFpsItem = menuItem;
}

- (void)switchFourcc:(NSMenuItem *)menuItem
{
    {
        NSAlert *alert = nil;
        [self.vtEncLock lock];
        if (self.vtEnc) {
            alert = [[NSAlert alloc] init];
            [alert setMessageText:@"AVCapture"];
            [alert setInformativeText:@"Please stop recording first"];
            [alert setAlertStyle:NSAlertStyleWarning];
        }
        [self.vtEncLock unlock];
        
        if (alert) {
            [alert runModal];
            return;
        }
    }
    int fourcc = (int)[menuItem tag];
    [self stopCapture];
    self.fourcc = fourcc;
    self.pixelFormat = fourcc_to_CVPixelFormat(self.fourcc);
    [self startCapture:self.selectedDeviceItem];
    if (self.selectedFourccItem) {
        [self.selectedFourccItem setState:NSControlStateValueOff];
        self.selectedFourccItem = NULL;
    }
    
    [menuItem setState:NSControlStateValueOn];
    self.selectedFourccItem = menuItem;
}
#pragma mark -
- (void)startContinuousAutoSwitch
{
    if (self.continuousAutoSwitch) {
        return;
    }
    self.continuousAutoSwitch = YES;
    self.currentCaptrueFormatIndex = 0;
    _timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(autoSwitch) userInfo:nil repeats:YES];
}

- (void)stopContinuousAutoSwitch
{
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
    self.continuousAutoSwitch = NO;
}

- (void)autoSwitch
{
    if (!self.continuousAutoSwitch) {
        return;
    }
    if (!self.suportCaptureFormatList) {
        return;
    }
    if (self.currentCaptrueFormatIndex >= [self.suportCaptureFormatList count]) {
        self.currentCaptrueFormatIndex = 0;
    }
    
    [self stopCapture];
    CaptureFormatInfo *info = [self.suportCaptureFormatList objectAtIndex:self.currentCaptrueFormatIndex];
    self.width = info.width;
    self.height = info.height;
    [self startCapture:self.selectedDeviceItem];
    
    self.currentCaptrueFormatIndex++;
}

- (BOOL)isDoingContinuousAutoSwitch
{
    if (self.continuousAutoSwitch) {
        return YES;
    }
    return NO;
}
#pragma mark -
- (void)onVideoCaptureFrame:(BYTE *)pBuffer bufferLen:(long)bufferLen pixelBufferFrame:(std::shared_ptr<PixelBufferFrame>) frame  {
    //render
    uint64_t timestamp = frame->timestamp;
    if (self.previousPts != 0) {
        uint64_t t = timestamp - self.previousPts;
        float delta = t - self.frameDuration/10000.0;
        if (fabsf(delta) >= 5) {
            self.delayVideoFrameCount++;
        }
        if (delta > self.frameDuration/10000.0) {
            self.dropVideoFrameCount++;
        }
//        NSLog(@"-onVideoCaptureFrame- timestamp:%lld delta:%0.f",timestamp,delta);
    }
    self.previousPts = timestamp;

    if (self.viewEnable) {
        CMSampleTimingInfo timing = {kCMTimeInvalid, kCMTimeInvalid, kCMTimeInvalid};
        CMVideoFormatDescriptionRef videoInfo = NULL;
        CVReturn result = CMVideoFormatDescriptionCreateForImageBuffer(NULL, frame->pixelBuffer, &videoInfo);

        CMSampleBufferRef sampleBuffer = NULL;
        result = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, frame->pixelBuffer, true, NULL, NULL, videoInfo, &timing, &sampleBuffer);
        CFRelease(videoInfo);

        CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
        CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
        CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);

        if (self.videoLayer) {
            [self.videoLayer enqueueSampleBuffer:(CMSampleBufferRef)sampleBuffer];
        }
        
        CFRelease(sampleBuffer);
    }
    
    //encode
    [self.vtEncLock lock];
    if (self.vtEnc) {
        ((std::queue<std::shared_ptr<PixelBufferFrame> > *)self.encPixelFrameQueue)->push(frame);
        while(((std::queue<std::shared_ptr<PixelBufferFrame> > *)self.encPixelFrameQueue)->size() > MAX_VIDEO_ENCODE_BUFFER_FRAMES) {
            ((std::queue<std::shared_ptr<PixelBufferFrame> > *)self.encPixelFrameQueue)->pop();
        }
    }
    [self.vtEncLock unlock];

    //capture fps
    {
        uint64_t now = getTimestamp();
        if (self.lastTimestamp == 0) {
            self.lastTimestamp = now;
            self.videoFrameCount = 0;
        } else {
            self.videoFrameCount++;
        }

        if (now - self.lastTimestamp >= 1000) {
            self.captureFps = self.videoFrameCount * 1000.0 / (now - self.lastTimestamp);
            self.lastTimestamp = now;
            self.videoFrameCount = 0;
        }
    }
}

- (void)onVideoCaptureProc {
    CVPixelBufferRef pixelBuffer[MAX_VIDEO_CAPTURE_BUFFER_FRAMES] = {NULL};
    NSDictionary *pixelAttributes = @{(NSString*)kCVPixelBufferIOSurfacePropertiesKey:@{}};
    for (int i = 0; i < sizeof(pixelBuffer)/sizeof(pixelBuffer[0]); i++) {
        CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,
                                              self.width,
                                              self.height,
                                              self.pixelFormat,
                                              (__bridge CFDictionaryRef)(pixelAttributes),
                                              &pixelBuffer[i]);
        
        if (kCVReturnSuccess == result) {
            ((std::queue<CVPixelBufferRef> *)self.capturePixelFrameQueue)->push(pixelBuffer[i]);
        }
    }

    // Wait Events
    self.videoCaptureEvent = MWCreateEvent();
    MWCAP_PTR hTimerEvent = MWCreateEvent();
    MWCAP_PTR hNotifyEvent = MWCreateEvent();

    MW_RESULT xr;
    do {
        xr = MWStartVideoCapture(self.hChannel, self.videoCaptureEvent);
        if (xr != MW_SUCCEEDED)
            break;
        HNOTIFY hSignalNotify = MWRegisterNotify(self.hChannel, hNotifyEvent, MWCAP_NOTIFY_VIDEO_SIGNAL_CHANGE);

        HTIMER hTimerNotify = MWRegisterTimer(self.hChannel, hTimerEvent);
        if (hTimerNotify == 0)
            break;

        LONGLONG llBegin = 0LL;
        xr = MWGetDeviceTime(self.hChannel, &llBegin);
        if (xr != MW_SUCCEEDED)
            break;

        LONGLONG llExpireTime = llBegin;
        DWORD dwFrameDuration = self.frameDuration;

        while (self.running) {
            if (!self.running) {
                break;
            }
            llExpireTime = llExpireTime + dwFrameDuration;
            
            LONGLONG llCurrentTime = 0LL;
            xr = MWGetDeviceTime(self.hChannel, &llCurrentTime);
            if (xr != MW_SUCCEEDED) {
                llExpireTime = 0LL;
                continue;
            }
            if (llExpireTime <= llCurrentTime) {
                if ([self captureVideoFrame:pixelBuffer] == MW_FAILED) {
                    break;
                }
            } else {
                xr = MWScheduleTimer(self.hChannel, hTimerNotify, llExpireTime);
                MWCAP_PTR event[2] = {hTimerNotify, hNotifyEvent};
                DWORD dwRet = MWMultiWaitEvent(event, 2, -1);
                if (dwRet == 0) {
                    //退出线程
                    break;
                } else if ((dwRet&0x01)) {
                    //timer fire
                    if ([self captureVideoFrame:pixelBuffer] == MW_FAILED) {
                        break;
                    }
                } else if ((dwRet&0x02)) {
                    //notify fire
                    NSLog(@"-- signal status been changed");
                }
            }
            
        }
        xr = MWUnregisterTimer(self.hChannel, hTimerNotify);
        hTimerNotify = 0;
        xr = MWUnregisterNotify(self.hChannel, hSignalNotify);
        xr = MWStopVideoCapture(self.hChannel);
    } while (FALSE);
    
    MWCloseEvent(hTimerEvent);

    MWCloseEvent(self.videoCaptureEvent);
    self.videoCaptureEvent = 0;

    std::queue<CVPixelBufferRef> empty;
    [self.capturePixelFrameLock lock];
    std::swap(*((std::queue<CVPixelBufferRef> *)self.capturePixelFrameQueue), empty);
    [self.capturePixelFrameLock unlock];

    for (int i = 0; i < sizeof(pixelBuffer)/sizeof(pixelBuffer[0]); i++) {
        CFRelease(pixelBuffer[i]);
        pixelBuffer[i] = NULL;
    }
}

- (MW_RESULT)captureVideoFrame:(CVPixelBufferRef*)pixelBuffer
{
    MWCAP_VIDEO_SIGNAL_STATUS videoSignalStatus;
    MW_RESULT xr = MWGetVideoSignalStatus(self.hChannel, &videoSignalStatus);
    if (xr != MW_SUCCEEDED) {
        return MW_SUCCEEDED;
    }

    RECT rcSrc = {0, 0, videoSignalStatus.cx, videoSignalStatus.cy};
    
    std::shared_ptr<PixelBufferFrame> frame(new PixelBufferFrame());
    frame->discard = (void (*)(PixelBufferFrame*, void*))(releasePixelBufferFrame);
    frame->param = (__bridge void*)self;
    
    [self.capturePixelFrameLock lock];
    if (!((std::queue<CVPixelBufferRef> *)self.capturePixelFrameQueue)->empty()) {
        frame->pixelBuffer = ((std::queue<CVPixelBufferRef> *)self.capturePixelFrameQueue)->front();
        ((std::queue<CVPixelBufferRef> *)self.capturePixelFrameQueue)->pop();
    }
    [self.capturePixelFrameLock unlock];
    
    if (frame->pixelBuffer) {
        CVPixelBufferLockBaseAddress(frame->pixelBuffer, 0);
        DWORD cbStride = (DWORD)CVPixelBufferGetBytesPerRowOfPlane(frame->pixelBuffer, 0);
        int height = self.height;
        if (self.fourcc == MWFOURCC_NV12) {
            height =  ((self.height+7)/8)*8;
        }
        DWORD dwFrameSize = FOURCC_CalcImageSize(self.fourcc, self.width, height, cbStride);  // (DWORD)CVPixelBufferGetDataSize(pixelBuffer[index]);
        BYTE* byBuffer = (BYTE *)CVPixelBufferGetBaseAddressOfPlane(frame->pixelBuffer, 0);
        if (MWTryWaitEvent(self.videoCaptureEvent) != MW_SUCCEEDED) {
            CVPixelBufferUnlockBaseAddress(frame->pixelBuffer, 0);
            return MW_SUCCEEDED;
        }
        xr = MWCaptureVideoFrameToVirtualAddressEx(self.hChannel, MWCAP_VIDEO_FRAME_ID_NEWEST_BUFFERED, byBuffer, dwFrameSize, cbStride, FALSE, (MWCAP_PTR64)pixelBuffer,
                                                   self.fourcc, self.width, self.height,
                                                   0, 0, NULL, NULL, 0, 100, 0, 100, 0, MWCAP_VIDEO_DEINTERLACE_BLEND, MWCAP_VIDEO_ASPECT_RATIO_IGNORE, &rcSrc, NULL, 0, 0,
                                                   MWCAP_VIDEO_COLOR_FORMAT_UNKNOWN, MWCAP_VIDEO_QUANTIZATION_UNKNOWN, MWCAP_VIDEO_SATURATION_UNKNOWN);
                   
        DWORD waitRet = MWWaitEvent(self.videoCaptureEvent, -1);
        if (waitRet == -1) {
            //退出线程
            CVPixelBufferUnlockBaseAddress(frame->pixelBuffer, 0);
            return MW_FAILED;
        } else {
            if (!self.running) {
                CVPixelBufferUnlockBaseAddress(frame->pixelBuffer, 0);
                return MW_FAILED;
            }
            
            frame->timestamp = getTimestamp();
            [self onVideoCaptureFrame:byBuffer bufferLen:dwFrameSize pixelBufferFrame:frame];
        }
        
        MWCAP_VIDEO_CAPTURE_STATUS captureStatus;
        xr = MWGetVideoCaptureStatus(self.hChannel, &captureStatus);
        
    }
    return MW_SUCCEEDED;
}

- (void)onAudioCaptureProc {
    self.audioCaptureEvent = MWCreateEvent();
    HNOTIFY hAudioNotify = 0;

    MW_RESULT xr = MW_SUCCEEDED;
    do {
        xr = MWStartAudioCapture(self.hChannel);
        
        if (xr != MW_SUCCEEDED)
            break;

        hAudioNotify = MWRegisterNotify(self.hChannel, self.audioCaptureEvent, MWCAP_NOTIFY_AUDIO_FRAME_BUFFERED);
        if (hAudioNotify == 0)
            break;

        while (self.running) {
            DWORD dwRet = MWWaitEvent(self.audioCaptureEvent, -1);
            if (dwRet == -1) {
                break;
            } else {
                if (!self.running) {
                    break;
                }
                ULONGLONG ullStatusBits = 0LL;
                MWGetNotifyStatus(self.hChannel, hAudioNotify, &ullStatusBits);
                if (ullStatusBits & MWCAP_NOTIFY_AUDIO_FRAME_BUFFERED) {
    //                NSLog(@"---audio YES");
                } else {
                    continue;
                }

                MWCAP_AUDIO_CAPTURE_FRAME audioFrame= {0};
                xr = MWCaptureAudioFrame(self.hChannel, &audioFrame);
                if (xr == MW_SUCCEEDED) {
                    short asAudioSamples[MWCAP_AUDIO_SAMPLES_PER_FRAME * 2];
                    for (int i = 0; i < MWCAP_AUDIO_SAMPLES_PER_FRAME; i++) {
                        short sLeft = (short)(audioFrame.adwSamples[i * 8] >> 16);
                        short sRight = (short)(audioFrame.adwSamples[i * 8 + 4] >> 16);
                        
                        asAudioSamples[i * 2] = sLeft;
                        asAudioSamples[i * 2 + 1] = sRight;
                    }
                    
                    MWAudioRenderPut(self.audioRender, (uint8_t*)asAudioSamples, MWCAP_AUDIO_SAMPLES_PER_FRAME * 2 * sizeof(short));
                    [self.aacEncLock lock];
                    if (self.aacEnc) {
                        self.aacEnc->encode((uint8_t*)asAudioSamples, MWCAP_AUDIO_SAMPLES_PER_FRAME * 2 * sizeof(short), getTimestamp());
                    }
                    [self.aacEncLock unlock];
                }
            }

            
        }

        xr = MWUnregisterNotify(self.hChannel, hAudioNotify);
        xr = MWStopAudioCapture(self.hChannel);
    } while (0);

    MWCloseEvent(self.audioCaptureEvent);
    self.audioCaptureEvent = NULL;
}

- (void)onVideoEncodeProc {
    std::queue<std::shared_ptr<PixelBufferFrame> > *encQueue = (std::queue<std::shared_ptr<PixelBufferFrame> > *)self.encPixelFrameQueue;
    
    while (self.encoding) {
        std::shared_ptr<PixelBufferFrame> frame;
        
        [self.vtEncLock lock];
        if (!encQueue->empty()) {
            frame = encQueue->front();
        }
        [self.vtEncLock unlock];
        
        if (frame != NULL && frame->pixelBuffer) {
            if (self.vtEnc) {
                //printf("put video frame:%lld\n", frame->timestamp);
                mw_venc_put_imagebuffer(self.vtEnc, frame->pixelBuffer, frame->timestamp);
            }
            
            [self.vtEncLock lock];
            encQueue->pop();
            [self.vtEncLock unlock];
            
            //encode fps
            {
                uint64_t now = getTimestamp();
                if (self.lastEncodeTimestamp == 0) {
                    self.lastEncodeTimestamp = now;
                    self.encodeFrameCount = 0;
                } else {
                    self.encodeFrameCount++;
                }

                if (now - self.lastEncodeTimestamp >= 1000) {
                    self.encodeFps = self.encodeFrameCount * 1000.0 / (now - self.lastEncodeTimestamp);
                    self.lastEncodeTimestamp = now;
                    self.encodeFrameCount = 0;
                }
            }
        } else {
            usleep(5000);
        }
    }
    
    std::queue<std::shared_ptr<PixelBufferFrame> > empty;
    [self.vtEncLock lock];
    std::swap(*encQueue, empty);
    [self.vtEncLock unlock];
}

- (void)onVTBoxEncodeFrame:(const uint8_t**)nalus nalu_lens:(uint32_t*) nalu_lens nalu_num:(uint32_t) nalu_num frame_info:(mw_venc_frame_info_t*) frame_info {
    //printf("got encoded frames:%d, size[0]:%ld, frame_type:%d, timestamp:%llu\n", nbufs, bufs[0].len, frame_info->frame_type, frame_info->pts);
    if (!self.gotVideoTrack) {
        mw_mp4_video_info_t video_info;
        memset(&video_info, 0 , sizeof(video_info));
        video_info.codec_type = self.h265Enable ? MW_MP4_VIDEO_TYPE_HEVC: MW_MP4_VIDEO_TYPE_H264;
        video_info.timescale = 1000;
        video_info.width = self.width;
        video_info.height = self.height;
        mw_mp4_set_video(self.mp4Record, &video_info);
        
        self.gotVideoTrack = true;
    }
    
    static char startCode[4] = { 0x00, 0x00, 0x00, 0x01};
    uint32_t size = 0;
    for (int i = 0; i < nalu_num; i++) {
        //should check size
        memcpy(self.videoBufferFrame + size, startCode, sizeof(startCode)); size += sizeof(startCode);
        memcpy(self.videoBufferFrame + size, nalus[i], nalu_lens[i]);    size += nalu_lens[i];
    }
    if(mw_mp4_write_video(self.mp4Record, self.videoBufferFrame, size, (frame_info->pts - self.startRecordTime)) != MW_MP4_STATUS_SUCCESS){
        printf("write video fail\n");
    }
}

- (void)onAacEncodeFrame:(const uint8_t*)frame size:(size_t) size timestamp:(uint64_t) timestamp {
    //printf("got audio encode frames, size:%ld, timestamp:%llu\n", size, timestamp);
    if (!self.gotAudioTrack) {
        mw_mp4_audio_info_t audio_info = {
            .timescale = self.samplerate,
            .codec_type = MW_MP4_AUDIO_TYPE_ADTS_AAC,
            .sample_rate = 0,
            .channels = 0,
            .profile = 0
        };
        mw_mp4_set_audio(self.mp4Record, &audio_info);

        self.gotAudioTrack = true;
    }
    if(mw_mp4_write_audio(self.mp4Record, frame, (uint32_t)size, ((timestamp-self.startRecordTime) * self.samplerate / 1000)) != MW_MP4_STATUS_SUCCESS){
        printf("write audio fail\n");
    }
}
@end
