This commit is contained in:
2026-07-01 12:43:09 +07:00
parent 0d1e240227
commit 44888520cb
16 changed files with 588 additions and 22 deletions

View File

@@ -0,0 +1,166 @@
#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>
#import <CoreImage/CoreImage.h>
#import <AppKit/AppKit.h>
#include <stdlib.h>
#include <string.h>
// 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 <AVCaptureVideoDataOutputSampleBufferDelegate>
@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]);
}