Files
2026-08-24 10:03:24 +08:00

9631 lines
215 KiB
C

/*
* atsc-cc -- ATSC Closed Caption decoder
*
* Copyright (C) 2008 Michael H. Schimek <[email protected]>
*
* Contains code from zvbi-ntsc-cc closed caption decoder written by
* <[email protected]>, Mike Baker <[email protected]>,
* Mark K. Kim <[email protected]>.
*
* Thanks to Karol Zapolski for his support.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#define _GNU_SOURCE 1
#include <aio.h>
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <locale.h>
#include <malloc.h>
#include <math.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_GETOPT_LONG
# include <getopt.h>
#endif
#include "src/libzvbi.h"
/* Linux DVB driver interface. */
#include "src/dvb/dmx.h"
#include "src/dvb/frontend.h"
#undef PROGRAM
#define PROGRAM "ATSC-CC"
#undef VERSION
#define VERSION "0.4"
#if __GNUC__ < 3
# define likely(expr) (expr)
# define unlikely(expr) (expr)
#else
# define likely(expr) __builtin_expect(expr, 1)
# define unlikely(expr) __builtin_expect(expr, 0)
#endif
#define N_ELEMENTS(array) (sizeof (array) / sizeof ((array)[0]))
#define CLEAR(var) memset (&(var), 0, sizeof (var))
/* FIXME __typeof__ is a GCC extension. */
#undef SWAP
#define SWAP(x, y) \
do { \
__typeof__ (x) _x = x; \
x = y; \
y = _x; \
} while (0)
#undef MIN
#define MIN(x, y) ({ \
__typeof__ (x) _x = (x); \
__typeof__ (y) _y = (y); \
(void)(&_x == &_y); /* warn if types do not match */ \
/* return */ (_x < _y) ? _x : _y; \
})
#undef MAX
#define MAX(x, y) ({ \
__typeof__ (x) _x = (x); \
__typeof__ (y) _y = (y); \
(void)(&_x == &_y); /* warn if types do not match */ \
/* return */ (_x > _y) ? _x : _y; \
})
#undef PARENT
#define PARENT(_ptr, _type, _member) ({ \
__typeof__ (&((_type *) 0)->_member) _p = (_ptr); \
(_p != 0) ? (_type *)(((char *) _p) - offsetof (_type, \
_member)) : (_type *) 0; \
})
/* These should be defined in inttypes.h. */
#ifndef PRId64
# define PRId64 "lld"
#endif
#ifndef PRIu64
# define PRIu64 "llu"
#endif
#ifndef PRIx64
# define PRIx64 "llx"
#endif
/* EIA 608-B decoder. */
enum field_num {
FIELD_1 = 0,
FIELD_2,
MAX_FIELDS
};
enum cc_mode {
CC_MODE_UNKNOWN,
CC_MODE_ROLL_UP,
CC_MODE_POP_ON,
CC_MODE_PAINT_ON,
CC_MODE_TEXT
};
/* EIA 608-B Section 4.1. */
#define VBI_CAPTION_CC1 1 /* primary synchronous caption service (F1) */
#define VBI_CAPTION_CC2 2 /* special non-synchronous use captions (F1) */
#define VBI_CAPTION_CC3 3 /* secondary synchronous caption service (F2) */
#define VBI_CAPTION_CC4 4 /* special non-synchronous use captions (F2) */
#define VBI_CAPTION_T1 5 /* first text service (F1) */
#define VBI_CAPTION_T2 6 /* second text service (F1) */
#define VBI_CAPTION_T3 7 /* third text service (F2) */
#define VBI_CAPTION_T4 8 /* fourth text service (F2) */
#define UNKNOWN_CC_CHANNEL 0
#define MAX_CC_CHANNELS 8
/* 47 CFR 15.119 (d) Screen format. */
#define CC_FIRST_ROW 0
#define CC_LAST_ROW 14
#define CC_MAX_ROWS 15
#define CC_FIRST_COLUMN 1
#define CC_LAST_COLUMN 32
#define CC_MAX_COLUMNS 32
#define CC_ALL_ROWS_MASK ((1 << CC_MAX_ROWS) - 1)
#define VBI_TRANSLUCENT VBI_SEMI_TRANSPARENT
struct cc_timestamp {
/* System time when the event occured, zero if no event
occured yet. */
struct timeval sys;
/* Presentation time stamp of the event. Only the 33 least
significant bits are valid. < 0 if no event occured yet. */
int64_t pts;
};
struct cc_channel {
/**
* [0] and [1] are the displayed and non-displayed buffer as
* defined in 47 CFR 15.119, and selected by displayed_buffer
* below. [2] is a snapshot of the displayed buffer at the
* last stream event.
*
* XXX Text channels don't need buffer[2] and buffer[3], we're
* wasting memory.
*/
uint16_t buffer[3][CC_MAX_ROWS][1 + CC_MAX_COLUMNS];
/**
* For buffer[0 ... 2], if bit 1 << row is set this row
* contains displayable characters, spacing or non-spacing
* attributes. (Special character 0x1139 "transparent space"
* is not a displayable character.) This information is
* intended to speed up copying, erasing and formatting.
*/
unsigned int dirty[3];
/** Index of displayed buffer, 0 or 1. */
unsigned int displayed_buffer;
/**
* Cursor position: FIRST_ROW ... LAST_ROW and
* FIRST_COLUMN ... LAST_COLUMN.
*/
unsigned int curr_row;
unsigned int curr_column;
/**
* Text window height in CC_MODE_ROLL_UP. The first row of the
* window is curr_row - window_rows + 1, the last row is
* curr_row.
*
* Note: curr_row - window_rows + 1 may be < FIRST_ROW, this
* must be clipped before using window_rows:
*
* actual_rows = MIN (curr_row - FIRST_ROW + 1, window_rows);
*
* We won't do that at the RUx command because usually a PAC
* follows which may change curr_row.
*/
unsigned int window_rows;
/* Most recently received PAC command. */
unsigned int last_pac;
/**
* This variable counts successive transmissions of the
* letters A to Z. It is reset to zero on reception of any
* letter a to z.
*
* Some stations do not transmit EIA 608-B extended characters
* and except for N with tilde the standard and special
* character sets contain only lower case accented
* characters. We force these characters to upper case if this
* variable indicates live caption, which is usually all upper
* case.
*/
unsigned int uppercase_predictor;
/** Current caption mode or CC_MODE_UNKNOWN. */
enum cc_mode mode;
/**
* The time when we last received data for this
* channel. Intended to detect if this caption channel is
* active.
*/
struct cc_timestamp timestamp;
/**
* The time when we received the first (but not necessarily
* leftmost) character in the current row. Unless the mode is
* CC_MODE_POP_ON the next stream event will carry this
* timestamp.
*/
struct cc_timestamp timestamp_c0;
};
struct cc_decoder {
/**
* Decoder state. We decode all channels in parallel, this way
* clients can switch between channels without data loss, or
* capture multiple channels with a single decoder instance.
*
* Also 47 CFR 15.119 and EIA 608-C require us to remember the
* cursor position on each channel.
*/
struct cc_channel channel[MAX_CC_CHANNELS];
/**
* Current channel, switched by caption control codes. Can be
* one of @c VBI_CAPTION_CC1 ... @c VBI_CAPTION_CC4 or @c
* VBI_CAPTION_T1 ... @c VBI_CAPTION_T4 or @c
* UNKNOWN_CC_CHANNEL if no channel number was received yet.
*/
vbi_pgno curr_ch_num[MAX_FIELDS];
/**
* Caption control codes (two bytes) may repeat once for error
* correction. -1 if no repeated control code can be expected.
*/
int expect_ctrl[MAX_FIELDS][2];
/** Receiving XDS data, as opposed to caption / ITV data. */
vbi_bool in_xds[MAX_FIELDS];
/**
* Pointer into the channel[] array if a display update event
* shall be sent at the end of this iteration, %c NULL
* otherwise. Purpose is to suppress an event for the first of
* two displayable characters in a caption byte pair.
*/
struct cc_channel * event_pending;
/**
* Remembers past parity errors: One bit for each call of
* cc_feed(), most recent result in lsb. The idea is to
* disable the decoder if we detect too many errors.
*/
unsigned int error_history;
/**
* The time when we last received data, including NUL bytes.
* Intended to detect if the station transmits any data on
* line 21 or 284 at all.
*/
struct cc_timestamp timestamp;
};
/* CEA 708-C decoder. */
enum justify {
JUSTIFY_LEFT = 0,
JUSTIFY_RIGHT,
JUSTIFY_CENTER,
JUSTIFY_FULL
};
enum direction {
DIR_LEFT_RIGHT = 0,
DIR_RIGHT_LEFT,
DIR_TOP_BOTTOM,
DIR_BOTTOM_TOP
};
enum display_effect {
DISPLAY_EFFECT_SNAP = 0,
DISPLAY_EFFECT_FADE,
DISPLAY_EFFECT_WIPE
};
enum opacity {
OPACITY_SOLID = 0,
OPACITY_FLASH,
OPACITY_TRANSLUCENT,
OPACITY_TRANSPARENT
};
enum edge {
EDGE_NONE = 0,
EDGE_RAISED,
EDGE_DEPRESSED,
EDGE_UNIFORM,
EDGE_SHADOW_LEFT,
EDGE_SHADOW_RIGHT
};
enum pen_size {
PEN_SIZE_SMALL = 0,
PEN_SIZE_STANDARD,
PEN_SIZE_LARGE
};
enum font_style {
FONT_STYLE_DEFAULT = 0,
FONT_STYLE_MONO_SERIF,
FONT_STYLE_PROP_SERIF,
FONT_STYLE_MONO_SANS,
FONT_STYLE_PROP_SANS,
FONT_STYLE_CASUAL,
FONT_STYLE_CURSIVE,
FONT_STYLE_SMALL_CAPS
};
enum text_tag {
TEXT_TAG_DIALOG = 0,
TEXT_TAG_SOURCE_ID,
TEXT_TAG_DEVICE,
TEXT_TAG_DIALOG_2,
TEXT_TAG_VOICEOVER,
TEXT_TAG_AUDIBLE_TRANSL,
TEXT_TAG_SUBTITLE_TRANSL,
TEXT_TAG_VOICE_DESCR,
TEXT_TAG_LYRICS,
TEXT_TAG_EFFECT_DESCR,
TEXT_TAG_SCORE_DESCR,
TEXT_TAG_EXPLETIVE,
TEXT_TAG_NOT_DISPLAYABLE = 15
};
enum offset {
OFFSET_SUBSCRIPT = 0,
OFFSET_NORMAL,
OFFSET_SUPERSCRIPT
};
/* RGB 2:2:2 (lsb = B). */
typedef uint8_t dtvcc_color;
/* Lsb = window 0, msb = window 7. */
typedef uint8_t dtvcc_window_map;
struct dtvcc_pen_style {
enum pen_size pen_size;
enum font_style font_style;
enum offset offset;
vbi_bool italics;
vbi_bool underline;
enum edge edge_type;
dtvcc_color fg_color;
enum opacity fg_opacity;
dtvcc_color bg_color;
enum opacity bg_opacity;
dtvcc_color edge_color;
};
struct dtvcc_pen {
enum text_tag text_tag;
struct dtvcc_pen_style style;
};
struct dtvcc_window_style {
enum justify justify;
enum direction print_direction;
enum direction scroll_direction;
vbi_bool wordwrap;
enum display_effect display_effect;
enum direction effect_direction;
unsigned int effect_speed; /* 1/10 sec */
dtvcc_color fill_color;
enum opacity fill_opacity;
enum edge border_type;
dtvcc_color border_color;
};
struct dtvcc_window {
/* EIA 708-C window state. */
uint16_t buffer[16][42];
vbi_bool visible;
/* 0 = highest ... 7 = lowest. */
unsigned int priority;
unsigned int anchor_point;
unsigned int anchor_horizontal;
unsigned int anchor_vertical;
vbi_bool anchor_relative;
unsigned int row_count;
unsigned int column_count;
vbi_bool row_lock;
vbi_bool column_lock;
unsigned int curr_row;
unsigned int curr_column;
struct dtvcc_pen curr_pen;
struct dtvcc_window_style style;
/* Our stuff. */
/**
* If bit 1 << row is set we already sent a stream event for
* this row.
*/
unsigned int streamed;
/**
* The time when we received the first (but not necessarily
* leftmost) character in the current row. Unless a
* DisplayWindow or ToggleWindow command completed the line
* the next stream event will carry this timestamp.
*/
struct cc_timestamp timestamp_c0;
};
struct dtvcc_service {
/* Interpretation Layer. */
struct dtvcc_window window[8];
struct dtvcc_window * curr_window;
dtvcc_window_map created;
/* For debugging. */
unsigned int error_line;
/* Service Layer. */
uint8_t service_data[128];
unsigned int service_data_in;
/** The time when we last received data for this service. */
struct cc_timestamp timestamp;
};
struct dtvcc_decoder {
struct dtvcc_service service[2];
/* Packet Layer. */
uint8_t packet[128];
unsigned int packet_size;
/* Next expected DTVCC packet sequence_number. Only the two
most significant bits are valid. < 0 if no sequence_number
has been received yet. */
int next_sequence_number;
/** The time when we last received data. */
struct cc_timestamp timestamp;
};
/* ATSC A/53 Part 4:2007 Closed Caption Data decoder. */
enum cc_type {
NTSC_F1 = 0,
NTSC_F2 = 1,
DTVCC_DATA = 2,
DTVCC_START = 3,
};
struct cc_data_decoder {
/* Test tap. */
const char * option_cc_data_tap_file_name;
FILE * cc_data_tap_fp;
/* For debugging. */
int64_t last_pts;
};
/* Caption recorder. */
enum cc_attr {
VBI_UNDERLINE = (1 << 0),
VBI_ITALIC = (1 << 2),
VBI_FLASH = (1 << 3)
};
struct cc_pen {
uint8_t attr;
uint8_t fg_color;
uint8_t fg_opacity;
uint8_t bg_color;
uint8_t bg_opacity;
uint8_t edge_type;
uint8_t edge_color;
uint8_t edge_opacity;
uint8_t pen_size;
uint8_t font_style;
uint8_t reserved[6];
};
enum caption_format {
FORMAT_PLAIN,
FORMAT_VT100,
FORMAT_NTSC_CC
};
struct caption_recorder {
/* Caption stream filter:
NTSC CC1 ... CC4 (1 << 0 ... 1 << 3),
NTSC T1 ... T4 (1 << 4 ... 1 << 7),
ATSC Service 1 ... 2 (1 << 8 ... 1 << 9). */
unsigned int option_caption_mask;
/* Output file name for caption stream. */
const char * option_caption_file_name[10];
/* Output file name for XDS data. */
const char * option_xds_output_file_name;
vbi_bool option_caption_timestamps;
enum caption_format option_caption_format;
/* old options */
char usexds;
char usecc;
char usesen;
char usewebtv;
struct cc_data_decoder ccd;
struct cc_decoder cc;
struct dtvcc_decoder dtvcc;
//XDSdecode
unsigned int field;
struct {
char packet[34];
uint8_t length;
int print : 1;
} info[2][8][25];
char newinfo[2][8][25][34];
char *infoptr;
int mode,type;
char infochecksum;
const char * xds_info_prefix;
const char * xds_info_suffix;
FILE * xds_fp;
uint16_t * ucs_buffer;
unsigned int ucs_buffer_length;
unsigned int ucs_buffer_capacity;
FILE * caption_fp[10];
int minicut_min[10];
};
/* Video recorder. */
enum start_code {
PICTURE_START_CODE = 0x00,
/* 0x01 ... 0xAF slice_start_code */
/* 0xB0 reserved */
/* 0xB1 reserved */
USER_DATA_START_CODE = 0xB2,
SEQUENCE_HEADER_CODE = 0xB3,
SEQUENCE_ERROR_CODE = 0xB4,
EXTENSION_START_CODE = 0xB5,
/* 0xB6 reserved */
SEQUENCE_END_CODE = 0xB7,
GROUP_START_CODE = 0xB8,
/* 0xB9 ... 0xFF system start codes */
PRIVATE_STREAM_1 = 0xBD,
PADDING_STREAM = 0xBE,
PRIVATE_STREAM_2 = 0xBF,
AUDIO_STREAM_0 = 0xC0,
AUDIO_STREAM_31 = 0xDF,
VIDEO_STREAM_0 = 0xE0,
VIDEO_STREAM_15 = 0xEF,
};
enum extension_start_code_identifier {
/* 0x0 reserved */
SEQUENCE_EXTENSION_ID = 0x1,
SEQUENCE_DISPLAY_EXTENSION_ID = 0x2,
QUANT_MATRIX_EXTENSION_ID = 0x3,
COPYRIGHT_EXTENSION_ID = 0x4,
SEQUENCE_SCALABLE_EXTENSION_ID = 0x5,
/* 0x6 reserved */
PICTURE_DISPLAY_EXTENSION_ID = 0x7,
PICTURE_CODING_EXTENSION_ID = 0x8,
PICTURE_SPATIAL_SCALABLE_EXTENSION_ID = 0x9,
PICTURE_TEMPORAL_SCALABLE_EXTENSION_ID = 0xA,
/* 0xB ... 0xF reserved */
};
enum picture_coding_type {
/* 0 forbidden */
I_TYPE = 1,
P_TYPE = 2,
B_TYPE = 3,
D_TYPE = 4,
/* 5 ... 7 reserved */
};
enum picture_structure {
/* 0 reserved */
TOP_FIELD = 1,
BOTTOM_FIELD = 2,
FRAME_PICTURE = 3
};
/* PTSs and DTSs are 33 bits wide. */
#define TIMESTAMP_MASK (((int64_t) 1 << 33) - 1)
struct packet {
/* Offset in bytes from the buffer start. */
unsigned int offset;
/* Packet and payload size in bytes. */
unsigned int size;
unsigned int payload;
/* Decoding and presentation time stamp and display duration
of this packet. */
int64_t dts;
int64_t pts;
int64_t duration;
/* Cut the stream at this packet. */
vbi_bool splice;
/* Data is missing before this packet but the producer will
correct that later. */
vbi_bool data_lost;
};
struct buffer {
uint8_t * base;
/* Capacity of the buffer in bytes. */
unsigned int capacity;
/* Offset in bytes from base where the next data will be
stored. */
unsigned int in;
/* Offset in bytes from base where the next data will be
removed. */
unsigned int out;
};
#define MAX_PACKETS 64
struct pes_buffer {
uint8_t * base;
/* Capacity of the buffer in bytes. */
unsigned int capacity;
/* Offset in bytes from base where the next data will be stored. */
unsigned int in;
/* Information about the packets in the buffer.
packet[0].offset is the offset in bytes from base
where the next data will be removed. */
struct packet packet[MAX_PACKETS];
/* Number of packets in the packet[] array. packet[0] must not
be removed unless n_packets >= 2. */
unsigned int n_packets;
};
struct pes_multiplexer {
vbi_bool new_file;
unsigned int b_state;
time_t minicut_end;
FILE * minicut_fp;
};
struct audio_es_packetizer {
/* Test tap. */
const char * option_audio_es_tap_file_name;
FILE * audio_es_tap_fp;
struct buffer ac3_buffer;
struct pes_buffer pes_buffer;
/* Number of bytes we want to examine at pes_buffer.in. */
unsigned int need;
/* Estimated PTS of the next/current AC3 frame (pts or
pes_packet_pts plus previous frame duration), -1 if no PTS
was received yet. */
int64_t pts;
/* Next/current AC3 frame is the first received frame. */
vbi_bool first_frame;
/* Data may have been lost between the previous and
next/current AC3 frame. */
vbi_bool data_lost;
uint64_t pes_audio_bit_rate;
};
struct video_es_packetizer {
struct pes_buffer pes_buffer;
int sequence_header_offset;
unsigned int packet_filled;
uint8_t pes_packet_header_6;
uint64_t pes_video_bit_rate;
vbi_bool aligned;
};
struct audio_pes_decoder {
struct buffer buffer;
unsigned int need;
unsigned int look_ahead;
};
struct video_recorder {
struct audio_pes_decoder apesd;
struct video_es_packetizer vesp;
struct audio_es_packetizer aesp;
struct pes_multiplexer pm;
/* TS recorder */
unsigned int pat_cc;
unsigned int pmt_cc;
time_t minicut_end;
FILE * minicut_fp;
};
enum received_blocks {
RECEIVED_PES_PACKET = (1 << 0),
RECEIVED_PICTURE = (1 << 1),
RECEIVED_PICTURE_EXT = (1 << 2),
RECEIVED_MPEG_CC_DATA = (1 << 3)
};
struct video_es_decoder {
/* Test tap. */
const char * option_video_es_all_tap_file_name;
const char * option_video_es_tap_file_name;
FILE * video_es_tap_fp;
/* Video elementary stream buffer. */
struct buffer buffer;
unsigned int min_bytes_valid;
/* Number of bytes after buffer.out which have already
been scanned for a start code prefix. */
unsigned int skip;
/* Last received start code, < 0 if none. If valid
buffer.out points at the first start code prefix
byte. */
enum start_code last_start_code;
/* The decoding and presentation time stamp of the
current picture. Only the lowest 33 bits are
valid. < 0 if no PTS or DTS was received or the PES
packet header was malformed. */
int64_t pts;
int64_t dts;
/* For debugging. */
uint64_t n_pictures_received;
/* Parameters of the current picture. */
enum picture_coding_type picture_coding_type;
enum picture_structure picture_structure;
unsigned int picture_temporal_reference;
/* Set of the data blocks we received so far. */
enum received_blocks received_blocks;
/* Describes the contents of the reorder_buffer[]:
Bit 0 - a top field in reorder_buffer[0],
Bit 1 - a bottom field in reorder_buffer[1],
Bit 2 - a frame in reorder_buffer[0]. Only the
combinations 0, 1, 2, 3, 4 are valid. */
unsigned int reorder_pictures;
/* The PTS (as above) of the data in the reorder_buffer. */
int64_t reorder_pts[2];
unsigned int reorder_n_bytes[2];
/* Buffer to convert picture user data from coded
order to display order, for the top and bottom
field. Maximum size required: 11 + cc_count * 3,
where cc_count = 0 ... 31. */
uint8_t reorder_buffer[2][128];
};
struct ts_decoder {
/* TS PID of the video [0] and audio [1] stream. */
unsigned int pid[2];
/* Next expected video and audio TS packet continuity
counter. Only the lowest four bits are valid. < 0
if no continuity counter has been received yet. */
int next_ts_cc[2];
/* One or more TS packets were lost. */
vbi_bool data_lost;
} tsd;
struct program {
const char * option_station_name;
/* Convert the captured video and audio data to PES format,
cut the stream into one minute long fragments and save them
with file name
<option_minicut_dir_name>/yyyymmddhh0000/yyyymmddhhmm00.mpg
One minute is measured in real time, yyyymmddhhmm is the
system time in the UTC zone when the data was received and
decoded. */
const char * option_minicut_dir_name;
struct timeval now;
int64_t first_dts;
struct ts_decoder tsd;
struct video_es_decoder vesd;
struct video_recorder vr;
struct caption_recorder cr;
};
struct station {
struct station * next;
char * name;
enum fe_type type;
unsigned long frequency;
unsigned int video_pid;
unsigned int audio_pid;
union {
struct {
enum fe_modulation modulation;
} atsc;
struct {
enum fe_spectral_inversion inversion;
enum fe_bandwidth bandwidth;
enum fe_code_rate code_rate_HP;
enum fe_code_rate code_rate_LP;
enum fe_modulation constellation;
enum fe_transmit_mode transm_mode;
enum fe_guard_interval guard_interval;
enum fe_hierarchy hierarchy;
} dvb_t;
} u;
};
enum debug {
DEBUG_VESD_START_CODE = (1 << 0),
DEBUG_VESD_PES_PACKET = (1 << 1),
DEBUG_VESD_CC_DATA = (1 << 2),
DEBUG_CC_DATA = (1 << 3),
DEBUG_CC_F1 = (1 << 4),
DEBUG_CC_F2 = (1 << 5),
DEBUG_CC_DECODER = (1 << 6),
DEBUG_DTVCC_PACKET = (1 << 7),
DEBUG_DTVCC_SE = (1 << 8),
DEBUG_DTVCC_PUT_CHAR = (1 << 9),
DEBUG_DTVCC_STREAM_EVENT = (1 << 10),
DEBUG_CONFIG = (1 << 11)
};
enum source {
SOURCE_DVB_DEVICE = 1,
SOURCE_STDIN_TS,
/* Not implemented yet. */
SOURCE_STDIN_PES,
/* For tests only. */
SOURCE_STDIN_VIDEO_ES,
SOURCE_STDIN_CC_DATA
};
static const char * my_name;
static unsigned int option_verbosity;
static unsigned int option_debug;
/* Input. */
static enum source option_source;
/* DVB device. */
static enum fe_type option_dvb_type;
static unsigned long option_dvb_adapter_num;
static unsigned long option_dvb_frontend_id;
static unsigned long option_dvb_demux_id;
static unsigned long option_dvb_dvr_id;
static const char * option_channel_conf_file_name;
/* Test taps. */
const char * option_ts_all_tap_file_name;
const char * option_ts_tap_file_name;
static vbi_bool option_minicut_test;
static const char * locale_codeset;
/* DVB devices. */
static int fe_fd; /* frontend */
static int dvr_fd; /* data stream */
static int dmx_fd; /* demultiplexer */
/* Capture thread. */
static pthread_t capture_thread_id;
/* The read buffer of the capture thread. */
static uint8_t * ct_buffer;
static unsigned int ct_buffer_capacity;
/* For debugging. */
static uint64_t ct_n_bytes_in;
/* Transport stream decoder. */
/* static pthread_t demux_thread_id; */
/* Transport stream buffer. The capture thread stores TS packets at
ts_buffer_in, the TS decoder removes packets from ts_buffer_out,
and increments the respective pointer by the number of bytes
transferred. The input and output pointers jump back to the start
of the buffer before they would exceed ts_buffer_capacity (wraps at
packet, not byte granularity). The buffer is empty if ts_buffer_in
equals ts_buffer_out. */
static uint8_t * ts_buffer;
static unsigned int ts_buffer_capacity;
static volatile unsigned int ts_buffer_in;
static volatile unsigned int ts_buffer_out;
static uint8_t ts_error;
/* For debugging. */
static uint64_t ts_n_packets_in;
/* Test tap into the transport stream. */
static FILE * ts_tap_fp;
/* Notifies the TS decoder when data is available in the ts_buffer. */
static pthread_mutex_t dx_mutex;
static pthread_cond_t dx_cond;
/* If pid_map[].program < 0, no TS packet with PID n is needed and the
capture thread will drop the packet. Otherwise pid_map[].program is
an index into the programs[] table. This information is used by the
TS decoder to separate multiple video streams. */
static struct {
int8_t program;
} pid_map[0x2000];
/* The programs we want to record. */
static struct program program_table[12];
static unsigned int n_programs;
/* A list of stations found in the channel.conf file. */
static struct station * station_list;
/* Any station on the selected transponder. */
static struct station * station;
static void
list_stations (void);
static void
init_cc_decoder (struct cc_decoder * cd);
static void
init_dtvcc_decoder (struct dtvcc_decoder * dc);
static void
init_cc_data_decoder (struct cc_data_decoder *cd);
#define CASE(x) case x: return #x;
static const char *
picture_coding_type_name (enum picture_coding_type t)
{
switch (t) {
CASE (I_TYPE)
CASE (P_TYPE)
CASE (B_TYPE)
CASE (D_TYPE)
}
return "invalid";
}
static const char *
picture_structure_name (enum picture_structure t)
{
switch (t) {
CASE (TOP_FIELD)
CASE (BOTTOM_FIELD)
CASE (FRAME_PICTURE)
}
return "invalid";
}
static const char *
cc_type_name (enum cc_type t)
{
switch (t) {
CASE (NTSC_F1)
CASE (NTSC_F2)
CASE (DTVCC_DATA)
CASE (DTVCC_START)
}
return "invalid";
}
#undef CASE
static int
printable (int c)
{
if ((c & 0x7F) < 0x20)
return '.';
else
return c & 0x7F;
}
static void
dump (FILE * fp,
const uint8_t * buf,
unsigned int n_bytes)
_vbi_unused;
static void
dump (FILE * fp,
const uint8_t * buf,
unsigned int n_bytes)
{
const unsigned int width = 16;
unsigned int i;
for (i = 0; i < n_bytes; i += width) {
unsigned int end;
unsigned int j;
end = MIN (i + width, n_bytes);
for (j = i; j < end; ++j)
fprintf (fp, "%02x ", buf[j]);
for (; j < i + width; ++j)
fputs (" ", fp);
fputc (' ', fp);
for (j = i; j < end; ++j) {
int c = buf[j];
fputc (printable (c), fp);
}
fputc ('\n', fp);
}
}
#define log(verb, templ, args...) \
log_message (verb, /* print_errno */ FALSE, templ , ##args)
#define log_errno(verb, templ, args...) \
log_message (verb, /* print_errno */ TRUE, templ , ##args)
#define bug(templ, args...) \
log_message (1, /* print_errno */ FALSE, "BUG: " templ , ##args)
static void
log_message (unsigned int verbosity,
vbi_bool print_errno,
const char * templ,
...)
_vbi_format ((printf, 3, 4));
static void
log_message (unsigned int verbosity,
vbi_bool print_errno,
const char * templ,
...)
{
if (verbosity <= option_verbosity) {
va_list ap;
va_start (ap, templ);
fprintf (stderr, "%s: ", my_name);
vfprintf (stderr, templ, ap);
if (print_errno) {
fprintf (stderr, ": %s.\n",
strerror (errno));
}
va_end (ap);
}
}
#define error_exit(templ, args...) \
error_message_exit (/* print_errno */ FALSE, templ , ##args)
#define errno_exit(templ, args...) \
error_message_exit (/* print_errno */ TRUE, templ , ##args)
static void
error_message_exit (vbi_bool print_errno,
const char * templ,
...)
_vbi_format ((printf, 2, 3));
static void
error_message_exit (vbi_bool print_errno,
const char * templ,
...)
{
if (option_verbosity > 0) {
va_list ap;
va_start (ap, templ);
fprintf (stderr, "%s: ", my_name);
vfprintf (stderr, templ, ap);
if (print_errno) {
fprintf (stderr, ": %s.\n",
strerror (errno));
}
va_end (ap);
}
exit (EXIT_FAILURE);
}
static void
no_mem_exit (void)
{
error_exit ("Out of memory.");
}
static void *
xmalloc (size_t size)
{
void *p;
p = malloc (size);
if (NULL == p) {
no_mem_exit ();
}
return p;
}
static char *
xasprintf (const char * templ,
...)
_vbi_format ((printf, 1, 2));
static char *
xasprintf (const char * templ,
...)
{
va_list ap;
char *s;
int r;
va_start (ap, templ);
r = vasprintf (&s, templ, ap);
if (r < 0 || NULL == s) {
no_mem_exit ();
}
va_end (ap);
return s;
}
static int
xioctl_may_fail (int fd,
int request,
void * arg)
{
int r;
do r = ioctl (fd, request, arg);
while (-1 == r && EINTR == errno);
return r;
}
#define xioctl(fd, request, arg) \
do { \
int r; \
\
r = xioctl_may_fail (fd, request, arg); \
if (-1 == r) { \
errno_exit (#request " failed"); \
} \
} while (0)
static FILE *
open_output_file (const char * name)
{
FILE *fp;
if (NULL == name || 0 == strcmp (name, "-")) {
fp = stdout;
} else {
fp = fopen (name, "a");
if (NULL == fp) {
errno_exit ("Cannot open output file '%s'",
name);
}
}
return fp;
}
static FILE *
open_test_file (const char * name)
{
FILE *fp;
if (NULL == name || 0 == strcmp (name, "-")) {
fp = stdin;
} else {
fp = fopen (name, "r");
if (NULL == fp) {
errno_exit ("Cannot open test file '%s'",
name);
}
}
return fp;
}
static FILE *
open_minicut_file (struct program * pr,
const struct tm * tm,
const char * file_name,
const char * extension)
{
char dir_name[32];
size_t base_len;
size_t dir_len;
char *buf;
struct stat st;
unsigned int i;
FILE *fp;
base_len = strlen (pr->option_minicut_dir_name);
dir_len = snprintf (dir_name, sizeof (dir_name),
"/%04u%02u%02u%02u0000",
tm->tm_year + 1900,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_hour);
buf = xmalloc (base_len + dir_len
+ strlen (file_name) + 2
+ strlen (extension) + 1);
strcpy (buf, pr->option_minicut_dir_name);
if (0 != stat (buf, &st)) {
errno_exit ("Cannot open '%s'", buf);
} else if (!S_ISDIR(st.st_mode)) {
error_exit ("'%s' is not a directory.\n", buf);
}
strcpy (buf + base_len, dir_name);
if (0 != stat (buf, &st)) {
if (ENOENT != errno)
errno_exit ("Cannot open '%s'", buf);
if (-1 == mkdir (buf, /* mode */ 0777))
errno_exit ("Cannot create '%s'", buf);
} else if (!S_ISDIR(st.st_mode)) {
error_exit ("'%s' is not a directory.\n", buf);
}
for (i = 0; i < 100; ++i) {
int fd;
if (0 == i) {
sprintf (buf + base_len + dir_len,
"%s%s",
file_name, extension);
} else {
sprintf (buf + base_len + dir_len,
"%s-%u%s",
file_name, i, extension);
}
fd = open64 (buf, (O_CREAT | O_EXCL |
O_LARGEFILE | O_WRONLY), 0666);
if (fd >= 0) {
fp = fdopen (fd, "w");
if (NULL == fp)
goto failed;
log (2, "Opened '%s'.\n", buf);
free (buf);
return fp;
}
if (EEXIST == errno)
continue;
if (ENOSPC == errno)
break;
failed:
errno_exit ("Cannot open output file '%s'", buf);
}
free (buf);
/* Will try again later. */
log_errno (1, "Cannot open output file '%s'", buf);
return NULL;
}
static unsigned int
station_num (struct program * pr)
{
return (pr - program_table) + 1;
}
/* Caption recorder */
static const char *
cr_file_name_suffix [10] = {
"-cc1", "-cc2", "-cc3", "-cc4",
"-t1", "-t2", "-t3", "-t4",
"-s1", "-s2"
};
static void
cr_grow_buffer (struct caption_recorder *cr,
unsigned int n_chars)
{
uint16_t *new_buffer;
size_t min_size;
size_t new_size;
if (likely (cr->ucs_buffer_length + n_chars
<= cr->ucs_buffer_capacity))
return;
min_size = (cr->ucs_buffer_length + n_chars) * 2;
min_size = MAX ((size_t) 64, min_size);
new_size = MAX (min_size, (size_t) cr->ucs_buffer_capacity * 4);
new_buffer = realloc (cr->ucs_buffer, new_size);
if (NULL == new_buffer)
no_mem_exit ();
cr->ucs_buffer = new_buffer;
cr->ucs_buffer_capacity = new_size / 2;
}
static void
cr_putuc (struct caption_recorder *cr,
uint16_t uc)
{
cr_grow_buffer (cr, 1);
cr->ucs_buffer[cr->ucs_buffer_length++] = uc;
}
static void
cr_puts (struct caption_recorder *cr,
const char * s)
{
while (0 != *s)
cr_putuc (cr, *s++);
}
_vbi_inline void
vbi_char_copy_attr (struct vbi_char * cp1,
struct vbi_char * cp2,
unsigned int attr)
{
if (attr & VBI_UNDERLINE)
cp1->underline = cp2->underline;
if (attr & VBI_ITALIC)
cp1->italic = cp2->italic;
if (attr & VBI_FLASH)
cp1->flash = cp2->flash;
}
_vbi_inline void
vbi_char_clear_attr (struct vbi_char * cp,
unsigned int attr)
{
if (attr & VBI_UNDERLINE)
cp->underline = 0;
if (attr & VBI_ITALIC)
cp->italic = 0;
if (attr & VBI_FLASH)
cp->flash = 0;
}
_vbi_inline void
vbi_char_set_attr (struct vbi_char * cp,
unsigned int attr)
{
if (attr & VBI_UNDERLINE)
cp->underline = 1;
if (attr & VBI_ITALIC)
cp->italic = 1;
if (attr & VBI_FLASH)
cp->flash = 1;
}
_vbi_inline unsigned int
vbi_char_has_attr (struct vbi_char * cp,
unsigned int attr)
{
attr &= (VBI_UNDERLINE | VBI_ITALIC | VBI_FLASH);
if (0 == cp->underline)
attr &= ~VBI_UNDERLINE;
if (0 == cp->italic)
attr &= ~VBI_ITALIC;
if (0 == cp->flash)
attr &= ~VBI_FLASH;
return attr;
}
_vbi_inline unsigned int
vbi_char_xor_attr (struct vbi_char * cp1,
struct vbi_char * cp2,
unsigned int attr)
{
attr &= (VBI_UNDERLINE | VBI_ITALIC | VBI_FLASH);
if (0 == (cp1->underline ^ cp2->underline))
attr &= ~VBI_UNDERLINE;
if (0 == (cp1->italic ^ cp2->italic))
attr &= ~VBI_ITALIC;
if (0 == (cp1->flash ^ cp2->flash))
attr &= ~VBI_FLASH;
return attr;
}
static vbi_bool
cr_put_attr (struct caption_recorder *cr,
vbi_char * prev,
vbi_char curr)
{
uint16_t *d;
switch (cr->option_caption_format) {
case FORMAT_PLAIN:
return TRUE;
case FORMAT_NTSC_CC:
/* Same output as the [zvbi-]ntsc-cc app. */
curr.opacity = VBI_OPAQUE;
/* Use the default foreground and background color of
the terminal. */
curr.foreground = -1;
curr.background = -1;
if (vbi_char_has_attr (&curr, VBI_ITALIC))
curr.foreground = VBI_CYAN;
vbi_char_clear_attr (&curr, VBI_ITALIC | VBI_FLASH);
break;
case FORMAT_VT100:
break;
}
cr_grow_buffer (cr, 32);
d = cr->ucs_buffer + cr->ucs_buffer_length;
/* Control sequences based on ECMA-48,
http://www.ecma-international.org/ */
/* SGR sequence */
d[0] = 27; /* CSI */
d[1] = '[';
d += 2;
switch (curr.opacity) {
case VBI_TRANSPARENT_SPACE:
vbi_char_clear_attr (&curr, -1);
curr.foreground = -1;
curr.background = -1;
break;
case VBI_TRANSPARENT_FULL:
curr.background = -1;
break;
case VBI_SEMI_TRANSPARENT:
case VBI_OPAQUE:
break;
}
if ((prev->foreground != curr.foreground
&& (uint8_t) -1 == curr.foreground)
|| (prev->background != curr.background
&& (uint8_t) -1 == curr.background)) {
*d++ = ';'; /* "[0m;" reset */
vbi_char_clear_attr (prev, -1);
prev->foreground = -1;
prev->background = -1;
}
if (vbi_char_xor_attr (prev, &curr, VBI_ITALIC)) {
if (!vbi_char_has_attr (&curr, VBI_ITALIC))
*d++ = '2'; /* off */
d[0] = '3'; /* italic */
d[1] = ';';
d += 2;
}
if (vbi_char_xor_attr (prev, &curr, VBI_UNDERLINE)) {
if (!vbi_char_has_attr (&curr, VBI_UNDERLINE))
*d++ = '2'; /* off */
d[0] = '4'; /* underline */
d[1] = ';';
d += 2;
}
if (vbi_char_xor_attr (prev, &curr, VBI_FLASH)) {
if (!vbi_char_has_attr (&curr, VBI_FLASH))
*d++ = '2'; /* steady */
d[0] = '5'; /* slowly blinking */
d[1] = ';';
d += 2;
}
if (prev->foreground != curr.foreground) {
d[0] = '3';
d[1] = curr.foreground + '0';
d[2] = ';';
d += 3;
}
if (prev->background != curr.background) {
d[0] = '4';
d[1] = curr.background + '0';
d[2] = ';';
d += 3;
}
vbi_char_copy_attr (prev, &curr, -1);
prev->foreground = curr.foreground;
prev->background = curr.background;
if ('[' == d[-1])
d -= 2; /* no change, remove CSI */
else
d[-1] = 'm'; /* replace last semicolon */
cr->ucs_buffer_length = d - cr->ucs_buffer;
return TRUE;
}
static void
cr_timestamp (struct caption_recorder *cr,
struct tm * tm,
time_t t)
{
char time_str[32];
if (!cr->option_caption_timestamps)
return;
if (tm->tm_mday <= 0) {
if (NULL == gmtime_r (&t, tm)) {
/* Should not happen. */
error_exit ("System time invalid.\n");
}
}
snprintf (time_str, sizeof (time_str),
"%04u%02u%02u%02u%02u%02u|",
tm->tm_year + 1900, tm->tm_mon + 1,
tm->tm_mday, tm->tm_hour,
tm->tm_min, tm->tm_sec);
cr_puts (cr, time_str);
}
static void
cr_minicut (struct caption_recorder *cr,
struct tm * tm,
time_t t,
vbi_pgno channel)
{
struct program *pr = PARENT (cr, struct program, cr);
if (NULL == pr->option_minicut_dir_name)
return;
if (NULL != cr->option_caption_file_name[channel - 1])
return;
if (tm->tm_mday <= 0) {
if (NULL == gmtime_r (&t, tm)) {
/* Should not happen. */
error_exit ("System time invalid.\n");
}
}
if (tm->tm_min != cr->minicut_min[channel - 1]) {
char file_name[32];
FILE *fp;
fp = cr->caption_fp[channel - 1];
if (NULL != fp) {
if (0 != fclose (fp)) {
log_errno (1, "Station %u CC file "
"write error",
station_num (pr));
}
}
snprintf (file_name, sizeof (file_name),
"/%04u%02u%02u%02u%02u00%s",
tm->tm_year + 1900, tm->tm_mon + 1,
tm->tm_mday, tm->tm_hour, tm->tm_min,
cr_file_name_suffix [channel - 1]);
/* Note: May be NULL. */
cr->caption_fp[channel - 1] =
open_minicut_file (pr, tm, file_name, ".txt");
cr->minicut_min[channel - 1] = tm->tm_min;
}
}
static void
cr_new_line (struct caption_recorder *cr,
struct cc_timestamp * ts,
vbi_pgno channel,
const vbi_char text[42],
unsigned int length)
{
FILE *fp;
if (0 == (ts->sys.tv_sec | ts->sys.tv_usec)
|| channel >= 10 || length < 32)
return;
if (0 == (cr->option_caption_mask & (1 << (channel - 1))))
return;
cr->ucs_buffer_length = 0;
if (cr->usesen) {
unsigned int uc[3];
unsigned int column;
unsigned int end;
unsigned int separator;
end = length;
/* Eat trailing spaces. */
while (end > 0 && ' ' == text[end - 1].unicode)
--end;
uc[2] = ' ';
separator = 0;
for (column = 0; column < end + 1; ++column) {
uc[0] = uc[1];
uc[1] = uc[2];
uc[2] = 0;
if (column < end)
uc[2] = text[column].unicode;
if (0 == separator && ' ' == uc[1])
continue;
cr_putuc (cr, uc[1]);
separator = ' ';
switch (uc[1]) {
case '"':
if ('.' != uc[0] && '!' != uc[0]
&& '?' != uc[0])
continue;
break;
case '.':
if ('.' == uc[0] || '.' == uc[2])
continue;
/* fall through */
case '!':
case '?':
if ('"' == uc[2])
continue;
if (0 != uc[2] && ' ' != uc[2])
continue;
break;
default:
continue;
}
cr_putuc (cr, '\n');
separator = 0;
}
if (0 != separator)
cr_putuc (cr, separator);
} else {
struct tm tm;
vbi_char prev_char;
unsigned int column;
tm.tm_mday = 0;
cr_minicut (cr, &tm, (time_t) ts->sys.tv_sec, channel);
cr_timestamp (cr, &tm, (time_t) ts->sys.tv_sec);
vbi_char_clear_attr (&prev_char, -1);
prev_char.foreground = -1;
prev_char.background = -1;
for (column = 0; column < length; ++column) {
cr_put_attr (cr, &prev_char, text[column]);
cr_putuc (cr, text[column].unicode);
}
if (0 != vbi_char_has_attr (&prev_char, -1)
|| (uint8_t) -1 != prev_char.foreground
|| (uint8_t) -1 != prev_char.background) {
static const char end_seq[] = {
27, '[', 'm', '\n', 0
};
cr_puts (cr, end_seq);
} else {
cr_putuc (cr, '\n');
}
}
fp = cr->caption_fp[channel - 1];
/* May be NULL due to a bug or a temporary failure of
open_minicut_file(). */
if (NULL != fp) {
vbi_fputs_iconv_ucs2 (fp, locale_codeset,
cr->ucs_buffer,
cr->ucs_buffer_length,
/* repl_char */ '?');
}
}
static void
init_caption_recorder (struct caption_recorder *cr)
{
CLEAR (*cr);
cr->option_xds_output_file_name = "-";
init_cc_data_decoder (&cr->ccd);
init_cc_decoder (&cr->cc);
init_dtvcc_decoder (&cr->dtvcc);
cr->infoptr = cr->newinfo[0][0][0];
cr->xds_info_prefix = "\33[33m% ";
cr->xds_info_suffix = "\33[0m\n";
cr->usewebtv = 1;
memset (cr->minicut_min, -1, sizeof (cr->minicut_min));
}
/* EIA 608-B Closed Caption decoder. */
static void
cc_timestamp_reset (struct cc_timestamp * ts)
{
CLEAR (ts->sys);
ts->pts = -1;
}
static vbi_bool
cc_timestamp_isset (struct cc_timestamp * ts)
{
return (ts->pts >= 0 || 0 != (ts->sys.tv_sec | ts->sys.tv_usec));
}
static const vbi_color
cc_color_map [8] = {
VBI_WHITE, VBI_GREEN, VBI_BLUE, VBI_CYAN,
VBI_RED, VBI_YELLOW, VBI_MAGENTA, VBI_BLACK
};
static const int8_t
cc_pac_row_map [16] = {
/* 0 */ 10, /* 0x1040 */
/* 1 */ -1, /* no function */
/* 2 */ 0, 1, 2, 3, /* 0x1140 ... 0x1260 */
/* 6 */ 11, 12, 13, 14, /* 0x1340 ... 0x1460 */
/* 10 */ 4, 5, 6, 7, 8, 9 /* 0x1540 ... 0x1760 */
};
static void
dump_cc (FILE * fp,
unsigned int index,
unsigned int cc_count,
unsigned int cc_valid,
unsigned int cc_type,
unsigned int c1,
unsigned int c2)
{
uint16_t ucs2_str[2];
unsigned int ch;
unsigned int a7;
unsigned int f;
unsigned int b7;
unsigned int u;
fprintf (fp,
"%s%u/%u %d %s %02X%02X %02X%c%02X%c",
(NTSC_F2 == cc_type) ? "\t\t\t\t\t\t\t\t" : "",
index, cc_count, !!cc_valid,
cc_type_name (cc_type), c1, c2,
c1 & 0x7F, vbi_unpar8 (c1) < 0 ? '*' : ' ',
c2 & 0x7F, vbi_unpar8 (c2) < 0 ? '*' : ' ');
c1 &= 0x7F;
c2 &= 0x7F;
if (0 == c1) {
fputs (" null\n", fp);
return;
} else if (c1 < 0x10) {
fputc ('\n', fp);
return;
} else if (c1 >= 0x20) {
fputs (" '", fp);
ucs2_str[0] = vbi_caption_unicode (c1, /* to_upper */ FALSE);
ucs2_str[1] = vbi_caption_unicode (c2, /* to_upper */ FALSE);
vbi_fputs_iconv_ucs2 (fp, locale_codeset,
ucs2_str, 2,
/* repl_char */ '?');
fputs ("'\n", fp);
return;
} else if (c2 < 0x20) {
fputs (" INVALID\n", fp);
return;
}
/* Some common bit groups. */
ch = (c1 >> 3) & 1; /* channel */
a7 = c1 & 7;
f = c1 & 1; /* field */
b7 = (c2 >> 1) & 7;
u = c2 & 1; /* underline */
if (c2 >= 0x40) {
unsigned int row;
/* Preamble Address Codes -- 001 crrr 1ri bbbu */
row = cc_pac_row_map [a7 * 2 + ((c2 >> 5) & 1)];
if (c2 & 0x10)
fprintf (fp, " PAC ch=%u row=%d column=%u u=%u\n",
ch, row, b7 * 4, u);
else
fprintf (fp, " PAC ch=%u row=%d color=%u u=%u\n",
ch, row, b7, u);
return;
}
/* Control codes -- 001 caaa 01x bbbu */
switch (a7) {
case 0:
if (c2 < 0x30) {
static const char mnemo [16 * 4] =
"BWO\0BWS\0BGO\0BGS\0"
"BBO\0BBS\0BCO\0BCS\0"
"BRO\0BRS\0BYO\0BYS\0"
"BMO\0BMS\0BAO\0BAS";
fprintf (fp, " %s ch=%u\n",
mnemo + (c2 & 0xF) * 4, ch);
return;
}
break;
case 1:
if (c2 < 0x30) {
fprintf (fp, " mid-row ch=%u color=%u u=%u\n",
ch, b7, u);
} else {
fprintf (fp, " special character ch=%u '", ch);
ucs2_str[0] = vbi_caption_unicode
(0x1100 | c2, /* to_upper */ FALSE);
vbi_fputs_iconv_ucs2 (fp, locale_codeset,
ucs2_str, 1,
/* repl_char */ '?');
fputs ("'\n", fp);
}
return;
case 2: /* first group */
case 3: /* second group */
fprintf (fp, " extended character ch=%u '", ch);
ucs2_str[0] = vbi_caption_unicode
(c1 * 256 + c2, /* to_upper */ FALSE);
vbi_fputs_iconv_ucs2 (fp, locale_codeset,
ucs2_str, 1,
/* repl_char */ '?');
fputs ("'\n", fp);
return;
case 4: /* f=0 */
case 5: /* f=1 */
if (c2 < 0x30) {
static const char mnemo [16 * 4] =
"RCL\0BS \0AOF\0AON\0"
"DER\0RU2\0RU3\0RU4\0"
"FON\0RDC\0TR \0RTD\0"
"EDM\0CR \0ENM\0EOC";
fprintf (fp, " %s ch=%u f=%u\n",
mnemo + (c2 & 0xF) * 4, ch, f);
return;
}
break;
case 6:
fprintf (fp, " reserved\n");
return;
case 7:
switch (c2) {
case 0x21 ... 0x23:
fprintf (fp, " TO%u ch=%u\n", c2 - 0x20, ch);
return;
case 0x2D:
fprintf (fp, " BT ch=%u\n", ch);
return;
case 0x2E:
fprintf (fp, " FA ch=%u\n", ch);
return;
case 0x2F:
fprintf (fp, " FAU ch=%u\n", ch);
return;
default:
break;
}
break;
}
fprintf (fp, " unknown\n");
}
static void
cc_reset (struct cc_decoder * cd);
static vbi_pgno
cc_channel_num (struct cc_decoder * cd,
struct cc_channel * ch)
{
return (ch - cd->channel) + 1;
}
/* Note 47 CFR 15.119 (h) Character Attributes: "(1) Transmission of
Attributes. A character may be transmitted with any or all of four
attributes: Color, italics, underline, and flash. All of these
attributes are set by control codes included in the received
data. An attribute will remain in effect until changed by another
control code or until the end of the row is reached. Each row
begins with a control code which sets the color and underline
attributes. (White non-underlined is the default display attribute
if no Preamble Address Code is received before the first character
on an empty row.) Attributes are not affected by transparent spaces
within a row. (i) All Mid-Row Codes and the Flash On command are
spacing attributes which appear in the display just as if a
standard space (20h) had been received. Preamble Address Codes are
non-spacing and will not alter any attributes when used to position
the cursor in the midst of a row of characters. (ii) The color
attribute has the highest priority and can only be changed by the
Mid-Row Code of another color. Italics has the next highest
priority. If characters with both color and italics are desired,
the italics Mid-Row Code must follow the color assignment. Any
color Mid-Row Code will turn off italics. If the least significant
bit of a Preamble Address Code or of a color or italics Mid-Row
Code is a 1 (high), underlining is turned on. If that bit is a 0
(low), underlining is off. (iii) The flash attribute is transmitted
as a Miscellaneous Control Code. The Flash On command will not
alter the status of the color, italics, or underline
attributes. However, any coloror italics Mid-Row Code will turn off
flash. (iv) Thus, for example, if a red, italicized, underlined,
flashing character is desired, the attributes must be received in
the following order: a red Mid-Row or Preamble Address Code, an
italics Mid-Row Code with underline bit, and the Flash On
command. The character will then be preceded by three spaces (two
if red was assigned via a Preamble Address Code)."
EIA 608-B Annex C.7 Preamble Address Codes and Tab Offsets
(Regulatory/Preferred): "In general, Preamble Address Codes (PACs)
have no immediate effect on the display. A major exception is the
receipt of a PAC during roll-up captioning. In that case, if the
base row designated in the PAC is not the same as the current base
row, the display shall be moved immediately to the new base
row. [...]
cc_channel_num (struct cc_decoder * cd,
struct cc_channel * ch)
indenting PAC carries the attributes of white,
non-italicized, and it sets underlining on or off. Tab Offset
commands do not change these attributes. If an indenting PAC with
underline ON is received followed by a Tab Offset and by text, the
text shall be underlined (except as noted below). When a
displayable character is received, it is deposited at the current
cursor position. If there is already a displayable character in the
column immediately to the left, the new character assumes the
attributes of that character. The new character may be arriving as
the result of an indenting PAC (with or without a Tab Offset), and
that PAC may designate other attributes, but the new character is
forced to assume the attributes of the character immediately to its
left, and the PAC's attributes are ignored. If, when a displayable
character is received, it overwrites an existing PAC or mid-row
code, and there are already characters to the right of the new
character, these existing characters shall assume the same
attributes as the new character. This adoption can result in a
whole caption row suddenly changing color, underline, italics,
and/or flash attributes."
EIA 608-B Annex C.14 Special Cases Regarding Attributes
(Normative): "In most cases, Preamble Address Codes shall set
attributes for the caption elements they address. It is
theoretically possible for a service provider to use an indenting
PAC to start a row at Column 5 or greater, and then to use
Backspace to move the cursor to the left of the PAC into an area to
which no attributes have been assigned. It is also possible for a
roll-up row, having been created by a Carriage Return, to receive
characters with no PAC used to set attributes. In these cases, and
in any other case where no explicit attributes have been assigned,
the display shall be white, non-underlined, non-italicized, and
non-flashing. In case new displayable characters are received
immediately after a Delete to End of Row (DER), the display
attributes of the first deleted character shall remain in effect if
there is a displayable character to the left of the cursor;
otherwise, the most recently received PAC shall set the display
attributes."
47 CFR 15.119 (n) Glossary of terms: "(6) Displayable character:
Any letter, number or symbol which is defined for on-screen
display, plus the 20h space. [...] (13) Special characters:
Displayable characters (except for "transparent space") [...]" */
static void
cc_format_row (struct cc_decoder * cd,
struct vbi_char * cp,
struct cc_channel * ch,
unsigned int buffer,
unsigned int row,
vbi_bool to_upper,
vbi_bool padding)
{
struct vbi_char ac;
unsigned int i;
cd = cd; /* unused */
/* 47 CFR 15.119 (h)(1). EIA 608-B Section 6.4. */
CLEAR (ac);
ac.foreground = VBI_WHITE;
ac.background = VBI_BLACK;
/* Shortcut. */
if (0 == (ch->dirty[buffer] & (1 << row))) {
vbi_char *end;
ac.unicode = 0x20;
ac.opacity = VBI_TRANSPARENT_SPACE;
end = cp + CC_MAX_COLUMNS;
if (padding)
end += 2;
while (cp < end)
*cp++ = ac;
return;
}
if (padding) {
ac.unicode = 0x20;
ac.opacity = VBI_TRANSPARENT_SPACE;
*cp++ = ac;
}
/* EIA 608-B Section 6.4. */
ac.opacity = VBI_OPAQUE;
for (i = CC_FIRST_COLUMN - 1; i <= CC_LAST_COLUMN; ++i) {
unsigned int color;
unsigned int c;
ac.unicode = 0x20;
c = ch->buffer[buffer][row][i];
if (0 == c) {
if (padding
&& VBI_TRANSPARENT_SPACE != cp[-1].opacity
&& 0x20 != cp[-1].unicode) {
/* Append a space with the same colors
and opacity (opaque or
transp. backgr.) as the text to the
left of it. */
*cp++ = ac;
/* We don't underline spaces, see
below. */
vbi_char_clear_attr (cp - 1, -1);
} else if (i > 0) {
*cp++ = ac;
cp[-1].opacity = VBI_TRANSPARENT_SPACE;
}
continue;
} else if (c < 0x1040) {
if (padding
&& VBI_TRANSPARENT_SPACE == cp[-1].opacity) {
/* Prepend a space with the same
colors and opacity (opaque or
transp. backgr.) as the text to the
right of it. */
cp[-1] = ac;
/* We don't underline spaces, see
below. */
vbi_char_clear_attr (cp - 1, -1);
}
if ((c >= 'a' && c <= 'z')
|| 0x7E == c /* n with tilde */) {
/* We do not force these characters to
upper case because the standard
character set includes upper case
versions of these characters and
lower case was probably
deliberately transmitted. */
ac.unicode = vbi_caption_unicode
(c, /* to_upper */ FALSE);
} else {
ac.unicode = vbi_caption_unicode
(c, to_upper);
}
} else if (c < 0x1120) {
/* Preamble Address Codes -- 001 crrr 1ri xxxu */
/* PAC is a non-spacing attribute and only
stored in the buffer at the addressed
column minus one if it replaces a
transparent space (EIA 608-B Annex C.7,
C.14). There's always a transparent space
to the left of the first column but we show
this zeroth column only if padding is
enabled. */
if (padding
&& VBI_TRANSPARENT_SPACE != cp[-1].opacity
&& 0x20 != cp[-1].unicode) {
/* See 0 == c. */
*cp++ = ac;
vbi_char_clear_attr (cp - 1, -1);
} else if (i > 0) {
*cp++ = ac;
cp[-1].opacity = VBI_TRANSPARENT_SPACE;
}
vbi_char_clear_attr (&ac, VBI_UNDERLINE | VBI_ITALIC);
if (c & 0x0001)
vbi_char_set_attr (&ac, VBI_UNDERLINE);
if (c & 0x0010) {
ac.foreground = VBI_WHITE;
} else {
color = (c >> 1) & 7;
if (7 == color) {
ac.foreground = VBI_WHITE;
vbi_char_set_attr (&ac, VBI_ITALIC);
} else {
ac.foreground = cc_color_map[color];
}
}
continue;
} else if (c < 0x1130) {
/* Mid-Row Codes -- 001 c001 010 xxxu */
/* 47 CFR 15.119 Mid-Row Codes table,
(h)(1)(ii), (h)(1)(iii). */
/* 47 CFR 15.119 (h)(1)(i), EIA 608-B Section
6.2: Mid-Row codes, FON, BT, FA and FAU are
set-at spacing attributes. */
vbi_char_clear_attr (&ac, -1);
if (c & 0x0001)
vbi_char_set_attr (&ac, VBI_UNDERLINE);
color = (c >> 1) & 7;
if (7 == color) {
vbi_char_set_attr (&ac, VBI_ITALIC);
} else {
ac.foreground = cc_color_map[color];
}
} else if (c < 0x1220) {
/* Special Characters -- 001 c001 011 xxxx */
/* 47 CFR 15.119 Character Set Table. */
if (padding
&& VBI_TRANSPARENT_SPACE == cp[-1].opacity) {
cp[-1] = ac;
vbi_char_clear_attr (cp - 1, -1);
}
/* Note we already stored 0 instead of 0x1139
(transparent space) in the ch->buffer. */
ac.unicode = vbi_caption_unicode (c, to_upper);
} else if (c < 0x1428) {
/* Extended Character Set -- 001 c01x 01x xxxx */
/* EIA 608-B Section 6.4.2 */
if (padding
&& VBI_TRANSPARENT_SPACE == cp[-1].opacity) {
cp[-1] = ac;
vbi_char_clear_attr (cp - 1, -1);
}
/* We do not force these characters to upper
case because the extended character set
includes upper case versions of all letters
and lower case was probably deliberately
transmitted. */
ac.unicode = vbi_caption_unicode
(c, /* to_upper */ FALSE);
} else if (c < 0x172D) {
/* FON Flash On -- 001 c10f 010 1000 */
/* 47 CFR 15.119 (h)(1)(iii). */
vbi_char_set_attr (&ac, VBI_FLASH);
} else if (c < 0x172E) {
/* BT Background Transparent -- 001 c111 010 1101 */
/* EIA 608-B Section 6.4. */
ac.opacity = VBI_TRANSPARENT_FULL;
} else if (c <= 0x172F) {
/* FA Foreground Black -- 001 c111 010 111u */
/* EIA 608-B Section 6.4. */
if (c & 0x0001)
vbi_char_set_attr (&ac, VBI_UNDERLINE);
ac.foreground = VBI_BLACK;
}
*cp++ = ac;
/* 47 CFR 15.119 and EIA 608-B are silent about
underlined spaces, but considering the example in
47 CFR (h)(1)(iv) which would produce something
ugly like "__text" I suppose we should not
underline them. For good measure we also clear the
invisible italic and flash attribute. */
if (0x20 == ac.unicode)
vbi_char_clear_attr (cp - 1, -1);
}
if (padding) {
ac.unicode = 0x20;
vbi_char_clear_attr (&ac, -1);
if (VBI_TRANSPARENT_SPACE != cp[-1].opacity
&& 0x20 != cp[-1].unicode) {
*cp = ac;
} else {
ac.opacity = VBI_TRANSPARENT_SPACE;
*cp = ac;
}
}
}
typedef enum {
/**
* 47 CFR Section 15.119 requires caption decoders to roll
* caption smoothly: Nominally each character cell has a
* height of 13 field lines. When this flag is set the current
* caption should be displayed with a vertical offset of 12
* field lines, and after every 1001 / 30000 seconds the
* caption overlay should move up by one field line until the
* offset is zero. The roll rate should be no more than 0.433
* seconds/row for other character cell heights.
*
* The flag may be set again before the offset returned to
* zero. The caption overlay should jump to offset 12 in this
* case regardless.
*/
VBI_START_ROLLING = (1 << 0)
} vbi_cc_page_flags;
static void
cc_display_event (struct cc_decoder * cd,
struct cc_channel * ch,
vbi_cc_page_flags flags)
{
cd = cd; /* unused */
ch = ch;
flags = flags;
}
/* This decoder is mainly designed to overlay caption onto live video,
but to create transcripts we also offer an event every time a line
of caption is complete. The event occurs when certain control codes
are received.
In POP_ON mode we send the event upon reception of EOC, which swaps
the displayed and non-displayed memory.
In ROLL_UP and TEXT mode captioners are not expected to display new
text by erasing and overwriting a row with PAC, TOx, BS and DER so
we ignore these codes. In ROLL_UP mode CR, EDM, EOC, RCL and RDC
complete a line. CR moves the cursor to a new row, EDM erases the
displayed memory. The remaining codes switch to POP_ON or PAINT_ON
mode. In TEXT mode CR and TR are our line completion indicators. CR
works as above and TR erases the displayed memory. EDM, EOC, RDC,
RCL and RUx have no effect on TEXT buffers.
In PAINT_ON mode RDC never erases the displayed memory and CR has
no function. Instead captioners can freely position the cursor and
erase or overwrite (parts of) rows with PAC, TOx, BS and DER, or
erase all rows with EDM. We send an event on PAC, EDM, EOC, RCL and
RUx, provided the characters (including spacing attributes) in the
current row changed since the last event. PAC is the only control
code which can move the cursor to the left and/or to a new row, and
likely to introduce a new line. EOC, RCL and RUx switch to POP_ON
or ROLL_UP mode. */
static void
cc_stream_event (struct cc_decoder * cd,
struct cc_channel * ch,
unsigned int first_row,
unsigned int last_row)
{
vbi_pgno channel;
unsigned int row;
channel = cc_channel_num (cd, ch);
for (row = first_row; row <= last_row; ++row) {
struct vbi_char text[36];
unsigned int end;
cc_format_row (cd, text, ch,
ch->displayed_buffer,
row, /* to_upper */ FALSE,
/* padding */ FALSE);
for (end = 32; end > 0; --end) {
if (VBI_TRANSPARENT_SPACE != text[end - 1].opacity)
break;
}
if (0 == end)
continue;
{
struct program *pr;
pr = PARENT (cd, struct program, cr.cc);
cr_new_line (&pr->cr, &ch->timestamp_c0,
channel, text, /* length */ 32);
}
}
cc_timestamp_reset (&ch->timestamp_c0);
}
static void
cc_put_char (struct cc_decoder * cd,
struct cc_channel * ch,
int c,
vbi_bool displayable,
vbi_bool backspace)
{
uint16_t *text;
unsigned int curr_buffer;
unsigned int row;
unsigned int column;
/* 47 CFR Section 15.119 (f)(1), (f)(2), (f)(3). */
curr_buffer = ch->displayed_buffer
^ (CC_MODE_POP_ON == ch->mode);
row = ch->curr_row;
column = ch->curr_column;
if (unlikely (backspace)) {
/* 47 CFR 15.119 (f)(1)(vi), (f)(2)(ii),
(f)(3)(i). EIA 608-B Section 6.4.2, 7.4. */
if (column > CC_FIRST_COLUMN)
--column;
} else {
/* 47 CFR 15.119 (f)(1)(v), (f)(1)(vi), (f)(2)(ii),
(f)(3)(i). EIA 608-B Section 7.4. */
if (column < CC_LAST_COLUMN)
ch->curr_column = column + 1;
}
text = &ch->buffer[curr_buffer][row][0];
text[column] = c;
/* Send a display update event when the displayed buffer of
the current channel changed, but no more than once for each
pair of Closed Caption bytes. */
/* XXX This may not be a visible change, but such cases are
rare and we'd need something close to format_row() to be
sure. */
if (CC_MODE_POP_ON != ch->mode) {
cd->event_pending = ch;
}
if (likely (displayable)) {
/* Note EIA 608-B Annex C.7, C.14. */
if (CC_FIRST_COLUMN == column
|| 0 == text[column - 1]) {
/* Note last_pac may be 0 as well. */
text[column - 1] = ch->last_pac;
}
if (c >= 'a' && c <= 'z') {
ch->uppercase_predictor = 0;
} else if (c >= 'A' && c <= 'Z') {
unsigned int up;
up = ch->uppercase_predictor + 1;
if (up > 0)
ch->uppercase_predictor = up;
}
} else if (unlikely (0 == c)) {
unsigned int i;
/* This is Special Character "Transparent space". */
for (i = CC_FIRST_COLUMN; i <= CC_LAST_COLUMN; ++i)
c |= ch->buffer[curr_buffer][row][i];
ch->dirty[curr_buffer] &= ~((0 == c) << row);
return;
}
assert (sizeof (ch->dirty[0]) * 8 - 1 >= CC_MAX_ROWS);
ch->dirty[curr_buffer] |= 1 << row;
if (ch->timestamp_c0.pts < 0
&& 0 == (ch->timestamp_c0.sys.tv_sec
| ch->timestamp_c0.sys.tv_usec)) {
ch->timestamp_c0 = cd->timestamp;
}
}
static void
cc_ext_control_code (struct cc_decoder * cd,
struct cc_channel * ch,
unsigned int c2)
{
unsigned int column;
switch (c2) {
case 0x21: /* TO1 */
case 0x22: /* TO2 */
case 0x23: /* TO3 Tab Offset -- 001 c111 010 00xx */
/* 47 CFR 15.119 (e)(1)(ii). EIA 608-B Section 7.4,
Annex C.7. */
column = ch->curr_column + (c2 & 3);
ch->curr_column = MIN (column,
(unsigned int) CC_LAST_COLUMN);
break;
case 0x24: /* Select standard character set in normal size */
case 0x25: /* Select standard character set in double size */
case 0x26: /* Select first private character set */
case 0x27: /* Select second private character set */
case 0x28: /* Select character set GB 2312-80 (Chinese) */
case 0x29: /* Select character set KSC 5601-1987 (Korean) */
case 0x2A: /* Select first registered character set. */
/* EIA 608-B Section 6.3 Closed Group Extensions. */
break;
case 0x2D: /* BT Background Transparent -- 001 c111 010 1101 */
case 0x2E: /* FA Foreground Black -- 001 c111 010 1110 */
case 0x2F: /* FAU Foregr. Black Underl. -- 001 c111 010 1111 */
/* EIA 608-B Section 6.2. */
cc_put_char (cd, ch, 0x1700 | c2,
/* displayable */ FALSE,
/* backspace */ TRUE);
break;
default:
/* 47 CFR Section 15.119 (j): Ignore. */
break;
}
}
/* Send a stream event if the current row has changed since the last
stream event. This is necessary in paint-on mode where CR has no
function and captioners can freely position the cursor to erase or
overwrite (parts of) rows. */
static void
cc_stream_event_if_changed (struct cc_decoder * cd,
struct cc_channel * ch)
{
unsigned int curr_buffer;
unsigned int row;
unsigned int i;
curr_buffer = ch->displayed_buffer;
row = ch->curr_row;
if (0 == (ch->dirty[curr_buffer] & (1 << row)))
return;
for (i = CC_FIRST_COLUMN; i <= CC_LAST_COLUMN; ++i) {
unsigned int c1;
unsigned int c2;
c1 = ch->buffer[curr_buffer][row][i];
if (c1 >= 0x1040) {
if (c1 < 0x1120) {
c1 = 0; /* PAC -- non-spacing */
} else if (c1 < 0x1130 || c1 >= 0x1428) {
/* MR, FON, BT, FA, FAU -- spacing */
c1 = 0x20;
}
}
c2 = ch->buffer[2][row][i];
if (c2 >= 0x1040) {
if (c2 < 0x1120) {
c2 = 0;
} else if (c2 < 0x1130 || c2 >= 0x1428) {
c1 = 0x20;
}
}
if (c1 != c2) {
cc_stream_event (cd, ch, row, row);
memcpy (ch->buffer[2][row],
ch->buffer[curr_buffer][row],
sizeof (ch->buffer[0][0]));
ch->dirty[2] = ch->dirty[curr_buffer];
return;
}
}
}
static void
cc_end_of_caption (struct cc_decoder * cd,
struct cc_channel * ch)
{
unsigned int curr_buffer;
unsigned int row;
/* EOC End Of Caption -- 001 c10f 010 1111 */
curr_buffer = ch->displayed_buffer;
switch (ch->mode) {
case CC_MODE_UNKNOWN:
case CC_MODE_POP_ON:
break;
case CC_MODE_ROLL_UP:
row = ch->curr_row;
if (0 != (ch->dirty[curr_buffer] & (1 << row)))
cc_stream_event (cd, ch, row, row);
break;
case CC_MODE_PAINT_ON:
cc_stream_event_if_changed (cd, ch);
break;
case CC_MODE_TEXT:
/* Not reached. (ch is a caption channel.) */
return;
}
ch->displayed_buffer = curr_buffer ^= 1;
/* 47 CFR Section 15.119 (f)(2). */
ch->mode = CC_MODE_POP_ON;
if (0 != ch->dirty[curr_buffer]) {
ch->timestamp_c0 = cd->timestamp;
cc_stream_event (cd, ch,
CC_FIRST_ROW,
CC_LAST_ROW);
cc_display_event (cd, ch, 0);
}
}
static void
cc_carriage_return (struct cc_decoder * cd,
struct cc_channel * ch)
{
unsigned int curr_buffer;
unsigned int row;
unsigned int window_rows;
unsigned int first_row;
/* CR Carriage Return -- 001 c10f 010 1101 */
curr_buffer = ch->displayed_buffer;
row = ch->curr_row;
switch (ch->mode) {
case CC_MODE_UNKNOWN:
return;
case CC_MODE_ROLL_UP:
/* 47 CFR Section 15.119 (f)(1)(iii). */
ch->curr_column = CC_FIRST_COLUMN;
/* 47 CFR 15.119 (f)(1): "The cursor always remains on
the base row." */
/* XXX Spec? */
ch->last_pac = 0;
/* No event if the buffer contains only
TRANSPARENT_SPACEs. */
if (0 == ch->dirty[curr_buffer])
return;
window_rows = MIN (row + 1 - CC_FIRST_ROW,
ch->window_rows);
break;
case CC_MODE_POP_ON:
case CC_MODE_PAINT_ON:
/* 47 CFR 15.119 (f)(2)(i), (f)(3)(i): No effect. */
return;
case CC_MODE_TEXT:
/* 47 CFR Section 15.119 (f)(1)(iii). */
ch->curr_column = CC_FIRST_COLUMN;
/* XXX Spec? */
ch->last_pac = 0;
/* EIA 608-B Section 7.4: "When Text Mode has
initially been selected and the specified Text
memory is empty, the cursor starts at the topmost
row, Column 1, and moves down to Column 1 on the
next row each time a Carriage Return is received
until the last available row is reached. A variety
of methods may be used to accomplish the scrolling,
provided that the text is legible while moving. For
example, as soon as all of the available rows of
text are on the screen, Text Mode switches to the
standard roll-up type of presentation." */
if (CC_LAST_ROW != row) {
if (0 != (ch->dirty[curr_buffer] & (1 << row))) {
cc_stream_event (cd, ch, row, row);
}
ch->curr_row = row + 1;
return;
}
/* No event if the buffer contains all
TRANSPARENT_SPACEs. */
if (0 == ch->dirty[curr_buffer])
return;
window_rows = CC_MAX_ROWS;
break;
}
/* 47 CFR Section 15.119 (f)(1)(iii). In roll-up mode: "Each
time a Carriage Return is received, the text in the top row
of the window is erased from memory and from the display or
scrolled off the top of the window. The remaining rows of
text are each rolled up into the next highest row in the
window, leaving the base row blank and ready to accept new
text." */
if (0 != (ch->dirty[curr_buffer] & (1 << row))) {
cc_stream_event (cd, ch, row, row);
}
first_row = row + 1 - window_rows;
memmove (ch->buffer[curr_buffer][first_row],
ch->buffer[curr_buffer][first_row + 1],
(window_rows - 1) * sizeof (ch->buffer[0][0]));
ch->dirty[curr_buffer] >>= 1;
memset (ch->buffer[curr_buffer][row], 0,
sizeof (ch->buffer[0][0]));
cc_display_event (cd, ch, VBI_START_ROLLING);
}
static void
cc_erase_memory (struct cc_decoder * cd,
struct cc_channel * ch,
unsigned int buffer)
{
if (0 != ch->dirty[buffer]) {
CLEAR (ch->buffer[buffer]);
ch->dirty[buffer] = 0;
if (buffer == ch->displayed_buffer)
cc_display_event (cd, ch, 0);
}
}
static void
cc_erase_displayed_memory (struct cc_decoder * cd,
struct cc_channel * ch)
{
unsigned int row;
/* EDM Erase Displayed Memory -- 001 c10f 010 1100 */
switch (ch->mode) {
case CC_MODE_UNKNOWN:
/* We have not received EOC, RCL, RDC or RUx yet, but
ch is valid. */
break;
case CC_MODE_ROLL_UP:
row = ch->curr_row;
if (0 != (ch->dirty[ch->displayed_buffer] & (1 << row)))
cc_stream_event (cd, ch, row, row);
break;
case CC_MODE_PAINT_ON:
cc_stream_event_if_changed (cd, ch);
break;
case CC_MODE_POP_ON:
/* Nothing to do. */
break;
case CC_MODE_TEXT:
/* Not reached. (ch is a caption channel.) */
return;
}
/* May send a display event. */
cc_erase_memory (cd, ch, ch->displayed_buffer);
}
static void
cc_text_restart (struct cc_decoder * cd,
struct cc_channel * ch)
{
unsigned int curr_buffer;
unsigned int row;
/* TR Text Restart -- 001 c10f 010 1010 */
curr_buffer = ch->displayed_buffer;
row = ch->curr_row;
/* ch->mode is invariably CC_MODE_TEXT. */
if (0 != (ch->dirty[curr_buffer] & (1 << row))) {
cc_stream_event (cd, ch, row, row);
}
/* EIA 608-B Section 7.4. */
/* May send a display event. */
cc_erase_memory (cd, ch, ch->displayed_buffer);
/* EIA 608-B Section 7.4. */
ch->curr_row = CC_FIRST_ROW;
ch->curr_column = CC_FIRST_COLUMN;
}
static void
cc_resume_direct_captioning (struct cc_decoder * cd,
struct cc_channel * ch)
{
unsigned int curr_buffer;
unsigned int row;
/* RDC Resume Direct Captioning -- 001 c10f 010 1001 */
/* 47 CFR 15.119 (f)(1)(x), (f)(2)(vi) and EIA 608-B Annex
B.7: Does not erase memory, does not move the cursor when
resuming after a Text transmission.
XXX If ch->mode is unknown, roll-up or pop-on, what shall
we do if no PAC is received between RDC and the text? */
curr_buffer = ch->displayed_buffer;
row = ch->curr_row;
switch (ch->mode) {
case CC_MODE_ROLL_UP:
if (0 != (ch->dirty[curr_buffer] & (1 << row)))
cc_stream_event (cd, ch, row, row);
/* fall through */
case CC_MODE_UNKNOWN:
case CC_MODE_POP_ON:
/* No change since last stream_event(). */
memcpy (ch->buffer[2], ch->buffer[curr_buffer],
sizeof (ch->buffer[2]));
break;
case CC_MODE_PAINT_ON:
/* Mode continues. */
break;
case CC_MODE_TEXT:
/* Not reached. (ch is a caption channel.) */
return;
}
ch->mode = CC_MODE_PAINT_ON;
}
static void
cc_resize_window (struct cc_decoder * cd,
struct cc_channel * ch,
unsigned int new_rows)
{
unsigned int curr_buffer;
unsigned int max_rows;
unsigned int old_rows;
unsigned int row1;
curr_buffer = ch->displayed_buffer;
/* No event if the buffer contains all TRANSPARENT_SPACEs. */
if (0 == ch->dirty[curr_buffer])
return;
row1 = ch->curr_row + 1;
max_rows = row1 - CC_FIRST_ROW;
old_rows = MIN (ch->window_rows, max_rows);
new_rows = MIN (new_rows, max_rows);
/* Nothing to do unless the window shrinks. */
if (0 == new_rows || new_rows >= old_rows)
return;
memset (&ch->buffer[curr_buffer][row1 - old_rows][0], 0,
(old_rows - new_rows)
* sizeof (ch->buffer[0][0]));
ch->dirty[curr_buffer] &= -1 << (row1 - new_rows);
cc_display_event (cd, ch, 0);
}
static void
cc_roll_up_caption (struct cc_decoder * cd,
struct cc_channel * ch,
unsigned int c2)
{
unsigned int window_rows;
/* Roll-Up Captions -- 001 c10f 010 01xx */
window_rows = (c2 & 7) - 3; /* 2, 3, 4 */
switch (ch->mode) {
case CC_MODE_ROLL_UP:
/* 47 CFR 15.119 (f)(1)(iv). */
/* May send a display event. */
cc_resize_window (cd, ch, window_rows);
/* fall through */
case CC_MODE_UNKNOWN:
ch->mode = CC_MODE_ROLL_UP;
ch->window_rows = window_rows;
/* 47 CFR 15.119 (f)(1)(ix): No cursor movements,
no memory erasing. */
break;
case CC_MODE_PAINT_ON:
cc_stream_event_if_changed (cd, ch);
/* fall through */
case CC_MODE_POP_ON:
ch->mode = CC_MODE_ROLL_UP;
ch->window_rows = window_rows;
/* 47 CFR 15.119 (f)(1)(ii). */
ch->curr_row = CC_LAST_ROW;
ch->curr_column = CC_FIRST_COLUMN;
/* 47 CFR 15.119 (f)(1)(x). */
/* May send a display event. */
cc_erase_memory (cd, ch, ch->displayed_buffer);
cc_erase_memory (cd, ch, ch->displayed_buffer ^ 1);
break;
case CC_MODE_TEXT:
/* Not reached. (ch is a caption channel.) */
return;
}
}
static void
cc_delete_to_end_of_row (struct cc_decoder * cd,
struct cc_channel * ch)
{
unsigned int curr_buffer;
unsigned int row;
/* DER Delete To End Of Row -- 001 c10f 010 0100 */
/* 47 CFR 15.119 (f)(1)(vii), (f)(2)(iii), (f)(3)(ii) and EIA
608-B Section 7.4: In all caption modes and Text mode
"[the] Delete to End of Row command will erase from memory
any characters or control codes starting at the current
cursor location and in all columns to its right on the same
row." */
curr_buffer = ch->displayed_buffer
^ (CC_MODE_POP_ON == ch->mode);
row = ch->curr_row;
/* No event if the row contains only TRANSPARENT_SPACEs. */
if (0 != (ch->dirty[curr_buffer] & (1 << row))) {
unsigned int column;
unsigned int i;
uint16_t c;
column = ch->curr_column;
memset (&ch->buffer[curr_buffer][row][column], 0,
(CC_LAST_COLUMN - column + 1)
* sizeof (ch->buffer[0][0][0]));
c = 0;
for (i = CC_FIRST_COLUMN; i < column; ++i)
c |= ch->buffer[curr_buffer][row][i];
ch->dirty[curr_buffer] &= ~((0 == c) << row);
cc_display_event (cd, ch, 0);
}
}
static void
cc_backspace (struct cc_decoder * cd,
struct cc_channel * ch)
{
unsigned int curr_buffer;
unsigned int row;
unsigned int column;
/* BS Backspace -- 001 c10f 010 0001 */
/* 47 CFR Section 15.119 (f)(1)(vi), (f)(2)(ii), (f)(3)(i) and
EIA 608-B Section 7.4. */
column = ch->curr_column;
if (column <= CC_FIRST_COLUMN)
return;
ch->curr_column = --column;
curr_buffer = ch->displayed_buffer
^ (CC_MODE_POP_ON == ch->mode);
row = ch->curr_row;
/* No event if there's no visible effect. */
if (0 != ch->buffer[curr_buffer][row][column]) {
unsigned int i;
uint16_t c;
/* 47 CFR 15.119 (f), (f)(1)(vi), (f)(2)(ii) and EIA
608-B Section 7.4. */
ch->buffer[curr_buffer][row][column] = 0;
c = 0;
for (i = CC_FIRST_COLUMN; i <= CC_LAST_COLUMN; ++i)
c |= ch->buffer[curr_buffer][row][i];
ch->dirty[curr_buffer] &= ~((0 == c) << row);
cc_display_event (cd, ch, 0);
}
}
static void
cc_resume_caption_loading (struct cc_decoder * cd,
struct cc_channel * ch)
{
unsigned int row;
/* RCL Resume Caption Loading -- 001 c10f 010 0000 */
switch (ch->mode) {
case CC_MODE_UNKNOWN:
case CC_MODE_POP_ON:
break;
case CC_MODE_ROLL_UP:
row = ch->curr_row;
if (0 != (ch->dirty[ch->displayed_buffer] & (1 << row)))
cc_stream_event (cd, ch, row, row);
break;
case CC_MODE_PAINT_ON:
cc_stream_event_if_changed (cd, ch);
break;
case CC_MODE_TEXT:
/* Not reached. (ch is a caption channel.) */
return;
}
/* 47 CFR 15.119 (f)(1)(x): Does not erase memory.
(f)(2)(iv): Cursor position remains unchanged. */
ch->mode = CC_MODE_POP_ON;
}
/* Note curr_ch is invalid if UNKNOWN_CC_CHANNEL == cd->cc.curr_ch_num. */
static struct cc_channel *
cc_switch_channel (struct cc_decoder * cd,
struct cc_channel * curr_ch,
vbi_pgno new_ch_num,
enum field_num f)
{
struct cc_channel *new_ch;
if (UNKNOWN_CC_CHANNEL != cd->curr_ch_num[f]
&& CC_MODE_UNKNOWN != curr_ch->mode) {
/* XXX Force a display update if we do not send events
on every display change. */
}
cd->curr_ch_num[f] = new_ch_num;
new_ch = &cd->channel[new_ch_num - VBI_CAPTION_CC1];
return new_ch;
}
/* Note ch is invalid if UNKNOWN_CC_CHANNEL == cd->cc.curr_ch_num[f]. */
static void
cc_misc_control_code (struct cc_decoder * cd,
struct cc_channel * ch,
unsigned int c2,
unsigned int ch_num0,
enum field_num f)
{
unsigned int new_ch_num;
/* Misc Control Codes -- 001 c10f 010 xxxx */
/* c = channel (0 -> CC1/CC3/T1/T3, 1 -> CC2/CC4/T2/T4)
-- 47 CFR Section 15.119, EIA 608-B Section 7.7.
f = field (0 -> F1, 1 -> F2)
-- EIA 608-B Section 8.4, 8.5. */
/* XXX The f flag is intended to detect accidental field
swapping and we should use it for that purpose. */
switch (c2 & 15) {
case 0: /* RCL Resume Caption Loading -- 001 c10f 010 0000 */
/* 47 CFR 15.119 (f)(2) and EIA 608-B Section 7.7. */
new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3);
ch = cc_switch_channel (cd, ch, new_ch_num, f);
cc_resume_caption_loading (cd, ch);
break;
case 1: /* BS Backspace -- 001 c10f 010 0001 */
if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f]
|| CC_MODE_UNKNOWN == ch->mode)
break;
cc_backspace (cd, ch);
break;
case 2: /* reserved (formerly AOF Alarm Off) */
case 3: /* reserved (formerly AON Alarm On) */
break;
case 4: /* DER Delete To End Of Row -- 001 c10f 010 0100 */
if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f]
|| CC_MODE_UNKNOWN == ch->mode)
break;
cc_delete_to_end_of_row (cd, ch);
break;
case 5: /* RU2 */
case 6: /* RU3 */
case 7: /* RU4 Roll-Up Captions -- 001 c10f 010 01xx */
/* 47 CFR 15.119 (f)(1) and EIA 608-B Section 7.7. */
new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3);
ch = cc_switch_channel (cd, ch, new_ch_num, f);
cc_roll_up_caption (cd, ch, c2);
break;
case 8: /* FON Flash On -- 001 c10f 010 1000 */
if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f]
|| CC_MODE_UNKNOWN == ch->mode)
break;
/* 47 CFR 15.119 (h)(1)(i): Spacing attribute. */
cc_put_char (cd, ch, 0x1428,
/* displayable */ FALSE,
/* backspace */ FALSE);
break;
case 9: /* RDC Resume Direct Captioning -- 001 c10f 010 1001 */
/* 47 CFR 15.119 (f)(3) and EIA 608-B Section 7.7. */
new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3);
ch = cc_switch_channel (cd, ch, new_ch_num, f);
cc_resume_direct_captioning (cd, ch);
break;
case 10: /* TR Text Restart -- 001 c10f 010 1010 */
/* EIA 608-B Section 7.4. */
new_ch_num = VBI_CAPTION_T1 + (ch_num0 & 3);
ch = cc_switch_channel (cd, ch, new_ch_num, f);
cc_text_restart (cd, ch);
break;
case 11: /* RTD Resume Text Display -- 001 c10f 010 1011 */
/* EIA 608-B Section 7.4. */
new_ch_num = VBI_CAPTION_T1 + (ch_num0 & 3);
ch = cc_switch_channel (cd, ch, new_ch_num, f);
/* ch->mode is invariably CC_MODE_TEXT. */
break;
case 12: /* EDM Erase Displayed Memory -- 001 c10f 010 1100 */
/* 47 CFR 15.119 (f). EIA 608-B Section 7.7 and Annex
B.7: "[The] command shall be acted upon as
appropriate for caption processing without
terminating the Text Mode data stream." */
/* We need not check cd->curr_ch_num because bit 2 is
implied, bit 1 is the known field number and bit 0
is coded in the control code. */
ch = &cd->channel[ch_num0 & 3];
cc_erase_displayed_memory (cd, ch);
break;
case 13: /* CR Carriage Return -- 001 c10f 010 1101 */
if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f])
break;
cc_carriage_return (cd, ch);
break;
case 14: /* ENM Erase Non-Displayed Memory -- 001 c10f 010 1110 */
/* 47 CFR 15.119 (f)(2)(v). EIA 608-B Section 7.7 and
Annex B.7: "[The] command shall be acted upon as
appropriate for caption processing without
terminating the Text Mode data stream." */
/* See EDM. */
ch = &cd->channel[ch_num0 & 3];
cc_erase_memory (cd, ch, ch->displayed_buffer ^ 1);
break;
case 15: /* EOC End Of Caption -- 001 c10f 010 1111 */
/* 47 CFR 15.119 (f), (f)(2), (f)(3)(iv) and EIA 608-B
Section 7.7, Annex C.11. */
new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3);
ch = cc_switch_channel (cd, ch, new_ch_num, f);
cc_end_of_caption (cd, ch);
break;
}
}
static void
cc_move_window (struct cc_decoder * cd,
struct cc_channel * ch,
unsigned int new_base_row)
{
uint8_t *base;
unsigned int curr_buffer;
unsigned int bytes_per_row;
unsigned int old_max_rows;
unsigned int new_max_rows;
unsigned int copy_bytes;
unsigned int erase_begin;
unsigned int erase_end;
curr_buffer = ch->displayed_buffer;
/* No event if we do not move the window or the buffer
contains only TRANSPARENT_SPACEs. */
if (new_base_row == ch->curr_row
|| 0 == ch->dirty[curr_buffer])
return;
base = (void *) &ch->buffer[curr_buffer][CC_FIRST_ROW][0];
bytes_per_row = sizeof (ch->buffer[0][0]);
old_max_rows = ch->curr_row + 1 - CC_FIRST_ROW;
new_max_rows = new_base_row + 1 - CC_FIRST_ROW;
copy_bytes = MIN (MIN (old_max_rows, new_max_rows),
ch->window_rows) * bytes_per_row;
if (new_base_row < ch->curr_row) {
erase_begin = (new_base_row + 1) * bytes_per_row;
erase_end = (ch->curr_row + 1) * bytes_per_row;
memmove (base + erase_begin - copy_bytes,
base + erase_end - copy_bytes, copy_bytes);
ch->dirty[curr_buffer] >>= ch->curr_row - new_base_row;
} else {
erase_begin = (ch->curr_row + 1) * bytes_per_row
- copy_bytes;
erase_end = (new_base_row + 1) * bytes_per_row
- copy_bytes;
memmove (base + erase_end,
base + erase_begin, copy_bytes);
ch->dirty[curr_buffer] <<= new_base_row - ch->curr_row;
ch->dirty[curr_buffer] &= CC_ALL_ROWS_MASK;
}
memset (base + erase_begin, 0, erase_end - erase_begin);
cc_display_event (cd, ch, 0);
}
static void
cc_preamble_address_code (struct cc_decoder * cd,
struct cc_channel * ch,
unsigned int c1,
unsigned int c2)
{
unsigned int row;
/* PAC Preamble Address Codes -- 001 crrr 1ri xxxu */
row = cc_pac_row_map[(c1 & 7) * 2 + ((c2 >> 5) & 1)];
if ((int) row < 0)
return;
switch (ch->mode) {
case CC_MODE_UNKNOWN:
return;
case CC_MODE_ROLL_UP:
/* EIA 608-B Annex C.4. */
if (ch->window_rows > row + 1)
row = ch->window_rows - 1;
/* 47 CFR Section 15.119 (f)(1)(ii). */
/* May send a display event. */
cc_move_window (cd, ch, row);
ch->curr_row = row;
break;
case CC_MODE_PAINT_ON:
cc_stream_event_if_changed (cd, ch);
/* fall through */
case CC_MODE_POP_ON:
/* XXX 47 CFR 15.119 (f)(2)(i), (f)(3)(i): In Pop-on
and paint-on mode "Preamble Address Codes can be
used to move the cursor around the screen in random
order to place captions on Rows 1 to 15." We do not
have a limit on the number of displayable rows, but
as EIA 608-B Annex C.6 points out, if more than
four rows must be displayed they were probably
received in error and we should respond
accordingly. */
/* 47 CFR Section 15.119 (d)(1)(i) and EIA 608-B Annex
C.7. */
ch->curr_row = row;
break;
case CC_MODE_TEXT:
/* 47 CFR 15.119 (e)(1) and EIA 608-B Section 7.4:
Does not change the cursor row. */
break;
}
if (c2 & 0x10) {
/* 47 CFR 15.119 (e)(1)(i) and EIA 608-B Table 71. */
ch->curr_column = CC_FIRST_COLUMN + (c2 & 0x0E) * 2;
}
/* PAC is a non-spacing attribute for the next character, see
cc_put_char(). */
ch->last_pac = 0x1000 | c2;
}
static void
cc_control_code (struct cc_decoder * cd,
unsigned int c1,
unsigned int c2,
enum field_num f)
{
struct cc_channel *ch;
unsigned int ch_num0;
if (option_debug & DEBUG_CC_DECODER) {
fprintf (stdout, "%s %02x %02x %d\n",
__FUNCTION__, c1, c2, f);
}
/* Caption / text, field 1 / 2, primary / secondary channel. */
ch_num0 = (((cd->curr_ch_num[f] - VBI_CAPTION_CC1) & 4)
+ f * 2
+ ((c1 >> 3) & 1));
/* Note ch is invalid if UNKNOWN_CC_CHANNEL ==
cd->curr_ch_num[f]. */
ch = &cd->channel[ch_num0];
if (c2 >= 0x40) {
/* Preamble Address Codes -- 001 crrr 1ri xxxu */
if (UNKNOWN_CC_CHANNEL != cd->curr_ch_num[f])
cc_preamble_address_code (cd, ch, c1, c2);
return;
}
switch (c1 & 7) {
case 0:
if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f]
|| CC_MODE_UNKNOWN == ch->mode)
break;
if (c2 < 0x30) {
/* Backgr. Attr. Codes -- 001 c000 010 xxxt */
/* EIA 608-B Section 6.2. */
cc_put_char (cd, ch, 0x1000 | c2,
/* displayable */ FALSE,
/* backspace */ TRUE);
} else {
/* Undefined. */
}
break;
case 1:
if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f]
|| CC_MODE_UNKNOWN == ch->mode)
break;
if (c2 < 0x30) {
/* Mid-Row Codes -- 001 c001 010 xxxu */
/* 47 CFR 15.119 (h)(1)(i): Spacing attribute. */
cc_put_char (cd, ch, 0x1100 | c2,
/* displayable */ FALSE,
/* backspace */ FALSE);
} else {
/* Special Characters -- 001 c001 011 xxxx */
if (0x39 == c2) {
/* Transparent space. */
cc_put_char (cd, ch, 0,
/* displayable */ FALSE,
/* backspace */ FALSE);
} else {
cc_put_char (cd, ch, 0x1100 | c2,
/* displayable */ TRUE,
/* backspace */ FALSE);
}
}
break;
case 2:
case 3: /* Extended Character Set -- 001 c01x 01x xxxx */
if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f]
|| CC_MODE_UNKNOWN == ch->mode)
break;
/* EIA 608-B Section 6.4.2. */
cc_put_char (cd, ch, (c1 * 256 + c2) & 0x777F,
/* displayable */ TRUE,
/* backspace */ TRUE);
break;
case 4:
case 5:
if (c2 < 0x30) {
/* Misc. Control Codes -- 001 c10f 010 xxxx */
cc_misc_control_code (cd, ch, c2, ch_num0, f);
} else {
/* Undefined. */
}
break;
case 6: /* reserved */
break;
case 7: /* Extended control codes -- 001 c111 01x xxxx */
if (UNKNOWN_CC_CHANNEL == cd->curr_ch_num[f]
|| CC_MODE_UNKNOWN == ch->mode)
break;
cc_ext_control_code (cd, ch, c2);
break;
}
}
static vbi_bool
cc_characters (struct cc_decoder * cd,
struct cc_channel * ch,
int c)
{
if (option_debug & DEBUG_CC_DECODER) {
fprintf (stdout, "%s %02x '%c'\n",
__FUNCTION__, c, printable (c));
}
if (0 == c) {
if (CC_MODE_UNKNOWN == ch->mode)
return TRUE;
/* XXX After x NUL characters (presumably a caption
pause), force a display update if we do not send
events on every display change. */
return TRUE;
}
if (c < 0x20) {
/* Parity error or invalid data. */
if (c < 0 && CC_MODE_UNKNOWN != ch->mode) {
/* 47 CFR Section 15.119 (j)(1). */
cc_put_char (cd, ch, 0x7F,
/* displayable */ TRUE,
/* backspace */ FALSE);
}
return FALSE;
}
if (CC_MODE_UNKNOWN != ch->mode) {
cc_put_char (cd, ch, c,
/* displayable */ TRUE,
/* backspace */ FALSE);
}
return TRUE;
}
static vbi_bool
cc_feed (struct cc_decoder * cd,
const uint8_t buffer[2],
unsigned int line,
const struct timeval * tv,
int64_t pts)
{
int c1, c2;
enum field_num f;
vbi_bool all_successful;
assert (NULL != cd);
if (option_debug & DEBUG_CC_DECODER) {
fprintf (stdout, "%s %02x %02x '%c%c' "
"%3d %f %" PRId64 "\n",
__FUNCTION__,
buffer[0] & 0x7F,
buffer[1] & 0x7F,
printable (buffer[0]),
printable (buffer[1]),
line,
tv->tv_sec + tv->tv_usec * (1 / 1e6),
pts);
}
f = FIELD_1;
switch (line) {
case 21: /* NTSC */
case 22: /* PAL/SECAM */
break;
case 284: /* NTSC */
f = FIELD_2;
break;
default:
return FALSE;
}
cd->timestamp.sys = *tv;
cd->timestamp.pts = pts;
/* FIXME deferred reset here */
c1 = vbi_unpar8 (buffer[0]);
c2 = vbi_unpar8 (buffer[1]);
all_successful = TRUE;
/* 47 CFR 15.119 (2)(i)(4): "If the first transmission of a
control code pair passes parity, it is acted upon within
one video frame. If the next frame contains a perfect
repeat of the same pair, the redundant code is ignored. If,
however, the next frame contains a different but also valid
control code pair, this pair, too, will be acted upon (and
the receiver will expect a repeat of this second pair in
the next frame). If the first byte of the expected
redundant control code pair fails the parity check and the
second byte is identical to the second byte in the
immediately preceding pair, then the expected redundant
code is ignored. If there are printing characters in place
of the redundant code, they will be processed normally."
EIA 608-B Section 8.3: Caption control codes on field 2 may
repeat as on field 1. Section 8.6.2: XDS control codes
shall not repeat. */
if (unlikely (c1 < 0)) {
goto parity_error;
} else if (c1 == cd->expect_ctrl[f][0]
&& c2 == cd->expect_ctrl[f][1]) {
/* Already acted upon. */
cd->expect_ctrl[f][0] = -1;
goto finish;
}
if (c1 >= 0x10 && c1 < 0x20) {
/* Caption control code. */
/* There's no XDS on field 1, we just
use an array to save a branch. */
cd->in_xds[f] = FALSE;
/* 47 CFR Section 15.119 (i)(1), (i)(2). */
if (c2 < 0x20) {
/* Parity error or invalid control code.
Let's hope it repeats. */
goto parity_error;
}
cc_control_code (cd, c1, c2, f);
if (cd->event_pending) {
cc_display_event (cd, cd->event_pending, 0);
cd->event_pending = NULL;
}
cd->expect_ctrl[f][0] = c1;
cd->expect_ctrl[f][1] = c2;
} else {
cd->expect_ctrl[f][0] = -1;
if (c1 < 0x10) {
if (FIELD_1 == f) {
/* 47 CFR Section 15.119 (i)(1): "If the
non-printing character in the pair is
in the range 00h to 0Fh, that character
alone will be ignored and the second
character will be treated normally." */
c1 = 0;
} else if (0x0F == c1) {
/* XDS packet terminator. */
cd->in_xds[FIELD_2] = FALSE;
goto finish;
} else if (c1 >= 0x01) {
/* XDS packet start or continuation.
EIA 608-B Section 7.7, 8.5: Also
interrupts a Text mode
transmission. */
cd->in_xds[FIELD_2] = TRUE;
goto finish;
}
}
{
struct cc_channel *ch;
vbi_pgno ch_num;
ch_num = cd->curr_ch_num[f];
if (UNKNOWN_CC_CHANNEL == ch_num)
goto finish;
ch_num = ((ch_num - VBI_CAPTION_CC1) & 5) + f * 2;
ch = &cd->channel[ch_num];
all_successful &= cc_characters (cd, ch, c1);
all_successful &= cc_characters (cd, ch, c2);
if (cd->event_pending) {
cc_display_event (cd, cd->event_pending, 0);
cd->event_pending = NULL;
}
}
}
finish:
cd->error_history = cd->error_history * 2 + all_successful;
return all_successful;
parity_error:
cd->expect_ctrl[f][0] = -1;
/* XXX Some networks stupidly transmit 0x0000 instead of
0x8080 as filler. Perhaps we shouldn't take that as a
serious parity error. */
cd->error_history *= 2;
return FALSE;
}
static void
cc_reset (struct cc_decoder * cd)
{
unsigned int ch_num;
assert (NULL != cd);
if (option_debug & DEBUG_CC_DECODER) {
fprintf (stdout, "%s\n", __FUNCTION__);
}
for (ch_num = 0; ch_num < MAX_CC_CHANNELS; ++ch_num) {
struct cc_channel *ch;
ch = &cd->channel[ch_num];
if (ch_num <= 3) {
ch->mode = CC_MODE_UNKNOWN;
/* Something suitable for roll-up mode. */
ch->curr_row = CC_LAST_ROW;
ch->curr_column = CC_FIRST_COLUMN;
ch->window_rows = 4;
} else {
ch->mode = CC_MODE_TEXT; /* invariable */
/* EIA 608-B Section 7.4: "When Text Mode has
initially been selected and the specified
Text memory is empty, the cursor starts at
the topmost row, Column 1." */
ch->curr_row = CC_FIRST_ROW;
ch->curr_column = CC_FIRST_COLUMN;
ch->window_rows = 0; /* n/a */
}
ch->displayed_buffer = 0;
ch->last_pac = 0;
CLEAR (ch->buffer);
CLEAR (ch->dirty);
cc_timestamp_reset (&ch->timestamp);
cc_timestamp_reset (&ch->timestamp_c0);
}
cd->curr_ch_num[0] = UNKNOWN_CC_CHANNEL;
cd->curr_ch_num[1] = UNKNOWN_CC_CHANNEL;
memset (cd->expect_ctrl, -1, sizeof (cd->expect_ctrl));
CLEAR (cd->in_xds);
cd->event_pending = NULL;
}
static void
init_cc_decoder (struct cc_decoder * cd)
{
cc_reset (cd);
cd->error_history = 0;
cc_timestamp_reset (&cd->timestamp);
}
/* Some code left over from ntsc-cc, to be replaced. */
const char * const ratings[] = {
"(NOT RATED)","TV-Y","TV-Y7","TV-G",
"TV-PG","TV-14","TV-MA","(NOT RATED)"};
const char * const modes[]={
"current","future","channel","miscellaneous","public service",
"reserved","invalid","invalid","invalid","invalid"};
static void
print_xds_info (struct caption_recorder *cr,
unsigned int mode,
unsigned int type)
{
const char *infoptr;
if (!cr->info[0][mode][type].print)
return;
infoptr = cr->info[cr->field][mode][type].packet;
switch ((mode << 8) + type) {
case 0x0101:
fprintf (cr->xds_fp,
"%sTIMECODE: %d/%02d %d:%02d%s",
cr->xds_info_prefix,
infoptr[3]&0x0f,infoptr[2]&0x1f,
infoptr[1]&0x1f,infoptr[0]&0x3f,
cr->xds_info_suffix);
case 0x0102:
if ((infoptr[1]&0x3f)>5)
break;
fprintf (cr->xds_fp,
"%s LENGTH: %d:%02d:%02d of %d:%02d:00%s",
cr->xds_info_prefix,
infoptr[3]&0x3f,infoptr[2]&0x3f,
infoptr[4]&0x3f,infoptr[1]&0x3f,
infoptr[0]&0x3f,
cr->xds_info_suffix);
break;
case 0x0103:
fprintf (cr->xds_fp,
"%s TITLE: %s%s",
cr->xds_info_prefix,
infoptr,
cr->xds_info_suffix);
break;
case 0x0105:
fprintf (cr->xds_fp,
"%s RATING: %s (%d)",
cr->xds_info_prefix,
ratings[infoptr[0]&0x07],infoptr[0]);
if ((infoptr[0]&0x07)>0) {
if (infoptr[0]&0x20) fputs (" VIOLENCE", cr->xds_fp);
if (infoptr[0]&0x10) fputs (" SEXUAL", cr->xds_fp);
if (infoptr[0]&0x08) fputs (" LANGUAGE", cr->xds_fp);
}
fputs (cr->xds_info_suffix, cr->xds_fp);
break;
case 0x0501:
fprintf (cr->xds_fp,
"%s NETWORK: %s%s",
cr->xds_info_prefix,
infoptr,
cr->xds_info_suffix);
break;
case 0x0502:
fprintf (cr->xds_fp,
"%s CALL: %s%s",
cr->xds_info_prefix,
infoptr,
cr->xds_info_suffix);
break;
case 0x0701:
fprintf (cr->xds_fp,
"%sCUR.TIME: %d:%02d %d/%02d/%04d UTC%s",
cr->xds_info_prefix,
infoptr[1]&0x1F,infoptr[0]&0x3f,
infoptr[3]&0x0f,infoptr[2]&0x1f,
(infoptr[5]&0x3f)+1990,
cr->xds_info_suffix);
break;
case 0x0704: //timezone
fprintf (cr->xds_fp,
"%sTIMEZONE: UTC-%d%s",
cr->xds_info_prefix,
infoptr[0]&0x1f,
cr->xds_info_suffix);
break;
case 0x0104: //program genere
break;
case 0x0110:
case 0x0111:
case 0x0112:
case 0x0113:
case 0x0114:
case 0x0115:
case 0x0116:
case 0x0117:
fprintf (cr->xds_fp,
"%s DESC: %s%s",
cr->xds_info_prefix,
infoptr,
cr->xds_info_suffix);
break;
}
fflush (cr->xds_fp);
}
static int XDSdecode(struct caption_recorder *cr, int data)
{
static vbi_bool in_xds[2];
int b1, b2, length;
if (data == -1)
return -1;
b1 = data & 0x7F;
b2 = (data>>8) & 0x7F;
if (0 == b1) {
/* Filler, discard. */
return -1;
}
else if (b1 < 15) // start packet
{
cr->mode = b1;
cr->type = b2;
cr->infochecksum = b1 + b2 + 15;
if (cr->mode > 8 || cr->type > 20)
{
// printf("%% Unsupported mode %s(%d) [%d]\n",modes[(mode-1)>>1],mode,type);
cr->mode=0; cr->type=0;
}
cr->infoptr = cr->newinfo[cr->field][cr->mode][cr->type];
in_xds[cr->field] = TRUE;
}
else if (b1 == 15) // eof (next byte is checksum)
{
#if 0 //debug
if (mode == 0)
{
length=infoptr - newinfo[cr->field][0][0];
infoptr[1]=0;
printf("LEN: %d\n",length);
for (y=0;y<length;y++)
printf(" %03d",newinfo[cr->field][0][0][y]);
printf(" --- %s\n",newinfo[cr->field][0][0]);
}
#endif
if (cr->mode == 0) return 0;
if (b2 != 128-((cr->infochecksum%128)&0x7F)) return 0;
length = cr->infoptr - cr->newinfo[cr->field][cr->mode][cr->type];
//don't bug the user with repeated data
//only parse it if it's different
if (cr->info[cr->field][cr->mode][cr->type].length != length
|| 0 != memcmp (cr->info[cr->field][cr->mode][cr->type].packet,
cr->newinfo[cr->field][cr->mode][cr->type],
length))
{
memcpy (cr->info[cr->field][cr->mode][cr->type].packet,
cr->newinfo[cr->field][cr->mode][cr->type], 32);
cr->info[cr->field][cr->mode][cr->type].packet[length] = 0;
cr->info[cr->field][cr->mode][cr->type].length = length;
if (0)
fprintf (stderr, "XDS %d %d %d %d %d\n",
cr->field, cr->mode, cr->type, length,
cr->info[0][cr->mode][cr->type].print);
print_xds_info (cr, cr->mode, cr->type);
}
cr->mode = 0; cr->type = 0;
in_xds[cr->field] = FALSE;
} else if (b1 <= 31) {
/* Caption control code. */
in_xds[cr->field] = FALSE;
} else if (in_xds[cr->field]) {
if (cr->infoptr >= &cr->newinfo[cr->field][cr->mode][cr->type][32]) {
/* Bad packet. */
cr->mode = 0;
cr->type = 0;
in_xds[cr->field] = 0;
} else {
cr->infoptr[0] = b1; cr->infoptr++;
cr->infoptr[0] = b2; cr->infoptr++;
cr->infochecksum += b1 + b2;
}
}
return 0;
}
#if 0 /* to be replaced */
static int webtv_check(struct caption_recorder *cr, char * buf,int len)
{
unsigned long sum;
unsigned long nwords;
unsigned short csum=0;
char temp[9];
int nbytes=0;
while (buf[0] != '<' && len > 6) //search for the start
{
buf++; len--;
}
if (len == 6) //failure to find start
return 0;
while (nbytes+6 <= len)
{
//look for end of object checksum, it's enclosed in []'s and there shouldn't be any [' after
if (buf[nbytes] == '[' && buf[nbytes+5] == ']' && buf[nbytes+6] != '[')
break;
else
nbytes++;
}
if (nbytes+6 > len) //failure to find end
return 0;
nwords = nbytes >> 1; sum = 0;
//add up all two byte words
while (nwords-- > 0) {
sum += *buf++ << 8;
sum += *buf++;
}
if (nbytes & 1) {
sum += *buf << 8;
}
csum = (unsigned short)(sum >> 16);
while (csum !=0) {
sum = csum + (sum & 0xffff);
csum = (unsigned short)(sum >> 16);
}
sprintf(temp,"%04X\n",(int)~sum&0xffff);
buf++;
if (!strncmp(buf,temp,4))
{
buf[5]=0;
if (cr->cur_ch[cr->field] >= 0 && cr->cc_fp[cr->cur_ch[cr->field]]) {
if (!cr->plain)
fprintf(cr->cc_fp[cr->cur_ch[cr->field]], "\33[35mWEBTV: %s\33[0m\n",buf-nbytes-1);
else
fprintf(cr->cc_fp[cr->cur_ch[cr->field]], "WEBTV: %s\n",buf-nbytes-1);
fflush (cr->cc_fp[cr->cur_ch[cr->field]]);
}
}
return 0;
}
#endif /* 0 */
static void
xds_filter_option (struct caption_recorder *cr,
const char * optarg)
{
const char *s;
/* Attention: may be called repeatedly. */
if (NULL == optarg
|| 0 == strcasecmp (optarg, "all")) {
unsigned int i;
for (i = 0; i < (N_ELEMENTS (cr->info[0])
* N_ELEMENTS (cr->info[0][0])); ++i) {
cr->info[0][0][i].print = TRUE;
}
return;
}
s = optarg;
while (0 != *s) {
char buf[16];
unsigned int len;
for (;;) {
if (0 == *s)
return;
if (isalnum (*s))
break;
++s;
}
for (len = 0; len < N_ELEMENTS (buf) - 1; ++len) {
if (!isalnum (*s))
break;
buf[len] = *s++;
}
buf[len] = 0;
if (0 == strcasecmp (buf, "timecode")) {
cr->info[0][1][1].print = TRUE;
} else if (0 == strcasecmp (buf, "length")) {
cr->info[0][1][2].print = TRUE;
} else if (0 == strcasecmp (buf, "title")) {
cr->info[0][1][3].print = TRUE;
} else if (0 == strcasecmp (buf, "rating")) {
cr->info[0][1][5].print = TRUE;
} else if (0 == strcasecmp (buf, "network")) {
cr->info[0][5][1].print = TRUE;
} else if (0 == strcasecmp (buf, "call")) {
cr->info[0][5][2].print = TRUE;
} else if (0 == strcasecmp (buf, "time")) {
cr->info[0][7][1].print = TRUE;
} else if (0 == strcasecmp (buf, "timezone")) {
cr->info[0][7][4].print = TRUE;
} else if (0 == strcasecmp (buf, "desc")) {
cr->info[0][1][0x10].print = TRUE;
cr->info[0][1][0x11].print = TRUE;
cr->info[0][1][0x12].print = TRUE;
cr->info[0][1][0x13].print = TRUE;
cr->info[0][1][0x14].print = TRUE;
cr->info[0][1][0x15].print = TRUE;
cr->info[0][1][0x16].print = TRUE;
cr->info[0][1][0x17].print = TRUE;
} else {
fprintf (stderr, "Unknown XDS info '%s'\n", buf);
}
}
}
/* CEA 708-C Digital TV Closed Caption decoder. */
static const uint8_t
dtvcc_c0_length [4] = {
1, 1, 2, 3
};
static const uint8_t
dtvcc_c1_length [32] = {
/* 0x80 CW0 ... CW7 */ 1, 1, 1, 1, 1, 1, 1, 1,
/* 0x88 CLW */ 2,
/* 0x89 DSW */ 2,
/* 0x8A HDW */ 2,
/* 0x8B TGW */ 2,
/* 0x8C DLW */ 2,
/* 0x8D DLY */ 2,
/* 0x8E DLC */ 1,
/* 0x8F RST */ 1,
/* 0x90 SPA */ 3,
/* 0x91 SPC */ 4,
/* 0x92 SPL */ 3,
/* CEA 708-C Section 7.1.5.1: 0x93 ... 0x96 are
reserved one byte codes. */ 1, 1, 1, 1,
/* 0x97 SWA */ 5,
/* 0x98 DF0 ... DF7 */ 7, 7, 7, 7, 7, 7, 7, 7
};
static const uint16_t
dtvcc_g2 [96] = {
/* Note Unicode defines no transparent spaces. */
0x0020, /* 0x1020 Transparent space */
0x00A0, /* 0x1021 Non-breaking transparent space */
0, /* 0x1022 reserved */
0,
0,
0x2026, /* 0x1025 Horizontal ellipsis */
0,
0,
0,
0,
0x0160, /* 0x102A S with caron */
0,
0x0152, /* 0x102C Ligature OE */
0,
0,
0,
/* CEA 708-C Section 7.1.8: "The character (0x30) is a solid
block which fills the entire character position with the
text foreground color." */
0x2588, /* 0x1030 Full block */
0x2018, /* 0x1031 Left single quotation mark */
0x2019, /* 0x1032 Right single quotation mark */
0x201C, /* 0x1033 Left double quotation mark */
0x201D, /* 0x1034 Right double quotation mark */
0,
0,
0,
0x2122, /* 0x1039 Trademark sign */
0x0161, /* 0x103A s with caron */
0,
0x0153, /* 0x103C Ligature oe */
0x2120, /* 0x103D Service mark */
0,
0x0178, /* 0x103F Y with diaeresis */
/* Code points 0x1040 ... 0x106F reserved. */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, /* 0x1070 reserved */
0,
0,
0,
0,
0,
0x215B, /* 0x1076 1/8 */
0x215C, /* 0x1077 3/8 */
0x215D, /* 0x1078 5/8 */
0x215E, /* 0x1079 7/8 */
0x2502, /* 0x107A Box drawings vertical */
0x2510, /* 0x107B Box drawings down and left */
0x2514, /* 0x107C Box drawings up and right */
0x2500, /* 0x107D Box drawings horizontal */
0x2518, /* 0x107E Box drawings up and left */
0x250C /* 0x107F Box drawings down and right */
};
static unsigned int
dtvcc_unicode (unsigned int c)
{
if (unlikely (0 == (c & 0x60))) {
/* C0, C1, C2, C3 */
return 0;
} else if (likely (c < 0x100)) {
/* G0, G1 */
if (unlikely (0x7F == c))
return 0x266A; /* music note */
else
return c;
} else if (c < 0x1080) {
if (unlikely (c < 0x1020))
return 0;
else
return dtvcc_g2[c - 0x1020];
} else if (0x10A0 == c) {
/* We map all G2/G3 characters which are not
representable in Unicode to private code U+E900
... U+E9FF. */
return 0xE9A0; /* caption icon */
}
return 0;
}
static void
dump_dtvcc_se (FILE * fp,
const uint8_t * buf,
unsigned int n_bytes)
{
uint16_t ucs2_str[1];
unsigned int se_length;
unsigned int c;
unsigned int i;
if (0 == n_bytes)
return;
c = buf[0];
if (0 != (c & 0x60)) {
ucs2_str[0] = dtvcc_unicode (c);
fprintf (fp, "G0/G1 0x%02X U+%04X '",
c, ucs2_str[0]);
vbi_fputs_iconv_ucs2 (fp, locale_codeset,
ucs2_str, 1,
/* repl_char */ '?');
fputs ("'\n", fp);
return;
} else if ((int8_t) c < 0) {
static const char *mnemo [32] = {
"CW0", "CW1", "CW2", "CW3",
"CW4", "CW5", "CW6", "CW7",
"CLW", "DSW", "HDW", "TGW",
"DLW", "DLY", "DLC", "RST",
"SPA", "SPC", "SPL", "93",
"reserved", "reserved",
"reserved", "SWA",
"DF0", "DF1", "DF2", "DF3",
"DF4", "DF5", "DF6", "DF7"
};
static const char *opacity_name [4] = {
"Solid", "Flash", "Transl", "Transp"
};
static const char *edge_name [8] = {
"None", "Raised", "Depressed",
"Uniform", "ShadowL", "ShadowR",
"INVALID", "INVALID"
};
fprintf (fp, "C1 0x%02X %s", c, mnemo[c & 31]);
se_length = dtvcc_c1_length[c - 0x80];
if (n_bytes < se_length) {
fputs (" incomplete\n", fp);
return;
}
switch (c) {
case 0x80 ... 0x87: /* CWx */
case 0x8E: /* DLC */
case 0x8F: /* RST */
case 0x93 ... 0x96: /* reserved */
fputc ('\n', fp);
return;
case 0x88: /* CLW */
case 0x89: /* DSW */
case 0x8A: /* HDW */
case 0x8B: /* TGW */
case 0x8C: /* DLW */
fputs (" 0b", fp);
for (i = 0; i < 8; ++i) {
unsigned int bit;
bit = !!(buf[1] & (0x80 >> i));
fputc ('0' + bit, fp);
}
fputc ('\n', fp);
return;
case 0x8D: /* DLY */
fprintf (fp, " t=%u\n", buf[1]);
return;
case 0x90: /* SPA */
{
static const char *s_name [4] = {
"Small", "Std", "Large", "INVALID"
};
static const char *fs_name [8] = {
"Default", "MonoSerif", "PropSerif",
"MonoSans", "PropSans", "Casual",
"Cursive", "SmallCaps"
};
static const char *tt_name [16] = {
"Dialog", "SourceID", "Device",
"Dialog2", "Voiceover", "AudTransl",
"SubTransl", "VoiceDescr", "Lyrics",
"EffectDescr", "ScoreDescr",
"Expletive", "INVALID", "INVALID",
"INVALID", "NotDisplayable"
};
static const char *o_name [4] = {
"Sub", "Normal", "Super", "INVALID"
};
fprintf (fp, " s=%s fs=%s tt=%s o=%s i=%u "
"u=%u et=%s\n",
s_name[buf[1] & 3],
fs_name[buf[2] & 7],
tt_name[(buf[1] >> 4) & 15],
o_name[(buf[1] >> 2) & 3],
!!(buf[2] & 0x80),
!!(buf[2] & 0x40),
edge_name[(buf[2] >> 3) & 7]);
return;
}
case 0x91: /* SPC */
{
fprintf (fp, " fg=%u%u%u fo=%s bg=%u%u%u bo=%s "
"edge=%u%u%u\n",
(buf[1] >> 4) & 3, (buf[1] >> 2) & 3,
buf[1] & 3,
opacity_name[(buf[1] >> 6) & 3],
(buf[2] >> 4) & 3, (buf[2] >> 2) & 3,
buf[2] & 3,
opacity_name[(buf[2] >> 6) & 3],
(buf[3] >> 4) & 3, (buf[3] >> 2) & 3,
buf[3] & 3);
return;
}
case 0x92: /* SPL */
fprintf (fp, " r=%u c=%u\n",
buf[1] & 0x0F,
buf[2] & 0x3F);
return;
case 0x97: /* SWA */
{
static const char *j_name [4] = {
"L", "R", "C", "F"
};
static const char *pd_sd_ed_name [4] = {
"LR", "RL", "TB", "BT"
};
static const char *de_name [4] = {
"Snap", "Fade", "Wipe", "INVALID"
};
fprintf (fp, " j=%s pd=%s sd=%s ww=%u de=%s "
"ed=%s es=%u fill=%u%u%u fo=%s "
"bt=%s border=%u%u%u\n",
j_name [buf[3] & 3],
pd_sd_ed_name [(buf[3] >> 4) & 3],
pd_sd_ed_name [(buf[3] >> 2) & 3],
!!(buf[3] & 0x40),
de_name [buf[4] & 3],
pd_sd_ed_name [(buf[4] >> 2) & 3],
(buf[4] >> 4) & 15,
(buf[1] >> 4) & 3, (buf[1] >> 2) & 3,
buf[1] & 3,
opacity_name[(buf[1] >> 6) & 3],
edge_name[(buf[2] >> 6) & 3],
(buf[2] >> 4) & 3, (buf[2] >> 2) & 3,
buf[2] & 3);
return;
}
case 0x98 ... 0x9F: /* DFx */
{
static const char *ap_name [16] = {
"TL", "TC", "TR",
"CL", "C", "CR",
"BL", "BC", "BR",
"INVALID", "INVALID", "INVALID",
"INVALID", "INVALID", "INVALID",
"INVALID"
};
static const char *ws_name [8] = {
"0", "PopUp", "TranspPopUp",
"CentPopUp", "RollUp", "TranspRollUp",
"CentRollUp", "Ticker"
};
static const char *ps_name [8] = {
"0", "NTSC", "NTSCMonoSerif",
"NTSCPropSerif", "NTSCMonoSans",
"NTSCPropSans", "MonoSans",
"PropSans"
};
fprintf (fp, " p=%u ap=%s rp=%u av=%u ah=%u "
"rc=%u cc=%u rl=%u cl=%u v=%u "
"ws=%s ps=%s\n",
buf[1] & 7,
ap_name[(buf[4] >> 4) & 15],
!!(buf[2] & 0x80),
buf[2] & 0x7F,
buf[3],
buf[4] & 0x0F,
buf[5] & 0x3F,
!!(buf[1] & 0x10),
!!(buf[1] & 0x08),
!!(buf[1] & 0x20),
ws_name [(buf[6] >> 3) & 7],
ps_name [buf[6] & 7]);
return;
}
} /* switch */
} else {
static const char *mnemo [32] = {
"NUL", "reserved", "reserved",
"ETX", "reserved", "reserved",
"reserved", "reserved",
"BS", "reserved", "reserved",
"reserved", "FF", "CR", "HCR",
"reserved", "EXT1", "reserved",
"reserved", "reserved", "reserved",
"reserved", "reserved", "reserved",
"P16", "reserved", "reserved",
"reserved", "reserved", "reserved",
"reserved", "reserved"
};
/* C0 code. */
fprintf (fp, "C0 0x%02X %s", c, mnemo [c]);
se_length = dtvcc_c0_length[c >> 3];
if (n_bytes < se_length) {
fputs (" incomplete\n", fp);
return;
}
if (0x10 != c) {
if (se_length > 1)
fprintf (fp, " 0x%02X", buf[1]);
if (se_length > 2)
fprintf (fp, " 0x%02X", buf[2]);
fputc ('\n', fp);
return;
}
}
/* Two-byte codes. */
c = buf[1];
if (0 != (c & 0x60)) {
ucs2_str[0] = dtvcc_unicode (0x1000 | c);
fprintf (fp, "G2/G3 0x10%02X U+%04X '",
c, ucs2_str[0]);
vbi_fputs_iconv_ucs2 (fp, locale_codeset,
ucs2_str, 1,
/* repl_char */ '?');
fputs ("'\n", fp);
return;
} else if ((int8_t) c >= 0) {
/* C2 code. */
se_length = (c >> 3) + 2;
fprintf (fp, "C2 0x10%02X reserved", c);
} else if (c < 0x90) {
/* C3 Fixed Length Commands. */
se_length = (c >> 3) - 10;
fprintf (fp, "C3 0x10%02X reserved", c);
} else {
/* C3 Variable Length Commands. */
if (n_bytes < 3) {
fprintf (fp, "C3 0x10%02X incomplete\n", c);
return;
}
/* type [2], zero_bit [1],
length [5] */
se_length = (buf[2] & 0x1F) + 3;
fprintf (fp, "C3 0x10%02X%02X reserved",
c, buf[2]);
}
for (i = 2; i < se_length; ++i)
fprintf (fp, " 0x%02X", buf[i]);
fputc ('\n', fp);
}
static void
dump_dtvcc_buffer (FILE * fp,
struct dtvcc_window * dw)
{
unsigned int row;
unsigned int column;
for (row = 0; row < dw->row_count; ++row) {
uint16_t ucs2_str[42];
fprintf (fp, "%02u '", row);
for (column = 0; column < dw->column_count; ++column) {
unsigned int c;
c = dw->buffer[row][column];
if (0 == c) {
ucs2_str[column] = 0x20;
continue;
}
c = dtvcc_unicode (c);
if (0 == c) {
ucs2_str[column] = '?';
continue;
}
ucs2_str[column] = c;
}
vbi_fputs_iconv_ucs2 (fp, locale_codeset,
ucs2_str, dw->column_count,
/* repl_char */ '?');
fputs ("'\n", fp);
}
}
static void
dtvcc_reset (struct dtvcc_decoder * cd);
static void
dtvcc_reset_service (struct dtvcc_service * ds);
static unsigned int
dtvcc_window_id (struct dtvcc_service * ds,
struct dtvcc_window * dw)
{
return dw - ds->window;
}
static unsigned int
dtvcc_service_num (struct dtvcc_decoder * dc,
struct dtvcc_service * ds)
{
return ds - dc->service + 1;
}
/* Up to eight windows can be visible at once, so which one displays
the caption? Let's take a guess. */
static struct dtvcc_window *
dtvcc_caption_window (struct dtvcc_service * ds)
{
struct dtvcc_window *dw;
unsigned int max_priority;
unsigned int window_id;
dw = NULL;
max_priority = 8;
for (window_id = 0; window_id < 8; ++window_id) {
if (0 == (ds->created & (1 << window_id)))
continue;
if (!ds->window[window_id].visible)
continue;
if (DIR_BOTTOM_TOP
!= ds->window[window_id].style.scroll_direction)
continue;
if (ds->window[window_id].priority < max_priority) {
dw = &ds->window[window_id];
max_priority = ds->window[window_id].priority;
}
}
return dw;
}
static void
dtvcc_stream_event (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
struct dtvcc_window * dw,
unsigned int row)
{
vbi_char text[48];
vbi_char ac;
unsigned int column;
if (NULL == dw || dw != dtvcc_caption_window (ds))
return;
if (option_debug & DEBUG_DTVCC_STREAM_EVENT) {
fprintf (stderr, "%s row=%u streamed=%08x\n",
__FUNCTION__, row, dw->streamed);
dump_dtvcc_buffer (stderr, dw);
}
/* Note we only stream windows with scroll direction
upwards. */
if (0 != (dw->streamed & (1 << row))
|| !cc_timestamp_isset (&dw->timestamp_c0))
return;
dw->streamed |= 1 << row;
for (column = 0; column < dw->column_count; ++column) {
if (0 != dw->buffer[row][column])
break;
}
/* Row contains only transparent spaces. */
if (column >= dw->column_count)
return;
/* TO DO. */
CLEAR (ac);
ac.foreground = VBI_WHITE;
ac.background = VBI_BLACK;
ac.opacity = VBI_OPAQUE;
for (column = 0; column < dw->column_count; ++column) {
unsigned int c;
c = dw->buffer[row][column];
if (0 == c) {
ac.unicode = 0x20;
} else {
ac.unicode = dtvcc_unicode (c);
if (0 == ac.unicode) {
ac.unicode = 0x20;
}
}
text[column] = ac;
}
{
struct program *pr;
pr = PARENT (dc, struct program, cr.dtvcc);
cr_new_line (&pr->cr, &dw->timestamp_c0,
/* channel */ dtvcc_service_num (dc, ds) + 8,
text, /* length */ dw->column_count);
}
cc_timestamp_reset (&dw->timestamp_c0);
}
static vbi_bool
dtvcc_put_char (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
unsigned int c)
{
struct dtvcc_window *dw;
unsigned int row;
unsigned int column;
dc = dc; /* unused */
dw = ds->curr_window;
if (NULL == dw) {
ds->error_line = __LINE__;
return FALSE;
}
row = dw->curr_row;
column = dw->curr_column;
/* FIXME how should we handle TEXT_TAG_NOT_DISPLAYABLE? */
dw->buffer[row][column] = c;
if (option_debug & DEBUG_DTVCC_PUT_CHAR) {
fprintf (stderr, "%s row=%u/%u column=%u/%u\n",
__FUNCTION__,
row, dw->row_count,
column, dw->column_count);
dump_dtvcc_buffer (stdout, dw);
}
switch (dw->style.print_direction) {
case DIR_LEFT_RIGHT:
dw->streamed &= ~(1 << row);
if (!cc_timestamp_isset (&dw->timestamp_c0))
dw->timestamp_c0 = ds->timestamp;
if (++column >= dw->column_count)
return TRUE;
break;
case DIR_RIGHT_LEFT:
dw->streamed &= ~(1 << row);
if (!cc_timestamp_isset (&dw->timestamp_c0))
dw->timestamp_c0 = ds->timestamp;
if (column-- <= 0)
return TRUE;
break;
case DIR_TOP_BOTTOM:
dw->streamed &= ~(1 << column);
if (!cc_timestamp_isset (&dw->timestamp_c0))
dw->timestamp_c0 = ds->timestamp;
if (++row >= dw->row_count)
return TRUE;
break;
case DIR_BOTTOM_TOP:
dw->streamed &= ~(1 << column);
if (!cc_timestamp_isset (&dw->timestamp_c0))
dw->timestamp_c0 = ds->timestamp;
if (row-- <= 0)
return TRUE;
break;
}
dw->curr_row = row;
dw->curr_column = column;
return TRUE;
}
static vbi_bool
dtvcc_set_pen_location (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
const uint8_t * buf)
{
struct dtvcc_window *dw;
unsigned int row;
unsigned int column;
dw = ds->curr_window;
if (NULL == dw) {
ds->error_line = __LINE__;
return FALSE;
}
row = buf[1];
/* We check the top four zero bits. */
if (row >= 16) {
ds->error_line = __LINE__;
return FALSE;
}
column = buf[2];
/* We also check the top two zero bits. */
if (column >= 42) {
ds->error_line = __LINE__;
return FALSE;
}
if (row > dw->row_count)
row = dw->row_count - 1;
if (column > dw->column_count)
column = dw->column_count - 1;
if (row != dw->curr_row) {
dtvcc_stream_event (dc, ds, dw, dw->curr_row);
}
/* FIXME there's more. */
dw->curr_row = row;
dw->curr_column = column;
return TRUE;
}
static vbi_bool
dtvcc_set_pen_color (struct dtvcc_service * ds,
const uint8_t * buf)
{
struct dtvcc_window *dw;
unsigned int c;
dw = ds->curr_window;
if (NULL == dw) {
ds->error_line = __LINE__;
return FALSE;
}
c = buf[3];
if (0 != (c & 0xC0)) {
ds->error_line = __LINE__;
return FALSE;
}
dw->curr_pen.style.edge_color = c;
c = buf[1];
dw->curr_pen.style.fg_opacity = c >> 6;
dw->curr_pen.style.fg_color = c & 0x3F;
c = buf[2];
dw->curr_pen.style.bg_opacity = c >> 6;
dw->curr_pen.style.bg_color = c & 0x3F;
return TRUE;
}
static vbi_bool
dtvcc_set_pen_attributes (struct dtvcc_service * ds,
const uint8_t * buf)
{
struct dtvcc_window *dw;
unsigned int c;
enum pen_size pen_size;
enum offset offset;
enum edge edge_type;
dw = ds->curr_window;
if (NULL == dw) {
ds->error_line = __LINE__;
return FALSE;
}
c = buf[1];
offset = (c >> 2) & 3;
pen_size = c & 3;
if ((offset | pen_size) >= 3) {
ds->error_line = __LINE__;
return FALSE;
}
c = buf[2];
edge_type = (c >> 3) & 7;
if (edge_type >= 6) {
ds->error_line = __LINE__;
return FALSE;
}
c = buf[1];
dw->curr_pen.text_tag = c >> 4;
dw->curr_pen.style.offset = offset;
dw->curr_pen.style.pen_size = pen_size;
c = buf[2];
dw->curr_pen.style.italics = c >> 7;
dw->curr_pen.style.underline = (c >> 6) & 1;
dw->curr_pen.style.edge_type = edge_type;
dw->curr_pen.style.font_style = c & 7;
return TRUE;
}
static vbi_bool
dtvcc_set_window_attributes (struct dtvcc_service * ds,
const uint8_t * buf)
{
struct dtvcc_window *dw;
unsigned int c;
enum edge border_type;
enum display_effect display_effect;
dw = ds->curr_window;
if (NULL == dw)
return FALSE;
c = buf[2];
border_type = ((buf[3] >> 5) & 0x04) | (c >> 6);
if (border_type >= 6)
return FALSE;
c = buf[4];
display_effect = c & 3;
if (display_effect >= 3)
return FALSE;
c = buf[1];
dw->style.fill_opacity = c >> 6;
dw->style.fill_color = c & 0x3F;
c = buf[2];
dw->style.border_type = border_type;
dw->style.border_color = c & 0x3F;
c = buf[3];
dw->style.wordwrap = (c >> 6) & 1;
dw->style.print_direction = (c >> 4) & 3;
dw->style.scroll_direction = (c >> 2) & 3;
dw->style.justify = c & 3;
c = buf[4];
dw->style.effect_speed = c >> 4;
dw->style.effect_direction = (c >> 2) & 3;
dw->style.display_effect = display_effect;
return TRUE;
}
static vbi_bool
dtvcc_clear_windows (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
dtvcc_window_map window_map)
{
unsigned int i;
window_map &= ds->created;
for (i = 0; i < 8; ++i) {
struct dtvcc_window *dw;
if (0 == (window_map & (1 << i)))
continue;
dw = &ds->window[i];
dtvcc_stream_event (dc, ds, dw, dw->curr_row);
memset (dw->buffer, 0, sizeof (dw->buffer));
dw->streamed = 0;
/* FIXME CEA 708-C Section 7.1.4 (Form Feed)
and 8.10.5.3 confuse me. */
if (0) {
dw->curr_column = 0;
dw->curr_row = 0;
}
}
return TRUE;
}
static vbi_bool
dtvcc_define_window (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
uint8_t * buf)
{
static const struct dtvcc_window_style window_styles [7] = {
{
JUSTIFY_LEFT, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP,
FALSE, DISPLAY_EFFECT_SNAP, 0, 0, 0,
OPACITY_SOLID, EDGE_NONE, 0
}, {
JUSTIFY_LEFT, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP,
FALSE, DISPLAY_EFFECT_SNAP, 0, 0, 0,
OPACITY_TRANSPARENT, EDGE_NONE, 0
}, {
JUSTIFY_CENTER, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP,
FALSE, DISPLAY_EFFECT_SNAP, 0, 0, 0,
OPACITY_SOLID, EDGE_NONE, 0
}, {
JUSTIFY_LEFT, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP,
TRUE, DISPLAY_EFFECT_SNAP, 0, 0, 0,
OPACITY_SOLID, EDGE_NONE, 0
}, {
JUSTIFY_LEFT, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP,
TRUE, DISPLAY_EFFECT_SNAP, 0, 0, 0,
OPACITY_TRANSPARENT, EDGE_NONE, 0
}, {
JUSTIFY_CENTER, DIR_LEFT_RIGHT, DIR_BOTTOM_TOP,
TRUE, DISPLAY_EFFECT_SNAP, 0, 0, 0,
OPACITY_SOLID, EDGE_NONE, 0
}, {
JUSTIFY_LEFT, DIR_TOP_BOTTOM, DIR_RIGHT_LEFT,
FALSE, DISPLAY_EFFECT_SNAP, 0, 0, 0,
OPACITY_SOLID, EDGE_NONE, 0
}
};
static const struct dtvcc_pen_style pen_styles [7] = {
{
PEN_SIZE_STANDARD, 0, OFFSET_NORMAL, FALSE,
FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID,
0x00, OPACITY_SOLID, 0
}, {
PEN_SIZE_STANDARD, 1, OFFSET_NORMAL, FALSE,
FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID,
0x00, OPACITY_SOLID, 0
}, {
PEN_SIZE_STANDARD, 2, OFFSET_NORMAL, FALSE,
FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID,
0x00, OPACITY_SOLID, 0
}, {
PEN_SIZE_STANDARD, 3, OFFSET_NORMAL, FALSE,
FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID,
0x00, OPACITY_SOLID, 0
}, {
PEN_SIZE_STANDARD, 4, OFFSET_NORMAL, FALSE,
FALSE, EDGE_NONE, 0x3F, OPACITY_SOLID,
0x00, OPACITY_SOLID, 0
}, {
PEN_SIZE_STANDARD, 3, OFFSET_NORMAL, FALSE,
FALSE, EDGE_UNIFORM, 0x3F, OPACITY_SOLID,
0, OPACITY_TRANSPARENT, 0x00
}, {
PEN_SIZE_STANDARD, 4, OFFSET_NORMAL, FALSE,
FALSE, EDGE_UNIFORM, 0x3F, OPACITY_SOLID,
0, OPACITY_TRANSPARENT, 0x00
}
};
struct dtvcc_window *dw;
dtvcc_window_map window_map;
vbi_bool anchor_relative;
unsigned int anchor_vertical;
unsigned int anchor_horizontal;
unsigned int anchor_point;
unsigned int column_count_m1;
unsigned int window_id;
unsigned int window_style_id;
unsigned int pen_style_id;
unsigned int c;
if (0 != ((buf[1] | buf[6]) & 0xC0)) {
ds->error_line = __LINE__;
return FALSE;
}
c = buf[2];
anchor_relative = (c >> 7) & 1;
anchor_vertical = c & 0x7F;
anchor_horizontal = buf[3];
if (0 == anchor_relative) {
if (unlikely (anchor_vertical >= 75
|| anchor_horizontal >= 210)) {
ds->error_line = __LINE__;
return FALSE;
}
} else {
if (unlikely (anchor_vertical >= 100
|| anchor_horizontal >= 100)) {
ds->error_line = __LINE__;
return FALSE;
}
}
c = buf[4];
anchor_point = c >> 4;
if (unlikely (anchor_point >= 9)) {
ds->error_line = __LINE__;
return FALSE;
}
column_count_m1 = buf[5];
/* We also check the top two zero bits. */
if (unlikely (column_count_m1 >= 41)) {
ds->error_line = __LINE__;
return FALSE;
}
window_id = buf[0] & 7;
dw = &ds->window[window_id];
window_map = 1 << window_id;
ds->curr_window = dw;
c = buf[1];
dw->visible = (c >> 5) & 1;
dw->row_lock = (c >> 4) & 1;
dw->column_lock = (c >> 4) & 1;
dw->priority = c & 7;
dw->anchor_relative = anchor_relative;
dw->anchor_vertical = anchor_vertical;
dw->anchor_horizontal = anchor_horizontal;
dw->anchor_point = anchor_point;
c = buf[4];
dw->row_count = (c & 15) + 1;
dw->column_count = column_count_m1 + 1;
c = buf[6];
window_style_id = (c >> 3) & 7;
pen_style_id = c & 7;
if (window_style_id > 0) {
dw->style = window_styles[window_style_id];
} else if (0 == (ds->created & window_map)) {
dw->style = window_styles[1];
}
if (pen_style_id > 0) {
dw->curr_pen.style = pen_styles[pen_style_id];
} else if (0 == (ds->created & window_map)) {
dw->curr_pen.style = pen_styles[1];
}
if (0 != (ds->created & window_map))
return TRUE;
/* Has to be something, no? */
dw->curr_pen.text_tag = TEXT_TAG_NOT_DISPLAYABLE;
dw->curr_column = 0;
dw->curr_row = 0;
dw->streamed = 0;
cc_timestamp_reset (&dw->timestamp_c0);
ds->created |= window_map;
return dtvcc_clear_windows (dc, ds, window_map);
}
static vbi_bool
dtvcc_display_windows (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
unsigned int c,
dtvcc_window_map window_map)
{
unsigned int i;
window_map &= ds->created;
for (i = 0; i < 8; ++i) {
struct dtvcc_window *dw;
vbi_bool was_visible;
if (0 == (window_map & (1 << i)))
continue;
dw = &ds->window[i];
was_visible = dw->visible;
switch (c) {
case 0x89: /* DSW DisplayWindows */
dw->visible = TRUE;
break;
case 0x8A: /* HDW HideWindows */
dw->visible = FALSE;
break;
case 0x8B: /* TGW ToggleWindows */
dw->visible = was_visible ^ TRUE;
break;
}
if (!was_visible) {
unsigned int row;
dw->timestamp_c0 = ds->timestamp;
for (row = 0; row < dw->row_count; ++row) {
dtvcc_stream_event (dc, ds, dw, row);
}
}
}
return TRUE;
}
static vbi_bool
dtvcc_carriage_return (struct dtvcc_decoder * dc,
struct dtvcc_service * ds)
{
struct dtvcc_window *dw;
unsigned int row;
unsigned int column;
dw = ds->curr_window;
if (NULL == dw) {
ds->error_line = __LINE__;
return FALSE;
}
dtvcc_stream_event (dc, ds, dw, dw->curr_row);
row = dw->curr_row;
column = dw->curr_column;
switch (dw->style.scroll_direction) {
case DIR_LEFT_RIGHT:
dw->curr_row = 0;
if (column > 0) {
dw->curr_column = column - 1;
break;
}
dw->streamed = (dw->streamed << 1)
& ~(1 << dw->column_count);
for (row = 0; row < dw->row_count; ++row) {
for (column = dw->column_count - 1;
column > 0; --column) {
dw->buffer[row][column] =
dw->buffer[row][column - 1];
}
dw->buffer[row][column] = 0;
}
break;
case DIR_RIGHT_LEFT:
dw->curr_row = 0;
if (column + 1 < dw->row_count) {
dw->curr_column = column + 1;
break;
}
dw->streamed >>= 1;
for (row = 0; row < dw->row_count; ++row) {
for (column = 0;
column < dw->column_count - 1; ++column) {
dw->buffer[row][column] =
dw->buffer[row][column + 1];
}
dw->buffer[row][column] = 0;
}
break;
case DIR_TOP_BOTTOM:
dw->curr_column = 0;
if (row > 0) {
dw->curr_row = row - 1;
break;
}
dw->streamed = (dw->streamed << 1)
& ~(1 << dw->row_count);
memmove (&dw->buffer[1], &dw->buffer[0],
sizeof (dw->buffer[0]) * (dw->row_count - 1));
memset (&dw->buffer[0], 0, sizeof (dw->buffer[0]));
break;
case DIR_BOTTOM_TOP:
dw->curr_column = 0;
if (row + 1 < dw->row_count) {
dw->curr_row = row + 1;
break;
}
dw->streamed >>= 1;
memmove (&dw->buffer[0], &dw->buffer[1],
sizeof (dw->buffer[0]) * (dw->row_count - 1));
memset (&dw->buffer[row], 0, sizeof (dw->buffer[0]));
break;
}
return TRUE;
}
static vbi_bool
dtvcc_form_feed (struct dtvcc_decoder * dc,
struct dtvcc_service * ds)
{
struct dtvcc_window *dw;
dtvcc_window_map window_map;
dw = ds->curr_window;
if (NULL == dw) {
ds->error_line = __LINE__;
return FALSE;
}
window_map = 1 << dtvcc_window_id (ds, dw);
if (!dtvcc_clear_windows (dc, ds, window_map))
return FALSE;
dw->curr_row = 0;
dw->curr_column = 0;
return TRUE;
}
static vbi_bool
dtvcc_backspace (struct dtvcc_decoder * dc,
struct dtvcc_service * ds)
{
struct dtvcc_window *dw;
unsigned int row;
unsigned int column;
unsigned int mask;
dc = dc; /* unused */
dw = ds->curr_window;
if (NULL == dw) {
ds->error_line = __LINE__;
return FALSE;
}
row = dw->curr_row;
column = dw->curr_column;
switch (dw->style.print_direction) {
case DIR_LEFT_RIGHT:
mask = 1 << row;
if (column-- <= 0)
return TRUE;
break;
case DIR_RIGHT_LEFT:
mask = 1 << row;
if (++column >= dw->column_count)
return TRUE;
break;
case DIR_TOP_BOTTOM:
mask = 1 << column;
if (row-- <= 0)
return TRUE;
break;
case DIR_BOTTOM_TOP:
mask = 1 << column;
if (++row >= dw->row_count)
return TRUE;
break;
}
if (0 != dw->buffer[row][column]) {
dw->streamed &= ~mask;
dw->buffer[row][column] = 0;
}
dw->curr_row = row;
dw->curr_column = column;
return TRUE;
}
static vbi_bool
dtvcc_hor_carriage_return (struct dtvcc_decoder * dc,
struct dtvcc_service * ds)
{
struct dtvcc_window *dw;
unsigned int row;
unsigned int column;
unsigned int mask;
dc = dc; /* unused */
dw = ds->curr_window;
if (NULL == dw) {
ds->error_line = __LINE__;
return FALSE;
}
row = dw->curr_row;
column = dw->curr_column;
switch (dw->style.print_direction) {
case DIR_LEFT_RIGHT:
case DIR_RIGHT_LEFT:
mask = 1 << row;
memset (&dw->buffer[row][0], 0,
sizeof (dw->buffer[0]));
if (DIR_LEFT_RIGHT == dw->style.print_direction)
dw->curr_column = 0;
else
dw->curr_column = dw->column_count - 1;
break;
case DIR_TOP_BOTTOM:
case DIR_BOTTOM_TOP:
mask = 1 << column;
for (row = 0; row < dw->column_count; ++row)
dw->buffer[row][column] = 0;
if (DIR_TOP_BOTTOM == dw->style.print_direction)
dw->curr_row = 0;
else
dw->curr_row = dw->row_count - 1;
break;
}
dw->streamed &= ~mask;
return TRUE;
}
static vbi_bool
dtvcc_delete_windows (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
dtvcc_window_map window_map)
{
struct dtvcc_window *dw;
dw = ds->curr_window;
if (NULL != dw) {
unsigned int window_id;
window_id = dtvcc_window_id (ds, dw);
if (0 != (window_map & (1 << window_id))) {
dtvcc_stream_event (dc, ds, dw, dw->curr_row);
ds->curr_window = NULL;
}
}
ds->created &= ~window_map;
return TRUE;
}
static vbi_bool
dtvcc_command (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
unsigned int * se_length,
uint8_t * buf,
unsigned int n_bytes)
{
unsigned int c;
unsigned int window_id;
c = buf[0];
if ((int8_t) c < 0) {
*se_length = dtvcc_c1_length[c - 0x80];
} else {
*se_length = dtvcc_c0_length[c >> 3];
}
if (*se_length > n_bytes) {
ds->error_line = __LINE__;
return FALSE;
}
switch (c) {
case 0x08: /* BS Backspace */
return dtvcc_backspace (dc, ds);
case 0x0C: /* FF Form Feed */
return dtvcc_form_feed (dc, ds);
case 0x0D: /* CR Carriage Return */
return dtvcc_carriage_return (dc, ds);
case 0x0E: /* HCR Horizontal Carriage Return */
return dtvcc_hor_carriage_return (dc, ds);
case 0x80 ... 0x87: /* CWx SetCurrentWindow */
window_id = c & 7;
if (0 == (ds->created & (1 << window_id))) {
ds->error_line = __LINE__;
return FALSE;
}
ds->curr_window = &ds->window[window_id];
return TRUE;
case 0x88: /* CLW ClearWindows */
return dtvcc_clear_windows (dc, ds, buf[1]);
case 0x89: /* DSW DisplayWindows */
return dtvcc_display_windows (dc, ds, c, buf[1]);
case 0x8A: /* HDW HideWindows */
return dtvcc_display_windows (dc, ds, c, buf[1]);
case 0x8B: /* TGW ToggleWindows */
return dtvcc_display_windows (dc, ds, c, buf[1]);
case 0x8C: /* DLW DeleteWindows */
return dtvcc_delete_windows (dc, ds, buf[1]);
case 0x8F: /* RST Reset */
dtvcc_reset_service (ds);
return TRUE;
case 0x90: /* SPA SetPenAttributes */
return dtvcc_set_pen_attributes (ds, buf);
case 0x91: /* SPC SetPenColor */
return dtvcc_set_pen_color (ds, buf);
case 0x92: /* SPL SetPenLocation */
return dtvcc_set_pen_location (dc, ds, buf);
case 0x97: /* SWA SetWindowAttributes */
return dtvcc_set_window_attributes (ds, buf);
case 0x98 ... 0x9F: /* DFx DefineWindow */
return dtvcc_define_window (dc, ds, buf);
default:
return TRUE;
}
}
static vbi_bool
dtvcc_decode_se (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
unsigned int * se_length,
uint8_t * buf,
unsigned int n_bytes)
{
unsigned int c;
c = buf[0];
if (likely (0 != (c & 0x60))) {
/* G0/G1 character. */
*se_length = 1;
return dtvcc_put_char (dc, ds, c);
}
if (0x10 != c) {
/* C0/C1 control code. */
return dtvcc_command (dc, ds, se_length,
buf, n_bytes);
}
if (unlikely (n_bytes < 2)) {
ds->error_line = __LINE__;
return FALSE;
}
c = buf[1];
if (likely (0 != (c & 0x60))) {
/* G2/G3 character. */
*se_length = 2;
return dtvcc_put_char (dc, ds, 0x1000 | c);
}
/* CEA 708-C defines no C2 or C3 commands. */
if ((int8_t) c >= 0) {
/* C2 code. */
*se_length = (c >> 3) + 2;
} else if (c < 0x90) {
/* C3 Fixed Length Commands. */
*se_length = (c >> 3) - 10;
} else {
/* C3 Variable Length Commands. */
if (unlikely (n_bytes < 3)) {
ds->error_line = __LINE__;
return FALSE;
}
/* type [2], zero_bit [1],
length [5] */
*se_length = (buf[2] & 0x1F) + 3;
}
if (unlikely (n_bytes < *se_length)) {
ds->error_line = __LINE__;
return FALSE;
}
return TRUE;
}
static vbi_bool
dtvcc_decode_syntactic_elements (struct dtvcc_decoder * dc,
struct dtvcc_service * ds,
uint8_t * buf,
unsigned int n_bytes)
{
ds->timestamp = dc->timestamp;
while (n_bytes > 0) {
unsigned int se_length;
if (option_debug & DEBUG_DTVCC_SE) {
fprintf (stderr, "S%u ",
dtvcc_service_num (dc, ds));
dump_dtvcc_se (stderr, buf, n_bytes);
}
if (0x8D /* DLY */ == *buf
|| 0x8E /* DLC */ == *buf) {
/* FIXME ignored for now. */
++buf;
--n_bytes;
continue;
}
if (!dtvcc_decode_se (dc, ds,
&se_length,
buf, n_bytes)) {
return FALSE;
}
buf += se_length;
n_bytes -= se_length;
}
return TRUE;
}
static void
dtvcc_decode_packet (struct dtvcc_decoder * dc,
const struct timeval * tv,
int64_t pts)
{
unsigned int packet_size_code;
unsigned int packet_size;
unsigned int i;
dc->timestamp.sys = *tv;
dc->timestamp.pts = pts;
/* Packet Layer. */
/* sequence_number [2], packet_size_code [6],
packet_data [n * 8] */
if (dc->next_sequence_number >= 0
&& 0 != ((dc->packet[0] ^ dc->next_sequence_number) & 0xC0)) {
struct program *pr;
pr = PARENT (dc, struct program, cr.dtvcc);
log (4, "Station %u DTVCC packet lost.\n",
station_num (pr));
dtvcc_reset (dc);
return;
}
dc->next_sequence_number = dc->packet[0] + 0x40;
packet_size_code = dc->packet[0] & 0x3F;
packet_size = 128;
if (packet_size_code > 0)
packet_size = packet_size_code * 2;
if (option_debug & DEBUG_DTVCC_PACKET) {
unsigned int sequence_number;
sequence_number = (dc->packet[0] >> 6) & 3;
fprintf (stderr, "DTVCC packet packet_size=%u "
"(transmitted %u), sequence_number %u\n",
packet_size, dc->packet_size,
sequence_number);
dump (stderr, dc->packet, dc->packet_size);
}
/* CEA 708-C Section 5: Apparently packet_size need not be
equal to the actually transmitted amount of data. */
if (packet_size > dc->packet_size) {
struct program *pr;
pr = PARENT (dc, struct program, cr.dtvcc);
log (4, "Station %u DTVCC packet incomplete (%u/%u).\n",
station_num (pr),
dc->packet_size, packet_size);
dtvcc_reset (dc);
return;
}
/* Service Layer. */
/* CEA 708-C Section 6.2.5, 6.3: Service Blocks and syntactic
elements must not cross Caption Channel Packet
boundaries. */
for (i = 1; i < packet_size;) {
unsigned int service_number;
unsigned int block_size;
unsigned int header_size;
unsigned int c;
header_size = 1;
/* service_number [3], block_size [5],
(null_fill [2], extended_service_number [6]),
(Block_data [n * 8]) */
c = dc->packet[i];
service_number = (c & 0xE0) >> 5;
/* CEA 708-C Section 6.3: Ignore block_size if
service_number is zero. */
if (0 == service_number) {
/* NULL Service Block Header, no more data in
this Caption Channel Packet. */
break;
}
/* CEA 708-C Section 6.2.1: Apparently block_size zero
is valid, although properly it should only occur in
NULL Service Block Headers. */
block_size = c & 0x1F;
if (7 == service_number) {
if (i + 1 > packet_size)
goto service_block_incomplete;
header_size = 2;
c = dc->packet[i + 1];
/* We also check the null_fill bits. */
if (c < 7 || c > 63)
goto invalid_service_block;
service_number = c;
}
if (i + header_size + block_size > packet_size)
goto service_block_incomplete;
if (service_number <= 2) {
struct dtvcc_service *ds;
unsigned int in;
ds = &dc->service[service_number - 1];
in = ds->service_data_in;
memcpy (ds->service_data + in,
dc->packet + i + header_size,
block_size);
ds->service_data_in = in + block_size;
}
i += header_size + block_size;
}
for (i = 0; i < 2; ++i) {
struct dtvcc_service *ds;
struct program *pr;
vbi_bool success;
ds = &dc->service[i];
if (0 == ds->service_data_in)
continue;
success = dtvcc_decode_syntactic_elements
(dc, ds, ds->service_data, ds->service_data_in);
ds->service_data_in = 0;
if (success)
continue;
pr = PARENT (dc, struct program, cr.dtvcc);
log (4, "Station %u DTVCC invalid "
"syntactic element (%u).\n",
station_num (pr), ds->error_line);
if (option_debug & DEBUG_DTVCC_PACKET) {
fprintf (stderr, "Packet (%d/%d):\n",
packet_size, dc->packet_size);
dump (stderr, dc->packet, packet_size);
fprintf (stderr, "Service Data:\n");
dump (stderr, ds->service_data,
ds->service_data_in);
}
dtvcc_reset_service (ds);
}
return;
invalid_service_block:
{
struct program *pr;
pr = PARENT (dc, struct program, cr.dtvcc);
log (4, "Station %u DTVCC invalid "
"service block (%u).\n",
station_num (pr), i);
if (option_debug & DEBUG_DTVCC_PACKET) {
fprintf (stderr, "Packet (%d/%d):\n",
packet_size, dc->packet_size);
dump (stderr, dc->packet, packet_size);
}
dtvcc_reset (dc);
return;
}
service_block_incomplete:
{
struct program *pr;
pr = PARENT (dc, struct program, cr.dtvcc);
log (4, "Station %u DTVCC incomplete "
"service block (%u).\n",
station_num (pr), i);
if (option_debug & DEBUG_DTVCC_PACKET) {
fprintf (stderr, "Packet (%d/%d):\n",
packet_size, dc->packet_size);
dump (stderr, dc->packet, packet_size);
}
dtvcc_reset (dc);
return;
}
}
static void
dtvcc_reset_service (struct dtvcc_service * ds)
{
ds->curr_window = NULL;
ds->created = 0;
cc_timestamp_reset (&ds->timestamp);
}
static void
dtvcc_reset (struct dtvcc_decoder * dc)
{
dtvcc_reset_service (&dc->service[0]);
dtvcc_reset_service (&dc->service[1]);
dc->packet_size = 0;
dc->next_sequence_number = -1;
}
static void
init_dtvcc_decoder (struct dtvcc_decoder * dc)
{
dtvcc_reset (dc);
cc_timestamp_reset (&dc->timestamp);
}
/* ATSC A/53 Part 4:2007 Closed Caption Data decoder */
static void
dump_cc_data_pair (FILE * fp,
unsigned int index,
const uint8_t buf[3])
{
unsigned int one_bit;
unsigned int reserved;
unsigned int cc_valid;
enum cc_type cc_type;
unsigned int cc_data_1;
unsigned int cc_data_2;
/* Was marker_bits: "11111". */
one_bit = (buf[0] >> 7) & 1;
reserved = (buf[0] >> 3) & 15;
cc_valid = (buf[0] >> 2) & 1;
cc_type = (enum cc_type)(buf[0] & 3);
cc_data_1 = buf[1];
cc_data_2 = buf[2];
fprintf (fp, " %2u '1F'=%u%X%s valid=%u type=%s "
"%02x %02x '%c%c'\n",
index, one_bit, reserved,
(1 != one_bit || 0xF != reserved) ? "*" : "",
cc_valid, cc_type_name (cc_type),
cc_data_1, cc_data_2,
printable (cc_data_1), printable (cc_data_2));
}
static void
dump_cc_data (FILE * fp,
const uint8_t * buf,
unsigned int n_bytes,
int64_t pts,
int64_t last_pts)
{
unsigned int reserved1;
unsigned int process_cc_data_flag;
unsigned int zero_bit;
unsigned int cc_count;
unsigned int reserved2;
unsigned int same;
unsigned int marker_bits;
unsigned int i;
/* Was process_em_data_flag: "This flag is set to
indicate whether it is necessary to process the em_data. If
it is set to 1, the em_data has to be parsed and its
meaning has to be processed. When it is set to 0, the
em_data can be discarded." */
reserved1 = (buf[9] >> 7) & 1;
process_cc_data_flag = (buf[9] >> 6) & 1;
/* Was: additional_cc_data. */
zero_bit = (buf[9] >> 5) & 1;
cc_count = buf[9] & 0x1F;
/* Was em_data: "Eight bits for representing emergency
message." */
reserved2 = buf[10];
fprintf (fp, "cc_data pts=%" PRId64 " (%+" PRId64 ") "
"'1'=%u%s process_cc_data_flag=%u "
"'0'=%u%s cc_count=%u 'FF'=0x%02X%s:\n",
pts, pts - last_pts, reserved1,
(1 != reserved1) ? "*" : "", process_cc_data_flag,
zero_bit, (0 != zero_bit) ? "*" : "",
cc_count, reserved2,
(0xFF != reserved2) ? "*" : "");
same = 0;
for (i = 0; i <= cc_count; ++i) {
if (i > 0 && i < cc_count
&& 0 == memcmp (&buf[11 + i * 3],
&buf[ 8 + i * 3], 3)) {
++same;
} else {
if (same > 1) {
fprintf (fp, " %2u-%u as above\n",
i - same, i - 1);
} else if (same > 0) {
dump_cc_data_pair (fp, i - 1, &buf[8 + i * 3]);
}
if (i < cc_count)
dump_cc_data_pair (fp, i, &buf[11 + i * 3]);
same = 0;
}
}
marker_bits = buf[11 + cc_count * 3];
fprintf (fp, " marker_bits=0x%02X%s\n",
marker_bits, (0xFF != marker_bits) ? "*" : "");
if (n_bytes > 12 + cc_count * 3) {
fprintf (fp, " extraneous");
for (i = 12 + cc_count * 3; i < n_bytes; ++i)
fprintf (stderr, " %02x", buf[i]);
fputc ('\n', stderr);
}
}
/* Note pts may be < 0 if no PTS was received. */
static void
decode_cc_data (struct program * pr,
int64_t pts,
const uint8_t * buf,
unsigned int n_bytes)
{
unsigned int process_cc_data_flag;
unsigned int cc_count;
unsigned int i;
vbi_bool dtvcc;
if (option_debug & DEBUG_CC_DATA) {
static int64_t last_pts = 0; /* XXX */
dump_cc_data (stderr, buf, n_bytes, pts, last_pts);
last_pts = pts;
}
process_cc_data_flag = buf[9] & 0x40;
if (!process_cc_data_flag)
return;
cc_count = buf[9] & 0x1F;
dtvcc = FALSE;
if (NULL != pr->cr.ccd.cc_data_tap_fp) {
static uint8_t output_buffer [8 + 11 + 31 * 3];
unsigned int in;
unsigned int out;
unsigned int n_bytes;
for (in = 0; in < 8; ++in)
output_buffer[in] = pts >> (56 - in * 8);
n_bytes = 11 + cc_count * 3;
memcpy (output_buffer + in, buf, n_bytes);
in += n_bytes;
out = sizeof (output_buffer);
memset (output_buffer + in, 0, out - in);
if (out != fwrite (output_buffer, 1, out,
pr->cr.ccd.cc_data_tap_fp)) {
errno_exit ("cc_data tap write error");
}
}
for (i = 0; i < cc_count; ++i) {
unsigned int b0;
unsigned int cc_valid;
enum cc_type cc_type;
unsigned int cc_data_1;
unsigned int cc_data_2;
unsigned int j;
b0 = buf[11 + i * 3];
cc_valid = b0 & 4;
cc_type = (enum cc_type)(b0 & 3);
cc_data_1 = buf[12 + i * 3];
cc_data_2 = buf[13 + i * 3];
switch (cc_type) {
case NTSC_F1:
case NTSC_F2:
/* Note CEA 708-C Table 4: Only one NTSC pair
will be present in field picture user_data
or in progressive video pictures, and up to
three can occur if the frame rate < 30 Hz
or repeat_first_field = 1. */
if (!cc_valid || i >= 3 || dtvcc) {
/* Illegal, invalid or filler. */
break;
}
if (option_debug & (DEBUG_CC_F1 | DEBUG_CC_F2)) {
if ((NTSC_F1 == cc_type
&& 0 != (option_debug & DEBUG_CC_F1))
|| (NTSC_F2 == cc_type
&& 0 != (option_debug & DEBUG_CC_F2)))
dump_cc (stderr, i, cc_count,
cc_valid, cc_type,
cc_data_1, cc_data_2);
}
cc_feed (&pr->cr.cc, &buf[12 + i * 3],
/* line */ (NTSC_F1 == cc_type) ? 21 : 284,
&pr->now, pts);
/* XXX replace me. */
if (NTSC_F1 == cc_type) {
pr->cr.field = 0;
if (pr->cr.usexds) /* fields swapped? */
XDSdecode(&pr->cr, cc_data_1
+ cc_data_2 * 256);
} else {
pr->cr.field = 1;
if (pr->cr.usexds)
XDSdecode(&pr->cr, cc_data_1
+ cc_data_2 * 256);
}
break;
case DTVCC_DATA:
j = pr->cr.dtvcc.packet_size;
if (j <= 0) {
/* Missed packet start. */
break;
} else if (!cc_valid) {
/* End of DTVCC packet. */
dtvcc_decode_packet (&pr->cr.dtvcc,
&pr->now, pts);
pr->cr.dtvcc.packet_size = 0;
} else if (j >= 128) {
/* Packet buffer overflow. */
dtvcc_reset (&pr->cr.dtvcc);
pr->cr.dtvcc.packet_size = 0;
} else {
pr->cr.dtvcc.packet[j] = cc_data_1;
pr->cr.dtvcc.packet[j + 1] = cc_data_2;
pr->cr.dtvcc.packet_size = j + 2;
}
break;
case DTVCC_START:
dtvcc = TRUE;
j = pr->cr.dtvcc.packet_size;
if (j > 0) {
/* End of DTVCC packet. */
dtvcc_decode_packet (&pr->cr.dtvcc,
&pr->now, pts);
}
if (!cc_valid) {
/* No new data. */
pr->cr.dtvcc.packet_size = 0;
} else {
pr->cr.dtvcc.packet[0] = cc_data_1;
pr->cr.dtvcc.packet[1] = cc_data_2;
pr->cr.dtvcc.packet_size = 2;
}
break;
}
}
}
static void
init_cc_data_decoder (struct cc_data_decoder *cd)
{
CLEAR (*cd);
}
static void
cc_data_test_loop (struct program * pr,
const char * test_file_name)
{
FILE *test_fp;
test_fp = open_test_file (test_file_name);
for (;;) {
static uint8_t buffer[8 + 11 + 31 * 3];
size_t todo;
size_t actual;
todo = sizeof (buffer);
actual = fread (buffer, 1, todo, test_fp);
if (likely (actual == todo)) {
int64_t pts;
unsigned int i;
pts = 0;
for (i = 0; i < 8; ++i) {
pts |= buffer[i] << (56 - i * 8);
}
decode_cc_data (pr, pts, &buffer[8], actual);
continue;
}
if (ferror (test_fp)) {
errno_exit ("CC data file read error");
} else {
log (1, "End of CC data file.\n");
fclose (test_fp);
return;
}
}
}
/* DVB capture functions and transport stream decoding. */
static void
init_buffer (struct buffer * b,
unsigned int capacity)
{
b->capacity = capacity;
b->base = xmalloc (capacity);
b->in = 0;
b->out = 0;
}
static void
dump_pes_buffer (FILE * fp,
const struct pes_buffer *b,
const char * name)
_vbi_unused;
static void
dump_pes_buffer (FILE * fp,
const struct pes_buffer *b,
const char * name)
{
unsigned int i;
fprintf (fp, "%s PES buffer:\n", name);
for (i = 0; i < b->n_packets; ++i) {
fprintf (fp, "%2u: offs=%5u size=%u/%u "
"dts=%" PRId64 " (%+" PRId64 ") "
"duration=%" PRId64 " splice=%d lost=%d\n",
i,
b->packet[i].offset,
b->packet[i].payload,
b->packet[i].size,
b->packet[i].dts,
(i > 0) ? (b->packet[i].dts
- b->packet[i - 1].dts) : 0,
b->packet[i].duration,
b->packet[i].splice,
b->packet[i].data_lost);
}
}
static vbi_bool
decode_time_stamp (int64_t * ts,
const uint8_t * buf,
unsigned int marker)
{
/* ISO 13818-1 Section 2.4.3.6 */
if (0 != ((marker ^ buf[0]) & 0xF1))
return FALSE;
if (NULL != ts) {
unsigned int a, b, c;
/* marker [4], TS [32..30], marker_bit,
TS [29..15], marker_bit,
TS [14..0], marker_bit */
a = (buf[0] >> 1) & 0x7;
b = (buf[1] * 256 + buf[2]) >> 1;
c = (buf[3] * 256 + buf[4]) >> 1;
*ts = ((int64_t) a << 30) + (b << 15) + (c << 0);
}
return TRUE;
}
static void
dump_pes_packet_header (FILE * fp,
const uint8_t * buf)
{
unsigned int packet_start_code_prefix;
unsigned int stream_id;
unsigned int PES_packet_length;
unsigned int PES_scrambling_control;
unsigned int PES_priority;
unsigned int data_alignment_indicator;
unsigned int copyright;
unsigned int original_or_copy;
unsigned int PTS_DTS_flags;
unsigned int ESCR_flag;
unsigned int ES_rate_flag;
unsigned int DSM_trick_mode_flag;
unsigned int additional_copy_info_flag;
unsigned int PES_CRC_flag;
unsigned int PES_extension_flag;
unsigned int PES_header_data_length;
int64_t ts;
/* ISO 13818-1 Section 2.4.3.6. */
packet_start_code_prefix = buf[0] * 65536 + buf[1] * 256 + buf[2];
stream_id = buf[3];
PES_packet_length = buf[4] * 256 + buf[5];
/* '10' */
PES_scrambling_control = (buf[6] & 0x30) >> 4;
PES_priority = buf[6] & 0x08;
data_alignment_indicator = buf[6] & 0x04;
copyright = buf[6] & 0x02;
original_or_copy = buf[6] & 0x01;
PTS_DTS_flags = (buf[7] & 0xC0) >> 6;
ESCR_flag = buf[7] & 0x20;
ES_rate_flag = buf[7] & 0x10;
DSM_trick_mode_flag = buf[7] & 0x08;
additional_copy_info_flag = buf[7] & 0x04;
PES_CRC_flag = buf[7] & 0x02;
PES_extension_flag = buf[7] & 0x01;
PES_header_data_length = buf[8];
fprintf (fp, "PES %06X%02X %5u "
"%u%u%u%c%c%c%c%u%c%c%c%c%c%c %u",
packet_start_code_prefix, stream_id,
PES_packet_length,
!!(buf[6] & 0x80),
!!(buf[6] & 0x40),
PES_scrambling_control,
PES_priority ? 'P' : '-',
data_alignment_indicator ? 'A' : '-',
copyright ? 'C' : '-',
original_or_copy ? 'O' : 'C',
PTS_DTS_flags,
ESCR_flag ? 'E' : '-',
ES_rate_flag ? 'E' : '-',
DSM_trick_mode_flag ? 'D' : '-',
additional_copy_info_flag ? 'A' : '-',
PES_CRC_flag ? 'C' : '-',
PES_extension_flag ? 'X' : '-',
PES_header_data_length);
switch (PTS_DTS_flags) {
case 0: /* no timestamps */
case 1: /* forbidden */
fputc ('\n', fp);
break;
case 2: /* PTS only */
if (decode_time_stamp (&ts, &buf[9], 0x21))
fprintf (fp, " %" PRId64 "\n", ts);
else
fputs (" PTS?\n", fp);
break;
case 3: /* PTS and DTS */
if (decode_time_stamp (&ts, &buf[9], 0x31))
fprintf (fp, " %" PRId64, ts);
else
fputs (" PTS?", fp);
if (decode_time_stamp (&ts, &buf[14], 0x11))
fprintf (fp, " %" PRId64 "\n", ts);
else
fputs (" DTS?\n", fp);
break;
}
}
static void
close_ts_file (struct video_recorder *vr)
{
if (NULL != vr->minicut_fp) {
if (0 != fclose (vr->minicut_fp)) {
struct program *pr;
pr = PARENT (vr, struct program, vr);
log_errno (1, "TS stream %u close error",
(unsigned int)(pr - program_table));
}
vr->minicut_fp = NULL;
}
}
static unsigned int
mpeg2_crc (const uint8_t * buf,
unsigned int n_bytes)
{
static uint32_t crc_table[256];
unsigned int crc;
unsigned int i;
/* ISO 13818-1 Annex B. */
if (unlikely (0 == crc_table[255])) {
const unsigned int poly =
((1 << 26) | (1 << 23) | (1 << 22) | (1 << 16) |
(1 << 12) | (1 << 11) | (1 << 10) | (1 << 8) |
(1 << 7) | (1 << 5) | (1 << 4) | (1 << 2) |
(1 << 1) | 1);
unsigned int c, j;
for (i = 0; i < 256; ++i) {
c = i << 24;
for (j = 0; j < 8; ++j) {
if (c & (1 << 31))
c = (c << 1) ^ poly;
else
c <<= 1;
}
crc_table[i] = c;
}
assert (0 != crc_table[255]);
}
crc = -1;
for (i = 0; i < n_bytes; ++i)
crc = crc_table[(buf[i] ^ (crc >> 24)) & 0xFF] ^ (crc << 8);
return crc & 0xFFFFFFFFUL;
}
static const unsigned int pmt_pid = 0x5A5;
static void
init_pmt (uint8_t buf[188],
struct video_recorder *vr,
const struct ts_decoder *td)
{
uint32_t CRC_32;
/* sync_byte [8], transport_error_indicator,
payload_unit_start_indicator, transport_priority, PID [13],
transport_scrambling_control [2], adaptation_field_control
[2], continuity_counter [4] */
buf[0] = 0x47;
buf[1] = 0x40 | ((pmt_pid & 0x1FFF) >> 8);
buf[2] = pmt_pid;
buf[3] = 0x10 | (vr->pmt_cc & 0x0F);
++vr->pmt_cc;
/* pointer_field */
buf[4] = 0x00;
/* table_id [8] */
buf[5] = 0x02; /* TS_program_map_section */
/* section_syntax_indicator, '0', reserved [2], section_length
[12] */
buf[6] = 0xB0;
buf[7] = 31 - 8;
/* program_number [16] */
buf[8] = 0x00;
buf[9] = 0x01;
/* reserved [2], version_number [5], current_next_indicator */
buf[10] = 0xC1;
/* section_number [8], last_section_number [8] */
buf[11] = 0x00;
buf[12] = 0x00;
/* reserved [3], PCR_PID [13] */
buf[13] = 0xE0 | (td->pid[0] >> 8);
buf[14] = td->pid[0];
/* reserved [4], program_info_length [12] */
buf[15] = 0xF0;
buf[16] = 0x00;
/* stream_type [8], reserved [3], elementary_PID [13],
reserved [4], ES_info_length [12] */
buf[17] = 0x02; /* MPEG-2 video */
buf[18] = 0xE0 | (td->pid[0] >> 8);
buf[19] = td->pid[0];
buf[20] = 0xF0;
buf[21] = 0x00;
buf[22] = 0x81; /* AC3 audio */
buf[23] = 0xE0 | (td->pid[1] >> 8);
buf[24] = td->pid[1];
buf[25] = 0xF0;
buf[26] = 0x00;
CRC_32 = mpeg2_crc (buf + 5, 27 - 5);
buf[27] = CRC_32 >> 24;
buf[28] = CRC_32 >> 16;
buf[29] = CRC_32 >> 8;
buf[30] = CRC_32;
memset (buf + 31, -1, 188 - 31);
}
static void
init_pat (uint8_t buf[188],
struct video_recorder *vr)
{
uint32_t CRC_32;
/* sync_byte [8], transport_error_indicator,
payload_unit_start_indicator, transport_priority, PID [13],
transport_scrambling_control [2], adaptation_field_control
[2], continuity_counter [4] */
buf[0] = 0x47;
buf[1] = 0x40;
buf[2] = 0x00;
buf[3] = 0x10 | (vr->pat_cc & 0x0F);
++vr->pat_cc;
/* pointer_field [8] */
buf[4] = 0x00;
/* table_id [8] */
buf[5] = 0x00; /* program_association_section */
/* section_syntax_indicator, '0', reserved [2],
section_length [12] */
buf[6] = 0xB0;
buf[7] = 21 - 8;
/* transport_stream_id [16] */
buf[8] = 0x00;
buf[9] = 0x01;
/* reserved [2], version_number [5], current_next_indicator */
buf[10] = 0xC1;
/* section_number [8], last_section_number [8] */
buf[11] = 0x00;
buf[12] = 0x00;
/* program_number [16] */
buf[13] = 0x00;
buf[14] = 0x01;
/* reserved [3], program_map_PID [13] */
buf[15] = 0xE0 | ((pmt_pid & 0x1FFF) >> 8);
buf[16] = pmt_pid;
CRC_32 = mpeg2_crc (buf + 5, 17 - 5);
buf[17] = CRC_32 >> 24;
buf[18] = CRC_32 >> 16;
buf[19] = CRC_32 >> 8;
buf[20] = CRC_32;
memset (buf + 21, -1, 188 - 21);
}
static void
video_recorder (struct video_recorder *vr,
const uint8_t buf[188])
{
struct program *pr = PARENT (vr, struct program, vr);
size_t actual;
if (NULL == pr->option_minicut_dir_name)
return; /* no video recording */
/* The TS packet rate is too high to call time() here. We do
that when a picture arrives, some 24 to 60 times/second. */
if (0 == (pr->now.tv_sec | pr->now.tv_usec))
return; /* no picture received yet */
/* Note minicut_end is initially zero. */
if (pr->now.tv_sec >= vr->minicut_end) {
char file_name[32];
struct tm tm;
time_t t;
t = pr->now.tv_sec;
if (NULL == gmtime_r (&t, &tm)) {
/* Should not happen. */
error_exit ("System time invalid.\n");
}
vr->minicut_end = t + (60 - tm.tm_sec);
if (option_minicut_test) {
tm.tm_sec = 0;
} else if (1) {
tm.tm_sec = 0;
} else {
if (0 != tm.tm_sec)
return;
}
close_ts_file (vr);
snprintf (file_name, sizeof (file_name),
"/%04u%02u%02u%02u%02u%02u",
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec);
vr->minicut_fp = open_minicut_file (pr, &tm, file_name, ".ts");
if (NULL != vr->minicut_fp) {
uint8_t buf[2 * 188];
init_pat (buf, vr);
init_pmt (buf + 188, vr, &pr->tsd);
actual = fwrite (buf, 1, 2 * 188, vr->minicut_fp);
if (2 * 188 != actual) {
log_errno (1, "TS stream %u write error",
station_num (pr));
}
}
}
if (NULL == vr->minicut_fp)
return;
actual = fwrite (buf, 1, 188, vr->minicut_fp);
if (188 != actual) {
log_errno (1, "TS stream %u write error",
station_num (pr));
}
}
static void
init_video_recorder (struct video_recorder *vr)
{
vr->pat_cc = 0;
vr->pmt_cc = 0;
vr->minicut_end = 0;
vr->minicut_fp = NULL;
}
/* Video elementary stream decoder. */
static void
vesd_reorder_decode_cc_data (struct video_es_decoder *vd,
const uint8_t * buf,
unsigned int n_bytes)
{
struct program *pr = PARENT (vd, struct program, vesd);
n_bytes = MIN (n_bytes, (unsigned int)
sizeof (vd->reorder_buffer[0]));
switch (vd->picture_structure) {
case FRAME_PICTURE:
if (0 != vd->reorder_pictures) {
if (vd->reorder_pictures & 5) {
/* Top field or top and bottom field. */
decode_cc_data (pr, vd->reorder_pts[0],
vd->reorder_buffer[0],
vd->reorder_n_bytes[0]);
}
if (vd->reorder_pictures & 2) {
/* Bottom field. */
decode_cc_data (pr, vd->reorder_pts[1],
vd->reorder_buffer[1],
vd->reorder_n_bytes[1]);
}
}
memcpy (vd->reorder_buffer[0], buf, n_bytes);
vd->reorder_n_bytes[0] = n_bytes;
vd->reorder_pts[0] = vd->pts;
/* We have a frame. */
vd->reorder_pictures = 4;
break;
case TOP_FIELD:
if (vd->reorder_pictures >= 3) {
/* Top field or top and bottom field. */
decode_cc_data (pr, vd->reorder_pts[0],
vd->reorder_buffer[0],
vd->reorder_n_bytes[0]);
vd->reorder_pictures &= 2;
} else if (1 == vd->reorder_pictures) {
/* Apparently we missed a bottom field. */
}
memcpy (vd->reorder_buffer[0], buf, n_bytes);
vd->reorder_n_bytes[0] = n_bytes;
vd->reorder_pts[0] = vd->pts;
/* We have a top field. */
vd->reorder_pictures |= 1;
break;
case BOTTOM_FIELD:
if (vd->reorder_pictures >= 3) {
if (vd->reorder_pictures >= 4) {
/* Top and bottom field. */
decode_cc_data (pr, vd->reorder_pts[0],
vd->reorder_buffer[0],
vd->reorder_n_bytes[0]);
} else {
/* Bottom field. */
decode_cc_data (pr, vd->reorder_pts[1],
vd->reorder_buffer[1],
vd->reorder_n_bytes[1]);
}
vd->reorder_pictures &= 1;
} else if (2 == vd->reorder_pictures) {
/* Apparently we missed a top field. */
}
memcpy (vd->reorder_buffer[1], buf, n_bytes);
vd->reorder_n_bytes[1] = n_bytes;
vd->reorder_pts[1] = vd->pts;
/* We have a bottom field. */
vd->reorder_pictures |= 2;
break;
default: /* invalid */
break;
}
}
static void
vesd_user_data (struct video_es_decoder *vd,
const uint8_t * buf,
unsigned int min_bytes_valid)
{
unsigned int ATSC_identifier;
unsigned int user_data_type_code;
unsigned int cc_count;
/* ATSC A/53 Part 4:2007 Section 6.2.2 */
if (0) {
fprintf (stderr, "Picture: %s %s ref=%u "
"dts=%" PRId64 " pts=%" PRId64 "\n",
picture_coding_type_name (vd->picture_coding_type),
picture_structure_name (vd->picture_structure),
vd->picture_temporal_reference,
vd->dts, vd->pts);
}
if (0 == (vd->received_blocks & RECEIVED_PICTURE)) {
/* Either sequence or group user_data, or we missed
the picture_header. */
vd->received_blocks &= ~RECEIVED_PES_PACKET;
return;
}
/* start_code_prefix [24], start_code [8],
ATSC_identifier [32], user_data_type_code [8] */
if (min_bytes_valid < 9)
return;
ATSC_identifier = ((buf[4] << 24) | (buf[5] << 16) |
(buf[6] << 8) | buf[7]);
if (0x47413934 != ATSC_identifier)
return;
user_data_type_code = buf[8];
if (0x03 != user_data_type_code)
return;
/* ATSC A/53 Part 4:2007 Section 6.2.1: "No more than one
user_data() structure using the same user_data_type_code
[...] shall be present following any given picture
header." */
if (vd->received_blocks & RECEIVED_MPEG_CC_DATA) {
/* Too much data lost. */
return;
}
vd->received_blocks |= RECEIVED_MPEG_CC_DATA;
/* reserved, process_cc_data_flag, zero_bit, cc_count [5],
reserved [8] */
if (min_bytes_valid < 11)
return;
/* one_bit, reserved [4], cc_valid, cc_type [2],
cc_data_1 [8], cc_data_2 [8] */
cc_count = buf[9] & 0x1F;
/* CEA 708-C Section 4.4 permits padding, so we have to see
all cc_data elements. */
if (min_bytes_valid < 11 + cc_count * 3)
return;
if (option_debug & DEBUG_VESD_CC_DATA) {
char text[0x1F * 2 + 1];
unsigned int i;
vbi_bool ooo;
for (i = 0; i < cc_count; ++i) {
text[i * 2 + 0] = printable (buf[12 + i * 3]);
text[i * 2 + 1] = printable (buf[13 + i * 3]);
}
text[cc_count * 2] = 0;
ooo = (B_TYPE == vd->picture_coding_type
&& vd->reorder_pictures < 3);
fprintf (stderr, "DTVCC coding=%s structure=%s "
"pts=%" PRId64 " cc_count=%u "
"n_bytes=%u '%s'%s\n",
picture_coding_type_name
(vd->picture_coding_type),
picture_structure_name
(vd->picture_structure),
vd->pts, cc_count, min_bytes_valid,
text, ooo ? " (out of order)" : "");
}
/* CEA 708-C Section 4.4.1.1 */
switch (vd->picture_coding_type) {
case I_TYPE:
case P_TYPE:
vesd_reorder_decode_cc_data (vd, buf, min_bytes_valid);
break;
case B_TYPE:
/* To prevent a gap in the caption stream we must not
decode B pictures until we have buffered both
fields of the temporally following I or P picture. */
if (vd->reorder_pictures < 3) {
vd->reorder_pictures = 0;
break;
}
/* To do: If a B picture appears to have a higher
temporal_reference than the picture it forward
references we lost that I or P picture. */
{
struct program *pr;
pr = PARENT (vd, struct program, vesd);
decode_cc_data (pr, vd->pts, buf,
min_bytes_valid);
}
break;
default: /* invalid */
break;
}
}
static void
vesd_extension (struct video_es_decoder *vd,
const uint8_t * buf,
unsigned int min_bytes_valid)
{
enum extension_start_code_identifier extension_start_code_identifier;
/* extension_start_code [32],
extension_start_code_identifier [4],
f_code [4][4], intra_dc_precision [2],
picture_structure [2], ... */
if (min_bytes_valid < 7)
return;
extension_start_code_identifier =
(enum extension_start_code_identifier)(buf[4] >> 4);
if (PICTURE_CODING_EXTENSION_ID
!= extension_start_code_identifier)
return;
vd->picture_structure = (enum picture_structure)(buf[6] & 3);
vd->received_blocks |= RECEIVED_PICTURE_EXT;
}
static void
vesd_picture_header (struct video_es_decoder *vd,
const uint8_t * buf,
unsigned int min_bytes_valid)
{
unsigned int c;
/* picture_start_code [32],
picture_temporal_reference [10],
picture_coding_type [3], ... */
if (min_bytes_valid < 6
|| vd->received_blocks != RECEIVED_PES_PACKET) {
/* Too much data lost. */
vd->received_blocks = 0;
return;
}
c = buf[4] * 256 + buf[5];
vd->picture_temporal_reference = (c >> 6) & 0x3FF;
vd->picture_coding_type = (enum picture_coding_type)((c >> 3) & 7);
vd->received_blocks = (RECEIVED_PES_PACKET |
RECEIVED_PICTURE);
++vd->n_pictures_received;
}
static void
vesd_pes_packet_header (struct video_es_decoder *vd,
const uint8_t * buf,
unsigned int min_bytes_valid)
{
unsigned int PES_packet_length;
unsigned int PTS_DTS_flags;
int64_t pts;
if (unlikely (option_debug & DEBUG_VESD_PES_PACKET)) {
dump_pes_packet_header (stderr, buf);
}
vd->pts = -1;
vd->dts = -1;
vd->received_blocks = 0;
/* packet_start_code_prefix [24], stream_id [8],
PES_packet_length [16],
'10', PES_scrambling_control [2], PES_priority,
data_alignment_indicator, copyright, original_or_copy,
PTS_DTS_flags [2], ESCR_flag, ES_rate_flag,
DSM_trick_mode_flag, additional_copy_info_flag,
PES_CRC_flag, PES_extension_flag,
PES_header_data_length [8] */
if (min_bytes_valid < 9)
return;
PES_packet_length = buf[4] * 256 + buf[5];
PTS_DTS_flags = (buf[7] & 0xC0) >> 6;
/* ISO 13818-1 Section 2.4.3.7: In transport streams video PES
packets do not carry data, they only contain the DTS/PTS of
the following picture and PES_packet_length must be
zero. */
if (0 != PES_packet_length)
return;
switch (PTS_DTS_flags) {
case 0: /* no timestamps */
return;
case 1: /* forbidden */
return;
case 2: /* PTS only */
if (min_bytes_valid < 14)
return;
if (!decode_time_stamp (&vd->pts, &buf[9], 0x21))
return;
break;
case 3: /* PTS and DTS */
if (min_bytes_valid < 19)
return;
if (!decode_time_stamp (&pts, &buf[9], 0x31))
return;
if (!decode_time_stamp (&vd->dts, &buf[14], 0x11))
return;
vd->pts = pts;
break;
}
if (unlikely (option_minicut_test)) {
struct program *pr;
int64_t dts;
pr = PARENT (vd, struct program, vesd);
dts = vd->dts;
if (dts < 0)
dts = vd->pts;
if (pr->first_dts < 0) {
pr->first_dts = dts;
} else if (dts < pr->first_dts) {
dts += TIMESTAMP_MASK + 1;
}
pr->now.tv_sec = (dts - pr->first_dts) / 90000;
pr->now.tv_usec = (dts - pr->first_dts) % 90000 * 100 / 9;
} else {
struct program *pr;
pr = PARENT (vd, struct program, vesd);
gettimeofday (&pr->now, /* tz */ NULL);
}
vd->received_blocks = RECEIVED_PES_PACKET;
}
static void
vesd_decode_block (struct video_es_decoder *vd,
unsigned int start_code,
const uint8_t * buf,
unsigned int n_bytes,
unsigned int min_bytes_valid,
vbi_bool data_lost)
{
if (unlikely (option_debug & DEBUG_VESD_START_CODE)) {
fprintf (stderr, "VES 0x000001%02X %u %u\n",
start_code, min_bytes_valid, n_bytes);
}
/* The CEA 608-C and 708-C Close Caption data is encoded in
picture user data fields. ISO 13818-2 requires the start
code sequence 0x00, 0xB5/8, (0xB5?, 0xB2?)*. To properly
convert from coded order to display order we also need the
picture_coding_type and picture_structure fields. */
if (likely (start_code <= 0xAF)) {
if (unlikely (0x00 == start_code) && !data_lost) {
vesd_picture_header (vd, buf, min_bytes_valid);
} else {
/* slice_start_code or data lost in or after
the picture_header. */
/* For all we care the picture data is just
useless filler prior to the next PES packet
header, and we need an uninterrupted
sequence from there to the next picture
user_data to ensure the PTS, DTS,
picture_temporal_reference,
picture_coding_type, picture_structure and
cc_data belong together. */
vd->received_blocks = 0;
}
} else if (USER_DATA_START_CODE == start_code) {
vesd_user_data (vd, buf, min_bytes_valid);
} else if (data_lost) {
/* Data lost in or after this block. */
vd->received_blocks = 0;
} else if (EXTENSION_START_CODE == start_code) {
vesd_extension (vd, buf, min_bytes_valid);
} else if (start_code >= VIDEO_STREAM_0
&& start_code <= VIDEO_STREAM_15) {
vesd_pes_packet_header (vd, buf, min_bytes_valid);
} else {
/* Should be a sequence_header or
group_of_pictures_header. */
vd->received_blocks &= (1 << 0);
}
/* Not all of this data is relevant for caption decoding but
we may need it to debug the video ES decoder. Without the
actual picture data it should be highly repetitive and
compress rather well. */
if (unlikely (NULL != vd->video_es_tap_fp)
&& (NULL != vd->option_video_es_all_tap_file_name
|| start_code < 0x01
|| start_code > 0xAF)) {
if (n_bytes != fwrite (buf, 1, n_bytes,
vd->video_es_tap_fp)) {
errno_exit ("Video ES tap write error");
}
}
}
static unsigned int
vesd_make_room (struct video_es_decoder *vd,
unsigned int required)
{
struct buffer *b;
unsigned int capacity;
unsigned int in;
b = &vd->buffer;
capacity = b->capacity;
in = b->in;
if (unlikely (in + required > capacity)) {
unsigned int consumed;
unsigned int unconsumed;
consumed = b->out;
unconsumed = in - consumed;
if (required > capacity - unconsumed) {
/* XXX make this a recoverable error. */
error_exit ("Video ES buffer overflow.\n");
}
memmove (b->base, b->base + consumed, unconsumed);
in = unconsumed;
b->out = 0;
}
return in;
}
static void
video_es_decoder (struct video_es_decoder *vd,
const uint8_t * buf,
unsigned int n_bytes,
vbi_bool data_lost)
{
const uint8_t *s;
const uint8_t *e;
const uint8_t *e_max;
unsigned int in;
/* This code searches for a start code and then decodes the
data between the previous and the current start code. */
in = vesd_make_room (vd, n_bytes);
memcpy (vd->buffer.base + in, buf, n_bytes);
vd->buffer.in = in + n_bytes;
s = vd->buffer.base + vd->buffer.out + vd->skip;
e = vd->buffer.base + in + n_bytes - 4;
e_max = e;
if (unlikely (data_lost)) {
if (vd->min_bytes_valid >= UINT_MAX) {
vd->min_bytes_valid =
in - vd->buffer.out;
}
/* Data is missing after vd->buffer.base + in, so
we must ignore apparent start codes crossing that
boundary. */
e -= n_bytes;
}
for (;;) {
const uint8_t *b;
enum start_code start_code;
unsigned int n_bytes;
unsigned int min_bytes_valid;
for (;;) {
if (s >= e) {
/* Need more data. */
if (unlikely (s < e_max)) {
/* Skip over the lost data. */
s = e + 4;
e = e_max;
continue;
}
/* In the next iteration skip the
bytes we already scanned. */
vd->skip = s - vd->buffer.base
- vd->buffer.out;
return;
}
if (likely (0 != (s[2] & ~1))) {
/* Not 000001 or xx0000 or xxxx00. */
s += 3;
} else if (0 != (s[0] | s[1]) || 1 != s[2]) {
++s;
} else {
break;
}
}
b = vd->buffer.base + vd->buffer.out;
n_bytes = s - b;
min_bytes_valid = n_bytes;
data_lost = FALSE;
if (unlikely (vd->min_bytes_valid < UINT_MAX)) {
if (n_bytes < vd->min_bytes_valid) {
/* We found a new start code before
the missing data. */
vd->min_bytes_valid -= n_bytes;
} else {
min_bytes_valid = vd->min_bytes_valid;
vd->min_bytes_valid = UINT_MAX;
/* Need a flag in case we lost data just
before the next start code. */
data_lost = TRUE;
}
}
start_code = vd->last_start_code;
if (likely ((int) start_code >= 0)) {
vesd_decode_block (vd, start_code, b,
n_bytes, min_bytes_valid,
data_lost);
}
/* Remove the data we just decoded from the
buffer. Remember the position of the new start code
we found, skip it and continue the search. */
vd->buffer.out = s - vd->buffer.base;
vd->last_start_code = (enum start_code) s[3];
s += 4;
}
}
static void
reset_video_es_decoder (struct video_es_decoder *vd)
{
vd->buffer.in = 0;
vd->buffer.out = 0;
vd->min_bytes_valid = UINT_MAX;
vd->skip = 0;
vd->last_start_code = (enum start_code) -1;
vd->pts = -1;
vd->dts = -1;
vd->picture_coding_type = (enum picture_coding_type) -1;
vd->picture_structure = (enum picture_structure) -1;
vd->received_blocks = 0;
vd->reorder_pictures = 0;
}
static void
init_video_es_decoder (struct video_es_decoder *vd)
{
CLEAR (*vd);
init_buffer (&vd->buffer, /* capacity */ 1 << 20);
reset_video_es_decoder (vd);
}
static void
video_es_test_loop (struct program * pr,
const char * test_file_name)
{
FILE *test_fp;
assert (NULL != ts_buffer);
test_fp = open_test_file (test_file_name);
for (;;) {
size_t todo;
size_t actual;
todo = 4096;
actual = fread (ts_buffer, 1, todo, test_fp);
if (likely (actual == todo)) {
video_es_decoder (&pr->vesd, ts_buffer,
/* n_bytes */ actual,
/* data_lost */ FALSE);
} else if (ferror (test_fp)) {
errno_exit ("Video ES read error");
} else {
log (1, "End of video ES file.\n");
fclose (test_fp);
return;
}
}
}
static void
dump_ts_packet_header (FILE * fp,
const uint8_t buf[188])
{
unsigned int sync_byte;
unsigned int transport_error_indicator;
unsigned int payload_unit_start_indicator;
unsigned int transport_priority;
unsigned int PID;
unsigned int transport_scrambling_control;
unsigned int adaptation_field_control;
unsigned int continuity_counter;
unsigned int header_length;
sync_byte = buf[0];
transport_error_indicator = buf[1] & 0x80;
payload_unit_start_indicator = buf[1] & 0x40;
transport_priority = buf[1] & 0x20;
PID = (buf[1] * 256 + buf[2]) & 0x1FFF;
transport_scrambling_control = (buf[3] & 0xC0) >> 6;
adaptation_field_control = (buf[3] & 0x30) >> 4;
continuity_counter = buf[3] & 0x0F;
if (adaptation_field_control >= 2) {
unsigned int adaptation_field_length;
adaptation_field_length = buf[4];
header_length = 5 + adaptation_field_length;
} else {
header_length = 4;
}
fprintf (fp,
"TS %02x %c%c%c %04x %u%u%x %u\n",
sync_byte,
transport_error_indicator ? 'E' : '-',
payload_unit_start_indicator ? 'S' : '-',
transport_priority ? 'P' : '-',
PID,
transport_scrambling_control,
adaptation_field_control,
continuity_counter,
header_length);
}
static void
tsd_program (struct program * pr,
const uint8_t buf[188],
unsigned int pid,
unsigned int es_num)
{
unsigned int adaptation_field_control;
unsigned int header_length;
unsigned int payload_length;
vbi_bool data_lost;
adaptation_field_control = (buf[3] & 0x30) >> 4;
if (likely (1 == adaptation_field_control)) {
header_length = 4;
} else if (3 == adaptation_field_control) {
unsigned int adaptation_field_length;
adaptation_field_length = buf[4];
/* Zero length is used for stuffing. */
if (adaptation_field_length > 0) {
unsigned int discontinuity_indicator;
/* ISO 13818-1 Section 2.4.3.5. Also the code
below would be rather upset if
header_length > packet_size. */
if (adaptation_field_length > 182) {
log (2, "Invalid TS header "
"on station %u, stream %u.\n",
station_num (pr), pid);
/* Possibly. */
pr->tsd.data_lost = TRUE;
return;
}
/* ISO 13818-1 Section 2.4.3.5 */
discontinuity_indicator = buf[5] & 0x80;
if (discontinuity_indicator)
pr->tsd.next_ts_cc[es_num] = -1;
}
header_length = 5 + adaptation_field_length;
} else {
/* 0 == adaptation_field_control: invalid;
2 == adaptation_field_control: no payload. */
/* ISO 13818-1 Section 2.4.3.3:
continuity_counter shall not increment. */
return;
}
payload_length = 188 - header_length;
data_lost = pr->tsd.data_lost;
if (unlikely (0 != ((pr->tsd.next_ts_cc[es_num]
^ buf[3]) & 0x0F))) {
/* Continuity counter mismatch. */
if (pr->tsd.next_ts_cc[es_num] < 0) {
/* First TS packet. */
} else if (0 == (((pr->tsd.next_ts_cc[es_num] - 1)
^ buf[3]) & 0x0F)) {
/* ISO 13818-1 Section 2.4.3.3: Repeated packet. */
return;
} else {
log (2, "TS continuity error "
"on station %u, stream %u.\n",
station_num (pr), pid);
data_lost = TRUE;
}
}
pr->tsd.next_ts_cc[es_num] = buf[3] + 1;
pr->tsd.data_lost = FALSE;
if (NULL != pr->option_minicut_dir_name)
video_recorder (&pr->vr, buf);
if (0 == es_num) {
video_es_decoder (&pr->vesd, buf + header_length,
payload_length, data_lost);
}
}
static void
ts_decoder (const uint8_t buf[188])
{
unsigned int pid;
unsigned int i;
if (0) {
dump_ts_packet_header (stderr, buf);
}
if (unlikely (NULL != ts_tap_fp)) {
if (188 != fwrite (buf, 1, 188, ts_tap_fp)) {
errno_exit ("TS tap write error");
}
}
if (unlikely (buf[1] & 0x80)) {
log (2, "TS transmission error.\n");
/* The PID may be wrong, we don't know how much data
was lost, and continuity counters match by chance
with 1:16 probability. */
for (i = 0; i < n_programs; ++i) {
video_recorder (&program_table[i].vr, buf);
program_table[i].tsd.data_lost = TRUE;
}
return;
}
pid = (buf[1] * 256 + buf[2]) & 0x1FFF;
/* Note two or more programs may share one elementary stream
(e.g. radio programs with a dummy video stream). */
for (i = 0; i < n_programs; ++i) {
struct program *pr;
unsigned int es_num;
pr = program_table + i;
es_num = 0;
if (pid == pr->tsd.pid[1]) {
es_num = 1;
} else if (pid != pr->tsd.pid[0]) {
continue;
}
tsd_program (pr, buf, pid, es_num);
}
}
static void
init_ts_decoder (struct ts_decoder * td)
{
CLEAR (*td);
memset (&td->next_ts_cc, -1, sizeof (td->next_ts_cc));
}
static void
ts_test_loop (const char * test_file_name)
{
FILE *test_fp;
assert (NULL != ts_buffer);
test_fp = open_test_file (test_file_name);
for (;;) {
size_t todo;
size_t actual;
todo = 188;
actual = fread (ts_buffer, 1, todo, test_fp);
if (likely (actual == todo)) {
ts_decoder (ts_buffer);
} else if (ferror (test_fp)) {
errno_exit ("TS read error");
} else {
log (1, "End of TS file.\n");
fclose (test_fp);
return;
}
}
}
static void *
demux_thread (void * arg)
{
unsigned int in;
unsigned int out;
arg = arg; /* unused */
assert (0 == ts_buffer_capacity % 188);
out = ts_buffer_out;
/* We don't actually need the mutex but pthread_cond_wait()
won't work without it. */
pthread_mutex_lock (&dx_mutex);
for (;;) {
unsigned int avail;
in = ts_buffer_in;
avail = in - out;
if (in < out)
avail += ts_buffer_capacity;
if (avail <= 0) {
/* Yield the CPU if the buffer is empty. */
pthread_cond_wait (&dx_cond, &dx_mutex);
continue;
}
if (0) {
fputc (',', stderr);
fflush (stderr);
} else {
ts_decoder (ts_buffer + out);
}
out += 188;
if (out >= ts_buffer_capacity)
out = 0;
ts_buffer_out = out;
}
pthread_mutex_unlock (&dx_mutex);
return NULL;
}
static void
init_program (struct program * pr)
{
CLEAR (*pr);
pr->first_dts = -1;
init_ts_decoder (&pr->tsd);
init_video_es_decoder (&pr->vesd);
init_video_recorder (&pr->vr);
init_caption_recorder (&pr->cr);
}
static void
destroy_demux_state (void)
{
free (ts_buffer);
ts_buffer = NULL;
ts_buffer_capacity = 0;
pthread_cond_destroy (&dx_cond);
pthread_mutex_destroy (&dx_mutex);
}
static void
init_demux_state (void)
{
pthread_mutex_init (&dx_mutex, /* attr */ NULL);
pthread_cond_init (&dx_cond, /* attr */ NULL);
/* This buffer prevents data loss if disk activity blocks
a printf() in the caption decoder. Actually we need to
buffer only CC data which trickles in at 9600 bits/s but I
don't want to burden the capture thread with TS and PES
demultiplexing and avoid the overhead of another thread. */
ts_buffer_capacity = 20000 * 188;
ts_buffer = xmalloc (ts_buffer_capacity);
/* Copy-on-write and lock the pages. */
memset (ts_buffer, -1, ts_buffer_capacity);
ts_error = 0x00;
ts_buffer_in = 0;
ts_buffer_out = 0;
ts_n_packets_in = 0;
}
/* Capture thread */
static const char *
fe_type_name (enum fe_type t)
{
#undef CASE
#define CASE(x) case FE_##x: return #x;
switch (t) {
CASE (QPSK)
CASE (QAM)
CASE (OFDM)
CASE (ATSC)
}
return "invalid";
}
static const char *
fe_spectral_inversion_name (enum fe_spectral_inversion t)
{
#undef CASE
#define CASE(x) case INVERSION_##x: return #x;
switch (t) {
CASE (OFF)
CASE (ON)
CASE (AUTO)
}
return "invalid";
}
static const char *
fe_code_rate_name (enum fe_code_rate t)
{
#undef CASE
#define CASE(x) case FEC_##x: return #x;
switch (t) {
CASE (NONE)
CASE (1_2)
CASE (2_3)
CASE (3_4)
CASE (4_5)
CASE (5_6)
CASE (6_7)
CASE (7_8)
CASE (8_9)
CASE (AUTO)
}
return "invalid";
}
static const char *
fe_modulation_name (enum fe_modulation t)
{
#undef CASE
#define CASE(x) case x: return #x;
switch (t) {
CASE (QPSK)
CASE (QAM_16)
CASE (QAM_32)
CASE (QAM_64)
CASE (QAM_128)
CASE (QAM_256)
CASE (QAM_AUTO)
CASE (VSB_8)
CASE (VSB_16)
}
return "invalid";
}
static const char *
fe_transmit_mode_name (enum fe_transmit_mode t)
{
#undef CASE
#define CASE(x) case TRANSMISSION_MODE_##x: return #x;
switch (t) {
CASE (2K)
CASE (8K)
CASE (AUTO)
}
return "invalid";
}
static const char *
fe_bandwidth_name (enum fe_bandwidth t)
{
#undef CASE
#define CASE(x) case BANDWIDTH_##x: return #x;
switch (t) {
CASE (8_MHZ)
CASE (7_MHZ)
CASE (6_MHZ)
CASE (AUTO)
}
return "invalid";
}
static const char *
fe_guard_interval_name (enum fe_guard_interval t)
{
#undef CASE
#define CASE(x) case GUARD_INTERVAL_##x: return #x;
switch (t) {
CASE (1_32)
CASE (1_16)
CASE (1_8)
CASE (1_4)
CASE (AUTO)
}
return "invalid";
}
static const char *
fe_hierarchy_name (enum fe_hierarchy t)
{
#undef CASE
#define CASE(x) case HIERARCHY_##x: return #x;
switch (t) {
CASE (NONE)
CASE (1)
CASE (2)
CASE (4)
CASE (AUTO)
}
return "invalid";
}
#undef CASE
static vbi_bool
same_transponder (struct station * s1,
struct station * s2)
{
if (s1->frequency != s2->frequency)
return FALSE;
if (s1->type != s2->type)
return FALSE;
switch (s1->type) {
case FE_ATSC:
if (s1->u.atsc.modulation != s1->u.atsc.modulation)
return FALSE;
break;
case FE_OFDM:
if (s1->u.dvb_t.inversion != s2->u.dvb_t.inversion ||
s1->u.dvb_t.bandwidth != s2->u.dvb_t.bandwidth ||
s1->u.dvb_t.code_rate_HP != s2->u.dvb_t.code_rate_HP ||
s1->u.dvb_t.code_rate_LP != s2->u.dvb_t.code_rate_LP ||
s1->u.dvb_t.constellation != s2->u.dvb_t.constellation ||
s1->u.dvb_t.transm_mode != s2->u.dvb_t.transm_mode ||
s1->u.dvb_t.guard_interval != s2->u.dvb_t.guard_interval ||
s1->u.dvb_t.hierarchy != s2->u.dvb_t.hierarchy)
return FALSE;
break;
case FE_QPSK:
case FE_QAM:
assert (0);
}
return TRUE;
}
/* We use a FIFO because the caption decoder may block for
extended periods due to disk activity, and buffering in
the kernel driver is unreliable in my experience. */
static void
ct_filter (const uint8_t buf[188])
{
unsigned int in;
unsigned int out;
unsigned int free;
/* Supposedly all modern CPUs read and write ints
atomically so we can avoid the mutex locking
overhead. */
in = ts_buffer_in;
out = ts_buffer_out;
assert (in < ts_buffer_capacity);
assert (out < ts_buffer_capacity);
if (likely (0 == (buf[1] & 0x80))) {
unsigned int pid;
pid = (buf[1] * 256 + buf[2]) & 0x1FFF;
if (0) {
fprintf (stderr, "CT TS 0x%04x = %u\n", pid, pid);
return;
}
if (pid_map[pid].program < 0) {
if (NULL == option_ts_all_tap_file_name)
return;
}
}
free = out - in;
if (out <= in)
free += ts_buffer_capacity;
if (unlikely (free <= 188)) {
ts_error = 0x80;
return;
}
memcpy (ts_buffer + in, buf, 188);
ts_buffer[in + 1] |= ts_error;
ts_error = 0;
++ts_n_packets_in;
in += 188;
if (in >= ts_buffer_capacity)
in = 0;
ts_buffer_in = in;
/* Hm. This delays the output. */
if (1 || free < (ts_buffer_capacity / 2)) {
pthread_cond_signal (&dx_cond);
}
}
static unsigned int
ct_resync (const uint8_t buf[2 * 188])
{
unsigned int i;
for (i = 1; i < 188; ++i) {
if (0x47 == buf[i] && 0x47 == buf[i + 188])
return i;
}
log (1, "Capture thread cannot synchronize.\n");
capture_thread_id = 0;
pthread_exit (NULL);
return 0;
}
static vbi_bool
ct_read (uint8_t * buffer,
ssize_t todo)
{
unsigned int retry = 100;
do {
ssize_t actual;
actual = read (dvr_fd, buffer, todo);
if (likely (actual == todo))
return TRUE;
if (actual > 0) {
if (unlikely (actual >= todo)) {
log (1, "DVB device read size "
"error.\n");
return FALSE;
}
buffer += actual;
todo -= actual;
continue;
} else if (actual < 0) {
int saved_errno;
saved_errno = errno;
if (EINTR == saved_errno)
continue;
log_errno (1, "DVB device read error");
errno = saved_errno;
return FALSE;
} else {
log (2, "EOF from DVB device (ignored).\n");
return FALSE;
}
} while (--retry > 0);
log (2, "DVB device read error: EINTR or "
"read size problem.\n");
errno = EINTR;
return FALSE;
}
static void *
capture_thread (void * arg)
{
uint8_t *start;
uint8_t *end;
const uint8_t *s;
const uint8_t *e;
unsigned int left;
ssize_t size;
arg = arg; /* unused */
log (2, "Capture thread ready.\n");
/* Don't swap out any code or data pages. If the capture
thread is delayed we may lose packets. Errors ignored. */
mlockall (MCL_CURRENT | MCL_FUTURE);
pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, /* old */ NULL);
pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, /* old */ NULL);
/* Page aligned reads are not required, but who knows, it
may allow some DMA magic or speed up copying. */
start = ct_buffer + 4096;
size = ct_buffer_capacity - 4096;
left = 0;
assert (ct_buffer_capacity > 4096);
assert (0 == ct_buffer_capacity % 4096);
xioctl (dmx_fd, DMX_START, 0);
for (;;) {
if (!ct_read (start, size))
continue;
ct_n_bytes_in += size;
end = start + size;
s = start - left;
e = end - 4096;
while (s < e) {
if (0x47 != s[0] || 0x47 != s[188]) {
if (ts_n_packets_in > 0) {
log (2, "Capture thread "
"lost sync.\n");
}
s += ct_resync (s);
}
if (0) {
fputc ('.', stderr);
fflush (stderr);
} else {
ct_filter (s);
}
s += 188;
}
left = end - s;
memcpy (start - left, s, left);
}
return NULL;
}
static void
destroy_capture_state (void)
{
/* FIXME this a glibc feature. */
free (ct_buffer);
ct_buffer = NULL;
ct_buffer_capacity = 0;
}
static void
init_capture_state (void)
{
ct_buffer_capacity = 32 * 1024;
ct_buffer = memalign (4096, ct_buffer_capacity);
if (NULL == ct_buffer) {
no_mem_exit ();
}
/* Copy-on-write and lock the pages. */
memset (ct_buffer, -1, ct_buffer_capacity);
ct_n_bytes_in = 0;
}
static int
xopen_device (const char * dev_name,
int flags)
{
struct stat st;
int fd;
if (-1 == stat (dev_name, &st)) {
goto open_failed;
}
if (!S_ISCHR (st.st_mode)) {
error_exit ("'%s' is not a DVB device.\n",
dev_name);
}
fd = open (dev_name, flags, /* mode */ 0);
if (-1 == fd) {
goto open_failed;
}
return fd;
open_failed:
errno_exit ("Cannot open '%s'", dev_name);
return -1; /* not reached */
}
static void
close_device (void)
{
log (2, "Closing DVB device.\n");
if (0 != capture_thread_id) {
pthread_cancel (capture_thread_id);
pthread_join (capture_thread_id, NULL);
capture_thread_id = 0;
}
destroy_capture_state ();
if (-1 != dmx_fd) {
close (dmx_fd);
dmx_fd = -1;
}
if (-1 != dvr_fd) {
close (dvr_fd);
dvr_fd = -1;
}
if (-1 != fe_fd) {
close (fe_fd);
fe_fd = -1;
}
}
static void
open_device (void)
{
struct dvb_frontend_info fe_info;
struct dvb_frontend_parameters fe_param;
struct dmx_pes_filter_params filter;
char *dev_name;
unsigned int retry;
dev_name = NULL;
init_capture_state ();
/* Front end. */
log (2, "Opening dvb/adapter%lu.\n",
option_dvb_adapter_num);
dev_name = xasprintf ("/dev/dvb/adapter%lu/frontend%lu",
option_dvb_adapter_num,
option_dvb_frontend_id);
fe_fd = xopen_device (dev_name, O_RDWR);
CLEAR (fe_info);
xioctl (fe_fd, FE_GET_INFO, &fe_info);
switch (fe_info.type) {
case FE_ATSC:
case FE_OFDM:
if (fe_info.type != station->type) {
error_exit ("'%s' is not %s device.\n",
dev_name,
(FE_ATSC == station->type) ?
"an ATSC" : "a DVB-T");
}
break;
case FE_QPSK:
case FE_QAM:
error_exit ("'%s' is not an ATSC device.\n",
dev_name);
break;
}
CLEAR (fe_param);
fe_param.frequency = station->frequency;
switch (fe_info.type) {
case FE_ATSC:
fe_param.u.vsb.modulation =
station->u.atsc.modulation;
break;
case FE_OFDM:
fe_param.inversion =
station->u.dvb_t.inversion;
fe_param.u.ofdm.bandwidth =
station->u.dvb_t.bandwidth;
fe_param.u.ofdm.code_rate_HP =
station->u.dvb_t.code_rate_HP;
fe_param.u.ofdm.code_rate_LP =
station->u.dvb_t.code_rate_LP;
fe_param.u.ofdm.constellation =
station->u.dvb_t.constellation;
fe_param.u.ofdm.transmission_mode =
station->u.dvb_t.transm_mode;
fe_param.u.ofdm.guard_interval =
station->u.dvb_t.guard_interval;
fe_param.u.ofdm.hierarchy_information =
station->u.dvb_t.hierarchy;
break;
case FE_QPSK:
case FE_QAM:
assert (0);
}
xioctl (fe_fd, FE_SET_FRONTEND, &fe_param);
for (retry = 0;;) {
fe_status_t status;
xioctl (fe_fd, FE_READ_STATUS, &status);
if (status & FE_HAS_LOCK)
break;
if (++retry > 20) {
error_exit ("No signal detected.\n");
}
if (7 == (retry & 7)) {
log (2, "Waiting for a signal.\n");
}
usleep (250000);
}
log (2, "Signal detected.\n");
free (dev_name);
/* DVR. */
dev_name = xasprintf ("/dev/dvb/adapter%lu/dvr%lu",
option_dvb_adapter_num,
option_dvb_dvr_id);
dvr_fd = xopen_device (dev_name, O_RDONLY);
/* Not implemented? Let's try anyway. */
xioctl_may_fail (dvr_fd, DMX_SET_BUFFER_SIZE, (void *)(4 << 20));
free (dev_name);
/* Demultiplexer. */
dev_name = xasprintf ("/dev/dvb/adapter%lu/demux%lu",
option_dvb_adapter_num,
option_dvb_demux_id);
dmx_fd = xopen_device (dev_name, O_RDWR);
xioctl_may_fail (dmx_fd, DMX_SET_BUFFER_SIZE, (void *)(4 << 20));
CLEAR (filter);
/* We capture the entire transport multiplex so we can receive
all stations on this transponder at once and properly handle
transmission errors in the video ES demultiplexer. */
filter.pid = 0x2000;
filter.input = DMX_IN_FRONTEND;
filter.output = DMX_OUT_TS_TAP;
filter.pes_type = DMX_PES_OTHER;
xioctl (dmx_fd, DMX_SET_PES_FILTER, &filter);
free (dev_name);
/* Start capture thread. */
if (0 != pthread_create (&capture_thread_id,
/* attr */ NULL,
capture_thread,
/* arg */ NULL)) {
errno_exit ("Cannot start capture thread");
}
log (2, "Opened dvb/adapter%lu, tuned to %.3f MHz and "
"started capture thread.\n",
option_dvb_adapter_num,
station->frequency / 1e6);
}
static enum fe_type
device_type (void)
{
struct dvb_frontend_info fe_info;
char *dev_name;
int fd;
dev_name = xasprintf ("/dev/dvb/adapter%lu/frontend%lu",
option_dvb_adapter_num,
option_dvb_frontend_id);
fd = xopen_device (dev_name, O_RDWR);
CLEAR (fe_info);
xioctl (fd, FE_GET_INFO, &fe_info);
close (fd);
switch (fe_info.type) {
case FE_ATSC:
case FE_OFDM:
break;
case FE_QPSK:
case FE_QAM:
error_exit ("'%s' is not an ATSC device.\n",
dev_name);
break;
}
free (dev_name);
return fe_info.type;
}
static void
list_stations (void)
{
const struct station *st;
size_t max_len;
if (NULL == station_list) {
printf ("The channel config file is empty.\n");
return;
}
/* FIXME the encoding of station names is unknown,
could be a multi-byte coding like UTF-8. */
max_len = 0;
for (st = station_list; NULL != st; st = st->next) {
size_t len;
len = strlen (st->name);
max_len = MAX (max_len, len);
}
for (st = station_list; NULL != st; st = st->next) {
printf ("%-*s %3.3f MHz\n",
(int) max_len,
st->name,
st->frequency / 1e6);
}
}
static struct station *
find_station (const char * station_name)
{
struct station *st;
for (st = station_list; NULL != st; st = st->next) {
if (0 == strcmp (station_name, st->name))
return st;
}
return NULL;
}
static char *
parse_station_name (const char ** sp,
int delimiter)
{
const char *s;
const char *s_name;
char *station_name;
size_t len;
s = *sp;
while (isspace (*s))
++s;
s_name = s;
while (0 != *s && delimiter != *s)
++s;
*sp = s;
while (s > s_name && isspace (s[-1]))
--s;
len = s - s_name;
if (0 == len)
return NULL;
station_name = xmalloc (len + 1);
memcpy (station_name, s_name, len);
station_name[len] = 0;
return station_name;
}
struct key_value {
const char * key;
int value;
};
static vbi_bool
parse_enum (int * value,
const char ** sp,
const struct key_value *table)
{
const char *s;
unsigned int i;
s = *sp;
while (isspace (*s))
++s;
for (i = 0; NULL != table[i].key; ++i) {
size_t len = strlen (table[i].key);
if (0 == strncmp (s, table[i].key, len)) {
s += len;
break;
}
}
if (NULL == table[i].key)
return FALSE;
while (isspace (*s))
++s;
if (':' != *s++)
return FALSE;
*value = table[i].value;
*sp = s;
return TRUE;
}
static void
parse_tzap_channel_conf_line (const char * filename,
unsigned int line_number,
const char * buffer)
{
static const struct key_value inversion [] = {
{ "INVERSION_OFF", INVERSION_OFF },
{ "INVERSION_ON", INVERSION_ON },
{ "INVERSION_AUTO", INVERSION_AUTO },
{ NULL, 0 }
};
static const struct key_value bandwidth [] = {
{ "BANDWIDTH_6_MHZ", BANDWIDTH_6_MHZ },
{ "BANDWIDTH_7_MHZ", BANDWIDTH_7_MHZ },
{ "BANDWIDTH_8_MHZ", BANDWIDTH_8_MHZ },
{ NULL, 0 }
};
static const struct key_value fec [] = {
{ "FEC_1_2", FEC_1_2 },
{ "FEC_2_3", FEC_2_3 },
{ "FEC_3_4", FEC_3_4 },
{ "FEC_4_5", FEC_4_5 },
{ "FEC_5_6", FEC_5_6 },
{ "FEC_6_7", FEC_6_7 },
{ "FEC_7_8", FEC_7_8 },
{ "FEC_8_9", FEC_8_9 },
{ "FEC_AUTO", FEC_AUTO },
{ "FEC_NONE", FEC_NONE },
{ NULL, 0 }
};
static const struct key_value constellation [] = {
{ "QPSK", QPSK },
{ "QAM_16", QAM_16 },
{ "QAM_32", QAM_32 },
{ "QAM_64", QAM_64 },
{ "QAM_128", QAM_128 },
{ "QAM_256", QAM_256 },
{ NULL, 0 }
};
static const struct key_value transmission_mode [] = {
{ "TRANSMISSION_MODE_2K", TRANSMISSION_MODE_2K },
{ "TRANSMISSION_MODE_8K", TRANSMISSION_MODE_8K },
{ NULL, 0 }
};
static const struct key_value guard_interval [] = {
{"GUARD_INTERVAL_1_16", GUARD_INTERVAL_1_16},
{"GUARD_INTERVAL_1_32", GUARD_INTERVAL_1_32},
{"GUARD_INTERVAL_1_4", GUARD_INTERVAL_1_4},
{"GUARD_INTERVAL_1_8", GUARD_INTERVAL_1_8},
{ NULL, 0 }
};
static const struct key_value hierarchy [] = {
{ "HIERARCHY_1", HIERARCHY_1 },
{ "HIERARCHY_2", HIERARCHY_2 },
{ "HIERARCHY_4", HIERARCHY_4 },
{ "HIERARCHY_NONE", HIERARCHY_NONE },
{ NULL, 0 }
};
struct station *st;
struct station **stp;
const struct station *st2;
const char *detail;
const char *s;
char *s_end;
int value;
/* TZAP channel config file format:
A number of lines with the fields:
1. Station name (encoding?)
2. Transponder frequency in Hz
3. Inversion: INVERSION_(ON|OFF|AUTO)
4. Bandwidth: BANDWIDTH_(6|7|8)_MHZ
5. Code rate HP: FEC_(1_2|2_3|3_4|4_5|5_6|6_7|7_8|8_9|AUTO|NONE)
6. Code rate LP: as above
7. Constellation: QPSK, QAM_(16|32|64|128|256)
8. Transmission mode: TRANSMISSION_MODE_(2K|8K)
9. Guard interval: GUARD_INTERVAL_1_(4|8|16|32)
10. Hierarchy information: HIERARCHY_(1|2|4|NONE)
11. Video stream PID
12. Audio stream PID
13. ?
The fields are separated by one colon. We skip whitespace
at the beginning of a line, whitespace before and after
colons, empty lines, and lines starting with a number
sign. */
st = xmalloc (sizeof (*st));
CLEAR (*st);
s = buffer;
while (isspace (*s))
++s;
if (0 == *s || '#' == *s)
return;
detail = "station name";
st->name = parse_station_name (&s, /* delimiter */ ':');
if (NULL == st->name)
goto invalid;
if (':' != *s++)
goto invalid;
st2 = find_station (st->name);
if (NULL != st2) {
error_exit ("Duplicate station name '%s' "
"in %s line %u.\n",
st->name, filename, line_number);
}
st->type = FE_OFDM;
detail = "frequency";
/* NB. strtoul() skips leading whitespace. */
st->frequency = strtoul (s, &s_end, 0);
if (s_end == s || st->frequency < 1)
goto invalid;
s = s_end;
while (isspace (*s))
++s;
if (':' != *s++)
goto invalid;
detail = "inversion";
if (!parse_enum (&value, &s, inversion))
goto invalid;
st->u.dvb_t.inversion = value;
detail = "bandwidth";
if (!parse_enum (&value, &s, bandwidth))
goto invalid;
st->u.dvb_t.bandwidth = value;
detail = "code rate HP";
if (!parse_enum (&value, &s, fec))
goto invalid;
st->u.dvb_t.code_rate_HP = value;
detail = "code rate LP";
if (!parse_enum (&value, &s, fec))
goto invalid;
st->u.dvb_t.code_rate_LP = value;
detail = "constellation";
if (!parse_enum (&value, &s, constellation))
goto invalid;
st->u.dvb_t.constellation = value;
detail = "transmission_mode";
if (!parse_enum (&value, &s, transmission_mode))
goto invalid;
st->u.dvb_t.transm_mode = value;
detail = "guard_interval";
if (!parse_enum (&value, &s, guard_interval))
goto invalid;
st->u.dvb_t.guard_interval = value;
detail = "hierarchy";
if (!parse_enum (&value, &s, hierarchy))
goto invalid;
st->u.dvb_t.hierarchy = value;
detail = "video PID";
st->video_pid = strtoul (s, &s_end, 0);
if (s_end == s || (unsigned int) st->video_pid > 0x1FFE)
goto invalid;
s = s_end;
while (isspace (*s))
++s;
if (':' != *s++)
goto invalid;
detail = "audio PID";
st->audio_pid = strtoul (s, &s_end, 0);
if (s_end == s || (unsigned int) st->audio_pid > 0x1FFE)
goto invalid;
s = s_end;
while (isspace (*s))
++s;
if (':' != *s++)
goto invalid;
if (0 == st->video_pid) {
if (option_debug & DEBUG_CONFIG) {
fprintf (stderr, "Skipping radio station '%s'.\n",
st->name);
}
free (st->name);
free (st);
return;
}
if (option_debug & DEBUG_CONFIG) {
fprintf (stderr, "%3u: station_name='%s' frequency=%lu "
"inversion=%s bandwidth=%s code_rate=%s/%s "
"constellation=%s transm_mode=%s "
"guard_interval=%s hierarchy=%s "
"video_pid=%u audio_pid=%u.\n",
line_number,
st->name,
st->frequency,
fe_spectral_inversion_name (st->u.dvb_t.inversion),
fe_bandwidth_name (st->u.dvb_t.bandwidth),
fe_code_rate_name (st->u.dvb_t.code_rate_HP),
fe_code_rate_name (st->u.dvb_t.code_rate_LP),
fe_modulation_name (st->u.dvb_t.constellation),
fe_transmit_mode_name (st->u.dvb_t.transm_mode),
fe_guard_interval_name (st->u.dvb_t.guard_interval),
fe_hierarchy_name (st->u.dvb_t.hierarchy),
st->video_pid,
st->audio_pid);
}
/* Append to station list. */
for (stp = &station_list; NULL != *stp; stp = &(*stp)->next)
;
*stp = st;
return;
invalid:
error_exit ("Invalid %s field in '%s' line %u.\n",
detail, filename, line_number);
}
static void
parse_azap_channel_conf_line (const char * filename,
unsigned int line_number,
const char * buffer)
{
static const struct key_value modulations [] = {
{ "8VSB", VSB_8 },
{ "16VSB", VSB_16 },
{ "QAM_64", QAM_64 },
{ "QAM_256", QAM_256 },
{ NULL, 0 }
};
struct station *st;
struct station **stp;
const struct station *st2;
const char *detail;
const char *s;
char *s_end;
int value;
/* AZAP channel config file format:
A number of lines with the fields:
1. Station name (encoding?)
2. Transponder frequency in Hz
3. Modulation: 8VSB, 16VSB, QAM_64, QAM_256
4. Video stream PID
5. Audio stream PID
6. ?
The fields are separated by one colon. We skip whitespace
at the beginning of a line, whitespace before and after
colons, empty lines, and lines starting with a number
sign. */
st = xmalloc (sizeof (*st));
CLEAR (*st);
s = buffer;
while (isspace (*s))
++s;
if (0 == *s || '#' == *s)
return;
detail = "station name";
st->name = parse_station_name (&s, /* delimiter */ ':');
if (NULL == st->name)
goto invalid;
if (':' != *s++)
goto invalid;
st2 = find_station (st->name);
if (NULL != st2) {
error_exit ("Duplicate station name '%s' "
"in %s line %u.\n",
st->name, filename, line_number);
}
st->type = FE_ATSC;
detail = "frequency";
/* NB. strtoul() skips leading whitespace. */
st->frequency = strtoul (s, &s_end, 0);
if (s_end == s || st->frequency < 1)
goto invalid;
s = s_end;
while (isspace (*s))
++s;
if (':' != *s++)
goto invalid;
detail = "modulation";
if (!parse_enum (&value, &s, modulations))
goto invalid;
st->u.atsc.modulation = value;
detail = "video PID";
st->video_pid = strtoul (s, &s_end, 0);
if (s_end == s || (unsigned int) st->video_pid > 0x1FFE)
goto invalid;
s = s_end;
while (isspace (*s))
++s;
if (':' != *s++)
goto invalid;
detail = "audio PID";
st->audio_pid = strtoul (s, &s_end, 0);
if (s_end == s || (unsigned int) st->audio_pid > 0x1FFE)
goto invalid;
s = s_end;
while (isspace (*s))
++s;
if (':' != *s++)
goto invalid;
if (0 == st->video_pid) {
if (option_debug & DEBUG_CONFIG) {
fprintf (stderr, "Skipping radio station '%s'.\n",
st->name);
}
free (st->name);
free (st);
return;
}
if (option_debug & DEBUG_CONFIG) {
fprintf (stderr, "%3u: station_name='%s' frequency=%lu "
"modulation=%s video_pid=%u audio_pid=%u.\n",
line_number,
st->name,
st->frequency,
fe_modulation_name (st->u.atsc.modulation),
st->video_pid,
st->audio_pid);
}
/* Append to station list. */
for (stp = &station_list; NULL != *stp; stp = &(*stp)->next)
;
*stp = st;
return;
invalid:
error_exit ("Invalid %s field in '%s' line %u.\n",
detail, filename, line_number);
}
static char *
get_channel_conf_name (enum fe_type type)
{
const char *home;
const char *s;
s = option_channel_conf_file_name;
if (NULL == s) {
switch (type) {
case FE_ATSC:
s = "~/.azap/channels.conf";
break;
case FE_OFDM:
s = "~/.tzap/channels.conf";
break;
case FE_QPSK:
case FE_QAM:
assert (0);
break;
}
}
if (0 == strncmp (s, "~/", 2)) {
home = getenv ("HOME");
if (NULL == home) {
error_exit ("Cannot open '%s' because the "
"HOME environment variable "
"is unset.\n",
s);
}
++s;
} else {
home = "";
}
return xasprintf ("%s%s", home, s);
}
static void
read_channel_conf (void)
{
char buffer[256];
char *channel_conf_name;
enum fe_type type;
unsigned int line_number;
FILE *fp;
if (NULL != station_list)
return;
type = option_dvb_type;
if ((enum fe_type) -1 == option_dvb_type)
type = device_type ();
channel_conf_name = get_channel_conf_name (type);
fp = fopen (channel_conf_name, "r");
if (NULL == fp) {
errno_exit ("Cannot open '%s'",
channel_conf_name);
}
if (option_debug & DEBUG_CONFIG) {
fprintf (stderr, "Opened '%s' (%s):\n",
channel_conf_name,
fe_type_name (type));
}
line_number = 1;
while (NULL != fgets (buffer, sizeof (buffer), fp)) {
const char *s;
s = buffer;
while (isspace (*s))
++s;
if (0 == *s || '#' == *s)
continue;
switch (type) {
case FE_ATSC:
parse_azap_channel_conf_line (channel_conf_name,
line_number,
buffer);
break;
case FE_OFDM:
parse_tzap_channel_conf_line (channel_conf_name,
line_number,
buffer);
break;
case FE_QPSK:
case FE_QAM:
assert (0);
}
++line_number;
}
if (ferror (fp) || 0 != fclose (fp)) {
errno_exit ("Error while reading '%s'",
channel_conf_name);
}
free (channel_conf_name);
}
static void
usage (FILE * fp)
{
fprintf (fp, "\
" PROGRAM " " VERSION " -- ATSC Closed Caption and XDS decoder\n\
Copyright (C) 2008 Michael H. Schimek <[email protected]>\n\
Based on code by Mike Baker, Mark K. Kim and [email protected].\n\
This program is licensed under GPL 2 or later. NO WARRANTIES.\n\n\
Usage: %s [options] [-n] station name\n\
Options:\n\
-? | -h | --help | --usage Print this message, then terminate\n\
-1 ... -4 | --cc1-file ... --cc4-file file name\n\
Append CC1 ... CC4 to this file\n\
-5 ... -8 | --t1-file ... --t4-file file name\n\
Append T1 ... T4 to this file\n\
-9 ... -0 | --s1-file ... --s2-file file name\n\
Append DTVCC service 1 ... 2 to this file\n\
-a | --adapter-num number DVB device adapter [%lu]\n\
-b | --no-webtv Do not print WebTV links\n\
-c | --cc Print Closed Caption (includes WebTV)\n\
-d | --demux-id number DVB device demultiplexer [%lu]\n\
-e | --channel-conf file name Channel config. file [~/.azap/channels.conf]\n\
-f | --filter type[,type]* Select XDS info: all, call, desc, length,\n\
network, rating, time, timecode, timezone,\n\
title. Multiple -f options accumulate. [all]\n\
-i | --frontend-id number DVB device frontend [%lu]\n\
-j | --format type Print caption in 'plain' encoding, with\n\
'vt100' control codes or like the 'ntsc-cc'\n\
tool [ntsc-cc].\n\
-l | --channel number Select caption channel 1 ... 4 [nothing]\n\
-m | --timestamps Prepend timestamps to caption lines\n\
-n | --station name Station name. Usually the -n can be omitted\n\
-q | --quiet Suppress all progress and error messages\n\
-p | --plain Same as -j plain.\n\
-r | --dvr-id number DVB device dvr [%lu]\n\
-s | --sentences Decode caption by sentences\n\
-v | --verbose Increase verbosity\n\
-x | --xds Print XDS info\n\
-C | --cc-file file name Append all caption to this file [stdout]\n\
-L | --list List all TV stations in the channel\n\
configuration file\n\
-T | --ts Decode a DVB Transport Stream on stdin\n\
instead of opening a DVB device\n\
-X | --xds-file file name Append XDS info to this file [stdout]\n\
\n\
To record data from multiple stations sharing a transponder frequency\n\
you can specify caption options and a station name repeatedly.\n\
",
my_name,
option_dvb_adapter_num,
option_dvb_demux_id,
option_dvb_frontend_id,
option_dvb_dvr_id);
}
static const char
short_options [] = ("-?1:2:3:4:5:6:7:8:9:0:"
"a:bcd:e:f:hi:j:l:mn:pr:svx"
"C:DELM:PTX:");
#ifdef HAVE_GETOPT_LONG
static const struct option
long_options [] = {
{ "help", no_argument, NULL, '?' },
/* From ntsc-cc.c. */
{ "cc1-file", required_argument, NULL, '1' },
{ "cc2-file", required_argument, NULL, '2' },
{ "cc3-file", required_argument, NULL, '3' },
{ "cc4-file", required_argument, NULL, '4' },
/* From ntsc-cc.c. */
{ "t1-file", required_argument, NULL, '5' },
{ "t2-file", required_argument, NULL, '6' },
{ "t3-file", required_argument, NULL, '7' },
{ "t4-file", required_argument, NULL, '8' },
{ "s1-file", required_argument, NULL, '9' },
{ "s2-file", required_argument, NULL, '0' },
{ "adapter-num", required_argument, NULL, 'a' },
/* From ntsc-cc.c. */
{ "no-webtv", no_argument, NULL, 'b' },
/* From ntsc-cc.c. */
{ "cc", no_argument, NULL, 'c' },
{ "demux-id", required_argument, NULL, 'd' },
{ "conf-file", required_argument, NULL, 'e' },
/* From ntsc-cc.c. */
{ "filter", required_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' },
{ "usage", no_argument, NULL, 'h' },
{ "frontend-id", required_argument, NULL, 'i' },
{ "format", required_argument, NULL, 'j' },
{ "channel", required_argument, NULL, 'l' },
{ "timestamps", no_argument, NULL, 'm' },
{ "station", required_argument, NULL, 'n' },
/* From ntsc-cc.c. */
{ "plain", no_argument, NULL, 'p' },
/* From ntsc-cc.c. Actually the output was never limited to ASCII. */
{ "plain-ascii", no_argument, NULL, 'p' },
{ "quiet", no_argument, NULL, 'q' },
{ "dvr-id", required_argument, NULL, 'r' },
/* From ntsc-cc.c. */
{ "sentences", no_argument, NULL, 's' },
{ "verbose", no_argument, NULL, 'v' },
/* From ntsc-cc.c. */
{ "xds", no_argument, NULL, 'x' },
/* From ntsc-cc.c. */
{ "cc-file", required_argument, NULL, 'C' },
/* 'E' - video elementary stream? */
{ "list", no_argument, NULL, 'L' },
{ "minicut", required_argument, NULL, 'M' },
{ "pes", no_argument, NULL, 'P' },
{ "ts", no_argument, NULL, 'T' },
/* From ntsc-cc.c. */
{ "xds-file", required_argument, NULL, 'X' },
/* Test options, may change. */
{ "atsc", no_argument, NULL, 301 },
{ "dvb-t", no_argument, NULL, 302 },
{ "ts-all-tap", required_argument, NULL, 303 },
{ "ts-tap", required_argument, NULL, 304 },
{ "video-all-tap", required_argument, NULL, 305 },
{ "video-tap", required_argument, NULL, 306 },
{ "cc-data-tap", required_argument, NULL, 308 },
{ "debug", required_argument, NULL, 309 },
{ "mtest", no_argument, NULL, 310 },
{ "cc-data", no_argument, NULL, 311 },
{ "es", no_argument, NULL, 312 },
{ 0, 0, 0, 0 }
};
#else
# define getopt_long(ac, av, s, l, i) getopt(ac, av, s)
#endif
static int option_index;
static void
list_programs (void)
{
const unsigned int ll = 1; /* log level */
unsigned int i;
for (i = 0; i < n_programs; ++i) {
struct program *pr;
unsigned int j;
pr = &program_table[i];
log (ll, "Station %u: '%s'\n",
station_num (pr),
pr->option_station_name);
for (j = 0; j < 10; ++j) {
const char *stream_name [10] = {
"NTSC CC1", "NTSC CC2",
"NTSC CC3", "NTSC CC4",
"NTSC T1", "NTSC T2",
"NTSC T3", "NTSC T4",
"ATSC S1", "ATSC S2"
};
const char *file_name;
if (0 == (pr->cr.option_caption_mask & (1 << j)))
continue;
file_name = pr->cr.option_caption_file_name[j];
if (NULL != file_name) {
log (ll, " %s -> '%s'\n",
stream_name[j], file_name);
} else if (NULL != pr->option_minicut_dir_name) {
log (ll, " %s -> '%s/"
"YYYYMMDDHH0000/"
"YYYYMMDDHHMM00%s.txt'\n",
stream_name[j],
pr->option_minicut_dir_name,
cr_file_name_suffix[j]);
}
}
if (pr->cr.usexds) {
log (ll, " XDS -> '%s'\n",
pr->cr.option_xds_output_file_name);
}
if (NULL != pr->option_minicut_dir_name) {
log (ll, " TS -> '%s/"
"YYYYMMDDHH0000/"
"YYYYMMDDHHMM00.ts'\n",
pr->option_minicut_dir_name);
}
if (NULL != pr->vesd.video_es_tap_fp) {
const char *name;
name = pr->vesd.option_video_es_all_tap_file_name;
if (NULL == name)
name = pr->vesd.option_video_es_tap_file_name;
log (ll, " V-ES -> '%s'\n",
name);
}
if (NULL != pr->vr.aesp.audio_es_tap_fp) {
log (ll, " A-ES -> '%s'\n",
pr->vr.aesp.option_audio_es_tap_file_name);
}
if (NULL != pr->cr.ccd.cc_data_tap_fp) {
log (ll, " cc_data -> '%s'\n",
pr->cr.ccd.option_cc_data_tap_file_name);
}
}
}
static void
cr_open_xds_output_file (struct caption_recorder *cr)
{
cr->xds_fp = open_output_file (cr->option_xds_output_file_name);
}
static void
cr_open_caption_output_files (struct caption_recorder *cr)
{
unsigned int i;
for (i = 0; i < 10; ++i) {
const char *name;
name = cr->option_caption_file_name[i];
if (cr->option_caption_mask & (1 << i)
&& NULL != name) {
cr->caption_fp[i] = open_output_file (name);
}
}
}
static void
open_output_files (void)
{
unsigned int i;
for (i = 0; i < n_programs; ++i) {
struct program *pr;
pr = &program_table[i];
if (pr->cr.usecc)
cr_open_caption_output_files (&pr->cr);
if (pr->cr.usexds)
cr_open_xds_output_file (&pr->cr);
}
}
static void
look_up_station_names (void)
{
unsigned int i;
read_channel_conf ();
for (i = 0; i < n_programs; ++i) {
struct program *pr;
struct station *st;
pr = &program_table[i];
st = find_station (pr->option_station_name);
if (NULL == st) {
error_exit ("Station '%s' is unknown. List "
"all stations with the -L option.\n",
pr->option_station_name);
}
if (NULL == station) {
station = st;
} else if (!same_transponder (st, station)) {
error_exit ("To receive multiple programs the "
"stations must share one "
"transponder frequency.\n");
}
pr->tsd.pid[0] = st->video_pid;
pr->tsd.pid[1] = st->audio_pid;
assert (st->video_pid <= N_ELEMENTS (pid_map));
assert (st->audio_pid <= N_ELEMENTS (pid_map));
pid_map[st->video_pid].program = i;
pid_map[st->audio_pid].program = i;
}
}
static void
finish_program_setup (struct program * pr,
vbi_bool have_cc_filter_option,
vbi_bool have_xds_filter_option)
{
if (NULL != pr->option_minicut_dir_name) {
pr->cr.usecc = TRUE;
if (0 == pr->cr.option_caption_mask)
pr->cr.option_caption_mask = 0x30F;
} else {
unsigned int i;
if (!(pr->cr.usecc | pr->cr.usexds)) {
error_exit ("Please give option -c or -x, "
"or -h for help.\n");
}
if (pr->cr.usecc && !have_cc_filter_option)
pr->cr.option_caption_mask = 0x001;
for (i = 0; i < 10; ++i) {
if (0 != (pr->cr.option_caption_mask & (1 << i))
&& NULL == pr->cr.option_caption_file_name[i]) {
pr->cr.option_caption_file_name[i] = "-";
}
}
}
if (pr->cr.usexds && !have_xds_filter_option)
xds_filter_option (&pr->cr, "all");
if (NULL != pr->vesd.option_video_es_all_tap_file_name) {
pr->vesd.video_es_tap_fp = open_output_file
(pr->vesd.option_video_es_all_tap_file_name);
} else if (NULL != pr->vesd.option_video_es_tap_file_name) {
pr->vesd.video_es_tap_fp = open_output_file
(pr->vesd.option_video_es_tap_file_name);
}
if (NULL != pr->vr.aesp.option_audio_es_tap_file_name) {
pr->vr.aesp.audio_es_tap_fp = open_output_file
(pr->vr.aesp.option_audio_es_tap_file_name);
}
if (NULL != pr->cr.ccd.option_cc_data_tap_file_name) {
pr->cr.ccd.cc_data_tap_fp = open_output_file
(pr->cr.ccd.option_cc_data_tap_file_name);
}
}
static unsigned int
uint_option (const char * option_name,
const char * optarg)
{
const char *s;
char *end;
unsigned long ul;
s = optarg;
while (isspace (*s))
++s;
if (!isdigit (*s))
goto failed;
ul = strtoul (optarg, &end, 0);
while (isspace (*end))
++end;
if (0 == *end && ul < UINT_MAX)
return ul;
failed:
error_exit ("Invalid %s '%s'.\n", option_name, optarg);
return 0;
}
static void
format_option (struct caption_recorder *cr,
const char * optarg)
{
static const struct {
const char * name;
enum caption_format format;
} formats [] = {
{ "plain", FORMAT_PLAIN },
{ "vt100", FORMAT_VT100 },
{ "ntsc-cc", FORMAT_NTSC_CC }
};
unsigned int i;
for (i = 0; i < N_ELEMENTS (formats); ++i) {
if (NULL != optarg
&& 0 == strcmp (optarg, formats[i].name)) {
cr->option_caption_format = formats[i].format;
break;
}
}
if (i >= N_ELEMENTS (formats)) {
error_exit ("Invalid caption format '%s'. "
"Try 'plain', 'vt100' or 'ntsc-cc'.\n",
optarg);
}
if (FORMAT_PLAIN == cr->option_caption_format) {
cr->xds_info_prefix = "% ";
cr->xds_info_suffix = "\n";
} else {
cr->xds_info_prefix = "\33[33m% ";
cr->xds_info_suffix = "\33[0m\n";
}
}
static void
debug_option (const char * optarg)
{
static const struct {
const char * name;
unsigned int flag;
} flags [] = {
{ "all", -1 },
{ "ccdata", DEBUG_CC_DATA },
{ "ccdec", DEBUG_CC_DECODER },
{ "ccf1", DEBUG_CC_F1 },
{ "ccf2", DEBUG_CC_F2 },
{ "conf", DEBUG_CONFIG },
{ "dtvccp", DEBUG_DTVCC_PACKET },
{ "dtvccpc", DEBUG_DTVCC_PUT_CHAR },
{ "dtvccse", DEBUG_DTVCC_SE },
{ "dtvccsev", DEBUG_DTVCC_STREAM_EVENT },
{ "vesdcc", DEBUG_VESD_CC_DATA },
{ "vesdpesp", DEBUG_VESD_PES_PACKET },
{ "vesdsc", DEBUG_VESD_START_CODE },
};
const char *s;
if (NULL == optarg) {
option_debug = -1;
return;
} else if (0 == strcmp (optarg, "help")) {
unsigned int i;
printf ("Debugging switches:\n");
for (i = 0; i < N_ELEMENTS (flags); ++i) {
printf (" %s\n", flags[i].name);
}
exit (EXIT_SUCCESS);
}
s = optarg;
while (isspace (*s))
++s;
while (0 != *s) {
unsigned int i;
for (i = 0; i < N_ELEMENTS (flags); ++i) {
const char *t;
const char *u;
t = s;
u = flags[i].name;
for (;;) {
if (0 == *t || ',' == *t || ' ' == *t) {
if (0 != *u)
break;
option_debug |= flags[i].flag;
s = t;
goto next_item;
} else if ('-' == *t || '_' == *t) {
++t;
continue;
} else if (*t++ != *u++) {
break;
}
}
}
error_exit ("Invalid debugging switch '%s'. "
"Try --debug help.\n", optarg);
next_item:
while (',' == *s || isspace (*s))
++s;
}
}
static struct program *
add_program (void)
{
struct program *pr;
if (n_programs >= N_ELEMENTS (program_table)) {
error_exit ("Sorry, too many programs.\n");
}
pr = &program_table[n_programs++];
init_program (pr);
return pr;
}
static void
parse_args (int argc,
char ** argv)
{
struct program *pr;
vbi_bool have_cc_filter_option;
vbi_bool have_xds_filter_option;
unsigned int n_program_options;
option_source = SOURCE_DVB_DEVICE;
option_dvb_type = -1; /* query device */
option_dvb_adapter_num = 0;
option_dvb_frontend_id = 0;
option_dvb_demux_id = 0;
option_dvb_dvr_id = 0;
option_verbosity = 1;
pr = add_program ();
format_option (&pr->cr, "ntsc-cc");
have_xds_filter_option = FALSE;
have_cc_filter_option = FALSE;
n_program_options = 0;
for (;;) {
int c;
c = getopt_long (argc, argv, short_options,
long_options, &option_index);
if (-1 == c)
break;
switch (c) {
const char *name;
unsigned int i;
long ch;
case '?':
case 'h':
usage (stdout);
exit (EXIT_SUCCESS);
case '0' ... '9':
assert (NULL != optarg);
if ('0' == c)
i = 9;
else
i = c - '1';
pr->cr.option_caption_file_name[i] = optarg;
pr->cr.option_caption_mask |= 1 << i;
have_cc_filter_option = TRUE;
++n_program_options;
pr->cr.usecc = 1;
break;
case 'a':
assert (NULL != optarg);
option_dvb_adapter_num =
uint_option ("DVB adapter number",
optarg);
break;
case 'b':
pr->cr.usewebtv = 0; /* sic, compatibility */
++n_program_options;
break;
case 'c':
pr->cr.usecc = 1;
++n_program_options;
break;
case 'd':
assert (NULL != optarg);
option_dvb_demux_id =
uint_option ("DVB demux device number",
optarg);
break;
case 'e':
assert (NULL != optarg);
option_channel_conf_file_name = optarg;
break;
case 'f':
pr->cr.usexds = TRUE;
xds_filter_option (&pr->cr, optarg);
have_xds_filter_option = TRUE;
++n_program_options;
break;
case 'i':
assert (NULL != optarg);
option_dvb_frontend_id =
uint_option ("DVB frontend device number",
optarg);
break;
case 'j':
assert (NULL != optarg);
format_option (&pr->cr, optarg);
++n_program_options;
break;
case 'l':
assert (NULL != optarg);
ch = strtol (optarg, NULL, 0);
if (ch < 1 || ch > 10) {
error_exit ("Invalid caption stream "
"number %ld. The valid "
"range is 1 ... 10.\n",
ch);
}
pr->cr.option_caption_mask |= 1 << (ch - 1);
have_cc_filter_option = TRUE;
++n_program_options;
pr->cr.usecc = 1;
break;
case 'm':
pr->cr.option_caption_timestamps = TRUE;
++n_program_options;
break;
case '\1': /* not an option */
/* NB this is a GNU extension, hence the
-n option. */
case 'n':
assert (NULL != optarg);
name = optarg;
if (NULL == pr->option_station_name) {
pr->option_station_name = name;
if (0 == n_program_options)
break;
name = NULL;
}
finish_program_setup (pr,
have_cc_filter_option,
have_xds_filter_option);
pr = add_program ();
pr->option_station_name = name;
have_xds_filter_option = FALSE;
have_cc_filter_option = FALSE;
n_program_options = 0;
break;
case 'p':
format_option (&pr->cr, "plain");
++n_program_options;
break;
case 'q':
option_verbosity = 0;
break;
case 'r':
assert (NULL != optarg);
option_dvb_dvr_id =
uint_option ("DVB DVR device number",
optarg);
break;
case 's':
pr->cr.usesen = 1;
++n_program_options;
break;
case 'v':
++option_verbosity;
break;
case 'x':
pr->cr.usexds = 1;
++n_program_options;
break;
case 'C':
assert (NULL != optarg);
for (i = 0; i < 10; ++i) {
pr->cr.option_caption_file_name[i] =
optarg;
}
pr->cr.usecc = 1;
++n_program_options;
break;
case 'L':
read_channel_conf ();
list_stations ();
exit (EXIT_SUCCESS);
case 'M':
assert (NULL != optarg);
pr->option_minicut_dir_name = optarg;
++n_program_options;
break;
case 'P':
option_source = SOURCE_STDIN_PES;
break;
case 'T':
option_source = SOURCE_STDIN_TS;
break;
case 'X':
assert (NULL != optarg);
pr->cr.option_xds_output_file_name = optarg;
++n_program_options;
break;
/* Test options. */
case 301:
option_dvb_type = FE_ATSC;
break;
case 302:
option_dvb_type = FE_OFDM;
break;
case 303:
assert (NULL != optarg);
option_ts_all_tap_file_name = optarg;
break;
case 304:
assert (NULL != optarg);
option_ts_tap_file_name = optarg;
break;
case 305:
assert (NULL != optarg);
pr->vesd.option_video_es_all_tap_file_name = optarg;
++n_program_options;
break;
case 306:
assert (NULL != optarg);
pr->vesd.option_video_es_tap_file_name = optarg;
++n_program_options;
break;
case 307:
assert (NULL != optarg);
pr->vr.aesp.option_audio_es_tap_file_name = optarg;
++n_program_options;
break;
case 308:
assert (NULL != optarg);
pr->cr.ccd.option_cc_data_tap_file_name = optarg;
++n_program_options;
break;
case 309:
assert (NULL != optarg);
debug_option (optarg);
break;
case 310:
option_minicut_test = TRUE;
break;
case 311:
option_source = SOURCE_STDIN_CC_DATA;
break;
case 312:
option_source = SOURCE_STDIN_VIDEO_ES;
break;
default:
usage (stderr);
exit (EXIT_FAILURE);
}
}
if (pr->cr.usesen
&& (pr->cr.option_caption_timestamps
|| NULL != pr->option_minicut_dir_name)) {
error_exit ("Sorry, option -s does not "
"combine with -m or -M.\n");
}
if (NULL == pr->option_station_name) {
if (0 == n_program_options) {
--n_programs;
if (SOURCE_DVB_DEVICE == option_source
|| SOURCE_STDIN_TS == option_source) {
if (0 == n_programs)
goto no_station;
}
} else {
if (SOURCE_DVB_DEVICE == option_source
|| SOURCE_STDIN_TS == option_source) {
if (optind == argc)
goto no_station;
pr->option_station_name = argv[optind];
}
finish_program_setup (pr,
have_cc_filter_option,
have_xds_filter_option);
}
} else {
finish_program_setup (pr,
have_cc_filter_option,
have_xds_filter_option);
}
if (SOURCE_DVB_DEVICE == option_source
|| SOURCE_STDIN_TS == option_source) {
look_up_station_names ();
} else {
if (n_programs > 1) {
goto too_many_stations;
} else if (NULL != program_table[0].option_station_name) {
log (1, "Ignoring station name.\n");
}
}
return;
no_station:
error_exit ("Please give a station name. "
"List all stations with the -L option.\n");
too_many_stations:
error_exit ("Sorry, only one program can be decoded with "
"the --cc-data, --es or\n"
"--pes option.\n");
}
int
main (int argc,
char ** argv)
{
my_name = argv[0];
setlocale (LC_ALL, "");
locale_codeset = vbi_locale_codeset (),
/* Don't swap out any code or data pages (if we have the
privilege). If the capture thread is delayed we may lose
packets. Errors ignored. */
mlockall (MCL_CURRENT | MCL_FUTURE);
memset (pid_map, -1, sizeof (pid_map));
parse_args (argc, argv);
init_demux_state ();
if (NULL != option_ts_all_tap_file_name) {
ts_tap_fp = open_output_file (option_ts_all_tap_file_name);
} else if (NULL != option_ts_tap_file_name) {
ts_tap_fp = open_output_file (option_ts_tap_file_name);
}
switch (option_source) {
case SOURCE_DVB_DEVICE:
open_device ();
open_output_files ();
if (n_programs > 1)
list_programs ();
demux_thread (/* arg */ NULL);
close_device ();
break;
case SOURCE_STDIN_TS:
open_output_files ();
if (n_programs > 1)
list_programs ();
ts_test_loop ("-");
break;
case SOURCE_STDIN_PES:
error_exit ("Sorry, the --pes option "
"is not implemented yet.\n");
break;
case SOURCE_STDIN_VIDEO_ES:
/* For tests only. */
open_output_files ();
video_es_test_loop (&program_table[0], "-");
break;
case SOURCE_STDIN_CC_DATA:
/* For tests only. */
open_output_files ();
cc_data_test_loop (&program_table[0], "-");
break;
}
destroy_demux_state ();
exit (EXIT_SUCCESS);
}