/**
 * \file
 * Webcam library implementation.
 *
 * \ingroup libwebcam
 */

/*
 * Copyright (c) 2006-2008 Logitech.
 *
 * This file is part of libwebcam.
 *
 * libwebcam is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libwebcam is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libwebcam.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _GNU_SOURCE
	#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <limits.h>
#include <dirent.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/ioctl.h>
#include <errno.h>

#include "webcam.h"
#include "libwebcam.h"

#include "compat.h"

#ifdef USE_LOGITECH_DYNCTRL
#include "dynctrl-logitech.h"
#endif


/// A flag indicating whether the library was initialized.
int initialized = 0;
/// A list of webcam devices found in the system.
static DeviceList device_list;
/// The fixed size list of file handles.
HandleList handle_list;


/*
 * Forward declarations
 */

void print_libwebcam_error (char *format, ...);
static void print_libwebcam_c_error (CResult error, char *format, ...);

static unsigned int get_control_dynamics_length(Device *device, unsigned int *names_length, unsigned int *choices_length);
static Control *find_control_by_id (Device *dev, CControlId id);

static CResult refresh_device_list (void);
static Device *find_device_by_name (const char *name);
static int get_device_dynamics_length (CDevice *device);
static int get_devices_dynamics_length (void);

int open_v4l2_device(char *device_name);
static CResult read_v4l2_control(Device *device, Control *control, CControlValue *value, CHandle hDevice);
static CResult write_v4l2_control(Device *device, Control *control, const CControlValue *value, CHandle hDevice);
static CControlId get_control_id_from_v4l2 (int v4l2_id, Device *dev);

static CResult get_device_usb_info (Device *device, CUSBInfo *usbinfo);
static CResult get_mimetype_from_fourcc(char **mimetype, unsigned int fourcc);

static CHandle create_handle(Device *device);
static void close_handle(CHandle handle);
static void set_last_error(CHandle hDevice, int error);


/*
 * Devices
 */

/**
 * Opens a camera device.
 *
 * The function returns a handle that can be used for all functions that require
 * a device handle.
 *
 * @param device_name	Name of the device to open.
 * 						Two different naming schemes are accepted: Full device names
 * 						(e.g. '/dev/video0') and short names (e.g. 'video0') as
 * 						returned by c_enum_devices().
 * @return
 * 		- a device handle greater than zero on success
 * 		- 0 if an error has occurred
 */
CHandle c_open_device (const char *device_name)
{
	CHandle handle;
	const char *v4l2_name;

	if(device_name == NULL || !initialized) {
		print_libwebcam_error("Unable to open device. No name given or library not initialized.");
		return 0;
	}

	// Try to find the device with the given name.
	// Note: If the given name is a device path (e.g. /dev/video0), the V4L2 name
	// is simply generated by cutting off the '/dev/' part. If the given name
	// starts with 'video', it is taken as is.
	if(strstr(device_name, "/dev/video") == device_name)
		v4l2_name = &device_name[5];
	else if(strstr(device_name, "video") == device_name)
		v4l2_name = device_name;
	else {
		print_libwebcam_error("Unable to open device '%s'. Unrecognized device name.", device_name);
		return 0;
	}
	Device *device = find_device_by_name(v4l2_name);
	if(device == NULL) {
		print_libwebcam_error("Unable to open device '%s'. Device not found.", device_name);
		return 0;
	}

	// Open device when needed
	if (device->fd== 0) {
		device->fd= open_v4l2_device(device->v4l2_name);
		if (device->fd<= 0) {
			print_libwebcam_error("open sys call failed for %s'.", device_name);
			// Open error
			device->fd= 0;
			return 0;
		}
	}

	// Create a handle for the given device
	// TODO Race condition if delete_device is called here (via c_cleanup)
	handle = create_handle(device);
	if (handle== 0) {
		close(device->fd);
		device->fd= 0;
	}
	return handle;
}


/**
 * Closes a device handle.
 *
 * @param	hDevice		a handle obtained from c_open_device()
 */
void c_close_device (CHandle hDevice)
{
	if(!initialized)
		return;
	close_handle(hDevice);
}

/**
 * Given a handle returns the corresponding file descriptor.
 *
 * The function returns a file descriptor that can be used for low level operations outside
 * this library.
 *
 * @param	hDevice		a handle obtained from c_open_device()
 *
 * @return
 * 		- a file descriptor greater than zero on success
 * 		- 0 if an error has occurred
 */
int c_get_file_descriptor (CHandle hDevice)
{
	// Check the given handle and arguments
	if(!initialized)
		return 0;
	if(!HANDLE_OPEN(hDevice))
		return 0;
	if(!HANDLE_VALID(hDevice))
		return 0;
	Device *device = GET_HANDLE(hDevice).device;
	if (!device) return 0;
	
	return device->fd;
}

/**
 * Enumerates all devices available in the system.
 *
 * Users must call c_init() prior to using this function.
 *
 * If the buffer is not large enough, #C_BUFFER_TOO_SMALL is returned and
 * the \a size parameter is modified to contain the required buffer size.
 *
 * @param	devices		a pointer to a buffer that retrieves the list of devices
 * @param	size		a pointer to an integer that contains or receives the size
 * 						of the @a devices buffer
 * @param	count		a pointer to an integer that receives the number of devices
 * 						available. Can be NULL. If this argument is not NULL, the
 * 						device count is returned independent of whether or not the
 * 						buffer is large enough.
 * @return
 * 		- #C_SUCCESS on success
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_SYNC_ERROR if the synchronization structures could not be initialized
 * 		- #C_INVALID_ARG if no size pointer was given or if a size pointer was given
 * 		  but no @a devices buffer was given
 * 		- #C_BUFFER_TOO_SMALL if the supplied buffer is not large enough
 */
CResult c_enum_devices (CDevice *devices, unsigned int *size, unsigned int *count)
{
	CResult ret = C_SUCCESS;

	if(!initialized)
		return C_INIT_ERROR;
	if(size == NULL)
		return C_INVALID_ARG;

	// Refresh the internal device list
	ret = refresh_device_list();
	if(ret) return ret;

	if(lock_mutex(&device_list.mutex))
		return C_SYNC_ERROR;

	// Return the required size if the given size is not large enough
	if(count)
		*count = device_list.count;
	int dynamics_length = get_devices_dynamics_length();
	int req_size = device_list.count * sizeof(CDevice) + dynamics_length;
	if(req_size > *size) {
		*size = req_size;
		ret = C_BUFFER_TOO_SMALL;
		goto done;
	}
	if(device_list.count == 0)
		goto done;
	if(devices == NULL) {
		ret = C_INVALID_ARG;
		goto done;
	}

	// Loop through all devices and return a list of CDevice structs
	CDevice *current = devices;
	Device *elem = device_list.first;
	unsigned int dynamics_offset = device_list.count * sizeof(CDevice);
	while(elem) {
		// Copy the simple attributes
		memcpy(current, &elem->device, sizeof(elem->device));

		// Copy the strings
		copy_string_to_buffer(&current->shortName,	elem->device.shortName, devices, &dynamics_offset);
		copy_string_to_buffer(&current->name,		elem->device.name,		devices, &dynamics_offset);
		copy_string_to_buffer(&current->driver,		elem->device.driver,	devices, &dynamics_offset);
		copy_string_to_buffer(&current->location,	elem->device.location,	devices, &dynamics_offset);

		current++;
		elem = elem->next;
	}
	assert(dynamics_offset == req_size);

done:
	unlock_mutex(&device_list.mutex);
	return ret;
}


/**
 * Returns information about a given camera device.
 *
 * The function returns information about a device specified using a device handle
 * obtained from c_open_device() or a string that would be recognized by the same
 * function. The @a hDevice and @a device_name arguments are mutually exclusive.
 * If both are specified, the handle is used. If the device name should be used,
 * @a hDevice should be set to zero.
 *
 * If the buffer is not large enough, #C_BUFFER_TOO_SMALL is returned and
 * the \a size parameter is modified to contain the required buffer size.
 *
 * Specifying a size of sizeof(CDevice) + strlen(device_name) + 84 will usually
 * be enough. This information can be used to try receiving device information in
 * a statically allocated buffer first. The number comes from the field lengths
 * that V4L2 uses internally. There is no guarantee, however, that this does not
 * change in the future, so applications must be prepared to allocate more memory
 * if indicated by a return value of #C_BUFFER_TOO_SMALL.
 *
 * @param hDevice		a handle obtained from c_open_device()
 * @param device_name	a device name as accepted by c_open_device()
 * @param info			a pointer to a buffer to receive the device information
 * @param size			a pointer to an integer that contains or receives the size
 * 						of the @a info buffer
 * @return
 * 		- #C_SUCCESS on success
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_SYNC_ERROR if the synchronization structures could not be initialized
 * 		- #C_INVALID_ARG if no size pointer was given or if a size pointer was given
 * 		  but no @a info buffer was given
 * 		- #C_INVALID_HANDLE if a non-zero invalid handle was specified
 * 		- #C_BUFFER_TOO_SMALL if the supplied buffer is not large enough
 */
CResult c_get_device_info (CHandle hDevice, const char *device_name, CDevice *info, unsigned int *size)
{
	CDevice *info_src;

	if(!initialized)
		return C_INIT_ERROR;
	if(size == NULL)
		return C_INVALID_ARG;

	// Look for the device
	if(hDevice) {				// By device handle
		if(!HANDLE_OPEN(hDevice))
			return C_INVALID_HANDLE;
		if(!HANDLE_VALID(hDevice))
			return C_NOT_EXIST;
		info_src = &GET_HANDLE(hDevice).device->device;
	}
	else if(device_name) {		// By device name
		Device *device = find_device_by_name(device_name);
		if(device == NULL)
			return C_NOT_FOUND;
		info_src = &device->device;
	}
	else {
		return C_INVALID_ARG;
	}

	// Return the required size if the given size is not large enough
	int dynamics_length = get_device_dynamics_length(info_src);
	int req_size = sizeof(*info_src) + dynamics_length;
	if(req_size > *size) {
		*size = req_size;
		return C_BUFFER_TOO_SMALL;
	}
	if(info == NULL)
		return C_INVALID_ARG;

	// Copy the simple values
	memcpy(info, info_src, sizeof(*info_src));

	// Copy the strings
	unsigned int dynamics_offset = sizeof(*info_src);
	copy_string_to_buffer(&info->shortName,	info_src->shortName, info, &dynamics_offset);
	copy_string_to_buffer(&info->name,		info_src->name,		 info, &dynamics_offset);
	copy_string_to_buffer(&info->driver,	info_src->driver,	 info, &dynamics_offset);
	copy_string_to_buffer(&info->location,	info_src->location,	 info, &dynamics_offset);
	assert(dynamics_offset == req_size);

	return C_SUCCESS;
}



/*
 * Frame format enumeration
 */

/**
 * Enumerates all pixel formats supported by the given camera.
 *
 * If the buffer is not large enough, #C_BUFFER_TOO_SMALL is returned and
 * the \a size parameter is modified to contain the required buffer size.
 *
 * @param 	hDevice		a handle obtained from c_open_device()
 * @param	formats		a pointer to a buffer that retrieves the list of pixel formats
 * @param	size		a pointer to an integer that contains or receives the size
 * 						of the @a formats buffer
 * @param	count		a pointer to an integer that receives the number of pixel
 * 						formats supported. Can be NULL. If this argument is not NULL,
 * 						the device count is returned independent of whether or not
 * 						the buffer is large enough.
 * @return
 * 		- #C_SUCCESS on success
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_INVALID_HANDLE if the given device handle is invalid
 * 		- #C_INVALID_ARG if no size pointer was given or if a size pointer was given
 * 		  but no @a formats buffer was given
 * 		- #C_INVALID_DEVICE if the device could not be opened
 * 		- #C_BUFFER_TOO_SMALL if the supplied buffer is not large enough
 * 		- #C_NO_MEMORY if no temporary memory could be allocated
 * 		- #C_V4L2_ERROR if a V4L2 error occurred during pixel format enumeration
 */
CResult c_enum_pixel_formats (CHandle hDevice, CPixelFormat *formats, unsigned int *size, unsigned int *count)
{
	CResult ret = C_SUCCESS;
	int v4l2_dev;

	// Check the given handle and arguments
	if(!initialized)
		return C_INIT_ERROR;
	if(!HANDLE_OPEN(hDevice))
		return C_INVALID_HANDLE;
	if(!HANDLE_VALID(hDevice))
		return C_NOT_EXIST;
	Device *device = GET_HANDLE(hDevice).device;
	if(size == NULL)
		return C_INVALID_ARG;

	// Open the corresponding V4L2 device
	v4l2_dev = device->fd; //open_v4l2_device(device->v4l2_name);
	if(!v4l2_dev)
		return C_INVALID_DEVICE;

	// Run V4L2 pixel format enumeration
	typedef struct _PixelFormat {
		CPixelFormat		format;
		struct _PixelFormat	* next;
	} PixelFormat;
	PixelFormat *head = NULL, *tail = NULL;
	unsigned int req_size = 0, format_count = 0;
	struct v4l2_fmtdesc fmt;
	memset(&fmt, 0, sizeof(fmt));
	fmt.index = 0;
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	while(ioctl(v4l2_dev, VIDIOC_ENUM_FMT, &fmt) == 0) {
		PixelFormat *format = (PixelFormat *)malloc(sizeof(PixelFormat));
		if(!format) {
			ret = C_NO_MEMORY;
			goto done;
		}
		memset(format, 0, sizeof(PixelFormat));
		fmt.index++;

		// Copy the pixel format attributes
		sprintf(format->format.fourcc, "%c%c%c%c",
				fmt.pixelformat & 0xFF, (fmt.pixelformat >> 8) & 0xFF,
				(fmt.pixelformat >> 16) & 0xFF, (fmt.pixelformat >> 24) & 0xFF);
		format->format.name = strdup((char *)fmt.description);
		req_size += sizeof(CPixelFormat) + strlen(format->format.name) + 1;
		if(!get_mimetype_from_fourcc(&format->format.mimeType, fmt.pixelformat))
			req_size += strlen(format->format.mimeType) + 1;
		else
			format->format.mimeType = NULL;
		format_count++;

		// Append the format to the list
		if(head == NULL)
			head = tail = format;
		else
			tail->next = format;
		tail = format;
	}
	if(errno != EINVAL) {
		ret = C_V4L2_ERROR;
		set_last_error(hDevice, errno);
		goto done;
	}

	// Return the required size if the given size is not large enough
	if(count)
		*count = format_count;
	if(req_size > *size) {
		*size = req_size;
		ret = C_BUFFER_TOO_SMALL;
		goto done;
	}
	if(format_count == 0)
		goto done;
	if(formats == NULL) {
		ret = C_INVALID_ARG;
		goto done;
	}

	// Loop through the formats and return a list of CPixelFormat structs
	CPixelFormat *current = formats;
	PixelFormat *elem = head;
	unsigned int dynamics_offset = format_count * sizeof(CPixelFormat);
	while(elem) {
		// Copy the simple attributes
		memcpy(current, &elem->format, sizeof(elem->format));

		// Copy the strings
		copy_string_to_buffer(&current->name, elem->format.name, formats, &dynamics_offset);
		if(elem->format.mimeType)
			copy_string_to_buffer(&current->mimeType, elem->format.mimeType, formats, &dynamics_offset);

		current++;
		elem = elem->next;
	}
	assert(dynamics_offset == req_size);

done:
	// Free the list of pixel formats and close the V4L2 device
	//close(v4l2_dev);
	elem = head;
	while(elem) {
		PixelFormat *next = elem->next;
		if(elem->format.mimeType) free(elem->format.mimeType);
		if(elem->format.name) free(elem->format.name);
		free(elem);
		elem = next;
	}
	return ret;
}


/**
 * Enumerates all frame sizes supported for the given pixel format.
 *
 * If the buffer is not large enough, #C_BUFFER_TOO_SMALL is returned and
 * the \a size parameter is modified to contain the required buffer size.
 *
 * A list of pixel formats can be obtained from c_enum_pixel_formats().
 *
 * @param 	hDevice		a handle obtained from c_open_device()
 * @param	pixelformat	the pixel format for which the frame sizes should be
 * 						enumerated
 * @param	sizes		a pointer to a buffer that retrieves the list of frame sizes
 * @param	size		a pointer to an integer that contains or receives the size
 * 						of the @a sizes buffer
 * @param	count		a pointer to an integer that receives the number of frame
 * 						sizes supported for the given pixel format. Can be NULL.
 * 						If this argument is not NULL, the frame size count is
 * 						returned independent of whether or not the buffer is large
 * 						enough.
 * @return
 * 		- #C_SUCCESS on success
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_INVALID_HANDLE if the given device handle is invalid
 * 		- #C_INVALID_ARG if no size pointer was given; if a size pointer was given
 * 		  but no @a sizes buffer was given; if no @a pixelformat was given
 * 		- #C_INVALID_DEVICE if the device could not be opened
 * 		- #C_BUFFER_TOO_SMALL if the supplied buffer is not large enough
 * 		- #C_NO_MEMORY if no temporary memory could be allocated
 * 		- #C_V4L2_ERROR if a V4L2 error occurred during frame size enumeration
 */
CResult c_enum_frame_sizes (CHandle hDevice, const CPixelFormat *pixelformat, CFrameSize *sizes, unsigned int *size, unsigned int *count)
{
	CResult ret = C_SUCCESS;
	int v4l2_dev;

	// Check the given handle and arguments
	if(!initialized)
		return C_INIT_ERROR;
	if(!HANDLE_OPEN(hDevice))
		return C_INVALID_HANDLE;
	if(!HANDLE_VALID(hDevice))
		return C_NOT_EXIST;
	Device *device = GET_HANDLE(hDevice).device;
	if(size == NULL || pixelformat == NULL)
		return C_INVALID_ARG;

	// Open the corresponding V4L2 device
	v4l2_dev =  device->fd; //open_v4l2_device(device->v4l2_name);
	if(!v4l2_dev)
		return C_INVALID_DEVICE;

	// Run V4L2 frame size enumeration
	typedef struct _FrameSize {
		CFrameSize			size;
		struct _FrameSize	* next;
	} FrameSize;
	FrameSize *head = NULL, *tail = NULL;
	unsigned int req_size = 0, size_count = 0;
	struct v4l2_frmsizeenum fsize;
	memset(&fsize, 0, sizeof(fsize));
	fsize.index = 0;
	fsize.pixel_format = pixelformat->fourcc[0] |
			(unsigned long)pixelformat->fourcc[1] << 8 |
			(unsigned long)pixelformat->fourcc[2] << 16 |
			(unsigned long)pixelformat->fourcc[3] << 24;
	fsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	while(ioctl(v4l2_dev, VIDIOC_ENUM_FRAMESIZES, &fsize) == 0) {
		FrameSize *framesize = (FrameSize *)malloc(sizeof(FrameSize));
		if(!framesize) {
			ret = C_NO_MEMORY;
			goto done;
		}
		memset(framesize, 0, sizeof(FrameSize));
		fsize.index++;

		// Copy the frame size attributes
		if(fsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
			framesize->size.type = CF_SIZE_DISCRETE;
			framesize->size.width = fsize.discrete.width;
			framesize->size.height = fsize.discrete.height;
		}
		else if(fsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
			framesize->size.type = CF_SIZE_CONTINUOUS;
			framesize->size.min_width = fsize.stepwise.min_width;
			framesize->size.max_width = fsize.stepwise.max_width;
			framesize->size.step_width = 1;
			framesize->size.min_height = fsize.stepwise.min_height;
			framesize->size.max_height = fsize.stepwise.max_height;
			framesize->size.step_height = 1;
		}
		else if(fsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
			framesize->size.type = CF_SIZE_STEPWISE;
			framesize->size.min_width = fsize.stepwise.min_width;
			framesize->size.max_width = fsize.stepwise.max_width;
			framesize->size.step_width = fsize.stepwise.step_width;
			framesize->size.min_height = fsize.stepwise.min_height;
			framesize->size.max_height = fsize.stepwise.max_height;
			framesize->size.step_height = fsize.stepwise.step_height;
		}
		req_size += sizeof(CFrameSize);
		size_count++;

		// Append the frame size to the list
		if(head == NULL)
			head = tail = framesize;
		else
			tail->next = framesize;
		tail = framesize;
	}
	if(errno != EINVAL) {
		ret = C_V4L2_ERROR;
		set_last_error(hDevice, errno);
		goto done;
	}

	// Return the required size if the given size is not large enough
	if(count)
		*count = size_count;
	if(req_size > *size) {
		*size = req_size;
		ret = C_BUFFER_TOO_SMALL;
		goto done;
	}
	if(size_count == 0)
		goto done;
	if(sizes == NULL)
		return C_INVALID_ARG;

	// Loop through the formats and return a list of CFrameSize structs
	CFrameSize *current = sizes;
	FrameSize *elem = head;
	while(elem) {
		memcpy(current, &elem->size, sizeof(elem->size));
		current++;
		elem = elem->next;
	}

done:
	// Free the list of frame sizes and close the V4L2 device
	//close(v4l2_dev);
	elem = head;
	while(elem) {
		FrameSize *next = elem->next;
		free(elem);
		elem = next;
	}
	return ret;
}


/**
 * Enumerates all frame intervals supported for the given pixel format and frame size.
 *
 * If the buffer is not large enough, #C_BUFFER_TOO_SMALL is returned and
 * the \a size parameter is modified to contain the required buffer size.
 *
 * A list of pixel formats can be obtained from c_enum_pixel_formats(). In a
 * similar manner the list of supported frame sizes for each pixel format can be
 * obtained from c_enum_frame_sizes().
 *
 * @param 	hDevice		a handle obtained from c_open_device()
 * @param	pixelformat	the pixel format for which the frame intervals should be
 * 						enumerated
 * @param	framesize	the frame size for which the frame intervals should be
 * 						enumerated. Note that this frame size's type must be discrete.
 * @param	intervals	a pointer to a buffer that retrieves the list of frame
 * 						intervals
 * @param	size		a pointer to an integer that contains or receives the size
 * 						of the @a intervals buffer
 * @param	count		a pointer to an integer that receives the number of frame
 * 						intervals supported for the given pixel format and frame
 * 						size. Can be NULL. If this argument is not NULL, the frame
 * 						size count is returned independent of whether or not the
 * 						buffer is large enough.
 * @return
 * 		- #C_SUCCESS on success
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_INVALID_HANDLE if the given device handle is invalid
 * 		- #C_INVALID_ARG if no size pointer was given; if a size pointer was given
 * 		  but no @a sizes buffer was given; if @a pixelformat or @a framesize were
 * 		  not given; if a non-descrete frame size was given
 * 		- #C_INVALID_DEVICE if the device could not be opened
 * 		- #C_BUFFER_TOO_SMALL if the supplied buffer is not large enough
 * 		- #C_NO_MEMORY if no temporary memory could be allocated
 * 		- #C_V4L2_ERROR if a V4L2 error occurred during frame interval enumeration
 */
CResult c_enum_frame_intervals (CHandle hDevice, const CPixelFormat *pixelformat, const CFrameSize *framesize, CFrameInterval *intervals, unsigned int *size, unsigned int *count)
{
	CResult ret = C_SUCCESS;
	int v4l2_dev;

	// Check the given handle and arguments
	if(!initialized)
		return C_INIT_ERROR;
	if(!HANDLE_OPEN(hDevice))
		return C_INVALID_HANDLE;
	if(!HANDLE_VALID(hDevice))
		return C_NOT_EXIST;
	Device *device = GET_HANDLE(hDevice).device;
	if(size == NULL || pixelformat == NULL || framesize == NULL)
		return C_INVALID_ARG;

	// The frame size must be discrete because V4L2's VIDIOC_ENUM_FRAMEINTERVALS function
	// only accepts a single width and height.
	if(framesize->type != CF_SIZE_DISCRETE)
		return C_INVALID_ARG;

	// Open the corresponding V4L2 device
	v4l2_dev = device->fd; //open_v4l2_device(device->v4l2_name);
	if(!v4l2_dev)
		return C_INVALID_DEVICE;

	// Run V4L2 frame interval enumeration
	typedef struct _FrameInterval {
		CFrameInterval			interval;
		struct _FrameInterval	* next;
	} FrameInterval;
	FrameInterval *head = NULL, *tail = NULL;
	unsigned int req_size = 0, interval_count = 0;
	struct v4l2_frmivalenum fival;
	memset(&fival, 0, sizeof(fival));
	fival.index = 0;
	fival.pixel_format = pixelformat->fourcc[0] |
			(unsigned long)pixelformat->fourcc[1] << 8 |
			(unsigned long)pixelformat->fourcc[2] << 16 |
			(unsigned long)pixelformat->fourcc[3] << 24;
	fival.width = framesize->width;
	fival.height = framesize->height;
	fival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	while(ioctl(v4l2_dev, VIDIOC_ENUM_FRAMEINTERVALS, &fival) == 0) {
		FrameInterval *frameinterval = (FrameInterval *)malloc(sizeof(FrameInterval));
		if(!frameinterval) {
			ret = C_NO_MEMORY;
			goto done;
		}
		memset(frameinterval, 0, sizeof(FrameInterval));
		fival.index++;

		// Copy the frame interval attributes
		if(fival.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
			frameinterval->interval.type = CF_INTERVAL_DISCRETE;
			frameinterval->interval.n = fival.discrete.numerator;
			frameinterval->interval.d = fival.discrete.denominator;
		}
		else if(fival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) {
			frameinterval->interval.type = CF_INTERVAL_CONTINUOUS;
			frameinterval->interval.min_n = fival.stepwise.min.numerator;
			frameinterval->interval.min_d = fival.stepwise.min.denominator;
			frameinterval->interval.max_n = fival.stepwise.max.numerator;
			frameinterval->interval.max_d = fival.stepwise.max.denominator;
			frameinterval->interval.step_n = 1;
			frameinterval->interval.step_d = 1;
		}
		else if(fival.type == V4L2_FRMIVAL_TYPE_STEPWISE) {
			frameinterval->interval.type = CF_INTERVAL_STEPWISE;
			frameinterval->interval.min_n = fival.stepwise.min.numerator;
			frameinterval->interval.min_d = fival.stepwise.min.denominator;
			frameinterval->interval.max_n = fival.stepwise.max.numerator;
			frameinterval->interval.max_d = fival.stepwise.max.denominator;
			frameinterval->interval.step_n = fival.stepwise.step.numerator;
			frameinterval->interval.step_d = fival.stepwise.step.denominator;
		}
		req_size += sizeof(CFrameInterval);
		interval_count++;

		// Append the frame interval to the list
		if(head == NULL)
			head = tail = frameinterval;
		else
			tail->next = frameinterval;
		tail = frameinterval;
	}
	if(errno != EINVAL) {
		ret = C_V4L2_ERROR;
		set_last_error(hDevice, errno);
		goto done;
	}

	// Return the required size if the given size is not large enough
	if(count)
		*count = interval_count;
	if(req_size > *size) {
		*size = req_size;
		ret = C_BUFFER_TOO_SMALL;
		goto done;
	}
	if(interval_count == 0)
		goto done;
	if(intervals == NULL)
		return C_INVALID_ARG;

	// Loop through the formats and return a list of CFrameInterval structs
	CFrameInterval *current = intervals;
	FrameInterval *elem = head;
	while(elem) {
		memcpy(current, &elem->interval, sizeof(elem->interval));
		current++;
		elem = elem->next;
	}

done:
	// Free the list of frame sizes and close the V4L2 device
	//close(v4l2_dev);
	elem = head;
	while(elem) {
		FrameInterval *next = elem->next;
		free(elem);
		elem = next;
	}
	return ret;
}



/*
 * Controls
 */

/**
 * Enumerates all controls supported by the given device.
 *
 * If the buffer is not large enough, #C_BUFFER_TOO_SMALL is returned and
 * the \a size parameter is modified to contain the required buffer size.
 *
 * @param hDevice	a device handle obtained from c_open_device()
 * @param controls	a pointer to a buffer that retrieves the list of supported
 * 					controls.
 * @param size		a pointer to an integer that contains or receives the size of
 * 					the \a controls buffer.
 * @param count		a pointer to an integer that receives the number of controls
 * 					supported. Can be NULL. If this argument is not NULL, the control
 * 					count is returned independent of whether or not the buffer is
 * 					large enough.
 * @return
 * 		- #C_SUCCESS on success
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_INVALID_HANDLE if the given device handle is invalid
 * 		- #C_SYNC_ERROR if the synchronization structures could not be initialized
 * 		- #C_INVALID_ARG if no size pointer was given or if a size pointer was given
 * 		  but no @a controls buffer was given
 * 		- #C_BUFFER_TOO_SMALL if the supplied buffer is not large enough
 */
CResult c_enum_controls (CHandle hDevice, CControl *controls, unsigned int *size, unsigned int *count)
{
	CResult ret = C_SUCCESS;
	unsigned int names_length, choices_length;

	// Check the given handle and arguments
	if(!initialized)
		return C_INIT_ERROR;
	if(!HANDLE_OPEN(hDevice))
		return C_INVALID_HANDLE;
	if(!HANDLE_VALID(hDevice))
		return C_NOT_EXIST;
	Device *device = GET_HANDLE(hDevice).device;
	if(size == NULL)
		return C_INVALID_ARG;

	if(lock_mutex(&device->controls.mutex))
		return C_SYNC_ERROR;

	// Determine the buffer size needed to describe all controls
	if(count)
		*count = device->controls.count;
	get_control_dynamics_length(device, &names_length, &choices_length);
	int req_size = device->controls.count * sizeof(CControl) + names_length + choices_length;
	if(req_size > *size) {
		*size = req_size;
		ret = C_BUFFER_TOO_SMALL;
		goto done;
	}
	if(device->controls.count == 0)
		goto done;
	if(controls == NULL)
		return C_INVALID_ARG;

	// Loop through all the device's controls and return a list of CControl structs
	CControl *current = controls;
	Control *elem = device->controls.first;
	unsigned int names_offset = device->controls.count * sizeof(CControl);
	unsigned int choices_offset = names_offset + names_length;
	while(elem) {
		// Copy the simple attributes
		memcpy(current, &elem->control, sizeof(elem->control));

		// Copy the name
		unsigned int name_length = strlen(elem->control.name);
		current->name = (char *)controls + names_offset;
		memcpy(current->name, elem->control.name, name_length + 1);
		assert(names_offset + name_length < req_size);
		names_offset += name_length + 1;

		// Copy the choices
		if(elem->control.type == CC_TYPE_CHOICE) {
			current->choices.count = elem->control.choices.count;
			current->choices.list = (CControlChoice *)((char *)controls + choices_offset);
			choices_offset += elem->control.choices.count * sizeof(CControlChoice);
			current->choices.names = (char *)controls + choices_offset;

			int index;
			for(index = 0; index < elem->control.choices.count; index++) {
				int name_length = strlen(elem->control.choices.list[index].name);
				current->choices.list[index].index = elem->control.choices.list[index].index;
				current->choices.list[index].name = (char *)controls + choices_offset;
				memcpy(current->choices.list[index].name, elem->control.choices.list[index].name, name_length + 1);
				assert(choices_offset + name_length < req_size);
				choices_offset += name_length + 1;
			}
		}

		current++;
		elem = elem->next;
	}
	assert(choices_offset == req_size);

done:
	unlock_mutex(&device->controls.mutex);
	return ret;
}


/**
 * Sets the value of a device control.
 *
 * @param hDevice		a device handle obtained from c_open_device()
 * @param control_id	the ID of the control whose value shall be set
 * @param value			the value to which the control shall be set
 * @return
 * 		- #C_SUCCESS on success
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_INVALID_HANDLE if the given device handle is invalid
 * 		- #C_INVALID_ARG if no value is given
 * 		- #C_NOT_FOUND if the device does not support the given control
 * 		- #C_CANNOT_WRITE if the given control is not writable
 * 		- #C_INVALID_DEVICE if the device could not be opened
 * 		- #C_V4L2_ERROR if a V4L2 error occurred during control access
 */
CResult c_set_control (CHandle hDevice, CControlId control_id, const CControlValue *value)
{
	CResult ret = C_SUCCESS;

	// Check the given handle and arguments
	if(!initialized)
		return C_INIT_ERROR;
	if(!HANDLE_OPEN(hDevice))
		return C_INVALID_HANDLE;
	if(!HANDLE_VALID(hDevice))
		return C_NOT_EXIST;
	Device *device = GET_HANDLE(hDevice).device;
	if(value == NULL)
		return C_INVALID_ARG;

	// Look for the requested control within the given device
	Control *control = find_control_by_id(device, control_id);
	if(!control)
		return C_NOT_FOUND;

	// Check if the control is writable
	if(!(control->control.flags & CC_CAN_WRITE))
		return C_CANNOT_WRITE;

	// Write the control in a way that depends on its source
	if(control->v4l2_control) {		// V4L2
		ret = write_v4l2_control(device, control, value, hDevice);
	}
	else {
		assert(0);
		return C_INVALID_ARG;
	}

	return ret;
}


/**
 * Returns the value of a device control.
 *
 * @param hDevice		a device handle obtained from c_open_device()
 * @param control_id	the ID of the control whose value shall be read
 * @param value			a pointer to receive the value read
 * @return
 * 		- #C_SUCCESS on success
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_INVALID_HANDLE if the given device handle is invalid
 * 		- #C_INVALID_ARG if no value is given
 * 		- #C_NOT_FOUND if the device does not support the given control
 * 		- #C_CANNOT_READ if the given control is not readable
 * 		- #C_INVALID_DEVICE if the device could not be opened
 * 		- #C_V4L2_ERROR if a V4L2 error occurred during control access
 */
CResult c_get_control (CHandle hDevice, CControlId control_id, CControlValue *value)
{
	CResult ret = C_SUCCESS;

	// Check the given handle and arguments
	if(!initialized)
		return C_INIT_ERROR;
	if(!HANDLE_OPEN(hDevice))
		return C_INVALID_HANDLE;
	if(!HANDLE_VALID(hDevice))
		return C_NOT_EXIST;
	Device *device = GET_HANDLE(hDevice).device;
	if(value == NULL)
		return C_INVALID_ARG;

	// Look for the requested control within the given device
	Control *control = find_control_by_id(device, control_id);
	if(!control)
		return C_NOT_FOUND;

	// Check if the control is readable
	if(!(control->control.flags & CC_CAN_READ))
		return C_CANNOT_READ;

	// Read the control in a way that depends on its source
	if(control->v4l2_control) {			// V4L2
		ret = read_v4l2_control(device, control, value, hDevice);
	}
	else {
		assert(0);
		return C_INVALID_ARG;
	}

	return ret;
}


/*
 * Events
 */

/**
 * Enumerates the events supported by the given device. @e [unimplemented]
 *
 * @return
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_NOT_IMPLEMENTED
 */
CResult c_enum_events (CHandle hDevice, CEvent *events, unsigned int *size, unsigned int *count)
{
	if(!initialized)
		return C_INIT_ERROR;
	return C_NOT_IMPLEMENTED;
}


/**
 * Subscribes the caller to receive the given event. @e [unimplemented]
 *
 * @return
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_NOT_IMPLEMENTED
 */
CResult c_subscribe_event (CHandle hDevice, CEventId event_id, CEventHandler handler, void *context)
{
	if(!initialized)
		return C_INIT_ERROR;
	return C_NOT_IMPLEMENTED;
}


/**
 * Unsubscribes the caller from the given event. @e [unimplemented]
 *
 * @return
 * 		- #C_INIT_ERROR if the library has not been initialized
 * 		- #C_NOT_IMPLEMENTED
 */
CResult c_unsubscribe_event (CHandle hDevice, CEventId event_id)
{
	if(!initialized)
		return C_INIT_ERROR;
	return C_NOT_IMPLEMENTED;
}


/**
 * Returns the error message associated with a given error code.
 *
 * Note that the caller must free the buffer returned by this function.
 *
 * @param error		error code for which the message should be retrieved
 * @return
 * 		- a newly allocated string describing the given error code
 * 		- NULL if not enough memory was available or if the error code is
 * 		  unknown
 */
char *c_get_error_text (CResult error)
{
	return c_get_handle_error_text(0, error);
}


/**
 * Returns the error message associated with a given error code and device handle.
 *
 * Note that the caller must free the buffer returned by this function. Compared to the
 * #c_get_error_text() function, this function can take the handle's last system error
 * into account.
 *
 * @param hDevice	an open device handle. If this parameter is 0, the function behaves
 *					exactly like #c_get_error_text().
 * @param error		error code for which the message should be retrieved
 * @return
 * 		- a newly allocated string describing the given error code
 * 		- NULL if not enough memory was available or if the error code is
 * 		  unknown
 */
char *c_get_handle_error_text (CHandle hDevice, CResult error)
{
	switch(error) {
		case C_SUCCESS:				return strdup("Success");
		case C_NOT_IMPLEMENTED:		return strdup("The function is not implemented");
		case C_INIT_ERROR:			return strdup("Error during initialization or library not initialized");
		case C_INVALID_ARG:			return strdup("Invalid argument");
		case C_INVALID_HANDLE:		return strdup("Invalid handle");
		case C_INVALID_DEVICE:		return strdup("Invalid device or device cannot be opened");
		case C_NOT_FOUND:			return strdup("Object not found");
		case C_BUFFER_TOO_SMALL:	return strdup("Buffer too small");
		case C_SYNC_ERROR:			return strdup("Error during data synchronization");
		case C_NO_MEMORY:			return strdup("Out of memory");
		case C_NO_HANDLES:			return strdup("Out of handles");
		case C_V4L2_ERROR:
		{
			char *text = NULL;
			if(hDevice && HANDLE_OPEN(hDevice)) {
				if(asprintf(&text, "A Video4Linux2 API call returned an unexpected error %d", GET_HANDLE(hDevice).last_system_error) == -1)
					text = NULL;;
			}
			if(!text)
				return strdup("A Video4Linux2 API call returned an unexpected error");
			return text;
		}
		case C_SYSFS_ERROR:			return strdup("A sysfs file access returned an error");
		case C_PARSE_ERROR:			return strdup("A control could not be parsed");
		case C_CANNOT_WRITE:		return strdup("Writing not possible (e.g. read-only control)");
		case C_CANNOT_READ:			return strdup("Reading not possible (e.g. write-only control)");
		default:					return NULL;
	}
}



/*
 * Helper functions
 */

/**
 * Prints a generic error message to stderr.
 */
void print_libwebcam_error (char *format, ...)
{
	char *newformat;
	va_list ap;

	if(asprintf(&newformat, "[libwebcam] %s\n", format) == -1)
		newformat = format;

	va_start(ap, format);
	vfprintf(stderr, newformat, ap);
	va_end(ap);

	if(newformat != format)
		free(newformat);
	else
		fprintf(stderr, "\n");
}


/**
 * Prints a libwebcam error message to stderr.
 *
 * @param error		a #CResult error code whose error text is appended
 * @param format	a @a printf compatible format
 */
static void print_libwebcam_c_error (CResult error, char *format, ...)
{
	char *unknown_text = "Unknown error";
	char *text, *newformat;
	va_list ap;

	// Retrieve the libwebcam error text
	text = c_get_error_text(error);
	if(text == NULL)
		text = unknown_text;

	if(asprintf(&newformat, "[libwebcam] %s (error %d: %s)\n", format, error, text) == -1)
		newformat = format;

	if(text != unknown_text)
		free(text);

	va_start(ap, format);
	vfprintf(stderr, newformat, ap);
	va_end(ap);

	if(newformat != format)
		free(newformat);
	else
		fprintf(stderr, "\n");
}



/*
 * Control management
 */

/**
 * Queries the control choice values of the given V4L2 control and uses the results to
 * fill in the choice structures of a libwebcam control.
 *
 * @param ctrl		Internal control for which the choice data is requested.
 * @param v4l2_ctrl	Pointer to a structure obtained from VIDIOC_QUERYCTRL and containing
 * 					the V4L2 control data.
 * @param v4l2_dev	Open V4L2 device handle.
 */
static CResult create_control_choices (Control *ctrl, struct v4l2_queryctrl *v4l2_ctrl, int v4l2_dev)
{
	CResult ret = C_SUCCESS;

	int choices_count = v4l2_ctrl->maximum - v4l2_ctrl->minimum + 1;
	ctrl->control.choices.count = choices_count;

	// Allocate memory for the choices.names and choices.list members
	ctrl->control.choices.names = (char *)malloc(choices_count * V4L2_MENU_CTRL_MAX_NAME_SIZE);
	if(ctrl->control.choices.names == NULL) {
		ret = C_NO_MEMORY;
		goto done;
	}
	ctrl->control.choices.list = (CControlChoice *)malloc(choices_count * sizeof(CControlChoice));
	if(ctrl->control.choices.list == NULL) {
		ret = C_NO_MEMORY;
		goto done;
	}

	// Query the menu items of the given control and transform them
	// into CControlChoice.
	struct v4l2_querymenu v4l2_menu = { .id = v4l2_ctrl->id };
	for(v4l2_menu.index = v4l2_ctrl->minimum; v4l2_menu.index <= v4l2_ctrl->maximum; v4l2_menu.index++) {
		int choice_index = v4l2_menu.index - v4l2_ctrl->minimum;
		if(ioctl(v4l2_dev, VIDIOC_QUERYMENU, &v4l2_menu)) {
			if(errno == EINVAL) {
#ifdef V4L2_CID_EXPOSURE_AUTO
				// Some newer versions of the UVC driver implement an 'Exposure, Auto'
				// menu control whose menu choices don't have contiguous IDs
				// but { 1, 2, 4, 8 } instead.
				if(v4l2_ctrl->id == V4L2_CID_EXPOSURE_AUTO && errno == EINVAL &&
						v4l2_menu.index == 0) {
					print_libwebcam_error(
						"Unsupported V4L2_CID_EXPOSURE_AUTO control with a non-contiguous \n"
						"  range of choice IDs found");
				}
				else
#endif
				{
					print_libwebcam_error(
						"Invalid menu control choice range encountered.\n"
						"  Indicated range is [ %d .. %d ] but querying choice %d failed.",
						v4l2_ctrl->minimum, v4l2_ctrl->maximum, v4l2_menu.index
					);
				}
				ret = C_NOT_IMPLEMENTED;
				goto done;
			}
			ret = C_V4L2_ERROR;
			goto done;
		}
		ctrl->control.choices.list[choice_index].index = v4l2_menu.index;
		ctrl->control.choices.list[choice_index].name = ctrl->control.choices.names + choice_index * V4L2_MENU_CTRL_MAX_NAME_SIZE;
		if(strlen((char *)v4l2_menu.name))
			memcpy(ctrl->control.choices.list[choice_index].name, v4l2_menu.name, V4L2_MENU_CTRL_MAX_NAME_SIZE);
		else
			snprintf(ctrl->control.choices.list[choice_index].name, V4L2_MENU_CTRL_MAX_NAME_SIZE, "%d", v4l2_menu.index);

	}

done:
	if(ret != C_SUCCESS) {
		if(ctrl->control.choices.names)
			free(ctrl->control.choices.names);
		if(ctrl->control.choices.list)
			free(ctrl->control.choices.list);
	}
	return ret;
}


/**
 * Create a libwebcam control from a V4L2 control.
 *
 * If necessary, further information is requested by this function, e.g. in the case
 * of a choice control.
 *
 * @param device	Device to which the control should be appended.
 * @param v4l2_ctrl	Pointer to a structure obtained from VIDIOC_QUERYCTRL and containing
 * 					the V4L2 control data.
 * @param v4l2_dev	Open V4L2 device handle.
 * @param pret		Pointer to receive the error code in the case of an error.
 * 					(Can be NULL.)
 *
 * @return
 * 		- NULL if an error occurred. The associated error can be found in @a pret.
 * 		- Pointer to the newly created control.
 */
static Control *create_v4l2_control (Device *device, struct v4l2_queryctrl *v4l2_ctrl, int v4l2_dev, CResult *pret)
{
	CResult ret = C_SUCCESS;
	Control *ctrl = NULL;

	// Map V4L2 control types to libwebcam control types
	CControlType type;
	switch(v4l2_ctrl->type) {
		case V4L2_CTRL_TYPE_INTEGER:	type = CC_TYPE_DWORD;		break;
		case V4L2_CTRL_TYPE_BOOLEAN:	type = CC_TYPE_BOOLEAN;		break;
		case V4L2_CTRL_TYPE_MENU:		type = CC_TYPE_CHOICE;		break;
#ifdef ENABLE_RAW_CONTROLS
		case V4L2_CTRL_TYPE_STRING:		type = CC_TYPE_RAW;			break;
#endif
		case V4L2_CTRL_TYPE_BUTTON:		// TODO implement
		case V4L2_CTRL_TYPE_INTEGER64:	// TODO implement
			ret = C_NOT_IMPLEMENTED;
			print_libwebcam_error("Warning: Unsupported V4L2 control type encountered: ctrl_id = 0x%08X, "
					"name = '%s', type = %d",
					v4l2_ctrl->id, v4l2_ctrl->name, v4l2_ctrl->type);
			goto done;
		default:
			ret = C_PARSE_ERROR;
			print_libwebcam_error("Invalid V4L2 control type encountered: ctrl_id = 0x%08X, "
					"name = '%s', type = %d",
					v4l2_ctrl->id, v4l2_ctrl->name, v4l2_ctrl->type);
			goto done;
	}

	// Map the V4L2 control ID to a libwebcam ID
	CControlId ctrl_id = get_control_id_from_v4l2(v4l2_ctrl->id, device);
	//printf("Mapping V4L2 control ID 0x%08X => 0x%08X\n", v4l2_ctrl->id, ctrl_id);
	if(ctrl_id == 0) {
		ret = C_NOT_IMPLEMENTED;
		goto done;
	}

	// Create the internal control info structure
	ctrl = (Control *)malloc(sizeof(*ctrl));
	if(ctrl) {
		memset(ctrl, 0, sizeof(*ctrl));
		ctrl->control.id		= ctrl_id;
		ctrl->v4l2_control		= v4l2_ctrl->id;
		if(strlen((char *)v4l2_ctrl->name))
			ctrl->control.name		= strdup((char *)v4l2_ctrl->name);
		else
			ctrl->control.name		= strdup(UNKNOWN_CONTROL_NAME);
		ctrl->control.type		= type;
		ctrl->control.flags		= CC_CAN_READ;
		if(!(v4l2_ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY))
			ctrl->control.flags	|= CC_CAN_WRITE;
		if(v4l2_ctrl->id >= V4L2_CID_PRIVATE_BASE)
			ctrl->control.flags |= CC_IS_CUSTOM;
		ctrl->control.def.value	= v4l2_ctrl->default_value;

		// Process V4L2 menu-style and raw controls
		if(type == CC_TYPE_CHOICE) {
			ret = create_control_choices(ctrl, v4l2_ctrl, v4l2_dev);
			if(ret) goto done;
		}
		else if(type == CC_TYPE_RAW) {
			if(v4l2_ctrl->minimum != v4l2_ctrl->maximum || v4l2_ctrl->step != 1) {
				print_libwebcam_error("Unsupported V4L2 string control encountered: ctrl_id = 0x%08X, "
					"name = '%s', min = %u, max = %u, step = %u",
					v4l2_ctrl->id, v4l2_ctrl->name,
					v4l2_ctrl->minimum, v4l2_ctrl->maximum, v4l2_ctrl->step);
				ret = C_NOT_IMPLEMENTED;
				goto done;
			}
			ctrl->control.value.raw.size =
			ctrl->control.min.raw.size =
			ctrl->control.max.raw.size =
			ctrl->control.def.raw.size = v4l2_ctrl->maximum;
		}
		else {
			ctrl->control.min.value		= v4l2_ctrl->minimum;
			ctrl->control.max.value		= v4l2_ctrl->maximum;
			ctrl->control.step.value	= v4l2_ctrl->step;
		}

		// Add the new control to the control list of the given device
		ctrl->next = device->controls.first;
		device->controls.first = ctrl;
		device->controls.count++;
	}
	else {
		ret = C_NO_MEMORY;
	}

done:
	if(ret != C_SUCCESS && ctrl) {
		if(ctrl->control.name) {
			free(ctrl->control.name);
			ctrl->control.name = NULL;
		}
		free(ctrl);
		ctrl = NULL;
	}
	if(pret)
		*pret = ret;
	return ctrl;
}


/**
 * Frees all resources associated with the given control, including choice data.
 *
 * Note that this functino does not remove the control from its device's control list.
 */
static void delete_control (Control *ctrl)
{
	if(ctrl->control.type == CC_TYPE_CHOICE) {
		if(ctrl->control.choices.list)
			free(ctrl->control.choices.list);
		if(ctrl->control.choices.names)
			free(ctrl->control.choices.names);
	}
	if(ctrl->control.name)
		free(ctrl->control.name);
	free(ctrl);
}


/**
 * Looks up the control with the given ID for the given device.
 *
 * @return
 * 		- NULL if no corresponding control was found for the given device.
 * 		- Pointer to the control if it was found.
 */
static Control *find_control_by_id (Device *dev, CControlId id)
{
	Control *elem = dev->controls.first;
	while(elem) {
		if(elem->control.id == id)
			break;
		elem = elem->next;
	}
	return elem;
}


/**
 * Clears the control list of the given device and frees all associated resources.
 */
static void clear_control_list (Device *dev)
{
	lock_mutex(&dev->controls.mutex);

	Control *elem = dev->controls.first;
	while(elem) {
		Control *next = elem->next;
		delete_control(elem);
		elem = next;
	}
	dev->controls.first = NULL;
	dev->controls.count = 0;

	unlock_mutex(&dev->controls.mutex);
}


/**
 * Scans the given device for supported controls and adds them to the internal list.
 *
 * Note that this function clears all existing controls prior to reenumerating them.
 */
static CResult refresh_control_list (Device *dev)
{
	CResult ret = C_SUCCESS;
	int v4l2_dev;
	struct v4l2_queryctrl v4l2_ctrl = { 0 };

	// Clear control list first
	clear_control_list(dev);

	// Open the corresponding V4L2 device	
	if (dev->fd) v4l2_dev = dev->fd;
	else v4l2_dev = open_v4l2_device(dev->v4l2_name);
	if(!v4l2_dev)
		return C_INVALID_DEVICE;

	if(lock_mutex(&dev->controls.mutex)) {
		ret = C_SYNC_ERROR;
		goto done;
	}

	// Test if the driver supports the V4L2_CTRL_FLAG_NEXT_CTRL flag
#ifdef ENABLE_V4L2_ADVANCED_CONTROL_ENUMERATION
	v4l2_ctrl.id = 0 | V4L2_CTRL_FLAG_NEXT_CTRL;
	if(ioctl(v4l2_dev, VIDIOC_QUERYCTRL, &v4l2_ctrl) == 0) {
		// The driver supports the V4L2_CTRL_FLAG_NEXT_CTRL flag, so go ahead with
		// the advanced enumeration way.

		int r;
		v4l2_ctrl.id = 0;
		int current_ctrl = v4l2_ctrl.id;
		v4l2_ctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
		// Loop as long as ioctl does not return EINVAL
		while((r = ioctl(v4l2_dev, VIDIOC_QUERYCTRL, &v4l2_ctrl)), r ? errno != EINVAL : 1) {
#ifdef CONTROL_IO_ERROR_RETRIES
			if(r && (errno == EIO || errno == EPIPE || errno == ETIMEDOUT)) {
				// An I/O error occurred, so retry the query a few times.
				// This part is a little tricky. On the one hand, we want to retrieve the ID
				// of the next control in case the query succeeds or we give up on retrying.
				// On the other hand we want to retry the erroneous control instead of just
				// skipping to the next one, wo we needed to backup the ID of the failing
				// control first (above in current_ctrl).
				// Keep in mind that with the NEXT_CTRL flag VIDIO_QUERYCTRL returns the
				// first control with a *higher* ID than the specified one.
				int tries = CONTROL_IO_ERROR_RETRIES;
				v4l2_ctrl.id = current_ctrl | V4L2_CTRL_FLAG_NEXT_CTRL;
				while(tries-- &&
					  (r = ioctl(v4l2_dev, VIDIOC_QUERYCTRL, &v4l2_ctrl)) &&
					  (errno == EIO || errno == EPIPE || errno == ETIMEDOUT)) {
					v4l2_ctrl.id = current_ctrl | V4L2_CTRL_FLAG_NEXT_CTRL;
				}
			}
#endif
			// Prevent infinite loops for buggy NEXT_CTRL implementations
			if(r && v4l2_ctrl.id <= current_ctrl) {
				// If there was an error but the driver failed to provide us with the ID
				// of the next control, we have to manually increase the control ID,
				// otherwise we risk getting stuck querying the erroneous control.
				current_ctrl++;
				print_libwebcam_error(
						"Warning: The driver behind device %s has a slightly buggy implementation\n"
						"  of the V4L2_CTRL_FLAG_NEXT_CTRL flag. It does not return the next higher\n"
						"  control ID if a control query fails. A workaround has been enabled.",
						dev->v4l2_name);
				goto next_control;
			}
			else if(!r && v4l2_ctrl.id == current_ctrl) {
				// If there was no error but the driver did not increase the control ID
				// we simply cancel the enumeration.
				print_libwebcam_error(
						"Error: The driver %s behind device %s has a buggy\n"
						"  implementation of the V4L2_CTRL_FLAG_NEXT_CTRL flag. It does not raise an\n"
						"  error or return the next control. Canceling control enumeration.",
						dev->device.driver, dev->v4l2_name);
				goto done;
			}

			current_ctrl = v4l2_ctrl.id;

			// Skip failed and disabled controls
			if(r || v4l2_ctrl.flags & V4L2_CTRL_FLAG_DISABLED)
				goto next_control;

			Control *ctrl = create_v4l2_control(dev, &v4l2_ctrl, v4l2_dev, &ret);
			if(ctrl == NULL) {
				if(ret == C_PARSE_ERROR || ret == C_NOT_IMPLEMENTED) {
					print_libwebcam_error("Invalid or unsupported V4L2 control encountered: "
							"ctrl_id = 0x%08X, name = '%s'", v4l2_ctrl.id, v4l2_ctrl.name);
					ret = C_SUCCESS;
				}
				else {
					goto done;
				}
			}

next_control:
			v4l2_ctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
		}
	}
	else
#endif
	{
		// The driver does not support the V4L2_CTRL_FLAG_NEXT_CTRL flag, so we need
		// to fall back to the old way of enumerating controls, i.e. enumerating the
		// standard V4L2 controls first, followed by the driver's private controls.
		// It won't be possible to enumerate controls with non-contiguous IDs but in
		// this case the driver probably doesn't implement any.

		// Enumerate default V4L2 controls.
		// We use a separate variable instead of v4l2_ctrl.id for the loop counter because
		// some drivers (bttv) simply return a fake control with ID 0 when the device
		// doesn't support a control in the [V4L2_CID_BASE, V4L2_CID_LASTP1) interval,
		// thereby overwriting our loop variable and causing us to restart from 0.
		int current_ctrl;
		for(current_ctrl = V4L2_CID_BASE; current_ctrl < V4L2_CID_LASTP1; current_ctrl++) {
			v4l2_ctrl.id = current_ctrl;
#ifndef CONTROL_IO_ERROR_RETRIES
			if(ioctl(v4l2_dev, VIDIOC_QUERYCTRL, &v4l2_ctrl) ||
			   v4l2_ctrl.flags & V4L2_CTRL_FLAG_DISABLED)
				continue;
#else
			int r = 0, tries = 1 + CONTROL_IO_ERROR_RETRIES;
			while(tries-- &&
				  (r = ioctl(v4l2_dev, VIDIOC_QUERYCTRL, &v4l2_ctrl)) &&
				  (errno == EIO || errno == EPIPE || errno == ETIMEDOUT));
			if(r || v4l2_ctrl.flags & V4L2_CTRL_FLAG_DISABLED)
				continue;
#endif

			Control *ctrl = create_v4l2_control(dev, &v4l2_ctrl, v4l2_dev, &ret);
			if(ctrl == NULL) {
				if(ret == C_PARSE_ERROR || ret == C_NOT_IMPLEMENTED) {
					print_libwebcam_error("Invalid or unsupported V4L2 control encountered: "
							"ctrl_id = 0x%08X, name = '%s'", v4l2_ctrl.id, v4l2_ctrl.name);
					ret = C_SUCCESS;
					continue;
				}
				goto done;
			}
		}

		// Enumerate custom controls
		for(v4l2_ctrl.id = V4L2_CID_PRIVATE_BASE;; v4l2_ctrl.id++) {
#ifndef CONTROL_IO_ERROR_RETRIES
			if(ioctl(v4l2_dev, VIDIOC_QUERYCTRL, &v4l2_ctrl))
				break;
#else
			int r = 0, tries = 1 + CONTROL_IO_ERROR_RETRIES;
			while(tries-- &&
				  (r = ioctl(v4l2_dev, VIDIOC_QUERYCTRL, &v4l2_ctrl)) &&
				  (errno == EIO || errno == EPIPE || errno == ETIMEDOUT));
			if(r)
				break;
#endif
			if(v4l2_ctrl.flags & V4L2_CTRL_FLAG_DISABLED)
				continue;

			Control *ctrl = create_v4l2_control(dev, &v4l2_ctrl, v4l2_dev, &ret);
			if(ctrl == NULL) {
				if(ret == C_PARSE_ERROR || ret == C_NOT_IMPLEMENTED) {
					print_libwebcam_error("Invalid or unsupported custom V4L2 control encountered: "
							"ctrl_id = 0x%08X, name = '%s'", v4l2_ctrl.id, v4l2_ctrl.name);
					ret = C_SUCCESS;
					continue;
				}
				goto done;
			}
		}
	}

done:
	unlock_mutex(&dev->controls.mutex);
	if (!dev->fd) close(v4l2_dev);

	return ret;
}


/**
 * Retrieve device information for the given device.
 */
static CResult refresh_device_details (Device *dev)
{
	CResult ret = C_SUCCESS;
	int v4l2_dev;
	struct v4l2_capability v4l2_cap;

	// Open the corresponding V4L2 device
	if (dev->fd) v4l2_dev = dev->fd;
	else v4l2_dev = open_v4l2_device(dev->v4l2_name);
	if(!v4l2_dev)
		return C_INVALID_DEVICE;

	// Query the device
	if(!ioctl(v4l2_dev, VIDIOC_QUERYCAP, &v4l2_cap)) {
		if(v4l2_cap.card[0])
			dev->device.name = strdup((char *)v4l2_cap.card);
		else
			dev->device.name = dev->v4l2_name;
		dev->device.driver = strdup((char *)v4l2_cap.driver);
		if(v4l2_cap.bus_info[0])
			dev->device.location = strdup((char *)v4l2_cap.bus_info);
		else
			dev->device.location = dev->v4l2_name;
	}
	else {
		ret = C_V4L2_ERROR;
	}

	if (!dev->fd) close(v4l2_dev);

	return ret;
}


/**
 * Returns the length required to store all the (null-terminated) names of the given
 * device's controls in a buffer.
 *
 * Note: The control list should be locked before calling this function.
 */
static unsigned int get_control_dynamics_length(Device *device, unsigned int *names_length, unsigned int *choices_length)
{
	unsigned int names = 0, choices = 0;
	Control *elem = device->controls.first;
	while(elem) {
		// Add length of the control name
		if(elem->control.name)
			names += strlen(elem->control.name) + 1;

		// Add length of the control choice names
		if(elem->control.type == CC_TYPE_CHOICE) {
			int index;
			for(index = 0; index < elem->control.choices.count; index++) {
				choices += sizeof(CControlChoice);
				choices += strlen(elem->control.choices.list[index].name) + 1;
			}
		}

		elem = elem->next;
	}
	if(names_length)
		*names_length = names;
	if(choices_length)
		*choices_length = choices;
	return names + choices;
}


/**
 * Converts a V4L2 control ID to a libwebcam control ID.
 *
 * @param v4l2_id	V4L2 ID that should be converted.
 * @param dev		Pointer to the device to which the control belongs.
 */
static CControlId get_control_id_from_v4l2 (int v4l2_id, Device *dev)
{
	switch(v4l2_id) {
		// Basic V4L2 controls
		case V4L2_CID_BRIGHTNESS:			return CC_BRIGHTNESS;
		case V4L2_CID_CONTRAST:				return CC_CONTRAST;
		case V4L2_CID_SATURATION:			return CC_SATURATION;
		case V4L2_CID_HUE:					return CC_HUE;
		case V4L2_CID_AUDIO_VOLUME:			break;	// not supported by libwebcam
		case V4L2_CID_AUDIO_BALANCE:		break;	// not supported by libwebcam
		case V4L2_CID_AUDIO_BASS:			break;	// not supported by libwebcam
		case V4L2_CID_AUDIO_TREBLE:			break;	// not supported by libwebcam
		case V4L2_CID_AUDIO_MUTE:			break;	// not supported by libwebcam
		case V4L2_CID_AUDIO_LOUDNESS:		break;	// not supported by libwebcam
		case V4L2_CID_BLACK_LEVEL:			break;	// deprecated
		case V4L2_CID_AUTO_WHITE_BALANCE:	return CC_AUTO_WHITE_BALANCE_TEMPERATURE;
		case V4L2_CID_DO_WHITE_BALANCE:		break;	// not supported by libwebcam
		case V4L2_CID_RED_BALANCE:			break;	// not supported by libwebcam
		case V4L2_CID_BLUE_BALANCE:			break;	// not supported by libwebcam
		case V4L2_CID_GAMMA:				return CC_GAMMA;
		case V4L2_CID_EXPOSURE:				return CC_EXPOSURE_TIME_ABSOLUTE;
		case V4L2_CID_AUTOGAIN:				break;	// not supported by libwebcam
		case V4L2_CID_GAIN:					return CC_GAIN;
		case V4L2_CID_HFLIP:				break;	// not supported by libwebcam
		case V4L2_CID_VFLIP:				break;	// not supported by libwebcam
		case V4L2_CID_HCENTER:				break;	// deprecated
		case V4L2_CID_VCENTER:				break;	// deprecated
#ifdef V4L2_CID_POWER_LINE_FREQUENCY
		case V4L2_CID_POWER_LINE_FREQUENCY:		return CC_POWER_LINE_FREQUENCY;
#endif
#ifdef V4L2_CID_HUE_AUTO
		case V4L2_CID_HUE_AUTO:					return CC_AUTO_HUE;
#endif
#ifdef V4L2_CID_WHITE_BALANCE_TEMPERATURE
		case V4L2_CID_WHITE_BALANCE_TEMPERATURE:	return CC_WHITE_BALANCE_TEMPERATURE;
#endif
#ifdef V4L2_CID_SHARPNESS
		case V4L2_CID_SHARPNESS:				return CC_SHARPNESS;
#endif
#ifdef V4L2_CID_BACKLIGHT_COMPENSATION
		case V4L2_CID_BACKLIGHT_COMPENSATION:	return CC_BACKLIGHT_COMPENSATION;
#endif
#ifdef V4L2_CID_CHROMA_AGC
		case V4L2_CID_CHROMA_AGC:				break;	// not supported by libwebcam
#endif
#ifdef V4L2_CID_COLOR_KILLER
		case V4L2_CID_COLOR_KILLER:				break;	// not supported by libwebcam
#endif
#ifdef V4L2_CID_COLORFX
		case V4L2_CID_COLORFX:					break;	// not supported by libwebcam
#endif

		// Camera/UVC driver controls
#ifdef V4L2_CID_EXPOSURE_AUTO
		case V4L2_CID_EXPOSURE_AUTO:			return CC_AUTO_EXPOSURE_MODE;
#endif
#ifdef V4L2_CID_EXPOSURE_ABSOLUTE
		case V4L2_CID_EXPOSURE_ABSOLUTE:		return CC_EXPOSURE_TIME_ABSOLUTE;
#endif
#ifdef V4L2_CID_EXPOSURE_AUTO_PRIORITY
		case V4L2_CID_EXPOSURE_AUTO_PRIORITY:	return CC_AUTO_EXPOSURE_PRIORITY;
#endif
#ifdef V4L2_CID_PAN_RELATIVE
		case V4L2_CID_PAN_RELATIVE:				return CC_PAN_RELATIVE;
#endif
#ifdef V4L2_CID_TILT_RELATIVE
		case V4L2_CID_TILT_RELATIVE:			return CC_TILT_RELATIVE;
#endif
#ifdef V4L2_CID_PAN_RESET
		case V4L2_CID_PAN_RESET:				return CC_PAN_RESET;
#endif
#ifdef V4L2_CID_TILT_RESET
		case V4L2_CID_TILT_RESET:				return CC_TILT_RESET;
#endif
#ifdef V4L2_CID_PAN_ABSOLUTE
		case V4L2_CID_PAN_ABSOLUTE:				return CC_PAN_ABSOLUTE;
#endif
#ifdef V4L2_CID_TILT_ABSOLUTE
		case V4L2_CID_TILT_ABSOLUTE:			return CC_TILT_ABSOLUTE;
#endif
#ifdef V4L2_CID_FOCUS_ABSOLUTE
		case V4L2_CID_FOCUS_ABSOLUTE:			return CC_FOCUS_ABSOLUTE;
#endif
#ifdef V4L2_CID_FOCUS_RELATIVE
		case V4L2_CID_FOCUS_RELATIVE:			return CC_FOCUS_RELATIVE;
#endif
#ifdef V4L2_CID_FOCUS_AUTO
		case V4L2_CID_FOCUS_AUTO:				return CC_AUTO_FOCUS;
#endif
#ifdef V4L2_CID_ZOOM_ABSOLUTE
		case V4L2_CID_ZOOM_ABSOLUTE:			return CC_ZOOM_ABSOLUTE;
#endif
#ifdef V4L2_CID_ZOOM_RELATIVE
		case V4L2_CID_ZOOM_RELATIVE:			return CC_ZOOM_RELATIVE;
#endif
#ifdef V4L2_CID_ZOOM_CONTINUOUS
		case V4L2_CID_ZOOM_CONTINUOUS:			break;	// not supported by libwebcam
#endif
#ifdef V4L2_CID_PRIVACY
		case V4L2_CID_PRIVACY:					return CC_PRIVACY;
#endif
#ifdef V4L2_CID_LED1_MODE
		case V4L2_CID_LED1_MODE:				return CC_LOGITECH_LED1_MODE;
#endif
#ifdef V4L2_CID_LED1_FREQUENCY
		case V4L2_CID_LED1_FREQUENCY:			return CC_LOGITECH_LED1_FREQUENCY;
#endif
#ifdef V4L2_CID_DISABLE_PROCESSING
		case V4L2_CID_DISABLE_PROCESSING:		return CC_LOGITECH_DISABLE_PROCESSING;
#endif
#ifdef V4L2_CID_RAW_BITS_PER_PIXEL
		case V4L2_CID_RAW_BITS_PER_PIXEL:		return CC_LOGITECH_RAW_BITS_PER_PIXEL;
#endif
	};

#ifdef USE_UVCVIDEO
	// Controls contained only in older UVC drivers
	//printf("*** 0x%08X 0x%08X\n", v4l2_id, UVC_CID_EXPOSURE_AUTO);
	if(strcmp(dev->device.driver, "uvcvideo") == 0) {
		switch(v4l2_id) {
#ifdef UVC_CID_BACKLIGHT_COMPENSATION
			case UVC_CID_BACKLIGHT_COMPENSATION:			return CC_BACKLIGHT_COMPENSATION;
#endif
#ifdef UVC_CID_POWER_LINE_FREQUENCY
			case UVC_CID_POWER_LINE_FREQUENCY:				return CC_POWER_LINE_FREQUENCY;
#endif
#ifdef UVC_CID_SHARPNESS
			case UVC_CID_SHARPNESS:							return CC_SHARPNESS;
#endif
#ifdef UVC_CID_HUE_AUTO
			case UVC_CID_HUE_AUTO:							return CC_AUTO_HUE;
#endif
#ifdef UVC_CID_FOCUS_AUTO
			case UVC_CID_FOCUS_AUTO:						return CC_AUTO_FOCUS;
#endif
#ifdef UVC_CID_FOCUS_ABSOLUTE
			case UVC_CID_FOCUS_ABSOLUTE:					return CC_FOCUS_ABSOLUTE;
#endif
#ifdef UVC_CID_FOCUS_RELATIVE
			case UVC_CID_FOCUS_RELATIVE:					return CC_FOCUS_RELATIVE;
#endif
#ifdef UVC_CID_PAN_RELATIVE
			case UVC_CID_PAN_RELATIVE:						return CC_PAN_RELATIVE;
#endif
#ifdef UVC_CID_TILT_RELATIVE
			case UVC_CID_TILT_RELATIVE:						return CC_TILT_RELATIVE;
#endif
#ifdef UVC_CID_PANTILT_RESET
			case UVC_CID_PANTILT_RESET:						return CC_LOGITECH_PANTILT_RESET;
#endif
#ifdef UVC_CID_EXPOSURE_AUTO
			case UVC_CID_EXPOSURE_AUTO:						return CC_AUTO_EXPOSURE_MODE;
#endif
#ifdef UVC_CID_EXPOSURE_ABSOLUTE
			case UVC_CID_EXPOSURE_ABSOLUTE:					return CC_EXPOSURE_TIME_ABSOLUTE;
#endif
#ifdef UVC_CID_EXPOSURE_AUTO_PRIORITY
			case UVC_CID_EXPOSURE_AUTO_PRIORITY:			return CC_AUTO_EXPOSURE_PRIORITY;
#endif
#ifdef UVC_CID_WHITE_BALANCE_TEMPERATURE_AUTO
			case UVC_CID_WHITE_BALANCE_TEMPERATURE_AUTO:	return CC_AUTO_WHITE_BALANCE_TEMPERATURE;
#endif
#ifdef UVC_CID_WHITE_BALANCE_TEMPERATURE
			case UVC_CID_WHITE_BALANCE_TEMPERATURE:			return CC_WHITE_BALANCE_TEMPERATURE;
#endif
#ifdef V4L2_CID_PANTILT_RELATIVE
			case V4L2_CID_PANTILT_RELATIVE:					return CC_LOGITECH_PANTILT_RELATIVE;
#endif
		}
	}
#endif

	// Unknown V4L2 controls
	// Note that there is a margin of 256 control values for controls that are added
	// after libwebcam compilation time.
	if(V4L2_CTRL_ID2CLASS(v4l2_id) == V4L2_CTRL_CLASS_USER) {
		// Unknown user control
		print_libwebcam_error(
			"Unknown V4L2 user control ID encountered: 0x%08X (V4L2_CID_USER_BASE + %d)",
			v4l2_id, v4l2_id - V4L2_CID_USER_BASE
		);
		return CC_V4L2_BASE + (v4l2_id - V4L2_CID_USER_BASE);
	}
	else if(V4L2_CTRL_ID2CLASS(v4l2_id) == V4L2_CTRL_CLASS_MPEG) {
		// Unknown MPEG control
		print_libwebcam_error(
			"Unknown V4L2 MPEG control ID encountered: 0x%08X (V4L2_CID_MPEG_BASE + %d)",
			v4l2_id, v4l2_id - V4L2_CID_MPEG_BASE
		);
		return CC_V4L2_MPEG_BASE + (v4l2_id - V4L2_CID_MPEG_BASE);
	}
	else if(V4L2_CTRL_ID2CLASS(v4l2_id) == V4L2_CTRL_CLASS_CAMERA) {
		// Unknown camera class (UVC) control
		print_libwebcam_error(
			"Unknown V4L2 camera class (UVC) control ID encountered: 0x%08X (V4L2_CID_CAMERA_CLASS_BASE + %d)",
			v4l2_id, v4l2_id - V4L2_CID_CAMERA_CLASS_BASE
		);
		return CC_V4L2_CAMERA_CLASS_BASE + (v4l2_id - V4L2_CID_CAMERA_CLASS_BASE);
	}
	else if(v4l2_id >= V4L2_CID_PRIVATE_BASE) {
		// Unknown private control
		print_libwebcam_error(
			"Unknown V4L2 private control ID encountered: 0x%08X (V4L2_CID_PRIVATE_BASE + %d)",
			v4l2_id, v4l2_id - V4L2_CID_PRIVATE_BASE
		);
		return CC_V4L2_CUSTOM_BASE + (v4l2_id - V4L2_CID_PRIVATE_BASE);
	}

	print_libwebcam_error("Unknown V4L2 control ID encountered: 0x%08X", v4l2_id);
	return 0;
}


/*
 * Device management
 */

/**
 * Allocate a new device with the given name and add it to the global device list.
 */
static Device *create_device (char *name)
{
	Device *dev = NULL;

	dev = (Device *)malloc(sizeof(*dev));
	if(dev) {
		memset(dev, 0, sizeof(*dev));
		strcpy(dev->v4l2_name, name);
		dev->device.shortName = strdup(name);
		dev->valid = 1;

		// Add the new device to the global device list
		dev->next = device_list.first;
		device_list.first = dev;
		device_list.count++;
	}

	return dev;
}


/**
 * Free up the given device.
 *
 * Note that this function does not remove the device from the global device list.
 */
static void delete_device (Device *dev)
{
	// Free all the handles that point to this device
	lock_mutex(&handle_list.mutex);
	if(dev->handles > 0) {
		CHandle hDevice;
		for(hDevice = 1; hDevice < MAX_HANDLES; hDevice++) {
			if(HANDLE_OPEN(hDevice) && GET_HANDLE(hDevice).device == dev) {
				// Remove the device link from the handle but leave the handle around
				dev->handles--;
				GET_HANDLE(hDevice).device = NULL;
			}
		}
	}
	unlock_mutex(&handle_list.mutex);

	// Free all controls of this device
	clear_control_list(dev);

	if(dev->device.shortName)
		free(dev->device.shortName);
	if(dev->device.name && dev->device.name != dev->v4l2_name)
		free(dev->device.name);
	if(dev->device.driver)
		free(dev->device.driver);
	if(dev->device.location && dev->device.location != dev->v4l2_name)
		free(dev->device.location);
	free(dev);
}


/**
 * Mark all entries in the device list as invalid. This allows the cleanup_device_list()
 * function to be used to clear the entire device list.
 *
 * Note: The device list should be locked before calling this function.
 */
static void invalidate_device_list (void)
{
	Device *elem = device_list.first;
	while(elem) {
		elem->valid = 0;
		elem = elem->next;
	}
}


/**
 * Remove all entries marked as invalid from the device list.
 *
 * Note: The device list should be locked before calling this function.
 */
static void cleanup_device_list (void)
{
	Device *elem = device_list.first;
	Device *prev = NULL, *next;
	while(elem) {
		next = elem->next;
		if(!elem->valid) {
			if(prev)
				prev->next = next;
			else
				device_list.first = next;
			delete_device(elem);
			device_list.count--;
		}
		else {
			prev = elem;
		}
		elem = next;
	}
}


/**
 * Searches the device list for the device with the given name.
 */
static Device *find_device_by_name (const char *name)
{
	Device *elem = device_list.first;
	while(elem) {
		if(strcmp(name, elem->v4l2_name) == 0)
			return elem;
		elem = elem->next;
	}
	return NULL;
}


/**
 * Returns the length required to store all the (null-terminated) strings of the
 * given device in a buffer.
 */
static int get_device_dynamics_length (CDevice *device)
{
	return strlen(device->shortName)	+ 1 +
		   strlen(device->name)			+ 1 +
		   strlen(device->driver)		+ 1 +
		   strlen(device->location)		+ 1;
}


/**
 * Returns the length required to store all the (null-terminated) strings of the
 * current devices in a buffer.
 *
 * Note: The device list should be locked before calling this function.
 */
static int get_devices_dynamics_length (void)
{
	int size = 0;
	Device *elem = device_list.first;
	while(elem) {
		size += get_device_dynamics_length(&elem->device);
		elem = elem->next;
	}
	return size;
}


/**
 * Synchronizes the device list with the information available in sysfs.
 */
static CResult refresh_device_list (void)
{
	CResult ret = C_SUCCESS;
	DIR *v4l_dir = NULL;
	struct dirent *dir_entry;

	if(lock_mutex(&device_list.mutex))
		return C_SYNC_ERROR;

	// Invalidate all list entries
	invalidate_device_list();

	// Go through all devices in sysfs and validate the list entries that have
	// correspondences in sysfs.
	v4l_dir = opendir("/sys/class/video4linux");
	if(v4l_dir) {
		while((dir_entry = readdir(v4l_dir))) {
			// Ignore non-video devices
			if(strstr(dir_entry->d_name, "video") != dir_entry->d_name)
				continue;

			Device *dev = find_device_by_name(dir_entry->d_name);
			if(dev) {
				dev->valid = 1;
			}
			else {
				dev = create_device(dir_entry->d_name);
				if(dev == NULL) {
					ret = C_NO_MEMORY;
					goto done;
				}

				// Read detail information about the device
				ret = refresh_device_details(dev);
				if(ret) {
					// Invalidate the device immediately, so it gets deleted by the call
					// to cleanup_device_list() below.
					dev->valid = 0;

					// If there was a V4L2 error reset the error code, so that device enumeration
					// continues. This is necessary because V4L1 devices will let
					// refresh_device_details fail as they don't understand VIDIOC_QUERYCAP.
					if(ret == C_V4L2_ERROR) {
						print_libwebcam_error(
								"Warning: The driver behind device %s does not seem to support V4L2.",
								dev->v4l2_name);
						ret = C_SUCCESS;
						continue;
					}
					break;
				}
				get_device_usb_info(dev, &dev->device.usb);

				// Create the control list for the given device
				ret = refresh_control_list(dev);
				if(ret) goto done;
			}
		}
	}

	// Clean out all invalid device list entries
	cleanup_device_list();

done:
	if(v4l_dir)
		closedir(v4l_dir);
	unlock_mutex(&device_list.mutex);
	if(ret)
		print_libwebcam_c_error(ret, "Unable to refresh device list.");
	return ret;
}


/**
 * Open the V4L2 device node with the given name.
 *
 * @param device_name	A device name as accepted by c_open_device()
 *
 * @return
 * 		- 0 if the device could not be opened
 * 		- a device handle > 0 on success
 */
int open_v4l2_device(char *device_name)
{
	int v4l2_dev;
	char *dev_node;

	if(device_name == NULL)
		return C_INVALID_ARG;

	dev_node = (char *)malloc(5 + strlen(device_name) + 1);
	if(!dev_node)
		return 0;
	sprintf(dev_node, "/dev/%s", device_name);
	v4l2_dev = open(dev_node, O_RDWR);
	free(dev_node);
	return v4l2_dev;
}


/**
 * Retrieves the value of a given V4L2 control.
 */
static CResult read_v4l2_control(Device *device, Control *control, CControlValue *value, CHandle hDevice)
{
	CResult ret = C_SUCCESS;

	if(device == NULL || control == NULL || value == NULL)
		return C_INVALID_ARG;

	int v4l2_dev = device->fd; //open_v4l2_device(device->v4l2_name);
	if(!v4l2_dev)
		return C_INVALID_DEVICE;

#ifdef ENABLE_RAW_CONTROLS
	if(control->control.type == CC_TYPE_RAW) {
		unsigned int ctrl_size = control->control.max.raw.size;

		if(value->raw.data == NULL)
			return C_INVALID_ARG;
		if(value->raw.size < ctrl_size)
			return C_INVALID_ARG;

		struct v4l2_ext_control v4l2_ext_ctrl = {
			.id		= control->v4l2_control,
			.size	= ctrl_size,
		};
		v4l2_ext_ctrl.string = value->raw.data;
		struct v4l2_ext_controls v4l2_ext_ctrls = {
			.ctrl_class	= V4L2_CTRL_CLASS_USER,
			.count		= 1,
			.controls	= &v4l2_ext_ctrl
		};
		if(ioctl(v4l2_dev, VIDIOC_G_EXT_CTRLS, &v4l2_ext_ctrls)) {
			ret = C_V4L2_ERROR;
			set_last_error(hDevice, errno);
			goto done;
		}

		// Set the raw data size to the size of the control
		value->raw.size = ctrl_size;
	}
	else
#endif
	{
		struct v4l2_control v4l2_ctrl = { .id = control->v4l2_control };
		if(ioctl(v4l2_dev, VIDIOC_G_CTRL, &v4l2_ctrl)) {
			ret = C_V4L2_ERROR;
			set_last_error(hDevice, errno);
			goto done;
		}
		value->value	= v4l2_ctrl.value;
	}

	value->type		= control->control.type;

done:
	//close(v4l2_dev);
	return ret;
}


/**
 * Changes the value of a given V4L2 control.
 */
static CResult write_v4l2_control(Device *device, Control *control, const CControlValue *value, CHandle hDevice)
{
	CResult ret = C_SUCCESS;

	if(device == NULL || control == NULL || value == NULL)
		return C_INVALID_ARG;

	int v4l2_dev = device->fd; //open_v4l2_device(device->v4l2_name);
	if(!v4l2_dev)
		return C_INVALID_DEVICE;

#ifdef ENABLE_RAW_CONTROLS
	if(control->control.type == CC_TYPE_RAW) {
		unsigned int ctrl_size = control->control.max.raw.size;

		if(value->raw.data == NULL)
			return C_INVALID_ARG;
		if(value->raw.size < ctrl_size)
			return C_INVALID_ARG;

		struct v4l2_ext_control v4l2_ext_ctrl = {
			.id		= control->v4l2_control,
			.size	= ctrl_size,
		};
		v4l2_ext_ctrl.string = value->raw.data;
		struct v4l2_ext_controls v4l2_ext_ctrls = {
			.ctrl_class	= V4L2_CTRL_CLASS_USER,
			.count		= 1,
			.controls	= &v4l2_ext_ctrl
		};
		if(ioctl(v4l2_dev, VIDIOC_S_EXT_CTRLS, &v4l2_ext_ctrls)) {
			ret = C_V4L2_ERROR;
			set_last_error(hDevice, errno);
			goto done;
		}
	}
	else
#endif
	{
		struct v4l2_control v4l2_ctrl = {
			.id		= control->v4l2_control,
			.value	= value->value
		};
		if(ioctl(v4l2_dev, VIDIOC_S_CTRL, &v4l2_ctrl)) {
			ret = C_V4L2_ERROR;
			set_last_error(hDevice, errno);
		}
	}

#ifdef ENABLE_RAW_CONTROLS
done:
#endif
	//close(v4l2_dev);
	return ret;
}


/**
 * Reads the USB information for the given device into the given #CUSBInfo structure.
 */
static CResult get_device_usb_info (Device *device, CUSBInfo *usbinfo)
{
	if(device == NULL || usbinfo == NULL)
		return C_INVALID_ARG;

	// File names in the /sys/class/video4linux/video?/device directory and
	// corresponding pointers in the CUSBInfo structure.
	char *files[] = {
		"idVendor",
		"idProduct",
		"bcdDevice"
	};
	unsigned short *fields[] = {
		&usbinfo->vendor,
		&usbinfo->product,
		&usbinfo->release
	};

	// Read USB information
	int i;
	for(i = 0; i < 3; i++) {
		char *filename = NULL;
		if(asprintf(&filename, "/sys/class/video4linux/%s/device/%s",
					device->v4l2_name, files[i]) < 0)
			return C_NO_MEMORY;

		FILE *input = fopen(filename, "r");
		if(input) {
			if(fscanf(input, "%hx", fields[i]) != 1)
				*fields[i] = 0;
			fclose(input);
		}

		free(filename);
	}

	return C_SUCCESS;
}


/*
 * Utility functions
 */

/**
 * Converts a FourCC code into a MIME type string.
 */
static CResult get_mimetype_from_fourcc(char **mimetype, unsigned int fourcc)
{
	if(mimetype == NULL)
		return C_INVALID_ARG;

	char *result;
	switch(fourcc) {
		case MAKE_FOURCC('Y','U','Y','2'):
		case MAKE_FOURCC('Y','U','Y','V'):
			result = "video/x-raw-yuv";
			break;
		case MAKE_FOURCC('M','J','P','G'):
			result = "image/jpeg";
			break;
		default:
			return C_NOT_FOUND;
	};

	*mimetype = strdup(result);
	return C_SUCCESS;
}


/*
 * Handle management
 */

/**
 * Creates a new device handle for the given device.
 *
 * @return
 * 		- 0 if there are no free handles left.
 * 		- A libwebcam handle > 0 on success.
 */
static CHandle create_handle(Device *device)
{
	CHandle handle = handle_list.first_free;
	int first_free, next_free;
	if(device == NULL)
		return 0;
	if(handle == 0) {
		print_libwebcam_error("No free device handles left. Unable to create handle "
				"for device '%s'.", device->v4l2_name);
		return 0;
	}

	if(lock_mutex(&handle_list.mutex))
		return C_SYNC_ERROR;

	GET_HANDLE(handle).device = device;
	GET_HANDLE(handle).open = 1;
	device->handles++;

	// Look for the next free handle index
	first_free = handle_list.first_free;
	next_free = first_free;
	do {
		next_free = (next_free + 1) % MAX_HANDLES;
		if(next_free == 0) next_free = 1;

		if(!HANDLE_OPEN(next_free)) {
			handle_list.first_free = next_free;
			break;
		}
	}
	while(next_free != first_free);
	if(next_free == first_free)
		handle_list.first_free = 0;		// No free handles left

	unlock_mutex(&handle_list.mutex);
	return handle;
}


/**
 * Closes the given handle.
 */
static void close_handle(CHandle hDevice)
{
	if(!HANDLE_OPEN(hDevice))
		return;

	// If the handle is open, close it. If it is also valid, remove the device reference.
	if(HANDLE_VALID(hDevice)) {
		lock_mutex(&handle_list.mutex);
		GET_HANDLE(hDevice).device->handles--;
		// Closes device when reference count reaches 0
		if (GET_HANDLE(hDevice).device->handles== 0) {
			close (GET_HANDLE(hDevice).device->fd);
			GET_HANDLE(hDevice).device->fd= 0;
		}
		GET_HANDLE(hDevice).device = NULL;
		GET_HANDLE(hDevice).open = 0;
		unlock_mutex(&handle_list.mutex);
	}
	else {
		GET_HANDLE(hDevice).open = 0;
	}
	GET_HANDLE(hDevice).last_system_error = 0;
}


/**
 * Sets the last system error for the given handle.
 */
static void set_last_error(CHandle hDevice, int error)
{
	if(HANDLE_OPEN(hDevice))
		GET_HANDLE(hDevice).last_system_error = error;
}



/*
 * Initialization and cleanup
 */

/**
 * Initializes libwebcam.
 * This method must be called prior to using most of the other methods.
 * To release resources allocated during initialization, users should make a call
 * to c_cleanup() when the library is no longer used.
 */
CResult c_init(void)
{
	CResult ret = C_SUCCESS;

	// Don't reinitialize
	if(initialized)
		return C_SUCCESS;

	// Initialize the handle list
	memset(&handle_list, 0, sizeof(handle_list));
	handle_list.first_free = 1;
	if(pthread_mutex_init(&handle_list.mutex, NULL))
		return C_INIT_ERROR;

	// Initialize the device list
	device_list.first = NULL;
	if(pthread_mutex_init(&device_list.mutex, NULL))
		return C_INIT_ERROR;
	device_list.count = 0;
	ret = refresh_device_list();

	if(ret == C_SUCCESS)
		initialized = 1;
	return ret;
}


/**
 * Clean up resources.
 * This method should be called when the library is no longer used.
 */
void c_cleanup(void)
{
	if(!initialized)
		return;
	initialized = 0;

	// Clear the device list
	lock_mutex(&device_list.mutex);
	invalidate_device_list();
	cleanup_device_list();
	unlock_mutex(&device_list.mutex);

	pthread_mutex_destroy(&device_list.mutex);
	pthread_mutex_destroy(&handle_list.mutex);
}


/**
 * Make sure the library resources are cleaned up when the library is unloaded.
 */
static void __attribute__ ((constructor)) c_unload(void)
{
	c_cleanup();
}
