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 | 
