diff --git a/meson.build b/meson.build index 1554504fb..f2370e4a9 100644 --- a/meson.build +++ b/meson.build @@ -450,6 +450,9 @@ configure_file(output : 'config.h', if not get_option('pipewire-jack').disabled() subdir('pipewire-jack') endif +if not get_option('pipewire-v4l2').disabled() + subdir('pipewire-v4l2') +endif if alsa_dep.found() subdir('pipewire-alsa/alsa-plugins') diff --git a/meson_options.txt b/meson_options.txt index db4b7f6a5..71c2e35e9 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -50,6 +50,10 @@ option('pipewire-jack', description: 'Enable pipewire-jack integration', type: 'feature', value: 'enabled') +option('pipewire-v4l2', + description: 'Enable pipewire-v4l2 integration', + type: 'feature', + value: 'enabled') option('jack-devel', description: 'Install jack development files', type: 'boolean', diff --git a/pipewire-v4l2/meson.build b/pipewire-v4l2/meson.build new file mode 100644 index 000000000..9537275d6 --- /dev/null +++ b/pipewire-v4l2/meson.build @@ -0,0 +1 @@ +subdir('src') diff --git a/pipewire-v4l2/src/meson.build b/pipewire-v4l2/src/meson.build new file mode 100644 index 000000000..9675aea4b --- /dev/null +++ b/pipewire-v4l2/src/meson.build @@ -0,0 +1,36 @@ +pipewire_v4l2_sources = [ + 'pipewire-v4l2.c', + 'v4l2-func.c', +] + +pipewire_v4l2_c_args = [ + # Meson enables large file support unconditionally, which redirect file + # operations to 64-bit versions. This results in some symbols being + # renamed, for instance open() being renamed to open64(). As the V4L2 + # adaptation wrapper needs to provide both 32-bit and 64-bit versions of + # file operations, disable transparent large file support. + '-U_FILE_OFFSET_BITS', + '-D_FILE_OFFSET_BITS=32', + '-D_LARGEFILE64_SOURCE', + '-fvisibility=hidden', +] + +libv4l2_path = modules_install_dir / 'v4l2' +libv4l2_path_dlopen = modules_install_dir_dlopen / 'v4l2' + +tools_config = configuration_data() +tools_config.set('LIBV4L2_PATH', libv4l2_path_dlopen) + +configure_file(input : 'pw-v4l2.in', + output : 'pw-v4l2', + configuration : tools_config, + install_dir : pipewire_bindir) + +pipewire_v4l2 = shared_library('pw-v4l2', + pipewire_v4l2_sources, + c_args : pipewire_v4l2_c_args, + include_directories : [configinc], + dependencies : [pipewire_dep, mathlib, dl_lib], + install : true, + install_dir : libv4l2_path, +) diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c new file mode 100644 index 000000000..d33825ce2 --- /dev/null +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -0,0 +1,623 @@ +/* PipeWire + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pipewire-v4l2.h" + +#include + +#include + +PW_LOG_TOPIC_STATIC(v4l2_log_topic, "v4l2"); +#define PW_LOG_TOPIC_DEFAULT v4l2_log_topic + +struct globals { + pthread_mutex_t lock; + + struct fops old_fops; + + struct spa_list files; + struct spa_list maps; +}; + +static struct globals globals; + +struct map { + struct spa_list link; + int ref; + void *addr; + struct file *file; +}; + +struct file { + struct spa_list link; + int ref; + + struct pw_properties *props; + struct pw_thread_loop *loop; + struct pw_loop *l; + struct pw_context *context; + + struct pw_core *core; + struct spa_hook core_listener; + int pending_sync; + int last_sync; + int last_res; + bool error; + + struct pw_registry *registry; + struct spa_hook registry_listener; + + int fd; + int other_fd; +}; + +#define ATOMIC_DEC(s) __atomic_sub_fetch(&(s), 1, __ATOMIC_SEQ_CST) +#define ATOMIC_INC(s) __atomic_add_fetch(&(s), 1, __ATOMIC_SEQ_CST) + +static struct file *make_file(void) +{ + struct file *file; + + file = calloc(1, sizeof(*file)); + if (file == NULL) + return NULL; + + file->ref = 1; + spa_list_init(&file->link); + return file; +} + +static void put_file(struct file *file) +{ + pthread_mutex_lock(&globals.lock); + spa_list_append(&globals.files, &file->link); + pthread_mutex_unlock(&globals.lock); +} + +static struct file *find_file(int fd) +{ + struct file *f, *res = NULL; + pthread_mutex_lock(&globals.lock); + spa_list_for_each(f, &globals.files, link) + if (f->fd == fd) { + res = f; + ATOMIC_INC(f->ref); + break; + } + pthread_mutex_unlock(&globals.lock); + return res; +} + +static void free_file(struct file *file) +{ + struct map *m, *t; + + pthread_mutex_lock(&globals.lock); + spa_list_remove(&file->link); + spa_list_for_each_safe(m, t, &globals.maps, link) { + if (m->file == file) { + spa_list_remove(&m->link); + free(m); + } + } + pthread_mutex_unlock(&globals.lock); + free(file); +} + +static void unref_file(struct file *file) +{ + if (ATOMIC_DEC(file->ref) <= 0) + free_file(file); +} + +static struct map *make_map(struct file *file, void *addr) +{ + struct map *map; + + map = calloc(1, sizeof(*map)); + if (map == NULL) + return NULL; + + map->ref = 1; + map->file = file; + map->addr = addr; + spa_list_init(&map->link); + return map; +} + +static void put_map(struct map *map) +{ + pthread_mutex_lock(&globals.lock); + spa_list_append(&globals.maps, &map->link); + pthread_mutex_unlock(&globals.lock); +} + +static struct map *find_map(void *addr) +{ + struct map *m, *res = NULL; + pthread_mutex_lock(&globals.lock); + spa_list_for_each(m, &globals.maps, link) + if (m->addr == addr) { + res = m; + ATOMIC_INC(m->ref); + break; + } + pthread_mutex_unlock(&globals.lock); + return res; +} + +static void free_map(struct map *map) +{ + pthread_mutex_lock(&globals.lock); + spa_list_remove(&map->link); + pthread_mutex_unlock(&globals.lock); + free(map); +} + +static void unref_map(struct map *map) +{ + if (ATOMIC_DEC(map->ref) <= 0) + free_map(map); +} + +static void on_sync_reply(void *data, uint32_t id, int seq) +{ + struct file *file = data; + if (id != PW_ID_CORE) + return; + file->last_sync = seq; + if (file->pending_sync == seq) + pw_thread_loop_signal(file->loop, false); +} + +static void on_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + struct file *file = data; + + pw_log_warn("%p: error id:%u seq:%d res:%d (%s): %s", file, + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE) { + file->error = true; + file->last_res = res; + } + pw_thread_loop_signal(file->loop, false); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = on_sync_reply, + .error = on_error, +}; + +static void registry_event_global(void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) +{ +} + +static void registry_event_global_remove(void *object, uint32_t id) +{ +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, + .global_remove = registry_event_global_remove, +}; + +static int v4l2_openat(int dirfd, const char *path, int oflag, mode_t mode) +{ + int res; + int fds[2]; + struct file *file; + + if (!spa_strstartswith(path, "/dev/video0")) + return globals.old_fops.openat(dirfd, path, oflag, mode); + + if ((file = make_file()) == NULL) + return -1; + + file->props = pw_properties_new( + PW_KEY_CLIENT_API, "v4l2", + NULL); + file->loop = pw_thread_loop_new("v4l2", NULL); + file->l = pw_thread_loop_get_loop(file->loop); + file->context = pw_context_new(file->l, + pw_properties_copy(file->props), 0); + if (file->context == NULL) + return -1; + + pw_thread_loop_start(file->loop); + + pw_thread_loop_lock(file->loop); + + file->core = pw_context_connect(file->context, + pw_properties_copy(file->props), 0); + if (file->core == NULL) + return -1; + + pw_core_add_listener(file->core, + &file->core_listener, + &core_events, file); + file->registry = pw_core_get_registry(file->core, + PW_VERSION_REGISTRY, 0); + pw_registry_add_listener(file->registry, + &file->registry_listener, + ®istry_events, file); + + pw_thread_loop_unlock(file->loop); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) + return -1; + + res = file->fd = fds[0]; + file->other_fd = fds[1]; + + pw_log_info("path:%s oflag:%d mode:%d -> %d (%s)", path, oflag, mode, + res, strerror(res < 0 ? errno : 0)); + + put_file(file); + + return res; +} + +static int v4l2_dup(int oldfd) +{ + int res; + struct file *file; + + if ((file = find_file(oldfd)) == NULL) + return globals.old_fops.dup(oldfd); + + res = globals.old_fops.dup(oldfd); + + pw_log_info("fd:%d -> %d (%s)", oldfd, + res, strerror(res < 0 ? errno : 0)); + unref_file(file); + + return res; +} + +static int v4l2_close(int fd) +{ + int res; + struct file *file; + + if ((file = find_file(fd)) == NULL) + return globals.old_fops.close(fd); + + res = globals.old_fops.close(file->fd); + res = globals.old_fops.close(file->other_fd); + + if (file->loop) + pw_thread_loop_stop(file->loop); + + if (file->registry) { + spa_hook_remove(&file->registry_listener); + pw_proxy_destroy((struct pw_proxy*)file->registry); + } + if (file->core) { + spa_hook_remove(&file->core_listener); + pw_core_disconnect(file->core); + } + if (file->context) + pw_context_destroy(file->context); + if (file->loop) + pw_thread_loop_destroy(file->loop); + + free_file(file); + + pw_log_info("fd:%d -> %d (%s)", fd, + res, strerror(res < 0 ? errno : 0)); + return res; +} + +#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c)) + +static int vidioc_querycap(struct file *file, struct v4l2_capability *arg) +{ + int res = 0; + + spa_scnprintf((char*)arg->driver, sizeof(arg->driver), "%s", "pipewire"); + spa_scnprintf((char*)arg->card, sizeof(arg->card), "%s", "cam1"); + spa_scnprintf((char*)arg->bus_info, sizeof(arg->bus_info), "%s:%d", "pipewire", 1); + + arg->version = KERNEL_VERSION(5, 2, 0); + arg->device_caps = V4L2_CAP_VIDEO_CAPTURE + | V4L2_CAP_STREAMING + | V4L2_CAP_EXT_PIX_FORMAT; + arg->capabilities = arg->device_caps | V4L2_CAP_DEVICE_CAPS; + memset(arg->reserved, 0, sizeof(arg->reserved)); + + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} + +static int vidioc_enum_framesizes(struct file *file, struct v4l2_frmsizeenum *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_g_fmt(struct file *file, struct v4l2_format *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_s_fmt(struct file *file, struct v4l2_format *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_try_fmt(struct file *file, struct v4l2_format *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_g_priority(struct file *file, enum v4l2_priority *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_s_priority(struct file *file, enum v4l2_priority *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_enuminput(struct file *file, struct v4l2_input *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_g_input(struct file *file, int *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_s_input(struct file *file, int *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_reqbufs(struct file *file, struct v4l2_requestbuffers *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_querybuf(struct file *file, struct v4l2_buffer *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_qbuf(struct file *file, struct v4l2_buffer *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_dqbuf(struct file *file, struct v4l2_buffer *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_streamon(struct file *file, int *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} +static int vidioc_streamoff(struct file *file, int *arg) +{ + int res = -ENOTTY; + pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + return res; +} + +static int v4l2_ioctl(int fd, unsigned long int request, void *arg) +{ + int res; + struct file *file; + + if ((file = find_file(fd)) == NULL) + return globals.old_fops.ioctl(fd, request, arg); + + if (arg == NULL && (_IOC_DIR(request) & (_IOC_WRITE | _IOC_READ))) { + res = -EFAULT; + goto done; + } + + switch (request & 0xffffffff) { + case VIDIOC_QUERYCAP: + res = vidioc_querycap(file, (struct v4l2_capability *)arg); + break; + case VIDIOC_ENUM_FRAMESIZES: + res = vidioc_enum_framesizes(file, (struct v4l2_frmsizeenum *)arg); + break; + case VIDIOC_ENUM_FMT: + res = vidioc_enum_fmt(file, (struct v4l2_fmtdesc *)arg); + break; + case VIDIOC_G_FMT: + res = vidioc_g_fmt(file, (struct v4l2_format *)arg); + break; + case VIDIOC_S_FMT: + res = vidioc_s_fmt(file, (struct v4l2_format *)arg); + break; + case VIDIOC_TRY_FMT: + res = vidioc_try_fmt(file, (struct v4l2_format *)arg); + break; + case VIDIOC_G_PRIORITY: + res = vidioc_g_priority(file, (enum v4l2_priority *)arg); + break; + case VIDIOC_S_PRIORITY: + res = vidioc_s_priority(file, (enum v4l2_priority *)arg); + break; + case VIDIOC_ENUMINPUT: + res = vidioc_enuminput(file, (struct v4l2_input *)arg); + break; + case VIDIOC_G_INPUT: + res = vidioc_g_input(file, (int *)arg); + break; + case VIDIOC_S_INPUT: + res = vidioc_s_input(file, (int *)arg); + break; + case VIDIOC_REQBUFS: + res = vidioc_reqbufs(file, (struct v4l2_requestbuffers *)arg); + break; + case VIDIOC_QUERYBUF: + res = vidioc_querybuf(file, (struct v4l2_buffer *)arg); + break; + case VIDIOC_QBUF: + res = vidioc_qbuf(file, (struct v4l2_buffer *)arg); + break; + case VIDIOC_DQBUF: + res = vidioc_dqbuf(file, (struct v4l2_buffer *)arg); + break; + case VIDIOC_STREAMON: + res = vidioc_streamon(file, (int *)arg); + break; + case VIDIOC_STREAMOFF: + res = vidioc_streamoff(file, (int *)arg); + break; + default: + res = -ENOTTY; + break; + } +done: + if (res < 0) { + errno = -res; + res = -1; + } + pw_log_info("fd:%d request:%lx nr:%d arg:%p -> %d (%s)", + fd, request, (int)_IOC_NR(request), arg, + res, strerror(res < 0 ? errno : 0)); + + unref_file(file); + + return res; +} + +static void *v4l2_mmap(void *addr, size_t length, int prot, + int flags, int fd, off64_t offset) +{ + void *res; + struct file *file; + struct map *map; + + if ((file = find_file(fd)) == NULL) + return globals.old_fops.mmap(addr, length, prot, flags, fd, offset); + + if ((map = make_map(addr, file)) == NULL) { + res = MAP_FAILED; + goto exit; + } + + res = MAP_FAILED; + errno = ENOTSUP; + + pw_log_info("addr:%p length:%zu prot:%d flags:%d fd:%d offset:%"PRIi64" -> %p (%s)" , + addr, length, prot, flags, fd, offset, + res, strerror(res == MAP_FAILED ? errno : 0)); + + put_map(map); +exit: + unref_file(file); + + return res; +} + +static int v4l2_munmap(void *addr, size_t length) +{ + int res; + struct map *map; + + if ((map = find_map(addr)) == NULL) + return globals.old_fops.munmap(addr, length); + + res = globals.old_fops.munmap(addr, length); + + pw_log_info("addr:%p length:%zu -> %d (%s)", addr, length, + res, strerror(res < 0 ? errno : 0)); + + unref_map(map); + + return res; +} + +const struct fops fops = { + .openat = v4l2_openat, + .dup = v4l2_dup, + .close = v4l2_close, + .ioctl = v4l2_ioctl, + .mmap = v4l2_mmap, + .munmap = v4l2_munmap, +}; + +static void reg(void) __attribute__ ((constructor)); +static void reg(void) +{ + globals.old_fops.openat = dlsym(RTLD_NEXT, "openat64"); + globals.old_fops.dup = dlsym(RTLD_NEXT, "dup"); + globals.old_fops.close = dlsym(RTLD_NEXT, "close"); + globals.old_fops.ioctl = dlsym(RTLD_NEXT, "ioctl"); + globals.old_fops.mmap = dlsym(RTLD_NEXT, "mmap64"); + globals.old_fops.munmap = dlsym(RTLD_NEXT, "munmap"); + + pw_init(NULL, NULL); + PW_LOG_TOPIC_INIT(v4l2_log_topic); + + pthread_mutex_init(&globals.lock, NULL); + spa_list_init(&globals.files); + spa_list_init(&globals.maps); +} diff --git a/pipewire-v4l2/src/pipewire-v4l2.h b/pipewire-v4l2/src/pipewire-v4l2.h new file mode 100644 index 000000000..45079b4db --- /dev/null +++ b/pipewire-v4l2/src/pipewire-v4l2.h @@ -0,0 +1,38 @@ +/* PipeWire + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +struct fops { + int (*openat)(int dirfd, const char *path, int oflag, mode_t mode); + int (*dup)(int oldfd); + int (*close)(int fd); + int (*ioctl)(int fd, unsigned long request, void *arg); + void *(*mmap)(void *addr, size_t length, int prot, + int flags, int fd, off64_t offset); + int (*munmap)(void *addr, size_t length); +}; + +extern const struct fops fops; diff --git a/pipewire-v4l2/src/pw-v4l2.in b/pipewire-v4l2/src/pw-v4l2.in new file mode 100755 index 000000000..e56245979 --- /dev/null +++ b/pipewire-v4l2/src/pw-v4l2.in @@ -0,0 +1,65 @@ +#!/bin/sh + +# This file is part of PipeWire. +# +# Copyright © 2021 Wim Taymans +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +while getopts 'hr:vs:p:' param ; do + case $param in + r) + PIPEWIRE_REMOTE="$OPTARG" + export PIPEWIRE_REMOTE + ;; + v) + if [ -z "$PIPEWIRE_DEBUG" ]; then + PIPEWIRE_DEBUG=3 + else + PIPEWIRE_DEBUG=$(( PIPEWIRE_DEBUG + 1 )) + fi + export PIPEWIRE_DEBUG + ;; + *) + echo "$0 - run v4l2 applications on PipeWire" + echo " " + echo "$0 [options] application [arguments]" + echo " " + echo "options:" + echo " -h show brief help" + echo " -r remote daemon name" + echo " -v verbose debug info" + exit 0 + ;; + esac +done + +shift $(( OPTIND - 1 )) + +if [ x"$LD_PRELOAD" = x ] ; then + LD_PRELOAD="@LIBV4L2_PATH@/libpw-v4l2.so" +else + LD_PRELOAD="$LD_PRELOAD @LIBV4L2_PATH@/libpw-v4l2.so" +fi + +export LD_PRELOAD + +exec "$@" diff --git a/pipewire-v4l2/src/v4l2-func.c b/pipewire-v4l2/src/v4l2-func.c new file mode 100644 index 000000000..af40f1dc3 --- /dev/null +++ b/pipewire-v4l2/src/v4l2-func.c @@ -0,0 +1,140 @@ +/* PipeWire + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "pipewire-v4l2.h" + +#define SPA_EXPORT __attribute__((visibility("default"))) + +#define extract_va_arg(type, arg, last) \ +{ \ + va_list ap; \ + va_start(ap, last); \ + arg = va_arg(ap, type); \ + va_end(ap); \ +} + +SPA_EXPORT int open(const char *path, int oflag, ...) +{ + mode_t mode = 0; + if (oflag & O_CREAT || oflag & O_TMPFILE) + extract_va_arg(mode_t, mode, oflag); + + return fops.openat(AT_FDCWD, path, oflag, mode); +} + +/* _FORTIFY_SOURCE redirects open to __open_2 */ +SPA_EXPORT int __open_2(const char *path, int oflag) +{ + return open(path, oflag); +} + +#ifndef open64 +SPA_EXPORT int open64(const char *path, int oflag, ...) +{ + mode_t mode = 0; + if (oflag & O_CREAT || oflag & O_TMPFILE) + extract_va_arg(mode_t, mode, oflag); + + return fops.openat(AT_FDCWD, path, oflag | O_LARGEFILE, mode); +} + +SPA_EXPORT int __open64_2(const char *path, int oflag) +{ + return open(path, oflag); +} +#endif + +SPA_EXPORT int openat(int dirfd, const char *path, int oflag, ...) +{ + mode_t mode = 0; + if (oflag & O_CREAT || oflag & O_TMPFILE) + extract_va_arg(mode_t, mode, oflag); + + return fops.openat(dirfd, path, oflag, mode); +} + +SPA_EXPORT int __openat_2(int dirfd, const char *path, int oflag) +{ + return openat(dirfd, path, oflag); +} + +#ifndef openat64 +SPA_EXPORT int openat64(int dirfd, const char *path, int oflag, ...) +{ + mode_t mode = 0; + if (oflag & O_CREAT || oflag & O_TMPFILE) + extract_va_arg(mode_t, mode, oflag); + + return fops.openat(dirfd, path, oflag | O_LARGEFILE, mode); +} + +SPA_EXPORT int __openat64_2(int dirfd, const char *path, int oflag) +{ + return openat(dirfd, path, oflag); +} +#endif + +SPA_EXPORT int dup(int oldfd) +{ + return fops.dup(oldfd); +} + +SPA_EXPORT int close(int fd) +{ + return fops.close(fd); +} + +SPA_EXPORT void *mmap(void *addr, size_t length, int prot, int flags, + int fd, off_t offset) +{ + return fops.mmap(addr, length, prot, flags, fd, offset); +} + +#ifndef mmap64 +SPA_EXPORT void *mmap64(void *addr, size_t length, int prot, int flags, + int fd, off64_t offset) +{ + return fops.mmap(addr, length, prot, flags, fd, offset); +} +#endif + +SPA_EXPORT int munmap(void *addr, size_t length) +{ + return fops.munmap(addr, length); +} + +SPA_EXPORT int ioctl(int fd, unsigned long int request, ...) +{ + void *arg; + extract_va_arg(void *, arg, request); + return fops.ioctl(fd, request, arg); +}