/*
* libzvbi - V4L2 (version 2002-10 and later) interface
*
* Copyright (C) 2002-2005 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-v4l2k.c,v 1.42 2006/09/24 03:10:04 mschimek Exp $";
/*
* Around Oct-Nov 2002 the V4L2 API was revised for inclusion into
* Linux 2.5/2.6. There are a few subtle differences, in order to
* keep the source clean this interface has been forked off from the
* old V4L2 interface. "v4l2k" is no official designation, there is
* none, take it as v4l2-kernel or v4l-2000.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "vbi.h"
#include "io.h"
#ifdef ENABLE_V4L2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <unistd.h> /* read(), dup2() */
#include <assert.h>
#include <sys/time.h> /* timeval */
#include <sys/types.h> /* fd_set */
#include <sys/ioctl.h> /* for (_)videodev2k.h */
#include <sys/mman.h> /* PROT_READ, MAP_SHARED */
#include <asm/types.h> /* for videodev2k.h */
#include <pthread.h>
#include "raw_decoder.h"
#include "version.h"
#include "videodev2k.h"
#include "_videodev2k.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)))
#undef REQUIRE_SELECT
#undef REQUIRE_G_FMT /* before S_FMT */
#undef REQUIRE_S_FMT /* else accept current format */
#define ENQUEUE_SUSPENDED -3
#define ENQUEUE_STREAM_OFF -2
#define ENQUEUE_BUFS_QUEUED -1
#define ENQUEUE_IS_UNQUEUED(X) ((X) >= 0)
#define FLUSH_FRAME_COUNT 2
typedef struct vbi_capture_v4l2 {
vbi_capture capture;
int fd;
vbi_bool close_me;
int btype; /* v4l2 stream type */
vbi_bool streaming;
vbi_bool read_active;
int has_try_fmt;
int enqueue;
struct v4l2_buffer vbuf;
struct v4l2_capability vcap;
char * p_dev_name;
vbi_sampling_par sp;
vbi3_raw_decoder rd;
unsigned int services; /* all services, including raw */
double time_per_frame;
vbi_capture_buffer *raw_buffer;
unsigned int num_raw_buffers;
int buf_req_count;
vbi_capture_buffer sliced_buffer;
int flush_frame_count;
vbi_bool pal_start1_fix;
vbi_bool saa7134_ntsc_fix;
vbi_bool bttv_offset_fix;
vbi_bool cx88_ntsc_fix;
vbi_bool bttv_min_start_fix;
vbi_bool bttv_ntsc_rate_fix;
_vbi_log_hook log;
} vbi_capture_v4l2;
static void
vbi_sliced_data_from_raw (vbi_capture_v4l2 * v,
vbi_capture_buffer ** sliced,
const vbi_capture_buffer *raw)
{
vbi_capture_buffer *b;
unsigned int max_lines;
unsigned int n_lines;
assert (NULL != sliced);
b = *sliced;
if (NULL == b) {
/* Store sliced data in our buffer
and return a buffer pointer. */
b = &v->sliced_buffer;
*sliced = b;
} else {
/* Store sliced data in client buffer. */
}
max_lines = v->sp.count[0] + v->sp.count[1];
n_lines = vbi3_raw_decoder_decode (&v->rd,
(vbi_sliced *) b->data,
max_lines,
(uint8_t *) raw->data);
b->size = n_lines * sizeof (vbi_sliced);
b->timestamp = raw->timestamp;
}
static void
v4l2_stream_stop(vbi_capture_v4l2 *v)
{
if (v->enqueue >= ENQUEUE_BUFS_QUEUED) {
info (&v->log, "Suspending stream.");
if (-1 == xioctl (v, VIDIOC_STREAMOFF, &v->btype)) {
/* Error ignored. */
}
}
for (; v->num_raw_buffers > 0; v->num_raw_buffers--) {
device_munmap (v->capture.sys_log_fp,
v->raw_buffer[v->num_raw_buffers - 1].data,
v->raw_buffer[v->num_raw_buffers - 1].size);
}
if (v->raw_buffer != NULL) {
free(v->raw_buffer);
v->raw_buffer = NULL;
}
v->enqueue = ENQUEUE_SUSPENDED;
}
static int
v4l2_stream_alloc(vbi_capture_v4l2 *v, char ** errstr)
{
struct v4l2_requestbuffers vrbuf;
struct v4l2_buffer vbuf;
char * guess = NULL;
int errno_copy;
assert(v->enqueue == ENQUEUE_SUSPENDED);
assert(v->raw_buffer == NULL);
info (&v->log,
"Requesting %d streaming i/o buffers.",
v->buf_req_count);
memset(&vrbuf, 0, sizeof(vrbuf));
vrbuf.type = v->btype;
vrbuf.count = v->buf_req_count;
vrbuf.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl (v, VIDIOC_REQBUFS, &vrbuf)) {
asprintf (errstr,
_("Cannot request streaming i/o buffers "
"from %s (%s): %s."),
v->p_dev_name, v->vcap.card,
strerror(errno));
guess = _("Possibly a driver bug.");
goto failure;
}
if (vrbuf.count == 0) {
asprintf(errstr, _("%s (%s) granted no streaming i/o buffers, "
"perhaps the physical memory is exhausted."),
v->p_dev_name, v->vcap.card);
goto failure;
}
info (&v->log,
"Mapping %d streaming i/o buffers.",
vrbuf.count);
v->raw_buffer = calloc(vrbuf.count, sizeof(v->raw_buffer[0]));
if (v->raw_buffer == NULL) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
goto failure;
}
v->num_raw_buffers = 0;
while (v->num_raw_buffers < vrbuf.count) {
uint8_t *p;
vbuf.type = v->btype;
vbuf.index = v->num_raw_buffers;
if (-1 == xioctl (v, VIDIOC_QUERYBUF, &vbuf)) {
asprintf (errstr,
_("Querying streaming i/o buffer #%d "
"from %s (%s) failed: %s."),
v->num_raw_buffers, v->p_dev_name,
v->vcap.card, strerror(errno));
goto mmap_failure;
}
p = device_mmap (v->capture.sys_log_fp,
/* start any */ NULL,
vbuf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED, /* MAP_PRIVATE ? */
v->fd,
vbuf.m.offset);
/* V4L2 spec requires PROT_WRITE regardless if we write
buffers, but broken drivers might reject it. */
if (MAP_FAILED == p)
p = device_mmap (v->capture.sys_log_fp,
/* start any */ NULL,
vbuf.length,
PROT_READ,
MAP_SHARED,
v->fd,
vbuf.m.offset);
if (MAP_FAILED == p) {
if (errno == ENOMEM && v->num_raw_buffers >= 2) {
info (&v->log,
"Memory mapping buffer #%d "
"failed with errno %d (ignored).",
v->num_raw_buffers, errno);
break;
}
asprintf(errstr, _("Memory mapping streaming i/o buffer #%d "
"from %s (%s) failed: %s."),
v->num_raw_buffers, v->p_dev_name, v->vcap.card,
strerror(errno));
goto mmap_failure;
} else {
unsigned int i, s;
v->raw_buffer[v->num_raw_buffers].data = p;
v->raw_buffer[v->num_raw_buffers].size = vbuf.length;
for (i = s = 0; i < vbuf.length; i++)
s += p[i];
if (s % vbuf.length) {
fprintf(stderr,
"Security warning: driver %s (%s) seems to mmap "
"physical memory uncleared. Please contact the "
"driver author.\n", v->p_dev_name, v->vcap.card);
exit(EXIT_FAILURE);
}
}
if (-1 == xioctl (v, VIDIOC_QBUF, &vbuf)) {
asprintf (errstr,
_("Cannot enqueue streaming i/o buffer "
"#%d to %s (%s): %s."),
v->num_raw_buffers, v->p_dev_name,
v->vcap.card, strerror(errno));
guess = _("Probably a driver bug.");
goto mmap_failure;
}
v->num_raw_buffers++;
}
v->enqueue = ENQUEUE_STREAM_OFF;
return 0;
mmap_failure:
errno_copy = errno;
v4l2_stream_stop(v);
errno = errno_copy;
failure:
info (&v->log,
"Failed with errno %d, errmsg '%s'.",
errno, *errstr);
return -1;
}
static vbi_bool
restart_stream (vbi_capture_v4l2 * v)
{
unsigned int i;
if (-1 == xioctl (v, VIDIOC_STREAMOFF, &v->btype))
return FALSE;
for (i = 0; i < v->num_raw_buffers; ++i) {
struct v4l2_buffer vbuf;
CLEAR (vbuf);
vbuf.index = i;
vbuf.type = v->btype;
if (-1 == xioctl (v, VIDIOC_QBUF, &vbuf))
return FALSE;
}
if (-1 == xioctl (v, VIDIOC_STREAMON, &v->btype))
return FALSE;
return TRUE;
}
static int
v4l2_stream(vbi_capture *vc, vbi_capture_buffer **raw,
vbi_capture_buffer **sliced, const struct timeval *timeout_orig)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
struct timeval timeout = *timeout_orig;
vbi_capture_buffer *b;
int r;
if ((v->enqueue == ENQUEUE_SUSPENDED) || (v->services == 0)) {
/* stream was suspended (add_services not committed) */
error (&v->log, "No services set or not committed.");
errno = ESRCH;
return -1;
}
if (v->enqueue == ENQUEUE_STREAM_OFF) {
if (-1 == xioctl (v, VIDIOC_STREAMON, &v->btype)) {
error (&v->log,
"Failed to enable streaming, errno %d.",
errno);
return -1;
}
} else if (ENQUEUE_IS_UNQUEUED(v->enqueue)) {
v->vbuf.type = v->btype;
v->vbuf.index = v->enqueue;
if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf)) {
error (&v->log,
"Failed to enqueue previous "
"buffer, errno %d.",
errno);
return -1;
}
}
v->enqueue = ENQUEUE_BUFS_QUEUED;
while (1)
{
/* wait for the next frame */
r = vbi_capture_io_select(v->fd, &timeout);
if (r <= 0) {
if (r < 0) {
error (&v->log,
"select() failed with errno %d.",
errno);
}
return r;
}
v->vbuf.type = v->btype;
/* retrieve the captured frame from the queue */
r = xioctl (v, VIDIOC_DQBUF, &v->vbuf);
if (-1 == r) {
int saved_errno;
saved_errno = errno;
error (&v->log,
"Failed to dequeue buffer, errno %d.",
errno);
/* On EIO bttv dequeues the buffer, or it does not,
or it resets the hardware (SCERR). QBUF alone
is insufficient.
Actually the caller should restart on error. */
/* Errors ignored. */
restart_stream (v);
errno = saved_errno;
return -1;
}
if (v->flush_frame_count > 0) {
v->flush_frame_count -= 1;
info (&v->log,
"Skipping frame (%d remaining).",
v->flush_frame_count);
if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf)) {
error (&v->log,
"Failed to enqueue buffer, errno %d.",
errno);
return -1;
}
} else {
break;
}
}
assert (v->vbuf.index < v->num_raw_buffers);
b = &v->raw_buffer[v->vbuf.index];
b->timestamp = v->vbuf.timestamp.tv_sec
+ v->vbuf.timestamp.tv_usec * (1 / 1e6);
if (NULL != raw) {
vbi_capture_buffer *r;
r = *raw;
if (NULL == r) {
/* Store raw data in our buffer
and return a buffer pointer. */
*raw = b;
/* Keep this buffer out of the queue. */
v->enqueue = v->vbuf.index;
} else {
/* Store raw data in client buffer. */
memcpy (r->data, b->data, b->size);
/* FIXME client should pass max buffer size. */
r->size = b->size;
r->timestamp = b->timestamp;
}
}
if (NULL != sliced) {
vbi_sliced_data_from_raw (v, sliced, b);
}
/* if no raw pointer returned to the caller, re-queue buffer immediately
** else the buffer is re-queued upon the next call to read() */
if (v->enqueue == ENQUEUE_BUFS_QUEUED) {
if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf)) {
error (&v->log,
"Failed to queue buffer, errno %d.",
errno);
return -1;
}
}
return 1;
}
static void
v4l2_stream_flush(vbi_capture *vc)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
struct timeval tv;
unsigned int max_loop;
/* stream not enabled yet -> nothing to flush */
if ( (v->enqueue == ENQUEUE_SUSPENDED) ||
(v->enqueue == ENQUEUE_STREAM_OFF) )
return;
if (ENQUEUE_IS_UNQUEUED(v->enqueue)) {
v->vbuf.type = v->btype;
v->vbuf.index = v->enqueue;
if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf)) {
error (&v->log,
"Failed to enqueue buffer, errno %d.",
errno);
return;
}
}
v->enqueue = ENQUEUE_BUFS_QUEUED;
for (max_loop = 0; max_loop < v->num_raw_buffers; max_loop++) {
/* check if there are any buffers pending for de-queueing */
/* use zero timeout to prevent select() from blocking */
memset(&tv, 0, sizeof(tv));
if (vbi_capture_io_select(v->fd, &tv) <= 0)
return;
if ((-1 == xioctl (v, VIDIOC_DQBUF, &v->vbuf))
&& (errno != EIO))
return;
/* immediately queue the buffer again, thereby discarding it's content */
if (-1 == xioctl (v, VIDIOC_QBUF, &v->vbuf))
return;
}
}
static void
v4l2_read_stop(vbi_capture_v4l2 *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
v4l2_suspend(vbi_capture_v4l2 *v)
{
int fd;
if (v->streaming) {
v4l2_stream_stop(v);
}
else {
v4l2_read_stop(v);
if (v->read_active) {
info (&v->log, "Reopen device.");
/* hack: cannot suspend read to allow S_FMT, need to close device */
fd = device_open (v->capture.sys_log_fp,
v->p_dev_name, O_RDWR, 0);
if (fd == -1) {
error (&v->log,
"Failed to reopen device, "
"errno %d.",
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
v4l2_read_alloc(vbi_capture_v4l2 *v, char ** errstr)
{
assert(v->raw_buffer == NULL);
v->raw_buffer = calloc(1, sizeof(v->raw_buffer[0]));
if (!v->raw_buffer) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
goto failure;
}
v->raw_buffer[0].size = (v->sp.count[0] + v->sp.count[1])
* v->sp.bytes_per_line;
v->raw_buffer[0].data = malloc(v->raw_buffer[0].size);
if (!v->raw_buffer[0].data) {
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;
info (&v->log, "Capture buffer allocated.");
return 0;
failure:
info (&v->log,
"Failed with errno %d, errmsg '%s'.",
errno, *errstr);
return -1;
}
static int
v4l2_read_frame(vbi_capture_v4l2 *v, vbi_capture_buffer *raw, struct timeval *timeout)
{
int r;
/* wait until data is available or timeout expires */
r = vbi_capture_io_select(v->fd, timeout);
if (r <= 0) {
if (r < 0) {
error (&v->log,
"select() failed with errno %d.",
errno);
}
return r;
}
v->read_active = TRUE;
for (;;) {
/* from zapping/libvbi/v4lx.c */
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
v4l2_read(vbi_capture *vc, vbi_capture_buffer **raw,
vbi_capture_buffer **sliced, const struct timeval *timeout)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
vbi_capture_buffer *my_raw = v->raw_buffer;
struct timeval tv;
int r;
if ((my_raw == NULL) || (v->services == 0)) {
info (&v->log,
"No services set or not committed.");
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;
while (1)
{
r = v4l2_read_frame(v, *raw, &tv);
if (r <= 0)
return r;
if (v->flush_frame_count > 0) {
v->flush_frame_count -= 1;
info (&v->log,
"Skipping frame (%d remaining).",
v->flush_frame_count);
}
else
break;
}
gettimeofday(&tv, NULL);
(*raw)->timestamp = tv.tv_sec + tv.tv_usec * (1 / 1e6);
if (sliced) {
vbi_sliced_data_from_raw (v, sliced, *raw);
}
return 1;
}
static void v4l2_read_flush(vbi_capture *vc)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
struct timeval tv;
int r;
if ( (v->raw_buffer == NULL) || (v->read_active == FALSE) )
return;
memset(&tv, 0, sizeof(tv));
r = vbi_capture_io_select(v->fd, &tv);
if (r <= 0)
return;
do
{
r = read(v->fd, v->raw_buffer->data, v->raw_buffer->size);
}
while ((r < 0) && (errno == EINTR));
}
static vbi_bool
v4l2_get_videostd(vbi_capture_v4l2 *v, char ** errstr)
{
struct v4l2_standard vstd;
v4l2_std_id stdid;
unsigned int i;
int r;
char * guess = NULL;
if (-1 == xioctl (v, VIDIOC_G_STD, &stdid)) {
asprintf (errstr,
_("Cannot query current videostandard "
"of %s (%s): %s."),
v->p_dev_name, v->vcap.card,
strerror(errno));
guess = _("Probably a driver bug.");
goto failure;
}
for (i = 0; i < 100; ++i) {
CLEAR (vstd);
vstd.index = i;
r = xioctl (v, VIDIOC_ENUMSTD, &vstd);
if (-1 == r)
break;
if (vstd.id & stdid)
break;
}
if (i >= 100) {
r = -1;
errno = 0;
}
if (-1 == r) {
asprintf(errstr, _("Cannot query current "
"videostandard of %s (%s): %s."),
v->p_dev_name, v->vcap.card, strerror(errno));
guess = _("Probably a driver bug.");
goto failure;
}
info (&v->log,
"Current scanning system is %d.",
vstd.framelines);
/* add_vbi_services() eliminates non 525/625 */
v->sp.scanning = vstd.framelines;
return TRUE;
failure:
info (&v->log,
"Failed with errno %d, errmsg '%s'.",
errno, *errstr);
return FALSE;
}
static int
v4l2_get_scanning(vbi_capture *vc)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
int old_scanning = v->sp.scanning;
int new_scanning = -1;
if ( v4l2_get_videostd(v, NULL) ) {
new_scanning = v->sp.scanning;
}
v->sp.scanning = old_scanning;
return new_scanning;
}
static void
print_vfmt (vbi_capture_v4l2 * v,
const char * s,
struct v4l2_format * vfmt)
{
if (0 == (v->log.mask & VBI_LOG_INFO))
return;
_vbi_log_printf (v->log.fn, v->log.user_data,
VBI_LOG_INFO, __FUNCTION__,
"%sformat %08x [%c%c%c%c], %d Hz, %d bpl, offs %d, "
"F1 %d...%d, F2 %d...%d, flags %08x.", s,
vfmt->fmt.vbi.sample_format,
(char)((vfmt->fmt.vbi.sample_format ) & 0xff),
(char)((vfmt->fmt.vbi.sample_format >> 8) & 0xff),
(char)((vfmt->fmt.vbi.sample_format >> 16) & 0xff),
(char)((vfmt->fmt.vbi.sample_format >> 24) & 0xff),
vfmt->fmt.vbi.sampling_rate,
vfmt->fmt.vbi.samples_per_line,
vfmt->fmt.vbi.offset,
vfmt->fmt.vbi.start[0],
vfmt->fmt.vbi.start[0] + vfmt->fmt.vbi.count[0] - 1,
vfmt->fmt.vbi.start[1],
vfmt->fmt.vbi.start[1] + vfmt->fmt.vbi.count[1] - 1,
vfmt->fmt.vbi.flags);
}
static unsigned int
v4l2_update_services(vbi_capture *vc,
vbi_bool reset, vbi_bool commit,
unsigned int services, int strict,
char ** errstr)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
struct v4l2_format vfmt;
unsigned int max_rate;
int g_fmt;
int s_fmt;
char * guess = NULL;
/* suspend capturing, or driver will return EBUSY */
v4l2_suspend(v);
if (reset) {
/* query current norm */
if (v4l2_get_videostd(v, errstr) == FALSE)
goto io_error;
vbi3_raw_decoder_reset (&v->rd);
v->services = 0;
}
memset(&vfmt, 0, sizeof(vfmt));
vfmt.type = v->btype = V4L2_BUF_TYPE_VBI_CAPTURE;
max_rate = 0;
info (&v->log, "Querying current vbi parameters...");
g_fmt = xioctl (v, VIDIOC_G_FMT, &vfmt);
if (-1 == g_fmt) {
info (&v->log,
"...failed with errno %d.",
errno);
#ifdef REQUIRE_G_FMT
asprintf(errstr, _("Cannot query current "
"vbi parameters of %s (%s): %s."),
v->p_dev_name, v->vcap.card, strerror(errno));
goto io_error;
#else
strict = MAX(0, strict);
#endif
} else {
info (&v->log, "...success.");
print_vfmt (v, "VBI capture parameters supported: ", &vfmt);
if (v->has_try_fmt == -1) {
struct v4l2_format vfmt_temp = vfmt;
/* test if TRY_FMT is available by feeding it the current
** parameters, which should always succeed */
v->has_try_fmt =
(0 == xioctl (v, VIDIOC_TRY_FMT, &vfmt_temp));
}
}
if (strict >= 0) {
struct v4l2_format vfmt_temp = vfmt;
vbi_sampling_par dec_temp;
unsigned int f2_offset;
unsigned int sup_services;
int r;
info (&v->log, "Attempt to set vbi capture parameters.");
memset(&dec_temp, 0, sizeof(dec_temp));
sup_services = _vbi_sampling_par_from_services_log
(&dec_temp, &max_rate,
_vbi_videostd_set_from_scanning (v->sp.scanning),
services | v->services, &v->log);
services &= sup_services;
if (0 == services) {
asprintf(errstr,
_("Sorry, %s (%s) cannot capture any of the "
"requested data services with scanning %d."),
v->p_dev_name, v->vcap.card, v->sp.scanning);
goto failure;
}
vfmt.fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
vfmt.fmt.vbi.sampling_rate = dec_temp.sampling_rate;
vfmt.fmt.vbi.samples_per_line = dec_temp.bytes_per_line;
vfmt.fmt.vbi.offset = dec_temp.offset;
vfmt.fmt.vbi.start[0] = dec_temp.start[0];
vfmt.fmt.vbi.count[0] = dec_temp.count[0];
vfmt.fmt.vbi.start[1] = dec_temp.start[1];
vfmt.fmt.vbi.count[1] = dec_temp.count[1];
f2_offset = (625 == v->sp.scanning) ? 312 : 263;
/* Some broken drivers may take start (= 0) into account
despite count being zero. */
if (0 == vfmt.fmt.vbi.count[1]) {
vfmt.fmt.vbi.start[1] =
vfmt.fmt.vbi.start[0] + f2_offset;
} else if (0 == vfmt.fmt.vbi.count[0]) {
vfmt.fmt.vbi.start[0] =
vfmt.fmt.vbi.start[1] - f2_offset;
}
if (v->bttv_min_start_fix) {
int min_start[2];
unsigned int i;
/* Captures MAX (count[0], count[1]) lines
starting at min_start, ignoring the
requested start: proxy-test vps. */
if (625 == v->sp.scanning) {
min_start[0] = 7;
min_start[1] = 320;
} else {
min_start[0] = 10;
min_start[1] = 273;
}
for (i = 0; i < 2; ++i) {
if (vfmt.fmt.vbi.count[i] > 0) {
vfmt.fmt.vbi.count[i] +=
(int) vfmt.fmt.vbi.start[i]
- min_start[i];
vfmt.fmt.vbi.start[i] = min_start[i];
}
}
}
/* 0 == count check omitted because above we made
sure start is valid and drivers should ignore
it in this case anyway. */
if (v->pal_start1_fix
&& 625 == v->sp.scanning) {
vfmt.fmt.vbi.start[1] -= 1;
}
if (v->saa7134_ntsc_fix
&& 525 == v->sp.scanning) {
vfmt.fmt.vbi.start[0] += 6;
vfmt.fmt.vbi.start[1] += 6;
}
print_vfmt (v, "VBI capture parameters requested: ", &vfmt);
if ((v->has_try_fmt != 1) || commit) {
s_fmt = VIDIOC_S_FMT;
/* Arg type check requires constant cmd number. */
r = xioctl (v, VIDIOC_S_FMT, &vfmt);
} else {
s_fmt = VIDIOC_TRY_FMT;
r = xioctl (v, VIDIOC_TRY_FMT, &vfmt);
}
if (-1 == r) {
switch (errno) {
case EBUSY:
#ifndef REQUIRE_S_FMT
if (g_fmt != -1) {
info (&v->log,
"VIDIOC_S_FMT returned EBUSY, "
"will try the current "
"parameters.");
vfmt = vfmt_temp;
break;
}
#endif
asprintf(errstr, _("Cannot initialize %s (%s), "
"the device is already in use."),
v->p_dev_name, v->vcap.card);
goto io_error;
default:
asprintf(errstr, _("Could not set the vbi capture parameters "
"for %s (%s): %d, %s."),
v->p_dev_name, v->vcap.card, errno, strerror(errno));
guess = _("Possibly a driver bug.");
goto io_error;
}
if (commit && (v->has_try_fmt == 1)
&& 0 != vbi3_raw_decoder_services (&v->rd)) {
/* FIXME strictness of services is not considered */
unsigned int old_services;
unsigned int tmp_services;
old_services = vbi3_raw_decoder_services (&v->rd);
tmp_services =
_vbi_sampling_par_check_services_log
(&v->sp, old_services, /* strict */ 0,
&v->log);
if (old_services != tmp_services)
vbi3_raw_decoder_remove_services
(&v->rd, old_services & ~ tmp_services);
}
} else {
info (&v->log,
"Successfully %s vbi capture "
"parameters.",
((s_fmt == (int)VIDIOC_S_FMT)
? "set" : "tried"));
}
}
print_vfmt (v, "VBI capture parameters granted: ", &vfmt);
{
vbi_bool fixed = FALSE;
if (v->cx88_ntsc_fix
&& 9 == vfmt.fmt.vbi.start[0]
&& 272 == vfmt.fmt.vbi.start[1]) {
/* Captures only 288 * 4 samples/line,
work-around not possible. */
asprintf (errstr,
_("A known bug in driver %s %u.%u.%u "
"impedes VBI capturing in NTSC mode. "
"Please upgrade."),
v->vcap.driver,
(v->vcap.version >> 16) & 0xFF,
(v->vcap.version >> 8) & 0xFF,
(v->vcap.version >> 0) & 0xFF);
errno = 0;
goto io_error;
}
if (v->pal_start1_fix
&& 625 == v->sp.scanning
&& 319 == vfmt.fmt.vbi.start[1]) {
vfmt.fmt.vbi.start[1] += 1;
fixed = TRUE;
}
if (v->bttv_offset_fix
&& 128 == vfmt.fmt.vbi.offset) {
vfmt.fmt.vbi.offset = 244;
fixed = TRUE;
}
if (v->bttv_ntsc_rate_fix
&& 525 == v->sp.scanning
&& 35468950 == vfmt.fmt.vbi.sampling_rate) {
vfmt.fmt.vbi.sampling_rate = 28636363;
fixed = TRUE;
}
if (fixed)
print_vfmt (v, "Fixes applied: ", &vfmt);
}
v->sp.sampling_rate = vfmt.fmt.vbi.sampling_rate;
v->sp.bytes_per_line = vfmt.fmt.vbi.samples_per_line;
v->sp.offset = vfmt.fmt.vbi.offset;
v->sp.start[0] = vfmt.fmt.vbi.start[0];
v->sp.start[1] = vfmt.fmt.vbi.start[1];
v->sp.count[0] = vfmt.fmt.vbi.count[0];
v->sp.count[1] = vfmt.fmt.vbi.count[1];
v->sp.interlaced = !!(vfmt.fmt.vbi.flags
& V4L2_VBI_INTERLACED);
v->sp.synchronous = !(vfmt.fmt.vbi.flags
& V4L2_VBI_UNSYNC);
v->time_per_frame = ((v->sp.scanning == 625)
? 1.0 / 25 : 1001.0 / 30000);
v->sp.sampling_format = VBI_PIXFMT_YUV420;
if (vfmt.fmt.vbi.sample_format != V4L2_PIX_FMT_GREY) {
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.card, vfmt.fmt.vbi.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 */
vbi3_raw_decoder_set_sampling_par (&v->rd, &v->sp, /* strict */ 0);
if (services & ~(VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625)) {
/* Nyquist (we're generous at 1.5) */
if (v->sp.sampling_rate < (int) 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.card,
v->sp.sampling_rate / 1e6);
services = 0;
goto failure;
}
info (&v->log, "Nyquist check passed.");
info (&v->log,
"Request decoding of services 0x%08x, "
"strict level %d.",
services, strict);
/* those services which are already set must be checked for strictness */
if (strict > 0 && 0 != (services & vbi3_raw_decoder_services (&v->rd))) {
unsigned int old_services;
unsigned int tmp_services;
old_services = vbi3_raw_decoder_services (&v->rd);
tmp_services = _vbi_sampling_par_check_services_log
(&v->sp, services & old_services, strict,
&v->log);
/* mask out unsupported services */
services &= tmp_services | ~(services & old_services);
}
if ( (services & ~vbi3_raw_decoder_services (&v->rd)) != 0 )
services &= vbi3_raw_decoder_add_services
(&v->rd, services
& ~vbi3_raw_decoder_services (&v->rd), strict);
if (services == 0) {
asprintf(errstr, _("Sorry, %s (%s) cannot capture any of "
"the requested data services."),
v->p_dev_name, v->vcap.card);
goto failure;
}
if (v->sliced_buffer.data != NULL)
free(v->sliced_buffer.data);
v->sliced_buffer.data =
malloc((v->sp.count[0] + v->sp.count[1]) * sizeof(vbi_sliced));
if (!v->sliced_buffer.data) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
goto io_error;
}
}
failure:
v->services |= services;
info (&v->log,
"Will capture services 0x%08x, "
"added 0x%0x commit=%d.",
v->services, services, commit);
if (commit && (v->services != 0)) {
if (v->streaming) {
if (v4l2_stream_alloc(v, errstr) != 0)
goto io_error;
} else {
if (v4l2_read_alloc(v, errstr) != 0)
goto io_error;
}
}
return services;
io_error:
info (&v->log,
"Failed with errno %d, errmsg '%s'.",
errno, *errstr);
return 0;
}
#if 3 == VBI_VERSION_MINOR
static vbi_sampling_par *
v4l2_parameters(vbi_capture *vc)
#else
static vbi_raw_decoder *
v4l2_parameters(vbi_capture *vc)
#endif
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
/* For compatibility in libzvbi 0.2
struct vbi_sampling_par == vbi_raw_decoder. In 0.3
we'll drop the decoding related fields. */
return &v->sp;
}
static void
v4l2_delete(vbi_capture *vc)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
if (v->streaming)
v4l2_stream_stop(v);
else
v4l2_read_stop(v);
_vbi3_raw_decoder_destroy (&v->rd);
if (v->sliced_buffer.data)
free(v->sliced_buffer.data);
if (v->p_dev_name != NULL)
free(v->p_dev_name);
if (v->close_me && v->fd != -1)
device_close (v->capture.sys_log_fp, v->fd);
free(v);
}
static VBI_CAPTURE_FD_FLAGS
v4l2_get_fd_flags(vbi_capture *vc)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
VBI_CAPTURE_FD_FLAGS result;
result = VBI_FD_IS_DEVICE | VBI_FD_HAS_SELECT;
if (v->streaming)
result |= VBI_FD_HAS_MMAP;
return result;
}
static int
v4l2_get_fd(vbi_capture *vc)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
return v->fd;
}
static void
v4l2_flush(vbi_capture *vc)
{
vbi_capture_v4l2 *v = PARENT(vc, vbi_capture_v4l2, capture);
v->flush_frame_count = FLUSH_FRAME_COUNT;
if (v->streaming)
v4l2_stream_flush(vc);
else
v4l2_read_flush(vc);
}
/* document below */
vbi_capture *
vbi_capture_v4l2k_new (const char * dev_name,
int fd,
int buffers,
unsigned int * services,
int strict,
char ** errstr,
vbi_bool trace)
{
char *guess = NULL;
char *error = NULL;
vbi_capture_v4l2 *v;
pthread_once (&vbi_init_once, vbi_init);
/* Needed to reopen device. */
assert(dev_name != NULL);
assert(buffers > 0);
if (!errstr)
errstr = &error;
*errstr = NULL;
if (!(v = calloc(1, sizeof(*v)))) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
goto failure;
}
_vbi3_raw_decoder_init (&v->rd, /* sampling_par */ NULL);
if (trace) {
v->log.fn = vbi_log_on_stderr;
v->log.mask = VBI_LOG_INFO * 2 - 1;
vbi3_raw_decoder_set_log_fn (&v->rd,
vbi_log_on_stderr,
/* user_data */ NULL,
/* mask */ VBI_LOG_INFO * 2 - 1);
}
if (0)
v->capture.sys_log_fp = stderr;
info (&v->log,
"Try to open V4L2 2.6 VBI device, "
"libzvbi interface rev.\n %s.",
rcsid);
v->p_dev_name = strdup(dev_name);
if (v->p_dev_name == NULL) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
goto failure;
}
v->capture.parameters = v4l2_parameters;
v->capture._delete = v4l2_delete;
v->capture.get_fd = v4l2_get_fd;
v->capture.get_fd_flags = v4l2_get_fd_flags;
v->capture.update_services = v4l2_update_services;
v->capture.get_scanning = v4l2_get_scanning;
v->capture.flush = v4l2_flush;
if (-1 == fd) {
v->fd = device_open (v->capture.sys_log_fp,
v->p_dev_name, O_RDWR, 0);
if (-1 == v->fd) {
asprintf(errstr, _("Cannot open '%s': %d, %s."),
v->p_dev_name, errno, strerror(errno));
goto io_error;
}
v->close_me = TRUE;
info (&v->log,
"Opened %s.",
v->p_dev_name);
} else {
v->fd = fd;
v->close_me = FALSE;
info (&v->log,
"Using v4l2k device fd %d.",
fd);
}
if (-1 == xioctl (v, VIDIOC_QUERYCAP, &v->vcap)) {
asprintf(errstr, _("Cannot identify '%s': %d, %s."),
v->p_dev_name, errno, strerror(errno));
guess = _("Probably not a v4l2 device.");
goto io_error;
}
if (!(v->vcap.capabilities & V4L2_CAP_VBI_CAPTURE)) {
asprintf(errstr, _("%s (%s) is not a raw vbi device."),
v->p_dev_name, v->vcap.card);
goto failure;
}
info (&v->log,
"%s (%s) is a v4l2 vbi device,\n"
"driver %s, version 0x%08x.",
v->p_dev_name, v->vcap.card,
v->vcap.driver, v->vcap.version);
if (0 == strcmp ((char *) v->vcap.driver, "bttv")) {
if (v->vcap.version <= 0x00090F) {
v->pal_start1_fix = TRUE;
v->bttv_min_start_fix = TRUE;
}
v->bttv_offset_fix = TRUE;
v->bttv_ntsc_rate_fix = TRUE;
} else if (0 == strcmp ((char *) v->vcap.driver, "saa7134")) {
if (v->vcap.version <= 0x00020C)
v->saa7134_ntsc_fix = TRUE;
v->pal_start1_fix = TRUE;
} else if (0 == strcmp ((char *) v->vcap.driver, "cx8800")) {
v->cx88_ntsc_fix = TRUE;
}
v->has_try_fmt = -1;
v->buf_req_count = buffers;
if (v->vcap.capabilities & V4L2_CAP_STREAMING
&& !vbi_capture_force_read_mode) {
info (&v->log, "Using streaming interface.");
fcntl(v->fd, F_SETFL, O_NONBLOCK);
v->streaming = TRUE;
v->enqueue = ENQUEUE_SUSPENDED;
v->capture.read = v4l2_stream;
} else if (v->vcap.capabilities & V4L2_CAP_READWRITE) {
info (&v->log, "Using read interface.");
v->capture.read = v4l2_read;
v->read_active = FALSE;
} else {
asprintf(errstr,
_("%s (%s) lacks a vbi read interface, "
"possibly an output only device "
"or a driver bug."),
v->p_dev_name, v->vcap.card);
goto failure;
}
v->services = 0;
if (services != NULL) {
assert(*services != 0);
v->services = v4l2_update_services(&v->capture, TRUE, TRUE,
*services, strict, errstr);
if (v->services == 0)
goto failure;
*services = v->services;
}
info (&v->log,
"Successfully opened %s (%s).",
v->p_dev_name, v->vcap.card);
if (errstr == &error) {
free (error);
error = NULL;
}
return &v->capture;
io_error:
failure:
if (v)
v4l2_delete (&v->capture);
info (&v->log,
"Failed with errno %d, errmsg '%s'.",
errno, *errstr);
if (errstr == &error) {
free (error);
error = NULL;
}
return NULL;
}
#else
/**
* @param dev_name Name of the device to open, usually one of
* @c /dev/vbi or @c /dev/vbi0 and up.
* @param fd File handle of VBI device if already opened by caller,
* else value -1.
* @param buffers Number of device buffers for raw vbi data, when
* the driver supports streaming. Otherwise one bounce buffer
* is allocated for vbi_capture_pull().
* @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_v4l2k_new(const char *dev_name, int fd, int buffers,
unsigned int *services, int strict,
char **errstr, vbi_bool trace)
{
dev_name = dev_name;
fd = fd;
buffers = buffers;
services = services;
strict = strict;
pthread_once (&vbi_init_once, vbi_init);
if (trace)
fprintf (stderr, "Libzvbi V4L2 2.6 interface rev.\n %s\n",
rcsid);
if (errstr)
asprintf (errstr,
_("V4L2 driver interface not compiled."));
return NULL;
}
#endif /* !ENABLE_V4L2 */
syntax highlighted by Code2HTML, v. 0.9.1