SDL: camera: Rewrote Android support.

From 2613e3da24a9c083286d67bb4462531772c215ef Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sun, 18 Feb 2024 00:50:32 -0500
Subject: [PATCH] camera: Rewrote Android support.

This does something a little weird, in that it doesn't care what
`__ANDROID_API__` is set to, but will attempt to dlopen the system
libraries, like we do for many other platform-specific pieces of SDL.

This allows us to a) not bump the minimum required Android version, which is
extremely ancient but otherwise still working, doing the right thing on old
and new hardware in the field, and b) not require the app to link against
more libraries than it previously did before the feature was available.

The downside is that it's a little messy, but it's okay for now, I think.
---
 Android.mk                                    |    3 +
 .../app/src/main/AndroidManifest.xml          |    7 +
 .../build_config/SDL_build_config_android.h   |    1 +
 src/camera/SDL_syscamera.h                    |   10 +-
 src/camera/android/SDL_camera_android.c       | 1121 ++++++++++-------
 5 files changed, 665 insertions(+), 477 deletions(-)

diff --git a/Android.mk b/Android.mk
index 9c15c72c50a12..2758331744ddb 100644
--- a/Android.mk
+++ b/Android.mk
@@ -24,6 +24,9 @@ LOCAL_SRC_FILES := \
 	$(wildcard $(LOCAL_PATH)/src/audio/openslES/*.c) \
 	$(LOCAL_PATH)/src/atomic/SDL_atomic.c.arm \
 	$(LOCAL_PATH)/src/atomic/SDL_spinlock.c.arm \
+	$(wildcard $(LOCAL_PATH)/src/camera/*.c) \
+	$(wildcard $(LOCAL_PATH)/src/camera/android/*.c) \
+	$(wildcard $(LOCAL_PATH)/src/camera/dummy/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/core/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/core/android/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/cpuinfo/*.c) \
diff --git a/android-project/app/src/main/AndroidManifest.xml b/android-project/app/src/main/AndroidManifest.xml
index 8617dca9e8ed0..9b0a816d2b9d9 100644
--- a/android-project/app/src/main/AndroidManifest.xml
+++ b/android-project/app/src/main/AndroidManifest.xml
@@ -37,6 +37,13 @@
         android:name="android.hardware.microphone"
         android:required="false" /> -->
 
+    <!-- Camera support -->
+    <!-- if you want to record video, uncomment this. -->
+    <!--
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-feature android:name="android.hardware.camera" />
+    -->
+
     <!-- Allow downloading to the external storage on Android 5.1 and older -->
     <!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
 
diff --git a/include/build_config/SDL_build_config_android.h b/include/build_config/SDL_build_config_android.h
index 9ae2ca4d95bbe..f279a40c12698 100644
--- a/include/build_config/SDL_build_config_android.h
+++ b/include/build_config/SDL_build_config_android.h
@@ -192,5 +192,6 @@
 
 /* Enable the camera driver */
 #define SDL_CAMERA_DRIVER_ANDROID 1
+#define SDL_CAMERA_DRIVER_DUMMY 1
 
 #endif /* SDL_build_config_android_h_ */
diff --git a/src/camera/SDL_syscamera.h b/src/camera/SDL_syscamera.h
index 9f556523d109a..fc55b071be3fd 100644
--- a/src/camera/SDL_syscamera.h
+++ b/src/camera/SDL_syscamera.h
@@ -27,12 +27,6 @@
 
 #define DEBUG_CAMERA 0
 
-
-// !!! FIXME: update this driver!
-#ifdef SDL_CAMERA_DRIVER_ANDROID
-#undef SDL_CAMERA_DRIVER_ANDROID
-#endif
-
 typedef struct SDL_CameraDevice SDL_CameraDevice;
 
 /* Backends should call this as devices are added to the system (such as
@@ -53,6 +47,10 @@ extern void SDL_CameraDevicePermissionOutcome(SDL_CameraDevice *device, SDL_bool
 // Backends can call this to get a standardized name for a thread to power a specific camera device.
 extern char *SDL_GetCameraThreadName(SDL_CameraDevice *device, char *buf, size_t buflen);
 
+// Backends can call these to change a device's refcount.
+extern void RefPhysicalCameraDevice(SDL_CameraDevice *device);
+extern void UnrefPhysicalCameraDevice(SDL_CameraDevice *device);
+
 // These functions are the heart of the camera threads. Backends can call them directly if they aren't using the SDL-provided thread.
 extern void SDL_CameraThreadSetup(SDL_CameraDevice *device);
 extern SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device);
diff --git a/src/camera/android/SDL_camera_android.c b/src/camera/android/SDL_camera_android.c
index 20aeb06f106bd..5da1cc0cbe5bc 100644
--- a/src/camera/android/SDL_camera_android.c
+++ b/src/camera/android/SDL_camera_android.c
@@ -25,32 +25,34 @@
 #include "../../video/SDL_pixels_c.h"
 #include "../../thread/SDL_systhread.h"
 
-#if defined(SDL_CAMERA_DRIVER_ANDROID)
-
-#if __ANDROID_API__ >= 24
+#ifdef SDL_CAMERA_DRIVER_ANDROID
 
 /*
- * APP_PLATFORM=android-24
- * minSdkVersion=24
- *
- * link with: -lcamera2ndk -lmediandk
- *
  * AndroidManifest.xml:
  *   <uses-permission android:name="android.permission.CAMERA"></uses-permission>
  *   <uses-feature android:name="android.hardware.camera" />
  *
- *
- * Add: #define SDL_CAMERA 1
- * in:  include/build_config/SDL_build_config_android.h
- *
- *
  * Very likely SDL must be build with YUV support (done by default)
  *
  * https://developer.android.com/reference/android/hardware/camera2/CameraManager
  * "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
- * before configuring sessions on any of the camera devices.  * "
+ * before configuring sessions on any of the camera devices."
  */
 
+// this is kinda gross, but on older NDK headers all the camera stuff is
+//  gated behind __ANDROID_API__. We'll dlopen() it at runtime, so we'll do
+//  the right thing on pre-Android 7.0 devices, but we still
+//  need the struct declarations and such in those headers.
+// The other option is to make a massive jump in minimum Android version we
+//  support--going from ancient to merely really old--but this seems less
+//  distasteful and using dlopen matches practices on other SDL platforms.
+//  We'll see if it works out.
+#if __ANDROID_API__ < 24
+#undef __ANDROID_API__
+#define __ANDROID_API__ 24
+#endif
+
+#include <dlfcn.h>
 #include <camera/NdkCameraDevice.h>
 #include <camera/NdkCameraManager.h>
 #include <media/NdkImage.h>
@@ -58,87 +60,195 @@
 
 #include "../../core/android/SDL_android.h"
 
+static void *libcamera2ndk = NULL;
+typedef ACameraManager* (*pfnACameraManager_create)(void);
+typedef camera_status_t (*pfnACameraManager_registerAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
+typedef camera_status_t (*pfnACameraManager_unregisterAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
+typedef camera_status_t (*pfnACameraManager_getCameraIdList)(ACameraManager*, ACameraIdList**);
+typedef void (*pfnACameraManager_deleteCameraIdList)(ACameraIdList*);
+typedef void (*pfnACameraCaptureSession_close)(ACameraCaptureSession*);
+typedef void (*pfnACaptureRequest_free)(ACaptureRequest*);
+typedef void (*pfnACameraOutputTarget_free)(ACameraOutputTarget*);
+typedef camera_status_t (*pfnACameraDevice_close)(ACameraDevice*);
+typedef void (*pfnACameraManager_delete)(ACameraManager*);
+typedef void (*pfnACaptureSessionOutputContainer_free)(ACaptureSessionOutputContainer*);
+typedef void (*pfnACaptureSessionOutput_free)(ACaptureSessionOutput*);
+typedef camera_status_t (*pfnACameraManager_openCamera)(ACameraManager*, const char*, ACameraDevice_StateCallbacks*, ACameraDevice**);
+typedef camera_status_t (*pfnACameraDevice_createCaptureRequest)(const ACameraDevice*, ACameraDevice_request_template, ACaptureRequest**);
+typedef camera_status_t (*pfnACameraDevice_createCaptureSession)(ACameraDevice*, const ACaptureSessionOutputContainer*, const ACameraCaptureSession_stateCallbacks*,ACameraCaptureSession**);
+typedef camera_status_t (*pfnACameraManager_getCameraCharacteristics)(ACameraManager*, const char*, ACameraMetadata**);
+typedef void (*pfnACameraMetadata_free)(ACameraMetadata*);
+typedef camera_status_t (*pfnACameraMetadata_getConstEntry)(const ACameraMetadata*, uint32_t tag, ACameraMetadata_const_entry*);
+typedef camera_status_t (*pfnACameraCaptureSession_setRepeatingRequest)(ACameraCaptureSession*, ACameraCaptureSession_captureCallbacks*, int numRequests, ACaptureRequest**, int*);
+typedef camera_status_t (*pfnACameraOutputTarget_create)(ACameraWindowType*,ACameraOutputTarget**);
+typedef camera_status_t (*pfnACaptureRequest_addTarget)(ACaptureRequest*, const ACameraOutputTarget*);
+typedef camera_status_t (*pfnACaptureSessionOutputContainer_add)(ACaptureSessionOutputContainer*, const ACaptureSessionOutput*);
+typedef camera_status_t (*pfnACaptureSessionOutputContainer_create)(ACaptureSessionOutputContainer**);
+typedef camera_status_t (*pfnACaptureSessionOutput_create)(ACameraWindowType*, ACaptureSessionOutput**);
+static pfnACameraManager_create pACameraManager_create = NULL;
+static pfnACameraManager_registerAvailabilityCallback pACameraManager_registerAvailabilityCallback = NULL;
+static pfnACameraManager_unregisterAvailabilityCallback pACameraManager_unregisterAvailabilityCallback = NULL;
+static pfnACameraManager_getCameraIdList pACameraManager_getCameraIdList = NULL;
+static pfnACameraManager_deleteCameraIdList pACameraManager_deleteCameraIdList = NULL;
+static pfnACameraCaptureSession_close pACameraCaptureSession_close = NULL;
+static pfnACaptureRequest_free pACaptureRequest_free = NULL;
+static pfnACameraOutputTarget_free pACameraOutputTarget_free = NULL;
+static pfnACameraDevice_close pACameraDevice_close = NULL;
+static pfnACameraManager_delete pACameraManager_delete = NULL;
+static pfnACaptureSessionOutputContainer_free pACaptureSessionOutputContainer_free = NULL;
+static pfnACaptureSessionOutput_free pACaptureSessionOutput_free = NULL;
+static pfnACameraManager_openCamera pACameraManager_openCamera = NULL;
+static pfnACameraDevice_createCaptureRequest pACameraDevice_createCaptureRequest = NULL;
+static pfnACameraDevice_createCaptureSession pACameraDevice_createCaptureSession = NULL;
+static pfnACameraManager_getCameraCharacteristics pACameraManager_getCameraCharacteristics = NULL;
+static pfnACameraMetadata_free pACameraMetadata_free = NULL;
+static pfnACameraMetadata_getConstEntry pACameraMetadata_getConstEntry = NULL;
+static pfnACameraCaptureSession_setRepeatingRequest pACameraCaptureSession_setRepeatingRequest = NULL;
+static pfnACameraOutputTarget_create pACameraOutputTarget_create = NULL;
+static pfnACaptureRequest_addTarget pACaptureRequest_addTarget = NULL;
+static pfnACaptureSessionOutputContainer_add pACaptureSessionOutputContainer_add = NULL;
+static pfnACaptureSessionOutputContainer_create pACaptureSessionOutputContainer_create = NULL;
+static pfnACaptureSessionOutput_create pACaptureSessionOutput_create = NULL;
+
+static void *libmediandk = NULL;
+typedef void (*pfnAImage_delete)(AImage*);
+typedef media_status_t (*pfnAImage_getTimestamp)(const AImage*, int64_t*);
+typedef media_status_t (*pfnAImage_getNumberOfPlanes)(const AImage*, int32_t*);
+typedef media_status_t (*pfnAImage_getPlaneRowStride)(const AImage*, int, int32_t*);
+typedef media_status_t (*pfnAImage_getPlaneData)(const AImage*, int, uint8_t**, int*);
+typedef media_status_t (*pfnAImageReader_acquireNextImage)(AImageReader*, AImage**);
+typedef void (*pfnAImageReader_delete)(AImageReader*);
+typedef media_status_t (*pfnAImageReader_setImageListener)(AImageReader*, AImageReader_ImageListener*);
+typedef media_status_t (*pfnAImageReader_getWindow)(AImageReader*, ANativeWindow**);
+typedef media_status_t (*pfnAImageReader_new)(int32_t, int32_t, int32_t, int32_t, AImageReader**);
+static pfnAImage_delete pAImage_delete = NULL;
+static pfnAImage_getTimestamp pAImage_getTimestamp = NULL;
+static pfnAImage_getNumberOfPlanes pAImage_getNumberOfPlanes = NULL;
+static pfnAImage_getPlaneRowStride pAImage_getPlaneRowStride = NULL;
+static pfnAImage_getPlaneData pAImage_getPlaneData = NULL;
+static pfnAImageReader_acquireNextImage pAImageReader_acquireNextImage = NULL;
+static pfnAImageReader_delete pAImageReader_delete = NULL;
+static pfnAImageReader_setImageListener pAImageReader_setImageListener = NULL;
+static pfnAImageReader_getWindow pAImageReader_getWindow = NULL;
+static pfnAImageReader_new pAImageReader_new = NULL;
+
+typedef media_status_t (*pfnAImage_getWidth)(const AImage*, int32_t*);
+typedef media_status_t (*pfnAImage_getHeight)(const AImage*, int32_t*);
+static pfnAImage_getWidth pAImage_getWidth = NULL;
+static pfnAImage_getHeight pAImage_getHeight = NULL;
 
-static ACameraManager *cameraMgr = NULL;
-static ACameraIdList *cameraIdList = NULL;
+struct SDL_PrivateCameraData
+{
+    ACameraDevice *device;
+    AImageReader *reader;
+    ANativeWindow *window;
+    ACaptureSessionOutput *sessionOutput;
+    ACaptureSessionOutputContainer *sessionOutputContainer;
+    ACameraOutputTarget *outputTarget;
+    ACaptureRequest *request;
+    ACameraCaptureSession *session;
+    SDL_CameraSpec requested_spec;
+};
 
-static int CreateCameraManager(void)
+static int SetErrorStr(const char *what, const char *errstr, const int rc)
 {
-    if (cameraMgr == NULL) {
-        #if 0  // !!! FIXME: this is getting replaced in a different branch.
-        if (!Android_JNI_RequestPermission("android.permission.CAMERA")) {
-            SDL_SetError("This app doesn't have CAMERA permission");
-            return;
-        }
-        #endif
-        cameraMgr = ACameraManager_create();
-        if (cameraMgr == NULL) {
-            SDL_Log("Error creating ACameraManager");
-        } else {
-            SDL_Log("Create ACameraManager");
-        }
+    char errbuf[128];
+    if (!errstr) {
+        SDL_snprintf(errbuf, sizeof (errbuf), "Unknown error #%d", rc);
+        errstr = errbuf;
     }
+    return SDL_SetError("%s: %s", what, errstr);
+}
 
-    cameraMgr = ACameraManager_create();
-
-    return cameraMgr ? 0 : SDL_SetError("Error creating ACameraManager");
+static const char *CameraStatusStr(const camera_status_t rc)
+{
+    switch (rc) {
+        case ACAMERA_OK: return "no error";
+        case ACAMERA_ERROR_UNKNOWN: return "unknown error";
+        case ACAMERA_ERROR_INVALID_PARAMETER: return "invalid parameter";
+        case ACAMERA_ERROR_CAMERA_DISCONNECTED: return "camera disconnected";
+        case ACAMERA_ERROR_NOT_ENOUGH_MEMORY: return "not enough memory";
+        case ACAMERA_ERROR_METADATA_NOT_FOUND: return "metadata not found";
+        case ACAMERA_ERROR_CAMERA_DEVICE: return "camera device error";
+        case ACAMERA_ERROR_CAMERA_SERVICE: return "camera service error";
+        case ACAMERA_ERROR_SESSION_CLOSED: return "session closed";
+        case ACAMERA_ERROR_INVALID_OPERATION: return "invalid operation";
+        case ACAMERA_ERROR_STREAM_CONFIGURE_FAIL: return "configure failure";
+        case ACAMERA_ERROR_CAMERA_IN_USE: return "camera in use";
+        case ACAMERA_ERROR_MAX_CAMERA_IN_USE: return "max cameras in use";
+        case ACAMERA_ERROR_CAMERA_DISABLED: return "camera disabled";
+        case ACAMERA_ERROR_PERMISSION_DENIED: return "permission denied";
+        case ACAMERA_ERROR_UNSUPPORTED_OPERATION: return "unsupported operation";
+        default: break;
+    }
+
+    return NULL;  // unknown error
 }
 
-static void DestroyCameraManager(void)
+static int SetCameraError(const char *what, const camera_status_t rc)
 {
-    if (cameraIdList) {
-        ACameraManager_deleteCameraIdList(cameraIdList);
-        cameraIdList = NULL;
-    }
+    return SetErrorStr(what, CameraStatusStr(rc), (int) rc);
+}
 
-    if (cameraMgr) {
-        ACameraManager_delete(cameraMgr);
-        cameraMgr = NULL;
-    }
+static const char *MediaStatusStr(const media_status_t rc)
+{
+    switch (rc) {
+        case AMEDIA_OK: return "no error";
+        case AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE: return "insuffient resources";
+        case AMEDIACODEC_ERROR_RECLAIMED: return "reclaimed";
+        case AMEDIA_ERROR_UNKNOWN: return "unknown error";
+        case AMEDIA_ERROR_MALFORMED: return "malformed";
+        case AMEDIA_ERROR_UNSUPPORTED: return "unsupported";
+        case AMEDIA_ERROR_INVALID_OBJECT: return "invalid object";
+        case AMEDIA_ERROR_INVALID_PARAMETER: return "invalid parameter";
+        case AMEDIA_ERROR_INVALID_OPERATION: return "invalid operation";
+        case AMEDIA_ERROR_END_OF_STREAM: return "end of stream";
+        case AMEDIA_ERROR_IO: return "i/o error";
+        case AMEDIA_ERROR_WOULD_BLOCK: return "operation would block";
+        case AMEDIA_DRM_NOT_PROVISIONED: return "DRM not provisioned";
+        case AMEDIA_DRM_RESOURCE_BUSY: return "DRM resource busy";
+        case AMEDIA_DRM_DEVICE_REVOKED: return "DRM device revoked";
+        case AMEDIA_DRM_SHORT_BUFFER: return "DRM short buffer";
+        case AMEDIA_DRM_SESSION_NOT_OPENED: return "DRM session not opened";
+        case AMEDIA_DRM_TAMPER_DETECTED: return "DRM tampering detected";
+        case AMEDIA_DRM_VERIFY_FAILED: return "DRM verify failed";
+        case AMEDIA_DRM_NEED_KEY: return "DRM need key";
+        case AMEDIA_DRM_LICENSE_EXPIRED: return "DRM license expired";
+        case AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE: return "no buffer available";
+        case AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED: return "maximum images acquired";
+        case AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE: return "cannot lock image";
+        case AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE: return "cannot unlock image";
+        case AMEDIA_IMGREADER_IMAGE_NOT_LOCKED: return "image not locked";
+        default: break;
+    }
+
+    return NULL;  // unknown error
 }
 
-struct SDL_PrivateCameraData
+static int SetMediaError(const char *what, const media_status_t rc)
 {
-    ACameraDevice *device;
-    ACameraCaptureSession *session;
-    ACameraDevice_StateCallbacks dev_callbacks;
-    ACameraCaptureSession_stateCallbacks capture_callbacks;
-    ACaptureSessionOutputContainer *sessionOutputContainer;
-    AImageReader *reader;
-    int num_formats;
-    int count_formats[6]; // see format_to_id
-};
+    return SetErrorStr(what, MediaStatusStr(rc), (int) rc);
+}
 
 
-#define FORMAT_SDL SDL_PIXELFORMAT_NV12
+static ACameraManager *cameraMgr = NULL;
 
-static int format_to_id(int fmt) {
-     switch (fmt) {
-        #define CASE(x, y)  case x: return y
-        CASE(FORMAT_SDL, 0);
-        CASE(SDL_PIXELFORMAT_RGB565, 1);
-        CASE(SDL_PIXELFORMAT_XRGB8888, 2);
-        CASE(SDL_PIXELFORMAT_RGBA8888, 3);
-        CASE(SDL_PIXELFORMAT_RGBX8888, 4);
-        CASE(SDL_PIXELFORMAT_UNKNOWN, 5);
-        #undef CASE
-        default:
-            return 5;
+static int CreateCameraManager(void)
+{
+    SDL_assert(cameraMgr == NULL);
+
+    cameraMgr = pACameraManager_create();
+    if (!cameraMgr) {
+        return SDL_SetError("Error creating ACameraManager");
     }
+    return 0;
 }
 
-static int id_to_format(int fmt) {
-     switch (fmt) {
-        #define CASE(x, y)  case y: return x
-        CASE(FORMAT_SDL, 0);
-        CASE(SDL_PIXELFORMAT_RGB565, 1);
-        CASE(SDL_PIXELFORMAT_XRGB8888, 2);
-        CASE(SDL_PIXELFORMAT_RGBA8888, 3);
-        CASE(SDL_PIXELFORMAT_RGBX8888, 4);
-        CASE(SDL_PIXELFORMAT_UNKNOWN, 5);
-        #undef CASE
-        default:
-            return SDL_PIXELFORMAT_UNKNOWN;
+static void DestroyCameraManager(void)
+{
+    if (cameraMgr) {
+        pACameraManager_delete(cameraMgr);
+        cameraMgr = NULL;
     }
 }
 
@@ -146,27 +256,30 @@ static Uint32 format_android_to_sdl(Uint32 fmt)
 {
     switch (fmt) {
         #define CASE(x, y)  case x: return y
-        CASE(AIMAGE_FORMAT_YUV_420_888, FORMAT_SDL);
+        CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12);
         CASE(AIMAGE_FORMAT_RGB_565,     SDL_PIXELFORMAT_RGB565);
         CASE(AIMAGE_FORMAT_RGB_888,     SDL_PIXELFORMAT_XRGB8888);
         CASE(AIMAGE_FORMAT_RGBA_8888,   SDL_PIXELFORMAT_RGBA8888);
         CASE(AIMAGE_FORMAT_RGBX_8888,   SDL_PIXELFORMAT_RGBX8888);
-
-        CASE(AIMAGE_FORMAT_RGBA_FP16,   SDL_PIXELFORMAT_UNKNOWN); // 64bits
-        CASE(AIMAGE_FORMAT_RAW_PRIVATE, SDL_PIXELFORMAT_UNKNOWN);
-        CASE(AIMAGE_FORMAT_JPEG,        SDL_PIXELFORMAT_UNKNOWN);
+        //CASE(AIMAGE_FORMAT_RGBA_FP16,   SDL_PIXELFORMAT_UNKNOWN); // 64bits
+        //CASE(AIMAGE_FORMAT_RAW_PRIVATE, SDL_PIXELFORMAT_UNKNOWN);
+        //CASE(AIMAGE_FORMAT_JPEG,        SDL_PIXELFORMAT_UNKNOWN);
         #undef CASE
-        default:
-            SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt);
-            return SDL_PIXELFORMAT_UNKNOWN;
+        default: break;
     }
+
+    #if DEBUG_CAMERA
+    //SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt);
+    #endif
+
+    return SDL_PIXELFORMAT_UNKNOWN;
 }
 
 static Uint32 format_sdl_to_android(Uint32 fmt)
 {
     switch (fmt) {
         #define CASE(x, y)  case y: return x
-        CASE(AIMAGE_FORMAT_YUV_420_888, FORMAT_SDL);
+        CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12);
         CASE(AIMAGE_FORMAT_RGB_565,     SDL_PIXELFORMAT_RGB565);
         CASE(AIMAGE_FORMAT_RGB_888,     SDL_PIXELFORMAT_XRGB8888);
         CASE(AIMAGE_FORMAT_RGBA_8888,   SDL_PIXELFORMAT_RGBA8888);
@@ -177,12 +290,95 @@ static Uint32 format_sdl_to_android(Uint32 fmt)
     }
 }
 
+static int ANDROIDCAMERA_WaitDevice(SDL_CameraDevice *device)
+{
+    return 0;  // this isn't used atm, since we run our own thread via onImageAvailable callbacks.
+}
+
+static int ANDROIDCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
+{
+    int retval = 1;
+    media_status_t res;
+    AImage *image = NULL;
+
+    res = pAImageReader_acquireNextImage(device->hidden->reader, &image);
+    // We could also use this one:
+    //res = AImageReader_acquireLatestImage(device->hidden->reader, &image);
+
+    SDL_assert(res != AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE);  // we should only be here if onImageAvailable was called.
+
+    if (res != AMEDIA_OK) {
+        return SetMediaError("Error AImageReader_acquireNextImage", res);
+    }
+
+    int64_t atimestamp = 0;
+    if (pAImage_getTimestamp(image, &atimestamp) == AMEDIA_OK) {
+        *timestampNS = (Uint64) atimestamp;
+    } else {
+        *timestampNS = 0;
+    }
+
+    // !!! 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...
+    int32_t num_planes = 0;
+    pAImage_getNumberOfPlanes(image, &num_planes);
+
+    if ((num_planes == 3) && (device->spec.format == SDL_PIXELFORMAT_NV12)) {
+        num_planes--;   // treat the interleaved planes as one.
+    }
+
+    // !!! 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.
+    size_t buflen = 0;
+    for (int i = 0; (i < num_planes) && (i < 3); i++) {
+        uint8_t *data = NULL;
+        int32_t datalen = 0;
+        pAImage_getPlaneData(image, i, &data, &datalen);
+        buflen += (int) datalen;
+    }
+
+    frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
+    if (frame->pixels == NULL) {
+        retval = -1;
+    } else {
+        int32_t row_stride = 0;
+        Uint8 *dst = frame->pixels;
+        pAImage_getPlaneRowStride(image, 0, &row_stride);
+        frame->pitch = (int) row_stride;  // this is what SDL3 currently expects, probably incorrectly.
+
+        for (int i = 0; (i < num_planes) && (i < 3); i++) {
+            uint8_t *data = NULL;
+            int32_t datalen = 0;
+            pAImage_getPlaneData(image, i, &data, &datalen);
+            const void *src = data;
+            SDL_memcpy(dst, src, datalen);
+            dst += datalen;
+        }
+    }
+
+    pAImage_delete(image);
+
+    return retval;
+}
+
+static void ANDROIDCAMERA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
+{
+    // !!! FIXME: this currently copies the data to the surface, but in theory we could just keep the AImage until ReleaseFrame...
+    SDL_aligned_free(frame->pixels);
+}
+
+static void onImageAvailable(void *context, AImageReader *reader)
+{
+    #if DEBUG_CAMERA
+    SDL_Log("CAMERA: CB onImageAvailable");
+    #endif
+    SDL_CameraDevice *device = (SDL_CameraDevice *) context;
+    SDL_CameraThreadIterate(device);
+}
 
 static void onDisconnected(void *context, ACameraDevice *device)
 {
     // SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
     #if DEBUG_CAMERA
-    SDL_Log("CB onDisconnected");
+    SDL_Log("CAMERA: CB onDisconnected");
     #endif
 }
 
@@ -190,16 +386,15 @@ static void onError(void *context, ACameraDevice *device, int error)
 {
     // SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
     #if DEBUG_CAMERA
-    SDL_Log("CB onError");
+    SDL_Log("CAMERA: CB onError");
     #endif
 }
 
-
 static void onClosed(void* context, ACameraCaptureSession *session)
 {
     // SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
     #if DEBUG_CAMERA
-    SDL_Log("CB onClosed");
+    SDL_Log("CAMERA: CB onClosed");
     #endif
 }
 
@@ -207,7 +402,7 @@ static void onReady(void* context, ACameraCaptureSession *session)
 {
     // SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
     #if DEBUG_CAMERA
-    SDL_Log("CB onReady");
+    SDL_Log("CAMERA: CB onReady");
     #endif
 }
 
@@ -215,493 +410,478 @@ static void onActive(void* context, ACameraCaptureSession *session)
 {
     // SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
     #if DEBUG_CAMERA
-    SDL_Log("CB onActive");
+    SDL_Log("CAMERA: CB onActive");
     #endif
 }
 
-static int ANDROIDCAMERA_OpenDevice(SDL_CameraDevice *_this)
+static void ANDROIDCAMERA_CloseDevice(SDL_CameraDevice *device)
 {
-    /* Cannot open a second camera, while the first one is opened.
-     * If you want to play several camera, they must all be opened first, then played.
-     *
-     * https://developer.android.com/reference/android/hardware/camera2/CameraManager
-     * "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
-     * before configuring sessions on any of the camera devices.  * "
-     *
-     */
-    if (CheckDevicePlaying()) {
-        return SDL_SetError("A camera is already playing");
-    }
+    if (device && device->hidden) {
+        struct SDL_PrivateCameraData *hidden = device->hidden;
+        device->hidden = NULL;
 
-    _this->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
-    if (_this->hidden == NULL) {
-        return -1;
-    }
-
-    CreateCameraManager();
-
-    _this->hidden->dev_callbacks.context = (void *) _this;
-    _this->hidden->dev_callbacks.onDisconnected = onDisconnected;
-    _this->hidden->dev_callbacks.onError = onError;
-
-    camera_status_t res = ACameraManager_openCamera(cameraMgr, _this->dev_name, &_this->hidden->dev_callbacks, &_this->hidden->device);
-    if (res != ACAMERA_OK) {
-        return SDL_SetError("Failed to open camera");
-    }
+        if (hidden->reader) {
+            pAImageReader_setImageListener(hidden->reader, NULL);
+        }
 
-    return 0;
-}
+        if (hidden->session) {
+            pACameraCaptureSession_close(hidden->session);
+        }
 
-static void ANDROIDCAMERA_CloseDevice(SDL_CameraDevice *_this)
-{
-    if (_this && _this->hidden) {
-        if (_this->hidden->session) {
-            ACameraCaptureSession_close(_this->hidden->session);
+        if (hidden->request) {
+            pACaptureRequest_free(hidden->request);
         }
 
-        if (_this->hidden->sessionOutputContainer) {
-            ACaptureSessionOutputContainer_free(_this->hidden->sessionOutputContainer);
+        if (hidden->outputTarget) {
+            pACameraOutputTarget_free(hidden->outputTarget);
         }
 
-        if (_this->hidden->reader) {
-            AImageReader_delete(_this->hidden->reader);
+        if (hidden->sessionOutputContainer) {
+            pACaptureSessionOutputContainer_free(hidden->sessionOutputContainer);
         }
 
-        if (_this->hidden->device) {
-            ACameraDevice_close(_this->hidden->device);
+        if (hidden->sessionOutput) {
+            pACaptureSessionOutput_free(hidden->sessionOutput);
         }
 
-        SDL_free(_this->hidden);
+        // we don't free hidden->window here, it'll be cleaned up by AImageReader_delete.
 
-        _this->hidden = NULL;
-    }
-}
+        if (hidden->reader) {
+            pAImageReader_delete(hidden->reader);
+        }
 
-static int ANDROIDCAMERA_InitDevice(SDL_CameraDevice *_this)
-{
-    size_t size, pitch;
-    SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE);
-    SDL_Log("Buffer size: %d x %d", _this->spec.width, _this->spec.height);
-    return 0;
-}
+        if (hidden->device) {
+            pACameraDevice_close(hidden->device);
+        }
 
-static int ANDROIDCAMERA_GetDeviceSpec(SDL_CameraDevice *_this, SDL_CameraSpec *spec)
-{
-    // !!! FIXME: catch NULLs at higher level
-    if (spec) {
-        SDL_copyp(spec, &_this->spec);
-        return 0;
+        SDL_free(hidden);
     }
-    return -1;
 }
 
-static int ANDROIDCAMERA_StartCamera(SDL_CameraDevice *_this)
+// this is where the "opening" of the camera happens, after permission is granted.
+static int PrepareCamera(SDL_CameraDevice *device)
 {
-    // !!! FIXME: maybe log the error code in SDL_SetError
+    SDL_assert(device->hidden != NULL);
+
     camera_status_t res;
     media_status_t res2;
-    ANativeWindow *window = NULL;
-    ACaptureSessionOutput *sessionOutput;
-    ACameraOutputTarget *outputTarget;
-    ACaptureRequest *request;
-
-    res2 = AImageReader_new(_this->spec.width, _this->spec.height, format_sdl_to_android(_this->spec.format), 10 /* nb buffers */, &_this->hidden->reader);
-    if (res2 != AMEDIA_OK) {
-        SDL_SetError("Error AImageReader_new");
-        goto error;
-    }
-    res2 = AImageReader_getWindow(_this->hidden->reader, &window);
-    if (res2 != AMEDIA_OK) {
-        SDL_SetError("Error AImageReader_new");
-        goto error;
-    }
-
-    res = ACaptureSessionOutput_create(window, &sessionOutput);
-    if (res != ACAMERA_OK) {
-        SDL_SetError("Error ACaptureSessionOutput_create");
-        goto error;
-    }
-    res = ACaptureSessionOutputContainer_create(&_this->hidden->sessionOutputContainer);
-    if (res != ACAMERA_OK) {
-        SDL_SetError("Error ACaptureSessionOutputContainer_create");
-        goto error;
-    }
-    res = ACaptureSessionOutputContainer_add(_this->hidden->sessionOutputContainer, sessionOutput);
-    if (res != ACAMERA_OK) {
-        SDL_SetError("Error ACaptureSessionOutputContainer_add");
-        goto error;
-    }
-
-    res = ACameraOutputTarget_create(window, &outputTarget);
-    if (res != ACAMERA_OK) {
-        SDL_SetError("Error ACameraOutputTarget_create");
-        goto error;
-    }
 
-    res = ACameraDevice_createCaptureRequest(_this->hidden->device, TEMPLATE_RECORD, &request);
-    if (res != ACAMERA_OK) {
-        SDL_SetError("Error ACameraDevice_createCaptureRequest");
-        goto error;
-    }
-
-    res = ACaptureRequest_addTarget(request, outputTarget);
-    if (res != ACAMERA_OK) {
-        SDL_SetError("Error ACaptureRequest_addTarget");
-        goto error;
-    }
-
-    _this->hidden->capture_callbacks.context = (void *) _this;
-    _this->hidden->capture_callbacks.onClosed = onClosed;
-    _this->hidden->capture_callbacks.onReady = onReady;
-    _this->hidden->capture_callbacks.onActive = onActive;
-
-    res = ACameraDevice_createCaptureSession(_this->hidden->device,
-            _this->hidden->sessionOutputContainer,
-            &_this->hidden->capture_callbacks,
-            &_this->hidden->session

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