SDL_VideoCapture: allow add/remove device at runtime on linux

main
Sylvain 2023-11-27 19:35:45 +01:00 committed by Ryan C. Gordon
parent f0e47f8ee0
commit 6bb40f1d8d
9 changed files with 411 additions and 49 deletions

View File

@ -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)

View File

@ -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 */

View File

@ -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);

View File

@ -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 */

View File

@ -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 */

View File

@ -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(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

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}