diff options
Diffstat (limited to 'util/gnutv')
-rw-r--r-- | util/gnutv/Makefile | 21 | ||||
-rw-r--r-- | util/gnutv/gnutv.c | 365 | ||||
-rw-r--r-- | util/gnutv/gnutv.h | 37 | ||||
-rw-r--r-- | util/gnutv/gnutv_ca.c | 404 | ||||
-rw-r--r-- | util/gnutv/gnutv_ca.h | 40 | ||||
-rw-r--r-- | util/gnutv/gnutv_data.c | 459 | ||||
-rw-r--r-- | util/gnutv/gnutv_data.h | 39 | ||||
-rw-r--r-- | util/gnutv/gnutv_dvb.c | 376 | ||||
-rw-r--r-- | util/gnutv/gnutv_dvb.h | 44 |
9 files changed, 1785 insertions, 0 deletions
diff --git a/util/gnutv/Makefile b/util/gnutv/Makefile new file mode 100644 index 0000000..3a679c4 --- /dev/null +++ b/util/gnutv/Makefile @@ -0,0 +1,21 @@ +# Makefile for linuxtv.org dvb-apps/util/gnutv + +objects = gnutv_ca.o \ + gnutv_dvb.o \ + gnutv_data.o + +binaries = gnutv + +inst_bin = $(binaries) + +CPPFLAGS += -I../../lib +LDFLAGS += -L../../lib/libdvbapi -L../../lib/libdvbcfg -L../../lib/libdvbsec -L../../lib/libdvben50221 -L../../lib/libucsi +LDLIBS += -ldvbcfg -ldvben50221 -lucsi -ldvbsec -ldvbapi -lpthread + +.PHONY: all + +all: $(binaries) + +$(binaries): $(objects) + +include ../../Make.rules diff --git a/util/gnutv/gnutv.c b/util/gnutv/gnutv.c new file mode 100644 index 0000000..62f19be --- /dev/null +++ b/util/gnutv/gnutv.c @@ -0,0 +1,365 @@ +/* + gnutv utility + + Copyright (C) 2004, 2005 Manu Abraham <abraham.manu@gmail.com> + Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net) + + 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 <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <string.h> +#include <fcntl.h> +#include <signal.h> +#include <pthread.h> +#include <sys/poll.h> +#include <libdvbapi/dvbdemux.h> +#include <libdvbapi/dvbaudio.h> +#include <libdvbsec/dvbsec_cfg.h> +#include <libucsi/mpeg/section.h> +#include "gnutv.h" +#include "gnutv_dvb.h" +#include "gnutv_ca.h" +#include "gnutv_data.h" + + +static void signal_handler(int _signal); + +static int quit_app = 0; + +void usage(void) +{ + static const char *_usage = "\n" + " gnutv: A digital tv utility\n" + " Copyright (C) 2004, 2005, 2006 Manu Abraham (manu@kromtek.com)\n" + " Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net)\n\n" + " usage: gnutv <options> as follows:\n" + " -h help\n" + " -adapter <id> adapter to use (default 0)\n" + " -frontend <id> frontend to use (default 0)\n" + " -demux <id> demux to use (default 0)\n" + " -caslotnum <id> ca slot number to use (default 0)\n" + " -channels <filename> channels.conf file.\n" + " -secfile <filename> Optional sec.conf file.\n" + " -secid <secid> ID of the SEC configuration to use, one of:\n" + " * UNIVERSAL (default) - Europe, 10800 to 11800 MHz and 11600 to 12700 Mhz,\n" + " Dual LO, loband 9750, hiband 10600 MHz.\n" + " * DBS - Expressvu, North America, 12200 to 12700 MHz, Single LO, 11250 MHz.\n" + " * STANDARD - 10945 to 11450 Mhz, Single LO, 10000 Mhz.\n" + " * ENHANCED - Astra, 10700 to 11700 MHz, Single LO, 9750 MHz.\n" + " * C-BAND - Big Dish, 3700 to 4200 MHz, Single LO, 5150 Mhz.\n" + " * C-MULTI - Big Dish - Multipoint LNBf, 3700 to 4200 MHz,\n" + " Dual LO, H:5150MHz, V:5750MHz.\n" + " * One of the sec definitions from the secfile if supplied\n" + " -out decoder Output to hardware decoder (default)\n" + " decoderabypass Output to hardware decoder using audio bypass\n" + " dvr Output stream to dvr device\n" + " null Do not output anything\n" + " stdout Output to stdout\n" + " file <filename> Output stream to file\n" + " udp <address> <port> Output stream to address:port using udp\n" + " udpif <address> <port> <interface> Output stream to address:port using udp\n" + " forcing the specified interface\n" + " rtp <address> <port> Output stream to address:port using udp-rtp\n" + " rtpif <address> <port> <interface> Output stream to address:port using udp-rtp\n" + " forcing the specified interface\n" + " -timeout <secs> Number of seconds to output channel for\n" + " (0=>exit immediately after successful tuning, default is to output forever)\n" + " -cammenu Show the CAM menu\n" + " -nomoveca Do not attempt to move CA descriptors from stream to programme level\n" + " <channel name>\n"; + fprintf(stderr, "%s\n", _usage); + + exit(1); +} + +int find_channel(struct dvbcfg_zapchannel *channel, void *private_data) +{ + struct dvbcfg_zapchannel *tmpchannel = private_data; + + if (strcmp(channel->name, tmpchannel->name) == 0) { + memcpy(tmpchannel, channel, sizeof(struct dvbcfg_zapchannel)); + return 1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + int adapter_id = 0; + int frontend_id = 0; + int demux_id = 0; + int caslot_num = 0; + char *chanfile = "/etc/channels.conf"; + char *secfile = NULL; + char *secid = NULL; + char *channel_name = NULL; + int output_type = OUTPUT_TYPE_DECODER; + char *outfile = NULL; + char *outhost = NULL; + char *outport = NULL; + char *outif = NULL; + struct addrinfo *outaddrs = NULL; + int timeout = -1; + int moveca = 1; + int cammenu = 0; + int argpos = 1; + struct gnutv_dvb_params gnutv_dvb_params; + struct gnutv_ca_params gnutv_ca_params; + int ffaudiofd = -1; + int usertp = 0; + + while(argpos != argc) { + if (!strcmp(argv[argpos], "-h")) { + usage(); + } else if (!strcmp(argv[argpos], "-adapter")) { + if ((argc - argpos) < 2) + usage(); + if (sscanf(argv[argpos+1], "%i", &adapter_id) != 1) + usage(); + argpos+=2; + } else if (!strcmp(argv[argpos], "-frontend")) { + if ((argc - argpos) < 2) + usage(); + if (sscanf(argv[argpos+1], "%i", &frontend_id) != 1) + usage(); + argpos+=2; + } else if (!strcmp(argv[argpos], "-demux")) { + if ((argc - argpos) < 2) + usage(); + if (sscanf(argv[argpos+1], "%i", &demux_id) != 1) + usage(); + argpos+=2; + } else if (!strcmp(argv[argpos], "-caslotnum")) { + if ((argc - argpos) < 2) + usage(); + if (sscanf(argv[argpos+1], "%i", &caslot_num) != 1) + usage(); + argpos+=2; + } else if (!strcmp(argv[argpos], "-channels")) { + if ((argc - argpos) < 2) + usage(); + chanfile = argv[argpos+1]; + argpos+=2; + } else if (!strcmp(argv[argpos], "-secfile")) { + if ((argc - argpos) < 2) + usage(); + secfile = argv[argpos+1]; + argpos+=2; + } else if (!strcmp(argv[argpos], "-secid")) { + if ((argc - argpos) < 2) + usage(); + secid = argv[argpos+1]; + argpos+=2; + } else if (!strcmp(argv[argpos], "-out")) { + if ((argc - argpos) < 2) + usage(); + if (!strcmp(argv[argpos+1], "decoder")) { + output_type = OUTPUT_TYPE_DECODER; + } else if (!strcmp(argv[argpos+1], "decoderabypass")) { + output_type = OUTPUT_TYPE_DECODER_ABYPASS; + } else if (!strcmp(argv[argpos+1], "dvr")) { + output_type = OUTPUT_TYPE_DVR; + } else if (!strcmp(argv[argpos+1], "null")) { + output_type = OUTPUT_TYPE_NULL; + } else if (!strcmp(argv[argpos+1], "stdout")) { + output_type = OUTPUT_TYPE_STDOUT; + } else if (!strcmp(argv[argpos+1], "file")) { + output_type = OUTPUT_TYPE_FILE; + if ((argc - argpos) < 3) + usage(); + outfile = argv[argpos+2]; + argpos++; + } else if ((!strcmp(argv[argpos+1], "udp")) || + (!strcmp(argv[argpos+1], "rtp"))) { + output_type = OUTPUT_TYPE_UDP; + if ((argc - argpos) < 4) + usage(); + + if (!strcmp(argv[argpos+1], "rtp")) + usertp = 1; + outhost = argv[argpos+2]; + outport = argv[argpos+3]; + argpos+=2; + } else if ((!strcmp(argv[argpos+1], "udpif")) || + (!strcmp(argv[argpos+1], "rtpif"))) { + output_type = OUTPUT_TYPE_UDP; + if ((argc - argpos) < 5) + usage(); + + if (!strcmp(argv[argpos+1], "rtpif")) + usertp = 1; + outhost = argv[argpos+2]; + outport = argv[argpos+3]; + outif = argv[argpos+4]; + argpos+=3; + } else { + usage(); + } + argpos+=2; + } else if (!strcmp(argv[argpos], "-timeout")) { + if ((argc - argpos) < 2) + usage(); + if (sscanf(argv[argpos+1], "%i", &timeout) != 1) + usage(); + argpos+=2; + } else if (!strcmp(argv[argpos], "-nomoveca")) { + moveca = 0; + argpos++; + } else if (!strcmp(argv[argpos], "-cammenu")) { + cammenu = 1; + argpos++; + } else { + if ((argc - argpos) != 1) + usage(); + channel_name = argv[argpos]; + argpos++; + } + } + + // the user didn't select anything! + if ((channel_name == NULL) && (!cammenu)) + usage(); + + // resolve host/port + if ((outhost != NULL) && (outport != NULL)) { + int res; + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + if ((res = getaddrinfo(outhost, outport, &hints, &outaddrs)) != 0) { + fprintf(stderr, "Unable to resolve requested address: %s\n", gai_strerror(res)); + exit(1); + } + } + + // setup any signals + signal(SIGINT, signal_handler); + signal(SIGPIPE, SIG_IGN); + + // start the CA stuff + gnutv_ca_params.adapter_id = adapter_id; + gnutv_ca_params.caslot_num = caslot_num; + gnutv_ca_params.cammenu = cammenu; + gnutv_ca_params.moveca = moveca; + gnutv_ca_start(&gnutv_ca_params); + + // frontend setup if a channel name was supplied + if ((!cammenu) && (channel_name != NULL)) { + // find the requested channel + if (strlen(channel_name) >= sizeof(gnutv_dvb_params.channel.name)) { + fprintf(stderr, "Channel name is too long %s\n", channel_name); + exit(1); + } + FILE *channel_file = fopen(chanfile, "r"); + if (channel_file == NULL) { + fprintf(stderr, "Could open channel file %s\n", chanfile); + exit(1); + } + memcpy(gnutv_dvb_params.channel.name, channel_name, strlen(channel_name) + 1); + if (dvbcfg_zapchannel_parse(channel_file, find_channel, &gnutv_dvb_params.channel) != 1) { + fprintf(stderr, "Unable to find requested channel %s\n", channel_name); + exit(1); + } + fclose(channel_file); + + // default SEC with a DVBS card + if ((secid == NULL) && (gnutv_dvb_params.channel.fe_type == DVBFE_TYPE_DVBS)) + secid = "UNIVERSAL"; + + // look it up if one were supplied + gnutv_dvb_params.valid_sec = 0; + if (secid != NULL) { + if (dvbsec_cfg_find(secfile, secid, + &gnutv_dvb_params.sec)) { + fprintf(stderr, "Unable to find suitable sec/lnb configuration for channel\n"); + exit(1); + } + gnutv_dvb_params.valid_sec = 1; + } + + // open the frontend + gnutv_dvb_params.fe = dvbfe_open(adapter_id, frontend_id, 0); + if (gnutv_dvb_params.fe == NULL) { + fprintf(stderr, "Failed to open frontend\n"); + exit(1); + } + + // failover decoder to dvr output if decoder not available + if ((output_type == OUTPUT_TYPE_DECODER) || + (output_type == OUTPUT_TYPE_DECODER_ABYPASS)) { + ffaudiofd = dvbaudio_open(adapter_id, 0); + if (ffaudiofd < 0) { + fprintf(stderr, "Cannot open decoder; defaulting to dvr output\n"); + output_type = OUTPUT_TYPE_DVR; + } + } + + // start the DVB stuff + gnutv_dvb_params.adapter_id = adapter_id; + gnutv_dvb_params.frontend_id = frontend_id; + gnutv_dvb_params.demux_id = demux_id; + gnutv_dvb_params.output_type = output_type; + gnutv_dvb_start(&gnutv_dvb_params); + + // start the data stuff + gnutv_data_start(output_type, ffaudiofd, adapter_id, demux_id, outfile, outif, outaddrs, usertp); + } + + // the UI + time_t start = 0; + while(!quit_app) { + if (gnutv_dvb_locked() && (start == 0)) + start = time(NULL); + + // the timeout + if ((timeout != -1) && (start != 0)) { + if ((time(NULL) - start) >= timeout) + break; + } + + if (cammenu) + gnutv_ca_ui(); + else + usleep(1); + } + + // stop data handling + gnutv_data_stop(); + + // shutdown DVB stuff + if (channel_name != NULL) + gnutv_dvb_stop(); + + // shutdown CA stuff + gnutv_ca_stop(); + + // done + exit(0); +} + +static void signal_handler(int _signal) +{ + (void) _signal; + + if (!quit_app) { + quit_app = 1; + } +} diff --git a/util/gnutv/gnutv.h b/util/gnutv/gnutv.h new file mode 100644 index 0000000..02ed041 --- /dev/null +++ b/util/gnutv/gnutv.h @@ -0,0 +1,37 @@ +/* + gnutv utility + + Copyright (C) 2004, 2005 Manu Abraham <abraham.manu@gmail.com> + Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net) + + 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. +*/ + +#ifndef __CA_gnutv_H__ +#define __CA_gnutv_H__ + +#include <stdlib.h> +#include <stdint.h> + +#define OUTPUT_TYPE_DECODER 0 +#define OUTPUT_TYPE_DECODER_ABYPASS 1 +#define OUTPUT_TYPE_DVR 2 +#define OUTPUT_TYPE_NULL 3 +#define OUTPUT_TYPE_FILE 4 +#define OUTPUT_TYPE_UDP 5 +#define OUTPUT_TYPE_STDOUT 6 + +#endif diff --git a/util/gnutv/gnutv_ca.c b/util/gnutv/gnutv_ca.c new file mode 100644 index 0000000..5830f21 --- /dev/null +++ b/util/gnutv/gnutv_ca.c @@ -0,0 +1,404 @@ +/* + gnutv utility + + Copyright (C) 2004, 2005 Manu Abraham <abraham.manu@gmail.com> + Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net) + + 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/poll.h> +#include <pthread.h> +#include <libdvben50221/en50221_stdcam.h> +#include "gnutv.h" +#include "gnutv_ca.h" + + + +#define MMI_STATE_CLOSED 0 +#define MMI_STATE_OPEN 1 +#define MMI_STATE_ENQ 2 +#define MMI_STATE_MENU 3 + +static int gnutv_ca_info_callback(void *arg, uint8_t slot_id, uint16_t session_number, uint32_t ca_id_count, uint16_t *ca_ids); +static int gnutv_ai_callback(void *arg, uint8_t slot_id, uint16_t session_number, + uint8_t application_type, uint16_t application_manufacturer, + uint16_t manufacturer_code, uint8_t menu_string_length, + uint8_t *menu_string); + +static int gnutv_mmi_close_callback(void *arg, uint8_t slot_id, uint16_t session_number, + uint8_t cmd_id, uint8_t delay); +static int gnutv_mmi_display_control_callback(void *arg, uint8_t slot_id, uint16_t session_number, + uint8_t cmd_id, uint8_t mmi_mode); +static int gnutv_mmi_enq_callback(void *arg, uint8_t slot_id, uint16_t session_number, + uint8_t blind_answer, uint8_t expected_answer_length, + uint8_t *text, uint32_t text_size); +static int gnutv_mmi_menu_callback(void *arg, uint8_t slot_id, uint16_t session_number, + struct en50221_app_mmi_text *title, + struct en50221_app_mmi_text *sub_title, + struct en50221_app_mmi_text *bottom, + uint32_t item_count, struct en50221_app_mmi_text *items, + uint32_t item_raw_length, uint8_t *items_raw); +static void *camthread_func(void* arg); + +static struct en50221_transport_layer *tl = NULL; +static struct en50221_session_layer *sl = NULL; +static struct en50221_stdcam *stdcam = NULL; + +static int ca_resource_connected = 0; +static int mmi_state = MMI_STATE_CLOSED; +static int mmi_enq_blind; +static int mmi_enq_length; + +static int camthread_shutdown = 0; +static pthread_t camthread; +int moveca = 0; +int seenpmt = 0; +int cammenu = 0; + +char ui_line[256]; +uint32_t ui_linepos = 0; + + +void gnutv_ca_start(struct gnutv_ca_params *params) +{ + // create transport layer + tl = en50221_tl_create(1, 16); + if (tl == NULL) { + fprintf(stderr, "Failed to create transport layer\n"); + return; + } + + // create session layer + sl = en50221_sl_create(tl, 16); + if (sl == NULL) { + fprintf(stderr, "Failed to create session layer\n"); + en50221_tl_destroy(tl); + return; + } + + // create the stdcam instance + stdcam = en50221_stdcam_create(params->adapter_id, params->caslot_num, tl, sl); + if (stdcam == NULL) { + en50221_sl_destroy(sl); + en50221_tl_destroy(tl); + return; + } + + // hook up the AI callbacks + if (stdcam->ai_resource) { + en50221_app_ai_register_callback(stdcam->ai_resource, gnutv_ai_callback, stdcam); + } + + // hook up the CA callbacks + if (stdcam->ca_resource) { + en50221_app_ca_register_info_callback(stdcam->ca_resource, gnutv_ca_info_callback, stdcam); + } + + // hook up the MMI callbacks + if (params->cammenu) { + if (stdcam->mmi_resource) { + en50221_app_mmi_register_close_callback(stdcam->mmi_resource, gnutv_mmi_close_callback, stdcam); + en50221_app_mmi_register_display_control_callback(stdcam->mmi_resource, gnutv_mmi_display_control_callback, stdcam); + en50221_app_mmi_register_enq_callback(stdcam->mmi_resource, gnutv_mmi_enq_callback, stdcam); + en50221_app_mmi_register_menu_callback(stdcam->mmi_resource, gnutv_mmi_menu_callback, stdcam); + en50221_app_mmi_register_list_callback(stdcam->mmi_resource, gnutv_mmi_menu_callback, stdcam); + } else { + fprintf(stderr, "CAM Menus are not supported by this interface hardware\n"); + exit(1); + } + } + + // any other stuff + moveca = params->moveca; + cammenu = params->cammenu; + + // start the cam thread + pthread_create(&camthread, NULL, camthread_func, NULL); +} + +void gnutv_ca_stop(void) +{ + if (stdcam == NULL) + return; + + // shutdown the cam thread + camthread_shutdown = 1; + pthread_join(camthread, NULL); + + // destroy the stdcam + if (stdcam->destroy) + stdcam->destroy(stdcam, 1); + + // destroy session layer + en50221_sl_destroy(sl); + + // destroy transport layer + en50221_tl_destroy(tl); +} + +void gnutv_ca_ui(void) +{ + // make up polling structure for stdin + struct pollfd pollfd; + pollfd.fd = 0; + pollfd.events = POLLIN|POLLPRI|POLLERR; + + if (stdcam == NULL) + return; + + // is there a character? + if (poll(&pollfd, 1, 10) != 1) + return; + if (pollfd.revents & POLLERR) + return; + + // try to read the character + char c; + if (read(0, &c, 1) != 1) + return; + if (c == '\r') { + return; + } else if (c == '\n') { + switch(mmi_state) { + case MMI_STATE_CLOSED: + case MMI_STATE_OPEN: + if ((ui_linepos == 0) && (ca_resource_connected)) { + en50221_app_ai_entermenu(stdcam->ai_resource, stdcam->ai_session_number); + } + break; + + case MMI_STATE_ENQ: + if (ui_linepos == 0) { + en50221_app_mmi_answ(stdcam->mmi_resource, stdcam->mmi_session_number, + MMI_ANSW_ID_CANCEL, NULL, 0); + } else { + en50221_app_mmi_answ(stdcam->mmi_resource, stdcam->mmi_session_number, + MMI_ANSW_ID_ANSWER, (uint8_t*) ui_line, ui_linepos); + } + mmi_state = MMI_STATE_OPEN; + break; + + case MMI_STATE_MENU: + ui_line[ui_linepos] = 0; + en50221_app_mmi_menu_answ(stdcam->mmi_resource, stdcam->mmi_session_number, + atoi(ui_line)); + mmi_state = MMI_STATE_OPEN; + break; + } + ui_linepos = 0; + } else { + if (ui_linepos < (sizeof(ui_line)-1)) { + ui_line[ui_linepos++] = c; + } + } +} + +int gnutv_ca_new_pmt(struct mpeg_pmt_section *pmt) +{ + uint8_t capmt[4096]; + int size; + + if (stdcam == NULL) + return -1; + + if (ca_resource_connected) { + fprintf(stderr, "Received new PMT - sending to CAM...\n"); + + // translate it into a CA PMT + int listmgmt = CA_LIST_MANAGEMENT_ONLY; + if (seenpmt) { + listmgmt = CA_LIST_MANAGEMENT_UPDATE; + } + seenpmt = 1; + + if ((size = en50221_ca_format_pmt(pmt, capmt, sizeof(capmt), moveca, listmgmt, + CA_PMT_CMD_ID_OK_DESCRAMBLING)) < 0) { + fprintf(stderr, "Failed to format PMT\n"); + return -1; + } + + // set it + if (en50221_app_ca_pmt(stdcam->ca_resource, stdcam->ca_session_number, capmt, size)) { + fprintf(stderr, "Failed to send PMT\n"); + return -1; + } + + // we've seen this PMT + return 1; + } + + return 0; +} + +void gnutv_ca_new_dvbtime(time_t dvb_time) +{ + if (stdcam == NULL) + return; + + if (stdcam->dvbtime) + stdcam->dvbtime(stdcam, dvb_time); +} + +static void *camthread_func(void* arg) +{ + (void) arg; + int entered_menu = 0; + + while(!camthread_shutdown) { + stdcam->poll(stdcam); + + if ((!entered_menu) && cammenu && ca_resource_connected && stdcam->mmi_resource) { + en50221_app_ai_entermenu(stdcam->ai_resource, stdcam->ai_session_number); + entered_menu = 1; + } + } + + return 0; +} + +static int gnutv_ai_callback(void *arg, uint8_t slot_id, uint16_t session_number, + uint8_t application_type, uint16_t application_manufacturer, + uint16_t manufacturer_code, uint8_t menu_string_length, + uint8_t *menu_string) +{ + (void) arg; + (void) slot_id; + (void) session_number; + + fprintf(stderr, "CAM Application type: %02x\n", application_type); + fprintf(stderr, "CAM Application manufacturer: %04x\n", application_manufacturer); + fprintf(stderr, "CAM Manufacturer code: %04x\n", manufacturer_code); + fprintf(stderr, "CAM Menu string: %.*s\n", menu_string_length, menu_string); + + return 0; +} + +static int gnutv_ca_info_callback(void *arg, uint8_t slot_id, uint16_t session_number, uint32_t ca_id_count, uint16_t *ca_ids) +{ + (void) arg; + (void) slot_id; + (void) session_number; + + fprintf(stderr, "CAM supports the following ca system ids:\n"); + uint32_t i; + for(i=0; i< ca_id_count; i++) { + fprintf(stderr, " 0x%04x\n", ca_ids[i]); + } + ca_resource_connected = 1; + return 0; +} + +static int gnutv_mmi_close_callback(void *arg, uint8_t slot_id, uint16_t session_number, + uint8_t cmd_id, uint8_t delay) +{ + (void) arg; + (void) slot_id; + (void) session_number; + (void) cmd_id; + (void) delay; + + // note: not entirely correct as its supposed to delay if asked + mmi_state = MMI_STATE_CLOSED; + return 0; +} + +static int gnutv_mmi_display_control_callback(void *arg, uint8_t slot_id, uint16_t session_number, + uint8_t cmd_id, uint8_t mmi_mode) +{ + struct en50221_app_mmi_display_reply_details reply; + (void) arg; + (void) slot_id; + + // don't support any commands but set mode + if (cmd_id != MMI_DISPLAY_CONTROL_CMD_ID_SET_MMI_MODE) { + en50221_app_mmi_display_reply(stdcam->mmi_resource, session_number, + MMI_DISPLAY_REPLY_ID_UNKNOWN_CMD_ID, &reply); + return 0; + } + + // we only support high level mode + if (mmi_mode != MMI_MODE_HIGH_LEVEL) { + en50221_app_mmi_display_reply(stdcam->mmi_resource, session_number, + MMI_DISPLAY_REPLY_ID_UNKNOWN_MMI_MODE, &reply); + return 0; + } + + // ack the high level open + reply.u.mode_ack.mmi_mode = mmi_mode; + en50221_app_mmi_display_reply(stdcam->mmi_resource, session_number, + MMI_DISPLAY_REPLY_ID_MMI_MODE_ACK, &reply); + mmi_state = MMI_STATE_OPEN; + return 0; +} + +static int gnutv_mmi_enq_callback(void *arg, uint8_t slot_id, uint16_t session_number, + uint8_t blind_answer, uint8_t expected_answer_length, + uint8_t *text, uint32_t text_size) +{ + (void) arg; + (void) slot_id; + (void) session_number; + + fprintf(stderr, "%.*s: ", text_size, text); + fflush(stdout); + + mmi_enq_blind = blind_answer; + mmi_enq_length = expected_answer_length; + mmi_state = MMI_STATE_ENQ; + return 0; +} + +static int gnutv_mmi_menu_callback(void *arg, uint8_t slot_id, uint16_t session_number, + struct en50221_app_mmi_text *title, + struct en50221_app_mmi_text *sub_title, + struct en50221_app_mmi_text *bottom, + uint32_t item_count, struct en50221_app_mmi_text *items, + uint32_t item_raw_length, uint8_t *items_raw) +{ + (void) arg; + (void) slot_id; + (void) session_number; + (void) item_raw_length; + (void) items_raw; + + fprintf(stderr, "------------------------------\n"); + + if (title->text_length) { + fprintf(stderr, "%.*s\n", title->text_length, title->text); + } + if (sub_title->text_length) { + fprintf(stderr, "%.*s\n", sub_title->text_length, sub_title->text); + } + + uint32_t i; + fprintf(stderr, "0. Quit menu\n"); + for(i=0; i< item_count; i++) { + fprintf(stderr, "%i. %.*s\n", i+1, items[i].text_length, items[i].text); + } + + if (bottom->text_length) { + fprintf(stderr, "%.*s\n", bottom->text_length, bottom->text); + } + fprintf(stderr, "Enter option: "); + fflush(stdout); + + mmi_state = MMI_STATE_MENU; + return 0; +} diff --git a/util/gnutv/gnutv_ca.h b/util/gnutv/gnutv_ca.h new file mode 100644 index 0000000..7b5d5b3 --- /dev/null +++ b/util/gnutv/gnutv_ca.h @@ -0,0 +1,40 @@ +/* + gnutv utility + + Copyright (C) 2004, 2005 Manu Abraham <abraham.manu@gmail.com> + Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net) + + 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. +*/ + +#ifndef gnutv_CA_H +#define gnutv_CA_H 1 + +struct gnutv_ca_params { + int adapter_id; + int caslot_num; + int cammenu; + int moveca; +}; + +extern void gnutv_ca_start(struct gnutv_ca_params *params); +extern void gnutv_ca_ui(void); +extern void gnutv_ca_stop(void); + +extern int gnutv_ca_new_pmt(struct mpeg_pmt_section *pmt); +extern void gnutv_ca_new_dvbtime(time_t dvb_time); + +#endif diff --git a/util/gnutv/gnutv_data.c b/util/gnutv/gnutv_data.c new file mode 100644 index 0000000..7ac0f23 --- /dev/null +++ b/util/gnutv/gnutv_data.c @@ -0,0 +1,459 @@ +/* + gnutv utility + + Copyright (C) 2004, 2005 Manu Abraham <abraham.manu@gmail.com> + Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net) + + 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. +*/ + +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE_SOURCE 1 +#define _LARGEFILE64_SOURCE 1 + +#include <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <string.h> +#include <fcntl.h> +#include <signal.h> +#include <pthread.h> +#include <errno.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <libdvbapi/dvbdemux.h> +#include <libdvbapi/dvbaudio.h> +#include <libucsi/mpeg/section.h> +#include "gnutv.h" +#include "gnutv_dvb.h" +#include "gnutv_ca.h" +#include "gnutv_data.h" + +static void *fileoutputthread_func(void* arg); +static void *udpoutputthread_func(void* arg); + +static int gnutv_data_create_decoder_filter(int adapter, int demux, uint16_t pid, int pestype); +static int gnutv_data_create_dvr_filter(int adapter, int demux, uint16_t pid); + +static void gnutv_data_decoder_pmt(struct mpeg_pmt_section *pmt); +static void gnutv_data_dvr_pmt(struct mpeg_pmt_section *pmt); + +static void gnutv_data_append_pid_fd(int pid, int fd); +static void gnutv_data_free_pid_fds(void); + +static pthread_t outputthread; +static int outfd = -1; +static int dvrfd = -1; +static int pat_fd_dvrout = -1; +static int pmt_fd_dvrout = -1; +static int outputthread_shutdown = 0; + +static int usertp = 0; +static int adapter_id = -1; +static int demux_id = -1; +static int output_type = 0; +static struct addrinfo *outaddrs = NULL; + +struct pid_fd { + int pid; + int fd; +}; +static struct pid_fd *pid_fds = NULL; +static int pid_fds_count = 0; + +void gnutv_data_start(int _output_type, + int ffaudiofd, int _adapter_id, int _demux_id, + char *outfile, + char* outif, struct addrinfo *_outaddrs, int _usertp) +{ + usertp = _usertp; + demux_id = _demux_id; + adapter_id = _adapter_id; + output_type = _output_type; + + // setup output + switch(output_type) { + case OUTPUT_TYPE_DECODER: + case OUTPUT_TYPE_DECODER_ABYPASS: + dvbaudio_set_bypass(ffaudiofd, (output_type == OUTPUT_TYPE_DECODER_ABYPASS) ? 1 : 0); + close(ffaudiofd); + break; + + case OUTPUT_TYPE_STDOUT: + case OUTPUT_TYPE_FILE: + if (output_type == OUTPUT_TYPE_FILE) { + // open output file + outfd = open(outfile, O_WRONLY|O_CREAT|O_LARGEFILE|O_TRUNC, 0644); + if (outfd < 0) { + fprintf(stderr, "Failed to open output file\n"); + exit(1); + } + } else { + outfd = STDOUT_FILENO; + } + + // open dvr device + dvrfd = dvbdemux_open_dvr(adapter_id, 0, 1, 0); + if (dvrfd < 0) { + fprintf(stderr, "Failed to open DVR device\n"); + exit(1); + } + + pthread_create(&outputthread, NULL, fileoutputthread_func, NULL); + break; + + case OUTPUT_TYPE_UDP: + outaddrs = _outaddrs; + + // open output socket + outfd = socket(outaddrs->ai_family, outaddrs->ai_socktype, outaddrs->ai_protocol); + if (outfd < 0) { + fprintf(stderr, "Failed to open output socket\n"); + exit(1); + } + + // bind to local interface if requested + if (outif != NULL) { + if (setsockopt(outfd, SOL_SOCKET, SO_BINDTODEVICE, outif, strlen(outif)) < 0) { + fprintf(stderr, "Failed to bind to interface %s\n", outif); + exit(1); + } + } + + // open dvr device + dvrfd = dvbdemux_open_dvr(adapter_id, 0, 1, 0); + if (dvrfd < 0) { + fprintf(stderr, "Failed to open DVR device\n"); + exit(1); + } + + pthread_create(&outputthread, NULL, udpoutputthread_func, NULL); + break; + } + + // output PAT to DVR if requested + switch(output_type) { + case OUTPUT_TYPE_DVR: + case OUTPUT_TYPE_FILE: + case OUTPUT_TYPE_STDOUT: + case OUTPUT_TYPE_UDP: + pat_fd_dvrout = gnutv_data_create_dvr_filter(adapter_id, demux_id, TRANSPORT_PAT_PID); + } +} + +void gnutv_data_stop() +{ + // shutdown output thread if necessary + if (dvrfd != -1) { + outputthread_shutdown = 1; + pthread_join(outputthread, NULL); + } + gnutv_data_free_pid_fds(); + if (pat_fd_dvrout != -1) + close(pat_fd_dvrout); + if (pmt_fd_dvrout != -1) + close(pmt_fd_dvrout); + if (outaddrs) + freeaddrinfo(outaddrs); +} + +void gnutv_data_new_pat(int pmt_pid) +{ + // output PMT to DVR if requested + switch(output_type) { + case OUTPUT_TYPE_DVR: + case OUTPUT_TYPE_FILE: + case OUTPUT_TYPE_STDOUT: + case OUTPUT_TYPE_UDP: + if (pmt_fd_dvrout != -1) + close(pmt_fd_dvrout); + pmt_fd_dvrout = gnutv_data_create_dvr_filter(adapter_id, demux_id, pmt_pid); + } +} + +int gnutv_data_new_pmt(struct mpeg_pmt_section *pmt) +{ + // close all old PID FDs + gnutv_data_free_pid_fds(); + + // deal with the PMT appropriately + switch(output_type) { + case OUTPUT_TYPE_DECODER: + case OUTPUT_TYPE_DECODER_ABYPASS: + gnutv_data_decoder_pmt(pmt); + break; + + case OUTPUT_TYPE_DVR: + case OUTPUT_TYPE_FILE: + case OUTPUT_TYPE_STDOUT: + case OUTPUT_TYPE_UDP: + gnutv_data_dvr_pmt(pmt); + break; + } + + return 1; +} + +static void *fileoutputthread_func(void* arg) +{ + (void)arg; + uint8_t buf[4096]; + struct pollfd pollfd; + int written; + + pollfd.fd = dvrfd; + pollfd.events = POLLIN|POLLPRI|POLLERR; + + while(!outputthread_shutdown) { + if (poll(&pollfd, 1, 1000) != 1) + continue; + if (pollfd.revents & POLLERR) { + if (errno == EINTR) + continue; + fprintf(stderr, "DVR device read failure\n"); + return 0; + } + + int size = read(dvrfd, buf, sizeof(buf)); + if (size < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "DVR device read failure\n"); + return 0; + } + + written = 0; + while(written < size) { + int tmp = write(outfd, buf + written, size - written); + if (tmp == -1) { + if (errno != EINTR) { + fprintf(stderr, "Write error: %m\n"); + break; + } + } else { + written += tmp; + } + } + } + + return 0; +} + +#define TS_PAYLOAD_SIZE (188*7) + +static void *udpoutputthread_func(void* arg) +{ + (void)arg; + uint8_t buf[12 + TS_PAYLOAD_SIZE]; + struct pollfd pollfd; + int bufsize = 0; + int bufbase = 0; + int readsize; + uint16_t rtpseq = 0; + + pollfd.fd = dvrfd; + pollfd.events = POLLIN|POLLPRI|POLLERR; + + if (usertp) { + srandom(time(NULL)); + int ssrc = random(); + rtpseq = random(); + buf[0x0] = 0x80; + buf[0x1] = 0x21; + buf[0x4] = 0x00; // } + buf[0x5] = 0x00; // } FIXME: should really be a valid stamp + buf[0x6] = 0x00; // } + buf[0x7] = 0x00; // } + buf[0x8] = ssrc >> 24; + buf[0x9] = ssrc >> 16; + buf[0xa] = ssrc >> 8; + buf[0xb] = ssrc; + bufbase = 12; + } + + while(!outputthread_shutdown) { + if (poll(&pollfd, 1, 1000) != 1) + continue; + if (pollfd.revents & POLLERR) { + if (errno == EINTR) + continue; + fprintf(stderr, "DVR device read failure\n"); + return 0; + } + + readsize = TS_PAYLOAD_SIZE - bufsize; + readsize = read(dvrfd, buf + bufbase + bufsize, readsize); + if (readsize < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "DVR device read failure\n"); + return 0; + } + bufsize += readsize; + + if (bufsize == TS_PAYLOAD_SIZE) { + if (usertp) { + buf[2] = rtpseq >> 8; + buf[3] = rtpseq; + } + if (sendto(outfd, buf, bufbase + bufsize, 0, outaddrs->ai_addr, outaddrs->ai_addrlen) < 0) { + if (errno != EINTR) { + fprintf(stderr, "Socket send failure: %m\n"); + return 0; + } + } + rtpseq++; + bufsize = 0; + } + } + + if (bufsize) { + if (usertp) { + buf[2] = rtpseq >> 8; + buf[3] = rtpseq; + } + if (sendto(outfd, buf, bufbase + bufsize, 0, outaddrs->ai_addr, outaddrs->ai_addrlen) < 0) { + if (errno != EINTR) + fprintf(stderr, "Socket send failure: %m\n"); + } + } + + return 0; +} + +static int gnutv_data_create_decoder_filter(int adapter, int demux, uint16_t pid, int pestype) +{ + int demux_fd = -1; + + // open the demuxer + if ((demux_fd = dvbdemux_open_demux(adapter, demux, 0)) < 0) { + return -1; + } + + // create a section filter + if (dvbdemux_set_pes_filter(demux_fd, pid, DVBDEMUX_INPUT_FRONTEND, DVBDEMUX_OUTPUT_DECODER, pestype, 1)) { + close(demux_fd); + return -1; + } + + // done + return demux_fd; +} + +static int gnutv_data_create_dvr_filter(int adapter, int demux, uint16_t pid) +{ + int demux_fd = -1; + + // open the demuxer + if ((demux_fd = dvbdemux_open_demux(adapter, demux, 0)) < 0) { + return -1; + } + + // create a section filter + if (dvbdemux_set_pid_filter(demux_fd, pid, DVBDEMUX_INPUT_FRONTEND, DVBDEMUX_OUTPUT_DVR, 1)) { + close(demux_fd); + return -1; + } + + // done + return demux_fd; +} + +static void gnutv_data_decoder_pmt(struct mpeg_pmt_section *pmt) +{ + int audio_pid = -1; + int video_pid = -1; + struct mpeg_pmt_stream *cur_stream; + mpeg_pmt_section_streams_for_each(pmt, cur_stream) { + switch(cur_stream->stream_type) { + case 1: + case 2: // video + video_pid = cur_stream->pid; + break; + + case 3: + case 4: // audio + audio_pid = cur_stream->pid; + break; + } + } + + if (audio_pid != -1) { + int fd = gnutv_data_create_decoder_filter(adapter_id, demux_id, audio_pid, DVBDEMUX_PESTYPE_AUDIO); + if (fd < 0) { + fprintf(stderr, "Unable to create dvr filter for PID %i\n", audio_pid); + } else { + gnutv_data_append_pid_fd(audio_pid, fd); + } + } + if (video_pid != -1) { + int fd = gnutv_data_create_decoder_filter(adapter_id, demux_id, video_pid, DVBDEMUX_PESTYPE_VIDEO); + if (fd < 0) { + fprintf(stderr, "Unable to create dvr filter for PID %i\n", video_pid); + } else { + gnutv_data_append_pid_fd(video_pid, fd); + } + } + int fd = gnutv_data_create_decoder_filter(adapter_id, demux_id, pmt->pcr_pid, DVBDEMUX_PESTYPE_PCR); + if (fd < 0) { + fprintf(stderr, "Unable to create dvr filter for PID %i\n", pmt->pcr_pid); + } else { + gnutv_data_append_pid_fd(pmt->pcr_pid, fd); + } +} + +static void gnutv_data_dvr_pmt(struct mpeg_pmt_section *pmt) +{ + struct mpeg_pmt_stream *cur_stream; + mpeg_pmt_section_streams_for_each(pmt, cur_stream) { + int fd = gnutv_data_create_dvr_filter(adapter_id, demux_id, cur_stream->pid); + if (fd < 0) { + fprintf(stderr, "Unable to create dvr filter for PID %i\n", cur_stream->pid); + } else { + gnutv_data_append_pid_fd(cur_stream->pid, fd); + } + } +} + +static void gnutv_data_append_pid_fd(int pid, int fd) +{ + struct pid_fd *tmp; + if ((tmp = realloc(pid_fds, (pid_fds_count +1) * sizeof(struct pid_fd))) == NULL) { + fprintf(stderr, "Out of memory when adding a new pid_fd\n"); + exit(1); + } + tmp[pid_fds_count].pid = pid; + tmp[pid_fds_count].fd = fd; + pid_fds_count++; + pid_fds = tmp; +} + +static void gnutv_data_free_pid_fds() +{ + if (pid_fds_count) { + int i; + for(i=0; i< pid_fds_count; i++) { + close(pid_fds[i].fd); + } + } + if (pid_fds) + free(pid_fds); + + pid_fds_count = 0; + pid_fds = NULL; +} diff --git a/util/gnutv/gnutv_data.h b/util/gnutv/gnutv_data.h new file mode 100644 index 0000000..8e47e3a --- /dev/null +++ b/util/gnutv/gnutv_data.h @@ -0,0 +1,39 @@ +/* + gnutv utility + + Copyright (C) 2004, 2005 Manu Abraham <abraham.manu@gmail.com> + Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net) + + 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. +*/ + +#ifndef gnutv_DATA_H +#define gnutv_DATA_H 1 + +#include <netdb.h> + +extern void gnutv_data_start(int output_type, + int ffaudiofd, int adapter_id, int demux_id, + char *outfile, + char* outif, struct addrinfo *outaddrs, int usertp); +extern void gnutv_data_stop(void); + +extern void gnutv_data_new_pat(int pmt_pid); +extern int gnutv_data_new_pmt(struct mpeg_pmt_section *pmt); + + + +#endif diff --git a/util/gnutv/gnutv_dvb.c b/util/gnutv/gnutv_dvb.c new file mode 100644 index 0000000..a903c26 --- /dev/null +++ b/util/gnutv/gnutv_dvb.c @@ -0,0 +1,376 @@ +/* + gnutv utility + + Copyright (C) 2004, 2005 Manu Abraham <abraham.manu@gmail.com> + Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net) + + 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 <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <string.h> +#include <signal.h> +#include <pthread.h> +#include <errno.h> +#include <sys/poll.h> +#include <libdvbapi/dvbdemux.h> +#include <libucsi/section.h> +#include <libucsi/mpeg/section.h> +#include <libucsi/dvb/section.h> +#include "gnutv.h" +#include "gnutv_dvb.h" +#include "gnutv_data.h" +#include "gnutv_ca.h" + +#define FE_STATUS_PARAMS (DVBFE_INFO_LOCKSTATUS|DVBFE_INFO_SIGNAL_STRENGTH|DVBFE_INFO_BER|DVBFE_INFO_SNR|DVBFE_INFO_UNCORRECTED_BLOCKS) + +static int dvbthread_shutdown = 0; +static pthread_t dvbthread; +static int tune_state = 0; + +static int pat_version = -1; +static int ca_pmt_version = -1; +static int data_pmt_version = -1; + +static void *dvbthread_func(void* arg); + +static void process_pat(int pat_fd, struct gnutv_dvb_params *params, int *pmt_fd, struct pollfd *pollfd); +static void process_tdt(int tdt_fd); +static void process_pmt(int pmt_fd, struct gnutv_dvb_params *params); +static int create_section_filter(int adapter, int demux, uint16_t pid, uint8_t table_id); + + +int gnutv_dvb_start(struct gnutv_dvb_params *params) +{ + pthread_create(&dvbthread, NULL, dvbthread_func, (void*) params); + return 0; +} + +void gnutv_dvb_stop(void) +{ + dvbthread_shutdown = 1; + pthread_join(dvbthread, NULL); +} + +int gnutv_dvb_locked(void) +{ + return tune_state == 2; +} + +static void *dvbthread_func(void* arg) +{ + int pat_fd = -1; + int pmt_fd = -1; + int tdt_fd = -1; + struct pollfd pollfds[3]; + + struct gnutv_dvb_params *params = (struct gnutv_dvb_params *) arg; + + tune_state = 0; + + // create PAT filter + if ((pat_fd = create_section_filter(params->adapter_id, params->demux_id, + TRANSPORT_PAT_PID, stag_mpeg_program_association)) < 0) { + fprintf(stderr, "Failed to create PAT section filter\n"); + exit(1); + } + pollfds[0].fd = pat_fd; + pollfds[0].events = POLLIN|POLLPRI|POLLERR; + + // create TDT filter + if ((tdt_fd = create_section_filter(params->adapter_id, params->demux_id, TRANSPORT_TDT_PID, stag_dvb_time_date)) < 0) { + fprintf(stderr, "Failed to create TDT section filter\n"); + exit(1); + } + pollfds[1].fd = tdt_fd; + pollfds[1].events = POLLIN|POLLPRI|POLLERR; + + // zero PMT filter + pollfds[2].fd = 0; + pollfds[2].events = 0; + + // the DVB loop + while(!dvbthread_shutdown) { + // tune frontend + monitor lock status + if (tune_state == 0) { + // get the type of frontend + struct dvbfe_info result; + char *types; + memset(&result, 0, sizeof(result)); + dvbfe_get_info(params->fe, 0, &result, DVBFE_INFO_QUERYTYPE_IMMEDIATE, 0); + switch(result.type) { + case DVBFE_TYPE_DVBS: + types = "DVB-S"; + break; + case DVBFE_TYPE_DVBC: + types = "DVB-C"; + break; + case DVBFE_TYPE_DVBT: + types = "DVB-T"; + break; + case DVBFE_TYPE_ATSC: + types = "ATSC"; + break; + default: + types = "Unknown"; + } + fprintf(stderr, "Using frontend \"%s\", type %s\n", result.name, types); + + // do we have a valid SEC configuration? + struct dvbsec_config *sec = NULL; + if (params->valid_sec) + sec = ¶ms->sec; + + // tune! + if (dvbsec_set(params->fe, + sec, + params->channel.polarization, + (params->channel.diseqc_switch & 0x01) ? DISEQC_SWITCH_B : DISEQC_SWITCH_A, + (params->channel.diseqc_switch & 0x02) ? DISEQC_SWITCH_B : DISEQC_SWITCH_A, + ¶ms->channel.fe_params, + 0)) { + fprintf(stderr, "Failed to set frontend\n"); + exit(1); + } + + tune_state++; + } else if (tune_state == 1) { + struct dvbfe_info result; + memset(&result, 0, sizeof(result)); + dvbfe_get_info(params->fe, + FE_STATUS_PARAMS, + &result, + DVBFE_INFO_QUERYTYPE_IMMEDIATE, + 0); + + fprintf(stderr, "status %c%c%c%c%c | signal %04x | snr %04x | ber %08x | unc %08x | %s\r", + result.signal ? 'S' : ' ', + result.carrier ? 'C' : ' ', + result.viterbi ? 'V' : ' ', + result.sync ? 'Y' : ' ', + result.lock ? 'L' : ' ', + result.signal_strength, + result.snr, + result.ber, + result.ucblocks, + result.lock ? "FE_HAS_LOCK" : ""); + fflush(stderr); + + if (result.lock) { + tune_state++; + fprintf(stderr, "\n"); + fflush(stderr); + } else { + usleep(500000); + } + } + + // is there SI data? + int count = poll(pollfds, 3, 100); + if (count < 0) { + if (errno != EINTR) + fprintf(stderr, "Poll error: %m\n"); + break; + } + if (count == 0) { + continue; + } + + // PAT + if (pollfds[0].revents & (POLLIN|POLLPRI)) { + process_pat(pat_fd, params, &pmt_fd, &pollfds[2]); + } + + // TDT + if (pollfds[1].revents & (POLLIN|POLLPRI)) { + process_tdt(tdt_fd); + } + + // PMT + if (pollfds[2].revents & (POLLIN|POLLPRI)) { + process_pmt(pmt_fd, params); + } + } + + // close demuxers + if (pat_fd != -1) + close(pat_fd); + if (pmt_fd != -1) + close(pmt_fd); + if (tdt_fd != -1) + close(tdt_fd); + + return 0; +} + +static void process_pat(int pat_fd, struct gnutv_dvb_params *params, int *pmt_fd, struct pollfd *pollfd) +{ + int size; + uint8_t sibuf[4096]; + + // read the section + if ((size = read(pat_fd, sibuf, sizeof(sibuf))) < 0) { + return; + } + + // parse section + struct section *section = section_codec(sibuf, size); + if (section == NULL) { + return; + } + + // parse section_ext + struct section_ext *section_ext = section_ext_decode(section, 0); + if (section_ext == NULL) { + return; + } + if (pat_version == section_ext->version_number) { + return; + } + + // parse PAT + struct mpeg_pat_section *pat = mpeg_pat_section_codec(section_ext); + if (pat == NULL) { + return; + } + + // try and find the requested program + struct mpeg_pat_program *cur_program; + mpeg_pat_section_programs_for_each(pat, cur_program) { + if (cur_program->program_number == params->channel.service_id) { + // close old PMT fd + if (*pmt_fd != -1) + close(*pmt_fd); + + // create PMT filter + if ((*pmt_fd = create_section_filter(params->adapter_id, params->demux_id, + cur_program->pid, stag_mpeg_program_map)) < 0) { + return; + } + pollfd->fd = *pmt_fd; + pollfd->events = POLLIN|POLLPRI|POLLERR; + + gnutv_data_new_pat(cur_program->pid); + + // we have a new PMT pid + data_pmt_version = -1; + ca_pmt_version = -1; + break; + } + } + + // remember the PAT version + pat_version = section_ext->version_number; +} + +static void process_tdt(int tdt_fd) +{ + int size; + uint8_t sibuf[4096]; + + // read the section + if ((size = read(tdt_fd, sibuf, sizeof(sibuf))) < 0) { + return; + } + + // parse section + struct section *section = section_codec(sibuf, size); + if (section == NULL) { + return; + } + + // parse TDT + struct dvb_tdt_section *tdt = dvb_tdt_section_codec(section); + if (tdt == NULL) { + return; + } + + // done + gnutv_ca_new_dvbtime(dvbdate_to_unixtime(tdt->utc_time)); +} + +static void process_pmt(int pmt_fd, struct gnutv_dvb_params *params) +{ + int size; + uint8_t sibuf[4096]; + + // read the section + if ((size = read(pmt_fd, sibuf, sizeof(sibuf))) < 0) { + return; + } + + // parse section + struct section *section = section_codec(sibuf, size); + if (section == NULL) { + return; + } + + // parse section_ext + struct section_ext *section_ext = section_ext_decode(section, 0); + if (section_ext == NULL) { + return; + } + if ((section_ext->table_id_ext != params->channel.service_id) || + ((section_ext->version_number == data_pmt_version) && + (section_ext->version_number == ca_pmt_version))) { + return; + } + + // parse PMT + struct mpeg_pmt_section *pmt = mpeg_pmt_section_codec(section_ext); + if (pmt == NULL) { + return; + } + + // do data handling + if (section_ext->version_number != data_pmt_version) { + if (gnutv_data_new_pmt(pmt) == 1) + data_pmt_version = pmt->head.version_number; + } + + // do ca handling + if (section_ext->version_number != ca_pmt_version) { + if (gnutv_ca_new_pmt(pmt) == 1) + ca_pmt_version = pmt->head.version_number; + } +} + +static int create_section_filter(int adapter, int demux, uint16_t pid, uint8_t table_id) +{ + int demux_fd = -1; + uint8_t filter[18]; + uint8_t mask[18]; + + // open the demuxer + if ((demux_fd = dvbdemux_open_demux(adapter, demux, 0)) < 0) { + return -1; + } + + // create a section filter + memset(filter, 0, sizeof(filter)); + memset(mask, 0, sizeof(mask)); + filter[0] = table_id; + mask[0] = 0xFF; + if (dvbdemux_set_section_filter(demux_fd, pid, filter, mask, 1, 1)) { + close(demux_fd); + return -1; + } + + // done + return demux_fd; +} diff --git a/util/gnutv/gnutv_dvb.h b/util/gnutv/gnutv_dvb.h new file mode 100644 index 0000000..83ec086 --- /dev/null +++ b/util/gnutv/gnutv_dvb.h @@ -0,0 +1,44 @@ +/* + gnutv utility + + Copyright (C) 2004, 2005 Manu Abraham <abraham.manu@gmail.com> + Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net) + + 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. +*/ + +#ifndef gnutv_DVB_H +#define gnutv_DVB_H 1 + +#include <libdvbcfg/dvbcfg_zapchannel.h> +#include <libdvbsec/dvbsec_api.h> + +struct gnutv_dvb_params { + int adapter_id; + int frontend_id; + int demux_id; + struct dvbcfg_zapchannel channel; + struct dvbsec_config sec; + int valid_sec; + int output_type; + struct dvbfe_handle *fe; +}; + +extern int gnutv_dvb_start(struct gnutv_dvb_params *params); +extern void gnutv_dvb_stop(void); +extern int gnutv_dvb_locked(void); + +#endif |