From 2613e3da24a9c083286d67bb4462531772c215ef Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" 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 | 1187 ++++++++++------- 5 files changed, 698 insertions(+), 510 deletions(-) diff --git a/Android.mk b/Android.mk index 9c15c72c5..275833174 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 8617dca9e..9b0a816d2 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" /> --> + + + + diff --git a/include/build_config/SDL_build_config_android.h b/include/build_config/SDL_build_config_android.h index 9ae2ca4d9..f279a40c1 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 9f556523d..fc55b071b 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 20aeb06f1..5da1cc0cb 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: * * * - * - * 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 #include #include #include @@ -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 ACameraManager *cameraMgr = NULL; -static ACameraIdList *cameraIdList = 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; -static int CreateCameraManager(void) -{ - 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"); - } - } - - cameraMgr = ACameraManager_create(); - - return cameraMgr ? 0 : SDL_SetError("Error creating ACameraManager"); -} - -static void DestroyCameraManager(void) -{ - if (cameraIdList) { - ACameraManager_deleteCameraIdList(cameraIdList); - cameraIdList = NULL; - } - - if (cameraMgr) { - ACameraManager_delete(cameraMgr); - cameraMgr = 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; struct SDL_PrivateCameraData { 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 + ANativeWindow *window; + ACaptureSessionOutput *sessionOutput; + ACaptureSessionOutputContainer *sessionOutputContainer; + ACameraOutputTarget *outputTarget; + ACaptureRequest *request; + ACameraCaptureSession *session; + SDL_CameraSpec requested_spec; }; - -#define FORMAT_SDL SDL_PIXELFORMAT_NV12 - -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 SetErrorStr(const char *what, const char *errstr, const int rc) +{ + char errbuf[128]; + if (!errstr) { + SDL_snprintf(errbuf, sizeof (errbuf), "Unknown error #%d", rc); + errstr = errbuf; } + return SDL_SetError("%s: %s", what, errstr); } -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 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 int SetCameraError(const char *what, const camera_status_t rc) +{ + return SetErrorStr(what, CameraStatusStr(rc), (int) rc); +} + +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 +} + +static int SetMediaError(const char *what, const media_status_t rc) +{ + return SetErrorStr(what, MediaStatusStr(rc), (int) rc); +} + + +static ACameraManager *cameraMgr = NULL; + +static int CreateCameraManager(void) +{ + SDL_assert(cameraMgr == NULL); + + cameraMgr = pACameraManager_create(); + if (!cameraMgr) { + return SDL_SetError("Error creating ACameraManager"); + } + return 0; +} + +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,12 +410,134 @@ 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) { + if (device && device->hidden) { + struct SDL_PrivateCameraData *hidden = device->hidden; + device->hidden = NULL; + + if (hidden->reader) { + pAImageReader_setImageListener(hidden->reader, NULL); + } + + if (hidden->session) { + pACameraCaptureSession_close(hidden->session); + } + + if (hidden->request) { + pACaptureRequest_free(hidden->request); + } + + if (hidden->outputTarget) { + pACameraOutputTarget_free(hidden->outputTarget); + } + + if (hidden->sessionOutputContainer) { + pACaptureSessionOutputContainer_free(hidden->sessionOutputContainer); + } + + if (hidden->sessionOutput) { + pACaptureSessionOutput_free(hidden->sessionOutput); + } + + // we don't free hidden->window here, it'll be cleaned up by AImageReader_delete. + + if (hidden->reader) { + pAImageReader_delete(hidden->reader); + } + + if (hidden->device) { + pACameraDevice_close(hidden->device); + } + + SDL_free(hidden); + } +} + +// this is where the "opening" of the camera happens, after permission is granted. +static int PrepareCamera(SDL_CameraDevice *device) +{ + SDL_assert(device->hidden != NULL); + + camera_status_t res; + media_status_t res2; + + ACameraDevice_StateCallbacks dev_callbacks; + SDL_zero(dev_callbacks); + dev_callbacks.context = device; + dev_callbacks.onDisconnected = onDisconnected; + dev_callbacks.onError = onError; + + ACameraCaptureSession_stateCallbacks capture_callbacks; + SDL_zero(capture_callbacks); + capture_callbacks.context = device; + capture_callbacks.onClosed = onClosed; + capture_callbacks.onReady = onReady; + capture_callbacks.onActive = onActive; + + AImageReader_ImageListener imglistener; + SDL_zero(imglistener); + imglistener.context = device; + imglistener.onImageAvailable = onImageAvailable; + + // just in case SDL_OpenCameraDevice is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy. + const SDL_CameraSpec *spec = &device->hidden->requested_spec; + + if ((res = pACameraManager_openCamera(cameraMgr, (const char *) device->handle, &dev_callbacks, &device->hidden->device)) != ACAMERA_OK) { + return SetCameraError("Failed to open camera", res); + } else if ((res2 = pAImageReader_new(spec->width, spec->height, format_sdl_to_android(spec->format), 10 /* nb buffers */, &device->hidden->reader)) != AMEDIA_OK) { + return SetMediaError("Error AImageReader_new", res2); + } else if ((res2 = pAImageReader_getWindow(device->hidden->reader, &device->hidden->window)) != AMEDIA_OK) { + return SetMediaError("Error AImageReader_getWindow", res2); + } else if ((res = pACaptureSessionOutput_create(device->hidden->window, &device->hidden->sessionOutput)) != ACAMERA_OK) { + return SetCameraError("Error ACaptureSessionOutput_create", res); + } else if ((res = pACaptureSessionOutputContainer_create(&device->hidden->sessionOutputContainer)) != ACAMERA_OK) { + return SetCameraError("Error ACaptureSessionOutputContainer_create", res); + } else if ((res = pACaptureSessionOutputContainer_add(device->hidden->sessionOutputContainer, device->hidden->sessionOutput)) != ACAMERA_OK) { + return SetCameraError("Error ACaptureSessionOutputContainer_add", res); + } else if ((res = pACameraOutputTarget_create(device->hidden->window, &device->hidden->outputTarget)) != ACAMERA_OK) { + return SetCameraError("Error ACameraOutputTarget_create", res); + } else if ((res = pACameraDevice_createCaptureRequest(device->hidden->device, TEMPLATE_RECORD, &device->hidden->request)) != ACAMERA_OK) { + return SetCameraError("Error ACameraDevice_createCaptureRequest", res); + } else if ((res = pACaptureRequest_addTarget(device->hidden->request, device->hidden->outputTarget)) != ACAMERA_OK) { + return SetCameraError("Error ACaptureRequest_addTarget", res); + } else if ((res = pACameraDevice_createCaptureSession(device->hidden->device, device->hidden->sessionOutputContainer, &capture_callbacks, &device->hidden->session)) != ACAMERA_OK) { + return SetCameraError("Error ACameraDevice_createCaptureSession", res); + } else if ((res = pACameraCaptureSession_setRepeatingRequest(device->hidden->session, NULL, 1, &device->hidden->request, NULL)) != ACAMERA_OK) { + return SetCameraError("Error ACameraCaptureSession_setRepeatingRequest", res); + } else if ((res2 = pAImageReader_setImageListener(device->hidden->reader, &imglistener)) != AMEDIA_OK) { + return SetMediaError("Error AImageReader_setImageListener", res2); + } + + return 0; +} + +static void SDLCALL CameraPermissionCallback(void *userdata, const char *permission, SDL_bool granted) +{ + SDL_CameraDevice *device = (SDL_CameraDevice *) userdata; + if (device->hidden != NULL) { // if device was already closed, don't send an event. + if (!granted) { + SDL_CameraDevicePermissionOutcome(device, SDL_FALSE); // sorry, permission denied. + } else if (PrepareCamera(device) < 0) { // permission given? Actually open the camera now. + // uhoh, setup failed; since the app thinks we already "opened" the device, mark it as disconnected and don't report the permission. + SDL_CameraDeviceDisconnected(device); + } else { + // okay! We have permission to use the camera _and_ opening the hardware worked out, report that the camera is usable! + SDL_CameraDevicePermissionOutcome(device, SDL_TRUE); // go go go! + } + } + + UnrefPhysicalCameraDevice(device); // we ref'd this in OpenDevice, release the extra reference. +} + + +static int ANDROIDCAMERA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec) +{ +#if 0 // !!! FIXME: for now, we'll just let this fail if it is going to fail, without checking for this /* 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. * @@ -232,476 +549,339 @@ static int ANDROIDCAMERA_OpenDevice(SDL_CameraDevice *_this) if (CheckDevicePlaying()) { return SDL_SetError("A camera is already playing"); } +#endif - _this->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData)); + if (device->hidden == NULL) { return -1; } - CreateCameraManager(); + RefPhysicalCameraDevice(device); // ref'd until permission callback fires. - _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"); + // just in case SDL_OpenCameraDevice is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy. + SDL_copyp(&device->hidden->requested_spec, spec); + if (SDL_AndroidRequestPermission("android.permission.CAMERA", CameraPermissionCallback, device) < 0) { + UnrefPhysicalCameraDevice(device); + return -1; } - return 0; + return 0; // we don't open the camera until permission is granted, so always succeed for now. } -static void ANDROIDCAMERA_CloseDevice(SDL_CameraDevice *_this) +static void ANDROIDCAMERA_FreeDeviceHandle(SDL_CameraDevice *device) { - if (_this && _this->hidden) { - if (_this->hidden->session) { - ACameraCaptureSession_close(_this->hidden->session); + if (device) { + SDL_free(device->handle); + } +} + +static void GatherCameraSpecs(const char *devid, CameraFormatAddData *add_data, char **fullname, const char **posstr) +{ + SDL_zerop(add_data); + + ACameraMetadata *metadata = NULL; + ACameraMetadata_const_entry cfgentry; + ACameraMetadata_const_entry durentry; + ACameraMetadata_const_entry infoentry; + + // This can fail with an "unknown error" (with `adb logcat` reporting "no such file or directory") + // for "LEGACY" level cameras. I saw this happen on a 30-dollar budget phone I have for testing + // (but a different brand budget phone worked, so it's not strictly the low-end of Android devices). + // LEGACY devices are seen by onCameraAvailable, but are not otherwise accessible through + // libcamera2ndk. The Java camera2 API apparently _can_ access these cameras, but we're going on + // without them here for now, in hopes that such hardware is a dying breed. + if (pACameraManager_getCameraCharacteristics(cameraMgr, devid, &metadata) != ACAMERA_OK) { + return; // oh well. + } else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &cfgentry) != ACAMERA_OK) { + pACameraMetadata_free(metadata); + return; // oh well. + } else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS, &durentry) != ACAMERA_OK) { + pACameraMetadata_free(metadata); + return; // oh well. + } + + *fullname = NULL; + if (pACameraMetadata_getConstEntry(metadata, ACAMERA_INFO_VERSION, &infoentry) == ACAMERA_OK) { + *fullname = (char *) SDL_malloc(infoentry.count + 1); + if (*fullname) { + SDL_strlcpy(*fullname, (const char *) infoentry.data.u8, infoentry.count + 1); } - - if (_this->hidden->sessionOutputContainer) { - ACaptureSessionOutputContainer_free(_this->hidden->sessionOutputContainer); - } - - if (_this->hidden->reader) { - AImageReader_delete(_this->hidden->reader); - } - - if (_this->hidden->device) { - ACameraDevice_close(_this->hidden->device); - } - - SDL_free(_this->hidden); - - _this->hidden = NULL; - } -} - -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; -} - -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; - } - return -1; -} - -static int ANDROIDCAMERA_StartCamera(SDL_CameraDevice *_this) -{ - // !!! FIXME: maybe log the error code in SDL_SetError - 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); - if (res != ACAMERA_OK) { - SDL_SetError("Error ACameraDevice_createCaptureSession"); - goto error; - } - - res = ACameraCaptureSession_setRepeatingRequest(_this->hidden->session, NULL, 1, &request, NULL); - if (res != ACAMERA_OK) { - SDL_SetError("Error ACameraDevice_createCaptureSession"); - goto error; - } - - return 0; - -error: - return -1; -} - -static int ANDROIDCAMERA_StopCamera(SDL_CameraDevice *_this) -{ - ACameraCaptureSession_close(_this->hidden->session); - _this->hidden->session = NULL; - return 0; -} - -static int ANDROIDCAMERA_AcquireFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) -{ - media_status_t res; - AImage *image; - res = AImageReader_acquireNextImage(_this->hidden->reader, &image); - /* We could also use this one: - res = AImageReader_acquireLatestImage(_this->hidden->reader, &image); - */ - if (res == AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE ) { - SDL_Delay(20); // TODO fix some delay - #if DEBUG_CAMERA - //SDL_Log("AImageReader_acquireNextImage: AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE"); - #endif - } else if (res == AMEDIA_OK ) { - int32_t numPlanes = 0; - AImage_getNumberOfPlanes(image, &numPlanes); - - frame->timestampNS = SDL_GetTicksNS(); - - for (int i = 0; i < numPlanes && i < 3; i++) { - int dataLength = 0; - int rowStride = 0; - uint8_t *data = NULL; - frame->num_planes += 1; - AImage_getPlaneRowStride(image, i, &rowStride); - res = AImage_getPlaneData(image, i, &data, &dataLength); - if (res == AMEDIA_OK) { - frame->data[i] = data; - frame->pitch[i] = rowStride; + *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"; + if (!*fullname) { + *fullname = SDL_strdup("Front-facing camera"); + } + } else if (*posentry.data.u8 == ACAMERA_LENS_FACING_BACK) { + *posstr = "back"; + if (!*fullname) { + *fullname = SDL_strdup("Back-facing camera"); } } - - if (frame->num_planes == 3) { - /* plane 2 and 3 are interleaved NV12. SDL only takes two planes for this format */ - int pixelStride = 0; - AImage_getPlanePixelStride(image, 1, &pixelStride); - if (pixelStride == 2) { - frame->num_planes -= 1; - } - } - - frame->internal = (void*)image; - } else if (res == AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED) { - return SDL_SetError("AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED"); - } else { - return SDL_SetError("AImageReader_acquireNextImage: %d", res); } - return 0; -} - -static int ANDROIDCAMERA_ReleaseFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) -{ - if (frame->internal){ - AImage_delete((AImage *)frame->internal); - } - return 0; -} - -static int ANDROIDCAMERA_GetNumFormats(SDL_CameraDevice *_this) -{ - camera_status_t res; - SDL_bool unknown = SDL_FALSE; - ACameraMetadata *metadata; - ACameraMetadata_const_entry entry; - - if (_this->hidden->num_formats != 0) { - return _this->hidden->num_formats; + if (!*fullname) { + *fullname = SDL_strdup("Generic camera"); // we tried. } - res = ACameraManager_getCameraCharacteristics(cameraMgr, _this->dev_name, &metadata); - if (res != ACAMERA_OK) { - return -1; - } - - res = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry); - if (res != ACAMERA_OK) { - return -1; - } - - SDL_Log("got entry ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS"); - - for (int i = 0; i < entry.count; i += 4) { - const int32_t format = entry.data.i32[i + 0]; - const int32_t type = entry.data.i32[i + 3]; + const int32_t *i32ptr = cfgentry.data.i32; + for (int i = 0; i < cfgentry.count; i++, i32ptr += 4) { + const int32_t fmt = i32ptr[0]; + const int w = (int) i32ptr[1]; + const int h = (int) i32ptr[2]; + const int32_t type = i32ptr[3]; + Uint32 sdlfmt; if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) { continue; + } else if ((w <= 0) || (h <= 0)) { + continue; + } else if ((sdlfmt = format_android_to_sdl(fmt)) == SDL_PIXELFORMAT_UNKNOWN) { + continue; } - const Uint32 fmt = format_android_to_sdl(format); - _this->hidden->count_formats[format_to_id(fmt)] += 1; - - #if DEBUG_CAMERA - if (fmt != SDL_PIXELFORMAT_UNKNOWN) { - int w = entry.data.i32[i + 1]; - int h = entry.data.i32[i + 2]; - SDL_Log("Got format android 0x%08x -> %s %d x %d", format, SDL_GetPixelFormatName(fmt), w, h); - } else { - unknown = SDL_TRUE; +#if 0 // !!! FIXME: these all come out with 0 durations on my test phone. :( + const int64_t *i64ptr = durentry.data.i64; + for (int j = 0; j < durentry.count; j++, i64ptr += 4) { + const int32_t fpsfmt = (int32_t) i64ptr[0]; + const int fpsw = (int) i64ptr[1]; + const int fpsh = (int) i64ptr[2]; + const long long duration = (long long) i64ptr[3]; + SDL_Log("CAMERA: possible fps %s %dx%d duration=%lld", SDL_GetPixelFormatName(format_android_to_sdl(fpsfmt)), fpsw, fpsh, duration); + if ((duration > 0) && (fpsfmt == fmt) && (fpsw == w) && (fpsh == h)) { + SDL_AddCameraFormat(add_data, sdlfmt, w, h, duration, 1000000000); + } } - #endif +#else + SDL_AddCameraFormat(add_data, sdlfmt, w, h, 1, 30); +#endif } + pACameraMetadata_free(metadata); +} + +static SDL_bool FindAndroidCameraDeviceByID(SDL_CameraDevice *device, void *userdata) +{ + const char *devid = (const char *) userdata; + return (SDL_strcmp(devid, (const char *) device->handle) == 0); +} + +static void MaybeAddDevice(const char *devid) +{ #if DEBUG_CAMERA - if (unknown) { - SDL_Log("Got unknown android"); - } + SDL_Log("CAMERA: MaybeAddDevice('%s')", devid); #endif + if (SDL_FindPhysicalCameraDeviceByCallback(FindAndroidCameraDeviceByID, (void *) devid)) { + return; // already have this one. + } - if ( _this->hidden->count_formats[0]) _this->hidden->num_formats += 1; - if ( _this->hidden->count_formats[1]) _this->hidden->num_formats += 1; - if ( _this->hidden->count_formats[2]) _this->hidden->num_formats += 1; - if ( _this->hidden->count_formats[3]) _this->hidden->num_formats += 1; - if ( _this->hidden->count_formats[4]) _this->hidden->num_formats += 1; - if ( _this->hidden->count_formats[5]) _this->hidden->num_formats += 1; + const char *posstr = NULL; + char *fullname = NULL; + CameraFormatAddData add_data; + GatherCameraSpecs(devid, &add_data, &fullname, &posstr); + 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); + 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); + } + } + } + } - return _this->hidden->num_formats; + SDL_free(fullname); + SDL_free(add_data.specs); } -static int ANDROIDCAMERA_GetFormat(SDL_CameraDevice *_this, int index, Uint32 *format) +// note that camera "availability" covers both hotplugging and whether another +// has the device opened, but for something like Android, it's probably fine +// to treat both unplugging and loss of access as disconnection events. When +// the other app closes the camera, we get an available event as if it was +// just plugged back in. + +static void onCameraAvailable(void *context, const char *cameraId) { - int i2 = 0; - - if (_this->hidden->num_formats == 0) { - GetNumFormats(_this); - } - - if (index < 0 || index >= _this->hidden->num_formats) { - // !!! FIXME: call SDL_SetError()? - return -1; - } - - for (int i = 0; i < SDL_arraysize(_this->hidden->count_formats); i++) { - if (_this->hidden->count_formats[i] == 0) { - continue; - } - - if (i2 == index) { - *format = id_to_format(i); - } - - i2++; - - } - return 0; + #if DEBUG_CAMERA + SDL_Log("CAMERA: CB onCameraAvailable('%s')", cameraId); + #endif + SDL_assert(cameraId != NULL); + MaybeAddDevice(cameraId); } -static int ANDROIDCAMERA_GetNumFrameSizes(SDL_CameraDevice *_this, Uint32 format) +static void onCameraUnavailable(void *context, const char *cameraId) { - // !!! FIXME: call SDL_SetError()? - if (_this->hidden->num_formats == 0) { - GetNumFormats(_this); - } - - const int index = format_to_id(format); - - int i2 = 0; - for (int i = 0; i < SDL_arraysize(_this->hidden->count_formats); i++) { - if (_this->hidden->count_formats[i] == 0) { - continue; - } - - if (i2 == index) { - /* number of resolution for this format */ - return _this->hidden->count_formats[i]; - } - - i2++; - } - - return -1; + #if DEBUG_CAMERA + SDL_Log("CAMERA: CB onCameraUnvailable('%s')", cameraId); + #endif + SDL_assert(cameraId != NULL); + SDL_CameraDeviceDisconnected(SDL_FindPhysicalCameraDeviceByCallback(FindAndroidCameraDeviceByID, (void *) cameraId)); } -static int ANDROIDCAMERA_GetFrameSize(SDL_CameraDevice *_this, Uint32 format, int index, int *width, int *height) +static const ACameraManager_AvailabilityCallbacks camera_availability_listener = { + NULL, + onCameraAvailable, + onCameraUnavailable +}; + +static void ANDROIDCAMERA_DetectDevices(void) { - // !!! FIXME: call SDL_SetError()? - camera_status_t res; - ACameraMetadata *metadata; - ACameraMetadata_const_entry entry; + ACameraIdList *list = NULL; + camera_status_t res = pACameraManager_getCameraIdList(cameraMgr, &list); - if (_this->hidden->num_formats == 0) { - GetNumFormats(_this); - } - - res = ACameraManager_getCameraCharacteristics(cameraMgr, _this->dev_name, &metadata); - if (res != ACAMERA_OK) { - return -1; - } - - res = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry); - if (res != ACAMERA_OK) { - return -1; - } - - int i2 = 0; - for (int i = 0; i < entry.count; i += 4) { - int32_t f = entry.data.i32[i + 0]; - const int w = entry.data.i32[i + 1]; - const int h = entry.data.i32[i + 2]; - int32_t type = entry.data.i32[i + 3]; - - if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) { - continue; + if ((res == ACAMERA_OK) && list) { + const int total = list->numCameras; + for (int i = 0; i < total; i++) { + MaybeAddDevice(list->cameraIds[i]); } - Uint32 fmt = format_android_to_sdl(f); - if (fmt != format) { - continue; - } - - if (i2 == index) { - *width = w; - *height = h; - return 0; - } - - i2++; + pACameraManager_deleteCameraIdList(list); } - return -1; -} - -static int ANDROIDCAMERA_GetNumDevices(void) -{ - camera_status_t res; - CreateCameraManager(); - - if (cameraIdList) { - ACameraManager_deleteCameraIdList(cameraIdList); - cameraIdList = NULL; - } - - res = ACameraManager_getCameraIdList(cameraMgr, &cameraIdList); - - if (res == ACAMERA_OK) { - if (cameraIdList) { - return cameraIdList->numCameras; - } - } - return -1; -} - -static int ANDROIDCAMERA_GetDeviceName(SDL_CameraDeviceID instance_id, char *buf, int size) -{ - // !!! FIXME: call SDL_SetError()? - int index = instance_id - 1; - CreateCameraManager(); - - if (cameraIdList == NULL) { - GetNumDevices(); - } - - if (cameraIdList) { - if (index >= 0 && index < cameraIdList->numCameras) { - SDL_snprintf(buf, size, "%s", cameraIdList->cameraIds[index]); - return 0; - } - } - - return -1; -} - -static SDL_CameraDeviceID *ANDROIDCAMERA_GetDevices(int *count) -{ - // hard-coded list of ID - const int num = GetNumDevices(); - SDL_CameraDeviceID *retval = (SDL_CameraDeviceID *)SDL_malloc((num + 1) * sizeof(*ret)); - - if (retval == NULL) { - *count = 0; - return NULL; - } - - for (int i = 0; i < num; i++) { - retval[i] = i + 1; - } - retval[num] = 0; - *count = num; - return retval; + pACameraManager_registerAvailabilityCallback(cameraMgr, &camera_availability_listener); } static void ANDROIDCAMERA_Deinitialize(void) { + pACameraManager_unregisterAvailabilityCallback(cameraMgr, &camera_availability_listener); DestroyCameraManager(); + + dlclose(libcamera2ndk); + libcamera2ndk = NULL; + pACameraManager_create = NULL; + pACameraManager_registerAvailabilityCallback = NULL; + pACameraManager_unregisterAvailabilityCallback = NULL; + pACameraManager_getCameraIdList = NULL; + pACameraManager_deleteCameraIdList = NULL; + pACameraCaptureSession_close = NULL; + pACaptureRequest_free = NULL; + pACameraOutputTarget_free = NULL; + pACameraDevice_close = NULL; + pACameraManager_delete = NULL; + pACaptureSessionOutputContainer_free = NULL; + pACaptureSessionOutput_free = NULL; + pACameraManager_openCamera = NULL; + pACameraDevice_createCaptureRequest = NULL; + pACameraDevice_createCaptureSession = NULL; + pACameraManager_getCameraCharacteristics = NULL; + pACameraMetadata_free = NULL; + pACameraMetadata_getConstEntry = NULL; + pACameraCaptureSession_setRepeatingRequest = NULL; + pACameraOutputTarget_create = NULL; + pACaptureRequest_addTarget = NULL; + pACaptureSessionOutputContainer_add = NULL; + pACaptureSessionOutputContainer_create = NULL; + pACaptureSessionOutput_create = NULL; + + dlclose(libmediandk); + libmediandk = NULL; + pAImage_delete = NULL; + pAImage_getTimestamp = NULL; + pAImage_getNumberOfPlanes = NULL; + pAImage_getPlaneRowStride = NULL; + pAImage_getPlaneData = NULL; + pAImageReader_acquireNextImage = NULL; + pAImageReader_delete = NULL; + pAImageReader_setImageListener = NULL; + pAImageReader_getWindow = NULL; + pAImageReader_new = NULL; } -#endif // __ANDROID_API__ >= 24 - - static SDL_bool ANDROIDCAMERA_Init(SDL_CameraDriverImpl *impl) { -#if __ANDROID_API__ < 24 - return SDL_FALSE; -#else - if (CreateCameraManager() < 0) { + // !!! FIXME: slide this off into a subroutine + // system libraries are in android-24 and later; we currently target android-16 and later, so check if they exist at runtime. + void *libcamera2 = dlopen("libcamera2ndk.so", RTLD_NOW | RTLD_LOCAL); + if (!libcamera2) { + SDL_Log("CAMERA: libcamera2ndk.so can't be loaded: %s", dlerror()); return SDL_FALSE; } + void *libmedia = dlopen("libmediandk.so", RTLD_NOW | RTLD_LOCAL); + if (!libmedia) { + SDL_Log("CAMERA: libmediandk.so can't be loaded: %s", dlerror()); + dlclose(libcamera2); + return SDL_FALSE; + } + + SDL_bool okay = SDL_TRUE; + #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 = SDL_FALSE; } } + //#define LOADSYM(lib, fn) p##fn = (pfn##fn) fn + LOADSYM(libcamera2, ACameraManager_create); + LOADSYM(libcamera2, ACameraManager_registerAvailabilityCallback); + LOADSYM(libcamera2, ACameraManager_unregisterAvailabilityCallback); + LOADSYM(libcamera2, ACameraManager_getCameraIdList); + LOADSYM(libcamera2, ACameraManager_deleteCameraIdList); + LOADSYM(libcamera2, ACameraCaptureSession_close); + LOADSYM(libcamera2, ACaptureRequest_free); + LOADSYM(libcamera2, ACameraOutputTarget_free); + LOADSYM(libcamera2, ACameraDevice_close); + LOADSYM(libcamera2, ACameraManager_delete); + LOADSYM(libcamera2, ACaptureSessionOutputContainer_free); + LOADSYM(libcamera2, ACaptureSessionOutput_free); + LOADSYM(libcamera2, ACameraManager_openCamera); + LOADSYM(libcamera2, ACameraDevice_createCaptureRequest); + LOADSYM(libcamera2, ACameraDevice_createCaptureSession); + LOADSYM(libcamera2, ACameraManager_getCameraCharacteristics); + LOADSYM(libcamera2, ACameraMetadata_free); + LOADSYM(libcamera2, ACameraMetadata_getConstEntry); + LOADSYM(libcamera2, ACameraCaptureSession_setRepeatingRequest); + LOADSYM(libcamera2, ACameraOutputTarget_create); + LOADSYM(libcamera2, ACaptureRequest_addTarget); + LOADSYM(libcamera2, ACaptureSessionOutputContainer_add); + LOADSYM(libcamera2, ACaptureSessionOutputContainer_create); + LOADSYM(libcamera2, ACaptureSessionOutput_create); + LOADSYM(libmedia, AImage_delete); + LOADSYM(libmedia, AImage_getTimestamp); + LOADSYM(libmedia, AImage_getNumberOfPlanes); + LOADSYM(libmedia, AImage_getPlaneRowStride); + LOADSYM(libmedia, AImage_getPlaneData); + LOADSYM(libmedia, AImageReader_acquireNextImage); + LOADSYM(libmedia, AImageReader_delete); + LOADSYM(libmedia, AImageReader_setImageListener); + LOADSYM(libmedia, AImageReader_getWindow); + LOADSYM(libmedia, AImageReader_new); + LOADSYM(libmedia, AImage_getWidth); + LOADSYM(libmedia, AImage_getHeight); + + #undef LOADSYM + + if (!okay) { + dlclose(libmedia); + dlclose(libcamera2); + } + + if (CreateCameraManager() < 0) { + dlclose(libmedia); + dlclose(libcamera2); + return SDL_FALSE; + } + + libcamera2ndk = libcamera2; + libmediandk = libmedia; + impl->DetectDevices = ANDROIDCAMERA_DetectDevices; impl->OpenDevice = ANDROIDCAMERA_OpenDevice; impl->CloseDevice = ANDROIDCAMERA_CloseDevice; - impl->InitDevice = ANDROIDCAMERA_InitDevice; - impl->GetDeviceSpec = ANDROIDCAMERA_GetDeviceSpec; - impl->StartCamera = ANDROIDCAMERA_StartCamera; - impl->StopCamera = ANDROIDCAMERA_StopCamera; + impl->WaitDevice = ANDROIDCAMERA_WaitDevice; impl->AcquireFrame = ANDROIDCAMERA_AcquireFrame; impl->ReleaseFrame = ANDROIDCAMERA_ReleaseFrame; - impl->GetNumFormats = ANDROIDCAMERA_GetNumFormats; - impl->GetFormat = ANDROIDCAMERA_GetFormat; - impl->GetNumFrameSizes = ANDROIDCAMERA_GetNumFrameSizes; - impl->GetFrameSize = ANDROIDCAMERA_GetFrameSize; - impl->GetDeviceName = ANDROIDCAMERA_GetDeviceName; - impl->GetDevices = ANDROIDCAMERA_GetDevices; + impl->FreeDeviceHandle = ANDROIDCAMERA_FreeDeviceHandle; impl->Deinitialize = ANDROIDCAMERA_Deinitialize; + impl->ProvidesOwnCallbackThread = SDL_TRUE; + return SDL_TRUE; -#endif } CameraBootStrap ANDROIDCAMERA_bootstrap = { @@ -709,4 +889,3 @@ CameraBootStrap ANDROIDCAMERA_bootstrap = { }; #endif -