SDL: camera: Added SDL_GetCameraDevicePosition.

From 70b89ab70d06257af009ed132acd19794a86c553 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 19 Feb 2024 14:19:57 -0500
Subject: [PATCH] camera: Added SDL_GetCameraDevicePosition.

Otherwise, as a property, you have to open each camera device to figure out
which ones are which.
---
 include/SDL3/SDL_camera.h                     | 44 ++++++++++++++-----
 src/camera/SDL_camera.c                       | 25 +++++++++--
 src/camera/SDL_syscamera.h                    |  7 ++-
 src/camera/android/SDL_camera_android.c       | 19 +++-----
 src/camera/coremedia/SDL_camera_coremedia.m   | 23 +++-------
 src/camera/emscripten/SDL_camera_emscripten.c |  2 +-
 .../SDL_camera_mediafoundation.c              |  2 +-
 src/camera/v4l2/SDL_camera_v4l2.c             |  2 +-
 8 files changed, 75 insertions(+), 49 deletions(-)

diff --git a/include/SDL3/SDL_camera.h b/include/SDL3/SDL_camera.h
index 57c41a130aa46..e4ac69a88ac14 100644
--- a/include/SDL3/SDL_camera.h
+++ b/include/SDL3/SDL_camera.h
@@ -70,6 +70,19 @@ typedef struct SDL_CameraSpec
     int interval_denominator;  /**< Frame rate demoninator ((dom / num) == fps, (num / dom) == duration) */
 } SDL_CameraSpec;
 
+/**
+ * The position of camera in relation to system device.
+ *
+ * \sa SDL_GetCameraDevicePosition
+ */
+typedef enum SDL_CameraPosition
+{
+    SDL_CAMERA_POSITION_UNKNOWN,
+    SDL_CAMERA_POSITION_FRONT_FACING,
+    SDL_CAMERA_POSITION_BACK_FACING
+} SDL_CameraPosition;
+
+
 /**
  * Use this function to get the number of built-in camera drivers.
  *
@@ -210,6 +223,25 @@ extern DECLSPEC SDL_CameraSpec *SDLCALL SDL_GetCameraDeviceSupportedFormats(SDL_
  */
 extern DECLSPEC char * SDLCALL SDL_GetCameraDeviceName(SDL_CameraDeviceID instance_id);
 
+/**
+ * Get the position of the camera in relation to the system.
+ *
+ * Most platforms will report UNKNOWN, but mobile devices, like phones, can
+ * often make a distiction between cameras on the front of the device (that
+ * points towards the user, for taking "selfies") and cameras on the back
+ * (for filming in the direction the user is facing).
+ *
+ * \param instance_id the camera device instance ID
+ * \returns The position of the camera on the system hardware.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameraDevices
+ */
+extern DECLSPEC SDL_CameraPosition SDLCALL SDL_GetCameraDevicePosition(SDL_CameraDeviceID instance_id);
+
 /**
  * Open a video capture device (a "camera").
  *
@@ -305,16 +337,6 @@ extern DECLSPEC SDL_CameraDeviceID SDLCALL SDL_GetCameraInstanceID(SDL_Camera *c
 /**
  * Get the properties associated with an opened camera.
  *
- * The following read-only properties are provided by SDL:
- *
- * - `SDL_PROP_CAMERA_POSITION_STRING`: the position of the camera in
- *   relation to the hardware it is connected to. This is currently either
- *   the string "front" or "back", to signify which side of the user's
- *   device a camera is on. Future versions of SDL may add other position
- *   strings. This property is only set if this information can be
- *   determined by SDL. Most platforms do not set this attribute at all,
- *   but mobile devices tend to.
- *
  * \param camera the SDL_Camera obtained from SDL_OpenCameraDevice()
  * \returns a valid property ID on success or 0 on failure; call
  *          SDL_GetError() for more information.
@@ -328,8 +350,6 @@ extern DECLSPEC SDL_CameraDeviceID SDLCALL SDL_GetCameraInstanceID(SDL_Camera *c
  */
 extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetCameraProperties(SDL_Camera *camera);
 
-#define SDL_PROP_CAMERA_POSITION_STRING "SDL.camera.position"
-
 /**
  * Get the spec that a camera is using when generating images.
  *
diff --git a/src/camera/SDL_camera.c b/src/camera/SDL_camera.c
index f70cd75f552a9..8710be864183b 100644
--- a/src/camera/SDL_camera.c
+++ b/src/camera/SDL_camera.c
@@ -398,9 +398,8 @@ static int SDLCALL CameraSpecCmp(const void *vpa, const void *vpb)
     return 0;  // apparently, they're equal.
 }
 
-
 // The camera backends call this when a new device is plugged in.
-SDL_CameraDevice *SDL_AddCameraDevice(const char *name, int num_specs, const SDL_CameraSpec *specs, void *handle)
+SDL_CameraDevice *SDL_AddCameraDevice(const char *name, SDL_CameraPosition position, int num_specs, const SDL_CameraSpec *specs, void *handle)
 {
     SDL_assert(name != NULL);
     SDL_assert(num_specs >= 0);
@@ -425,6 +424,8 @@ SDL_CameraDevice *SDL_AddCameraDevice(const char *name, int num_specs, const SDL
         return NULL;
     }
 
+    device->position = position;
+
     device->lock = SDL_CreateMutex();
     if (!device->lock) {
         SDL_free(device->name);
@@ -457,7 +458,13 @@ SDL_CameraDevice *SDL_AddCameraDevice(const char *name, int num_specs, const SDL
     }
 
     #if DEBUG_CAMERA
-    SDL_Log("CAMERA: Adding device ('%s') with %d spec%s%s", name, num_specs, (num_specs == 1) ? "" : "s", (num_specs == 0) ? "" : ":");
+    const char *posstr = "unknown position";
+    if (position == SDL_CAMERA_POSITION_FRONT_FACING) {
+        posstr = "front-facing";
+    } else if (position == SDL_CAMERA_POSITION_BACK_FACING) {
+        posstr = "back-facing";
+    }
+    SDL_Log("CAMERA: Adding device '%s' (%s) with %d spec%s%s", name, posstr, num_specs, (num_specs == 1) ? "" : "s", (num_specs == 0) ? "" : ":");
     for (int i = 0; i < num_specs; i++) {
         const SDL_CameraSpec *spec = &device->all_specs[i];
         SDL_Log("CAMERA:   - fmt=%s, w=%d, h=%d, numerator=%d, denominator=%d", SDL_GetPixelFormatName(spec->format), spec->width, spec->height, spec->interval_numerator, spec->interval_denominator);
@@ -658,6 +665,18 @@ char *SDL_GetCameraDeviceName(SDL_CameraDeviceID instance_id)
     return retval;
 }
 
+SDL_CameraPosition SDL_GetCameraDevicePosition(SDL_CameraDeviceID instance_id)
+{
+    SDL_CameraPosition retval = SDL_CAMERA_POSITION_UNKNOWN;
+    SDL_CameraDevice *device = ObtainPhysicalCameraDevice(instance_id);
+    if (device) {
+        retval = device->position;
+        ReleaseCameraDevice(device);
+    }
+    return retval;
+}
+
+
 SDL_CameraDeviceID *SDL_GetCameraDevices(int *count)
 {
     int dummy_count;
diff --git a/src/camera/SDL_syscamera.h b/src/camera/SDL_syscamera.h
index 82032a563e79d..1ff5271089d9d 100644
--- a/src/camera/SDL_syscamera.h
+++ b/src/camera/SDL_syscamera.h
@@ -25,14 +25,14 @@
 
 #include "../SDL_hashtable.h"
 
-#define DEBUG_CAMERA 0
+#define DEBUG_CAMERA 1
 
 typedef struct SDL_CameraDevice SDL_CameraDevice;
 
 /* Backends should call this as devices are added to the system (such as
    a USB camera being plugged in), and should also be called for
    for every device found during DetectDevices(). */
-extern SDL_CameraDevice *SDL_AddCameraDevice(const char *name, int num_specs, const SDL_CameraSpec *specs, void *handle);
+extern SDL_CameraDevice *SDL_AddCameraDevice(const char *name, SDL_CameraPosition position, int num_specs, const SDL_CameraSpec *specs, void *handle);
 
 /* Backends should call this if an opened camera device is lost.
    This can happen due to i/o errors, or a device being unplugged, etc. */
@@ -82,6 +82,9 @@ struct SDL_CameraDevice
     // Human-readable device name.
     char *name;
 
+    // Position of camera (front-facing, back-facing, etc).
+    SDL_CameraPosition position;
+
     // When refcount hits zero, we destroy the device object.
     SDL_AtomicInt refcount;
 
diff --git a/src/camera/android/SDL_camera_android.c b/src/camera/android/SDL_camera_android.c
index 5da1cc0cbe5bc..621f7c7dcaf81 100644
--- a/src/camera/android/SDL_camera_android.c
+++ b/src/camera/android/SDL_camera_android.c
@@ -575,7 +575,7 @@ static void ANDROIDCAMERA_FreeDeviceHandle(SDL_CameraDevice *device)
     }
 }
 
-static void GatherCameraSpecs(const char *devid, CameraFormatAddData *add_data, char **fullname, const char **posstr)
+static void GatherCameraSpecs(const char *devid, CameraFormatAddData *add_data, char **fullname, SDL_CameraPosition *position)
 {
     SDL_zerop(add_data);
 
@@ -608,16 +608,15 @@ static void GatherCameraSpecs(const char *devid, CameraFormatAddData *add_data,
         }
     }
 
-    *posstr = NULL;
     ACameraMetadata_const_entry posentry;
     if (pACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &posentry) == ACAMERA_OK) {  // ignore this if it fails.
         if (*posentry.data.u8 == ACAMERA_LENS_FACING_FRONT) {
-            *posstr = "front";
+            *position = SDL_CAMERA_POSITION_FRONT_FACING;
             if (!*fullname) {
                 *fullname = SDL_strdup("Front-facing camera");
             }
         } else if (*posentry.data.u8 == ACAMERA_LENS_FACING_BACK) {
-            *posstr = "back";
+            *position = SDL_CAMERA_POSITION_BACK_FACING;
             if (!*fullname) {
                 *fullname = SDL_strdup("Back-facing camera");
             }
@@ -680,22 +679,16 @@ static void MaybeAddDevice(const char *devid)
         return;  // already have this one.
     }
 
-    const char *posstr = NULL;
+    SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
     char *fullname = NULL;
     CameraFormatAddData add_data;
-    GatherCameraSpecs(devid, &add_data, &fullname, &posstr);
+    GatherCameraSpecs(devid, &add_data, &fullname, &position);
     if (add_data.num_specs > 0) {
         char *namecpy = SDL_strdup(devid);
         if (namecpy) {
-            SDL_CameraDevice *device = SDL_AddCameraDevice(fullname, add_data.num_specs, add_data.specs, namecpy);
+            SDL_CameraDevice *device = SDL_AddCameraDevice(fullname, position, add_data.num_specs, add_data.specs, namecpy);
             if (!device) {
                 SDL_free(namecpy);
-            } else if (device && posstr) {
-                SDL_Camera *camera = (SDL_Camera *) device;  // currently there's no separation between physical and logical device.
-                SDL_PropertiesID props = SDL_GetCameraProperties(camera);
-                if (props) {
-                    SDL_SetStringProperty(props, SDL_PROP_CAMERA_POSITION_STRING, posstr);
-                }
             }
         }
     }
diff --git a/src/camera/coremedia/SDL_camera_coremedia.m b/src/camera/coremedia/SDL_camera_coremedia.m
index 4d2e40f351f1f..049c4a4065d13 100644
--- a/src/camera/coremedia/SDL_camera_coremedia.m
+++ b/src/camera/coremedia/SDL_camera_coremedia.m
@@ -399,24 +399,15 @@ static void MaybeAddDevice(AVCaptureDevice *avdevice)
     CameraFormatAddData add_data;
     GatherCameraSpecs(avdevice, &add_data);
     if (add_data.num_specs > 0) {
-        SDL_CameraDevice *device = SDL_AddCameraDevice(avdevice.localizedName.UTF8String, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(avdevice));
-        if (device) {
-            const char *posstr = NULL;
-            if (avdevice.position == AVCaptureDevicePositionFront) {
-                posstr = "front";
-            } else if (avdevice.position == AVCaptureDevicePositionBack) {
-                posstr = "back";
-            }
-
-            if (posstr) {
-                SDL_Camera *camera = (SDL_Camera *) device;  // currently there's no separation between physical and logical device.
-                SDL_PropertiesID props = SDL_GetCameraProperties(camera);
-                if (props) {
-                    SDL_SetStringProperty(props, SDL_PROP_CAMERA_POSITION_STRING, posstr);
-                }
-            }
+        SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
+        if (avdevice.position == AVCaptureDevicePositionFront) {
+            position = SDL_CAMERA_POSITION_FRONT_FACING;
+        } else if (avdevice.position == AVCaptureDevicePositionBack) {
+            position = SDL_CAMERA_POSITION_BACK_FACING;
         }
+        SDL_AddCameraDevice(avdevice.localizedName.UTF8String, position, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(avdevice));
     }
+
     SDL_free(add_data.specs);
 }
 
diff --git a/src/camera/emscripten/SDL_camera_emscripten.c b/src/camera/emscripten/SDL_camera_emscripten.c
index 45be949692181..9a605a7527904 100644
--- a/src/camera/emscripten/SDL_camera_emscripten.c
+++ b/src/camera/emscripten/SDL_camera_emscripten.c
@@ -228,7 +228,7 @@ static void EMSCRIPTENCAMERA_DetectDevices(void)
     //  will pop up a user permission dialog warning them we're trying to access the camera, and we generally
     //  don't want that during SDL_Init().
     if (supported) {
-        SDL_AddCameraDevice("Web browser's camera", 0, NULL, (void *) (size_t) 0x1);
+        SDL_AddCameraDevice("Web browser's camera", SDL_CAMERA_POSITION_UNKNOWN, 0, NULL, (void *) (size_t) 0x1);
     }
 }
 
diff --git a/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/src/camera/mediafoundation/SDL_camera_mediafoundation.c
index d2d79f94cc018..dfbfc57996f82 100644
--- a/src/camera/mediafoundation/SDL_camera_mediafoundation.c
+++ b/src/camera/mediafoundation/SDL_camera_mediafoundation.c
@@ -777,7 +777,7 @@ static void MaybeAddDevice(IMFActivate *activation)
             CameraFormatAddData add_data;
             GatherCameraSpecs(source, &add_data);
             if (add_data.num_specs > 0) {
-                SDL_AddCameraDevice(name, add_data.num_specs, add_data.specs, symlink);
+                SDL_AddCameraDevice(name, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, symlink);
             }
             SDL_free(add_data.specs);
             IMFActivate_ShutdownObject(activation);
diff --git a/src/camera/v4l2/SDL_camera_v4l2.c b/src/camera/v4l2/SDL_camera_v4l2.c
index 0e2915910fbff..4a94b2563b04f 100644
--- a/src/camera/v4l2/SDL_camera_v4l2.c
+++ b/src/camera/v4l2/SDL_camera_v4l2.c
@@ -778,7 +778,7 @@ static void MaybeAddDevice(const char *path)
             if (handle->path) {
                 handle->bus_info = SDL_strdup((char *)vcap.bus_info);
                 if (handle->bus_info) {
-                    if (SDL_AddCameraDevice((const char *) vcap.card, add_data.num_specs, add_data.specs, handle)) {
+                    if (SDL_AddCameraDevice((const char *) vcap.card, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, handle)) {
                         SDL_free(add_data.specs);
                         return;  // good to go.
                     }