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