summaryrefslogtreecommitdiffstats
path: root/spa/plugins/v4l2
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:28:17 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:28:17 +0000
commit7a46c07230b8d8108c0e8e80df4522d0ac116538 (patch)
treed483300dab478b994fe199a5d19d18d74153718a /spa/plugins/v4l2
parentInitial commit. (diff)
downloadpipewire-7a46c07230b8d8108c0e8e80df4522d0ac116538.tar.xz
pipewire-7a46c07230b8d8108c0e8e80df4522d0ac116538.zip
Adding upstream version 0.3.65.upstream/0.3.65upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--spa/plugins/v4l2/meson.build10
-rw-r--r--spa/plugins/v4l2/v4l2-device.c291
-rw-r--r--spa/plugins/v4l2/v4l2-source.c1055
-rw-r--r--spa/plugins/v4l2/v4l2-udev.c754
-rw-r--r--spa/plugins/v4l2/v4l2-utils.c1743
-rw-r--r--spa/plugins/v4l2/v4l2.c59
-rw-r--r--spa/plugins/v4l2/v4l2.h49
7 files changed, 3961 insertions, 0 deletions
diff --git a/spa/plugins/v4l2/meson.build b/spa/plugins/v4l2/meson.build
new file mode 100644
index 0000000..648583f
--- /dev/null
+++ b/spa/plugins/v4l2/meson.build
@@ -0,0 +1,10 @@
+v4l2_sources = ['v4l2.c',
+ 'v4l2-device.c',
+ 'v4l2-udev.c',
+ 'v4l2-source.c']
+
+v4l2lib = shared_library('spa-v4l2',
+ v4l2_sources,
+ dependencies : [ spa_dep, libudev_dep, libinotify_dep ],
+ install : true,
+ install_dir : spa_plugindir / 'v4l2')
diff --git a/spa/plugins/v4l2/v4l2-device.c b/spa/plugins/v4l2/v4l2-device.c
new file mode 100644
index 0000000..53fb033
--- /dev/null
+++ b/spa/plugins/v4l2/v4l2-device.c
@@ -0,0 +1,291 @@
+/* Spa V4l2 Source
+ *
+ * Copyright © 2018 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 <stddef.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <linux/videodev2.h>
+
+#include <spa/support/plugin.h>
+#include <spa/support/log.h>
+#include <spa/support/loop.h>
+#include <spa/utils/keys.h>
+#include <spa/utils/names.h>
+#include <spa/utils/string.h>
+#include <spa/node/node.h>
+#include <spa/pod/builder.h>
+#include <spa/monitor/device.h>
+#include <spa/monitor/utils.h>
+#include <spa/param/param.h>
+
+#include "v4l2.h"
+
+#define NAME "v4l2-device"
+
+static const char default_device[] = "/dev/video0";
+
+struct props {
+ char device[64];
+ char product_id[6];
+ char vendor_id[6];
+ int device_fd;
+};
+
+static void reset_props(struct props *props)
+{
+ strncpy(props->device, default_device, 64);
+}
+
+struct impl {
+ struct spa_handle handle;
+ struct spa_device device;
+
+ struct spa_log *log;
+
+ struct props props;
+
+ struct spa_hook_list hooks;
+
+ struct spa_v4l2_device dev;
+};
+
+static int emit_info(struct impl *this, bool full)
+{
+ int res;
+ struct spa_dict_item items[12];
+ uint32_t n_items = 0;
+ struct spa_device_info info;
+ struct spa_param_info params[2];
+ char path[128], version[16], capabilities[16], device_caps[16];
+
+ if ((res = spa_v4l2_open(&this->dev, this->props.device)) < 0)
+ return res;
+
+ info = SPA_DEVICE_INFO_INIT();
+
+ info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS;
+
+#define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value)
+ snprintf(path, sizeof(path), "v4l2:%s", this->props.device);
+ ADD_ITEM(SPA_KEY_OBJECT_PATH, path);
+ ADD_ITEM(SPA_KEY_DEVICE_API, "v4l2");
+ ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Video/Device");
+ if (this->props.product_id[0])
+ ADD_ITEM(SPA_KEY_DEVICE_PRODUCT_ID, this->props.product_id);
+ if (this->props.vendor_id[0])
+ ADD_ITEM(SPA_KEY_DEVICE_VENDOR_ID, this->props.vendor_id);
+ ADD_ITEM(SPA_KEY_API_V4L2_PATH, (char *)this->props.device);
+ ADD_ITEM(SPA_KEY_API_V4L2_CAP_DRIVER, (char *)this->dev.cap.driver);
+ ADD_ITEM(SPA_KEY_API_V4L2_CAP_CARD, (char *)this->dev.cap.card);
+ ADD_ITEM(SPA_KEY_API_V4L2_CAP_BUS_INFO, (char *)this->dev.cap.bus_info);
+ snprintf(version, sizeof(version), "%u.%u.%u",
+ (this->dev.cap.version >> 16) & 0xFF,
+ (this->dev.cap.version >> 8) & 0xFF,
+ (this->dev.cap.version) & 0xFF);
+ ADD_ITEM(SPA_KEY_API_V4L2_CAP_VERSION, version);
+ snprintf(capabilities, sizeof(capabilities), "%08x", this->dev.cap.capabilities);
+ ADD_ITEM(SPA_KEY_API_V4L2_CAP_CAPABILITIES, capabilities);
+ snprintf(device_caps, sizeof(device_caps), "%08x", this->dev.cap.device_caps);
+ ADD_ITEM(SPA_KEY_API_V4L2_CAP_DEVICE_CAPS, device_caps);
+#undef ADD_ITEM
+ info.props = &SPA_DICT_INIT(items, n_items);
+
+ info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
+ params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ);
+ params[1] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_WRITE);
+ info.n_params = 0;
+ info.params = params;
+
+ spa_device_emit_info(&this->hooks, &info);
+
+ if (spa_v4l2_is_capture(&this->dev)) {
+ struct spa_device_object_info oinfo;
+
+ oinfo = SPA_DEVICE_OBJECT_INFO_INIT();
+ oinfo.type = SPA_TYPE_INTERFACE_Node;
+ oinfo.factory_name = SPA_NAME_API_V4L2_SOURCE;
+ oinfo.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
+ oinfo.props = &SPA_DICT_INIT(items, n_items);
+
+ spa_device_emit_object_info(&this->hooks, 0, &oinfo);
+ }
+
+ spa_v4l2_close(&this->dev);
+
+ return 0;
+}
+
+static int impl_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_device_events *events,
+ void *data)
+{
+ struct impl *this = object;
+ struct spa_hook_list save;
+ int res = 0;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(events != NULL, -EINVAL);
+
+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
+
+ if (events->info || events->object_info)
+ res = emit_info(this, true);
+
+ spa_hook_list_join(&this->hooks, &save);
+
+ return res;
+}
+
+static int impl_sync(void *object, int seq)
+{
+ struct impl *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ spa_device_emit_result(&this->hooks, seq, 0, 0, NULL);
+
+ return 0;
+}
+
+static int impl_enum_params(void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ return -ENOTSUP;
+}
+
+static int impl_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ return -ENOTSUP;
+}
+
+static const struct spa_device_methods impl_device = {
+ SPA_VERSION_DEVICE_METHODS,
+ .add_listener = impl_add_listener,
+ .sync = impl_sync,
+ .enum_params = impl_enum_params,
+ .set_param = impl_set_param,
+};
+
+static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
+{
+ struct impl *this;
+
+ spa_return_val_if_fail(handle != NULL, -EINVAL);
+ spa_return_val_if_fail(interface != NULL, -EINVAL);
+
+ this = (struct impl *) handle;
+
+ if (spa_streq(type, SPA_TYPE_INTERFACE_Device))
+ *interface = &this->device;
+ else
+ return -ENOENT;
+
+ return 0;
+}
+
+static int impl_clear(struct spa_handle *handle)
+{
+ return 0;
+}
+
+static size_t
+impl_get_size(const struct spa_handle_factory *factory,
+ const struct spa_dict *params)
+{
+ return sizeof(struct impl);
+}
+
+static int
+impl_init(const struct spa_handle_factory *factory,
+ struct spa_handle *handle,
+ const struct spa_dict *info,
+ const struct spa_support *support,
+ uint32_t n_support)
+{
+ struct impl *this;
+ const char *str;
+
+ spa_return_val_if_fail(factory != NULL, -EINVAL);
+ spa_return_val_if_fail(handle != NULL, -EINVAL);
+
+ handle->get_interface = impl_get_interface;
+ handle->clear = impl_clear, this = (struct impl *) handle;
+
+ this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
+
+ spa_hook_list_init(&this->hooks);
+
+ this->device.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Device,
+ SPA_VERSION_DEVICE,
+ &impl_device, this);
+ this->dev.log = this->log;
+ this->dev.fd = -1;
+
+ reset_props(&this->props);
+
+ if (info && (str = spa_dict_lookup(info, SPA_KEY_API_V4L2_PATH)))
+ strncpy(this->props.device, str, 63);
+ if (info && (str = spa_dict_lookup(info, SPA_KEY_DEVICE_PRODUCT_ID)))
+ strncpy(this->props.product_id, str, 5);
+ if (info && (str = spa_dict_lookup(info, SPA_KEY_DEVICE_VENDOR_ID)))
+ strncpy(this->props.vendor_id, str, 5);
+
+ return 0;
+}
+
+static const struct spa_interface_info impl_interfaces[] = {
+ {SPA_TYPE_INTERFACE_Device,},
+};
+
+static int impl_enum_interface_info(const struct spa_handle_factory *factory,
+ const struct spa_interface_info **info,
+ uint32_t *index)
+{
+ spa_return_val_if_fail(factory != NULL, -EINVAL);
+ spa_return_val_if_fail(info != NULL, -EINVAL);
+ spa_return_val_if_fail(index != NULL, -EINVAL);
+
+ if (*index >= SPA_N_ELEMENTS(impl_interfaces))
+ return 0;
+
+ *info = &impl_interfaces[(*index)++];
+
+ return 1;
+}
+
+const struct spa_handle_factory spa_v4l2_device_factory = {
+ SPA_VERSION_HANDLE_FACTORY,
+ SPA_NAME_API_V4L2_DEVICE,
+ NULL,
+ impl_get_size,
+ impl_init,
+ impl_enum_interface_info,
+};
diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c
new file mode 100644
index 0000000..b6eae49
--- /dev/null
+++ b/spa/plugins/v4l2/v4l2-source.c
@@ -0,0 +1,1055 @@
+/* Spa V4l2 Source
+ *
+ * Copyright © 2018 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 <stddef.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <linux/videodev2.h>
+
+#include <spa/support/plugin.h>
+#include <spa/support/log.h>
+#include <spa/support/loop.h>
+#include <spa/utils/list.h>
+#include <spa/utils/keys.h>
+#include <spa/utils/names.h>
+#include <spa/utils/string.h>
+#include <spa/monitor/device.h>
+#include <spa/node/node.h>
+#include <spa/node/io.h>
+#include <spa/node/utils.h>
+#include <spa/node/keys.h>
+#include <spa/param/video/format-utils.h>
+#include <spa/param/param.h>
+#include <spa/param/latency-utils.h>
+#include <spa/pod/filter.h>
+#include <spa/control/control.h>
+
+#include "v4l2.h"
+
+static const char default_device[] = "/dev/video0";
+
+struct props {
+ char device[64];
+ char device_name[128];
+ int device_fd;
+};
+
+static void reset_props(struct props *props)
+{
+ strncpy(props->device, default_device, 64);
+}
+
+#define MAX_BUFFERS 32
+
+#define BUFFER_FLAG_OUTSTANDING (1<<0)
+#define BUFFER_FLAG_ALLOCATED (1<<1)
+#define BUFFER_FLAG_MAPPED (1<<2)
+
+struct buffer {
+ uint32_t id;
+ uint32_t flags;
+ struct spa_list link;
+ struct spa_buffer *outbuf;
+ struct spa_meta_header *h;
+ struct v4l2_buffer v4l2_buffer;
+ void *ptr;
+};
+
+#define MAX_CONTROLS 64
+
+struct control {
+ uint32_t id;
+ uint32_t ctrl_id;
+ double value;
+};
+
+struct port {
+ struct impl *impl;
+
+ bool alloc_buffers;
+ bool have_expbuf;
+
+ bool next_fmtdesc;
+ struct v4l2_fmtdesc fmtdesc;
+ bool next_frmsize;
+ struct v4l2_frmsizeenum frmsize;
+ struct v4l2_frmivalenum frmival;
+
+ bool have_format;
+ struct spa_video_info current_format;
+ struct spa_fraction rate;
+
+ struct spa_v4l2_device dev;
+
+ bool have_query_ext_ctrl;
+ struct v4l2_format fmt;
+ enum v4l2_buf_type type;
+ enum v4l2_memory memtype;
+
+ struct control controls[MAX_CONTROLS];
+ uint32_t n_controls;
+
+ struct buffer buffers[MAX_BUFFERS];
+ uint32_t n_buffers;
+ struct spa_list queue;
+
+ struct spa_source source;
+
+ uint64_t info_all;
+ struct spa_port_info info;
+ struct spa_io_buffers *io;
+ struct spa_io_sequence *control;
+#define PORT_PropInfo 0
+#define PORT_EnumFormat 1
+#define PORT_Meta 2
+#define PORT_IO 3
+#define PORT_Format 4
+#define PORT_Buffers 5
+#define PORT_Latency 6
+#define N_PORT_PARAMS 7
+ struct spa_param_info params[N_PORT_PARAMS];
+};
+
+struct impl {
+ struct spa_handle handle;
+ struct spa_node node;
+
+ struct spa_log *log;
+ struct spa_loop *data_loop;
+
+ uint64_t info_all;
+ struct spa_node_info info;
+#define NODE_PropInfo 0
+#define NODE_Props 1
+#define NODE_EnumFormat 2
+#define NODE_Format 3
+#define N_NODE_PARAMS 4
+ struct spa_param_info params[N_NODE_PARAMS];
+ struct props props;
+
+ struct spa_hook_list hooks;
+ struct spa_callbacks callbacks;
+
+ struct port out_ports[1];
+
+ struct spa_io_position *position;
+ struct spa_io_clock *clock;
+
+ struct spa_latency_info latency[2];
+};
+
+#define CHECK_PORT(this,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0)
+
+#define GET_OUT_PORT(this,p) (&this->out_ports[p])
+#define GET_PORT(this,d,p) GET_OUT_PORT(this,p)
+
+#include "v4l2-utils.c"
+
+static int port_get_format(struct port *port,
+ uint32_t index,
+ const struct spa_pod *filter,
+ struct spa_pod **param,
+ struct spa_pod_builder *builder)
+{
+ struct spa_pod_frame f;
+
+ if (!port->have_format)
+ return -EIO;
+ if (index > 0)
+ return 0;
+
+ spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format);
+ spa_pod_builder_add(builder,
+ SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format.media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format.media_subtype),
+ 0);
+
+ switch (port->current_format.media_subtype) {
+ case SPA_MEDIA_SUBTYPE_raw:
+ spa_pod_builder_add(builder,
+ SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format.info.raw.format),
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.raw.size),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.raw.framerate),
+ 0);
+ break;
+ case SPA_MEDIA_SUBTYPE_mjpg:
+ case SPA_MEDIA_SUBTYPE_jpeg:
+ spa_pod_builder_add(builder,
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.mjpg.size),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.mjpg.framerate),
+ 0);
+ break;
+ case SPA_MEDIA_SUBTYPE_h264:
+ spa_pod_builder_add(builder,
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.h264.size),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.h264.framerate),
+ 0);
+ break;
+ default:
+ return -EIO;
+ }
+
+ *param = spa_pod_builder_pop(builder, &f);
+
+ return 1;
+}
+
+
+static int impl_node_enum_params(void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct impl *this = object;
+ struct spa_pod *param;
+ struct spa_pod_builder b = { 0 };
+ uint8_t buffer[1024];
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+ int res;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(num != 0, -EINVAL);
+
+ result.id = id;
+ result.next = start;
+ next:
+ result.index = result.next++;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+
+ switch (id) {
+ case SPA_PARAM_PropInfo:
+ {
+ struct props *p = &this->props;
+
+ switch (result.index) {
+ case 0:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_PropInfo, id,
+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device),
+ SPA_PROP_INFO_description, SPA_POD_String("The V4L2 device"),
+ SPA_PROP_INFO_type, SPA_POD_String(p->device));
+ break;
+ case 1:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_PropInfo, id,
+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName),
+ SPA_PROP_INFO_description, SPA_POD_String("The V4L2 device name"),
+ SPA_PROP_INFO_type, SPA_POD_String(p->device_name));
+ break;
+ case 2:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_PropInfo, id,
+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceFd),
+ SPA_PROP_INFO_description, SPA_POD_String("The V4L2 fd"),
+ SPA_PROP_INFO_type, SPA_POD_Int(p->device_fd));
+ break;
+ default:
+ return spa_v4l2_enum_controls(this, seq, result.index - 3, num, filter);
+ }
+ break;
+ }
+ case SPA_PARAM_Props:
+ {
+ struct props *p = &this->props;
+
+ switch (result.index) {
+ case 0:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Props, id,
+ SPA_PROP_device, SPA_POD_String(p->device),
+ SPA_PROP_deviceName, SPA_POD_String(p->device_name),
+ SPA_PROP_deviceFd, SPA_POD_Int(p->device_fd));
+ break;
+ default:
+ return 0;
+ }
+ break;
+ }
+ case SPA_PARAM_EnumFormat:
+ return spa_v4l2_enum_format(this, seq, start, num, filter);
+ case SPA_PARAM_Format:
+ if((res = port_get_format(GET_OUT_PORT(this, 0),
+ result.index, filter, &param, &b)) <= 0)
+ return res;
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
+ goto next;
+
+ spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+
+ if (++count != num)
+ goto next;
+
+ return 0;
+}
+
+static int impl_node_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct impl *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ switch (id) {
+ case SPA_PARAM_Props:
+ {
+ struct props *p = &this->props;
+ struct spa_pod_object *obj = (struct spa_pod_object *) param;
+ struct spa_pod_prop *prop;
+
+ if (param == NULL) {
+ reset_props(p);
+ return 0;
+ }
+ SPA_POD_OBJECT_FOREACH(obj, prop) {
+ switch (prop->key) {
+ case SPA_PROP_device:
+ strncpy(p->device,
+ (char *)SPA_POD_CONTENTS(struct spa_pod_string, &prop->value),
+ sizeof(p->device)-1);
+ break;
+ default:
+ spa_v4l2_set_control(this, prop->key, prop);
+ break;
+ }
+ }
+
+ break;
+ }
+ default:
+ return -ENOENT;
+ }
+ return 0;
+}
+
+static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
+{
+ struct impl *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ switch (id) {
+ case SPA_IO_Clock:
+ this->clock = data;
+ break;
+ case SPA_IO_Position:
+ this->position = data;
+ break;
+ default:
+ return -ENOENT;
+ }
+ return 0;
+}
+
+static int impl_node_send_command(void *object, const struct spa_command *command)
+{
+ struct impl *this = object;
+ struct port *port;
+ int res;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(command != NULL, -EINVAL);
+
+ port = GET_OUT_PORT(this, 0);
+
+ switch (SPA_NODE_COMMAND_ID(command)) {
+ case SPA_NODE_COMMAND_ParamBegin:
+ if ((res = spa_v4l2_open(&port->dev, this->props.device)) < 0)
+ return res;
+ break;
+ case SPA_NODE_COMMAND_ParamEnd:
+ if (port->have_format)
+ return 0;
+ if ((res = spa_v4l2_close(&port->dev)) < 0)
+ return res;
+ break;
+ case SPA_NODE_COMMAND_Start:
+ {
+ if (!port->have_format) {
+ spa_log_error(this->log, "no format");
+ return -EIO;
+ }
+ if (port->n_buffers == 0) {
+ spa_log_error(this->log, "no buffers");
+ return -EIO;
+ }
+
+ if ((res = spa_v4l2_stream_on(this)) < 0)
+ return res;
+ break;
+ }
+ case SPA_NODE_COMMAND_Pause:
+ case SPA_NODE_COMMAND_Suspend:
+ if ((res = spa_v4l2_stream_off(this)) < 0)
+ return res;
+ break;
+ default:
+ return -ENOTSUP;
+ }
+
+ return 0;
+}
+
+static const struct spa_dict_item info_items[] = {
+ { SPA_KEY_DEVICE_API, "v4l2" },
+ { SPA_KEY_MEDIA_CLASS, "Video/Source" },
+ { SPA_KEY_MEDIA_ROLE, "Camera" },
+ { SPA_KEY_NODE_DRIVER, "true" },
+};
+
+static void emit_node_info(struct impl *this, bool full)
+{
+ uint64_t old = full ? this->info.change_mask : 0;
+ if (full)
+ this->info.change_mask = this->info_all;
+ if (this->info.change_mask) {
+ this->info.props = &SPA_DICT_INIT_ARRAY(info_items);
+ spa_node_emit_info(&this->hooks, &this->info);
+ this->info.change_mask = old;
+ }
+}
+
+static void emit_port_info(struct impl *this, struct port *port, bool full)
+{
+ uint64_t old = full ? port->info.change_mask : 0;
+ if (full)
+ port->info.change_mask = port->info_all;
+ if (port->info.change_mask) {
+ spa_node_emit_port_info(&this->hooks,
+ SPA_DIRECTION_OUTPUT, 0, &port->info);
+ port->info.change_mask = old;
+ }
+}
+
+static int
+impl_node_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_node_events *events,
+ void *data)
+{
+ struct impl *this = object;
+ struct spa_hook_list save;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
+
+ emit_node_info(this, true);
+ emit_port_info(this, GET_OUT_PORT(this, 0), true);
+
+ spa_hook_list_join(&this->hooks, &save);
+
+ return 0;
+}
+
+static int impl_node_set_callbacks(void *object,
+ const struct spa_node_callbacks *callbacks,
+ void *data)
+{
+ struct impl *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ this->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
+
+ return 0;
+}
+
+static int impl_node_sync(void *object, int seq)
+{
+ struct impl *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ spa_node_emit_result(&this->hooks, seq, 0, 0, NULL);
+
+ return 0;
+}
+
+static int impl_node_add_port(void *object,
+ enum spa_direction direction,
+ uint32_t port_id, const struct spa_dict *props)
+{
+ return -ENOTSUP;
+}
+
+static int impl_node_remove_port(void *object,
+ enum spa_direction direction,
+ uint32_t port_id)
+{
+ return -ENOTSUP;
+}
+
+static int impl_node_port_enum_params(void *object, int seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+
+ struct impl *this = object;
+ struct port *port;
+ struct spa_pod *param;
+ struct spa_pod_builder b = { 0 };
+ uint8_t buffer[1024];
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+ int res;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(num != 0, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
+
+ port = GET_PORT(this, direction, port_id);
+
+ result.id = id;
+ result.next = start;
+ next:
+ result.index = result.next++;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+
+ switch (id) {
+ case SPA_PARAM_PropInfo:
+ return spa_v4l2_enum_controls(this, seq, start, num, filter);
+
+ case SPA_PARAM_EnumFormat:
+ return spa_v4l2_enum_format(this, seq, start, num, filter);
+
+ case SPA_PARAM_Format:
+ if((res = port_get_format(port, result.index, filter, &param, &b)) <= 0)
+ return res;
+ break;
+ case SPA_PARAM_Buffers:
+ if (!port->have_format)
+ return -EIO;
+ if (result.index > 0)
+ return 0;
+
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, id,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(4, 1, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(port->fmt.fmt.pix.sizeimage),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->fmt.fmt.pix.bytesperline));
+ break;
+
+ case SPA_PARAM_Meta:
+ switch (result.index) {
+ case 0:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, id,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
+ break;
+ default:
+ return 0;
+ }
+ break;
+ case SPA_PARAM_IO:
+ switch (result.index) {
+ case 0:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamIO, id,
+ SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers),
+ SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers)));
+ break;
+ case 1:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamIO, id,
+ SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock),
+ SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock)));
+ break;
+ case 2:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamIO, id,
+ SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Control),
+ SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence)));
+ break;
+ default:
+ return 0;
+ }
+ break;
+ case SPA_PARAM_Latency:
+ switch (result.index) {
+ case 0: case 1:
+ param = spa_latency_build(&b, id, &this->latency[result.index]);
+ break;
+ default:
+ return 0;
+ }
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
+ goto next;
+
+ spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+
+ if (++count != num)
+ goto next;
+
+ return 0;
+}
+
+static int port_set_format(struct impl *this, struct port *port,
+ uint32_t flags,
+ const struct spa_pod *format)
+{
+ struct spa_video_info info;
+ int res;
+
+ if (port->have_format) {
+ spa_v4l2_stream_off(this);
+ spa_v4l2_clear_buffers(this);
+ }
+ if (format == NULL) {
+ if (!port->have_format)
+ return 0;
+
+ port->have_format = false;
+ port->dev.have_format = false;
+ spa_v4l2_close(&port->dev);
+ goto done;
+ } else {
+ if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0)
+ return res;
+
+ if (info.media_type != SPA_MEDIA_TYPE_video) {
+ spa_log_error(this->log, "media type must be video");
+ return -EINVAL;
+ }
+
+ switch (info.media_subtype) {
+ case SPA_MEDIA_SUBTYPE_raw:
+ if (spa_format_video_raw_parse(format, &info.info.raw) < 0) {
+ spa_log_error(this->log, "can't parse video raw");
+ return -EINVAL;
+ }
+ break;
+ case SPA_MEDIA_SUBTYPE_mjpg:
+ if (spa_format_video_mjpg_parse(format, &info.info.mjpg) < 0)
+ return -EINVAL;
+ break;
+ case SPA_MEDIA_SUBTYPE_h264:
+ if (spa_format_video_h264_parse(format, &info.info.h264) < 0)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (port->have_format && !SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_TEST_ONLY)) {
+ port->have_format = false;
+ }
+
+ if ((res = spa_v4l2_set_format(this, &info, flags)) < 0)
+ return res;
+
+ if (!SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_TEST_ONLY)) {
+ port->current_format = info;
+ port->have_format = true;
+ }
+
+ done:
+ this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
+ if (port->have_format) {
+ port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE);
+ port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ);
+ this->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READ);
+ } else {
+ port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
+ this->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, 0);
+ }
+ emit_port_info(this, port, false);
+ emit_node_info(this, false);
+
+ return 0;
+}
+
+static int impl_node_port_set_param(void *object,
+ enum spa_direction direction, uint32_t port_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ int res;
+ struct impl *this = object;
+ struct port *port;
+
+ spa_return_val_if_fail(object != NULL, -EINVAL);
+
+ spa_return_val_if_fail(CHECK_PORT(object, direction, port_id), -EINVAL);
+
+ port = GET_PORT(this, direction, port_id);
+
+ switch (id) {
+ case SPA_PARAM_Latency:
+ {
+ struct spa_latency_info info;
+ if ((res = spa_latency_parse(param, &info)) < 0)
+ return res;
+ if (direction == info.direction)
+ return -EINVAL;
+
+ this->latency[info.direction] = info;
+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
+ port->params[PORT_Latency].flags ^= SPA_PARAM_INFO_SERIAL;
+ emit_port_info(this, port, false);
+ break;
+ }
+ case SPA_PARAM_Format:
+ res = port_set_format(object, port, flags, param);
+ break;
+ default:
+ res = -ENOENT;
+ }
+ return res;
+}
+
+static int impl_node_port_use_buffers(void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t flags,
+ struct spa_buffer **buffers,
+ uint32_t n_buffers)
+{
+ struct impl *this = object;
+ struct port *port;
+ int res;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
+
+ port = GET_PORT(this, direction, port_id);
+
+ if (port->n_buffers) {
+ spa_v4l2_stream_off(this);
+ if ((res = spa_v4l2_clear_buffers(this)) < 0)
+ return res;
+ }
+ if (n_buffers > 0 && !port->have_format)
+ return -EIO;
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+ if (buffers == NULL)
+ return 0;
+
+ if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) {
+ res = spa_v4l2_alloc_buffers(this, buffers, n_buffers);
+ } else {
+ res = spa_v4l2_use_buffers(this, buffers, n_buffers);
+ }
+ return res;
+}
+
+static int impl_node_port_set_io(void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t id,
+ void *data, size_t size)
+{
+ struct impl *this = object;
+ struct port *port;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
+
+ port = GET_PORT(this, direction, port_id);
+
+ switch (id) {
+ case SPA_IO_Buffers:
+ port->io = data;
+ break;
+ case SPA_IO_Control:
+ port->control = data;
+ break;
+ default:
+ return -ENOENT;
+ }
+ return 0;
+}
+
+static int impl_node_port_reuse_buffer(void *object,
+ uint32_t port_id,
+ uint32_t buffer_id)
+{
+ struct impl *this = object;
+ struct port *port;
+ int res;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(port_id == 0, -EINVAL);
+
+ port = GET_OUT_PORT(this, port_id);
+
+ spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL);
+
+ res = spa_v4l2_buffer_recycle(this, buffer_id);
+
+ return res;
+}
+
+static int process_control(struct impl *this, struct spa_pod_sequence *control)
+{
+ struct spa_pod_control *c;
+
+ SPA_POD_SEQUENCE_FOREACH(control, c) {
+ switch (c->type) {
+ case SPA_CONTROL_Properties:
+ {
+ struct spa_pod_prop *prop;
+ struct spa_pod_object *obj = (struct spa_pod_object *) &c->value;
+
+ SPA_POD_OBJECT_FOREACH(obj, prop) {
+ spa_v4l2_set_control(this, prop->key, prop);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+static int impl_node_process(void *object)
+{
+ struct impl *this = object;
+ int res;
+ struct spa_io_buffers *io;
+ struct port *port;
+ struct buffer *b;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ port = GET_OUT_PORT(this, 0);
+ if ((io = port->io) == NULL)
+ return -EIO;
+
+ if (port->control)
+ process_control(this, &port->control->sequence);
+
+ spa_log_trace(this->log, "%p; status %d", this, io->status);
+
+ if (io->status == SPA_STATUS_HAVE_DATA)
+ return SPA_STATUS_HAVE_DATA;
+
+ if (io->buffer_id < port->n_buffers) {
+ if ((res = spa_v4l2_buffer_recycle(this, io->buffer_id)) < 0)
+ return res;
+
+ io->buffer_id = SPA_ID_INVALID;
+ }
+
+ if (spa_list_is_empty(&port->queue))
+ return SPA_STATUS_OK;
+
+ b = spa_list_first(&port->queue, struct buffer, link);
+ spa_list_remove(&b->link);
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING);
+
+ spa_log_trace(this->log, "%p: dequeue buffer %d", this, b->id);
+
+ io->buffer_id = b->id;
+ io->status = SPA_STATUS_HAVE_DATA;
+
+ return SPA_STATUS_HAVE_DATA;
+}
+
+static const struct spa_node_methods impl_node = {
+ SPA_VERSION_NODE_METHODS,
+ .add_listener = impl_node_add_listener,
+ .set_callbacks = impl_node_set_callbacks,
+ .sync = impl_node_sync,
+ .enum_params = impl_node_enum_params,
+ .set_param = impl_node_set_param,
+ .set_io = impl_node_set_io,
+ .send_command = impl_node_send_command,
+ .add_port = impl_node_add_port,
+ .remove_port = impl_node_remove_port,
+ .port_enum_params = impl_node_port_enum_params,
+ .port_set_param = impl_node_port_set_param,
+ .port_use_buffers = impl_node_port_use_buffers,
+ .port_set_io = impl_node_port_set_io,
+ .port_reuse_buffer = impl_node_port_reuse_buffer,
+ .process = impl_node_process,
+};
+
+static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
+{
+ struct impl *this;
+
+ spa_return_val_if_fail(handle != NULL, -EINVAL);
+ spa_return_val_if_fail(interface != NULL, -EINVAL);
+
+ this = (struct impl *) handle;
+
+ if (spa_streq(type, SPA_TYPE_INTERFACE_Node))
+ *interface = &this->node;
+ else
+ return -ENOENT;
+
+ return 0;
+}
+
+static int impl_clear(struct spa_handle *handle)
+{
+ return 0;
+}
+
+static size_t
+impl_get_size(const struct spa_handle_factory *factory,
+ const struct spa_dict *params)
+{
+ return sizeof(struct impl);
+}
+
+static int
+impl_init(const struct spa_handle_factory *factory,
+ struct spa_handle *handle,
+ const struct spa_dict *info,
+ const struct spa_support *support,
+ uint32_t n_support)
+{
+ struct impl *this;
+ const char *str;
+ struct port *port;
+ int res;
+
+ spa_return_val_if_fail(factory != NULL, -EINVAL);
+ spa_return_val_if_fail(handle != NULL, -EINVAL);
+
+ handle->get_interface = impl_get_interface;
+ handle->clear = impl_clear;
+
+ this = (struct impl *) handle;
+
+ this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
+ v4l2_log_topic_init(this->log);
+
+ this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop);
+ if (this->data_loop == NULL) {
+ spa_log_error(this->log, "a data_loop is needed");
+ return -EINVAL;
+ }
+
+ this->node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ &impl_node, this);
+ spa_hook_list_init(&this->hooks);
+
+ this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT);
+ this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT);
+
+ this->info_all = SPA_NODE_CHANGE_MASK_FLAGS |
+ SPA_NODE_CHANGE_MASK_PROPS |
+ SPA_NODE_CHANGE_MASK_PARAMS;
+ this->info = SPA_NODE_INFO_INIT();
+ this->info.max_output_ports = 1;
+ this->info.flags = SPA_NODE_FLAG_RT;
+ this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
+ this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
+ this->params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
+ this->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, 0);
+ this->info.params = this->params;
+ this->info.n_params = N_NODE_PARAMS;
+ reset_props(&this->props);
+
+ port = GET_OUT_PORT(this, 0);
+ port->impl = this;
+ spa_list_init(&port->queue);
+ port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
+ SPA_PORT_CHANGE_MASK_PARAMS;
+ port->info = SPA_PORT_INFO_INIT();
+ port->info.flags = SPA_PORT_FLAG_LIVE |
+ SPA_PORT_FLAG_PHYSICAL |
+ SPA_PORT_FLAG_TERMINAL;
+ port->params[PORT_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
+ port->params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
+ port->params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
+ port->params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
+ port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
+ port->params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READ);
+ port->info.params = port->params;
+ port->info.n_params = N_PORT_PARAMS;
+
+ port->alloc_buffers = true;
+ port->have_expbuf = true;
+ port->have_query_ext_ctrl = true;
+ port->dev.log = this->log;
+ port->dev.fd = -1;
+
+ if (info && (str = spa_dict_lookup(info, SPA_KEY_API_V4L2_PATH))) {
+ strncpy(this->props.device, str, 63);
+ if ((res = spa_v4l2_open(&port->dev, this->props.device)) < 0)
+ return res;
+ spa_v4l2_close(&port->dev);
+ }
+
+ return 0;
+}
+
+static const struct spa_interface_info impl_interfaces[] = {
+ {SPA_TYPE_INTERFACE_Node,},
+};
+
+static int impl_enum_interface_info(const struct spa_handle_factory *factory,
+ const struct spa_interface_info **info,
+ uint32_t *index)
+{
+ spa_return_val_if_fail(factory != NULL, -EINVAL);
+ spa_return_val_if_fail(info != NULL, -EINVAL);
+ spa_return_val_if_fail(index != NULL, -EINVAL);
+
+ if (*index >= SPA_N_ELEMENTS(impl_interfaces))
+ return 0;
+
+ *info = &impl_interfaces[(*index)++];
+
+ return 1;
+}
+
+const struct spa_handle_factory spa_v4l2_source_factory = {
+ SPA_VERSION_HANDLE_FACTORY,
+ SPA_NAME_API_V4L2_SOURCE,
+ NULL,
+ impl_get_size,
+ impl_init,
+ impl_enum_interface_info,
+};
diff --git a/spa/plugins/v4l2/v4l2-udev.c b/spa/plugins/v4l2/v4l2-udev.c
new file mode 100644
index 0000000..e7f9265
--- /dev/null
+++ b/spa/plugins/v4l2/v4l2-udev.c
@@ -0,0 +1,754 @@
+/* Spa V4l2 udev monitor
+ *
+ * Copyright © 2018 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 <stddef.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/inotify.h>
+#include <fcntl.h>
+
+#include <libudev.h>
+
+#include <spa/support/log.h>
+#include <spa/utils/type.h>
+#include <spa/utils/keys.h>
+#include <spa/utils/names.h>
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/support/loop.h>
+#include <spa/support/plugin.h>
+#include <spa/monitor/device.h>
+#include <spa/monitor/utils.h>
+
+#define NAME "v4l2-udev"
+
+#define MAX_DEVICES 64
+
+#define ACTION_ADD 0
+#define ACTION_REMOVE 1
+#define ACTION_DISABLE 2
+
+struct device {
+ uint32_t id;
+ struct udev_device *dev;
+ unsigned int accessible:1;
+ unsigned int ignored:1;
+ unsigned int emitted:1;
+};
+
+struct impl {
+ struct spa_handle handle;
+ struct spa_device device;
+
+ struct spa_log *log;
+ struct spa_loop *main_loop;
+
+ struct spa_hook_list hooks;
+
+ uint64_t info_all;
+ struct spa_device_info info;
+
+ struct udev *udev;
+ struct udev_monitor *umonitor;
+
+ struct device devices[MAX_DEVICES];
+ uint32_t n_devices;
+
+ struct spa_source source;
+ struct spa_source notify;
+};
+
+static int impl_udev_open(struct impl *this)
+{
+ if (this->udev == NULL) {
+ this->udev = udev_new();
+ if (this->udev == NULL)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static int impl_udev_close(struct impl *this)
+{
+ if (this->udev != NULL)
+ udev_unref(this->udev);
+ this->udev = NULL;
+ return 0;
+}
+
+static struct device *add_device(struct impl *this, uint32_t id, struct udev_device *dev)
+{
+ struct device *device;
+
+ if (this->n_devices >= MAX_DEVICES)
+ return NULL;
+ device = &this->devices[this->n_devices++];
+ spa_zero(*device);
+ device->id = id;
+ udev_device_ref(dev);
+ device->dev = dev;
+ return device;
+}
+
+static struct device *find_device(struct impl *this, uint32_t id)
+{
+ uint32_t i;
+ for (i = 0; i < this->n_devices; i++) {
+ if (this->devices[i].id == id)
+ return &this->devices[i];
+ }
+ return NULL;
+}
+
+static void remove_device(struct impl *this, struct device *device)
+{
+ udev_device_unref(device->dev);
+ *device = this->devices[--this->n_devices];
+}
+
+static void clear_devices(struct impl *this)
+{
+ uint32_t i;
+ for (i = 0; i < this->n_devices; i++)
+ udev_device_unref(this->devices[i].dev);
+ this->n_devices = 0;
+}
+
+static uint32_t get_device_id(struct impl *this, struct udev_device *dev)
+{
+ const char *str;
+
+ if ((str = udev_device_get_devnode(dev)) == NULL)
+ return SPA_ID_INVALID;
+
+ if (!(str = strrchr(str, '/')))
+ return SPA_ID_INVALID;
+
+ if (strlen(str) <= 6 || strncmp(str, "/video", 6) != 0)
+ return SPA_ID_INVALID;
+
+ return atoi(str + 6);
+}
+
+static int dehex(char x)
+{
+ if (x >= '0' && x <= '9')
+ return x - '0';
+ if (x >= 'A' && x <= 'F')
+ return x - 'A' + 10;
+ if (x >= 'a' && x <= 'f')
+ return x - 'a' + 10;
+ return -1;
+}
+
+static void unescape(const char *src, char *dst)
+{
+ const char *s;
+ char *d;
+ int h1 = 0, h2 = 0;
+ enum { TEXT, BACKSLASH, EX, FIRST } state = TEXT;
+
+ for (s = src, d = dst; *s; s++) {
+ switch (state) {
+ case TEXT:
+ if (*s == '\\')
+ state = BACKSLASH;
+ else
+ *(d++) = *s;
+ break;
+
+ case BACKSLASH:
+ if (*s == 'x')
+ state = EX;
+ else {
+ *(d++) = '\\';
+ *(d++) = *s;
+ state = TEXT;
+ }
+ break;
+
+ case EX:
+ h1 = dehex(*s);
+ if (h1 < 0) {
+ *(d++) = '\\';
+ *(d++) = 'x';
+ *(d++) = *s;
+ state = TEXT;
+ } else
+ state = FIRST;
+ break;
+
+ case FIRST:
+ h2 = dehex(*s);
+ if (h2 < 0) {
+ *(d++) = '\\';
+ *(d++) = 'x';
+ *(d++) = *(s-1);
+ *(d++) = *s;
+ } else
+ *(d++) = (char) (h1 << 4) | h2;
+ state = TEXT;
+ break;
+ }
+ }
+ switch (state) {
+ case TEXT:
+ break;
+ case BACKSLASH:
+ *(d++) = '\\';
+ break;
+ case EX:
+ *(d++) = '\\';
+ *(d++) = 'x';
+ break;
+ case FIRST:
+ *(d++) = '\\';
+ *(d++) = 'x';
+ *(d++) = *(s-1);
+ break;
+ }
+ *d = 0;
+}
+
+static int emit_object_info(struct impl *this, struct device *device)
+{
+ struct spa_device_object_info info;
+ uint32_t id = device->id;
+ struct udev_device *dev = device->dev;
+ const char *str;
+ struct spa_dict_item items[20];
+ uint32_t n_items = 0;
+
+ info = SPA_DEVICE_OBJECT_INFO_INIT();
+
+ info.type = SPA_TYPE_INTERFACE_Device;
+ info.factory_name = SPA_NAME_API_V4L2_DEVICE;
+ info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS |
+ SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
+ info.flags = 0;
+
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ENUM_API,"udev");
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "v4l2");
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Video/Device");
+
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_V4L2_PATH, udev_device_get_devnode(dev));
+
+ if ((str = udev_device_get_property_value(dev, "USEC_INITIALIZED")) && *str)
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PLUGGED_USEC, str);
+
+ str = udev_device_get_property_value(dev, "ID_PATH");
+ if (!(str && *str))
+ str = udev_device_get_syspath(dev);
+ if (str && *str) {
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS_PATH, str);
+ }
+ if ((str = udev_device_get_devpath(dev)) && *str) {
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SYSFS_PATH, str);
+ }
+ if ((str = udev_device_get_property_value(dev, "ID_ID")) && *str) {
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS_ID, str);
+ }
+ if ((str = udev_device_get_property_value(dev, "ID_BUS")) && *str) {
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, str);
+ }
+ if ((str = udev_device_get_property_value(dev, "SUBSYSTEM")) && *str) {
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SUBSYSTEM, str);
+ }
+ if ((str = udev_device_get_property_value(dev, "ID_VENDOR_ID")) && *str) {
+ int32_t val;
+ if (spa_atoi32(str, &val, 16)) {
+ char *dec = alloca(12); /* 0xffff is max */
+ snprintf(dec, 12, "0x%04x", val);
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, dec);
+ }
+ }
+ str = udev_device_get_property_value(dev, "ID_VENDOR_FROM_DATABASE");
+ if (!(str && *str)) {
+ str = udev_device_get_property_value(dev, "ID_VENDOR_ENC");
+ if (!(str && *str)) {
+ str = udev_device_get_property_value(dev, "ID_VENDOR");
+ } else {
+ char *t = alloca(strlen(str) + 1);
+ unescape(str, t);
+ str = t;
+ }
+ }
+ if (str && *str) {
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_NAME, str);
+ }
+ if ((str = udev_device_get_property_value(dev, "ID_MODEL_ID")) && *str) {
+ int32_t val;
+ if (spa_atoi32(str, &val, 16)) {
+ char *dec = alloca(12); /* 0xffff is max */
+ snprintf(dec, 12, "0x%04x", val);
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, dec);
+ }
+ }
+
+ str = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE");
+ if (!(str && *str)) {
+ str = udev_device_get_property_value(dev, "ID_MODEL_ENC");
+ if (!(str && *str)) {
+ str = udev_device_get_property_value(dev, "ID_MODEL");
+ if (!(str && *str))
+ str = udev_device_get_property_value(dev, "ID_V4L_PRODUCT");
+ } else {
+ char *t = alloca(strlen(str) + 1);
+ unescape(str, t);
+ str = t;
+ }
+ }
+ if (str && *str)
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_NAME, str);
+
+ if ((str = udev_device_get_property_value(dev, "ID_SERIAL")) && *str) {
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SERIAL, str);
+ }
+ if ((str = udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES")) && *str) {
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_CAPABILITIES, str);
+ }
+ info.props = &SPA_DICT_INIT(items, n_items);
+ spa_device_emit_object_info(&this->hooks, id, &info);
+ device->emitted = true;
+
+ return 1;
+}
+
+static bool check_access(struct impl *this, struct device *device)
+{
+ char path[128];
+
+ snprintf(path, sizeof(path), "/dev/video%u", device->id);
+ device->accessible = access(path, R_OK|W_OK) >= 0;
+ spa_log_debug(this->log, "%s accessible:%u", path, device->accessible);
+
+ return device->accessible;
+}
+
+static void process_device(struct impl *this, uint32_t action, struct udev_device *dev)
+{
+ uint32_t id;
+ struct device *device;
+ bool emitted;
+
+ if ((id = get_device_id(this, dev)) == SPA_ID_INVALID)
+ return;
+
+ device = find_device(this, id);
+ if (device && device->ignored)
+ return;
+
+ switch (action) {
+ case ACTION_ADD:
+ if (device == NULL)
+ device = add_device(this, id, dev);
+ if (device == NULL)
+ return;
+ if (!check_access(this, device))
+ return;
+ emit_object_info(this, device);
+ break;
+
+ case ACTION_REMOVE:
+ if (device == NULL)
+ return;
+ emitted = device->emitted;
+ remove_device(this, device);
+ if (emitted)
+ spa_device_emit_object_info(&this->hooks, id, NULL);
+ break;
+
+ case ACTION_DISABLE:
+ if (device == NULL)
+ return;
+ if (device->emitted) {
+ device->emitted = false;
+ spa_device_emit_object_info(&this->hooks, id, NULL);
+ }
+ break;
+ }
+}
+
+static int stop_inotify(struct impl *this)
+{
+ if (this->notify.fd == -1)
+ return 0;
+ spa_log_info(this->log, "stop inotify");
+ spa_loop_remove_source(this->main_loop, &this->notify);
+ close(this->notify.fd);
+ this->notify.fd = -1;
+ return 0;
+}
+
+static void impl_on_notify_events(struct spa_source *source)
+{
+ bool deleted = false;
+ struct impl *this = source->data;
+ union {
+ unsigned char name[sizeof(struct inotify_event) + NAME_MAX + 1];
+ struct inotify_event e; /* for appropriate alignment */
+ } buf;
+
+ while (true) {
+ ssize_t len;
+ const struct inotify_event *event;
+ void *p, *e;
+
+ len = read(source->fd, &buf, sizeof(buf));
+ if (len < 0 && errno != EAGAIN)
+ break;
+ if (len <= 0)
+ break;
+
+ e = SPA_PTROFF(&buf, len, void);
+
+ for (p = &buf; p < e;
+ p = SPA_PTROFF(p, sizeof(struct inotify_event) + event->len, void)) {
+ unsigned int id;
+ struct device *device;
+
+ event = (const struct inotify_event *) p;
+
+ if ((event->mask & IN_ATTRIB)) {
+ bool access;
+ if (sscanf(event->name, "video%u", &id) != 1)
+ continue;
+ if ((device = find_device(this, id)) == NULL)
+ continue;
+ access = check_access(this, device);
+ if (access && !device->emitted)
+ process_device(this, ACTION_ADD, device->dev);
+ else if (!access && device->emitted)
+ process_device(this, ACTION_DISABLE, device->dev);
+ }
+ /* /dev/ might have been removed */
+ if ((event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)))
+ deleted = true;
+ }
+ }
+ if (deleted)
+ stop_inotify(this);
+}
+
+static int start_inotify(struct impl *this)
+{
+ int res, notify_fd;
+
+ if (this->notify.fd != -1)
+ return 0;
+
+ if ((notify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK)) < 0)
+ return -errno;
+
+ res = inotify_add_watch(notify_fd, "/dev",
+ IN_ATTRIB | IN_CLOSE_WRITE | IN_DELETE_SELF | IN_MOVE_SELF);
+ if (res < 0) {
+ res = -errno;
+ close(notify_fd);
+
+ if (res == -ENOENT) {
+ spa_log_debug(this->log, "/dev/ does not exist yet");
+ return 0;
+ }
+ spa_log_error(this->log, "inotify_add_watch() failed: %s", spa_strerror(res));
+ return res;
+ }
+ spa_log_info(this->log, "start inotify");
+ this->notify.func = impl_on_notify_events;
+ this->notify.data = this;
+ this->notify.fd = notify_fd;
+ this->notify.mask = SPA_IO_IN | SPA_IO_ERR;
+
+ spa_loop_add_source(this->main_loop, &this->notify);
+
+ return 0;
+}
+
+static void impl_on_fd_events(struct spa_source *source)
+{
+ struct impl *this = source->data;
+ struct udev_device *dev;
+ const char *action;
+
+ dev = udev_monitor_receive_device(this->umonitor);
+ if (dev == NULL)
+ return;
+
+ if ((action = udev_device_get_action(dev)) == NULL)
+ action = "change";
+
+ spa_log_debug(this->log, "action %s", action);
+
+ start_inotify(this);
+
+ if (spa_streq(action, "add") ||
+ spa_streq(action, "change")) {
+ process_device(this, ACTION_ADD, dev);
+ } else if (spa_streq(action, "remove")) {
+ process_device(this, ACTION_REMOVE, dev);
+ }
+ udev_device_unref(dev);
+}
+
+static int start_monitor(struct impl *this)
+{
+ int res;
+
+ if (this->umonitor != NULL)
+ return 0;
+
+ this->umonitor = udev_monitor_new_from_netlink(this->udev, "udev");
+ if (this->umonitor == NULL)
+ return -ENOMEM;
+
+ udev_monitor_filter_add_match_subsystem_devtype(this->umonitor,
+ "video4linux", NULL);
+ udev_monitor_enable_receiving(this->umonitor);
+
+ this->source.func = impl_on_fd_events;
+ this->source.data = this;
+ this->source.fd = udev_monitor_get_fd(this->umonitor);
+ this->source.mask = SPA_IO_IN | SPA_IO_ERR;
+
+ spa_log_debug(this->log, "monitor %p", this->umonitor);
+ spa_loop_add_source(this->main_loop, &this->source);
+
+ if ((res = start_inotify(this)) < 0)
+ return res;
+
+ return 0;
+}
+
+static int stop_monitor(struct impl *this)
+{
+ if (this->umonitor == NULL)
+ return 0;
+
+ clear_devices (this);
+
+ spa_loop_remove_source(this->main_loop, &this->source);
+ udev_monitor_unref(this->umonitor);
+ this->umonitor = NULL;
+
+ stop_inotify(this);
+
+ return 0;
+}
+
+static int enum_devices(struct impl *this)
+{
+ struct udev_enumerate *enumerate;
+ struct udev_list_entry *devices;
+
+ enumerate = udev_enumerate_new(this->udev);
+ if (enumerate == NULL)
+ return -ENOMEM;
+
+ udev_enumerate_add_match_subsystem(enumerate, "video4linux");
+ udev_enumerate_scan_devices(enumerate);
+
+ for (devices = udev_enumerate_get_list_entry(enumerate); devices;
+ devices = udev_list_entry_get_next(devices)) {
+ struct udev_device *dev;
+
+ dev = udev_device_new_from_syspath(this->udev, udev_list_entry_get_name(devices));
+ if (dev == NULL)
+ continue;
+
+ process_device(this, ACTION_ADD, dev);
+
+ udev_device_unref(dev);
+ }
+ udev_enumerate_unref(enumerate);
+
+ return 0;
+}
+
+static const struct spa_dict_item device_info_items[] = {
+ { SPA_KEY_DEVICE_API, "udev" },
+ { SPA_KEY_DEVICE_NICK, "v4l2-udev" },
+ { SPA_KEY_API_UDEV_MATCH, "video4linux" },
+};
+
+static void emit_device_info(struct impl *this, bool full)
+{
+ uint64_t old = full ? this->info.change_mask : 0;
+ if (full)
+ this->info.change_mask = this->info_all;
+ if (this->info.change_mask) {
+ this->info.props = &SPA_DICT_INIT_ARRAY(device_info_items);
+ spa_device_emit_info(&this->hooks, &this->info);
+ this->info.change_mask = old;
+ }
+}
+
+static void impl_hook_removed(struct spa_hook *hook)
+{
+ struct impl *this = hook->priv;
+ if (spa_hook_list_is_empty(&this->hooks)) {
+ stop_monitor(this);
+ impl_udev_close(this);
+ }
+}
+
+static int
+impl_device_add_listener(void *object, struct spa_hook *listener,
+ const struct spa_device_events *events, void *data)
+{
+ int res;
+ struct impl *this = object;
+ struct spa_hook_list save;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(events != NULL, -EINVAL);
+
+ if ((res = impl_udev_open(this)) < 0)
+ return res;
+
+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
+
+ emit_device_info(this, true);
+
+ if ((res = enum_devices(this)) < 0)
+ return res;
+
+ if ((res = start_monitor(this)) < 0)
+ return res;
+
+ spa_hook_list_join(&this->hooks, &save);
+
+ listener->removed = impl_hook_removed;
+ listener->priv = this;
+
+ return 0;
+}
+
+static const struct spa_device_methods impl_device = {
+ SPA_VERSION_DEVICE_METHODS,
+ .add_listener = impl_device_add_listener,
+};
+
+static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
+{
+ struct impl *this;
+
+ spa_return_val_if_fail(handle != NULL, -EINVAL);
+ spa_return_val_if_fail(interface != NULL, -EINVAL);
+
+ this = (struct impl *) handle;
+
+ if (spa_streq(type, SPA_TYPE_INTERFACE_Device))
+ *interface = &this->device;
+ else
+ return -ENOENT;
+
+ return 0;
+}
+
+static int impl_clear(struct spa_handle *handle)
+{
+ struct impl *this = (struct impl *) handle;
+ stop_monitor(this);
+ impl_udev_close(this);
+ return 0;
+}
+
+static size_t
+impl_get_size(const struct spa_handle_factory *factory,
+ const struct spa_dict *params)
+{
+ return sizeof(struct impl);
+}
+
+static int
+impl_init(const struct spa_handle_factory *factory,
+ struct spa_handle *handle,
+ const struct spa_dict *info,
+ const struct spa_support *support,
+ uint32_t n_support)
+{
+ struct impl *this;
+
+ spa_return_val_if_fail(factory != NULL, -EINVAL);
+ spa_return_val_if_fail(handle != NULL, -EINVAL);
+
+ handle->get_interface = impl_get_interface;
+ handle->clear = impl_clear;
+
+ this = (struct impl *) handle;
+ this->notify.fd = -1;
+
+ this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
+ this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
+
+ if (this->main_loop == NULL) {
+ spa_log_error(this->log, "a main-loop is needed");
+ return -EINVAL;
+ }
+ spa_hook_list_init(&this->hooks);
+
+ this->device.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Device,
+ SPA_VERSION_DEVICE,
+ &impl_device, this);
+
+ this->info = SPA_DEVICE_INFO_INIT();
+ this->info_all = SPA_DEVICE_CHANGE_MASK_FLAGS |
+ SPA_DEVICE_CHANGE_MASK_PROPS;
+ this->info.flags = 0;
+
+ return 0;
+}
+
+static const struct spa_interface_info impl_interfaces[] = {
+ {SPA_TYPE_INTERFACE_Device,},
+};
+
+static int
+impl_enum_interface_info(const struct spa_handle_factory *factory,
+ const struct spa_interface_info **info,
+ uint32_t *index)
+{
+ spa_return_val_if_fail(factory != NULL, -EINVAL);
+ spa_return_val_if_fail(info != NULL, -EINVAL);
+ spa_return_val_if_fail(index != NULL, -EINVAL);
+
+ if (*index >= SPA_N_ELEMENTS(impl_interfaces))
+ return 0;
+
+ *info = &impl_interfaces[(*index)++];
+ return 1;
+}
+
+const struct spa_handle_factory spa_v4l2_udev_factory = {
+ SPA_VERSION_HANDLE_FACTORY,
+ SPA_NAME_API_V4L2_ENUM_UDEV,
+ NULL,
+ impl_get_size,
+ impl_init,
+ impl_enum_interface_info,
+};
diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c
new file mode 100644
index 0000000..8ffda13
--- /dev/null
+++ b/spa/plugins/v4l2/v4l2-utils.c
@@ -0,0 +1,1743 @@
+/* Spa
+ *
+ * Copyright © 2018 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 <string.h>
+#include <unistd.h>
+#include <sched.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <poll.h>
+
+#include <spa/utils/result.h>
+
+static int xioctl(int fd, int request, void *arg)
+{
+ int err;
+
+ do {
+ err = ioctl(fd, request, arg);
+ } while (err == -1 && errno == EINTR);
+
+ return err;
+}
+
+int spa_v4l2_open(struct spa_v4l2_device *dev, const char *path)
+{
+ struct stat st;
+ int err;
+
+ if (dev->fd != -1)
+ return 0;
+
+ if (path == NULL) {
+ spa_log_error(dev->log, "Device property not set");
+ return -EIO;
+ }
+
+ spa_log_info(dev->log, "device is '%s'", path);
+
+ dev->fd = open(path, O_RDWR | O_NONBLOCK, 0);
+ if (dev->fd == -1) {
+ err = errno;
+ spa_log_error(dev->log, "Cannot open '%s': %d, %s",
+ path, err, strerror(err));
+ goto error;
+ }
+
+ if (fstat(dev->fd, &st) < 0) {
+ err = errno;
+ spa_log_error(dev->log, "Cannot identify '%s': %d, %s",
+ path, err, strerror(err));
+ goto error_close;
+ }
+
+ if (!S_ISCHR(st.st_mode)) {
+ spa_log_error(dev->log, "%s is no device", path);
+ err = ENODEV;
+ goto error_close;
+ }
+
+ if (xioctl(dev->fd, VIDIOC_QUERYCAP, &dev->cap) < 0) {
+ err = errno;
+ spa_log_error(dev->log, "'%s' QUERYCAP: %m", path);
+ goto error_close;
+ }
+ snprintf(dev->path, sizeof(dev->path), "%s", path);
+ return 0;
+
+error_close:
+ close(dev->fd);
+ dev->fd = -1;
+error:
+ return -err;
+}
+
+int spa_v4l2_is_capture(struct spa_v4l2_device *dev)
+{
+ uint32_t caps = dev->cap.capabilities;
+ if ((caps & V4L2_CAP_DEVICE_CAPS))
+ caps = dev->cap.device_caps;
+ return (caps & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE;
+}
+
+int spa_v4l2_close(struct spa_v4l2_device *dev)
+{
+ if (dev->fd == -1)
+ return 0;
+
+ if (dev->active || dev->have_format)
+ return 0;
+
+ spa_log_info(dev->log, "close '%s'", dev->path);
+
+ if (close(dev->fd))
+ spa_log_warn(dev->log, "close: %m");
+
+ dev->fd = -1;
+ return 0;
+}
+
+static int spa_v4l2_buffer_recycle(struct impl *this, uint32_t buffer_id)
+{
+ struct port *port = &this->out_ports[0];
+ struct buffer *b = &port->buffers[buffer_id];
+ struct spa_v4l2_device *dev = &port->dev;
+ int err;
+
+ if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING))
+ return 0;
+
+ SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUTSTANDING);
+ spa_log_trace(this->log, "v4l2 %p: recycle buffer %d", this, buffer_id);
+
+ if (xioctl(dev->fd, VIDIOC_QBUF, &b->v4l2_buffer) < 0) {
+ err = errno;
+ spa_log_error(this->log, "'%s' VIDIOC_QBUF: %m", this->props.device);
+ return -err;
+ }
+
+ return 0;
+}
+
+static int spa_v4l2_clear_buffers(struct impl *this)
+{
+ struct port *port = &this->out_ports[0];
+ struct v4l2_requestbuffers reqbuf;
+ uint32_t i;
+
+ if (port->n_buffers == 0)
+ return 0;
+
+ for (i = 0; i < port->n_buffers; i++) {
+ struct buffer *b;
+ struct spa_data *d;
+
+ b = &port->buffers[i];
+ d = b->outbuf->datas;
+
+ if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) {
+ spa_log_debug(this->log, "queueing outstanding buffer %p", b);
+ spa_v4l2_buffer_recycle(this, i);
+ }
+ if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) {
+ munmap(b->ptr, d[0].maxsize);
+ }
+ if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ALLOCATED)) {
+ spa_log_debug(this->log, "close %d", (int) d[0].fd);
+ close(d[0].fd);
+ }
+ d[0].type = SPA_ID_INVALID;
+ }
+
+ spa_zero(reqbuf);
+ reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ reqbuf.memory = port->memtype;
+ reqbuf.count = 0;
+
+ if (xioctl(port->dev.fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
+ spa_log_warn(this->log, "VIDIOC_REQBUFS: %m");
+ }
+ port->n_buffers = 0;
+
+ return 0;
+}
+
+
+struct format_info {
+ uint32_t fourcc;
+ uint32_t format;
+ uint32_t media_type;
+ uint32_t media_subtype;
+};
+
+#define VIDEO SPA_MEDIA_TYPE_video
+#define IMAGE SPA_MEDIA_TYPE_image
+
+#define RAW SPA_MEDIA_SUBTYPE_raw
+
+#define BAYER SPA_MEDIA_SUBTYPE_bayer
+#define MJPG SPA_MEDIA_SUBTYPE_mjpg
+#define JPEG SPA_MEDIA_SUBTYPE_jpeg
+#define DV SPA_MEDIA_SUBTYPE_dv
+#define MPEGTS SPA_MEDIA_SUBTYPE_mpegts
+#define H264 SPA_MEDIA_SUBTYPE_h264
+#define H263 SPA_MEDIA_SUBTYPE_h263
+#define MPEG1 SPA_MEDIA_SUBTYPE_mpeg1
+#define MPEG2 SPA_MEDIA_SUBTYPE_mpeg2
+#define MPEG4 SPA_MEDIA_SUBTYPE_mpeg4
+#define XVID SPA_MEDIA_SUBTYPE_xvid
+#define VC1 SPA_MEDIA_SUBTYPE_vc1
+#define VP8 SPA_MEDIA_SUBTYPE_vp8
+
+#define FORMAT_UNKNOWN SPA_VIDEO_FORMAT_UNKNOWN
+#define FORMAT_ENCODED SPA_VIDEO_FORMAT_ENCODED
+#define FORMAT_RGB15 SPA_VIDEO_FORMAT_RGB15
+#define FORMAT_BGR15 SPA_VIDEO_FORMAT_BGR15
+#define FORMAT_RGB16 SPA_VIDEO_FORMAT_RGB16
+#define FORMAT_BGR SPA_VIDEO_FORMAT_BGR
+#define FORMAT_RGB SPA_VIDEO_FORMAT_RGB
+#define FORMAT_BGRA SPA_VIDEO_FORMAT_BGRA
+#define FORMAT_BGRx SPA_VIDEO_FORMAT_BGRx
+#define FORMAT_ARGB SPA_VIDEO_FORMAT_ARGB
+#define FORMAT_xRGB SPA_VIDEO_FORMAT_xRGB
+#define FORMAT_GRAY8 SPA_VIDEO_FORMAT_GRAY8
+#define FORMAT_GRAY16_LE SPA_VIDEO_FORMAT_GRAY16_LE
+#define FORMAT_GRAY16_BE SPA_VIDEO_FORMAT_GRAY16_BE
+#define FORMAT_YVU9 SPA_VIDEO_FORMAT_YVU9
+#define FORMAT_YV12 SPA_VIDEO_FORMAT_YV12
+#define FORMAT_YUY2 SPA_VIDEO_FORMAT_YUY2
+#define FORMAT_YVYU SPA_VIDEO_FORMAT_YVYU
+#define FORMAT_UYVY SPA_VIDEO_FORMAT_UYVY
+#define FORMAT_Y42B SPA_VIDEO_FORMAT_Y42B
+#define FORMAT_Y41B SPA_VIDEO_FORMAT_Y41B
+#define FORMAT_YUV9 SPA_VIDEO_FORMAT_YUV9
+#define FORMAT_I420 SPA_VIDEO_FORMAT_I420
+#define FORMAT_NV12 SPA_VIDEO_FORMAT_NV12
+#define FORMAT_NV12_64Z32 SPA_VIDEO_FORMAT_NV12_64Z32
+#define FORMAT_NV21 SPA_VIDEO_FORMAT_NV21
+#define FORMAT_NV16 SPA_VIDEO_FORMAT_NV16
+#define FORMAT_NV61 SPA_VIDEO_FORMAT_NV61
+#define FORMAT_NV24 SPA_VIDEO_FORMAT_NV24
+
+static const struct format_info format_info[] = {
+ /* RGB formats */
+ {V4L2_PIX_FMT_RGB332, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_ARGB555, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_XRGB555, FORMAT_RGB15, VIDEO, RAW},
+ {V4L2_PIX_FMT_ARGB555X, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_XRGB555X, FORMAT_BGR15, VIDEO, RAW},
+ {V4L2_PIX_FMT_RGB565, FORMAT_RGB16, VIDEO, RAW},
+ {V4L2_PIX_FMT_RGB565X, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_BGR666, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_BGR24, FORMAT_BGR, VIDEO, RAW},
+ {V4L2_PIX_FMT_RGB24, FORMAT_RGB, VIDEO, RAW},
+ {V4L2_PIX_FMT_ABGR32, FORMAT_BGRA, VIDEO, RAW},
+ {V4L2_PIX_FMT_XBGR32, FORMAT_BGRx, VIDEO, RAW},
+ {V4L2_PIX_FMT_ARGB32, FORMAT_ARGB, VIDEO, RAW},
+ {V4L2_PIX_FMT_XRGB32, FORMAT_xRGB, VIDEO, RAW},
+
+ /* Deprecated Packed RGB Image Formats (alpha ambiguity) */
+ {V4L2_PIX_FMT_RGB444, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_RGB555, FORMAT_RGB15, VIDEO, RAW},
+ {V4L2_PIX_FMT_RGB555X, FORMAT_BGR15, VIDEO, RAW},
+ {V4L2_PIX_FMT_BGR32, FORMAT_BGRx, VIDEO, RAW},
+ {V4L2_PIX_FMT_RGB32, FORMAT_xRGB, VIDEO, RAW},
+
+ /* Grey formats */
+ {V4L2_PIX_FMT_GREY, FORMAT_GRAY8, VIDEO, RAW},
+ {V4L2_PIX_FMT_Y4, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_Y6, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_Y10, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_Y12, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_Y16, FORMAT_GRAY16_LE, VIDEO, RAW},
+ {V4L2_PIX_FMT_Y16_BE, FORMAT_GRAY16_BE, VIDEO, RAW},
+ {V4L2_PIX_FMT_Y10BPACK, FORMAT_UNKNOWN, VIDEO, RAW},
+
+ /* Palette formats */
+ {V4L2_PIX_FMT_PAL8, FORMAT_UNKNOWN, VIDEO, RAW},
+
+ /* Chrominance formats */
+ {V4L2_PIX_FMT_UV8, FORMAT_UNKNOWN, VIDEO, RAW},
+
+ /* Luminance+Chrominance formats */
+ {V4L2_PIX_FMT_YVU410, FORMAT_YVU9, VIDEO, RAW},
+ {V4L2_PIX_FMT_YVU420, FORMAT_YV12, VIDEO, RAW},
+ {V4L2_PIX_FMT_YVU420M, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUYV, FORMAT_YUY2, VIDEO, RAW},
+ {V4L2_PIX_FMT_YYUV, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_YVYU, FORMAT_YVYU, VIDEO, RAW},
+ {V4L2_PIX_FMT_UYVY, FORMAT_UYVY, VIDEO, RAW},
+ {V4L2_PIX_FMT_VYUY, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUV422P, FORMAT_Y42B, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUV411P, FORMAT_Y41B, VIDEO, RAW},
+ {V4L2_PIX_FMT_Y41P, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUV444, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUV555, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUV565, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUV32, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUV410, FORMAT_YUV9, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUV420, FORMAT_I420, VIDEO, RAW},
+ {V4L2_PIX_FMT_YUV420M, FORMAT_I420, VIDEO, RAW},
+ {V4L2_PIX_FMT_HI240, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_HM12, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_M420, FORMAT_UNKNOWN, VIDEO, RAW},
+
+ /* two planes -- one Y, one Cr + Cb interleaved */
+ {V4L2_PIX_FMT_NV12, FORMAT_NV12, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV12M, FORMAT_NV12, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV12MT, FORMAT_NV12_64Z32, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV12MT_16X16, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV21, FORMAT_NV21, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV21M, FORMAT_NV21, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV16, FORMAT_NV16, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV16M, FORMAT_NV16, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV61, FORMAT_NV61, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV61M, FORMAT_NV61, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV24, FORMAT_NV24, VIDEO, RAW},
+ {V4L2_PIX_FMT_NV42, FORMAT_UNKNOWN, VIDEO, RAW},
+
+ /* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */
+ {V4L2_PIX_FMT_SBGGR8, FORMAT_UNKNOWN, VIDEO, BAYER},
+ {V4L2_PIX_FMT_SGBRG8, FORMAT_UNKNOWN, VIDEO, BAYER},
+ {V4L2_PIX_FMT_SGRBG8, FORMAT_UNKNOWN, VIDEO, BAYER},
+ {V4L2_PIX_FMT_SRGGB8, FORMAT_UNKNOWN, VIDEO, BAYER},
+
+ /* compressed formats */
+ {V4L2_PIX_FMT_MJPEG, FORMAT_ENCODED, VIDEO, MJPG},
+ {V4L2_PIX_FMT_JPEG, FORMAT_ENCODED, VIDEO, MJPG},
+ {V4L2_PIX_FMT_PJPG, FORMAT_ENCODED, VIDEO, MJPG},
+ {V4L2_PIX_FMT_DV, FORMAT_ENCODED, VIDEO, DV},
+ {V4L2_PIX_FMT_MPEG, FORMAT_ENCODED, VIDEO, MPEGTS},
+ {V4L2_PIX_FMT_H264, FORMAT_ENCODED, VIDEO, H264},
+ {V4L2_PIX_FMT_H264_NO_SC, FORMAT_ENCODED, VIDEO, H264},
+ {V4L2_PIX_FMT_H264_MVC, FORMAT_ENCODED, VIDEO, H264},
+ {V4L2_PIX_FMT_H263, FORMAT_ENCODED, VIDEO, H263},
+ {V4L2_PIX_FMT_MPEG1, FORMAT_ENCODED, VIDEO, MPEG1},
+ {V4L2_PIX_FMT_MPEG2, FORMAT_ENCODED, VIDEO, MPEG2},
+ {V4L2_PIX_FMT_MPEG4, FORMAT_ENCODED, VIDEO, MPEG4},
+ {V4L2_PIX_FMT_XVID, FORMAT_ENCODED, VIDEO, XVID},
+ {V4L2_PIX_FMT_VC1_ANNEX_G, FORMAT_ENCODED, VIDEO, VC1},
+ {V4L2_PIX_FMT_VC1_ANNEX_L, FORMAT_ENCODED, VIDEO, VC1},
+ {V4L2_PIX_FMT_VP8, FORMAT_ENCODED, VIDEO, VP8},
+
+ /* Vendor-specific formats */
+ {V4L2_PIX_FMT_WNVA, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_SN9C10X, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_PWC1, FORMAT_UNKNOWN, VIDEO, RAW},
+ {V4L2_PIX_FMT_PWC2, FORMAT_UNKNOWN, VIDEO, RAW},
+};
+
+static const struct format_info *fourcc_to_format_info(uint32_t fourcc)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(format_info, i) {
+ if (i->fourcc == fourcc)
+ return i;
+ }
+ return NULL;
+}
+
+#if 0
+static const struct format_info *video_format_to_format_info(uint32_t format)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(format_info, i) {
+ if (i->format == format)
+ return i;
+ }
+ return NULL;
+}
+#endif
+
+static const struct format_info *find_format_info_by_media_type(uint32_t type,
+ uint32_t subtype,
+ uint32_t format,
+ int startidx)
+{
+ size_t i;
+
+ for (i = startidx; i < SPA_N_ELEMENTS(format_info); i++) {
+ const struct format_info *fi = &format_info[i];
+ if ((fi->media_type == type) &&
+ (fi->media_subtype == subtype) &&
+ (format == 0 || fi->format == format))
+ return fi;
+ }
+ return NULL;
+}
+
+static uint32_t
+enum_filter_format(uint32_t media_type, int32_t media_subtype,
+ const struct spa_pod *filter, uint32_t index)
+{
+ uint32_t video_format = 0;
+
+ switch (media_type) {
+ case SPA_MEDIA_TYPE_video:
+ case SPA_MEDIA_TYPE_image:
+ if (media_subtype == SPA_MEDIA_SUBTYPE_raw) {
+ const struct spa_pod_prop *p;
+ const struct spa_pod *val;
+ uint32_t n_values, choice;
+ const uint32_t *values;
+
+ if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_format)))
+ return SPA_VIDEO_FORMAT_UNKNOWN;
+
+ val = spa_pod_get_values(&p->value, &n_values, &choice);
+
+ if (val->type != SPA_TYPE_Id)
+ return SPA_VIDEO_FORMAT_UNKNOWN;
+
+ values = SPA_POD_BODY(val);
+
+ if (choice == SPA_CHOICE_None) {
+ if (index == 0)
+ video_format = values[0];
+ } else {
+ if (index + 1 < n_values)
+ video_format = values[index + 1];
+ }
+ } else {
+ if (index == 0)
+ video_format = SPA_VIDEO_FORMAT_ENCODED;
+ }
+ }
+ return video_format;
+}
+
+static bool
+filter_framesize(struct v4l2_frmsizeenum *frmsize,
+ const struct spa_rectangle *min,
+ const struct spa_rectangle *max,
+ const struct spa_rectangle *step)
+{
+ if (frmsize->type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+ if (frmsize->discrete.width < min->width ||
+ frmsize->discrete.height < min->height ||
+ frmsize->discrete.width > max->width ||
+ frmsize->discrete.height > max->height) {
+ return false;
+ }
+ } else if (frmsize->type == V4L2_FRMSIZE_TYPE_CONTINUOUS ||
+ frmsize->type == V4L2_FRMSIZE_TYPE_STEPWISE) {
+ /* FIXME, use LCM */
+ frmsize->stepwise.step_width *= step->width;
+ frmsize->stepwise.step_height *= step->height;
+
+ if (frmsize->stepwise.max_width < min->width ||
+ frmsize->stepwise.max_height < min->height ||
+ frmsize->stepwise.min_width > max->width ||
+ frmsize->stepwise.min_height > max->height)
+ return false;
+
+ frmsize->stepwise.min_width = SPA_MAX(frmsize->stepwise.min_width, min->width);
+ frmsize->stepwise.min_height = SPA_MAX(frmsize->stepwise.min_height, min->height);
+ frmsize->stepwise.max_width = SPA_MIN(frmsize->stepwise.max_width, max->width);
+ frmsize->stepwise.max_height = SPA_MIN(frmsize->stepwise.max_height, max->height);
+ } else
+ return false;
+
+ return true;
+}
+
+static int compare_fraction(struct v4l2_fract *f1, const struct spa_fraction *f2)
+{
+ uint64_t n1, n2;
+
+ /* fractions are reduced when set, so we can quickly see if they're equal */
+ if (f1->denominator == f2->num && f1->numerator == f2->denom)
+ return 0;
+
+ /* extend to 64 bits */
+ n1 = ((int64_t) f1->denominator) * f2->denom;
+ n2 = ((int64_t) f1->numerator) * f2->num;
+ if (n1 < n2)
+ return -1;
+ return 1;
+}
+
+static bool
+filter_framerate(struct v4l2_frmivalenum *frmival,
+ const struct spa_fraction *min,
+ const struct spa_fraction *max,
+ const struct spa_fraction *step)
+{
+ if (frmival->type == V4L2_FRMIVAL_TYPE_DISCRETE) {
+ if (compare_fraction(&frmival->discrete, min) < 0 ||
+ compare_fraction(&frmival->discrete, max) > 0)
+ return false;
+ } else if (frmival->type == V4L2_FRMIVAL_TYPE_CONTINUOUS ||
+ frmival->type == V4L2_FRMIVAL_TYPE_STEPWISE) {
+ /* FIXME, use LCM */
+ frmival->stepwise.step.denominator *= step->num;
+ frmival->stepwise.step.numerator *= step->denom;
+
+ if (compare_fraction(&frmival->stepwise.max, min) < 0 ||
+ compare_fraction(&frmival->stepwise.min, max) > 0)
+ return false;
+
+ if (compare_fraction(&frmival->stepwise.min, min) < 0) {
+ frmival->stepwise.min.denominator = min->num;
+ frmival->stepwise.min.numerator = min->denom;
+ }
+ if (compare_fraction(&frmival->stepwise.max, max) > 0) {
+ frmival->stepwise.max.denominator = max->num;
+ frmival->stepwise.max.numerator = max->denom;
+ }
+ } else
+ return false;
+
+ return true;
+}
+
+#define FOURCC_ARGS(f) (f)&0x7f,((f)>>8)&0x7f,((f)>>16)&0x7f,((f)>>24)&0x7f
+
+static int
+spa_v4l2_enum_format(struct impl *this, int seq,
+ uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct port *port = &this->out_ports[0];
+ int res, n_fractions;
+ const struct format_info *info;
+ struct spa_pod_choice *choice;
+ uint32_t filter_media_type, filter_media_subtype, video_format;
+ struct spa_v4l2_device *dev = &port->dev;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ struct spa_pod_frame f[2];
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+
+ if ((res = spa_v4l2_open(dev, this->props.device)) < 0)
+ return res;
+
+ result.id = SPA_PARAM_EnumFormat;
+ result.next = start;
+
+ if (result.next == 0) {
+ spa_zero(port->fmtdesc);
+ port->fmtdesc.index = 0;
+ port->fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ port->next_fmtdesc = true;
+ spa_zero(port->frmsize);
+ port->next_frmsize = true;
+ spa_zero(port->frmival);
+ }
+
+ if (filter) {
+ if ((res = spa_format_parse(filter, &filter_media_type, &filter_media_subtype)) < 0)
+ return res;
+ }
+
+ if (false) {
+ next_fmtdesc:
+ port->fmtdesc.index++;
+ port->next_fmtdesc = true;
+ }
+
+ next:
+ result.index = result.next++;
+
+ while (port->next_fmtdesc) {
+ if (filter) {
+ struct v4l2_format fmt;
+
+ video_format = enum_filter_format(filter_media_type,
+ filter_media_subtype,
+ filter, port->fmtdesc.index);
+
+ if (video_format == SPA_VIDEO_FORMAT_UNKNOWN)
+ goto enum_end;
+
+ info = find_format_info_by_media_type(filter_media_type,
+ filter_media_subtype,
+ video_format, 0);
+ if (info == NULL)
+ goto next_fmtdesc;
+
+ port->fmtdesc.pixelformat = info->fourcc;
+
+ spa_zero(fmt);
+ fmt.type = port->fmtdesc.type;
+ fmt.fmt.pix.pixelformat = info->fourcc;
+ fmt.fmt.pix.field = V4L2_FIELD_ANY;
+ fmt.fmt.pix.width = 0;
+ fmt.fmt.pix.height = 0;
+
+ if ((res = xioctl(dev->fd, VIDIOC_TRY_FMT, &fmt)) < 0) {
+ spa_log_debug(this->log, "'%s' VIDIOC_TRY_FMT %08x: %m",
+ this->props.device, info->fourcc);
+ goto next_fmtdesc;
+ }
+ if (fmt.fmt.pix.pixelformat != info->fourcc) {
+ spa_log_debug(this->log, "'%s' VIDIOC_TRY_FMT wanted %.4s gave %.4s",
+ this->props.device, (char*)&info->fourcc,
+ (char*)&fmt.fmt.pix.pixelformat);
+ goto next_fmtdesc;
+ }
+
+ } else {
+ if ((res = xioctl(dev->fd, VIDIOC_ENUM_FMT, &port->fmtdesc)) < 0) {
+ if (errno == EINVAL)
+ goto enum_end;
+
+ res = -errno;
+ spa_log_error(this->log, "'%s' VIDIOC_ENUM_FMT: %m",
+ this->props.device);
+ goto exit;
+ }
+ }
+ port->next_fmtdesc = false;
+ port->frmsize.index = 0;
+ port->frmsize.pixel_format = port->fmtdesc.pixelformat;
+ port->next_frmsize = true;
+ }
+ if (!(info = fourcc_to_format_info(port->fmtdesc.pixelformat)))
+ goto next_fmtdesc;
+
+ next_frmsize:
+ while (port->next_frmsize) {
+ if (filter) {
+ const struct spa_pod_prop *p;
+ struct spa_pod *val;
+ uint32_t n_vals, choice;
+
+ /* check if we have a fixed frame size */
+ if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_size)))
+ goto do_frmsize;
+
+ val = spa_pod_get_values(&p->value, &n_vals, &choice);
+ if (val->type != SPA_TYPE_Rectangle)
+ goto enum_end;
+
+ if (choice == SPA_CHOICE_None) {
+ const struct spa_rectangle *values = SPA_POD_BODY(val);
+
+ if (port->frmsize.index > 0)
+ goto next_fmtdesc;
+
+ port->frmsize.type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ port->frmsize.discrete.width = values[0].width;
+ port->frmsize.discrete.height = values[0].height;
+ goto have_size;
+ }
+ }
+ do_frmsize:
+ if ((res = xioctl(dev->fd, VIDIOC_ENUM_FRAMESIZES, &port->frmsize)) < 0) {
+ if (errno == EINVAL)
+ goto next_fmtdesc;
+
+ res = -errno;
+ spa_log_error(this->log, "'%s' VIDIOC_ENUM_FRAMESIZES: %m",
+ this->props.device);
+ goto exit;
+ }
+ if (filter) {
+ static const struct spa_rectangle step = {1, 1};
+
+ const struct spa_rectangle *values;
+ const struct spa_pod_prop *p;
+ struct spa_pod *val;
+ uint32_t choice, i, n_values;
+
+ /* check if we have a fixed frame size */
+ if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_size)))
+ goto have_size;
+
+ val = spa_pod_get_values(&p->value, &n_values, &choice);
+ if (val->type != SPA_TYPE_Rectangle)
+ goto have_size;
+
+ values = SPA_POD_BODY_CONST(val);
+
+ if (choice == SPA_CHOICE_Range && n_values > 2) {
+ if (filter_framesize(&port->frmsize, &values[1], &values[2], &step))
+ goto have_size;
+ } else if (choice == SPA_CHOICE_Step && n_values > 3) {
+ if (filter_framesize(&port->frmsize, &values[1], &values[2], &values[3]))
+ goto have_size;
+ } else if (choice == SPA_CHOICE_Enum) {
+ for (i = 1; i < n_values; i++) {
+ if (filter_framesize(&port->frmsize, &values[i], &values[i], &step))
+ goto have_size;
+ }
+ }
+ /* nothing matches the filter, get next frame size */
+ port->frmsize.index++;
+ continue;
+ }
+
+ have_size:
+ if (port->frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+ /* we have a fixed size, use this to get the frame intervals */
+ port->frmival.index = 0;
+ port->frmival.pixel_format = port->frmsize.pixel_format;
+ port->frmival.width = port->frmsize.discrete.width;
+ port->frmival.height = port->frmsize.discrete.height;
+ port->next_frmsize = false;
+ } else if (port->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS ||
+ port->frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
+ /* we have a non fixed size, fix to something sensible to get the
+ * framerate */
+ port->frmival.index = 0;
+ port->frmival.pixel_format = port->frmsize.pixel_format;
+ port->frmival.width = port->frmsize.stepwise.min_width;
+ port->frmival.height = port->frmsize.stepwise.min_height;
+ port->next_frmsize = false;
+ } else {
+ port->frmsize.index++;
+ }
+ }
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
+ spa_pod_builder_add(&b,
+ SPA_FORMAT_mediaType, SPA_POD_Id(info->media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(info->media_subtype),
+ 0);
+
+ if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) {
+ spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_format, 0);
+ spa_pod_builder_id(&b, info->format);
+ }
+ spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_size, 0);
+ if (port->frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+ spa_pod_builder_rectangle(&b,
+ port->frmsize.discrete.width,
+ port->frmsize.discrete.height);
+ } else if (port->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS ||
+ port->frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
+ spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_None, 0);
+ choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b, &f[1]);
+
+ spa_pod_builder_rectangle(&b,
+ port->frmsize.stepwise.min_width,
+ port->frmsize.stepwise.min_height);
+ spa_pod_builder_rectangle(&b,
+ port->frmsize.stepwise.min_width,
+ port->frmsize.stepwise.min_height);
+ spa_pod_builder_rectangle(&b,
+ port->frmsize.stepwise.max_width,
+ port->frmsize.stepwise.max_height);
+
+ if (port->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
+ choice->body.type = SPA_CHOICE_Range;
+ } else {
+ choice->body.type = SPA_CHOICE_Step;
+ spa_pod_builder_rectangle(&b,
+ port->frmsize.stepwise.max_width,
+ port->frmsize.stepwise.max_height);
+ }
+ spa_pod_builder_pop(&b, &f[1]);
+ }
+
+ spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_framerate, 0);
+
+ n_fractions = 0;
+
+ spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_None, 0);
+ choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b, &f[1]);
+ port->frmival.index = 0;
+
+ while (true) {
+ if ((res = xioctl(dev->fd, VIDIOC_ENUM_FRAMEINTERVALS, &port->frmival)) < 0) {
+ res = -errno;
+ if (errno == EINVAL) {
+ port->frmsize.index++;
+ port->next_frmsize = true;
+ if (port->frmival.index == 0)
+ goto next_frmsize;
+ break;
+ }
+ spa_log_error(this->log, "'%s' VIDIOC_ENUM_FRAMEINTERVALS: %m",
+ this->props.device);
+ goto exit;
+ }
+ if (filter) {
+ static const struct spa_fraction step = {1, 1};
+
+ const struct spa_fraction *values;
+ const struct spa_pod_prop *p;
+ struct spa_pod *val;
+ uint32_t i, n_values, choice;
+
+ if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_framerate)))
+ goto have_framerate;
+
+ val = spa_pod_get_values(&p->value, &n_values, &choice);
+
+ if (val->type != SPA_TYPE_Fraction)
+ goto enum_end;
+
+ values = SPA_POD_BODY(val);
+
+ switch (choice) {
+ case SPA_CHOICE_None:
+ if (filter_framerate(&port->frmival, &values[0], &values[0], &step))
+ goto have_framerate;
+ break;
+
+ case SPA_CHOICE_Range:
+ if (n_values > 2 && filter_framerate(&port->frmival, &values[1], &values[2], &step))
+ goto have_framerate;
+ break;
+
+ case SPA_CHOICE_Step:
+ if (n_values > 3 && filter_framerate(&port->frmival, &values[1], &values[2], &values[3]))
+ goto have_framerate;
+ break;
+
+ case SPA_CHOICE_Enum:
+ for (i = 1; i < n_values; i++) {
+ if (filter_framerate(&port->frmival, &values[i], &values[i], &step))
+ goto have_framerate;
+ }
+ break;
+ default:
+ break;
+ }
+ port->frmival.index++;
+ continue;
+ }
+
+ have_framerate:
+
+ if (port->frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
+ choice->body.type = SPA_CHOICE_Enum;
+ if (n_fractions == 0)
+ spa_pod_builder_fraction(&b,
+ port->frmival.discrete.denominator,
+ port->frmival.discrete.numerator);
+ spa_pod_builder_fraction(&b,
+ port->frmival.discrete.denominator,
+ port->frmival.discrete.numerator);
+ port->frmival.index++;
+ } else if (port->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS ||
+ port->frmival.type == V4L2_FRMIVAL_TYPE_STEPWISE) {
+ if (n_fractions == 0)
+ spa_pod_builder_fraction(&b, 25, 1);
+ spa_pod_builder_fraction(&b,
+ port->frmival.stepwise.min.denominator,
+ port->frmival.stepwise.min.numerator);
+ spa_pod_builder_fraction(&b,
+ port->frmival.stepwise.max.denominator,
+ port->frmival.stepwise.max.numerator);
+
+ if (port->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) {
+ choice->body.type = SPA_CHOICE_Range;
+ } else {
+ choice->body.type = SPA_CHOICE_Step;
+ spa_pod_builder_fraction(&b,
+ port->frmival.stepwise.step.denominator,
+ port->frmival.stepwise.step.numerator);
+ }
+
+ port->frmsize.index++;
+ port->next_frmsize = true;
+ break;
+ }
+ n_fractions++;
+ }
+ if (n_fractions <= 1)
+ choice->body.type = SPA_CHOICE_None;
+
+ spa_pod_builder_pop(&b, &f[1]);
+ result.param = spa_pod_builder_pop(&b, &f[0]);
+
+ spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+
+ if (++count != num)
+ goto next;
+
+ enum_end:
+ res = 0;
+ exit:
+ spa_v4l2_close(dev);
+ return res;
+}
+
+static int spa_v4l2_set_format(struct impl *this, struct spa_video_info *format, uint32_t flags)
+{
+ struct port *port = &this->out_ports[0];
+ struct spa_v4l2_device *dev = &port->dev;
+ int res, cmd;
+ struct v4l2_format reqfmt, fmt;
+ struct v4l2_streamparm streamparm;
+ const struct format_info *info = NULL;
+ uint32_t video_format;
+ struct spa_rectangle *size = NULL;
+ struct spa_fraction *framerate = NULL;
+ bool match;
+
+ spa_zero(fmt);
+ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ spa_zero(streamparm);
+ streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ switch (format->media_subtype) {
+ case SPA_MEDIA_SUBTYPE_raw:
+ video_format = format->info.raw.format;
+ size = &format->info.raw.size;
+ framerate = &format->info.raw.framerate;
+ break;
+ case SPA_MEDIA_SUBTYPE_mjpg:
+ case SPA_MEDIA_SUBTYPE_jpeg:
+ video_format = SPA_VIDEO_FORMAT_ENCODED;
+ size = &format->info.mjpg.size;
+ framerate = &format->info.mjpg.framerate;
+ break;
+ case SPA_MEDIA_SUBTYPE_h264:
+ video_format = SPA_VIDEO_FORMAT_ENCODED;
+ size = &format->info.h264.size;
+ framerate = &format->info.h264.framerate;
+ break;
+ default:
+ video_format = SPA_VIDEO_FORMAT_ENCODED;
+ break;
+ }
+
+ info = find_format_info_by_media_type(format->media_type,
+ format->media_subtype, video_format, 0);
+ if (info == NULL || size == NULL || framerate == NULL) {
+ spa_log_error(this->log, "unknown media type %d %d %d", format->media_type,
+ format->media_subtype, video_format);
+ return -EINVAL;
+ }
+
+ fmt.fmt.pix.pixelformat = info->fourcc;
+ fmt.fmt.pix.field = V4L2_FIELD_ANY;
+ fmt.fmt.pix.width = size->width;
+ fmt.fmt.pix.height = size->height;
+ streamparm.parm.capture.timeperframe.numerator = framerate->denom;
+ streamparm.parm.capture.timeperframe.denominator = framerate->num;
+
+ spa_log_debug(this->log, "set %.4s %dx%d %d/%d", (char *)&fmt.fmt.pix.pixelformat,
+ fmt.fmt.pix.width, fmt.fmt.pix.height,
+ streamparm.parm.capture.timeperframe.denominator,
+ streamparm.parm.capture.timeperframe.numerator);
+
+ reqfmt = fmt;
+
+ if ((res = spa_v4l2_open(dev, this->props.device)) < 0)
+ return res;
+
+ cmd = (flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) ? VIDIOC_TRY_FMT : VIDIOC_S_FMT;
+ if (xioctl(dev->fd, cmd, &fmt) < 0) {
+ res = -errno;
+ spa_log_error(this->log, "'%s' VIDIOC_S_FMT: %m",
+ this->props.device);
+ return res;
+ }
+
+ /* some cheap USB cam's won't accept any change */
+ if (xioctl(dev->fd, VIDIOC_S_PARM, &streamparm) < 0)
+ spa_log_warn(this->log, "VIDIOC_S_PARM: %m");
+
+ match = (reqfmt.fmt.pix.pixelformat == fmt.fmt.pix.pixelformat &&
+ reqfmt.fmt.pix.width == fmt.fmt.pix.width &&
+ reqfmt.fmt.pix.height == fmt.fmt.pix.height);
+
+ if (!match && !SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_NEAREST)) {
+ spa_log_error(this->log, "wanted %.4s %dx%d, got %.4s %dx%d",
+ (char *)&reqfmt.fmt.pix.pixelformat,
+ reqfmt.fmt.pix.width, reqfmt.fmt.pix.height,
+ (char *)&fmt.fmt.pix.pixelformat,
+ fmt.fmt.pix.width, fmt.fmt.pix.height);
+ return -EINVAL;
+ }
+
+ if (flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)
+ return match ? 0 : 1;
+
+ spa_log_info(this->log, "'%s' got %.4s %dx%d %d/%d",
+ dev->path, (char *)&fmt.fmt.pix.pixelformat,
+ fmt.fmt.pix.width, fmt.fmt.pix.height,
+ streamparm.parm.capture.timeperframe.denominator,
+ streamparm.parm.capture.timeperframe.numerator);
+
+ dev->have_format = true;
+ size->width = fmt.fmt.pix.width;
+ size->height = fmt.fmt.pix.height;
+ port->rate.denom = framerate->num = streamparm.parm.capture.timeperframe.denominator;
+ port->rate.num = framerate->denom = streamparm.parm.capture.timeperframe.numerator;
+
+ port->fmt = fmt;
+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE;
+ port->info.flags = (port->alloc_buffers ? SPA_PORT_FLAG_CAN_ALLOC_BUFFERS : 0) |
+ SPA_PORT_FLAG_LIVE |
+ SPA_PORT_FLAG_PHYSICAL |
+ SPA_PORT_FLAG_TERMINAL;
+ port->info.rate = SPA_FRACTION(port->rate.num, port->rate.denom);
+
+ return match ? 0 : 1;
+}
+
+static int query_ext_ctrl_ioctl(struct port *port, struct v4l2_query_ext_ctrl *qctrl)
+{
+ struct spa_v4l2_device *dev = &port->dev;
+ struct v4l2_queryctrl qc;
+ int res;
+
+ if (port->have_query_ext_ctrl) {
+ res = xioctl(dev->fd, VIDIOC_QUERY_EXT_CTRL, qctrl);
+ if (errno != ENOTTY)
+ return res;
+ port->have_query_ext_ctrl = false;
+ }
+ spa_zero(qc);
+ qc.id = qctrl->id;
+ res = xioctl(dev->fd, VIDIOC_QUERYCTRL, &qc);
+ if (res == 0) {
+ qctrl->type = qc.type;
+ memcpy(qctrl->name, qc.name, sizeof(qctrl->name));
+ qctrl->minimum = qc.minimum;
+ if (qc.type == V4L2_CTRL_TYPE_BITMASK) {
+ qctrl->maximum = (__u32)qc.maximum;
+ qctrl->default_value = (__u32)qc.default_value;
+ } else {
+ qctrl->maximum = qc.maximum;
+ qctrl->default_value = qc.default_value;
+ }
+ qctrl->step = qc.step;
+ qctrl->flags = qc.flags;
+ qctrl->elems = 1;
+ qctrl->nr_of_dims = 0;
+ memset(qctrl->dims, 0, sizeof(qctrl->dims));
+ switch (qctrl->type) {
+ case V4L2_CTRL_TYPE_INTEGER64:
+ qctrl->elem_size = sizeof(__s64);
+ break;
+ case V4L2_CTRL_TYPE_STRING:
+ qctrl->elem_size = qc.maximum + 1;
+ break;
+ default:
+ qctrl->elem_size = sizeof(__s32);
+ break;
+ }
+ memset(qctrl->reserved, 0, sizeof(qctrl->reserved));
+ }
+ qctrl->id = qc.id;
+ return res;
+}
+
+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 control_to_prop_id(struct impl *impl, uint32_t control_id)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(control_map, c) {
+ if (c->v4l2_id == control_id)
+ return c->spa_id;
+ }
+ return SPA_PROP_START_CUSTOM + control_id;
+}
+
+static uint32_t prop_id_to_control(struct impl *impl, 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
+spa_v4l2_enum_controls(struct impl *this, int seq,
+ uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct port *port = &this->out_ports[0];
+ struct spa_v4l2_device *dev = &port->dev;
+ struct v4l2_query_ext_ctrl queryctrl;
+ struct spa_pod *param;
+ struct spa_pod_builder b = { 0 };
+ uint32_t prop_id, ctrl_id;
+ uint8_t buffer[1024];
+ int res;
+ const unsigned next_fl = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
+ struct spa_pod_frame f[2];
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+
+ if ((res = spa_v4l2_open(dev, this->props.device)) < 0)
+ return res;
+
+ result.id = SPA_PARAM_PropInfo;
+ result.next = start;
+ next:
+ result.index = result.next;
+
+ spa_zero(queryctrl);
+
+ if (result.next == 0) {
+ result.next |= next_fl;
+ port->n_controls = 0;
+ }
+
+ queryctrl.id = result.next;
+ spa_log_debug(this->log, "test control %08x", queryctrl.id);
+
+ if (query_ext_ctrl_ioctl(port, &queryctrl) != 0) {
+ if (errno == ENOTTY)
+ goto enum_end;
+ if (errno == EINVAL) {
+ if (queryctrl.id != next_fl)
+ goto enum_end;
+
+ if (result.next & next_fl)
+ result.next = V4L2_CID_USER_BASE;
+ else if (result.next >= V4L2_CID_USER_BASE && result.next < V4L2_CID_LASTP1)
+ result.next++;
+ else if (result.next >= V4L2_CID_LASTP1)
+ result.next = V4L2_CID_PRIVATE_BASE;
+ else
+ goto enum_end;
+ goto next;
+ }
+ res = -errno;
+ spa_log_error(this->log, "'%s' VIDIOC_QUERYCTRL: %m", this->props.device);
+ spa_v4l2_close(dev);
+ return res;
+ }
+ if (result.next & next_fl)
+ result.next = queryctrl.id | next_fl;
+ else
+ result.next++;
+
+ if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
+ goto next;
+
+ if (port->n_controls >= MAX_CONTROLS)
+ goto enum_end;
+
+ ctrl_id = queryctrl.id & ~next_fl;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+
+ prop_id = control_to_prop_id(this, ctrl_id);
+
+ port->controls[port->n_controls].id = prop_id;
+ port->controls[port->n_controls].ctrl_id = ctrl_id;
+ port->controls[port->n_controls].value = queryctrl.default_value;
+
+ spa_log_debug(this->log, "Control '%s' %d %d", queryctrl.name, prop_id, ctrl_id);
+
+ port->n_controls++;
+
+ switch (queryctrl.type) {
+ case V4L2_CTRL_TYPE_INTEGER:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
+ SPA_PROP_INFO_id, SPA_POD_Id(prop_id),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_STEP_Int(
+ (int32_t)queryctrl.default_value,
+ (int32_t)queryctrl.minimum,
+ (int32_t)queryctrl.maximum,
+ (int32_t)queryctrl.step),
+ SPA_PROP_INFO_description, SPA_POD_String(queryctrl.name));
+ break;
+ case V4L2_CTRL_TYPE_BOOLEAN:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
+ SPA_PROP_INFO_id, SPA_POD_Id(prop_id),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool((bool)queryctrl.default_value),
+ SPA_PROP_INFO_description, SPA_POD_String(queryctrl.name));
+ break;
+ case V4L2_CTRL_TYPE_MENU:
+ {
+ struct v4l2_querymenu querymenu;
+ struct spa_pod_builder_state state;
+
+ spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo);
+ spa_pod_builder_add(&b,
+ SPA_PROP_INFO_id, SPA_POD_Id(prop_id),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_ENUM_Int(1, (int32_t)queryctrl.default_value),
+ SPA_PROP_INFO_description, SPA_POD_String(queryctrl.name),
+ 0);
+
+ spa_zero(querymenu);
+ querymenu.id = queryctrl.id;
+
+ spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0);
+
+ spa_pod_builder_get_state(&b, &state);
+ spa_pod_builder_push_struct(&b, &f[1]);
+ for (querymenu.index = queryctrl.minimum;
+ querymenu.index <= queryctrl.maximum;
+ querymenu.index++) {
+ if (xioctl(dev->fd, VIDIOC_QUERYMENU, &querymenu) == 0) {
+ spa_pod_builder_int(&b, querymenu.index);
+ spa_pod_builder_string(&b, (const char *)querymenu.name);
+ }
+ }
+ if (spa_pod_builder_pop(&b, &f[1]) == NULL) {
+ spa_log_warn(this->log, "can't create Control '%s' overflow %d",
+ queryctrl.name, b.state.offset);
+ spa_pod_builder_reset(&b, &state);
+ spa_pod_builder_none(&b);
+ }
+ param = spa_pod_builder_pop(&b, &f[0]);
+ break;
+ }
+ case V4L2_CTRL_TYPE_INTEGER_MENU:
+ case V4L2_CTRL_TYPE_BITMASK:
+ case V4L2_CTRL_TYPE_BUTTON:
+ case V4L2_CTRL_TYPE_INTEGER64:
+ case V4L2_CTRL_TYPE_STRING:
+ default:
+ goto next;
+
+ }
+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
+ goto next;
+
+ spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+
+ if (++count != num)
+ goto next;
+
+ enum_end:
+ res = 0;
+ spa_v4l2_close(dev);
+ return res;
+}
+
+static int
+spa_v4l2_set_control(struct impl *this, uint32_t id,
+ const struct spa_pod_prop *prop)
+{
+ struct port *port = &this->out_ports[0];
+ struct spa_v4l2_device *dev = &port->dev;
+ struct v4l2_control control;
+ int res;
+
+ spa_zero(control);
+ control.id = prop_id_to_control(this, prop->key);
+ if (control.id == SPA_ID_INVALID)
+ return -ENOENT;
+
+ if ((res = spa_v4l2_open(dev, this->props.device)) < 0)
+ return res;
+
+ switch (SPA_POD_TYPE(&prop->value)) {
+ case SPA_TYPE_Bool:
+ {
+ bool val;
+ if ((res = spa_pod_get_bool(&prop->value, &val)) < 0)
+ goto done;
+ control.value = val;
+ break;
+ }
+ case SPA_TYPE_Int:
+ {
+ int32_t val;
+ if ((res = spa_pod_get_int(&prop->value, &val)) < 0)
+ goto done;
+ control.value = val;
+ break;
+ }
+ default:
+ res = -EINVAL;
+ goto done;
+ }
+ if (xioctl(dev->fd, VIDIOC_S_CTRL, &control) < 0) {
+ res = -errno;
+ goto done;
+ }
+
+ res = 0;
+
+done:
+ spa_v4l2_close(dev);
+ return res;
+}
+
+static int mmap_read(struct impl *this)
+{
+ struct port *port = &this->out_ports[0];
+ struct spa_v4l2_device *dev = &port->dev;
+ struct v4l2_buffer buf;
+ struct buffer *b;
+ struct spa_data *d;
+ int64_t pts;
+
+ spa_zero(buf);
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = port->memtype;
+
+ if (xioctl(dev->fd, VIDIOC_DQBUF, &buf) < 0)
+ return -errno;
+
+ pts = SPA_TIMEVAL_TO_NSEC(&buf.timestamp);
+ spa_log_trace(this->log, "v4l2 %p: have output %d", this, buf.index);
+
+ if (this->clock) {
+ this->clock->nsec = pts;
+ this->clock->rate = port->rate;
+ this->clock->position = buf.sequence;
+ this->clock->duration = 1;
+ this->clock->delay = 0;
+ this->clock->rate_diff = 1.0;
+ this->clock->next_nsec = pts + 1000000000LL / port->rate.denom;
+ }
+
+ b = &port->buffers[buf.index];
+ if (b->h) {
+ b->h->flags = 0;
+ if (buf.flags & V4L2_BUF_FLAG_ERROR)
+ b->h->flags |= SPA_META_HEADER_FLAG_CORRUPTED;
+ b->h->offset = 0;
+ b->h->seq = buf.sequence;
+ b->h->pts = pts;
+ b->h->dts_offset = 0;
+ }
+
+ d = b->outbuf->datas;
+ d[0].chunk->offset = 0;
+ d[0].chunk->size = buf.bytesused;
+ d[0].chunk->stride = port->fmt.fmt.pix.bytesperline;
+ d[0].chunk->flags = 0;
+ if (buf.flags & V4L2_BUF_FLAG_ERROR)
+ d[0].chunk->flags |= SPA_CHUNK_FLAG_CORRUPTED;
+
+ spa_list_append(&port->queue, &b->link);
+ return 0;
+}
+
+static void v4l2_on_fd_events(struct spa_source *source)
+{
+ struct impl *this = source->data;
+ struct spa_io_buffers *io;
+ struct port *port = &this->out_ports[0];
+ struct buffer *b;
+
+ if (source->rmask & SPA_IO_ERR) {
+ struct port *port = &this->out_ports[0];
+ spa_log_error(this->log, "'%p' error %08x", this->props.device, source->rmask);
+ if (port->source.loop)
+ spa_loop_remove_source(this->data_loop, &port->source);
+ return;
+ }
+
+ if (!(source->rmask & SPA_IO_IN)) {
+ spa_log_warn(this->log, "v4l2 %p: spurious wakeup %d", this, source->rmask);
+ return;
+ }
+
+ if (mmap_read(this) < 0)
+ return;
+
+ if (spa_list_is_empty(&port->queue))
+ return;
+
+ io = port->io;
+ if (io == NULL) {
+ b = spa_list_first(&port->queue, struct buffer, link);
+ spa_list_remove(&b->link);
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING);
+ spa_v4l2_buffer_recycle(this, b->id);
+ }
+ else if (io->status != SPA_STATUS_HAVE_DATA) {
+ if (io->buffer_id < port->n_buffers)
+ spa_v4l2_buffer_recycle(this, io->buffer_id);
+
+ b = spa_list_first(&port->queue, struct buffer, link);
+ spa_list_remove(&b->link);
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING);
+
+ io->buffer_id = b->id;
+ io->status = SPA_STATUS_HAVE_DATA;
+ spa_log_trace(this->log, "v4l2 %p: now queued %d", this, b->id);
+ }
+ spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA);
+}
+
+static int spa_v4l2_use_buffers(struct impl *this, struct spa_buffer **buffers, uint32_t n_buffers)
+{
+ struct port *port = &this->out_ports[0];
+ struct spa_v4l2_device *dev = &port->dev;
+ struct v4l2_requestbuffers reqbuf;
+ unsigned int i;
+ struct spa_data *d;
+
+ if (n_buffers > 0) {
+ d = buffers[0]->datas;
+
+ if (d[0].type == SPA_DATA_MemFd ||
+ (d[0].type == SPA_DATA_MemPtr && d[0].data != NULL)) {
+ port->memtype = V4L2_MEMORY_USERPTR;
+ } else if (d[0].type == SPA_DATA_DmaBuf) {
+ port->memtype = V4L2_MEMORY_DMABUF;
+ } else {
+ spa_log_error(this->log, "can't use buffers of type %d", d[0].type);
+ return -EINVAL;
+ }
+ }
+
+ spa_zero(reqbuf);
+ reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ reqbuf.memory = port->memtype;
+ reqbuf.count = n_buffers;
+
+ if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
+ spa_log_error(this->log, "'%s' VIDIOC_REQBUFS %m", this->props.device);
+ return -errno;
+ }
+ spa_log_debug(this->log, "got %d buffers", reqbuf.count);
+ if (reqbuf.count < n_buffers) {
+ spa_log_error(this->log, "'%s' can't allocate enough buffers %d < %d",
+ this->props.device, reqbuf.count, n_buffers);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < reqbuf.count; i++) {
+ struct buffer *b;
+
+ b = &port->buffers[i];
+ b->id = i;
+ b->outbuf = buffers[i];
+ b->flags = BUFFER_FLAG_OUTSTANDING;
+ b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h));
+
+ spa_log_debug(this->log, "import buffer %p", buffers[i]);
+
+ if (buffers[i]->n_datas < 1) {
+ spa_log_error(this->log, "invalid memory on buffer %p", buffers[i]);
+ return -EINVAL;
+ }
+ d = buffers[i]->datas;
+
+ spa_zero(b->v4l2_buffer);
+ b->v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ b->v4l2_buffer.memory = port->memtype;
+ b->v4l2_buffer.index = i;
+
+ if (port->memtype == V4L2_MEMORY_USERPTR) {
+ if (d[0].data == NULL) {
+ void *data;
+
+ data = mmap(NULL,
+ d[0].maxsize,
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ d[0].fd,
+ d[0].mapoffset);
+ if (data == MAP_FAILED)
+ return -errno;
+
+ b->ptr = data;
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED);
+ }
+ else
+ b->ptr = d[0].data;
+
+ b->v4l2_buffer.m.userptr = (unsigned long) b->ptr;
+ b->v4l2_buffer.length = d[0].maxsize;
+ }
+ else if (port->memtype == V4L2_MEMORY_DMABUF) {
+ b->v4l2_buffer.m.fd = d[0].fd;
+ }
+ else {
+ spa_log_error(this->log, "invalid port memory %d",
+ port->memtype);
+ return -EIO;
+ }
+
+ spa_v4l2_buffer_recycle(this, i);
+ }
+ port->n_buffers = reqbuf.count;
+
+ return 0;
+}
+
+static int
+mmap_init(struct impl *this,
+ struct spa_buffer **buffers, uint32_t n_buffers)
+{
+ struct port *port = &this->out_ports[0];
+ struct spa_v4l2_device *dev = &port->dev;
+ struct v4l2_requestbuffers reqbuf;
+ unsigned int i;
+ bool use_expbuf = false;
+
+ port->memtype = V4L2_MEMORY_MMAP;
+
+ spa_zero(reqbuf);
+ reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ reqbuf.memory = port->memtype;
+ reqbuf.count = n_buffers;
+
+ if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
+ spa_log_error(this->log, "'%s' VIDIOC_REQBUFS: %m", this->props.device);
+ return -errno;
+ }
+
+ spa_log_debug(this->log, "got %d buffers", reqbuf.count);
+
+ if (reqbuf.count < n_buffers) {
+ spa_log_error(this->log, "'%s' can't allocate enough buffers (%d < %d)",
+ this->props.device, reqbuf.count, n_buffers);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < n_buffers; i++) {
+ struct buffer *b;
+ struct spa_data *d;
+
+ if (buffers[i]->n_datas < 1) {
+ spa_log_error(this->log, "invalid buffer data");
+ return -EINVAL;
+ }
+
+ b = &port->buffers[i];
+ b->id = i;
+ b->outbuf = buffers[i];
+ b->flags = BUFFER_FLAG_OUTSTANDING;
+ b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h));
+
+ spa_zero(b->v4l2_buffer);
+ b->v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ b->v4l2_buffer.memory = port->memtype;
+ b->v4l2_buffer.index = i;
+
+ if (xioctl(dev->fd, VIDIOC_QUERYBUF, &b->v4l2_buffer) < 0) {
+ spa_log_error(this->log, "'%s' VIDIOC_QUERYBUF: %m", this->props.device);
+ return -errno;
+ }
+
+ if (b->v4l2_buffer.flags & V4L2_BUF_FLAG_QUEUED) {
+ /* some drivers can give us an already queued buffer. */
+ spa_log_warn(this->log, "buffer %d was already queued", i);
+ n_buffers = i;
+ break;
+ }
+
+ d = buffers[i]->datas;
+ d[0].mapoffset = 0;
+ d[0].maxsize = b->v4l2_buffer.length;
+ d[0].chunk->offset = 0;
+ d[0].chunk->size = 0;
+ d[0].chunk->stride = port->fmt.fmt.pix.bytesperline;
+ d[0].chunk->flags = 0;
+
+ spa_log_debug(this->log, "data types %08x", d[0].type);
+
+ if (port->have_expbuf &&
+ d[0].type != SPA_ID_INVALID &&
+ (d[0].type & ((1u << SPA_DATA_DmaBuf)|(1u<<SPA_DATA_MemFd)))) {
+ struct v4l2_exportbuffer expbuf;
+
+ spa_zero(expbuf);
+ expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ expbuf.index = i;
+ expbuf.flags = O_CLOEXEC | O_RDONLY;
+ if (xioctl(dev->fd, VIDIOC_EXPBUF, &expbuf) < 0) {
+ if (errno == ENOTTY || errno == EINVAL) {
+ spa_log_debug(this->log, "'%s' VIDIOC_EXPBUF not supported: %m",
+ this->props.device);
+ port->have_expbuf = false;
+ goto fallback;
+ }
+ spa_log_error(this->log, "'%s' VIDIOC_EXPBUF: %m", this->props.device);
+ return -errno;
+ }
+ if (d[0].type & (1u<<SPA_DATA_DmaBuf))
+ d[0].type = SPA_DATA_DmaBuf;
+ else
+ d[0].type = SPA_DATA_MemFd;
+ d[0].flags = SPA_DATA_FLAG_READABLE;
+ d[0].fd = expbuf.fd;
+ d[0].data = NULL;
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_ALLOCATED);
+ spa_log_debug(this->log, "EXPBUF fd:%d", expbuf.fd);
+ use_expbuf = true;
+ } else if (d[0].type & (1u << SPA_DATA_MemPtr)) {
+fallback:
+ d[0].type = SPA_DATA_MemPtr;
+ d[0].flags = SPA_DATA_FLAG_READABLE;
+ d[0].fd = -1;
+ d[0].mapoffset = b->v4l2_buffer.m.offset;
+ d[0].data = mmap(NULL,
+ b->v4l2_buffer.length,
+ PROT_READ, MAP_SHARED,
+ dev->fd,
+ b->v4l2_buffer.m.offset);
+ if (d[0].data == MAP_FAILED) {
+ spa_log_error(this->log, "'%s' mmap: %m", this->props.device);
+ return -errno;
+ }
+ b->ptr = d[0].data;
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED);
+ spa_log_debug(this->log, "mmap offset:%u data:%p", d[0].mapoffset, b->ptr);
+ use_expbuf = false;
+ } else {
+ spa_log_error(this->log, "unsupported data type:%08x", d[0].type);
+ return -ENOTSUP;
+ }
+ spa_v4l2_buffer_recycle(this, i);
+ }
+ spa_log_info(this->log, "%s: have %u buffers using %s", dev->path, n_buffers,
+ use_expbuf ? "EXPBUF" : "MMAP");
+
+ port->n_buffers = n_buffers;
+
+ return 0;
+}
+
+static int userptr_init(struct impl *this)
+{
+ return -ENOTSUP;
+}
+
+static int read_init(struct impl *this)
+{
+ return -ENOTSUP;
+}
+
+static int
+spa_v4l2_alloc_buffers(struct impl *this,
+ struct spa_buffer **buffers,
+ uint32_t n_buffers)
+{
+ int res;
+ struct port *port = &this->out_ports[0];
+ struct spa_v4l2_device *dev = &port->dev;
+
+ if (port->n_buffers > 0)
+ return -EIO;
+
+ if (dev->cap.capabilities & V4L2_CAP_STREAMING) {
+ if ((res = mmap_init(this, buffers, n_buffers)) < 0)
+ if ((res = userptr_init(this)) < 0)
+ return res;
+ } else if (dev->cap.capabilities & V4L2_CAP_READWRITE) {
+ if ((res = read_init(this)) < 0)
+ return res;
+ } else {
+ spa_log_error(this->log, "invalid capabilities %08x",
+ dev->cap.capabilities);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int spa_v4l2_stream_on(struct impl *this)
+{
+ struct port *port = &this->out_ports[0];
+ struct spa_v4l2_device *dev = &port->dev;
+ enum v4l2_buf_type type;
+
+ if (dev->fd == -1)
+ return -EIO;
+
+ if (!dev->have_format)
+ return -EIO;
+
+ if (dev->active)
+ return 0;
+
+ spa_log_debug(this->log, "starting");
+
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (xioctl(dev->fd, VIDIOC_STREAMON, &type) < 0) {
+ spa_log_error(this->log, "'%s' VIDIOC_STREAMON: %m", this->props.device);
+ return -errno;
+ }
+
+ port->source.func = v4l2_on_fd_events;
+ port->source.data = this;
+ port->source.fd = dev->fd;
+ port->source.mask = SPA_IO_IN | SPA_IO_ERR;
+ port->source.rmask = 0;
+ spa_loop_add_source(this->data_loop, &port->source);
+
+ dev->active = true;
+
+ return 0;
+}
+
+static int do_remove_source(struct spa_loop *loop,
+ bool async,
+ uint32_t seq,
+ const void *data,
+ size_t size,
+ void *user_data)
+{
+ struct port *port = user_data;
+ if (port->source.loop)
+ spa_loop_remove_source(loop, &port->source);
+ return 0;
+}
+
+static int spa_v4l2_stream_off(struct impl *this)
+{
+ struct port *port = &this->out_ports[0];
+ struct spa_v4l2_device *dev = &port->dev;
+ enum v4l2_buf_type type;
+ uint32_t i;
+
+ if (!dev->active)
+ return 0;
+
+ if (dev->fd == -1)
+ return -EIO;
+
+ spa_log_debug(this->log, "stopping");
+
+ spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, port);
+
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (xioctl(dev->fd, VIDIOC_STREAMOFF, &type) < 0) {
+ spa_log_error(this->log, "'%s' VIDIOC_STREAMOFF: %m", this->props.device);
+ return -errno;
+ }
+ for (i = 0; i < port->n_buffers; i++) {
+ struct buffer *b;
+
+ b = &port->buffers[i];
+ if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) {
+ if (xioctl(dev->fd, VIDIOC_QBUF, &b->v4l2_buffer) < 0)
+ spa_log_warn(this->log, "VIDIOC_QBUF: %s", strerror(errno));
+ }
+ }
+ spa_list_init(&port->queue);
+ dev->active = false;
+
+ return 0;
+}
diff --git a/spa/plugins/v4l2/v4l2.c b/spa/plugins/v4l2/v4l2.c
new file mode 100644
index 0000000..77fb4ce
--- /dev/null
+++ b/spa/plugins/v4l2/v4l2.c
@@ -0,0 +1,59 @@
+/* Spa V4l2 support
+ *
+ * Copyright © 2018 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 <spa/support/plugin.h>
+#include <spa/support/log.h>
+
+extern const struct spa_handle_factory spa_v4l2_source_factory;
+extern const struct spa_handle_factory spa_v4l2_udev_factory;
+extern const struct spa_handle_factory spa_v4l2_device_factory;
+
+struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.v4l2");
+struct spa_log_topic *v4l2_log_topic = &log_topic;
+
+SPA_EXPORT
+int spa_handle_factory_enum(const struct spa_handle_factory **factory,
+ uint32_t *index)
+{
+ spa_return_val_if_fail(factory != NULL, -EINVAL);
+ spa_return_val_if_fail(index != NULL, -EINVAL);
+
+ switch (*index) {
+ case 0:
+ *factory = &spa_v4l2_source_factory;
+ break;
+ case 1:
+ *factory = &spa_v4l2_udev_factory;
+ break;
+ case 2:
+ *factory = &spa_v4l2_device_factory;
+ break;
+ default:
+ return 0;
+ }
+ (*index)++;
+ return 1;
+}
diff --git a/spa/plugins/v4l2/v4l2.h b/spa/plugins/v4l2/v4l2.h
new file mode 100644
index 0000000..e2293c7
--- /dev/null
+++ b/spa/plugins/v4l2/v4l2.h
@@ -0,0 +1,49 @@
+/* Spa V4l2 support
+ *
+ * Copyright © 2018 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 <spa/support/log.h>
+
+#undef SPA_LOG_TOPIC_DEFAULT
+#define SPA_LOG_TOPIC_DEFAULT v4l2_log_topic
+extern struct spa_log_topic *v4l2_log_topic;
+
+static inline void v4l2_log_topic_init(struct spa_log *log)
+{
+ spa_log_topic_init(log, v4l2_log_topic);
+}
+
+struct spa_v4l2_device {
+ struct spa_log *log;
+ int fd;
+ struct v4l2_capability cap;
+ unsigned int active:1;
+ unsigned int have_format:1;
+ char path[64];
+};
+
+int spa_v4l2_open(struct spa_v4l2_device *dev, const char *path);
+int spa_v4l2_close(struct spa_v4l2_device *dev);
+int spa_v4l2_is_capture(struct spa_v4l2_device *dev);