apple: enable JoeMatt's avfoundation-based camera driver

This commit is contained in:
Eric Warmenhoven 2025-03-03 23:15:54 -05:00
parent 58752d0755
commit 8c729fd029
5 changed files with 750 additions and 0 deletions

View File

@ -58,6 +58,9 @@ const camera_driver_t *camera_drivers[] = {
#ifdef ANDROID
&camera_android,
#endif
#ifdef HAVE_AVF
&camera_avfoundation,
#endif
#ifdef HAVE_FFMPEG
&camera_ffmpeg,
#endif

View File

@ -0,0 +1,725 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2025 - Joseph Mattiello
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <TargetConditionals.h>
#include <Foundation/Foundation.h>
#include <AVFoundation/AVFoundation.h>
#include <libretro.h>
#include "../camera/camera_driver.h"
#include "../verbosity.h"
/// For image scaling and color space DSP
#import <Accelerate/Accelerate.h>
#if TARGET_OS_IOS
/// For camera rotation detection
#import <UIKit/UIKit.h>
#endif
// TODO: Add an API to retroarch to allow selection of camera
#ifndef CAMERA_PREFER_FRONTFACING
#define CAMERA_PREFER_FRONTFACING 1 /// Default to front camera
#endif
#ifndef CAMERA_MIRROR_FRONT_CAMERA
#define CAMERA_MIRROR_FRONT_CAMERA 1
#endif
@interface AVCameraManager : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
@property (strong, nonatomic) AVCaptureSession *session;
@property (strong, nonatomic) AVCaptureDeviceInput *input;
@property (strong, nonatomic) AVCaptureVideoDataOutput *output;
@property (assign) uint32_t *frameBuffer;
@property (assign) size_t width;
@property (assign) size_t height;
- (bool)setupCameraSession;
@end
@implementation AVCameraManager
+ (AVCameraManager *)sharedInstance {
static AVCameraManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[AVCameraManager alloc] init];
});
return instance;
}
- (void)requestCameraAuthorizationWithCompletion:(void (^)(BOOL granted))completion {
RARCH_LOG("[Camera]: Checking camera authorization status\n");
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
switch (status) {
case AVAuthorizationStatusAuthorized: {
RARCH_LOG("[Camera]: Camera access already authorized\n");
completion(YES);
break;
}
case AVAuthorizationStatusNotDetermined: {
RARCH_LOG("[Camera]: Requesting camera authorization...\n");
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
completionHandler:^(BOOL granted) {
RARCH_LOG("[Camera]: Authorization %s\n", granted ? "granted" : "denied");
completion(granted);
}];
break;
}
case AVAuthorizationStatusDenied: {
RARCH_ERR("[Camera]: Camera access denied by user\n");
completion(NO);
break;
}
case AVAuthorizationStatusRestricted: {
RARCH_ERR("[Camera]: Camera access restricted (parental controls?)\n");
completion(NO);
break;
}
default: {
RARCH_ERR("[Camera]: Unknown authorization status\n");
completion(NO);
break;
}
}
}
- (void)captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
@autoreleasepool {
if (!self.frameBuffer)
return;
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (!imageBuffer) {
RARCH_ERR("[Camera]: Failed to get image buffer\n");
return;
}
CVPixelBufferLockBaseAddress(imageBuffer, 0);
size_t sourceWidth = CVPixelBufferGetWidth(imageBuffer);
size_t sourceHeight = CVPixelBufferGetHeight(imageBuffer);
OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);
#ifdef DEBUG
RARCH_LOG("[Camera]: Processing frame %zux%zu format: %u\n", sourceWidth, sourceHeight, (unsigned int)pixelFormat);
#endif
// Create intermediate buffer for full-size converted image
uint32_t *intermediateBuffer = (uint32_t*)malloc(sourceWidth * sourceHeight * 4);
if (!intermediateBuffer) {
RARCH_ERR("[Camera]: Failed to allocate intermediate buffer\n");
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
vImage_Buffer srcBuffer = {}, intermediateVBuffer = {}, dstBuffer = {};
vImage_Error err = kvImageNoError;
// Setup intermediate buffer
intermediateVBuffer.data = intermediateBuffer;
intermediateVBuffer.width = sourceWidth;
intermediateVBuffer.height = sourceHeight;
intermediateVBuffer.rowBytes = sourceWidth * 4;
// Setup destination buffer
dstBuffer.data = self.frameBuffer;
dstBuffer.width = self.width;
dstBuffer.height = self.height;
dstBuffer.rowBytes = self.width * 4;
// Convert source format to RGBA
switch (pixelFormat) {
case kCVPixelFormatType_32BGRA: {
srcBuffer.data = CVPixelBufferGetBaseAddress(imageBuffer);
srcBuffer.width = sourceWidth;
srcBuffer.height = sourceHeight;
srcBuffer.rowBytes = CVPixelBufferGetBytesPerRow(imageBuffer);
uint8_t permuteMap[4] = {2, 1, 0, 3}; // BGRA -> RGBA
err = vImagePermuteChannels_ARGB8888(&srcBuffer, &intermediateVBuffer, permuteMap, kvImageNoFlags);
break;
}
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange: {
// YUV to RGB conversion
vImage_Buffer srcY = {}, srcCbCr = {};
srcY.data = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
srcY.width = sourceWidth;
srcY.height = sourceHeight;
srcY.rowBytes = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
srcCbCr.data = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
srcCbCr.width = sourceWidth / 2;
srcCbCr.height = sourceHeight / 2;
srcCbCr.rowBytes = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
vImage_YpCbCrToARGB info;
vImage_YpCbCrPixelRange pixelRange =
(pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) ?
(vImage_YpCbCrPixelRange){16, 128, 235, 240} : // Video range
(vImage_YpCbCrPixelRange){0, 128, 255, 255}; // Full range
err = vImageConvert_YpCbCrToARGB_GenerateConversion(kvImage_YpCbCrToARGBMatrix_ITU_R_601_4,
&pixelRange,
&info,
kvImage420Yp8_CbCr8,
kvImageARGB8888,
kvImageNoFlags);
if (err == kvImageNoError) {
err = vImageConvert_420Yp8_CbCr8ToARGB8888(&srcY,
&srcCbCr,
&intermediateVBuffer,
&info,
NULL,
255,
kvImageNoFlags);
}
break;
}
default:
RARCH_ERR("[Camera]: Unsupported pixel format: %u\n", (unsigned int)pixelFormat);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
if (err != kvImageNoError) {
RARCH_ERR("[Camera]: Error converting color format: %ld\n", err);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
// Determine rotation based on platform and camera type
#if TARGET_OS_OSX
int rotationDegrees = 0; // Default 180-degree rotation for most cases
bool shouldMirror = true;
#else
int rotationDegrees = 180; // Default 180-degree rotation for most cases
bool shouldMirror = false;
/// For camera rotation detection
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationPortrait ||
orientation == UIDeviceOrientationPortraitUpsideDown) {
// In portrait mode, adjust rotation based on camera type
if (self.input.device.position == AVCaptureDevicePositionFront) {
rotationDegrees = 270;
#if CAMERA_MIRROR_FRONT_CAMERA
// TODO: Add an API to retroarch to allow for mirroring of front camera
shouldMirror = true; // Mirror front camera
#endif
RARCH_LOG("[Camera]: Using 270-degree rotation with mirroring for front camera in portrait mode\n");
}
}
#endif
// Rotate image
vImage_Buffer rotatedBuffer = {};
rotatedBuffer.data = malloc(sourceWidth * sourceHeight * 4);
if (!rotatedBuffer.data) {
RARCH_ERR("[Camera]: Failed to allocate rotation buffer\n");
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
// Set dimensions based on rotation angle
if (rotationDegrees == 90 || rotationDegrees == 270) {
rotatedBuffer.width = sourceHeight;
rotatedBuffer.height = sourceWidth;
} else {
rotatedBuffer.width = sourceWidth;
rotatedBuffer.height = sourceHeight;
}
rotatedBuffer.rowBytes = rotatedBuffer.width * 4;
const Pixel_8888 backgroundColor = {0, 0, 0, 255};
err = vImageRotate90_ARGB8888(&intermediateVBuffer,
&rotatedBuffer,
rotationDegrees / 90,
backgroundColor,
kvImageNoFlags);
if (err != kvImageNoError) {
RARCH_ERR("[Camera]: Error rotating image: %ld\n", err);
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
// Mirror the image if needed
if (shouldMirror) {
vImage_Buffer mirroredBuffer = {};
mirroredBuffer.data = malloc(rotatedBuffer.height * rotatedBuffer.rowBytes);
if (!mirroredBuffer.data) {
RARCH_ERR("[Camera]: Failed to allocate mirror buffer\n");
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
mirroredBuffer.width = rotatedBuffer.width;
mirroredBuffer.height = rotatedBuffer.height;
mirroredBuffer.rowBytes = rotatedBuffer.rowBytes;
err = vImageHorizontalReflect_ARGB8888(&rotatedBuffer, &mirroredBuffer, kvImageNoFlags);
if (err == kvImageNoError) {
// Free rotated buffer and use mirrored buffer for scaling
free(rotatedBuffer.data);
rotatedBuffer = mirroredBuffer;
} else {
RARCH_ERR("[Camera]: Error mirroring image: %ld\n", err);
free(mirroredBuffer.data);
}
}
// Calculate aspect fill scaling
float sourceAspect = (float)rotatedBuffer.width / rotatedBuffer.height;
float targetAspect = (float)self.width / self.height;
vImage_Buffer scaledBuffer = {};
size_t scaledWidth, scaledHeight;
if (sourceAspect > targetAspect) {
// Source is wider - scale to match height
scaledHeight = self.height;
scaledWidth = (size_t)(self.height * sourceAspect);
} else {
// Source is taller - scale to match width
scaledWidth = self.width;
scaledHeight = (size_t)(self.width / sourceAspect);
}
RARCH_LOG("[Camera]: Aspect fill scaling from %zux%zu to %zux%zu\n",
rotatedBuffer.width, rotatedBuffer.height, scaledWidth, scaledHeight);
scaledBuffer.data = malloc(scaledWidth * scaledHeight * 4);
if (!scaledBuffer.data) {
RARCH_ERR("[Camera]: Failed to allocate scaled buffer\n");
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
scaledBuffer.width = scaledWidth;
scaledBuffer.height = scaledHeight;
scaledBuffer.rowBytes = scaledWidth * 4;
// Scale maintaining aspect ratio
err = vImageScale_ARGB8888(&rotatedBuffer, &scaledBuffer, NULL, kvImageHighQualityResampling);
if (err != kvImageNoError) {
RARCH_ERR("[Camera]: Error scaling image: %ld\n", err);
free(scaledBuffer.data);
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
// Center crop the scaled image into the destination buffer
size_t xOffset = (scaledWidth > self.width) ? (scaledWidth - self.width) / 2 : 0;
size_t yOffset = (scaledHeight > self.height) ? (scaledHeight - self.height) / 2 : 0;
// Copy the centered portion to the destination buffer
uint32_t *srcPtr = (uint32_t *)scaledBuffer.data;
uint32_t *dstPtr = (uint32_t *)self.frameBuffer;
for (size_t y = 0; y < self.height; y++) {
memcpy(dstPtr + y * self.width,
srcPtr + (y + yOffset) * scaledWidth + xOffset,
self.width * 4);
}
// Clean up
free(scaledBuffer.data);
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
} // End of autorelease pool
}
- (AVCaptureDevice *)selectCameraDevice {
RARCH_LOG("[Camera]: Selecting camera device\n");
NSArray<AVCaptureDevice *> *devices;
#if TARGET_OS_OSX
// On macOS, use default discovery method
// Could probably due the same as iOS but need to test.
devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
#else
// On iOS/tvOS use modern discovery session
NSArray<AVCaptureDeviceType> *deviceTypes;
if (@available(iOS 17.0, *)) {
deviceTypes = @[
AVCaptureDeviceTypeExternal,
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInTelephotoCamera,
AVCaptureDeviceTypeBuiltInUltraWideCamera,
// AVCaptureDeviceTypeBuiltInDualCamera,
// AVCaptureDeviceTypeBuiltInDualWideCamera,
// AVCaptureDeviceTypeBuiltInTripleCamera,
// AVCaptureDeviceTypeBuiltInTrueDepthCamera,
// AVCaptureDeviceTypeBuiltInLiDARDepthCamera,
// AVCaptureDeviceTypeContinuityCamera,
];
} else {
deviceTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInTelephotoCamera,
AVCaptureDeviceTypeBuiltInUltraWideCamera,
// AVCaptureDeviceTypeBuiltInDualCamera,
// AVCaptureDeviceTypeBuiltInDualWideCamera,
// AVCaptureDeviceTypeBuiltInTripleCamera,
// AVCaptureDeviceTypeBuiltInTrueDepthCamera,
// AVCaptureDeviceTypeBuiltInLiDARDepthCamera,
// AVCaptureDeviceTypeContinuityCamera,
];
}
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:deviceTypes
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified];
devices = discoverySession.devices;
#endif
if (devices.count == 0) {
RARCH_ERR("[Camera]: No camera devices found\n");
return nil;
}
// Log available devices
for (AVCaptureDevice *device in devices) {
RARCH_LOG("[Camera]: Found device: %s - Position: %d\n",
[device.localizedName UTF8String],
(int)device.position);
}
#if TARGET_OS_OSX
// macOS: Just use the first available camera if only one exists
if (devices.count == 1) {
RARCH_LOG("[Camera]: Using only available camera: %s\n",
[devices.firstObject.localizedName UTF8String]);
return devices.firstObject;
}
// Try to match by name for built-in cameras
for (AVCaptureDevice *device in devices) {
BOOL isFrontFacing = [device.localizedName containsString:@"FaceTime"] ||
[device.localizedName containsString:@"Front"];
if (CAMERA_PREFER_FRONTFACING == isFrontFacing) {
RARCH_LOG("[Camera]: Selected macOS camera: %s\n",
[device.localizedName UTF8String]);
return device;
}
}
#else
// iOS: Use position property
AVCaptureDevicePosition preferredPosition = CAMERA_PREFER_FRONTFACING ?
AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
// Try to find preferred camera
for (AVCaptureDevice *device in devices) {
if (device.position == preferredPosition) {
RARCH_LOG("[Camera]: Selected iOS camera position: %d\n",
(int)preferredPosition);
return device;
}
}
#endif
// Fallback to first available camera
RARCH_LOG("[Camera]: Using fallback camera: %s\n",
[devices.firstObject.localizedName UTF8String]);
return devices.firstObject;
}
- (bool)setupCameraSession {
// Initialize capture session
self.session = [[AVCaptureSession alloc] init];
// Get camera device
AVCaptureDevice *device = [self selectCameraDevice];
if (!device) {
RARCH_ERR("[Camera]: No camera device found\n");
return false;
}
// Create device input
NSError *error = nil;
self.input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (error) {
RARCH_ERR("[Camera]: Failed to create device input: %s\n",
[error.localizedDescription UTF8String]);
return false;
}
if ([self.session canAddInput:self.input]) {
[self.session addInput:self.input];
RARCH_LOG("[Camera]: Added camera input to session\n");
}
// Create and configure video output
self.output = [[AVCaptureVideoDataOutput alloc] init];
self.output.videoSettings = @{
(NSString*)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)
};
[self.output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
if ([self.session canAddOutput:self.output]) {
[self.session addOutput:self.output];
RARCH_LOG("[Camera]: Added video output to session\n");
}
return true;
}
@end
typedef struct
{
AVCameraManager *manager;
unsigned width;
unsigned height;
} avfoundation_t;
static void generateColorBars(uint32_t *buffer, size_t width, size_t height) {
const uint32_t colors[] = {
0xFFFFFFFF, // White
0xFFFFFF00, // Yellow
0xFF00FFFF, // Cyan
0xFF00FF00, // Green
0xFFFF00FF, // Magenta
0xFFFF0000, // Red
0xFF0000FF, // Blue
0xFF000000 // Black
};
size_t barWidth = width / 8;
for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) {
size_t colorIndex = x / barWidth;
buffer[y * width + x] = colors[colorIndex];
}
}
}
static void *avfoundation_init(const char *device, uint64_t caps,
unsigned width, unsigned height)
{
RARCH_LOG("[Camera]: Initializing AVFoundation camera %ux%u\n", width, height);
avfoundation_t *avf = (avfoundation_t*)calloc(1, sizeof(avfoundation_t));
if (!avf) {
RARCH_ERR("[Camera]: Failed to allocate avfoundation_t\n");
return NULL;
}
avf->manager = [AVCameraManager sharedInstance];
avf->width = width;
avf->height = height;
avf->manager.width = width;
avf->manager.height = height;
// Check if we're on the main thread
if ([NSThread isMainThread]) {
RARCH_LOG("[Camera]: Initializing on main thread\n");
// Direct initialization on main thread
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (status != AVAuthorizationStatusAuthorized) {
RARCH_ERR("[Camera]: Camera access not authorized (status: %d)\n", (int)status);
free(avf);
return;
}
}];
} else {
RARCH_LOG("[Camera]: Initializing on background thread\n");
// Use dispatch_sync to run authorization check on main thread
__block AVAuthorizationStatus status;
dispatch_sync(dispatch_get_main_queue(), ^{
status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
});
if (status != AVAuthorizationStatusAuthorized) {
RARCH_ERR("[Camera]: Camera access not authorized (status: %d)\n", (int)status);
free(avf);
return NULL;
}
}
// Allocate frame buffer
avf->manager.frameBuffer = (uint32_t*)calloc(width * height, sizeof(uint32_t));
if (!avf->manager.frameBuffer) {
RARCH_ERR("[Camera]: Failed to allocate frame buffer\n");
free(avf);
return NULL;
}
// Initialize capture session on main thread
__block bool setupSuccess = false;
if ([NSThread isMainThread]) {
@autoreleasepool {
setupSuccess = [avf->manager setupCameraSession];
if (setupSuccess) {
[avf->manager.session startRunning];
RARCH_LOG("[Camera]: Started camera session\n");
}
}
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
@autoreleasepool {
setupSuccess = [avf->manager setupCameraSession];
if (setupSuccess) {
[avf->manager.session startRunning];
RARCH_LOG("[Camera]: Started camera session\n");
}
}
});
}
if (!setupSuccess) {
RARCH_ERR("[Camera]: Failed to setup camera\n");
free(avf->manager.frameBuffer);
free(avf);
return NULL;
}
// Add a check to verify the session is actually running
if (!avf->manager.session.isRunning) {
RARCH_ERR("[Camera]: Failed to start camera session\n");
free(avf->manager.frameBuffer);
free(avf);
return NULL;
}
RARCH_LOG("[Camera]: AVFoundation camera initialized and started successfully\n");
return avf;
}
static void avfoundation_free(void *data)
{
avfoundation_t *avf = (avfoundation_t*)data;
if (!avf)
return;
RARCH_LOG("[Camera]: Freeing AVFoundation camera\n");
if (avf->manager.session) {
[avf->manager.session stopRunning];
}
if (avf->manager.frameBuffer) {
free(avf->manager.frameBuffer);
avf->manager.frameBuffer = NULL;
}
free(avf);
RARCH_LOG("[Camera]: AVFoundation camera freed\n");
}
static bool avfoundation_start(void *data)
{
avfoundation_t *avf = (avfoundation_t*)data;
if (!avf || !avf->manager.session) {
RARCH_ERR("[Camera]: Cannot start - invalid data\n");
return false;
}
RARCH_LOG("[Camera]: Starting AVFoundation camera\n");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[avf->manager.session startRunning];
RARCH_LOG("[Camera]: Camera session started on background thread\n");
});
// Give the session a moment to start
usleep(100000); // 100ms
bool isRunning = avf->manager.session.isRunning;
RARCH_LOG("[Camera]: Camera session running: %s\n", isRunning ? "YES" : "NO");
return isRunning;
}
static void avfoundation_stop(void *data)
{
avfoundation_t *avf = (avfoundation_t*)data;
if (!avf || !avf->manager.session)
return;
RARCH_LOG("[Camera]: Stopping AVFoundation camera\n");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[avf->manager.session stopRunning];
RARCH_LOG("[Camera]: Camera session stopped on background thread\n");
});
}
static bool avfoundation_poll(void *data,
retro_camera_frame_raw_framebuffer_t frame_raw_cb,
retro_camera_frame_opengl_texture_t frame_gl_cb)
{
avfoundation_t *avf = (avfoundation_t*)data;
if (!avf || !frame_raw_cb) {
RARCH_ERR("[Camera]: Cannot poll - invalid data or callback\n");
return false;
}
if (!avf->manager.session.isRunning) {
RARCH_LOG("[Camera]: Camera not running, generating color bars\n");
uint32_t *tempBuffer = (uint32_t*)calloc(avf->width * avf->height, sizeof(uint32_t));
if (tempBuffer) {
generateColorBars(tempBuffer, avf->width, avf->height);
frame_raw_cb(tempBuffer, avf->width, avf->height, avf->width * 4);
free(tempBuffer);
return true;
}
return false;
}
#ifdef DEBUG
RARCH_LOG("[Camera]: Delivering camera frame\n");
#endif
frame_raw_cb(avf->manager.frameBuffer, avf->width, avf->height, avf->width * 4);
return true;
}
camera_driver_t camera_avfoundation = {
avfoundation_init,
avfoundation_free,
avfoundation_start,
avfoundation_stop,
avfoundation_poll,
"avfoundation"
};

View File

@ -65,6 +65,10 @@
#include "../location/drivers/corelocation.m"
#endif
#ifdef HAVE_AVF
#include "../camera/drivers/avfoundation.m"
#endif
#if defined(HAVE_DISCORD)
#include "../deps/discord-rpc/src/discord_register_osx.m"
#endif

View File

@ -91,6 +91,7 @@ OTHER_CFLAGS[arch=x86_64] = $(inherited) -DHAVE_SSE
OTHER_CFLAGS[arch=arm64*] = $(inherited) -D__ARM_NEON__ -DHAVE_NEON
OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DGL_SILENCE_DEPRECATION
OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DHAVE_AVF
OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DHAVE_COMMAND
OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DHAVE_COREAUDIO3
OTHER_CFLAGS[sdk=macosx*] = $(inherited) -DHAVE_COREMIDI
@ -124,6 +125,7 @@ OTHER_CFLAGS[sdk=iphonesimulator*] = $(inherited) $(OTHER_CFLAGS_IOS_TVOS_SHARE)
OTHER_CFLAGS[sdk=appletvos*] = $(inherited) $(OTHER_CFLAGS_IOS_TVOS_SHARE)
OTHER_CFLAGS[sdk=appletvsimulator*] = $(inherited) $(OTHER_CFLAGS_IOS_TVOS_SHARE)
OTHER_CFLAGS_IOS = $(inherited) -DHAVE_AVF
OTHER_CFLAGS_IOS = $(inherited) -DHAVE_COREMIDI
OTHER_CFLAGS_IOS = $(inherited) -DHAVE_COREMOTION
OTHER_CFLAGS_IOS = $(inherited) -DHAVE_IOS_CUSTOMKEYBOARD

View File

@ -38,6 +38,9 @@
05A8E23C20A63CF50084ABDA /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05A8E23B20A63CF50084ABDA /* QuartzCore.framework */; };
05D7753520A567A400646447 /* griffin_cpp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 05D7753320A5678300646447 /* griffin_cpp.cpp */; };
05D7753720A567A700646447 /* griffin_glslang.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 05D7753420A5678400646447 /* griffin_glslang.cpp */; };
0703E3292D76B6ED00D7B9BE /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0703E3282D76B6ED00D7B9BE /* CoreMedia.framework */; };
0703E32A2D76B6F400D7B9BE /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0703E3282D76B6ED00D7B9BE /* CoreMedia.framework */; };
0703E32B2D76B6FA00D7B9BE /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0703E3282D76B6ED00D7B9BE /* CoreMedia.framework */; };
07097FFB2D60F4C80021608F /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07097FFA2D60F4C80021608F /* CoreMIDI.framework */; };
07097FFC2D60F4D00021608F /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07097FFA2D60F4C80021608F /* CoreMIDI.framework */; };
07097FFD2D60F4D60021608F /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07097FFA2D60F4C80021608F /* CoreMIDI.framework */; };
@ -68,6 +71,9 @@
0720995929B1258C001642BB /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84DD5EB41A89E737007336C1 /* IOKit.framework */; };
072976DD296284F600D6E00C /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 072976DC296284F600D6E00C /* OpenGL.framework */; };
0746953A2997393000CCB7BD /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 074695362995C03900CCB7BD /* GameController.framework */; };
074A924B2D76B1850084364A /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 074A924A2D76B1850084364A /* Accelerate.framework */; };
074A924C2D76B18D0084364A /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 074A924A2D76B1850084364A /* Accelerate.framework */; };
074A924D2D76B19A0084364A /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 074A924A2D76B1850084364A /* Accelerate.framework */; };
075650252C488918004C5E7E /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 075650242C488918004C5E7E /* CloudKit.framework */; };
076512622D64E99200E1F6BE /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 076512612D64E99200E1F6BE /* CoreLocation.framework */; };
076512632D64E99A00E1F6BE /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 076512612D64E99200E1F6BE /* CoreLocation.framework */; };
@ -449,12 +455,14 @@
05F2873F20F2BEEA00632D47 /* task_content.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = task_content.c; sourceTree = "<group>"; };
05F2874020F2BEEA00632D47 /* task_http.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = task_http.c; sourceTree = "<group>"; };
05F2874120F2BEEA00632D47 /* task_patch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = task_patch.c; sourceTree = "<group>"; };
0703E3282D76B6ED00D7B9BE /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
07097FFA2D60F4C80021608F /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = System/Library/Frameworks/CoreMIDI.framework; sourceTree = SDKROOT; };
070A883F2A4E7A1B003161C0 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; };
071051BE2BEFEFBA009C29D8 /* Info_AppStore.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info_AppStore.plist; path = OSX/Info_AppStore.plist; sourceTree = "<group>"; };
0720996029B1258C001642BB /* RetroArch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RetroArch.app; sourceTree = BUILT_PRODUCTS_DIR; };
072976DC296284F600D6E00C /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
074695362995C03900CCB7BD /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
074A924A2D76B1850084364A /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
075650242C488918004C5E7E /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
075650262C48B417004C5E7E /* RetroArchCI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RetroArchCI.entitlements; sourceTree = "<group>"; };
076512612D64E99200E1F6BE /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; };
@ -525,6 +533,7 @@
0720994C29B1258C001642BB /* OpenGL.framework in Frameworks */,
0720994D29B1258C001642BB /* GameController.framework in Frameworks */,
0720994F29B1258C001642BB /* AVFoundation.framework in Frameworks */,
0703E3292D76B6ED00D7B9BE /* CoreMedia.framework in Frameworks */,
0720995029B1258C001642BB /* QuartzCore.framework in Frameworks */,
0720995129B1258C001642BB /* IOSurface.framework in Frameworks */,
0720995229B1258C001642BB /* CoreHaptics.framework in Frameworks */,
@ -536,6 +545,7 @@
076512642D64E9A000E1F6BE /* CoreLocation.framework in Frameworks */,
0720995629B1258C001642BB /* CoreAudio.framework in Frameworks */,
0720995729B1258C001642BB /* AudioUnit.framework in Frameworks */,
074A924D2D76B19A0084364A /* Accelerate.framework in Frameworks */,
0720995829B1258C001642BB /* AppKit.framework in Frameworks */,
0720995929B1258C001642BB /* IOKit.framework in Frameworks */,
);
@ -548,6 +558,8 @@
07F2BBD32BE83A4700FD1295 /* AudioToolbox.framework in Frameworks */,
07F2BBD42BE83A4700FD1295 /* OpenGL.framework in Frameworks */,
07F2BBD52BE83A4700FD1295 /* GameController.framework in Frameworks */,
074A924C2D76B18D0084364A /* Accelerate.framework in Frameworks */,
0703E32B2D76B6FA00D7B9BE /* CoreMedia.framework in Frameworks */,
07F2BBD72BE83A4700FD1295 /* AVFoundation.framework in Frameworks */,
07F2BBD82BE83A4700FD1295 /* QuartzCore.framework in Frameworks */,
07F2BBD92BE83A4700FD1295 /* IOSurface.framework in Frameworks */,
@ -573,6 +585,8 @@
D27C508C2228362700113BC0 /* AudioToolbox.framework in Frameworks */,
072976DD296284F600D6E00C /* OpenGL.framework in Frameworks */,
0746953A2997393000CCB7BD /* GameController.framework in Frameworks */,
074A924B2D76B1850084364A /* Accelerate.framework in Frameworks */,
0703E32A2D76B6F400D7B9BE /* CoreMedia.framework in Frameworks */,
D27C508B2228361D00113BC0 /* AVFoundation.framework in Frameworks */,
05A8E23C20A63CF50084ABDA /* QuartzCore.framework in Frameworks */,
05A8E23A20A63CED0084ABDA /* IOSurface.framework in Frameworks */,
@ -1259,6 +1273,8 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
0703E3282D76B6ED00D7B9BE /* CoreMedia.framework */,
074A924A2D76B1850084364A /* Accelerate.framework */,
076512612D64E99200E1F6BE /* CoreLocation.framework */,
07097FFA2D60F4C80021608F /* CoreMIDI.framework */,
075650242C488918004C5E7E /* CloudKit.framework */,