diff --git a/src/core/linux/SDL_evdev_capabilities.h b/src/core/linux/SDL_evdev_capabilities.h index 94afee796..ca90309ed 100644 --- a/src/core/linux/SDL_evdev_capabilities.h +++ b/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) diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c index a459489b2..903f27208 100644 --- a/src/core/linux/SDL_udev.c +++ b/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 */ diff --git a/src/video/SDL_sysvideocapture.h b/src/video/SDL_sysvideocapture.h index fe71664f5..305ba53ec 100644 --- a/src/video/SDL_sysvideocapture.h +++ b/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); diff --git a/src/video/SDL_video_capture.c b/src/video/SDL_video_capture.c index bd4fc8257..57f960add 100644 --- a/src/video/SDL_video_capture.c +++ b/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 */ diff --git a/src/video/SDL_video_capture_apple.m b/src/video/SDL_video_capture_apple.m index 3e58cf4e7..7e88ea08a 100644 --- a/src/video/SDL_video_capture_apple.m +++ b/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 *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 *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 */ diff --git a/src/video/SDL_video_capture_v4l2.c b/src/video/SDL_video_capture_v4l2.c index 539cc1711..29dcb0f4e 100644 --- a/src/video/SDL_video_capture_v4l2.c +++ b/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 /* 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(int index, char *buf, int size) { - SDL_snprintf(buf, size, "/dev/video%d", index); +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; + + 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; - } - SDL_RWclose(src); +#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; } - return num; + + 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; + } + prev = item; + } + return 0; } +#endif /* SDL_USE_LIBUDEV */ + + #endif diff --git a/src/video/android/SDL_android_video_capture.c b/src/video/android/SDL_android_video_capture.c index 244d779c2..3e8e433bb 100644 --- a/src/video/android/SDL_android_video_capture.c +++ b/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 diff --git a/test/testvideocapture.c b/test/testvideocapture.c index aff7642b1..62471a763 100644 --- a/test/testvideocapture.c +++ b/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; } diff --git a/test/testvideocaptureminimal.c b/test/testvideocaptureminimal.c index 870dcab66..3c29d47ce 100644 --- a/test/testvideocaptureminimal.c +++ b/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; }