/**
* \file
* Webcam library implementation.
*
* \ingroup libwebcam
*/
/*
* Copyright (c) 2006-2010 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 .
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "webcam.h"
#include "libwebcam.h"
#include "compat.h"
#ifdef USE_LOGITECH_DYNCTRL
#include
#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 if(strstr(device_name, "subdev") == 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(¤t->shortName, elem->device.shortName, devices, &dynamics_offset);
copy_string_to_buffer(¤t->name, elem->device.name, devices, &dynamics_offset);
copy_string_to_buffer(¤t->driver, elem->device.driver, devices, &dynamics_offset);
copy_string_to_buffer(¤t->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(¤t->name, elem->format.name, formats, &dynamics_offset);
if(elem->format.mimeType)
copy_string_to_buffer(¤t->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;
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;
}
/**
* refreshes the values of all device controls.
*
* @param hDevice a device handle obtained from c_open_device()
* @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
*/
static CResult refresh_control_values(CHandle hDevice)
{
// 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;
int ret = C_SUCCESS;
//update control values
Control *current = device->controls.first;
while(current)
{
ret = read_v4l2_control(device, current, ¤t->control.value, hDevice);
if(ret != C_SUCCESS)
print_libwebcam_error("Could not read control: 0x%08x.\n", current->v4l2_control);
current = current->next;
}
return C_SUCCESS;
}
/**
* set special auto controls (higher id) before setting manual counterparts.
*
* @param hDevice a device handle obtained from c_open_device()
* @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
*/
static CResult set_special_auto_controls(CHandle hDevice)
{
// 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;
int ret = C_SUCCESS;
//update control values
Control *current = device->controls.first;
while(current)
{
if( (current->v4l2_control == V4L2_CID_FOCUS_AUTO) ||
(current->v4l2_control == V4L2_CID_HUE_AUTO) )
{
if(current->control.flags & CC_NEED_SET)
{
ret = write_v4l2_control(device, current, ¤t->control.value, hDevice);
if(ret != C_SUCCESS)
{
print_libwebcam_error("Could not set control: 0x%08x.\n", current->v4l2_control);
// update with the real value
read_v4l2_control(device, current, ¤t->control.value, hDevice);
}
current->control.flags &= ~(CC_NEED_SET); //reset flag
}
}
current = current->next;
}
return C_SUCCESS;
}
/**
* set the values of all device controls.
*
* @param hDevice a device handle obtained from c_open_device()
* @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
*/
static CResult set_control_values(CHandle hDevice)
{
// 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;
//make sure special auto controls have already been set
int ret = set_special_auto_controls(hDevice);
//update all other control values
Control *current = device->controls.first;
while(current)
{
if(current->control.flags & CC_NEED_SET)
{
ret = write_v4l2_control(device, current, ¤t->control.value, hDevice);
if(ret != C_SUCCESS)
{
print_libwebcam_error("Could not set control: 0x%08x.\n", current->v4l2_control);
// update with the real value
read_v4l2_control(device, current, ¤t->control.value, hDevice);
}
current->control.flags &= ~(CC_NEED_SET); //reset flag
}
current = current->next;
}
return C_SUCCESS;
}
/**
* Looks up the control with the given V4L2 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_v4l2_id (Device *dev, int id)
{
Control *elem = dev->controls.first;
while(elem) {
if(elem->v4l2_control == id)
break;
elem = elem->next;
}
return elem;
}
/**
* Stores the values of all device controls into a file.
*
* @param hDevice a device handle obtained from c_open_device()
* @param filename file name for storing the controls data
* @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
*/
CResult c_save_controls (CHandle hDevice, const char *filename)
{
// 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(lock_mutex(&device->controls.mutex))
return C_SYNC_ERROR;
//printf("%i device controls\n",device->controls.count);
FILE *fp = fopen(filename, "w");
if( fp == NULL )
{
print_libwebcam_error("Could not open control data file for write: %s.\n", filename);
return (-1);
}
else
{
refresh_control_values(hDevice);
//write header
fprintf(fp, "#V4L2/CTRL/0.0.2\n");
fprintf(fp, "APP{\"libwebcam\"}\n");
//write control data
fprintf(fp, "# control data\n");
Control *current = device->controls.first;
while(current)
{
//printf("printing control id: 0x%08x\n", current->v4l2_control);
if(!(current->control.flags & (CC_CAN_READ | CC_CAN_WRITE)))
{
current = current->next;
printf("jumping\n");
continue;
}
fprintf(fp, "#%s\n", current->control.name);
switch(current->control.type)
{
#ifdef ENABLE_RAW_CONTROLS
case CC_TYPE_RAW :
fprintf(fp, "ID{0x%08x};CHK{%i:%i:1:0}=STR{\"%s\"}\n",
current->v4l2_control,
current->control.min.raw.size,
current->control.min.raw.size,
(char *) current->control.value.raw.data);
break;
#endif
case CC_TYPE_CHOICE :
//since we don't have real v4l2 maximum, minimum, step
//use 1 for step, 0 for minimum and calculate maximum from choices.count
//NOTE: although this should be valid for most cases, it's not necessarly true.
fprintf(fp, "ID{0x%08x};CHK{0:%i:1:%i}=VAL{%i}\n",
current->v4l2_control,
current->control.choices.count - 1,
current->control.def.value,
current->control.value.value);
break;
default :
fprintf(fp, "ID{0x%08x};CHK{%i:%i:%i:%i}=VAL{%i}\n",
current->v4l2_control,
current->control.min.value,
current->control.max.value,
current->control.step.value,
current->control.def.value,
current->control.value.value);
break;
}
current = current->next;
}
}
fclose(fp);
unlock_mutex(&device->controls.mutex);
return C_SUCCESS;
}
/**
* Loads the values of all device controls from a file.
*
* @param hDevice a device handle obtained from c_open_device()
* @param filename file name for reading the controls data
* @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_load_controls (CHandle hDevice, const char *filename)
{
// 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(lock_mutex(&device->controls.mutex))
return C_SYNC_ERROR;
FILE *fp = fopen(filename, "r");
if( fp == NULL )
{
print_libwebcam_error("Could not open control data file for read: %s.\n", filename);
return (-1);
}
else
{
char line[200];
if(fgets(line, sizeof(line), fp) != NULL)
{
int major,minor,rev;
if(sscanf(line,"#V4L2/CTRL/%i.%i.%i", &major, &minor, &rev) == 3)
{
//check standard version if needed
}
else
{
print_libwebcam_error("no valid control file header found\n");
goto finish;
}
}
else
{
print_libwebcam_error("no valid control file header found\n");
goto finish;
}
while (fgets(line, sizeof(line), fp) != NULL)
{
int id = 0;
int min = 0, max = 0, step = 0, def = 0;
int32_t val = 0;
//int64_t val64 = 0;
if ((line[0]!='#') && (line[0]!='\n'))
{
if(sscanf(line,"ID{0x%08x};CHK{%i:%i:%i:%i}=VAL{%i}",
&id, &min, &max, &step, &def, &val) == 6)
{
Control *current = find_control_by_v4l2_id(device, id);
if(current)
{
//check values
if((current->control.type != CC_TYPE_CHOICE &&
(current->control.min.value == min &&
current->control.max.value == max &&
current->control.step.value == step &&
current->control.def.value == def)) ||
(current->control.type == CC_TYPE_CHOICE &&
current->control.def.value == def))
{
current->control.value.value = val;
current->control.flags |= CC_NEED_SET; //set flag
//printf("setting 0x%08x to %i\n", id, val);
}
else
{
print_libwebcam_error("control (0x%08x) - doesn't match hardware\n", id);
}
}
}
else if(sscanf(line,"ID{0x%08x};CHK{0:0:0:0}=VAL64{",
&id) == 1)
{
print_libwebcam_error("control (0x%08x) - 64 bit controls not supported\n", id);
}
else if(sscanf(line,"ID{0x%08x};CHK{%i:%i:%i:0}=STR{\"%*s\"}",
&id, &min, &max, &step) == 5)
{
#ifdef ENABLE_RAW_CONTROLS
Control *current = find_control_by_v4l2_id(device, id);
if(current)
{
//check values
if(current->control.min.raw.size == min &&
current->control.max.raw.size == max &&
1 == step)
{
char str[max+1];
sscanf(line, "ID{0x%*x};CHK{%*i:%*i:%*i:0}==STR{\"%s\"}", str);
if(strlen(str) > max) //FIXME: should also check (minimum +N*step)
{
print_libwebcam_error("string bigger than maximum buffer size");
}
else
{
strcpy(current->control.value.raw.data, str);
current->control.flags |= CC_NEED_SET; //set flag
}
}
}
#endif
}
//else printf("skip line\n");
}
}
set_control_values(hDevice);
}
finish:
fclose(fp);
unlock_mutex(&device->controls.mutex);
return C_SUCCESS;
}
/*
* 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 = 0;
ctrl->control.choices.list = NULL;
// 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 = {0};
v4l2_menu.id = v4l2_ctrl->id;
for(v4l2_menu.index = v4l2_ctrl->minimum; v4l2_menu.index <= v4l2_ctrl->maximum; v4l2_menu.index++) {
if(ioctl(v4l2_dev, VIDIOC_QUERYMENU, &v4l2_menu) < 0)
continue;
ctrl->control.choices.count++;
if(!ctrl->control.choices.list)
ctrl->control.choices.list = (CControlChoice *)malloc(sizeof(CControlChoice));
else
ctrl->control.choices.list = (CControlChoice *)realloc(ctrl->control.choices.list, ctrl->control.choices.count * sizeof(CControlChoice));
ctrl->control.choices.list[ctrl->control.choices.count-1].index = v4l2_menu.index;
ctrl->control.choices.list[ctrl->control.choices.count-1].id = v4l2_menu.id;
if(strlen((char *)v4l2_menu.name))
memcpy(ctrl->control.choices.list[ctrl->control.choices.count-1].name, v4l2_menu.name, V4L2_MENU_CTRL_MAX_NAME_SIZE);
else
snprintf(ctrl->control.choices.list[ctrl->control.choices.count-1].name, V4L2_MENU_CTRL_MAX_NAME_SIZE, "%d", v4l2_menu.index);
}
if(ret != C_SUCCESS) {
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: type = CC_TYPE_BUTTON; break;
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;
}
#ifdef ENABLE_RAW_CONTROLS
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.def.raw.size = v4l2_ctrl->maximum;
//allocate data buffer
ctrl->control.value.raw.data = calloc(ctrl->control.def.raw.size, sizeof(char));
}
#endif
else {
ctrl->control.min.value = v4l2_ctrl->minimum;
ctrl->control.max.value = v4l2_ctrl->maximum;
ctrl->control.step.value = v4l2_ctrl->step;
}
ctrl->next = NULL;
// Add the new control to the end of the control list of the given device
if(device->controls.last)
{
device->controls.last->next = ctrl;
device->controls.last = device->controls.last->next;
}
else
{
// is the first control to be added
device->controls.first = ctrl;
device->controls.last = device->controls.first;
}
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);
}
#ifdef ENABLE_RAW_CONTROLS
if(ctrl->control.type == CC_TYPE_RAW)
{
if(ctrl->control.value.raw.data)
free(ctrl->control.value.raw.data);
}
#endif
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; // strdup?!
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; // strdup?!
}
else {
//ret = C_V4L2_ERROR;
dev->device.name = strdup(dev->v4l2_name);
dev->device.driver = strdup("uvcvideo");
dev->device.location = strdup(dev->v4l2_name);
}
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
#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 &&
strstr(dir_entry->d_name, "subdev") != 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();
}