examples/pdc2.c
/*
* libzvbi VPS/PDC example 2
*
* Copyright (C) 2009 Michael H. Schimek
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* $Id: pdc2.c,v 1.1 2009-03-23 01:30:39 mschimek Exp $ */
/* This example shows how to receive and decode VPS/PDC Program IDs.
For simplicity channel change functions have been omitted and not
all PDC features are supported. (A more complete example will be
added later.)
To compile this program type:
gcc -o pdc2 pdc2.c `pkg-config zvbi-0.2 --cflags --libs`
This program expects the starting date and time, ending time
and VPS/PDC time of a TV program to record as arguments:
./pdc2 YYYY-MM-DD HH:MM HH:MM HH:MM
It opens a V4L2 device at /dev/vbi and scans the currently tuned in
channel for a matching VPS/PDC label, logging the progress on
standard output, without actually recording anything.
The -t option enables a test mode where the program reads VPS/PDC
signal changes from standard input instead of opening a VBI
device. See parse_test_file_line() for a description of the file
format.
*/
#define _GNU_SOURCE 1
#undef NDEBUG
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <float.h>
#include <math.h>
#ifdef _WIN32
#define __STDC_WANT_LIB_EXT1__ 1
#endif
#include <time.h>
#include <locale.h>
#include <ctype.h>
#include <unistd.h>
#include <getopt.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>
#include <libzvbi.h>
#ifdef _WIN32
#include "src/strptime.h"
#define timegm _mkgmtime
#endif
#ifndef N_ELEMENTS
# define N_ELEMENTS(array) (sizeof (array) / sizeof (*(array)))
#endif
#ifndef MIN
# define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif
static vbi_capture * cap;
static vbi_decoder * dec;
static const char * dev_name;
static vbi_bool quit;
static int exit_code;
/* The current time of the intended audience of the tuned in network
according to the network (see VBI_EVENT_LOCAL_TIME). It may differ
from system time if the system is not in sync with UTC or if we
receive the TV signal with a delay. */
static time_t audience_time;
/* The system time in seconds when the most recent PDC signal was
received. */
static double timestamp;
/* PDC Label Channel state. */
struct lc_state {
/* The PIL most recently received on this LC, zero if none. */
vbi_pil pil;
/* The system time in seconds when the PIL was most recently
received. */
double last_at;
};
/* The most recently received PILs. */
static struct lc_state lc_state[VBI_MAX_PID_CHANNELS];
/* Video recorder states. */
enum vcr_state {
/* All capturing stopped. */
VCR_STATE_STBY,
/* Searching for a PDC signal. */
VCR_STATE_SCAN,
/* Preparing to record. */
VCR_STATE_PTR,
/* Recording a program. */
VCR_STATE_REC
};
/* The current video recorder state. */
static enum vcr_state vcr_state;
/* The system time in seconds at the last change of vcr_state. */
static double vcr_state_since;
/* In timer control mode we start and stop recording at the scheduled
times. Timer control mode is enabled when the network does not
transmit program IDs or when we lost all PDC signals. */
static vbi_bool timer_control_mode;
/* In VCR_STATE_REC this variable stops recording with a 30 second
delay as required by EN 300 231. This is a system time in
seconds, or DBL_MAX if no stop is planned. */
static double delayed_stop_at;
/* In VCR_REC_STATE if delayed_stop_at < DBL_MAX, delayed_stop_pid
contains a copy of the program ID which caused the delayed stop.
If delayed_stop_pid.luf == 1 the program will continue on the
channel with delayed_stop_pid.cni, accompanied by
delayed_stop_pid.pil (which may also provide a new start date and
time for the schedule).
Otherwise delayed_stop_pid.pil can be a valid PIL, a RI/T or INT
service code, or zero if a loss of the PDC signal or service caused
the delayed stop. */
static vbi_program_id delayed_stop_pid;
/* A program to be recorded. */
struct program {
struct program * next;
/* A number in lieu of a title. */
unsigned int index;
/* The most recently announced start and end time of the
program ("AT-1" in EN 300 231 in parlance), in case we do
not receive a PDC signal. When the duration of the program
is unknown start_time == end_time. end_time is
exclusive. */
time_t start_time;
time_t end_time;
/* The expected Program Identification Label. Usually this is
the originally announced start date and time of the program
("AT-2" in EN 300 231), relative to the time zone of the
intended audience. */
vbi_pil pil;
/* The validity window of pil, that is the time when the
network can be expected to transmit the PIL. Usually from
00:00 on the same day to 04:00 on the next
day. pil_valid_end is exclusive. */
time_t pil_valid_start;
time_t pil_valid_end;
/* Recording is in progress or was interrupted. */
vbi_bool continues;
};
/* The recording schedule, a singly-linked list of program
structures. */
static struct program * schedule;
/* In VCR_STATE_PTR and VCR_STATE_REC the program we (are about to)
record, a pointer into the schedule list. Otherwise NULL. */
static struct program * curr_program;
/* If curr_program != NULL this variable contains a copy of the
program ID which put us into PTR or REC state. If recording was
started by the timer curr_pid.pil is zero. */
static vbi_program_id curr_pid;
static vbi_bool test_mode;
/* In test mode this is the expected VCR state after the most recent
PDC signal change. */
static enum vcr_state test_exp_vcr_state;
static const double
signal_timeout [VBI_MAX_PID_CHANNELS] = {
[VBI_PID_CHANNEL_LCI_0] = 2,
[VBI_PID_CHANNEL_LCI_1] = 2,
[VBI_PID_CHANNEL_LCI_2] = 2,
[VBI_PID_CHANNEL_LCI_3] = 2,
/* VPS signals have no error protection. When the payload
changes, libzvbi will wait for one repetition to confirm
correct reception. */
[VBI_PID_CHANNEL_VPS] = 3 / 25.0,
/* Other channels not implemented yet. */
};
static const double
signal_period [VBI_MAX_PID_CHANNELS] = {
/* EN 300 231 Section 8.3: "In the case of the packet 8/30
version (Method B) the repetition rate of labels in any
label data channel is once per second." Section E.2: "Where
more than one label channel is in use the signalling rate
is normally one line per label channel per second." */
[VBI_PID_CHANNEL_LCI_0] = 1,
[VBI_PID_CHANNEL_LCI_1] = 1,
[VBI_PID_CHANNEL_LCI_2] = 1,
[VBI_PID_CHANNEL_LCI_3] = 1,
[VBI_PID_CHANNEL_VPS] = 1 / 25.0,
/* Other channels not implemented yet. */
};
/* For debugging. */
#define D printf ("%s:%u\n", __FILE__, __LINE__)
/* For debugging. */
static void
print_time (time_t time)
{
char buffer[80];
struct tm tm;
memset (&tm, 0, sizeof (tm));
#ifdef _WIN32
localtime_s (&tm, &time);
#else
localtime_r (&time, &tm);
#endif
strftime (buffer, sizeof (buffer),
"%Y-%m-%d %H:%M:%S %Z = ", &tm);
fputs (buffer, stdout);
memset (&tm, 0, sizeof (tm));
#ifdef _WIN32
gmtime_s (&tm, &time);
#else
gmtime_r (&time, &tm);
#endif
strftime (buffer, sizeof (buffer),
"%Y-%m-%d %H:%M:%S UTC", &tm);
puts (buffer);
}
/* Attention! This function returns a static string. */
static const char *
pil_str (vbi_pil pil)
{
static char buffer[32];
switch (pil) {
case VBI_PIL_TIMER_CONTROL: return "TC";
case VBI_PIL_INHIBIT_TERMINATE: return "RI/T";
case VBI_PIL_INTERRUPTION: return "INT";
case VBI_PIL_CONTINUE: return "CONT";
case VBI_PIL_NSPV:
/* NSVP service code if source is VPS/PDC,
END code if source is XDS. */
return "NSPV/END";
default:
snprintf (buffer, sizeof (buffer),
"%02u%02uT%02u%02u",
VBI_PIL_MONTH (pil),
VBI_PIL_DAY (pil),
VBI_PIL_HOUR (pil),
VBI_PIL_MINUTE (pil));
return buffer;
}
}
static void
msg (const char * templ,
...)
{
va_list ap;
va_start (ap, templ);
if (test_mode) {
char buffer[80];
struct tm tm;
memset (&tm, 0, sizeof (tm));
#ifdef _WIN32
localtime_s (&tm, &audience_time);
#else
localtime_r (&audience_time, &tm);
#endif
strftime (buffer, sizeof (buffer), "%Y%m%dT%H%M%S ", &tm);
fputs (buffer, stdout);
}
vprintf (templ, ap);
va_end (ap);
}
static void
remove_program_from_schedule (struct program * p)
{
struct program **pp;
if (p == curr_program) {
assert (quit
|| VCR_STATE_STBY == vcr_state
|| VCR_STATE_SCAN == vcr_state);
curr_program = NULL;
}
for (pp = &schedule; NULL != *pp; pp = &(*pp)->next) {
if (*pp == p) {
*pp = p->next;
free (p);
break;
}
}
}
static void
remove_stale_programs_from_schedule (void)
{
struct program *p;
struct program *p_next;
for (p = schedule; NULL != p; p = p_next) {
p_next = p->next;
if (audience_time >= p->end_time
&& audience_time >= p->pil_valid_end) {
msg ("PIL %s no longer valid, "
"removing program %u from schedule.\n",
pil_str (p->pil), p->index);
remove_program_from_schedule (p);
}
}
}
static struct program *
find_program_by_pil (vbi_pil pil)
{
struct program *p;
for (p = schedule; NULL != p; p = p->next) {
if (pil == p->pil)
return p;
}
return NULL;
}
static const char *
vcr_state_name (enum vcr_state state)
{
switch (state) {
#define CASE(x) case VCR_STATE_ ## x: return #x;
CASE (STBY)
CASE (SCAN)
CASE (PTR)
CASE (REC)
#undef CASE
}
assert (0);
}
static void
change_vcr_state (enum vcr_state new_state)
{
if (new_state == vcr_state)
return;
msg ("VCR state %s -> %s.\n",
vcr_state_name (vcr_state),
vcr_state_name (new_state));
vcr_state = new_state;
vcr_state_since = timestamp;
}
static vbi_bool
teletext_8302_available (void)
{
return (0 != (lc_state[VBI_PID_CHANNEL_LCI_0].pil |
lc_state[VBI_PID_CHANNEL_LCI_1].pil |
lc_state[VBI_PID_CHANNEL_LCI_2].pil |
lc_state[VBI_PID_CHANNEL_LCI_3].pil));
}
static void
disable_timer_control (void)
{
if (!timer_control_mode)
return;
msg ("Leaving timer control mode.\n");
timer_control_mode = FALSE;
}
static void
enable_timer_control (void)
{
if (timer_control_mode)
return;
msg ("Entering timer control mode.\n");
timer_control_mode = TRUE;
}
static void
stop_recording_now (void)
{
assert (VCR_STATE_REC == vcr_state);
msg ("Program %u ended according to %s%s.\n",
curr_program->index,
timer_control_mode ? "schedule" : "VPS/PDC signal",
(delayed_stop_at < DBL_MAX) ? " with delay" : "");
change_vcr_state (VCR_STATE_SCAN);
delayed_stop_at = DBL_MAX;
}
static void
stop_recording_in_30s (const vbi_program_id * pid)
{
assert (VCR_STATE_REC == vcr_state);
/* What triggered the stop. */
if (NULL == pid) {
/* Signal lost. */
memset (&delayed_stop_pid, 0,
sizeof (delayed_stop_pid));
} else {
delayed_stop_pid = *pid;
}
/* If we stop because the PIL is no longer transmitted we may
need one second to realize (e.g. receiving LCI 0 at time t,
LCI 1 at t + 0.2, then LCI 0 at t + 1, and again LCI 0 at
t + 2 seconds) so we start counting 30 seconds not from
the current time (t + 2) but the first time the label was
missing (t + 1). */
if (NULL == pid && 0 != curr_pid.pil) {
delayed_stop_at = lc_state[curr_pid.channel].last_at + 31;
} else {
delayed_stop_at = timestamp + 30;
}
msg ("Will stop recording in %d seconds.\n",
(int)(delayed_stop_at - timestamp));
}
static void
start_recording_by_pil (struct program * p,
const vbi_program_id * pid)
{
assert (!timer_control_mode);
assert (VCR_STATE_SCAN == vcr_state
|| VCR_STATE_PTR == vcr_state);
msg ("Recording program %u using VPS/PDC signal.\n",
p->index);
/* EN 300 231 Section 9.4.1: "[When] labels are not received
correctly during a recording, the recording will be
continued for the computed duration following the actual
start time" */
if (!p->continues) {
p->end_time += audience_time - p->start_time;
p->start_time = audience_time;
p->continues = TRUE;
}
change_vcr_state (VCR_STATE_REC);
curr_program = p;
curr_pid = *pid;
}
static void
prepare_to_record_by_pil (struct program * p,
const vbi_program_id * pid)
{
assert (!timer_control_mode);
assert (VCR_STATE_SCAN == vcr_state);
change_vcr_state (VCR_STATE_PTR);
curr_program = p;
curr_pid = *pid;
}
static void
start_recording_by_timer (struct program * p)
{
assert (timer_control_mode);
assert (VCR_STATE_SCAN == vcr_state);
msg ("Recording program %u using timer.\n",
p->index);
change_vcr_state (VCR_STATE_REC);
curr_program = p;
memset (&curr_pid, 0, sizeof (curr_pid));
}
static void
remove_program_if_ended (struct program * p,
const vbi_program_id * pid)
{
if (timer_control_mode) {
/* We don't know if the program really ends now, so we
keep it scheduled until curr_program->pil_valid_end
in case we receive its PIL after all. */
return;
} else if (NULL != pid && VBI_PIL_INTERRUPTION == pid->pil) {
/* The program pauses, will not be removed. */
return;
} else if (NULL != pid && pid->luf) {
/* The program has been rescheduled to another date,
we don't care in this example. */
}
/* Objective accomplished. */
remove_program_from_schedule (p);
}
static void
signal_or_service_lost (void)
{
struct program *p;
if (timer_control_mode)
return;
enable_timer_control ();
switch (vcr_state) {
case VCR_STATE_STBY:
assert (0);
case VCR_STATE_SCAN:
break;
case VCR_STATE_PTR:
p = curr_program;
/* According to EN 300 231 Section E.1 and Section E.3
Example 12 the program should begin within one
minute when PRF=1, so we start recording now. We
will stop by PIL if we pick up a VPS or Teletext
signal again before curr_program->end_time, but we
will not return to VCR_STATE_PTR if PRF is still
1. */
msg ("Recording program %u using lost "
"PDC signal with PRF=1.\n",
p->index);
/* Record for the scheduled duration... */
p->end_time = p->end_time - p->start_time + audience_time;
/* ...plus one minute since PRF was set. */
p->end_time += 60 - MIN (vcr_state_since - timestamp,
60.0);
p->start_time = audience_time;
change_vcr_state (VCR_STATE_REC);
/* Now recording by timer. */
memset (&curr_pid, 0, sizeof (curr_pid));
break;
case VCR_STATE_REC:
if (delayed_stop_at < DBL_MAX) {
msg ("PDC signal lost; already stopping in "
"%d seconds.\n",
(int)(delayed_stop_at - timestamp));
} else if (curr_program->start_time
== curr_program->end_time) {
/* Since we don't know the program duration,
we cannot record under timer control. We
stop recording in 30 seconds as shown in EN
300 231 Annex E.3, Example 11, 16:20:10,
but with an extra twist: If we receive
curr_program->pil again within those 30
seconds the stop will be canceled. */
stop_recording_in_30s (/* pid */ NULL);
} else {
/* Keep recording by timer. */
memset (&curr_pid, 0, sizeof (curr_pid));
}
break;
}
}
static void
pil_no_longer_transmitted (const vbi_program_id * pid)
{
vbi_bool mi;
switch (vcr_state) {
case VCR_STATE_STBY:
case VCR_STATE_SCAN:
assert (0);
case VCR_STATE_PTR:
assert (!timer_control_mode);
msg ("PIL %s is no longer present on LC %u.\n",
pil_str (curr_program->pil),
curr_pid.channel);
change_vcr_state (VCR_STATE_SCAN);
return;
case VCR_STATE_REC:
assert (!timer_control_mode);
msg ("PIL %s is no longer present on LC %u.\n",
pil_str (curr_program->pil),
curr_pid.channel);
if (delayed_stop_at < DBL_MAX) {
msg ("Already stopping in %d seconds.\n",
(int)(delayed_stop_at - timestamp));
return;
}
break;
}
if (NULL != pid
/* EN 300 231 Annex E.3 Example 8. */
&& !pid->luf
/* EN 300 231 Section 6.2 p) and Annex E.3 Example 7 and
9. */
&& (VBI_PIL_INTERRUPTION == pid->pil
|| VBI_PIL_INHIBIT_TERMINATE == pid->pil)) {
mi = pid->mi;
} else {
/* EN 300 231 is unclear about the expected response
if a PIL with MI = 1 replaces a PIL with MI = 0 or
vice versa. Section 6.2 p) suggests that only the
MI flag of the old label determines when the
program stops and Annex E.3 Example 1 to 7 are
consistent with this interpretation, Example 10 is
not. */
if (0 == curr_pid.pil) {
/* Recording was started by timer. */
mi = TRUE;
} else {
mi = curr_pid.mi;
}
}
if (mi) {
stop_recording_now ();
remove_program_if_ended (curr_program, pid);
} else {
stop_recording_in_30s (pid);
}
}
/* Interruption or Recording Inhibit/Terminate service code. */
static void
received_int_rit (const vbi_program_id * pid)
{
switch (vcr_state) {
case VCR_STATE_STBY:
assert (0);
case VCR_STATE_SCAN:
disable_timer_control ();
return;
case VCR_STATE_PTR:
assert (!timer_control_mode);
if (pid->channel != curr_pid.channel) {
msg ("Ignore %s/%02X with different LCI.\n",
pil_str (pid->pil), pid->pty);
return;
}
break;
case VCR_STATE_REC:
if (timer_control_mode) {
/* Impossible to know if this service code
refers to curr_program, so we keep
recording for now. */
return;
} else if (pid->channel != curr_pid.channel) {
msg ("Ignore %s/%02X with different LCI.\n",
pil_str (pid->pil), pid->pty);
return;
}
break;
}
pil_no_longer_transmitted (pid);
}
static void
received_pil (const vbi_program_id * pid)
{
struct program *p;
switch (vcr_state) {
case VCR_STATE_STBY:
assert (0);
case VCR_STATE_SCAN:
disable_timer_control ();
if (pid->luf)
return;
p = find_program_by_pil (pid->pil);
break;
case VCR_STATE_PTR:
assert (!timer_control_mode);
if (pid->channel != curr_pid.channel) {
msg ("Ignore %s/%02X with different LCI.\n",
pil_str (pid->pil), pid->pty);
return;
} else if (pid->luf) {
pil_no_longer_transmitted (pid);
/* This example does not support VCR
reprogramming. */
return;
} else if (pid->pil != curr_pid.pil) {
pil_no_longer_transmitted (pid);
p = find_program_by_pil (pid->pil);
break;
} else if (pid->prf) {
if (timestamp >= vcr_state_since + 60) {
/* EN 300 231 Section E.1,
Section E.3 Example 12. */
msg ("Overriding stuck PRF flag.\n");
} else {
msg ("Already prepared to record.\n");
return;
}
}
/* PRF 1 -> 0, program starts now. */
start_recording_by_pil (curr_program, pid);
return;
case VCR_STATE_REC:
if (timer_control_mode) {
if (pid->luf) {
/* Impossible to know if this service
code refers to curr_program, so we
keep recording for now. */
return;
}
p = find_program_by_pil (pid->pil);
if (p == curr_program) {
disable_timer_control ();
msg ("Continue recording using "
"VPS/PDC signal.\n");
curr_pid = *pid;
/* Cancel a delayed stop because the
program is evidently still
running. */
delayed_stop_at = DBL_MAX;
return;
} else if (NULL == p) {
/* This program is not scheduled for
recording but the network may
transmit other PILs in parallel, so
we allow some time to pick them up
before we stop. */
stop_recording_in_30s (/* pil */ NULL);
return;
} else {
disable_timer_control ();
/* Perhaps in practice one should just
open a new file and not restart
capturing. */
stop_recording_now ();
}
} else if (pid->channel != curr_pid.channel) {
msg ("Ignore %s/%02X with different LCI.\n",
pil_str (pid->pil), pid->pty);
return;
} else if (pid->luf) {
pil_no_longer_transmitted (pid);
/* This example does not support VCR
reprogramming. */
return;
} else if (pid->pil == curr_pid.pil) {
if (delayed_stop_at < DBL_MAX) {
/* We lost all PDC signals and
timer_control() arranged for a
delayed stop. Or we received an INT
or RI/T code or a different PIL
than curr_program->pil with
MI=0. But now we receive
curr_program->pil again. */
delayed_stop_at = DBL_MAX;
msg ("Delayed stop canceled.\n");
return;
} else {
/* We lost all PDC signals and
timer_control() started recording
out of SCAN or PTR state, but now
we receive curr_program->pil
(again). Or this is just a
retransmission of the PIL which
started recording. Either way, we
do not return to VCR_STATE_PTR if
PRF is (still or again) 1. */
msg ("Already recording.\n");
return;
}
} else {
pil_no_longer_transmitted (pid);
if (VCR_STATE_SCAN != vcr_state) {
/* Stopping later. */
return;
}
p = find_program_by_pil (pid->pil);
}
break;
}
assert (VCR_STATE_SCAN == vcr_state);
if (NULL == p)
return;
if (pid->prf) {
prepare_to_record_by_pil (p, pid);
} else {
start_recording_by_pil (p, pid);
}
}
static void
event_handler (vbi_event * ev,
void * user_data)
{
const vbi_program_id *pid;
vbi_pid_channel lci;
user_data = user_data; /* unused, no warning please */
assert (VCR_STATE_STBY != vcr_state);
pid = ev->ev.prog_id;
lci = pid->channel;
switch (lci) {
case VBI_PID_CHANNEL_LCI_0:
case VBI_PID_CHANNEL_LCI_1:
case VBI_PID_CHANNEL_LCI_2:
case VBI_PID_CHANNEL_LCI_3:
break;
case VBI_PID_CHANNEL_VPS:
/* EN 300 231 Section 9.4.1: "When both line 16 (VPS)
and Teletext-delivered labels are available
simultaneously, decoders should default to the
Teletext-delivered service;" */
if (teletext_8302_available ())
goto finish;
break;
default:
/* Support for other sources not implemented yet. */
return;
}
msg ("Received PIL %s/%02X on LC %u.\n",
pil_str (pid->pil), pid->pty, lci);
switch (pid->pil) {
case VBI_PIL_TIMER_CONTROL:
case VBI_PIL_CONTINUE:
signal_or_service_lost ();
break;
case VBI_PIL_INTERRUPTION:
case VBI_PIL_INHIBIT_TERMINATE:
received_int_rit (pid);
break;
default:
received_pil (pid);
break;
}
finish:
lc_state[lci].pil = pid->pil;
lc_state[lci].last_at = timestamp;
}
static vbi_bool
in_pil_validity_window (void)
{
struct program *p;
for (p = schedule; NULL != p; p = p->next) {
/* The announced start and end time should fall within
the PIL validity window, but just in case. */
if ((audience_time >= p->start_time
&& audience_time < p->end_time)
|| (audience_time >= p->pil_valid_start
&& audience_time < p->pil_valid_end))
return TRUE;
}
return FALSE;
}
static void
timer_control (void)
{
struct program *p;
assert (timer_control_mode);
switch (vcr_state) {
case VCR_STATE_STBY:
case VCR_STATE_PTR:
assert (0);
case VCR_STATE_SCAN:
break;
case VCR_STATE_REC:
if (delayed_stop_at < DBL_MAX) {
/* Will stop later. */
return;
} else if (audience_time >= curr_program->end_time) {
stop_recording_now ();
/* We remove the program from the schedule as
shown in EN 300 231 Annex E.3, Example 11,
01:58:00. However as the example itself
demonstrates this is not in the best
interest of the user. A better idea may be
to keep the program scheduled until
curr_program->pil_valid_end, in case the
program is late or overrunning and we
receive its PIL after all. */
remove_program_from_schedule (curr_program);
} else {
/* Still running. */
return;
}
assert (VCR_STATE_SCAN == vcr_state);
break;
}
for (p = schedule; NULL != p; p = p->next) {
/* Note if no program length has been specified
(start_time == end_time) this function will not
record the program. */
/* We must also compare against p->end_time because we
will not always remove the program from the
schedule at that time. See
remove_program_if_ended(). */
if (audience_time >= p->start_time
&& audience_time < p->end_time) {
start_recording_by_timer (p);
return;
}
}
}
static void
pdc_signal_check (void)
{
static const unsigned int ttx_chs =
((1 << VBI_PID_CHANNEL_LCI_0) |
(1 << VBI_PID_CHANNEL_LCI_0) |
(1 << VBI_PID_CHANNEL_LCI_0) |
(1 << VBI_PID_CHANNEL_LCI_0));
static const unsigned int vps_ch =
(1 << VBI_PID_CHANNEL_VPS);
unsigned int active_chs;
unsigned int lost_chs;
vbi_pid_channel i;
if (timer_control_mode)
return;
/* Determine if we lost signals. */
active_chs = 0;
lost_chs = 0;
for (i = 0; i < VBI_MAX_PID_CHANNELS; ++i) {
double timeout_at;
if (0 == lc_state[i].pil)
continue;
timeout_at = lc_state[i].last_at + signal_timeout[i];
if (timestamp >= timeout_at) {
lost_chs |= 1 << i;
} else {
active_chs |= 1 << i;
}
}
/* For now only Teletext and VPS delivery is supported, so we
don't check other channels. */
if (0 == active_chs) {
if (0 != lost_chs) {
msg ("All Teletext and VPS signals lost, "
"will fall back to timer control.\n");
signal_or_service_lost ();
}
} else {
if (vps_ch == active_chs
&& 0 != (lost_chs & ttx_chs)) {
msg ("Teletext signal lost, "
"will fall back to VPS.\n");
if (curr_pid.pil
== lc_state[VBI_PID_CHANNEL_VPS].pil) {
curr_pid.channel = VBI_PID_CHANNEL_VPS;
}
}
if ((VCR_STATE_PTR == vcr_state
|| VCR_STATE_REC == vcr_state)
&& 0 != curr_pid.pil
&& 0 != (lost_chs & (1 << curr_pid.channel))) {
/* Note if multiple label channels are in use
(Teletext only) a PIL may just "disappear"
without a RI/T service code or other PIL
subsequently transmitted on the same
channel. */
pil_no_longer_transmitted (/* pid */ NULL);
}
}
if (0 != lost_chs) {
for (i = 0; i < VBI_MAX_PID_CHANNELS; ++i) {
if (0 == (lost_chs & (1 << i)))
continue;
lc_state[i].pil = 0;
lc_state[i].last_at = timestamp;
}
}
}
static void
parse_test_file_line (time_t * timestamp,
vbi_program_id * pid,
enum vcr_state * exp_state,
unsigned int line_counter,
const char * test_file_line)
{
struct tm tm;
const char *s;
char *s_end;
const char *detail;
unsigned long ul;
/* Test file format (based on examples in EN 300 231):
One line of text for each PID change, with 4 or 9 fields
separated by one or more tabs or spaces:
1. Name of the broadcasting network, e.g. BBC1.
2. Date of the change: yyyymmddThhmmss (local time)
or yyyymmddThhmmssZ (UTC)
Lines must be sorted by this date, oldest first. Dates
must not repeat unless these lines have different LCI
fields.
3. Label Channel Identifier (vbi_pid_channel): 0 ... n
or the name VPS (channel 4).
4. Label Update Flag: 0 or 1.
5. Mode Identifier: 0 or 1 or x (any).
6. Prepare to Record Flag: 0 or 1 or x (any).
7. Program Identification Label: mmddThhmm or one of the
names
- TC (Timer Control code)
- RI/T (Recording Inhibit/Terminate code)
- INT (Interruption code)
- CONT (Continuation code)
- NSPV (No Specific PIL Value).
A Program Type can be appended, separated by a slash:
- /A to /Z (Series Code)
- /NN (a hex number, e.g. /3F)
8. Channel or Network Identifier: a name like BBC1.
9. Expected VCR state:
- STBY
- SCAN
- PTR
- REC.
If fields 4 to 8 are omitted the transmission of the label
on the given label channel ceases. If field 9 is omitted
the same VCR state as before is expected.
All text after a number sign (#) is ignored.
*/
s = test_file_line;
/* Network name ignored in this example. */
while (isalnum (*s))
++s;
detail = "channel field";
if (!isspace (*s))
goto invalid;
memset (&tm, 0, sizeof (tm));
tm.tm_isdst = -1; /* unknown */
s = strptime (s, "%Y%m%dT%H%M%S", &tm);
detail = "date field";
if (NULL == s)
goto invalid;
while (isspace (*s))
++s;
if ('Z' == *s) {
++s;
*timestamp = timegm (&tm);
} else {
*timestamp = mktime (&tm);
}
if ((time_t) -1 == *timestamp)
goto invalid;
memset (pid, 0, sizeof (*pid));
while (isspace (*s))
++s;
if (0 == strncmp (s, "VPS", 3)) {
pid->channel = VBI_PID_CHANNEL_VPS;
s += 3;
} else {
ul = strtoul (s, &s_end, 0);
detail = "LCI field";
if (s_end == s
|| ul >= (unsigned long) VBI_MAX_PID_CHANNELS)
goto invalid;
pid->channel = ul;
s = s_end;
}
while (isspace (*s))
++s;
if (!isdigit (*s)) {
/* Cease transmission on this label channel,
pid->pil = 0. */
} else {
ul = strtoul (s, &s_end, 0);
detail = "LUF field";
if (s_end == s || ul > 1)
goto invalid;
pid->luf = ul;
s = s_end;
while (isspace (*s))
++s;
if ('x' == *s) {
++s;
} else {
ul = strtoul (s, &s_end, 0);
detail = "MI field";
if (s_end == s || ul > 1)
goto invalid;
pid->mi = ul;
s = s_end;
}
while (isspace (*s))
++s;
if ('x' == *s) {
++s;
} else {
ul = strtoul (s, &s_end, 0);
detail = "PRF field";
if (s_end == s || ul > 1)
goto invalid;
pid->prf = ul;
s = s_end;
}
while (isspace (*s))
++s;
if (0 == strncmp (s, "CONT", 4)) {
pid->pil = VBI_PIL_CONTINUE;
s += 4;
} else if (0 == strncmp (s, "END", 3)) {
pid->pil = VBI_PIL_END;
s += 3;
} else if (0 == strncmp (s, "INT", 3)) {
pid->pil = VBI_PIL_INTERRUPTION;
s += 3;
} else if (0 == strncmp (s, "NSPV", 4)) {
pid->pil = VBI_PIL_NSPV;
s += 4;
} else if (0 == strncmp (s, "RI/T", 4)) {
pid->pil = VBI_PIL_INHIBIT_TERMINATE;
s += 4;
} else if (0 == strncmp (s, "TC", 2)) {
pid->pil = VBI_PIL_TIMER_CONTROL;
s += 2;
} else {
ul = strtoul (s, &s_end, 10);
detail = "PIL field";
if (s_end == s
|| ul % 100 > 31
|| ul > 1531)
goto invalid;
s = s_end;
if (ul > 0) {
pid->pil = VBI_PIL (ul / 100,
ul % 100, 0, 0);
if ('T' != *s++)
goto invalid;
ul = strtoul (s, &s_end, 10);
if (s_end == s
|| ul % 100 > 63
|| ul > 3163)
goto invalid;
s = s_end;
pid->pil |= VBI_PIL (0, 0,
ul / 100,
ul % 100);
}
}
if ('/' == *s) {
do ++s;
while (isspace (*s));
if (isalpha (s[0]) && 0x20 == s[1]) {
/* Series code. This isn't magic, EN
300 231 just gives letters instead
of the codes 0x80 ... 0xFF for
easier reading. */
pid->pty = 0x80 | *s++;
} else {
ul = strtoul (s, &s_end, 16);
detail = "PTY field";
if (s_end == s || ul > 0xFF)
goto invalid;
pid->pty = ul;
s = s_end;
}
} else {
pid->pty = 0;
}
/* Network name ignored in this example. */
while (isspace (*s))
++s;
while (isalnum (*s))
++s;
detail = "CNI field";
if (0 != *s && !isspace (*s))
goto invalid;
if (VBI_PID_CHANNEL_VPS == pid->channel) {
pid->cni_type = VBI_CNI_TYPE_VPS;
pid->cni = 0x1234;
} else {
pid->cni_type = VBI_CNI_TYPE_8302;
pid->cni = 0x1234;
}
}
while (isspace (*s))
++s;
if ('#' == *s || 0 == *s) {
*exp_state = -1; /* no change */
return;
} else if (0 == strncmp (s, "PTR", 3)) {
*exp_state = VCR_STATE_PTR;
s += 3;
} else if (0 == strncmp (s, "REC", 3)) {
*exp_state = VCR_STATE_REC;
s += 3;
} else if (0 == strncmp (s, "SCAN", 4)) {
*exp_state = VCR_STATE_SCAN;
s += 4;
} else if (0 == strncmp (s, "STBY", 4)) {
*exp_state = VCR_STATE_STBY;
s += 4;
} else {
detail = "VCR state field";
goto invalid;
}
while (isspace (*s))
++s;
if ('#' == *s || 0 == *s)
return;
detail = "garbage at end of line";
invalid:
fprintf (stderr, "Error in test file line %u, %s:\n%s\n",
line_counter, detail, test_file_line);
exit (EXIT_FAILURE);
}
static void
simulate_signals (void)
{
static char buffer[256];
static vbi_program_id test_pid[VBI_MAX_PID_CHANNELS];
static vbi_program_id next_pid;
static time_t next_event_time = 0;
static enum vcr_state next_exp_vcr_state = (enum vcr_state) -1;
static unsigned int line_counter;
vbi_pid_channel i;
while (timestamp >= next_event_time) {
if (0 != buffer[0]) {
printf ("> %s", buffer);
test_pid[next_pid.channel] = next_pid;
if ((enum vcr_state) -1 == next_exp_vcr_state)
test_exp_vcr_state = test_exp_vcr_state;
else
test_exp_vcr_state = next_exp_vcr_state;
}
for (;;) {
const char *s;
if (NULL == fgets (buffer, sizeof (buffer),
stdin)) {
printf ("End of test file.\n");
next_event_time = INT_MAX;
quit = TRUE;
break;
}
s = buffer;
while (isspace (*s))
++s;
if (0 == *s)
continue;
if ('#' == *s) {
printf ("> %s", s);
continue;
}
parse_test_file_line (&next_event_time,
&next_pid,
&next_exp_vcr_state,
line_counter, s);
++line_counter;
break;
}
}
/* See standby_loop(). */
audience_time = (time_t) timestamp;
/* We stop recording before examining the received PIDs so we
can respond to a new PID immediately. */
if (VCR_STATE_REC == vcr_state
&& timestamp >= delayed_stop_at) {
stop_recording_now ();
assert (VCR_STATE_SCAN == vcr_state);
remove_program_if_ended (curr_program,
&delayed_stop_pid);
}
/* Note in reality PIDs may arrive in any order, with a delay
of several frames between them. */
for (i = 0; i < VBI_MAX_PID_CHANNELS; ++i) {
if (0 != test_pid[i].pil) {
vbi_event ev;
memset (&ev, 0, sizeof (ev));
ev.ev.prog_id = &test_pid[i];
event_handler (&ev, /* user_data */ NULL);
}
}
}
static void
capture_and_decode_frame (void)
{
struct timeval timeout;
vbi_capture_buffer *sliced_buffer;
unsigned int n_lines;
int r;
/* Don't wait more than two seconds for the driver
to return data. */
timeout.tv_sec = 2;
timeout.tv_usec = 0;
r = vbi_capture_pull (cap,
/* raw_buffer */ NULL,
&sliced_buffer,
&timeout);
switch (r) {
case -1:
fprintf (stderr,
"VBI read error: %s.\n",
strerror (errno));
/* Could be ignored, esp. EIO from some
drivers. */
exit (EXIT_FAILURE);
case 0:
fprintf (stderr, "VBI read timeout\n");
exit (EXIT_FAILURE);
case 1: /* success */
break;
default:
assert (0);
}
timestamp = sliced_buffer->timestamp;
n_lines = sliced_buffer->size / sizeof (vbi_sliced);
/* See standby_loop(). */
audience_time = (time_t) timestamp;
/* We stop recording before examining the received PIDs so we
can respond to a new PID immediately. */
if (VCR_STATE_REC == vcr_state
&& timestamp >= delayed_stop_at) {
stop_recording_now ();
assert (VCR_STATE_SCAN == vcr_state);
remove_program_if_ended (curr_program,
&delayed_stop_pid);
}
/* Calls event_handler(). */
vbi_decode (dec, (vbi_sliced *) sliced_buffer->data,
n_lines, timestamp);
}
static void
close_vbi_device (void)
{
vbi_capture_delete (cap);
cap = NULL;
}
static void
open_vbi_device (void)
{
vbi_service_set services;
char *errstr;
services = (VBI_SLICED_TELETEXT_B |
VBI_SLICED_VPS);
cap = vbi_capture_v4l2_new (dev_name,
/* buffers */ 5,
&services,
/* strict */ 0,
&errstr,
/* verbose */ FALSE);
if (NULL == cap) {
fprintf (stderr,
"Cannot capture VBI data from %s "
"with V4L2 interface:\n"
"%s\n",
dev_name, errstr);
free (errstr);
exit (EXIT_FAILURE);
}
}
/* We wait in this function until we receive the expected PIL(s) or a
program starts and ends as scheduled, and record it. */
static void
capture_loop (void)
{
double last_timestamp;
assert (VCR_STATE_STBY == vcr_state);
if (!test_mode)
open_vbi_device ();
/* Reset the VBI decoder. */
vbi_channel_switched (dec, 0);
change_vcr_state (VCR_STATE_SCAN);
last_timestamp = 0;
while (VCR_STATE_STBY != vcr_state && !quit) {
if (test_mode) {
simulate_signals ();
} else {
capture_and_decode_frame ();
}
/* Once per second is enough. */
if ((long) last_timestamp != (long) timestamp) {
if (!timer_control_mode) {
/* May enable timer control mode. */
pdc_signal_check ();
}
if (timer_control_mode)
timer_control ();
}
last_timestamp = timestamp;
if (VCR_STATE_SCAN == vcr_state
&& !in_pil_validity_window ()) {
change_vcr_state (VCR_STATE_STBY);
}
if (test_mode) {
if ((enum vcr_state) -1 != test_exp_vcr_state
&& test_exp_vcr_state != vcr_state) {
printf ("*** Unexpected VCR state %s\n",
vcr_state_name (vcr_state));
exit_code = EXIT_FAILURE;
}
/* Advance by one second. Note a VPS signal is
transmitted on each frame, 25 times per
second, but we simulate at most one PID
change per second per label channel. */
++timestamp;
}
}
if (!test_mode)
close_vbi_device ();
}
/* We wait in this function until the starting time of the earliest
program on the recording schedule is approaching. */
static void
standby_loop (void)
{
while (!quit) {
struct program *p;
time_t first_scan;
assert (VCR_STATE_STBY == vcr_state);
if (test_mode) {
/* Simulated current time. */
audience_time = (time_t) timestamp;
} else {
/* The current time of the intended audience
of the tuned in network according to the
network. It may differ from system time if
the system is not in sync with UTC or if we
receive the TV signal with a delay. For
simplicity we will not determine the offset
in this example, see VBI_EVENT_LOCAL_TIME
if you want to try that. */
audience_time = time (NULL);
}
remove_stale_programs_from_schedule ();
if (NULL == schedule) {
printf ("Recording schedule is empty.\n");
break;
}
first_scan = schedule->start_time;
for (p = schedule; NULL != p; p = p->next) {
if (p->start_time < first_scan)
first_scan = p->start_time;
if (p->pil_valid_start < first_scan)
first_scan = p->pil_valid_start;
}
while (first_scan > audience_time) {
char buffer[80];
struct tm tm;
memset (&tm, 0, sizeof (tm));
#ifdef _WIN32
localtime_s (&tm, &first_scan);
#else
localtime_r (&first_scan, &tm);
#endif
strftime (buffer, sizeof (buffer),
"%Y-%m-%d %H:%M:%S %Z", &tm);
msg ("Sleeping until %s.\n", buffer);
if (test_mode) {
audience_time = first_scan;
timestamp = first_scan;
} else {
/* In a loop because the sleep()
function may abort earlier. */
sleep (first_scan - audience_time);
audience_time = time (NULL);
}
}
capture_loop ();
}
}
static void
reset_state (void)
{
unsigned int i;
audience_time = 0.0;
timestamp = 0.0;
for (i = 0; i < VBI_MAX_PID_CHANNELS; ++i) {
lc_state[i].pil = 0; /* none received */
lc_state[i].last_at = 0.0;
}
vcr_state = VCR_STATE_STBY;
vcr_state_since = 0.0;
timer_control_mode = FALSE;
delayed_stop_at = DBL_MAX;
test_exp_vcr_state = (enum vcr_state) -1; /* unknown */
}
static void
add_program_to_schedule (const struct tm * start_tm,
const struct tm * end_tm,
const struct tm * pdc_tm)
{
struct program *p;
struct program **pp;
struct tm tm;
time_t pil_time;
/* Note PILs represent the originally announced start date of
the program in the time zone of the intended audience. When
we convert pdc_tm to a PIL we assume that zone is the same
as the system time zone (TZ environment variable), and
start_tm, end_tm and pdc_tm are also given relative to this
time zone. We do not consider the case where a program
straddles a daylight saving time discontinuity, e.g. starts
in the CET zone and ends in the CEST zone. */
p = calloc (1, sizeof (*p));
assert (NULL != p);
tm = *start_tm;
tm.tm_isdst = -1; /* unknown */
p->start_time = mktime (&tm);
if ((time_t) -1 == p->start_time) {
fprintf (stderr, "Invalid start time.\n");
exit (EXIT_FAILURE);
}
tm = *start_tm;
tm.tm_isdst = -1; /* unknown */
tm.tm_hour = end_tm->tm_hour;
tm.tm_min = end_tm->tm_min;
if (end_tm->tm_hour < start_tm->tm_hour) {
/* mktime() should handle a 32nd. */
++tm.tm_mday;
}
p->end_time = mktime (&tm);
if ((time_t) -1 == p->end_time) {
fprintf (stderr, "Invalid end time.\n");
exit (EXIT_FAILURE);
}
tm = *start_tm;
tm.tm_isdst = -1; /* unknown */
tm.tm_hour = pdc_tm->tm_hour;
tm.tm_min = pdc_tm->tm_min;
if (pdc_tm->tm_hour >= start_tm->tm_hour + 12) {
/* mktime() should handle a 0th. */
--tm.tm_mday;
} else if (pdc_tm->tm_hour + 12 < start_tm->tm_hour) {
++tm.tm_mday;
}
/* Normalize day and month. */
pil_time = mktime (&tm);
#ifdef _WIN32
if ((time_t) -1 == pil_time
|| 0 != localtime_s (&tm, &pil_time)) {
#else
if ((time_t) -1 == pil_time
|| NULL == localtime_r (&pil_time, &tm)) {
#endif
fprintf (stderr, "Cannot determine PIL month/day.\n");
exit (EXIT_FAILURE);
}
p->pil = VBI_PIL (tm.tm_mon + 1, /* 1 ... 12 */
tm.tm_mday,
tm.tm_hour,
tm.tm_min);
if (!vbi_pil_validity_window (&p->pil_valid_start,
&p->pil_valid_end,
p->pil,
p->start_time,
NULL /* system tz */)) {
fprintf (stderr, "Cannot determine PIL validity.\n");
exit (EXIT_FAILURE);
}
p->index = 0;
for (pp = &schedule; NULL != *pp; pp = &(*pp)->next)
++p->index;
*pp = p;
if (0) {
printf ("Program %u start: ", p->index);
print_time (p->start_time);
printf ("End: ");
print_time (p->end_time);
printf ("PIL: ");
print_time (pil_time);
printf ("PIL valid from: ");
print_time (p->pil_valid_start);
printf ("PIL valid until: ");
print_time (p->pil_valid_end);
}
}
static void
usage (FILE * fp)
{
fprintf (fp,
"Please specify the start time of a program in the format\n"
"YYYY-MM-DD HH:MM, the end time HH:MM and a VPS/PDC time HH:MM.\n");
}
static void
parse_args (int argc,
char ** argv)
{
struct tm start_tm;
struct tm end_tm;
struct tm pdc_tm;
dev_name = "/dev/vbi";
for (;;) {
int c;
c = getopt (argc, argv, "d:ht");
if (-1 == c)
break;
switch (c) {
case 'd':
dev_name = optarg;
break;
case 'h':
usage (stdout);
exit (EXIT_SUCCESS);
case 't':
test_mode = TRUE;
break;
default:
usage (stderr);
exit (EXIT_FAILURE);
}
}
while (argc - optind >= 4) {
memset (&start_tm, 0, sizeof (struct tm));
if (NULL == strptime (argv[optind + 0], "%Y-%m-%d",
&start_tm))
goto invalid;
if (NULL == strptime (argv[optind + 1], "%H:%M",
&start_tm))
goto invalid;
memset (&end_tm, 0, sizeof (struct tm));
if (NULL == strptime (argv[optind + 2], "%H:%M",
&end_tm))
goto invalid;
memset (&pdc_tm, 0, sizeof (struct tm));
if (NULL == strptime (argv[optind + 3], "%H:%M",
&pdc_tm))
goto invalid;
add_program_to_schedule (&start_tm, &end_tm, &pdc_tm);
optind += 4;
}
if (argc != optind)
goto invalid;
return;
invalid:
usage (stderr);
exit (EXIT_FAILURE);
}
int
main (int argc,
char ** argv)
{
vbi_bool success;
setlocale (LC_ALL, "");
parse_args (argc, argv);
exit_code = EXIT_SUCCESS;
dec = vbi_decoder_new ();
assert (NULL != dec);
success = vbi_event_handler_register (dec, VBI_EVENT_PROG_ID,
event_handler,
/* user_data */ NULL);
assert (success);
reset_state ();
standby_loop ();
vbi_decoder_delete (dec);
while (NULL != schedule)
remove_program_from_schedule (schedule);
exit (exit_code);
}
vbi_event_handler_register
vbi_bool vbi_event_handler_register(vbi_decoder *vbi, int event_mask, vbi_event_handler handler, void *user_data)
Definition vbi.c:278
VBI_PIL_DAY
#define VBI_PIL_DAY(pil)
Definition pdc.h:79
vbi_pid_channel
vbi_pid_channel
Sources of PIDs.
Definition pdc.h:246
VBI_PIL
#define VBI_PIL(month, day, hour, minute)
Macro to create a PIL.
Definition pdc.h:72
VBI_PIL_HOUR
#define VBI_PIL_HOUR(pil)
Definition pdc.h:82
vbi_pil
unsigned int vbi_pil
Program Identification Label.
Definition pdc.h:57
VBI_PIL_MINUTE
#define VBI_PIL_MINUTE(pil)
Definition pdc.h:85
VBI_PIL_MONTH
#define VBI_PIL_MONTH(pil)
Definition pdc.h:76
vbi_pil_validity_window
vbi_bool vbi_pil_validity_window(time_t *begin, time_t *end, vbi_pil pil, time_t start, const char *tz)
Definition pdc.c:1412
VBI_PID_CHANNEL_VPS
@ VBI_PID_CHANNEL_VPS
Definition pdc.h:283
VBI_MAX_PID_CHANNELS
@ VBI_MAX_PID_CHANNELS
Definition pdc.h:313
VBI_PID_CHANNEL_LCI_0
@ VBI_PID_CHANNEL_LCI_0
Definition pdc.h:266
VBI_PID_CHANNEL_LCI_3
@ VBI_PID_CHANNEL_LCI_3
Definition pdc.h:275
VBI_PID_CHANNEL_LCI_2
@ VBI_PID_CHANNEL_LCI_2
Definition pdc.h:272
VBI_PID_CHANNEL_LCI_1
@ VBI_PID_CHANNEL_LCI_1
Definition pdc.h:269
VBI_PIL_CONTINUE
@ VBI_PIL_CONTINUE
Definition pdc.h:145
VBI_PIL_END
@ VBI_PIL_END
Definition pdc.h:162
VBI_PIL_INTERRUPTION
@ VBI_PIL_INTERRUPTION
Definition pdc.h:135
VBI_PIL_INHIBIT_TERMINATE
@ VBI_PIL_INHIBIT_TERMINATE
Definition pdc.h:120
VBI_PIL_NSPV
@ VBI_PIL_NSPV
Definition pdc.h:154
VBI_PIL_TIMER_CONTROL
@ VBI_PIL_TIMER_CONTROL
Definition pdc.h:108
vbi_decoder_new
vbi_decoder * vbi_decoder_new(void)
Allocate a new data service decoder instance.
Definition vbi.c:870
vbi_decode
void vbi_decode(vbi_decoder *vbi, vbi_sliced *sliced, int lines, double time)
Main function of the data service decoder.
Definition vbi.c:423
vbi_channel_switched
void vbi_channel_switched(vbi_decoder *vbi, vbi_nuid nuid)
Definition vbi.c:580
vbi_decoder_delete
void vbi_decoder_delete(vbi_decoder *vbi)
Delete a data service decoder instance.
Definition vbi.c:832
event_handler
Definition vbi.h:41
vbi_decoder
Definition vbi.h:48
vbi_event
Event union.
Definition event.h:736
vbi_program_id
Program Identification.
Definition pdc.h:351
vbi_program_id::pty
unsigned int pty
Definition pdc.h:421
vbi_program_id::pil
vbi_pil pil
Definition pdc.h:375
vbi_program_id::prf
vbi_bool prf
Definition pdc.h:412
vbi_program_id::cni_type
vbi_cni_type cni_type
Definition pdc.h:361
vbi_program_id::channel
vbi_pid_channel channel
Definition pdc.h:353
vbi_program_id::mi
vbi_bool mi
Definition pdc.h:404
vbi_program_id::cni
unsigned int cni
Definition pdc.h:369
vbi_program_id::luf
vbi_bool luf
Definition pdc.h:393
vbi_sliced
This structure holds one scan line of sliced vbi data.
Definition sliced.h:320