SDL: camera: Reworked to operate with a driver interface, like other subsystems.

From cb10c80aafb42d1dfb6d8bcc34aa765e83dfe619 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Fri, 1 Dec 2023 10:59:13 -0500
Subject: [PATCH] camera: Reworked to operate with a driver interface, like
 other subsystems.

---
 CMakeLists.txt                                |  28 +-
 include/SDL3/SDL_camera.h                     |  65 +++++
 include/SDL3/SDL_hints.h                      |  17 +-
 include/SDL3/SDL_init.h                       |   3 +-
 include/build_config/SDL_build_config.h.cmake |   9 +-
 .../build_config/SDL_build_config_android.h   |   2 +-
 .../SDL_build_config_emscripten.h             |   2 +-
 include/build_config/SDL_build_config_ios.h   |   2 +-
 include/build_config/SDL_build_config_macos.h |   3 +-
 .../build_config/SDL_build_config_minimal.h   |   2 +-
 include/build_config/SDL_build_config_ngage.h |   2 +-
 .../build_config/SDL_build_config_windows.h   |   2 +-
 .../build_config/SDL_build_config_wingdk.h    |   2 +-
 include/build_config/SDL_build_config_winrt.h |   2 +-
 include/build_config/SDL_build_config_xbox.h  |   2 +-
 src/SDL.c                                     |  37 +++
 src/camera/SDL_camera.c                       | 239 ++++++++++++++----
 src/camera/SDL_camera_c.h                     |   2 +-
 src/camera/SDL_syscamera.h                    |  69 +++--
 src/camera/android/SDL_camera_android.c       |  96 ++++---
 .../SDL_camera_coremedia.m}                   | 130 ++++------
 src/camera/v4l2/SDL_camera_v4l2.c             | 215 ++++++++--------
 src/video/SDL_video.c                         |   9 -
 test/testcamera.c                             |   2 +-
 test/testcameraminimal.c                      |   2 +-
 25 files changed, 610 insertions(+), 334 deletions(-)
 rename src/camera/{apple/SDL_camera_apple.m => coremedia/SDL_camera_coremedia.m} (87%)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d3b09c6b3fe61..b6eefb29650dc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -332,6 +332,7 @@ set_option(SDL_KMSDRM              "Use KMS DRM video driver" ${UNIX_SYS})
 dep_option(SDL_KMSDRM_SHARED       "Dynamically load KMS DRM support" ON "SDL_KMSDRM" OFF)
 set_option(SDL_OFFSCREEN           "Use offscreen video driver" ON)
 dep_option(SDL_CAMERA              "Enable camera support" ON SDL_VIDEO OFF)
+set_option(SDL_DUMMYCAMERA         "Support the dummy camera driver" ON)
 option_string(SDL_BACKGROUNDING_SIGNAL "number to use for magic backgrounding signal or 'OFF'" OFF)
 option_string(SDL_FOREGROUNDING_SIGNAL "number to use for magic foregrounding signal or 'OFF'" OFF)
 dep_option(SDL_HIDAPI              "Enable the HIDAPI subsystem" ON "NOT VISIONOS" OFF)
@@ -1172,6 +1173,23 @@ if(SDL_AUDIO)
   endif()
 endif()
 
+if(SDL_CAMERA)
+  # CheckDummyCamera/CheckDiskCamera - valid for all platforms
+  if(SDL_DUMMYCAMERA)
+    set(SDL_CAMERA_DRIVER_DUMMY 1)
+    sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c")
+    set(HAVE_DUMMYCAMERA TRUE)
+    set(HAVE_SDL_CAMERA TRUE)
+  endif()
+  # !!! FIXME: for later.
+  #if(SDL_DISKCAMERA)
+  #  set(SDL_CAMERA_DRIVER_DISK 1)
+  #  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/disk/*.c")
+  #  set(HAVE_DISKCAMERA TRUE)
+  #  set(HAVE_SDL_CAMERA TRUE)
+  #endif()
+endif()
+
 if(UNIX OR APPLE)
   # Relevant for Unix/Darwin only
   set(DYNAPI_NEEDS_DLOPEN 1)
@@ -1290,7 +1308,7 @@ if(ANDROID)
   endif()
 
   if(SDL_CAMERA)
-    set(SDL_CAMERA_ANDROID 1)
+    set(SDL_CAMERA_DRIVER_ANDROID 1)
     set(HAVE_CAMERA TRUE)
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/android/*.c")
   endif()
@@ -1531,7 +1549,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU)
     endif()
 
     if(SDL_CAMERA AND HAVE_LINUX_VIDEODEV2_H)
-      set(SDL_CAMERA_V4L2 1)
+      set(SDL_CAMERA_DRIVER_V4L2 1)
       set(HAVE_CAMERA TRUE)
       sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/v4l2/*.c")
     endif()
@@ -2035,9 +2053,9 @@ elseif(APPLE)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/file/cocoa/*.m")
 
   if(IOS OR TVOS OR MACOSX OR DARWIN)
-    set(SDL_CAMERA_APPLE TRUE)
+    set(SDL_CAMERA_DRIVER_COREMEDIA 1)
     set(HAVE_CAMERA TRUE)
-    sdl_sources("${SDL3_SOURCE_DIR}/src/camera/apple/SDL_camera_apple.m")
+    sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/coremedia/*.m")
   endif()
 
   if(IOS OR TVOS OR VISIONOS)
@@ -2739,7 +2757,7 @@ if(NOT HAVE_SDL_MISC)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/dummy/*.c")
 endif()
 if(NOT HAVE_CAMERA)
-  set(SDL_CAMERA_DUMMY 1)
+  set(SDL_CAMERA_DRIVER_DUMMY 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c")
 endif()
 
diff --git a/include/SDL3/SDL_camera.h b/include/SDL3/SDL_camera.h
index c37499c54592d..e2b13cfe82dd4 100644
--- a/include/SDL3/SDL_camera.h
+++ b/include/SDL3/SDL_camera.h
@@ -108,6 +108,71 @@ typedef struct SDL_CameraFrame
 } SDL_CameraFrame;
 
 
+/**
+ * Use this function to get the number of built-in camera drivers.
+ *
+ * This function returns a hardcoded number. This never returns a negative
+ * value; if there are no drivers compiled into this build of SDL, this
+ * function returns zero. The presence of a driver in this list does not mean
+ * it will function, it just means SDL is capable of interacting with that
+ * interface. For example, a build of SDL might have v4l2 support, but if
+ * there's no kernel support available, SDL's v4l2 driver would fail if used.
+ *
+ * By default, SDL tries all drivers, in its preferred order, until one is
+ * found to be usable.
+ *
+ * \returns the number of built-in camera drivers.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameraDriver
+ */
+extern DECLSPEC int SDLCALL SDL_GetNumCameraDrivers(void);
+
+/**
+ * Use this function to get the name of a built in camera driver.
+ *
+ * The list of camera drivers is given in the order that they are normally
+ * initialized by default; the drivers that seem more reasonable to choose
+ * first (as far as the SDL developers believe) are earlier in the list.
+ *
+ * The names of drivers are all simple, low-ASCII identifiers, like "v4l2",
+ * "coremedia" or "android". These never have Unicode characters, and are not
+ * meant to be proper names.
+ *
+ * \param index the index of the camera driver; the value ranges from 0 to
+ *              SDL_GetNumCameraDrivers() - 1
+ * \returns the name of the camera driver at the requested index, or NULL if an
+ *          invalid index was specified.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetNumCameraDrivers
+ */
+extern DECLSPEC const char *SDLCALL SDL_GetCameraDriver(int index);
+
+/**
+ * Get the name of the current camera driver.
+ *
+ * The returned string points to internal static memory and thus never becomes
+ * invalid, even if you quit the camera subsystem and initialize a new driver
+ * (although such a case would return a different static string from another
+ * call to this function, of course). As such, you should not modify or free
+ * the returned string.
+ *
+ * \returns the name of the current camera driver or NULL if no driver has been
+ *          initialized.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC const char *SDLCALL SDL_GetCurrentCameraDriver(void);
+
 /**
  * Get a list of currently connected camera devices.
  *
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index acbbd84ea223c..7c30a707dc8a8 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -355,6 +355,22 @@ extern "C" {
  */
 #define SDL_HINT_BMP_SAVE_LEGACY_FORMAT "SDL_BMP_SAVE_LEGACY_FORMAT"
 
+/**
+ *  A variable that decides what camera backend to use.
+ *
+ *  By default, SDL will try all available camera backends in a reasonable
+ *  order until it finds one that can work, but this hint allows the app
+ *  or user to force a specific target, such as "directshow" if, say, you are
+ *  on Windows Media Foundations but want to try DirectShow instead.
+ *
+ *  The default value is unset, in which case SDL will try to figure out
+ *  the best camera backend on your behalf. This hint needs to be set
+ *  before SDL_Init() is called to be useful.
+ *
+ *  This hint is available since SDL 3.0.0.
+ */
+#define SDL_HINT_CAMERA_DRIVER "SDL_CAMERA_DRIVER"
+
 /**
  * A variable controlling whether DirectInput should be used for controllers
  *
@@ -2478,7 +2494,6 @@ extern "C" {
  */
 #define SDL_HINT_XINPUT_ENABLED "SDL_XINPUT_ENABLED"
 
-
 /**
  *  An enumeration of hint priorities
  */
diff --git a/include/SDL3/SDL_init.h b/include/SDL3/SDL_init.h
index c208b5f7d3814..8459bf9ebbb2f 100644
--- a/include/SDL3/SDL_init.h
+++ b/include/SDL3/SDL_init.h
@@ -59,7 +59,8 @@ typedef enum
     SDL_INIT_HAPTIC       = 0x00001000,
     SDL_INIT_GAMEPAD      = 0x00002000,  /**< `SDL_INIT_GAMEPAD` implies `SDL_INIT_JOYSTICK` */
     SDL_INIT_EVENTS       = 0x00004000,
-    SDL_INIT_SENSOR       = 0x00008000
+    SDL_INIT_SENSOR       = 0x00008000,
+    SDL_INIT_CAMERA       = 0x00010000   /**< `SDL_INIT_CAMERA` implies `SDL_INIT_EVENTS` */
 } SDL_InitFlags;
 
 /**
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index 076d3120f14ff..7985065dcbfc4 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/include/build_config/SDL_build_config.h.cmake
@@ -467,10 +467,11 @@
 #cmakedefine SDL_FILESYSTEM_N3DS @SDL_FILESYSTEM_N3DS@
 
 /* Enable camera subsystem */
-#cmakedefine SDL_CAMERA_DUMMY @SDL_CAMERA_DUMMY@
-#cmakedefine SDL_CAMERA_V4L2 @SDL_CAMERA_V4L2@
-#cmakedefine SDL_CAMERA_APPLE @SDL_CAMERA_APPLE@
-#cmakedefine SDL_CAMERA_ANDROID @SDL_CAMERA_ANDROID@
+#cmakedefine SDL_CAMERA_DRIVER_DUMMY @SDL_CAMERA_DRIVER_DUMMY@
+/* !!! FIXME: for later cmakedefine SDL_CAMERA_DRIVER_DISK @SDL_CAMERA_DRIVER_DISK@ */
+#cmakedefine SDL_CAMERA_DRIVER_V4L2 @SDL_CAMERA_DRIVER_V4L2@
+#cmakedefine SDL_CAMERA_DRIVER_COREMEDIA @SDL_CAMERA_DRIVER_COREMEDIA@
+#cmakedefine SDL_CAMERA_DRIVER_ANDROID @SDL_CAMERA_DRIVER_ANDROID@
 
 /* Enable misc subsystem */
 #cmakedefine SDL_MISC_DUMMY @SDL_MISC_DUMMY@
diff --git a/include/build_config/SDL_build_config_android.h b/include/build_config/SDL_build_config_android.h
index c2eaf09859212..9ae2ca4d95bbe 100644
--- a/include/build_config/SDL_build_config_android.h
+++ b/include/build_config/SDL_build_config_android.h
@@ -191,6 +191,6 @@
 #define SDL_FILESYSTEM_ANDROID   1
 
 /* Enable the camera driver */
-#define SDL_CAMERA_ANDROID 1
+#define SDL_CAMERA_DRIVER_ANDROID 1
 
 #endif /* SDL_build_config_android_h_ */
diff --git a/include/build_config/SDL_build_config_emscripten.h b/include/build_config/SDL_build_config_emscripten.h
index 3d7836678229e..07a94d615dce4 100644
--- a/include/build_config/SDL_build_config_emscripten.h
+++ b/include/build_config/SDL_build_config_emscripten.h
@@ -210,6 +210,6 @@
 #define SDL_FILESYSTEM_EMSCRIPTEN 1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */  /* !!! FIXME */
-#define SDL_CAMERA_DUMMY  1
+#define SDL_CAMERA_DRIVER_DUMMY  1
 
 #endif /* SDL_build_config_emscripten_h */
diff --git a/include/build_config/SDL_build_config_ios.h b/include/build_config/SDL_build_config_ios.h
index a356f8920fab4..649ff4e86690d 100644
--- a/include/build_config/SDL_build_config_ios.h
+++ b/include/build_config/SDL_build_config_ios.h
@@ -213,6 +213,6 @@
 #define SDL_FILESYSTEM_COCOA   1
 
 /* enable camera support */
-#define SDL_CAMERA_APPLE 1
+#define SDL_CAMERA_DRIVER_COREMEDIA 1
 
 #endif /* SDL_build_config_ios_h_ */
diff --git a/include/build_config/SDL_build_config_macos.h b/include/build_config/SDL_build_config_macos.h
index d45a3980cd32f..a8bed484d94d3 100644
--- a/include/build_config/SDL_build_config_macos.h
+++ b/include/build_config/SDL_build_config_macos.h
@@ -270,7 +270,8 @@
 #define SDL_FILESYSTEM_COCOA   1
 
 /* enable camera support */
-#define SDL_CAMERA_APPLE 1
+#define SDL_CAMERA_DRIVER_COREMEDIA 1
+#define SDL_CAMERA_DRIVER_DUMMY 1
 
 /* Enable assembly routines */
 #ifdef __ppc__
diff --git a/include/build_config/SDL_build_config_minimal.h b/include/build_config/SDL_build_config_minimal.h
index 38e870160a77b..06d02557ea223 100644
--- a/include/build_config/SDL_build_config_minimal.h
+++ b/include/build_config/SDL_build_config_minimal.h
@@ -90,6 +90,6 @@ typedef unsigned int uintptr_t;
 #define SDL_FILESYSTEM_DUMMY  1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */
-#define SDL_CAMERA_DUMMY  1
+#define SDL_CAMERA_DRIVER_DUMMY  1
 
 #endif /* SDL_build_config_minimal_h_ */
diff --git a/include/build_config/SDL_build_config_ngage.h b/include/build_config/SDL_build_config_ngage.h
index a8719bd90905e..3449627b02e8a 100644
--- a/include/build_config/SDL_build_config_ngage.h
+++ b/include/build_config/SDL_build_config_ngage.h
@@ -87,6 +87,6 @@ typedef unsigned long      uintptr_t;
 #define SDL_FILESYSTEM_DUMMY 1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */
-#define SDL_CAMERA_DUMMY  1
+#define SDL_CAMERA_DRIVER_DUMMY  1
 
 #endif /* SDL_build_config_ngage_h_ */
diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h
index 42682d8fcf3c5..066888fbd7bc7 100644
--- a/include/build_config/SDL_build_config_windows.h
+++ b/include/build_config/SDL_build_config_windows.h
@@ -312,6 +312,6 @@ typedef unsigned int uintptr_t;
 #define SDL_FILESYSTEM_WINDOWS  1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */  /* !!! FIXME */
-#define SDL_CAMERA_DUMMY  1
+#define SDL_CAMERA_DRIVER_DUMMY  1
 
 #endif /* SDL_build_config_windows_h_ */
diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h
index 0c60bea6f32ba..a636694a0e775 100644
--- a/include/build_config/SDL_build_config_wingdk.h
+++ b/include/build_config/SDL_build_config_wingdk.h
@@ -248,7 +248,7 @@
 #define SDL_FILESYSTEM_WINDOWS  1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */  /* !!! FIXME */
-#define SDL_CAMERA_DUMMY  1
+#define SDL_CAMERA_DRIVER_DUMMY  1
 
 /* Use the (inferior) GDK text input method for GDK platforms */
 /*#define SDL_GDK_TEXTINPUT 1*/
diff --git a/include/build_config/SDL_build_config_winrt.h b/include/build_config/SDL_build_config_winrt.h
index 4b3f865c48519..07c656ef34e6c 100644
--- a/include/build_config/SDL_build_config_winrt.h
+++ b/include/build_config/SDL_build_config_winrt.h
@@ -216,6 +216,6 @@
 #define SDL_POWER_WINRT 1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */  /* !!! FIXME */
-#define SDL_CAMERA_DUMMY  1
+#define SDL_CAMERA_DRIVER_DUMMY  1
 
 #endif /* SDL_build_config_winrt_h_ */
diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h
index 11116f9a60274..ea050da7d2831 100644
--- a/include/build_config/SDL_build_config_xbox.h
+++ b/include/build_config/SDL_build_config_xbox.h
@@ -237,6 +237,6 @@
 #define SDL_GDK_TEXTINPUT 1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */
-#define SDL_CAMERA_DUMMY  1
+#define SDL_CAMERA_DRIVER_DUMMY  1
 
 #endif /* SDL_build_config_wingdk_h_ */
diff --git a/src/SDL.c b/src/SDL.c
index e5c43fee134b0..1553f80bcfbff 100644
--- a/src/SDL.c
+++ b/src/SDL.c
@@ -46,6 +46,7 @@
 #include "joystick/SDL_gamepad_c.h"
 #include "joystick/SDL_joystick_c.h"
 #include "sensor/SDL_sensor_c.h"
+#include "camera/SDL_camera_c.h"
 
 #define SDL_INIT_EVERYTHING ~0U
 
@@ -365,6 +366,30 @@ int SDL_InitSubSystem(Uint32 flags)
 #endif
     }
 
+    /* Initialize the camera subsystem */
+    if (flags & SDL_INIT_CAMERA) {
+#ifndef SDL_CAMERA_DISABLED
+        if (SDL_ShouldInitSubsystem(SDL_INIT_CAMERA)) {
+            /* camera implies events */
+            if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
+                goto quit_and_error;
+            }
+
+            SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
+            if (SDL_CameraInit(NULL) < 0) {
+                SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
+                goto quit_and_error;
+            }
+        } else {
+            SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
+        }
+        flags_initialized |= SDL_INIT_CAMERA;
+#else
+        SDL_SetError("SDL not built with camera support");
+        goto quit_and_error;
+#endif
+    }
+
     (void)flags_initialized; /* make static analysis happy, since this only gets used in error cases. */
 
     return 0;
@@ -382,6 +407,18 @@ int SDL_Init(Uint32 flags)
 void SDL_QuitSubSystem(Uint32 flags)
 {
     /* Shut down requested initialized subsystems */
+
+#ifndef SDL_CAMERA_DISABLED
+    if (flags & SDL_INIT_CAMERA) {
+        if (SDL_ShouldQuitSubsystem(SDL_INIT_CAMERA)) {
+            SDL_QuitCamera();
+            /* camera implies events */
+            SDL_QuitSubSystem(SDL_INIT_EVENTS);
+        }
+        SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
+    }
+#endif
+
 #ifndef SDL_SENSOR_DISABLED
     if (flags & SDL_INIT_SENSOR) {
         if (SDL_ShouldQuitSubsystem(SDL_INIT_SENSOR)) {
diff --git a/src/camera/SDL_camera.c b/src/camera/SDL_camera.c
index d62854c9c3528..5a8c08f8c31df 100644
--- a/src/camera/SDL_camera.c
+++ b/src/camera/SDL_camera.c
@@ -25,7 +25,25 @@
 #include "../video/SDL_pixels_c.h"
 #include "../thread/SDL_systhread.h"
 
-#define DEBUG_CAMERA 1
+// Available camera drivers
+static const CameraBootStrap *const bootstrap[] = {
+#ifdef SDL_CAMERA_DRIVER_V4L2
+    &V4L2_bootstrap,
+#endif
+#ifdef SDL_CAMERA_DRIVER_COREMEDIA
+    &COREMEDIA_bootstrap,
+#endif
+#ifdef SDL_CAMERA_DRIVER_ANDROID
+    &ANDROIDCAMERA_bootstrap,
+#endif
+#ifdef SDL_CAMERA_DRIVER_DUMMY
+    &DUMMYCAMERA_bootstrap,
+#endif
+    NULL
+};
+
+static SDL_CameraDriver camera_driver;
+
 
 // list node entries to share frames between SDL and user app
 // !!! FIXME: do we need this struct?
@@ -36,6 +54,25 @@ typedef struct entry_t
 
 static SDL_CameraDevice *open_devices[16];  // !!! FIXME: remove limit
 
+int SDL_GetNumCameraDrivers(void)
+{
+    return SDL_arraysize(bootstrap) - 1;
+}
+
+const char *SDL_GetCameraDriver(int index)
+{
+    if (index >= 0 && index < SDL_GetNumCameraDrivers()) {
+        return bootstrap[index]->name;
+    }
+    return NULL;
+}
+
+const char *SDL_GetCurrentCameraDriver(void)
+{
+    return camera_driver.name;
+}
+
+
 static void CloseCameraDevice(SDL_CameraDevice *device)
 {
     if (!device) {
@@ -69,46 +106,18 @@ static void CloseCameraDevice(SDL_CameraDevice *device)
             SDL_CameraFrame f = entry->frame;
             // Release frames not acquired, if any
             if (f.timestampNS) {
-                ReleaseFrame(device, &f);
+                camera_driver.impl.ReleaseFrame(device, &f);
             }
             SDL_free(entry);
         }
     }
 
-    CloseDevice(device);
+    camera_driver.impl.CloseDevice(device);
 
     SDL_free(device->dev_name);
     SDL_free(device);
 }
 
-// Tell if all devices are closed
-SDL_bool CheckAllDeviceClosed(void)
-{
-    const int n = SDL_arraysize(open_devices);
-    int all_closed = SDL_TRUE;
-    for (int i = 0; i < n; i++) {
-        if (open_devices[i]) {
-            all_closed = SDL_FALSE;
-            break;
-        }
-    }
-    return all_closed;
-}
-
-// Tell if at least one device is in playing state
-SDL_bool CheckDevicePlaying(void)
-{
-    const int n = SDL_arraysize(open_devices);
-    for (int i = 0; i < n; i++) {
-        if (open_devices[i]) {
-            if (SDL_GetCameraStatus(open_devices[i]) == SDL_CAMERA_PLAYING) {
-                return SDL_TRUE;
-            }
-        }
-    }
-    return SDL_FALSE;
-}
-
 void SDL_CloseCamera(SDL_CameraDevice *device)
 {
     if (!device) {
@@ -128,7 +137,7 @@ int SDL_StartCamera(SDL_CameraDevice *device)
         return SDL_SetError("invalid state");
     }
 
-    const int result = StartCamera(device);
+    const int result = camera_driver.impl.StartCamera(device);
     if (result < 0) {
         return result;
     }
@@ -147,7 +156,7 @@ int SDL_GetCameraSpec(SDL_CameraDevice *device, SDL_CameraSpec *spec)
     }
 
     SDL_zerop(spec);
-    return GetDeviceSpec(device, spec);
+    return camera_driver.impl.GetDeviceSpec(device, spec);
 }
 
 int SDL_StopCamera(SDL_CameraDevice *device)
@@ -162,7 +171,7 @@ int SDL_StopCamera(SDL_CameraDevice *device)
     SDL_AtomicSet(&device->shutdown, 1);
 
     SDL_LockMutex(device->acquiring_lock);
-    const int retval = StopCamera(device);
+    const int retval = camera_driver.impl.StopCamera(device);
     SDL_UnlockMutex(device->acquiring_lock);
 
     return (retval < 0) ? -1 : 0;
@@ -254,14 +263,13 @@ const char *SDL_GetCameraDeviceName(SDL_CameraDeviceID instance_id)
         return NULL;
     }
 
-    if (GetCameraDeviceName(instance_id, buf, sizeof (buf)) < 0) {
+    if (camera_driver.impl.GetDeviceName(instance_id, buf, sizeof (buf)) < 0) {
         buf[0] = 0;
     }
 
     return buf;
 }
 
-
 SDL_CameraDeviceID *SDL_GetCameraDevices(int *count)
 {
     int dummycount = 0;
@@ -270,7 +278,7 @@ SDL_CameraDeviceID *SDL_GetCameraDevices(int *count)
     }
 
     int num = 0;
-    SDL_CameraDeviceID *retval = GetCameraDevices(&num);
+    SDL_CameraDeviceID *retval = camera_driver.impl.GetDevices(&num);
     if (retval) {
         *count = num;
         return retval;
@@ -279,7 +287,6 @@ SDL_CameraDeviceID *SDL_GetCameraDevices(int *count)
     // return list of 0 ID, null terminated
     retval = (SDL_CameraDeviceID *)SDL_calloc(1, sizeof(*retval));
     if (retval == NULL) {
-        SDL_OutOfMemory();
         *count = 0;
         return NULL;
     }
@@ -331,7 +338,7 @@ static int SDLCALL SDL_CameraThread(void *devicep)
         SDL_zero(f);
 
         SDL_LockMutex(device->acquiring_lock);
-        ret = AcquireFrame(device, &f);
+        ret = camera_driver.impl.AcquireFrame(device, &f);
         SDL_UnlockMutex(device->acquiring_lock);
 
         if (ret == 0) {
@@ -376,7 +383,6 @@ static int SDLCALL SDL_CameraThread(void *devicep)
     SDL_Log("dev[%p] End thread 'SDL_CameraThread' with error: %s", (void *)device, SDL_GetError());
 #endif
     SDL_AtomicSet(&device->shutdown, 1);
-    SDL_OutOfMemory();  // !!! FIXME: this error isn't accessible since the thread is about to terminate
     return 0;
 }
 
@@ -436,7 +442,6 @@ SDL_CameraDevice *SDL_OpenCamera(SDL_CameraDeviceID instance_id)
 
     device = (SDL_CameraDevice *) SDL_calloc(1, sizeof (SDL_CameraDevice));
     if (device == NULL) {
-        SDL_OutOfMemory();
         goto error;
     }
     device->dev_name = SDL_strdup(device_name);
@@ -456,7 +461,7 @@ SDL_CameraDevice *SDL_OpenCamera(SDL_CameraDeviceID instance_id)
         goto error;
     }
 
-    if (OpenDevice(device) < 0) {
+    if (camera_driver.impl.OpenDevice(device) < 0) {
         goto error;
     }
 
@@ -515,7 +520,7 @@ int SDL_SetCameraSpec(SDL_CameraDevice *device, const SDL_CameraSpec *desired, S
 
     device->spec = *obtained;
 
-    result = InitDevice(device);
+    result = camera_driver.impl.InitDevice(device);
     if (result < 0) {
         return result;
     }
@@ -541,7 +546,7 @@ int SDL_AcquireCameraFrame(SDL_CameraDevice *device, SDL_CameraFrame *frame)
         int ret;
 
         // Wait for a frame
-        while ((ret = AcquireFrame(device, frame)) == 0) {
+        while ((ret = camera_driver.impl.AcquireFrame(device, frame)) == 0) {
             if (frame->num_planes) {
                 return 0;
             }
@@ -576,7 +581,7 @@ int SDL_ReleaseCameraFrame(SDL_CameraDevice *device, SDL_CameraFrame *frame)
         return SDL_InvalidParamError("device");
     } else if (frame == NULL) {
         return SDL_InvalidParamError("frame");
-    } else if (ReleaseFrame(device, frame) < 0) {
+    } else if (camera_driver.impl.ReleaseFrame(device, frame) < 0) {
         return -1;
     }
 
@@ -589,7 +594,7 @@ int SDL_GetNumCameraFormats(SDL_CameraDevice *device)
     if (!device) {
         return SDL_InvalidParamError("device");
     }
-    return GetNumFormats(device);
+    return camera_driver.impl.GetNumFormats(device);
 }
 
 int SDL_GetCameraFormat(SDL_CameraDevice *device, int index, Uint32 *format)
@@ -600,7 +605,7 @@ int SDL_GetCameraFormat(SDL_CameraDevice *device, int index, Uint32 *format)
         return SDL_InvalidParamError("format");
     }
     *format = 0;
-    return GetFormat(device, index, format);
+    return camera_driver.impl.GetFormat(device, index, format);
 }
 
 int SDL_GetNumCameraFrameSizes(SDL_CameraDevice *device, Uint32 format)
@@ -608,7 +613,7 @@ int SDL_GetNumCameraFrameSizes(SDL_CameraDevice *device, Uint32 format)
     if (!device) {
         return SDL_InvalidParamError("device");
     }
-    return GetNumFrameSizes(device, format);
+    return camera_driver.impl.GetNumFrameSizes(device, format);
 }
 
 int SDL_GetCameraFrameSize(SDL_CameraDevice *device, Uint32 format, int index, int *width, int *height)
@@ -621,7 +626,7 @@ int SDL_GetCameraFrameSize(SDL_CameraDevice *device, Uint32 format, int index, i
         return SDL_InvalidParamError("height");
     }
     *width = *height = 0;
-    return GetFrameSize(device, format, index, width, height);
+    return camera_driver.impl.GetFrameSize(device, format, index, width, height);
 }
 
 SDL_CameraDevice *SDL_OpenCameraWithSpec(SDL_CameraDeviceID instance_id, const SDL_CameraSpec *desired, SDL_CameraSpec *obtained, int allowed_changes)
@@ -653,15 +658,35 @@ SDL_CameraStatus SDL_GetCameraStatus(SDL_CameraDevice *device)
     return SDL_CAMERA_INIT;
 }
 
-int SDL_CameraInit(void)
+static void CompleteCameraEntryPoints(void)
 {
-    SDL_zeroa(open_devices);
-    SDL_SYS_CameraInit();
-    return 0;
+    // this doesn't currently fill in stub implementations, it just asserts the backend filled them all in.
+    #define FILL_STUB(x) SDL_assert(camera_driver.impl.x != NULL)
+    FILL_STUB(DetectDevices);
+    FILL_STUB(OpenDevice);
+    FILL_STUB(CloseDevice);
+    FILL_STUB(InitDevice);
+    FILL_STUB(GetDeviceSpec);
+    FILL_STUB(StartCamera);
+    FILL_STUB(StopCamera);
+    FILL_STUB(AcquireFrame);
+    FILL_STUB(ReleaseFrame);
+    FILL_STUB(GetNumFormats);
+    FILL_STUB(GetFormat);
+    FILL_STUB(GetNumFrameSizes);
+    FILL_STUB(GetFrameSize);
+    FILL_STUB(GetDeviceName);
+    FILL_STUB(GetDevices);
+    FILL_STUB(Deinitialize);
+    #undef FILL_STUB
 }
 
 void SDL_QuitCamera(void)
 {
+    if (!camera_driver.name) {  // not initialized?!
+        return;
+    }
+
     const int n = SDL_arraysize(open_devices);
     for (int i = 0; i < n; i++) {
         CloseCameraDevice(open_devices[i]);
@@ -669,6 +694,112 @@ void SDL_QuitCamera(void)
 
     SDL_zeroa(open_devices);
 
-    SDL_SYS_CameraQuit();
+#if 0 // !!! FIXME
+    SDL_PendingCameraDeviceEvent *pending_events = camera_driver.pending_events.next;
+    camera_driver.pending_events.next = NULL;
+
+    SDL_PendingCameraDeviceEvent *pending_next = NULL;
+    for (SDL_PendingCameraDeviceEvent *i = pending_events; i; i = pending_next) {
+        pending_next = i->next;
+        SDL_free(i);
+    }
+#endif
+
+    // Free the driver data
+    camera_driver.impl.Deinitialize();
+
+    SDL_zero(camera_driver);
+}
+
+// this is 90% the same code as the audio subsystem uses.
+int SDL_CameraInit(const char *driver_name)
+{
+    if (SDL_GetCurrentCameraDriver()) {
+        SDL_QuitCamera(); // shutdown driver if already running.
+    }
+
+    SDL_zeroa(open_devices);
+
+    // Select the proper camera driver
+    if (!driver_name) {
+        driver_name = SDL_GetHint(SDL_HINT_CAMERA_DRIVER);
+    }
+
+    SDL_bool initialized = SDL_FALSE;
+    SDL_bool tried_to_init = SDL_FALSE;
+
+    if (driver_name && (*driver_name != 0)) {
+        char *driver_name_copy = SDL_strdup(driver_name);
+        const char *driver_attempt = driver_name_copy;
+
+        if (!driver_name_copy) {
+            return -1;
+        }
+
+        while (driver_attempt && (*driver_attempt != 0) && !initialized) {
+            char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
+            if (driver_attempt_end) {
+                *driver_attempt_end = '\0';
+            }
+
+            for (int i = 0; bootstrap[i]; i++) {
+                if (SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) {
+                    tried_to_init = SDL_TRUE;
+                    SDL_zero(camera_driver);
+                    #if 0  // !!! FIXME
+                    camera_driver.pending_events_tail = &camera_driver.pending_events;
+                    #endif
+                    if (bootstrap[i]->init(&camera_driver.impl)) {
+                        camera_driver.name = bootstrap[i]->name;
+                        camera_driver.desc = bootstrap[i]->desc;
+                        initialized = SDL_TRUE;
+                    }
+                    break;
+                }
+            }
+
+            driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
+        }
+
+        SDL_free(driver_name_copy);
+    } else {
+        for (int i = 0; !initialized && bootstrap[i]; i++) {
+            if (bootstrap[i]->demand_only) {
+                continue;
+            }
+
+            tried_to_init = SDL_TRUE;
+            SDL_zero(camera_driver);
+            #if 0  // !!! FIXME
+            camera_driver.pending_events_tail = &camera_driver.pending_events;
+            #endif
+            if (bootstrap[i]->init(&camera_driver.impl)) {
+                camera_driver.name = bootstrap[i]->name;
+                camera_driver.desc = bootstrap[i]->desc;
+                initialized = SDL_TRUE;
+            }
+        }
+    }
+
+    if (!initialized) {
+        // specific drivers will set the error message if they fail, but otherwise we do it here.
+        if (!tried_to_init) {
+            if (driver_name) {
+                SDL_SetError("Camera driver '%s' not available", driver_name);
+            } else {
+                SDL_SetError("No available camera driver");
+            }
+        }
+
+        SDL_zero(camera_driver);
+        return -1;  // No driver was available, so fail.
+    }
+
+    CompleteCameraEntryPoints();
+
+    // Make sure we have a list of devices available at startup...
+    camera_driver.impl.DetectDevices();
+
+    return 0;
 }
 
diff --git a/src/camera/SDL_camera_c.h b/src/camera/SDL_camera_c.h
index 787b5f2db5fce..56921ab8a0b25 100644
--- a/src/camera/SDL_camera_c.h
+++ b/src/camera/SDL_camera_c.h
@@ -24,7 +24,7 @@
 #define SDL_camera_c_h_
 
 // Initialize the camera subsystem
-int SDL_CameraInit(void);
+int SDL_CameraInit(const char *driver_name);
 
 // Shutdown the camera subsystem
 void SDL_QuitCamera(void);
diff --git a/src/camera/SDL_syscamera.h b/src/camera/SDL_syscamera.h
index 990272f8d0a82..6a7c56356760b 100644
--- a/src/camera/SDL_syscamera.h
+++ b/src/camera/SDL_syscamera.h
@@ -25,6 +25,8 @@
 
 #include "../SDL_list.h"
 
+#define DEBUG_CAMERA 1
+
 // The SDL camera driver
 typedef struct SDL_CameraDevice SDL_CameraDevice;
 
@@ -57,34 +59,45 @@ struct SDL_CameraDevice
     struct SDL_PrivateCameraData *hidden;
 };
 
-extern int SDL_SYS_CameraInit(void);
-extern int SDL_SYS_CameraQuit(void);
-
-// !!! FIXME: These names need to be made camera-specific.
-
-extern int OpenDevice(SDL_CameraDevice *_this);
-extern void CloseDevice(SDL_CameraDevice *_this);
-
-extern int InitDevice(SDL_CameraDevice *_this);
-
-extern int GetDeviceSpec(SDL_CameraDevice *_this, SDL_CameraSpec *spec);
-
-extern int StartCamera(SDL_CameraDevice *_this);
-extern int StopCamera(SDL_CameraDevice *_this);
-
-extern int AcquireFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame);
-extern int ReleaseFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame);
-
-extern int GetNumF

(Patch may be truncated, please check the link at the top of this post.)