1 /* tzap -- DVB-T zapping utility
2  */
3 
4 /*
5  * Added recording to a file
6  * arguments:
7  *
8  * -t	timeout (seconds)
9  * -o filename		output filename (use -o - for stdout)
10  * -s	only print summary
11  * -S	run silently (no output)
12  *
13  * Bernard Hatt 24/2/04
14  */
15 
16 
17 
18 #define _FILE_OFFSET_BITS 64
19 #define _LARGEFILE_SOURCE 1
20 #define _LARGEFILE64_SOURCE 1
21 
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <sys/ioctl.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <stdint.h>
28 #include <string.h>
29 #include <stdio.h>
30 #include <fcntl.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <signal.h>
34 
35 #include <linux/dvb/frontend.h>
36 #include <linux/dvb/dmx.h>
37 
38 #include "util.h"
39 
40 static char FRONTEND_DEV [80];
41 static char DEMUX_DEV [80];
42 static char DVR_DEV [80];
43 static int timeout_flag=0;
44 static int silent=0,timeout=0;
45 static int exit_after_tuning;
46 
47 #define CHANNEL_FILE "channels.conf"
48 
49 #define ERROR(x...)                                                     \
50 	do {                                                            \
51 		fprintf(stderr, "ERROR: ");                             \
52 		fprintf(stderr, x);                                     \
53 		fprintf (stderr, "\n");                                 \
54 	} while (0)
55 
56 #define PERROR(x...)                                                    \
57 	do {                                                            \
58 		fprintf(stderr, "ERROR: ");                             \
59 		fprintf(stderr, x);                                     \
60 		fprintf (stderr, " (%s)\n", strerror(errno));		\
61 	} while (0)
62 
63 
64 typedef struct {
65 	char *name;
66 	int value;
67 } Param;
68 
69 static const Param inversion_list [] = {
70 	{ "INVERSION_OFF", INVERSION_OFF },
71 	{ "INVERSION_ON", INVERSION_ON },
72 	{ "INVERSION_AUTO", INVERSION_AUTO }
73 };
74 
75 static const Param bw_list [] = {
76 	{ "BANDWIDTH_6_MHZ", BANDWIDTH_6_MHZ },
77 	{ "BANDWIDTH_7_MHZ", BANDWIDTH_7_MHZ },
78 	{ "BANDWIDTH_8_MHZ", BANDWIDTH_8_MHZ }
79 };
80 
81 static const Param fec_list [] = {
82 	{ "FEC_1_2", FEC_1_2 },
83 	{ "FEC_2_3", FEC_2_3 },
84 	{ "FEC_3_4", FEC_3_4 },
85 	{ "FEC_4_5", FEC_4_5 },
86 	{ "FEC_5_6", FEC_5_6 },
87 	{ "FEC_6_7", FEC_6_7 },
88 	{ "FEC_7_8", FEC_7_8 },
89 	{ "FEC_8_9", FEC_8_9 },
90 	{ "FEC_AUTO", FEC_AUTO },
91 	{ "FEC_NONE", FEC_NONE }
92 };
93 
94 static const Param guard_list [] = {
95 	{"GUARD_INTERVAL_1_16", GUARD_INTERVAL_1_16},
96 	{"GUARD_INTERVAL_1_32", GUARD_INTERVAL_1_32},
97 	{"GUARD_INTERVAL_1_4", GUARD_INTERVAL_1_4},
98 	{"GUARD_INTERVAL_1_8", GUARD_INTERVAL_1_8},
99 	{"GUARD_INTERVAL_AUTO", GUARD_INTERVAL_AUTO}
100 };
101 
102 static const Param hierarchy_list [] = {
103 	{ "HIERARCHY_1", HIERARCHY_1 },
104 	{ "HIERARCHY_2", HIERARCHY_2 },
105 	{ "HIERARCHY_4", HIERARCHY_4 },
106 	{ "HIERARCHY_NONE", HIERARCHY_NONE },
107 	{ "HIERARCHY_AUTO", HIERARCHY_AUTO }
108 };
109 
110 static const Param constellation_list [] = {
111 	{ "QPSK", QPSK },
112 	{ "QAM_128", QAM_128 },
113 	{ "QAM_16", QAM_16 },
114 	{ "QAM_256", QAM_256 },
115 	{ "QAM_32", QAM_32 },
116 	{ "QAM_64", QAM_64 },
117 	{ "QAM_AUTO", QAM_AUTO }
118 };
119 
120 static const Param transmissionmode_list [] = {
121 	{ "TRANSMISSION_MODE_2K", TRANSMISSION_MODE_2K },
122 	{ "TRANSMISSION_MODE_8K", TRANSMISSION_MODE_8K },
123 	{ "TRANSMISSION_MODE_AUTO", TRANSMISSION_MODE_AUTO }
124 };
125 
126 #define LIST_SIZE(x) sizeof(x)/sizeof(Param)
127 
parse_param(int fd,const Param * plist,int list_size,int * param)128 static int parse_param (int fd, const Param * plist, int list_size, int *param)
129 {
130 	char c;
131 	int character = 0;
132 	int _index = 0;
133 
134 	while (1) {
135 		if (read(fd, &c, 1) < 1)
136 			return -1;	/*  EOF? */
137 
138 		if ((c == ':' || c == '\n')
139 		    && plist->name[character] == '\0')
140 			break;
141 
142 		while (toupper(c) != plist->name[character]) {
143 			_index++;
144 			plist++;
145 			if (_index >= list_size)	 /*  parse error, no valid */
146 				return -2;	 /*  parameter name found  */
147 		}
148 
149 		character++;
150 	}
151 
152 	*param = plist->value;
153 
154 	return 0;
155 }
156 
157 
parse_int(int fd,int * val)158 static int parse_int(int fd, int *val)
159 {
160 	char number[11];	/* 2^32 needs 10 digits... */
161 	int character = 0;
162 
163 	while (1) {
164 		if (read(fd, &number[character], 1) < 1)
165 			return -1;	/*  EOF? */
166 
167 		if (number[character] == ':' || number[character] == '\n') {
168 			number[character] = '\0';
169 			break;
170 		}
171 
172 		if (!isdigit(number[character]))
173 			return -2;	/*  parse error, not a digit... */
174 
175 		character++;
176 
177 		if (character > 10)	/*  overflow, number too big */
178 			return -3;	/*  to fit in 32 bit */
179 	};
180 
181 	errno = 0;
182 	*val = strtol(number, NULL, 10);
183 	if (errno == ERANGE)
184 		return -4;
185 
186 	return 0;
187 }
188 
189 
find_channel(int fd,const char * channel)190 static int find_channel(int fd, const char *channel)
191 {
192 	int character = 0;
193 
194 	while (1) {
195 		char c;
196 
197 		if (read(fd, &c, 1) < 1)
198 			return -1;	/*  EOF! */
199 
200 		if ( '\n' == c ) /* start of line */
201 			character = 0;
202 		else if ( character >= 0 ) { /* we are in the namefield */
203 
204 			if (c == ':' && channel[character] == '\0')
205 				break;
206 
207 			if (toupper(c) == toupper(channel[character]))
208 				character++;
209 			else
210 				character = -1;
211 		}
212 	};
213 
214 	return 0;
215 }
216 
217 
218 static
try_parse_int(int fd,int * val,const char * pname)219 int try_parse_int(int fd, int *val, const char *pname)
220 {
221 	int err;
222 
223 	err = parse_int(fd, val);
224 
225 	if (err)
226 		ERROR("error while parsing %s (%s)", pname,
227 		      err == -1 ? "end of file" :
228 		      err == -2 ? "not a number" : "number too big");
229 
230 	return err;
231 }
232 
233 
234 static
try_parse_param(int fd,const Param * plist,int list_size,int * param,const char * pname)235 int try_parse_param(int fd, const Param * plist, int list_size, int *param,
236 		    const char *pname)
237 {
238 	int err;
239 
240 	err = parse_param(fd, plist, list_size, param);
241 
242 	if (err)
243 		ERROR("error while parsing %s (%s)", pname,
244 		      err == -1 ? "end of file" : "syntax error");
245 
246 	return err;
247 }
248 
check_fec(fe_code_rate_t * fec)249 static int check_fec(fe_code_rate_t *fec)
250 {
251 	switch (*fec)
252 	{
253 	case FEC_NONE:
254 		*fec = FEC_AUTO;
255 	case FEC_AUTO:
256 	case FEC_1_2:
257 	case FEC_2_3:
258 	case FEC_3_4:
259 	case FEC_5_6:
260 	case FEC_7_8:
261 		return 0;
262 	default:
263 		;
264 	}
265 	return 1;
266 }
267 
268 
parse(const char * fname,const char * channel,struct dvb_frontend_parameters * frontend,int * vpid,int * apid,int * sid)269 int parse(const char *fname, const char *channel,
270 	  struct dvb_frontend_parameters *frontend, int *vpid, int *apid,
271 	  int *sid)
272 {
273 	int fd;
274 	int err;
275 	int tmp;
276 
277 	if ((fd = open(fname, O_RDONLY | O_NONBLOCK)) < 0) {
278 		PERROR ("could not open file '%s'", fname);
279 		perror ("");
280 		return -1;
281 	}
282 
283 	if (find_channel(fd, channel) < 0) {
284 		ERROR("could not find channel '%s' in channel list", channel);
285 		return -2;
286 	}
287 
288 	if ((err = try_parse_int(fd, &tmp, "frequency")))
289 		return -3;
290 	frontend->frequency = tmp;
291 
292 	if ((err = try_parse_param(fd,
293 				   inversion_list, LIST_SIZE(inversion_list),
294 				   &tmp, "inversion")))
295 		return -4;
296 	frontend->inversion = tmp;
297 
298 	if ((err = try_parse_param(fd, bw_list, LIST_SIZE(bw_list),
299 				   &tmp, "bandwidth")))
300 		return -5;
301 	frontend->u.ofdm.bandwidth = tmp;
302 
303 	if ((err = try_parse_param(fd, fec_list, LIST_SIZE(fec_list),
304 				   &tmp, "code_rate_HP")))
305 		return -6;
306 	frontend->u.ofdm.code_rate_HP = tmp;
307 	if (check_fec(&frontend->u.ofdm.code_rate_HP))
308 		return -6;
309 
310 	if ((err = try_parse_param(fd, fec_list, LIST_SIZE(fec_list),
311 				   &tmp, "code_rate_LP")))
312 		return -7;
313 	frontend->u.ofdm.code_rate_LP = tmp;
314 	if (check_fec(&frontend->u.ofdm.code_rate_LP))
315 		return -7;
316 
317 	if ((err = try_parse_param(fd, constellation_list,
318 				   LIST_SIZE(constellation_list),
319 				   &tmp, "constellation")))
320 		return -8;
321 	frontend->u.ofdm.constellation = tmp;
322 
323 	if ((err = try_parse_param(fd, transmissionmode_list,
324 				   LIST_SIZE(transmissionmode_list),
325 				   &tmp, "transmission_mode")))
326 		return -9;
327 	frontend->u.ofdm.transmission_mode = tmp;
328 
329 	if ((err = try_parse_param(fd, guard_list, LIST_SIZE(guard_list),
330 				   &tmp, "guard_interval")))
331 		return -10;
332 	frontend->u.ofdm.guard_interval = tmp;
333 
334 	if ((err = try_parse_param(fd, hierarchy_list,
335 				   LIST_SIZE(hierarchy_list),
336 				   &tmp, "hierarchy_information")))
337 		return -11;
338 	frontend->u.ofdm.hierarchy_information = tmp;
339 
340 	if ((err = try_parse_int(fd, vpid, "Video PID")))
341 		return -12;
342 
343 	if ((err = try_parse_int(fd, apid, "Audio PID")))
344 		return -13;
345 
346 	if ((err = try_parse_int(fd, sid, "Service ID")))
347 	    return -14;
348 
349 	close(fd);
350 	return 0;
351 }
352 
353 
setup_frontend(int fe_fd,struct dvb_frontend_parameters * frontend)354 static int setup_frontend (int fe_fd, struct dvb_frontend_parameters *frontend)
355 {
356 	int ret;
357 	uint32_t mstd;
358 
359 	if (check_frontend(fe_fd, FE_OFDM, &mstd) < 0) {
360 		close(fe_fd);
361 		return -1;
362 	}
363 	ret = dvbfe_set_delsys(fe_fd, SYS_DVBT);
364 	if (ret) {
365 		PERROR("SET Delsys failed");
366 		return -1;
367 	}
368 	if (silent < 2)
369 		fprintf (stderr,"tuning to %i Hz\n", frontend->frequency);
370 
371 	if (ioctl(fe_fd, FE_SET_FRONTEND, frontend) < 0) {
372 		PERROR("ioctl FE_SET_FRONTEND failed");
373 		return -1;
374 	}
375 	return 0;
376 }
377 
do_timeout(int x)378 static void do_timeout(int x)
379 {
380 	(void)x;
381 	if (timeout_flag == 0) {
382 		timeout_flag = 1;
383 		alarm(2);
384 		signal(SIGALRM, do_timeout);
385 	} else {
386 		/* something has gone wrong ... exit */
387 		exit(1);
388 	}
389 }
390 
print_frontend_stats(int fe_fd,int human_readable)391 static void print_frontend_stats(int fe_fd, int human_readable)
392 {
393 	fe_status_t status;
394 	uint16_t snr, _signal;
395 	uint32_t ber, uncorrected_blocks;
396 
397 	ioctl(fe_fd, FE_READ_STATUS, &status);
398 	ioctl(fe_fd, FE_READ_SIGNAL_STRENGTH, &_signal);
399 	ioctl(fe_fd, FE_READ_SNR, &snr);
400 	ioctl(fe_fd, FE_READ_BER, &ber);
401 	ioctl(fe_fd, FE_READ_UNCORRECTED_BLOCKS, &uncorrected_blocks);
402 
403 	if (human_readable) {
404 		printf ("status %02x | signal %3u%% | snr %3u%% | ber %d | unc %d | ",
405 			status, (_signal * 100) / 0xffff, (snr * 100) / 0xffff, ber, uncorrected_blocks);
406 	} else {
407 		fprintf (stderr, "status %02x | signal %04x | snr %04x | ber %08x | unc %08x | ",
408 			status, _signal, snr, ber, uncorrected_blocks);
409 	}
410 
411 	if (status & FE_HAS_LOCK)
412 		fprintf(stderr,"FE_HAS_LOCK");
413 
414 	fprintf(stderr,"\n");
415 }
416 
417 static
monitor_frontend(int fe_fd,int human_readable)418 int monitor_frontend (int fe_fd, int human_readable)
419 {
420 	fe_status_t status;
421 	do {
422 	        ioctl(fe_fd, FE_READ_STATUS, &status);
423 		if (!silent)
424 			print_frontend_stats(fe_fd, human_readable);
425 		if (exit_after_tuning && (status & FE_HAS_LOCK))
426 			break;
427 		usleep(1000000);
428 	} while (!timeout_flag);
429 	if (silent < 2)
430 		print_frontend_stats (fe_fd, human_readable);
431 
432 	return 0;
433 }
434 
435 #define BUFLEN (188*256)
copy_to_file(int in_fd,int out_fd)436 static void copy_to_file(int in_fd, int out_fd)
437 {
438 	char buf[BUFLEN];
439 	int r;
440 	long long int rc = 0LL;
441 
442 	while (timeout_flag==0) {
443 		r=read(in_fd,buf,BUFLEN);
444 		if (r < 0) {
445 			if (errno == EOVERFLOW) {
446 				printf("buffer overrun\n");
447 				continue;
448 			}
449 			PERROR("Read failed");
450 			break;
451 		}
452 		if (write(out_fd,buf,r) < 0) {
453 			PERROR("Write failed");
454 			break;
455 		}
456 		rc+=r;
457 	}
458 
459 	if (silent<2) {
460 		fprintf(stderr, "copied %lld bytes (%lld Kbytes/sec)\n",rc,rc/(1024*timeout));
461 	}
462 }
463 
464 static char *usage =
465 	"usage:\n"
466 	"       tzap [options] <channel_name>\n"
467 	"         zap to channel channel_name (case insensitive)\n"
468 	"     -a number : use given adapter (default 0)\n"
469 	"     -f number : use given frontend (default 0)\n"
470 	"     -d number : use given demux (default 0)\n"
471 	"     -c file   : read channels list from 'file'\n"
472 	"     -x        : exit after tuning\n"
473 	"     -r        : set up /dev/dvb/adapterX/dvr0 for TS recording\n"
474 	"     -p        : add pat and pmt to TS recording (implies -r)\n"
475 	"     -s        : only print summary\n"
476 	"     -S        : run silently (no output)\n"
477 	"     -H        : human readable output\n"
478 	"     -F        : set up frontend only, don't touch demux\n"
479 	"     -t number : timeout (seconds)\n"
480 	"     -o file   : output filename (use -o - for stdout)\n"
481 	"     -h -?     : display this help and exit\n";
482 
483 
main(int argc,char ** argv)484 int main(int argc, char **argv)
485 {
486 	struct dvb_frontend_parameters frontend_param;
487 	char *homedir = getenv("HOME");
488 	char *confname = NULL;
489 	char *channel = NULL;
490 	int adapter = 0, frontend = 0, demux = 0, dvr = 0;
491 	int vpid, apid, sid, pmtpid = 0;
492 	int pat_fd, pmt_fd;
493 	int frontend_fd, audio_fd = 0, video_fd = 0, dvr_fd, file_fd;
494 	int opt;
495 	int record = 0;
496 	int frontend_only = 0;
497 	char *filename = NULL;
498 	int human_readable = 0, rec_psi = 0;
499 
500 	while ((opt = getopt(argc, argv, "H?hrpxRsFSn:a:f:d:c:t:o:")) != -1) {
501 		switch (opt) {
502 		case 'a':
503 			adapter = strtoul(optarg, NULL, 0);
504 			break;
505 		case 'f':
506 			frontend = strtoul(optarg, NULL, 0);
507 			break;
508 		case 'd':
509 			demux = strtoul(optarg, NULL, 0);
510 			break;
511 		case 't':
512 			timeout = strtoul(optarg, NULL, 0);
513 			break;
514 		case 'o':
515 			filename = strdup(optarg);
516 			record=1;
517 			/* fall through */
518 		case 'r':
519 			dvr = 1;
520 			break;
521 		case 'p':
522 			rec_psi = 1;
523 			break;
524 		case 'x':
525 			exit_after_tuning = 1;
526 			break;
527 		case 'c':
528 			confname = optarg;
529 			break;
530 		case 's':
531 			silent = 1;
532 			break;
533 		case 'S':
534 			silent = 2;
535 			break;
536 		case 'F':
537 			frontend_only = 1;
538 			break;
539 		case 'H':
540 			human_readable = 1;
541 			break;
542 		case '?':
543 		case 'h':
544 		default:
545 			fprintf (stderr, usage, argv[0]);
546 			return -1;
547 		};
548 	}
549 
550 	if (optind < argc)
551 		channel = argv[optind];
552 
553 	if (!channel) {
554 		fprintf (stderr, usage, argv[0]);
555 		return -1;
556 	}
557 
558 	snprintf(FRONTEND_DEV,
559 		 sizeof(FRONTEND_DEV),
560 		 "/dev/dvb/adapter%i/frontend%i",
561 	  	 adapter,
562 	  	 frontend);
563 
564 	snprintf(DEMUX_DEV,
565 		 sizeof(DEMUX_DEV),
566 		 "/dev/dvb/adapter%i/demux%i",
567 	  	 adapter,
568 	  	 demux);
569 
570 	snprintf(DVR_DEV,
571 		 sizeof(DVR_DEV),
572 		 "/dev/dvb/adapter%i/dvr%i",
573 	  	 adapter,
574 	  	 demux);
575 
576 	if (silent < 2)
577 		fprintf (stderr,"using '%s' and '%s'\n", FRONTEND_DEV, DEMUX_DEV);
578 
579 	if (!confname) {
580 		int len = strlen(homedir) + strlen(CHANNEL_FILE) + 18;
581 		if (!homedir)
582 			ERROR ("$HOME not set");
583 		confname = malloc (len);
584 		snprintf(confname, len, "%s/.tzap/%i/%s", homedir, adapter, CHANNEL_FILE);
585 		if (access (confname, R_OK))
586 			snprintf(confname, len, "%s/.tzap/%s", homedir, CHANNEL_FILE);
587 	}
588 	printf("reading channels from file '%s'\n", confname);
589 	memset(&frontend_param, 0, sizeof(struct dvb_frontend_parameters));
590 
591 	if (parse (confname, channel, &frontend_param, &vpid, &apid, &sid))
592 		return -1;
593 
594 	if ((frontend_fd = open(FRONTEND_DEV, O_RDWR | O_NONBLOCK)) < 0) {
595 		PERROR ("failed opening '%s'", FRONTEND_DEV);
596 		return -1;
597 	}
598 
599 	if (setup_frontend (frontend_fd, &frontend_param) < 0)
600 		return -1;
601 
602 	if (frontend_only)
603 		goto just_the_frontend_dude;
604 
605 	if (rec_psi) {
606 	    	pmtpid = get_pmt_pid(DEMUX_DEV, sid);
607 	    	if (pmtpid <= 0) {
608 			fprintf(stderr,"couldn't find pmt-pid for sid %04x\n",sid);
609 			return -1;
610 	    	}
611 
612 	    	if ((pat_fd = open(DEMUX_DEV, O_RDWR)) < 0) {
613 			perror("opening pat demux failed");
614 			return -1;
615 	    	}
616 	    	if (set_pesfilter(pat_fd, 0, DMX_PES_OTHER, dvr) < 0)
617 			return -1;
618 
619 	    	if ((pmt_fd = open(DEMUX_DEV, O_RDWR)) < 0) {
620 			perror("opening pmt demux failed");
621 			return -1;
622 	    	}
623 	    	if (set_pesfilter(pmt_fd, pmtpid, DMX_PES_OTHER, dvr) < 0)
624 			return -1;
625 	}
626 
627 	if ((video_fd = open(DEMUX_DEV, O_RDWR)) < 0) {
628 		PERROR("failed opening '%s'", DEMUX_DEV);
629 		return -1;
630 	}
631 
632 	if (silent<2)
633 		fprintf(stderr,"video pid 0x%04x, audio pid 0x%04x\n", vpid, apid);
634 
635 	if (set_pesfilter(video_fd, vpid, DMX_PES_VIDEO, dvr) < 0)
636 		return -1;
637 
638 	if ((audio_fd = open(DEMUX_DEV, O_RDWR)) < 0) {
639 		PERROR("failed opening '%s'", DEMUX_DEV);
640 		return -1;
641 	}
642 
643 	if (set_pesfilter(audio_fd, apid, DMX_PES_AUDIO, dvr) < 0)
644 		return -1;
645 
646 	signal(SIGALRM,do_timeout);
647 	if (timeout > 0)
648 		alarm(timeout);
649 
650 	if (record) {
651 		if (filename!=NULL) {
652 			if (strcmp(filename,"-")!=0) {
653 				file_fd = open(filename,O_WRONLY|0|O_CREAT,0644);
654 				if (file_fd<0) {
655 					PERROR("open of '%s' failed",filename);
656 					return -1;
657 				}
658 			} else {
659 				file_fd=1;
660 			}
661 		} else {
662 			PERROR("Record mode but no filename!");
663 			return -1;
664 		}
665 
666 		if ((dvr_fd = open(DVR_DEV, O_RDONLY)) < 0) {
667 	                PERROR("failed opening '%s'", DVR_DEV);
668 	                return -1;
669 	        }
670 		if (silent<2)
671 			print_frontend_stats(frontend_fd, human_readable);
672 
673 		copy_to_file(dvr_fd,file_fd);
674 
675 		if (silent<2)
676 			print_frontend_stats(frontend_fd, human_readable);
677 	} else {
678 just_the_frontend_dude:
679 		monitor_frontend(frontend_fd, human_readable);
680 	}
681 
682 	close(pat_fd);
683 	close(pmt_fd);
684 	close(audio_fd);
685 	close(video_fd);
686 	close(frontend_fd);
687 	return 0;
688 }
689