Browse Source

SDL_VideoCapture: allow add/remove device at runtime on linux

Sylvain 1 năm trước cách đây
mục cha
commit
6bb40f1d8d

+ 1 - 0
src/core/linux/SDL_evdev_capabilities.h

@@ -53,6 +53,7 @@ typedef enum
     SDL_UDEV_DEVICE_ACCELEROMETER = 0x0020,
     SDL_UDEV_DEVICE_TOUCHPAD = 0x0040,
     SDL_UDEV_DEVICE_HAS_KEYS = 0x0080,
+    SDL_UDEV_DEVICE_VIDEO_CAPTURE = 0x0100,
 } SDL_UDEV_deviceclass;
 
 #define BITS_PER_LONG        (sizeof(unsigned long) * 8)

+ 10 - 0
src/core/linux/SDL_udev.c

@@ -139,6 +139,7 @@ int SDL_UDEV_Init(void)
 
         _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL);
         _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL);
+        _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "video4linux", NULL);
         _this->syms.udev_monitor_enable_receiving(_this->udev_mon);
 
         /* Do an initial scan of existing devices */
@@ -200,6 +201,7 @@ int SDL_UDEV_Scan(void)
 
     _this->syms.udev_enumerate_add_match_subsystem(enumerate, "input");
     _this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound");
+    _this->syms.udev_enumerate_add_match_subsystem(enumerate, "video4linux");
 
     _this->syms.udev_enumerate_scan_devices(enumerate);
     devs = _this->syms.udev_enumerate_get_list_entry(enumerate);
@@ -405,8 +407,16 @@ static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
     }
 
     subsystem = _this->syms.udev_device_get_subsystem(dev);
+
     if (SDL_strcmp(subsystem, "sound") == 0) {
         devclass = SDL_UDEV_DEVICE_SOUND;
+    } else if (SDL_strcmp(subsystem, "video4linux") == 0) {
+        devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;
+
+        val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES");
+        if (!val || !SDL_strcasestr(val, "capture")) {
+            return;
+        }
     } else if (SDL_strcmp(subsystem, "input") == 0) {
         /* udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c */
 

+ 5 - 3
src/video/SDL_sysvideocapture.h

@@ -61,6 +61,9 @@ struct SDL_VideoCaptureDevice
     struct SDL_PrivateVideoCaptureData *hidden;
 };
 
+extern int SDL_SYS_VideoCaptureInit(void);
+extern int SDL_SYS_VideoCaptureQuit(void);
+
 extern int OpenDevice(SDL_VideoCaptureDevice *_this);
 extern void CloseDevice(SDL_VideoCaptureDevice *_this);
 
@@ -80,9 +83,8 @@ extern int GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format);
 extern int GetNumFrameSizes(SDL_VideoCaptureDevice *_this, Uint32 format);
 extern int GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height);
 
-extern int GetDeviceName(int index, char *buf, int size);
-extern int GetNumDevices(void);
-
+extern int GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size);
+extern SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count);
 
 extern SDL_bool check_all_device_closed(void);
 extern SDL_bool check_device_playing(void);

+ 35 - 15
src/video/SDL_video_capture.c

@@ -311,7 +311,6 @@ const char *
 SDL_GetVideoCaptureDeviceName(SDL_VideoCaptureDeviceID instance_id)
 {
 #ifdef SDL_VIDEO_CAPTURE
-    int index = instance_id - 1;
     static char buf[256];
     buf[0] = 0;
     buf[255] = 0;
@@ -321,7 +320,7 @@ SDL_GetVideoCaptureDeviceName(SDL_VideoCaptureDeviceID instance_id)
         return NULL;
     }
 
-    if (GetDeviceName(index, buf, sizeof (buf)) < 0) {
+    if (GetDeviceName(instance_id, buf, sizeof (buf)) < 0) {
         buf[0] = 0;
     }
     return buf;
@@ -336,14 +335,21 @@ SDL_VideoCaptureDeviceID *
 SDL_GetVideoCaptureDevices(int *count)
 {
 
-    int i;
-#ifdef SDL_VIDEO_CAPTURE
-    int num = GetNumDevices();
-#else
     int num = 0;
+    SDL_VideoCaptureDeviceID *ret = NULL;
+#ifdef SDL_VIDEO_CAPTURE
+    ret = GetVideoCaptureDevices(&num);
 #endif
-    SDL_VideoCaptureDeviceID *ret;
 
+    if (ret) {
+        if (count) {
+            *count = num;
+        }
+        return ret;
+    }
+
+    /* return list of 0 ID, null terminated */
+    num = 0;
     ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret));
 
     if (ret == NULL) {
@@ -354,11 +360,7 @@ SDL_GetVideoCaptureDevices(int *count)
         return NULL;
     }
 
-    for (i = 0; i < num; i++) {
-        ret[i] = i + 1;
-    }
     ret[num] = 0;
-
     if (count) {
         *count = num;
     }
@@ -501,6 +503,8 @@ SDL_OpenVideoCapture(SDL_VideoCaptureDeviceID instance_id)
         }
     }
 
+#if 0
+    // FIXME do we need this ?
     /* Let the user override. */
     {
         const char *dev = SDL_getenv("SDL_VIDEO_CAPTURE_DEVICE_NAME");
@@ -508,6 +512,7 @@ SDL_OpenVideoCapture(SDL_VideoCaptureDeviceID instance_id)
             device_name = dev;
         }
     }
+#endif
 
     if (device_name == NULL) {
         goto error;
@@ -823,6 +828,8 @@ SDL_VideoCaptureInit(void)
 {
 #ifdef SDL_VIDEO_CAPTURE
     SDL_zeroa(open_devices);
+
+    SDL_SYS_VideoCaptureInit();
     return 0;
 #else
     return 0;
@@ -839,6 +846,8 @@ SDL_QuitVideoCapture(void)
     }
 
     SDL_zeroa(open_devices);
+
+    SDL_SYS_VideoCaptureQuit();
 #endif
 }
 
@@ -857,6 +866,16 @@ SDL_QuitVideoCapture(void)
 /* See SDL_video_capture_apple.m */
 #else
 
+int SDL_SYS_VideoCaptureInit(void)
+{
+    return 0;
+}
+
+int SDL_SYS_VideoCaptureQuit(void)
+{
+    return 0;
+}
+
 int
 OpenDevice(SDL_VideoCaptureDevice *_this)
 {
@@ -933,16 +952,17 @@ GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width
 }
 
 int
-GetDeviceName(int index, char *buf, int size)
+GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size)
 {
     return -1;
 }
 
-int
-GetNumDevices(void)
+SDL_VideoCaptureDeviceID *
+GetVideoCaptureDevices(int *count)
 {
-    return -1;
+    return NULL;
 }
+
 #endif
 
 #endif /* SDL_VIDEO_CAPTURE */

+ 49 - 5
src/video/SDL_video_capture_apple.m

@@ -49,7 +49,7 @@ int AcquireFrame(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureFrame *frame) {
 }
 void CloseDevice(SDL_VideoCaptureDevice *_this) {
 }
-int GetDeviceName(int index, char *buf, int size) {
+int GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size) {
     return -1;
 }
 int GetDeviceSpec(SDL_VideoCaptureDevice *_this, SDL_VideoCaptureSpec *spec) {
@@ -61,8 +61,8 @@ int GetFormat(SDL_VideoCaptureDevice *_this, int index, Uint32 *format) {
 int GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width, int *height) {
     return -1;
 }
-int GetNumDevices(void) {
-    return 0;
+SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count) {
+    return NULL;
 }
 int GetNumFormats(SDL_VideoCaptureDevice *_this) {
     return 0;
@@ -79,6 +79,13 @@ int StartCapture(SDL_VideoCaptureDevice *_this) {
 int StopCapture(SDL_VideoCaptureDevice *_this) {
     return 0;
 }
+int SDL_SYS_VideoCaptureInit(void) {
+    return 0;
+}
+int SDL_SYS_VideoCaptureQuit(void) {
+    return 0;
+}
+
 
 #else
 
@@ -589,8 +596,9 @@ GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width
 }
 
 int
-GetDeviceName(int index, char *buf, int size)
+GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size)
 {
+    int index = instance_id - 1;
     NSArray<AVCaptureDevice *> *devices = discover_devices();
     if (index < [devices count]) {
         AVCaptureDevice *device = devices[index];
@@ -602,13 +610,49 @@ GetDeviceName(int index, char *buf, int size)
     return -1;
 }
 
-int
+static int
 GetNumDevices(void)
 {
     NSArray<AVCaptureDevice *> *devices = discover_devices();
     return [devices count];
 }
 
+SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count)
+{
+    /* hard-coded list of ID */
+    int i;
+    int num = GetNumDevices();
+    SDL_VideoCaptureDeviceID *ret;
+
+    ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret));
+
+    if (ret == NULL) {
+        SDL_OutOfMemory();
+        *count = 0;
+        return NULL;
+    }
+
+    for (i = 0; i < num; i++) {
+        ret[i] = i + 1;
+    }
+    ret[num] = 0;
+    *count = num;
+    return ret;
+}
+
+int SDL_SYS_VideoCaptureInit(void)
+{
+    return 0;
+}
+
+int SDL_SYS_VideoCaptureQuit(void)
+{
+    return 0;
+}
+
+
+
+
 #endif /* HAVE_COREMEDIA */
 
 #endif /* SDL_VIDEO_CAPTURE */

+ 268 - 18
src/video/SDL_video_capture_v4l2.c

@@ -28,11 +28,41 @@
 #include "SDL_video_capture_c.h"
 #include "SDL_pixels_c.h"
 #include "../thread/SDL_systhread.h"
+#include "../../core/linux/SDL_evdev_capabilities.h"
+#include "../../core/linux/SDL_udev.h"
+#include <limits.h>      /* INT_MAX */
 
 #define DEBUG_VIDEO_CAPTURE_CAPTURE 1
 
 #if defined(__linux__) && !defined(__ANDROID__)
 
+
+#define MAX_CAPTURE_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 capture_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath);
+#endif /* SDL_USE_LIBUDEV */
+
+/*
+ * List of available capture devices.
+ */
+typedef struct SDL_capturelist_item
+{
+    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_VideoCaptureDeviceID instance_id;
+    SDL_VideoCaptureDevice *device; /* Associated device */
+    struct SDL_capturelist_item *next;
+} SDL_capturelist_item;
+
+static SDL_capturelist_item *SDL_capturelist = NULL;
+static SDL_capturelist_item *SDL_capturelist_tail = NULL;
+static int num_video_captures = 0;
+
+
+
 enum io_method {
     IO_METHOD_READ,
     IO_METHOD_MMAP,
@@ -933,32 +963,252 @@ OpenDevice(SDL_VideoCaptureDevice *_this)
     return 0;
 }
 
+int
+GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size)
+{
+    SDL_capturelist_item *item;
+    for (item = SDL_capturelist; item; item = item->next) {
+        if (item->instance_id == instance_id) {
+            SDL_snprintf(buf, size, "%s", item->fname);
+            return 0;
+        }
+    }
+
+    /* unknown instance_id */
+    return -1;
+}
+
 
+SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count)
+{
+    /* real list of ID */
+    int i = 0;
+    int num = num_video_captures;
+    SDL_VideoCaptureDeviceID *ret;
+    SDL_capturelist_item *item;
 
-int
-GetDeviceName(int index, char *buf, int size) {
-    SDL_snprintf(buf, size, "/dev/video%d", index);
+    ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret));
+
+    if (ret == NULL) {
+        SDL_OutOfMemory();
+        *count = 0;
+        return NULL;
+    }
+
+    for (item = SDL_capturelist; item; item = item->next) {
+        ret[i] = item->instance_id;
+        i++;
+    }
+
+    ret[num] = 0;
+    *count = num;
+    return ret;
+}
+
+
+/*
+ * Initializes the subsystem by finding available devices.
+ */
+int SDL_SYS_VideoCaptureInit(void)
+{
+    const char pattern[] = "/dev/video%d";
+    char path[PATH_MAX];
+    int i, j;
+
+    /*
+     * Limit amount of checks to MAX_CAPTURE_DEVICES since we may or may not have
+     * permission to some or all devices.
+     */
+    i = 0;
+    for (j = 0; j < MAX_CAPTURE_DEVICES; ++j) {
+        (void)SDL_snprintf(path, PATH_MAX, pattern, i++);
+        if (MaybeAddDevice(path) == -2) {
+            break;
+        }
+    }
+
+#ifdef SDL_USE_LIBUDEV
+    if (SDL_UDEV_Init() < 0) {
+        return SDL_SetError("Could not initialize UDEV");
+    }
+
+    if (SDL_UDEV_AddCallback(capture_udev_callback) < 0) {
+        SDL_UDEV_Quit();
+        return SDL_SetError("Could not setup Video Capture <-> udev callback");
+    }
+
+    /* Force a scan to build the initial device list */
+    SDL_UDEV_Scan();
+#endif /* SDL_USE_LIBUDEV */
+
+    return num_video_captures;
+}
+
+
+int SDL_SYS_VideoCaptureQuit(void)
+{
+    SDL_capturelist_item *item;
+    for (item = SDL_capturelist; item; ) {
+        SDL_capturelist_item *tmp = item->next;
+
+        SDL_free(item->fname);
+        SDL_free(item->bus_info);
+        SDL_free(item);
+        item = tmp;
+    }
+
+    num_video_captures = 0;
+    SDL_capturelist = NULL;
+    SDL_capturelist_tail = NULL;
+
+    return SDL_FALSE;
+}
+
+#ifdef SDL_USE_LIBUDEV
+static void capture_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
+{
+    if (!devpath || !(udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) {
+        return;
+    }
+
+    switch (udev_type) {
+    case SDL_UDEV_DEVICEADDED:
+        MaybeAddDevice(devpath);
+        break;
+
+    case SDL_UDEV_DEVICEREMOVED:
+        MaybeRemoveDevice(devpath);
+        break;
+
+    default:
+        break;
+    }
+}
+#endif /* SDL_USE_LIBUDEV */
+
+static SDL_bool DeviceExists(const char *path, const char *bus_info) {
+    SDL_capturelist_item *item;
+
+    for (item = SDL_capturelist; item; item = item->next) {
+        /* found same dev name */
+        if (SDL_strcmp(path, item->fname) == 0) {
+            return SDL_TRUE;
+        }
+        /* found same bus_info */
+        if (SDL_strcmp(bus_info, item->bus_info) == 0) {
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
+}
+
+static int MaybeAddDevice(const char *path)
+{
+    char *bus_info = NULL;
+    struct v4l2_capability vcap;
+    int err;
+    int fd;
+    SDL_capturelist_item *item;
+
+    if (!path) {
+        return -1;
+    }
+
+    fd = open(path, O_RDWR);
+    if (fd < 0) {
+        return -2; /* stop iterating /dev/video%d */
+    }
+    err = ioctl(fd, VIDIOC_QUERYCAP, &vcap);
+    close(fd);
+    if (err) {
+        return -1;
+    }
+
+    bus_info = SDL_strdup((char *)vcap.bus_info);
+
+    if (DeviceExists(path, bus_info)) {
+        SDL_free(bus_info);
+        return 0;
+    }
+
+
+    /* Add new item */
+    item = (SDL_capturelist_item *)SDL_calloc(1, sizeof(SDL_capturelist_item));
+    if (!item) {
+        SDL_free(bus_info);
+        return -1;
+    }
+
+    item->fname = SDL_strdup(path);
+    if (!item->fname) {
+        SDL_free(item);
+        SDL_free(bus_info);
+        return -1;
+    }
+
+    item->fname = SDL_strdup(path);
+    item->bus_info = bus_info;
+    item->instance_id = SDL_GetNextObjectID();
+
+
+    if (!SDL_capturelist_tail) {
+        SDL_capturelist = SDL_capturelist_tail = item;
+    } else {
+        SDL_capturelist_tail->next = item;
+        SDL_capturelist_tail = item;
+    }
+
+    ++num_video_captures;
+
+    /* !!! TODO: Send a add event? */
+#if DEBUG_VIDEO_CAPTURE_CAPTURE
+    SDL_Log("Added video capture ID: %d %s (%s) (total: %d)", item->instance_id, path, bus_info, num_video_captures);
+#endif
     return 0;
 }
 
-int
-GetNumDevices(void) {
-    int num;
-    for (num = 0; num < 128; num++) {
-        static char buf[256];
-        buf[0] = 0;
-        buf[255] = 0;
-        GetDeviceName(num, buf, sizeof (buf));
-        SDL_RWops *src = SDL_RWFromFile(buf, "rb");
-        if (src == NULL) {
-            // When file does not exist, an error is set. Clear it.
-            SDL_ClearError();
-            return num;
+#ifdef SDL_USE_LIBUDEV
+static int MaybeRemoveDevice(const char *path)
+{
+
+    SDL_capturelist_item *item;
+    SDL_capturelist_item *prev = NULL;
+#if DEBUG_VIDEO_CAPTURE_CAPTURE
+    SDL_Log("Remove video capture %s", path);
+#endif
+    if (!path) {
+        return -1;
+    }
+
+    for (item = SDL_capturelist; 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_capturelist == item);
+                SDL_capturelist = item->next;
+            }
+            if (item == SDL_capturelist_tail) {
+                SDL_capturelist_tail = prev;
+            }
+
+            /* Need to decrement the count */
+            --num_video_captures;
+            /* !!! TODO: Send a remove event? */
+
+            SDL_free(item->fname);
+            SDL_free(item->bus_info);
+            SDL_free(item);
+            return 0;
         }
-        SDL_RWclose(src);
+        prev = item;
     }
-    return num;
+    return 0;
 }
+#endif /* SDL_USE_LIBUDEV */
+
+
 
 #endif
 

+ 37 - 2
src/video/android/SDL_android_video_capture.c

@@ -633,9 +633,12 @@ GetFrameSize(SDL_VideoCaptureDevice *_this, Uint32 format, int index, int *width
     return -1;
 }
 
+static int GetNumDevices(void);
+
 int
-GetDeviceName(int index, char *buf, int size)
+GetDeviceName(SDL_VideoCaptureDeviceID instance_id, char *buf, int size)
 {
+    int index = instance_id - 1;
     create_cameraMgr();
 
     if (cameraIdList == NULL) {
@@ -652,7 +655,7 @@ GetDeviceName(int index, char *buf, int size)
     return -1;
 }
 
-int
+static int
 GetNumDevices(void)
 {
     camera_status_t res;
@@ -673,6 +676,38 @@ GetNumDevices(void)
     return -1;
 }
 
+SDL_VideoCaptureDeviceID *GetVideoCaptureDevices(int *count)
+{
+    /* hard-coded list of ID */
+    int i;
+    int num = GetNumDevices();
+    SDL_VideoCaptureDeviceID *ret;
+
+    ret = (SDL_VideoCaptureDeviceID *)SDL_malloc((num + 1) * sizeof(*ret));
+
+    if (ret == NULL) {
+        SDL_OutOfMemory();
+        *count = 0;
+        return NULL;
+    }
+
+    for (i = 0; i < num; i++) {
+        ret[i] = i + 1;
+    }
+    ret[num] = 0;
+    *count = num;
+    return ret;
+}
+
+int SDL_SYS_VideoCaptureInit(void) {
+    return 0;
+}
+
+int SDL_SYS_VideoCaptureQuit(void) {
+    return 0;
+}
+
+
 #endif
 
 

+ 3 - 3
test/testvideocapture.c

@@ -125,7 +125,7 @@ static SDL_VideoCaptureDeviceID get_instance_id(int index) {
     }
 
     if (ret == 0) {
-        SDL_Log("invalid index");
+/*        SDL_Log("invalid index"); */
     }
 
     return ret;
@@ -212,7 +212,7 @@ int main(int argc, char **argv)
     SDL_Log("%s", usage);
 
     /* Load the SDL library */
-    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { /* FIXME: SDL_INIT_JOYSTICK needed for add/removing devices at runtime */
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
         return 1;
     }
@@ -439,7 +439,7 @@ int main(int argc, char **argv)
                 SAVE_CAPTURE_STATE(current_dev);
 
                 current_dev += 1;
-                if (current_dev == num || current_dev >= (int) SDL_arraysize(data_capture_tab)) {
+                if (current_dev >= num || current_dev >= (int) SDL_arraysize(data_capture_tab)) {
                     current_dev = 0;
                 }
 

+ 1 - 1
test/testvideocaptureminimal.c

@@ -53,7 +53,7 @@ int main(int argc, char **argv)
     SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
 
     /* Load the SDL library */
-    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { /* FIXME: SDL_INIT_JOYSTICK needed for add/removing devices at runtime */
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
         return 1;
     }