[MPlayer-dev-eng] [PATCH] v4l2 interface

Martin Olschewski olschewski at zpr.uni-koeln.de
Mon Jan 13 14:34:09 CET 2003


Hello,

I am using a TV-card with a Philips saa7134 chip.  Gerd Knorr's
v4l2 driver is afaik the only available driver for this card.
Since mplayer has a v4l interface only, I had to add a new
interface.  The very first try was based on the 0.1.x version
of the v4l2 driver but many changes have been necessary to
make it work with the 0.2.x version.

This patch brings to you what I am using for a couple of weeks
now.  The attached tvi_v4l2.c has to be placed in libmpdemux/.
The command lind parameters should be the same as for v4l, i.e.
mencoder -tv on:driver=v4l2:adevice=/dev/dsp:...
Since the v4l2 driver supports oss only, there is no support
for alsa.

If somebody is interessed in this interface I would be glad to
hear from you, especially if you use it with an other tv-card
than the Tevion MD 9717 that I have.  Please be award that the
interface (like the whole mplayer project ;-) is still under
heavy development.

Have fun,

  Martin

-- 
Dipl.-Inform. Martin Olschewski 	Center for Applied Computer Science
Email: olschewski at zpr.uni-koeln.de	University of Cologne
Tel.:  ++49/221/470-6018		Weyertal 80
Fax.:  ++49/221/470-5160		D-50931 K\"oln
-------------- next part --------------
Index: configure
===================================================================
RCS file: /cvsroot/mplayer/main/configure,v
retrieving revision 1.635
diff -u -r1.635 configure
--- configure	9 Jan 2003 19:01:24 -0000	1.635
+++ configure	12 Jan 2003 23:23:06 -0000
@@ -149,6 +149,7 @@
   --enable-joystick      enable joystick support [disable]
   --disable-tv           disable TV Interface (tv/dvb grabbers) [enable]
   --disable-tv-v4l       disable Video4Linux TV Interface support [autodetect]
+  --disable-tv-v4l2      disable Video4Linux2 TV Interface support [autodetect]
   --disable-tv-bsdbt848  disable BSD BT848 Interface support [autodetect]
   --disable-edl          disable EDL (edit decision list) support [enable]
   --disable-rtc          disable RTC (/dev/rtc) on Linux [autodetect]
@@ -1016,6 +1017,7 @@
 _select=yes
 _tv=yes
 _tv_v4l=auto
+_tv_v4l2=auto
 _tv_bsdbt848=auto
 _edl=yes
 _streaming=yes
@@ -1178,7 +1180,8 @@
   --enable-tv-bsdbt848)	_tv_bsdbt848=yes	;;
   --disable-tv-bsdbt848)	_tv_bsdbt848=no	;;
   --enable-tv-v4l)	_tv_v4l=yes	;;
-  --disable-tv-v4l)	_tv_v4l=no	;;
+  --enable-tv-v4l2)	_tv_v4l2=yes	;;
+  --disable-tv-v4l2)	_tv_v4l2=no	;;
   --enable-fastmemcpy)	_fastmemcpy=yes	;;
   --disable-fastmemcpy)	_fastmemcpy=no	;;
   --enable-streaming)	_streaming=yes	;;
@@ -4370,6 +4373,34 @@
 echores "$_tv_v4l"
 
 
+echocheck "Video 4 Linux 2 TV interface"
+if test "$_tv_v4l2" = auto ; then
+ _tv_v4l2=no
+ if test "$_tv" = yes && linux ; then
+  for I in /dev/video /dev/video? ; do
+    if test -c $I ; then
+      cat > $TMPC <<EOF
+#include <stdlib.h>
+#include <linux/videodev.h>
+#include <linux/videodev2.h>
+int main(void) { return 0; }
+EOF
+      cc_check && _tv_v4l2=yes
+      break
+    fi
+  done
+ fi
+fi
+if test "$_tv_v4l2" = yes ; then
+  _def_tv_v4l2='#define HAVE_TV_V4L2 1'
+  _inputmodules="tv-v4l2 $_inputmodules"
+else
+  _noinputmodules="tv-v4l2 $_noinputmodules"
+  _def_tv_v4l='#undef HAVE_TV_V4L2'
+fi
+echores "$_tv_v4l2"
+
+
 echocheck "audio select()"
 if test "$_select" = no ; then
   _def_select='#undef HAVE_AUDIO_SELECT'
@@ -5145,6 +5176,9 @@
 
 /* Enable Video 4 Linux TV interface support */
 $_def_tv_v4l
+
+/* Enable Video 4 Linux 2 TV interface support */
+$_def_tv_v4l2
 
 /* Enable *BSD BrookTree TV interface support */
 $_def_tv_bsdbt848
Index: libmpdemux/Makefile
===================================================================
RCS file: /cvsroot/mplayer/main/libmpdemux/Makefile,v
retrieving revision 1.47
diff -u -r1.47 Makefile
--- libmpdemux/Makefile	5 Jan 2003 23:51:05 -0000	1.47
+++ libmpdemux/Makefile	12 Jan 2003 23:23:06 -0000
@@ -3,7 +3,7 @@
 
 include ../config.mak
 
-SRCS = mp3_hdr.c video.c mpeg_hdr.c cache2.c asfheader.c aviheader.c aviprint.c muxer.c muxer_avi.c muxer_mpeg.c demux_asf.c demux_avi.c demux_mov.c parse_mp4.c demux_mpg.c demux_pva.c demux_viv.c demuxer.c dvdauth.c dvdnav_stream.c open.c parse_es.c stream.c tv.c tvi_dummy.c tvi_v4l.c tvi_bsdbt848.c frequencies.c demux_fli.c demux_real.c demux_y4m.c yuv4mpeg.c yuv4mpeg_ratio.c demux_nuv.c demux_film.c demux_roq.c mf.c demux_mf.c demux_audio.c demux_demuxers.c demux_ogg.c demux_bmp.c cdda.c demux_rawaudio.c cddb.c cdinfo.c demux_rawdv.c ai_alsa.c ai_oss.c audio_in.c demux_smjpeg.c cue_read.c
+SRCS = mp3_hdr.c video.c mpeg_hdr.c cache2.c asfheader.c aviheader.c aviprint.c muxer.c muxer_avi.c muxer_mpeg.c demux_asf.c demux_avi.c demux_mov.c parse_mp4.c demux_mpg.c demux_pva.c demux_viv.c demuxer.c dvdauth.c dvdnav_stream.c open.c parse_es.c stream.c tv.c tvi_dummy.c tvi_v4l.c tvi_v4l2.c tvi_bsdbt848.c frequencies.c demux_fli.c demux_real.c demux_y4m.c yuv4mpeg.c yuv4mpeg_ratio.c demux_nuv.c demux_film.c demux_roq.c mf.c demux_mf.c demux_audio.c demux_demuxers.c demux_ogg.c demux_bmp.c cdda.c demux_rawaudio.c cddb.c cdinfo.c demux_rawdv.c ai_alsa.c ai_oss.c audio_in.c demux_smjpeg.c cue_read.c
 ifeq ($(XMMS_PLUGINS),yes)
 SRCS += demux_xmms.c
 endif 
-------------- next part --------------
/*
**  Video 4 Linux 2 input
**
**  This file is part of MPlayer, see http://mplayerhq.hu/ for info.  
**
**  (c) 2003 Martin Olschewski <olschewski at zpr.uni-koeln.de>
**  
**  File licensed under the GPL, see http://www.fsf.org/ for more info.
**
**  Some ideas are based on works from
**    Alex Beregszaszi <alex at naxine.org>
**    Jindrich Makovicka <makovick at kmlinux.fjfi.cvut.cz>
**    Gerd Knorr <kraxel at bytesex.org>
**
**  CODE IS UNDER DEVELOPMENT, NO FEATURE REQUESTS PLEASE!
*/

#include "config.h"

#if defined(USE_TV) && defined(HAVE_TV_V4L2)

#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/videodev.h>
#include "../mp_msg.h"
#include "../libvo/img_format.h"
#include "tv.h"

#ifdef HAVE_SYS_SOUNDCARD_H
#include <sys/soundcard.h>
#else
#ifdef HAVE_SOUNDCARD_H
#include <soundcard.h>
#else
#include <machine/soundcard.h>
#endif /* HAVE_SOUNDCARD_H */
#endif /* HAVE_SYS_SOUNDCARD_H */


/* enough video buffer for 3 seconds PAL */
#define VIDEO_BUFFERS (75)


/* information about this file */
static tvi_info_t info = {
    "Video 4 Linux 2 input",
    "v4l2",
    "Martin Olschewski <olschewski at zpr.uni-koeln.de>",
    "first try, more to come ;-)"
};

struct map {
    struct v4l2_buffer buf;
    void   *addr;
    size_t len;
};

struct audio_frame {
    struct audio_frame	*next;
    char		data;
};

/* private data */
typedef struct {
    /* video */
    char			*video_dev;
    int				video_fd;
    struct v4l2_capability	capability;
    struct v4l2_format		format;
    struct v4l2_standard	standard;
    struct v4l2_tuner		tuner;
    struct map			*map;
    int				mapcount;
    int				frames;
    struct timeval		first_frame;
    struct timeval		curr_frame;
    /* audio video interleaving ;-) */
    volatile int		streamon;
    volatile struct audio_frame	*audio_list;
    volatile unsigned int	listsize;
    unsigned int		maxlist;
    pthread_t			audio_thread;
    pthread_mutex_t		audio_mutex;
    /* audio */
    char			*audio_dev;		/* /dev/dsp	*/
    int				audio_fd;
    int				dsp_speed;		/* 44100	*/
    int				dsp_channels;		/* 2		*/
    int				dsp_format;		/* AFMT_S16_LE	*/
    int				dsp_framesize;		/* 7056 (PAL)	*/
    int				dsp_frames;
} priv_t;

#include "tvi_def.h"


/**********************************************************************\

    Only few of the fourccs are the same in v4l2 and mplayer:

    IMGFMT_YVU9 == V4L2_PIX_FMT_YVU410
    IMGFMT_YV12 == V4L2_PIX_FMT_YVU420
    IMGFMT_NV12 == V4L2_PIX_FMT_NV12
    IMGFMT_422P == V4L2_PIX_FMT_YUV422P
    IMGFMT_411P == V4L2_PIX_FMT_YUV411P
    IMGFMT_UYVY == V4L2_PIX_FMT_UYVY
    IMGFMT_Y41P == V4L2_PIX_FMT_Y41P

    This may be an useful translation table for some others:

    IMGFMT_RGB8  == V4L2_PIX_FMT_RGB332
    IMGFMT_RGB15 == V4L2_PIX_FMT_RGB555
    IMGFMT_RGB16 == V4L2_PIX_FMT_RGB565
    IMGFMT_RGB24 == V4L2_PIX_FMT_RGB24
    IMGFMT_RGB32 == V4L2_PIX_FMT_RGB32
    IMGFMT_BGR24 == V4L2_PIX_FMT_BGR24
    IMGFMT_BGR32 == V4L2_PIX_FMT_BGR32
    IMGFMT_Y800  == V4L2_PIX_FMT_GREY
    IMGFMT_IF09  == V4L2_PIX_FMT_YUV410
    IMGFMT_I420  == V4L2_PIX_FMT_YUV420
    IMGFMT_YUY2  == V4L2_PIX_FMT_YUYV

\**********************************************************************/

/*
** Translate a mplayer fourcc to a video4linux2 pixel format.
*/
static int fcc_mp2vl(int fcc)
{
    switch (fcc) {
	case IMGFMT_RGB8:	return V4L2_PIX_FMT_RGB332;
	case IMGFMT_RGB15:	return V4L2_PIX_FMT_RGB555;
	case IMGFMT_RGB16:	return V4L2_PIX_FMT_RGB565;
	case IMGFMT_RGB24:	return V4L2_PIX_FMT_RGB24;
	case IMGFMT_RGB32:	return V4L2_PIX_FMT_RGB32;
	case IMGFMT_BGR24:	return V4L2_PIX_FMT_BGR24;
	case IMGFMT_BGR32:	return V4L2_PIX_FMT_BGR32;
	case IMGFMT_Y800:	return V4L2_PIX_FMT_GREY;
	case IMGFMT_IF09:	return V4L2_PIX_FMT_YUV410;
	case IMGFMT_I420:	return V4L2_PIX_FMT_YUV420;
	case IMGFMT_YUY2:	return V4L2_PIX_FMT_YUYV;
    }
    return fcc;
}

/*
** Translate a video4linux2 fourcc aka pixel format to mplayer.
*/
static int fcc_vl2mp(int fcc)
{
    switch (fcc) {
	case V4L2_PIX_FMT_RGB332:	return IMGFMT_RGB8;
	case V4L2_PIX_FMT_RGB555:	return IMGFMT_RGB15;
	case V4L2_PIX_FMT_RGB565:	return IMGFMT_RGB16;
	case V4L2_PIX_FMT_RGB24:	return IMGFMT_RGB24;
	case V4L2_PIX_FMT_RGB32:	return IMGFMT_RGB32;
	case V4L2_PIX_FMT_BGR24:	return IMGFMT_BGR24;
	case V4L2_PIX_FMT_BGR32:	return IMGFMT_BGR32;
	case V4L2_PIX_FMT_GREY:		return IMGFMT_Y800;
	case V4L2_PIX_FMT_YUV410:	return IMGFMT_IF09;
	case V4L2_PIX_FMT_YUV420:	return IMGFMT_I420;
	case V4L2_PIX_FMT_YUYV:		return IMGFMT_YUY2;
    }
    return fcc;
}

/*
** Translate a video4linux2 fourcc aka pixel format
** to a human readable string.
*/
static char *pixfmt2name(int pixfmt)
{
    static char unknown[24];

    switch (pixfmt) {
	case V4L2_PIX_FMT_RGB332:	return "RGB332";
	case V4L2_PIX_FMT_RGB555:	return "RGB555";
	case V4L2_PIX_FMT_RGB565:	return "RGB565";
	case V4L2_PIX_FMT_RGB555X:	return "RGB555X";
	case V4L2_PIX_FMT_RGB565X:	return "RGB565X";
	case V4L2_PIX_FMT_BGR24:	return "BGR24";
	case V4L2_PIX_FMT_RGB24:	return "RGB24";
	case V4L2_PIX_FMT_BGR32:	return "BGR32";
	case V4L2_PIX_FMT_RGB32:	return "RGB32";
	case V4L2_PIX_FMT_GREY:		return "GREY";
	case V4L2_PIX_FMT_YVU410:	return "YVU410";
	case V4L2_PIX_FMT_YVU420:	return "YVU420";
	case V4L2_PIX_FMT_YUYV:		return "YUYV";
	case V4L2_PIX_FMT_UYVY:		return "UYVY";
/*	case V4L2_PIX_FMT_YVU422P:	return "YVU422P"; */
/*	case V4L2_PIX_FMT_YVU411P:	return "YVU411P"; */
	case V4L2_PIX_FMT_YUV422P:	return "YUV422P";
	case V4L2_PIX_FMT_YUV411P:	return "YUV411P";
	case V4L2_PIX_FMT_Y41P:		return "Y41P";
	case V4L2_PIX_FMT_NV12:		return "NV12";
	case V4L2_PIX_FMT_NV21:		return "NV21";
	case V4L2_PIX_FMT_YUV410:	return "YUV410";
	case V4L2_PIX_FMT_YUV420:	return "YUV420";
	case V4L2_PIX_FMT_YYUV:		return "YYUV";
	case V4L2_PIX_FMT_HI240:	return "HI240";
	case V4L2_PIX_FMT_WNVA:		return "WNVA";
    }
    sprintf(unknown, "unknown (0x%x)", pixfmt);
    return unknown;
}


/*
** Gives the depth of a video4linux2 fourcc aka pixel format in bits.
*/
static int pixfmt2depth(int pixfmt)
{
    switch (pixfmt) {
	case V4L2_PIX_FMT_RGB332:
	    return 8;
	case V4L2_PIX_FMT_RGB555:
	case V4L2_PIX_FMT_RGB565:
	case V4L2_PIX_FMT_RGB555X:
	case V4L2_PIX_FMT_RGB565X:
	    return 16;
	case V4L2_PIX_FMT_BGR24:
	case V4L2_PIX_FMT_RGB24:
	    return 24;
	case V4L2_PIX_FMT_BGR32:
	case V4L2_PIX_FMT_RGB32:
	    return 32;
	case V4L2_PIX_FMT_GREY:
	    return 8;
	case V4L2_PIX_FMT_YVU410:
	    return 9;
	case V4L2_PIX_FMT_YVU420:
	    return 12;
	case V4L2_PIX_FMT_YUYV:
	case V4L2_PIX_FMT_UYVY:
	case V4L2_PIX_FMT_YUV422P:
	case V4L2_PIX_FMT_YUV411P:
	    return 16;
	case V4L2_PIX_FMT_Y41P:
	case V4L2_PIX_FMT_NV12:
	case V4L2_PIX_FMT_NV21:
	    return 12;
	case V4L2_PIX_FMT_YUV410:
	    return 9;
	case V4L2_PIX_FMT_YUV420:
	    return 12;
	case V4L2_PIX_FMT_YYUV:
	    return 16;
	case V4L2_PIX_FMT_HI240:
	    return 8;

    }
    return 0;
}


/*
** the number of milliseconds elapsed between time0 and time1
*/
static size_t difftv(struct timeval time1, struct timeval time0)
{
    return	(time1.tv_sec  - time0.tv_sec)  * 1000 +
		(time1.tv_usec - time0.tv_usec) / 1000;
}


/*
** Get current video capture format.
*/
static int getfmt(priv_t *priv)
{
    int i;

    priv->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if ((i = ioctl(priv->video_fd, VIDIOC_G_FMT, &priv->format)) < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl get format failed: %s\n",
	 info.short_name, strerror(errno));
    }
    return i;
}


/*
** Get current video capture standard.
*/
static int getstd(priv_t *priv)
{
    v4l2_std_id id;
    int i=0;

    if (ioctl(priv->video_fd, VIDIOC_G_STD, &id) < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl get standard failed: %s\n",
	 info.short_name, strerror(errno));
	return -1;
    }
    do {
	priv->standard.index = i++;
	if (ioctl(priv->video_fd, VIDIOC_ENUMSTD, &priv->standard) < 0) {
	    return -1;
	}
    } while (priv->standard.id != id);
    return 0;
}


#define PRIV ((priv_t *) priv)

/*
** This thread copies frames from the audio device into a linked list.
*/
static void *read_audio(void *priv)
{
    struct audio_frame *newaf, *lastaf = NULL;
    int  n, bytesleft;
    char *buffer;

    while (PRIV->streamon) {
	/* we read one frame per cycle, first allocate a buffer */
	newaf = malloc(sizeof(struct audio_frame) + PRIV->dsp_framesize - 1);
	if (!newaf) {
	    mp_msg(MSGT_TV, MSGL_ERR, "%s: malloc: %s\n",
	     info.short_name, strerror(errno));
	    usleep(500000);
	    continue;
	}

	/* fill the buffer */
	newaf->next = NULL;
	buffer      = &newaf->data;
	bytesleft   = PRIV->dsp_framesize;
	while (bytesleft > 0 && PRIV->streamon) {
	    if ((n = read(PRIV->audio_fd, buffer, bytesleft)) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: read audio failed: %s\n",
		 info.short_name, strerror(errno));
	    }
	    else {
		buffer    += n;
		bytesleft -= n;
	    }
	    /* sleep until 1k of the missing bytes should have arrived */
	    usleep((bytesleft < 1024? bytesleft: 1024) * 500000 /
	     PRIV->dsp_speed / PRIV->dsp_channels);
	}

	/* link the buffer */
	pthread_mutex_lock(&PRIV->audio_mutex);
	if (PRIV->audio_list) {
	    lastaf->next = newaf;
	}
	else {
	    PRIV->audio_list = newaf;
	}
	lastaf = newaf;
	if (++PRIV->listsize > PRIV->maxlist) {
	    PRIV->maxlist = PRIV->listsize;
	}
	pthread_mutex_unlock(&PRIV->audio_mutex);
    }

    /* free all buffers before exiting */
    pthread_mutex_lock(&PRIV->audio_mutex);
    while (PRIV->audio_list) {
	newaf = PRIV->audio_list->next;
	free((void *) PRIV->audio_list);
	PRIV->audio_list = newaf;
    }
    pthread_mutex_unlock(&PRIV->audio_mutex);

    return NULL;
}

#undef PRIV


/***********************************************************************\
*									*
*									*
*	Interface to mplayer						*
*									*
*									*
\***********************************************************************/

static int control(priv_t *priv, int cmd, void *arg)
{
    struct v4l2_control control;
    struct v4l2_frequency frequency;

    switch(cmd) {
	case TVI_CONTROL_IS_AUDIO:
	    return priv->audio_fd >= 0?
	     TVI_CONTROL_TRUE: TVI_CONTROL_FALSE;
	case TVI_CONTROL_IS_VIDEO:
	    return priv->capability.capabilities & V4L2_CAP_VIDEO_CAPTURE?
	     TVI_CONTROL_TRUE: TVI_CONTROL_FALSE;
	case TVI_CONTROL_IS_TUNER:
	    return priv->capability.capabilities & V4L2_CAP_TUNER?
	     TVI_CONTROL_TRUE: TVI_CONTROL_FALSE;
	case TVI_CONTROL_IMMEDIATE:
	    return TVI_CONTROL_FALSE;
	case TVI_CONTROL_VID_GET_FPS:
	    *(int *)arg = priv->standard.frameperiod.denominator /
	     priv->standard.frameperiod.numerator;
	    mp_msg(MSGT_TV, MSGL_V, "%s: get fps: %d\n", info.short_name,
	     *(int *)arg);
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_GET_BITS:
	    if (getfmt(priv) < 0) return TVI_CONTROL_FALSE;
	    *(int *)arg = pixfmt2depth(priv->format.fmt.pix.pixelformat);
	    mp_msg(MSGT_TV, MSGL_V, "%s: get depth: %d\n", info.short_name,
	     *(int *)arg);
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_GET_FORMAT:
	    if (getfmt(priv) < 0) return TVI_CONTROL_FALSE;
	    *(int *)arg = fcc_vl2mp(priv->format.fmt.pix.pixelformat);
	    mp_msg(MSGT_TV, MSGL_V, "%s: get format: %s\n", info.short_name,
	     pixfmt2name(priv->format.fmt.pix.pixelformat));
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_SET_FORMAT:
	    priv->format.fmt.pix.pixelformat = fcc_mp2vl(*(int *)arg);
	    mp_msg(MSGT_TV, MSGL_V, "%s: set format: %s\n", info.short_name,
	     pixfmt2name(priv->format.fmt.pix.pixelformat));
	    if (ioctl(priv->video_fd, VIDIOC_S_FMT, &priv->format) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set format failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_GET_WIDTH:
	    if (getfmt(priv) < 0) return TVI_CONTROL_FALSE;
	    *(int *)arg = priv->format.fmt.pix.width;
	    mp_msg(MSGT_TV, MSGL_V, "%s: get width: %d\n", info.short_name,
	     *(int *)arg);
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_CHK_WIDTH:
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_SET_WIDTH:
	    priv->format.fmt.pix.width = *(int *)arg;
	    mp_msg(MSGT_TV, MSGL_V, "%s: set width: %d\n", info.short_name,
	     *(int *)arg);
	    if (ioctl(priv->video_fd, VIDIOC_S_FMT, &priv->format) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set width failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_GET_HEIGHT:
	    if (getfmt(priv) < 0) return TVI_CONTROL_FALSE;
	    *(int *)arg = priv->format.fmt.pix.height;
	    mp_msg(MSGT_TV, MSGL_V, "%s: get height: %d\n", info.short_name,
	     *(int *)arg);
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_CHK_HEIGHT:
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_SET_HEIGHT:
	    priv->format.fmt.pix.height = *(int *)arg;
	    mp_msg(MSGT_TV, MSGL_V, "%s: set height: %d\n", info.short_name,
	     *(int *)arg);
	    if (ioctl(priv->video_fd, VIDIOC_S_FMT, &priv->format) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set height failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_GET_BRIGHTNESS:
	    control.id = V4L2_CID_BRIGHTNESS;
	    if (ioctl(priv->video_fd, VIDIOC_G_CTRL, &control) < 0) {
		mp_msg(MSGT_TV,MSGL_ERR,"%s: ioctl get brightness failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    *(int *)arg = control.value;
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_SET_BRIGHTNESS:
	    control.id = V4L2_CID_BRIGHTNESS;
	    control.value = *(int *)arg;
	    if (ioctl(priv->video_fd, VIDIOC_S_CTRL, &control) < 0) {
		mp_msg(MSGT_TV,MSGL_ERR,"%s: ioctl set brightness failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_GET_HUE:
	    control.id = V4L2_CID_HUE;
	    if (ioctl(priv->video_fd, VIDIOC_G_CTRL, &control) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl get hue failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    *(int *)arg = control.value;
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_SET_HUE:
	    control.id = V4L2_CID_HUE;
	    control.value = *(int *)arg;
	    if (ioctl(priv->video_fd, VIDIOC_S_CTRL, &control) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set hue failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_GET_SATURATION:
	    control.id = V4L2_CID_SATURATION;
	    if (ioctl(priv->video_fd, VIDIOC_G_CTRL, &control) < 0) {
		mp_msg(MSGT_TV,MSGL_ERR,"%s: ioctl get saturation failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    *(int *)arg = control.value;
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_SET_SATURATION:
	    control.id = V4L2_CID_SATURATION;
	    control.value = *(int *)arg;
	    if (ioctl(priv->video_fd, VIDIOC_S_CTRL, &control) < 0) {
		mp_msg(MSGT_TV,MSGL_ERR,"%s: ioctl set saturation failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_GET_CONTRAST:
	    control.id = V4L2_CID_CONTRAST;
	    if (ioctl(priv->video_fd, VIDIOC_G_CTRL, &control) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl get contrast failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    *(int *)arg = control.value;
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_VID_SET_CONTRAST:
	    control.id = V4L2_CID_CONTRAST;
	    control.value = *(int *)arg;
	    if (ioctl(priv->video_fd, VIDIOC_S_CTRL, &control) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set contrast failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_TUN_GET_FREQ:
	    frequency.tuner = 0;
	    frequency.type  = V4L2_TUNER_ANALOG_TV;
	    if (ioctl(priv->video_fd, VIDIOC_G_FREQUENCY, &frequency) < 0) {
		mp_msg(MSGT_TV,MSGL_ERR,"%s: ioctl get frequency failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    *(int *)arg = frequency.frequency;
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_TUN_SET_FREQ:
	    frequency.tuner = 0;
	    frequency.type  = V4L2_TUNER_ANALOG_TV;
	    frequency.frequency = *(int *)arg;
	    if (ioctl(priv->video_fd, VIDIOC_S_FREQUENCY, &frequency) < 0) {
		mp_msg(MSGT_TV,MSGL_ERR,"%s: ioctl set frequency failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_TUN_GET_TUNER:
	    if (ioctl(priv->video_fd, VIDIOC_G_TUNER, &priv->tuner) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl get tuner failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_TUN_SET_TUNER:
	    if (ioctl(priv->video_fd, VIDIOC_S_TUNER, &priv->tuner) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set tuner failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_TUN_GET_NORM:
	    *(int *)arg = priv->standard.index;
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_TUN_SET_NORM:
	    priv->standard.index = *(int *)arg - 1;
	    if (ioctl(priv->video_fd, VIDIOC_ENUMSTD, &priv->standard) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl enum norm failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    if (ioctl(priv->video_fd, VIDIOC_S_STD, &priv->standard.id) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set norm failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_SPC_GET_INPUT:
	    if (ioctl(priv->video_fd, VIDIOC_G_INPUT, (int *)arg) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl get input failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_SPC_SET_INPUT:
	    if (ioctl(priv->video_fd, VIDIOC_S_INPUT, (int *)arg) < 0) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set input failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_AUD_GET_FORMAT:
	    *(int *)arg = priv->dsp_format;
	    mp_msg(MSGT_TV, MSGL_V, "%s: get audio format: %d\n",
	     info.short_name, *(int *)arg);
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_AUD_GET_SAMPLERATE:
	    *(int *)arg = priv->dsp_speed;
	    mp_msg(MSGT_TV, MSGL_V, "%s: get audio samplerate: %d\n",
	     info.short_name, *(int *)arg);
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_AUD_GET_SAMPLESIZE:
	    *(int *)arg = 2;
	    mp_msg(MSGT_TV, MSGL_V, "%s: get audio samplesize: %d\n",
	     info.short_name, *(int *)arg);
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_AUD_GET_CHANNELS:
	    *(int *)arg = priv->dsp_channels;
	    mp_msg(MSGT_TV, MSGL_V, "%s: get audio channels: %d\n",
	     info.short_name, *(int *)arg);
	    return TVI_CONTROL_TRUE;
	case TVI_CONTROL_AUD_SET_SAMPLERATE:
	    mp_msg(MSGT_TV, MSGL_V, "%s: set audio samplerate: %d\n",
	     info.short_name, *(int *)arg);
	    priv->dsp_speed = *(int *)arg;
	    if (ioctl(priv->audio_fd, SNDCTL_DSP_SPEED, &priv->dsp_speed) < 0) {
		mp_msg(MSGT_TV,MSGL_ERR,"%s: ioctl set samplerate failed: %s\n",
		 info.short_name, strerror(errno));
		return TVI_CONTROL_FALSE;
	    }
	    if (priv->dsp_speed != *(int *)arg) {
		mp_msg(MSGT_TV, MSGL_ERR, "%s: audio samplerate: %d -> %d\n",
		 info.short_name, *(int *)arg, priv->dsp_speed);
	    }
	    /* An audio frame should take as much time as a video frame. */
	    priv->dsp_framesize = 2 * priv->dsp_channels * priv->dsp_speed *
	     priv->standard.frameperiod.numerator /
	     priv->standard.frameperiod.denominator;
	    return TVI_CONTROL_TRUE;
    }
    mp_msg(MSGT_TV, MSGL_V, "%s: unknown control: %d\n", info.short_name, cmd);
    return(TVI_CONTROL_UNKNOWN);
}


#define PRIV ((priv_t *) (tvi_handle->priv))

/* handler creator - entry point ! */
tvi_handle_t *tvi_init_v4l2(char *video_dev, char *audio_dev)
{
    tvi_handle_t *tvi_handle;

    /* new_handle initializes priv with memset 0 */
    tvi_handle = new_handle();
    if (!tvi_handle) {
	return NULL;
    }
    PRIV->video_fd = -1;
    PRIV->audio_fd = -1;

    PRIV->video_dev = strdup(video_dev? video_dev: "/dev/video");
    if (!PRIV->video_dev) {
	free_handle(tvi_handle);
	return NULL;
    }

    if (audio_dev) {
	PRIV->audio_dev = strdup(audio_dev);
	if (!PRIV->audio_dev) {
	    free(PRIV->video_dev);
	    free_handle(tvi_handle);
	    return NULL;
	}
    }

    return tvi_handle;
}

#undef PRIV


static int uninit(priv_t *priv)
{
    int i, frames, dropped;

    if (priv->streamon) {
	struct v4l2_buffer buf;

	/* get performance */
	frames = 1 + difftv(priv->curr_frame, priv->first_frame) *
	 priv->standard.frameperiod.denominator /
	 priv->standard.frameperiod.numerator / 1000;
	dropped = frames - priv->frames;

	/* turn off streaming */
	if (ioctl(priv->video_fd, VIDIOC_STREAMOFF, &(priv->map[0].buf.type))
	 < 0) {
	    mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl streamoff failed: %s\n",
	     info.short_name, strerror(errno));
	}
	priv->streamon = 0;

	/* unqueue all remaining buffers */
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	while (!ioctl(priv->video_fd, VIDIOC_DQBUF, &buf))
	    ;
    }

    /* unmap all buffers */
    for (i = 0; i < priv->mapcount; i++) {
	if (munmap(priv->map[i].addr, priv->map[i].len) < 0) {
	    mp_msg(MSGT_TV, MSGL_ERR, "%s: munmap capture buffer failed: %s\n",
	     info.short_name, strerror(errno));
	}
    }

    /* stop audio thread */
    if (priv->audio_dev) {
	pthread_join(priv->audio_thread, NULL);
	pthread_mutex_destroy(&priv->audio_mutex);
	close(priv->audio_fd);	priv->audio_fd  = -1;
	free(priv->audio_dev);	priv->audio_dev = NULL;
    }

    /* free memory and close device */
    free(priv->map);		priv->map = NULL;
    priv->mapcount = 0;
    close(priv->video_fd);	priv->video_fd  = -1;
    free(priv->video_dev);	priv->video_dev = NULL;

    /* show some nice statistics ;-) */
    mp_msg(MSGT_TV, MSGL_INFO,
     "%s: %d frames successfully processed, %d frames dropped.\n",
     info.short_name, priv->frames, dropped);
    mp_msg(MSGT_TV, MSGL_V, "%s: up to %u audio frames buffered.\n",
     info.short_name, priv->maxlist);
    return 1;
}


/* initialisation */
static int init(priv_t *priv)
{
    int i;

    /* Open the video device. */
    priv->video_fd = open(priv->video_dev, O_RDWR);
    if (priv->video_fd < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: unable to open '%s': %s\n",
	 info.short_name, priv->video_dev, strerror(errno));
	uninit(priv);
	return 0;
    }
    mp_msg(MSGT_TV, MSGL_DBG2, "%s: video fd: %s: %d\n",
     info.short_name, priv->video_dev, priv->video_fd);

    /* Open the audio device. */
    if (priv->audio_dev) {
	priv->audio_fd = open(priv->audio_dev, O_RDONLY);
	if (priv->audio_fd < 0) {
	    mp_msg(MSGT_TV, MSGL_ERR, "%s: unable to open '%s': %s\n",
	     info.short_name, priv->audio_dev, strerror(errno));
	    uninit(priv);
	    return 0;
	}
	mp_msg(MSGT_TV, MSGL_DBG2, "%s: audio fd: %s: %d\n",
	 info.short_name, priv->audio_dev, priv->audio_fd);
    }

    /*
    ** Query the video capabilities and current settings
    ** for further control calls.
    */
    if (ioctl(priv->video_fd, VIDIOC_QUERYCAP, &priv->capability) < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl query capabilities failed: %s\n",
	 info.short_name, strerror(errno));
	uninit(priv);
	return 0;
    }
    if (getfmt(priv) < 0 || getstd(priv) < 0) {
	uninit(priv);
	return 0;
    }
    /*
    ** if this device has got a tuner query it's settings
    ** otherwise set some nice defaults
    */
    if (priv->capability.capabilities & V4L2_CAP_TUNER) {
	if (ioctl(priv->video_fd, VIDIOC_G_TUNER, &priv->tuner) < 0) {
	    mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl get tuner failed: %s\n",
	     info.short_name, strerror(errno));
	    uninit(priv);
	    return 0;
	}
    }
    else {
	/*
	** new_handle initializes priv with memset 0
	** thus it is sufficient to set non zero values
	*/
	priv->standard.frameperiod.numerator   = 1;
	priv->standard.frameperiod.denominator = 25;
	priv->standard.id = V4L2_STD_PAL;
    }
    mp_msg(MSGT_TV, MSGL_INFO, "Selected device: %s\n", priv->capability.card);
    mp_msg(MSGT_TV, MSGL_INFO, " Capabilites:%s%s%s%s%s%s%s%s%s%s%s\n",
     priv->capability.capabilities & V4L2_CAP_VIDEO_CAPTURE?
      "  video capture": "",
     priv->capability.capabilities & V4L2_CAP_VIDEO_OUTPUT?
      "  video output": "",
     priv->capability.capabilities & V4L2_CAP_VIDEO_OVERLAY?
      "  video overlay": "",
     priv->capability.capabilities & V4L2_CAP_VBI_CAPTURE?
      "  VBI capture device": "",
     priv->capability.capabilities & V4L2_CAP_VBI_OUTPUT?
      "  VBI output": "",
     priv->capability.capabilities & V4L2_CAP_RDS_CAPTURE?
      "  RDS data capture": "",
     priv->capability.capabilities & V4L2_CAP_TUNER?
      "  tuner": "",
     priv->capability.capabilities & V4L2_CAP_AUDIO?
      "  audio": "",
     priv->capability.capabilities & V4L2_CAP_READWRITE?
      "  read/write": "",
     priv->capability.capabilities & V4L2_CAP_ASYNCIO?
      "  async i/o": "",
     priv->capability.capabilities & V4L2_CAP_STREAMING?
      "  streaming": "");
    mp_msg(MSGT_TV, MSGL_INFO, " inputs:");
    for (i = 0; 1; i++) {
	struct v4l2_input input;

	input.index = i;
	if (ioctl(priv->video_fd, VIDIOC_ENUMINPUT, &input) < 0) {
	    break;
	}
	mp_msg(MSGT_TV, MSGL_INFO, " %d = %s;", i, input.name);
    }
    if (ioctl(priv->video_fd, VIDIOC_G_INPUT, &i) < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl get input failed: %s\n",
	 info.short_name, strerror(errno));
    }
    mp_msg(MSGT_TV, MSGL_INFO, "\n Current input: %d\n", i);
    for (i = 0; ; i++) {
	struct v4l2_fmtdesc fmtdesc;

	fmtdesc.index = i;
	fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (ioctl(priv->video_fd, VIDIOC_ENUM_FMT, &fmtdesc) < 0) {
	    break;
	}
	mp_msg(MSGT_TV, MSGL_V, " Format %-6s (%2d bits, %s): %s\n",
	 pixfmt2name(fmtdesc.pixelformat), pixfmt2depth(fmtdesc.pixelformat),
	 fmtdesc.description, vo_format_name(fcc_vl2mp(fmtdesc.pixelformat)));
    }
    mp_msg(MSGT_TV, MSGL_INFO, " Current format: %s\n",
     pixfmt2name(priv->format.fmt.pix.pixelformat));
    /* set some nice defaults */
    priv->format.fmt.pix.width  = 640;
    priv->format.fmt.pix.height = 480;
    if (ioctl(priv->video_fd, VIDIOC_S_FMT, &priv->format) < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set format failed: %s\n",
	 info.short_name, strerror(errno));
	uninit(priv);
	return 0;
    }

    if (priv->audio_dev) {
	/*
	** Initialize audio,
	** 16-bit signed little endian stereo at 44100 Hz should be default.
	*/
	priv->dsp_format = AFMT_S16_LE;
	if (ioctl(priv->audio_fd, SNDCTL_DSP_SETFMT, &priv->dsp_format) < 0) {
	    mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl set audio format failed: %s\n",
	     info.short_name, strerror(errno));
	}
	priv->dsp_channels = 2;
	if (ioctl(priv->audio_fd,SNDCTL_DSP_CHANNELS,&priv->dsp_channels) < 0) {
	    mp_msg(MSGT_TV,MSGL_ERR,"%s: ioctl set audio channels failed: %s\n",
	     info.short_name, strerror(errno));
	}
	priv->dsp_speed = 44100;
	control(priv, TVI_CONTROL_AUD_SET_SAMPLERATE, &priv->dsp_speed);
    }

    return 1;
}


/* that's the real start, we'got the format parameters (checked with control) */
static int start(priv_t *priv)
{
    struct v4l2_requestbuffers request;
    int i;

    /* request buffers */
    request.count = VIDEO_BUFFERS;
    request.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(priv->video_fd, VIDIOC_REQBUFS, &request) < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl request buffers failed: %s\n",
	 info.short_name, strerror(errno));
	return 0;
    }

    /* query buffers */
    if (!(priv->map = malloc(sizeof(struct map) * request.count))) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: malloc capture buffers failed: %s\n",
	 info.short_name, strerror(errno));
	return 0;
    }

    /* map and queue buffers */
    for (i = 0; i < request.count; i++) {
	priv->map[i].buf.index = i;
	priv->map[i].buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (ioctl(priv->video_fd, VIDIOC_QUERYBUF, &(priv->map[i].buf)) < 0) {
	    mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl request buffers failed: %s\n",
	     info.short_name, strerror(errno));
	    free(priv->map);
	    priv->map = NULL;
	    return 0;
	}
	priv->map[i].addr = mmap (0, priv->map[i].buf.length, PROT_READ |
	 PROT_WRITE, MAP_SHARED, priv->video_fd, priv->map[i].buf.m.offset);
	if (priv->map[i].addr == MAP_FAILED) {
	    mp_msg(MSGT_TV, MSGL_ERR, "%s: mmap capture buffer failed: %s\n",
	     info.short_name, strerror(errno));
	    priv->map[i].len = 0;
	    return 0;
	}
	priv->map[i].len = priv->map[i].buf.length;
	/* count up to make sure this is correct everytime */
	priv->mapcount++;

	if (ioctl(priv->video_fd, VIDIOC_QBUF, &(priv->map[i].buf)) < 0) {
	    mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl queue buffer failed: %s\n",
	     info.short_name, strerror(errno));
	    return 0;
	}
    }

    if (ioctl(priv->video_fd, VIDIOC_STREAMON, &(priv->map[0].buf.type)) < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl streamon failed: %s\n",
	 info.short_name, strerror(errno));
	return 0;
    }
    priv->streamon = 1;

    /* start audio thread */
    if (priv->audio_dev) {
	pthread_mutex_init(&priv->audio_mutex, NULL);
	pthread_create(&priv->audio_thread, NULL, read_audio, priv);
    }

    return 1;
}


#ifdef HAVE_TV_BSDBT848
static double grabimmediate_video_frame(priv_t *priv, char *buffer, int len)
{
    memset(buffer, 0xCC, len);
    return(1);
}
#endif /* HAVE_TV_BSDBT848 */


static double grab_video_frame(priv_t *priv, char *buffer, int len)
{
    fd_set rdset;
    struct timeval timeout;
    struct v4l2_buffer buf;
    int i;

    FD_ZERO (&rdset);
    FD_SET (priv->video_fd, &rdset);

    timeout.tv_sec = 1;
    timeout.tv_usec = 0;

    i = select(priv->video_fd + 1, &rdset, NULL, NULL, &timeout);
    if (i < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: select failed: %s\n",
	 info.short_name, strerror(errno));
	return 0;
    }
    else if (i == 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: select timeout\n", info.short_name);
	return 0;
    }
    else if (!FD_ISSET(priv->video_fd, &rdset)) {
	return 0;
    }

    /* dequeue buffer */
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(priv->video_fd, VIDIOC_DQBUF, &buf) < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl dequeue buffer failed: %s\n",
	 info.short_name, strerror(errno));
	return 0;
    }

    /* store the timestamp of the very first frame as reference */
    if (!priv->frames++) {
	priv->first_frame = buf.timestamp;
    }
    priv->curr_frame = buf.timestamp;

    /* copy the buffer */
    if ((unsigned) len > buf.bytesused) {
	len = buf.bytesused;
    }
    memcpy(buffer, priv->map[buf.index].addr, len);
    if (ioctl(priv->video_fd, VIDIOC_QBUF, &buf) < 0) {
	mp_msg(MSGT_TV, MSGL_ERR, "%s: ioctl queue buffer failed: %s\n",
	 info.short_name, strerror(errno));
	return 0;
    }

    return difftv(buf.timestamp, priv->first_frame) / 1000.0;
}


static int get_video_framesize(priv_t *priv)
{
    return priv->format.fmt.pix.sizeimage;
}


static double grab_audio_frame(priv_t *priv, char *buffer, int len)
{
    struct audio_frame *af;

    /* paranoia */
    if(len != priv->dsp_framesize) {
	mp_msg(MSGT_TV, MSGL_ERR,
	 "%s: audio framesize does not match: %d != %d\n",
	 info.short_name, len, priv->dsp_framesize);
	return 0;
    }

    /* wait until next audio frame is available */
    while (!priv->audio_list) {
	usleep(10000);
    }

    /* unqueue audio frame */
    pthread_mutex_lock(&priv->audio_mutex);
    af = (struct audio_frame *) priv->audio_list;
    priv->audio_list = af->next;
    priv->listsize--;
    pthread_mutex_unlock(&priv->audio_mutex);

    /* copy and free audio frame */
    memcpy(buffer, &af->data, len);
    free(af);

    return (double) priv->dsp_frames++ * priv->dsp_framesize / 
     priv->dsp_speed / priv->dsp_channels / 2;
}


static int get_audio_framesize(priv_t *priv)
{
    return priv->dsp_framesize;
}

#endif /* USE_TV || HAVE_TV_V4L2 */


More information about the MPlayer-dev-eng mailing list