SDL_camera_coremedia.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
  4. This software is provided 'as-is', without any express or implied
  5. warranty. In no event will the authors be held liable for any damages
  6. arising from the use of this software.
  7. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #include "SDL_internal.h"
  19. #ifdef SDL_CAMERA_DRIVER_COREMEDIA
  20. #include "../SDL_syscamera.h"
  21. #include "../SDL_camera_c.h"
  22. #include "../../thread/SDL_systhread.h"
  23. #import <AVFoundation/AVFoundation.h>
  24. #import <CoreMedia/CoreMedia.h>
  25. /*
  26. * Need to link with:: CoreMedia CoreVideo
  27. *
  28. * Add in pInfo.list:
  29. * <key>NSCameraUsageDescription</key> <string>Access camera</string>
  30. *
  31. *
  32. * MACOSX:
  33. * Add to the Code Sign Entitlement file:
  34. * <key>com.apple.security.device.camera</key> <true/>
  35. */
  36. static SDL_PixelFormatEnum CoreMediaFormatToSDL(FourCharCode fmt)
  37. {
  38. switch (fmt) {
  39. #define CASE(x, y) case x: return y
  40. // the 16LE ones should use 16BE if we're on a Bigendian system like PowerPC,
  41. // but at current time there is no bigendian Apple platform that has CoreMedia.
  42. CASE(kCMPixelFormat_16LE555, SDL_PIXELFORMAT_XRGB1555);
  43. CASE(kCMPixelFormat_16LE5551, SDL_PIXELFORMAT_RGBA5551);
  44. CASE(kCMPixelFormat_16LE565, SDL_PIXELFORMAT_RGB565);
  45. CASE(kCMPixelFormat_24RGB, SDL_PIXELFORMAT_RGB24);
  46. CASE(kCMPixelFormat_32ARGB, SDL_PIXELFORMAT_ARGB32);
  47. CASE(kCMPixelFormat_32BGRA, SDL_PIXELFORMAT_BGRA32);
  48. CASE(kCMPixelFormat_422YpCbCr8, SDL_PIXELFORMAT_UYVY);
  49. CASE(kCMPixelFormat_422YpCbCr8_yuvs, SDL_PIXELFORMAT_YUY2);
  50. CASE(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, SDL_PIXELFORMAT_NV12);
  51. CASE(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, SDL_PIXELFORMAT_NV12);
  52. CASE(kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange, SDL_PIXELFORMAT_P010);
  53. CASE(kCVPixelFormatType_420YpCbCr10BiPlanarFullRange, SDL_PIXELFORMAT_P010);
  54. #undef CASE
  55. default:
  56. #if DEBUG_CAMERA
  57. SDL_Log("CAMERA: Unknown format FourCharCode '%d'", (int) fmt);
  58. #endif
  59. break;
  60. }
  61. return SDL_PIXELFORMAT_UNKNOWN;
  62. }
  63. @class SDLCaptureVideoDataOutputSampleBufferDelegate;
  64. // just a simple wrapper to help ARC manage memory...
  65. @interface SDLPrivateCameraData : NSObject
  66. @property(nonatomic, retain) AVCaptureSession *session;
  67. @property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate;
  68. @property(nonatomic, assign) CMSampleBufferRef current_sample;
  69. @end
  70. @implementation SDLPrivateCameraData
  71. @end
  72. static SDL_bool CheckCameraPermissions(SDL_CameraDevice *device)
  73. {
  74. if (device->permission == 0) { // still expecting a permission result.
  75. if (@available(macOS 14, *)) {
  76. const AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
  77. if (status != AVAuthorizationStatusNotDetermined) { // NotDetermined == still waiting for an answer from the user.
  78. SDL_CameraDevicePermissionOutcome(device, (status == AVAuthorizationStatusAuthorized) ? SDL_TRUE : SDL_FALSE);
  79. }
  80. } else {
  81. SDL_CameraDevicePermissionOutcome(device, SDL_TRUE); // always allowed (or just unqueryable...?) on older macOS.
  82. }
  83. }
  84. return (device->permission > 0);
  85. }
  86. // this delegate just receives new video frames on a Grand Central Dispatch queue, and fires off the
  87. // main device thread iterate function directly to consume it.
  88. @interface SDLCaptureVideoDataOutputSampleBufferDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
  89. @property SDL_CameraDevice *device;
  90. -(id) init:(SDL_CameraDevice *) dev;
  91. -(void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
  92. @end
  93. @implementation SDLCaptureVideoDataOutputSampleBufferDelegate
  94. -(id) init:(SDL_CameraDevice *) dev {
  95. if ( self = [super init] ) {
  96. _device = dev;
  97. }
  98. return self;
  99. }
  100. - (void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
  101. {
  102. SDL_CameraDevice *device = self.device;
  103. if (!device || !device->hidden) {
  104. return; // oh well.
  105. }
  106. if (!CheckCameraPermissions(device)) {
  107. return; // nothing to do right now, dump what is probably a completely black frame.
  108. }
  109. SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
  110. hidden.current_sample = sampleBuffer;
  111. SDL_CameraThreadIterate(device);
  112. hidden.current_sample = NULL;
  113. }
  114. - (void)captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
  115. {
  116. #if DEBUG_CAMERA
  117. SDL_Log("CAMERA: Drop frame.");
  118. #endif
  119. }
  120. @end
  121. static int COREMEDIA_WaitDevice(SDL_CameraDevice *device)
  122. {
  123. return 0; // this isn't used atm, since we run our own thread out of Grand Central Dispatch.
  124. }
  125. static int COREMEDIA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
  126. {
  127. int retval = 1;
  128. SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
  129. CMSampleBufferRef sample_buffer = hidden.current_sample;
  130. hidden.current_sample = NULL;
  131. SDL_assert(sample_buffer != NULL); // should only have been called from our delegate with a new frame.
  132. CMSampleTimingInfo timinginfo;
  133. if (CMSampleBufferGetSampleTimingInfo(sample_buffer, 0, &timinginfo) == noErr) {
  134. *timestampNS = (Uint64) (CMTimeGetSeconds(timinginfo.presentationTimeStamp) * ((Float64) SDL_NS_PER_SECOND));
  135. } else {
  136. SDL_assert(!"this shouldn't happen, I think.");
  137. *timestampNS = 0;
  138. }
  139. CVImageBufferRef image = CMSampleBufferGetImageBuffer(sample_buffer); // does not retain `image` (and we don't want it to).
  140. const int numPlanes = (int) CVPixelBufferGetPlaneCount(image);
  141. const int planar = (int) CVPixelBufferIsPlanar(image);
  142. #if DEBUG_CAMERA
  143. const int w = (int) CVPixelBufferGetWidth(image);
  144. const int h = (int) CVPixelBufferGetHeight(image);
  145. const int sz = (int) CVPixelBufferGetDataSize(image);
  146. const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
  147. SDL_Log("CAMERA: buffer planar=%d numPlanes=%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch);
  148. #endif
  149. // !!! FIXME: this currently copies the data to the surface (see FIXME about non-contiguous planar surfaces, but in theory we could just keep this locked until ReleaseFrame...
  150. CVPixelBufferLockBaseAddress(image, 0);
  151. frame->w = (int)CVPixelBufferGetWidth(image);
  152. frame->h = (int)CVPixelBufferGetHeight(image);
  153. if ((planar == 0) && (numPlanes == 0)) {
  154. const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
  155. const size_t buflen = pitch * frame->h;
  156. frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
  157. if (frame->pixels == NULL) {
  158. retval = -1;
  159. } else {
  160. frame->pitch = pitch;
  161. SDL_memcpy(frame->pixels, CVPixelBufferGetBaseAddress(image), buflen);
  162. }
  163. } else {
  164. // !!! FIXME: we have an open issue in SDL3 to allow SDL_Surface to support non-contiguous planar data, but we don't have it yet.
  165. size_t buflen = 0;
  166. for (int i = 0; i < numPlanes; i++) {
  167. size_t plane_height = CVPixelBufferGetHeightOfPlane(image, i);
  168. size_t plane_pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
  169. size_t plane_size = (plane_pitch * plane_height);
  170. buflen += plane_size;
  171. }
  172. frame->pitch = (int)CVPixelBufferGetBytesPerRowOfPlane(image, 0); // this is what SDL3 currently expects
  173. frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
  174. if (frame->pixels == NULL) {
  175. retval = -1;
  176. } else {
  177. Uint8 *dst = frame->pixels;
  178. for (int i = 0; i < numPlanes; i++) {
  179. const void *src = CVPixelBufferGetBaseAddressOfPlane(image, i);
  180. size_t plane_height = CVPixelBufferGetHeightOfPlane(image, i);
  181. size_t plane_pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
  182. size_t plane_size = (plane_pitch * plane_height);
  183. SDL_memcpy(dst, src, plane_size);
  184. dst += plane_size;
  185. }
  186. }
  187. }
  188. CVPixelBufferUnlockBaseAddress(image, 0);
  189. return retval;
  190. }
  191. static void COREMEDIA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
  192. {
  193. // !!! FIXME: this currently copies the data to the surface, but in theory we could just keep this locked until ReleaseFrame...
  194. SDL_aligned_free(frame->pixels);
  195. }
  196. static void COREMEDIA_CloseDevice(SDL_CameraDevice *device)
  197. {
  198. if (device && device->hidden) {
  199. SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden);
  200. device->hidden = NULL;
  201. AVCaptureSession *session = hidden.session;
  202. if (session) {
  203. hidden.session = nil;
  204. [session stopRunning];
  205. [session removeInput:[session.inputs objectAtIndex:0]];
  206. [session removeOutput:(AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]];
  207. session = nil;
  208. }
  209. hidden.delegate = NULL;
  210. hidden.current_sample = NULL;
  211. }
  212. }
  213. static int COREMEDIA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
  214. {
  215. AVCaptureDevice *avdevice = (__bridge AVCaptureDevice *) device->handle;
  216. // Pick format that matches the spec
  217. const SDL_PixelFormatEnum sdlfmt = spec->format;
  218. const int w = spec->width;
  219. const int h = spec->height;
  220. const int rate = spec->interval_denominator;
  221. AVCaptureDeviceFormat *spec_format = nil;
  222. NSArray<AVCaptureDeviceFormat *> *formats = [avdevice formats];
  223. for (AVCaptureDeviceFormat *format in formats) {
  224. CMFormatDescriptionRef formatDescription = [format formatDescription];
  225. if (CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(formatDescription)) != sdlfmt) {
  226. continue;
  227. }
  228. const CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
  229. if ( ((int) dim.width != w) || (((int) dim.height) != h) ) {
  230. continue;
  231. }
  232. for (AVFrameRateRange *framerate in format.videoSupportedFrameRateRanges) {
  233. if ((rate == (int) SDL_ceil((double) framerate.minFrameRate)) || (rate == (int) SDL_floor((double) framerate.maxFrameRate))) {
  234. spec_format = format;
  235. break;
  236. }
  237. }
  238. if (spec_format != nil) {
  239. break;
  240. }
  241. }
  242. if (spec_format == nil) {
  243. return SDL_SetError("camera spec format not available");
  244. } else if (![avdevice lockForConfiguration:NULL]) {
  245. return SDL_SetError("Cannot lockForConfiguration");
  246. }
  247. avdevice.activeFormat = spec_format;
  248. [avdevice unlockForConfiguration];
  249. AVCaptureSession *session = [[AVCaptureSession alloc] init];
  250. if (session == nil) {
  251. return SDL_SetError("Failed to allocate/init AVCaptureSession");
  252. }
  253. session.sessionPreset = AVCaptureSessionPresetHigh;
  254. NSError *error = nil;
  255. AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:avdevice error:&error];
  256. if (!input) {
  257. return SDL_SetError("Cannot create AVCaptureDeviceInput");
  258. }
  259. AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
  260. if (!output) {
  261. return SDL_SetError("Cannot create AVCaptureVideoDataOutput");
  262. }
  263. output.videoSettings = @{
  264. (id)kCVPixelBufferWidthKey : @(spec->width),
  265. (id)kCVPixelBufferHeightKey : @(spec->height),
  266. (id)kCVPixelBufferPixelFormatTypeKey : @(CMFormatDescriptionGetMediaSubType([spec_format formatDescription]))
  267. };
  268. char threadname[64];
  269. SDL_GetCameraThreadName(device, threadname, sizeof (threadname));
  270. dispatch_queue_t queue = dispatch_queue_create(threadname, NULL);
  271. //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  272. if (!queue) {
  273. return SDL_SetError("dispatch_queue_create() failed");
  274. }
  275. SDLCaptureVideoDataOutputSampleBufferDelegate *delegate = [[SDLCaptureVideoDataOutputSampleBufferDelegate alloc] init:device];
  276. if (delegate == nil) {
  277. return SDL_SetError("Cannot create SDLCaptureVideoDataOutputSampleBufferDelegate");
  278. }
  279. [output setSampleBufferDelegate:delegate queue:queue];
  280. if (![session canAddInput:input]) {
  281. return SDL_SetError("Cannot add AVCaptureDeviceInput");
  282. }
  283. [session addInput:input];
  284. if (![session canAddOutput:output]) {
  285. return SDL_SetError("Cannot add AVCaptureVideoDataOutput");
  286. }
  287. [session addOutput:output];
  288. [session commitConfiguration];
  289. SDLPrivateCameraData *hidden = [[SDLPrivateCameraData alloc] init];
  290. if (hidden == nil) {
  291. return SDL_SetError("Cannot create SDLPrivateCameraData");
  292. }
  293. hidden.session = session;
  294. hidden.delegate = delegate;
  295. hidden.current_sample = NULL;
  296. device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden);
  297. [session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`?
  298. CheckCameraPermissions(device); // check right away, in case the process is already granted permission.
  299. return 0;
  300. }
  301. static void COREMEDIA_FreeDeviceHandle(SDL_CameraDevice *device)
  302. {
  303. if (device && device->handle) {
  304. CFBridgingRelease(device->handle);
  305. }
  306. }
  307. static void GatherCameraSpecs(AVCaptureDevice *device, CameraFormatAddData *add_data)
  308. {
  309. SDL_zerop(add_data);
  310. for (AVCaptureDeviceFormat *fmt in device.formats) {
  311. if (CMFormatDescriptionGetMediaType(fmt.formatDescription) != kCMMediaType_Video) {
  312. continue;
  313. }
  314. const SDL_PixelFormatEnum sdlfmt = CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(fmt.formatDescription));
  315. if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
  316. continue;
  317. }
  318. const CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(fmt.formatDescription);
  319. const int w = (int) dims.width;
  320. const int h = (int) dims.height;
  321. for (AVFrameRateRange *framerate in fmt.videoSupportedFrameRateRanges) {
  322. int rate;
  323. rate = (int) SDL_ceil((double) framerate.minFrameRate);
  324. if (rate) {
  325. SDL_AddCameraFormat(add_data, sdlfmt, w, h, 1, rate);
  326. }
  327. rate = (int) SDL_floor((double) framerate.maxFrameRate);
  328. if (rate) {
  329. SDL_AddCameraFormat(add_data, sdlfmt, w, h, 1, rate);
  330. }
  331. }
  332. }
  333. }
  334. static SDL_bool FindCoreMediaCameraDeviceByUniqueID(SDL_CameraDevice *device, void *userdata)
  335. {
  336. NSString *uniqueid = (__bridge NSString *) userdata;
  337. AVCaptureDevice *avdev = (__bridge AVCaptureDevice *) device->handle;
  338. return ([uniqueid isEqualToString:avdev.uniqueID]) ? SDL_TRUE : SDL_FALSE;
  339. }
  340. static void MaybeAddDevice(AVCaptureDevice *avdevice)
  341. {
  342. if (!avdevice.connected) {
  343. return; // not connected.
  344. } else if (![avdevice hasMediaType:AVMediaTypeVideo]) {
  345. return; // not a camera.
  346. } else if (SDL_FindPhysicalCameraDeviceByCallback(FindCoreMediaCameraDeviceByUniqueID, (__bridge void *) avdevice.uniqueID)) {
  347. return; // already have this one.
  348. }
  349. CameraFormatAddData add_data;
  350. GatherCameraSpecs(avdevice, &add_data);
  351. if (add_data.num_specs > 0) {
  352. SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
  353. if (avdevice.position == AVCaptureDevicePositionFront) {
  354. position = SDL_CAMERA_POSITION_FRONT_FACING;
  355. } else if (avdevice.position == AVCaptureDevicePositionBack) {
  356. position = SDL_CAMERA_POSITION_BACK_FACING;
  357. }
  358. SDL_AddCameraDevice(avdevice.localizedName.UTF8String, position, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(avdevice));
  359. }
  360. SDL_free(add_data.specs);
  361. }
  362. static void COREMEDIA_DetectDevices(void)
  363. {
  364. NSArray<AVCaptureDevice *> *devices = nil;
  365. if (@available(macOS 10.15, iOS 13, *)) {
  366. // kind of annoying that there isn't a "give me anything that looks like a camera" option,
  367. // so this list will need to be updated when Apple decides to add
  368. // AVCaptureDeviceTypeBuiltInQuadrupleCamera some day.
  369. NSArray *device_types = @[
  370. #ifdef SDL_PLATFORM_IOS
  371. AVCaptureDeviceTypeBuiltInTelephotoCamera,
  372. AVCaptureDeviceTypeBuiltInDualCamera,
  373. AVCaptureDeviceTypeBuiltInDualWideCamera,
  374. AVCaptureDeviceTypeBuiltInTripleCamera,
  375. AVCaptureDeviceTypeBuiltInUltraWideCamera,
  376. #else
  377. AVCaptureDeviceTypeExternalUnknown,
  378. #endif
  379. AVCaptureDeviceTypeBuiltInWideAngleCamera
  380. ];
  381. AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
  382. discoverySessionWithDeviceTypes:device_types
  383. mediaType:AVMediaTypeVideo
  384. position:AVCaptureDevicePositionUnspecified];
  385. devices = discoverySession.devices;
  386. // !!! FIXME: this can use Key Value Observation to get hotplug events.
  387. } else {
  388. // this is deprecated but works back to macOS 10.7; 10.15 added AVCaptureDeviceDiscoverySession as a replacement.
  389. devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
  390. // !!! FIXME: this can use AVCaptureDeviceWasConnectedNotification and AVCaptureDeviceWasDisconnectedNotification with NSNotificationCenter to get hotplug events.
  391. }
  392. for (AVCaptureDevice *device in devices) {
  393. MaybeAddDevice(device);
  394. }
  395. }
  396. static void COREMEDIA_Deinitialize(void)
  397. {
  398. // !!! FIXME: disable hotplug.
  399. }
  400. static SDL_bool COREMEDIA_Init(SDL_CameraDriverImpl *impl)
  401. {
  402. impl->DetectDevices = COREMEDIA_DetectDevices;
  403. impl->OpenDevice = COREMEDIA_OpenDevice;
  404. impl->CloseDevice = COREMEDIA_CloseDevice;
  405. impl->WaitDevice = COREMEDIA_WaitDevice;
  406. impl->AcquireFrame = COREMEDIA_AcquireFrame;
  407. impl->ReleaseFrame = COREMEDIA_ReleaseFrame;
  408. impl->FreeDeviceHandle = COREMEDIA_FreeDeviceHandle;
  409. impl->Deinitialize = COREMEDIA_Deinitialize;
  410. impl->ProvidesOwnCallbackThread = SDL_TRUE;
  411. return SDL_TRUE;
  412. }
  413. CameraBootStrap COREMEDIA_bootstrap = {
  414. "coremedia", "SDL Apple CoreMedia camera driver", COREMEDIA_Init, SDL_FALSE
  415. };
  416. #endif // SDL_CAMERA_DRIVER_COREMEDIA