From d3e6ef3cc6e04139204302f07b59a08b3d41cbd1 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 15 Dec 2023 11:45:11 -0500 Subject: [PATCH] camera: Massive code reworking. - Simplified public API, simplified backend interface. - Camera device hotplug events. - Thread code is split up so it backends that provide own threads can use it. - Added "dummy" backend. Note that CoreMedia (Apple) and Android backends need to be updated, as does the testcamera app (testcameraminimal works). --- include/SDL3/SDL_camera.h | 365 ++--- include/SDL3/SDL_events.h | 17 + src/camera/SDL_camera.c | 1360 ++++++++++++------- src/camera/SDL_camera_c.h | 3 + src/camera/SDL_syscamera.h | 132 +- src/camera/coremedia/SDL_camera_coremedia.m | 15 +- src/camera/dummy/SDL_camera_dummy.c | 80 ++ src/camera/v4l2/SDL_camera_v4l2.c | 1208 ++++++---------- src/dynapi/SDL_dynapi.sym | 17 +- src/dynapi/SDL_dynapi_overrides.h | 17 +- src/dynapi/SDL_dynapi_procs.h | 27 +- src/events/SDL_events.c | 14 + test/testcamera.c | 10 +- test/testcameraminimal.c | 94 +- 14 files changed, 1772 insertions(+), 1587 deletions(-) create mode 100644 src/camera/dummy/SDL_camera_dummy.c diff --git a/include/SDL3/SDL_camera.h b/include/SDL3/SDL_camera.h index e2b13cfe8..eb6a9f2d8 100644 --- a/include/SDL3/SDL_camera.h +++ b/include/SDL3/SDL_camera.h @@ -49,23 +49,16 @@ typedef Uint32 SDL_CameraDeviceID; /** - * The structure used to identify an SDL camera device + * The structure used to identify an opened SDL camera */ -struct SDL_CameraDevice; -typedef struct SDL_CameraDevice SDL_CameraDevice; - -#define SDL_CAMERA_ALLOW_ANY_CHANGE 1 +struct SDL_Camera; +typedef struct SDL_Camera SDL_Camera; /** * SDL_CameraSpec structure * - * Only those field can be 'desired' when configuring the device: - * - format - * - width - * - height - * - * \sa SDL_GetCameraFormat - * \sa SDL_GetCameraFrameSize + * \sa SDL_GetCameraDeviceSupportedSpecs + * \sa SDL_GetCameraSpec * */ typedef struct SDL_CameraSpec @@ -75,39 +68,6 @@ typedef struct SDL_CameraSpec int height; /**< Frame height */ } SDL_CameraSpec; -/** - * SDL Camera Status - * - * Change states but calling the function in this order: - * - * SDL_OpenCamera() - * SDL_SetCameraSpec() -> Init - * SDL_StartCamera() -> Playing - * SDL_StopCamera() -> Stopped - * SDL_CloseCamera() - * - */ -typedef enum -{ - SDL_CAMERA_FAIL = -1, /**< Failed */ - SDL_CAMERA_INIT = 0, /**< Init, spec hasn't been set */ - SDL_CAMERA_STOPPED, /**< Stopped */ - SDL_CAMERA_PLAYING /**< Playing */ -} SDL_CameraStatus; - -/** - * SDL Video Capture Status - */ -typedef struct SDL_CameraFrame -{ - Uint64 timestampNS; /**< Frame timestamp in nanoseconds when read from the driver */ - int num_planes; /**< Number of planes */ - Uint8 *data[3]; /**< Pointer to data of i-th plane */ - int pitch[3]; /**< Pitch of i-th plane */ - void *internal; /**< Private field */ -} SDL_CameraFrame; - - /** * Use this function to get the number of built-in camera drivers. * @@ -176,11 +136,13 @@ extern DECLSPEC const char *SDLCALL SDL_GetCurrentCameraDriver(void); /** * Get a list of currently connected camera devices. * - * \param count a pointer filled in with the number of camera devices + * \param count a pointer filled in with the number of camera devices. Can be NULL. * \returns a 0 terminated array of camera instance IDs which should be * freed with SDL_free(), or NULL on error; call SDL_GetError() for * more details. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_OpenCamera @@ -188,237 +150,202 @@ extern DECLSPEC const char *SDLCALL SDL_GetCurrentCameraDriver(void); extern DECLSPEC SDL_CameraDeviceID *SDLCALL SDL_GetCameraDevices(int *count); /** - * Open a Video Capture device + * Get the list of native formats/sizes a camera supports. + * + * This returns a list of all formats and frame sizes that a specific + * camera can offer. This is useful if your app can accept a variety + * of image formats and sizes and so want to find the optimal spec + * that doesn't require conversion. + * + * This function isn't strictly required; if you call SDL_OpenCameraDevice + * with a NULL spec, SDL will choose a native format for you, and if you + * instead specify a desired format, it will transparently convert to the + * requested format on your behalf. + * + * If `count` is not NULL, it will be filled with the number of elements + * in the returned array. Additionally, the last element of the array + * has all fields set to zero (this element is not included in `count`). + * + * The returned list is owned by the caller, and should be released with + * SDL_free() when no longer needed. + * + * \param devid the camera device instance ID to query. + * \param count a pointer filled in with the number of elements in the list. Can be NULL. + * \returns a 0 terminated array of SDL_CameraSpecs, which should be + * freed with SDL_free(), or NULL on error; call SDL_GetError() for + * more details. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetCameraDevices + * \sa SDL_OpenCameraDevice + */ +extern DECLSPEC SDL_CameraSpec *SDLCALL SDL_GetCameraDeviceSupportedSpecs(SDL_CameraDeviceID devid, int *count); + +/** + * Get human-readable device name for a camera. + * + * The returned string is owned by the caller; please release it with + * SDL_free() when done with it. * * \param instance_id the camera device instance ID + * \returns Human-readable device name, or NULL on error; call SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetCameraDevices + */ +extern DECLSPEC char * SDLCALL SDL_GetCameraDeviceName(SDL_CameraDeviceID instance_id); + +/** + * Open a video capture device (a "camera"). + * + * You can open the device with any reasonable spec, and if the hardware can't + * directly support it, it will convert data seamlessly to the requested + * format. This might incur overhead, including scaling of image data. + * + * If you would rather accept whatever format the device offers, you can + * pass a NULL spec here and it will choose one for you (and you can use + * SDL_Surface's conversion/scaling functions directly if necessary). + * + * You can call SDL_GetCameraSpec() to get the actual data format if + * passing a NULL spec here. You can see the exact specs a device can + * support without conversion with SDL_GetCameraSupportedSpecs(). + * + * \param instance_id the camera device instance ID + * \param spec The desired format for data the device will provide. Can be NULL. * \returns device, or NULL on failure; call SDL_GetError() for more * information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_GetCameraDeviceName * \sa SDL_GetCameraDevices * \sa SDL_OpenCameraWithSpec */ -extern DECLSPEC SDL_CameraDevice *SDLCALL SDL_OpenCamera(SDL_CameraDeviceID instance_id); +extern DECLSPEC SDL_Camera *SDLCALL SDL_OpenCameraDevice(SDL_CameraDeviceID instance_id, const SDL_CameraSpec *spec); /** - * Set specification + * Get the instance ID of an opened camera. * - * \param device opened camera device - * \param desired desired camera spec - * \param obtained obtained camera spec - * \param allowed_changes allow changes or not - * \returns 0 on success or a negative error code on failure; call + * \param device an SDL_Camera to query + * \returns the instance ID of the specified camera on success or 0 on + * failure; call SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenCameraDevice + */ +extern DECLSPEC SDL_CameraDeviceID SDLCALL SDL_GetCameraInstanceID(SDL_Camera *camera); + +/** + * Get the properties associated with an opened camera. + * + * \param device the SDL_Camera obtained from SDL_OpenCameraDevice() + * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_OpenCamera - * \sa SDL_OpenCameraWithSpec - * \sa SDL_GetCameraSpec - */ -extern DECLSPEC int SDLCALL SDL_SetCameraSpec(SDL_CameraDevice *device, - const SDL_CameraSpec *desired, - SDL_CameraSpec *obtained, - int allowed_changes); - -/** - * Open a Video Capture device and set specification - * - * \param instance_id the camera device instance ID - * \param desired desired camera spec - * \param obtained obtained camera spec - * \param allowed_changes allow changes or not - * \returns device, or NULL on failure; call SDL_GetError() for more - * information. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.0.0. * - * \sa SDL_OpenCamera - * \sa SDL_SetCameraSpec - * \sa SDL_GetCameraSpec + * \sa SDL_GetProperty + * \sa SDL_SetProperty */ -extern DECLSPEC SDL_CameraDevice *SDLCALL SDL_OpenCameraWithSpec(SDL_CameraDeviceID instance_id, - const SDL_CameraSpec *desired, - SDL_CameraSpec *obtained, - int allowed_changes); +extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetCameraProperties(SDL_Camera *camera); /** - * Get device name + * Get the spec that a camera is using when generating images. * - * \param instance_id the camera device instance ID - * \returns device name, shouldn't be freed - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetCameraDevices - */ -extern DECLSPEC const char * SDLCALL SDL_GetCameraDeviceName(SDL_CameraDeviceID instance_id); - -/** - * Get the obtained camera spec + * Note that this might not be the native format of the hardware, as SDL + * might be converting to this format behind the scenes. * * \param device opened camera device * \param spec The SDL_CameraSpec to be initialized by this function. * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_SetCameraSpec - * \sa SDL_OpenCameraWithSpec - */ -extern DECLSPEC int SDLCALL SDL_GetCameraSpec(SDL_CameraDevice *device, SDL_CameraSpec *spec); - - -/** - * Get frame format of camera device. - * - * The value can be used to fill SDL_CameraSpec structure. - * - * \param device opened camera device - * \param index format between 0 and num -1 - * \param format pointer output format (SDL_PixelFormatEnum) - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.0.0. * - * \sa SDL_GetNumCameraFormats + * \sa SDL_OpenCameraDevice */ -extern DECLSPEC int SDLCALL SDL_GetCameraFormat(SDL_CameraDevice *device, - int index, - Uint32 *format); - -/** - * Number of available formats for the device - * - * \param device opened camera device - * \returns number of formats or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetCameraFormat - * \sa SDL_SetCameraSpec - */ -extern DECLSPEC int SDLCALL SDL_GetNumCameraFormats(SDL_CameraDevice *device); - -/** - * Get frame sizes of the device and the specified input format. - * - * The value can be used to fill SDL_CameraSpec structure. - * - * \param device opened camera device - * \param format a format that can be used by the device (SDL_PixelFormatEnum) - * \param index framesize between 0 and num -1 - * \param width output width - * \param height output height - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetNumCameraFrameSizes - */ -extern DECLSPEC int SDLCALL SDL_GetCameraFrameSize(SDL_CameraDevice *device, Uint32 format, int index, int *width, int *height); - -/** - * Number of different framesizes available for the device and pixel format. - * - * \param device opened camera device - * \param format frame pixel format (SDL_PixelFormatEnum) - * \returns number of framesizes or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetCameraFrameSize - * \sa SDL_SetCameraSpec - */ -extern DECLSPEC int SDLCALL SDL_GetNumCameraFrameSizes(SDL_CameraDevice *device, Uint32 format); - - -/** - * Get camera status - * - * \param device opened camera device - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_CameraStatus - */ -extern DECLSPEC SDL_CameraStatus SDLCALL SDL_GetCameraStatus(SDL_CameraDevice *device); - -/** - * Start camera - * - * \param device opened camera device - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_StopCamera - */ -extern DECLSPEC int SDLCALL SDL_StartCamera(SDL_CameraDevice *device); +extern DECLSPEC int SDLCALL SDL_GetCameraSpec(SDL_Camera *camera, SDL_CameraSpec *spec); /** * Acquire a frame. * * The frame is a memory pointer to the image data, whose size and format are - * given by the the obtained spec. + * given by the spec requested when opening the device. * - * Non blocking API. If there is a frame available, frame->num_planes is non - * 0. If frame->num_planes is 0 and returned code is 0, there is no frame at - * that time. + * This is a non blocking API. If there is a frame available, a non-NULL surface is + * returned, and timestampNS will be filled with a non-zero value. * - * After used, the frame should be released with SDL_ReleaseCameraFrame + * Note that an error case can also return NULL, but a NULL by itself is normal + * and just signifies that a new frame is not yet available. Note that even if a + * camera device fails outright (a USB camera is unplugged while in use, etc), SDL + * will send an event separately to notify the app, but continue to provide blank + * frames at ongoing intervals until SDL_CloseCamera() is called, so real + * failure here is almost always an out of memory condition. + * + * After use, the frame should be released with SDL_ReleaseCameraFrame(). If you + * don't do this, the system may stop providing more video! If the hardware is + * using DMA to write directly into memory, frames held too long may be overwritten + * with new data. + * + * Do not call SDL_FreeSurface() on the returned surface! It must be given back + * to the camera subsystem with SDL_ReleaseCameraFrame! * * \param device opened camera device - * \param frame pointer to get the frame - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. + * \param timestampNS a pointer filled in with the frame's timestamp, or 0 on error. Can be NULL. + * \returns A new frame of video on success, NULL if none is currently available. + * + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.0.0. * * \sa SDL_ReleaseCameraFrame */ -extern DECLSPEC int SDLCALL SDL_AcquireCameraFrame(SDL_CameraDevice *device, SDL_CameraFrame *frame); +extern DECLSPEC SDL_Surface * SDLCALL SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS); /** - * Release a frame. + * Release a frame of video acquired from a camera. * * Let the back-end re-use the internal buffer for camera. * - * All acquired frames should be released before closing the device. + * This function _must_ be called only on surface objects returned by + * SDL_AcquireCameraFrame(). This function should be called as quickly as + * possible after acquisition, as SDL keeps a small FIFO queue of surfaces + * for video frames; if surfaces aren't released in a timely manner, SDL + * may drop upcoming video frames from the camera. + * + * If the app needs to keep the surface for a significant time, they should + * make a copy of it and release the original. + * + * The app should not use the surface again after calling this function; + * assume the surface is freed and the pointer is invalid. * * \param device opened camera device - * \param frame frame pointer. + * \param frame The video frame surface to release. * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_AcquireCameraFrame */ -extern DECLSPEC int SDLCALL SDL_ReleaseCameraFrame(SDL_CameraDevice *device, SDL_CameraFrame *frame); - -/** - * Stop Video Capture - * - * \param device opened camera device - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_StartCamera - */ -extern DECLSPEC int SDLCALL SDL_StopCamera(SDL_CameraDevice *device); +extern DECLSPEC int SDLCALL SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame); /** * Use this function to shut down camera processing and close the @@ -426,12 +353,16 @@ extern DECLSPEC int SDLCALL SDL_StopCamera(SDL_CameraDevice *device); * * \param device opened camera device * + * \threadsafety It is safe to call this function from any thread, but + * no thread may reference `device` once this function + * is called. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_OpenCameraWithSpec * \sa SDL_OpenCamera */ -extern DECLSPEC void SDLCALL SDL_CloseCamera(SDL_CameraDevice *device); +extern DECLSPEC void SDLCALL SDL_CloseCamera(SDL_Camera *camera); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index d209801ed..74a39267c 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -205,6 +205,10 @@ typedef enum SDL_EVENT_PEN_BUTTON_DOWN, /**< Pressure-sensitive pen button pressed */ SDL_EVENT_PEN_BUTTON_UP, /**< Pressure-sensitive pen button released */ + /* Camera hotplug events */ + SDL_EVENT_CAMERA_DEVICE_ADDED = 0x1400, /**< A new camera device is available */ + SDL_EVENT_CAMERA_DEVICE_REMOVED, /**< A camera device has been removed. */ + /* Render events */ SDL_EVENT_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */ SDL_EVENT_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */ @@ -526,6 +530,18 @@ typedef struct SDL_AudioDeviceEvent Uint8 padding3; } SDL_AudioDeviceEvent; +/** + * Camera device event structure (event.cdevice.*) + */ +typedef struct SDL_CameraDeviceEvent +{ + Uint32 type; /**< ::SDL_EVENT_CAMERA_DEVICE_ADDED, or ::SDL_EVENT_CAMERA_DEVICE_REMOVED */ + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + SDL_CameraDeviceID which; /**< SDL_CameraDeviceID for the device being added or removed or changing */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; +} SDL_CameraDeviceEvent; /** * Touch finger event structure (event.tfinger.*) @@ -699,6 +715,7 @@ typedef union SDL_Event SDL_GamepadTouchpadEvent gtouchpad; /**< Gamepad touchpad event data */ SDL_GamepadSensorEvent gsensor; /**< Gamepad sensor event data */ SDL_AudioDeviceEvent adevice; /**< Audio device event data */ + SDL_CameraDeviceEvent cdevice; /**< Camera device event data */ SDL_SensorEvent sensor; /**< Sensor event data */ SDL_QuitEvent quit; /**< Quit request event data */ SDL_UserEvent user; /**< Custom event data */ diff --git a/src/camera/SDL_camera.c b/src/camera/SDL_camera.c index 5a8c08f8c..d9c581b04 100644 --- a/src/camera/SDL_camera.c +++ b/src/camera/SDL_camera.c @@ -25,6 +25,10 @@ #include "../video/SDL_pixels_c.h" #include "../thread/SDL_systhread.h" + +// A lot of this is a simplified version of SDL_audio.c; if fixing stuff here, +// maybe check that file, too. + // Available camera drivers static const CameraBootStrap *const bootstrap[] = { #ifdef SDL_CAMERA_DRIVER_V4L2 @@ -45,15 +49,6 @@ static const CameraBootStrap *const bootstrap[] = { static SDL_CameraDriver camera_driver; -// list node entries to share frames between SDL and user app -// !!! FIXME: do we need this struct? -typedef struct entry_t -{ - SDL_CameraFrame frame; -} entry_t; - -static SDL_CameraDevice *open_devices[16]; // !!! FIXME: remove limit - int SDL_GetNumCameraDrivers(void) { return SDL_arraysize(bootstrap) - 1; @@ -72,242 +67,435 @@ const char *SDL_GetCurrentCameraDriver(void) return camera_driver.name; } - -static void CloseCameraDevice(SDL_CameraDevice *device) +static void ClosePhysicalCameraDevice(SDL_CameraDevice *device) { if (!device) { return; } SDL_AtomicSet(&device->shutdown, 1); - SDL_AtomicSet(&device->enabled, 1); + +// !!! FIXME: the close_cond stuff from audio might help the race condition here. if (device->thread != NULL) { SDL_WaitThread(device->thread, NULL); - } - if (device->device_lock != NULL) { - SDL_DestroyMutex(device->device_lock); - } - if (device->acquiring_lock != NULL) { - SDL_DestroyMutex(device->acquiring_lock); + device->thread = NULL; } - const int n = SDL_arraysize(open_devices); - for (int i = 0; i < n; i++) { - if (open_devices[i] == device) { - open_devices[i] = NULL; + // release frames that are queued up somewhere... + if (!device->needs_conversion && !device->needs_scaling) { + for (SurfaceList *i = device->filled_output_surfaces.next; i != NULL; i = i->next) { + camera_driver.impl.ReleaseFrame(device, i->surface); } - } - - entry_t *entry = NULL; - while (device->buffer_queue != NULL) { - SDL_ListPop(&device->buffer_queue, (void**)&entry); - if (entry) { - SDL_CameraFrame f = entry->frame; - // Release frames not acquired, if any - if (f.timestampNS) { - camera_driver.impl.ReleaseFrame(device, &f); - } - SDL_free(entry); + for (SurfaceList *i = device->app_held_output_surfaces.next; i != NULL; i = i->next) { + camera_driver.impl.ReleaseFrame(device, i->surface); } } camera_driver.impl.CloseDevice(device); - SDL_free(device->dev_name); - SDL_free(device); + 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); + + device->filled_output_surfaces.next = NULL; + device->empty_output_surfaces.next = NULL; + device->app_held_output_surfaces.next = NULL; } -void SDL_CloseCamera(SDL_CameraDevice *device) +// this must not be called while `device` is still in a device list, or while a device's camera thread is still running. +static void DestroyPhysicalCameraDevice(SDL_CameraDevice *device) { + if (device) { + // Destroy any logical devices that still exist... + ClosePhysicalCameraDevice(device); + camera_driver.impl.FreeDeviceHandle(device); + SDL_DestroyMutex(device->lock); + SDL_free(device->all_specs); + SDL_free(device->name); + SDL_free(device); + } +} + + +// Don't hold the device lock when calling this, as we may destroy the device! +void UnrefPhysicalCameraDevice(SDL_CameraDevice *device) +{ + if (SDL_AtomicDecRef(&device->refcount)) { + // take it out of the device list. + SDL_LockRWLockForWriting(camera_driver.device_hash_lock); + if (SDL_RemoveFromHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id)) { + SDL_AtomicAdd(&camera_driver.device_count, -1); + } + SDL_UnlockRWLock(camera_driver.device_hash_lock); + DestroyPhysicalCameraDevice(device); // ...and nuke it. + } +} + +void RefPhysicalCameraDevice(SDL_CameraDevice *device) +{ + SDL_AtomicIncRef(&device->refcount); +} + +static void ObtainPhysicalCameraDeviceObj(SDL_CameraDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXMEL SDL_ACQUIRE +{ + if (device) { + RefPhysicalCameraDevice(device); + SDL_LockMutex(device->lock); + } +} + +static SDL_CameraDevice *ObtainPhysicalCameraDevice(SDL_CameraDeviceID devid) // !!! FIXME: SDL_ACQUIRE +{ + if (!SDL_GetCurrentCameraDriver()) { + SDL_SetError("Camera subsystem is not initialized"); + return NULL; + } + + SDL_CameraDevice *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_InvalidParamError("device"); + SDL_SetError("Invalid camera device instance ID"); } else { - CloseCameraDevice(device); + ObtainPhysicalCameraDeviceObj(device); + } + return device; +} + +static void ReleaseCameraDevice(SDL_CameraDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_RELEASE +{ + if (device) { + SDL_UnlockMutex(device->lock); + UnrefPhysicalCameraDevice(device); } } -int SDL_StartCamera(SDL_CameraDevice *device) +// we want these sorted by format first, so you can find a block of all +// resolutions that are supported for a format. The formats are sorted in +// "best" order, but that's subjective: right now, we prefer planar +// formats, since they're likely what the cameras prefer to produce +// anyhow, and they basically send the same information in less space +// than an RGB-style format. After that, sort by bits-per-pixel. + +// we want specs sorted largest to smallest dimensions, larger width taking precedence over larger height. +static int SDLCALL CameraSpecCmp(const void *vpa, const void *vpb) { - if (!device) { - return SDL_InvalidParamError("device"); - } else if (device->is_spec_set == SDL_FALSE) { - return SDL_SetError("no spec set"); - } else if (SDL_GetCameraStatus(device) != SDL_CAMERA_INIT) { - return SDL_SetError("invalid state"); + const SDL_CameraSpec *a = (const SDL_CameraSpec *) vpa; + const SDL_CameraSpec *b = (const SDL_CameraSpec *) vpb; + + // driver shouldn't send specs like this, check here since we're eventually going to sniff the whole array anyhow. + 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 Uint32 afmt = a->format; + const Uint32 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; } - const int result = camera_driver.impl.StartCamera(device); - if (result < 0) { - return result; - } - - SDL_AtomicSet(&device->enabled, 1); - - return 0; + return 0; // apparently, they're equal. } -int SDL_GetCameraSpec(SDL_CameraDevice *device, SDL_CameraSpec *spec) + +// The camera backends call this when a new device is plugged in. +SDL_CameraDevice *SDL_AddCameraDevice(const char *name, int num_specs, const SDL_CameraSpec *specs, void *handle) +{ + SDL_assert(name != NULL); + SDL_assert(num_specs > 0); + SDL_assert(specs != NULL); + SDL_assert(handle != NULL); + + SDL_LockRWLockForReading(camera_driver.device_hash_lock); + const int shutting_down = SDL_AtomicGet(&camera_driver.shutting_down); + SDL_UnlockRWLock(camera_driver.device_hash_lock); + if (shutting_down) { + return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment. + } + + SDL_CameraDevice *device = (SDL_CameraDevice *)SDL_calloc(1, sizeof(SDL_CameraDevice)); + if (!device) { + return NULL; + } + + device->name = SDL_strdup(name); + if (!device->name) { + SDL_free(device); + return NULL; + } + + device->lock = SDL_CreateMutex(); + if (!device->lock) { + SDL_free(device->name); + SDL_free(device); + return NULL; + } + + device->all_specs = SDL_calloc(num_specs + 1, sizeof (*specs)); + if (!device->all_specs) { + SDL_DestroyMutex(device->lock); + SDL_free(device->name); + SDL_free(device); + return NULL; + } + + SDL_memcpy(device->all_specs, specs, sizeof (*specs) * num_specs); + SDL_qsort(device->all_specs, num_specs, sizeof (*specs), CameraSpecCmp); + + // weed out duplicates, just in case. + for (int i = 0; i < num_specs; i++) { + SDL_CameraSpec *a = &device->all_specs[i]; + SDL_CameraSpec *b = &device->all_specs[i + 1]; + if ((a->format == b->format) && (a->width == b->width) && (a->height == b->height)) { + SDL_memmove(a, b, sizeof (*specs) * (num_specs - i)); + i--; + num_specs--; + } + } + + device->num_specs = num_specs; + device->handle = handle; + device->instance_id = SDL_GetNextObjectID(); + SDL_AtomicSet(&device->shutdown, 0); + SDL_AtomicSet(&device->zombie, 0); + RefPhysicalCameraDevice(device); + + SDL_LockRWLockForWriting(camera_driver.device_hash_lock); + if (SDL_InsertIntoHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id, device)) { + SDL_AtomicAdd(&camera_driver.device_count, 1); + } else { + SDL_DestroyMutex(device->lock); + SDL_free(device->all_specs); + SDL_free(device->name); + SDL_free(device); + device = NULL; + } + + // Add a device add event to the pending list, to be pushed when the event queue is pumped (away from any of our internal threads). + if (device) { + SDL_PendingCameraDeviceEvent *p = (SDL_PendingCameraDeviceEvent *) SDL_malloc(sizeof (SDL_PendingCameraDeviceEvent)); + if (p) { // if allocation fails, you won't get an event, but we can't help that. + 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; +} + +// Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the camera device's thread. +void SDL_CameraDeviceDisconnected(SDL_CameraDevice *device) { if (!device) { - return SDL_InvalidParamError("device"); + return; + } + + // Save off removal info in a list so we can send events for each, next + // time the event queue pumps, in case something tries to close a device + // from an event filter, as this would risk deadlocks and other disasters + // if done from the device thread. + SDL_PendingCameraDeviceEvent pending; + pending.next = NULL; + SDL_PendingCameraDeviceEvent *pending_tail = &pending; + + ObtainPhysicalCameraDeviceObj(device); + + const SDL_bool first_disconnect = SDL_AtomicCAS(&device->zombie, 0, 1); + if (first_disconnect) { // if already disconnected this device, don't do it twice. + // Swap in "Zombie" versions of the usual platform interfaces, so the device will keep + // making progress until the app closes it. +#if 0 // !!! FIXME +sdfsdf + device->WaitDevice = ZombieWaitDevice; + device->GetDeviceBuf = ZombieGetDeviceBuf; + device->PlayDevice = ZombiePlayDevice; + device->WaitCaptureDevice = ZombieWaitDevice; + device->CaptureFromDevice = ZombieCaptureFromDevice; + device->FlushCapture = ZombieFlushCapture; +sdfsdf +#endif + + SDL_PendingCameraDeviceEvent *p = (SDL_PendingCameraDeviceEvent *) SDL_malloc(sizeof (SDL_PendingCameraDeviceEvent)); + if (p) { // if this failed, no event for you, but you have deeper problems anyhow. + p->type = SDL_EVENT_CAMERA_DEVICE_REMOVED; + p->devid = device->instance_id; + p->next = NULL; + pending_tail->next = p; + pending_tail = p; + } + } + + ReleaseCameraDevice(device); + + if (first_disconnect) { + if (pending.next) { // NULL if event is disabled or disaster struck. + 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); + } + } +} + +SDL_CameraDevice *SDL_FindPhysicalCameraDeviceByCallback(SDL_bool (*callback)(SDL_CameraDevice *device, void *userdata), void *userdata) +{ + if (!SDL_GetCurrentCameraDriver()) { + SDL_SetError("Camera subsystem is not initialized"); + return NULL; + } + + const void *key; + const void *value; + void *iter = NULL; + + SDL_LockRWLockForReading(camera_driver.device_hash_lock); + while (SDL_IterateHashTable(camera_driver.device_hash, &key, &value, &iter)) { + SDL_CameraDevice *device = (SDL_CameraDevice *) value; + if (callback(device, userdata)) { // found it? + SDL_UnlockRWLock(camera_driver.device_hash_lock); + return device; + } + } + + SDL_UnlockRWLock(camera_driver.device_hash_lock); + + SDL_SetError("Device not found"); + return NULL; +} + +void SDL_CloseCamera(SDL_Camera *camera) +{ + SDL_CameraDevice *device = (SDL_CameraDevice *) camera; // currently there's no separation between physical and logical device. + ClosePhysicalCameraDevice(device); +} + +int SDL_GetCameraSpec(SDL_Camera *camera, SDL_CameraSpec *spec) +{ + if (!camera) { + return SDL_InvalidParamError("camera"); } else if (!spec) { return SDL_InvalidParamError("spec"); } - SDL_zerop(spec); - return camera_driver.impl.GetDeviceSpec(device, spec); -} - -int SDL_StopCamera(SDL_CameraDevice *device) -{ - if (!device) { - return SDL_InvalidParamError("device"); - } else if (SDL_GetCameraStatus(device) != SDL_CAMERA_PLAYING) { - return SDL_SetError("invalid state"); - } - - SDL_AtomicSet(&device->enabled, 0); - SDL_AtomicSet(&device->shutdown, 1); - - SDL_LockMutex(device->acquiring_lock); - const int retval = camera_driver.impl.StopCamera(device); - SDL_UnlockMutex(device->acquiring_lock); - - return (retval < 0) ? -1 : 0; -} - -// Check spec has valid format and frame size -static int prepare_cameraspec(SDL_CameraDevice *device, const SDL_CameraSpec *desired, SDL_CameraSpec *obtained, int allowed_changes) -{ - // Check format - const int numfmts = SDL_GetNumCameraFormats(device); - SDL_bool is_format_valid = SDL_FALSE; - - for (int i = 0; i < numfmts; i++) { - Uint32 format; - if (SDL_GetCameraFormat(device, i, &format) == 0) { - if (format == desired->format && format != SDL_PIXELFORMAT_UNKNOWN) { - is_format_valid = SDL_TRUE; - obtained->format = format; - break; - } - } - } - - if (!is_format_valid) { - if (allowed_changes) { - for (int i = 0; i < numfmts; i++) { - Uint32 format; - if (SDL_GetCameraFormat(device, i, &format) == 0) { - if (format != SDL_PIXELFORMAT_UNKNOWN) { - obtained->format = format; - is_format_valid = SDL_TRUE; - break; - } - } - } - } else { - return SDL_SetError("Not allowed to change the format"); - } - } - - if (!is_format_valid) { - return SDL_SetError("Invalid format"); - } - - // Check frame size - const int numsizes = SDL_GetNumCameraFrameSizes(device, obtained->format); - SDL_bool is_framesize_valid = SDL_FALSE; - - for (int i = 0; i < numsizes; i++) { - int w, h; - if (SDL_GetCameraFrameSize(device, obtained->format, i, &w, &h) == 0) { - if (desired->width == w && desired->height == h) { - is_framesize_valid = SDL_TRUE; - obtained->width = w; - obtained->height = h; - break; - } - } - } - - if (!is_framesize_valid) { - if (allowed_changes) { - int w, h; - if (SDL_GetCameraFrameSize(device, obtained->format, 0, &w, &h) == 0) { - is_framesize_valid = SDL_TRUE; - obtained->width = w; - obtained->height = h; - } - } else { - return SDL_SetError("Not allowed to change the frame size"); - } - } - - if (!is_framesize_valid) { - return SDL_SetError("Invalid frame size"); - } - + SDL_CameraDevice *device = (SDL_CameraDevice *) camera; // currently there's no separation between physical and logical device. + SDL_copyp(spec, &device->spec); return 0; } -const char *SDL_GetCameraDeviceName(SDL_CameraDeviceID instance_id) +char *SDL_GetCameraDeviceName(SDL_CameraDeviceID instance_id) { - static char buf[256]; - buf[0] = 0; - buf[255] = 0; - - if (instance_id == 0) { - SDL_InvalidParamError("instance_id"); - return NULL; + char *retval = NULL; + SDL_CameraDevice *device = ObtainPhysicalCameraDevice(instance_id); + if (device) { + retval = SDL_strdup(device->name); + ReleaseCameraDevice(device); } - - if (camera_driver.impl.GetDeviceName(instance_id, buf, sizeof (buf)) < 0) { - buf[0] = 0; - } - - return buf; + return retval; } SDL_CameraDeviceID *SDL_GetCameraDevices(int *count) { - int dummycount = 0; - if (!count) { - count = &dummycount; - } - - int num = 0; - SDL_CameraDeviceID *retval = camera_driver.impl.GetDevices(&num); - if (retval) { - *count = num; - return retval; - } - - // return list of 0 ID, null terminated - retval = (SDL_CameraDeviceID *)SDL_calloc(1, sizeof(*retval)); - if (retval == NULL) { - *count = 0; + if (!SDL_GetCurrentCameraDriver()) { + SDL_SetError("Camera subsystem is not initialized"); return NULL; } - retval[0] = 0; - *count = 0; + SDL_CameraDeviceID *retval = NULL; + + SDL_LockRWLockForReading(camera_driver.device_hash_lock); + int num_devices = SDL_AtomicGet(&camera_driver.device_count); + if (num_devices > 0) { + retval = (SDL_CameraDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_CameraDeviceID)); + if (!retval) { + num_devices = 0; + } else { + int devs_seen = 0; + const void *key; + const void *value; + void *iter = NULL; + while (SDL_IterateHashTable(camera_driver.device_hash, &key, &value, &iter)) { + retval[devs_seen++] = (SDL_CameraDeviceID) (uintptr_t) key; + } + + SDL_assert(devs_seen == num_devices); + retval[devs_seen] = 0; // null-terminated. + } + } + SDL_UnlockRWLock(camera_driver.device_hash_lock); + + if (count) { + *count = num_devices; + } + + return retval; + +} + +SDL_CameraSpec *SDL_GetCameraDeviceSupportedSpecs(SDL_CameraDeviceID instance_id, int *count) +{ + if (count) { + *count = 0; + } + + SDL_CameraDevice *device = ObtainPhysicalCameraDevice(instance_id); + if (!device) { + return NULL; + } + + SDL_CameraSpec *retval = (SDL_CameraSpec *) SDL_calloc(device->num_specs + 1, sizeof (SDL_CameraSpec)); + if (retval) { + SDL_memcpy(retval, device->all_specs, sizeof (SDL_CameraSpec) * device->num_specs); + if (count) { + *count = device->num_specs; + } + } + + ReleaseCameraDevice(device); return retval; } -// Camera thread function -static int SDLCALL SDL_CameraThread(void *devicep) + +// Camera device thread. This is split into chunks, so drivers that need to control this directly can use the pieces they need without duplicating effort. + +void SDL_CameraThreadSetup(SDL_CameraDevice *device) { - const int delay = 20; - SDL_CameraDevice *device = (SDL_CameraDevice *) devicep; - -#if DEBUG_CAMERA - SDL_Log("Start thread 'SDL_CameraThread'"); -#endif - - + //camera_driver.impl.ThreadInit(device); #ifdef SDL_VIDEO_DRIVER_ANDROID // TODO /* @@ -320,342 +508,464 @@ static int SDLCALL SDL_CameraThread(void *devicep) // The camera capture is always a high priority thread SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); #endif +} - // Perform any thread setup - device->threadid = SDL_GetCurrentThreadID(); +SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device) +{ + SDL_LockMutex(device->lock); - // Init state - // !!! FIXME: use a semaphore or something - while (!SDL_AtomicGet(&device->enabled)) { - SDL_Delay(delay); + if (SDL_AtomicGet(&device->shutdown)) { + SDL_UnlockMutex(device->lock); + return SDL_FALSE; // we're done, shut it down. } - // Loop, filling the camera buffers - while (!SDL_AtomicGet(&device->shutdown)) { - SDL_CameraFrame f; - int ret; + // !!! FIXME: this should block elsewhere without holding the lock until a frame is available, like the audio subsystem does. - SDL_zero(f); + SDL_bool failed = SDL_FALSE; // set to true if disaster worthy of treating the device as lost has happened. + SDL_Surface *acquired = NULL; + SDL_Surface *output_surface = NULL; + SurfaceList *slist = NULL; + Uint64 timestampNS = 0; - SDL_LockMutex(device->acquiring_lock); - ret = camera_driver.impl.AcquireFrame(device, &f); - SDL_UnlockMutex(device->acquiring_lock); + // AcquireFrame SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead! + const int rc = camera_driver.impl.AcquireFrame(device, device->acquire_surface, ×tampNS); - if (ret == 0) { - if (f.num_planes == 0) { - continue; + if (rc == 1) { // new frame acquired! + #if DEBUG_CAMERA + SDL_Log("CAMERA: New frame available!"); + #endif + + if (device->empty_output_surfaces.next == NULL) { + // uhoh, no output frames available! Either the app is slow, or it forgot to release frames when done with them. Drop this new frame. + #if DEBUG_CAMERA + SDL_Log("CAMERA: No empty output surfaces! Dropping frame!"); + #endif + camera_driver.impl.ReleaseFrame(device, device->acquire_surface); + device->acquire_surface->pixels = NULL; + device->acquire_surface->pitch = 0; + } else { + 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 == 0) { // no frame available yet; not an error. + #if 0 //DEBUG_CAMERA + SDL_Log("CAMERA: No frame available yet."); + #endif + } else { // fatal error! + SDL_assert(rc == -1); + #if DEBUG_CAMERA + SDL_Log("CAMERA: dev[%p] error AcquireFrame: %s", device, SDL_GetError()); + #endif + failed = SDL_TRUE; + } + + // we can let go of the lock once we've tried to grab a frame of video and maybe moved the output frame from the empty to the filled list. + // this lets us chew up the CPU for conversion and scaling without blocking other threads. + SDL_UnlockMutex(device->lock); + + if (failed) { + SDL_assert(slist == NULL); + SDL_assert(acquired == NULL); + SDL_CameraDeviceDisconnected(device); // doh. + } else if (acquired) { // we have a new frame, scale/convert if necessary and queue it for the app! + SDL_assert(slist != NULL); + if (!device->needs_scaling && !device->needs_conversion) { // no conversion needed? Just move the pointer/pitch into the output surface. + output_surface->pixels = acquired->pixels; + output_surface->pitch = acquired->pitch; + } else { // convert/scale into a different surface. + SDL_Surface *srcsurf = acquired; + if (device->needs_scaling == -1) { // downscaling? Do it first. -1: downscale, 0: no scaling, 1: upscale + SDL_Surface *dstsurf = device->needs_conversion ? device->conversion_surface : output_surface; + SDL_SoftStretch(srcsurf, NULL, dstsurf, NULL); // !!! FIXME: linear scale? letterboxing? + 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->format, srcsurf->pixels, srcsurf->pitch, + dstsurf->format->format, dstsurf->pixels, dstsurf->pitch); + srcsurf = dstsurf; + } + if (device->needs_scaling == 1) { // upscaling? Do it last. -1: downscale, 0: no scaling, 1: upscale + SDL_SoftStretch(srcsurf, NULL, output_surface, NULL); // !!! FIXME: linear scale? letterboxing? + } + + // we made a copy, so we can give the driver back its resources. + camera_driver.impl.ReleaseFrame(device, acquired); } - if (ret < 0) { - // Flag it as an error -#if DEBUG_CAMERA - SDL_Log("dev[%p] error AcquireFrame: %d %s", (void *)device, ret, SDL_GetError()); -#endif - f.num_planes = 0; - } + // we either released these already after we copied the data, or the pointer was migrated to output_surface. + acquired->pixels = NULL; + acquired->pitch = 0; - - entry_t *entry = SDL_malloc(sizeof (entry_t)); - if (entry == NULL) { - goto error_mem; - } - - entry->frame = f; - - SDL_LockMutex(device->device_lock); - ret = SDL_ListAdd(&device->buffer_queue, entry); - SDL_UnlockMutex(device->device_lock); - - if (ret < 0) { - SDL_free(entry); - goto error_mem; - } + // make the filled output surface available to the app. + SDL_LockMutex(device->lock); + slist->next = device->filled_output_surfaces.next; + device->filled_output_surfaces.next = slist; + SDL_UnlockMutex(device->lock); } -#if DEBUG_CAMERA - SDL_Log("dev[%p] End thread 'SDL_CameraThread'", (void *)device); -#endif - return 0; + return SDL_TRUE; // always go on if not shutting down, even if device failed. +} + +void SDL_CameraThreadShutdown(SDL_CameraDevice *device) +{ + //device->FlushCapture(device); + //camera_driver.impl.ThreadDeinit(device); + //SDL_CameraThreadFinalize(device); +} + +// Actual thread entry point, if driver didn't handle this itself. +static int SDLCALL CameraThread(void *devicep) +{ + SDL_CameraDevice *device = (SDL_CameraDevice *) devicep; + + #if DEBUG_CAMERA + SDL_Log("CAMERA: Start thread 'SDL_CameraThread'"); + #endif + + SDL_assert(device != NULL); + SDL_CameraThreadSetup(device); + + do { + if (camera_driver.impl.WaitDevice(device) < 0) { + SDL_CameraDeviceDisconnected(device); // doh. (but don't break out of the loop, just be a zombie for now!) + } + } while (SDL_CameraThreadIterate(device)); + + SDL_CameraThreadShutdown(device); + + #if DEBUG_CAMERA + SDL_Log("CAMERA: dev[%p] End thread 'SDL_CameraThread'", (void *)device); + #endif -error_mem: -#if DEBUG_CAMERA - SDL_Log("dev[%p] End thread 'SDL_CameraThread' with error: %s", (void *)device, SDL_GetError()); -#endif - SDL_AtomicSet(&device->shutdown, 1); return 0; } -SDL_CameraDevice *SDL_OpenCamera(SDL_CameraDeviceID instance_id) +static void ChooseBestCameraSpec(SDL_CameraDevice *device, const SDL_CameraSpec *spec, SDL_CameraSpec *closest) { - const int n = SDL_arraysize(open_devices); - SDL_CameraDevice *device = NULL; - const char *device_name = NULL; - int id = -1; + // Find the closest available native format/size... + // + // We want the exact size if possible, even if we have + // to convert formats, because we can _probably_ do that + // conversion losslessly at less expense verses scaling. + // + // Failing that, we want the size that's closest to the + // requested aspect ratio, then the closest size within + // that. - if (!SDL_WasInit(SDL_INIT_VIDEO)) { - SDL_SetError("Video subsystem is not initialized"); - goto error; + SDL_zerop(closest); + SDL_assert(((Uint32) SDL_PIXELFORMAT_UNKNOWN) == 0); // since we SDL_zerop'd to this value. + + if (!spec) { // nothing specifically requested, get the best format we can... + // we sorted this into the "best" format order when adding the camera. + SDL_assert(device->num_specs > 0); + SDL_copyp(closest, &device->all_specs[0]); + } else { // specific thing requested, try to get as close to that as possible... + const int num_specs = device->num_specs; + int wantw = spec->width; + int wanth = spec->height; + + // Find the sizes with the closest aspect ratio and then find the best fit of those. + 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_fabs(wantaspect - thisaspect); + const float diff = SDL_fabs(closestaspect - thisaspect); + const int diffw = SDL_abs(thisw - wantw); + if (diff < epsilon) { // matches current closestaspect? See if resolution is closer in size. + if (diffw < closestdiffw) { + closestdiffw = diffw; + closest->width = thisw; + closest->height = thish; + } + } else if (aspectdiff < closestdiff) { // this is a closer aspect ratio? Take it, reset resolution checks. + closestdiff = aspectdiff; + closestaspect = thisaspect; + closestdiffw = diffw; + closest->width = thisw; + closest->height = thish; + } + } + + SDL_assert(closest->width > 0); + SDL_assert(closest->height > 0); + + // okay, we have what we think is the best resolution, now we just need the best format that supports it... + const Uint32 wantfmt = spec->format; + Uint32 bestfmt = SDL_PIXELFORMAT_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 (bestfmt == SDL_PIXELFORMAT_UNKNOWN) { + bestfmt = thisspec->format; // spec list is sorted by what we consider "best" format, so unless we find an exact match later, first size match is the one! + } + if (thisspec->format == wantfmt) { + bestfmt = thisspec->format; + break; // exact match, stop looking. + } + } + } + + SDL_assert(bestfmt != SDL_PIXELFORMAT_UNKNOWN); + closest->format = bestfmt; } - // !!! FIXME: there is a race condition here if two devices open from two threads at once. - // Find an available device ID... - for (int i = 0; i < n; i++) { - if (open_devices[i] == NULL) { - id = i; - break; + SDL_assert(closest->width > 0); + SDL_assert(closest->height > 0); + SDL_assert(closest->format != SDL_PIXELFORMAT_UNKNOWN); +} + +SDL_Camera *SDL_OpenCameraDevice(SDL_CameraDeviceID instance_id, const SDL_CameraSpec *spec) +{ + if (spec) { + if ((spec->width <= 0) || (spec->height <= 0)) { + SDL_SetError("Requested spec frame size is invalid"); + return NULL; + } else if (spec->format == SDL_PIXELFORMAT_UNKNOWN) { + SDL_SetError("Requested spec format is invalid"); + return NULL; } } - if (id == -1) { - SDL_SetError("Too many open camera devices"); - goto error; + SDL_CameraDevice *device = ObtainPhysicalCameraDevice(instance_id); + if (!device) { + return NULL; } - if (instance_id != 0) { - device_name = SDL_GetCameraDeviceName(instance_id); - if (device_name == NULL) { - goto error; - } - } else { - SDL_CameraDeviceID *devices = SDL_GetCameraDevices(NULL); - if (devices && devices[0]) { - device_name = SDL_GetCameraDeviceName(devices[0]); - SDL_free(devices); - } + if (device->hidden != NULL) { + ReleaseCameraDevice(device); + SDL_SetError("Camera already opened"); // we may remove this limitation at some point. + return NULL; } -#if 0 - // FIXME do we need this ? - // Let the user override. - { - const char *dev = SDL_getenv("SDL_CAMERA_DEVICE_NAME"); - if (dev && dev[0]) { - device_name = dev; - } - } -#endif - - if (device_name == NULL) { - goto error; - } - - device = (SDL_CameraDevice *) SDL_calloc(1, sizeof (SDL_CameraDevice)); - if (device == NULL) { - goto error; - } - device->dev_name = SDL_strdup(device_name); - SDL_AtomicSet(&device->shutdown, 0); - SDL_AtomicSet(&device->enabled, 0); - device->device_lock = SDL_CreateMutex(); - if (device->device_lock == NULL) { - SDL_SetError("Couldn't create acquiring_lock"); - goto error; + SDL_CameraSpec closest; + ChooseBestCameraSpec(device, spec, &closest); + + #if DEBUG_CAMERA + SDL_Log("CAMERA: App wanted [(%dx%d) fmt=%s], chose [(%dx%d) fmt=%s]", spec ? spec->width : -1, spec ? spec->height : -1, spec ? SDL_GetPixelFormatName(spec->format) : "(null)", closest.width, closest.height, SDL_GetPixelFormatName(closest.format)); + #endif + + if (camera_driver.impl.OpenDevice(device, &closest) < 0) { + ClosePhysicalCameraDevice(device); // in case anything is half-initialized. + ReleaseCameraDevice(device); + return NULL; } - device->acquiring_lock = SDL_CreateMutex(); - if (device->acquiring_lock == NULL) { - SDL_SetError("Couldn't create acquiring_lock"); - goto error; - } - - if (camera_driver.impl.OpenDevice(device) < 0) { - goto error; - } - - // empty - device->buffer_queue = NULL; - open_devices[id] = device; // add it to our list of open devices. - - - // Start the camera thread - char threadname[64]; - SDL_snprintf(threadname, sizeof (threadname), "SDLCamera%d", id); - device->thread = SDL_CreateThreadInternal(SDL_CameraThread, threadname, 0, device); - if (device->thread == NULL) { - SDL_SetError("Couldn't create camera thread"); - goto error; - } - - return device; - -error: - CloseCameraDevice(device); - return NULL; -} - -int SDL_SetCameraSpec(SDL_CameraDevice *device, const SDL_CameraSpec *desired, SDL_CameraSpec *obtained, int allowed_changes) -{ - SDL_CameraSpec _obtained; - SDL_CameraSpec _desired; - int result; - - if (!device) { - return SDL_InvalidParamError("device"); - } else if (device->is_spec_set == SDL_TRUE) { - return SDL_SetError("already configured"); - } - - if (!desired) { - SDL_zero(_desired); - desired = &_desired; - allowed_changes = SDL_CAMERA_ALLOW_ANY_CHANGE; + if (!spec) { + SDL_copyp(&device->spec, &closest); } else { - // in case desired == obtained - _desired = *desired; - desired = &_desired; + SDL_copyp(&device->spec, spec); } - if (!obtained) { - obtained = &_obtained; - } + SDL_copyp(&device->actual_spec, &closest); - SDL_zerop(obtained); - - if (prepare_cameraspec(device, desired, obtained, allowed_changes) < 0) { - return -1; - } - - device->spec = *obtained; - - result = camera_driver.impl.InitDevice(device); - if (result < 0) { - return result; - } - - *obtained = device->spec; - - device->is_spec_set = SDL_TRUE; - - return 0; -} - -int SDL_AcquireCameraFrame(SDL_CameraDevice *device, SDL_CameraFrame *frame) -{ - if (!device) { - return SDL_InvalidParamError("device"); - } else if (!frame) { - return SDL_InvalidParamError("frame"); - } - - SDL_zerop(frame); - - if (device->thread == NULL) { - int ret; - - // Wait for a frame - while ((ret = camera_driver.impl.AcquireFrame(device, frame)) == 0) { - if (frame->num_planes) { - return 0; - } - } - return -1; + if ((closest.width == device->spec.width) && (closest.height == device->spec.height)) { + device->needs_scaling = 0; } else { - entry_t *entry = NULL; - - SDL_LockMutex(device->device_lock); - SDL_ListPop(&device->buffer_queue, (void**)&entry); - SDL_UnlockMutex(device->device_lock); - - if (entry) { - *frame = entry->frame; - SDL_free(entry); - - // Error from thread - if (frame->num_planes == 0 && frame->timestampNS == 0) { - return SDL_SetError("error from acquisition thread"); - } + const Uint64 srcarea = ((Uint64) closest.width) * ((Uint64) closest.height); + const Uint64 dstarea = ((Uint64) device->spec.width) * ((Uint64) device->spec.height); + if (dstarea <= srcarea) { + device->needs_scaling = -1; // downscaling (or changing to new aspect ratio with same area) } else { - // Queue is empty. Not an error. + device->needs_scaling = 1; // upscaling } } - return 0; + device->needs_conversion = (closest.format != device->spec.format); + + device->acquire_surface = SDL_CreateSurfaceFrom(NULL, closest.width, closest.height, 0, closest.format); + if (!device->acquire_surface) { + ClosePhysicalCameraDevice(device); + ReleaseCameraDevice(device); + return NULL; + } + + // if we have to scale _and_ convert, we need a middleman surface, since we can't do both changes at once. + if (device->needs_scaling && device->needs_conversion) { + const SDL_bool downsampling_first = (device->needs_scaling < 0); + const SDL_CameraSpec *s = downsampling_first ? &device->spec : &closest; + const Uint32 fmt = downsampling_first ? closest.format : device->spec.format; + device->conversion_surface = SDL_CreateSurface(s->width, s->height, fmt); + } + + // output surfaces are in the app-requested format. If no conversion is necessary, we'll just use the pointers + // the backend fills into acquired_surface, and you can get all the way from DMA access in the camera hardware + // to the app without a single copy. Otherwise, these will be full surfaces that hold converted/scaled copies. + + 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(device->spec.width, device->spec.height, device->spec.format); + } else { + surf = SDL_CreateSurfaceFrom(NULL, device->spec.width, device->spec.height, 0, device->spec.format); + } + + if (!surf) { + ClosePhysicalCameraDevice(device); + ReleaseCameraDevice(device); + return NULL; + } + + device->output_surfaces[i].surface = surf; + } + + // Start the camera thread if necessary + if (!camera_driver.impl.ProvidesOwnCallbackThread) { + char threadname[64]; + SDL_snprintf(threadname, sizeof (threadname), "SDLCamera%d", instance_id); + device->thread = SDL_CreateThreadInternal(CameraThread, threadname, 0, device); + if (!device->thread) { + ClosePhysicalCameraDevice(device); + ReleaseCameraDevice(device); + SDL_SetError("Couldn't create camera thread"); + return NULL; + } + } + + ReleaseCameraDevice(device); // unlock, we're good to go! + + return (SDL_Camera *) device; // currently there's no separation between physical and logical device. } -int SDL_ReleaseCameraFrame(SDL_CameraDevice *device, SDL_CameraFrame *frame) +SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS) { - if (!device) { - return SDL_InvalidParamError("device"); + if (timestampNS) { + *timestampNS = 0; + } + + if (!camera) { + SDL_InvalidParamError("camera"); + return NULL; + } + + SDL_CameraDevice *device = (SDL_CameraDevice *) camera; // currently there's no separation between physical and logical device. + + ObtainPhysicalCameraDeviceObj(device); + + SDL_Surface *retval = NULL; + + // frames are in this list from newest to oldest, so find the end of the list... + SurfaceList *slistprev = &device->filled_output_surfaces; + SurfaceList *slist = slistprev; + while (slist->next) { + slistprev = slist; + slist = slist->next; + } + + const SDL_bool list_is_empty = (slist == slistprev); + if (!list_is_empty) { // report the oldest frame. + if (timestampNS) { + *timestampNS = slist->timestampNS; + } + retval = slist->surface; + slistprev->next = slist->next; // remove from filled list. + slist->next = device->app_held_output_surfaces.next; // add to app_held list. + device->app_held_output_surfaces.next = slist; + } + + ReleaseCameraDevice(device); + + return retval; +} + +int SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame) +{ + if (!camera) { + return SDL_InvalidParamError("camera"); } else if (frame == NULL) { return SDL_InvalidParamError("frame"); - } else if (camera_driver.impl.ReleaseFrame(device, frame) < 0) { - return -1; } - SDL_zerop(frame); + SDL_CameraDevice *device = (SDL_CameraDevice *) camera; // currently there's no separation between physical and logical device. + ObtainPhysicalCameraDeviceObj(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) { + ReleaseCameraDevice(device); + return SDL_SetError("Surface was not acquired from this camera, or was already released"); + } + + // this pointer was owned by the backend (DMA memory or whatever), clear it out. + if (!device->needs_conversion && !device->needs_scaling) { + camera_driver.impl.ReleaseFrame(device, frame); + frame->pixels = NULL; + frame->pitch = 0; + } + + slist->timestampNS = 0; + + // remove from app_held list... + slistprev->next = slist->next; + + // insert at front of empty list (and we'll use it first when we need to fill a new frame). + slist->next = device->empty_output_surfaces.next; + device->empty_output_surfaces.next = slist; + + ReleaseCameraDevice(device); + return 0; } -int SDL_GetNumCameraFormats(SDL_CameraDevice *device) +// !!! FIXME: add a way to "pause" camera output. + +SDL_CameraDeviceID SDL_GetCameraInstanceID(SDL_Camera *camera) { - if (!device) { - return SDL_InvalidParamError("device"); + SDL_CameraDeviceID retval = 0; + if (!camera) { + SDL_InvalidParamError("camera"); + } else { + SDL_CameraDevice *device = (SDL_CameraDevice *) camera; // currently there's no separation between physical and logical device. + ObtainPhysicalCameraDeviceObj(device); + retval = device->instance_id; + ReleaseCameraDevice(device); } - return camera_driver.impl.GetNumFormats(device); + + return retval; } -int SDL_GetCameraFormat(SDL_CameraDevice *device, int index, Uint32 *format) +SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera) { - if (!device) { - return SDL_InvalidParamError("device"); - } else if (!format) { - return SDL_InvalidParamError("format"); - } - *format = 0; - return camera_driver.impl.GetFormat(device, index, format); -} - -int SDL_GetNumCameraFrameSizes(SDL_CameraDevice *device, Uint32 format) -{ - if (!device) { - return SDL_InvalidParamError("device"); - } - return camera_driver.impl.GetNumFrameSizes(device, format); -} - -int SDL_GetCameraFrameSize(SDL_CameraDevice *device, Uint32 format, int index, int *width, int *height) -{ - if (!device) { - return SDL_InvalidParamError("device"); - } else if (!width) { - return SDL_InvalidParamError("width"); - } else if (!height) { - return SDL_InvalidParamError("height"); - } - *width = *height = 0; - return camera_driver.impl.GetFrameSize(device, format, index, width, height); -} - -SDL_CameraDevice *SDL_OpenCameraWithSpec(SDL_CameraDeviceID instance_id, const SDL_CameraSpec *desired, SDL_CameraSpec *obtained, int allowed_changes) -{ - SDL_CameraDevice *device; - - if ((device = SDL_OpenCamera(instance_id)) == NULL) { - return NULL; + SDL_PropertiesID retval = 0; + if (!camera) { + SDL_InvalidParamError("camera"); + } else { + SDL_CameraDevice *device = (SDL_CameraDevice *) camera; // currently there's no separation between physical and logical device. + ObtainPhysicalCameraDeviceObj(device); + if (device->props == 0) { + device->props = SDL_CreateProperties(); + } + retval = device->props; + ReleaseCameraDevice(device); } - if (SDL_SetCameraSpec(device, desired, obtained, allowed_changes) < 0) { - SDL_CloseCamera(device); - return NULL; - } - return device; -} - -SDL_CameraStatus SDL_GetCameraStatus(SDL_CameraDevice *device) -{ - if (device == NULL) { - return SDL_CAMERA_INIT; - } else if (device->is_spec_set == SDL_FALSE) { - return SDL_CAMERA_INIT; - } else if (SDL_AtomicGet(&device->shutdown)) { - return SDL_CAMERA_STOPPED; - } else if (SDL_AtomicGet(&device->enabled)) { - return SDL_CAMERA_PLAYING; - } - return SDL_CAMERA_INIT; + return retval; } static void CompleteCameraEntryPoints(void) @@ -665,18 +975,9 @@ static void CompleteCameraEntryPoints(void) FILL_STUB(DetectDevices); FILL_STUB(OpenDevice); FILL_STUB(CloseDevice); - FILL_STUB(InitDevice); - FILL_STUB(GetDeviceSpec); - FILL_STUB(StartCamera); - FILL_STUB(StopCamera); FILL_STUB(AcquireFrame); FILL_STUB(ReleaseFrame); - FILL_STUB(GetNumFormats); - FILL_STUB(GetFormat); - FILL_STUB(GetNumFrameSizes); - FILL_STUB(GetFrameSize); - FILL_STUB(GetDeviceName); - FILL_STUB(GetDevices); + FILL_STUB(FreeDeviceHandle); FILL_STUB(Deinitialize); #undef FILL_STUB } @@ -687,38 +988,70 @@ void SDL_QuitCamera(void) return; } - const int n = SDL_arraysize(open_devices); - for (int i = 0; i < n; i++) { - CloseCameraDevice(open_devices[i]); - } - - SDL_zeroa(open_devices); - -#if 0 // !!! FIXME + SDL_LockRWLockForWriting(camera_driver.device_hash_lock); + SDL_AtomicSet(&camera_driver.shutting_down, 1); + SDL_HashTable *device_hash = camera_driver.device_hash; + camera_driver.device_hash = NULL; SDL_PendingCameraDeviceEvent *pending_events = camera_driver.pending_events.next; camera_driver.pending_events.next = NULL; + SDL_AtomicSet(&camera_driver.device_count, 0); + SDL_UnlockRWLock(camera_driver.device_hash_lock); SDL_PendingCameraDeviceEvent *pending_next = NULL; for (SDL_PendingCameraDeviceEvent *i = pending_events; i; i = pending_next) { pending_next = i->next; SDL_free(i); } -#endif + + const void *key; + const void *value; + void *iter = NULL; + while (SDL_IterateHashTable(device_hash, &key, &value, &iter)) { + DestroyPhysicalCameraDevice((SDL_CameraDevice *) value); + } // Free the driver data camera_driver.impl.Deinitialize(); + SDL_DestroyRWLock(camera_driver.device_hash_lock); + SDL_DestroyHashTable(device_hash); + SDL_zero(camera_driver); } -// this is 90% the same code as the audio subsystem uses. + +static Uint32 HashCameraDeviceID(const void *key, void *data) +{ + // The values are unique incrementing integers, starting at 1, so just return minus 1 to start with bucket zero. + return ((Uint32) ((uintptr_t) key)) - 1; +} + +static SDL_bool MatchCameraDeviceID(const void *a, const void *b, void *data) +{ + return (a == b); // simple integers, just compare them as pointer values. +} + +static void NukeCameraDeviceHashItem(const void *key, const void *value, void *data) +{ + // no-op, keys and values in this hashtable are treated as Plain Old Data and don't get freed here. +} + int SDL_CameraInit(const char *driver_name) { if (SDL_GetCurrentCameraDriver()) { SDL_QuitCamera(); // shutdown driver if already running. } - SDL_zeroa(open_devices); + SDL_RWLock *device_hash_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole camera subsystem. + if (!device_hash_lock) { + return -1; + } + + SDL_HashTable *device_hash = SDL_CreateHashTable(NULL, 8, HashCameraDeviceID, MatchCameraDeviceID, NukeCameraDeviceHashItem, SDL_FALSE); + if (!device_hash) { + SDL_DestroyRWLock(device_hash_lock); + return -1; + } // Select the proper camera driver if (!driver_name) { @@ -733,6 +1066,8 @@ int SDL_CameraInit(const char *driver_name) const char *driver_attempt = driver_name_copy; if (!driver_name_copy) { + SDL_DestroyRWLock(device_hash_lock); + SDL_DestroyHashTable(device_hash); return -1; } @@ -746,9 +1081,9 @@ int SDL_CameraInit(const char *driver_name) if (SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) { tried_to_init = SDL_TRUE; SDL_zero(camera_driver); - #if 0 // !!! FIXME camera_driver.pending_events_tail = &camera_driver.pending_events; - #endif + 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; @@ -770,9 +1105,9 @@ int SDL_CameraInit(const char *driver_name) tried_to_init = SDL_TRUE; SDL_zero(camera_driver); - #if 0 // !!! FIXME camera_driver.pending_events_tail = &camera_driver.pending_events; - #endif + 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; @@ -792,6 +1127,8 @@ int SDL_CameraInit(const char *driver_name) } SDL_zero(camera_driver); + SDL_DestroyRWLock(device_hash_lock); + SDL_DestroyHashTable(device_hash); return -1; // No driver was available, so fail. } @@ -803,3 +1140,36 @@ int SDL_CameraInit(const char *driver_name) return 0; } +// This is an internal function, so SDL_PumpEvents() can check for pending camera device events. +// ("UpdateSubsystem" is the same naming that the other things that hook into PumpEvents use.) +void SDL_UpdateCamera(void) +{ + SDL_LockRWLockForReading(camera_driver.device_hash_lock); + SDL_PendingCameraDeviceEvent *pending_events = camera_driver.pending_events.next; + SDL_UnlockRWLock(camera_driver.device_hash_lock); + + if (!pending_events) { + return; // nothing to do, check next time. + } + + // okay, let's take this whole list of events so we can dump the lock, and new ones can queue up for a later update. + SDL_LockRWLockForWriting(camera_driver.device_hash_lock); + pending_events = camera_driver.pending_events.next; // in case this changed... + camera_driver.pending_events.next = NULL; + camera_driver.pending_events_tail = &camera_driver.pending_events; + SDL_UnlockRWLock(camera_driver.device_hash_lock); + + SDL_PendingCameraDeviceEvent *pending_next = NULL; + for (SDL_PendingCameraDeviceEvent *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.adevice.which = (Uint32) i->devid; + SDL_PushEvent(&event); + } + SDL_free(i); + } +} + diff --git a/src/camera/SDL_camera_c.h b/src/camera/SDL_camera_c.h index 56921ab8a..6fae3101d 100644 --- a/src/camera/SDL_camera_c.h +++ b/src/camera/SDL_camera_c.h @@ -29,4 +29,7 @@ int SDL_CameraInit(const char *driver_name); // Shutdown the camera subsystem void SDL_QuitCamera(void); +// "Pump" the event queue. +extern void SDL_UpdateCamera(void); + #endif // SDL_camera_c_h_ diff --git a/src/camera/SDL_syscamera.h b/src/camera/SDL_syscamera.h index 6a7c56356..a48a15298 100644 --- a/src/camera/SDL_syscamera.h +++ b/src/camera/SDL_syscamera.h @@ -23,67 +23,141 @@ #ifndef SDL_syscamera_h_ #define SDL_syscamera_h_ -#include "../SDL_list.h" +#include "../SDL_hashtable.h" #define DEBUG_CAMERA 1 -// The SDL camera driver + +// !!! FIXME: update these drivers! +#ifdef SDL_CAMERA_DRIVER_COREMEDIA +#undef SDL_CAMERA_DRIVER_COREMEDIA +#endif +#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 + a USB camera being plugged in), and should also be called for + for every device found during DetectDevices(). */ +extern SDL_CameraDevice *SDL_AddCameraDevice(const char *name, int num_specs, const SDL_CameraSpec *specs, void *handle); + +/* Backends should call this if an opened camera device is lost. + This can happen due to i/o errors, or a device being unplugged, etc. */ +extern void SDL_CameraDeviceDisconnected(SDL_CameraDevice *device); + +// Find an SDL_CameraDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE. +extern SDL_CameraDevice *SDL_FindPhysicalCameraDeviceByCallback(SDL_bool (*callback)(SDL_CameraDevice *device, void *userdata), void *userdata); + +// 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); +extern void SDL_CameraThreadShutdown(SDL_CameraDevice *device); + +typedef struct SurfaceList +{ + SDL_Surface *surface; + Uint64 timestampNS; + struct SurfaceList *next; +} SurfaceList; + // Define the SDL camera driver structure struct SDL_CameraDevice { - // The device's current camera specification + // A mutex for locking + SDL_Mutex *lock; + + // Human-readable device name. + char *name; + + // When refcount hits zero, we destroy the device object. + SDL_AtomicInt refcount; + + // All supported formats/dimensions for this device. + SDL_CameraSpec *all_specs; + + // Elements in all_specs. + int num_specs; + + // The device's actual specification that the camera is outputting, before conversion. + SDL_CameraSpec actual_spec; + + // The device's current camera specification, after conversions. SDL_CameraSpec spec; - // Device name - char *dev_name; + // Unique value assigned at creation time. + SDL_CameraDeviceID instance_id; + + // Driver-specific hardware data on how to open device (`hidden` is driver-specific data _when opened_). + void *handle; + + // Pixel data flows from the driver into these, then gets converted for the app if necessary. + SDL_Surface *acquire_surface; + + // acquire_surface converts or scales to this surface before landing in output_surfaces, if necessary. + SDL_Surface *conversion_surface; + + // A queue of surfaces that buffer converted/scaled frames of video until the app claims them. + SurfaceList output_surfaces[8]; + SurfaceList filled_output_surfaces; // this is FIFO + SurfaceList empty_output_surfaces; // this is LIFO + SurfaceList app_held_output_surfaces; + + // non-zero if acquire_surface needs to be scaled for final output. + int needs_scaling; // -1: downscale, 0: no scaling, 1: upscale + + // SDL_TRUE if acquire_surface needs to be converted for final output. + SDL_bool needs_conversion; // Current state flags SDL_AtomicInt shutdown; - SDL_AtomicInt enabled; - SDL_bool is_spec_set; - - // A mutex for locking the queue buffers - SDL_Mutex *device_lock; - SDL_Mutex *acquiring_lock; + SDL_AtomicInt zombie; // A thread to feed the camera device SDL_Thread *thread; - SDL_ThreadID threadid; - // Queued buffers (if app not using callback). - SDL_ListNode *buffer_queue; + // Optional properties. + SDL_PropertiesID props; - // Data private to this driver + // Data private to this driver, used when device is opened and running. struct SDL_PrivateCameraData *hidden; }; typedef struct SDL_CameraDriverImpl { void (*DetectDevices)(void); - int (*OpenDevice)(SDL_CameraDevice *_this); - void (*CloseDevice)(SDL_CameraDevice *_this); - int (*InitDevice)(SDL_CameraDevice *_this); - int (*GetDeviceSpec)(SDL_CameraDevice *_this, SDL_CameraSpec *spec); - int (*StartCamera)(SDL_CameraDevice *_this); - int (*StopCamera)(SDL_CameraDevice *_this); - int (*AcquireFrame)(SDL_CameraDevice *_this, SDL_CameraFrame *frame); - int (*ReleaseFrame)(SDL_CameraDevice *_this, SDL_CameraFrame *frame); - int (*GetNumFormats)(SDL_CameraDevice *_this); - int (*GetFormat)(SDL_CameraDevice *_this, int index, Uint32 *format); - int (*GetNumFrameSizes)(SDL_CameraDevice *_this, Uint32 format); - int (*GetFrameSize)(SDL_CameraDevice *_this, Uint32 format, int index, int *width, int *height); - int (*GetDeviceName)(SDL_CameraDeviceID instance_id, char *buf, int size); - SDL_CameraDeviceID *(*GetDevices)(int *count); + int (*OpenDevice)(SDL_CameraDevice *device, const SDL_CameraSpec *spec); + void (*CloseDevice)(SDL_CameraDevice *device); + int (*WaitDevice)(SDL_CameraDevice *device); + int (*AcquireFrame)(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS); // set frame->pixels, frame->pitch, and *timestampNS! + void (*ReleaseFrame)(SDL_CameraDevice *device, SDL_Surface *frame); // Reclaim frame->pixels and frame->pitch! + void (*FreeDeviceHandle)(SDL_CameraDevice *device); // SDL is done with this device; free the handle from SDL_AddCameraDevice() void (*Deinitialize)(void); + + SDL_bool ProvidesOwnCallbackThread; } SDL_CameraDriverImpl; +typedef struct SDL_PendingCameraDeviceEvent +{ + Uint32 type; + SDL_CameraDeviceID devid; + struct SDL_PendingCameraDeviceEvent *next; +} SDL_PendingCameraDeviceEvent; + typedef struct SDL_CameraDriver { const char *name; // The name of this camera driver const char *desc; // The description of this camera driver SDL_CameraDriverImpl impl; // the backend's interface + + SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` + SDL_HashTable *device_hash; // the collection of currently-available camera devices + SDL_PendingCameraDeviceEvent pending_events; + SDL_PendingCameraDeviceEvent *pending_events_tail; + + SDL_AtomicInt device_count; + SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs. } SDL_CameraDriver; typedef struct CameraBootStrap diff --git a/src/camera/coremedia/SDL_camera_coremedia.m b/src/camera/coremedia/SDL_camera_coremedia.m index f5ade8ca0..69f2815ed 100644 --- a/src/camera/coremedia/SDL_camera_coremedia.m +++ b/src/camera/coremedia/SDL_camera_coremedia.m @@ -135,7 +135,9 @@ static Uint32 nsfourcc_to_sdlformat(NSString *nsfourcc) if (SDL_strcmp("yuvs", str) == 0) return SDL_PIXELFORMAT_UYVY; if (SDL_strcmp("420f", str) == 0) return SDL_PIXELFORMAT_UNKNOWN; - SDL_Log("Unknown format '%s'", str); + #if DEBUG_CAMERA + SDL_Log("CAMERA: Unknown format '%s'", str); + #endif return SDL_PIXELFORMAT_UNKNOWN; } @@ -177,8 +179,9 @@ static NSString *sdlformat_to_nsfourcc(Uint32 fmt) - (void)captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { - // !!! FIXME #if DEBUG_CAMERA - SDL_Log("Drop frame.."); + #if DEBUG_CAMERA + SDL_Log("CAMERA: Drop frame.."); + #endif } @end @@ -362,13 +365,13 @@ static int COREMEDIA_AcquireFrame(SDL_CameraDevice *_this, SDL_CameraFrame *fram const int numPlanes = CVPixelBufferGetPlaneCount(image); const int planar = CVPixelBufferIsPlanar(image); -#if 0 + #if DEBUG_CAMERA const int w = CVPixelBufferGetWidth(image); const int h = CVPixelBufferGetHeight(image); const int sz = CVPixelBufferGetDataSize(image); const int pitch = CVPixelBufferGetBytesPerRow(image); - SDL_Log("buffer planar=%d count:%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch); -#endif + SDL_Log("CAMERA: buffer planar=%d count:%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch); + #endif CVPixelBufferLockBaseAddress(image, 0); diff --git a/src/camera/dummy/SDL_camera_dummy.c b/src/camera/dummy/SDL_camera_dummy.c new file mode 100644 index 000000000..1dcdd2958 --- /dev/null +++ b/src/camera/dummy/SDL_camera_dummy.c @@ -0,0 +1,80 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_CAMERA_DRIVER_DUMMY + +#include "../SDL_syscamera.h" + +static int DUMMYCAMERA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec) +{ + return SDL_Unsupported(); +} + +static void DUMMYCAMERA_CloseDevice(SDL_CameraDevice *device) +{ +} + +static int DUMMYCAMERA_WaitDevice(SDL_CameraDevice *device) +{ + return SDL_Unsupported(); +} + +static int DUMMYCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS) +{ + return SDL_Unsupported(); +} + +static void DUMMYCAMERA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame) +{ +} + +static void DUMMYCAMERA_DetectDevices(void) +{ +} + +static void DUMMYCAMERA_FreeDeviceHandle(SDL_CameraDevice *device) +{ +} + +static void DUMMYCAMERA_Deinitialize(void) +{ +} + +static SDL_bool DUMMYCAMERA_Init(SDL_CameraDriverImpl *impl) +{ + impl->DetectDevices = DUMMYCAMERA_DetectDevices; + impl->OpenDevice = DUMMYCAMERA_OpenDevice; + impl->CloseDevice = DUMMYCAMERA_CloseDevice; + impl->WaitDevice = DUMMYCAMERA_WaitDevice; + impl->AcquireFrame = DUMMYCAMERA_AcquireFrame; + impl->ReleaseFrame = DUMMYCAMERA_ReleaseFrame; + impl->FreeDeviceHandle = DUMMYCAMERA_FreeDeviceHandle; + impl->Deinitialize = DUMMYCAMERA_Deinitialize; + + return SDL_TRUE; +} + +CameraBootStrap DUMMYCAMERA_bootstrap = { + "dummy", "SDL dummy camera driver", DUMMYCAMERA_Init, SDL_TRUE +}; + +#endif diff --git a/src/camera/v4l2/SDL_camera_v4l2.c b/src/camera/v4l2/SDL_camera_v4l2.c index f4f484666..0eebc7196 100644 --- a/src/camera/v4l2/SDL_camera_v4l2.c +++ b/src/camera/v4l2/SDL_camera_v4l2.c @@ -22,43 +22,38 @@ #ifdef SDL_CAMERA_DRIVER_V4L2 +#include +#include +#include // low-level i/o +#include +#include +#include +#include + #include "../SDL_syscamera.h" #include "../SDL_camera_c.h" #include "../../video/SDL_pixels_c.h" #include "../../thread/SDL_systhread.h" #include "../../core/linux/SDL_evdev_capabilities.h" #include "../../core/linux/SDL_udev.h" -#include // INT_MAX -#define MAX_CAMERA_DEVICES 128 // It's doubtful someone has more than that +#ifndef SDL_USE_LIBUDEV +#include +#endif -static int MaybeAddDevice(const char *path); -#ifdef SDL_USE_LIBUDEV -static int MaybeRemoveDevice(const char *path); -static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath); -#endif // SDL_USE_LIBUDEV - -// List of available camera devices. -typedef struct SDL_cameralist_item +typedef struct V4L2DeviceHandle { - char *fname; // Dev path name (like /dev/video0) - char *bus_info; // don't add two paths with same bus_info (eg /dev/video0 and /dev/video1 - SDL_CameraDeviceID instance_id; - SDL_CameraDevice *device; // Associated device - struct SDL_cameralist_item *next; -} SDL_cameralist_item; - -static SDL_cameralist_item *SDL_cameralist = NULL; -static SDL_cameralist_item *SDL_cameralist_tail = NULL; -static int num_cameras = 0; + char *bus_info; + char *path; +} V4L2DeviceHandle; - -enum io_method { +typedef enum io_method { + IO_METHOD_INVALID, IO_METHOD_READ, IO_METHOD_MMAP, IO_METHOD_USERPTR -}; +} io_method; struct buffer { void *start; @@ -69,21 +64,13 @@ struct buffer { struct SDL_PrivateCameraData { int fd; - enum io_method io; + io_method io; int nb_buffers; struct buffer *buffers; int first_start; int driver_pitch; }; -#include -#include -#include // low-level i/o -#include -#include -#include -#include - static int xioctl(int fh, int request, void *arg) { int r; @@ -95,17 +82,40 @@ static int xioctl(int fh, int request, void *arg) return r; } -// -1:error 1:frame 0:no frame -static int acquire_frame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) +static int V4L2_WaitDevice(SDL_CameraDevice *device) { - const int fd = _this->hidden->fd; - enum io_method io = _this->hidden->io; - size_t size = _this->hidden->buffers[0].length; + const int fd = device->hidden->fd; + + int retval; + + do { + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; + + retval = select(fd + 1, &fds, NULL, NULL, &tv); + if ((retval == -1) && (errno == EINTR)) { + retval = 0; // pretend it was a timeout, keep looping. + } + } while (retval == 0); + + return retval; +} + +static int V4L2_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS) +{ + const int fd = device->hidden->fd; + const io_method io = device->hidden->io; + size_t size = device->hidden->buffers[0].length; struct v4l2_buffer buf; switch (io) { case IO_METHOD_READ: - if (read(fd, _this->hidden->buffers[0].start, size) == -1) { + if (read(fd, device->hidden->buffers[0].start, size) == -1) { switch (errno) { case EAGAIN: return 0; @@ -119,9 +129,8 @@ static int acquire_frame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) } } - frame->num_planes = 1; - frame->data[0] = _this->hidden->buffers[0].start; - frame->pitch[0] = _this->hidden->driver_pitch; + frame->pixels = device->hidden->buffers[0].start; + frame->pitch = device->hidden->driver_pitch; break; case IO_METHOD_MMAP: @@ -144,18 +153,17 @@ static int acquire_frame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) } } - if ((int)buf.index < 0 || (int)buf.index >= _this->hidden->nb_buffers) { + if ((int)buf.index < 0 || (int)buf.index >= device->hidden->nb_buffers) { return SDL_SetError("invalid buffer index"); } - frame->num_planes = 1; - frame->data[0] = _this->hidden->buffers[buf.index].start; - frame->pitch[0] = _this->hidden->driver_pitch; - _this->hidden->buffers[buf.index].available = 1; + frame->pixels = device->hidden->buffers[buf.index].start; + frame->pitch = device->hidden->driver_pitch; + device->hidden->buffers[buf.index].available = 1; -#if DEBUG_CAMERA - SDL_Log("debug mmap: image %d/%d num_planes:%d data[0]=%p", buf.index, _this->hidden->nb_buffers, frame->num_planes, (void*)frame->data[0]); -#endif + #if DEBUG_CAMERA + SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels); + #endif break; case IO_METHOD_USERPTR: @@ -180,45 +188,49 @@ static int acquire_frame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) } int i; - for (i = 0; i < _this->hidden->nb_buffers; ++i) { - if (buf.m.userptr == (unsigned long)_this->hidden->buffers[i].start && buf.length == size) { + for (i = 0; i < device->hidden->nb_buffers; ++i) { + if (buf.m.userptr == (unsigned long)device->hidden->buffers[i].start && buf.length == size) { break; } } - if (i >= _this->hidden->nb_buffers) { + if (i >= device->hidden->nb_buffers) { return SDL_SetError("invalid buffer index"); } - frame->num_planes = 1; - frame->data[0] = (void*)buf.m.userptr; - frame->pitch[0] = _this->hidden->driver_pitch; - _this->hidden->buffers[i].available = 1; -#if DEBUG_CAMERA - SDL_Log("debug userptr: image %d/%d num_planes:%d data[0]=%p", buf.index, _this->hidden->nb_buffers, frame->num_planes, (void*)frame->data[0]); -#endif + frame->pixels = (void*)buf.m.userptr; + frame->pitch = device->hidden->driver_pitch; + device->hidden->buffers[i].available = 1; + + #if DEBUG_CAMERA + SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels); + #endif + break; + + case IO_METHOD_INVALID: + SDL_assert(!"Shouldn't have hit this"); break; } + *timestampNS = SDL_GetTicksNS(); // !!! FIXME: can we get this info more accurately from v4l2? return 1; } - -static int V4L2_ReleaseFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) +static void V4L2_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame) { struct v4l2_buffer buf; - const int fd = _this->hidden->fd; - enum io_method io = _this->hidden->io; + const int fd = device->hidden->fd; + const io_method io = device->hidden->io; int i; - for (i = 0; i < _this->hidden->nb_buffers; ++i) { - if (frame->num_planes && frame->data[0] == _this->hidden->buffers[i].start) { + for (i = 0; i < device->hidden->nb_buffers; ++i) { + if (frame->pixels == device->hidden->buffers[i].start) { break; } } - if (i >= _this->hidden->nb_buffers) { - return SDL_SetError("invalid buffer index"); + if (i >= device->hidden->nb_buffers) { + return; // oh well, we didn't own this. } switch (io) { @@ -233,9 +245,10 @@ static int V4L2_ReleaseFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) buf.index = i; if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { - return SDL_SetError("VIDIOC_QBUF"); + // !!! FIXME: disconnect the device. + return; //SDL_SetError("VIDIOC_QBUF"); } - _this->hidden->buffers[i].available = 0; + device->hidden->buffers[i].available = 0; break; case IO_METHOD_USERPTR: @@ -244,102 +257,33 @@ static int V4L2_ReleaseFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_USERPTR; buf.index = i; - buf.m.userptr = (unsigned long)frame->data[0]; - buf.length = (int) _this->hidden->buffers[i].length; + buf.m.userptr = (unsigned long)frame->pixels; + buf.length = (int) device->hidden->buffers[i].length; if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { - return SDL_SetError("VIDIOC_QBUF"); + // !!! FIXME: disconnect the device. + return; //SDL_SetError("VIDIOC_QBUF"); } - _this->hidden->buffers[i].available = 0; + device->hidden->buffers[i].available = 0; + break; + + case IO_METHOD_INVALID: + SDL_assert(!"Shouldn't have hit this"); break; } - - return 0; } -static int V4L2_AcquireFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame) +static int EnqueueBuffers(SDL_CameraDevice *device) { - fd_set fds; - struct timeval tv; - - const int fd = _this->hidden->fd; - - FD_ZERO(&fds); - FD_SET(fd, &fds); - - // Timeout. - tv.tv_sec = 0; - tv.tv_usec = 300 * 1000; - - int retval = select(fd + 1, &fds, NULL, NULL, &tv); - - if (retval == -1) { - if (errno == EINTR) { -#if DEBUG_CAMERA - SDL_Log("continue .."); -#endif - return 0; - } - return SDL_SetError("select"); - } - - if (retval == 0) { - // Timeout. Not an error - SDL_SetError("timeout select"); - return 0; - } - - retval = acquire_frame(_this, frame); - if (retval < 0) { - return -1; - } - - if (retval == 1){ - frame->timestampNS = SDL_GetTicksNS(); - } else if (retval == 0) { -#if DEBUG_CAMERA - SDL_Log("No frame continue: %s", SDL_GetError()); -#endif - } - - // EAGAIN - continue select loop. - return 0; -} - - -static int V4L2_StopCamera(SDL_CameraDevice *_this) -{ - enum v4l2_buf_type type; - const int fd = _this->hidden->fd; - enum io_method io = _this->hidden->io; - + const int fd = device->hidden->fd; + const io_method io = device->hidden->io; switch (io) { case IO_METHOD_READ: break; case IO_METHOD_MMAP: - case IO_METHOD_USERPTR: - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (xioctl(fd, VIDIOC_STREAMOFF, &type) == -1) { - return SDL_SetError("VIDIOC_STREAMOFF"); - } - break; - } - - return 0; -} - -static int EnqueueBuffers(SDL_CameraDevice *_this) -{ - const int fd = _this->hidden->fd; - enum io_method io = _this->hidden->io; - switch (io) { - case IO_METHOD_READ: - break; - - case IO_METHOD_MMAP: - for (int i = 0; i < _this->hidden->nb_buffers; ++i) { - if (_this->hidden->buffers[i].available == 0) { + for (int i = 0; i < device->hidden->nb_buffers; ++i) { + if (device->hidden->buffers[i].available == 0) { struct v4l2_buffer buf; SDL_zero(buf); @@ -355,16 +299,16 @@ static int EnqueueBuffers(SDL_CameraDevice *_this) break; case IO_METHOD_USERPTR: - for (int i = 0; i < _this->hidden->nb_buffers; ++i) { - if (_this->hidden->buffers[i].available == 0) { + for (int i = 0; i < device->hidden->nb_buffers; ++i) { + if (device->hidden->buffers[i].available == 0) { struct v4l2_buffer buf; SDL_zero(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_USERPTR; buf.index = i; - buf.m.userptr = (unsigned long)_this->hidden->buffers[i].start; - buf.length = (int) _this->hidden->buffers[i].length; + buf.m.userptr = (unsigned long)device->hidden->buffers[i].start; + buf.length = (int) device->hidden->buffers[i].length; if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { return SDL_SetError("VIDIOC_QBUF"); @@ -372,115 +316,24 @@ static int EnqueueBuffers(SDL_CameraDevice *_this) } } break; + + case IO_METHOD_INVALID: SDL_assert(!"Shouldn't have hit this"); break; } return 0; } -static int PreEnqueueBuffers(SDL_CameraDevice *_this) +static int AllocBufferRead(SDL_CameraDevice *device, size_t buffer_size) { - struct v4l2_requestbuffers req; - const int fd = _this->hidden->fd; - enum io_method io = _this->hidden->io; - - switch (io) { - case IO_METHOD_READ: - break; - - case IO_METHOD_MMAP: - SDL_zero(req); - req.count = _this->hidden->nb_buffers; - req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - req.memory = V4L2_MEMORY_MMAP; - - if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) { - if (errno == EINVAL) { - return SDL_SetError("Does not support memory mapping"); - } else { - return SDL_SetError("VIDIOC_REQBUFS"); - } - } - - if (req.count < 2) { - return SDL_SetError("Insufficient buffer memory"); - } - - _this->hidden->nb_buffers = req.count; - break; - - case IO_METHOD_USERPTR: - SDL_zero(req); - req.count = _this->hidden->nb_buffers; - req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - req.memory = V4L2_MEMORY_USERPTR; - - if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) { - if (errno == EINVAL) { - return SDL_SetError("Does not support user pointer i/o"); - } else { - return SDL_SetError("VIDIOC_REQBUFS"); - } - } - break; - } - return 0; + device->hidden->buffers[0].length = buffer_size; + device->hidden->buffers[0].start = SDL_calloc(1, buffer_size); + return device->hidden->buffers[0].start ? 0 : -1; } -static int V4L2_StartCamera(SDL_CameraDevice *_this) +static int AllocBufferMmap(SDL_CameraDevice *device) { - enum v4l2_buf_type type; - - const int fd = _this->hidden->fd; - enum io_method io = _this->hidden->io; - - - if (_this->hidden->first_start == 0) { - _this->hidden->first_start = 1; - } else { - const int old = _this->hidden->nb_buffers; - // TODO mmap; doesn't work with stop->start -#if 1 - // Can change nb_buffers for mmap - if (PreEnqueueBuffers(_this) < 0) { - return -1; - } else if (old != _this->hidden->nb_buffers) { - return SDL_SetError("different nb of buffers requested"); - } -#endif - _this->hidden->first_start = 1; - } - - if (EnqueueBuffers(_this) < 0) { - return -1; - } - - switch (io) { - case IO_METHOD_READ: - break; - - case IO_METHOD_MMAP: - case IO_METHOD_USERPTR: - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) { - return SDL_SetError("VIDIOC_STREAMON"); - } - break; - } - - return 0; -} - -static int AllocBufferRead(SDL_CameraDevice *_this, size_t buffer_size) -{ - _this->hidden->buffers[0].length = buffer_size; - _this->hidden->buffers[0].start = SDL_calloc(1, buffer_size); - return _this->hidden->buffers[0].start ? 0 : -1; -} - -static int AllocBufferMmap(SDL_CameraDevice *_this) -{ - int fd = _this->hidden->fd; + const int fd = device->hidden->fd; int i; - for (i = 0; i < _this->hidden->nb_buffers; ++i) { + for (i = 0; i < device->hidden->nb_buffers; ++i) { struct v4l2_buffer buf; SDL_zero(buf); @@ -493,29 +346,29 @@ static int AllocBufferMmap(SDL_CameraDevice *_this) return SDL_SetError("VIDIOC_QUERYBUF"); } - _this->hidden->buffers[i].length = buf.length; - _this->hidden->buffers[i].start = + device->hidden->buffers[i].length = buf.length; + device->hidden->buffers[i].start = mmap(NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, fd, buf.m.offset); - if (MAP_FAILED == _this->hidden->buffers[i].start) { + if (MAP_FAILED == device->hidden->buffers[i].start) { return SDL_SetError("mmap"); } } return 0; } -static int AllocBufferUserPtr(SDL_CameraDevice *_this, size_t buffer_size) +static int AllocBufferUserPtr(SDL_CameraDevice *device, size_t buffer_size) { int i; - for (i = 0; i < _this->hidden->nb_buffers; ++i) { - _this->hidden->buffers[i].length = buffer_size; - _this->hidden->buffers[i].start = SDL_calloc(1, buffer_size); + for (i = 0; i < device->hidden->nb_buffers; ++i) { + device->hidden->buffers[i].length = buffer_size; + device->hidden->buffers[i].start = SDL_calloc(1, buffer_size); - if (!_this->hidden->buffers[i].start) { + if (!device->hidden->buffers[i].start) { return -1; } } @@ -530,7 +383,9 @@ static Uint32 format_v4l2_to_sdl(Uint32 fmt) CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN); #undef CASE default: - SDL_Log("Unknown format V4L2_PIX_FORMAT '%d'", fmt); + #if DEBUG_CAMERA + SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%d'", fmt); + #endif return SDL_PIXELFORMAT_UNKNOWN; } } @@ -547,599 +402,442 @@ static Uint32 format_sdl_to_v4l2(Uint32 fmt) } } -static int V4L2_GetNumFormats(SDL_CameraDevice *_this) +static void V4L2_CloseDevice(SDL_CameraDevice *device) { - int fd = _this->hidden->fd; - int i = 0; - struct v4l2_fmtdesc fmtdesc; - - SDL_zero(fmtdesc); - fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - while (ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0) { - fmtdesc.index++; - i++; - } - return i; -} - -static int V4L2_GetFormat(SDL_CameraDevice *_this, int index, Uint32 *format) -{ - int fd = _this->hidden->fd; - struct v4l2_fmtdesc fmtdesc; - - SDL_zero(fmtdesc); - fmtdesc.index = index; - fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0) { - *format = format_v4l2_to_sdl(fmtdesc.pixelformat); - -#if DEBUG_CAMERA - if (fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) { - SDL_Log("%s format emulated", SDL_GetPixelFormatName(*format)); - } - if (fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) { - SDL_Log("%s format compressed", SDL_GetPixelFormatName(*format)); - } -#endif - return 0; - } - - return -1; -} - -static int V4L2_GetNumFrameSizes(SDL_CameraDevice *_this, Uint32 format) -{ - int fd = _this->hidden->fd; - int i = 0; - struct v4l2_frmsizeenum frmsizeenum; - - SDL_zero(frmsizeenum); - frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - frmsizeenum.pixel_format = format_sdl_to_v4l2(format); - while (ioctl(fd,VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) { - frmsizeenum.index++; - if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) { - i++; - } else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) { - i += (1 + (frmsizeenum.stepwise.max_width - frmsizeenum.stepwise.min_width) / frmsizeenum.stepwise.step_width) - * (1 + (frmsizeenum.stepwise.max_height - frmsizeenum.stepwise.min_height) / frmsizeenum.stepwise.step_height); - } else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { - SDL_SetError("V4L2_FRMSIZE_TYPE_CONTINUOUS not handled"); - } - } - return i; -} - -static int V4L2_GetFrameSize(SDL_CameraDevice *_this, Uint32 format, int index, int *width, int *height) -{ - int fd = _this->hidden->fd; - struct v4l2_frmsizeenum frmsizeenum; - int i = 0; - - SDL_zero(frmsizeenum); - frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - frmsizeenum.pixel_format = format_sdl_to_v4l2(format); - while (ioctl(fd,VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) { - frmsizeenum.index++; - - if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) { - if (i == index) { - *width = frmsizeenum.discrete.width; - *height = frmsizeenum.discrete.height; - return 0; - } - i++; - } else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) { - unsigned int w; - for (w = frmsizeenum.stepwise.min_width; w <= frmsizeenum.stepwise.max_width; w += frmsizeenum.stepwise.step_width) { - unsigned int h; - for (h = frmsizeenum.stepwise.min_height; h <= frmsizeenum.stepwise.max_height; h += frmsizeenum.stepwise.step_height) { - if (i == index) { - *width = h; - *height = w; - return 0; - } - i++; - } - } - } else if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { - } - } - - return -1; -} -static void dbg_v4l2_pixelformat(const char *str, int f) -{ - SDL_Log("%s V4L2_format=%d %c%c%c%c", str, f, - (f >> 0) & 0xff, - (f >> 8) & 0xff, - (f >> 16) & 0xff, - (f >> 24) & 0xff); -} -#endif - -static int V4L2_GetDeviceSpec(SDL_CameraDevice *_this, SDL_CameraSpec *spec) -{ - struct v4l2_format fmt; - int fd = _this->hidden->fd; - unsigned int min; - - SDL_zero(fmt); - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - - // Preserve original settings as set by v4l2-ctl for example - if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) { - return SDL_SetError("Error VIDIOC_G_FMT"); - } - - // Buggy driver paranoia. - min = fmt.fmt.pix.width * 2; - if (fmt.fmt.pix.bytesperline < min) { - fmt.fmt.pix.bytesperline = min; - } - min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; - if (fmt.fmt.pix.sizeimage < min) { - fmt.fmt.pix.sizeimage = min; - } - - //spec->width = fmt.fmt.pix.width; - //spec->height = fmt.fmt.pix.height; - _this->hidden->driver_pitch = fmt.fmt.pix.bytesperline; - //spec->format = format_v4l2_to_sdl(fmt.fmt.pix.pixelformat); - - return 0; -} - -static int V4L2_InitDevice(SDL_CameraDevice *_this) -{ - struct v4l2_cropcap cropcap; - struct v4l2_crop crop; - - int fd = _this->hidden->fd; - enum io_method io = _this->hidden->io; - int retval = -1; - - // Select video input, video standard and tune here. - SDL_zero(cropcap); - - cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) { - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - crop.c = cropcap.defrect; // reset to default - - if (xioctl(fd, VIDIOC_S_CROP, &crop) == -1) { - switch (errno) { - case EINVAL: - // Cropping not supported. - break; - default: - // Errors ignored. - break; - } - } - } else { - // Errors ignored. - } - - - { - struct v4l2_format fmt; - SDL_zero(fmt); - - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - fmt.fmt.pix.width = _this->spec.width; - fmt.fmt.pix.height = _this->spec.height; - - - fmt.fmt.pix.pixelformat = format_sdl_to_v4l2(_this->spec.format); - // fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; - fmt.fmt.pix.field = V4L2_FIELD_ANY; - -#if DEBUG_CAMERA - SDL_Log("set SDL format %s", SDL_GetPixelFormatName(_this->spec.format)); - dbg_v4l2_pixelformat("set format", fmt.fmt.pix.pixelformat); -#endif - - if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { - return SDL_SetError("Error VIDIOC_S_FMT"); - } - } - - V4L2_GetDeviceSpec(_this, &_this->spec); - - if (PreEnqueueBuffers(_this) < 0) { - return -1; - } - - { - _this->hidden->buffers = SDL_calloc(_this->hidden->nb_buffers, sizeof(*_this->hidden->buffers)); - if (!_this->hidden->buffers) { - return -1; - } - } - - { - size_t size, pitch; - SDL_CalculateSize(_this->spec.format, _this->spec.width, _this->spec.height, &size, &pitch, SDL_FALSE); - - switch (io) { - case IO_METHOD_READ: - retval = AllocBufferRead(_this, size); - break; - - case IO_METHOD_MMAP: - retval = AllocBufferMmap(_this); - break; - - case IO_METHOD_USERPTR: - retval = AllocBufferUserPtr(_this, size); - break; - } - } - - return (retval < 0) ? -1 : 0; -} - -static void V4L2_CloseDevice(SDL_CameraDevice *_this) -{ - if (!_this) { + if (!device) { return; } - if (_this->hidden) { - if (_this->hidden->buffers) { - enum io_method io = _this->hidden->io; + if (device->hidden) { + const io_method io = device->hidden->io; + const int fd = device->hidden->fd; + if ((io == IO_METHOD_MMAP) || (io == IO_METHOD_USERPTR)) { + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + xioctl(fd, VIDIOC_STREAMOFF, &type); + } + + if (device->hidden->buffers) { switch (io) { + case IO_METHOD_INVALID: + break; + case IO_METHOD_READ: - SDL_free(_this->hidden->buffers[0].start); + SDL_free(device->hidden->buffers[0].start); break; case IO_METHOD_MMAP: - for (int i = 0; i < _this->hidden->nb_buffers; ++i) { - if (munmap(_this->hidden->buffers[i].start, _this->hidden->buffers[i].length) == -1) { + for (int i = 0; i < device->hidden->nb_buffers; ++i) { + if (munmap(device->hidden->buffers[i].start, device->hidden->buffers[i].length) == -1) { SDL_SetError("munmap"); } } break; case IO_METHOD_USERPTR: - for (int i = 0; i < _this->hidden->nb_buffers; ++i) { - SDL_free(_this->hidden->buffers[i].start); + for (int i = 0; i < device->hidden->nb_buffers; ++i) { + SDL_free(device->hidden->buffers[i].start); } break; } - SDL_free(_this->hidden->buffers); + SDL_free(device->hidden->buffers); } - if (_this->hidden->fd != -1) { - if (close(_this->hidden->fd)) { - SDL_SetError("close camera device"); // !!! FIXME: we probably won't ever see this error - } + if (fd != -1) { + close(fd); } - SDL_free(_this->hidden); + SDL_free(device->hidden); - _this->hidden = NULL; + device->hidden = NULL; } } -static int V4L2_OpenDevice(SDL_CameraDevice *_this) +static int V4L2_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec) { + const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle; struct stat st; struct v4l2_capability cap; - enum io_method io; - int fd; + const int fd = open(handle->path, O_RDWR /* required */ | O_NONBLOCK, 0); - _this->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData)); - if (_this->hidden == NULL) { + // most of this probably shouldn't fail unless the filesystem node changed out from under us since MaybeAddDevice(). + if (fd == -1) { + return SDL_SetError("Cannot open '%s': %d, %s", handle->path, errno, strerror(errno)); + } else if (fstat(fd, &st) == -1) { + close(fd); + return SDL_SetError("Cannot identify '%s': %d, %s", handle->path, errno, strerror(errno)); + } else if (!S_ISCHR(st.st_mode)) { + close(fd); + return SDL_SetError("%s is not a character device", handle->path); + } else if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { + const int err = errno; + close(fd); + if (err == EINVAL) { + return SDL_SetError("%s is unexpectedly not a V4L2 device", handle->path); + } + return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", err, handle->path); + } else if ((cap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) { + close(fd); + return SDL_SetError("%s is unexpectedly not a video capture device", handle->path); + } + + device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData)); + if (device->hidden == NULL) { + close(fd); return -1; } - _this->hidden->fd = -1; + device->hidden->fd = fd; + device->hidden->io = IO_METHOD_INVALID; - if (stat(_this->dev_name, &st) == -1) { - return SDL_SetError("Cannot identify '%s': %d, %s", _this->dev_name, errno, strerror(errno)); - } else if (!S_ISCHR(st.st_mode)) { - return SDL_SetError("%s is no device", _this->dev_name); - } else if ((fd = open(_this->dev_name, O_RDWR /* required */ | O_NONBLOCK, 0)) == -1) { - return SDL_SetError("Cannot open '%s': %d, %s", _this->dev_name, errno, strerror(errno)); + // Select video input, video standard and tune here. + // errors in the crop code are not fatal. + struct v4l2_cropcap cropcap; + SDL_zero(cropcap); + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) { + struct v4l2_crop crop; + SDL_zero(crop); + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; // reset to default + xioctl(fd, VIDIOC_S_CROP, &crop); } - _this->hidden->fd = fd; - _this->hidden->io = IO_METHOD_MMAP; -// _this->hidden->io = IO_METHOD_USERPTR; -// _this->hidden->io = IO_METHOD_READ; -// - if (_this->hidden->io == IO_METHOD_READ) { - _this->hidden->nb_buffers = 1; - } else { - _this->hidden->nb_buffers = 8; // Number of image as internal buffer, - } - io = _this->hidden->io; + struct v4l2_format fmt; + SDL_zero(fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = spec->width; + fmt.fmt.pix.height = spec->height; + fmt.fmt.pix.pixelformat = format_sdl_to_v4l2(spec->format); + //fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + fmt.fmt.pix.field = V4L2_FIELD_ANY; - if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { - if (errno == EINVAL) { - return SDL_SetError("%s is no V4L2 device", _this->dev_name); - } else { - return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", errno, _this->dev_name); + #if DEBUG_CAMERA + SDL_Log("CAMERA: set SDL format %s", SDL_GetPixelFormatName(spec->format)); + { const Uint32 f = fmt.fmt.pix.pixelformat; SDL_Log("CAMERA: set format V4L2_format=%d %c%c%c%c", f, (f >> 0) & 0xff, (f >> 8) & 0xff, (f >> 16) & 0xff, (f >> 24) & 0xff); } + #endif + + if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { + return SDL_SetError("Error VIDIOC_S_FMT"); + } + + SDL_zero(fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) { + return SDL_SetError("Error VIDIOC_G_FMT"); + } + device->hidden->driver_pitch = fmt.fmt.pix.bytesperline; + + io_method io = IO_METHOD_INVALID; + if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_STREAMING)) { + struct v4l2_requestbuffers req; + SDL_zero(req); + req.count = 8; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + if ((xioctl(fd, VIDIOC_REQBUFS, &req) == 0) && (req.count >= 2)) { + io = IO_METHOD_MMAP; + device->hidden->nb_buffers = req.count; + } else { // mmap didn't work out? Try USERPTR. + SDL_zero(req); + req.count = 8; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; + if (xioctl(fd, VIDIOC_REQBUFS, &req) == 0) { + io = IO_METHOD_USERPTR; + device->hidden->nb_buffers = 8; + } } } - if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { - return SDL_SetError("%s is no video capture device", _this->dev_name); + if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_READWRITE)) { + io = IO_METHOD_READ; + device->hidden->nb_buffers = 1; } -#if 0 - if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { - SDL_Log("%s is video capture device - single plane", _this->dev_name); + if (io == IO_METHOD_INVALID) { + return SDL_SetError("Don't have a way to talk to this device"); } - if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE)) { - SDL_Log("%s is video capture device - multiple planes", _this->dev_name); - } -#endif + device->hidden->io = io; + + device->hidden->buffers = SDL_calloc(device->hidden->nb_buffers, sizeof(*device->hidden->buffers)); + if (!device->hidden->buffers) { + return -1; + } + + size_t size, pitch; + SDL_CalculateSize(device->spec.format, device->spec.width, device->spec.height, &size, &pitch, SDL_FALSE); + + int rc = 0; switch (io) { case IO_METHOD_READ: - if (!(cap.capabilities & V4L2_CAP_READWRITE)) { - return SDL_SetError("%s does not support read i/o", _this->dev_name); - } + rc = AllocBufferRead(device, size); break; case IO_METHOD_MMAP: + rc = AllocBufferMmap(device); + break; + case IO_METHOD_USERPTR: - if (!(cap.capabilities & V4L2_CAP_STREAMING)) { - return SDL_SetError("%s does not support streaming i/o", _this->dev_name); - } + rc = AllocBufferUserPtr(device, size); + break; + + case IO_METHOD_INVALID: + SDL_assert(!"Shouldn't have hit this"); break; } - return 0; -} - -static int V4L2_GetDeviceName(SDL_CameraDeviceID instance_id, char *buf, int size) -{ - SDL_cameralist_item *item; - for (item = SDL_cameralist; item; item = item->next) { - if (item->instance_id == instance_id) { - SDL_snprintf(buf, size, "%s", item->fname); - return 0; + if (rc < 0) { + return -1; + } else if (EnqueueBuffers(device) < 0) { + return -1; + } else if (io != IO_METHOD_READ) { + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) { + return SDL_SetError("VIDIOC_STREAMON"); } } - // unknown instance_id - return -1; + return 0; } -static SDL_CameraDeviceID *V4L2_GetDevices(int *count) +static SDL_bool FindV4L2CameraDeviceByBusInfoCallback(SDL_CameraDevice *device, void *userdata) { - // real list of ID - const int num = num_cameras; - SDL_CameraDeviceID *retval = (SDL_CameraDeviceID *)SDL_malloc((num + 1) * sizeof(*retval)); - - if (retval == NULL) { - *count = 0; - return NULL; - } - - int i = 0; - for (SDL_cameralist_item *item = SDL_cameralist; item; item = item->next) { - retval[i++] = item->instance_id; - } - - retval[num] = 0; - *count = num; - return retval; + const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle; + return (SDL_strcmp(handle->bus_info, (const char *) userdata) == 0); } - -#ifdef SDL_USE_LIBUDEV -static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) +typedef struct SpecAddData { - if (!devpath || !(udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) { + SDL_CameraSpec *specs; + int num_specs; + int allocated_specs; +} SpecAddData; + +static int AddCameraSpec(SpecAddData *data, Uint32 fmt, int w, int h) +{ + 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 -1; + } + data->specs = (SDL_CameraSpec *) ptr; + data->allocated_specs = newalloc; + } + + SDL_CameraSpec *spec = &data->specs[data->num_specs]; + spec->format = fmt; + spec->width = w; + spec->height = h; + + data->num_specs++; + + return 0; +} + +static void MaybeAddDevice(const char *path) +{ + if (!path) { return; } - switch (udev_type) { - case SDL_UDEV_DEVICEADDED: - MaybeAddDevice(devpath); - break; - - case SDL_UDEV_DEVICEREMOVED: - MaybeRemoveDevice(devpath); - break; - - default: - break; + struct stat st; + const int fd = open(path, O_RDWR /* required */ | O_NONBLOCK, 0); + if (fd == -1) { + return; // can't open it? skip it. + } else if (fstat(fd, &st) == -1) { + close(fd); + return; // can't stat it? skip it. + } else if (!S_ISCHR(st.st_mode)) { + close(fd); + return; // not a character device. } -} -#endif // SDL_USE_LIBUDEV -static SDL_bool DeviceExists(const char *path, const char *bus_info) { - for (SDL_cameralist_item *item = SDL_cameralist; item; item = item->next) { - // found same dev name - if (SDL_strcmp(path, item->fname) == 0) { - return SDL_TRUE; - } - // found same bus_info - if (SDL_strcmp(bus_info, item->bus_info) == 0) { - return SDL_TRUE; - } - } - return SDL_FALSE; -} - -static int MaybeAddDevice(const char *path) -{ - char *bus_info = NULL; struct v4l2_capability vcap; - int err; - int fd; - SDL_cameralist_item *item; - - if (!path) { - return -1; + const int rc = ioctl(fd, VIDIOC_QUERYCAP, &vcap); + if (rc != 0) { + close(fd); + return; // probably not a v4l2 device at all. + } else if ((vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) { + close(fd); + return; // not a video capture device. + } else if (SDL_FindPhysicalCameraDeviceByCallback(FindV4L2CameraDeviceByBusInfoCallback, vcap.bus_info)) { + close(fd); + return; // already have it. } - fd = open(path, O_RDWR); - if (fd < 0) { - return -2; // stop iterating /dev/video%d + #if DEBUG_CAMERA + SDL_Log("CAMERA: V4L2 camera path='%s' bus_info='%s' name='%s'", path, (const char *) vcap.bus_info, vcap.card); + #endif + + SpecAddData add_data; + SDL_zero(add_data); + + struct v4l2_fmtdesc fmtdesc; + SDL_zero(fmtdesc); + fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) { + const Uint32 sdlfmt = format_v4l2_to_sdl(fmtdesc.pixelformat); + + #if DEBUG_CAMERA + SDL_Log("CAMERA: - Has format '%s'%s%s", SDL_GetPixelFormatName(sdlfmt), + (fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) ? " [EMULATED]" : "", + (fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) ? " [COMPRESSED]" : ""); + #endif + + fmtdesc.index++; // prepare for next iteration. + + if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) { + continue; // unsupported by SDL atm. + } + + struct v4l2_frmsizeenum frmsizeenum; + SDL_zero(frmsizeenum); + frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + frmsizeenum.pixel_format = fmtdesc.pixelformat; + + while (ioctl(fd,VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) { + if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + const int w = (int) frmsizeenum.discrete.width; + const int h = (int) frmsizeenum.discrete.height; + #if DEBUG_CAMERA + SDL_Log("CAMERA: * Has discrete size %dx%d", w, h); + #endif + if (AddCameraSpec(&add_data, sdlfmt, w, h) == -1) { + break; // Probably out of memory; we'll go with what we have, if anything. + } + frmsizeenum.index++; // set up for the next one. + } else if ((frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) || (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS)) { + const int minw = (int) frmsizeenum.stepwise.min_width; + const int minh = (int) frmsizeenum.stepwise.min_height; + const int maxw = (int) frmsizeenum.stepwise.max_width; + const int maxh = (int) frmsizeenum.stepwise.max_height; + const int stepw = (int) frmsizeenum.stepwise.step_width; + const int steph = (int) frmsizeenum.stepwise.step_height; + for (int w = minw; w <= maxw; w += stepw) { + for (int h = minh; w <= maxh; w += steph) { + #if DEBUG_CAMERA + SDL_Log("CAMERA: * Has %s size %dx%d", (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) ? "stepwise" : "continuous", w, h); + #endif + if (AddCameraSpec(&add_data, sdlfmt, w, h) == -1) { + break; // Probably out of memory; we'll go with what we have, if anything. + } + + } + } + break; + } + } } - err = ioctl(fd, VIDIOC_QUERYCAP, &vcap); + close(fd); - if (err) { - return -1; + + #if DEBUG_CAMERA + SDL_Log("CAMERA: (total specs: %d)", add_data.num_specs); + #endif + + if (add_data.num_specs > 0) { + V4L2DeviceHandle *handle = (V4L2DeviceHandle *) SDL_calloc(1, sizeof (V4L2DeviceHandle)); + if (handle) { + handle->path = SDL_strdup(path); + if (handle->path) { + handle->bus_info = SDL_strdup((char *)vcap.bus_info); + if (handle->bus_info) { + if (SDL_AddCameraDevice((const char *) vcap.card, add_data.num_specs, add_data.specs, handle)) { + SDL_free(add_data.specs); + return; // good to go. + } + SDL_free(handle->bus_info); + } + SDL_free(handle->path); + } + SDL_free(handle); + } } + SDL_free(add_data.specs); +} - bus_info = SDL_strdup((char *)vcap.bus_info); - - if (DeviceExists(path, bus_info)) { - SDL_free(bus_info); - return 0; +static void V4L2_FreeDeviceHandle(SDL_CameraDevice *device) +{ + if (device) { + V4L2DeviceHandle *handle = (V4L2DeviceHandle *) device->handle; + SDL_free(handle->path); + SDL_free(handle->bus_info); + SDL_free(handle); } - - - // Add new item - item = (SDL_cameralist_item *)SDL_calloc(1, sizeof(SDL_cameralist_item)); - if (!item) { - SDL_free(bus_info); - return -1; - } - - item->fname = SDL_strdup(path); - if (!item->fname) { - SDL_free(item); - SDL_free(bus_info); - return -1; - } - - item->fname = SDL_strdup(path); - item->bus_info = bus_info; - item->instance_id = SDL_GetNextObjectID(); - - - if (!SDL_cameralist_tail) { - SDL_cameralist = SDL_cameralist_tail = item; - } else { - SDL_cameralist_tail->next = item; - SDL_cameralist_tail = item; - } - - ++num_cameras; - - // !!! TODO: Send a add event? -#if DEBUG_CAMERA - SDL_Log("Added video camera ID: %d %s (%s) (total: %d)", item->instance_id, path, bus_info, num_cameras); -#endif - return 0; } #ifdef SDL_USE_LIBUDEV -static int MaybeRemoveDevice(const char *path) +static SDL_bool FindV4L2CameraDeviceByPathCallback(SDL_CameraDevice *device, void *userdata) { + const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle; + return (SDL_strcmp(handle->path, (const char *) userdata) == 0); +} - SDL_cameralist_item *item; - SDL_cameralist_item *prev = NULL; -#if DEBUG_CAMERA - SDL_Log("Remove video camera %s", path); -#endif - if (!path) { - return -1; +static void MaybeRemoveDevice(const char *path) +{ + if (path) { + SDL_CameraDeviceDisconnected(SDL_FindPhysicalCameraDeviceByCallback(FindV4L2CameraDeviceByPathCallback, (void *) path)); } +} - for (item = SDL_cameralist; item; item = item->next) { - // found it, remove it. - if (SDL_strcmp(path, item->fname) == 0) { - if (prev) { - prev->next = item->next; - } else { - SDL_assert(SDL_cameralist == item); - SDL_cameralist = item->next; - } - if (item == SDL_cameralist_tail) { - SDL_cameralist_tail = prev; - } - - // Need to decrement the count - --num_cameras; - // !!! TODO: Send a remove event? - - SDL_free(item->fname); - SDL_free(item->bus_info); - SDL_free(item); - return 0; +static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) +{ + if (devpath && (udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) { + if (udev_type == SDL_UDEV_DEVICEADDED) { + MaybeAddDevice(devpath); + } else if (udev_type == SDL_UDEV_DEVICEREMOVED) { + MaybeRemoveDevice(devpath); } - prev = item; } - return 0; } #endif // SDL_USE_LIBUDEV - static void V4L2_Deinitialize(void) { - for (SDL_cameralist_item *item = SDL_cameralist; item; ) { - SDL_cameralist_item *tmp = item->next; - SDL_free(item->fname); - SDL_free(item->bus_info); - SDL_free(item); - item = tmp; - } - - num_cameras = 0; - SDL_cameralist = NULL; - SDL_cameralist_tail = NULL; +#ifdef SDL_USE_LIBUDEV + SDL_UDEV_DelCallback(CameraUdevCallback); + SDL_UDEV_Quit(); +#endif // SDL_USE_LIBUDEV } static void V4L2_DetectDevices(void) { +#ifdef SDL_USE_LIBUDEV + if (SDL_UDEV_Init() == 0) { + if (SDL_UDEV_AddCallback(CameraUdevCallback) >= 0) { // !!! FIXME: this should return 0 on success, it currently returns 1. + SDL_UDEV_Scan(); // Force a scan to build the initial device list + } + } +#else + DIR *dirp = opendir("/dev"); + if (dirp) { + struct dirent *dent; + while ((dent = readdir(dirp)) != NULL) { + int num = 0; + if (SDL_sscanf(dent->d_name, "video%d", &num) == 1) { + char fullpath[64]; + SDL_snprintf(fullpath, sizeof (fullpath), "/dev/video%d", num); + MaybeAddDevice(fullpath); + } + } + closedir(dirp); + } +#endif // SDL_USE_LIBUDEV } static SDL_bool V4L2_Init(SDL_CameraDriverImpl *impl) { - // !!! FIXME: move to DetectDevices - const char pattern[] = "/dev/video%d"; - char path[PATH_MAX]; - - /* - * Limit amount of checks to MAX_CAMERA_DEVICES since we may or may not have - * permission to some or all devices. - */ - for (int i = 0; i < MAX_CAMERA_DEVICES; i++) { - (void)SDL_snprintf(path, PATH_MAX, pattern, i); - if (MaybeAddDevice(path) == -2) { - break; - } - } - -#ifdef SDL_USE_LIBUDEV - if (SDL_UDEV_Init() < 0) { - return SDL_SetError("Could not initialize UDEV"); - } else if (SDL_UDEV_AddCallback(CameraUdevCallback) < 0) { - SDL_UDEV_Quit(); - return SDL_SetError("Could not setup Video Capture <-> udev callback"); - } - - // Force a scan to build the initial device list - SDL_UDEV_Scan(); -#endif // SDL_USE_LIBUDEV - impl->DetectDevices = V4L2_DetectDevices; impl->OpenDevice = V4L2_OpenDevice; impl->CloseDevice = V4L2_CloseDevice; - impl->InitDevice = V4L2_InitDevice; - impl->GetDeviceSpec = V4L2_GetDeviceSpec; - impl->StartCamera = V4L2_StartCamera; - impl->StopCamera = V4L2_StopCamera; + impl->WaitDevice = V4L2_WaitDevice; impl->AcquireFrame = V4L2_AcquireFrame; impl->ReleaseFrame = V4L2_ReleaseFrame; - impl->GetNumFormats = V4L2_GetNumFormats; - impl->GetFormat = V4L2_GetFormat; - impl->GetNumFrameSizes = V4L2_GetNumFrameSizes; - impl->GetFrameSize = V4L2_GetFrameSize; - impl->GetDeviceName = V4L2_GetDeviceName; - impl->GetDevices = V4L2_GetDevices; + impl->FreeDeviceHandle = V4L2_FreeDeviceHandle; impl->Deinitialize = V4L2_Deinitialize; return SDL_TRUE; diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index b9ebf221e..9943d9d0f 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -957,21 +957,18 @@ SDL3_0.0.0 { SDL_SetWindowShape; SDL_RenderViewportSet; SDL_HasProperty; + SDL_GetNumCameraDrivers; + SDL_GetCameraDriver; + SDL_GetCurrentCameraDriver; SDL_GetCameraDevices; - SDL_OpenCamera; - SDL_SetCameraSpec; - SDL_OpenCameraWithSpec; + SDL_GetCameraDeviceSupportedSpecs; SDL_GetCameraDeviceName; + SDL_OpenCameraDevice; + SDL_GetCameraInstanceID; + SDL_GetCameraProperties; SDL_GetCameraSpec; - SDL_GetCameraFormat; - SDL_GetNumCameraFormats; - SDL_GetCameraFrameSize; - SDL_GetNumCameraFrameSizes; - SDL_GetCameraStatus; - SDL_StartCamera; SDL_AcquireCameraFrame; SDL_ReleaseCameraFrame; - SDL_StopCamera; SDL_CloseCamera; # extra symbols go here (don't modify this line) local: *; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 79206a493..e7f050c03 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -982,19 +982,16 @@ #define SDL_SetWindowShape SDL_SetWindowShape_REAL #define SDL_RenderViewportSet SDL_RenderViewportSet_REAL #define SDL_HasProperty SDL_HasProperty_REAL +#define SDL_GetNumCameraDrivers SDL_GetNumCameraDrivers_REAL +#define SDL_GetCameraDriver SDL_GetCameraDriver_REAL +#define SDL_GetCurrentCameraDriver SDL_GetCurrentCameraDriver_REAL #define SDL_GetCameraDevices SDL_GetCameraDevices_REAL -#define SDL_OpenCamera SDL_OpenCamera_REAL -#define SDL_SetCameraSpec SDL_SetCameraSpec_REAL -#define SDL_OpenCameraWithSpec SDL_OpenCameraWithSpec_REAL +#define SDL_GetCameraDeviceSupportedSpecs SDL_GetCameraDeviceSupportedSpecs_REAL #define SDL_GetCameraDeviceName SDL_GetCameraDeviceName_REAL +#define SDL_OpenCameraDevice SDL_OpenCameraDevice_REAL +#define SDL_GetCameraInstanceID SDL_GetCameraInstanceID_REAL +#define SDL_GetCameraProperties SDL_GetCameraProperties_REAL #define SDL_GetCameraSpec SDL_GetCameraSpec_REAL -#define SDL_GetCameraFormat SDL_GetCameraFormat_REAL -#define SDL_GetNumCameraFormats SDL_GetNumCameraFormats_REAL -#define SDL_GetCameraFrameSize SDL_GetCameraFrameSize_REAL -#define SDL_GetNumCameraFrameSizes SDL_GetNumCameraFrameSizes_REAL -#define SDL_GetCameraStatus SDL_GetCameraStatus_REAL -#define SDL_StartCamera SDL_StartCamera_REAL #define SDL_AcquireCameraFrame SDL_AcquireCameraFrame_REAL #define SDL_ReleaseCameraFrame SDL_ReleaseCameraFrame_REAL -#define SDL_StopCamera SDL_StopCamera_REAL #define SDL_CloseCamera SDL_CloseCamera_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 4dd7e019e..9f1f73589 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1007,19 +1007,16 @@ SDL_DYNAPI_PROC(int,SDL_RenderGeometryRawFloat,(SDL_Renderer *a, SDL_Texture *b, SDL_DYNAPI_PROC(int,SDL_SetWindowShape,(SDL_Window *a, SDL_Surface *b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_RenderViewportSet,(SDL_Renderer *a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_HasProperty,(SDL_PropertiesID a, const char *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_GetNumCameraDrivers,(void),(),return) +SDL_DYNAPI_PROC(const char*,SDL_GetCameraDriver,(int a),(a),return) +SDL_DYNAPI_PROC(const char*,SDL_GetCurrentCameraDriver,(void),(),return) SDL_DYNAPI_PROC(SDL_CameraDeviceID*,SDL_GetCameraDevices,(int *a),(a),return) -SDL_DYNAPI_PROC(SDL_CameraDevice*,SDL_OpenCamera,(SDL_CameraDeviceID a),(a),return) -SDL_DYNAPI_PROC(int,SDL_SetCameraSpec,(SDL_CameraDevice *a, const SDL_CameraSpec *b, SDL_CameraSpec *c, int d),(a,b,c,d),return) -SDL_DYNAPI_PROC(SDL_CameraDevice*,SDL_OpenCameraWithSpec,(SDL_CameraDeviceID a, const SDL_CameraSpec *b, SDL_CameraSpec *c, int d),(a,b,c,d),return) -SDL_DYNAPI_PROC(const char*,SDL_GetCameraDeviceName,(SDL_CameraDeviceID a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetCameraSpec,(SDL_CameraDevice *a, SDL_CameraSpec *b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_GetCameraFormat,(SDL_CameraDevice *a, int b, Uint32 *c),(a,b,c),return) -SDL_DYNAPI_PROC(int,SDL_GetNumCameraFormats,(SDL_CameraDevice *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetCameraFrameSize,(SDL_CameraDevice *a, Uint32 b, int c, int *d, int *e),(a,b,c,d,e),return) -SDL_DYNAPI_PROC(int,SDL_GetNumCameraFrameSizes,(SDL_CameraDevice *a, Uint32 b),(a,b),return) -SDL_DYNAPI_PROC(SDL_CameraStatus,SDL_GetCameraStatus,(SDL_CameraDevice *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_StartCamera,(SDL_CameraDevice *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_AcquireCameraFrame,(SDL_CameraDevice *a, SDL_CameraFrame *b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_ReleaseCameraFrame,(SDL_CameraDevice *a, SDL_CameraFrame *b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_StopCamera,(SDL_CameraDevice *a),(a),return) -SDL_DYNAPI_PROC(void,SDL_CloseCamera,(SDL_CameraDevice *a),(a),) +SDL_DYNAPI_PROC(SDL_CameraSpec*,SDL_GetCameraDeviceSupportedSpecs,(SDL_CameraDeviceID a, int *b),(a,b),return) +SDL_DYNAPI_PROC(char*,SDL_GetCameraDeviceName,(SDL_CameraDeviceID a),(a),return) +SDL_DYNAPI_PROC(SDL_Camera*,SDL_OpenCameraDevice,(SDL_CameraDeviceID a, const SDL_CameraSpec *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_CameraDeviceID,SDL_GetCameraInstanceID,(SDL_Camera *a),(a),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetCameraProperties,(SDL_Camera *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_GetCameraSpec,(SDL_Camera *a, SDL_CameraSpec *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_AcquireCameraFrame,(SDL_Camera *a, Uint64 *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_ReleaseCameraFrame,(SDL_Camera *a, SDL_Surface *b),(a,b),return) +SDL_DYNAPI_PROC(void,SDL_CloseCamera,(SDL_Camera *a),(a),) diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 02600437c..04a54953a 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -25,6 +25,7 @@ #include "SDL_events_c.h" #include "../SDL_hints_c.h" #include "../audio/SDL_audio_c.h" +#include "../camera/SDL_camera_c.h" #include "../timer/SDL_timer_c.h" #ifndef SDL_JOYSTICK_DISABLED #include "../joystick/SDL_joystick_c.h" @@ -554,6 +555,15 @@ static void SDL_LogEvent(const SDL_Event *event) break; #undef PRINT_AUDIODEV_EVENT +#define PRINT_CAMERADEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->cdevice.timestamp, (uint)event->cdevice.which) + SDL_EVENT_CASE(SDL_EVENT_CAMERA_DEVICE_ADDED) + PRINT_CAMERADEV_EVENT(event); + break; + SDL_EVENT_CASE(SDL_EVENT_CAMERA_DEVICE_REMOVED) + PRINT_CAMERADEV_EVENT(event); + break; +#undef PRINT_CAMERADEV_EVENT + SDL_EVENT_CASE(SDL_EVENT_SENSOR_UPDATE) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%d data[0]=%f data[1]=%f data[2]=%f data[3]=%f data[4]=%f data[5]=%f)", (uint)event->sensor.timestamp, (int)event->sensor.which, @@ -942,6 +952,10 @@ static void SDL_PumpEventsInternal(SDL_bool push_sentinel) SDL_UpdateAudio(); #endif +#ifndef SDL_CAMERA_DISABLED + SDL_UpdateCamera(); +#endif + #ifndef SDL_SENSOR_DISABLED /* Check for sensor state change */ if (SDL_update_sensors) { diff --git a/test/testcamera.c b/test/testcamera.c index f96b72866..c524a13fa 100644 --- a/test/testcamera.c +++ b/test/testcamera.c @@ -18,8 +18,13 @@ #include #endif -#include - +#if 1 +int main(int argc, char **argv) +{ + SDL_Log("FIXME: update me"); + return 0; +} +#else static const char *usage = "\ \n\ =========================================================================\n\ @@ -769,3 +774,4 @@ int main(int argc, char **argv) return 0; } +#endif \ No newline at end of file diff --git a/test/testcameraminimal.c b/test/testcameraminimal.c index ce2d3ec20..57ce1830d 100644 --- a/test/testcameraminimal.c +++ b/test/testcameraminimal.c @@ -28,21 +28,14 @@ int main(int argc, char **argv) int quit = 0; SDLTest_CommonState *state = NULL; - SDL_CameraDevice *device = NULL; - SDL_CameraSpec obtained; - - SDL_CameraFrame frame_current; + SDL_Camera *camera = NULL; + SDL_CameraSpec spec; SDL_Texture *texture = NULL; int texture_updated = 0; + SDL_Surface *frame_current = NULL; SDL_zero(evt); - SDL_zero(obtained); - SDL_zero(frame_current); - - /* Set 0 to disable TouchEvent to be duplicated as MouseEvent with SDL_TOUCH_MOUSEID */ - SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); - /* Set 0 to disable MouseEvent to be duplicated as TouchEvent with SDL_MOUSE_TOUCHID */ - SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); + SDL_zero(spec); /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -73,20 +66,42 @@ int main(int argc, char **argv) return 1; } - device = SDL_OpenCameraWithSpec(0, NULL, &obtained, SDL_CAMERA_ALLOW_ANY_CHANGE); - if (!device) { - SDL_Log("No camera? %s", SDL_GetError()); + SDL_CameraDeviceID *devices = SDL_GetCameraDevices(NULL); + if (!devices) { + SDL_Log("SDL_GetCameraDevices failed: %s", SDL_GetError()); return 1; } - if (SDL_StartCamera(device) < 0) { - SDL_Log("error SDL_StartCamera(): %s", SDL_GetError()); + const SDL_CameraDeviceID devid = devices[0]; /* just take the first one. */ + SDL_free(devices); + + if (!devid) { + SDL_Log("No cameras available? %s", SDL_GetError()); + return 1; + } + + SDL_CameraSpec *pspec = NULL; + #if 0 /* just for edge-case testing purposes, ignore. */ + pspec = &spec; + spec.width = 100 /*1280 * 2*/; + spec.height = 100 /*720 * 2*/; + spec.format = SDL_PIXELFORMAT_YUY2 /*SDL_PIXELFORMAT_RGBA8888*/; + #endif + + camera = SDL_OpenCameraDevice(devid, pspec); + if (!camera) { + SDL_Log("Failed to open camera device: %s", SDL_GetError()); + return 1; + } + + if (SDL_GetCameraSpec(camera, &spec) < 0) { + SDL_Log("Couldn't get camera spec: %s", SDL_GetError()); return 1; } /* Create texture with appropriate format */ if (texture == NULL) { - texture = SDL_CreateTexture(renderer, obtained.format, SDL_TEXTUREACCESS_STATIC, obtained.width, obtained.height); + texture = SDL_CreateTexture(renderer, spec.format, SDL_TEXTUREACCESS_STATIC, spec.width, spec.height); if (texture == NULL) { SDL_Log("Couldn't create texture: %s", SDL_GetError()); return 1; @@ -118,21 +133,18 @@ int main(int argc, char **argv) } { - SDL_CameraFrame frame_next; - SDL_zero(frame_next); + Uint64 timestampNS = 0; + SDL_Surface *frame_next = SDL_AcquireCameraFrame(camera, ×tampNS); - if (SDL_AcquireCameraFrame(device, &frame_next) < 0) { - SDL_Log("err SDL_AcquireCameraFrame: %s", SDL_GetError()); - } #if 0 - if (frame_next.num_planes) { - SDL_Log("frame: %p at %" SDL_PRIu64, (void*)frame_next.data[0], frame_next.timestampNS); + if (frame_next) { + SDL_Log("frame: %p at %" SDL_PRIu64, (void*)frame_next->pixels, timestampNS); } #endif - if (frame_next.num_planes) { - if (frame_current.num_planes) { - if (SDL_ReleaseCameraFrame(device, &frame_current) < 0) { + if (frame_next) { + if (frame_current) { + if (SDL_ReleaseCameraFrame(camera, frame_current) < 0) { SDL_Log("err SDL_ReleaseCameraFrame: %s", SDL_GetError()); } } @@ -146,20 +158,8 @@ int main(int argc, char **argv) } /* Update SDL_Texture with last video frame (only once per new frame) */ - if (frame_current.num_planes && texture_updated == 0) { - /* Use software data */ - if (frame_current.num_planes == 1) { - SDL_UpdateTexture(texture, NULL, - frame_current.data[0], frame_current.pitch[0]); - } else if (frame_current.num_planes == 2) { - SDL_UpdateNVTexture(texture, NULL, - frame_current.data[0], frame_current.pitch[0], - frame_current.data[1], frame_current.pitch[1]); - } else if (frame_current.num_planes == 3) { - SDL_UpdateYUVTexture(texture, NULL, frame_current.data[0], frame_current.pitch[0], - frame_current.data[1], frame_current.pitch[1], - frame_current.data[2], frame_current.pitch[2]); - } + if (frame_current && texture_updated == 0) { + SDL_UpdateTexture(texture, NULL, frame_current->pixels, frame_current->pitch); texture_updated = 1; } @@ -177,7 +177,7 @@ int main(int argc, char **argv) th = (int)((float) th * scale); } d.x = (float)(10 ); - d.y = (float)(win_h - th); + d.y = ((float)(win_h - th)) / 2.0f; d.w = (float)tw; d.h = (float)(th - 10); SDL_RenderTexture(renderer, texture, NULL, &d); @@ -186,13 +186,10 @@ int main(int argc, char **argv) SDL_RenderPresent(renderer); } - if (SDL_StopCamera(device) < 0) { - SDL_Log("error SDL_StopCamera(): %s", SDL_GetError()); + if (frame_current) { + SDL_ReleaseCameraFrame(camera, frame_current); } - if (frame_current.num_planes) { - SDL_ReleaseCameraFrame(device, &frame_current); - } - SDL_CloseCamera(device); + SDL_CloseCamera(camera); if (texture) { SDL_DestroyTexture(texture); @@ -205,3 +202,4 @@ int main(int argc, char **argv) return 0; } +