/*
* libzvbi - V4L interface
*
* Copyright (C) 1999-2004 Michael H. Schimek
* Copyright (C) 2003, 2004 Tom Zoerner
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
static const char rcsid [] =
"$Id: io-v4l.c,v 1.33 2006/05/22 09:01:04 mschimek Exp $";
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "misc.h"
#include "vbi.h"
#include "io.h"
#ifdef ENABLE_V4L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <unistd.h> /* read(), dup2(), getuid() */
#include <assert.h>
#include <sys/time.h> /* timeval */
#include <sys/types.h> /* fd_set, uid_t */
#include <sys/ioctl.h> /* for (_)videodev.h */
#include <pthread.h>
#include "videodev.h"
#include "_videodev.h"
/* This macro checks at compile time if the arg type is correct,
device_ioctl() repeats the ioctl if interrupted (EINTR) and logs
the args and result if sys_log_fp is non-zero. */
#define xioctl(v, cmd, arg) \
(IOCTL_ARG_TYPE_CHECK_ ## cmd (arg), \
device_ioctl (v->capture.sys_log_fp, fprint_ioctl_arg, v->fd, \
cmd, (void *)(arg)))
#define xioctl_fd(v, fd, cmd, arg) \
(IOCTL_ARG_TYPE_CHECK_ ## cmd (arg), \
device_ioctl (v->capture.sys_log_fp, fprint_ioctl_arg, fd, \
cmd, (void *)(arg)))
/* Custom ioctl of the bttv driver. */
#define BTTV_VBISIZE _IOR('v' , BASE_VIDIOCPRIVATE+8, int)
static __inline__ void IOCTL_ARG_TYPE_CHECK_BTTV_VBISIZE
(const int *arg __attribute__ ((unused))) {}
#undef REQUIRE_SELECT
#undef REQUIRE_SVBIFMT /* else accept current parameters */
#undef REQUIRE_VIDEOSTD /* if clueless, assume PAL/SECAM */
#define FLUSH_FRAME_COUNT 2
#define printv(format, args...) \
do { \
if (v->do_trace) { \
fprintf(stderr, "libzvbi: " format ,##args); \
fflush(stderr); \
} \
} while (0)
typedef struct vbi_capture_v4l {
vbi_capture capture;
int fd;
vbi_bool has_select;
vbi_bool read_active;
vbi_bool do_trace;
signed char has_s_fmt;
struct video_capability vcap;
char * p_dev_name;
char * p_video_name;
int fd_video;
vbi_raw_decoder dec;
unsigned int services; /* all services, including raw */
double time_per_frame;
vbi_capture_buffer *raw_buffer;
int num_raw_buffers;
vbi_capture_buffer sliced_buffer;
int flush_frame_count;
} vbi_capture_v4l;
static void
v4l_read_stop(vbi_capture_v4l *v)
{
for (; v->num_raw_buffers > 0; v->num_raw_buffers--) {
free(v->raw_buffer[v->num_raw_buffers - 1].data);
v->raw_buffer[v->num_raw_buffers - 1].data = NULL;
}
free(v->raw_buffer);
v->raw_buffer = NULL;
}
static int
v4l_suspend(vbi_capture_v4l *v)
{
int fd;
v4l_read_stop(v);
if (v->read_active) {
printv("Suspending read: re-open device...\n");
/* hack: cannot suspend read to allow SVBIFMT,
need to close device */
fd = device_open (v->capture.sys_log_fp,
v->p_dev_name, O_RDWR, 0);
if (-1 == fd) {
printv ("v4l2-suspend: failed to re-open "
"VBI device: %d: %s\n",
errno, strerror(errno));
return -1;
}
/* use dup2() to keep the same fd,
which may be used by our client */
device_close (v->capture.sys_log_fp, v->fd);
dup2 (fd, v->fd);
device_close (v->capture.sys_log_fp, fd);
v->read_active = FALSE;
}
return 0;
}
static int
v4l_read_alloc(vbi_capture_v4l *v, char ** errstr)
{
assert(v->raw_buffer == NULL);
v->raw_buffer = calloc(1, sizeof(v->raw_buffer[0]));
if (v->raw_buffer == NULL) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
goto failure;
}
v->raw_buffer[0].size = (v->dec.count[0] + v->dec.count[1])
* v->dec.bytes_per_line;
v->raw_buffer[0].data = malloc(v->raw_buffer[0].size);
if (v->raw_buffer[0].data == NULL) {
asprintf(errstr, _("Not enough memory to allocate "
"vbi capture buffer (%d KB)."),
(v->raw_buffer[0].size + 1023) >> 10);
goto failure;
}
v->num_raw_buffers = 1;
printv("Capture buffer allocated: %d bytes\n", v->raw_buffer[0].size);
return 0;
failure:
v4l_read_stop(v);
return -1;
}
static int
v4l_read_frame(vbi_capture_v4l *v, vbi_capture_buffer *raw, struct timeval *timeout)
{
struct timeval tv;
int r;
if (v->has_select) {
tv = *timeout;
r = vbi_capture_io_select(v->fd, &tv);
if (r <= 0)
return r;
}
v->read_active = TRUE;
for (;;) {
pthread_testcancel();
r = read(v->fd, raw->data, raw->size);
if (r == -1 && (errno == EINTR || errno == ETIME))
continue;
if (r == -1)
return -1;
if (r != raw->size) {
errno = EIO;
return -1;
}
else
break;
}
return 1;
}
static int
v4l_read(vbi_capture *vc, vbi_capture_buffer **raw,
vbi_capture_buffer **sliced, const struct timeval *timeout_orig)
{
vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture);
vbi_capture_buffer *my_raw = v->raw_buffer;
struct timeval tv;
int r;
if (my_raw == NULL) {
printv("read buffer not allocated (must add services first)\n");
errno = EINVAL;
return -1;
}
if (raw == NULL)
raw = &my_raw;
if (*raw == NULL)
*raw = v->raw_buffer;
else
(*raw)->size = v->raw_buffer[0].size;
tv = *timeout_orig;
while (1)
{
r = v4l_read_frame(v, *raw, &tv);
if (r <= 0)
return r;
if (v->flush_frame_count > 0) {
v->flush_frame_count -= 1;
printv("Skipping frame (%d remaining)\n", v->flush_frame_count);
}
else
break;
}
gettimeofday(&tv, NULL);
(*raw)->timestamp = tv.tv_sec + tv.tv_usec * (1 / 1e6);
if (sliced) {
int lines;
if (*sliced) {
lines = vbi_raw_decode(&v->dec, (*raw)->data,
(vbi_sliced *)(*sliced)->data);
} else {
*sliced = &v->sliced_buffer;
lines = vbi_raw_decode(&v->dec, (*raw)->data,
(vbi_sliced *)(v->sliced_buffer.data));
}
(*sliced)->size = lines * sizeof(vbi_sliced);
(*sliced)->timestamp = (*raw)->timestamp;
}
return 1;
}
static void v4l_flush(vbi_capture *vc)
{
vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture);
struct timeval tv;
int fd_flags = 0;
int r;
if ( (v->raw_buffer == NULL) || (v->read_active == FALSE) )
return;
v->flush_frame_count = FLUSH_FRAME_COUNT;
if (v->has_select) {
memset(&tv, 0, sizeof(tv));
r = vbi_capture_io_select(v->fd, &tv);
if (r <= 0)
return;
}
if (v->has_select == FALSE) {
fd_flags = fcntl(v->fd, F_GETFL, NULL);
if (fd_flags == -1)
return;
/* no select supported by driver -> make read non-blocking */
if ((fd_flags & O_NONBLOCK) == 0) {
fcntl(v->fd, F_SETFL, fd_flags | O_NONBLOCK);
}
}
r = read(v->fd, v->raw_buffer->data, v->raw_buffer->size);
if ((v->has_select == FALSE) && ((fd_flags & O_NONBLOCK) == 0)) {
fcntl(v->fd, F_SETFL, fd_flags);
}
}
/* Molto rumore per nulla. */
#include <dirent.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
static void
perm_check(vbi_capture_v4l *v, const char *name)
{
struct stat st;
int old_errno = errno;
uid_t uid = geteuid();
gid_t gid = getegid();
if (stat(name, &st) == -1) {
printv("stat %s failed: %d, %s\n", name, errno, strerror(errno));
errno = old_errno;
return;
}
printv("%s permissions: user=%d.%d mode=0%o, I am %d.%d\n",
name, st.st_uid, st.st_gid, st.st_mode, uid, gid);
errno = old_errno;
}
static vbi_bool
reverse_lookup(vbi_capture_v4l *v, int fd, struct stat *vbi_stat)
{
struct video_capability vcap;
struct video_unit vunit;
CLEAR (vcap);
if (-1 == xioctl_fd (v, fd, VIDIOCGCAP, &vcap)) {
printv ("Driver doesn't support VIDIOCGCAP, "
"probably not V4L API\n");
return FALSE;
}
if (!(vcap.type & VID_TYPE_CAPTURE)) {
printv("Driver is no video capture device\n");
return FALSE;
}
CLEAR (vunit);
if (-1 == xioctl_fd (v, fd, VIDIOCGUNIT, &vunit)) {
printv ("Driver doesn't support VIDIOCGUNIT\n");
return FALSE;
}
if (vunit.vbi != (int) minor(vbi_stat->st_rdev)) {
printv("Driver reports vbi minor %d, need %d\n",
vunit.vbi, minor(vbi_stat->st_rdev));
return FALSE;
}
printv("Matched\n");
return TRUE;
}
static void
set_scanning_from_mode(vbi_capture_v4l *v, int mode, int * strict)
{
switch (mode) {
case VIDEO_MODE_NTSC:
printv("Videostandard is NTSC\n");
v->dec.scanning = 525;
break;
case VIDEO_MODE_PAL:
case VIDEO_MODE_SECAM:
printv("Videostandard is PAL/SECAM\n");
v->dec.scanning = 625;
break;
default:
/*
* One last chance, we'll try to guess
* the scanning if GVBIFMT is available.
*/
printv("Videostandard unknown (%d)\n", mode);
v->dec.scanning = 0;
*strict = TRUE;
break;
}
}
static vbi_bool
get_videostd(vbi_capture_v4l *v, int fd, int *mode)
{
struct video_tuner vtuner;
struct video_channel vchan;
CLEAR (vtuner);
CLEAR (vchan);
if (0 == xioctl_fd (v, fd, VIDIOCGTUNER, &vtuner)) {
printv ("Driver supports VIDIOCGTUNER: "
"mode %d (0=PAL, 1=NTSC, 2=SECAM)\n", vtuner.mode);
*mode = vtuner.mode;
return TRUE;
} else if (0 == xioctl_fd (v, fd, VIDIOCGCHAN, &vchan)) {
printv ("Driver supports VIDIOCGCHAN: norm %d\n", vchan.norm);
*mode = vchan.norm;
return TRUE;
} else
printv("Driver doesn't support VIDIOCGTUNER or VIDIOCGCHAN\n");
return FALSE;
}
static int
probe_video_device(vbi_capture_v4l *v, const char *name, struct stat *vbi_stat )
{
struct stat vid_stat;
int video_fd;
if (stat(name, &vid_stat) == -1) {
printv("stat failed: %d, %s\n", errno, strerror(errno));
return -1;
}
if (!S_ISCHR(vid_stat.st_mode)) {
printv("%s is no character special file\n", name);
return -1;
}
if (major(vid_stat.st_rdev) != major(vbi_stat->st_rdev)) {
printv("Mismatch of major device number: "
"%s: %d, %d; vbi: %d, %d\n", name,
major(vid_stat.st_rdev), minor(vid_stat.st_rdev),
major(vbi_stat->st_rdev), minor(vbi_stat->st_rdev));
return -1;
}
/* when radio device is opened a running video capture is destroyed (v4l2) @TZO@ */
if (minor(vid_stat.st_rdev) >= 64) {
printv("Not a v4l video minor device number (i.e. >= 64): "
"%s: %d, %d\n", name,
major(vid_stat.st_rdev), minor(vid_stat.st_rdev));
return -1;
}
video_fd = device_open (v->capture.sys_log_fp, name, O_RDWR, 0);
if (-1 == video_fd) {
printv ("Cannot open %s: %d, %s\n",
name, errno, strerror(errno));
perm_check(v, name);
return -1;
}
if (!reverse_lookup(v, video_fd, vbi_stat)) {
device_close (v->capture.sys_log_fp, video_fd);
return -1;
}
return video_fd;
}
static vbi_bool
xopendir (const char * name,
DIR ** dir,
struct dirent ** dirent)
{
int saved_errno;
long int size;
int fd;
*dir = opendir (name);
if (NULL == *dir)
return FALSE;
fd = dirfd (*dir);
if (-1 == fd)
goto failure;
size = fpathconf (fd, _PC_NAME_MAX);
if (size <= 0)
goto failure;
size = MAX (size, (long int) sizeof ((*dirent)->d_name));
size += sizeof (**dirent) - sizeof ((*dirent)->d_name) + 1;
*dirent = calloc (1, size);
if (NULL == *dirent)
goto failure;
return TRUE;
failure:
saved_errno = errno;
closedir (*dir);
*dir = NULL;
errno = saved_errno;
return FALSE;
}
static int
open_video_dev(vbi_capture_v4l *v, struct stat *p_vbi_stat, vbi_bool do_dev_scan)
{
static const char * const video_devices[] = {
"/dev/video",
"/dev/video0",
"/dev/video1",
"/dev/video2",
"/dev/video3",
"/dev/v4l/video",
"/dev/v4l/video0",
"/dev/v4l/video1",
"/dev/v4l/video2",
"/dev/v4l/video3",
};
struct dirent *dirent;
struct dirent *pdirent;
DIR *dir;
int video_fd;
unsigned int i;
video_fd = -1;
for (i = 0; i < sizeof(video_devices) / sizeof(video_devices[0]); i++) {
printv("Try %s: ", video_devices[i]);
video_fd = probe_video_device(v, video_devices[i], p_vbi_stat);
if (video_fd != -1) {
v->p_video_name = strdup(video_devices[i]);
goto done;
}
}
if (do_dev_scan) {
/* @TOMZO@ note: this is insane - dev directory has typically ~4000 nodes */
printv("Traversing /dev\n");
if (!xopendir ("/dev", &dir, &dirent)) {
printv ("Cannot open /dev: %d, %s\n",
errno, strerror (errno));
perm_check (v, "/dev");
goto done;
}
while (0 == readdir_r (dir, dirent, &pdirent)
&& pdirent == dirent) {
char name[256];
snprintf (name, sizeof(name),
"/dev/%s", dirent->d_name);
printv("Try %s: ", name);
video_fd = probe_video_device(v, name, p_vbi_stat);
if (video_fd != -1) {
v->p_video_name = strdup(name);
free (dirent);
closedir (dir);
goto done;
}
}
printv("Traversing finished\n");
free (dirent);
closedir (dir);
}
errno = ENOENT;
done:
return video_fd;
}
static vbi_bool
guess_bttv_v4l(vbi_capture_v4l *v, int *strict,
int given_fd, int scanning)
{
struct stat vbi_stat;
int video_fd;
int mode = -1;
if (scanning) {
v->dec.scanning = scanning;
return TRUE;
}
printv("Attempt to guess the videostandard\n");
if (get_videostd(v, v->fd, &mode))
goto finish;
/*
* Bttv v4l has no VIDIOCGUNIT pointing back to
* the associated video device, now it's getting
* dirty. We'll walk /dev, first level of, and
* assume v4l major is still 81. Not tested with devfs.
*/
printv("Attempt to find a reverse VIDIOCGUNIT\n");
if (fstat(v->fd, &vbi_stat) == -1) {
printv("fstat failed: %d, %s\n", errno, strerror(errno));
goto finish;
}
if (!S_ISCHR(vbi_stat.st_mode)) {
printv("VBI device is no character special file, reject\n");
return FALSE;
}
if (major(vbi_stat.st_rdev) != 81) {
printv("VBI device CSF has major number %d, expect 81\n"
"Warning: will assume this is still a v4l device\n",
major(vbi_stat.st_rdev));
goto finish;
}
printv("VBI device type verified\n");
if (given_fd > -1) {
printv("Try suggested corresponding video fd\n");
if (reverse_lookup(v, given_fd, &vbi_stat)) {
if (get_videostd(v, given_fd, &mode))
v->fd_video = given_fd;
goto finish;
}
}
/* find video device path and open the device */
video_fd = open_video_dev(v, &vbi_stat, TRUE);
if (video_fd != -1) {
if (get_videostd(v, video_fd, &mode)) {
device_close (v->capture.sys_log_fp, video_fd);
return FALSE;
}
device_close (v->capture.sys_log_fp, video_fd);
}
finish:
set_scanning_from_mode(v, mode, strict);
return TRUE;
}
static vbi_bool
v4l_update_scanning(vbi_capture_v4l *v, int * p_strict)
{
int video_fd;
int mode = -1;
vbi_bool result = FALSE;
if ( get_videostd(v, v->fd, &mode) ) {
result = TRUE;
} else if (v->p_video_name != NULL) {
video_fd = device_open (v->capture.sys_log_fp,
v->p_video_name, O_RDWR, 0);
if (-1 != video_fd) {
if (get_videostd(v, video_fd, &mode)) {
result = TRUE;
}
device_close (v->capture.sys_log_fp, video_fd);
} else {
printv("Failed to open video device '%d': %s", errno, strerror(errno));
}
} else if (v->fd_video != -1) {
if (get_videostd(v, v->fd_video, &mode)) {
result = TRUE;
}
}
if (result)
set_scanning_from_mode(v, mode, p_strict);
return result;
}
static int
v4l_get_scanning(vbi_capture *vc)
{
vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture);
int strict;
int old_scanning = v->dec.scanning;
int new_scanning = -1;
if ( v4l_update_scanning(v, &strict) ) {
new_scanning = v->dec.scanning;
}
v->dec.scanning = old_scanning;
printv("Guessed video standard %d\n", new_scanning);
return new_scanning;
}
static vbi_bool
set_parameters(vbi_capture_v4l *v, struct vbi_format *p_vfmt, int *p_max_rate,
unsigned int *services, int strict,
char **errstr)
{
struct vbi_format vfmt_temp;
vbi_raw_decoder dec_temp;
unsigned int sup_services;
/* check if the driver supports CSVBIFMT: try with unchanged parameters */
if (v->has_s_fmt == -1) {
vfmt_temp = *p_vfmt;
v->has_s_fmt = (0 == xioctl (v, VIDIOCSVBIFMT, &vfmt_temp)
|| errno == EBUSY);
printv ("Driver does%s support VIDIOCSVBIFMT\n",
v->has_s_fmt ? "" : " not");
}
if (v->has_s_fmt == 0)
return TRUE;
/* Speculative, vbi_format is not documented */
printv("Attempt to set vbi capture parameters\n");
memset(&dec_temp, 0, sizeof(dec_temp));
sup_services = vbi_raw_decoder_parameters(&dec_temp, *services | v->services,
dec_temp.scanning, p_max_rate);
if ((sup_services & *services) == 0) {
asprintf(errstr, _("Sorry, %s (%s) cannot capture any of the "
"requested data services."),
v->p_dev_name, v->vcap.name);
return FALSE;
}
*services &= sup_services;
vfmt_temp = *p_vfmt;
memset(p_vfmt, 0, sizeof(*p_vfmt));
p_vfmt->sample_format = VIDEO_PALETTE_RAW;
p_vfmt->sampling_rate = dec_temp.sampling_rate;
p_vfmt->samples_per_line = dec_temp.bytes_per_line;
p_vfmt->start[0] = dec_temp.start[0];
p_vfmt->count[0] = dec_temp.count[1];
p_vfmt->start[1] = dec_temp.start[0];
p_vfmt->count[1] = dec_temp.count[1];
/* Single field allowed? */
if (!p_vfmt->count[0]) {
p_vfmt->start[0] = (dec_temp.scanning == 625) ? 6 : 10;
p_vfmt->count[0] = 1;
} else if (!p_vfmt->count[1]) {
p_vfmt->start[1] = (dec_temp.scanning == 625) ? 318 : 272;
p_vfmt->count[1] = 1;
}
if (0 == xioctl (v, VIDIOCSVBIFMT, p_vfmt))
return TRUE;
p_vfmt->sampling_rate = vfmt_temp.sampling_rate;
p_vfmt->samples_per_line = vfmt_temp.samples_per_line;
if (0 == xioctl (v, VIDIOCSVBIFMT, p_vfmt))
return TRUE;
/* XXX correct count */
p_vfmt->start[0] = vfmt_temp.start[0];
p_vfmt->start[1] = vfmt_temp.start[1];
if (0 == xioctl (v, VIDIOCSVBIFMT, p_vfmt))
return TRUE;
switch (errno) {
case EBUSY:
#ifndef REQUIRE_SVBIFMT
printv("VIDIOCSVBIFMT returned EBUSY, "
"will try the current parameters\n");
*p_vfmt = vfmt_temp;
return TRUE;
#endif
asprintf(errstr, _("Cannot initialize %s (%s), "
"the device is already in use."),
v->p_dev_name, v->vcap.name);
break;
case EINVAL:
if (strict < 2) {
printv("VIDIOCSVBIFMT returned EINVAL, "
"will try the current parameters\n");
*p_vfmt = vfmt_temp;
return TRUE;
}
break;
default:
asprintf(errstr, _("Could not set the vbi "
"capture parameters for %s (%s): %s."),
v->p_dev_name, v->vcap.name, strerror(errno));
/* guess = _("Maybe a bug in the driver or libzvbi."); */
break;
}
return FALSE;
}
static vbi_raw_decoder *
v4l_parameters(vbi_capture *vc)
{
vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture);
return &v->dec;
}
static void
v4l_delete(vbi_capture *vc)
{
vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture);
v4l_read_stop(v);
vbi_raw_decoder_destroy(&v->dec);
if (v->sliced_buffer.data)
free(v->sliced_buffer.data);
if (v->p_dev_name != NULL)
free(v->p_dev_name);
if (v->p_video_name != NULL)
free(v->p_video_name);
if (-1 != v->fd)
device_close (v->capture.sys_log_fp, v->fd);
free(v);
}
static VBI_CAPTURE_FD_FLAGS
v4l_get_fd_flags(vbi_capture *vc)
{
vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture);
VBI_CAPTURE_FD_FLAGS result;
result = VBI_FD_IS_DEVICE;
if (v->has_select)
result |= VBI_FD_HAS_SELECT;
return result;
}
static vbi_bool
v4l_set_video_path(vbi_capture *vc, const char * p_dev_video)
{
vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture);
if (v->p_video_name != NULL)
free(v->p_video_name);
v->p_video_name = strdup(p_dev_video);
return TRUE;
}
static int
v4l_get_fd(vbi_capture *vc)
{
vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture);
return v->fd;
}
static void
print_vfmt(const char *s, struct vbi_format *vfmt)
{
fprintf(stderr, "%sformat %08x, %d Hz, %d bpl, "
"F1 %d+%d, F2 %d+%d, flags %08x\n", s,
vfmt->sample_format,
vfmt->sampling_rate, vfmt->samples_per_line,
vfmt->start[0], vfmt->count[0],
vfmt->start[1], vfmt->count[1],
vfmt->flags);
}
static unsigned int
v4l_update_services(vbi_capture *vc,
vbi_bool reset, vbi_bool commit,
unsigned int services, int strict,
char ** errstr)
{
vbi_capture_v4l *v = PARENT(vc, vbi_capture_v4l, capture);
struct vbi_format vfmt;
int max_rate;
max_rate = 0;
/* suspend capturing, or driver will return EBUSY */
v4l_suspend(v);
if (reset) {
v4l_update_scanning(v, &strict);
vbi_raw_decoder_reset(&v->dec);
v->services = 0;
}
CLEAR (vfmt);
if (0 == xioctl (v, VIDIOCGVBIFMT, &vfmt)) {
if (vfmt.start[1] > 0 && vfmt.count[1]) {
if (vfmt.start[1] >= 286)
v->dec.scanning = 625;
else
v->dec.scanning = 525;
}
printv("Driver supports VIDIOCGVBIFMT, "
"guessed videostandard %d\n", v->dec.scanning);
if (v->do_trace)
print_vfmt("VBI capture parameters supported: ", &vfmt);
if (strict >= 0 && v->dec.scanning)
if (!set_parameters(v, &vfmt, &max_rate,
&services, strict,
errstr))
goto failure;
if (v->do_trace)
print_vfmt("VBI capture parameters granted: ", &vfmt);
printv("Accept current vbi parameters\n");
if (vfmt.sample_format != VIDEO_PALETTE_RAW) {
asprintf(errstr, _("%s (%s) offers unknown vbi "
"sampling format #%d. "
"This may be a driver bug "
"or libzvbi is too old."),
v->p_dev_name, v->vcap.name,
vfmt.sample_format);
goto io_error;
}
/* grow pattern array if necessary
** note: must do this even if service add fails later, to stay in sync with driver */
vbi_raw_decoder_resize(&v->dec, vfmt.start, vfmt.count);
v->dec.sampling_rate = vfmt.sampling_rate;
v->dec.bytes_per_line = vfmt.samples_per_line;
if (v->dec.scanning == 625)
/* v->dec.offset = (int)(10.2e-6 * vfmt.sampling_rate); */
v->dec.offset = (int)(6.8e-6 * vfmt.sampling_rate);
else if (v->dec.scanning == 525)
v->dec.offset = (int)(9.2e-6 * vfmt.sampling_rate);
else /* we don't know */
v->dec.offset = (int)(9.7e-6 * vfmt.sampling_rate);
v->dec.start[0] = vfmt.start[0];
v->dec.count[0] = vfmt.count[0];
v->dec.start[1] = vfmt.start[1];
v->dec.count[1] = vfmt.count[1];
v->dec.interlaced = !!(vfmt.flags & VBI_INTERLACED);
v->dec.synchronous = !(vfmt.flags & VBI_UNSYNC);
v->time_per_frame = (v->dec.scanning == 625) ?
1.0 / 25 : 1001.0 / 30000;
/* Unknown. */
v->has_select = FALSE;
} else {
int size;
/*
* If a more reliable method exists to identify the bttv
* driver I'll be glad to hear about it. Lesson: Don't
* call a v4l private IOCTL without knowing who's
* listening. All we know at this point: It's a csf, and
* it may be a v4l device.
* garetxe: This isn't reliable, bttv doesn't return
* anything useful in vcap.name.
*/
printv("Driver doesn't support VIDIOCGVBIFMT (errno %d), "
"will assume bttv interface\n", errno);
/* bttv 0.7.x has no select. 0.8+ supports VIDIOCGVBIFMT. */
v->has_select = FALSE;
if (0 && !strstr(v->vcap.name, "bttv")
&& !strstr(v->vcap.name, "BTTV")) {
asprintf(errstr, _("Cannot capture with %s (%s), "
"has no standard vbi interface."),
v->p_dev_name, v->vcap.name);
goto io_error;
}
v->dec.bytes_per_line = 2048;
v->dec.interlaced = FALSE;
v->dec.synchronous = TRUE;
printv("Attempt to determine vbi frame size\n");
size = xioctl (v, BTTV_VBISIZE, 0);
if (-1 == size) {
printv ("Driver does not support BTTV_VBISIZE, "
"assume old BTTV driver\n");
v->dec.count[0] = 16;
v->dec.count[1] = 16;
} else if (size % 2048) {
asprintf (errstr,
_("Cannot identify %s (%s), reported "
"vbi frame size suggests this is "
"not a bttv driver."),
v->p_dev_name, v->vcap.name);
goto io_error;
} else {
printv ("Driver supports BTTV_VBISIZE: %d bytes, "
"assume top field dominance and 2048 bpl\n",
size);
size /= 2048;
v->dec.count[0] = size >> 1;
v->dec.count[1] = size - v->dec.count[0];
}
switch (v->dec.scanning) {
default:
#ifdef REQUIRE_VIDEOSTD
asprintf(errstr, _("Cannot set or determine current "
"videostandard of %s (%s)."),
v->p_dev_name, v->vcap.name);
goto io_error;
#endif
printv("Warning: Videostandard not confirmed, "
"will assume PAL/SECAM\n");
v->dec.scanning = 625;
/* fall through */
case 625:
/* Not confirmed */
v->dec.sampling_rate = 35468950;
v->dec.offset = (int)(9.2e-6 * 35468950);
v->dec.start[0] = 22 + 1 - v->dec.count[0];
v->dec.start[1] = 335 + 1 - v->dec.count[1];
break;
case 525:
/* Confirmed for bttv 0.7.52 */
v->dec.sampling_rate = 28636363;
v->dec.offset = (int)(9.2e-6 * 28636363);
v->dec.start[0] = 10;
v->dec.start[1] = 273;
break;
}
v->time_per_frame =
(v->dec.scanning == 625) ? 1.0 / 25 : 1001.0 / 30000;
}
v->dec.sampling_format = VBI_PIXFMT_YUV420;
if (services & ~(VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625)) {
/* Nyquist */
if (v->dec.sampling_rate < max_rate * 3 / 2) {
asprintf(errstr, _("Cannot capture the requested "
"data services with "
"%s (%s), the sampling frequency "
"%.2f MHz is too low."),
v->p_dev_name, v->vcap.name,
v->dec.sampling_rate / 1e6);
services = 0;
goto failure;
}
printv("Nyquist check passed\n");
printv("Request decoding of services 0x%08x, strict level %d\n", services, strict);
/* those services which are already set must be checked for strictness */
if ( (strict > 0) && ((services & v->dec.services) != 0) ) {
unsigned int tmp_services;
tmp_services = vbi_raw_decoder_check_services(&v->dec, services & v->dec.services, strict);
/* mask out unsupported services */
services &= tmp_services | ~(services & v->dec.services);
}
if ( (services & ~v->dec.services) != 0 )
services &= vbi_raw_decoder_add_services(&v->dec,
services & ~ v->dec.services,
strict);
if (services == 0) {
asprintf(errstr, _("Sorry, %s (%s) cannot "
"capture any of "
"the requested data services."),
v->p_dev_name, v->vcap.name);
goto failure;
}
if (v->sliced_buffer.data != NULL)
free(v->sliced_buffer.data);
v->sliced_buffer.data =
malloc((v->dec.count[0] + v->dec.count[1])
* sizeof(vbi_sliced));
if (!v->sliced_buffer.data) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
goto failure;
}
}
failure:
v->services |= services;
printv("Will capture services 0x%08x, added 0x%0x commit:%d\n", v->services, services, commit);
if (commit && (v->services != 0))
v4l_read_alloc(v, errstr);
return services;
io_error:
return 0;
}
static vbi_capture *
v4l_new(const char *dev_name, int given_fd, int scanning,
unsigned int *services, int strict,
char **errstr, vbi_bool trace)
{
char *error = NULL;
vbi_capture_v4l *v;
pthread_once (&vbi_init_once, vbi_init);
if (!errstr)
errstr = &error;
*errstr = NULL;
if (scanning != 525 && scanning != 625)
scanning = 0;
if (!(v = (vbi_capture_v4l *) calloc(1, sizeof(*v)))) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
goto failure;
}
vbi_raw_decoder_init (&v->dec);
v->do_trace = trace;
printv ("Try to open v4l vbi device, "
"libzvbi interface rev.\n %s\n", rcsid);
v->capture.parameters = v4l_parameters;
v->capture._delete = v4l_delete;
v->capture.get_fd = v4l_get_fd;
v->capture.get_fd_flags = v4l_get_fd_flags;
v->capture.read = v4l_read;
v->capture.update_services = v4l_update_services;
v->capture.get_scanning = v4l_get_scanning;
v->capture.flush = v4l_flush;
v->capture.set_video_path = v4l_set_video_path;
if (0)
v->capture.sys_log_fp = stderr;
v->p_dev_name = strdup(dev_name);
if (v->p_dev_name == NULL) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
goto failure;
}
v->fd = device_open (v->capture.sys_log_fp,
v->p_dev_name, O_RDONLY, 0);
if (-1 == v->fd) {
asprintf (errstr, _("Cannot open '%s': %d, %s."),
v->p_dev_name, errno, strerror(errno));
perm_check(v, v->p_dev_name);
goto io_error;
}
printv("Opened %s\n", v->p_dev_name);
/* used to store given_fd if necessary */
v->fd_video = -1;
if (-1 == xioctl (v, VIDIOCGCAP, &v->vcap)) {
/*
* Older bttv drivers don't support any
* v4l ioctls, let's see if we can guess the beast.
*/
printv("Driver doesn't support VIDIOCGCAP\n");
strncpy(v->vcap.name, _("driver unknown"), sizeof(v->vcap.name) - 1);
v->vcap.name[sizeof(v->vcap.name) - 1] = 0;
if (!guess_bttv_v4l(v, &strict, given_fd, scanning))
goto failure;
} else {
if (v->vcap.name[0] != 0) {
printv("Driver name '%s'\n", v->vcap.name);
} else {
strncpy(v->vcap.name, _("driver unknown"), sizeof(v->vcap.name) - 1);
v->vcap.name[sizeof(v->vcap.name) - 1] = 0;
}
if (!(v->vcap.type & VID_TYPE_TELETEXT)) {
asprintf(errstr,
_("%s (%s) is not a raw vbi device."),
v->p_dev_name, v->vcap.name);
goto failure;
}
guess_bttv_v4l(v, &strict, given_fd, scanning);
}
printv("%s (%s) is a v4l vbi device\n", v->p_dev_name, v->vcap.name);
v->has_select = FALSE; /* FIXME if possible */
v->has_s_fmt = -1;
v->read_active = FALSE;
printv("Hinted video standard %d, guessed %d\n",
scanning, v->dec.scanning);
#ifdef REQUIRE_SELECT
if (!v->select) {
asprintf(errstr, _("%s (%s) does not support "
"the select() function."),
v->p_dev_name, v->vcap.name);
goto failure;
}
#endif
v->services = 0;
if (services != NULL) {
assert(*services != 0);
v->services = v4l_update_services(&v->capture, FALSE, TRUE,
*services, strict, errstr);
if (v->services == 0) {
goto failure;
}
*services = v->services;
if (!v->dec.scanning && strict >= 1) {
printv("Try to guess video standard from vbi bottom field "
"boundaries: start=%d, count=%d\n",
v->dec.start[1], v->dec.count[1]);
if (v->dec.start[1] <= 0 || !v->dec.count[1]) {
/*
* We may have requested single field capture
* ourselves, but then we had guessed already.
*/
#ifdef REQUIRE_VIDEOSTD
asprintf(errstr, _("Cannot set or determine current "
"videostandard of %s (%s)."),
v->p_dev_name, v->vcap.name);
goto failure;
#endif
printv("Warning: Videostandard not confirmed, "
"will assume PAL/SECAM\n");
v->dec.scanning = 625;
v->time_per_frame = 1.0 / 25;
} else if (v->dec.start[1] < 286) {
v->dec.scanning = 525;
v->time_per_frame = 1001.0 / 30000;
} else {
v->dec.scanning = 625;
v->time_per_frame = 1.0 / 25;
}
}
printv("Guessed videostandard %d\n", v->dec.scanning);
}
if (!v->has_select)
printv("Warning: no read select, reading will block\n");
printv("Successful opened %s (%s)\n",
v->p_dev_name, v->vcap.name);
if (errstr == &error) {
free (error);
error = NULL;
}
return &v->capture;
failure:
io_error:
if (v)
v4l_delete(&v->capture);
if (errstr == &error) {
free (error);
error = NULL;
}
return NULL;
}
vbi_capture *
vbi_capture_v4l_sidecar_new(const char *dev_name, int video_fd,
unsigned int *services, int strict,
char **errstr, vbi_bool trace)
{
return v4l_new(dev_name, video_fd, 0,
services, strict, errstr, trace);
}
vbi_capture *
vbi_capture_v4l_new(const char *dev_name, int scanning,
unsigned int *services, int strict,
char **errstr, vbi_bool trace)
{
return v4l_new(dev_name, -1, scanning,
services, strict, errstr, trace);
}
#else
/**
* @param dev_name Name of the device to open, usually one of
* @c /dev/vbi or @c /dev/vbi0 and up.
* @param given_fd File handle of an already open video device,
* usually one of @c /dev/video or @c /dev/video0 and up.
* Must be assorted with the named vbi device, i.e. refer to
* the same driver instance and hardware.
* @param services This must point to a set of @ref VBI_SLICED_
* symbols describing the
* data services to be decoded. On return the services actually
* decodable will be stored here. See vbi_raw_decoder_add()
* for details. If you want to capture raw data only, set to
* @c VBI_SLICED_VBI_525, @c VBI_SLICED_VBI_625 or both.
* If this parameter is @c NULL, no services will be installed.
* You can do so later with vbi_capture_update_services(); note the
* reset parameter must be set to @c TRUE in this case.
* @param strict Will be passed to vbi_raw_decoder_add().
* @param errstr If not @c NULL this function stores a pointer to an error
* description here. You must free() this string when no longer needed.
* @param trace If @c TRUE print progress messages on stderr.
*
* This functions behaves much like vbi_capture_v4l_new, with the sole
* difference that it uses the given file handle to determine the current
* video standard if such queries aren't supported by the VBI device.
*
* @return
* Initialized vbi_capture context, @c NULL on failure.
*/
vbi_capture *
vbi_capture_v4l_sidecar_new(const char *dev_name, int given_fd,
unsigned int *services, int strict,
char **errstr, vbi_bool trace)
{
pthread_once (&vbi_init_once, vbi_init);
if (errstr)
asprintf(errstr, _("V4L driver interface not compiled."));
return NULL;
}
/**
* @param dev_name Name of the device to open, usually one of
* @c /dev/vbi or @c /dev/vbi0 and up.
* @param scanning Can be used to specify the current TV norm for
* old drivers which don't support ioctls to query the current
* norm. Value is 625 (PAL/SECAM family) or 525 (NTSC family).
* Set to 0 if you don't know the norm.
* @param services This must point to a set of @ref VBI_SLICED_
* symbols describing the
* data services to be decoded. On return the services actually
* decodable will be stored here. See vbi_raw_decoder_add()
* for details. If you want to capture raw data only, set to
* @c VBI_SLICED_VBI_525, @c VBI_SLICED_VBI_625 or both.
* If this parameter is @c NULL, no services will be installed.
* You can do so later with vbi_capture_update_services(); note the
* reset parameter must be set to @c TRUE in this case.
* @param strict Will be passed to vbi_raw_decoder_add().
* @param errstr If not @c NULL this function stores a pointer to an error
* description here. You must free() this string when no longer needed.
* @param trace If @c TRUE print progress messages on stderr.
*
* @return
* Initialized vbi_capture context, @c NULL on failure.
*/
vbi_capture *
vbi_capture_v4l_new(const char *dev_name, int scanning,
unsigned int *services, int strict,
char **errstr, vbi_bool trace)
{
dev_name = dev_name;
scanning = scanning;
services = services;
strict = strict;
pthread_once (&vbi_init_once, vbi_init);
if (trace)
fprintf (stderr, "Libzvbi V4L interface rev.\n %s\n", rcsid);
if (errstr)
asprintf (errstr, _("V4L driver interface not compiled."));
return NULL;
}
#endif /* !ENABLE_V4L */
syntax highlighted by Code2HTML, v. 0.9.1