mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-28 05:40:26 -04:00
pipewire-v4l2: the start of a v4l2 LD_PRELOAD library
This commit is contained in:
parent
57b3fe537e
commit
e68fd36f90
8 changed files with 910 additions and 0 deletions
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
1
pipewire-v4l2/meson.build
Normal file
1
pipewire-v4l2/meson.build
Normal file
|
|
@ -0,0 +1 @@
|
|||
subdir('src')
|
||||
36
pipewire-v4l2/src/meson.build
Normal file
36
pipewire-v4l2/src/meson.build
Normal file
|
|
@ -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,
|
||||
)
|
||||
623
pipewire-v4l2/src/pipewire-v4l2.c
Normal file
623
pipewire-v4l2/src/pipewire-v4l2.c
Normal file
|
|
@ -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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <pthread.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "pipewire-v4l2.h"
|
||||
|
||||
#include <spa/utils/result.h>
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
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);
|
||||
}
|
||||
38
pipewire-v4l2/src/pipewire-v4l2.h
Normal file
38
pipewire-v4l2/src/pipewire-v4l2.h
Normal file
|
|
@ -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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
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;
|
||||
65
pipewire-v4l2/src/pw-v4l2.in
Executable file
65
pipewire-v4l2/src/pw-v4l2.in
Executable file
|
|
@ -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> 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 "$@"
|
||||
140
pipewire-v4l2/src/v4l2-func.c
Normal file
140
pipewire-v4l2/src/v4l2-func.c
Normal file
|
|
@ -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 <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue