/*
* libzvbi -- dvb driver interface
*
* (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs]
* (c) 2004-2005 Michael H. Schimek (vbi_dvb_demux, new dvb_read)
*
* 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.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef ENABLE_DVB
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <inttypes.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include "dvb/frontend.h"
#include "dvb/dmx.h"
#include "hamm.h"
#include "io.h"
#include "vbi.h"
#include "dvb_demux.h"
/* ----------------------------------------------------------------------- */
typedef struct {
vbi_capture cap;
vbi_dvb_demux * demux;
int fd;
int debug;
vbi_capture_buffer sliced_buffer;
vbi_sliced sliced_data[128];
double sample_time;
uint8_t pes_buffer[1024*8];
const uint8_t * bp;
unsigned int b_left;
int64_t last_pts;
vbi_bool bug_compatible;
} vbi_capture_dvb;
/* ----------------------------------------------------------------------- */
static vbi_capture_dvb* dvb_init(const char *dev, char **errstr, int debug)
{
vbi_capture_dvb *dvb;
dvb = malloc(sizeof(*dvb));
if (NULL == dvb) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
return NULL;
}
CLEAR (*dvb);
if (!(dvb->demux = vbi_dvb_pes_demux_new (NULL, NULL))) {
asprintf(errstr, _("Virtual memory exhausted."));
errno = ENOMEM;
free (dvb);
return NULL;
}
dvb->debug = debug;
dvb->fd = open(dev, O_RDWR | O_NONBLOCK);
if (-1 == dvb->fd) {
asprintf(errstr, _("Cannot open '%s': %d, %s."),
dev, errno, strerror(errno));
free(dvb);
return NULL;
}
if (dvb->debug)
fprintf(stderr,"dvb-vbi: opened device %s\n",dev);
return dvb;
}
/* ----------------------------------------------------------------------- */
static __inline__ void
timeval_subtract (struct timeval * delta,
const struct timeval * tv1,
const struct timeval * tv2)
{
if (tv1->tv_usec < tv2->tv_usec) {
delta->tv_sec = tv1->tv_sec - tv2->tv_sec - 1;
delta->tv_usec = 1000000 + tv1->tv_usec - tv2->tv_usec;
} else {
delta->tv_sec = tv1->tv_sec - tv2->tv_sec;
delta->tv_usec = tv1->tv_usec - tv2->tv_usec;
}
}
static void
timeout_subtract_elapsed (struct timeval * result,
const struct timeval * timeout,
const struct timeval * now,
const struct timeval * start)
{
struct timeval elapsed;
timeval_subtract (&elapsed, now, start);
if ((elapsed.tv_sec | elapsed.tv_usec) > 0) {
timeval_subtract (result, timeout, &elapsed);
if ((result->tv_sec | result->tv_usec) < 0) {
result->tv_sec = 0;
result->tv_usec = 0;
}
} else {
*result = *timeout;
}
}
static ssize_t
select_read (vbi_capture_dvb * dvb,
struct timeval * now,
const struct timeval * start,
const struct timeval * timeout)
{
struct timeval tv;
ssize_t actual;
select_again:
/* tv = timeout - (now - start). */
timeout_subtract_elapsed (&tv, timeout, now, start);
/* Shortcut: don't wait if timeout is zero or elapsed. */
if ((tv.tv_sec | tv.tv_usec) > 0) {
fd_set set;
int r;
select_again_with_timeout:
FD_ZERO (&set);
FD_SET (dvb->fd, &set);
/* Note Linux select() may change tv. */
r = select (dvb->fd + 1,
/* readable */ &set,
/* writeable */ NULL,
/* in exception */ NULL,
&tv);
switch (r) {
case -1: /* error */
switch (errno) {
case EINTR:
gettimeofday (now, /* timezone */ NULL);
goto select_again;
default:
return -1; /* error */
}
case 0: /* timeout */
if (dvb->bug_compatible)
return -1; /* error */
return 0; /* timeout */
default:
break;
}
}
read_again:
/* Non-blocking. */
actual = read (dvb->fd,
dvb->pes_buffer,
sizeof (dvb->pes_buffer));
switch (actual) {
case -1: /* error */
switch (errno) {
case EAGAIN:
if (dvb->bug_compatible)
return -1; /* error */
if ((timeout->tv_sec | timeout->tv_usec) <= 0)
return 0; /* timeout */
gettimeofday (now, /* timezone */ NULL);
timeout_subtract_elapsed (&tv, timeout, now, start);
if ((tv.tv_sec | tv.tv_usec) <= 0)
return 0; /* timeout */
goto select_again_with_timeout;
case EINTR:
goto read_again;
default:
return -1; /* error */
}
case 0: /* EOF? */
if (dvb->debug)
fprintf (stderr, "dvb-vbi: end of file\n");
errno = 0;
return -1; /* error */
default:
break;
}
return actual;
}
static int
dvb_read (vbi_capture * cap,
vbi_capture_buffer ** raw,
vbi_capture_buffer ** sliced,
const struct timeval * timeout)
{
vbi_capture_dvb *dvb = PARENT (cap, vbi_capture_dvb, cap);
vbi_capture_buffer *sb;
struct timeval start;
struct timeval now;
unsigned int n_lines;
int64_t pts;
if (!sliced || !(sb = *sliced)) {
sb = &dvb->sliced_buffer;
sb->data = dvb->sliced_data;
}
start.tv_sec = 0;
start.tv_usec = 0;
/* When timeout is zero elapsed time doesn't matter. */
if ((timeout->tv_sec | timeout->tv_usec) > 0)
gettimeofday (&start, /* timezone */ NULL);
now = start;
for (;;) {
if (0 == dvb->b_left) {
ssize_t actual;
actual = select_read (dvb, &now, &start, timeout);
if (actual <= 0)
return actual;
gettimeofday (&now, /* timezone */ NULL);
/* XXX inaccurate. Should be the time when we
received the first byte of the first packet
containing data of the returned frame. Or so. */
dvb->sample_time = now.tv_sec
+ now.tv_usec * (1 / 1e6);
dvb->bp = dvb->pes_buffer;
dvb->b_left = actual;
}
/* Demultiplexer coroutine. Returns when one frame is complete
or the buffer is empty, advancing bp and b_left. Don't
change sb->data in flight. */
/* XXX max sliced lines needs an API change. Currently this
value is determined by vbi_raw_decoder line count below,
256 / 2 because fields don't really apply here and in
practice even 32 should be enough. */
n_lines = vbi_dvb_demux_cor (dvb->demux,
sb->data,
/* max sliced lines */ 128,
&pts,
&dvb->bp,
&dvb->b_left);
if (n_lines > 0)
break;
if (dvb->bug_compatible) {
/* Only one read(), timeout ignored. */
return 0; /* timeout */
} else {
/* Read until EAGAIN, another error or the
timeout expires, in this order. */
}
}
if (sliced) {
sb->size = n_lines * sizeof (vbi_sliced);
sb->timestamp = dvb->sample_time;
/* XXX PTS needs an API change.
sb->sample_time = dvb->sample_time;
sb->stream_time = pts; (first sliced line) */
dvb->last_pts = pts;
*sliced = sb;
}
if (raw && *raw) {
/* Not implemented yet. */
sb = *raw;
sb->size = 0;
}
return 1; /* success */
}
static vbi_raw_decoder* dvb_parameters(vbi_capture *cap)
{
static vbi_raw_decoder raw = {
.count = { 128, 128 },
};
cap = cap;
return &raw;
}
static void
dvb_delete(vbi_capture *cap)
{
vbi_capture_dvb *dvb = PARENT (cap, vbi_capture_dvb, cap);
if (dvb->fd != -1)
close(dvb->fd);
vbi_dvb_demux_delete (dvb->demux);
/* Make unusable. */
CLEAR (*dvb);
free(dvb);
}
static int
dvb_fd(vbi_capture *cap)
{
vbi_capture_dvb *dvb = PARENT (cap, vbi_capture_dvb, cap);
return dvb->fd;
}
/* ----------------------------------------------------------------------- */
/* public interface */
int64_t
vbi_capture_dvb_last_pts (const vbi_capture * cap)
{
const vbi_capture_dvb *dvb = CONST_PARENT (cap, vbi_capture_dvb, cap);
return dvb->last_pts;
}
int vbi_capture_dvb_filter(vbi_capture *cap, int pid)
{
vbi_capture_dvb *dvb = PARENT (cap, vbi_capture_dvb, cap);
struct dmx_pes_filter_params filter;
CLEAR (filter);
filter.pid = pid;
filter.input = DMX_IN_FRONTEND;
filter.output = DMX_OUT_TAP;
filter.pes_type = DMX_PES_OTHER;
filter.flags = DMX_IMMEDIATE_START;
if (0 != ioctl(dvb->fd, DMX_SET_PES_FILTER, &filter)) {
if (dvb->debug)
perror("ioctl DMX_SET_PES_FILTER");
return -1;
}
if (dvb->debug)
fprintf(stderr,"dvb-vbi: filter setup done | fd %d pid %d\n",
dvb->fd, pid);
return 0;
}
vbi_capture *
vbi_capture_dvb_new2 (const char * device_name,
unsigned int pid,
char ** errstr,
vbi_bool trace)
{
char *error = NULL;
vbi_capture_dvb *dvb;
if (!errstr)
errstr = &error;
*errstr = NULL;
dvb = dvb_init(device_name,errstr,trace);
if (NULL == dvb)
goto failure;
dvb->cap.parameters = dvb_parameters;
dvb->cap.read = dvb_read;
dvb->cap.get_fd = dvb_fd;
dvb->cap._delete = dvb_delete;
if (0 != pid) {
if (-1 == vbi_capture_dvb_filter (&dvb->cap, pid)) {
asprintf (errstr, "DMX_SET_PES_FILTER: %s",
strerror (errno));
dvb_delete (&dvb->cap);
goto failure;
}
}
if (errstr == &error) {
free (error);
error = NULL;
}
return &dvb->cap;
failure:
if (errstr == &error) {
free (error);
error = NULL;
}
return NULL;
}
vbi_capture*
vbi_capture_dvb_new(char *dev, int scanning,
unsigned int *services, int strict,
char **errstr, vbi_bool trace)
{
char *error = NULL;
vbi_capture *cap;
scanning = scanning;
services = services;
strict = strict;
if (!errstr)
errstr = &error;
*errstr = NULL;
if ((cap = vbi_capture_dvb_new2 (dev, 0, errstr, trace))) {
vbi_capture_dvb *dvb = PARENT (cap, vbi_capture_dvb, cap);
dvb->bug_compatible = TRUE;
}
if (errstr == &error) {
free (error);
error = NULL;
}
return cap;
}
#else /* !ENABLE_DVB */
#include "io.h"
#include "vbi.h"
/**
* @param cap Initialized DVB vbi_capture context.
*
* Returns the presentation time stamp associated with the data
* last read from the context. The PTS refers to the first sliced
* VBI line, not the last packet containing data of that frame.
*
* Note timestamps returned by vbi_capture read functions contain
* the sampling time of the data, that is the time at which the
* packet containing the first sliced line arrived.
*
* @returns
* Presentation time stamp (33 bits).
*
* @bug PTS' should be part of the generic I/O interface.
*
* @since 0.2.13
*/
int64_t
vbi_capture_dvb_last_pts (const vbi_capture * cap)
{
cap = cap;
return 0;
}
/**
* @param cap Initialized DVB vbi_capture context.
* @param pid Filter out a stream with this PID.
*
* Programs the DVB device transport stream demultiplexer to filter
* out PES packets with this PID.
*
* @returns
* -1 on failure, 0 on success.
*/
int
vbi_capture_dvb_filter (vbi_capture * cap,
int pid)
{
cap = cap;
pid = pid;
return -1;
}
/**
* @param device_name Name of the DVB device to open.
* @param pid Filter out a stream with this PID. You can pass 0 here
* and set or change the PID later with vbi_capture_dvb_filter().
* @param errstr If not @c NULL the 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 and warning messages on stderr.
*
* Initializes a vbi_capture context reading from a Linux DVB device.
*
* @returns
* Initialized vbi_capture context, @c NULL on failure.
*
* @since 0.2.13
*/
vbi_capture *
vbi_capture_dvb_new2 (const char * device_name,
unsigned int pid,
char ** errstr,
vbi_bool trace)
{
device_name = device_name;
pid = pid;
trace = trace;
if (errstr)
asprintf (errstr, _("DVB interface not compiled."));
return NULL;
}
/**
* @param dev Name of the DVB device to open.
* @param scanning Ignored.
* @param services Ignored.
* @param strict Ignored.
* @param errstr If not @c NULL the 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 and warning messages on stderr.
*
* @deprecated
* This function is deprecated. Use vbi_capture_dvb_new2() instead.
*
* Initializes a vbi_capture context reading from a Linux DVB device.
* You must call vbi_capture_dvb_filter() before you can read.
*
* @returns
* Initialized vbi_capture context, @c NULL on failure.
*
* @bug
* This function ignores the scanning, services and strict parameter.
* The read method of this DVB capture context returns -1 on timeout
* instead of 0. It returns 0 when a single read() does not complete
* a frame, regardless if the timeout expired. (Decoding resumes with
* the next call.) Older versions pass select or read EINTR errors
* back to the caller. They may return partial frames when VBI data
* of one frame is distributed across multiple PES packets. They will
* not return VPS, CC, or WSS data and can malfunction or segfault
* given unusual PES streams. On error and select timeout older versions
* invariably print a warning on stderr.
*/
vbi_capture*
vbi_capture_dvb_new(char *dev, int scanning,
unsigned int *services, int strict,
char **errstr, vbi_bool trace)
{
dev = dev;
scanning = scanning;
services = services;
strict = strict;
trace = trace;
if (errstr)
asprintf (errstr, _("DVB interface not compiled."));
return NULL;
}
#endif /* !ENABLE_DVB */
syntax highlighted by Code2HTML, v. 0.9.1