/*
Video 4 Linux input
(C) Alex Beregszaszi <alex@naxine.org>
Some ideas are based on xawtv/libng's grab-v4l.c written by
Gerd Knorr <kraxel@bytesex.org>
Multithreading, a/v sync and native ALSA support by
Jindrich Makovicka <makovick@kmlinux.fjfi.cvut.cz>
CODE IS UNDER DEVELOPMENT, NO FEATURE REQUESTS PLEASE!
*/
#include "config.h"
#if defined(USE_TV) && defined(HAVE_TV_V4L)
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <linux/videodev.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#ifdef HAVE_SYS_SYSINFO_H
#include <sys/sysinfo.h>
#endif
#include "mp_msg.h"
#include "../libao2/afmt.h"
#include "../libvo/img_format.h"
#include "../libvo/fastmemcpy.h"
#include "tv.h"
#include "audio_in.h"
static tvi_info_t info = {
"Video 4 Linux input",
"v4l",
"Alex Beregszaszi <alex@naxine.org>",
"under development"
};
#define PAL_WIDTH 768
#define PAL_HEIGHT 576
#define PAL_FPS 25
#define NTSC_WIDTH 640
#define NTSC_HEIGHT 480
#define NTSC_FPS 30
#define MAX_AUDIO_CHANNELS 10
#define VID_BUF_SIZE_IMMEDIATE 2
typedef struct {
/* general */
char *video_device;
int video_fd;
struct video_capability capability;
struct video_channel *channels;
int act_channel;
struct video_tuner tuner;
/* video */
struct video_picture picture;
int format; /* output format */
int width;
int height;
int bytesperline;
int fps;
struct video_mbuf mbuf;
unsigned char *mmap;
struct video_mmap *buf;
int nbuf;
/* audio */
char *audio_device;
audio_in_t audio_in;
int audio_id;
struct video_audio audio[MAX_AUDIO_CHANNELS];
int audio_channels[MAX_AUDIO_CHANNELS];
/* buffering stuff */
int immediate_mode;
int audio_buffer_size;
int aud_skew_cnt;
unsigned char *audio_ringbuffer;
long long *audio_skew_buffer;
volatile int audio_head;
volatile int audio_tail;
volatile int audio_cnt;
volatile long long audio_skew;
volatile double audio_skew_factor;
volatile long long audio_skew_measure_time;
volatile int audio_drop;
int first;
int video_buffer_size_max;
volatile int video_buffer_size_current;
unsigned char **video_ringbuffer;
long long *video_timebuffer;
volatile int video_head;
volatile int video_tail;
volatile int video_cnt;
volatile int shutdown;
pthread_t audio_grabber_thread;
pthread_t video_grabber_thread;
pthread_mutex_t audio_starter;
pthread_mutex_t skew_mutex;
pthread_mutex_t video_buffer_mutex;
long long starttime;
double audio_secs_per_block;
long long audio_skew_total;
long audio_recv_blocks_total;
long audio_sent_blocks_total;
} priv_t;
#include "tvi_def.h"
static const char *device_cap2name[] = {
"capture", "tuner", "teletext", "overlay", "chromakey", "clipping",
"frameram", "scales", "monochrome", "subcapture", "mpeg-decoder",
"mpeg-encoder", "mjpeg-decoder", "mjpeg-encoder", NULL
};
static const char *device_palette2name[] = {
"-", "grey", "hi240", "rgb16", "rgb24", "rgb32", "rgb15", "yuv422",
"yuyv", "uyvy", "yuv420", "yuv411", "raw", "yuv422p", "yuv411p",
"yuv420p", "yuv410p"
};
#define PALETTE(x) ((x < sizeof(device_palette2name)/sizeof(char*)) ? device_palette2name[x] : "UNKNOWN")
static const char *norm2name(int mode)
{
switch (mode) {
case VIDEO_MODE_PAL:
return "pal";
case VIDEO_MODE_SECAM:
return "secam";
case VIDEO_MODE_NTSC:
return "ntsc";
case VIDEO_MODE_AUTO:
return "auto";
default:
return "unknown";
}
};
static const char *audio_mode2name(int mode)
{
switch (mode) {
case VIDEO_SOUND_MONO:
return "mono";
case VIDEO_SOUND_STEREO:
return "stereo";
case VIDEO_SOUND_LANG1:
return "language1";
case VIDEO_SOUND_LANG2:
return "language2";
default:
return "unknown";
}
};
static void *audio_grabber(void *data);
static void *video_grabber(void *data);
static int palette2depth(int palette)
{
switch(palette)
{
/* component */
case VIDEO_PALETTE_RGB555:
return(15);
case VIDEO_PALETTE_RGB565:
return(16);
case VIDEO_PALETTE_RGB24:
return(24);
case VIDEO_PALETTE_RGB32:
return(32);
/* planar */
case VIDEO_PALETTE_YUV411P:
case VIDEO_PALETTE_YUV420P:
case VIDEO_PALETTE_YUV410P:
return(12);
/* packed */
case VIDEO_PALETTE_YUV422P:
case VIDEO_PALETTE_YUV422:
case VIDEO_PALETTE_YUYV:
case VIDEO_PALETTE_UYVY:
case VIDEO_PALETTE_YUV420:
case VIDEO_PALETTE_YUV411:
return(16);
}
return(-1);
}
static int format2palette(int format)
{
switch(format)
{
case IMGFMT_BGR15:
return(VIDEO_PALETTE_RGB555);
case IMGFMT_BGR16:
return(VIDEO_PALETTE_RGB565);
case IMGFMT_BGR24:
return(VIDEO_PALETTE_RGB24);
case IMGFMT_BGR32:
return(VIDEO_PALETTE_RGB32);
case IMGFMT_YV12:
case IMGFMT_I420:
return(VIDEO_PALETTE_YUV420P);
case IMGFMT_YUY2:
return(VIDEO_PALETTE_YUV422);
}
return(-1);
}
// sets and sanitizes audio buffer/block sizes
static void setup_audio_buffer_sizes(priv_t *priv)
{
int bytes_per_sample = priv->audio_in.bytes_per_sample;
// make the audio buffer at least 5 seconds long
priv->audio_buffer_size = 1 + 5*priv->audio_in.samplerate
*priv->audio_in.channels
*bytes_per_sample/priv->audio_in.blocksize;
if (priv->audio_buffer_size < 256) priv->audio_buffer_size = 256;
// make the skew buffer at least 1 second long
priv->aud_skew_cnt = 1 + 1*priv->audio_in.samplerate
*priv->audio_in.channels
*bytes_per_sample/priv->audio_in.blocksize;
if (priv->aud_skew_cnt < 16) priv->aud_skew_cnt = 16;
mp_msg(MSGT_TV, MSGL_V, "Audio capture - buffer %d blocks of %d bytes, skew average from %d meas.\n",
priv->audio_buffer_size, priv->audio_in.blocksize, priv->aud_skew_cnt);
}
tvi_handle_t *tvi_init_v4l(char *device, char *adevice)
{
tvi_handle_t *h;
priv_t *priv;
h = new_handle();
if (!h)
return(NULL);
priv = h->priv;
/* set video device name */
if (!device)
priv->video_device = strdup("/dev/video");
else
priv->video_device = strdup(device);
/* set video device name */
if (!adevice)
priv->audio_device = NULL;
else {
priv->audio_device = strdup(adevice);
}
/* allocation failed */
if (!priv->video_device) {
free_handle(h);
return(NULL);
}
return(h);
}
/* retrieves info about audio channels from the BTTV */
static void init_v4l_audio(priv_t *priv)
{
int i;
int reqmode;
if (!priv->capability.audios) return;
/* audio chanlist */
mp_msg(MSGT_TV, MSGL_V, " Audio devices: %d\n", priv->capability.audios);
mp_msg(MSGT_TV, MSGL_V, "Video capture card reports the audio setup as follows:\n");
for (i = 0; i < priv->capability.audios; i++)
{
if (i >= MAX_AUDIO_CHANNELS)
{
mp_msg(MSGT_TV, MSGL_ERR, "no space for more audio channels (increase in source!) (%d > %d)\n",
i, MAX_AUDIO_CHANNELS);
i = priv->capability.audios;
break;
}
priv->audio[i].audio = i;
if (ioctl(priv->video_fd, VIDIOCGAUDIO, &priv->audio[i]) == -1)
{
mp_msg(MSGT_TV, MSGL_ERR, "ioctl get audio failed: %s\n", strerror(errno));
break;
}
/* mute all channels */
priv->audio[i].volume = 0;
priv->audio[i].flags |= VIDE
|