#import #import #import #import #include #include // ── Shared state ────────────────────────────────────────────── static AVCaptureSession *captureSession = nil; static AVCaptureVideoDataOutput *videoOutput = nil; static dispatch_queue_t captureQueue = nil; // Latest frame stored as JPEG base64 static NSString *latestFrameBase64 = nil; static NSLock *frameLock = nil; // ── Delegate that receives sample buffers ───────────────────── @interface CameraFrameDelegate : NSObject @end @implementation CameraFrameDelegate - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); if (!imageBuffer) return; CIImage *ciImage = [CIImage imageWithCVPixelBuffer:imageBuffer]; CIContext *context = [CIContext contextWithOptions:nil]; CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent]; if (!cgImage) return; // Convert to JPEG NSData (quality ≈ 0.4) NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; CGImageRelease(cgImage); NSDictionary *props = @{NSImageCompressionFactor: @(0.4)}; NSData *jpegData = [rep representationUsingType:NSBitmapImageFileTypeJPEG properties:props]; if (!jpegData) return; NSString *b64 = [jpegData base64EncodedStringWithOptions:0]; NSString *dataUrl = [NSString stringWithFormat:@"data:image/jpeg;base64,%@", b64]; [frameLock lock]; latestFrameBase64 = dataUrl; [frameLock unlock]; } @end static CameraFrameDelegate *frameDelegate = nil; // ── C-exported functions ────────────────────────────────────── // StartNativeCamera: returns 0 on success, -1 on failure int StartNativeCamera(void) { if (captureSession && captureSession.isRunning) { return 0; // already running } if (!frameLock) { frameLock = [[NSLock alloc] init]; } // Check camera permission if (@available(macOS 10.14, *)) { AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; if (status == AVAuthorizationStatusDenied || status == AVAuthorizationStatusRestricted) { NSLog(@"[CAMERA] Camera permission denied (status=%ld)", (long)status); return -1; } if (status == AVAuthorizationStatusNotDetermined) { // Request permission synchronously via semaphore dispatch_semaphore_t sem = dispatch_semaphore_create(0); __block BOOL granted = NO; [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL g) { granted = g; dispatch_semaphore_signal(sem); }]; dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); if (!granted) { NSLog(@"[CAMERA] Camera permission not granted"); return -1; } } } captureSession = [[AVCaptureSession alloc] init]; captureSession.sessionPreset = AVCaptureSessionPresetLow; // 320×240-ish // Find default video device AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; if (!camera) { NSLog(@"[CAMERA] No camera device found"); captureSession = nil; return -1; } NSError *error = nil; AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:camera error:&error]; if (error || !input) { NSLog(@"[CAMERA] Cannot create camera input: %@", error); captureSession = nil; return -1; } if ([captureSession canAddInput:input]) { [captureSession addInput:input]; } else { NSLog(@"[CAMERA] Cannot add camera input to session"); captureSession = nil; return -1; } // Video data output videoOutput = [[AVCaptureVideoDataOutput alloc] init]; videoOutput.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA) }; videoOutput.alwaysDiscardsLateVideoFrames = YES; captureQueue = dispatch_queue_create("com.simplecare.camera", DISPATCH_QUEUE_SERIAL); frameDelegate = [[CameraFrameDelegate alloc] init]; [videoOutput setSampleBufferDelegate:frameDelegate queue:captureQueue]; if ([captureSession canAddOutput:videoOutput]) { [captureSession addOutput:videoOutput]; } else { NSLog(@"[CAMERA] Cannot add video output to session"); captureSession = nil; return -1; } [captureSession startRunning]; NSLog(@"[CAMERA] Native camera capture started"); return 0; } void StopNativeCamera(void) { if (captureSession && captureSession.isRunning) { [captureSession stopRunning]; NSLog(@"[CAMERA] Native camera capture stopped"); } captureSession = nil; videoOutput = nil; frameDelegate = nil; [frameLock lock]; latestFrameBase64 = nil; [frameLock unlock]; } // GetLatestCameraFrame: returns a C-string (caller must free) or NULL char* GetLatestCameraFrame(void) { [frameLock lock]; NSString *frame = latestFrameBase64; latestFrameBase64 = nil; // consume [frameLock unlock]; if (!frame || frame.length == 0) { return NULL; } return strdup([frame UTF8String]); }