Browse Source

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).
Ryan C. Gordon 1 year ago
parent
commit
d3e6ef3cc6

+ 123 - 192
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
  *  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
 typedef struct SDL_CameraSpec
@@ -75,39 +68,6 @@ typedef struct SDL_CameraSpec
     int height;             /**< Frame height */
     int height;             /**< Frame height */
 } SDL_CameraSpec;
 } 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.
  * 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.
  * 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
  * \returns a 0 terminated array of camera instance IDs which should be
  *          freed with SDL_free(), or NULL on error; call SDL_GetError() for
  *          freed with SDL_free(), or NULL on error; call SDL_GetError() for
  *          more details.
  *          more details.
  *
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.0.0.
  * \since This function is available since SDL 3.0.0.
  *
  *
  * \sa SDL_OpenCamera
  * \sa SDL_OpenCamera
@@ -188,237 +150,202 @@ extern DECLSPEC const char *SDLCALL SDL_GetCurrentCameraDriver(void);
 extern DECLSPEC SDL_CameraDeviceID *SDLCALL SDL_GetCameraDevices(int *count);
 extern DECLSPEC SDL_CameraDeviceID *SDLCALL SDL_GetCameraDevices(int *count);
 
 
 /**
 /**
- * Open a Video Capture device
+ * Get the list of native formats/sizes a camera supports.
  *
  *
- * \param instance_id the camera device instance ID
- * \returns device, or NULL on failure; call SDL_GetError() for more
- *          information.
+ * 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.
  *
  *
- * \since This function is available since SDL 3.0.0.
+ * 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.
  *
  *
- * \sa SDL_GetCameraDeviceName
- * \sa SDL_GetCameraDevices
- * \sa SDL_OpenCameraWithSpec
- */
-extern DECLSPEC SDL_CameraDevice *SDLCALL SDL_OpenCamera(SDL_CameraDeviceID instance_id);
-
-/**
- * Set specification
+ * 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`).
  *
  *
- * \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
- *          SDL_GetError() for more information.
+ * The returned list is owned by the caller, and should be released with
+ * SDL_free() when no longer needed.
  *
  *
- * \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 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.
  *
  *
- * \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.
  * \since This function is available since SDL 3.0.0.
  *
  *
- * \sa SDL_OpenCamera
- * \sa SDL_SetCameraSpec
- * \sa SDL_GetCameraSpec
+ * \sa SDL_GetCameraDevices
+ * \sa SDL_OpenCameraDevice
  */
  */
-extern DECLSPEC SDL_CameraDevice *SDLCALL SDL_OpenCameraWithSpec(SDL_CameraDeviceID instance_id,
-                                                                              const SDL_CameraSpec *desired,
-                                                                              SDL_CameraSpec *obtained,
-                                                                              int allowed_changes);
+extern DECLSPEC SDL_CameraSpec *SDLCALL SDL_GetCameraDeviceSupportedSpecs(SDL_CameraDeviceID devid, int *count);
 
 
 /**
 /**
- * Get device name
+ * Get human-readable device name for a camera.
  *
  *
- * \param instance_id the camera device instance ID
- * \returns device name, shouldn't be freed
+ * The returned string is owned by the caller; please release it with
+ * SDL_free() when done with it.
  *
  *
- * \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
+ * \param instance_id the camera device instance ID
+ * \returns Human-readable device name, or NULL on error; call SDL_GetError() for more information.
  *
  *
- * \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.
+ * \threadsafety It is safe to call this function from any thread.
  *
  *
  * \since This function is available since SDL 3.0.0.
  * \since This function is available since SDL 3.0.0.
  *
  *
- * \sa SDL_SetCameraSpec
- * \sa SDL_OpenCameraWithSpec
+ * \sa SDL_GetCameraDevices
  */
  */
-extern DECLSPEC int SDLCALL SDL_GetCameraSpec(SDL_CameraDevice *device, SDL_CameraSpec *spec);
-
+extern DECLSPEC char * SDLCALL SDL_GetCameraDeviceName(SDL_CameraDeviceID instance_id);
 
 
 /**
 /**
- * Get frame format of camera device.
+ * Open a video capture device (a "camera").
  *
  *
- * The value can be used to fill SDL_CameraSpec structure.
+ * 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.
  *
  *
- * \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.
+ * 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).
  *
  *
- * \since This function is available since SDL 3.0.0.
+ * 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().
  *
  *
- * \sa SDL_GetNumCameraFormats
- */
-extern DECLSPEC int SDLCALL SDL_GetCameraFormat(SDL_CameraDevice *device,
-                                                      int index,
-                                                      Uint32 *format);
-
-/**
- * Number of available formats for the device
+ * \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.
  *
  *
- * \param device opened camera device
- * \returns number of formats 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.
  * \since This function is available since SDL 3.0.0.
  *
  *
- * \sa SDL_GetCameraFormat
- * \sa SDL_SetCameraSpec
+ * \sa SDL_GetCameraDeviceName
+ * \sa SDL_GetCameraDevices
+ * \sa SDL_OpenCameraWithSpec
  */
  */
-extern DECLSPEC int SDLCALL SDL_GetNumCameraFormats(SDL_CameraDevice *device);
+extern DECLSPEC SDL_Camera *SDLCALL SDL_OpenCameraDevice(SDL_CameraDeviceID instance_id, const SDL_CameraSpec *spec);
 
 
 /**
 /**
- * Get frame sizes of the device and the specified input format.
+ * Get the instance ID of an opened camera.
  *
  *
- * The value can be used to fill SDL_CameraSpec structure.
+ * \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.
  *
  *
- * \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.
+ * \threadsafety It is safe to call this function from any thread.
  *
  *
  * \since This function is available since SDL 3.0.0.
  * \since This function is available since SDL 3.0.0.
  *
  *
- * \sa SDL_GetNumCameraFrameSizes
+ * \sa SDL_OpenCameraDevice
  */
  */
-extern DECLSPEC int SDLCALL SDL_GetCameraFrameSize(SDL_CameraDevice *device, Uint32 format, int index, int *width, int *height);
+extern DECLSPEC SDL_CameraDeviceID SDLCALL SDL_GetCameraInstanceID(SDL_Camera *camera);
 
 
 /**
 /**
- * Number of different framesizes available for the device and pixel format.
+ * Get the properties associated with an opened camera.
  *
  *
- * \param device opened camera device
- * \param format frame pixel format (SDL_PixelFormatEnum)
- * \returns number of framesizes or a negative error code on failure; call
+ * \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.
  *          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.
+ * \threadsafety It is safe to call this function from any thread.
  *
  *
  * \since This function is available since SDL 3.0.0.
  * \since This function is available since SDL 3.0.0.
  *
  *
- * \sa SDL_CameraStatus
+ * \sa SDL_GetProperty
+ * \sa SDL_SetProperty
  */
  */
-extern DECLSPEC SDL_CameraStatus SDLCALL SDL_GetCameraStatus(SDL_CameraDevice *device);
+extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetCameraProperties(SDL_Camera *camera);
 
 
 /**
 /**
- * Start camera
+ * Get the spec that a camera is using when generating images.
+ *
+ * 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 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
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
  *          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.
  * \since This function is available since SDL 3.0.0.
  *
  *
- * \sa SDL_StopCamera
+ * \sa SDL_OpenCameraDevice
  */
  */
-extern DECLSPEC int SDLCALL SDL_StartCamera(SDL_CameraDevice *device);
+extern DECLSPEC int SDLCALL SDL_GetCameraSpec(SDL_Camera *camera, SDL_CameraSpec *spec);
 
 
 /**
 /**
  * Acquire a frame.
  * Acquire a frame.
  *
  *
  * The frame is a memory pointer to the image data, whose size and format are
  * 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.
+ *
+ * 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.
+ *
+ * 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.
  *
  *
- * 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.
+ * 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.
  *
  *
- * After used, the frame should be released with SDL_ReleaseCameraFrame
+ * 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 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.
  * \since This function is available since SDL 3.0.0.
  *
  *
  * \sa SDL_ReleaseCameraFrame
  * \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.
  * 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.
  *
  *
- * \param device opened camera device
- * \param frame frame pointer.
- * \returns 0 on success or a negative error code on failure; call
- *          SDL_GetError() for more information.
+ * If the app needs to keep the surface for a significant time, they should
+ * make a copy of it and release the original.
  *
  *
- * \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
+ * 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 device opened camera device
+ * \param frame The video frame surface to release.
  * \returns 0 on success or a negative error code on failure; call
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
  *          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.
  * \since This function is available since SDL 3.0.0.
  *
  *
- * \sa SDL_StartCamera
+ * \sa SDL_AcquireCameraFrame
  */
  */
-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
  * 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
  * \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.
  * \since This function is available since SDL 3.0.0.
  *
  *
  * \sa SDL_OpenCameraWithSpec
  * \sa SDL_OpenCameraWithSpec
  * \sa SDL_OpenCamera
  * \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++ */
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 17 - 0
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_DOWN,            /**< Pressure-sensitive pen button pressed */
     SDL_EVENT_PEN_BUTTON_UP,              /**< Pressure-sensitive pen button released */
     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 */
     /* Render events */
     SDL_EVENT_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
     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 */
     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;
     Uint8 padding3;
 } SDL_AudioDeviceEvent;
 } 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.*)
  *  Touch finger event structure (event.tfinger.*)
@@ -699,6 +715,7 @@ typedef union SDL_Event
     SDL_GamepadTouchpadEvent gtouchpad;     /**< Gamepad touchpad event data */
     SDL_GamepadTouchpadEvent gtouchpad;     /**< Gamepad touchpad event data */
     SDL_GamepadSensorEvent gsensor;         /**< Gamepad sensor event data */
     SDL_GamepadSensorEvent gsensor;         /**< Gamepad sensor event data */
     SDL_AudioDeviceEvent adevice;           /**< Audio device event data */
     SDL_AudioDeviceEvent adevice;           /**< Audio device event data */
+    SDL_CameraDeviceEvent cdevice;          /**< Camera device event data */
     SDL_SensorEvent sensor;                 /**< Sensor event data */
     SDL_SensorEvent sensor;                 /**< Sensor event data */
     SDL_QuitEvent quit;                     /**< Quit request event data */
     SDL_QuitEvent quit;                     /**< Quit request event data */
     SDL_UserEvent user;                     /**< Custom event data */
     SDL_UserEvent user;                     /**< Custom event data */

File diff suppressed because it is too large
+ 709 - 399
src/camera/SDL_camera.c


+ 3 - 0
src/camera/SDL_camera_c.h

@@ -29,4 +29,7 @@ int SDL_CameraInit(const char *driver_name);
 // Shutdown the camera subsystem
 // Shutdown the camera subsystem
 void SDL_QuitCamera(void);
 void SDL_QuitCamera(void);
 
 
+// "Pump" the event queue.
+extern void SDL_UpdateCamera(void);
+
 #endif // SDL_camera_c_h_
 #endif // SDL_camera_c_h_

+ 103 - 29
src/camera/SDL_syscamera.h

@@ -23,67 +23,141 @@
 #ifndef SDL_syscamera_h_
 #ifndef SDL_syscamera_h_
 #define SDL_syscamera_h_
 #define SDL_syscamera_h_
 
 
-#include "../SDL_list.h"
+#include "../SDL_hashtable.h"
 
 
 #define DEBUG_CAMERA 1
 #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;
 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
 // Define the SDL camera driver structure
 struct SDL_CameraDevice
 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;
     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
     // Current state flags
     SDL_AtomicInt shutdown;
     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
     // A thread to feed the camera device
     SDL_Thread *thread;
     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;
     struct SDL_PrivateCameraData *hidden;
 };
 };
 
 
 typedef struct SDL_CameraDriverImpl
 typedef struct SDL_CameraDriverImpl
 {
 {
     void (*DetectDevices)(void);
     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);
     void (*Deinitialize)(void);
+
+    SDL_bool ProvidesOwnCallbackThread;
 } SDL_CameraDriverImpl;
 } SDL_CameraDriverImpl;
 
 
+typedef struct SDL_PendingCameraDeviceEvent
+{
+    Uint32 type;
+    SDL_CameraDeviceID devid;
+    struct SDL_PendingCameraDeviceEvent *next;
+} SDL_PendingCameraDeviceEvent;
+
 typedef struct SDL_CameraDriver
 typedef struct SDL_CameraDriver
 {
 {
     const char *name;  // The name of this camera driver
     const char *name;  // The name of this camera driver
     const char *desc;  // The description of this camera driver
     const char *desc;  // The description of this camera driver
     SDL_CameraDriverImpl impl; // the backend's interface
     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;
 } SDL_CameraDriver;
 
 
 typedef struct CameraBootStrap
 typedef struct CameraBootStrap

+ 9 - 6
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("yuvs", str) == 0)  return SDL_PIXELFORMAT_UYVY;
     if (SDL_strcmp("420f", str) == 0)  return SDL_PIXELFORMAT_UNKNOWN;
     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;
     return SDL_PIXELFORMAT_UNKNOWN;
 }
 }
@@ -177,8 +179,9 @@ static NSString *sdlformat_to_nsfourcc(Uint32 fmt)
     - (void)captureOutput:(AVCaptureOutput *)output
     - (void)captureOutput:(AVCaptureOutput *)output
         didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
         didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
         fromConnection:(AVCaptureConnection *)connection {
         fromConnection:(AVCaptureConnection *)connection {
-            // !!! FIXME #if DEBUG_CAMERA
-            SDL_Log("Drop frame..");
+            #if DEBUG_CAMERA
+            SDL_Log("CAMERA: Drop frame..");
+            #endif
         }
         }
 @end
 @end
 
 
@@ -362,13 +365,13 @@ static int COREMEDIA_AcquireFrame(SDL_CameraDevice *_this, SDL_CameraFrame *fram
         const int numPlanes = CVPixelBufferGetPlaneCount(image);
         const int numPlanes = CVPixelBufferGetPlaneCount(image);
         const int planar = CVPixelBufferIsPlanar(image);
         const int planar = CVPixelBufferIsPlanar(image);
 
 
-#if 0
+        #if DEBUG_CAMERA
         const int w = CVPixelBufferGetWidth(image);
         const int w = CVPixelBufferGetWidth(image);
         const int h = CVPixelBufferGetHeight(image);
         const int h = CVPixelBufferGetHeight(image);
         const int sz = CVPixelBufferGetDataSize(image);
         const int sz = CVPixelBufferGetDataSize(image);
         const int pitch = CVPixelBufferGetBytesPerRow(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);
         CVPixelBufferLockBaseAddress(image, 0);
 
 

+ 80 - 0
src/camera/dummy/SDL_camera_dummy.c

@@ -0,0 +1,80 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <[email protected]>
+
+  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

+ 434 - 736
src/camera/v4l2/SDL_camera_v4l2.c

@@ -22,43 +22,38 @@
 
 
 #ifdef SDL_CAMERA_DRIVER_V4L2
 #ifdef SDL_CAMERA_DRIVER_V4L2
 
 
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>              // low-level i/o
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <linux/videodev2.h>
+
 #include "../SDL_syscamera.h"
 #include "../SDL_syscamera.h"
 #include "../SDL_camera_c.h"
 #include "../SDL_camera_c.h"
 #include "../../video/SDL_pixels_c.h"
 #include "../../video/SDL_pixels_c.h"
 #include "../../thread/SDL_systhread.h"
 #include "../../thread/SDL_systhread.h"
 #include "../../core/linux/SDL_evdev_capabilities.h"
 #include "../../core/linux/SDL_evdev_capabilities.h"
 #include "../../core/linux/SDL_udev.h"
 #include "../../core/linux/SDL_udev.h"
-#include <limits.h>      // INT_MAX
-
-#define MAX_CAMERA_DEVICES 128 // It's doubtful someone has more than that
 
 
-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
+#ifndef SDL_USE_LIBUDEV
+#include <dirent.h>
+#endif
 
 
-// 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;
+    char *bus_info;
+    char *path;
+} V4L2DeviceHandle;
 
 
-static SDL_cameralist_item *SDL_cameralist = NULL;
-static SDL_cameralist_item *SDL_cameralist_tail = NULL;
-static int num_cameras = 0;
 
 
-
-
-enum io_method {
+typedef enum io_method {
+    IO_METHOD_INVALID,
     IO_METHOD_READ,
     IO_METHOD_READ,
     IO_METHOD_MMAP,
     IO_METHOD_MMAP,
     IO_METHOD_USERPTR
     IO_METHOD_USERPTR
-};
+} io_method;
 
 
 struct buffer {
 struct buffer {
     void   *start;
     void   *start;
@@ -69,21 +64,13 @@ struct buffer {
 struct SDL_PrivateCameraData
 struct SDL_PrivateCameraData
 {
 {
     int fd;
     int fd;
-    enum io_method io;
+    io_method io;
     int nb_buffers;
     int nb_buffers;
     struct buffer *buffers;
     struct buffer *buffers;
     int first_start;
     int first_start;
     int driver_pitch;
     int driver_pitch;
 };
 };
 
 
-#include <unistd.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>              // low-level i/o
-#include <errno.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <linux/videodev2.h>
-
 static int xioctl(int fh, int request, void *arg)
 static int xioctl(int fh, int request, void *arg)
 {
 {
     int r;
     int r;
@@ -95,17 +82,40 @@ static int xioctl(int fh, int request, void *arg)
     return r;
     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;
     struct v4l2_buffer buf;
 
 
     switch (io) {
     switch (io) {
         case IO_METHOD_READ:
         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) {
                 switch (errno) {
                     case EAGAIN:
                     case EAGAIN:
                         return 0;
                         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;
             break;
 
 
         case IO_METHOD_MMAP:
         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");
                 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;
             break;
 
 
         case IO_METHOD_USERPTR:
         case IO_METHOD_USERPTR:
@@ -180,45 +188,49 @@ static int acquire_frame(SDL_CameraDevice *_this, SDL_CameraFrame *frame)
             }
             }
 
 
             int i;
             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;
                     break;
                 }
                 }
             }
             }
 
 
-            if (i >= _this->hidden->nb_buffers) {
+            if (i >= device->hidden->nb_buffers) {
                 return SDL_SetError("invalid buffer index");
                 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;
             break;
     }
     }
 
 
+    *timestampNS = SDL_GetTicksNS();  // !!! FIXME: can we get this info more accurately from v4l2?
     return 1;
     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;
     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;
     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;
             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) {
     switch (io) {
@@ -233,9 +245,10 @@ static int V4L2_ReleaseFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame)
             buf.index = i;
             buf.index = i;
 
 
             if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
             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;
             break;
 
 
         case IO_METHOD_USERPTR:
         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.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
             buf.memory = V4L2_MEMORY_USERPTR;
             buf.memory = V4L2_MEMORY_USERPTR;
             buf.index = i;
             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) {
             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;
-            break;
-    }
-
-    return 0;
-}
-
-static int V4L2_AcquireFrame(SDL_CameraDevice *_this, SDL_CameraFrame *frame)
-{
-    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;
-
-    switch (io) {
-        case IO_METHOD_READ:
+            device->hidden->buffers[i].available = 0;
             break;
             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");
-            }
+        case IO_METHOD_INVALID:
+            SDL_assert(!"Shouldn't have hit this");
             break;
             break;
     }
     }
-
-    return 0;
 }
 }
 
 
-static int EnqueueBuffers(SDL_CameraDevice *_this)
+static int EnqueueBuffers(SDL_CameraDevice *device)
 {
 {
-    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) {
     switch (io) {
         case IO_METHOD_READ:
         case IO_METHOD_READ:
             break;
             break;
 
 
         case IO_METHOD_MMAP:
         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;
                     struct v4l2_buffer buf;
 
 
                     SDL_zero(buf);
                     SDL_zero(buf);
@@ -355,16 +299,16 @@ static int EnqueueBuffers(SDL_CameraDevice *_this)
             break;
             break;
 
 
         case IO_METHOD_USERPTR:
         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;
                     struct v4l2_buffer buf;
 
 
                     SDL_zero(buf);
                     SDL_zero(buf);
                     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                     buf.memory = V4L2_MEMORY_USERPTR;
                     buf.memory = V4L2_MEMORY_USERPTR;
                     buf.index = i;
                     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) {
                     if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
                         return SDL_SetError("VIDIOC_QBUF");
                         return SDL_SetError("VIDIOC_QBUF");
@@ -372,115 +316,24 @@ static int EnqueueBuffers(SDL_CameraDevice *_this)
                 }
                 }
             }
             }
             break;
             break;
-    }
-    return 0;
-}
-
-static int PreEnqueueBuffers(SDL_CameraDevice *_this)
-{
-    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;
+        case IO_METHOD_INVALID: SDL_assert(!"Shouldn't have hit this"); break;
     }
     }
     return 0;
     return 0;
 }
 }
 
 
-static int V4L2_StartCamera(SDL_CameraDevice *_this)
+static int AllocBufferRead(SDL_CameraDevice *device, size_t buffer_size)
 {
 {
-    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;
+    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 AllocBufferMmap(SDL_CameraDevice *_this)
+static int AllocBufferMmap(SDL_CameraDevice *device)
 {
 {
-    int fd = _this->hidden->fd;
+    const int fd = device->hidden->fd;
     int i;
     int i;
-    for (i = 0; i < _this->hidden->nb_buffers; ++i) {
+    for (i = 0; i < device->hidden->nb_buffers; ++i) {
         struct v4l2_buffer buf;
         struct v4l2_buffer buf;
 
 
         SDL_zero(buf);
         SDL_zero(buf);
@@ -493,29 +346,29 @@ static int AllocBufferMmap(SDL_CameraDevice *_this)
             return SDL_SetError("VIDIOC_QUERYBUF");
             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 */,
             mmap(NULL /* start anywhere */,
                     buf.length,
                     buf.length,
                     PROT_READ | PROT_WRITE /* required */,
                     PROT_READ | PROT_WRITE /* required */,
                     MAP_SHARED /* recommended */,
                     MAP_SHARED /* recommended */,
                     fd, buf.m.offset);
                     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 SDL_SetError("mmap");
         }
         }
     }
     }
     return 0;
     return 0;
 }
 }
 
 
-static int AllocBufferUserPtr(SDL_CameraDevice *_this, size_t buffer_size)
+static int AllocBufferUserPtr(SDL_CameraDevice *device, size_t buffer_size)
 {
 {
     int i;
     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;
             return -1;
         }
         }
     }
     }
@@ -530,7 +383,9 @@ static Uint32 format_v4l2_to_sdl(Uint32 fmt)
         CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN);
         CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN);
         #undef CASE
         #undef CASE
         default:
         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;
             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;
+    if (!device) {
+        return;
     }
     }
 
 
-    return -1;
-}
+    if (device->hidden) {
+        const io_method io = device->hidden->io;
+        const int fd = device->hidden->fd;
 
 
-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");
+        if ((io == IO_METHOD_MMAP) || (io == IO_METHOD_USERPTR)) {
+            enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+            xioctl(fd, VIDIOC_STREAMOFF, &type);
         }
         }
-    }
-    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.
+        if (device->hidden->buffers) {
+            switch (io) {
+                case IO_METHOD_INVALID:
                     break;
                     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) {
-        return;
-    }
-
-    if (_this->hidden) {
-        if (_this->hidden->buffers) {
-            enum io_method io = _this->hidden->io;
 
 
-            switch (io) {
                 case IO_METHOD_READ:
                 case IO_METHOD_READ:
-                    SDL_free(_this->hidden->buffers[0].start);
+                    SDL_free(device->hidden->buffers[0].start);
                     break;
                     break;
 
 
                 case IO_METHOD_MMAP:
                 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");
                             SDL_SetError("munmap");
                         }
                         }
                     }
                     }
                     break;
                     break;
 
 
                 case IO_METHOD_USERPTR:
                 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;
                     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 stat st;
     struct v4l2_capability cap;
     struct v4l2_capability cap;
-    enum io_method io;
-    int fd;
+    const int fd = open(handle->path, O_RDWR /* required */ | O_NONBLOCK, 0);
+
+    // 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);
+    }
 
 
-    _this->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
-    if (_this->hidden == NULL) {
+    device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
+    if (device->hidden == NULL) {
+        close(fd);
         return -1;
         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,
+    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 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");
     }
     }
-    io = _this->hidden->io;
 
 
-    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);
+    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);
+
+    device->hidden->io = io;
+
+    device->hidden->buffers = SDL_calloc(device->hidden->nb_buffers, sizeof(*device->hidden->buffers));
+    if (!device->hidden->buffers) {
+        return -1;
     }
     }
-#endif
 
 
+    size_t size, pitch;
+    SDL_CalculateSize(device->spec.format, device->spec.width, device->spec.height, &size, &pitch, SDL_FALSE);
+
+    int rc = 0;
     switch (io) {
     switch (io) {
         case IO_METHOD_READ:
         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;
             break;
 
 
         case IO_METHOD_MMAP:
         case IO_METHOD_MMAP:
+            rc = AllocBufferMmap(device);
+            break;
+
         case IO_METHOD_USERPTR:
         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;
             break;
-    }
 
 
-    return 0;
-}
+        case IO_METHOD_INVALID:
+            SDL_assert(!"Shouldn't have hit this");
+            break;
+    }
 
 
-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);
 }
 }
 
 
+typedef struct SpecAddData
+{
+    SDL_CameraSpec *specs;
+    int num_specs;
+    int allocated_specs;
+} SpecAddData;
 
 
-#ifdef SDL_USE_LIBUDEV
-static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
+static int AddCameraSpec(SpecAddData *data, Uint32 fmt, int w, int h)
 {
 {
-    if (!devpath || !(udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) {
-        return;
+    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;
     }
     }
 
 
-    switch (udev_type) {
-    case SDL_UDEV_DEVICEADDED:
-        MaybeAddDevice(devpath);
-        break;
-
-    case SDL_UDEV_DEVICEREMOVED:
-        MaybeRemoveDevice(devpath);
-        break;
+    SDL_CameraSpec *spec = &data->specs[data->num_specs];
+    spec->format = fmt;
+    spec->width = w;
+    spec->height = h;
 
 
-    default:
-        break;
-    }
-}
-#endif // SDL_USE_LIBUDEV
+    data->num_specs++;
 
 
-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;
+    return 0;
 }
 }
 
 
-static int MaybeAddDevice(const char *path)
+static void MaybeAddDevice(const char *path)
 {
 {
-    char *bus_info = NULL;
-    struct v4l2_capability vcap;
-    int err;
-    int fd;
-    SDL_cameralist_item *item;
-
     if (!path) {
     if (!path) {
-        return -1;
+        return;
     }
     }
 
 
-    fd = open(path, O_RDWR);
-    if (fd < 0) {
-        return -2; // stop iterating /dev/video%d
+    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.
     }
     }
-    err = ioctl(fd, VIDIOC_QUERYCAP, &vcap);
-    close(fd);
-    if (err) {
-        return -1;
+
+    struct v4l2_capability vcap;
+    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.
     }
     }
 
 
-    bus_info = SDL_strdup((char *)vcap.bus_info);
+    #if DEBUG_CAMERA
+    SDL_Log("CAMERA: V4L2 camera path='%s' bus_info='%s' name='%s'", path, (const char *) vcap.bus_info, vcap.card);
+    #endif
 
 
-    if (DeviceExists(path, bus_info)) {
-        SDL_free(bus_info);
-        return 0;
-    }
+    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);
 
 
-    // Add new item
-    item = (SDL_cameralist_item *)SDL_calloc(1, sizeof(SDL_cameralist_item));
-    if (!item) {
-        SDL_free(bus_info);
-        return -1;
-    }
+        #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
 
 
-    item->fname = SDL_strdup(path);
-    if (!item->fname) {
-        SDL_free(item);
-        SDL_free(bus_info);
-        return -1;
-    }
+        fmtdesc.index++;  // prepare for next iteration.
 
 
-    item->fname = SDL_strdup(path);
-    item->bus_info = bus_info;
-    item->instance_id = SDL_GetNextObjectID();
+        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.
+                        }
 
 
-    if (!SDL_cameralist_tail) {
-        SDL_cameralist = SDL_cameralist_tail = item;
-    } else {
-        SDL_cameralist_tail->next = item;
-        SDL_cameralist_tail = item;
+                    }
+                }
+                break;
+            }
+        }
     }
     }
 
 
-    ++num_cameras;
+    close(fd);
 
 
-    // !!! 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;
+    #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);
 }
 }
 
 
-#ifdef SDL_USE_LIBUDEV
-static int MaybeRemoveDevice(const char *path)
+static void V4L2_FreeDeviceHandle(SDL_CameraDevice *device)
 {
 {
-
-    SDL_cameralist_item *item;
-    SDL_cameralist_item *prev = NULL;
-#if DEBUG_CAMERA
-    SDL_Log("Remove video camera %s", path);
-#endif
-    if (!path) {
-        return -1;
+    if (device) {
+        V4L2DeviceHandle *handle = (V4L2DeviceHandle *) device->handle;
+        SDL_free(handle->path);
+        SDL_free(handle->bus_info);
+        SDL_free(handle);
     }
     }
+}
 
 
-    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;
-            }
+#ifdef SDL_USE_LIBUDEV
+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);
+}
 
 
-            // Need to decrement the count
-            --num_cameras;
-            // !!! TODO: Send a remove event?
+static void MaybeRemoveDevice(const char *path)
+{
+    if (path) {
+        SDL_CameraDeviceDisconnected(SDL_FindPhysicalCameraDeviceByCallback(FindV4L2CameraDeviceByPathCallback, (void *) path));
+    }
+}
 
 
-            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
 #endif // SDL_USE_LIBUDEV
 
 
-
 static void V4L2_Deinitialize(void)
 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)
 static void V4L2_DetectDevices(void)
 {
 {
-}
-
-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) {
+        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
         }
         }
     }
     }
-
-#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");
+#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);
     }
     }
-
-    // Force a scan to build the initial device list
-    SDL_UDEV_Scan();
 #endif // SDL_USE_LIBUDEV
 #endif // SDL_USE_LIBUDEV
+}
 
 
+static SDL_bool V4L2_Init(SDL_CameraDriverImpl *impl)
+{
     impl->DetectDevices = V4L2_DetectDevices;
     impl->DetectDevices = V4L2_DetectDevices;
     impl->OpenDevice = V4L2_OpenDevice;
     impl->OpenDevice = V4L2_OpenDevice;
     impl->CloseDevice = V4L2_CloseDevice;
     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->AcquireFrame = V4L2_AcquireFrame;
     impl->ReleaseFrame = V4L2_ReleaseFrame;
     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;
     impl->Deinitialize = V4L2_Deinitialize;
 
 
     return SDL_TRUE;
     return SDL_TRUE;

+ 7 - 10
src/dynapi/SDL_dynapi.sym

@@ -957,21 +957,18 @@ SDL3_0.0.0 {
     SDL_SetWindowShape;
     SDL_SetWindowShape;
     SDL_RenderViewportSet;
     SDL_RenderViewportSet;
     SDL_HasProperty;
     SDL_HasProperty;
+    SDL_GetNumCameraDrivers;
+    SDL_GetCameraDriver;
+    SDL_GetCurrentCameraDriver;
     SDL_GetCameraDevices;
     SDL_GetCameraDevices;
-    SDL_OpenCamera;
-    SDL_SetCameraSpec;
-    SDL_OpenCameraWithSpec;
+    SDL_GetCameraDeviceSupportedSpecs;
     SDL_GetCameraDeviceName;
     SDL_GetCameraDeviceName;
+    SDL_OpenCameraDevice;
+    SDL_GetCameraInstanceID;
+    SDL_GetCameraProperties;
     SDL_GetCameraSpec;
     SDL_GetCameraSpec;
-    SDL_GetCameraFormat;
-    SDL_GetNumCameraFormats;
-    SDL_GetCameraFrameSize;
-    SDL_GetNumCameraFrameSizes;
-    SDL_GetCameraStatus;
-    SDL_StartCamera;
     SDL_AcquireCameraFrame;
     SDL_AcquireCameraFrame;
     SDL_ReleaseCameraFrame;
     SDL_ReleaseCameraFrame;
-    SDL_StopCamera;
     SDL_CloseCamera;
     SDL_CloseCamera;
     # extra symbols go here (don't modify this line)
     # extra symbols go here (don't modify this line)
   local: *;
   local: *;

+ 7 - 10
src/dynapi/SDL_dynapi_overrides.h

@@ -982,19 +982,16 @@
 #define SDL_SetWindowShape SDL_SetWindowShape_REAL
 #define SDL_SetWindowShape SDL_SetWindowShape_REAL
 #define SDL_RenderViewportSet SDL_RenderViewportSet_REAL
 #define SDL_RenderViewportSet SDL_RenderViewportSet_REAL
 #define SDL_HasProperty SDL_HasProperty_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_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_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_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_AcquireCameraFrame SDL_AcquireCameraFrame_REAL
 #define SDL_ReleaseCameraFrame SDL_ReleaseCameraFrame_REAL
 #define SDL_ReleaseCameraFrame SDL_ReleaseCameraFrame_REAL
-#define SDL_StopCamera SDL_StopCamera_REAL
 #define SDL_CloseCamera SDL_CloseCamera_REAL
 #define SDL_CloseCamera SDL_CloseCamera_REAL

+ 12 - 15
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(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_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(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_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),)

+ 14 - 0
src/events/SDL_events.c

@@ -25,6 +25,7 @@
 #include "SDL_events_c.h"
 #include "SDL_events_c.h"
 #include "../SDL_hints_c.h"
 #include "../SDL_hints_c.h"
 #include "../audio/SDL_audio_c.h"
 #include "../audio/SDL_audio_c.h"
+#include "../camera/SDL_camera_c.h"
 #include "../timer/SDL_timer_c.h"
 #include "../timer/SDL_timer_c.h"
 #ifndef SDL_JOYSTICK_DISABLED
 #ifndef SDL_JOYSTICK_DISABLED
 #include "../joystick/SDL_joystick_c.h"
 #include "../joystick/SDL_joystick_c.h"
@@ -554,6 +555,15 @@ static void SDL_LogEvent(const SDL_Event *event)
         break;
         break;
 #undef PRINT_AUDIODEV_EVENT
 #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)
         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)",
         (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,
                            (uint)event->sensor.timestamp, (int)event->sensor.which,
@@ -942,6 +952,10 @@ static void SDL_PumpEventsInternal(SDL_bool push_sentinel)
     SDL_UpdateAudio();
     SDL_UpdateAudio();
 #endif
 #endif
 
 
+#ifndef SDL_CAMERA_DISABLED
+    SDL_UpdateCamera();
+#endif
+
 #ifndef SDL_SENSOR_DISABLED
 #ifndef SDL_SENSOR_DISABLED
     /* Check for sensor state change */
     /* Check for sensor state change */
     if (SDL_update_sensors) {
     if (SDL_update_sensors) {

+ 8 - 2
test/testcamera.c

@@ -18,8 +18,13 @@
 #include <emscripten/emscripten.h>
 #include <emscripten/emscripten.h>
 #endif
 #endif
 
 
-#include <stdio.h>
-
+#if 1
+int main(int argc, char **argv)
+{
+    SDL_Log("FIXME: update me");
+    return 0;
+}
+#else
 static const char *usage = "\
 static const char *usage = "\
  \n\
  \n\
  =========================================================================\n\
  =========================================================================\n\
@@ -769,3 +774,4 @@ int main(int argc, char **argv)
 
 
     return 0;
     return 0;
 }
 }
+#endif

+ 46 - 48
test/testcameraminimal.c

@@ -28,21 +28,14 @@ int main(int argc, char **argv)
     int quit = 0;
     int quit = 0;
     SDLTest_CommonState  *state = NULL;
     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;
     SDL_Texture *texture = NULL;
     int texture_updated = 0;
     int texture_updated = 0;
+    SDL_Surface *frame_current = NULL;
 
 
     SDL_zero(evt);
     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 */
     /* Initialize test framework */
     state = SDLTest_CommonCreateState(argv, 0);
     state = SDLTest_CommonCreateState(argv, 0);
@@ -73,20 +66,42 @@ int main(int argc, char **argv)
         return 1;
         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;
+    }
+
+    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;
         return 1;
     }
     }
 
 
-    if (SDL_StartCamera(device) < 0) {
-        SDL_Log("error SDL_StartCamera(): %s", SDL_GetError());
+   if (SDL_GetCameraSpec(camera, &spec) < 0) {
+        SDL_Log("Couldn't get camera spec: %s", SDL_GetError());
         return 1;
         return 1;
     }
     }
 
 
     /* Create texture with appropriate format */
     /* Create texture with appropriate format */
     if (texture == NULL) {
     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) {
         if (texture == NULL) {
             SDL_Log("Couldn't create texture: %s", SDL_GetError());
             SDL_Log("Couldn't create texture: %s", SDL_GetError());
             return 1;
             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, &timestampNS);
 
 
-            if (SDL_AcquireCameraFrame(device, &frame_next) < 0) {
-                SDL_Log("err SDL_AcquireCameraFrame: %s", SDL_GetError());
-            }
 #if 0
 #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
 #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());
                         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) */
         /* 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;
             texture_updated = 1;
         }
         }
 
 
@@ -177,7 +177,7 @@ int main(int argc, char **argv)
                 th = (int)((float) th * scale);
                 th = (int)((float) th * scale);
             }
             }
             d.x = (float)(10 );
             d.x = (float)(10 );
-            d.y = (float)(win_h - th);
+            d.y = ((float)(win_h - th)) / 2.0f;
             d.w = (float)tw;
             d.w = (float)tw;
             d.h = (float)(th - 10);
             d.h = (float)(th - 10);
             SDL_RenderTexture(renderer, texture, NULL, &d);
             SDL_RenderTexture(renderer, texture, NULL, &d);
@@ -186,13 +186,10 @@ int main(int argc, char **argv)
         SDL_RenderPresent(renderer);
         SDL_RenderPresent(renderer);
     }
     }
 
 
-    if (SDL_StopCamera(device) < 0) {
-        SDL_Log("error SDL_StopCamera(): %s", SDL_GetError());
-    }
-    if (frame_current.num_planes) {
-        SDL_ReleaseCameraFrame(device, &frame_current);
+    if (frame_current) {
+        SDL_ReleaseCameraFrame(camera, frame_current);
     }
     }
-    SDL_CloseCamera(device);
+    SDL_CloseCamera(camera);
 
 
     if (texture) {
     if (texture) {
         SDL_DestroyTexture(texture);
         SDL_DestroyTexture(texture);
@@ -205,3 +202,4 @@ int main(int argc, char **argv)
 
 
     return 0;
     return 0;
 }
 }
+

Some files were not shown because too many files changed in this diff