diff --git a/cmake/Modules/FindLibUDev.cmake b/cmake/Modules/FindLibUDev.cmake
new file mode 100644
index 000000000..7731ec0eb
--- /dev/null
+++ b/cmake/Modules/FindLibUDev.cmake
@@ -0,0 +1,33 @@
+# Once done these will be defined:
+#
+# UDEV_FOUND
+# UDEV_INCLUDE_DIRS
+# UDEV_LIBRARIES
+
+find_package(PkgConfig QUIET)
+if (PKG_CONFIG_FOUND)
+ pkg_check_modules(_UDEV QUIET udev)
+endif()
+
+find_path(UDEV_INCLUDE_DIR
+ NAMES libudev.h
+ HINTS
+ ${_UDEV_INCLUDE_DIRS}
+ PATHS
+ /usr/include /usr/local/include /opt/local/include)
+
+find_library(UDEV_LIB
+ NAMES udev libudev
+ HINTS
+ ${_UDEV_LIBRARY_DIRS}
+ PATHS
+ /usr/lib /usr/local/lib /opt/local/lib)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(UDev DEFAULT_MSG UDEV_LIB UDEV_INCLUDE_DIR)
+mark_as_advanced(UDEV_INCLUDE_DIR UDEV_LIB)
+
+if(UDEV_FOUND)
+ set(UDEV_INCLUDE_DIRS ${UDEV_INCLUDE_DIR})
+ set(UDEV_LIBRARIES ${UDEV_LIB})
+endif()
diff --git a/plugins/linux-v4l2/CMakeLists.txt b/plugins/linux-v4l2/CMakeLists.txt
index db728f475..a466c818c 100644
--- a/plugins/linux-v4l2/CMakeLists.txt
+++ b/plugins/linux-v4l2/CMakeLists.txt
@@ -6,6 +6,8 @@ if(DISABLE_V4L2)
endif()
find_package(Libv4l2)
+find_package(LibUDev QUIET)
+
if(NOT LIBV4L2_FOUND AND ENABLE_V4L2)
message(FATAL_ERROR "libv4l2 not found bit plugin set as enabled")
elseif(NOT LIBV4L2_FOUND)
@@ -13,10 +15,20 @@ elseif(NOT LIBV4L2_FOUND)
return()
endif()
+if(NOT UDEV_FOUND OR DISABLE_UDEV)
+ message(STATUS "udev disabled for v4l2 plugin")
+else()
+ set(linux-v4l2-udev_SOURCES
+ v4l2-udev.c
+ )
+ add_definitions(-DHAVE_UDEV)
+endif()
+
set(linux-v4l2_SOURCES
linux-v4l2.c
v4l2-input.c
v4l2-helpers.c
+ ${linux-v4l2-udev_SOURCES}
)
add_library(linux-v4l2 MODULE
@@ -25,6 +37,7 @@ add_library(linux-v4l2 MODULE
target_link_libraries(linux-v4l2
libobs
${LIBV4L2_LIBRARIES}
+ ${UDEV_LIBRARIES}
)
install_obs_plugin_with_data(linux-v4l2 data)
diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c
index 9537701a0..88155db9d 100644
--- a/plugins/linux-v4l2/v4l2-input.c
+++ b/plugins/linux-v4l2/v4l2-input.c
@@ -36,6 +36,10 @@ along with this program. If not, see .
#include "v4l2-helpers.h"
+#if HAVE_UDEV
+#include "v4l2-udev.h"
+#endif
+
#define V4L2_DATA(voidptr) struct v4l2_data *data = voidptr;
#define timeval2ns(tv) \
@@ -58,6 +62,7 @@ struct v4l2_data {
obs_source_t *source;
pthread_t thread;
os_event_t *event;
+ void *udev;
int_fast32_t dev;
int width;
@@ -69,6 +74,10 @@ struct v4l2_data {
uint64_t frames;
};
+/* forward declarations */
+static void v4l2_init(struct v4l2_data *data);
+static void v4l2_terminate(struct v4l2_data *data);
+
/**
* Prepare the output frame structure for obs and compute plane offsets
*
@@ -240,7 +249,7 @@ static void v4l2_device_list(obs_property_t *prop, obs_data_t *settings)
if (v4l2_ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1) {
blog(LOG_INFO, "Failed to query capabilities for %s",
device.array);
- close(fd);
+ v4l2_close(fd);
continue;
}
@@ -251,7 +260,7 @@ static void v4l2_device_list(obs_property_t *prop, obs_data_t *settings)
if (!(caps & V4L2_CAP_VIDEO_CAPTURE)) {
blog(LOG_INFO, "%s seems to not support video capture",
device.array);
- close(fd);
+ v4l2_close(fd);
continue;
}
@@ -260,7 +269,7 @@ static void v4l2_device_list(obs_property_t *prop, obs_data_t *settings)
blog(LOG_INFO, "Found device '%s' at %s", video_cap.card,
device.array);
- close(fd);
+ v4l2_close(fd);
}
closedir(dirp);
@@ -504,6 +513,43 @@ static bool resolution_selected(obs_properties_t *props, obs_property_t *p,
return true;
}
+#if HAVE_UDEV
+/**
+ * Device added callback
+ *
+ * If everything went fine we can start capturing again when the device is
+ * reconnected
+ */
+static void device_added(const char *dev, void *vptr)
+{
+ V4L2_DATA(vptr);
+
+ if (strcmp(data->device_id, dev))
+ return;
+
+ blog(LOG_INFO, "Device %s reconnected", dev);
+
+ v4l2_init(data);
+}
+/**
+ * Device removed callback
+ *
+ * We stop recording here so we don't block the device node
+ */
+static void device_removed(const char *dev, void *vptr)
+{
+ V4L2_DATA(vptr);
+
+ if (strcmp(data->device_id, dev))
+ return;
+
+ blog(LOG_INFO, "Device %s disconnected", dev);
+
+ v4l2_terminate(data);
+}
+
+#endif
+
static obs_properties_t *v4l2_properties(void *unused)
{
UNUSED_PARAMETER(unused);
@@ -567,6 +613,11 @@ static void v4l2_destroy(void *vptr)
if (data->device_id)
bfree(data->device_id);
+
+#if HAVE_UDEV
+ v4l2_unref_udev(data->udev);
+#endif
+
bfree(data);
}
@@ -672,6 +723,12 @@ static void *v4l2_create(obs_data_t *settings, obs_source_t *source)
v4l2_update(data, settings);
+#if HAVE_UDEV
+ data->udev = v4l2_init_udev();
+ v4l2_set_device_added_callback(data->udev, &device_added, data);
+ v4l2_set_device_removed_callback(data->udev, &device_removed, data);
+#endif
+
return data;
}
diff --git a/plugins/linux-v4l2/v4l2-udev.c b/plugins/linux-v4l2/v4l2-udev.c
new file mode 100644
index 000000000..8538197e5
--- /dev/null
+++ b/plugins/linux-v4l2/v4l2-udev.c
@@ -0,0 +1,224 @@
+/*
+Copyright (C) 2014 by Leonhard Oelke
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#include
+
+#include
+#include
+#include
+
+#include "v4l2-udev.h"
+
+#define UDEV_DATA(voidptr) struct v4l2_udev_mon_t *m \
+ = (struct v4l2_udev_mon_t *) voidptr;
+
+/** udev action enum */
+enum udev_action {
+ UDEV_ACTION_ADDED,
+ UDEV_ACTION_REMOVED,
+ UDEV_ACTION_UNKNOWN
+};
+
+/** monitor object holding the callbacks */
+struct v4l2_udev_mon_t {
+ /* data for the device added callback */
+ void *dev_added_userdata;
+ v4l2_device_added_cb dev_added_cb;
+ /* data for the device removed callback */
+ void *dev_removed_userdata;
+ v4l2_device_removed_cb dev_removed_cb;
+};
+
+/* global data */
+static uint_fast32_t udev_refs = 0;
+static pthread_mutex_t udev_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static pthread_t udev_thread;
+static os_event_t *udev_event;
+
+static DARRAY(struct v4l2_udev_mon_t) udev_clients;
+
+/**
+ * udev gives us the device action as string, so we convert it here ...
+ *
+ * @param action the udev action as string
+ * @return the udev action as enum value
+ */
+static enum udev_action udev_action_to_enum(const char *action)
+{
+ if (!action)
+ return UDEV_ACTION_UNKNOWN;
+
+ if (!strncmp("add", action, 3))
+ return UDEV_ACTION_ADDED;
+ if (!strncmp("remove", action, 6))
+ return UDEV_ACTION_REMOVED;
+
+ return UDEV_ACTION_UNKNOWN;
+}
+
+/**
+ * Call all registered callbacks with the event
+ *
+ * @param dev udev device that had an event occuring
+ */
+static inline void udev_call_callbacks(struct udev_device *dev)
+{
+ const char *node;
+ enum udev_action action;
+
+ pthread_mutex_lock(&udev_mutex);
+
+ node = udev_device_get_devnode(dev);
+ action = udev_action_to_enum(udev_device_get_action(dev));
+
+ for (size_t idx = 0; idx < udev_clients.num; idx++) {
+ struct v4l2_udev_mon_t *c = &udev_clients.array[idx];
+
+ switch (action) {
+ case UDEV_ACTION_ADDED:
+ if (!c->dev_added_cb)
+ continue;
+ c->dev_added_cb(node, c->dev_added_userdata);
+ break;
+ case UDEV_ACTION_REMOVED:
+ if (!c->dev_removed_cb)
+ continue;
+ c->dev_removed_cb(node, c->dev_removed_userdata);
+ break;
+ default:
+ break;
+ }
+ }
+
+ pthread_mutex_unlock(&udev_mutex);
+}
+
+/**
+ * Event listener thread
+ */
+static void *udev_event_thread(void *vptr)
+{
+ UNUSED_PARAMETER(vptr);
+
+ int fd;
+ fd_set fds;
+ struct timeval tv;
+ struct udev *udev;
+ struct udev_monitor *mon;
+ struct udev_device *dev;
+
+ /* set up udev monitoring */
+ udev = udev_new();
+ mon = udev_monitor_new_from_netlink(udev, "udev");
+ udev_monitor_filter_add_match_subsystem_devtype(
+ mon, "video4linux", NULL);
+ if (udev_monitor_enable_receiving(mon) < 0)
+ return NULL;
+
+ /* set up fds */
+ fd = udev_monitor_get_fd(mon);
+
+ while (os_event_try(udev_event) == EAGAIN) {
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ if (select(fd + 1, &fds, NULL, NULL, &tv) <= 0)
+ continue;
+
+ dev = udev_monitor_receive_device(mon);
+ if (!dev)
+ continue;
+
+ udev_call_callbacks(dev);
+
+ udev_device_unref(dev);
+ }
+
+ udev_monitor_unref(mon);
+ udev_unref(udev);
+
+ return NULL;
+}
+
+void *v4l2_init_udev(void)
+{
+ struct v4l2_udev_mon_t *ret = NULL;
+
+ pthread_mutex_lock(&udev_mutex);
+
+ /* set up udev */
+ if (udev_refs == 0) {
+ if (os_event_init(&udev_event, OS_EVENT_TYPE_MANUAL) != 0)
+ goto fail;
+ if (pthread_create(&udev_thread, NULL, udev_event_thread,
+ NULL) != 0)
+ goto fail;
+ da_init(udev_clients);
+ }
+ udev_refs++;
+
+ /* create monitor object */
+ ret = da_push_back_new(udev_clients);
+fail:
+ pthread_mutex_unlock(&udev_mutex);
+ return ret;
+}
+
+void v4l2_unref_udev(void *monitor)
+{
+ UDEV_DATA(monitor);
+ pthread_mutex_lock(&udev_mutex);
+
+ /* clean up monitor object */
+ da_erase_item(udev_clients, m);
+
+ /* unref udev monitor */
+ udev_refs--;
+ if (udev_refs == 0) {
+ os_event_signal(udev_event);
+ pthread_join(udev_thread, NULL);
+ os_event_destroy(udev_event);
+ da_free(udev_clients);
+ }
+
+ pthread_mutex_unlock(&udev_mutex);
+}
+
+void v4l2_set_device_added_callback(void *monitor, v4l2_device_added_cb cb,
+ void *userdata)
+{
+ UDEV_DATA(monitor);
+ if (!m)
+ return;
+
+ m->dev_added_cb = cb;
+ m->dev_added_userdata = userdata;
+}
+
+void v4l2_set_device_removed_callback(void *monitor, v4l2_device_removed_cb cb,
+ void *userdata)
+{
+ UDEV_DATA(monitor);
+ if (!m)
+ return;
+
+ m->dev_removed_cb = cb;
+ m->dev_removed_userdata = userdata;
+}
diff --git a/plugins/linux-v4l2/v4l2-udev.h b/plugins/linux-v4l2/v4l2-udev.h
new file mode 100644
index 000000000..f95966ca1
--- /dev/null
+++ b/plugins/linux-v4l2/v4l2-udev.h
@@ -0,0 +1,80 @@
+/*
+Copyright (C) 2014 by Leonhard Oelke
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#pragma once
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Initialize udev system to watch for device events
+ *
+ * @return monitor object, or NULL on error
+ */
+void *v4l2_init_udev(void);
+
+/**
+ * Unref the udev system
+ *
+ * This will also remove any registered callbacks if there are any
+ *
+ * @param monitor monitor object
+ */
+void v4l2_unref_udev(void *monitor);
+
+/**
+ * Callback when a device was added.
+ *
+ * @param dev device node of the device that was added
+ * @param userdata pointer to userdata specified when registered
+ */
+typedef void (*v4l2_device_added_cb)(const char *dev, void *userdata);
+
+/**
+ * Callback when a device was removed.
+ *
+ * @param dev device node of the device that was removed
+ * @param userdata pointer to userdata specified when registered
+ */
+typedef void (*v4l2_device_removed_cb)(const char *dev, void *userdata);
+
+/**
+ * Register the device added callback
+ *
+ * @param monitor monitor object
+ * @param cb the function that should be called
+ * @param userdata pointer to userdata that should be passed to the callback
+ */
+void v4l2_set_device_added_callback(void *monitor, v4l2_device_added_cb cb,
+ void *userdata);
+
+/**
+ * Register the device remove callback
+ *
+ * @param monitor monitor object
+ * @param cb the function that should be called
+ * @param userdata pointer to userdata that should be passed to the callback
+ */
+void v4l2_set_device_removed_callback(void *monitor, v4l2_device_removed_cb cb,
+ void *userdata);
+
+#ifdef __cplusplus
+}
+#endif