| /* |
| * Copyright (c) 2011-2012 - Mauro Carvalho Chehab |
| * |
| * 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 version 2 |
| * of the License. |
| * |
| * 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 |
| * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html |
| * |
| * Based on dvbv5-tzap utility. |
| */ |
| |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #include <argp.h> |
| |
| #include <config.h> |
| |
| #ifdef ENABLE_NLS |
| # define _(string) gettext(string) |
| # include "gettext.h" |
| # include <locale.h> |
| # include <langinfo.h> |
| # include <iconv.h> |
| #else |
| # define _(string) string |
| #endif |
| |
| # define N_(string) string |
| |
| #include <linux/dvb/dmx.h> |
| #include "libdvbv5/dvb-file.h" |
| #include "libdvbv5/dvb-demux.h" |
| #include "libdvbv5/dvb-dev.h" |
| #include "libdvbv5/dvb-v5-std.h" |
| #include "libdvbv5/dvb-scan.h" |
| #include "libdvbv5/countries.h" |
| |
| #define PROGRAM_NAME "dvbv5-scan" |
| #define DEFAULT_OUTPUT "dvb_channel.conf" |
| |
| const char *argp_program_version = PROGRAM_NAME " version " V4L_UTILS_VERSION; |
| const char *argp_program_bug_address = "Mauro Carvalho Chehab <mchehab@kernel.org>"; |
| |
| struct arguments { |
| char *confname, *lnb_name, *output, *demux_dev; |
| unsigned adapter, n_adapter, adapter_fe, adapter_dmx, frontend, demux, get_detected, get_nit; |
| int lna, lnb, sat_number, freq_bpf; |
| unsigned diseqc_wait, dont_add_new_freqs, timeout_multiply; |
| unsigned other_nit; |
| enum dvb_file_formats input_format, output_format; |
| const char *cc; |
| |
| /* Used by status print */ |
| unsigned n_status_lines; |
| }; |
| |
| static const struct argp_option options[] = { |
| {"adapter", 'a', N_("adapter#"), 0, N_("use given adapter (default 0)"), 0}, |
| {"frontend", 'f', N_("frontend#"), 0, N_("use given frontend (default 0)"), 0}, |
| {"demux", 'd', N_("demux#"), 0, N_("use given demux (default 0)"), 0}, |
| {"lnbf", 'l', N_("LNBf_type"), 0, N_("type of LNBf to use. 'help' lists the available ones"), 0}, |
| {"lna", 'w', N_("LNA (0, 1, -1)"), 0, N_("enable/disable/auto LNA power"), 0}, |
| {"sat_number", 'S', N_("satellite_number"), 0, N_("satellite number. If not specified, disable DISEqC"), 0}, |
| {"freq_bpf", 'U', N_("frequency"), 0, N_("SCR/Unicable band-pass filter frequency to use, in kHz"), 0}, |
| {"wait", 'W', N_("time"), 0, N_("adds additional wait time for DISEqC command completion"), 0}, |
| {"nit", 'N', NULL, 0, N_("use data from NIT table on the output file"), 0}, |
| {"get_frontend",'G', NULL, 0, N_("use data from get_frontend on the output file"), 0}, |
| {"verbose", 'v', NULL, 0, N_("be (very) verbose"), 0}, |
| {"output", 'o', N_("file"), 0, N_("output filename (default: ") DEFAULT_OUTPUT ")", 0}, |
| {"file-freqs-only", 'F', NULL, 0, N_("don't use the other frequencies discovered during scan"), 0}, |
| {"timeout-multiply", 'T', N_("factor"), 0, N_("Multiply scan timeouts by this factor"), 0}, |
| {"parse-other-nit", 'p', NULL, 0, N_("Parse the other NIT/SDT tables"), 0}, |
| {"input-format", 'I', N_("format"), 0, N_("Input format: CHANNEL, DVBV5 (default: DVBV5)"), 0}, |
| {"output-format", 'O', N_("format"), 0, N_("Output format: VDR, CHANNEL, ZAP, DVBV5 (default: DVBV5)"), 0}, |
| {"cc", 'C', N_("country_code"), 0, N_("Set the default country to be used (in ISO 3166-1 two letter code)"), 0}, |
| {"help", '?', 0, 0, N_("Give this help list"), -1}, |
| {"usage", -3, 0, 0, N_("Give a short usage message")}, |
| {"version", 'V', 0, 0, N_("Print program version"), -1}, |
| { 0, 0, 0, 0, 0, 0 } |
| }; |
| |
| static int verbose = 0; |
| #define CHANNEL_FILE "channels.conf" |
| |
| #define ERROR(x...) \ |
| do { \ |
| fprintf(stderr, _("ERROR: ")); \ |
| fprintf(stderr, x); \ |
| fprintf(stderr, "\n"); \ |
| } while (0) |
| |
| #define PERROR(x...) \ |
| do { \ |
| fprintf(stderr, _("ERROR: ")); \ |
| fprintf(stderr, x); \ |
| fprintf(stderr, " (%s)\n", strerror(errno)); \ |
| } while (0) |
| |
| static int print_frontend_stats(struct arguments *args, |
| struct dvb_v5_fe_parms *parms) |
| { |
| char buf[512], *p; |
| int rc, i, len, show; |
| uint32_t status = 0; |
| |
| /* Move cursor up and cleans down */ |
| if (isatty(STDERR_FILENO) && args->n_status_lines) |
| fprintf(stderr, "\r\x1b[%dA\x1b[J", args->n_status_lines); |
| |
| args->n_status_lines = 0; |
| |
| if (isatty(STDERR_FILENO)) { |
| rc = dvb_fe_retrieve_stats(parms, DTV_STATUS, &status); |
| if (rc) |
| status = 0; |
| if (status & FE_HAS_LOCK) |
| fprintf(stderr, "\x1b[1;32m"); |
| else |
| fprintf(stderr, "\x1b[33m"); |
| } |
| |
| p = buf; |
| len = sizeof(buf); |
| dvb_fe_snprintf_stat(parms, DTV_STATUS, NULL, 0, &p, &len, &show); |
| |
| for (i = 0; i < MAX_DTV_STATS; i++) { |
| show = 1; |
| |
| dvb_fe_snprintf_stat(parms, DTV_QUALITY, _("Quality"), |
| i, &p, &len, &show); |
| |
| dvb_fe_snprintf_stat(parms, DTV_STAT_SIGNAL_STRENGTH, _("Signal"), |
| i, &p, &len, &show); |
| |
| dvb_fe_snprintf_stat(parms, DTV_STAT_CNR, _("C/N"), |
| i, &p, &len, &show); |
| |
| dvb_fe_snprintf_stat(parms, DTV_STAT_ERROR_BLOCK_COUNT, _("UCB"), |
| i, &p, &len, &show); |
| |
| dvb_fe_snprintf_stat(parms, DTV_BER, _("postBER"), |
| i, &p, &len, &show); |
| |
| dvb_fe_snprintf_stat(parms, DTV_PRE_BER, _("preBER"), |
| i, &p, &len, &show); |
| |
| dvb_fe_snprintf_stat(parms, DTV_PER, _("PER"), |
| i, &p, &len, &show); |
| |
| if (p != buf) { |
| if (args->n_status_lines) |
| fprintf(stderr, "\t%s\n", buf); |
| else |
| fprintf(stderr, "%s\n", buf); |
| |
| args->n_status_lines++; |
| |
| p = buf; |
| len = sizeof(buf); |
| } |
| } |
| |
| fflush(stderr); |
| |
| return 0; |
| } |
| |
| static int check_frontend(void *__args, |
| struct dvb_v5_fe_parms *parms) |
| { |
| struct arguments *args = __args; |
| int rc, i; |
| fe_status_t status; |
| |
| args->n_status_lines = 0; |
| for (i = 0; i < args->timeout_multiply * 40; i++) { |
| if (parms->abort) |
| return 0; |
| rc = dvb_fe_get_stats(parms); |
| if (rc) |
| PERROR(_("dvb_fe_get_stats failed")); |
| |
| rc = dvb_fe_retrieve_stats(parms, DTV_STATUS, &status); |
| if (rc) |
| status = 0; |
| print_frontend_stats(args, parms); |
| if (status & FE_HAS_LOCK) |
| break; |
| usleep(100000); |
| }; |
| |
| if (isatty(STDERR_FILENO)) { |
| fprintf(stderr, "\x1b[37m"); |
| } |
| |
| return (status & FE_HAS_LOCK) ? 0 : -1; |
| } |
| |
| static int run_scan(struct arguments *args, struct dvb_device *dvb) |
| { |
| struct dvb_v5_fe_parms *parms = dvb->fe_parms; |
| struct dvb_file *dvb_file = NULL, *dvb_file_new = NULL; |
| struct dvb_entry *entry; |
| struct dvb_open_descriptor *dmx_fd; |
| int count = 0, shift; |
| uint32_t freq, sys; |
| enum dvb_sat_polarization pol; |
| |
| /* This is used only when reading old formats */ |
| switch (parms->current_sys) { |
| case SYS_DVBT: |
| case SYS_DVBS: |
| case SYS_DVBC_ANNEX_A: |
| case SYS_ATSC: |
| sys = parms->current_sys; |
| break; |
| case SYS_DVBC_ANNEX_C: |
| sys = SYS_DVBC_ANNEX_A; |
| break; |
| case SYS_DVBC_ANNEX_B: |
| sys = SYS_ATSC; |
| break; |
| case SYS_ISDBT: |
| case SYS_DTMB: |
| sys = SYS_DVBT; |
| break; |
| default: |
| sys = SYS_UNDEFINED; |
| break; |
| } |
| dvb_file = dvb_read_file_format(args->confname, sys, |
| args->input_format); |
| if (!dvb_file) |
| return -2; |
| |
| /* FIXME: should be replaced by dvb_dev_open() */ |
| dmx_fd = dvb_dev_open(dvb, args->demux_dev, O_RDWR); |
| if (!dmx_fd) { |
| perror(_("opening demux failed")); |
| return -3; |
| } |
| |
| for (entry = dvb_file->first_entry; entry != NULL; entry = entry->next) { |
| struct dvb_v5_descriptors *dvb_scan_handler = NULL; |
| uint32_t stream_id; |
| |
| /* |
| * If the channel file has duplicated frequencies, or some |
| * entries without any frequency at all, discard. |
| */ |
| if (dvb_retrieve_entry_prop(entry, DTV_FREQUENCY, &freq)) |
| continue; |
| shift = dvb_estimate_freq_shift(parms); |
| |
| if (dvb_retrieve_entry_prop(entry, DTV_POLARIZATION, &pol)) |
| pol = POLARIZATION_OFF; |
| |
| if (dvb_retrieve_entry_prop(entry, DTV_STREAM_ID, &stream_id)) |
| stream_id = NO_STREAM_ID_FILTER; |
| |
| if (!dvb_new_entry_is_needed(dvb_file->first_entry, entry, |
| freq, shift, pol, stream_id)) |
| continue; |
| |
| count++; |
| dvb_log(_("Scanning frequency #%d %d"), count, freq); |
| |
| /* |
| * update params->lnb only if it differs from entry->lnb |
| * (and "--lnbf" option was not provided), |
| * to avoid linear search of LNB types for every entries. |
| */ |
| if (!args->lnb_name && entry->lnb && |
| (!parms->lnb || strcasecmp(entry->lnb, parms->lnb->alias))) |
| parms->lnb = dvb_sat_get_lnb(dvb_sat_search_lnb(entry->lnb)); |
| |
| /* |
| * Run the scanning logic |
| */ |
| |
| dvb_scan_handler = dvb_dev_scan(dmx_fd, entry, |
| &check_frontend, args, |
| args->other_nit, |
| args->timeout_multiply); |
| |
| if (parms->abort) { |
| dvb_scan_free_handler_table(dvb_scan_handler); |
| break; |
| } |
| if (!dvb_scan_handler) |
| continue; |
| |
| /* |
| * Store the service entry |
| */ |
| dvb_store_channel(&dvb_file_new, parms, dvb_scan_handler, |
| args->get_detected, args->get_nit); |
| |
| /* |
| * Add new transponders based on NIT table information |
| */ |
| if (!args->dont_add_new_freqs) |
| dvb_add_scaned_transponders(parms, dvb_scan_handler, |
| dvb_file->first_entry, entry); |
| |
| /* |
| * Free the scan handler associated with the transponder |
| */ |
| |
| dvb_scan_free_handler_table(dvb_scan_handler); |
| } |
| |
| if (dvb_file_new) |
| dvb_write_file_format(args->output, dvb_file_new, |
| parms->current_sys, args->output_format); |
| |
| dvb_file_free(dvb_file); |
| if (dvb_file_new) |
| dvb_file_free(dvb_file_new); |
| |
| dvb_dev_close(dmx_fd); |
| return 0; |
| } |
| |
| static error_t parse_opt(int k, char *optarg, struct argp_state *state) |
| { |
| struct arguments *args = state->input; |
| switch (k) { |
| case 'a': |
| args->adapter = strtoul(optarg, NULL, 0); |
| args->n_adapter++; |
| break; |
| case 'f': |
| args->frontend = strtoul(optarg, NULL, 0); |
| args->adapter_fe = args->adapter; |
| break; |
| case 'd': |
| args->demux = strtoul(optarg, NULL, 0); |
| args->adapter_dmx = args->adapter; |
| break; |
| case 'w': |
| if (!strcasecmp(optarg,"on")) { |
| args->lna = 1; |
| } else if (!strcasecmp(optarg,"off")) { |
| args->lna = 0; |
| } else if (!strcasecmp(optarg,"auto")) { |
| args->lna = LNA_AUTO; |
| } else { |
| int val = strtoul(optarg, NULL, 0); |
| if (!val) |
| args->lna = 0; |
| else if (val > 0) |
| args->lna = 1; |
| else |
| args->lna = LNA_AUTO; |
| } |
| break; |
| case 'l': |
| args->lnb_name = optarg; |
| break; |
| case 'S': |
| args->sat_number = strtoul(optarg, NULL, 0); |
| break; |
| case 'U': |
| args->freq_bpf = strtoul(optarg, NULL, 0); |
| break; |
| case 'W': |
| args->diseqc_wait = strtoul(optarg, NULL, 0); |
| break; |
| case 'N': |
| args->get_nit++; |
| break; |
| case 'G': |
| args->get_detected++; |
| break; |
| case 'F': |
| args->dont_add_new_freqs++; |
| break; |
| case 'p': |
| args->other_nit++; |
| break; |
| case 'v': |
| verbose++; |
| break; |
| case 'T': |
| args->timeout_multiply = strtoul(optarg, NULL, 0); |
| break; |
| case 'I': |
| args->input_format = dvb_parse_format(optarg); |
| break; |
| case 'O': |
| args->output_format = dvb_parse_format(optarg); |
| break; |
| case 'o': |
| args->output = optarg; |
| break; |
| case 'C': |
| args->cc = strndup(optarg, 2); |
| break; |
| case '?': |
| argp_state_help(state, state->out_stream, |
| ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG |
| | ARGP_HELP_DOC); |
| fprintf(state->out_stream, _("\nReport bugs to %s.\n"), argp_program_bug_address); |
| exit(0); |
| case 'V': |
| fprintf (state->out_stream, "%s\n", argp_program_version); |
| exit(0); |
| case -3: |
| argp_state_help(state, state->out_stream, ARGP_HELP_USAGE); |
| exit(0); |
| default: |
| return ARGP_ERR_UNKNOWN; |
| }; |
| return 0; |
| } |
| |
| static int *timeout_flag; |
| |
| static void do_timeout(int x) |
| { |
| (void)x; |
| if (*timeout_flag == 0) { |
| *timeout_flag = 1; |
| alarm(5); |
| signal(SIGALRM, do_timeout); |
| } else { |
| /* something has gone wrong ... exit */ |
| exit(1); |
| } |
| } |
| |
| |
| int main(int argc, char **argv) |
| { |
| struct arguments args = {}; |
| int err, lnb = -1,idx = -1; |
| struct dvb_device *dvb; |
| struct dvb_dev_list *dvb_dev; |
| struct dvb_v5_fe_parms *parms; |
| const struct argp argp = { |
| .options = options, |
| .parser = parse_opt, |
| .doc = N_("scan DVB services using the channel file"), |
| .args_doc = N_("<initial file>"), |
| }; |
| |
| #ifdef ENABLE_NLS |
| setlocale (LC_ALL, ""); |
| bindtextdomain (PACKAGE, LOCALEDIR); |
| textdomain (PACKAGE); |
| #endif |
| |
| memset(&args, 0, sizeof(args)); |
| args.sat_number = -1; |
| args.output = DEFAULT_OUTPUT; |
| args.input_format = FILE_DVBV5; |
| args.output_format = FILE_DVBV5; |
| args.timeout_multiply = 1; |
| args.adapter = (unsigned)-1; |
| args.lna = LNA_AUTO; |
| |
| if (argp_parse(&argp, argc, argv, ARGP_NO_HELP | ARGP_NO_EXIT, &idx, &args)) { |
| argp_help(&argp, stderr, ARGP_HELP_SHORT_USAGE, PROGRAM_NAME); |
| return -1; |
| } |
| |
| if (args.timeout_multiply == 0) |
| args.timeout_multiply = 1; |
| |
| if (args.n_adapter == 1) { |
| args.adapter_fe = args.adapter; |
| args.adapter_dmx = args.adapter; |
| } |
| |
| if (args.lnb_name) { |
| lnb = dvb_sat_search_lnb(args.lnb_name); |
| if (lnb < 0) { |
| printf(_("Please select one of the LNBf's below:\n")); |
| dvb_print_all_lnb(); |
| exit(1); |
| } else { |
| printf(_("Using LNBf ")); |
| dvb_print_lnb(lnb); |
| } |
| } |
| |
| if (idx < argc) |
| args.confname = argv[idx]; |
| |
| if (!args.confname || idx < 0) { |
| argp_help(&argp, stderr, ARGP_HELP_STD_HELP, PROGRAM_NAME); |
| return -1; |
| } |
| if ((args.input_format == FILE_ZAP) || |
| (args.input_format == FILE_UNKNOWN) || |
| (args.output_format == FILE_UNKNOWN)) { |
| fprintf(stderr, _("ERROR: Please specify a valid format\n")); |
| argp_help(&argp, stderr, ARGP_HELP_STD_HELP, PROGRAM_NAME); |
| return -1; |
| } |
| |
| dvb = dvb_dev_alloc(); |
| if (!dvb) |
| return -1; |
| dvb_dev_set_log(dvb, verbose, NULL); |
| dvb_dev_find(dvb, NULL, NULL); |
| parms = dvb->fe_parms; |
| |
| dvb_dev = dvb_dev_seek_by_adapter(dvb, args.adapter_dmx, args.demux, DVB_DEVICE_DEMUX); |
| if (!dvb_dev) { |
| fprintf(stderr, _("Couldn't find demux device node\n")); |
| dvb_dev_free(dvb); |
| return -1; |
| } |
| args.demux_dev = dvb_dev->sysname; |
| |
| if (verbose) |
| fprintf(stderr, _("using demux '%s'\n"), args.demux_dev); |
| |
| dvb_dev = dvb_dev_seek_by_adapter(dvb, args.adapter_fe, args.frontend, |
| DVB_DEVICE_FRONTEND); |
| if (!dvb_dev) |
| return -1; |
| |
| if (!dvb_dev_open(dvb, dvb_dev->sysname, O_RDWR)) { |
| free(args.demux_dev); |
| return -1; |
| } |
| if (lnb >= 0) |
| parms->lnb = dvb_sat_get_lnb(lnb); |
| if (args.sat_number >= 0) |
| parms->sat_number = args.sat_number; |
| parms->diseqc_wait = args.diseqc_wait; |
| parms->freq_bpf = args.freq_bpf; |
| parms->lna = args.lna; |
| err = dvb_fe_set_default_country(parms, args.cc); |
| if (err < 0) |
| fprintf(stderr, _("Failed to set the country code:%s\n"), args.cc); |
| |
| timeout_flag = &parms->abort; |
| signal(SIGTERM, do_timeout); |
| signal(SIGINT, do_timeout); |
| |
| err = run_scan(&args, dvb); |
| |
| dvb_dev_free(dvb); |
| |
| return err; |
| } |