From 43184d17752ebb4f3a0a0dbbee64e5c351c4558f Mon Sep 17 00:00:00 2001 From: fryshorts Date: Tue, 14 Oct 2014 20:07:19 +0200 Subject: [PATCH 1/4] Add cmake module for finding udev. This adds a cmake module to find udev/libudev for linux. --- cmake/Modules/FindLibUDev.cmake | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 cmake/Modules/FindLibUDev.cmake 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() From 1bae57668c0c9c1966b6234767b7feb0c800237f Mon Sep 17 00:00:00 2001 From: fryshorts Date: Tue, 14 Oct 2014 23:32:52 +0200 Subject: [PATCH 2/4] Add udev helper library to v4l2 plugin. This adds a small helper library to the v4l2 plugin that uses udev to get events for v4l2 devices. --- plugins/linux-v4l2/CMakeLists.txt | 13 ++ plugins/linux-v4l2/v4l2-udev.c | 224 ++++++++++++++++++++++++++++++ plugins/linux-v4l2/v4l2-udev.h | 80 +++++++++++ 3 files changed, 317 insertions(+) create mode 100644 plugins/linux-v4l2/v4l2-udev.c create mode 100644 plugins/linux-v4l2/v4l2-udev.h 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-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 From 9a37b2ce9cfca7e105babd62de6f7d80ce373be9 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Tue, 14 Oct 2014 23:38:21 +0200 Subject: [PATCH 3/4] Use udev events in the v4l2 plugin. This adds code to set up the udev monitoring library and use the events to detect connects/disconnects of devices. When the currently used device is disconnected the plugin will stop recording and clean up, so that the device node is freed up. On reconnection of the device the plugin will use the event to automatically start the capture again. --- plugins/linux-v4l2/v4l2-input.c | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index 9537701a0..0e6b995dc 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 * @@ -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; } From 8a51431fdb04446e492e7412abbc5dcd682031af Mon Sep 17 00:00:00 2001 From: fryshorts Date: Tue, 14 Oct 2014 23:48:59 +0200 Subject: [PATCH 4/4] Fix left open file descriptor in v4l2 plugin. --- plugins/linux-v4l2/v4l2-input.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index 0e6b995dc..88155db9d 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -249,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; } @@ -260,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; } @@ -269,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);