/* * atsc_epg utility * * Copyright (C) 2009 Yufei Yuan * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TIMEOUT 60 #define RRT_TIMEOUT 60 #define MAX_NUM_EVENT_TABLES 128 #define TITLE_BUFFER_LEN 4096 #define MESSAGE_BUFFER_LEN (16 * 1024) #define MAX_NUM_CHANNELS 16 #define MAX_NUM_EVENTS_PER_CHANNEL (4 * 24 * 7) static int atsc_scan_table(int dmxfd, uint16_t pid, enum atsc_section_tag tag, void **table_section); static const char *program; static int adapter = 0; static int period = 12; /* hours */ static int frequency; static int enable_ett = 0; static int ctrl_c = 0; static const char *modulation = NULL; static char separator[80]; void (*old_handler)(int); struct atsc_string_buffer { int buf_len; int buf_pos; char *string; }; struct atsc_event_info { uint16_t id; struct tm start; struct tm end; int title_pos; int title_len; int msg_pos; int msg_len; }; struct atsc_eit_section_info { uint8_t section_num; uint8_t num_events; uint8_t num_etms; uint8_t num_received_etms; struct atsc_event_info **events; }; struct atsc_eit_info { int num_eit_sections; struct atsc_eit_section_info *section; }; struct atsc_channel_info { uint8_t num_eits; uint8_t service_type; char short_name[8]; uint16_t major_num; uint16_t minor_num; uint16_t tsid; uint16_t prog_num; uint16_t src_id; struct atsc_eit_info *eit; struct atsc_event_info *last_event; int event_info_index; struct atsc_event_info e[MAX_NUM_EVENTS_PER_CHANNEL]; struct atsc_string_buffer title_buf; struct atsc_string_buffer msg_buf; }; struct atsc_virtual_channels_info { int num_channels; uint16_t eit_pid[MAX_NUM_EVENT_TABLES]; uint16_t ett_pid[MAX_NUM_EVENT_TABLES]; struct atsc_channel_info ch[MAX_NUM_CHANNELS]; } guide; struct mgt_table_name { uint16_t range; const char *string; }; struct mgt_table_name mgt_tab_name_array[] = { {0x0000, "terrestrial VCT with current_next_indictor=1"}, {0x0001, "terrestrial VCT with current_next_indictor=0"}, {0x0002, "cable VCT with current_next_indictor=1"}, {0x0003, "cable VCT with current_next_indictor=0"}, {0x0004, "channel ETT"}, {0x0005, "DCCSCT"}, {0x00FF, "reserved for future ATSC use"}, {0x017F, "EIT"}, {0x01FF, "reserved for future ATSC use"}, {0x027F, "event ETT"}, {0x02FF, "reserved for future ATSC use"}, /* FIXME */ {0x03FF, "RRT with rating region"}, {0x0FFF, "user private"}, {0x13FF, "reserved for future ATSC use"}, {0x14FF, "DCCT with dcc_id"}, {0xFFFF, "reserved for future ATSC use"} }; const char *channel_modulation_mode[] = { "", "analog", "SCTE mode 1", "SCTE mode 2", "ATSC 8VSB", "ATSC 16VSB" }; const char *channel_service_type[] = { "", "analog TV", "ATSC digital TV", "ATSC audio", "ATSC data-only" }; void *(*table_callback[16])(struct atsc_section_psip *) = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, (void *(*)(struct atsc_section_psip *))atsc_mgt_section_codec, (void *(*)(struct atsc_section_psip *))atsc_tvct_section_codec, (void *(*)(struct atsc_section_psip *))atsc_cvct_section_codec, (void *(*)(struct atsc_section_psip *))atsc_rrt_section_codec, (void *(*)(struct atsc_section_psip *))atsc_eit_section_codec, (void *(*)(struct atsc_section_psip *))atsc_ett_section_codec, (void *(*)(struct atsc_section_psip *))atsc_stt_section_codec, NULL, NULL }; static void int_handler(int sig_num) { if(SIGINT != sig_num) { return; } ctrl_c = 1; } /* shamelessly stolen from dvbsnoop, but almost not modified */ static uint32_t get_bits(const uint8_t *buf, int startbit, int bitlen) { const uint8_t *b; uint32_t mask,tmp_long; int bitHigh,i; b = &buf[startbit / 8]; startbit %= 8; bitHigh = 8; tmp_long = b[0]; for (i = 0; i < ((bitlen-1) >> 3); i++) { tmp_long <<= 8; tmp_long |= b[i+1]; bitHigh += 8; } startbit = bitHigh - startbit - bitlen; tmp_long = tmp_long >> startbit; mask = (1ULL << bitlen) - 1; return tmp_long & mask; } static void usage(void) { fprintf(stderr, "usage: %s [-a ] -f [-p ]" " [-m ] [-t] [-h]\n", program); } static void help(void) { fprintf(stderr, "\nhelp:\n" "%s [-a ] -f [-p ] [-m ] [-t] [-h]\n" " -a: adapter index to use, (default 0)\n" " -f: tuning frequency\n" " -p: period in hours, (default 12)\n" " -m: modulation ATSC vsb_8|vsb_16 (default vsb_8)\n" " -t: enable ETT to receive program details, if available\n" " -h: display this message\n", program); } static int close_frontend(struct dvbfe_handle *fe) { if(NULL == fe) { fprintf(stderr, "%s(): NULL pointer detected\n", __FUNCTION__); } dvbfe_close(fe); return 0; } static int open_frontend(struct dvbfe_handle **fe) { struct dvbfe_info fe_info; if(NULL == (*fe = dvbfe_open(adapter, 0, 0))) { fprintf(stderr, "%s(): error calling dvbfe_open()\n", __FUNCTION__); return -1; } dvbfe_get_info(*fe, 0, &fe_info, DVBFE_INFO_QUERYTYPE_IMMEDIATE, 0); if(DVBFE_TYPE_ATSC != fe_info.type) { fprintf(stderr, "%s(): only ATSC frontend supported currently\n", __FUNCTION__); return -1; } fe_info.feparams.frequency = frequency; fe_info.feparams.inversion = DVBFE_INVERSION_AUTO; fe_info.feparams.u.atsc.modulation = DVBFE_ATSC_MOD_VSB_8; fprintf(stdout, "tuning to %d Hz, please wait...\n", frequency); if(dvbfe_set(*fe, &fe_info.feparams, TIMEOUT * 1000)) { fprintf(stderr, "%s(): cannot lock to %d Hz in %d seconds\n", __FUNCTION__, frequency, TIMEOUT); return -1; } fprintf(stdout, "tuner locked.\n"); return 0; } #if ENABLE_RRT /* this is untested as since this part of the library is broken */ static int parse_rrt(int dmxfd) { const enum atsc_section_tag tag = stag_atsc_rating_region; struct atsc_rrt_section *rrt; struct atsc_text *region_name; struct atsc_text_string *atsc_str; int i, j, ret; i = 0; fprintf(stdout, "waiting for RRT: "); fflush(stdout); while(i < RRT_TIMEOUT) { ret = atsc_scan_table(dmxfd, ATSC_BASE_PID, tag, (void **)&rrt); if(0 > ret) { fprintf(stderr, "%s(): error calling atsc_scan_table()\n", __FUNCTION__); return -1; } if(0 == ret) { if(RRT_TIMEOUT > i) { fprintf(stdout, "."); fflush(stdout); } else { fprintf(stdout, "\nno RRT in %d seconds\n", RRT_TIMEOUT); return 0; } i += TIMEOUT; } else { fprintf(stdout, "\n"); fflush(stdout); break; } } region_name = atsc_rrt_section_rating_region_name_text(rrt); atsc_text_strings_for_each(region_name, atsc_str, i) { struct atsc_text_string_segment *seg; atsc_text_string_segments_for_each(atsc_str, seg, j) { const char *c; int k; if(seg->mode < 0x3E) { fprintf(stderr, "%s(): text mode of 0x%02X " "not supported yet\n", __FUNCTION__, seg->mode); return -1; } c = (const char *)atsc_text_string_segment_bytes(seg); for(k = 0; k < seg->number_bytes; k++) { fprintf(stdout, "%c", c[k]); } } } return 0; } #endif static int parse_stt(int dmxfd) { const enum atsc_section_tag tag = stag_atsc_system_time; const struct atsc_stt_section *stt; time_t rx_time; time_t sys_time; int ret; ret = atsc_scan_table(dmxfd, ATSC_BASE_PID, tag, (void **)&stt); if(0 > ret) { fprintf(stderr, "%s(): error calling atsc_scan_table()\n", __FUNCTION__); return -1; } if(0 == ret) { fprintf(stdout, "no STT in %d seconds\n", TIMEOUT); return 0; } rx_time = atsctime_to_unixtime(stt->system_time); time(&sys_time); fprintf(stdout, "system time: %s", ctime(&sys_time)); fprintf(stdout, "TS STT time: %s", ctime(&rx_time)); return 0; } static int parse_tvct(int dmxfd) { int num_sections; uint32_t section_pattern; const enum atsc_section_tag tag = stag_atsc_terrestrial_virtual_channel; struct atsc_tvct_section *tvct; struct atsc_tvct_channel *ch; struct atsc_channel_info *curr_info; int i, k, ret; section_pattern = 0; num_sections = -1; do { ret = atsc_scan_table(dmxfd, ATSC_BASE_PID, tag, (void **)&tvct); if(0 > ret) { fprintf(stderr, "%s(): error calling atsc_scan_table()\n", __FUNCTION__); return -1; } if(0 == ret) { fprintf(stdout, "no TVCT in %d seconds\n", TIMEOUT); return 0; } if(-1 == num_sections) { num_sections = 1 + tvct->head.ext_head.last_section_number; if(32 < num_sections) { fprintf(stderr, "%s(): no support yet for " "tables having more than 32 sections\n", __FUNCTION__); return -1; } } else { if(num_sections != 1 + tvct->head.ext_head.last_section_number) { fprintf(stderr, "%s(): last section number does not match\n", __FUNCTION__); return -1; } } if(section_pattern & (1 << tvct->head.ext_head.section_number)) { continue; } section_pattern |= 1 << tvct->head.ext_head.section_number; if(MAX_NUM_CHANNELS < guide.num_channels + tvct->num_channels_in_section) { fprintf(stderr, "%s(): no support for more than %d " "virtual channels in a pyhsical channel\n", __FUNCTION__, MAX_NUM_CHANNELS); return -1; } curr_info = &guide.ch[guide.num_channels]; guide.num_channels += tvct->num_channels_in_section; atsc_tvct_section_channels_for_each(tvct, ch, i) { /* initialize the curr_info structure */ /* each EIT covers 3 hours */ curr_info->num_eits = (period / 3) + !!(period % 3); while (curr_info->num_eits && (0xFFFF == guide.eit_pid[curr_info->num_eits - 1])) { curr_info->num_eits -= 1; } if(curr_info->eit) { fprintf(stderr, "%s(): non-NULL pointer detected " "during initialization", __FUNCTION__); return -1; } if(NULL == (curr_info->eit = calloc(curr_info->num_eits, sizeof(struct atsc_eit_info)))) { fprintf(stderr, "%s(): error calling calloc()\n", __FUNCTION__); return -1; } if(NULL == (curr_info->title_buf.string = calloc(TITLE_BUFFER_LEN, sizeof(char)))) { fprintf(stderr, "%s(): error calling calloc()\n", __FUNCTION__); return -1; } curr_info->title_buf.buf_len = TITLE_BUFFER_LEN; curr_info->title_buf.buf_pos = 0; if(NULL == (curr_info->msg_buf.string = calloc(MESSAGE_BUFFER_LEN, sizeof(char)))) { fprintf(stderr, "%s(): error calling calloc()\n", __FUNCTION__); return -1; } curr_info->msg_buf.buf_len = MESSAGE_BUFFER_LEN; curr_info->msg_buf.buf_pos = 0; for(k = 0; k < 7; k++) { curr_info->short_name[k] = get_bits((const uint8_t *)ch->short_name, k * 16, 16); } curr_info->service_type = ch->service_type; curr_info->major_num = ch->major_channel_number; curr_info->minor_num = ch->minor_channel_number; curr_info->tsid = ch->channel_TSID; curr_info->prog_num = ch->program_number; curr_info->src_id = ch->source_id; curr_info++; } } while(section_pattern != (uint32_t)((1 << num_sections) - 1)); return 0; } static int match_event(struct atsc_eit_info *eit, uint16_t event_id, struct atsc_event_info **event, uint8_t *curr_index) { int j, k; struct atsc_eit_section_info *section; if(NULL == eit || NULL == event || NULL == curr_index) { fprintf(stderr, "%s(): NULL pointer detected\n", __FUNCTION__); return -1; } for(j = 0; j < eit->num_eit_sections; j++) { section = &eit->section[j]; for(k = 0; k < section->num_events; k++) { if(section->events[k] && section->events[k]->id == event_id) { *event = section->events[k]; break; } } if(*event) { *curr_index = j; break; } } return 0; } static int parse_message(struct atsc_channel_info *channel, struct atsc_ett_section *ett, struct atsc_event_info *event) { int i, j; struct atsc_text *text; struct atsc_text_string *str; if(NULL == ett || NULL == event || NULL == channel) { fprintf(stderr, "%s(): NULL pointer detected\n", __FUNCTION__); return -1; } text = atsc_ett_section_extended_text_message(ett); atsc_text_strings_for_each(text, str, i) { struct atsc_text_string_segment *seg; atsc_text_string_segments_for_each(str, seg, j) { event->msg_pos = channel->msg_buf.buf_pos; if(0 > atsc_text_segment_decode(seg, (uint8_t **)&channel->msg_buf.string, (size_t *)&channel->msg_buf.buf_len, (size_t *)&channel->msg_buf.buf_pos)) { fprintf(stderr, "%s(): error calling " "atsc_text_segment_decode()\n", __FUNCTION__); return -1; } event->msg_len = 1 + channel->msg_buf.buf_pos - event->msg_pos; } } return 0; } static int parse_ett(int dmxfd, int index, uint16_t pid) { uint8_t curr_index; uint32_t section_pattern; const enum atsc_section_tag tag = stag_atsc_extended_text; struct atsc_eit_info *eit; struct atsc_ett_section *ett; struct atsc_channel_info *channel; struct atsc_event_info *event; struct atsc_eit_section_info *section; uint16_t source_id, event_id; int c, ret; if(0xFFFF == guide.ett_pid[index]) { return 0; } for(c = 0; c < guide.num_channels; c++) { channel = &guide.ch[c]; eit = &channel->eit[index]; section_pattern = 0; while(section_pattern != (uint32_t)((1 << eit->num_eit_sections) - 1)) { if(ctrl_c) { return 0; } ret = atsc_scan_table(dmxfd, pid, tag, (void **)&ett); fprintf(stdout, "."); fflush(stdout); if(0 > ret) { fprintf(stderr, "%s(): error calling " "atsc_scan_table()\n", __FUNCTION__); return -1; } if(0 == ret) { fprintf(stdout, "no ETT %d in %d seconds\n", index, TIMEOUT); return 0; } source_id = ett->ETM_source_id; event_id = ett->ETM_sub_id; if(source_id != channel->src_id) { continue; } event = NULL; if(match_event(eit, event_id, &event, &curr_index)) { fprintf(stderr, "%s(): error calling " "match_event()\n", __FUNCTION__); return -1; } if(NULL == event) { continue; } if(section_pattern & (1 << curr_index)) { /* the section has been filled, so skip, * not consider version yet */ continue; } if(event->msg_len) { /* the message has been filled */ continue; } if(parse_message(channel, ett, event)) { fprintf(stderr, "%s(): error calling " "parse_message()\n", __FUNCTION__); return -1; } section = &eit->section[curr_index]; if(++section->num_received_etms == section->num_etms) { section_pattern |= 1 << curr_index; } } } return 0; } static int parse_events(struct atsc_channel_info *curr_info, struct atsc_eit_section *eit, struct atsc_eit_section_info *section) { int i, j, k; struct atsc_eit_event *e; time_t start_time, end_time; if(NULL == curr_info || NULL == eit) { fprintf(stderr, "%s(): NULL pointer detected\n", __FUNCTION__); return -1; } atsc_eit_section_events_for_each(eit, e, i) { struct atsc_text *title; struct atsc_text_string *str; struct atsc_event_info *e_info = &curr_info->e[curr_info->event_info_index]; if(0 == i && curr_info->last_event) { if(e->event_id == curr_info->last_event->id) { section->events[i] = NULL; /* skip if it's the same event spanning * over sections */ continue; } } curr_info->event_info_index += 1; section->events[i] = e_info; e_info->id = e->event_id; start_time = atsctime_to_unixtime(e->start_time); end_time = start_time + e->length_in_seconds; localtime_r(&start_time, &e_info->start); localtime_r(&end_time, &e_info->end); if(0 != e->ETM_location && 3 != e->ETM_location) { /* FIXME assume 1 and 2 is interchangable as of now */ section->num_etms++; } title = atsc_eit_event_name_title_text(e); atsc_text_strings_for_each(title, str, j) { struct atsc_text_string_segment *seg; atsc_text_string_segments_for_each(str, seg, k) { e_info->title_pos = curr_info->title_buf.buf_pos; if(0 > atsc_text_segment_decode(seg, (uint8_t **)&curr_info->title_buf.string, (size_t *)&curr_info->title_buf.buf_len, (size_t *)&curr_info->title_buf.buf_pos)) { fprintf(stderr, "%s(): error calling " "atsc_text_segment_decode()\n", __FUNCTION__); return -1; } e_info->title_len = curr_info->title_buf.buf_pos - e_info->title_pos + 1; } } } return 0; } static int parse_eit(int dmxfd, int index, uint16_t pid) { int num_sections; uint8_t section_num; uint8_t curr_channel_index; uint32_t section_pattern; const enum atsc_section_tag tag = stag_atsc_event_information; struct atsc_eit_section *eit; struct atsc_channel_info *curr_info; struct atsc_eit_info *eit_info; struct atsc_eit_section_info *section; uint16_t source_id; uint32_t eit_instance_pattern = 0; int i, k, ret; while(eit_instance_pattern != (uint32_t)((1 << guide.num_channels) - 1)) { source_id = 0xFFFF; section_pattern = 0; num_sections = -1; do { ret = atsc_scan_table(dmxfd, pid, tag, (void **)&eit); fprintf(stdout, "."); fflush(stdout); if(0 > ret) { fprintf(stderr, "%s(): error calling " "atsc_scan_table()\n", __FUNCTION__); return -1; } if(0 == ret) { fprintf(stdout, "no EIT %d in %d seconds\n", index, TIMEOUT); return 0; } if(0xFFFF == source_id) { source_id = atsc_eit_section_source_id(eit); for(k = 0; k < guide.num_channels; k++) { if(source_id == guide.ch[k].src_id) { curr_info = &guide.ch[k]; curr_channel_index = k; if(0 == index) { curr_info->last_event = NULL; } break; } } if(k == guide.num_channels) { fprintf(stderr, "%s(): cannot find source_id " "0x%04X in the EIT\n", __FUNCTION__, source_id); return -1; } } else { if(source_id != atsc_eit_section_source_id(eit)) { continue; } } if(eit_instance_pattern & (1 << curr_channel_index)) { /* we have received this instance, * so quit quick */ break; } if(-1 == num_sections) { num_sections = 1 + eit->head.ext_head.last_section_number; if(32 < num_sections) { fprintf(stderr, "%s(): no support yet for " "tables having more than " "32 sections\n", __FUNCTION__); return -1; } } else { if(num_sections != 1 + eit->head.ext_head.last_section_number) { fprintf(stderr, "%s(): last section number " "does not match\n", __FUNCTION__); return -1; } } if(section_pattern & (1 << eit->head.ext_head.section_number)) { continue; } section_pattern |= 1 << eit->head.ext_head.section_number; eit_info = &curr_info->eit[index]; if(NULL == (eit_info->section = realloc(eit_info->section, (eit_info->num_eit_sections + 1) * sizeof(struct atsc_eit_section_info)))) { fprintf(stderr, "%s(): error calling realloc()\n", __FUNCTION__); return -1; } section_num = eit->head.ext_head.section_number; if(0 == eit_info->num_eit_sections) { eit_info->num_eit_sections = 1; section = eit_info->section; } else { /* have to sort it into section order * (temporal order) */ for(i = 0; i < eit_info->num_eit_sections; i++) { if(eit_info->section[i].section_num > section_num) { break; } } memmove(&eit_info->section[i + 1], &eit_info->section[i], (eit_info->num_eit_sections - i) * sizeof(struct atsc_eit_section_info)); section = &eit_info->section[i - 1]; section = &eit_info->section[i]; eit_info->num_eit_sections += 1; } section->section_num = section_num; section->num_events = eit->num_events_in_section; section->num_etms = 0; section->num_received_etms = 0; if(NULL == (section->events = calloc(section->num_events, sizeof(struct atsc_event_info *)))) { fprintf(stderr, "%s(): error calling calloc()\n", __FUNCTION__); return -1; } if(parse_events(curr_info, eit, section)) { fprintf(stderr, "%s(): error calling " "parse_events()\n", __FUNCTION__); return -1; } } while(section_pattern != (uint32_t)((1 << num_sections) - 1)); eit_instance_pattern |= 1 << curr_channel_index; } for(i = 0; i < guide.num_channels; i++) { struct atsc_channel_info *channel = &guide.ch[i]; struct atsc_eit_info *ei = &channel->eit[index]; struct atsc_eit_section_info *s; if(0 == ei->num_eit_sections) { channel->last_event = NULL; continue; } s = &ei->section[ei->num_eit_sections - 1]; /* BUG: it's incorrect when last section has no event */ if(0 == s->num_events) { channel->last_event = NULL; continue; } channel->last_event = s->events[s->num_events - 1]; } return 0; } static int parse_mgt(int dmxfd) { const enum atsc_section_tag tag = stag_atsc_master_guide; struct atsc_mgt_section *mgt; struct atsc_mgt_table *t; int i, j, ret; ret = atsc_scan_table(dmxfd, ATSC_BASE_PID, tag, (void **)&mgt); if(0 > ret) { fprintf(stderr, "%s(): error calling atsc_scan_table()\n", __FUNCTION__); return -1; } if(0 == ret) { fprintf(stdout, "no MGT in %d seconds\n", TIMEOUT); return 0; } fprintf(stdout, "MGT table:\n"); atsc_mgt_section_tables_for_each(mgt, t, i) { struct mgt_table_name table; for(j = 0; j < (int)(sizeof(mgt_tab_name_array) / sizeof(struct mgt_table_name)); j++) { if(t->table_type > mgt_tab_name_array[j].range) { continue; } table = mgt_tab_name_array[j]; if(0 == j || mgt_tab_name_array[j - 1].range + 1 == mgt_tab_name_array[j].range) { j = -1; } else { j = t->table_type - mgt_tab_name_array[j - 1].range - 1; if(0x017F == table.range) { guide.eit_pid[j] = t->table_type_PID; } else if (0x027F == table.range) { guide.ett_pid[j] = t->table_type_PID; } } break; } fprintf(stdout, " %2d: type = 0x%04X, PID = 0x%04X, %s", i, t->table_type, t->table_type_PID, table.string); if(-1 != j) { fprintf(stdout, " %d", j); } fprintf(stdout, "\n"); } return 0; } static int cleanup_guide(void) { int i, j, k; for(i = 0; i < guide.num_channels; i++) { struct atsc_channel_info *channel = &guide.ch[i]; if(channel->title_buf.string) { free(channel->title_buf.string); } if(channel->msg_buf.string) { free(channel->msg_buf.string); } for(j = 0; j < channel->num_eits; j++) { struct atsc_eit_info *eit = &channel->eit[j]; for(k = 0; k < eit->num_eit_sections; k++) { struct atsc_eit_section_info *section = &eit->section[k]; if(section->num_events) { free(section->events); } } if(k) { free(eit->section); } } if(j) { free(channel->eit); } } return 0; } static int print_events(struct atsc_channel_info *channel, struct atsc_eit_section_info *section) { int m; char line[256]; if(NULL == section) { fprintf(stderr, "%s(): NULL pointer detected", __FUNCTION__); return -1; } for(m = 0; m < section->num_events; m++) { struct atsc_event_info *event = section->events[m]; if(NULL == event) { continue; } fprintf(stdout, "|%02d:%02d--%02d:%02d| ", event->start.tm_hour, event->start.tm_min, event->end.tm_hour, event->end.tm_min); snprintf(line, event->title_len, "%s", &channel->title_buf.string[event->title_pos]); line[event->title_len] = '\0'; fprintf(stdout, "%s\n", line); if(event->msg_len) { int len = event->msg_len; int pos = event->msg_pos; size_t part; do { part = len > 255 ? 255 : len; snprintf(line, part, "%s", &channel->msg_buf.string[pos]); line[part] = '\0'; fprintf(stdout, "%s", line); len -= part; pos += part; } while(0 < len); fprintf(stdout, "\n"); } } return 0; } static int print_guide(void) { int i, j, k; fprintf(stdout, "%s\n", separator); for(i = 0; i < guide.num_channels; i++) { struct atsc_channel_info *channel = &guide.ch[i]; fprintf(stdout, "%d.%d %s\n", channel->major_num, channel->minor_num, channel->short_name); for(j = 0; j < channel->num_eits; j++) { struct atsc_eit_info *eit = &channel->eit[j]; for(k = 0; k < eit->num_eit_sections; k++) { struct atsc_eit_section_info *section = &eit->section[k]; if(print_events(channel, section)) { fprintf(stderr, "%s(): error calling " "print_events()\n", __FUNCTION__); return -1; } } } fprintf(stdout, "%s\n", separator); } return 0; } static int open_demux(int *dmxfd) { if((*dmxfd = dvbdemux_open_demux(adapter, 0, 0)) < 0) { fprintf(stderr, "%s(): error calling dvbdemux_open_demux()\n", __FUNCTION__); return -1; } return 0; } static int close_demux(int dmxfd) { if(dvbdemux_stop(dmxfd)) { fprintf(stderr, "%s(): error calling dvbdemux_stop()\n", __FUNCTION__); return -1; } return 0; } /* used other utilities as template and generalized here */ static int atsc_scan_table(int dmxfd, uint16_t pid, enum atsc_section_tag tag, void **table_section) { uint8_t filter[18]; uint8_t mask[18]; unsigned char sibuf[4096]; int size; int ret; struct pollfd pollfd; struct section *section; struct section_ext *section_ext; struct atsc_section_psip *psip; /* create a section filter for the table */ memset(filter, 0, sizeof(filter)); memset(mask, 0, sizeof(mask)); filter[0] = tag; mask[0] = 0xFF; if(dvbdemux_set_section_filter(dmxfd, pid, filter, mask, 1, 1)) { fprintf(stderr, "%s(): error calling atsc_scan_table()\n", __FUNCTION__); return -1; } /* poll for data */ pollfd.fd = dmxfd; pollfd.events = POLLIN | POLLERR |POLLPRI; if((ret = poll(&pollfd, 1, TIMEOUT * 1000)) < 0) { if(ctrl_c) { return 0; } fprintf(stderr, "%s(): error calling poll()\n", __FUNCTION__); return -1; } if(0 == ret) { return 0; } /* read it */ if((size = read(dmxfd, sibuf, sizeof(sibuf))) < 0) { fprintf(stderr, "%s(): error calling read()\n", __FUNCTION__); return -1; } /* parse section */ section = section_codec(sibuf, size); if(NULL == section) { fprintf(stderr, "%s(): error calling section_codec()\n", __FUNCTION__); return -1; } section_ext = section_ext_decode(section, 0); if(NULL == section_ext) { fprintf(stderr, "%s(): error calling section_ext_decode()\n", __FUNCTION__); return -1; } psip = atsc_section_psip_decode(section_ext); if(NULL == psip) { fprintf(stderr, "%s(): error calling atsc_section_psip_decode()\n", __FUNCTION__); return -1; } *table_section = table_callback[tag & 0x0F](psip); if(NULL == *table_section) { fprintf(stderr, "%s(): error decode table section\n", __FUNCTION__); return -1; } return 1; } int main(int argc, char *argv[]) { int i, dmxfd; struct dvbfe_handle *fe; program = argv[0]; if(1 == argc) { usage(); exit(-1); } for( ; ; ) { char c; if(-1 == (c = getopt(argc, argv, "a:f:p:m:th"))) { break; } switch(c) { case 'a': adapter = strtoll(optarg, NULL, 0); break; case 'f': frequency = strtol(optarg, NULL, 0); break; case 'p': period = strtol(optarg, NULL, 0); /* each table covers 3 hours */ if((3 * MAX_NUM_EVENT_TABLES) < period) { period = 3 * MAX_NUM_EVENT_TABLES; } break; case 'm': /* just stub, so far ATSC only has VSB_8 */ modulation = optarg; break; case 't': enable_ett = 1; break; case 'h': help(); exit(0); default: usage(); exit(-1); } } memset(separator, '-', sizeof(separator)); separator[79] = '\0'; memset(&guide, 0, sizeof(struct atsc_virtual_channels_info)); memset(guide.eit_pid, 0xFF, MAX_NUM_EVENT_TABLES * sizeof(uint16_t)); memset(guide.ett_pid, 0xFF, MAX_NUM_EVENT_TABLES * sizeof(uint16_t)); if(open_frontend(&fe)) { fprintf(stderr, "%s(): error calling open_frontend()\n", __FUNCTION__); return -1; } if(open_demux(&dmxfd)) { fprintf(stderr, "%s(): error calling open_demux()\n", __FUNCTION__); return -1; } if(parse_stt(dmxfd)) { fprintf(stderr, "%s(): error calling parse_stt()\n", __FUNCTION__); return -1; } if(parse_mgt(dmxfd)) { fprintf(stderr, "%s(): error calling parse_mgt()\n", __FUNCTION__); return -1; } if(parse_tvct(dmxfd)) { fprintf(stderr, "%s(): error calling parse_tvct()\n", __FUNCTION__); return -1; } #ifdef ENABLE_RRT if(parse_rrt(dmxfd)) { fprintf(stderr, "%s(): error calling parse_rrt()\n", __FUNCTION__); return -1; } #endif fprintf(stdout, "receiving EIT "); for(i = 0; i < guide.ch[0].num_eits; i++) { if(parse_eit(dmxfd, i, guide.eit_pid[i])) { fprintf(stderr, "%s(): error calling parse_eit()\n", __FUNCTION__); return -1; } } fprintf(stdout, "\n"); old_handler = signal(SIGINT, int_handler); if(enable_ett) { fprintf(stdout, "receiving ETT "); for(i = 0; i < guide.ch[0].num_eits; i++) { if(0xFFFF != guide.ett_pid[i]) { if(parse_ett(dmxfd, i, guide.ett_pid[i])) { fprintf(stderr, "%s(): error calling " "parse_eit()\n", __FUNCTION__); return -1; } } if(ctrl_c) { break; } } fprintf(stdout, "\n"); } signal(SIGINT, old_handler); if(print_guide()) { fprintf(stderr, "%s(): error calling print_guide()\n", __FUNCTION__); return -1; } if(cleanup_guide()) { fprintf(stderr, "%s(): error calling cleanup_guide()\n", __FUNCTION__); return -1; } if(close_demux(dmxfd)) { fprintf(stderr, "%s(): error calling close_demux()\n", __FUNCTION__); return -1; } if(close_frontend(fe)) { fprintf(stderr, "%s(): error calling close_demux()\n", __FUNCTION__); return -1; } return 0; }