blob: 112d86b63123cd689ba294700bfec10eed19f237 [file] [log] [blame]
/* video grab for linux ... uses the original v4l
*/
/*
This file is part of VIPS.
VIPS 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#ifdef HAVE_VIDEODEV
/* Lots of debugging output.
#define DEBUG
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /*HAVE_UNISTD_H*/
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <linux/types.h>
#include <linux/videodev.h>
#include <vips/vips.h>
#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/
/* Zero freed mem to help catch stray pointers.
*/
#ifdef NDEBUG
#define FREE( S ) { if( S ) { (void) im_free( (char *) (S) ); \
(char *) (S) = NULL; } }
#else
#define FREE( S ) { if( S ) { memset( (char *)(S), 0, sizeof( *(S) ) ); \
(void) im_free( (char *) (S) ); (S) = NULL; } }
#endif /*NDEBUG*/
#define FREEF( F, S ) { if( S ) { (void) F( S ); (S) = NULL; } }
#define FREEFI( F, S ) { if( S ) { (void) F( S ); (S) = 0; } }
#define SETSTR( S, V ) \
{ const char *sst = (V); FREE( S ); (S) = im_strdup( NULL, sst ); }
/* Max channels on a device.
*/
#define IM_MAXCHANNELS (10)
/* Video input sources, e.g. tuner, svideo, composite.
*/
#define TUNER (0)
#define COMPOSITE (1)
#define SVIDEO (2)
typedef struct lgrab {
/* Mmap here, plus file descriptor.
*/
char *device;
char *capture_buffer;
int capture_size;
int fd;
/* Current settings.
*/
int c_channel;
int c_width;
int c_height;
int c_ngrabs;
/* Extract capabilities here.
*/
struct video_capability capability;
struct video_channel channel[IM_MAXCHANNELS];
struct video_window window;
struct video_picture picture;
struct video_mbuf mbuf;
struct video_mmap mmap;
} LGrab;
#ifdef DEBUG
/* Decode various things ... capability bits etc.
*/
typedef struct {
int value;
const char *name;
const char *description;
} Decode;
static const Decode decode_palette[] = {
{ VIDEO_PALETTE_GREY,
"VIDEO_PALETTE_GREY", "Linear greyscale" },
{ VIDEO_PALETTE_HI240,
"VIDEO_PALETTE_HI240", "High 240 cube (BT848)" },
{ VIDEO_PALETTE_RGB565,
"VIDEO_PALETTE_RGB565", "565 16 bit RGB" },
{ VIDEO_PALETTE_RGB24,
"VIDEO_PALETTE_RGB24", "24bit RGB" },
{ VIDEO_PALETTE_RGB32,
"VIDEO_PALETTE_RGB32", "32bit RGB" },
{ VIDEO_PALETTE_RGB555,
"VIDEO_PALETTE_RGB555", "555 15bit RGB" },
{ VIDEO_PALETTE_YUV422,
"VIDEO_PALETTE_YUV422", "YUV422 capture" },
{ VIDEO_PALETTE_YUYV,
"VIDEO_PALETTE_YUYV", "" },
{ VIDEO_PALETTE_UYVY,
"VIDEO_PALETTE_UYVY", "" },
{ VIDEO_PALETTE_YUV420,
"VIDEO_PALETTE_YUV420", "" },
{ VIDEO_PALETTE_YUV411,
"VIDEO_PALETTE_YUV411", "YUV411 capture" },
{ VIDEO_PALETTE_RAW,
"VIDEO_PALETTE_RAW", "RAW capture (BT848)" },
{ VIDEO_PALETTE_YUV422P,
"VIDEO_PALETTE_YUV422P", "YUV 4:2:2 Planar" },
{ VIDEO_PALETTE_YUV411P,
"VIDEO_PALETTE_YUV411P", "YUV 4:1:1 Planar" },
{ VIDEO_PALETTE_YUV420P,
"VIDEO_PALETTE_YUV420P", "YUV 4:2:0 Planar" },
{ VIDEO_PALETTE_YUV410P,
"VIDEO_PALETTE_YUV410P", "YUV 4:1:0 Planar" }
};
static const Decode decode_type[] = {
{ VIDEO_TYPE_TV,
"VIDEO_TYPE_TV", "TV" },
{ VIDEO_TYPE_CAMERA,
"VIDEO_TYPE_CAMERA", "Camera" },
};
static const Decode decode_vtype[] = {
{ VID_TYPE_CAPTURE,
"VID_TYPE_CAPTURE", "Can capture to memory" },
{ VID_TYPE_TUNER,
"VID_TYPE_TUNER", "Has Tuner" },
{ VID_TYPE_TELETEXT,
"VID_TYPE_TELETEXT", "Has Teletext" },
{ VID_TYPE_OVERLAY,
"VID_TYPE_OVERLAY", "Chromakeyed overlay" },
{ VID_TYPE_CLIPPING,
"VID_TYPE_CLIPPING", "Overlay clipping" },
{ VID_TYPE_FRAMERAM,
"VID_TYPE_FRAMERAM", "Overlay overwrites frame buffer memory" },
{ VID_TYPE_SCALES,
"VID_TYPE_SCALES", "Hardware supports image scaling" },
{ VID_TYPE_MONOCHROME,
"VID_TYPE_MONOCHROME", "Image capture is grey scale only" },
{ VID_TYPE_SUBCAPTURE,
"VID_TYPE_SUBCAPTURE", "Capture sub-image" },
{ VID_TYPE_MPEG_DECODER,
"VID_TYPE_MPEG_DECODER", "Can decode MPEG streams" },
{ VID_TYPE_MPEG_ENCODER,
"VID_TYPE_MPEG_ENCODER", "Can encode MPEG streams" },
{ VID_TYPE_MJPEG_DECODER,
"VID_TYPE_MJPEG_DECODER", "Can decode MJPEG streams" },
{ VID_TYPE_MJPEG_ENCODER,
"VID_TYPE_MJPEG_ENCODER", "Can encode MJPEG streams" }
};
static const Decode decode_ctype[] = {
{ VIDEO_VC_TUNER,
"VIDEO_VC_TUNER", "Has tuner" },
{ VIDEO_VC_AUDIO,
"VIDEO_VC_AUDIO", "Has audio" }
};
/* Prettyprint a value.
*/
static void
decode_print( const Decode *decode, int ndecode, int value )
{
int i;
for( i = 0; i < ndecode; i++ )
if( decode[i].value == value ) {
printf( "%s, %s",
decode[i].name, decode[i].description );
return;
}
printf( "unknown (%p)", value );
}
/* Prettyprint a set of flags.
*/
static void
decode_print_flags( const Decode *decode, int ndecode, int flags )
{
int i;
printf( "0x%x ", (unsigned int) flags );
for( i = 0; i < ndecode; i++ )
if( decode[i].value & flags ) {
printf( "[" );
decode_print( decode, ndecode,
decode[i].value & flags );
printf( "] " );
flags &= -1 ^ decode[i].value;
}
if( flags )
printf( "[unknown extra flags 0x%x]", (unsigned int) flags );
}
#endif /*DEBUG*/
static int
lgrab_ioctl( LGrab *lg, int request, void *argp )
{
if( !lg->fd ) {
im_error( "lgrab_ioctl", "%s", _( "no file descriptor" ) );
return( -1 );
}
if( ioctl( lg->fd, request, argp ) < 0 ) {
im_error( "lgrab_ioctl", _( "ioctl(0x%x) failed: %s" ),
(unsigned int) request, strerror( errno ) );
return( -1 );
}
return( 0 );
}
static void
lgrab_destroy( LGrab *lg )
{
if( lg->fd != -1 ) {
int zero = 0;
(void) lgrab_ioctl( lg, VIDIOCCAPTURE, &zero );
close( lg->fd );
lg->fd = -1;
}
if( lg->capture_buffer ) {
munmap( lg->capture_buffer, lg->capture_size );
lg->capture_buffer = NULL;
}
FREE( lg->device );
FREE( lg );
}
static LGrab *
lgrab_new( const char *device )
{
LGrab *lg = IM_NEW( NULL, LGrab );
int i;
if( !lg )
return( NULL );
lg->device = NULL;
lg->capture_buffer = NULL;
lg->capture_size = 0;
lg->fd = -1;
lg->c_channel = -1;
lg->c_width = -1;
lg->c_height = -1;
lg->c_ngrabs = 1;
SETSTR( lg->device, device );
if( !lg->device || (lg->fd = open( lg->device, O_RDWR )) == -1 ) {
im_error( "lgrab_new", _( "cannot open video device \"%s\"" ),
lg->device );
lgrab_destroy( lg );
return( NULL );
}
if( lgrab_ioctl( lg, VIDIOCGCAP, &lg->capability ) ) {
im_error( "lgrab_new",
"%s", _( "cannot get video capability" ) );
lgrab_destroy( lg );
return( NULL );
}
/* Check that it can capture to memory.
*/
if( !(lg->capability.type & VID_TYPE_CAPTURE) ) {
im_error( "lgrab_new",
"%s", _( "card cannot capture to memory" ) );
lgrab_destroy( lg );
return( NULL );
}
/* Read channel info.
*/
for( i = 0; i < IM_MIN( lg->capability.channels, IM_MAXCHANNELS );
i++ ) {
lg->channel[i].channel = i;
if( lgrab_ioctl( lg, VIDIOCGCHAN, &lg->channel[i] ) ) {
lgrab_destroy( lg );
return( NULL );
}
}
/* Get other props.
*/
if( lgrab_ioctl( lg, VIDIOCGWIN, &lg->window) ||
lgrab_ioctl( lg, VIDIOCGPICT, &lg->picture) ) {
lgrab_destroy( lg );
return( NULL );
}
/* Set 24 bit mode.
*/
lg->picture.depth = 24;
lg->picture.palette = VIDEO_PALETTE_RGB24;
if( lgrab_ioctl( lg, VIDIOCSPICT, &lg->picture ) ) {
lgrab_destroy( lg );
return( NULL );
}
return( lg );
}
#ifdef DEBUG
static void
lgrab_dump_capability( struct video_capability *capability )
{
printf( "capability->name = \"%s\"\n", capability->name );
printf( "capability->channels = %d\n", capability->channels );
printf( "capability->audios = %d\n", capability->audios );
printf( "capability->maxwidth = %d\n", capability->maxwidth );
printf( "capability->maxheight = %d\n", capability->maxheight );
printf( "capability->minwidth = %d\n", capability->maxwidth );
printf( "capability->minheight = %d\n", capability->maxheight );
printf( "capability->type = " );
decode_print_flags( decode_vtype, IM_NUMBER( decode_vtype ),
capability->type );
printf( "\n" );
}
static void
lgrab_dump_channel( struct video_channel *channel )
{
printf( "channel->channel = %d\n", channel->channel );
printf( "channel->name = \"%s\"\n", channel->name );
printf( "channel->tuners = %d\n", channel->tuners );
printf( "channel->flags = " );
decode_print_flags( decode_ctype, IM_NUMBER( decode_ctype ),
channel->flags );
printf( "\n" );
printf( "channel->type = " );
decode_print( decode_type, IM_NUMBER( decode_type ),
channel->type );
printf( "\n" );
printf( "channel->norm = %d\n", channel->norm );
}
static void
lgrab_dump_picture( struct video_picture *picture )
{
printf( "picture->brightness = %d\n", picture->brightness );
printf( "picture->hue = %d\n", picture->hue );
printf( "picture->colour = %d\n", picture->colour );
printf( "picture->contrast = %d\n", picture->contrast );
printf( "picture->whiteness = %d\n", picture->whiteness );
printf( "picture->depth = %d\n", picture->depth );
printf( "picture->palette = " );
decode_print( decode_palette, IM_NUMBER( decode_palette ),
picture->palette );
printf( "\n" );
}
static void
lgrab_dump( LGrab *lg )
{
int i;
printf( "lg->device = \"%s\"\n", lg->device );
printf( "lg->capture_buffer = %p\n",
lg->capture_buffer );
printf( "lg->capture_size = 0x%x\n",
(unsigned int) lg->capture_size );
printf( "lg->fd = %d\n", lg->fd );
printf( "lg->c_channel = %d\n", lg->c_channel );
printf( "lg->c_width = %d\n", lg->c_width );
printf( "lg->c_height = %d\n", lg->c_height );
printf( "lg->c_ngrabs = %d\n", lg->c_ngrabs );
lgrab_dump_capability( &lg->capability );
for( i = 0; i < lg->capability.channels; i++ )
lgrab_dump_channel( &lg->channel[i] );
lgrab_dump_picture( &lg->picture );
printf( "mbuf->size = 0x%x\n", (unsigned int) lg->mbuf.size );
printf( "mbuf->frames = %d\n", lg->mbuf.frames );
printf( "mbuf->offsets = " );
for( i = 0; i < lg->mbuf.frames; i++ )
printf( "0x%x ", (unsigned int) lg->mbuf.offsets[i] );
printf( "\n" );
}
#endif /*DEBUG*/
static int
lgrab_set_capture_size( LGrab *lg, int width, int height )
{
lg->c_width = width;
lg->c_height = height;
lg->window.clipcount = 0;
lg->window.flags = 0;
lg->window.x = 0;
lg->window.y = 0;
lg->window.width = width;
lg->window.height = height;
if( lgrab_ioctl( lg, VIDIOCSWIN, &lg->window ) )
return( -1 );
/* Make sure the correct amount of memory is mapped.
*/
if( lgrab_ioctl( lg, VIDIOCGMBUF, &lg->mbuf ) )
return( -1 );
if( lg->capture_buffer ) {
munmap( lg->capture_buffer, lg->capture_size );
lg->capture_buffer = NULL;
}
lg->capture_size = lg->mbuf.size;
if( !(lg->capture_buffer = mmap( 0, lg->capture_size,
PROT_READ | PROT_WRITE, MAP_SHARED, lg->fd, 0 )) ) {
im_error( "lgrab_set_capture_size",
"%s", _( "unable to map memory" ) );
return( -1 );
}
return( 0 );
}
static int
lgrab_set_channel( LGrab *lg, int channel )
{
if( channel < 0 || channel >= lg->capability.channels ) {
im_error( "lgrab_set_channel",
_( "channel not between 0 and %d" ),
lg->capability.channels - 1 );
return( -1 );
}
if( lgrab_ioctl( lg, VIDIOCSCHAN, &lg->channel[channel] ) )
return( -1 );
lg->c_channel = channel;
return( 0 );
}
static int
lgrab_set_brightness( LGrab *lg, int brightness )
{
lg->picture.brightness = IM_CLIP( 0, brightness, 65535 );
if( lgrab_ioctl( lg, VIDIOCSPICT, &lg->picture ) )
return( -1 );
return( 0 );
}
static int
lgrab_set_colour( LGrab *lg, int colour )
{
lg->picture.colour = IM_CLIP( 0, colour, 65535 );
if( lgrab_ioctl( lg, VIDIOCSPICT, &lg->picture ) )
return( -1 );
return( 0 );
}
static int
lgrab_set_contrast( LGrab *lg, int contrast )
{
lg->picture.contrast = IM_CLIP( 0, contrast, 65535 );
if( lgrab_ioctl( lg, VIDIOCSPICT, &lg->picture ) )
return( -1 );
return( 0 );
}
static int
lgrab_set_hue( LGrab *lg, int hue )
{
lg->picture.hue = IM_CLIP( 0, hue, 65535 );
if( lgrab_ioctl( lg, VIDIOCSPICT, &lg->picture ) )
return( -1 );
return( 0 );
}
static int
lgrab_set_ngrabs( LGrab *lg, int ngrabs )
{
lg->c_ngrabs = IM_CLIP( 1, ngrabs, 1000 );
return( 0 );
}
/* Grab a single frame.
*/
static int
lgrab_capture1( LGrab *lg )
{
lg->mmap.format = lg->picture.palette;
lg->mmap.frame = 0;
lg->mmap.width = lg->c_width;
lg->mmap.height = lg->c_height;
if( lgrab_ioctl( lg, VIDIOCMCAPTURE, &lg->mmap ) ||
lgrab_ioctl( lg, VIDIOCSYNC, &lg->mmap.frame ) )
return( -1 );
return( 0 );
}
/* Grab and average many frames.
*/
static int
lgrab_capturen( LGrab *lg )
{
if( lg->c_ngrabs == 1 ) {
if( lgrab_capture1( lg ) )
return( -1 );
}
else {
int i, j;
int npx = lg->c_width * lg->c_height * 3;
unsigned int *acc;
if( !(acc = IM_ARRAY( NULL, npx, unsigned int )) )
return( -1 );
memset( acc, 0, npx * sizeof( unsigned int ) );
for( i = 0; i < lg->c_ngrabs; i++ ) {
if( lgrab_capture1( lg ) ) {
FREE( acc );
return( -1 );
}
for( j = 0; j < npx; j++ )
acc[j] += (unsigned char) lg->capture_buffer[j];
}
for( j = 0; j < npx; j++ ) {
int avg = (acc[j] + lg->c_ngrabs / 2) / lg->c_ngrabs;
lg->capture_buffer[j] = IM_CLIP( 0, avg, 255 );
}
FREE( acc );
}
return( 0 );
}
static int
lgrab_capture( LGrab *lg, IMAGE *im )
{
int x, y;
unsigned char *line;
if( lgrab_capturen( lg ) )
return( -1 );
if( im_outcheck( im ) )
return( -1 );
im_initdesc( im, lg->c_width, lg->c_height, 3,
IM_BBITS_BYTE, IM_BANDFMT_UCHAR,
IM_CODING_NONE, IM_TYPE_MULTIBAND, 1.0, 1.0, 0, 0 );
if( im_setupout( im ) )
return( -1 );
if( !(line = IM_ARRAY( im,
IM_IMAGE_SIZEOF_LINE( im ), unsigned char )) )
return( -1 );
for( y = 0; y < lg->c_height; y++ ) {
unsigned char *p = (unsigned char *) lg->capture_buffer +
y * IM_IMAGE_SIZEOF_LINE( im );
unsigned char *q = line;
for( x = 0; x < lg->c_width; x++ ) {
q[0] = p[2];
q[1] = p[1];
q[2] = p[0];
p += 3;
q += 3;
}
if( im_writeline( y, im, line ) )
return( -1 );
}
return( 0 );
}
int
im_video_v4l1( IMAGE *im, const char *device,
int channel, int brightness, int colour, int contrast, int hue,
int ngrabs )
{
LGrab *lg;
if( !(lg = lgrab_new( device )) )
return( -1 );
if( lgrab_set_capture_size( lg,
lg->capability.maxwidth, lg->capability.maxheight ) ||
lgrab_set_channel( lg, channel ) ||
lgrab_set_brightness( lg, brightness ) ||
lgrab_set_colour( lg, colour ) ||
lgrab_set_contrast( lg, contrast ) ||
lgrab_set_hue( lg, hue ) ||
lgrab_set_ngrabs( lg, ngrabs ) ||
lgrab_capture( lg, im ) ) {
lgrab_destroy( lg );
return( -1 );
}
#ifdef DEBUG
printf( "Successful capture with:\n" );
lgrab_dump( lg );
#endif /*DEBUG*/
lgrab_destroy( lg );
return( 0 );
}
#else /*!HAVE_VIDEODEV*/
#include <vips/vips.h>
int
im_video_v4l1( IMAGE *im, const char *device,
int channel, int brightness, int colour, int contrast, int hue,
int ngrabs )
{
im_error( "im_video_v4l1",
"%s", _( "compiled without im_video_v4l1 support" ) );
return( -1 );
}
#endif /*HAVE_VIDEODEV*/