1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_syscamera.h"
24#include "SDL_camera_c.h"
25#include "../video/SDL_pixels_c.h"
26#include "../video/SDL_surface_c.h"
27#include "../thread/SDL_systhread.h"
28
29
30// A lot of this is a simplified version of SDL_audio.c; if fixing stuff here,
31// maybe check that file, too.
32
33// Available camera drivers
34static const CameraBootStrap *const bootstrap[] = {
35#ifdef SDL_CAMERA_DRIVER_V4L2
36 &V4L2_bootstrap,
37#endif
38#ifdef SDL_CAMERA_DRIVER_PIPEWIRE
39 &PIPEWIRECAMERA_bootstrap,
40#endif
41#ifdef SDL_CAMERA_DRIVER_COREMEDIA
42 &COREMEDIA_bootstrap,
43#endif
44#ifdef SDL_CAMERA_DRIVER_ANDROID
45 &ANDROIDCAMERA_bootstrap,
46#endif
47#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
48 &EMSCRIPTENCAMERA_bootstrap,
49#endif
50#ifdef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
51 &MEDIAFOUNDATION_bootstrap,
52#endif
53#ifdef SDL_CAMERA_DRIVER_VITA
54 &VITACAMERA_bootstrap,
55#endif
56#ifdef SDL_CAMERA_DRIVER_DUMMY
57 &DUMMYCAMERA_bootstrap,
58#endif
59 NULL
60};
61
62static SDL_CameraDriver camera_driver;
63
64
65int SDL_GetNumCameraDrivers(void)
66{
67 return SDL_arraysize(bootstrap) - 1;
68}
69
70const char *SDL_GetCameraDriver(int index)
71{
72 if (index >= 0 && index < SDL_GetNumCameraDrivers()) {
73 return bootstrap[index]->name;
74 }
75 SDL_InvalidParamError("index");
76 return NULL;
77}
78
79const char *SDL_GetCurrentCameraDriver(void)
80{
81 return camera_driver.name;
82}
83
84char *SDL_GetCameraThreadName(SDL_Camera *device, char *buf, size_t buflen)
85{
86 (void)SDL_snprintf(buf, buflen, "SDLCamera%d", (int) device->instance_id);
87 return buf;
88}
89
90bool SDL_AddCameraFormat(CameraFormatAddData *data, SDL_PixelFormat format, SDL_Colorspace colorspace, int w, int h, int framerate_numerator, int framerate_denominator)
91{
92 SDL_assert(data != NULL);
93 if (data->allocated_specs <= data->num_specs) {
94 const int newalloc = data->allocated_specs ? (data->allocated_specs * 2) : 16;
95 void *ptr = SDL_realloc(data->specs, sizeof (SDL_CameraSpec) * newalloc);
96 if (!ptr) {
97 return false;
98 }
99 data->specs = (SDL_CameraSpec *) ptr;
100 data->allocated_specs = newalloc;
101 }
102
103 SDL_CameraSpec *spec = &data->specs[data->num_specs];
104 spec->format = format;
105 spec->colorspace = colorspace;
106 spec->width = w;
107 spec->height = h;
108 spec->framerate_numerator = framerate_numerator;
109 spec->framerate_denominator = framerate_denominator;
110
111 data->num_specs++;
112
113 return true;
114}
115
116
117// Zombie device implementation...
118
119// These get used when a device is disconnected or fails. Apps that ignore the
120// loss notifications will get black frames but otherwise keep functioning.
121static bool ZombieWaitDevice(SDL_Camera *device)
122{
123 if (!SDL_GetAtomicInt(&device->shutdown)) {
124 // !!! FIXME: this is bad for several reasons (uses double, could be precalculated, doesn't track elapsed time).
125 const double duration = ((double) device->actual_spec.framerate_denominator / ((double) device->actual_spec.framerate_numerator));
126 SDL_Delay((Uint32) (duration * 1000.0));
127 }
128 return true;
129}
130
131static size_t GetFrameBufLen(const SDL_CameraSpec *spec)
132{
133 const size_t w = (const size_t) spec->width;
134 const size_t h = (const size_t) spec->height;
135 const size_t wxh = w * h;
136 const SDL_PixelFormat fmt = spec->format;
137
138 switch (fmt) {
139 // Some YUV formats have a larger Y plane than their U or V planes.
140 case SDL_PIXELFORMAT_YV12:
141 case SDL_PIXELFORMAT_IYUV:
142 case SDL_PIXELFORMAT_NV12:
143 case SDL_PIXELFORMAT_NV21:
144 return wxh + (wxh / 2);
145
146 default: break;
147 }
148
149 // this is correct for most things.
150 return wxh * SDL_BYTESPERPIXEL(fmt);
151}
152
153static SDL_CameraFrameResult ZombieAcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
154{
155 const SDL_CameraSpec *spec = &device->actual_spec;
156
157 if (!device->zombie_pixels) {
158 // attempt to allocate and initialize a fake frame of pixels.
159 const size_t buflen = GetFrameBufLen(&device->actual_spec);
160 device->zombie_pixels = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
161 if (!device->zombie_pixels) {
162 *timestampNS = 0;
163 return SDL_CAMERA_FRAME_SKIP; // oh well, say there isn't a frame yet, so we'll go back to waiting. Maybe allocation will succeed later...?
164 }
165
166 Uint8 *dst = device->zombie_pixels;
167 switch (spec->format) {
168 // in YUV formats, the U and V values must be 128 to get a black frame. If set to zero, it'll be bright green.
169 case SDL_PIXELFORMAT_YV12:
170 case SDL_PIXELFORMAT_IYUV:
171 case SDL_PIXELFORMAT_NV12:
172 case SDL_PIXELFORMAT_NV21:
173 SDL_memset(dst, 0, spec->width * spec->height); // set Y to zero.
174 SDL_memset(dst + (spec->width * spec->height), 128, (spec->width * spec->height) / 2); // set U and V to 128.
175 break;
176
177 case SDL_PIXELFORMAT_YUY2:
178 case SDL_PIXELFORMAT_YVYU:
179 // Interleaved Y1[U1|V1]Y2[U2|V2].
180 for (size_t i = 0; i < buflen; i += 4) {
181 dst[i] = 0;
182 dst[i+1] = 128;
183 dst[i+2] = 0;
184 dst[i+3] = 128;
185 }
186 break;
187
188
189 case SDL_PIXELFORMAT_UYVY:
190 // Interleaved [U1|V1]Y1[U2|V2]Y2.
191 for (size_t i = 0; i < buflen; i += 4) {
192 dst[i] = 128;
193 dst[i+1] = 0;
194 dst[i+2] = 128;
195 dst[i+3] = 0;
196 }
197 break;
198
199 default:
200 // just zero everything else, it'll _probably_ be okay.
201 SDL_memset(dst, 0, buflen);
202 break;
203 }
204 }
205
206
207 *timestampNS = SDL_GetTicksNS();
208 frame->pixels = device->zombie_pixels;
209
210 // SDL (currently) wants the pitch of YUV formats to be the pitch of the (1-byte-per-pixel) Y plane.
211 frame->pitch = spec->width;
212 if (!SDL_ISPIXELFORMAT_FOURCC(spec->format)) { // checking if it's not FOURCC to only do this for non-YUV data is good enough for now.
213 frame->pitch *= SDL_BYTESPERPIXEL(spec->format);
214 }
215
216 #if DEBUG_CAMERA
217 SDL_Log("CAMERA: dev[%p] Acquired Zombie frame, timestamp %llu", device, (unsigned long long) *timestampNS);
218 #endif
219
220 return SDL_CAMERA_FRAME_READY; // frame is available.
221}
222
223static void ZombieReleaseFrame(SDL_Camera *device, SDL_Surface *frame) // Reclaim frame->pixels and frame->pitch!
224{
225 if (frame->pixels != device->zombie_pixels) {
226 // this was a frame from before the disconnect event; let the backend make an attempt to free it.
227 camera_driver.impl.ReleaseFrame(device, frame);
228 }
229 // we just leave zombie_pixels alone, as we'll reuse it for every new frame until the camera is closed.
230}
231
232static void ClosePhysicalCamera(SDL_Camera *device)
233{
234 if (!device) {
235 return;
236 }
237
238 SDL_SetAtomicInt(&device->shutdown, 1);
239
240// !!! FIXME: the close_cond stuff from audio might help the race condition here.
241
242 if (device->thread != NULL) {
243 SDL_WaitThread(device->thread, NULL);
244 device->thread = NULL;
245 }
246
247 // release frames that are queued up somewhere...
248 if (!device->needs_conversion && !device->needs_scaling) {
249 for (SurfaceList *i = device->filled_output_surfaces.next; i != NULL; i = i->next) {
250 device->ReleaseFrame(device, i->surface);
251 }
252 for (SurfaceList *i = device->app_held_output_surfaces.next; i != NULL; i = i->next) {
253 device->ReleaseFrame(device, i->surface);
254 }
255 }
256
257 camera_driver.impl.CloseDevice(device);
258
259 SDL_DestroyProperties(device->props);
260
261 SDL_DestroySurface(device->acquire_surface);
262 device->acquire_surface = NULL;
263 SDL_DestroySurface(device->conversion_surface);
264 device->conversion_surface = NULL;
265
266 for (int i = 0; i < SDL_arraysize(device->output_surfaces); i++) {
267 SDL_DestroySurface(device->output_surfaces[i].surface);
268 }
269 SDL_zeroa(device->output_surfaces);
270
271 SDL_aligned_free(device->zombie_pixels);
272
273 device->permission = 0;
274 device->zombie_pixels = NULL;
275 device->filled_output_surfaces.next = NULL;
276 device->empty_output_surfaces.next = NULL;
277 device->app_held_output_surfaces.next = NULL;
278
279 device->base_timestamp = 0;
280 device->adjust_timestamp = 0;
281
282 SDL_zero(device->spec);
283}
284
285// Don't hold the device lock when calling this, as we may destroy the device!
286void UnrefPhysicalCamera(SDL_Camera *device)
287{
288 if (SDL_AtomicDecRef(&device->refcount)) {
289 // take it out of the device list.
290 SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
291 if (SDL_RemoveFromHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id)) {
292 SDL_AddAtomicInt(&camera_driver.device_count, -1);
293 }
294 SDL_UnlockRWLock(camera_driver.device_hash_lock);
295 }
296}
297
298void RefPhysicalCamera(SDL_Camera *device)
299{
300 SDL_AtomicIncRef(&device->refcount);
301}
302
303static void ObtainPhysicalCameraObj(SDL_Camera *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_ACQUIRE
304{
305 if (device) {
306 RefPhysicalCamera(device);
307 SDL_LockMutex(device->lock);
308 }
309}
310
311static SDL_Camera *ObtainPhysicalCamera(SDL_CameraID devid) // !!! FIXME: SDL_ACQUIRE
312{
313 if (!SDL_GetCurrentCameraDriver()) {
314 SDL_SetError("Camera subsystem is not initialized");
315 return NULL;
316 }
317
318 SDL_Camera *device = NULL;
319 SDL_LockRWLockForReading(camera_driver.device_hash_lock);
320 SDL_FindInHashTable(camera_driver.device_hash, (const void *) (uintptr_t) devid, (const void **) &device);
321 SDL_UnlockRWLock(camera_driver.device_hash_lock);
322 if (!device) {
323 SDL_SetError("Invalid camera device instance ID");
324 } else {
325 ObtainPhysicalCameraObj(device);
326 }
327 return device;
328}
329
330static void ReleaseCamera(SDL_Camera *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_RELEASE
331{
332 if (device) {
333 SDL_UnlockMutex(device->lock);
334 UnrefPhysicalCamera(device);
335 }
336}
337
338// we want these sorted by format first, so you can find a block of all
339// resolutions that are supported for a format. The formats are sorted in
340// "best" order, but that's subjective: right now, we prefer planar
341// formats, since they're likely what the cameras prefer to produce
342// anyhow, and they basically send the same information in less space
343// than an RGB-style format. After that, sort by bits-per-pixel.
344
345// we want specs sorted largest to smallest dimensions, larger width taking precedence over larger height.
346static int SDLCALL CameraSpecCmp(const void *vpa, const void *vpb)
347{
348 const SDL_CameraSpec *a = (const SDL_CameraSpec *) vpa;
349 const SDL_CameraSpec *b = (const SDL_CameraSpec *) vpb;
350
351 // driver shouldn't send specs like this, check here since we're eventually going to sniff the whole array anyhow.
352 SDL_assert(a->format != SDL_PIXELFORMAT_UNKNOWN);
353 SDL_assert(a->width > 0);
354 SDL_assert(a->height > 0);
355 SDL_assert(b->format != SDL_PIXELFORMAT_UNKNOWN);
356 SDL_assert(b->width > 0);
357 SDL_assert(b->height > 0);
358
359 const SDL_PixelFormat afmt = a->format;
360 const SDL_PixelFormat bfmt = b->format;
361 if (SDL_ISPIXELFORMAT_FOURCC(afmt) && !SDL_ISPIXELFORMAT_FOURCC(bfmt)) {
362 return -1;
363 } else if (!SDL_ISPIXELFORMAT_FOURCC(afmt) && SDL_ISPIXELFORMAT_FOURCC(bfmt)) {
364 return 1;
365 } else if (SDL_BITSPERPIXEL(afmt) > SDL_BITSPERPIXEL(bfmt)) {
366 return -1;
367 } else if (SDL_BITSPERPIXEL(bfmt) > SDL_BITSPERPIXEL(afmt)) {
368 return 1;
369 } else if (a->width > b->width) {
370 return -1;
371 } else if (b->width > a->width) {
372 return 1;
373 } else if (a->height > b->height) {
374 return -1;
375 } else if (b->height > a->height) {
376 return 1;
377 }
378
379 // still here? We care about framerate less than format or size, but faster is better than slow.
380 if (a->framerate_numerator && !b->framerate_numerator) {
381 return -1;
382 } else if (!a->framerate_numerator && b->framerate_numerator) {
383 return 1;
384 }
385
386 const float fpsa = ((float)a->framerate_numerator / a->framerate_denominator);
387 const float fpsb = ((float)b->framerate_numerator / b->framerate_denominator);
388 if (fpsa > fpsb) {
389 return -1;
390 } else if (fpsb > fpsa) {
391 return 1;
392 }
393
394 if (SDL_COLORSPACERANGE(a->colorspace) == SDL_COLOR_RANGE_FULL &&
395 SDL_COLORSPACERANGE(b->colorspace) != SDL_COLOR_RANGE_FULL) {
396 return -1;
397 }
398 if (SDL_COLORSPACERANGE(a->colorspace) != SDL_COLOR_RANGE_FULL &&
399 SDL_COLORSPACERANGE(b->colorspace) == SDL_COLOR_RANGE_FULL) {
400 return 1;
401 }
402
403 return 0; // apparently, they're equal.
404}
405
406// The camera backends call this when a new device is plugged in.
407SDL_Camera *SDL_AddCamera(const char *name, SDL_CameraPosition position, int num_specs, const SDL_CameraSpec *specs, void *handle)
408{
409 SDL_assert(name != NULL);
410 SDL_assert(num_specs >= 0);
411 SDL_assert((specs != NULL) == (num_specs > 0));
412 SDL_assert(handle != NULL);
413
414 SDL_LockRWLockForReading(camera_driver.device_hash_lock);
415 const int shutting_down = SDL_GetAtomicInt(&camera_driver.shutting_down);
416 SDL_UnlockRWLock(camera_driver.device_hash_lock);
417 if (shutting_down) {
418 return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment.
419 }
420
421 SDL_Camera *device = (SDL_Camera *)SDL_calloc(1, sizeof(SDL_Camera));
422 if (!device) {
423 return NULL;
424 }
425
426 device->name = SDL_strdup(name);
427 if (!device->name) {
428 SDL_free(device);
429 return NULL;
430 }
431
432 device->position = position;
433
434 device->lock = SDL_CreateMutex();
435 if (!device->lock) {
436 SDL_free(device->name);
437 SDL_free(device);
438 return NULL;
439 }
440
441 device->all_specs = (SDL_CameraSpec *)SDL_calloc(num_specs + 1, sizeof (*specs));
442 if (!device->all_specs) {
443 SDL_DestroyMutex(device->lock);
444 SDL_free(device->name);
445 SDL_free(device);
446 return NULL;
447 }
448
449 if (num_specs > 0) {
450 SDL_memcpy(device->all_specs, specs, sizeof (*specs) * num_specs);
451 SDL_qsort(device->all_specs, num_specs, sizeof (*specs), CameraSpecCmp);
452
453 // weed out duplicates, just in case.
454 for (int i = 0; i < num_specs; i++) {
455 SDL_CameraSpec *a = &device->all_specs[i];
456 SDL_CameraSpec *b = &device->all_specs[i + 1];
457 if (SDL_memcmp(a, b, sizeof (*a)) == 0) {
458 SDL_memmove(a, b, sizeof (*specs) * (num_specs - i));
459 i--;
460 num_specs--;
461 }
462 }
463 }
464
465 #if DEBUG_CAMERA
466 const char *posstr = "unknown position";
467 if (position == SDL_CAMERA_POSITION_FRONT_FACING) {
468 posstr = "front-facing";
469 } else if (position == SDL_CAMERA_POSITION_BACK_FACING) {
470 posstr = "back-facing";
471 }
472 SDL_Log("CAMERA: Adding device '%s' (%s) with %d spec%s%s", name, posstr, num_specs, (num_specs == 1) ? "" : "s", (num_specs == 0) ? "" : ":");
473 for (int i = 0; i < num_specs; i++) {
474 const SDL_CameraSpec *spec = &device->all_specs[i];
475 SDL_Log("CAMERA: - fmt=%s, w=%d, h=%d, numerator=%d, denominator=%d", SDL_GetPixelFormatName(spec->format), spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator);
476 }
477 #endif
478
479 device->num_specs = num_specs;
480 device->handle = handle;
481 device->instance_id = SDL_GetNextObjectID();
482 SDL_SetAtomicInt(&device->shutdown, 0);
483 SDL_SetAtomicInt(&device->zombie, 0);
484 RefPhysicalCamera(device);
485
486 SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
487 if (SDL_InsertIntoHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id, device, false)) {
488 SDL_AddAtomicInt(&camera_driver.device_count, 1);
489 } else {
490 SDL_DestroyMutex(device->lock);
491 SDL_free(device->all_specs);
492 SDL_free(device->name);
493 SDL_free(device);
494 device = NULL;
495 }
496
497 // Add a device add event to the pending list, to be pushed when the event queue is pumped (away from any of our internal threads).
498 if (device) {
499 SDL_PendingCameraEvent *p = (SDL_PendingCameraEvent *) SDL_malloc(sizeof (SDL_PendingCameraEvent));
500 if (p) { // if allocation fails, you won't get an event, but we can't help that.
501 p->type = SDL_EVENT_CAMERA_DEVICE_ADDED;
502 p->devid = device->instance_id;
503 p->next = NULL;
504 SDL_assert(camera_driver.pending_events_tail != NULL);
505 SDL_assert(camera_driver.pending_events_tail->next == NULL);
506 camera_driver.pending_events_tail->next = p;
507 camera_driver.pending_events_tail = p;
508 }
509 }
510 SDL_UnlockRWLock(camera_driver.device_hash_lock);
511
512 return device;
513}
514
515// Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the camera device's thread.
516void SDL_CameraDisconnected(SDL_Camera *device)
517{
518 if (!device) {
519 return;
520 }
521
522 #if DEBUG_CAMERA
523 SDL_Log("CAMERA: DISCONNECTED! dev[%p]", device);
524 #endif
525
526 // Save off removal info in a list so we can send events for each, next
527 // time the event queue pumps, in case something tries to close a device
528 // from an event filter, as this would risk deadlocks and other disasters
529 // if done from the device thread.
530 SDL_PendingCameraEvent pending;
531 pending.next = NULL;
532 SDL_PendingCameraEvent *pending_tail = &pending;
533
534 ObtainPhysicalCameraObj(device);
535
536 const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 1);
537 if (first_disconnect) { // if already disconnected this device, don't do it twice.
538 // Swap in "Zombie" versions of the usual platform interfaces, so the device will keep
539 // making progress until the app closes it. Otherwise, streams might continue to
540 // accumulate waste data that never drains, apps that depend on audio callbacks to
541 // progress will freeze, etc.
542 device->WaitDevice = ZombieWaitDevice;
543 device->AcquireFrame = ZombieAcquireFrame;
544 device->ReleaseFrame = ZombieReleaseFrame;
545
546 // Zombie functions will just report the timestamp as SDL_GetTicksNS(), so we don't need to adjust anymore to get it to match.
547 device->adjust_timestamp = 0;
548 device->base_timestamp = 0;
549
550 SDL_PendingCameraEvent *p = (SDL_PendingCameraEvent *) SDL_malloc(sizeof (SDL_PendingCameraEvent));
551 if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
552 p->type = SDL_EVENT_CAMERA_DEVICE_REMOVED;
553 p->devid = device->instance_id;
554 p->next = NULL;
555 pending_tail->next = p;
556 pending_tail = p;
557 }
558 }
559
560 ReleaseCamera(device);
561
562 if (first_disconnect) {
563 if (pending.next) { // NULL if event is disabled or disaster struck.
564 SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
565 SDL_assert(camera_driver.pending_events_tail != NULL);
566 SDL_assert(camera_driver.pending_events_tail->next == NULL);
567 camera_driver.pending_events_tail->next = pending.next;
568 camera_driver.pending_events_tail = pending_tail;
569 SDL_UnlockRWLock(camera_driver.device_hash_lock);
570 }
571 }
572}
573
574void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved)
575{
576 if (!device) {
577 return;
578 }
579
580 SDL_PendingCameraEvent pending;
581 pending.next = NULL;
582 SDL_PendingCameraEvent *pending_tail = &pending;
583
584 const int permission = approved ? 1 : -1;
585
586 ObtainPhysicalCameraObj(device);
587 if (device->permission != permission) {
588 device->permission = permission;
589 SDL_PendingCameraEvent *p = (SDL_PendingCameraEvent *) SDL_malloc(sizeof (SDL_PendingCameraEvent));
590 if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
591 p->type = approved ? SDL_EVENT_CAMERA_DEVICE_APPROVED : SDL_EVENT_CAMERA_DEVICE_DENIED;
592 p->devid = device->instance_id;
593 p->next = NULL;
594 pending_tail->next = p;
595 pending_tail = p;
596 }
597 }
598
599 ReleaseCamera(device);
600
601 if (pending.next) { // NULL if event is disabled or disaster struck.
602 SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
603 SDL_assert(camera_driver.pending_events_tail != NULL);
604 SDL_assert(camera_driver.pending_events_tail->next == NULL);
605 camera_driver.pending_events_tail->next = pending.next;
606 camera_driver.pending_events_tail = pending_tail;
607 SDL_UnlockRWLock(camera_driver.device_hash_lock);
608 }
609}
610
611typedef struct FindOnePhysicalCameraByCallbackData
612{
613 bool (*callback)(SDL_Camera *device, void *userdata);
614 void *userdata;
615 SDL_Camera *device;
616} FindOnePhysicalCameraByCallbackData;
617
618static bool SDLCALL FindOnePhysicalCameraByCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
619{
620 FindOnePhysicalCameraByCallbackData *data = (FindOnePhysicalCameraByCallbackData *) userdata;
621 SDL_Camera *device = (SDL_Camera *) value;
622 if (data->callback(device, data->userdata)) {
623 data->device = device;
624 return false; // stop iterating.
625 }
626 return true; // keep iterating.
627}
628
629// !!! FIXME: this doesn't follow SDL convention of `userdata` being the first param of the callback.
630SDL_Camera *SDL_FindPhysicalCameraByCallback(bool (*callback)(SDL_Camera *device, void *userdata), void *userdata)
631{
632 if (!SDL_GetCurrentCameraDriver()) {
633 SDL_SetError("Camera subsystem is not initialized");
634 return NULL;
635 }
636
637
638 FindOnePhysicalCameraByCallbackData data = { callback, userdata, NULL };
639 SDL_LockRWLockForReading(camera_driver.device_hash_lock);
640 SDL_IterateHashTable(camera_driver.device_hash, FindOnePhysicalCameraByCallback, &data);
641 SDL_UnlockRWLock(camera_driver.device_hash_lock);
642
643 if (!data.device) {
644 SDL_SetError("Device not found");
645 }
646
647 return data.device;
648}
649
650void SDL_CloseCamera(SDL_Camera *camera)
651{
652 SDL_Camera *device = camera; // currently there's no separation between physical and logical device.
653 ClosePhysicalCamera(device);
654}
655
656bool SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec)
657{
658 bool result;
659
660 if (!camera) {
661 return SDL_InvalidParamError("camera");
662 } else if (!spec) {
663 return SDL_InvalidParamError("spec");
664 }
665
666 SDL_Camera *device = camera; // currently there's no separation between physical and logical device.
667 ObtainPhysicalCameraObj(device);
668 if (device->permission > 0) {
669 SDL_copyp(spec, &device->spec);
670 result = true;
671 } else {
672 SDL_zerop(spec);
673 result = SDL_SetError("Camera permission has not been granted");
674 }
675 ReleaseCamera(device);
676
677 return result;
678}
679
680const char *SDL_GetCameraName(SDL_CameraID instance_id)
681{
682 const char *result = NULL;
683 SDL_Camera *device = ObtainPhysicalCamera(instance_id);
684 if (device) {
685 result = SDL_GetPersistentString(device->name);
686 ReleaseCamera(device);
687 }
688 return result;
689}
690
691SDL_CameraPosition SDL_GetCameraPosition(SDL_CameraID instance_id)
692{
693 SDL_CameraPosition result = SDL_CAMERA_POSITION_UNKNOWN;
694 SDL_Camera *device = ObtainPhysicalCamera(instance_id);
695 if (device) {
696 result = device->position;
697 ReleaseCamera(device);
698 }
699 return result;
700}
701
702
703typedef struct GetOneCameraData
704{
705 SDL_CameraID *result;
706 int devs_seen;
707} GetOneCameraData;
708
709static bool SDLCALL GetOneCamera(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
710{
711 GetOneCameraData *data = (GetOneCameraData *) userdata;
712 data->result[data->devs_seen++] = (SDL_CameraID) (uintptr_t) key;
713 return true; // keep iterating.
714}
715
716SDL_CameraID *SDL_GetCameras(int *count)
717{
718 int dummy_count;
719 if (!count) {
720 count = &dummy_count;
721 }
722
723 if (!SDL_GetCurrentCameraDriver()) {
724 *count = 0;
725 SDL_SetError("Camera subsystem is not initialized");
726 return NULL;
727 }
728
729 SDL_CameraID *result = NULL;
730
731 SDL_LockRWLockForReading(camera_driver.device_hash_lock);
732 int num_devices = SDL_GetAtomicInt(&camera_driver.device_count);
733 result = (SDL_CameraID *) SDL_malloc((num_devices + 1) * sizeof (SDL_CameraID));
734 if (!result) {
735 num_devices = 0;
736 } else {
737 GetOneCameraData data = { result, 0 };
738 SDL_IterateHashTable(camera_driver.device_hash, GetOneCamera, &data);
739 SDL_assert(data.devs_seen == num_devices);
740 result[num_devices] = 0; // null-terminated.
741 }
742 SDL_UnlockRWLock(camera_driver.device_hash_lock);
743
744 *count = num_devices;
745
746 return result;
747}
748
749SDL_CameraSpec **SDL_GetCameraSupportedFormats(SDL_CameraID instance_id, int *count)
750{
751 if (count) {
752 *count = 0;
753 }
754
755 SDL_Camera *device = ObtainPhysicalCamera(instance_id);
756 if (!device) {
757 return NULL;
758 }
759
760 int i;
761 int num_specs = device->num_specs;
762 SDL_CameraSpec **result = (SDL_CameraSpec **) SDL_malloc(((num_specs + 1) * sizeof(*result)) + (num_specs * sizeof (**result)));
763 if (result) {
764 SDL_CameraSpec *specs = (SDL_CameraSpec *)(result + (num_specs + 1));
765 SDL_memcpy(specs, device->all_specs, num_specs * sizeof(*specs));
766 for (i = 0; i < num_specs; ++i) {
767 result[i] = specs++;
768 }
769 result[i] = NULL;
770
771 if (count) {
772 *count = num_specs;
773 }
774 }
775
776 ReleaseCamera(device);
777
778 return result;
779}
780
781
782// Camera device thread. This is split into chunks, so drivers that need to control this directly can use the pieces they need without duplicating effort.
783
784void SDL_CameraThreadSetup(SDL_Camera *device)
785{
786 //camera_driver.impl.ThreadInit(device);
787#ifdef SDL_VIDEO_DRIVER_ANDROID
788 // TODO
789 /*
790 {
791 // Set thread priority to THREAD_PRIORITY_VIDEO
792 extern void Android_JNI_CameraSetThreadPriority(int, int);
793 Android_JNI_CameraSetThreadPriority(device->recording, device);
794 }*/
795#else
796 // The camera capture is always a high priority thread
797 SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH);
798#endif
799}
800
801bool SDL_CameraThreadIterate(SDL_Camera *device)
802{
803 SDL_LockMutex(device->lock);
804
805 if (SDL_GetAtomicInt(&device->shutdown)) {
806 SDL_UnlockMutex(device->lock);
807 return false; // we're done, shut it down.
808 }
809
810 const int permission = device->permission;
811 if (permission <= 0) {
812 SDL_UnlockMutex(device->lock);
813 return (permission < 0) ? false : true; // if permission was denied, shut it down. if undecided, we're done for now.
814 }
815
816 bool failed = false; // set to true if disaster worthy of treating the device as lost has happened.
817 SDL_Surface *acquired = NULL;
818 SDL_Surface *output_surface = NULL;
819 SurfaceList *slist = NULL;
820 Uint64 timestampNS = 0;
821
822 // AcquireFrame SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead!
823 const SDL_CameraFrameResult rc = device->AcquireFrame(device, device->acquire_surface, &timestampNS);
824
825 if (rc == SDL_CAMERA_FRAME_READY) { // new frame acquired!
826 #if DEBUG_CAMERA
827 SDL_Log("CAMERA: New frame available! pixels=%p pitch=%d", device->acquire_surface->pixels, device->acquire_surface->pitch);
828 #endif
829
830 if (device->drop_frames > 0) {
831 #if DEBUG_CAMERA
832 SDL_Log("CAMERA: Dropping an initial frame");
833 #endif
834 device->drop_frames--;
835 device->ReleaseFrame(device, device->acquire_surface);
836 device->acquire_surface->pixels = NULL;
837 device->acquire_surface->pitch = 0;
838 } else if (device->empty_output_surfaces.next == NULL) {
839 // uhoh, no output frames available! Either the app is slow, or it forgot to release frames when done with them. Drop this new frame.
840 #if DEBUG_CAMERA
841 SDL_Log("CAMERA: No empty output surfaces! Dropping frame!");
842 #endif
843 device->ReleaseFrame(device, device->acquire_surface);
844 device->acquire_surface->pixels = NULL;
845 device->acquire_surface->pitch = 0;
846 } else {
847 if (!device->adjust_timestamp) {
848 device->adjust_timestamp = SDL_GetTicksNS();
849 device->base_timestamp = timestampNS;
850 }
851 timestampNS = (timestampNS - device->base_timestamp) + device->adjust_timestamp;
852
853 slist = device->empty_output_surfaces.next;
854 output_surface = slist->surface;
855 device->empty_output_surfaces.next = slist->next;
856 acquired = device->acquire_surface;
857 slist->timestampNS = timestampNS;
858 }
859 } else if (rc == SDL_CAMERA_FRAME_SKIP) { // no frame available yet; not an error.
860 #if 0 //DEBUG_CAMERA
861 SDL_Log("CAMERA: No frame available yet.");
862 #endif
863 } else { // fatal error!
864 #if DEBUG_CAMERA
865 SDL_Log("CAMERA: dev[%p] error AcquireFrame: %s", device, SDL_GetError());
866 #endif
867 failed = true;
868 }
869
870 // we can let go of the lock once we've tried to grab a frame of video and maybe moved the output frame off the empty list.
871 // this lets us chew up the CPU for conversion and scaling without blocking other threads.
872 SDL_UnlockMutex(device->lock);
873
874 if (failed) {
875 SDL_assert(slist == NULL);
876 SDL_assert(acquired == NULL);
877 SDL_CameraDisconnected(device); // doh.
878 } else if (acquired) { // we have a new frame, scale/convert if necessary and queue it for the app!
879 SDL_assert(slist != NULL);
880 if (!device->needs_scaling && !device->needs_conversion) { // no conversion needed? Just move the pointer/pitch into the output surface.
881 #if DEBUG_CAMERA
882 SDL_Log("CAMERA: Frame is going through without conversion!");
883 #endif
884 output_surface->w = acquired->w;
885 output_surface->h = acquired->h;
886 output_surface->pixels = acquired->pixels;
887 output_surface->pitch = acquired->pitch;
888 } else { // convert/scale into a different surface.
889 #if DEBUG_CAMERA
890 SDL_Log("CAMERA: Frame is getting converted!");
891 #endif
892 SDL_Surface *srcsurf = acquired;
893 if (device->needs_scaling == -1) { // downscaling? Do it first. -1: downscale, 0: no scaling, 1: upscale
894 SDL_Surface *dstsurf = device->needs_conversion ? device->conversion_surface : output_surface;
895 SDL_StretchSurface(srcsurf, NULL, dstsurf, NULL, SDL_SCALEMODE_NEAREST); // !!! FIXME: linear scale? letterboxing?
896 srcsurf = dstsurf;
897 }
898 if (device->needs_conversion) {
899 SDL_Surface *dstsurf = (device->needs_scaling == 1) ? device->conversion_surface : output_surface;
900 SDL_ConvertPixels(srcsurf->w, srcsurf->h,
901 srcsurf->format, srcsurf->pixels, srcsurf->pitch,
902 dstsurf->format, dstsurf->pixels, dstsurf->pitch);
903 srcsurf = dstsurf;
904 }
905 if (device->needs_scaling == 1) { // upscaling? Do it last. -1: downscale, 0: no scaling, 1: upscale
906 SDL_StretchSurface(srcsurf, NULL, output_surface, NULL, SDL_SCALEMODE_NEAREST); // !!! FIXME: linear scale? letterboxing?
907 }
908
909 // we made a copy, so we can give the driver back its resources.
910 device->ReleaseFrame(device, acquired);
911 }
912
913 // we either released these already after we copied the data, or the pointer was migrated to output_surface.
914 acquired->pixels = NULL;
915 acquired->pitch = 0;
916
917 // make the filled output surface available to the app.
918 SDL_LockMutex(device->lock);
919 slist->next = device->filled_output_surfaces.next;
920 device->filled_output_surfaces.next = slist;
921 SDL_UnlockMutex(device->lock);
922 }
923
924 return true; // always go on if not shutting down, even if device failed.
925}
926
927void SDL_CameraThreadShutdown(SDL_Camera *device)
928{
929 //device->FlushRecording(device);
930 //camera_driver.impl.ThreadDeinit(device);
931 //SDL_CameraThreadFinalize(device);
932}
933
934// Actual thread entry point, if driver didn't handle this itself.
935static int SDLCALL CameraThread(void *devicep)
936{
937 SDL_Camera *device = (SDL_Camera *) devicep;
938
939 #if DEBUG_CAMERA
940 SDL_Log("CAMERA: dev[%p] Start thread 'CameraThread'", devicep);
941 #endif
942
943 SDL_assert(device != NULL);
944 SDL_CameraThreadSetup(device);
945
946 do {
947 if (!device->WaitDevice(device)) {
948 SDL_CameraDisconnected(device); // doh. (but don't break out of the loop, just be a zombie for now!)
949 }
950 } while (SDL_CameraThreadIterate(device));
951
952 SDL_CameraThreadShutdown(device);
953
954 #if DEBUG_CAMERA
955 SDL_Log("CAMERA: dev[%p] End thread 'CameraThread'", devicep);
956 #endif
957
958 return 0;
959}
960
961bool SDL_PrepareCameraSurfaces(SDL_Camera *device)
962{
963 SDL_CameraSpec *appspec = &device->spec; // the app wants this format.
964 const SDL_CameraSpec *devspec = &device->actual_spec; // the hardware is set to this format.
965
966 SDL_assert(device->acquire_surface == NULL); // shouldn't call this function twice on an opened camera!
967 SDL_assert(devspec->format != SDL_PIXELFORMAT_UNKNOWN); // fix the backend, it should have an actual format by now.
968 SDL_assert(devspec->width >= 0); // fix the backend, it should have an actual format by now.
969 SDL_assert(devspec->height >= 0); // fix the backend, it should have an actual format by now.
970
971 if (appspec->width <= 0 || appspec->height <= 0) {
972 appspec->width = devspec->width;
973 appspec->height = devspec->height;
974 }
975
976 if (appspec->format == SDL_PIXELFORMAT_UNKNOWN) {
977 appspec->format = devspec->format;
978 }
979
980 if (appspec->framerate_denominator == 0) {
981 appspec->framerate_numerator = devspec->framerate_numerator;
982 appspec->framerate_denominator = devspec->framerate_denominator;
983 }
984
985 if ((devspec->width == appspec->width) && (devspec->height == appspec->height)) {
986 device->needs_scaling = 0;
987 } else {
988 const Uint64 srcarea = ((Uint64) devspec->width) * ((Uint64) devspec->height);
989 const Uint64 dstarea = ((Uint64) appspec->width) * ((Uint64) appspec->height);
990 if (dstarea <= srcarea) {
991 device->needs_scaling = -1; // downscaling (or changing to new aspect ratio with same area)
992 } else {
993 device->needs_scaling = 1; // upscaling
994 }
995 }
996
997 device->needs_conversion = (devspec->format != appspec->format);
998
999 device->acquire_surface = SDL_CreateSurfaceFrom(devspec->width, devspec->height, devspec->format, NULL, 0);
1000 if (!device->acquire_surface) {
1001 goto failed;
1002 }
1003 SDL_SetSurfaceColorspace(device->acquire_surface, devspec->colorspace);
1004
1005 // if we have to scale _and_ convert, we need a middleman surface, since we can't do both changes at once.
1006 if (device->needs_scaling && device->needs_conversion) {
1007 const bool downscaling_first = (device->needs_scaling < 0);
1008 const SDL_CameraSpec *s = downscaling_first ? appspec : devspec;
1009 const SDL_PixelFormat fmt = downscaling_first ? devspec->format : appspec->format;
1010 device->conversion_surface = SDL_CreateSurface(s->width, s->height, fmt);
1011 if (!device->conversion_surface) {
1012 goto failed;
1013 }
1014 SDL_SetSurfaceColorspace(device->conversion_surface, devspec->colorspace);
1015 }
1016
1017 // output surfaces are in the app-requested format. If no conversion is necessary, we'll just use the pointers
1018 // the backend fills into acquired_surface, and you can get all the way from DMA access in the camera hardware
1019 // to the app without a single copy. Otherwise, these will be full surfaces that hold converted/scaled copies.
1020
1021 for (int i = 0; i < (SDL_arraysize(device->output_surfaces) - 1); i++) {
1022 device->output_surfaces[i].next = &device->output_surfaces[i + 1];
1023 }
1024 device->empty_output_surfaces.next = device->output_surfaces;
1025
1026 for (int i = 0; i < SDL_arraysize(device->output_surfaces); i++) {
1027 SDL_Surface *surf;
1028 if (device->needs_scaling || device->needs_conversion) {
1029 surf = SDL_CreateSurface(appspec->width, appspec->height, appspec->format);
1030 } else {
1031 surf = SDL_CreateSurfaceFrom(appspec->width, appspec->height, appspec->format, NULL, 0);
1032 }
1033 if (!surf) {
1034 goto failed;
1035 }
1036 SDL_SetSurfaceColorspace(surf, devspec->colorspace);
1037
1038 device->output_surfaces[i].surface = surf;
1039 }
1040
1041 return true;
1042
1043failed:
1044 if (device->acquire_surface) {
1045 SDL_DestroySurface(device->acquire_surface);
1046 device->acquire_surface = NULL;
1047 }
1048
1049 if (device->conversion_surface) {
1050 SDL_DestroySurface(device->conversion_surface);
1051 device->conversion_surface = NULL;
1052 }
1053
1054 for (int i = 0; i < SDL_arraysize(device->output_surfaces); i++) {
1055 SDL_Surface *surf = device->output_surfaces[i].surface;
1056 if (surf) {
1057 SDL_DestroySurface(surf);
1058 }
1059 }
1060 SDL_zeroa(device->output_surfaces);
1061
1062 return false;
1063}
1064
1065static void ChooseBestCameraSpec(SDL_Camera *device, const SDL_CameraSpec *spec, SDL_CameraSpec *closest)
1066{
1067 // Find the closest available native format/size...
1068 //
1069 // We want the exact size if possible, even if we have
1070 // to convert formats, because we can _probably_ do that
1071 // conversion losslessly at less expense verses scaling.
1072 //
1073 // Failing that, we want the size that's closest to the
1074 // requested aspect ratio, then the closest size within
1075 // that.
1076
1077 SDL_zerop(closest);
1078 SDL_assert(((Uint32) SDL_PIXELFORMAT_UNKNOWN) == 0); // since we SDL_zerop'd to this value.
1079
1080 if (device->num_specs == 0) { // device listed no specs! You get whatever you want!
1081 if (spec) {
1082 SDL_copyp(closest, spec);
1083 }
1084 return;
1085 } else if (!spec) { // nothing specifically requested, get the best format we can...
1086 // we sorted this into the "best" format order when adding the camera.
1087 SDL_copyp(closest, &device->all_specs[0]);
1088 } else { // specific thing requested, try to get as close to that as possible...
1089 const int num_specs = device->num_specs;
1090 int wantw = spec->width;
1091 int wanth = spec->height;
1092
1093 if (wantw > 0 && wanth > 0) {
1094 // Find the sizes with the closest aspect ratio and then find the best fit of those.
1095 const float wantaspect = ((float)wantw) / ((float)wanth);
1096 const float epsilon = 1e-6f;
1097 float closestaspect = -9999999.0f;
1098 float closestdiff = 999999.0f;
1099 int closestdiffw = 9999999;
1100
1101 for (int i = 0; i < num_specs; i++) {
1102 const SDL_CameraSpec *thisspec = &device->all_specs[i];
1103 const int thisw = thisspec->width;
1104 const int thish = thisspec->height;
1105 const float thisaspect = ((float)thisw) / ((float)thish);
1106 const float aspectdiff = SDL_fabsf(wantaspect - thisaspect);
1107 const float diff = SDL_fabsf(closestaspect - thisaspect);
1108 const int diffw = SDL_abs(thisw - wantw);
1109 if (diff < epsilon) { // matches current closestaspect? See if resolution is closer in size.
1110 if (diffw < closestdiffw) {
1111 closestdiffw = diffw;
1112 closest->width = thisw;
1113 closest->height = thish;
1114 }
1115 } else if (aspectdiff < closestdiff) { // this is a closer aspect ratio? Take it, reset resolution checks.
1116 closestdiff = aspectdiff;
1117 closestaspect = thisaspect;
1118 closestdiffw = diffw;
1119 closest->width = thisw;
1120 closest->height = thish;
1121 }
1122 }
1123 } else {
1124 SDL_copyp(closest, &device->all_specs[0]);
1125 }
1126
1127 SDL_assert(closest->width > 0);
1128 SDL_assert(closest->height > 0);
1129
1130 // okay, we have what we think is the best resolution, now we just need the best format that supports it...
1131 const SDL_PixelFormat wantfmt = spec->format;
1132 SDL_PixelFormat best_format = SDL_PIXELFORMAT_UNKNOWN;
1133 SDL_Colorspace best_colorspace = SDL_COLORSPACE_UNKNOWN;
1134 for (int i = 0; i < num_specs; i++) {
1135 const SDL_CameraSpec *thisspec = &device->all_specs[i];
1136 if ((thisspec->width == closest->width) && (thisspec->height == closest->height)) {
1137 if (best_format == SDL_PIXELFORMAT_UNKNOWN) {
1138 best_format = thisspec->format; // spec list is sorted by what we consider "best" format, so unless we find an exact match later, first size match is the one!
1139 best_colorspace = thisspec->colorspace;
1140 }
1141 if (thisspec->format == wantfmt) {
1142 best_format = thisspec->format;
1143 best_colorspace = thisspec->colorspace;
1144 break; // exact match, stop looking.
1145 }
1146 }
1147 }
1148
1149 SDL_assert(best_format != SDL_PIXELFORMAT_UNKNOWN);
1150 SDL_assert(best_colorspace != SDL_COLORSPACE_UNKNOWN);
1151 closest->format = best_format;
1152 closest->colorspace = best_colorspace;
1153
1154 // We have a resolution and a format, find the closest framerate...
1155 const float wantfps = spec->framerate_denominator ? ((float)spec->framerate_numerator / spec->framerate_denominator) : 0.0f;
1156 float closestfps = 9999999.0f;
1157 for (int i = 0; i < num_specs; i++) {
1158 const SDL_CameraSpec *thisspec = &device->all_specs[i];
1159 if ((thisspec->format == closest->format) && (thisspec->width == closest->width) && (thisspec->height == closest->height)) {
1160 if ((thisspec->framerate_numerator == spec->framerate_numerator) && (thisspec->framerate_denominator == spec->framerate_denominator)) {
1161 closest->framerate_numerator = thisspec->framerate_numerator;
1162 closest->framerate_denominator = thisspec->framerate_denominator;
1163 break; // exact match, stop looking.
1164 }
1165
1166 const float thisfps = thisspec->framerate_denominator ? ((float)thisspec->framerate_numerator / thisspec->framerate_denominator) : 0.0f;
1167 const float fpsdiff = SDL_fabsf(wantfps - thisfps);
1168 if (fpsdiff < closestfps) { // this is a closest FPS? Take it until something closer arrives.
1169 closestfps = fpsdiff;
1170 closest->framerate_numerator = thisspec->framerate_numerator;
1171 closest->framerate_denominator = thisspec->framerate_denominator;
1172 }
1173 }
1174 }
1175 }
1176
1177 SDL_assert(closest->width > 0);
1178 SDL_assert(closest->height > 0);
1179 SDL_assert(closest->format != SDL_PIXELFORMAT_UNKNOWN);
1180}
1181
1182SDL_Camera *SDL_OpenCamera(SDL_CameraID instance_id, const SDL_CameraSpec *spec)
1183{
1184 SDL_Camera *device = ObtainPhysicalCamera(instance_id);
1185 if (!device) {
1186 return NULL;
1187 }
1188
1189 if (device->hidden != NULL) {
1190 ReleaseCamera(device);
1191 SDL_SetError("Camera already opened"); // we may remove this limitation at some point.
1192 return NULL;
1193 }
1194
1195 SDL_SetAtomicInt(&device->shutdown, 0);
1196
1197 // These start with the backend's implementation, but we might swap them out with zombie versions later.
1198 device->WaitDevice = camera_driver.impl.WaitDevice;
1199 device->AcquireFrame = camera_driver.impl.AcquireFrame;
1200 device->ReleaseFrame = camera_driver.impl.ReleaseFrame;
1201
1202 SDL_CameraSpec closest;
1203 ChooseBestCameraSpec(device, spec, &closest);
1204
1205 #if DEBUG_CAMERA
1206 SDL_Log("CAMERA: App wanted [(%dx%d) fmt=%s framerate=%d/%d], chose [(%dx%d) fmt=%s framerate=%d/%d]",
1207 spec ? spec->width : -1, spec ? spec->height : -1, spec ? SDL_GetPixelFormatName(spec->format) : "(null)", spec ? spec->framerate_numerator : -1, spec ? spec->framerate_denominator : -1,
1208 closest.width, closest.height, SDL_GetPixelFormatName(closest.format), closest.framerate_numerator, closest.framerate_denominator);
1209 #endif
1210
1211 if (!camera_driver.impl.OpenDevice(device, &closest)) {
1212 ClosePhysicalCamera(device); // in case anything is half-initialized.
1213 ReleaseCamera(device);
1214 return NULL;
1215 }
1216
1217 SDL_copyp(&device->spec, spec ? spec : &closest);
1218 SDL_copyp(&device->actual_spec, &closest);
1219
1220 // SDL_PIXELFORMAT_UNKNOWN here is taken as a signal that the backend
1221 // doesn't know its format yet (Emscripten waiting for user permission,
1222 // in this case), and the backend will call SDL_PrepareCameraSurfaces()
1223 // itself, later but before the app is allowed to acquire images.
1224 if (closest.format != SDL_PIXELFORMAT_UNKNOWN) {
1225 if (!SDL_PrepareCameraSurfaces(device)) {
1226 ClosePhysicalCamera(device);
1227 ReleaseCamera(device);
1228 return NULL;
1229 }
1230 }
1231
1232 device->drop_frames = 1;
1233
1234 // Start the camera thread if necessary
1235 if (!camera_driver.impl.ProvidesOwnCallbackThread) {
1236 char threadname[64];
1237 SDL_GetCameraThreadName(device, threadname, sizeof (threadname));
1238 device->thread = SDL_CreateThread(CameraThread, threadname, device);
1239 if (!device->thread) {
1240 ClosePhysicalCamera(device);
1241 ReleaseCamera(device);
1242 SDL_SetError("Couldn't create camera thread");
1243 return NULL;
1244 }
1245 }
1246
1247 ReleaseCamera(device); // unlock, we're good to go!
1248
1249 return device; // currently there's no separation between physical and logical device.
1250}
1251
1252SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS)
1253{
1254 if (timestampNS) {
1255 *timestampNS = 0;
1256 }
1257
1258 if (!camera) {
1259 SDL_InvalidParamError("camera");
1260 return NULL;
1261 }
1262
1263 SDL_Camera *device = camera; // currently there's no separation between physical and logical device.
1264
1265 ObtainPhysicalCameraObj(device);
1266
1267 if (device->permission <= 0) {
1268 ReleaseCamera(device);
1269 SDL_SetError("Camera permission has not been granted");
1270 return NULL;
1271 }
1272
1273 SDL_Surface *result = NULL;
1274
1275 // frames are in this list from newest to oldest, so find the end of the list...
1276 SurfaceList *slistprev = &device->filled_output_surfaces;
1277 SurfaceList *slist = slistprev;
1278 while (slist->next) {
1279 slistprev = slist;
1280 slist = slist->next;
1281 }
1282
1283 const bool list_is_empty = (slist == slistprev);
1284 if (!list_is_empty) { // report the oldest frame.
1285 if (timestampNS) {
1286 *timestampNS = slist->timestampNS;
1287 }
1288 result = slist->surface;
1289 slistprev->next = slist->next; // remove from filled list.
1290 slist->next = device->app_held_output_surfaces.next; // add to app_held list.
1291 device->app_held_output_surfaces.next = slist;
1292 }
1293
1294 ReleaseCamera(device);
1295
1296 return result;
1297}
1298
1299void SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame)
1300{
1301 if (!camera || !frame) {
1302 return;
1303 }
1304
1305 SDL_Camera *device = camera; // currently there's no separation between physical and logical device.
1306 ObtainPhysicalCameraObj(device);
1307
1308 SurfaceList *slistprev = &device->app_held_output_surfaces;
1309 SurfaceList *slist;
1310 for (slist = slistprev->next; slist != NULL; slist = slist->next) {
1311 if (slist->surface == frame) {
1312 break;
1313 }
1314 slistprev = slist;
1315 }
1316
1317 if (!slist) {
1318 ReleaseCamera(device);
1319 return;
1320 }
1321
1322 // this pointer was owned by the backend (DMA memory or whatever), clear it out.
1323 if (!device->needs_conversion && !device->needs_scaling) {
1324 device->ReleaseFrame(device, frame);
1325 frame->pixels = NULL;
1326 frame->pitch = 0;
1327 }
1328
1329 slist->timestampNS = 0;
1330
1331 // remove from app_held list...
1332 slistprev->next = slist->next;
1333
1334 // insert at front of empty list (and we'll use it first when we need to fill a new frame).
1335 slist->next = device->empty_output_surfaces.next;
1336 device->empty_output_surfaces.next = slist;
1337
1338 ReleaseCamera(device);
1339}
1340
1341SDL_CameraID SDL_GetCameraID(SDL_Camera *camera)
1342{
1343 SDL_CameraID result = 0;
1344 if (!camera) {
1345 SDL_InvalidParamError("camera");
1346 } else {
1347 SDL_Camera *device = camera; // currently there's no separation between physical and logical device.
1348 ObtainPhysicalCameraObj(device);
1349 result = device->instance_id;
1350 ReleaseCamera(device);
1351 }
1352
1353 return result;
1354}
1355
1356SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera)
1357{
1358 SDL_PropertiesID result = 0;
1359 if (!camera) {
1360 SDL_InvalidParamError("camera");
1361 } else {
1362 SDL_Camera *device = camera; // currently there's no separation between physical and logical device.
1363 ObtainPhysicalCameraObj(device);
1364 if (device->props == 0) {
1365 device->props = SDL_CreateProperties();
1366 }
1367 result = device->props;
1368 ReleaseCamera(device);
1369 }
1370
1371 return result;
1372}
1373
1374int SDL_GetCameraPermissionState(SDL_Camera *camera)
1375{
1376 int result;
1377 if (!camera) {
1378 SDL_InvalidParamError("camera");
1379 result = -1;
1380 } else {
1381 SDL_Camera *device = camera; // currently there's no separation between physical and logical device.
1382 ObtainPhysicalCameraObj(device);
1383 result = device->permission;
1384 ReleaseCamera(device);
1385 }
1386 return result;
1387}
1388
1389
1390static void CompleteCameraEntryPoints(void)
1391{
1392 // this doesn't currently fill in stub implementations, it just asserts the backend filled them all in.
1393 #define FILL_STUB(x) SDL_assert(camera_driver.impl.x != NULL)
1394 FILL_STUB(DetectDevices);
1395 FILL_STUB(OpenDevice);
1396 FILL_STUB(CloseDevice);
1397 FILL_STUB(AcquireFrame);
1398 FILL_STUB(ReleaseFrame);
1399 FILL_STUB(FreeDeviceHandle);
1400 FILL_STUB(Deinitialize);
1401 #undef FILL_STUB
1402}
1403
1404void SDL_QuitCamera(void)
1405{
1406 if (!camera_driver.name) { // not initialized?!
1407 return;
1408 }
1409
1410 SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
1411 SDL_SetAtomicInt(&camera_driver.shutting_down, 1);
1412 SDL_HashTable *device_hash = camera_driver.device_hash;
1413 camera_driver.device_hash = NULL;
1414 SDL_PendingCameraEvent *pending_events = camera_driver.pending_events.next;
1415 camera_driver.pending_events.next = NULL;
1416 SDL_SetAtomicInt(&camera_driver.device_count, 0);
1417 SDL_UnlockRWLock(camera_driver.device_hash_lock);
1418
1419 SDL_PendingCameraEvent *pending_next = NULL;
1420 for (SDL_PendingCameraEvent *i = pending_events; i; i = pending_next) {
1421 pending_next = i->next;
1422 SDL_free(i);
1423 }
1424
1425 SDL_DestroyHashTable(device_hash);
1426
1427 // Free the driver data
1428 camera_driver.impl.Deinitialize();
1429
1430 SDL_DestroyRWLock(camera_driver.device_hash_lock);
1431
1432 SDL_zero(camera_driver);
1433}
1434
1435// Physical camera objects are only destroyed when removed from the device hash.
1436static void SDLCALL DestroyCameraHashItem(void *userdata, const void *key, const void *value)
1437{
1438 SDL_Camera *device = (SDL_Camera *) value;
1439 ClosePhysicalCamera(device);
1440 camera_driver.impl.FreeDeviceHandle(device);
1441 SDL_DestroyMutex(device->lock);
1442 SDL_free(device->all_specs);
1443 SDL_free(device->name);
1444 SDL_free(device);
1445}
1446
1447bool SDL_CameraInit(const char *driver_name)
1448{
1449 if (SDL_GetCurrentCameraDriver()) {
1450 SDL_QuitCamera(); // shutdown driver if already running.
1451 }
1452
1453 SDL_RWLock *device_hash_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole camera subsystem.
1454 if (!device_hash_lock) {
1455 return false;
1456 }
1457
1458 SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, DestroyCameraHashItem, NULL);
1459 if (!device_hash) {
1460 SDL_DestroyRWLock(device_hash_lock);
1461 return false;
1462 }
1463
1464 // Select the proper camera driver
1465 if (!driver_name) {
1466 driver_name = SDL_GetHint(SDL_HINT_CAMERA_DRIVER);
1467 }
1468
1469 bool initialized = false;
1470 bool tried_to_init = false;
1471
1472 if (driver_name && (*driver_name != 0)) {
1473 char *driver_name_copy = SDL_strdup(driver_name);
1474 const char *driver_attempt = driver_name_copy;
1475
1476 if (!driver_name_copy) {
1477 SDL_DestroyRWLock(device_hash_lock);
1478 SDL_DestroyHashTable(device_hash);
1479 return false;
1480 }
1481
1482 while (driver_attempt && (*driver_attempt != 0) && !initialized) {
1483 char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
1484 if (driver_attempt_end) {
1485 *driver_attempt_end = '\0';
1486 }
1487
1488 for (int i = 0; bootstrap[i]; i++) {
1489 if (SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) {
1490 tried_to_init = true;
1491 SDL_zero(camera_driver);
1492 camera_driver.pending_events_tail = &camera_driver.pending_events;
1493 camera_driver.device_hash_lock = device_hash_lock;
1494 camera_driver.device_hash = device_hash;
1495 if (bootstrap[i]->init(&camera_driver.impl)) {
1496 camera_driver.name = bootstrap[i]->name;
1497 camera_driver.desc = bootstrap[i]->desc;
1498 initialized = true;
1499 }
1500 break;
1501 }
1502 }
1503
1504 driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
1505 }
1506
1507 SDL_free(driver_name_copy);
1508 } else {
1509 for (int i = 0; !initialized && bootstrap[i]; i++) {
1510 if (bootstrap[i]->demand_only) {
1511 continue;
1512 }
1513
1514 tried_to_init = true;
1515 SDL_zero(camera_driver);
1516 camera_driver.pending_events_tail = &camera_driver.pending_events;
1517 camera_driver.device_hash_lock = device_hash_lock;
1518 camera_driver.device_hash = device_hash;
1519 if (bootstrap[i]->init(&camera_driver.impl)) {
1520 camera_driver.name = bootstrap[i]->name;
1521 camera_driver.desc = bootstrap[i]->desc;
1522 initialized = true;
1523 }
1524 }
1525 }
1526
1527 if (!initialized) {
1528 // specific drivers will set the error message if they fail, but otherwise we do it here.
1529 if (!tried_to_init) {
1530 if (driver_name) {
1531 SDL_SetError("Camera driver '%s' not available", driver_name);
1532 } else {
1533 SDL_SetError("No available camera driver");
1534 }
1535 }
1536
1537 SDL_zero(camera_driver);
1538 SDL_DestroyRWLock(device_hash_lock);
1539 SDL_DestroyHashTable(device_hash);
1540 return false; // No driver was available, so fail.
1541 }
1542
1543 CompleteCameraEntryPoints();
1544
1545 // Make sure we have a list of devices available at startup...
1546 camera_driver.impl.DetectDevices();
1547
1548 return true;
1549}
1550
1551// This is an internal function, so SDL_PumpEvents() can check for pending camera device events.
1552// ("UpdateSubsystem" is the same naming that the other things that hook into PumpEvents use.)
1553void SDL_UpdateCamera(void)
1554{
1555 SDL_LockRWLockForReading(camera_driver.device_hash_lock);
1556 SDL_PendingCameraEvent *pending_events = camera_driver.pending_events.next;
1557 SDL_UnlockRWLock(camera_driver.device_hash_lock);
1558
1559 if (!pending_events) {
1560 return; // nothing to do, check next time.
1561 }
1562
1563 // okay, let's take this whole list of events so we can dump the lock, and new ones can queue up for a later update.
1564 SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
1565 pending_events = camera_driver.pending_events.next; // in case this changed...
1566 camera_driver.pending_events.next = NULL;
1567 camera_driver.pending_events_tail = &camera_driver.pending_events;
1568 SDL_UnlockRWLock(camera_driver.device_hash_lock);
1569
1570 SDL_PendingCameraEvent *pending_next = NULL;
1571 for (SDL_PendingCameraEvent *i = pending_events; i; i = pending_next) {
1572 pending_next = i->next;
1573 if (SDL_EventEnabled(i->type)) {
1574 SDL_Event event;
1575 SDL_zero(event);
1576 event.type = i->type;
1577 event.cdevice.which = (Uint32) i->devid;
1578 SDL_PushEvent(&event);
1579 }
1580 SDL_free(i);
1581 }
1582}
1583
1584