#include "SDL_internal.h"
#include "SDL_syscamera.h"
#include "SDL_camera_c.h"
#include "../video/SDL_pixels_c.h"
#include "../video/SDL_surface_c.h"
#include "../thread/SDL_systhread.h"
static const CameraBootStrap *const bootstrap[] = {
#ifdef SDL_CAMERA_DRIVER_V4L2
&V4L2_bootstrap,
#endif
#ifdef SDL_CAMERA_DRIVER_PIPEWIRE
&PIPEWIRECAMERA_bootstrap,
#endif
#ifdef SDL_CAMERA_DRIVER_COREMEDIA
&COREMEDIA_bootstrap,
#endif
#ifdef SDL_CAMERA_DRIVER_ANDROID
&ANDROIDCAMERA_bootstrap,
#endif
#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
&EMSCRIPTENCAMERA_bootstrap,
#endif
#ifdef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
&MEDIAFOUNDATION_bootstrap,
#endif
#ifdef SDL_CAMERA_DRIVER_VITA
&VITACAMERA_bootstrap,
#endif
#ifdef SDL_CAMERA_DRIVER_DUMMY
&DUMMYCAMERA_bootstrap,
#endif
NULL
};
static SDL_CameraDriver camera_driver;
int SDL_GetNumCameraDrivers(void)
{
return SDL_arraysize(bootstrap) - 1;
}
const char *SDL_GetCameraDriver(int index)
{
CHECK_PARAM(index < 0 || index >= SDL_GetNumCameraDrivers()) {
SDL_InvalidParamError("index");
return NULL;
}
return bootstrap[index]->name;
}
const char *SDL_GetCurrentCameraDriver(void)
{
return camera_driver.name;
}
char *SDL_GetCameraThreadName(SDL_Camera *device, char *buf, size_t buflen)
{
(void)SDL_snprintf(buf, buflen, "SDLCamera%d", (int) device->instance_id);
return buf;
}
bool SDL_AddCameraFormat(CameraFormatAddData *data, SDL_PixelFormat format, SDL_Colorspace colorspace, int w, int h, int framerate_numerator, int framerate_denominator)
{
SDL_assert(data != NULL);
if (data->allocated_specs <= data->num_specs) {
const int newalloc = data->allocated_specs ? (data->allocated_specs * 2) : 16;
void *ptr = SDL_realloc(data->specs, sizeof (SDL_CameraSpec) * newalloc);
if (!ptr) {
return false;
}
data->specs = (SDL_CameraSpec *) ptr;
data->allocated_specs = newalloc;
}
SDL_CameraSpec *spec = &data->specs[data->num_specs];
spec->format = format;
spec->colorspace = colorspace;
spec->width = w;
spec->height = h;
spec->framerate_numerator = framerate_numerator;
spec->framerate_denominator = framerate_denominator;
data->num_specs++;
return true;
}
static bool ZombieWaitDevice(SDL_Camera *device)
{
if (!SDL_GetAtomicInt(&device->shutdown)) {
const double duration = ((double) device->actual_spec.framerate_denominator / ((double) device->actual_spec.framerate_numerator));
SDL_Delay((Uint32) (duration * 1000.0));
}
return true;
}
static size_t GetFrameBufLen(const SDL_CameraSpec *spec)
{
const size_t w = (const size_t) spec->width;
const size_t h = (const size_t) spec->height;
const size_t wxh = w * h;
const SDL_PixelFormat fmt = spec->format;
switch (fmt) {
case SDL_PIXELFORMAT_YV12:
case SDL_PIXELFORMAT_IYUV:
case SDL_PIXELFORMAT_NV12:
case SDL_PIXELFORMAT_NV21:
return wxh + (wxh / 2);
default: break;
}
return wxh * SDL_BYTESPERPIXEL(fmt);
}
static SDL_CameraFrameResult ZombieAcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
{
const SDL_CameraSpec *spec = &device->actual_spec;
if (!device->zombie_pixels) {
const size_t buflen = GetFrameBufLen(&device->actual_spec);
device->zombie_pixels = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
if (!device->zombie_pixels) {
*timestampNS = 0;
return SDL_CAMERA_FRAME_SKIP; }
Uint8 *dst = device->zombie_pixels;
switch (spec->format) {
case SDL_PIXELFORMAT_YV12:
case SDL_PIXELFORMAT_IYUV:
case SDL_PIXELFORMAT_NV12:
case SDL_PIXELFORMAT_NV21:
SDL_memset(dst, 0, spec->width * spec->height); SDL_memset(dst + (spec->width * spec->height), 128, (spec->width * spec->height) / 2); break;
case SDL_PIXELFORMAT_YUY2:
case SDL_PIXELFORMAT_YVYU:
for (size_t i = 0; i < buflen; i += 4) {
dst[i] = 0;
dst[i+1] = 128;
dst[i+2] = 0;
dst[i+3] = 128;
}
break;
case SDL_PIXELFORMAT_UYVY:
for (size_t i = 0; i < buflen; i += 4) {
dst[i] = 128;
dst[i+1] = 0;
dst[i+2] = 128;
dst[i+3] = 0;
}
break;
default:
SDL_memset(dst, 0, buflen);
break;
}
}
*timestampNS = SDL_GetTicksNS();
frame->pixels = device->zombie_pixels;
frame->pitch = spec->width;
if (!SDL_ISPIXELFORMAT_FOURCC(spec->format)) { frame->pitch *= SDL_BYTESPERPIXEL(spec->format);
}
#if DEBUG_CAMERA
SDL_Log("CAMERA: dev[%p] Acquired Zombie frame, timestamp %llu", device, (unsigned long long) *timestampNS);
#endif
return SDL_CAMERA_FRAME_READY; }
static void ZombieReleaseFrame(SDL_Camera *device, SDL_Surface *frame) {
if (frame->pixels != device->zombie_pixels) {
camera_driver.impl.ReleaseFrame(device, frame);
}
}
static void ObtainPhysicalCameraObj(SDL_Camera *device);
static void ReleaseCamera(SDL_Camera *device);
static void ClosePhysicalCamera(SDL_Camera *device)
{
if (!device || (device->hidden == NULL)) {
return; }
SDL_SetAtomicInt(&device->shutdown, 1);
if (device->thread != NULL) {
SDL_WaitThread(device->thread, NULL);
device->thread = NULL;
}
ObtainPhysicalCameraObj(device);
if (!device->needs_conversion && !device->needs_scaling) {
for (SurfaceList *i = device->filled_output_surfaces.next; i != NULL; i = i->next) {
device->ReleaseFrame(device, i->surface);
}
for (SurfaceList *i = device->app_held_output_surfaces.next; i != NULL; i = i->next) {
device->ReleaseFrame(device, i->surface);
}
}
camera_driver.impl.CloseDevice(device);
device->hidden = NULL;
SDL_DestroyProperties(device->props);
SDL_DestroySurface(device->acquire_surface);
device->acquire_surface = NULL;
SDL_DestroySurface(device->conversion_surface);
device->conversion_surface = NULL;
for (int i = 0; i < SDL_arraysize(device->output_surfaces); i++) {
SDL_DestroySurface(device->output_surfaces[i].surface);
}
SDL_zeroa(device->output_surfaces);
SDL_aligned_free(device->zombie_pixels);
device->permission = SDL_CAMERA_PERMISSION_STATE_PENDING;
device->zombie_pixels = NULL;
device->filled_output_surfaces.next = NULL;
device->empty_output_surfaces.next = NULL;
device->app_held_output_surfaces.next = NULL;
device->base_timestamp = 0;
device->adjust_timestamp = 0;
SDL_zero(device->spec);
UnrefPhysicalCamera(device);
ReleaseCamera(device);
}
void UnrefPhysicalCamera(SDL_Camera *device)
{
if (SDL_AtomicDecRef(&device->refcount)) {
SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
if (SDL_RemoveFromHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id)) {
SDL_AddAtomicInt(&camera_driver.device_count, -1);
}
SDL_UnlockRWLock(camera_driver.device_hash_lock);
}
}
void RefPhysicalCamera(SDL_Camera *device)
{
SDL_AtomicIncRef(&device->refcount);
}
static void ObtainPhysicalCameraObj(SDL_Camera *device) SDL_NO_THREAD_SAFETY_ANALYSIS {
if (device) {
RefPhysicalCamera(device);
SDL_LockMutex(device->lock);
}
}
static SDL_Camera *ObtainPhysicalCamera(SDL_CameraID devid) {
if (!SDL_GetCurrentCameraDriver()) {
SDL_SetError("Camera subsystem is not initialized");
return NULL;
}
SDL_Camera *device = NULL;
SDL_LockRWLockForReading(camera_driver.device_hash_lock);
SDL_FindInHashTable(camera_driver.device_hash, (const void *) (uintptr_t) devid, (const void **) &device);
SDL_UnlockRWLock(camera_driver.device_hash_lock);
if (!device) {
SDL_SetError("Invalid camera device instance ID");
} else {
ObtainPhysicalCameraObj(device);
}
return device;
}
static void ReleaseCamera(SDL_Camera *device) SDL_NO_THREAD_SAFETY_ANALYSIS {
if (device) {
SDL_UnlockMutex(device->lock);
UnrefPhysicalCamera(device);
}
}
static int SDLCALL CameraSpecCmp(const void *vpa, const void *vpb)
{
const SDL_CameraSpec *a = (const SDL_CameraSpec *) vpa;
const SDL_CameraSpec *b = (const SDL_CameraSpec *) vpb;
SDL_assert(a->format != SDL_PIXELFORMAT_UNKNOWN);
SDL_assert(a->width > 0);
SDL_assert(a->height > 0);
SDL_assert(b->format != SDL_PIXELFORMAT_UNKNOWN);
SDL_assert(b->width > 0);
SDL_assert(b->height > 0);
const SDL_PixelFormat afmt = a->format;
const SDL_PixelFormat bfmt = b->format;
if (SDL_ISPIXELFORMAT_FOURCC(afmt) && !SDL_ISPIXELFORMAT_FOURCC(bfmt)) {
return -1;
} else if (!SDL_ISPIXELFORMAT_FOURCC(afmt) && SDL_ISPIXELFORMAT_FOURCC(bfmt)) {
return 1;
} else if (SDL_BITSPERPIXEL(afmt) > SDL_BITSPERPIXEL(bfmt)) {
return -1;
} else if (SDL_BITSPERPIXEL(bfmt) > SDL_BITSPERPIXEL(afmt)) {
return 1;
} else if (a->width > b->width) {
return -1;
} else if (b->width > a->width) {
return 1;
} else if (a->height > b->height) {
return -1;
} else if (b->height > a->height) {
return 1;
}
if (a->framerate_numerator && !b->framerate_numerator) {
return -1;
} else if (!a->framerate_numerator && b->framerate_numerator) {
return 1;
}
const float fpsa = ((float)a->framerate_numerator / a->framerate_denominator);
const float fpsb = ((float)b->framerate_numerator / b->framerate_denominator);
if (fpsa > fpsb) {
return -1;
} else if (fpsb > fpsa) {
return 1;
}
if (SDL_COLORSPACERANGE(a->colorspace) == SDL_COLOR_RANGE_FULL &&
SDL_COLORSPACERANGE(b->colorspace) != SDL_COLOR_RANGE_FULL) {
return -1;
}
if (SDL_COLORSPACERANGE(a->colorspace) != SDL_COLOR_RANGE_FULL &&
SDL_COLORSPACERANGE(b->colorspace) == SDL_COLOR_RANGE_FULL) {
return 1;
}
return 0; }
SDL_Camera *SDL_AddCamera(const char *name, SDL_CameraPosition position, int num_specs, const SDL_CameraSpec *specs, void *handle)
{
SDL_assert(name != NULL);
SDL_assert(num_specs >= 0);
SDL_assert((specs != NULL) == (num_specs > 0));
SDL_assert(handle != NULL);
SDL_LockRWLockForReading(camera_driver.device_hash_lock);
const int shutting_down = SDL_GetAtomicInt(&camera_driver.shutting_down);
SDL_UnlockRWLock(camera_driver.device_hash_lock);
if (shutting_down) {
return NULL; }
SDL_Camera *device = (SDL_Camera *)SDL_calloc(1, sizeof(SDL_Camera));
if (!device) {
return NULL;
}
device->name = SDL_strdup(name);
if (!device->name) {
SDL_free(device);
return NULL;
}
device->position = position;
device->lock = SDL_CreateMutex();
if (!device->lock) {
SDL_free(device->name);
SDL_free(device);
return NULL;
}
device->all_specs = (SDL_CameraSpec *)SDL_calloc(num_specs + 1, sizeof (*specs));
if (!device->all_specs) {
SDL_DestroyMutex(device->lock);
SDL_free(device->name);
SDL_free(device);
return NULL;
}
if (num_specs > 0) {
SDL_memcpy(device->all_specs, specs, sizeof (*specs) * num_specs);
SDL_qsort(device->all_specs, num_specs, sizeof (*specs), CameraSpecCmp);
for (int i = 0; i < num_specs; i++) {
SDL_CameraSpec *a = &device->all_specs[i];
SDL_CameraSpec *b = &device->all_specs[i + 1];
if (SDL_memcmp(a, b, sizeof (*a)) == 0) {
SDL_memmove(a, b, sizeof (*specs) * (num_specs - i));
i--;
num_specs--;
}
}
}
#if DEBUG_CAMERA
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->framerate_numerator, spec->framerate_denominator);
}
#endif
device->num_specs = num_specs;
device->handle = handle;
device->instance_id = SDL_GetNextObjectID();
SDL_SetAtomicInt(&device->shutdown, 0);
SDL_SetAtomicInt(&device->zombie, 0);
RefPhysicalCamera(device);
SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
if (SDL_InsertIntoHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id, device, false)) {
SDL_AddAtomicInt(&camera_driver.device_count, 1);
} else {
SDL_DestroyMutex(device->lock);
SDL_free(device->all_specs);
SDL_free(device->name);
SDL_free(device);
device = NULL;
}
if (device) {
SDL_PendingCameraEvent *p = (SDL_PendingCameraEvent *) SDL_malloc(sizeof (SDL_PendingCameraEvent));
if (p) { p->type = SDL_EVENT_CAMERA_DEVICE_ADDED;
p->devid = device->instance_id;
p->next = NULL;
SDL_assert(camera_driver.pending_events_tail != NULL);
SDL_assert(camera_driver.pending_events_tail->next == NULL);
camera_driver.pending_events_tail->next = p;
camera_driver.pending_events_tail = p;
}
}
SDL_UnlockRWLock(camera_driver.device_hash_lock);
return device;
}
void SDL_CameraDisconnected(SDL_Camera *device)
{
if (!device) {
return;
}
#if DEBUG_CAMERA
SDL_Log("CAMERA: DISCONNECTED! dev[%p]", device);
#endif
SDL_PendingCameraEvent pending;
pending.next = NULL;
SDL_PendingCameraEvent *pending_tail = &pending;
ObtainPhysicalCameraObj(device);
const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 1);
if (first_disconnect) { device->WaitDevice = ZombieWaitDevice;
device->AcquireFrame = ZombieAcquireFrame;
device->ReleaseFrame = ZombieReleaseFrame;
device->adjust_timestamp = 0;
device->base_timestamp = 0;
SDL_PendingCameraEvent *p = (SDL_PendingCameraEvent *) SDL_malloc(sizeof (SDL_PendingCameraEvent));
if (p) { p->type = SDL_EVENT_CAMERA_DEVICE_REMOVED;
p->devid = device->instance_id;
p->next = NULL;
pending_tail->next = p;
pending_tail = p;
}
UnrefPhysicalCamera(device); }
ReleaseCamera(device);
if (first_disconnect) {
if (pending.next) { SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
SDL_assert(camera_driver.pending_events_tail != NULL);
SDL_assert(camera_driver.pending_events_tail->next == NULL);
camera_driver.pending_events_tail->next = pending.next;
camera_driver.pending_events_tail = pending_tail;
SDL_UnlockRWLock(camera_driver.device_hash_lock);
}
}
}
void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved)
{
if (!device) {
return;
}
SDL_PendingCameraEvent pending;
pending.next = NULL;
SDL_PendingCameraEvent *pending_tail = &pending;
const SDL_CameraPermissionState permission = approved ? SDL_CAMERA_PERMISSION_STATE_APPROVED : SDL_CAMERA_PERMISSION_STATE_DENIED;
ObtainPhysicalCameraObj(device);
if (device->permission != permission) {
device->permission = permission;
SDL_PendingCameraEvent *p = (SDL_PendingCameraEvent *) SDL_malloc(sizeof (SDL_PendingCameraEvent));
if (p) { p->type = approved ? SDL_EVENT_CAMERA_DEVICE_APPROVED : SDL_EVENT_CAMERA_DEVICE_DENIED;
p->devid = device->instance_id;
p->next = NULL;
pending_tail->next = p;
pending_tail = p;
}
}
ReleaseCamera(device);
if (pending.next) { SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
SDL_assert(camera_driver.pending_events_tail != NULL);
SDL_assert(camera_driver.pending_events_tail->next == NULL);
camera_driver.pending_events_tail->next = pending.next;
camera_driver.pending_events_tail = pending_tail;
SDL_UnlockRWLock(camera_driver.device_hash_lock);
}
}
typedef struct FindOnePhysicalCameraByCallbackData
{
bool (*callback)(SDL_Camera *device, void *userdata);
void *userdata;
SDL_Camera *device;
} FindOnePhysicalCameraByCallbackData;
static bool SDLCALL FindOnePhysicalCameraByCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
FindOnePhysicalCameraByCallbackData *data = (FindOnePhysicalCameraByCallbackData *) userdata;
SDL_Camera *device = (SDL_Camera *) value;
if (data->callback(device, data->userdata)) {
data->device = device;
return false; }
return true; }
SDL_Camera *SDL_FindPhysicalCameraByCallback(bool (*callback)(SDL_Camera *device, void *userdata), void *userdata)
{
if (!SDL_GetCurrentCameraDriver()) {
SDL_SetError("Camera subsystem is not initialized");
return NULL;
}
FindOnePhysicalCameraByCallbackData data = { callback, userdata, NULL };
SDL_LockRWLockForReading(camera_driver.device_hash_lock);
SDL_IterateHashTable(camera_driver.device_hash, FindOnePhysicalCameraByCallback, &data);
SDL_UnlockRWLock(camera_driver.device_hash_lock);
if (!data.device) {
SDL_SetError("Device not found");
}
return data.device;
}
void SDL_CloseCamera(SDL_Camera *camera)
{
SDL_Camera *device = camera; ClosePhysicalCamera(device);
}
bool SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec)
{
bool result;
CHECK_PARAM(!camera) {
return SDL_InvalidParamError("camera");
}
CHECK_PARAM(!spec) {
return SDL_InvalidParamError("spec");
}
SDL_Camera *device = camera; ObtainPhysicalCameraObj(device);
if (device->permission > SDL_CAMERA_PERMISSION_STATE_PENDING) {
SDL_copyp(spec, &device->spec);
result = true;
} else {
SDL_zerop(spec);
result = SDL_SetError("Camera permission has not been granted");
}
ReleaseCamera(device);
return result;
}
const char *SDL_GetCameraName(SDL_CameraID instance_id)
{
const char *result = NULL;
SDL_Camera *device = ObtainPhysicalCamera(instance_id);
if (device) {
result = SDL_GetPersistentString(device->name);
ReleaseCamera(device);
}
return result;
}
SDL_CameraPosition SDL_GetCameraPosition(SDL_CameraID instance_id)
{
SDL_CameraPosition result = SDL_CAMERA_POSITION_UNKNOWN;
SDL_Camera *device = ObtainPhysicalCamera(instance_id);
if (device) {
result = device->position;
ReleaseCamera(device);
}
return result;
}
typedef struct GetOneCameraData
{
SDL_CameraID *result;
int devs_seen;
} GetOneCameraData;
static bool SDLCALL GetOneCamera(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
GetOneCameraData *data = (GetOneCameraData *) userdata;
data->result[data->devs_seen++] = (SDL_CameraID) (uintptr_t) key;
return true; }
SDL_CameraID *SDL_GetCameras(int *count)
{
int dummy_count;
if (!count) {
count = &dummy_count;
}
if (!SDL_GetCurrentCameraDriver()) {
*count = 0;
SDL_SetError("Camera subsystem is not initialized");
return NULL;
}
SDL_CameraID *result = NULL;
SDL_LockRWLockForReading(camera_driver.device_hash_lock);
int num_devices = SDL_GetAtomicInt(&camera_driver.device_count);
result = (SDL_CameraID *) SDL_malloc((num_devices + 1) * sizeof (SDL_CameraID));
if (!result) {
num_devices = 0;
} else {
GetOneCameraData data = { result, 0 };
SDL_IterateHashTable(camera_driver.device_hash, GetOneCamera, &data);
SDL_assert(data.devs_seen == num_devices);
result[num_devices] = 0; }
SDL_UnlockRWLock(camera_driver.device_hash_lock);
*count = num_devices;
return result;
}
SDL_CameraSpec **SDL_GetCameraSupportedFormats(SDL_CameraID instance_id, int *count)
{
if (count) {
*count = 0;
}
SDL_Camera *device = ObtainPhysicalCamera(instance_id);
if (!device) {
return NULL;
}
int i;
int num_specs = device->num_specs;
SDL_CameraSpec **result = (SDL_CameraSpec **) SDL_malloc(((num_specs + 1) * sizeof(*result)) + (num_specs * sizeof (**result)));
if (result) {
SDL_CameraSpec *specs = (SDL_CameraSpec *)(result + (num_specs + 1));
SDL_memcpy(specs, device->all_specs, num_specs * sizeof(*specs));
for (i = 0; i < num_specs; ++i) {
result[i] = specs++;
}
result[i] = NULL;
if (count) {
*count = num_specs;
}
}
ReleaseCamera(device);
return result;
}
void SDL_CameraThreadSetup(SDL_Camera *device)
{
#ifdef SDL_VIDEO_DRIVER_ANDROID
#else
SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH);
#endif
}
bool SDL_CameraThreadIterate(SDL_Camera *device)
{
SDL_LockMutex(device->lock);
if (SDL_GetAtomicInt(&device->shutdown)) {
SDL_UnlockMutex(device->lock);
return false; }
const int permission = device->permission;
if (permission <= SDL_CAMERA_PERMISSION_STATE_PENDING) {
SDL_UnlockMutex(device->lock);
return (permission < SDL_CAMERA_PERMISSION_STATE_PENDING) ? false : true; }
bool failed = false; SDL_Surface *acquired = NULL;
SDL_Surface *output_surface = NULL;
SurfaceList *slist = NULL;
Uint64 timestampNS = 0;
float rotation = 0.0f;
const SDL_CameraFrameResult rc = device->AcquireFrame(device, device->acquire_surface, ×tampNS, &rotation);
if (rc == SDL_CAMERA_FRAME_READY) { #if DEBUG_CAMERA
SDL_Log("CAMERA: New frame available! pixels=%p pitch=%d", device->acquire_surface->pixels, device->acquire_surface->pitch);
#endif
if (device->drop_frames > 0) {
#if DEBUG_CAMERA
SDL_Log("CAMERA: Dropping an initial frame");
#endif
device->drop_frames--;
device->ReleaseFrame(device, device->acquire_surface);
device->acquire_surface->pixels = NULL;
device->acquire_surface->pitch = 0;
} else if (device->empty_output_surfaces.next == NULL) {
#if DEBUG_CAMERA
SDL_Log("CAMERA: No empty output surfaces! Dropping frame!");
#endif
device->ReleaseFrame(device, device->acquire_surface);
device->acquire_surface->pixels = NULL;
device->acquire_surface->pitch = 0;
} else {
if (!device->adjust_timestamp) {
device->adjust_timestamp = SDL_GetTicksNS();
device->base_timestamp = timestampNS;
}
timestampNS = (timestampNS - device->base_timestamp) + device->adjust_timestamp;
slist = device->empty_output_surfaces.next;
output_surface = slist->surface;
device->empty_output_surfaces.next = slist->next;
acquired = device->acquire_surface;
slist->timestampNS = timestampNS;
}
} else if (rc == SDL_CAMERA_FRAME_SKIP) { #if 0 #endif
} else { #if DEBUG_CAMERA
SDL_Log("CAMERA: dev[%p] error AcquireFrame: %s", device, SDL_GetError());
#endif
failed = true;
}
SDL_UnlockMutex(device->lock);
if (failed) {
SDL_assert(slist == NULL);
SDL_assert(acquired == NULL);
SDL_CameraDisconnected(device); } else if (acquired) { SDL_assert(slist != NULL);
if (!device->needs_scaling && !device->needs_conversion) { #if DEBUG_CAMERA
SDL_Log("CAMERA: Frame is going through without conversion!");
#endif
output_surface->w = acquired->w;
output_surface->h = acquired->h;
output_surface->pixels = acquired->pixels;
output_surface->pitch = acquired->pitch;
} else { #if DEBUG_CAMERA
SDL_Log("CAMERA: Frame is getting converted!");
#endif
SDL_Surface *srcsurf = acquired;
if (device->needs_scaling == -1) { SDL_Surface *dstsurf = device->needs_conversion ? device->conversion_surface : output_surface;
SDL_StretchSurface(srcsurf, NULL, dstsurf, NULL, SDL_SCALEMODE_NEAREST); srcsurf = dstsurf;
}
if (device->needs_conversion) {
SDL_Surface *dstsurf = (device->needs_scaling == 1) ? device->conversion_surface : output_surface;
SDL_ConvertPixels(srcsurf->w, srcsurf->h,
srcsurf->format, srcsurf->pixels, srcsurf->pitch,
dstsurf->format, dstsurf->pixels, dstsurf->pitch);
srcsurf = dstsurf;
}
if (device->needs_scaling == 1) { SDL_StretchSurface(srcsurf, NULL, output_surface, NULL, SDL_SCALEMODE_NEAREST); }
device->ReleaseFrame(device, acquired);
}
acquired->pixels = NULL;
acquired->pitch = 0;
SDL_SetFloatProperty(SDL_GetSurfaceProperties(output_surface), SDL_PROP_SURFACE_ROTATION_FLOAT, rotation);
SDL_LockMutex(device->lock);
slist->next = device->filled_output_surfaces.next;
device->filled_output_surfaces.next = slist;
SDL_UnlockMutex(device->lock);
}
return true; }
void SDL_CameraThreadShutdown(SDL_Camera *device)
{
}
static int SDLCALL CameraThread(void *devicep)
{
SDL_Camera *device = (SDL_Camera *) devicep;
#if DEBUG_CAMERA
SDL_Log("CAMERA: dev[%p] Start thread 'CameraThread'", devicep);
#endif
RefPhysicalCamera(device);
SDL_assert(device != NULL);
SDL_CameraThreadSetup(device);
do {
if (!device->WaitDevice(device)) {
SDL_CameraDisconnected(device); }
} while (SDL_CameraThreadIterate(device));
SDL_CameraThreadShutdown(device);
UnrefPhysicalCamera(device);
#if DEBUG_CAMERA
SDL_Log("CAMERA: dev[%p] End thread 'CameraThread'", devicep);
#endif
return 0;
}
bool SDL_PrepareCameraSurfaces(SDL_Camera *device)
{
SDL_CameraSpec *appspec = &device->spec; const SDL_CameraSpec *devspec = &device->actual_spec;
SDL_assert(device->acquire_surface == NULL); SDL_assert(devspec->format != SDL_PIXELFORMAT_UNKNOWN); SDL_assert(devspec->width >= 0); SDL_assert(devspec->height >= 0);
if (appspec->width <= 0 || appspec->height <= 0) {
appspec->width = devspec->width;
appspec->height = devspec->height;
}
if (appspec->format == SDL_PIXELFORMAT_UNKNOWN) {
appspec->format = devspec->format;
}
if (appspec->framerate_denominator == 0) {
appspec->framerate_numerator = devspec->framerate_numerator;
appspec->framerate_denominator = devspec->framerate_denominator;
}
if ((devspec->width == appspec->width) && (devspec->height == appspec->height)) {
device->needs_scaling = 0;
} else {
const Uint64 srcarea = ((Uint64) devspec->width) * ((Uint64) devspec->height);
const Uint64 dstarea = ((Uint64) appspec->width) * ((Uint64) appspec->height);
if (dstarea <= srcarea) {
device->needs_scaling = -1; } else {
device->needs_scaling = 1; }
}
device->needs_conversion = (devspec->format != appspec->format);
device->acquire_surface = SDL_CreateSurfaceFrom(devspec->width, devspec->height, devspec->format, NULL, 0);
if (!device->acquire_surface) {
goto failed;
}
SDL_SetSurfaceColorspace(device->acquire_surface, devspec->colorspace);
if (device->needs_scaling && device->needs_conversion) {
const bool downscaling_first = (device->needs_scaling < 0);
const SDL_CameraSpec *s = downscaling_first ? appspec : devspec;
const SDL_PixelFormat fmt = downscaling_first ? devspec->format : appspec->format;
device->conversion_surface = SDL_CreateSurface(s->width, s->height, fmt);
if (!device->conversion_surface) {
goto failed;
}
SDL_SetSurfaceColorspace(device->conversion_surface, devspec->colorspace);
}
for (int i = 0; i < (SDL_arraysize(device->output_surfaces) - 1); i++) {
device->output_surfaces[i].next = &device->output_surfaces[i + 1];
}
device->empty_output_surfaces.next = device->output_surfaces;
for (int i = 0; i < SDL_arraysize(device->output_surfaces); i++) {
SDL_Surface *surf;
if (device->needs_scaling || device->needs_conversion) {
surf = SDL_CreateSurface(appspec->width, appspec->height, appspec->format);
} else {
surf = SDL_CreateSurfaceFrom(appspec->width, appspec->height, appspec->format, NULL, 0);
}
if (!surf) {
goto failed;
}
SDL_SetSurfaceColorspace(surf, devspec->colorspace);
device->output_surfaces[i].surface = surf;
}
return true;
failed:
if (device->acquire_surface) {
SDL_DestroySurface(device->acquire_surface);
device->acquire_surface = NULL;
}
if (device->conversion_surface) {
SDL_DestroySurface(device->conversion_surface);
device->conversion_surface = NULL;
}
for (int i = 0; i < SDL_arraysize(device->output_surfaces); i++) {
SDL_Surface *surf = device->output_surfaces[i].surface;
if (surf) {
SDL_DestroySurface(surf);
}
}
SDL_zeroa(device->output_surfaces);
return false;
}
static void ChooseBestCameraSpec(SDL_Camera *device, const SDL_CameraSpec *spec, SDL_CameraSpec *closest)
{
SDL_zerop(closest);
if (device->num_specs == 0) { if (spec) {
SDL_copyp(closest, spec);
}
return;
} else if (!spec) { SDL_copyp(closest, &device->all_specs[0]);
} else { const int num_specs = device->num_specs;
int wantw = spec->width;
int wanth = spec->height;
if (wantw > 0 && wanth > 0) {
const float wantaspect = ((float)wantw) / ((float)wanth);
const float epsilon = 1e-6f;
float closestaspect = -9999999.0f;
float closestdiff = 999999.0f;
int closestdiffw = 9999999;
for (int i = 0; i < num_specs; i++) {
const SDL_CameraSpec *thisspec = &device->all_specs[i];
const int thisw = thisspec->width;
const int thish = thisspec->height;
const float thisaspect = ((float)thisw) / ((float)thish);
const float aspectdiff = SDL_fabsf(wantaspect - thisaspect);
const float diff = SDL_fabsf(closestaspect - thisaspect);
const int diffw = SDL_abs(thisw - wantw);
if (diff < epsilon) { if (diffw < closestdiffw) {
closestdiffw = diffw;
closest->width = thisw;
closest->height = thish;
}
} else if (aspectdiff < closestdiff) { closestdiff = aspectdiff;
closestaspect = thisaspect;
closestdiffw = diffw;
closest->width = thisw;
closest->height = thish;
}
}
} else {
SDL_copyp(closest, &device->all_specs[0]);
}
SDL_assert(closest->width > 0);
SDL_assert(closest->height > 0);
const SDL_PixelFormat wantfmt = spec->format;
SDL_PixelFormat best_format = SDL_PIXELFORMAT_UNKNOWN;
SDL_Colorspace best_colorspace = SDL_COLORSPACE_UNKNOWN;
for (int i = 0; i < num_specs; i++) {
const SDL_CameraSpec *thisspec = &device->all_specs[i];
if ((thisspec->width == closest->width) && (thisspec->height == closest->height)) {
if (best_format == SDL_PIXELFORMAT_UNKNOWN) {
best_format = thisspec->format; best_colorspace = thisspec->colorspace;
}
if (thisspec->format == wantfmt) {
best_format = thisspec->format;
best_colorspace = thisspec->colorspace;
break; }
}
}
SDL_assert(best_format != SDL_PIXELFORMAT_UNKNOWN);
SDL_assert(best_colorspace != SDL_COLORSPACE_UNKNOWN);
closest->format = best_format;
closest->colorspace = best_colorspace;
const float wantfps = spec->framerate_denominator ? ((float)spec->framerate_numerator / spec->framerate_denominator) : 0.0f;
float closestfps = 9999999.0f;
for (int i = 0; i < num_specs; i++) {
const SDL_CameraSpec *thisspec = &device->all_specs[i];
if ((thisspec->format == closest->format) && (thisspec->width == closest->width) && (thisspec->height == closest->height)) {
if ((thisspec->framerate_numerator == spec->framerate_numerator) && (thisspec->framerate_denominator == spec->framerate_denominator)) {
closest->framerate_numerator = thisspec->framerate_numerator;
closest->framerate_denominator = thisspec->framerate_denominator;
break; }
const float thisfps = thisspec->framerate_denominator ? ((float)thisspec->framerate_numerator / thisspec->framerate_denominator) : 0.0f;
const float fpsdiff = SDL_fabsf(wantfps - thisfps);
if (fpsdiff < closestfps) { closestfps = fpsdiff;
closest->framerate_numerator = thisspec->framerate_numerator;
closest->framerate_denominator = thisspec->framerate_denominator;
}
}
}
}
SDL_assert(closest->width > 0);
SDL_assert(closest->height > 0);
SDL_assert(closest->format != SDL_PIXELFORMAT_UNKNOWN);
}
SDL_Camera *SDL_OpenCamera(SDL_CameraID instance_id, const SDL_CameraSpec *spec)
{
SDL_Camera *device = ObtainPhysicalCamera(instance_id);
if (!device) {
return NULL;
}
if (device->hidden != NULL) {
ReleaseCamera(device);
SDL_SetError("Camera already opened"); return NULL;
}
SDL_SetAtomicInt(&device->shutdown, 0);
device->WaitDevice = camera_driver.impl.WaitDevice;
device->AcquireFrame = camera_driver.impl.AcquireFrame;
device->ReleaseFrame = camera_driver.impl.ReleaseFrame;
SDL_CameraSpec closest;
ChooseBestCameraSpec(device, spec, &closest);
#if DEBUG_CAMERA
SDL_Log("CAMERA: App wanted [(%dx%d) fmt=%s framerate=%d/%d], chose [(%dx%d) fmt=%s framerate=%d/%d]",
spec ? spec->width : -1, spec ? spec->height : -1, spec ? SDL_GetPixelFormatName(spec->format) : "(null)", spec ? spec->framerate_numerator : -1, spec ? spec->framerate_denominator : -1,
closest.width, closest.height, SDL_GetPixelFormatName(closest.format), closest.framerate_numerator, closest.framerate_denominator);
#endif
if (!camera_driver.impl.OpenDevice(device, &closest)) {
ClosePhysicalCamera(device); ReleaseCamera(device);
return NULL;
}
SDL_copyp(&device->spec, spec ? spec : &closest);
SDL_copyp(&device->actual_spec, &closest);
if (closest.format != SDL_PIXELFORMAT_UNKNOWN) {
if (!SDL_PrepareCameraSurfaces(device)) {
ClosePhysicalCamera(device);
ReleaseCamera(device);
return NULL;
}
}
device->drop_frames = 1;
if (!camera_driver.impl.ProvidesOwnCallbackThread) {
char threadname[64];
SDL_GetCameraThreadName(device, threadname, sizeof (threadname));
device->thread = SDL_CreateThread(CameraThread, threadname, device);
if (!device->thread) {
ClosePhysicalCamera(device);
ReleaseCamera(device);
SDL_SetError("Couldn't create camera thread");
return NULL;
}
}
RefPhysicalCamera(device);
ReleaseCamera(device);
return device; }
SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS)
{
if (timestampNS) {
*timestampNS = 0;
}
CHECK_PARAM(!camera) {
SDL_InvalidParamError("camera");
return NULL;
}
SDL_Camera *device = camera;
ObtainPhysicalCameraObj(device);
if (device->permission <= SDL_CAMERA_PERMISSION_STATE_PENDING) {
ReleaseCamera(device);
SDL_SetError("Camera permission has not been granted");
return NULL;
}
SDL_Surface *result = NULL;
SurfaceList *slistprev = &device->filled_output_surfaces;
SurfaceList *slist = slistprev;
while (slist->next) {
slistprev = slist;
slist = slist->next;
}
const bool list_is_empty = (slist == slistprev);
if (!list_is_empty) { if (timestampNS) {
*timestampNS = slist->timestampNS;
}
result = slist->surface;
slistprev->next = slist->next; slist->next = device->app_held_output_surfaces.next; device->app_held_output_surfaces.next = slist;
}
ReleaseCamera(device);
return result;
}
void SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame)
{
if (!camera || !frame) {
return;
}
SDL_Camera *device = camera; ObtainPhysicalCameraObj(device);
SurfaceList *slistprev = &device->app_held_output_surfaces;
SurfaceList *slist;
for (slist = slistprev->next; slist != NULL; slist = slist->next) {
if (slist->surface == frame) {
break;
}
slistprev = slist;
}
if (!slist) {
ReleaseCamera(device);
return;
}
if (!device->needs_conversion && !device->needs_scaling) {
device->ReleaseFrame(device, frame);
frame->pixels = NULL;
frame->pitch = 0;
}
slist->timestampNS = 0;
slistprev->next = slist->next;
slist->next = device->empty_output_surfaces.next;
device->empty_output_surfaces.next = slist;
ReleaseCamera(device);
}
SDL_CameraID SDL_GetCameraID(SDL_Camera *camera)
{
SDL_CameraID result;
CHECK_PARAM(!camera) {
SDL_InvalidParamError("camera");
return 0;
}
SDL_Camera *device = camera; ObtainPhysicalCameraObj(device);
result = device->instance_id;
ReleaseCamera(device);
return result;
}
SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera)
{
SDL_PropertiesID result;
CHECK_PARAM(!camera) {
SDL_InvalidParamError("camera");
return 0;
}
SDL_Camera *device = camera; ObtainPhysicalCameraObj(device);
if (device->props == 0) {
device->props = SDL_CreateProperties();
}
result = device->props;
ReleaseCamera(device);
return result;
}
SDL_CameraPermissionState SDL_GetCameraPermissionState(SDL_Camera *camera)
{
SDL_CameraPermissionState result;
CHECK_PARAM(!camera) {
SDL_InvalidParamError("camera");
return SDL_CAMERA_PERMISSION_STATE_DENIED;
}
SDL_Camera *device = camera; ObtainPhysicalCameraObj(device);
result = device->permission;
ReleaseCamera(device);
return result;
}
static void CompleteCameraEntryPoints(void)
{
#define FILL_STUB(x) SDL_assert(camera_driver.impl.x != NULL)
FILL_STUB(DetectDevices);
FILL_STUB(OpenDevice);
FILL_STUB(CloseDevice);
FILL_STUB(AcquireFrame);
FILL_STUB(ReleaseFrame);
FILL_STUB(FreeDeviceHandle);
FILL_STUB(Deinitialize);
#undef FILL_STUB
}
void SDL_QuitCamera(void)
{
if (!camera_driver.name) { return;
}
SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
SDL_SetAtomicInt(&camera_driver.shutting_down, 1);
SDL_HashTable *device_hash = camera_driver.device_hash;
camera_driver.device_hash = NULL;
SDL_PendingCameraEvent *pending_events = camera_driver.pending_events.next;
camera_driver.pending_events.next = NULL;
SDL_SetAtomicInt(&camera_driver.device_count, 0);
SDL_UnlockRWLock(camera_driver.device_hash_lock);
SDL_PendingCameraEvent *pending_next = NULL;
for (SDL_PendingCameraEvent *i = pending_events; i; i = pending_next) {
pending_next = i->next;
SDL_free(i);
}
SDL_DestroyHashTable(device_hash);
camera_driver.impl.Deinitialize();
SDL_DestroyRWLock(camera_driver.device_hash_lock);
SDL_zero(camera_driver);
}
static void SDLCALL DestroyCameraHashItem(void *userdata, const void *key, const void *value)
{
SDL_Camera *device = (SDL_Camera *) value;
#if DEBUG_CAMERA
SDL_Log("DESTROYING CAMERA '%s'", device->name);
#endif
ClosePhysicalCamera(device);
camera_driver.impl.FreeDeviceHandle(device);
SDL_DestroyMutex(device->lock);
SDL_free(device->all_specs);
SDL_free(device->name);
SDL_free(device);
}
bool SDL_CameraInit(const char *driver_name)
{
if (SDL_GetCurrentCameraDriver()) {
SDL_QuitCamera(); }
SDL_RWLock *device_hash_lock = SDL_CreateRWLock(); if (!device_hash_lock) {
return false;
}
SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, DestroyCameraHashItem, NULL);
if (!device_hash) {
SDL_DestroyRWLock(device_hash_lock);
return false;
}
if (!driver_name) {
driver_name = SDL_GetHint(SDL_HINT_CAMERA_DRIVER);
}
bool initialized = false;
bool tried_to_init = false;
if (driver_name && (*driver_name != 0)) {
char *driver_name_copy = SDL_strdup(driver_name);
const char *driver_attempt = driver_name_copy;
if (!driver_name_copy) {
SDL_DestroyRWLock(device_hash_lock);
SDL_DestroyHashTable(device_hash);
return false;
}
while (driver_attempt && (*driver_attempt != 0) && !initialized) {
char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
if (driver_attempt_end) {
*driver_attempt_end = '\0';
}
for (int i = 0; bootstrap[i]; i++) {
if (SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) {
tried_to_init = true;
SDL_zero(camera_driver);
camera_driver.pending_events_tail = &camera_driver.pending_events;
camera_driver.device_hash_lock = device_hash_lock;
camera_driver.device_hash = device_hash;
if (bootstrap[i]->init(&camera_driver.impl)) {
camera_driver.name = bootstrap[i]->name;
camera_driver.desc = bootstrap[i]->desc;
initialized = true;
}
break;
}
}
driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
}
SDL_free(driver_name_copy);
} else {
for (int i = 0; !initialized && bootstrap[i]; i++) {
if (bootstrap[i]->demand_only) {
continue;
}
tried_to_init = true;
SDL_zero(camera_driver);
camera_driver.pending_events_tail = &camera_driver.pending_events;
camera_driver.device_hash_lock = device_hash_lock;
camera_driver.device_hash = device_hash;
if (bootstrap[i]->init(&camera_driver.impl)) {
camera_driver.name = bootstrap[i]->name;
camera_driver.desc = bootstrap[i]->desc;
initialized = true;
}
}
}
if (initialized) {
SDL_DebugLogBackend("camera", camera_driver.name);
} else {
if (!tried_to_init) {
if (driver_name) {
SDL_SetError("Camera driver '%s' not available", driver_name);
} else {
SDL_SetError("No available camera driver");
}
}
SDL_zero(camera_driver);
SDL_DestroyRWLock(device_hash_lock);
SDL_DestroyHashTable(device_hash);
return false; }
CompleteCameraEntryPoints();
camera_driver.impl.DetectDevices();
return true;
}
void SDL_UpdateCamera(void)
{
SDL_LockRWLockForReading(camera_driver.device_hash_lock);
SDL_PendingCameraEvent *pending_events = camera_driver.pending_events.next;
SDL_UnlockRWLock(camera_driver.device_hash_lock);
if (!pending_events) {
return; }
SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
pending_events = camera_driver.pending_events.next; camera_driver.pending_events.next = NULL;
camera_driver.pending_events_tail = &camera_driver.pending_events;
SDL_UnlockRWLock(camera_driver.device_hash_lock);
SDL_PendingCameraEvent *pending_next = NULL;
for (SDL_PendingCameraEvent *i = pending_events; i; i = pending_next) {
pending_next = i->next;
if (SDL_EventEnabled(i->type)) {
SDL_Event event;
SDL_zero(event);
event.type = i->type;
event.cdevice.which = (Uint32) i->devid;
SDL_PushEvent(&event);
}
SDL_free(i);
}
}