/** * \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(); }