pipewire/pipewire-v4l2/src/pipewire-v4l2.c
Wim Taymans 3905e3b3d3 stream: set errno to the current error
Make the state_changed event and _get_state() function set errno with
the current error value if the state is in error, so that application
can use this to give more detailed error reporting.

Use this in alsa, v4l2 and pulse to give some other error codes than
EIO.

Fixes #4574
2025-03-04 13:22:51 +01:00

2580 lines
62 KiB
C

/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <limits.h>
#include <linux/videodev2.h>
#include "pipewire-v4l2.h"
#include <spa/utils/atomic.h>
#include <spa/utils/result.h>
#include <spa/utils/keys.h>
#include <spa/pod/iter.h>
#include <spa/pod/parser.h>
#include <spa/pod/filter.h>
#include <spa/param/video/format-utils.h>
#include <pipewire/pipewire.h>
PW_LOG_TOPIC_STATIC(v4l2_log_topic, "v4l2");
#define PW_LOG_TOPIC_DEFAULT v4l2_log_topic
#define MIN_BUFFERS 2u
#define MAX_BUFFERS 32u
#define DEFAULT_TIMEOUT 30
#define DEFAULT_DRIVER "PipeWire"
#define DEFAULT_CARD "PipeWire Camera"
#define DEFAULT_BUS_INFO "PipeWire"
#define MAX_DEV 32
struct file_map {
void *addr;
struct file *file;
};
struct fd_map {
int fd;
#define FD_MAP_DUP (1<<0)
uint32_t flags;
struct file *file;
};
struct globals {
struct fops old_fops;
pthread_mutex_t lock;
struct pw_array fd_maps;
struct pw_array file_maps;
uint32_t dev_map[MAX_DEV];
};
static struct globals globals;
struct global;
struct buffer_map {
void *addr;
uint32_t id;
};
struct buffer {
struct v4l2_buffer v4l2;
struct pw_buffer *buf;
uint32_t id;
};
struct file {
int ref;
uint32_t dev_id;
uint32_t serial;
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 last_seq;
int pending_seq;
int error;
struct pw_registry *registry;
struct spa_hook registry_listener;
struct spa_list globals;
struct global *node;
struct pw_stream *stream;
struct spa_hook stream_listener;
enum v4l2_priority priority;
struct v4l2_format v4l2_format;
uint32_t reqbufs;
int reqbufs_fd;
struct buffer buffers[MAX_BUFFERS];
uint32_t n_buffers;
uint32_t size;
uint32_t sequence;
struct pw_array buffer_maps;
uint32_t last_fourcc;
unsigned int running:1;
unsigned int closed:1;
int fd;
};
struct global_info {
const char *type;
uint32_t version;
const void *events;
pw_destroy_t destroy;
int (*init) (struct global *g);
};
struct global {
struct spa_list link;
struct file *file;
const struct global_info *ginfo;
uint32_t id;
uint32_t permissions;
struct pw_properties *props;
struct pw_proxy *proxy;
struct spa_hook proxy_listener;
struct spa_hook object_listener;
int changed;
void *info;
struct spa_list pending_list;
struct spa_list param_list;
union {
struct {
#define NODE_FLAG_SOURCE (1<<0)
#define NODE_FLAG_SINK (1<<0)
uint32_t flags;
uint32_t device_id;
int priority;
} node;
};
};
struct param {
struct spa_list link;
uint32_t id;
int32_t seq;
struct spa_pod *param;
};
static uint32_t clear_params(struct spa_list *param_list, uint32_t id)
{
struct param *p, *t;
uint32_t count = 0;
spa_list_for_each_safe(p, t, param_list, link) {
if (id == SPA_ID_INVALID || p->id == id) {
spa_list_remove(&p->link);
free(p);
count++;
}
}
return count;
}
static struct param *add_param(struct spa_list *params,
int seq, uint32_t id, const struct spa_pod *param)
{
struct param *p;
if (id == SPA_ID_INVALID) {
if (param == NULL || !spa_pod_is_object(param)) {
errno = EINVAL;
return NULL;
}
id = SPA_POD_OBJECT_ID(param);
}
p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0));
if (p == NULL)
return NULL;
p->id = id;
p->seq = seq;
if (param != NULL) {
p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod);
memcpy(p->param, param, SPA_POD_SIZE(param));
} else {
clear_params(params, id);
p->param = NULL;
}
spa_list_append(params, &p->link);
return p;
}
static void update_params(struct file *file)
{
struct param *p, *t;
struct global *node;
struct pw_node_info *info;
uint32_t i;
if ((node = file->node) == NULL)
return;
if ((info = node->info) == NULL)
return;
for (i = 0; i < info->n_params; i++) {
spa_list_for_each_safe(p, t, &node->pending_list, link) {
if (p->id == info->params[i].id &&
p->seq != info->params[i].seq &&
p->param != NULL) {
spa_list_remove(&p->link);
free(p);
}
}
}
spa_list_consume(p, &node->pending_list, link) {
spa_list_remove(&p->link);
if (p->param == NULL) {
clear_params(&node->param_list, p->id);
free(p);
} else {
spa_list_append(&node->param_list, &p->link);
}
}
}
static struct file *make_file(void)
{
struct file *file;
file = calloc(1, sizeof(*file));
if (file == NULL)
return NULL;
file->ref = 1;
file->fd = -1;
file->reqbufs_fd = -1;
file->priority = V4L2_PRIORITY_DEFAULT;
spa_list_init(&file->globals);
pw_array_init(&file->buffer_maps, sizeof(struct buffer_map) * MAX_BUFFERS);
return file;
}
static void free_file(struct file *file)
{
pw_log_info("file:%d", file->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->stream) {
spa_hook_remove(&file->stream_listener);
pw_stream_destroy(file->stream);
}
if (file->core) {
spa_hook_remove(&file->core_listener);
pw_core_disconnect(file->core);
}
if (file->context)
pw_context_destroy(file->context);
if (file->fd != -1)
spa_system_close(file->l->system, file->fd);
if (file->loop)
pw_thread_loop_destroy(file->loop);
pw_array_clear(&file->buffer_maps);
free(file);
}
static void unref_file(struct file *file)
{
pw_log_debug("file:%d ref:%d", file->fd, file->ref);
if (SPA_ATOMIC_DEC(file->ref) <= 0)
free_file(file);
}
static int add_fd_map(int fd, struct file *file, uint32_t flags)
{
struct fd_map *map;
pthread_mutex_lock(&globals.lock);
map = pw_array_add(&globals.fd_maps, sizeof(*map));
if (map != NULL) {
map->fd = fd;
map->flags = flags;
map->file = file;
SPA_ATOMIC_INC(file->ref);
pw_log_debug("fd:%d -> file:%d ref:%d", fd, file->fd, file->ref);
}
pthread_mutex_unlock(&globals.lock);
return 0;
}
static uint32_t find_dev_for_serial(uint32_t serial)
{
uint32_t i, res = SPA_ID_INVALID;
pthread_mutex_lock(&globals.lock);
for (i = 0; i < SPA_N_ELEMENTS(globals.dev_map); i++) {
if (globals.dev_map[i] == serial) {
res = i;
break;
}
}
pthread_mutex_unlock(&globals.lock);
return res;
}
static bool add_dev_for_serial(uint32_t dev, uint32_t serial)
{
pthread_mutex_lock(&globals.lock);
globals.dev_map[dev] = serial;
pthread_mutex_unlock(&globals.lock);
return true;
}
/* must be called with `globals.lock` held */
static struct fd_map *find_fd_map_unlocked(int fd)
{
struct fd_map *map;
pw_array_for_each(map, &globals.fd_maps) {
if (map->fd == fd) {
SPA_ATOMIC_INC(map->file->ref);
pw_log_debug("fd:%d find:%d ref:%d", map->fd, fd, map->file->ref);
return map;
}
}
return NULL;
}
static struct file *find_file(int fd, uint32_t *flags)
{
pthread_mutex_lock(&globals.lock);
struct fd_map *map = find_fd_map_unlocked(fd);
struct file *file = NULL;
if (map != NULL) {
file = map->file;
*flags = map->flags;
}
pthread_mutex_unlock(&globals.lock);
return file;
}
static struct file *find_file_by_dev(uint32_t dev)
{
struct fd_map *map = NULL, *tmp;
struct file *file = NULL;
pthread_mutex_lock(&globals.lock);
pw_array_for_each(tmp, &globals.fd_maps) {
if (tmp->file->dev_id == dev) {
if (tmp->file->closed)
tmp->file->fd = tmp->fd;
SPA_ATOMIC_INC(tmp->file->ref);
map = tmp;
pw_log_debug("dev:%d find:%d ref:%d",
tmp->file->dev_id, dev, tmp->file->ref);
break;
}
}
if (map != NULL)
file = map->file;
pthread_mutex_unlock(&globals.lock);
return file;
}
static struct file *remove_fd_map(int fd)
{
pthread_mutex_lock(&globals.lock);
struct fd_map *map = find_fd_map_unlocked(fd);
struct file *file = NULL;
if (map != NULL) {
file = map->file;
pw_log_debug("fd:%d find:%d", map->fd, fd);
pw_array_remove(&globals.fd_maps, map);
}
pthread_mutex_unlock(&globals.lock);
if (file != NULL)
unref_file(file);
return file;
}
static int add_file_map(struct file *file, void *addr)
{
struct file_map *map;
pthread_mutex_lock(&globals.lock);
map = pw_array_add(&globals.file_maps, sizeof(*map));
if (map != NULL) {
map->addr = addr;
map->file = file;
}
pthread_mutex_unlock(&globals.lock);
return 0;
}
/* must be called with `globals.lock` held */
static struct file_map *find_file_map_unlocked(void *addr)
{
struct file_map *map;
pw_array_for_each(map, &globals.file_maps) {
if (map->addr == addr)
return map;
}
return NULL;
}
static struct file *remove_file_map(void *addr)
{
pthread_mutex_lock(&globals.lock);
struct file_map *map = find_file_map_unlocked(addr);
struct file *file = NULL;
if (map != NULL) {
file = map->file;
pw_array_remove(&globals.file_maps, map);
}
pthread_mutex_unlock(&globals.lock);
return file;
}
static int add_buffer_map(struct file *file, void *addr, uint32_t id)
{
struct buffer_map *map;
map = pw_array_add(&file->buffer_maps, sizeof(*map));
if (map != NULL) {
map->addr = addr;
map->id = id;
}
return 0;
}
static struct buffer_map *find_buffer_map(struct file *file, void *addr)
{
struct buffer_map *map;
pw_array_for_each(map, &file->buffer_maps) {
if (map->addr == addr)
return map;
}
return NULL;
}
static void remove_buffer_map(struct file *file, struct buffer_map *map)
{
pw_array_remove(&file->buffer_maps, map);
}
static void do_resync(struct file *file)
{
file->pending_seq = pw_core_sync(file->core, PW_ID_CORE, file->pending_seq);
}
static int wait_resync(struct file *file)
{
int res;
do_resync(file);
while (true) {
pw_thread_loop_wait(file->loop);
res = file->error;
if (res < 0) {
file->error = 0;
return res;
}
if (file->pending_seq == file->last_seq)
break;
}
return 0;
}
static void on_sync_reply(void *data, uint32_t id, int seq)
{
struct file *file = data;
if (id != PW_ID_CORE)
return;
file->last_seq = seq;
if (file->pending_seq == seq) {
update_params(file);
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("file:%d: error id:%u seq:%d res:%d (%s): %s", file->fd,
id, seq, res, spa_strerror(res), message);
if (id == PW_ID_CORE) {
switch (res) {
case -ENOENT:
break;
default:
file->error = 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,
};
/** node */
static void node_event_info(void *object, const struct pw_node_info *info)
{
struct global *g = object;
struct file *file = g->file;
const char *str;
uint32_t i;
info = g->info = pw_node_info_merge(g->info, info, g->changed == 0);
if (info == NULL)
return;
pw_log_debug("update %d %"PRIu64, g->id, info->change_mask);
if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS && info->props) {
if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)))
g->node.device_id = atoi(str);
else
g->node.device_id = SPA_ID_INVALID;
if ((str = spa_dict_lookup(info->props, PW_KEY_PRIORITY_SESSION)))
g->node.priority = atoi(str);
if ((str = spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS))) {
if (spa_streq(str, "Video/Sink"))
g->node.flags |= NODE_FLAG_SINK;
else if (spa_streq(str, "Video/Source"))
g->node.flags |= NODE_FLAG_SOURCE;
}
}
if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
for (i = 0; i < info->n_params; i++) {
uint32_t id = info->params[i].id;
int res;
if (info->params[i].user == 0)
continue;
info->params[i].user = 0;
add_param(&g->pending_list, info->params[i].seq, id, NULL);
if (!(info->params[i].flags & SPA_PARAM_INFO_READ))
continue;
res = pw_node_enum_params((struct pw_node*)g->proxy,
++info->params[i].seq, id, 0, -1, NULL);
if (SPA_RESULT_IS_ASYNC(res))
info->params[i].seq = res;
}
}
do_resync(file);
}
static void node_event_param(void *object, int seq,
uint32_t id, uint32_t index, uint32_t next,
const struct spa_pod *param)
{
struct global *g = object;
pw_log_debug("update param %d %d %d", g->id, id, seq);
add_param(&g->pending_list, seq, id, param);
}
static const struct pw_node_events node_events = {
PW_VERSION_NODE_EVENTS,
.info = node_event_info,
.param = node_event_param,
};
static const struct global_info node_info = {
.type = PW_TYPE_INTERFACE_Node,
.version = PW_VERSION_NODE,
.events = &node_events,
};
/** proxy */
static void proxy_removed(void *data)
{
struct global *g = data;
pw_proxy_destroy(g->proxy);
}
static void proxy_destroy(void *data)
{
struct global *g = data;
spa_list_remove(&g->link);
g->proxy = NULL;
if (g->file)
g->file->node = NULL;
clear_params(&g->param_list, SPA_ID_INVALID);
clear_params(&g->pending_list, SPA_ID_INVALID);
}
static const struct pw_proxy_events proxy_events = {
PW_VERSION_PROXY_EVENTS,
.removed = proxy_removed,
.destroy = proxy_destroy
};
static void registry_event_global(void *data, uint32_t id,
uint32_t permissions, const char *type, uint32_t version,
const struct spa_dict *props)
{
struct file *file = data;
const struct global_info *info = NULL;
struct pw_proxy *proxy;
const char *str;
uint32_t serial = SPA_ID_INVALID, dev, req_serial;
if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
if (file->node != NULL)
return;
pw_log_info("got %d %s", id, type);
if (props == NULL)
return;
if (((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) == NULL) ||
((!spa_streq(str, "Video/Sink")) &&
(!spa_streq(str, "Video/Source"))))
return;
if (((str = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL)) == NULL) ||
!spa_atou32(str, &serial, 10))
return;
if ((str = getenv("PIPEWIRE_V4L2_TARGET")) != NULL
&& spa_atou32(str, &req_serial, 10)
&& req_serial != serial)
return;
dev = find_dev_for_serial(serial);
if (dev != SPA_ID_INVALID && dev != file->dev_id)
return;
pw_log_info("found node:%d serial:%d type:%s", id, serial, str);
info = &node_info;
}
if (info) {
struct global *g;
proxy = pw_registry_bind(file->registry,
id, info->type, info->version,
sizeof(struct global));
g = pw_proxy_get_user_data(proxy);
g->file = file;
g->ginfo = info;
g->id = id;
g->permissions = permissions;
g->props = props ? pw_properties_new_dict(props) : NULL;
g->proxy = proxy;
spa_list_init(&g->pending_list);
spa_list_init(&g->param_list);
spa_list_append(&file->globals, &g->link);
pw_proxy_add_listener(proxy,
&g->proxy_listener,
&proxy_events, g);
if (info->events) {
pw_proxy_add_object_listener(proxy,
&g->object_listener,
info->events, g);
}
if (info->init)
info->init(g);
file->serial = serial;
file->node = g;
do_resync(file);
}
}
static struct global *find_global(struct file *file, uint32_t id)
{
struct global *g;
spa_list_for_each(g, &file->globals, link) {
if (g->id == id)
return g;
}
return NULL;
}
static void registry_event_global_remove(void *data, uint32_t id)
{
struct file *file = data;
struct global *g;
if ((g = find_global(file, id)) == NULL)
return;
pw_proxy_destroy(g->proxy);
}
static const struct pw_registry_events registry_events = {
PW_VERSION_REGISTRY_EVENTS,
.global = registry_event_global,
.global_remove = registry_event_global_remove,
};
static int do_dup(int oldfd, uint32_t flags)
{
int res;
struct file *file;
uint32_t fl;
res = globals.old_fops.dup(oldfd);
if (res < 0)
return res;
if ((file = find_file(oldfd, &fl)) != NULL) {
add_fd_map(res, file, flags | fl);
unref_file(file);
pw_log_info("fd:%d %08x -> %d (%s)", oldfd, flags,
res, strerror(res < 0 ? errno : 0));
}
return res;
}
static int v4l2_dup(int oldfd)
{
return do_dup(oldfd, FD_MAP_DUP);
}
static int v4l2_openat(int dirfd, const char *path, int oflag, mode_t mode)
{
int res, flags;
struct file *file;
bool passthrough = true;
uint32_t dev_id = SPA_ID_INVALID;
char *real_path;
real_path = realpath(path, NULL);
if (!real_path)
real_path = (char *)path;
if (spa_strstartswith(real_path, "/dev/video")) {
if (spa_atou32(real_path+10, &dev_id, 10) && dev_id < MAX_DEV)
passthrough = false;
}
if (real_path && real_path != path)
free(real_path);
if (passthrough)
return globals.old_fops.openat(dirfd, path, oflag, mode);
pw_log_info("path:%s oflag:%d mode:%d", path, oflag, mode);
if ((file = find_file_by_dev(dev_id)) != NULL) {
res = do_dup(file->fd, 0);
unref_file(file);
if (res < 0)
return res;
if (fcntl(res, F_SETFL, oflag) < 0)
pw_log_warn("fd:%d failed to set flags: %m", res);
return res;
}
if ((file = make_file()) == NULL)
goto error;
file->dev_id = dev_id;
file->props = pw_properties_new(
PW_KEY_CLIENT_API, "v4l2",
NULL);
file->loop = pw_thread_loop_new("v4l2", NULL);
if (file->loop == NULL)
goto error;
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)
goto error;
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)
goto error_unlock;
pw_core_add_listener(file->core,
&file->core_listener,
&core_events, file);
file->registry = pw_core_get_registry(file->core,
PW_VERSION_REGISTRY, 0);
if (file->registry == NULL)
goto error_unlock;
pw_registry_add_listener(file->registry,
&file->registry_listener,
&registry_events, file);
res = wait_resync(file);
if (res < 0) {
errno = -res;
goto error_unlock;
}
if (file->node == NULL) {
errno = ENOENT;
goto error_unlock;
}
pw_thread_loop_unlock(file->loop);
flags = SPA_FD_CLOEXEC;
if (oflag & O_NONBLOCK)
flags |= SPA_FD_NONBLOCK;
res = spa_system_eventfd_create(file->l->system, flags);
if (res < 0)
goto error;
file->fd = res;
pw_log_info("path:%s oflag:%d mode:%d -> %d (%s)", path, oflag, mode,
res, strerror(res < 0 ? errno : 0));
add_fd_map(res, file, 0);
add_dev_for_serial(file->dev_id, file->serial);
unref_file(file);
return res;
error_unlock:
pw_thread_loop_unlock(file->loop);
error:
res = -errno;
if (file)
free_file(file);
pw_log_info("path:%s oflag:%d mode:%d -> %d (%s)", path, oflag, mode,
-1, spa_strerror(res));
errno = -res;
return -1;
}
static int v4l2_close(int fd)
{
struct file *file;
if ((file = remove_fd_map(fd)) == NULL)
return globals.old_fops.close(fd);
pw_log_info("fd:%d file:%d", fd, file->fd);
if (fd != file->fd)
spa_system_close(file->l->system, fd);
file->closed = true;
unref_file(file);
return 0;
}
#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;
const char *card = NULL, *bus_info = NULL;
struct pw_node_info *info;
if (file->node == NULL)
return -EIO;
info = file->node->info;
if (info != NULL && info->props != NULL) {
card = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION);
bus_info = spa_dict_lookup(info->props, SPA_KEY_API_V4L2_CAP_BUS_INFO);
}
if (card == NULL)
card = DEFAULT_CARD;
spa_scnprintf((char*)arg->driver, sizeof(arg->driver), "%s", DEFAULT_DRIVER);
spa_scnprintf((char*)arg->card, sizeof(arg->card), "%s", card);
if (bus_info == NULL)
spa_scnprintf((char*)arg->bus_info, sizeof(arg->bus_info), "platform:%s-%d",
DEFAULT_BUS_INFO, file->node->id);
else
spa_scnprintf((char*)arg->bus_info, sizeof(arg->bus_info), "%s", bus_info);
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:%d -> %d (%s)", file->fd, res, spa_strerror(res));
return res;
}
struct format_info {
uint32_t fourcc;
uint32_t media_type;
uint32_t media_subtype;
uint32_t format;
uint32_t bpp;
const char *desc;
};
#define MAKE_FORMAT(fcc,mt,mst,bpp,fmt,...) \
{ V4L2_PIX_FMT_ ## fcc, SPA_MEDIA_TYPE_ ## mt, SPA_MEDIA_SUBTYPE_ ## mst, SPA_VIDEO_FORMAT_ ## fmt, bpp, __VA_ARGS__ }
static const struct format_info format_info[] = {
/* RGB formats */
MAKE_FORMAT(RGB332, video, raw, 4, UNKNOWN, "8-bit RGB 3-3-2"),
MAKE_FORMAT(ARGB555, video, raw, 4, UNKNOWN),
MAKE_FORMAT(XRGB555, video, raw, 4, RGB15, "16-bit XRGB 1-5-5-5"),
MAKE_FORMAT(ARGB555X, video, raw, 4, UNKNOWN),
MAKE_FORMAT(XRGB555X, video, raw, 4, BGR15, "16-bit XRGB 1-5-5-5 BE"),
MAKE_FORMAT(RGB565, video, raw, 4, RGB16, "16-bit RGB 5-6-5"),
MAKE_FORMAT(RGB565X, video, raw, 4, UNKNOWN),
MAKE_FORMAT(BGR666, video, raw, 4, UNKNOWN),
MAKE_FORMAT(BGR24, video, raw, 4, BGR),
MAKE_FORMAT(RGB24, video, raw, 4, RGB),
MAKE_FORMAT(ABGR32, video, raw, 4, BGRA),
MAKE_FORMAT(XBGR32, video, raw, 4, BGRx),
MAKE_FORMAT(ARGB32, video, raw, 4, ARGB),
MAKE_FORMAT(XRGB32, video, raw, 4, xRGB),
/* Deprecated Packed RGB Image Formats (alpha ambiguity) */
MAKE_FORMAT(RGB444, video, raw, 2, UNKNOWN),
MAKE_FORMAT(RGB555, video, raw, 2, RGB15),
MAKE_FORMAT(RGB555X, video, raw, 2, BGR15),
MAKE_FORMAT(BGR32, video, raw, 4, BGRx),
MAKE_FORMAT(RGB32, video, raw, 4, xRGB),
/* Grey formats */
MAKE_FORMAT(GREY, video, raw, 1, GRAY8),
MAKE_FORMAT(Y4, video, raw, 1, UNKNOWN),
MAKE_FORMAT(Y6, video, raw, 1, UNKNOWN),
MAKE_FORMAT(Y10, video, raw, 2, UNKNOWN),
MAKE_FORMAT(Y12, video, raw, 2, UNKNOWN),
MAKE_FORMAT(Y16, video, raw, 2, GRAY16_LE),
#ifdef V4L2_PIX_FMT_Y16_BE
MAKE_FORMAT(Y16_BE, video, raw, 2, GRAY16_BE),
#endif
MAKE_FORMAT(Y10BPACK, video, raw, 2, UNKNOWN),
/* Palette formats */
MAKE_FORMAT(PAL8, video, raw, 1, UNKNOWN),
/* Chrominance formats */
MAKE_FORMAT(UV8, video, raw, 2, UNKNOWN),
/* Luminance+Chrominance formats */
MAKE_FORMAT(YVU410, video, raw, 1, YVU9, "Planar YVU 4:1:0"),
MAKE_FORMAT(YVU420, video, raw, 1, YV12, "Planar YVU 4:2:0"),
MAKE_FORMAT(YVU420M, video, raw, 1, UNKNOWN),
MAKE_FORMAT(YUYV, video, raw, 2, YUY2, "YUYV 4:2:2"),
MAKE_FORMAT(YYUV, video, raw, 2, UNKNOWN),
MAKE_FORMAT(YVYU, video, raw, 2, YVYU, "YVYU 4:2:2"),
MAKE_FORMAT(UYVY, video, raw, 2, UYVY, "UYVY 4:2:2"),
MAKE_FORMAT(VYUY, video, raw, 2, UNKNOWN),
MAKE_FORMAT(YUV422P, video, raw, 1, Y42B),
MAKE_FORMAT(YUV411P, video, raw, 1, Y41B),
MAKE_FORMAT(Y41P, video, raw, 1, UNKNOWN),
MAKE_FORMAT(YUV444, video, raw, 1, UNKNOWN),
MAKE_FORMAT(YUV555, video, raw, 1, UNKNOWN),
MAKE_FORMAT(YUV565, video, raw, 1, UNKNOWN),
MAKE_FORMAT(YUV32, video, raw, 1, UNKNOWN),
MAKE_FORMAT(YUV410, video, raw, 1, YUV9),
MAKE_FORMAT(YUV420, video, raw, 1, I420, "Planar YUV 4:2:0"),
MAKE_FORMAT(YUV420M, video, raw, 1, I420, "Planar YUV 4:2:0 (N-C)"),
MAKE_FORMAT(HI240, video, raw, 1, UNKNOWN),
MAKE_FORMAT(HM12, video, raw, 1, UNKNOWN),
MAKE_FORMAT(M420, video, raw, 1, UNKNOWN),
/* two planes -- one Y, one Cr + Cb interleaved */
MAKE_FORMAT(NV12, video, raw, 1, NV12, "Y/CbCr 4:2:0"),
MAKE_FORMAT(NV12M, video, raw, 1, NV12, "Y/CbCr 4:2:0 (N-C)"),
MAKE_FORMAT(NV12MT, video, raw, 1, NV12_64Z32, "Y/CbCr 4:2:0 (64x32 MB, N-C)"),
MAKE_FORMAT(NV12MT_16X16, video, raw, 1, UNKNOWN),
MAKE_FORMAT(NV21, video, raw, 1, NV21, "Y/CrCb 4:2:0"),
MAKE_FORMAT(NV21M, video, raw, 1, NV21, "Y/CrCb 4:2:0 (N-C)"),
MAKE_FORMAT(NV16, video, raw, 1, NV16, "Y/CbCr 4:2:2"),
MAKE_FORMAT(NV16M, video, raw, 1, NV16, "Y/CbCr 4:2:2 (N-C)"),
MAKE_FORMAT(NV61, video, raw, 1, NV61, "Y/CrCb 4:2:2"),
MAKE_FORMAT(NV61M, video, raw, 1, NV61, "Y/CrCb 4:2:2 (N-C)"),
MAKE_FORMAT(NV24, video, raw, 1, NV24, "Y/CbCr 4:4:4"),
MAKE_FORMAT(NV42, video, raw, 1, UNKNOWN),
/* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */
MAKE_FORMAT(SBGGR8, video, bayer, 1, UNKNOWN),
MAKE_FORMAT(SGBRG8, video, bayer, 1, UNKNOWN),
MAKE_FORMAT(SGRBG8, video, bayer, 1, UNKNOWN),
MAKE_FORMAT(SRGGB8, video, bayer, 1, UNKNOWN),
/* compressed formats */
MAKE_FORMAT(MJPEG, video, mjpg, 1, ENCODED, "Motion-JPEG"),
MAKE_FORMAT(JPEG, video, mjpg, 1, ENCODED, "JFIF JPEG"),
MAKE_FORMAT(PJPG, video, mjpg, 1, ENCODED, "GSPCA PJPG"),
MAKE_FORMAT(DV, video, dv, 1, ENCODED),
MAKE_FORMAT(MPEG, video, mpegts, 1, ENCODED),
MAKE_FORMAT(H264, video, h264, 1, ENCODED, "H.264"),
MAKE_FORMAT(H264_NO_SC, video, h264, 1, ENCODED, "H.264 (No Start Codes)"),
MAKE_FORMAT(H264_MVC, video, h264, 1, ENCODED, "H.264 MVC"),
MAKE_FORMAT(H263, video, h263, 1, ENCODED),
MAKE_FORMAT(MPEG1, video, mpeg1, 1, ENCODED),
MAKE_FORMAT(MPEG2, video, mpeg2, 1, ENCODED),
MAKE_FORMAT(MPEG4, video, mpeg4, 1, ENCODED),
MAKE_FORMAT(XVID, video, xvid, 1, ENCODED),
MAKE_FORMAT(VC1_ANNEX_G, video, vc1, 1, ENCODED),
MAKE_FORMAT(VC1_ANNEX_L, video, vc1, 1, ENCODED),
MAKE_FORMAT(VP8, video, vp8, 1, ENCODED),
/* Vendor-specific formats */
MAKE_FORMAT(WNVA, video, raw, 1, UNKNOWN),
MAKE_FORMAT(SN9C10X, video, raw, 1, UNKNOWN),
MAKE_FORMAT(PWC1, video, raw, 1, UNKNOWN),
MAKE_FORMAT(PWC2, video, raw, 1, UNKNOWN),
};
static const struct format_info *format_info_from_media_type(uint32_t type,
uint32_t subtype, uint32_t format)
{
SPA_FOR_EACH_ELEMENT_VAR(format_info, i)
if ((i->media_type == type) &&
(i->media_subtype == subtype) &&
(format == 0 || i->format == format))
return i;
return NULL;
}
static const struct format_info *format_info_from_fourcc(uint32_t fourcc)
{
SPA_FOR_EACH_ELEMENT_VAR(format_info, i)
if (i->fourcc == fourcc)
return i;
return NULL;
}
static int format_to_info(const struct v4l2_format *arg, struct spa_video_info *info)
{
const struct format_info *fi;
pw_log_info("type: %u", arg->type);
pw_log_info("width: %u", arg->fmt.pix.width);
pw_log_info("height: %u", arg->fmt.pix.height);
pw_log_info("fmt: %.4s", (char*)&arg->fmt.pix.pixelformat);
if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
fi = format_info_from_fourcc(arg->fmt.pix.pixelformat);
if (fi == NULL)
return -EINVAL;
spa_zero(*info);
info->media_type = fi->media_type;
info->media_subtype = fi->media_subtype;
switch (info->media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
info->info.raw.format = fi->format;
info->info.raw.size.width = arg->fmt.pix.width;
info->info.raw.size.height = arg->fmt.pix.height;
break;
case SPA_MEDIA_SUBTYPE_h264:
info->info.h264.size.width = arg->fmt.pix.width;
info->info.h264.size.height = arg->fmt.pix.height;
break;
case SPA_MEDIA_SUBTYPE_mjpg:
case SPA_MEDIA_SUBTYPE_jpeg:
info->info.mjpg.size.width = arg->fmt.pix.width;
info->info.mjpg.size.height = arg->fmt.pix.height;
break;
default:
return -EINVAL;
}
return 0;
}
static struct spa_pod *info_to_param(struct spa_pod_builder *builder, uint32_t id,
struct spa_video_info *info)
{
struct spa_pod *pod;
if (info->media_type != SPA_MEDIA_TYPE_video)
return NULL;
switch (info->media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
pod = spa_format_video_raw_build(builder, id, &info->info.raw);
break;
case SPA_MEDIA_SUBTYPE_mjpg:
case SPA_MEDIA_SUBTYPE_jpeg:
pod = spa_format_video_mjpg_build(builder, id, &info->info.mjpg);
break;
case SPA_MEDIA_SUBTYPE_h264:
pod = spa_format_video_h264_build(builder, id, &info->info.h264);
break;
default:
return NULL;
}
return pod;
}
static struct spa_pod *fmt_to_param(struct spa_pod_builder *builder, uint32_t id,
const struct v4l2_format *fmt)
{
struct spa_video_info info;
if (format_to_info(fmt, &info) < 0)
return NULL;
return info_to_param(builder, id, &info);
}
static int param_to_info(const struct spa_pod *param, struct spa_video_info *info)
{
int res;
spa_zero(*info);
if (spa_format_parse(param, &info->media_type, &info->media_subtype) < 0)
return -EINVAL;
if (info->media_type != SPA_MEDIA_TYPE_video)
return -EINVAL;
switch (info->media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
res = spa_format_video_raw_parse(param, &info->info.raw);
break;
case SPA_MEDIA_SUBTYPE_h264:
res = spa_format_video_h264_parse(param, &info->info.h264);
break;
case SPA_MEDIA_SUBTYPE_mjpg:
case SPA_MEDIA_SUBTYPE_jpeg:
res = spa_format_video_mjpg_parse(param, &info->info.mjpg);
break;
default:
return -EINVAL;
}
return res;
}
static int info_to_fmt(const struct spa_video_info *info, struct v4l2_format *fmt)
{
const struct format_info *fi;
uint32_t format;
if (info->media_type != SPA_MEDIA_TYPE_video)
return -EINVAL;
if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) {
format = info->info.raw.format;
} else {
format = SPA_VIDEO_FORMAT_ENCODED;
}
fi = format_info_from_media_type(info->media_type, info->media_subtype,
format);
if (fi == NULL)
return -EINVAL;
spa_zero(*fmt);
fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt->fmt.pix.pixelformat = fi->fourcc;
fmt->fmt.pix.field = V4L2_FIELD_NONE;
switch (info->media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
fmt->fmt.pix.width = info->info.raw.size.width;
fmt->fmt.pix.height = info->info.raw.size.height;
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
break;
case SPA_MEDIA_SUBTYPE_mjpg:
case SPA_MEDIA_SUBTYPE_jpeg:
fmt->fmt.pix.width = info->info.mjpg.size.width;
fmt->fmt.pix.height = info->info.mjpg.size.height;
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG;
break;
case SPA_MEDIA_SUBTYPE_h264:
fmt->fmt.pix.width = info->info.h264.size.width;
fmt->fmt.pix.height = info->info.h264.size.height;
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
break;
default:
return -EINVAL;
}
if (fmt->fmt.pix.width == 0 ||
fmt->fmt.pix.height == 0)
return -EINVAL;
fmt->fmt.pix.bytesperline = SPA_ROUND_UP_N(fmt->fmt.pix.width, 4) * fi->bpp;
fmt->fmt.pix.sizeimage = fmt->fmt.pix.bytesperline *
SPA_ROUND_UP_N(fmt->fmt.pix.height, 2);
return 0;
}
static int param_to_fmt(const struct spa_pod *param, struct v4l2_format *fmt)
{
struct spa_video_info info;
struct spa_pod *copy;
int res;
copy = spa_pod_copy(param);
spa_pod_fixate(copy);
res = param_to_info(copy, &info);
free(copy);
if (res < 0)
return -EINVAL;
if (info_to_fmt(&info, fmt) < 0)
return -EINVAL;
return 0;
}
static void on_stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
{
struct file *file = data;
const struct spa_pod *params[4];
uint32_t n_params = 0;
uint8_t buffer[4096];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
uint32_t buffers, size, stride;
struct v4l2_format fmt;
if (param == NULL || id != SPA_PARAM_Format)
return;
if (param_to_fmt(param, &fmt) < 0)
return;
file->v4l2_format = fmt;
buffers = SPA_CLAMP(file->reqbufs, 1u, MAX_BUFFERS);
size = fmt.fmt.pix.sizeimage;
stride = fmt.fmt.pix.bytesperline;
params[n_params++] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers,
1, MAX_BUFFERS),
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, 0, INT_MAX),
SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(stride, 0, INT_MAX),
SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<<SPA_DATA_MemFd)));
pw_stream_update_params(file->stream, params, n_params);
}
static void on_stream_state_changed(void *data, enum pw_stream_state old,
enum pw_stream_state state, const char *error)
{
struct file *file = data;
pw_log_info("file:%d: state %s", file->fd, pw_stream_state_as_string(state));
switch (state) {
case PW_STREAM_STATE_ERROR:
break;
case PW_STREAM_STATE_UNCONNECTED:
break;
case PW_STREAM_STATE_CONNECTING:
case PW_STREAM_STATE_PAUSED:
case PW_STREAM_STATE_STREAMING:
break;
}
pw_thread_loop_signal(file->loop, false);
}
static void on_stream_add_buffer(void *data, struct pw_buffer *b)
{
struct file *file = data;
uint32_t id = file->n_buffers;
struct buffer *buf = &file->buffers[id];
struct v4l2_buffer vb;
struct spa_data *d = &b->buffer->datas[0];
file->size = d->maxsize;
pw_log_info("file:%d: id:%d fd:%"PRIi64" size:%u offset:%u", file->fd,
id, d->fd, file->size, id * file->size);
spa_zero(vb);
vb.index = id;
vb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vb.flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
vb.memory = V4L2_MEMORY_MMAP;
vb.m.offset = id * file->size;
vb.length = file->size;
buf->v4l2 = vb;
buf->id = id;
buf->buf = b;
b->user_data = buf;
file->n_buffers++;
}
static void on_stream_remove_buffer(void *data, struct pw_buffer *b)
{
struct file *file = data;
file->n_buffers--;
}
static void on_stream_process(void *data)
{
struct file *file = data;
pw_log_debug("file:%d", file->fd);
spa_system_eventfd_write(file->l->system, file->fd, 1);
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.param_changed = on_stream_param_changed,
.state_changed = on_stream_state_changed,
.add_buffer = on_stream_add_buffer,
.remove_buffer = on_stream_remove_buffer,
.process = on_stream_process,
};
static int vidioc_enum_framesizes(struct file *file, struct v4l2_frmsizeenum *arg)
{
uint32_t count = 0;
struct global *g = file->node;
struct param *p;
bool found = false;
pw_log_info("index: %u", arg->index);
pw_log_info("format: %.4s", (char*)&arg->pixel_format);
pw_thread_loop_lock(file->loop);
spa_list_for_each(p, &g->param_list, link) {
const struct format_info *fi;
uint32_t media_type, media_subtype, format;
struct spa_rectangle size;
if (p->id != SPA_PARAM_EnumFormat || p->param == NULL)
continue;
if (spa_format_parse(p->param, &media_type, &media_subtype) < 0)
continue;
if (media_type != SPA_MEDIA_TYPE_video)
continue;
if (media_subtype == SPA_MEDIA_SUBTYPE_raw) {
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_Format, NULL,
SPA_FORMAT_VIDEO_format, SPA_POD_Id(&format)) < 0)
continue;
} else {
format = SPA_VIDEO_FORMAT_ENCODED;
}
fi = format_info_from_media_type(media_type, media_subtype, format);
if (fi == NULL)
continue;
if (fi->fourcc != arg->pixel_format)
continue;
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_Format, NULL,
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size)) < 0)
continue;
arg->type = V4L2_FRMSIZE_TYPE_DISCRETE;
arg->discrete.width = size.width;
arg->discrete.height = size.height;
pw_log_debug("count:%d %.4s %dx%d", count, (char*)&fi->fourcc,
size.width, size.height);
if (count == arg->index) {
found = true;
break;
}
count++;
}
pw_thread_loop_unlock(file->loop);
if (!found)
return -EINVAL;
switch (arg->type) {
case V4L2_FRMSIZE_TYPE_DISCRETE:
pw_log_info("type: discrete");
pw_log_info("width: %u", arg->discrete.width);
pw_log_info("height: %u", arg->discrete.height);
break;
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
case V4L2_FRMSIZE_TYPE_STEPWISE:
pw_log_info("type: stepwise");
pw_log_info("min-width: %u", arg->stepwise.min_width);
pw_log_info("max-width: %u", arg->stepwise.max_width);
pw_log_info("step-width: %u", arg->stepwise.step_width);
pw_log_info("min-height: %u", arg->stepwise.min_height);
pw_log_info("max-height: %u", arg->stepwise.max_height);
pw_log_info("step-height: %u", arg->stepwise.step_height);
break;
}
memset(arg->reserved, 0, sizeof(arg->reserved));
return 0;
}
static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *arg)
{
uint32_t count = 0, last_fourcc = 0;
struct global *g = file->node;
struct param *p;
bool found = false;
pw_log_info("index: %u", arg->index);
pw_log_info("type: %u", arg->type);
if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
pw_thread_loop_lock(file->loop);
spa_list_for_each(p, &g->param_list, link) {
const struct format_info *fi;
uint32_t media_type, media_subtype, format;
if (p->id != SPA_PARAM_EnumFormat || p->param == NULL)
continue;
if (spa_format_parse(p->param, &media_type, &media_subtype) < 0)
continue;
if (media_type != SPA_MEDIA_TYPE_video)
continue;
if (media_subtype == SPA_MEDIA_SUBTYPE_raw) {
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_Format, NULL,
SPA_FORMAT_VIDEO_format, SPA_POD_Id(&format)) < 0)
continue;
} else {
format = SPA_VIDEO_FORMAT_ENCODED;
}
fi = format_info_from_media_type(media_type, media_subtype, format);
if (fi == NULL)
continue;
if (fi->fourcc == last_fourcc)
continue;
pw_log_info("count:%d fourcc:%.4s last:%.4s", count,
(char*)&fi->fourcc, (char*)&last_fourcc);
arg->flags = fi->format == SPA_VIDEO_FORMAT_ENCODED ? V4L2_FMT_FLAG_COMPRESSED : 0;
arg->pixelformat = fi->fourcc;
snprintf((char*)arg->description, sizeof(arg->description), "%s",
fi->desc ? fi->desc : "Unknown");
last_fourcc = fi->fourcc;
if (count == arg->index) {
found = true;
break;
}
count++;
}
pw_thread_loop_unlock(file->loop);
if (!found)
return -EINVAL;
pw_log_info("format: %.4s", (char*)&arg->pixelformat);
pw_log_info("flags: %u", arg->type);
memset(arg->reserved, 0, sizeof(arg->reserved));
return 0;
}
static int vidioc_g_fmt(struct file *file, struct v4l2_format *arg)
{
struct param *p;
struct global *g = file->node;
int res;
if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
pw_thread_loop_lock(file->loop);
if (file->v4l2_format.fmt.pix.pixelformat != 0) {
*arg = file->v4l2_format;
} else {
struct v4l2_format tmp;
bool found = false;
spa_list_for_each(p, &g->param_list, link) {
if (p->id != SPA_PARAM_EnumFormat || p->param == NULL)
continue;
if (param_to_fmt(p->param, &tmp) < 0)
continue;
found = true;
break;
}
if (!found) {
res = -EINVAL;
goto exit_unlock;
}
*arg = file->v4l2_format = tmp;
}
res = 0;
exit_unlock:
pw_thread_loop_unlock(file->loop);
return res;
}
static int score_diff(struct v4l2_format *fmt, struct v4l2_format *tmp)
{
int score = 0, w, h;
if (fmt->fmt.pix.pixelformat != tmp->fmt.pix.pixelformat)
score += 20000;
w = SPA_ABS((int)fmt->fmt.pix.width - (int)tmp->fmt.pix.width);
h = SPA_ABS((int)fmt->fmt.pix.height - (int)tmp->fmt.pix.height);
return score + (w*w) + (h*h);
}
static int try_format(struct file *file, struct v4l2_format *fmt)
{
struct param *p;
struct global *g = file->node;
struct v4l2_format best_fmt = *fmt;
int best = -1;
pw_log_info("in: type: %u", fmt->type);
if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
pw_log_info("in: format: %.4s", (char*)&fmt->fmt.pix.pixelformat);
pw_log_info("in: width: %u", fmt->fmt.pix.width);
pw_log_info("in: height: %u", fmt->fmt.pix.height);
pw_log_info("in: field: %u", fmt->fmt.pix.field);
spa_list_for_each(p, &g->param_list, link) {
struct v4l2_format tmp;
int score;
if (p->param == NULL)
continue;
if (p->id != SPA_PARAM_EnumFormat && p->id != SPA_PARAM_Format)
continue;
if (param_to_fmt(p->param, &tmp) < 0)
continue;
score = score_diff(fmt, &tmp);
pw_log_debug("check: type: %u", tmp.type);
pw_log_debug("check: format: %.4s", (char*)&tmp.fmt.pix.pixelformat);
pw_log_debug("check: width: %u", tmp.fmt.pix.width);
pw_log_debug("check: height: %u", tmp.fmt.pix.height);
pw_log_debug("check: score: %d best:%d", score, best);
if (p->id == SPA_PARAM_Format) {
best_fmt = tmp;
break;
}
if (best == -1 || score < best) {
best = score;
best_fmt = tmp;
}
}
*fmt = best_fmt;
pw_log_info("out: format: %.4s", (char*)&fmt->fmt.pix.pixelformat);
pw_log_info("out: width: %u", fmt->fmt.pix.width);
pw_log_info("out: height: %u", fmt->fmt.pix.height);
pw_log_info("out: field: %u", fmt->fmt.pix.field);
pw_log_info("out: size: %u", fmt->fmt.pix.sizeimage);
return 0;
}
static int disconnect_stream(struct file *file)
{
if (file->stream != NULL) {
pw_log_info("file:%d disconnect", file->fd);
pw_stream_destroy(file->stream);
file->stream = NULL;
file->n_buffers = 0;
}
return 0;
}
static int connect_stream(struct file *file)
{
int res;
struct global *g = file->node;
struct timespec abstime;
const char *error = NULL;
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
const struct spa_pod *params[1];
struct pw_properties *props;
params[0] = fmt_to_param(&b, SPA_PARAM_EnumFormat, &file->v4l2_format);
if (params[0] == NULL) {
res = -EINVAL;
goto exit;
}
disconnect_stream(file);
props = pw_properties_new(NULL, NULL);
if (props == NULL) {
res = -errno;
goto exit;
}
pw_properties_set(props, PW_KEY_CLIENT_API, "v4l2");
pw_properties_setf(props, PW_KEY_APP_NAME, "%s", pw_get_prgname());
if (pw_properties_get(props, PW_KEY_MEDIA_TYPE) == NULL)
pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Video");
if (pw_properties_get(props, PW_KEY_MEDIA_CATEGORY) == NULL)
pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, "Capture");
file->stream = pw_stream_new(file->core, "v4l2 capture", props);
if (file->stream == NULL) {
res = -errno;
goto exit;
}
pw_stream_add_listener(file->stream,
&file->stream_listener,
&stream_events, file);
file->error = 0;
pw_stream_connect(file->stream,
PW_DIRECTION_INPUT,
g->id,
PW_STREAM_FLAG_DONT_RECONNECT |
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_RT_PROCESS,
params, 1);
pw_thread_loop_get_time (file->loop, &abstime,
DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
while (true) {
enum pw_stream_state state = pw_stream_get_state(file->stream, &error);
if (state == PW_STREAM_STATE_STREAMING)
break;
if (state == PW_STREAM_STATE_ERROR) {
res = -errno;
goto exit;
}
if (file->error < 0) {
res = file->error;
goto exit;
}
if (pw_thread_loop_timed_wait_full(file->loop, &abstime) < 0) {
res = -ETIMEDOUT;
goto exit;
}
}
/* pause stream */
res = pw_stream_set_active(file->stream, false);
exit:
return res;
}
static int vidioc_s_fmt(struct file *file, struct v4l2_format *arg)
{
int res;
pw_thread_loop_lock(file->loop);
if ((res = try_format(file, arg)) < 0)
goto exit_unlock;
file->v4l2_format = *arg;
exit_unlock:
pw_thread_loop_unlock(file->loop);
return res;
}
static int vidioc_try_fmt(struct file *file, struct v4l2_format *arg)
{
int res;
pw_thread_loop_lock(file->loop);
res = try_format(file, arg);
pw_thread_loop_unlock(file->loop);
return res;
}
static int vidioc_g_parm(struct file *file, struct v4l2_streamparm *arg)
{
if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
struct param *p;
pw_thread_loop_lock(file->loop);
bool found = false;
struct spa_video_info info;
int num = 0, denom = 0;
spa_list_for_each(p, &file->node->param_list, link) {
if (p->id != SPA_PARAM_EnumFormat || p->param == NULL)
continue;
if (param_to_info(p->param, &info) < 0)
continue;
switch (info.media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
num = info.info.raw.framerate.num;
denom = info.info.raw.framerate.denom;
break;
case SPA_MEDIA_SUBTYPE_mjpg:
num = info.info.mjpg.framerate.num;
denom = info.info.mjpg.framerate.denom;
break;
case SPA_MEDIA_SUBTYPE_h264:
num = info.info.h264.framerate.num;
denom = info.info.h264.framerate.denom;
break;
}
if (num == 0 || denom == 0)
continue;
found = true;
break;
}
if (!found) {
pw_thread_loop_unlock(file->loop);
return -EINVAL;
}
pw_thread_loop_unlock(file->loop);
spa_zero(*arg);
arg->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
arg->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
arg->parm.capture.capturemode = 0;
arg->parm.capture.extendedmode = 0;
arg->parm.capture.readbuffers = 0;
arg->parm.capture.timeperframe.numerator = denom;
arg->parm.capture.timeperframe.denominator = num;
pw_log_info("VIDIOC_G_PARM frametime: %d/%d", num, denom);
return 0;
}
// TODO: implement setting parameters
static int vidioc_s_parm(struct file *file, struct v4l2_streamparm *arg)
{
pw_log_warn("VIDIOC_S_PARM is unimplemented, returning current value");
vidioc_g_parm(file, arg);
return 0;
}
static int vidioc_enuminput(struct file *file, struct v4l2_input *arg)
{
uint32_t index = arg->index;
spa_zero(*arg);
arg->index = index;
switch (arg->index) {
case 0:
spa_scnprintf((char*)arg->name, sizeof(arg->name), "%s", DEFAULT_CARD);
arg->type = V4L2_INPUT_TYPE_CAMERA;
break;
default:
return -EINVAL;
}
return 0;
}
static int vidioc_g_input(struct file *file, int *arg)
{
*arg = 0;
return 0;
}
static int vidioc_s_input(struct file *file, int *arg)
{
if (*arg != 0)
return -EINVAL;
return 0;
}
static int vidioc_g_priority(struct file *file, enum v4l2_priority *arg)
{
*arg = file->priority;
pw_log_info("file:%d prio:%d", file->fd, *arg);
return 0;
}
static int vidioc_s_priority(struct file *file, int fd, enum v4l2_priority *arg)
{
if (*arg > V4L2_PRIORITY_RECORD)
return -EINVAL;
if (file->fd != fd && file->priority > *arg)
return -EINVAL;
pw_log_info("file:%d (%d) prio:%d", file->fd, fd, *arg);
file->priority = *arg;
return 0;
}
static int vidioc_reqbufs(struct file *file, int fd, struct v4l2_requestbuffers *arg)
{
int res;
pw_log_info("count: %u", arg->count);
pw_log_info("type: %u", arg->type);
pw_log_info("memory: %u", arg->memory);
#ifdef V4L2_MEMORY_FLAG_NON_COHERENT
pw_log_info("flags: %08x", arg->flags);
#endif
if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
if (arg->memory != V4L2_MEMORY_MMAP)
return -EINVAL;
pw_thread_loop_lock(file->loop);
if (file->n_buffers > 0 && file->reqbufs_fd != fd) {
pw_log_info("%u fd:%d != %d", file->n_buffers, file->reqbufs_fd, fd);
res = -EBUSY;
goto exit_unlock;
}
if (arg->count == 0) {
if (pw_array_get_len(&file->buffer_maps, struct buffer_map) != 0) {
pw_log_info("fd:%d have maps", fd);
res = -EBUSY;
goto exit_unlock;
}
if (file->running) {
pw_log_info("fd:%d running", fd);
res = -EBUSY;
goto exit_unlock;
}
res = disconnect_stream(file);
file->reqbufs = 0;
file->reqbufs_fd = -1;
} else {
file->reqbufs = arg->count;
if ((res = connect_stream(file)) < 0)
goto exit_unlock;
arg->count = file->n_buffers;
file->reqbufs_fd = fd;
}
#ifdef V4L2_MEMORY_FLAG_NON_COHERENT
arg->flags = 0;
#endif
#ifdef V4L2_BUF_CAP_SUPPORTS_MMAP
arg->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;
#endif
memset(arg->reserved, 0, sizeof(arg->reserved));
pw_log_info("result count: %u", arg->count);
exit_unlock:
if (res < 0)
pw_log_info("error : %s", spa_strerror(res));
pw_thread_loop_unlock(file->loop);
return res;
}
static int vidioc_querybuf(struct file *file, struct v4l2_buffer *arg)
{
int res;
if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
pw_thread_loop_lock(file->loop);
if (arg->index >= file->n_buffers) {
res = -EINVAL;
goto exit_unlock;
}
*arg = file->buffers[arg->index].v4l2;
res = 0;
exit_unlock:
pw_thread_loop_unlock(file->loop);
return res;
}
static int vidioc_qbuf(struct file *file, struct v4l2_buffer *arg)
{
int res = 0;
struct buffer *buf;
if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
if (arg->memory != V4L2_MEMORY_MMAP)
return -EINVAL;
pw_thread_loop_lock(file->loop);
if (arg->index >= file->n_buffers) {
res = -EINVAL;
goto exit;
}
buf = &file->buffers[arg->index];
if (SPA_FLAG_IS_SET(buf->v4l2.flags, V4L2_BUF_FLAG_QUEUED)) {
res = -EINVAL;
goto exit;
}
SPA_FLAG_SET(buf->v4l2.flags, V4L2_BUF_FLAG_QUEUED);
arg->flags = buf->v4l2.flags;
pw_stream_queue_buffer(file->stream, buf->buf);
exit:
pw_log_debug("file:%d %d -> %d (%s)", file->fd, arg->index, res, spa_strerror(res));
pw_thread_loop_unlock(file->loop);
return res;
}
static int vidioc_dqbuf(struct file *file, int fd, struct v4l2_buffer *arg)
{
int res = 0;
struct pw_buffer *b;
struct buffer *buf;
uint64_t val;
struct spa_data *d;
struct timespec ts;
if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
if (arg->memory != V4L2_MEMORY_MMAP)
return -EINVAL;
pw_log_debug("file:%d (%d) %d", file->fd, fd,
arg->index);
pw_thread_loop_lock(file->loop);
if (arg->index >= file->n_buffers) {
res = -EINVAL;
goto exit_unlock;
}
while (true) {
if (!file->running) {
res = -EINVAL;
goto exit_unlock;
}
b = pw_stream_dequeue_buffer(file->stream);
if (b != NULL)
break;
pw_thread_loop_unlock(file->loop);
res = spa_system_eventfd_read(file->l->system, fd, &val);
pw_thread_loop_lock(file->loop);
if (res < 0)
goto exit_unlock;
}
buf = b->user_data;
d = &buf->buf->buffer->datas[0];
SPA_FLAG_CLEAR(buf->v4l2.flags, V4L2_BUF_FLAG_QUEUED);
SPA_FLAG_UPDATE(buf->v4l2.flags, V4L2_BUF_FLAG_ERROR,
SPA_FLAG_IS_SET(d->chunk->flags, SPA_CHUNK_FLAG_CORRUPTED));
SPA_FLAG_SET(buf->v4l2.flags, V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC);
clock_gettime(CLOCK_MONOTONIC, &ts);
buf->v4l2.timestamp.tv_sec = ts.tv_sec;
buf->v4l2.timestamp.tv_usec = ts.tv_nsec / 1000;
buf->v4l2.field = V4L2_FIELD_NONE;
buf->v4l2.bytesused = d->chunk->size;
buf->v4l2.sequence = file->sequence++;
*arg = buf->v4l2;
exit_unlock:
pw_log_debug("file:%d (%d) %d -> %d (%s)", file->fd, fd,
arg->index, res, spa_strerror(res));
pw_thread_loop_unlock(file->loop);
return res;
}
static int vidioc_streamon(struct file *file, int *arg)
{
int res;
if (*arg != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
pw_thread_loop_lock(file->loop);
if (file->n_buffers == 0) {
res = -EINVAL;
goto exit_unlock;
}
if (file->running) {
res = 0;
goto exit_unlock;
}
res = pw_stream_set_active(file->stream, true);
if (res >= 0)
file->running = true;
exit_unlock:
pw_thread_loop_unlock(file->loop);
pw_log_info("file:%d -> %d (%s)", file->fd, res, spa_strerror(res));
return res;
}
static int vidioc_streamoff(struct file *file, int *arg)
{
int res;
uint32_t i;
if (*arg != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
pw_thread_loop_lock(file->loop);
for (i = 0; i < file->n_buffers; i++) {
struct buffer *buf = &file->buffers[i];
SPA_FLAG_CLEAR(buf->v4l2.flags, V4L2_BUF_FLAG_QUEUED);
}
if (!file->running) {
res = 0;
goto exit_unlock;
}
res = pw_stream_set_active(file->stream, false);
file->running = false;
file->sequence = 0;
exit_unlock:
pw_thread_loop_unlock(file->loop);
pw_log_info("file:%d -> %d (%s)", file->fd, res, spa_strerror(res));
return res;
}
// Copied from spa/plugins/v4l2/v4l2-utils.c
// TODO: unify with source
static struct {
uint32_t v4l2_id;
uint32_t spa_id;
} control_map[] = {
{ V4L2_CID_BRIGHTNESS, SPA_PROP_brightness },
{ V4L2_CID_CONTRAST, SPA_PROP_contrast },
{ V4L2_CID_SATURATION, SPA_PROP_saturation },
{ V4L2_CID_HUE, SPA_PROP_hue },
{ V4L2_CID_GAMMA, SPA_PROP_gamma },
{ V4L2_CID_EXPOSURE, SPA_PROP_exposure },
{ V4L2_CID_GAIN, SPA_PROP_gain },
{ V4L2_CID_SHARPNESS, SPA_PROP_sharpness },
};
static uint32_t prop_id_to_control(uint32_t prop_id)
{
SPA_FOR_EACH_ELEMENT_VAR(control_map, c) {
if (c->spa_id == prop_id)
return c->v4l2_id;
}
if (prop_id >= SPA_PROP_START_CUSTOM)
return prop_id - SPA_PROP_START_CUSTOM;
return SPA_ID_INVALID;
}
static int vidioc_queryctrl(struct file *file, struct v4l2_queryctrl *arg)
{
struct param *p;
bool found = false, next = false;
memset(arg->reserved, 0, sizeof(arg->reserved));
// TODO: V4L2_CTRL_FLAG_NEXT_COMPOUND
if (arg->id & V4L2_CTRL_FLAG_NEXT_CTRL) {
pw_log_debug("VIDIOC_QUERYCTRL: 0x%08" PRIx32 " | V4L2_CTRL_FLAG_NEXT_CTRL", arg->id);
arg->id = arg->id & ~V4L2_CTRL_FLAG_NEXT_CTRL & ~V4L2_CTRL_FLAG_NEXT_COMPOUND;
next = true;
}
pw_log_debug("VIDIOC_QUERYCTRL: 0x%08" PRIx32, arg->id);
if (file->node == NULL)
return -EIO;
pw_thread_loop_lock(file->loop);
// FIXME: place found item into a variable. Will fix unordered ctrls
spa_list_for_each(p, &file->node->param_list, link) {
uint32_t prop_id, ctrl_id, n_vals, choice = SPA_ID_INVALID;
const char *prop_description;
const struct spa_pod *type, *pod;
if (p->id != SPA_PARAM_PropInfo || p->param == NULL)
continue;
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_id, SPA_POD_Id(&prop_id),
SPA_PROP_INFO_description, SPA_POD_String(&prop_description)) < 0)
continue;
if ((ctrl_id = prop_id_to_control(prop_id)) == SPA_ID_INVALID)
continue;
if ((next && ctrl_id > arg->id) || (!next && ctrl_id == arg->id)) {
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_type, SPA_POD_PodChoice(&type)) < 0)
continue;
// TODO: support setting controls
arg->flags = V4L2_CTRL_FLAG_READ_ONLY;
spa_scnprintf((char*)arg->name, sizeof(arg->name), "%s", prop_description);
// check type and populate range
pod = spa_pod_get_values(type, &n_vals, &choice);
if (spa_pod_is_int(pod)) {
if (n_vals < 4)
break;
arg->type = V4L2_CTRL_TYPE_INTEGER;
int *v = SPA_POD_BODY(pod);
arg->default_value = v[0];
arg->minimum = v[1];
arg->maximum = v[2];
arg->step = v[3];
}
else if (spa_pod_is_bool(pod) && n_vals > 0) {
arg->type = V4L2_CTRL_TYPE_BOOLEAN;
arg->default_value = SPA_POD_VALUE(struct spa_pod_bool, pod);
arg->minimum = 0;
arg->maximum = 1;
arg->step = 1;
}
else {
break;
}
arg->id = ctrl_id;
found = true;
pw_log_debug("ctrl 0x%08" PRIx32 " ok", arg->id);
break;
}
}
pw_thread_loop_unlock(file->loop);
if (!found) {
pw_log_info("not found ctrl 0x%08" PRIx32, arg->id);
return -EINVAL;
}
return 0;
}
static int vidioc_g_ctrl(struct file *file, struct v4l2_control *arg)
{
struct param *p;
bool found = false;
pw_log_debug("VIDIOC_G_CTRL: 0x%08" PRIx32, arg->id);
if (file->node == NULL)
return -EIO;
pw_thread_loop_lock(file->loop);
spa_list_for_each(p, &file->node->param_list, link) {
uint32_t prop_id, ctrl_id, n_vals, choice = SPA_ID_INVALID;
const char *prop_description;
const struct spa_pod *type, *pod;
if (p->id != SPA_PARAM_PropInfo || p->param == NULL)
continue;
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_id, SPA_POD_Id(&prop_id),
SPA_PROP_INFO_description, SPA_POD_String(&prop_description)) < 0)
continue;
if ((ctrl_id = prop_id_to_control(prop_id)) == SPA_ID_INVALID)
continue;
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_type, SPA_POD_PodChoice(&type)) < 0)
continue;
if (ctrl_id == arg->id) {
// TODO: support getting true ctrl values instead of defaults
pod = spa_pod_get_values(type, &n_vals, &choice);
if (spa_pod_is_int(pod)) {
if (n_vals < 4)
break;
int *v = SPA_POD_BODY(pod);
arg->value = v[0];
}
else if (spa_pod_is_bool(pod) && n_vals > 0) {
arg->value = SPA_POD_VALUE(struct spa_pod_bool, pod);
}
else {
break;
}
found = true;
pw_log_debug("ctrl 0x%08" PRIx32 " ok", arg->id);
break;
}
}
pw_thread_loop_unlock(file->loop);
if (!found) {
pw_log_info("not found ctrl 0x%08" PRIx32, arg->id);
return -EINVAL;
}
return 0;
}
static int vidioc_s_ctrl(struct file *file, struct v4l2_control *arg)
{
struct param *p;
bool found = false;
pw_log_info("VIDIOC_S_CTRL: 0x%08" PRIx32 " 0x%08" PRIx32, arg->id, arg->value);
if (file->node == NULL)
return -EIO;
pw_thread_loop_lock(file->loop);
spa_list_for_each(p, &file->node->param_list, link) {
uint32_t prop_id, ctrl_id, n_vals, choice = SPA_ID_INVALID;
const char *prop_description;
const struct spa_pod *type, *pod;
if (p->id != SPA_PARAM_PropInfo || p->param == NULL)
continue;
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_id, SPA_POD_Id(&prop_id),
SPA_PROP_INFO_description, SPA_POD_String(&prop_description)) < 0)
continue;
if ((ctrl_id = prop_id_to_control(prop_id)) == SPA_ID_INVALID)
continue;
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_type, SPA_POD_PodChoice(&type)) < 0)
continue;
if (ctrl_id == arg->id) {
char buf[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
struct spa_pod_frame f[1];
struct spa_pod *param;
pod = spa_pod_get_values(type, &n_vals, &choice);
spa_pod_builder_push_object(&b, &f[0],
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
if (spa_pod_is_int(pod)) {
spa_pod_builder_add(&b, prop_id, SPA_POD_Int(arg->value), 0);
} else if (spa_pod_is_bool(pod)) {
spa_pod_builder_add(&b, prop_id, SPA_POD_Bool(arg->value), 0);
} else {
// TODO: float and other formats
pw_log_info("unknown type");
break;
}
param = spa_pod_builder_pop(&b, &f[0]);
pw_node_set_param((struct pw_node*)file->node->proxy, SPA_PARAM_Props, 0, param);
found = true;
pw_log_info("ctrl 0x%08" PRIx32 " set ok", arg->id);
break;
}
}
pw_thread_loop_unlock(file->loop);
if (!found) {
pw_log_info("not found ctrl 0x%08" PRIx32, arg->id);
return -EINVAL;
}
return 0;
}
static int v4l2_ioctl(int fd, unsigned long int request, void *arg)
{
int res;
struct file *file;
uint32_t flags;
if ((file = find_file(fd, &flags)) == NULL)
return globals.old_fops.ioctl(fd, request, arg);
#if defined(__FreeBSD__) || defined(__MidnightBSD__)
if (arg == NULL && (request & IOC_DIRMASK != IOC_VOID)) {
#else
if (arg == NULL && (_IOC_DIR(request) & (_IOC_WRITE | _IOC_READ))) {
#endif
res = -EFAULT;
goto done;
}
if (flags & FD_MAP_DUP)
fd = file->fd;
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_PARM:
res = vidioc_g_parm(file, (struct v4l2_streamparm *)arg);
break;
case VIDIOC_S_PARM:
res = vidioc_s_parm(file, (struct v4l2_streamparm *)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_G_PRIORITY:
res = vidioc_g_priority(file, (enum v4l2_priority *)arg);
break;
case VIDIOC_S_PRIORITY:
res = vidioc_s_priority(file, fd, (enum v4l2_priority *)arg);
break;
case VIDIOC_REQBUFS:
res = vidioc_reqbufs(file, fd, (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, fd, (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;
// TODO: VIDIOC_QUERY_EXT_CTRL
case VIDIOC_QUERYCTRL:
res = vidioc_queryctrl(file, (struct v4l2_queryctrl *)arg);
break;
case VIDIOC_G_CTRL:
res = vidioc_g_ctrl(file, (struct v4l2_control *)arg);
break;
case VIDIOC_S_CTRL:
res = vidioc_s_ctrl(file, (struct v4l2_control *)arg);
break;
default:
res = -ENOTTY;
break;
}
done:
if (res < 0) {
errno = -res;
res = -1;
}
pw_log_debug("file:%d fd:%d request:%lx nr:%d arg:%p -> %d (%s)",
file->fd, 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;
off64_t id;
struct pw_map_range range;
struct buffer *buf;
struct spa_data *data;
uint32_t fl;
if ((file = find_file(fd, &fl)) == NULL)
return globals.old_fops.mmap(addr, length, prot, flags, fd, offset);
pw_thread_loop_lock(file->loop);
if (file->size == 0) {
errno = EIO;
res = MAP_FAILED;
goto error_unlock;
}
id = offset / file->size;
if ((id * file->size) != offset || file->size != length) {
errno = EINVAL;
res = MAP_FAILED;
goto error_unlock;
}
buf = &file->buffers[id];
data = &buf->buf->buffer->datas[0];
pw_map_range_init(&range, data->mapoffset, data->maxsize, 1024);
if (!SPA_FLAG_IS_SET(data->flags, SPA_DATA_FLAG_READABLE))
prot &= ~PROT_READ;
if (!SPA_FLAG_IS_SET(data->flags, SPA_DATA_FLAG_WRITABLE))
prot &= ~PROT_WRITE;
if (data->data == NULL)
res = globals.old_fops.mmap(addr, range.size, prot, flags, data->fd, range.offset);
else
res = data->data;
add_file_map(file, res);
add_buffer_map(file, res, id);
SPA_FLAG_SET(buf->v4l2.flags, V4L2_BUF_FLAG_MAPPED);
pw_log_info("file:%d addr:%p length:%zu prot:%d flags:%d fd:%"PRIi64
" offset:%"PRIi64" (%u - %u) -> %p (%s)" ,
file->fd, addr, length, prot, flags, data->fd, offset,
range.offset, range.size,
res, strerror(res == MAP_FAILED ? errno : 0));
error_unlock:
pw_thread_loop_unlock(file->loop);
unref_file(file);
return res;
}
static int v4l2_munmap(void *addr, size_t length)
{
int res;
struct buffer_map *bmap;
struct buffer *buf;
struct file *file;
struct spa_data *data;
if ((file = remove_file_map(addr)) == NULL)
return globals.old_fops.munmap(addr, length);
pw_thread_loop_lock(file->loop);
bmap = find_buffer_map(file, addr);
if (bmap == NULL) {
res = -EINVAL;
goto exit_unlock;
}
buf = &file->buffers[bmap->id];
data = &buf->buf->buffer->datas[0];
if (data->data == NULL)
res = globals.old_fops.munmap(addr, length);
else
res = 0;
pw_log_info("addr:%p length:%zu -> %d (%s)", addr, length,
res, strerror(res < 0 ? errno : 0));
buf->v4l2.flags &= ~V4L2_BUF_FLAG_MAPPED;
remove_buffer_map(file, bmap);
exit_unlock:
pw_thread_loop_unlock(file->loop);
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 initialize(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);
pw_array_init(&globals.file_maps, 1024);
pw_array_init(&globals.fd_maps, 256);
}
const struct fops *get_fops(void)
{
static pthread_once_t initialized = PTHREAD_ONCE_INIT;
pthread_once(&initialized, initialize);
return &fops;
}