/*
* Simple V4L2 video viewer.
*
* This program can be used and distributed without restrictions.
*
* Generation (adjust to your libraries):
gcc -Wall svv.c -o svv $(pkg-config gtk+-2.0 libv4lconvert --cflags --libs)
*/
/* comment these lines if you have not */
#define WITH_V4L2_LIB 1 /* v4l library */
#define WITH_GTK 1 /* gtk+ */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h> /* getopt_long() */
#include <fcntl.h> /* low-level i/o */
#include <unistd.h>
#include <errno.h>
#include <malloc.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h> /* for videodev2.h */
#include <linux/videodev2.h>
#ifdef WITH_GTK
#include <gtk/gtk.h>
#endif
#ifdef WITH_V4L2_LIB
#include "libv4lconvert.h"
static struct v4lconvert_data *v4lconvert_data;
static struct v4l2_format src_fmt; /* raw format */
static unsigned char *dst_buf;
#endif
#define IO_METHOD_READ 7 /* !! must be != V4L2_MEMORY_MMAP / USERPTR */
static struct v4l2_format fmt; /* gtk pormat */
#define CLEAR(x) memset(&(x), 0, sizeof(x))
struct buffer {
void *start;
size_t length;
};
static char *dev_name = "/dev/video0";
static int io = V4L2_MEMORY_MMAP;
static int fd = -1;
static struct buffer *buffers;
static int n_buffers;
static int grab, info, raw;
#define NFRAMES 30
static struct timeval cur_time;
static void errno_exit(const char *s)
{
fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
#ifdef WITH_V4L2_LIB
fprintf(stderr, "%s\n",
v4lconvert_get_error_message(v4lconvert_data));
#endif
exit(EXIT_FAILURE);
}
#ifdef WITH_GTK
static int read_frame(void);
/* graphic functions */
static GtkWidget *drawing_area;
static void delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
gtk_main_quit();
}
static void key_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
unsigned int k;
k = event->key.keyval;
//printf("%08x %s\n", k, event->key.string);
if (k < 0xff) {
if (k == 'g') /* grab a raw image */
raw = grab = 1;
else
gtk_main_quit();
}
}
static void frame_ready(gpointer data,
gint source,
GdkInputCondition condition)
{
if (condition != GDK_INPUT_READ)
errno_exit("poll");
read_frame();
}
static int main_frontend(int argc, char *argv[])
{
GtkWidget *window;
gtk_init(&argc, &argv);
gdk_rgb_init();
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "My WebCam");
gtk_signal_connect(GTK_OBJECT(window), "delete_event",
GTK_SIGNAL_FUNC(delete_event), NULL);
gtk_signal_connect(GTK_OBJECT(window), "destroy",
GTK_SIGNAL_FUNC(delete_event), NULL);
gtk_signal_connect(GTK_OBJECT(window), "key_release_event",
GTK_SIGNAL_FUNC(key_event), NULL);
gtk_container_set_border_width(GTK_CONTAINER(window), 2);
drawing_area = gtk_drawing_area_new();
gtk_drawing_area_size(GTK_DRAWING_AREA(drawing_area),
fmt.fmt.pix.width, fmt.fmt.pix.height);
gtk_container_add(GTK_CONTAINER(window), drawing_area);
gtk_widget_show_all(window);
gdk_input_add(fd,
GDK_INPUT_READ,
frame_ready,
NULL);
gtk_main();
return 0;
}
#endif
static int xioctl(int fd, int request, void *arg)
{
int r;
do {
r = ioctl(fd, request, arg);
} while (r < 0 && EINTR == errno);
return r;
}
static void process_image(unsigned char *p, int len)
{
#ifdef WITH_V4L2_LIB
if (!raw) {
if (v4lconvert_convert(v4lconvert_data,
&src_fmt,
&fmt,
p, len,
dst_buf, fmt.fmt.pix.sizeimage) < 0) {
if (errno != EAGAIN)
errno_exit("v4l_convert");
return;
}
p = dst_buf;
len = fmt.fmt.pix.sizeimage;
}
#endif
if (grab) {
FILE *f;
f = fopen("image.dat", "w");
fwrite(p, 1, len, f);
fclose(f);
if (raw)
printf ("raw ");
printf("image dumped to 'image.dat'\n");
exit(EXIT_SUCCESS);
}
#ifdef WITH_GTK
gdk_draw_rgb_image(drawing_area->window,
drawing_area->style->white_gc,
0, 0, /* xpos, ypos */
fmt.fmt.pix.width, fmt.fmt.pix.height,
// GDK_RGB_DITHER_MAX,
GDK_RGB_DITHER_NORMAL,
p,
fmt.fmt.pix.width * 3);
#else
fputc('.', stdout);
#endif
if (info) {
if (--info <= 0) {
__time_t sec;
long int usec;
int d1, d2;
sec = cur_time.tv_sec;
usec = cur_time.tv_usec;
gettimeofday(&cur_time, 0);
d1 = cur_time.tv_sec - sec;
d2 = cur_time.tv_usec - usec;
while (d2 < 0) {
d2 += 1000000;
d1--;
}
printf("FPS: %5.2f\n",
(float) NFRAMES / (d1 + 0.0000001 * d2));
info = NFRAMES;
}
}
}
static int read_frame(void)
{
struct v4l2_buffer buf;
int i;
switch (io) {
case IO_METHOD_READ:
i = read(fd, buffers[0].start, buffers[0].length);
if (i < 0) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit("read");
}
}
process_image(buffers[0].start, i);
break;
case V4L2_MEMORY_MMAP:
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit("VIDIOC_DQBUF");
}
}
assert(buf.index < n_buffers);
process_image(buffers[buf.index].start, buf.bytesused);
if (xioctl(fd, VIDIOC_QBUF, &buf) < 0)
errno_exit("VIDIOC_QBUF");
break;
case V4L2_MEMORY_USERPTR:
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
if (xioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit("VIDIOC_DQBUF");
}
}
for (i = 0; i < n_buffers; ++i)
if (buf.m.userptr == (unsigned long) buffers[i].start
&& buf.length == buffers[i].length)
break;
assert(i < n_buffers);
process_image((unsigned char *) buf.m.userptr,
buf.bytesused);
if (xioctl(fd, VIDIOC_QBUF, &buf) < 0)
errno_exit("VIDIOC_QBUF");
break;
}
return 1;
}
static int get_frame()
{
fd_set fds;
struct timeval tv;
int r;
FD_ZERO(&fds);
FD_SET(fd, &fds);
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
r = select(fd + 1, &fds, NULL, NULL, &tv);
if (r < 0) {
if (EINTR == errno)
return 0;
errno_exit("select");
}
if (0 == r) {
fprintf(stderr, "select timeout\n");
exit(EXIT_FAILURE);
}
return read_frame();
}
#ifndef WITH_GTK
static void mainloop(void)
{
int count;
// count = 20;
count = 10000;
while (--count >= 0) {
for (;;) {
if (get_frame())
break;
}
}
}
#endif
static void stop_capturing(void)
{
enum v4l2_buf_type type;
switch (io) {
case IO_METHOD_READ:
/* Nothing to do. */
break;
case V4L2_MEMORY_MMAP:
case V4L2_MEMORY_USERPTR:
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
errno_exit("VIDIOC_STREAMOFF");
break;
}
}
static void start_capturing(void)
{
int i;
enum v4l2_buf_type type;
switch (io) {
case IO_METHOD_READ:
printf("read method\n");
/* Nothing to do. */
break;
case V4L2_MEMORY_MMAP:
printf("mmap method\n");
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QBUF, &buf) < 0)
errno_exit("VIDIOC_QBUF");
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_STREAMON, &type) < 0)
errno_exit("VIDIOC_STREAMON");
break;
case V4L2_MEMORY_USERPTR:
printf("userptr method\n");
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
buf.index = i;
buf.m.userptr = (unsigned long) buffers[i].start;
buf.length = buffers[i].length;
if (xioctl(fd, VIDIOC_QBUF, &buf) < 0)