summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/camera
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/camera
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/camera')
-rw-r--r--contrib/SDL-3.2.8/src/camera/SDL_camera.c1583
-rw-r--r--contrib/SDL-3.2.8/src/camera/SDL_camera_c.h35
-rw-r--r--contrib/SDL-3.2.8/src/camera/SDL_syscamera.h224
-rw-r--r--contrib/SDL-3.2.8/src/camera/android/SDL_camera_android.c905
-rw-r--r--contrib/SDL-3.2.8/src/camera/coremedia/SDL_camera_coremedia.m508
-rw-r--r--contrib/SDL-3.2.8/src/camera/dummy/SDL_camera_dummy.c81
-rw-r--r--contrib/SDL-3.2.8/src/camera/emscripten/SDL_camera_emscripten.c275
-rw-r--r--contrib/SDL-3.2.8/src/camera/mediafoundation/SDL_camera_mediafoundation.c1143
-rw-r--r--contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c1144
-rw-r--r--contrib/SDL-3.2.8/src/camera/v4l2/SDL_camera_v4l2.c929
-rw-r--r--contrib/SDL-3.2.8/src/camera/vita/SDL_camera_vita.c258
11 files changed, 7085 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/camera/SDL_camera.c b/contrib/SDL-3.2.8/src/camera/SDL_camera.c
new file mode 100644
index 0000000..9f71cea
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/SDL_camera.c
@@ -0,0 +1,1583 @@
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
diff --git a/contrib/SDL-3.2.8/src/camera/SDL_camera_c.h b/contrib/SDL-3.2.8/src/camera/SDL_camera_c.h
new file mode 100644
index 0000000..316ae7d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/SDL_camera_c.h
@@ -0,0 +1,35 @@
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#ifndef SDL_camera_c_h_
24#define SDL_camera_c_h_
25
26// Initialize the camera subsystem
27extern bool SDL_CameraInit(const char *driver_name);
28
29// Shutdown the camera subsystem
30extern void SDL_QuitCamera(void);
31
32// "Pump" the event queue.
33extern void SDL_UpdateCamera(void);
34
35#endif // SDL_camera_c_h_
diff --git a/contrib/SDL-3.2.8/src/camera/SDL_syscamera.h b/contrib/SDL-3.2.8/src/camera/SDL_syscamera.h
new file mode 100644
index 0000000..30a02f3
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/SDL_syscamera.h
@@ -0,0 +1,224 @@
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#ifndef SDL_syscamera_h_
24#define SDL_syscamera_h_
25
26#include "../video/SDL_surface_c.h"
27
28#define DEBUG_CAMERA 0
29
30/* Backends should call this as devices are added to the system (such as
31 a USB camera being plugged in), and should also be called for
32 for every device found during DetectDevices(). */
33extern SDL_Camera *SDL_AddCamera(const char *name, SDL_CameraPosition position, int num_specs, const SDL_CameraSpec *specs, void *handle);
34
35/* Backends should call this if an opened camera device is lost.
36 This can happen due to i/o errors, or a device being unplugged, etc. */
37extern void SDL_CameraDisconnected(SDL_Camera *device);
38
39// Find an SDL_Camera, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
40extern SDL_Camera *SDL_FindPhysicalCameraByCallback(bool (*callback)(SDL_Camera *device, void *userdata), void *userdata);
41
42// Backends should call this when the user has approved/denied access to a camera.
43extern void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved);
44
45// Backends can call this to get a standardized name for a thread to power a specific camera device.
46extern char *SDL_GetCameraThreadName(SDL_Camera *device, char *buf, size_t buflen);
47
48// Backends can call these to change a device's refcount.
49extern void RefPhysicalCamera(SDL_Camera *device);
50extern void UnrefPhysicalCamera(SDL_Camera *device);
51
52// These functions are the heart of the camera threads. Backends can call them directly if they aren't using the SDL-provided thread.
53extern void SDL_CameraThreadSetup(SDL_Camera *device);
54extern bool SDL_CameraThreadIterate(SDL_Camera *device);
55extern void SDL_CameraThreadShutdown(SDL_Camera *device);
56
57// Backends can call this if they have to finish initializing later, like Emscripten. Most backends should _not_ call this directly!
58extern bool SDL_PrepareCameraSurfaces(SDL_Camera *device);
59
60
61// common utility functionality to gather up camera specs. Not required!
62typedef struct CameraFormatAddData
63{
64 SDL_CameraSpec *specs;
65 int num_specs;
66 int allocated_specs;
67} CameraFormatAddData;
68
69bool SDL_AddCameraFormat(CameraFormatAddData *data, SDL_PixelFormat format, SDL_Colorspace colorspace, int w, int h, int framerate_numerator, int framerate_denominator);
70
71typedef enum SDL_CameraFrameResult
72{
73 SDL_CAMERA_FRAME_ERROR,
74 SDL_CAMERA_FRAME_SKIP,
75 SDL_CAMERA_FRAME_READY
76} SDL_CameraFrameResult;
77
78typedef struct SurfaceList
79{
80 SDL_Surface *surface;
81 Uint64 timestampNS;
82 struct SurfaceList *next;
83} SurfaceList;
84
85// Define the SDL camera driver structure
86struct SDL_Camera
87{
88 // A mutex for locking
89 SDL_Mutex *lock;
90
91 // Human-readable device name.
92 char *name;
93
94 // Position of camera (front-facing, back-facing, etc).
95 SDL_CameraPosition position;
96
97 // When refcount hits zero, we destroy the device object.
98 SDL_AtomicInt refcount;
99
100 // These are, initially, set from camera_driver, but we might swap them out with Zombie versions on disconnect/failure.
101 bool (*WaitDevice)(SDL_Camera *device);
102 SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS);
103 void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame);
104
105 // All supported formats/dimensions for this device.
106 SDL_CameraSpec *all_specs;
107
108 // Elements in all_specs.
109 int num_specs;
110
111 // The device's actual specification that the camera is outputting, before conversion.
112 SDL_CameraSpec actual_spec;
113
114 // The device's current camera specification, after conversions.
115 SDL_CameraSpec spec;
116
117 // Unique value assigned at creation time.
118 SDL_CameraID instance_id;
119
120 // Driver-specific hardware data on how to open device (`hidden` is driver-specific data _when opened_).
121 void *handle;
122
123 // Dropping the first frame(s) after open seems to help timing on some platforms.
124 int drop_frames;
125
126 // Backend timestamp of first acquired frame, so we can keep these meaningful regardless of epoch.
127 Uint64 base_timestamp;
128
129 // SDL timestamp of first acquired frame, so we can roughly convert to SDL ticks.
130 Uint64 adjust_timestamp;
131
132 // Pixel data flows from the driver into these, then gets converted for the app if necessary.
133 SDL_Surface *acquire_surface;
134
135 // acquire_surface converts or scales to this surface before landing in output_surfaces, if necessary.
136 SDL_Surface *conversion_surface;
137
138 // A queue of surfaces that buffer converted/scaled frames of video until the app claims them.
139 SurfaceList output_surfaces[8];
140 SurfaceList filled_output_surfaces; // this is FIFO
141 SurfaceList empty_output_surfaces; // this is LIFO
142 SurfaceList app_held_output_surfaces;
143
144 // A fake video frame we allocate if the camera fails/disconnects.
145 Uint8 *zombie_pixels;
146
147 // non-zero if acquire_surface needs to be scaled for final output.
148 int needs_scaling; // -1: downscale, 0: no scaling, 1: upscale
149
150 // true if acquire_surface needs to be converted for final output.
151 bool needs_conversion;
152
153 // Current state flags
154 SDL_AtomicInt shutdown;
155 SDL_AtomicInt zombie;
156
157 // A thread to feed the camera device
158 SDL_Thread *thread;
159
160 // Optional properties.
161 SDL_PropertiesID props;
162
163 // -1: user denied permission, 0: waiting for user response, 1: user approved permission.
164 int permission;
165
166 // Data private to this driver, used when device is opened and running.
167 struct SDL_PrivateCameraData *hidden;
168};
169
170typedef struct SDL_CameraDriverImpl
171{
172 void (*DetectDevices)(void);
173 bool (*OpenDevice)(SDL_Camera *device, const SDL_CameraSpec *spec);
174 void (*CloseDevice)(SDL_Camera *device);
175 bool (*WaitDevice)(SDL_Camera *device);
176 SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS); // set frame->pixels, frame->pitch, and *timestampNS!
177 void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame); // Reclaim frame->pixels and frame->pitch!
178 void (*FreeDeviceHandle)(SDL_Camera *device); // SDL is done with this device; free the handle from SDL_AddCamera()
179 void (*Deinitialize)(void);
180
181 bool ProvidesOwnCallbackThread;
182} SDL_CameraDriverImpl;
183
184typedef struct SDL_PendingCameraEvent
185{
186 Uint32 type;
187 SDL_CameraID devid;
188 struct SDL_PendingCameraEvent *next;
189} SDL_PendingCameraEvent;
190
191typedef struct SDL_CameraDriver
192{
193 const char *name; // The name of this camera driver
194 const char *desc; // The description of this camera driver
195 SDL_CameraDriverImpl impl; // the backend's interface
196
197 SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` // !!! FIXME: device_hash _also_ has a rwlock, see if we still need this one.
198 SDL_HashTable *device_hash; // the collection of currently-available camera devices
199 SDL_PendingCameraEvent pending_events;
200 SDL_PendingCameraEvent *pending_events_tail;
201
202 SDL_AtomicInt device_count;
203 SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
204} SDL_CameraDriver;
205
206typedef struct CameraBootStrap
207{
208 const char *name;
209 const char *desc;
210 bool (*init)(SDL_CameraDriverImpl *impl);
211 bool demand_only; // if true: request explicitly, or it won't be available.
212} CameraBootStrap;
213
214// Not all of these are available in a given build. Use #ifdefs, etc.
215extern CameraBootStrap DUMMYCAMERA_bootstrap;
216extern CameraBootStrap PIPEWIRECAMERA_bootstrap;
217extern CameraBootStrap V4L2_bootstrap;
218extern CameraBootStrap COREMEDIA_bootstrap;
219extern CameraBootStrap ANDROIDCAMERA_bootstrap;
220extern CameraBootStrap EMSCRIPTENCAMERA_bootstrap;
221extern CameraBootStrap MEDIAFOUNDATION_bootstrap;
222extern CameraBootStrap VITACAMERA_bootstrap;
223
224#endif // SDL_syscamera_h_
diff --git a/contrib/SDL-3.2.8/src/camera/android/SDL_camera_android.c b/contrib/SDL-3.2.8/src/camera/android/SDL_camera_android.c
new file mode 100644
index 0000000..54b539a
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/android/SDL_camera_android.c
@@ -0,0 +1,905 @@
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#ifdef SDL_CAMERA_DRIVER_ANDROID
30
31/*
32 * AndroidManifest.xml:
33 * <uses-permission android:name="android.permission.CAMERA"></uses-permission>
34 * <uses-feature android:name="android.hardware.camera" />
35 *
36 * Very likely SDL must be build with YUV support (done by default)
37 *
38 * https://developer.android.com/reference/android/hardware/camera2/CameraManager
39 * "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
40 * before configuring sessions on any of the camera devices."
41 */
42
43// this is kinda gross, but on older NDK headers all the camera stuff is
44// gated behind __ANDROID_API__. We'll dlopen() it at runtime, so we'll do
45// the right thing on pre-Android 7.0 devices, but we still
46// need the struct declarations and such in those headers.
47// The other option is to make a massive jump in minimum Android version we
48// support--going from ancient to merely really old--but this seems less
49// distasteful and using dlopen matches practices on other SDL platforms.
50// We'll see if it works out.
51#if __ANDROID_API__ < 24
52#undef __ANDROID_API__
53#define __ANDROID_API__ 24
54#endif
55
56#include <dlfcn.h>
57#include <camera/NdkCameraDevice.h>
58#include <camera/NdkCameraManager.h>
59#include <media/NdkImage.h>
60#include <media/NdkImageReader.h>
61
62#include "../../core/android/SDL_android.h"
63
64static void *libcamera2ndk = NULL;
65typedef ACameraManager* (*pfnACameraManager_create)(void);
66typedef camera_status_t (*pfnACameraManager_registerAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
67typedef camera_status_t (*pfnACameraManager_unregisterAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
68typedef camera_status_t (*pfnACameraManager_getCameraIdList)(ACameraManager*, ACameraIdList**);
69typedef void (*pfnACameraManager_deleteCameraIdList)(ACameraIdList*);
70typedef void (*pfnACameraCaptureSession_close)(ACameraCaptureSession*);
71typedef void (*pfnACaptureRequest_free)(ACaptureRequest*);
72typedef void (*pfnACameraOutputTarget_free)(ACameraOutputTarget*);
73typedef camera_status_t (*pfnACameraDevice_close)(ACameraDevice*);
74typedef void (*pfnACameraManager_delete)(ACameraManager*);
75typedef void (*pfnACaptureSessionOutputContainer_free)(ACaptureSessionOutputContainer*);
76typedef void (*pfnACaptureSessionOutput_free)(ACaptureSessionOutput*);
77typedef camera_status_t (*pfnACameraManager_openCamera)(ACameraManager*, const char*, ACameraDevice_StateCallbacks*, ACameraDevice**);
78typedef camera_status_t (*pfnACameraDevice_createCaptureRequest)(const ACameraDevice*, ACameraDevice_request_template, ACaptureRequest**);
79typedef camera_status_t (*pfnACameraDevice_createCaptureSession)(ACameraDevice*, const ACaptureSessionOutputContainer*, const ACameraCaptureSession_stateCallbacks*,ACameraCaptureSession**);
80typedef camera_status_t (*pfnACameraManager_getCameraCharacteristics)(ACameraManager*, const char*, ACameraMetadata**);
81typedef void (*pfnACameraMetadata_free)(ACameraMetadata*);
82typedef camera_status_t (*pfnACameraMetadata_getConstEntry)(const ACameraMetadata*, uint32_t tag, ACameraMetadata_const_entry*);
83typedef camera_status_t (*pfnACameraCaptureSession_setRepeatingRequest)(ACameraCaptureSession*, ACameraCaptureSession_captureCallbacks*, int numRequests, ACaptureRequest**, int*);
84typedef camera_status_t (*pfnACameraOutputTarget_create)(ACameraWindowType*,ACameraOutputTarget**);
85typedef camera_status_t (*pfnACaptureRequest_addTarget)(ACaptureRequest*, const ACameraOutputTarget*);
86typedef camera_status_t (*pfnACaptureSessionOutputContainer_add)(ACaptureSessionOutputContainer*, const ACaptureSessionOutput*);
87typedef camera_status_t (*pfnACaptureSessionOutputContainer_create)(ACaptureSessionOutputContainer**);
88typedef camera_status_t (*pfnACaptureSessionOutput_create)(ACameraWindowType*, ACaptureSessionOutput**);
89static pfnACameraManager_create pACameraManager_create = NULL;
90static pfnACameraManager_registerAvailabilityCallback pACameraManager_registerAvailabilityCallback = NULL;
91static pfnACameraManager_unregisterAvailabilityCallback pACameraManager_unregisterAvailabilityCallback = NULL;
92static pfnACameraManager_getCameraIdList pACameraManager_getCameraIdList = NULL;
93static pfnACameraManager_deleteCameraIdList pACameraManager_deleteCameraIdList = NULL;
94static pfnACameraCaptureSession_close pACameraCaptureSession_close = NULL;
95static pfnACaptureRequest_free pACaptureRequest_free = NULL;
96static pfnACameraOutputTarget_free pACameraOutputTarget_free = NULL;
97static pfnACameraDevice_close pACameraDevice_close = NULL;
98static pfnACameraManager_delete pACameraManager_delete = NULL;
99static pfnACaptureSessionOutputContainer_free pACaptureSessionOutputContainer_free = NULL;
100static pfnACaptureSessionOutput_free pACaptureSessionOutput_free = NULL;
101static pfnACameraManager_openCamera pACameraManager_openCamera = NULL;
102static pfnACameraDevice_createCaptureRequest pACameraDevice_createCaptureRequest = NULL;
103static pfnACameraDevice_createCaptureSession pACameraDevice_createCaptureSession = NULL;
104static pfnACameraManager_getCameraCharacteristics pACameraManager_getCameraCharacteristics = NULL;
105static pfnACameraMetadata_free pACameraMetadata_free = NULL;
106static pfnACameraMetadata_getConstEntry pACameraMetadata_getConstEntry = NULL;
107static pfnACameraCaptureSession_setRepeatingRequest pACameraCaptureSession_setRepeatingRequest = NULL;
108static pfnACameraOutputTarget_create pACameraOutputTarget_create = NULL;
109static pfnACaptureRequest_addTarget pACaptureRequest_addTarget = NULL;
110static pfnACaptureSessionOutputContainer_add pACaptureSessionOutputContainer_add = NULL;
111static pfnACaptureSessionOutputContainer_create pACaptureSessionOutputContainer_create = NULL;
112static pfnACaptureSessionOutput_create pACaptureSessionOutput_create = NULL;
113
114static void *libmediandk = NULL;
115typedef void (*pfnAImage_delete)(AImage*);
116typedef media_status_t (*pfnAImage_getTimestamp)(const AImage*, int64_t*);
117typedef media_status_t (*pfnAImage_getNumberOfPlanes)(const AImage*, int32_t*);
118typedef media_status_t (*pfnAImage_getPlaneRowStride)(const AImage*, int, int32_t*);
119typedef media_status_t (*pfnAImage_getPlaneData)(const AImage*, int, uint8_t**, int*);
120typedef media_status_t (*pfnAImageReader_acquireNextImage)(AImageReader*, AImage**);
121typedef void (*pfnAImageReader_delete)(AImageReader*);
122typedef media_status_t (*pfnAImageReader_setImageListener)(AImageReader*, AImageReader_ImageListener*);
123typedef media_status_t (*pfnAImageReader_getWindow)(AImageReader*, ANativeWindow**);
124typedef media_status_t (*pfnAImageReader_new)(int32_t, int32_t, int32_t, int32_t, AImageReader**);
125static pfnAImage_delete pAImage_delete = NULL;
126static pfnAImage_getTimestamp pAImage_getTimestamp = NULL;
127static pfnAImage_getNumberOfPlanes pAImage_getNumberOfPlanes = NULL;
128static pfnAImage_getPlaneRowStride pAImage_getPlaneRowStride = NULL;
129static pfnAImage_getPlaneData pAImage_getPlaneData = NULL;
130static pfnAImageReader_acquireNextImage pAImageReader_acquireNextImage = NULL;
131static pfnAImageReader_delete pAImageReader_delete = NULL;
132static pfnAImageReader_setImageListener pAImageReader_setImageListener = NULL;
133static pfnAImageReader_getWindow pAImageReader_getWindow = NULL;
134static pfnAImageReader_new pAImageReader_new = NULL;
135
136typedef media_status_t (*pfnAImage_getWidth)(const AImage*, int32_t*);
137typedef media_status_t (*pfnAImage_getHeight)(const AImage*, int32_t*);
138static pfnAImage_getWidth pAImage_getWidth = NULL;
139static pfnAImage_getHeight pAImage_getHeight = NULL;
140
141struct SDL_PrivateCameraData
142{
143 ACameraDevice *device;
144 AImageReader *reader;
145 ANativeWindow *window;
146 ACaptureSessionOutput *sessionOutput;
147 ACaptureSessionOutputContainer *sessionOutputContainer;
148 ACameraOutputTarget *outputTarget;
149 ACaptureRequest *request;
150 ACameraCaptureSession *session;
151 SDL_CameraSpec requested_spec;
152};
153
154static bool SetErrorStr(const char *what, const char *errstr, const int rc)
155{
156 char errbuf[128];
157 if (!errstr) {
158 SDL_snprintf(errbuf, sizeof (errbuf), "Unknown error #%d", rc);
159 errstr = errbuf;
160 }
161 return SDL_SetError("%s: %s", what, errstr);
162}
163
164static const char *CameraStatusStr(const camera_status_t rc)
165{
166 switch (rc) {
167 case ACAMERA_OK: return "no error";
168 case ACAMERA_ERROR_UNKNOWN: return "unknown error";
169 case ACAMERA_ERROR_INVALID_PARAMETER: return "invalid parameter";
170 case ACAMERA_ERROR_CAMERA_DISCONNECTED: return "camera disconnected";
171 case ACAMERA_ERROR_NOT_ENOUGH_MEMORY: return "not enough memory";
172 case ACAMERA_ERROR_METADATA_NOT_FOUND: return "metadata not found";
173 case ACAMERA_ERROR_CAMERA_DEVICE: return "camera device error";
174 case ACAMERA_ERROR_CAMERA_SERVICE: return "camera service error";
175 case ACAMERA_ERROR_SESSION_CLOSED: return "session closed";
176 case ACAMERA_ERROR_INVALID_OPERATION: return "invalid operation";
177 case ACAMERA_ERROR_STREAM_CONFIGURE_FAIL: return "configure failure";
178 case ACAMERA_ERROR_CAMERA_IN_USE: return "camera in use";
179 case ACAMERA_ERROR_MAX_CAMERA_IN_USE: return "max cameras in use";
180 case ACAMERA_ERROR_CAMERA_DISABLED: return "camera disabled";
181 case ACAMERA_ERROR_PERMISSION_DENIED: return "permission denied";
182 case ACAMERA_ERROR_UNSUPPORTED_OPERATION: return "unsupported operation";
183 default: break;
184 }
185
186 return NULL; // unknown error
187}
188
189static bool SetCameraError(const char *what, const camera_status_t rc)
190{
191 return SetErrorStr(what, CameraStatusStr(rc), (int) rc);
192}
193
194static const char *MediaStatusStr(const media_status_t rc)
195{
196 switch (rc) {
197 case AMEDIA_OK: return "no error";
198 case AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE: return "insufficient resources";
199 case AMEDIACODEC_ERROR_RECLAIMED: return "reclaimed";
200 case AMEDIA_ERROR_UNKNOWN: return "unknown error";
201 case AMEDIA_ERROR_MALFORMED: return "malformed";
202 case AMEDIA_ERROR_UNSUPPORTED: return "unsupported";
203 case AMEDIA_ERROR_INVALID_OBJECT: return "invalid object";
204 case AMEDIA_ERROR_INVALID_PARAMETER: return "invalid parameter";
205 case AMEDIA_ERROR_INVALID_OPERATION: return "invalid operation";
206 case AMEDIA_ERROR_END_OF_STREAM: return "end of stream";
207 case AMEDIA_ERROR_IO: return "i/o error";
208 case AMEDIA_ERROR_WOULD_BLOCK: return "operation would block";
209 case AMEDIA_DRM_NOT_PROVISIONED: return "DRM not provisioned";
210 case AMEDIA_DRM_RESOURCE_BUSY: return "DRM resource busy";
211 case AMEDIA_DRM_DEVICE_REVOKED: return "DRM device revoked";
212 case AMEDIA_DRM_SHORT_BUFFER: return "DRM short buffer";
213 case AMEDIA_DRM_SESSION_NOT_OPENED: return "DRM session not opened";
214 case AMEDIA_DRM_TAMPER_DETECTED: return "DRM tampering detected";
215 case AMEDIA_DRM_VERIFY_FAILED: return "DRM verify failed";
216 case AMEDIA_DRM_NEED_KEY: return "DRM need key";
217 case AMEDIA_DRM_LICENSE_EXPIRED: return "DRM license expired";
218 case AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE: return "no buffer available";
219 case AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED: return "maximum images acquired";
220 case AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE: return "cannot lock image";
221 case AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE: return "cannot unlock image";
222 case AMEDIA_IMGREADER_IMAGE_NOT_LOCKED: return "image not locked";
223 default: break;
224 }
225
226 return NULL; // unknown error
227}
228
229static bool SetMediaError(const char *what, const media_status_t rc)
230{
231 return SetErrorStr(what, MediaStatusStr(rc), (int) rc);
232}
233
234
235static ACameraManager *cameraMgr = NULL;
236
237static bool CreateCameraManager(void)
238{
239 SDL_assert(cameraMgr == NULL);
240
241 cameraMgr = pACameraManager_create();
242 if (!cameraMgr) {
243 return SDL_SetError("Error creating ACameraManager");
244 }
245 return true;
246}
247
248static void DestroyCameraManager(void)
249{
250 if (cameraMgr) {
251 pACameraManager_delete(cameraMgr);
252 cameraMgr = NULL;
253 }
254}
255
256static void format_android_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
257{
258 switch (fmt) {
259 #define CASE(x, y, z) case x: *format = y; *colorspace = z; return
260 CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED);
261 CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565, SDL_COLORSPACE_SRGB);
262 CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888, SDL_COLORSPACE_SRGB);
263 CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888, SDL_COLORSPACE_SRGB);
264 CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888, SDL_COLORSPACE_SRGB);
265 CASE(AIMAGE_FORMAT_RGBA_FP16, SDL_PIXELFORMAT_RGBA64_FLOAT, SDL_COLORSPACE_SRGB);
266 #undef CASE
267 default: break;
268 }
269
270 #if DEBUG_CAMERA
271 //SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt);
272 #endif
273
274 *format = SDL_PIXELFORMAT_UNKNOWN;
275 *colorspace = SDL_COLORSPACE_UNKNOWN;
276}
277
278static Uint32 format_sdl_to_android(SDL_PixelFormat fmt)
279{
280 switch (fmt) {
281 #define CASE(x, y) case y: return x
282 CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12);
283 CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565);
284 CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888);
285 CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
286 CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888);
287 #undef CASE
288 default:
289 return 0;
290 }
291}
292
293static bool ANDROIDCAMERA_WaitDevice(SDL_Camera *device)
294{
295 return true; // this isn't used atm, since we run our own thread via onImageAvailable callbacks.
296}
297
298static SDL_CameraFrameResult ANDROIDCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
299{
300 SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
301 media_status_t res;
302 AImage *image = NULL;
303
304 res = pAImageReader_acquireNextImage(device->hidden->reader, &image);
305 // We could also use this one:
306 //res = AImageReader_acquireLatestImage(device->hidden->reader, &image);
307
308 SDL_assert(res != AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE); // we should only be here if onImageAvailable was called.
309
310 if (res != AMEDIA_OK) {
311 SetMediaError("Error AImageReader_acquireNextImage", res);
312 return SDL_CAMERA_FRAME_ERROR;
313 }
314
315 int64_t atimestamp = 0;
316 if (pAImage_getTimestamp(image, &atimestamp) == AMEDIA_OK) {
317 *timestampNS = (Uint64) atimestamp;
318 } else {
319 *timestampNS = 0;
320 }
321
322 // !!! 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...
323 int32_t num_planes = 0;
324 pAImage_getNumberOfPlanes(image, &num_planes);
325
326 if ((num_planes == 3) && (device->spec.format == SDL_PIXELFORMAT_NV12)) {
327 num_planes--; // treat the interleaved planes as one.
328 }
329
330 size_t buflen = 0;
331 pAImage_getPlaneRowStride(image, 0, &frame->pitch);
332 for (int i = 0; (i < num_planes) && (i < 3); i++) {
333 int32_t expected;
334 if (i == 0) {
335 expected = frame->pitch * frame->h;
336 } else {
337 expected = frame->pitch * (frame->h + 1) / 2;
338 }
339 buflen += expected;
340 }
341
342 frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
343 if (frame->pixels == NULL) {
344 result = SDL_CAMERA_FRAME_ERROR;
345 } else {
346 Uint8 *dst = frame->pixels;
347
348 for (int i = 0; (i < num_planes) && (i < 3); i++) {
349 uint8_t *data = NULL;
350 int32_t datalen = 0;
351 int32_t expected;
352 if (i == 0) {
353 expected = frame->pitch * frame->h;
354 } else {
355 expected = frame->pitch * (frame->h + 1) / 2;
356 }
357 pAImage_getPlaneData(image, i, &data, &datalen);
358
359 int32_t row_stride = 0;
360 pAImage_getPlaneRowStride(image, i, &row_stride);
361 SDL_assert(row_stride == frame->pitch);
362 SDL_memcpy(dst, data, SDL_min(expected, datalen));
363 dst += expected;
364 }
365 }
366
367 pAImage_delete(image);
368
369 return result;
370}
371
372static void ANDROIDCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
373{
374 // !!! FIXME: this currently copies the data to the surface, but in theory we could just keep the AImage until ReleaseFrame...
375 SDL_aligned_free(frame->pixels);
376}
377
378static void onImageAvailable(void *context, AImageReader *reader)
379{
380 #if DEBUG_CAMERA
381 SDL_Log("CAMERA: CB onImageAvailable");
382 #endif
383 SDL_Camera *device = (SDL_Camera *) context;
384 SDL_CameraThreadIterate(device);
385}
386
387static void onDisconnected(void *context, ACameraDevice *device)
388{
389 #if DEBUG_CAMERA
390 SDL_Log("CAMERA: CB onDisconnected");
391 #endif
392 SDL_CameraDisconnected((SDL_Camera *) context);
393}
394
395static void onError(void *context, ACameraDevice *device, int error)
396{
397 #if DEBUG_CAMERA
398 SDL_Log("CAMERA: CB onError");
399 #endif
400 SDL_CameraDisconnected((SDL_Camera *) context);
401}
402
403static void onClosed(void* context, ACameraCaptureSession *session)
404{
405 // SDL_Camera *_this = (SDL_Camera *) context;
406 #if DEBUG_CAMERA
407 SDL_Log("CAMERA: CB onClosed");
408 #endif
409}
410
411static void onReady(void* context, ACameraCaptureSession *session)
412{
413 // SDL_Camera *_this = (SDL_Camera *) context;
414 #if DEBUG_CAMERA
415 SDL_Log("CAMERA: CB onReady");
416 #endif
417}
418
419static void onActive(void* context, ACameraCaptureSession *session)
420{
421 // SDL_Camera *_this = (SDL_Camera *) context;
422 #if DEBUG_CAMERA
423 SDL_Log("CAMERA: CB onActive");
424 #endif
425}
426
427static void ANDROIDCAMERA_CloseDevice(SDL_Camera *device)
428{
429 if (device && device->hidden) {
430 struct SDL_PrivateCameraData *hidden = device->hidden;
431 device->hidden = NULL;
432
433 if (hidden->reader) {
434 pAImageReader_setImageListener(hidden->reader, NULL);
435 }
436
437 if (hidden->session) {
438 pACameraCaptureSession_close(hidden->session);
439 }
440
441 if (hidden->request) {
442 pACaptureRequest_free(hidden->request);
443 }
444
445 if (hidden->outputTarget) {
446 pACameraOutputTarget_free(hidden->outputTarget);
447 }
448
449 if (hidden->sessionOutputContainer) {
450 pACaptureSessionOutputContainer_free(hidden->sessionOutputContainer);
451 }
452
453 if (hidden->sessionOutput) {
454 pACaptureSessionOutput_free(hidden->sessionOutput);
455 }
456
457 // we don't free hidden->window here, it'll be cleaned up by AImageReader_delete.
458
459 if (hidden->reader) {
460 pAImageReader_delete(hidden->reader);
461 }
462
463 if (hidden->device) {
464 pACameraDevice_close(hidden->device);
465 }
466
467 SDL_free(hidden);
468 }
469}
470
471// this is where the "opening" of the camera happens, after permission is granted.
472static bool PrepareCamera(SDL_Camera *device)
473{
474 SDL_assert(device->hidden != NULL);
475
476 camera_status_t res;
477 media_status_t res2;
478
479 ACameraDevice_StateCallbacks dev_callbacks;
480 SDL_zero(dev_callbacks);
481 dev_callbacks.context = device;
482 dev_callbacks.onDisconnected = onDisconnected;
483 dev_callbacks.onError = onError;
484
485 ACameraCaptureSession_stateCallbacks capture_callbacks;
486 SDL_zero(capture_callbacks);
487 capture_callbacks.context = device;
488 capture_callbacks.onClosed = onClosed;
489 capture_callbacks.onReady = onReady;
490 capture_callbacks.onActive = onActive;
491
492 AImageReader_ImageListener imglistener;
493 SDL_zero(imglistener);
494 imglistener.context = device;
495 imglistener.onImageAvailable = onImageAvailable;
496
497 // just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
498 const SDL_CameraSpec *spec = &device->hidden->requested_spec;
499
500 if ((res = pACameraManager_openCamera(cameraMgr, (const char *) device->handle, &dev_callbacks, &device->hidden->device)) != ACAMERA_OK) {
501 return SetCameraError("Failed to open camera", res);
502 } else if ((res2 = pAImageReader_new(spec->width, spec->height, format_sdl_to_android(spec->format), 10 /* nb buffers */, &device->hidden->reader)) != AMEDIA_OK) {
503 return SetMediaError("Error AImageReader_new", res2);
504 } else if ((res2 = pAImageReader_getWindow(device->hidden->reader, &device->hidden->window)) != AMEDIA_OK) {
505 return SetMediaError("Error AImageReader_getWindow", res2);
506 } else if ((res = pACaptureSessionOutput_create(device->hidden->window, &device->hidden->sessionOutput)) != ACAMERA_OK) {
507 return SetCameraError("Error ACaptureSessionOutput_create", res);
508 } else if ((res = pACaptureSessionOutputContainer_create(&device->hidden->sessionOutputContainer)) != ACAMERA_OK) {
509 return SetCameraError("Error ACaptureSessionOutputContainer_create", res);
510 } else if ((res = pACaptureSessionOutputContainer_add(device->hidden->sessionOutputContainer, device->hidden->sessionOutput)) != ACAMERA_OK) {
511 return SetCameraError("Error ACaptureSessionOutputContainer_add", res);
512 } else if ((res = pACameraOutputTarget_create(device->hidden->window, &device->hidden->outputTarget)) != ACAMERA_OK) {
513 return SetCameraError("Error ACameraOutputTarget_create", res);
514 } else if ((res = pACameraDevice_createCaptureRequest(device->hidden->device, TEMPLATE_RECORD, &device->hidden->request)) != ACAMERA_OK) {
515 return SetCameraError("Error ACameraDevice_createCaptureRequest", res);
516 } else if ((res = pACaptureRequest_addTarget(device->hidden->request, device->hidden->outputTarget)) != ACAMERA_OK) {
517 return SetCameraError("Error ACaptureRequest_addTarget", res);
518 } else if ((res = pACameraDevice_createCaptureSession(device->hidden->device, device->hidden->sessionOutputContainer, &capture_callbacks, &device->hidden->session)) != ACAMERA_OK) {
519 return SetCameraError("Error ACameraDevice_createCaptureSession", res);
520 } else if ((res = pACameraCaptureSession_setRepeatingRequest(device->hidden->session, NULL, 1, &device->hidden->request, NULL)) != ACAMERA_OK) {
521 return SetCameraError("Error ACameraCaptureSession_setRepeatingRequest", res);
522 } else if ((res2 = pAImageReader_setImageListener(device->hidden->reader, &imglistener)) != AMEDIA_OK) {
523 return SetMediaError("Error AImageReader_setImageListener", res2);
524 }
525
526 return true;
527}
528
529static void SDLCALL CameraPermissionCallback(void *userdata, const char *permission, bool granted)
530{
531 SDL_Camera *device = (SDL_Camera *) userdata;
532 if (device->hidden != NULL) { // if device was already closed, don't send an event.
533 if (!granted) {
534 SDL_CameraPermissionOutcome(device, false); // sorry, permission denied.
535 } else if (!PrepareCamera(device)) { // permission given? Actually open the camera now.
536 // uhoh, setup failed; since the app thinks we already "opened" the device, mark it as disconnected and don't report the permission.
537 SDL_CameraDisconnected(device);
538 } else {
539 // okay! We have permission to use the camera _and_ opening the hardware worked out, report that the camera is usable!
540 SDL_CameraPermissionOutcome(device, true); // go go go!
541 }
542 }
543
544 UnrefPhysicalCamera(device); // we ref'd this in OpenDevice, release the extra reference.
545}
546
547
548static bool ANDROIDCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
549{
550#if 0 // !!! FIXME: for now, we'll just let this fail if it is going to fail, without checking for this
551 /* Cannot open a second camera, while the first one is opened.
552 * If you want to play several camera, they must all be opened first, then played.
553 *
554 * https://developer.android.com/reference/android/hardware/camera2/CameraManager
555 * "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
556 * before configuring sessions on any of the camera devices. * "
557 *
558 */
559 if (CheckDevicePlaying()) {
560 return SDL_SetError("A camera is already playing");
561 }
562#endif
563
564 device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
565 if (device->hidden == NULL) {
566 return false;
567 }
568
569 RefPhysicalCamera(device); // ref'd until permission callback fires.
570
571 // just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
572 SDL_copyp(&device->hidden->requested_spec, spec);
573 if (!SDL_RequestAndroidPermission("android.permission.CAMERA", CameraPermissionCallback, device)) {
574 UnrefPhysicalCamera(device);
575 return false;
576 }
577
578 return true; // we don't open the camera until permission is granted, so always succeed for now.
579}
580
581static void ANDROIDCAMERA_FreeDeviceHandle(SDL_Camera *device)
582{
583 if (device) {
584 SDL_free(device->handle);
585 }
586}
587
588static void GatherCameraSpecs(const char *devid, CameraFormatAddData *add_data, char **fullname, SDL_CameraPosition *position)
589{
590 SDL_zerop(add_data);
591
592 ACameraMetadata *metadata = NULL;
593 ACameraMetadata_const_entry cfgentry;
594 ACameraMetadata_const_entry durentry;
595 ACameraMetadata_const_entry infoentry;
596
597 // This can fail with an "unknown error" (with `adb logcat` reporting "no such file or directory")
598 // for "LEGACY" level cameras. I saw this happen on a 30-dollar budget phone I have for testing
599 // (but a different brand budget phone worked, so it's not strictly the low-end of Android devices).
600 // LEGACY devices are seen by onCameraAvailable, but are not otherwise accessible through
601 // libcamera2ndk. The Java camera2 API apparently _can_ access these cameras, but we're going on
602 // without them here for now, in hopes that such hardware is a dying breed.
603 if (pACameraManager_getCameraCharacteristics(cameraMgr, devid, &metadata) != ACAMERA_OK) {
604 return; // oh well.
605 } else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &cfgentry) != ACAMERA_OK) {
606 pACameraMetadata_free(metadata);
607 return; // oh well.
608 } else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS, &durentry) != ACAMERA_OK) {
609 pACameraMetadata_free(metadata);
610 return; // oh well.
611 }
612
613 *fullname = NULL;
614 if (pACameraMetadata_getConstEntry(metadata, ACAMERA_INFO_VERSION, &infoentry) == ACAMERA_OK) {
615 *fullname = (char *) SDL_malloc(infoentry.count + 1);
616 if (*fullname) {
617 SDL_strlcpy(*fullname, (const char *) infoentry.data.u8, infoentry.count + 1);
618 }
619 }
620
621 ACameraMetadata_const_entry posentry;
622 if (pACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &posentry) == ACAMERA_OK) { // ignore this if it fails.
623 if (*posentry.data.u8 == ACAMERA_LENS_FACING_FRONT) {
624 *position = SDL_CAMERA_POSITION_FRONT_FACING;
625 if (!*fullname) {
626 *fullname = SDL_strdup("Front-facing camera");
627 }
628 } else if (*posentry.data.u8 == ACAMERA_LENS_FACING_BACK) {
629 *position = SDL_CAMERA_POSITION_BACK_FACING;
630 if (!*fullname) {
631 *fullname = SDL_strdup("Back-facing camera");
632 }
633 }
634 }
635
636 if (!*fullname) {
637 *fullname = SDL_strdup("Generic camera"); // we tried.
638 }
639
640 const int32_t *i32ptr = cfgentry.data.i32;
641 for (int i = 0; i < cfgentry.count; i++, i32ptr += 4) {
642 const int32_t fmt = i32ptr[0];
643 const int w = i32ptr[1];
644 const int h = i32ptr[2];
645 const int32_t type = i32ptr[3];
646 SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN;
647 SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN;
648
649 if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
650 continue;
651 } else if ((w <= 0) || (h <= 0)) {
652 continue;
653 } else {
654 format_android_to_sdl(fmt, &sdlfmt, &colorspace);
655 if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
656 continue;
657 }
658 }
659
660#if 0 // !!! FIXME: these all come out with 0 durations on my test phone. :(
661 const int64_t *i64ptr = durentry.data.i64;
662 for (int j = 0; j < durentry.count; j++, i64ptr += 4) {
663 const int32_t fpsfmt = (int32_t) i64ptr[0];
664 const int fpsw = (int) i64ptr[1];
665 const int fpsh = (int) i64ptr[2];
666 const long long duration = (long long) i64ptr[3];
667 SDL_Log("CAMERA: possible fps %s %dx%d duration=%lld", SDL_GetPixelFormatName(sdlfmt), fpsw, fpsh, duration);
668 if ((duration > 0) && (fpsfmt == fmt) && (fpsw == w) && (fpsh == h)) {
669 SDL_AddCameraFormat(add_data, sdlfmt, colorspace, w, h, 1000000000, duration);
670 }
671 }
672#else
673 SDL_AddCameraFormat(add_data, sdlfmt, colorspace, w, h, 30, 1);
674#endif
675 }
676
677 pACameraMetadata_free(metadata);
678}
679
680static bool FindAndroidCameraByID(SDL_Camera *device, void *userdata)
681{
682 const char *devid = (const char *) userdata;
683 return (SDL_strcmp(devid, (const char *) device->handle) == 0);
684}
685
686static void MaybeAddDevice(const char *devid)
687{
688 #if DEBUG_CAMERA
689 SDL_Log("CAMERA: MaybeAddDevice('%s')", devid);
690 #endif
691
692 if (SDL_FindPhysicalCameraByCallback(FindAndroidCameraByID, (void *) devid)) {
693 return; // already have this one.
694 }
695
696 SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
697 char *fullname = NULL;
698 CameraFormatAddData add_data;
699 GatherCameraSpecs(devid, &add_data, &fullname, &position);
700 if (add_data.num_specs > 0) {
701 char *namecpy = SDL_strdup(devid);
702 if (namecpy) {
703 SDL_Camera *device = SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, namecpy);
704 if (!device) {
705 SDL_free(namecpy);
706 }
707 }
708 }
709
710 SDL_free(fullname);
711 SDL_free(add_data.specs);
712}
713
714// note that camera "availability" covers both hotplugging and whether another
715// has the device opened, but for something like Android, it's probably fine
716// to treat both unplugging and loss of access as disconnection events. When
717// the other app closes the camera, we get an available event as if it was
718// just plugged back in.
719
720static void onCameraAvailable(void *context, const char *cameraId)
721{
722 #if DEBUG_CAMERA
723 SDL_Log("CAMERA: CB onCameraAvailable('%s')", cameraId);
724 #endif
725 SDL_assert(cameraId != NULL);
726 MaybeAddDevice(cameraId);
727}
728
729static void onCameraUnavailable(void *context, const char *cameraId)
730{
731 #if DEBUG_CAMERA
732 SDL_Log("CAMERA: CB onCameraUnvailable('%s')", cameraId);
733 #endif
734
735 SDL_assert(cameraId != NULL);
736
737 // THIS CALLBACK FIRES WHEN YOU OPEN THE DEVICE YOURSELF. :(
738 // Make sure we don't have the device opened, in which case onDisconnected will fire instead if actually lost.
739 SDL_Camera *device = SDL_FindPhysicalCameraByCallback(FindAndroidCameraByID, (void *) cameraId);
740 if (device && !device->hidden) {
741 SDL_CameraDisconnected(device);
742 }
743}
744
745static const ACameraManager_AvailabilityCallbacks camera_availability_listener = {
746 NULL,
747 onCameraAvailable,
748 onCameraUnavailable
749};
750
751static void ANDROIDCAMERA_DetectDevices(void)
752{
753 ACameraIdList *list = NULL;
754 camera_status_t res = pACameraManager_getCameraIdList(cameraMgr, &list);
755
756 if ((res == ACAMERA_OK) && list) {
757 const int total = list->numCameras;
758 for (int i = 0; i < total; i++) {
759 MaybeAddDevice(list->cameraIds[i]);
760 }
761
762 pACameraManager_deleteCameraIdList(list);
763 }
764
765 pACameraManager_registerAvailabilityCallback(cameraMgr, &camera_availability_listener);
766}
767
768static void ANDROIDCAMERA_Deinitialize(void)
769{
770 pACameraManager_unregisterAvailabilityCallback(cameraMgr, &camera_availability_listener);
771 DestroyCameraManager();
772
773 dlclose(libcamera2ndk);
774 libcamera2ndk = NULL;
775 pACameraManager_create = NULL;
776 pACameraManager_registerAvailabilityCallback = NULL;
777 pACameraManager_unregisterAvailabilityCallback = NULL;
778 pACameraManager_getCameraIdList = NULL;
779 pACameraManager_deleteCameraIdList = NULL;
780 pACameraCaptureSession_close = NULL;
781 pACaptureRequest_free = NULL;
782 pACameraOutputTarget_free = NULL;
783 pACameraDevice_close = NULL;
784 pACameraManager_delete = NULL;
785 pACaptureSessionOutputContainer_free = NULL;
786 pACaptureSessionOutput_free = NULL;
787 pACameraManager_openCamera = NULL;
788 pACameraDevice_createCaptureRequest = NULL;
789 pACameraDevice_createCaptureSession = NULL;
790 pACameraManager_getCameraCharacteristics = NULL;
791 pACameraMetadata_free = NULL;
792 pACameraMetadata_getConstEntry = NULL;
793 pACameraCaptureSession_setRepeatingRequest = NULL;
794 pACameraOutputTarget_create = NULL;
795 pACaptureRequest_addTarget = NULL;
796 pACaptureSessionOutputContainer_add = NULL;
797 pACaptureSessionOutputContainer_create = NULL;
798 pACaptureSessionOutput_create = NULL;
799
800 dlclose(libmediandk);
801 libmediandk = NULL;
802 pAImage_delete = NULL;
803 pAImage_getTimestamp = NULL;
804 pAImage_getNumberOfPlanes = NULL;
805 pAImage_getPlaneRowStride = NULL;
806 pAImage_getPlaneData = NULL;
807 pAImageReader_acquireNextImage = NULL;
808 pAImageReader_delete = NULL;
809 pAImageReader_setImageListener = NULL;
810 pAImageReader_getWindow = NULL;
811 pAImageReader_new = NULL;
812}
813
814static bool ANDROIDCAMERA_Init(SDL_CameraDriverImpl *impl)
815{
816 // !!! FIXME: slide this off into a subroutine
817 // system libraries are in android-24 and later; we currently target android-16 and later, so check if they exist at runtime.
818 void *libcamera2 = dlopen("libcamera2ndk.so", RTLD_NOW | RTLD_LOCAL);
819 if (!libcamera2) {
820 SDL_Log("CAMERA: libcamera2ndk.so can't be loaded: %s", dlerror());
821 return false;
822 }
823
824 void *libmedia = dlopen("libmediandk.so", RTLD_NOW | RTLD_LOCAL);
825 if (!libmedia) {
826 SDL_Log("CAMERA: libmediandk.so can't be loaded: %s", dlerror());
827 dlclose(libcamera2);
828 return false;
829 }
830
831 bool okay = true;
832 #define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) dlsym(lib, #fn); if (!p##fn) { SDL_Log("CAMERA: symbol '%s' can't be found in %s: %s", #fn, #lib "ndk.so", dlerror()); okay = false; } }
833 //#define LOADSYM(lib, fn) p##fn = (pfn##fn) fn
834 LOADSYM(libcamera2, ACameraManager_create);
835 LOADSYM(libcamera2, ACameraManager_registerAvailabilityCallback);
836 LOADSYM(libcamera2, ACameraManager_unregisterAvailabilityCallback);
837 LOADSYM(libcamera2, ACameraManager_getCameraIdList);
838 LOADSYM(libcamera2, ACameraManager_deleteCameraIdList);
839 LOADSYM(libcamera2, ACameraCaptureSession_close);
840 LOADSYM(libcamera2, ACaptureRequest_free);
841 LOADSYM(libcamera2, ACameraOutputTarget_free);
842 LOADSYM(libcamera2, ACameraDevice_close);
843 LOADSYM(libcamera2, ACameraManager_delete);
844 LOADSYM(libcamera2, ACaptureSessionOutputContainer_free);
845 LOADSYM(libcamera2, ACaptureSessionOutput_free);
846 LOADSYM(libcamera2, ACameraManager_openCamera);
847 LOADSYM(libcamera2, ACameraDevice_createCaptureRequest);
848 LOADSYM(libcamera2, ACameraDevice_createCaptureSession);
849 LOADSYM(libcamera2, ACameraManager_getCameraCharacteristics);
850 LOADSYM(libcamera2, ACameraMetadata_free);
851 LOADSYM(libcamera2, ACameraMetadata_getConstEntry);
852 LOADSYM(libcamera2, ACameraCaptureSession_setRepeatingRequest);
853 LOADSYM(libcamera2, ACameraOutputTarget_create);
854 LOADSYM(libcamera2, ACaptureRequest_addTarget);
855 LOADSYM(libcamera2, ACaptureSessionOutputContainer_add);
856 LOADSYM(libcamera2, ACaptureSessionOutputContainer_create);
857 LOADSYM(libcamera2, ACaptureSessionOutput_create);
858 LOADSYM(libmedia, AImage_delete);
859 LOADSYM(libmedia, AImage_getTimestamp);
860 LOADSYM(libmedia, AImage_getNumberOfPlanes);
861 LOADSYM(libmedia, AImage_getPlaneRowStride);
862 LOADSYM(libmedia, AImage_getPlaneData);
863 LOADSYM(libmedia, AImageReader_acquireNextImage);
864 LOADSYM(libmedia, AImageReader_delete);
865 LOADSYM(libmedia, AImageReader_setImageListener);
866 LOADSYM(libmedia, AImageReader_getWindow);
867 LOADSYM(libmedia, AImageReader_new);
868 LOADSYM(libmedia, AImage_getWidth);
869 LOADSYM(libmedia, AImage_getHeight);
870
871 #undef LOADSYM
872
873 if (!okay) {
874 dlclose(libmedia);
875 dlclose(libcamera2);
876 }
877
878 if (!CreateCameraManager()) {
879 dlclose(libmedia);
880 dlclose(libcamera2);
881 return false;
882 }
883
884 libcamera2ndk = libcamera2;
885 libmediandk = libmedia;
886
887 impl->DetectDevices = ANDROIDCAMERA_DetectDevices;
888 impl->OpenDevice = ANDROIDCAMERA_OpenDevice;
889 impl->CloseDevice = ANDROIDCAMERA_CloseDevice;
890 impl->WaitDevice = ANDROIDCAMERA_WaitDevice;
891 impl->AcquireFrame = ANDROIDCAMERA_AcquireFrame;
892 impl->ReleaseFrame = ANDROIDCAMERA_ReleaseFrame;
893 impl->FreeDeviceHandle = ANDROIDCAMERA_FreeDeviceHandle;
894 impl->Deinitialize = ANDROIDCAMERA_Deinitialize;
895
896 impl->ProvidesOwnCallbackThread = true;
897
898 return true;
899}
900
901CameraBootStrap ANDROIDCAMERA_bootstrap = {
902 "android", "SDL Android camera driver", ANDROIDCAMERA_Init, false
903};
904
905#endif
diff --git a/contrib/SDL-3.2.8/src/camera/coremedia/SDL_camera_coremedia.m b/contrib/SDL-3.2.8/src/camera/coremedia/SDL_camera_coremedia.m
new file mode 100644
index 0000000..2ecfd13
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/coremedia/SDL_camera_coremedia.m
@@ -0,0 +1,508 @@
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#ifdef SDL_CAMERA_DRIVER_COREMEDIA
24
25#include "../SDL_syscamera.h"
26#include "../SDL_camera_c.h"
27#include "../../thread/SDL_systhread.h"
28
29#import <AVFoundation/AVFoundation.h>
30#import <CoreMedia/CoreMedia.h>
31
32/*
33 * Need to link with:: CoreMedia CoreVideo
34 *
35 * Add in pInfo.list:
36 * <key>NSCameraUsageDescription</key> <string>Access camera</string>
37 *
38 *
39 * MACOSX:
40 * Add to the Code Sign Entitlement file:
41 * <key>com.apple.security.device.camera</key> <true/>
42 */
43
44static void CoreMediaFormatToSDL(FourCharCode fmt, SDL_PixelFormat *pixel_format, SDL_Colorspace *colorspace)
45{
46 switch (fmt) {
47 #define CASE(x, y, z) case x: *pixel_format = y; *colorspace = z; return
48 // the 16LE ones should use 16BE if we're on a Bigendian system like PowerPC,
49 // but at current time there is no bigendian Apple platform that has CoreMedia.
50 CASE(kCMPixelFormat_16LE555, SDL_PIXELFORMAT_XRGB1555, SDL_COLORSPACE_SRGB);
51 CASE(kCMPixelFormat_16LE5551, SDL_PIXELFORMAT_RGBA5551, SDL_COLORSPACE_SRGB);
52 CASE(kCMPixelFormat_16LE565, SDL_PIXELFORMAT_RGB565, SDL_COLORSPACE_SRGB);
53 CASE(kCMPixelFormat_24RGB, SDL_PIXELFORMAT_RGB24, SDL_COLORSPACE_SRGB);
54 CASE(kCMPixelFormat_32ARGB, SDL_PIXELFORMAT_ARGB32, SDL_COLORSPACE_SRGB);
55 CASE(kCMPixelFormat_32BGRA, SDL_PIXELFORMAT_BGRA32, SDL_COLORSPACE_SRGB);
56 CASE(kCMPixelFormat_422YpCbCr8, SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED);
57 CASE(kCMPixelFormat_422YpCbCr8_yuvs, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED);
58 CASE(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED);
59 CASE(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_FULL);
60 CASE(kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange, SDL_PIXELFORMAT_P010, SDL_COLORSPACE_BT2020_LIMITED);
61 CASE(kCVPixelFormatType_420YpCbCr10BiPlanarFullRange, SDL_PIXELFORMAT_P010, SDL_COLORSPACE_BT2020_FULL);
62 #undef CASE
63 default:
64 #if DEBUG_CAMERA
65 SDL_Log("CAMERA: Unknown format FourCharCode '%d'", (int) fmt);
66 #endif
67 break;
68 }
69 *pixel_format = SDL_PIXELFORMAT_UNKNOWN;
70 *colorspace = SDL_COLORSPACE_UNKNOWN;
71}
72
73@class SDLCaptureVideoDataOutputSampleBufferDelegate;
74
75// just a simple wrapper to help ARC manage memory...
76@interface SDLPrivateCameraData : NSObject
77@property(nonatomic, retain) AVCaptureSession *session;
78@property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate;
79@property(nonatomic, assign) CMSampleBufferRef current_sample;
80@end
81
82@implementation SDLPrivateCameraData
83@end
84
85
86static bool CheckCameraPermissions(SDL_Camera *device)
87{
88 if (device->permission == 0) { // still expecting a permission result.
89 if (@available(macOS 14, *)) {
90 const AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
91 if (status != AVAuthorizationStatusNotDetermined) { // NotDetermined == still waiting for an answer from the user.
92 SDL_CameraPermissionOutcome(device, (status == AVAuthorizationStatusAuthorized) ? true : false);
93 }
94 } else {
95 SDL_CameraPermissionOutcome(device, true); // always allowed (or just unqueryable...?) on older macOS.
96 }
97 }
98
99 return (device->permission > 0);
100}
101
102// this delegate just receives new video frames on a Grand Central Dispatch queue, and fires off the
103// main device thread iterate function directly to consume it.
104@interface SDLCaptureVideoDataOutputSampleBufferDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
105 @property SDL_Camera *device;
106 -(id) init:(SDL_Camera *) dev;
107 -(void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
108@end
109
110@implementation SDLCaptureVideoDataOutputSampleBufferDelegate
111
112 -(id) init:(SDL_Camera *) dev {
113 if ( self = [super init] ) {
114 _device = dev;
115 }
116 return self;
117 }
118
119 - (void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
120 {
121 SDL_Camera *device = self.device;
122 if (!device || !device->hidden) {
123 return; // oh well.
124 }
125
126 if (!CheckCameraPermissions(device)) {
127 return; // nothing to do right now, dump what is probably a completely black frame.
128 }
129
130 SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
131 hidden.current_sample = sampleBuffer;
132 SDL_CameraThreadIterate(device);
133 hidden.current_sample = NULL;
134 }
135
136 - (void)captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
137 {
138 #if DEBUG_CAMERA
139 SDL_Log("CAMERA: Drop frame.");
140 #endif
141 }
142@end
143
144static bool COREMEDIA_WaitDevice(SDL_Camera *device)
145{
146 return true; // this isn't used atm, since we run our own thread out of Grand Central Dispatch.
147}
148
149static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
150{
151 SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
152 SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
153 CMSampleBufferRef sample_buffer = hidden.current_sample;
154 hidden.current_sample = NULL;
155 SDL_assert(sample_buffer != NULL); // should only have been called from our delegate with a new frame.
156
157 CMSampleTimingInfo timinginfo;
158 if (CMSampleBufferGetSampleTimingInfo(sample_buffer, 0, &timinginfo) == noErr) {
159 *timestampNS = (Uint64) (CMTimeGetSeconds(timinginfo.presentationTimeStamp) * ((Float64) SDL_NS_PER_SECOND));
160 } else {
161 SDL_assert(!"this shouldn't happen, I think.");
162 *timestampNS = 0;
163 }
164
165 CVImageBufferRef image = CMSampleBufferGetImageBuffer(sample_buffer); // does not retain `image` (and we don't want it to).
166 const int numPlanes = (int) CVPixelBufferGetPlaneCount(image);
167 const int planar = (int) CVPixelBufferIsPlanar(image);
168
169 #if DEBUG_CAMERA
170 const int w = (int) CVPixelBufferGetWidth(image);
171 const int h = (int) CVPixelBufferGetHeight(image);
172 const int sz = (int) CVPixelBufferGetDataSize(image);
173 const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
174 SDL_Log("CAMERA: buffer planar=%d numPlanes=%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch);
175 #endif
176
177 // !!! 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...
178 CVPixelBufferLockBaseAddress(image, 0);
179
180 frame->w = (int)CVPixelBufferGetWidth(image);
181 frame->h = (int)CVPixelBufferGetHeight(image);
182
183 if ((planar == 0) && (numPlanes == 0)) {
184 const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
185 const size_t buflen = pitch * frame->h;
186 frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
187 if (frame->pixels == NULL) {
188 result = SDL_CAMERA_FRAME_ERROR;
189 } else {
190 frame->pitch = pitch;
191 SDL_memcpy(frame->pixels, CVPixelBufferGetBaseAddress(image), buflen);
192 }
193 } else {
194 // !!! 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.
195 size_t buflen = 0;
196 for (int i = 0; i < numPlanes; i++) {
197 size_t plane_height = CVPixelBufferGetHeightOfPlane(image, i);
198 size_t plane_pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
199 size_t plane_size = (plane_pitch * plane_height);
200 buflen += plane_size;
201 }
202
203 frame->pitch = (int)CVPixelBufferGetBytesPerRowOfPlane(image, 0); // this is what SDL3 currently expects
204 frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
205 if (frame->pixels == NULL) {
206 result = SDL_CAMERA_FRAME_ERROR;
207 } else {
208 Uint8 *dst = frame->pixels;
209 for (int i = 0; i < numPlanes; i++) {
210 const void *src = CVPixelBufferGetBaseAddressOfPlane(image, i);
211 size_t plane_height = CVPixelBufferGetHeightOfPlane(image, i);
212 size_t plane_pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
213 size_t plane_size = (plane_pitch * plane_height);
214 SDL_memcpy(dst, src, plane_size);
215 dst += plane_size;
216 }
217 }
218 }
219
220 CVPixelBufferUnlockBaseAddress(image, 0);
221
222 return result;
223}
224
225static void COREMEDIA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
226{
227 // !!! FIXME: this currently copies the data to the surface, but in theory we could just keep this locked until ReleaseFrame...
228 SDL_aligned_free(frame->pixels);
229}
230
231static void COREMEDIA_CloseDevice(SDL_Camera *device)
232{
233 if (device && device->hidden) {
234 SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden);
235 device->hidden = NULL;
236
237 AVCaptureSession *session = hidden.session;
238 if (session) {
239 hidden.session = nil;
240 [session stopRunning];
241 [session removeInput:[session.inputs objectAtIndex:0]];
242 [session removeOutput:(AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]];
243 session = nil;
244 }
245
246 hidden.delegate = NULL;
247 hidden.current_sample = NULL;
248 }
249}
250
251static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
252{
253 AVCaptureDevice *avdevice = (__bridge AVCaptureDevice *) device->handle;
254
255 // Pick format that matches the spec
256 const int w = spec->width;
257 const int h = spec->height;
258 const float rate = (float)spec->framerate_numerator / spec->framerate_denominator;
259 AVCaptureDeviceFormat *spec_format = nil;
260 NSArray<AVCaptureDeviceFormat *> *formats = [avdevice formats];
261 for (AVCaptureDeviceFormat *format in formats) {
262 CMFormatDescriptionRef formatDescription = [format formatDescription];
263 SDL_PixelFormat device_format = SDL_PIXELFORMAT_UNKNOWN;
264 SDL_Colorspace device_colorspace = SDL_COLORSPACE_UNKNOWN;
265 CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(formatDescription), &device_format, &device_colorspace);
266 if (device_format != spec->format || device_colorspace != spec->colorspace) {
267 continue;
268 }
269
270 const CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
271 if ((int)dim.width != w || (int)dim.height != h) {
272 continue;
273 }
274
275 const float FRAMERATE_EPSILON = 0.01f;
276 for (AVFrameRateRange *framerate in format.videoSupportedFrameRateRanges) {
277 if (rate > (framerate.minFrameRate - FRAMERATE_EPSILON) &&
278 rate < (framerate.maxFrameRate + FRAMERATE_EPSILON)) {
279 spec_format = format;
280 break;
281 }
282 }
283
284 if (spec_format != nil) {
285 break;
286 }
287 }
288
289 if (spec_format == nil) {
290 return SDL_SetError("camera spec format not available");
291 } else if (![avdevice lockForConfiguration:NULL]) {
292 return SDL_SetError("Cannot lockForConfiguration");
293 }
294
295 avdevice.activeFormat = spec_format;
296 [avdevice unlockForConfiguration];
297
298 AVCaptureSession *session = [[AVCaptureSession alloc] init];
299 if (session == nil) {
300 return SDL_SetError("Failed to allocate/init AVCaptureSession");
301 }
302
303 session.sessionPreset = AVCaptureSessionPresetHigh;
304#if defined(SDL_PLATFORM_IOS)
305 if (@available(iOS 10.0, tvOS 17.0, *)) {
306 session.automaticallyConfiguresCaptureDeviceForWideColor = NO;
307 }
308#endif
309
310 NSError *error = nil;
311 AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:avdevice error:&error];
312 if (!input) {
313 return SDL_SetError("Cannot create AVCaptureDeviceInput");
314 }
315
316 AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
317 if (!output) {
318 return SDL_SetError("Cannot create AVCaptureVideoDataOutput");
319 }
320
321 output.videoSettings = @{
322 (id)kCVPixelBufferWidthKey : @(spec->width),
323 (id)kCVPixelBufferHeightKey : @(spec->height),
324 (id)kCVPixelBufferPixelFormatTypeKey : @(CMFormatDescriptionGetMediaSubType([spec_format formatDescription]))
325 };
326
327 char threadname[64];
328 SDL_GetCameraThreadName(device, threadname, sizeof (threadname));
329 dispatch_queue_t queue = dispatch_queue_create(threadname, NULL);
330 //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
331 if (!queue) {
332 return SDL_SetError("dispatch_queue_create() failed");
333 }
334
335 SDLCaptureVideoDataOutputSampleBufferDelegate *delegate = [[SDLCaptureVideoDataOutputSampleBufferDelegate alloc] init:device];
336 if (delegate == nil) {
337 return SDL_SetError("Cannot create SDLCaptureVideoDataOutputSampleBufferDelegate");
338 }
339 [output setSampleBufferDelegate:delegate queue:queue];
340
341 if (![session canAddInput:input]) {
342 return SDL_SetError("Cannot add AVCaptureDeviceInput");
343 }
344 [session addInput:input];
345
346 if (![session canAddOutput:output]) {
347 return SDL_SetError("Cannot add AVCaptureVideoDataOutput");
348 }
349 [session addOutput:output];
350
351 [session commitConfiguration];
352
353 SDLPrivateCameraData *hidden = [[SDLPrivateCameraData alloc] init];
354 if (hidden == nil) {
355 return SDL_SetError("Cannot create SDLPrivateCameraData");
356 }
357
358 hidden.session = session;
359 hidden.delegate = delegate;
360 hidden.current_sample = NULL;
361 device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden);
362
363 [session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`?
364
365 CheckCameraPermissions(device); // check right away, in case the process is already granted permission.
366
367 return true;
368}
369
370static void COREMEDIA_FreeDeviceHandle(SDL_Camera *device)
371{
372 if (device && device->handle) {
373 CFBridgingRelease(device->handle);
374 }
375}
376
377static void GatherCameraSpecs(AVCaptureDevice *device, CameraFormatAddData *add_data)
378{
379 SDL_zerop(add_data);
380
381 for (AVCaptureDeviceFormat *fmt in device.formats) {
382 if (CMFormatDescriptionGetMediaType(fmt.formatDescription) != kCMMediaType_Video) {
383 continue;
384 }
385
386//NSLog(@"Available camera format: %@\n", fmt);
387 SDL_PixelFormat device_format = SDL_PIXELFORMAT_UNKNOWN;
388 SDL_Colorspace device_colorspace = SDL_COLORSPACE_UNKNOWN;
389 CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(fmt.formatDescription), &device_format, &device_colorspace);
390 if (device_format == SDL_PIXELFORMAT_UNKNOWN) {
391 continue;
392 }
393
394 const CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(fmt.formatDescription);
395 const int w = (int) dims.width;
396 const int h = (int) dims.height;
397 for (AVFrameRateRange *framerate in fmt.videoSupportedFrameRateRanges) {
398 int min_numerator = 0, min_denominator = 1;
399 int max_numerator = 0, max_denominator = 1;
400
401 SDL_CalculateFraction(framerate.minFrameRate, &min_numerator, &min_denominator);
402 SDL_AddCameraFormat(add_data, device_format, device_colorspace, w, h, min_numerator, min_denominator);
403 SDL_CalculateFraction(framerate.maxFrameRate, &max_numerator, &max_denominator);
404 if (max_numerator != min_numerator || max_denominator != min_denominator) {
405 SDL_AddCameraFormat(add_data, device_format, device_colorspace, w, h, max_numerator, max_denominator);
406 }
407 }
408 }
409}
410
411static bool FindCoreMediaCameraByUniqueID(SDL_Camera *device, void *userdata)
412{
413 NSString *uniqueid = (__bridge NSString *) userdata;
414 AVCaptureDevice *avdev = (__bridge AVCaptureDevice *) device->handle;
415 return ([uniqueid isEqualToString:avdev.uniqueID]) ? true : false;
416}
417
418static void MaybeAddDevice(AVCaptureDevice *avdevice)
419{
420 if (!avdevice.connected) {
421 return; // not connected.
422 } else if (![avdevice hasMediaType:AVMediaTypeVideo]) {
423 return; // not a camera.
424 } else if (SDL_FindPhysicalCameraByCallback(FindCoreMediaCameraByUniqueID, (__bridge void *) avdevice.uniqueID)) {
425 return; // already have this one.
426 }
427
428 CameraFormatAddData add_data;
429 GatherCameraSpecs(avdevice, &add_data);
430 if (add_data.num_specs > 0) {
431 SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
432 if (avdevice.position == AVCaptureDevicePositionFront) {
433 position = SDL_CAMERA_POSITION_FRONT_FACING;
434 } else if (avdevice.position == AVCaptureDevicePositionBack) {
435 position = SDL_CAMERA_POSITION_BACK_FACING;
436 }
437 SDL_AddCamera(avdevice.localizedName.UTF8String, position, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(avdevice));
438 }
439
440 SDL_free(add_data.specs);
441}
442
443static void COREMEDIA_DetectDevices(void)
444{
445 NSArray<AVCaptureDevice *> *devices = nil;
446
447 if (@available(macOS 10.15, iOS 13, *)) {
448 // kind of annoying that there isn't a "give me anything that looks like a camera" option,
449 // so this list will need to be updated when Apple decides to add
450 // AVCaptureDeviceTypeBuiltInQuadrupleCamera some day.
451 NSArray *device_types = @[
452 #ifdef SDL_PLATFORM_IOS
453 AVCaptureDeviceTypeBuiltInTelephotoCamera,
454 AVCaptureDeviceTypeBuiltInDualCamera,
455 AVCaptureDeviceTypeBuiltInDualWideCamera,
456 AVCaptureDeviceTypeBuiltInTripleCamera,
457 AVCaptureDeviceTypeBuiltInUltraWideCamera,
458 #else
459 AVCaptureDeviceTypeExternalUnknown,
460 #endif
461 AVCaptureDeviceTypeBuiltInWideAngleCamera
462 ];
463
464 AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
465 discoverySessionWithDeviceTypes:device_types
466 mediaType:AVMediaTypeVideo
467 position:AVCaptureDevicePositionUnspecified];
468
469 devices = discoverySession.devices;
470 // !!! FIXME: this can use Key Value Observation to get hotplug events.
471 } else {
472 // this is deprecated but works back to macOS 10.7; 10.15 added AVCaptureDeviceDiscoverySession as a replacement.
473 devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
474 // !!! FIXME: this can use AVCaptureDeviceWasConnectedNotification and AVCaptureDeviceWasDisconnectedNotification with NSNotificationCenter to get hotplug events.
475 }
476
477 for (AVCaptureDevice *device in devices) {
478 MaybeAddDevice(device);
479 }
480}
481
482static void COREMEDIA_Deinitialize(void)
483{
484 // !!! FIXME: disable hotplug.
485}
486
487static bool COREMEDIA_Init(SDL_CameraDriverImpl *impl)
488{
489 impl->DetectDevices = COREMEDIA_DetectDevices;
490 impl->OpenDevice = COREMEDIA_OpenDevice;
491 impl->CloseDevice = COREMEDIA_CloseDevice;
492 impl->WaitDevice = COREMEDIA_WaitDevice;
493 impl->AcquireFrame = COREMEDIA_AcquireFrame;
494 impl->ReleaseFrame = COREMEDIA_ReleaseFrame;
495 impl->FreeDeviceHandle = COREMEDIA_FreeDeviceHandle;
496 impl->Deinitialize = COREMEDIA_Deinitialize;
497
498 impl->ProvidesOwnCallbackThread = true;
499
500 return true;
501}
502
503CameraBootStrap COREMEDIA_bootstrap = {
504 "coremedia", "SDL Apple CoreMedia camera driver", COREMEDIA_Init, false
505};
506
507#endif // SDL_CAMERA_DRIVER_COREMEDIA
508
diff --git a/contrib/SDL-3.2.8/src/camera/dummy/SDL_camera_dummy.c b/contrib/SDL-3.2.8/src/camera/dummy/SDL_camera_dummy.c
new file mode 100644
index 0000000..b2a4dc1
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/dummy/SDL_camera_dummy.c
@@ -0,0 +1,81 @@
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#ifdef SDL_CAMERA_DRIVER_DUMMY
24
25#include "../SDL_syscamera.h"
26
27static bool DUMMYCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
28{
29 return SDL_Unsupported();
30}
31
32static void DUMMYCAMERA_CloseDevice(SDL_Camera *device)
33{
34}
35
36static bool DUMMYCAMERA_WaitDevice(SDL_Camera *device)
37{
38 return SDL_Unsupported();
39}
40
41static SDL_CameraFrameResult DUMMYCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
42{
43 SDL_Unsupported();
44 return SDL_CAMERA_FRAME_ERROR;
45}
46
47static void DUMMYCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
48{
49}
50
51static void DUMMYCAMERA_DetectDevices(void)
52{
53}
54
55static void DUMMYCAMERA_FreeDeviceHandle(SDL_Camera *device)
56{
57}
58
59static void DUMMYCAMERA_Deinitialize(void)
60{
61}
62
63static bool DUMMYCAMERA_Init(SDL_CameraDriverImpl *impl)
64{
65 impl->DetectDevices = DUMMYCAMERA_DetectDevices;
66 impl->OpenDevice = DUMMYCAMERA_OpenDevice;
67 impl->CloseDevice = DUMMYCAMERA_CloseDevice;
68 impl->WaitDevice = DUMMYCAMERA_WaitDevice;
69 impl->AcquireFrame = DUMMYCAMERA_AcquireFrame;
70 impl->ReleaseFrame = DUMMYCAMERA_ReleaseFrame;
71 impl->FreeDeviceHandle = DUMMYCAMERA_FreeDeviceHandle;
72 impl->Deinitialize = DUMMYCAMERA_Deinitialize;
73
74 return true;
75}
76
77CameraBootStrap DUMMYCAMERA_bootstrap = {
78 "dummy", "SDL dummy camera driver", DUMMYCAMERA_Init, true
79};
80
81#endif // SDL_CAMERA_DRIVER_DUMMY
diff --git a/contrib/SDL-3.2.8/src/camera/emscripten/SDL_camera_emscripten.c b/contrib/SDL-3.2.8/src/camera/emscripten/SDL_camera_emscripten.c
new file mode 100644
index 0000000..fa2a511
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/emscripten/SDL_camera_emscripten.c
@@ -0,0 +1,275 @@
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#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
24
25#include "../SDL_syscamera.h"
26#include "../SDL_camera_c.h"
27#include "../../video/SDL_pixels_c.h"
28#include "../../video/SDL_surface_c.h"
29
30#include <emscripten/emscripten.h>
31
32// just turn off clang-format for this whole file, this INDENT_OFF stuff on
33// each EM_ASM section is ugly.
34/* *INDENT-OFF* */ // clang-format off
35
36EM_JS_DEPS(sdlcamera, "$dynCall");
37
38static bool EMSCRIPTENCAMERA_WaitDevice(SDL_Camera *device)
39{
40 SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread.
41 return false;
42}
43
44static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
45{
46 void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4);
47 if (!rgba) {
48 return SDL_CAMERA_FRAME_ERROR;
49 }
50
51 *timestampNS = SDL_GetTicksNS(); // best we can do here.
52
53 const int rc = MAIN_THREAD_EM_ASM_INT({
54 const w = $0;
55 const h = $1;
56 const rgba = $2;
57 const SDL3 = Module['SDL3'];
58 if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.ctx2d) === 'undefined')) {
59 return 0; // don't have something we need, oh well.
60 }
61
62 SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h);
63 const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data;
64 Module.HEAPU8.set(imgrgba, rgba);
65
66 return 1;
67 }, device->actual_spec.width, device->actual_spec.height, rgba);
68
69 if (!rc) {
70 SDL_free(rgba);
71 return SDL_CAMERA_FRAME_ERROR; // something went wrong, maybe shutting down; just don't return a frame.
72 }
73
74 frame->pixels = rgba;
75 frame->pitch = device->actual_spec.width * 4;
76
77 return SDL_CAMERA_FRAME_READY;
78}
79
80static void EMSCRIPTENCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
81{
82 SDL_free(frame->pixels);
83}
84
85static void EMSCRIPTENCAMERA_CloseDevice(SDL_Camera *device)
86{
87 if (device) {
88 MAIN_THREAD_EM_ASM({
89 const SDL3 = Module['SDL3'];
90 if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
91 return; // camera was closed and/or subsystem was shut down, we're already done.
92 }
93 SDL3.camera.stream.getTracks().forEach(track => track.stop()); // stop all recording.
94 SDL3.camera = {}; // dump our references to everything.
95 });
96 SDL_free(device->hidden);
97 device->hidden = NULL;
98 }
99}
100
101static int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps)
102{
103 if (approved) {
104 device->actual_spec.format = SDL_PIXELFORMAT_RGBA32;
105 device->actual_spec.width = w;
106 device->actual_spec.height = h;
107 device->actual_spec.framerate_numerator = fps;
108 device->actual_spec.framerate_denominator = 1;
109
110 if (!SDL_PrepareCameraSurfaces(device)) {
111 // uhoh, we're in trouble. Probably ran out of memory.
112 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Camera could not prepare surfaces: %s ... revoking approval!", SDL_GetError());
113 approved = 0; // disconnecting the SDL camera might not be safe here, just mark it as denied by user.
114 }
115 }
116
117 SDL_CameraPermissionOutcome(device, approved ? true : false);
118 return approved;
119}
120
121static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
122{
123 MAIN_THREAD_EM_ASM({
124 // Since we can't get actual specs until we make a move that prompts the user for
125 // permission, we don't list any specs for the device and wrangle it during device open.
126 const device = $0;
127 const w = $1;
128 const h = $2;
129 const framerate_numerator = $3;
130 const framerate_denominator = $4;
131 const outcome = $5;
132 const iterate = $6;
133
134 const constraints = {};
135 if ((w <= 0) || (h <= 0)) {
136 constraints.video = true; // didn't ask for anything, let the system choose.
137 } else {
138 constraints.video = {}; // asked for a specific thing: request it as "ideal" but take closest hardware will offer.
139 constraints.video.width = w;
140 constraints.video.height = h;
141 }
142
143 if ((framerate_numerator > 0) && (framerate_denominator > 0)) {
144 var fps = framerate_numerator / framerate_denominator;
145 constraints.video.frameRate = { ideal: fps };
146 }
147
148 function grabNextCameraFrame() { // !!! FIXME: this (currently) runs as a requestAnimationFrame callback, for lack of a better option.
149 const SDL3 = Module['SDL3'];
150 if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
151 return; // camera was closed and/or subsystem was shut down, stop iterating here.
152 }
153
154 // time for a new frame from the camera?
155 const nextframems = SDL3.camera.next_frame_time;
156 const now = performance.now();
157 if (now >= nextframems) {
158 dynCall('vi', iterate, [device]); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation.
159
160 // bump ahead but try to stay consistent on timing, in case we dropped frames.
161 while (SDL3.camera.next_frame_time < now) {
162 SDL3.camera.next_frame_time += SDL3.camera.fpsincrms;
163 }
164 }
165
166 requestAnimationFrame(grabNextCameraFrame); // run this function again at the display framerate. (!!! FIXME: would this be better as requestIdleCallback?)
167 }
168
169 navigator.mediaDevices.getUserMedia(constraints)
170 .then((stream) => {
171 const settings = stream.getVideoTracks()[0].getSettings();
172 const actualw = settings.width;
173 const actualh = settings.height;
174 const actualfps = settings.frameRate;
175 console.log("Camera is opened! Actual spec: (" + actualw + "x" + actualh + "), fps=" + actualfps);
176
177 if (dynCall('iiiiii', outcome, [device, 1, actualw, actualh, actualfps])) {
178 const video = document.createElement("video");
179 video.width = actualw;
180 video.height = actualh;
181 video.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
182 video.srcObject = stream;
183
184 const canvas = document.createElement("canvas");
185 canvas.width = actualw;
186 canvas.height = actualh;
187 canvas.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
188
189 const ctx2d = canvas.getContext('2d');
190
191 const SDL3 = Module['SDL3'];
192 SDL3.camera.width = actualw;
193 SDL3.camera.height = actualh;
194 SDL3.camera.fps = actualfps;
195 SDL3.camera.fpsincrms = 1000.0 / actualfps;
196 SDL3.camera.stream = stream;
197 SDL3.camera.video = video;
198 SDL3.camera.canvas = canvas;
199 SDL3.camera.ctx2d = ctx2d;
200 SDL3.camera.next_frame_time = performance.now();
201
202 video.play();
203 video.addEventListener('loadedmetadata', () => {
204 grabNextCameraFrame(); // start this loop going.
205 });
206 }
207 })
208 .catch((err) => {
209 console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message);
210 dynCall('iiiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is.
211 });
212 }, device, spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator, SDLEmscriptenCameraPermissionOutcome, SDL_CameraThreadIterate);
213
214 return true; // the real work waits until the user approves a camera.
215}
216
217static void EMSCRIPTENCAMERA_FreeDeviceHandle(SDL_Camera *device)
218{
219 // no-op.
220}
221
222static void EMSCRIPTENCAMERA_Deinitialize(void)
223{
224 MAIN_THREAD_EM_ASM({
225 if (typeof(Module['SDL3']) !== 'undefined') {
226 Module['SDL3'].camera = undefined;
227 }
228 });
229}
230
231static void EMSCRIPTENCAMERA_DetectDevices(void)
232{
233 // `navigator.mediaDevices` is not defined if unsupported or not in a secure context!
234 const int supported = MAIN_THREAD_EM_ASM_INT({ return (navigator.mediaDevices === undefined) ? 0 : 1; });
235
236 // if we have support at all, report a single generic camera with no specs.
237 // We'll find out if there really _is_ a camera when we try to open it, but querying it for real here
238 // will pop up a user permission dialog warning them we're trying to access the camera, and we generally
239 // don't want that during SDL_Init().
240 if (supported) {
241 SDL_AddCamera("Web browser's camera", SDL_CAMERA_POSITION_UNKNOWN, 0, NULL, (void *) (size_t) 0x1);
242 }
243}
244
245static bool EMSCRIPTENCAMERA_Init(SDL_CameraDriverImpl *impl)
246{
247 MAIN_THREAD_EM_ASM({
248 if (typeof(Module['SDL3']) === 'undefined') {
249 Module['SDL3'] = {};
250 }
251 Module['SDL3'].camera = {};
252 });
253
254 impl->DetectDevices = EMSCRIPTENCAMERA_DetectDevices;
255 impl->OpenDevice = EMSCRIPTENCAMERA_OpenDevice;
256 impl->CloseDevice = EMSCRIPTENCAMERA_CloseDevice;
257 impl->WaitDevice = EMSCRIPTENCAMERA_WaitDevice;
258 impl->AcquireFrame = EMSCRIPTENCAMERA_AcquireFrame;
259 impl->ReleaseFrame = EMSCRIPTENCAMERA_ReleaseFrame;
260 impl->FreeDeviceHandle = EMSCRIPTENCAMERA_FreeDeviceHandle;
261 impl->Deinitialize = EMSCRIPTENCAMERA_Deinitialize;
262
263 impl->ProvidesOwnCallbackThread = true;
264
265 return true;
266}
267
268CameraBootStrap EMSCRIPTENCAMERA_bootstrap = {
269 "emscripten", "SDL Emscripten MediaStream camera driver", EMSCRIPTENCAMERA_Init, false
270};
271
272/* *INDENT-ON* */ // clang-format on
273
274#endif // SDL_CAMERA_DRIVER_EMSCRIPTEN
275
diff --git a/contrib/SDL-3.2.8/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/contrib/SDL-3.2.8/src/camera/mediafoundation/SDL_camera_mediafoundation.c
new file mode 100644
index 0000000..d9d627d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/mediafoundation/SDL_camera_mediafoundation.c
@@ -0,0 +1,1143 @@
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// the Windows Media Foundation API
24
25#ifdef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
26
27#define COBJMACROS
28
29// this seems to be a bug in mfidl.h, just define this to avoid the problem section.
30#define __IMFVideoProcessorControl3_INTERFACE_DEFINED__
31
32#include "../../core/windows/SDL_windows.h"
33
34#include <mfapi.h>
35#include <mfidl.h>
36#include <mfreadwrite.h>
37
38#include "../SDL_syscamera.h"
39#include "../SDL_camera_c.h"
40
41static const IID SDL_IID_IMFMediaSource = { 0x279a808d, 0xaec7, 0x40c8, { 0x9c, 0x6b, 0xa6, 0xb4, 0x92, 0xc7, 0x8a, 0x66 } };
42static const IID SDL_IID_IMF2DBuffer = { 0x7dc9d5f9, 0x9ed9, 0x44ec, { 0x9b, 0xbf, 0x06, 0x00, 0xbb, 0x58, 0x9f, 0xbb } };
43static const IID SDL_IID_IMF2DBuffer2 = { 0x33ae5ea6, 0x4316, 0x436f, { 0x8d, 0xdd, 0xd7, 0x3d, 0x22, 0xf8, 0x29, 0xec } };
44static const GUID SDL_MF_MT_DEFAULT_STRIDE = { 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 } };
45static const GUID SDL_MF_MT_MAJOR_TYPE = { 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f } };
46static const GUID SDL_MF_MT_SUBTYPE = { 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 } };
47static const GUID SDL_MF_MT_VIDEO_NOMINAL_RANGE = { 0xc21b8ee5, 0xb956, 0x4071, { 0x8d, 0xaf, 0x32, 0x5e, 0xdf, 0x5c, 0xab, 0x11 } };
48static const GUID SDL_MF_MT_VIDEO_PRIMARIES = { 0xdbfbe4d7, 0x0740, 0x4ee0, { 0x81, 0x92, 0x85, 0x0a, 0xb0, 0xe2, 0x19, 0x35 } };
49static const GUID SDL_MF_MT_TRANSFER_FUNCTION = { 0x5fb0fce9, 0xbe5c, 0x4935, { 0xa8, 0x11, 0xec, 0x83, 0x8f, 0x8e, 0xed, 0x93 } };
50static const GUID SDL_MF_MT_YUV_MATRIX = { 0x3e23d450, 0x2c75, 0x4d25, { 0xa0, 0x0e, 0xb9, 0x16, 0x70, 0xd1, 0x23, 0x27 } };
51static const GUID SDL_MF_MT_VIDEO_CHROMA_SITING = { 0x65df2370, 0xc773, 0x4c33, { 0xaa, 0x64, 0x84, 0x3e, 0x06, 0x8e, 0xfb, 0x0c } };
52static const GUID SDL_MF_MT_FRAME_SIZE = { 0x1652c33d, 0xd6b2, 0x4012, { 0xb8, 0x34, 0x72, 0x03, 0x08, 0x49, 0xa3, 0x7d } };
53static const GUID SDL_MF_MT_FRAME_RATE = { 0xc459a2e8, 0x3d2c, 0x4e44, { 0xb1, 0x32, 0xfe, 0xe5, 0x15, 0x6c, 0x7b, 0xb0 } };
54static const GUID SDL_MFMediaType_Video = { 0x73646976, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } };
55static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME = { 0x60d0e559, 0x52f8, 0x4fa2, { 0xbb, 0xce, 0xac, 0xdb, 0x34, 0xa8, 0xec, 0x1 } };
56static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE = { 0xc60ac5fe, 0x252a, 0x478f, { 0xa0, 0xef, 0xbc, 0x8f, 0xa5, 0xf7, 0xca, 0xd3 } };
57static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK = { 0x58f0aad8, 0x22bf, 0x4f8a, { 0xbb, 0x3d, 0xd2, 0xc4, 0x97, 0x8c, 0x6e, 0x2f } };
58static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID = { 0x8ac3587a, 0x4ae7, 0x42d8, { 0x99, 0xe0, 0x0a, 0x60, 0x13, 0xee, 0xf9, 0x0f } };
59
60#ifdef __GNUC__
61#pragma GCC diagnostic push
62#pragma GCC diagnostic ignored "-Wmultichar"
63#endif
64
65#define SDL_DEFINE_MEDIATYPE_GUID(name, fmt) static const GUID SDL_##name = { fmt, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }
66SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB555, 24);
67SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB565, 23);
68SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB24, 20);
69SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB32, 22);
70SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_ARGB32, 21);
71SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_A2R10G10B10, 31);
72SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YV12, FCC('YV12'));
73SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_IYUV, FCC('IYUV'));
74SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YUY2, FCC('YUY2'));
75SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_UYVY, FCC('UYVY'));
76SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YVYU, FCC('YVYU'));
77SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV12, FCC('NV12'));
78SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV21, FCC('NV21'));
79SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_MJPG, FCC('MJPG'));
80#undef SDL_DEFINE_MEDIATYPE_GUID
81
82#ifdef __GNUC__
83#pragma GCC diagnostic pop
84#endif
85
86static const struct
87{
88 const GUID *guid;
89 SDL_PixelFormat format;
90 SDL_Colorspace colorspace;
91} fmtmappings[] = {
92 // This is not every possible format, just popular ones that SDL can reasonably handle.
93 // (and we should probably trim this list more.)
94 { &SDL_MFVideoFormat_RGB555, SDL_PIXELFORMAT_XRGB1555, SDL_COLORSPACE_SRGB },
95 { &SDL_MFVideoFormat_RGB565, SDL_PIXELFORMAT_RGB565, SDL_COLORSPACE_SRGB },
96 { &SDL_MFVideoFormat_RGB24, SDL_PIXELFORMAT_RGB24, SDL_COLORSPACE_SRGB },
97 { &SDL_MFVideoFormat_RGB32, SDL_PIXELFORMAT_XRGB8888, SDL_COLORSPACE_SRGB },
98 { &SDL_MFVideoFormat_ARGB32, SDL_PIXELFORMAT_ARGB8888, SDL_COLORSPACE_SRGB },
99 { &SDL_MFVideoFormat_A2R10G10B10, SDL_PIXELFORMAT_ARGB2101010, SDL_COLORSPACE_SRGB },
100 { &SDL_MFVideoFormat_YV12, SDL_PIXELFORMAT_YV12, SDL_COLORSPACE_BT709_LIMITED },
101 { &SDL_MFVideoFormat_IYUV, SDL_PIXELFORMAT_IYUV, SDL_COLORSPACE_BT709_LIMITED },
102 { &SDL_MFVideoFormat_YUY2, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED },
103 { &SDL_MFVideoFormat_UYVY, SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED },
104 { &SDL_MFVideoFormat_YVYU, SDL_PIXELFORMAT_YVYU, SDL_COLORSPACE_BT709_LIMITED },
105 { &SDL_MFVideoFormat_NV12, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED },
106 { &SDL_MFVideoFormat_NV21, SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED },
107 { &SDL_MFVideoFormat_MJPG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB }
108};
109
110static SDL_Colorspace GetMediaTypeColorspace(IMFMediaType *mediatype, SDL_Colorspace default_colorspace)
111{
112 SDL_Colorspace colorspace = default_colorspace;
113
114 if (SDL_COLORSPACETYPE(colorspace) == SDL_COLOR_TYPE_YCBCR) {
115 HRESULT ret;
116 UINT32 range = 0, primaries = 0, transfer = 0, matrix = 0, chroma = 0;
117
118 ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_VIDEO_NOMINAL_RANGE, &range);
119 if (SUCCEEDED(ret)) {
120 switch (range) {
121 case MFNominalRange_0_255:
122 range = SDL_COLOR_RANGE_FULL;
123 break;
124 case MFNominalRange_16_235:
125 range = SDL_COLOR_RANGE_LIMITED;
126 break;
127 default:
128 range = (UINT32)SDL_COLORSPACERANGE(default_colorspace);
129 break;
130 }
131 } else {
132 range = (UINT32)SDL_COLORSPACERANGE(default_colorspace);
133 }
134
135 ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_VIDEO_PRIMARIES, &primaries);
136 if (SUCCEEDED(ret)) {
137 switch (primaries) {
138 case MFVideoPrimaries_BT709:
139 primaries = SDL_COLOR_PRIMARIES_BT709;
140 break;
141 case MFVideoPrimaries_BT470_2_SysM:
142 primaries = SDL_COLOR_PRIMARIES_BT470M;
143 break;
144 case MFVideoPrimaries_BT470_2_SysBG:
145 primaries = SDL_COLOR_PRIMARIES_BT470BG;
146 break;
147 case MFVideoPrimaries_SMPTE170M:
148 primaries = SDL_COLOR_PRIMARIES_BT601;
149 break;
150 case MFVideoPrimaries_SMPTE240M:
151 primaries = SDL_COLOR_PRIMARIES_SMPTE240;
152 break;
153 case MFVideoPrimaries_EBU3213:
154 primaries = SDL_COLOR_PRIMARIES_EBU3213;
155 break;
156 case MFVideoPrimaries_BT2020:
157 primaries = SDL_COLOR_PRIMARIES_BT2020;
158 break;
159 case MFVideoPrimaries_XYZ:
160 primaries = SDL_COLOR_PRIMARIES_XYZ;
161 break;
162 case MFVideoPrimaries_DCI_P3:
163 primaries = SDL_COLOR_PRIMARIES_SMPTE432;
164 break;
165 default:
166 primaries = (UINT32)SDL_COLORSPACEPRIMARIES(default_colorspace);
167 break;
168 }
169 } else {
170 primaries = (UINT32)SDL_COLORSPACEPRIMARIES(default_colorspace);
171 }
172
173 ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_TRANSFER_FUNCTION, &transfer);
174 if (SUCCEEDED(ret)) {
175 switch (transfer) {
176 case MFVideoTransFunc_10:
177 transfer = SDL_TRANSFER_CHARACTERISTICS_LINEAR;
178 break;
179 case MFVideoTransFunc_22:
180 transfer = SDL_TRANSFER_CHARACTERISTICS_GAMMA22;
181 break;
182 case MFVideoTransFunc_709:
183 transfer = SDL_TRANSFER_CHARACTERISTICS_BT709;
184 break;
185 case MFVideoTransFunc_240M:
186 transfer = SDL_TRANSFER_CHARACTERISTICS_SMPTE240;
187 break;
188 case MFVideoTransFunc_sRGB:
189 transfer = SDL_TRANSFER_CHARACTERISTICS_SRGB;
190 break;
191 case MFVideoTransFunc_28:
192 transfer = SDL_TRANSFER_CHARACTERISTICS_GAMMA28;
193 break;
194 case MFVideoTransFunc_Log_100:
195 transfer = SDL_TRANSFER_CHARACTERISTICS_LOG100;
196 break;
197 case MFVideoTransFunc_2084:
198 transfer = SDL_TRANSFER_CHARACTERISTICS_PQ;
199 break;
200 case MFVideoTransFunc_HLG:
201 transfer = SDL_TRANSFER_CHARACTERISTICS_HLG;
202 break;
203 case 18 /* MFVideoTransFunc_BT1361_ECG */:
204 transfer = SDL_TRANSFER_CHARACTERISTICS_BT1361;
205 break;
206 case 19 /* MFVideoTransFunc_SMPTE428 */:
207 transfer = SDL_TRANSFER_CHARACTERISTICS_SMPTE428;
208 break;
209 default:
210 transfer = (UINT32)SDL_COLORSPACETRANSFER(default_colorspace);
211 break;
212 }
213 } else {
214 transfer = (UINT32)SDL_COLORSPACETRANSFER(default_colorspace);
215 }
216
217 ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_YUV_MATRIX, &matrix);
218 if (SUCCEEDED(ret)) {
219 switch (matrix) {
220 case MFVideoTransferMatrix_BT709:
221 matrix = SDL_MATRIX_COEFFICIENTS_BT709;
222 break;
223 case MFVideoTransferMatrix_BT601:
224 matrix = SDL_MATRIX_COEFFICIENTS_BT601;
225 break;
226 case MFVideoTransferMatrix_SMPTE240M:
227 matrix = SDL_MATRIX_COEFFICIENTS_SMPTE240;
228 break;
229 case MFVideoTransferMatrix_BT2020_10:
230 matrix = SDL_MATRIX_COEFFICIENTS_BT2020_NCL;
231 break;
232 case 6 /* MFVideoTransferMatrix_Identity */:
233 matrix = SDL_MATRIX_COEFFICIENTS_IDENTITY;
234 break;
235 case 7 /* MFVideoTransferMatrix_FCC47 */:
236 matrix = SDL_MATRIX_COEFFICIENTS_FCC;
237 break;
238 case 8 /* MFVideoTransferMatrix_YCgCo */:
239 matrix = SDL_MATRIX_COEFFICIENTS_YCGCO;
240 break;
241 case 9 /* MFVideoTransferMatrix_SMPTE2085 */:
242 matrix = SDL_MATRIX_COEFFICIENTS_SMPTE2085;
243 break;
244 case 10 /* MFVideoTransferMatrix_Chroma */:
245 matrix = SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL;
246 break;
247 case 11 /* MFVideoTransferMatrix_Chroma_const */:
248 matrix = SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL;
249 break;
250 case 12 /* MFVideoTransferMatrix_ICtCp */:
251 matrix = SDL_MATRIX_COEFFICIENTS_ICTCP;
252 break;
253 default:
254 matrix = (UINT32)SDL_COLORSPACEMATRIX(default_colorspace);
255 break;
256 }
257 } else {
258 matrix = (UINT32)SDL_COLORSPACEMATRIX(default_colorspace);
259 }
260
261 ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_VIDEO_CHROMA_SITING, &chroma);
262 if (SUCCEEDED(ret)) {
263 switch (chroma) {
264 case MFVideoChromaSubsampling_MPEG2:
265 chroma = SDL_CHROMA_LOCATION_LEFT;
266 break;
267 case MFVideoChromaSubsampling_MPEG1:
268 chroma = SDL_CHROMA_LOCATION_CENTER;
269 break;
270 case MFVideoChromaSubsampling_DV_PAL:
271 chroma = SDL_CHROMA_LOCATION_TOPLEFT;
272 break;
273 default:
274 chroma = (UINT32)SDL_COLORSPACECHROMA(default_colorspace);
275 break;
276 }
277 } else {
278 chroma = (UINT32)SDL_COLORSPACECHROMA(default_colorspace);
279 }
280
281 colorspace = SDL_DEFINE_COLORSPACE(SDL_COLOR_TYPE_YCBCR, range, primaries, transfer, matrix, chroma);
282 }
283 return colorspace;
284}
285
286static void MediaTypeToSDLFmt(IMFMediaType *mediatype, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
287{
288 HRESULT ret;
289 GUID type;
290
291 ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_SUBTYPE, &type);
292 if (SUCCEEDED(ret)) {
293 for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
294 if (WIN_IsEqualGUID(&type, fmtmappings[i].guid)) {
295 *format = fmtmappings[i].format;
296 *colorspace = GetMediaTypeColorspace(mediatype, fmtmappings[i].colorspace);
297 return;
298 }
299 }
300 }
301#if DEBUG_CAMERA
302 SDL_Log("Unknown media type: 0x%x (%c%c%c%c)", type.Data1,
303 (char)(Uint8)(type.Data1 >> 0),
304 (char)(Uint8)(type.Data1 >> 8),
305 (char)(Uint8)(type.Data1 >> 16),
306 (char)(Uint8)(type.Data1 >> 24));
307#endif
308 *format = SDL_PIXELFORMAT_UNKNOWN;
309 *colorspace = SDL_COLORSPACE_UNKNOWN;
310}
311
312static const GUID *SDLFmtToMFVidFmtGuid(SDL_PixelFormat format)
313{
314 for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
315 if (fmtmappings[i].format == format) {
316 return fmtmappings[i].guid;
317 }
318 }
319 return NULL;
320}
321
322
323// handle to Media Foundation libs--Vista and later!--for access to the Media Foundation API.
324
325// mf.dll ...
326static HMODULE libmf = NULL;
327typedef HRESULT(WINAPI *pfnMFEnumDeviceSources)(IMFAttributes *,IMFActivate ***,UINT32 *);
328typedef HRESULT(WINAPI *pfnMFCreateDeviceSource)(IMFAttributes *, IMFMediaSource **);
329static pfnMFEnumDeviceSources pMFEnumDeviceSources = NULL;
330static pfnMFCreateDeviceSource pMFCreateDeviceSource = NULL;
331
332// mfplat.dll ...
333static HMODULE libmfplat = NULL;
334typedef HRESULT(WINAPI *pfnMFStartup)(ULONG, DWORD);
335typedef HRESULT(WINAPI *pfnMFShutdown)(void);
336typedef HRESULT(WINAPI *pfnMFCreateAttributes)(IMFAttributes **, UINT32);
337typedef HRESULT(WINAPI *pfnMFCreateMediaType)(IMFMediaType **);
338typedef HRESULT(WINAPI *pfnMFGetStrideForBitmapInfoHeader)(DWORD, DWORD, LONG *);
339
340static pfnMFStartup pMFStartup = NULL;
341static pfnMFShutdown pMFShutdown = NULL;
342static pfnMFCreateAttributes pMFCreateAttributes = NULL;
343static pfnMFCreateMediaType pMFCreateMediaType = NULL;
344static pfnMFGetStrideForBitmapInfoHeader pMFGetStrideForBitmapInfoHeader = NULL;
345
346// mfreadwrite.dll ...
347static HMODULE libmfreadwrite = NULL;
348typedef HRESULT(WINAPI *pfnMFCreateSourceReaderFromMediaSource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **);
349static pfnMFCreateSourceReaderFromMediaSource pMFCreateSourceReaderFromMediaSource = NULL;
350
351
352typedef struct SDL_PrivateCameraData
353{
354 IMFSourceReader *srcreader;
355 IMFSample *current_sample;
356 int pitch;
357} SDL_PrivateCameraData;
358
359static bool MEDIAFOUNDATION_WaitDevice(SDL_Camera *device)
360{
361 SDL_assert(device->hidden->current_sample == NULL);
362
363 IMFSourceReader *srcreader = device->hidden->srcreader;
364 IMFSample *sample = NULL;
365
366 while (!SDL_GetAtomicInt(&device->shutdown)) {
367 DWORD stream_flags = 0;
368 const HRESULT ret = IMFSourceReader_ReadSample(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &stream_flags, NULL, &sample);
369 if (FAILED(ret)) {
370 return false; // ruh roh.
371 }
372
373 // we currently ignore stream_flags format changes, but my _hope_ is that IMFSourceReader is handling this and
374 // will continue to give us the explicitly-specified format we requested when opening the device, though, and
375 // we don't have to manually deal with it.
376
377 if (sample != NULL) {
378 break;
379 } else if (stream_flags & (MF_SOURCE_READERF_ERROR | MF_SOURCE_READERF_ENDOFSTREAM)) {
380 return false; // apparently this camera has gone down. :/
381 }
382
383 // otherwise, there was some minor burp, probably; just try again.
384 }
385
386 device->hidden->current_sample = sample;
387
388 return true;
389}
390
391
392#ifdef KEEP_ACQUIRED_BUFFERS_LOCKED
393
394#define PROP_SURFACE_IMFOBJS_POINTER "SDL.camera.mediafoundation.imfobjs"
395
396typedef struct SDL_IMFObjects
397{
398 IMF2DBuffer2 *buffer2d2;
399 IMF2DBuffer *buffer2d;
400 IMFMediaBuffer *buffer;
401 IMFSample *sample;
402} SDL_IMFObjects;
403
404static void SDLCALL CleanupIMF2DBuffer2(void *userdata, void *value)
405{
406 SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
407 IMF2DBuffer2_Unlock2D(objs->buffer2d2);
408 IMF2DBuffer2_Release(objs->buffer2d2);
409 IMFMediaBuffer_Release(objs->buffer);
410 IMFSample_Release(objs->sample);
411 SDL_free(objs);
412}
413
414static void SDLCALL CleanupIMF2DBuffer(void *userdata, void *value)
415{
416 SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
417 IMF2DBuffer_Unlock2D(objs->buffer2d);
418 IMF2DBuffer_Release(objs->buffer2d);
419 IMFMediaBuffer_Release(objs->buffer);
420 IMFSample_Release(objs->sample);
421 SDL_free(objs);
422}
423
424static void SDLCALL CleanupIMFMediaBuffer(void *userdata, void *value)
425{
426 SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
427 IMFMediaBuffer_Unlock(objs->buffer);
428 IMFMediaBuffer_Release(objs->buffer);
429 IMFSample_Release(objs->sample);
430 SDL_free(objs);
431}
432
433static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
434{
435 SDL_assert(device->hidden->current_sample != NULL);
436
437 SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
438 HRESULT ret;
439 LONGLONG timestamp100NS = 0;
440 SDL_IMFObjects *objs = (SDL_IMFObjects *) SDL_calloc(1, sizeof (SDL_IMFObjects));
441
442 if (objs == NULL) {
443 return SDL_CAMERA_FRAME_ERROR;
444 }
445
446 objs->sample = device->hidden->current_sample;
447 device->hidden->current_sample = NULL;
448
449 const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
450 if (!surfprops) {
451 result = SDL_CAMERA_FRAME_ERROR;
452 } else {
453 ret = IMFSample_GetSampleTime(objs->sample, &timestamp100NS);
454 if (FAILED(ret)) {
455 result = SDL_CAMERA_FRAME_ERROR;
456 }
457
458 *timestampNS = timestamp100NS * 100; // the timestamps are in 100-nanosecond increments; move to full nanoseconds.
459 }
460
461 ret = (result == SDL_CAMERA_FRAME_ERROR) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(objs->sample, &objs->buffer); // IMFSample_GetBufferByIndex(objs->sample, 0, &objs->buffer);
462
463 if (FAILED(ret)) {
464 SDL_free(objs);
465 result = SDL_CAMERA_FRAME_ERROR;
466 } else {
467 BYTE *pixels = NULL;
468 LONG pitch = 0;
469 DWORD buflen = 0;
470
471 if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer2, (void **)&objs->buffer2d2))) {
472 BYTE *bufstart = NULL;
473 ret = IMF2DBuffer2_Lock2DSize(objs->buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
474 if (FAILED(ret)) {
475 result = SDL_CAMERA_FRAME_ERROR;
476 CleanupIMF2DBuffer2(NULL, objs);
477 } else {
478 if (frame->format == SDL_PIXELFORMAT_MJPG) {
479 pitch = (LONG)buflen;
480 }
481 if (pitch < 0) { // image rows are reversed.
482 pixels += -pitch * (frame->h - 1);
483 }
484 frame->pixels = pixels;
485 frame->pitch = (int)pitch;
486 if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer2, NULL)) {
487 result = SDL_CAMERA_FRAME_ERROR;
488 }
489 }
490 } else if (frame->format != SDL_PIXELFORMAT_MJPG &&
491 SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer, (void **)&objs->buffer2d))) {
492 ret = IMF2DBuffer_Lock2D(objs->buffer2d, &pixels, &pitch);
493 if (FAILED(ret)) {
494 CleanupIMF2DBuffer(NULL, objs);
495 result = SDL_CAMERA_FRAME_ERROR;
496 } else {
497 if (pitch < 0) { // image rows are reversed.
498 pixels += -pitch * (frame->h - 1);
499 }
500 frame->pixels = pixels;
501 frame->pitch = (int)pitch;
502 if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer, NULL)) {
503 result = SDL_CAMERA_FRAME_ERROR;
504 }
505 }
506 } else {
507 DWORD maxlen = 0;
508 ret = IMFMediaBuffer_Lock(objs->buffer, &pixels, &maxlen, &buflen);
509 if (FAILED(ret)) {
510 CleanupIMFMediaBuffer(NULL, objs);
511 result = SDL_CAMERA_FRAME_ERROR;
512 } else {
513 if (frame->format == SDL_PIXELFORMAT_MJPG) {
514 pitch = (LONG)buflen;
515 } else {
516 pitch = (LONG)device->hidden->pitch;
517 }
518 if (pitch < 0) { // image rows are reversed.
519 pixels += -pitch * (frame->h - 1);
520 }
521 frame->pixels = pixels;
522 frame->pitch = (int)pitch;
523 if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMFMediaBuffer, NULL)) {
524 result = SDL_CAMERA_FRAME_ERROR;
525 }
526 }
527 }
528 }
529
530 if (result != SDL_CAMERA_FRAME_READY) {
531 *timestampNS = 0;
532 }
533
534 return result;
535}
536
537static void MEDIAFOUNDATION_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
538{
539 const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
540 if (surfprops) {
541 // this will release the IMFBuffer and IMFSample objects for this frame.
542 SDL_ClearProperty(surfprops, PROP_SURFACE_IMFOBJS_POINTER);
543 }
544}
545
546#else
547
548static SDL_CameraFrameResult MEDIAFOUNDATION_CopyFrame(SDL_Surface *frame, const BYTE *pixels, LONG pitch, DWORD buflen)
549{
550 frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
551 if (!frame->pixels) {
552 return SDL_CAMERA_FRAME_ERROR;
553 }
554
555 const BYTE *start = pixels;
556 if (pitch < 0) { // image rows are reversed.
557 start += -pitch * (frame->h - 1);
558 }
559 SDL_memcpy(frame->pixels, start, buflen);
560 frame->pitch = (int)pitch;
561
562 return SDL_CAMERA_FRAME_READY;
563}
564
565static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
566{
567 SDL_assert(device->hidden->current_sample != NULL);
568
569 SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
570 HRESULT ret;
571 LONGLONG timestamp100NS = 0;
572
573 IMFSample *sample = device->hidden->current_sample;
574 device->hidden->current_sample = NULL;
575
576 const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
577 if (!surfprops) {
578 result = SDL_CAMERA_FRAME_ERROR;
579 } else {
580 ret = IMFSample_GetSampleTime(sample, &timestamp100NS);
581 if (FAILED(ret)) {
582 result = SDL_CAMERA_FRAME_ERROR;
583 }
584
585 *timestampNS = timestamp100NS * 100; // the timestamps are in 100-nanosecond increments; move to full nanoseconds.
586 }
587
588 IMFMediaBuffer *buffer = NULL;
589 ret = (result < 0) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(sample, &buffer); // IMFSample_GetBufferByIndex(sample, 0, &buffer);
590
591 if (FAILED(ret)) {
592 result = SDL_CAMERA_FRAME_ERROR;
593 } else {
594 IMF2DBuffer *buffer2d = NULL;
595 IMF2DBuffer2 *buffer2d2 = NULL;
596 BYTE *pixels = NULL;
597 LONG pitch = 0;
598 DWORD buflen = 0;
599
600 if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer2, (void **)&buffer2d2))) {
601 BYTE *bufstart = NULL;
602 ret = IMF2DBuffer2_Lock2DSize(buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
603 if (FAILED(ret)) {
604 result = SDL_CAMERA_FRAME_ERROR;
605 } else {
606 if (frame->format == SDL_PIXELFORMAT_MJPG) {
607 pitch = (LONG)buflen;
608 }
609 result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen);
610 IMF2DBuffer2_Unlock2D(buffer2d2);
611 }
612 IMF2DBuffer2_Release(buffer2d2);
613 } else if (frame->format != SDL_PIXELFORMAT_MJPG &&
614 SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer, (void **)&buffer2d))) {
615 ret = IMF2DBuffer_Lock2D(buffer2d, &pixels, &pitch);
616 if (FAILED(ret)) {
617 result = SDL_CAMERA_FRAME_ERROR;
618 } else {
619 buflen = SDL_abs((int)pitch) * frame->h;
620 result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen);
621 IMF2DBuffer_Unlock2D(buffer2d);
622 }
623 IMF2DBuffer_Release(buffer2d);
624 } else {
625 DWORD maxlen = 0;
626 ret = IMFMediaBuffer_Lock(buffer, &pixels, &maxlen, &buflen);
627 if (FAILED(ret)) {
628 result = SDL_CAMERA_FRAME_ERROR;
629 } else {
630 if (frame->format == SDL_PIXELFORMAT_MJPG) {
631 pitch = (LONG)buflen;
632 } else {
633 pitch = (LONG)device->hidden->pitch;
634 }
635 result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen);
636 IMFMediaBuffer_Unlock(buffer);
637 }
638 }
639 IMFMediaBuffer_Release(buffer);
640 }
641
642 IMFSample_Release(sample);
643
644 if (result != SDL_CAMERA_FRAME_READY) {
645 *timestampNS = 0;
646 }
647
648 return result;
649}
650
651static void MEDIAFOUNDATION_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
652{
653 SDL_aligned_free(frame->pixels);
654}
655
656#endif
657
658static void MEDIAFOUNDATION_CloseDevice(SDL_Camera *device)
659{
660 if (device && device->hidden) {
661 if (device->hidden->srcreader) {
662 IMFSourceReader_Release(device->hidden->srcreader);
663 }
664 if (device->hidden->current_sample) {
665 IMFSample_Release(device->hidden->current_sample);
666 }
667 SDL_free(device->hidden);
668 device->hidden = NULL;
669 }
670}
671
672// this function is from https://learn.microsoft.com/en-us/windows/win32/medfound/uncompressed-video-buffers
673static HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride)
674{
675 LONG lStride = 0;
676
677 // Try to get the default stride from the media type.
678 HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride);
679 if (FAILED(ret)) {
680 // Attribute not set. Try to calculate the default stride.
681
682 GUID subtype = GUID_NULL;
683 UINT32 width = 0;
684 // UINT32 height = 0;
685 UINT64 val = 0;
686
687 // Get the subtype and the image size.
688 ret = IMFMediaType_GetGUID(pType, &SDL_MF_MT_SUBTYPE, &subtype);
689 if (FAILED(ret)) {
690 goto done;
691 }
692
693 ret = IMFMediaType_GetUINT64(pType, &SDL_MF_MT_FRAME_SIZE, &val);
694 if (FAILED(ret)) {
695 goto done;
696 }
697
698 width = (UINT32) (val >> 32);
699 // height = (UINT32) val;
700
701 ret = pMFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lStride);
702 if (FAILED(ret)) {
703 goto done;
704 }
705
706 // Set the attribute for later reference.
707 IMFMediaType_SetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32) lStride);
708 }
709
710 if (SUCCEEDED(ret)) {
711 *plStride = lStride;
712 }
713
714done:
715 return ret;
716}
717
718
719static bool MEDIAFOUNDATION_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
720{
721 const char *utf8symlink = (const char *) device->handle;
722 IMFAttributes *attrs = NULL;
723 LPWSTR wstrsymlink = NULL;
724 IMFMediaSource *source = NULL;
725 IMFMediaType *mediatype = NULL;
726 IMFSourceReader *srcreader = NULL;
727#if 0
728 DWORD num_streams = 0;
729#endif
730 LONG lstride = 0;
731 //PROPVARIANT var;
732 HRESULT ret;
733
734 #if 0
735 IMFStreamDescriptor *streamdesc = NULL;
736 IMFPresentationDescriptor *presentdesc = NULL;
737 IMFMediaTypeHandler *handler = NULL;
738 #endif
739
740 #if DEBUG_CAMERA
741 SDL_Log("CAMERA: opening device with symlink of '%s'", utf8symlink);
742 #endif
743
744 wstrsymlink = WIN_UTF8ToString(utf8symlink);
745 if (!wstrsymlink) {
746 goto failed;
747 }
748
749 #define CHECK_HRESULT(what, r) if (FAILED(r)) { WIN_SetErrorFromHRESULT(what " failed", r); goto failed; }
750
751 ret = pMFCreateAttributes(&attrs, 1);
752 CHECK_HRESULT("MFCreateAttributes", ret);
753
754 ret = IMFAttributes_SetGUID(attrs, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
755 CHECK_HRESULT("IMFAttributes_SetGUID(srctype)", ret);
756
757 ret = IMFAttributes_SetString(attrs, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, wstrsymlink);
758 CHECK_HRESULT("IMFAttributes_SetString(symlink)", ret);
759
760 ret = pMFCreateDeviceSource(attrs, &source);
761 CHECK_HRESULT("MFCreateDeviceSource", ret);
762
763 IMFAttributes_Release(attrs);
764 SDL_free(wstrsymlink);
765 attrs = NULL;
766 wstrsymlink = NULL;
767
768 // !!! FIXME: I think it'd be nice to do this without an IMFSourceReader,
769 // since it's just utility code that has to handle more complex media streams
770 // than we're dealing with, but this will do for now. The docs are slightly
771 // insistent that you should use one, though...Maybe it's extremely hard
772 // to handle directly at the IMFMediaSource layer...?
773 ret = pMFCreateSourceReaderFromMediaSource(source, NULL, &srcreader);
774 CHECK_HRESULT("MFCreateSourceReaderFromMediaSource", ret);
775
776 // !!! FIXME: do we actually have to find the media type object in the source reader or can we just roll our own like this?
777 ret = pMFCreateMediaType(&mediatype);
778 CHECK_HRESULT("MFCreateMediaType", ret);
779
780 ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &SDL_MFMediaType_Video);
781 CHECK_HRESULT("IMFMediaType_SetGUID(major_type)", ret);
782
783 ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_SUBTYPE, SDLFmtToMFVidFmtGuid(spec->format));
784 CHECK_HRESULT("IMFMediaType_SetGUID(subtype)", ret);
785
786 ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, (((UINT64)spec->width) << 32) | ((UINT64)spec->height));
787 CHECK_HRESULT("MFSetAttributeSize(frame_size)", ret);
788
789 ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, (((UINT64)spec->framerate_numerator) << 32) | ((UINT64)spec->framerate_denominator));
790 CHECK_HRESULT("MFSetAttributeRatio(frame_rate)", ret);
791
792 ret = IMFSourceReader_SetCurrentMediaType(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediatype);
793 CHECK_HRESULT("IMFSourceReader_SetCurrentMediaType", ret);
794
795 #if 0 // this (untested thing) is what we would do to get started with a IMFMediaSource that _doesn't_ use IMFSourceReader...
796 ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc);
797 CHECK_HRESULT("IMFMediaSource_CreatePresentationDescriptor", ret);
798
799 ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams);
800 CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorCount", ret);
801
802 for (DWORD i = 0; i < num_streams; i++) {
803 BOOL selected = FALSE;
804 ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc);
805 CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorByIndex", ret);
806
807 if (selected) {
808 ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler);
809 CHECK_HRESULT("IMFStreamDescriptor_GetMediaTypeHandler", ret);
810 IMFMediaTypeHandler_SetCurrentMediaType(handler, mediatype);
811 IMFMediaTypeHandler_Release(handler);
812 handler = NULL;
813 }
814
815 IMFStreamDescriptor_Release(streamdesc);
816 streamdesc = NULL;
817 }
818
819 PropVariantInit(&var);
820 var.vt = VT_EMPTY;
821 ret = IMFMediaSource_Start(source, presentdesc, NULL, &var);
822 PropVariantClear(&var);
823 CHECK_HRESULT("IMFMediaSource_Start", ret);
824
825 IMFPresentationDescriptor_Release(presentdesc);
826 presentdesc = NULL;
827 #endif
828
829 ret = GetDefaultStride(mediatype, &lstride);
830 CHECK_HRESULT("GetDefaultStride", ret);
831
832 IMFMediaType_Release(mediatype);
833 mediatype = NULL;
834
835 device->hidden = (SDL_PrivateCameraData *) SDL_calloc(1, sizeof (SDL_PrivateCameraData));
836 if (!device->hidden) {
837 goto failed;
838 }
839
840 device->hidden->pitch = (int) lstride;
841 device->hidden->srcreader = srcreader;
842 IMFMediaSource_Release(source); // srcreader is holding a reference to this.
843
844 // There is no user permission prompt for camera access (I think?)
845 SDL_CameraPermissionOutcome(device, true);
846
847 #undef CHECK_HRESULT
848
849 return true;
850
851failed:
852
853 if (srcreader) {
854 IMFSourceReader_Release(srcreader);
855 }
856
857 #if 0
858 if (handler) {
859 IMFMediaTypeHandler_Release(handler);
860 }
861
862 if (streamdesc) {
863 IMFStreamDescriptor_Release(streamdesc);
864 }
865
866 if (presentdesc) {
867 IMFPresentationDescriptor_Release(presentdesc);
868 }
869 #endif
870
871 if (source) {
872 IMFMediaSource_Shutdown(source);
873 IMFMediaSource_Release(source);
874 }
875
876 if (mediatype) {
877 IMFMediaType_Release(mediatype);
878 }
879
880 if (attrs) {
881 IMFAttributes_Release(attrs);
882 }
883 SDL_free(wstrsymlink);
884
885 return false;
886}
887
888static void MEDIAFOUNDATION_FreeDeviceHandle(SDL_Camera *device)
889{
890 if (device) {
891 SDL_free(device->handle); // the device's symlink string.
892 }
893}
894
895static char *QueryActivationObjectString(IMFActivate *activation, const GUID *pguid)
896{
897 LPWSTR wstr = NULL;
898 UINT32 wlen = 0;
899 HRESULT ret = IMFActivate_GetAllocatedString(activation, pguid, &wstr, &wlen);
900 if (FAILED(ret)) {
901 return NULL;
902 }
903
904 char *utf8str = WIN_StringToUTF8(wstr);
905 CoTaskMemFree(wstr);
906 return utf8str;
907}
908
909static void GatherCameraSpecs(IMFMediaSource *source, CameraFormatAddData *add_data)
910{
911 HRESULT ret;
912
913 // this has like a thousand steps. :/
914
915 SDL_zerop(add_data);
916
917 IMFPresentationDescriptor *presentdesc = NULL;
918 ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc);
919 if (FAILED(ret) || !presentdesc) {
920 return;
921 }
922
923 DWORD num_streams = 0;
924 ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams);
925 if (FAILED(ret)) {
926 num_streams = 0;
927 }
928
929 for (DWORD i = 0; i < num_streams; i++) {
930 IMFStreamDescriptor *streamdesc = NULL;
931 BOOL selected = FALSE;
932 ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc);
933 if (FAILED(ret) || !streamdesc) {
934 continue;
935 }
936
937 if (selected) {
938 IMFMediaTypeHandler *handler = NULL;
939 ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler);
940 if (SUCCEEDED(ret) && handler) {
941 DWORD num_mediatype = 0;
942 ret = IMFMediaTypeHandler_GetMediaTypeCount(handler, &num_mediatype);
943 if (FAILED(ret)) {
944 num_mediatype = 0;
945 }
946
947 for (DWORD j = 0; j < num_mediatype; j++) {
948 IMFMediaType *mediatype = NULL;
949 ret = IMFMediaTypeHandler_GetMediaTypeByIndex(handler, j, &mediatype);
950 if (SUCCEEDED(ret) && mediatype) {
951 GUID type;
952 ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &type);
953 if (SUCCEEDED(ret) && WIN_IsEqualGUID(&type, &SDL_MFMediaType_Video)) {
954 SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN;
955 SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN;
956 MediaTypeToSDLFmt(mediatype, &sdlfmt, &colorspace);
957 if (sdlfmt != SDL_PIXELFORMAT_UNKNOWN) {
958 UINT64 val = 0;
959 UINT32 w = 0, h = 0;
960 ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, &val);
961 w = (UINT32)(val >> 32);
962 h = (UINT32)val;
963 if (SUCCEEDED(ret) && w && h) {
964 UINT32 framerate_numerator = 0, framerate_denominator = 0;
965 ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, &val);
966 framerate_numerator = (UINT32)(val >> 32);
967 framerate_denominator = (UINT32)val;
968 if (SUCCEEDED(ret) && framerate_numerator && framerate_denominator) {
969 SDL_AddCameraFormat(add_data, sdlfmt, colorspace, (int) w, (int) h, (int)framerate_numerator, (int)framerate_denominator);
970 }
971 }
972 }
973 }
974 IMFMediaType_Release(mediatype);
975 }
976 }
977 IMFMediaTypeHandler_Release(handler);
978 }
979 }
980 IMFStreamDescriptor_Release(streamdesc);
981 }
982
983 IMFPresentationDescriptor_Release(presentdesc);
984}
985
986static bool FindMediaFoundationCameraBySymlink(SDL_Camera *device, void *userdata)
987{
988 return (SDL_strcmp((const char *) device->handle, (const char *) userdata) == 0);
989}
990
991static void MaybeAddDevice(IMFActivate *activation)
992{
993 char *symlink = QueryActivationObjectString(activation, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK);
994
995 if (SDL_FindPhysicalCameraByCallback(FindMediaFoundationCameraBySymlink, symlink)) {
996 SDL_free(symlink);
997 return; // already have this one.
998 }
999
1000 char *name = QueryActivationObjectString(activation, &SDL_MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME);
1001 if (name && symlink) {
1002 IMFMediaSource *source = NULL;
1003 // "activating" here only creates an object, it doesn't open the actual camera hardware or start recording.
1004 HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, (void**)&source);
1005 if (SUCCEEDED(ret) && source) {
1006 CameraFormatAddData add_data;
1007 GatherCameraSpecs(source, &add_data);
1008 if (add_data.num_specs > 0) {
1009 SDL_AddCamera(name, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, symlink);
1010 }
1011 SDL_free(add_data.specs);
1012 IMFActivate_ShutdownObject(activation);
1013 IMFMediaSource_Release(source);
1014 }
1015 }
1016
1017 SDL_free(name);
1018}
1019
1020static void MEDIAFOUNDATION_DetectDevices(void)
1021{
1022 // !!! FIXME: use CM_Register_Notification (Win8+) to get device notifications.
1023 // !!! FIXME: Earlier versions can use RegisterDeviceNotification, but I'm not bothering: no hotplug for you!
1024 HRESULT ret;
1025
1026 IMFAttributes *attrs = NULL;
1027 ret = pMFCreateAttributes(&attrs, 1);
1028 if (FAILED(ret)) {
1029 return; // oh well, no cameras for you.
1030 }
1031
1032 ret = IMFAttributes_SetGUID(attrs, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
1033 if (FAILED(ret)) {
1034 IMFAttributes_Release(attrs);
1035 return; // oh well, no cameras for you.
1036 }
1037
1038 IMFActivate **activations = NULL;
1039 UINT32 total = 0;
1040 ret = pMFEnumDeviceSources(attrs, &activations, &total);
1041 IMFAttributes_Release(attrs);
1042 if (FAILED(ret)) {
1043 return; // oh well, no cameras for you.
1044 }
1045
1046 for (UINT32 i = 0; i < total; i++) {
1047 MaybeAddDevice(activations[i]);
1048 IMFActivate_Release(activations[i]);
1049 }
1050
1051 CoTaskMemFree(activations);
1052}
1053
1054static void MEDIAFOUNDATION_Deinitialize(void)
1055{
1056 pMFShutdown();
1057
1058 FreeLibrary(libmfreadwrite);
1059 libmfreadwrite = NULL;
1060 FreeLibrary(libmfplat);
1061 libmfplat = NULL;
1062 FreeLibrary(libmf);
1063 libmf = NULL;
1064
1065 pMFEnumDeviceSources = NULL;
1066 pMFCreateDeviceSource = NULL;
1067 pMFStartup = NULL;
1068 pMFShutdown = NULL;
1069 pMFCreateAttributes = NULL;
1070 pMFCreateMediaType = NULL;
1071 pMFCreateSourceReaderFromMediaSource = NULL;
1072 pMFGetStrideForBitmapInfoHeader = NULL;
1073}
1074
1075static bool MEDIAFOUNDATION_Init(SDL_CameraDriverImpl *impl)
1076{
1077 // !!! FIXME: slide this off into a subroutine
1078 HMODULE mf = LoadLibrary(TEXT("Mf.dll")); // this library is available in Vista and later, but also can be on XP with service packs and Windows
1079 if (!mf) {
1080 return false;
1081 }
1082
1083 HMODULE mfplat = LoadLibrary(TEXT("Mfplat.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
1084 if (!mfplat) {
1085 FreeLibrary(mf);
1086 return false;
1087 }
1088
1089 HMODULE mfreadwrite = LoadLibrary(TEXT("Mfreadwrite.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
1090 if (!mfreadwrite) {
1091 FreeLibrary(mfplat);
1092 FreeLibrary(mf);
1093 return false;
1094 }
1095
1096 bool okay = true;
1097 #define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) GetProcAddress(lib, #fn); if (!p##fn) { okay = false; } }
1098 LOADSYM(mf, MFEnumDeviceSources);
1099 LOADSYM(mf, MFCreateDeviceSource);
1100 LOADSYM(mfplat, MFStartup);
1101 LOADSYM(mfplat, MFShutdown);
1102 LOADSYM(mfplat, MFCreateAttributes);
1103 LOADSYM(mfplat, MFCreateMediaType);
1104 LOADSYM(mfplat, MFGetStrideForBitmapInfoHeader);
1105 LOADSYM(mfreadwrite, MFCreateSourceReaderFromMediaSource);
1106 #undef LOADSYM
1107
1108 if (okay) {
1109 const HRESULT ret = pMFStartup(MF_VERSION, MFSTARTUP_LITE);
1110 if (FAILED(ret)) {
1111 okay = false;
1112 }
1113 }
1114
1115 if (!okay) {
1116 FreeLibrary(mfreadwrite);
1117 FreeLibrary(mfplat);
1118 FreeLibrary(mf);
1119 return false;
1120 }
1121
1122 libmf = mf;
1123 libmfplat = mfplat;
1124 libmfreadwrite = mfreadwrite;
1125
1126 impl->DetectDevices = MEDIAFOUNDATION_DetectDevices;
1127 impl->OpenDevice = MEDIAFOUNDATION_OpenDevice;
1128 impl->CloseDevice = MEDIAFOUNDATION_CloseDevice;
1129 impl->WaitDevice = MEDIAFOUNDATION_WaitDevice;
1130 impl->AcquireFrame = MEDIAFOUNDATION_AcquireFrame;
1131 impl->ReleaseFrame = MEDIAFOUNDATION_ReleaseFrame;
1132 impl->FreeDeviceHandle = MEDIAFOUNDATION_FreeDeviceHandle;
1133 impl->Deinitialize = MEDIAFOUNDATION_Deinitialize;
1134
1135 return true;
1136}
1137
1138CameraBootStrap MEDIAFOUNDATION_bootstrap = {
1139 "mediafoundation", "SDL Windows Media Foundation camera driver", MEDIAFOUNDATION_Init, false
1140};
1141
1142#endif // SDL_CAMERA_DRIVER_MEDIAFOUNDATION
1143
diff --git a/contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c b/contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c
new file mode 100644
index 0000000..5d868bf
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c
@@ -0,0 +1,1144 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4 Copyright (C) 2024 Wim Taymans <wtaymans@redhat.com>
5
6 This software is provided 'as-is', without any express or implied
7 warranty. In no event will the authors be held liable for any damages
8 arising from the use of this software.
9
10 Permission is granted to anyone to use this software for any purpose,
11 including commercial applications, and to alter it and redistribute it
12 freely, subject to the following restrictions:
13
14 1. The origin of this software must not be misrepresented; you must not
15 claim that you wrote the original software. If you use this software
16 in a product, an acknowledgment in the product documentation would be
17 appreciated but is not required.
18 2. Altered source versions must be plainly marked as such, and must not be
19 misrepresented as being the original software.
20 3. This notice may not be removed or altered from any source distribution.
21*/
22#include "SDL_internal.h"
23
24#ifdef SDL_CAMERA_DRIVER_PIPEWIRE
25
26#include "../SDL_syscamera.h"
27
28#include <spa/utils/type.h>
29#include <spa/pod/builder.h>
30#include <spa/pod/iter.h>
31#include <spa/param/video/raw.h>
32#include <spa/param/video/format.h>
33#include <spa/utils/result.h>
34#include <spa/utils/json.h>
35
36#include <pipewire/pipewire.h>
37#include <pipewire/extensions/metadata.h>
38
39#define PW_POD_BUFFER_LENGTH 1024
40#define PW_THREAD_NAME_BUFFER_LENGTH 128
41#define PW_MAX_IDENTIFIER_LENGTH 256
42
43#define PW_REQUIRED_MAJOR 1
44#define PW_REQUIRED_MINOR 0
45#define PW_REQUIRED_PATCH 0
46
47enum PW_READY_FLAGS
48{
49 PW_READY_FLAG_BUFFER_ADDED = 0x1,
50 PW_READY_FLAG_STREAM_READY = 0x2,
51 PW_READY_FLAG_ALL_BITS = 0x3
52};
53
54#define PW_ID_TO_HANDLE(x) (void *)((uintptr_t)x)
55#define PW_HANDLE_TO_ID(x) (uint32_t)((uintptr_t)x)
56
57static bool pipewire_initialized = false;
58
59// Pipewire entry points
60static const char *(*PIPEWIRE_pw_get_library_version)(void);
61#if PW_CHECK_VERSION(0, 3, 75)
62static bool (*PIPEWIRE_pw_check_library_version)(int major, int minor, int micro);
63#endif
64static void (*PIPEWIRE_pw_init)(int *, char ***);
65static void (*PIPEWIRE_pw_deinit)(void);
66static struct pw_main_loop *(*PIPEWIRE_pw_main_loop_new)(const struct spa_dict *loop);
67static struct pw_loop *(*PIPEWIRE_pw_main_loop_get_loop)(struct pw_main_loop *loop);
68static int (*PIPEWIRE_pw_main_loop_run)(struct pw_main_loop *loop);
69static int (*PIPEWIRE_pw_main_loop_quit)(struct pw_main_loop *loop);
70static void(*PIPEWIRE_pw_main_loop_destroy)(struct pw_main_loop *loop);
71static struct pw_thread_loop *(*PIPEWIRE_pw_thread_loop_new)(const char *, const struct spa_dict *);
72static void (*PIPEWIRE_pw_thread_loop_destroy)(struct pw_thread_loop *);
73static void (*PIPEWIRE_pw_thread_loop_stop)(struct pw_thread_loop *);
74static struct pw_loop *(*PIPEWIRE_pw_thread_loop_get_loop)(struct pw_thread_loop *);
75static void (*PIPEWIRE_pw_thread_loop_lock)(struct pw_thread_loop *);
76static void (*PIPEWIRE_pw_thread_loop_unlock)(struct pw_thread_loop *);
77static void (*PIPEWIRE_pw_thread_loop_signal)(struct pw_thread_loop *, bool);
78static void (*PIPEWIRE_pw_thread_loop_wait)(struct pw_thread_loop *);
79static int (*PIPEWIRE_pw_thread_loop_start)(struct pw_thread_loop *);
80static struct pw_context *(*PIPEWIRE_pw_context_new)(struct pw_loop *, struct pw_properties *, size_t);
81static void (*PIPEWIRE_pw_context_destroy)(struct pw_context *);
82static struct pw_core *(*PIPEWIRE_pw_context_connect)(struct pw_context *, struct pw_properties *, size_t);
83static void (*PIPEWIRE_pw_proxy_add_object_listener)(struct pw_proxy *, struct spa_hook *, const void *, void *);
84static void (*PIPEWIRE_pw_proxy_add_listener)(struct pw_proxy *, struct spa_hook *, const struct pw_proxy_events *, void *);
85static void *(*PIPEWIRE_pw_proxy_get_user_data)(struct pw_proxy *);
86static void (*PIPEWIRE_pw_proxy_destroy)(struct pw_proxy *);
87static int (*PIPEWIRE_pw_core_disconnect)(struct pw_core *);
88static struct pw_node_info * (*PIPEWIRE_pw_node_info_merge)(struct pw_node_info *info, const struct pw_node_info *update, bool reset);
89static void (*PIPEWIRE_pw_node_info_free)(struct pw_node_info *info);
90static struct pw_stream *(*PIPEWIRE_pw_stream_new)(struct pw_core *, const char *, struct pw_properties *);
91static void (*PIPEWIRE_pw_stream_add_listener)(struct pw_stream *stream, struct spa_hook *listener, const struct pw_stream_events *events, void *data);
92static void (*PIPEWIRE_pw_stream_destroy)(struct pw_stream *);
93static int (*PIPEWIRE_pw_stream_connect)(struct pw_stream *, enum pw_direction, uint32_t, enum pw_stream_flags,
94 const struct spa_pod **, uint32_t);
95static enum pw_stream_state (*PIPEWIRE_pw_stream_get_state)(struct pw_stream *stream, const char **error);
96static struct pw_buffer *(*PIPEWIRE_pw_stream_dequeue_buffer)(struct pw_stream *);
97static int (*PIPEWIRE_pw_stream_queue_buffer)(struct pw_stream *, struct pw_buffer *);
98static struct pw_properties *(*PIPEWIRE_pw_properties_new)(const char *, ...)SPA_SENTINEL;
99static struct pw_properties *(*PIPEWIRE_pw_properties_new_dict)(const struct spa_dict *dict);
100static int (*PIPEWIRE_pw_properties_set)(struct pw_properties *, const char *, const char *);
101static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, const char *, ...) SPA_PRINTF_FUNC(3, 4);
102
103#ifdef SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC
104
105static const char *pipewire_library = SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC;
106static SDL_SharedObject *pipewire_handle = NULL;
107
108static bool pipewire_dlsym(const char *fn, void **addr)
109{
110 *addr = SDL_LoadFunction(pipewire_handle, fn);
111 if (!*addr) {
112 // Don't call SDL_SetError(): SDL_LoadFunction already did.
113 return false;
114 }
115
116 return true;
117}
118
119#define SDL_PIPEWIRE_SYM(x) \
120 if (!pipewire_dlsym(#x, (void **)(char *)&PIPEWIRE_##x)) \
121 return false
122
123static bool load_pipewire_library(void)
124{
125 pipewire_handle = SDL_LoadObject(pipewire_library);
126 return pipewire_handle ? true : false;
127}
128
129static void unload_pipewire_library(void)
130{
131 if (pipewire_handle) {
132 SDL_UnloadObject(pipewire_handle);
133 pipewire_handle = NULL;
134 }
135}
136
137#else
138
139#define SDL_PIPEWIRE_SYM(x) PIPEWIRE_##x = x
140
141static bool load_pipewire_library(void)
142{
143 return true;
144}
145
146static void unload_pipewire_library(void)
147{
148 // Nothing to do
149}
150
151#endif // SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC
152
153static bool load_pipewire_syms(void)
154{
155 SDL_PIPEWIRE_SYM(pw_get_library_version);
156#if PW_CHECK_VERSION(0, 3, 75)
157 SDL_PIPEWIRE_SYM(pw_check_library_version);
158#endif
159 SDL_PIPEWIRE_SYM(pw_init);
160 SDL_PIPEWIRE_SYM(pw_deinit);
161 SDL_PIPEWIRE_SYM(pw_main_loop_new);
162 SDL_PIPEWIRE_SYM(pw_main_loop_get_loop);
163 SDL_PIPEWIRE_SYM(pw_main_loop_run);
164 SDL_PIPEWIRE_SYM(pw_main_loop_quit);
165 SDL_PIPEWIRE_SYM(pw_main_loop_destroy);
166 SDL_PIPEWIRE_SYM(pw_thread_loop_new);
167 SDL_PIPEWIRE_SYM(pw_thread_loop_destroy);
168 SDL_PIPEWIRE_SYM(pw_thread_loop_stop);
169 SDL_PIPEWIRE_SYM(pw_thread_loop_get_loop);
170 SDL_PIPEWIRE_SYM(pw_thread_loop_lock);
171 SDL_PIPEWIRE_SYM(pw_thread_loop_unlock);
172 SDL_PIPEWIRE_SYM(pw_thread_loop_signal);
173 SDL_PIPEWIRE_SYM(pw_thread_loop_wait);
174 SDL_PIPEWIRE_SYM(pw_thread_loop_start);
175 SDL_PIPEWIRE_SYM(pw_context_new);
176 SDL_PIPEWIRE_SYM(pw_context_destroy);
177 SDL_PIPEWIRE_SYM(pw_context_connect);
178 SDL_PIPEWIRE_SYM(pw_proxy_add_listener);
179 SDL_PIPEWIRE_SYM(pw_proxy_add_object_listener);
180 SDL_PIPEWIRE_SYM(pw_proxy_get_user_data);
181 SDL_PIPEWIRE_SYM(pw_proxy_destroy);
182 SDL_PIPEWIRE_SYM(pw_core_disconnect);
183 SDL_PIPEWIRE_SYM(pw_node_info_merge);
184 SDL_PIPEWIRE_SYM(pw_node_info_free);
185 SDL_PIPEWIRE_SYM(pw_stream_new);
186 SDL_PIPEWIRE_SYM(pw_stream_add_listener);
187 SDL_PIPEWIRE_SYM(pw_stream_destroy);
188 SDL_PIPEWIRE_SYM(pw_stream_connect);
189 SDL_PIPEWIRE_SYM(pw_stream_get_state);
190 SDL_PIPEWIRE_SYM(pw_stream_dequeue_buffer);
191 SDL_PIPEWIRE_SYM(pw_stream_queue_buffer);
192 SDL_PIPEWIRE_SYM(pw_properties_new);
193 SDL_PIPEWIRE_SYM(pw_properties_new_dict);
194 SDL_PIPEWIRE_SYM(pw_properties_set);
195 SDL_PIPEWIRE_SYM(pw_properties_setf);
196
197 return true;
198}
199
200static bool init_pipewire_library(void)
201{
202 if (load_pipewire_library()) {
203 if (load_pipewire_syms()) {
204 PIPEWIRE_pw_init(NULL, NULL);
205 return true;
206 }
207 }
208 return false;
209}
210
211static void deinit_pipewire_library(void)
212{
213 PIPEWIRE_pw_deinit();
214 unload_pipewire_library();
215}
216
217// The global hotplug thread and associated objects.
218static struct
219{
220 struct pw_thread_loop *loop;
221
222 struct pw_context *context;
223
224 struct pw_core *core;
225 struct spa_hook core_listener;
226 int server_major;
227 int server_minor;
228 int server_patch;
229 int last_seq;
230 int pending_seq;
231
232 struct pw_registry *registry;
233 struct spa_hook registry_listener;
234
235 struct spa_list global_list;
236
237 bool have_1_0_5;
238 bool init_complete;
239 bool events_enabled;
240} hotplug;
241
242struct global
243{
244 struct spa_list link;
245
246 const struct global_class *class;
247
248 uint32_t id;
249 uint32_t permissions;
250 struct pw_properties *props;
251
252 char *name;
253
254 struct pw_proxy *proxy;
255 struct spa_hook proxy_listener;
256 struct spa_hook object_listener;
257
258 int changed;
259 void *info;
260 struct spa_list pending_list;
261 struct spa_list param_list;
262
263 bool added;
264};
265
266struct global_class
267{
268 const char *type;
269 uint32_t version;
270 const void *events;
271 int (*init) (struct global *g);
272 void (*destroy) (struct global *g);
273};
274
275struct param {
276 uint32_t id;
277 int32_t seq;
278 struct spa_list link;
279 struct spa_pod *param;
280};
281
282static uint32_t param_clear(struct spa_list *param_list, uint32_t id)
283{
284 struct param *p, *t;
285 uint32_t count = 0;
286
287 spa_list_for_each_safe(p, t, param_list, link) {
288 if (id == SPA_ID_INVALID || p->id == id) {
289 spa_list_remove(&p->link);
290 free(p); // This should NOT be SDL_free()
291 count++;
292 }
293 }
294 return count;
295}
296
297#if PW_CHECK_VERSION(0,3,60)
298#define SPA_PARAMS_INFO_SEQ(p) ((p).seq)
299#else
300#define SPA_PARAMS_INFO_SEQ(p) ((p).padding[0])
301#endif
302
303static struct param *param_add(struct spa_list *params,
304 int seq, uint32_t id, const struct spa_pod *param)
305{
306 struct param *p;
307
308 if (id == SPA_ID_INVALID) {
309 if (param == NULL || !spa_pod_is_object(param)) {
310 errno = EINVAL;
311 return NULL;
312 }
313 id = SPA_POD_OBJECT_ID(param);
314 }
315
316 p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0));
317 if (p == NULL)
318 return NULL;
319
320 p->id = id;
321 p->seq = seq;
322 if (param != NULL) {
323 p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod);
324 SDL_memcpy(p->param, param, SPA_POD_SIZE(param));
325 } else {
326 param_clear(params, id);
327 p->param = NULL;
328 }
329 spa_list_append(params, &p->link);
330
331 return p;
332}
333
334static void param_update(struct spa_list *param_list, struct spa_list *pending_list,
335 uint32_t n_params, struct spa_param_info *params)
336{
337 struct param *p, *t;
338 uint32_t i;
339
340 for (i = 0; i < n_params; i++) {
341 spa_list_for_each_safe(p, t, pending_list, link) {
342 if (p->id == params[i].id &&
343 p->seq != SPA_PARAMS_INFO_SEQ(params[i]) &&
344 p->param != NULL) {
345 spa_list_remove(&p->link);
346 free(p); // This should NOT be SDL_free()
347 }
348 }
349 }
350 spa_list_consume(p, pending_list, link) {
351 spa_list_remove(&p->link);
352 if (p->param == NULL) {
353 param_clear(param_list, p->id);
354 free(p); // This should NOT be SDL_free()
355 } else {
356 spa_list_append(param_list, &p->link);
357 }
358 }
359}
360
361static struct sdl_video_format {
362 SDL_PixelFormat format;
363 SDL_Colorspace colorspace;
364 uint32_t id;
365} sdl_video_formats[] = {
366 { SDL_PIXELFORMAT_RGBX32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_RGBx },
367 { SDL_PIXELFORMAT_XRGB32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_xRGB },
368 { SDL_PIXELFORMAT_BGRX32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_BGRx },
369 { SDL_PIXELFORMAT_XBGR32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_xBGR },
370 { SDL_PIXELFORMAT_RGBA32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_RGBA },
371 { SDL_PIXELFORMAT_ARGB32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_ARGB },
372 { SDL_PIXELFORMAT_BGRA32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_BGRA },
373 { SDL_PIXELFORMAT_ABGR32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_ABGR },
374 { SDL_PIXELFORMAT_RGB24, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_RGB },
375 { SDL_PIXELFORMAT_BGR24, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_BGR },
376 { SDL_PIXELFORMAT_YV12, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YV12 },
377 { SDL_PIXELFORMAT_IYUV, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_I420 },
378 { SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YUY2 },
379 { SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_UYVY },
380 { SDL_PIXELFORMAT_YVYU, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YVYU },
381 { SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV12 },
382 { SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV21 }
383};
384
385static uint32_t sdl_format_to_id(SDL_PixelFormat format)
386{
387 struct sdl_video_format *f;
388 SPA_FOR_EACH_ELEMENT(sdl_video_formats, f) {
389 if (f->format == format)
390 return f->id;
391 }
392 return SPA_VIDEO_FORMAT_UNKNOWN;
393}
394
395static void id_to_sdl_format(uint32_t id, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
396{
397 struct sdl_video_format *f;
398 SPA_FOR_EACH_ELEMENT(sdl_video_formats, f) {
399 if (f->id == id) {
400 *format = f->format;
401 *colorspace = f->colorspace;
402 return;
403 }
404 }
405 *format = SDL_PIXELFORMAT_UNKNOWN;
406 *colorspace = SDL_COLORSPACE_UNKNOWN;
407}
408
409struct SDL_PrivateCameraData
410{
411 struct pw_stream *stream;
412 struct spa_hook stream_listener;
413
414 struct pw_array buffers;
415};
416
417static void on_process(void *data)
418{
419 PIPEWIRE_pw_thread_loop_signal(hotplug.loop, false);
420}
421
422static void on_stream_state_changed(void *data, enum pw_stream_state old,
423 enum pw_stream_state state, const char *error)
424{
425 SDL_Camera *device = data;
426 switch (state) {
427 case PW_STREAM_STATE_UNCONNECTED:
428 break;
429 case PW_STREAM_STATE_STREAMING:
430 SDL_CameraPermissionOutcome(device, true);
431 break;
432 default:
433 break;
434 }
435}
436
437static void on_stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
438{
439}
440
441static void on_add_buffer(void *data, struct pw_buffer *buffer)
442{
443 SDL_Camera *device = data;
444 pw_array_add_ptr(&device->hidden->buffers, buffer);
445}
446
447static void on_remove_buffer(void *data, struct pw_buffer *buffer)
448{
449 SDL_Camera *device = data;
450 struct pw_buffer **p;
451 pw_array_for_each(p, &device->hidden->buffers) {
452 if (*p == buffer) {
453 pw_array_remove(&device->hidden->buffers, p);
454 return;
455 }
456 }
457}
458
459static const struct pw_stream_events stream_events = {
460 .version = PW_VERSION_STREAM_EVENTS,
461 .add_buffer = on_add_buffer,
462 .remove_buffer = on_remove_buffer,
463 .state_changed = on_stream_state_changed,
464 .param_changed = on_stream_param_changed,
465 .process = on_process,
466};
467
468static bool PIPEWIRECAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
469{
470 struct pw_properties *props;
471 const struct spa_pod *params[3];
472 int res, n_params = 0;
473 uint8_t buffer[1024];
474 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
475
476 if (!device) {
477 return false;
478 }
479 device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
480 if (device->hidden == NULL) {
481 return false;
482 }
483 pw_array_init(&device->hidden->buffers, 64);
484
485 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
486
487 props = PIPEWIRE_pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
488 PW_KEY_MEDIA_CATEGORY, "Capture",
489 PW_KEY_MEDIA_ROLE, "Camera",
490 PW_KEY_TARGET_OBJECT, device->name,
491 NULL);
492 if (props == NULL) {
493 return false;
494 }
495
496 device->hidden->stream = PIPEWIRE_pw_stream_new(hotplug.core, "SDL PipeWire Camera", props);
497 if (device->hidden->stream == NULL) {
498 return false;
499 }
500
501 PIPEWIRE_pw_stream_add_listener(device->hidden->stream,
502 &device->hidden->stream_listener,
503 &stream_events, device);
504
505 if (spec->format == SDL_PIXELFORMAT_MJPG) {
506 params[n_params++] = spa_pod_builder_add_object(&b,
507 SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
508 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
509 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg),
510 SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)),
511 SPA_FORMAT_VIDEO_framerate,
512 SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator)));
513 } else {
514 params[n_params++] = spa_pod_builder_add_object(&b,
515 SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
516 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
517 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
518 SPA_FORMAT_VIDEO_format, SPA_POD_Id(sdl_format_to_id(spec->format)),
519 SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)),
520 SPA_FORMAT_VIDEO_framerate,
521 SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator)));
522 }
523
524 if ((res = PIPEWIRE_pw_stream_connect(device->hidden->stream,
525 PW_DIRECTION_INPUT,
526 PW_ID_ANY,
527 PW_STREAM_FLAG_AUTOCONNECT |
528 PW_STREAM_FLAG_MAP_BUFFERS,
529 params, n_params)) < 0) {
530 return false;
531 }
532
533 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
534
535 return true;
536}
537
538static void PIPEWIRECAMERA_CloseDevice(SDL_Camera *device)
539{
540 if (!device) {
541 return;
542 }
543
544 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
545 if (device->hidden) {
546 if (device->hidden->stream)
547 PIPEWIRE_pw_stream_destroy(device->hidden->stream);
548 pw_array_clear(&device->hidden->buffers);
549 SDL_free(device->hidden);
550 device->hidden = NULL;
551 }
552 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
553}
554
555static bool PIPEWIRECAMERA_WaitDevice(SDL_Camera *device)
556{
557 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
558 PIPEWIRE_pw_thread_loop_wait(hotplug.loop);
559 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
560 return true;
561}
562
563static SDL_CameraFrameResult PIPEWIRECAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
564{
565 struct pw_buffer *b;
566
567 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
568 b = NULL;
569 while (true) {
570 struct pw_buffer *t;
571 if ((t = PIPEWIRE_pw_stream_dequeue_buffer(device->hidden->stream)) == NULL)
572 break;
573 if (b)
574 PIPEWIRE_pw_stream_queue_buffer(device->hidden->stream, b);
575 b = t;
576 }
577 if (b == NULL) {
578 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
579 return SDL_CAMERA_FRAME_SKIP;
580 }
581
582#if PW_CHECK_VERSION(1,0,5)
583 *timestampNS = hotplug.have_1_0_5 ? b->time : SDL_GetTicksNS();
584#else
585 *timestampNS = SDL_GetTicksNS();
586#endif
587 frame->pixels = b->buffer->datas[0].data;
588 if (frame->format == SDL_PIXELFORMAT_MJPG) {
589 frame->pitch = b->buffer->datas[0].chunk->size;
590 } else {
591 frame->pitch = b->buffer->datas[0].chunk->stride;
592 }
593
594 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
595
596 return SDL_CAMERA_FRAME_READY;
597}
598
599static void PIPEWIRECAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
600{
601 struct pw_buffer **p;
602 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
603 pw_array_for_each(p, &device->hidden->buffers) {
604 if ((*p)->buffer->datas[0].data == frame->pixels) {
605 PIPEWIRE_pw_stream_queue_buffer(device->hidden->stream, (*p));
606 break;
607 }
608 }
609 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
610}
611
612static void collect_rates(CameraFormatAddData *data, struct param *p, SDL_PixelFormat sdlfmt, SDL_Colorspace colorspace, const struct spa_rectangle *size)
613{
614 const struct spa_pod_prop *prop;
615 struct spa_pod * values;
616 uint32_t i, n_vals, choice;
617 struct spa_fraction *rates;
618
619 prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_VIDEO_framerate);
620 if (prop == NULL)
621 return;
622
623 values = spa_pod_get_values(&prop->value, &n_vals, &choice);
624 if (values->type != SPA_TYPE_Fraction || n_vals == 0)
625 return;
626
627 rates = SPA_POD_BODY(values);
628 switch (choice) {
629 case SPA_CHOICE_None:
630 n_vals = 1;
631 SDL_FALLTHROUGH;
632 case SPA_CHOICE_Enum:
633 for (i = 0; i < n_vals; i++) {
634 if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, size->width, size->height, rates[i].num, rates[i].denom)) {
635 return; // Probably out of memory; we'll go with what we have, if anything.
636 }
637 }
638 break;
639 default:
640 SDL_Log("CAMERA: unimplemented choice:%d", choice);
641 break;
642 }
643}
644
645static void collect_size(CameraFormatAddData *data, struct param *p, SDL_PixelFormat sdlfmt, SDL_Colorspace colorspace)
646{
647 const struct spa_pod_prop *prop;
648 struct spa_pod * values;
649 uint32_t i, n_vals, choice;
650 struct spa_rectangle *rectangles;
651
652 prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_VIDEO_size);
653 if (prop == NULL)
654 return;
655
656 values = spa_pod_get_values(&prop->value, &n_vals, &choice);
657 if (values->type != SPA_TYPE_Rectangle || n_vals == 0)
658 return;
659
660 rectangles = SPA_POD_BODY(values);
661 switch (choice) {
662 case SPA_CHOICE_None:
663 n_vals = 1;
664 SDL_FALLTHROUGH;
665 case SPA_CHOICE_Enum:
666 for (i = 0; i < n_vals; i++) {
667 collect_rates(data, p, sdlfmt, colorspace, &rectangles[i]);
668 }
669 break;
670 default:
671 SDL_Log("CAMERA: unimplemented choice:%d", choice);
672 break;
673 }
674}
675
676static void collect_raw(CameraFormatAddData *data, struct param *p)
677{
678 const struct spa_pod_prop *prop;
679 SDL_PixelFormat sdlfmt;
680 SDL_Colorspace colorspace;
681 struct spa_pod * values;
682 uint32_t i, n_vals, choice, *ids;
683
684 prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_VIDEO_format);
685 if (prop == NULL)
686 return;
687
688 values = spa_pod_get_values(&prop->value, &n_vals, &choice);
689 if (values->type != SPA_TYPE_Id || n_vals == 0)
690 return;
691
692 ids = SPA_POD_BODY(values);
693 switch (choice) {
694 case SPA_CHOICE_None:
695 n_vals = 1;
696 SDL_FALLTHROUGH;
697 case SPA_CHOICE_Enum:
698 for (i = 0; i < n_vals; i++) {
699 id_to_sdl_format(ids[i], &sdlfmt, &colorspace);
700 if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
701 continue;
702 }
703 collect_size(data, p, sdlfmt, colorspace);
704 }
705 break;
706 default:
707 SDL_Log("CAMERA: unimplemented choice: %d", choice);
708 break;
709 }
710}
711
712static void collect_format(CameraFormatAddData *data, struct param *p)
713{
714 const struct spa_pod_prop *prop;
715 struct spa_pod * values;
716 uint32_t i, n_vals, choice, *ids;
717
718 prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_mediaSubtype);
719 if (prop == NULL)
720 return;
721
722 values = spa_pod_get_values(&prop->value, &n_vals, &choice);
723 if (values->type != SPA_TYPE_Id || n_vals == 0)
724 return;
725
726 ids = SPA_POD_BODY(values);
727 switch (choice) {
728 case SPA_CHOICE_None:
729 n_vals = 1;
730 SDL_FALLTHROUGH;
731 case SPA_CHOICE_Enum:
732 for (i = 0; i < n_vals; i++) {
733 switch (ids[i]) {
734 case SPA_MEDIA_SUBTYPE_raw:
735 collect_raw(data, p);
736 break;
737 case SPA_MEDIA_SUBTYPE_mjpg:
738 collect_size(data, p, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_JPEG);
739 break;
740 default:
741 // Unsupported format
742 break;
743 }
744 }
745 break;
746 default:
747 SDL_Log("CAMERA: unimplemented choice: %d", choice);
748 break;
749 }
750}
751
752static void add_device(struct global *g)
753{
754 struct param *p;
755 CameraFormatAddData data;
756
757 SDL_zero(data);
758
759 spa_list_for_each(p, &g->param_list, link) {
760 if (p->id != SPA_PARAM_EnumFormat)
761 continue;
762
763 collect_format(&data, p);
764 }
765 if (data.num_specs > 0) {
766 SDL_AddCamera(g->name, SDL_CAMERA_POSITION_UNKNOWN,
767 data.num_specs, data.specs, g);
768 }
769 SDL_free(data.specs);
770
771 g->added = true;
772}
773
774static void PIPEWIRECAMERA_DetectDevices(void)
775{
776 struct global *g;
777
778 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
779
780 // Wait until the initial registry enumeration is complete
781 while (!hotplug.init_complete) {
782 PIPEWIRE_pw_thread_loop_wait(hotplug.loop);
783 }
784
785 spa_list_for_each (g, &hotplug.global_list, link) {
786 if (!g->added) {
787 add_device(g);
788 }
789 }
790
791 hotplug.events_enabled = true;
792
793 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
794}
795
796static void PIPEWIRECAMERA_FreeDeviceHandle(SDL_Camera *device)
797{
798}
799
800static void do_resync(void)
801{
802 hotplug.pending_seq = pw_core_sync(hotplug.core, PW_ID_CORE, 0);
803}
804
805/** node */
806static void node_event_info(void *object, const struct pw_node_info *info)
807{
808 struct global *g = object;
809 uint32_t i;
810
811 info = g->info = PIPEWIRE_pw_node_info_merge(g->info, info, g->changed == 0);
812 if (info == NULL)
813 return;
814
815 if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
816 for (i = 0; i < info->n_params; i++) {
817 uint32_t id = info->params[i].id;
818 int res;
819
820 if (info->params[i].user == 0)
821 continue;
822 info->params[i].user = 0;
823
824 if (id != SPA_PARAM_EnumFormat)
825 continue;
826
827 param_add(&g->pending_list, SPA_PARAMS_INFO_SEQ(info->params[i]), id, NULL);
828 if (!(info->params[i].flags & SPA_PARAM_INFO_READ))
829 continue;
830
831 res = pw_node_enum_params((struct pw_node*)g->proxy,
832 ++SPA_PARAMS_INFO_SEQ(info->params[i]), id, 0, -1, NULL);
833 if (SPA_RESULT_IS_ASYNC(res))
834 SPA_PARAMS_INFO_SEQ(info->params[i]) = res;
835
836 g->changed++;
837 }
838 }
839 do_resync();
840}
841
842static void node_event_param(void *object, int seq,
843 uint32_t id, uint32_t index, uint32_t next,
844 const struct spa_pod *param)
845{
846 struct global *g = object;
847 param_add(&g->pending_list, seq, id, param);
848}
849
850static const struct pw_node_events node_events = {
851 .version = PW_VERSION_NODE_EVENTS,
852 .info = node_event_info,
853 .param = node_event_param,
854};
855
856static void node_destroy(struct global *g)
857{
858 if (g->info) {
859 PIPEWIRE_pw_node_info_free(g->info);
860 g->info = NULL;
861 }
862}
863
864
865static const struct global_class node_class = {
866 .type = PW_TYPE_INTERFACE_Node,
867 .version = PW_VERSION_NODE,
868 .events = &node_events,
869 .destroy = node_destroy,
870};
871
872/** proxy */
873static void proxy_removed(void *data)
874{
875 struct global *g = data;
876 PIPEWIRE_pw_proxy_destroy(g->proxy);
877}
878
879static void proxy_destroy(void *data)
880{
881 struct global *g = data;
882 spa_list_remove(&g->link);
883 g->proxy = NULL;
884 if (g->class) {
885 if (g->class->events)
886 spa_hook_remove(&g->object_listener);
887 if (g->class->destroy)
888 g->class->destroy(g);
889 }
890 param_clear(&g->param_list, SPA_ID_INVALID);
891 param_clear(&g->pending_list, SPA_ID_INVALID);
892 free(g->name); // This should NOT be SDL_free()
893}
894
895static const struct pw_proxy_events proxy_events = {
896 .version = PW_VERSION_PROXY_EVENTS,
897 .removed = proxy_removed,
898 .destroy = proxy_destroy
899};
900
901// called with thread_loop lock
902static void hotplug_registry_global_callback(void *object, uint32_t id,
903 uint32_t permissions, const char *type, uint32_t version,
904 const struct spa_dict *props)
905{
906 const struct global_class *class = NULL;
907 struct pw_proxy *proxy;
908 const char *str, *name = NULL;
909
910 if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
911 if (props == NULL)
912 return;
913 if (((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) == NULL) ||
914 (!spa_streq(str, "Video/Source")))
915 return;
916
917 if ((name = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION)) == NULL &&
918 (name = spa_dict_lookup(props, PW_KEY_NODE_NAME)) == NULL)
919 name = "unnamed camera";
920
921 class = &node_class;
922 }
923 if (class) {
924 struct global *g;
925
926 proxy = pw_registry_bind(hotplug.registry,
927 id, class->type, class->version,
928 sizeof(struct global));
929
930 g = PIPEWIRE_pw_proxy_get_user_data(proxy);
931 g->class = class;
932 g->id = id;
933 g->permissions = permissions;
934 g->props = props ? PIPEWIRE_pw_properties_new_dict(props) : NULL;
935 g->proxy = proxy;
936 g->name = strdup(name);
937 spa_list_init(&g->pending_list);
938 spa_list_init(&g->param_list);
939 spa_list_append(&hotplug.global_list, &g->link);
940
941 PIPEWIRE_pw_proxy_add_listener(proxy,
942 &g->proxy_listener,
943 &proxy_events, g);
944
945 if (class->events) {
946 PIPEWIRE_pw_proxy_add_object_listener(proxy,
947 &g->object_listener,
948 class->events, g);
949 }
950 if (class->init)
951 class->init(g);
952
953 do_resync();
954 }
955}
956
957// called with thread_loop lock
958static void hotplug_registry_global_remove_callback(void *object, uint32_t id)
959{
960}
961
962static const struct pw_registry_events hotplug_registry_events =
963{
964 .version = PW_VERSION_REGISTRY_EVENTS,
965 .global = hotplug_registry_global_callback,
966 .global_remove = hotplug_registry_global_remove_callback
967};
968
969static void parse_version(const char *str, int *major, int *minor, int *patch)
970{
971 if (SDL_sscanf(str, "%d.%d.%d", major, minor, patch) < 3) {
972 *major = 0;
973 *minor = 0;
974 *patch = 0;
975 }
976}
977
978// Core info, called with thread_loop lock
979static void hotplug_core_info_callback(void *data, const struct pw_core_info *info)
980{
981 parse_version(info->version, &hotplug.server_major, &hotplug.server_minor, &hotplug.server_patch);
982}
983
984// Core sync points, called with thread_loop lock
985static void hotplug_core_done_callback(void *object, uint32_t id, int seq)
986{
987 hotplug.last_seq = seq;
988 if (id == PW_ID_CORE && seq == hotplug.pending_seq) {
989 struct global *g;
990 struct pw_node_info *info;
991
992 spa_list_for_each(g, &hotplug.global_list, link) {
993 if (!g->changed)
994 continue;
995
996 info = g->info;
997 param_update(&g->param_list, &g->pending_list, info->n_params, info->params);
998
999 if (!g->added && hotplug.events_enabled) {
1000 add_device(g);
1001 }
1002 }
1003 hotplug.init_complete = true;
1004 PIPEWIRE_pw_thread_loop_signal(hotplug.loop, false);
1005 }
1006}
1007static const struct pw_core_events hotplug_core_events =
1008{
1009 .version = PW_VERSION_CORE_EVENTS,
1010 .info = hotplug_core_info_callback,
1011 .done = hotplug_core_done_callback
1012};
1013
1014/* When in a container, the library version can differ from the underlying core version,
1015 * so make sure the underlying Pipewire implementation meets the version requirement.
1016 */
1017static bool pipewire_server_version_at_least(int major, int minor, int patch)
1018{
1019 return (hotplug.server_major >= major) &&
1020 (hotplug.server_major > major || hotplug.server_minor >= minor) &&
1021 (hotplug.server_major > major || hotplug.server_minor > minor || hotplug.server_patch >= patch);
1022}
1023
1024// The hotplug thread
1025static bool hotplug_loop_init(void)
1026{
1027 int res;
1028
1029 spa_list_init(&hotplug.global_list);
1030
1031#if PW_CHECK_VERSION(0, 3, 75)
1032 hotplug.have_1_0_5 = PIPEWIRE_pw_check_library_version(1,0,5);
1033#else
1034 hotplug.have_1_0_5 = false;
1035#endif
1036
1037 hotplug.loop = PIPEWIRE_pw_thread_loop_new("SDLPwCameraPlug", NULL);
1038 if (!hotplug.loop) {
1039 return SDL_SetError("Pipewire: Failed to create hotplug detection loop (%i)", errno);
1040 }
1041
1042 hotplug.context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(hotplug.loop), NULL, 0);
1043 if (!hotplug.context) {
1044 return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno);
1045 }
1046
1047 hotplug.core = PIPEWIRE_pw_context_connect(hotplug.context, NULL, 0);
1048 if (!hotplug.core) {
1049 return SDL_SetError("Pipewire: Failed to connect hotplug detection context (%i)", errno);
1050 }
1051 spa_zero(hotplug.core_listener);
1052 pw_core_add_listener(hotplug.core, &hotplug.core_listener, &hotplug_core_events, NULL);
1053
1054 hotplug.registry = pw_core_get_registry(hotplug.core, PW_VERSION_REGISTRY, 0);
1055 if (!hotplug.registry) {
1056 return SDL_SetError("Pipewire: Failed to acquire hotplug detection registry (%i)", errno);
1057 }
1058
1059 spa_zero(hotplug.registry_listener);
1060 pw_registry_add_listener(hotplug.registry, &hotplug.registry_listener, &hotplug_registry_events, NULL);
1061
1062 do_resync();
1063
1064 res = PIPEWIRE_pw_thread_loop_start(hotplug.loop);
1065 if (res != 0) {
1066 return SDL_SetError("Pipewire: Failed to start hotplug detection loop");
1067 }
1068
1069 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
1070 while (!hotplug.init_complete) {
1071 PIPEWIRE_pw_thread_loop_wait(hotplug.loop);
1072 }
1073 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
1074
1075 if (!pipewire_server_version_at_least(PW_REQUIRED_MAJOR, PW_REQUIRED_MINOR, PW_REQUIRED_PATCH)) {
1076 return SDL_SetError("Pipewire: server version is too old %d.%d.%d < %d.%d.%d",
1077 hotplug.server_major, hotplug.server_minor, hotplug.server_patch,
1078 PW_REQUIRED_MAJOR, PW_REQUIRED_MINOR, PW_REQUIRED_PATCH);
1079 }
1080
1081 return true;
1082}
1083
1084
1085static void PIPEWIRECAMERA_Deinitialize(void)
1086{
1087 if (pipewire_initialized) {
1088 if (hotplug.loop) {
1089 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
1090 }
1091 if (hotplug.registry) {
1092 spa_hook_remove(&hotplug.registry_listener);
1093 PIPEWIRE_pw_proxy_destroy((struct pw_proxy *)hotplug.registry);
1094 }
1095 if (hotplug.core) {
1096 spa_hook_remove(&hotplug.core_listener);
1097 PIPEWIRE_pw_core_disconnect(hotplug.core);
1098 }
1099 if (hotplug.context) {
1100 PIPEWIRE_pw_context_destroy(hotplug.context);
1101 }
1102 if (hotplug.loop) {
1103 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
1104 PIPEWIRE_pw_thread_loop_destroy(hotplug.loop);
1105 }
1106 deinit_pipewire_library();
1107 spa_zero(hotplug);
1108 pipewire_initialized = false;
1109 }
1110}
1111
1112static bool PIPEWIRECAMERA_Init(SDL_CameraDriverImpl *impl)
1113{
1114 if (!pipewire_initialized) {
1115
1116 if (!init_pipewire_library()) {
1117 return false;
1118 }
1119
1120 pipewire_initialized = true;
1121
1122 if (!hotplug_loop_init()) {
1123 PIPEWIRECAMERA_Deinitialize();
1124 return false;
1125 }
1126 }
1127
1128 impl->DetectDevices = PIPEWIRECAMERA_DetectDevices;
1129 impl->OpenDevice = PIPEWIRECAMERA_OpenDevice;
1130 impl->CloseDevice = PIPEWIRECAMERA_CloseDevice;
1131 impl->WaitDevice = PIPEWIRECAMERA_WaitDevice;
1132 impl->AcquireFrame = PIPEWIRECAMERA_AcquireFrame;
1133 impl->ReleaseFrame = PIPEWIRECAMERA_ReleaseFrame;
1134 impl->FreeDeviceHandle = PIPEWIRECAMERA_FreeDeviceHandle;
1135 impl->Deinitialize = PIPEWIRECAMERA_Deinitialize;
1136
1137 return true;
1138}
1139
1140CameraBootStrap PIPEWIRECAMERA_bootstrap = {
1141 "pipewire", "SDL PipeWire camera driver", PIPEWIRECAMERA_Init, false
1142};
1143
1144#endif // SDL_CAMERA_DRIVER_PIPEWIRE
diff --git a/contrib/SDL-3.2.8/src/camera/v4l2/SDL_camera_v4l2.c b/contrib/SDL-3.2.8/src/camera/v4l2/SDL_camera_v4l2.c
new file mode 100644
index 0000000..9cdb54b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/v4l2/SDL_camera_v4l2.c
@@ -0,0 +1,929 @@
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#ifdef SDL_CAMERA_DRIVER_V4L2
24
25#include <dirent.h>
26#include <errno.h>
27#include <fcntl.h> // low-level i/o
28#include <stddef.h>
29#include <sys/ioctl.h>
30#include <sys/mman.h>
31#include <sys/stat.h>
32#include <unistd.h>
33#include <linux/videodev2.h>
34
35#ifndef V4L2_CAP_DEVICE_CAPS
36// device_caps was added to struct v4l2_capability as of kernel 3.4.
37#define device_caps reserved[0]
38SDL_COMPILE_TIME_ASSERT(v4l2devicecaps, offsetof(struct v4l2_capability,device_caps) == offsetof(struct v4l2_capability,capabilities) + 4);
39#endif
40
41#include "../SDL_syscamera.h"
42#include "../SDL_camera_c.h"
43#include "../../video/SDL_pixels_c.h"
44#include "../../video/SDL_surface_c.h"
45#include "../../thread/SDL_systhread.h"
46#include "../../core/linux/SDL_evdev_capabilities.h"
47#include "../../core/linux/SDL_udev.h"
48
49#ifndef SDL_USE_LIBUDEV
50#include <dirent.h>
51#endif
52
53typedef struct V4L2DeviceHandle
54{
55 char *bus_info;
56 char *path;
57} V4L2DeviceHandle;
58
59
60typedef enum io_method {
61 IO_METHOD_INVALID,
62 IO_METHOD_READ,
63 IO_METHOD_MMAP,
64 IO_METHOD_USERPTR
65} io_method;
66
67struct buffer {
68 void *start;
69 size_t length;
70 int available; // Is available in userspace
71};
72
73struct SDL_PrivateCameraData
74{
75 int fd;
76 io_method io;
77 int nb_buffers;
78 struct buffer *buffers;
79 int driver_pitch;
80};
81
82static int xioctl(int fh, int request, void *arg)
83{
84 int r;
85
86 do {
87 r = ioctl(fh, request, arg);
88 } while ((r == -1) && (errno == EINTR));
89
90 return r;
91}
92
93static bool V4L2_WaitDevice(SDL_Camera *device)
94{
95 const int fd = device->hidden->fd;
96
97 int rc;
98
99 do {
100 fd_set fds;
101 FD_ZERO(&fds);
102 FD_SET(fd, &fds);
103
104 struct timeval tv;
105 tv.tv_sec = 0;
106 tv.tv_usec = 100 * 1000;
107
108 rc = select(fd + 1, &fds, NULL, NULL, &tv);
109 if ((rc == -1) && (errno == EINTR)) {
110 rc = 0; // pretend it was a timeout, keep looping.
111 } else if (rc > 0) {
112 return true;
113 }
114
115 // Thread is requested to shut down
116 if (SDL_GetAtomicInt(&device->shutdown)) {
117 return true;
118 }
119
120 } while (rc == 0);
121
122 return false;
123}
124
125static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
126{
127 const int fd = device->hidden->fd;
128 const io_method io = device->hidden->io;
129 size_t size = device->hidden->buffers[0].length;
130 struct v4l2_buffer buf;
131 ssize_t amount;
132
133 switch (io) {
134 case IO_METHOD_READ:
135 if ((amount = read(fd, device->hidden->buffers[0].start, size)) == -1) {
136 switch (errno) {
137 case EAGAIN:
138 return SDL_CAMERA_FRAME_SKIP;
139
140 case EIO:
141 // Could ignore EIO, see spec.
142 // fall through
143
144 default:
145 SDL_SetError("read");
146 return SDL_CAMERA_FRAME_ERROR;
147 }
148 }
149
150 *timestampNS = SDL_GetTicksNS(); // oh well, close enough.
151 frame->pixels = device->hidden->buffers[0].start;
152 if (device->hidden->driver_pitch) {
153 frame->pitch = device->hidden->driver_pitch;
154 } else {
155 frame->pitch = (int)amount;
156 }
157 break;
158
159 case IO_METHOD_MMAP:
160 SDL_zero(buf);
161
162 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
163 buf.memory = V4L2_MEMORY_MMAP;
164
165 if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
166 switch (errno) {
167 case EAGAIN:
168 return SDL_CAMERA_FRAME_SKIP;
169
170 case EIO:
171 // Could ignore EIO, see spec.
172 // fall through
173
174 default:
175 SDL_SetError("VIDIOC_DQBUF: %d", errno);
176 return SDL_CAMERA_FRAME_ERROR;
177 }
178 }
179
180 if ((int)buf.index < 0 || (int)buf.index >= device->hidden->nb_buffers) {
181 SDL_SetError("invalid buffer index");
182 return SDL_CAMERA_FRAME_ERROR;
183 }
184
185 frame->pixels = device->hidden->buffers[buf.index].start;
186 if (device->hidden->driver_pitch) {
187 frame->pitch = device->hidden->driver_pitch;
188 } else {
189 frame->pitch = buf.bytesused;
190 }
191 device->hidden->buffers[buf.index].available = 1;
192
193 *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
194
195 #if DEBUG_CAMERA
196 SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels);
197 #endif
198 break;
199
200 case IO_METHOD_USERPTR:
201 SDL_zero(buf);
202
203 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
204 buf.memory = V4L2_MEMORY_USERPTR;
205
206 if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
207 switch (errno) {
208 case EAGAIN:
209 return SDL_CAMERA_FRAME_SKIP;
210
211 case EIO:
212 // Could ignore EIO, see spec.
213 // fall through
214
215 default:
216 SDL_SetError("VIDIOC_DQBUF");
217 return SDL_CAMERA_FRAME_ERROR;
218 }
219 }
220
221 int i;
222 for (i = 0; i < device->hidden->nb_buffers; ++i) {
223 if (buf.m.userptr == (unsigned long)device->hidden->buffers[i].start && buf.length == size) {
224 break;
225 }
226 }
227
228 if (i >= device->hidden->nb_buffers) {
229 SDL_SetError("invalid buffer index");
230 return SDL_CAMERA_FRAME_ERROR;
231 }
232
233 frame->pixels = (void*)buf.m.userptr;
234 if (device->hidden->driver_pitch) {
235 frame->pitch = device->hidden->driver_pitch;
236 } else {
237 frame->pitch = buf.bytesused;
238 }
239 device->hidden->buffers[i].available = 1;
240
241 *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
242
243 #if DEBUG_CAMERA
244 SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels);
245 #endif
246 break;
247
248 case IO_METHOD_INVALID:
249 SDL_assert(!"Shouldn't have hit this");
250 break;
251 }
252
253 return SDL_CAMERA_FRAME_READY;
254}
255
256static void V4L2_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
257{
258 struct v4l2_buffer buf;
259 const int fd = device->hidden->fd;
260 const io_method io = device->hidden->io;
261 int i;
262
263 for (i = 0; i < device->hidden->nb_buffers; ++i) {
264 if (frame->pixels == device->hidden->buffers[i].start) {
265 break;
266 }
267 }
268
269 if (i >= device->hidden->nb_buffers) {
270 return; // oh well, we didn't own this.
271 }
272
273 switch (io) {
274 case IO_METHOD_READ:
275 break;
276
277 case IO_METHOD_MMAP:
278 SDL_zero(buf);
279
280 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
281 buf.memory = V4L2_MEMORY_MMAP;
282 buf.index = i;
283
284 if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
285 // !!! FIXME: disconnect the device.
286 return; //SDL_SetError("VIDIOC_QBUF");
287 }
288 device->hidden->buffers[i].available = 0;
289 break;
290
291 case IO_METHOD_USERPTR:
292 SDL_zero(buf);
293
294 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
295 buf.memory = V4L2_MEMORY_USERPTR;
296 buf.index = i;
297 buf.m.userptr = (unsigned long)frame->pixels;
298 buf.length = (int) device->hidden->buffers[i].length;
299
300 if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
301 // !!! FIXME: disconnect the device.
302 return; //SDL_SetError("VIDIOC_QBUF");
303 }
304 device->hidden->buffers[i].available = 0;
305 break;
306
307 case IO_METHOD_INVALID:
308 SDL_assert(!"Shouldn't have hit this");
309 break;
310 }
311}
312
313static bool EnqueueBuffers(SDL_Camera *device)
314{
315 const int fd = device->hidden->fd;
316 const io_method io = device->hidden->io;
317 switch (io) {
318 case IO_METHOD_READ:
319 break;
320
321 case IO_METHOD_MMAP:
322 for (int i = 0; i < device->hidden->nb_buffers; ++i) {
323 if (device->hidden->buffers[i].available == 0) {
324 struct v4l2_buffer buf;
325
326 SDL_zero(buf);
327 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
328 buf.memory = V4L2_MEMORY_MMAP;
329 buf.index = i;
330
331 if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
332 return SDL_SetError("VIDIOC_QBUF");
333 }
334 }
335 }
336 break;
337
338 case IO_METHOD_USERPTR:
339 for (int i = 0; i < device->hidden->nb_buffers; ++i) {
340 if (device->hidden->buffers[i].available == 0) {
341 struct v4l2_buffer buf;
342
343 SDL_zero(buf);
344 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
345 buf.memory = V4L2_MEMORY_USERPTR;
346 buf.index = i;
347 buf.m.userptr = (unsigned long)device->hidden->buffers[i].start;
348 buf.length = (int) device->hidden->buffers[i].length;
349
350 if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
351 return SDL_SetError("VIDIOC_QBUF");
352 }
353 }
354 }
355 break;
356
357 case IO_METHOD_INVALID: SDL_assert(!"Shouldn't have hit this"); break;
358 }
359 return true;
360}
361
362static bool AllocBufferRead(SDL_Camera *device, size_t buffer_size)
363{
364 device->hidden->buffers[0].length = buffer_size;
365 device->hidden->buffers[0].start = SDL_calloc(1, buffer_size);
366 return (device->hidden->buffers[0].start != NULL);
367}
368
369static bool AllocBufferMmap(SDL_Camera *device)
370{
371 const int fd = device->hidden->fd;
372 int i;
373 for (i = 0; i < device->hidden->nb_buffers; ++i) {
374 struct v4l2_buffer buf;
375
376 SDL_zero(buf);
377
378 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
379 buf.memory = V4L2_MEMORY_MMAP;
380 buf.index = i;
381
382 if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
383 return SDL_SetError("VIDIOC_QUERYBUF");
384 }
385
386 device->hidden->buffers[i].length = buf.length;
387 device->hidden->buffers[i].start =
388 mmap(NULL /* start anywhere */,
389 buf.length,
390 PROT_READ | PROT_WRITE /* required */,
391 MAP_SHARED /* recommended */,
392 fd, buf.m.offset);
393
394 if (MAP_FAILED == device->hidden->buffers[i].start) {
395 return SDL_SetError("mmap");
396 }
397 }
398 return true;
399}
400
401static bool AllocBufferUserPtr(SDL_Camera *device, size_t buffer_size)
402{
403 int i;
404 for (i = 0; i < device->hidden->nb_buffers; ++i) {
405 device->hidden->buffers[i].length = buffer_size;
406 device->hidden->buffers[i].start = SDL_calloc(1, buffer_size);
407
408 if (!device->hidden->buffers[i].start) {
409 return false;
410 }
411 }
412 return true;
413}
414
415static void format_v4l2_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
416{
417 switch (fmt) {
418 #define CASE(x, y, z) case x: *format = y; *colorspace = z; return
419 CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED);
420 CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB);
421 #undef CASE
422 default:
423 #if DEBUG_CAMERA
424 SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%c%c%c%c' (0x%x)",
425 (char)(Uint8)(fmt >> 0),
426 (char)(Uint8)(fmt >> 8),
427 (char)(Uint8)(fmt >> 16),
428 (char)(Uint8)(fmt >> 24), fmt);
429 #endif
430 break;
431 }
432 *format = SDL_PIXELFORMAT_UNKNOWN;
433 *colorspace = SDL_COLORSPACE_UNKNOWN;
434}
435
436static Uint32 format_sdl_to_v4l2(SDL_PixelFormat fmt)
437{
438 switch (fmt) {
439 #define CASE(y, x) case x: return y
440 CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
441 CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG);
442 #undef CASE
443 default:
444 return 0;
445 }
446}
447
448static void V4L2_CloseDevice(SDL_Camera *device)
449{
450 if (!device) {
451 return;
452 }
453
454 if (device->hidden) {
455 const io_method io = device->hidden->io;
456 const int fd = device->hidden->fd;
457
458 if ((io == IO_METHOD_MMAP) || (io == IO_METHOD_USERPTR)) {
459 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
460 xioctl(fd, VIDIOC_STREAMOFF, &type);
461 }
462
463 if (device->hidden->buffers) {
464 switch (io) {
465 case IO_METHOD_INVALID:
466 break;
467
468 case IO_METHOD_READ:
469 SDL_free(device->hidden->buffers[0].start);
470 break;
471
472 case IO_METHOD_MMAP:
473 for (int i = 0; i < device->hidden->nb_buffers; ++i) {
474 if (munmap(device->hidden->buffers[i].start, device->hidden->buffers[i].length) == -1) {
475 SDL_SetError("munmap");
476 }
477 }
478 break;
479
480 case IO_METHOD_USERPTR:
481 for (int i = 0; i < device->hidden->nb_buffers; ++i) {
482 SDL_free(device->hidden->buffers[i].start);
483 }
484 break;
485 }
486
487 SDL_free(device->hidden->buffers);
488 }
489
490 if (fd != -1) {
491 close(fd);
492 }
493 SDL_free(device->hidden);
494
495 device->hidden = NULL;
496 }
497}
498
499static bool V4L2_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
500{
501 const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
502 struct stat st;
503 struct v4l2_capability cap;
504 const int fd = open(handle->path, O_RDWR /* required */ | O_NONBLOCK, 0);
505
506 // most of this probably shouldn't fail unless the filesystem node changed out from under us since MaybeAddDevice().
507 if (fd == -1) {
508 return SDL_SetError("Cannot open '%s': %d, %s", handle->path, errno, strerror(errno));
509 } else if (fstat(fd, &st) == -1) {
510 close(fd);
511 return SDL_SetError("Cannot identify '%s': %d, %s", handle->path, errno, strerror(errno));
512 } else if (!S_ISCHR(st.st_mode)) {
513 close(fd);
514 return SDL_SetError("%s is not a character device", handle->path);
515 } else if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
516 const int err = errno;
517 close(fd);
518 if (err == EINVAL) {
519 return SDL_SetError("%s is unexpectedly not a V4L2 device", handle->path);
520 }
521 return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", err, handle->path);
522 } else if ((cap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) {
523 close(fd);
524 return SDL_SetError("%s is unexpectedly not a video capture device", handle->path);
525 }
526
527 device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
528 if (device->hidden == NULL) {
529 close(fd);
530 return false;
531 }
532
533 device->hidden->fd = fd;
534 device->hidden->io = IO_METHOD_INVALID;
535
536 // Select video input, video standard and tune here.
537 // errors in the crop code are not fatal.
538 struct v4l2_cropcap cropcap;
539 SDL_zero(cropcap);
540 cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
541 if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) {
542 struct v4l2_crop crop;
543 SDL_zero(crop);
544 crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
545 crop.c = cropcap.defrect; // reset to default
546 xioctl(fd, VIDIOC_S_CROP, &crop);
547 }
548
549 struct v4l2_format fmt;
550 SDL_zero(fmt);
551 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
552 fmt.fmt.pix.width = spec->width;
553 fmt.fmt.pix.height = spec->height;
554 fmt.fmt.pix.pixelformat = format_sdl_to_v4l2(spec->format);
555 //fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
556 fmt.fmt.pix.field = V4L2_FIELD_ANY;
557
558 #if DEBUG_CAMERA
559 SDL_Log("CAMERA: set SDL format %s", SDL_GetPixelFormatName(spec->format));
560 { const Uint32 f = fmt.fmt.pix.pixelformat; SDL_Log("CAMERA: set format V4L2_format=%d %c%c%c%c", f, (f >> 0) & 0xff, (f >> 8) & 0xff, (f >> 16) & 0xff, (f >> 24) & 0xff); }
561 #endif
562
563 if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
564 return SDL_SetError("Error VIDIOC_S_FMT");
565 }
566
567 if (spec->framerate_numerator && spec->framerate_denominator) {
568 struct v4l2_streamparm setfps;
569 SDL_zero(setfps);
570 setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
571 if (xioctl(fd, VIDIOC_G_PARM, &setfps) == 0) {
572 if ( (setfps.parm.capture.timeperframe.denominator != spec->framerate_numerator) ||
573 (setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator) ) {
574 setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
575 setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator;
576 setfps.parm.capture.timeperframe.denominator = spec->framerate_numerator;
577 if (xioctl(fd, VIDIOC_S_PARM, &setfps) == -1) {
578 return SDL_SetError("Error VIDIOC_S_PARM");
579 }
580 }
581 }
582 }
583
584 SDL_zero(fmt);
585 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
586 if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
587 return SDL_SetError("Error VIDIOC_G_FMT");
588 }
589 device->hidden->driver_pitch = fmt.fmt.pix.bytesperline;
590
591 io_method io = IO_METHOD_INVALID;
592 if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_STREAMING)) {
593 struct v4l2_requestbuffers req;
594 SDL_zero(req);
595 req.count = 8;
596 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
597 req.memory = V4L2_MEMORY_MMAP;
598 if ((xioctl(fd, VIDIOC_REQBUFS, &req) == 0) && (req.count >= 2)) {
599 io = IO_METHOD_MMAP;
600 device->hidden->nb_buffers = req.count;
601 } else { // mmap didn't work out? Try USERPTR.
602 SDL_zero(req);
603 req.count = 8;
604 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
605 req.memory = V4L2_MEMORY_USERPTR;
606 if (xioctl(fd, VIDIOC_REQBUFS, &req) == 0) {
607 io = IO_METHOD_USERPTR;
608 device->hidden->nb_buffers = 8;
609 }
610 }
611 }
612
613 if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_READWRITE)) {
614 io = IO_METHOD_READ;
615 device->hidden->nb_buffers = 1;
616 }
617
618 if (io == IO_METHOD_INVALID) {
619 return SDL_SetError("Don't have a way to talk to this device");
620 }
621
622 device->hidden->io = io;
623
624 device->hidden->buffers = SDL_calloc(device->hidden->nb_buffers, sizeof(*device->hidden->buffers));
625 if (!device->hidden->buffers) {
626 return false;
627 }
628
629 size_t size, pitch;
630 if (!SDL_CalculateSurfaceSize(device->spec.format, device->spec.width, device->spec.height, &size, &pitch, false)) {
631 return false;
632 }
633
634 bool rc = true;
635 switch (io) {
636 case IO_METHOD_READ:
637 rc = AllocBufferRead(device, size);
638 break;
639
640 case IO_METHOD_MMAP:
641 rc = AllocBufferMmap(device);
642 break;
643
644 case IO_METHOD_USERPTR:
645 rc = AllocBufferUserPtr(device, size);
646 break;
647
648 case IO_METHOD_INVALID:
649 SDL_assert(!"Shouldn't have hit this");
650 break;
651 }
652
653 if (!rc) {
654 return false;
655 } else if (!EnqueueBuffers(device)) {
656 return false;
657 } else if (io != IO_METHOD_READ) {
658 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
659 if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) {
660 return SDL_SetError("VIDIOC_STREAMON");
661 }
662 }
663
664 // Currently there is no user permission prompt for camera access, but maybe there will be a D-Bus portal interface at some point.
665 SDL_CameraPermissionOutcome(device, true);
666
667 return true;
668}
669
670static bool FindV4L2CameraByBusInfoCallback(SDL_Camera *device, void *userdata)
671{
672 const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
673 return (SDL_strcmp(handle->bus_info, (const char *) userdata) == 0);
674}
675
676static bool AddCameraFormat(const int fd, CameraFormatAddData *data, SDL_PixelFormat sdlfmt, SDL_Colorspace colorspace, Uint32 v4l2fmt, int w, int h)
677{
678 struct v4l2_frmivalenum frmivalenum;
679 SDL_zero(frmivalenum);
680 frmivalenum.pixel_format = v4l2fmt;
681 frmivalenum.width = (Uint32) w;
682 frmivalenum.height = (Uint32) h;
683
684 while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == 0) {
685 if (frmivalenum.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
686 const int numerator = (int) frmivalenum.discrete.numerator;
687 const int denominator = (int) frmivalenum.discrete.denominator;
688 #if DEBUG_CAMERA
689 const float fps = (float) denominator / (float) numerator;
690 SDL_Log("CAMERA: * Has discrete frame interval (%d / %d), fps=%f", numerator, denominator, fps);
691 #endif
692 if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, denominator, numerator)) {
693 return false; // Probably out of memory; we'll go with what we have, if anything.
694 }
695 frmivalenum.index++; // set up for the next one.
696 } else if ((frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) || (frmivalenum.type == V4L2_FRMIVAL_TYPE_CONTINUOUS)) {
697 int d = frmivalenum.stepwise.min.denominator;
698 // !!! FIXME: should we step by the numerator...?
699 for (int n = (int) frmivalenum.stepwise.min.numerator; n <= (int) frmivalenum.stepwise.max.numerator; n += (int) frmivalenum.stepwise.step.numerator) {
700 #if DEBUG_CAMERA
701 const float fps = (float) d / (float) n;
702 SDL_Log("CAMERA: * Has %s frame interval (%d / %d), fps=%f", (frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) ? "stepwise" : "continuous", n, d, fps);
703 #endif
704 // SDL expects framerate, V4L2 provides interval
705 if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, d, n)) {
706 return false; // Probably out of memory; we'll go with what we have, if anything.
707 }
708 d += (int) frmivalenum.stepwise.step.denominator;
709 }
710 break;
711 }
712 }
713
714 return true;
715}
716
717
718static void MaybeAddDevice(const char *path)
719{
720 if (!path) {
721 return;
722 }
723
724 struct stat st;
725 const int fd = open(path, O_RDWR /* required */ | O_NONBLOCK, 0);
726 if (fd == -1) {
727 return; // can't open it? skip it.
728 } else if (fstat(fd, &st) == -1) {
729 close(fd);
730 return; // can't stat it? skip it.
731 } else if (!S_ISCHR(st.st_mode)) {
732 close(fd);
733 return; // not a character device.
734 }
735
736 struct v4l2_capability vcap;
737 const int rc = ioctl(fd, VIDIOC_QUERYCAP, &vcap);
738 if (rc != 0) {
739 close(fd);
740 return; // probably not a v4l2 device at all.
741 } else if ((vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) {
742 close(fd);
743 return; // not a video capture device.
744 } else if (SDL_FindPhysicalCameraByCallback(FindV4L2CameraByBusInfoCallback, vcap.bus_info)) {
745 close(fd);
746 return; // already have it.
747 }
748
749 #if DEBUG_CAMERA
750 SDL_Log("CAMERA: V4L2 camera path='%s' bus_info='%s' name='%s'", path, (const char *) vcap.bus_info, vcap.card);
751 #endif
752
753 CameraFormatAddData add_data;
754 SDL_zero(add_data);
755
756 struct v4l2_fmtdesc fmtdesc;
757 SDL_zero(fmtdesc);
758 fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
759 while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
760 SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN;
761 SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN;
762 format_v4l2_to_sdl(fmtdesc.pixelformat, &sdlfmt, &colorspace);
763
764 #if DEBUG_CAMERA
765 SDL_Log("CAMERA: - Has format '%s'%s%s", SDL_GetPixelFormatName(sdlfmt),
766 (fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) ? " [EMULATED]" : "",
767 (fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) ? " [COMPRESSED]" : "");
768 #endif
769
770 fmtdesc.index++; // prepare for next iteration.
771
772 if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
773 continue; // unsupported by SDL atm.
774 }
775
776 struct v4l2_frmsizeenum frmsizeenum;
777 SDL_zero(frmsizeenum);
778 frmsizeenum.pixel_format = fmtdesc.pixelformat;
779
780 while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) {
781 if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
782 const int w = (int) frmsizeenum.discrete.width;
783 const int h = (int) frmsizeenum.discrete.height;
784 #if DEBUG_CAMERA
785 SDL_Log("CAMERA: * Has discrete size %dx%d", w, h);
786 #endif
787 if (!AddCameraFormat(fd, &add_data, sdlfmt, colorspace, fmtdesc.pixelformat, w, h)) {
788 break; // Probably out of memory; we'll go with what we have, if anything.
789 }
790 frmsizeenum.index++; // set up for the next one.
791 } else if ((frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) || (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS)) {
792 const int minw = (int) frmsizeenum.stepwise.min_width;
793 const int minh = (int) frmsizeenum.stepwise.min_height;
794 const int maxw = (int) frmsizeenum.stepwise.max_width;
795 const int maxh = (int) frmsizeenum.stepwise.max_height;
796 const int stepw = (int) frmsizeenum.stepwise.step_width;
797 const int steph = (int) frmsizeenum.stepwise.step_height;
798 for (int w = minw; w <= maxw; w += stepw) {
799 for (int h = minh; w <= maxh; w += steph) {
800 #if DEBUG_CAMERA
801 SDL_Log("CAMERA: * Has %s size %dx%d", (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) ? "stepwise" : "continuous", w, h);
802 #endif
803 if (!AddCameraFormat(fd, &add_data, sdlfmt, colorspace, fmtdesc.pixelformat, w, h)) {
804 break; // Probably out of memory; we'll go with what we have, if anything.
805 }
806 }
807 }
808 break;
809 }
810 }
811 }
812
813 close(fd);
814
815 #if DEBUG_CAMERA
816 SDL_Log("CAMERA: (total specs: %d)", add_data.num_specs);
817 #endif
818
819 if (add_data.num_specs > 0) {
820 V4L2DeviceHandle *handle = (V4L2DeviceHandle *) SDL_calloc(1, sizeof (V4L2DeviceHandle));
821 if (handle) {
822 handle->path = SDL_strdup(path);
823 if (handle->path) {
824 handle->bus_info = SDL_strdup((char *)vcap.bus_info);
825 if (handle->bus_info) {
826 if (SDL_AddCamera((const char *) vcap.card, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, handle)) {
827 SDL_free(add_data.specs);
828 return; // good to go.
829 }
830 SDL_free(handle->bus_info);
831 }
832 SDL_free(handle->path);
833 }
834 SDL_free(handle);
835 }
836 }
837 SDL_free(add_data.specs);
838}
839
840static void V4L2_FreeDeviceHandle(SDL_Camera *device)
841{
842 if (device) {
843 V4L2DeviceHandle *handle = (V4L2DeviceHandle *) device->handle;
844 SDL_free(handle->path);
845 SDL_free(handle->bus_info);
846 SDL_free(handle);
847 }
848}
849
850#ifdef SDL_USE_LIBUDEV
851static bool FindV4L2CameraByPathCallback(SDL_Camera *device, void *userdata)
852{
853 const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
854 return (SDL_strcmp(handle->path, (const char *) userdata) == 0);
855}
856
857static void MaybeRemoveDevice(const char *path)
858{
859 if (path) {
860 SDL_CameraDisconnected(SDL_FindPhysicalCameraByCallback(FindV4L2CameraByPathCallback, (void *) path));
861 }
862}
863
864static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
865{
866 if (devpath && (udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) {
867 if (udev_type == SDL_UDEV_DEVICEADDED) {
868 MaybeAddDevice(devpath);
869 } else if (udev_type == SDL_UDEV_DEVICEREMOVED) {
870 MaybeRemoveDevice(devpath);
871 }
872 }
873}
874#endif // SDL_USE_LIBUDEV
875
876static void V4L2_Deinitialize(void)
877{
878#ifdef SDL_USE_LIBUDEV
879 SDL_UDEV_DelCallback(CameraUdevCallback);
880 SDL_UDEV_Quit();
881#endif // SDL_USE_LIBUDEV
882}
883
884static void V4L2_DetectDevices(void)
885{
886#ifdef SDL_USE_LIBUDEV
887 if (SDL_UDEV_Init()) {
888 if (SDL_UDEV_AddCallback(CameraUdevCallback)) {
889 SDL_UDEV_Scan(); // Force a scan to build the initial device list
890 }
891 return;
892 }
893#endif // SDL_USE_LIBUDEV
894
895 DIR *dirp = opendir("/dev");
896 if (dirp) {
897 struct dirent *dent;
898 while ((dent = readdir(dirp)) != NULL) {
899 int num = 0;
900 if (SDL_sscanf(dent->d_name, "video%d", &num) == 1) {
901 char fullpath[64];
902 SDL_snprintf(fullpath, sizeof (fullpath), "/dev/video%d", num);
903 MaybeAddDevice(fullpath);
904 }
905 }
906 closedir(dirp);
907 }
908}
909
910static bool V4L2_Init(SDL_CameraDriverImpl *impl)
911{
912 impl->DetectDevices = V4L2_DetectDevices;
913 impl->OpenDevice = V4L2_OpenDevice;
914 impl->CloseDevice = V4L2_CloseDevice;
915 impl->WaitDevice = V4L2_WaitDevice;
916 impl->AcquireFrame = V4L2_AcquireFrame;
917 impl->ReleaseFrame = V4L2_ReleaseFrame;
918 impl->FreeDeviceHandle = V4L2_FreeDeviceHandle;
919 impl->Deinitialize = V4L2_Deinitialize;
920
921 return true;
922}
923
924CameraBootStrap V4L2_bootstrap = {
925 "v4l2", "SDL Video4Linux2 camera driver", V4L2_Init, false
926};
927
928#endif // SDL_CAMERA_DRIVER_V4L2
929
diff --git a/contrib/SDL-3.2.8/src/camera/vita/SDL_camera_vita.c b/contrib/SDL-3.2.8/src/camera/vita/SDL_camera_vita.c
new file mode 100644
index 0000000..42a5a89
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/vita/SDL_camera_vita.c
@@ -0,0 +1,258 @@
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#ifdef SDL_CAMERA_DRIVER_VITA
24
25#include "../SDL_syscamera.h"
26#include <psp2/camera.h>
27#include <psp2/kernel/sysmem.h>
28
29static struct {
30 Sint32 w;
31 Sint32 h;
32 Sint32 res;
33} resolutions[] = {
34 {640, 480, SCE_CAMERA_RESOLUTION_640_480},
35 {320, 240, SCE_CAMERA_RESOLUTION_320_240},
36 {160, 120, SCE_CAMERA_RESOLUTION_160_120},
37 {352, 288, SCE_CAMERA_RESOLUTION_352_288},
38 {176, 144, SCE_CAMERA_RESOLUTION_176_144},
39 {480, 272, SCE_CAMERA_RESOLUTION_480_272},
40 {640, 360, SCE_CAMERA_RESOLUTION_640_360},
41 {0, 0, 0}
42};
43
44static Sint32 fps[] = {5, 10, 15, 20, 24, 25, 30, 60, 0};
45
46static void GatherCameraSpecs(Sint32 devid, CameraFormatAddData *add_data, char **fullname, SDL_CameraPosition *position)
47{
48 SDL_zerop(add_data);
49
50 if (devid == SCE_CAMERA_DEVICE_FRONT) {
51 *position = SDL_CAMERA_POSITION_FRONT_FACING;
52 *fullname = SDL_strdup("Front-facing camera");
53 } else if (devid == SCE_CAMERA_DEVICE_BACK) {
54 *position = SDL_CAMERA_POSITION_BACK_FACING;
55 *fullname = SDL_strdup("Back-facing camera");
56 }
57
58 if (!*fullname) {
59 *fullname = SDL_strdup("Generic camera");
60 }
61
62 // Note: there are actually more fps and pixelformats. Planar YUV is fastest. Support only YUV and integer fps for now
63 Sint32 idx = 0;
64 while (resolutions[idx].res > 0) {
65 Sint32 fps_idx = 0;
66 while (fps[fps_idx] > 0) {
67 SDL_AddCameraFormat(add_data, SDL_PIXELFORMAT_IYUV, SDL_COLORSPACE_BT601_LIMITED, resolutions[idx].w, resolutions[idx].h, fps[fps_idx], 1); /* SCE_CAMERA_FORMAT_ARGB */
68 fps_idx++;
69 }
70 idx++;
71 }
72}
73
74static bool FindVitaCameraByID(SDL_Camera *device, void *userdata)
75{
76 Sint32 devid = (Sint32) userdata;
77 return (devid == (Sint32)device->handle);
78}
79
80static void MaybeAddDevice(Sint32 devid)
81{
82 #if DEBUG_CAMERA
83 SDL_Log("CAMERA: MaybeAddDevice('%d')", devid);
84 #endif
85
86 if (SDL_FindPhysicalCameraByCallback(FindVitaCameraByID, (void *) devid)) {
87 return; // already have this one.
88 }
89
90 SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
91 char *fullname = NULL;
92 CameraFormatAddData add_data;
93 GatherCameraSpecs(devid, &add_data, &fullname, &position);
94
95 if (add_data.num_specs > 0) {
96 SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, (void*)devid);
97 }
98
99 SDL_free(fullname);
100 SDL_free(add_data.specs);
101}
102
103static SceUID imbUid = -1;
104
105static void freeBuffers(SceCameraInfo* info)
106{
107 if (imbUid != -1) {
108 sceKernelFreeMemBlock(imbUid);
109 info->pIBase = NULL;
110 imbUid = -1;
111 }
112}
113
114static bool VITACAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
115{
116 // we can't open more than one camera, so error-out early
117 if (imbUid != -1) {
118 return SDL_SetError("Only one camera can be active");
119 }
120
121 SceCameraInfo* info = (SceCameraInfo*)SDL_calloc(1, sizeof(SceCameraInfo));
122
123 info->size = sizeof(SceCameraInfo);
124 info->priority = SCE_CAMERA_PRIORITY_SHARE;
125 info->buffer = 0; // target buffer set by sceCameraOpen
126
127 info->framerate = spec->framerate_numerator / spec->framerate_denominator;
128
129 Sint32 idx = 0;
130 while (resolutions[idx].res > 0) {
131 if (spec->width == resolutions[idx].w && spec->height == resolutions[idx].h) {
132 info->resolution = resolutions[idx].res;
133 break;
134 }
135 idx++;
136 }
137
138 info->range = 1;
139 info->format = SCE_CAMERA_FORMAT_YUV420_PLANE;
140 info->pitch = 0; // same size surface
141
142 info->sizeIBase = spec->width*spec->height;;
143 info->sizeUBase = ((spec->width+1)/2) * ((spec->height+1) / 2);
144 info->sizeVBase = ((spec->width+1)/2) * ((spec->height+1) / 2);
145
146 // PHYCONT memory size *must* be a multiple of 1MB, we can just always spend 2MB, since we don't use PHYCONT anywhere else
147 imbUid = sceKernelAllocMemBlock("CameraI", SCE_KERNEL_MEMBLOCK_TYPE_USER_MAIN_PHYCONT_NC_RW, 2*1024*1024 , NULL);
148 if (imbUid < 0)
149 {
150 return SDL_SetError("sceKernelAllocMemBlock error: 0x%08X", imbUid);
151 }
152 sceKernelGetMemBlockBase(imbUid, &(info->pIBase));
153
154 info->pUBase = info->pIBase + info->sizeIBase;
155 info->pVBase = info->pIBase + (info->sizeIBase + info->sizeUBase);
156
157 device->hidden = (struct SDL_PrivateCameraData *)info;
158
159 int ret = sceCameraOpen((int)device->handle, info);
160 if (ret == 0) {
161 ret = sceCameraStart((int)device->handle);
162 if (ret == 0) {
163 SDL_CameraPermissionOutcome(device, true);
164 return true;
165 } else {
166 SDL_SetError("sceCameraStart error: 0x%08X", imbUid);
167 }
168 } else {
169 SDL_SetError("sceCameraOpen error: 0x%08X", imbUid);
170 }
171
172 freeBuffers(info);
173
174 return false;
175}
176
177static void VITACAMERA_CloseDevice(SDL_Camera *device)
178{
179 if (device->hidden) {
180 sceCameraStop((int)device->handle);
181 sceCameraClose((int)device->handle);
182 freeBuffers((SceCameraInfo*)device->hidden);
183 SDL_free(device->hidden);
184 }
185}
186
187static bool VITACAMERA_WaitDevice(SDL_Camera *device)
188{
189 while(!sceCameraIsActive((int)device->handle)) {}
190 return true;
191}
192
193static SDL_CameraFrameResult VITACAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
194{
195 SceCameraRead read = {0};
196 read.size = sizeof(SceCameraRead);
197 read.mode = 1; // don't wait next frame
198
199 int ret = sceCameraRead((int)device->handle, &read);
200
201 if (ret < 0) {
202 SDL_SetError("sceCameraRead error: 0x%08X", ret);
203 return SDL_CAMERA_FRAME_ERROR;
204 }
205
206 *timestampNS = read.timestamp;
207
208 SceCameraInfo* info = (SceCameraInfo*)(device->hidden);
209
210 frame->pitch = info->width;
211 frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), info->sizeIBase + info->sizeUBase + info->sizeVBase);
212
213 if (frame->pixels) {
214 SDL_memcpy(frame->pixels, info->pIBase, info->sizeIBase + info->sizeUBase + info->sizeVBase);
215 return SDL_CAMERA_FRAME_READY;
216 }
217
218 return SDL_CAMERA_FRAME_ERROR;
219}
220
221static void VITACAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
222{
223 SDL_aligned_free(frame->pixels);
224}
225
226static void VITACAMERA_DetectDevices(void)
227{
228 MaybeAddDevice(SCE_CAMERA_DEVICE_FRONT);
229 MaybeAddDevice(SCE_CAMERA_DEVICE_BACK);
230}
231
232static void VITACAMERA_FreeDeviceHandle(SDL_Camera *device)
233{
234}
235
236static void VITACAMERA_Deinitialize(void)
237{
238}
239
240static bool VITACAMERA_Init(SDL_CameraDriverImpl *impl)
241{
242 impl->DetectDevices = VITACAMERA_DetectDevices;
243 impl->OpenDevice = VITACAMERA_OpenDevice;
244 impl->CloseDevice = VITACAMERA_CloseDevice;
245 impl->WaitDevice = VITACAMERA_WaitDevice;
246 impl->AcquireFrame = VITACAMERA_AcquireFrame;
247 impl->ReleaseFrame = VITACAMERA_ReleaseFrame;
248 impl->FreeDeviceHandle = VITACAMERA_FreeDeviceHandle;
249 impl->Deinitialize = VITACAMERA_Deinitialize;
250
251 return true;
252}
253
254CameraBootStrap VITACAMERA_bootstrap = {
255 "vita", "SDL PSVita camera driver", VITACAMERA_Init, false
256};
257
258#endif // SDL_CAMERA_DRIVER_VITA