Files
rikkei_simple_care/client/internal/camera/camera_darwin.m
2026-07-01 12:43:09 +07:00

167 lines
5.7 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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]);
}