diff options
Diffstat (limited to 'android/camera/camera-capture-linux.c')
-rw-r--r-- | android/camera/camera-capture-linux.c | 1096 |
1 files changed, 1096 insertions, 0 deletions
diff --git a/android/camera/camera-capture-linux.c b/android/camera/camera-capture-linux.c new file mode 100644 index 0000000..5243fb6 --- /dev/null +++ b/android/camera/camera-capture-linux.c @@ -0,0 +1,1096 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Contains code that is used to capture video frames from a camera device + * on Linux. This code uses V4L2 API to work with camera devices, and requires + * Linux kernel version at least 2.5 + */ + +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include "android/camera/camera-capture.h" +#include "android/camera/camera-format-converters.h" + +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) +#define D(...) VERBOSE_PRINT(camera,__VA_ARGS__) +#define D_ACTIVE VERBOSE_CHECK(camera) + +/* the T(...) macro is used to dump traffic */ +#define T_ACTIVE 0 + +#if T_ACTIVE +#define T(...) VERBOSE_PRINT(camera,__VA_ARGS__) +#else +#define T(...) ((void)0) +#endif + +#define CLEAR(x) memset (&(x), 0, sizeof(x)) + +/* Pixel format descriptor. + * Instances of this descriptor are created during camera device enumeration, and + * an instance of this structure describing pixel format chosen for the camera + * emulation is saved by the camera factory service to represent an emulating + * camera properties. + */ +typedef struct QemuPixelFormat { + /* Pixel format in V4L2_PIX_FMT_XXX form. */ + uint32_t format; + /* Frame dimensions supported by this format. */ + CameraFrameDim* dims; + /* Number of frame dimensions supported by this format. */ + int dim_num; +} QemuPixelFormat; + +/* Describes a framebuffer. */ +typedef struct CameraFrameBuffer { + /* Framebuffer data. */ + uint8_t* data; + /* Framebuffer data size. */ + size_t size; +} CameraFrameBuffer; + +/* Defines type of the I/O used to obtain frames from the device. */ +typedef enum CameraIoType { + /* Framebuffers are shared via memory mapping. */ + CAMERA_IO_MEMMAP, + /* Framebuffers are available via user pointers. */ + CAMERA_IO_USERPTR, + /* Framebuffers are to be read from the device. */ + CAMERA_IO_DIRECT +} CameraIoType; + +typedef struct LinuxCameraDevice LinuxCameraDevice; +/* + * Describes a connection to an actual camera device. + */ +struct LinuxCameraDevice { + /* Common header. */ + CameraDevice header; + + /* Camera device name. (default is /dev/video0) */ + char* device_name; + /* Input channel. (default is 0) */ + int input_channel; + + /* + * Set by the framework after initializing camera connection. + */ + + /* Handle to the opened camera device. */ + int handle; + /* Device capabilities. */ + struct v4l2_capability caps; + /* Actual pixel format reported by the device when capturing is started. */ + struct v4l2_pix_format actual_pixel_format; + /* Defines type of the I/O to use to retrieve frames from the device. */ + CameraIoType io_type; + /* Allocated framebuffers. */ + struct CameraFrameBuffer* framebuffers; + /* Actual number of allocated framebuffers. */ + int framebuffer_num; +}; + +/* Preferred pixel formats arranged from the most to the least desired. + * + * More than anything else this array is defined by an existance of format + * conversion between the camera supported formats, and formats that are + * supported by camera framework in the guest system. Currently, guest supports + * only YV12 pixel format for data, and RGB32 for preview. So, this array should + * contain only those formats, for which converters are implemented. Generally + * speaking, the order in which entries should be arranged in this array matters + * only as far as conversion speed is concerned. So, formats with the fastest + * converters should be put closer to the top of the array, while slower ones + * should be put closer to the bottom. But as far as functionality is concerned, + * the orser doesn't matter, and any format can be placed anywhere in this array, + * as long as conversion for it exists. + */ +static const uint32_t _preferred_formats[] = +{ + /* Native format for the emulated camera: no conversion at all. */ + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + /* Continue with YCbCr: less math than with RGB */ + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_YUYV, + /* End with RGB. */ + V4L2_PIX_FMT_RGB32, + V4L2_PIX_FMT_RGB24, + V4L2_PIX_FMT_RGB565, +}; +/* Number of entries in _preferred_formats array. */ +static const int _preferred_format_num = + sizeof(_preferred_formats)/sizeof(*_preferred_formats); + +/******************************************************************************* + * Helper routines + ******************************************************************************/ + +/* IOCTL wrapper. */ +static int +_xioctl(int fd, int request, void *arg) { + int r; + do { + r = ioctl(fd, request, arg); + } while (-1 == r && EINTR == errno); + return r; +} + +/* Frees resource allocated for QemuPixelFormat instance, excluding the instance + * itself. + */ +static void _qemu_pixel_format_free(QemuPixelFormat* fmt) +{ + if (fmt != NULL) { + if (fmt->dims != NULL) + free(fmt->dims); + } +} + +/* Returns an index of the given pixel format in an array containing pixel + * format descriptors. + * This routine is used to choose a pixel format for a camera device. The idea + * is that when the camera service enumerates all pixel formats for all cameras + * connected to the host, we need to choose just one, which would be most + * appropriate for camera emulation. To do that, the camera service will run + * formats, contained in _preferred_formats array against enumerated pixel + * formats to pick the first format that match. + * Param: + * fmt - Pixel format, for which to obtain the index. + * formats - Array containing list of pixel formats, supported by the camera + * device. + * size - Number of elements in the 'formats' array. + * Return: + * Index of the matched entry in the array, or -1 if no entry has been found. + */ +static int +_get_format_index(uint32_t fmt, QemuPixelFormat* formats, int size) +{ + int f; + for (f = 0; f < size && formats[f].format != fmt; f++); + return f < size ? f : -1; +} + +/******************************************************************************* + * CameraFrameBuffer routines + ******************************************************************************/ + +/* Frees array of framebuffers, depending on the I/O method the array has been + * initialized for. + * Note that this routine doesn't frees the array itself. + * Param: + * fb, num - Array data, and its size. + * io_type - Type of the I/O the array has been initialized for. + */ +static void +_free_framebuffers(CameraFrameBuffer* fb, int num, CameraIoType io_type) +{ + if (fb != NULL) { + int n; + + switch (io_type) { + case CAMERA_IO_MEMMAP: + /* Unmap framebuffers. */ + for (n = 0; n < num; n++) { + if (fb[n].data != NULL) { + munmap(fb[n].data, fb[n].size); + fb[n].data = NULL; + fb[n].size = 0; + } + } + break; + + case CAMERA_IO_USERPTR: + case CAMERA_IO_DIRECT: + /* Free framebuffers. */ + for (n = 0; n < num; n++) { + if (fb[n].data != NULL) { + free(fb[n].data); + fb[n].data = NULL; + fb[n].size = 0; + } + } + break; + + default: + E("%s: Invalid I/O type %d", __FUNCTION__, io_type); + break; + } + } +} + +/******************************************************************************* + * CameraDevice routines + ******************************************************************************/ + +/* Allocates an instance of LinuxCameraDevice structure. + * Return: + * Allocated instance of LinuxCameraDevice structure. Note that this routine + * also sets 'opaque' field in the 'header' structure to point back to the + * containing LinuxCameraDevice instance. + */ +static LinuxCameraDevice* +_camera_device_alloc(void) +{ + LinuxCameraDevice* cd; + + ANEW0(cd); + memset(cd, 0, sizeof(*cd)); + cd->header.opaque = cd; + cd->handle = -1; + + return cd; +} + +/* Uninitializes and frees CameraDevice structure. + */ +static void +_camera_device_free(LinuxCameraDevice* lcd) +{ + if (lcd != NULL) { + /* Closing handle will also disconnect from the driver. */ + if (lcd->handle >= 0) { + close(lcd->handle); + } + if (lcd->device_name != NULL) { + free(lcd->device_name); + } + if (lcd->framebuffers != NULL) { + _free_framebuffers(lcd->framebuffers, lcd->framebuffer_num, + lcd->io_type); + free(lcd->framebuffers); + } + AFREE(lcd); + } else { + E("%s: No descriptor", __FUNCTION__); + } +} + +/* Resets camera device after capturing. + * Since new capture request may require different frame dimensions we must + * reset camera device by reopening its handle. Otherwise attempts to set up new + * frame properties (different from the previous one) may fail. */ +static void +_camera_device_reset(LinuxCameraDevice* cd) +{ + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + + /* Free capturing framebuffers first. */ + if (cd->framebuffers != NULL) { + _free_framebuffers(cd->framebuffers, cd->framebuffer_num, cd->io_type); + free(cd->framebuffers); + cd->framebuffers = NULL; + cd->framebuffer_num = 0; + } + + /* Reset device handle. */ + close(cd->handle); + cd->handle = open(cd->device_name, O_RDWR | O_NONBLOCK, 0); + + if (cd->handle >= 0) { + /* Select video input, video standard and tune here. */ + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + _xioctl(cd->handle, VIDIOC_CROPCAP, &cropcap); + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; /* reset to default */ + _xioctl (cd->handle, VIDIOC_S_CROP, &crop); + } +} + +/* Memory maps buffers and shares mapped memory with the device. + * Return: + * 0 Framebuffers have been mapped. + * -1 A critical error has ocurred. + * 1 Memory mapping is not available. + */ +static int +_camera_device_mmap_framebuffer(LinuxCameraDevice* cd) +{ + struct v4l2_requestbuffers req; + CLEAR(req); + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + /* Request memory mapped buffers. Note that device can return less buffers + * than requested. */ + if(_xioctl(cd->handle, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + D("%s: Device '%s' does not support memory mapping", + __FUNCTION__, cd->device_name); + return 1; + } else { + E("%s: VIDIOC_REQBUFS has failed: %s", + __FUNCTION__, strerror(errno)); + return -1; + } + } + + /* Allocate framebuffer array. */ + cd->framebuffers = calloc(req.count, sizeof(CameraFrameBuffer)); + if (cd->framebuffers == NULL) { + E("%s: Not enough memory to allocate framebuffer array", __FUNCTION__); + return -1; + } + + /* Map every framebuffer to the shared memory, and queue it + * with the device. */ + for(cd->framebuffer_num = 0; cd->framebuffer_num < req.count; + cd->framebuffer_num++) { + /* Map framebuffer. */ + struct v4l2_buffer buf; + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = cd->framebuffer_num; + if(_xioctl(cd->handle, VIDIOC_QUERYBUF, &buf) < 0) { + E("%s: VIDIOC_QUERYBUF has failed: %s", + __FUNCTION__, strerror(errno)); + return -1; + } + cd->framebuffers[cd->framebuffer_num].size = buf.length; + cd->framebuffers[cd->framebuffer_num].data = + mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, + cd->handle, buf.m.offset); + if (MAP_FAILED == cd->framebuffers[cd->framebuffer_num].data) { + E("%s: Memory mapping has failed: %s", + __FUNCTION__, strerror(errno)); + return -1; + } + + /* Queue the mapped buffer. */ + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = cd->framebuffer_num; + if (_xioctl(cd->handle, VIDIOC_QBUF, &buf) < 0) { + E("%s: VIDIOC_QBUF has failed: %s", __FUNCTION__, strerror(errno)); + return -1; + } + } + + cd->io_type = CAMERA_IO_MEMMAP; + + return 0; +} + +/* Allocates frame buffers and registers them with the device. + * Return: + * 0 Framebuffers have been mapped. + * -1 A critical error has ocurred. + * 1 Device doesn't support user pointers. + */ +static int +_camera_device_user_framebuffer(LinuxCameraDevice* cd) +{ + struct v4l2_requestbuffers req; + CLEAR (req); + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; + + /* Request user buffers. Note that device can return less buffers + * than requested. */ + if(_xioctl(cd->handle, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + D("%s: Device '%s' does not support user pointers", + __FUNCTION__, cd->device_name); + return 1; + } else { + E("%s: VIDIOC_REQBUFS has failed: %s", + __FUNCTION__, strerror(errno)); + return -1; + } + } + + /* Allocate framebuffer array. */ + cd->framebuffers = calloc(req.count, sizeof(CameraFrameBuffer)); + if (cd->framebuffers == NULL) { + E("%s: Not enough memory to allocate framebuffer array", __FUNCTION__); + return -1; + } + + /* Allocate buffers, queueing them wit the device at the same time */ + for(cd->framebuffer_num = 0; cd->framebuffer_num < req.count; + cd->framebuffer_num++) { + cd->framebuffers[cd->framebuffer_num].size = + cd->actual_pixel_format.sizeimage; + cd->framebuffers[cd->framebuffer_num].data = + malloc(cd->framebuffers[cd->framebuffer_num].size); + if (cd->framebuffers[cd->framebuffer_num].data == NULL) { + E("%s: Not enough memory to allocate framebuffer", __FUNCTION__); + return -1; + } + + /* Queue the user buffer. */ + struct v4l2_buffer buf; + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.m.userptr = (unsigned long)cd->framebuffers[cd->framebuffer_num].data; + buf.length = cd->framebuffers[cd->framebuffer_num].size; + if (_xioctl(cd->handle, VIDIOC_QBUF, &buf) < 0) { + E("%s: VIDIOC_QBUF has failed: %s", __FUNCTION__, strerror(errno)); + return -1; + } + } + + cd->io_type = CAMERA_IO_USERPTR; + + return 0; +} + +/* Allocate frame buffer for direct read from the device. + * Return: + * 0 Framebuffers have been mapped. + * -1 A critical error has ocurred. + * 1 Memory mapping is not available. + */ +static int +_camera_device_direct_framebuffer(LinuxCameraDevice* cd) +{ + /* Allocate framebuffer array. */ + cd->framebuffer_num = 1; + cd->framebuffers = malloc(sizeof(CameraFrameBuffer)); + if (cd->framebuffers == NULL) { + E("%s: Not enough memory to allocate framebuffer array", __FUNCTION__); + return -1; + } + + cd->framebuffers[0].size = cd->actual_pixel_format.sizeimage; + cd->framebuffers[0].data = malloc(cd->framebuffers[0].size); + if (cd->framebuffers[0].data == NULL) { + E("%s: Not enough memory to allocate framebuffer", __FUNCTION__); + return -1; + } + + cd->io_type = CAMERA_IO_DIRECT; + + return 0; +} + +/* Opens camera device. + * Param: + * cd - Camera device descriptor to open the camera for. + * Return: + * 0 on success, != 0 on failure. + */ +static int +_camera_device_open(LinuxCameraDevice* cd) +{ + struct stat st; + + if (stat(cd->device_name, &st)) { + return -1; + } + + if (!S_ISCHR(st.st_mode)) { + E("%s: '%s' is not a device", __FUNCTION__, cd->device_name); + return -1; + } + + /* Open handle to the device, and query device capabilities. */ + cd->handle = open(cd->device_name, O_RDWR | O_NONBLOCK, 0); + if (cd->handle < 0) { + E("%s: Cannot open camera device '%s': %s", + __FUNCTION__, cd->device_name, strerror(errno)); + return -1; + } + if (_xioctl(cd->handle, VIDIOC_QUERYCAP, &cd->caps) < 0) { + if (EINVAL == errno) { + E("%s: Camera '%s' is not a V4L2 device", + __FUNCTION__, cd->device_name); + close(cd->handle); + cd->handle = -1; + return -1; + } else { + E("%s: Unable to query capabilities for camera device '%s'", + __FUNCTION__, cd->device_name); + close(cd->handle); + cd->handle = -1; + return -1; + } + } + + /* Make sure that camera supports minimal requirements. */ + if (!(cd->caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + E("%s: Camera '%s' is not a video capture device", + __FUNCTION__, cd->device_name); + close(cd->handle); + cd->handle = -1; + return -1; + } + + return 0; +} + +/* Enumerates frame sizes for the given pixel format. + * Param: + * cd - Opened camera device descriptor. + * fmt - Pixel format to enum frame sizes for. + * sizes - Upon success contains an array of supported frame sizes. The size of + * the array is defined by the value, returned from this routine. The caller + * is responsible for freeing memory allocated for this array. + * Return: + * On success returns number of entries in the 'sizes' array. On failure returns + * a negative value. + */ +static int +_camera_device_enum_format_sizes(LinuxCameraDevice* cd, + uint32_t fmt, + CameraFrameDim** sizes) +{ + int n; + int sizes_num = 0; + int out_num = 0; + struct v4l2_frmsizeenum size_enum; + CameraFrameDim* arr; + + /* Calculate number of supported sizes for the given format. */ + for (n = 0; ; n++) { + size_enum.index = n; + size_enum.pixel_format = fmt; + if(_xioctl(cd->handle, VIDIOC_ENUM_FRAMESIZES, &size_enum)) { + break; + } + if (size_enum.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + /* Size is in the simpe width, height form. */ + sizes_num++; + } else if (size_enum.type == V4L2_FRMSIZE_TYPE_STEPWISE) { + /* Sizes are represented as min/max width and height with a step for + * each dimension. Since at the end we want to list each supported + * size in the array (that's the only format supported by the guest + * camera framework), we need to calculate how many array entries + * this will generate. */ + const uint32_t dif_widths = + (size_enum.stepwise.max_width - size_enum.stepwise.min_width) / + size_enum.stepwise.step_width + 1; + const uint32_t dif_heights = + (size_enum.stepwise.max_height - size_enum.stepwise.min_height) / + size_enum.stepwise.step_height + 1; + sizes_num += dif_widths * dif_heights; + } else if (size_enum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { + /* Special stepwise case, when steps are set to 1. We still need to + * flatten this for the guest, but the array may be too big. + * Fortunately, we don't need to be fancy, so three sizes would be + * sufficient here: min, max, and one in the middle. */ + sizes_num += 3; + } + + } + if (sizes_num == 0) { + return 0; + } + + /* Allocate, and initialize the array of supported entries. */ + *sizes = (CameraFrameDim*)malloc(sizes_num * sizeof(CameraFrameDim)); + if (*sizes == NULL) { + E("%s: Memory allocation failure", __FUNCTION__); + return -1; + } + arr = *sizes; + for (n = 0; out_num < sizes_num; n++) { + size_enum.index = n; + size_enum.pixel_format = fmt; + if(_xioctl(cd->handle, VIDIOC_ENUM_FRAMESIZES, &size_enum)) { + /* Errors are not welcome here anymore. */ + E("%s: Unexpected failure while getting pixel dimensions: %s", + __FUNCTION__, strerror(errno)); + free(arr); + return -1; + } + + if (size_enum.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + arr[out_num].width = size_enum.discrete.width; + arr[out_num].height = size_enum.discrete.height; + out_num++; + } else if (size_enum.type == V4L2_FRMSIZE_TYPE_STEPWISE) { + uint32_t w; + for (w = size_enum.stepwise.min_width; + w <= size_enum.stepwise.max_width; + w += size_enum.stepwise.step_width) { + uint32_t h; + for (h = size_enum.stepwise.min_height; + h <= size_enum.stepwise.max_height; + h += size_enum.stepwise.step_height) { + arr[out_num].width = w; + arr[out_num].height = h; + out_num++; + } + } + } else if (size_enum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { + /* min */ + arr[out_num].width = size_enum.stepwise.min_width; + arr[out_num].height = size_enum.stepwise.min_height; + out_num++; + /* one in the middle */ + arr[out_num].width = + (size_enum.stepwise.min_width + size_enum.stepwise.max_width) / 2; + arr[out_num].height = + (size_enum.stepwise.min_height + size_enum.stepwise.max_height) / 2; + out_num++; + /* max */ + arr[out_num].width = size_enum.stepwise.max_width; + arr[out_num].height = size_enum.stepwise.max_height; + out_num++; + } + } + + return out_num; +} + +/* Enumerates pixel formats, supported by the device. + * Note that this routine will enumerate only raw (uncompressed) formats. + * Param: + * cd - Opened camera device descriptor. + * fmts - Upon success contains an array of supported pixel formats. The size of + * the array is defined by the value, returned from this routine. The caller + * is responsible for freeing memory allocated for this array. + * Return: + * On success returns number of entries in the 'fmts' array. On failure returns + * a negative value. + */ +static int +_camera_device_enum_pixel_formats(LinuxCameraDevice* cd, QemuPixelFormat** fmts) +{ + int n, max_fmt; + int fmt_num = 0; + int out_num = 0; + struct v4l2_fmtdesc fmt_enum; + QemuPixelFormat* arr; + + /* Calculate number of supported formats. */ + for (max_fmt = 0; ; max_fmt++) { + memset(&fmt_enum, 0, sizeof(fmt_enum)); + fmt_enum.index = max_fmt; + fmt_enum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if(_xioctl(cd->handle, VIDIOC_ENUM_FMT, &fmt_enum)) { + break; + } + /* Skip the compressed ones. */ + if ((fmt_enum.flags & V4L2_FMT_FLAG_COMPRESSED) == 0) { + fmt_num++; + } + } + if (fmt_num == 0) { + return 0; + } + + /* Allocate, and initialize array for enumerated formats. */ + *fmts = (QemuPixelFormat*)malloc(fmt_num * sizeof(QemuPixelFormat)); + if (*fmts == NULL) { + E("%s: Memory allocation failure", __FUNCTION__); + return -1; + } + arr = *fmts; + memset(arr, 0, fmt_num * sizeof(QemuPixelFormat)); + for (n = 0; n < max_fmt && out_num < fmt_num; n++) { + memset(&fmt_enum, 0, sizeof(fmt_enum)); + fmt_enum.index = n; + fmt_enum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if(_xioctl(cd->handle, VIDIOC_ENUM_FMT, &fmt_enum)) { + int nn; + /* Errors are not welcome here anymore. */ + E("%s: Unexpected failure while getting pixel format: %s", + __FUNCTION__, strerror(errno)); + for (nn = 0; nn < out_num; nn++) { + _qemu_pixel_format_free(arr + nn); + } + free(arr); + return -1; + } + /* Skip the compressed ones. */ + if ((fmt_enum.flags & V4L2_FMT_FLAG_COMPRESSED) == 0) { + arr[out_num].format = fmt_enum.pixelformat; + /* Enumerate frame dimensions supported for this format. */ + arr[out_num].dim_num = + _camera_device_enum_format_sizes(cd, fmt_enum.pixelformat, + &arr[out_num].dims); + if (arr[out_num].dim_num > 0) { + out_num++; + } else if (arr[out_num].dim_num < 0) { + int nn; + E("Unable to enumerate supported dimensions for pixel format %d", + fmt_enum.pixelformat); + for (nn = 0; nn < out_num; nn++) { + _qemu_pixel_format_free(arr + nn); + } + free(arr); + return -1; + } + } + } + + return out_num; +} + +/* Collects information about an opened camera device. + * The information collected in this routine contains list of pixel formats, + * supported by the device, and list of frame dimensions supported by the camera + * for each pixel format. + * Param: + * cd - Opened camera device descriptor. + * cis - Upon success contains information collected from the camera device. + * Return: + * 0 on success, != 0 on failure. + */ +static int +_camera_device_get_info(LinuxCameraDevice* cd, CameraInfo* cis) +{ + int f; + int chosen = -1; + QemuPixelFormat* formats = NULL; + int num_pix_fmts = _camera_device_enum_pixel_formats(cd, &formats); + if (num_pix_fmts <= 0) { + return -1; + } + + /* Lets see if camera supports preferred formats */ + for (f = 0; f < _preferred_format_num; f++) { + chosen = _get_format_index(_preferred_formats[f], formats, num_pix_fmts); + if (chosen >= 0) { + break; + } + } + if (chosen < 0) { + /* Camera doesn't support any of the chosen formats. Then it doesn't + * matter which one we choose. Lets choose the first one. */ + chosen = 0; + } + + cis->device_name = ASTRDUP(cd->device_name); + cis->inp_channel = cd->input_channel; + cis->pixel_format = formats[chosen].format; + cis->frame_sizes_num = formats[chosen].dim_num; + /* Swap instead of copy. */ + cis->frame_sizes = formats[chosen].dims; + formats[chosen].dims = NULL; + cis->in_use = 0; + + for (f = 0; f < num_pix_fmts; f++) { + _qemu_pixel_format_free(formats + f); + } + free(formats); + + return 0; +} + +/******************************************************************************* + * CameraDevice API + ******************************************************************************/ + +CameraDevice* +camera_device_open(const char* name, int inp_channel) +{ + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + LinuxCameraDevice* cd; + + /* Allocate and initialize the descriptor. */ + cd = _camera_device_alloc(); + cd->device_name = name != NULL ? ASTRDUP(name) : ASTRDUP("/dev/video0"); + cd->input_channel = inp_channel; + + /* Open the device. */ + if (_camera_device_open(cd)) { + _camera_device_free(cd); + return NULL; + } + + /* Select video input, video standard and tune here. */ + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + _xioctl(cd->handle, VIDIOC_CROPCAP, &cropcap); + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; /* reset to default */ + _xioctl (cd->handle, VIDIOC_S_CROP, &crop); + + return &cd->header; +} + +int +camera_device_start_capturing(CameraDevice* ccd, + uint32_t pixel_format, + int frame_width, + int frame_height) +{ + struct v4l2_format fmt; + LinuxCameraDevice* cd; + char fmt_str[5]; + int r; + + /* Sanity checks. */ + if (ccd == NULL || ccd->opaque == NULL) { + E("%s: Invalid camera device descriptor", __FUNCTION__); + return -1; + } + cd = (LinuxCameraDevice*)ccd->opaque; + if (cd->handle < 0) { + E("%s: Camera device is not opened", __FUNCTION__); + return -1; + } + + /* Try to set pixel format with the given dimensions. */ + CLEAR(fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = frame_width; + fmt.fmt.pix.height = frame_height; + fmt.fmt.pix.pixelformat = pixel_format; + if (_xioctl(cd->handle, VIDIOC_S_FMT, &fmt) < 0) { + memcpy(fmt_str, &pixel_format, 4); + fmt_str[4] = '\0'; + E("%s: Camera '%s' does not support pixel format '%s' with dimensions %dx%d", + __FUNCTION__, cd->device_name, fmt_str, frame_width, frame_height); + _camera_device_reset(cd); + return -1; + } + /* VIDIOC_S_FMT may has changed some properties of the structure. Make sure + * that dimensions didn't change. */ + if (fmt.fmt.pix.width != frame_width || fmt.fmt.pix.height != frame_height) { + memcpy(fmt_str, &pixel_format, 4); + fmt_str[4] = '\0'; + E("%s: Dimensions %dx%d are wrong for pixel format '%s'", + __FUNCTION__, frame_width, frame_height, fmt_str); + _camera_device_reset(cd); + return -1; + } + memcpy(&cd->actual_pixel_format, &fmt.fmt.pix, sizeof(struct v4l2_pix_format)); + + /* + * Lets initialize frame buffers, and see what kind of I/O we're going to + * use to retrieve frames. + */ + + /* First, lets see if we can do mapped I/O (as most performant one). */ + r = _camera_device_mmap_framebuffer(cd); + if (r < 0) { + /* Some critical error has ocurred. Bail out. */ + _camera_device_reset(cd); + return -1; + } else if (r > 0) { + /* Device doesn't support memory mapping. Retrieve to the next performant + * one: preallocated user buffers. */ + r = _camera_device_user_framebuffer(cd); + if (r < 0) { + /* Some critical error has ocurred. Bail out. */ + _camera_device_reset(cd); + return -1; + } else if (r > 0) { + /* The only thing left for us is direct reading from the device. */ + if (!(cd->caps.capabilities & V4L2_CAP_READWRITE)) { + E("%s: Don't know how to access frames on device '%s'", + __FUNCTION__, cd->device_name); + _camera_device_reset(cd); + return -1; + } + r = _camera_device_direct_framebuffer(cd); + if (r != 0) { + /* Any error at this point is a critical one. */ + _camera_device_reset(cd); + return -1; + } + } + } + + /* Start capturing from the device. */ + if (cd->io_type != CAMERA_IO_DIRECT) { + enum v4l2_buf_type type; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (_xioctl (cd->handle, VIDIOC_STREAMON, &type) < 0) { + E("%s: VIDIOC_STREAMON on camera '%s' has failed: %s", + __FUNCTION__, cd->device_name, strerror(errno)); + _camera_device_reset(cd); + return -1; + } + } + return 0; +} + +int +camera_device_stop_capturing(CameraDevice* ccd) +{ + enum v4l2_buf_type type; + LinuxCameraDevice* cd; + + /* Sanity checks. */ + if (ccd == NULL || ccd->opaque == NULL) { + E("%s: Invalid camera device descriptor", __FUNCTION__); + return -1; + } + cd = (LinuxCameraDevice*)ccd->opaque; + if (cd->handle < 0) { + E("%s: Camera device is not opened", __FUNCTION__); + return -1; + } + + switch (cd->io_type) { + case CAMERA_IO_DIRECT: + /* Nothing to do. */ + break; + + case CAMERA_IO_MEMMAP: + case CAMERA_IO_USERPTR: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (_xioctl(cd->handle, VIDIOC_STREAMOFF, &type) < 0) { + E("%s: VIDIOC_STREAMOFF on camera '%s' has failed: %s", + __FUNCTION__, cd->device_name, strerror(errno)); + return -1; + } + break; + default: + E("%s: Unknown I/O method: %d", __FUNCTION__, cd->io_type); + return -1; + } + + /* Reopen the device to reset its internal state. It seems that if we don't + * do that, an attempt to reinit the device with different frame dimensions + * would fail. */ + _camera_device_reset(cd); + + return 0; +} + +int +camera_device_read_frame(CameraDevice* ccd, + ClientFrameBuffer* framebuffers, + int fbs_num) +{ + LinuxCameraDevice* cd; + + /* Sanity checks. */ + if (ccd == NULL || ccd->opaque == NULL) { + E("%s: Invalid camera device descriptor", __FUNCTION__); + return -1; + } + cd = (LinuxCameraDevice*)ccd->opaque; + if (cd->handle < 0) { + E("%s: Camera device is not opened", __FUNCTION__); + return -1; + } + + if (cd->io_type == CAMERA_IO_DIRECT) { + /* Read directly from the device. */ + size_t total_read_bytes = 0; + /* There is one framebuffer allocated for direct read. */ + void* buff = cd->framebuffers[0].data; + do { + int read_bytes = + read(cd->handle, buff + total_read_bytes, + cd->actual_pixel_format.sizeimage - total_read_bytes); + if (read_bytes < 0) { + switch (errno) { + case EIO: + case EAGAIN: + continue; + default: + E("%s: Unable to read from the camera device '%s': %s", + __FUNCTION__, cd->device_name, strerror(errno)); + return -1; + } + } + total_read_bytes += read_bytes; + } while (total_read_bytes < cd->actual_pixel_format.sizeimage); + /* Convert the read frame into the caller's framebuffers. */ + return convert_frame(buff, cd->actual_pixel_format.pixelformat, + cd->actual_pixel_format.sizeimage, + cd->actual_pixel_format.width, + cd->actual_pixel_format.height, + framebuffers, fbs_num); + } else { + /* Dequeue next buffer from the device. */ + struct v4l2_buffer buf; + int res; + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = cd->io_type == CAMERA_IO_MEMMAP ? V4L2_MEMORY_MMAP : + V4L2_MEMORY_USERPTR; + for (;;) { + const int res = _xioctl(cd->handle, VIDIOC_DQBUF, &buf); + if (res >= 0) { + break; + } else if (errno == EAGAIN) { + return 1; // Tells the caller to repeat. + } else if (errno != EINTR && errno != EIO) { + E("%s: VIDIOC_DQBUF on camera '%s' has failed: %s", + __FUNCTION__, cd->device_name, strerror(errno)); + return -1; + } + } + + /* Convert frame to the receiving buffers. */ + res = convert_frame(cd->framebuffers[buf.index].data, + cd->actual_pixel_format.pixelformat, + cd->actual_pixel_format.sizeimage, + cd->actual_pixel_format.width, + cd->actual_pixel_format.height, + framebuffers, fbs_num); + + /* Requeue the buffer back to the device. */ + if (_xioctl(cd->handle, VIDIOC_QBUF, &buf) < 0) { + W("%s: VIDIOC_QBUF on camera '%s' has failed: %s", + __FUNCTION__, cd->device_name, strerror(errno)); + } + + return res; + } +} + +void +camera_device_close(CameraDevice* ccd) +{ + LinuxCameraDevice* cd; + + /* Sanity checks. */ + if (ccd != NULL && ccd->opaque != NULL) { + cd = (LinuxCameraDevice*)ccd->opaque; + _camera_device_free(cd); + } else { + E("%s: Invalid camera device descriptor", __FUNCTION__); + } +} + +int +enumerate_camera_devices(CameraInfo* cis, int max) +{ + char dev_name[24]; + int found = 0; + int n; + + for (n = 0; n < max; n++) { + CameraDevice* cd; + + sprintf(dev_name, "/dev/video%d", n); + cd = camera_device_open(dev_name, 0); + if (cd != NULL) { + LinuxCameraDevice* lcd = (LinuxCameraDevice*)cd->opaque; + if (!_camera_device_get_info(lcd, cis + found)) { + char user_name[24]; + sprintf(user_name, "webcam%d", found); + cis[found].display_name = ASTRDUP(user_name); + cis[found].in_use = 0; + found++; + } + camera_device_close(cd); + } else { + break; + } + } + + return found; +} |