diff mbox

[libv4l] : Make libv4l2 usable on devices with complex pipeline

Message ID 20180708213258.GA18217@amd (mailing list archive)
State New, archived
Headers show

Commit Message

Pavel Machek July 8, 2018, 9:32 p.m. UTC
Add support for opening multiple devices in v4l2_open(), and for
mapping controls between devices.

This is necessary for complex devices, such as Nokia N900.

Signed-off-by: Pavel Machek <pavel@ucw.cz>

Comments

Pavel Machek July 19, 2018, 8:53 p.m. UTC | #1
On Sun 2018-07-08 23:32:58, Pavel Machek wrote:
> 
> Add support for opening multiple devices in v4l2_open(), and for
> mapping controls between devices.
> 
> This is necessary for complex devices, such as Nokia N900.
> 
> Signed-off-by: Pavel Machek <pavel@ucw.cz>

Ping?

There's a lot of work to do on libv4l2... timely patch handling would
be nice.

> diff --git a/lib/include/libv4l2.h b/lib/include/libv4l2.h
> index ea1870d..a0ec0a9 100644
> --- a/lib/include/libv4l2.h
> +++ b/lib/include/libv4l2.h
> @@ -58,6 +58,10 @@ LIBV4L_PUBLIC extern FILE *v4l2_log_file;
>     invalid memory address will not lead to failure with errno being EFAULT,
>     as it would with a real ioctl, but will cause libv4l2 to break, and you
>     get to keep both pieces.
> +
> +   You can open complex pipelines by passing ".cv" file with pipeline
> +   description to v4l2_open(). libv4l2 will open all the required
> +   devices automatically in that case.
>  */
>  
>  LIBV4L_PUBLIC int v4l2_open(const char *file, int oflag, ...);
> diff --git a/lib/libv4l2/libv4l2-priv.h b/lib/libv4l2/libv4l2-priv.h
> index 1924c91..1ee697a 100644
> --- a/lib/libv4l2/libv4l2-priv.h
> +++ b/lib/libv4l2/libv4l2-priv.h
> @@ -104,6 +104,7 @@ struct v4l2_dev_info {
>  	void *plugin_library;
>  	void *dev_ops_priv;
>  	const struct libv4l_dev_ops *dev_ops;
> +	struct v4l2_controls_map *map;
>  };
>  
>  /* From v4l2-plugin.c */
> @@ -130,4 +131,20 @@ static inline void v4l2_plugin_cleanup(void *plugin_lib, void *plugin_priv,
>  extern const char *v4l2_ioctls[];
>  void v4l2_log_ioctl(unsigned long int request, void *arg, int result);
>  
> +
> +struct v4l2_control_map {
> +	unsigned long control;
> +	int fd;
> +};
> +
> +struct v4l2_controls_map {
> +	int main_fd;
> +	int num_fds;
> +	int num_controls;
> +	struct v4l2_control_map map[];
> +};
> +
> +int v4l2_open_pipeline(struct v4l2_controls_map *map, int v4l2_flags);
> +LIBV4L_PUBLIC int v4l2_get_fd_for_control(int fd, unsigned long control);
> +
>  #endif
> diff --git a/lib/libv4l2/libv4l2.c b/lib/libv4l2/libv4l2.c
> index 2db25d1..ac430f0 100644
> --- a/lib/libv4l2/libv4l2.c
> +++ b/lib/libv4l2/libv4l2.c
> @@ -70,6 +70,8 @@
>  #include <sys/types.h>
>  #include <sys/mman.h>
>  #include <sys/stat.h>
> +#include <dirent.h>
> +
>  #include "libv4l2.h"
>  #include "libv4l2-priv.h"
>  #include "libv4l-plugin.h"
> @@ -618,6 +620,8 @@ static void v4l2_update_fps(int index, struct v4l2_streamparm *parm)
>  		devices[index].fps = 0;
>  }
>  
> +static int v4l2_open_complex(int fd, int v4l2_flags);
> +
>  int v4l2_open(const char *file, int oflag, ...)
>  {
>  	int fd;
> @@ -641,6 +645,21 @@ int v4l2_open(const char *file, int oflag, ...)
>  	if (fd == -1)
>  		return fd;
>  
> +	int len = strlen(file);
> +	char *end = ".cv";
> +	int len2 = strlen(end);
> +	if ((len > len2) && (!strcmp(file + len - len2, end))) {
> +		/* .cv extension */
> +		struct stat sb;
> +
> +		if (fstat(fd, &sb) == 0) {
> +			if ((sb.st_mode & S_IFMT) == S_IFREG) {
> +				return v4l2_open_complex(fd, 0);
> +			}
> +		}
> +		
> +	}
> +
>  	if (v4l2_fd_open(fd, 0) == -1) {
>  		int saved_err = errno;
>  
> @@ -787,6 +806,8 @@ no_capture:
>  	if (index >= devices_used)
>  		devices_used = index + 1;
>  
> +	devices[index].map = NULL;
> +
>  	/* Note we always tell v4lconvert to optimize src fmt selection for
>  	   our default fps, the only exception is the app explicitly selecting
>  	   a frame rate using the S_PARM ioctl after a S_FMT */
> @@ -1056,12 +1077,47 @@ static int v4l2_s_fmt(int index, struct v4l2_format *dest_fmt)
>  	return 0;
>  }
>  
> +int v4l2_get_fd_for_control(int fd, unsigned long control)
> +{
> +	int index = v4l2_get_index(fd);
> +	struct v4l2_controls_map *map;
> +	int lo = 0;
> +	int hi;
> +
> +	if (index < 0)
> +		return fd;
> +
> +	map = devices[index].map;
> +	if (!map)
> +		return fd;
> +	hi = map->num_controls;
> +
> +	while (lo < hi) {
> +		int i = (lo + hi) / 2;
> +		if (map->map[i].control == control) {
> +			return map->map[i].fd;
> +		}
> +		if (map->map[i].control > control) {
> +			hi = i;
> +			continue;
> +		}
> +		if (map->map[i].control < control) {
> +			lo = i+1;
> +			continue;
> +		}
> +		printf("Bad: impossible condition in binary search\n");
> +		exit(1);
> +	}
> +	return fd;
> +}
> +
>  int v4l2_ioctl(int fd, unsigned long int request, ...)
>  {
>  	void *arg;
>  	va_list ap;
>  	int result, index, saved_err;
> -	int is_capture_request = 0, stream_needs_locking = 0;
> +	int is_capture_request = 0, stream_needs_locking = 0, 
> +	    is_subdev_request = 0;
>  
>  	va_start(ap, request);
>  	arg = va_arg(ap, void *);
> @@ -1076,18 +1132,19 @@ int v4l2_ioctl(int fd, unsigned long int request, ...)
>  	   ioctl, causing it to get sign extended, depending upon this behavior */
>  	request = (unsigned int)request;
>  
>  	if (devices[index].convert == NULL)
>  		goto no_capture_request;
>  
>  	/* Is this a capture request and do we need to take the stream lock? */
>  	switch (request) {
> -	case VIDIOC_QUERYCAP:
>  	case VIDIOC_QUERYCTRL:
>  	case VIDIOC_G_CTRL:
>  	case VIDIOC_S_CTRL:
>  	case VIDIOC_G_EXT_CTRLS:
> -	case VIDIOC_TRY_EXT_CTRLS:
>  	case VIDIOC_S_EXT_CTRLS:
> +		is_subdev_request = 1;
> +	case VIDIOC_QUERYCAP:
> +	case VIDIOC_TRY_EXT_CTRLS:
>  	case VIDIOC_ENUM_FRAMESIZES:
>  	case VIDIOC_ENUM_FRAMEINTERVALS:
>  		is_capture_request = 1;
> @@ -1151,10 +1209,15 @@ int v4l2_ioctl(int fd, unsigned long int request, ...)
>  	}
>  
>  	if (!is_capture_request) {
> +		int sub_fd;
>  no_capture_request:
> +		sub_fd = fd;
> +		if (is_subdev_request) {
> +			sub_fd = v4l2_get_fd_for_control(index, ((struct v4l2_queryctrl *) arg)->id);
> +		}
>  		result = devices[index].dev_ops->ioctl(
>  				devices[index].dev_ops_priv,
> -				fd, request, arg);
> +				sub_fd, request, arg);
>  		saved_err = errno;
>  		v4l2_log_ioctl(request, arg, result);
>  		errno = saved_err;
> @@ -1782,3 +1845,194 @@ int v4l2_get_control(int fd, int cid)
>  			(qctrl.maximum - qctrl.minimum) / 2) /
>  		(qctrl.maximum - qctrl.minimum);
>  }
> +
> +int v4l2_open_pipeline(struct v4l2_controls_map *map, int v4l2_flags)
> +{
> +	int index;
> +	int i;
> +
> +	for (i=0; i<map->num_controls; i++) {
> +		if (map->map[i].fd <= 0) {
> +			V4L2_LOG_ERR("v4l2_open_pipeline: Bad fd in map.\n");
> +			return -1;
> +		}
> +		if (i>=1 && map->map[i].control <= map->map[i-1].control) {
> +			V4L2_LOG_ERR("v4l2_open_pipeline: Controls not sorted.\n");
> +			return -1;
> +		}
> +	}
> +
> +	i = v4l2_fd_open(map->main_fd, v4l2_flags);
> +	index = v4l2_get_index(map->main_fd);
> +	devices[index].map = map;
> +	return i;
> +}
> +
> +static void scan_devices(char **device_names, int *device_fds, int num)
> +{
> +	struct dirent **namelist;
> +	int n;
> +	char *class_v4l = "/sys/class/video4linux";
> +
> +	n = scandir(class_v4l, &namelist, NULL, alphasort);
> +	if (n < 0) {
> +		perror("scandir");
> +		return;
> +	}
> +	
> +	while (n--) {
> +		if (namelist[n]->d_name[0] != '.') {
> +			char filename[1024], content[1024];
> +			sprintf(filename, "%s/%s/name", class_v4l, namelist[n]->d_name);
> +			FILE *f = fopen(filename, "r");
> +			if (!f) {
> +				printf("Strange, can't open %s", filename);
> +			} else {
> +				fgets(content, 1024, f);
> +				fclose(f);
> +
> +				int i;
> +				for (i = num-1; i >=0; i--) {
> +					if (!strcmp(content, device_names[i])) {
> +						sprintf(filename, "/dev/%s", namelist[n]->d_name);
> +						device_fds[i] = open(filename, O_RDWR);
> +						if (device_fds[i] < 0) {
> +							V4L2_LOG_ERR("Error opening %s: %m\n", filename);
> +						}
> +					}
> +				}
> +			}
> +		}
> +		free(namelist[n]);
> +	}
> +	free(namelist);
> +  
> +}
> +
> +static int v4l2_open_complex(int fd, int v4l2_flags)
> +{
> +#define perr(s) V4L2_LOG_ERR("open_complex: " s "\n")
> +#define BUF 256
> +	FILE *f = fdopen(fd, "r");
> +
> +	int res = -1;
> +	char buf[BUF];
> +	int version, num_modes, num_devices, num_controls;
> +	int dev, control;
> +
> +	if (!f) {
> +		perr("open of .cv file failed: %m");
> +		goto err;
> +	}
> +
> +	if (fscanf(f, "Complex Video: %d\n", &version) != 1) {
> +		perr(".cv file does not have required header");
> +		goto close;
> +	}
> +
> +	if (version != 0) {
> +		perr(".cv file has unknown version");
> +		goto close;
> +	}
> +  
> +	if (fscanf(f, "#modes: %d\n", &num_modes) != 1) {
> +		perr("could not parse modes");
> +		goto close;
> +	}
> +
> +	if (num_modes != 1) {
> +		perr("only single mode is supported for now");
> +		goto close;
> +	}
> +
> +	if (fscanf(f, "Mode: %s\n", buf) != 1) {
> +		perr("could not parse mode name");
> +		goto close;
> +	}
> +
> +	if (fscanf(f, " #devices: %d\n", &num_devices) != 1) {
> +		perr("could not parse number of devices");
> +		goto close;
> +	}
> +#define MAX_DEVICES 16
> +	char *device_names[MAX_DEVICES] = { NULL, };
> +	int device_fds[MAX_DEVICES];
> +	if (num_devices > MAX_DEVICES) {
> +		perr("too many devices");
> +		goto close;
> +	}
> +  
> +	for (dev = 0; dev < num_devices; dev++) {
> +		int tmp;
> +		if (fscanf(f, "%d: ", &tmp) != 1) {
> +			perr("could not parse device");
> +			goto free_devices;
> +		}
> +		if (tmp != dev) {
> +			perr("bad device number");
> +			goto free_devices;
> +		}
> +		fgets(buf, BUF, f);
> +		device_names[dev] = strdup(buf);
> +		device_fds[dev] = -1;
> +	}
> +
> +	scan_devices(device_names, device_fds, num_devices);
> +
> +	for (dev = 0; dev < num_devices; dev++) {
> +		if (device_fds[dev] == -1) {
> +			perr("Could not open all required devices");
> +			goto close_devices;
> +		}
> +	}
> +
> +	if (fscanf(f, " #controls: %d\n", &num_controls) != 1) {
> +		perr("can not parse number of controls");
> +		goto close_devices;
> +	}
> +
> +	struct v4l2_controls_map *map = malloc(sizeof(struct v4l2_controls_map) +
> +					       num_controls*sizeof(struct v4l2_control_map));
> +
> +	map->num_controls = num_controls;
> +	map->num_fds = num_devices;
> +	map->main_fd = device_fds[0];
> +  
> +	for (control = 0; control < num_controls; control++) {
> +		unsigned long num;
> +		int dev;
> +		if (fscanf(f, "0x%lx: %d\n", &num, &dev) != 2) {
> +			perr("could not parse control");
> +			goto free_map;
> +		}
> +		if ((dev < 0) || (dev >= num_devices)) {
> +			perr("device out of range");
> +			goto free_map;
> +		}
> +		map->map[control].control = num;
> +		map->map[control].fd = device_fds[dev];
> +	}
> +	if (fscanf(f, "%s", buf) > 0) {
> +		perr("junk at end of file");
> +		goto free_map;
> +	}
> +
> +	res = v4l2_open_pipeline(map, v4l2_flags);
> +
> +	if (res < 0) {
> +free_map:
> +		free(map);
> +close_devices:
> +		for (dev = 0; dev < num_devices; dev++)
> +			close(device_fds[dev]);
> +	}
> +free_devices:
> +	for (dev = 0; dev < num_devices; dev++) {
> +		free(device_names[dev]);
> +	}
> +close:
> +	fclose(f);
> +err:
> +	return res;
> +}
> +
> diff --git a/lib/libv4lconvert/control/libv4lcontrol.c b/lib/libv4lconvert/control/libv4lcontrol.c
> index 59f28b1..c1e6f93 100644
> --- a/lib/libv4lconvert/control/libv4lcontrol.c
> +++ b/lib/libv4lconvert/control/libv4lcontrol.c
> @@ -863,6 +863,7 @@ int v4lcontrol_vidioc_queryctrl(struct v4lcontrol_data *data, void *arg)
>  	struct v4l2_queryctrl *ctrl = arg;
>  	int retval;
>  	uint32_t orig_id = ctrl->id;
> +	int fd;
>  
>  	/* if we have an exact match return it */
>  	for (i = 0; i < V4LCONTROL_COUNT; i++)
> @@ -872,8 +873,9 @@ int v4lcontrol_vidioc_queryctrl(struct v4lcontrol_data *data, void *arg)
>  			return 0;
>  		}
>  
> +	fd = v4l2_get_fd_for_control(data->fd, ctrl->id);
>  	/* find out what the kernel driver would respond. */
> -	retval = data->dev_ops->ioctl(data->dev_ops_priv, data->fd,
> +	retval = data->dev_ops->ioctl(data->dev_ops_priv, fd,
>  			VIDIOC_QUERYCTRL, arg);
>  
>  	if ((data->priv_flags & V4LCONTROL_SUPPORTS_NEXT_CTRL) &&
> @@ -903,6 +905,7 @@ int v4lcontrol_vidioc_g_ctrl(struct v4lcontrol_data *data, void *arg)
>  {
>  	int i;
>  	struct v4l2_control *ctrl = arg;
> +	int fd;
>  
>  	for (i = 0; i < V4LCONTROL_COUNT; i++)
>  		if ((data->controls & (1 << i)) &&
> @@ -911,7 +914,8 @@ int v4lcontrol_vidioc_g_ctrl(struct v4lcontrol_data *data, void *arg)
>  			return 0;
>  		}
>  
> -	return data->dev_ops->ioctl(data->dev_ops_priv, data->fd,
> +	fd = v4l2_get_fd_for_control(data->fd, ctrl->id);
> +	return data->dev_ops->ioctl(data->dev_ops_priv, fd,
>  			VIDIOC_G_CTRL, arg);
>  }
>  
> @@ -994,6 +998,7 @@ int v4lcontrol_vidioc_s_ctrl(struct v4lcontrol_data *data, void *arg)
>  {
>  	int i;
>  	struct v4l2_control *ctrl = arg;
> +	int fd;
>  
>  	for (i = 0; i < V4LCONTROL_COUNT; i++)
>  		if ((data->controls & (1 << i)) &&
> @@ -1008,7 +1013,8 @@ int v4lcontrol_vidioc_s_ctrl(struct v4lcontrol_data *data, void *arg)
>  			return 0;
>  		}
>  
> -	return data->dev_ops->ioctl(data->dev_ops_priv, data->fd,
> +	fd = v4l2_get_fd_for_control(data->fd, ctrl->id);
> +	return data->dev_ops->ioctl(data->dev_ops_priv, fd,
>  			VIDIOC_S_CTRL, arg);
>  }
>  
>
Mauro Carvalho Chehab July 23, 2018, 6:36 p.m. UTC | #2
Hi Pavel,

Em Thu, 19 Jul 2018 22:53:44 +0200
Pavel Machek <pavel@ucw.cz> escreveu:

> On Sun 2018-07-08 23:32:58, Pavel Machek wrote:
> > 
> > Add support for opening multiple devices in v4l2_open(), and for
> > mapping controls between devices.
> > 
> > This is necessary for complex devices, such as Nokia N900.
> > 
> > Signed-off-by: Pavel Machek <pavel@ucw.cz>  
> 
> Ping?
> 
> There's a lot of work to do on libv4l2... timely patch handling would
> be nice.

As we're be start working at the new library in order to support
complex cameras, and I don't want to prevent you keeping doing your
work, IMHO the best way to keep doing it would be to create two
libv4l2 forks:

	- one to be used by you - meant to support N900 camera;
	- another one to be used by Google/Intel people that will
	  be working at the Complex camera.

This way, we can proceed with the development without causing
instability to v4l-utils. Once we have the projects done at the
separate repositories, we can work on merging them back upstream.

So, please send me, in priv, a .ssh key for me to add you an
account at linuxtv.org. I'll send you instructions about how to
use the new account.

Thanks,
Mauro
Mauro Carvalho Chehab July 27, 2018, 12:47 p.m. UTC | #3
Em Sun, 8 Jul 2018 23:32:58 +0200
Pavel Machek <pavel@ucw.cz> escreveu:

> Add support for opening multiple devices in v4l2_open(), and for
> mapping controls between devices.
> 
> This is necessary for complex devices, such as Nokia N900.

Hi Pavel,

I tried to apply it here, but it seems that there are something
wrong with the patch:

$ quilt push -f --merge
Applying patch patches/lmml_50901_libv4l_make_libv4l2_usable_on_devices_with_complex_pipeline.patch
patching file lib/include/libv4l2.h
patching file lib/libv4l2/libv4l2-priv.h
patching file lib/libv4l2/libv4l2.c
Hunk #6 NOT MERGED at 1830-1857.
misordered hunks! output would be garbled
Hunk #7 FAILED at 1224.
Hunk #8 NOT MERGED at 1858-2057.
1 out of 8 hunks FAILED -- saving rejects to file lib/libv4l2/libv4l2.c.rej
patching file lib/libv4lconvert/control/libv4lcontrol.c
Applied patch patches/lmml_50901_libv4l_make_libv4l2_usable_on_devices_with_complex_pipeline.patch (forced; needs refresh)

I even tried to reset the tree to a patchset earlier than July, 8.
Same issue.

I could manually try to fix it, but that misordered hunks
warning is weird. Makes me wonder if the patch is not mangled.

Could you please re-send it on the top of upstream? Better to use
git send-email, as it won't mangle your patch.

Alternatively, please send me a ssh public key in private, and I'll
give you access to the repository where you can store those patches.

Thanks!
Mauro



Thanks,
Mauro
Pavel Machek July 28, 2018, 7:36 p.m. UTC | #4
Hi!

> > > Add support for opening multiple devices in v4l2_open(), and for
> > > mapping controls between devices.
> > > 
> > > This is necessary for complex devices, such as Nokia N900.
> > > 
> > > Signed-off-by: Pavel Machek <pavel@ucw.cz>  
> > 
> > Ping?
> > 
> > There's a lot of work to do on libv4l2... timely patch handling would
> > be nice.
> 
> As we're be start working at the new library in order to support
> complex cameras, and I don't want to prevent you keeping doing your
> work, IMHO the best way to keep doing it would be to create two
> libv4l2 forks:
> 
> 	- one to be used by you - meant to support N900 camera;
> 	- another one to be used by Google/Intel people that will
> 	  be working at the Complex camera.

Actually, I guess it would be better to share branches. N900 really
has Complex camera :-). Yes, there are some details that are different
(IPU3 will need to pass data between sensor and processing pipeline),
but it

1) will probably need to "propagate controls"
2) will need autofocus
3) would benefit from same autogain improvements
4) [probably most difficult] figure out how to support more than
8-bits depth.

> So, please send me, in priv, a .ssh key for me to add you an
> account at linuxtv.org. I'll send you instructions about how to
> use the new account.

Done, you should have key in your inbox.

Best regards,
									Pavel
Pavel Machek July 28, 2018, 9:02 p.m. UTC | #5
Hi!

> Alternatively, please send me a ssh public key in private, and I'll
> give you access to the repository where you can store those patches.

Lets do it that way. Sorry for the confusion.
									Pavel
Pavel Machek July 28, 2018, 9:11 p.m. UTC | #6
Hi!

> > > Add support for opening multiple devices in v4l2_open(), and for
> > > mapping controls between devices.
> > > 
> > > This is necessary for complex devices, such as Nokia N900.
> > > 
> > > Signed-off-by: Pavel Machek <pavel@ucw.cz>  
> > 
> > Ping?
> > 
> > There's a lot of work to do on libv4l2... timely patch handling would
> > be nice.
> 
> As we're be start working at the new library in order to support
> complex cameras, and I don't want to prevent you keeping doing your
> work, IMHO the best way to keep doing it would be to create two
> libv4l2 forks:

BTW.. new library. Was there decision what langauge to use? I know C
is obvious choice, but while working on libv4l2, I wished it would be
Rust...

Rewriting same routine over and over, with slightly different types
was not too much fun, and it looked like textbook example for
generics...

Best regards,
									Pavel
Mauro Carvalho Chehab July 30, 2018, 9:31 a.m. UTC | #7
Em Sat, 28 Jul 2018 23:11:10 +0200
Pavel Machek <pavel@ucw.cz> escreveu:

> Hi!
> 
> > > > Add support for opening multiple devices in v4l2_open(), and for
> > > > mapping controls between devices.
> > > > 
> > > > This is necessary for complex devices, such as Nokia N900.
> > > > 
> > > > Signed-off-by: Pavel Machek <pavel@ucw.cz>    
> > > 
> > > Ping?
> > > 
> > > There's a lot of work to do on libv4l2... timely patch handling would
> > > be nice.  
> > 
> > As we're be start working at the new library in order to support
> > complex cameras, and I don't want to prevent you keeping doing your
> > work, IMHO the best way to keep doing it would be to create two
> > libv4l2 forks:  
> 
> BTW.. new library. Was there decision what langauge to use? I know C
> is obvious choice, but while working on libv4l2, I wished it would be
> Rust...
> 
> Rewriting same routine over and over, with slightly different types
> was not too much fun, and it looked like textbook example for
> generics...

Whatever language it uses, the library should provide a standard C API
interface and avoid using libraries that may not be available on
the systems supported by the v4l-utils package, as other packages
and a libv4l-compatible interface should be linked using it.

It should also be something that the existing v4l-utils developers are
familiar with. Right now, we have only C and C++ code inside v4l-utils.

So, I'd say that the language should be either C (the obvious choice)
or C++.

It should also be licensed using the same terms as v4l-utils libraries,
e. g. LGPL 2.1+.

Thanks,
Mauro
diff mbox

Patch

diff --git a/lib/include/libv4l2.h b/lib/include/libv4l2.h
index ea1870d..a0ec0a9 100644
--- a/lib/include/libv4l2.h
+++ b/lib/include/libv4l2.h
@@ -58,6 +58,10 @@  LIBV4L_PUBLIC extern FILE *v4l2_log_file;
    invalid memory address will not lead to failure with errno being EFAULT,
    as it would with a real ioctl, but will cause libv4l2 to break, and you
    get to keep both pieces.
+
+   You can open complex pipelines by passing ".cv" file with pipeline
+   description to v4l2_open(). libv4l2 will open all the required
+   devices automatically in that case.
 */
 
 LIBV4L_PUBLIC int v4l2_open(const char *file, int oflag, ...);
diff --git a/lib/libv4l2/libv4l2-priv.h b/lib/libv4l2/libv4l2-priv.h
index 1924c91..1ee697a 100644
--- a/lib/libv4l2/libv4l2-priv.h
+++ b/lib/libv4l2/libv4l2-priv.h
@@ -104,6 +104,7 @@  struct v4l2_dev_info {
 	void *plugin_library;
 	void *dev_ops_priv;
 	const struct libv4l_dev_ops *dev_ops;
+	struct v4l2_controls_map *map;
 };
 
 /* From v4l2-plugin.c */
@@ -130,4 +131,20 @@  static inline void v4l2_plugin_cleanup(void *plugin_lib, void *plugin_priv,
 extern const char *v4l2_ioctls[];
 void v4l2_log_ioctl(unsigned long int request, void *arg, int result);
 
+
+struct v4l2_control_map {
+	unsigned long control;
+	int fd;
+};
+
+struct v4l2_controls_map {
+	int main_fd;
+	int num_fds;
+	int num_controls;
+	struct v4l2_control_map map[];
+};
+
+int v4l2_open_pipeline(struct v4l2_controls_map *map, int v4l2_flags);
+LIBV4L_PUBLIC int v4l2_get_fd_for_control(int fd, unsigned long control);
+
 #endif
diff --git a/lib/libv4l2/libv4l2.c b/lib/libv4l2/libv4l2.c
index 2db25d1..ac430f0 100644
--- a/lib/libv4l2/libv4l2.c
+++ b/lib/libv4l2/libv4l2.c
@@ -70,6 +70,8 @@ 
 #include <sys/types.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
+#include <dirent.h>
+
 #include "libv4l2.h"
 #include "libv4l2-priv.h"
 #include "libv4l-plugin.h"
@@ -618,6 +620,8 @@  static void v4l2_update_fps(int index, struct v4l2_streamparm *parm)
 		devices[index].fps = 0;
 }
 
+static int v4l2_open_complex(int fd, int v4l2_flags);
+
 int v4l2_open(const char *file, int oflag, ...)
 {
 	int fd;
@@ -641,6 +645,21 @@  int v4l2_open(const char *file, int oflag, ...)
 	if (fd == -1)
 		return fd;
 
+	int len = strlen(file);
+	char *end = ".cv";
+	int len2 = strlen(end);
+	if ((len > len2) && (!strcmp(file + len - len2, end))) {
+		/* .cv extension */
+		struct stat sb;
+
+		if (fstat(fd, &sb) == 0) {
+			if ((sb.st_mode & S_IFMT) == S_IFREG) {
+				return v4l2_open_complex(fd, 0);
+			}
+		}
+		
+	}
+
 	if (v4l2_fd_open(fd, 0) == -1) {
 		int saved_err = errno;
 
@@ -787,6 +806,8 @@  no_capture:
 	if (index >= devices_used)
 		devices_used = index + 1;
 
+	devices[index].map = NULL;
+
 	/* Note we always tell v4lconvert to optimize src fmt selection for
 	   our default fps, the only exception is the app explicitly selecting
 	   a frame rate using the S_PARM ioctl after a S_FMT */
@@ -1056,12 +1077,47 @@  static int v4l2_s_fmt(int index, struct v4l2_format *dest_fmt)
 	return 0;
 }
 
+int v4l2_get_fd_for_control(int fd, unsigned long control)
+{
+	int index = v4l2_get_index(fd);
+	struct v4l2_controls_map *map;
+	int lo = 0;
+	int hi;
+
+	if (index < 0)
+		return fd;
+
+	map = devices[index].map;
+	if (!map)
+		return fd;
+	hi = map->num_controls;
+
+	while (lo < hi) {
+		int i = (lo + hi) / 2;
+		if (map->map[i].control == control) {
+			return map->map[i].fd;
+		}
+		if (map->map[i].control > control) {
+			hi = i;
+			continue;
+		}
+		if (map->map[i].control < control) {
+			lo = i+1;
+			continue;
+		}
+		printf("Bad: impossible condition in binary search\n");
+		exit(1);
+	}
+	return fd;
+}
+
 int v4l2_ioctl(int fd, unsigned long int request, ...)
 {
 	void *arg;
 	va_list ap;
 	int result, index, saved_err;
-	int is_capture_request = 0, stream_needs_locking = 0;
+	int is_capture_request = 0, stream_needs_locking = 0, 
+	    is_subdev_request = 0;
 
 	va_start(ap, request);
 	arg = va_arg(ap, void *);
@@ -1076,18 +1132,19 @@  int v4l2_ioctl(int fd, unsigned long int request, ...)
 	   ioctl, causing it to get sign extended, depending upon this behavior */
 	request = (unsigned int)request;
 
 	if (devices[index].convert == NULL)
 		goto no_capture_request;
 
 	/* Is this a capture request and do we need to take the stream lock? */
 	switch (request) {
-	case VIDIOC_QUERYCAP:
 	case VIDIOC_QUERYCTRL:
 	case VIDIOC_G_CTRL:
 	case VIDIOC_S_CTRL:
 	case VIDIOC_G_EXT_CTRLS:
-	case VIDIOC_TRY_EXT_CTRLS:
 	case VIDIOC_S_EXT_CTRLS:
+		is_subdev_request = 1;
+	case VIDIOC_QUERYCAP:
+	case VIDIOC_TRY_EXT_CTRLS:
 	case VIDIOC_ENUM_FRAMESIZES:
 	case VIDIOC_ENUM_FRAMEINTERVALS:
 		is_capture_request = 1;
@@ -1151,10 +1209,15 @@  int v4l2_ioctl(int fd, unsigned long int request, ...)
 	}
 
 	if (!is_capture_request) {
+		int sub_fd;
 no_capture_request:
+		sub_fd = fd;
+		if (is_subdev_request) {
+			sub_fd = v4l2_get_fd_for_control(index, ((struct v4l2_queryctrl *) arg)->id);
+		}
 		result = devices[index].dev_ops->ioctl(
 				devices[index].dev_ops_priv,
-				fd, request, arg);
+				sub_fd, request, arg);
 		saved_err = errno;
 		v4l2_log_ioctl(request, arg, result);
 		errno = saved_err;
@@ -1782,3 +1845,194 @@  int v4l2_get_control(int fd, int cid)
 			(qctrl.maximum - qctrl.minimum) / 2) /
 		(qctrl.maximum - qctrl.minimum);
 }
+
+int v4l2_open_pipeline(struct v4l2_controls_map *map, int v4l2_flags)
+{
+	int index;
+	int i;
+
+	for (i=0; i<map->num_controls; i++) {
+		if (map->map[i].fd <= 0) {
+			V4L2_LOG_ERR("v4l2_open_pipeline: Bad fd in map.\n");
+			return -1;
+		}
+		if (i>=1 && map->map[i].control <= map->map[i-1].control) {
+			V4L2_LOG_ERR("v4l2_open_pipeline: Controls not sorted.\n");
+			return -1;
+		}
+	}
+
+	i = v4l2_fd_open(map->main_fd, v4l2_flags);
+	index = v4l2_get_index(map->main_fd);
+	devices[index].map = map;
+	return i;
+}
+
+static void scan_devices(char **device_names, int *device_fds, int num)
+{
+	struct dirent **namelist;
+	int n;
+	char *class_v4l = "/sys/class/video4linux";
+
+	n = scandir(class_v4l, &namelist, NULL, alphasort);
+	if (n < 0) {
+		perror("scandir");
+		return;
+	}
+	
+	while (n--) {
+		if (namelist[n]->d_name[0] != '.') {
+			char filename[1024], content[1024];
+			sprintf(filename, "%s/%s/name", class_v4l, namelist[n]->d_name);
+			FILE *f = fopen(filename, "r");
+			if (!f) {
+				printf("Strange, can't open %s", filename);
+			} else {
+				fgets(content, 1024, f);
+				fclose(f);
+
+				int i;
+				for (i = num-1; i >=0; i--) {
+					if (!strcmp(content, device_names[i])) {
+						sprintf(filename, "/dev/%s", namelist[n]->d_name);
+						device_fds[i] = open(filename, O_RDWR);
+						if (device_fds[i] < 0) {
+							V4L2_LOG_ERR("Error opening %s: %m\n", filename);
+						}
+					}
+				}
+			}
+		}
+		free(namelist[n]);
+	}
+	free(namelist);
+  
+}
+
+static int v4l2_open_complex(int fd, int v4l2_flags)
+{
+#define perr(s) V4L2_LOG_ERR("open_complex: " s "\n")
+#define BUF 256
+	FILE *f = fdopen(fd, "r");
+
+	int res = -1;
+	char buf[BUF];
+	int version, num_modes, num_devices, num_controls;
+	int dev, control;
+
+	if (!f) {
+		perr("open of .cv file failed: %m");
+		goto err;
+	}
+
+	if (fscanf(f, "Complex Video: %d\n", &version) != 1) {
+		perr(".cv file does not have required header");
+		goto close;
+	}
+
+	if (version != 0) {
+		perr(".cv file has unknown version");
+		goto close;
+	}
+  
+	if (fscanf(f, "#modes: %d\n", &num_modes) != 1) {
+		perr("could not parse modes");
+		goto close;
+	}
+
+	if (num_modes != 1) {
+		perr("only single mode is supported for now");
+		goto close;
+	}
+
+	if (fscanf(f, "Mode: %s\n", buf) != 1) {
+		perr("could not parse mode name");
+		goto close;
+	}
+
+	if (fscanf(f, " #devices: %d\n", &num_devices) != 1) {
+		perr("could not parse number of devices");
+		goto close;
+	}
+#define MAX_DEVICES 16
+	char *device_names[MAX_DEVICES] = { NULL, };
+	int device_fds[MAX_DEVICES];
+	if (num_devices > MAX_DEVICES) {
+		perr("too many devices");
+		goto close;
+	}
+  
+	for (dev = 0; dev < num_devices; dev++) {
+		int tmp;
+		if (fscanf(f, "%d: ", &tmp) != 1) {
+			perr("could not parse device");
+			goto free_devices;
+		}
+		if (tmp != dev) {
+			perr("bad device number");
+			goto free_devices;
+		}
+		fgets(buf, BUF, f);
+		device_names[dev] = strdup(buf);
+		device_fds[dev] = -1;
+	}
+
+	scan_devices(device_names, device_fds, num_devices);
+
+	for (dev = 0; dev < num_devices; dev++) {
+		if (device_fds[dev] == -1) {
+			perr("Could not open all required devices");
+			goto close_devices;
+		}
+	}
+
+	if (fscanf(f, " #controls: %d\n", &num_controls) != 1) {
+		perr("can not parse number of controls");
+		goto close_devices;
+	}
+
+	struct v4l2_controls_map *map = malloc(sizeof(struct v4l2_controls_map) +
+					       num_controls*sizeof(struct v4l2_control_map));
+
+	map->num_controls = num_controls;
+	map->num_fds = num_devices;
+	map->main_fd = device_fds[0];
+  
+	for (control = 0; control < num_controls; control++) {
+		unsigned long num;
+		int dev;
+		if (fscanf(f, "0x%lx: %d\n", &num, &dev) != 2) {
+			perr("could not parse control");
+			goto free_map;
+		}
+		if ((dev < 0) || (dev >= num_devices)) {
+			perr("device out of range");
+			goto free_map;
+		}
+		map->map[control].control = num;
+		map->map[control].fd = device_fds[dev];
+	}
+	if (fscanf(f, "%s", buf) > 0) {
+		perr("junk at end of file");
+		goto free_map;
+	}
+
+	res = v4l2_open_pipeline(map, v4l2_flags);
+
+	if (res < 0) {
+free_map:
+		free(map);
+close_devices:
+		for (dev = 0; dev < num_devices; dev++)
+			close(device_fds[dev]);
+	}
+free_devices:
+	for (dev = 0; dev < num_devices; dev++) {
+		free(device_names[dev]);
+	}
+close:
+	fclose(f);
+err:
+	return res;
+}
+
diff --git a/lib/libv4lconvert/control/libv4lcontrol.c b/lib/libv4lconvert/control/libv4lcontrol.c
index 59f28b1..c1e6f93 100644
--- a/lib/libv4lconvert/control/libv4lcontrol.c
+++ b/lib/libv4lconvert/control/libv4lcontrol.c
@@ -863,6 +863,7 @@  int v4lcontrol_vidioc_queryctrl(struct v4lcontrol_data *data, void *arg)
 	struct v4l2_queryctrl *ctrl = arg;
 	int retval;
 	uint32_t orig_id = ctrl->id;
+	int fd;
 
 	/* if we have an exact match return it */
 	for (i = 0; i < V4LCONTROL_COUNT; i++)
@@ -872,8 +873,9 @@  int v4lcontrol_vidioc_queryctrl(struct v4lcontrol_data *data, void *arg)
 			return 0;
 		}
 
+	fd = v4l2_get_fd_for_control(data->fd, ctrl->id);
 	/* find out what the kernel driver would respond. */
-	retval = data->dev_ops->ioctl(data->dev_ops_priv, data->fd,
+	retval = data->dev_ops->ioctl(data->dev_ops_priv, fd,
 			VIDIOC_QUERYCTRL, arg);
 
 	if ((data->priv_flags & V4LCONTROL_SUPPORTS_NEXT_CTRL) &&
@@ -903,6 +905,7 @@  int v4lcontrol_vidioc_g_ctrl(struct v4lcontrol_data *data, void *arg)
 {
 	int i;
 	struct v4l2_control *ctrl = arg;
+	int fd;
 
 	for (i = 0; i < V4LCONTROL_COUNT; i++)
 		if ((data->controls & (1 << i)) &&
@@ -911,7 +914,8 @@  int v4lcontrol_vidioc_g_ctrl(struct v4lcontrol_data *data, void *arg)
 			return 0;
 		}
 
-	return data->dev_ops->ioctl(data->dev_ops_priv, data->fd,
+	fd = v4l2_get_fd_for_control(data->fd, ctrl->id);
+	return data->dev_ops->ioctl(data->dev_ops_priv, fd,
 			VIDIOC_G_CTRL, arg);
 }
 
@@ -994,6 +998,7 @@  int v4lcontrol_vidioc_s_ctrl(struct v4lcontrol_data *data, void *arg)
 {
 	int i;
 	struct v4l2_control *ctrl = arg;
+	int fd;
 
 	for (i = 0; i < V4LCONTROL_COUNT; i++)
 		if ((data->controls & (1 << i)) &&
@@ -1008,7 +1013,8 @@  int v4lcontrol_vidioc_s_ctrl(struct v4lcontrol_data *data, void *arg)
 			return 0;
 		}
 
-	return data->dev_ops->ioctl(data->dev_ops_priv, data->fd,
+	fd = v4l2_get_fd_for_control(data->fd, ctrl->id);
+	return data->dev_ops->ioctl(data->dev_ops_priv, fd,
 			VIDIOC_S_CTRL, arg);
 }