diff --git a/Makefile b/Makefile index 6b50978777..d66150787c 100644 --- a/Makefile +++ b/Makefile @@ -156,6 +156,11 @@ ifeq ($(HAVE_AL), 1) endif endif +ifeq ($(HAVE_V4L2),1) + OBJ += camera/video4linux2.o + DEFINES += -DHAVE_CAMERA -DHAVE_V4L2 +endif + ifeq ($(HAVE_JACK),1) OBJ += audio/jack.o LIBS += $(JACK_LIBS) diff --git a/camera/video4linux2.c b/camera/video4linux2.c new file mode 100644 index 0000000000..cea33de924 --- /dev/null +++ b/camera/video4linux2.c @@ -0,0 +1,715 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * Copyright (C) 2011-2013 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include +#include "../driver.h" +#include "../miscellaneous.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +typedef enum +{ + IO_METHOD_READ, + IO_METHOD_MMAP, + IO_METHOD_USERPTR, +} io_method; + +struct buffer +{ + void *start; + size_t length; +}; + +typedef struct video4linux +{ + char *dev_name; + int fd; + int ready; + io_method io; + struct buffer *buffers; + unsigned int n_buffers; + size_t width; + size_t height; +} video4linux_t; + +#if 0 +static void YCbCrToRGB(int y, int cb, int cr, uint8_t * r, uint8_t * g, uint8_t * b) +{ + double Y = (double)y; + double Cb = (double)cb; + double Cr = (double)cr; + + int R = (int)(Y + 1.40200 * (Cr - 0x80)); + int G = (int)(Y - 0.34414 * (Cb - 0x80) - 0.71414 * (Cr - 0x80)); + int B = (int)(Y + 1.77200 * (Cb - 0x80)); + + *r = max(0, min(255, R)); + *g = max(0, min(255, G)); + *b = max(0, min(255, B)); +} +#endif + +/* + * YCbCr to RGB lookup table + * + * Indexes are [Y][Cb][Cr] + * Y, Cb, Cr range is 0-255 + * + * Stored value bits: + * 24-16 Red + * 15-8 Green + * 7-0 Blue + */ +uint32_t YCbCr_to_RGB[256][256][256]; + +static void generate_YCbCr_to_RGB_lookup(void) +{ + int y; + int cb; + int cr; + + for (y = 0; y < 256; y++) + { + for (cb = 0; cb < 256; cb++) + { + for (cr = 0; cr < 256; cr++) + { + double Y = (double)y; + double Cb = (double)cb; + double Cr = (double)cr; + + int R = (int)(Y+1.40200*(Cr - 0x80)); + int G = (int)(Y-0.34414*(Cb - 0x80)-0.71414*(Cr - 0x80)); + int B = (int)(Y+1.77200*(Cb - 0x80)); + + R = max(0, min(255, R)); + G = max(0, min(255, G)); + B = max(0, min(255, B)); + + YCbCr_to_RGB[y][cb][cr] = R << 16 | G << 8 | B; + } + } + } +} + +#define COLOR_GET_RED(color) ((color >> 16) & 0xFF) +#define COLOR_GET_GREEN(color) ((color >> 8) & 0xFF) +#define COLOR_GET_BLUE(color) (color & 0xFF) + +/** + * Converts YUV422 to RGB + * Before first use call generate_YCbCr_to_RGB_lookup(); + * + * input is pointer to YUV422 encoded data in following order: Y0, Cb, Y1, Cr. + * output is pointer to 24 bit RGB buffer. + * Output data is written in following order: R1, G1, B1, R2, G2, B2. + */ + +// FIXME: Software CPU color conersion from YUV to RGB - we'll make two codepaths +// eventually - GL binding to texture and color conversion through shaders, +// and this approach + +static inline void YUV422_to_RGB(uint8_t * output, const uint8_t * input) +{ + uint8_t y0 = input[0]; + uint8_t cb = input[1]; + uint8_t y1 = input[2]; + uint8_t cr = input[3]; + + uint32_t rgb = YCbCr_to_RGB[y0][cb][cr]; + output[0] = COLOR_GET_RED(rgb); + output[1] = COLOR_GET_GREEN(rgb); + output[2] = COLOR_GET_BLUE(rgb); + + rgb = YCbCr_to_RGB[y1][cb][cr]; + output[3] = COLOR_GET_RED(rgb); + output[4] = COLOR_GET_GREEN(rgb); + output[5] = COLOR_GET_BLUE(rgb); +} + +static void process_image(const void *p) +{ + (void)p; + //FIXME - fill in here how we are going to render + //this - could have two codepaths - one for GL + //and one non-GL +#if 0 + const uint8_t *buffer_yuv = p; + + size_t x; + size_t y; + + for (y = 0; y < height; y++) + for (x = 0; x < width; x += 2) + YUV422_to_RGB(buffer_sdl + (y * width + x) * 3, + buffer_yuv + (y * width + x) * 2); + + render(data_sf); +#endif +} + +static int xioctl(int fd, int request, void *args) +{ + int r; + + do + { + r = ioctl(fd, request, args); + }while(r == -1 && errno == EINTR); + + return r; +} + +static int init_read(void *data, unsigned int buffer_size) +{ + video4linux_t *v4l = (video4linux_t*)data; + v4l->buffers = (struct buffer*)calloc(1, sizeof(*v4l->buffers)); + + if (!v4l->buffers) + { + RARCH_ERR("Out of memory allocating V4L2 buffers.\n"); + return -1; + } + + v4l->buffers[0].length = buffer_size; + v4l->buffers[0].start = (void*)malloc(buffer_size); + + if (!v4l->buffers[0].start) + { + RARCH_ERR("Out of memory allocating V4L2 buffers.\n"); + return -1; + } + + return 0; +} + +static int init_mmap(void *data) +{ + struct v4l2_requestbuffers req; + video4linux_t *v4l = (video4linux_t*)data; + + memset(&req, 0, sizeof(req)); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (xioctl(v4l->fd, VIDIOC_REQBUFS, &req) == -1) + { + if (errno == EINVAL) + { + RARCH_ERR("%s does not support memory mapping.\n", v4l->dev_name); + return -1; + } + else + { + RARCH_ERR("xioctl of VIDIOC_REQBUFS failed.\n"); + return -1; + } + } + + if (req.count < 2) + { + RARCH_ERR("Insufficient buffer memory on %s.\n", v4l->dev_name); + return -1; + } + + v4l->buffers = (struct buffer*)calloc(req.count, sizeof(*v4l->buffers)); + + if (!v4l->buffers) + { + RARCH_ERR("Out of memory allocating V4L2 buffers.\n"); + return -1; + } + + for (v4l->n_buffers = 0; v4l->n_buffers < req.count; ++v4l->n_buffers) + { + struct v4l2_buffer buf; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = v4l->n_buffers; + + if (xioctl(v4l->fd, VIDIOC_QUERYBUF, &buf) == -1) + { + RARCH_ERR("Error - xioctl VIDIOC_QUERYBUF.\n"); + return -1; + } + + v4l->buffers[v4l->n_buffers].length = buf.length; + v4l->buffers[v4l->n_buffers].start = mmap(NULL, + buf.length, PROT_READ | PROT_WRITE, + MAP_SHARED, + v4l->fd, buf.m.offset); + + if (v4l->buffers[v4l->n_buffers].start == MAP_FAILED) + { + RARCH_ERR("Error - mmap.\n"); + return -1; + } + } + + return 0; +} + +static int init_device(void *data) +{ + struct v4l2_capability cap; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + unsigned int min; + video4linux_t *v4l = (video4linux_t*)data; + + if (xioctl(v4l->fd, VIDIOC_QUERYCAP, &cap) == -1) + { + if (errno == EINVAL) + { + RARCH_ERR("%s is no V4L2 device.\n", v4l->dev_name); + return -1; + } + else + { + RARCH_ERR("Error - VIDIOC_QUERYCAP.\n"); + return -1; + } + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) + { + RARCH_ERR("%s is no video capture device.\n", v4l->dev_name); + return -1; + } + + switch (v4l->io) + { + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) + { + RARCH_ERR("%s does not support read I/O (V4L2_CAP_READWRITE).\n", v4l->dev_name); + return -1; + } + + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) + { + RARCH_ERR("%s does not support streaming I/O (V4L2_CAP_STREAMING).\n", v4l->dev_name); + return -1; + } + + break; + } + + /* Select video input, video standard and tune here. */ + + memset (&(cropcap), 0, sizeof (cropcap)); + + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (xioctl(v4l->fd, VIDIOC_CROPCAP, &cropcap) == 0) + { + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; /* reset to default */ + + if (xioctl(v4l->fd, VIDIOC_S_CROP, &crop) == -1) + { + switch (errno) + { + case EINVAL: + /* Cropping not supported. */ + break; + default: + /* Errors ignored. */ + break; + } + } + } + + memset (&fmt, 0, sizeof(fmt)); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = v4l->width; + fmt.fmt.pix.height = v4l->height; + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + + if (xioctl(v4l->fd, VIDIOC_S_FMT, &fmt) == -1) + { + RARCH_ERR("Error - VIDIOC_S_FMT\n"); + return -1; + } + + /* Note VIDIOC_S_FMT may change width and height. */ + + /* Buggy driver paranoia. */ + min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) + fmt.fmt.pix.bytesperline = min; + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) + fmt.fmt.pix.sizeimage = min; + + if (fmt.fmt.pix.width != v4l->width) + v4l->width = fmt.fmt.pix.width; + + if (fmt.fmt.pix.height != v4l->height) + v4l->height = fmt.fmt.pix.height; + + switch (v4l->io) + { + case IO_METHOD_READ: + return init_read(v4l, fmt.fmt.pix.sizeimage); + case IO_METHOD_MMAP: + return init_mmap(v4l); + case IO_METHOD_USERPTR: + //init_userp(fmt.fmt.pix.sizeimage); + break; + } + + return 0; +} + +static int v4l_stop(void *data) +{ + enum v4l2_buf_type type; + video4linux_t *v4l = (video4linux_t*)data; + + switch (v4l->io) + { + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (xioctl(v4l->fd, VIDIOC_STREAMOFF, &type) == -1) + { + RARCH_ERR("Error - VIDIOC_STREAMOFF.\n"); + return -1; + } + + break; + } + + v4l->ready = 0; + + return 0; +} + +static int v4l_start(void *data) +{ + video4linux_t *v4l = (video4linux_t*)data; + unsigned int i; + enum v4l2_buf_type type; + + switch (v4l->io) + { + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + for (i = 0; i < v4l->n_buffers; ++i) + { + struct v4l2_buffer buf; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (xioctl(v4l->fd, VIDIOC_QBUF, &buf) == -1) + { + RARCH_ERR("Error - VIDIOC_QBUF.\n"); + return -1; + } + } + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (xioctl(v4l->fd, VIDIOC_STREAMON, &type) == -1) + { + RARCH_ERR("Error - VIDIOC_STREAMON.\n"); + return -1; + } + + break; + + case IO_METHOD_USERPTR: + for (i = 0; i < v4l->n_buffers; ++i) + { + struct v4l2_buffer buf; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)v4l->buffers[i].start; + buf.length = v4l->buffers[i].length; + + if (xioctl(v4l->fd, VIDIOC_QBUF, &buf) == -1) + { + RARCH_ERR("Error - VIDIOC_QBUF.\n"); + return -1; + } + } + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (xioctl(v4l->fd, VIDIOC_STREAMON, &type) == -1) + { + RARCH_ERR("Error - VIDIOC_STREAMON.\n"); + return -1; + } + + break; + } + + v4l->ready = 1; + + return 0; +} + +static void *v4l_init(void) +{ + struct stat st; + video4linux_t *v4l = (video4linux_t*)calloc(1, sizeof(video4linux_t)); + if (!v4l) + return NULL; + + // FIXME - /dev/video0 assumed for now - should allow for selecting which device + v4l->dev_name = "/dev/video0"; + v4l->width = 640; + v4l->height = 480; + v4l->ready = 0; + + if (stat(v4l->dev_name, &st) == -1) + { + RARCH_ERR("Cannot identify '%s' : %d, %s\n", v4l->dev_name, errno, strerror(errno)); + goto error; + } + + if (!S_ISCHR(st.st_mode)) + { + RARCH_ERR("%s is no device.\n", v4l->dev_name); + goto error; + } + + v4l->fd = open(v4l->dev_name, O_RDWR | O_NONBLOCK, 0); + + if (v4l->fd == -1) + { + RARCH_ERR("Cannot open '%s': %d, %s\n", v4l->dev_name, errno, strerror(errno)); + goto error; + } + + v4l->io = IO_METHOD_MMAP; + + if (init_device(v4l) == -1) + goto error; + + generate_YCbCr_to_RGB_lookup(); + + return v4l; + +error: + RARCH_ERR("V4L2: Failed to initialize camera.\n"); + return NULL; +} + +static void v4l_free(void *data) +{ + video4linux_t *v4l = (video4linux_t*)data; + + unsigned int i; + + switch (v4l->io) + { + case IO_METHOD_READ: + free(v4l->buffers[0].start); + break; + case IO_METHOD_MMAP: + for (i = 0; i < v4l->n_buffers; ++i) + if (munmap(v4l->buffers[i].start, v4l->buffers[i].length) == -1) + { + RARCH_ERR("munmap failed.\n"); + return; + } + break; + case IO_METHOD_USERPTR: + for (i = 0; i < v4l->n_buffers; ++i) + free(v4l->buffers[i].start); + break; + } + + if (close(v4l->fd) == -1) + RARCH_ERR("close of file descriptor failed.\n"); + + v4l->fd = -1; +} + +static void preprocess_image(void *data) +{ + video4linux_t *v4l = (video4linux_t*)data; + struct v4l2_buffer buf; + unsigned int i; + + switch (v4l->io) + { + case IO_METHOD_READ: + if (read(v4l->fd, v4l->buffers[0].start, v4l->buffers[0].length) == -1) + { + switch (errno) + { + case EAGAIN: + return; + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + return; + } + } + + process_image(v4l->buffers[0].start); + + break; + + case IO_METHOD_MMAP: + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (xioctl(v4l->fd, VIDIOC_DQBUF, &buf) == -1) + { + switch (errno) + { + case EAGAIN: + return; + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + RARCH_ERR("VIDIOC_DQBUF.\n"); + return; + } + } + + assert(buf.index < v4l->n_buffers); + + process_image(v4l->buffers[buf.index].start); + + if (xioctl(v4l->fd, VIDIOC_QBUF, &buf) == -1) + { + RARCH_ERR("VIDIOC_QBUF\n"); + return; + } + + break; + + case IO_METHOD_USERPTR: + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + + if (xioctl(v4l->fd, VIDIOC_DQBUF, &buf) == -1) + { + switch (errno) + { + case EAGAIN: + return; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + RARCH_ERR("VIDIOC_DQBUF.\n"); + return; + } + } + + for (i = 0; i < v4l->n_buffers; ++i) + if (buf.m.userptr == (unsigned long)v4l->buffers[i].start + && buf.length == v4l->buffers[i].length) + break; + + assert(i < v4l->n_buffers); + + process_image((void *)buf.m.userptr); + + if (xioctl(v4l->fd, VIDIOC_QBUF, &buf) == -1) + RARCH_ERR("VIDIOC_QBUF.\n"); + + break; + } +} + +static void v4l_texture_image_2d(void *data) +{ + preprocess_image(data); +} + +static void v4l_texture_subimage_2d(void *data) +{ + preprocess_image(data); +} + +static int v4l_ready(void *data) +{ + video4linux_t *v4l = (video4linux_t*)data; + return v4l->ready; +} + +const camera_driver_t camera_v4l2 = { + v4l_init, + v4l_free, + v4l_start, + v4l_stop, + v4l_ready, + v4l_texture_image_2d, + v4l_texture_subimage_2d, + "video4linux2", +}; diff --git a/driver.h b/driver.h index 8ac160fc3f..688e5f422b 100644 --- a/driver.h +++ b/driver.h @@ -331,6 +331,20 @@ typedef struct input_driver const rarch_joypad_driver_t *(*get_joypad_driver)(void *data); } input_driver_t; +typedef struct camera_driver +{ + //FIXME - params for init - queries for resolution, framerate, color format + //which might or might not be honored + void *(*init)(void); + void (*free)(void *data); + int (*start)(void *data); + int (*stop)(void *data); + int (*ready)(void *data); + void (*texture_image_2d)(void *data); + void (*texture_subimage_2d)(void *data); + const char *ident; +} camera_driver_t; + struct rarch_viewport; #ifdef HAVE_OVERLAY @@ -554,6 +568,7 @@ extern const input_driver_t input_apple; extern const input_driver_t input_qnx; extern const input_driver_t input_rwebinput; extern const input_driver_t input_null; +extern const camera_driver_t camera_v4l2; #include "driver_funcs.h"