Ignore built executable files
[atscap.git] / atscap.c
blob639318bf9c081bd10ea6198636410449316e6db8
1 #define NAME "atscap"
2 #define AUTHOR "inkling"
3 #define EMAIL "inkling@users.sourceforge.net"
4 #define WEBPAGE "http://atscap.sourceforge.net"
5 #define COPYRIGHT "(C) 2004-2008"
6 #define LICENSE "GNU General Public License V2"
7 #define LASTEDIT "20080103"
8 #define LASTTIME __TIME__
9 #define LASTDATE __DATE__
10 #define VERSION "1.1"
11 #define VER "1.1"
13 /*****************************************************************************
15 * atscap.c (c) Copyright 2004-2008 by inkling@users.sourceforge.net
16 * ATSC Transport Stream Capture Application Program
18 * atscap is free software; you may only redistribute it and/or modify
19 * it under the terms of the GNU General Public License Version 2, or later,
20 * as published by the Free Software Foundation.
22 * atscap source code is distributed to you in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY OR SUPPORT; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please see the
25 * GNU General Public License Version 3 for more details.
27 * You should have received a copy of the GNU General Public License Version 2
28 * along with this program; if not, write me or the Free Software Foundation,
29 * Inc., at 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
31 *****************************************************************************/
34 ATSCap.c version 1.1.0
35 January 1, 2008
36 ATSC Capture Application Program
37 Copyright (c) 2004-2008 by inkling@users.sourceforge.net
41 /* COPYRIGHT FAIR USE INTENT DISCLAIMER ***************************************
42 * *
43 * IN LIGHT OF RECENT COURT DECISIONS THAT FOCUS ON INTENT, THE FOLLOWING *
44 * IS REQUIRED TO STATE UNEQUIVOCALLY WHAT IS THE INTENT OF THIS PROGRAM: *
45 * *
46 * THIS PROGRAM IS SOLELY FOR YOU TO EXERCISE YOUR FAIR USE RIGHTS. IT IS *
47 * NOT MEANT, NOR SHOULD IT BE USED BY YOU, TO ENGAGE IN ANY INFRINGEMENT *
48 * OF ANY OF THE COPYRIGHTS OF ANY COMPANIES, CORPORATIONS OR INDIVIDUALS. *
49 * *
50 * YOU MAY NOW PROCEED TO EXERCISE YOUR FAIR USE RIGHTS TO TIME-SHIFT *
51 * AS AFFIRMED IN THE FOLLOWING UNITED STATES SUPREME COURT DECISION: *
52 * *
53 * SONY CORP. V. UNIVERSAL CITY STUDIOS *
54 * 464 U.S. 417, 104 S. Ct 774, 78L. Ed 2d 574 (1984) *
55 * *
56 *****************************************************************************/
59 #warning using ... lines are PLATFORM SPECIFIC!
61 /* COMPILE OPTIONS *********************************************************/
62 /* You can override the Makefile defaults here */
64 /* This define enables use of coloration for the console. If you don't
65 * want any colors other than bright normal and grey on your console,
66 * then you should comment the following define.
67 * The Makefile sets this by default.
69 /* #define USE_ECMA48 */
71 /* This define enables use of translation to ASCII 7-bit for all events.
72 * This will provide some consistency in naming, for non-English events,
73 * for the capture file names and compares used to generate the EPG.
74 * This keeps the file name easy to type when capturing Mexican TV.
76 #define USE_ASCII_XLATE
78 /* This define enables echo of current capture over UDP multicast address.
79 * It is disabled by default. No one wants useless traffic on local net.
80 * Only those who actually want to use this feature should uncomment it.
81 * The Makefile will enable this.
83 /* #define USE_MCAST */
86 /* This define enables rebuilding of PAT+PMT on single Program captures.
87 * This is mostly to try to overcome limitations in xine audio selection.
88 * It would be better to fix xine audio select than try to do it here.
90 * You have to PICK the secondary audio from [v] VCT display, and be
91 * careful that you don't leave it set that way for regular programs.
92 * Secondary audio is supposed to be SAP, but very few stations use it.
94 * This doesn't work if your station sends 2 packet PMTs, like KQED-HD.
95 * It tries to change MPEG PAT + PMT to point to single program/audio,
96 * but it only processes one packet at a timeso 2 packet PMTs confuse it.
97 * You can verify if the output is correct with atscut -m3 file.ts
98 * This is very experimental and it probably will not work.
99 * Define it in the Makefile if you want to try it, not here.
101 /* #define USE_MPEG_REBUILD */
103 /* This define controls POSIX memory locking. Define this to use
104 * mlockall()/munlockall() if you run with a swapfile. This will prevent
105 * the delays caused by EPG or FIFO memory being swapped out and reloading.
106 * The latest changes for dynamic allocation may have broken this.
108 /* #define USE_MMAN */
110 /* Enable multi-threaded http server for default or -p capture directory
111 * Most people will want to use the web browser UI because it's simpler.
112 * The console UI is still available even if you define USE_WWW.
113 * This is currently experimental but it will be the preferred UI.
114 * The web pages will still be built, even with this undefined.
115 * Makefile sets this by default, but will need -w option to enable it.
117 /* #define USE_WWW */
119 /* Enable GNU backtrace for automatic segfault bug-report generation. */
120 /* USE_GNU_BACKTRACE_SCRIPT creates /dtv/atscap-pid.sh to get line info, */
121 /* otherwise it creates atscap-pid.bt to inspect with atscap-bt.sh */
122 /* #define USE_GNU_BACKTRACE */
123 /* #define USE_GNU_BACKTRACE_SCRIPT */
125 /* This sets the format of the server list at the top of the web interface.
126 * If you have multiple ATSC devices connected to one machine, leave this
127 * uncommented and put an entry for "dtv" in /etc/hosts. If you have multiple
128 * machines with one ATSC device connected to each machine, you will need
129 * to add /etc/hosts entries for "dtv0" "dtv1" "dtv2" "dtv3". Only 4 hosts
130 * are currently supported but you can add more by editing the source.
131 * See also WWW_DVBS for the number of servers to list. The inactive server
132 * highlighting in the HTML works only for multi-card/one-machine setup.
133 * TESTME:
134 * Multi-card will break if one device is cable and another is broadcast?
136 #define USE_MULTI_CARD
138 #define USE_AUTO_TSID
140 /* These defines should be in Makefile. Set some defaults if they are not. */
141 /* server defaults: # of servers, hostname, base port, server threads */
142 #ifndef WWW_DVBS
143 #define WWW_DVBS 1
144 #endif
146 #ifndef WWW_HOST
147 #define WWW_HOST "dtv"
148 #endif
150 #ifndef WWW_PORT
151 #define WWW_PORT 1380
152 #endif
154 #ifndef WWW_THREADS
155 #define WWW_THREADS 3
156 #endif
158 /* page colors, background, foreground, link, visited link, active link */
159 #define WWW_BG "#000000"
160 #define WWW_FG "#FFFFFF"
161 #define WWW_HLINK "#00FF00"
162 #define WWW_VLINK "#FF4040"
163 #define WWW_ALINK "#00FFBF"
165 /* Kern font pixel sizes for mozilla san-serif for CSS EPG tiles */
166 #define CSS_KERN_TLARGE 16
167 #define CSS_KERN_DLARGE 16
169 #define CSS_KERN_TSMALL 14
170 #define CSS_KERN_DSMALL 14
172 #define CSS_KERN_TTINY 13
174 /* tupari requested these to handle issues with dvico driver:
175 minimum signal strength for AOS and loop count to meet the
176 minimum delay for channel change, in milliseconds.
178 You may need to increase AOS_*_DELAY to 500 if driver doesn't
179 already wait 250ms. 8VSB sometimes get lock faster than QAM.
181 #define AOS_MIN_STRENGTH 45
182 #define AOS_MIN_LOOPS 5
183 #define AOS_MAX_LOOPS 3
185 #define AOS_QAM_DELAY 500
186 #define AOS_VSB_DELAY 250
188 #ifdef USE_POWERDOWN
189 #warning using POWERDOWN delay 300s
190 #define USE_POWERDELAY 300
191 #endif
193 /* CSS tiled EPG puts a small divider at this point */
194 #define PRIMETIME_HOUR 18
196 /* Use XHTML 1.0 vs HTML 4.01. XHTML is only for validator testing */
197 // #define USE_XHTML
198 /* only define this if layout can't be achieved through proper means */
199 // #define USE_BADML
201 /******************************************************* COMPILE OPTIONS END */
204 /******************* standard definitions and includes **********************/
206 /* __FUNCTION__ compiler debug macro gives type warnings on gcc 3.4.4 */
207 #define WHO (char *) __FUNCTION__
208 #define WHERE (int) __LINE__
209 #define WHOAMI dvrlog( LOG_INFO, "%s:%d", WHO, WHERE)
210 /* seem to be some differences in gcc representation of __FUNCTION__.
211 gcc 3.4.4 probably using unsigned char, and 3.3.5 signed char,
213 gcc 2.96 is unsigned char. Remove the labels with #define WHO ""
216 #define itlower(a) if ((a>='A')&&(a<='Z')) a |= 0x20
218 /* 5 minutes max in signal scan */
219 #define SIGNAL_SCAN_TIMEOUT 300
220 /* console timeout before autoguide starts, 60 for distro 5 for debug */
221 #define PROGRAM_GUIDE_TIMEOUT 60
223 /* try to find screen on path and execute it for window manager */
224 #define SCREEN
226 /* set these before includes to trigger different methods */
227 /* PORTING: */
228 #define GNULINUX
229 #ifdef GNULINUX
230 #define _GNU_SOURCE
231 /* fseeko ftello */
232 /* #define _LARGEFILE_SOURCE */
233 /* fseeko64 ftello64 */
234 /* #define _LARGEFILE64_SOURCE */
235 /* which one to use by default */
236 #define _FILE_OFFSET_BITS 64
237 #endif
239 /* linuxthreads docs say #define _REENTRANT for thread-safe glibc functions. */
240 #define _REENTRANT
241 #include <sys/select.h>
242 #include <pthread.h>
244 #include <ctype.h>
245 #include <stdio.h>
246 #include <unistd.h>
247 #include <stdlib.h>
248 #include <stdarg.h>
250 #include <fcntl.h>
251 #include <string.h>
252 #include <errno.h>
253 #include <getopt.h>
254 #include <sys/ioctl.h>
255 #include <termios.h>
256 #include <time.h>
257 #include <utime.h>
258 #include <sys/time.h>
259 #include <sched.h>
260 #include <sys/mount.h>
262 /* kb blinkies ioctl vals */
263 #include <linux/kd.h>
265 /* will need for signal actions, sigterm mostly */
266 #define USE_SIGNALS
267 #ifdef USE_SIGNALS
268 #warning using POSIX signals
269 #include <signal.h>
270 #endif
272 /* statfs to get free space */
273 // #include <sys/vfs.h>
274 #include <sys/statfs.h>
275 #include <math.h>
277 /* openlog syslog closelog syslog(3) */
278 #include <syslog.h>
280 #warning using Linux DVB
281 #include <linux/dvb/frontend.h>
282 #include <linux/dvb/dmx.h>
284 /* make atscap-udp */
285 #ifdef USE_WWW
286 #warning using TCP for HTTP
287 #include <sys/socket.h>
288 #include <netinet/in.h>
289 #include <net/if.h>
290 #include <arpa/inet.h>
291 #include <netdb.h>
293 #endif
295 #ifdef USE_GNU_BACKTRACE
296 #warning Using GNU backtrace()
297 #include <execinfo.h>
298 #ifndef __USE_GNU
299 #define __USE_GNU
300 #endif
301 #include <ucontext.h>
302 #define BTZ 32
303 #endif
306 /* non-break space, won't split on this */
307 #define NBSP "&nbsp;"
309 #ifdef USE_PNG
310 #warning using PNG for signal chart
311 #include <png.h>
312 #endif
314 #define SIG_PNG_W 50
315 #define SIG_PNG_H 50
316 #define SIG_PNG_B 4
317 #define SIG_PNG_COMP Z_BEST_COMPRESSION
318 /* #define SIG_PNG_COMP Z_NO_COMPRESSION */
320 #define WWW_HOST_MAX 256
322 /* INADDR_ANY is default */
323 #define WWW_IP "0.0.0.0"
324 /* Trying three threads to see what happens, seems OK. */
325 /* Class c is default netmask if not bound to INADDR_ANY. */
326 #define WWW_MASK 0xFFFFFF00
327 /* default directory. should be long enough */
328 #define WWW_ROOT "/dtv"
329 #define WWW_ROOT_MAX 256
331 /* use this for jumbo frames */
332 /* #define WWW_TCP_MAX 7000 */
333 /* but most people will use this. may cause weirdo problems */
334 #define WWW_TCP_MAX 1500
336 #include <glob.h>
338 #ifdef USE_MCAST
339 #warning using UDP for MULTICAST
340 #include <sys/socket.h>
341 #include <netinet/in.h>
342 #include <arpa/inet.h>
343 #endif
346 /* POSIX unistd.h sets _POSIX_MEMLOCK, unistd.h is above */
347 #ifdef _POSIX_MEMLOCK
348 #ifdef USE_MMAN
349 #include <sys/mman.h>
350 #warning using POSIX mlockall()/munlockall()
351 #endif
352 #endif
354 /****************************** END OF INCLUDES ******************************/
357 /* DEFINITIONS BEGIN ******************************************************/
359 #define ATSC_MGT 0xC7
360 #define ATSC_TVCT 0xC8
361 #define ATSC_CVCT 0xC9
362 #define ATSC_RRT 0xCA
363 #define ATSC_EIT 0xCB
364 #define ATSC_ETT 0xCC
365 #define ATSC_STT 0xCD
367 #define MPEG_PAT 0x00
368 #define MPEG_CAT 0x01
369 #define MPEG_PMT 0x02
371 /* this one is broken for KRIV FOX local station. enable for testing */
372 /* #define ENABLE_VN_ETT */
374 #define CAP_INFO 3
375 #define CAP_TIME 2
376 #define CAP_USER 1
377 #define CAP_NONE 0
379 /* O_STREAMING does nothing if not defined. it's for 2.4.x with RML patch.
380 fix compile problem when O_STREAMING (2.4 RML patch) not in fcntl.h
382 #ifndef O_STREAMING
383 #define O_STREAMING 0
384 #endif
385 /* defaults for file access via open() */
386 #define FILE_ROMODE O_RDONLY
387 #define FILE_RMODE O_RDWR
388 #define FILE_WMODE O_RDWR | O_CREAT | O_TRUNC | O_STREAMING
389 /* umask 022 makes this 0644 */
390 #define FILE_PERMS 0644
392 /* transport stream packet size */
393 #define TSPZ 188
395 /* transport stream sync byte */
396 #define TSYNC 0x47
398 /* transport stream packets per block */
399 #define TSPPB 21
401 /* 21 packets, just under 4k */
402 #define TSBUFZ (TSPZ * TSPPB)
403 /* Average packet rate, octet rate and bit rates for transport stream.
404 * 12898 comes from most frequently observed rate with better timing measure.
405 * It should also be enough for cable, since cable sends HD at similar rate.
406 * Nothing should depend on TSPRATE. TSORATE is used to calculate how much
407 * space the current timers on the list might use, but it is only a very
408 * rough estimate and way too high for SD content.
411 #define TSPRATE 12898
412 #define TSORATE (TSPRATE * TSPZ)
413 #define TSBRATE (TSORATE * 8)
415 /* use the larger define if filesystem is ext2 or ext3 or heavy XFS loading */
416 #define FIFOZ_CAP (TSPZ * TSPRATE * 4)
417 #define FIFOZ_EPG (TSPZ * 6450)
419 /* standard seconds in a standard day */
420 #define SECDAY 86400
421 /* standard hour */
422 #define SECHR 3600
423 #define SEC3HR 10800
424 /* standard minute */
425 #define SECMIN 60
426 #define SECDT 5
429 /* This program does not use curses, it uses macros for VT codes.
430 * The reason for this is that curses is a PITA and is not needed.
431 * Everyone should have xterm, and these codes work fine with it:
432 * man console_codes
435 /* see keys.c for a way to print out your edit pad keycodes */
437 /* console scan esc uses
438 kb remap of special esc keys to single byte values
439 move these around to anything above 127 to suit yourself
440 vt102 1980 standard stuff? maybe
442 KR_ is raw return byte string from read(0,...)
444 KB_ is cooked to something simpler for console_scan()
448 /* ESC [ A */
449 #define KR_UP 0x1B5B41
450 #define KB_UP 0xF1
451 /* ESC [ B */
452 #define KR_DN 0x1B5B42
453 #define KB_DN 0xF2
454 /* ESC [ C */
455 #define KR_RT 0x1B5B43
456 #define KB_RT 0xF3
457 /* ESC [ D */
458 #define KR_LF 0x1B5B44
459 #define KB_LF 0xF4
461 /* things get a bit odder here. may have to redefine if not xterm/aterm. */
462 /* ESC [ 1 ~ */
463 #define KR_HM 0x1B5B317E
464 #define KB_HM 0xF5
465 /* ESC [ 2 ~ */
466 #define KR_IN 0x1B5B327E
467 #define KB_IN 0xF6
468 /* ESC [ 3 ~ */
469 #define KR_DL 0x1B5B337E
470 #define KB_DL 0xF7
471 /* ESC [ 4 ~ */
472 #define KR_EN 0x1B5B347E
473 #define KB_EN 0xF8
474 /* ESC [ 5 ~ */
475 #define KR_PU 0x1B5B357E
476 #define KB_PU 0xF9
477 /* ESC [ 6 ~ */
478 #define KR_PD 0x1B5B367E
479 #define KB_PD 0xFA
481 /* these color definitions assume linux vt102/xterm + ecma48 support */
482 #define N "\000"
483 #define VT102
485 #ifdef VT102
486 #define CR "\033[K\r"
487 #define NL "\033[K\n"
488 #define CLS "\033[1;1H\033[2J"
490 /* clear to end of line */
491 #define CEL "\033[K"
492 /* clear entire display, keep cursor pos */
493 #define CED "\033[2J"
494 /* clear from start of screen to cursor */
495 #define CSC "\033[1J"
496 /* clear from cursor to end of screen */
497 #define CCE "\033[J"
498 /* home cursor */
499 #define HOM "\033[1;1H"
501 #define DCARC "\033[%d;%dH"
503 /* version+email then last cap status */
504 #define DCA0 HOM
505 /* use/free/total */
506 #define DCA_SPACE "\033[1;32H"
507 /* clock */
508 #define DCA_CLOCK "\033[1;59H"
509 /* header2 */
510 #define DCA_HEAD2 "\033[2;1H"
511 /* input field */
512 #define DCA_INPUT "\033[2;1H"
513 /* sig hdr field */
514 #define DCA_SIG "\033[2;18H"
515 /* device ID */
516 #define DCA_DEV "\033[2;72H"
517 /* signal scan line */
518 #define DCA_HEAD3 "\033[3;1H"
519 #define DCA_HEAD3C "\033[3;%dH"
520 /* timer header */
521 #define DCA_HEAD4 "\033[4;1H"
522 /* help header */
523 #define DCA_HELP "\033[5;1H"
524 /* tvct header */
525 #define DCA_VCT "\033[4;1H"
526 #define DCA_MGT "\033[4;1H"
527 #define DCA_CVC "\033[5;1H"
529 /* epg filter flags, bottom center */
531 /* program guide body start */
532 #define DCA_PG "\033[4;1H"
533 #define DCA_PAGE "\033[25;1H"
535 /* extra lines */
536 #define DCA_15_1 "\033[19;1H"
537 #define DCA_16_1 "\033[19;1H"
538 #define DCA_17_1 "\033[19;1H"
539 #define DCA_18_1 "\033[19;1H"
540 #define DCA_19_1 "\033[19;1H"
541 #define DCA_20_1 "\033[20;1H"
542 #define DCA_21_1 "\033[21;1H"
543 #define DCA_22_1 "\033[22;1H"
544 #define DCA_23_1 "\033[23;1H"
545 #define DCA_24_1 "\033[24;1H"
546 #define DCA_25_1 "\033[25;1H"
548 /* STT, full time it thinks it has from the stream */
549 #define DCA_STT "\033[25;52H"
551 /* these seem broken in older aterm, show status uses */
552 /* save and restore cursor position */
553 #define SCP "\033[s"
554 #define RCP "\033[u"
556 /* set cursor invisible and visible
557 #define SCI ""
558 #define SCV ""
560 #define SCI "\033[?25l"
561 #define SCV "\033[?25h"
563 /* dumbterm or printer use has been limited by direct cursor address */
564 #else
565 #define CR "\r"
566 #define NL "\n"
567 #define CLS "\014"
568 #endif
570 /* colors, these can be safely disabled without breaking the output
571 bright attribute for: black red green yellow blue magenta cyna white
574 /* need bright control if no color */
575 /* attributes, normal, blink, inverse */
576 #define BK "\033[1;30m"
577 #define BN "\033[0m"
578 #define BW "\033[1;37m"
579 #define BL "\033[5m"
580 #define BV "\033[7m"
581 /* bright white background for timer blink, inverts it so bright white text */
582 #define BBW "\033[47m"
583 #define BT "\033[1;41;37m"
585 #ifdef USE_ECMA48
586 #warning using ECMA-48 for console colors
587 #define BR "\033[1;31m"
588 #define BG "\033[1;32m"
589 #define BY "\033[1;33m"
590 #define BB "\033[1;34m"
591 #define BM "\033[1;35m"
592 #define BC "\033[1;36m"
593 #else
594 #define BR ""
595 #define BG BW
596 #define BY ""
597 #define BB ""
598 #define BM ""
599 #define BC ""
600 #endif
602 /* bar is 0-49, 2% increments */
603 #define SCALE_BAR 50
605 /* test for detached or quiet: fprintf if not detached and not quiet */
606 #define aprintf if ((arg_detach == 0) && (arg_quiet == 0)) fprintf
608 #define ahttp_log if (0 != arg_hlog) http_log
609 #define advrlog if (0 != arg_log) dvrlog
610 #define asyslog if (0 != arg_log) syslog
612 /* payload status */
613 #define PAY_READ 1
614 #define PAY_PARSE 2
615 #define PAY_GOOD 3
616 #define PAY_DROP 4
618 /* virtual channel elementary stream limit (first two, vid + main aud) */
619 #define VC_ES_LIMIT 2
621 /* cap info can only get so much from a bad stream. threshold and seconds */
622 /* SS is signal strength limit */
623 #define QOS_INFO_SS_LIMIT 40
624 #define QOS_INFO_TIMEOUT 30
625 #define PGM_INFO_TIMEOUT 60
627 /* ******************************************************* HTML definitions **/
628 #define DOCTYPE_XHTML "<!DOCTYPE html PUBLIC \042-//W3C//DTD XHTML 1.0 Transitional//EN\042 \042http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\042>\n"
629 #define DOCTYPE_HTML4 "<!DOCTYPE html PUBLIC \042-//W3C//DTD HTML 4.01 Transitional//EN\042 \042http://www.w3.org/TR/html4/loose.dtd\042>\n"
630 #define DOCTYPE_HTML3 "<!DOCTYPE html PUBLIC \042-//W3C//DTD HTML 3.2 Final//EN\042>\n"
632 /* uses these in non-http mode, too. yes i know, it's wide */
633 #define IMG_FMT "<img alt=\042%s\042 src=\042%s\042 width=%d height=%d hspace=%d vspace=%d border=%d />"
634 #define IMGA_FMT "<img alt=\042%s\042 src=\042%s\042 align=\042%s\042 width=\042%d\042 height=\042%d\042 hspace=\042%d\042 vspace=\042%d\042 border=%d />"
635 #define IMGB_FMT "<img alt=\042%s\042 src=\042/pg/img/%s\042 width=%d height=%d hspace=%d vspace=%d border=%d />"
636 #define IMGX_FMT "<img alt=\042%s\042 src=\042%s\042 border=\042%d\042 />"
637 #define IMG_EPG_FMT "<img alt=\042%s\042 src=\042/pg/img/%s\042 border=\0420\042 />"
638 #define IMG_EPG_CLASS "<img class=\042img_%s\042 alt=\042%s\042 src=\042/pg/img/%s\042 />"
639 #define IMG_EPG_CSS "<img class=\042%s\042 />"
641 /* table format: validator says align can only be: left center right */
642 #define TBL_FMT "<table border=%d cellspacing=%d cellpadding=%d align=\042%s\042 width=\042%s\042>\n"
643 #define TBL_BRD 0
645 /* table data format: validator says no halign: dump_epg_html after head */
646 #define TD_FMT "<td valign=\042%s\042 width=\042%s\042>"
647 //#define TD_FMT "<td halign=\042%s\042 width=\042%s\042>"
649 /* table header format: validator says no border or halign: build head html3 */
650 #define TH_FMT "<th valign=\042%s\042 width=\042%s\042>"
652 #define FN_FMT "<%s %s %s> %s\n"
654 #define HR_FMT "<hr width=\042%s\042>\n"
658 /********************************************** end of standard definitions */
661 /*** START GLOBAL ****************************************** START GLOBAL ***/
663 volatile long long vol_free = 0LL;
664 volatile long long vol_size = 0LL;
665 volatile long long cut_free = 0LL;
666 volatile long long cut_size = 0LL;
668 /* table of ecma-48 bright on black text colors */
669 char bc[8][8] =
671 BN,BB,BM,BR,BY,BG,BC,BW
674 char *boilerplate =
675 BG NAME " " VERSION " " LASTEDIT " is " COPYRIGHT " by " EMAIL "\n"
676 "New versions of " NAME " may be found here: " WEBPAGE "\n"
677 "The author of " NAME " may be reached here: " EMAIL "\n"
678 "\n"
679 BW NAME " is free GPL software. Any use" BR "*" BW " implies acceptance of the "
680 "terms of the\n"
681 " " LICENSE ". If you did NOT receive a copy of the Source and\n"
682 " the License, please report it to me or to the Free Software Foundation, Inc.,\n"
683 " at 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n"
684 "\n"
685 BR"*USE " NAME " AT YOUR OWN RISK. " NAME " IS DISTRIBUTED WITHOUT ANY WARRANTY.\n"
686 "\n";
688 /* every option program needs one of these */
689 char *usehelp =
690 "Usage:\n"
691 " "NAME" {-options}\n"
692 "\n"
693 " -h Help? This is it! Good Luck!\n"
694 "\n"
695 " -v Show version, boilerplate and buffer use\n"
696 "\n"
697 " -i # Input device #, default is 0 for /dev/dvb/adapter0\n"
698 "\n"
699 " -m Captures sequence and frame data to .tsx files.\n"
700 " This makes xtscut usable immediately after capture,\n"
701 " without having to generate the files with atscut -s\n"
702 " Data is generated only for single Program captures.\n"
703 "\n"
704 " -n Pad single Program cap with NULL packets. Keeps the\n"
705 " bitrate constant at 19.36Mb/s for hardware players.\n"
706 " NOTE: This uses 8.7G per hour no matter what.\n"
707 " If you have Roku HD1000 or similar, use this option.\n"
708 "\n"
709 " -c path Config file path override, default is /etc/atscap/.\n"
710 " Files are named atscap[0-3].conf to match\n"
711 " /dev/dvb/adapter[0-3]. See also: -i\n"
712 "\n"
713 " -s seconds Capture arg channel for seconds duration; example:\n"
714 " -s1800 19 gets chan 19 for 30 minutes.\n"
715 " -s ignores stdin.\n"
716 " See also: -o -p -i -q -d\n"
717 "\n"
718 " -o file Output filename override default of MMDD-hhmm-CH.ts\n"
719 " Mostly useful for single capture from cron script.\n"
720 " See also: -s -p\n"
721 "\n"
722 " -p path Capture output path override, default is /dtv/.\n"
723 " Remember you can symlink /dtv/ anywhere you like.\n"
724 " See also: -s -o\n"
725 "\n"
726 " -g seconds change default guide load/display timeout.\n"
727 "\n"
728 " -z nn [99] Zap cap if QoS drops below this value for a minute.\n"
729 "\n"
730 " -k Klutz Keyboardo mode, disables stdin. Use for demos.\n"
731 "\n"
732 " -F n Select frequency table from one of the following:\n"
733 " 8VSB: (default if -F not specified)\n"
734 " 0 USA ATSC center frequencies (-28615 Hz)\n"
735 " QAM256:\n"
736 " 1 USA Cable EAI-542 HRC center frequencies\n"
737 " 2 USA Cable EAI-542 IRC center frequencies\n"
738 " 3 USA Cable HRC center frequencies\n"
739 " 4 USA Cable IRC center frequencies\n"
740 " 5 USA Cable Standard center frequencies\n"
741 "\n"
742 " -l Log a lot more activity than usual; debug log\n"
743 " Default is to log version and capture info.\n"
744 " Be advised this is likely to cause buffer overruns\n"
745 " from the dvb device when this is enabled, unless\n"
746 " you send the log to a file on a RAM volume.\n"
747 "\n"
748 " -d Detach forks itself to a process without stdio.\n"
749 " Control is returned immediately to caller.\n"
750 " Use with -s if you don't want script to wait.\n"
751 " Use without -s and it enters timer daemon mode.\n"
752 "\n"
753 " -q Quiet disables stdout, but doesn't detach.\n"
754 " Control is not returned to caller until completion.\n"
755 " Use with -s if you want script to wait.\n"
756 "\n"
757 " -N No boilerplate/mem usage splash delay.\n"
758 " Makes it start immediately. This is not recommended\n"
759 " because the device needs a few seconds to reset.\n"
760 "\n"
761 " GNU SCREEN OPTIONS ARE CONSIDERED EXPERIMENTAL BUT WORK FINE.\n"
762 "\n"
763 " -W Use GNU screen to manage the session. Default: no.\n"
764 "\n"
765 " -D Use GNU screen to start detached. Default: no.\n"
766 "\n"
767 " -R Use GNU screen to re-attach session. Default: no.\n"
768 "\n\n\n"
769 "********************************* EXPERIMENTAL ****************************\n"
770 "\n"
771 " -E EPA Energy Star mode. EPG from RAM allows hard drive\n"
772 " spin-down timer to kick in and save some wattage.\n"
773 "\n"
774 " Use hdparm -C to inspect, hdparm -S60 to spin-down.\n"
775 " Cool-n-Quiet or SpeedStep should also be used.\n"
776 " tmpfs will be mounted at /dtv/ram, or -p path/ram\n"
777 " Spin down may not be supported by your IDE drive.\n"
778 " hdparm -S may be IDE and SATA specific.\n"
779 "\n"
780 "\n";
782 char usehelpx[] = ""
783 #ifdef USE_WWW
784 "\n"
785 "\n"
786 " THE REST ARE VERY EXPERIMENTAL. YOU HAVE BEEN WARNED.\n"
787 " THESE MAY NOT WORK AT ALL, OR CAUSE ALL KINDS OF PROBLEMS:\n"
788 "\n"
789 "***************** EXPERIMENTAL TCP HTTP WEB SERVER EXAMPLES ***************\n"
790 "\n"
791 " Put an IP address and name entry in /etc/hosts for this machine.\n"
792 " Put a service name and host mask in /etc/hosts.allow for local net.\n"
793 "\n"
794 " /etc/hosts:\n"
795 " 192.168.1.2 dtv\n"
796 "\n"
797 " /etc/hosts.allow:\n"
798 " atscap: 192.168.1.\n"
799 "\n"
800 " atscap -w ADDR[:MASK:PORT:THREADS]\n"
801 " Enable small HTTP server:\n"
802 " ADDR is required, dotted IP or hostname allowed\n"
803 " MASK defaults to Class C subnet, dotted or number of bits\n"
804 " PORT defaults to TCP port 80, is PORT+devnum for actual port\n"
805 " THREADS defaults to 5 threads\n"
806 "\n"
807 " atscap -i0 -w dtv:24:380:10\n"
808 " Start server on host dtv, TCP port 380\n"
809 " atscap -i1 -w dtv:24:380:10\n"
810 " Start server on host dtv, TCP port 381 (-i1 adds 1 to PORT)\n"
811 " The subnet mask will allow connects from 192.168.1.x only.\n"
812 " The server will start 10 threads for incoming connections.\n"
813 "\n"
814 " NOTE: xine http:// doesn't do Range: but mplayer reportedly does.\n"
815 "\n"
816 "\n"
817 #endif
818 #ifdef USE_MCAST
819 "************** EXPERIMENTAL ************* UDP MULTICAST SERVER ************\n"
820 "\n"
821 " -u ADDR:PORT\n"
822 "\n"
823 " Echo live stream to Multicast UDP address and port.\n"
824 " [u] key will toggle transmit off or on. Default is on.\n"
825 "\n"
826 " NOTES: This will probably only work with hardware decoders that\n"
827 " actually can deliver a certain frame rate without delays.\n"
828 " Use -n (nulls) for hardware deocders. They need them.\n"
829 "\n"
830 " You have to set the Virtual Program Number because most\n"
831 " hardware players can't understand multiplexed streams.\n"
832 "\n"
833 " IP UDP Multi-Casting Setup:\n"
834 " Build a kernel with IP multi-casting, but no tunnelling\n"
835 " or routing unless you need those for other uses.\n"
836 "\n"
837 " The problems with UDP are that it sends too fast, has no\n"
838 " handshake or throttling and no trick modes. Unless decoder\n"
839 " is fast enough to keep up, it will drop frames and look bad.\n"
840 "\n"
841 " UDP works live because ATSC dvb device is the throttle.\n"
842 " With TCP + software playback, the decoder is the throttle.\n"
843 "\n"
844 " Examples:\n"
845 "\n"
846 " For capture system [as root]:\n"
847 " # " NAME " -u 224.1.2.3:1234\n"
848 "\n"
849 " For player system(s) [as user]:\n"
850 " $ xine udp://224.1.2.3.4:1234\n"
851 "\n"
852 " Captures are still saved so you won't miss anything.\n"
853 " Real testing shows playback worse than http mode, but YMMV!\n"
854 " It looks about as bad as xine direct from dvb device.\n"
855 "\n"
856 "\n"
857 #endif
859 "************** EXPERIMENTAL **************** MISCELLANY *******************\n"
860 "\n"
861 " -a Enable Acquisition Of Signal check before capture.\n"
862 " This depends on your ATSC device driver having a\n"
863 " working signal strength function. Most devices\n"
864 " do NOT have a working signal strength function.\n"
865 "\n"
866 " -A Strict enforcement ATSC A/65b & MPEG reserved bits:\n"
867 " If [v] or [g] or [p] stop working with this\n"
868 " it means the station is a bit out of spec.\n"
869 " Default check is Table ID and protocol version.\n"
870 "\n"
871 " -r file.ts Read-only replay simulation of capture, 10x speed.\n"
872 " Mostly used for PSIP debug of atscut -kmgt samples.\n"
873 " Path in filename overrides /dtv/ or -p path.\n"
874 "\n"
875 " -e Extra error detail toggle off. This will not fill\n"
876 " the .html capture log with error per second detail.\n"
877 "\n"
878 "************** FILE SYSTEMS ***********************************************\n"
879 "\n"
880 " Ext2/3 and others will glitch the capture transfer if you delete\n"
881 " files from the capture directory during a current capture.\n"
882 " The ext2/3 unlink() routines are too inefficient for large files.\n"
883 "\n"
884 " XFS does not suffer this problem, which is why it is recommended.\n"
885 " If you can manage to move your drives to XFS you will see a benefit.\n"
886 "\n"
887 " The only drawback is XFS does not support bad blocks like ext2/3.\n"
888 " If you have drives with bad blocks, you should stay with ext2/3.\n"
889 "\n"
890 " If you can manage cutting from one physical volume to another,\n"
891 " you should not see any problems with single channel saturation.\n"
892 " I'm testing xfs/xfs, xfs/ext3 and ext3/ext3 to see how it goes.\n"
893 "\n"
894 " NOTE: USB storage: use separate controllers to make it faster.\n"
895 "\n"
896 "***************************************************************************\n"
897 "\n"
898 #if 0
899 "************************************ TODO *********************************\n"
900 "ENVIRONMENT VARIABLES: [TODO: need to write parse envars()...]\n"
901 "\n"
902 " ATSCAP_FIFO=n FIFO allocated n bytes instead of 9MB\n"
903 " ATSCAP_MCAST=addr:port IP multi-cast uses this address for UDP broadcast\n"
904 " ATSCAP_HTTP=addr:port http binds to this instead of inaddr_any\n"
905 " ATSCAP_THREADS=n http uses this many threads instead of 3\n"
906 "\n"
907 " If someone wants to write these, go ahead! It's far down my own list.\n"
908 "***************************************************************************\n"
909 #endif
910 "\n";
912 char *usehelpshort =
913 "\n"
914 "\n"
915 "EXAMPLE OPTION USAGE:\n"
916 "\n"
917 " Check device number X for stations and create "
918 "/etc/" NAME "/atscapX.conf:\n"
919 "\n"
920 " Where X is number 0-3 given with -i:\n"
921 " " NAME " -i0 -S (single card users or first card)\n"
922 " \042 -i1 -S (two card users)\n"
923 " \042 -i2 -S (three card users)\n"
924 " \042 -i3 -S (four card users)\n"
925 "\n"
926 " Multi-card users may save time by copying the first config file.\n"
927 "\n"
928 " Start a web server for a 253 IP subnet at 1.2.3.1, on port 1080\n"
929 " with a maximum of 5 threads to be used for http, on dvb0.dvr0.\n"
930 " This is assuming you have the ethernet card at 1.2.3.1.\n"
931 "\n"
932 "\t\t" NAME " -i0 -w 1.2.3.1:24:1080:5\n"
933 "\n"
934 " You can also use a hostname to access it behind NAT, but you will\n"
935 " need to update /etc/hosts with the hostname you will use, with\n"
936 " different ip addresses on the server and the client machines.\n"
937 "\n"
938 " This example will start the http server without a netmask. You\n"
939 " use TCP wrapper /etc/hosts.allow & hosts.deny to limit access.\n"
940 "\t\t" NAME " -i0 -w dtv:0:380:5\n"
941 "\n"
945 char *welcome =
946 BW "Welcome to " NAME " " VERSION "-" LASTEDIT
947 BC " compiled on " LASTDATE " at " LASTTIME "\n"
951 /* what you see when you hit ? */
952 char *timerskeyhelp =
953 BG "Manual Control: "BR"(locked out during capture, "BY"[o] overrides for Over Time"BR")\n\n"
954 BG " [v] Virtual Channel Guide "BW"[p] view Program Guide"BG" [g] Master Guide\n"
955 BG " = [ ] ' + arrows Scroll keys [e] toggle elapsed/ETA [s] toggle TS stats\n"
956 BG " [u] toggle UDP simulcasting [f] last capture log [^L] redraw console\n"
957 BR " [1-9/A-Z] channel hotkeys [ENTER] manual capture " BY "[BKSP] stop capture\n"
958 BR " [TAB] toggle timers/channels [w] toggle sig info [x] clear sig stats\n"
959 BR " [l] load Program Guide [a] toggle auto EPG [q] quit atscap\n"
960 BN "\n"
961 BM "Timer Control:\n\n"
962 " [t] set a timer [m] move timer [r] reschedule/remove\n"
963 " [d] delete a timer [!] clear all timers [z] zap current timer\n"
964 " [_] load configuration [+] save configuration\n"
965 BN "\n";
967 char *programskeyhelp =
968 "\n" CEL
969 BC"Program Guide Control:\n\n"
970 " [UP] / [=] [DN] / ['] scroll large list\n"
971 " [0] - [9] / [A] - [Z] jump to page 0 - 35\n"
972 " [t] add timer for event range, optional weekday bits\n"
973 " [m] show only events with names that match timers\n"
974 " [s] toggle spam status for event range\n"
975 " [a] toggle age past events\n"
976 " [h] toggle hide junk events\n"
977 " [n] toggle numeric debug display\n"
978 " [z] stop PG cap & exit PG\n"
979 " [x] clear PG\n"
980 " [p] exit PG\n"
981 "\n"
983 " EPG# (range) is specified by \042r1 - r2\042\n"
984 " Input abort for [s] and [t] is [ENTER] key (blank line)\n"
986 " [t] with weekday bits specifies permanent weekday timer.\n"
987 " [t] without weekday bits specifies volatile one-shot timer.\n"
989 ; /* programskeyhelp */
991 char *vchannelskeyhelp =
992 "\n"CEL
993 BC "Virtual Channel Guide Control:\n\n"
994 " [UP] or [=] [DN] or ['] scroll large list\n"
995 " [1] to [9] select Virtual Channel 1 through 9 to set Program\n"
996 " [c] select next Virtual Channel Program \n"
997 " [a] select next Audio Channel within Program\n"
998 " [r] reset to full capture: ATSC PSIP and all Virtual Channels\n"
999 " [d] toggle details for VCT and PMT descriptors\n"
1000 " [v] or [ENTER] exit Virtual Channel Guide\n"
1001 " [m] to toggle MPEG PAT/PMT Virtual Channels (vs ATSC)\n"
1002 " [ENTER] twice to start cap with current Program and Audio\n"
1003 "\n"
1004 " The Program and Audio selected will be used for manual captures.\n"
1005 BR" RED missing, " BY "YELLOW ignored, " BG "GREEN current\n"
1007 ; /* vchannelskeyhelp */
1009 char *mgtkeyhelp =
1010 "\n" CEL
1011 BG "Master Guide Table " BN "shows the relationship between\n"
1012 "PID (Packet ID) and ATSC table type. Without this guide,\n"
1013 "there is no Event Program Guide, since this table contains\n"
1014 "the only way to cross reference the ATSC EPG table types.\n"
1015 "\n"
1016 "It is scrollable with the up and down arrows or [=] ['] keys, but\n"
1017 "no other options are available for it since it is display only.\n"
1018 "Also, be aware that it is not kept after channel changes.\n"
1019 ; /* mgtkeyhelp */
1021 char *caplogkeyhelp =
1022 "\n" CEL
1023 BG " Capture Log " BN "shows the current capture error status.\n"
1024 "\n"
1025 "It is scrollable with the UP and DOWN arrows or [=] ['] keys, but\n"
1026 "no other options are available for it since it is display only.\n"
1027 "\n"
1028 "After the log has enough minutes to fill the screen, you can hit\n"
1029 "[f] key twice to start the display from current minute.\n"
1031 ; /* caplogkeyhelp */
1034 /* what you see when you pull up usage */
1035 char *keyhelp =
1036 " Manual Control ( *=locked out during capture, $=locked out during timer )\n\n"
1037 " PGUP/DN scroll keys for navigation\n"
1038 " Arrows more scroll keys for navigation\n"
1039 " = [ ] ' non edit scroll keys for navigation\n"
1040 "\n"
1041 " * 1-9/A-Z hotkey to conf channel list offset and lock onto it\n"
1042 " hit same key to toggle signal scan on/off\n"
1043 " * SP toggle channel lock | scan (spacebar key)\n"
1044 " * w toggle signal percent | peak | wavelen | freq | SNR\n"
1045 " * a toggle automatic signal scan timeout enable | disable\n"
1046 " * q quit (locked out during capture to prevent accidents)\n"
1047 " $ CR [ENTER] start VC capture of MPEG2 on selected VC\n"
1048 /* " $ \\ [BackSlash] start capture of full ATSC stream\n" */
1049 " $ BS [BackSpace Delete] (not ^H) stop capture\n"
1050 " TAB toggle timer/channel list display\n"
1051 " l load Program Guide for current channel\n"
1052 " o override in-progress timer, switch to manual, unlock DEL/BS\n"
1053 " x clear logged signal or capture statistics\n"
1054 " p Event Program Guide (EPG)\n"
1055 " v Virtual Channel Table display and VC + audio selection\n"
1056 " g Master Guide Table (MGT) display, scrollable list\n"
1057 " f capture log with overall errors per second, scrollable list\n"
1058 " s statistics display toggle\n"
1059 " z zap, stop capture and delete capture files, or stop info cap\n"
1060 " ctrl-L clear and refresh display\n"
1061 " ctrl-C abort " NAME ". stops capture or signal scan then exits\n"
1062 "\n"
1063 " Timer Control:\n\n"
1064 " t set a timer for channel, hh:mm, length mm, daybits and name\n"
1065 " m move timer to another config file\n"
1066 " r reschedule/remove a timer or range of timers\n"
1067 " d delete a timer or range of timers\n"
1068 " e toggle elaspsed or remaining time for current timer\n"
1069 " _ load configuration info (channels, timers) from file\n"
1070 " + save configuration info (channels, timers) to file\n"
1071 " ! delete all timers on list (does not save until next add)\n"
1072 "\n"
1073 " Program Guide Control (after [p] key):\n\n"
1074 " 0-9/A-Z jumps to page of program guide, 0 is first page\n"
1075 " t add Program Guide event or range of events to timer list\n"
1076 " a toggle display of aged-out Program Guide events\n"
1077 " h toggle display of hidden/unavailable/junk events\n"
1078 " m toggle display of events that match timer names\n"
1079 " s toggle three sort methods: pgm(default), date, name\n"
1080 " n toggle numeric debug display\n"
1081 " x clear program guide and all stored EIT/ETT payloads\n"
1082 " z zaps current capture, if any, and exits guide\n"
1083 " p exits guide\n"
1084 "\n"
1085 " Virtual Channel Control (after [v] key):\n\n"
1086 " c set the virtual channel number\n"
1087 " a set the audio stream number\n"
1088 " r reset to full capture mode\n"
1089 " d toggle VCT and PMT descriptor details\n"
1090 " m toggle MPEG/ATSC Virtual Channel info\n"
1091 ; /* keyhelp from -h command line option */
1095 /************************************************************* PACKET COUNTERS
1096 * ATSC packet counters in an easy to clear structure, 90 ints at least
1098 struct pkt_s {
1100 /* top level counters */
1101 int count; /* total ATSC TS packet count */
1102 int errors; /* total errors detected */
1104 /* transport counters */
1105 int tserrt; /* transport errors total */
1106 int tsccdt; /* countinuity counter duplicates total */
1107 int tsccet; /* continuity counter error total */
1108 int teiert; /* transport error indicator bit total */
1109 int tscert; /* scramble bit total */
1110 int tsprit; /* priority bit total */
1112 /* crc counters */
1113 int crcats; /* ATSC section CRC32 errors total */
1114 int crcstt; /* errors in STT */
1115 int crcmgt; /* errors in MGT */
1116 int crctvct; /* errors in VCT */
1117 int crccvct; /* errors in VCT */
1118 int crceit; /* errors in EIT */
1119 int crcett; /* errors in ETT section */
1120 int crcrrt; /* errors in RRT */
1122 int crcmp2; /* MPEG CRC32 errors total */
1123 int crcpat; /* errors in PAT */
1124 int crccat; /* errors in CAT */
1125 int crcpmt; /* errors in PMT */
1127 int crcpes; /* not sure if PES has crc, don't think so. checking */
1129 /* sync counters */
1130 int synert; /* sync errors total */
1131 int synct; /* sync total */
1132 int alignt; /* align count of non-zero align */
1133 int syncb; /* syncs in block */
1134 int synerr; /* error in block */
1135 int skips; /* bytes skipped from sync error */
1136 int align; /* sync alignment offset, 0 is aligned */
1138 /* packet type counters */
1139 int null; /* PID 1FFF is for NULL */
1140 int atsc; /* PID 1FFB is for ATSC */
1141 int mpeg2; /* PID 0000-0FFF 0=PAT 1=CAT */
1143 /* ATSC table type counters */
1144 int stt; /* STT System Time Table, single packet */
1145 int mgt; /* MGT Master Guide Table */
1146 int mgtp; /* MGT Master Guide Table */
1147 int tvct; /* TVCT Terrestrial Virtual Channel Table */
1148 int tvctp; /* TVCT Terrestrial Virtual Channel Table */
1149 int cvct; /* CVCT Cable Virtual Channel Table */
1150 int cvctp; /* CVCT Cable Virtual Channel Table */
1151 int eit; /* EIT Event Information Table */
1152 int eitp; /* EIT Event Information Table */
1153 int ett; /* ETT Extended Text Table */
1154 int ettp; /* ETT Extended Text Table */
1155 int rrt; /* RRT Region Rating Table */
1156 int rrtp; /* RRT Region Rating Table */
1158 /* not used */
1159 int dcct; /* DCCT Directed Channel Change Table */
1160 int dccsct; /* DCCSCT Directed Channel Change Selection Code Table */
1162 /* MPEG2 table type and PES counters */
1163 int pat; /* PAT Program Association Table unique */
1164 int patt; /* PAT Program Association Table total tables */
1165 int patp; /* PAT Program Association Table total packets */
1166 int cat; /* CAT Conditional Access Table (not used) */
1167 int catp; /* CAT Conditional Access Table (not used) */
1168 int pmt; /* PMT TS Program Map Table unique */
1169 int pmtt; /* PMT TS Program Map Table total tables */
1170 int pmtp; /* PMT TS Program Map Table total packets */
1171 int pes; /* PES (audio+video) count for single Program cap */
1172 int vid; /* MPEG video ES total count */
1173 int aud; /* AC3 audio ES total count */
1174 int vcvid; /* single virtual channel MPEG video ES count */
1175 int vcaud; /* single virtual channel AC3 audio ES count */
1176 int afcpcr; /* hold down VC video until AFC PCR seen sets this */
1177 int other; /* A/90 or other non audio/video packet */
1179 /* descriptor counts */
1180 int pmtrc; /* PMT has stupidity but not EIT like specs say */
1181 int eitrc; /* other place for stupidity according to spec but not seen */
1182 int rc; /* rc is a stupid waste of time */
1184 /* packet status */
1185 unsigned int psi; /* payload start indicator for current packet */
1186 unsigned int afc; /* adaptation field control */
1187 unsigned int afl; /* adaptation field length if afc=2 or 3 */
1188 unsigned int mpegtid; /* MPEG payload table id from last packet with psi=1 */
1189 unsigned int atsctid; /* ATSC payload table id from last packet with psi=1 */
1191 /* ATSC found status */
1192 int fstt; /* found stt */
1193 int fvct; /* found vct */
1194 int fmgt; /* found mgt */
1195 int feit; /* found eit */
1196 int fett; /* found ett */
1197 int frrt; /* not displaying? */
1199 /* MPEG2 found status */
1200 int fpat; /* found PAT program association table */
1201 int fcat;
1202 int fpmt; /* found PMT program map table after PAT */
1204 /* cap control */
1205 int keep; /* write current packet if PID's match */
1206 int kept; /* count writes */
1208 /* */
1209 int esec; /* current errors - previous errors */
1210 int perr;
1211 int audps; /* one shot payload start indicator */
1212 int vidps; /* same but not used, sequence_idx instead */
1215 struct pkt_s pkt;
1217 /* program guide masks and measurements */
1218 struct pgm_s {
1219 int chan; /* guide channel */
1220 int refresh; /* meta refresh for html */
1221 int mtime; /* uts last save epg */
1222 short idt; /* index total count (display only) */
1223 int idx; /* index un/filtered from above vars */
1224 short ett; /* description count */
1225 char age; /* 'a' guide filter: age */
1226 char hide; /* 'h' guide filter: spam */
1227 char find; /* 'm' guide filter: match timer */
1228 char search; /* 'f' guide filter: search */
1229 char etmids; /* 'n' numeric ETMIDs */
1230 char fail; /* guide fail reason */
1231 short pn;
1232 short daynum; /* day filter, only this EPG day if nz */
1235 /* TODO: OPTIMIZE THIS: some fields are over-large for 128 copies */
1236 /* NAMING FIXME: sig_s should be chan_s if you keep adding to it */
1237 /* signal structure */
1238 struct sig_s {
1239 char sid[8]; /* user supplied station id 3 chars */
1240 unsigned int freq; /* table lookup. set frequency */
1241 int freqr; /* driver frequency return */
1242 int fohz; /* calc frequency offset in hertz */
1243 unsigned int mode; /* VSB_8 or QAM_256 */
1245 char call[8]; /* user supplied callsign 7 chars */
1246 unsigned int ptc; /* channel index in ptc_*_frequencies */
1247 unsigned int chan; /* channel index in scanlist */
1248 short major; /* major channel # from vct, 10 bits */
1250 /* pointer to ecma48 color string for signal bar */
1251 char *sig_col;
1253 /* get signal lock sets these */
1254 unsigned int lock; /* nz if fe_status has FE_HAS_LOCK bit set */
1255 unsigned int lock_sum; /* how many signal locks, used for avg */
1256 unsigned short snr; /* SNR 8.8 value calc by driver */
1257 unsigned short snr_rms; /* RMS of 50 SNR samples */
1258 unsigned int strength; /* SNR % of 35dB, as computed by driver */
1259 unsigned int str_avg; /* Average of 50 strength % samples */
1260 unsigned int ber; /* bit errors */
1262 int vc; /* user selected virtual channel video # for manual cap */
1263 int va; /* user selected virtual channel audio # for manual cap */
1264 int pn; /* user selected program # for manual cap */
1265 int pnx; /* cable uses to translate program number in load epg */
1266 int pnxf; /* file# to download for EPG channel translate */
1267 int pgto; /* auto guide enable/disable */
1268 int pgmt; /* program guide mod time, -1 is skip */
1269 int pgrt; /* program guide refresh time */
1271 int cap_fail; /* last cap status for this channel */
1272 int cap_ets; /* last cap elapsed time in seconds */
1274 /* last zapped (or deleted) program guide timer start time */
1275 /* per PTC to prevent channel change autoguide restarting of zapped */
1277 int lzpgt;
1279 /* FIXME: needs pgm number too? */
1280 int lzpgm;
1282 /* -1 is unknown: b0 mgt b1 vct b2 ett b3 eit b4 rrt */
1283 unsigned char psip;
1285 int mgt_hrs; /* MGT EIT count * 3hrs */
1287 int smp[SIG_PNG_W]; /* strength sample history */
1288 int rmp[SIG_PNG_W]; /* SNR multi points */
1289 int smx; /* index to current sample */
1290 struct pkt_s pkt; /* packet counts for last capture */
1291 int ms; /* http stats mpeg toggle (volatile) */
1292 int as; /* http stats as toggle (volatile) */
1293 int pgx; /* how many events in last loaded epg? */
1294 /* station video h-res */
1295 int hres; /* detected video horizontal res */
1297 short yday; /* day filter, only this EPG day-1 if nz */
1300 /* glob sort list */
1301 struct globsort_s {
1302 char *name; /* file name pointer to globtmp allocation */
1303 time_t mt; /* file modification time */
1304 long long fz; /* file size */
1308 #ifdef USE_SIGNALS
1309 volatile int sig_val = 0;
1310 int sig_kill = 0;
1311 char *sig_text[32] = {
1312 "NULL", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS",
1313 "FPE", "KILL", "USR1", "SEGV", "USR2", "PIPE", "ALRM", "TERM",
1314 "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU", "URG",
1315 "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS"
1317 #endif
1319 char *mons[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1320 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1322 char *days[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
1323 char *daysl[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
1324 "Friday", "Saturday" };
1326 #define J_TEXT 0
1327 #define J_LARGE 1
1328 #define J_SMALL 2
1329 #define J_TINY 3
1330 #define J_GRID 4
1332 #define SGML_FMT 4
1333 #define SGML_MAX 8
1334 char *sgmls[SGML_MAX] = { "HTML", "HTML", "Big", "BIG",
1335 "Small", "SMALL", "Tiny", "TINY" };
1337 /* HTML4 + CSS stylesheet color definitions */
1338 #define COLORZ 9
1339 char *colors[COLORZ] = { "black", "red", "green", "blue",
1340 "cyan", "magenta", "yellow", "grey",
1341 "white" };
1343 /* HTML3 font color definitions */
1344 char *colors3[COLORZ] = {
1345 "color=\042#000000\042", /* black */
1346 "color=\042#FF0000\042", /* red */
1347 "color=\042#00FF00\042", /* green */
1348 "color=\042#FFFF00\042", /* yellow */
1349 "color=\042#808080\042", /* grey */
1350 "color=\042#00FFFF\042", /* cyan */
1351 "color=\042#FF00FF\042", /* magenta */
1352 "color=\042#FFFFFF\042", /* white */
1355 /* US broadcast */
1356 struct scanlist_s {
1357 struct sig_s sig; /* signal scan info */
1360 /* option -C sets pointer to broadcast or cable lists */
1361 int ptc_max = 70; /* default for ATSC 8VSB */
1362 struct scanlist_s ptc[128]; /* was *ptc for below */
1363 /* ptc gets populated by set_ptc_frequencies, based on -F option */
1364 int *frequencies; /* current frequency list for fine-tune reset */
1367 /* exported from dvb apps package by freqn.c. */
1369 /* TODO: center frequency is 28.615kc above what is in ptc_broadcast */
1370 /* TODO: test these frequencies below, also need QAM64 if anyone using it */
1372 /* users with stations still sending above 700MHz should define USE_700M */
1373 #define USE_700M
1374 #ifdef USE_700M
1375 #define ATSC_MAX 70
1376 #else
1377 #define ATSC_MAX 52
1378 #endif
1380 /* us-ATSC-center-frequencies-8VSB: 700MHz+ FCC auction limits to 2-51 */
1381 int ptc_atsc_center[ 128 ] = {
1382 0, 0, 57028615, 63028615, 69028615, 79028615, 85028615, 177028615, 183028615, 189028615,
1383 195028615, 201028615, 207028615, 213028615, 473028615, 479028615, 485028615, 491028615, 497028615, 503028615,
1384 509028615, 515028615, 521028615, 527028615, 533028615, 539028615, 545028615, 551028615, 557028615, 563028615,
1385 569028615, 575028615, 581028615, 587028615, 593028615, 599028615, 605028615, 611028615, 617028615, 623028615,
1386 629028615, 635028615, 641028615, 647028615, 653028615, 659028615, 665028615, 671028615, 677028615, 683028615,
1387 689028615, 695028615,
1389 #ifdef USE_700M
1390 /* FCC sold off this band. 52 - 69 will no longer exist for broadcast */
1391 701028615, 707028615, 713028615, 719028615, 725028615, 731028615, 737028615, 743028615,
1392 749028615, 755028615, 761028615, 767028615, 773028615, 779028615, 785028615, 791028615, 797028615, 803028615,
1393 #endif
1397 /* us-Cable-EIA-542-HRC-center-frequencies-QAM256 */
1398 int ptc_cable_eia542_hrc[ 128 ] = {
1399 0, 0, 73753600, 55752700, 61753000, 67753300, 77753900,
1400 83754200, 175758700, 181759000, 187759300, 193759600, 199759900, 205760200,
1401 211760500, 121756000, 127756300, 133756600, 139756900, 145757200, 151757500,
1402 157757800, 163758100, 169758400, 217760800, 223761100, 229761400, 235761700,
1403 241762000, 247762300, 253762600, 259762900, 265763200, 271763500, 277763800,
1404 283764100, 289764400, 295764700, 301765000, 307765300, 313765600, 319765900,
1405 325766200, 331766500, 337766800, 343767100, 349767400, 355767700, 361768000,
1406 367768300, 373768600, 379768900, 385769200, 391769500, 397769800, 403770100,
1407 409770400, 415770700, 421771000, 427771300, 433771600, 439771900, 445772200,
1408 451772500, 457772800, 463773100, 469773400, 475773700, 481774000, 487774300,
1409 493774600, 499774900, 505775200, 511775500, 517775800, 523776100, 529776400,
1410 535776700, 541777000, 547777300, 553777600, 559777900, 565778200, 571778500,
1411 577778800, 583779100, 589779400, 595779700, 601780000, 607780300, 613780600,
1412 619780900, 625781200, 631781500, 637781800, 643782100, 91754500, 97754800,
1413 103755100, 109775000, 115775000, 649782400, 655782700, 661783000, 667783300,
1414 673783600, 679783900, 685784200, 691784500, 697784800, 703785100, 709785400,
1415 715785700, 721786000, 727786300, 733786600, 739786900, 745787200, 751787500,
1416 757787800, 763788100, 769788400, 775788700, 781789000, 787789300, 793789600,
1417 799789900 };
1419 /* us-Cable-EIA-542-IRC-center_frequencies-QAM256 */
1420 int ptc_cable_eia542_irc[ 128 ] = {
1421 0, 0, 75012500, 57025000, 63012500, 69012500, 79012500,
1422 85012500, 177012500, 183012500, 189012500, 195012500, 201012500, 207012500,
1423 213012500, 123012500, 129012500, 135012500, 141012500, 147012500, 153012500,
1424 159012500, 165012500, 171012500, 219012500, 225012500, 231012500, 237012500,
1425 243012500, 249012500, 255012500, 261012500, 267012500, 273012500, 279012500,
1426 285012500, 291012500, 297012500, 303012500, 309012500, 315012500, 321012500,
1427 327012500, 333025000, 339012500, 345012500, 351012500, 357012500, 363012500,
1428 369012500, 375012500, 381012500, 387012500, 393012500, 399012500, 405012500,
1429 411012500, 417012500, 423012500, 429012500, 435012500, 441012500, 447012500,
1430 453012500, 459012500, 465012500, 471012500, 477012500, 483012500, 489012500,
1431 495012500, 501012500, 507012500, 513012500, 519012500, 525012500, 531012500,
1432 537012500, 543012500, 549012500, 555012500, 561012500, 567012500, 573012500,
1433 579012500, 585012500, 591012500, 597012500, 603012500, 609012500, 615012500,
1434 621012500, 627012500, 633012500, 639012500, 645012500, 93012500, 99012500,
1435 105012500, 111012500, 117012500, 651012500, 657012500, 663012500, 669012500,
1436 675012500, 681012500, 687012500, 693012500, 699012500, 705012500, 711012500,
1437 717012500, 723012500, 729012500, 735012500, 741012500, 747012500, 753012500,
1438 759012500, 765012500, 771012500, 777012500, 783012500, 789012500, 795012500,
1439 801012500 };
1441 /* us-Cable-HRC-center-frequencies-QAM256 */
1442 int ptc_cable_hrc[ 128 ] = {
1443 0, 0, 73753600, 55752700, 61753000, 67753300, 77753900,
1444 83754200, 175758700, 181759000, 187759300, 193759600, 199759900, 205760200,
1445 211760500, 121756000, 127756300, 133756600, 139756900, 145757200, 151757500,
1446 157757800, 163758100, 169758400, 217760800, 223761100, 229761400, 235761700,
1447 241762000, 247762300, 253762600, 259762900, 265763200, 271763500, 277763800,
1448 283764100, 289764400, 295764700, 301765000, 307765300, 313765600, 319765900,
1449 325766200, 331766500, 337766800, 343767100, 349767400, 355767700, 361768000,
1450 367768300, 373768600, 379768900, 385769200, 391769500, 397769800, 403770100,
1451 409770400, 415770700, 421771000, 427771300, 433771600, 439771900, 445772200,
1452 451772500, 457772800, 463773100, 469773400, 475773700, 481774000, 487774300,
1453 493774600, 499774900, 505775200, 511775500, 517775800, 523776100, 529776400,
1454 535776700, 541777000, 547777300, 553777600, 559777900, 565778200, 571778500,
1455 577778800, 583779100, 589779400, 595779700, 601780000, 607780300, 613780600,
1456 619780900, 625781200, 631781500, 637781800, 643782100, 91754500, 97754800,
1457 103755100, 109755400, 115755700, 649782400, 655782700, 661783000, 667783300,
1458 673783600, 679783900, 685784200, 691784500, 697784800, 703785100, 709785400,
1459 715785700, 721786000, 727786300, 733786600, 739786900, 745787200, 751787500,
1460 757787800, 763788100, 769788400, 775788700, 781789000, 787789300, 793789600,
1461 799789900 };
1463 /* us-Cable-IRC-center-frequencies-QAM256 */
1464 int ptc_cable_irc[ 128 ] = {
1465 0, 0, 75000000, 57000000, 63000000, 69000000, 79000000,
1466 85000000, 177000000, 183000000, 189000000, 195000000, 201000000, 207000000,
1467 213000000, 123000000, 129000000, 135000000, 141000000, 147000000, 153000000,
1468 159000000, 165000000, 171000000, 219000000, 225000000, 231000000, 237000000,
1469 243000000, 249000000, 255000000, 261000000, 267000000, 273000000, 279000000,
1470 285000000, 291000000, 297000000, 303000000, 309000000, 315000000, 321000000,
1471 327000000, 333000000, 339000000, 345000000, 351000000, 357000000, 363000000,
1472 369000000, 375000000, 381000000, 387000000, 393000000, 399000000, 405000000,
1473 411000000, 417000000, 423000000, 429000000, 435000000, 441000000, 447000000,
1474 453000000, 459000000, 465000000, 471000000, 477000000, 483000000, 489000000,
1475 495000000, 501000000, 507000000, 513000000, 519000000, 525000000, 531000000,
1476 537000000, 543000000, 549000000, 555000000, 561000000, 567000000, 573000000,
1477 579000000, 585000000, 591000000, 597000000, 603000000, 609000000, 615000000,
1478 621000000, 627000000, 633000000, 639000000, 645000000, 93000000, 99000000,
1479 105000000, 111000000, 117000000, 651000000, 657000000, 663000000, 669000000,
1480 675000000, 681000000, 687000000, 693000000, 699000000, 705000000, 711000000,
1481 717000000, 723000000, 729000000, 735000000, 741000000, 747000000, 753000000,
1482 759000000, 765000000, 771000000, 777000000, 783000000, 789000000, 795000000,
1483 801000000 };
1485 /* us-Cable-Standard-center-frequencies-QAM256 */
1486 int ptc_cable_std[ 128 ] = {
1487 0, 0, 57000000, 63000000, 69000000, 79000000, 85000000,
1488 177000000, 183000000, 189000000, 195000000, 201000000, 207000000, 213000000,
1489 123012500, 129012500, 135012500, 141000000, 147000000, 153000000, 159000000,
1490 165000000, 171000000, 219000000, 225000000, 231012500, 237012500, 243012500,
1491 249012500, 255012500, 261012500, 267012500, 273012500, 279012500, 285012500,
1492 291012500, 297012500, 303012500, 309012500, 315012500, 321012500, 327012500,
1493 333025000, 339012500, 345012500, 351012500, 357012500, 363012500, 369012500,
1494 375012500, 381012500, 387012500, 393012500, 399012500, 405000000, 411000000,
1495 417000000, 423000000, 429000000, 435000000, 441000000, 447000000, 453000000,
1496 459000000, 465000000, 471000000, 477000000, 483000000, 489000000, 495000000,
1497 501000000, 507000000, 513000000, 519000000, 525000000, 531000000, 537000000,
1498 543000000, 549000000, 555000000, 561000000, 567000000, 573000000, 579000000,
1499 585000000, 591000000, 597000000, 603000000, 609000000, 615000000, 621000000,
1500 627000000, 633000000, 639000000, 645000000, 93000000, 99000000, 105000000,
1501 111025000, 117025000, 651000000, 657000000, 663000000, 669000000, 675000000,
1502 681000000, 687000000, 693000000, 699000000, 705000000, 711000000, 717000000,
1503 723000000, 729000000, 735000000, 741000000, 747000000, 753000000, 759000000,
1504 765000000, 771000000, 777000000, 783000000, 789000000, 795000000, 801000000
1507 char *header_version = BG NAME VERSION "-" LASTEDIT " " BN;
1509 char *header_channels =
1510 BN "k Chn Call Net" BN " SIG% "
1511 BN " " BB ".JUNK " BM "-ECHO " BR "+SKIP "
1512 BY "=POOR " BG "#FAIR " BC "$GOOD " BW "!BEST" BN CEL;
1514 char *header_capture =
1515 "%sNet PTC Time %s"
1516 BN " SIG%%"
1517 BC " FIFO/max%%"
1518 BG " QoS/avg%%"
1519 BG " Error#"
1520 BW " TS PKT#"
1521 BG " MPG#" SCI CEL;
1523 char *header_timers =
1524 BY "T # " BG "Status Ch Name "
1525 "Day Date Time Len " BN "SMTWTFS " BC "Nextday"
1526 BN CEL SCI;
1528 /* filled in with 3 colors and "sid.chan call" by show program guide */
1529 char *header_guide =
1530 BG "%sEPG# "
1531 BW "Pgm#" BN " "
1532 BG "Day Time"BN " "
1533 BR "Len "
1534 BM "Events" BN " on "
1535 BY "%-15s"
1536 BR "[Rating] "
1537 BG "Description" BN SCI;
1539 /* each line is 2% */
1540 char sigbar[ SCALE_BAR + 1] [ SCALE_BAR + 1] = {
1542 ".",
1543 "..",
1544 "...",
1545 "....",
1546 "-...-",
1547 "--..--",
1548 "---.---",
1549 "--------",
1550 "+-------+",
1551 "++------++",
1552 "+++-----+++",
1553 "++++----++++",
1554 "+++++---+++++",
1555 "++++++--++++++",
1556 "+++++++-+++++++",
1557 "++++++++++++++++",
1558 "=+++++++=+++++++=",
1559 "==++++++==++++++==",
1560 "===+++++===+++++===",
1561 "====++++====++++====",
1562 "=====+++=====+++=====",
1563 "======++======++======",
1564 "=======+=======+=======",
1565 "========================",
1566 "#=======#=======#=======#",
1567 "##======##======##======##",
1568 "###=====###=====###=====###",
1569 "####====####====####====####",
1570 "#####===#####===#####===#####",
1571 "######==######==######==######",
1572 "#######=#######=#######=#######",
1573 "################################",
1574 "$#######$#######$#######$#######$",
1575 "$$######$$######$$######$$######$$",
1576 "$$$#####$$$#####$$$#####$$$#####$$$",
1577 "$$$$####$$$$####$$$$####$$$$####$$$$",
1578 "$$$$$###$$$$$###$$$$$###$$$$$###$$$$$",
1579 "$$$$$$##$$$$$$##$$$$$$##$$$$$$##$$$$$$",
1580 "$$$$$$$#$$$$$$$#$$$$$$$#$$$$$$$#$$$$$$$",
1581 "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
1582 "!$$$$$$$!$$$$$$$!$$$$$$$!$$$$$$$!$$$$$$$!",
1583 "!!$$$$$$!!$$$$$$!!$$$$$$!!$$$$$$!!$$$$$$!!",
1584 "!!!$$$$$!!!$$$$$!!!$$$$$!!!$$$$$!!!$$$$$!!!",
1585 "!!!!$$$$!!!!$$$$!!!!$$$$!!!!$$$$!!!!$$$$!!!!",
1586 "!!!!!$$$!!!!!$$$!!!!!$$$!!!!!$$$!!!!!$$$!!!!!",
1587 "!!!!!!$$!!!!!!$$!!!!!!$$!!!!!!$$!!!!!!$$!!!!!!",
1588 "!!!!!!!$!!!!!!!$!!!!!!!$!!!!!!!$!!!!!!!$!!!!!!!",
1589 "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
1590 "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
1591 "!!!!!!!!!!!!!!!!!!!!! 100% !!!!!!!!!!!!!!!!!!!!!!!",
1594 /* frame rates x100 */
1595 int frame_rate_table[9] =
1596 { -1, 2397, 2400, 2500, 2997, 3000, 5000, 5994, 6000 };
1598 struct dvb_frontend_info fe_info; /* front end info w/ name */
1599 struct dmx_pes_filter_params dmx_filter; /* front end demux filter */
1600 struct dvb_frontend_parameters fe_param; /* front end parameters */
1601 fe_status_t fe_status; /* front end status */
1602 unsigned short fe_strength; /* front end strength */
1603 unsigned int fe_ber; /* front end bit errors */
1605 pthread_t fifo_input_thread; /* input thread ptr */
1606 pthread_attr_t fifo_input_attr; /* input thread attributes */
1607 struct sched_param fifo_input_param; /* input sched parameters */
1609 pthread_t fifo_output_thread; /* output thread ptr */
1610 pthread_attr_t fifo_output_attr; /* output thread attributes */
1611 struct sched_param fifo_output_param; /* output thread parameters */
1613 /* fifo struct */
1615 /* combination smp/up support via mutex locks */
1616 struct fifo_s {
1617 pthread_mutex_t mutex; /* mutex lock for SMP */
1618 volatile size_t filled; /* how much FIFO currently has */
1619 unsigned char * volatile read_ptr; /* fifo read/file write output ptr */
1620 unsigned char * volatile write_ptr; /* device read/fifo write input ptr */
1622 unsigned char *buffer; /* constant after init, not volatile */
1623 unsigned char *end_ptr; /* constant after init, not volatile */
1624 size_t max_bytes; /* constant after init, not volatile */
1627 /* pointer to a fifo. buffer is created by fifo init malloc */
1628 struct fifo_s ts_fifo;
1630 /* DEFAULTS: first card and /dtv/ directory for config and output */
1631 char cfg_name[256] = "atscap0.conf"; /* new default config name */
1632 char cfg_path[256] = "/etc/atscap/"; /* new default config directory */
1633 char out_name[256] = ""; /* capture will change */
1634 char out_path[256] = "/dtv/"; /* default output path */
1635 char cut_path[256] = "/dtv/cut/"; /* default edit output path */
1636 char ram_path[256] = ""; /* -E sets this to "ram/" */
1637 char log_path[256] = "/dtv/"; /* -p, -E overrides this */
1638 char del_name[256] = ""; /* file to delete after cap */
1640 /* capture log */
1641 char caplog_name[256];
1642 char dvrlog_name[256];
1643 char syslog_name[256];
1645 /* serialize dvrlog */
1646 #define LOG_LINES 1024
1647 #define LOG_CHARS 1024
1648 FILE *dvrlog_fd; /* log file ptr */
1649 pthread_t dvrlog_thread; /* log thread ptr */
1650 pthread_attr_t dvrlog_attr; /* log thread attributes */
1651 struct sched_param dvrlog_param; /* log sched params NOT USED */
1652 pthread_mutex_t dvrlog_mutex; /* log thread mutex */
1653 char dvrlog_list[ LOG_LINES * LOG_CHARS ]; /* log list */
1654 volatile int dvrlog_idx; /* list length */
1656 /* serialize http index_html */
1657 pthread_mutex_t index_mutex;
1658 int idx_sort = 2; /* default is sort by date descending order */
1660 /* serialize test epgs */
1661 pthread_mutex_t epg_mutex;
1663 /* serialize dump cap log */
1664 pthread_mutex_t caplog_mutex;
1666 /* cable uses this for guide server */
1667 char epg_srv[ 255 ];
1669 /* serialize build sig png */
1670 pthread_mutex_t png_mutex;
1672 /* serialize dump epg grid */
1673 pthread_mutex_t grid_mutex;
1676 #ifdef USE_GNU_BACKTRACE
1677 pthread_mutex_t bt_mutex;
1678 #endif
1680 /* dvb names, adapter0 set by arg_devnum */
1681 char fe_name[256] = "/dev/dvb0.frontend0"; /* default */
1682 char dmx_name[256] = "/dev/dvb0.demux0"; /* dvb */
1683 char dvr_name[256] = "/dev/dvb0.dvr0"; /* names */
1685 char in_name[256]; /* same as dvr name or -r file name */
1687 /* last 188 are used for alignment with oversized read */
1688 unsigned char ts_buf[TSBUFZ+TSPZ]; /* device input buffer */
1689 unsigned char out_buf[TSBUFZ+TSPZ]; /* file output buffer */
1690 unsigned char cut_buf[TSBUFZ+TSPZ]; /* parsed output buffer */
1691 unsigned char nul_buf[TSPZ]; /* a single null packet */
1692 char chan_name[ 16 ]; /* current VCT channel name */
1694 int fe_file = -1; /* fd for dvb front end control */
1695 int dmx_file = -1; /* fd for dvb de-multiplex control */
1696 int dvr_file = -1; /* fd for dvb de-multiplex output */
1697 int in_file = -1; /* fd for dvr[0...3] */
1698 int out_file = -1; /* fd for output */
1700 unsigned char blinky = 0;
1701 volatile int block_widx = 0; /* block write index */
1703 char fe_text1[80]; /* device name */
1704 char fe_text2[80]; /* device supports */
1705 char fe_text3[80]; /* device supports */
1707 /* channel signal scan control, another struct candidate */
1709 load cfg sets this list and count from the C lines in conf file
1710 80x25: 2 line header, leaves 22 lines and a blank line for bottom
1712 int scan_list[ 128 ]; /* ATSC and cable channel range */
1713 int scan_idx = 0; /* shouldn't go past ptc_max */
1715 int scan_pos = 0; /* where in scan_list[...] are we? */
1717 /* flow control flags for channel scan */
1718 int scan_one = 0; /* make scan loop stay on this channel */
1719 int scan_next = 0; /* what channel to jump to next? */
1720 int scan_sig = 0; /* do signal scan or wait for timers only */
1721 int scan_rate = 9; /* get signal lock changes this */
1722 int scan_freq = 0; /* set channel one shot inhibit */
1723 int scan_png = ~0; /* sig scan chart size, 0 is 24 nz is 48 */
1724 #ifdef USE_DVB_EXPERIMENTAL
1725 int scan_snr = ~0; /* sig display for web is SNR RMS if nz */
1726 int scan_khz = ~0; /* sig display for web has +/- KHz for -x */
1727 #else
1728 int scan_snr = 0; /* sig display for web is SNR RMS if nz */
1729 int scan_khz = 0; /* sig display for web has +/- KHz for -x */
1730 #endif
1732 /* http flags for output control */
1733 int web_legend = 0; /* show legend: img + desc text */
1734 int web_config = ~0; /* show config options */
1735 int web_stats = 0; /* show time, uptime and transport stats */
1737 /* TODO: need web versions of vct selection and mgt display screens */
1738 int web_vctsel = 0; /* show vct selector */
1739 int web_vctdesc = 0; /* show vct descriptors */
1740 int web_mgtdisp = 0; /* show master guide table */
1742 /* int scan_pkt = 0; */ /* 'y' key, sets quiet mode and dumps pkts */
1744 /* capture control flags */
1745 volatile int cap_now = 0; /* start cap threads now */
1746 int cap_done = 0; /* signals end of ts capture */
1747 int cap_prev = 0; /* ts cap mode for dump cap stats */
1748 int cap_prvc = 0; /* ts cap vc for dump cap stats */
1749 int cap_prpn = 0; /* ts cap program for dump cap stats */
1750 int cap_pchn = 0; /* ts cap channel number for dump epg */
1751 volatile int cap_chan = 0; /* from which channel to cap */
1752 int cap_zap = 0; /* set if current cap gets removed */
1753 int cap_zapped = 0; /* set if previous cap was removed */
1754 int cap_etime = 0; /* set if timer shows elapsed not left */
1755 int cap_zc = 0; /* ATSC System Time channel */
1756 int cap_zd = 0; /* ATSC System Time delta to match ntp */
1757 int cap_zto = 0; /* timeout hold down one shot for STT */
1758 int cap_frames = 0; /* copy arg_frames, clears if out of frame storage */
1759 int cap_vc = -1; /* vc[cap_vc].espid[0-7] hunt for this VC */
1760 int cap_va = -1; /* -1 use first audio, else use audio 0-7 */
1761 int cap_pn = -1; /* -1 use full cap, otherwise this program number */
1762 int cap_pidx = 0; /* non zero if cap_pids populated */
1763 unsigned short cap_pids[ 8 ]; /* VC ES limit */
1764 unsigned short cap_tsid = 0; /* capture TSID of current PTC */
1766 char ntp_name[128] = ""; /* NTP server name for system("ntpdate Z-line") */
1768 /* failure modes to let you know why, sets s->cap_fail */
1769 #define FAIL_NONE 0
1770 #define FAIL_TSYNC 1
1771 #define FAIL_IFIFO 2
1772 #define FAIL_OFIFO 3
1773 #define FAIL_ZAP 4
1774 #define FAIL_NOSPC 5
1775 #define FAIL_QOS 6
1776 #define FAIL_AOS 7
1777 char *cap_fails[] = {
1778 "OK", "SYN", "IFI", "OFO",
1779 "ZAP", "FUL", "QOS", "AOS"
1782 /* this is status from load guide, sets pgm_fail */
1783 #define PG_FAIL_NONE 0
1784 #define PG_FAIL_NF 1
1785 #define PG_FAIL_TO 2
1786 #define PG_FAIL_GE 3
1787 #define PG_FAIL_BG 4
1789 /* FIXME: pick one: blank or empty. meaning has changed now, don't need both */
1790 char *epg_fails[] =
1791 { "OK", "NO EPG", "EPG EXPIRED", "EPG EMPTY", "EPG BLANK" };
1793 int epg_grd = ~0; /* CSS EPG grid on by default */
1794 int epg_sb = 0; /* CSS EPG scrollbars off by default */
1795 int use_css = 1; /* 0 is HTML3.2 1,2,3 is HTML4.01 with CSS */
1796 int epg_all = 0; /* nz uses old style large epg with all events */
1797 int epg_gs = 0; /* epg grid start hour */
1799 int cap_fail = 0; /* 0 if last capture was ok */
1801 /* total error count from everything */
1802 long long cap_errors = 0LL;
1804 /* fifo fill and fill max */
1805 volatile int fill_max = 0; /* per second fifo fill */
1806 volatile int fill_max2 = 0; /* highest fifo fill */
1808 long long frdbc = 0LL; /* fifo write bc */
1809 long long fwrbc = 0LL; /* fifo read bc */
1810 long long outbc = 0LL; /* number of bytes written to .ts file */
1811 /* nanosleeps */
1812 /* ATSC max frame time is 16.66_ ms, io thread sleep is half that */
1813 struct timespec atomic_sleep = { 0, 77555 }; /* 1/12898 sec */
1814 struct timespec atomic10x_sleep = { 0, 775550 }; /* 1/1290 sec */
1815 struct timespec atomic50x_sleep = { 0, 3906250 }; /* 1/256 sec */
1816 struct timespec dummy_read_sleep = { 0, 7756147 }; /* 1/129 sec */
1817 struct timespec console_read_sleep = { 0, 10000000 }; /* 1/100 sec */
1818 struct timespec dummy_write_sleep = { 0, 40000000 }; /* 1/25 sec */
1819 struct timespec guide_loop_sleep = { 0, 62500000 }; /* 1/16 sec */
1820 struct timespec write_thread_sleep = { 0, 83333330 }; /* 1/12 sec */
1821 struct timespec signal_loop_sleep = { 0, 100000000 }; /* 1/10 sec */
1822 struct timespec signal_sleep = { 0, 200000000 }; /* 1/5 sec */
1823 struct timespec msg_sleep = { 0, 250000000 }; /* 1/4 sec */
1824 struct timespec guide_sleep = { 0, 500000000 }; /* 1/2 sec */
1825 struct timespec signal_long_sleep = { 1, 0 }; /* signal scan off */
1826 struct timespec msg_long_sleep = { 2, 0 }; /* 2 seconds */
1827 struct timespec msg_longer_sleep = { 4, 0 }; /* 4 seconds */
1828 struct timespec cap_start = { 0, 0 }; /* capture start */
1829 struct timespec cap_stop = { 0, 0 }; /* capture stop */
1830 struct timespec cap_diff = { 0, 0 };
1831 int console_init = 1; /* init console to nonblock noecho */
1833 /* these are the direct cursor addresses for various parts that could change */
1834 struct {
1835 char tstat[16]; /* transport stat position bottom-6 */
1836 int tstatn; /* transport stat line start center */
1837 char astat[16]; /* atsc stat position bottom right */
1838 char pstat[16]; /* program stat position, bottom left */
1839 char sstat[16]; /* free space stat position */
1840 char wstat[16]; /* wall clock position */
1841 char cstat[16]; /* capture stat header start */
1842 char hstat[16]; /* line 2 headers to the left */
1843 char mstat[16]; /* program guide mask flags position */
1844 char zstat[16]; /* card type and dtv number status */
1845 char ustat[16]; /* user help indication */
1846 char vstat[16]; /* vc stats bottom-1 */
1847 } dca;
1849 char csp[16]; /* current scan position DCA string */
1851 /* date handling */
1852 char date_now[32]; /* now date string */
1853 char date_rec[32]; /* queue date string (computed after 1st) */
1854 char date_fut[32]; /* reque date string, computed */
1855 char date_next[32];
1856 char bascii[128]; /* unsigned to ascii bin output buffer */
1858 char wecma[32]; /* weekday letters, from bascii string */
1859 char *chid = NULL; /* gets pointed to station id */
1862 /* 80x25: 3 lines for legends, 1 line for channel status.
1863 This leaves 21 lines for timers, so that's why TIMER_LIST_MAX is 21.
1864 You can make TIMER_LIST_MAX higher if you have more vertical lines, but if
1865 you go past your consoles line count, the cursor addressing will be ugly.
1868 /* timer status flags */
1869 #define T_FUBAR 0
1870 #define T_STREAM 1
1871 #define T_ACTIVE 2
1872 #define T_IGNORE 3
1873 #define T_TODAY 4
1874 #define T_FUTURE 5
1875 #define T_SEARCH 6
1876 #define T_DONE 7
1877 /* queue status text */
1878 char st_a[8][8] = {
1879 "fubar!", "STREAM", "ACTIVE", "IGNORE",
1880 "TODAY ", "FUTURE", "SEARCH", " DONE " };
1884 /* some timer/display state flags */
1885 volatile int timer_rec = -1; /* q that is currently capturing */
1886 volatile int timer_idx = 0; /* next timer entry, shouldn't pass TIMER_LIST_MAX */
1888 int signal_timeout = ~0; /* signal scan always times out after 300s */
1890 int timer_tot = 0; /* total timer count including weekday bits */
1891 int timer_sort = ~0; /* sort timers at init */
1892 int timer_offset = 0; /* display offset n into timer list ' = keys */
1894 /* FIXME: should be time_t: time values in epoch seconds */
1895 volatile int utsnow = 0; /* epoch seconds for time now */
1896 int utstpg = 0; /* epoch seconds, overall hold down auto guide */
1897 int utsmgt = 0; /* hold down mgt to 5 minutes per update */
1898 int utsfut = 0; /* epoch seconds, signal scan timeout in future */
1899 int utsdsh = 0; /* dump stats in html when expired */
1900 int utsdcl = 0; /* dump cap log in text when expired */
1901 int utsdpg = 0; /* dump program guide when expired */
1902 int utsmid = 0; /* epoch seconds for time 0000 hours previous */
1903 int utscap = 0; /* writes started at this epoch second index */
1904 int utstcf = 0; /* hold down test config to once every 5s */
1905 int utspte = 0; /* hold down for test packet stats cap stats update */
1906 int utsvsu = 0; /* hold down for statfs */
1907 /* time values not in epoch seconds, but derived from above */
1908 int utsday = 0; /* number of seconds since 0000 hours previous */
1909 int utswday = 0; /* current weekday */
1910 int utsets = 0; /* capture elapsed time seconds */
1911 /* int sttdiff = 0; / * last diff from STT to utsnow */
1912 int utswuv; /* wake up volume */
1913 #ifdef USE_POWERDOWN
1914 int utspdd = USE_POWERDELAY; /* time_t to power down device */
1915 #endif
1916 int utsgmt = 0; /* seconds east or west of greenwich, west is negative */
1918 uid_t uid = 0;
1919 uid_t euid = 0;
1920 gid_t gid = 0;
1921 gid_t egid = 0;
1923 /* ATSC epoch time offset, including seconds west, set by calc_epoch */
1924 int utc_offset;
1925 int utc_check; /* 2007-Jan-1 set by calc_epoch */
1926 /* uptime: when program started in unix epoch seconds */
1927 int utc_up = 0;
1929 /* temp global vars for time functions, duplicated to avoid global overwrite */
1930 struct tm tloc; /* get_time sets to current local time */
1931 struct tm tloc1; /* current timer queue time */
1932 struct tm tloc2; /* current timer reschedule time */
1933 struct tm tloc3; /* capture start time */
1934 struct tm tloc4; /* epg grid start time */
1936 /* more epoch second time globals, but in a different format */
1937 time_t tnow; /* tnow is epoch seconds for current date/time */
1938 time_t ptnow; /* prev tnow, hold show clock/timer/cap to 1/s */
1939 time_t trec1; /* current queue date/time in epoch seconds */
1940 time_t trec2; /* reschedule date/time in epoch seconds */
1942 /* previous time check daylight savings time global flag */
1943 int pisdst; /* prev isdst value, if current differs, DST change */
1945 /* pthread ID globals, for tracking via debug */
1946 /* TESTME: what is the size of pid_t on 64bit x86? */
1947 volatile pid_t pid_m = 0; /* main process id console ui */
1948 volatile pid_t pid_i = 0; /* input process id, nz means thread is alive */
1949 volatile pid_t pid_o = 0; /* output process id, nz means thread is alive */
1950 volatile pid_t pid_c = 0; /* nz means ts capture in progress */
1952 /* statfs filesystem globals, give free/used statistics */
1953 struct statfs vol_fsp; /* statfs capture path for free space */
1954 struct statfs cut_fsp; /* statfs cut path for free space */
1955 int cf_mt = 0; /* config file modify time */
1956 int cf_smt = 0; /* config file spamlist modify time */
1957 int cf_no = ~0; /* no config file found flag */
1958 int cf_once = 0; /* oneshot to display config error once */
1959 int timer_lock = 0; /* lock out timer check until cfg loaded */
1961 int arg_nulls = 0; /* -n option gives nulls to hardware players */
1962 int arg_strict = 0; /* -A option ATSC strict checking */
1963 int arg_extras = 0; /* -x nonzero reads status during capture */
1964 int arg_devnum = 0; /* dtv[0...3] or dvb[0...3] */
1965 int arg_log = 0; /* -l full log enable, for debug */
1966 int arg_hlog = 0;
1967 int arg_quiet = 0; /* -q no stdio, don't detach */
1968 int arg_detach = 0; /* -d no stdio, detach from ctl'n tty */
1969 int arg_sdetach = 0; /* -D detach with screen */
1970 int arg_sattach = 0; /* -R re-attach with screen */
1971 int arg_capture = 0; /* -s ss nn, capture chan nn for ss seconds */
1972 int arg_edetail = ~0; /* -e error detail dump to capture log */
1973 int arg_notimers = 0; /* -t don't load timers from config file */
1974 int arg_nokb = 0; /* -k don't scan stdin */
1975 int arg_zapper = 0; /* -z nn QOS zap threshold, default worst case */
1976 int arg_fastfs = 0; /* -f file sys is fast, no delete time */
1977 int arg_tmpfs = 0; /* -E energy save run EPGs from tmpfs */
1978 int arg_dummy = 0; /* -r replay option */
1979 int arg_scan = 0; /* -S setup by scanning all frequencies */
1980 int arg_chan = 0; /* channel for -s */
1981 char arg_name[256]; /* argv[0] instance invokation name */
1982 char arg_dummyn[256] = ""; /* -r option changes with parm */
1984 char arg_whost[ WWW_HOST_MAX ] = WWW_HOST; /* www ip number */
1985 short arg_wport = WWW_PORT; /* http port */
1986 #ifdef USE_WWW
1987 int arg_www = 0; /* -w option to start http /dtv/ server */
1988 unsigned int arg_waddr = 0; /* inaddr_any is default */
1989 unsigned int arg_wmask = 0; /* http accept mask */
1990 int arg_wthreads = WWW_THREADS; /* default number of threads */
1991 #endif
1992 char arg_wroot[ WWW_ROOT_MAX ] = "/dtv"; /* www root */
1993 int www_dvrs = WWW_DVBS;
1995 #ifdef USE_MCAST
1996 int arg_mcport = 0; /* -u IP:port, port field */
1997 char arg_mcaddr[16]; /* -m IP:port, IP field */
1998 #endif
2000 char *arg_device = ""; /* -i name, input device name override */
2001 char *arg_ofile = ""; /* -o file, output override, for -s */
2002 char *arg_opath = ""; /* -p output path added to -o file spec */
2003 char *arg_cpath = NULL; /* -c config file path or full name */
2005 /* normal production defaults */
2006 /* you probably want to leave nosplash 0 otherwise you'll be wondering
2007 why the keys don't work for 3 seconds after everything is drawn
2010 #ifdef USER_TEST
2011 int arg_nosplash = ~0; /* -N no splash or device load delay */
2012 #else
2013 int arg_nosplash = 0; /* -N no splash or device load delay */
2014 #endif
2016 int arg_screen = 0; /* -W use screen (vty manager) */
2017 int arg_aos = 0; /* -a AOS check else aos always passes */
2018 int arg_frames = ~0; /* -m write frame and seq file for xtscut */
2019 int arg_cable = 0; /* -C option to use cable list */
2021 #ifdef USE_CABLE
2022 #warning using Cable default frtable 2
2023 int arg_frtable = 2; /* comcast works with table 2 */
2024 #else
2025 int arg_frtable = 0; /* default is 0, ATSC broadcast */
2026 #endif
2027 int arg_setuid = 0;
2028 int arg_setgid = 0;
2030 extern char **environ;
2032 /* This will be non-zero if there was a problem opening the input device
2033 The effect being no real I/O, except logging, will be done if not zero.
2034 The option -r allows read-only of device or file
2037 int test_mode = 0; /* not zero means demo/test/no stream mode */
2039 int uses_screen = 0; /* non zero means screen was detected */
2040 int in_screen = 0;
2042 /* show cap stats */
2043 int sec_good = 0; /* how many seconds of good stream */
2044 int sec_bad = 0; /* how many seconds of bad stream */
2047 /* list of frame types and packet counts */
2048 struct frame_s {
2049 int pct; /* frame type */
2050 int vpn; /* video packet number */
2053 /* TODO: needs -f frame hours option or similar envar + malloc */
2054 /* NOTE: this is a bit large because it's really 59.94fps */
2055 /* increased from 6: issues with FOX & MNT using insane Intra frames */
2056 #define FRAME_HOURS 12
2057 #define FRAME_MAX (FRAME_HOURS * SECHR * 60)
2059 #ifdef USE_DYNAMIC
2060 unsigned short *frames;
2061 #else
2062 unsigned short frames[ FRAME_MAX ];
2063 #endif
2064 int frame_idx = 0;
2065 int pict[4]; /* 0 none, 1 Intra-frame, 2 Predictor-frame, 3 Bidi-frame */
2067 /* 2 sequences per second, nominal */
2068 #define SEQUENCE_MAX (FRAME_HOURS * 7200)
2069 #ifdef USE_DYNAMIC
2070 unsigned int *sequences;
2071 #else
2072 unsigned int sequences[ SEQUENCE_MAX ];
2073 #endif
2074 int sequence_idx = 0;
2075 long long last_sbo = 0LL; /* last sequence byte offset */
2076 int last_vpn; /* last video packet number */
2078 /* 17 bytes per tsid entry */
2079 struct tsid_s {
2080 unsigned short tsid; /* Transport Stream ID */
2081 unsigned short pn; /* program number */
2082 char call[8]; /* call: 7 chars + NUL */
2083 char sid[4]; /* sid: 3 chars + NUL */
2084 unsigned char ptc; /* physical transmission channel */
2086 /* FIXME: limited to ptc[] max */
2087 #define TSID_MAX 0x80
2088 struct tsid_s tsids[ TSID_MAX ];
2089 pthread_mutex_t tsid_mutex;
2090 unsigned short tsidx = 0;
2091 unsigned short otsidx = 0;
2093 #ifdef USE_ASCII_XLATE
2094 /* translate chars 128 - 255 to ascii versions */
2095 unsigned char c8859_1[128] = {
2096 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
2097 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
2098 0xA0,0x21,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
2099 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0x3F,
2100 0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x43,0x45,0x45,0x45,0x45,0x49,0x49,0x49,0x49,
2101 0xD0,0x4E,0x4F,0x4F,0x4F,0x4F,0x4F,0xD7,0x4F,0x55,0x55,0x55,0x55,0x59,0xDE,0xDF,
2102 0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x63,0x65,0x65,0x65,0x65,0x69,0x69,0x69,0x69,
2103 0xF0,0x6E,0x6F,0x6F,0x6F,0x6F,0x6F,0xF7,0x6F,0x75,0x75,0x75,0x75,0x79,0xFE,0x41,
2106 unsigned char c8859_15[128] = {
2107 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
2108 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
2109 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
2110 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
2111 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
2112 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
2113 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
2114 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
2117 #endif
2119 /****************************************************************** ATSC CRC32
2120 from ffmpeg. the only cut and paste from ffmpeg, aside from the function
2122 const unsigned int crc_lut[256] = {
2123 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
2124 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
2125 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
2126 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
2127 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
2128 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
2129 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
2130 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
2131 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
2132 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
2133 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
2134 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
2135 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
2136 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
2137 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
2138 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
2139 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
2140 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
2141 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
2142 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
2143 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
2144 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
2145 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
2146 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
2147 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
2148 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
2149 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
2150 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
2151 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
2152 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
2153 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
2154 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
2155 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
2156 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
2157 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
2158 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
2159 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
2160 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
2161 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
2162 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
2163 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
2164 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
2165 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
2168 /********************************************************* ATSC HUFFMAN DECODE
2169 A/65b Table C5 Huffman Title Decode Tree (c) 1997 General Instruments Corp.
2171 Not exact table, extracted from specs by me, faster and more useful this way
2172 since exact table is big endian but x86 isn't.
2174 Byte offsets of character i tree root:
2176 unsigned int huffman1bo[128] = {
2177 0x0000, 0x003A, 0x003C, 0x003E, 0x0040, 0x0042, 0x0044, 0x0046,
2178 0x0048, 0x004A, 0x004C, 0x004E, 0x0050, 0x0052, 0x0054, 0x0056,
2179 0x0058, 0x005A, 0x005C, 0x005E, 0x0060, 0x0062, 0x0064, 0x0066,
2180 0x0068, 0x006A, 0x006C, 0x006E, 0x0070, 0x0072, 0x0074, 0x0076,
2181 0x0078, 0x00CE, 0x00D2, 0x00D4, 0x00D6, 0x00D8, 0x00DA, 0x00DC,
2182 0x00E6, 0x00E8, 0x00EA, 0x00F0, 0x00F2, 0x00F4, 0x0106, 0x0112,
2183 0x0114, 0x011C, 0x0128, 0x0130, 0x0134, 0x0136, 0x0138, 0x013A,
2184 0x013C, 0x013E, 0x0146, 0x0148, 0x014A, 0x014C, 0x014E, 0x0150,
2185 0x0152, 0x0154, 0x017E, 0x0192, 0x01AC, 0x01BA, 0x01D2, 0x01E4,
2186 0x01FA, 0x0206, 0x021E, 0x0226, 0x0232, 0x023E, 0x0252, 0x0264,
2187 0x027A, 0x0294, 0x0298, 0x02A4, 0x02C8, 0x02DE, 0x02E6, 0x02F4,
2188 0x0304, 0x0306, 0x030C, 0x0310, 0x0312, 0x0314, 0x0316, 0x0318,
2189 0x031A, 0x031C, 0x0352, 0x036A, 0x038E, 0x03AE, 0x03EE, 0x0406,
2190 0x0428, 0x0444, 0x0472, 0x0476, 0x0490, 0x04BE, 0x04D6, 0x050A,
2191 0x0544, 0x0564, 0x0566, 0x059A, 0x05D0, 0x05FC, 0x0622, 0x062C,
2192 0x0646, 0x0654, 0x067C, 0x068A, 0x068C, 0x068E, 0x0690, 0x0692
2195 /* These were also extracted from specs by me, but no changes made.
2196 Character i order-1 trees. These are byte sized for next branch or leaf.
2197 NOTE: Byte look-up should not have endian issues.
2200 #define TITLE_COZ 1683
2201 unsigned char huffman1co[1684] = {
2202 0x1B,0x1C,0xB4,0xA4,0xB2,0xB7,0xDA,0x01,0xD1,0x02,0x03,0x9B,0x04,0xD5,0xD9,0x05,
2203 0xCB,0xD6,0x06,0xCF,0x07,0x08,0xCA,0x09,0xC9,0xC5,0xC6,0x0A,0xD2,0xC4,0xC7,0xCC,
2204 0xD0,0xC8,0xD7,0xCE,0x0B,0xC1,0x0C,0xC2,0xCD,0xC3,0x0D,0x0E,0x0F,0x10,0xD3,0x11,
2205 0xD4,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2206 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2207 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2208 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2209 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x29,0x2A,0xD8,0xE5,0xB9,0x01,0xA7,0xB1,
2210 0xEC,0xD1,0x02,0xAD,0xB2,0xDA,0xE3,0xB3,0x03,0xE4,0xE6,0x04,0x9B,0xE2,0x05,0x06,
2211 0x07,0x08,0x09,0xD5,0x0A,0xD6,0x0B,0xD9,0x0C,0xA6,0xE9,0xCB,0xC5,0xCF,0x0D,0x0E,
2212 0xCA,0xC9,0x0F,0xC7,0x10,0x11,0xE1,0x12,0x13,0xC6,0xD2,0xC8,0xCE,0xC1,0xC4,0xD0,
2213 0xCC,0x14,0x15,0xEF,0xC2,0xD7,0x16,0xCD,0x17,0xF4,0xD4,0x18,0x19,0x1A,0xC3,0xD3,
2214 0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x01,0x80,
2215 0xA0,0x9B,0x9B,0x9B,0x9B,0x9B,0xB1,0x9B,0x9B,0x9B,0x9B,0xA0,0x04,0xF3,0xE4,0xB9,
2216 0x01,0xF4,0xA0,0x9B,0x02,0x03,0x9B,0x9B,0x9B,0x9B,0x01,0x02,0x9B,0xC1,0xC8,0xD3,
2217 0x9B,0x9B,0x9B,0xA0,0x07,0x08,0xB1,0xD2,0xD3,0xD4,0xD5,0xAD,0xCD,0xC1,0x01,0x02,
2218 0x03,0xA0,0x04,0x9B,0x05,0x06,0xA0,0x05,0xC9,0xD7,0xD3,0x01,0x02,0x9B,0xAE,0x80,
2219 0x03,0x04,0x9B,0x9B,0x02,0x03,0xAD,0x9B,0x01,0x80,0xA0,0xB0,0x04,0x05,0x80,0x9B,
2220 0xB1,0xB2,0xA0,0xB0,0xB9,0x01,0x02,0x03,0x02,0x03,0xB1,0xBA,0x01,0xB0,0x9B,0x80,
2221 0x80,0x01,0xB0,0x9B,0x9B,0xB8,0x9B,0x9B,0x9B,0x9B,0x9B,0xB0,0x9B,0xA0,0x02,0x03,
2222 0xB1,0xB3,0xB9,0xB0,0x01,0x9B,0x9B,0xA0,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2223 0x9B,0x80,0x9B,0x9B,0x13,0x14,0xAA,0xAD,0xAE,0xF6,0xE7,0xF4,0xE2,0xE9,0x01,0x02,
2224 0xC2,0xF0,0x9B,0xF3,0xE3,0xE6,0xF7,0x03,0xF5,0x04,0x05,0x06,0xF2,0x07,0x08,0x09,
2225 0x0A,0x0B,0x0C,0xE4,0xA0,0x0D,0xEC,0xEE,0x0E,0xED,0x0F,0x10,0x11,0x12,0x08,0x09,
2226 0xC1,0xD3,0x9B,0x01,0xC3,0x02,0xE9,0xEC,0x03,0xF2,0xF5,0x04,0xEF,0xE1,0x05,0xE5,
2227 0x06,0x07,0x0B,0x0C,0xC1,0xF9,0x01,0xC2,0xCF,0xE5,0xF5,0x9B,0xE9,0x02,0xA0,0x03,
2228 0x04,0x05,0xF2,0x06,0xEC,0x07,0xE1,0x08,0x09,0xE8,0x0A,0xEF,0x05,0x06,0xF9,0x9B,
2229 0x01,0xF5,0x02,0xF2,0xE9,0xE5,0xEF,0x03,0xE1,0x04,0x0A,0x0B,0xF1,0xF5,0xF3,0x01,
2230 0xED,0xF9,0xC3,0x02,0xEC,0xEE,0xE4,0xF8,0x03,0x9B,0xF6,0x04,0x05,0xE1,0x06,0x07,
2231 0x08,0x09,0x07,0x08,0xA0,0x9B,0xCC,0x01,0xE5,0x02,0xEC,0xF5,0xEF,0x03,0xE9,0xF2,
2232 0x04,0x05,0xE1,0x06,0x09,0x0A,0xAE,0xEC,0xF9,0xC1,0xE8,0x01,0x9B,0x02,0x03,0x04,
2233 0xE1,0xF5,0xE9,0x05,0xE5,0x06,0xF2,0xEF,0x07,0x08,0xEF,0x05,0x80,0x9B,0xF5,0x01,
2234 0x02,0xE9,0xE1,0x03,0xE5,0x04,0xEE,0x0B,0xBA,0xD4,0xAE,0xF2,0xE3,0x01,0xA0,0x02,
2235 0x80,0x9B,0xED,0x03,0xC9,0xF3,0xF4,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x02,0x03,
2236 0x9B,0xF5,0x01,0xE1,0xEF,0xE5,0x05,0xE9,0xE1,0xEF,0xF5,0xEE,0x9B,0xE5,0x01,0x02,
2237 0x03,0x04,0x04,0x05,0xA0,0x9B,0x01,0xF5,0x02,0xE5,0xEF,0x03,0xE1,0xE9,0x08,0x09,
2238 0xAA,0xD4,0x01,0x9B,0xE3,0x02,0xF2,0x03,0xE5,0x04,0xF5,0xF9,0xE9,0x05,0xEF,0x06,
2239 0x07,0xE1,0xE5,0x08,0xCE,0xA0,0xC6,0xF5,0x01,0x02,0x9B,0xC2,0x03,0xE1,0x04,0xEF,
2240 0x05,0xE9,0x06,0x07,0x09,0x0A,0xE4,0xF3,0xE6,0xF6,0xF7,0xF0,0xF2,0x01,0xEC,0x02,
2241 0x03,0xA0,0x9B,0x04,0x05,0xF5,0x06,0x07,0xEE,0x08,0x0B,0x0C,0xA0,0xF3,0xF9,0xAE,
2242 0xD2,0xC7,0x01,0x9B,0x02,0xF5,0x03,0x04,0x05,0xE9,0xEC,0x06,0xE5,0x07,0xEF,0x08,
2243 0xE1,0x09,0xF2,0x0A,0x01,0xF5,0x9B,0xD6,0x04,0x05,0xE8,0x9B,0x01,0xF5,0x02,0xE1,
2244 0xE9,0xEF,0x03,0xE5,0x10,0x11,0xAA,0xEC,0xF1,0xAE,0xA0,0xF7,0xED,0xEE,0x01,0x02,
2245 0x9B,0xEB,0x03,0x04,0x05,0x06,0xE3,0x07,0xEF,0x08,0xE9,0xF5,0x09,0xE1,0xE5,0xF0,
2246 0xE8,0x0A,0x0B,0x0C,0x0D,0xF4,0x0E,0x0F,0xE8,0x0A,0xAD,0xCE,0x9B,0x01,0xD6,0x02,
2247 0xF5,0xF7,0x03,0x04,0xE1,0xE5,0xE9,0x05,0xF2,0x06,0xEF,0x07,0x08,0x09,0xEE,0x03,
2248 0xEC,0xAE,0x01,0x9B,0x02,0xF0,0x06,0xE9,0xA0,0xC3,0xEF,0x9B,0xE5,0x01,0x80,0x02,
2249 0x03,0xE1,0x04,0x05,0x06,0x07,0xC6,0xD7,0x01,0x9B,0xF2,0x02,0x03,0xE8,0xE5,0xE1,
2250 0x04,0xE9,0xEF,0x05,0x9B,0x9B,0x02,0xEF,0xE1,0x9B,0x01,0xE5,0x01,0xEF,0x9B,0xE1,
2251 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x19,0x1A,0x9B,0xBA,
2252 0xE5,0xEA,0xF8,0x01,0x02,0xE6,0xA7,0x03,0xFA,0xE8,0x04,0xF7,0x05,0xF5,0xE2,0x06,
2253 0xEB,0x07,0xF0,0x08,0x80,0xF6,0xE7,0x09,0xE4,0x0A,0xA0,0xE9,0x0B,0xE3,0xF9,0x0C,
2254 0x0D,0xED,0x0E,0x0F,0xF3,0x10,0x11,0xEC,0x12,0xF4,0xF2,0x13,0xEE,0x14,0x15,0x16,
2255 0x17,0x18,0x0A,0x0B,0xF3,0x9B,0xF5,0xE2,0x01,0x80,0xA0,0x02,0xE5,0xF2,0xE9,0x03,
2256 0xEC,0x04,0xF9,0x05,0xEF,0x06,0xE1,0x07,0x08,0x09,0x10,0x11,0xC3,0xCC,0xC7,0x9B,
2257 0xE3,0x01,0x80,0xEC,0xF9,0x02,0xF3,0x03,0xF5,0x04,0x05,0xF2,0x06,0xE9,0xA0,0x07,
2258 0x08,0xEF,0xF4,0x09,0x0A,0xE1,0x0B,0xE8,0xEB,0xE5,0x0C,0x0D,0x0E,0x0F,0x0E,0x0F,
2259 0xAE,0xF5,0xF7,0x01,0xEC,0x02,0xE4,0xE7,0xF2,0x03,0x9B,0xEF,0x04,0xF6,0x05,0x06,
2260 0xF9,0xF3,0x07,0xE9,0xE1,0x08,0x09,0x80,0x0A,0x0B,0xE5,0x0C,0x0D,0xA0,0x1E,0x1F,
2261 0x9B,0xA1,0xAD,0xE8,0xEA,0xF1,0xF5,0xFA,0x01,0x02,0x03,0x04,0xBA,0xF8,0xA7,0xE2,
2262 0xE9,0x05,0x06,0x07,0xE6,0xED,0xE7,0xEB,0x08,0x09,0xF6,0xF0,0x0A,0xEF,0x0B,0xE3,
2263 0x0C,0x0D,0x0E,0xF9,0x0F,0xE4,0xEC,0x10,0xE5,0x11,0xF4,0xF7,0x12,0x13,0xE1,0x14,
2264 0x15,0x16,0xEE,0xF3,0x17,0x80,0x18,0x19,0xF2,0x1A,0x1B,0xA0,0x1C,0x1D,0xA0,0x0B,
2265 0xF5,0x9B,0x01,0xEC,0xF3,0xF2,0x80,0xE1,0x02,0x03,0xF4,0xE9,0xEF,0xE6,0x04,0x05,
2266 0x06,0x07,0xE5,0x08,0x09,0x0A,0x0F,0x10,0xBA,0xF9,0xA7,0xF4,0x9B,0x01,0xE7,0xEC,
2267 0x02,0xEE,0x03,0xEF,0xF5,0x04,0xF2,0x05,0x06,0xE9,0x07,0xF3,0xE1,0x08,0x09,0x0A,
2268 0x0B,0xE5,0x80,0x0C,0xE8,0xA0,0x0D,0x0E,0xE5,0x0D,0xE2,0xF5,0xF7,0x9B,0xEC,0x01,
2269 0xF9,0xEE,0x02,0x03,0x04,0xF2,0x05,0x80,0x06,0xA0,0xE1,0xEF,0x07,0xF4,0xE9,0x08,
2270 0x09,0x0A,0x0B,0x0C,0x15,0x16,0xA1,0xF8,0xE9,0xEB,0x01,0x80,0x9B,0xFA,0xE2,0x02,
2271 0x03,0x04,0xA0,0xF0,0x05,0x06,0x07,0xE1,0x08,0xE6,0xF2,0xED,0xF6,0x09,0xE4,0x0A,
2272 0xEF,0xF4,0xEC,0xF3,0xE7,0xE5,0x0B,0xE3,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,
2273 0xEE,0x14,0xEF,0x01,0x9B,0xE1,0x0B,0x0C,0xD4,0xEF,0xE6,0xEC,0xF7,0xE1,0x01,0xBA,
2274 0x02,0x9B,0xF9,0x03,0x04,0x05,0xF3,0x06,0x07,0x08,0xE9,0xA0,0x09,0x80,0xE5,0x0A,
2275 0x15,0x16,0xA7,0xBA,0xE3,0xF7,0xF2,0xAD,0xE2,0x01,0x02,0x9B,0xE6,0x03,0xED,0xF6,
2276 0x04,0xEB,0x05,0xF4,0x06,0x07,0x08,0xF3,0x09,0xF5,0x0A,0xEF,0x0B,0x0C,0x80,0xF9,
2277 0xE1,0x0D,0xE4,0xE9,0xA0,0x0E,0x0F,0xEC,0xE5,0x10,0x11,0x12,0x13,0x14,0x0A,0x0B,
2278 0xF9,0x9B,0xF5,0xF3,0x01,0x02,0xE2,0xED,0x80,0x03,0xF0,0xEF,0x04,0xA0,0x05,0xE9,
2279 0x06,0xE1,0x07,0x08,0x09,0xE5,0x18,0x19,0xE2,0xEA,0xF2,0xE8,0xEC,0xED,0xFA,0x9B,
2280 0x01,0xF5,0x02,0x03,0xF6,0x04,0xBA,0xE6,0x05,0x06,0xEB,0xEF,0x07,0xA7,0xF9,0x08,
2281 0x09,0x0A,0x0B,0xE3,0x0C,0xEE,0xE1,0x0D,0xF3,0x0E,0xE9,0x0F,0x10,0xF4,0x80,0xE4,
2282 0xE5,0x11,0x12,0xE7,0xA0,0x13,0x14,0x15,0x16,0x17,0x1B,0x1C,0xAE,0xFA,0xBF,0x01,
2283 0xA7,0x9B,0x02,0xE9,0xF8,0xF9,0x03,0xE5,0xE8,0x04,0xE1,0xEB,0x05,0xE2,0x06,0x07,
2284 0xE3,0x08,0xE7,0xF4,0x09,0x80,0xF6,0xF0,0x0A,0xE4,0x0B,0xF3,0xF7,0x0C,0x0D,0xEF,
2285 0xEC,0xA0,0x0E,0x0F,0xED,0xE6,0x10,0xF5,0x11,0x12,0x13,0x14,0x15,0xF2,0x16,0xEE,
2286 0x17,0x18,0x19,0x1A,0x0E,0x0F,0xED,0xA7,0x9B,0xE4,0x01,0xF9,0xF3,0xF2,0xF4,0x02,
2287 0xE8,0x03,0xEC,0xF0,0x04,0xE1,0xE9,0x05,0x06,0x80,0xA0,0x07,0x08,0x09,0x0A,0xE5,
2288 0xEF,0x0B,0x0C,0x0D,0x9B,0xF5,0x18,0x19,0xBA,0xAC,0xF6,0x9B,0xF0,0xE2,0x01,0xE6,
2289 0x02,0xA7,0xAE,0xE7,0x03,0xE3,0xF5,0x04,0xED,0x05,0x06,0x07,0xEB,0x08,0x09,0xEE,
2290 0xF2,0x0A,0xE4,0x0B,0xF9,0xEC,0x0C,0x0D,0xF4,0x80,0x0E,0xEF,0xF3,0xA0,0xE1,0x0F,
2291 0xE9,0x10,0x11,0xE5,0x12,0x13,0x14,0x15,0x16,0x17,0x19,0x1A,0xA7,0xAC,0xBF,0xC3,
2292 0xC8,0xE4,0xE6,0xED,0xF2,0xAE,0xEC,0xEE,0xF9,0x01,0x02,0x03,0x04,0xBA,0x05,0x9B,
2293 0xF5,0x06,0x07,0x08,0x09,0xEB,0xF0,0x0A,0x0B,0x0C,0xE1,0xE3,0x0D,0xE8,0x0E,0x0F,
2294 0xEF,0x10,0x11,0xF3,0x12,0xE9,0x13,0xE5,0x14,0x15,0xF4,0x16,0x17,0xA0,0x18,0x80,
2295 0x14,0x15,0xBA,0xBF,0xE4,0xF7,0x9B,0xA7,0x01,0xEE,0x02,0x03,0x04,0xE3,0xE2,0xED,
2296 0x05,0xF9,0x06,0xF4,0x07,0xEC,0x08,0xF5,0xF2,0x09,0xE1,0xF3,0x0A,0xEF,0x0B,0x0C,
2297 0x0D,0xE9,0x80,0xE5,0x0E,0xA0,0x0F,0xE8,0x10,0x11,0x12,0x13,0x11,0x12,0xEB,0xFA,
2298 0x80,0xE6,0x9B,0x01,0xA0,0x02,0x03,0xE9,0xE1,0x04,0xE4,0xF0,0xED,0xE2,0xE3,0xE7,
2299 0xEC,0x05,0xE5,0x06,0x07,0x08,0x09,0xF4,0x0A,0x0B,0x0C,0xF3,0xEE,0x0D,0x0E,0xF2,
2300 0x0F,0x10,0x04,0xE5,0xF3,0xEF,0x9B,0x01,0xE1,0x02,0x03,0xE9,0x0B,0x0C,0xA7,0xE2,
2301 0xEC,0xE3,0xF2,0x01,0x9B,0x02,0x03,0x04,0xE9,0xEF,0xEE,0xE5,0xE1,0x80,0x05,0xA0,
2302 0x06,0x07,0x08,0x09,0xF3,0x0A,0x05,0x06,0x9B,0xA0,0xE1,0xE5,0xE9,0x01,0x80,0xF0,
2303 0x02,0xF4,0x03,0x04,0xA0,0x13,0xE3,0xAD,0xE4,0xE9,0xEE,0xEF,0xF0,0xF4,0xF6,0xA1,
2304 0xE1,0xED,0x01,0xE2,0x02,0x03,0x04,0xA7,0x05,0x06,0xF7,0x07,0x9B,0xEC,0x08,0xE5,
2305 0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0xF3,0x0F,0x10,0x11,0x80,0x12,0x05,0x06,0xE5,0xFA,
2306 0xA0,0xF9,0x9B,0x01,0x80,0xE9,0x02,0xE1,0x03,0x04,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2307 0x9B,0x9B,0x9B,0x9B
2311 A/65b Table C7 Huffman Description Decode Tree (c) General Instruments Corp.
2313 Not exact table, extracted from specs by me, faster more useful version
2314 since exact table is big endian but x86 isn't.
2316 Byte offsets of character i tree root:
2318 unsigned int huffman2bo[128] = {
2319 0x0000, 0x002C, 0x002E, 0x0030, 0x0032, 0x0034, 0x0036, 0x0038,
2320 0x003A, 0x003C, 0x003E, 0x0040, 0x0042, 0x0044, 0x0046, 0x0048,
2321 0x004A, 0x004C, 0x004E, 0x0050, 0x0052, 0x0054, 0x0056, 0x0058,
2322 0x005A, 0x005C, 0x005E, 0x0060, 0x0062, 0x0064, 0x0066, 0x0068,
2323 0x006A, 0x00DE, 0x00E0, 0x00EA, 0x00EC, 0x00EE, 0x00F0, 0x00F2,
2324 0x00F8, 0x00FA, 0x00FC, 0x00FE, 0x0100, 0x0104, 0x0116, 0x0120,
2325 0x0122, 0x012C, 0x0132, 0x0138, 0x013C, 0x0140, 0x0144, 0x0146,
2326 0x014A, 0x014C, 0x0154, 0x0156, 0x0158, 0x015A, 0x015C, 0x015E,
2327 0x0160, 0x0162, 0x0176, 0x0184, 0x0194, 0x01A2, 0x01B2, 0x01BA,
2328 0x01C8, 0x01D2, 0x01DE, 0x01EA, 0x01F2, 0x01FC, 0x0208, 0x0210,
2329 0x021A, 0x0228, 0x022A, 0x0234, 0x024A, 0x025A, 0x025E, 0x0264,
2330 0x026E, 0x0270, 0x0272, 0x0274, 0x0276, 0x0278, 0x027A, 0x027C,
2331 0x027E, 0x0280, 0x02B4, 0x02CE, 0x02F0, 0x031A, 0x0358, 0x036E,
2332 0x038E, 0x03AC, 0x03D8, 0x03E0, 0x03F4, 0x0424, 0x0440, 0x0476,
2333 0x04AE, 0x04CE, 0x04D0, 0x0506, 0x0534, 0x0560, 0x0586, 0x0592,
2334 0x05AA, 0x05B8, 0x05DC, 0x05EC, 0x05EE, 0x05F0, 0x05F2, 0x05F4,
2337 #define DESCR_COZ 1525
2338 /* These were also extracted from specs by me, but no changes made.
2339 Character i order-1 trees. These are byte sized for next branch or leaf.
2340 NOTE: Byte look-up should not have endian issues.
2342 unsigned char huffman2co[1526] = {
2343 0x14,0x15,0x9B,0xD6,0xC9,0xCF,0xD7,0xC7,0x01,0xA2,0xCE,0xCB,0x02,0x03,0xC5,0xCC,
2344 0xC6,0xC8,0x04,0xC4,0x05,0xC2,0x06,0xC3,0xD2,0x07,0xD3,0x08,0xCA,0xD4,0x09,0xCD,
2345 0xD0,0x0A,0xC1,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x9B,0x9B,0x9B,0x9B,
2346 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2347 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2348 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2349 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x38,0x39,0xAD,0xAF,0xB7,0xDA,
2350 0xA8,0xB3,0xB5,0x01,0x02,0x9B,0xB4,0xF1,0xA2,0xD5,0xD6,0xD9,0x03,0x04,0x05,0xCF,
2351 0x06,0xC9,0xF9,0xEA,0xEB,0xF5,0xF6,0x07,0x08,0x09,0xB2,0xC5,0xC6,0xB1,0x0A,0xEE,
2352 0xCB,0x0B,0xD4,0x0C,0xC4,0xC8,0xD2,0x0D,0x0E,0x0F,0xC7,0xCA,0xCE,0xD0,0xD7,0x10,
2353 0xC2,0x11,0xCC,0xEC,0xE5,0xE7,0x12,0xCD,0x13,0x14,0xC3,0x15,0x16,0x17,0xED,0x18,
2354 0x19,0xF2,0x1A,0xD3,0x1B,0x1C,0xE4,0x1D,0xC1,0xE3,0x1E,0xE9,0xF0,0xE2,0xF7,0x1F,
2355 0xF3,0xE6,0x20,0x21,0x22,0xE8,0xEF,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0xF4,
2356 0x2B,0x2C,0x2D,0x2E,0x2F,0xE1,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x9B,0x9B,
2357 0x03,0x04,0x80,0xAE,0xC8,0xD4,0x01,0x02,0x9B,0xA0,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2358 0x9B,0x9B,0x02,0xF3,0xA0,0xF4,0x9B,0x01,0x9B,0x9B,0xAC,0x9B,0x9B,0x9B,0x9B,0x9B,
2359 0x01,0xA0,0x9B,0xA2,0x07,0x08,0xE2,0xE4,0xE5,0xE6,0xA0,0xF2,0xE1,0x01,0x02,0xF3,
2360 0xE3,0x03,0x04,0x05,0x9B,0x06,0x04,0x80,0xCA,0xD3,0xA2,0x01,0x9B,0x02,0x03,0xA0,
2361 0x9B,0xA0,0x03,0x04,0x9B,0xB7,0xF4,0xA0,0xB0,0xF3,0x01,0x02,0xB9,0x02,0xB8,0x9B,
2362 0xA0,0x01,0xAE,0x02,0xB6,0x9B,0x01,0xA0,0xA0,0x01,0x9B,0xB0,0xAE,0x01,0x9B,0xA0,
2363 0xAE,0x01,0xA0,0x9B,0x9B,0x9B,0x9B,0x01,0xAC,0xAE,0x9B,0x9B,0x02,0x03,0x9B,0xA0,
2364 0xB5,0xB6,0xB8,0x01,0x9B,0xA0,0x9B,0xA0,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0xA0,
2365 0x9B,0x9B,0x08,0x09,0xE6,0xF5,0xF3,0xF4,0x9B,0xE4,0x01,0xED,0x02,0x03,0x04,0xF2,
2366 0x05,0x06,0xEC,0xEE,0x07,0xA0,0x05,0x06,0x9B,0xEC,0xF5,0x01,0x02,0xE1,0xEF,0xE5,
2367 0xE9,0xF2,0x03,0x04,0x06,0x07,0x9B,0xE9,0xF9,0xF2,0xF5,0x01,0x02,0x03,0xEC,0xEF,
2368 0xE1,0x04,0xE8,0x05,0x05,0x06,0xF9,0xF2,0xF5,0x9B,0xE5,0xEF,0x01,0x02,0xE9,0xE1,
2369 0x03,0x04,0x06,0x07,0xE1,0xE9,0xEE,0xF6,0xE4,0xEC,0xF3,0x01,0x02,0xF2,0x03,0x04,
2370 0x9B,0x05,0x02,0x03,0xE5,0xEC,0x9B,0xEF,0x01,0xF2,0x05,0x06,0xF5,0xEF,0x9B,0xEC,
2371 0xE9,0x01,0xE1,0xF2,0x02,0xE5,0x03,0x04,0x03,0x04,0x9B,0xE5,0xE9,0xF5,0xE1,0x01,
2372 0xEF,0x02,0x04,0x05,0xA0,0xC9,0xF3,0x9B,0xAE,0xF2,0x01,0x02,0x03,0xEE,0xEF,0x05,
2373 0x9B,0xAE,0xE9,0xE5,0x01,0xF5,0x02,0xE1,0x03,0x04,0xE5,0x03,0xE1,0xE9,0xF2,0x9B,
2374 0x01,0x02,0x03,0x04,0x9B,0xE9,0xF5,0x01,0xE5,0x02,0xEF,0xE1,0xE1,0x05,0x9B,0xE3,
2375 0xEF,0x01,0xF5,0xE5,0x02,0x03,0xE9,0x04,0xE5,0x03,0x9B,0xE9,0x01,0xE1,0xEF,0x02,
2376 0x03,0x04,0xA7,0xEE,0xEC,0xF2,0xF3,0x01,0x9B,0x02,0xE1,0x06,0x9B,0xE8,0xE9,0x01,
2377 0xF2,0xEC,0x02,0xEF,0x03,0xE5,0x04,0x05,0x9B,0x9B,0x03,0x04,0x9B,0xAE,0x01,0xE9,
2378 0x02,0xE1,0xE5,0xEF,0x09,0x0A,0xF6,0xF9,0x01,0xAE,0xE3,0xE9,0xF5,0x9B,0xE5,0xEF,
2379 0x02,0x03,0xE1,0x04,0xE8,0x05,0x06,0xF4,0x07,0x08,0xE8,0x07,0xE5,0xF7,0xD6,0xE1,
2380 0x9B,0xE9,0xF2,0x01,0x02,0x03,0x04,0xEF,0x05,0x06,0xAE,0x01,0x9B,0xEE,0xE9,0x02,
2381 0xE5,0x9B,0xA0,0x01,0x03,0x04,0x9B,0xE8,0xE5,0xE1,0xEF,0x01,0xE9,0x02,0x9B,0x9B,
2382 0x9B,0xEF,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2383 0x18,0x19,0xE8,0xEF,0xF8,0x9B,0xA7,0xF7,0xFA,0x01,0x02,0x03,0x04,0xE5,0xAE,0x05,
2384 0xE6,0xE2,0x06,0xF6,0xEB,0xF5,0xE9,0x07,0xF0,0xF9,0xE7,0x08,0x09,0xE4,0x0A,0xE3,
2385 0x0B,0xED,0x0C,0xF3,0x0D,0x0E,0x0F,0xEC,0x10,0xF4,0x11,0x12,0xF2,0xA0,0x13,0x14,
2386 0x15,0xEE,0x16,0x17,0x0B,0x0C,0xE4,0xF3,0x9B,0xAE,0xE2,0x01,0x02,0x03,0xEC,0xA0,
2387 0x04,0xE9,0xF2,0xF5,0x05,0xF9,0xE1,0x06,0xEF,0x07,0xE5,0x08,0x09,0x0A,0x0F,0x10,
2388 0xF1,0xAE,0xC4,0xF9,0xAC,0x01,0xE3,0x02,0x9B,0xF2,0x03,0x04,0xA0,0xEC,0xF5,0x05,
2389 0x06,0xE9,0x07,0xEB,0x08,0xF4,0x09,0xE5,0x0A,0xEF,0xE1,0xE8,0x0B,0x0C,0x0D,0x0E,
2390 0x13,0x14,0xA7,0xBB,0xE6,0xED,0xF7,0xE7,0xF6,0x01,0x02,0x9B,0xEE,0x03,0x04,0xEC,
2391 0x05,0xF5,0x06,0xAC,0xE4,0xF9,0xF2,0x07,0x08,0x09,0xAE,0x0A,0xEF,0x0B,0xE1,0xF3,
2392 0x0C,0xE9,0x0D,0x0E,0x0F,0x10,0xE5,0x11,0x12,0xA0,0x1D,0x1E,0xA9,0xE8,0xF5,0x9B,
2393 0x01,0xAD,0xBB,0xEB,0xFA,0x02,0xA7,0xE6,0xE2,0xE7,0x03,0x04,0x05,0x06,0xE9,0xF8,
2394 0x07,0xAC,0xEF,0xF0,0x08,0xED,0xF6,0xF9,0x09,0xF7,0x0A,0x0B,0xAE,0x0C,0xE3,0x0D,
2395 0xE5,0xF4,0x0E,0x0F,0xE4,0x10,0xEC,0x11,0xE1,0x12,0x13,0x14,0x15,0x16,0xEE,0xF3,
2396 0x17,0x18,0xF2,0xA0,0x19,0x1A,0x1B,0x1C,0x09,0x0A,0xAE,0x9B,0xEC,0x01,0xF5,0x02,
2397 0xF4,0xE6,0x03,0xE1,0xE5,0xE9,0x04,0xF2,0xEF,0x05,0x06,0x07,0xA0,0x08,0x0E,0x0F,
2398 0xAD,0xE7,0x9B,0xA7,0xF9,0x01,0xEC,0x02,0xAC,0xF2,0x03,0xAE,0xF3,0xF5,0x04,0x05,
2399 0xEF,0x06,0x07,0xE9,0xE1,0x08,0x09,0xE8,0x0A,0x0B,0xE5,0x0C,0xA0,0x0D,0x0D,0x0E,
2400 0xA7,0xAC,0xF3,0xAD,0x01,0x02,0x9B,0xF9,0xF5,0xAE,0x03,0xEE,0x04,0xF2,0x05,0x06,
2401 0xF4,0x07,0x08,0x09,0xEF,0xE1,0xA0,0x0A,0xE9,0x0B,0x0C,0xE5,0x14,0x15,0xAC,0xE2,
2402 0xF8,0x9B,0xAE,0xFA,0x01,0xEB,0x02,0xA0,0x03,0x04,0xF0,0x05,0x06,0xE6,0xF6,0x07,
2403 0xE4,0xED,0xE7,0x08,0xE1,0xEF,0xF2,0x09,0x0A,0x0B,0xEC,0x0C,0xE5,0xE3,0x0D,0xF4,
2404 0x0E,0xF3,0x0F,0x10,0x11,0xEE,0x12,0x13,0x03,0xEF,0x9B,0xE1,0xE5,0xF5,0x01,0x02,
2405 0x08,0x09,0xEC,0xF9,0xA7,0xEE,0x01,0xAC,0x9B,0xAE,0x02,0x03,0x04,0xF3,0x05,0xE9,
2406 0x06,0xA0,0x07,0xE5,0x16,0x17,0xA7,0xAD,0xEE,0xE3,0xEB,0xF2,0x9B,0xE2,0x01,0x02,
2407 0xF5,0x03,0xF4,0xAC,0x04,0x05,0xE6,0xED,0xF6,0x06,0xAE,0xF0,0x07,0x08,0xF3,0x09,
2408 0x0A,0xE4,0x0B,0x0C,0xF9,0x0D,0xEF,0x0E,0xE1,0x0F,0x10,0xE9,0xEC,0x11,0xA0,0xE5,
2409 0x12,0x13,0x14,0x15,0x0C,0x0D,0xA7,0xBB,0x9B,0x01,0xF9,0xAE,0xE2,0x02,0xED,0xF3,
2410 0x03,0xF5,0xEF,0xF0,0x04,0x05,0xE9,0x06,0x07,0x08,0x09,0xA0,0xE1,0xE5,0x0A,0x0B,
2411 0x19,0x1A,0xAD,0xBB,0xE2,0xEA,0xED,0xF2,0xFA,0xE6,0xEC,0x01,0x02,0x03,0x9B,0xF5,
2412 0x04,0xA7,0xF6,0xF9,0x05,0x06,0xEB,0xEF,0x07,0x08,0x09,0x0A,0xAC,0x0B,0x0C,0xE3,
2413 0xAE,0x0D,0xEE,0xE9,0x0E,0xE1,0x0F,0xF3,0x10,0x11,0xF4,0x12,0xE7,0xE5,0x13,0x14,
2414 0xE4,0x15,0x16,0x17,0xA0,0x18,0x1A,0x1B,0xC2,0x9B,0xAD,0xAC,0xF8,0x01,0xAE,0x02,
2415 0x03,0xE5,0xE7,0xE8,0xF9,0xE9,0xEB,0x04,0xE3,0xE1,0x05,0xF6,0x06,0xE4,0x07,0xE2,
2416 0xF0,0x08,0x09,0xF3,0xF4,0xF7,0xEF,0x0A,0x0B,0x0C,0x0D,0xEC,0x0E,0x0F,0x10,0xF5,
2417 0xED,0x11,0xE6,0xA0,0x12,0xF2,0x13,0x14,0x15,0xEE,0x16,0x17,0x18,0x19,0x0E,0x0F,
2418 0xAD,0xED,0xF9,0x9B,0xAE,0x01,0xF3,0x02,0x03,0xF5,0xF4,0xF0,0x04,0xEF,0x05,0xE9,
2419 0x06,0xE8,0xA0,0xE1,0xEC,0x07,0xF2,0x08,0xE5,0x09,0x0A,0x0B,0x0C,0x0D,0x9B,0xF5,
2420 0x19,0x1A,0xA9,0xBB,0xF6,0xE6,0x01,0x9B,0xAD,0xE2,0xF0,0x02,0xA7,0x03,0x04,0x05,
2421 0xF5,0xE3,0xAC,0xE7,0xF2,0x06,0xEB,0x07,0xEC,0xED,0xEE,0xF9,0x08,0xAE,0x09,0x0A,
2422 0xE4,0x0B,0x0C,0xF4,0x0D,0xF3,0x0E,0x0F,0x10,0xE1,0xEF,0x11,0xE9,0x12,0x13,0xE5,
2423 0x14,0xA0,0x15,0x16,0x17,0x18,0xA0,0x16,0xA2,0xA7,0xE2,0xEB,0xED,0xEE,0x9B,0xF7,
2424 0x01,0x02,0x03,0xBB,0xF9,0xF0,0x04,0x05,0xEC,0x06,0x07,0x08,0xF5,0xE1,0x09,0xAC,
2425 0xE3,0x0A,0xE8,0x0B,0xE9,0x0C,0xEF,0xF3,0xAE,0x0D,0x0E,0xE5,0x0F,0x10,0x11,0xF4,
2426 0x12,0x13,0x14,0x15,0x14,0x15,0xBB,0xE2,0xAD,0xED,0x01,0x9B,0xA7,0xE3,0xAC,0xEC,
2427 0xEE,0x02,0xF7,0x03,0x04,0xF9,0x05,0x06,0x07,0x08,0xF4,0xAE,0xF5,0x09,0x0A,0xF2,
2428 0xE1,0xF3,0x0B,0x0C,0x0D,0xE9,0x0E,0x0F,0xEF,0xE5,0x10,0xA0,0xE8,0x11,0x12,0x13,
2429 0x11,0x12,0xEF,0xF6,0x9B,0xEB,0xF9,0x01,0xA0,0xE2,0x02,0xE1,0x03,0xED,0x04,0xE3,
2430 0xE9,0x05,0xE4,0xE5,0xE7,0x06,0xEC,0xF0,0x07,0x08,0x09,0x0A,0x0B,0xF3,0x0C,0xF4,
2431 0xEE,0x0D,0xF2,0x0E,0x0F,0x10,0x05,0xE5,0xF3,0xF9,0x9B,0x01,0xEF,0x02,0x03,0xE1,
2432 0x04,0xE9,0x0A,0x0B,0xAE,0x9B,0xEC,0xED,0x01,0x02,0xF3,0xEE,0xF2,0x03,0xE5,0x04,
2433 0xE8,0xA0,0xE1,0x05,0xEF,0x06,0x07,0x08,0xE9,0x09,0x05,0x06,0xA0,0xAC,0xAD,0xF4,
2434 0xE9,0x01,0x02,0xE1,0xE5,0x03,0x9B,0x04,0x11,0xA0,0xBF,0xE1,0xE2,0xE6,0xED,0xE4,
2435 0xE9,0xF7,0xA7,0x01,0x02,0xBB,0x03,0x04,0xEC,0x05,0x9B,0xEE,0x06,0xEF,0x07,0xAC,
2436 0xE5,0xF3,0x08,0x09,0x0A,0xAE,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x06,0x07,0xA0,0xAE,
2437 0xE1,0xE5,0xEC,0xFA,0x9B,0xEF,0xE9,0x01,0x02,0x03,0x04,0x05,0x9B,0x9B,0x9B,0x9B,
2438 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
2441 /* external is only for debugging kern values */
2442 //#define USE_KERNING_H
2443 #ifdef USE_KERNING_H
2444 #define USE_KERN_PER256
2445 #include "kern.h"
2446 #warning using EXTERNAL kerning header + func
2447 #else
2448 #warning using internal kerning header + func
2449 unsigned char kern_p256_8859_1[ 256 ] = {
2450 /* x00 */
2451 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2452 /* x10 */
2453 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2454 /* x20 ! " # $ % & ' ( ) * + , - . / */
2455 99, 77,118,215,215,215,215, 38, 97, 97,159,159, 77,159, 59,225,
2456 /* x30 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
2457 225,159,225,225,225,225,225,225,225,225, 77, 77,192,192,192,225,
2458 /* x40 @ A B C D E F G H I J K L M N O */
2459 225,225,225,225,225,225,225,225,225, 77,156,225,225,225,225,225,
2460 /* x50 P Q R S T U V W X Y Z [ \ ] ^ _ */
2461 225,225,225,225,225,225,225,255,225,192,118,118,225,118,118, 31,
2462 /* x60 ` a b c d e f g h i j k l m n o */
2463 15,156,156,156,156,156, 59,159,156, 38, 77,156, 38,216,156,156,
2464 /* x70 p q r s t u v w x y z { | } ~ . */
2465 156,156, 99,156, 99,159,179,243,177,177,177,118, 38,118,156,225,
2466 /* x80 */
2467 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2468 /* x90 */
2469 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2470 /* xa0 */
2471 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2472 /* xb0 */
2473 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2474 /* xc0 */
2475 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2476 /* xd0 */
2477 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2478 /* xe0 */
2479 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2480 /* xf0 */
2481 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2485 #endif
2487 /************************************************************* ATSC RRT [VCHIP]
2488 This avoids RRT huffman parse w/ fast lookup of pre-decoded built-in table:
2489 The RRT only repeats once per minute so better to have it ready here.
2490 Most (short) EPGs are complete in less time than the RRT repeat cycle.
2491 The actual received RRT is only parsed for CRC32 to help QoS detection.
2494 /* FUTURE FIXME: This Rating Region Table is built-in and will not reflect
2495 any new categories. The spec short-changes the RRT size, at 1024 bytes
2496 and it's mostly full, so adding new categories is a moot point.
2498 char *ratings[] = {
2499 "Audience", "", "None", "TV-G", "TV-PG", "TV-14", "TV-MA","","","",
2500 "Dialogue", "", "D","","","","","","","",
2501 "Language", "", "L","","","","","","","",
2502 "Sex", "", "S","","","","","","","",
2503 "Violence", "", "V","","","","","","","",
2504 "Children", "", "TV-Y","TV-Y7","","","","","","",
2505 "Fantasy Violence", "", "FV","","","","","","","",
2506 "MPAA", "", "N/A","G", "PG", "PG-13","R","NC-17","X","NR",
2507 }; /* aka vchip */
2509 /************************************************************** ATSC VARS */
2511 /* ATSC System Time, 32 bit and structure versions */
2512 time_t atsc_stt = 0;
2513 struct tm atsc_stt_tm;
2514 /* unsigned int stt_prev = 0; */
2517 /* shorts use about 84k bytes for 12 hours of transport error counts.
2518 Terrestrial should never see more than 12898 errors per second.
2519 Cable maximum error rate should be around 25800 errors per second.
2520 Define the limits. want to only change CAP_HRS, let rest be computed.
2522 #define CAP_HRS 6
2523 #define CAP_STZ (CAP_HRS * 3600)
2524 short cap_stats[ CAP_STZ ]; /* 6 hours of capture error stats log */
2525 /* enough rows to match size of cap_stats[], plus 2 for full error text */
2526 #define CAP_ROWS (2+(CAP_HRS * 60))
2527 /* entire line is 80 columns, but only two ansi colors in wall+ets
2528 giving computation of (62 * 8) + 20 bytes per line
2530 /* each line of capture log is this many bytes */
2531 #define CAP_COLS ((62 * 8) + 20)
2533 /* enough for descriptor length and width, with some ansi color codes */
2534 /* enough for 32 lines of description per vc and 8 vc selects */
2535 #define TVCT_ROWS 256
2536 /* at least 80 for no color codes, and 5 8-byte color codes, uses only 2 */
2537 #define TVCT_COLS 128
2539 /* enough for 128 EITs + 128 ETTs + 24 extras, adjust if needed */
2540 #define MGT_ROWS 280
2541 /* enough for some ansi color */
2542 #define MGT_COLS 128
2544 char cap_text[ CAP_ROWS ][ CAP_COLS ];
2545 unsigned int cap_idx; /* lines in cap text */
2547 /* too small to calloc */
2548 char tvct_text[ TVCT_ROWS ][ TVCT_COLS ];
2550 /* vct no idea how many lines needed */
2551 unsigned int tvct_idx;
2552 char mgt_text[ MGT_ROWS ][ MGT_COLS ];
2554 /* mgt needs enough for each EIT ETT + a few more */
2555 unsigned int mgt_idx;
2556 unsigned int mgt_dl; /* mgt descriptor length */
2558 unsigned int pmt_atcc; /* pmt closed caption indication */
2560 int vc_ncs = 0; /* # of populated vc[] entries */
2561 int pat_ncs = 0; /* # of PMTs found in PAT */
2562 int vct_ncs = 0; /* # of VCs found in VCT */
2564 unsigned int vct_crc = 0;
2565 unsigned int vct_dtl = 0; /* vct detail [d] key toggle */
2566 unsigned int mgt_crc = 0;
2568 /* display resize handling */
2569 int columns = 80; /* screen x max */
2570 int lines = 25; /* screen y max */
2571 int pcol;
2572 int prow;
2573 int user_lines = 20; /* how many lines after line 5 */
2575 /* program guide handling */
2576 volatile int pgme = 0; /* previous program event count */
2577 volatile int pgmd = 0; /* previous program description count */
2578 int pgmto = 0; /* program timeout */
2579 int pgm_done = 0;/* non zero when event + desc = no change */
2580 /* int pgm_fail = 0;/ * PG_* defines */
2582 int qosc = 0; /* 100 - (100 * errors / (packets/seconds)) */
2583 long long qost = 0LL; /* 100 - (100 * errors total / ts packet total) */
2584 int last_qos; /* ^ v indicators */
2585 int last_pktb; /* last packet bad count */
2586 int last_pktc; /* last packet count */
2588 /******************************************************************** TS
2589 * transport validation and counters: test packet_uses these
2592 unsigned char last_cc[0x2000]; /* 8k last CC seen by PID 0-1FFF */
2593 unsigned int cce_pids[0x2000]; /* 32k CC errors by PID 0-1FFF */
2594 unsigned int psi_pids[0x2000]; /* 32k payload counts by PID 0-1FFF */
2595 unsigned int pids[0x2000]; /* 32k packet counts by PID 0-1FFF */
2597 unsigned int mg_idx = 0; /* increments with new list entries */
2598 unsigned int mg_eit = 0; /* 0 = no EIT in MGT */
2599 unsigned int eit_idx = 0;
2600 unsigned int ett_idx = 0;
2601 unsigned char mgt_vn = 0xFF;
2603 /* Terrestrial has 8 vc max, cable has no EPG but > 16 possible VCs.
2604 The most I've seen active at once, with video, on cable is about 8 SDs.
2606 #ifdef USE_CABLE
2607 #define VCZ 32
2608 #else
2609 #define VCZ 8
2610 #endif
2612 /****************************************************************************
2613 * 16 virtual channels is all that is supported, but more could be.
2614 * VC n has Program # (.1 .2 .3) which EIT/ETT etmid uses from sr field
2615 * Each VC entry is just over 10 kilobytes.
2616 ********************************************************************* ATSC VC
2618 struct vc_s {
2619 char name[16]; /* 7 unicode 16 bit words for vc name + NULL */
2620 char cname[16]; /* component name from pmt for specific program */
2621 unsigned int major; /* 10 bit major channel number for vc */
2622 unsigned int minor; /* 10 bit minor channel number for vc */
2623 unsigned int mode; /* modulation mode: see A/65b table 6.5 */
2624 unsigned int freq; /* frequency, obsolete, 0 after jan 1 2010 */
2625 unsigned short tsid; /* VCT TSID to match against PAT TSID */
2626 unsigned int pn; /* program number for (or from) PAT and PMT */
2627 unsigned int etm; /* extended text message location, 0 if none */
2629 /* next three get ignored, but stored */
2630 unsigned int actrl; /* 1 bit access is not controlled if 0 */
2631 unsigned int hide; /* 1 bit nz makes channel surf skip this one */
2633 /* cable uses these two, but not terrestrial */
2634 unsigned int psel; /* path select (0=1st or 1=2nd tuner in STB) */
2635 unsigned int oob; /* 1 is TSID is not in PTC of this CVCT? */
2637 unsigned int hideg; /* if hidden set, hide the guide too */
2639 /* more useful bits */
2640 unsigned int svct; /* service type */
2641 unsigned int src; /* source id, part of etmid, pgn LU for EPG */
2642 unsigned short adl; /* addition descriptor length also skipped */
2643 unsigned int acn; /* audio channels in this vc */
2644 /* is adl is used? */
2646 unsigned int vctes; /* Elementary Streams in this VCT */
2647 unsigned int pmtes; /* Elementary Streams in this PMT */
2649 unsigned short pcrpid; /* ES used for timing on this VC */
2650 unsigned short pmtpid; /* PMT used for this VC */
2651 unsigned short vcdlen; /* VC descriptor length 10 bits */
2652 unsigned char vcdescr[1024]; /* ATSC VCT descriptors */
2653 unsigned char vct_vn; /* VCT vn, VCZ copies of same VN */
2655 unsigned short pilen; /* PMT info descriptor len 10 bits */
2656 unsigned char pidesc[1024]; /* PMT info descriptor */
2657 unsigned char pmtvn; /* PMT version for this VC */
2659 /* elementary stream max allowed for VCT is ESZ */
2660 #define ESZ 8
2661 unsigned int espid[ESZ]; /* VCT ES PID of streams */
2662 unsigned char estype[ESZ]; /* VCT ES stream types */
2663 char eslang[ESZ][4]; /* VCT ES ISO-639 lang code */
2664 unsigned int esilen[ESZ]; /* VCT ES descriptors length */
2665 unsigned char esdesc[ESZ][1024]; /* VCT ES descriptors */
2666 unsigned char ca; /* PMT has CA flag */
2667 int hres; /* SEQ video horizontal resolution */
2668 int vres; /* SEQ video vertical resolution */
2669 int vfr; /* SEQ video framerate */
2670 /* not on cable. ATSC broadcasts send bitrate in desc: 192 = 2.0 384 = 5.1 */
2671 int abr[ESZ]; /* AC3 desc audio bitrate */
2672 int ach[ESZ]; /* AC3 desc audio chans (calc) */
2674 struct vc_s vc[VCZ]; /* terrestrial uses less than 8 VChannels */
2676 /* PAT + PMT fill these in, VCT copies over descriptors when it arrives */
2677 struct pa_s {
2678 unsigned short pmtpid; /* PAT PMTPID 12 bits */
2679 unsigned short pn; /* PAT program number 16 bits */
2680 unsigned short tsid; /* PAT transport stream id 16 bits */
2681 unsigned char vn; /* PMT version number 8 bits */
2682 unsigned char lv; /* PMT last version 8 bits */
2683 unsigned short pcrpid; /* PMT pcrpid 12 bits */
2684 unsigned short pilen; /* PMT program info len 10 bits */
2685 unsigned char pidesc[1024]; /* PMT program info desc 1024 bytes */
2686 unsigned short pmtes; /* PMT number of streams 2 bytes */
2687 unsigned short acn; /* PMT number of audios 2 bytes */
2689 /* 8 PMT streams per program map, with 8 PAT programs, 1 video, multi audios */
2690 unsigned short espid[ESZ]; /* PMT ES PIDs of streams 16 bytes */
2691 unsigned char estype[ESZ]; /* PMT ES stream types 8 bytes */
2692 char eslang[ESZ][4]; /* PMT ES ISO-639 lang code 32 bytes */
2693 unsigned short esilen[ESZ]; /* PMT ES stream desc len 16 bytes */
2694 unsigned char esdesc[ESZ][1024]; /* PMT ES stream descrptr 8192 bytes */
2695 unsigned char ca; /* PMT has CA flag */
2696 int hres; /* SEQ video horizontal resolution */
2697 int vres; /* SEQ video vertical resolution */
2698 int vfr; /* SEQ video frame rate */
2700 /* not on cable. ATSC broadcasts send bitrate in desc: 192 = 2.0 384 = 5.1 */
2701 int abr[ESZ]; /* AC3 desc audio bitrate */
2702 int ach[ESZ]; /* AC3 desc audio chans (calc) */
2704 struct pa_s pa[ VCZ ]; /* allocation minimum 75k bytes */
2708 /* NOTE: KQED has 5, but they cheat by hiding some streams at different
2709 times, so not all the VChanels on the list are actually active.
2710 This should not present any timer scheduling issues, as long as you
2711 don't depend on a specific Vchannel order but instead use program number.
2715 A/65b specification does not give any fixed limit to how many events may
2716 be included in a single EIT, only that the EIT covers a 3 hour time frame.
2718 program guide eit section event max
2719 8 gives round numbers, 7 seems to be the max so far, 6 is no-dup minimum,
2720 except FOX has nationwide PSIP screw-up that prevents 6 from working.
2722 PEZ also see (numevs-1) for why last event is ignored
2723 [because it's duplicated in next EIT]
2725 Math is easier to debug with PEZ 8, but wastes memory. Using it anyway because
2726 of issues with FOX sending weirdo out-of-chronological-order EIT events.
2728 Program Event siZe, anticipated max number of events per EIT
2731 #define PEZ 8
2732 /* EITn maximum count, in the spec */
2733 #define EIZ 128
2734 /* ETTn maximum count, in the spec */
2735 #define ETZ 128
2736 /* virtual channel count, vc[8] limits to 8 */
2738 /* program guide size */
2739 /* VC # is top level multiplex for each entry in pgm */
2740 #ifdef USE_CABLE
2741 #define PGZ (1 * EIZ * PEZ)
2742 #else
2743 #define PGZ (VCZ * EIZ * PEZ)
2744 #endif
2746 /* If you change the EPG data sizes, don't forget to do make eclean,
2747 or else the saved EPG data structure being wrong size will crash it.
2749 ATSC A/65b allows 512 bytes for event title and description.
2750 It uses over 8MB in RAM with full EPG data allocated, mostly blank.
2752 Define USE_TINY_EPG for an EPG that uses about 2.5MB for 8 VCs.
2754 FIXME: Are any places left where the size is numerical constant,
2755 and not using either the define constant or sizeof?
2756 Seems to be OK so far, aside from forgetting make eclean.
2758 #define USE_TINY_EPG
2759 #ifndef USE_TINY_EPG
2760 /* old style */
2761 #define PNZ 512
2762 #define PDZ 512
2763 #warning using full EPG
2764 #else
2765 /* new style */
2766 #define PNZ 64
2767 #define PDZ 256
2768 #warning using tiny EPG
2769 #endif
2771 /* rating text limit */
2772 #define PRZ 16
2775 /* NOTE:
2776 1k per event for text only is wasteful. Usually is about 160 chars,
2777 with around 20-30 being used for event name and rest for text, so
2778 64 and 192 chars should be enough for most event guides.
2780 PDZ 256 may be insufficient. uncompressed raw text max should be 256,
2781 but some Huffman decompressed descriptions on KPXB are nearly 512 chars
2782 so 512 is probably the real limit for ETT single mss
2784 This should be limited in huffman decode, so PNZ/PDZ can be tweaked here.
2785 Do not tweak it here until huffman decode isolates the 512 char buffer, as
2786 well as boundary checking on parse eit/ett mss so they don't overflow.
2787 The rest of the code should treat PNZ PDZ as the maximums for the fields.
2789 TODO:
2790 Eventuallly I'll have realloc on the EPG events and EIT/ETT to allow
2791 the maximum title and description sizes without wasting too much memory.
2794 /************************* ATSC PSIP PROGRAM LIST ****************************
2795 * This is the largest array the program needs for speed.
2796 * Maximum is 128 EIT's, each EIT covers 3 hours, so max is 6 programs/EIT
2797 * 6 because assuming 30 min smallest pgm, gives 6 pgms in 3 hours
2798 * NOTE: always seems to be the last EIT is duplicate of next first.
2799 * 128 * VCZ * 6 is maximum number of program events to allocate for 8 vc's
2801 * NEEDLESS COMPLEXITY: MEMORY IS CHEAP: USING 128 * VCZ * 8
2802 * MGT should calloc this based on (EIT+ETT)*VCZ tables given?
2803 * MGT can't be depended upon because it seems to be broken.
2804 * Need to test if more than 6 valid pgms ever in one EIT. ok so far.
2806 * NOTE: handling non-chron broken FOX EPG requires 8, not 6, EIZ events.
2808 ******************************************************* ATSC PROGRAM GUIDE */
2809 struct event_s {
2810 unsigned int etmid; /* Extended Text Message ID */
2812 char name[PNZ]; /* from EIT, name title text/huffman + NUL */
2813 char tname[32]; /* name truncated to 16 chars [+ MMDD-HHMM?] */
2815 char nlang[4]; /* from EIT, name title language code + NUL */
2816 unsigned char eitn; /* from EIT number n */
2817 unsigned char eitvn; /* from EIT, last version for title */
2818 unsigned char ncomp; /* from EIT, name compression type */
2819 unsigned char nmode; /* from EIT, name unicode mode */
2820 unsigned char nchr; /* from EIT, name bytes in description */
2823 char desc[PDZ]; /* from ETT, description text/huffman + NUL */
2824 char dlang[4]; /* from ETT, description language code + NUL */
2825 unsigned char ettn; /* from ETT number n */
2826 unsigned char ettvn; /* from ETT, last version seen for desc */
2827 unsigned char dcomp; /* from ETT, desc compression type */
2828 unsigned char dmode; /* from ETT, desc unicode mode */
2829 unsigned char dchr; /* from ETT, desc bytes in description */
2831 char rating[PRZ]; /* from EIT, descriptors xref RRT + NUL */
2832 char movie; /* 0 is no rating/not movie, nz is mpaa idx */
2834 unsigned short source; /* from EIT ETMID top 16 bits */
2835 unsigned short pn; /* from VCT lookup of source */
2837 unsigned int st; /* from EIT, start time */
2838 struct tm stm; /* start time as type tm */
2839 unsigned int ls; /* from EIT, length in seconds */
2841 char vc; /* from ETMID source, VC select */
2842 char va; /* VC audio select, always 0 for now */
2843 char chan; /* scanlist ch # for logical ch # */
2847 struct event_s epg[ PGZ ]; /* live program guide raw [chopped] format */
2848 struct event_s *epg2 = NULL; /* test epg pointer */
2849 #ifdef USE_WWW
2850 struct event_s *epg3 = NULL; /* http epg pointer */
2851 struct event_s *epg4 = NULL; /* http epg grid pointer */
2852 #endif
2854 /* program guide version of epg re-sorted/re-arranged, blanks moved to end */
2855 short pg[ PGZ ]; /* index to user selected entries in epg[] */
2856 short pg1[ PGZ ]; /* index for save/load */
2857 short pg2[ PGZ ]; /* index for test guides */
2858 #ifdef USE_WWW
2859 short pg3[ PGZ ]; /* index for dump epg */
2860 short pg4[ PGZ ]; /* index for dump epg */
2861 #endif
2863 struct pgm_s pgm; /* active program guide settings */
2864 struct pgm_s pgm1; /* load/save epg copy */
2865 struct pgm_s pgm2; /* test guides copy */
2866 #ifdef USE_WWW
2867 struct pgm_s pgm3; /* http refresh copy */
2868 struct pgm_s pgm4; /* http refresh copy */
2869 #endif
2871 unsigned char eit_vn[EIZ][VCZ];
2873 /* If guide ever gets above 32768 entries, short will be a problem.
2874 It's not likely to for terrestrial and no guide on cable yet, so is moot.
2878 7 bits for eit and 3 bits for source, terrestrial hasnt had 8 VCs here yet
2879 SEE numevs, using numevs-1, because last event is repeated as first next
2880 multiply by 6 to account for 6 out of 7 EIT mss being non-duplicate
2881 multiply by 8 too, because that's how many vc[] structs for Program lookup.
2884 int epg_test = 0; /* test guides in progress hold down */
2885 int epg_www = 0; /* 'w' status means http is bound */
2889 /******************************************************************** ATSC STT
2890 * see A/65b Table 6.1
2892 struct stt_s {
2893 /* payload data */
2894 unsigned int st; /* system time */
2895 unsigned char guo; /* gps utc offset */
2896 unsigned short ds; /* daylight savings flag */
2898 /* parsed data */
2899 unsigned char dss; /* nz means DST in effect */
2900 unsigned char dsd; /* nz means DST change on this day of month */
2901 unsigned char dsh; /* nz means DST change on this hour of day */
2903 struct stt_s astt; /* copy of data in last STT read */
2905 /* generic payload structure */
2906 struct payload_s {
2907 unsigned int crc; /* crc32 result */
2908 unsigned int payoff; /* payload offset */
2909 unsigned short sl; /* section length */
2910 unsigned char payok; /* payload status */
2911 unsigned char vn; /* version number */
2912 unsigned char lv; /* last version */
2913 unsigned char payload[4096]; /* payload */
2916 /* These structures are the generic payload structures used during capture.
2917 They are allocated before capture to point to valid memory, and
2918 freed after capture because they aren't useful until next capture.
2920 struct payload_s stt; /* single packet, 56 bit payload */
2921 struct payload_s rrt; /* 4k rating region table */
2922 struct payload_s ctt; /* 4k channel text table */
2923 struct payload_s mgt; /* 4k master guide table */
2924 struct payload_s vct; /* 4k virtual channel table */
2925 struct payload_s pat; /* 4k program association table */
2926 struct payload_s cat; /* 4k conditional access table */
2927 struct payload_s pmt[VCZ]; /* 4k program map table per VC */
2929 struct payload_s stt; /* single packet, 56 bit payload */
2930 struct payload_s rrt; /* 4k rating region table */
2931 struct payload_s ctt; /* 4k channel text table */
2932 struct payload_s mgt; /* 4k master guide table */
2933 struct payload_s vct; /* 4k virtual channel table */
2934 struct payload_s pat; /* 4k program association table */
2935 struct payload_s cat; /* 4k conditional access table */
2936 struct payload_s pmt[VCZ]; /* 4k program map table per VC */
2938 #ifdef USE_DYNAMIC
2939 #warning using dynamic EIT & ETT & FIFO
2940 /* dynamic EIT ETT allocation, causes crash on [l]-[p]-[enter] ? */
2941 struct payload_s *eit = NULL; /* 4k * 128 Event Info Table alloc */
2942 struct payload_s *ett = NULL; /* 4k * 128 Event Text Table alloc */
2943 #else
2944 #warning using static EIT & ETT & FIFO
2945 /* old static usage > 1MB, no pointer changes needed to parse eit/ett yet */
2946 struct payload_s eit[ EIZ ]; /* 4k * 128 Event Info Table */
2947 struct payload_s ett[ EIZ ]; /* 4k * 128 Event Text Table */
2948 #endif
2950 #ifdef USE_AV_PAYLOADS
2951 /* not using yet, but may find use later. gobbles CPU to assemble mpeg2 */
2952 /* atscut -s tries to avoid parsing mpeg2 (except SEQ) and is real fast now */
2953 /************************************************************** MPEG2 Video */
2954 /* MPEG2 Video Payload */
2955 #define MPEG2_MAX (128 << 10)
2956 struct {
2957 unsigned int payoff;
2958 unsigned int payok;
2959 unsigned int afl_payoff; /* where section starts after afc */
2960 unsigned char payload[ MPEG2_MAX ]; /* don't know size limit */
2961 } mpv;
2962 /************************************************************** MPEG2 Audio */
2963 /* A/52 Audio Payload */
2964 #define A52_MAX 65536
2965 struct {
2966 unsigned int payoff;
2967 unsigned int payok;
2968 unsigned int afl_payoff; /* where section starts after afc */
2969 unsigned char payload[ A52_MAX ]; /* dont know size limit */
2970 } a52;
2971 /****************************************************************************/
2972 #endif
2973 unsigned char new_pat[188]; /* no descriptors */
2974 unsigned char new_pmt[376]; /* has descriptors */
2975 int pat_built = 0;
2976 int pmt_built = 0;
2978 /*************** END OF RAW PAYLOAD TABLES BUILT FROM STREAM ***************/
2980 /* it's only a concidence that Table limit is same as PID limit */
2981 #define TABLE_MAX 0x2000
2982 #define PID_MAX 0x2000
2983 /*************************************************************** LOCAL MGTs */
2984 /* master guide created every time MGT version number changes */
2985 struct mg_s {
2986 unsigned short tt;
2987 unsigned short pid;
2988 unsigned short nb;
2989 unsigned short nb1;
2990 unsigned short dl;
2991 unsigned char vn;
2992 unsigned char vn1;
2993 }; /* 9 bytes */
2995 /* specs says reserved in some places so don't need all 0x2000
2996 has big gaps, wastes memory, but a much faster lookup.
2998 TODO:
2999 All the ATSC stuff should be above 0x1000, so can halve the list size.
3001 4096 . 9 bytes, 36k per struct below, about 144k total
3003 struct mg_s mg[ TABLE_MAX ]; /* raw order parsed by MGT, up to mg_idx */
3004 struct mg_s mgp[ PID_MAX ]; /* mg by PID, not for sytem PID 1FFB */
3005 struct mg_s mgs[ TABLE_MAX ]; /* mg sorted by table type */
3007 /* max chars in name */
3008 #define SEARCH_NAME_MAX 32
3009 /* max names on list */
3010 #define SEARCH_LIST_MAX 256
3012 struct search_s {
3013 char name[ SEARCH_NAME_MAX ]; /* search name [required] */
3014 int chan; /* channel filter [optional] */
3015 unsigned short pn; /* program# filter [optional] */
3016 char days; /* week day filter [optional] */
3019 struct search_s search_list[ SEARCH_LIST_MAX ]; /* dynamic event search */
3020 int search_idx; /* number of search event names in cfg file */
3022 /* spam filter, name and age are saved, len is computed on load */
3023 /* max age before spam is automatically removed is 30 days */
3024 #define SPAM_NAME_MAX 20
3025 #define SPAM_LIST_MAX 1024
3026 #define SPAM_AGE_MAX (SECDAY * 365)
3027 struct spam_s {
3028 char name[ SPAM_NAME_MAX ]; /* truncated spam name */
3029 time_t st; /* last matching spam found */
3030 int len; /* byte len of name */
3033 struct spam_s spam_list[ SPAM_LIST_MAX ];
3034 pthread_mutex_t spam_mutex;
3035 int spam_idx = 0; /* spam index */
3037 /* output reductions for ssh and maximum efficiency with no extra redraws */
3038 struct {
3039 int w; /* old termio width to trigger refresh if current differs */
3040 int h; /* old termio height to trigger refresh if current differs */
3042 int channels; /* TODO */
3043 int clock; /* TODO date and time */
3044 int volstats; /* TODO cap dir space left */
3045 int headers; /* TODO headers for various modes */
3046 int timers; /* replaces timer_list */
3047 int pstats; /* table parser stats */
3048 int vstats; /* channel vc status */
3049 int estats; /* event stats */
3050 int mgt; /* no redux? */
3051 int vct; /* redux */
3052 int epg; /* redux */
3053 int log; /* redux (this one does not redux very well) */
3054 } refresh;
3056 /* EIT locks allow drain thread to change epg[] before UI thread uses it */
3057 volatile unsigned char epg_locks[ EIZ * VCZ ]; /* 8 VCs of 128 EITs per VC */
3059 /* timer, mgt, vct, epg and capture log display
3060 start or end of capture resets to 0 for timers
3061 and to make sure capture log gets written
3063 #define D_TIMERS 0
3064 #define D_HELP 1
3065 #define D_MGT 2
3066 #define D_VCT 3
3067 #define D_EPG 4
3068 #define D_LOG 5
3069 #define D_CHANNELS 6
3070 /* default is always fall back to timer list */
3071 int display_type = D_TIMERS;
3072 int channel_offset = 0;
3073 int channel_idx = 0;
3075 /* standard [w] key display options for most DVB drivers */
3076 #define SIG_STR 0
3077 #define SIG_SNR 1
3078 #define SIG_FMHZ 2
3079 #define SIG_WLEN 3
3081 /* 2 options for custom HD3000 driver, bit error rate/s and modulator freq */
3082 #ifndef USE_DVB_EXPERIMENTAL
3083 #define SIG_MODREG 4
3084 #else
3085 #warning using DVB EXPERIMENTAL driver
3086 #define SIG_FOHZ 4
3087 #define SIG_BER 5
3088 #define SIG_MODREG 6
3089 /* not done yet */
3090 #endif
3092 int display_sigtype = SIG_STR; /* default is show % of 30dB */
3094 /* if non zero, show ATSC and MPEG2 transport status in bottom 7 lines */
3095 int display_stat = ~0;
3097 int udp_mcast = 0; /* user toggles with u key */
3098 #ifdef USE_MCAST
3099 struct udp_s {
3100 int sock;
3101 int bind;
3102 struct sockaddr_in addr;
3104 struct udp_s udp;
3105 #endif
3108 #define ALLOC_LIMIT 32
3109 #define ALLOC_CHARS 32
3110 struct alloc_s {
3111 void *p;
3112 size_t z;
3113 char n[ ALLOC_CHARS ];
3116 struct alloc_s alloc_list[ ALLOC_LIMIT ];
3118 #define TIMER_NAME_MAX 20
3119 #define AOS_TIME_MIN 5
3121 /* timer struct */
3122 struct qtimer_s {
3123 /* FIXME: use TIMER_NAME_MAX and fix ALL, still looking for stragglers */
3124 unsigned int chan; /* PTC to capture */
3125 unsigned int start; /* event start time, keep as unsigned so -1 > rest */
3126 int h; /* weekday timer start hours */
3127 int m; /* weekday timer start minutes */
3128 int len; /* event length in seconds */
3129 int days; /* weekday bits */
3130 int pn; /* program number */
3131 int vc; /* VC to capture, -1 for raw stream capture */
3132 int va; /* VC audio to capture */
3133 int future; /* future time, if weekday bits set */
3134 int secdt; /* short timer by this much for ext2fs delete delay */
3135 int etmid; /* etmid to hold down auto-add after zap */
3136 int qstat; /* queue status, set by show timers */
3137 char *tcol; /* timer color set from status */
3138 char name[TIMER_NAME_MAX]; /* 16 chars + @.3 will fit in 19 allowed */
3139 char ename[PNZ]; /* full name from epg[] */
3140 char edesc[PDZ]; /* full description from epg[] */
3141 char lang[4];
3144 /* mem use is MAX * ( (4 * 12) + 32 + (512 + 512) ) */
3145 /* 256 is 282624 bytes */
3146 #define TIMER_LIST_MAX 256
3147 /* TIMER_LIST_MAX qtimer_s structures */
3148 struct qtimer_s timer[ TIMER_LIST_MAX ];
3150 /* from current timer if it was added from EPG */
3151 char epg_name[PNZ];
3152 char epg_desc[PDZ];
3153 int epg_to;
3154 int epg_ch;
3156 #define RTC_MAX 3
3157 char *clock_text[ RTC_MAX ] = {
3158 "REALTIME",
3159 "PROCESS_CPUTIME_ID",
3160 "THREAD_CPUTIME_ID"
3163 clockid_t clock_ids[ RTC_MAX ] = {
3164 CLOCK_REALTIME,
3165 CLOCK_PROCESS_CPUTIME_ID,
3166 CLOCK_THREAD_CPUTIME_ID
3168 clockid_t clock_method = CLOCK_PROCESS_CPUTIME_ID;
3169 long long clock_res; /* (tv.sec<<32) | tv.nsec */
3170 int clock_idx;
3172 #ifdef USE_PNG
3173 struct img_s {
3174 jmp_buf jmpbuf;
3175 int width;
3176 int height;
3177 int depth;
3178 int transform;
3179 int colortype;
3180 int interlace;
3181 png_bytep row_pointers[SIG_PNG_H];
3183 #endif
3185 int fifoz = FIFOZ_CAP;
3187 #ifndef USE_DYNAMIC
3188 unsigned char fifo_buffer[FIFOZ_CAP];
3189 #endif
3191 #define F_PATH 1
3192 #define F_PFILE 2
3193 #define F_TFILE 3
3194 #define F_FILE 4
3196 /****************************** globals end *********************************/
3199 /******************************* prototypes ***********************************/
3200 /* Other prototypes would go here as necessary, if re-arranging doesn't help. */
3202 /* threads. pthreads complains if static, so using globals */
3203 void * dvrlog_loop ( void * arg );
3204 void * fifo_input_loop ( void * arg );
3205 void * fifo_output_loop ( void * arg );
3206 void free_fifo( struct fifo_s *fifo );
3208 /* functions, can be static or global */
3209 void get_time( void );
3210 void test_epgs( char *caller );
3211 void open_device( char *caller );
3212 void close_device( char *caller );
3213 void save_config( char *caller );
3214 void console_reset( void );
3215 void console_scan( void );
3216 // void console_exit( int errorcode );
3218 /* http functions are stuck to the end for author expediency */
3219 #ifdef USE_WWW
3220 void http_close_sockets(void);
3221 void http_load_allows(void);
3222 #endif
3224 int aos_delay = AOS_VSB_DELAY;
3227 /***************************** main functions begin ************************/
3230 /* Similar to but different from strncpy:
3231 No string greater than 64k allowed to be copied, log > 64k and NULLs.
3232 Does not pad destination with 0's if length of source is shorter than n.
3233 There is no valid reason to spend cpu cycles filling memory with zeros.
3234 This will always null terminate at n-1 even if s is n len bytes long.
3235 d result will always be n-1 characters long.
3237 /* static */
3238 void
3239 astrncpy ( char *d, char *s, unsigned int n )
3241 int c = n;
3242 char *t = d;
3243 /* log nulls */
3244 if ( (NULL == d) || (NULL == s) ) {
3245 #if 0
3246 dvrlog( LOG_INFO, "astrncpy() %p %p NULL", d, s);
3247 #endif
3248 return;
3250 /* log 0 and > 65535 len */
3251 if ( (0 == n) || (n > 65535) ) {
3252 #if 0
3253 dvrlog( LOG_INFO, "astrncpy() %d chars", n);
3254 #endif
3255 return;
3258 /* count auto decrements, pointers auto increment */
3259 while (c-- > 0) if (0 == (*t++ = *s++)) break;
3261 d[n-1] = 0;
3265 /* debug: used to find missing parameters. comment this for normal use */
3266 /* #define asnprintf snprintf */
3268 #ifndef asnprintf
3269 /* Incremental snprintf, appends to string 'a' until size 'b' reached.
3270 This keeps 'b' limited to 'a' string alloc instead of blind melon limit.
3271 First call should have a strlen 0, *a = 0 or you get variable junk.
3273 /* static */
3274 void
3275 asnprintf ( char *a, size_t b, const char *fmt, ... )
3277 char *p, t[1024];
3278 int la;
3279 int lt;
3280 va_list ap;
3282 if (NULL == a) return;
3283 if (b < 1) return;
3285 memset(t, 0, sizeof(t));
3286 va_start(ap, fmt);
3287 /* variable arg macro start, pass fmt and ap, variable arg macro end */
3288 vsnprintf( t, sizeof(t)-1, fmt, ap );
3289 va_end(ap);
3291 la = strlen(a);
3292 lt = strlen(t);
3294 /* bit bucket does not overflow? */
3295 if ( (la + lt + 1) < b ) {
3296 p = &a[la];
3297 /* nul term it */
3298 astrncpy( p, t, lt + 1 );
3300 /* if bit bucket does overflow, silently fail and don't exceed boundary */
3302 #endif
3306 /* VCT MGT EPG LOG guides need to refresh stats on exit to timer list */
3307 /* Control-L also uses this to refresh the tube
3309 /* static */
3310 void
3311 set_refresh( void )
3313 refresh.channels = 1;
3314 refresh.headers = 1;
3315 refresh.volstats = 1;
3316 refresh.clock = 1;
3317 refresh.timers = 1;
3318 refresh.vstats = 1;
3319 refresh.pstats = 1;
3320 refresh.estats = 1;
3321 refresh.epg = 1;
3322 refresh.vct = 1;
3323 refresh.mgt = 1;
3324 refresh.log = 1;
3329 #ifdef USE_ASCII_XLATE
3330 /* step thru t until 0 and convert chars > 128 to ASCII table[char-128] */
3331 /* static */
3332 void
3333 ascii_xlate( unsigned char *table, unsigned char *t )
3335 while (0 != *t) {
3336 if (*t > 128) *t = table[ *t - 128 ];
3337 if ('.' == *t) *t = ' ';
3338 if ('`' == *t) *t = ' ';
3339 if ('\'' == *t) *t = ' ';
3340 t++;
3343 #endif
3345 /* debug: find missing parameters. comment this for normal use. */
3346 /* also helped to find deadlock in dvrlog, thus the rewrite */
3347 /* #define USE_SYSLOG */
3348 #ifdef USE_SYSLOG
3349 #define dvrlog syslog
3350 #warning using syslog
3351 #endif
3353 #ifndef dvrlog
3354 #warning using dvrlog
3355 /* replacement for syslog */
3356 /* static */
3357 void
3358 dvrlog ( int level, char *fmt, ... )
3360 char m[ LOG_CHARS ]; /* log text */
3361 va_list ap; /* variable length arg list */
3364 /* variable arg macro start, pass fmt and ap, variable arg macro end */
3365 va_start( ap, fmt );
3366 vsnprintf( m, sizeof(m)-1, fmt, ap );
3367 va_end( ap );
3369 /* log full silently drops messages */
3370 if (dvrlog_idx >= LOG_LINES) return;
3372 while (EBUSY == pthread_mutex_trylock( &dvrlog_mutex )) {
3373 nanosleep( &atomic_sleep, NULL );
3376 get_time();
3378 /* log file is open */
3379 snprintf( &dvrlog_list[ dvrlog_idx * LOG_CHARS], LOG_CHARS-1,
3380 "%s %s\n", &date_now[4], m);
3381 dvrlog_idx++;
3383 pthread_mutex_unlock( &dvrlog_mutex );
3385 #endif
3389 #ifndef USE_SYSLOG
3390 /* log queue drain thread */
3391 void *
3392 dvrlog_loop ( void * arg )
3394 int i;
3395 char *t;
3397 dvrlog( LOG_INFO, "%s detached %d", WHO, getpid() );
3399 while (dvrlog_fd != NULL) {
3400 if (dvrlog_idx > 0) {
3401 while (EBUSY == pthread_mutex_trylock( &dvrlog_mutex ))
3402 nanosleep( &atomic_sleep, NULL );
3404 for (i = 0; i < dvrlog_idx; i++) {
3405 t = &dvrlog_list[i * LOG_CHARS];
3406 fprintf( dvrlog_fd, "%s", t);
3407 *t = 0;
3409 dvrlog_idx = 0;
3410 fflush( dvrlog_fd );
3411 pthread_mutex_unlock( &dvrlog_mutex );
3413 nanosleep( &atomic_sleep, NULL );
3415 return NULL;
3418 /* start threads for dvrlog */
3419 /* static */
3420 void
3421 init_dvrlog ( void )
3423 char n[256];
3424 int ok;
3426 if (NULL != dvrlog_fd) return;
3428 snprintf( dvrlog_name, sizeof(n)-1, "%s%s%d.log",
3429 log_path, NAME, arg_devnum);
3431 #ifdef USE_MULTI_LOG
3432 /* need to rename it? */
3433 struct stat f;
3434 ok = stat( dvrlog_name, &f);
3435 if (0 == ok) {
3436 struct tm t;
3437 localtime_r( &f.st_mtime, &t);
3438 snprintf( n, sizeof(dvrlog_name)-1,
3439 "%s.%04d%02d%02d-%02d%02d%02d",
3440 dvrlog_name,
3441 t.tm_year + 1900,
3442 t.tm_mon + 1,
3443 t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec );
3444 rename( dvrlog_name, n );
3446 /* USE_MULTI_LOG */
3447 #endif
3449 /* append to current file (or makes new one) */
3450 dvrlog_fd = fopen( dvrlog_name, "a" );
3451 advrlog( LOG_INFO, "log_fd %p %s", dvrlog_fd, dvrlog_name);
3453 /* if opened, see if thread can start, if can't start, fallback to syslog */
3454 if (NULL != dvrlog_fd) {
3455 fprintf( dvrlog_fd, "--------------- ");
3456 fprintf( dvrlog_fd, "%s %s-%s %s\n",
3457 syslog_name, VERSION, LASTEDIT, __DATE__ );
3458 fflush( dvrlog_fd );
3460 memset( &dvrlog_thread, 0, sizeof(pthread_t) );
3461 ok = pthread_attr_init( &dvrlog_attr );
3462 ok |= pthread_attr_setdetachstate( &dvrlog_attr,
3463 PTHREAD_CREATE_DETACHED);
3465 ok |= pthread_create( &dvrlog_thread, &dvrlog_attr,
3466 &dvrlog_loop, NULL );
3467 if (0 != ok) {
3468 dvrlog_fd = NULL; /* fallback to syslog */
3469 dvrlog( LOG_INFO, "%s failed, using syslog()", WHO);
3474 /* if running in an xterm set the title and icon name */
3475 /* static */
3476 void
3477 set_title()
3479 char *dpy = NULL;
3480 char *vty = NULL;
3482 dpy = getenv( "DISPLAY" );
3483 vty = getenv( "TERM" );
3484 if (NULL == dpy) return;
3485 if (NULL == vty) return;
3487 advrlog( LOG_INFO, "%s DISPLAY \042%s\042 TERM \042%s\042",
3488 WHO, dpy, vty);
3490 if (0 == strcmp(vty, "rxvt"))
3491 fprintf(stderr, "\033]0;%s%d\007", NAME, arg_devnum);
3493 if (0 == strcmp(vty, "screen"))
3494 fprintf(stderr, "\033k%s%d\033\\", NAME, arg_devnum);
3497 /* not USE_SYSLOG */
3498 #endif
3500 #ifndef USE_KERNING_H
3501 /* yet another variant of my old word wrapper code:
3502 word wrap san-serif size u for box x pixels wide and y lines high
3503 truncate s below matching pixel truncate point n,
3504 for string s using kern table c, stopping at k lines
3505 s string,
3506 k table,
3507 x pixel width of box
3508 p padding pixels (left+right) 1px margin is p = 2
3509 y number of lines in box
3510 u is pixel width of largest character 'W'
3511 f nz is fragment last word on last line, 0 is break only at ' '
3513 void
3514 truncate_text_kern_box ( unsigned char *s,
3515 unsigned char *k,
3516 int x, int p, int y, int u, int f)
3518 unsigned char *c, *d;
3519 int i, r, t, v, w, z;
3520 unsigned char b;
3522 w = i = 0;
3523 t = x - p;
3524 d = s;
3525 c = d;
3527 while ( 0 != *d ) {
3528 b = *d;
3529 c = d;
3530 z = 0;
3531 /* sum next word */
3532 while ( 0 != *c ) {
3533 b = *c;
3534 v = k[ b ]; /* per256 */
3535 v *= u; /* times width */
3536 r = 0xFF & v; /* remainder */
3537 v >>= 8; /* result width in pixels */
3538 if (r > 127) v++; /* round up 1 pixel? */
3539 v++; /* 1 pixel space */
3540 z += v; /* total pixels in word */
3541 w += v; /* total for line */
3543 /* fragment last word on last line? */
3544 if ( (0 != f) && (w >= t) && (i==(y-1)) ) {
3545 /* truncate after last letter that fit */
3546 *c = 0;
3547 return;
3549 c++;
3550 /* NOTE: trailing space gets counted */
3551 if (' ' == b) break;
3554 if (w >= t) { /* word too big for line? */
3555 i++;
3556 if (i >= y) { /* last line? */
3558 /* truncate after last word that fit */
3559 *d = 0;
3560 return;
3562 w = z; /* next line, end of word */
3564 if (0 == *c) break;
3565 d = c; /* next word */
3567 /* FIXME: non-wrappable word stops here and loses logical line count */
3568 *c = 0;
3570 #endif
3572 /* filebase performs various truncations of a given file name:
3573 caller must insure destination can hold the truncated result
3575 d is destination address, s is source address, o is option flags
3576 Options are mutually exclusive:
3577 F_PATH gives path
3578 F_PFILE gives path and truncated filename
3579 F_TFILE gives truncated filename
3580 F_FILE gives filename
3582 filebase delimiter is considered the last . at end of path/filename,
3583 because all files are considered to be *.ts or *.something.
3584 missing . in path+filename (sourced in s) will do nothing.
3585 missing / in path+filename (sourced in s) will do nothing.
3587 Example: /dtv/News.0.ts
3588 has 4 reusable parts:
3589 F_PATH path base /dtv/
3590 F_PFILE path file base /dtv/News.0
3591 F_TFILE file base name News.0
3592 F_FILE file name only News.0.ts
3594 Example: x.conf
3595 if no / in source, path is loaded with ./
3596 F_PATH path base ./
3597 F_PFILE path file base ./x
3598 F_TFILE file base name x
3599 F_FILE file name only x.conf
3602 /* static */
3603 void
3604 filebase ( char *d, char *s, int o )
3606 char *t;
3608 *d = 0;
3610 /* limit option flags to non-bogus */
3611 if (0 == o) return;
3612 if (0 > o) return;
3614 /* limit string handling to non-bogus */
3615 if (NULL == d) return;
3616 if (NULL == s) return;
3617 if (0 == *s) return;
3619 /* mutually exclusive options for how to truncate the source name */
3620 switch( o )
3622 /* path only, with / */
3623 case F_PATH:
3624 strcpy( d, "./" );
3625 t = strrchr( s, '/' );
3626 if (NULL != t) {
3627 strcpy( d, s );
3628 t = strrchr( d, '/');
3629 t++;
3630 *t = 0;
3632 break;
3634 /* path and file, without last .ext */
3635 case F_PFILE:
3636 strcpy( d, "./" );
3637 t = strrchr( s, '/' );
3638 if (NULL == t) d += 2;
3639 strcpy( d, s );
3640 t = strrchr( d, '.');
3641 if (NULL != t) *t = 0; /* truncate at last . */
3642 break;
3644 /* truncated file only, without last .ext */
3645 case F_TFILE:
3646 t = strrchr( s, '/' ); /* skip path */
3647 if (NULL != t) {
3648 t++;
3649 strcpy( d, t );
3651 /* truncate at last . */
3652 t = strrchr( d, '.');
3653 if (NULL != t) *t = 0;
3654 } else {
3655 /* no path, truncate at last . */
3656 strcpy( d, s );
3657 t = strrchr( d, '.');
3658 if (NULL != t) *t = 0;
3660 break;
3662 /* file only with .ext */
3663 case F_FILE:
3664 t = strrchr( s, '/' );
3665 if (NULL != t) {
3666 t++;
3667 strcpy( d, t );
3668 } else {
3669 strcpy( d, s );
3671 break;
3673 default:
3674 dvrlog( LOG_INFO, "filebase unknown parse type %u\n", o);
3675 break;
3679 /* Copy source file s to destination file d, a is 0 copy or 1 append:
3680 If source contains '*', source is used for glob list.
3681 If destination term with '/', [glob] source appends to dest.
3682 NOTE: It could *always* glob, however glob() uses a lot of cycles.
3683 Also, it tries to behave like cp -a and keep file date and mode bits.
3684 TODO? alloc buf fs.blocksize or page-size? 4k is OK
3686 /* static */
3688 filecopy ( char *d, char *s, int a )
3690 char buf[4096], ni[512], no[512], fb[256], *p;
3691 int i, j, k, o, r, w, z, e, g, f, rc;
3692 struct utimbuf ut;
3693 int upm;
3694 glob_t globt;
3695 struct stat64 fs;
3697 z = sizeof(buf);
3699 /* sanity checks */
3700 if (NULL == d) return -1;
3701 if (NULL == s) return -1;
3702 if (0 == *d) return -1;
3703 if (0 == *s) return -1;
3705 e = 0;
3706 f = 1;
3707 g = 0;
3709 /* glob needed for * in filename? */
3710 p = strchr( s, '*' );
3711 if (NULL == p) p = strchr( s, '?' );
3712 if (NULL != p) g = ~0;
3714 if (0 != g) {
3715 /* WARNING: no returns until globt is freed, only continues and breaks */
3716 glob( s, 0, NULL, &globt );
3718 f = globt.gl_pathc;
3719 advrlog( LOG_INFO, "%s glob %s files %d", WHO, s, f);
3722 /* any more glob files left to do, or is single file? */
3723 for (j = 0; j < f; j++) {
3725 /* set name in and name out */
3726 astrncpy( ni, s, sizeof(ni) );
3727 astrncpy( no, d, sizeof(no) );
3729 /* glob will change source name to glob index if found */
3730 if (0 != g) astrncpy( ni, globt.gl_pathv[ j ], sizeof(ni) );
3732 /* remove any trailing / */
3733 p = no + strlen( no );
3734 p--;
3735 if ('/' == *p) *p = 0;
3737 /* destination exists? */
3738 rc = stat64( no, &fs );
3739 if (0 == rc) {
3740 if (0 != (S_IFDIR & fs.st_mode)) {
3741 filebase( fb, ni, F_FILE );
3742 asnprintf( no, sizeof(no), "/%s", fb );
3746 /* keep source metadata: cp -a emulation sets mtime and mode after copy */
3747 rc = stat64( ni, &fs);
3749 /* skip bad file for whatever reason */
3750 if (rc < 0) {
3751 dvrlog( LOG_INFO, "%s can't stat %s", WHO, ni);
3752 continue;
3755 /* clear other time stamps */
3756 memset( &ut, 0, sizeof(ut) );
3757 ut.modtime = fs.st_mtime;
3758 ut.actime = fs.st_atime;
3760 upm = fs.st_mode;
3762 advrlog( LOG_INFO, "%s %s %s", (0 == a)?"copy":"append", ni, no );
3764 /* peter knaggs says read only helps luser mode */
3765 i = open( ni, FILE_ROMODE );
3766 if (i < 3) {
3767 dvrlog( LOG_INFO, "%s can't open %s err %d", WHO, ni, errno);
3768 e = -1;
3769 continue;
3772 /* copy, mode is octal */
3773 if (0 == a)
3774 o = open( no, FILE_WMODE, 0644 );
3776 /* append. seeks end of file instead of O_APPEND flag with NFS problems */
3777 if (0 != a)
3778 o = open( no, O_RDWR | O_CREAT, 0644 );
3780 if (o < 3) {
3781 dvrlog( LOG_INFO, "%s can't open %s", WHO, no);
3782 close ( i );
3783 e = -1;
3784 continue;
3787 /* append sets file position to end, should avoid NFS problems?
3788 no. append is broken. don't use it here.
3790 if (0 != a) lseek( o, 0, SEEK_END );
3792 r = 1;
3793 e = 0;
3794 k = 0;
3795 while (r > 0) {
3796 r = read( i, buf, z );
3797 if (r > 0) {
3798 w = write( o, buf, r );
3799 k += r;
3800 if (w < 1) {
3801 dvrlog( LOG_INFO, "%s can't write %s", WHO, no);
3802 e = -1;
3803 break;
3806 if (w != r) {
3807 dvrlog( LOG_INFO, "%s wrote %d of %d to %s",
3808 WHO, w, r, no);
3809 e = -1;
3810 break;
3813 if (r != z) {
3814 advrlog( LOG_INFO, "%s EOF %s", WHO, ni);
3815 break;
3819 if (r < 0) {
3820 dvrlog( LOG_INFO, "%s can't read %s", WHO, no);
3821 e = -1;
3822 break;
3825 close( i );
3826 fsync( o );
3827 close( o );
3829 /* Change file permissions after close to match original mode then set
3830 mtime after chmod. This prevents guide from reloading every time it
3831 restarts with recent EPGs, and keeps auto-EPG reload on 3hr meridian.
3833 NOTE: It is not expected that you will have to restart it very often.
3834 This is mostly to make it easier if you're trying to add new features.
3835 The wait for guide load can be very annoying after a few dozen times. :>
3837 chmod( no, upm );
3838 utime( no, &ut );
3841 /* don't forget to free what glob allocated */
3842 if (0 != g) globfree( &globt );
3844 return e;
3847 /* see if this instance d is already running in /proc/ */
3848 /* test luser calls to prevent starting another instance */
3849 /* build snav_html calls to find other running servers */
3850 /* returns 0 if not found, -1 if found */
3851 /* static */
3853 test_var_procpid( int d, int v )
3855 FILE *f;
3856 char n[64], s[1024];
3857 int p;
3859 snprintf(n, sizeof(n), "/var/run/%s/%s%d.pid", NAME, NAME, d);
3860 f = fopen(n, "r");
3862 if ((0 != v) && (NULL != f))
3863 fprintf( stderr, "fopen %p %s\n", f, n);
3865 /* var pid file found */
3866 if (NULL == f) return 0;
3867 fscanf(f, "%d", &p);
3868 fclose(f);
3869 snprintf(n, sizeof(n), "/proc/%d/cmdline", p);
3870 f = fopen(n, "r");
3871 if (0 != v) fprintf( stderr, "fopen %p %s\n", f, n);
3873 /* var pid file found but no matching cmdline */
3874 if (NULL == f) return 0;
3875 fread(s, sizeof(s), 1, f);
3876 fclose(f);
3877 s[sizeof(s)-1] = 0;
3878 if (0 != v) fprintf( stderr, "checking %s for %s\n", s, NAME);
3880 /* command line has name */
3881 if (NULL != strstr(s, NAME)) return -1;
3883 /* no match */
3884 return 0;
3888 /* version that doesn't use system() */
3889 /* static */
3890 void
3891 save_tmpfs ( char *caller )
3893 char s[512], d[512], r[512], o[1024];
3894 int rc;
3896 if (0 == arg_tmpfs) return;
3897 dvrlog( LOG_INFO, "%s %s", caller, WHO );
3899 snprintf( r, sizeof(r), "%s%s", out_path, ram_path);
3901 /* destination is /etc/atscap/ usually */
3902 snprintf( d, sizeof(d), "/etc/%s/", NAME);
3904 /* source is /dtv/ram/atscap[0-3].conf */
3905 snprintf( s, sizeof(s), "%s%s%d.conf", r, NAME, arg_devnum);
3906 rc = filecopy( d, s, 0 );
3908 /* source is /dtv/ram/atscap[0-3].tsid,[vsb/qam] */
3909 snprintf( s, sizeof(s), "%s%s%d.tsid.%s", r, NAME, arg_devnum,
3910 (0 == arg_cable)?"vsb":"qam" );
3911 rc = filecopy( d, s, 0 );
3913 /* if you stop multiple instances, only instance 0 writes these files */
3914 if (0 == arg_devnum) {
3915 snprintf( s, sizeof(s), "%s%s.spam", r, NAME);
3916 rc = filecopy( d, s, 0 );
3918 /* copy back possibly modified hosts.allow to /etc/hosts.allow */
3919 // no: luser may not have permission for that
3920 // snprintf( s, sizeof(s), "%shosts.allow", r );
3921 // strcpy( d, "/etc/" );
3922 // rc = filecopy( d, s, 0 );
3924 /* copy .epg and .vc files from ram/pg/ to /dtv/pg/, if any exist */
3925 snprintf( d, sizeof(d), "%spg/", out_path);
3926 snprintf( s, sizeof(s), "%spg/*.epg", r );
3927 rc = filecopy( d, s, 0 );
3928 snprintf( s, sizeof(s), "%spg/*.vc", r);
3929 rc = filecopy( d, s, 0 );
3933 /* append /dtv/ram/atscap#.log to /dtv/atscap#.log */
3934 snprintf( s, sizeof(s), "%s%s%d.log", r, NAME, arg_devnum );
3935 snprintf( d, sizeof(d), "%s%s%d.log", out_path, NAME, arg_devnum);
3937 /* FIXME: append has bugs that make it go crazy on control C early during init
3938 rc = filecopy(d, s, 1); // append flag set for atscap#.log file
3940 snprintf( o, sizeof(o), "cat %s >>%s", s, d);
3941 system( o );
3943 /* remove ram log to avoid appending duplicate data */
3944 snprintf( s, sizeof(s), "%s%s%d.log", r, NAME, arg_devnum);
3945 remove( s );
3948 /* Called after initial test config, scan_list[] populated to scan_idx
3949 This sets the ram path and copies config, epg and images to ram path.
3951 /* static */
3952 void
3953 load_tmpfs ( void )
3955 char s[512], d[512], r[512];
3956 int rc;
3958 if (0 == arg_tmpfs) return;
3959 advrlog( LOG_INFO, "%s", WHO );
3961 /* tell everything else where the ram directory is */
3962 snprintf( r, sizeof(r), "%s%s", out_path, ram_path );
3963 snprintf( cfg_path, sizeof(cfg_path), "%s", r );
3964 snprintf( log_path, sizeof(log_path), "%s", r );
3965 advrlog( LOG_INFO, "cfg %s log %s", cfg_path, log_path );
3967 /* all instances copy the config file for the instance */
3968 snprintf( s, sizeof(s), "/etc/%s/%s%d.conf", NAME, NAME, arg_devnum);
3969 snprintf( d, sizeof(d), "%s", r );
3970 rc = filecopy( d, s, 0 );
3973 /* should only be for -S: OR can rerun & find stations without restarting? */
3974 snprintf( s, sizeof(s), "/etc/%s/%s%d.tsid.%s",
3975 NAME, NAME, arg_devnum, (0 == arg_cable)?"vsb":"qam");
3976 rc = filecopy( d, s, 0 );
3978 /* if multiple instances, only first one copies these files */
3979 if (0 == arg_devnum) {
3980 snprintf( s, sizeof(s), "/etc/%s/%s.spam", NAME, NAME);
3981 rc = filecopy( d, s, 0 );
3983 /* copy /etc/hosts.allow */
3984 snprintf( s, sizeof(s), "/etc/hosts.allow");
3985 rc = filecopy( d, s, 0 );
3987 snprintf( d, sizeof(d), "%spg/", r);
3988 mkdir( d, 0777 );
3989 snprintf( s, sizeof(s), "%spg/*.epg", out_path);
3990 rc = filecopy( d, s, 0 );
3992 snprintf( s, sizeof(s), "%spg/*.vc", out_path);
3993 rc = filecopy( d, s, 0 );
3995 snprintf( d, sizeof(d), "%spg/img/", r);
3996 mkdir( d, 0777 );
3997 snprintf( s, sizeof(s), "%spg/img/*.png", out_path );
3998 rc = filecopy( d, s, 0 );
4000 snprintf( s, sizeof(s), "%spg/img/*.css", out_path );
4001 rc = filecopy( d, s, 0 );
4006 #ifdef USE_SIGNALS
4007 /* Dump backtrace to file and to stderr after clearing screen */
4008 /* Check ALL pointers before output in case it's toast. */
4009 /* NOTE: It should be pointed out that this is a Bad Idea. */
4010 #ifdef USE_GNU_BACKTRACE_SCRIPT
4011 #warning using GNU backtrace() script
4012 /* static */
4013 void
4014 dump_backtrace( char **bts, size_t z, void *addr )
4016 FILE *f;
4017 char a[128+3]; /* address, up to 64 bits, plus 3 for 0x and nul */
4018 char o[256]; /* dump output name */
4019 char t[256]; /* temp copy of backtrace string */
4020 char n[256]; /* app or lib name */
4021 char s[256]; /* source function after address + name extract */
4022 char *p, *r;
4023 size_t y;
4024 unsigned int i;
4025 char has_name, has_addr, has_func;
4027 if (0 == z) return;
4028 if (NULL == bts) return;
4030 has_name = has_addr = has_func = 0;
4032 /* Crash log script is /dtv/atscap#-pid.sh, calling addr2line for line numbers.
4033 NOTE: This only works if crash log matches current version compiled.
4035 snprintf( o, sizeof(o), "%s%s%d-%d.sh",
4036 out_path, NAME, arg_devnum, (int)pid_m);
4038 f = fopen( o, "wb");
4040 /* make .sh executable to call addr2line.sh to help debug */
4041 if (NULL != f) chmod( o, 0755 );
4043 if (NULL != f) {
4044 fprintf( f, "# %s\n", o);
4045 fprintf( f, "echo %s%d-%s SIG%s at address %p on %s.\n",
4046 NAME, arg_devnum, VERSION,
4047 sig_text[sig_val], addr, date_now );
4048 fprintf( f, "# addr2line helps find backtrace() line number.\n");
4049 fprintf( f, "# Comments below are strings from backtrace().\n");
4050 fprintf( f, "# Only %s lines are used for line numbers.\n", NAME);
4051 fprintf( f, "# Found %d stack frames.\n", z);
4052 fprintf( f, "#\n");
4054 /* start at 1, because 0 is always signal_handler */
4055 for (y = 1; y < z; y++) {
4056 i = 0;
4058 /* zero strings */
4059 memset( t, 0, sizeof(t) );
4060 memset( s, 0, sizeof(s) );
4061 memset( n, 0, sizeof(n) );
4062 memset( a, 0, sizeof(a) );
4064 /* copy backtrace string for editing, first time */
4065 astrncpy( t, bts[ y ], sizeof(t) );
4067 if ( '[' != *t) has_name = ~0;
4069 /* has name ? */
4070 if (0 != has_name) {
4071 astrncpy( n, bts[ y ], sizeof(n));
4072 if (NULL != strstr( n, NAME )) {
4073 strcpy( n, NAME );
4075 /* remove anything after name, work backwards from [ ( and blank */
4076 p = strchr( n, '[' ); /* remove addr */
4077 if (NULL != p) *p = 0;
4078 p = strchr( n, '(' ); /* remove func */
4079 if (NULL != p) *p = 0;
4080 p = strchr( n, ' ' ); /* remove blank */
4081 if (NULL != p) *p = 0;
4084 /* copy backtrace string for editing, again */
4085 astrncpy( t, bts[ y ], sizeof(t) );
4086 p = strchr( t, '[' );
4087 if (NULL != p) has_addr = ~0;
4089 /* has address? */
4090 if (0 != has_addr) {
4091 p = strchr( t, '[' ); /* point to addr */
4092 if (NULL != p) {
4093 p++;
4094 sscanf( p, "%x", &i ); /* get addr */
4096 /* convert all hexadecimal displays to upper case */
4097 snprintf( a, sizeof(a), "0x%08X", i);
4101 /* copy backtrace string for editing, again */
4102 astrncpy( t, bts[ y ], sizeof(t) );
4103 p = strchr( t, '(' );
4104 if (NULL != p) has_func = ~0;
4106 *s = 0;
4108 /* has function? */
4109 if (0 != has_func) {
4110 p = strchr( t, '(' );
4111 if (NULL != p) {
4112 p++;
4113 r = strchr( p, ')' );
4114 if (NULL != r) {
4115 *r = 0;
4116 astrncpy( s, p, sizeof(s) );
4121 /* atscap default install location */
4122 #define USE_PREFIX_BIN "/usr/local/bin/"
4124 /* if file open */
4125 if (NULL != f) {
4126 fprintf( f, "# %s\n", bts[ y ] );
4127 if (0 != *n) {
4128 if ( '/' != *n)
4129 fprintf( f, "addr2line -s -f -e %s%s %s # %s\n",
4130 USE_PREFIX_BIN, n, a, s );
4135 if (NULL != f) fprintf( f, "# EOF\n");
4138 if (NULL != f) fclose(f);
4140 /* let user know it's all bad */
4141 fprintf( stdout, "Running %s to get backtrace lines:\n\n", o);
4142 fflush( stdout );
4144 nanosleep( &console_read_sleep, NULL );
4145 console_reset();
4146 console_reset();
4147 system( o );
4148 free( bts );
4149 exit( 252 );
4150 fprintf( stdout, "\n");
4152 #endif
4154 /* See http://www.linuxjournal.com/article/6391 Listing 3. */
4155 /* NOTE: stack crashes will prevent most of this from working right,
4156 but simpler errors like NULL pointers may be more easily found.
4158 /* static */
4159 void
4160 /* signal_handler ( int sigval ) */ /* old style */
4161 signal_handler( int sigval, siginfo_t *info, void *secret )
4163 #ifdef USE_GNU_BACKTRACE
4164 void *bt[BTZ];
4165 size_t z;
4166 char t[256];
4167 int f;
4168 #ifdef USE_GNU_BACKTRACE_SCRIPT
4169 char **bts = (char **) NULL;
4170 #endif
4171 #endif
4172 ucontext_t *uc;
4173 void *addr;
4174 char n[256];
4176 // return;
4178 *n = 0;
4180 addr = NULL;
4182 /* EIP only works with GNU/Linux on intel/amd x86 32-bit arch */
4183 #if defined(REG_EIP)
4184 #warning using x86 arch uc_mcontext.gregs REG_EIP
4185 uc = (ucontext_t *)secret;
4186 addr = (void *) uc->uc_mcontext.gregs[ REG_EIP ];
4187 #endif
4188 /* RIP only works with GNU/Linux on intel/amd x86_64 64-bit arch */
4189 #if defined(REG_RIP)
4190 #warning using x86_64 arch uc_mcontext.gregs REG_RIP
4191 uc = (ucontext_t *)secret;
4192 addr = (void *) uc->uc_mcontext.gregs[ REG_RIP ];
4193 #endif
4195 sig_val = sigval; /* save signal for exit processing */
4197 switch (sig_val)
4200 /* ignore sigpipe, is likely to be web server aborted connection */
4201 case SIGPIPE:
4202 return;
4203 break;
4205 /* only sets flag for signal test called from console scan */
4206 case SIGWINCH:
4207 return;
4208 break;
4209 case SIGTERM:
4210 return;
4211 break;
4212 case SIGQUIT:
4213 return;
4214 break;
4215 case SIGINT:
4216 return;
4217 break;
4219 #ifdef USE_GNU_BACKTRACE
4220 case SIGUSR1:
4221 z = backtrace( bt, BTZ);
4222 if (0 == z) break;
4224 /* addr will be arch dependent on x86 and derivatives with newer GCC only */
4225 bt[2] = addr;
4227 snprintf( n, sizeof(n), "%s%s%d-%d.bt",
4228 out_path, NAME, arg_devnum, pid_m);
4229 snprintf( t, sizeof(t),
4230 "# %s%d-%s SIG%s at addr %p pid %d tid %d (%d) on %s\n\n",
4231 NAME, arg_devnum, VERSION,
4232 sig_text[sig_val], addr, pid_m,
4233 (int) pthread_self(),
4234 0x3FFF & (int) pthread_self(), /* ignore if NPTL */
4235 date_now );
4237 f = open( n, O_RDWR | O_CREAT, 0644 );
4239 if (f > 2) {
4240 write( f, t, strlen(t) );
4242 /* use btfd.sh to extract line numbers */
4243 backtrace_symbols_fd( bt, z, f );
4244 fsync( f );
4245 close( f );
4247 return;
4248 break;
4249 #endif
4251 /* fatal errors */
4252 case SIGSEGV:
4253 case SIGILL:
4254 case SIGFPE:
4255 case SIGBUS:
4256 case SIGIOT:
4258 #ifdef USE_GNU_BACKTRACE
4259 /* if backtrace or backtrace_symbols fail, give some exit indication */
4260 z = backtrace( bt, BTZ);
4261 if (0 == z) {
4262 fprintf( stderr, CLS BN SCV
4263 "Fatal error at addr %p, no backtrace.\n", addr );
4264 console_reset();
4265 console_reset();
4266 exit(254);
4269 /* This is typical place where stack trace goes wrong, for pthreads. All
4270 examples of how to use have bt[1] instead, but are not pthread apps.
4272 bt[2] = addr;
4274 #ifdef USE_GNU_BACKTRACE_SCRIPT
4275 /* Save backtrace to addr2line script. Is problematic if stack is crashed. */
4276 bts = backtrace_symbols( bt, z );
4277 if (NULL == bts) {
4278 fprintf( stderr, CLS BN SCV
4279 "Fatal error at addr %p, no backtrace.\n", addr );
4280 console_reset();
4281 console_reset();
4282 exit(255);
4285 /* If you're here, it must have crashed. LET ME KNOW or send me a patch. */
4286 /* It logs what can be logged about crash. addr2line will help, somewhat. */
4287 fprintf( stderr, CLS SCV BN );
4288 #if 0
4289 fprintf( stderr, "%s%d-%s SIG%s at addr %p on %s.\n",
4290 NAME, arg_devnum, VERSION,
4291 sig_text[sig_val], addr, date_now );
4292 #endif
4294 dump_backtrace( bts, z, addr ); /* does not return, exit252 */
4295 #else
4296 /* Save backtrace to file instead. Is problematic if stack is crashed. */
4297 /* snprintf( n, sizeof(n), "%s%s-%d.bt", out_path, NAME, pid_m ); */
4298 snprintf( n, sizeof(n), "%s%s%d-%d.bt",
4299 out_path, NAME, arg_devnum, getpid());
4300 snprintf( t, sizeof(t),
4301 "# %s%d-%s SIG%s pid %d tid %d (%d) on %s\n",
4302 NAME, arg_devnum, VERSION,
4303 sig_text[sig_val], pid_m,
4304 (int) pthread_self(),
4305 0x3FFF & (int) pthread_self(), /* ignore if NPTL */
4306 date_now );
4308 f = open( n, O_RDWR | O_CREAT | O_TRUNC, 0644 );
4309 if (f > 2) {
4310 write( f, t, strlen(t) );
4311 backtrace_symbols_fd( bt, z, f );
4312 fsync(f);
4313 close(f);
4316 /* USE_GNU_BACKTRACE_SCRIPT */
4317 #endif
4318 /* USE_GNU_BACKTRACE */
4320 /* NOTE: if stack is crashed, these won't help and may make it worse */
4321 fprintf( stderr, CLS SCV BN
4322 "%s%d-%s SIG%s at addr %p on %s.\n",
4323 NAME, arg_devnum, VERSION,
4324 sig_text[sig_val], addr, date_now );
4326 fprintf( stderr, "Run btfd.sh <%s for stack trace.\n\n", n);
4327 #endif
4328 dvrlog( LOG_INFO, "SIG%s at addr %p\n",
4329 sig_text[sig_val], addr );
4330 fflush( dvrlog_fd );
4331 fclose( dvrlog_fd );
4333 pid_i = 0;
4334 pid_o = 0;
4335 console_reset();
4336 console_reset();
4337 exit(253);
4338 break;
4340 /* the rest get logged */
4341 default:
4342 if (sig_val < 32) {
4343 dvrlog( LOG_INFO, "%s %s(%d) tid %d ignored",
4344 WHO, sig_text[ sig_val ], sigval, getpid() );
4345 } else {
4346 dvrlog( LOG_INFO, "%s %d", WHO, sig_val);
4348 break;
4351 fprintf( stderr, CLS SCV BN
4352 "%s%d-%s unhandled SIG%s at addr %p\n\n",
4353 NAME, arg_devnum, VERSION, sig_text[sig_val], addr);
4354 console_reset();
4355 console_reset();
4356 exit( sig_val );
4359 /* global signal init. got rid of -ansi -pedantic porting errors */
4360 /* static */
4361 void
4362 signal_init ( void )
4364 struct sigaction act;
4366 /* act.sa_handler = signal_handler; */
4367 /* old style doesn't give EIP */
4369 /* sigemptyset is required */
4370 sigemptyset( &act.sa_mask);
4371 act.sa_flags = SA_RESTART | SA_SIGINFO;
4372 act.sa_sigaction = signal_handler;
4374 /* signals quit and term can be used to terminate it gracefully */
4375 sigaction( SIGQUIT, &act, NULL );
4376 sigaction( SIGTERM, &act, NULL );
4378 /* control c will terminate it gracefully if not capturing */
4379 sigaction( SIGINT, &act, NULL );
4381 /* act.sa_handler = SIG_IGN; */
4383 /* console resize, using something else */
4384 sigaction( SIGWINCH, &act, NULL );
4386 /* HTTP remote closed connection is usual cause of this.
4387 Example: refresh the page before it's done loading.
4389 sigaction( SIGPIPE, &act, NULL );
4391 /* these are all fatal */
4392 sigaction( SIGSEGV, &act, NULL );
4393 sigaction( SIGILL, &act, NULL );
4394 sigaction( SIGFPE, &act, NULL );
4395 sigaction( SIGBUS, &act, NULL );
4396 sigaction( SIGIOT, &act, NULL );
4398 /* SIGUSR1 will dump a backtrace log of current threads */
4399 sigaction( SIGUSR1, &act, NULL );
4403 /* SIGINT enable 1, disable 0 */
4404 /* static */
4405 void
4406 sigint_set ( int s )
4408 struct sigaction act;
4410 /* sigemptyset is required */
4411 sigemptyset( &act.sa_mask );
4412 act.sa_flags = SA_RESTART;
4413 act.sa_handler = SIG_IGN;
4415 /* if (0 != s) act.sa_handler = signal_handler; */ /* old style */
4416 if (0 != s) act.sa_sigaction = signal_handler;
4418 sigaction( SIGINT, &act, NULL );
4421 /* USE_SIGNALS */
4422 #endif
4425 #ifndef aprintf
4426 /* insert delays each time it is called for debugging ui */
4427 /* static */
4428 void
4429 aprintf ( FILE *f, const char *fmt, ... )
4431 char t[1024];
4432 struct timespec print_sleep = {0,100000000};
4434 va_list ap;
4436 if ((arg_detach != 0) || (arg_quiet != 0)) return;
4437 memset(t, 0, sizeof(t));
4438 va_start(ap, fmt);
4440 /* variable arg macro start, pass fmt and ap, variable arg macro end */
4441 vsnprintf( t, sizeof(t)-1, fmt, ap );
4442 va_end(ap);
4443 fprintf( f, "%s", t);
4444 nanosleep( &print_sleep, NULL );
4446 #endif
4449 /* functions to track malloc, calloc and free usage */
4451 /* unused compiler error avoid */
4452 /* static */
4453 void *
4454 imalloc ( size_t z, char *id )
4456 void *p;
4457 int i;
4459 for (i = 0; i < ALLOC_LIMIT; i++)
4460 if (NULL == alloc_list[i].p)
4461 break;
4463 if (ALLOC_LIMIT == i) {
4464 fprintf(stderr, CLS "malloc %s %d failed: alloc limit\n", id, z);
4465 exit(1);
4466 return NULL;
4469 p = malloc( z );
4470 if (NULL == p) {
4471 fprintf(stderr, CLS "malloc %s %d failed\n", id, z);
4472 exit(1);
4475 alloc_list[ i ].p = p;
4476 alloc_list[ i ].z = z;
4477 astrncpy( alloc_list[ i ].n, id, ALLOC_CHARS);
4478 return p;
4481 /* static */
4482 void *
4483 icalloc ( size_t n, size_t z, char *id )
4485 void *p;
4486 int i;
4488 for (i = 0; i < ALLOC_LIMIT; i++)
4489 if (NULL == alloc_list[i].p)
4490 break;
4492 if (ALLOC_LIMIT == i) {
4493 fprintf(stderr, CLS "calloc %s %d failed: alloc limit\n", id, z);
4494 exit(1);
4495 return NULL;
4498 p = calloc( n, z );
4499 if (NULL == p) {
4500 fprintf(stderr, CLS "calloc %s %d failed\n", id, z);
4501 exit(1);
4503 alloc_list[ i ].p = p;
4504 alloc_list[ i ].z = z * n;
4505 astrncpy( alloc_list[ i ].n, id, ALLOC_CHARS );
4506 return p;
4509 /* incremental realloc, does not shrink only expands */
4510 /* this behaviour is different from normal realloc which sets z size */
4511 /* static */
4512 void *
4513 irealloc( void *p, int z, char *id )
4515 int i;
4517 if (NULL == p) return NULL;
4518 if (0 == z) return p;
4520 for (i = 0; i < ALLOC_LIMIT; i++)
4521 if (p == alloc_list[i].p)
4522 break;
4524 if (ALLOC_LIMIT == i) {
4525 fprintf( stderr, CLS
4526 "irealloc %p %d not found for %s\n", p, z, id);
4527 exit(1);
4530 astrncpy( alloc_list[i].n, id, ALLOC_CHARS );
4532 alloc_list[i].z += z;
4533 alloc_list[i].p = realloc( p, z );
4535 if (NULL == alloc_list[i].p) {
4536 fprintf( stderr, CLS
4537 "irealloc failed for %s %p %d\n", id, p, z);
4538 exit(1);
4541 return alloc_list[i].p;
4544 /* static */
4545 void
4546 ifree ( void *p, char *n )
4548 int i;
4550 if (NULL == p) {
4551 fprintf( stderr, CLS "ifree can not free NULL pointer %s\n",n);
4552 exit(1);
4553 return;
4556 for ( i = 0; i < ALLOC_LIMIT; i++ )
4557 if ( p == alloc_list[ i ].p )
4558 break;
4560 if (ALLOC_LIMIT == i) {
4561 fprintf(stderr, CLS "ifree can not find pointer %s %p\n", n, p);
4562 exit(1);
4563 return;
4566 /* found a match? */
4567 free( p );
4568 memset( alloc_list[i].n, 0, ALLOC_CHARS );
4569 alloc_list[i].p = NULL;
4570 alloc_list[i].z = 0;
4573 static
4574 void
4575 init_allocs( void )
4577 int i;
4579 for (i = 0; i < ALLOC_LIMIT; i++) {
4580 memset( alloc_list[i].n, 0, ALLOC_CHARS );
4581 alloc_list[i].p = NULL;
4582 alloc_list[i].z = 0;
4585 /* static spam list clear */
4586 memset( spam_list, 0, sizeof(spam_list));
4589 /* long long to ascii comma delimited to make big numbers easier to read.
4590 Assumes dest has enough bytes for result (19 digits + 6 commas + nul).
4591 Largest number is around +/- 9,000,000,000,000,000,000 (quintillion).
4593 PROOF:
4595 value sl dest dl dl computation
4597 1 1 1 1 (0 * 4) + 1 = 1
4598 10 2 10 2 (0 * 4) + 2 = 2
4599 100 3 100 3 (1 * 4) - 1 = 3
4600 1000 4 1,000 5 (1 * 4) + 1 = 5
4601 10000 5 10,000 6 (1 * 4) + 2 = 6
4602 100000 6 100,000 7 (2 * 4) - 1 = 7
4603 1000000 7 1,000,000 9 (2 * 4) + 1 = 9
4604 10000000 8 10,000,000 10 (2 * 4) + 2 = 10
4605 100000000 9 100,000,000 11 (3 * 4) - 1 = 11
4606 1000000000 10 1,000,000,000 13 (3 * 4) + 1 = 13
4608 NOTE: atoll() breaks after 2^30 on 32-bit with some older compilers.
4609 Auto-verification using atoll on char c[] doesn't always work.
4610 This function also depends on snprintf getting it right.
4612 10000000000 11 10,000,000,000 14 (3 * 4) + 2 = 14
4614 #define COMMA_MAX 32
4616 /* static */
4617 void
4618 lltoasc ( char *dest, long long value )
4620 char *s, *b, *d; /* source and bottom boundary for d */
4621 int i, k, sl, dl; /* loops, source len, destination len */
4622 char c[COMMA_MAX]; /* value in ascii */
4624 d = dest;
4626 /* Don't want the sign confusing the comma calculation so fix it here. */
4627 if ( value < 0LL ) {
4628 // value ^= -1LL; /* one */
4629 // value++; /* way */
4630 value = 0LL - value; /* or another */
4631 /* if still negative, it is largest negative and will not complement. */
4632 if (value >= 0LL) *d++ = '-';
4635 snprintf( c, COMMA_MAX, "%lld", value );
4637 if (value > 999LL) { /* commas needed ? */
4638 b = d; /* save stop pointer */
4639 sl = strlen( c ); /* src len */
4640 dl = ((sl / 3) * 4) + (sl % 3); /* dest len */
4641 if ( 0 == ( sl % 3)) dl--; /* fixup for last , */
4643 d[dl] = 0; /* NUL term */
4644 dl--; /* and back up */
4646 if ( dl < COMMA_MAX ) { /* will added commas fit? */
4648 /* copy bytes from s to d in reverse order, adding , to d every 3 bytes */
4650 k = sl - 1; /* loop limit */
4651 s = c + k; /* point: 1's place end of c */
4652 d += dl; /* point: computed end of d */
4654 for ( i = sl; i > 0; ) {
4655 if ( (d < b) || (s < c) ) break; /* boundary */
4656 i--;
4657 *d-- = *s--; /* copy & decrement */
4658 if ( (d < b) || (s < c) ) break; /* boundary */
4660 if (0 == ((sl-i) % 3)) *d-- = ',';
4661 if (d < b) break; /* boundary check */
4663 } else {
4664 astrncpy( d, c, COMMA_MAX ); /* commas won't fit */
4666 } else {
4667 astrncpy( d, c, COMMA_MAX ); /* no commas needed */
4669 return;
4672 /* ascii binary to unsigned */
4673 /* NOTE: this is where weekday bits get reversed */
4674 /* static */
4675 unsigned int
4676 abtou ( char *p )
4678 unsigned int i = 0;
4679 unsigned int b = 0;
4681 if (p == NULL) return 0;
4682 b = strlen(p);
4684 /* return if nothing to do */
4685 if (b == 0) return 0;
4687 /* nl removal and return if nothing to do */
4688 if (p[b-1] == 10) p[b-1] = 0;
4689 b = strlen(p);
4690 if (b == 0) return 0;
4692 while (*p) i |= ( *p++ & 1 ) << --b;
4693 return i;
4696 /* unsigned to ascii binary,
4697 p is output, b is number of bits, i is value
4699 /* static */
4700 void
4701 utoab ( char *p, unsigned int b, unsigned int i )
4703 unsigned int j = b;
4705 while (j > 0)
4706 *p++ = 48 + ( (i & 1<<--j) ? 1:0 );
4708 *p = 0; /* null term string */
4712 #ifdef USE_WWW
4713 /* FIXME: should not include port. adjust size back to 16 after port remove */
4714 /* static */
4715 void
4716 uintodot( char *d, unsigned int s )
4718 if (NULL == d) return;
4719 snprintf( d, 22, "%d.%d.%d.%d:%d",
4720 0xFF & (s>>24),
4721 0xFF & (s>>16),
4722 0xFF & (s>>8),
4723 0xFF & s,
4724 0xFFFF & arg_wport );
4727 /* returns 0 if address was good */
4728 /* static */
4730 dotouint ( unsigned int *d, char *p )
4732 unsigned int ok;
4733 char *p0, *p1, *p2, *p3;
4734 int a0, a1, a2, a3;
4735 char s[256];
4737 memset( s, 0, sizeof(s) );
4738 astrncpy( s, p, sizeof(s) );
4739 ok = ~0;
4740 if (NULL == s) return -1;
4741 if (NULL == d) return -1;
4743 p0 = p1 = p2 = p3 = "";
4744 p0 = s;
4745 p1 = strchr( p0, '.' );
4746 if (NULL != p1) {
4747 *p1 = 0;
4748 p1++;
4749 p2 = strchr( p1, '.');
4750 if (NULL != p2) {
4751 *p2 = 0;
4752 p2++;
4753 p3 = strchr( p2, '.');
4754 if (NULL != p3) {
4755 *p3 = 0;
4756 p3++;
4757 ok = 0;
4762 if (0 != ok) return ok;
4764 *d = 0;
4765 a0 = atoi( p0 );
4766 if ((a0 < 0) || (a0 > 255)) return -1;
4768 a1 = atoi( p1 );
4769 if ((a1 < 0) || (a1 > 255)) return -1;
4771 a2 = atoi( p2 );
4772 if ((a2 < 0) || (a2 > 255)) return -1;
4774 a3 = atoi( p3 );
4775 if ((a3 < 0) || (a3 > 255)) return -1;
4777 *d |= a0 << 24;
4778 *d |= a1 << 16;
4779 *d |= a2 << 8;
4780 *d |= a3;
4781 return 0;
4783 #endif
4785 #if 0
4786 /* event truncate isn't doing this right for some reason, but filesystem is
4787 storing the characters illegibly even if it does match a compare.
4791 /* Similar to isalnum, but considers chars over 128 to be alpha. */
4792 /* Like isalnum, return non-zer if falls into tested class, zero if not. */
4793 /* This may generate filenames that are difficult to handle from console. */
4794 /* static */
4796 isalnum2 ( int c )
4798 if ( ((c >= '1') && (c <= '9'))
4799 || ((c >= 'A') && (c <= 'Z'))
4800 || ((c >= 'a') && (c <= 'z'))
4801 || (c >= 128) ) return ~0;
4802 return 0;
4804 #endif
4806 /* static */
4807 unsigned char
4808 ahex2u( char *p )
4810 unsigned char c, c1, c2;
4811 c = 0;
4812 c1 = p[0];
4813 c2 = p[1];
4815 if ( (c1 >= 'a') && (c1 <= 'f') ) c1 = 10 + c1 - 'a';
4816 if ( (c1 >= 'A') && (c1 <= 'F') ) c1 = 10 + c1 - 'A';
4817 if ( (c1 >= '0') && (c1 <= '9') ) c1 -= '0';
4819 if ( (c2 >= 'a') && (c2 <= 'f') ) c2 = 10 + c2 - 'a';
4820 if ( (c2 >= 'A') && (c2 <= 'F') ) c2 = 10 + c2 - 'A';
4821 if ( (c2 >= '0') && (c2 <= '9') ) c2 -= '0';
4823 c1 &= 0x0F;
4824 c2 &= 0x0F;
4826 c = (c1 << 4) | c2;
4827 return c;
4830 /* build a list of cgi form pointers up to n pointers from string s */
4831 /* s is modified */
4832 /* static */
4833 void
4834 build_form_args( char **p, int n, char *s )
4836 int i;
4837 char *r, *t;
4839 for (i = 0; i < n; i++) p[i] = NULL;
4841 r = s;
4843 /* point to everything following ='s and remove trailing & */
4844 for (i = 0; i < n; i++) {
4845 t = strchr( r, '=' );
4846 if (NULL == t) break;
4847 t++;
4848 p[i] = t;
4849 t = strchr( p[i], '&' );
4850 if (NULL == t) break;
4851 *t = 0;
4852 t++;
4853 r = t;
4857 /* build a list of pointers up to n pointers from string s using delim c */
4858 /* s is modified with NUL terms replacing each delim c */
4859 /* static */
4861 build_args( char **p, int n, char *s, char c, char *caller )
4863 int i, j;
4864 char *r, *t;
4866 advrlog( LOG_INFO, "%s %s %s", caller, WHO, s);
4868 for (i = 0; i < n; i++) p[i] = NULL;
4870 r = s;
4872 j = 0;
4873 for (i = 0; i < n; i++) {
4874 if (0 == *r) break;
4875 p[i] = r;
4876 j++;
4877 t = strchr( r, c );
4878 /* end of list? */
4879 if (NULL == t) break;
4880 *t = 0;
4881 t++;
4882 r = t;
4884 return j;
4889 This is called by show clock to set the current time in the program.
4890 It can be called from other places, for slightly more exact time.
4892 /* static */
4893 void
4894 get_time ( void )
4896 tnow = time( NULL );
4897 localtime_r( &tnow, &tloc );
4898 utsnow = (int)tnow;
4899 utsday = (tloc.tm_hour * SECHR) + (tloc.tm_min * 60) + (tloc.tm_sec);
4900 utsmid = utsnow - utsday;
4902 /* NOTE: make sure date_now is at least 24 bytes */
4903 asctime_r( &tloc, date_now );
4905 /* truncate before year */
4906 date_now[19] = 0;
4907 utswday = tloc.tm_wday;
4910 /* test epgs calls this to figure out when to reload the next EPG.
4911 Return is staggered 5m by device number starting at 5m after meridian.
4914 get_next_meridian( void )
4916 int f, l, m;
4917 f = l = m = 0;
4918 get_time();
4919 /* failsafe value is next 3hr meridian */
4920 m = utsnow + 10799;
4922 /* convert to earliest 3hr meridian */
4923 m /= SEC3HR;
4924 m *= SEC3HR;
4926 /* it should already be ahead, correct it if not */
4927 if (m <= utsnow) m += SEC3HR;
4929 /* stagger by 5m so all 4 instances don't hammer the cpu at once */
4930 m += 300 + (arg_devnum * 300);
4932 /* stagger cable instances another 5m so they fetch a good epg */
4933 if (0 != arg_cable) m += 300;
4934 return m;
4937 /* tt is int representation of time_t */
4938 /* d should be 32 bytes, trun should be less than 31 for nul term */
4939 /* static */
4940 void
4941 text_time ( char *d, int tt, int trun ) {
4942 struct tm taloc;
4943 time_t tany;
4945 tany = (time_t)tt;
4946 localtime_r( &tany, &taloc);
4947 asctime_r( &taloc, d );
4949 if (trun < strlen(d)) d[trun] = 0;
4952 /* find the most accurate clock and set clock_method global */
4953 /* static */
4954 void
4955 test_clock_res ( void )
4957 struct timespec res;
4958 int i, j;
4959 long long r, s;
4961 j = -1;
4962 r = 1000000000LL; /* worst case scenario is 1 clock per second */
4964 for (i = 0; i < RTC_MAX; i++) {
4966 /* is clock res return valid? */
4967 if (0 == clock_getres( clock_ids[i], &res)) {
4968 s = res.tv_sec;
4969 s <<= 32;
4971 s <<= 16;
4972 s <<= 16;
4974 s |= res.tv_nsec;
4976 /* is clock res non-zero? */
4977 if (0LL != s)
4978 if (s < r) {
4979 j = i;
4980 r = s;
4985 if (-1 == j) {
4986 dvrlog( LOG_INFO, "%s has no clock method? check librt", NAME);
4987 fprintf(stderr, CLS "%s has no clock method? check librt\n", NAME);
4988 exit(1);
4991 /* use last lowest value in l for the syslog */
4992 clock_method = clock_ids[j];
4993 clock_idx = j;
4994 clock_res = r;
4997 /* want this done after banner */
4998 /* static */
4999 void
5000 log_clock_res ( void )
5002 long long r;
5003 char *s, *t;
5005 r = clock_res;
5006 t = clock_text[clock_idx];
5007 /* scale */
5008 s = "ns";
5009 if (r > 1000) { s = "us"; r /= 1000; };
5010 if (r > 1000) { s = "ms"; r /= 1000; };
5011 if (r > 1000) { s = "s"; r /= 1000; }; /* very slow clock */
5012 advrlog( LOG_INFO, "using CLOCK_%s %lld %s", t, r, s );
5015 /* RRT version reset */
5016 /* static */
5017 void
5018 init_rrt( struct payload_s *p )
5020 p->vn = 0xFF;
5023 /* clears entries in mg* */
5024 /* static */
5025 void
5026 reset_mg( void )
5028 memset( mg, 0xFF, sizeof( mg ) );
5029 memset( mgp, 0xFF, sizeof( mgp ) );
5030 memset( mgs, 0xFF, sizeof( mgs ) );
5033 /* clear all entries in mg* and resets version number */
5034 /* static */
5035 void
5036 init_mg ( void )
5038 memset( mgt_text, 0, sizeof( mgt_text ));
5040 /* TESTME: dont wipe MGT:
5041 version change overwrites rest with new info from mgt[].payload
5043 /* This breaks EPG. Not sure of the interaction. */
5044 /* memset( mgt, 0, sizeof( mgt ) ); */
5046 /* overwrite the table lookups with invalid PID flag */
5047 mgt_vn = 0xFF;
5048 mg_idx = 0;
5049 utsmgt = 0;
5050 init_rrt( &rrt );
5051 reset_mg();
5054 /* static */
5055 void
5056 init_pat_pmt( struct payload_s *a, struct payload_s *m )
5058 int i;
5059 memset( pa, 0, sizeof( pa ));
5060 memset( m, 0, VCZ * sizeof( struct payload_s )); /* multi-vc */
5061 memset( a, 0, sizeof( struct payload_s ));
5062 a->vn = a->lv = 0xFF;
5064 for (i=0; i<VCZ; i++) pa[i].vn = 0xFF;
5065 pat_ncs = 0;
5069 /* clear all virtual channel data and set version numbers to invalid */
5070 /* static */
5071 void
5072 init_vct ( struct payload_s *v, char *caller )
5074 int i;
5076 advrlog( LOG_INFO, "%s <- %s", WHO, caller );
5077 memset( vc, 0, sizeof( vc ));
5078 memset( tvct_text, 0, sizeof( tvct_text )); /* reset VCT guide text */
5079 memset( v, 0, sizeof( vct )); /* reset data buffers */
5080 v->vn = v->lv = 0xFF; /* set versions to invalid */
5082 for (i=0; i<VCZ; i++) vc[i].pmtvn = 0xFF;
5084 vc_ncs = vct_ncs = 0; /* reset virtual channel counts */
5086 init_pat_pmt( &pat, pmt );
5087 /* pkt.tvct = pkt.pat = pkt.pmt = 0; / * reset packet counts */
5091 /* clear epg and related */
5092 /* static */
5093 void
5094 init_pgm ( void )
5096 int i;
5098 memset( &epg_locks, 0xFF, sizeof( epg_locks )); /* initially locked */
5099 memset( epg, 0, sizeof( epg ) ); /* wipe program guide */
5100 memset( pg, 0xFF, sizeof( pg ) ); /* wipe program guide index */
5102 pgm.idx = 0; /* reset program guide counts */
5103 pgm.idt = 0;
5104 pgm.ett = 0;
5106 for (i=0;i<PGZ;i++) { /* reset numbers and version numbers */
5107 epg[i].eitn = epg[i].eitvn = epg[i].ettn = epg[i].ettvn = 0xFF;
5109 eit_idx = 0;
5110 ett_idx = 0;
5113 /* channel change resets pid totals for that channel */
5114 /* static */
5115 void
5116 init_pids ( void )
5118 memset( pids, 0, sizeof(pids) );
5122 /* ts capture loop calls this at start */
5123 /* static */
5124 void
5125 init_stats ( struct sig_s *s )
5127 /* clear packet stats */
5128 memset( &pkt, 0, sizeof( struct pkt_s ));
5129 memset( &s->pkt, 0, sizeof( struct pkt_s ));
5131 /* clear continuity and error lookups, and table lookups */
5132 memset( last_cc, 0xFF, sizeof( last_cc ));
5133 memset( cce_pids, 0, sizeof( cce_pids ));
5134 memset( psi_pids, 0, sizeof( psi_pids ));
5137 /* clear atsc and related */
5138 /* static */
5139 void
5140 reset_atsc ( void )
5142 int i;
5144 memset( &stt, 0, sizeof( stt ));
5145 memset( &rrt, 0, sizeof( rrt ));
5146 memset( &ctt, 0, sizeof( ctt ));
5148 memset( eit_vn, 0xFF, sizeof( eit_vn ) );
5150 ctt.payok = 1;
5151 stt.payok = 1;
5152 rrt.payok = 1; /* no payload yet */
5154 for (i=0; i < EIZ; i++) {
5155 eit[i].vn = 0xFF; /* set version to invalid */
5156 ett[i].vn = 0xFF;
5157 eit[i].payok = 1; /* no payload yet */
5158 ett[i].payok = 1;
5163 /* init ATSC structures */
5164 /* static */
5165 void
5166 init_atsc ( void )
5168 size_t z, n;
5169 z = sizeof( struct payload_s );
5170 n = EIZ;
5172 #ifdef USE_DYNAMIC
5173 #warning using dynamic EIT & ETT allocation
5174 eit = icalloc( n, z, "EIT 00-7F" ); /* 4k * 128 Event Info Table */
5175 ett = icalloc( n, z, "ETT 00-7F" ); /* 4k * 128 Event Text Table */
5176 #endif
5177 /* clear the locks and data areas then set table version numbers invalid */
5178 reset_atsc();
5181 /* Free the ATSC tables allocated above */
5182 /* static */
5183 void
5184 free_atsc ( void )
5186 #ifdef USE_DYNAMIC
5187 #warning using dynamic EIT & ETT free
5188 if (NULL != eit)
5189 ifree( eit, "eit" );/* 4k * 128 Event Info Table */
5190 eit = NULL;
5192 if (NULL != ett)
5193 ifree( ett, "ett" );/* 4k * 128 Event Text Table */
5194 ett = NULL;
5195 #endif
5198 /* Frames and sequences are only allocated on single program capture. */
5199 /* USE_DYNAMIC doesn't matter here. It's big. It should always be dynamic. */
5200 /* static */
5201 void
5202 init_frames ( void )
5204 #ifdef USE_DYNAMIC
5205 #warning using dynamic frames[] & sequences[]
5206 if (0 == arg_frames) return; /* not -m */
5207 if (CAP_INFO == cap_now) return; /* not info cap */
5208 if (cap_pn < 1) return; /* not program cap */
5210 frames = icalloc( FRAME_MAX, sizeof( short ), "frames[]");
5211 if (NULL != frames) {
5212 advrlog( LOG_INFO, "icalloc %d frames",
5213 FRAME_MAX * sizeof(short) );
5214 } else {
5215 /* don't process frames if can't allocate space */
5216 advrlog( LOG_INFO, "%s failed frames calloc", WHO );
5217 cap_frames = 0;
5220 sequences = icalloc( SEQUENCE_MAX, sizeof( int ), "sequences[]");
5221 if (NULL != sequences) {
5222 advrlog( LOG_INFO, "icalloc %d sequences",
5223 SEQUENCE_MAX * sizeof(int) );
5224 } else {
5225 /* don't process sequences if can't allocate space */
5226 dvrlog( LOG_INFO, "%s failed sequences calloc", WHO );
5227 cap_frames = 0;
5229 #else
5230 #warning using static frames[] & sequences[]
5231 #endif
5233 frame_idx = 0;
5234 sequence_idx = 0;
5237 /* Frames and sequences are only needed during capture. */
5238 /* static */
5239 void
5240 free_frames ( void )
5242 #ifdef USE_DYNAMIC
5243 if (NULL != sequences) ifree( sequences, "sequences" );
5244 sequences = NULL;
5245 if (NULL != frames) ifree( frames, "frames" );
5246 frames = NULL;
5247 #endif
5250 #if 0
5251 /* This one presents a chicken-and-egg scenario with load config/epg
5252 because you want to run this before load config, but there is
5253 no channel list to provide a list of VC & EPG files to remove.
5254 Replacing make eclean with C code isn't so easy?
5255 Sure, could do system("rm *.vc *.epg") in out_path directory.
5256 but not much finesse in that now is there?
5258 /* static */
5259 void
5260 init_files ( void )
5263 #endif
5266 clear all the arrays
5267 set .vn in all the arrays to 255 to prevent version 0 = version 0 no update
5269 /* static */
5270 void
5271 init_arrays ( void )
5273 unsigned char *p;
5275 /* erase pid list for this cap */
5276 memset( cap_pids, 0, sizeof(cap_pids) );
5277 /* load the null packet */
5278 memset( nul_buf, 0xFF, sizeof(nul_buf) );
5279 p = nul_buf;
5280 *p++ = 0x47;
5281 *p++ = 0x1F;
5282 *p++ = 0xFF;
5283 *p++ = 0x10;
5285 /* TESTME: here or init_mg? seems to break EPG if init_mg */
5286 memset( &mgt, 0, sizeof( mgt ) );
5287 init_mg(); /* see above */
5289 /* TESTME: cable should keep using stored guide */
5290 if (0 == arg_cable) init_pgm();
5292 init_vct( &vct, WHO );
5294 init_pids();
5296 /* reset frame counts */
5297 memset( pict, 0, sizeof(pict));
5302 /************************************************************* ffmpeg CRC32 */
5303 /* static */
5304 unsigned int
5305 calc_crc32 ( unsigned char *data, unsigned int len)
5307 unsigned int i;
5308 unsigned int crc = 0xffffffff;
5310 if (len > 4096) return ~0;
5312 for (i=0; i<len; i++)
5313 crc = (crc << 8) ^ crc_lut[ 0xFF & ((crc >> 24) ^ *data++) ];
5314 return crc;
5316 /************************************************************* ffmpeg CRC32 */
5318 #ifdef USE_MCAST
5320 /************************************************************** UDP MULTICAST
5321 open socket, 0 if ok, not zero if not ok
5323 /* static */
5325 udp_open_socket ( struct udp_s *u )
5327 int yes, no, ok;
5328 struct ip_mreq mca;
5330 yes = 1;
5331 no = 0;
5333 /* open socket */
5334 u->sock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
5335 if (u->sock < 0) {
5336 dvrlog( LOG_INFO, "socket open error");
5337 return 1;
5340 /* set socket reusable (initiator of group has to do this) */
5341 ok = setsockopt( u->sock,
5342 SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes) );
5343 if (0 != ok) {
5344 dvrlog( LOG_INFO, "socket reuse error");
5345 return 2;
5348 /* set socket multicast loopback off (disables local listen) */
5349 #ifdef MCAST_NOLOOP
5350 sok = setsockopt( u->sock,
5351 IPPROTO_IP, IP_MULTICAST_LOOP, &no, sizeof(no) );
5352 if (0 != ok) {
5353 dvrlog( LOG_INFO, "socket loop disable error");
5354 return 2;
5356 #endif
5358 /* set socket to multicast address */
5359 memset( &mca, 0, sizeof( mca ) );
5360 mca.imr_multiaddr.s_addr = inet_addr( arg_mcaddr );
5362 /* FIXME: multicast should bind to any, but change here if needed */
5363 mca.imr_interface.s_addr = htonl( INADDR_ANY );
5365 ok = setsockopt( u->sock,
5366 IPPROTO_IP, IP_ADD_MEMBERSHIP, &mca, sizeof(mca) );
5367 if (0 != ok) {
5368 dvrlog( LOG_INFO, "socket mcast group error");
5369 return 3;
5372 /* bind socket */
5373 memset( &u->addr, 0, sizeof(struct sockaddr));
5374 u->addr.sin_family = AF_INET;
5375 u->addr.sin_addr.s_addr = inet_addr( arg_mcaddr );
5376 u->addr.sin_port = htons( arg_mcport );
5378 u->bind = bind( u->sock, (struct sockaddr *)&u->addr,
5379 sizeof(struct sockaddr));
5381 if (0 != u->bind) {
5382 dvrlog( LOG_INFO, "socket bind error");
5383 return 4;
5386 /* everything ok once it gets here */
5387 dvrlog( LOG_INFO, "UDP MCAST %s port %u",
5388 inet_ntoa( u->addr.sin_addr ),
5389 ntohs( u->addr.sin_port ) );
5390 return 0;
5393 /************************************************************** UDP MULTICAST
5394 write a transport packet to a UDP multicast socket
5396 /* static */
5398 udp_write_socket ( struct udp_s *u, unsigned char *p )
5400 int sendok = 0;
5402 /* send what */
5403 sendok = sendto( u->sock, p, 188,
5404 0, &u->addr,
5405 sizeof(struct sockaddr) );
5406 /* to who */
5407 return sendok;
5410 /* static */
5411 void
5412 udp_close_socket ( struct udp_s *u )
5414 if (u->sock > 2) close( u->sock );
5416 #endif
5419 /*********************************************************** HUFFMAN DECODE
5420 It's a binary tree lookup for common letter combinations, used for
5421 EIT Event Title and ETT Event Description Text compression modes.
5422 *d destination, dlen destination max length,
5423 *s source, slen source length
5424 comp is 1 for title decode, 2 for description decode
5425 This was fun to write.
5427 /* static */
5428 void
5429 huffman_decode ( char *d, unsigned char *s,
5430 int dlen, int slen, int comp)
5432 unsigned char p, c, o, b, *co;
5433 unsigned int i, j, k, to, zo, z, *bo;
5435 /* sanity checks */
5436 if (NULL == d) return;
5437 *d = 0; /* NUL term the destination */
5439 if (NULL == s) return; /* no nulls */
5440 if (comp < 1) return;
5441 if (comp > 2) return; /* no bogus comp mode */
5442 if (dlen < 1) return;
5443 if (slen < 1) return; /* no bogus len */
5445 p = c = o = 0; /* first char assumed to be NUL or term char */
5446 bo = huffman1bo; /* byte offset of char p tree root */
5447 co = huffman1co; /* char p order-1 tree */
5448 z = TITLE_COZ; /* default 1 is title/name decode */
5450 /* otherwise 2 is extended text decode */
5451 if (comp == 2) {
5452 bo = huffman2bo;
5453 co = huffman2co;
5454 z = DESCR_COZ;
5457 /* should be pointing to correct tables now */
5458 for (i=0; i < (slen<<3); i++)
5460 if (p > 127) { /* sanity check, don't stray outside table */
5461 /* change to advrlog if it gets annoying */
5462 advrlog( LOG_INFO, "%s BAD Tree Offset %02X", WHO, p);
5463 return;
5465 /* get tree offset for char p from order-1 tree byte offset table */
5466 to = bo[ p ];
5468 /* direction in tree from bit in compressed string */
5469 /* compressed bit sets to left(0) or right(1) for next branch or leaf */
5470 b = s[ i >> 3 ] & (1 << (~i & 7));
5472 /* force comparison to binary */
5473 if (b != 0) b = 1;
5475 /* minimum of two linked-list lookups for shortest first order entry
5476 such as common following letters, but most will be multiple lookups
5479 zo = to + (o << 1) + b;
5480 if (zo > z) { /* sanity check, don't stray outside table */
5481 /* change to advrlog if it gets annoying */
5482 advrlog( LOG_INFO, "%s BAD Branch Offset %04X", WHO, zo);
5483 return;
5485 /* first entry has tree lookup to first order left/right choice tree */
5486 o = co[ zo ]; /* tree root offset is anchor for branches */
5488 /* top bit set means it's a leaf char. this implies that */
5489 /* branches will loop until it is a leaf char */
5490 if (0 != (0x80 & o) )
5492 c = 0x7F & o;
5494 if ( c == 27 )
5496 /* handle Escape to 8 bit mode */
5497 i++; /* point to msb of uncompressed byte */
5498 j = i & 7;
5499 k = 8 - j;
5500 /* get current byte */
5501 c = s[ i >> 3 ];
5502 /* shift needed? */
5503 if (0 != j)
5505 c <<= j;
5506 b = s[ (i >> 3) + 1];
5507 b >>= k;
5508 c |= b;
5510 i += 7; /* skip past lsb of uncompressed byte */
5513 p = c; /* c leaf becomes new index for order-1 tree root offset */
5514 o = 0; /* clear offset to order-1 tree */
5515 *d = c;
5516 dlen--;
5518 /* out of space gets nul term and exits */
5519 if (dlen < 1) {
5520 *d = 0;
5521 break;
5524 d++; /* else move to next char */
5526 /* nul term exits */
5527 if ( c == 0 ) break;
5532 #ifdef USE_WWW
5533 /* "Sun, 01 Jan 1995 00:00:00 GMT" */
5534 /* gcc circa 1994 and later only */
5535 /* static */
5536 void
5537 http_text_time( char *d, time_t *t )
5539 struct tm ut;
5540 gmtime_r( t, &ut );
5541 strftime( d, 30, "%a, %d %b %Y %T GMT", &ut);
5544 #endif
5546 #define USE_SNR_RMS
5547 #ifdef USE_SNR_RMS
5548 /* Root Mean Square the SNR samples stored in the signal history */
5549 /* static */
5550 void
5551 calc_snr_rms( struct sig_s *s )
5553 double rms; /* unsigned int for b avoids nan */
5554 unsigned int i, a,c; /* i and c counts; a square; b sum */
5555 unsigned long long b; /* total on good sig might be > 8g */
5557 a = b = c = 0;
5558 if (SIG_PNG_W < 1) return; /* sanity limit, div/0 avoid */
5559 for (i = 0; i < SIG_PNG_W; i++) /* SNR 8.8 samples */
5561 if (s->rmp[i] < 1) continue; /* don't count zero readings */
5562 a = s->rmp[i];
5563 a *= a; /* square */
5564 b += a; /* sum */
5565 c++; /* count */
5567 if (c > 0) b /= c; /* avoid div by 0 error */
5568 rms = sqrt( (double) b ); /* root of mean squares */
5569 s->snr_rms = (int) rms; /* float doesn't matter? */
5570 advrlog( LOG_INFO, "SNR RMS %5.2f dB", rms / 256.0 );
5572 #endif
5574 /* average the FE locked strength samples stored in the signal history */
5575 /* static */
5576 void
5577 calc_sig_avg( struct sig_s *s )
5579 int i, a, c;
5581 a = c = 0;
5583 for (i = 0; i < SIG_PNG_W; i++)
5585 if (s->smp[i] < 1) continue; /* don't count zero readings */
5586 a += s->smp[i];
5587 c++;
5590 if (c > 0) a /= c; /* avoid div by 0 error */
5591 s->str_avg = a;
5594 /* return index of tsid list with matching tsid and program number, or -1 */
5595 /* static */
5596 short
5597 find_tsidx ( unsigned short id, unsigned short pn, char *caller )
5599 short i;
5600 struct tsid_s *t;
5602 for (i = 0; i < tsidx; i++) {
5603 t = tsids + i;
5604 if ((id == t->tsid) && (pn == t->pn)) {
5605 return i;
5608 return -1;
5611 /* return index of tsid list with matching ptc and program number, or -1 */
5612 /* static */
5613 short
5614 find_tsid_ptc( int pc, unsigned short pn )
5616 short i;
5617 struct tsid_s *t;
5619 for (i = 0; i < tsidx; i++) {
5620 t = tsids + i;
5621 if ((pc == t->ptc) && (pn == t->pn)) {
5622 advrlog( LOG_INFO, "%s TSID %04X.%d found %d %s %s",
5623 WHO, t->tsid, pn, i, t->call, t->sid);
5624 return i;
5627 advrlog( LOG_INFO, "TSID PTC %03d.%d not found", pc, pn);
5629 return -1;
5633 /* stop capture, set reason and set display to timers */
5634 /* static */
5635 void
5636 stop_capture ( char *caller, int fail )
5638 struct sig_s *s;
5640 if (CAP_NONE == cap_now) return; /* nothing to do */
5641 s = &ptc[ cap_chan ].sig;
5642 advrlog( LOG_INFO, "%s stops cap, fail %d", caller, fail);
5643 if (CAP_TIME == cap_now) timer_rec = -1;
5644 cap_now = CAP_NONE;
5645 cap_fail = fail;
5646 s->cap_fail = fail;
5647 #if 0
5648 /* wait for output fifo flush and thread termination */
5649 while (0 != pid_o) nanosleep( &console_read_sleep, NULL);
5650 #endif
5652 display_type = D_TIMERS;
5653 refresh.timers = 1;
5656 /* static */
5657 void
5658 read_cutvol_stats( void )
5660 int ok;
5661 char e[256] = "";
5662 cut_free = -1LL;
5664 ok = statfs( cut_path, &cut_fsp );
5665 if (0 != ok) {
5666 strerror_r(errno, e, sizeof(e));
5667 advrlog( LOG_INFO, "%s statfs returns %d %s", WHO, errno, e);
5668 } else {
5669 cut_free = (cut_fsp.f_bsize * cut_fsp.f_bavail) >> 30;
5670 cut_size = (cut_fsp.f_bsize * cut_fsp.f_blocks) >> 30;
5674 /* static */
5675 void
5676 read_capvol_stats( void )
5678 int ok;
5679 char e[256] = "";
5680 vol_free = -1LL;
5682 ok = statfs( out_path, &vol_fsp );
5683 if (0 != ok) {
5684 strerror_r(errno, e, sizeof(e));
5685 advrlog( LOG_INFO, "%s statfs returns %d %s", WHO, errno, e);
5686 } else {
5687 /* These are binary gigabytes not decimal ones with same for cutvol above. */
5688 vol_free = (vol_fsp.f_bsize * vol_fsp.f_bavail) >> 30;
5689 vol_size = (vol_fsp.f_bsize * vol_fsp.f_blocks) >> 30;
5691 /* If timer or manual capture in progress and volume space drops below 1GB,
5692 there is about 7 minutes left before volume is full. Stop the capture.
5693 EPG capture should be allowed to finish. It will need at least 3MB free.
5695 if (CAP_NONE == cap_now) return;
5696 if (CAP_INFO == cap_now) return;
5698 /* Capture is in progress, will need to abort the capture if below 1G left. */
5699 if (vol_free < 1) {
5700 /* User needs to know why the capture failed. Drive full is critical. */
5701 dvrlog( LOG_INFO, "capvol %s is FULL!\n", out_path);
5702 stop_capture( WHO, FAIL_NOSPC );
5707 /* Top level navigation for html with servers, channels, and auto-epg toggles.
5709 Builds up to and including <body>, caller will need to do </body>.
5711 instance0 ... instance3 [cap]
5712 SID0 SID1 SID2 ... SIDn
5714 Instance will be atscap0...n-1, where n is WWW_DVBS
5715 SID will be the hrefs for all guides in config, ABC FOX etc
5716 If auto EPG, SIDn font+color is large+cyan. Current channel is white.
5718 flags bitfields:
5719 bit 0 nz list [cap] [cut] dir href after instance names
5720 bit 1 nz list epg + signal strength imgs and legend
5721 bit 2 nz epg html uses rel hrefs, 0 is for index html
5723 d destinatation address
5724 z maximum number of bytes for destination
5725 title is string to use for title text
5726 caller is string of who called this to put in html comment
5727 r is refresh value to use if non-zero
5729 /* static */
5730 void
5731 build_head_html3 ( char *d, int z, int flags, char *title, int r, char *caller)
5733 int i, iz, fz, ok;
5734 struct sig_s *s;
5735 char *c; /* font color */
5736 c = "";
5737 fz = 3;
5738 ok = 0;
5740 /* force 1s delayed update of free space */
5741 utsvsu = 0;
5743 /* FIXME: should be HTML3.2 header */
5744 asnprintf(d, z, "%s", DOCTYPE_HTML3);
5746 asnprintf( d, z, "<!-- %s-%s is %s by %s -->\n",
5747 NAME, VERSION, COPYRIGHT, EMAIL );
5749 asnprintf( d, z, "<!-- %s-%s is released under the %s -->\n",
5750 NAME, VERSION, LICENSE );
5751 if (NULL != caller)
5752 asnprintf(d, z, "<!-- %s %s -->\n", caller, &date_now[4]);
5755 asnprintf(d, z, "<html>\n");
5758 asnprintf(d, z, "<head>\n");
5760 asnprintf(d, z, "<meta http-equiv=\042%s\042 content=\042%s\042>\n",
5761 "Content-Type", "text/html;charset=ISO-8859-1");
5763 if (0 < r) asnprintf(d, z,
5764 "<meta http-equiv=\042%s\042 content=\042%d\042>\n",
5765 "Refresh", r);
5767 if (NULL != title) asnprintf(d, z, "<title>%s</title>", title);
5768 asnprintf(d, z, "</head>\n");
5770 /* <body ...> settings */
5771 asnprintf(d, z, "<body "
5772 "id=\042%s\042 "
5773 "bgcolor=\042%s\042 "
5774 "text=\042%s\042 "
5775 "link=\042%s\042 "
5776 "vlink=\042%s\042 "
5777 "alink=\042%s\042"
5778 ">\n",
5779 "top", WWW_BG, WWW_FG, WWW_HLINK, WWW_VLINK, WWW_ALINK );
5780 asnprintf(d, z, "<center>\n");
5782 /* top level server navigation. list available atscap instances
5784 conditional set to 0 is for multi-host single-servers
5785 4 cards in 4 machines
5787 conditional set to 1 (default) for single-host multi-server
5788 4 cards in 1 machine
5790 for (i = 0; i < www_dvrs; i++) {
5792 /* active server is normal font and green (assumed true for multi-sever) */
5793 fz = 3;
5794 c = "#00FF00";
5796 /* undefine this for one device per server */
5797 #define USE_MULTI_CARD
5798 #ifdef USE_MULTI_CARD
5799 /* this is for multiple cards in one machine, the usual default */
5800 ok = test_var_procpid(i, 0);
5801 /* inactive server gets smaller font and grey */
5802 if (0 == ok) {
5803 fz = 2;
5804 c = "#C0C0C0";
5807 asnprintf( d, z, "<a href=\042http://%s:%u/%s%d.html\042>",
5808 arg_whost, (0xFFFC & arg_wport) + i, NAME, i );
5809 #else
5810 /* this is for multiple machines with one card each */
5811 /* edit your /etc/hosts to give ip addresses to dtv0 dtv1 dtv2 dtv3 */
5813 /* nothing written to test for the other machine being up. Would need to
5814 fetch the server header from other machine and check for atscap */
5815 asnprintf( d, z, "<a href=\042http://%s%d:%u/%s%d.html\042>",
5816 arg_whost, i, arg_wport, NAME, 0 );
5817 #endif
5818 if (i == arg_devnum) {
5819 fz = 4; c = "#FFFFFF";
5822 asnprintf(d, z, "<font size=\042%d\042 color=\042%s\042>",
5823 fz, c);
5824 asnprintf(d, z, "%s%d </font>", NAME, i);
5825 asnprintf(d, z, "</a>\n");
5828 /* volume available indication, cap directory is always available */
5829 if (1 & flags) {
5831 asnprintf( d, z, "<a href=\042%s\042>%s </a>\n",
5832 "/", "[cap]");
5834 /* show the href if cut directory is available */
5835 if (-1LL != cut_free)
5836 asnprintf( d, z, "<a href=\042%s\042>%s </a>\n",
5837 "/cut/", "[cut]");
5840 asnprintf( d, z, "<a href=\042/pg/grid%d.html\042>%s </a>\n",
5841 arg_devnum, "[grid]");
5843 asnprintf(d, z, "</center>\n");
5844 asnprintf(d, z, "<hr />\n");
5846 /* show list of available channels,
5847 blue and larger for auto-epg set, white for current channel
5848 signal scan graph listed underneath network id
5850 user hits refresh to get same result as current channel href.?? deleteme
5852 if (0 == (2 & flags)) {
5854 asnprintf(d, z, "<center>\n");
5855 for (i = 0; i < scan_idx; i++) {
5856 s = &ptc[scan_list[i]].sig;
5858 /* use rel href */
5859 if (0 != (4 & flags)) {
5860 asnprintf(d,z, "<a href=\042%02d.html\042>", s->chan);
5861 } else {
5862 asnprintf(d,z, "<a href=\042/pg/%02d.html\042>", s->chan);
5865 fz = 3;
5866 c = "#00FF00";
5868 if (0 != s->pgto) {
5869 fz = 4;
5870 c = "#00BFFF";
5873 /* in EPG page, current EPG channel is white */
5874 if (s->chan == pgm3.chan) {
5875 fz = 4;
5876 c = "#FFFFFF";
5879 asnprintf(d, z, "<font size=\042%d\042 color=\042%s\042>",
5880 fz, c);
5881 asnprintf(d, z, "%s </font>", s->sid);
5882 asnprintf(d, z, "</a>\n");
5884 asnprintf(d, z, "</center>\n");
5887 /* This flag shows signal strength image graphs for each channel
5888 a table is needed to get everything to line up properly. It may
5889 not look very good for more than 15 channels on 1024 wide browser.
5891 Options are displayed at bottom along with legend shown or hidden.
5893 if (0 != (2 & flags)) {
5895 /* table is 100% of browser width */
5896 asnprintf(d, z, TBL_FMT, TBL_BRD, 0, 0, "center", "100%");
5898 /************************************************ table header and SIDn */
5899 asnprintf(d, z, " <tr>\n");
5901 for (i = 0; i < scan_idx; i++) {
5902 s = &ptc[scan_list[i]].sig;
5904 asnprintf(d, z, " " TH_FMT, "top", "5%");
5905 asnprintf(d, z, "<a href=\042pg/%02d.html\042>", s->chan);
5906 fz = 3;
5907 c = "#00FF00";
5908 if (0 != s->pgto) {
5909 fz = 4;
5910 c = "#00BFFF";
5913 /* in server page, current scan channel is white */
5914 if (s->chan == cap_chan) {
5915 fz = 4;
5916 c = "#FFFFFF";
5919 asnprintf(d, z, "<font size=\042%d\042 color=\042%s\042>",
5920 fz, c);
5922 /* table entries do not get following spaces. centering handles this */
5923 asnprintf(d, z, "%s</font>", s->sid);
5924 asnprintf(d, z, "</a>");
5925 asnprintf(d, z, "</th>\n");
5929 asnprintf( d, z, " </tr>\n");
5932 #ifdef USE_DVB_EXPERIMENTAL
5933 /********************************************** tuner frequency drift */
5934 if (0 != scan_khz) {
5935 asnprintf(d, z, " <tr>\n");
5936 for (i = 0; i < scan_idx; i++) {
5937 char n[32];
5938 s = &ptc[scan_list[i]].sig;
5939 snprintf(n, sizeof(n), "%dkc", s->fohz/1000);
5940 asnprintf(d, z, " " TD_FMT, "top", "5%");
5941 asnprintf(d, z, "<center>%s</center>", n);
5942 asnprintf(d, z, "</td>\n");
5945 asnprintf(d, z, " </tr>\n");
5947 #endif
5949 /***************************************************** signal SNR RMS */
5950 if (0 != scan_snr) {
5951 asnprintf(d, z, " <tr>\n");
5952 for (i = 0; i < scan_idx; i++) {
5953 char n[32];
5954 s = &ptc[scan_list[i]].sig;
5955 calc_snr_rms( s );
5956 snprintf(n, sizeof(n), "%ddB", 0xFF & (s->snr_rms>>8));
5957 asnprintf(d, z, " " TD_FMT, "top", "5%");
5958 asnprintf(d, z, "<center>%s</center>", n);
5959 asnprintf(d, z, "</td>\n");
5961 asnprintf(d, z, " </tr>\n");
5964 /********************************************** signal strength% average */
5965 asnprintf(d, z, " <tr>\n");
5966 for (i = 0; i < scan_idx; i++) {
5967 char n[32];
5968 s = &ptc[scan_list[i]].sig;
5969 calc_sig_avg( s );
5970 snprintf(n, sizeof(n), "%d%%", s->str_avg);
5971 asnprintf(d, z, " " TD_FMT, "top", "5%");
5972 asnprintf(d, z, "<center>%s</center>", n);
5973 asnprintf(d, z, "</td>\n");
5976 asnprintf(d, z, " </tr>\n");
5978 /********************************************** signal strength chart */
5980 /* image size only used in html3 code, html4 + css is fluid */
5981 iz = 24;
5982 if (0 != scan_png) iz = 48;
5984 asnprintf(d, z, " <tr>\n");
5985 for (i = 0; i < scan_idx; i++) {
5986 char n[16];
5987 int v = -1;
5989 s = &ptc[scan_list[i]].sig;
5990 iz = 50;
5991 if (0 == scan_png) iz = 25;
5993 snprintf( n, sizeof(n), "pg/%d-%02d.png",
5994 arg_devnum, s->chan);
5996 asnprintf(d, z, " " TH_FMT, "middle", "5%");
5997 // if ((0 == scan_sig) || (s->chan != cap_chan))
5999 v = s->chan;
6001 asnprintf(d, z, "<a href=\042/%s%d.html?v=%d\042>",
6002 NAME, arg_devnum, v );
6003 asnprintf(d, z, IMG_FMT, s->call, n, iz, iz, 0, 0, 1);
6004 asnprintf(d, z, "</a>");
6005 asnprintf(d, z, "</th>\n");
6007 asnprintf(d, z, " </tr>\n");
6009 /************************************************* auto EPG toggle */
6010 asnprintf(d, z, " <tr>\n");
6011 for (i = 0; i < scan_idx; i++) {
6012 char *n, *a;
6013 s = &ptc[scan_list[i]].sig;
6015 /* alt text */
6016 a = "OFF";
6018 /* default image is epg off */
6019 n = "pg/img/epgoff24.png";
6020 if (0 != scan_png) n = "pg/img/epgoff.png";
6022 /* image changes if auto epg */
6023 if (0 != s->pgto) {
6024 a = "ON";
6025 n = "pg/img/epgon24.png";
6026 if (0 != scan_png) n = "pg/img/epgon.png";
6027 /* image size and name. actual size images less jagged than browser resize */
6030 /* live cap shows blue, sig scan shows antenna, else green, variable size */
6031 if (cap_chan == s->chan) {
6032 /* is cap? */
6033 if (CAP_NONE != cap_now) {
6034 a = "LIVE";
6035 n = "pg/img/epglive24.png";
6036 if (0 != scan_png) n = "pg/img/epglive.png";
6037 } else {
6038 /* no cap, is sig scan? */
6039 if (0 != scan_sig) {
6040 a = "SCAN";
6041 n = "pg/img/scanon24.png";
6042 if (0 != scan_png) n = "pg/img/scanon.png";
6047 asnprintf( d, z, " " TH_FMT, "top", "5%");
6048 asnprintf( d, z, "<a href=\042/%s%d.html?e=%02d\042>",
6049 NAME, arg_devnum, s->chan );
6051 asnprintf( d, z, IMG_FMT, a, n, iz, iz, 0, 0, 0 );
6052 asnprintf( d, z, "</a>");
6053 asnprintf( d, z, "</th>\n");
6055 asnprintf(d, z, " </tr>\n");
6056 /************************************************ end of auto epg toggles */
6058 asnprintf(d, z, "</table>\n");
6059 /************************************************* end of server page table */
6061 /* display options */
6062 asnprintf(d, z, "<hr />\n");
6064 /*********************************************************** legend toggle */
6065 asnprintf(d, z, "<center>\n");
6066 asnprintf(d, z, "<a href=\042%s%d.html?l=1\042>",
6067 NAME, arg_devnum);
6068 asnprintf(d, z, "%s", (0 == web_legend) ? "Help":"HELP");
6069 asnprintf(d, z, "</a> " NBSP " \n");
6071 asnprintf(d, z, "<a href=\042%s%d.html?f=%d\042>",
6072 NAME, arg_devnum, 1 );
6073 asnprintf(d, z, "%s", "CSS");
6074 asnprintf(d, z, "</a> \n");
6076 /******************************************************* stats toggle */
6078 #ifdef USE_DVB_EXPERIMENTAL
6079 asnprintf(d, z, "<a href=\042%s%d.html?q=2\042>",
6080 NAME, arg_devnum );
6081 asnprintf(d, z, "%s", (0 == scan_khz)?"freq":"FREQ");
6082 asnprintf(d, z, "</a> \n");
6083 #endif
6085 asnprintf(d, z, "<a href=\042%s%d.html?q=1\042>",
6086 NAME, arg_devnum );
6087 asnprintf(d, z, "%s", (0 == scan_snr)?"snr":"SNR");
6088 asnprintf(d, z, "</a> \n");
6090 asnprintf(d, z, "<a href=\042%s%d.html?k=%d\042>",
6091 NAME, arg_devnum, (0 == web_stats) ? 1:0 );
6092 asnprintf(d, z, "%s", (0 == web_stats)?"stats":"STATS");
6093 asnprintf(d, z, "</a> \n");
6095 #if 0
6096 /* signal chart large or small */
6097 asnprintf(d, z, "<a href=\042%s%d.html?p=%d\042>",
6098 NAME, arg_devnum, (0 == scan_png) ? 1:0 );
6099 asnprintf(d, z, "%s", (0 == scan_png)?"png":"PNG");
6100 asnprintf(d, z, "</a> \n");
6101 #endif
6103 /* signal scan start or stop */
6104 if (CAP_NONE == cap_now) {
6105 asnprintf(d, z, "<a href=\042%s%d.html?w=%d\042>",
6106 NAME, arg_devnum, (0 == scan_one) ? 0:1 );
6107 asnprintf(d, z, "%s", (0 != scan_one)?"scan":"SCAN");
6108 asnprintf(d, z, "</a> \n");
6111 /************************************************************** legend */
6112 if (0 != web_legend) {
6113 asnprintf(d, z, "<br>\n");
6114 asnprintf(d, z, "EPG: \n");
6115 asnprintf(d, z, IMG_FMT, "AUTO", "pg/img/epgon24.png",
6116 24, 24, 0, 0, 0 );
6117 asnprintf(d, z, "Auto\n");
6119 asnprintf(d, z, IMG_FMT, "OFF", "pg/img/epgoff24.png",
6120 24, 24, 0, 0, 0 );
6121 asnprintf(d, z, "Off\n");
6123 asnprintf( d, z, " STATUS: ");
6124 asnprintf(d, z, IMG_FMT, "CAP", "pg/img/epglive24.png",
6125 24, 24, 0, 0, 0 );
6126 asnprintf(d, z, "Cap\n");
6128 asnprintf(d, z, IMG_FMT, "SCAN", "pg/img/scanon24.png",
6129 24, 24, 0, 0, 0 );
6130 asnprintf(d, z, "Scan\n");
6132 asnprintf(d, z, "</center>\n");
6135 asnprintf(d, z, "<hr />\n");
6138 /************************************************** end of build_head_html3 */
6141 /************************************************ start of build head html4 */
6143 flags bitfields:
6144 bit 0 list [cap] dir href after device names
6145 bit 1 list epg + signal strength imgs and legend
6146 bit 2 nz epg html uses rel hrefs, 0 is for index html
6147 bit 3 nz use XHTML 1.0 header
6148 d destinatation address
6149 z maximum number of bytes for destination
6150 title is string to use for title text
6151 caller is string of who called this to put in html comment
6152 r is refresh value to use if non-zero
6156 /* server navigation header */
6157 /* Top level server navigation.
6159 Some people have multiple machines with 1 device each,
6160 others have all devices in one machine. Still others
6161 have some odd mixture that can't be predicted here.
6162 The default is one capture server with multiple devices.
6164 conditional compile set to 0 is for multi-host single-servers
6165 4 cards in 4 machines
6167 conditional compile set to 1 (default) for single-host multi-server
6168 4 cards in 1 machine
6170 checks /var/run/atscap/atscap[0-3].pid against process list
6171 for active servers. .pid only generated if -w option.
6174 /* snav and cnav are not using CSS */
6175 /* static */
6176 void
6177 build_snav_html4 ( char *d, int z, int flags )
6179 int i, ok;
6180 char *a; /* font and color class for href */
6182 a = "";
6184 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
6186 asnprintf(d, z, "<div class=\042%s\042>\n", "snbl f5");
6187 asnprintf(d, z, "<center>\n");
6189 // asnprintf( d, z, "<ul class=\042%s\042>\n", "snbul");
6191 /* one device */
6192 if (1 == www_dvrs) {
6193 asnprintf(d, z, "<a class=\042%s\042 "
6194 "href=\042/%s%d.html\042>",
6195 a, NAME, arg_devnum );
6196 asnprintf(d, z, "%s%d ", NAME, arg_devnum);
6197 asnprintf(d, z, "</a>\n");
6198 } else {
6199 for (i = 0; i < www_dvrs; i++) {
6201 #ifdef USE_MULTI_CARD
6202 ok = test_var_procpid(i, 0);
6203 #endif
6205 // asnprintf( d, z, "<li class=\042%s\042>", "snbl1");
6206 a = "snba0"; /* smaller font and grey */
6207 if (0 != ok)
6208 a = "snba1"; /* smaller font and green */
6210 if (i == arg_devnum) /* current device? */
6211 a = "snba2"; /* bigger font, and white */
6213 /* undefine this for one device per server */
6214 #ifdef USE_MULTI_CARD
6215 /* this is for multiple cards in one machine, the usual default */
6216 asnprintf(d, z, "<a class=\042%s\042 "
6217 "href=\042http://%s:%u/%s%d.html\042>",
6218 a, arg_whost,
6219 (0xFFFC & arg_wport) + i, NAME, i );
6220 #else
6221 /* this is for multiple machines with one card each */
6222 /* edit your /etc/hosts to give ip addresses to dtv0 dtv1 dtv2 dtv3 */
6223 asnprintf(d, z, "<a class=\042%s\042 "
6224 "href=\042http://%s%d:%u/%s%d.html\042>",
6225 a, arg_whost, i, arg_wport, NAME, 0 );
6226 #endif
6227 asnprintf(d, z, "%s%d ", NAME, i);
6228 asnprintf(d, z, "</a>\n");
6229 // asnprintf(d, z, "</li>\n");
6232 // asnprintf(d, z, "</ul>\n");
6234 // asnprintf( d, z, "<ul class=\042%s\042>\n", "snbul");
6235 /* volume available indication, cap directory is always available */
6236 if (1 & flags) {
6238 // asnprintf(d, z, "<li class=\042%s\042>", "snbl2");
6239 asnprintf(d, z, "<a class=\042%s\042 "
6240 "href=\042%s\042>%s </a>\n",
6241 "snba3", "/", " [cap] ");
6242 // asnprintf( d, z, "</li>\n");
6244 /* show the href if cut directory is available */
6245 if (-1LL != cut_free) {
6246 // asnprintf(d, z, "<li class=\042%s\042>", "snbl2");
6247 asnprintf(d, z, "<a class=\042%s\042 "
6248 "href=\042%s\042>%s </a>\n",
6249 "snba4", "/cut/", " [cut] ");
6250 // asnprintf( d, z, "</li>\n");
6254 asnprintf(d, z, "<a class=\042%s\042 "
6255 "href=\042/pg/grid%d.html\042>%s </a>\n",
6256 "snba1", arg_devnum, " [grid] ");
6258 // asnprintf(d, z, "</ul>\n");
6259 asnprintf(d, z, "</center>\n");
6260 asnprintf(d, z, "<hr width=\042%s\042 />\n", "100%");
6261 asnprintf(d, z, "</div>\n");
6262 asnprintf(d, z, "<br class=\042%s\042 />\n", "br_epgdiv");
6266 /* CSS EPG channel navigation header. dump cap log and http index use it too.
6267 CSS server config page uses a different format.
6269 /* static */
6270 void
6271 build_cnav_html4 ( char *d, int z, int flags )
6273 int i;
6274 char *a, *c, *i0, *i1;
6275 struct sig_s *s;
6277 a = c = i0 = i1 = "";
6279 /* TODO: optimization: move the branch up to save a call */
6280 if (0 != (2 & flags)) return;
6282 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
6283 asnprintf(d, z, "<div class=\042%s\042>\n", "f5");
6284 for (i = 0; i < scan_idx; i++) {
6286 // Peter Knaggs reports SEGV here where scan_list is corrupted with vc data?
6287 // seems to be OK now?
6288 s = &ptc[scan_list[i]].sig;
6290 /* channel is not selected for EPG nor current */
6291 a = "cnbl1";
6292 c = "cnba0";
6294 /* has an epg */
6295 if (s->pgx > 0) c = "cnba6";
6297 /* channel has auto EPG */
6298 if (s->pgto != 0) c = "cnba2";
6300 /* has timesource */
6301 if (s->ptc == cap_zc) c = "cnba5";
6303 /* is current scan channel */
6304 if (s->chan == pgm.chan) c = "cnba3";
6306 /* live capture channel */
6307 if ((CAP_NONE != cap_now) && (s->chan == cap_chan))
6308 c = "cnba4";
6310 /* channel is same as the epg at which we're looking.
6311 for non EPG, it provides a visual anchor back to last EPG viewed
6313 if (s->chan == pgm3.chan) c = "cnba1";
6315 /* if not at least 500 wide, channel might be dead or in test pattern */
6316 if (500 > s->hres) c = "cnba7";
6318 asnprintf(d, z, "<div class=\042%s\042>", c);
6319 /* use rel href */
6320 if (0 != (4 & flags)) {
6321 asnprintf(d, z, "<a class=\042%s\042 "
6322 "href=\042%02d.html\042>",
6323 "cnbt", s->chan);
6324 } else {
6325 asnprintf(d, z, "<a class=\042%s\042 "
6326 "href=\042/pg/%02d.html\042>",
6327 "cnbt", s->chan);
6330 /* italicize current channel if browser colors forced to "use my colors".
6331 TODO: put it in the style sheet, class cnba3 maybe, but still want colors
6333 i1 = i0 = "";
6334 if (cap_chan == s->chan) {
6335 i1 = "<i>";
6336 i0 = "</i>";
6338 // try callsign instead. nope, too big
6339 // asnprintf(d, z, "%s%s%s ", i1, s->call, i0);
6340 asnprintf(d, z, "%s%s%s ", i1, s->sid, i0);
6341 asnprintf(d, z, "</a>");
6342 asnprintf(d, z, "</div>\n");
6344 asnprintf(d, z, "<br class=\042%s\042 />\n", "br_epgdiv");
6345 asnprintf(d, z, "<hr width=\042%s\042 />\n", "100%");
6346 asnprintf(d, z, "</div>\n");
6349 /* This flag shows signal strength image graphs for each channel
6350 CSS should allow it to resize better than old table did
6352 /* static */
6353 void
6354 build_sigs_html4 ( char *d, int z, int flags )
6356 int i, iz, v;
6357 struct sig_s *s;
6358 char *a, *b, *e, *f, *c, *i0, *i1, hr[64];
6360 i = iz = v = 0;
6361 a = "";
6362 s = NULL;
6363 b = "sgbl1";
6364 e = "";
6365 c = "green";
6366 f = "f3";
6367 i0 = i1 = "";
6369 /* epg doesn't use this format, but uses similar */
6370 if (0 == (2 & flags)) return;
6372 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
6374 a = "";
6375 asnprintf(d, z, "<div class=\042%s\042>\n", "f4");
6377 /* can't get centering to work decently with CSS so far */
6378 // asnprintf(d, z, "<center>\n");
6379 for (i = 0; i < scan_idx; i++) {
6380 s = &ptc[scan_list[i]].sig;
6382 asnprintf(d, z, "<div class=\042%s\042>\n", "sgbl");
6384 /***************************************************** start of <ul> */
6385 asnprintf(d, z, "<ul class=\042%s\042>\n", "sgbul");
6388 /*************************************************************** EPG nav */
6390 /* default style */
6391 c = "cnta0";
6393 /* auto epg style */
6394 if (0 != s->pgto) c = "cnta2";
6396 /* current channel overrides auto-epg style */
6397 i0 = i1 = "";
6398 if (cap_chan == s->chan) {
6399 c = "cnta1";
6401 /* TODO: move italics to style sheet once decided what gets italics */
6402 i1 = "<i>";
6403 i0 = "</i>";
6406 asnprintf( d, z, "<li class=\042%s\042>"
6407 "<a class=\042%s\042 "
6408 "href=\042/pg/%02d.html\042>"
6409 "%s%s%s</a></li>\n",
6410 b, c, s->chan, i1, s->sid, i0 );
6412 /*************************************************** Signal scan control */
6415 /* kc, dB, then strength average % on top of chart */
6416 f = "f2";
6418 #ifdef USE_DVB_EXPERIMENTAL
6419 /* drift kilocycles */
6420 if (0 != scan_khz) {
6421 asnprintf(d, z, "<li class=\042%s %s\042> %-dkc"
6422 "</li>\n",
6423 b, f, s->fohz / 1000);
6425 #endif
6427 if (0 != scan_snr) {
6428 calc_snr_rms(s);
6429 #ifdef USE_DB_PERCENT
6430 /* N.nn instead of N dB */
6431 asnprintf(d, z, "<li class=\042%s %s\042> %d.%02d "
6432 "</li>\n",
6433 b, f, 0xFF & (s->snr_rms >> 8),
6434 ((0xFF & s->snr_rms) * 100) >> 8 );
6435 #else
6436 /* N dB instead of n.nn */
6437 asnprintf(d, z, "<li class=\042%s %s\042> %ddB "
6438 "</li>\n",
6439 b, f, 0xFF & (s->snr_rms >> 8) );
6440 #endif
6443 /* this value is average of signal strength, reflects graph below it.
6444 it gets a channel select href if not using png graph
6446 calc_sig_avg(s);
6448 snprintf( hr, sizeof(hr)-1, "%d%%", s->str_avg);
6450 if (0 == scan_png) {
6451 snprintf( hr, sizeof(hr)-1,
6452 "<a class=\042%s\042"
6453 " href=\042%s%d.html?v=%d\042>%d%%</a>",
6454 c, NAME, arg_devnum, s->chan, s->str_avg );
6457 asnprintf(d, z, "<li class=\042%s %s\042> %s </li>\n",
6458 b, f, hr);
6460 v = s->chan;
6462 /* if png graph is enabled it gets href for channel select */
6463 if (0 != scan_png)
6465 /* no background image */
6466 a = "SIG";
6467 e = "img_sig";
6469 /* epg type background image */
6470 if ((cap_chan == s->chan) && (0 != scan_sig)) {
6471 e = "epgsgi";
6472 a = "NOSIG";
6475 asnprintf(d, z, "<li class=\042%s\042>"
6476 "<a class=\042%s\042 "
6477 "href=\042%s%d.html?v=%d\042>"
6478 "<img class=\042%s\042 "
6479 "alt=\042%s%02d\042 "
6480 "border=\042%s\042 "
6481 "src=\042/pg/%d-%02d.png\042 />"
6482 "</a></li>\n",
6483 b, c, NAME, arg_devnum, v, e, a,
6484 s->chan, "1", arg_devnum, s->chan);
6487 /****************************************************** Auto EPG control */
6488 e = "epgoff.png";
6489 a = "APG OFF";
6491 if (0 != s->pgto) {
6492 e = "epgon.png"; a = "APG ON";
6494 if ((CAP_NONE != cap_now) && (cap_chan == s->chan)) {
6495 e = "epglive.png";
6496 a = "LIVE";
6499 asnprintf(d, z, "<li class=\042%s\042>"
6500 "<a href=\042/%s%d.html?e=%02d\042>"
6501 "<img class=\042%s\042 "
6502 "alt=\042%s\042 "
6503 "src=\042/pg/img/%s\042 />"
6504 "</a></li>\n",
6505 b, NAME, arg_devnum, s->chan, "img_apg", a, e);
6508 /******************************************************* end of <ul> */
6509 asnprintf(d, z, "</ul>\n");
6510 asnprintf(d, z, "</div>\n\n");
6512 // asnprintf(d, z, "</center>\n");
6513 asnprintf(d, z, "</div>\n");
6516 /* CSS sets the colors */
6517 /* <body ...> settings */
6518 /* build_foot_html should close the <body> bag */
6519 /* static */
6520 void
6521 build_body_html4 ( char *d, int z, int flags )
6523 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
6524 asnprintf(d, z, "<body id=\042%s\042>\n", "top");
6525 build_snav_html4(d, z, flags);
6526 build_cnav_html4(d, z, flags);
6527 build_sigs_html4(d, z, flags);
6530 /* static */
6531 void
6532 build_head_html4 ( char *d, int z, int flags, char *title, int r, char *caller)
6535 /* w3c validator needs this to determine how to validate it */
6536 asnprintf(d, z, "%s", DOCTYPE_HTML4 );
6538 /* force 1s delayed update of free space */
6539 utsvsu = 0;
6541 asnprintf( d, z, "<!-- %s-%s is %s by %s -->\n",
6542 NAME, VERSION, COPYRIGHT, EMAIL );
6544 asnprintf( d, z, "<!-- %s-%s is released under the %s -->\n",
6545 NAME, VERSION, LICENSE );
6546 if (NULL != caller)
6547 asnprintf(d, z, "<!-- %s %s -->\n", caller, &date_now[4]);
6549 asnprintf(d, z, "<html>\n");
6551 asnprintf(d, z, "<head>\n");
6552 asnprintf(d, z, "<meta http-equiv=\042%s\042 content=\042%s\042>\n",
6553 "Content-Type", "text/html;charset=ISO-8859-1");
6555 #if 0
6556 /* Don't override refresh settings */
6557 /* This makes it so you can't use atscapN.html page for idle during capture.
6558 Refresh is SIG_PNG_W because that's all the chart will between refresh.
6559 If player and mozilla are running at same time, change to cap/cut dir.
6561 if (CAP_NONE != cap_now) r = SIG_PNG_W;
6562 #endif
6564 if (r > 0)
6565 asnprintf(d, z, "<meta http-equiv=\042%s\042 "
6566 "content=\042%d\042>\n",
6567 "Refresh", r);
6569 asnprintf(d, z, "<link rel=\042%s\042 "
6570 "href=\042%s\042 "
6571 "type=\042%s\042>\n",
6572 "stylesheet", "/pg/img/style.css", "text/css");
6574 if (NULL != title) asnprintf(d, z, "<title>%s</title>", title);
6575 asnprintf(d, z, "</head>\n");
6577 /************************************************** end of build_head_html4 */
6580 /************************************************ start of build_head_xhtml */
6581 /* static */
6582 void
6583 build_head_xhtml( char *d, int z, int flags, char *title, int r, char *caller)
6585 char *t;
6587 /* xhtml wants " />" for end tag on certain things */
6588 t = " /";
6589 utsvsu = 0; /* force 1s delayed update of free space */
6591 /* w3c validator needs this to determine how to validate it */
6592 /* this document isn't really xhtml or xml, only fancy html 4.01 */
6593 asnprintf(d, z, "<?xml version=\042%s\042?>\n", "1.0");
6595 asnprintf(d, z, "%s", DOCTYPE_XHTML );
6597 asnprintf( d, z, "<!-- %s-%s is %s by %s -->\n",
6598 NAME, VERSION, COPYRIGHT, EMAIL );
6600 asnprintf( d, z, "<!-- %s-%s is released under the %s -->\n",
6601 NAME, VERSION, LICENSE );
6602 if (NULL != caller)
6603 asnprintf(d, z, "<!-- %s %s -->\n", caller, &date_now[4]);
6605 asnprintf(d, z, "<html xmlns=\042http://www.w3.org/1999/xhtml\042 "
6606 "xml:lang=\042en\042 lang=\042en\042>\n" );
6608 asnprintf(d, z, "<head>\n");
6610 asnprintf(d, z, "<meta http-equiv=\042%s\042 "
6611 "content=\042%s\042%s>\n",
6612 "Content-Type",
6613 "text/html;charset=ISO-8859-1", t);
6615 if (0 < r)
6616 asnprintf(d, z,"<meta http-equiv=\042%s\042 "
6617 "content=\042%d\042%s>\n",
6618 "Refresh", r, t);
6620 #ifdef USE_STYLE_EMBED
6621 /* only use this if css styles are in this file */
6622 asnprintf(d, z, "<meta http-equiv=\042%s\042 "
6623 "content=\042%s\042%s>\n",
6624 "Content-Style-Type", "text/css", t);
6625 #else
6626 asnprintf(d, z, "<link rel=\042%s\042 "
6627 "href=\042%s\042 "
6628 "type=\042%s\042%s>\n",
6629 "stylesheet", "/pg/img/style.css", "text/css", t);
6630 #endif
6631 if (NULL != title) asnprintf(d, z, "<title>%s</title>", title);
6632 asnprintf(d, z, "</head>\n");
6634 /************************************************** end of build_head_xhtml */
6636 /* static void */
6637 void
6638 build_head_html ( char *d, int z, int flags, char *title, int r, char *caller)
6640 if (0 == use_css) {
6641 build_head_html3( d, z, flags, title, r, caller );
6642 } else {
6643 if (0 == (8 & flags)) {
6644 build_head_html4( d, z, flags, title, r, caller );
6645 } else {
6646 build_head_xhtml( d, z, flags, title, r, caller );
6649 /* TODO: should be able to make HTML4 body compatible with XHTML1 */
6650 build_body_html4( d, z, flags );
6654 /* build standard html footer with optional w3c validated imgs */
6655 /* validated is optional because http index_html breaks the html rules
6656 by using <hr> inside <pre> block, even though it renders OK.
6658 /* static */
6659 void
6660 build_foot_html3 ( char *d, int z, int v )
6663 asnprintf(d, z, "<br>\n");
6664 asnprintf(d, z, "<hr>\n");
6665 asnprintf(d, z, "<center>\n");
6667 /* show the log link for this instance */
6668 asnprintf(d, z, "<a href=\042/%s%d.log\042>", NAME, arg_devnum);
6669 asnprintf(d, z, " %s-%s\n",NAME, VERSION);
6670 asnprintf(d, z, "</a> ");
6671 asnprintf(d, z, "%s %s:%d\n", LASTEDIT, arg_whost, arg_wport);
6672 asnprintf(d, z, "<br>\n");
6673 asnprintf(d, z, "<br>\n");
6675 /* show the html 4.01 validator image if it has been validated */
6676 if (1 == (1 & v))
6677 asnprintf(d, z, "<img alt=\042%s\042 src=\042%s\042 />\n",
6678 "HTML401", "/pg/img/valid-html401-blue.png");
6680 asnprintf(d, z, "</center>\n");
6681 asnprintf(d, z, "</body>\n");
6682 asnprintf(d, z, "</html>\n");
6686 /* build standard xhtml footer with optional w3c validated imgs */
6687 /* static */
6688 void
6689 build_foot_html4 ( char *d, int z, int v )
6691 int i;
6693 #if 0
6694 for (i=0; i < 2; i++) asnprintf(d, z,"<br />");
6695 asnprintf(d, z, "\n</body>\n");
6696 asnprintf(d, z, "\n</html>\n");
6697 return;
6698 #endif
6700 // *d = 0;
6702 /* some vertical space to show the HTML 4 validation logos */
6703 for (i=0; i < 30; i++) asnprintf(d, z,"<br />");
6704 asnprintf(d, z, "\n");
6706 asnprintf(d, z, "\n\n");
6707 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
6708 asnprintf(d, z, "<div id=\042%s\042>\n", "foot");
6709 asnprintf(d, z, "<br class=\042%s\042 />\n", "br_epgdiv");
6710 asnprintf(d, z, "<br /><br />\n");
6711 asnprintf(d, z, "<hr />\n");
6712 asnprintf(d, z, "<center>\n");
6714 /* show the log link for this instance */
6715 asnprintf(d, z, "<a class=\042%s\042 "
6716 "href=\042/%s%d.log\042>",
6717 "green", NAME, arg_devnum);
6718 asnprintf(d, z, " %s-%s\n",NAME, VERSION);
6719 asnprintf(d, z, "</a> ");
6720 asnprintf(d, z, "%s %s %s %s:%d\n", LASTEDIT, LASTDATE, LASTTIME,
6721 arg_whost, arg_wport);
6722 asnprintf(d, z, "<br class=\042%s\042/>\n", "br_epgdiv");
6723 asnprintf(d, z, "<br class=\042%s\042/>\n", "br_epgdiv");
6725 /* show the html 4.01 validator image if it has been validated */
6726 if (1 == (1 & v))
6727 asnprintf(d, z, "<img alt=\042%s\042 src=\042%s\042 />\n",
6728 "HTML401", "/pg/img/valid-html401-blue.png");
6730 #ifdef USE_XHTML
6731 if (2 == (2 & v))
6732 asnprintf(d, z, "<img alt=\042%s\042 src=\042%s\042 />\n",
6733 "XHTML10", "/pg/img/valid-xhtml10-blue.png");
6734 #endif
6736 if (4 == (4 & v))
6737 asnprintf(d, z, "<img alt=\042%s\042 src=\042%s\042 />\n",
6738 "CSS", "/pg/img/valid-css-blue.png");
6740 #ifdef USE_POWERDOWN
6741 #ifdef USE_POWERDOWN_DEBUG
6742 /* show powerdown status at end bottom */
6743 if (0 != arg_tmpfs) {
6744 char tx[64];
6746 asnprintf(d, z, "<br />\n");
6747 asnprintf(d, z, "Next:<br />\n");
6749 text_time(tx, utspdd, 20);
6750 asnprintf(d, z, "PD %s<br />\n", tx);
6752 text_time(tx, utswuv, 20);
6753 asnprintf(d, z, "PV %s<br />\n", tx);
6755 text_time(tx, utstpg, 20);
6756 asnprintf(d, z, "PG %s<br />\n", tx);
6758 #endif
6759 #endif
6761 asnprintf(d, z, "</center>\n");
6762 asnprintf(d, z, "</div>\n");
6763 asnprintf(d, z, "</body>\n");
6764 asnprintf(d, z, "</html>\n");
6767 /* static */
6768 void
6769 build_foot_html( char *d, int z, int v )
6771 if (0 == use_css) {
6772 build_foot_html3( d, z, v );
6773 } else {
6774 build_foot_html4( d, z, v );
6778 /* Build capture stats as html into string at s, with maximum size z. */
6779 /* TODO: this should be done as a table instead of pre-formatted text */
6780 /* static */
6781 void
6782 build_stats_html3 ( char *d, int z )
6784 time_t t, tu; /* current time */
6785 long long tt, te, tp, tb; /* totals: packets, tables, errors, band */
6786 long long ets, ln;
6787 int dd,hh,mm,ss;
6788 int i, j, k;
6789 char dn[ 32 ], c[256], p[256], uri[256],
6790 *c0, *c1, *c2, *c3, *c4, *c5, *c6, *c7,
6791 *u, *up; /* text fields */
6793 struct sig_s *s;
6795 s = &ptc[ cap_chan ].sig;
6797 ets = s->cap_ets;
6799 if (CAP_NONE != cap_now) {
6800 ets = (long long)utsets;
6802 /* normally done only at end of cap but want live info now */
6803 memcpy( &s->pkt, &pkt, sizeof( pkt ));
6806 if (ets <1) ets = 1; /* /0 avoid */
6808 u = "Log";
6809 /* if (CAP_NONE == cap_now) u = "Save"; */
6811 tt = te = tp = tb = 0LL;
6813 /* average rate starts inaccurate but as sample gets larger, it stabilizes */
6815 memset( c, 0, sizeof(c));
6817 c0 = &c[0x00];
6818 c1 = &c[0x20];
6819 c2 = &c[0x40];
6820 c3 = &c[0x60];
6821 c4 = &c[0x80];
6823 /* spares if needed */
6824 c5 = &c[0xA0];
6825 c6 = &c[0xC0];
6826 c7 = &c[0xE0];
6828 t = (time_t) utsnow;
6829 ctime_r( &t, dn );
6830 dn[19] = 0;
6832 /* header */
6833 asnprintf( d, z, "<center>\n");
6835 // validator doesn't like this
6836 // asnprintf( d, z, "<font color=\042%s\042>\n", "#FFFFFF");
6838 tu = t - utc_up;
6839 dd = tu / SECDAY;
6840 hh = (tu / SECHR) % 24;
6841 mm = (tu / SECMIN) % 60;
6842 ss = tu % 60;
6844 asnprintf( d, z, "%s, <i>Uptime %dd %02d:%02d:%02d</i><br>", dn, dd, hh, mm, ss);
6845 asnprintf( d, z, "ATSC and MPEG Transport Stream Statistics<br>\n");
6846 asnprintf( d, z, "LCN %d %s %s PTC %d.%d TSID %04X<br>\n",
6847 cap_chan, s->call, s->sid, s->ptc, s->pn, vc[0].tsid);
6849 filebase( p, out_name, F_FILE);
6851 /* info cap log html file was last log available? */
6852 up = "";
6853 if ((CAP_INFO == cap_now) || (CAP_INFO == cap_prev))
6854 up = "pg/";
6856 /* FIXME: presumes *p is 0 if info cap
6857 TODO: don't show blank link
6859 if (0 != *p) {
6861 hh = ets / SECHR;
6862 mm = (ets / SECMIN) % 60;
6863 ss = ets % 60;
6865 filebase( uri, p, F_TFILE);
6866 asnprintf( d, z, "<a href=\042/%s%s.html\042>", up, uri);
6868 asnprintf( d, z, "%s %s", u, p);
6870 asnprintf( d, z, "</a>");
6871 asnprintf( d, z, " ET %02d:%02d:%02d", hh, mm, ss);
6874 asnprintf( d, z, "<hr width=\042%s\042>\n", "33%");
6877 /* body */
6879 asnprintf( d, z, "<pre>");
6881 tt = te = tp = tb = 0LL;
6882 asnprintf( d, z, "Virtual Channels on PTC %d\n", s->ptc);
6883 asnprintf( d, z, HR_FMT, "50%");
6884 asnprintf( d, z, "%s Tables Errors Packets Rate bits/s KBytes/s\n",
6885 "Virtual Channels");
6886 /* if live, show the rates per VC ES */
6887 // if (CAP_NONE != cap_now)
6889 for (i = 0; i < vc_ncs; i++) {
6891 char *sp = " "; /* selected program or full cap is all *'s */
6893 /* totals for each vc */
6894 tt = te = tp = tb = 0LL;
6896 if (CAP_NONE != cap_now) {
6897 if (cap_pn == vc[i].pn) sp = "***";
6898 if (-1 == cap_pn) sp = "***";
6899 } else {
6900 if (s->pn == vc[i].pn) sp = "***";
6903 /* one video per vc */
6905 /* video tables are number of payload starts, actually is video frame count */
6907 ln = (long long) psi_pids[ vc[i].espid[0] ];
6908 if (CAP_NONE == cap_now) ln = 0;
6909 tt += ln;
6910 lltoasc( c0, ln );
6912 /* errors from cce_pids[ vc[i].espid[0] ] */
6913 ln = cce_pids[ vc[i].espid[0] ];
6914 if (CAP_NONE == cap_now) ln = 0;
6915 te += ln;
6916 lltoasc( c1, ln );
6918 /* packet counts from pids[ vc[i].espid[0] ] */
6919 ln = (long long) pids[ vc[i].espid[0] ];
6920 if (CAP_NONE == cap_now) ln = 0;
6921 tp += ln;
6922 lltoasc( c2, ln );
6924 /* bit count is packet count * 1504, rate is per ets */
6925 ln *= 188LL;
6926 ln <<= 3;
6927 ln /= ets;
6928 tb += ln;
6929 if (CAP_NONE == cap_now) ln = 0;
6930 lltoasc( c3, ln );
6931 ln >>= 13;
6932 if (CAP_NONE == cap_now) ln = 0;
6933 lltoasc( c4, ln );
6935 /* look up name from tsids. if not found, fallback to vc.cname */
6936 k = find_tsidx( vc[i].tsid, vc[i].pn, WHO );
6937 u = vc[i].cname;
6938 if (k >= 0) u = tsids[k].call;
6940 /* only lines up if component name < 10 chars */
6941 snprintf( uri, sizeof(uri), "?c=%d", i);
6942 asnprintf( d, z, "<a href=\042%s\042>%3s %7s</a>", uri, sp, u);
6943 asnprintf( d, z, " V0: %11s %7s %12s %12s %8s\n",
6944 c0, c1, c2, c3, c4 );
6946 /* may have multiple audios */
6947 for (j = 0; j < vc[i].acn; j++) {
6948 sp = " ";
6949 if (CAP_NONE != cap_now) {
6950 if (cap_pn == vc[i].pn)
6951 if (cap_va == j) sp = "***";
6952 if (-1 == cap_pn) sp = "***";
6953 } else {
6954 if (s->pn == vc[i].pn)
6955 if (s->va == j) sp = "***";
6958 /* audio tables are number of payload starts, audio frame count? */
6959 ln = (long long) psi_pids[ vc[i].espid[j+1] ];
6960 if (CAP_NONE == cap_now) ln = 0;
6961 tt += ln;
6962 lltoasc( c0, ln );
6964 /* errors from cc[ vc[i].espid[j] ] */
6965 ln = (long long) cce_pids[ vc[i].espid[j+1] ];
6966 if (CAP_NONE == cap_now) ln = 0;
6967 te += ln;
6968 lltoasc( c1, ln );
6970 /* counts from pids[ vc[i].espid[j] ] */
6971 ln = (long long) pids[ vc[i].espid[j+1] ];
6972 if (CAP_NONE == cap_now) ln = 0;
6973 tp += ln;
6974 lltoasc( c2, ln );
6976 /* bit count is packet count * 1504, rate is per ets */
6977 ln *= 188LL;
6978 ln <<= 3;
6979 ln /= ets;
6980 tb += ln;
6981 if (CAP_NONE == cap_now) ln = 0;
6982 lltoasc( c3, ln );
6983 ln >>= 13;
6984 if (CAP_NONE != cap_now) ln = 0;
6985 lltoasc( c4, ln );
6987 /* audio call not needed, but lang might be useful. eng if lang is blank */
6988 u = vc[i].eslang[j+1];
6989 if (*u <= ' ') u = "eng";
6990 snprintf( uri, sizeof(uri), "?c=%d,%d", i, j);
6992 asnprintf( d, z, "<a href=\042%s\042>%3s %7s</a>",
6993 uri, sp, u);
6994 asnprintf( d, z, " A%d: %11s %7s %12s %12s %8s\n",
6995 j, c0, c1, c2, c3, c4 );
6998 /* only ever 2 transport tables for each program, 1 PAT + 1 PMT */
6999 lltoasc( c0, tt );
7000 lltoasc( c1, te );
7001 lltoasc( c2, tp );
7002 lltoasc( c3, tb );
7004 /* divide by 8192 not 8000 */
7005 tb >>= 13;
7006 lltoasc( c4, tb);
7008 asnprintf( d, z, " Totals: %11s %7s %12s %12s %8s\n",
7009 c0, c1, c2, c3, c4 );
7010 // validator doesn't like this
7011 // asnprintf( d, z, HR_FMT, "70%");
7016 asnprintf( d, z, HR_FMT, "50%");
7019 lltoasc( c0, s->pkt.tserrt );
7020 lltoasc( c1, s->pkt.teiert );
7021 lltoasc( c2, s->pkt.tsccet );
7022 lltoasc( c3, s->pkt.synert );
7023 lltoasc( c4, s->pkt.tscert );
7025 asnprintf( d, z,"%s", "Transport Errors"
7026 " Error Set"
7027 " Continuity"
7028 " Lost sync"
7029 " Scrambled\n");
7031 asnprintf( d, z, " %12s %12s %12s %12s %11s\n",
7032 c0, c1, c2, c3, c4 );
7035 asnprintf( d, z, "\n");
7037 asnprintf( d, z, "<a href=\042%s\042>ATSC Transport</a>", "?c=-3");
7038 if (0 == s->as) {
7039 asnprintf( d, z, "\n");
7040 } else {
7041 asnprintf( d, z, " Tables Errors Packets Rate bits/s KBytes/s\n");
7043 lltoasc( c0, s->pkt.stt );
7044 lltoasc( c1, s->pkt.crcstt);
7045 lltoasc( c2, s->pkt.stt );
7046 lltoasc( c3, (s->pkt.stt * 1504LL) / ets);
7048 ln = (long long) s->pkt.stt;
7049 ln *= 188LL;
7050 ln <<= 3;
7051 ln /= ets;
7052 lltoasc( c3, ln );
7054 ln >>= 13;
7055 lltoasc( c4, ln );
7057 if (0 != s->as)
7058 asnprintf( d, z, " System Time: %7s %7s %12s %12s %8s\n",
7059 c0, c1, c2, c3, c4 );
7061 lltoasc( c0, s->pkt.mgt );
7062 lltoasc( c1, s->pkt.crcmgt);
7063 lltoasc( c2, s->pkt.mgtp );
7065 ln = (long long) s->pkt.mgtp;
7066 ln *= 188LL;
7067 ln <<= 3;
7068 ln /= ets;
7069 lltoasc( c3, ln );
7071 ln >>= 13;
7072 lltoasc( c4, ln );
7074 if (0 != s->as)
7075 asnprintf( d, z, " Master Guide: %7s %7s %12s %12s %8s\n",
7076 c0, c1, c2, c3, c4 );
7078 lltoasc( c0, s->pkt.tvct );
7079 lltoasc( c1, s->pkt.crctvct);
7080 lltoasc( c2, s->pkt.tvctp );
7082 ln = (long long) s->pkt.tvctp;
7083 ln *= 188LL;
7084 ln <<= 3;
7085 ln /= ets;
7086 lltoasc( c3, ln );
7088 ln >>= 13;
7089 lltoasc( c4, ln );
7091 if (0 != s->as)
7092 asnprintf( d, z, " T Virtual Channel: %7s %7s %12s %12s %8s\n",
7093 c0, c1, c2, c3, c4 );
7095 if (0 != arg_cable) {
7096 lltoasc( c0, s->pkt.cvct );
7097 lltoasc( c1, s->pkt.crccvct);
7098 lltoasc( c2, s->pkt.cvctp);
7100 ln = (long long) s->pkt.cvctp;
7101 ln *= 188LL;
7102 ln <<= 3;
7103 ln /= ets;
7104 lltoasc( c3, ln );
7106 ln >>= 13;
7107 lltoasc( c4, ln );
7109 if (0 != s->as)
7110 asnprintf( d, z,
7111 " C Virtual Channel: %7s %7s %12s %12s %8s\n",
7112 c0, c1, c2, c3, c4 );
7113 tp += s->pkt.cvctp;
7114 tt += s->pkt.cvct;
7115 te += s->pkt.crccvct;
7118 lltoasc( c0, s->pkt.eit );
7119 lltoasc( c1, s->pkt.crceit);
7120 lltoasc( c2, s->pkt.eitp );
7122 ln = (long long) s->pkt.eitp;
7123 ln *= 188LL;
7124 ln <<= 3;
7125 ln /= ets;
7126 lltoasc( c3, ln );
7128 ln >>= 13;
7129 lltoasc( c4, ln );
7131 if (0 != s->as)
7132 asnprintf( d, z, " Event Information: %7s %7s %12s %12s %8s\n",
7133 c0, c1, c2, c3, c4 );
7135 lltoasc( c0, s->pkt.ett );
7136 lltoasc( c1, s->pkt.crcett);
7137 lltoasc( c2, s->pkt.ettp );
7139 ln = (long long) s->pkt.ettp;
7140 ln *= 188LL;
7141 ln <<= 3;
7142 ln /= ets;
7143 lltoasc( c3, ln );
7145 ln >>= 13;
7146 lltoasc( c4, ln );
7148 if (0 != s->as)
7149 asnprintf( d, z, " Extended Text: %7s %7s %12s %12s %8s\n",
7150 c0, c1, c2, c3, c4 );
7152 lltoasc( c0, s->pkt.rrt );
7153 lltoasc( c1, s->pkt.crcrrt);
7154 lltoasc( c2, s->pkt.rrtp );
7156 ln = (long long) s->pkt.rrtp;
7157 ln *= 188LL;
7158 ln <<= 3;
7159 ln /= ets;
7160 lltoasc( c3, ln );
7162 ln >>= 13;
7163 lltoasc( c4, ln );
7165 if (0 != s->as)
7166 asnprintf( d, z, " Rating Region: %7s %7s %12s %12s %8s\n",
7167 c0, c1, c2, c3, c4 );
7169 tp += s->pkt.mgtp + s->pkt.tvctp + s->pkt.eitp
7170 + s->pkt.ettp + s->pkt.rrtp + s->pkt.stt;
7172 tt += s->pkt.mgt + s->pkt.tvct + s->pkt.eit
7173 + s->pkt.ett + s->pkt.rrt + s->pkt.stt;
7175 te += s->pkt.crcmgt + s->pkt.crctvct + s->pkt.crceit
7176 + s->pkt.crcett + s->pkt.crcrrt + s->pkt.crcstt;
7178 tb = tp;
7179 tb *= 188LL;
7180 tb <<= 3;
7181 tb /= ets;
7183 lltoasc( c0, tt );
7184 lltoasc( c1, te );
7185 lltoasc( c2, tp );
7186 lltoasc( c3, tb );
7188 /* divide by 8192 not 8000 */
7189 tb >>= 13;
7190 lltoasc( c4, tb );
7192 asnprintf( d, z, " Totals: %7s %7s %12s %12s %8s\n",
7193 c0, c1, c2, c3, c4 );
7194 asnprintf( d, z, "\n");
7197 tt = te = tp = tb = 0LL;
7200 asnprintf( d, z, "<a href=\042%s\042>MPEG Transport</a>", "?c=-2");
7201 if (0 == s->ms) {
7202 asnprintf( d, z, "\n");
7203 } else {
7204 asnprintf( d, z, " Tables Errors Packets Rate bits/s KBytes/s\n");
7206 if (0 != s->ms)
7208 lltoasc( c0, s->pkt.pat );
7209 lltoasc( c1, s->pkt.crcpat );
7210 lltoasc( c2, s->pkt.patp );
7212 ln = (long long) s->pkt.patp;
7213 ln *= 188LL;
7214 ln <<= 3;
7215 ln /= ets;
7216 lltoasc( c3, ln );
7218 ln >>= 13;
7219 lltoasc( c4, ln );
7221 if (0 != s->ms)
7222 asnprintf( d, z, " Program Assoc: %7s %7s %12s %12s %8s\n",
7223 c0, c1, c2, c3, c4 );
7225 lltoasc( c0, s->pkt.pmt );
7226 lltoasc( c1, s->pkt.crcpmt );
7227 lltoasc( c2, s->pkt.pmtp );
7229 ln = (long long) s->pkt.pmtp;
7230 ln *= 188LL;
7231 ln <<= 3;
7232 ln /= ets;
7233 lltoasc( c3, ln );
7235 ln >>= 13;
7236 lltoasc( c4, ln );
7238 if (0 != s->ms)
7239 asnprintf( d, z, " Program Map: %7s %7s %12s %12s %8s\n",
7240 c0, c1, c2, c3, c4 );
7242 lltoasc( c0, 0LL );
7243 lltoasc( c1, 0LL ); /* FIXME: walk vc for all video CC */
7244 lltoasc( c2, s->pkt.vid );
7245 ln = (long long) s->pkt.vid;
7246 ln *= 188LL;
7247 ln <<= 3;
7248 ln /= ets;
7249 lltoasc( c3, ln );
7251 ln >>= 13;
7252 lltoasc( c4, ln );
7254 if (0 != s->ms)
7255 asnprintf( d, z, " MPEG Video: %7s %7s %12s %12s %8s\n",
7256 c0, c1, c2, c3, c4 );
7258 lltoasc( c0, 0LL );
7259 lltoasc( c1, 0LL ); /* FIXME: walk vc for all audio CC */
7260 lltoasc( c2, s->pkt.aud );
7262 ln = (long long) s->pkt.aud;
7263 ln *= 188LL;
7264 ln <<= 3;
7265 ln /= ets;
7266 lltoasc( c3, ln );
7268 ln >>= 13;
7269 lltoasc( c4, ln );
7271 if (0 != s->ms)
7272 asnprintf( d, z, " A/52 Audio: %7s %7s %12s %12s %8s\n",
7273 c0, c1, c2, c3, c4 );
7275 ln = (long long) s->pkt.null;
7276 if (ln > 0LL) ln = 1LL;
7277 lltoasc( c0, ln );
7278 ln = (long long) cce_pids[ 0x1FFF ];
7279 lltoasc( c1, ln );
7281 ln = (long long) s->pkt.null;
7282 lltoasc( c2, ln );
7284 ln *= 188LL;
7285 ln <<= 3;
7286 ln /= ets;
7287 lltoasc( c3, ln );
7289 ln >>= 13;
7290 lltoasc( c4, ln );
7292 if (0 != s->ms)
7293 asnprintf( d, z, " Null: %7s %7s %12s %12s %8s\n",
7294 c0, c1, c2, c3, c4 );
7296 tt = s->pkt.pat + s->pkt.pmt;
7297 te = s->pkt.crcpat + s->pkt.crcpmt;
7298 tp = s->pkt.patp + s->pkt.pmtp + s->pkt.vid + s->pkt.aud + s->pkt.null;
7299 tb = (tp * 1504LL) / ets;
7301 lltoasc( c0, tt );
7302 lltoasc( c0, te );
7303 lltoasc( c2, tp );
7304 lltoasc( c3, tb );
7306 /* divide by 8192 not 8000 */
7307 tb >>= 13;
7308 lltoasc( c4, tb );
7310 asnprintf( d, z, " Totals: %7s %7s %12s %12s %8s\n",
7311 c0, c1, c2, c3, c4 );
7313 asnprintf( d, z, "\n");
7316 asnprintf( d, z, "</pre>\n");
7317 asnprintf( d, z, HR_FMT, "70%");
7318 asnprintf( d, z, "</center>\n");
7319 return;
7323 /* Build div + ul + li, up to 7 li items supplied in c.
7324 ul list header centered, numbers are right justfied, text is left justified
7325 via the CSS classes for tsl tsul tslir tslil.
7327 d destination string
7328 z is max len of destination string
7329 dc is div class
7330 uc is ul class
7331 lc is li class
7332 c is 8 x 32 strings of 31 bytes each, c[0] is ul list header
7333 if c[240] non blank, use c[0] for text part of href built from c[240]
7335 n is number of items in c, including ul list header at c[0]
7338 void
7339 build_stats_dl (char *d, int z, char *dc, char *uc, char *lc, char *c, int n)
7341 int i;
7342 char *s, *t, *b;
7344 char *div = "<div class=\042%s\042>\n";
7345 char *ul = "<ul class=\042tsul %s\042>\n";
7346 char *lh = "<li class=\042%s %s\042><b>%s</b></li>\n"; /* li header */
7347 char *li = "<li class=\042%s %s\042>%s</li>\n";
7348 char *ah = "<a class=\042%s\042 href=\042/%s\042>%s</a>";
7350 char hr[256];
7352 asnprintf(d, z, div, dc);
7353 asnprintf(d, z, ul, uc);
7355 b = lc;
7357 for (i = 0; i < n; i++) {
7358 lc = b;
7359 t = s = "";
7361 /* check for href for list title. title is centered. href is at c[224] or \0 */
7362 if (0 == i) {
7363 s = c + 0xE0;
7364 if (0 == *s) {
7365 asnprintf(d, z, lh, t, uc, c);
7366 } else {
7367 snprintf(hr, sizeof(hr)-1, ah, "cfsv", s, c);
7368 asnprintf(d, z, lh, t, uc, hr );
7369 *s = 0;
7371 } else {
7372 s = c + (i*32);
7374 /* if errors non-zero, change the color */
7375 if ((CAP_NONE != cap_now) && (2 == i)) {
7376 if (0 != atoi(s)) lc = "red0";
7379 /* li is label on left */
7380 t = "tslil";
7382 /* li is number on right */
7383 if ((*s >= '0') && (*s <= '9')) t = "tslir";
7384 asnprintf(d, z, li, t, lc, s );
7388 asnprintf(d, z, "</ul>\n");
7389 asnprintf(d, z, "</div>\n\n");
7393 /* ATSC Table status */
7394 #define AT_NO 0
7395 #define AT_OK 1
7396 #define AT_ERR 2
7397 #define AT_MAX 3
7398 /* Build capture stats as html into string at s, with maximum size z. */
7399 /* CSS styles: tss tsl tsul tsli0 (normal) tsli1 (red) */
7400 /* static */
7401 void
7402 build_stats_html4 (char *d, int z )
7404 time_t t, tu; /* current time */
7405 long long tt, te, tp, tb; /* totals: packets, tables, errors, band */
7406 long long ets, n;
7408 int dd, hh, mm, ss;
7410 int i, j, k, vpn;
7411 char dt[ 32 ], uri[256], cl[256];
7412 char *u, *up, *tc[AT_MAX]; /* text fields */
7414 /* pointers to 32 byte string divisions of c[256] */
7415 char c[256], *c0, *c1, *c2, *c3, *c4, *c5, *c6, *c7;
7417 struct sig_s *s;
7418 struct pkt_s *p;
7420 s = &ptc[ cap_chan ].sig;
7422 /* no cap shows last cap values, live cap shows current values */
7423 p = &s->pkt;
7424 if (CAP_NONE != cap_now) p = &pkt;
7426 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
7428 tc[AT_NO] = "black1"; /* table missing */
7429 tc[AT_OK] = "green1"; /* table normal */
7430 tc[AT_ERR] = "red1"; /* table error */
7432 k = AT_NO; /* shut up, compiler */
7434 u = "Log"; /* for Log href if any log available */
7436 tt = te = tp = tb = 0LL;
7438 /* average rate starts inaccurate but as sample gets larger, it stabilizes */
7439 ets = (long long)utsets;
7440 if (CAP_NONE == cap_now) ets = (long long)s->cap_ets;
7441 if (ets < 1) ets = 1;
7443 memset(c, 0, sizeof(c));
7445 /* 32 bytes per string, c holds 8 strings total */
7446 c0 = &c[0x00];
7447 c1 = &c[0x20];
7448 c2 = &c[0x40];
7449 c3 = &c[0x60];
7450 c4 = &c[0x80];
7451 c5 = &c[0xA0];
7453 /* reserved for future use */
7454 c6 = &c[0xC0];
7455 c7 = &c[0xE0];
7457 t = (time_t) utsnow;
7458 ctime_r( &t, dt );
7459 dt[19] = 0;
7461 if (ets <1) ets = 1;
7463 /* stats header */
7464 asnprintf(d, z, "<div class=\042%s\042>\n", "tssdiv");
7465 asnprintf(d, z, "<br class=\042%s\042 />\n", "br_tssdiv");
7466 asnprintf(d, z, "<br class=\042%s\042 />\n", "br_tssdiv");
7467 asnprintf(d, z, "<center>\n");
7469 tu = utsnow - utc_up;
7470 dd = tu / SECDAY;
7471 hh = (tu / SECHR) % 24;
7472 mm = (tu / SECMIN) % 60;
7473 ss = tu % 60;
7475 asnprintf(d, z, "%s, <i>Uptime %dd %02d:%02d:%02d</i>\n",
7476 dt, dd, hh, mm, ss);
7478 // asnprintf(d, z, HR_FMT, "33%");
7480 filebase( cl, out_name, F_FILE);
7482 /* info cap log html file was last log available? */
7483 up = "";
7484 if ((CAP_INFO == cap_now) || (CAP_INFO == cap_prev))
7485 up = "pg/";
7487 asnprintf(d, z, "<br />\n");
7488 asnprintf(d, z, "ATSC and MPEG Transport Stream Rates %ds\n", ets);
7489 asnprintf(d, z, "<br />\n");
7491 vpn = cap_pn;
7492 if (CAP_NONE == cap_now) vpn = s->pn;
7494 asnprintf(d, z, "LCN %d %s %s PTC %d.%d TSID %04X\n",
7495 cap_chan, s->call, s->sid, s->ptc, vpn, vc[0].tsid);
7496 asnprintf(d, z, "<br />\n");
7498 /* FIXME: presumes *p is 0 if info cap
7499 TODO: don't show blank link
7501 if (0 != *cl) {
7503 hh = utsets / SECHR;
7504 mm = (utsets / SECMIN) % 60;
7505 ss = utsets % 60;
7507 filebase( uri, cl, F_TFILE);
7508 asnprintf(d, z, "<a class=\042%s\042 "
7509 "href=\042/%s%s.html\042>", "green", up, uri);
7511 asnprintf(d, z, "%s %s", u, cl);
7513 asnprintf(d, z, "</a>");
7514 asnprintf(d, z, " ET %02d:%02d:%02d", hh, mm, ss);
7518 asnprintf(d, z, HR_FMT "\n", "25%");
7519 asnprintf(d, z, "</center>\n");
7521 asnprintf(d, z, "<div class=\042%s\042>\n", "tss");
7523 /* stats error summary */
7525 k = AT_OK;
7526 if (0 != p->tserrt) k = AT_ERR;
7527 snprintf(c0, 31, "TS Errors");
7528 lltoasc( c1, p->tserrt );
7529 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 2);
7531 k = AT_OK;
7532 if (0 != p->teiert) k = AT_ERR;
7533 snprintf(c0, 31, "TEI Set");
7534 lltoasc( c1, p->teiert );
7535 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 2);
7537 k = AT_OK;
7538 if (0 != p->tsccet) k = AT_ERR;
7539 snprintf(c0, 31, "CC Errs");
7540 lltoasc( c1, p->tsccet );
7541 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 2);
7543 k = AT_OK;
7544 if (0 != p->synert) k = AT_ERR;
7545 snprintf(c0, 31, "Sync Errs");
7546 lltoasc( c1, p->synert );
7547 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 2);
7549 k = AT_OK;
7550 if (0 != p->tscert) k = AT_ERR;
7551 snprintf(c0, 31, "Scrambled");
7552 lltoasc( c1, p->tscert );
7553 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 2);
7555 asnprintf(d, z, "<br class=\042%s\042 />\n", "br_tssdiv");
7556 asnprintf(d, z, HR_FMT "\n", "25%");
7558 /* stats table summary */
7560 tc[AT_OK] = "cyan1";
7561 snprintf(c0, 31, "ATSC");
7562 snprintf(c1, 31, "Tables");
7563 snprintf(c2, 31, "Errors");
7564 snprintf(c3, 31, "Packets");
7565 snprintf(c4, 31, "Bits/s");
7566 snprintf(c5, 31, "Kbytes/s");
7568 snprintf(c7, 31, "%s%d.html?c=%d", NAME, arg_devnum, -3);
7569 build_stats_dl(d, z, "tsl", tc[AT_OK], "cyan0", c, 6);
7571 k = AT_NO;
7572 if (0 != p->stt) k = AT_OK;
7573 if (0 != p->crcstt) k = AT_ERR;
7575 snprintf(c0, 31, "STT");
7576 lltoasc( c1, p->stt );
7577 lltoasc( c2, p->crcstt);
7578 lltoasc( c3, p->stt ); /* table count is same as packet count */
7579 n = (long long) p->stt;
7580 n *= 188LL;
7581 n <<= 3;
7582 n /= ets;
7583 lltoasc( c4, n );
7584 n >>= 13; /* NOTE: KB is /1024 not /1000 */
7585 lltoasc( c5, n );
7587 if ((0 != s->as) && (p->stt > 0))
7588 build_stats_dl(d, z, "tsl", tc[k], "cyan0", c, 6);
7591 k = AT_NO;
7592 if (0 != p->mgt) k = AT_OK;
7593 if (0 != p->crcmgt) k = AT_ERR;
7594 snprintf(c0, 31, "MGT");
7595 lltoasc( c1, p->mgt );
7596 lltoasc( c2, p->crcmgt);
7597 lltoasc( c3, p->mgtp );
7598 n = (long long) p->mgtp;
7599 n *= 188LL;
7600 n <<= 3;
7601 n /= ets;
7602 lltoasc( c4, n );
7603 n >>= 13;
7604 lltoasc( c5, n );
7606 if ((0 != s->as) && (p->mgtp > 0))
7607 build_stats_dl(d, z, "tsl", tc[k], "cyan0", c, 6);
7609 if (0 == arg_cable) {
7610 k = AT_NO;
7611 if (0 != p->tvct) k = AT_OK;
7612 if (0 != p->crctvct) k = AT_ERR;
7613 snprintf(c0, 31, "TVCT");
7614 lltoasc( c1, p->tvct );
7615 lltoasc( c2, p->crctvct);
7616 lltoasc( c3, p->tvctp );
7617 n = (long long) p->tvctp;
7618 n *= 188LL;
7619 n <<= 3;
7620 n /= ets;
7621 lltoasc( c4, n );
7622 n >>= 13;
7623 lltoasc( c5, n );
7625 if ((0 != s->as) && (p->tvctp > 0))
7626 build_stats_dl(d, z, "tsl", tc[k], "cyan0", c, 6);
7627 } else {
7629 k = AT_NO;
7630 if (0 != p->cvct) k = AT_OK;
7631 if (0 != p->crccvct) k = AT_ERR;
7632 snprintf(c0, 31, "CVCT");
7633 lltoasc( c1, p->cvct );
7634 lltoasc( c2, p->crccvct);
7635 lltoasc( c3, p->cvctp);
7636 n = (long long) p->cvctp;
7637 n *= 188LL;
7638 n <<= 3;
7639 n /= ets;
7640 lltoasc( c4, n );
7641 n >>= 13;
7642 lltoasc( c5, n );
7643 tp += p->cvctp;
7644 tt += p->cvct;
7645 te += p->crccvct;
7646 if ((0 != s->as) && (p->cvctp > 0))
7647 build_stats_dl(d, z, "tsl", tc[k], "cyan0", c, 6);
7650 k = AT_NO;
7651 if (0 != p->eit) k = AT_OK;
7652 if (0 != p->crceit) k = AT_ERR;
7653 snprintf(c0, 31, "EIT");
7654 lltoasc( c1, p->eit );
7655 lltoasc( c2, p->crceit);
7656 lltoasc( c3, p->eitp );
7657 n = (long long) p->eitp;
7658 n *= 188LL;
7659 n <<= 3;
7660 n /= ets;
7661 lltoasc( c4, n );
7662 n >>= 13;
7663 lltoasc( c5, n );
7665 if ((0 != s->as) && (p->eitp > 0))
7666 build_stats_dl(d, z, "tsl", tc[k], "cyan0", c, 6);
7668 k = AT_NO;
7669 if (0 != p->ett) k = AT_OK;
7670 if (0 != p->crcett) k = AT_ERR;
7671 snprintf(c0, 31, "ETT");
7672 lltoasc( c1, p->ett );
7673 lltoasc( c2, p->crcett);
7674 lltoasc( c3, p->ettp );
7675 n = (long long) p->ettp;
7676 n *= 188LL;
7677 n <<= 3;
7678 n /= ets;
7679 lltoasc( c4, n );
7680 n >>= 13;
7681 lltoasc( c5, n );
7683 if ((0 != s->as) && (p->ettp > 0))
7684 build_stats_dl(d, z, "tsl", tc[k], "cyan0", c, 6);
7686 #if USE_RRT_CSS_STATS
7687 k = AT_NO;
7688 if (0 != p->rrt) k = AT_OK;
7689 if (0 != p->crcrrt) k = AT_ERR;
7690 snprintf(c0, 31, "RRT");
7691 lltoasc( c1, p->rrt );
7692 lltoasc( c2, p->crcrrt);
7693 lltoasc( c3, p->rrtp );
7694 n = (long long) p->rrtp;
7695 n *= 188LL;
7696 n <<= 3;
7697 n /= ets;
7698 lltoasc( c4, n );
7699 n >>= 13;
7700 lltoasc( c5, n );
7702 if ((0 != s->as) && (p->rrtp > 0))
7703 build_stats_dl(d, z, "tsl", tc[k], "cyan0", c, 6);
7704 #endif
7707 tp += p->mgtp + p->tvctp + p->eitp + p->ettp + p->rrtp + p->stt;
7708 tt += p->mgt + p->tvct + p->eit + p->ett + p->rrt + p->stt;
7710 te += p->crcmgt + p->crctvct + p->crceit + p->crcett
7711 + p->crcrrt + p->crcstt;
7712 tb = tp;
7713 tb *= 188LL;
7714 tb <<= 3;
7715 tb /= ets;
7716 snprintf(c0, 31, "ATSC");
7717 lltoasc( c1, tt );
7718 lltoasc( c2, te );
7719 lltoasc( c3, tp );
7720 lltoasc( c4, tb );
7721 tb >>= 13;
7722 lltoasc( c5, tb );
7724 snprintf(c7, 31, "%s%d.html?c=%d", NAME, arg_devnum, -3);
7726 if ((0 != s->as) && (tp > 0))
7727 build_stats_dl(d, z, "tsl", "cyan0", "cyan0", c, 6);
7729 // asnprintf(d, z, "<br class=\042%s\042 />\n", "br_tssdiv");
7730 // asnprintf(d, z, HR_FMT "\n", "25%");
7732 tt = te = tp = tb = 0LL;
7734 /* TODO: show video and audio dimensions as 1920x1080i 5.0 */
7735 snprintf(c0, 31, "MPEG");
7736 if (CAP_NONE == cap_now) {
7737 snprintf(c1, 31, "Size");
7738 snprintf(c2, 31, "Rate");
7739 snprintf(c3, 31, "Packets");
7740 snprintf(c4, 31, "Bits/s");
7741 snprintf(c5, 31, "Kbytes/s");
7743 } else {
7744 snprintf(c1, 31, "Tables");
7745 snprintf(c2, 31, "Errors");
7746 snprintf(c3, 31, "Packets");
7747 snprintf(c4, 31, "Bits/s");
7748 snprintf(c5, 31, "Kbytes/s");
7752 snprintf(c7, 31, "%s%d.html?c=%d", NAME, arg_devnum, -2);
7753 build_stats_dl(d, z, "tsl", "green1", "green0", c, 6);
7755 tc[AT_OK] = "green1";
7757 k = AT_NO;
7758 if (0 != p->pat) k = AT_OK;
7759 if (0 != p->crcpat) k = AT_ERR;
7760 snprintf(c0, 31, "PAT");
7761 lltoasc( c1, p->pat );
7762 lltoasc( c2, p->crcpat );
7763 lltoasc( c3, p->patp );
7764 n = (long long) p->patp;
7765 n *= 188LL;
7766 n <<= 3;
7767 n /= ets;
7768 lltoasc( c4, n );
7769 n >>= 13;
7770 lltoasc( c5, n );
7772 if ((0 != s->ms) && (p->patp > 0))
7773 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 6);
7775 k = AT_NO;
7776 if (0 != arg_cable) {
7777 if (0 != p->cat) k = AT_OK;
7778 if (0 != p->crccat) k = AT_ERR;
7779 snprintf(c0, 31, "CAT");
7780 lltoasc( c1, p->cat );
7781 lltoasc( c2, p->crccat );
7782 lltoasc( c3, p->catp );
7783 n = (long long) p->catp;
7784 n *= 188LL;
7785 n <<= 3;
7786 n /= ets;
7787 lltoasc( c4, n );
7788 n >>= 13;
7789 lltoasc( c5, n );
7790 if ((0 != s->ms) && (p->catp > 0))
7791 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 6);
7794 k = AT_NO;
7795 if (0 != p->pmt) k = AT_OK;
7796 if (0 != p->crcpmt) k = AT_ERR;
7797 snprintf(c0, 31, "PMT");
7798 lltoasc( c1, p->pmt );
7799 lltoasc( c2, p->crcpmt );
7800 lltoasc( c3, p->pmtp );
7801 n = (long long) p->pmtp;
7802 n *= 188LL;
7803 n <<= 3;
7804 n /= ets;
7805 lltoasc( c4, n );
7806 n >>= 13;
7807 lltoasc( c5, n );
7809 if ((0 != s->ms) && (p->pmtp > 0))
7810 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 6);
7812 /* FIXME: walk vc for all video and audio CC for errors. PITA */
7813 k = AT_NO;
7814 if (0 != p->vid) k = AT_OK;
7815 snprintf(c0, 31, "VID");
7816 lltoasc( c1, 0LL );
7817 lltoasc( c2, 0LL );
7818 lltoasc( c3, p->vid );
7819 n = (long long) p->vid;
7820 n *= 188LL;
7821 n <<= 3;
7822 n /= ets;
7823 lltoasc( c4, n );
7824 n >>= 13;
7825 lltoasc( c5, n );
7827 if ((0 != s->ms) && (p->vid > 0))
7828 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 6);
7830 k = AT_NO;
7831 if (0 != p->aud) k = AT_OK;
7832 snprintf(c0, 31, "AUD");
7833 lltoasc( c1, 0LL );
7834 lltoasc( c2, 0LL ); /* FIXME: walk vc for all audio CC */
7835 lltoasc( c3, p->aud );
7836 n = (long long) p->aud;
7837 n *= 188LL;
7838 n <<= 3;
7839 n /= ets;
7840 lltoasc( c4, n );
7841 n >>= 13;
7842 lltoasc( c5, n );
7844 if ((0 != s->ms) && (p->aud > 0))
7845 build_stats_dl(d, z, "tsl", tc[k], "green0", c, 6);
7847 k = AT_NO;
7848 /* only show red title if -n and no nulls found */
7849 if (0 != arg_nulls) {
7850 if (0 != p->null) k = AT_OK;
7852 snprintf(c0, 31, "NULL");
7853 n = (long long) p->null;
7854 if (n > 0LL) n = 1LL;
7855 lltoasc( c1, n );
7856 n = (long long) cce_pids[ 0x1FFF ];
7857 lltoasc( c2, n );
7858 n = (long long) p->null;
7859 lltoasc( c3, n );
7860 n *= 188LL;
7861 n <<= 3;
7862 n /= ets;
7863 lltoasc( c4, n );
7864 n >>= 13;
7865 lltoasc( c5, n );
7867 if ((0 != s->ms) && (p->null > 0))
7868 build_stats_dl(d, z, "tsl", "green1", "grey0", c, 6);
7870 tt = p->pat + p->pmt;
7871 te = p->crcpat + p->crcpmt;
7872 tp = p->patp + p->pmtp + p->vid + p->aud + p->null;
7873 tb = (tp * 1504LL) / ets;
7875 snprintf(c0, 31, "MPEG");
7876 lltoasc( c1, tt );
7877 lltoasc( c2, te );
7878 lltoasc( c3, tp );
7879 lltoasc( c4, tb );
7880 tb >>= 13;
7881 lltoasc( c5, tb );
7883 snprintf(c7, 31, "%s%d.html?c=%d", NAME, arg_devnum, -2);
7885 if ((0 != s->ms) && (tp > 0))
7886 build_stats_dl(d, z, "tsl", "green1", "green1", c, 6);
7888 tt = te = tp = tb = 0LL;
7890 /* The fixme probably isn't as important as being able to select a VC */
7892 /***** FIXME: This is a data collection desync point. It will only show the
7893 last vc information from last capture, because it's not stored by channel.
7894 cce_pids, psi_pids, and pids[] have to be stored on per channel basis.
7895 This will add approximately 12Mbyte to overall ptc[] size. Not a good thing.
7896 PID list shortening is drastically needed for this to be workable,
7897 but then you have to spend a few cycles on the list comparison.
7899 /* There shall be at most only one video per virtual channel pgm for ATSC. */
7900 /* Walk vc[] for each virtual channel program. */
7901 for (i = 0; i < vc_ncs; i++) {
7903 /* selected program and audio, or full cap, has '*' */
7904 char *sp; /* unselected program color */
7905 char hr[256];
7906 short tf;
7908 *hr = 0;
7910 memset( c, 0, sizeof(c));
7912 sp = "grey1";
7913 // asnprintf(d, z, "<br class=\042%s\042 />\n", "br_epgdiv");
7914 // asnprintf(d, z, "<hr width=\042%s\042 />\n", "33%");
7916 if (0 == vc[i].pn) continue; /* no 0 program */
7917 if (65535 == vc[i].pn) continue; /* no NTSC listing */
7918 if (0 == vc[i].pmtes) continue;
7919 if (0 != vc[i].ca) continue; /* no ca ca ca */
7921 /* shows only the set program stats if capture in progress */
7922 if ((CAP_NONE != cap_now)
7923 && (CAP_INFO != cap_now)) {
7924 if ((cap_pn > 0) && (cap_pn != vc[i].pn))
7925 continue;
7926 sp = "white0";
7929 if ( (CAP_NONE == cap_now)
7930 && ((s->pn == vc[i].pn) || (-1 == s->pn)) )
7931 sp = "white0";
7933 /* TESTME: move this higher to legend text too, or leave for visual index? */
7934 // if (CAP_INFO == cap_now) sp = "grey1";
7937 /* TODO: VC select from stats page */
7939 #if 0
7940 /* try vc name and cname, and fallback to callsign and finally ptc.pn */
7941 if (0 != vc[i].name)
7942 snprintf(c0, 31, "%s", vc[i].name);
7944 /* component name as likely to be wrong on cable or broadcast */
7945 if (0 != arg_cable)
7946 if (0 == *c0)
7947 snprintf(c0, 31, "%s", vc[i].cname);
7949 if (0 == *c0)
7950 snprintf(c0, 31, "%s", s->call);
7951 #endif
7953 /* tsid lookup */
7954 if (0 == *c0) {
7955 tf = find_tsidx( vc[i].tsid, vc[i].pn, WHO );
7956 if (tf >= 0)
7957 snprintf(c0, 31, "%s", tsids[ tf ].call);
7960 if (0 == *c0) {
7961 snprintf(c0, 31, "%d.%d", s->ptc, vc[i].pn);
7962 // snprintf(c0, 31, "%04X.%d", vc[i].tsid, vc[i].pn);
7965 #if USE_FULL_TABLES
7966 snprintf(c1, 31, "Tables");
7967 snprintf(c2, 31, "Errors");
7968 snprintf(c3, 31, "Packets");
7969 snprintf(c4, 31, "Bits/s");
7970 snprintf(c5, 31, "Kbytes/s");
7971 build_stats_dl(d, z, "tsl", "white1", sp, c, 6);
7972 #endif
7974 /* totals for each vc */
7975 tt = te = tp = tb = 0LL;
7978 /* video tables are number of payload starts, actually is video frame count */
7979 #ifdef USE_FULL_TABLES
7980 snprintf(c0, 31, "VID%d ", i);
7981 #endif
7982 if (CAP_NONE != cap_now) {
7983 n = (long long) psi_pids[ vc[i].espid[0] ];
7984 tt += n;
7985 lltoasc( c1, n );
7986 /* errors from cce_pids[ vc[i].espid[0] ] */
7987 n = cce_pids[ vc[i].espid[0] ];
7988 te += n;
7989 lltoasc( c2, n );
7990 /* packet counts from pids[ vc[i].espid[0] ] */
7991 n = (long long) pids[ vc[i].espid[0] ];
7992 tp += n;
7993 lltoasc( c3, n );
7994 n *= 188LL;
7995 n <<= 3;
7996 n /= ets;
7997 tb += n;
7998 lltoasc( c4, n );
7999 n >>= 13;
8000 lltoasc( c5, n );
8001 } else {
8003 /* video dimensions and frame rate from SEQ header */
8004 snprintf(c1, 31, "%dx%d", vc[i].hres, vc[i].vres);
8005 snprintf(c2, 31, "%d.%d", vc[i].vfr/100, vc[i].vfr % 100);
8006 strcpy(c3, "0");
8007 strcpy(c4, "0");
8008 strcpy(c5, "0");
8011 /* http video program select, resets audio to 0 */
8012 snprintf(c7, 31, "%s%d.html?c=%d", NAME, arg_devnum, i );
8014 build_stats_dl(d, z, "tsl", "green1", sp, c, 6);
8016 /* There may be more than one audio for each virtual channel pgm for ATSC. */
8017 for (j = 1; j < vc[i].acn+1; j++) {
8019 memset(c, 0, sizeof(c));
8021 /* pre-fill these with something to maintain dl spacing and grid size */
8022 strcpy(c0, "Audio");
8023 if (2 == j) strcpy(c0, "SAP");
8024 strcpy(c1, "0");
8026 sp = "grey1";
8028 if ( (CAP_NONE != cap_now)
8029 && (CAP_INFO != cap_now) ) {
8031 if ( (-1 != cap_pn)
8032 && (cap_pn == vc[i].pn)
8033 && (cap_va != (j - 1)) )
8034 continue;
8036 sp = "white0";
8039 if ( (CAP_NONE == cap_now)
8040 && (s->pn == vc[i].pn)
8041 && (s->va == (j - 1)) )
8042 sp = "white0";
8044 if ( (CAP_NONE == cap_now)
8045 && (-1 == s->pn) )
8046 sp = "white0";
8048 if (CAP_INFO == cap_now) sp = "grey1";
8050 /* audio number and language */
8052 snprintf(c0, 31, "A%d %s", j-1, vc[i].eslang[j] );
8054 /* live cap gets tables and error status */
8055 if (CAP_NONE != cap_now) {
8057 /* audio tables are number of payload starts. video is number of pictures. */
8058 n = (long long) psi_pids[ vc[i].espid[j] ];
8059 tt += n;
8060 lltoasc( c1, n );
8062 /* continuity counter errors */
8063 n = (long long) cce_pids[ vc[i].espid[j] ];
8064 te += n;
8065 lltoasc( c2, n );
8066 /* counts from pids[ vc[i].espid[j] ] */
8067 n = (long long) pids[ vc[i].espid[j] ];
8068 tp += n;
8069 lltoasc( c3, n );
8070 n *= 188LL;
8071 n <<= 3;
8072 n /= ets;
8073 tb += n;
8074 lltoasc( c4, n );
8075 n >>= 13;
8076 lltoasc( c5, n );
8077 } else {
8079 /* idle shows video frame size plus rate and audio channels plus bitrate */
8080 if (1 == vc[i].ach[ j ]) strcpy(c1, "1.0 Mono");
8081 if (2 == vc[i].ach[ j ]) strcpy(c1, "2.0 Stereo");
8082 if (5 == vc[i].ach[ j ]) strcpy(c1, "5.1 Low");
8083 if (6 == vc[i].ach[ j ]) strcpy(c1, "5.1 High");
8085 /* limit to 1024k bits per second rate and set reserved rate to 0 */
8086 n = 0xFFFFF & vc[i].abr[ j ];
8087 if (0xFFFFF == n) n = 0;
8088 lltoasc( c2, n );
8089 strcpy(c3, "0");
8090 strcpy(c4, "0");
8091 strcpy(c5, "0");
8094 /* http video program audio select, only if more than one audio */
8095 if (vc[i].acn > 1)
8096 snprintf(c7, 31, "%s%d.html?c=%d,%d",
8097 NAME, arg_devnum, i, j-1 );
8099 // build_stats_dl(d, z, "tsl", "green1", sp, c, 6);
8100 build_stats_dl(d, z, "tsl", sp, sp, c, 6);
8103 #if 0
8104 /* only ever 2 transport tables for each program, 1 PAT + 1 PMT except
8105 cable has 3 but no way to really use the CAT other than to identify
8106 streams that are NOT encrypted. most are encrypted or non-mpeg
8108 snprintf(c0, 31, "PGM.%d", vc[i].pn );
8109 lltoasc( c1, tt );
8110 lltoasc( c2, te );
8111 lltoasc( c3, tp );
8112 lltoasc( c4, tb );
8113 tb >>= 13;
8114 lltoasc( c5, tb );
8115 build_stats_dl(d, z, "tsl", "white1", sp, c, 6);
8116 #endif
8119 asnprintf(d, z, "</div>\n\n"); /* tss */
8120 asnprintf(d, z, "</div>\n\n"); /* f5 at top */
8121 return;
8124 /* static */
8125 void
8126 build_html_stats ( char *s, int z )
8128 if (0 == use_css) build_stats_html3(s, z);
8129 if (0 != use_css) build_stats_html4(s, z);
8132 #define CC "class=\042"
8133 #define CC1 "class=\042%s\042 "
8134 #define CC2 "class=\042%s%s\042 "
8136 /* static */
8137 void
8138 build_epg_filter_html4( char *d, int z )
8140 int i, j;
8141 char r[256], *lt, *a[3], *b[3], *c[2], *s, *f, *h;
8142 struct pgm_s *p;
8144 a[0] = "<img class=\042cfgbi\042 alt=\042ON\042 src=\042/pg/img/on24.png\042 />";
8145 a[1] = "<img class=\042cfgbi\042 alt=\042OFF\042 src=\042/pg/img/off24.png\042 />";
8147 /* button legend image type, no background */
8148 a[2] = "<img class=\042cfgbli\042 alt=\042%s\042 src=\042%s\042 />";
8150 b[0] = "cfgbt1"; /* unselected button, grip style bright bg */
8151 b[1] = "cfgbt0"; /* selected button, grip style dark bg */
8152 b[2] = "cfgbt2"; /* EPG related style, smooth no bright/dim bg */
8154 c[0] = "cfgba1"; /* unselected button href, action is opposite */
8155 c[1] = "cfgba0"; /* selected button href, action is opposite */
8157 s = " SNR "; /* SNR in dB RMS last 50 samples */
8158 f = " Freq "; /* Frequency drift for arg_extras button */
8160 /* auto-epg/sig button legends (different style for legend button) */
8161 h = "cfgbl";
8163 p = &pgm3;
8165 lt = "div";
8167 snprintf(r, sizeof(r)-1, "%02d", p->chan);
8169 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
8171 asnprintf(d, z, "<div " CC1 ">\n", "cfg");
8173 /* filters for the EPG */
8174 j = 0;
8175 if (0 == p->find) j = 1;
8176 asnprintf(d, z, "<%s " CC "%s %s\042>", lt, "cyan1", b[2] );
8177 asnprintf(d, z, "<a " CC "%s%s\042 href=\042%s.html?m=%d\042>%s%s</a>",
8178 c[j], "", r, j, a[j], " Find " );
8179 asnprintf(d, z, "</%s>\n", lt);
8181 j = 0;
8182 if (0 == p->hide) j = 1;
8183 asnprintf(d, z, "<%s " CC "%s %s\042>", lt, "grey1", b[2] );
8184 asnprintf(d, z, "<a " CC1 "href=\042%s.html?h=%d\042>%s%s</a>",
8185 c[j], r, j, a[j], " Junk " );
8186 asnprintf(d, z, "</%s>\n", lt);
8188 j = 0;
8189 if (0 == p->age) j = 1;
8190 asnprintf(d, z, "<%s " CC "%s %s\042>", lt, "magenta1", b[2] );
8191 asnprintf(d, z, "<a " CC "%s %s\042 href=\042%s.html?o=%d\042>%s%s</a>",
8192 c[j], "black", r, j, a[j], " Aged " );
8193 asnprintf(d, z, "</%s>\n", lt);
8195 j = 0;
8196 if (0 == web_config) j = 1;
8197 asnprintf(d, z, "<div " CC1 ">", b[j]);
8198 asnprintf(d, z, "<a " CC1 "href=\042%s.html?l=2\042>%s%s</a>",
8199 c[j], r, a[j],
8200 (0 != j) ? " More " : " Less ");
8201 asnprintf(d, z, "</div>\n");
8203 if (0 == web_config) return;
8205 j = 0;
8206 if (0 == web_legend) j = 1;
8207 asnprintf(d, z, "<%s " CC1 ">", lt, b[j]);
8208 asnprintf(d, z, "<a " CC1 "href=\042%s.html?l=1\042>%s%s</a>",
8209 c[j], r, a[j], " Help ");
8210 asnprintf(d, z, "</%s>\n", lt);
8212 for (i = 0; i < SGML_FMT; i++) {
8213 j = 0;
8214 if (use_css != i) j = 1;
8215 asnprintf(d, z, "<%s " CC "%s%s\042>", lt, b[2], " grey1");
8217 asnprintf(d, z, "<a " CC1 " href=\042%s.html?f=%d\042>",
8218 c[j], r, i );
8219 if (1 == j) {
8220 asnprintf(d, z, a[j]);
8221 } else {
8222 asnprintf(d, z, "<img class=\042cfgbi black\042 "
8223 "alt=\042OFF\042 "
8224 "src=\042/pg/img/rec24.png\042 />");
8226 asnprintf(d, z, " %s</a>", sgmls[ (i * 2) ]);
8228 asnprintf(d, z, "</%s>\n", lt);
8232 j = 0;
8233 if (0 == epg_grd) j = 1;
8235 asnprintf(d, z, "<%s " CC "%s %s\042>", lt, "grey1", b[2]);
8236 asnprintf(d, z, "<a " CC1
8237 "href=\042%s.html?f=4\042>%s%s</a>",
8238 c[j], r, a[j], " Fills " );
8240 asnprintf(d, z, "</%s>\n", lt);
8242 j = 0;
8243 if (0 == epg_all) j = 1;
8245 asnprintf(d, z, "<%s " CC "%s %s\042>", lt, "grey1", b[2]);
8246 asnprintf(d, z, "<a " CC1
8247 "href=\042%s.html?f=16\042>%s%s</a>",
8248 c[j], r, a[j], " All " );
8250 asnprintf(d, z, "</%s>\n", lt);
8253 /* large tiles get scrollbar toggle */
8254 if (use_css == 1) {
8255 j = 0;
8256 if (0 == epg_sb) j = 1;
8258 /* use epg style buttons */
8259 asnprintf(d, z, "<%s " CC "%s %s\042>", lt, "grey1", b[2]);
8260 asnprintf(d, z, "<a " CC1
8261 " href=\042%s.html?f=8\042>%s%s</a>",
8262 c[j], r, a[j], " Scroll ");
8263 asnprintf(d, z, "</%s>\n", lt);
8265 // asnprintf(d, z, "</div>\n\n");
8269 /* This goes is the server configuration page under the signal charts.
8270 It has the toggles for various displays on this page.
8271 Gold sphere is on, black sphere is off.
8273 static
8274 void
8275 build_cfg_buttons_html4( char *d, int z )
8277 int j;
8278 char *a[3], *b[3], *c[2], *s, *f, *h;
8279 struct pgm_s *p;
8281 if (0 == use_css) return;
8283 a[0] = "<img class=\042cfgbi\042 alt=\042ON\042 src=\042/pg/img/on24.png\042 />";
8284 a[1] = "<img class=\042cfgbi\042 alt=\042OFF\042 src=\042/pg/img/off24.png\042 />";
8286 /* button legend image type, no background */
8287 a[2] = "<img class=\042cfgbli\042 alt=\042%s\042 src=\042%s\042 />";
8289 b[0] = "cfgbt1"; /* unselected button, grip style bright bg */
8290 b[1] = "cfgbt0"; /* selected button, grip style dark bg */
8291 b[2] = "cfgbt2"; /* EPG related style, smooth no bright/dim bg */
8293 c[0] = "cfgba1"; /* unselected button href, action is opposite */
8294 c[1] = "cfgba0"; /* selected button href, action is opposite */
8296 s = " SNR "; /* SNR in dB RMS last 50 samples */
8297 f = " Freq "; /* Frequency drift for arg_extras button */
8299 /* auto-epg/sig button legends (different style for legend button) */
8300 h = "cfgbl";
8302 p = &pgm3;
8304 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
8306 // asnprintf(d, z, "<br " CC1 "/>\n", "br_epgdiv");
8307 asnprintf(d, z, "<div " CC1 ">\n", "cfg");
8309 /* html text mode gets text instead of spheres for config toggles
8310 (mostly done, need to add PNG toggle back to html3 mode)
8314 j = 0;
8315 if (0 == web_config) j = 1;
8316 asnprintf(d, z, "<div " CC1 ">", b[j]);
8317 asnprintf(d, z, "<a " CC1 "href=\042%s%d.html?l=2\042>%s%s</a>",
8318 c[j], NAME, arg_devnum, a[j],
8319 (0 != j) ? " More " : " Less ");
8320 asnprintf(d, z, "</div>\n");
8323 if (0 == web_config) {
8324 asnprintf(d, z, "</div>\n");
8325 asnprintf(d, z, "<br " CC1 "/>\n", "br_epgdiv");
8326 return;
8329 j = 0;
8330 if (0 == web_legend) j = 1;
8331 asnprintf(d, z, "<div " CC1 ">", b[j]);
8332 asnprintf(d, z, "<a " CC1 "href=\042%s%d.html?l=1\042>%s%s</a>",
8333 c[j], NAME, arg_devnum, a[j], " Help ");
8334 asnprintf(d, z, "</div>\n");
8336 j = 0;
8337 if (0 == use_css) j = 1;
8338 asnprintf(d, z, "<div " CC1 ">", b[j]);
8339 asnprintf(d, z, "<a " CC1 "href=\042%s%d.html?f=%d\042>%s%s</a>",
8340 c[j], NAME, arg_devnum, j, a[j], (0 == j)?" HTML ":" CSS");
8341 asnprintf(d, z, "</div>\n");
8343 #ifdef USE_DVB_EXPERIMENTAL
8344 /* CUSTOM: Frequency Drift in KHz toggle, is on top so goes first if used */
8345 if (0 != arg_extras) {
8346 j = 0;
8347 if (0 == scan_khz) j = 1;
8348 asnprintf(d, z, "<div " CC1 ">", b[j]);
8349 asnprintf(d, z, "<a " CC1 "href=\042%s%d.html?q=2\042>%s%s</a>",
8350 c[j], NAME, arg_devnum, a[j], " Freq " );
8351 asnprintf(d, z, "</div>\n");
8353 #endif
8355 /* Signal Display as SNR or Strength % toggle */
8356 j = 0;
8357 if (0 == scan_snr) j = 1;
8358 asnprintf(d, z, "<div " CC1 ">", b[j]);
8359 asnprintf(d, z, "<a " CC1 "href=\042%s%d.html?q=1\042>%s%s</a>",
8360 c[j], NAME, arg_devnum, a[j], " SNR " );
8361 asnprintf(d, z, "</div>\n");
8363 /* Transport Stream Statistics with Bitrates */
8364 j = 0;
8365 if (0 == web_stats) j = 1;
8366 asnprintf(d, z, "<div " CC1 ">", b[j]);
8367 asnprintf(d, z, "<a " CC1 "href=\042%s%d.html?k=%d\042>%s%s</a>",
8368 c[j], NAME, arg_devnum, j, a[j], " Stats ");
8369 asnprintf(d, z, "</div>\n");
8372 // moved to epg pages
8373 // build_epg_filter_html4(d, z );
8375 /* Signal chart PNG display on/off */
8376 j = 0;
8377 if (0 == scan_png) j = 1;
8378 asnprintf(d, z, "<div " CC1 ">", b[j]);
8379 asnprintf(d, z, "<a " CC1
8380 "href=\042%s%d.html?p=%d\042>%s%s</a>",
8381 c[j], NAME, arg_devnum,
8382 (0 == scan_png) ? 1:0, a[j], " PNG " );
8383 asnprintf(d, z, "</div>\n");
8385 /* Signal Scanner for all channels start or stop */
8386 if (CAP_NONE == cap_now) {
8387 j = 1;
8389 // j = 0;
8390 // if (0 != scan_one) j = 1;
8392 if (0 != scan_sig) j = 0;
8393 asnprintf(d, z, "<div " CC1 ">", b[j]);
8394 asnprintf(d, z, "<a " CC1
8395 "href=\042%s%d.html?w=%d\042>%s%s</a>",
8396 c[j], NAME, arg_devnum, j, a[j], " Scan " );
8397 asnprintf(d, z, "</div>\n");
8400 /* legend buttons */
8401 if (0 != web_legend) {
8402 // asnprintf(d, z, "<div " CC1 ">\n", "cfgleg");
8403 // asnprintf(d, z, "<br " CC1 "/>\n", "br_cfgdiv");
8404 asnprintf(d, z, "<div " CC1 ">", h);
8405 asnprintf(d, z, a[2], "EPG-", "/pg/img/epgon24.png", 0);
8406 asnprintf(d, z, " EPG- ");
8407 asnprintf(d, z, "</div>\n");
8409 asnprintf(d, z, "<div " CC1 ">", h);
8410 asnprintf(d, z, a[2], "EPG+", "/pg/img/epgoff24.png", 0);
8411 asnprintf(d, z, " EPG+ ");
8412 asnprintf(d, z, "</div>\n");
8414 asnprintf(d, z, "<div " CC1 ">", h);
8415 asnprintf(d, z, a[2], "LIVE", "/pg/img/epglive24.png", 0);
8416 asnprintf(d, z, " Live ");
8417 asnprintf(d, z, "</div>\n");
8419 asnprintf(d, z, "<div " CC1 ">", h);
8420 asnprintf(d, z, a[2], "SCAN", "/pg/img/scanon24.png", 0);
8421 asnprintf(d, z, " Scan ");
8422 asnprintf(d, z, "</div>\n");
8423 // asnprintf(d, z, "</div>\n");
8426 asnprintf(d, z, "</div>\n");
8427 asnprintf(d, z, "<br " CC1 "/>\n", "br_epgdiv");
8433 /* Main server configuration page:
8434 Server nav
8435 Channel nav
8437 Enable/disable auto-epg for each channel
8438 Scan each channel or all channels
8440 Legend
8441 CSS server option buttons
8443 footer with valid HTML+CSS imgs
8446 /* static */
8447 void
8448 dump_cfg_html ( char *caller )
8450 FILE *f;
8451 char d[32768], n[256];
8452 int z, r;
8454 if (0 != arg_scan) return;
8456 z = sizeof(d);
8458 snprintf( n, sizeof(n), "%s%s%s%d.html",
8459 out_path, ram_path, NAME, arg_devnum);
8461 advrlog( LOG_INFO, "%s(%s) %s", WHO, caller, n );
8463 f = fopen( n, "w");
8464 if (NULL == f) {
8465 dvrlog( LOG_INFO, "%s write fail %s", WHO, n);
8466 return;
8469 /* scan all gets 15s refresh, otherwise don't refresh */
8470 r = 0;
8471 // if ((CAP_NONE == cap_now) && (0 != scan_sig) && (0 == scan_one))
8472 if ((CAP_NONE == cap_now) && (0 != scan_sig))
8473 r = 5;
8475 snprintf( n, sizeof(n), "%s%d", NAME, arg_devnum );
8477 /* html 3 header has sig charts, html4 header does not */
8478 *d = 0;
8479 build_head_html( d, z, 7, n, r, WHO );
8480 fprintf( f, "%s", d);
8482 *d = 0;
8483 if (0 != use_css) {
8484 build_cfg_buttons_html4( d, z );
8485 fprintf( f, "%s", d);
8488 *d = 0;
8489 if (0 != web_stats) build_html_stats( d, z );
8490 fprintf( f, "%s", d);
8492 *d = 0;
8493 build_foot_html( d, z, 5); /* HTML validated */
8494 fprintf( f, "%s", d);
8496 fclose( f );
8497 return;
8500 /* truncate event name to w significant words limited to c chars
8501 s points to long event name, d points to truncated name
8503 FIXME: "Reptiles, The" slipped through as ReptilesThe not Reptiles
8504 because there is no space following "The". FIXED?
8506 /* static */
8507 void
8508 event_truncate ( char *d, char *s, int w, int c )
8510 unsigned int wc, z, z1;
8511 char *r, t;
8512 char e[PNZ];
8513 char *lang = "spa"; /* FIXME: get lang or blank from caller */
8514 char *p;
8516 /* sanity checks */
8517 if ( ( 0 == *s ) || ( w < 1 ) || ( c < 1 )
8518 || ( NULL == d) || ( NULL == s) )
8519 return;
8521 wc = 0;
8522 /* memcpy( e, s, PNZ); */ /* pknaggs says: no NUL term on long event */
8523 astrncpy( e, s, PNZ ); /* this should work better for long event */
8524 p = e;
8525 r = d;
8527 /* add space to end, if it will fit */
8528 z = strlen(p);
8529 if (z < (PNZ-1)) { p[z] = ' '; p[z+1] = 0; }
8531 while( wc < w ) {
8533 /* ENG */
8534 if ( 0 == *p ) break; /* stop at end of event string */
8535 if ( 0 == strncasecmp( p, " " , 1 ) ) { p += 1; continue; }
8536 if ( 0 == strncasecmp( p, "& " , 2 ) ) { p += 2; continue; }
8537 if ( 0 == strncasecmp( p, "a " , 2 ) ) { p += 2; continue; }
8538 if ( 0 == strncasecmp( p, "i " , 2 ) ) { p += 2; continue; }
8539 if ( 0 == strncasecmp( p, "s " , 2 ) ) { p += 2; continue; }
8541 if ( 0 == strncasecmp( p, "as ", 3 ) ) { p += 3; continue; }
8542 if ( 0 == strncasecmp( p, "at " , 3 ) ) { p += 3; continue; }
8543 if ( 0 == strncasecmp( p, "by " , 3 ) ) { p += 3; continue; }
8544 if ( 0 == strncasecmp( p, "in " , 3 ) ) { p += 3; continue; }
8545 if ( 0 == strncasecmp( p, "is " , 3 ) ) { p += 3; continue; }
8546 if ( 0 == strncasecmp( p, "of " , 3 ) ) { p += 3; continue; }
8547 if ( 0 == strncasecmp( p, "to " , 3 ) ) { p += 3; continue; }
8549 if ( 0 == strncasecmp( p, "and ", 4 ) ) { p += 4; continue; }
8550 if ( 0 == strncasecmp( p, "for ", 4 ) ) { p += 4; continue; }
8551 if ( 0 == strncasecmp( p, "the ", 4 ) ) { p += 4; continue; }
8553 if ( 0 == strncasecmp( p, "from ", 5 ) ) { p += 5; continue; }
8554 if ( 0 == strncasecmp( p, "with ", 5 ) ) { p += 5; continue; }
8556 /* TODO: spanish and french equivalents to above */
8557 /* use optional compile until stations start sending proper lang code */
8558 #ifdef USE_ASCII_XLATE
8559 ascii_xlate( c8859_1, (unsigned char *)p );
8560 /* but the spanish stations here send EITs with [eng] for lang! ha! */
8561 if ( 0 == strncasecmp( lang, "spa", 3 )) {
8562 /* SPA */
8563 if ( 0 == strncasecmp( p, "a ", 2 ) ) { p += 2; continue; }
8564 if ( 0 == strncasecmp( p, "y ", 2 ) ) { p += 2; continue; }
8566 if ( 0 == strncasecmp( p, "de ", 3 ) ) { p += 3; continue; }
8567 if ( 0 == strncasecmp( p, "el ", 3 ) ) { p += 3; continue; }
8568 if ( 0 == strncasecmp( p, "en ", 3 ) ) { p += 3; continue; }
8569 if ( 0 == strncasecmp( p, "es ", 3 ) ) { p += 3; continue; }
8570 if ( 0 == strncasecmp( p, "la ", 3 ) ) { p += 3; continue; }
8571 if ( 0 == strncasecmp( p, "lo ", 3 ) ) { p += 3; continue; }
8572 if ( 0 == strncasecmp( p, "no ", 3 ) ) { p += 3; continue; }
8573 if ( 0 == strncasecmp( p, "un ", 3 ) ) { p += 3; continue; }
8575 /* interferes with "las vegas" until lang from caller done */
8576 /* if ( 0 == strncasecmp( p, "las ", 4 ) ) { p += 4; continue; } */
8578 if ( 0 == strncasecmp( p, "con ", 4 ) ) { p += 4; continue; }
8579 if ( 0 == strncasecmp( p, "del ", 4 ) ) { p += 4; continue; }
8580 if ( 0 == strncasecmp( p, "mas ", 4 ) ) { p += 4; continue; }
8581 if ( 0 == strncasecmp( p, "por ", 4 ) ) { p += 4; continue; }
8582 if ( 0 == strncasecmp( p, "que ", 4 ) ) { p += 4; continue; }
8583 if ( 0 == strncasecmp( p, "sin ", 4 ) ) { p += 4; continue; }
8585 if ( 0 == strncasecmp( p, "para ", 5 ) ) { p += 5; continue; }
8588 /* TODO: get samples to test with */
8589 if ( 0 == strncasecmp( lang, "fre", 3 )) {
8591 #endif
8593 z1 = strlen(p);
8594 for ( z = 0; z < z1; z++ ) {
8595 /* equal to or past the destination limit? */
8596 if ( (r-d) >= (c-1) ) {
8597 *r = 0; /* null term uses one char */
8598 return;
8600 t = p[z];
8602 if ( 0 == t ) break; /* term? */
8603 if ( ' ' == t ) break; /* end of word? */
8605 /* NOTE: There are certain characters you don't want to use for filenames,
8606 * if you want a hope of using the console. Maybe could filter by those.
8607 * Skip non alphanumeric. this is where multi-lingual gets screwed up.
8608 * As long as it screws it up consistently it shouldn't be a real problem,
8609 * except for people who want their capture names WITH special chars.
8612 /* if ( 0 == isalnum( p[z]) ) continue; / * isalnum too slow? */
8613 if (((t < '0') || (t > '9'))
8614 && ((t < 'A') || (t > 'z'))
8615 && ((t < 'a') || (t > 'z'))) continue;
8617 *r = t; /* copy char */
8618 r++; /* next char */
8620 p += z; /* next word */
8621 wc++; /* counts */
8623 *r = 0; /* term */
8626 /* spam event test
8627 config file /etc/atscap/atscap.spam
8628 e is pointer to the event to be checked
8630 Returns:
8631 -1 is not on spam list
8632 0-n is spam list index if found
8634 /* static */
8636 test_spam_event ( struct event_s *e1 )
8638 int i, j, k;
8639 char c1, c2;
8640 struct spam_s *s;
8641 char en[ SPAM_NAME_MAX ];
8643 while (EBUSY == pthread_mutex_trylock( &spam_mutex ))
8644 nanosleep( &atomic_sleep, NULL );
8646 /* Walk the spam list looking for truncated matches between s->name and en */
8647 event_truncate( en, e1->name, 2, sizeof( en ));
8649 /* lowercase */
8650 c1 = *en;
8651 if ((c1 >= 'A') && (c1 <= 'Z')) c1 |= 0x20; /* set bit 5 */
8652 k = -1;
8653 for (i = 0; i < spam_idx; i++) {
8654 s = &spam_list[i];
8655 c2 = *s->name;
8656 /* lowercase */
8657 if ((c2 >= 'A') && (c2 <= 'Z')) c2 |= 0x20; /* set bit 5 */
8658 /* Compare redux to matching first letter, saves calls to strncasecmp. */
8659 if (c1 != c2) continue;
8661 j = strncasecmp( s->name, en, s->len );
8663 /* 0 is spam */
8664 if (0 == j) {
8666 /* keep last time spam matched */
8667 // s->st = utsnow;
8668 /* keep last event time that spam matched */
8669 s->st = e1->st;
8671 k = i;
8672 break;
8676 pthread_mutex_unlock( &spam_mutex );
8677 return k;
8681 /* strip timer or search list flags, : # $ @ . and , */
8682 /* only do this to copies of timer and search names, not the originals */
8683 /* static */
8684 void
8685 strip_timer_flags ( char *n )
8687 char *p;
8688 p = strchr( n, ':');
8689 if (NULL != p) *p = 0;
8690 p = strchr( n, '$');
8691 if (NULL != p) *p = 0;
8692 p = strchr( n, '@');
8693 if (NULL != p) *p = 0;
8694 p = strchr( n, '.');
8695 if (NULL != p) *p = 0;
8696 p = strchr( n, ',');
8697 if (NULL != p) *p = 0;
8700 /* returns search list index that matches t, or -1 if no match */
8701 /* used for search duplicate check and search delete */
8702 /* static */
8704 find_search_event ( char *t, int wdb, int chan, char *caller )
8706 struct search_s *s;
8707 char n[ SEARCH_NAME_MAX ],
8708 r[ SEARCH_NAME_MAX ],
8709 c1, c2;
8710 int i, j;
8712 if (NULL == t) return -1;
8713 if (0 == *t) return -1;
8714 if (0 == search_idx) return -1;
8716 /* copy before modify */
8717 astrncpy( r, t, sizeof(r) );
8718 strip_timer_flags( r );
8720 advrlog( LOG_INFO, "%s -> %s %d r %s w %d c %d",
8721 caller, WHO, search_idx, r, wdb, chan);
8723 c1 = *r;
8724 if ((c1 >= 'A' && c1 <='Z')) c1 |= 0x20;
8726 for (i = 0; i < search_idx; i++) {
8727 s = &search_list[i];
8728 c2 = *s->name;
8729 if ((c2 >= 'A' && c2 <='Z')) c2 |= 0x20;
8731 /* only check if matching first letter */
8732 if (c1 != c2) continue;
8734 astrncpy( n, s->name, sizeof(n) );
8735 strip_timer_flags( n );
8737 /* search on the name without flags, with stripped t limiting length */
8738 j = strncasecmp( n, r, strlen( r ) );
8740 advrlog( LOG_INFO, "%s -> %s %d %d %s %s",
8741 caller, WHO, i, j, r, n);
8743 /* does the name match? */
8744 if (0 == j) {
8746 advrlog( LOG_INFO, "%s -> %s sidx %d n %s w %d c %d",
8747 caller, WHO, search_idx, n, wdb, chan);
8749 /* no weekday or channel limit on the search list entry? */
8750 if ((0 == s->days) && (0 == s->chan)) return i;
8752 /* no weekday limit? */
8753 if (0 == wdb) {
8754 /* no channel limit? */
8755 if (0 == chan) return i;
8756 /* has channel match? */
8757 if (chan == s->chan) return i;
8760 /* weekday limit and at least one weekday bit matches? */
8761 if (0 != (wdb & s->days)) {
8762 /* no channel limit? */
8763 if (0 == s->chan) return i;
8764 /* has channel match? */
8765 if (chan == s->chan) return i;
8767 /* name matched, but weekday bits or channel didn't match, try next */
8770 return -1;
8773 /* delete search event is only caller */
8774 /* return index of volatile timer that matches name to t, or return -1 */
8775 /* static */
8777 find_event_timer ( char *p )
8779 int i;
8780 struct qtimer_s *t;
8781 char n[ TIMER_NAME_MAX ],
8782 s[ TIMER_NAME_MAX ],
8783 c1, c2;
8785 if (NULL == p) return -1;
8786 if (0 == *p) return -1;
8788 /* search name */
8789 astrncpy( s, p, sizeof(s) );
8790 strip_timer_flags( s );
8792 c1 = *s;
8793 if ((c1 >= 'A' && c1 <='Z')) c1 |= 0x20;
8795 for (i = 0; i < timer_idx; i++) {
8796 t = &timer[i];
8798 /* skip current streaming timer */
8799 if (T_STREAM == t->qstat) continue;
8801 /* skip weekday timer */
8802 if (0 != t->days) continue;
8804 /* skip search entry */
8805 if (T_SEARCH == t->qstat) continue;
8807 c2 = *t->name;
8808 if ((c2 >= 'A' && c2 <='Z')) c2 |= 0x20;
8809 if (c1 != c2) continue;
8811 astrncpy( n, t->name, sizeof(n) );
8813 /* timer name only, not the flags */
8814 strip_timer_flags( n );
8816 /* return index if match found. timer name length limits match */
8817 if (0 == strncasecmp( n, s, strlen( s ) )) {
8818 advrlog( LOG_INFO, "%s %d %s %s", WHO, i, s, n);
8819 return i;
8823 /* no matching volatile timer found */
8824 return -1;
8827 /* return index of search entry match, or -1 if no match */
8828 /* static */
8830 find_search_timer ( char *m )
8832 int i, j;
8833 struct qtimer_s *t;
8834 char n[ TIMER_NAME_MAX ],
8835 s[ TIMER_NAME_MAX ],
8836 c1, c2;
8838 astrncpy( s, m, sizeof(s) );
8839 strip_timer_flags( s );
8841 c1 = *s;
8843 /* first char to lower case */
8844 if ((c1 >= 'A' && c1 <='Z')) c1 |= 0x20;
8846 for (i = 0; i < timer_idx; i++) {
8847 t = &timer[i];
8849 /* skip non search entries */
8850 if (T_SEARCH != t->qstat) continue;
8852 /* skip if first char doesn't match. tolower() not used, depends on locale */
8853 c2 = *t->name;
8855 /* first char to lower case */
8856 if ((c2 >= 'A' && c2 <= 'Z')) c2 |= 0x20;
8857 if (c1 != c2) continue;
8859 astrncpy( n, t->name, sizeof(n) );
8861 /* strip any flags from search entry */
8862 strip_timer_flags( n );
8864 /* search entry name limits match length */
8865 j = strncasecmp( n, s, strlen( n ));
8866 advrlog( LOG_INFO, "%s %d %d %s %s %d", WHO, i, j, s, n );
8867 if (0 == j) return i;
8869 return -1;
8872 /* return timer index if timer matches epg event e, else return -1 */
8873 /* static */
8875 find_timer_epg ( struct event_s *e )
8877 int i;
8878 struct qtimer_s *t;
8880 if (timer_idx < 1) return -1; /* nothing to do */
8881 for (i=0; i<timer_idx; i++) {
8882 t = &timer[i];
8884 /* skip search entries, non-channel and non-program match */
8885 if (T_SEARCH == t->qstat) continue;
8886 if (t->chan != e->chan) continue;
8887 if (t->pn != e->pn) continue;
8889 /* fuzzy start time match in case started mid-program */
8890 if (t->start >= e->st)
8891 if (t->start < (e->st + e->ls))
8892 return i;
8893 #if 0
8894 /* exact timer match */
8895 if (t->start != e->st) continue;
8896 if (t->len != e->ls) continue;
8897 return i;
8898 #endif
8900 return -1;
8903 /* TESTME: is this redundant? other code elsewhere does the same? */
8904 /* update timer description from epg if timer matches epg event */
8905 /* static */
8906 void
8907 update_timer_epg( void )
8909 int i, j, k;
8910 struct qtimer_s *t;
8911 struct event_s *e;
8913 for (i = 0; i < pgm.idt; i++) {
8914 j = pg[i];
8916 if (j >= PGZ) continue;
8918 k = find_timer_epg( &epg[j] );
8920 if (-1 == k) continue;
8922 t = &timer[k];
8923 e = &epg[j];
8925 if (0 == *t->ename) astrncpy( t->ename, e->name, PNZ);
8926 if (0 == *t->edesc) astrncpy( t->edesc, e->desc, PDZ);
8930 /* parse automatic search timer: name, program, channel and weekday bits
8931 adds search entry to timer list with qstat set to T_SEARCH
8932 does not add duplicate search events
8934 format:
8935 Aname[.pn][:ch[:days]]
8936 name is required, .pn is optional for same event on two VCs.
8937 ch is optional channel filter for same event on two PTCs.
8938 days is optional weekday filter to avoid weekend re-runs
8940 /* static */
8941 void
8942 parse_search_event ( char *c )
8944 int i, ch, wd, pn;
8945 char *p[3], *r;
8946 struct qtimer_s *t;
8947 struct search_s *s;
8949 // if (0 != arg_dummy) return;
8951 if (NULL == c) return;
8952 if (0 == *c) return;
8954 advrlog( LOG_INFO, "%s %s", WHO, c);
8956 if (search_idx >= SEARCH_LIST_MAX) {
8957 dvrlog( LOG_INFO,"increase SEARCH_LIST_MAX for more searches");
8958 return;
8961 if (timer_idx >= TIMER_LIST_MAX) {
8962 dvrlog( LOG_INFO,"increase TIMER_LIST_MAX for more searches");
8963 return;
8966 wd = ch = pn = 0;
8968 /* this one is variable so don't check build_args return */
8969 build_args( p, 4, c, ':', WHO);
8971 /* need search name at the very least */
8972 if (NULL == p[0]) return;
8973 if (0 == *p[0]) return;
8975 /* has program number? */
8976 r = strrchr( p[0], '.' );
8977 if (NULL != r) {
8979 /* remove program number from name, extracting program number */
8980 *r = 0;
8981 r++;
8982 pn = atoi( r );
8985 /* rest are optional parameters */
8986 if (NULL != p[1]) {
8987 ch = atoi(p[1]);
8988 if ( (ch < 0) || (ch > (ptc_max-1)) )
8989 ch = 0; /* silent fail */
8991 if (NULL != p[2]) wd = 0x7F & abtou(p[2]);
8993 /* search name duplicate check */
8994 i = find_search_event( p[0], wd, ch, WHO );
8996 advrlog(LOG_INFO,"%s find %d %s ch %s pn %d, wd %s",
8997 WHO, i, p[0], p[1], pn, p[2]);
8999 /* do not add duplicates */
9000 if (-1 != i) return;
9002 advrlog( LOG_INFO, "%s adds %s ch %d pn %d wd %d ",
9003 WHO, p[0], ch, pn, wd);
9005 /* add to search list */
9006 s = &search_list[ search_idx ];
9008 memset( s, 0, sizeof(struct search_s) );
9009 astrncpy( s->name, p[0], SEARCH_NAME_MAX );
9010 s->chan = ch;
9011 s->days = wd;
9012 s->pn = pn;
9013 search_idx++;
9014 advrlog( LOG_INFO, "add to search list %s ch %d pn %d wd %d",
9015 s->name, s->chan, s->pn, s->days );
9017 /* add to timer list */
9018 t = &timer[ timer_idx ];
9020 memset( t, 0, sizeof(struct qtimer_s) );
9021 astrncpy( t->name, p[0], TIMER_NAME_MAX );
9022 t->qstat = T_SEARCH; /* search status */
9023 t->start = -1; /* puts at end of timer sort */
9024 t->days = wd;
9025 t->chan = ch;
9026 t->pn = pn;
9027 timer_idx++;
9028 advrlog( LOG_INFO, "%s %s ch %d pn %d st %d wd %d",
9029 WHO, t->name, t->chan, t->pn, t->start, t->days );
9033 /* move timers after tq down to replace tq, decrement timer count */
9034 /* this does not need to know if it is a search timer */
9035 /* static */
9036 void
9037 delete_timer ( int i, char *caller )
9039 int m;
9040 struct sig_s *s;
9041 struct qtimer_s *t, *u;
9042 s = &ptc[ cap_chan ].sig;
9043 if ( (i < 0) || (i >= timer_idx) ) {
9044 dvrlog( LOG_INFO, "%s -> %s no timer %d", caller, WHO, i);
9045 return; /* sanity check */
9048 t = &timer[i];
9050 /* stop capture if deleting the current timer */
9051 if ( (CAP_TIME == cap_now) && (i == timer_rec) ) {
9052 int fail;
9053 fail = cap_fail; /* keep the failure reason */
9054 stop_capture( WHO, fail );
9057 /* don't auto-start another search if user deletes currently active timer 0? */
9058 s->lzpgt = t->start; /* last zapped timer start for this channel */
9060 advrlog( LOG_INFO, "%s (%s) %d %s", WHO, caller, i, t->name);
9062 /* if current is not last timer, move next other(s) down on top of current */
9063 if (i < (timer_idx-1)) {
9064 for (m = i; m < (timer_idx - 1); m++) {
9065 t = &timer[m];
9066 u = &timer[m+1];
9067 memcpy( t, u, sizeof(struct qtimer_s) );
9070 timer_idx--; /* one less timer now */
9072 /* delete needs to redraw list */
9073 refresh.timers = 1;
9074 advrlog( LOG_INFO, "timer %d deleted\n", i);
9078 /* http uses these two for trunc event name add/remove from search list */
9080 /* add search list entry p, returns 0: ok(or dup), -1 list full
9081 caller is expected to sort and save the search list config
9083 /* static */
9085 add_search ( char *p, int ch )
9087 struct qtimer_s *t;
9088 struct search_s *s;
9090 if (SEARCH_LIST_MAX <= search_idx) {
9091 dvrlog( LOG_INFO, "search list full, increase SEARCH_LIST_MAX");
9092 return -1;
9095 s = &search_list[ search_idx ];
9097 /* clear when adding, to reset rest of search struct (ch pn daybits) */
9098 memset( s, 0, sizeof(struct search_s));
9099 astrncpy( s->name, p, SEARCH_NAME_MAX );
9100 s->chan = ch;
9102 search_idx++;
9103 if (TIMER_LIST_MAX <= timer_idx) {
9104 dvrlog( LOG_INFO, "timer list full, increase TIMER_LIST_MAX");
9105 return -1;
9108 t = &timer[ timer_idx ];
9110 /* no duplicates */
9111 if (-1 == find_search_timer( p )) {
9112 astrncpy( t->name, p, TIMER_NAME_MAX );
9113 t->chan = ch;
9114 t->days = 0;
9115 t->start = -1;
9116 t->qstat = T_SEARCH;
9117 timer_idx++;
9120 return 0;
9123 /* delete an entry in search_list array */
9124 /* static */
9125 void
9126 delete_search ( int n )
9128 int i;
9129 struct search_s *s1, *s2;
9131 if (0 == search_idx) return;
9132 if (n >= search_idx) return;
9134 /* not last entry in main search list? */
9135 if (n < (search_idx - 1)) {
9137 /* move items from next down to overwrite current and next if more than 1 */
9138 for (i = n; i < (search_idx - 1); i++) {
9139 s1 = &search_list[i];
9140 s2 = &search_list[i+1];
9142 /* use memcpy to get all struct elements so doesn't scramble chan/daybits */
9143 memcpy( s1, s2, sizeof( struct search_s ) );
9147 /* if it was last entry, it's gone now, else rest moved down to cover it */
9148 search_idx--;
9149 if (search_idx < 0) search_idx = 0;
9152 /* timer[tm] lookup removes timer tm name from search list then removes
9153 Search event timer tm and then removes any matching volatile timers.
9154 There are 3 search items:
9155 search_list[search]
9156 timer_list[search] (magenta at bottom of console timer list)
9157 timer_list[found] volatile timer, may be more than one
9159 Don't remove timer0 if it's active even though it matches, but
9160 do remove the rest of them. User can delete the active one.
9161 This is error prevention so user can accidentally remove search,
9162 but not lose the current capture that matched the search.
9164 /* static */
9165 void
9166 delete_search_event ( int tm, char *caller ) {
9167 int j;
9168 char n[32];
9169 struct qtimer_s *t;
9171 t = &timer[ tm ];
9172 /* this should only be a search timer not a regular timer */
9173 if (T_SEARCH != t->qstat) return;
9175 astrncpy( n, t->name, sizeof(n) );
9176 n[16] = 0; /* search timer gets truncated to match */
9178 /* remove any search entries that match first letters */
9179 j = find_search_event( n, 0, 0, WHO );
9181 /* search list entry gets deleted */
9182 if (-1 < j) delete_search( j );
9184 /* search_list[] done, now do timer[] search match */
9186 /* reset test for multiple search loop, but should really only find one */
9187 j = 0;
9188 /* remove search event(s) on timer list with matching length */
9189 while( -1 != j ) {
9190 j = find_search_timer( n );
9191 advrlog( LOG_INFO, "find search timer %d", j);
9192 if (-1 != j) {
9193 delete_timer( j, "remove search" );
9197 /* reset test for multiple timer search match loop */
9198 j = 0;
9200 /* timer search entry done, now remove the matching timers,
9201 but leave timer0 match if it is active */
9203 /* remove matching timer event(s) */
9204 while( -1 != j ) {
9205 j = find_event_timer( n );
9206 advrlog( LOG_INFO, "find event timer %d", j);
9208 /* don't delete active found timer 0 or -1 not found */
9209 if (j > -1) {
9210 t = &timer[j];
9212 /* weekday bits means manual timer. only delete if no weekday bits */
9213 if (0 == t->days) {
9214 if (j != 0) {
9215 delete_timer( j, "remove volatile" );
9216 } else {
9218 /* current timer? use stop or zap (stop + delete) for current timer */
9219 if ( T_STREAM != t->qstat )
9220 delete_timer( j, "remove volatile" );
9226 /* want timer list to redraw from top in case user scrolled off page 0 */
9227 refresh.timers = 1;
9228 timer_offset = 0;
9231 /*********************************************************** PAT PMT VID AUD
9232 * cap pids are: PAT, PMT, VID, AUD
9233 * returns 0 if match, -1 otherwise
9235 /* static */
9237 find_cap_pid ( unsigned int pid )
9239 int i;
9241 for (i = 0; i < 4; i++)
9242 if ( pid == cap_pids[i] )
9243 return 0;
9245 return -1;
9248 /* build vc text, et al, calls this:
9249 search all of vc[] until found matching program number
9250 returns vc[] index or -1 for failed to find a match
9252 /* static */
9254 find_vc_pgm ( int pgn, char *caller )
9256 int i;
9257 for (i=0; i< vc_ncs; i++) if ( pgn == vc[i].pn ) return i;
9258 return -1;
9261 /* find vc# for this Video ES PID */
9263 find_vc_vespid ( unsigned short pid )
9265 int i, j;
9267 for (i=0; i< vc_ncs; i++)
9268 for (j = 0; j < vc[i].pmtes; j++)
9269 if (2 == vc[i].estype[j])
9270 if (pid == vc[i].espid[j])
9271 return i;
9272 return -1;
9275 /* find pa# for this Video ES PID */
9277 find_pa_vespid ( unsigned short pid )
9279 int i, j;
9281 for (i=0; i< pat_ncs; i++)
9282 for (j = 0; j < pa[i].pmtes; j++)
9283 if (2 == pa[i].estype[j])
9284 if (pid == pa[i].espid[j])
9285 return i;
9286 return -1;
9289 #if 1
9290 /* parse PMT calls this:
9291 Search pa[] until found matching program number.
9292 Returns pa[] index or -1 for failed to find a match.
9293 This index will be VC index if no VCT found.
9295 /* static */
9297 find_pa_pgm ( int pgn, char *caller )
9299 int i;
9301 for (i=0; i< pat_ncs; i++) {
9302 if ( pgn == pa[i].pn ) {
9303 advrlog( LOG_INFO, "%s finds pa[%d].pn %04X",
9304 caller, i, pgn );
9305 return i;
9308 return -1;
9310 #endif
9312 /* see if pid is a pmt on the program association list, return index or -1 */
9313 /* static */
9315 find_pa_pmtpid ( short pid )
9317 int i;
9318 for (i = 0; i < pat_ncs; i++) if (pid == pa[i].pmtpid) return i;
9319 return -1;
9323 #if 0
9324 /* not used */
9326 /* parse VCT called this, using find_vc_pgm instead:
9327 search vc[] until found matching program number (may be obsoleted)
9328 returns vc[] index or -1 for failed to find a match
9330 /* static */
9332 find_vct_pgm ( int pgm, char *caller )
9334 int i;
9336 for (i=0; i< vct_ncs; i++) {
9337 if ( pgm == vc[i].pn ) {
9338 advrlog( LOG_INFO, "%s finds vc[%d].pn %04X",
9339 caller, i, pgm );
9340 return i;
9343 return -1;
9345 #endif
9348 /* if single program capture, set the pids based on cap_pn cap_vc cap_va */
9349 /* static */
9350 void
9351 build_cap_pids ( char *caller )
9353 int svp, svc, sva;
9354 struct sig_s *s;
9355 s = &ptc[cap_chan].sig;
9356 svp = cap_pn;
9357 sva = cap_va;
9358 if (CAP_NONE == cap_now) {
9359 svp = s->pn;
9360 sva = s->va;
9362 svc = find_vc_pgm( svp, WHO );
9363 if (-1 == svc) svc = find_pa_pgm( svp, WHO );
9365 if (0 == vc_ncs) return; /* if this is zero, no pat+pmt or vct found */
9366 if (-1 == svp) return; /* full cap does nothing */
9367 if (-1 == svc) return; /* full cap or vc not found yet does nothing */
9369 if (svp > 0)
9370 if (-1 == sva)
9371 sva = 0; /* -1 and zero select audio 0 in VC cap */
9373 /* advrlog( LOG_INFO, "%s <- %s cap p %d v %d a %d",
9374 WHO, caller, svp, svc, sva);
9376 cap_pidx = 0;
9377 /* add PAT to list, PAT is always PID 0000 */
9378 cap_pids[ cap_pidx++ ] = 0;
9380 /* add PMT to list */
9381 cap_pids[ cap_pidx++ ] = vc[ svc ].pmtpid; /* add PMT */
9383 /* video always first, after vc ES pid sort enforced with KTRK fix */
9384 cap_pids[ cap_pidx++ ] = vc[ svc ].espid[ 0 ];
9386 /* audio always after first ES (video) */
9387 cap_pids[ cap_pidx++ ] = vc[ svc ].espid[ 1 + sva ];
9392 /* persistent vc select: called by channel select */
9393 /* cap_chan has current capture channel */
9394 /* static */
9395 void
9396 set_vc ( char *caller )
9398 struct sig_s *s;
9399 s = &ptc[ cap_chan ].sig;
9401 if (0 != arg_scan) return;
9403 if ( (cap_chan < 0) || (cap_chan >= ptc_max) ) {
9404 advrlog( LOG_INFO, "%s(%s) can't do channel %d",
9405 WHO, caller, cap_chan);
9406 return; /* sanity limit */
9409 /* user cap uses current cfg pn and pa setting */
9410 if (CAP_USER == cap_now) {
9412 /* none and info cap read from the config (show virt changes it)
9413 user and timer cap can only change cap_va if single program capture
9415 cap_pn = s->pn;
9416 cap_vc = find_vc_pgm( cap_pn, WHO );
9417 cap_va = s->va;
9419 advrlog( LOG_INFO, "%s1 <- %s ch %d pn %d vc %d va %d",
9420 WHO, caller, cap_chan, cap_pn, cap_vc, cap_va);
9422 advrlog( LOG_INFO, "sp %d sv %d sa %d", s->pn, s->vc, s->va);
9425 /* but still need this done even in user/timer cap for audio select */
9426 build_cap_pids( WHO );
9431 subtract timespec a from timespec b and put result in timespec d
9432 a should be start time, b should be stop time
9434 /* static */
9435 void
9436 time_diff ( struct timespec *d, struct timespec *a, struct timespec *b)
9438 time_t sec;
9440 /* FIXME: portability issue? only long in the whole program */
9441 long nsec;
9443 sec = b->tv_sec;
9444 nsec = b->tv_nsec;
9445 if (a->tv_nsec > nsec) { /* borrow */
9446 nsec += 1000000000;
9447 sec--;
9449 d->tv_nsec = nsec - a->tv_nsec;
9450 d->tv_sec = sec - a->tv_sec;
9454 here's yet another version of my XenoTerm(c)1980 terminal word wrapper,
9455 it's nearly as concise in C as it was in Z-80 assembler, 26 years ago.
9456 used for dump of program guide to text file.
9457 f is output file, s is output string. cl is column limit of 78
9459 /* static */
9460 void
9461 word_wrap_indent ( FILE *f, char *s )
9463 char *t;
9464 char indent[80];
9466 int ts, ns, cc, cl, i, i1;
9468 if (NULL == f) return;
9470 ts = cc = ns = 0;
9472 /* column limit, needs to account for non word chars after tab */
9473 cl = 78;
9475 memset( indent, 0, sizeof(indent));
9476 t = strchr( s, '|'); /* first | is tab stop */
9477 if (t != NULL) {
9478 ts = t - s;
9479 memset( indent, ' ', ts );
9482 fprintf( f, "\n" );
9483 i1 = strlen(s);
9484 for (i = 0; i < i1; i++) {
9485 /* if this char not a space, find next space */
9486 if (s[i] != ' ') {
9487 t = strchr( &s[i], ' ');
9488 if (t != NULL) {
9489 ns = t - &s[i]; /* how many bytes until next space? */
9490 if ((ns + cc) >= cl) /* does it go over column limit? */
9492 fprintf( f, "\n%s| ", indent); /* newline plus indent */
9493 cc = ts+4; /* 4 chars after indent */
9494 fprintf( f, "%c", s[i]);
9495 continue; /* needed to skip space */
9499 fprintf( f, "%c", s[i]);
9500 cc++; /* bump current column count */
9504 /* build pg epg calls this to attempt to organize the list:
9506 NOTE:
9507 Stations that get the EIT order wrong are not A/65b compliant.
9508 Sorting these should not be necessary. It is not a perfect world.
9509 eit locks has other examples of broadcasters [FOX] getting it wrong.
9510 It would be a Bad Thing to see bad [FOX] practices propagated.
9512 sort pgx is two functions in one:
9513 sort pgx by e[pgx].pn to arrange in program order
9514 sort pgx again by e[n].st to arrange in pgm+chrono order
9516 This should bunch all pn's together first for non-compliant stations,
9517 then st's in chronological order, so dump epg day hrefs work correctly.
9519 NOTE: This is somewhat less costly than moving entire event structs.
9522 /* static */
9523 void
9524 sort_pgx (struct event_s *e, struct pgm_s *p, short *pgx, char *caller)
9526 int i, j, k;
9528 /* sort pgx by first order program number */
9529 for ( i = 0; i < ( p->idx - 1); i++ ) {
9530 for ( j = i + 1; j < p->idx; j++ ) {
9531 if ( e[ pgx[ i ]].pn > e[ pgx[ j ]].pn ) {
9532 k = pgx[j];
9533 pgx[j] = pgx[i];
9534 pgx[i] = k;
9539 /* if program number matches, sort pgx by second order start time */
9540 for ( i = 0; i < ( p->idx - 1); i++ ) {
9541 for ( j = i + 1; j < p->idx; j++ ) {
9542 if ( e[ pgx[ i ]].pn == e[ pgx[ j ]].pn ) {
9543 if ( e[ pgx[ i ]].st > e[ pgx[ j ]].st ) {
9544 k = pgx[j];
9545 pgx[j] = pgx[i];
9546 pgx[i] = k;
9553 /* Abstracted pointer version of old build pg epg to be a bit more flexible.
9554 Builds compressed pgx index from sparse epg in e and filter settings in p.
9556 Guides that have the full 16 days will not realize any gain, and
9557 this code may be simplified once all stations send 16 days worth.
9558 There are enough reserved table id's to send 32 days worth.
9560 Sets p->idx, p->ett to found counts.
9562 Event inclusion/exclusion control via p->:
9563 p->age Filter aged events
9564 p->hide Filter spam events
9565 p->find Filter timer name matches
9567 e points to event_s epg[PGZ]
9568 p points to pgm_s filters, counters
9569 idx points to short pg[PGZ] where in epg[] event can be found
9570 caller points to text of what called this.
9572 /* static */
9573 void
9574 build_pg_epg (struct event_s *e, struct pgm_s *p, short *pgx, char *caller)
9576 int j;
9577 int letmid = 0; /* last etmid seen */
9578 struct event_s *e1;
9580 advrlog( LOG_INFO, "EPG%02d %s ( %s )", pgm.chan, WHO, caller );
9582 p->idx = 0; /* pgm guide index current select mode hide/age/match */
9583 p->idt = 0; /* pgm guide index total */
9584 p->ett = 0; /* pgm guide description count */
9586 /* TODO: sort pgx by start time */
9588 /* create new program count and index every loop */
9589 for (j = 0; j < PGZ; j++) {
9590 pgx[ j ] = -1; /* disabled until proven otherwise */
9591 e1 = &e[j];
9593 /* Async operation on live epg[] skips locked EITs until sort_eit_epg done */
9594 #if 1
9595 if (CAP_NONE != cap_now) {
9596 if (p == &pgm) {
9597 if (0 != epg_locks[ j / PEZ ])
9598 continue;
9600 // TESTME: try a wait instead, it shouldn't be very long?
9601 // FIXME: nope, freezes up, must need a clear somewhere
9602 // while (0 != epg_locks[ j / PEZ ])
9603 // nanosleep( &atomic_sleep, NULL );
9606 #endif
9608 if (0 != e1->etmid) {
9610 /* idt has total entries vs current filter list idx */
9611 p->idt++;
9613 if ( letmid == e1->etmid ) continue; /* skip duplicates */
9614 letmid = e1->etmid; /* current for next check */
9616 /* if pn set, and doesn't match event, skip it */
9617 if ( (p->pn > 0) && (p->pn != e1->pn) ) continue;
9619 /* skip: 0 hidden, 65535 NTSC */
9620 if ( (0 == e1->pn) || (e1->pn == 65535) ) continue;
9622 /* if hide spam enabled, look for spam to remove from list */
9623 if (0 != p->hide)
9624 if ( test_spam_event( e1 ) >= 0 ) continue;
9626 /* skip aged out programs */
9627 if (0 != p->age) {
9628 if ( utsnow > (e1->st + e1->ls) ) {
9629 continue;
9633 /* skip what doesn't match timer list, might be a cpu pig if called too much */
9634 if (0 != p->find) {
9635 if ( -1 == find_timer_epg( e1 )) {
9636 continue;
9640 /* count events that passed above tests */
9641 pgx[ p->idx ] = j;
9642 p->idx++;
9644 /* count non blank event descriptions too */
9645 if (0 != *e1->desc) p->ett++;
9649 /* after the above is filtered, send it through pgm + st sort
9650 to get it ready for dump epg html and console epg display
9652 sort_pgx( e, p, pgx, WHO );
9655 advrlog(LOG_INFO, "EPG%02d %s( %s ) pgm idx %d idt %d",
9656 pgm.chan, WHO, caller, p->idx, p->idt);
9660 /* These always display and always get updated when channel changes. */
9661 /* TODO: Make it use what is in pgm_s array instead of build epg. */
9662 /* static */
9663 void
9664 show_vc_selection ( void )
9666 struct sig_s *s;
9667 char vcn[4] = "ALL"; /* virtual channel number */
9668 char vca[4] = "ALL"; /* virtual channal audio number */
9669 char vcx[32] = "";
9670 char vcy[32] = "";
9671 int i, svp, svc, sva;
9673 if (0 != arg_scan) return;
9675 if (0 == refresh.vstats) return;
9676 refresh.vstats = 0; /* one shot output redux */
9677 s = &ptc[cap_chan].sig;
9678 // s = &ptc[ scan_pos ].sig;
9681 build_pg_epg( epg, &pgm, pg, WHO);
9682 svp = cap_pn;
9683 sva = cap_va;
9684 if (CAP_NONE == cap_now) {
9685 svp = s->pn;
9686 sva = s->va;
9688 svc = find_vc_pgm( svp, WHO );
9690 if (svc > -1) {
9691 snprintf( vcn, sizeof(vcn), " %1d ", svc );
9692 snprintf( vca, sizeof(vca), " %1d ", sva );
9694 /* use vct channel name info by default if available */
9695 if (vct_ncs > 0) {
9696 snprintf( vcx, sizeof(vcx), "%s", vc[svc].name);
9697 snprintf( vcy, sizeof(vcy), "%s %2d.%d.%d",
9698 s->sid, vc[svc].major, vc[svc].minor, sva );
9700 /* MPEG fallback does not usually have embedded callsigns. resort to cfg */
9701 if ( (vct_ncs < 1) && (pat_ncs > 0) ) {
9702 snprintf( vcx, sizeof(vcx), "%s", s->call);
9703 snprintf( vcx, sizeof(vcx), "%s", s->call);
9704 snprintf( vcy, sizeof(vcy), "%s %2d.%d",
9705 s->sid, s->ptc, svp);
9706 if (sva > 0)
9707 snprintf(vcy, sizeof(vcy), "%s %2d.%d,%d",
9708 s->sid, s->ptc, svp, sva);
9710 } else {
9711 snprintf( vcx, sizeof(vcx), "%s", s->call);
9712 snprintf( vcy, sizeof(vcy), "%s", s->sid);
9715 aprintf( stderr, "%s", dca.vstat); /* set curpos */
9716 aprintf( stderr,
9717 BG "%-7s %-10s Video: " BC "%s"
9718 BG " Audio: " BC "%s "
9719 BW " PIDS: %d.%d " BM CEL,
9720 vcx, vcy, vcn, vca, s->ptc, s->pn );
9722 build_cap_pids( WHO );
9723 if (svc > -1) {
9724 for (i = 0; i < 4; i++) {
9725 if (i > 0) {
9726 if (0 == cap_pids[i]) {
9727 aprintf( stderr, " " );
9728 } else {
9729 aprintf( stderr, " %04X", cap_pids[ i ] );
9731 } else {
9732 aprintf( stderr, " %04X", cap_pids[ i ] );
9735 } else {
9736 aprintf( stderr, " ALL");
9740 /* #define USE_RATE */
9741 /* shows subset of ATSC and MPEG Transport packet stats that are counted */
9742 /* static */
9743 void
9744 show_packet_stats ( void )
9746 /* color codes for each packet stat */
9747 char *ctse, *ctei, *ctsc, *ctss, *cecc; /* transport */
9748 char *catt, *camg, *cavc, *caei, *caet; /* atsc */
9749 char *cats, *cvct, *cmgt, *ceit, *cett;
9750 char *cmpp, *cmpa, *cmpm, *cmvp, *cmap; /* mpeg */
9752 struct sig_s *s;
9753 struct pkt_s *p;
9755 #ifdef USE_RATE
9756 static int ovcv, ovca; /* old values from last time thru */
9757 #endif
9758 int i, vcv, vca; /* bits per second */
9760 s = &ptc[cap_chan].sig;
9761 p = &s->pkt;
9762 if (CAP_NONE != cap_now) p = &pkt;
9764 /* [s] key off. don't display */
9765 if (0 == display_stat) return;
9767 /* one shot flip flop hold down */
9768 if (0 == refresh.pstats) return;
9769 refresh.pstats = 0;
9771 ctse = ctei = ctsc = ctss = cecc = BG;
9773 /* non zeros turn red */
9774 if (p->tserrt > 0) ctse = BR; /* transport error total */
9775 if (p->synert > 0) ctss = BR; /* transport sync error total */
9776 if (p->teiert > 0) ctei = BR; /* transport error indicator */
9777 if (p->tscert > 0) ctsc = BR; /* transport scramble control nz */
9778 if (p->tsccet > 0) cecc = BR; /* transport sync align error */
9780 /* erase stat area, it is 80x5 */
9781 for (i = 0; i < 5; i++)
9782 aprintf( stderr,"\033[%d;1H" CEL, dca.tstatn + i );
9784 aprintf( stderr, BN DCARC
9785 "%sTSER: %9d "
9786 "%sSYN: %9d "
9787 "%sTEI: %9d "
9788 "%sSCR: %9d "
9789 "%s CC: %9d" BN
9790 /* "%sALN: %8d\n" / same as synert */
9791 , dca.tstatn + 0
9792 , (columns - 80) >> 1
9793 , ctse, p->tserrt
9794 , ctss, p->synert
9795 , ctei, p->teiert
9796 , ctsc, p->tscert
9797 , cecc, p->tsccet
9798 /* , caln, p->alignt / same as synert */
9801 /* ATSC packets get cyan unless error. 0 is considered error */
9802 catt = camg = cavc = caei = caet = BC;
9803 if (0 == p->atsc) catt = BR;
9804 if (0 == p->mgt ) camg = BR;
9805 if (0 == p->tvct) cavc = BR;
9806 if (0 == p->eit ) caei = BR;
9807 if (0 == p->ett ) caet = BR;
9808 aprintf( stderr, BN DCARC
9809 "%sATSC: %9d "
9810 "%sMGT: %9d "
9811 "%sVCT: %9d "
9812 "%sEIT: %9d "
9813 "%sETT: %9d" CEL BN
9814 , dca.tstatn+1
9815 , (columns - 80) >> 1
9816 , catt, p->atsc
9817 , camg, p->mgt
9818 // , cavc, (0==arg_cable)?p->tvct:p->cvct
9819 , cavc, p->tvct + p->cvct
9820 , caei, p->eit
9821 , caet, p->ett
9824 cats = cvct = cmgt = ceit = cett = BG;
9825 if (p->crcats > 0) cats = BR; /* if any atsc packets bad, its red */
9826 if (p->crctvct > 0) cvct = BR; /* vct is red importance */
9827 if (p->crccvct > 0) cvct = BR; /* vct is red importance */
9828 if (p->crcmgt > 0) cmgt = BM; /* MGT gets magenta but maybe red */
9829 if (p->crceit > 0) ceit = BY; /* EIT are low priority yellow */
9830 if (p->crcett > 0) cett = BY; /* ETT are same or lower */
9832 aprintf( stderr, BN DCARC
9833 "%s CRC: %9d "
9834 "%s %9d "
9835 "%s %9d "
9836 "%s %9d "
9837 "%s %9d" CEL BN
9838 , dca.tstatn+2
9839 , (columns - 80) >> 1
9840 , cats, p->crcats
9841 // , cvct, (0==arg_cable)?p->crctvct:p->crccvct
9842 , cvct, p->crctvct + p->crccvct
9843 , cmgt, p->crcmgt
9844 , ceit, p->crceit
9845 , cett, p->crcett
9848 /* MPEG packets get white */
9849 cmpp = cmpa = cmpm = cmvp = cmap = BW;
9850 if (p->mpeg2 == 0) cmpp = BR;
9851 if (p->pat == 0) cmpa = BR;
9852 if (p->pmt == 0) cmpm = BR;
9853 if (p->vid == 0) cmvp = BR;
9854 if (p->aud == 0) cmap = BR;
9856 if (0 != p->pmtrc) cmpm = BR BL;
9858 aprintf( stderr, BN DCARC
9859 "%sMPEG: %9d "
9860 "%sPAT: %9d "
9861 "%sPMT: %9d" BN " " BW
9862 "%sVID: %9d "
9863 "%sAUD: %9d" BN CEL
9864 , dca.tstatn+3
9865 , (columns - 80) >> 1
9866 , cmpp, p->mpeg2
9867 , cmpa, p->pat
9868 , cmpm, p->pmt
9869 , cmvp, p->vid
9870 , cmap, p->aud
9873 #ifdef USE_RATE
9874 /* VC video and audio get green */
9875 vcv = p->vcvid - ovcv;
9876 vca = p->vcaud - ovca;
9877 ovcv = p->vcvid; /* for next time thru */
9878 ovca = p->vcaud;
9880 vcv *= 1504;
9881 vca *= 1504;
9882 #else
9883 vcv = p->vcvid;
9884 vca = p->vcaud;
9885 #endif
9887 aprintf( stderr, BG DCARC
9888 " CRC: %9d "
9889 " %9d "
9890 " %9d "
9891 #ifdef USE_RATE
9892 /* show current bit rate for single program capture */
9893 " Vbs: %9d "
9894 " Abs: %9d" CEL BN
9895 #else
9896 " VCV: %9d "
9897 " VCA: %9d" CEL BN
9898 #endif
9899 , dca.tstatn+4
9900 , (columns - 80) >> 1
9901 , p->crcmp2, p->crcpat, p->crcpmt
9902 , vcv, vca
9906 /* display legends, optional clear screen */
9907 /* static */
9908 void
9909 show_headers ( int clear_console )
9911 char p[256], *s, *t;
9912 char f[32]; /* cap status field */
9914 if (0 == refresh.headers) return;
9915 refresh.headers = 0;
9918 if (0 != clear_console) aprintf( stderr, CLS );
9920 /* text for failure reason, if any */
9921 s = ""; /* blank text */
9922 switch( cap_fail )
9924 case FAIL_TSYNC:
9925 s = "SYN "; /* loss of sync, failed test sync */
9926 break;
9928 case FAIL_IFIFO:
9929 s = "IFE "; /* input fifo error, error in loop */
9932 if (0 != arg_dummy) {
9933 s = "";
9934 cap_fail = FAIL_NONE;
9937 break;
9939 case FAIL_OFIFO:
9940 s = "OFE "; /* output fifo error, error in loop */
9941 break;
9942 case FAIL_ZAP:
9943 s = "QOS "; /* transport stream errors too high */
9944 break;
9945 case FAIL_NOSPC:
9946 s = "FUL "; /* volume full */
9947 break;
9948 case FAIL_QOS:
9949 s = "QOS "; /* program guide load failed */
9950 break;
9951 case FAIL_AOS:
9952 s = "AOS "; /* no signal */
9953 break;
9956 /* put version or last file opened on screen */
9957 if (0 == strlen(out_name)) {
9958 aprintf( stderr, HOM "%s" BN CEL, header_version);
9959 } else {
9961 /* extract filebase only */
9962 filebase(p, out_name, F_TFILE);
9964 /* if (0 != strlen(p))
9967 /* ok, fail or user zap */
9968 t = " ";
9969 if (0 != cap_fail) t = "FAIL: ";
9970 if (0 != cap_zapped) t = " ";
9972 snprintf( f, sizeof(f)-1, "%s%s%s", s, t, p);
9974 /* cyan for last cap good, red for last cap fail */
9975 /* cap status will handle if cap in progress */
9976 if (CAP_NONE == cap_now)
9977 aprintf( stderr, HOM "%s%-32s" BN CEL,
9978 (cap_fail==0) ? BC : BR, f );
9982 if (CAP_NONE == cap_now) {
9983 if ((D_TIMERS == display_type) || (D_CHANNELS == display_type) ) {
9984 aprintf( stderr, DCARC CEL, 3, 1);
9985 aprintf( stderr, "%s%s", dca.hstat, header_channels );
9987 } else {
9988 aprintf( stderr, header_capture, dca.hstat, "Left" );
9991 if (D_EPG == display_type) return;
9992 if (D_MGT == display_type) return;
9993 if (D_VCT == display_type) return;
9995 /* signal header starts on second line */
9996 if (CAP_NONE == cap_now)
9997 { switch( display_sigtype )
9999 case SIG_STR:
10000 aprintf( stderr, DCA_SIG " Strength%% ");
10001 break;
10003 case SIG_SNR:
10004 aprintf( stderr, DCA_SIG " SNR dB " );
10005 break;
10007 case SIG_FMHZ:
10008 aprintf( stderr, DCA_SIG " Freq Hz " );
10009 break;
10011 case SIG_WLEN:
10012 aprintf( stderr, DCA_SIG " Wavelen\042 " );
10013 break;
10015 #ifdef USE_DVB_EXPERIMENTAL
10016 case SIG_FOHZ:
10017 aprintf( stderr, DCA_SIG " FreqDrift " );
10018 break;
10020 case SIG_BER:
10021 aprintf( stderr, DCA_SIG " BitErr1s " );
10022 break;
10023 #endif
10025 default:
10026 break;
10030 if ( D_TIMERS == display_type ) {
10031 aprintf( stderr, DCA_HEAD4 "%s", header_timers);
10032 aprintf( stderr, BN"%s[?] help", dca.ustat);
10036 /* Parse screen size environment variables and set some values from it.
10037 Falls back to termios if environment variables are not set. */
10038 /* static */
10039 void
10040 test_termsize ( void ) {
10041 int x;
10042 char *p;
10043 struct winsize ttyz;
10045 /* only works when invoked from xterm+bash, not directly invoked */
10046 p = getenv( "LINES" );
10048 /* has LINES, may have rest */
10049 if (p != NULL) {
10050 lines = atoi(p);
10051 p = getenv( "COLUMNS" );
10052 if ( p != NULL) {
10053 columns = atoi(p);
10055 } else {
10057 /* include <termio.h> defines TIOCGWINSZ */
10058 #ifdef TIOCGWINSZ
10059 #warning using Linux TIOCGWINSZ
10060 /* fall back to non-portable non-POSIX Linux-only ioctl for tty */
10061 x = ioctl( 1, TIOCGWINSZ, &ttyz );
10062 if (x == 0) {
10063 lines = ttyz.ws_row;
10064 columns = ttyz.ws_col;
10066 #endif
10069 /* top left is used for capture type and name */
10070 /* top middle is timer usage/volume free status */
10071 /* top right is wall clock, yellow for DST, normal for non-DST */
10072 /* ... (timers, channels, guides) ... */
10073 /* bottom middle above last two lines is packet stats */
10074 /* bottom above last line is virtual channel info */
10075 /* bottom left is how many events on this (virtual) channel */
10076 /* bottom middle is event mask flags */
10077 /* bottom right is ATSC packet process flags and ATSC system time */
10079 memset( &dca, 0, sizeof(dca));
10081 snprintf( dca.sstat, sizeof(dca.sstat), "\033[1;%dH", (columns>>1) - 8 );
10083 /* unix/wall clock top right */
10084 snprintf( dca.wstat, sizeof(dca.wstat), "\033[1;%dH", columns - 20 );
10086 /* [?] help goes under clock */
10087 snprintf( dca.ustat, sizeof(dca.ustat), "\033[4;%dH", columns - 8 );
10089 /* program status position, bottom left */
10090 snprintf( dca.pstat, sizeof(dca.pstat), "\033[%d;1H", lines );
10092 /* program guide mask flags, left of middle of bottom line */
10093 snprintf( dca.mstat, sizeof(dca.mstat), "\033[%d;%dH", lines, ((columns>>1)-2) - 5 );
10095 /* atsc status position, bottom right */
10096 snprintf( dca.astat, sizeof(dca.astat), "\033[%d;%dH", lines, columns - 34 );
10098 /* PID cap status, transport status, ATSC and MPEG status, bottom - 6 */
10099 snprintf( dca.tstat, sizeof(dca.tstat), "\033[%d;%dH", lines-6, ((columns>>1)-40) );
10101 /* VC cap status, callsign video # audio # and PIDs to cap, bottom - 1 */
10102 snprintf( dca.vstat, sizeof(dca.tstat), "\033[%d;%dH", lines-1, ((columns>>1)-40) );
10104 /* capture stat position for headers and time/error/pkt status */
10105 snprintf( dca.cstat, sizeof(dca.cstat), "\033[3;%dH", (columns>>1) - 40 );
10107 /* line 2 headers go to left */
10108 snprintf( dca.hstat, sizeof(dca.hstat), "\033[2;1H");
10110 /* device name */
10111 snprintf( dca.zstat, sizeof(dca.zstat), "\033[2;%dH", columns - 5 );
10113 /* compute how many lines are visible for timer and guide */
10114 dca.tstatn = lines-6;
10116 /* always uses 6 lines, 4 at top 2 at bottom. pkt stats use 5 lines */
10117 user_lines = (lines - 6) - ((0 != display_stat) ? 5:0);
10118 user_lines += (D_CHANNELS == display_type) ? 2:0;
10120 if ( (lines != prow) || (columns != pcol) ) {
10121 show_headers(1); /* CLS */
10123 set_refresh();
10125 prow = lines;
10126 pcol = columns;
10130 /* if event e1 is already on timer list, return 0, else return nz */
10131 /* static */
10133 search_timers ( struct event_s *e1 )
10135 int i;
10136 struct qtimer_s *t;
10137 char en[ TIMER_NAME_MAX], /* truncated EPG name */
10138 tn[ TIMER_NAME_MAX]; /* truncated timer name */
10140 if (NULL == e1) return 0;
10142 /* truncate epg name */
10143 event_truncate( en, e1->name, 2, TIMER_NAME_MAX );
10145 /* step through timer list */
10146 for (i = 0; i < timer_idx; i++ ) {
10148 t = &timer[ i ];
10150 /* skip timer entry that is actually a search entry */
10151 if ( T_SEARCH == t->qstat ) continue;
10153 /* copy timer name for flag removal */
10154 astrncpy( tn, t->name, sizeof(tn) );
10156 /* remove flags from timer name */
10157 strip_timer_flags( tn );
10159 /* if trunc timer name doesn't match trunc event name, try next */
10160 if ( 0 != strncasecmp( tn, en, strlen(tn) ) ) continue;
10162 /* if timer start doesn't match event start, try next */
10163 if ( t->start != e1->st ) continue;
10165 /* if timer program number doesn't match event program number, try next */
10166 if ( t->pn != e1->pn ) continue;
10168 /* timer name, start time and program number matches event:
10169 copy event name and description to timer entry for caplog */
10170 if ( 0 != *e1->name ) astrncpy( t->ename, e1->name, PNZ );
10171 if ( 0 != *e1->desc ) astrncpy( t->edesc, e1->desc, PDZ );
10173 advrlog (LOG_INFO, "%s found timer %d %s", WHO, i, tn );
10174 return 0;
10177 /* no timer matches the event */
10178 return ~0;
10182 /* return day of week as day bits for event program guide entry pgo.
10183 Sunday is b6, Monday is b5 ... Saturday is b0
10185 /* static */
10187 get_pgm_wday ( struct event_s *e1 )
10189 unsigned char day;
10190 time_t start;
10191 struct tm lt;
10193 if (NULL == e1) return 0;
10195 start = e1->st;
10196 localtime_r( &start, &lt );
10197 day = lt.tm_wday;
10199 return (1<<(6-day));
10202 /* e1 is pointer to one EPG event_s to use for new timer */
10203 /* static */
10204 void
10205 add_search_event_timer ( struct event_s *e1 )
10207 int i, ch;
10208 struct sig_s *s;
10209 struct qtimer_s *t;
10210 char tn[ 32 ];
10212 if (NULL == e1) return; /* sanity check */
10214 /* don't allow new event timer to overflow timer list max */
10215 if (timer_idx >= TIMER_LIST_MAX) {
10216 dvrlog( LOG_INFO, "%s %s timers full, increase TIMER_LIST_MAX",
10217 WHO, e1->name);
10218 return;
10221 /* if timer adds itself again at end of capture, it may truncate capture */
10222 if ( ((e1->st + e1->ls)-60) < utsnow ) {
10223 advrlog(LOG_INFO, "%s not adding %s", WHO, e1->name);
10224 return;
10227 ch = e1->chan; /* event channel */
10228 s = &ptc[ ch ].sig; /* sig struct for channel */
10230 /* stops endless zap when reselect ch and endless [d]elete timer0 loop */
10231 if (s->lzpgt == e1->st) return;
10233 /* Look for duplicate timers */
10234 for (i = 0; i < timer_idx; i++) {
10235 t = &timer[ i ];
10237 /* Skip search entries */
10238 if (T_SEARCH == t->qstat) continue;
10240 /* TESTME: is this event/timer check needed anymore? yes: for web EPG */
10241 /* Does this event already exist on the timer list? */
10242 if ( (t->start == e1->st) /* start time match? */
10243 && (t->len == e1->ls) /* run length match? */
10244 && (t->pn == e1->pn) /* program number match? */
10245 && (t->chan == e1->chan) ) /* channel match? */
10247 advrlog( LOG_INFO, "dup ev %d.%d %s %s",
10248 t->chan, t->pn, t->name, e1->name );
10250 /* Set timer ETMID so EPG volatiles don't show up in build epg timers,
10251 which should only show volatile timers without EPG entries.
10253 t->etmid = e1->etmid;
10254 /* caplog needs full name and full description, if available */
10255 *t->ename = 0;
10256 *t->edesc = 0;
10258 if (0 != *e1->name) astrncpy( t->ename, e1->name, PNZ );
10259 if (0 != *e1->desc) astrncpy( t->edesc, e1->desc, PDZ );
10261 /* First duplicate gets out, name doesn't matter */
10262 return;
10266 event_truncate( tn, e1->name, 2, 32 ); /* PG event name two words */
10267 if (0 == *tn) return; /* should never happen? abort if it does */
10269 /* shorten event name to allow inclusion of @.n */
10270 tn[16] = 0;
10272 /* Add the timer to the end of of the timer list */
10273 t = &timer[ timer_idx ];
10274 timer_idx++;
10276 *t->edesc = 0;
10277 *t->ename = 0;
10279 /* add truncated name to timer list w/ mmdd-hhmm format and program number */
10280 snprintf( t->name, TIMER_NAME_MAX, "%s@.%d", tn, e1->pn );
10282 /* add the timer */
10283 t->days = 0; /* no weekday bits for automatic events */
10284 t->future = 0; /* no future queue for automatic events */
10285 t->secdt = 5; /* AOS delay time is 5s prior to start */
10286 t->chan = e1->chan; /* channel */
10287 t->pn = e1->pn; /* program */
10288 t->start = e1->st; /* start time */
10289 t->len = e1->ls; /* length seconds */
10290 t->qstat = T_FUTURE; /* show timers might change this */
10291 t->vc = -1; /* capture VCT/PMT fills this in? */
10292 t->va = -1; /* done from timer name flag not here yet */
10293 t->etmid = e1->etmid; /* volatile timer came from EPG data */
10295 /* caplog needs full name and full description, if available */
10296 *t->ename = 0;
10297 *t->edesc = 0;
10298 if (0 != *e1->name) astrncpy( t->ename, e1->name, PNZ );
10299 if (0 != *e1->desc) astrncpy( t->edesc, e1->desc, PDZ );
10301 advrlog( LOG_INFO, "add ev %d.%d %s", t->chan, t->pn, t->name );
10304 /* if EPG event e1 isn't on timer list, and is on search list,
10305 add a timer but only if the channel, program and weekday bits match,
10307 e1 is pointer to one EPG event
10308 p is pointer to EPG filter (not used? can it be used?)
10310 /* static */
10311 void
10312 search_events ( struct event_s *e1, struct pgm_s *p, char *caller )
10314 int i, j, k, ch, wd, pn;
10315 char en[32], sn[32], tn[32];
10317 if (NULL == e1) return;
10319 /* try the obvious disqualify first: the event already has a timer? */
10320 k = search_timers( e1 );
10321 if (0 == k) return;
10323 advrlog( LOG_INFO, "%s %s", WHO, e1->name);
10325 /* truncate event name */
10326 event_truncate( en, e1->name, 2, 16 );
10328 for (i = 0; i < search_idx; i++) {
10330 /* search list name, remove any flags */
10331 astrncpy( sn, search_list[i].name, sizeof(sn) );
10332 strip_timer_flags( sn );
10334 /* truncated search list name */
10335 event_truncate( tn, sn, 2, 16 );
10337 /* TESTME: not needed anymore? force names to 16 chars max */
10338 en[16] = 0;
10339 tn[16] = 0;
10341 /* shorthand these to reduce pointer confusion in following tests */
10342 ch = search_list[i].chan;
10343 wd = search_list[i].days;
10344 pn = search_list[i].pn;
10346 /* limit compare to length of truncated search list item */
10347 k = strlen( tn );
10349 /* no name match, try next search list item */
10350 if ( 0 != strncasecmp(en, tn, k) ) continue;
10352 /* name matches, see if rest of search parameters match */
10353 advrlog( LOG_INFO, "%s found %s", WHO, tn);
10355 /* if event already on timer list, get out now */
10356 k = search_timers( e1 );
10357 if (0 == k) return;
10359 /* timer add status is false until proven true */
10360 j = 0;
10362 /* 4 conditions can trigger adding the event */
10363 /* 1 condition can reverse the above (pn not matching) */
10365 if (0 == ch) { /* if not filtered by chan */
10366 if (0 == wd) { /* and not filtered by days */
10367 j = ~0;
10368 } else { /* is filtered by days */
10369 if (0 != (wd & get_pgm_wday( e1 )) )
10370 j = ~0;
10374 if (ch == e1->chan) { /* if filtered by channel */
10375 if (0 == wd) { /* and not filtered by days */
10376 j = ~0;
10377 } else { /* if filtered by channel and days */
10378 if (0 != (wd & get_pgm_wday( e1 )))
10379 j = ~0;
10383 /* if program match selected, and no match, do not add it */
10384 if (pn > 0)
10385 if (pn != e1->pn)
10386 j = 0;
10388 advrlog( LOG_INFO, "%s add %d name %s ch %d pn %d wd %d",
10389 WHO, j, e1->name, ch, pn, wd );
10391 if (0 == j) return; /* did not pass filter */
10393 add_search_event_timer( e1 );
10395 /* timer list needs sorting and refresh */
10396 timer_sort = ~0;
10397 refresh.timers = 1;
10398 return;
10403 /* these get some abstraction but still depends on vc and pa structures */
10404 /* load vc and pa structures:
10405 file format:
10406 short vct_ncs
10407 short pat_ncs
10408 struct vc_s vc
10409 struct pa_s pa
10411 /* static */
10412 void
10413 load_vct ( char *caller, int ch, int *ncv, struct vc_s *v,
10414 int *ncp, struct pa_s *p )
10416 FILE *f; /* file descriptor for input */
10417 struct stat fs; /* file stat struct */
10418 char n[256]; /* file name for input */
10419 size_t r;
10420 int ok;
10421 struct sig_s *s;
10422 int vcn;
10424 if (ch > ptc_max) { /* boundary check */
10425 dvrlog( LOG_INFO, "%s %d >= ptc max %d",
10426 WHO, ch, ptc_max);
10427 return;
10429 s = &ptc[ch].sig;
10431 init_vct( &vct, WHO );
10433 snprintf( n, sizeof(n), "%s%spg/%02d.vc", out_path, ram_path, ch);
10434 advrlog( LOG_INFO, "%s %s %s", WHO, caller, n );
10436 /* small file */
10437 ok = stat( n, &fs );
10438 if (0 != ok) {
10440 /* this one isn't very important */
10441 advrlog( LOG_INFO, "%s stat %s error %d", WHO, n, errno );
10442 return;
10445 f = fopen( n, "r");
10446 if (NULL == f) {
10447 dvrlog( LOG_INFO, "%s read %s has error %d",
10448 WHO, n, errno);
10449 return; /* no vct found */
10452 /* read vct_ncs and pat_ncs */
10453 r = fread( ncv, sizeof( int ), 1, f);
10454 if (1 != r) {
10455 dvrlog( LOG_INFO,"%s error %d rd vctncs %s",WHO,r,n );
10456 fclose( f );
10457 return;
10460 r = fread( ncp, sizeof( int ), 1, f);
10461 if (1 != r) {
10462 dvrlog( LOG_INFO,"%s error %d rd patncs %s",WHO,r,n );
10463 fclose( f );
10464 return;
10467 /* read vc and pa */
10468 r = fread( v, sizeof( vc ), 1, f); /* FIXME: vc needs abstraction */
10469 if (1 != r) {
10470 dvrlog( LOG_INFO, "%s error %d rd vc %s",WHO,r,n );
10471 fclose( f );
10472 return;
10475 r = fread( p, sizeof( pa ), 1, f); /* FIXME: pa needs abstraction */
10476 if (1 != r) {
10477 dvrlog( LOG_INFO, "%s error %d rd pa %s",WHO,r,n );
10480 /* if the program exists in vc, set station hres to last saved pn hres */
10481 s->hres = 0;
10482 if (s->pn > 0) {
10483 vcn = find_pa_pgm( s->pn, WHO );
10484 if (vcn >= 0) s->hres = pa[vcn].hres;
10487 fclose( f );
10490 /* Open ch.epg file, read indices into i, then read events into e.
10491 file format:
10492 pgx[ ... ] index values for e[], -1 terminates list
10493 e[ pgx[ ... ] ] epg entries for each event_s
10495 ch is channel
10496 e is event list event_s, epg[] or copy
10497 pgx is index list pg[] or pg3[]
10498 p is pointer to program mask/status
10500 p->fail is set to one of these:
10501 PG_FAIL_NONE no error
10502 PG_FAIL_BG blank guide
10503 PG_FAIL_GE guide empty(same as blank?remove me)
10504 PG_FAIL_NF not found
10505 PG_FAIL_TO time out (last event start+len is < utsnow)
10508 /* static */
10509 void
10510 load_epg ( int ch, struct event_s *e, struct pgm_s *p, short *pgx, char *who )
10512 FILE *f; /* fd for input */
10513 struct stat fs; /* need for file mtime */
10514 struct event_s *e1, e2; /* one event */
10515 size_t r;
10516 char n[256], g[256]; /* file name for input */
10517 unsigned short i, j;
10518 short k, po;
10519 struct sig_s *s;
10520 unsigned int et; /* last event end time */
10521 int fdn, cdn; /* first and last day numbers */
10523 s = &ptc[ch].sig;
10526 memset( e, 0, sizeof( epg ));
10527 p->fail = PG_FAIL_NONE;
10529 snprintf( g, sizeof(g), "%02d.epg", ch);
10531 snprintf( n, sizeof(n), "%s%spg/%s", out_path, ram_path, g);
10533 i = stat(n, &fs);
10534 if (0 != i) {
10535 advrlog( LOG_INFO, "%s error stat %s", WHO, n);
10536 p->fail = PG_FAIL_NF;
10537 return; /* file problem */
10540 p->mtime = (int)fs.st_ctime;
10542 f = fopen( n, "r");
10543 if (NULL == f) {
10544 dvrlog( LOG_INFO, "%s error reading %s", WHO, n);
10545 p->fail = PG_FAIL_NF;
10546 return; /* file problem */
10549 j = 0;
10550 r = ~0;
10551 while (0 != r) {
10552 r = fread( &po, sizeof(short), 1, f);
10553 if (0 == r) break; /* EOF */
10554 if (-1 == po) break;
10556 /* broadcast likes the slots to be the same as the broadcast slots. */
10557 if (0 == arg_cable) {
10558 if (j >= PGZ) break;
10559 if (po >= PGZ) break; /* boundary/FFFF check */
10560 pgx[ j ] = po; /* store epg[index] */
10562 j++;
10565 // p->idt = j; /* event index count */
10566 // p->idx = j; /* event index count */
10568 if (0 == j) {
10569 advrlog( LOG_INFO, "%s %s empty", WHO, n );
10570 fclose( f );
10571 p->fail = PG_FAIL_GE;
10572 return;
10575 e1 = NULL;
10576 et = 0;
10577 k = 0;
10579 fdn = -1;
10580 cdn = 0;
10582 for (i = 0; i < j; i++) {
10583 if (k >= PGZ) break;
10585 /* cable doesn't need to store it in original slots, broadcast does? */
10586 if (0 == arg_cable) {
10587 e1 = &e[ pgx[i] ];
10588 } else {
10589 e1 = &e[ k ];
10592 r = fread( &e2, sizeof(struct event_s), 1, f );
10593 /* EOF */
10594 if (0 == r) break;
10596 /* program number translate set? */
10597 if (s->pnx > 0) {
10599 /* skip if no translate match */
10600 if (e2.pn != s->pnx) continue;
10601 e2.chan = s->chan;
10602 e2.pn = s->pn;
10605 /* set first day number and last day number from epg current day number */
10606 cdn = e2.stm.tm_yday;
10607 if (-1 == fdn) fdn = cdn;
10609 /* end time of last event copied */
10610 et = e2.st + e2.ls;
10611 memcpy( e1, &e2, sizeof(e2) );
10613 /* look for search event matches and add a timer for any matches */
10614 search_events( e1, p, WHO );
10616 k++;
10619 /* if any first day, see if s->yday within range of epg
10620 if not in range, set s->yday to today
10622 if (-1 != fdn) {
10624 /* did year meridian occur between first day num and current day num? */
10625 if (fdn <= cdn) {
10627 /* same year, does day need to be fixed because it's out of range? */
10628 if ((s->yday < fdn) || (s->yday > cdn)) {
10629 s->yday = fdn;
10630 advrlog(LOG_INFO, "%s same year fix s->yday %d",
10631 WHO, s->yday);
10633 } else {
10635 /* end of year handling. order is important. do next year test first */
10637 advrlog(LOG_INFO, "%s same year s->yday %d", WHO, s->yday);
10638 if ((s->yday > cdn) && (s->yday < fdn)) {
10639 s->yday = fdn;
10640 advrlog(LOG_INFO, "%s next year fix s->yday %d",
10641 WHO, s->yday);
10644 #if 0
10645 if (s->yday < fdn) {
10646 s->yday = fdn;
10647 advrlog(LOG_INFO, "%s this year fix s->yday %d",
10648 WHO, s->yday);
10651 #endif
10654 } else {
10655 advrlog(LOG_INFO, "%s no fdn, s->yday %d is today",
10656 WHO, s->yday);
10657 s->yday = tloc.tm_yday;
10660 if (fdn == cdn) s->yday = fdn;
10661 if (-1 == s->yday) s->yday = fdn;
10663 advrlog( LOG_INFO, "%s %s ch %d yday %d fdn %d cdn %d",
10664 WHO, who, s->ptc, s->yday, fdn, cdn);
10667 p->idt = k; /* event index count */
10668 p->idx = k; /* event index count */
10670 fclose( f );
10672 /* some kind of time should have been set by at least one event */
10673 if (0 == et) {
10674 /* if no time set, guide is blank */
10675 p->fail = PG_FAIL_BG;
10676 } else {
10677 /* last entry read is used for guide expired determination */
10678 if (et < utsnow)
10679 p->fail = PG_FAIL_TO;
10681 advrlog( LOG_INFO, "%s %s pg idx %d found .%d %d",
10682 WHO, n, p->idx, s->pn, k );
10684 /* PG_FAIL_NONE was set above */
10687 /* load guide uses same error returns as load epg above */
10688 /* TODO: remove redundant pg fail set code leftover from before load epg */
10689 /* static */
10690 void
10691 load_guide ( int ch, char *caller )
10693 char n[256]; /* file name */
10694 struct stat fs;
10695 struct sig_s *s;
10696 struct pgm_s *p;
10697 int i;
10699 /* FIXME: test mode might need this, but don't let it save */
10700 if (0 != test_mode) return;
10701 if (0 != arg_scan) return; /* not needed for -S setup */
10703 if (ch > ptc_max) {
10704 dvrlog( LOG_INFO, "%s chan %d > ptc max %d",
10705 WHO, ch, ptc_max);
10706 return;
10709 s = &ptc[ ch ].sig;
10710 p = &pgm;
10711 pkt.feit = 0;
10712 p->idx = 0;
10713 p->chan = ch;
10715 p->fail = PG_FAIL_NONE;
10716 init_vct( &vct, WHO );
10717 load_vct( caller, ch, &vct_ncs, vc, &pat_ncs, pa ); /* vct & pat+pmt */
10719 if (vct_ncs >= pat_ncs) {
10720 vc_ncs = vct_ncs;
10721 } else {
10722 vc_ncs = pat_ncs;
10724 init_pgm();
10726 pkt.feit = PAY_GOOD; /* cheat event status display */
10727 pgm_done = ~0;
10729 snprintf( n, sizeof(n), "%s%spg/%02d.epg", out_path, ram_path, ch);
10731 /* stat file to see if it's more than 3 hours past now */
10732 /* file date was set by save guide to last event */
10733 i = stat( n, &fs);
10734 if (0 != i) {
10735 advrlog( LOG_INFO, "EPG%02d %s ( %s ) guide not found",
10736 p->chan, WHO, caller);
10737 p->fail = PG_FAIL_NF;
10738 return; /* not found nop */
10741 /* rely on save guide mtime for auto guide time out */
10742 /* mtime is from EIT-01.0 or calculated from failure future retry time */
10743 s->pgmt = (int)fs.st_mtime;
10745 /* test and fail if empty file size, can use touch as place holder */
10746 if (0 == fs.st_size) {
10747 advrlog( LOG_INFO, "EPG%02d %s guide empty", ch, n);
10748 p->fail = PG_FAIL_GE;
10749 return;
10752 /* use 'live' versions, http abstracts them to epg3 pg3 pgm3.idt */
10753 // load_epg( ch, epg, &pgm, pg, WHO );
10754 load_epg( ch, epg, &pgm, pg, caller );
10756 advrlog( LOG_INFO, "EPG%02d load epg %d events", ch, p->idx );
10757 /* is guide blank? */
10758 if (0 == p->idt) {
10759 advrlog( LOG_INFO, "EPG%02d %s blank", ch, n);
10760 p->fail = PG_FAIL_BG;
10761 return;
10764 build_pg_epg( epg, &pgm, pg, WHO );
10766 s->pgx = p->idt;
10768 advrlog( LOG_INFO, "EPG%02d build pg %d/%d", ch, p->idx, p->idt );
10771 /* build filtered pg from full epg */
10772 if (0 == p->idx) {
10773 advrlog( LOG_INFO, "EPG%02d %s %s filtered %d/%d",
10774 ch, WHO, caller, p->idx, p->idt );
10775 return;
10778 /* copy name/description from epg ch+pgm+time match to timer that matches */
10779 update_timer_epg();
10781 /* want to know if last event in epg has expired time */
10782 i = epg[ pg[pgm.idx-1] ].st + epg[ pg[pgm.idx-1]].ls;
10783 if (i < utsnow) {
10784 p->fail = PG_FAIL_TO; /* too old/time out */
10787 advrlog(LOG_INFO, "EPG%02d %s ( %s ) idx %d/%d",
10788 pgm.chan, WHO, caller, pgm.idx, pgm.idt );
10790 return;
10794 /* set physical transmission channel from s->chan
10795 and virtual channel and audio from s->vc and s->va
10797 /* static */
10798 void
10799 set_channel ( struct sig_s *s, char *caller )
10801 int ir = -1;
10802 unsigned int f = 0;
10804 /* STD MHz seems to be 1.25MHz from bottom of band, NTSC video carrier. */
10805 /* DVB API wants 3MHz from bottom of band (center), offset 28.615kc. */
10806 /* NOTE: according to driver, the tuner PLL resolution is 62.5kc */
10807 f = s->freq;
10809 if (0 == f) {
10810 dvrlog( LOG_INFO, "%s %d has freq %f", WHO, s->chan, s->freq);
10811 return;
10814 if (f == scan_freq) return; /* don't set if already set */
10815 advrlog( LOG_INFO, "%s %s f %d scan_freq %d",
10816 caller, WHO, f, scan_freq);
10817 scan_freq = f;
10819 init_mg();
10820 init_pids();
10822 advrlog( LOG_INFO, "%s(%s) %d", caller, WHO, s->chan);
10823 load_guide( s->chan, WHO );
10824 set_vc(WHO); /* sets cap_vc and cap_va and builds pid list */
10826 #ifdef USE_POWERDOWN
10827 if ( (0 != arg_tmpfs) && (fe_file < 3) ) open_device( caller );
10828 #endif
10829 if ( (0 == test_mode) && (0 == arg_dummy) ) {
10830 struct timespec ns;
10831 fe_param.frequency = f;
10832 if ( -1 != fe_file) {
10833 ir = ioctl( fe_file, FE_SET_FRONTEND, &fe_param );
10834 if (ir < 0) {
10835 dvrlog( LOG_INFO,
10836 "FE %d SET FRONTEND %d Hz %d fail %d",
10837 fe_file, f, s->freq, ir);
10838 return;
10839 } else {
10840 advrlog( LOG_INFO,
10841 "%s FE %d SET FRONTEND %d Hz",
10842 s->call, fe_file, f);
10844 /* Don't wait too long here because it will makes channel scan slower. */
10845 ns.tv_sec = 0;
10846 ns.tv_nsec = aos_delay * 1000000;
10847 nanosleep(&ns, NULL);
10849 } else {
10850 dvrlog( LOG_INFO, "%s FE %d FILE NOT OPEN?",
10851 WHO, fe_file );
10852 test_mode = ~0;
10854 scan_freq = f; /* one shot this */
10857 refresh.pstats = 1;
10858 refresh.vstats = 1;
10859 refresh.estats = 1;
10862 /* See if wake-up is expired and wake the drive if it is expired. */
10863 /* This should only be called by test timer with current now timer. */
10864 /* static */
10865 void
10866 test_timer_wakeup_devices ( struct qtimer_s *t )
10869 FILE *f;
10870 struct sig_s *s;
10871 char n[256];
10874 /* no -E? */
10875 if (0 == arg_tmpfs) return;
10877 /* is awake already ? */
10878 if (-1 == utswuv) return;
10879 /* is not idle? */
10880 if ((CAP_NONE != cap_now) && (CAP_INFO != cap_now)) return;
10881 /* no timer yet? */
10882 if (t->qstat == T_SEARCH) return;
10883 /* not within 30s of timer yet? */
10884 if ((t->start - 30) > utsnow) return;
10885 if (utswuv > utsnow) return;
10887 /* first time through, or after capture done ? */
10888 if (0 == utswuv) {
10889 utswuv = t->start - 30;
10890 advrlog( LOG_INFO, "utswuv %d in %d", utswuv, utswuv - utsnow);
10891 return;
10894 s = &ptc[t->chan].sig;
10896 /* One-shot test wakeup until capture stops and clears the flip-flop. */
10897 utswuv = -1;
10899 /* Make a filename in a directory on the volume that needs to wake up. */
10900 snprintf( n, sizeof(n), "%s%s%d.wakeup", out_path, NAME, arg_devnum);
10902 /* is enough to wake up the drive? */
10903 f = fopen( n, "a" );
10904 if (NULL == f) {
10906 /* does the delay cause an error? lets see it, if so */
10907 dvrlog( LOG_INFO, "hdd wakeup error %d %s", errno, strerror(errno) );
10908 return;
10909 } else {
10911 /* may need to actually write something other than dirent to spin-up */
10912 fprintf(f, "%s", "awaken");
10913 fflush(f);
10914 fclose(f);
10916 /* Maybe this will stall somewhat synchronously to drive spin-up, but it's
10917 wishful thinking. It's already in kernel write cache. Find another way
10918 wait until drive is spun up.
10920 remove(n);
10923 #ifdef USE_POWERDOWN
10924 /* Wake up DVB device BEFORE ts capture. */
10925 /* NOTE: make sure USE_POWERDELAY isn't shorter than 30s, or else
10926 device will power off after wakeup but before capture starts.
10928 if ((0 == arg_dummy) && (in_file < 3)) {
10929 scan_freq = 0; /* force new freq select */
10930 set_channel( s, WHO ); /* set channel will open device */
10931 dvrlog( LOG_INFO, "awoke dvb, set ch %2d", s->chan);
10933 #endif
10938 check given timer q to see if it's current
10939 return -1 if not current, or q if current
10941 /* static */
10943 test_timer ( int q )
10945 struct qtimer_s *t;
10946 int tstart, tstop, istop;
10948 if (0 == timer_idx) return -1; /* no timers on list */
10949 if (q >= timer_idx) return -1; /* sanity boundary check */
10950 if (T_SEARCH == timer[q].qstat) return -1; /* no search events */
10951 if (T_DONE == timer[q].qstat) return -1; /* timer to be removed */
10953 t = &timer[q];
10954 get_time();
10956 if (0 == q) test_timer_wakeup_devices( t );
10958 tstop = t->start + (t->len - t->secdt); /* len adjust for delete time */
10959 istop = t->start - 5; /* info cap kick out 5s before timer */
10960 tstart = t->start - 3; /* AOS starts 3s before timer */
10962 /* info cap should kick out 5 seconds before next timer start */
10963 if (CAP_INFO == cap_now) {
10964 if (utsnow >= istop) {
10965 /* normal info cap exit so the guide will save */
10966 stop_capture( WHO, FAIL_NONE );
10967 return -1;
10971 /* if now is past time for AOS pre-timer check */
10972 /* FIXME: problem location for the double start on back-to-back timers */
10973 /* FIXED: only allow timer 0 to become active, rest have to calc status */
10974 if (0 == q) {
10975 if ( (utsnow >= tstart) && (utsnow < tstop) )
10976 return q; /* this timer might be active */
10978 return -1;
10981 /* add up all the weekdays and lengths for each timer, return # of sec */
10982 /* static */
10984 total_timers ( void )
10986 int i, j, k, s, t;
10988 s = t = 0;
10990 for (i=0; i< timer_idx; i++)
10992 if (T_SEARCH == timer[i].qstat) continue;
10994 /* count actual usage, not time that isn't used */
10995 k = timer[i].len - timer[i].secdt;
10996 if (timer[i].days != 0)
10998 char *tn = timer[i].name;
10999 char tx = tn[strlen(tn)-1];
11001 /* only add weekday timers if $ or # at end of name
11002 NOTE: both are guesses at usage for this week only */
11003 if ( (tx == '#') || (tx == '$') )
11005 for (j = 0; j< 7; j++)
11007 if ( ( timer[i].days & (1<<j) ) != 0 )
11009 s += k;
11010 t++;
11013 /* no $ or #, must be overwriting, so add one timer only */
11014 /* makes timers look wrong if any $ or #, but they are correct */
11015 } else {
11016 s += k;
11017 t++;
11019 /* no weekdays, only a single timer that does not reschedule */
11020 } else {
11021 s += k;
11022 t++;
11026 /* total number of timers */
11027 if (0 != arg_capture)
11029 /* no timer, but some time to calc for usage */
11030 timer_tot = 0;
11031 return arg_capture; /* -s # of seconds */
11032 } else {
11033 /* got number of timers and number of seconds used */
11034 timer_tot = t;
11035 return s; /* computed number of seconds from all timers and days */
11039 /* look at timer[r] .days and .start and figure out how many seconds
11040 until the next reschedule time. return this value.
11041 PLUS silly math tricks:
11042 tm_wday is 0(sunday)...6(saturday), numerical for single day
11043 .days is bit6 is sunday, bit0 is saturday, for multiple days
11045 /* static */
11047 calc_future_date ( int r )
11049 int i, j, k, t;
11051 /* sanity check, 0 means nothing gets added since no bits set */
11052 if (0 == timer[r].days) return 0;
11053 if (T_SEARCH == timer[r].qstat) return 0;
11055 /* don't reschedule future items that are already rescheduled */
11056 if (timer[r].start > utsnow) {
11057 if (timer[r].future > timer[r].start) {
11058 return 0;
11062 /* don't loop on this once timers are a week ahead from start time
11063 if ( timer[r].start > (utsmid + (14*SECDAY)) ) return 0;
11064 not needed now, endless reque loop problem fixed, but keep in case needed.
11066 /* reschedule attempts begin with start time, not today's time */
11067 trec1 = timer[r].start;
11068 memcpy( &tloc1, localtime( &trec1 ), sizeof( tloc1 ) );
11070 /* at least one bit is set, so at least one day for rescheduling */
11071 t = SECDAY;
11073 k = ( tloc1.tm_wday + 1 ) % 7; /* starting from start time weekday */
11075 /* only look 6 days into future? */
11076 /* start time + 1 day, test bitfields for seven days, mod7 */
11077 for (i=k; i < k+7; i++)
11079 j = 6 - (i % 7); /* adjust for backwards weekday bit notation */
11081 /* stop adding days when you find a day */
11082 if ( timer[r].days & ( 1 << j) )
11083 break;
11085 t += SECDAY; /* add a day */
11088 return t;
11091 /* compute next weekday timer start time from now */
11092 /* store result in timer[r] */
11093 /* static */
11094 void
11095 calc_first_weekday( struct qtimer_s *t )
11097 int i, j, b;
11098 struct tm f; /* current event or future start */
11099 time_t s;
11101 if (T_SEARCH == t->qstat) return;
11102 if (0 == t->days) return;
11105 /* start at current day meridian */
11106 t->start = utsmid;
11108 /* parse weekday timer sets h and m */
11109 t->start += (t->h * SECHR) + (t->m * SECMIN);
11111 /* start at current weekday */
11112 j = tloc.tm_wday;
11114 /* find first start day */
11115 for (i = 0; i < 7; i++) { /* 8 days should find first/last overlap */
11116 b = 1 << (6 - j); /* daybit to compare against timer */
11118 /* daybit matches b. if event is still current, use this for t->start, else
11119 it won't be valid until the 8th time through, if only 1 weekday bit set
11121 if (0 != (b & t->days)) {
11122 if (utsnow < (t->start + t->len))
11123 break;
11126 /* add a day, try next day bit and keep within weekday bit shift range */
11127 t->start += SECDAY;
11128 j++;
11129 j %= 7;
11131 /* fall through means it was today and got rescheduled 1 week */
11132 /* break is where it found the day matched and wasn't expired */
11135 /* t->start should have correct time unless DST change occurred between */
11136 s = (time_t) t->start;
11138 localtime_r(&s, &f);
11140 /* most of the year, the dst value won't change */
11141 if (tloc.tm_isdst == f.tm_isdst) return;
11143 /* spring forward */
11144 if (0 == tloc.tm_isdst) {
11145 t->start += 3600;
11146 } else {
11147 /* fall back */
11148 t->start -= 3600;
11152 /* sort search list */
11153 void
11154 /* static */
11155 sort_searchlist ( void )
11157 int i,j, k, z;
11158 struct search_s search_tmp;
11160 if (search_idx < 2) return;
11162 z = sizeof(struct search_s);
11163 for (i = 0; i < (search_idx - 1); i++) {
11164 for (j = i + 1; j < search_idx; j++) {
11165 k = strncasecmp( search_list[i].name, search_list[j].name,
11166 SEARCH_NAME_MAX-1 );
11167 if (k > 0) {
11168 memcpy( &search_tmp, &search_list[i], z );
11169 memcpy( &search_list[i], &search_list[j], z );
11170 memcpy( &search_list[j], &search_tmp, z );
11176 /* sort spamlist, st is sort type 0 for name, nz for date */
11177 void
11178 /* static */
11179 sort_spamlist ( int st )
11181 int i,j, k, z;
11182 struct spam_s spam_tmp;
11184 if (spam_idx < 2) return;
11186 z = sizeof(struct spam_s);
11187 for (i = 0; i < spam_idx; i++) {
11188 for (j = i + 1; j < spam_idx; j++) {
11189 if (0 == st) {
11190 k = strncasecmp( spam_list[i].name,
11191 spam_list[j].name,
11192 SPAM_NAME_MAX-1 );
11193 } else {
11194 k = spam_list[j].st - spam_list[i].st;
11196 if (k > 0) {
11197 memcpy( &spam_tmp, &spam_list[i], z );
11198 memcpy( &spam_list[i], &spam_list[j], z );
11199 memcpy( &spam_list[j], &spam_tmp, z );
11205 /* Sort timers by start date. This could be faster with pointers.
11206 It's slow because it's swapping whole structures. Bubble sort isn't
11207 the fastest, either, but it's good enough for < 1000 items.
11208 See program guide sort for bubble with pointers for > 1000 items.
11210 unsigned int -1 for start time keeps search events always at end
11212 /* static */
11213 void
11214 sort_timers ( void )
11216 int i, j, k, s, z;
11217 struct qtimer_s tmp;
11219 if (timer_idx < 2) return;
11221 advrlog( LOG_INFO, "%s", WHO );
11223 z = sizeof(struct qtimer_s);
11224 s = 0;
11226 /* don't sort timer0 if it's active, regardless of volatile/weekday */
11227 if ((T_STREAM == timer[0].qstat) || (T_ACTIVE == timer[0].qstat)) s = 1;
11229 for (i=s; i < (timer_idx - 1); i++)
11231 /* start j from i+1 to move forward in list */
11232 for (j = i + 1; j < timer_idx; j++)
11235 /* swap flag, 0 is no swap, 1 is swap */
11236 k = 0;
11238 /* less than loops here to avoid more branch prediction */
11239 if (timer[i].start < timer[j].start ) continue;
11241 /* equal to can swap depending on: search name or no daybits */
11242 if ( timer[i].start == timer[j].start ) {
11244 /* no daybits volatile sets precedence over weekday timers */
11245 if ( (T_SEARCH != timer[i].qstat)
11246 && (T_SEARCH != timer[j].qstat) )
11248 if ( (0 != timer[i].days) && (0 == timer[j].days) ) {
11249 k = 1;
11253 /* alphabetic order sets precedence for search timers */
11254 if ( (T_SEARCH == timer[i].qstat)
11255 && (T_SEARCH == timer[j].qstat) )
11257 k = strncasecmp( timer[i].name,
11258 timer[j].name,
11259 TIMER_NAME_MAX);
11260 if (0 < k) {
11261 k = 1; /* flag it for swap */
11262 } else {
11263 k = 0;
11268 /* finally make sure search timers get swapped with non search timers */
11269 if ( (T_SEARCH == timer[i].qstat)
11270 && (T_SEARCH != timer[j].qstat) )
11271 k = 1;
11273 /* greater than always sets flag for a swap */
11274 if ( timer[i].start > timer[j].start ) k = 1;
11276 /* only swap if flag is non-zero */
11277 if (k > 0) {
11278 memcpy( &tmp, &timer[i], z );
11279 memcpy( &timer[i], &timer[j], z );
11280 memcpy( &timer[j], &tmp, z );
11285 timer_sort = 0; /* one shot this */
11290 binary ascii to weekday
11291 wecma has 32 bytes for weekdays,
11292 and 2x8 ansi color codes for highlight
11294 /* static */
11295 void
11296 batowd ( int tq )
11298 int i, z, today = 0;
11299 char *d, *s, *wt = "SMTWTFS";
11301 d = wecma;
11302 z = sizeof(wecma);
11303 *d = 0;
11305 for ( i=0; i < 7; i++) {
11306 today = 0;
11307 if ( ( timer[ tq ].start >= utsmid)
11308 & ( timer[ tq ].start < utsmid + SECDAY )
11309 & ( tloc.tm_wday == i ) )
11310 today = 1;
11312 /* highlight current day of week */
11313 if ( today != 0 ) asnprintf( d, z, BY );
11315 s = " ";
11316 if ( bascii[ i ] & 1 ) s = wt + i;
11317 asnprintf( d, z, "%c", *s);
11319 /* unhighlight */
11320 if ( today != 0 ) asnprintf( d, z, BK );
11324 /* close the atsc device(s) */
11325 /* static */
11326 void
11327 close_device ( char *caller )
11329 if (in_file < 3) {
11330 dvrlog( LOG_INFO, "*** %s device %s already closed",
11331 caller, in_name);
11332 return;
11335 scan_sig = 0;
11336 scan_one = ~0;
11337 scan_freq = 0;
11339 /* in reverse order of open */
11340 if ( dvr_file > 2 ) close( dvr_file );
11341 if ( dmx_file > 2 ) close( dmx_file );
11342 if ( fe_file > 2 ) close( fe_file );
11344 advrlog( LOG_INFO,"*** %s closed device %s fd %d", caller, in_name, in_file);
11345 in_file = dvr_file = dmx_file = fe_file = -1;
11347 if (0 == arg_scan)
11348 aprintf( stderr, HOM BR "CLOSED %-24s" BN, in_name);
11351 /* put the console back to a usable state */
11352 /* static */
11353 void
11354 console_reset ( void )
11356 struct termios modes;
11357 int f;
11359 /* no stdio in detached mode or it will block */
11360 if (0 != arg_detach)
11361 return;
11363 f = 0;
11364 fcntl( 0, F_GETFL, &f );
11365 f &= ~O_NONBLOCK;
11366 fcntl( 0, F_SETFL, f );
11368 /* indicate the console will need init before nonblock nonecho use */
11369 console_init = 1;
11370 if ( tcgetattr( 0, &modes ) < 0 )
11371 advrlog( LOG_INFO, "c_reset tcgetattr" );
11373 modes.c_lflag |= ICANON;
11374 modes.c_lflag |= ECHO;
11375 if ( tcsetattr( 0, TCSAFLUSH, &modes ) < 0 )
11376 advrlog( LOG_INFO, "c_reset tcsetattr" );
11380 /* sum the largest static tables and report to user */
11381 /* static */
11382 long long
11383 show_mem_static ( void )
11385 long long i, t, m, v;
11386 int c;
11387 char d[32];
11389 i = t = 0;
11391 aprintf( stdout, BG "Static memory:\n" );
11393 i = sizeof(epg);
11394 i += sizeof(pg) + sizeof(pg1) + sizeof(pg2);
11395 i += sizeof(pgm) + sizeof(pgm1) + sizeof(pgm2);
11396 #ifdef USE_WWW
11397 i += sizeof(pg3);
11398 i += sizeof(pgm3);
11399 #endif
11400 lltoasc( d, i );
11401 aprintf( stdout, " %20s %16s (%d events x %u)\n",
11402 "EPG + helpers", d, PGZ, sizeof(struct event_s) );
11403 t += i;
11405 i = sizeof(mgt) + sizeof(vct); /* 2 more payload_s */
11406 i += sizeof(stt) + sizeof(rrt); /* 2 more payload_s */
11407 v = sizeof(vc);
11408 i += v; /* vc helper is pretty big */
11410 /* TODO: shrink these to one or two, kind of big at 81920 bytes each */
11411 m = sizeof(mg);
11412 m += sizeof(mgp); /* kind of large */
11413 m += sizeof(mgs); /* dynamic now */
11414 i += m;
11415 #ifdef USE_DYNAMIC
11416 /* VCT MGT STT RRT tables static, EIT and ETT tables dynamic */
11417 c = 4;
11418 #else
11419 /* VCT MGT STT RRT EIT ETT tables static */
11420 c = 260;
11421 i += sizeof(eit);
11422 i += sizeof(ett);
11423 #endif
11424 lltoasc( d, i );
11425 aprintf( stdout, " %20s %16s (%d tables + vc %lld + mg* %lld)\n",
11426 "ATSC + helpers", d, c, v, m );
11427 t += i;
11429 i = sizeof(pat) + sizeof(cat); /* 3 payload_s */
11430 i += sizeof(pmt);
11431 v = sizeof(pa); /* pa helper is pretty big */
11432 i += v;
11434 m = sizeof(last_cc); /* plus PID trackers, 104k */
11435 m += sizeof(cce_pids);
11436 m += sizeof(psi_pids);
11437 m += sizeof(pids);
11438 i += m;
11440 lltoasc( d, i );
11441 aprintf( stdout, " %20s %16s (%d tables + pa %lld + pids %lld)\n",
11442 "MPEG + helpers", d, 3, v, m );
11443 t += i;
11445 i = sizeof(cap_text) + sizeof(cap_stats);
11446 lltoasc( d, i );
11447 aprintf( stdout, " %20s %16s (%u text %u errlog)\n",
11448 "Capture Stats", d, sizeof(cap_text) , sizeof(cap_stats) );
11449 t += i;
11451 i = sizeof(dvrlog_list) + sizeof(tvct_text) + sizeof(mgt_text);
11452 lltoasc( d, i );
11453 aprintf( stdout, " %20s %16s\n", "Text + helpers", d );
11454 t += i;
11456 i = sizeof(ptc) + sizeof(timer);
11457 lltoasc( d, i );
11458 aprintf( stdout, " %20s %16s\n", "Chans & Timers", d );
11459 t += i;
11461 i = sizeof( spam_list ) + sizeof( search_list );
11462 lltoasc( d, i );
11463 aprintf( stdout, " %20s %16s\n", "Search & Spam", d );
11464 t += i;
11466 #ifndef USE_DYNAMIC
11467 i = sizeof( fifo_buffer );
11468 lltoasc( d, i );
11469 aprintf( stdout, " %20s %16s\n", "Capture FIFO", d );
11470 t += i;
11472 i = sizeof( frames );
11473 lltoasc( d, i );
11474 aprintf( stdout, " %20s %16s\n", "frames[]", d );
11475 t += i;
11477 i = sizeof( sequences );
11478 lltoasc( d, i );
11479 aprintf( stdout, " %20s %16s\n", "sequences[]", d );
11480 t += i;
11481 #endif
11482 lltoasc( d, t );
11483 aprintf( stdout, " %20s " BW "%16s bytes\n", "Total static:", d);
11485 return t;
11491 /* sum the dynamic tables in use and report to user */
11492 /* static */
11493 long long
11494 show_mem_dynamic ( void )
11496 long long i, t;
11497 char d[32];
11498 int j;
11500 i = t = 0;
11502 aprintf( stdout, BC "Dynamic memory:\n" );
11504 for (j = 0; j < ALLOC_LIMIT; j++) {
11505 if ( NULL == alloc_list[j].p ) continue;
11506 i = alloc_list[ j ].z;
11507 lltoasc( d, i );
11508 aprintf( stdout, " %20s %16s\n", alloc_list[ j ].n, d );
11509 t += i;
11512 lltoasc( d, t);
11513 if (t > 0)
11514 aprintf( stdout, " %20s " BW "%16s bytes\n"BN, "Total dynamic:", d );
11515 return t;
11518 /* static */
11519 void
11520 show_mem_use ( void )
11522 long long t;
11523 char d[32];
11525 t = show_mem_static();
11526 t += show_mem_dynamic();
11527 lltoasc( d, t );
11528 aprintf( stdout, BW "%-21s %16s bytes\n" BN , "Total memory:", d );
11533 /* need to intercept sigterm to handle better shutdown and flushing */
11534 /* use this in place of exit() to (usually) reset console to usable state */
11535 /* static */
11536 void
11537 console_exit ( int errorcode )
11539 struct timespec ns;
11540 char n[64];
11542 memcpy( &ns, &signal_long_sleep, sizeof(ns) );
11543 ns.tv_nsec *= (1 + arg_devnum); /* one second delay per instance */
11544 nanosleep( &ns, NULL );
11546 if (out_file > 2) close( out_file );
11548 /* don't do this here, you want to keep all files if exit called */
11549 /* remove_renamed(); */
11552 #ifdef USE_MMAN
11553 munlockall(); /* clean exit */
11554 #endif
11556 #ifdef USE_MCAST
11557 if (0 != arg_mcport) udp_close_socket( &udp );
11558 #endif
11560 #ifdef USE_WWW
11561 /* if (0 != arg_www) http_close_sockets(); */
11562 #endif
11564 nanosleep( &msg_sleep, NULL);
11566 dvrlog( LOG_INFO, "console_exit ec %d en %d bc %lld es %s",
11567 errorcode, errno, outbc,
11568 (errorcode == 0) ? "": strerror( errno ) );
11570 if (0 == arg_detach) {
11572 /* once works usually, but twice works better */
11573 console_reset();
11574 console_reset();
11575 aprintf( stderr, "%s\n", BN);
11578 /* copy config, epg dirs, append /dtv/ram/atscap[0-3].log to /dtv/ version */
11579 close_device( WHO );
11580 free_fifo( &ts_fifo );
11581 if (0 == arg_dummy) {
11582 save_config( WHO );
11583 if (0 == arg_detach) {
11585 /* looks better */
11586 if (0 == arg_scan) /* don't move cursor in case -S makes long list */
11587 aprintf( stderr, BN SCV "\033[21;1H" CCE "\n");
11588 /* aprintf( stderr, "\033[0q"); */ /* reset? */
11589 // show_mem_use();
11590 aprintf( stderr, "\nExit code %d\n\n", errorcode );
11592 save_tmpfs( WHO );
11595 snprintf(n, sizeof(n), "/var/run/%s/%s%d.pid", NAME, NAME, arg_devnum);
11596 remove(n);
11598 exit( errorcode );
11602 /* The problem with this is the possible race condition between instances,
11603 due to spamlist being separated from card config. Will think on it
11604 because the result is very nasty with a corrupted spam list.
11606 One possible solution is it only updates when user adds/removes
11607 spam entry, then let other instances figure out reload time. This
11608 assumes only one user at a time which should be the usual case.
11610 /* static */
11611 void
11612 save_spamlist ( char *caller )
11614 int i;
11615 FILE *f;
11616 char n[256];
11617 char o[80];
11618 struct stat fs;
11619 struct tm t;
11620 time_t *st;
11622 while (EBUSY == pthread_mutex_trylock( &spam_mutex ))
11623 nanosleep( &atomic_sleep, NULL );
11625 // WHOAMI;
11627 /* spam list by time so user can easily find aged spam events */
11628 sort_spamlist( 1 );
11630 advrlog( LOG_INFO, "%s has %d entries", WHO, spam_idx);
11632 snprintf( n, sizeof(n), "%s%s.spam", cfg_path, NAME );
11633 f = fopen( n, "w");
11634 if (NULL == f) {
11635 dvrlog( LOG_INFO, "%s can't write %s", WHO, n);
11636 pthread_mutex_unlock( &spam_mutex );
11637 return;
11640 fprintf( f, "# atscap%d spamlist[%d] %s %s %d\n",
11641 arg_devnum, spam_idx, caller, date_now, utsnow);
11643 for (i = 0; i < spam_idx; i++) {
11645 st = &spam_list[i].st;
11646 localtime_r( st, &t );
11647 snprintf( o, sizeof(o), "%s:", spam_list[i].name);
11649 #if 1
11650 /* human readable date after # is ignored by load spamlist */
11651 fprintf( f, "%-17s%10d # %04d%02d%02d-%02d:%02d\n",
11652 o, (int)spam_list[i].st,
11653 t.tm_year+1900, t.tm_mon+1, t.tm_mday,
11654 t.tm_hour, t.tm_min);
11655 #endif
11658 fflush( f );
11659 fclose( f );
11660 i = stat( n, &fs );
11661 if (0 == i) {
11663 /* set cf spam mod time into future 1s to prevent loop on load */
11664 cf_smt = fs.st_mtime + 1; // + (time_t) arg_devnum;
11665 advrlog( LOG_INFO, "%s %s new mt %d", WHO, caller, cf_smt);
11668 /* spam list by name to speed up search */
11669 sort_spamlist( 0 );
11670 pthread_mutex_unlock( &spam_mutex );
11673 /* save channels and timers to a file */
11674 /* static */
11675 void
11676 save_config ( char *caller )
11678 FILE *o;
11679 struct stat st;
11680 struct qtimer_s *t;
11681 char ba[16]; /* binary to ascii output string */
11682 char fn[256];
11683 int i, j, ch;
11684 struct sig_s *s;
11686 if (0 != test_mode) return; /* prevent accidents */
11687 if (0 != arg_scan) return;
11689 /* don't overwrite with blank if control c before load config */
11690 if (0 != cf_no) return;
11692 snprintf( fn, sizeof(fn)-1, "%s%s", cfg_path, cfg_name );
11693 advrlog( LOG_INFO, "%s(%s) %s", WHO, caller, fn);
11695 o = fopen( fn, "w" );
11696 if ( NULL == o )
11698 fprintf( stderr, CLS "Can't open %s for write --", fn);
11699 dvrlog( LOG_ERR, "can't write config %s", fn);
11700 perror(0);
11701 console_exit(0);
11702 return;
11705 chmod( fn, 0660 );
11706 get_time();
11707 fprintf( o, "# Configuration %s created %s\n#\n", fn, date_now);
11708 fprintf( o,
11709 "# You may edit this file but only valid settings are kept.\n#\n"
11712 #if 1
11713 fprintf( o,
11714 "# SYNOPSIS: [optional]\n"
11715 "#\n"
11716 "# The order of these parameters is important. You want search event\n"
11717 "# A-lines before C-lines so when it loads the EPG, it finds an event.\n"
11718 "# You also want F-line frequency table selected before first C-line.\n"
11719 "# If you omit the F-line it will default to 8-VSB ATSC broadcast mode.\n"
11720 "#\n"
11721 "# nameX is event name and [optional #/@ flags] and [.program number].\n"
11722 "# After the '.program', ',1' can sometimes be specified for SAP.\n"
11723 "#\n"
11724 "# For V and W lines, nameX with #/@ will append -weekday/-mmdd.\n"
11725 "#\n"
11726 "# days are 1111111 for SMTWTFS, 0 = no cap that day.\n"
11727 "#\n"
11728 "# LCN = logical channel number, is C-line load order starting with 0.\n"
11729 "#\n"
11730 "# AnameX:channel:days\n"
11731 "# Search for name[.pgn] [on logical channel] [for match on days]\n"
11732 "#\n"
11733 "# Frequency table (F0 - F5, default is F0 ATSC bcast)\n"
11734 "# You can try -Fn -S option a few n's for optimal cable frequencies.\n"
11735 "# If you need different frequencies, edit the source for table n.\n"
11736 "# This sets the frequency table to one of the following definitions:\n"
11737 "# 0 8-VSB ATSC bcast 1 QAM EIA-IRC\n"
11738 "# 2 QAM EIA-HRC 3 QAM Std-IRC\n"
11739 "# 4 QAM Std-HRC 5 QAM us-cable\n"
11740 "#\n"
11741 "# Saddr:port\n"
11742 "# If C-line cable optional parameters are specified for remote EPG\n"
11743 "# this address and port are used for the remote atscap EPG server.\n"
11744 "# You can use a name and put it in /etc/hosts. You will need to\n"
11745 "# co-ordinate to the remote server for your C-line rpgn/repg values.\n"
11746 "#\n"
11747 "# I lines, user interface settings. Can be omitted. Updates on next save.\n"
11748 "#\n"
11749 "# Cptc:call:net:auto:program:audio[:rpgn:repg]\n"
11750 "# ptc is offset in F[0-5] frequency table; call is 7 chars; \n"
11751 "# net is 3 chars; auto is non-zero to get EPGs every 3hrs\n"
11752 "# program is MPEG program number; audio is 0 or 1 (SAP)\n"
11753 "# [cable option: get bcast EPG program rpgn from remote EPG repg]\n"
11754 "#\n"
11755 "# Vchannel:yyyymmdd:hh:mm:len:nameX\n"
11756 "# channel = LCN index, date and start time, len in minutes\n"
11757 "# It is converted to this when the config is saved:\n"
11758 "# Vchannel:epochseconds:len:nameX\n"
11759 "3 where epoch starts at 00:00:00 1-Jan-1970\n"
11760 "#\n"
11761 "# Wchannel:hh:mm:len:days:nameX\n"
11762 "# channel = LCN index, daily start time, len in minutes, days to cap\n"
11763 "#\n"
11764 "# Zname or Zptc[:drift] (PTC not LCN) time service configuration\n"
11765 "# Zname will use ntpdate to fetch time from ntp server 'name'.\n"
11766 "# I know ntpdate is deprecated, but it is working well enough here.\n"
11767 "#\n"
11768 "# Z0 will disable any time adjustments. This is the default, because.\n"
11769 "# most people already have ntpd running and also because most stations\n"
11770 "# are still sending useless timestamps in the ATSC STT. It is possible\n"
11771 "# you will find 1 good ATSC STT source on bcast or cable. If you find\n"
11772 "# one that works, but has stable offset, use :drift offset (seconds).\n"
11773 "#\n"
11774 "# Zhost runs ntpdate to host every 3 hours or use broadcast time with:\n"
11775 "# Zptc = offset in F[0-5] frequency table that has working ATSC STT\n"
11776 "# drift = ATSC STT drift, use +/- offset seen on console bottom right.\n"
11777 "#\n"
11778 "# SEE ALSO:\n"
11779 "# atscap_conf(5)\n"
11780 "#\n\n"
11782 #endif
11784 /* if cable, use EPG server line for broadcast to cable matching */
11785 if (0 != arg_frtable) fprintf( o, "F%d\n\n", arg_frtable);
11786 if (0 != *epg_srv) fprintf( o, "S%s\n\n", epg_srv);
11788 #ifdef USE_WWW
11789 /* save web user interface config */
11790 fprintf( o, "I%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d\n\n",
11791 web_config,
11792 web_legend,
11793 web_stats,
11794 scan_png,
11795 scan_snr,
11797 pgm3.find,
11798 pgm3.hide,
11799 pgm3.age,
11801 epg_grd,
11802 epg_sb,
11803 use_css
11805 #endif
11807 /* save automatic search list */
11808 // fprintf( o, "# Search events\n");
11809 for (i=0; i<search_idx; i++) {
11810 fprintf( o, "A%s", search_list[i].name );
11811 memset( ba, 0, sizeof(ba));
11812 if (search_list[i].chan >= 0) {
11813 fprintf( o, ":%02d", search_list[i].chan);
11814 if (search_list[i].days > 0) {
11815 utoab( ba, 7, search_list[i].days );
11816 fprintf( o, ":%s", ba );
11819 advrlog( LOG_INFO, "%s auto search %s:%d:%7s", WHO,
11820 search_list[i].name, search_list[i].chan, ba);
11821 fprintf( o, "\n");
11823 if (search_idx > 0) fprintf( o, "\n");
11825 /* save channels on the scan list */
11826 for (i=0; i<scan_idx; i++) {
11827 ch = scan_list[i];
11828 s = &ptc[ ch ].sig;
11830 /* old style */
11831 fprintf( o, "C%02d:%s:%s:%d:%d:%d:%d:%d\n",
11832 s->ptc, s->call, s->sid, s->pgto, s->pn, s->va,
11833 s->pnx, (s->pnx < 1)?0:s->pnxf );
11835 #if 0
11836 /* new style */
11837 fprintf( o, "D%02d:%d:%d:%s:%s",
11838 s->ptc, s->pn, s->va, s->call, s->sid);
11840 if (0 != s->pgto) {
11841 fprintf( o, ":%d", s->pgto);
11842 if (0 != s->pnx)
11843 fprintf( o, ":%d:%d",
11844 s->pnx, (s->pnxf < 0)?ch:s->pnxf );
11846 fprintf( o, "\n" );
11847 #endif
11849 /* save timers that match the channel */
11850 for (j = 0; j < timer_idx; j++) {
11851 t = &timer[j];
11853 if (T_SEARCH == t->qstat) continue;
11854 if (-1 == t->start) continue;
11855 if (scan_list[i] != t->chan) continue;
11857 /* volatile timers, kept until expired */
11858 if ( (-1 != t->start) /* !search */
11859 && ((t->start + t->len) > utsnow) /* !expired */
11860 && (0 == t->days) /* !daily/weekly */
11864 /* Save volatile timer */
11865 fprintf( o, "V%02d:%010d:%03d:%s\n",
11866 t->chan, t->start, t->len/60, t->name );
11868 advrlog( LOG_INFO, "%s vtimer V%02d:%010d:%05d:%s\n",
11869 WHO, t->chan, t->start, t->len, t->name );
11870 continue;
11873 /* Arrival here means it's a weekday timer */
11875 memset( ba, 0, sizeof(ba));
11876 utoab( ba, 7, t->days );
11878 fprintf( o, "W%02d:%02d:%02d:%03d:%s:%s\n",
11879 t->chan,
11880 t->h,
11881 t->m,
11882 t->len / 60,
11884 t->name );
11886 advrlog( LOG_INFO, "%s wtimer W%02d:%02d:%02d:%03d:%s:%s",
11887 WHO,
11888 t->chan,
11889 t->h,
11890 t->m,
11891 t->len / 60,
11893 t->name );
11895 fprintf( o, "\n");
11897 fprintf( o, "\n");
11899 /* Save Time source */
11900 fprintf( o, "# Zch gets time from PTC ch. Zhost gets time from ntpd\n");
11901 if (cap_zc > 0) {
11902 fprintf( o, "Z%d", cap_zc);
11903 if (0 != cap_zd) fprintf( o, ":%d", cap_zd);
11904 } else {
11905 if (0 != *ntp_name) {
11906 fprintf( o, "Z%s", ntp_name);
11907 } else {
11908 fprintf( o, "Z0");
11911 fprintf( o, "\n\n");
11913 fflush( o );
11914 fclose( o );
11916 /* make configuration auto-reload not trigger, no need for it */
11917 i = stat( fn, &st ); /* get file time AFTER close */
11919 /* no error? */
11920 if (i == 0) {
11921 /* global cf file time is now, more or less */
11922 cf_mt = (int)st.st_mtime;
11925 advrlog( LOG_INFO, "saved config %s", fn);
11928 /* if timer[q] has weekday bits, add schedule interval to timer[q].start
11929 otherwise delete the volatile timer and sort the timer list.
11931 /* static */
11932 void
11933 reschedule_timer ( int q, char *caller )
11935 if ((q < 0) || (q >= timer_idx)) return; /* sanity check */
11937 if (T_SEARCH == timer[ q ].qstat) return; /* nop search events */
11939 advrlog( LOG_INFO, "%s -> timer[%d] %s",
11940 caller, WHO, q, timer[q].name);
11942 if (0 != timer[ q ].days) { /* weekday bits? */
11943 timer[ q ].start += timer[ q ].future;
11944 timer[ q ].future = calc_future_date( q );
11946 /* clear event name and description */
11947 timer[ q ].ename[0] = 0;
11948 timer[ q ].edesc[0] = 0;
11950 /* config does not change for weekday timers, only start time */
11951 /* timer_sort = ~0; */ /* caller should do this */
11952 refresh.timers = 1;
11954 } else {
11956 /* no rescheduling possible for volatile timer, only delete */
11957 delete_timer ( q, WHO );
11959 /* config does change, save it */
11960 save_config( WHO );
11961 refresh.timers = 1;
11965 /* show all timers currently in the timer list.
11966 it also checks current time against timer for timer events.
11967 show clock calls this once per second to trigger timers.
11969 /* TODO: split apart and simplify */
11970 /* static */
11971 void
11972 show_timers ( void )
11974 int i, k, j;
11975 int timer_check = 0; /* -1 if current time not within timer range */
11976 int oldstatus;
11977 int tl = 0;
11978 char *sc, *tc, *rc; /* status, timer, reschedule colors */
11979 char chan_rec[16];
11980 char len_rec[16];
11981 char dst;
11982 struct qtimer_s *t;
11984 /* hold down while config reloads */
11985 if (0 != timer_lock) return;
11987 memset( date_rec, 0, sizeof(date_rec));
11988 memset( date_fut, 0, sizeof(date_fut));
11989 memset( chan_rec, 0, sizeof(chan_rec));
11990 memset( len_rec, 0, sizeof( len_rec));
11992 k = 4; /* default for everything except channel list */
11993 if ( D_CHANNELS == display_type ) k = 2;
11995 /* should fix using g p v from channel list */
11996 if ( D_TIMERS == display_type )
11997 snprintf( csp, sizeof(csp)-1, "%s", "\033[3;1H");
11999 /* no timers, nothing to do except erase timer[0] 'DONE' display */
12000 if ( 0 == timer_idx )
12002 /* only do this once refresh flag set, then reset the flag */
12003 if ( (0 != refresh.timers) && ( D_TIMERS == display_type ) )
12005 for (i = 0; i < user_lines; i++)
12006 aprintf( stderr, "\033[%d;1H" CEL, k+i+1 );
12007 refresh.timers = 0; /* nothing to refresh */
12010 /* stop an existing timer capture if one in progress */
12011 /* in case config file reload erased the current one */
12012 if ( CAP_TIME == cap_now )
12013 stop_capture( "show_timers stopped last timer 0", FAIL_NONE );
12014 return; /* nothing left to do, no more timers */
12018 /* debug pause each update of timer list
12019 if ( CAP_NONE == cap_now) while ( console_getch() == 0 );
12022 /* STATUS: TODAY FUTURE ACTIVE STREAM IGNORE DONE,
12023 FUBAR! if not handled for whatever reason
12027 /* one-shot re-sort every time this called */
12028 if (timer_sort != 0) {
12029 advrlog( LOG_INFO, "%s triggers sort_timers", WHO );
12030 sort_timers();
12031 /* set up one-shot re-draw */
12032 refresh.timers = 1;
12035 /* check timer 0 for active, returns -1 if not currently active */
12036 timer_check = test_timer( 0 );
12038 #if 1
12039 /* FIXME: this is where the double-start of back-to-back timers occurs */
12040 /* timer0 not within current time. was timer cap that is now over */
12041 if (-1 == timer_check) {
12042 if (CAP_TIME == cap_now) /* was timer cap, now over */
12043 stop_capture( "show_timers timer_check == -1", FAIL_NONE );
12045 #endif
12047 /* display status for each timer, set/clear cap_now */
12048 for (j = 0; j < user_lines; j++)
12051 /* scrolling EPG, then exit to timer list makes timer #'s go negative */
12052 if (timer_offset < 0) timer_offset = 0;
12054 /* default is space, but will chg to - or + for timer DST overlap */
12055 i = j + timer_offset;
12057 /* end of list blanks the rest of screen then turns off refresh until needed
12058 if ( i >= timer_idx) break; // list done yet?
12060 if ( i >= timer_idx)
12062 if (0 != refresh.timers)
12064 if ( D_TIMERS == display_type )
12066 /* annoying: blanks pkt stat display until 1s refresh timeout */
12067 /* aprintf( stderr, "\033[%d;1H%s", j+k+1, CCE); */
12068 aprintf( stderr, "\033[%d;1H%s", j+k+1, CEL);
12069 /* refresh.timers = 0; */
12072 continue;
12075 t = &timer[i];
12077 /* if this changes, timer refresh is set for list update */
12078 oldstatus = t->qstat;
12080 sc = tc = rc = BN;
12082 snprintf( chan_rec, sizeof(chan_rec)-1, "%2d", t->chan);
12084 /* search timers have optional chan and wd but no dates or lengths */
12085 /* advrlog( LOG_INFO, "%s check %s %s", WHO, t->name, st_a[t->qstat] ); */
12087 if (T_SEARCH == t->qstat) {
12088 t->tcol = sc = BM;
12089 date_rec[0] = 0;
12090 date_fut[0] = 0;
12091 len_rec[0] = 0;
12092 if (0 == t->chan) chan_rec[0] = 0;
12094 /* advrlog( LOG_INFO, "%s T_SEARCH0 %s", WHO, t->name); */
12096 } else {
12098 /* weekday bits set? */
12099 if (t->days != 0)
12101 dst = ' ';
12102 trec2 = t->start + t->future;
12103 memcpy( &tloc2, localtime( &trec2 ), sizeof( tloc2 ) );
12105 /* indicate timer future dst status differs from timer start */
12106 if (tloc2.tm_isdst != tloc.tm_isdst )
12108 if (tloc2.tm_isdst != 0) {
12109 dst = '+'; /* spring forward */
12110 // trec2 -= SECHR;
12111 } else {
12112 // trec2 += SECHR;
12113 dst = '-'; /* fall back */
12115 /* fix display time */
12116 memcpy( &tloc2, localtime( &trec2 ), sizeof( tloc2) );
12119 snprintf( date_fut, 17, "%s", asctime( &tloc2 ) );
12120 /* date_fut[16] = 0; */
12121 date_fut[10] = dst;
12122 } else {
12123 date_fut[0] = 0; /* no future date, no text to display */
12126 timer_check = test_timer( i ); /* returns -1 or i */
12128 /* reschedule done weekday timer, or remove if volatile timer */
12129 if (-1 == timer_check) {
12130 if (timer_idx > 0) {
12131 if (timer[0].qstat == T_DONE) {
12132 reschedule_timer( 0, WHO );
12136 /* see if timer needs to start or be ignored */
12137 if ( i == timer_check )
12139 /* check cap now flag to see if start or ignore */
12140 switch( cap_now )
12142 /* no cap in progress, ok to activate */
12143 case CAP_NONE:
12144 sc = BR;
12145 tc = BG;
12147 t->qstat = T_ACTIVE; /* only visible until AOS done */
12148 advrlog( LOG_INFO, "%s T_ACTIVE0 %s", WHO,
12149 t->name);
12151 cap_chan = t->chan;
12152 timer_rec = i; /* set the current cap queue */
12153 cap_now = CAP_TIME;
12155 display_type = D_TIMERS; /* 0 */
12156 break;
12158 /* manual cap already in progress, ignore this timer */
12159 case CAP_USER:
12160 t->qstat = T_IGNORE;
12161 advrlog( LOG_INFO, "%s T_IGNORE0 %s", WHO,
12162 t->name);
12163 /* force list update, manual timer ignored */
12164 /* refresh.timers = 1; */
12165 break;
12167 /* triggers after ACTIVE, which displays until cap starts */
12168 case CAP_TIME:
12169 if (i == timer_rec) /* if i == 0 should do same */
12171 t->qstat = T_STREAM;
12172 advrlog( LOG_INFO, "%s T_STREAM0 %s", WHO,
12173 t->name);
12174 cap_chan = t->chan;
12175 sc = BW;
12176 tc = BW; /* current active timer is bright white */
12177 } else {
12178 t->qstat = T_IGNORE;
12179 advrlog( LOG_INFO, "%s T_IGNORE1 %s", WHO,
12180 t->name);
12182 break;
12184 /* info cap needs to stop when timer activates */
12185 case CAP_INFO:
12186 t->qstat = T_ACTIVE;
12187 advrlog( LOG_INFO, "%s T_ACTIVE1 %s", WHO,
12188 t->name);
12189 stop_capture( "show timers2", FAIL_NONE);
12191 /* this isn't working? maybe needs a hold down until cap stopped */
12192 /* return; */
12193 break;
12195 default:
12196 break;
12200 /* if stop time is in the past, see if can queue for future */
12201 if ((t->start+(t->len-t->secdt)) < utsnow)
12203 /* note: changes from this too quick to see this */
12204 t->qstat = T_DONE;
12205 advrlog( LOG_INFO, "%s T_DONE0 %s", WHO,
12206 t->name);
12207 sc = BW;
12208 tc = BW;
12210 display_type = D_TIMERS; /* 0 */
12212 /* if this timer has weekday bits, reschedule it */
12213 if ( t->days != 0 ) {
12214 /* fix start to next re-queue time */
12216 advrlog( LOG_INFO, "weekday timer calc future date %s",
12217 t->name);
12219 t->future = calc_future_date( i );
12220 t->start += t->future;
12222 /* list needs resorting since it's aged to bottom */
12223 timer_sort = ~0;
12224 refresh.timers = 1;
12225 /* otherwise this is a volatile timer that needs removal */
12226 } else {
12227 /* don't remove search timer entry with start -1 */
12228 if (-1 != t->start) {
12230 /* remove timer, should call delete_timer() instead? */
12231 /* shouldn't need sorting */
12232 int m;
12234 advrlog( LOG_INFO, "%s volatile timer %s deleted",
12235 WHO, t->name);
12237 /* not last timer in list? */
12238 if (i < (timer_idx - 1) ) {
12239 for (m=i+1; m < timer_idx; m++)
12240 memcpy( &timer[m-1],
12241 &timer[m],
12242 sizeof(struct qtimer_s) );
12244 timer_idx--;
12245 refresh.timers = 1;
12247 /* update config to remove the expired timer */
12248 save_config( WHO);
12250 /* volatile got deleted, see if next on list is search timer */
12251 if (T_SEARCH == t->qstat) {
12252 #if 0
12253 t->tcol = sc = BM;
12254 date_rec[0] = 0;
12255 date_fut[0] = 0;
12256 len_rec[0] = 0;
12257 if (0 == t->chan) chan_rec[0] = 0;
12258 advrlog( LOG_INFO, "%s T_SEARCH0 %s", WHO,
12259 t->name);
12260 #endif
12261 refresh.timers = 1;
12262 /* loop needs to restart or else it will corrupt the first search entry */
12263 return;
12270 /* start time in future? */
12271 if (t->start > utsnow)
12273 /* less than 0000 hours tomorrow means it's a timer for today */
12274 if ( t->start < (utsmid+SECDAY) ) {
12275 t->qstat = T_TODAY;
12276 advrlog( LOG_INFO, "%s T_TODAY0 %s", WHO, t->name);
12277 sc = BG;
12278 rc = BN;
12279 tc = BN;
12281 t->future = calc_future_date( i );
12282 if (i > 0) {
12283 struct qtimer_s *t0 = &timer[i-1];
12284 struct qtimer_s *t1 = &timer[i];
12286 if ( (t1->start >= t0->start)
12287 && (t1->start < t0->start+t0->len) ) {
12288 t->qstat = T_IGNORE;
12289 advrlog( LOG_INFO, "%s T_IGNORE2 %s", WHO,
12290 t->name);
12293 } else {
12294 t->qstat = T_FUTURE;
12296 /* advrlog( LOG_INFO, "%s T_FUTURE0 %s", WHO, t->name); */
12298 tc = rc = sc = BN;
12299 t->future = calc_future_date( i );
12302 /* if weekday reschedule bits set */
12303 if ( t->days != 0 ) {
12304 /* does timer start after 23:59:59 today? */
12305 if ( t->start >= (utsmid + SECDAY) ) {
12306 t->qstat = T_FUTURE;
12308 /* advrlog( LOG_INFO, "%s T_FUTURE1 %s", WHO, t->name); */
12310 tc = rc = sc = BN;
12314 /* if more than 1 timer */
12315 if (i > 0) {
12316 struct qtimer_s *u;
12317 u = &timer[i-1];
12319 /* if timer overlaps previous, set ignore */
12320 if ( (t->start >= u->start)
12321 && (t->start < (u->start + u->len)) )
12323 t->qstat = T_IGNORE;
12324 tc = rc = sc = BR;
12328 /* blink red when within 30 minutes of activation
12329 red is within 1 hour
12330 yellow is 1-6,
12331 cyan is 6-12,
12332 green is 12+ (default)
12335 TODO: Change timer line colors to:
12336 brite white blink for cap
12337 brite white for within 30m
12338 brite cyan within 1 hr
12339 brite yellow within 3 hours
12340 brite green within 12 hours
12341 brite red ignored timer
12342 normal is search name or more than 12 hours in future
12344 Timer status colors:
12345 brite white streaming
12346 brite magenta search
12347 brite green today
12348 brite red ignore
12349 normal future
12351 if ( (t->start) < (utsnow + SECDAY) )
12353 tc = BG; /* start at green and work upwards */
12355 /* tc should be constant for the compiled file */
12356 if (t->start < (utsnow + (SECHR * 12))) tc = BC;
12357 if (t->start < (utsnow + (SECHR * 6))) tc = BY;
12358 if (t->start < (utsnow + SECHR)) tc = BR;
12359 if (t->start < (utsnow + (SECMIN * 30))) tc = BR BL;
12360 if (T_IGNORE == t->qstat) rc = tc = BN;
12362 /* if timer color is different, trigger timer redraw */
12363 if (t->tcol != tc)
12365 refresh.timers = 1;
12366 t->tcol = tc;
12370 snprintf( chan_rec, sizeof(chan_rec)-1, "%2d", t->chan);
12372 /* round timer len up to nearest minute */
12373 tl = t->len / 60;
12374 if ( (t->len % 60) >= 1) tl++;
12375 snprintf( len_rec, sizeof(len_rec)-1, "%3d", tl);
12378 /* if status changed, redraw all timers */
12379 if (oldstatus != t->qstat) refresh.timers = 1;
12381 if ( 0 != refresh.timers )
12383 /* convert weekday bits to ascii bits */
12384 utoab( bascii, 7, t->days );
12386 /* if it was all 0, don't display it */
12387 if (t->days == 0) bascii[0] = 0;
12389 trec1 = t->start;
12390 memcpy( &tloc1, localtime( &trec1 ), sizeof( tloc1 ) );
12392 dst = ' ';
12394 /* indicate current DST status is different from timer DST status */
12395 if (tloc1.tm_isdst != tloc.tm_isdst)
12397 if (tloc1.tm_isdst != 0) {
12398 /* subtract hour for correct display time */
12399 // trec1 -= SECHR;
12400 dst = '+'; /* spring forward */
12401 } else {
12402 /* add hour for correct display time */
12403 // trec1 += SECHR;
12404 dst = '-'; /* fall back */
12406 /* start time display corrected for DST; fix tloc1 same */
12407 memcpy( &tloc1, localtime( &trec1 ), sizeof ( tloc1 ) );
12410 /* date_rec gets time string after DST fix if any */
12411 if (T_SEARCH != t->qstat) {
12412 snprintf( date_rec, 17, "%s", asctime( &tloc1 ) );
12413 date_rec[10] = dst;
12415 if (T_IGNORE == t->qstat) sc = BR;
12417 /* show timer */
12418 if ( D_TIMERS == display_type ) {
12420 aprintf( stderr, "\033[%d;1H" BN
12421 /* tn stat ch name */
12422 "%3d %s%s %s%2s %-19s ",
12423 j+k+1, i, sc, st_a[t->qstat], tc, chan_rec, t->name);
12425 /* turn off blink with BN */
12426 aprintf( stderr, "%16s %3s "BN , date_rec, len_rec);
12428 if (0 != t->days) {
12430 /* build weeday ECMA-48 highlighted string */
12431 batowd( i );
12432 aprintf( stderr, BK "%s %s%16s" BN,
12433 wecma,
12435 date_fut );
12437 } else {
12438 /* volatile timer will try to fill in with partial episode description */
12439 char d[PDZ];
12440 int cm;
12441 cm = columns - 55;
12442 if (cm < 0) cm = 24;
12443 if (cm > sizeof(d)) cm = PDZ - 1;
12445 astrncpy( d, t->edesc, cm);
12447 aprintf( stderr, "%s%s", BN, d );
12449 aprintf( stderr, CEL );
12454 /* one-shot output redux */
12455 refresh.timers = 0;
12459 /* delete renamed file, gets called more than once */
12460 /* static */
12461 void
12462 remove_renamed ( void )
12464 char f[256];
12465 int rmdone;
12467 rmdone = 0;
12469 /* this is a one shot */
12470 if ( 0 != *del_name ) {
12472 /* if path, point past it */
12473 filebase( f, del_name, F_FILE);
12475 /* CR instead of NL to overwrite same line */
12476 aprintf( stderr, CR "remove %s" CEL, f);
12477 advrlog( LOG_INFO, "remove1 %s", f );
12479 /* dummy doesn't do remove, and neither does -o */
12480 if (0 == test_mode)
12481 if ( 0 == *arg_ofile )
12482 remove( del_name );
12484 rmdone = 1;
12486 /* del_name is a one shot, so clear it */
12487 *del_name = 0;
12490 /* removed the renamed del_name file, now check for zap remove too */
12493 /* user hits z to remove the previous and current files */
12494 if (0 != cap_zap) {
12495 filebase( f, out_name, F_FILE );
12496 advrlog( LOG_INFO, "ZAP%02d %s", cap_chan, f );
12497 if ( out_file > 2 ) {
12499 /* if path, point past path */
12501 /* CR instead of NL to overwrite same line */
12502 aprintf( stderr, CR "zap %s" CEL, f );
12504 /* not dummy? */
12505 if (0 == test_mode) {
12507 /* not cli single capture? */
12508 if ( 0 == *arg_ofile ) {
12510 /* order of removal is important to web ui, so refresh doesn't show log href */
12511 filebase( f, out_name, F_PFILE );
12513 /* remove transport stream log file */
12514 snprintf( del_name, sizeof(del_name), "%s.html", f);
12515 remove( del_name );
12516 advrlog( LOG_INFO, "removed %s", del_name);
12518 /* remove transport stream sequences and frames file */
12519 if (0 != arg_frames) {
12520 snprintf( del_name, sizeof(del_name), "%s.tsx", f);
12521 remove( del_name );
12522 advrlog( LOG_INFO, "removed %s", del_name);
12525 *del_name = 0;
12527 /* big file last so web ui refresh looks correct */
12528 remove( out_name );
12529 advrlog( LOG_INFO, "removed %s", out_name);
12534 /* zap is also a one shot, so clear it */
12535 cap_zap = 0;
12536 cap_zapped = ~0;
12537 rmdone = 1;
12541 #ifdef USE_SIGNALS
12542 /* want to close the device properly if possible */
12543 /* static */
12544 void
12545 signal_test ( void )
12547 if (0 == sig_val) return; /* nothing to do? */
12548 if (sig_val < 32)
12549 advrlog( LOG_INFO, "received SIG%s", sig_text[sig_val] );
12551 switch( sig_val )
12554 /* these three indicate user requested program terminate */
12555 case SIGINT:
12556 case SIGTERM:
12557 case SIGQUIT:
12558 sig_kill = ~0;
12559 dvrlog( LOG_INFO, "exiting on signal %d", sig_val);
12560 stop_capture( WHO, FAIL_NONE );
12561 break;
12563 /* GNU screen changed the term size, or xterm was resized */
12564 case SIGWINCH:
12565 dvrlog( LOG_INFO, "%s tty dimension changed", WHO);
12566 test_termsize();
12567 break;
12569 case SIGPIPE:
12570 dvrlog( LOG_INFO, "%s broken pipe or socket", WHO);
12571 break;
12573 /* even with SEGV still want to try to close device properly if possible */
12574 case SIGSEGV:
12575 sig_kill = ~0;
12576 break;
12578 default:
12579 if (sig_val < 32)
12580 dvrlog( LOG_INFO, "%s signal %s ignored",
12581 WHO, sig_text[sig_val] );
12582 break;
12584 sig_val = 0;
12587 #endif
12589 /* proto this one rather than esc handling below */
12591 /* non-blocking non-echoing single-char stdin, from mzplay.c */
12592 /* static */
12594 console_getch ( void ) {
12595 unsigned c = 0;
12597 #ifdef USE_SIGNALS
12598 signal_test(); /* any flags worth noticing? */
12599 if (0 != sig_kill) console_exit(0);
12600 #endif
12601 if (0 != arg_detach) return 0;
12603 if (console_init) {
12604 struct termios modes;
12605 int f;
12607 console_init = 0;
12609 if ( tcgetattr(0, &modes) < 0 )
12610 advrlog( LOG_ERR, "c_getch tcgetattr" );
12612 modes.c_lflag &= ~ICANON;
12613 modes.c_lflag &= ~ECHO;
12615 if ( tcsetattr(0, TCSAFLUSH, &modes) < 0 )
12616 advrlog( LOG_ERR, "c_getch tcsetattr" );
12618 f = 0;
12619 fcntl( 0, F_GETFL, &f );
12620 f |= O_NONBLOCK;
12621 fcntl( 0, F_SETFL, f );
12623 if ( read( 0, &c, 1 ) < 1 ) c = 0;
12625 /* sleep is not always good, can miss keys */
12626 /* nanosleep( &console_read_sleep, NULL); */
12627 return c;
12631 when console scan gets ESC, look for [ and if so do these
12632 make easier to use with following keys: up/dn/lt/rt/hm/in/dl/en/pu/pd
12634 /* static */
12635 void
12636 console_scan_esc ( unsigned char *kb )
12638 unsigned char k;
12639 int arrow;
12641 *kb = console_getch(); /* get char after ESC */
12642 if (0 == *kb) return; /* nothing pending from function keys */
12643 if ('[' != *kb) return; /* no [ means no arrows or function keys */
12646 /* utstpg = utsnow + PROGRAM_GUIDE_TIMEOUT; */
12648 /* nanosleep( &console_read_sleep, NULL); */
12649 *kb = console_getch(); /* get what follows ESC [ */
12651 /* should be cursor/arrow keys */
12652 /* dvrlog( LOG_INFO, "ESC[ %02X", *kb); */
12654 arrow = 0;
12655 /* "ESC [" and 4 choices for arrows are A B C D */
12656 switch( *kb ) {
12657 /* no chars to clear after these */
12658 case 'A':
12659 *kb = KB_UP; /* UP ARROW */
12660 arrow = ~0;
12661 break;
12663 case 'B':
12664 *kb = KB_DN; /* DOWN ARROW */
12665 arrow = ~0;
12666 break;
12668 case 'C':
12669 *kb = KB_RT; /* RIGHT ARROW */
12670 arrow = ~0;
12671 break;
12673 case 'D':
12674 *kb = KB_LF;
12675 arrow = ~0;
12676 break;
12678 case 'H':
12679 *kb = KB_HM; /* HOME */
12680 arrow = ~0;
12681 break;
12683 case 'F':
12684 *kb = KB_EN; /* END */
12685 arrow = ~0;
12686 break;
12690 /* have an arrow so get out, no need for ~ term */
12691 if (arrow != 0) return;
12693 /* checked for arrows, kb still has numeric, so check for ~ term char */
12694 k = console_getch();
12695 if (0 == k) { *kb = 0; return; } /* it's incomplete so abort */
12696 if ('~' != k) { *kb = 0; return; } /* no ~ term char so abort */
12698 /* check char after ESC [ again */
12699 switch ( *kb ) {
12701 case '7': /* newer aterm doing this? */
12702 case '1':
12703 *kb = KB_HM; /* HOME */
12704 break;
12706 case '2':
12707 *kb = KB_IN; /* INSERT */
12708 break;
12710 case '3':
12711 *kb = KB_DL; /* DELETE */
12712 break;
12714 case '8': /* newer aterm doing this? */
12715 case '4':
12716 *kb = KB_EN; /* END */
12717 break;
12719 case '5':
12720 *kb = KB_PU; /* PAGE UP */
12721 break;
12722 case '6':
12723 *kb = KB_PD; /* PAGE DOWN */
12724 break;
12725 default:
12726 *kb = 0; /* try to prevent false triggers if no conditions met */
12727 break;
12730 return;
12733 /* wrapper to return keyboard special keys as single byte */
12734 /* static */
12735 void
12736 console_getch_fn ( unsigned char *kb )
12738 /* nanosleep( &console_read_sleep, NULL ); */
12739 *kb = console_getch();
12741 #ifdef USE_POWERDOWN
12742 /* any key resets dvb power down time */
12743 if (0 != *kb) utspdd = utsnow + USE_POWERDELAY;
12744 #endif
12746 if (0x1B != *kb) return; /* let normal keys slide */
12747 console_scan_esc( kb ); /* try to parse function keys */
12751 /* REMOVEME: this is now obsolete because s->chan is scan_list offset */
12752 /* this will break on cable. what calls it? */
12753 /* return scan list index of channel, if not found, return 0 (ch 2) */
12754 /* static */
12756 get_scanlist_offset ( unsigned int channel )
12758 int i;
12759 for (i=0; i<scan_idx; i++)
12760 if (scan_list[i] == channel)
12761 return i;
12762 return 0;
12766 timed signal lock check
12768 return 0 if no lock, 1 if lock, channel is provided in sig_s s*
12770 If caller wants avgs, s->*_sum = 0; then do math after (x) calls.
12771 s->lock_sum is what you use for math against other s->*_sum
12773 It usually takes a few of these, if not already tuned in, because of
12774 tank stabilization and AGC adjustments. You can actually see the
12775 AGC kick in on HD2/3000 after a few calls on a weaker station.
12777 This also times get signal and puts the inverse of time difference
12778 into scan_rate for tracking number of scans doable in one second.
12780 /* static */
12781 void
12782 get_signal_lock ( struct sig_s *s )
12784 int ir = 0;
12785 struct timespec sig_start = { 0, 0 };
12786 struct timespec sig_stop = { 0, 0 };
12787 struct timespec sig_diff = { 0, 0 };
12789 /* want to keep track of how many scans per second can be done */
12790 clock_gettime( clock_method, &sig_start );
12792 /* PORTABILITY:
12793 Set channel enforces at least a 250ms delay on frequency change. It's
12794 probably better to use 500ms but the console seems sluggish with that.
12795 Delay after tuning should be enforced by device driver and not by user.
12797 HD3000 driver is zeroing the min_delay_ms. What do other drivers do?
12799 set_channel( s, WHO );
12801 s->lock = 0;
12802 s->strength = 0;
12804 #undef SIG100
12805 #ifdef SIG100
12806 s->lock = 1;
12807 s->strength = 100; /* leave color normal to alert it's fake */
12808 scan_rate = 10; /* was 9 */
12809 return;
12810 #endif
12812 /* dummy */
12813 if (test_mode != 0) {
12814 clock_gettime( clock_method, &sig_stop );
12815 return;
12818 /* new method from ideas in dvb-atsc-tools and Peter Knaggs */
12819 if ( (0 == test_mode) && (0 == arg_dummy) )
12821 s->strength = 0; /* if the ioctl fails, */
12822 s->freqr = s->freq; /* want freqret same as freqset */
12823 s->fohz = 0; /* and offset 0 hz */
12825 memset( &fe_status, 0, sizeof(fe_status));
12827 /* NOTE: latest patched driver reads all the raw data with read status, then
12828 read SNR and read signal strength work on data collected by read status.
12829 This is to reduce i2c chatter on the unreliable HD3000 i2c bus, which
12830 could probably be fixed with 1uF coupling cap on i2c clock and data lines.
12831 This should be compatible with older drivers as well, but the i2c
12832 chatter will be 3x higher than with my v4l-dvb-HD3000-ATSC-0.3 driver.
12834 ir = ioctl( fe_file, FE_READ_STATUS, &fe_status );
12835 ir = 0; /* dummy the status check */
12837 if (ir < 0) {
12838 dvrlog( LOG_INFO, "FE READ STATUS fail %d", ir);
12839 return;
12841 advrlog( LOG_INFO, "%s FE READ STATUS 0x%04X %s", s->call, fe_status,
12842 (fe_status & FE_HAS_LOCK)?"FE_HAS_LOCK":"");
12844 s->lock = (fe_status & FE_HAS_LOCK) ? 1:0;
12846 if (0 == s->lock) return;
12848 ir = ioctl( fe_file, FE_READ_SIGNAL_STRENGTH, &fe_strength);
12849 s->strength = fe_strength/655;
12851 advrlog( LOG_INFO, "%s FE READ STRENGTH %04X %02d%% ",
12852 s->call, fe_strength, s->strength);
12853 if (ir < 0) {
12854 dvrlog( LOG_INFO, "FE READ STRENGTH PTC %02d FAILED %d",
12855 s->chan, ir);
12856 return;
12857 console_exit(EIO);
12860 ir = ioctl( fe_file, FE_READ_SNR, &s->snr);
12861 advrlog( LOG_INFO, "%s FE READ SNR dB %2d.%02d ",
12862 s->call,
12863 0xFF & s->snr>>8,
12864 (0xFF & s->snr * 100) >> 8 );
12866 if (ir < 0) {
12867 dvrlog( LOG_INFO, "FE READ SNR PTC %02d FAILED %d",
12868 s->chan, ir);
12869 return;
12870 console_exit(EIO);
12873 #ifdef USE_DVB_EXPERIMENTAL
12874 /* This is a new option for or51132.c. It won't work with stock driver. */
12875 /* Save frequency offset for display with [w] key. */
12876 ir = ioctl( fe_file, FE_GET_FRONTEND, &fe_param );
12877 if (0 == ir) {
12878 s->freqr = fe_param.frequency;
12879 s->fohz = s->freqr - s->freq;
12882 ir = ioctl( fe_file, FE_READ_BER, &fe_ber );
12883 s->ber = 0;
12884 if (0 == ir) s->ber = fe_ber;
12885 #endif
12888 if (s->strength > 100) s->strength = 100; /* clamp to 100% */
12889 /* color the signal even without the lock, colors are 0 to 7 */
12891 s->sig_col = &bc[ 7 & (s->strength / 13)][0]; /* sanity limit */
12892 s->smp[ s->smx ] = s->strength;
12893 s->rmp[ s->smx ] = s->snr;
12894 s->smx++;
12896 s->smx %= SIG_PNG_W;
12897 // might be faster to use below to avoid a branch?
12898 // if (s->smx >= SIG_PNG_W) s->smx = 0;
12900 clock_gettime( clock_method, &sig_stop );
12902 time_diff( &sig_diff, &sig_start, &sig_stop );
12904 #if 0
12905 /* demoth time delay on signal scan acquisition time */
12906 dvrlog( LOG_INFO, "ss %ld.%09ld se %ld.%09ld sd %ld.%09ld",
12907 sig_start.tv_sec, sig_start.tv_nsec,
12908 sig_stop.tv_sec, sig_stop.tv_nsec,
12909 sig_diff.tv_sec, sig_diff.tv_nsec);
12910 #endif
12912 scan_rate = 10; /* 10 scans/second max */
12913 /* anything over one second is bogus, avoid divide by zero */
12914 if ( (sig_diff.tv_sec == 0) && (sig_diff.tv_nsec != 0) )
12916 struct timespec scan_sleep;
12917 scan_rate = 999999999/sig_diff.tv_nsec;
12918 /* advrlog( LOG_INFO, "sig scan rate %d/s period %d nS",
12919 scan_rate, (int)sig_diff.tv_nsec );
12921 if (scan_rate < 1) scan_rate = 1; /* no zero allowed */
12922 if (scan_rate > 20) scan_rate = 20;
12924 scan_sleep.tv_sec = 0;
12925 scan_sleep.tv_nsec = 5000000; // * scan_rate;
12927 // nanosleep(&scan_sleep, NULL);
12928 } else {
12929 if (test_mode == 0)
12930 advrlog( LOG_INFO, "%s: signal scan rate bogus", WHO );
12932 return;
12936 manual timer validate
12937 check list of current timers for tstart to tstart+tlen
12938 return:
12939 0 if no error
12940 1 if time overlap with existing timer
12941 note:
12942 it will be a valid result only if all start timers don't match
12943 it doesn't check against future time nor weekday bits
12945 /* static */
12947 validate_timer ( int tstart, int tlen )
12949 int i, tstop;
12950 struct qtimer_s *t;
12952 return 0;
12954 /* FIXME: not implemented yet because I can move intersecting timers */
12956 for (i=0; i < timer_idx; i++) {
12957 t = &timer[i];
12958 /* first check for tstart intersecting a timer */
12959 if ( (tstart >= t->start)
12960 && (tstart <= (t->start + t->len) ) )
12961 return 1;
12963 /* then check for tstop intersecting a timer */
12964 tstop = tstart + tlen;
12965 if ( (tstop >= t->start)
12966 && (tstop <= (t->start + t->len) ) )
12967 return 1;
12969 /* if the sets do not intersect, return no error */
12970 return 0;
12973 /* clear all the timers */
12974 /* static */
12975 void
12976 reset_timers ( void )
12978 /* faster */
12979 memset( timer, 0, sizeof(timer) );
12980 timer_idx = 0;
12981 /* reset does not save list in case user screwed up, but next addition will */
12988 user input of timer # to reque. allows range, same as delete
12989 is actually copy of get timer delete with names changed
12991 /* static */
12992 void
12993 get_timer_reschedule ( void )
12995 char req_in[16];
12996 char *warn = BW;
12997 char req_r[8];
12998 unsigned int req_r1;
12999 unsigned int req_r2;
13000 char *p;
13001 int i;
13003 /* turn echo and blocking back on, get input */
13004 while ( 1 )
13006 aprintf( stderr, SCV BL
13007 "%s%sReschedule which timer(s) (0-%d) ?"BN" " CEL,
13008 dca.hstat, warn, timer_idx-1 );
13009 console_reset();
13010 req_in[0] = 0;
13012 fgets( req_in, sizeof(req_in)-1, stdin );
13014 /* dummy read to reset back to nonblock/nonecho */
13015 console_getch();
13017 /* redraw header and return if <ENTER> */
13018 if ( req_in[0] == 10 ) {
13019 show_headers(0);
13020 /* aprintf( stderr, "%s%s" CEL, dca.hstat, header2 ); */
13021 return;
13024 /* remove any terminating NL */
13025 if ( req_in[ strlen( req_in ) - 1 ] == 10 )
13026 req_in[ strlen( req_in ) - 1 ] = 0;
13028 warn = BR; /* 2nd or more try at input */
13030 if (strlen(req_in) > 7)
13031 continue; /* junk, try again */
13033 sscanf( req_in, "%7s", req_r );
13035 req_r1 = atoi( req_r );
13036 req_r2 = req_r1;
13038 p = strchr( req_r, '-' ); /* - means range */
13039 if (NULL != p) {
13040 *p++ = 0;
13041 req_r2 = atoi( p );
13044 /* user input error check */
13045 if ( (req_r1 >= timer_idx)
13046 || (req_r2 >= timer_idx)
13047 || (req_r2 < req_r1)
13048 ) continue;
13050 break;
13054 reschedule can re-arrange it anywhere, so prevent sort until done,
13055 unlike delete where it only makes sense to remove the first one
13056 as the others bump upwards
13058 timer_sort = 0;
13059 refresh.timers = 0;
13061 for (i = req_r1; i <= req_r2; i++)
13062 reschedule_timer( i, WHO );
13064 advrlog( LOG_INFO, "get timer reque triggers timer sort and list" );
13065 refresh.timers = 1; /* need to redraw */
13066 timer_sort = ~0; /* need to resort */
13067 show_headers(0);
13068 show_timers();
13069 /* reque does not need to save list, uses same daybits
13070 save_config( WHO );
13074 /* console 'r' key, remove or reschedule timer.
13075 it does not remove the capture file if active timer 0
13077 /* static */
13078 void
13079 timer_reschedule( void )
13081 struct sig_s *s;
13083 if (CAP_TIME == cap_now) {
13085 /* prevent add search event timer endless loop */
13086 if (-1 != timer[0].etmid ) {
13087 s = &ptc[ cap_chan ].sig;
13088 s->lzpgt = timer[0].start;
13092 get_timer_reschedule();
13095 /* conzole 'z' key, zap stops any cap but also stops test epg looping.
13096 it also deletes the capture file for the current capture.
13098 /* static */
13099 void
13100 timer_zap( void )
13102 struct sig_s *s;
13104 if (CAP_NONE == cap_now) return; /* no cap no zap */
13105 /* tell ts capture to remove the files when capture terminated */
13106 cap_zap = ~0;
13108 if (CAP_INFO == cap_now) {
13109 stop_capture( "info zap", FAIL_NONE);
13111 /* if zap aborts epg_test it should set next test epg 30 seconds ahead */
13112 if (0 != epg_test) utstpg = utsnow + 30;
13114 /* tell test epgs to stop looping */
13115 epg_test = 0;
13117 return;
13120 /* don't allow it to restart unless user adds via console or web ui */
13121 if (CAP_TIME == cap_now) {
13122 if (-1 != timer[0].etmid ) {
13123 s = &ptc[ cap_chan ].sig;
13124 s->lzpgt = timer[0].start;
13126 /* it can be presumed that timer0.qstat is always T_STREAM here? */
13127 if (T_STREAM == timer[0].qstat)
13128 reschedule_timer(0, WHO);
13131 stop_capture( WHO, FAIL_NONE );
13134 /* interactive user input of timer:
13135 ch hh mm len daymask label
13137 ch 2 - 69 channel
13138 hh 0 - 23 start hour
13139 mm 0 - 59 start minute
13140 len 1-360 program length
13141 daymask is binary, e.g. SMTWTFS=0111110 to do monday to friday only
13142 label is [not?] optional filename to save as, blank uses mmdd-hhmm-ch
13144 OLD BUGS FINALLY FIXED:
13145 27-mar-2006
13146 when daybits prior to today, and same as today but timer has passed,
13147 it would get the start time wrong and require load config to correct it.
13149 19-Aug-2007
13150 when putting in a volatile timer, with no daybits set, if the time given
13151 has passed, the timer is ignored instead of being set for tomorrow.
13153 /* static */
13154 void
13155 get_timer_add ( void )
13157 int i, c, h, m, r;
13158 char timer_in[64]; /* timer input string */
13159 char nbuf[32]; /* timer name buffer */
13160 char wdbuf[16];
13161 unsigned int wd;
13162 char *warn = BW;
13163 int ttime, ttlen, ttchan;
13164 int wdo, wdu;
13166 wdbuf[0] = 0;
13167 nbuf[0] = 0;
13169 if (timer_idx == TIMER_LIST_MAX) {
13170 aprintf( stderr, BL BR
13171 "%s TIMER MAX REACHED. Delete before adding more."
13172 CEL BN, dca.hstat );
13173 nanosleep( &msg_long_sleep, NULL );
13174 show_headers(0);
13175 return;
13178 /* turn echo and blocking back on, get input */
13179 while ( 1 )
13181 aprintf( stderr, SCV BL
13182 "%s%sCh Hh Mm Len Wd Name or +Search:Ch:Wd >"
13183 BN" " CEL, dca.hstat, warn );
13184 console_reset();
13185 fgets( timer_in, sizeof(timer_in)-1, stdin );
13187 /* dummy read to reset back to nonblock/nonecho */
13188 console_getch();
13190 /* do nothing if <ENTER> */
13191 if ( timer_in[0] == 10 ) {
13192 show_headers(0);
13193 /* aprintf( stderr, "%s%s", dca.hstat, header2); */
13194 return;
13197 /* remove the NL */
13198 if ( 10 == timer_in[ strlen( timer_in ) - 1 ] )
13199 timer_in[ strlen( timer_in ) - 1 ] = 0;
13201 warn = BR; /* 2nd or more try at input */
13203 /* TODO: try to leverage parse search for this, but not really important
13204 since user can edit the config for the same effect
13206 /* check for +search event */
13207 if ('+' == timer_in[0]) { /* add if doesn't already exist */
13208 advrlog( LOG_INFO, "add search timer");
13210 parse_search_event( &timer_in[1] );
13211 sort_searchlist();
13212 save_config( WHO );
13213 timer_sort = ~0;
13214 refresh.timers = 1;
13215 show_headers(0);
13216 return;
13219 if (strlen(timer_in) < 5) continue; /* junk, try again */
13221 sscanf( timer_in, "%d %d %d %d %s %s",
13222 &c, &h, &m, &r, wdbuf, nbuf );
13224 if ( strlen(nbuf) < 1 ) continue;
13226 wd = 0;
13227 /* try to convert ascii binary to weekday value */
13228 if ( ('0' == *wdbuf) || ('1' == *wdbuf) ) wd = abtou( wdbuf );
13230 /* range check: h 0-23, m 0-60, r 1-360, c 0-ptcmax */
13231 if ( (h >= 0) && (h < 24)
13232 && (m > -1) && (m < 60)
13233 && (r > 0) && (r < 360) /* 6 hours */
13234 && (c >= 0) && (c < ptc_max) /* channel range check */
13235 && (wd < 0x80)
13237 /* loop until a good value is input, or [ENTER] to exit */
13238 break;
13241 if (0 == wd) {
13242 dvrlog( LOG_INFO, "add volatile timer");
13244 } else {
13245 dvrlog( LOG_INFO, "add weekday timer");
13248 /* number of seconds from 0000 hours when timer starts */
13249 ttime = (SECHR * h) + (60 * m);
13251 /* number of seconds in timer */
13252 ttlen = (60 * r);
13254 /* current scan/cap channel */
13255 ttchan = c;
13257 /* make sure it's only 7 bits */
13258 wd &= 0x7F;
13260 #if 0
13261 /* NO: use IGNORE status to warn user. */
13262 /* make sure no timers in list conflict */
13263 int ok;
13264 ok = validate_timer( ttime, ttlen ); /* nop for now */
13265 if (ok != 0) return;
13266 #endif
13269 if (timer_idx >= TIMER_LIST_MAX) {
13270 dvrlog( LOG_INFO, "%s recompile with larger TIMER_MAX", WHO);
13271 return;
13275 /* fix midnight if it wrapped during input */
13276 get_time();
13277 wdo = wdu = 0; /* volatile for today has 0 offset */
13279 /* silly math tricks, find how many weekdays from now if not today,
13280 wdo will be: 0 to 6 * SECDAY
13282 #if 1
13283 if (wd != 0) {
13284 for (i=6; i>=0; i--) if (wd & (1 << i)) break;
13285 wdu = 6 - i; /* back to localtime 0 sunday ... 6 saturday */
13286 wdo = wdu - tloc.tm_wday; /* 0 is today, positive is future */
13287 if (wdo < 0) wdo += 7; /* negative is not useful? */
13288 wdo *= SECDAY; /* number of seconds per day */
13289 } else {
13291 /* If it's a volatile, and the timer is expired, add 24 hours to it.
13292 The presumption being the user is trying to enter a volatile timer for:
13293 a) an event before midnight but after now, b) an in-progress event, or
13294 c) an event occurring tomorrow but current time is past timer hh:mm+len.
13296 int et;
13297 et = utsmid + ttime + ttlen;
13299 /* This adds logic path c). Logic paths a) and b) already done. */
13300 if (utsnow > et) wdo = SECDAY;
13302 #endif
13304 timer[timer_idx].start = utsmid + wdo + ttime;
13305 timer[timer_idx].len = ttlen;
13306 timer[timer_idx].chan = ttchan;
13307 timer[timer_idx].days = wd;
13308 timer[timer_idx].qstat = T_FUTURE;
13309 timer[timer_idx].secdt = 5; /* minimum AOS time */
13311 if (wd != 0) {
13312 timer[timer_idx].future = calc_future_date( timer_idx );
13314 #if 0
13315 /* prevent user from seeing expired timer change to correct timer.
13316 user should see only the corrected timer.
13318 if (utsnow > timer[timer_idx].start) {
13319 timer[timer_idx].start = timer[timer_idx].future;
13320 timer[timer_idx].future = calc_future_date( timer_idx );
13322 #endif
13325 astrncpy( timer[timer_idx].name, nbuf, TIMER_NAME_MAX );
13326 timer_idx++;
13328 if (timer_idx > 0) {
13329 /* make the list look neat */
13330 advrlog( LOG_INFO, "timer add triggers timer sort and list");
13331 timer_sort = ~0; /* defer sort until display */
13332 refresh.timers = 1; /* make display happen */
13334 save_config( WHO ); /* add saves list */
13335 show_headers(0);
13336 show_timers();
13340 /* user input to remove timer */
13341 /* static */
13342 void
13343 get_timer_delete ( void )
13345 char del_in[16];
13346 char *warn = BW;
13347 char del_r[8];
13348 unsigned int del_r1;
13349 unsigned int del_r2;
13350 char *p;
13351 int i;
13353 /* turn echo and blocking back on, get input */
13354 while ( 1 )
13356 aprintf( stderr, SCV BL
13357 "%s%sDelete which timer(s) (0-%d) ?"BN" " CEL,
13358 dca.hstat, warn, timer_idx-1 );
13359 console_reset();
13360 del_in[0] = 0;
13362 fgets( del_in, sizeof(del_in)-1, stdin );
13364 /* dummy read to reset back to nonblock/nonecho */
13365 console_getch();
13367 /* redraw header and return if <ENTER> */
13368 if ( del_in[0] == 10 ) {
13369 show_headers(0);
13370 /* aprintf( stderr, "%s%s" CEL, dca.hstat, header2 ); */
13371 return;
13374 /* remove any terminating NL */
13375 if ( del_in[ strlen( del_in ) - 1 ] == 10 )
13376 del_in[ strlen( del_in ) - 1 ] = 0;
13378 warn = BR; /* 2nd or more try at input */
13380 if (strlen(del_in) > 7) /* 3 digits - 3 digits */
13381 continue; /* junk, try again */
13383 sscanf( del_in, "%7s", del_r ); /* 3 digits - 3 digits */
13385 del_r1 = atoi( del_r);
13386 del_r2 = del_r1;
13387 p = strchr( del_r, '-'); /* - means range */
13388 if (NULL != p) {
13389 *p++ = 0;
13390 del_r2 = atoi( p );
13393 /* user input error check */
13394 if ( (del_r1 >= timer_idx)
13395 || (del_r2 < del_r1)
13396 || (del_r2 >= timer_idx)
13397 ) continue;
13399 break;
13402 /* keep removing the same timer because the list bumps up */
13403 /* the problem here is delete search events screws up timer idx */
13405 for (i = del_r1; i <= del_r2; i++) {
13406 if (T_SEARCH == timer[ del_r1 ].qstat) {
13407 advrlog( LOG_INFO, "%s del_r1 %d", WHO, del_r1);
13408 /* remove search + events */
13409 delete_search_event( del_r1, WHO );
13410 break; /* only one because timer idx changed and r2 is useless */
13411 } else {
13412 delete_timer( del_r1, "range remove" ); /* remove events */
13415 advrlog( LOG_INFO, "timer delete triggers timer list" );
13416 refresh.timers = 1; /* need to redraw */
13417 timer_sort = 0; /* sort not needed if list is already sorted */
13418 show_headers(0);
13420 /* show_timers(); */
13421 /* use status update, but it feels slower that way? */
13423 /* delete saves list but does it have to ? */
13424 save_config( WHO );
13425 /* yes, but prob only if deleted one with weekday bits */
13429 move a single volatile or a single weekday timer # tm to config file # tc
13430 assumes parsing for sanity been done
13431 i.e.
13432 do not write to same cfg file this instance uses
13433 do not write non-existent timer
13434 timers are removed from local config after move to other config
13436 /* static */
13437 void
13438 move_timer ( int tm, int tc )
13440 FILE *f;
13441 char fo[256];
13442 char ba[8];
13443 char o[80];
13445 if (T_SEARCH == timer[tm].qstat) return; /* see move search event */
13447 snprintf( fo, sizeof(fo)-1, "%s%s%d.conf", cfg_path, NAME, tc);
13448 advrlog( LOG_INFO, "moving timer %s %d to %s", timer[tm].name, tm, fo);
13449 /* open for append: timer line of text at end of file */
13450 f = fopen( fo, "a");
13453 if ( NULL != f ) { /* only if open worked */
13454 trec1 = timer[tm].start; /* set up time */
13455 memcpy( &tloc1, localtime( &trec1 ), sizeof(tloc1) );
13457 if (timer[tm].days != 0) { /* is manual weekday event? */
13458 utoab( ba, 7, timer[tm].days ); /* set up weekday bit string */
13459 ba[7] = 0;
13461 snprintf( o, sizeof(o)-1, "W%02d:%02d:%02d:%03d:%s:%s",
13462 timer[tm].chan,
13463 tloc1.tm_hour,
13464 tloc1.tm_min,
13465 timer[tm].len / 60,
13467 timer[tm].name );
13468 } else { /* handle volatile timers */
13470 snprintf( o, sizeof(o)-1, "V%02d:%010d:%05d:%s",
13471 timer[tm].chan,
13472 timer[tm].start,
13473 timer[tm].len,
13474 timer[tm].name );
13476 dvrlog( LOG_INFO, "%s %s %s", WHO, fo, o );
13477 fprintf( f, "%s\n", o );
13478 delete_timer( tm, "move timer"); /* remove the timer after moving */
13479 fclose( f );
13481 } else {
13482 dvrlog( LOG_INFO, "error appending %s", fo);
13484 save_config( WHO );
13485 refresh.timers = 1;
13488 /* moves search timer entry and all matching events */
13489 /* static */
13490 void
13491 move_search_event ( int tm, int tc )
13493 FILE *f;
13494 char fo[256];
13495 char tn[32];
13496 char wd[8];
13497 char o[1024]; /* want at least 16 entries of 64 chars for move */
13498 char *p;
13500 int i, tf; /* counter, timer found */
13502 p = o;
13504 /* only moving search events */
13505 if (T_SEARCH != timer[tm].qstat) return;
13507 snprintf( tn, sizeof(tn)-1, "%s", timer[tm].name);
13508 snprintf( fo, sizeof(fo)-1, "%s%s%d.conf", cfg_path, NAME, tc);
13509 advrlog( LOG_INFO, "moving search %s %d to %s", tn, tm, fo);
13510 /* open for append: timer line(s) of text at end of file */
13511 f = fopen( fo, "a");
13513 if (NULL != f) {
13514 tf = 0; /* no event search or event timer found */
13516 /* copy search event */
13517 snprintf( p, 1 + SEARCH_NAME_MAX, "A%s", tn);
13518 p = o + strlen(o);
13519 if (0 != timer[tm].chan) { /* channel limit */
13520 p = o + strlen(o);
13521 snprintf( p, 4, ":%02d", timer[tm].chan);
13522 if (0 != timer[tm].days) { /* weekday limit */
13523 utoab( wd, 7, timer[tm].days );
13524 wd[7] = 0;
13525 p = o + strlen(o);
13526 snprintf( p, 9, ":%7s", wd );
13530 advrlog( LOG_INFO, "%s", o); /* first one uses o, rest use p */
13531 p = o + strlen(o);
13532 snprintf( p, 2, "\n");
13533 p = o + strlen(o);
13535 /* copy any volatile timers that match search event */
13536 for (i = 0; i < timer_idx; i++) {
13537 tf = 0;
13538 if (p >= ( o + sizeof(o) ) ) break; /* limit string build */
13540 /* don't move other search events */
13541 if (T_SEARCH == timer[i].qstat) continue;
13543 if (0 == strncasecmp( timer[i].name, tn, strlen(tn) ) ) tf = ~0;
13545 /* if match found, copy the timer */
13546 if (0 != tf) {
13547 trec1 = timer[i].start;
13548 memcpy( &tloc1, localtime( &trec1 ), sizeof(tloc1) );
13550 if (0 == timer[i].days) {
13551 /* is not weekday event, handle volatile event, len seconds */
13552 snprintf( p, 64, "V%02d:%010d:%05d:%s\n",
13553 timer[i].chan,
13554 timer[i].start,
13555 timer[i].len,
13556 timer[i].name );
13557 advrlog( LOG_INFO, "%s", p);
13560 /* don't want to repunch this. for now, don't move weekdays w/ search */
13561 #if 0
13562 if (0 != timer[i].days) {
13563 /* is weekday event, set up weekday bit string, len minutes */
13564 utoab( wd, 7, timer[i].days );
13565 wd[7] = 0;
13567 snprintf( p, 64, "T%02d:%02d:%02d:%03d:%s:%s\n",
13568 timer[i].chan,
13569 tloc1.tm_hour,
13570 tloc1.tm_min,
13571 timer[i].len / 60,
13572 wd,
13573 timer[i].name );
13574 advrlog( LOG_INFO, "%s", p);
13576 #endif
13578 p = o + strlen(o); /* move to end of string */
13582 /* one fell swoop to prevent race condition from corrupting load config
13583 on the target. daybits confusion should now be fixed, too
13585 fprintf( f, "%s", o);
13586 fclose( f );
13588 advrlog( LOG_INFO, "delete search event %d %s", tm, tn);
13590 /* maybe needs to delete more than one search event? still deciding.
13591 as in the case of search for CSI, CSIMiami, CSINY, CSICrime
13592 this is only an issue if you use multiple CSI* entries instead of only CSI
13594 delete_search_event( tm, WHO );
13595 /* latest dvrlog stops at first NL, so convert NL to space for log */
13596 for (i=0; i< strlen(o); i++) if ('\n' == o[i]) o[i] = ' ';
13597 advrlog( LOG_INFO, "%s %s\n%s", WHO, fo, o );
13599 } else {
13600 dvrlog( LOG_INFO, "error appending %s", fo);
13602 save_config( WHO);
13603 refresh.timers = 1;
13607 move timer to another configuration file, only useful for multicard.
13608 nothing damaged w/o multicard, but does append to destination config
13609 to create a junk timer file you could look at later.
13611 /* static */
13612 void
13613 get_timer_move ( void )
13615 char timer_in[8];
13616 char *warn = BW; /* no warn color first time */
13617 int tm = 0;
13618 int tc = 0;
13620 /* turn echo and blocking back on, get input */
13621 while ( 1 )
13623 aprintf( stderr, SCV BL
13624 "%s%sMove timer nn to config n <tt c> ?"BN" " CEL,
13625 dca.hstat, warn );
13626 console_reset();
13627 timer_in[0] = 0;
13629 fgets( timer_in, sizeof(timer_in)-1, stdin );
13631 /* dummy read to reset back to nonblock/nonecho */
13632 console_getch();
13634 /* redraw header and return if <ENTER> */
13635 if ( timer_in[0] == 10 ) {
13636 show_headers(0);
13637 return;
13640 /* remove any terminating NL */
13641 if ( timer_in[ strlen( timer_in ) - 1 ] == 10 )
13642 timer_in[ strlen( timer_in ) - 1 ] = 0;
13644 warn = BR; /* 2nd or more try at input */
13646 /* need 3 chars min, TIMER [SPACE] DESTCFG */
13647 if ( strlen( timer_in ) < 3 )
13648 continue; /* junk, try again, not enough chars */
13650 sscanf( timer_in, "%2d %1d", &tm, &tc );
13652 if ( (tm < 0) || (tm > timer_idx) )
13653 continue; /* junk, try again, use valid timer this time */
13655 if ( (tc > 3) || (tc < 0) || (tc == arg_devnum) )
13656 continue; /* junk, try again, sanity limit on confile file write */
13658 /* volatiles are allowed to be moved now
13659 if ( timer[tm].days == 0)
13660 continue; / junk, not moving FTO temp timers
13662 /* if you get this far, input might be useful, so get out of loop */
13663 break;
13666 if (T_SEARCH != timer[tm].qstat) {
13667 move_timer( tm, tc );
13668 } else {
13669 move_search_event( tm, tc );
13673 /* NO: it's sorted before move, and therefore after, too
13674 advrlog( LOG_INFO, "get timer move setting sort timer" );
13675 timer_sort = ~0;
13677 timer_sort = 0;
13678 refresh.timers = 1;
13679 show_headers(0);
13680 /* save_config( WHO ); */ /* move saves list */
13684 /* parse chan, call and id's from config file Cx lines
13685 *p is pointer to string to parse for channel # and id strings
13686 data stored in ptc[chan]sig.sid & sig.call
13687 scan_list is populated and scan_idx set to number of channels found.
13688 duplicates are discarded, so only the first one counts
13689 CPTC:CALL:SID:GTO:PN:PA:PX:XN
13690 px is program xlate. non zero means do the xlate on this in load_epg.
13691 it requires the source (remote) epg to have same ch.epg name file.
13692 xf use this number .epg file for xlate if specified
13693 make sure it gets set to -1 so it doesn't keep trying to get 00.epg
13694 also the remote fetch should use this number and save as NN.epx
13696 /* static */
13697 void
13698 parse_channel ( char *t )
13700 FILE *f;
13701 struct sig_s *s = NULL; /* sig_s member of ptc */
13702 struct utimbuf ut;
13703 char *p[8], tf[256]; /* parameters, touch file */
13704 int ch, i;
13706 if (NULL == t) return;
13707 if (0 == *t) return;
13709 advrlog( LOG_INFO, "%s (%s)", WHO, t);
13711 memset( &ut, 0, sizeof(ut));
13713 /* TESTME: load cfg may exclude this? */
13714 if (scan_idx >= ptc_max) {
13715 dvrlog( LOG_INFO, "%s scan_idx %d exceeds list limit of %d",
13716 WHO, scan_idx, ptc_max );
13717 return; /* not fatal */
13720 build_args( p, 8, t, ':', WHO );
13722 if (NULL == p[0]) return;
13723 if (0 == strlen(p[0])) return;
13725 ch = atoi( p[0] );
13727 /* discard bogus channel if over maximum allowed channels */
13728 if ( (ch < 1) || (ch >= ptc_max) ) {
13729 /* let user know what the problem is */
13730 dvrlog( LOG_INFO, "parse channel %d error: range is 1 to %d",
13731 ch, ptc_max-1);
13732 return;
13735 /* discard bogus frequency from bogus channel */
13736 if ( (0 == frequencies[ch]) || (-1 == frequencies[ch]) ) {
13737 dvrlog( LOG_INFO, "%s PTC %d has -F%d entry = %d",
13738 WHO, ch, arg_frtable, s->freq);
13739 return;
13742 i = scan_idx;
13743 scan_idx++;
13745 /* save index in scan_list and increment, starting at logical ch 0 */
13746 scan_list[i] = i;
13747 // was = ch for physical channel numbering, can't revert it easily now
13749 s = &ptc[ i ].sig;
13750 s->freq = frequencies[ ch ];
13752 /* set defaults in case parse fails */
13753 s->chan = i;
13754 s->ptc = ch;
13755 s->pn = -1;
13756 s->vc = -1;
13757 s->va = -1;
13758 s->pnx = 0;
13759 s->pnxf = -1;
13760 s->lzpgt = 0;
13761 s->pgmt = 0;
13762 s->pgto = 0;
13764 memset( s->call, 0, 8);
13765 memset( s->sid, 0, 4);
13767 /* s->chan is offset into scanlist (logical channel number) */
13769 snprintf( s->call, 8, "C%03d", s->chan ); /* 7 char callsign */
13770 if (NULL != p[1]) snprintf( s->call, 8, "%s", p[1] );
13772 snprintf( s->sid, 4, "%03d", s->ptc); /* 3 char network id */
13773 if (NULL != p[2]) snprintf( s->sid, 4, "%s", p[2] );
13775 /* auto guide time-out */
13776 if (NULL != p[3]) s->pgto = atoi( p[3] ); /* any non zero to enable */
13777 if (0 != s->pgto) s->pgto = 1; /* 1 to enable auto EPG */
13779 /* TODO: move these to [1],[2], after PTC */
13780 if (NULL != p[4]) s->pn = atoi( p[4] ); /* program number */
13781 if (NULL != p[5]) s->va = atoi( p[5] ); /* audio number */
13783 if (NULL != p[6]) s->pnx = atoi( p[6] );
13784 if (NULL != p[7]) s->pnxf = atoi( p[7] );
13786 /* this might be irritating to the web user if it resets itself
13787 constantly. reset it only if the day is passed.
13789 // if ((s->yday >=0) && (s->yday < tloc.tm_yday))
13791 s->yday = tloc.tm_yday;
13793 /* old vc 0 sets full cap to make user set the new program # */
13794 if (s->pn < 1) s->pn = s->vc = s->va = s->yday = -1;
13796 /* pre-load pgmt from existing EPG mod time, if any */
13797 if (0 != s->pgto) {
13798 struct stat st;
13799 char n[256];
13800 int ok;
13802 snprintf( n, sizeof(n)-1, "%s%spg/%02d.epg",
13803 out_path, ram_path, s->chan );
13804 ok = stat( n, &st);
13805 s->pgmt = 0;
13806 if (0 == ok) s->pgmt = (int)st.st_mtime;
13807 advrlog( LOG_INFO, "stat %s %d %d", n, ok, s->pgmt);
13810 /* load the guide to do an auto search on the event names */
13811 load_guide( s->chan, WHO );
13813 /* guide may not have loaded. try to set the vc anyway? */
13814 s->vc = find_vc_pgm( s->pn, WHO ); /* -1 is ok */
13816 advrlog( LOG_INFO, "%s sp %d sv %d sa %d",
13817 WHO, s->pn, s->vc, s->va);
13819 /* touch .html file in case it's missing */
13820 snprintf( tf, sizeof(tf), "%s%spg/%02d.html",
13821 out_path,ram_path, s->chan);
13822 f = fopen( tf, "a");
13823 if (NULL == f) {
13824 dvrlog( LOG_INFO, "can't open %s", tf);
13825 } else {
13826 fclose(f);
13829 /* reset .epg modtime so test guides will refresh it after load config */
13830 snprintf( tf, sizeof(tf), "%s%spg/%02d.epg",
13831 out_path, ram_path, s->chan);
13832 ut.modtime = (time_t)(0L);
13833 utime( tf, &ut);
13836 #ifdef USE_PNG
13837 /* touch .png file in case it's missing */
13838 snprintf( tf, sizeof(tf), "%s%spg/%d-%02d.png",
13839 out_path, ram_path, arg_devnum, s->chan);
13840 f = fopen( tf, "a");
13841 if (NULL == f) {
13842 dvrlog( LOG_INFO, "can't open %s", tf);
13843 } else {
13844 fclose(f);
13846 #endif
13848 advrlog( LOG_INFO, "%s %d stop", WHO, ch );
13849 advrlog( LOG_INFO, "\n");
13852 #ifdef USE_WWW
13853 /* -w name:netmask:port:threads
13854 example:
13855 -w 1.2.3.8:24:80:5
13856 24 is netmask as number of ms bits to set
13857 80 is port, defaults to 80 + arg_devnum if not specified
13858 5 is thread count for server instance
13860 /* static */
13861 void
13862 parse_www_bind( char *o )
13864 char p[256], host[256], root[256], *p1, *p2, *p3, *p4, *p5;
13865 unsigned int addr, mask;
13866 unsigned short port;
13867 int pt, i, j;
13868 struct stat64 fs;
13870 if (NULL == o) return;
13871 if (0 == *o) return;
13873 advrlog( LOG_INFO, "%s (%s)", WHO, o);
13875 p1 = p2 = p3 = p4 = p5 = NULL;
13876 addr = 0; /* 0 triggers INADDR_ANY */
13878 mask = WWW_MASK;
13879 port = WWW_PORT + arg_devnum; /* each instance defaults to new port */
13880 pt = WWW_THREADS;
13882 memset( p, 0, sizeof(p));
13883 memset( host, 0, sizeof(p));
13884 memset( root, 0, sizeof(p));
13886 astrncpy( host, WWW_IP, sizeof(host) );
13887 astrncpy( root, out_path, sizeof(root) );
13888 astrncpy( p, o, sizeof(p) );
13890 j = -1;
13892 /* address required, 0.0.0.0 is INADDR_ANY */
13893 p1 = p;
13894 p2 = strchr( p1, ':');
13895 if (NULL != p2) {
13896 *p2++ = 0;
13898 /* host name or IP address */
13899 astrncpy( host, p1, sizeof(host) );
13901 /* netmask optional 0-32 or dotted notation, default no mask */
13902 j = atoi( p2 );
13903 p3 = strchr( p2, ':');
13904 if (NULL != p3) {
13905 *p3++ = 0;
13907 /* port optional 0-65535, default 80+dev# */
13908 port = atoi( p3 );
13909 p4 = strchr( p3, ':');
13910 if (NULL != p4) {
13911 *p4++ = 0;
13913 /* threads optional, default 3 */
13914 pt = atoi( p4 );
13920 /* if mask not blank */
13921 if (NULL != p2) {
13922 /* mask can be dotted notation or number of bits */
13923 if (NULL != strchr( p2, '.')) {
13924 /* xlate dotted version */
13925 dotouint( &mask, p2 );
13926 } else {
13927 /* mask can be xlated from bit count */
13928 if ((j > 0) && (j < 33)) {
13929 mask = 0;
13930 for (i = 0; i < j; i++) mask |= 1 << (31-i);
13935 astrncpy( host, p1, sizeof(host) );
13936 astrncpy( arg_whost, p1, sizeof(arg_whost) );
13938 /* it might be a name or a dotted number. will false trigger on dotted name */
13939 if (NULL != strchr( host, '.'))
13940 dotouint( &addr, host );
13942 /* 0 or -1 for port will give default port 80 */
13943 if (0 == port) port = WWW_PORT;
13944 if (0xFFFF == port) port = WWW_PORT;
13946 if (pt < 1) pt = WWW_THREADS;
13948 arg_wthreads = pt;
13949 arg_wmask = mask;
13950 arg_wport = port;
13952 i = stat64( root, &fs );
13953 if (0 == i) {
13954 if (S_IFDIR & fs.st_mode) {
13955 advrlog( LOG_INFO, "using %s for www root\n", root);
13956 } else {
13957 astrncpy( root, WWW_ROOT, WWW_ROOT_MAX );
13959 } else {
13960 astrncpy( root, WWW_ROOT, WWW_ROOT_MAX );
13963 if (0 == mask) {
13964 advrlog( LOG_INFO, "no mask given, host is w-w-w-ide open\n");
13967 astrncpy( arg_wroot, out_path, sizeof(arg_wroot) );
13968 astrncpy( arg_whost, host, sizeof(arg_whost) );
13969 arg_wmask = mask;
13970 arg_wport = port;
13971 arg_waddr = addr;
13973 /* binding to 0.0.0.0 requires using hosts/dns lookup for host dtv.
13974 this only matters for dump guides; index html uses thread ip name.
13977 /* name only? */
13978 if (NULL == strchr( host, '.')) {
13979 struct hostent *localhost;
13980 localhost = gethostbyname( host );
13981 if (0 != h_errno) {
13982 dvrlog( LOG_INFO, "%s gethostbyname error %d %s",
13983 NAME, h_errno, host );
13984 fprintf( stderr, CLS "%s gethostbyname error %d %s\n",
13985 NAME, h_errno, host );
13986 exit(0);
13989 /* network byte order */
13990 addr = 0xFF & localhost->h_addr[0];
13991 addr <<= 8;
13992 addr |= 0xFF & localhost->h_addr[1];
13993 addr <<= 8;
13994 addr |= 0xFF & localhost->h_addr[2];
13995 addr <<= 8;
13996 addr |= 0xFF & localhost->h_addr[3];
13997 arg_waddr = addr;
14000 #endif
14002 /* step thru timer list fixing start times if weekday bits given
14003 TODO: could also remove overlapping timers, or at least the first one
14005 /* static */
14006 void
14007 validate_config (void)
14009 int i, j;
14010 int d;
14011 int c;
14013 c = 1;
14015 sort_timers(); /* move search timers to end */
14017 for (i = 0; i < timer_idx; i++) {
14019 /* skip search events */
14020 if (T_SEARCH == timer[i].qstat) continue;
14022 if (i > 1) {
14023 if ( (timer[i-1].chan == timer[i].chan)
14024 && (timer[i-1].start == timer[i].start)
14025 && (timer[i-1].days == timer[i].days) ) /* tupari */
14026 delete_timer( i, WHO );
14029 if (timer[i].days != 0) {
14030 /* convert today weekday sun=0,sat=6 to bit # (sun=6,sat=0);
14031 start from today weekday and see if it matches weekday mask
14033 d = tloc.tm_wday;
14035 /* wraparound up to a week */
14036 for (j = d; j < 14; j++) {
14037 /* find first set weekday bit and break out */
14038 if (timer[i].days & 1<<( (6-(j % 7) ) )) break;
14041 /* debug
14042 aprintf(stderr, "\n %s %d %d %d %d",
14043 timer[i].name, timer[i].start, d, j, j - d);
14045 /* tricky math alert
14046 j can be 14, so that's 2 weeks in advance, should be ok
14048 /* should be zero if current day is reschedule day */
14049 d = (j - d) % 7;
14051 timer[i].start += (SECDAY * d);
14053 /* make reque same as submit time to catch non-repeating timers */
14054 timer[i].future = 0;
14056 /* this is enough to get everything calculated from today 0000h */
14057 if (timer[i].days != 0)
14058 timer[i].future = calc_future_date( i );
14060 /* but you need this to age out today stuff that is past */
14061 if ( (timer[i].start+timer[i].len) < utsnow )
14062 timer[i].start += timer[i].future;
14066 /* sort list by next activation time before using show_timers */
14067 advrlog( LOG_INFO, "valcfg triggers timer sort");
14068 timer_sort = ~0;
14069 sort_timers();
14074 load config calls this to remove duplicate timers
14075 check for exact duplicate and return 0 if match else return non zero
14077 /* static */
14079 test_timer_dup ( char *caller, char *n, unsigned int st, unsigned int ls )
14081 int i;
14082 struct qtimer_s *t;
14084 for (i=0; i< timer_idx; i++) { /* step thru known timers */
14085 t = &timer[i];
14086 if (T_SEARCH == t->qstat) continue;
14087 if ( 0 == strncmp( t->name, n, strlen(n) ) ) {
14088 if ( t->start == st ) {
14089 if (t->len == ls ) {
14090 advrlog( LOG_INFO, "%s dup timer%d %s %s",
14091 caller, i, t->name, n);
14092 return 0; /* is exact duplicate */
14097 return ~0; /* not exact duplicate */
14101 #if 0
14102 /* IS THIS REALLY NEEDED TO FIX LCN ISSUES? IT BREAKS TOO MANY THINGS */
14103 /* find logical channel number (scan list index) from (PTC) ch and pn */
14104 /* static */
14106 find_lcn_ptc_pn( int ch, int pn )
14108 int i;
14109 struct sig_s *s;
14111 if ( (ch < 0) || (ch > ptc_max) ) return -1;
14112 if ( (pn < 1) || (pn > 65534) ) return -1;
14114 for (i = 0; i < scan_idx; i++) {
14115 s = &ptc[ scan_list[i]].sig;
14116 if (ch != s->ptc) continue;
14117 if (pn != s->pn) continue;
14118 return i;
14121 return -1;
14123 #endif
14125 /* parse timer in config file format
14126 handles temp timer that has absolute epoch time, no daybits.
14127 p is config file string
14128 ignores temp timers that have expired
14130 /* static */
14131 void
14132 parse_volatile_timer ( char *s )
14134 int ac, as, y, m, d, hh, mm, al, i, pn, ei;
14135 char *p[6], *n, *r, dt[32];
14136 struct qtimer_s *t;
14137 struct tm tmv;
14138 time_t at;
14140 // if (0 != arg_dummy) return;
14141 if (NULL == s) return;
14142 if (0 == *s) return;
14144 advrlog( LOG_INFO, "%s \042%s\042", WHO, s);
14146 /* try the obvious disqualify */
14147 if (strlen( s ) > 52) {
14148 dvrlog( LOG_INFO, "timer too long %s", s);
14149 return;
14152 /* mangle s1 instead of s */
14153 ac = as = y = m = d = hh = mm = al = i = pn = 0;
14154 *dt = 0;
14156 if (timer_idx >= TIMER_LIST_MAX) {
14157 dvrlog(LOG_INFO, "rebuild with larger TIMER_LIST_MAX for %s", s);
14158 return;
14161 /* Two formats:
14162 Both formats will try to set 'as'.
14164 Old machine readable format
14165 with one new optional etmid field for EPG:
14167 ch:uts:lenm:name[:etmid]
14169 New human readable format:
14171 ch:yearmmdd:hh:mm:lenm:name
14173 32:20060917:23:35:60:StargateSG1@.3
14176 i = build_args( p, 6, s, ':', WHO);
14177 if (i < 4) {
14178 dvrlog(LOG_INFO, "Bad config V-line: %s", s);
14179 return;
14182 if ( NULL == p[0] ) return;
14183 ac = atoi(p[0]);
14185 /* start uts or YYYYMMDD */
14186 if ( NULL == p[1] ) return;
14187 as = atoi(p[1]);
14189 ei = 0;
14191 /**** old machine readable format: distinction fails with dates > 20991231 */
14192 if (as > 21000000) {
14193 if (NULL == p[2]) return;
14194 al = atoi(p[2]); /* length in minutes */
14195 if ((al < 1) || (al > 1440)) return;
14196 al *= 60;
14197 if (NULL == p[3]) return;
14198 n = p[3];
14199 if (NULL != p[4]) ei = atoi(p[4]);
14201 /***** new human readble format 20060820 */
14202 } else {
14203 y = as/10000; /* year > 2000 */
14204 m = (as-(y*10000)) / 100; /* month 1-12*/
14205 d = as % 100; /* day of month 1-31 */
14207 /* abort if range error */
14208 if (y < 2000) return;
14209 if ((m < 1) || (m > 12)) return;
14210 if ((d < 1) || (d > 31)) return;
14212 if (NULL == p[2]) return;
14213 hh = atoi(p[2]); /* start hour < 24 */
14214 if ((hh < 0) || (hh > 23)) return;
14216 if (NULL == p[3])
14217 mm = atoi(p[3]); /* start minute < 60 */
14218 if ((mm < 0) || (mm > 59)) return;
14220 if (NULL == p[4]) return;
14221 al = atoi(p[4]); /* length in minutes < 1440 */
14222 if ((al < 1) || (al > 1440)) return;
14223 al *= SECMIN;
14225 if (NULL == p[5]) return;
14226 n = p[5]; /* name */
14228 /* valgrind */
14229 memset( &tmv, 0, sizeof( tmv ));
14231 /* fill in time structure to convert to new as, al should be correct */
14232 tmv.tm_sec = 0;
14233 tmv.tm_min = mm;
14234 tmv.tm_hour = hh;
14235 tmv.tm_mday = d;
14236 tmv.tm_mon = m - 1;
14237 tmv.tm_year = y - 1900;
14239 as = mktime( &tmv ); /* convert to uts */
14240 at = (time_t) as;
14241 localtime_r( &at, &tmv );
14243 /* if dst set for that date, subtract an hour from the time */
14244 if (tmv.tm_isdst > 0) as -= SECHR;
14247 /* stuff localtime into tmv, should handle dst ok even if display is wrong */
14248 at = (time_t) as;
14249 localtime_r( &at, &tmv );
14251 /* parse didn't work */
14252 if (0 == *n) {
14253 dvrlog( LOG_INFO, "%s blank name %s", WHO, s);
14254 return;
14257 // advrlog( LOG_INFO, "ac %d as %d al %d an %s s %s", ac, as, al, n, s );
14259 /* timer is duplicate */
14260 if ( 0 == test_timer_dup( WHO, n, as, al) ) {
14261 advrlog( LOG_INFO, "timer %s is dup", s);
14262 return;
14265 /* timer is stale and expired */
14266 if ( utsnow > (as + (al-5))) {
14267 dvrlog( LOG_INFO, "timer is expired %d %d %s",
14268 utsnow, as + (al-5), s);
14269 return;
14272 /* user error range check: channel number and length > 0 */
14273 if ( (ac >= 0) && (ac < ptc_max) && (al > 0) )
14274 /* && (al < (1 + (24 * 3600))) ) */ /* KTRK event >1 day fix by no len chk */
14276 i = timer_idx;
14277 t = &timer[i];
14278 timer_idx++;
14280 t->etmid = ei;
14281 t->qstat = T_FUBAR;
14282 t->chan = ac;
14284 t->start = as;
14285 t->len = al;
14286 t->days = 0; /* no daybits, timer removes itself on expiration */
14287 t->secdt = 5; /* 5 second early end for back to back AOS */
14288 astrncpy( t->name, n, TIMER_NAME_MAX );
14290 t->pn = t->va = t->vc = -1;
14292 /* TESTME: if C-line is before V-line, is VC loaded from guide data?
14293 YES: if it exists from previous run, but not after make eclean
14295 /* parse program number from timer name */
14296 r = strchr( n, '.');
14297 if (NULL != r) {
14298 r++;
14299 pn = atoi( r );
14300 t->pn = pn;
14301 t->vc = find_vc_pgm( pn, WHO);
14304 /* parse progam audio number from timer name */
14305 r = strchr( n, ',');
14306 if (NULL != r) {
14307 r++;
14308 t->va = atoi( r );
14311 /* TODO: use program number in channel # V13.1:10digits:60:LOST@ */
14312 /* channel # needs to be expanded to 5 chars wide, ch.pn,
14313 or 6 if there are unscrambled cable sources above 99.9
14315 advrlog( LOG_INFO, "%s %s %d.%d,%d", WHO, timer[i].name,
14316 timer[i].chan, timer[i].pn, timer[i].va);
14318 } else {
14319 dvrlog( LOG_INFO, "%s range error: %s", WHO, s);
14321 return;
14324 /* parse timer in config file format
14325 ignores timers with no weekday bits set
14326 or crazy channel/hour/minute/runtime
14328 /* static */
14329 void
14330 parse_weekday_timer ( char *s )
14332 struct qtimer_s *t;
14333 char s1[64], *p[6], *aw, *an, *r;
14334 int ac, ah, am, ar; /* given */
14335 int ad, as, al; /* computed */
14336 int i;
14338 // if (0 != arg_dummy) return; /* no timers to confuse replay mode */
14340 if (NULL == s) return;
14341 if (0 == *s) return;
14343 advrlog( LOG_INFO, "%s (%s)", WHO, s );
14345 ac = ah = am = ar = ad = as = al = i = 0;
14347 if (timer_idx >= TIMER_LIST_MAX) {
14348 advrlog(LOG_INFO, "rebuild w/ larger TIMER_LIST_MAX for %s", s);
14349 return;
14352 /* try the obvious disqualify */
14353 if ( strlen(s) > 64 ) {
14354 advrlog( LOG_INFO, "timer too long, ignoring %s", s);
14355 return;
14358 astrncpy( s1, s, sizeof(s1));
14360 i = build_args( p, 6, s1, ':', WHO );
14361 if (6 != i) return;
14363 if (NULL == p[0]) return;
14364 ac = atoi(p[0]);
14365 if ((ac < 0) || (ac >= ptc_max)) return;
14367 if (NULL == p[1]) return;
14368 ah = atoi(p[1]);
14369 if ((ah < 0) || (ah > 23)) return;
14371 if (NULL == p[2]) return;
14372 am = atoi(p[2]);
14373 if ((am < 0) || (am > 59)) return;
14375 if (NULL == p[3]) return;
14376 ar = atoi(p[3]);
14377 if ((ar < 0) || (ar > 1440)) return;
14379 if (NULL == p[4]) return;
14380 aw = p[4];
14382 if (NULL == p[5]) return;
14383 an = p[5];
14384 if (0 == *an) return;
14386 ad = abtou( aw );
14388 /* user error range check: h 0-23, m 0-60, r 1-720 */
14389 if ( (ac >= 0) && (ac < ptc_max) /* ATSC channel range check */
14390 && (ah > -1) && (ah < 24) /* no crazy hour */
14391 && (am > -1) && (am < 60) /* no crazy min */
14392 && (ar > 0) && (ar <= 1440) /* 720m is 70G ATSC or 56G MPEG2 */
14393 && (ad != 0) /* volatiles not handled here */
14394 && ( 0 != test_timer_dup( WHO, an, as, al ) ) /* not a dup? */
14397 i = timer_idx;
14398 timer_idx++;
14400 as = (SECHR * ah) + (SECMIN * am);
14401 al = (60 * ar);
14402 t = &timer[i];
14403 get_time();
14405 /* channel offset adjust */
14406 t->chan = ac;
14407 t->qstat = T_FUBAR;
14409 t->h = ah;
14410 t->m = am;
14411 t->len = al;
14412 t->days = ad;
14414 #if 1
14415 /* timer starts today, but may get corrected later */
14416 t->start = utsmid+as;
14417 #else
14418 calc_first_weekday(t); // FIXME
14419 #endif
14420 t->secdt = 5; /* 5 second early end for back to back AOS */
14421 t->etmid = 0; /* not from program guide */
14423 /* clear reschedule time, gets fixed later */
14424 t->future = 0;
14426 /* with no EPG on cable, manual timers are the only game in town */
14428 /* add program number? */
14429 r = strrchr( an, '.' ); /* .3 near end of timer name */
14430 if (NULL != r) {
14431 r++;
14432 t->pn = 0xFFFF & atoi(r);
14435 /* add audio number? limited to 4 audios per program */
14436 r = strrchr( an, ',' ); /* ,1 near end of timer name */
14437 if (NULL != r) {
14438 r++;
14439 t->va = 3 & atoi(r);
14442 /* FIXME: needs defined constant, or variable for screen size change */
14443 astrncpy( t->name, an, TIMER_NAME_MAX );
14445 advrlog( LOG_INFO, "%s timer%d %s c%d p%d s%d l%d d%d q%d",
14446 WHO, timer_idx, t->name, t->chan, t->pn,
14447 t->start, t->len, t->days, t->future);
14448 } else {
14449 dvrlog( LOG_INFO, "%s range error: %s\n", WHO, s);
14452 return;
14455 /* cfg_path & cfg_name is set by this and opened by load/save_config */
14456 /* order for looking for the config file is:
14457 arg_cpath non-null will override directory
14458 /dtv/ram/atscap/
14459 /etc/atscap/
14461 /* static */
14462 void
14463 find_config ( void )
14465 int s;
14466 char n[256]; /* ridiculously long path */
14467 char *cp;
14468 struct stat cf;
14470 /* default is /etc/atscap/atscap[0-3].conf */
14471 snprintf( cfg_name, sizeof(cfg_name), "%s%d.conf", NAME, arg_devnum);
14473 /* default is not found */
14474 cf_no = ~0;
14476 /* check for -c config overriding filename or pathname first */
14477 cp = arg_cpath;
14478 if ((NULL != cp) && (0 != *cp)) {
14480 /* see if path or filename exists */
14481 s = stat( cp, &cf );
14483 /* 0 means it exists */
14484 if (0 == stat(cp, &cf)) {
14486 /* is it a file? if so, extract path and name */
14487 if (0 == (cf.st_mode & S_IFDIR)) {
14488 filebase( cfg_path, cp, F_PATH );
14489 filebase( cfg_name, cp, F_FILE );
14491 /* -c path and name extracted */
14492 cf_no = 0;
14495 /* is it a dir? if so, copy and add / if doesn't have and test result */
14496 if (0 != (cf.st_mode & S_IFDIR)) {
14497 snprintf( cfg_path, sizeof(cfg_path)-1, "%s%s",
14498 cp, (strrchr(cp, '/') == NULL) ? "/":"" );
14499 /* make full name, then check to see if default cfg is there */
14500 snprintf( n, sizeof(n)-1, "%s%s", cfg_path, cfg_name);
14501 s = stat( n, &cf );
14503 /* no error and is regular file? */
14504 if ( (s == 0) && (0 != (cf.st_mode & S_IFREG)) )
14505 cf_no = 0;
14507 } else {
14509 /* -c test failed, fall through to rest of checks */
14510 dvrlog( LOG_INFO, "ignoring bogus -c %s", arg_cpath);
14514 /* if found, we're done here */
14515 if (0 == cf_no) {
14516 advrlog( LOG_INFO, "-c config is %s%s", cfg_path, cfg_name);
14517 return;
14520 /* default is /etc/atscap/atscap[0-3].conf */
14521 snprintf( cfg_name, sizeof(cfg_name), "%s%d.conf", NAME, arg_devnum);
14523 /* still looking for config, try out_path/ram/atscapn.conf/ */
14524 /* if ram_path "", it will check /dtv/ directory, dual purpose then */
14525 snprintf( n, sizeof(n), "%s%s%s", out_path, ram_path, cfg_name );
14526 s = stat( n, &cf );
14528 /* exists and is regular file */
14529 if ( (0 == s) && (0 != (cf.st_mode & S_IFREG)) ) {
14530 snprintf( cfg_path, sizeof(cfg_path)-1, "%s%s", out_path, ram_path );
14531 cf_no = 0;
14534 /* still looking for config, try /etc/atscap/ */
14535 if (0 != cf_no) {
14536 snprintf( n, sizeof(n), "/etc/%s/%s", NAME, cfg_name );
14537 s = stat( n, &cf );
14538 /* exists and is regular file returns */
14539 if ( (s == 0) && ( 0 != (cf.st_mode & S_IFREG)) ) {
14540 snprintf( cfg_path, sizeof(cfg_path)-1, "/etc/%s/", NAME );
14541 cf_no = 0;
14545 /* anything found? */
14546 if (0 == cf_no) {
14547 advrlog( LOG_INFO, "config is %s%s", cfg_path, cfg_name);
14548 } else {
14549 dvrlog( LOG_INFO, "no config found");
14555 /* return -1 if not found, or spam list index if found */
14556 /* static */
14558 test_spam_name( char *p )
14560 int i;
14561 char c1, c2;
14562 struct spam_s *s;
14564 if (NULL == p) return -1;
14565 if (0 == *p) return -1;
14567 c1 = *p;
14568 if ((c1 >= 'A') && (c1 <='Z')) c1 |= 0x20;
14570 /* ASCII tolower() */
14571 /* find duplicates. lots of cycles used here */
14572 for (i = 0; i < spam_idx; i++) {
14573 s = &spam_list[i];
14574 c2 = *s->name;
14575 if ((c2 >= 'A') && (c2 <='Z')) c2 |= 0x20;
14576 if (c1 != c2) continue; /* save a stack cycle */
14578 if (0 == strncasecmp( s->name, p, s->len )) {
14579 s->st = utsnow;
14580 return i;
14583 return -1;
14586 /* remove spam list entry n, move entries n+1 down one.
14587 n ranges from 0 to (spam_idx-1) */
14588 /* static */
14589 void
14590 delete_spam( int n )
14592 int i;
14593 struct spam_s *s1, *s2;
14595 // WHOAMI;
14597 if (0 == spam_idx) return;
14598 if (n >= spam_idx) return;
14600 while (EBUSY == pthread_mutex_trylock( &spam_mutex ))
14601 nanosleep( &atomic_sleep, NULL );
14603 /* not last entry in spam list? */
14604 if (n < (spam_idx - 1)) {
14606 /* move spamlist up (towards 0) to cover current entry */
14607 for (i = n; i < spam_idx-1; i++) {
14608 s1 = &spam_list[i];
14609 s2 = &spam_list[i+1];
14610 memcpy( s1, s2, sizeof( struct spam_s ) );
14613 spam_idx--;
14614 if (spam_idx < 0) spam_idx = 0;
14616 pthread_mutex_unlock( &spam_mutex );
14619 /* add spam list entry p, returns 0 ok(or dup), -1 list full */
14620 /* static */
14622 add_spam( char *p, int st )
14624 struct spam_s *s;
14626 if (SPAM_LIST_MAX <= spam_idx) {
14627 dvrlog( LOG_INFO, "spam list full, increase SPAM_LIST_MAX");
14628 return -1;
14631 s = &spam_list[spam_idx];
14633 /* adding to list clears entry before adding */
14634 memset( s, 0, sizeof( struct spam_s));
14635 astrncpy( s->name, p, SPAM_NAME_MAX );
14636 s->st = st;
14637 /* store actual copied count not original length */
14638 s->len = strlen( s->name );
14640 spam_idx++;
14642 return 0;
14645 /* Tired of editing 4 spamlists? Me too. use global /etc/atscap/atscap.spam */
14646 /* To see spam click off junk filter on WebUI or [h] key in [p] EPG console */
14647 /* static */
14648 void
14649 load_spamlist( void )
14651 FILE *f;
14652 char sf[256]; /* spamlist file name */
14653 char si[80]; /* input buffer */
14654 char sn[32]; /* spamlist name */
14655 char *ss; /* spam start time */
14656 char *ok, *p;
14657 int k, st;
14658 time_t sm;
14659 struct stat fs; /* stat64 not needed for small file */
14661 // WHOAMI;
14663 get_time();
14665 memset( sn, 0, sizeof(sn));
14666 memset( si, 0, sizeof(si));
14668 snprintf( sf, sizeof(sf)-1, "%s%s.spam", cfg_path, NAME);
14669 k = stat( sf, &fs );
14670 if (0 != k) {
14671 advrlog( LOG_INFO, "can't stat %s", sf );
14672 return;
14674 /* file size needs at least 13 bytes, minimum "A:1234567890\nl" */
14675 if (fs.st_size < 13) return;
14677 /* hold down until spam modify time changes by 5s */
14678 // if (cf_smt == fs.st_mtime) return;
14679 sm = fs.st_mtime - cf_smt;
14680 if (sm < 1) return;
14682 advrlog( LOG_INFO, "atscap.spam mt %d smt %d diff %ds",
14683 (int)fs.st_mtime, cf_smt, (int)sm );
14685 cf_smt = fs.st_mtime;
14687 while (EBUSY == pthread_mutex_trylock( &spam_mutex ))
14688 nanosleep( &atomic_sleep, NULL );
14690 // WHOAMI;
14692 f = fopen( sf, "r" );
14694 /* TODO: log error reason */
14695 if (NULL == f) {
14696 dvrlog( LOG_INFO, "can't open %s", sf );
14697 pthread_mutex_unlock( &spam_mutex );
14698 return;
14702 /* reset spam list */
14703 spam_idx = 0;
14704 memset( spam_list, 0, sizeof( spam_list ));
14706 /* EOF is end of list, or MAX is */
14707 while (1) {
14708 if (spam_idx >= SPAM_LIST_MAX) break; /* list full */
14710 ok = fgets( si, sizeof(si)-1, f ); /* read a line */
14711 if (NULL == ok) break; /* EOF */
14713 /* # comment has human readable form of timestamp */
14714 p = strchr( si, '#');
14715 if (NULL != p) *p = 0; /* truncate at # */
14716 if (0 == *si) continue; /* skip blanks */
14718 si[ sizeof(si) - 1 ] = 0; /* NUL term */
14719 si[ strlen(si) - 1 ] = 0; /* toss NL */
14721 st = utsnow;
14722 ss = strchr( si, ':'); /* point to time */
14724 /* : is [?]optional last seen spam time. if not given, is now. See below. */
14725 if (NULL != ss) {
14726 *ss = 0; /* remove : */
14727 ss++;
14728 sscanf( ss, "%d", &st); /* text to time_t */
14731 #if USE_SPAM_TIMEOUT
14732 /* This tries to automatically de-clutter the spam list.
14733 Expires spam after SPAM_AGE_MAX. This is disabled by default.
14734 Some unused station guides may not update for a long time.
14736 if (st < (utsnow - SPAM_AGE_MAX)) {
14737 dvrlog( LOG_INFO, "spam expired %s %d", si, st);
14738 continue; /* don't add to spam list */
14740 #endif
14742 /* event truncate zero terms at 16 chars. one or two words, no spc */
14743 event_truncate( sn, si, 2, SPAM_NAME_MAX );
14745 /* TODO: consolidate this? find is useful by itself */
14746 k = test_spam_name( sn );
14747 if (-1 == k) add_spam( sn, st );
14750 fclose( f );
14752 /* turn on spam filter if spam list has entries */
14753 /* age filter is intentionally left off so user can see what just capped */
14754 if (0 < spam_idx ) {
14755 sort_spamlist( 0 ); /* spamlist by name for faster search */
14757 /////////////////////////////////////////////////////////////
14758 // DELETEME: bug: why does console always turn these back on?
14759 // save_config only saves state of www?
14761 // pgm.age = ~0;
14762 // pgm.hide = ~0;
14763 /////////////////////////////////////////////////////////////
14767 advrlog( LOG_INFO, "%s mt %d with %d entries", WHO, (int)sm, spam_idx);
14769 pthread_mutex_unlock( &spam_mutex );
14772 /* parse system time config line. captures on this channel set the time.
14773 time is given as Zchannel:offset. if offset is not given is assumed 0
14775 /* static */
14776 void
14777 parse_ztime ( char *t )
14779 char *p[2];
14780 struct sig_s *s;
14781 int i, ptch;
14783 cap_zc = 0;
14784 cap_zd = 0;
14785 cap_zto = 0;
14787 if (NULL == t) return;
14788 if (0 == *t) return;
14790 advrlog( LOG_INFO, "%s (%s)", WHO, t);
14792 /* ignore build args return value, this one can be variable */
14793 build_args( p, 2, t, ':', WHO );
14796 ptch = atoi(p[0]);
14798 /* has PTC number and not NTP name */
14799 if (ptch > 0) {
14800 if ((ptch < 1) || (ptch > ptc_max)) return;
14801 cap_zc = ptch;
14803 /* drift offset is allowed to be negative */
14804 if (NULL != p[1]) cap_zd = atoi(p[1]);
14806 /* find first matching ptc in scanlist and enable auto EPG to get ATSC STT */
14807 for (i = 0; i < scan_idx; i++) {
14808 s = &ptc[scan_list[i]].sig;
14809 if (ptch == s->ptc)
14810 s->pgto = 1;
14813 } else {
14814 /* if parameter is not numeric, copy server name to use for ntpdate */
14815 memset(&ntp_name, 0, sizeof(ntp_name));
14816 strncpy( ntp_name, p[0], sizeof(ntp_name));
14820 /* Inum:[...]:num sets some defaults for the user interface */
14821 /* static */
14822 void
14823 parse_webcfg( char *t )
14825 int i;
14826 char *p[11];
14828 if (NULL == t) return;
14829 if (0 == *t) return;
14831 advrlog( LOG_INFO, "%s (%s)", WHO, t);
14833 i = build_args( p, 11, t, ':', WHO);
14834 if (11 != i) return;
14836 web_config = atoi(p[0]);
14837 web_legend = atoi(p[1]);
14838 web_stats = atoi(p[2]);;
14839 scan_png = atoi(p[3]);
14840 scan_snr = atoi(p[4]);
14842 /* TESTME: this is where web interface interferes with console? */
14843 pgm3.find = atoi(p[5]);
14844 pgm3.hide = atoi(p[6]);
14845 pgm3.age = atoi(p[7]);
14847 epg_grd = atoi(p[8]);
14848 epg_sb = atoi(p[9]);
14849 use_css = 3 & atoi(p[10]); /* TODO rename this to html_ver */
14852 /* Sserver config option adds the www server for cable EPG fetch */
14853 /* broadcast doesn't need to do this */
14854 /* static */
14855 void
14856 parse_epgsrv( char * t )
14858 if (0 == arg_cable) return;
14859 if (NULL == t) return;
14860 if (0 == *t) return;
14862 advrlog( LOG_INFO, "%s (%s)", WHO, t);
14864 memset( &epg_srv, 0, sizeof(epg_srv));
14865 snprintf( epg_srv, sizeof(epg_srv)-1, t );
14866 return;
14869 /* static */
14870 void
14871 remove_tsid( int tx )
14873 int i;
14874 struct tsid_s *t0, *t1;
14877 if (tsidx < 1) return;
14878 if (tx < 0) return;
14879 if (tx >= TSID_MAX) return;
14881 while (EBUSY == pthread_mutex_trylock( &tsid_mutex ))
14882 nanosleep( &atomic_sleep, NULL );
14884 /* move everything up one list item */
14885 for (i = tx; i < tsidx; i++) {
14886 t0 = &tsids[i];
14887 t1 = &tsids[i+1];
14888 memcpy(t0, t1, sizeof(struct tsid_s));
14890 tsidx--;
14892 pthread_mutex_unlock( &tsid_mutex );
14895 /* sort by ptc then pn */
14896 /* static */
14897 void
14898 sort_tsids( void )
14900 int i, j, x;
14901 struct tsid_s t, *t0, *t1;
14903 /* no sort needed */
14904 if (tsidx < 2) return;
14906 /* move everything up one list item */
14907 for (i = 0; i < (tsidx-1); i++) {
14908 for (j = i+1; j < tsidx; j++) {
14909 x = 0;
14910 t0 = tsids + i;
14911 t1 = tsids + j;
14912 if (t0->ptc > t1->ptc)
14913 x = ~0;
14914 if (t0->ptc == t1->ptc)
14915 if (t0->pn > t1->pn)
14916 x = ~0;
14917 if (0 != x) {
14918 memcpy(&t, t0, sizeof(struct tsid_s));
14919 memcpy(t0, t1, sizeof(struct tsid_s));
14920 memcpy(t1, &t, sizeof(struct tsid_s));
14926 /* NOTE: Transport Stream IDs shouldn't change much for broadcast streams,
14927 but on cable they can change often. This list will have wrong data
14928 if the cable provider changes the channel list + TSIDs and will need
14929 need to be deleted/re-generated, but otherwise it should be persistent.
14933 /* find stations calls this if the tsid list is different after load tsid */
14934 /* static */
14935 void
14936 save_tsids( void )
14938 int i;
14939 FILE *o;
14940 char n[256];
14941 struct tsid_s *t;
14943 while (EBUSY == pthread_mutex_trylock( &tsid_mutex ))
14944 nanosleep( &atomic_sleep, NULL );
14946 sort_tsids();
14948 snprintf( n, sizeof(n), "%satscap%d.tsid.%s",
14949 cfg_path, arg_devnum, (0 == arg_cable)?"vsb":"qam");
14951 o = fopen(n, "w");
14952 if (NULL == o) {
14953 dvrlog( LOG_INFO, "%s can't save %s %s", n, strerror(errno));
14956 fprintf(o, "# TSID:PTC:PN:CALL:SID\n");
14958 for (i=0; i < tsidx; i++) {
14959 t = &tsids[ i ];
14960 fprintf(o, "0x%04X:%03d:%02d:%s:%s\n",
14961 t->tsid, t->ptc, t->pn, t->call, t->sid);
14964 fflush(o);
14965 fclose(o);
14966 pthread_mutex_unlock( &tsid_mutex );
14969 /* atscap?.tsid.* format: 0xTSID:PTC:PN:CALL:SID
14971 This is a list of detected TSIDs created by -S option. The purpose of
14972 keeping a separate TSID list is so you can run -S option multiple times
14973 and not have to fix the .conf file CALL and SID fields each time.
14975 -S overwrites the .conf file so storing TSIDs there is currently NOT OK.
14977 /* static */
14978 void
14979 load_tsids( void )
14981 int i, j; /* line count, sscanf return */
14982 FILE *f; /* input file descriptor */
14983 char n[256]; /* input file name */
14984 char s[256]; /* input string */
14985 char *r; /* pointer to input string */
14986 char *p[5]; /* pointers to parts of string */
14987 struct tsid_s *t; /* pointer to tsid list */
14989 memset( tsids, 0, sizeof( tsids ) );
14990 memset( n, 0, sizeof(n) );
14991 memset( s, 0, sizeof(s));
14993 snprintf( n, sizeof(n)-1, "%satscap%d.tsid.%s",
14994 cfg_path, arg_devnum, (0 == arg_cable) ? "vsb":"qam" );
14996 f = fopen( n, "r");
14997 if (NULL == f) {
14998 dvrlog( LOG_INFO, "TSID file %s not opened", n);
14999 return;
15002 while (EBUSY == pthread_mutex_trylock( &tsid_mutex ))
15003 nanosleep( &atomic_sleep, NULL );
15005 r = s;
15007 /* i is line counter */
15008 i = 0;
15009 tsidx = 0;
15010 while (1) {
15011 if (tsidx >= TSID_MAX) {
15012 dvrlog( LOG_INFO, "%s too many TSIDs (%d)", tsidx);
15013 break;
15016 memset( s, 0, sizeof(s));
15018 /* point to new entry */
15019 t = &tsids[ tsidx ];
15021 /* erase the possibly bogus previous loop result */
15022 memset( t, 0, sizeof(struct tsid_s));
15024 r = fgets( s, sizeof(s), f);
15025 /* EOF check */
15026 if (NULL == r) break;
15027 if (r != s) break;
15028 i++;
15030 /* EOL check */
15031 r = strchr( s, '#');
15032 if (NULL != r) *r = 0;
15034 r = strchr( s, '\n');
15035 if (NULL != r) *r = 0;
15037 r = strchr( s, '\r');
15038 if (NULL != r) *r = 0;
15040 /* skip line if comment/nl/cr removal left a blank line */
15041 if (0 == *s) continue;
15043 /* skip line if no delimiters on the line? */
15044 if (NULL == strchr(s, ':')) continue;
15046 /* set p to a list of pointers */
15047 build_args( p, 5, s, ':', WHO );
15049 /* testing various incorrect values will make it skip the line */
15051 /* TSID base16, 0 to 65535 */
15052 if (NULL == p[0]) continue;
15053 j = sscanf( p[0], "%hX", &t->tsid );
15055 /* should have scanned one item */
15056 if (1 != j) continue;
15058 /* invalid TSID values */
15059 if (0 == t->tsid) continue;
15060 if (0xFFFF == t->tsid) continue;
15062 /* PTC base10, 0 to 127 */
15063 if (NULL == p[1]) continue;
15064 t->ptc = 0xFF & atoi( p[1] );
15065 if (0 == t->ptc) continue;
15067 /* init channels should have set ptc_max already */
15068 if (t->ptc > ptc_max) continue;
15070 /* Pgm# base10, 0 to 65535 */
15071 if (NULL == p[2]) continue;
15072 t->pn = 0xFFFF & atoi( p[2] );
15073 if (0 == t->pn) continue;
15075 /* call text, NUL term */
15076 if (NULL == p[3]) continue;
15077 astrncpy( t->call, p[3], 8 );
15078 if (0 == *t->call ) continue;
15080 /* sid text, NUL term */
15081 if (NULL == p[4]) continue;
15082 astrncpy( t->sid, p[4], 4 );
15083 if (0 == *t->sid ) continue;
15085 advrlog( LOG_INFO, "tsids[%2d] Ch.Pn %2d.%d TSID %04X CALL %7s SID %-3s",
15086 tsidx, t->ptc, t->pn, t->tsid, t->call, t->sid );
15087 tsidx++;
15089 fclose(f);
15090 advrlog( LOG_INFO, "%s %s has %d TSIDs", WHO, n, tsidx);
15092 pthread_mutex_unlock( &tsid_mutex );
15094 /* sort the list and save it */
15095 save_tsids();
15098 /* pick which ptc list to use from 6 available */
15099 /* static */
15100 void
15101 init_channels( void )
15103 int i;
15104 struct sig_s *s;
15106 /* force to 0 ATSC 8VSB in case any checks below fail */
15107 /* use 8VSB mode */
15108 arg_cable = 0;
15110 /* FUTURE FIXME: by 2009 this will be 52 */
15111 ptc_max = 70;
15113 aos_delay = AOS_VSB_DELAY;
15115 /* use QAM256 mode */
15116 if (arg_frtable > 0) {
15117 arg_cable = ~0;
15118 ptc_max = 127;
15119 aos_delay = AOS_QAM_DELAY;
15122 load_tsids();
15124 switch( arg_frtable ) {
15126 case 0:
15127 frequencies = ptc_atsc_center; /* was ptc_broadcast */
15128 ptc_max = ATSC_MAX;
15129 break;
15131 case 1:
15132 frequencies = ptc_cable_eia542_hrc;
15133 break;
15135 case 2:
15136 frequencies = ptc_cable_eia542_irc;
15137 break;
15139 case 3:
15140 frequencies = ptc_cable_hrc;
15141 break;
15143 case 4:
15144 frequencies = ptc_cable_irc;
15145 break;
15147 case 5:
15148 frequencies = ptc_cable_std;
15149 break;
15151 default:
15152 aprintf(stderr,
15153 HOM CCE BR "bad frequency table selection %d\n" BN,
15154 arg_frtable );
15156 console_exit(0);
15157 break;
15160 advrlog(LOG_INFO, "%s frtbl %d ptc max %d",
15161 WHO, arg_frtable, ptc_max);
15164 /* populate the frequency table, add some bogus callsigns and station ids */
15165 for (i = 0; i < ptc_max; i++) {
15166 s = &ptc[i].sig;
15167 s->freq = frequencies[i]; /* load the frequency */
15169 /* TESTME: tuner 62.5kc granularity obviates this? Need a supertuner? */
15170 if (0 == arg_frtable) s->freq -= 28615;
15172 s->ptc = i;
15173 s->chan = i; /* breadcrumbs to find scan_list index */
15174 s->vc = s->va = -1; /* full cap is startup default */
15175 s->pgto = 0; /* clear autoguide timeout */
15176 s->pgmt = 0; /* and program guide modify time */
15177 s->psip = 0; /* clear psip data bits */
15178 s->sig_col = &bc[0][0];
15179 snprintf( s->sid, 4, "%03d", s->ptc);
15180 snprintf( s->call, 5, "%s%03d", (0 == arg_cable)?"T":"C", i);
15181 memset( &s->pkt, 0, sizeof(struct pkt_s));
15185 /* static */
15186 void
15187 parse_freqlist( char * t )
15189 int n;
15191 if (NULL == t) return;
15192 if (0 == *t) return;
15194 dvrlog( LOG_INFO, "%s (%s)", WHO, t );
15196 n = 0;
15197 n = atoi(t);
15198 if (n >= 6) return;
15199 arg_frtable = n;
15200 if (n > 0) arg_cable = ~0;
15201 init_channels();
15205 /* show clock tests config file every second, calls this if cfg mod time chg */
15206 /* static */
15207 void
15208 load_config ( char *caller )
15210 int i;
15211 FILE *o; /* points to fopen */
15212 char *c, *t; /* points to tt after good fgets */
15213 char fn[256]; /* config path and file as one string */
15214 char tt[256]; /* config file text input line */
15215 char b;
15216 int pch;
15218 /* creating config, no need to load it */
15219 if ( 0 != arg_scan ) return;
15221 pch = cap_chan;
15223 advrlog(LOG_INFO, "%s start from %s\n", WHO, caller );
15225 get_time();
15226 cap_zto = 0;
15228 nanosleep(&msg_sleep, NULL); /* 1/4s sleep for file update after mt chg */
15231 timer_lock = ~0;
15233 timer_offset = 0;
15235 /* if ( D_TIMERS == display_type ) */
15236 aprintf( stderr, "\033[5;1H" CCE );
15238 get_time();
15239 for ( i = 0; i < scan_idx; i++ ) {
15240 struct sig_s *s;
15241 s = &ptc[ scan_list[i] ].sig;
15242 s->pgto = 0; /* reset autoguide timeout */
15243 s->pgmt = 0; /* reset program guide modtime */
15246 /* touch the EPG grid */
15247 snprintf( fn, sizeof(fn)-1, "%s%s/pg/grid%d.html",
15248 out_path, ram_path, arg_devnum );
15249 o = fopen( fn, "a" );
15250 if (NULL != o) fclose(o);
15252 snprintf( fn, sizeof(fn)-1, "%s%s", cfg_path, cfg_name );
15254 o = fopen( fn, "r" );
15255 if ( NULL == o ) {
15256 dvrlog( LOG_INFO, "load config failed %s", fn);
15257 /* alert user there still was a problem with the config file */
15258 cf_no = ~0;
15259 fprintf( stderr, CLS "load config %s failed\n", fn );
15260 console_exit(0);
15262 /* fatal, enter warn/test mode, if not doing console exit instead
15263 test_mode = ~0;
15264 timer_lock = 0;
15265 return;
15269 /* clear the timer list */
15270 reset_timers();
15272 /* clear the search list */
15273 memset( search_list, 0, sizeof(search_list) );
15274 search_idx = 0;
15276 memset( scan_list, 0, sizeof(scan_list) );
15278 /* add or remove channels loses the current index */
15279 scan_pos = 0;
15280 scan_idx = 0;
15282 c = tt;
15283 while (NULL != c) {
15284 memset(tt, 0, sizeof(tt));
15285 c = fgets( tt, 128, o ); /* c gets NULL at EOF */
15286 if (NULL == c) {
15287 advrlog( LOG_INFO, "%s EOF %s", WHO, fn);
15288 break;
15291 /* change NL terminate to NUL term */
15292 t = strchr( tt, '\n');
15293 if (NULL != t) *t = 0;
15295 /* change comment to NUL term */
15296 t = strchr( tt, '#');
15297 if (NULL != t) *t = 0;
15299 /* blank line? */
15300 if (0 == *tt) continue;
15302 advrlog( LOG_INFO, "%s:%d [%s]", WHO, WHERE, tt);
15304 t = tt;
15305 b = *t;
15306 t++;
15308 switch(b) {
15310 /* there is now an order requirement, C lines before W or V */
15312 /* automatic search event */
15313 case 'A':
15314 parse_search_event( t );
15315 break;
15317 /* broadcast or cable channel */
15318 case 'C':
15319 parse_channel( t );
15320 break;
15322 /* last set -F option */
15323 case 'F':
15324 parse_freqlist( t );
15325 break;
15327 /* user interface format string */
15328 case 'I':
15329 parse_webcfg( t );
15330 break;
15332 /* epg server */
15333 case 'S':
15334 parse_epgsrv( t );
15335 break;
15337 /* volatile timer */
15338 case 'V':
15339 parse_volatile_timer( t );
15340 break;
15342 /* new mnemonic for Weekday instead of T for Timer */
15343 /* weekday timer */
15344 case 'W':
15345 parse_weekday_timer( t );
15346 break;
15348 /* ATSC system time channel select */
15349 case 'Z':
15350 parse_ztime( t );
15351 break;
15353 /* ignore the other lines */
15354 default:
15355 break;
15359 // advrlog( LOG_INFO, "%s %s%s has: %2d sc, %2d ti, %2d si",
15360 // WHO, cfg_path, cfg_name, scan_idx,
15361 // timer_idx - search_idx, search_idx);
15363 #if 0
15364 // stops at parse before saving
15365 fprintf( stdout, "\n\n%s%s has: %2d channels %2d timers %2d searches\n\n",
15366 cfg_path, cfg_name, scan_idx,
15367 timer_idx - search_idx, search_idx);
15369 console_reset();
15370 console_reset();
15371 exit(0);
15372 #endif
15375 /* fixup weekday and dst issues, toss overlaps, and sort */
15376 validate_config();
15378 fclose( o );
15380 sort_searchlist();
15382 /* DEBUG: log the list of event names to search */
15383 if ( search_idx > 0 ) {
15384 advrlog( LOG_INFO, "Search event list:");
15385 for ( i=0; i < search_idx; i++ ) {
15386 advrlog( LOG_INFO, " %s", search_list[ i ].name);
15390 /* update modtime on config file and save cleaned out events */
15391 save_config( WHO );
15393 get_time();
15395 /* CHANGE: trigger guide test in 5s */
15396 utstpg = utsnow + (300 * (1 + arg_devnum));
15398 display_type = D_TIMERS; /* 0 */
15399 refresh.timers = 1;
15400 timer_lock = 0; /* unlock so show timers works */
15402 show_headers(1);
15404 /* put it back so nothing gets confused */
15405 cap_chan = pgm.chan = pch;
15406 #if 0
15407 /* TESTME: is this why guide for KUHT showing up on KHCW? */
15408 load_guide( pch, WHO );
15409 #endif
15410 advrlog( LOG_INFO, "%s stop\n", WHO);
15413 /* show vc selection calls this to count filtered events on each program */
15414 /* static */
15416 count_pg_pgm( int pn )
15418 int i, j;
15419 /* i steps thru the list, j only increments when entry moved down */
15420 /* j becomes new pgm.idx, thus removing all but program# matches */
15422 j = 0;
15423 for (i = 0; i < pgm.idx; i++)
15424 if (pn == epg[ pg[ i ] ].pn)
15425 j++;
15426 return j;
15430 /* similar to load epg, but in reverse */
15431 /* static */
15432 void
15433 save_epg ( int ch, struct event_s *e, struct pgm_s *p, short *pgx,
15434 char *caller )
15436 FILE *f; /* fd for output */
15437 char n[256]; /* name for output */
15438 unsigned short m1, i, j, k;
15439 struct event_s *e1;
15440 size_t w;
15442 /* cable has to use static EPG copy. test epgs could fetch new copy? */
15443 if (0 != arg_cable) return;
15444 if (0 != arg_scan) return;
15446 advrlog( LOG_INFO, "%s %s %s", WHO, caller, n );
15448 memset( p, 0, sizeof(struct pgm_s) ); /* clear filters */
15449 memset( pgx, 0xFF, sizeof(pg) ); /* clear indices */
15450 p->chan = ch; /* save epg chan */
15452 /* build unfiltered copy (blanks removed, pg1 indices may give shorter list) */
15453 build_pg_epg( e, p, pgx, WHO );
15455 /* minus 1 */
15456 m1 = 0;
15457 m1--;
15459 j = 0xFFFF & p->idx; /* hmm, want full list */
15461 if (j >= PGZ) return; /* boundary check */
15462 if (j == 0) return; /* nothing to do */
15464 snprintf( n, sizeof(n)-1, "%s%spg/%02d.epg", out_path, ram_path, ch);
15466 f = fopen( n, "w");
15467 if (NULL == f) {
15468 dvrlog( LOG_INFO, "%s writing %s error %s",
15469 WHO, n, strerror(errno) );
15470 return;
15473 /* write whole pgx structure, j items */
15474 w = fwrite( pgx, sizeof( short ), j, f);
15475 if (j != w) {
15476 dvrlog( LOG_INFO, "%s error %d wr idx %s", WHO, w, n);
15477 fclose( f );
15478 return;
15481 /* term list with short -1 */
15482 w = fwrite( &m1, sizeof( short ), 1, f);
15483 if (1 != w) {
15484 dvrlog( LOG_INFO, "%s -1 wr %s is %d", WHO, n, w);
15485 fclose( f );
15486 return;
15489 for (i = 0; i < j; i++) {
15491 k = pgx[ i ];
15492 if (0xFFFF == k) break;
15494 /* one event */
15495 e1 = &e[ k ];
15497 w = fwrite( e1, sizeof(struct event_s), 1, f );
15499 if (1 != w) {
15500 dvrlog( LOG_INFO, "%s error %d wr %s event %d",
15501 WHO, w, n, i);
15502 break; /* EOF */
15506 fclose( f );
15508 /* update callers mtime */
15509 p->mtime = utsnow;
15512 /* similar to load vct, but in reverse:
15513 ncv is number of channls in VCT
15514 ncp is number of channels in PAT
15515 v is vc structure
15516 p is pa structure
15518 If the volume is full, there will be a lot of errors in log. The log
15519 will not be written, if not using -E option for logging to tmpfs.
15521 /* static */
15522 void
15523 save_vct( int ch, int *ncv, struct vc_s *v, int *ncp, struct pa_s *p )
15525 FILE *f; /* fd for output */
15526 char n[256]; /* name for output */
15527 size_t w;
15529 if (0 != arg_scan) return;
15531 if (ch > ptc_max) { /* boundary check */
15532 dvrlog( LOG_INFO, "%s %d >= ptc max %d",
15533 WHO, ch, ptc_max);
15534 return;
15537 snprintf( n, sizeof(n)-1, "%s%spg/%02d.vc", out_path, ram_path, ch);
15538 advrlog( LOG_INFO, "%s %s", WHO, n );
15540 f = fopen( n, "w");
15541 if (NULL == f) {
15542 dvrlog( LOG_INFO, "%s writing %s error %s",
15543 WHO, n, strerror(errno) );
15544 return;
15547 /* write vct_ncs and pat_ncs */
15548 w = fwrite( ncv, sizeof( int ), 1, f);
15549 if (1 != w) {
15550 dvrlog( LOG_INFO,"%s error %d wr vctncs %s",WHO, w, n );
15551 fclose( f );
15552 return;
15554 w = fwrite( ncp, sizeof( int ), 1, f);
15555 if (1 != w) {
15556 dvrlog( LOG_INFO,"%s error %d wr patncs %s",WHO, w, n );
15557 fclose( f );
15558 return;
15561 /* write vc and pa */
15562 w = fwrite( v, sizeof( vc ), 1, f); /* FIXME: vc needs abstraction */
15563 if (1 != w) {
15564 dvrlog( LOG_INFO, "%s error %d wr vc %s",WHO,w,n );
15565 fclose( f );
15566 return;
15568 w = fwrite( p, sizeof( pa ), 1, f); /* FIXME: pa needs abstraction */
15569 if (1 != w) {
15570 dvrlog( LOG_INFO, "%s error %d wr pa %s",WHO,w,n );
15573 fclose( f );
15577 /* test for existing capture file to add to epg html in dump epg
15578 d is destination name tried,
15579 base is file base name to use for attempts,
15580 n is epg# used for program # and start time lookup
15581 return 0 if found, nz if not found
15583 /* static */
15585 test_ts_file( char *d, char *base, struct event_s *e1, struct stat64 *fs )
15587 int ok, mn, dy, h, m, p;
15588 struct tm tne;
15589 time_t tany;
15591 /* check is name-mmdd-hhmm.pn.ts format */
15592 tany = (time_t)e1->st;
15593 p = e1->pn;
15595 localtime_r( &tany, &tne);
15596 mn = tne.tm_mon+1;
15597 dy = tne.tm_mday;
15598 h = tne.tm_hour;
15599 m = tne.tm_min;
15601 /* base is truncated 2 word version of eventname */
15603 /* program cap with timestamp, could be aged or current cap */
15604 snprintf( d, 64, "%s%s-%02d%02d-%02d%02d.%d.ts",
15605 out_path, base, mn, dy, h, m, p );
15606 ok = stat64( d, fs ); /* big files need stat64 */
15607 if (0 == ok) {
15608 advrlog( LOG_INFO, "html stat64 %s = %d", d, ok);
15609 return 0;
15612 /* leave these out for now. requires further thought on need or use */
15613 #if 0
15614 /* TODO: program cap. no provision to add event without @ */
15615 snprintf( d, 64, "%s%s.%d.ts", out_path, base, p );
15616 ok = stat64( d, fs ); /* big files need stat64 */
15617 if (0 == ok) {
15618 advrlog( LOG_INFO, "html stat64 %s = %d", d, ok);
15619 return 0;
15622 /* TODO: full cap. no provision to add event as full cap from html */
15623 snprintf( d, 64, "%s%s.ts", out_path, base );
15624 ok = stat64( d, fs ); /* big files need stat64 */
15625 if (0 == ok) {
15626 advrlog( LOG_INFO, "html stat64 %s = %d", d, ok);
15627 return 0;
15629 #endif
15631 return -1;
15634 #ifdef USE_PNG
15635 /* static */
15636 void
15637 write_png_error( png_structp png_ptr, png_const_charp msg)
15639 struct img_s *img;
15640 dvrlog( LOG_INFO, "%s", msg);
15641 img = png_get_error_ptr( png_ptr );
15642 if (NULL == img) {
15643 dvrlog( LOG_INFO, "write_png jmpbuf not recoverable!");
15644 console_exit(0);
15646 longjmp( img->jmpbuf, 1 );
15649 /* create png file n from signal image data created by build sig png */
15650 /* compression and a bunch of other things are set to defaults */
15651 /* static */
15652 void
15653 write_png( struct img_s *img, char *n )
15655 int ok;
15656 FILE *fp;
15657 png_structp png_ptr;
15658 png_infop info_ptr;
15659 png_bytepp row_pointers;
15661 ok = ~0; /* nz is not ok */
15663 fp = NULL;
15665 /* allocates memory */
15666 png_ptr = png_create_write_struct(
15667 PNG_LIBPNG_VER_STRING,
15668 (png_voidp) img, write_png_error, NULL );
15670 /* all these gotos are to destroy libpng allocated memory */
15671 if (NULL == png_ptr) goto write_png_exit;
15673 /* also allocates memory */
15674 info_ptr = png_create_info_struct( png_ptr );
15675 if (NULL == info_ptr) goto write_png_exit;
15676 if ( setjmp( img->jmpbuf ) ) goto write_png_exit;
15678 /* open channel.png file for writing */
15679 fp = fopen( n, "wb");
15680 if (NULL == fp) {
15681 dvrlog( LOG_INFO, "can't open %s for writing", n);
15682 return;
15685 /* tell png what file to write */
15686 png_init_io( png_ptr, fp );
15688 /* compression and filtering settings */
15689 /* TESTME: Z_BEST should be less storage for tmpfs but eats more CPU.
15690 Remote users will find the large sig.png file size very annoying,
15691 probably even with compression, until I create an 8 entry palette for it.
15693 NOTE: most href imgs are not compressed but are also fairly small.
15695 png_set_compression_level( png_ptr, SIG_PNG_COMP );
15696 png_set_filter( png_ptr, 0, PNG_FILTER_NONE );
15698 /* image header data */
15699 png_set_IHDR(
15700 png_ptr, info_ptr,
15701 img->width, img->height, img->depth,
15702 img->colortype, img->interlace,
15703 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
15705 /* RGB image pixel data, as a list of row pointers, 0 to height-1 */
15706 png_set_packing( png_ptr );
15707 row_pointers = (png_bytepp) &img->row_pointers;
15708 png_set_rows( png_ptr, info_ptr, row_pointers );
15709 png_write_png( png_ptr, info_ptr, img->transform, NULL);
15711 /* all done */
15712 ok = 0;
15714 write_png_exit:
15716 /* No error? */
15717 if (0 == ok) {
15718 advrlog( LOG_INFO, "wrote %s", n);
15719 } else {
15721 /* Show error */
15722 dvrlog( LOG_INFO, "libpng error %s", n);
15725 /* Close file? */
15726 if (NULL != fp) {
15727 fflush( fp );
15728 fclose( fp );
15731 /* Destroy pointer(s) and free memory. */
15732 if (NULL != png_ptr) {
15733 if (NULL != info_ptr) {
15734 /* Destroy both? */
15735 png_destroy_write_struct( &png_ptr, &info_ptr );
15736 } else {
15737 /* Destroy only png_ptr. */
15738 png_destroy_write_struct( &png_ptr, (png_infopp) NULL);
15743 /* Build RGB data from s->smp strength sample data */
15744 /* raster: 50 rows (%/2) of 60 RGB bytes (seconds) in 3 byte packing,
15745 signal strength is y dimension, x is time, slower during cap
15746 TODO: use RGBA and do transform to set Alpha to tranparency.
15747 This should make it look a little better on a white background.
15749 /* static */
15750 void
15751 build_sig_png ( int ch, int a, char *caller )
15753 unsigned char p[ SIG_PNG_H ][ SIG_PNG_W * SIG_PNG_B ];
15754 int i, j, k, m, o, r, t;
15755 unsigned int c; /* color */
15756 char n[ 256 ];
15757 struct img_s spng;
15758 struct sig_s *s;
15759 struct timespec png_start = { 0, 0 };
15760 struct timespec png_stop = { 0, 0 };
15761 struct timespec png_diff = { 0, 0 };
15762 struct timespec png_sleep = { 0, 1000000 }; /* 1 ms */
15763 int png_bench = 0;
15765 while ( EBUSY == pthread_mutex_trylock( &png_mutex ) )
15766 nanosleep( &png_sleep, NULL );
15768 if (0 != png_bench) clock_gettime( clock_method, &png_start );
15770 s = &ptc[ ch ].sig;
15771 advrlog( LOG_INFO, "%s %s %d", WHO, caller, s->chan);
15773 spng.width = SIG_PNG_W;
15774 spng.height = SIG_PNG_H;
15775 spng.transform = PNG_TRANSFORM_IDENTITY;
15776 // spng.transform |= PNG_TRANSFORM_INVERT_ALPHA;
15777 spng.colortype = PNG_COLOR_TYPE_RGB_ALPHA;
15778 spng.interlace = PNG_INTERLACE_NONE;
15779 spng.depth = 8;
15781 /* point to image data for each row */
15782 for (i = 0; i < SIG_PNG_H; i++)
15783 spng.row_pointers[i] = &p[i][0];
15785 /* needs a faint background to delineate upper boundary */
15786 memset( p, 0x10, sizeof(p) );
15788 /* greyscale for now, do colors later */
15789 for (i = 0; i < SIG_PNG_W; i++ ) {
15790 k = s->smx + i;
15791 if (k > SIG_PNG_W) k -= SIG_PNG_W;
15793 /* colorize the sample according to signal strength */
15794 t = s->smp[k];
15795 c = 0;
15796 if (t > 0) c = 0x80808000; /* gray */
15797 if (t > 12) c = 0x0000FF00; /* blue */
15798 if (t > 25) c = 0xFF00FF00; /* magenta */
15799 if (t > 38) c = 0xFF000000; /* red */
15800 if (t > 51) c = 0xFFFF0000; /* yellow */
15801 if (t > 64) c = 0x00FF0000; /* green */
15802 if (t > 77) c = 0x00FFFF00; /* cyan */
15803 if (t > 90) c = 0xFFFFFF00; /* white */
15805 c |= 0xff & a; /* alpha blend opacity, higher is more opaque */
15807 /* set the R pixel offset */
15808 o = i * SIG_PNG_B;
15810 /* normalize strength/100 to sigbar pixels y / height */
15811 m = s->smp[ k ];
15812 if (m < 0) m = 0;
15813 m *= SIG_PNG_H;
15814 m /= 100;
15815 if (m >= SIG_PNG_H) m = SIG_PNG_H - 1;
15817 /* NOTE: origin is top left so have to do column pixels backwards */
15818 for (j = 0; j < m; j++) {
15819 r = (SIG_PNG_H - 1) - j;
15820 if (r >= 0) {
15821 p[r][ o + 0 ] = 0xFF & (c >> 24); /* R */
15822 p[r][ o + 1 ] = 0xFF & (c >> 16); /* G */
15823 p[r][ o + 2 ] = 0xFF & (c >> 8); /* B */
15824 p[r][ o + 3 ] = 0xFF & c; /* A */
15829 /* not until graph is much larger. is too small even for tick marks?
15830 c = 0x808080;
15831 rgb_drawscale( &p, c, 0, 0, img->width-1, img->height-1);
15834 /* RGBA data is created, now write it as png file for this channel */
15835 snprintf( n, sizeof(n), "%s%spg/%d-%02d.png",
15836 out_path, ram_path, arg_devnum, s->chan);
15838 write_png( &spng, n );
15839 if (0 != png_bench) {
15840 clock_gettime( clock_method, &png_stop );
15841 time_diff( &png_diff, &png_start, &png_stop);
15843 dvrlog( LOG_INFO, "%s %s %d.%09ds", WHO, n,
15844 (int) png_diff.tv_sec, (int) png_diff.tv_nsec );
15847 pthread_mutex_unlock( &png_mutex );
15849 #endif
15851 /* form input text format */
15852 #define FIT_FMT "<input type=\042%s\042 name=\042%s\042 size=\042%d\042 maxlength=\042%d\042>\n"
15853 /* static */
15854 void
15855 build_epg_form( char *d, int z, int ch )
15857 char *g, *r, *n;
15858 struct sig_s *s;
15860 s = &ptc[ ch ].sig;
15862 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
15863 r = "epgtile";
15864 g = "large";
15865 n = "New Timer";
15867 /* only for big tiles and web config enabled (more button) */
15868 if (1 != use_css) return;
15869 if (0 == web_config) return;
15871 asnprintf(d, z, "<form name=\042%s\042"
15872 " method=\042%s\042"
15873 " enctype=\042%s\042>\n",
15874 "epgform", "GET", "application/x-www-form-urlencoded");
15876 asnprintf(d, z, "<div class=\042%s %s%d\042>\n", r, "cyan", 1);
15877 asnprintf(d, z, "<dl class=\042dl_%s\042>\n", g );
15879 asnprintf(d, z, "<dt class=\042dt_%s %s\042>\n", g, "epgtc cyan");
15880 asnprintf(d, z, "<input type=\042%s\042 value=\042%s\042>\n",
15881 "submit", "Add");
15883 /* is s->pn ever zero? won't display anything if so */
15884 if (s->pn > 0) {
15885 asnprintf(d, z, "%s.%d\n", s->sid, s->pn);
15886 } else {
15887 asnprintf(d, z, "Full Cap\n");
15889 asnprintf(d, z, "<br />%s\n", n);
15891 asnprintf(d, z, "</dt>\n");
15892 asnprintf(d, z, "<dd class=\042%s\042>\n", "dd_large epgvsb cyan1");
15894 /* set the cgi value and channel in a hidden type */
15895 asnprintf(d, z, "<input type=\042%s\042 name=\042%s\042"
15896 " value=\042%d\042>\n",
15897 "hidden", "t", ch);
15899 /* name, start time and minutes run time */
15900 asnprintf(d, z, FIT_FMT "Name<br />", "text", "n", 17, 17);
15901 asnprintf(d, z, FIT_FMT "Time hh:mm<br />", "text","s", 5, 5);
15902 asnprintf(d, z, FIT_FMT "Len hh:mm<br />\n", "text", "m", 5, 5);
15904 /* check boxes for days */
15905 asnprintf(d, z, FIT_FMT "*Weekdays<br />*OR*<br />\n", "text", "w", 7, 7);
15906 asnprintf(d, z, FIT_FMT "*YYYYMMDD<br />\n", "text", "d", 8, 8);
15908 asnprintf(d, z, "Name@ = +mmdd<br />\n");
15909 asnprintf(d, z, "Name$ = +wday<br />\n");
15910 asnprintf(d, z, "1111111 = SMTWTFS<br />\n");
15911 // asnprintf(d, z, " * optional<br />\n");
15912 asnprintf(d, z, "</dd>\n");
15913 asnprintf(d, z, "</dl>\n");
15914 asnprintf(d, z, "</div>\n");
15915 asnprintf(d, z, "</form>\n\n");
15918 /* build epg legend as string */
15919 /* static */
15920 void
15921 build_epg_legend ( char *d, int z )
15923 int i;
15924 char *s[12] = {
15925 "epgon24", "scanon", "spam24", "unspam24", "searchadd", "searchdel",
15926 "timeradd", "timerdel", "zap24", "timerstop", "log24", "edit24"
15929 char *t[12] = {
15930 "EPG", "Sig", "Junk+", "Junk-", "Find+", "Find-",
15931 "Add", "Del", "Zap", "Stop", "Log", "Edit"
15934 char *c0, *c1; /* center legend color list */
15936 c0 = c1 = "";
15938 /* nothing to do if web legend disabled */
15939 if (0 == web_legend) return;
15941 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
15943 /* button legend */
15945 #if 0
15946 asnprintf(d, z, "<div class=\042%s\042>%s%s%s</div>\n",
15947 "epgbl2 magenta1", c1, " Past", c0 );
15949 asnprintf(d, z, "<div class=\042%s\042>%s%s%s</div>\n",
15950 "epgbl2 black1", c1, " Junk", c0 );
15951 #endif
15953 asnprintf(d, z, "<div class=\042%s\042>%s%s%s</div>\n",
15954 "epgbl2 white0", c1, " Now", c0 );
15956 asnprintf(d, z, "<div class=\042%s\042>%s%s%s</div>\n",
15957 "epgbl2 blue1", c1, " Movie", c0 );
15959 asnprintf(d, z, "<div class=\042%s\042>%s%s%s</div>\n",
15960 "epgbl2 green1", c1, " Timer", c0 );
15962 #if 0
15963 /* if it's a search, it should already have a timer so is redundant? */
15964 asnprintf(d, z, "<div class=\042%s\042>%s%s%s</div>\n",
15965 "epgbl2 cyan1", c1, " Search", c0 );
15966 #endif
15968 asnprintf(d, z, "<div class=\042%s\042>%s%s%s</div>\n",
15969 "epgbl2 yellow1", c1, " Capture", c0 );
15973 for (i = 0; i < 12; i++) {
15974 asnprintf(d, z, "<div class=\042%s\042>", "epgbl1");
15975 asnprintf(d, z, "<img class=\042%s\042 "
15976 "alt=\042%s\042 "
15977 "src=\042img/%s.png\042 />",
15978 "epgbti", t[i], s[i]);
15979 asnprintf(d, z, " %s", t[i]);
15980 asnprintf(d, z, "</div>\n");
15985 /* event status bits */
15987 #define E_SPAM 1
15988 #define E_AGED 2
15989 #define E_NOW 4
15990 #define E_MOVIE 8
15991 #define E_TIME 16
15992 #define E_FIND 32
15993 #define E_IMG 64
15994 #define E_LOG 128
15995 #define E_TS 256
15996 #define E_MTX 512
15997 #define E_TSX 1024
15998 #define E_TSC 2048
15999 /* check for following:
16000 spam is on spamlist
16001 aged is aged event
16002 now is current event
16003 movie is mpaa rated
16004 timer is on timer list
16005 find has match to timer list
16006 img has event image
16007 log has capture log
16008 ts has capture file
16009 mtx has modtime expired 3s+
16010 tsx has sequence index
16011 tsc has been edited
16014 /* static */
16016 test_event_status ( struct event_s *e )
16018 struct stat64 fs;
16019 int ok, es; /* event status bits */
16020 char s[64], t[64]; /* truncated name */
16021 int d, c; /* daybits and channel */
16023 es = 0;
16025 /* Event check */
16026 if ((e->st+e->ls) < utsnow) es |= E_AGED;
16027 if ( (utsnow >= e->st) && (utsnow < (e->st + e->ls)) ) es |= E_NOW;
16028 if (0 != e->movie) es |= E_MOVIE;
16030 /* List check */
16031 if (-1 != find_timer_epg(e)) es |= E_TIME;
16033 /* sunday is bit 6 */
16034 d = 1 << (6 - e->stm.tm_wday);
16035 c = e->chan;
16037 if (-1 != find_search_event( e->tname, d, c, WHO ))
16038 es |= E_FIND;
16039 /* FIXME: EPG loop caller needs to check ALL events, not the pg index. */
16040 // if ( (-1 != test_spam_name(e->tname))
16041 if ( (-1 != test_spam_event( e ))
16042 /* Junk filter ON and using build_pg_epg pg index to epg makes this a nop. */
16043 && (0 == (es & E_TIME)) ) es |= E_SPAM;
16045 /* base file name for .ts .html .tsx .tsc */
16046 snprintf(s, sizeof(s), "%s%spg/img/%s.png",
16047 out_path, ram_path, e->tname);
16048 ok = stat64(s, &fs);
16049 if (0 == ok) advrlog( LOG_INFO, "%s %s", WHO, s);
16050 if (0 == ok) es |= E_IMG;
16052 snprintf(s, sizeof(s), "%s%s-%02d%02d-%02d%02d.%d",
16053 out_path, e->tname,
16054 e->stm.tm_mon+1, e->stm.tm_mday,
16055 e->stm.tm_hour, e->stm.tm_min, e->pn);
16058 snprintf(t, sizeof(t), "%s.ts", s);
16059 ok = stat64( t, &fs );
16060 if (0 == ok) es |= E_TS;
16062 /* not used? */
16063 if (0 != (es & E_TS)) {
16064 if (fs.st_mtime < (utsnow - 2)) es |= E_MTX;
16067 snprintf(t, sizeof(t), "%s.html", s);
16068 ok = stat64( t, &fs );
16069 if (0 == ok) es |= E_LOG;
16071 snprintf(t, sizeof(t), "%s.tsx", s);
16072 ok = stat64( t, &fs );
16073 if (0 == ok) es |= E_TSX;
16075 snprintf(t, sizeof(t), "%s.tsc", s);
16076 ok = stat64( t, &fs );
16077 if (0 == ok)
16078 if (0 != fs.st_size) es |= E_TSC;
16080 /* File check. Stays on list if no auto-EPG update throws out event. */
16081 /* Check for .ts file, .html log file, .png img file, .tsx file, .tsc file */
16084 return es;
16087 /* [timer entry form], station png, status text and sigchart */
16088 /* static */
16089 void
16090 build_epg_top ( FILE *wf, char *d, int z, struct pgm_s *p )
16092 struct stat fs;
16093 struct sig_s *s;
16094 char *si;
16095 int i, j, ch;
16096 int sr;
16097 char *ef; /* epg class for font size */
16098 char n[256]; /* stationid.png */
16099 char ni[16]; /* signalchart.png */
16100 char sa[64]; /* signal a href */
16102 char *c[2]; /* inactive and active classes for config */
16103 c[0] = "green";
16104 c[1] = "yellow";
16106 i = j = 0;
16107 /* text mode sizing is default */
16108 ef = "";
16109 if (J_LARGE == use_css) ef = "large";
16110 if (J_SMALL == use_css) ef = "small";
16111 if (J_TINY == use_css) ef = "tiny";
16113 ch = p->chan;
16115 s = &ptc[ ch ].sig;
16117 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
16119 // asnprintf(d, z, "<br class=\042%s\042 />\n", "br_epgdiv");
16120 asnprintf(d, z, "<div class=\042%s_%s\042>\n", "epgtop", ef);
16122 /* manual timer entry form, only shows up if (more) button enabled. */
16123 build_epg_form(d, z, ch);
16125 /* station image (optional) */
16126 if (0 != s->sid[0])
16128 snprintf(n, sizeof(n), "%s%s/pg/img/%s.png",
16129 out_path, ram_path, s->sid);
16130 i = stat(n, &fs);
16131 /* TODO: if regular file ... */
16132 if (0 == i)
16134 asnprintf(d, z, "<div class=\042%s\042>\n", "epgsid");
16135 asnprintf(d, z, "<img alt=\042%s\042 "
16136 "src=\042/pg/img/%s.png\042 />\n",
16137 s->call, s->sid);
16138 asnprintf(d, z, "</div>\n");
16141 // asnprintf(d, z, "<br />\n"); // too far down
16142 asnprintf(d, z, "<div class=\042%s\042>\n", "epgevs");
16144 asnprintf(d, z, "%d/%d Events %dh<br>\n%s %s %02d.%d",
16145 p->idx, p->idt, s->mgt_hrs, s->call, s->sid, s->ptc, s->pn);
16147 /********************************************************** current status */
16149 asnprintf(d, z, "<br />\n");
16152 asnprintf(d, z, "Status: ");
16154 si = "Idle";
16156 if (CAP_NONE != cap_now) {
16157 si = "Cap EPG";
16158 if (0 == arg_cable) si = "Cap Info";
16159 if (CAP_TIME == cap_now) si = "Cap Timer";
16160 if (CAP_USER == cap_now) si = "Cap Manual";
16163 if (CAP_NONE == cap_now) {
16164 if (0 != scan_sig) si = "Scan";
16165 if (0 == scan_one) si = "Scan All";
16168 if (in_file < 3) si = "Sleeping";
16170 /* put up status and relative href for finding current capture EPG */
16171 asnprintf(d, z, "%s ", si);
16173 /* not Idle gets href to active guide unless already here */
16174 if ( ('I' != *si) && (cap_chan != s->chan) ) {
16175 struct sig_s *s1;
16176 s1 = &ptc[ cap_chan ].sig;
16177 asnprintf(d, z,
16178 "<a href=\042/pg/%02d.html\042> %s </a>",
16179 cap_chan, s1->sid );
16182 #if 0
16183 /* Idle shows last capture fail status for this channel, if not sig scan */
16184 if ('I' == *si) {
16185 if (0 == scan_sig) {
16186 if (s->cap_fail > 0) {
16187 asnprintf(d, z,"%s FAIL", cap_fails[s->cap_fail] );
16191 #endif
16193 #if USE_EPG_ATSC_STATS
16194 /* show atsc psip status if it has been checked from any capture */
16195 asnprintf(d, z, "<br />\n");
16196 if (0 == s->psip) asnprintf(d, z, "No ATSC PSIP");
16197 // if (0 != s->psip) asnprintf(d, z, "ATSC:");
16198 if ( 1 & s->psip) asnprintf(d, z, " MGT");
16199 if ( 2 & s->psip) asnprintf(d, z, " VCT");
16200 if ( 4 & s->psip) asnprintf(d, z, " EIT");
16201 if ( 8 & s->psip) asnprintf(d, z, " ETT");
16202 // if (16 & s->psip) asnprintf(d, z, " RRT");
16203 #endif
16205 asnprintf(d, z, "\n</div>\n");
16207 /****************************************** signal strength chart */
16209 asnprintf(d, z, "<div class=\042%s\042>\n", "epgsg");
16211 /* other buttons may be variable, put them to right of signal graph */
16212 snprintf( ni, sizeof(ni), "%d-%02d.png", arg_devnum, s->chan);
16214 *sa = 0;
16216 /* signal scan toggle href only if not capturing */
16217 if (CAP_NONE == cap_now) {
16219 /* not scanning or not same channel gets scan enable button */
16220 if ( (0 == scan_sig) || (s->chan != cap_chan) )
16222 snprintf(sa, sizeof(sa), "\042%02d.html?v=%d\042",
16223 s->chan, s->chan );
16224 } else {
16226 /* is scanning and is same channel gets signal scan disable button */
16227 /* TESTME: have to click it twice to turn it off if not same channel? */
16228 snprintf(sa, sizeof(sa), "\042%02d.html?v=%d\042",
16229 s->chan, s->chan );
16233 /* if href made, put the signal chart inside of it for scan toggle */
16234 if (0 != *sa) asnprintf(d, z, "<a class=\042%s\042 href=%s>",
16235 "epgsga", sa);
16236 asnprintf(d, z, "<img class=\042%s\042 "
16237 "alt=\042%s%d\042 "
16238 "src=\042%s\042 "
16239 "border=\042%d\042 />",
16240 "epgsgi", "SIG", s->chan, ni, 1);
16242 if (0 != *sa) asnprintf(d, z, "</a>");
16243 asnprintf(d, z, "\n");
16246 /************************************* load EPG href/img when not capturing */
16247 /* cgi reload (info cap) href img if no capture */
16248 if (CAP_NONE == cap_now) {
16249 asnprintf(d, z, "<a href=\042/pg/%02d.html?g=%d\042>",
16250 s->chan, s->chan);
16251 asnprintf(d, z, IMGX_FMT, "EPG", "img/epgon.png", 0);
16252 asnprintf(d, z, "</a>\n");
16253 } else {
16254 /* cgi only info cap gets zap? full cap could use it too */
16255 if ((CAP_INFO == cap_now) && (s->chan == cap_chan)) {
16256 asnprintf(d, z, "<a href=\042%02d.html?x=%d\042>",
16257 s->chan, s->chan);
16258 asnprintf(d, z, IMGX_FMT, "ZAP", "img/epgoff.png", 0);
16259 asnprintf(d, z, "</a>\n");
16264 /* show last sig avg */
16265 asnprintf(d, z, "<br />\n");
16267 sr = scan_rate;
16268 if (0 == scan_sig) sr = 0;
16269 if (CAP_NONE != cap_now) sr = 1;
16272 /* do SNR RMS or Strength average */
16273 if (0 != scan_snr) {
16275 #if USE_DEBUG
16276 asnprintf(d, z, "<!-- SNR dB ");
16277 for (i = 0; i < SIG_PNG_W; i++)
16278 asnprintf(d, z, "%d.%02d ",
16279 0xFF & (s->rmp[i] >> 8),
16280 ((0xFF & s->rmp[i]) * 100 ) >> 8 );
16281 asnprintf(d, z, " -->\n");
16282 #endif
16283 calc_snr_rms( s );
16284 asnprintf(d, z, "SNR: %d.%02d dB RMS\n",
16285 0xFF & (s->snr_rms >> 8),
16286 ((0xFF & s->snr_rms) * 100 ) >> 8 );
16287 } else {
16289 #if USE_DEBUG
16290 asnprintf(d, z, "<!-- STR % ");
16291 for (i = 0; i < SIG_PNG_W; i++)
16292 asnprintf(d, z, "%d ", s->smp[i]);
16293 asnprintf(d, z, " -->\n");
16294 #endif
16296 calc_sig_avg( s );
16297 asnprintf(d, z, "Sig: ");
16298 asnprintf(d, z, "%02d%% Avg\n", s->str_avg);
16301 #if USE_RMS_RATE
16302 if (0 != sr) {
16303 if (s->chan == cap_chan)
16304 asnprintf(d, z, "@ %d/s", sr);
16306 #endif
16308 asnprintf(d, z, "<br />\n");
16310 /* show error rate during capture, or blank line */
16311 if (CAP_NONE != cap_now) {
16312 double erate;
16313 erate = 100.0 * (double) pkt.errors / (double) pkt.count;
16315 /* show errors if doing capture */
16316 if ( (pkt.errors > 0) && (cap_chan == s->chan) ) {
16317 asnprintf(d, z, "Err: ");
16318 asnprintf(d, z, "%4.2f%% (%d)", erate, pkt.errors);
16319 asnprintf(d, z, "<br />\n");
16320 } else {
16321 /* no errors gets a blank line */
16322 asnprintf(d, z, "<br />\n");
16324 } else {
16325 asnprintf(d, z, "<br />\n");
16327 asnprintf(d, z, "</div>\n");
16330 /********************************************************* CSS legend */
16332 asnprintf(d, z, "<br class=\042%s\042 />\n", "br_epgdiv");
16333 asnprintf(d, z, "<br class=\042%s\042 />\n", "br_epgdiv");
16335 build_epg_filter_html4(d, z );
16336 build_epg_legend( d, z );
16338 asnprintf(d, z, "</div>\n");
16339 asnprintf(d, z, "</div>\n");
16341 fprintf( wf, "%s", d);
16343 /* div for epgtop */
16345 return;
16349 /* insert blank entries, only if no filters */
16350 /* d is destination of size z
16351 n is name of blank for header
16352 b is number of blank tiles to insert
16353 c is color index of blanks to insert
16354 e is current stage index to set to white for NOW indication
16355 g is tile size, "big", "small" or "tiny"
16357 /* static */
16358 void
16359 build_epg_filler (char *d, int z, int b, int c, char *n, char *g, int i,
16360 struct event_s *e )
16362 int j, sr, hh, mm, ss;
16363 char *r, *s, *t, a[32];
16366 /* pointers wrong? */
16367 if ( (NULL == d) || (NULL == n) || (NULL == g) )
16368 return;
16370 /* counters wrong? */
16371 if ((z < 1) || (b < 1) || (c < 0) || (c >= COLORZ) ) return;
16372 /* is ok if i is bogus, it won't match */
16374 /* grid not enabled? checked elsewhere to save a call */
16375 // if (0 == epg_grd) return;
16377 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
16379 r = "epgtile";
16380 for (j=0; j < b; j++) {
16381 t = colors[c];
16382 asnprintf(d, z, "<div class=\042%s %s%d\042>\n", r, t, 1);
16383 asnprintf(d, z, "<dl class=\042dl_%s\042>\n", g );
16385 /****************************************************** filler event name */
16386 asnprintf(d, z, "<dt class=\042dt_%s %s\042>\n", g, "epgtc");
16387 asnprintf(d, z, "%s\n", n);
16388 asnprintf(d, z, "</dt>\n");
16390 /* show (continued) for description else color or abbreviate it */
16392 s = "(continued)";
16394 *a = 0;
16395 /* current 30m filler timeframe is indicated by white description */
16396 if ((j+1) == i) {
16397 t = "white";
16398 sr = (e->st + (e->ls - SECDT)) - utsnow;
16399 if (sr < 0) sr = 0;
16400 hh = sr / 3600;
16401 mm = (sr - (hh * 3600)) / 60;
16402 ss = sr % 60;
16403 snprintf( a, sizeof(a), "%02d:%02d:%02d left",
16404 hh, mm, ss);
16407 /* tiny tile doesn't have enough room for this */
16408 if (3 == use_css) { s = "(cont)"; }
16410 /****************************************************** filler event cont */
16411 asnprintf(d, z, "<dd class=\042dd_%s %s%d %s\042>",
16412 g, t, 0, "epgtc");
16414 // if (3 != use_css) asnprintf(d, z, "<br />");
16416 asnprintf(d, z, "%s", s);
16417 if (*a != 0) asnprintf(d, z, "<br />%s", a);
16419 asnprintf(d, z, "</dd>\n");
16421 asnprintf(d, z,"</dl>\n");
16422 asnprintf(d, z,"</div>\n");
16426 /* build tiles for each weekday timer.
16427 cyan tiles are normal weekday timers
16428 red tiles are ignored timers because of overlap with another timer
16431 /* static */
16432 void
16433 build_epg_timers ( FILE *wf, char *d, int z, char *g, int ch )
16435 int b, i, j, k;
16436 struct qtimer_s *t;
16437 struct tm estm; /* event start time tm */
16438 struct sig_s *s;
16439 char n[PNZ]; /* event name */
16440 // char *n1; /* name strip pointer */
16441 char *c; /* tile color */
16442 char *w; /* weekday color class */
16443 char *sb; /* scrollbar class */
16445 s = &ptc[ ch ].sig;
16447 sb = c = w = "";
16448 *d = 0;
16449 if (1 != use_css) return; /* only large tiles */
16451 /* count the number of weekday timers */
16452 k = 0;
16453 for (i=0; i<timer_idx; i++) {
16454 t = &timer[i];
16456 if (T_SEARCH == t->qstat) continue;
16457 if (ch != t->chan) continue;
16458 if (0 != t->etmid) continue;
16459 // if (0 == t->days) continue;
16461 k++;
16464 /* nothing to do */
16465 if (0 == k) return;
16467 asnprintf(d, z, "\n<!-- %s -->\n", WHO);
16468 /* show any weekday timers that match this channel */
16469 asnprintf(d, z, "<div class=\"%s\">", "epgdiv");
16470 asnprintf(d, z, "<br class=\"%s\" />", "br_epgdiv");
16471 asnprintf(d, z, "<center id=\"%s\" />", "weekday");
16472 asnprintf(d, z, "<hr />");
16473 asnprintf(d, z, "Volatile and Weekday Timers %s.%d", s->sid, s->pn);
16474 asnprintf(d, z, "</center>");
16475 asnprintf(d, z, "</div>\n");
16477 fprintf(wf, "%s\n", d);
16478 *d = 0;
16480 t = &timer[0];
16482 for (i=0; i < timer_idx; i++)
16484 t = &timer[i];
16486 *d = 0;
16487 /* no search, no EPG entered event, non channel events */
16488 if (T_SEARCH == t->qstat) continue;
16489 if (ch != t->chan) continue;
16490 if (0 != t->etmid) continue;
16491 // if (0 == t->days) continue;
16493 localtime_r( (time_t *)&t->start, &estm);
16495 snprintf( n, sizeof(n), "%s", t->name);
16497 #ifdef USE_SHORT_TIMER_NAME
16498 /* strip timer flags. there's already a function for this... */
16499 n1 = strchr(n, '@');
16500 if (NULL != n1) *n1 = 0;
16501 n1 = strchr(n, '.');
16502 if (NULL != n1) *n1 = 0;
16503 if (0 == *n) continue;
16504 #endif
16506 /* not specified or ignore gets red */
16507 c = "wdi";
16509 /* future is dark cyan */
16510 if (T_FUTURE == t->qstat) c = "wdn";
16512 /* today but not live gets green */
16513 if (estm.tm_yday == tloc.tm_yday) c = "wdt";
16515 /* stream gets dark yellow */
16516 if (T_STREAM == t->qstat) c = "wds";
16518 /* ignore gets red */
16519 if (T_IGNORE == t->qstat) c = "wdi";
16522 #ifdef USE_WEEKDAY_FORMS
16523 /* useful for changing a timer? could put in value='s from the fields */
16524 asnprintf(d, z, "<form name=\042%s\042"
16525 " method=\042%s\042"
16526 " enctype=\042%s\042>\n",
16527 "timerdel", "GET", "application/x-www-form-urlencoded");
16528 #endif
16530 asnprintf(d, z, "<div class=\042%s %s%d\042>\n", "epgtile", c, 1);
16532 asnprintf(d, z, "<dl class=\042dl_%s\042>\n", g);
16534 asnprintf(d, z, "<dt class=\042dt_%s %s\042>", g, "epgtc");
16536 #if USE_WEEKDAY_FORMS
16537 asnprintf(d, z, "<input type=\042%s\042 value=\042%s\042>\n",
16538 "submit", "Del");
16539 #endif
16541 /* TODO needs buttons similar to EPG tiles */
16543 /* click title to remove timer */
16544 asnprintf(d, z, "<a href=\042?d=%c%d:%d:%d:%s\042>",
16545 (0 == t->days)?'V':'W',
16546 t->chan, t->start, t->len/60, t->name);
16548 asnprintf(d, z, "%s", n);
16550 asnprintf(d, z, "</a>");
16552 asnprintf(d, z, "<br />");
16554 asnprintf(d, z, "%02d:%02d %dm %s %s %d",
16555 estm.tm_hour, estm.tm_min, t->len/60,
16556 days[ estm.tm_wday ],
16557 mons[ estm.tm_mon ],
16558 estm.tm_mday
16560 asnprintf(d, z, "</dt>\n");
16561 sb = "";
16563 /* not needed? */
16564 // if (0 != epg_sb) sb = " epgvsb";
16566 asnprintf(d, z, "<dd class=\"dd_%s %s%d%s\">\n",
16567 g, c, 0, sb );
16569 /* TODO: need program # for vc list here too, but by s-> struct!??
16570 maybe not if only changing and not adding
16573 /* put up caplog icon if it's current timer */
16574 if (T_STREAM == t->qstat) {
16575 asnprintf(d, z,"<a href=\042/%s\042>"
16576 "<img border=0 src=\042%s\042></a>\n",
16577 &caplog_name[strlen(out_path)],
16578 "/pg/img/log24.png");
16581 if (0 == t->days) asnprintf(d, z, "Volatile ");
16582 if (0 != t->days) asnprintf(d, z, "Weekday ");
16584 if (T_IGNORE == t->qstat) {
16585 asnprintf(d, z, "IGNORED ");
16587 #if 0
16588 /* etmid debug */
16589 asnprintf(d, z, " %04X.%04X",
16590 0xFFFF & (t->etmid>>16), 0xFFFF & t->etmid);
16591 #endif
16593 /* hrefs for each weekday to toggle the bit for that day */
16594 if (t->days != 0) {
16595 for (j=6; j >= 0; j--) {
16596 b = (1<<j) & t->days;
16598 /* grey: no weekday bits match */
16599 w = "wda0";
16601 if (0 != b) {
16602 /* cyan: weekday bits match */
16603 w = "wda1";
16604 if ( ((6-j) == tloc.tm_wday)
16605 && (estm.tm_yday == tloc.tm_yday) )
16606 /* green: weekday bits match, weekday is today, yearday is today */
16607 w = "wda2";
16610 asnprintf(d, z, "<br />\n");
16611 asnprintf(d, z, "<a class=\"%s\" "
16612 "href=\"%02d.html?y=%d:%s:%d\">",
16613 w, t->chan, t->chan, t->name, (1 << j) );
16615 if (0 == b) {
16616 asnprintf(d, z, "<img alt=\"%s\""
16617 " class=\"%s\""
16618 " src=\"%s\">",
16619 "0", "wdb0", "img/norec16.png");
16620 } else {
16621 asnprintf(d, z, "<img alt=\"%s\""
16622 " class=\"%s\""
16623 " src=\"%s\">",
16624 "1 ", "wdb1", "img/rec16.png");
16627 asnprintf(d, z, " %s", daysl[6-j]);
16628 asnprintf(d, z, "</a>");
16631 asnprintf(d, z, "</dd>\n");
16632 asnprintf(d, z, "</dl>\n");
16633 asnprintf(d, z, "</div>\n");
16634 asnprintf(d, z, "</form>\n");
16635 fprintf(wf, "%s\n", d);
16637 *d = 0;
16642 /* TODO: conditionals for each event are messy. Break it down some more. */
16643 /* TODO: abstract the tile creation. */
16644 /* Similar to html version but style may be tweakable via style.css */
16645 /* static */
16646 void
16647 dump_epg_html4 ( struct event_s *ev, struct pgm_s *p, short *pgx, char *caller )
16649 FILE *wf; /* write file */
16650 struct sig_s *s; /* sig structure */
16651 struct event_s *e1; /* point to one event */
16652 time_t et; /* event time time_t */
16654 char h[8192], /* header */
16655 n[128], /* file name */
16656 ht[16], /* http title */
16657 en[PNZ], /* event name */
16658 ed[PDZ], /* event description */
16659 tn[64], /* truncated name NOT USED? */
16660 d[8192], /* string buf */
16661 u[64]; /* href url */
16663 char a[32]; /* action image */
16664 char *b; /* image format */
16666 char *c = ""; /* alt text */
16667 char *ed1; /* description pointer */
16668 char *g; /* tile large small tiny */
16669 char *d1; /* temp string fix */
16670 char *sb; /* scrollbar class */
16672 int mo; /* filters off is zero */
16673 int bt; /* blank tile grid filler */
16674 int n1; /* skip past initial "The " */
16675 int ec; /* event color index now */
16676 int sc; /* event color index filler */
16677 int es; /* event status bits */
16678 int i, j, k, x, y, z; /* counters */
16679 int ch; /* channel */
16680 int iz; /* icon size */
16682 /* lots of ways to track how the divider will appear */
16683 int cpn = -1; /* current program number */
16684 int cdn = -1; /* current day number */
16685 int ppn = -1; /* previous program number */
16686 int pdn = -1; /* previous day number */
16687 int ptd = -1; /* primetime day number */
16688 unsigned short pv[VCZ]; /* event program numbers */
16689 unsigned short nd[366]; /* 18 days is actual max */
16690 // char nd[366]; /* event day flags */
16691 int pvc = 0; /* prev virt channel */
16692 int nde = 0; /* number of days of events */
16693 int wdo = 0; /* week day offset */
16694 int lyo = 0; /* normal/leap year offset */
16695 unsigned int r; /* refresh time */
16696 int gts; /* grid time slot */
16697 int w3cv;
16699 /* FIXME: scan conflict wrong epg top */
16700 ch = p->chan;
16701 s = &ptc[ ch ].sig;
16703 /* old style, shows all events (all programs and all weekdays) on one page */
16704 if (0 != epg_all) s->yday = -1;
16706 d1 = "";
16707 *h = 0;
16709 /* file name */
16710 snprintf( n, sizeof(n), "%s%s/pg/%02d.html",
16711 out_path, ram_path, ch );
16713 get_time();
16714 advrlog( LOG_INFO, "%s(%s) %d", WHO, caller, ch);
16716 /* refresh is every 30m (or sooner if signal scanning this channel?) */
16717 r = utsnow;
16718 r += 1800;
16719 r /= 1800;
16720 r *= 1800;
16721 r -= utsnow;
16723 /* Stagger each refresh by 5s so multi-card doesn't waylay the CPU. */
16724 /* The CPU usage is an issue if capture and playback machine are the same. */
16725 r += 5 * (1 + arg_devnum);
16727 wf = fopen( n, "w");
16728 if (NULL == wf) return;
16730 g = "";
16731 if (1 == use_css) g = "large";
16732 if (2 == use_css) g = "small";
16733 if (3 == use_css) g = "tiny";
16735 // b = " <a href=\042%s\042>\n "IMG_EPG_FMT "\n </a>\n";
16736 // b = " <a href=\042%s\042>\n "IMG_EPG_CSS "\n </a>\n";
16737 b = "<a href=\042%s\042> " IMG_EPG_CLASS "</a>\n";
16740 e1 = &ev[ pgx[ 0 ] ];
16742 /* make a list of programs and days in guide */
16743 memset( nd, 0, sizeof(nd));
16744 memset( pv, 0, sizeof(pv));
16745 pvc = 0;
16747 /* calc start weekday of year */
16748 wdo = tloc.tm_wday - (tloc.tm_yday % 7);
16749 if (wdo < 0) wdo += 7;
16750 wdo %= 7;
16752 advrlog( LOG_INFO, "%s year %d starts on %s %d",
16753 WHO, 1900+tloc.tm_year, daysl[wdo], wdo);
16756 /************************************ build list of programs */
16757 for (i = 0; i < p->idx; i++) {
16758 e1 = &ev[ pgx[ i ] ];
16759 if (ppn != e1->pn) {
16760 /* duplicate check */
16761 k = ~0;
16762 for (j = 0; j < pvc; j++) {
16763 if (pv[j] == e1->pn) {
16764 k = 0;
16765 break;
16768 if (0 == k) continue;
16770 pv[ pvc ] = e1->pn;
16771 advrlog( LOG_INFO, "%s.%d pn %d",
16772 s->sid, pvc+1, pv[pvc] );
16773 pvc++;
16774 if (VCZ == pvc) break;
16776 ppn = e1->pn;
16779 /**************************************** build list of days */
16780 nde = 0;
16781 for (i = 0; i < p->idx; i++) {
16782 e1 = &ev[ pgx[ i ] ];
16783 if (pdn != e1->stm.tm_yday) {
16785 /* duplicate check */
16786 k = ~0;
16787 for (j = 0; j < nde; j++) {
16788 if ( nd[ j ] == e1->stm.tm_yday ) {
16789 k = 0;
16790 break;
16793 if (0 == k) continue;
16795 nd[ nde ] = e1->stm.tm_yday;
16796 nde++;
16797 if (18 == nde) break;
16799 pdn = e1->stm.tm_yday;
16802 /* sanity check, limits number of pgms to VCZ and days of events to 18 */
16803 if ( (pvc >= VCZ) || (nde > 18) ) {
16805 if (pvc >= VCZ)
16806 dvrlog( LOG_INFO, "%s ch %d invalid pvc %d idx %d",
16807 WHO, p->chan, pvc, p->idx);
16808 if (nde > 18)
16809 dvrlog( LOG_INFO, "%s ch %d invalid nde %d idx %d",
16810 WHO, p->chan, nde, p->idx);
16812 if (NULL != wf) fclose( wf );
16813 return;
16816 ppn = -1;
16817 pdn = -1;
16819 advrlog( LOG_INFO, "EPG%02d has ncs %d, days %d, events %d",
16820 ch, pvc, nde, p->idx );
16822 *d = 0;
16823 z = sizeof(d);
16825 /* header: */
16827 /* refresh is every 30m, or sooner if signal scanning this channel */
16828 if ((0 != scan_one) && (0 != scan_sig) && (p->chan == cap_chan))
16829 r = 30;
16831 snprintf( ht, sizeof(ht), "%s %s %02d", s->call, s->sid, s->chan);
16833 // build_head_html( h, sizeof(h), 13, ht, r, WHO ); /* xhtml 1.0 bit 3 */
16834 build_head_html( h, sizeof(h), 5, ht, r, WHO );
16835 fprintf( wf, "%s\n", h);
16837 /* top: station png, status and sig, and current event */
16838 *h = 0;
16840 build_epg_top(wf, h, sizeof(h), p);
16841 /* body: */
16843 // mo = p->hide | p->age | p->find;
16844 mo = 0;
16845 cdn = 0;
16847 /* displayed event count */
16848 y = 0;
16850 *d = 0;
16854 /********************************************* first EPG divider "sid.pn" */
16855 /* show top, pgm hrefs (if > 1 pgm), and day hrefs (if > 1 day) */
16856 if (s->yday >= 0) {
16857 cpn = s->pn;
16858 if (pvc > 0) cpn = pv[0];
16859 cdn = s->yday;
16861 /* is floating */
16862 asnprintf(d, z, "\n");
16863 asnprintf(d, z, "<div class=\042%s\042>\n", "epgdiv");
16865 asnprintf(d, z, "<br class=\042%s\042 />\n",
16866 "br_epgdiv" );
16868 /* center is pgm place-holder */
16869 if (ppn != cpn) {
16870 asnprintf(d, z, "<center id=\042pgm%d\042>\n", cpn );
16871 } else {
16873 asnprintf(d, z, "<center>\n", cpn );
16875 asnprintf(d, z, "<hr id=\042pgm%dday%d\042 />\n",
16876 cpn, cdn);
16878 #if 0
16879 /* first one doesn't need TOP? maybe it does for cellphone */
16880 /* FIXME: fat space after 'TOP', needs uparrow image too */
16881 asnprintf(d, z, "<a class=\042%s\042 "
16882 "href=\042%s\042>%s</a>\n | \n",
16883 "f4 green", "#top", "TOP ");
16884 #endif
16886 /*************************************** first Primetime href and sid.pn(s) */
16887 asnprintf(d, z, "<a class=\042%s\042 "
16888 "href=\042#pgm%dpt%d\042>"
16889 "Prime Time %s.%d</a>",
16890 "f4 green", cpn, cdn, s->sid, cpn);
16892 /* show virtual channel sid + minor program nav hrefs if more than one */
16893 if ( (0 != epg_all) || (s->pn < 1) ) {
16894 if (pvc > 1) {
16895 asnprintf(d, z, " | \n");
16896 for (j = 0; j < pvc; j++) {
16897 k = ~0;
16899 /* full cap shows all programs, new logical chan cap shows only this pgm# */
16900 if ( (s->pn > 0) && (s->pn != pv[j]) )
16901 continue;
16903 /* current program does not get href */
16904 if (cpn == pv[ j ]) k = 0;
16905 if (0 != k)
16907 /* TODO: can the VC also be selected with correct cgi? */
16908 asnprintf(d, z,
16909 "<a class=\042%s\042 "
16910 "href=\042#pgm%d\042>",
16911 "f4 green", pv[j]);
16913 /* netname.pgm# */
16914 asnprintf(d, z, "%s.%d \n", s->sid, pv[j] );
16916 if (0 != k) asnprintf(d, z, "</a>\n");
16919 /* fat space at end of list */
16920 asnprintf(d, z, " ");
16925 /******************************************** first EPG divider weekday(s) */
16927 /* guide day nav hrefs if more than one day in guide */
16928 if (nde > 1) asnprintf(d, z, " | \n");
16930 pdn = -1;
16932 // asnprintf(d, z, "wdo %d, ", wdo);
16934 for (j = 0; j < nde; j++) {
16935 char *fz;
16936 char **wdt;
16938 wdt = daysl;
16939 if (nde > 2) wdt = days;
16940 fz = "f4 green";
16941 lyo = 0;
16943 /* if next year-day number is less than first one, year meridian occurred */
16944 if ( (j > 0) && (nd[0] > nd[j]) ) {
16945 lyo++;
16946 // if (0 == (e1->stm.tm_year & 3 )) lyo++;
16949 k = ~0;
16951 if (cdn == nd[j]) k = 0;
16952 if ( nd[j] == s->yday ) fz = "f4 white";
16954 /* setting s->yday limits to single day uses less memory on web cell phone */
16955 if (0 != k)
16956 asnprintf( d, z, "<a class=\042%s\042 "
16957 "href=\042?n=%d:%d\042>",
16958 fz, ch, nd[j]);
16960 asnprintf( d, z, " %s ", wdt[(nd[j] + wdo+lyo) % 7 ]);
16961 // asnprintf( d, z, " %d ", (nd[j] + wdo + lyo) % 7 );
16963 if (0 != k) asnprintf(d, z, "</a>\n" );
16966 asnprintf(d, z, "</center>\n");
16967 asnprintf(d, z, "</div>\n");
16969 pdn = cdn;
16970 ppn = cpn;
16973 if (0 != *d) fprintf( wf, "%s", d);
16974 *d = 0;
16976 /***************** build list of events: p->idx is fewer loops than E*P*V */
16977 for (i = 0; i < p->idx; i++) {
16978 j = pgx[ i ];
16979 e1 = &ev[ j ];
16982 *d = 0;
16983 z = sizeof(d);
16985 cpn = e1->pn;
16986 cdn = e1->stm.tm_yday;
16988 /* if epg_all is not selected, and only one program selected,
16989 then only show the selected program, otherwise show all.
16991 if (0 == epg_all)
16992 if ((s->pn > 0) && (cpn != s->pn))
16993 continue;
16995 /* moved EPG days divider here before the further calculations */
16997 /* if year day set, only 1 divider, otherwise dividers for each day */
16998 x = 0;
16999 if ( (-1 < s->yday) && (s->yday == cdn) ) x = ~0;
17000 if (-1 == s->yday) x = ~0;
17002 /* is divider needed for new day or new program? */
17003 if ( (0 != x) && ((ppn != cpn) || (pdn != cdn)) ) {
17005 /*************************************************** EPG divider "sid.pgm" */
17006 /* show top, pgms (if > 1), and days (if > 1) */
17008 /* is floating */
17009 asnprintf(d, z, "\n");
17010 asnprintf(d, z, "<div class=\042%s\042>\n", "epgdiv");
17012 asnprintf(d, z, "<br class=\042%s\042 />\n",
17013 "br_epgdiv" );
17015 /* new program number or day number gets new primetime header */
17016 ptd = -1;
17018 if (ppn != cpn) {
17020 /* center is pgm place-holder */
17021 asnprintf(d, z, "<center id=\042pgm%d\042>\n", cpn );
17022 } else {
17024 /* still needs to be centered */
17025 asnprintf(d, z, "<center>\n", cpn );
17028 asnprintf(d, z, "<hr id=\042pgm%dday%d\042 />\n",
17029 cpn, cdn);
17030 #ifdef USE_EPG_TOP_IMG
17031 /* fat space after top icon */
17032 asnprintf(d, z, "<a class=\042%s\042"
17033 " href=\042%s\042>"
17034 "<img alt=\042%s\042"
17035 " src=\042%s\042"
17036 " border=\042%d\042></a>\n | \n",
17037 "f4 green", "#top", "TOP", "/pg/img/top24.png", 0);
17038 #else
17039 /* text only. top16.img is too small to see, and top24 makes vspacing odd */
17040 asnprintf(d, z, "<a class=\042%s\042 "
17041 "href=\042%s\042>%s</a>\n | \n",
17042 "f4 green", "#top", "TOP ");
17044 #endif
17046 /* primetime href and station ID */
17047 asnprintf(d, z, "<a class=\042%s\042 "
17048 "href=\042#pgm%dpt%d\042>"
17049 "Prime Time %s.%d</a>",
17050 "f4 green", cpn, cdn, s->sid, cpn);
17052 /* show virtual channel sid + minor program nav hrefs if more than one */
17053 if ((0 != epg_all) || (s->pn < 1)) {
17054 if (pvc > 1) {
17055 asnprintf(d, z, " | \n");
17057 for (j = 0; j < pvc; j++) {
17058 k = ~0;
17060 /* full cap shows all programs, new logical chan cap shows only this pgm# */
17061 if (0 == epg_all)
17062 if ( (s->pn > 0) && (s->pn != pv[j]) )
17063 continue;
17065 /* current program does not get href */
17066 if (cpn == pv[ j ]) k = 0;
17067 if (0 != k)
17068 asnprintf(d, z,
17069 "<a class=\042%s\042 "
17070 "href=\042#pgm%d\042>",
17071 "f4 green", pv[j]);
17073 /* netname.pgm# */
17074 asnprintf(d, z, "%s.%d ", s->sid, pv[j] );
17076 if (0 != k) asnprintf(d, z, "</a>\n");
17079 /* fat space at end of list */
17080 asnprintf(d, z, " ");
17083 ppn = cpn;
17085 /*************************************************** EPG divider weekday(s) */
17087 /* guide day nav hrefs if more than one day in guide */
17088 if (nde > 1) asnprintf(d, z, " | \n");
17090 cdn = e1->stm.tm_yday;
17092 pdn = -1;
17094 for (j = 0; j < nde; j++) {
17095 char **wdt;
17096 char *fz;
17098 wdt = daysl;
17099 if (nde > 1) wdt = days;
17100 fz = "f4 green";
17101 lyo = 0;
17103 /* normal year starts 1 weekday later */
17104 // if ( (j > 0) && (nd[0] > nd[j]) && (nd[j] < 7) )
17105 if ( (j > 0) && (nd[0] > nd[j]) ) {
17106 lyo++;
17107 /* leap year starts 2 weekdays later */
17108 // if (0 == (3 & e1->stm.tm_year)) lyo++;
17111 k = ~0;
17113 /* Old style: crashes a web cell phone when phone runs out of memory. */
17114 /* Current day has no href. All use local id tags
17115 for multi-day EPG if s->yday is not set
17117 if (cdn == nd[j]) k = 0;
17118 if ( nd[j] == s->yday ) fz = "f4 white";
17119 if (-1 == s->yday) {
17120 if (0 != k) asnprintf(d, z,
17121 "<a class=\042%s\042 "
17122 "href=\042#pgm%dday%d\042>",
17123 fz, cpn, nd[j]);
17124 asnprintf( d, z, " %s \n",
17125 wdt[ (nd[j] + wdo + lyo) % 7 ]);
17127 if (0 != k) asnprintf(d, z, "</a>\n" );
17128 } else {
17130 /* setting s->yday limits to single day uses less memory on web cell phone */
17131 if (0 != k) asnprintf( d, z,
17132 "<a class=\042%s\042 "
17133 "href=\042?n=%d:%d\042>",
17134 fz, ch, nd[j]);
17135 asnprintf( d, z, " %s \n",
17136 wdt[ (nd[j] + wdo + lyo) % 7 ]);
17138 if (0 != k) asnprintf(d, z, "</a>\n" );
17141 pdn = nd[ j ];
17144 asnprintf(d, z, "</center>\n");
17145 asnprintf(d, z, "</div>\n");
17146 pdn = cdn;
17147 /* if ppn pdn diff */
17151 /**************************************************** EPG divider primetime */
17154 /* draw primetime line at first timeslot starting at PRIMETIME HOUR or later */
17155 x = 0;
17156 if ( (s->yday >= 0) && (s->yday == cdn) ) x = ~0;
17157 if (s->yday < 0) x = ~0;
17159 if ( (0 != x) && (e1->stm.tm_hour >= PRIMETIME_HOUR) ) {
17161 /* primetime day number diff? */
17162 if (ptd != cdn) {
17164 /* needs id for primetime in href local jump list */
17165 asnprintf(d, z,"<div class=\042%s\042>\n",
17166 "epgdiv" );
17167 asnprintf(d, z,"<br class=\042%s\042 />\n",
17168 "br_epgdiv");
17170 asnprintf(d, z, "<hr />\n");
17172 /* id can't be used on div or hr? ok, use it on center */
17173 asnprintf(d, z, "<center id=\042pgm%dpt%d\042>\n",
17174 e1->pn, e1->stm.tm_yday);
17176 asnprintf(d, z, "<a class=\042%s\042 "
17177 "href=\042%s\042>TOP</a> | ",
17178 "green", "#top");
17180 asnprintf(d, z,
17181 "Prime Time %s.%d | %s, %s %d %d\n",
17182 s->sid, e1->pn,
17183 daysl[ e1->stm.tm_wday ],
17184 mons[ e1->stm.tm_mon ],
17185 e1->stm.tm_mday,
17186 1900 + e1->stm.tm_year );
17188 asnprintf(d, z, "</center>\n");
17190 asnprintf(d, z, "</div>\n");
17192 ptd = cdn;
17195 /* if divider is not blank, write it */
17196 if (0 != *d) fprintf(wf, "%s\n", d);
17197 *d = 0;
17200 /* day filter and program filter, but only if not using find or all */
17201 if ( (0 == epg_all) && (0 == p->find) ) {
17202 if ( (s->yday >= 0) && (e1->stm.tm_yday != s->yday) )
17203 continue;
17205 if ( (s->pn > 0) && (e1->pn != s->pn) )
17206 continue;
17210 #if 0
17211 /* if not using find & has set day filter & event doesn't match selected day,
17212 skip event
17214 if (0 == p->find)
17215 if (s->yday >= 0)
17216 if (e1->stm.tm_yday != s->yday)
17217 continue;
17218 #endif
17221 /*************************************************** build one event */
17222 y++;
17223 astrncpy( tn, e1->tname, PNZ );
17224 astrncpy( en, e1->name, PNZ );
17225 n1 = 0;
17227 /* We all know it's The... fix crapola */
17228 if (e1->name == strstr(e1->name, "The ")) n1 = 4;
17229 if (e1->name == strstr(e1->name, "THE ")) n1 = 4;
17232 astrncpy( en, &e1->name[n1], PNZ );
17233 astrncpy( ed, e1->desc, PDZ );
17235 /* Also fix this crapola. Needs less, not more. */
17236 d1 = strstr(en, ".com");
17237 if (d1 != NULL) *d1 = 0;
17239 /* NOTE: you'll probably have to adjust the last parm on truncate_text_kern
17240 to the number of pixels for the letter capital W in your san-serif font,
17241 if you're not using the font: mozilla san-serif smallest size 14.
17242 Sizes do not translate very well to width. You'll have to play with it.
17243 W is usually the widest character but that depends on diagonal W.
17246 /* needs to match _large dimensions in style.css */
17247 /* large event name with description */
17248 if (1 == use_css) {
17249 truncate_text_kern_box(
17250 en, kern_p256_8859_1,
17251 232, 8, 1, CSS_KERN_TLARGE, 1);
17252 // en[20] = 0;
17253 /* 7 lines */
17254 truncate_text_kern_box(
17255 ed, kern_p256_8859_1,
17256 226, 8, 7, CSS_KERN_DLARGE, 0);
17259 /* needs to match _small dimensions in style.css */
17260 /* small event name with 2 line description */
17261 if (2 == use_css) {
17262 astrncpy( en, e1->tname, sizeof(en)-1 );
17263 truncate_text_kern_box(
17264 en, kern_p256_8859_1,
17265 152, 8, 1, CSS_KERN_TSMALL, 1);
17266 // en[16] = 0;
17267 truncate_text_kern_box(
17268 ed, kern_p256_8859_1,
17269 148, 4, 2, CSS_KERN_DSMALL, 0);
17272 /* needs to match _tiny dimensions in style.css */
17273 /* tiny event name with no description */
17274 if (3 == use_css) {
17275 astrncpy( en, e1->tname, sizeof(en)-1 );
17276 /* 80px wide, 5 = 4px + 1px tile border, 1 line, 13 pixel, fragment ok */
17277 truncate_text_kern_box(
17278 en, kern_p256_8859_1,
17279 80, 5, 1, CSS_KERN_TTINY, 1);
17280 // en[16] = 0;
17281 ed[0] = 0;
17284 es = test_event_status( e1 );
17285 ec = 7; /* grey if nothing picks a color */
17287 /* pick event color from test event bits. order is important. blue not used. */
17289 /* TEST: skip spam except for current event?
17290 The problem is spam is removed in build pg epg. Whoops.
17291 The junk filter enabled will hide the current junk event.
17293 if ((p->hide)
17294 && (3 == ec)
17295 && (0 == (es & E_NOW)))
17296 continue;
17299 // cyan used for weekday timers, not automatic ones, they're green!
17300 // if (0 != (es & E_TIME)) ec = 4; /* cyan */
17301 if (0 != (es & E_SPAM)) ec = 0; /* black */
17302 if (0 != (es & E_MOVIE)) ec = 3; /* blue */
17303 if (0 != (es & E_TIME)) ec = 2; /* green */
17304 if (0 != (es & E_FIND)) ec = 2; /* green */
17305 /* aged will set on all old events except captures and cuts */
17306 if (0 != (es & E_AGED)) ec = 5; /* magenta */
17307 if (0 != (es & E_TS)) ec = 6; /* yellow */
17308 if (0 != (es & E_TSC)) ec = 1; /* red */
17310 /* staging grid color */
17311 sc = ec;
17312 /* es & E_NOW is set during entire event if it's current */
17313 if (0 != (es & E_NOW)) ec = 8; /* white */
17314 gts = 0;
17316 /* if grid and event > 30m, want white NOW tile for the proper timeslot */
17317 if ((0 != epg_grd) && (8 == ec)) {
17319 /* how many 30m blocks since start? */
17320 gts = (utsnow - e1->st) / 1800;
17321 if (0 != gts) ec = sc;
17324 /***************************************************** event detail list */
17325 *d = 0;
17327 /* classes broken down for smaller style.css */
17328 asnprintf(d, z, "\n");
17330 asnprintf(d, z, "<div id=\042p%dt%d\042"
17331 " class=\042%s %s%d\042>\n",
17332 e1->pn, e1->st, "epgtile", colors[sc], 1);
17333 asnprintf(d, z, "<dl class=\042dl_%s\042>\n", g);
17334 asnprintf(d, z, "<dt class=\042dt_%s %s\042>\n", g, "epgtc");
17336 /*************************************************************** event name */
17337 asnprintf(d, z, "%s", en );
17339 /* time is not displayed in small or tiny css mode */
17340 et = e1->st;
17341 if (0 == (2 & use_css)) {
17342 // localtime_r( &et, &estm );
17344 asnprintf(d, z, "<br />\n");
17346 asnprintf(d, z, "%02d:%02d ",
17347 e1->stm.tm_hour, e1->stm.tm_min);
17348 asnprintf(d, z, "%dm ", e1->ls/60);
17350 asnprintf(d, z, "%s ", days[e1->stm.tm_wday]);
17352 /* date (only when changes) is not in small css mode either */
17353 /* NOTE: sets pdn to cdn at end of loop */
17354 if ( pdn != e1->stm.tm_yday )
17355 asnprintf(d, z, "%s %2d ",
17356 mons[e1->stm.tm_mon], e1->stm.tm_mday);
17359 /* update previous program number and day number with today */
17360 pdn = e1->stm.tm_yday;
17361 ppn = e1->pn;
17363 // asnprintf(d, z, "%04d ", 1900 + estm.tm_year);
17364 // asnprintf(d, z, " <br />\n");
17366 asnprintf(d, z, "\n</dt>\n");
17368 /**************************************************** EPG <dd> + buttons */
17370 /* no scrollbars yet */
17371 sb = "";
17372 /* want kern truncated description with no scrollbars */
17373 ed1 = ed;
17375 /* large tiles, if scroll enabled, add that class and use longer desc */
17376 if ( (1 == use_css) && (0 != epg_sb)
17377 && (strlen(ed) < strlen(e1->desc)) )
17379 sb = " epgvsb";
17381 /* scrollbars get the non kern-truncated version */
17382 ed1 = e1->desc;
17384 asnprintf(d, z,"<dd class=\042dd_%s %s%d%s\042>\n",
17385 g, colors[ec], 0, sb);
17387 // asnprintf(d, z,"<dd class=\042dd_%s %s%d\042>\n",
17388 // g, colors[ec], 0);
17389 // asnprintf(d, z,"<div class=\042%s\042>\n", t);
17392 /************************************************** <dd> button list */
17394 /* action list, junk status is always visible */
17396 iz = 24;
17397 if (use_css > 1) iz = 16;
17399 *a = 0;
17401 snprintf( a, sizeof(a), "spam%d.png", iz);
17403 c = "SPAM";
17404 snprintf( u, sizeof(u),
17405 "/pg/%02d.html?j=%s:%u#p%dt%d", ch, e1->tname, e1->st,
17406 e1->pn, e1->st );
17408 if ( ( (3 == (3 & use_css))
17409 || (0 == (es & E_TIME)) )
17410 && (0 == (es & E_TS))
17412 if (0 != (es & E_SPAM))
17413 snprintf( a, sizeof(a), "unspam%d.png", iz);
17415 asnprintf(d, z, b, u, g, c, a);
17419 /* search only valid for non-spam and non-aged */
17420 *a = 0;
17421 if ( (0 == (es & E_SPAM))
17422 && (3 > (3 & use_css))
17423 && (0 == (es & E_AGED)) ) {
17424 snprintf(a, sizeof(a), "search%d.png", iz);
17425 c = "FIND";
17426 snprintf( u, sizeof(u),
17427 "/pg/%02d.html?s=%02d:%s",
17428 ch, ch, e1->tname);
17429 if (0 != (es & E_FIND))
17430 snprintf(a, sizeof(a), "unsearch%d.png", iz);
17431 asnprintf(d, z, b, u, g, c, a);
17434 /* timer only valid for non-aged and non spam */
17435 snprintf(a, sizeof(a), "timeradd%d.png", iz);
17436 c = "ADD";
17437 if (0 != (es & E_TIME)) {
17438 snprintf( u, sizeof(u),
17439 "/pg/%02d.html?d=V%02d:%u:%03d:%s@.%d",
17440 ch, ch, e1->st, e1->ls/60, e1->tname, e1->pn);
17441 c = "DEL";
17442 snprintf(a, sizeof(a), "timerdel%d.png", iz);
17443 if (0 != (es & E_NOW)) {
17444 c = "STOP";
17445 snprintf(a, sizeof(a), "timerstop%d.png", iz);
17447 } else {
17448 snprintf( u, sizeof(u),
17449 "/pg/%02d.html?a=V%02d:%u:%03d:%s@.%d:%d",
17450 ch, ch, e1->st, e1->ls/60, e1->tname, e1->pn,
17451 e1->etmid);
17454 if ( (0 == (es & E_AGED)) && (0 == (es & E_SPAM)) )
17455 asnprintf(d, z, b, u, g, c, a);
17457 /* only do these if transport stream file exists */
17458 if (0 != (es & E_TS)) {
17460 /* intentionally put play farther away from zap */
17461 snprintf( a, sizeof(a), "blank%d.png", iz);
17462 c = "PLAY";
17463 if ( (3 != use_css) && (0 != (es & E_LOG)) ) {
17464 snprintf( u, sizeof(u),
17465 "mpeg://%s:%d/%s-%02d%02d-%02d%02d.%d.ts",
17466 arg_whost, arg_wport, e1->tname,
17467 e1->stm.tm_mon+1, e1->stm.tm_mday,
17468 e1->stm.tm_hour, e1->stm.tm_min,
17469 e1->pn );
17470 snprintf( a, sizeof(a), "xine%d.png", iz);
17471 c = "XINE";
17472 asnprintf(d, z, b, u, g, c, a);
17475 snprintf( a, sizeof(a), "blank%d.png", iz);
17476 c = "CAPLOG";
17477 if (0 != (es & E_LOG)) {
17478 snprintf( u, sizeof(u),
17479 "/%s-%02d%02d-%02d%02d.%d.html",
17480 e1->tname, e1->stm.tm_mon+1, e1->stm.tm_mday,
17481 e1->stm.tm_hour, e1->stm.tm_min, e1->pn );
17482 snprintf( a, sizeof(a), "log%d.png", iz);
17483 asnprintf(d, z, b, u, g, c, a);
17486 snprintf( a, sizeof(a), "blank%d.png", iz);
17487 c = "EDIT";
17489 if (0 != (es & E_TSX)) {
17490 snprintf( a, sizeof(a), "edit%d.png", iz);
17491 /* no port, assumes local running script maps the path to cut directory */
17492 snprintf( u, sizeof(u),
17493 "xtc://%s-%02d%02d-%02d%02d.%d.ts",
17494 e1->tname, e1->stm.tm_mon+1, e1->stm.tm_mday,
17495 e1->stm.tm_hour, e1->stm.tm_min, e1->pn );
17496 /* if have tsc file, use different color smiley */
17497 if (0 != (es & E_TSC))
17498 snprintf( a, sizeof(a), "cut%d.png", iz);
17499 asnprintf(d, z, b, u, g, c, a);
17502 snprintf( a, sizeof(a), "blank%d.png", iz);
17503 c = "CAPZAP";
17505 /* only zap timer if ts and on timer list and is now */
17506 if ( (0 != (es & E_TS))
17507 && (0 != (es & E_TIME))
17508 && (0 != (es & E_NOW))
17510 /* it could be on the timer list but getting ignored... */
17511 && (CAP_TIME == cap_now) /* timer cap */
17512 && (e1->chan == cap_chan) /* same chan */
17513 && (e1->pn == cap_pn) /* same pgm */
17516 snprintf( a, sizeof(a), "zap%d.png", iz);
17517 snprintf( u, sizeof(u),
17518 "/pg/%02d.html?z=V%02d:%u:%05d:%s@.%d",
17519 ch, ch, e1->st, e1->ls, e1->tname,
17520 e1->pn);
17521 #if 0
17522 /* unlink only works from index.html */
17523 if (0 != (es & E_MTX))
17524 snprintf( u, sizeof(u),
17525 "/pg/%02d.html?u="
17526 "%s-%02d%02d-%02d%02d.%d.ts",
17527 ch, e1->tname,
17528 e1->stm.tm_mon+1,
17529 e1->stm.tm_mday,
17530 e1->stm.tm_hour,
17531 e1->stm.tm_min,
17532 e1->pn);
17533 #endif
17534 asnprintf(d, z, b, u, g, c, a);
17538 /* this will only fit if no .ts* buttons */
17539 if (0 == (es & E_TS)) {
17540 c = " ";
17541 if (0 != (es & E_MOVIE)) {
17542 c = "MOVIE ";
17543 } else {
17544 if (0 != (es & E_SPAM)) c = "SPAM ";
17545 if (0 != (es & E_NOW)) c = "NOW ";
17547 /* large tiles can hold rating */
17548 if (0 == (2 & use_css)) {
17549 asnprintf(d, z, "%s%s ", c, e1->rating);
17550 } else {
17551 if (2 == use_css) {
17552 // asnprintf(d, z, "%02d%02d %dm",
17553 asnprintf(d, z, "%02d:%02d",
17554 e1->stm.tm_hour,
17555 e1->stm.tm_min );
17556 // e1->ls/60);
17561 /* tiny doesn't need the break, everything else does */
17562 if (3 != use_css) {
17563 asnprintf(d, z, "\n<br />\n");
17564 if (0 != *e1->desc) {
17566 /* KTRK EPG usually has event description same as event name */
17567 if (0 == strncmp(e1->desc, e1->name, PNZ)) {
17568 ed1 = "n/a";
17570 } else {
17571 ed1 = "n/a";
17575 asnprintf(d, z, "%s\n", ed1);
17577 // asnprintf(d, z, "</div>\n");
17578 asnprintf(d, z, "</dd>\n");
17579 asnprintf(d, z, "</dl>\n");
17580 asnprintf(d, z, "</div>\n");
17581 fprintf( wf, "%s\n", d);
17583 /* how many blank tiles? */
17584 bt = e1->ls / 1800;
17585 bt --;
17587 /* if grid, insert blanks for 30m intervals if event > 30m */
17588 /* delete (0 == mo) unused now */
17590 if ( (0 != epg_grd) && (0 == mo) && (bt > 0) )
17592 *d = 0;
17593 build_epg_filler(d, z, bt, sc, en, g, gts, e1);
17594 fprintf( wf, "%s\n", d);
17598 /* if no events, try to show why */
17599 if (0 == y) {
17600 char *ne = "";
17602 *d = 0;
17603 asnprintf(d, z, "<div class=\042%s\042>\n", "epgdiv");
17604 asnprintf(d, z, "<br class=\042%s\042 />\n", "br_epgdiv");
17605 asnprintf(d, z, "<center>");
17606 asnprintf(d, z, "<hr />\n");
17607 if (0 != p->find) ne = "matching ";
17609 /* depending on wdo and lyo to be correct from first or last divider */
17610 if (s->yday >= 0) ne = daysl[ (s->yday + wdo + lyo) % 7];
17612 /* full cap doesn't show program number */
17613 if (s->pn > 0) {
17614 asnprintf(d, z, "No %s EPG events on %s.%d",
17615 ne, s->sid, s->pn);
17616 } else {
17617 asnprintf(d, z, "No %s EPG events on %s",
17618 ne, s->sid);
17621 asnprintf(d, z, "</center>\n");
17622 asnprintf(d, z, "<hr />\n");
17623 asnprintf(d, z, "</div>\n");
17625 fprintf(wf, "%s\n", d);
17629 /* build a list of volatile and weekday timers. timer list order is fine */
17630 build_epg_timers(wf, d, z, g, ch);
17632 fprintf( wf, "<br clear=\042%s\042 />", "all");
17634 /* html 4.01 transitional validated */
17635 w3cv = 5;
17636 #ifdef USE_XHTML
17637 /* xhtml 1.0 transitional validated */
17638 w3cv = 6;
17639 #endif
17640 /* footer */
17641 *h = 0;
17642 build_foot_html( h, sizeof(h), w3cv ); /* validated */
17643 fprintf( wf, "%s\n", h);
17644 fflush( wf );
17645 fclose( wf );
17649 /* macro-ize it for reability: src alt w h hs vs b */
17650 /* Dump Event Program Guide
17652 Outputs the following:
17653 browser readable .html The new point and blech interface.
17654 p->chan has the guide number for the guide it will save to
17656 Optional output:
17657 human readable .txt text file
17658 machine readable .db tab delimited fields
17661 Changed to use same structures as load_epg:
17662 e points to epg[] structure to use
17663 p points to pgm[] filter structure
17664 pgx points to pg[] index of filtered/unfiltered events
17668 /* TODO: It writes 3 files where one is enough for http operation.
17669 Break apart build_epg_html for string and call this dump epg text,
17670 after all the html moved to build epg html. CONS: 3 loops not one.
17672 The other files are for people who want to play with the EPG data
17673 themselves, since some have expressed interest in feeding a database
17674 with it. To me, once the show is over, I'm done with it.
17675 The temporary nature of this html EPG is more than enough for my needs.
17676 Eventually the stations will send 16 day guides if they can.
17679 /* 1400 lines? sheesh. might want to break it down some. */
17681 /* static */
17682 void
17683 dump_epg_html3 ( struct event_s *e, struct pgm_s *p, short *pgx, char *caller )
17685 char h[8192], /* html header and footer */
17686 n[PNZ+16], /* event name, using larger than PNZ cutoff */
17687 t[PDZ+16], /* event description, using larger than PDZ cutoff */
17688 epn[256], /* EPG html name, ch.epg */
17689 dn[256], /* database file name */
17690 gn[256], /* guide text file name */
17691 wn[256], /* web html file name */
17692 ln[256], /* html capture log name */
17693 dt[32], /* date time */
17694 un[64], /* filename and url name */
17695 en[20]; /* truncated eventname */
17698 char *h0, *h1, *h2, *h3, *h4, /* font size */
17699 *c0, *c1, *c2, *c3, *c4, *c5, *c6, *c7, /* color */
17700 *f, *gt, *ec; /* misc */
17701 char *ename = ""; /* full eventname ptr */
17702 char *edesc = ""; /* full event desc ptr */
17704 /* TODO these should be moved to global as daysl[] */
17705 char *wdl[7] = { "Sunday", "Monday", "Tuesday", "Wednesday",
17706 "Thursday", "Friday", "Saturday" };
17708 char *wds[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
17711 char *wd[7]; /* weekdays, long or short format */
17714 FILE *df, /* database file */
17715 *gf, /* text file */
17716 *wf; /* html file */
17718 int i, j, k, ok,
17719 tpg, /* test program guide against timer events */
17720 pn, /* event program number */
17721 lpn, /* prev pgm#, if chg use <hr id=pgm#> */
17722 ldn, /* prev dayofweek, if chg use <hr id=day#> */
17723 nde, /* number of days for events */
17724 ptd, /* primetime day number */
17725 cdn, /* current day number */
17726 cdw, /* current day of week */
17727 sdn, /* start day number for epg 0(Sun)...6(Sat) */
17728 sdw; /* start day of week */
17730 int ref; /* meta refresh value to nearest 30m */
17731 int pf; /* program guide fail reason */
17732 short pv[VCZ]; /* vc program numbers, filtered */
17733 short pvc; /* vc program count, filtered */
17735 short nd[32]; /* number of days with daynumer in guide */
17737 struct sig_s *s;
17738 struct event_s *e1;
17739 unsigned int letmid = 0; /* last etmid seen */
17740 struct tm stm; /* event start time converted */
17741 struct stat64 fs; /* reused for various tests */
17742 time_t st; /* event start time raw */
17743 int sr; /* scan rate or 1 */
17744 char *si; /* IDLE, SCAN, SCANALL, CAP, FAIL */
17746 gf = NULL;
17747 df = NULL;
17749 s = &ptc[ p->chan ].sig;
17751 advrlog( LOG_INFO, "%s(%s) %d", WHO, caller, p->chan);
17753 /* needs a blank epg at the very least for status display */
17754 /* if (0 == p->idx) return; */
17756 ptd = 0;
17757 lpn = 0;
17759 f = "font face=\042sans-serif\042"; /* epg uses proportional font */
17761 /* smaller number is smaller font */
17762 h0 = "size=\0420\042";
17763 h1 = "size=\0421\042";
17764 h2 = "size=\0422\042";
17765 h3 = "size=\0423\042";
17766 h4 = "size=\0424\042";
17768 c0 = colors3[0];
17769 c1 = colors3[1];
17770 c2 = colors3[2];
17771 c3 = colors3[3];
17772 c4 = colors3[4];
17773 c5 = colors3[5];
17774 c6 = colors3[6];
17775 c7 = colors3[7];
17777 dt[0] = 0;
17779 /* use file modify time for live/save date */
17780 /* text_time( dt, p->mtime, 20); / / date in pgm is user refresh time */
17782 /* see if saved epg exists */
17783 snprintf( epn, sizeof(epn)-1, "%s%spg/%02d.epg",
17784 out_path, ram_path, p->chan);
17785 ok = stat64( epn, &fs);
17787 /* file exists, use file mtime for date time string */
17788 if (0 == ok) {
17789 text_time( dt, (int)fs.st_mtime, 20);
17790 } else {
17792 /* file does not exist, use now for date time string */
17793 st = time( NULL );
17794 localtime_r( &st, &stm );
17795 asctime_r( &stm, dt );
17796 dt[strlen(dt)-1] = 0; /* remove \n */
17797 advrlog( LOG_INFO, "epg %d update %s", p->chan, dt);
17800 ldn = -1;
17801 lpn = -1;
17803 if (0 != arg_dummy) p->chan = 0; /* dummy guide to channel 0 */
17805 snprintf( gn, sizeof(gn)-1, "%s%spg/%02d.txt",
17806 out_path, ram_path, p->chan);
17808 snprintf( dn, sizeof(dn)-1, "%s%spg/%02d.db",
17809 out_path, ram_path, p->chan);
17811 /* station/network id is more readable to www user */
17812 snprintf( wn, sizeof(wn)-1, "%s%spg/%02d.html",
17813 out_path, ram_path, p->chan);
17815 /* Open some files. the html file is mandatory or nothing else done. */
17816 wf = fopen( wn, "w" );
17817 if (NULL == wf) {
17818 dvrlog( LOG_INFO, "couldn't open output %s", wn);
17819 return;
17822 /* Tab separated and visual formatted text file output */
17823 #ifdef USE_EXTRA_OUTPUT
17824 gf = fopen( gn, "w" );
17825 if (NULL == gf) {
17826 dvrlog( LOG_INFO, "couldn't open output %s", wn);
17829 df = fopen( dn, "w" );
17830 if (NULL == df) {
17831 dvrlog( LOG_INFO, "couldn't open output %s", wn);
17833 #endif
17835 /* all three have to open or nothing happens */
17836 if (NULL != gf)
17837 fprintf( gf, "Event Program Guide for %s %s%d created on %s\n",
17838 s->call, s->sid, p->chan, date_now);
17840 /* It's less annoying to do the refresh at top and bottom of the hour,
17841 so the events can age off the list and current event status updated,
17842 and to prevent some stalls in playback during the refresh interval.
17844 User can still force immediate refresh with the recycle button.
17846 NOTE:
17847 This is not quite as useful when events don't start at hour or half hour,
17848 but adjusting it to refresh at end of event should reduce that problem.
17849 This is for those shows that refuse to use the half hour time slotting.
17851 CPU LOAD REDUX:
17852 This also has a device number time delay to prevent all the instances
17853 from saving/dumping the guides at exactly the same time.
17855 REFRESH:
17856 Refresh is variable and can be set by the following conditions:
17858 Capture:
17859 Refresh rate is managed to reduce CPU load during capture.
17861 Conditions:
17863 If capture elapsed time before 180s:
17864 Refresh is every 15s during first 60 seconds, 4x.
17866 If capture elapsed time after 180s:
17867 Refresh should act like no capture settings below.
17869 No Capture:
17870 Refresh is x seconds until next 30m frame expires.
17872 Conditions:
17874 If epg timeout is less than utsnow + refresh,
17875 the refresh is the epg timeout
17877 If timer end is less than refresh timeout,
17878 the refresh is the timer end
17880 TODO: now write the code to match above description. is close now.
17883 ref = 0;
17886 /* capture needs to refresh in 15s if cap duration less than 60s */
17887 if ( (CAP_NONE != cap_now) && (cap_chan == p->chan) ) {
17888 /* Manual user cap will be inefficient and dump epg html every 15s,
17889 but timer cap does have a known finite value from timer start + len,
17890 so it only updates when user requests it via http.
17892 if (CAP_TIME == cap_now) {
17893 struct qtimer_s *ts1;
17894 ts1 = &timer[ timer_rec ]; /* timer rec should be current */
17895 ref = (ts1->start + ts1->len) - utsnow;
17897 /* this should never happen. if it does, make it fall thru to below */
17898 if (ref < 0) ref = 0;
17900 if (utsets < 60) ref = 15;
17903 /* capture did not set refresh, so set it here */
17904 if (0 == ref) {
17906 /* see what EPG mtime - utsnow is, under 30m takes precedence */
17907 filebase( epn, wn, F_PFILE); /* file path and file name base */
17908 asnprintf( epn, sizeof(epn), "%s", ".epg"); /* ch.epg file */
17909 i = stat64( epn, &fs);
17911 /* get ch.epg mtime for refresh delay, if >30m, refresh 0 to handle it below */
17912 if (0 == i) {
17913 ref = (int)fs.st_mtime - utsnow;
17914 if (ref > 1800) ref = 0; /* expires beyond 30m time slot */
17915 if (ref <= 0) ref = 0; /* expired now, set 30m time slot */
17919 /* if all else fails use next 30m slot for refresh */
17920 if (0 == ref) {
17921 st = utsnow;
17922 st /= 1800; /* number of 30m slots */
17923 st *= 1800; /* this 30m slot */
17924 st += 1800; /* next 30m slot */
17925 ref = st - utsnow;
17928 /* the cpu load should be spread out by 5s for each instance */
17929 ref += 5 + (5 * arg_devnum);
17931 /* 10s refresh when signal scanning and same channel as epg */
17932 if (CAP_NONE == cap_now) {
17933 if (p->chan == cap_chan) {
17934 if (0 != scan_sig) {
17935 ref = 10;
17940 advrlog( LOG_INFO, "%s(%s) ch %02d utsnow %d refresh in %ds",
17941 WHO, caller, p->chan, utsnow, ref);
17943 if (NULL != wf) advrlog( LOG_INFO, "%s %s writing header", WHO, wn);
17945 /* Wait until guide has figured out when the html page expires:
17946 This is actually more complicated and probably requires dynamic update.
17948 The guide should refresh when program status changes. It should
17949 refresh more often than the computed EIT-1.0 start value, to make
17950 the web page reflect the latest current program as it sees it.
17953 *h = 0;
17954 snprintf( n, sizeof(n)-1, "%s %s%d", s->call, s->sid, s->chan);
17955 build_head_html( h, sizeof(h), 5, n, ref, WHO ); /* EPG rel href */
17957 fprintf( wf, "%s\n", h);
17959 /* table within a table */
17960 fprintf( wf, TBL_FMT, TBL_BRD, 0, 4, "center", "100%");
17962 /* 320 + 8 + 8, for station image size and borders */
17963 fprintf( wf, "<tr>\n");
17964 fprintf( wf, TD_FMT, "middle", "336");
17966 /* see if 320x180 station bitmap exists and add if so */
17967 snprintf( un, sizeof(un), "%s%spg/img/%s.png",
17968 out_path, ram_path, s->sid );
17969 ok = stat64( un, &fs );
17970 if (0 == ok) {
17971 snprintf( un, sizeof(un), "img/%s.png", s->sid);
17972 fprintf( wf, IMGA_FMT "\n", s->sid, un, "left", 320, 180, 8, 8, 0);
17975 /* should translate to 100% of what's left
17976 after 336 pixels took x% of original 100% */
17977 fprintf( wf, TBL_FMT, TBL_BRD, 0, 4, "center", "100%");
17979 /* middle 33% table, or left 50% table if no image */
17980 fprintf( wf, "<tr>\n");
17981 fprintf( wf, TD_FMT, "top", "50%");
17982 /* epg status font */
17983 f = "font";
17984 fprintf( wf, "<p>\n");
17986 /* indicate station is atsc system time-sync Z config line with yellow font */
17987 if (cap_zc == s->chan) {
17988 fprintf( wf, "<%s %s %s>", f, h3, c3);
17989 } else {
17990 fprintf( wf, "<%s %s %s>\n", f, h3, c7);
17993 fprintf( wf, "%d Events %s %s%d ", p->idx, s->call, s->sid, s->chan );
17994 if (s->mgt_hrs > 0) fprintf( wf, "%dh", s->mgt_hrs );
17996 /* turn off yellow text for rest of epg info */
17997 fprintf( wf, "</%s>\n", f);
17998 fprintf( wf, "<br>\n");
18000 gt = "";
18002 #ifdef USE_UPDATE
18003 #warning using EPG update
18004 /* see above for refresh setting */
18005 if (ref > 0) {
18006 st = utsnow + ref;
18007 localtime_r( &st, &stm );
18009 /* TODO: re-use refresh to be a link to refresh */
18010 fprintf( wf, "UPD %02d:%02d:%02d %lds",
18011 stm.tm_hour, stm.tm_min, stm.tm_sec, st - utsnow );
18014 #if 0
18015 else
18017 if (0 != s->pgto) {
18018 fprintf( wf, "Guide expired" );
18019 } else {
18020 fprintf( wf, "Non-auto EPG" );
18023 #endif
18025 fprintf( wf, "<br>\n");
18027 gt = "NEW";
18028 if (0 == s->pgto) gt = "OLD";
18029 if (CAP_NONE != cap_now)
18030 if (p->chan == cap_chan)
18031 gt = "Live";
18033 /* future mtime for saved or last ctime for live */
18034 fprintf( wf, "%s %s ", gt, &dt[11]); /* created on */
18035 dt[10] = 0;
18036 fprintf( wf, "%s ", &dt[4]);
18038 if ( (CAP_NONE != cap_now) && (p->chan == cap_chan) )
18039 fprintf( wf, "ET %ds", utsets);
18041 fprintf( wf, "<br>\n");
18042 #endif
18044 st = s->pgmt;
18045 localtime_r( &st, &stm );
18047 fprintf( wf, "Next %02d:%02d:%02d %lds\n",
18048 stm.tm_hour, stm.tm_min, stm.tm_sec, st - utsnow );
18049 fprintf( wf, "<br>\n");
18052 /* show filter settings and hrefs to change them */
18053 fprintf( wf, "<%s %s %s>%s </%s>\n", f, h3, c7, "Filters: ", f);
18055 /* cgi toggle search filter */
18056 fprintf( wf, "<a href=\042%02d.html?m=%d\042>",
18057 s->chan, s->chan );
18059 /* uppercase means filter on */
18060 fprintf( wf, "<%s %s %s> %s </%s>",
18061 f, h1, c3, (0 != p->find) ? "FIND":"find", f );
18063 fprintf( wf, "</a>\n");
18066 /* cgi toggle spam filter */
18067 fprintf( wf, "<a href=\042%02d.html?h=%d\042>",
18068 s->chan, s->chan );
18070 /* uppercase means filter on */
18071 fprintf( wf, "<%s %s %s> %s </%s>",
18072 f, h1, c4, (0 != p->hide) ? "JUNK":"junk", f );
18074 fprintf( wf, "</a>\n");
18077 /* cgi toggle aged filter */
18078 fprintf( wf, "<a href=\042%02d.html?o=%d\042>",
18079 s->chan, s->chan );
18081 /* uppercase means filter is on */
18082 fprintf( wf, "<%s %s %s> %s </%s>",
18083 f, h1, c6, (0 != p->age) ? "AGED":"aged", f );
18085 fprintf( wf, "</a>\n");
18086 fprintf( wf, "<br>\n");
18088 /* FIXME: spaghetti logic for this and the next one */
18090 /* STATUS FIRST:
18091 STATUS: IDLE SCAN SCANALL CAPTURE
18092 SIGNAL: xx%AVG
18094 fprintf( wf, "Status: ");
18096 si = "Idle";
18098 if (CAP_NONE != cap_now) {
18099 si = "Cap EPG ";
18100 if (CAP_TIME == cap_now) si = "Cap Timer ";
18101 if (CAP_USER == cap_now) si = "Cap Manual";
18105 if (CAP_NONE == cap_now) {
18106 if (0 != scan_sig) si = "Scan";
18107 if (0 == scan_one) si = "Scan All";
18110 if (in_file < 3) si = "Sleeping";
18112 /* put up status and relative href for finding current capture EPG */
18113 fprintf( wf, "%s\n", si);
18115 /* not Idle gets href to active guide unless already here */
18116 // if ( ('I' != *si) && (cap_chan != s->chan) )
18117 fprintf( wf, "<a href=\042/pg/%02d.html\042>%s </a>",
18118 cap_chan, ptc[cap_chan].sig.sid );
18120 #if 0
18121 /* Idle shows last capture fail status for this channel, if not sig scan */
18122 if ('I' == *si) {
18123 if (0 == scan_sig) {
18124 if (s->cap_fail > 0) {
18125 fprintf( wf, "<%s %s %s>\n", f, h3, c1 );
18126 fprintf( wf, "%s FAIL </%s>", cap_fails[s->cap_fail], f );
18130 #endif
18132 fprintf( wf, "<br>\n");
18134 /* show last sig avg */
18136 sr = scan_rate;
18137 if (0 == scan_sig) sr = 0;
18138 if (CAP_NONE != cap_now) sr = 1;
18140 if (scan_snr) {
18141 fprintf( wf, "SNR: ");
18142 calc_snr_rms( s );
18143 fprintf( wf, "%d.%02d RMS\n", 0xFF & (s->snr_rms >> 8),
18144 0xFF & ((s->snr_rms * 100) >> 8) );
18145 } else {
18146 fprintf( wf, "Sig: ");
18147 calc_sig_avg( s );
18148 fprintf( wf, "%02d%% Avg\n", s->str_avg);
18151 if (0 != sr) {
18152 if (s->chan == cap_chan)
18153 fprintf( wf, " @ %d/s", sr);
18155 fprintf( wf, "<br>\n");
18157 /* show error rate during capture, or blank line */
18158 if (CAP_NONE != cap_now) {
18159 double erate;
18160 erate = 100.0 * (double) pkt.errors / (double) pkt.count;
18161 ec = c2;
18163 /* red is over 1% yellow is over .5 % bad */
18164 if (erate > 0.5) ec = c3;
18165 if (erate > 1.0) ec = c1;
18167 /* show errors if doing capture */
18168 if ( (pkt.errors > 0) && (cap_chan == s->chan) ) {
18169 fprintf( wf, "Err: ");
18170 fprintf( wf, "<%s %s %s>\n", f, h2, ec);
18171 fprintf( wf, "%4.2f%% (%d)", erate, pkt.errors);
18172 fprintf( wf, "</%s>", f);
18173 fprintf( wf, "<br>\n");
18174 } else {
18175 /* no errors gets a blank line */
18176 fprintf( wf, "<br>\n");
18178 } else {
18179 fprintf( wf, "<br>\n");
18183 /* other buttons may be variable, put them to right of signal graph */
18184 #ifdef USE_PNG
18185 /* if (CAP_NONE != cap_now) */ /* no, show the signal graph always */
18187 char ni[16];
18188 snprintf( ni, sizeof(ni), "%d-%02d.png", arg_devnum, s->chan);
18189 fprintf( wf, IMG_FMT,
18190 "SIGNAL", ni, SIG_PNG_W, SIG_PNG_H, 4, 3, 1);
18191 fprintf( wf, "\n");
18193 #endif
18196 /* NOTE: these hrefs appear when not capturing */
18198 /* cgi reload (info cap) href img if no capture */
18199 if (CAP_NONE == cap_now) {
18200 fprintf( wf, "<a href=\042/pg/%02d.html?g=%d\042>",
18201 s->chan, s->chan);
18202 fprintf( wf, IMG_FMT,
18203 "EPG", "img/epgon.png", 48, 48, 4, 4, 0);
18204 fprintf( wf, "</a>\n");
18205 } else {
18206 /* cgi only info cap gets zap? full cap could use it too */
18207 if ((CAP_INFO == cap_now) && (s->chan == cap_chan)) {
18208 fprintf( wf, "<a href=\042%02d.html?x=%d\042>",
18209 s->chan, s->chan);
18210 fprintf( wf, IMG_FMT,
18211 "ZAP", "img/epgoff.png", 48, 48, 4, 4, 0);
18212 fprintf( wf, "</a>\n");
18216 /* signal scan toggle href and img */
18217 #if 1
18218 if (CAP_NONE == cap_now) {
18220 /* not scanning or not same channel gets scan enable button */
18221 if ( (0 == scan_sig) || (s->chan != cap_chan) )
18223 fprintf( wf, "<a href=\042%02d.html?v=%d\042>",
18224 s->chan, s->chan );
18225 fprintf( wf, IMG_FMT,
18226 "SCAN", "img/scanon.png", 48, 48, 4, 4, 0);
18227 fprintf( wf, "</a>\n");
18228 } else {
18230 /* is scanning and is same channel gets signal scan disable button */
18231 /* TESTME: have to click it twice to turn it off if not same channel? */
18232 fprintf( wf, "<a href=\042%02d.html?v=%d\042>",
18233 s->chan, s->chan );
18234 fprintf( wf, IMG_FMT,
18235 "SCAN", "img/scanoff.png", 48, 48, 4, 4, 0);
18236 fprintf( wf, "</a>\n");
18241 /* no, let user agent table render decide where it goes */
18242 /* fprintf( wf, "<br>\n"); */
18243 #endif
18245 #ifdef USE_EPG_MANUAL
18246 /* weekday manual timer entry href, new page, not done yet */
18247 fprintf( wf, "<a href=\042WD.html\042>" );
18248 fprintf( wf, IMG_FMT, "WD", "img/wd.png", 48, 48, 4, 4, 0);
18249 fprintf( wf, "</a>\n");
18251 /* manual capture. does not work yet. is placeholder */
18252 if (CAP_NONE == cap_now) {
18253 fprintf( wf, "<a href=\042%02d.html?m=1\042>",
18254 s->chan );
18255 fprintf( wf, IMG_FMT,
18256 "MANUAL", "img/capall.png", 48, 48, 4, 4, 0);
18257 fprintf( wf, "</a>\n");
18258 } else {
18259 if ((CAP_USER == cap_now) && (s->chan == cap_chan)) {
18261 /* is scanning and is same channel gets signal scan disable button */
18262 /* TESTME: have to click it twice to turn it off if not same channel? */
18263 fprintf( wf, "<a href=\042%02d.html?m=0\042>",
18264 s->chan );
18265 fprintf( wf, IMG_FMT,
18266 "STOP", "img/capoff.png", 48, 48, 4, 4, 0);
18267 fprintf( wf, "</a>\n");
18271 /* manual capture of first vc, doesn't work yet */
18272 if (CAP_NONE == cap_now) {
18273 fprintf( wf, "<a href=\042%02d.html?n=1\042>",
18274 s->chan );
18275 fprintf( wf, IMG_FMT,
18276 "CAPVCX", "img/capvc0.png", 48, 48, 4, 4, 0);
18277 fprintf( wf, "</a>\n");
18278 } else {
18279 if ((CAP_USER == cap_now) && (s->chan == cap_chan)) {
18281 /* is scanning and is same channel gets signal scan disable button */
18282 /* TESTME: have to click it twice to turn it off if not same channel? */
18283 fprintf( wf, "<a href=\042%02d.html?n=0\042>",
18284 s->chan );
18285 fprintf( wf, IMG_FMT,
18286 "STOP", "img/capoff.png", 48, 48, 4, 4, 0);
18287 fprintf( wf, "</a>\n");
18290 #endif
18292 /************************************************************* HTML3 legend */
18293 fprintf( wf, "<br clear=\042%s\042>\n", "both");
18294 fprintf( wf, "<a href=\042%02d.html?l=1\042>", s->chan);
18295 fprintf( wf, "%s", (0 == web_legend) ? "Help":"HELP");
18296 fprintf( wf, "</a> " NBSP "\n");
18298 #if 0
18299 for (i = 0; i < SGML_FMT; i++ ) {
18300 j = 0;
18301 if (use_css != i) j = 1;
18302 fprintf( wf,
18303 "<a href=\042/pg/%02d.html?f=%d\042>", s->chan, i);
18304 fprintf( wf, "%s", sgmls[ (i * 2) + j ] );
18305 fprintf( wf, "</a> \n");
18307 #endif
18308 fprintf( wf, "<a href=\042/pg/%02d.html?f=%d\042>%s</a>\n",
18309 s->chan, 1, "CSS");
18311 /* current event status and description */
18312 fprintf( wf, TD_FMT, "top", "50%");
18314 fprintf( wf, "<p>\n");
18315 fprintf( wf, "<%s %s %s>\n", f, h3, c5);
18316 fprintf( wf, "Current:\n");
18317 fprintf( wf, "</%s>\n", f );
18319 /* walk epg list to find first program that is current */
18320 j = -1;
18321 e1 = NULL;
18322 for (i = 0; i < PGZ; i++){
18323 e1 = &e[i];
18324 if (0 == *e1->name) continue; /* skip blanks */
18325 if ( (utsnow >= e1->st) && (utsnow < (e1->st + e1->ls)) ) {
18326 j = i;
18327 break;
18331 /* show current event time status too
18332 denoted as mm:ssr for remaining
18334 if (-1 < j) {
18335 time_t utr;
18336 struct tm e1tm;
18338 utr = e1->st;
18339 localtime_r( &utr, &e1tm );
18341 utr += e1->ls;
18342 utr -= utsnow; /* seconds remaining */
18344 /* old style
18345 fprintf( wf, "%02d:%02d %dm %dr",
18346 (int)e1tm.tm_hour, (int)e1tm.tm_min, e1->ls/60, (int)utr/60);
18348 fprintf( wf, "%02d:%02d left\n", (int)utr / 60, (int)utr % 60);
18351 fprintf( wf, "<br>\n");
18353 /* first current event found? */
18354 if (-1 < j) {
18355 fprintf( wf, "<%s %s %s>%s</%s><br>\n",
18356 f, h3, c7, e1->name, f );
18358 /* current event description not blank? */
18359 if (0 != *e1->desc) {
18361 /* KTRK ABC fix
18362 description same as name is considered blank description
18364 if (0 != strncasecmp( e1->name, e1->desc, strlen(e1->desc)) )
18366 char ed[PDZ];
18367 astrncpy( ed, e1->desc, PDZ );
18368 ed[255] = 0;
18369 fprintf( wf, "<%s %s %s>%s </%s>\n",
18370 f, h2, c7, ed, f );
18372 } /* description blank doesn't generate html */
18373 } else {
18375 /* no current event found (guide is blank or out of date) */
18376 fprintf( wf, "<%s %s %s>%s </%s>\n", f, h3, c1, "NONE", f );
18377 fprintf( wf, "<br>\n");
18379 /* show pgm fail reason too, if any. middle box gets capture fail reason */
18380 pf = p->fail; /* shut up gcc "array subscript is char" */
18381 fprintf( wf, "<%s %s %s>Status: %s</%s>\n",
18382 f, h3, c1, epg_fails[ pf ], f );
18384 /* show atsc psip status if it has been checked from any capture */
18385 fprintf( wf, "<br>\n");
18386 if (0 == s->psip) fprintf( wf, "No ATSC PSIP Loaded");
18387 if (0 != s->psip) fprintf( wf, "PSIP has: ");
18388 if ( 1 & s->psip) fprintf( wf, "MGT ");
18389 if ( 2 & s->psip) fprintf( wf, "VCT ");
18390 if ( 4 & s->psip) fprintf( wf, "EIT ");
18391 if ( 8 & s->psip) fprintf( wf, "ETT ");
18392 if (16 & s->psip) fprintf( wf, "RRT ");
18395 fprintf( wf, "</tr>\n");
18396 fprintf( wf, "</table>\n");
18397 fprintf( wf, "</table>\n");
18399 /* fprintf( wf, "<br clear=\042%s\042>\n", "left"); */
18401 #define USE_LEGEND
18402 #ifdef USE_LEGEND
18403 /* NOTE:
18404 The legend is displayed here to fill browser cache with images.
18405 Image matches in the rest of the page should come from browser cache.
18406 The legend can be turned off to cut down the browser clutter,
18407 but it may impact performance if the images are not loaded.
18409 /* if legend enabled, show it */
18410 if (0 != web_legend) {
18412 fprintf( wf, "<hr>\n");
18414 fprintf( wf, TBL_FMT, TBL_BRD, 0, 2, "center", "66%");
18417 /* row of event control imgs and text */
18418 fprintf( wf, "<tr>\n");
18420 fprintf( wf, TD_FMT, "top", "16%");
18421 fprintf( wf, IMG_FMT "\n", "EPG", "img/epgon.png", 24, 24, 0, 0, 0);
18422 fprintf( wf, FN_FMT, f, h1, c7, "EPG");
18423 fprintf( wf, "</font>\n");
18425 fprintf( wf, TD_FMT, "top", "16%");
18426 fprintf( wf, IMG_FMT "\n", "SIG", "img/scanon.png", 24, 24, 0, 0, 0);
18427 fprintf( wf, FN_FMT, f, h1, c7, "Sig");
18428 fprintf( wf, "</font>\n");
18430 fprintf( wf, TD_FMT, "top", "16%");
18431 fprintf( wf, IMG_FMT "\n", "JUNK+", "img/spamadd.png", 24, 24, 0, 0, 0);
18432 fprintf( wf, FN_FMT, f, h1, c7, "Junk+");
18433 fprintf( wf, "</font>\n");
18435 fprintf( wf, TD_FMT, "top", "16%");
18436 fprintf( wf, IMG_FMT "\n", "JUNK-", "img/spamdel.png", 24, 24, 0, 0, 0);
18437 fprintf( wf, FN_FMT, f, h1, c7, "Junk-");
18438 fprintf( wf, "</font>\n");
18440 fprintf( wf, TD_FMT, "top", "16%");
18441 fprintf( wf, IMG_FMT "\n", "FIND+", "img/searchadd.png", 24, 24, 0, 0, 0);
18442 fprintf( wf, FN_FMT, f, h1, c7, "Find+");
18443 fprintf( wf, "</font>\n");
18445 fprintf( wf, TD_FMT, "top", "16%");
18446 fprintf( wf, IMG_FMT "\n", "FIND-", "img/searchdel.png", 24, 24, 0, 0, 0);
18447 fprintf( wf, FN_FMT, f, h1, c7, "Find-");
18448 fprintf( wf, "</font>\n");
18450 /* row of timer control imgs and text */
18451 fprintf( wf, "<tr>\n");
18453 fprintf( wf, TD_FMT, "top", "16%");
18454 fprintf( wf, IMG_FMT "\n", "ADD", "img/timeradd.png", 24, 24, 0, 0, 0);
18455 fprintf( wf, FN_FMT, f, h1, c7, "Add");
18456 fprintf( wf, "</font>\n");
18458 fprintf( wf, TD_FMT, "top", "16%");
18459 fprintf( wf, IMG_FMT "\n", "DEL", "img/timerdel.png", 24, 24, 0, 0, 0);
18460 fprintf( wf, FN_FMT, f, h1, c7, "Del");
18461 fprintf( wf, "</font>\n");
18463 fprintf( wf, TD_FMT, "top", "16%");
18464 fprintf( wf, IMG_FMT "\n", "LOG", "img/log24.png", 24, 24, 0, 0, 0);
18465 fprintf( wf, FN_FMT, f, h1, c7, "Log");
18466 fprintf( wf, "</font>\n");
18468 fprintf( wf, TD_FMT, "top", "16%");
18469 fprintf( wf, IMG_FMT "\n", "STOP", "img/timerstop.png", 24, 24, 0, 0, 0);
18470 fprintf( wf, FN_FMT, f, h1, c7, "Stop");
18471 fprintf( wf, "</font>\n");
18473 fprintf( wf, TD_FMT, "top", "16%");
18474 fprintf( wf, IMG_FMT "\n", "ZAP", "img/zap24.png", 24, 24, 0, 0, 0);
18475 fprintf( wf, FN_FMT, f, h1, c7, "Zap");
18476 fprintf( wf, "</font>\n");
18478 fprintf( wf, TD_FMT, "top", "16%");
18479 fprintf( wf, IMG_FMT "\n", "CUT", "img/edit24.png", 24, 24, 0, 0, 0);
18480 fprintf( wf, FN_FMT, f, h1, c7, "Edit");
18481 fprintf( wf, "</font>\n");
18483 /* row of color legends for event namess. At bottom so vspace looks OK. */
18484 fprintf( wf, "<tr>\n");
18486 fprintf( wf, TD_FMT, "top", "16%");
18487 fprintf( wf, FN_FMT, f, h1, c4, "Junk </font>");
18489 fprintf( wf, TD_FMT, "top", "16%");
18490 fprintf( wf, FN_FMT, f, h1, c6, "Old </font>");
18492 fprintf( wf, TD_FMT, "top", "16%");
18493 fprintf( wf, FN_FMT, f, h1, c7, "Now </font>");
18495 fprintf( wf, TD_FMT, "top", "16%");
18496 fprintf( wf, FN_FMT, f, h1, c2, "Later </font>");
18498 fprintf( wf, TD_FMT, "top", "16%");
18499 fprintf( wf, FN_FMT, f, h1, c3, "Timer </font>");
18501 fprintf( wf, TD_FMT, "top", "16%");
18502 fprintf( wf, FN_FMT, f, h1, c3, "<i>Capture</i> </font>");
18504 fprintf( wf, "</table>\n");
18506 /* huh? why is this here? */
18507 // fprintf( wf, FN_FMT, f, h3, c7, "<center> </center>");
18510 /* end of legend table */
18511 #endif
18513 /* cgi */
18515 /* if no epg available, create this as stub page for user to try later */
18516 if (0 == p->idx) {
18517 if (NULL != gf) { fflush( gf ); fclose( gf ); }
18518 if (NULL != df) { fflush( df ); fclose( df ); }
18519 if (NULL != wf) {
18520 fprintf( wf, "<hr>");
18521 fprintf( wf, "<center>");
18522 fprintf( wf, "<b>");
18523 fprintf( wf, "No Events");
18524 if (0 != p->find) fprintf( wf, " Found");
18525 fprintf( wf, "</b>");
18526 fprintf( wf, "</center>");
18527 fprintf( wf, "<hr>\n");
18528 fprintf( wf, "\n</body>\n");
18529 fprintf( wf, "</html>\n");
18530 fflush( wf );
18531 fclose( wf );
18533 return;
18536 /* first event, day of year and day of week */
18537 st = e[ pgx[0] ].st;
18538 localtime_r( &st, &stm );
18539 sdn = stm.tm_yday; /* start day of year */
18540 sdw = stm.tm_wday; /* start day of week */
18542 /* lpn = e[ pgx[0] ].pn; */ /* don't know if there is one yet */
18543 lpn = 0;
18544 ldn = -1;
18545 nde = 0;
18546 pvc = 0;
18548 /* find number of programs and days in guide */
18549 for (j = 0; j < p->idx; j++) {
18551 /* unless event list is empty will always add +1 */
18552 if (lpn != e[ pgx[ j ]].pn) {
18553 pv[ pvc ] = e[ pgx[ j ]].pn;
18554 advrlog( LOG_INFO, "%s.%d pn %d",
18555 s->sid, pvc+1, pv[pvc] );
18556 pvc++;
18558 lpn = e[ pgx[ j ]].pn;
18560 /* after first program change, keep counting programs, but not days */
18561 if (pvc > 1) continue;
18563 /* count days until first program # change */
18564 st = e[ pgx[j] ].st;
18565 localtime_r( &st, &stm );
18567 /* unless event list is empty will always add +1 */
18568 if (ldn != stm.tm_yday) {
18569 /* advrlog( LOG_INFO, "day %d", stm.tm_yday); */
18570 nd[ nde ] = stm.tm_yday;
18571 nde++;
18573 ldn = stm.tm_yday;
18576 advrlog( LOG_INFO, "EPG%02d has ncs %d, days %d, events %d",
18577 p->chan, pvc, nde, p->idx );
18579 if ( (pvc >= VCZ) || (nde > 17) ) {
18581 if (pvc >= VCZ)
18582 dvrlog( LOG_INFO, "%s ch %d invalid pvc %d idx %d",
18583 WHO, p->chan, pvc, p->idx);
18584 if (nde > 17)
18585 dvrlog( LOG_INFO, "%s ch %d invalid nde %d idx %d",
18586 WHO, p->chan, nde, p->idx);
18588 if (NULL != wf) fclose( wf );
18589 if (NULL != df) fclose( df );
18590 if (NULL != gf) fclose( gf );
18591 return;
18593 advrlog( LOG_INFO, "%s ch %d pgms %d days %d",
18594 WHO, p->chan, pvc, nde);
18596 ldn = -1;
18597 lpn = -1;
18599 /* use long day names if guide has less than one week of data */
18600 if (nde < 8) {
18601 wd[0] = wdl[0];
18602 wd[1] = wdl[1];
18603 wd[2] = wdl[2];
18604 wd[3] = wdl[3];
18605 wd[4] = wdl[4];
18606 wd[5] = wdl[5];
18607 wd[6] = wdl[6];
18608 } else {
18610 /* want 16 days and 2 pgms to fit in 960 pixel wide browser window */
18611 wd[0] = wds[0];
18612 wd[1] = wds[1];
18613 wd[2] = wds[2];
18614 wd[3] = wds[3];
18615 wd[4] = wds[4];
18616 wd[5] = wds[5];
18617 wd[6] = wds[6];
18620 // validator doesn't like this
18621 // fprintf( wf, "<%s %s>", f, c7);
18623 /* Sorted in vc order by placement algorithm in parse eit.
18624 Should also be sorted by eit number within vc with sort eit, KRIV/FOX fix
18627 /* es is event status bits for this event (used by html, rest don't care):
18628 1 is spam
18629 2 is aged
18630 4 is now
18631 8 is movie
18632 16 has timer
18633 32 has image
18634 64 has log
18635 128 has ts
18636 256 has search timer entry
18637 512 has been modified recently
18639 The design objective is for a functional but not superfluous web interface:
18640 href imgs are drawn after the eventname/time, reduced to fewest sensible.
18641 Left-most icon least destructive to capture, right-most is scrap all bits.
18643 Spam does not change capture status so it goes first.
18645 Searchadd/searchdel does change timer and capture status but no delete.
18647 Log does not change capture status.
18649 Timeradd/timerdel changes capture status but does not delete capture.
18651 Zap deletes whatever it can find that matches that event.
18652 Zap is at far right to reduce mouse-fumble. [larger hspace needed?]
18653 Zap is also at far right in capture directory index.html.
18654 Zap is disabled if no matching timer found and file < 30s old
18657 for (k = 0; k < p->idx; k++) { /* fewer loops than E*P*V */
18658 char a;
18659 char *et = ""; /* event status text */
18660 char *jf = ""; /* spam add or del png */
18661 char *efz = ""; /* eventname font size */
18662 char *enc = c7;
18663 char wh[256]; /* web haystack */
18664 char sn[256]; /* stat name */
18665 char tr[256]; /* timer request string */
18666 int es; /* event status bits */
18667 int dd; /* event day diff from now */
18668 int iz = 24; /* image size, 16 or 24 */
18670 #ifdef USE_EPG_SLEEP
18671 /* TESTME: try to reduce cpu load on guide write, tweak this value lower? */
18672 struct timespec ns = { 0,1 }; /* short delay */
18673 nanosleep( &ns, NULL);
18674 #endif
18675 ename = ""; /* full eventname ptr */
18676 edesc = ""; /* full event desc ptr */
18678 ec = "";
18679 es = 0;
18680 i = pgx[ k ];
18682 e1 = &e[ i ];
18684 /* this should never display if build pg epg worked */
18685 if ( letmid == e1->etmid ) {
18686 dvrlog( LOG_INFO, "%s %s is duplicate", WHO, n);
18687 continue; /* skip duplicate */
18690 letmid = e1->etmid;
18692 /* TODO: these could be moved to build_pg_epg */
18693 /* skip blank or ntsc or invalid pgm# entries */
18694 astrncpy( n, e1->name, PNZ );
18696 /* this should never display if build pg epg worked */
18697 if ( (e1->pn < 1)
18698 || (e1->pn > 65534)
18699 || (0 == *n) ) {
18700 advrlog( LOG_INFO, "%s %s is invalid", WHO, n);
18701 continue;
18704 /* spam coloration and font size redux */
18705 efz = h4; /* event font size */
18708 if (E_SPAM & es) iz = 16; /* image size */
18710 /* age coloration and font size */
18711 if ( e1->st + e1->ls < utsnow ) {
18712 es |= E_AGED; /* aged is nz */
18713 efz = h3; /* spam font is h1 */
18716 /* this should never display if build pg epg worked */
18717 if (0 != p->hide) {
18718 if (E_SPAM & es) {
18719 advrlog( LOG_INFO, "%s %s is spam", WHO, n);
18720 continue; /* skip spam */
18724 /* this should never display if build pg epg worked */
18725 if (0 != p->age) {
18726 if (E_AGED & es) {
18727 advrlog( LOG_INFO, "%s %s is aged", WHO, n);
18728 continue; /* skip aged */
18732 if ( test_spam_event(e1) >= 0 ) {
18733 es |= E_SPAM; /* spam is nz */
18734 efz = h1; /* spam font is smaller */
18738 /* current overrides */
18739 if ( ( utsnow < e1->st + e1->ls )
18740 && ( utsnow >= e1->st) )
18741 es |= E_NOW; /* current is nz */
18743 if (0 != e1->movie) es |= E_MOVIE;
18745 /* check for event on timer list */
18746 tpg = find_timer_epg( e1 );
18747 if (-1 < tpg) es |= E_TIME; /* has timer is nz */
18749 ec = c2; /* future green */
18750 if (es > 0) {
18751 if (E_SPAM & es) ec = c4; /* spam grey */
18752 if (E_AGED & es) ec = c6; /* aged magenta */
18753 if (E_NOW & es) ec = c7; /* current white */
18754 if (E_TIME & es) ec = c3; /* timer yellow */
18757 pn = e1->pn;
18758 st = e1->st;
18759 localtime_r( &st, &stm );
18760 asctime_r( &stm, dt);
18763 n[42] = 0;
18765 /* strftime( w, sizeof(w)-1, "%A", &stm); */
18767 cdn = stm.tm_yday;
18768 cdw = stm.tm_wday;
18770 /* IDEA: image map for weekdays? problem is: variable browser resolution */
18771 /* if program number or day number changes, put up some navigation help */
18772 /* Prime time division check not done here but needs pt href here */
18773 if ( (pn != lpn) || (cdn != ldn) ) { /* || (td != ltd)) */
18775 /* show TOP, pgms (if > 1), and days (if > 1) */
18777 /* validator doesn't like two id same. 1st center cancelled. is placeholder */
18778 if (pn != lpn)
18779 fprintf( wf, "<center id=\042pgm%d\042></center>\n", pn );
18780 fprintf( wf, "<center>\n");
18782 fprintf( wf, "<hr id=\042pgm%dday%d\042 />\n", pn, cdn );
18783 /* fat space after top */
18784 fprintf( wf, "<a href=\042#top\042>%s %s</a>",
18785 "TOP", NBSP);
18786 /* fat space after primetime href */
18787 fprintf( wf, "<a href=\042#pgm%dpt%d\042>PT %s</a>",
18788 pn, cdn, NBSP);
18790 /* show virtual channel sid + minor program nav hrefs if more than one */
18791 if (pvc > 1) {
18792 fprintf( wf, "| %s ", NBSP );
18793 for (j = 0; j < pvc; j++) {
18794 a = ~0;
18796 /* current program does not get href */
18797 if (pn == pv[ j ]) a = 0;
18798 if (0 != a)
18799 fprintf( wf, "<a href=\042#pgm%d\042>", pv[j]);
18801 fprintf( wf, "%s.%d ", s->sid, pv[j] );
18803 if (0 != a)
18804 fprintf( wf, "</a>\n");
18806 /* fat space at end of list */
18807 fprintf( wf, " %s ", NBSP);
18809 /* show guide day nav hrefs if more than one day in guide */
18810 if (nde > 1) fprintf( wf, "| %s ", NBSP);
18811 for (j = 0; j < nde; j++) {
18812 a = ~0;
18814 /* current day does not get href */
18815 if (cdn == nd[j]) a = 0;
18817 if (0 != a)
18818 fprintf( wf, "<a href=\042#pgm%dday%d\042>",
18819 pn, nd[j] );
18821 /* weekday index does not reset at end of year */
18822 fprintf( wf, "%s ", wd[ (sdw + j) % 7 ] );
18824 if (0 != a) fprintf( wf, "</a>\n" );
18827 // DELETEME: doesn't need an end glyph?
18828 /* if (nde > 1) fprintf( wf, "| "); */
18830 fprintf( wf, "</center>\n");
18831 fprintf( wf, "<hr>\n");
18834 /* draw primetime line at first timeslot starting at 7pm or later */
18835 if (stm.tm_hour >= 19) {
18836 if (ptd != cdn) {
18837 int vcn;
18838 vcn = find_vc_pgm( pn, WHO );
18839 fprintf( wf, "<hr id=\042pgm%dpt%d\042 />\n", pn, cdn);
18841 fprintf( wf, "<center>Prime Time %s %s.%d</center><hr>\n",
18842 wdl[cdw], s->sid, vcn + 1 );
18844 fprintf( wf, "<center>Prime Time %s %s.%d</center>\n",
18845 wdl[cdw], s->sid, pn );
18846 fprintf( wf, "<hr />\n");
18847 ptd = cdn;
18851 lpn = pn;
18852 ldn = cdn;
18855 /* TAB separated for easy loading into SQL tables */
18856 /* always 5 separators, char TAB does not occur in ATSC */
18857 /* start time is unsigned int for now */
18858 if (NULL != df)
18859 fprintf( df, "%d\t%d\t%u\t%d\t\042%s\042\t",
18860 p->chan, pn, e1->st, e1->ls, e1->name );
18862 /* by the time you get here, e1-> should have one of the allowed events */
18863 /* event description is blanked if same as eventname or n/a, rating too */
18864 ename = e1->name;
18865 edesc = e1->desc;
18867 if (0 != *edesc)
18868 if ( 0 == strncasecmp( ename, edesc, strlen(edesc)) )
18869 edesc = "";
18871 /* if desc not blank, add it to text and database files */
18872 if (0 != *edesc) {
18874 /* Do not forget space at end for last word to wrap
18875 first line moved out to be staggered from title
18876 2nd and more lines of description get one more char indent
18879 /* NOTE: There is a variable space issue here. pad below "" at will, but
18880 realize anything decreasing description width will increase line count
18881 in the word wrap with indent scenario versus usual word wrap giving
18882 a decrease in the number of lines required.
18885 /* "" should be name bytes long or fixed at 20 spaces with trunc eventname */
18887 snprintf( t, sizeof( t )-1, " "
18888 "| %s ", e1->desc );
18890 word_wrap_indent( gf, t ); /* text file */
18891 snprintf( t, sizeof(t)-1, "\042%s\042", e1->desc );
18892 if (NULL != df)
18893 fprintf( df, "%s", t); /* sql table */
18895 if (NULL != df)
18896 fprintf( df, "\n");
18898 /* mangle name to two words */
18899 event_truncate( en, e1->name, 2, sizeof(en)-1 );
18900 /* truncate en same as add from show program guide does */
18901 en[16] = 0;
18903 if (-1 < find_search_timer(en)) es |= E_FIND;
18905 /* FIXME: glob existing png to redux event pixmap check to memory */
18907 /* generic movie image file if it exists */
18908 snprintf( wh, sizeof(wh), "%s%spg/img/%s.png",
18909 out_path, ram_path, en);
18910 if (E_MOVIE & es)
18911 snprintf( wh, sizeof(wh), "%s%spg/img/movie.png",
18912 out_path, ram_path);
18914 ok = stat64( wh, &fs );
18915 advrlog( LOG_INFO, "dump epg stat %d %s", ok, wh);
18917 /* build image href if pixmap available */
18918 if (0 == ok) {
18919 es |= E_IMG; /* event image exists */
18920 /* advrlog( LOG_INFO, "dump epg found %s", wh); */
18922 /* make file name for event pixmap */
18923 snprintf( wh, sizeof(wh), "img/%s.png", en);
18924 if (E_MOVIE & es)
18925 snprintf( wh, sizeof(wh), "img/movie.png");
18926 advrlog( LOG_INFO, "dump epg using %s", wh);
18930 /* if .html file available, set flag for log href */
18931 snprintf( ln, sizeof(ln)-1, "%s%s-%02d%02d-%02d%02d.%d.html",
18932 out_path, en, stm.tm_mon+1, stm.tm_mday,
18933 stm.tm_hour, stm.tm_min, e1->pn );
18934 ok = stat64( ln, &fs );
18935 if (0 == ok) es |= E_LOG;
18937 /* if .ts file available, set flag for ts href img video.png
18938 and also href for unlink
18940 ok = test_ts_file( sn, en, e1, &fs );
18941 if (0 == ok) {
18942 es |= E_TS;
18943 ec = c3;
18944 /* 3s past last mod? */
18945 if (fs.st_mtime < (utsnow - 3)) es |= E_MTX;
18948 /* see http help at top for configuring mozilla to handle mpeg://url */
18949 /* don't relative href this because player might need the specific port#? */
18950 if (E_TS & es) fprintf( wf, "<a href=\042mpeg://%s:%d/%s\042>",
18951 "dtv", arg_wport, &sn[strlen(out_path)] );
18952 /* if image exists, make it same href as eventname has, the .ts file */
18953 if (E_IMG & es)
18954 fprintf( wf, "<img "
18955 "src=\042%s\042 "
18956 "alt=\042%s\042 "
18957 "align=\042%s\042 "
18958 "width=%d "
18959 "height=%d "
18960 "hspace=%d "
18961 "vspace=%d>\n",
18962 wh, en, "left", 160, 90, 8, 8);
18963 if (E_TS & es) fprintf( wf, "</a>\n");
18964 enc = ec;
18966 /* removed line break after event name
18967 fprintf( wf, "<br>\n");
18969 ec = c7;
18970 if (E_SPAM & es) ec = c4; /* spam gets time as low light */
18971 if (E_TS & es) ec = c3; /* yellow for captures */
18973 /* event time */
18974 /* strategy:
18975 today shows time only.
18976 within a week shows day and time only
18977 outside of a week shows day date time
18978 outside of 16 days shows day date year time
18980 dd = abs(e1->st - utsnow); /* day diff */
18981 dd /= 86400; /* seconds to days */
18983 /* time only, same day meridian check */
18984 if (0 == dd) {
18986 /* same day meridian? */
18987 if (stm.tm_yday == tloc.tm_yday) { /* tloc is utsnow */
18988 fprintf( wf, "<%s %s %s> %02d:%02d %dm </font>\n",
18989 f, h3, ec, stm.tm_hour, stm.tm_min,
18990 e1->ls/60 );
18991 } else {
18992 dd = 1; /* not same day */
18996 /* TODO: end of month/year shows new month/year */
18998 /* day and time only, same as above for 1-6 days, 0 is today */
18999 if ((dd > 0) && (dd < 6))
19000 fprintf( wf, "<%s %s %s> %s %02d:%02d %dm </font>\n",
19001 f, h3, ec,
19002 wds[ stm.tm_wday ], stm.tm_hour, stm.tm_min,
19003 e1->ls/60 );
19005 /* day date time for 7-16 days */
19006 if ((dd > 5) && (dd < 17)) {
19007 dt[16] = 0; /* don't need seconds, yet... */
19008 fprintf( wf, "<%s %s %s> %s %dm </font>\n",
19009 f, h3, ec, dt, e1->ls/60 );
19012 /* day date time year for more than 16 day diff, shows stale epg full date */
19013 if (dd >16)
19014 fprintf( wf, "<%s %s %s> %s %dm </font>\n",
19015 f, h3, ec, dt, e1->ls/60 );
19017 /* regent dt again but truncate seconds + year for text file output */
19018 asctime_r( &stm, dt);
19019 dt[16] = 0; /* truncate time before seconds */
19020 if (NULL != gf)
19021 fprintf( gf, "\n%d %s %4dm | %s", pn, dt, e1->ls/60, n );
19024 /* give small description of status */
19025 et = "";
19027 if (E_SPAM & es)
19029 et = "JUNK";
19030 ec = c4;
19033 if (E_AGED & es)
19035 et = "AGED";
19036 ec = c6;
19039 if ( (E_AGED | E_SPAM) == ((E_AGED | E_SPAM) & es) )
19041 et = "AGED JUNK";
19042 ec = c4;
19045 if (E_NOW & es)
19047 et = "NOW";
19048 ec = c7;
19051 if ((E_SPAM | E_NOW) == ((E_SPAM | E_AGED | E_NOW) & es))
19053 et = "NOW JUNK";
19054 ec = c7;
19057 #if 0
19058 /* icon and aged is enough? */
19059 if (E_MOVIE & es) {
19060 et = "MOVIE";
19061 ec = c2;
19064 if ((E_MOVIE | E_AGED) == ((E_SPAM | E_AGED | E_NOW | E_MOVIE) & es))
19066 et = "AGED MOVIE";
19067 ec = c6;
19069 #endif
19070 if (E_FIND & es)
19072 ec = c3;
19075 fprintf( wf, "<%s %s %s>%s </font>\n",
19076 f, h1, ec, et);
19078 /* no spam href for search */
19079 /* hmm, below it says non-spam does the search toggle */
19080 /* haven't run out of bits in es yet, could add search bit */
19082 /* spam list add/remove toggle */
19083 jf = "img/spamadd.png";
19084 if (E_SPAM & es) {
19085 jf = "img/spamdel.png";
19088 fprintf( wf, "<a href=\042%02d.html?j=%s:%u\042>",
19089 s->chan, en, e1->st);
19091 fprintf( wf, IMG_FMT,
19092 (E_SPAM & es) ? "JUNK-":"JUNK+", jf, iz, iz, 4, 4, 0 );
19094 fprintf( wf, "</a>\n");
19096 /* search href img */
19097 if (0 == (E_SPAM & es)) { /* non spam gets search toggle */
19098 char *spn = "img/searchadd.png";
19100 if (E_FIND & es) spn = "img/searchdel.png";
19101 fprintf( wf, "<a href=\042%02d.html?s=%02d:%s\042>",
19102 s->chan, s->chan, en);
19103 fprintf( wf, IMG_FMT, "SEARCH", spn, 24, 24, 4, 4, 0 );
19104 fprintf( wf, "</a>\n");
19106 /* timer request, format matches config file */
19107 snprintf( tr, sizeof(tr)-1, "V%02d:%u:%03d:%s@.%d:%d",
19108 s->chan, e1->st, e1->ls/60, en, pn, e1->etmid );
19110 /* capture log href img */
19111 if (E_LOG & es) {
19112 /* see http at bottom for configuring mozilla to handle mpeg://url */
19113 fprintf( wf,"<a href=\042/%s\042>", &ln[strlen(out_path)] );
19115 fprintf( wf, IMG_FMT, "LOG", "img/log24.png", iz, iz, 4, 4, 0);
19116 fprintf( wf, "</a>\n");
19119 /* timer remove href img, if event on timer list */
19120 if (E_TIME & es) {
19122 /* not [spam, ] aged, current, nor search: timer gets timer delete img */
19123 /* if (0 == ((E_SPAM | E_AGED | E_NOW | E_FIND) & es)) { */
19124 if (0 == ((E_AGED | E_NOW | E_FIND) & es)) {
19125 /* cgi */
19126 fprintf( wf, "<a href=\042%02d.html?d=%s\042>",
19127 s->chan, tr );
19129 fprintf( wf, IMG_FMT,
19130 "DEL", "img/timerdel.png", iz, iz, 4, 4, 0 );
19131 fprintf( wf, "</a>\n");
19134 /* current timer gets timer stop */
19135 if (E_NOW & es) {
19136 fprintf( wf,
19137 "<a href=\042%02d.html?d=%s\042>",
19138 s->chan, tr );
19140 fprintf( wf, IMG_FMT,
19141 "DEL", "img/timerstop.png", iz, iz, 4, 4, 0);
19142 fprintf( wf, "</a>\n");
19145 /* event is user settable for future, gets face-on clock img */
19146 if (0 == (E_TIME & es)) {
19147 fprintf( wf, "<a href=\042%02d.html?d=%s\042>",
19148 s->chan, tr );
19150 fprintf( wf, IMG_FMT,
19151 "DEL", "img/timerclk.png", iz, iz, 4, 4, 0);
19152 fprintf( wf, "</a>\n");
19154 /* user settable for not future is clock turned off from facing */
19156 } else {
19158 /* timer add href img */
19159 if (0 == (E_AGED & es)) { /* aged does not need timer add href */
19161 /* spamlist cap image smaller for adjacent spam to use fewer vertical pixels */
19162 /* trash can is smaller too. but log and zap could be smaller too */
19164 /* cgi */
19165 /* FIXME: same ip/port concerns as above */
19166 fprintf( wf, "<a href=\042%02d.html?a=%s\042>",
19167 s->chan, tr );
19169 fprintf( wf, IMG_FMT,
19170 "ADD", "img/timeradd.png", iz, iz, 4, 4, 0 );
19171 fprintf( wf, "</a>\n");
19175 /* TODO: if aged, it gets the file name, if current it gets the timer name */
19176 /* zap only for current event, may try to reuse as file delete too */
19177 if ((E_TS | E_NOW | E_TIME) == ((E_TS | E_NOW | E_TIME) & es)) {
19178 fprintf( wf, "<a href=\042%02d.html?z=%s\042>",
19179 s->chan, tr );
19180 fprintf( wf, IMG_FMT, "ZAP", "img/zap24.png", iz, iz, 4, 4, 0);
19181 fprintf( wf, "</a>\n");
19184 #if 0
19185 /* deciding not to use this for now because it confuses ?u= function */
19186 /* u= unlink filename, same img as timer zap, need diff color? */
19187 if (E_TS & es) {
19188 /* mod time expired? */
19189 if (E_MTX & es) {
19190 /* sometimes you want to delete a near current cap */
19191 if ((E_AGED & es) || (CAP_NONE == cap_now))
19193 char un[256];
19194 filebase( un, sn, F_FILE );
19195 fprintf( wf, "<a href=\042%02d.html?u=%02d:%s\042>",
19196 s->chan, s->chan, un );
19197 fprintf( wf, IMG_FMT,
19198 "UNLINK", "img/unlink.png", iz, iz, 4, 4, 0 );
19199 fprintf( wf, "</a>\n");
19203 #endif
19205 #if 0
19206 /* secondary media player icon, not done but is very easy to do */
19207 if (E_TS & es) {
19208 /* see http at bottom for configuring mozilla to handle mpeg://url */
19210 /* like the xine href above, this one should keep ip and port#, not relative */
19211 fprintf( wf, "<a href=\042mplayer://%s:%d/%s\042>",
19212 "dtv", arg_wport, &sn[strlen(out_path)] );
19213 /* FIXME? mpeg://dtv has dtv as default host */
19214 fprintf( wf, IMG_FMT,
19215 "LOG", "img/video.png", iz, iz, 4, 4, 0 );
19216 fprintf( wf, "</a>\n");
19218 #endif
19221 /* event name */
19222 /* (different font and color possible) */
19223 if (E_TS & es) fprintf( wf, "<a href=\042mpeg://%s:%d/%s\042>",
19224 "dtv", arg_wport, &sn[strlen(out_path)] );
19225 fprintf( wf, "<%s %s %s> ", f, efz, enc);
19226 if (E_TS & es) fprintf( wf, "<i>");
19227 fprintf( wf, "%s ", n);
19228 if (E_TS & es) fprintf( wf, "</i>");
19229 fprintf( wf, "</%s>", f );
19230 if (E_TS & es) fprintf( wf, "</a>");
19231 fprintf( wf, "\n");
19235 /* description color is white for readability even at small font sizes */
19236 ec = c7;
19238 fprintf( wf, "<br>\n");
19240 /* spam always gets grey for all colors */
19241 if (E_SPAM & es) ec = c4;
19243 /* image needs hspace="8" attribute or text is too near image */
19245 if ( 0 != *edesc ) {
19246 /* event rating */
19247 if (0 != *e1->rating )
19248 fprintf( wf, "<%s %s %s>" "[%s] </font>\n",
19249 f, h1, ec, e1->rating);
19250 fprintf( wf,"<%s %s %s>%s</%s>\n", f, h2, ec, edesc, f);
19254 /* <br> after possible no description or else image sits on next event */
19255 fprintf( wf, "<br clear=\042%s\042>\n", "left");
19256 if ( (strlen(edesc)>1) || (E_IMG & es) )
19257 fprintf( wf, "<br>\n"); /* runs together without this */
19259 /* end of event loop */
19262 advrlog( LOG_INFO, "%s %s writing footer", WHO, wn);
19264 for (i = 0; i < 16; i++) fprintf( wf, "<br>\n");
19265 fprintf( wf, "\n");
19266 fprintf( wf, "<hr>\n" );
19268 *h = 0;
19269 build_foot_html( h, sizeof(h), 1); /* validated */
19270 fprintf( wf, "%s", h );
19272 if (NULL != wf) {
19273 fflush( wf );
19274 fclose( wf );
19277 #ifdef USE_EXTRA_OUTPUT
19278 if (NULL != df) {
19279 fflush( df );
19280 fclose( df );
19283 /* text file */
19284 if (NULL != gf) fprintf( gf, "\n");
19285 if (NULL != gf) {
19286 fflush( gf );
19287 fclose( gf );
19289 #endif
19291 advrlog( LOG_INFO, "%s done", WHO);
19294 /* static */
19295 void
19296 dump_epg_html ( struct event_s *e, struct pgm_s *p, short *pgx, char *caller )
19299 build_pg_epg( e, p, pgx, WHO);
19301 /* can't do it? */
19302 if (p->idx >= PGZ) {
19303 dvrlog( LOG_INFO, "%s chan %02d has bad event index %d",
19304 WHO, p->chan, p->idx);
19305 return;
19308 if (0 == use_css) {
19309 dump_epg_html3( e, p, pgx, caller );
19310 } else {
19311 dump_epg_html4( e, p, pgx, caller );
19315 #define SIDZ "5%"
19316 /* dump 3 hour grid starting at epg_gs */
19317 void
19318 dump_epg_grid_html4( void )
19320 FILE *f;
19321 struct event_s *e1;
19322 struct sig_s *s;
19323 unsigned int r, r1, r2;
19324 int c, i, j, k, ch, m, w, z, un, un1, es;
19325 char n[256], hr[256], *ir;
19326 char *d;
19327 time_t t;
19328 struct tm tt;
19330 z = 32768;
19331 d = icalloc( z, 1, "epg grid4" );
19333 epg4 = imalloc( sizeof( epg ), "http epg4");
19335 /* 3 hour meridian */
19336 un = utsnow / SEC3HR;
19337 un *= SEC3HR;
19339 /* 1 hour meridian */
19340 un1 = utsnow / SECHR;
19341 un1 *= SECHR;
19343 r1 = epg_gs;
19344 r1 /= SECHR;
19345 r1 *= SECHR;
19347 /* epg_gs not set or trying to go back before the current EPG meridian */
19348 if (r1 < un) r1 = un;
19349 epg_gs = r1;
19351 r2 = (r1 + SEC3HR) - 1;
19352 m = 180;
19353 r = 1800 - (utsnow % 1800);
19355 *d = 0;
19356 build_head_html( d, z, 1, "EPG Grid", r, WHO );
19358 asnprintf(d, z, "<table border=0 width=\042%s\042>\n", "100%" );
19359 asnprintf(d, z, "<tr>\n");
19360 asnprintf(d, z, "<th width=\042%s\042>", SIDZ);
19362 if (r1 > un) {
19364 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042>",
19365 arg_devnum, r1 - 10800 );
19366 asnprintf(d, z, "%s", "&lt&lt" );
19367 asnprintf(d, z, "</a>\n");
19369 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042>",
19370 arg_devnum, r1 - 3600 );
19371 asnprintf(d, z, "%s", "&lt" );
19372 asnprintf(d, z, "</a>\n");
19376 text_time( n, r1, 16 );
19377 t = (time_t) un1;
19378 localtime_r( &t, &tt );
19381 asnprintf(d, z, "<th width=\042%s\042>", "90%");
19383 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042> %s </a>\n",
19384 arg_devnum, un1, "Now" );
19386 /* switch to HTML3 */
19387 asnprintf(d, z, "<a href=\042/pg/grid%d.html?f=0\042> %s </a>\n",
19388 arg_devnum, "HTML");
19390 asnprintf(d, z, "%s \n", n);
19392 for (i = 0; i < 7; i++) {
19393 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042> %s </a>\n",
19394 arg_devnum,
19395 un1 + (i * SECDAY),
19396 days[ (i + tt.tm_wday) % 7]);
19399 /* next hour and next 3 hours */
19400 asnprintf(d, z, "<th width=\042%s\042>", SIDZ);
19401 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042>",
19402 arg_devnum, r1 + 3600 );
19403 asnprintf(d, z, "%s", "&gt" );
19404 asnprintf(d, z, "</a>\n");
19406 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042>",
19407 arg_devnum, r1 + 10800 );
19408 asnprintf(d, z, "%s", "&gt&gt" );
19409 asnprintf(d, z, "</a>\n");
19411 asnprintf(d, z, "</tr>\n");
19412 asnprintf(d, z, "</table>\n");
19415 asnprintf(d, z, "<table class=\042%s\042"
19416 // " border=1 frame=box align=\042center\042"
19417 // " cellspacing=0 cellpadding=3"
19418 " width=\042%s\042>\n",
19419 "ta_egs", "100%" );
19421 asnprintf(d, z, "<tr>\n");
19423 /* SID */
19424 asnprintf(d, z, "<th class=\042%s\042 width=\042%s\042>SID\n",
19425 "th_egs", SIDZ);
19427 /* draw hh:mm legend at top */
19428 for (i = 0; i < SEC3HR; i+= 1800) {
19429 asnprintf(d, z, "<th class=\042%s\042 width=\042%d%%\042>",
19430 "th_egs", 16 );
19432 t = (time_t) r1 + i;
19433 localtime_r(&t, &tloc4);
19434 asnprintf(d, z, "%02d:%02d\n", tloc4.tm_hour, tloc4.tm_min);
19435 // asnprintf(d, z, "<br>%d\n", r1 + i);
19437 asnprintf(d, z, "</tr>\n");
19438 asnprintf(d, z, "</table>\n\n");
19440 /* step through all channels and load the epg for each */
19441 for (i = 0; i < scan_idx; i++) {
19442 ch = scan_list[i];
19443 s = &ptc[ch].sig;
19445 memset(&pgm4, 0, sizeof(pgm4));
19446 pgm4.chan = ch;
19448 load_epg( ch, epg4, &pgm4, pg4, WHO );
19449 build_pg_epg( epg4, &pgm4, pg4, WHO );
19452 /* cable might have a lot of blank channels. bad reception will be blank too,
19453 but it won't be blank if the guide is stale, even if no events in range
19456 /* broadcast shows all, cable shows only what is loaded from remote */
19457 if (0 != arg_cable)
19458 if (0 == s->pgto)
19459 continue;
19461 if (0 == pgm4.idx) continue;
19463 asnprintf(d, z, "<table class=\042%s\042"
19464 // " border=1 frame=box"
19465 // " cellspacing=0 cellpadding=3"
19466 " width=\042%s\042>\n",
19467 "tb_egs", "100%");
19469 asnprintf(d, z, "<tr valign=\042%s\042>\n", "baseline");
19471 /* s->sid 4% */
19472 asnprintf(d, z, "<th class=\042%s\042 width=\042%s\042>",
19473 "th_egs", SIDZ );
19475 asnprintf(d, z, "<a href=\042/pg/%02d.html\042>%s\n",
19476 ch, s->sid);
19478 asnprintf(d, z, "</a>\n");
19480 k = 96;
19482 for (j = 0; j < pgm4.idx; j++) {
19483 e1 = &epg4[ pg4[ j ] ];
19484 /* skip anything outside the range */
19485 if (s->pn != e1->pn) continue;
19487 // if ((e1->st + e1->ls) < un) continue;
19488 if ((e1->st + e1->ls) < r1) continue;
19490 if (e1->st > r2) continue;
19492 r = e1->ls;
19493 if (e1->st < r1) r = (e1->st + e1->ls) - r1;
19494 r /= 60;
19495 // if (r > m) continue;
19497 // w = (100 * r) / m;
19498 w = (96 * r) / m;
19499 if (w > k) w = k;
19501 /* skip expired events */
19502 if (0 == w) continue;
19503 if (w < 6) w = 6; /* 10m is smallest division */
19505 k -= w;
19507 es = test_event_status( e1 );
19508 c = 7;
19509 // cyan used for weekday timers, not automatic ones, they're green!
19510 if (0 != (es & E_SPAM)) c = 0; /* black */
19511 if (0 != (es & E_MOVIE)) c = 3; /* blue */
19512 if (0 != (es & E_NOW)) c = 8; /* white */
19513 if (0 != (es & E_TIME)) c = 2; /* green */
19514 if (0 != (es & E_FIND)) c = 2; /* green */
19515 /* aged will set on all old events except captures and cuts */
19516 if (0 != (es & E_AGED)) c = 5; /* magenta */
19517 if (0 != (es & E_TS)) c = 6; /* yellow */
19518 if (0 != (es & E_TSC)) c = 1; /* red */
19520 ir = "";
19521 *hr = 0;
19523 if ( 0 == (es & ( E_SPAM | E_AGED)) ) {
19524 if (0 == (es & E_TIME)) {
19525 ir = "/pg/img/timeradd24.png";
19526 /* timer request, format matches config file */
19527 snprintf( hr, sizeof(hr)-1,
19528 "?a=V%02d:%u:%03d:%s@.%d:%d",
19529 s->chan, e1->st, e1->ls/60,
19530 e1->tname, e1->pn, e1->etmid );
19531 } else {
19532 ir = "/pg/img/timerdel24.png";
19533 snprintf( hr, sizeof(hr)-1,
19534 "?d=V%02d:%u:%03d:%s@.%d:%d",
19535 s->chan, e1->st, e1->ls/60,
19536 e1->tname, e1->pn, e1->etmid );
19540 asnprintf(d, z, "<td class=\042%s %s0\042"
19541 " width=\042%d%%\042>",
19542 "td_egs", colors[c], w );
19544 if (0 != *hr) {
19545 asnprintf(d, z, "<a href=\042%s\042>", hr);
19546 asnprintf(d, z, "<img class=\042%s\042"
19547 " src=\042%s\042>", "img_large", ir);
19548 asnprintf(d, z, "</a>");
19551 asnprintf(d, z, "%s", e1->name);
19553 // asnprintf(d, z, "<br>r%d w%d%%", r, w); // debug
19554 asnprintf(d, z, "\n");
19557 if (96 == k) {
19558 asnprintf(d, z, "<td class=\042%s\042 width=\042%d%%\042>",
19559 "td_egs", 96 );
19560 asnprintf(d, z, "n/a\n");
19561 } else {
19562 if (0 != k) {
19563 asnprintf(d, z,
19564 "<td class=\042%s\042 width=\042%d%%\042>",
19565 "td_egs", k );
19566 asnprintf(d, z, "n/a\n");
19567 // asnprintf(d, z, "%s\n", e1->name);
19568 // asnprintf(d, z, "<br>r%d w%d%%", r, w); // debug
19572 asnprintf(d, z, "</tr>\n");
19573 asnprintf(d, z, "</table>\n\n");
19576 ifree( epg4, "http epg4" );
19578 build_foot_html4(d, z, 1); /* TODO: validate me */
19580 memset(n, 0, sizeof(n));
19582 /* race condition for more than one user, use mutex to avoid */
19583 snprintf( n, sizeof(n)-1, "%s%s/pg/grid%d.html",
19584 out_path, ram_path, arg_devnum);
19586 f = fopen( n, "w");
19587 if (NULL == f) return;
19589 fprintf( f, "%s\n", d);
19590 fflush(f);
19591 fclose(f);
19593 ifree(d, "epg grid4");
19596 /* dump 3 hour grid starting at epg_gs */
19597 void
19598 dump_epg_grid_html3( void )
19600 FILE *f;
19601 struct event_s *e1;
19602 struct sig_s *s;
19603 unsigned int r, r1, r2;
19604 int c, i, j, k, ch, m, w, z, un, un1, es;
19605 char n[256], hr[256], *ir;
19606 char *d;
19607 time_t t;
19608 struct tm tt;
19610 z = 32768;
19611 d = icalloc( z, 1, "epg grid3" );
19613 epg4 = imalloc( sizeof( epg ), "http epg4");
19615 un = utsnow / SEC3HR;
19616 un *= SEC3HR;
19618 un1 = utsnow / SECHR;
19619 un1 *= SECHR;
19621 r1 = epg_gs;
19622 r1 /= 3600;
19623 r1 *= 3600;
19625 /* epg_gs not set or trying to go back before the current hour */
19626 if (r1 < un) r1 = un;
19627 epg_gs = r1;
19629 r2 = (r1 + SEC3HR) - 1;
19630 m = 180;
19631 r = 1800 - (utsnow % 1800);
19633 *d = 0;
19634 build_head_html( d, z, 1, "EPG Grid", r, WHO );
19636 asnprintf(d, z, "<table border=0"
19637 " cellspacing=0 cellpadding=3"
19638 " width=\042%s\042>\n",
19639 "100%" );
19640 asnprintf(d, z, "<tr>\n");
19642 asnprintf(d, z, "<th width=\042%s\042>", SIDZ);
19644 if (r1 > un) {
19645 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042>",
19646 arg_devnum, r1 - 3600 );
19647 asnprintf(d, z, "%s", "&lt" );
19648 asnprintf(d, z, "</a>\n");
19650 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042>",
19651 arg_devnum, r1 - 10800 );
19652 asnprintf(d, z, "%s", "&lt&lt" );
19653 asnprintf(d, z, "</a>\n");
19657 text_time(n, r1, 16);
19658 t = (time_t) un1;
19659 localtime_r( &t, &tt );
19661 asnprintf(d, z, "<th width=\042%s\042>", "90%");
19663 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042> %s </a>\n",
19664 arg_devnum, un1, "Now" );
19666 asnprintf(d, z, "<a href=\042/pg/grid%d.html?f=1\042> %s </a>\n",
19667 arg_devnum, "CSS");
19668 asnprintf(d, z, "%s \n", n);
19670 for (i = 0; i < 7; i++) {
19671 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042> %s </a>\n",
19672 arg_devnum,
19673 un1 + (i * SECDAY),
19674 days[ (i + tt.tm_wday) % 7]);
19678 asnprintf(d, z, "<th width=\042%s\042>", SIDZ);
19679 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042>",
19680 arg_devnum, r1 + 3600 );
19681 asnprintf(d, z, "%s", "&gt" );
19682 asnprintf(d, z, "</a>\n");
19684 asnprintf(d, z, "<a href=\042/pg/grid%d.html?i=%d\042>",
19685 arg_devnum, r1 + 10800 );
19686 asnprintf(d, z, "%s", "&gt&gt" );
19687 asnprintf(d, z, "</a>\n");
19689 asnprintf(d, z, "</tr>\n");
19690 asnprintf(d, z, "</table>\n");
19693 asnprintf(d, z, "<table border=1"
19694 " cellspacing=0 cellpadding=3"
19695 " width=\042%s\042>\n",
19696 "100%" );
19698 asnprintf(d, z, "<tr>\n");
19700 /* SID */
19701 asnprintf(d, z, "<th width=\042%s\042>SID\n", SIDZ );
19703 /* draw hh:mm legend at top */
19704 for (i = 0; i < SEC3HR; i+= 1800) {
19705 asnprintf(d, z, "<th width=\042%d%%\042>", 16 );
19707 t = (time_t) r1 + i;
19708 localtime_r(&t, &tloc4);
19709 asnprintf(d, z, "%02d:%02d\n", tloc4.tm_hour, tloc4.tm_min);
19710 // asnprintf(d, z, "<br>%d\n", r1 + i);
19712 asnprintf(d, z, "</tr>\n");
19713 asnprintf(d, z, "</table>\n\n");
19715 /* step through all channels and load the epg for each */
19716 for (i = 0; i < scan_idx; i++) {
19717 ch = scan_list[i];
19718 s = &ptc[ch].sig;
19720 memset(&pgm4, 0, sizeof(pgm4));
19721 pgm4.chan = ch;
19723 load_epg( ch, epg4, &pgm4, pg4, WHO );
19724 // don't filter, this grid needs to show current events
19725 build_pg_epg( epg4, &pgm4, pg4, WHO );
19728 /* cable might have a lot of blank channels. bad reception will be blank too,
19729 but it won't be blank if the guide is stale, even if no events in range
19731 if (0 == pgm4.idx) continue;
19733 /* broadcast shows all, cable shows only what is loaded from remote */
19734 if (0 != arg_cable)
19735 if (0 == s->pgto)
19736 continue;
19738 asnprintf(d, z, "<table"
19739 " border=1"
19740 " cellspacing=0 cellpadding=3"
19741 " width=\042%s\042>\n",
19742 "100%");
19744 asnprintf(d, z, "<tr valign=\042%s\042>\n", "top");
19746 /* s->sid 4% */
19747 asnprintf(d, z, "<th width=\042%s\042>", SIDZ );
19749 asnprintf(d, z, "<a href=\042/pg/%02d.html\042>%s</a>\n",
19750 ch, s->sid);
19752 k = 96;
19754 for (j = 0; j < pgm4.idx; j++) {
19755 e1 = &epg4[ pg4[ j ] ];
19756 /* skip anything outside the range */
19757 if (s->pn != e1->pn) continue;
19759 // if ((e1->st + e1->ls) < un) continue;
19760 if ((e1->st + e1->ls) < r1) continue;
19762 if (e1->st > r2) continue;
19764 r = e1->ls;
19765 if (e1->st < r1) r = (e1->st + e1->ls) - r1;
19766 r /= 60;
19767 if (r > m) continue;
19769 // w = (100 * r) / m;
19770 w = (96 * r) / m;
19771 if (w > k) w = k;
19773 /* skip expired events */
19774 if (0 == w) continue;
19775 if (w < 6) w = 6; /* 10m is smallest division */
19777 k -= w;
19779 /* set event text color */
19780 es = test_event_status( e1 );
19781 c = 2; /* green is not spam, no timer and not aged */
19782 if (0 != (es & E_SPAM)) c = 4; /* grey */
19783 if (0 != (es & E_MOVIE)) c = 5; /* cyan */
19784 if (0 != (es & E_NOW)) c = 7; /* white */
19785 if (0 != (es & E_TIME)) c = 3; /* yellow */
19786 if (0 != (es & E_FIND)) c = 3; /* yellow */
19787 /* aged will set on all old events except captures and cuts */
19788 if (0 != (es & E_AGED)) c = 6; /* magenta */
19789 if (0 != (es & E_TS)) c = 3; /* yellow */
19790 if (0 != (es & E_TSC)) c = 1; /* red */
19792 ir = "";
19793 *hr = 0;
19795 if ( 0 == (es & ( E_SPAM | E_AGED)) ) {
19796 if (0 == (es & E_TIME)) {
19797 ir = "/pg/img/timeradd24.png";
19798 /* timer request, format matches config file */
19799 snprintf( hr, sizeof(hr)-1,
19800 "?a=V%02d:%u:%03d:%s@.%d:%d",
19801 s->chan, e1->st, e1->ls/60,
19802 e1->tname, e1->pn, e1->etmid );
19803 } else {
19804 ir = "/pg/img/timerdel24.png";
19805 snprintf( hr, sizeof(hr)-1,
19806 "?d=V%02d:%u:%03d:%s@.%d:%d",
19807 s->chan, e1->st, e1->ls/60,
19808 e1->tname, e1->pn, e1->etmid );
19812 asnprintf(d, z, "<td width=\042%d%%\042>", w );
19814 if (0 != *hr) {
19815 asnprintf(d, z, "<a href=\042%s\042>", hr);
19816 asnprintf(d, z, "<img border=\042%s\042"
19817 " src=\042%s\042>", "0", ir);
19818 asnprintf(d, z, "</a>");
19822 asnprintf(d, z, "<font %s size=\042%s\042>",
19823 colors3[c], "-1");
19824 if (0 != (E_TS & es)) asnprintf(d, z, "<i>");
19825 asnprintf(d, z, "%s", e1->name);
19826 if (0 != (E_TS & es)) asnprintf(d, z, "</i>");
19827 asnprintf(d, z, "</font>");
19828 // asnprintf(d, z, "<br>r%d w%d%%", r, w);
19829 asnprintf(d, z, "\n");
19833 if (96 == k) {
19834 asnprintf(d, z, "<td width=\042%d%%\042>", 96 );
19835 asnprintf(d, z, "n/a\n");
19836 // asnprintf(d, z, "<br>r%d w%d%%", 180, 96);
19837 } else {
19838 if (0 != k) {
19839 asnprintf(d, z, "<td width=\042%d%%\042>", k );
19840 asnprintf(d, z, "n/a\n");
19843 asnprintf(d, z, "</tr>\n");
19844 asnprintf(d, z, "</table>\n\n");
19848 ifree(epg4, "http epg4");
19850 build_foot_html3(d, z, 1); /* TODO: validate me */
19852 memset(n, 0, sizeof(n));
19854 /* race condition for more than one user, use mutex to avoid */
19855 snprintf( n, sizeof(n)-1, "%s%s/pg/grid%d.html",
19856 out_path, ram_path, arg_devnum);
19858 f = fopen( n, "w");
19859 if (NULL == f) return;
19861 fprintf( f, "%s\n", d);
19862 fflush(f);
19863 fclose(f);
19865 ifree(d, "epg grid3");
19869 static
19870 void
19871 dump_epg_grid( void )
19873 /* is it already running? */
19874 while( (EBUSY == pthread_mutex_trylock( &grid_mutex )) ) {
19875 nanosleep( &console_read_sleep, NULL );
19878 if (0 == use_css) {
19879 dump_epg_grid_html3();
19880 } else {
19881 dump_epg_grid_html4();
19884 pthread_mutex_unlock(&grid_mutex);
19888 /* test config frequency can be reduced somewhat now. trying 60s delay */
19889 /* test to see if config file found or changed */
19890 /* static */
19891 void
19892 test_config ( int force, char *caller )
19894 char t[256]; /* test filename */
19895 struct stat st;
19896 int ok;
19898 if (0 != arg_scan) return;
19899 #if 0
19900 /* TESTME: other optimizations have made this obsolete? */
19901 if ((0 != utstcf) && (utstcf > utsnow)) return;
19902 utstcf = utsnow + 1;
19903 #endif
19905 advrlog( LOG_INFO, "%s(%s)", WHO, caller);
19907 /* does nothing but eat stack cycles if atscap.spam mtime does not change */
19908 load_spamlist();
19910 #ifdef USE_WWW
19911 http_load_allows();
19912 #endif
19914 /* is now part of config with auto-scheduling search events */
19915 test_epgs( WHO );
19917 /* if config found, dont look again; if no config, keep trying to find */
19918 if (cf_no != 0) find_config();
19920 snprintf( t, sizeof(t)-1, "%s%s", cfg_path, cfg_name );
19922 /* only one log file alert about missing config */
19923 if ( cf_no != 0) { /* not found? */
19924 if (cf_once == 0) { /* one strike? */
19925 advrlog( LOG_ERR, "config not found %s%s",
19926 cfg_path, cfg_name );
19927 cf_once = ~0; /* yer out! */
19929 return;
19932 /* 0 if exists, or -1 error (errno set) */
19933 ok = stat( t, &st );
19934 if (0 == ok) {
19936 /* FIXME? clear cf_no every time the file is found? */
19937 /* cf_no = 0; */
19939 /* return if no force load and config file modify time unchanged */
19940 if (0 == force) {
19941 /* nothing to do? */
19943 advrlog( LOG_INFO, "test cfg stat %s %d %d %d",
19944 t, ok, cf_mt, (int)st.st_mtime);
19945 if (cf_mt == (int)st.st_mtime) return;
19947 /* it has changed, so update time and proceed with reload */
19948 advrlog( LOG_INFO, "%s mtime %d != %d",
19949 cfg_name, (int)st.st_mtime, cf_mt );
19951 cf_mt = (int)st.st_mtime;
19952 } /* force ~0 falls thru here */
19955 load_config( WHO );
19958 /* Show how many minutes and gigabytes needed for all timers + weekdays.
19959 To attempt to keep the hard drive asleep if it is asleep, if no cap
19960 is in progress it will display the last read value.
19961 Clear utsvsu to force it to read an updated value in build html head.
19964 /* static */
19965 void
19966 show_volume_status ( void )
19968 char ds[64];
19969 long long tb = 0LL;
19970 long long tt = 0LL;
19971 long long ts = 0LL;
19972 long long fg = 0LL;
19973 char *tc;
19974 char *tca;
19976 if (0 == refresh.volstats) return;
19978 refresh.volstats = 0;
19980 tca = BN;
19982 /* green = more than 3 hours free space left */
19983 tc = BG;
19984 tt = total_timers();
19986 /* luser avail cut_ or vol_ fsp.f_bavail * .f_bsize */
19987 /* suser avail cut_ or vol_ fsp.f_bfree * .f_bsize */
19988 /* volume size cut_ or vol_ fsp.f_blocks * .f_bsize */
19990 if (CAP_NONE != cap_now) read_capvol_stats();
19992 /* free space in gigabytes */
19993 fg = vol_free;
19994 ts = vol_size;
19996 /* transport rate estimate bytes per second * alltimers seconds */
19997 tb = (TSORATE * tt) >> 30;
19999 /* if total size is less than total timer bytes */
20000 if (ts < tb) tca = BR;
20002 /* cyan = less than 3 hours free */
20003 if ( fg < 25 ) tc = BC;
20005 /* yellow less than 2 hours free */
20006 if ( fg < 16 ) tc = BY;
20008 /* red = less than 1 hour free, blink as warning */
20009 if ( fg < 9 ) tc = BL BR;
20011 snprintf( ds, sizeof(ds), "%dt %lldm %s%lldG (%s%lld%s/%lldG)"
20013 " ",
20014 timer_tot, tt / 60, tca, tb, tc, fg, BN, ts );
20016 /* 26 chars with 7 char ecma color and 4 char ecma color reset */
20017 ds[ 26 + strlen( tc ) + strlen( tca ) + strlen( BN ) ] = 0;
20019 /* timer_tot is number of shows total, including weekdays */
20020 aprintf( stderr, BN "%s %s", dca.sstat, ds);
20022 /* For -E energy saver mode, only wake up volumes to check once every 3 hours,
20023 except during capture, which needs to update current capture volume stats
20024 so the volume full test will work. Cut volume can still be delayed until
20025 the next 3hr meridian. http index_html should reset utsvsu to get it now.
20027 if (utsvsu > utsnow) return;
20028 utsvsu = get_next_meridian();
20030 if (CAP_NONE == cap_now) read_capvol_stats();
20031 read_cutvol_stats();
20034 /* -E power save:
20035 if open and idle and past power down time, close the dvb device
20037 /* static */
20038 void
20039 test_powerdown( void )
20041 if (
20042 (0 != arg_tmpfs)
20043 && (in_file > 2)
20044 /* dont power down during scan */
20045 && (0 == arg_scan)
20047 /* no cap and test epgs is idle too? */
20048 && (CAP_NONE == cap_now)
20049 && (0 == epg_test)
20051 /* if no channel scan: irritating. trying only if no sig scan at all */
20052 // && (D_CHANNELS != display_type)
20053 && (0 == scan_sig)
20056 /* time to power down device and device not already closed? */
20057 && (utsnow > utspdd)
20059 /* if test guides isn't going to run within next 60s */
20060 && ( (utsnow+60) < utstpg )
20065 close_device( WHO );
20066 dvrlog( LOG_INFO, "DVR CLOSED");
20068 utswuv = 0;
20069 /* volume will only spin up for timers, not EPG capture */
20070 if ( (timer_idx > 0) && (timer[0].qstat != T_SEARCH) ) {
20071 if (timer[0].start > utsnow)
20072 utswuv = timer[0].start - 30;
20075 if (utswuv > utsnow)
20076 advrlog( LOG_INFO, "hdd wake in %ds", utswuv - utsnow);
20078 if (utstpg > utsnow)
20079 advrlog( LOG_INFO, "dvb wake in %ds", utstpg - utsnow);
20084 /* Show wall clock in top right corner: DST is yellow, ST is normal */
20085 /* static */
20086 void
20087 show_clock ( void )
20089 char *tc;
20091 get_time();
20092 /* clock/timers update 1 per second */
20093 if ((0 == refresh.clock) && (ptnow == tnow)) return;
20095 refresh.clock = 0;
20097 ptnow = tnow;
20099 tc = BN;
20100 if (0 != tloc.tm_isdst) tc = BY;
20101 aprintf( stderr, SCI "%s%s%s", dca.wstat, tc, date_now );
20103 #ifdef USE_POWERDOWN
20104 test_powerdown();
20105 #endif
20110 /* This requires -x option to enable experimental capture status,
20111 otherwise all you get is "dvbN", "test" or "file" at dca.zstat.
20113 Because i2c may not work correctly, it is grouped under the -x option.
20114 During capture, query the device operational parameters once per second:
20115 SNR, strength, frequency drift, bit error rate & uncorrected blocks.
20117 /* static */
20118 void
20119 show_FE_status ( void )
20121 struct sig_s *s;
20122 char *t = "";
20123 char u[8];
20124 s = &ptc[ cap_chan ].sig;
20126 memset(u, ' ', sizeof(u));
20127 u[5] = 0;
20129 if (0 != test_mode) t = "test";
20130 if (0 != arg_dummy) t = "file";
20131 if (0 == arg_dummy) t = "dvb";
20133 /* TESTME: This was causing it to lock up before. Seems OK now. */
20134 /* Defers device read to channel scan when not capturing. */
20135 if ((CAP_NONE != cap_now) && (0 != arg_extras))
20136 get_signal_lock(s);
20138 aprintf( stderr, "%s" BN "%s%d", dca.zstat, t, arg_devnum );
20141 /* search vc[] until found matching source id, return index or -1 if failed */
20142 /* static */
20144 find_vc_src ( unsigned int src )
20146 int i;
20147 for (i=0; i< vc_ncs; i++)
20148 if ( src == vc[i].src)
20149 return i;
20150 return -1;
20154 #if 0
20155 /* not needed yet */
20157 /* return -1 if no matching timer etmid found, or 0-n for timer index */
20158 /* static */
20160 find_timer_etmid ( unsigned int etmid )
20162 int i;
20163 for (i=0;i<timer_idx;i++) {
20164 if (0 == timer[i].etmid) continue; /* skip non EPG sourced events */
20165 if (etmid == timer[i].etmid) return i;
20167 return -1;
20169 #endif
20171 /* search for etmid match, return index if found, -1 if not found */
20172 /* uses 1/VCZ loops of find_pgm_etmid using VCZ loops */
20173 /* static */
20175 find_epg_etmid ( unsigned int etmid )
20177 /* first, reduce the search to only this vc, so 1/8 as many loops */
20178 unsigned int i, j, sr;
20179 int v;
20180 sr = 0xFFFF & (etmid >> 16);
20182 /* if they're sending NTSC PG info, can get same ETT text for event
20183 this can make the descriptions fill in faster. it would be faster
20184 if KHWB didn't send NTSC info at all, too. give us 10 days not 5.
20186 v = find_vc_src( sr ); /* source sanity check */
20187 if ( -1 == v ) return -1; /* not found */
20189 j = v * PEZ * EIZ;
20190 if ( j >= PGZ ) j %= PGZ; /* compare is less than divide pipeline */
20192 for (i = 0; i < (PEZ * EIZ); i++) {
20193 /* check against ETMID loaded from EIT */
20194 if ( etmid == epg[ i + j ].etmid ) return i + j;
20197 return -1; /* not found */
20201 /* Save current vc and program guide structures to separate files. */
20202 /* static */
20203 void
20204 save_guide ( void )
20206 char n[256], t[32]; /* file name */
20207 struct sig_s *s;
20208 size_t ok;
20209 struct utimbuf gt; /* guide file time stamp */
20210 int i, g, ch;
20212 if (0 != test_mode) return;
20213 if (0 != arg_scan) return;
20215 ch = pgm.chan;
20216 s = &ptc[ pgm.chan ].sig;
20217 memset( &gt, 0, sizeof(struct utimbuf) ); /* clear all times */
20219 g = 0;
20221 advrlog( LOG_INFO, "%s: vc_ncs %d, pgm.idx %d",
20222 WHO, vc_ncs, pgm.idx );
20224 /* do not save until VCT or PAT+PMT available */
20225 if (0 == vc_ncs) {
20226 advrlog( LOG_INFO, "%s VCT%02d not saved: ncs: vc %d vct %d pat %d",
20227 WHO, ch, vc_ncs, vct_ncs, pat_ncs);
20228 return;
20230 memset(n, 0, sizeof(n));
20232 /* NOTE: Multi-card race condition possible, but likely? Try to avoid
20233 with staggered sleep, but it may not be a real solution, only a test.
20235 Guaranteed no-race way is to not have same channels on each instance,
20236 if user can remember to set the auto-epgs that way for each instance.
20238 for ( i = 0; i < arg_devnum; i++ )
20239 nanosleep(&signal_loop_sleep, NULL);
20241 /* at least one valid program entry */
20242 save_vct( ch, &vct_ncs, vc, &pat_ncs, pa );
20243 advrlog( LOG_INFO, "VCT%02d guide saved", pgm.chan);
20245 /* use copies to build full pg1 list, set pgm1.idx */
20246 memset( pg1, 0xFFFF, sizeof(pg1) ); /* clear indices */
20247 memset( &pgm1, 0, sizeof(pgm1) ); /* clear filters */
20248 pgm1.chan = ch; /* save epg chan */
20250 /* save epg creates binary unfiltered epg data as ch.epg */
20251 save_epg( ch, epg, &pgm1, pg1, WHO );
20252 advrlog( LOG_INFO, "EPG%02d guide saved", pgm.chan);
20254 /* This method should yield smoother 3hr meridian times. */
20255 if (0 != s->pgto) {
20256 // g = get_next_meridian( epg, &pgm1, pg1 );
20257 g = get_next_meridian();
20258 s->pgmt = g;
20259 gt.modtime = (time_t)g;
20263 /* Three files now, ch.vc, ch.epg and ch.html, but onl ch.epg needs
20264 expiration time set here. ch.html is updated by dump epg.
20266 if (0 != g) {
20267 snprintf( n, sizeof(n)-1, "%s%spg/%02d.epg",
20268 out_path, ram_path, pgm.chan);
20270 text_time( t, g, 16); /* don't need seconds */
20271 ok = utime( n, &gt );
20273 if (0 == ok) {
20274 advrlog( LOG_INFO, "EPG%02d expires %d events on %s",
20275 pgm.chan, pgm1.idx, &t[4] );
20278 if (-1 == ok) {
20279 advrlog( LOG_INFO, "EPG%02d utime(%s) error %s",
20280 pgm.chan, n, strerror(errno) );
20285 /* write capture log as capture name.html or return if info cap or invalid */
20286 /* changed to use asnprintf and one fprintf to reduce filesystem io */
20287 /* TODO: CSS version that gives each line green yellow or red background
20288 depending on the severity of the errors for that minute.
20290 For now, put in <div class=f5> at top and </div> at bottom if CSS.
20293 /* static */
20294 void
20295 dump_cap_log( char *caller )
20297 FILE *f; /* caplog file descriptor */
20298 double fets, /* ets as float */
20299 sbp, /* seconds bad% */
20300 sgp; /* seconds good% */
20301 long long brate; /* bit rate */
20302 int i, j, k, r, z, /* counters */
20303 etm, ets; /* et minutes and seconds */
20304 unsigned int sn, /* seconds n/a */
20305 sb, /* seconds bad */
20306 sg; /* seconds good */
20307 int t; /* error count per second test */
20308 char
20309 // cap_log[256], /* caplog file name */
20310 p[256], /* capture file name */
20311 d[256], /* deleted capture file name */
20312 chop_time[32], /* truncated time */
20313 bratet[32]; /* byte and bit rate text */
20314 char *s, /* allocated string pointer */
20315 *ft, /* fail text pointer */
20316 *tense; /* present/past tense pointer */
20317 char t1; /* error severity flag */
20318 struct sig_s *ps; /* scan list */
20320 if (0 != arg_scan) return;
20322 ps = &ptc[cap_chan].sig;
20324 /* can only log as big as buffer is. don't do log if it's wrapped junk, */
20325 if (utsets >= CAP_STZ) return; /* boundary check */
20327 advrlog( LOG_INFO, "%s(%s)", WHO, caller);
20328 sn = sg = sb = 0; /* clear seconds good,bad */
20330 /* ets = utsnow - utscap, set by show cap stats */
20331 ets = utsets;
20332 if (ets < 1) ets = 1;
20334 filebase( p, out_name, F_FILE ); /* capture name */
20335 filebase( d, del_name, F_FILE ); /* deleted name */
20337 if (0 == strlen(p)) return; /* sanity check */
20339 snprintf( chop_time, sizeof(chop_time)-1, asctime(&tloc3) );
20340 chop_time[19] = 0; /* only want weekday month day h:m:s */
20342 etm = ets / 60;
20343 if (etm < 1) etm = 1; /* at least one line */
20345 /* allocated string size is 4k for html and text headers and counts */
20346 /* plus 80 * number of minutes to display for error flags */
20347 /* plus 80 * number of (seconds / 4) for error detail */
20348 z = 4096;
20349 z += 80 * etm;
20351 /* five seconds per line for error detail, reuse etm */
20352 etm = ets / 5;
20353 if (etm < 1) etm = 1; /* at least one line */
20354 z += 80 * etm;
20356 /* is it already running? if it is, don't do rest */
20357 if (EBUSY == pthread_mutex_trylock( &caplog_mutex )) return;
20359 s = icalloc( z, sizeof(char), "caplog" );
20361 /* didn't calloc? need to unlock caplog mutex */
20362 if (NULL == s) {
20363 dvrlog( LOG_INFO, "%s failed caplog calloc", WHO );
20364 pthread_mutex_unlock( &caplog_mutex );
20365 return;
20368 /* any return after this point has to free s and unlock caplog mutex */
20371 /* Live capture sets refresh to once every 15s.
20372 It is presumed this will only be called once when no capture is
20373 occurring, from the end of ts capture for the final caplog.
20375 r = 0;
20376 if (0 == cap_done) r = 15;
20378 /* html headers */
20379 build_head_html( s, z, 1, p, r, WHO);
20381 asnprintf( s, z, "<br class=\042%s\042 />\n", "br_epgdiv");
20383 /* text for failure reason, should be copy of above */
20384 ft = "";
20385 switch(cap_fail) {
20386 case FAIL_NONE:
20387 ft = "OK";
20388 break;
20390 /* Loss of sync does not happen very often with the newer cx88 based designs.
20391 HD-2000 may still need it if the DVB API bttv driver doesn't handle it. */
20392 case FAIL_TSYNC:
20393 ft = "SYN FAIL";
20394 break;
20396 /* input fifo error, error in loop */
20397 case FAIL_IFIFO:
20398 ft = "IFE FAIL";
20399 break;
20401 /* output fifo error, error in loop */
20402 case FAIL_OFIFO:
20403 ft = "OFE FAIL";
20404 break;
20406 /* -z QoS bad threshold reached. is same as FAIL_QOS now? */
20407 case FAIL_ZAP:
20408 ft = "QoS FAIL";
20409 break;
20411 /* show cap stat triggers this */
20412 case FAIL_QOS:
20413 ft = "QoS FAIL";
20414 break;
20416 /* not sure how to trigger this */
20417 case FAIL_NOSPC:
20418 ft = "VFE FAIL"; /* volume full error */
20419 break;
20421 /* ts capture loop triggers this */
20422 case FAIL_AOS:
20423 ft = "AOS FAIL";
20424 break;
20426 /* what did i forget? */
20427 default:
20428 ft = "???"; /* unhandled */
20429 break;
20432 /* past tense if file was zapped, but then no reason to log? */
20433 tense = "has";
20434 if (cap_zap != 0) tense = "had";
20436 /* make the font bigger for entire page if CSS. is OK in HTML3 */
20437 if (0 != use_css) {
20438 asnprintf( s, z, "<div class=\042%s\042>\n", "f5" );
20441 /* "dtv program" does not get an event name */
20442 if (0 != *epg_name)
20444 asnprintf( s, z, "Event name: %s<br />\n", epg_name);
20445 if (0 != *epg_desc)
20446 asnprintf( s, z, "Event text: %s<br />\n", epg_desc);
20448 asnprintf( s, z, "<hr>\n");
20451 asnprintf( s, z, "<pre>\n"); /* start pre-formatted text */
20452 asnprintf( s, z, "%s ", p); /* capture name */
20454 /* quantitative summary */
20455 lltoasc( bratet, outbc );
20456 asnprintf( s, z, "%s bytes at ", bratet );
20458 brate = outbc;
20459 brate <<= 3;
20460 brate /= ets;
20461 lltoasc( bratet, brate );
20462 asnprintf( s, z, "%s bits/second\n", bratet );
20464 asnprintf( s, z, "%s from dvb%d PTC %d.%d: QoSavg %lld%%: %s, ET %d seconds\n",
20465 ft, arg_devnum, ps->ptc, ps->pn, qost, &chop_time[0], ets );
20467 /* qualitative summary header */
20468 asnprintf( s, z, "Errors/sec: GOOD: .=0,1-9 OK: a-z=10-35 BAD: A-Z=36-61 # >61 * >99 ?=na\n");
20469 asnprintf( s, z, " Wall Cap 0 1 2 3 4 5\n");
20470 asnprintf( s, z, "hh:mm:ss hh:mm:012345678901234567890123456789012345678901234567890123456789\n");
20472 /* step error count of 16-bit words 60 seconds at a time.
20473 16 bits should be large enough word size for error packets:
20474 13000 broadcast limit + cc errors (20Mbit)
20475 26000 cable limit + cc errors (40Mbit)
20476 52000 satellite limit + cc errors (80Mbit)
20479 for (i = 0; i < ets; i += 60 )
20481 int h2, m2, s2, h1, m1, s1;
20483 if ( i >= CAP_STZ) break; /* don't wrap around buffer */
20485 /* show minute at start of line */
20486 h1 = h2 = (i / 3600) % 24;
20487 m1 = m2 = (i / 60) % 60;
20488 s1 = s2 = i % 60;
20490 /* tloc3 is start cap timeval */
20491 s1 += tloc3.tm_sec;
20492 if ( s1 > 60 ) {
20493 s1 %= 60;
20494 m1++;
20497 m1 += tloc3.tm_min;
20498 if ( m1 > 60 ) {
20499 m1 %= 60;
20500 h1++;
20503 h1 += tloc3.tm_hour;
20504 h1 %= 24;
20506 #ifdef USE_CSS_CAPLOG
20507 /* broken? looks ok but colors are green when they should be red */
20508 /* is trying to nest a div inside a pre confusing the browser? */
20509 /* tally the error counts for the entire minute */
20510 if (0 != use_css) {
20511 char *c;
20512 c = "cltc0"; // cap log text color 0
20513 t = 0;
20514 for (j = 0; j < 60; j++) {
20515 k = i + j;
20516 if ( ets <= k ) break;
20517 if ( k >= CAP_STZ) break;
20518 t += cap_stats[k];
20520 /* average errors */
20521 t /= j; /* /60 assumes whole minute */
20522 if (t > 10) c = "cltc1";
20523 if (t > 30) c = "cltc2";
20525 /* total errors */
20526 // if (t > 1000) c = "cltc1";
20527 // if (t > 2000) c = "cltc2";
20528 asnprintf(s, z, "<div class=\042cltf0 %s\042>", c);
20530 #endif
20532 /* show wall clock and capture clock to the minute */
20533 asnprintf( s, z, "%02d:%02d:%02d=%02d:%02d ",
20534 h1, m1, s1, h2, m2);
20535 /* draw 60 bytes of seconds status for rest of line */
20536 for (j = 0; j < 60; j++) {
20538 k = i + j;
20539 /* don't show unlogged data at end of last minute */
20540 if ( ets <= k ) break;
20541 if ( k >= CAP_STZ) break;
20543 t = cap_stats[ k ];
20545 t1 = ' ';
20547 if ((t > 0) && (t < 62)) { /* 1-9 a-z A-Z, ASCII */
20548 t1 = '0' + t;
20549 if (t > 9) t1 = 'a' + (t-10);
20550 if (t > 35) t1 = 'A' + (t-36);
20553 if (t > 61) t1 = '#';
20554 if (t > 99) t1 = '*';
20556 if (t == 0 ) t1 = '.'; /* no errors is dot */
20557 if (t < 0 ) t1 = '?'; /* -1 is no data '?' */
20559 if (t < 0) { /* no data collected */
20560 sn++; /* should --ets? */
20561 } /* no: see rate below */
20562 else
20564 if (t < 36 ) { /* 36 is start of A-Z bads */
20565 sg++; /* seconds good */
20566 } else {
20567 sb++; /* seconds bad */
20570 asnprintf( s, z, "%c", t1);
20573 #ifdef USE_CSS_CAPLOG
20574 if (0 != use_css) asnprintf(s,z, "</div>");
20575 #endif
20577 asnprintf( s, z, "\n");
20580 ets -= sn;
20581 if (ets < 1) ets = 1;
20582 fets = 1.0 * ets;
20584 /* used anymore? */
20585 if (pkt.pes == 0) pkt.pes = 1;
20587 /* qualitative estimates, force type to float */
20588 sgp = (100.0 * sg) / fets;
20589 sbp = (100.0 * sb) / fets;
20591 /* html <pre> doesn't remove whitespace, <code> does */
20592 asnprintf( s, z, "</pre>\n"); /* stop pre-formatted text */
20593 asnprintf( s, z, "<hr>\n"); /* validator error if <hr> in <pre> */
20594 asnprintf( s, z, "<pre>\n"); /* start pre-formatted text */
20595 asnprintf( s, z, "%s used %d bytes of FIFO (%.4f%%)\n",
20596 in_name, fill_max2,
20597 (100.0 *fill_max2) / (double)fifoz );
20599 if (pkt.count == 0) pkt.count = 1;
20600 asnprintf( s, z, "%s read %d TS packets: %d had TEI set (%.4f%%)\n",
20601 in_name,
20602 pkt.count,
20603 pkt.teiert,
20604 (100.0 * pkt.teiert) / (double)pkt.count );
20606 cap_errors = pkt.errors;
20608 /* any errors? if so, make them look nice with commas */
20609 if (cap_errors != 0) {
20610 char caperr[32], synerr[32], teierr[32];
20611 char tscerr[32], tccerr[32], crcerr[32];
20613 lltoasc(caperr, (long long)cap_errors);
20614 lltoasc(synerr, (long long)pkt.synert);
20615 lltoasc(teierr, (long long)pkt.teiert);
20616 lltoasc(tscerr, (long long)pkt.tscert);
20617 lltoasc(tccerr, (long long)pkt.tsccet);
20618 lltoasc(crcerr, (long long)pkt.crcats+pkt.crcmp2);
20620 asnprintf( s, z, "Detected %s errors: "
20621 "%s SYNC, %s TEI, %s SCR, %s CC, %s CRC\n",
20622 caperr, synerr, teierr, tscerr, tccerr, crcerr );
20625 /* qualitative analysis summary */
20626 asnprintf( s, z,
20627 "Captured %d seconds: "
20628 "%d OK (%.4f%%) %d Bad (%.4f%%) %d n/a\n",
20629 ets, sg, sgp, sb, sbp, sn );
20630 if (0 != cap_tsid) {
20631 if (cap_pn > 0) {
20632 if (cap_vc >= 0) { /* pmt load sets this */
20633 t = find_tsidx( cap_tsid, cap_pn, WHO );
20634 if (t >= 0) {
20635 asnprintf( s, z, "TSID %04X PTC.PN %d.%d %s %s\n",
20636 tsids[t].tsid, tsids[t].ptc, tsids[t].pn,
20637 tsids[t].call, tsids[t].sid );
20643 /* alert if timer name did not set VC with ".x" in name */
20644 if ((CAP_TIME == cap_now) && (0 != cap_pidx) && (-1 == cap_vc))
20645 asnprintf( s, z, "\nERROR: full cap, VC was not set!\n");
20647 /* NOTE: TEI errors can't have PID summary because PID field likely bad. */
20648 /* If single program cap, show MPEG2 PIDs and continuity errors, if any. */
20649 if ( (cap_pidx > 0) && (-1 != cap_vc) ) {
20650 char *pt[4] = { "PAT", "PMT", "VID", "AUD" };
20652 asnprintf( s, z, "\n");
20653 asnprintf( s, z, "Video frames (/s) %d (%4.2f): ",
20654 frame_idx, ( (double)frame_idx ) / (double) ets );
20656 if (ets < 1) ets = 1;
20657 j = ets>>1;
20658 if (j < 1) j = 1;
20660 i = sequence_idx / ets;
20661 r = sequence_idx % ets;
20662 if (r > j) i++;
20663 asnprintf( s, z, " Types I %d (%d), ", sequence_idx, i);
20665 i = pict[2] / ets;
20666 r = pict[2] % ets;
20667 if (r > j) i++;
20668 asnprintf( s, z, "P %d (%d), ", pict[2], i);
20670 i = pict[3] / ets;
20671 r = pict[3] % ets;
20672 if (r > j) i++;
20673 asnprintf( s, z, "B %d (%d)\n", pict[3], i);
20675 asnprintf( s, z, "\n%s %s %d MPEG2 PIDs for VC %1X AUD %1X:\n",
20676 p, tense, pkt.kept, cap_vc, cap_va);
20678 for (i=0; i<cap_pidx; i++) {
20679 int ppc;
20680 ppc = pkt.count/100;
20681 if (ppc < 1) ppc = 1; /* avoid /0s */
20682 if (ets < 1) ets = 1;
20683 j = cap_pids[i];
20684 brate = pids[j];
20685 brate *= 188;
20686 brate <<= 3;
20687 brate /= ets;
20689 lltoasc( bratet, brate );
20690 asnprintf( s, z, " %s PID %04X %9d %12s bits/s (%2d%%)",
20691 pt[i], j, pids[j], bratet, (pids[j] / ppc) );
20693 if (cce_pids[j] > 0) {
20694 if (pids[j] > 0)
20695 asnprintf( s, z, " Err ");
20696 asnprintf( s, z, "%6d ", cce_pids[j] );
20697 asnprintf( s, z, "(%.4f%%)",
20698 (100.0 * (double)cce_pids[j]) / (double)pids[j]);
20700 asnprintf( s, z, "\n");
20702 } else {
20703 asnprintf( s, z, " cap_pidx %d cap_pn %d cap_vc %d\n",
20704 cap_pidx, cap_pn, cap_vc );
20705 for (i = 0; i < cap_pidx; i++) {
20706 asnprintf( s, z, " %04X", cap_pids[i]);
20708 asnprintf( s, z, "\n");
20711 /* EPG doesn't need to test for previous capture overwritten
20712 or show MPEG2 summary since EPG reads only ATSC packets. */
20713 if ((CAP_INFO != cap_prev) && (0 != pkt.mpeg2)) {
20714 int pk;
20715 pk = pkt.mpeg2;
20716 if (cap_pn > 0) pk = pkt.kept;
20718 /* if existing capture.$ file, show it will be or was deleted */
20719 if ( *del_name != 0 ) {
20720 asnprintf( s, z, "%s %s deleted\n",
20721 d, (0 == cap_done)?"will be":"was");
20724 #if 0
20725 /* MPEG2 packet summary */
20726 asnprintf( s, z, "\nMPEG2 packets %d:\n", pk);
20727 asnprintf( s, z,
20728 "PAT CAT PMT"
20729 " VID"
20730 " AUD"
20731 " v+a PES"
20732 " NULL(%sdiscarded)"
20733 "\n", arg_nulls ? "not ":"");
20735 asnprintf( s, z, "%3d %3d %3d %10d %10d %10d %10d\n",
20736 pkt.pat, pkt.cat, pkt.pmt,
20737 pkt.vid, pkt.aud, pkt.pes, pkt.null );
20738 #endif
20740 /* MPEG2 CRC32 error summary */
20741 if (0 != pkt.crcmp2) {
20742 asnprintf( s, z, "MPEG2 CRC errors %d:\n", pkt.crcmp2);
20743 asnprintf( s, z, "PAT CAT PMT\n");
20744 asnprintf( s, z, "%3d %3d %3d\n",
20745 pkt.crcpat, pkt.crccat, pkt.crcpmt );
20749 /* ATSC packet summary if full cap or cap info */
20750 if ( (( cap_pn < 1) || (CAP_INFO == cap_prev)) && (0 != pkt.atsc) ) {
20751 asnprintf( s, z, "\nATSC packets %d:\n", pkt.atsc);
20752 asnprintf( s, z, "MGT TVCT CVCT RRT "
20753 "EIT ETT STT\n");
20754 asnprintf( s, z, "%3d %4d %4d %3d %3d %3d %5d\n",
20755 pkt.mgt, pkt.tvct, pkt.cvct, pkt.rrt,
20756 pkt.eit, pkt.ett, pkt.stt);
20758 if (0 != pkt.crcats) {
20759 asnprintf( s, z, "ATSC CRC errors %d:\n", pkt.crcats);
20760 asnprintf( s, z, "MGT TVCT CVCT RRT "
20761 "EIT ETT STT\n");
20762 asnprintf( s, z, "%3d %4d %4d %3d %3d %3d %5d\n",
20763 pkt.crcmgt, pkt.crctvct, pkt.crccvct,
20764 pkt.crcrrt, pkt.crceit, pkt.crcett,
20765 pkt.crcstt );
20770 /* if error seconds detail is off or no errors, all done except for footer */
20771 asnprintf( s, z, "</pre>\n");
20773 if ( (0 != arg_edetail) && (cap_errors > 0) ) {
20774 asnprintf( s, z, "<hr>\n");
20775 asnprintf( s, z, "<pre>\n");
20776 asnprintf( s, z, "Errors per second detail (-1 is n/a):\n");
20778 j = 0;
20779 for (i = 0; i < ets; i++) {
20780 if (i >= CAP_STZ) break;
20781 if (0 != cap_stats[ i ] ) {
20783 /* 5 entries per line */
20784 j++;
20785 j %= 5;
20786 asnprintf( s, z, "%02d:%02d:%02d %-5d",
20787 (i/3600) % 24,
20788 (i/60) % 60,
20789 i % 60,
20790 cap_stats[ i ] );
20792 if (0 == j) {
20793 asnprintf( s, z, "\n");
20794 } else {
20795 asnprintf( s, z, " ");
20801 asnprintf( s, z, "</pre>\n");
20804 /* html footer */
20805 asnprintf( s, z, "<p>\n");
20807 /* turn off the big font if CSS. is OK in HTML3 */
20808 if (0 != use_css)
20809 asnprintf( s, z, "</div>\n" );
20811 build_foot_html( s, z, 1 );
20813 filebase( d, out_name, F_TFILE); /* ok to re-use d now */
20814 /* regular cap log to cap dir name.html */
20815 snprintf( caplog_name, sizeof(caplog_name), "%s%s.html", out_path, d);
20817 /* EPG cap log to /dtv/[ram/]pg/EPGch.html */
20818 if (NULL != strstr( d, "EPG" ))
20819 snprintf( caplog_name, sizeof(caplog_name), "%s%spg/%s.html",
20820 out_path, ram_path, d);
20822 /* open the file for write and write the assembled string */
20823 f = fopen( caplog_name, "w");
20824 if ( NULL != f ) {
20825 fprintf( f, "%s", s);
20826 fflush( f );
20827 fclose( f );
20828 } else {
20829 dvrlog( LOG_INFO, "%s error create %s", WHO, caplog_name);
20832 ifree( s, "caplog" );
20833 pthread_mutex_unlock( &caplog_mutex);
20836 /* rewrite of epg test code from old show packet stats */
20837 /* 30 seconds after last EIT/ETT change, stop cap, or 180 second limit.
20838 180 second limit is needed for stations that send more than one new
20839 MGT update, which can keep the guide load going for more than 180s.
20840 This only operates on pgm struct since that is the live one.
20842 /* static */
20843 void
20844 test_epg_done ( void )
20846 if (CAP_INFO != cap_now) return; /* only for info cap */
20848 /* NOTE: If you have 10 or 16 days of program guide you may need to
20849 make this number larger. If you are not getting the same
20850 number of programs with guide capture as you get with
20851 a manual capture of 5 minutes or so, try + 60 instead.
20852 If cable has a guide, it may be very large. For now it is
20853 assuming it does not have a guide and only needs quick check.
20855 if (0 != arg_cable) {
20856 if (0 == pgmto) pgmto = utsnow + 2; /* cable only needs PAT+PMT */
20857 } else {
20858 if (0 == pgmto) pgmto = utsnow + 30; /* use 60 if bad signal area */
20861 /* this is faster than calling build pg epg to find title/descriptions */
20862 /* check for EIT# or ETT # to change. versioning keeps these #'s accurate */
20863 if ( (pgme != pkt.eit) || (pgmd != pkt.ett) ) {
20864 pgme = pkt.eit; /* update current count */
20865 pgmd = pkt.ett; /* description count too */
20866 pgmto = utsnow + 30; /* each new item buys 30 more seconds */
20867 pgm.fail = 0; /* ok so far */
20868 refresh.epg = 1;
20871 /* EIT or ETT change resets timeout to the future, limited to 180 seconds */
20872 if (utsets > 180) pgmto = 0;
20873 if ( (0 != pgmto) && (utsnow < pgmto) ) return;
20875 stop_capture( WHO, FAIL_NONE );
20877 /* flag guide as done */
20878 pgm_done = ~0;
20879 advrlog( LOG_INFO, "APG%02d found %3d EITs %3d ETTs in %ds",
20880 cap_chan, pkt.eit, pkt.ett, utsets);
20881 return;
20885 blink capture time once per second, and do other things too.
20886 display time left/time used, fifo info, transport errors, PES counts
20888 added test cfg/show timers to this, as it's a separate thread now,
20889 even if it spends a bit of time, it shouldn't degrade cap anymore.
20891 this also calls test eit ett done (above) to stop info cap
20893 /* static */
20894 void
20895 show_cap_status ( void )
20897 char n[256];
20898 char *f = NULL; /* capture filename */
20899 char *tc; /* timer color */
20900 char d[32]; /* display area top left 31 chars */
20901 char tse = ' '; /* v or ^ for up or down vary, or blank */
20902 int utsel; /* timer eta, backwards count */
20903 struct sig_s *s; /* pointer for shorthand to ptc */
20904 char pe[32], pt[32], pk[32]; /* packet error strings */
20905 char *ec;
20906 long long pkt_err = 0LL; /* packet errors total */
20907 long long pkt_num = 0LL; /* packet number total */
20908 long long pkt_mp2 = 0LL; /* packet mpeg2 total */
20909 long long pc, pb;
20910 char *qtc, *qc; /* qos/t colors */
20912 ec = BR;
20914 if ( CAP_NONE == cap_now ) return; /* false alarm */
20916 #ifdef STOP_SCAN_ON_FIRST_VCT_FOUND
20917 /* disabled because want to get the video dimensions from ES SEQ */
20918 /* -S option stops at first received VCT for station ID */
20919 if ( (0 != arg_scan) && ( 0 != pkt.tvct ) ) {
20920 stop_capture( "-S found VCT, ", FAIL_NONE );
20922 #endif
20924 tc = "";
20925 refresh.pstats = 1; /* so stats will update during cap */
20927 get_time();
20929 if (tnow == ptnow) return;
20930 ptnow = tnow;
20932 if ( utscap == 0 ) return; /* hold down */
20935 /* filename shortcut w/o path or extension */
20936 filebase( n, out_name, F_TFILE );
20938 f = n;
20940 /* QOS per reporting period */
20941 pc = pkt.count - last_pktc; /* number of pkts since last time */
20942 pb = pkt.errors - last_pktb; /* number of errors since last time */
20943 if (pc < 1) pc = 1; /* avoid /0 floating point exception */
20944 qosc = 100 - ( (100*pb)/pc);
20945 if (qosc > 100) qosc = 100;
20946 if (qosc < 0) qosc = 0;
20948 /* QoS average */
20949 pc = last_pktc = pkt.count;
20950 pb = last_pktb = pkt.errors;
20952 if (pc < 1) pc = 1; /* avoid /0 floating point exception */
20953 qost = 100 - ( (100*pb)/pc);
20954 if (qost > 100) qost = 100;
20955 if (qost < 0) qost = 0;
20957 /* disable QoS test for now */
20958 /* if (0 == qost) stop_capture( "show cap stats", FAIL_QOS); */
20960 if (utsets < 0 ) utsets = 0; /* dont display negatives */
20961 utsel = utsets; /* default displays elapsed time by mode */
20963 test_epg_done(); /* stop cap if it's done */
20965 #if 0
20966 /* move to test_qos_good() */
20968 /* QoS check for timers, presume manual and info cap do not need QoS check. */
20969 if (CAP_TIME == cap_now) {
20970 if ( (utsets > 60) && (qost < 90) ) {
20971 char qfc[4096];
20972 snprintf( qfc, sizeof(qfc)-1,
20973 "timer qos fail ets %d qost %lld",
20974 utsets, qost);
20975 /* prevent test timers from setting cap now so it will terminate */
20976 timer[timer_rec].qstat = T_DONE;
20977 stop_capture( qfc, FAIL_QOS );
20980 /* want guide saved even if it only got eit0 (for search timers) */
20983 this prevents guide from being saved if QOS was too low
20984 but will only check for QOS after qos info ss timeout
20986 if (utsets > QOS_INFO_TIMEOUT) {
20987 if (qost < QOS_INFO_SS_LIMIT) {
20988 stop_capture( WHO, FAIL_QOS );
20992 /* no more found will set pgm_done and stop cap */
20993 if ( (CAP_INFO == cap_now) && (0 != pgm_done) ) {
20994 stop_capture( WHO, FAIL_NONE );
20995 display_type = D_TIMERS; /* 0 */
20997 #endif
20999 /* make error trend indicator as ^ for more errors and v for less errors
21001 if (qosc > last_qos) tse = '^';
21002 if (qosc < last_qos) tse = 'v';
21003 if (qosc == last_qos) tse= ' '; /* trend is blanked if no change */
21004 last_qos = qosc; /* same bat time */
21006 qc = BG;
21007 qtc = BG;
21008 if (qosc < 99) qc = BY;
21009 if (qosc < 95) qc = BR;
21010 if (qost < 99) qtc = BY;
21011 if (qost < 95) qtc = BR;
21013 /* don't zap info, might be trying to scan bad signal, want to see it.
21014 some guide entries might be able to get thru bad signal muck
21016 if (CAP_INFO != cap_now) {
21017 if ( (arg_zapper != 0) && (utsets > 59 ) && ( qost <= arg_zapper) )
21019 dvrlog( LOG_INFO, "ZAP%02d: %s QOS %lld%% < limit of %d%%",
21020 cap_chan, f, qost, arg_zapper);
21021 if (CAP_TIME == cap_now) reschedule_timer( timer_rec, WHO );
21022 stop_capture( WHO, FAIL_ZAP );
21023 cap_zap = ~0;
21027 /* not counting mpeg2 packet crc yet, but some (all?) is crc-able */
21028 cap_errors = pkt.errors;
21030 pkt_num = (long long)pkt.count; /* snapshot so vals don't change */
21031 pkt_err = (long long)cap_errors;
21032 pkt_mp2 = (long long)pkt.mpeg2;
21034 /* if (pkt.esec != 0) advrlog( LOG_INFO, "pkt esec %d", pkt_esec); */
21036 /* timer changes utsel to seconds remaining in capture, if 'e' toggle */
21037 if ( (CAP_TIME == cap_now) && (cap_etime == 0) )
21038 utsel = ( timer[timer_rec].start
21039 + (timer[timer_rec].len - timer[timer_rec].secdt))
21040 - utsnow;
21042 /* display elapsed doesn't go below zero */
21043 if (utsel < 0) utsel = 0;
21045 /* signal shortcut */
21046 s = &ptc[ cap_chan ].sig;
21048 /* show file progress, if after one second of cap */
21049 /* if (utsets > 1) */
21051 char *tnd = ""; /* color plus modifier to descriptor */
21052 if (test_mode != 0) {
21053 tnd = BT "TEST:";
21054 } else {
21055 tnd = (CAP_TIME == cap_now) ? BW:BG;
21058 /* load guide blink cycle time 2s, even number gets inverse text */
21059 if (CAP_INFO == cap_now) {
21061 /* EPG says program guide, otherwise the file name */
21062 tnd = &bc[7 & utsnow][0];
21063 f = "Events";
21065 /* cable only has program list in vct */
21066 if (0 != arg_cable) f = "Programs";
21068 /* config file sets station network id and call sign */
21069 snprintf( d, sizeof(d)-1, "%s %s %d.%d",
21070 f, s->call, s->ptc, s->pn);
21072 if (0 != arg_dummy) {
21073 snprintf( d, sizeof(d)-1, "%s %s", f, arg_dummyn );
21077 if (CAP_INFO != cap_now) {
21078 snprintf( d, sizeof(d)-1, "%s", f);
21079 #if 0
21080 if (0 != test_mode) {
21081 snprintf( d, sizeof(d)-1, "%s", f);
21083 #endif
21087 /* show current capture file */
21088 aprintf(stderr, DCA0 "%s%-32s", tnd, d);
21092 demoth
21093 if (CAP_INFO == cap_now) return;
21095 /* timer cap is white inverse blink */
21096 if (CAP_TIME == cap_now) {
21097 tc = (0 == (utsnow & 1)) ? BN BV : BW;
21100 aprintf( stderr, "%s%sNet PTC Time %s",
21101 tc, dca.hstat, (0 == cap_etime) ? "Left":"Used" );
21104 aprintf( stderr, "%sNet PTC Time %s",
21105 dca.hstat, (0 == cap_etime) ? "Left":"Used" );
21108 /* manual cap is fg:green bg:black or fg:white bg:green */
21109 if (CAP_USER == cap_now) {
21110 tc = (0 == (utsnow & 1)) ? BG BV : BG;
21111 aprintf( stderr, "%s%sNet PTC Time Used", tc, dca.hstat );
21114 if (CAP_INFO == cap_now) {
21115 tc = (0 == (utsnow & 1)) ? BV : "\033[27m";
21116 aprintf( stderr, "%s%sNet PTC Time Load", tc, dca.hstat );
21119 #if 0
21120 /* FIXME: obsolete this: user doesn't need to hit tab during cap */
21121 /* find which line to use for display for timer or channel list */
21122 snprintf ( csp, 15, "\033[%d;1H" ,
21123 ( D_TIMERS == display_type) ? 3 : 3+get_scanlist_offset( cap_chan ) );
21124 #endif
21126 snprintf( csp, sizeof(csp)-1, "%s", "\033[3;1H" );
21128 *pe = *pt = *pk = 0; /* valgrind */
21130 lltoasc( pe, pkt_err ); /* total transport errors */
21131 lltoasc( pt, pkt_num ); /* ts num */
21132 lltoasc( pk, pkt_mp2 ); /* mpeg2 num */
21134 if (pkt_err < 3) ec = BG;
21136 #if 0
21137 /* field align test toggles values to max and min */
21138 if (utsnow & 1) {
21139 lltoasc( pe, 999999999 );
21140 lltoasc( pt, 999999999 );
21141 lltoasc( pk, 999999999 );
21142 } else {
21143 lltoasc( pe, 0 );
21144 lltoasc( pt, 0 );
21145 lltoasc( pk, 0 );
21147 #endif
21149 /* display rate limiter */
21150 blinky++;
21151 blinky &= 1;
21153 /* 9 digs pkt/err total exceeding 2 hours (46 million pkts/hr) */
21154 aprintf ( stderr,
21155 "%s%3s %3d " /* net and ptc */
21156 "%s%02d:%02d:%02d"
21157 BN " "
21158 BN "%s%3d%% " /* sigcol + sigstr */
21159 BG "%4d/%4d " /* FIFO% */
21160 BR "%s%3d%c " /* QOS%+down=v/up=^ errors/second % */
21161 BR "%s%3lld " /* QOS average% errors/TS pkt % */
21162 "%s%11s " /* pkterr 9 digits, 11 with commas */
21163 BW "%11s " /* pktnum 9 digits, 11 with commas */
21164 BM "%-11s" BN, /* pktcap 9 digits, 11 with commas */
21165 csp, s->sid, (arg_dummy!=0)?0:s->ptc,
21166 tc, (utsel / 3600) % 24, (utsel / 60) % 60, utsel % 60,
21167 s->sig_col, s->strength,
21168 ( fill_max * 100 ) / fifoz, ( fill_max2 * 100 ) / fifoz,
21169 qc, qosc, tse, qtc, qost, /* avoid /0 */
21170 ec, pe, pt, pk);
21172 /* This may need to be changed to dynamic update to prevent cpu drain */
21174 /* capture log updates every 15s */
21175 if (utsdcl < utsnow) {
21176 dump_cap_log( WHO );
21177 utsdcl = utsnow + 15;
21180 #if USE_AUTO_HTML_UPDATE
21181 /* obsoleted: moved these to http requested updates only */
21182 if (utsdsh < utsnow) {
21183 dump_html_stats( WHO );
21184 utsdsh = utsnow + 5;
21187 if (utsdpg < utsnow) {
21188 save_epg( pgm.chan, epg, &pgm, WHO );
21189 utsdpg = utsnow + pgm.refresh;
21190 if (utsdpg <= utsnow) utsdpg += 15;
21192 #endif
21194 /* these reset after displaying current value */
21195 fill_max = 0;
21196 return;
21199 /* calc how long it will take to remove delname */
21200 /* TODO: needs FS detection and sample data for 9G delete times
21201 for the various filesystems that might be used.
21202 for example, ext2 needs 20 seconds to delete 9G,
21203 but XFS only needs about a half a second.
21205 /* static */
21206 void
21207 calc_secdt ( void )
21209 long long file_size;
21210 long long file_time;
21211 struct stat fs1;
21212 int stok;
21214 if ( (timer_rec != -1) && (CAP_TIME == cap_now) )
21215 timer[0].secdt = 5;
21217 /* check if cap del and no -f fast filesystem and more than 1 timer */
21218 if ( (strlen(del_name) != 0) && (arg_fastfs == 0) && (timer_idx > 1) )
21220 /* check for back-to-back, within 60 seconds of next timer for AOS */
21221 if ( (timer[0].start + timer[0].len + 60) >= timer[1].start ) {
21223 5s is minimum for aos check on back to back timers
21224 also, add 13s/9G of secdt len for delete time (my setup)
21226 timer[0].secdt = 5;
21228 /* stat the delname file to see how big it is */
21229 stok = stat( del_name, &fs1);
21230 if (stok != 0) {
21231 advrlog( LOG_INFO,"couldn't stat %s for delete", del_name);
21232 return;
21235 file_size = fs1.st_size;
21236 /* ext3 delete time here is about 500M/sec */
21237 file_time = file_size / 500000000;
21238 /* ext2 is ~20s for 9G */
21240 dvrlog( LOG_INFO,
21241 "calc secdt computed at %lld seconds", file_time);
21243 timer[0].secdt = 5 + file_time;
21245 return;
21249 timer[0].secdt = 5;
21252 /* program guide flags, bottom middle */
21253 /* static */
21254 void
21255 show_event_mask( void )
21257 int i;
21258 i = (columns - 6) >> 1;
21260 aprintf( stderr, BN DCARC "[%c%c%c%c]" CEL, lines, i,
21261 (pgm.hide != 0) ? 'h':' ',
21262 (pgm.age != 0) ? 'a':' ',
21263 (pgm.find != 0) ? 'm':' ',
21264 (epg_www != 0) ? 'w':' '
21268 /* short form of event program guide count/status, bottom left corner */
21269 /* static */
21270 void
21271 show_event_short ( void )
21273 struct sig_s *s;
21274 char pgn[32];
21275 char nvc[16]; /* network or network and vc program # */
21277 int pgi;
21279 /* TESTME: want it to update during capture or only info cap? */
21280 if ( (0 == refresh.estats) && (CAP_NONE == cap_now) )
21281 return;
21283 refresh.estats = 0;
21285 s = &ptc[ cap_chan ].sig;
21287 pgi = pgm.idx; /* pg index after build pg epg */
21289 snprintf( nvc, sizeof(nvc)-1, "%s", s->sid);
21291 if (s->pn > 0) {
21292 snprintf( nvc, sizeof(nvc)-8, "%s.%d", s->sid, s->pn);
21293 pgi = count_pg_pgm( s->pn );
21296 memset(pgn, 0, sizeof(pgn));
21298 /* default event status if nothing else done */
21300 snprintf( pgn, sizeof(pgn)-1, "%s EIT %03d ETT %03d",
21301 nvc, pkt.eit, pkt.ett);
21304 snprintf( pgn, sizeof(pgn)-1, "%4d Events on %s", pgi, nvc);
21306 if (pgm.idt < 1)
21307 snprintf( pgn, sizeof(pgn)-1, "No Events on %s", nvc);
21309 if ( (CAP_NONE != cap_now) && (pkt.eit < 1) ) {
21310 snprintf( pgn, sizeof(pgn)-1, "No EPG for %s", nvc);
21313 /* load guide failure reasons */
21314 switch( pgm.fail )
21316 case PG_FAIL_NONE:
21317 break;
21319 case PG_FAIL_NF:
21320 snprintf( pgn, sizeof(pgn)-1, "No Guide for %s", nvc);
21321 break;
21323 case PG_FAIL_TO:
21324 snprintf( pgn, sizeof(pgn)-1, "Old Guide for %s", nvc);
21325 break;
21327 case PG_FAIL_GE:
21328 snprintf( pgn, sizeof(pgn)-1, "Empty Guide for %s", nvc);
21329 break;
21331 case PG_FAIL_BG:
21332 snprintf( pgn, sizeof(pgn)-1, "Blank Guide for %s", nvc);
21333 break;
21335 default:
21336 break;
21339 aprintf( stderr, BN "%s%-30s" CEL, dca.pstat, pgn);
21340 /* advrlog( LOG_INFO, "pgm fail %d", pgm.fail); */
21342 /* [hamw] in bottom middle of screen hide/age/match/web */
21343 show_event_mask();
21347 show the following at the bottom right, or blank if not found
21348 APVMETR Z xx:xx:xx +nn
21349 APVMETR Z indicates which packets have been seen as valid
21350 Z indicates ATSC system stream time. It may be catastrophically wrong.
21351 +nn is number of seconds out, or OSL Off-Scale Low or OSH Off Scale High
21353 /* static */
21354 void
21355 show_atsc_stats ( void )
21357 char *s, *t;
21358 /* if (display_stat == 0) return; */
21359 if (CAP_NONE == cap_now) return; /* data valid during cap only */
21360 aprintf( stderr, "%s", dca.astat );
21362 /* legend:
21363 A is PAT, P is PMT, V is VCT, M is MGT, E is EIT, T is ETT and R is RRT
21364 bottom line, center right. color P yellow for RC detected in PMT
21366 /***********************************/
21367 s = BN;
21368 t = " ";
21369 if (0 != pkt.fpat) {
21370 t = "A";
21371 if ( pkt.fpat == PAY_READ ) s = BR; /* reading packet */
21372 if ( pkt.fpat == PAY_PARSE) s = BW; /* version diff */
21373 if ( pkt.fpat == PAY_GOOD ) s = BG; /* version same */
21374 if ( pkt.fpat == PAY_DROP ) s = BM; /* payload not handled */
21376 aprintf( stderr, "%s%s", s, t );
21378 /***********************************/
21380 s = BN;
21381 t = " ";
21382 if (0 != pkt.pmt) {
21383 s = BG;
21384 t = "P";
21385 if (0 != pkt.pmtrc) s = BY;
21387 aprintf( stderr, "%s%s", s, t);
21388 /***********************************/
21390 s = BN;
21391 t = " ";
21392 if (0 != pkt.fvct) {
21393 t = "V";
21394 if ( pkt.fvct == PAY_READ ) s = BR; /* reading packet */
21395 if ( pkt.fvct == PAY_PARSE) s = BW; /* version diff */
21396 if ( pkt.fvct == PAY_GOOD ) s = BG; /* version same */
21397 if ( pkt.fvct == PAY_DROP ) s = BM; /* payload not handled */
21399 aprintf( stderr, "%s%s", s, t );
21400 /***********************************/
21402 s = BN;
21403 t = " ";
21404 if (0 != pkt.fmgt) {
21405 t = "M";
21406 if ( pkt.fmgt == PAY_READ ) s = BR; /* reading packet */
21407 if ( pkt.fmgt == PAY_PARSE) s = BW; /* version diff */
21408 if ( pkt.fmgt == PAY_GOOD ) s = BG; /* version same */
21409 if ( pkt.fmgt == PAY_DROP ) s = BM; /* payload not handled */
21411 aprintf( stderr, "%s%s", s, t );
21412 /***********************************/
21414 s = BN;
21415 t = " ";
21416 if (0 != pkt.feit) {
21417 t = "E";
21418 if ( pkt.feit == PAY_READ ) s = BR; /* reading packet */
21419 if ( pkt.feit == PAY_PARSE) s = BW; /* version diff */
21420 if ( pkt.feit == PAY_GOOD ) s = BG; /* version same */
21421 if ( pkt.feit == PAY_DROP ) s = BM; /* payload not handled */
21423 aprintf( stderr, "%s%s", s, t );
21424 /***********************************/
21426 s = BN;
21427 t = " ";
21428 if (0 != pkt.fett) {
21429 t = "T";
21430 if ( pkt.fett == PAY_READ ) s = BR; /* reading packet */
21431 if ( pkt.fett == PAY_PARSE) s = BW; /* version diff */
21432 if ( pkt.fett == PAY_GOOD ) s = BG; /* version same */
21433 if ( pkt.fett == PAY_DROP ) s = BM; /* payload not handled */
21435 aprintf( stderr, "%s%s", s, t);
21436 /***********************************/
21438 s = BN;
21439 t = " ";
21440 if (0 != pkt.frrt) {
21441 t = "R";
21442 if ( pkt.frrt == PAY_READ ) s = BR; /* reading packet */
21443 if ( pkt.frrt == PAY_PARSE) s = BW; /* version diff */
21444 if ( pkt.frrt == PAY_GOOD ) s = BG; /* version same */
21445 if ( pkt.frrt == PAY_DROP ) s = BM; /* payload not handled */
21447 aprintf( stderr, "%s%s", s, t );
21448 /***********************************/
21450 /* multicast status */
21451 s = BN;
21452 t = " ";
21454 #ifdef USE_MCAST
21455 if (0 != udp_mcast) {
21456 t = "U";
21457 s = BY;
21459 #endif
21461 aprintf( stderr, "%s%s", s, t );
21462 /***********************************/
21464 /****************************************************************** ATSC STT */
21466 /* see if time is offscale, 999+/- seconds out is considered off scale */
21467 if (0 != pkt.fstt) {
21468 char d[32];
21469 char o[16];
21471 s = BW;
21472 if (0 != astt.dss) s = BY; /* ATSC daylight savings in yellow */
21474 get_time();
21476 asctime_r( &atsc_stt_tm, d );
21478 /* don't need year and time zone */
21479 d[19] = 0;
21481 snprintf( &o[1], sizeof(o)-1, "%u ", abs(atsc_stt - utsnow) );
21483 o[0] = '+';
21484 if ( (atsc_stt - utsnow) < 0) o[0] = '-';
21485 if ( abs(atsc_stt - utsnow) > 999 ) /* offscale high or low */
21487 /* KHCW: monitor intermittent offscale drift: is 6 hour jump ahead */
21488 advrlog( LOG_INFO, "STT%02d drift %ld",
21489 cap_chan, utsnow - atsc_stt );
21491 o[1] = 'O';
21492 o[2] = 'S';
21493 o[3] = ( o[0] == '+') ? 'H':'L';
21494 s = BR; /* red means time is offscale */
21497 if (0 == (atsc_stt - utsnow)) {
21498 o[0] = ' ';
21499 o[1] = ' ';
21500 o[2] = ' ';
21501 o[3] = ' ';
21504 o[4] = 0;
21505 aprintf( stderr, "%sZ %s %-3s", s, d, o );
21506 } else {
21507 aprintf( stderr, CEL);
21512 /* display all the status indications */
21513 /* capture mode will also display capture status on channel line */
21514 /* wait nonzero does nothing unless time has changed */
21515 /* certain key actions call with wait 0 to redraw sooner */
21516 /* static */
21517 void
21518 show_status ( int wait, char *caller )
21520 get_time();
21522 /* clock/timers update 1 per second */
21523 if ( (0 != wait) && (ptnow == tnow) ) return;
21524 ptnow = tnow;
21526 advrlog( LOG_INFO, "%s(%s)", WHO, caller);
21528 if (refresh.headers > 0) show_headers(0);
21530 /* make all guides update once per second at least */
21531 if ( (CAP_NONE != cap_now) && (0 != wait) ) {
21532 refresh.log = 1;
21533 refresh.mgt = 1;
21534 refresh.epg = 1;
21535 refresh.vct = 1;
21538 /* auto update config only when not capturing, so you don't
21539 erase/restart any currently in progress timers
21541 if (0 == arg_capture)
21542 /* if ( (CAP_INFO == cap_now) || (CAP_NONE == cap_now) ) */
21544 /* does nothing if config file didn't change */
21545 test_config(0, WHO);
21547 /* NOTE: force cfg reload when Local Time changes to/from Daylight/Standard
21548 this corrects the timers, but will erase unsaved timers
21549 2am timers will be wrong, not much can do there is no 2am
21551 if (tloc.tm_isdst != pisdst)
21553 pisdst = tloc.tm_isdst;
21554 advrlog( LOG_INFO, "load cfg @ DST change");
21555 test_config(1, "show_status DST change");
21560 refresh.pstats = 1;
21561 refresh.vstats = 1;
21562 refresh.estats = 1;
21565 test_termsize(); /* see if any environment change happened */
21567 // aprintf( stderr, SCP ); /* save cursor position for user input */
21569 /* divide by 0 in here somewhere */
21570 calc_secdt(); /* check for updates to secdt */
21572 show_volume_status(); /* used/free space, calc timer total usage */
21574 if (0 != wait) refresh.clock = 1;
21576 show_clock(); /* wall clock */
21577 show_cap_status(); /* time elapsed, QOS */
21579 show_FE_status(); /* faux LEDs */
21581 show_timers(); /* whats on the menu, also calls check timer */
21582 show_packet_stats(); /* counts of everything */
21583 show_vc_selection();
21584 show_event_short(); /* show filtered event and total event count */
21585 show_atsc_stats(); /* atsc tables rx + system (stream) time */
21587 /* if no device show that, if found device but test mode say no output */
21588 if (0 != test_mode) {
21589 aprintf( stderr, DCA_SPACE BN " " "\033[1;41;37m" " TESTING... ");
21591 if (in_file < 3) {
21592 aprintf( stderr, "NO DEVICE " BN " " );
21593 } else {
21594 aprintf( stderr, "NO OUTPUT " BN " " );
21598 /* see if scan is disabled, if it is, make it stay on one channel */
21599 if (scan_sig == 0) scan_one = ~0;
21600 aprintf( stderr, RCP );
21603 /* help shows uptime */
21604 /* static */
21605 void
21606 show_uptime ( void )
21608 int up, d, h, m, s, f, a, i;
21609 char to1[32];
21610 struct sig_s *ss;
21612 ss = &ptc[ cap_chan ].sig;
21614 up = utsnow - utc_up;
21615 s = up % 60;
21616 up /= 60;
21617 m = up % 60;
21618 up /= 60;
21619 h = up % 24;
21620 up /= 24;
21621 d = up;
21623 f = open( "/etc/hosts", O_RDONLY );
21624 if (0 < f) {
21625 close(f);
21626 } else {
21627 dvrlog( LOG_INFO, "%s file counter needs /etc/hosts", WHO );
21630 a = 0;
21631 for ( i = 0; i < ALLOC_LIMIT; i++ )
21632 if (NULL != alloc_list[i].p)
21633 a += alloc_list[ i ].z;
21635 aprintf( stderr, welcome );
21637 aprintf( stderr, BW "Uptime %d days %d hours %d minutes %d seconds "
21638 BN "%d files %d allocs\n",
21639 d, h, m, s, f - 1, a );
21640 aprintf( stderr, "Timers %d/%d Searches %d/%d Spams %d/%d "
21641 "Events: sel %d tot %d max %d\n",
21642 timer_idx, TIMER_LIST_MAX,
21643 search_idx, SEARCH_LIST_MAX,
21644 spam_idx, SPAM_LIST_MAX,
21645 pgm.idx, pgm.idt, PGZ);
21647 #ifdef USE_POWERDOWN
21648 if (0 != arg_tmpfs) {
21649 text_time( to1, utspdd, 19);
21650 aprintf( stderr, BN "Powerdown %s ", to1 );
21652 #endif
21654 if (0 != ss->pgto) {
21655 text_time( to1, ss->pgmt, 19 );
21657 aprintf( stderr, BN "Auto EPG Timeout %02d %s", cap_chan, to1 );
21659 aprintf( stderr, "\n" );
21661 /* FIXME: make list scrollable */
21662 /* display short keyhelp text until next timer,
21663 * or hit a key to get out sooner
21665 /* static */
21666 void
21667 show_keyhelp ( char *keyhelptext )
21669 int utsp = 0;
21670 int old_stat, old_type;
21671 int k, m;
21673 /* save pkt stats display status */
21675 old_stat = display_stat;
21676 old_type = display_type;
21678 display_type = D_HELP;
21679 display_stat = 0;
21681 get_time();
21683 aprintf( stderr, DCA_HELP CCE );
21684 show_uptime();
21686 aprintf( stderr, BN "%s", keyhelptext);
21688 utsp = utsnow;
21690 m = 0;
21692 while (1) {
21693 k = console_getch();
21695 /* if display type changes, need to get out */
21696 if (D_HELP != display_type) {
21697 aprintf( stderr, DCA_HELP CCE );
21698 break;
21701 /* m key shows memory usage details, or refreshes it */
21702 m = 0;
21703 if ('m' == k) m = ~m;
21705 if (0 != m) {
21706 aprintf( stderr, DCA_HELP CCE "\n" );
21707 show_mem_use();
21710 /* any key but m will get out */
21711 if ('m' != k) if (0 != k) break;
21713 /* reset by timer activity instead */
21714 get_time();
21716 show_status( 1, WHO );
21717 nanosleep( &msg_sleep, NULL );
21721 /* restore display and status */
21722 display_type = old_type;
21723 display_stat = old_stat;
21724 test_termsize(); /* recalc user_lines and dca.* addresses */
21726 /* don't overwrite headers and you dont have to show them again */
21727 /* show_headers(1); */
21728 set_refresh();
21729 return;
21733 /* sort copy of master guide table list by table type */
21734 /* static */
21735 void
21736 sort_mgs ( void )
21738 int i,j;
21739 struct mg_s t;
21740 struct mg_s *m1, *m2;
21742 if (mg_idx < 2) return;
21744 for (i = 0; i < (mg_idx - 1); i++) {
21745 for (j = i + 1; j < mg_idx; j++) {
21746 m1 = &mgs[i];
21747 m2 = &mgs[j];
21749 if ( m1->tt <= m2->tt ) continue;
21751 memcpy(&t, m1, sizeof(t));
21752 memcpy(m1, m2, sizeof(t));
21753 memcpy(m2, &t, sizeof(t));
21759 /* add string at *s to mgt_text, if it will fit, otherwise do nothing */
21760 /* static */
21761 void
21762 add_mgt_text ( char *s )
21764 char *t;
21766 /* ignore blanks */
21767 if ( 0 == strlen(s) ) return;
21769 /* NOTE: silently fails when past line max */
21770 if ( mgt_idx < MGT_ROWS) {
21771 t = &mgt_text[ mgt_idx ][0];
21772 snprintf( t, strlen(s)+1, "%s", s);
21773 mgt_idx++;
21777 /* show mgt table, has list of pids, like VCT, EIT & ETT pids */
21778 /* static */
21779 void
21780 build_mgt_text ( void )
21782 int i;
21783 /* int j = 0; */
21784 unsigned int tt, pid, vn, dl, nb, nbt, nbt1, nb1, vn1;
21785 char s[128]; /* enough for 80 column plus 5 ansi codes */
21786 char *tc; /* text color */
21787 char *ze; /* size error color */
21788 char *ve; /* version error color */
21789 char po, pv, pz;
21790 char tx[16]; /* table x */
21792 int one_eit; /* fixes display of section titles */
21793 int one_ett; /* even if eit0 ett0 or rrt1 missing */
21794 int one_rrt; /* but others than first one exist */
21796 struct mg_s *m1;
21798 /* (non compliant missing ETT0 KTMD fix) */
21799 one_eit = one_ett = one_rrt = ~0;
21801 memcpy( mgs, mg, sizeof(mgs) );
21802 sort_mgs();
21804 memset( s, 0, sizeof(s));
21805 memset( tx, 0, sizeof(tx));
21806 memset( mgt_text, 0, sizeof( mgt_text ));
21807 mgt_idx = 0;
21809 /* hold down until version not 0xFF, i.e. a live MGT */
21810 if (mgt_vn == 0xFF) {
21811 display_type = D_TIMERS;
21812 return;
21815 snprintf( s, sizeof(s)-1,
21816 BW "MGT Version %02X DLen %02X" BN, mgt_vn, mgt_dl);
21817 add_mgt_text( s );
21819 #if 0
21820 snprintf( s, sizeof(s)-1,
21821 BM "Next update is scheduled for %s", date_next);
21822 add_mgt_text( s );
21823 #endif
21825 /* reset number of bytes total for tables
21826 (does not take into account multi-EIT/ETT)
21828 nbt = nbt1 = 0;
21830 tc = BN;
21831 for (i = 0; i < mg_idx; i++) {
21833 m1 = &mgs[i];
21835 tt = m1->tt;
21836 pid = m1->pid;
21838 /* descriptor is always blank? not parsed: MRD length is display only */
21839 dl = m1->dl; /* MPEG registration descriptor length */
21841 /* vn and size provided by mgt */
21842 nb = m1->nb;
21843 vn = m1->vn;
21845 /* parsed data from last table of each type */
21846 nb1 = m1->nb1;
21847 vn1 = m1->vn1;
21849 /* FUTURE TODO: number of bytes to allocate for table, but there are problems.
21850 Some stations do not send correct values here. Do not depend on these!
21851 The ATSC spec is a bit fuzzy on the implementation of numbytes, so these
21852 values are for display only, and should not actually be used.
21853 Red text marks the items that seem to be different from A/65b.
21856 /* add MGT table size */
21857 nbt += nb;
21858 nbt1 += nb1;
21860 if (tt == 0) {
21861 add_mgt_text( BY " Terrestrial Virtual Channel0");
21862 tc = BY;
21863 snprintf( tx, sizeof(tx)-1, "TVCTC0");
21865 if (tt == 1) {
21866 add_mgt_text( BY " Terrestrial Virtual Channel1");
21867 tc = BY;
21868 snprintf( tx, sizeof(tx)-1, "TVCTC1");
21870 if (tt == 2) {
21871 add_mgt_text( BY " Cable Virtual Channel0");
21872 tc = BY;
21873 snprintf( tx, sizeof(tx)-1, "CVCTC0");
21875 if (tt == 3) {
21876 add_mgt_text( BY " Cable Virtual Channel1");
21877 tc = BY;
21878 snprintf( tx, sizeof(tx)-1, "CVCTC1");
21880 if (tt == 4) {
21881 add_mgt_text( BN " PTC Extended Text");
21882 tc = BN;
21883 snprintf( tx, sizeof(tx)-1, "PTCET ");
21886 /* have never seen this implemented in broadcast from 2004 through 2007 */
21887 if (tt == 5) {
21888 add_mgt_text( BM " Directed Channel Change Selection Code Table:");
21889 tc = BM;
21890 snprintf( tx, sizeof(tx)-1, "DCCSCT");
21893 /* single entry tables do not need to loop on multiple values */
21894 if (tt < 6) {
21895 po = ' ';
21896 pv = '=';
21897 pz = '>';
21898 ve = "";
21899 ze = "";
21900 if (vn != vn1) { pv = '!'; ve = BR; }
21902 /* insane number of bytes gets error flag */
21903 if (4096 < nb) { pz = '%'; ze = BR; }
21905 /* received total exceeding mgt nb also gets error flag */
21906 if (nb < nb1) { pz = '<'; ze = BR; }
21908 #if 1
21909 snprintf( s, sizeof(s)-1,
21910 "%s %s%c Table ID %04X PID %04X NB %s%04X %c %04X%s"
21911 " VN %s%02X %c %02X%s DL %X",
21912 tc, tx, po, tt, pid, ze, nb, pz, nb1, tc,
21913 ve, vn, pv, vn1, tc, dl );
21914 #else
21916 snprintf( s, sizeof(s)-1,
21917 "%s %s%c Table ID %04X PID %04X NB %s%04X%s"
21918 " VN %02X DL %X",
21919 tc, tx, po, tt, pid, ze, nb, tc,
21920 vn, dl );
21921 #endif
21923 add_mgt_text( s );
21924 continue;
21927 /* EIT-n ? */
21928 if ( (tt >= 0x100) && (tt < 0x17F) ) {
21929 po = ' ';
21930 tc = BN;
21931 if (0 != one_eit) add_mgt_text( BG " Event Information Tables:");
21932 one_eit = 0;
21934 /* FIXME: make decision: * is payok = 0 color is PAY_GOOD or vice versa */
21935 /* EIT is unmarked pending when payok clear */
21936 if (NULL != eit) {
21937 if (eit[0x7F & tt].payok == 0) {
21938 po = '*';
21939 tc = BG;
21942 if (CAP_NONE == cap_now) {
21943 po = ' ';
21944 if (0xFF != mgs[i].vn) {
21945 po = '*';
21946 tc = BG;
21950 snprintf( tx, sizeof(tx)-1, "EIT-%02X%c", 0x7F & tt, po);
21953 /* ETT-n ? */
21954 if ( (tt >= 0x200) && (tt <= 0x27F) ) {
21955 po = ' ';
21956 tc = BN;
21957 if ( 0 != one_ett ) add_mgt_text( BC " Extended Text Tables:");
21958 one_ett = 0;
21960 /* ETT is unmarked pending when both EIT and ETT payok clear */
21961 if (NULL != eit) {
21962 if ( ett[ 0x7F & tt ].payok == 0) {
21963 if ( eit[ 0x7F & tt ].payok == 0) {
21964 po = '*';
21965 tc = BC;
21970 if (CAP_NONE == cap_now) {
21971 po = ' ';
21972 if (0xFF != mgs[i].vn) {
21973 po = '*';
21974 tc = BC;
21978 snprintf( tx, sizeof(tx)-1, "ETT-%02X%c", 0x7F & tt, po);
21981 /* RRT-n ? */
21982 if ( (tt >= 0x301) && (tt <= 0x3FF) ) {
21983 po = ' ';
21984 tc = BN;
21985 if ( 0 != one_rrt ) add_mgt_text( BM " Rating Region Tables:");
21986 one_rrt = 0;
21988 /* RRT is unmarked pending when payok clear */
21989 if (rrt.payok == 0) {
21990 po = '*';
21991 tc = BM;
21994 if (CAP_NONE == cap_now) {
21995 if (0xFF != mgs[i].vn) {
21996 po = '*';
21997 tc = BM;
22001 snprintf( tx, sizeof(tx)-1, "RRT-%02X%c", 0xFF & tt, po);
22004 #if 0
22005 /* have not seen these from 2004 through 2007. maybe some cable systems? */
22006 if ( (tt >= 0x1400) && (tt <= 0x14FF) ) {
22007 if (tt == 0x1400) {
22008 add_mgt_text( BM " Directed Channel Change Tables:");
22009 tc = BM;
22010 snprintf( tx, sizeof(tx)-1, "DCCT-%02X", 0xFF & tt );
22013 #endif
22015 if (tt > 0x2000) return; /* sanity check */
22017 po = ' ';
22018 pv = '=';
22019 pz = '>';
22020 ve = "";
22021 ze = "";
22022 if (vn != vn1) { pv = '!'; ve = BR; }
22023 if (0xFF == vn1) tc = BN;
22025 /* insane number of bytes gets error flag */
22026 if (4096 < nb) { pz = '%'; ze = BR; }
22028 /* received total exceeding mgt nb also gets error flag */
22029 if (nb < nb1) { pz = '<'; ze = BR; }
22031 snprintf( s, sizeof(s)-1,
22033 "%s %s Table ID %04X PID %04X NB %s%04X %c %04X%s"
22034 " VN %s%02X %c %02X%s DL %02X",
22035 tc, tx, tt, pid, ze, nb, pz, nb1, tc,
22036 ve, vn, pv, vn1, tc, dl );
22038 /* if EIT, show hours ahead of currennt SEC3HR meridian */
22039 if ( (tt >= 0x100) && (tt < 0x17F) ) {
22040 char *p;
22041 p = s + strlen(s);
22042 sprintf( p, " Hours %3d", 3 * (tt - 0xFF));
22044 add_mgt_text( s );
22047 snprintf( s, sizeof(s)-1,
22048 BW " Master Guide Table: MGT bytes %08X RX bytes %08X", nbt, nbt1);
22049 add_mgt_text( s );
22050 if (mg_eit == 0) {
22051 add_mgt_text( BR BL "No EIT in MGT, Program Guide is Disabled" BN );
22053 add_mgt_text(" ");
22057 /* Shows Master Guide Table to see what is available with ATSC PSIP */
22058 /* static */
22059 void
22060 show_master_guide_table ( void )
22062 int i, j, tp;
22063 unsigned char key;
22064 int mo; /* mgt list offset */
22065 unsigned char vn;
22066 char *tt, pgm_id[64];
22067 struct sig_s *s;
22069 vn = mgt_vn; /* vn when guide started */
22071 key = 0;
22072 mo = 0;
22074 /* nothing to do */
22075 if (pkt.fmgt == 0) {
22076 dvrlog( LOG_INFO, "no master guide to show, pkt.fmgt 0");
22077 return;
22079 refresh.mgt = 1;
22081 if (mgt_vn != 0xFF) {
22082 dvrlog( LOG_INFO, "show master guide calls build mgt text");
22083 build_mgt_text();
22084 } else {
22085 dvrlog( LOG_INFO, "no master guide to show, mgt_vn %02X", mgt_vn);
22088 s = &ptc[cap_chan].sig;
22090 /* NOTE: user lines is never actually reached, but stop loop if it is */
22091 for (i=0; i<user_lines;) /* i gets incremented modulo user lines */
22093 show_status( 1, WHO );
22095 if ( D_MGT != display_type) break;
22097 j = i + mo;
22099 if (0 != refresh.mgt) {
22100 if (CAP_NONE == cap_now) {
22101 tt = "ATSC MASTER GUIDE TABLE";
22102 tp = columns - strlen(tt);
22103 tp >>= 1;
22104 if (tp < 0) tp = 0;
22105 /* replace signal header */
22106 aprintf( stderr, DCARC CEL, 2, 1);
22107 aprintf( stderr, DCARC BW "%s" BN, 2, tp, tt);
22109 snprintf( pgm_id, sizeof(pgm_id),
22110 "LCN %d PTC %d.%d %s %s",
22111 s->chan, s->ptc, s->pn, s->call, s->sid);
22113 tp = columns - strlen(pgm_id);
22114 tp >>= 1;
22115 if (tp < 0) tp = 0;
22116 /* and strength bar */
22117 aprintf( stderr, DCARC CEL, 3, 1);
22118 aprintf( stderr, DCARC BY "%s", 3, tp, pgm_id);
22121 aprintf( stderr, "\033[%d;1H" "%s" CEL, 5+i, &mgt_text[j][0] );
22124 i++;
22125 i %= user_lines;
22126 key = 0;
22127 /* end of list redraws header and put cursor where needs to be */
22128 if (0 == i) {
22130 if (0 != refresh.mgt) {
22132 if (mgt_vn != 0xFF) {
22134 aprintf( stderr, DCA_MGT BW
22135 "Master Guide Table "
22136 BW "Keys: " BN " [g] exit "
22137 BW "Status: " BG "* current, " BR BL "! error" BN
22138 SCI CEL);
22140 build_mgt_text(); /* might eat cpu */
22144 refresh.mgt--;
22145 if (refresh.mgt < 0) refresh.mgt = 0;
22147 console_getch_fn( &key );
22148 nanosleep( &console_read_sleep, NULL );
22149 if (0 == key) continue;
22151 refresh.mgt = 1;
22153 if ( 'g' == key ) break; /* g to get in, g to get out */
22155 if ( '?' == key ) show_keyhelp( mgtkeyhelp );
22157 if ( KB_UP == key ) mo -= 1;
22158 if ( '=' == key ) mo -= 1;
22160 if ( KB_DN == key ) mo += 1;
22161 if ( '\'' == key ) mo += 1;
22163 if ( KB_PU == key ) mo -= user_lines;
22164 if ( KB_PD == key ) mo += user_lines;
22166 if (mo < 0) mo = 0; /* sanity limit */
22167 if ( (mo+user_lines) >= mgt_idx) mo = mgt_idx - user_lines;
22168 if (mo < 0) mo = 0; /* sanity limit */
22174 /* add string at *s to tvct_text, if it will fit, otherwise do nothing */
22175 /* use " " by itself when calling this to add NL, and don't put NLs in text */
22176 /* static */
22177 void
22178 add_tvct_text ( char *s)
22180 char *t;
22182 /* ignore blanks */
22183 if ( 0 == s[0] ) return;
22185 /* NOTE: silently fails when past line max */
22186 if (tvct_idx < TVCT_ROWS) {
22187 t = &tvct_text[tvct_idx][0];
22188 snprintf( t, strlen(s)+1, "%s", s);
22189 tvct_idx++;
22194 Used for VCT Extended Channel Name, PMT Component Name and Content Advisory.
22195 Called by build vct text. Output is limited to VCT_MSS_MAX bytes.
22197 payload relative pointer is r, destination for uncompressed text is t
22199 #define VCT_MSS_MAX 32
22200 /* static */
22201 void
22202 build_vct_mss ( unsigned char *r, char *t )
22204 unsigned int nstr, nseg, comp, mode, nchr;
22205 unsigned int i,j;
22206 char d[512]; /* text temp destination */
22207 char lang[4];
22209 nstr = r[0];
22210 t[0] = 0; /* make it blank at the least */
22211 for (i = 0; i < nstr; i++) {
22212 memcpy(lang, &r[1], 3);
22213 lang[3] = 0;
22214 r += 4;
22215 nseg = *r++;
22216 for (j = 0; j < nseg; j++ ) {
22217 comp = *r++;
22218 mode = *r++;
22219 nchr = *r++;
22221 if (nchr > 0) {
22222 /* no compression and unicode page 0 */
22223 if ( (0 == comp) && (0 == mode) ) {
22224 nchr++;
22225 /* r does not have nul term */
22226 if (nchr > 256) nchr = 256;
22227 astrncpy( d, (char *)r, nchr );
22228 /* +8 for [eng][] */
22229 snprintf( t, VCT_MSS_MAX, "[%s] [%s]", lang, d);
22232 /* KPXB requires non-strict mode because they got it wrong here too */
22233 if ((0 == arg_strict) || ((0 != arg_strict) && (0xFF == mode)))
22235 if ( (comp == 1) || (comp == 2) ) {
22236 if (nchr > sizeof(d)) nchr = sizeof(d);
22237 huffman_decode( d, r, sizeof(d)-1, nchr, comp);
22238 snprintf( t, VCT_MSS_MAX, "[%s] [%s]", lang, d);
22239 /* +8 for [eng][] */
22242 /* something else */
22243 if ( (comp > 2) || ((mode > 0) && (mode < 255)) )
22245 snprintf( t, VCT_MSS_MAX, "[%s] L%02X C%02X M%02X",
22246 lang, nchr, comp, mode);
22248 } else {
22249 snprintf( t, VCT_MSS_MAX, "[%s] L%02X C%02X M%02X",
22250 lang, nchr, comp, mode);
22258 parse MPEG2 PMT descriptor and add lines of text to vct_text.
22259 these are shown after PMT PID line in [v] key Virtual Channel page.
22261 /* static */
22262 void
22263 build_mpeg2_descriptor ( unsigned char *r, unsigned int indent )
22265 char *t;
22266 char s[128];
22267 char ct[512];
22268 /* indent text */
22269 char it[16];
22270 unsigned char n, l;
22271 unsigned int i, j;
22272 char *tc = BM; /* tag color */
22274 memset( it, 0, sizeof(it) );
22275 if ( (0 != indent) && (indent < sizeof(it)) ) {
22276 memset(it, ' ', indent);
22279 t = BR"DESCRIPTOR UNPARSED"BN;
22280 n = r[0]; /* descriptor number */
22281 l = r[1]; /* desriptor length */
22283 if ((0 == n) || (0 == l)) return; /* bad descriptor */
22285 snprintf( s, sizeof(s)-1, BW " Tag %02X DLen %02X" BN, n, l);
22286 add_tvct_text( s );
22287 return;
22289 r += 2; /* descriptor data starts here */
22291 /********************************************************** MPEG descriptors */
22292 switch ( n ) {
22294 case 2:
22295 t = "Video Stream";
22297 unsigned char mfr, frc, m1o, cpf, spf;
22298 char *frct, *mfrt;
22299 snprintf( s, sizeof(s)-1,
22300 "%s%sTag %02X DLen %02X %s: "BN, it, tc, n, l, t);
22301 add_tvct_text( s );
22303 mfr = 1 & (r[0]>>7); /* single or multiple frame rates */
22304 frc = 15 & (r[0]>>3); /* frame rate code */
22305 m1o = 1 & (r[0]>>2); /* mpeg 1 only */
22306 cpf = 1 & (r[0]>>1); /* constrained parameters flag */
22307 spf = 1 & r[0]; /* still picture flag */
22309 frct = "Reserved";
22310 if (0 == mfr) {
22311 mfrt = "Single";
22312 if (0 == frc) frct = "Forbidden";
22313 if (1 == frc) frct = "23.976";
22314 if (2 == frc) frct = "24";
22315 if (3 == frc) frct = "25";
22316 if (4 == frc) frct = "29.97";
22317 if (5 == frc) frct = "30";
22318 if (6 == frc) frct = "50";
22319 if (7 == frc) frct = "59.94";
22320 if (8 == frc) frct = "60";
22321 } else {
22322 mfrt = "Multiple";
22323 if (0 == frc) frct = "Forbidden";
22324 if (1 == frc) frct = "23.976";
22325 if (2 == frc) frct = "24/23.976";
22326 if (3 == frc) frct = "25";
22327 if (4 == frc) frct = "29.97/23.976";
22328 if (5 == frc) frct = "30/23.976/24/29.97";
22329 if (6 == frc) frct = "50 25";
22330 if (7 == frc) frct = "59.94/23.976/29.97";
22331 if (8 == frc) frct = "60/23.976/24/29.97/30/59.94";
22334 snprintf( s, sizeof(s)-1, "%s %s Frame Rate %s", it, mfrt, frct);
22335 add_tvct_text( s );
22337 snprintf( s, sizeof(s)-1,
22338 "%s MPEG1 Only %u Constrained %u Still Picture %u",
22339 it, m1o, cpf, spf);
22340 add_tvct_text( s );
22342 /* not likely for terrestrial ATSC or cable */
22343 if (0 != m1o) {
22344 /* check reserved bits */
22345 if (0x1F == (0x1F & r[2])) {
22346 unsigned char pli, cf, fre;
22347 pli = r[1];
22348 cf = 3 & (r[2]>>6);
22349 fre = 1 & (r[2]>>5);
22350 snprintf( s, sizeof(s)-1,
22351 "%s MPEG1 Profile and Level %02X "
22352 "Chroma Format %01X Frate Rate Ext %1X",
22353 it, pli, cf, fre);
22354 add_tvct_text( s );
22357 return;
22359 break;
22361 /* MPEG audio not seen in ATSC, see case 0x81 for A/52 audio instead */
22362 case 3:
22363 t = "MPEG2 Audio Stream";
22364 break;
22366 /* not seen in ATSC */
22367 case 4:
22368 t = "Hierarchy";
22369 break;
22371 case 5:
22372 t = "Registration";
22373 snprintf( s, sizeof(s)-1,
22374 "%s%sTag %02X DLen %02X %s: " BN
22375 " Format ID [%c%c%c%c], ID Len %03X",
22376 it, tc, n, l, t, r[0], r[1], r[2], r[3], l-4);
22377 add_tvct_text( s );
22378 return;
22379 break;
22381 /* more or less where you can cut. usually alignment type 1 */
22382 case 6:
22383 t = "Stream Alignment";
22385 char *sa;
22386 unsigned char at = 0;
22387 sa = "Reserved";
22388 if (l == 1) at = r[0];
22389 if (1 == at) sa = "SLI, PIC, GOP or SEQ"; /* seen in ATSC */
22390 if (2 == at) sa = "PIC, GOP or SEQ";
22391 if (3 == at) sa = "GOP or SEQ";
22392 if (4 == at) sa = "SEQ";
22393 snprintf( s, sizeof(s)-1,
22394 "%s%sTag %02X DLen %02X %s: " BN "%02X %s",
22395 it, tc, n, l, t, at, sa);
22396 add_tvct_text( s );
22397 return;
22399 break;
22401 /* not seen in ATSC, but sure could be useful */
22402 case 7:
22403 t = "Target Background Grid";
22404 break; /* write me if ever seen */
22406 /* not seen in ATSC, but sure could be useful */
22407 case 8:
22408 t = "Video Window";
22409 break; /* write me if ever seen */
22411 /* not seen in ATSC ? */
22412 case 9:
22413 t = "Conditional Access";
22414 if ( 0xE0 == (0xE0 & r[2] ) ) { /* reserved bits check */
22415 unsigned short casid, capid;
22416 casid = (r[0]<<8) | r[1];
22417 capid = 0x1FFF & ( (r[2]<<8) | r[3] );
22418 r += 6; /* skip to private data */
22419 snprintf( s, sizeof(s)-1, "%s%sTag %02X DLen %02X %s: "BN
22420 " CA system ID %04X CA PID %04X",
22421 it, BR, n, l, t, casid, capid);
22422 add_tvct_text( s );
22423 // if (l > 6) add_tvct_text( " has private data" );
22424 return;
22426 break;
22428 case 10:
22429 t = "ISO 639 Language(s)";
22431 /* no reserved bits to check */
22432 char lang[4];
22433 unsigned char at; /* audio type */
22434 char *lat;
22436 spec mentions can be multiple audio lang descriptors here
22437 but this only gets last 3 bytes
22439 for (i = 0; i < (l - 1); ) { /* manually bump i */
22440 memcpy( lang, r, 3 );
22441 lang[3] = 0;
22442 r += 3;
22443 i += 3;
22446 /* last byte is audio type */
22447 at = r[0];
22448 lat = "Reserved";
22449 if (0 == at) lat = "ATSC Normal Audio";
22450 if (1 == at) lat = "Clean Effects";
22451 if (2 == at) lat = "Hearing Impaired";
22452 if (3 == at) lat = "Visual Impaired Commentary";
22453 snprintf( s, sizeof(s)-1,
22454 "%s%sTag %02X DLen %02X %s: " BN "[%s] %02X %s",
22455 it, tc, n, l, t, lang, at, lat);
22456 add_tvct_text( s );
22457 return;
22459 break;
22461 case 11:
22462 t = "System Clock";
22463 /* reserved bits must be set to qualify for rest */
22464 if ( (0x40 == (0x40 & r[0]))
22465 && (0x1F == (0x1F & r[1])) ) {
22466 unsigned char ecri, cai, cae;
22467 ecri = 1 & (r[0]>>7); /* external clock reference indicator */
22468 cai = 0x3F & r[0]; /* clock accuracy integer */
22469 cae = 7 & (r[1]>>5); /* clock accuracy exponent */
22470 snprintf( s, sizeof(s)-1, "%s%sTag %02X DLen %02X %s: " BN
22471 "External %s, Accuracy %u^%u",
22472 it, tc, n, l, t,
22473 (0==ecri)?"NO":"YES",
22474 cai, cae);
22475 add_tvct_text( s );
22476 return;
22478 break;
22480 case 12:
22481 t = "Multiplex Buffer Utilization";
22483 unsigned char bv;
22484 unsigned short ltwl, ltwu;
22486 ltwl = ltwu = 0;
22488 bv = 1 & (r[0] >> 7); /* bound valid flag */
22490 /* valid and reserved set? */
22491 if ( (1 == bv) && (0x80 == (0x80 & r[2])) ) {
22492 ltwl = 0x7FFF & ( (r[0]<<8) | r[1] );
22493 ltwu = 0x7FFF & ( (r[2]<<8) | r[3] );
22495 snprintf(s, sizeof(s)-1,
22496 "%s%sTag %02X DLen %02X %s: " BN "Valid %u",
22497 it, tc, n, l, t, bv);
22498 add_tvct_text( s );
22500 /* don't bother if not valid */
22501 if (1 == bv) {
22502 snprintf( s, sizeof(s)-1,
22503 "%s Legal Time Window Lower Bound %u Upper Bound %u",
22504 it, ltwl, ltwu);
22505 add_tvct_text( s );
22507 return;
22509 break;
22511 case 13:
22512 t = "Copyright";
22513 snprintf(s, sizeof(s)-1,"%s%sTag %02X DLen %02X %s",
22514 it, tc, n,l,t);
22515 add_tvct_text( s );
22516 return;
22517 break;
22519 case 14:
22520 t = "Maximum Bitrate";
22521 if ( 0xC0 == (0xC0 & r[0]) ) {
22522 unsigned int mbr;
22523 char m[32];
22524 mbr = 0x3FFFFF & ( ( r[0] << 8 ) | r[1] );
22525 lltoasc( m, (long long) mbr * 400LL );
22526 snprintf( s, sizeof(s)-1,
22527 "%s%sTag %02X DLen %02X "BN"%s: %s bits/second",
22528 it, tc, n, l, t, m);
22529 add_tvct_text( s );
22530 return;
22532 break;
22534 /* private data, not in 13818-1 draft 1994 jun 10, in nov 13 */
22535 case 15:
22537 unsigned char pd[16];
22538 t = "Private Data";
22539 if (0 != l) memcpy( pd, r, l);
22540 pd[l] = 0;
22541 snprintf( s, sizeof(s)-1, "%s%sTag %02X DLen %02X %s: " BN
22542 "%s", it, tc, n, l, t, pd);
22543 add_tvct_text( s );
22544 return;
22546 break;
22548 /* smoothing buffer, not in 13818-1 draft 1994 jun 10, in nov 13 */
22549 case 0x10:
22550 t = "Smoothing Buffer";
22552 unsigned int sblr, sbz;
22553 char lrt[32];
22554 if ( (0xC0 == (0xC0 & r[0]))
22555 && (0xC0 == (0xC0 & r[3])) ) {
22556 sblr = 0x3FFFFF & ( (r[0]<<16) | (r[1]<<8) | r[2] );
22557 sbz = 0x3FFFFF & ( (r[3]<<16) | (r[4]<<8) | r[5] );
22558 lltoasc( lrt, sblr * 400 );
22559 snprintf( s, sizeof(s)-1,
22560 "%s%sTag %02X DLen %02X %s: " BN
22561 "Leak Rate %s bits/s Size %u",
22562 it, tc, n, l, t, lrt, sbz);
22563 add_tvct_text( s );
22565 return;
22567 break;
22569 case 0x11:
22570 t = "System Target Decoder";
22571 break;
22573 case 0x12:
22574 t = "I P B Frame Layout";
22575 break;
22577 /************************************************************ ATSC specific */
22578 case 0x52:
22579 t = "SCTE-35 Cue ID";
22580 break;
22582 case 0x80:
22583 t = "Stuffing";
22584 break;
22586 case 0x81:
22587 t = "AC-3 Audio";
22589 /* unsigned char lc, lc2, mid, asvcf, tl, tc, tb; */
22590 unsigned char src, bsid, brc, sm, bsm, nc, fsv;
22591 char *srt, *brt, *brt1, *smt, *nct, *lft, *bsmt;
22592 src = 0x07 & (r[0]>>5);
22593 srt = "Reserved";
22594 if (0 == src) srt = "48 kHz";
22595 if (1 == src) srt = "44.1 kHz";
22596 if (2 == src) srt = "32 kHz";
22597 /* must be the can't decide section */
22598 if (4 == src) srt = "48 or 44.1 kHz";
22599 if (5 == src) srt = "48 or 32 kHz";
22600 if (6 == src) srt = "44.1 or 32 kHz";
22601 if (7 == src) srt = "48, 44.1 or 32 kHz";
22603 bsid= 0x1F & r[0];
22604 brc = 0x3F & (r[1]>>2); /* bit rate code */
22605 sm = 0x03 & r[1]; /* surround mode, docs also name it dsumod? */
22606 bsm = 0x07 & (r[2]>>5);
22607 nc = 0x0F & (r[2]>>4);
22608 fsv = 0x01 & r[2]; /* full audio main service */
22610 brt1 = "Exact";
22611 if (0x20 == (0x20 & brc)) brt1 = "Maximum";
22612 brc &= 0x1F; /* limit it now brt1 has been extracted */
22614 brt = "Reserved";
22615 if ( 0 == brc) brt = " 32";
22616 if ( 1 == brc) brt = " 40";
22617 if ( 2 == brc) brt = " 48";
22618 if ( 3 == brc) brt = " 56";
22619 if ( 4 == brc) brt = " 64";
22620 if ( 5 == brc) brt = " 80";
22621 if ( 6 == brc) brt = " 96";
22622 if ( 7 == brc) brt = "112";
22623 if ( 8 == brc) brt = "128";
22624 if ( 9 == brc) brt = "160";
22625 if (10 == brc) brt = "192";
22626 if (11 == brc) brt = "224";
22627 if (12 == brc) brt = "256";
22628 if (13 == brc) brt = "320";
22629 if (14 == brc) brt = "384";
22630 if (15 == brc) brt = "448";
22631 if (16 == brc) brt = "512";
22632 if (17 == brc) brt = "576";
22633 if (18 == brc) brt = "640"; /* want to hear */
22636 smt = "Reserved";
22637 if (0 == sm) smt = "Not Indicated";
22638 if (1 == sm) smt = "No Dolby Surround";
22639 if (2 == sm) smt = "Dolby Surround";
22641 nct = "Reserved";
22642 lft = "";
22643 if ( 8 == (8 & nc)) lft = " + LFE";
22644 if ( 0 == nc) nct = "1+1";
22645 if ( 1 == nc) nct = "1/0";
22646 if ( 2 == nc) nct = "2/0";
22647 if ( 3 == nc) nct = "3/0";
22648 if ( 4 == nc) nct = "2/1";
22649 if ( 5 == nc) nct = "3/1";
22650 if ( 6 == nc) nct = "2/2";
22651 if ( 7 == nc) nct = "3/2";
22652 /* lfe is extra channel but not in the text count */
22653 if ( 8 == nc) nct = "1";
22654 if ( 9 == nc) nct = "<=2";
22655 if (10 == nc) nct = "<=3";
22656 if (11 == nc) nct = "<=4";
22657 if (12 == nc) nct = "<=5";
22658 if (13 == nc) nct = "<=6";
22660 bsmt = "Reserved";
22661 if ( 0 == bsm) bsmt = "Main Audio: Complete Main";
22662 if ( 1 == bsm) bsmt = "Main Audio: Music and Effects";
22663 if ( 2 == bsm) bsmt = "Associated: Visually Impaired";
22664 if ( 3 == bsm) bsmt = "Associated: Hearing Impaired";
22665 if ( 4 == bsm) bsmt = "Associated: Dialogue";
22666 if ( 5 == bsm) bsmt = "Associated: Commentary";
22667 if ( 6 == bsm) bsmt = "Associated: Emergency";
22668 if ( 7 == bsm) bsmt = "Associated: Voice Over";
22670 snprintf( s, sizeof(s)-1, "%s%sTag %02X DLen %02X %s: " BN
22671 "Sample Rate %s, bsid %02X",
22672 it, tc, n, l, t, srt, bsid);
22673 add_tvct_text( s );
22674 snprintf( s, sizeof(s)-1, "%s %s Rate %s kbit/s Surround %s",
22675 it, brt1, brt, smt);
22676 add_tvct_text( s );
22677 snprintf( s, sizeof(s)-1,
22678 "%s Service %s, Channels %s %s Full Service %u",
22679 it, bsmt, nct, lft, fsv);
22680 add_tvct_text( s );
22681 return;
22683 break;
22685 case 0x86:
22686 t = "Caption Service";
22687 if ( 0xC0 == (0xC0 & r[0]) ) { /* reserved bits check */
22688 unsigned char nos, cct, l21f, csn, er, war;
22689 char lang[4];
22690 char *cctt;
22691 char cctx[64]; /* extra info */
22692 char ccty[64]; /* type */
22694 nos = 0x1F & r[0];
22695 r += 1; /* skip parsed */
22696 snprintf(s, sizeof(s)-1, "%s%sTag %02X DLen %02X %s: " BN
22697 "Number of CC Services %u",
22698 it, tc, n, l, t, nos);
22700 add_tvct_text( s );
22702 csn = 0;
22703 for (i = 0; i < nos; i++) {
22704 if ( 0x40 == (0x40 & r[3]) ) { /* reserved bits */
22705 memcpy( lang, r, 3 );
22706 lang[3] = 0; /* 3 char lang code per ISO 639.2/B */
22707 r += 3; /* skip lang */
22708 cct = 1 & (r[0]>>7);
22709 s[0] = 0;
22711 *cctx = 0;
22712 *ccty = 0;
22714 /* closed captioning for show virtual channels */
22715 if (0 == cct) {
22716 /* EIA/CEA-608-B. that's NTSC */
22717 if ( 0x3E == (0x3E & r[0]) ) { /* reserved */
22718 csn++;
22719 l21f = 1 & r[0];
22720 snprintf(ccty, sizeof(ccty)-1,
22721 " NTSC # %d [%s] "
22722 "Type NTSC Line21 %s Field",
22723 csn, lang,
22724 (l21f == 0) ? "Even" : " Odd" );
22726 } else {
22727 /* EIA-708-A is ATSC ATVCC */
22728 csn = 0x3F & r[0];
22729 cctt = "ATVCC";
22730 snprintf(ccty, sizeof(ccty)-1,
22731 " ATVCC # %d [%s] Type %s",
22732 csn, lang, cctt);
22735 if ( (0x3F == (0x3F & r[1])) && (0xFF == r[2]) ) {
22736 er = 1 & (r[1]>>7);
22737 war = 1 & (r[1]>>6);
22738 snprintf(cctx, sizeof(cctx)-1,
22739 ", Easy Reader %u Wide Aspect %u", er, war);
22740 r += 3;
22742 snprintf( s, sizeof(s)-1, "%s%s", ccty, cctx);
22743 add_tvct_text( s );
22746 return;
22748 break;
22750 case 0x87:
22751 t = "Content Advisory";
22752 if ( 0xC0 == (0xC0 & r[0]) ) {
22753 unsigned char rrc, rr, rd, rdj, rv, rdl;
22754 add_tvct_text( s );
22755 rrc = 0x3F & r[0];
22756 snprintf(s, sizeof(s)-1, "%s%sTag %02X DLen %02X %s: " BN
22757 "Rating Region Count %u",
22758 it, tc, n, l, t, rrc);
22759 add_tvct_text( s );
22760 r += 1; /* skip to loop vals */
22761 for (i = 0; i < rrc; i++) {
22762 rr = r[0]; /* rating region */
22763 rd = r[1]; /* rated dimensions */
22765 /* one dimension will try to print on one line */
22766 snprintf(s, sizeof(s)-1,
22767 "%s Region %u Dimensions %u",
22768 it, rr, rd);
22769 add_tvct_text( s );
22771 r += 2;
22772 for (j = 0; j < rd; j++) {
22773 if ( 0xF0 == (0xF0 & r[1]) ) {
22774 rdj = r[0]; /* rating dimension j */
22775 rv = 0xF & r[1]; /* rating value */
22776 snprintf(s, sizeof(s)-1,
22777 "%s Dimension %u Value %u",
22778 it, rdj, rv);
22779 add_tvct_text( s );
22780 r += 2;
22783 rdl = r[0];
22784 r++;
22785 if (rdl != 0) {
22786 char rt[512];
22787 build_vct_mss( r, rt );
22788 snprintf(s, sizeof(s)-1, "%s Rating: %s", it, rt);
22789 add_tvct_text( s );
22792 return;
22794 break;
22797 /* parsed in VCT but warn with blink if they show as mpeg descriptor */
22798 case 0xA0:
22799 t = BL "ECN" BN;
22800 break;
22802 case 0xA1:
22803 t = BL "SVLOC" BN;
22804 break;
22806 case 0xA2:
22807 t = BL "TSS" BN;
22808 break;
22809 /* end of parsed in VCT descriptors */
22811 case 0xA3:
22812 t = BM "Component Name" BN;
22813 build_vct_mss( r, ct );
22814 snprintf( s, sizeof(s)-1, "%s%sTag %02X DLen %02X %s: %s",
22815 it, BC, n, l, t, ct);
22816 add_tvct_text( s );
22817 return;
22818 break;
22820 /* warn with blink for these unsupported and so far unseen types */
22821 case 0xA8:
22822 t = BL "DCC Departing Request" BN;
22823 break;
22825 case 0xA9:
22826 t = BL "DCC Arriving Request" BN;
22827 break;
22829 /* warn with blink to advertise the imminent peril to the right of fair use */
22830 case 0xAA:
22831 t = BR BL "Redistribution Control - Fair Use Rights In Dire Jeopardy" BN;
22832 break;
22833 /****************************************************************************/
22835 /* handle 0-1, 16-63, 64 and rest not done above */
22836 default:
22838 these have nothing to parse
22839 well not completely true,
22840 there are some odd descriptors showing up
22842 if (n < 2) t = "Reserved";
22843 if ((n >= 16) && (n <= 63)) t = "ISO/IEC 13818-1 Reserved";
22844 if (n >= 64) t = "User Private";
22845 break;
22848 /* yellow is not parsed, no colon either */
22849 snprintf( s, sizeof(s)-1, "%s%sTag %02X DLen %02X %s ", it, BY, n, l, t);
22850 add_tvct_text( s );
22856 /* build vc text calls this */
22857 /* A/65b Table 6.26 Service Location Descriptor for n'th Virtual Channel */
22858 /* static */
22859 void
22860 build_vct_svloc ( unsigned int n )
22862 unsigned short pcrpid; /* program clock reference pid */
22863 unsigned short numes; /* number of elementary streams */
22864 unsigned short espid; /* elementary stream pid */
22865 unsigned char est; /* elementary stream type */
22866 char eslang[4]; /* elementary stream language */
22867 char estx[32]; /* elementary stream type text */
22868 char t[256]; /* temp text build */
22869 unsigned char i, a;
22870 unsigned int j, k;
22871 char *c; /* color based on cap vc & va */
22872 char *p; /* es type, vct or pat */
22873 unsigned char dtag, dlen;
22874 struct sig_s *s;
22875 int svc, sva, svp;
22877 s = &ptc[ cap_chan ].sig;
22879 svp = cap_pn;
22880 sva = cap_va;
22881 if (CAP_NONE == cap_now) {
22882 svp = s->pn;
22883 sva = s->va;
22885 svc = find_vc_pgm( svp, WHO);
22887 /* NO NTSC or Hidden
22888 if ( (0 == vc[n].pn) || (65535 == vc[n].pn) )
22889 return;
22892 /* flag for not parsed */
22893 snprintf( estx, sizeof(estx)-1, "%s", "?parse?");
22895 pcrpid = vc[n].pcrpid;
22897 numes = vc[n].vctes;
22898 if (numes < vc[n].pmtes) numes = vc[n].pmtes;
22899 /* so vc audio select works in MPEG only fallback */
22900 vc[n].pmtes = numes;
22902 c = BN; /* not cap */
22904 /* NTSC does not appear on list anymore
22905 if (vc[n].pn < 65535) {
22906 if (n == svc) c = BG;
22909 if (-1 == svp)
22910 if (-1 == svc)
22911 c = BG; /* cap all overrides colors */
22913 if (-1 < svp)
22914 if (svp == vc[n].pn)
22915 c = BG; /* cap program overrides color */
22917 p = "VCT";
22919 /* red indicates missing PMT PID */
22920 #if 0
22921 if (0 == strncmp( c, BG, 8))
22922 if (0 == pids[vc[n].pmtpid]) c = BR;
22923 #endif
22925 if ( (svp > 0) && (n != svc) ) c = BN; /* cap vc overrides color */
22926 snprintf( t, sizeof(t)-1,
22927 "%s %s Program Map PID %04X Vn %02X "
22928 "Clock Ref PID %04X DLen %03X ES Num %u:" BN,
22929 c, p, vc[n].pmtpid, vc[n].pmtvn, pcrpid,
22930 vc[n].pilen, vc[n].vctes);
22932 add_tvct_text( t );
22934 /* add an extra line to indicate PMT ES is different from VCT ES */
22935 if (vc[n].pmtes > vc[n].vctes) {
22937 c = BY; /* cap all overrides colors */
22939 p = "PMT";
22941 /* red indicates missing PMT PID */
22942 #if 0
22943 if (0 == strncmp( c, BG, 8))
22944 if (0 == pids[vc[n].pmtpid]) c = BR;
22945 #endif
22947 snprintf( t, sizeof(t)-1,
22948 "%s %s Program Map PID %04X Vn %02X "
22949 "Clock Ref PID %04X DLen %03X ES Num %u:" BN,
22950 c, p, vc[n].pmtpid, vc[n].pmtvn, pcrpid,
22951 vc[n].pilen, vc[n].pmtes);
22953 add_tvct_text( t );
22956 if (0 != vct_dtl) {
22958 snprintf( t, sizeof(t)-1,
22959 BM " PMT DESCRIPTORS: PID %04X DLen %02X:" BN,
22960 vc[n].pmtpid, vc[n].pilen );
22961 add_tvct_text( t );
22963 for (j = 0; j < ( 0x3FF & vc[n].pilen); ) {
22964 /* program info has PMT descriptors */
22965 dtag = vc[n].pidesc[j];
22966 dlen = vc[n].pidesc[j+1];
22967 build_mpeg2_descriptor( &vc[n].pidesc[j], 4 );
22968 j += 2;
22969 j += dlen; /* to next descriptor until pilen */
22971 add_tvct_text( " " );
22974 a = 0;
22975 for (i=0; i<numes; i++) {
22976 if (numes > 7 ) {
22977 return; /* 8 VC limit */
22978 } else {
22979 memcpy( eslang, &vc[n].eslang[i][0], 4);
22980 eslang[3] = 0; /* NUL term */
22981 est = vc[n].estype[i];
22982 espid = vc[n].espid[i];
22984 if (-1 < svp)
22985 if ( n == svc)
22986 c = BG;
22988 p = "VCT";
22990 /* full cap colors yellow unparsed non-video non-audio */
22991 if ( (-1 == svp) && (0x02 != est) && (0x81 != est) )
22992 c = BY;
22994 if (i >= vc[n].vctes) {
22995 p = "PMT";
22996 c = BM;
22999 snprintf( estx, sizeof(estx)-1, "%s A/90 Type [%02X]", p, est);
23001 if (0x02 == est) {
23002 snprintf( estx, sizeof(estx)-1, "%s MPEG Video # %d", p, n);
23005 if (0x81 == est) {
23006 snprintf( estx, sizeof(estx)-1, "%s A/52 Audio # %d", p, a);
23007 if ( ( -1 < svp) && (n == svc) && (a != sva) ) c = BN;
23008 a++;
23009 /* color audio being capped, but reset color if not this vc */
23012 /* red indicates missing ES PIDs */
23013 #if 0
23014 if (0 == strncmp( c, BG, 8))
23015 if (0 == pids[vc[n].espid[i]]) c = BR;
23016 #endif
23017 snprintf( t, sizeof(t)-1,
23018 "%s %s [%3s] ES # %d Type %02X ES PID %04X ES ILen %03X" BN,
23019 c, estx, eslang, i, est, espid, vc[n].esilen[i]);
23020 add_tvct_text( t );
23022 if ( (0 != vct_dtl)
23023 && (0 != vc[n].pmtpid)
23024 && (0 != vc[n].esilen[i]) )
23026 for (k = 0; k < (0x3FF & vc[n].esilen[i]); ) {
23027 /* elementary stream descriptors */
23028 dtag = vc[n].esdesc[i][k];
23029 dlen = vc[n].esdesc[i][k+1];
23031 build_mpeg2_descriptor( &vc[n].esdesc[i][k], 4);
23033 k += 2;
23034 k += dlen; /* to next descriptor until pilen */
23036 add_tvct_text( " " );
23045 r points to start of descriptors.
23046 vct may have: ext chan name, svc loc, time shift svc
23047 do until len exhausted
23048 n is vc[n] for svloc
23050 /* static */
23051 void
23052 build_vct_descriptor ( unsigned char *r, unsigned int n, unsigned short vcdlen )
23054 unsigned char dtag, dlen;
23055 char s[128];
23056 unsigned short len = vcdlen;
23058 /* should not be any descriptors for HIDDEN or NTSC?
23059 char *t;
23060 if ( (0 == vc[n].pn) || (vc[n].pn == 65535) ) return;
23063 /* uncomment this and you won't see the text below if bad descriptor
23065 if ((0 == r[0]) || (0 == r[1]))
23066 return;
23070 dtag = dlen = 0;
23071 len &= 0x3FF; /* boundary force, r stays within .vcdesc[1024] */
23073 if (0 != vct_dtl) {
23074 snprintf( s, sizeof(s)-1,
23075 BC " VCT DESCRIPTORS: DLen %02X ADLen %02X" BN,
23076 len, vc[n].adl);
23077 add_tvct_text( s );
23080 while ( len > 0 ) {
23081 if ((0 == r[0]) || (0 == r[1])) return; /* bad descriptor */
23082 dtag = *r;
23083 r++;
23084 dlen = *r;
23085 r++;
23086 len -= 2;
23088 switch( dtag ) {
23090 /* extended channel name descriptor is mulitple string structure */
23091 case 0xA0:
23092 if (0 != vct_dtl) {
23093 char ecn[512];
23094 build_vct_mss( r, ecn );
23095 snprintf( s, sizeof(s)-1,
23096 " %sTag %02X DLen %02X "
23097 "Ext Channel Name: " BN "%s", BC, dtag, dlen, ecn );
23098 add_tvct_text( s );
23100 break;
23102 /* service location descriptor */
23103 case 0xA1:
23104 if (0 != vct_dtl) {
23105 snprintf( s, sizeof(s)-1,
23106 " %sTag %02X DLen %02X "
23107 "Service Location:" BN, BC, dtag, dlen );
23108 add_tvct_text( s );
23110 build_vct_svloc( n );
23111 break;
23113 /* time shifted service descriptor, nop currently */
23114 case 0xA2:
23115 snprintf( s, sizeof(s)-1,
23116 " %sTag %02X DLen %02X "
23117 "Time Shifted Service:", BY, dtag, dlen );
23118 add_tvct_text( s );
23119 break;
23121 /* alert for anything not right */
23122 default:
23123 snprintf( s, sizeof(s)-1,
23124 BY " *Tag %02X DLen %02X ignored" BN, dtag, dlen);
23125 add_tvct_text( s );
23126 break;
23128 r += dlen;
23129 len -= dlen;
23136 /******************************************************************** VCT TEXT
23137 build an array of text lines with info from VCT and PMT.
23138 This gives info on which PIDs are for which virtual channel stream.
23139 It also gives you details on Program Map Tables and stream descriptors.
23141 /* static */
23142 void
23143 build_vc_text ( void )
23145 unsigned short sl, adl;
23146 unsigned char vn;
23147 /* unsigned char cni, sn, lsn, pv, ncs; */
23148 unsigned char i, j;
23149 unsigned int k;
23150 short tsi;
23151 char t[256]; /* temp text string */
23152 char *c; /* color capturable vcs */
23153 int svc, sva, svp;
23154 struct sig_s *s;
23156 s = &ptc[ cap_chan ].sig;
23157 svp = cap_pn;
23158 sva = cap_va;
23159 if (CAP_NONE == cap_now) {
23160 svp = s->pn;
23161 sva = s->va;
23163 svc = find_vc_pgm( svp, t);
23165 advrlog( LOG_INFO, "build vc text vcncs %d p %d v %d a %d",
23166 vc_ncs, svp, svc, sva);
23168 /* vc_ncs should not be zero if you want to display anything */
23169 if (vc_ncs < 1) return;
23171 /*(0 == pkt.pmt) && (0 == pkt.tvct) ) return; */
23173 sl = tsi = adl = 0;
23174 /* vn = cni = sn = lsn = pv = ncs = 0; */
23175 vn = 0xFF;
23176 i = j = 0;
23178 /* if ( (0 < pkt.tvct) && (0 < pkt.pat) ) return; */
23180 if (vct_ncs > 0) {
23181 unsigned char *r;
23182 r = vct.payload;
23183 sl = ( ( 0x0F & r[1] ) << 8 ) | r[2];
23184 tsi= ( r[3] << 8 ) | r[4];
23185 vn = 0x1F & (r[5]>>1);
23186 if (CAP_NONE == cap_now) vn = 0xFF;
23188 /* rest aren't used
23189 cni = 1 & r[5];
23190 sn = r[6];
23191 lsn = r[7];
23192 pv = [8];
23193 ncs = r[9];
23197 /* right now have 50 lines to work with. bump tvct_text if need more */
23198 memset( tvct_text, 0, sizeof(tvct_text) );
23199 tvct_idx = 0;
23200 /* ncs = vct_ncs; */
23202 #if DEBUG
23203 snprintf(t, sizeof(t)-1, "vct ncs %d pat ncs %d vc ncs %d",
23204 vct_ncs, pat_ncs, vc_ncs);
23205 add_tvct_text( t );
23206 #endif
23208 if (0 != vct_ncs) {
23209 snprintf( t, sizeof(t)-1,
23210 BW "VCT Vn %02X SLen %03X Channels %d" BN,
23211 vn, sl, vct_ncs );
23213 } else {
23215 /* red indicates missing VCT */
23216 snprintf( t, sizeof(t)-1, "%s",
23217 BR "VCT NONE MPEG2TS PAT+PMT ONLY" );
23219 add_tvct_text( t );
23221 if (0 != pat_ncs) {
23222 snprintf( t, sizeof(t)-1,
23223 BW "PAT Vn %02X SLen %03X Programs %d" BN,
23224 pat.vn, pat.sl, pat_ncs );
23225 add_tvct_text( t );
23228 /* NO: faulty VCT gets overridden by PAT VC list */
23229 /* if (vct_ncs < pat_ncs) ncs = pat_ncs; */
23231 add_tvct_text( " " );
23233 for (i=0; i < vc_ncs; i++)
23235 char ct[32]; /* program number or channel type */
23236 char *mm = ""; /* modulation mode */
23237 char *el = ""; /* etm location text */
23238 char *st = ""; /* service type */
23239 char *pw = ""; /* PAT warning of missing data */
23240 char *cn = ""; /* VCT callsign or PAT TSID lookup of same */
23241 ct[0] = 0;
23243 /* set vc colors based on sig->vc sig->va status and program */
23244 c = BN; /* not cap */
23246 if (-1 == svc) c = BG; /* cap all vc */
23248 if ( (vc[i].pn > 0) && (vc[i].pn < 65535) ) {
23249 if (i == svc) c = BG; /* cap this */
23252 /* QAM256 is close enough until can query device status */
23253 if ((0 != arg_cable) & (0 == vc[i].mode))
23254 vc[i].mode= 3;
23256 /* transmission mode */
23257 switch( vc[i].mode ) {
23258 case 1: mm = "NTSC"; c = BM; break;
23259 case 2: mm = "QAM64"; break;
23260 case 3: mm = "QAM256"; break;
23261 case 4: mm = "VSB8"; break;
23262 case 5: mm = "VSB16"; break;
23263 default: mm = "rsvd"; break;
23266 /* extended text message location */
23267 switch( vc[i].etm ) {
23268 case 0: el = "None"; break;
23269 case 1: el = "PSIP"; break;
23270 case 2: el = "TSID"; break;
23271 default: el = "rsvd"; break;
23274 /* service type */
23275 switch( vc[i].svct ) {
23276 case 1: st = "NTSC"; break;
23277 case 2: st = "ATSC DTV"; break;
23278 case 3: st = "ATSC Audio"; break;
23279 case 4: st = "ATSC Data"; break;
23280 default: st = "rsvd"; break;
23283 if (0xFFFF == vc[i].pn) {
23284 snprintf( ct, sizeof(ct)-1, "Analog TV");
23285 c = BM;
23288 if (0 == vc[i].pn) {
23289 snprintf( ct, sizeof(ct)-1, " HIDDEN ");
23290 c = BR;
23293 channel types, ATSC.PN, NTSC or HIDDEN is disabled
23294 defaul to ATSC color then fallback
23296 if (0 == *ct) { /* nothing else set it, so set it now */
23297 char pt[8];
23298 snprintf( pt, sizeof(pt)-1, "%d", vc[i].pn);
23299 snprintf( ct, sizeof(ct)-1, BW "Pgm %-5s %s", pt, c);
23302 /* vct specific */
23303 /* TSID/callsign lookup not done in ATSC. Uses component name or VCT name */
23304 if (i < vct_ncs) {
23305 snprintf( t, sizeof(t)-1,
23306 BG "ATSC %sChn %d %s TSID %04X [%-7s] %d.%d "
23307 "%dx%d Mode %s ETM %s",
23308 c, 1 + i, ct, vc[i].tsid, vc[i].name,
23309 vc[i].major, vc[i].minor, vc[i].hres, vc[i].vres, mm, el );
23310 add_tvct_text( t );
23312 if (0 != vct_dtl) {
23313 snprintf( t, sizeof(t)-1,
23314 "%s VCT Access Control %d Hidden %d Hide Guide %d "
23315 "Service Type %s Source %d",
23316 c, vc[i].actrl,
23317 vc[i].hide, vc[i].hideg, st, /* vc[i].svct, */
23318 (vc[i].src) );
23319 add_tvct_text( t );
23323 /* if program is hidden there is no other data so insert separator */
23325 if (0 == vc[i].pn) {
23326 add_tvct_text( " " );
23327 if (0 != vct_dtl) /* detail needs an extra separator */
23328 add_tvct_text( " " );
23331 /* show VCT descriptors if VCT exists and VC descriptor len not zero */
23332 if ( (i < vct_ncs)
23333 && (0 < vc[i].vcdlen)
23334 && (vc[i].vcdlen < 1021) )
23336 build_vct_descriptor( vc[i].vcdescr, i, vc[i].vcdlen );
23337 /* no more descriptors */
23338 add_tvct_text( " " );
23341 /* output redux: limit duplicate PAT output below until after VCT entries.
23342 If VCT exists, extra PAT entries will be added to VCT at vc[vct_ncs] and
23343 vc_ncs is adjusted to reflect larger number of entries in PAT
23344 If no VCT exists, the PAT vc order will reflect the PAT accurately
23346 if ( (i < vct_ncs)
23347 /* && (i == pat_ncs) */
23348 ) continue;
23351 cn = vc[i].cname; /* try to use component name if available */
23353 /* FIXME: because of the confusion on cable where they sometimes send the
23354 tvct and cvct, it may be best to ignore both of them and use pat+pmt
23356 if ((vct_ncs > 0) && (i >= vct_ncs)) pw = " NOT IN VCT";
23357 if (0 == vc[i].pmtes) pw = "* NO PMT INFO";
23358 if (0 != vc[i].ca) pw = "* SCRAMBLED";
23360 /* stream type 2 is first estype from PMT, if it has a video entry */
23361 if (CAP_NONE != cap_now) {
23362 if (2 == vc[i].estype[0]) {
23363 if (0 == pids[vc[i].espid[0]]) {
23364 pw = "* NO VIDEO PID";
23369 /* don't show if no details and problem text was set */
23370 if (0 == vct_dtl)
23371 if ('*' == *pw)
23372 continue;
23373 if ('*' == *pw) c = BR;
23375 /* Make users hit [d]etails to see other VC's. This is the new default
23376 for the logical channel numbering scheme to support cable and satellite.
23377 Each scanlist entry should point to what find stations located.
23378 That's not to say you can't go back to the old way by selecting the VC.
23380 if ( (0 == vct_dtl) && (s->pn != vc[i].pn) ) continue;
23382 tsi = find_tsidx( vc[i].tsid, vc[i].pn, WHO );
23383 if (tsi >= 0) cn = tsids[tsi].call;
23385 /* PAT gets colored magenta to indicate PAT supplied only, not in VCT */
23386 snprintf( t, sizeof(t)-1,
23387 BM" PAT %sChn %2d %s %sTSID %04X [%-7s] %dx%d %s",
23388 c, 1 + i, ct, c, vc[i].tsid, cn,
23389 vc[i].hres, vc[i].vres, pw);
23390 add_tvct_text( t );
23392 /* NOTE: this doesn't show if problem filter skipping it above */
23393 if (0 == vct_dtl) {
23394 if (0 != vc[i].ca) {
23395 add_tvct_text( " " );
23396 continue;
23398 if (0 == vc[i].pmtes) {
23399 add_tvct_text( " " );
23400 continue;
23404 /* fallback to PAT and PMT for Elementary Stream info */
23405 if (0 != vc[i].pmtpid) {
23407 /* red indicates missing PMT PID */
23408 #if 0
23409 if (0 == strncmp( c, BG, 8))
23410 if ( 0 == pids[vc[i].pmtpid] ) c = BR;
23411 #endif
23413 snprintf( t, sizeof(t)-1,
23414 "%s PMT Program Map PID %04X Vn %02X "
23415 "Clock Ref PID %04X DLen %03X ES Num %u:" BN,
23416 c, vc[i].pmtpid, vc[i].pmtvn, vc[i].pcrpid,
23417 vc[i].pilen, vc[i].pmtes );
23418 add_tvct_text( t );
23420 if ( 0 != vct_dtl ) {
23421 for ( j = 0; j < vc[i].pilen; ) {
23422 unsigned char dtag, dlen;
23423 /* program info has global PMT descriptors */
23424 dtag = vc[i].pidesc[j];
23425 dlen = vc[i].pidesc[j+1];
23426 build_mpeg2_descriptor( &vc[i].pidesc[j], 4 );
23427 j += 2;
23428 j += dlen; /* to next descriptor until pilen */
23430 add_tvct_text( " " );
23433 /* don't display if missing PMT info */
23435 /* or if already displayed above */
23437 if (0 != vc[i].pmtes) {
23439 int a;
23440 a = 0;
23441 /* step thru known PMT stream types and show descriptors */
23442 for (j = 0; j < vc[i].pmtes; j++ ) {
23443 char estx[32];
23444 unsigned char est;
23445 est = vc[i].estype[j];
23447 c = BN;
23449 /* treat everything unparsed as generic A/90 data type */
23450 if ( (-1 == svc) && (0x02 != est) && (0x81 != est) )
23451 c = BY;
23453 snprintf( estx, sizeof(estx)-1,
23454 " PMT A/90 Type [%02X]", est);
23456 /* Video type */
23457 if ( 0x02 == est ) {
23458 if (i == svc) c = BG;
23459 snprintf( estx, sizeof( estx )-1,
23460 " PMT MPEG Video # %d", i);
23463 /* Audio type, cap aud means highlight only one */
23464 if ( 0x81 == est ) {
23465 if ( (i == svc) && (a == sva) ) c = BG;
23466 snprintf( estx, sizeof( estx )-1,
23467 " PMT A/52 Audio # %d", a);
23468 a++;
23470 /* else could display other types like vct does above but not doing this */
23472 if (-1 == svp) c = BG;
23474 /* red indicates missing ES PIDs */
23475 #if 0
23476 if (0 == strncmp( c, BG, 8))
23477 if (0 == pids[vc[i].espid[j]]) c = BR;
23478 #endif
23480 snprintf( t, sizeof(t)-1,
23481 "%s %s [%3s] ES # %01X Type %02X ES PID %04X "
23482 "ES ILen %03X" BN,
23483 c, estx, vc[i].eslang[j], j, est,
23484 vc[i].espid[j], vc[i].esilen[j] );
23485 add_tvct_text( t );
23487 if (0 != vct_dtl) {
23488 for (k = 0; k < vc[i].esilen[j]; ) {
23489 unsigned char dtag, dlen;
23490 /* elementary stream descriptors */
23491 dtag = vc[i].esdesc[j][k];
23492 dlen = vc[i].esdesc[j][k+1];
23493 build_mpeg2_descriptor( &vc[i].esdesc[j][k], 4);
23494 k += 2;
23495 k += dlen; /* to next descriptor until pilen */
23497 if ( 0 != vc[i].esilen[j] ) add_tvct_text( " " );
23501 add_tvct_text( " " );
23505 #if 0
23506 /* additional VCT descriptors
23507 ignoring these because have no examples to test against.
23508 look for additional descriptors, spec says may be 0
23510 if (0xFC == (r[0] & 0xFC)) {
23511 adl = ((0x3 & r[0]) << 8) | r[1];
23512 if (adl != 0) {
23513 snprintf( t, sizeof(t)-1, "TVCT add'l desc NOT PARSED!\n");
23514 add_tvct_text( t );
23516 /* not doing anything with add'l descriptors */
23517 for ( j = 0; j < adl; j++) {
23518 /* aprintf( stderr, "%02X", *r); */
23519 r++; /* skip descriptor */
23523 #endif
23528 /* starting from vc[n], find next usable vc and return the index */
23529 /* used by the [c] key in show virtual channels to change to next VC */
23530 /* This is for cable users with more than broadcast usual of 1-5 VCs */
23531 /* This is until coded for 'A'-'Z' similar to channel/epg selects */
23532 /* static */
23534 find_next_vc( int n )
23536 int i, j;
23537 for (i = 1; i<vc_ncs; i++) {
23538 j = (i + n) % vc_ncs;
23539 advrlog( LOG_INFO, "%s n %d i %d j %d pn%d es%d ca%d",
23540 WHO, n, i, j, vc[j].pn, vc[j].pmtes, vc[j].ca);
23541 if (0 == vc[j].pn) continue;
23542 if (0xFFFF == vc[j].pn) continue;
23543 if (0 == vc[j].pmtes) continue;
23544 if (0 != vc[j].ca) continue;
23545 advrlog( LOG_INFO, "%s found %d", WHO, j);
23546 return j;
23548 advrlog( LOG_INFO, "%s fallback %d", WHO, n);
23549 return n;
23553 Show user the Virtual Channel Table with Program Map Table descriptors,
23554 or show Program Map Table with descriptors if no VCT found, or half and half
23555 if the # of channels don't match (can only do so much with non-compliance)
23557 /* static */
23558 void
23559 show_virtual_channels ( void )
23561 int i, j, tp;
23562 unsigned char key = 0;
23563 int vcs = 0;
23564 int vo = 0;
23565 int svc, sva, svp, ovnc;
23566 struct sig_s *s;
23567 char *tt, pgm_id[64];
23569 s = &ptc[ cap_chan ].sig;
23571 ovnc = 0; /* gets copy of vct ncs if [p] key hit */
23573 /* display uses current cap pn/va setting or config pn/va if no cap */
23574 svp = cap_pn;
23575 sva = cap_va;
23576 if (CAP_NONE == cap_now) {
23577 svp = s->pn;
23578 sva = s->va;
23580 svc = find_vc_pgm( svp, WHO );
23582 if (vc_ncs < 1) {
23583 dvrlog( LOG_INFO, "%s vc ncs %d", WHO, vc_ncs );
23584 return;
23587 /* test mode always starts out in full cap */
23588 if (0 != test_mode) {
23589 s->vc = s->va = cap_vc = cap_va = svc = sva = svp = -1;
23592 refresh.vct = 1;
23593 build_vc_text();
23595 for (i = 0; i < user_lines;) {
23597 if ( D_VCT != display_type) {
23598 if (0 != ovnc) vct_ncs = ovnc; /* have an ACME cigar! */
23599 return;
23601 j = i + vo;
23602 if (0 != refresh.vct) {
23603 if (CAP_NONE == cap_now) {
23604 tt = "ATSC VIRTUAL CHANNEL TABLE";
23605 tp = columns - strlen(tt);
23606 tp >>= 1;
23607 if (tp < 0) tp = 0;
23608 /* replace signal header */
23609 aprintf( stderr, DCARC CEL, 2, 1);
23610 aprintf( stderr, DCARC BW "%s" BN, 2, tp, tt);
23612 snprintf( pgm_id, sizeof(pgm_id),
23613 "LCN %d PTC %d.%d %s %s",
23614 s->chan, s->ptc, s->pn, s->call, s->sid);
23615 tp = columns - strlen(pgm_id);
23616 tp >>= 1;
23617 if (tp < 0) tp = 0;
23618 /* and strength bar */
23619 aprintf( stderr, DCARC CEL, 3, 1);
23620 aprintf( stderr, DCARC BY "%s", 3, tp, pgm_id);
23623 aprintf( stderr, "\033[%d;1H%s" CEL, 5+i, &tvct_text[j][0] );
23626 i++;
23627 i %= user_lines;
23629 if (i == 0) {
23630 char *t;
23631 show_status( 1, WHO );
23633 t = "";
23634 if (CAP_NONE == cap_now) {
23635 t = "Last";
23636 } else {
23637 t = "LIVE";
23639 if (0 != refresh.vct) {
23640 aprintf( stderr, DCA_VCT CEL BW "%s Virtual Channel Table ", t);
23641 aprintf( stderr, BN BW "(" BG "CAP" BW ")" BN " " SCV );
23643 if (-1 < svp) {
23644 aprintf( stderr, BG "%d.%d PGM %d VC %d VA %d" BN CEL,
23645 // vc[svc].major, vc[svc].minor, svp, svc, sva);
23646 s->ptc, s->pn, svp, svc, sva);
23647 } else {
23648 aprintf( stderr, BG "Full Capture" BN CEL);
23651 aprintf( stderr, BN"%s[?] help", dca.ustat);
23652 /* hold down further redraws */
23655 refresh.vct--;
23656 if (refresh.vct < 0) refresh.vct = 0;
23658 /* end of page? wait for key and process it */
23659 key = 0;
23660 console_getch_fn( &key );
23661 nanosleep( &console_read_sleep, NULL ); /* need 1/5s */
23662 if (0 == key) continue;
23664 if ( (10 == key) || ('v' == key) ) {
23665 if (0 != ovnc) vct_ncs = ovnc;
23666 return;
23668 /* any key refreshes */
23669 refresh.vct = 1;
23671 /* if cap is info or none, [0...9] key sets virtual channel # */
23672 /* don't allow user to change it during capture */
23673 if ( (CAP_INFO == cap_now) || (CAP_NONE == cap_now) )
23675 if ( ('c' == key) || ((key >= '1') && (key <= '9'))) {
23676 vcs = key - '1';
23677 if ('c' == key) vcs = find_next_vc( svc );
23678 if ( (vcs >= 0) && (vcs < vc_ncs) ) {
23680 /* don't select Hidden or NTSC, nothing there */
23681 if ( (vc[ vcs ].pn < 65535)
23682 && (vc[ vcs ].pn != 0)
23683 && (vc[ vcs ].pmtes != 0) ) /* no ES to select? */
23685 s->pn = cap_pn = svp = vc[vcs].pn;
23686 s->vc = cap_vc = svc = vcs;
23687 s->va = cap_va = sva = 0; /* default audio 0 */
23688 advrlog( LOG_INFO, "vc %d selected pgm %d",
23689 cap_vc, cap_pn);
23690 pat_built = 0; /* force a new PAT */
23692 } else {
23693 dvrlog( LOG_INFO, "vc can't select %d", vcs);
23695 nanosleep( &console_read_sleep, NULL ); /* 100ms */
23696 key = 0;
23702 /* if cap info or cap none, [a] sets the audio # within vc */
23703 /* if it gets it wrong, remove comment to disable audio select during capture
23704 if ( (CAP_NONE == cap_now) || (CAP_INFO == cap_now) )
23707 if ( 'a' == key) {
23708 advrlog( LOG_INFO, "va select vc%d", svc );
23710 /* wait til user sets vc first */
23711 if (-1 < svc) {
23713 /* only valid if more than 1 audio ES available */
23714 if (vc[svc].pmtes > 2) {
23716 /* why not toggle thru instead? now have vc[].acn for modulo now.
23717 more concise, but it might be annoying if more than 3 audios aaaa
23719 sva++;
23720 sva %= vc[svc].acn;
23721 s->va = cap_va = sva;
23722 advrlog( LOG_INFO,
23723 "p %d v %d a %d %% acn %d",
23724 svp, svc, sva, vc[svc].acn);
23726 /* 100ms? trying to slow down user input */
23727 nanosleep( &console_read_sleep, NULL );
23728 key = 0;
23729 pmt_built = 0; /* force a new PMT */
23735 /* don't allow reset during user or timer captures */
23736 if ((CAP_NONE == cap_now) || (CAP_INFO == cap_now)) {
23737 if ( 'r' == key ) {
23738 cap_pn = cap_vc = cap_va = -1;
23739 s->pn = s->vc = s->va = -1;
23740 svc = sva = svp = -1;
23741 key = 0;
23745 /* if key was handled, show select and save config */
23746 if (0 == key) {
23747 refresh.epg = 1; /* bottom left epg count refresh */
23748 refresh.vstats = 1; /* line above bottom */
23750 #if 0
23751 if (CAP_NONE == cap_now) {
23752 cap_pn = svp;
23753 cap_vc = svc;
23754 cap_va = sva;
23755 dvrlog( LOG_INFO, "cap p %d v%d a%d",
23756 svp, svc, sva );
23758 #endif
23759 set_vc(WHO); /* should set cap_pids */
23760 show_vc_selection();
23761 show_event_short();
23762 save_config( WHO );
23763 nanosleep( &guide_loop_sleep, NULL ); /* 100ms */
23767 if ( 'z' == key) {
23768 if (CAP_INFO == cap_now) {
23769 stop_capture( WHO, 0 );
23770 display_type = D_TIMERS;
23771 if (0 != ovnc) vct_ncs = ovnc;
23772 return;
23776 /* toggle vct_ncs to zero and back for mpeg-only test display */
23777 if ( 'm' == key) {
23778 if (ovnc != 0) {
23779 vct_ncs = ovnc;
23780 ovnc = 0;
23781 vo = 0; /* redraw list from start */
23782 } else {
23783 ovnc = vct_ncs;
23784 vct_ncs = 0;
23785 vo = 0; /* redraw list froms start */
23789 if ( 's' == key) {
23790 display_stat = ~display_stat;
23791 if (0 != display_stat) refresh.pstats = 1;
23792 show_status( 0, WHO );
23795 if ( '?' == key ) show_keyhelp( vchannelskeyhelp );
23797 if ( 'd' == key ) {
23798 vct_dtl = ~vct_dtl; /* toggle detail display */
23799 vo = 0; /* redraw from top of list */
23802 if ( KB_UP == key ) vo -= 1;
23803 if ( '=' == key ) vo -= 1;
23805 if ( KB_DN == key ) vo += 1; /* [=] */
23806 if ( '\'' == key ) vo += 1; /* ['] */
23808 /* page up down only useful for cable, broadcast hasn't many VCs */
23809 if ( KB_PU == key ) vo -= user_lines;
23810 if ( KB_PD == key ) vo += user_lines;
23812 /* key = 0; */
23813 /* make it wait til next time */
23815 if (vo < 0) vo = 0; /* sanity limit */
23816 if ( (vo + user_lines) >= tvct_idx) vo = tvct_idx - user_lines;
23817 if (vo < 0) vo = 0; /* sanity limit */
23819 dvrlog( LOG_INFO, "VCT key now %02X vo %d", key, vo);
23820 if (CAP_NONE != cap_now) continue; / / only set vc when not cap
23821 continue; / / continue if user needs verify, break if not
23823 /* moved here in case key changed something */
23824 build_vc_text();
23831 prompt for a program index and add two words of event name as timer name
23832 program number is appended to this name for parsing by build outnames
23833 timer name, start time and len added to timer array and timer_idx inc
23834 if user tries to set timer on current program, it will adjust that program
23835 to start later and run shorter to try to handle ts cap in main thread
23836 ts cap getting own thread with timer test included might [does] help
23838 /* static */
23839 void
23840 get_timer_add_event ( void )
23842 struct qtimer_s *t;
23843 struct event_s *e;
23844 char *warn;
23845 char *p;
23846 int i, j;
23847 int pgm_add; /* add entry from pgm guide # */
23848 int estart; /* event start in epoch seconds */
23849 int elen; /* event len in sec */
23850 short epn; /* event program number for event */
23851 short evc; /* event vc number */
23852 short pgm_wd; /* event weekday bits */
23853 short pgm_r1; /* EPG start range */
23854 short pgm_r2; /* EPG stop range */
23855 short pgo; /* pg[ pgm_add ] shorthand */
23856 char pgm_in[16]; /* input text */
23857 char *wdb; /* from input, weekday bits text */
23858 char n[PNZ]; /* event name */
23859 char pgm_name[PNZ]; /* mangled timer name */
23862 warn = BW;
23864 memset( pgm_in, 0, sizeof( pgm_in ));
23865 test_termsize();
23867 while (1)
23869 if (timer_idx >= TIMER_LIST_MAX) {
23870 aprintf( stderr,
23871 BR BL "%s"
23872 "Timers full. Recompile with larger TIMER_LIST_MAX." CEL,
23873 dca.hstat );
23874 nanosleep( &msg_long_sleep, NULL);
23875 return;
23878 /* input needs cursor visible */
23879 aprintf( stderr, SCV BL
23880 "%s%sEPG# index[-index2][ SMTWTFS bits] to add ?" BN " " CEL,
23881 dca.hstat, warn);
23883 console_reset(); /* echo and blocked input on */
23884 fgets( pgm_in, sizeof(pgm_in)-1, stdin );
23885 console_getch(); /* echo and blocked input off */
23887 /* quick exit is [enter] key */
23888 if ( 10 == *pgm_in ) {
23889 show_headers(0);
23890 return;
23893 /* quick exit is blank line */
23894 if ( 0 == *pgm_in ) {
23895 show_headers(0);
23896 return;
23899 /* remove NL and CR, if any */
23900 p = strchr( pgm_in, '\n');
23901 if (NULL != p) *p = 0;
23902 p = strchr( pgm_in, '\r');
23903 if (NULL != p) *p = 0;
23905 pgm_r1 = atoi( pgm_in );
23906 pgm_r2 = pgm_r1;
23908 p = strchr( pgm_in, '-'); /* dash indicates range */
23909 if (NULL != p) {
23910 p++;
23911 pgm_r2 = atoi( p );
23914 wdb = "";
23916 p = strchr( pgm_in, ' '); /* space indicates weekday bits */
23917 if (NULL != p) {
23918 p++;
23919 wdb = p;
23922 /* is ascii binary? hmm no isbinary()? */
23923 pgm_wd = 0;
23924 if ( ( '0' == *wdb) || ('1' == *wdb ) ) pgm_wd = abtou( wdb );
23926 warn = BR;
23928 /* user input error check */
23929 if ( (pgm_r1 >= pgm.idx)
23930 || (pgm_r2 >= pgm.idx)
23931 || (pgm_r2 < pgm_r1)
23932 || (pgm_wd > 127)
23933 ) continue;
23935 /* add a range of program events (or a single program) */
23936 for (i = pgm_r1; i <= pgm_r2; i++)
23938 if (timer_idx >= TIMER_LIST_MAX) break; /* stop if timer list full */
23939 pgm_add = i;
23941 pgo = pg[ pgm_add ];
23942 e = &epg[ pgo ];
23944 /* copy out the event name for mangling */
23945 memcpy( n, e->name, sizeof(pgm_name) );
23946 estart = e->st;
23947 elen = e->ls;
23949 /* vc not used anymore. use pgm instead in case of VCT gone PAT fallback */
23950 evc = e->vc;
23951 epn = e->pn;
23953 advrlog( LOG_INFO, "adding event %s %d %d %d %d",
23954 n, estart, elen, epn, evc);
23956 /* copy first two words of name, ignoring some common words */
23957 event_truncate( pgm_name, n, 2, sizeof(pgm_name) ); /* two words */
23959 /* add program id to name, so name.pn */
23960 pgm_name[16] = 0; /* limit the name so @.3 will fit */
23962 p = pgm_name;
23963 p += strlen(p);
23964 asnprintf( p, sizeof(pgm_name), "@.%d", epn );
23966 /* skip duplicates that are already on the timer list */
23967 if ( 0 == test_timer_dup( WHO, pgm_name, estart, elen) )
23968 continue;
23970 /* set start time far enough ahead so info cap has time to stop and restart
23971 you don't have this problem if you use guide while not capping.
23973 if (CAP_INFO == cap_now) {
23974 get_time();
23975 if ( ((estart+elen) > utsnow) && (estart < utsnow) ) {
23976 elen = ( (estart + elen) - utsnow) - 5;
23977 estart = utsnow + 5; /* AOS needs at least 3, using 5 */
23978 if (elen < 5) continue; /* dont add if close to end */
23979 stop_capture( WHO, FAIL_NONE );
23980 display_type = D_TIMERS;
23984 j = timer_idx;
23985 timer_idx++;
23987 t = &timer[ j ];
23989 astrncpy( t->name, pgm_name, TIMER_NAME_MAX );
23991 /* if DST, adjust program start non DST, other functions adjust... */
23992 trec2 = estart;
23993 memcpy( &tloc2, localtime( &trec2 ), sizeof( tloc2 ) );
23995 #if 0
23996 /* dst changover from now to timer? no, don't do this */
23997 if ( tloc.tm_isdst != tloc2.tm_isdst) {
23998 /* NOTE:
23999 no 2am on dst changeover days. it's easier on spring forward, drop 2am
24000 but on fall back, how to handle two 0100 hours? one will run and one
24001 will be ignored, then the other will run, maybe. not worth trouble
24003 one way is to use UTC only, no DST adjust, but will confuse most users
24006 if (tloc2.tm_isdst != 0)
24007 estart += SECHR; /* spring forward */
24008 if (tloc2.tm_isdst == 0)
24009 estart -= SECHR; /* fall back */
24011 #endif
24013 t->start = estart;
24014 t->len = elen;
24015 t->chan = cap_chan;
24016 t->days = pgm_wd;
24018 /* the problem with this is config reload will not reload the etmid */
24019 t->etmid = e->etmid;
24021 /* if weekday bits set, set start time back one day until before now */
24022 if (0 != t->days)
24023 while( t->start >= utsnow)
24024 t->start -= 86400;
24026 t->secdt = 5;
24028 /* use program number not timer.vc in case VCT gone and PAT only fallback */
24029 t->pn = epn;
24030 /* timer.vc not used anymore */
24031 t->vc = evc;
24033 t->va = cap_va;
24034 t->qstat = T_FUTURE;
24035 *t->ename = 0;
24036 *t->edesc = 0;
24038 /* faster */
24039 if (0 != *e->name) {
24040 astrncpy( t->ename, e->name, PNZ );
24041 if (0 != *e->desc) {
24042 astrncpy( t->edesc, e->desc, PDZ );
24045 #if 0
24046 /* strlen makes this slower */
24047 if (0 != strlen( e->name )) {
24048 astrncpy( t->ename, e->name, PNZ );
24049 if (0 != strlen( e->desc )) {
24050 astrncpy( t->edesc, e->desc, PDZ );
24053 #endif
24057 /* sort moved outside of range loop */
24058 timer_sort = ~0;
24059 sort_timers();
24061 save_config( WHO );
24062 break;
24065 refresh.timers = 1;
24066 timer_sort = ~0;
24067 display_type = D_TIMERS;
24068 show_headers(0);
24071 /* get program index [range] and toggle the spam status for the event(s) */
24072 /* static */
24073 void
24074 get_spam_toggle ( void )
24076 struct event_s *e1;
24077 char *warn;
24078 char *p;
24079 int i, j;
24080 short pgm_r1; /* EPG start range */
24081 short pgm_r2; /* EPG stop range */
24082 char pgm_in[16]; /* input text */
24083 char pgm_name[SPAM_NAME_MAX]; /* from input text, second half of range */
24084 warn = BW;
24086 while (1)
24088 memset( pgm_in, 0, sizeof( pgm_in ));
24089 test_termsize();
24090 if (spam_idx >= SPAM_LIST_MAX) {
24091 aprintf( stderr,
24092 BR BL "%s"
24093 "Spam list full. Recompile with larger SPAM_LIST_MAX." CEL,
24094 dca.hstat );
24095 nanosleep( &msg_long_sleep, NULL);
24096 return;
24099 /* input needs cursor visible */
24100 aprintf( stderr, SCV BL
24101 "%s%sEPG# index[-index2] for junk toggle?" BN " " CEL,
24102 dca.hstat, warn);
24104 console_reset(); /* echo and blocked input on */
24105 fgets( pgm_in, sizeof(pgm_in)-1, stdin );
24106 console_getch(); /* echo and blocked input off */
24108 /* quick exit is enter key by itself */
24109 if ( 10 == *pgm_in ) {
24110 show_headers(0);
24111 return;
24114 /* quick exit is blank line */
24115 if ( 0 == *pgm_in ) {
24116 show_headers(0);
24117 return;
24120 /* remove NL and CR if any */
24121 p = strchr( pgm_in, '\n');
24122 if (NULL != p) *p = 0;
24123 p = strchr( pgm_in, '\r');
24124 if (NULL != p) *p = 0;
24126 pgm_r1 = atoi( pgm_in );
24127 pgm_r2 = pgm_r1;
24130 p = strchr( pgm_in, '-'); /* dash indicates range */
24131 if (NULL != p) {
24132 p++;
24133 pgm_r2 = atoi( p );
24135 /* correct for end-user mistakes by limiting range to 1 item */
24136 if (0 == pgm_r2) pgm_r2 = pgm_r1;
24139 /* warning color for 2nd input iteration */
24140 warn = BR;
24142 /* user input error check */
24143 if ((pgm_r1 >= pgm.idx) || (pgm_r2 >= pgm.idx) || (pgm_r2 < pgm_r1))
24144 continue;
24146 /* toggle spam status for a range of events */
24147 for (i = pgm_r1; i <= pgm_r2; i++) {
24149 /* stop if spam list full */
24150 if (spam_idx >= SPAM_LIST_MAX) break;
24152 e1 = &epg[ pg[i] ];
24154 j = test_spam_event( e1 );
24156 if (-1 == j) {
24157 event_truncate( pgm_name, e1->name, 2, SPAM_NAME_MAX );
24158 add_spam( pgm_name, e1->st );
24159 sort_spamlist( 0 ); /* sort by name */
24160 } else {
24161 delete_spam( j );
24165 save_spamlist( WHO );
24166 break;
24168 refresh.epg = 1;
24169 show_headers( 0 );
24173 /* if user selected vct program#, find current epg entry matching it */
24174 /* otherwise find first current epg entry matching current time */
24175 /* this is to make [enter] key behave consistently in epg display */
24176 /* this gets current event unless it is hidden by spam filter */
24177 /* returns -1 if not found */
24178 /* static */
24180 find_current_epg_pgm( void )
24182 struct event_s *e;
24183 struct sig_s *s;
24184 int i, j;
24186 s = &ptc[cap_chan].sig;
24188 get_time();
24189 j = -1; /* should be found */ /* event may be old/filtered */
24190 for (i = 0; i < pgm.idx; i++) { /* use pgm.idx for filtered */
24191 e = &epg[ pg[ i ] ]; /* use epg[pg[i]] for fast xref */
24193 if (e->st <= utsnow) { /* now is after event start? */
24194 if ((e->st + e->ls) > utsnow) { /* and now is before stop? */
24195 if (s->pn > 0) { /* if program selected */
24196 if (e->pn != s->pn) { /* loop until program matches */
24197 advrlog( LOG_INFO, "%s %s.%d not.%d",
24198 WHO, e->name, e->pn, s->pn);
24199 continue;
24202 advrlog( LOG_INFO, "%s found %s.%d",
24203 WHO, e->name, e->pn);
24204 j = i; /* event match */
24205 break; /* loop is done */
24209 return j;
24213 #define NAM_LIM 25
24214 #define DES_LIM 32
24216 /* show programs found so far, scroller removes need for word wrap */
24217 /* static */
24218 void
24219 show_program_guide ( void )
24221 unsigned int etmid; /* ETM ID for one event */
24222 unsigned char key = 0; /* user input */
24223 struct event_s *e1; /* one event */
24224 struct sig_s *s1; /* one LCN */
24225 struct tm stm; /* time tm structure */
24226 time_t st; /* time_t */
24228 char t[512], /* output string */
24229 d[32], /* date string to be chopped up */
24230 pn[16], /* program.num string */
24231 name[PNZ],
24232 desc[PDZ],
24233 rate[PRZ],
24234 pgm_id[64],
24235 *p, /* description at scroll offset */
24236 *ec, /* EIT 0-3 get special colors */
24237 *cec; /* current event color hilite */
24239 int i, k, /* loop counts */
24240 td, /* time delta */
24241 lc, /* line counter */
24242 pgn, /* program numer where event resides */
24243 evc, /* vc number where event resides */
24244 ls, /* length in seconds */
24245 so, /* scroll offset */
24246 po, /* page offset */
24247 sr, /* source, also used before then */
24248 spam, /* TESTME: is this used? */
24249 cp, /* current event for [enter] start */
24250 se; /* show event flag, nz shows event */
24252 cp = -1;
24253 so = 0;
24254 po = 0;
24255 spam = 0;
24257 if (D_EPG != display_type) return; /* don't display if not EPG */
24259 evc = pgn = lc = po = 0;
24261 get_time();
24262 s1 = &ptc[ pgm.chan ].sig;
24264 refresh.epg = 1;
24265 pgm.idx = 0;
24267 pgm.pn = 0;
24269 /* current cap sets program view to same as cap, may be all when CAP_INFO */
24270 if (CAP_NONE != cap_now) {
24271 pgm.pn = cap_pn;
24272 } else {
24274 /* FIXME: idle sets to selected program, but not showing VC reset s->pn -1 ?
24276 // if (s1->pn > 0) pgm.pn = s1->pn;
24277 pgm.pn = s1->pn;
24280 /* build filtered (or not) pg list from master epg list */
24281 build_pg_epg( epg, &pgm, pg, WHO);
24283 if (cap_pn > 0) {
24284 po = find_current_epg_pgm();
24285 if (-1 == po) po = 0;
24288 /* no current program if not visible as highlight (stale guide) */
24289 cp = -1;
24291 /* program guide loop needs output redux, has it now.
24292 should never pass pgm lines, but stop loop if does
24294 for (i = 0; i < user_lines;)
24297 if ( D_EPG != display_type ) return;
24298 show_status( 1, WHO ); /* 1 is wait */
24300 /* output redux */
24301 if (0 != refresh.epg)
24303 if (0 == i) {
24305 /* only show title of current view if no capture in progress.
24306 the title serves to redraw line3 when resize occurs
24307 uses ec, sr that are re-used later for other purpose
24309 if (CAP_NONE == cap_now) {
24310 ec = "ATSC EVENT PROGRAM GUIDE";
24311 sr = columns - strlen(ec);
24312 sr >>= 1;
24313 if (sr < 0) sr = 0;
24314 /* replace signal header */
24315 aprintf( stderr, DCARC CEL, 2, 1);
24316 aprintf( stderr, DCARC BW "%s" BN, 2, sr, ec);
24318 snprintf( pgm_id, sizeof(pgm_id),
24319 "LCN %d PTC %d.%d %s %s",
24320 s1->chan, s1->ptc, s1->pn, s1->call, s1->sid);
24321 sr = columns - strlen(pgm_id);
24322 sr >>= 1;
24323 if (sr < 0) sr = 0;
24324 /* and strength bar */
24325 aprintf( stderr, DCARC CEL, 3, 1);
24326 aprintf( stderr, DCARC BY "%s", 3, sr, pgm_id);
24329 /* short form net.pn for EPG header */
24330 snprintf( pgm_id, sizeof(pgm_id)-1,
24331 "%s.%d", s1->sid, s1->pn );
24333 aprintf( stderr, header_guide, DCA_HEAD4, pgm_id);
24334 aprintf( stderr, CEL BN"%s[?] help", dca.ustat);
24337 /* increment po by user lines at end of page */
24338 k = i + po;
24339 memset( name, 0, sizeof(name) );
24340 memset( desc, 0, sizeof(desc) );
24341 memset( d, ' ', sizeof(d) );
24342 ls = 0;
24344 /* color for future show more than 12 hours */
24345 cec = ec = BN;
24346 se = 1;
24348 e1 = &epg[ pg[ k ]];
24350 if (-1 == pg[k]) se = 0;
24351 if ( (s1->pn > 0) && (e1->pn != s1->pn) ) se = 0;
24353 /* is sorted guide entry non blank, or is the current program? */
24354 if ( 0 != se )
24356 st = e1->st;
24357 ls = e1->ls;
24359 /* needed to be somewhere in the inner loop */
24360 get_time();
24361 td = st - utsnow;
24363 localtime_r( &st, &stm );
24364 asctime_r( &stm, d );
24366 if ( td < (12*SECHR) ) ec = BG; /* under 12 hours green */
24367 if ( td < ( 9*SECHR) ) ec = BC; /* under 9 is cyan */
24368 if ( td < ( 6*SECHR) ) ec = BY; /* under 6 is yellow */
24369 if ( td < ( 3*SECHR) ) ec = BR; /* under 3 is red */
24371 /* magenta for aged out */
24372 if ( td < 0 ) {
24373 ec = BM;
24374 if (0 != test_mode) ec = BN; /* test needs to see */
24377 /* low light all spam entries */
24378 spam = test_spam_event( e1 ); /* program guide mask */
24379 if (-1 < spam) ec = "\033[30;40;1m";
24381 cec = ec;
24383 /* whoops lost the current indicator, moved below spam check */
24384 if ( (td < 0) && ( (0-td) < ls) ) {
24386 /* current pgm has index pn date/time/len inverse video, rest bright white */
24387 ec = BW;
24388 cec = BW BV;
24390 /* default to last current program visible */
24391 if (-1 == cp) cp = k;
24393 /* pg[k] event number for <enter> add_search_event_timer */
24396 memcpy( rate, e1->rating, PRZ);
24398 /* handle event name. the memset is needed? description uses it so console
24399 will display blank when scrolling right, but name doesn't scroll?
24400 memset( name, 0, sizeof(name) );
24401 memcpy( name, e1->name, PNZ);
24402 // pknaggs: memcpy crashes on long name
24404 astrncpy( name, e1->name, PNZ); /* chop it to fit */
24406 /* [n] key in EPG makes it show ETMID, len, comp and mode */
24407 if (0 != pgm.etmids)
24409 snprintf( name, sizeof(name),
24410 "E%04X.%04X L%02X C%02X M%02X",
24411 0xFFFF & (e1->etmid>>16),
24412 0xFFFF & e1->etmid,
24413 e1->nchr,
24414 e1->ncomp,
24415 e1->nmode );
24416 } else {
24417 if ( 0 == e1->nchr )
24418 snprintf( name, sizeof(name) - 1, "%s", "n/a");
24421 /* description does need 0 fill for scrolling to the right to work */
24422 memset( desc, 0, sizeof(desc) );
24424 /* Displays differently if rating included or description blank.
24425 Inclusion of rating text makes full 255 char description truncate
24426 to about 236 chars for display. Using asnprintf to append the text.
24428 if ( 0 != *e1->rating) {
24429 asnprintf( desc, PDZ, "[%s] ", e1->rating);
24431 if ( 0 == e1->dchr) {
24432 asnprintf( desc, PDZ, "n/a" );
24433 } else {
24434 asnprintf( desc, PDZ, "%s", e1->desc );
24437 /* append numeric description if showing etmid detail */
24438 if (0 != pgm.etmids) {
24439 snprintf( desc, sizeof(desc)-1,
24440 " D%04X.%04X L%02X C%02X M%02X",
24441 0xFFFF & ( e1->etmid >> 16 ),
24442 0xFFFF & e1->etmid,
24443 e1->dchr,
24444 e1->dcomp,
24445 e1->dmode);
24448 /* TMI?
24449 else {
24450 if ( 0 == e1->dchr )
24451 snprintf( desc, sizeof(desc) - 1, "%s", "n/a");
24454 etmid = e1->etmid;
24456 /* see ETM id's received for multiplex vc station,
24457 use epg[n] n = src id * EIT * ETT multiplier
24460 sr = 0xFFFF & (etmid >> 16);
24462 pgn = e1->pn; /* new method is pgm# */
24463 evc = e1->vc; /* old method was vc# */
24465 /* vc method will not handle the timer properly if vc # changes,
24466 however program # will stay the same and should be used instead
24467 to look up the new VC
24469 /* snprintf( pn, sizeof(pn)-1, " .%d", evc); */
24470 snprintf( pn, sizeof(pn)-1, " .%d", pgn);
24471 if (0 < cap_va) {
24472 snprintf( pn, sizeof(pn)-1, ".%d,%d", pgn, cap_va);
24475 /* NOTE: cpu pig: bright white is event already on timer list */
24476 if ( -1 != find_timer_epg( e1 )) ec = BW;
24478 if (0 == pgn) {
24479 snprintf( pn, sizeof(pn)-1, "HIDE");
24482 if (pgn >= 65535) {
24483 snprintf( pn, sizeof(pn)-1, "NTSC");
24484 ec = BM;
24487 if (0 != pgm.etmids) {
24488 snprintf( pn, sizeof(pn)-1, "%04X", pgn);
24492 /* falls thru to here to draw at least a blank string */
24493 if ( strlen(name) > NAM_LIM ) {
24494 name[ NAM_LIM - 2 ] = '>';
24496 name[ NAM_LIM - 1 ] = 0;
24498 d[16] = 0;
24499 d[3] = 0;
24500 if (so >= PDZ) so = PDZ - 30;
24503 if ( 0 != se )
24506 /* truncate description by number of columns left to display it */
24507 p = &desc[so];
24508 if ( strlen(p) >= (columns - 51) ) {
24509 p[ columns - 51 ] = 0;
24510 p[ columns - 52 ] = '>';
24513 memset(t, 0, sizeof(t));
24515 if (0 == pgm.etmids) {
24516 snprintf( t, sizeof(t),
24517 "%s%4d %-4s %-3s %-5s %4d%s%s %-24s%c%-30s",
24518 cec, k, pn, d, &d[11], ls/60, BN, ec, name,
24519 (0 != so) ? '<':' ', p );
24520 } else {
24521 snprintf( t, sizeof(t),
24522 "%s%04X %-4s %-3s %-5s %4d%s%s %-24s%c%-30s",
24523 cec, pg[k], pn, d, &d[11], ls/60, BN, ec, name,
24524 (0 != so) ? '<':' ', p );
24528 /* invalid entry will blank the line (shows blanks at end of list) */
24529 if ( 0 == se )
24531 memset( t, ' ', columns );
24532 t[ columns - 1 ] = 0;
24535 /* aprintf( stderr, "\033[%d;1H%s%s" CEL, 5+i, ec, t); */
24536 aprintf( stderr, "\033[%d;1H%s%s" CEL BN, 5 + i, ec, t);
24538 lc++;
24539 } /* end of refresh.epg != 0 */
24541 i++;
24542 i %= user_lines;
24543 key = 0;
24545 /* rest of what happens when i == 0 */
24547 /* check pg list after loop iterations */
24548 if (0 == i)
24551 if ( 0 != refresh.epg ) {
24552 build_pg_epg(epg, &pgm, pg, WHO);
24553 refresh.epg--;
24556 if (refresh.epg < 0) refresh.epg = 0;
24558 bottom of rows, see what keys
24559 hotkeys: '[' scroll left ']' scroll right '=' forward, '-' back
24560 hotkeys: [0]-[9] and [A]-[Z] for page jump (up to entry 700-719)
24561 max programs is 768, so you have to manually advance to last 48 :P
24563 console_getch_fn( &key );
24564 nanosleep( &console_read_sleep, NULL );
24565 if (0 == key) continue;
24567 /* p to get in, p to get out */
24568 if ( 'p' == key) {
24569 save_config( WHO ); /* save any timers added and epg */
24570 return;
24573 /* any key refreshes */
24574 refresh.epg = 1;
24575 i = 0;
24577 if ('?' == key ) {
24578 show_keyhelp( programskeyhelp );
24581 /* stop info cap and get out of program guide */
24582 if ( 'z' == key ) {
24583 if ( CAP_INFO == cap_now )
24584 stop_capture( WHO, FAIL_NONE );
24586 display_type = D_TIMERS;
24587 save_config( WHO );
24588 return;
24591 /* trigger guide load */
24592 if ( 'l' == key ) {
24593 if ( CAP_NONE == cap_now ) {
24594 /* display_type = 0; */ /* don't kick out */
24595 pgmto = utsnow + PGM_INFO_TIMEOUT;
24596 pgm_done = 0;
24597 cap_now = CAP_INFO;
24598 advrlog( LOG_INFO, "load request from EPG");
24599 display_type = D_TIMERS;
24600 return;
24604 if ( 's' == key ) {
24605 get_spam_toggle();
24608 /* add event to timer list */
24609 if ( 't' == key ) {
24610 get_timer_add_event();
24612 /* refresh.timers = 0; */
24613 /* not redrawing list yet */
24614 /* timer_sort = ~0; */
24615 /* but sort it for the redraw later */
24618 /* check for when timer 0 goes active and kick out of
24619 info cap and program guide if timer is active already */
24620 if ( (CAP_INFO == cap_now)
24621 && (timer_idx > 0)
24622 && (T_SEARCH != timer[0].qstat) )
24624 if ( utsnow >= timer[0].start - timer[0].secdt )
24625 stop_capture( WHO, FAIL_NONE );
24626 return;
24628 key = 0;
24632 writes guide info to binary and text file with word wrapping.
24633 NOTE: this is done automatically when guide capture complete,
24634 but it is put here as a manual override for debug purposes.
24636 if ( 'd' == key ) {
24637 dvrlog( LOG_INFO, "EPG%02d manual guide save", pgm.chan);
24638 save_guide();
24641 /* age out, match, hidden, sort and numeric toggles */
24642 if ( 'a' == key ) {
24643 pgm.age = ~pgm.age;
24644 key = 0;
24646 if ( 'm' == key ) {
24647 pgm.find = ~pgm.find;
24648 key = 0;
24650 if ( 'h' == key ) {
24651 pgm.hide = ~pgm.hide;
24652 key = 0;
24654 if ( 'n' == key ) {
24655 pgm.etmids = ~pgm.etmids;
24656 key = 0;
24659 if (0 == key) {
24660 /* should do it when it refreshes, so don't do it here? */
24661 build_pg_epg( epg, &pgm, pg, WHO );
24662 show_event_short();
24663 refresh.epg = 1;
24664 refresh.estats = 1;
24665 show_event_short();
24666 po = 0;
24667 /* continue; */ /* TESTME, wastes cpu cycles? */
24670 /* user hits [ENTER] to start current event */
24671 /* NOTE: noticed a crash here if cap EPG and EIT+ETT still incrementing */
24672 /* so force user to wait until EPG cap done before [ENTER] works */
24673 /* This is caused by the new dynamic memory allocation strategy. */
24675 if ((CAP_NONE == cap_now) && (10 == key)) {
24676 struct sig_s *sig;
24677 sig = &ptc[ pgm.chan ].sig;
24679 /* user overrides last zap program guide timer hold down */
24680 sig->lzpgt = 0;
24682 /* find current program for this time or do nothing */
24683 cp = find_current_epg_pgm();
24684 if (cp >= 0) {
24685 advrlog( LOG_INFO, "%s add search event timer epg[%04X]",
24686 WHO, pg[cp] );
24687 add_search_event_timer( &epg[ pg[ cp ]] );
24689 save_config( WHO );
24690 timer_sort = ~0;
24691 refresh.timers = 1;
24695 /*********************************** GUIDE SCROLLING AND PAGING */
24696 /* scroll up and down non arrow */
24697 if (key == '=') po -= 1;
24698 if (key == '\'') if ( (po+user_lines) < pgm.idx ) po++;
24699 /* arrow keys up and down */
24700 if (key == KB_UP) po -= 1;
24701 if (key == KB_DN) if ( (po+user_lines) < pgm.idx ) po++;
24703 /* scroll left and right non arrow */
24704 if (key == '[') so--;
24705 if (key == ']') so++;
24707 /* arrow keys left and right, home returns to left */
24708 if (key == KB_LF) so--;
24709 if (key == KB_RT) so++;
24710 if (key == KB_HM) so = 0;
24712 /* page forward and back non arrow */
24713 if (key == ' ') po += user_lines;
24714 if (key == 'b') po -= user_lines;
24716 /* page up and page down keys */
24717 if (key == KB_PD) po += user_lines;
24718 if (key == KB_PU) po -= user_lines;
24720 /* quick jump pages 0-9, A-Z, 768 is max programs, misses last 48 */
24721 if ((key >= '0') && (key <= '9')) po = user_lines*(key-'0');
24722 if ((key >= 'A') && (key <= 'Z')) po = user_lines*(10 + (key-'A'));
24724 /* limit page offset */
24725 if (po < 0) po = 0;
24726 if ( (po+user_lines) >= pgm.idx) po = pgm.idx - user_lines;
24727 if (po < 0) po = 0;
24729 /* limit description offset */
24730 if (so < 0) so = 0;
24731 if (so > (PDZ - DES_LIM)) so = PDZ - DES_LIM;
24732 if (so < 0) so = 0;
24734 } /* 0 = i (end of list) */
24736 } /* for i loop */
24738 /* should never get here */
24741 /* show one line of signal meter info for a channel in sig_s *s */
24742 /* if not arg scan, gets ptc[i] from scanlist */
24743 /* static */
24744 void
24745 show_signal ( struct sig_s *s, int i )
24747 int j, b;
24748 char k;
24750 char t[16] = ""; /* pointed to by t1 */
24752 char *t1; /* text1: %30dB %Peak% MHz Wavelen */
24753 char *t2; /* text2: bar */
24754 char *tc1; /* textcolor1: peak is yellow, else normal */
24755 char *tc2; /* textcolor2: bar color */
24756 char *tccl; /* textcolor: channel lock brite */
24757 char *tcas; /* textcolor: auto scan timeout lock brite, GTO magenta */
24759 double cm = 300000000.0; /* speed of light in m/s */
24760 double im = 39.367; /* inches per meter */
24761 double wlm = 0.0; /* wavelength meters */
24762 double wli = 0.0; /* wavelength inches */
24763 double f = 0.0; /* frequency in megacycles */
24764 int fi = 0; /* frequency in kilocycles */
24766 /* slow link needs this:
24768 if (0 == scan_sig) {
24769 nanosleep( &signal_sleep, NULL );
24774 /* colors init to normal */
24775 tc1 = tc2 = tccl = tcas = BN;
24777 /* text init to blanks */
24778 t1 = t2 = "";
24780 /* w key toggles this through strength% peak% mhz or wavelen */
24781 t1 = t;
24783 /* bar is blank unless it has a real signal */
24784 t2 = "";
24786 /* normal operation: get channel # from scan_list */
24787 if (0 == arg_scan) j = scan_list[ i ];
24789 /* strength is 0-100, bar is 50 now, easy math. every 2% */
24791 /* FIXME: sig_col colors might still be a bit off */
24792 s->sig_col = &bc[ 7 & (s->strength/13) ][0];
24794 b = (s->strength / 2 );
24795 if (b > SCALE_BAR) b = SCALE_BAR; /* dont go past end of bar list */
24796 t2 = &sigbar[b][0];
24798 /* create strings for display, in t1 with color in tc1 */
24799 switch( display_sigtype )
24801 /* V4L2 % of 30dB, needs SNR dB version too */
24802 case SIG_STR:
24803 snprintf( t1, 11, " %3d%% ", s->strength);
24804 break;
24806 /* frequency to device */
24807 case SIG_FMHZ:
24808 snprintf( t1, 10, "%9d", s->freq );
24809 break;
24811 /* wavelength */
24812 case SIG_WLEN:
24813 fi = ptc[ s->chan ].sig.freq; /* LUT */
24814 /* TESTME: was changed from V4L that used freq * 1000.0 */
24815 f = 1.0 * fi; /* megahertz */
24816 wlm = cm / f; /* wavelength in meters */
24817 wli = wlm * im; /* wavelength in inches */
24818 snprintf( t1, 11, " %4.1f\042", wli);
24819 break;
24821 /* 2.2 decimal from 8.8 binary */
24822 case SIG_SNR:
24823 snprintf( t1, 11, " %2d.%02d dB", 0xFF & (s->snr>>8),
24824 ((0xFF & s->snr) * 100) >> 8 );
24825 break;
24827 #ifdef USE_DVB_EXPERIMENTAL
24828 /* frequency returned by new driver */
24829 case SIG_BER:
24830 /* only want 5 digits */
24831 snprintf( t1, 11, " %5d", s->ber );
24832 break;
24834 /* frequency offset calc from new driver return, 6 digits + sign */
24835 case SIG_FOHZ:
24836 snprintf( t1, 11, "%+6d Hz", s->fohz);
24837 break;
24839 #endif
24840 default:
24841 break;
24844 tc2 = s->sig_col;
24846 /* only change numeric to error text on signal scan */
24847 if ( SIG_STR == display_sigtype) {
24849 if (0 == s->lock) {
24850 tc1 = BR;
24851 tc2 = BR;
24852 t1 = " NO LOCK";
24853 t2 = "";
24856 /* test with real device */
24857 if ( (0 == arg_dummy) && (0 != test_mode) ) {
24858 tc1 = BW;
24861 /* no scan overrides value and sigbar */
24862 if (0 == scan_sig) {
24863 tc1 = BN;
24864 t1 = " SCAN OFF" SCV; /* value */
24865 tc2 = BN;
24866 t2 = ""; /* sigbar */
24867 if ( (0 != arg_dummy) && (0 != test_mode) ) {
24868 tc1 = BW;
24869 t1 = " SCAN ON " SCV;
24870 } /* dummy */
24874 /* no scan overrides sigbar
24875 if (0 == scan_sig) {
24876 tc2 = BN;
24877 t2 = "";
24881 /* channel lock brightens the hotkey */
24882 if ( (scan_pos == i) && (scan_one != 0) ) tcas = BW;
24884 /* auto guide colors channel number and callsign */
24885 if ( 0 != s->pgto) tccl = BM;
24887 k = ' ';
24889 /* signal strength same as bar color */
24890 tc1 = tc2;
24892 /* 1 to 9 or A to Z for channel hot-key display */
24893 if (0 == arg_scan) k = (i<9) ? i + 49 : i + 56;
24895 if (0 == scan_sig) { tc2 = ""; t2 = ""; }
24897 /* bar isn't really needed for cable, is always 99-100% */
24898 if (0 != arg_cable) { tc2 = ""; t2 = ""; }
24900 /* Hotkey Channel Call Net SIG% ************ BAR *********** */
24901 aprintf( stderr,
24902 BN "%s%c" /* hotkey */
24903 BN " %s%3d" /* color, channel number */
24904 " %7s" /* callsign */
24905 BN " %3s" /* net/station id */
24906 " %s%7s " /* color, signal strength % */
24907 BN " %s%s" /* color, signal strength bar */
24908 BN SCV CEL,
24910 tcas, k, /* color hotkey */
24911 tccl, s->chan, /* color channel */
24912 s->call, /* callsign */
24913 s->sid, /* network id */
24914 tc1, t1, /* color strength or misc */
24915 tc2, t2 /* color strength bar or OFF */
24920 /* add text to capture log text array */
24921 /* static */
24922 void
24923 add_cap_text( char *s )
24925 char *t;
24927 /* ignore blanks */
24928 if ( 0 == s[0] ) return;
24930 /* NOTE: silently fails when past line max */
24931 if (cap_idx < CAP_ROWS) {
24932 t = &cap_text[cap_idx][0];
24933 snprintf( t, strlen(s)+1, "%s", s);
24934 cap_idx++;
24938 /* static */
24939 void
24940 build_cap_text ( void )
24942 char r[ 60 * 8 ]; /* 60 seconds with possible diff color code for each */
24943 char hms[ CAP_COLS * 8 ];
24944 int i, j, k, sn, sg, sb;
24945 char *tc; /* point to text color */
24946 static char ltc[16]; /* last text color */
24948 memset( cap_text, 0, sizeof(cap_text));
24949 cap_idx = 0;
24950 /* if (utsets < 2) return; // too few seconds? */
24952 /* anything above 30 transport errors/second may be unwatchable */
24953 snprintf( r, sizeof(r)-1,
24954 BN "Errors/sec: " BG "GOOD: .=0,1-9 " BY "OK: a-z=10-35 " BR "BAD: A-Z=36-61 # >61 * >99 " BW "?=na");
24955 add_cap_text( r );
24956 snprintf( r, sizeof(r)-1, BN " Wall Cap 0 1 2 3 4 5");
24957 add_cap_text( r );
24958 snprintf( r, sizeof(r)-1, BN "hh:mm:ss " BW "hh:mm:" BN "012345678901234567890123456789012345678901234567890123456789");
24959 add_cap_text( r );
24961 /* step thru every 60 bytes */
24962 for (i = 0; i < utsets; i += 60 )
24964 int h,m,s, h1, m1, s1;
24966 if (i >= CAP_STZ) {
24967 add_cap_text( "Capture log limit reached.");
24968 add_cap_text( "Recompile with larger CAP_STZ.");
24969 break; /* don't go past buffer limit */
24971 /* show minute at start of line */
24972 h1 = h = (i / 3600) % 24;
24973 m1 = m = (i / 60) % 60;
24974 s1 = s = i % 60;
24976 /* tloc3 is start cap timeval */
24977 s1 += tloc3.tm_sec;
24978 if ( s1 > 60 ) {
24979 s1 %= 60;
24980 m1++;
24983 m1 += tloc3.tm_min;
24984 if ( m1 > 60 ) {
24985 m1 %= 60;
24986 h1++;
24989 h1 += tloc3.tm_hour;
24990 h1 %= 24;
24992 /* show wall clock and capture clock to the minute */
24993 snprintf( hms, sizeof(hms)-1, BN "%02d:%02d:%02d " BW "%02d:%02d " BN,
24994 h1, m1, s1, h, m);
24996 memset( ltc, 0, sizeof(ltc)); /* reset color so first test will set */
24997 memset(r, 0, sizeof(r)); /* clear it for this round */
24998 k = 0;
25000 /* build text for seconds status for rest of line */
25001 for (j = 0; j < 60; j++)
25003 short t; /* match cap stats data type */
25004 unsigned char t1;
25006 /* stop at current second of current minute */
25007 if ( (i+j) >= CAP_STZ) {
25008 add_cap_text("Capture log limit reached.");
25009 add_cap_text("Recompile with larger CAP_STZ.");
25010 break; /* don't go past buffer limit */
25012 if ( (i+j) >= utsets ) break; /* don't go past current time */
25014 t = cap_stats[ i + j ];
25016 t1 = ' '; /* want to know if still ' ' */
25017 /* 1-9 a-z A-Z, ASCII */
25018 if ((t > 0) && (t < 62)) {
25019 t1 = '0'+t; /* intermittent 0-9 is watchable */
25020 if (t > 9) t1 = 'a' + (t-10); /* not good */
25021 if (t > 35) t1 = 'A' + (t-36); /* gettin bad */
25024 if (t > 61) t1 = '#'; /* really bad */
25025 if (t > 99) t1 = '*'; /* doesnt matter after this bad */
25027 if (t == 0 ) t1 = '.'; /* no errors is on the dot */
25028 if (t < 0 ) t1 = '?'; /* -1 is no data available */
25030 if (t < 0) { /* no data collected */
25031 sn++; /* count no data seconds */
25032 } else {
25033 if (t < 36 ) {
25034 sg++; /* count good seconds */
25035 } else {
25036 sb++; /* count bad seconds */
25040 tc = BN;
25041 if (t > 35 ) tc = BR; /* 35 and above is red */
25042 if ( (t > 0) && (t < 10) ) tc = BG; /* 0-9 is green, good enough */
25043 if ( (t > 9) && (t < 36) ) tc = BY; /* 10 to 35 is yellow */
25044 if (-1 == t) tc = BW; /* no data is white */
25046 /* check against previous color used */
25047 if (0 == strncmp( tc, ltc, 8)) { /* FIXME: use constant */
25048 tc = ""; /* same doesn't emit color again */
25049 } else {
25051 /* FIXME: constant to describe why it is 8, ECMA_COLOR_LEN */
25052 astrncpy( ltc, tc, sizeof(ltc) );
25053 /* diff color copies for next test */
25056 if (strlen(tc) > 0) {
25057 memcpy( &r[k], tc, strlen(tc) ); /* emit color */
25058 k+= strlen(tc);
25061 r[k] = t1; /* error indication or . for no error */
25062 k++;
25064 snprintf( &hms[strlen(hms)], sizeof(hms)-1, "%s", r);
25065 add_cap_text( hms );
25070 /* similar output to dump cap log below, but on-screen instead of to file */
25071 /* static */
25072 void
25073 show_caplog ( void )
25075 int i, j, lo;
25076 unsigned char key;
25077 char f[256];
25078 char *t;
25079 int now;
25081 key = 0;
25082 lo = 0;
25084 now = utsnow;
25086 memset( cap_text, 0, sizeof(cap_text));
25088 if ( (utsets < 2) || (utsets > sizeof(cap_stats)) || (0 == cap_prev) ) {
25089 dvrlog( LOG_INFO, "%s utsets %d range", WHO, utsets );
25090 return; /* too many seconds? */
25093 filebase( f, out_name, F_FILE);
25095 /* NOTE:
25096 Set log offset start line to legend lines + current minute to make first
25097 view of >13m log jump ahead to current minute at top line, so it can
25098 fill up the display for user lines minutes before needing user to scroll.
25099 Tried lock-follow within loop but it didn't work very well. Logic Puzzles.
25100 A kind of scroll wrap that clears rest only if top line redrawn might do.
25102 if ( (utsets / 60) > (user_lines-3) )/* legend if minutes < lines */
25103 lo = 3 + utsets/60; /* otherwise jump past legend and old minutes */
25105 refresh.log = 1;
25106 build_cap_text();
25108 for (i=0; i<user_lines;)
25110 if ( D_LOG != display_type ) return; /* kick out */
25112 show_status( 1, WHO ); /* clock and cap stats need to update */
25114 j = i + lo;
25116 if (refresh.log) {
25117 aprintf( stderr, "\033[%d;1H" "%s" CEL, 5+i, &cap_text[j][0] );
25120 i++;
25121 i %= user_lines;
25122 key = 0;
25124 /* end of list redraws header, looks for key and sleeps if no key */
25125 if (0 == i)
25127 t = chan_name; /* "Current"; */
25128 /*if (CAP_NONE == cap_now) t = "Last"; */
25130 /* update is slow and not reflecting current QoS%, hold a key to update fast */
25131 if (0 != refresh.log)
25133 aprintf( stderr, DCA_MGT SCI BW
25134 "%s Capture Log for %s "
25135 BW BL"Keys: " BN BR " [f] exit" BN CEL, t, f);
25137 nanosleep( &console_read_sleep, NULL);
25138 /* get_time(); */
25139 build_cap_text(); /* might eat cpu */
25142 refresh.log--;
25143 if (refresh.log <0) refresh.log = 0;
25145 console_getch_fn( &key );
25146 nanosleep( &console_read_sleep, NULL);
25147 if (0 == key) continue;
25149 /* any key refreshes */
25150 refresh.log = 1;
25152 /* same key in and out */
25153 if ( 'f' == key ) return;
25155 /* z key stops info cap, but only info cap */
25156 if ( 'z' == key ) {
25157 if ( CAP_INFO == cap_now) {
25158 stop_capture( WHO, FAIL_NONE );
25160 return;
25163 if ( '?' == key ) show_keyhelp( caplogkeyhelp );
25165 if ( KB_UP == key ) lo -= 1;
25166 if ( '=' == key ) lo -= 1;
25168 if ( KB_DN == key ) lo += 1;
25169 if ( '\'' == key ) lo += 1;
25171 if ( KB_PU == key ) lo -= user_lines;
25172 if ( KB_PD == key ) lo += user_lines;
25174 if (lo < 0) lo = 0; /* sanity limit */
25175 if ( (lo+user_lines) >= cap_idx) lo = cap_idx - user_lines;
25176 if (lo < 0) lo = 0; /* sanity limit */
25181 /* write to .tsx file:
25182 sequence[] long longs, start byte offsets, list terminated by -1LL.
25183 frames[] structure of picture types found and packet counts per type.
25184 This is called at end of cap, so cap_prev is assumed to be valid.
25186 /* static */
25187 void
25188 dump_frame_sequence ( void )
25190 FILE *f;
25191 int i, ok;
25192 long long seq;
25193 struct frame_s frm;
25195 char n[256];
25196 char o[256];
25198 /* list of hold-downs. the order is important for this */
25199 if (CAP_INFO == cap_prev) return; /* not Event Program Guide Load */
25200 if (0 != cap_zap) return; /* not zapped files */
25201 if (cap_prpn < 1) return; /* not a single program capture */
25202 if (0 == arg_frames) return; /* not without -m */
25203 if (0 == sequence_idx) return; /* not without sequences */
25204 if (0 == cap_frames) return; /* out of frames overflow? */
25207 /* hold downs complete */
25208 filebase( n, out_name, F_PFILE );
25209 advrlog( LOG_INFO, "%s %d frames %d sequences",
25210 WHO, frame_idx, sequence_idx );
25212 /* open .tsx file */
25213 memset( o, 0, sizeof(o));
25214 snprintf( o, sizeof(o)-1, "%s.tsx", n );
25215 f = fopen( o, "w");
25217 if (NULL != f) {
25219 /* expand and write sequence data */
25220 seq = 0;
25221 for (i = 0; i < sequence_idx; i++) {
25222 seq += 0xFFFFFFFFLL & sequences[i];
25223 ok = fwrite( &seq, sizeof(seq), 1, f);
25224 if (1 != ok) {
25225 dvrlog( LOG_INFO, "failed to write sequences\n");
25226 fclose(f);
25227 return;
25230 seq = -1LL;
25231 ok = fwrite( &seq, sizeof(seq), 1, f);
25232 if (1 != ok) {
25233 dvrlog( LOG_INFO, "failed to write sequence terminator\n");
25234 fclose(f);
25235 return;
25238 /* expand and write frame data */
25239 frm.vpn = 0;
25240 for (i = 0; i < frame_idx; i++) {
25241 frm.pct = 3 & (frames[i] >> 14);
25242 frm.vpn += 0x3FFF & frames[i];
25243 ok = fwrite( &frm, sizeof(frm), 1, f);
25244 if (1 != ok) {
25245 dvrlog( LOG_INFO, "failed to write frames\n");
25246 fclose(f);
25247 return;
25251 fflush( f );
25252 fclose( f );
25253 } else {
25254 dvrlog( LOG_INFO, "%s error writing %s", WHO, o );
25257 advrlog( LOG_INFO, "%s done", WHO);
25264 show list of channels with callsigns and sids, DCA to line offset + 3.
25265 call this after display_type D_CHANNELS picked to draw list of channels.
25267 /* static */
25268 void
25269 show_chanlist ( void )
25271 int i, j;
25272 struct sig_s *s;
25273 char c[16]; /* DCA string */
25275 /* if displaying channel list */
25276 if (D_CHANNELS != display_type) return;
25277 if (0 == refresh.channels) return;
25278 refresh.channels = 0;
25280 show_status( 0, WHO );
25282 /* redraw is easier to notice when cursor is on */
25283 aprintf( stderr, "%s", SCI );
25285 for (j=0; j < user_lines; j++) {
25287 /* 7 bytes color, 7 bytes DCA, near 16 byte limit */
25288 snprintf( c, sizeof(c)-1, BN "\033[%d;1H", 3 + j );
25289 aprintf( stderr, "%s" CEL, c );
25291 /* blank the usable rows after last channel */
25292 i = channel_offset + j;
25293 if (i >= scan_idx) continue;
25294 s = &ptc[ scan_list[ i ] ].sig;
25296 /* doesn't need sleep here, this is refresh screen only */
25297 // console_scan();
25298 // nanosleep(&console_read_sleep, NULL);
25300 /* stop display if not in channel scan display anymore */
25301 if (D_CHANNELS != display_type) break;
25302 show_signal( s, i );
25310 TODO FIXME: re-write this to integrate pgm guide, channel list, timer list
25311 and vct/mgt lists and keys that are active in those screens
25313 needs to do different things based on display_type
25314 it's a big restructuring so it may never get done
25318 this is where you want to do user functions or set flags based on keys
25319 current key definitions: (incomplete)
25320 1-9 or A-Z is for channel hotkeys
25321 see keyhelp[] for rest
25322 function keys handled above in console esc
25324 /* static */
25325 void
25326 console_scan ( void )
25328 int i;
25329 // int x, y;
25330 unsigned char kb;
25331 struct sig_s *s;
25333 i = 0; // hush compiler
25334 s = &ptc[cap_chan].sig;
25336 /* sleep done elsewhere
25337 nanosleep( &console_read_sleep, NULL );
25340 /* do nothing if -d or -s. detach & scripted cap do not need user input */
25341 if (arg_detach != 0) return;
25342 /* if (arg_capture != 0) return; */
25344 console_getch_fn( &kb );
25346 if (kb == 0) return;
25348 /* force stale auto-guide to wait a few seconds if any key is hit */
25349 get_time();
25350 if (utstpg < utsnow) utstpg = utsnow + (300 * (1 + arg_devnum));
25352 /* -K console_getch disables echo first, then get outs */
25353 if (arg_nokb != 0) return;
25355 /* any key will reset the no scan timeout */
25356 if (kb != 0)
25357 /* NOTE: adjust this for at least one pass thru chanlist */
25358 utsfut = utsnow + SIGNAL_SCAN_TIMEOUT;
25360 /********************************
25361 LOOK FOR THESE KEYS ALWAYS
25362 ********************************/
25364 switch(kb) {
25366 /* do nothing */
25367 case 0:
25368 return;
25369 break;
25371 case 'j':
25372 dump_epg_grid();
25373 #if 0
25374 for (i=0; i<search_idx; i++)
25375 dvrlog(LOG_INFO, "%s", search_list[i].name);
25376 #endif
25377 break;
25380 /* DEBUG: manual force test guides */
25381 case 'i':
25382 dvrlog( LOG_INFO, "User requests test guides");
25383 utstpg = utsnow - 1;
25385 /* let the main show status loop catch it */
25386 /* test_epgs( WHO ); */
25387 fprintf( stderr, HOM BC"%-32s", "OK: Guide Test");
25388 break;
25390 #ifdef USE_MCAST
25391 case 'u':
25392 udp_mcast = ~udp_mcast;
25393 break;
25394 #endif
25396 /* show cap log */
25397 case 'f':
25398 if (utsets < 1) break;
25400 display_type = D_LOG;
25401 refresh.log = 1;
25402 show_caplog();
25403 display_type = D_TIMERS;
25404 set_refresh();
25405 show_status( 0, WHO );
25406 break;
25408 /* MGT table */
25409 case 'g':
25410 if (0 == pkt.fmgt) break;
25412 display_type = D_MGT;
25413 refresh.mgt = 1;
25414 show_master_guide_table();
25415 display_type = D_TIMERS;
25416 set_refresh();
25417 show_status( 0, WHO );
25418 break;
25420 /* Virtual Channel Table display */
25421 case 'v':
25422 if (0 == vc_ncs) {
25423 dvrlog( LOG_INFO, "%s no VCT to display", WHO);
25424 break;
25426 display_type = D_VCT;
25427 refresh.vct = 1;
25428 show_virtual_channels();
25429 display_type = D_TIMERS;
25430 set_refresh();
25431 show_status( 0, WHO );
25432 break;
25434 /* Program Guide */
25435 case 'p':
25437 scan_sig = 0; /* sig scan bogs program guide */
25439 #if 0
25440 if (0 == pgm.idx) break;
25441 /* extra annoying hold-downs */
25442 /* unselect any guide filters if no valid events */
25443 if (pgm.idx == 0) {
25444 pgm.age = 0;
25445 pgm.hide = 0;
25446 pgm.find = 0;
25448 #endif
25449 display_type = D_EPG;
25450 refresh.epg = 1;
25451 show_program_guide();
25453 /* in case any new timers added */
25454 /* dump_epg_html( epg, &pgm, pg, WHO ); */
25455 display_type = D_TIMERS;
25456 set_refresh();
25457 show_status( 0, WHO ); /* no wait for timer list */
25458 break;
25460 /* load program guide from current channel */
25461 case 'l':
25462 /* only if no cap already in progress */
25463 if (CAP_NONE == cap_now)
25464 cap_now = CAP_INFO;
25466 break;
25468 /* toggle ATSC/MPEG2 table and packet counts */
25469 case 's':
25470 refresh.pstats = 1;
25471 display_stat = ~display_stat;
25472 test_termsize(); /* pkt stat changes number of lines to display */
25473 set_refresh();
25474 show_status( 0, WHO );
25475 break;
25477 /* toggle timer time display between user and timer mode */
25478 case 'e':
25479 if (CAP_TIME == cap_now) {
25480 cap_etime = ~cap_etime;
25481 refresh.headers = 1;
25482 show_headers(0);
25483 /* show_status( 0, WHO ); */
25485 break;
25487 /* DEL (BS backspace key in console and aterm) (stop manual cap) */
25488 case 127:
25489 if ( (CAP_INFO == cap_now) || (CAP_USER == cap_now) ) {
25490 stop_capture( WHO, FAIL_NONE );
25491 scan_sig = ~0;
25493 break;
25495 /* DEL (BS backspace key in xterm) (stop manual cap) */
25496 case 8:
25497 if ( (CAP_INFO == cap_now) || (CAP_USER == cap_now) ) {
25498 stop_capture( WHO, FAIL_NONE );
25499 scan_sig = ~0;
25501 break;
25503 /* override timer, switches to manual cap mode */
25504 case 'o':
25505 if (CAP_TIME == cap_now) {
25507 #if 0
25508 /* FIXME: this causes problems with making capture stop
25509 reque or remove timer0 before going manual
25510 reschedule_timer(0, WHO);
25511 refresh.timers = 1;
25513 #endif
25514 cap_now = CAP_USER;
25516 break;
25518 /* reschedule weekday or remove volatile timer. stops cap if index is 0 */
25519 case 'r':
25520 timer_reschedule();
25521 /* timer may reschedule or expire, so refresh timer list */
25522 display_type = D_TIMERS;
25523 set_refresh();
25524 timer_sort = ~0;
25525 break;
25527 /* same as reschedule for active timer 0 and also deletes the capture */
25528 case 'z':
25529 timer_zap();
25530 /* timer may reschedule or expire, so refresh timer list */
25531 display_type = D_TIMERS;
25532 set_refresh();
25533 timer_sort = ~0;
25535 /* without this, if weekday timer zapped before end of timeslot,
25536 it reloads it in slot 0 because config file changed?
25538 TESTME: not supposed to save config on weekday timer reschedule...
25541 show_timers();
25543 /* wait one minute before doing next test epg */
25544 if (utsnow > utstpg) utstpg += PROGRAM_GUIDE_TIMEOUT;
25545 break;
25548 #if DEBUG
25549 /* clear all timers */
25550 case '!':
25551 reset_timers();
25552 aprintf(stderr, "\033[%d;1H" CCE, 4); /* keep header */
25553 show_headers(0);
25554 break;
25555 #endif
25557 /* too easy to hit instead of load guide */
25558 #if 0
25559 /* toggle quiet mode on or off */
25560 case 'k':
25561 arg_quiet = ~arg_quiet;
25562 show_headers(1);
25563 refresh.timers = 1;
25564 show_chanlist();
25565 show_timers();
25566 break;
25567 #endif
25569 /* toggle timer/channel list */
25570 case 9:
25571 if ( CAP_NONE != cap_now ) break; /* not while capture */
25572 /* if ( 0 != arg_cable ) break; // let cable show the long list */
25574 /* not as nice as an invert speedwise but doesn't matter here */
25575 if ( D_TIMERS == display_type ) {
25576 display_type = D_CHANNELS;
25577 refresh.channels = 1;
25578 } else {
25579 if ( D_CHANNELS == display_type ) {
25580 display_type = D_TIMERS;
25581 set_refresh();
25585 /* clear from start of line 3 to end */
25586 snprintf( csp, 16, "\033[3;1H");
25587 aprintf( stderr, "%s" CCE, csp);
25588 show_headers(0);
25590 /* enable signal scan for all channels */
25591 if ( D_CHANNELS == display_type )
25593 scan_sig = ~0;
25594 scan_one = 0;
25595 /* draw initial channel signal list display */
25596 show_chanlist();
25597 /* will be updated automatically by signal scan */
25600 show_status( 0, WHO); /* will call show_timers() */
25601 break;
25603 /* FF ^L clears tube, redraws headers */
25604 case 12:
25605 set_title(); /* FIXME: doesn't work over ssh + screen */
25606 set_refresh();
25607 timer_sort = ~0;
25609 /* will increment to first channel */
25610 /* scan_next = scan_idx; */
25611 /* scan_one = 0; */
25612 show_status( 0, WHO );
25613 show_chanlist();
25614 break;
25616 /* set a timer for the current channel */
25617 case KB_IN:
25618 case 't':
25619 get_timer_add();
25620 set_refresh();
25621 timer_sort = ~0;
25622 break;
25624 /* delete a timer by # */
25625 case KB_DL:
25626 case 'd':
25627 if (timer_idx > 0) {
25628 get_timer_delete();
25629 set_refresh();
25631 break;
25633 /* move a timer by # to config by # */
25634 case 'm':
25635 if (timer_idx > 0) {
25636 get_timer_move();
25637 set_refresh();
25639 break;
25641 /* quit */
25642 case 'q':
25643 if (CAP_NONE == cap_now) /* prevent accidental quit */
25644 // if (0 != arg_tmpfs) save_tmpfs( WHO );
25645 console_exit(0);
25646 break;
25648 /* shift for both of these to prevent accidents */
25649 /* input configuration file */
25650 case '_':
25651 advrlog( LOG_INFO, "user loads cfg" );
25652 /* reset test config update time */
25653 utstcf = 0; /* clear the hold down */
25654 test_config(1, WHO ); /* 1 forces load */
25655 break;
25657 /* output configuration file */
25658 case '+':
25659 advrlog( LOG_INFO, "user saves cfg" );
25660 save_config( WHO );
25661 break;
25664 /* timer/channel scroll control, up down, ' and = if no edit pad,
25665 up, down, page up and page down need edit pad or alt keypad
25667 case KB_UP:
25668 case '=': /* equal sign */
25669 if (D_CHANNELS == display_type) {
25670 if (channel_offset < 0) channel_offset = 0;
25671 if (channel_offset > 0) channel_offset--;
25672 scan_pos = channel_offset;
25673 refresh.channels = 1;
25674 show_chanlist();
25676 if (D_TIMERS == display_type) {
25677 if (timer_offset < 0) timer_offset = 0;
25678 if (timer_offset > 0) timer_offset--;
25679 refresh.timers = 1;
25680 show_timers();
25682 break;
25684 case KB_PU:
25685 if (D_CHANNELS == display_type) {
25686 if (channel_offset > user_lines) {
25687 channel_offset -= user_lines;
25688 } else {
25689 channel_offset = 0;
25691 scan_pos = channel_offset;
25692 refresh.channels = 1;
25693 show_chanlist();
25696 if (D_TIMERS == display_type) {
25698 if (timer_offset > user_lines) {
25699 timer_offset -= user_lines;
25700 } else {
25701 timer_offset = 0;
25703 if (timer_offset < 0) timer_offset = 0;
25704 refresh.timers = 1;
25705 show_timers();
25707 break;
25709 case KB_DN:
25710 case '\'': /* single quote */
25711 if (D_CHANNELS == display_type) {
25712 if (channel_offset < (channel_idx - user_lines))
25713 channel_offset++;
25714 if (channel_offset >= channel_idx)
25715 channel_offset = channel_idx - user_lines;
25716 scan_pos = channel_offset;
25717 refresh.channels = 1;
25718 show_chanlist();
25721 if (D_TIMERS == display_type) {
25722 if (timer_offset < (timer_idx - user_lines))
25723 timer_offset++;
25724 // if (timer_offset >= timer_idx) timer_offset = 0;
25725 if (timer_offset >= timer_idx)
25726 timer_offset = timer_idx - user_lines;
25727 refresh.timers = 1;
25728 show_timers();
25730 break;
25732 case KB_PD:
25733 if (D_CHANNELS == display_type) {
25734 if ((channel_offset+user_lines) < (channel_idx-user_lines)) {
25735 channel_offset += user_lines;
25736 } else {
25737 channel_offset = channel_idx - user_lines;
25739 scan_pos = channel_offset;
25740 refresh.channels = 1;
25741 show_chanlist();
25744 if (D_TIMERS == display_type) {
25745 if ((timer_offset + user_lines) < (timer_idx - user_lines)) {
25746 timer_offset += user_lines;
25747 } else {
25748 timer_offset = timer_idx - user_lines;
25750 // if (timer_offset >= timer_idx) timer_offset = 0;
25751 if (timer_offset >= timer_idx)
25752 timer_offset = timer_idx - user_lines;
25753 refresh.timers = 1;
25754 show_timers();
25756 break;
25758 case KB_HM:
25759 if (D_CHANNELS == display_type) {
25760 channel_offset = 0;
25761 refresh.channels = 1;
25762 show_chanlist();
25765 if (D_TIMERS == display_type) {
25766 timer_offset = 0;
25767 refresh.timers = 1;
25768 show_timers();
25770 break;
25772 case KB_EN:
25773 if (D_CHANNELS == display_type) {
25774 channel_offset = channel_idx - user_lines;
25775 refresh.channels = 1;
25776 show_chanlist();
25778 if (D_TIMERS == display_type) {
25779 timer_offset = timer_idx - user_lines;
25780 refresh.timers = 1;
25781 show_timers();
25783 break;
25785 /* keyboard help */
25786 case '/':
25787 case '?':
25788 advrlog( LOG_INFO, "user needs help" );
25790 refresh.timers = 0;
25791 show_keyhelp( timerskeyhelp );
25792 set_refresh();
25793 show_status( 0, WHO );
25794 break;
25796 /* range checks would go here if there were any for this mode */
25797 default:
25798 /* super debug */
25799 advrlog( LOG_INFO, "console any scan ignores key 0x%X", kb);
25800 break;
25802 /* done with keys available during capture */
25805 /************************************************
25806 LOOK FOR THESE KEYS IN TIMER/CHANNEL LISTS WITH NO CAPTURE IN PROGRESS
25807 ************************************************/
25809 if (CAP_NONE == cap_now) {
25810 switch( kb ) {
25812 #ifdef USE_POWERDOWN
25813 /* close device within 2s, so you can run something else on dvb device */
25814 case 'c':
25815 scan_sig = 0;
25816 scan_one = ~0;
25817 utspdd = utsnow + 2;
25818 break;
25819 #endif
25822 #if 0
25823 /* Start full cap no matter what current pn/vc/va set. */
25824 /* It works, but don't like the vc override. use VCT Guide instead. */
25825 /* It's also too close to the enter key. */
25826 case '\\':
25827 cap_now = CAP_USER;
25828 cap_pn = -1;
25829 cap_vc = -1;
25830 cap_va = -1;
25831 break;
25832 #endif
25834 /* ENTER key: full cap or vid+aud selected in VCT Guide. */
25835 case 10:
25836 cap_pn = s->pn;
25837 cap_vc = s->vc;
25838 cap_va = s->va;
25840 /* if pn set and epg exists, find current event in EPG and add a timer */
25841 if ((CAP_NONE == cap_now) && (cap_pn > 0)) {
25842 int cp;
25843 cp = find_current_epg_pgm();
25844 if (cp >= 0) {
25845 dvrlog( LOG_INFO,
25846 "%s add search event timer epg[%04X]",
25847 WHO, pg[cp] );
25848 s->lzpgt = 0;
25849 add_search_event_timer( &epg[ pg[ cp ]] );
25851 save_config( WHO );
25852 timer_sort = ~0;
25853 refresh.timers = 1;
25854 break;
25858 cap_now = CAP_USER;
25860 break;
25862 #if 0
25863 /* this is different than 'x' above that resets signal stats only */
25864 /* reset peaks, refresh */
25865 case 'x':
25867 /* only for channel list */
25868 if (D_CHANNELS != display_type) break;
25869 for ( x=0; x < scan_idx; x++ ) {
25870 struct scanlist_s *n;
25871 n = ptc;
25872 y = scan_list[x];
25873 s = &n[y].sig;
25874 s->strength = 0;
25875 s->str_avg = 0;
25876 s->snr = 0;
25877 s->lock = s->lock_sum = 0;
25879 show_chanlist();
25880 break;
25881 #endif
25883 /* SP (spacebar key) channel lock toggle */
25884 case 32:
25885 scan_one = ~scan_one;
25886 /* if unlocking, turn on scan, timer may disable it later */
25887 if (scan_one == 0) scan_sig = ~0;
25888 break;
25890 /* auto EPG toggle, new feature for pchdtvr 1.0 */
25891 case 'a':
25893 s = &ptc[ scan_list[scan_pos] ].sig;
25895 if (0 != s->pgto) {
25896 s->pgto = 0;
25897 s->pgmt = -1;
25898 } else {
25899 s->pgto = 1; /* default is 3 hours */
25900 s->pgmt = 0;
25901 /* utstpg = utsnow + PROGRAM_GUIDE_TIMEOUT; */
25903 advrlog( LOG_INFO, "APG%02d AGTO %d", s->chan, s->pgto);
25904 save_config( WHO );
25906 break;
25908 /* toggle snr display header/value change */
25909 case 'w':
25910 display_sigtype++;
25911 /* -x goes thru entire list including regular basic info */
25912 display_sigtype %= SIG_MODREG;
25913 show_headers(0);
25914 refresh.channels = 1;
25915 refresh.headers = 1;
25916 show_chanlist();
25917 break;
25919 /* range check instead of 35 cases for channel hotkeys */
25922 /* first group of channel hotkeys are numeric 1-9 */
25923 #define USE_ISOC_PEDANTIC
25924 #ifdef USE_ISOC_PEDANTIC
25925 case '1':
25926 case '2':
25927 case '3':
25928 case '4':
25929 case '5':
25930 case '6':
25931 case '7':
25932 case '8':
25933 case '9':
25934 #else
25935 case '1' ... '9':
25936 #endif
25938 /* plus one for scan_next to be 1-n instead of 0-n */
25939 scan_next = kb - '0'; /* 1 - 9 */
25941 /* stay in list */
25942 if (scan_next > scan_idx) break;
25944 /* loop around */
25945 // if (scan_next >= scan_idx) scan_next %= scan_idx;
25947 /* lock and scan for signal */
25948 scan_freq = 0;
25949 scan_one = ~0;
25951 /* if hit same channel twice and on bcast toggle scan sig, else set,
25952 unless esave or cable, then make user hit it twice to open device.
25954 if (scan_pos == (scan_next - 1) ) {
25955 scan_sig = ~scan_sig;
25956 scan_next = 0;
25957 } else {
25958 scan_sig = ~0;
25959 // scan_sig = 0; // doesnt change channels correctly
25961 /* channel change resets bottom line table found indicators */
25962 pkt.fvct = 0;
25963 pkt.fpat = 0;
25964 pkt.fpmt = 0;
25965 pkt.fmgt = 0;
25966 chan_name[0] = 0;
25969 if (scan_next != 0) {
25970 cap_chan = scan_list[ scan_next - 1 ];
25971 } else {
25972 cap_chan = scan_list[ scan_pos ];
25976 /* hot key needs to load guide to make console update faster */
25977 load_guide( cap_chan, WHO );
25980 if (0 != vc_ncs) set_vc(WHO);
25983 /* faster update than waiting on status */
25984 if (0 != vc_ncs) {
25985 refresh.vstats = 1;
25986 show_vc_selection();
25988 refresh.estats = 1;
25989 show_event_short();
25991 /* hitting the key should make it set the channel */
25992 scan_freq = 0;
25994 break;
25996 /* second group of channel hotkeys are alpha A-Z */
25997 #ifdef USE_ISOC_PEDANTIC
25998 case 'A':
25999 case 'B':
26000 case 'C':
26001 case 'D':
26002 case 'E':
26003 case 'F':
26004 case 'G':
26005 case 'H':
26006 case 'I':
26007 case 'J':
26008 case 'K':
26009 case 'L':
26010 case 'M':
26011 case 'N':
26012 case 'O':
26013 case 'P':
26014 case 'Q':
26015 case 'R':
26016 case 'S':
26017 case 'T':
26018 case 'U':
26019 case 'V':
26020 case 'W':
26021 case 'X':
26022 case 'Y':
26023 case 'Z':
26024 #else
26025 case 'A' ... 'Z':
26026 #endif
26028 scan_next = kb - 'A'; /* 11 - 36 */
26029 scan_next += 10;
26031 /* stay in list */
26032 if (scan_next > scan_idx) break;
26034 /* lock and scan for signal */
26035 scan_freq = 0;
26036 scan_one = ~0;
26038 /* if hit same channel twice and on bcast toggle scan sig, else set,
26039 unless esave or cable, then make user hit it twice to open device.
26041 if (scan_pos == (scan_next - 1) ) {
26042 scan_sig = ~scan_sig;
26043 scan_next = 0;
26044 } else {
26045 scan_sig = ~0;
26046 // scan_sig = 0; // doesn't change channels correctly
26048 #if REMOVEME
26049 /* do not reset cap pids */
26050 /* cap_vc = cap_va = -1; */
26051 /* cap_pidx = 0; */
26052 #endif
26054 /* channel change resets bottom line indicators */
26055 pkt.fvct = 0;
26056 pkt.fpat = 0;
26057 pkt.fpmt = 0;
26058 pkt.fmgt = 0;
26059 chan_name[0] = 0;
26062 if (0 != scan_next) {
26063 cap_chan = scan_list[ scan_next - 1 ];
26064 } else {
26065 cap_chan = scan_list[ scan_pos ];
26068 /* hot key needs to load guide to make console update faster */
26069 load_guide( cap_chan, WHO );
26072 if (0 != vc_ncs) set_vc(WHO);
26075 /* faster update than waiting on status */
26076 if (0 != vc_ncs) {
26077 refresh.vstats = 1;
26078 show_vc_selection();
26080 refresh.estats = 1;
26081 show_event_short();
26083 /* hitting the key should make it reset the channel */
26084 scan_freq = 0;
26086 break;
26088 default:
26089 advrlog( LOG_INFO, "console chan scan ignored key 0x%X", kb);
26090 break;
26096 /* ATSC TRANSPORT STREAM FUNCTIONS ******************************************/
26098 /************************************************************ ATSC STT helper
26099 ATSC uses 32 bit unsigned from 00:00:00 UTC, Jan 6, 1980 for epoch
26100 unix uses 32 bit unsigned from 00:00:00 UTC, Jan 1, 1970 for epoch
26101 main calls: inits global utc_offset difference, includes seconds east/west
26103 /* static */
26104 void
26105 atsc_calc_epoch ( void )
26107 struct tm as = { 0,0,0,6,0, 80,0,0,0 }; /* Jan 6, 1980 00:00:00 UTC */
26108 struct tm uc = { 0,0,0,1,0,107,0,0,0 }; /* Jan 1, 2007 00:00:00 UTC */
26109 time_t utd;
26111 /* in case Znn config file trusted time source becomes broken */
26112 utc_check = mktime( &uc ); /* don't let STT set time below this value */
26113 utsgmt = uc.tm_gmtoff;
26115 /* convert ATSC System epoch date+time into unix system epoch seconds */
26116 utd = mktime( &as );
26118 if (-1 != utd) {
26119 /* correct for seconds east of greenwich meridian (negative is west) */
26120 utc_offset = utd + utsgmt;
26121 advrlog( LOG_INFO, " UTC offset+DST %d", utc_offset);
26125 /* if arg_strict set, this logs the bits that failed */
26126 /* static */
26127 void
26128 atsc_strict_log ( unsigned char *r, char *n )
26130 char t[64];
26131 int i;
26132 if (0 == arg_strict) return;
26134 t[0] = 0;
26136 for (i=0; i< 12; i++) { /* build 33 byte string */
26137 sprintf( &t[strlen(t)], "%02X ", r[i]);
26140 dvrlog( LOG_INFO, "%-6s %s", n, t);
26143 /* generic build payload:
26144 For payload tables STT, MGT, VCT, EIT, ETT, RRT, PAT and PMT.
26146 This only works for payloads with section lengths, not video or audio.
26148 Also, to be generic, all payloads are allocated 4096 bytes, but
26149 RRT and VCT only use 1021 bytes. STT is 56 bits but uses one packet.
26151 RRT may be expanded soon, so there may be a need to parse it instead
26152 of current use of built-in table with RRT CRC32 used for QoS test.
26154 p is packet, s is payload structure, n is name for logging purposes.
26155 returns 0 if done, 1 if not done.
26157 /* static */
26159 atsc_build_payload ( struct payload_s *s, unsigned char *p, char *n )
26161 unsigned char *r;
26162 int psi;
26163 int payout;
26164 short pid;
26166 r = NULL;
26167 payout = 0;
26169 pid = 0x1FFF & ( (p[1] << 8) | p[2] );
26170 psi = 1 & (p[1] >> 6);
26172 r = p + 4; /* skip 4 byte TS header */
26174 /* payload start indicator yes */
26175 if (0 != psi) {
26176 r++; /* skip past offset byte */
26177 /* atsc_strict_log( r, n ); */
26178 s->payoff = 0;
26179 s->sl = ((0xF & r[1])<<8) | r[2];
26180 payout = (3 + s->sl) - s->payoff;
26181 if (payout > 183) payout = 183;
26183 dvrlog( LOG_INFO, "%s PSI1 payout %03X", n, payout);
26185 if (payout < 1) return 0;
26186 memcpy( s->payload, r, payout);
26187 s->payoff += payout;
26189 dvrlog( LOG_INFO, "%s PSI1 payoff %03X", n, s->payoff);
26191 if ( s->payoff >= s->sl+3 ) {
26192 return 0;
26194 return 1;
26197 /* payload start indicator no */
26198 if (0 == psi) {
26199 /* first time, or last payload was good? */
26200 if (0 == s->payoff) { /* last valid payload clears s->payoff */
26201 return 1; /* drop payload, it is out of order */
26204 payout = (3 + s->sl) - s->payoff;
26205 if (payout > 184) payout = 184;
26207 dvrlog( LOG_INFO, "%s PSI0 payout %03X", n, payout);
26209 if (payout < 1) return 0;
26211 if ( (s->payoff + payout) > 4095 ) {
26212 advrlog( LOG_INFO, "%s payoff %04X exceeds %03X buffer\n",
26213 n, s->payoff + payout, 4095 );
26214 return 1;
26216 memcpy( &s->payload[ s->payoff ], r, payout);
26217 s->payoff += payout;
26220 dvrlog( LOG_INFO, "%s PSI0 payoff %03X", n, s->payoff);
26222 if ( s->payoff >= s->sl+3 ) {
26223 return 0;
26225 return 1;
26228 /* payload not handled */
26229 return 1; /* shush compiler */
26233 /* FIXME: TODO: if VCT exists, and program not on VCT list,
26234 add to end of vc[] list and increment vc_ncs
26236 /****************************************************************** MPEG2 PAT
26237 * rebuild pat to single vc pmt entry
26239 /* static */
26240 void
26241 atsc_build_pat ( unsigned char *p, int pn, int pmtpid )
26243 #ifdef USE_MPEG_REBUILD
26244 #warning using MPEG PAT rebuild
26245 unsigned int new_crc;
26246 unsigned short sl = 13; /* fixed length for one program */
26248 if (CAP_INFO == cap_now) return;
26249 if (pn < 1) return;
26250 if (pmtpid < 1) return;
26251 if (cap_pn < 1) return;
26252 if (cap_pn != pn) return;
26254 advrlog( LOG_INFO, "%s pn.%d PMT %04X", WHO, pn, pmtpid);
26256 /* copy section control header portion */
26257 memset( new_pat, 0, sizeof(new_pat));
26259 memcpy( &new_pat[0], p, 13 );
26261 /* copy section data portion for a specific VC */
26262 /* but only works if pg/nn.vc loaded... */
26264 memcpy( &new_pat[13], p + 13 + (cap_vc*4), 4);
26266 new_pat[13] = 0xFF & (pn >> 8);
26267 new_pat[14] = 0xFF & pn;
26269 /* 0xC0 is reserved bits for pmtpid */
26270 new_pat[15] = 0xC0 | (0x1F & (pmtpid >> 8));
26271 new_pat[16] = 0xFF & pmtpid;
26273 /* set new pat section length which is fixed at 13 for a single program. */
26274 // new_pat[6] = (0xF0 & new_pat[6]) | (0x0F & (sl>>8));
26275 new_pat[6] = 0xB0; /* syntax, 0, two reserved bits 1, 4 bit top of sl */
26276 /* 1 offset byte from payload start, 8 byte section header, 4 byte data */
26277 new_pat[7] = 0xFF & sl;
26279 advrlog( LOG_INFO, "%s new PAT pn.%d pmtpid %04X sl %03X",
26280 WHO, pn, pmtpid, sl);
26282 /* compute new pat crc */
26283 new_crc = calc_crc32( &new_pat[5], sl-1 );
26285 /* store crc at end of new pat */
26286 new_pat[ sl+4 ] = 0xFF & (new_crc>>24);
26287 new_pat[ sl+5 ] = 0xFF & (new_crc>>16);
26288 new_pat[ sl+6 ] = 0xFF & (new_crc>>8);
26289 new_pat[ sl+7 ] = 0xFF & new_crc;
26291 pat_built = ~0;
26292 #else
26293 pat_built = 0;
26294 #endif
26296 #if 0
26297 /* only needed for debugging, remove from production for one less crc32 */
26299 unsigned int chk_crc;
26300 chk_crc = calc_crc32( &new_pat[5], sl+3);
26301 if (0 != chk_crc)
26302 dvrlog( LOG_INFO, "%s chk_crc %08X", WHO, chk_crc);
26304 #endif
26309 /****************************************************************** MPEG2 PAT
26310 There is not a lot of interesting stuff here until/unless have to modify it.
26311 xine seems ok w/stream not fully matching PAT, i.e. missing program streams.
26312 Some stations send PAT program # info in a different order from the VCT.
26313 Solve by keeping one list for VCT and xref by pgm# 2nd list for PAT+PMT info.
26315 /* static */
26316 void
26317 atsc_parse_pat ( unsigned char *p )
26319 unsigned char *r;
26320 unsigned short sl;
26321 unsigned char vn, cni, sn, lsn;
26322 unsigned short tsi1, pgn, netpid, pmtpid;
26323 unsigned int pat_crc, vcn;
26324 short tsx;
26325 int terr;
26326 struct tsid_s *tsn;
26327 struct sig_s *s;
26329 s = &ptc[cap_chan].sig;
26331 pkt.patp++;
26332 pkt.fpat = PAY_READ;
26333 pat.payok = atsc_build_payload( &pat, p, "PAT" );
26334 if (0 != pat.payok) return;
26335 pat.payoff = 0;
26336 pkt.patt++;
26338 /* pat is one packet usually, so can get away with this */
26339 /* point to table id */
26340 r = pat.payload;
26342 terr = 0;
26343 if (MPEG_PAT != r[0]) terr = ~0; /* PAT table ID 0 */
26345 // if (0 != (0x80 & r[1])) terr = ~0; /* syntax bit must be set */
26347 /* -A strict also applies to MPEG. cable reserved bits are wrong */
26348 if (0 != arg_strict)
26350 if ( ( 0xB0 != (0xF0 & r[1]) ) /* syntax,0,two reserved */
26351 || ( 0xC1 != (0xC1 & r[5]) ) /* reserved/cni */
26352 || ( 0x00 != r[6] ) /* should be 0 */
26353 || ( 0x00 != r[7] ) /* should be 0 */
26355 terr = ~0;
26358 if (0 != terr) {
26359 dvrlog( LOG_INFO, "PAT PID 0000 reserved bits fail");
26360 atsc_strict_log( r, "PAT reserved bits fail");
26361 return; /* check reserved bits saves time */
26364 sl = ((0xF & r[1])<<8) | r[2]; /* 12 bit section length */
26365 pat.sl = sl;
26366 pat_crc = calc_crc32( r, sl+3 ); /* want crc to validate it */
26367 if (0 != pat_crc) {
26368 pkt.crcpat++;
26369 dvrlog( LOG_INFO, "PAT CRC error" );
26370 // return; /* ignore if invalid crc */
26373 tsi1 = (r[3]<<8) | r[4]; /* ts id is now checked */
26374 vn = 0x1F & (r[5]>>1); /* version is now checked */
26375 cni = 1 & r[5]; /* current next not checked. */
26376 sn = r[6]; /* section numbers not checked */
26377 lsn = r[7]; /* last section number not checked */
26379 r += 8;
26381 pkt.fpat = PAY_GOOD;
26384 /* if no version change, still need to change Continuity Counter */
26385 if (pat.vn == vn) {
26387 /* update pat if it was rebuilt */
26388 if ((CAP_INFO != cap_now) && (cap_pn>0) && (0 != pat_built)) {
26389 new_pat[3] &= 0xF0;
26391 /* set Continuity Counter to match current */
26392 new_pat[3] |= 0x0F & p[3];
26393 advrlog( LOG_INFO, "%s %04X PAT CC now %1X",
26394 WHO, 0, 0x0F & p[3]);
26396 #ifdef USE_MPEG_REBUILD
26397 memcpy( p, new_pat, 188);
26398 #endif
26401 /* don't update rest of table if no version change to save processing */
26402 return;
26407 pkt.fpat = PAY_PARSE;
26409 pat.vn = vn;
26410 pkt.pat++;
26412 /* set PAT OK to speed up display but right way is:
26413 set not found until PAT proven to be found and not obsolete NIT
26415 pkt.fpat = PAY_GOOD;
26417 pat_ncs = 0;
26419 /* start the payload loop, and stop at p+4+sl. */
26420 /* start with first vc[] and work way up saving PMT data for each */
26421 while( r < pat.payload + (pat.sl-1)) {
26422 if ( 0xC0 != (0xC0 & r[2]) ) {
26423 dvrlog( LOG_INFO, "PAT NCS bad rb %02X", r[2]);
26424 return; /* stop if no reserved bits */
26427 pgn = (r[0] << 8) | r[1]; /* should be valid program # */
26429 #ifdef USE_AUTO_TSID
26430 /* no hidden, NTSC or even TSID numbers */
26431 if ( (0 < pgn)
26432 && (0xFFFF != pgn)
26433 && (0x80 > pgn)
26434 && (1 == (tsi1 & 1))
26435 && (0 == arg_scan)
26437 tsx = find_tsidx( tsi1, pgn, WHO );
26439 /* -1 is not found */
26440 if (tsx >= 0)
26442 tsn = &tsids[tsx];
26443 if (pgn == s->pn) {
26444 snprintf( tsn->call, 8, "%s", s->call);
26445 tsn->call[7] = 0;
26446 snprintf( tsn->sid, 4, "%s", s->sid);
26447 tsn->sid[3] = 0;
26449 } else {
26450 tsn = &tsids[tsidx];
26451 tsn->tsid = tsi1;
26452 tsn->ptc = s->ptc;
26453 tsn->pn = pgn;
26455 *tsn->call = 0;
26456 *tsn->sid = 0;
26458 if (pgn == s->pn) {
26459 snprintf( tsn->call, 8, "%s", s->call);
26460 tsn->call[7] = 0;
26461 snprintf( tsn->sid, 4, "%s", s->sid);
26462 tsn->sid[3] = 0;
26463 } else {
26465 /* not current program, set Call Sign to TS-TSID and Station ID to PTC */
26466 snprintf( tsn->call, 8, "TS-%04X", tsi1 );
26467 snprintf( tsn->sid, 4, "%03d", s->ptc);
26469 tsidx++;
26470 /* is found, update the callsign and sid */
26473 #endif
26477 /* program number 0 is obsoleted network information table.
26478 TESTME: this shows up on cable PTC that has a lot of blank PMTs
26479 NIT may be an indicator for a list of services?
26481 if (0 == pgn) {
26482 netpid = 0x1FFF & ( (r[2]<<8) | r[3] );
26483 dvrlog( LOG_INFO, "PAT%02d obsoleted NIT %04X", cap_chan, netpid);
26484 r += 4;
26485 continue; /* skip NIT */
26488 pkt.fpat = PAY_GOOD;
26490 pmtpid = 0x1FFF & ( (r[2]<<8) | r[3] );
26492 if (pat_ncs >= VCZ) {
26494 /* It's likely this will occur on cable. Need to work on VC/PA abstract,
26495 but knowing which VC/PA to remove based on CA needs sort_vc + hindsight,
26496 and could generate all kinds of strange problems if vc index changes.
26498 Some viewable PMTs at end of big PAT on cable? Most is junk so far.
26500 For now: Only alert user if PAT is ridiculous size for broadcast.
26501 Tolerate ridiculous program numbers up to .16, but not higher.
26502 This will redux the log message for same error on cable.
26504 if (0 == arg_cable)
26505 dvrlog( LOG_INFO, "PAT ncs maxed at %d, PGM %04X PMTPID %04X "
26506 "and rest ignored", VCZ, pgn, pmtpid);
26507 break; /* only 8 VC via PAT */
26510 /* vc cap copies out a single program/pmtpid table entry */
26511 if (cap_pn > 0) {
26512 if (cap_pn == pgn) {
26513 if (sl < 179) {
26514 atsc_build_pat( p, pgn, pmtpid );
26517 /* if vc unset but pn set and matches this vc, use this pa index for vc */
26518 if (-1 == cap_vc) {
26519 cap_vc = pat_ncs;
26521 cap_pids[1] = pmtpid;
26523 /* if found, put in cap_pids for keeping */
26524 cap_pids[0] = 0;
26527 pa[ pat_ncs ].tsid = tsi1;
26528 pa[ pat_ncs ].pn = pgn;
26529 pa[ pat_ncs ].pmtpid = pmtpid;
26531 advrlog( LOG_INFO, "PAT ts id %04X pn %d PMTPID %04X rebuilt %d",
26532 tsi1, pgn, pmtpid, pat_built);
26534 /* NOTE: don't do this after VCT loads, the order may have changed */
26535 if (vct_ncs < 1) { /* no vct yet? have to put something in vc[] */
26536 if (-1 == find_vc_pgm( pgn, WHO) ) {
26537 advrlog( LOG_INFO, "PAT adds pn.%d new to vc[]", pgn);
26538 vc[ pat_ncs ].tsid = tsi1;
26539 vc[ pat_ncs ].pn = pgn;
26540 vc[ pat_ncs ].pmtpid = pmtpid;
26542 /* else already added to list */
26544 } else {
26546 /* NOTE:
26547 This may be source of KQED issues, and the logic may be incorrect for
26548 any station that plays the PMT activate and deactivate game. When KQED
26549 reconfigures, program number order in both VCT and PAT PMT list change.
26550 the vc ordering changes, but the program number doesn't change.
26552 /* have a vct, see if pgm is on it */
26553 vcn = find_vc_pgm( pgn, WHO);
26554 if (-1 == vcn) {
26555 advrlog( LOG_INFO, "PAT adds pn.%04X missing from VCT, vc_ncs %d",
26556 pgn, vc_ncs);
26557 vc[ vc_ncs ].pn = pgn; /* want extra pat+pmt to show at end */
26558 vc[ vc_ncs ].pmtpid = pmtpid;
26559 vc[ vc_ncs ].tsid = tsi1;
26560 vc_ncs++;
26561 } else {
26563 /* TESTME: This belongs under strict ATSC checking? FOX KRIV gets these? */
26564 /* if (0 != arg_strict) */
26566 if (pgn != vc[vcn].pn)
26567 dvrlog( LOG_INFO,
26568 "PAT%02d PGM %04X doesn't match VCT %04X",
26569 cap_chan, pgn, vc[vcn].pn );
26571 if (pmtpid != vc[vcn].pmtpid)
26572 dvrlog( LOG_INFO,
26573 "PAT%02d PMT %04X doesn't match VCT %04X",
26574 cap_chan, pmtpid, vc[vcn].pmtpid );
26576 if (tsi1 != vc[vcn].tsid)
26577 dvrlog( LOG_INFO,
26578 "PAT%02d TSID %04X doesn't match VCT %04X",
26579 cap_chan, tsi1, vc[vcn].tsid );
26584 advrlog( LOG_INFO, "PAT pa%d PMT %04X PGN %04X TS ID %04X",
26585 pat_ncs, pa[ pat_ncs ].pmtpid,
26586 pa[ pat_ncs ].pn, pa[ pat_ncs ].tsid );
26589 /* increment number of channels found in pat */
26590 pat_ncs++;
26591 r += 4; /* skip to next program number and PID */
26594 if (vct_ncs >= pat_ncs) {
26595 vc_ncs = vct_ncs;
26596 } else {
26597 vc_ncs = pat_ncs;
26600 if (0 != pat_built) memcpy( p, new_pat, 188 );
26601 refresh.vstats = 1; /* new PAT refreshes vc status line */
26603 advrlog( LOG_INFO, "PAT VN %02X found %d programs", vn, pat_ncs);
26605 /***************************************************************** MPEG2 PAT */
26607 /* not needed for broadcast, but what about cable? */
26608 /****************************************************************** MPEG2 CAT
26609 CAT appears when RC flag in PMT. since rc is stuffed, cat is too
26611 /* static */
26612 void
26613 atsc_test_cat ( unsigned char *p, unsigned short pid )
26615 unsigned char *r;
26616 unsigned int sl;
26617 unsigned int cat_crc = 0;
26619 pkt.catp++;
26620 pkt.fcat = PAY_READ;
26622 if (0 == arg_cable) return;
26624 cat.payok = atsc_build_payload( &cat, p, "CAT" );
26625 if (0 != cat.payok) return;
26627 advrlog(LOG_INFO, "%s CAT PID %04X payoff %03X", WHO, pid, cat.payoff);
26628 cat.payoff = 0;
26630 /* point to table id */
26631 r = cat.payload;
26633 if (MPEG_CAT != r[0]) return; /* CAT table ID 1 */
26635 if (0 != arg_strict) {
26636 if ((0x30 != (0x30 & r[1]))
26637 || (0xC0 != (0xC0 & r[5])) ) { /* reserved bits */
26638 atsc_strict_log( r, "CAT bad");
26639 return; /* check reserved bits saves time */
26643 sl = ((0xF & r[1])<<8) | r[2]; /* 12 bit section length */
26644 cat_crc = calc_crc32( r, sl+3 ); /* only want crc to validate it */
26645 if (cat_crc != 0) {
26646 pkt.crccat++;
26647 return;
26649 pkt.cat++;
26651 /* not doing anything with CAT except counting errors for QoS */
26652 if (cap_pn > 0) pkt.keep = 0;
26654 /***************************************************************** MPEG2 CAT */
26656 /**************************************************************** MPEG2 PMT
26657 * rebuild pmt to one video and one audio from cap_pn
26659 /* static */
26660 void
26661 atsc_build_pmt ( unsigned char *p, unsigned int pid, unsigned int osl )
26663 #ifdef USE_MPEG_REBUILD
26664 #warning using MPEG PMT rebuild
26665 unsigned char *r, *d, *s;
26666 unsigned int i, c, sl, new_crc;
26668 pmt_built = 0;
26670 if (cap_pn < 1) return;
26671 if (CAP_INFO == cap_now) return;
26673 if (pid != cap_pids[1]) return;
26675 /* KQED-HD PMT PID 0030 can't do this because it's too large
26676 and because the first packet is already written. So far,
26677 KQED-HD is only known station with this problem. All the
26678 rest send single packet PMTs.
26680 One way around is to hold down emit of PMT until rebuilt
26681 as one packet here, but chasing CC errors will be fun.
26683 if (osl > 179) {
26684 dvrlog( LOG_INFO, "Not building multi-packet PMT");
26685 return;
26688 memset( new_pmt, 0, sizeof(new_pmt));
26690 advrlog( LOG_INFO, "%s %04X PMT oldsl %03X newsl %03X",
26691 WHO, pid, osl, 13);
26693 d = new_pmt;
26694 memcpy( d, p, 17); /* 4 byte header, 13 byte control data */
26695 d += 17; /* new pmt */
26696 sl = 13; /* does not count header */
26697 r = p + 17; /* old pmt */
26699 /* pcr pid is don't care here. should have been set correctly elsewhere */
26700 c = ( (0x0F & p[15]) << 8 ) | p[16]; /* program info len 12 bottom bits */
26702 /* only copy and increment if program info len non zero */
26703 if (0 != c) {
26704 i = c;
26705 s = r;
26707 /*step thru descriptors */
26708 for ( i = 0; i < c; ) {
26709 if (0xAA == *s) {
26710 *s = 0x80; /* we have no corruption today */
26711 advrlog( LOG_INFO, "%s %04X RC stuffed", WHO, pid);
26713 advrlog( LOG_INFO, "%s %04X PMT PI tag %02X len %02X",
26714 WHO, pid, *s, s[1]);
26715 i += s[1]+2; /* increment count */
26716 s += s[1]+2; /* increment pointer */
26718 memcpy( d, r, c );
26719 r += c; /* next place in old pmt */
26720 sl += c; /* section len in new pmt */
26722 advrlog( LOG_INFO,"%s %04X PMT pilen %03X newsl %03X",
26723 WHO, pid, c, sl );
26725 /* ok to add zero, not ok to memcpy size 0 */
26726 d += c; /* next place in new pmt */
26728 while (r < (p + (osl-1)) ) {
26729 unsigned int e;
26730 if ( (0xE0 == (0xE0 & r[1])) && (0xF0 == (0xF0 & r[3])) ) {
26731 e = ((0x1F & r[1]) << 8 ) | r[2]; /* es pid bottom 13 bits */
26732 c = ((0x0F & r[3]) << 8 ) | r[4]; /* es info len 10 bottom bits */
26734 /* preamble 5 bytes, copy this much at least, es info len may be zero */
26735 c += 5;
26737 /* if es pid matches vc or va pid copy entry to new pmt */
26738 if ( (e == cap_pids[2]) || (e == cap_pids[3]) ) {
26740 /* needs a strict boundary check. the while above is not enough */
26741 memcpy( d, r, c );
26743 d += c; /* next place in new pmt */
26744 sl += c; /* section len in new pmt */
26745 advrlog(LOG_INFO,"%s %04X PMT pid %04X esi %03X newsl %03X",
26746 WHO, pid, e, c, sl);
26748 r += c; /* next place in old pmt */
26750 } else {
26751 /* no reserved bits stops loop */
26752 advrlog( LOG_INFO, "%s %04X no reserved bits",
26753 WHO, pid);
26754 break;
26758 /* set the following:
26759 set syntax and reseserved bits in top 4 bits of r[6]
26760 0 in bottom 4 bits of r[6]
26762 section length in all 8 bits of r[7].
26763 only handling sections < 179 bytes so 8 bits is enough.
26766 // this only copies the bad reserved bits
26767 // new_pmt[6] = (0xF0 & new_pmt[6]) | (0x0F & (sl>>8));
26769 new_pmt[6] = 0xB0;
26770 new_pmt[7] = 0xFF & sl;
26772 /* compute new pmt crc */
26773 new_crc = calc_crc32( &new_pmt[5], sl-1 );
26775 /* store crc at end of new pmt */
26776 new_pmt[ sl+4 ] = 0xFF & (new_crc>>24);
26777 new_pmt[ sl+5 ] = 0xFF & (new_crc>>16);
26778 new_pmt[ sl+6 ] = 0xFF & (new_crc>>8);
26779 new_pmt[ sl+7 ] = 0xFF & new_crc;
26781 /* tell the caller the new pmt has been built */
26782 pmt_built = ~0;
26784 advrlog( LOG_INFO, "PMT%02d %04X v %04X a %04X",
26785 cap_chan, pid, cap_pids[2], cap_pids[3]);
26786 #else
26787 pmt_built = 0;
26789 #endif
26791 #if 0
26792 /* only needed for debugging, remove from production for one less crc32 */
26794 unsigned int chk_crc;
26795 chk_crc = calc_crc32( &new_pmt[5], sl+3);
26796 if (0 != chk_crc)
26797 dvrlog( LOG_INFO, "%s %04X chk_crc %08X",
26798 WHO, pid, chk_crc);
26800 #endif
26803 /******************************************************** MPEG COMPONENT NAME
26804 A/65b Table 6.38 Multiple String Structure
26805 does Huffman decomp
26806 r points to start of descriptor after tag and count bytes
26807 puts result in vc[n].cname, truncated to 8 chars
26808 TESTME: These are not compressed and do not need huffman decode?
26810 /* static */
26811 void
26812 atsc_parse_mss_cname ( unsigned char *r, int n )
26814 unsigned int nstr, nseg, comp, mode, nchr;
26815 unsigned int i,j;
26816 char lang[4];
26817 char t[512];
26818 char *d;
26820 d = t; /* stupid compiler tricks */
26822 r += 2;
26824 memset( t, 0, sizeof(t));
26825 nstr = r[0];
26826 for (i = 0; i < nstr; i++) {
26827 memcpy(lang, &r[1], 3);
26828 lang[3] = 0;
26829 r += 4; /* 3? */
26830 nseg = *r++;
26831 for (j = 0; j < nseg; j++) {
26832 comp = *r++;
26833 mode = *r++;
26834 nchr = *r++;
26836 /* nchr should never be above 255 with no compression */
26837 if ( (0 == comp) && (0 == mode) ) {
26838 nchr++;
26839 if (nchr > 256) nchr = 256;
26840 astrncpy( t, (char *)r, nchr );
26843 /* FIXME: KPXB kludge, mode 0xFF. requires non-strict mode */
26844 /* Haven't been able to receive KPXB for > year, not sure if this works. */
26845 if ( (0 == arg_strict) || ((0 != arg_strict) && (0xFF == mode)) )
26847 /* nchr should never be above 512 with compression */
26848 if ((1 == comp) || (2 == comp)) {
26849 if (nchr > sizeof(t) ) nchr = sizeof( t );
26850 huffman_decode( d, r, sizeof(t)-1, nchr, comp);
26853 if (comp > 2) snprintf( t, sizeof(t)-1, "[%s] L%02X C%02X M%02X",
26854 lang, nchr, comp, mode);
26858 /* truncate PMT component name at 7 chars */
26859 t[7] = 0;
26860 strncpy( vc[n].cname, t, 8);
26861 advrlog( LOG_INFO, "PMT vc[%d].cname %s", n, vc[n].cname);
26864 /**************************************************************** MPEG2 PMT */
26865 /* The MPEG2 Program Map Table gives the following information on a single VC:
26866 * Video and Audio PID list and which PID for the clock reference.
26867 * Descriptors containing stream format parameters for Video and Audio.
26869 /* static */
26870 void
26871 atsc_parse_pmt ( unsigned char *p, short pan )
26873 unsigned char *r, *s, *b;
26874 unsigned short sl, pgn, pcrpid, pilen, pid, espid, esilen, acn;
26875 unsigned char vn, cni, sn, lsn, st, numes, stm;
26876 unsigned int pmt_crc;
26877 unsigned int pmt_crc0;
26878 int pmtvc, pmtpa;
26879 int i, terr;
26880 struct payload_s *pmp;
26881 short tsx;
26883 pkt.pmtp++;
26884 acn = 0;
26886 pmtpa = pan; /* program association index */
26887 pmp = &pmt[pan];
26889 /* single program cap needs one PAT as first packet */
26890 if (cap_pn > 0) {
26892 /* hold down output until PAT seen */
26893 if (0 == pkt.pat) {
26894 pkt.keep = 0;
26895 return;
26899 pid = 0x1FFF & ((p[1] << 8) | p[2]);
26901 pan &= (VCZ - 1);
26903 pkt.fpmt = PAY_READ;
26905 pmp->payok = atsc_build_payload( pmp, p, "PMT" );
26906 if (0 != pmp->payok) return;
26907 pmp->payoff = 0;
26908 pkt.pmtt++;
26910 /* point to table id */
26911 r = pmp->payload;
26913 terr = 0;
26914 // if (MPEG_PMT != r[0]) terr = ~0; /* PMT table ID 2 */
26915 if (MPEG_PMT != r[0]) return;
26917 /* -A option enables strict reserved bits check */
26918 if (0 != arg_strict)
26920 if ( ( 0xB0 != (0xF0 & r[1]) ) /* syntax, 0, two reserved */
26921 || ( 0xC1 != (0xC1 & r[5]) ) /* reserved/cni */
26922 || ( 0x00 != r[6] ) /* should be 0 */
26923 || ( 0x00 != r[7] ) /* should be 0 */
26924 || ( 0xE0 != (0xE0 & r[8]) ) /* reserved */
26925 || ( 0xF0 != (0xF0 & r[10]) ) /* reserved */
26927 terr = ~0;
26930 if (0 != terr) {
26931 dvrlog( LOG_INFO, "PMT PID %04X reserved bits fail", pid);
26932 atsc_strict_log( r, "PMT reserved bits fail" );
26933 return; /* check reserved bits saves time */
26936 pmp->sl = sl = ((0xF & r[1])<<8) | r[2]; /* 12 bit section length */
26937 pmt_crc0 = 0;
26939 #define DEBUG_PMT_CRC
26940 #ifdef DEBUG_PMT_CRC
26941 pmt_crc = calc_crc32( r, sl-1 ); /* computed crc is non zero */
26942 pmt_crc0 |= r[ sl - 1];
26943 pmt_crc0 <<= 8;
26944 pmt_crc0 |= r[ sl ];
26945 pmt_crc0 <<= 8;
26946 pmt_crc0 |= r[ sl + 1 ];
26947 pmt_crc0 <<= 8;
26948 pmt_crc0 |= r[ sl + 2 ];
26949 #else
26950 pmt_crc = calc_crc32( r, sl+3 ); /* full crc validate = 0 */
26951 #endif
26952 /* does computed match received? */
26953 if (pmt_crc0 != pmt_crc) {
26954 pkt.crcpmt++;
26955 dvrlog( LOG_INFO, "PMT PID %04X bad CRC32 RCVD %08X CALC %08X",
26956 pid, pmt_crc0, pmt_crc);
26957 // return;
26960 pgn = r[3] << 8;
26961 pgn |= r[4]; /* 16 bit program number */
26963 vn = 0x1F & (r[5] >> 1);
26964 cni = 1 & r[5];
26965 sn = r[6];
26966 lsn = r[7];
26968 advrlog( LOG_INFO, "%s %04X PMT SLen %03X PAT ncs %d",
26969 WHO, pid, sl, pat_ncs);
26971 pcrpid = (0x1F & r[8]) << 8;
26972 pcrpid |= r[9]; /* PID of stream with PCR */
26974 pilen = (0x0F & r[10]) << 8;
26975 pilen |= r[11]; /* program info len */
26977 r += 12; /* start of program info descriptors */
26979 /* do until PAT ncs says done */
26981 pmtvc = find_vc_pgm( pgn, WHO );
26982 pmtpa = find_pa_pgm( pgn, WHO );
26984 /* no vct, so pmt has to fill in */
26985 if (vct_ncs < 1) {
26986 pmtvc = pmtpa;
26989 /* will see this error if PMT arrives before PAT. this ok */
26990 if (-1 == pmtpa) {
26991 advrlog( LOG_INFO, "ch %02d PMT %04X no PAT pa[].pn stop",
26992 cap_chan, pid );
26993 return; /* get out because no PAT vc/pa means no place to put data */
26996 /* this one is more important to log for user to see */
26997 if ( pmtpa >= VCZ ) {
26998 dvrlog( LOG_INFO, "ch %02d PMT ncs %u over limit VCZ %d stop",
26999 cap_chan, pmtpa, VCZ);
27000 return;
27003 /* don't update vc[] if no version change */
27005 if ( vn == pa[ pmtpa ].vn ) {
27007 advrlog( LOG_INFO, "%s %04X pa%d.vn %02X no chg", WHO, pid, pmtpa, vn );
27009 /* update pmt if program search set vc #, pmt matches vc and was rebuilt */
27010 /* KQED-HD fix: but only if section len indicates PMT is a single packet */
27011 if ( (CAP_INFO != cap_now)
27012 && (cap_pn > 0)
27013 && (pid == cap_pids[1])
27014 && (sl < 180) /* 188 - (4 byte header + 4 byte crc) */
27015 && (0 != pmt_built) ) {
27017 /* set Continuity Counter to match current */
27018 new_pmt[3] &= 0xF0;
27019 new_pmt[3] |= 0x0F & p[3];
27021 #ifdef USE_MPEG_REBUILD
27022 memcpy( p, new_pmt, 188 );
27023 #endif
27025 if (vct_ncs < 1) return;
27027 /* should return if VCT vn same as last VCT vn, but fall thru if different */
27028 /* KQED-HD fix: handle new VCT arriving after PMT and needing update */
27029 /* hmm get rid of need to udpate vc[] and all those problems go away */
27030 if (vct.vn == vct.lv) {
27031 advrlog( LOG_INFO, "PMT %04X vct.vn %02X no chg",
27032 pid, vct.vn);
27034 #ifdef USE_MEPG_REBUILD
27035 // if (0 != pmt_built)
27036 #endif
27037 return; /* rebuild if need to */
27038 } else {
27039 dvrlog( LOG_INFO, "PMT %04X vct.vn %02X %02X chg",
27040 pid, vct.lv, vct.vn);
27044 advrlog( LOG_INFO, "PMT PID %04X pn %d pa %d patncs %d vc %d vcncs %d vctncs %d PCR %04X",
27045 pid, pgn, pmtpa, pat_ncs, pmtvc, vc_ncs, vct_ncs, vn, pcrpid);
27047 pa[ pmtpa ].lv = pa[ pmtpa ].vn;
27048 pa[ pmtpa ].vn = vn;
27049 if (pmtvc >= 0) vc[ pmtvc ].pmtvn = vn;
27051 pa[ pmtpa ].pcrpid = pcrpid;
27052 if (pmtvc >= 0) vc[ pmtvc ].pcrpid = pcrpid;
27054 if (pa[ pmtpa ].lv != pa[ pmtpa ].vn)
27055 pkt.pmt++;
27057 /* each PAT vc has separate PMT descriptor, copy to pa[] */
27058 if ( ((sl - 12) < 1021) && ((sl - 12) > 0) ) {
27059 pa[ pmtpa ].pilen = pilen;
27060 memcpy( pa[ pmtpa ].pidesc, r, pilen );
27062 /* if VCT vc found that matches, copy there as well */
27063 if (pmtvc >= 0) {
27064 vc[ pmtvc ].pilen = pilen;
27065 memcpy( vc[ pmtvc ].pidesc, r, pilen );
27069 pa[ pmtpa ].ca = 0;
27070 if (pmtvc >= 0) vc[ pmtvc ].ca = 0;
27072 /* parse program info descriptors for interesting flags */
27073 for ( ; r < (pmp->payload + 11 + pilen); ) {
27075 /* conditional access descriptor exists for cable PMT and also in ES info.
27076 indicate the VC is scrambled so build vc text can skip it.
27078 if (0x09 == *r) {
27079 pa[ pmtpa ].ca = 1;
27080 if (pmtvc >= 0) vc[ pmtvc ].ca = 1;
27082 /* corrupt hollywood, trying to establish a government appointed monopoly */
27083 if (0xAA == *r) { /* The Justice: "What's next? Washing machines?" */
27084 pkt.pmtrc = ~0; /* washingdone */
27085 pkt.rc++;
27088 /* Program Component Name, might be wrong. Ideally should be callsign. */
27089 if (0xA3 == *r) { /* component name */
27090 atsc_parse_mss_cname( r, pmtvc ); /* puts 7 chars in vc[i].cname */
27093 r += r[1]+2; /* step past junk */
27096 /* save out stream types with pids and descriptors */
27097 numes = 0; /* start with first vc */
27099 s = r; /* hey nettles have a cigar! */
27101 /* first loop is to put video first */
27102 for ( ; r < (pmp->payload + (sl-1)); ) {
27103 if (0xE0 != (0xE0 & r[1])) { /* reserved bits */
27104 dvrlog( LOG_INFO, "%s %04X MPEG ES rb[1] fail %02X",
27105 WHO, pid, r[1]);
27106 break;
27109 /* FIXME: cable has r[3] = 0, test it only under strict for now */
27110 if (0 != arg_strict) {
27111 if (0xF0 != (0xF0 & r[3])) {
27112 dvrlog( LOG_INFO, "%s %04X MPEG ES rb[3] fail %02X",
27113 WHO, pid, r[3]);
27114 break;
27118 st = r[0]; /* stream type */
27119 espid = 0x1FFF & ( (r[1] << 8) | r[2] );
27120 esilen = 0x0FFF & ( (r[3] << 8) | r[4] ); /* ES info len */
27122 /* video first */
27123 if (2 != st) {
27124 r += 5 + esilen;
27125 advrlog( LOG_INFO, "%s %04X MPEG not 1st ES", WHO, pid );
27126 continue;
27128 advrlog( LOG_INFO, "%s %04X pa%d.%d t %02X pid %04X esi %03X",
27129 WHO, pid, pmtpa, numes, st, espid, esilen);
27131 r += 5; /* skip parsed */
27132 pa[ pmtpa ].estype[ numes ] = st;
27133 pa[ pmtpa ].espid[ numes ] = espid;
27134 pa[ pmtpa ].esilen[ numes ] = esilen;
27136 /* single program capture needs to set the video pid to keep */
27137 if (cap_pn == pa[ pmtpa ].pn) {
27138 cap_pids[2] = espid;
27139 advrlog( LOG_INFO, "PMT VID PID %04X", espid);
27142 /* stream type no match */
27143 stm = 0;
27145 if (pmtvc >= 0) {
27147 #if 0
27148 /* NO: MPEG PMT should override possibly incorrect VCT */
27149 /* only copy to vc[] if stream type and espid match */
27150 if ( st == vc[ pmtvc ].estype[ numes ])
27151 if (espid == vc[ pmtvc ].espid[ numes ])
27152 #endif
27153 stm = ~0; /* stream type is match */
27155 /* no vct, so pmt has to fill in */
27156 if (vct_ncs < 1) stm = ~0;
27157 if (0 != stm) {
27158 advrlog( LOG_INFO, "PMT backfills vc ES info");
27159 vc[ pmtvc ].estype[ numes ] = st;
27160 vc[ pmtvc ].espid[ numes ] = espid;
27161 vc[ pmtvc ].esilen[ numes ] = esilen;
27165 /* is the ES info descriptor length sane? */
27166 if ( (esilen > 0) && (esilen < 1024) ) {
27168 if (0 != stm)
27169 vc[ pmtvc ].esilen[ numes ] = esilen;
27171 /* copy ES descriptor in case no VCT with same */
27172 memcpy( &pa[ pmtpa ].esdesc[ numes ][ 0 ], r, esilen );
27173 if (0 != stm)
27174 memcpy( &vc[ pmtvc ].esdesc[ numes ][ 0 ], r, esilen );
27176 /* always keep lang code */
27177 for (i = 0; i < esilen; ) { /* bump i manually here */
27179 /* if has conditonal access, set whole VC scrambled */
27180 if (0x09 == r[ i ]) vc[ pmtvc ].ca = pa[ pmtpa ].ca = 1;
27182 /* copy iso 639 lang code in case VCT has it missing */
27183 if (0x0A == r[ i ]) {
27185 memcpy( &pa[ pmtpa ].eslang[ numes ][0], &r[ i + 2 ], 3);
27186 pa[ pmtpa ].eslang[ numes ][3] = 0;
27188 /* FIXME: and vc[].eslang is blank, or different? or leave in pa[] only */
27189 if (0 != stm) {
27190 memcpy( &vc[ pmtvc ].eslang[ numes ][0], &r[ i + 2 ], 3);
27191 vc[ pmtvc ].eslang[ numes ][3] = 0;
27195 i += r[ i + 1 ] + 2; /* add data len + header len */
27198 r += esilen;
27199 numes++;
27200 break; /* do not loop */
27203 r = s; /* hey nettles have a cigar! */
27206 /* second loop is to put audio after video */
27207 for ( ; r < (pmp->payload + (sl-1)); ) {
27209 if (0xE0 != (0xE0 & r[1])) { /* reserved bits */
27210 dvrlog( LOG_INFO, "%s %04X A/52 ES rb[1] fail %02X",
27211 WHO, pid, r[1]);
27212 break;
27215 /* cable has r[3] = 0 */
27216 if (0 != arg_strict) {
27217 if (0xF0 != (0xF0 & r[3])) {
27218 dvrlog( LOG_INFO, "%s %04X A/52 ES rb[3] fail %02X",
27219 WHO, pid, r[3]);
27220 break;
27224 st = r[0]; /* stream type */
27225 espid = 0x1FFF & ( (r[1] << 8) | r[2] );
27226 esilen = 0x0FFF & ( (r[3] << 8) | r[4] ); /* ES info len */
27228 /* skip video already parsed in first loop above */
27229 if (0x81 != st) {
27230 r += 5 + esilen;
27231 continue;
27234 /* count the audio channels in this vc for show vct to have correct audio # */
27236 advrlog( LOG_INFO, "%s %04X pa%d.%d t %02X pid %04X esi %03X",
27237 WHO, pid, pmtpa, numes, st, espid, esilen );
27239 r += 5; /* skip parsed */
27240 pa[ pmtpa ].estype[numes] = st;
27241 pa[ pmtpa ].espid[numes] = espid;
27242 pa[ pmtpa ].esilen[numes] = esilen;
27244 /* single program capture needs to set the audio pid to keep */
27245 if (cap_pn == pa[ pmtpa ].pn)
27246 if (acn == cap_va) {
27247 cap_pids[3] = espid;
27248 advrlog( LOG_INFO, "PMT AUD PID %04X", espid);
27250 stm = 0;
27251 if (pmtvc >= 0) {
27253 #if 0
27254 /* only copy to vc[] if stream type and espid match */
27255 if (st == vc[ pmtvc ].estype[ numes ])
27256 if (espid == vc[ pmtvc ].espid[ numes ])
27257 #endif
27258 stm = ~0;
27260 /* no vct, so pmt has to fill in */
27261 if (vct_ncs < 1) stm = ~0;
27262 if (0 != stm) {
27263 advrlog( LOG_INFO, "PMT backfills audio ES info");
27264 vc[ pmtvc ].estype[numes] = st;
27265 vc[ pmtvc ].espid[numes] = espid;
27266 vc[ pmtvc ].esilen[numes] = esilen;
27270 if ( (0 != esilen) && (esilen < 1024) ) {
27271 if (0 != stm)
27272 vc[ pmtvc ].esilen[ numes ] = esilen;
27273 /* copy ES descriptor to vc[] for display */
27274 memcpy( &pa[ pmtpa ].esdesc[ numes ][0], r, esilen );
27275 if (0 != stm)
27276 memcpy( &vc[ pmtvc ].esdesc[ numes ][0], r, esilen );
27277 /* always keep lang code */
27278 for (i = 0; i < esilen; ) { /* bump i manually here */
27280 /* Extract audio rates and compute channels if available via descriptor.
27281 It may not be very accurate. some stations send 2.0 as 5.1 but only
27282 use the front left and front right audio channels.
27284 if (0x81 == r[ i ]) {
27285 unsigned char brc, nch;
27286 int brv;
27287 int brt[32] = {
27288 32, 40, 48, 56, 64, 80, 96, 112,
27289 128, 160, 192, 224, 256, 320, 384, 448,
27290 512, 576, 640, -1, -1, -1, -1, -1,
27291 -1, -1, -1, -1, -1, -1, -1, -1 };
27292 b = r + i + 2;
27293 brc = 0x1F & ( b[1] >> 2); /* bit rate code */
27294 if (brc > 19) brc = 19;
27295 brv = brt[brc];
27296 if (brv > 0) brv *= 1000;
27297 pa[ pmtpa ].abr[ numes ] = brv;
27298 vc[ pmtvc ].abr[ numes ] = brv;
27300 /* default: 1.0 is monaural at 48kHz */
27301 nch = 1;
27303 /* detected enough for stereo */
27304 if (brv >= 192000) nch = 2;
27306 /* 5.1 is 6 speakers. Broadcast can switch to 448kbits from 384kbits.
27307 384kbits might be the mode used when no full surround is available.
27308 They'll be called 5.1 Low and 5.1 High until I figure out the rest.
27310 if (brv >= 384000) nch = 5;
27311 if (brv >= 448000) nch = 6;
27313 pa[ pmtpa ].ach[ numes ] = nch;
27314 vc[ pmtvc ].ach[ numes ] = nch;
27318 /* if has conditonal access, set whole VC scrambled */
27319 if (0x09 == r[ i ]) vc[ pmtvc ].ca = pa[ pmtpa ].ca = 1;
27321 /* copy iso 639 lang code in case no VCT with same */
27322 if (0x0A == r[ i ]) {
27323 memcpy( &pa[ pmtpa ].eslang[ numes ][0], &r[ i+2 ], 3);
27324 pa[ pmtpa ].eslang[ numes ][3] = 0;
27325 if (0 != stm) {
27326 memcpy( &vc[ pmtvc ].eslang[ numes ][0], &r[ i+2 ], 3);
27327 vc[ pmtvc ].eslang[ numes ][3] = 0;
27330 i += r[ i+1 ] + 2; /* add data len + header len */
27333 r += esilen;
27334 numes++;
27335 acn++;
27339 /* third loop is to put non video and non audio last on the list */
27340 /* lumping all non-video non-audio under A/90 category, even if incorrect */
27341 for ( ; r < (pmp->payload + (sl-1)); ) {
27343 if (numes >= VCZ) break;
27345 if (0xE0 != (0xE0 & r[1])) { /* reserved bits */
27346 dvrlog( LOG_INFO, "%s %04X A/90 ES rb[1] fail %02X",
27347 WHO, pid, r[1]);
27348 break;
27350 if (0 != arg_strict) {
27351 if (0xF0 != (0xF0 & r[3])) {
27352 dvrlog( LOG_INFO, "%s %04X A/90 ES rb[3] fail %02X",
27353 WHO, pid, r[3]);
27354 break;
27358 st = r[0]; /* stream type */
27359 espid = 0x1FFF & ( (r[1] << 8) | r[2] );
27360 esilen = 0x0FFF & ( (r[3] << 8) | r[4] ); /* ES info len */
27362 /* skip video and audio already parsed above */
27363 if ( (0x02 == st) || (0x81 == st) ) {
27364 r += 5 +esilen;
27365 continue;
27368 advrlog( LOG_INFO, "%s %04X pa%d.%d t %02X pid %04X esi %03X",
27369 WHO, pid, pmtpa, numes, st, espid, esilen );
27371 /* non-video next */
27372 r += 5; /* skip parsed */
27373 pa[ pmtpa ].estype[numes] = st;
27374 pa[ pmtpa ].espid[numes] = espid;
27375 pa[ pmtpa ].esilen[numes] = esilen;
27377 /* shouldn't be any of these, log if they show up */
27378 advrlog( LOG_INFO, "PMT non-av type %02X PID %04X ILen %04X",
27379 st, espid, esilen);
27381 /* TESTME: possible contention point if PMT has different order for
27382 non-av than VCT, but is moot if they never show up here
27384 stm = 0; /* now means "ok to add" instead of "is match" */
27385 if (pmtvc >= 0) {
27387 /* only update vc[] if blank */
27388 /* if (0 == vc[ pmtvc ].estype) */
27390 vc[ pmtvc ].estype[numes] = st;
27391 vc[ pmtvc ].espid[numes] = espid;
27392 vc[ pmtvc ].esilen[numes] = esilen;
27393 stm = ~0;
27396 /* no vct, so pmt has to fill in */
27397 if (vct_ncs < 1) stm = ~0;
27400 if ( (0 != esilen) && (esilen < 1024) ) {
27401 /* copy ES descriptor to vc[] for display */
27402 memcpy( &pa[ pmtpa ].esdesc[ numes ][0], r, esilen );
27403 if (0 != stm)
27404 memcpy( &vc[ pmtvc ].esdesc[ numes ][0], r, esilen );
27405 /* always keep lang code */
27406 for (i = 0; i < esilen; ) { /* bump i manually here */
27408 /* if has conditonal access, set whole VC scrambled */
27409 if (0x09 == r[ i ]) vc[ pmtvc ].ca = pa[ pmtpa ].ca = 1;
27411 /* copy iso 639 lang code in case no VCT with same */
27412 if (0x0A == r[ i ]) {
27413 memcpy( &pa[ pmtpa ].eslang[ numes ][0], &r[ i+2 ], 3);
27414 pa[ pmtpa ].eslang[ numes ][3] = 0;
27415 if (0 != stm) {
27416 memcpy( &vc[ pmtvc ].eslang[ numes ][0], &r[ i+2 ], 3);
27417 vc[ pmtvc ].eslang[ numes ][3] = 0;
27420 i += r[ i+1 ] + 2; /* add data len + header len */
27423 r += esilen;
27424 numes++;
27428 for (i = 0; i < pat_ncs; i++) {
27429 if (0 != pa[i].ca) {
27430 tsx = find_tsidx( pa[i].tsid, pa[i].pn, WHO );
27431 if (tsx >= 0) remove_tsid(tsx);
27435 for (i = 0; i < vc_ncs; i++) {
27436 if (0 != vc[i].ca) {
27437 tsx = find_tsidx( vc[i].tsid, vc[i].pn, WHO );
27438 if (tsx >= 0) remove_tsid(tsx);
27442 pa[ pmtpa ].pmtes = numes;
27443 pa[ pmtpa ].acn = acn;
27445 if (pmtvc >= 0) {
27446 vc[ pmtvc ].pmtes = numes;
27447 vc[ pmtvc ].acn = acn;
27450 pkt.fpmt = PAY_GOOD;
27452 /* determine if this packet should be kept, full cap does not change keep */
27453 if (cap_pn >= 0) {
27454 pkt.keep = 0;
27455 if ( pid == cap_pids[1] ) {
27457 /* PMT not built? Can only rebuild if it's a single packet */
27458 if (0 == pmt_built) {
27459 if (sl < 179) atsc_build_pmt( p, pid, sl );
27460 /* if PMT is built, use it */
27461 if (0 != pmt_built) memcpy( p, new_pmt, 188 );
27463 pkt.keep = ~0; /* set by parse pat */
27466 refresh.vstats = 1; /* PMT updates the PIDs status display */
27467 advrlog( LOG_INFO, "PMT %04X done", pid);
27469 /***************************************************************** MPEG2 PMT */
27473 /*********************************************************** MPEG2 VIDEO PES */
27474 /* Looks for Sequence starts and Picture types in single Program capture.
27475 * Tracks Sequence starts and Picture type write byte offset from vc cap start.
27476 * If either frame or sequence counts exceed allocations, cap_frames is reset
27477 * so that dump_frame_sequence() will not write incorrect bogus data.
27478 * This is a memory optimization to reduce the running atscap footprint.
27479 * 'atscut -m' and xtscut use larger arrays and should be OK in most cases.
27481 /* static */
27482 void
27483 atsc_test_mpeg2_video_pes ( unsigned char *p, unsigned int pid )
27485 unsigned char psi, pct;
27486 unsigned int b;
27487 unsigned int vpn;
27488 int pav, vcv;
27489 long long seq;
27490 int i;
27491 struct sig_s *s;
27493 s = &ptc[cap_chan].sig;
27495 psi = 1 & (p[1]>>6);
27497 /* hold down until PAT and PMT have been seen */
27498 if ( (0 == pkt.pat) || (0 == pkt.pmt) ) {
27499 pkt.keep = 0;
27500 return;
27503 /* if psi set, check for sequence */
27504 b = 0xFFFFFFFFUL;
27505 /* payload start set should mean SEQ+I, P or B picture */
27506 if (0 != psi) {
27508 /* only if found pa index for video es pid */
27509 pav = find_pa_vespid( pid );
27510 vcv = find_vc_vespid( pid );
27512 if (pav >= 0) {
27513 advrlog( LOG_INFO, "%s ESPID %04X PS1 SEQ", WHO, pid);
27514 for (i = 4; i < 188; i++) {
27515 b = (b<<8) | p[i];
27516 /* look for start code */
27517 if (0x00000100UL != (0xFFFFFF00UL & b)) continue;
27518 /* Sequence Start code 000001B3 */
27519 if ( 0x1B3 == b ) {
27520 unsigned char frc;
27521 /* point to SEQ data */
27522 i++;
27523 /* marker bit */
27524 if (0 == (0x20 & p[i+6])) continue;
27525 /* 12 bits for width */
27526 pa[pav].hres = 0xFFF & ((p[i+0] << 4) | (p[i+1] >> 4));
27527 /* 12 bits for height */
27528 pa[pav].vres = 0xFFF & ((p[i+1] << 8) | p[i+2]);
27529 s->hres = pa[pav].hres;
27531 /* frame rate code, 4 bits */
27532 frc = 0x0F & p[i+3];
27533 if (frc > 8) frc = 0;
27535 if (vcv >= 0) {
27536 vc[vcv].vfr = frame_rate_table[frc];
27537 vc[vcv].hres = pa[pav].hres;
27538 vc[vcv].vres = pa[pav].vres;
27540 /* set picture type so next loop doesn't need to run */
27541 break; /* found it, stop the loop */
27548 /* not -m (or frames exceeded) or info cap, or not VC cap: do nothing */
27549 if (0 == cap_frames) return;
27550 if (CAP_INFO == cap_now) return;
27552 /* count single pgm vc video packets separately */
27553 if (cap_pn < 1) return;
27555 /* is safe to assume cap_pids[2] is video pid.
27556 it does not get set until pmt is parsed, so acts as another hold down
27558 if (pid != cap_pids[2]) {
27559 pkt.keep = 0;
27560 return;
27563 /* passed allocation and vc test and payload start indicator test */
27565 /* TESTME: not using Physical Clock Reference so this doesn't matter. */
27566 pkt.afcpcr = ~0; /* first pcr is loaded, ok to process rest */
27568 b = 0xFFFFFFFFUL;
27569 if (0 != psi)
27571 for (i = 4; i < 188; i++)
27573 b = (b<<8) | p[i];
27575 /* look for start code */
27576 if (0x00000100UL != (0xFFFFFF00UL & b)) continue;
27578 /* Sequence Start code 000001B3 */
27579 if ( 0x1B3 == b ) {
27581 /* output byte count at sequence start */
27582 if ( sequence_idx < SEQUENCE_MAX ) {
27584 /* This tracks the number of bytes since last sequence to save memory,
27585 because the offset between sequences will never need more than 32 bits.
27586 In actual practice, it seems to vary between 18 and 20 bits wide for HD.
27588 seq = outbc - last_sbo;
27589 sequences[ sequence_idx ] = 0xFFFFFFFFLL & seq;
27590 sequence_idx++;
27591 last_sbo = outbc;
27592 } else {
27593 dvrlog( LOG_INFO, "use larger SEQUENCE_MAX");
27594 dvrlog( LOG_INFO, ".tsx write disabled");
27596 /* ts capture must set cap_frames to arg_frames or else all captures
27597 that follow the capture that exceeded FRAME_MAX and/or SEQUENCE_MAX will
27598 not have sequence data saved, even if they may fit in sequnces[]/frames[].
27600 cap_frames = 0;
27603 /* Intra frame picture type */
27604 if ( frame_idx < FRAME_MAX ) {
27605 vpn = pkt.vcvid - last_vpn;
27607 /* top 2 bits hold picture type. 1 is Intra frame picture type */
27608 frames[ frame_idx ] = 1 << 14;
27610 /* packet count since last picture (should never exceed 16k packets) */
27611 frames[ frame_idx ] |= 0x3FFF & vpn;
27612 frame_idx++;
27613 last_vpn = pkt.vcvid;
27614 } else {
27615 dvrlog( LOG_INFO, "use larger FRAME_MAX");
27616 dvrlog( LOG_INFO, ".tsx write disabled");
27617 cap_frames = 0;
27618 break; /* save cycles, stop looping */
27620 /* break because using Sequence as boundary instead of Intra picture */
27621 break;
27624 /* Picture start code means I P or B frame */
27625 if ( 0x100 == b ) {
27627 /* wait for first sequence */
27628 if (0 != sequence_idx) {
27629 // pct = 7 & (p[i+2] >> 3);
27630 pct = 3 & (p[i+2] >> 3);
27632 /* I frame handled above, only check P and B frames */
27633 if ( (2 == pct) || (3 == pct) ) {
27634 pict[ pct ]++;
27635 if (frame_idx < FRAME_MAX) {
27636 vpn = pkt.vcvid - last_vpn;
27638 /* top 2 bits hold picture type. 2 is P type, 3 is B type picture */
27639 frames[ frame_idx ] = pct << 14;;
27641 /* packet count since last picture (should never exceed 16k packets) */
27642 frames[ frame_idx ] |= 0x3FFF & vpn;
27643 frame_idx++;
27644 last_vpn = pkt.vcvid;
27645 } else {
27646 dvrlog( LOG_INFO, "use larger FRAME_MAX");
27647 dvrlog( LOG_INFO, ".tsx write disabled");
27648 cap_frames = 0;
27649 break; /* save cycles, stop looping */
27653 /* break when picture type found */
27654 break;
27655 } /* keep looping past headers for picture start code */
27657 } /* end of packet test loop */
27658 } /* end of payload start indicator 1 */
27660 /* hold down output until first sequence start */
27661 if ( 0 == sequence_idx) {
27662 pkt.keep = 0;
27663 return;
27666 pkt.vcvid++;
27669 /*********************************************************** A/52 AUDIO PES */
27670 /* Single Program capture holds down output until first video sequence.
27671 Full capture gets all the packets regardless of any other setting.
27672 No CRC32 to use for QoS. The CRC16 is buried too deep to be quick.
27674 /* static */
27675 void
27676 atsc_test_a52_audio_pes ( unsigned char *p, unsigned int pid )
27678 unsigned char psi;
27679 psi = 1 & (p[1]>>6);
27681 /* count single vc audio packets separately */
27682 if (cap_va >= 0) {
27683 pkt.vcaud++;
27685 /* hold down until pid matches cap_pids[3] (implies vc set or will be set) */
27686 if (pid != cap_pids[3]) {
27687 pkt.keep = 0;
27688 return;
27691 /* hold down until AFTER first video sequence */
27692 if (0 == sequence_idx) {
27693 pkt.keep = 0;
27694 return;
27697 /* hold down until first payload start */
27698 if (0 != psi) pkt.audps = ~0;
27699 if (0 == pkt.audps) pkt.keep = 0;
27702 /*****************************************************************************/
27704 /* test pid against vc[] video ES PIDs, z if match, nz otherwise */
27705 /* static */
27707 atsc_test_vc_vid( short pid )
27709 int i, j;
27710 for (i = 0; i < vc_ncs; i++)
27711 for (j = 0; j < vc[ i ].pmtes; j++)
27713 if ( 0x02 == vc[ i ].estype[ j ] )
27715 if ( pid == vc[ i ].espid[ j ] )
27716 return 0;
27717 return ~0;
27720 /* test pid against vc[] audio ES PIDs, z if match, nz otherwise */
27721 /* static */
27723 atsc_test_vc_aud( short pid )
27725 int i, j;
27726 for (i = 0; i < vc_ncs; i++)
27727 for (j = 0; j < vc[ i ].pmtes; j++)
27729 if ( 0x81 == vc[ i ].estype[ j ] )
27731 if ( pid == vc[ i ].espid[ j ] )
27732 return 0;
27733 return ~0;
27737 /***************************************************************** MPEG2 PES */
27738 /* ATSC parsed out, whats left is PAT, CAT, PMT, MPEG2 Video and A/52 Audio ES
27739 TEI should have been checked in test packet. Control PIDs are PAT CAT PMT.
27740 Control PIDs have CRC32 check done, and some may be filtered if user
27741 has selected a specific program to capture in show virtual channels..
27743 /* static */
27744 void
27745 atsc_test_mpeg2 ( unsigned char *p, unsigned short pid )
27747 unsigned char *r;
27748 unsigned char psi;
27749 int t, pan;
27751 t = 0;
27753 psi = 1 & (p[1]>>6);
27754 r = p+5;
27756 /* have to do these keep checks here or else may miss keeping */
27757 /* packets in any multi-packet payload */
27759 /* full cap keeps everything */
27760 if (-1 == cap_pn) {
27761 pkt.keep = ~0;
27762 } else {
27763 /* single program cap keeps only PAT+PMT+AUD+VID */
27764 t = find_cap_pid( pid );
27765 if (t == 0) pkt.keep = ~0;
27768 /* update counters */
27769 pkt.mpeg2++;
27771 /***************************************************************** MPEG2 PES */
27774 /********************************************************* MPEG2 PAT/CAT/PMT */
27776 /* Program Association Table ID 0 in PID 0 */
27779 if (0 == pid) {
27780 atsc_parse_pat( p ); /* has Program Map PIDs */
27781 return;
27784 /* is not pid == 1, is table id == 1 */
27785 /* not seen in terrestrial? definitely in cable */
27786 /* Controlled Access Table ID 1 in PID 1 */
27787 if (1 == pid) {
27788 atsc_test_cat( p, pid ); /* crc check at least */
27789 return;
27792 /* Program Map Table ID 2 in variable PID packet byte 5 */
27793 pan = find_pa_pmtpid( pid );
27794 if (-1 < pan) {
27795 /* has video and audio PIDs & descriptors */
27796 atsc_parse_pmt( p, pan );
27797 return;
27800 /********************************************************* MPEG2 PAT/CAT/PMT */
27803 /***************************************************************** MPEG2 PES */
27804 /* PAT CAT PMT checks done. Only thing left is video, audio and unknown PIDs.
27805 * Full capture checks against all pa[] PMT ES PIDs, counts matches and exits;
27806 * VC/Program capture counts only current selected matching PIDs.
27807 * Only thing left after this point is handling the unknown data types
27808 * which will not be done because they do not affect playback.
27811 if (0 == atsc_test_vc_vid(pid)) pkt.vid++; /* overall video count */
27812 if (0 == atsc_test_vc_aud(pid)) pkt.aud++; /* overall audio count */
27815 atsc_test_mpeg2_video_pes( p, pid );
27817 if (cap_pn < 1) {
27818 pkt.pes++; /* count overall PES */
27819 return;
27822 /* no VC video until first sequence start seen */
27824 /* is selected video? */
27825 if ( pid == cap_pids[2] ) {
27826 // atsc_test_mpeg2_video_pes( p, pid );
27827 if (0 != pkt.keep) {
27828 pkt.pes++;
27830 return;
27833 /* no VC audio until video first then audio psi 1 */
27835 /* is selected audio? */
27836 if ( pid == cap_pids[3] ) {
27838 /* still waiting for video? */
27839 if (0 == sequence_idx) {
27840 pkt.keep = 0;
27841 return;
27842 } else {
27844 /* have video, ok to add audio */
27845 pkt.keep = ~0;
27846 /* fall thru to test a52 */
27848 } else {
27850 /* not cap pid[2 or 3], don't keep it */
27851 pkt.keep = 0;
27852 return;
27855 atsc_test_a52_audio_pes( p, pid );
27856 if (0 != pkt.keep) pkt.pes++;
27857 return;
27859 /***************************************************************** MPEG2 PES */
27862 /***************************************************************** ATSC MGT */
27863 /* static */
27865 atsc_find_mg_tt( unsigned int tt )
27867 int i;
27868 tt &= 0x1FFF;
27870 for (i = 0; i < mg_idx; i++)
27871 if (tt == mg[i].tt)
27872 return i;
27873 return -1;
27877 /***************************************************************** ATSC DCCSCT
27878 not using directed channel change selection code table
27880 /* static */
27881 void
27882 atsc_test_dccsct ( unsigned char *p )
27884 pkt.dccsct++;
27885 return;
27888 /***************************************************************** ATSC DCCT
27889 not using directed channel change table
27891 /* static */
27892 void
27893 atsc_test_dcct ( unsigned char *p )
27895 pkt.dcct++;
27896 return;
27900 /******************************************************************* ATSC RRT
27901 * FIXME: RRT is about to change, need to stop using built-in table.
27902 * Check the payload bits and check CRC32 for QoS
27904 * NOTE: this does not handle more than a single RRT
27906 /* static */
27907 void
27908 atsc_test_rrt ( unsigned char *p, int tn )
27910 struct sig_s *s;
27911 unsigned char *r, vn;
27912 unsigned short sl; /*, sr; */
27913 /* unsigned char rr, vn, cni, sn, lsn, pv; */
27914 unsigned int rrt_crc, pid;
27915 int terr, mi;
27917 pkt.rrtp++;
27918 sl = 0;
27919 rrt_crc = 0;
27921 pid = ((0x1F & p[1]) << 8) | p[2];
27923 /* returns 0 if packet done, else 1 */
27924 pkt.frrt = PAY_READ;
27925 rrt.payok = atsc_build_payload( &rrt, p, "RRT" );
27926 /* no checking until payload built */
27927 if (0 != rrt.payok) return;
27929 rrt.payoff = 0;
27931 r = rrt.payload;
27933 terr = 0;
27935 /* RRT table ID CA CA, protocol version 0 */
27936 if ( (ATSC_RRT != r[0]) || (0x00 != r[8]) ) terr = ~0;
27938 if (0 != arg_strict) { /* strict ATSC check */
27939 if ( ( 0xF0 != (0xF0 & r[1]) ) /* syntax/priv/rsvd */
27940 || ( 0xFF != r[3] ) /* reserved */
27941 || ( 0xC1 != (0xC1 & r[5]) ) /* reserved/cni */
27942 || ( 0x00 != r[6] ) /* must be 0 */
27943 || ( 0x00 != r[7] ) /* must be 0 */
27944 ) terr = ~0;
27947 /* reserved bits wrong */
27948 if (0 != terr) {
27949 atsc_strict_log( r, "RRT BAD TT/RB");
27950 return;
27952 /* 12 bit section length */
27953 r++; /* r[1] */
27954 sl = (0xF & *r) << 8;
27955 r++; /* r[2] */
27956 sl |= *r;
27958 r++; /* r[3]; */
27959 r++; /* r[4]; */
27960 r++; /* r[5] version */
27961 vn = 0x1F & (*r >> 1);
27963 rrt_crc = calc_crc32( rrt.payload, sl+3 );
27965 if (rrt_crc != 0) {
27966 pkt.crcrrt++;
27967 advrlog( LOG_INFO, "RRT bad CRC");
27968 pkt.frrt = PAY_DROP;
27969 return;
27972 /* because it's not parsed, flag it as good and done */
27973 pkt.frrt = PAY_GOOD;
27974 s = &ptc[cap_chan].sig;
27975 s->psip &= 0x7f;
27976 s->psip |= 16;
27977 pkt.rrt++; /* not displaying these counts yet */
27979 if (vn == rrt.vn) return;
27981 /* build mgt text */
27982 mi = atsc_find_mg_tt( 0x300 + tn );
27983 if (-1 < mi) {
27984 mg[ mi ].vn1 = vn;
27985 if (sl > mg[ mi ].nb1)
27986 mg[ mi ].nb1 = sl;
27989 rrt.vn = vn;
27990 return;
27994 /**************************************************************** ATSC ETT MSS
27995 A/65b Table 6.38 Multiple String Structure
27996 does Huffman decomp
27997 find pgo index via etmid and adds description to it
27998 r has source buffer
28000 /* static */
28001 void
28002 atsc_parse_mss_ett ( unsigned char *r, unsigned int n, unsigned int etmid, int pgo )
28004 struct event_s *e;
28005 unsigned int dstr, dseg, dcomp, dmode, dchr;
28006 unsigned int i,j;
28007 char *t;
28008 char *d;
28009 char lang[4];
28011 /* e is event, t is description buffer */
28012 e = &epg[pgo];
28013 t = e->desc;
28014 d = t;
28016 dstr = r[0];
28017 for (i = 0; i < dstr; i++) {
28018 memcpy(lang, &r[1], 3);
28019 lang[3] = 0;
28020 r += 4; /* 3? */
28021 dseg = *r++;
28022 for (j = 0; j < dseg; j++) {
28023 dcomp = *r++;
28024 dmode = *r++;
28025 dchr = *r++;
28027 t[0] = 0; /* need a blank if nothing done */
28029 /* no compression and unicode page 0 */
28030 if ( (0 == dcomp) && (0 == dmode) && (dchr > 0) ) {
28031 dchr++;
28032 if (dchr > PDZ) dchr = PDZ;
28033 /* uncompressed should never be over PDZ bytes */
28034 astrncpy( t, (char *)r, dchr );
28038 decompress huffman encoded description text
28039 no check for mode=0xFF because KPXB is non-compliant at 0.
28040 but was only source of Huffman compressed EPG in my area.
28041 the spec says mode=0xFF so KPXB is off-spec.
28042 can't seem to pull KPXB in anymore. it was always very weak.
28045 /* KPXB requires non-strict mode */
28046 if ( (0 == arg_strict) || ((0 != arg_strict) && (0xFF == dmode)) )
28048 if ( (1 == dcomp) || (2 == dcomp) ) {
28049 if (dchr > PDZ) dchr = PDZ;
28050 huffman_decode( d, r, PDZ-1, dchr, dcomp);
28054 /* it's not huffman, it's compressed with something else */
28055 if ( (dcomp > 2) || ((dmode > 0) && (dmode < 255)) )
28057 snprintf( t, 20, "[%s] L%02X C%02X M%02X",
28058 lang, dchr, dcomp, dmode);
28061 /* NUL term enforcement */
28062 t[PDZ-1] = 0;
28063 e->dmode = dmode;
28064 e->dchr = dchr;
28065 e->dcomp = dcomp;
28066 astrncpy( e->dlang, lang, 4 );
28073 /******************************************************************** ATSC ETT
28074 process ett packet and copy the description to EPG if ETMID found
28076 /* static */
28077 void
28078 atsc_parse_ett ( unsigned char *p, unsigned int n )
28080 struct sig_s *s;
28081 char t[16];
28082 unsigned char *r;
28083 unsigned short sl, etidx, pid, sr;
28084 unsigned char vn, cni, sn, lsn, pv;
28085 unsigned int ett_crc = 0;
28086 unsigned int etmid = 0;
28087 int sv, mi, pgo, terr;
28089 if (NULL == ett) return; /* prevent [l] [p] [enter] crash */
28090 pkt.ettp++;
28092 snprintf( t, 7, "ETT-%02X", n);
28094 vn = cni = sn = lsn = pv = sl = etidx = 0;
28096 pid = ((0x1F & p[1]) << 8) | p[2];
28098 pkt.fett = PAY_READ;
28099 /* returns 0 if packet done, else 1 */
28100 ett[n].payok = atsc_build_payload( &ett[n], p, t );
28101 /* no parsing until payload built */
28102 if (0 != ett[n].payok) return;
28103 ett[n].payoff = 0;
28105 /* hold down until an eit seen */
28106 if (0 == pkt.eit) return;
28108 /* hold down until eit_vn for eit-0 vc 0 is set.
28109 hmm no. vc could be anything
28110 if ( 0xFF == eit_vn[0][0] ) return;
28112 /* keep track of last ETT-n seen */
28113 if ( (n+1) > ett_idx) ett_idx = n+1;
28115 r = ett[n].payload;
28117 terr = 0;
28118 /* ETT table ID CC, protocol version 0 */
28119 if ( (ATSC_ETT != r[0]) || (0x00 != r[8]) ) terr = ~0;
28121 if (0 != arg_strict) { /* strict ATSC check? */
28122 if ( ( 0xF0 != (0xF0 & r[1]) ) /* syntax/private/reserved */
28123 || ( 0x00 != r[3] ) /* zero extID? not KHOU */
28124 || ( 0x00 != r[4] ) /* zero extID? not KHOU */
28125 || ( 0xC1 != (0xC1 & r[5]) ) /* reserved/cni */
28126 || ( 0x00 != r[6] ) /* must be 0 */
28127 || ( 0x00 != r[7] ) /* must be 0 */
28129 terr = ~0;
28132 /* reserved bits wrong */
28133 if (0 != terr) {
28134 t[6] = ' ';
28135 t[7] = 'b';
28136 t[8] = 'a';
28137 t[9] = 'd';
28138 t[10] = 0;
28140 atsc_strict_log( r, t );
28141 pkt.crcett++; /* hmm need different error category rbiett */
28142 return; /* check reserved bits first saves time */
28145 /* 12 bit section length */
28146 r++; /* r[1] */
28147 sl = (0xF & *r) << 8;
28148 r++; /* r[2] */
28149 sl |= *r;
28151 ett_crc = calc_crc32( ett[n].payload, sl+3 );
28152 if (ett_crc != 0) {
28153 pkt.crcett++;
28154 advrlog( LOG_INFO, "%s bad CRC", t);
28155 pkt.fett = PAY_DROP;
28156 return;
28159 /* ett table id extension */
28160 etidx = (r[3] << 8) | r[4]; /* should be 0 */
28161 r++; /* r[3] */
28162 etidx = *r << 8;
28163 r++; /* r[4] */
28164 etidx |= *r;
28166 /* these show up pretty often on CBS, they seem to be some kind of extra
28167 identifier for CBS ETT, but not using it, and apparently don't need it.
28169 #if 1
28170 /* two extra branch predictions */
28171 if (etidx != 0) {
28172 advrlog( LOG_INFO, "ETT-%02X tid ext %04X not 0", n, etidx);
28174 #endif
28176 r++; /* r[5] */
28177 vn = 0x1F & (*r >> 1);
28179 pkt.fett = PAY_PARSE; /* indicator for parse */
28181 /* stay on r[5] */
28182 cni = 1 & *r; /* current next indicator */
28183 r++; /* r[6] */
28184 sn = *r; /* section number */
28185 r++; /* r[7] */
28186 lsn = *r; /* last section number */
28188 r++; /* r[8] */
28189 pv = *r;
28190 if (pv != 0) return; /* protocol version 0 only */
28192 /* etmid in 32 bits, top 16 are src */
28193 r++; /* r[9] */
28194 etmid = *r << 24;
28195 r++; /* r[10] */
28196 etmid |= *r << 16;
28197 r++; /* r[11] */
28198 etmid |= *r << 8;
28199 r++; /* r[12] */
28200 etmid |= *r;
28202 r++; /* r[13] */
28203 /* start of text to extract */
28205 #if 1
28206 /* extra check to save further processing if no VCT or PAT with Source IDs */
28207 sr = 0xFFFF & (etmid >> 16);
28208 sv = find_vc_src( sr );
28209 if (-1 == sv) return; /* hold down until the Source ID is valid */
28210 /* demoth */
28211 advrlog( LOG_INFO, "ETT-%02X VN %02X ETMID %08X ETIDX %04X",
28212 n, vn, etmid, etidx);
28213 #endif
28215 /* find ETMID in epg[] array if EIT put it there */
28216 pgo = find_epg_etmid( etmid );
28217 if (pgo < 0) return; /* -1 or offset */
28219 /* but even if it doesn't change the payload is counted good */
28220 pkt.fett = PAY_GOOD;
28222 s = &ptc[cap_chan].sig;
28223 s->psip &= 0x7f;
28224 s->psip |= 8;
28226 /* if version doesn't change, do nothing */
28227 if (vn == epg[ pgo ].ettvn) return;
28229 /* fix KUHT KRIV KPRC with vn switching between values, bad psip encoder */
28230 if (0xFF != epg[pgo].ettvn) return; /* ignore it version set */
28232 mi = atsc_find_mg_tt( 0x200 + n );
28233 if (-1 < mi) {
28234 mg[ mi ].vn1 = vn;
28235 if (sl > mg[ mi ].nb1)
28236 mg[ mi ].nb1 = sl;
28239 /* debug ett vn issues with some stations */
28240 /* you should only see log lines with VN FF */
28241 advrlog( LOG_INFO, "ETT %02X VN %02X ETMID %08X PGO %04X VN %02X",
28242 n, vn, etmid, pgo, epg[pgo].ettvn );
28244 epg[ pgo ].ettn = n;
28245 epg[ pgo ].ettvn = vn;
28246 atsc_parse_mss_ett( r, n, etmid, pgo );
28247 pkt.ett++;
28251 /* bubble sort PEZ entries from epg offset pgo. FOX sends a confused EIT */
28252 /* swapping pointers would be faster. EIT version check limits processing */
28253 /* eit locks prevent race condition with UI thread running build pg epg */
28254 /* static */
28255 void
28256 atsc_sort_eit_epg ( unsigned int pgo )
28258 int i, j, p, pi, pj, v, e, z;
28259 struct event_s et;
28260 char s1[32], s2[32];
28262 z = sizeof(struct event_s);
28263 p = pgo;
28264 e = p / PEZ; /* div pgo by 8 to find which EIT lock */
28265 p = e * PEZ; /* mul result by 8 to find start of 8 entry EIT in epg[] */
28266 v = e / EIZ; /* div pgo by (8 * 128) to find which VC # */
28267 v &= 0x07;
28268 advrlog( LOG_INFO, "%s VC %d EIT-%02X %s", WHO, v, 0x7F & e,
28269 (0 == epg_locks[e])?"free":"lock" );
28271 /* stopping at epg[].st = 0 is same? not for FOX. They are non-compliant. */
28272 /* FOX must practice the GIGO management rule. Garbage rolls downhill to us. */
28273 for (i=0; i < PEZ; i++) { /* why make standards if FOX won't use them? */
28274 if (0 == epg[ p + i ].st) /* set FOX broken blank events to future */
28275 epg[ p + i ].st = ~0; /* it really must suck bad to work for FOX */
28276 } /* FOX is a perfect example of the motto: Quality does not exist here. */
28277 /* What can you expect from FOX? They show 4:3 widescreen with pillarboxes. */
28279 #define TP1 0
28280 for (i = 0; i < (PEZ - 1); i++) {
28281 pi = p + i;
28282 for (j = i + 1; j < PEZ; j++) {
28283 pj = p + j;
28284 text_time( s1, epg[ pi ].st, 0);
28285 text_time( s2, epg[ pj ].st, 0);
28286 if ( epg[ pj ].st < epg[ pi ].st) {
28287 if (p <= TP1) {
28288 advrlog( LOG_INFO,
28289 "swap pgo[%04X] %s <> pgo[%04X] %s",
28290 pi, s1, pj, s2);
28292 memcpy( &et, &epg[ pi ], z );
28293 memcpy( &epg[ pi ], &epg[ pj ], z );
28294 memcpy( &epg[ pj ], &et, z );
28297 if ( ~0 == epg[ pi ].st ) break; /* end of last loop */
28298 text_time( s1, epg[ pi ].st, 0 );
28299 if (p <= TP1) advrlog( LOG_INFO, "sort pgo[%04X] %s", pi, s1 );
28302 epg_locks[ e ] = 0; /* ok for build pg pgm to use this EIT info */
28303 advrlog( LOG_INFO, "EPG%02d VC %d EIT-%02X release", cap_chan, v, 0x7F & e);
28306 /************************************************************* ATSC EIT RR MSS
28307 A/65b Table 6.38 Multiple String Structure
28308 does Huffman decomp
28309 put EIT content advisory description text in epg[pgo].rating.
28310 vchip rating is truncated to 15 chars + NUL
28312 /* static */
28313 void
28314 atsc_test_eit_rr_mss ( unsigned char *r, unsigned int pgo )
28316 unsigned int nstr, nseg, comp, mode, nchr;
28317 unsigned int i,j;
28318 char lang[4];
28319 char *t;
28320 char *d;
28322 t = epg[pgo].rating;
28323 d = t;
28325 /* get mss number of strings, only using the last one seen */
28326 nstr = r[0];
28327 for (i = 0; i < nstr; i++)
28329 memcpy( lang, &r[1], 3 ); /* copy out language for this string */
28330 lang[3] = 0; /* and terminate with nul for display */
28332 r += 4;
28333 /* get number of segments for this string. not one, not handled */
28334 nseg = *r;
28335 r++;
28336 for (j = 0; j < nseg; j++)
28338 comp = *r++;
28339 mode = *r++;
28340 nchr = *r++;
28341 /* keeps last nul if t cut down to nchr size */
28342 if (nchr > PRZ) nchr = PRZ;
28344 t[0] = 0; /* need a blank if nothing done */
28346 /* uncompressed text unicode page 0 */
28347 if ( (comp == 0) && (mode == 0) && (nchr > 0) ) {
28348 nchr++;
28349 astrncpy( t, (char *)r, nchr );
28352 /* what huffman tree would rating text use? this is short text "TV-14-V-CC" */
28353 /* KPXB requires non-strict mode */
28354 if ( (0 == arg_strict) || ((0 != arg_strict) && (0xFF == mode)) )
28356 if ((1 == comp) || (2 == comp)) {
28357 huffman_decode( d, r, PRZ, nchr, comp);
28361 /* it's compressed with something else */
28362 if ( (comp > 2) || ((mode > 0) && (mode < 255)) )
28364 snprintf( t, PRZ-1, "L%02X C%02X M%02X", nchr, comp, mode);
28369 #define USE_FOX_RR_FIX
28370 #ifdef USE_FOX_RR_FIX
28371 /* UPN KTXH used to do this but they're gone now. faster without it. */
28372 /* convert ';' separator to - like everyone else */
28373 for (i=0;i<PRZ;i++) {
28374 if (t[i] == ';') t[i] = '-';
28376 #endif
28380 /*************************************************************** ATSC EIT MSS
28381 A/65b Table 6.38 Multiple String Structure
28382 does Huffman decomp
28383 NOTE: this one does need to use multiplex math so find etmid will work
28385 /* static */
28386 void
28387 atsc_parse_mss_eit ( unsigned char *r, unsigned int pgo, unsigned int etmid,
28388 unsigned int est, unsigned int els )
28390 struct event_s *e;
28391 unsigned int nstr, nseg, comp, mode, nchr, sr, i, j;
28392 char *d;
28393 char *t;
28394 char lang[4];
28395 int v;
28396 time_t st;
28398 e = &epg[pgo];
28399 t = e->name;
28400 d = t;
28402 nstr = r[0];
28403 nchr = 0;
28405 for (i = 0; i < nstr; i++)
28407 memcpy(lang, &r[1], 3);
28408 lang[3] = 0;
28409 r += 4;
28410 nseg = *r++;
28412 /* FIXME: No multi-segment. A/65[?] says not used in broadcast.
28413 This does not handle multiple segments properly.
28414 It will keep only the last segment. Cable might need?
28416 for (j = 0; j < nseg; j++)
28418 comp = *r++;
28419 mode = *r++;
28420 nchr = *r++;
28422 t[0] = 0; /* need a blank if nothing done */
28424 /* no compression, unicode page 0 */
28425 if ((comp == 0) && (mode == 0) && (nchr > 0) ) {
28427 nchr++;
28428 /* nchr can't be over PNZ */
28429 if (nchr > PNZ) nchr = PNZ;
28430 astrncpy( t, (char *)r, nchr );
28433 /* KPXB requires non-strict mode */
28434 if ( (0 == arg_strict) || ((0 != arg_strict) && (0xFF == mode)) )
28436 if ((1 == comp) || (2 == comp)) {
28437 if (nchr > PNZ) nchr = PNZ;
28438 huffman_decode( d, r, PNZ-1, nchr, comp );
28442 /* fall back to numeric mode compressed with something else */
28443 if ( (comp > 2) || ((mode>0) && (mode<255)) )
28445 snprintf( t, PNZ-1, "[%s] L%02X C%02X M%02X",
28446 lang, nchr, comp, mode);
28449 /* save truncated event name to reduce processing later */
28450 event_truncate( e->tname, e->name, 2, sizeof(e->tname) );
28451 e->tname[16] = 0; /* limit to 16 bytes */
28453 use only the hours and minutes advertised.
28454 start time seconds is almost always wrong here.
28456 est -= (est % 60);
28458 e->etmid = etmid; /* event text message id */
28459 e->st = est; /* event start time */
28460 st = (time_t) est;
28462 localtime_r( &st, &e->stm );
28464 e->ls = els; /* event length in seconds */
28465 e->ncomp = comp; /* compression type */
28466 e->nmode = mode; /* unicode mode */
28467 e->nchr = nchr; /* number of bytes */
28468 e->ettn = 0xFF; /* waiting for ETT */
28469 e->ettvn = 0xFF; /* waiting for ETT */
28471 astrncpy( e->nlang, lang, 4 ); /* event name language */
28473 /* this WILL bog the capture, even if logging to ram drive */
28474 #if 0
28475 dvrlog( LOG_INFO, "pgo %04X time %u etmid %08X %s", pgo, est, etmid, s);
28476 #endif
28480 get source id to find out which VC it belongs to
28481 so program number can be looked up from there
28483 VCT check maybe is not needed. if there is no VCT, will not be here?
28484 or could be here but with PAT+PMT and a broken VCT
28485 without the source id cross-reference, the EIT can't be assigned
28486 the correct slot in the EPG and will generate trash entries.
28489 if ( (0 != vct_ncs) || (0 != pat_ncs) )
28491 sr = 0xFFFF & ( etmid >> 16 );
28492 v = find_vc_src( sr ); /* if no vc, returns -1 */
28493 advrlog( LOG_INFO, "EIT mss ETMID %08X sr %d %d", etmid, sr, v );
28494 /* get program add needs to set timer name to name.pn */
28495 if (-1 < v) {
28496 advrlog( LOG_INFO, "ETMID %08X sr %d v %d pgm %d",
28497 etmid, sr, v, vc[v].pn );
28498 e->pn = vc[ v ].pn;
28499 e->vc = v;
28500 e->va = 0; /* first audio only */
28504 /* not needed unless nseg > 1, which is never is, so far
28505 r += nchr;
28509 #if 1
28510 /* see if event exists in search list. if it does, add to timers */
28511 search_events( e, &pgm, WHO );
28512 /* NO: not here? wastes CPU? looks ok so far */
28513 #endif
28517 /************************************************************* ATSC EIT DESC */
28519 p points to start of EIT descriptor(s)
28520 do until len exhausted
28521 there might be some rating text in EIT event to save RRT lookup,
28522 would like to find the HD descriptor. now that would be useful.
28523 some stations send AC-3 descriptors per event
28526 /* static */
28527 void
28528 atsc_parse_eit_descriptors ( unsigned char *p, int len, int pgo,
28529 int eitn, int evn )
28531 unsigned char *r, *q;
28532 unsigned char n, l;
28533 unsigned int i,j, dlen;
28534 int atcc, z;
28536 atcc = 0;
28537 dlen = len;
28539 r = p;
28540 n = *p;
28542 epg[pgo].rating[0] = 0;
28544 while ( dlen > 0 ) {
28545 n = *r++;
28546 l = *r++;
28547 dlen -= 2;
28549 /* r points to start of descriptor data */
28550 switch( n ) {
28552 /* useful? yes, to waste space in program guide */
28554 /* stuffing */
28555 case 0x80:
28556 break;
28558 /* MPEG video descriptor. Probably not used in EIT, but could be
28559 used for MPEG parameters to determine SD or HD or widescreen.
28561 case 0x02:
28562 dvrlog( LOG_INFO, "EIT %02X Event %d %s has MPEG desc?",
28563 eitn, evn, epg[pgo].name);
28564 break;
28566 /* A52 audio descriptor. If given, it may help determine if the event
28567 will have 2.0 or 5.1 audio, can add this info to web EPG? NO
28569 One problem is stations that actually send this in the EIT, and also
28570 have SAP, get two descriptors for the audio for one event. It's not
28571 clearly laid out in the A/65b spec how to tell which is which, but most
28572 examples list main audio first. If the station sends the order backwards,
28573 then trying to select one audio from the EPG will get it backwards, too.
28574 It could be possible to list the 'main audio' descriptors first.
28576 What's worse is the station doesn't send the future 2.0/5.1 info. It's
28577 all the same audio descriptor info that you get from any station cap.
28579 This makes it junk until bcasters put correct data in EIT event descriptor.
28581 case 0x81:
28582 break;
28584 /* Caption service descriptor. This seems to actually work per EIT event. */
28585 case 0x86:
28586 if ( 0xC0 == (0xC0 & r[0]) ) { /* reserved bits check */
28587 unsigned char nos, cct, l21f, csn, er, war;
28588 char lang[4];
28590 q = r;
28591 nos = 0x1F & q[0];
28592 q += 1; /* skip parsed */
28594 for (i = 0; i < nos; i++) {
28595 if ( 0x40 == (0x40 & q[3]) ) { /* reserved bit */
28596 memcpy( lang, q, 3 );
28597 lang[3] = 0; /* 3 char lang code per ISO 639.2/B */
28598 q += 3; /* skip lang */
28599 cct = 1 & (q[0]>>7);
28600 atcc = cct;
28601 if (1 == cct) {
28602 /* see if it's EIA-708-A ATVCC */
28603 csn = 0x3F & q[0];
28604 /* add CC to description if ATVCC type found */
28605 /* atcc = ~0; */
28606 } else {
28607 /* don't care about EIA/CEA-608-B. that's NTSC */
28608 if ( 0x3E == (0x3E & q[0]) ) { /* reserved */
28609 l21f = 1 & q[0];
28612 if ( (0x3F == (0x3F & q[1])) && (0xFF == q[2]) ) {
28613 er = 1 & (q[1]>>7);
28614 war = 1 & (q[1]>>6);
28615 q += 3;
28620 break;
28622 /* content advisory descriptor */
28623 case 0x87:
28624 q = r; /* dont want to change r */
28625 if ( 0xC0 == (0xC0 & q[0]) ) {
28626 unsigned char rrc, rr, rd, rdj, rv, rdl;
28628 rrc = 0x3F & q[0];
28629 q++; /* skip to loop vals */
28631 /* i number of rating regions. should be 1 for US ATSC, but may have more */
28632 for (i = 0; i < rrc; i++) {
28633 rr = q[0]; /* rating region */
28634 rd = q[1]; /* rated dimensions */
28635 q += 2;
28637 /* uses built-in RRT table rather than mess with RRT parse right now */
28638 for (j = 0; j < rd; j++) {
28639 if ( 0xF0 == (0xF0 & q[1]) ) {
28640 rdj = q[0]; /* rating dimension j */
28641 rv = 0xF & q[1]; /* rating value */
28642 q += 2;
28644 /* save to pgm struct from built-in table offsets
28645 dvrlog( LOG_INFO, "rdj %02X rv %02X", rdj, rv);
28647 snprintf( epg[pgo].rating, PRZ-1, "%s",
28648 ratings[(rdj*10)+rv+1] );
28649 /* truncated event name.png? */
28650 epg[pgo].movie = 0;
28651 /* movie[rv].png? */
28652 if (7 == rdj) epg[pgo].movie = rv;
28655 /* trying also for events with rating text provided, will override above */
28656 rdl = q[0]; /* rating descriptor length */
28657 q++; /* point to mss for this EIT */
28658 if (rdl != 0) {
28659 /* add rr text to epg[].rating */
28660 atsc_test_eit_rr_mss( q, pgo );
28664 break;
28666 /* they are *supposed* to flag the shows in the EIT too, but
28667 that doesn't seem to be happening in any streams seen so far.
28668 station either has flag on all the time or off all the time.
28670 case 0xAA:
28671 dvrlog( LOG_INFO, "Ch %02X EIT-%02X has RC", cap_chan, eitn);
28672 break;
28674 /* needs parsing if shows up here */
28675 default:
28676 break;
28678 r += l;
28679 dlen -= l;
28682 /* add CC to rating if caption descriptor found for ATVCC in EIT or PMT */
28683 if ( (atcc != 0) || (pmt_atcc) ) {
28685 epg[pgo].rating[ PRZ - 5 ] = 0;
28686 z = strlen( epg[pgo].rating );
28688 /* append -CC or CC by itself */
28689 asnprintf( epg[pgo].rating, PRZ, "%sCC", (0 == z) ? "":"-" );
28693 /****************************************************************** ATSC EIT */
28695 process eit[n] packet based on payload start indicator
28696 n is EIT # from MGT
28698 /* static */
28699 void
28700 atsc_parse_eit ( unsigned char *p, unsigned int n )
28702 struct sig_s *s;
28703 unsigned char *r;
28704 unsigned short sl, sr, pid;
28705 unsigned char sn, lsn, pv, numevs, cni, vn;
28706 unsigned int eit_crc = 0;
28707 unsigned int pgo = 0;
28708 unsigned short eid; /* event id */
28709 unsigned char etmloc; /* etm location */
28710 unsigned int els; /* length in seconds */
28711 unsigned char tlen; /* title length */
28712 unsigned int etmid; /* match val for text descripton */
28713 int i, v, mi;
28714 char t[16];
28715 int terr;
28716 time_t est; /* event start time */
28719 if (NULL == eit) return; /* prevent [l] [p] [enter] crash */
28720 pkt.eitp++;
28721 snprintf( t, 7, "EIT-%02X", n);
28723 pid = ( (0x1F & p[1]) << 8) | p[2];
28725 pkt.feit = PAY_READ;
28727 eit[n].payok = atsc_build_payload( &eit[n], p, t);
28729 /* if payok is 0, eit payload is done, so parse it else get next packet */
28730 if (0 != eit[n].payok) return;
28732 eit[n].payoff = 0;
28735 if (0 == vct_ncs) return; /* no vct, no srcid, no etmid, no epg */
28737 /* keep track of last EIT-n seen, so EIT-0 gives eit_idx 1 */
28738 if ((n+1) > eit_idx) eit_idx = n+1;
28740 r = eit[n].payload;
28742 terr = 0;
28744 /* EIT table ID 0xCB, protocol version 0 */
28745 if ( (ATSC_EIT != r[0]) || (0x00 != r[8]) ) terr = ~0;
28747 if (0 != arg_strict) { /* strict ATSC check? */
28749 if ( ( 0xF0 != (0xF0 & r[1]) ) /* syntax/private/reserved */
28751 || ( 0xC1 != (0xC1 & r[5]) ) /* reserved/cni */
28752 /* || ( 0x01 != (0x01 & r[5]) ) */ /* KTXH was missing bits7:6 */
28754 || ( 0x00 != r[6] ) /* must be 0? */
28755 || ( 0x00 != r[7] ) /* must be 0? */
28757 terr = ~0;
28760 /* reserved bits wrong */
28761 if (0 != terr) {
28762 t[6] = ' ';
28763 t[7] = 'b';
28764 t[8] = 'a';
28765 t[9] = 'd';
28766 t[10] = 0;
28768 atsc_strict_log( r, t );
28769 pkt.crceit++;
28770 return;
28773 /* 12 bit section length */
28774 r++; /* r[1] */
28775 sl = (0xF & *r) << 8;
28776 r++; /* r[2] */
28777 sl |= *r;
28779 eit_crc = calc_crc32( eit[n].payload, sl+3);
28781 if (eit_crc != 0) {
28782 pkt.crceit++;
28783 advrlog( LOG_INFO, "%s bad CRC", t );
28784 pkt.feit = PAY_DROP;
28785 return;
28788 /* 16 bit source id, used to compute etmid and to check versions */
28789 r++; /* r[3] */
28790 sr = *r << 8;
28791 r++; /* r[4] */
28792 sr |= *r;
28794 r++; /* r[5] */
28795 vn = 0x1F & (*r >> 1); /* 5 bit version number */
28797 /* tvct has source info for epg to match, so hold down until have tvct */
28798 if (0 == pkt.tvct) return;
28800 #if 0
28801 /* is there a need for this other than making it wait longer to start? */
28802 if ((0 == pkt.eit) && (0 != n)) return; /* hold down until rx eit 0 */
28803 #endif
28805 v = find_vc_src( sr ); /* find vc from src pgm */
28806 if (-1 == v) {
28807 dvrlog( LOG_INFO, "%s EIT-%02X can't find vc for src %04X",
28808 WHO, n, sr);
28809 return; /* abort if can't find, better luck next time */
28812 /* comment this line for debug to see all the unviewable entries
28813 skip the EIT if it's NTSC or INACTIVE
28815 if ( (0 == vc[v].pn) || (0xFFFF == vc[v].pn) ) return;
28818 NOTE:
28819 vc.pn shall not be interpreted as pointing to a Program Map Table entry
28820 instead it should be the lookup into the Program Association Table,
28821 which will in turn have the index for the Program Map table entry
28824 if ( (eit_vn[n][v % VCZ] == vn) ) { /* already have this vn? */
28825 pkt.feit = PAY_GOOD; /* green is good */
28826 return; /* nothing done, payload same */
28829 mi = atsc_find_mg_tt( 0x100 + n );
28830 if (-1 < mi) {
28831 mg[ mi ].vn1 = vn;
28832 if (sl > mg[ mi ].nb1)
28833 mg[ mi ].nb1 = sl;
28836 eit_vn[n][v % VCZ] = vn;
28838 pkt.feit = PAY_PARSE; /* set indicator to parse */
28839 pkt.eit++;
28841 s = &ptc[cap_chan].sig;
28842 s->psip &= 0x7f;
28843 s->psip |= 4;
28846 if ( -1 < cap_vc ) {
28847 if ( CAP_TIME == cap_now ) return;
28848 if ( CAP_USER == cap_now ) return;
28851 /* stay on r[5] */
28852 cni = 1 & *r; /* current next indicator */
28854 /* multiple sections are supported by the spec but not by this code */
28855 r++; /* r[6]; */
28856 sn = *r; /* section number */
28857 r++; /* r[7]; */
28858 lsn = *r; /* last section number */
28860 r++; /* r[8] */
28861 pv = *r;
28862 if (pv != 0) return ; /* protocol version 0 only */
28864 r++; /* r[9] */
28865 numevs = *r; /* number of events in section */
28867 r++; /* r[10] */
28869 /* sanity limit with a log warning, always want to know if this happens */
28870 if (numevs > PEZ) {
28871 advrlog( LOG_INFO, "VC %d EIT-%02X has > %d events, %d ignored",
28872 v, n, PEZ-1, numevs-PEZ );
28873 numevs = PEZ-1;
28876 advrlog( LOG_INFO, "VC %d EIT-%02X has %d events", v, n, numevs );
28878 /* keep the one usually bad event so user can tell it is working.
28879 want to see the one doppler program that's 720 minutes long if there.
28880 This: if (numevs > 1) numevs--;
28881 or this: (i=0; i<(numevs-1); i++)
28882 causes breakage now, but keep comment here as reminder
28884 for (i=0; i<numevs; i++)
28887 /* reserved bits. stop the event loop if any not set */
28888 if ( (0xC0 != (0xC0 & r[0]))
28889 || (0xC0 != (0xC0 & r[6]))
28890 ) break;
28892 /* r[0] */
28893 eid = (0x3F & *r) << 8; /* Event ID */
28894 r++; /* r[1] */
28895 eid |= *r;
28897 /* 32 bit A/65b Table 6.14 ETM ID */
28898 etmid = (sr<<16) | (eid<<2) | 2; /* ETMID from SR and EID */
28900 /* 32 bit event start time */
28901 r++; /* r[2] */
28902 est = *r << 24;
28903 r++; /* r[3] */
28904 est |= *r << 16;
28905 r++; /* r[4] */
28906 est |= *r << 8;
28907 r++; /* r[5] */
28908 est |= *r;
28910 r++; /* r[6] */
28911 etmloc = 3 & (*r >> 4); /* ETM location */
28913 /* 20 bit event length in seconds, limit to 1024 * 1024 seconds */
28914 els = (0xF & *r) << 16; /* stay on r[6] */
28915 r++; /* r[7] */
28916 els |= *r << 8;
28917 r++; /* r[8] */
28918 els |= *r;
28920 r++; /* r[9] */
28921 tlen = *r;
28923 r++; /* r[10] */
28925 /* recalibrate for unix time */
28926 est += utc_offset;
28928 n &= 0x7f; /* sanity limit eit-n */
28930 v = find_vc_src( sr ); /* sr already set above */
28932 if (-1 != v) {
28933 pgo = ( v * EIZ * PEZ ) + ( n * PEZ ) + i;
28935 /* KPXB fix for source id above 7 sanity limit */
28936 if (pgo >= PGZ) pgo %= PGZ;
28938 epg[pgo].eitn = n; /* used for guide done check */
28939 epg[pgo].eitvn = vn; /* used for guide done check */
28940 epg[pgo].chan = cap_chan;
28942 /* save event name, start time, length and etmid in epg[] structure
28943 duplicate check so don't have to remove it later
28944 (KRIV bad EIT event order fix)
28947 /* multi VC stations were not updating in order, look OK now.
28948 [?] only newest etmids count (ABC/CBS missing EIT-0 fix)
28950 if (-1 == find_epg_etmid( etmid )) { /* -1 is not found in list */
28951 atsc_parse_mss_eit( r, pgo, etmid, est, els );
28953 r += tlen;
28955 /* events have additional descriptors if these reserved bits set */
28956 if ( 0xF0 == (0xF0 & r[0]) )
28958 unsigned int dlen;
28959 dlen = ((0xF & r[0])<<8) | r[1];
28960 r += 2;
28962 /* V-Chip Rating indices are found in this descriptor */
28963 /* NOTE: Ratings are display-only, but the most obvious
28964 use would be to save captures in specific directories
28965 and only allow the kids access to the Y-rated directory.
28968 atsc_parse_eit_descriptors( r, dlen, pgo, n, i );
28969 r += dlen;
28974 /* FOX needs epg sorted here because FOX is sending broken EITs.
28975 This has been independently verified with user in New York, so if FOX
28976 in the largest market is broken, and mine, they are probably all broken.
28977 ATSC A/65b mentions the EIT events should be chronologically sequential.
28979 atsc_sort_eit_epg( pgo );
28981 /* uses 1/7 of the calls to epg index if done outside of event loop */
28982 build_pg_epg( epg, &pgm, pg, WHO);
28983 pkt.feit = PAY_GOOD;
28985 /* one good eit clears program fail indication */
28986 pgm.fail = 0;
28989 /***************************************************************** ATSC ETM MSS
28990 A/65b Table 6.38 Multiple String Structure
28991 does Huffman decomp
28993 /* static */
28994 void
28995 atsc_parse_channel_etm_mss ( unsigned char *r )
28997 unsigned int nstr, nseg, comp, mode, nchr;
28998 unsigned int i,j;
28999 char lang[4];
29000 char t[512];
29001 char *d;
29003 return; /* no point in wasting cpu cycles on unused data */
29005 d = t;
29007 memset( t, 0, sizeof(t));
29008 nstr = r[0];
29009 for (i = 0; i < nstr; i++) {
29010 memcpy(lang, &r[1], 3);
29011 lang[3] = 0;
29012 r += 4; /* 3? */
29013 nseg = *r++;
29014 for (j = 0; j < nseg; j++) {
29015 comp = *r++;
29016 mode = *r++;
29017 nchr = *r++;
29019 if ( (0 == comp) && (0 == mode) ) {
29020 nchr++;
29021 if (nchr > 256) nchr = 256;
29022 astrncpy( t, (char *)r, nchr);
29025 /* KPXB requires non-strict mode. not sure that still applies */
29026 if ( (0 == arg_strict) || ((0 != arg_strict) && (0xFF == mode)) )
29028 if ((1 == comp) || (2 == comp)) {
29029 if (nchr > 511) nchr = 511;
29030 huffman_decode( d, r, sizeof(t)-1, nchr, comp);
29033 if (comp > 2) snprintf( t, sizeof(t)-1, "[%s] L%02X C%02X M%02X",
29034 lang, nchr, comp, mode);
29036 /* all that work and nothing done with result?!? */
29040 /***************************************************************** ATSC PTC ETT
29041 This is the Extended Text Table for the Physical Transmission Channel.
29042 It is checked for Quality of Service via CRC32 but mss is not parsed.
29043 The information located in the mss has nothing useful to the capture.
29045 /* static */
29046 void
29047 atsc_parse_channel_ett ( unsigned char *p )
29049 unsigned char *r;
29050 unsigned int sl, idx, vn, etmid, pid;
29051 int terr, mi;
29053 ctt.payok = atsc_build_payload( &ctt, p, "PTCETT");
29054 if (0 != ctt.payok) return;
29055 ctt.payoff = 0;
29057 r = ctt.payload;
29059 terr = 0;
29061 pid = 0x1FFF & ( (p[1] << 8) | p[2] );
29063 /* all must be valid or nothing done */
29064 if (
29065 ( 0xF0 != (0xF0 & r[1]) )
29066 || ( 0xC1 != (0xC1 & r[5]) )
29067 || ( 0x00 != r[8] )
29069 terr = ~0;
29071 /* reserved bits wrong */
29072 if (0 != terr) {
29073 atsc_strict_log( r, "PTCETT bad");
29074 return;
29077 sl = ((0xF & r[1]) << 8) | r[2]; /* 12bit section len */
29079 ctt.crc = calc_crc32( r, sl+3 );
29080 if (ctt.crc != 0) {
29081 pkt.crcett++; /* should count this */
29082 advrlog( LOG_INFO, "PTCETT bad CRC");
29083 return; /* crc for QoS only */
29086 /* the rest is for build mgt text */
29087 idx = (r[3] << 8) | r[4]; /* TESTME: what is this index? */
29088 vn = (0x3E & r[5]) >> 1; /* version */
29089 etmid = r[9]<<24 | r[10]<<16 | r[11]<<8 | r[12]; /* ETMID unused */
29091 /* build mgt text */
29092 mi = atsc_find_mg_tt( 4 );
29093 if (-1 < mi) {
29094 mg[ mi ].vn1 = vn;
29095 if (sl > mg[ mi ].nb1)
29096 mg[ mi ].nb1 = sl;
29099 /* rest is unused because some stations put really long useless text in */
29100 r += 13;
29101 atsc_parse_channel_etm_mss( r ); /* nop */
29105 /************************************************************** ATSC VCT SLD
29106 A/65b Table 6.26 Service Location Descriptor for n'th VCT
29108 /* static */
29109 void
29110 atsc_parse_vct_svloc ( unsigned char *r, unsigned int n )
29112 unsigned short pcrpid; /* program clock reference pid */
29113 unsigned short numes; /* number of elementary streams */
29114 unsigned short espid; /* elementary stream pid */
29115 unsigned char est; /* stream type */
29116 char eslang[4]; /* stream language */
29117 unsigned char i, j;
29118 unsigned char *p;
29119 int acn;
29121 acn = 0;
29123 if (0xE0 != (0xE0 & r[0])) return; /* reserved bits */
29124 pcrpid = ((0x1F & r[0]) << 8) | r[1];
29125 numes = r[2];
29127 if (numes > 7 ) return; /* 8 VC limit */
29128 vc[n].vctes = numes;
29129 vc[n].pcrpid = pcrpid;
29131 /* MPEG2 limited to 0-FFF, assume PMT is 0 anchor for PCR PID range */
29132 vc[n].pmtpid = 0x0FF0 & pcrpid;
29134 r += 3;
29135 p = r; /* save for video and audio type loops */
29136 j = 0; /* starting with first ES */
29138 /* do video type loop first to correct any vct / pmt mismatch */
29139 /* KTRK has a bad habit of sending the VCT ES info backwards from PMT ES */
29140 for (i=0; i<numes; i++) {
29142 if (0xE0 == (0xE0 & p[1])) { /* reserved bits */
29143 est = *p++; /* ES type */
29144 espid = ((0x1F & p[0])<<8) | p[1];
29145 p += 2;
29146 memcpy( eslang, p, 3);
29147 eslang[3] = 0;
29148 p += 3;
29150 if (est != 2) continue; /* not video? keep going */
29151 vc[n].estype[j] = est;
29152 vc[n].espid[j] = espid;
29153 memcpy( &vc[n].eslang[j][0], eslang, sizeof(eslang));
29154 j++;
29157 /* should be only one video per vc, so don't need vc[].vcn */
29159 for (i=0; i<numes; i++) {
29161 if (0xE0 == (0xE0 & r[1])) { /* reserved bits */
29162 memset(eslang, 0, sizeof(eslang) );
29163 est = *r++; /* ES type */
29164 espid = ((0x1F & r[0])<<8) | r[1];
29165 r += 2;
29166 memcpy( eslang, r, 3);
29167 eslang[3] = 0;
29168 r += 3;
29169 if (0x81 != est) continue; /* skip video done above */
29170 acn++;
29171 vc[n].estype[j] = est;
29172 vc[n].espid[j] = espid;
29173 memcpy( &vc[n].eslang[j][0], eslang, sizeof(eslang));
29174 j++;
29177 vc[n].acn = acn; /* number of audio channels in this vc */
29179 /* everything not video and not audio gets listed last */
29180 for (i=0; i<numes; i++) {
29182 if (0xE0 == (0xE0 & r[1])) { /* reserved bits */
29183 memset(eslang, 0, sizeof(eslang) );
29184 est = *r++; /* ES type */
29185 espid = ((0x1F & r[0])<<8) | r[1];
29186 r += 2;
29187 memcpy( eslang, r, 3);
29188 eslang[3] = 0;
29189 r += 3;
29190 /* skip video and audio done above */
29191 if ( (0x02 == est) || (0x81 == est)) continue;
29192 vc[n].estype[j] = est;
29193 vc[n].espid[j] = espid;
29194 memcpy( &vc[n].eslang[j][0], eslang, sizeof(eslang));
29195 j++;
29200 #if 0
29201 /****************************************************************** ATSC ECN
29202 A/65b Table 6.38 Multiple String Structure
29203 does Huffman decomp
29204 Does nothing but waste cycles. Extended Channel name is not displayed.
29205 All that work and nothing done with result.
29207 /* static */
29208 void
29209 atsc_parse_mss_vct ( unsigned char *r )
29211 unsigned int nstr, nseg, comp, mode, nchr;
29212 unsigned int i,j;
29213 char lang[4];
29214 char t[512];
29216 /* return; */ /* see build vct text for where this is parsed */
29218 memset( t, 0, sizeof(t) );
29219 nstr = r[0];
29220 for (i = 0; i < nstr; i++) {
29221 memcpy(lang, &r[1], 3);
29222 lang[3] = 0;
29223 r += 4;
29224 nseg = *r++;
29225 for (j = 0; j < nseg; j++ ) {
29226 comp = *r++;
29227 mode = *r++;
29228 nchr = *r++;
29230 t[0] = 0;
29232 if ( (0 == comp) && (0 == mode) && (nchr > 0) ) {
29233 nchr++;
29234 if (nchr > 256) nchr = 256;
29235 astrncpy( t, r, nchr );
29238 /* KPXB requires non-strict mode */
29239 if ( (0 == arg_strict) || ((0 != arg_strict) && (0xFF == mode)) )
29241 if ((comp == 1) || (comp == 2)) {
29242 if (nchr > sizeof(t) ) nchr = sizeof(t);
29243 huffman_decode( t, r, sizeof(t)-1, nchr, comp);
29247 if ( (comp > 2) || ((mode > 0) && (mode < 0xFF)) )
29248 snprintf( t, sizeof(t)-1, "[%s] L%02X C%02X M%02X",
29249 lang, nchr, comp, mode );
29253 #endif
29255 /********************************************************** ATSC VCT DESCRIPTOR
29256 r points to start of descriptors. vct may have: ecn sl tss
29257 do until len exhausted
29258 n is VC[n] for svloc, rest are text dont care about
29260 Time shifted service descriptor is currently unused for broadcast.
29261 Service location descriptor has superset of PAT+PMT info with VC PIDs.
29263 /* static */
29264 void
29265 atsc_parse_vct_descriptor ( unsigned char *r, unsigned int n, unsigned int len )
29267 unsigned char dtag, dlen;
29269 dtag = dlen = 0;
29271 while ( len > 0 ) {
29272 dtag = *r++;
29273 dlen = *r++;
29274 len -= 2;
29275 switch( dtag ) {
29277 /* Extended Channel Name descriptor is multiple string structure */
29278 case 0xA0:
29279 /* atsc_parse_mss_vct( r ); // no useful information, is nop */
29280 break;
29282 /* service location descriptor, equivalent to MPEG PMT with ES PIDs */
29283 case 0xA1:
29284 atsc_parse_vct_svloc( r, n );
29285 break;
29287 /* time shifted service descriptor, log if seen but still ignores it */
29288 case 0xA2:
29289 dvrlog( LOG_INFO, "TSSD ignored");
29290 break;
29292 /* ignore anything else. see table of where descriptors are allowed */
29293 default:
29294 break;
29296 r += dlen;
29297 len -= dlen;
29301 /******************************************************************** ATSC VCT
29302 * Cable and Terrestrial Virtual Channel Tables. tt is table type.
29303 * This table gives information on which PIDs are for which Virtual Channel.
29304 * 400ms is maximum (longest delay allowed) cycle time, according to A/65b.
29305 * Cable has 2 bits following hidden bit in virtual channel loop.
29306 * Path select is for telling it which tuner to use in 2-tuner cable STB.
29307 * Out-of-band may indicate TSID isn't on the PTC where CVCT was found.
29308 * Terrestrial reserves these bits (set 1). Do not use as extra markers?
29310 /* static */
29311 void
29312 atsc_parse_vct ( unsigned char *p, int tn )
29314 struct sig_s *s;
29315 unsigned char *r, *t;
29316 unsigned short sl, tsi0, adl, pid;
29317 signed short sr; /* -1 is NTSC, 0 is HIDDEN */
29318 unsigned char vn, cni, sn, lsn, pv;
29319 int c, i, j, mi;
29320 int terr;
29321 int has_name = 0;
29323 short tsx;
29324 struct tsid_s *tsn;
29326 c = 0;
29328 if (ATSC_CVCT == tn) {
29329 c = 2;
29330 t = "CVCT";
29331 pkt.cvctp++;
29332 } else {
29333 c = 0;
29334 t = "TVCT";
29335 pkt.tvctp++;
29338 i = 0;
29340 #ifdef USE_MPEG_ONLY
29341 #warning using MPEG ONLY --- NO ATSC PSIP
29342 return; /* no ATSC guide with this */
29343 #endif
29345 pid = 0x1FFF & ((p[1] << 8) | p[2]);
29347 pkt.fvct = PAY_READ;
29348 vct.payok = atsc_build_payload( &vct, p, t );
29349 if (0 != vct.payok) return;
29350 vct.payoff = 0;
29352 r = vct.payload;
29354 terr = 0;
29356 if (ATSC_TVCT == tn) {
29357 /* TVCT table ID C8, protocol version 0 */
29358 if ( (ATSC_TVCT != r[0]) || (0x00 != r[8]) ) terr = ~0;
29359 } else {
29360 /* TVCT table ID C8, protocol version 0 */
29361 if ( (ATSC_CVCT != r[0]) || (0x00 != r[8]) ) terr = ~0;
29364 /* cable disables this by setting r[1] to 0xC0 */
29365 if (0xF0 != (0xF0 & r[1])) terr = ~0; /* syntax,private,reserved */
29367 if (0 != arg_strict) { /* strict ATSC check */
29368 if ( ( 0xC1 != (0xC1 & r[5]) ) /* reserved/cni */
29369 || ( 0x00 != r[6] ) /* CSN should be 0 */
29370 || ( 0x00 != r[7] ) /* LSN should be 0 */
29372 terr = ~0;
29375 /* reserved bits wrong */
29376 if (0 != terr) {
29377 advrlog( LOG_INFO, "%s:%d %s reserved bits not set", WHO, __LINE__, t);
29378 atsc_strict_log( r, t );
29379 pkt.crctvct++;
29380 return; /* check reserved bits saves time */
29383 r++; /* r[1] */
29384 sl = (0xF & *r) << 8;
29385 r++; /* r[2] */
29386 sl |= *r; /* section length may not be > 1021 */
29388 if (sl > 1021) return; /* if it is > 1021, ignore packet */
29390 vct_crc = calc_crc32( vct.payload, sl+3 ); /* crc check to qualify */
29391 if (vct_crc != 0) {
29392 pkt.crctvct++;
29393 /* TEI should have caught? */
29394 advrlog( LOG_INFO, "%s bad CRC", t);
29395 pkt.fvct = PAY_DROP;
29396 return; /* bad packet do not process */
29399 r++; /* r[3] */
29400 tsi0 = *r << 8; /* transport stream id */
29401 r++; /* r[4] */
29402 tsi0 |= *r;
29404 r++; /* r[5] */
29405 vn = 0x1F & (*r >> 1); /* version number limits processing */
29407 vct.lv = vct.vn;
29408 if (vn == vct.vn) { /* version hasn't changed */
29409 pkt.fvct = PAY_GOOD;
29411 /* pkt.tvct++; */ /* only count new ones */
29412 return;
29415 /* set a bit to indicate channel has VCT */
29416 s = &ptc[ cap_chan ].sig;
29417 s->psip &= 0x7f;
29418 s->psip |= 2;
29420 mi = atsc_find_mg_tt( c );
29421 if (-1 < mi) {
29422 mg[ mi ].vn1 = vn;
29423 mg[ mi ].nb1 = sl;
29426 advrlog( LOG_INFO, "%s new %s VN %02X", WHO, t, vn);
29428 vct.vn = vn; /* version has changed */
29429 pkt.fvct = PAY_PARSE;
29430 pkt.tvct++; /* indicate parse and bump count */
29432 /* keep er steady r[5] */
29433 cni = 1 & *r & 1; /* current/next indicator */
29434 r++; /* r[6] */
29435 sn = *r; /* section number */
29436 r++; /* r[7] */
29437 lsn = *r; /* last section number */
29438 r++; /* r[8] */
29439 pv = *r; /* protocol version */
29440 if (pv != 0) return; /* only supporting protocol 0 */
29442 r++; /* r[9] */
29443 vct_ncs = *r; /* number of channels in section */
29445 if (vct_ncs > VCZ) {
29446 vct_ncs = VCZ; /* 7 is enough? not for cable or sat */
29447 dvrlog( LOG_INFO, "%s limits %s to %d entries", WHO, t, VCZ);
29450 // vc_ncs = vct_ncs; /* is also max vc[] index */
29452 if (vct_ncs >= pat_ncs) {
29453 vc_ncs = vct_ncs;
29454 } else {
29455 vc_ncs = pat_ncs;
29459 /* NOTE: vc[] build from VCT overrides anything PAT+PMT made before,
29460 and then PAT+PMT has to reference what VCT made, if it can.
29461 If it can't, the extra PMTs are added to vc[] after VCT ones.
29464 r++;
29466 for (j = 0; j < vct_ncs; j++)
29468 char *n;
29469 unsigned char mode;
29470 unsigned int freq;
29471 unsigned short major, minor, pgn, tsi1;
29473 /* all these reserved bits have to be set or nothing done */
29474 if ( (0xF0 != (0xF0 & r[14] )) /* after short name */
29475 // || (0x0D != (0x0D & r[26] )) /* +12 bytes after name */
29476 || (0xC0 != (0xC0 & r[27] )) /* +13 bytes after name */
29478 dvrlog( LOG_INFO, "%s:%d %s reserved bits not set",
29479 WHO, __LINE__, t);
29480 return;
29483 /* pmt may have to check for non-zero instead of here */
29484 n = vc[j].name;
29486 /* 7 shorts of UTF-16 big endian, but only want bottom 8 bits */
29487 *n++ = r[1];
29488 *n++ = r[3];
29489 *n++ = r[5];
29490 *n++ = r[7];
29491 *n++ = r[9];
29492 *n++ = r[11];
29493 *n++ = r[13];
29494 *n = 0;
29495 n = vc[j].name;
29497 has_name = 0;
29498 if (' ' == *n) *n = 0;
29499 if (0 != *n) has_name = 1;
29501 /* global channel name is chopped down copy of first VC name */
29502 /* ignore bud paxons 'i' first channel and look for K or W callsign */
29503 if ( (0 == j)
29504 && (0 != has_name)
29505 && (0 == (0x20 & n[1])) /* 2nd char uppercase? */
29506 && ( ('K' == *n) || ('W' == *n) ) ) {
29509 /* first VC name will be chan name, but may be wrong if 2+ stations on PTC */
29510 memcpy( chan_name, n, 8);
29513 #if USE_CALLSIGN_TRUNC
29514 /* term the station name at first non alphanumeric */
29515 int k;
29516 for (k=0; k< 7; k++) {
29517 if (0 == isalnum(chan_name[k]))
29518 chan_name[k]=0;
29520 #endif
29524 #if USE_AUTO_CALLSIGN
29525 /* might be 3 char call signs, maybe */
29526 if (strlen(chan_name)>2) {
29528 /* handle the station getting a new call sign:
29529 (KHWB to KHCW) or user forgetting to enter it
29531 astrncpy( s->call, chan_name, 8 );
29532 s->call[7] = 0;
29533 has_name = ~0;
29535 #endif
29536 advrlog( LOG_INFO, "VCT vc[%d].name %s", j, vc[j].name);
29538 /* skip past virtual channel name */
29539 r += 14; /* r[0] */
29541 /* HAVE to keep these fields local until program number known */
29543 major = (0xF & *r) << 6;
29544 r++; /* r[1] */
29545 major |= 0x3F & (*r >>2); /* major channel number 10 bits */
29546 s->major = major; /* update station list */
29548 minor = (3 & *r) << 8;
29549 r++; /* r[2] */
29550 minor |= *r; /* minor channel number 10 bits */
29552 r++; /* r[3]; */
29553 mode = *r; /* modulation mode 8 bits */
29555 r++; /* r[4] */
29556 freq = *r << 24;
29557 r++; /* r[5] */
29558 freq |= *r << 16;
29559 r++; /* r[6] */
29560 freq |= *r << 8;
29561 r++; /* r[7] */
29562 freq |= *r; /* frequency 32 bits (obsoleted) */
29564 r++; /* r[8] */
29565 tsi1 = *r << 8;
29566 r++; /* r[9] */
29567 tsi1 |= *r; /* transport stream ID 16 bits */
29568 // if (cap_pn > 0)
29569 cap_tsid = tsi1;
29571 r++; /* r[10] */
29572 pgn = *r << 8;
29573 r++; /* r[11] */
29574 pgn |= *r; /* program number 16 bits*/
29576 #ifdef USE_AUTO_TSID
29577 /* no hidden, NTSC or even TSID numbers */
29578 if ( (0 != pgn) && (0xFFFF != pgn) && (1 == (tsi1 & 1)) ) {
29579 tsx = find_tsidx( tsi1, pgn, WHO );
29580 if ( (tsx < 0) && (0 == arg_scan) ) {
29581 tsn = &tsids[tsidx];
29582 tsn->tsid = tsi1;
29583 tsn->ptc = s->ptc;
29584 tsn->pn = pgn;
29585 if (0 != has_name) {
29586 snprintf( tsn->call, 8, "%s", n);
29587 } else {
29588 snprintf( tsn->call, 8, "TS-%04X", tsi1 );
29590 snprintf( tsn->sid, 8, "%03d", s->ptc);
29591 tsidx++;
29594 #endif
29596 #ifdef USE_VC_PA_MISMATCH
29597 /* if PAT already loaded, have to store in PAT index order */
29598 /* if VCT is first, will sort by pgn at end */
29599 if (0 != pkt.pat) {
29600 i = find_vc_pgm( pgn, WHO);
29601 if (-1 == i) {
29602 dvrlog( LOG_INFO, "VCT/PAT Program mismatch %s", WHO);
29603 return; /* nothing left to do if it's too broken */
29606 #endif
29608 /* force vct to set the order for vc[]. parse pat/pmt will have to adjust */
29609 i = j;
29611 vc[i].major = major;
29612 vc[i].minor = minor;
29613 vc[i].mode = mode;
29614 vc[i].freq = freq; /* obsoleted */
29615 vc[i].tsid = tsi1;
29616 vc[i].pn = pgn;
29618 r++; /* r[12] */
29619 vc[i].etm = 3 & (*r >> 6); /* extended text message location/ 0 */
29621 /* stay on r[12] */
29622 vc[i].actrl = 1 & (*r >> 5); /* access control */
29623 vc[i].hide = 1 & (*r >> 4); /* hide channel */
29625 vc[i].psel = 0;
29626 vc[i].oob = 0;
29628 /* FIXME: build vc text will not show this yet, but may not have CVCT's? */
29629 if (ATSC_CVCT == tn) {
29630 vc[i].psel = 1 & (*r >> 3);
29631 vc[i].oob = 1 & (*r >> 2);
29634 vc[i].hideg = 1 & (*r >> 1); /* hide guide for this channel */
29636 r++; /* r[13] */
29637 vc[i].svct = 0x3F & *r; /* service type */
29639 r++; /* r[14] */
29640 sr = *r << 8; /* KPXB has large killer source id */
29641 r++; /* r[15] */
29642 sr |= *r;
29644 vc[i].src = sr;
29646 r++; /* r[16] */
29647 vc[i].vcdlen = (3 & *r) << 8;
29648 r++; /* r[17] */
29649 vc[i].vcdlen |= *r; /* descriptor length */
29651 r++; /* r[18] */
29653 /* parse descriptor for a/v */
29654 if (0 != vc[ i ].vcdlen) {
29655 /* make copy for display */
29656 memcpy( vc[i].vcdescr, r, vc[i].vcdlen);
29657 atsc_parse_vct_descriptor( r, i, vc[i].vcdlen);
29658 /* skip to next descriptor */
29659 r += vc[i].vcdlen;
29663 /* NOTE: not seeing this in local streams.
29664 Look for additional descriptors, but spec says may be 0.
29666 if (0xFC == (r[0] & 0xFC)) {
29668 adl = ((0x3 & r[0]) << 8) | r[1];
29670 /* never seen in my local streams? */
29671 if (0 != adl) {
29672 #if 0
29673 char t[80];
29674 *t = 0;
29675 /* not doing anything with add'l descriptors, shouldn't bother */
29676 for ( j = 0; j < adl; j++) {
29677 asnprintf( t, 80, " %02X", *r);
29678 r++;
29680 dvrlog( LOG_INFO, "Add'l hex:%s", t);
29681 #endif
29682 dvrlog( LOG_INFO, "TVCT add'l descriptor exists\n");
29686 for (i=0; i < vct_ncs; i++) {
29687 j = find_pa_pgm( vc[i].pn, WHO );
29688 advrlog( LOG_INFO, "vc %2d vct.vn %02X pn %d pa %d",
29689 i, vct.vn, vc[i].pn, j );
29691 vct.lv = vct.vn;
29693 /* NOTE: VC capture now starts at first PAT. may be smoother? */
29694 pkt.fvct = PAY_GOOD;
29697 /***************************************************************** ATSC MGT */
29698 /* Master Guide Table
29699 * This table gives information on which PIDs are for which Table Types,
29700 * as well as the expected Table version and number of bytes in Table.
29701 * Tables can be one of the following: TVCT CVCT EIT ETT RRT PTC_ETT.
29702 * s->mgt_hrs is computed from number of EITs listed * 3 hrs
29704 * NOTE: DCCT and DCCSCT are not currently used. Directed Channel Change
29705 * is a feature that is not being used by any ATSC broadcasters.
29707 * NOTE: The "number of bytes in Table" field is not always being encoded
29708 * correctly by all broadcasters. It is display only and not used for
29709 * any allocation of table space. The incorrect values are displayed
29710 * in red by the show master guide function.
29712 /* static */
29713 void
29714 atsc_parse_mgt ( unsigned char *p )
29716 struct sig_s *s;
29717 unsigned char *r;
29718 unsigned short sl, tidx, td, dl, i, j, pid;
29719 unsigned char vn, cni, sn, lsn, pv;
29720 int eitnum;
29721 int terr;
29722 struct mg_s *m1, *m2;
29724 pkt.mgtp++;
29725 pid = ((0x1F & p[1])<<8) | p[2];
29727 pkt.fmgt = PAY_READ;
29728 mgt.payok = atsc_build_payload( &mgt, p, "MGT" );
29729 if (0 != mgt.payok) return;
29730 mgt.payoff = 0;
29732 r = mgt.payload;
29734 terr = 0;
29736 /* MGT table ID C7, protocol version 0 */
29737 if ( (ATSC_MGT != r[0]) || (0x00 != r[8]) ) terr = ~0;
29739 /* cable disables this by using 0xC0 instead of 0xF0 */
29740 if (0xF0 != (0xF0 & r[1])) terr = ~0; /* syntax,private,reserved */
29742 /* strict ATSC check? */
29743 if (0 != arg_strict) {
29744 if ( ( 0x00 != r[3] ) /* must be 0 */
29745 || ( 0x00 != r[4] ) /* must be 0 */
29746 || ( 0xC1 != (0xC1 & r[5]) ) /* reserved/cni */
29747 || ( 0x00 != r[6] ) /* must be 0 */
29748 || ( 0x00 != r[7] ) /* must be 0 */
29750 terr = ~0;
29753 /* reserved bits wrong */
29754 if (0 != terr) {
29755 atsc_strict_log( r, "MGT bad" );
29756 pkt.crcmgt++;
29757 return; /* check reserved bits saves time */
29760 /* 12 bit section length */
29761 r++; /* r[1] */
29762 sl = (0xF & *r) << 8;
29763 r++; /* r[2] */
29764 sl |= *r; /* bytes after section header */
29766 if (sl > 4093) return; /* payload limit */
29768 mgt.crc = mgt_crc = calc_crc32( mgt.payload, sl+3 );
29770 /* TEI should have caught? */
29771 if (mgt_crc != 0) {
29772 pkt.crcmgt++;
29773 advrlog( LOG_INFO, "MGT bad CRC");
29774 pkt.fmgt = PAY_DROP;
29775 return;
29778 r++; /* r[3] */
29779 tidx = *r << 8;
29780 r++; /* r[4] */
29781 tidx |= *r; /* table id extension / 0 */
29783 r++; /* r[5] */
29784 vn = 0x1F & (*r >> 1); /* 5 bit version number */
29786 /* vn is also in mgt.vn, but that's for payload build. ui wants mgt_vn */
29788 if (mgt_vn == vn) { /* already have this vn? */
29789 pkt.fmgt = PAY_GOOD; /* green is good */
29790 /* pkt.mgt++; *//* count it as good */
29791 return; /* nothing done, payload same */
29793 mgt_vn = vn;
29795 /* refresh the mgt in case user is in mgt display */
29796 refresh.mgt = 1;
29798 s = &ptc[ cap_chan ].sig;
29799 s->psip &= 0x7f;
29800 s->psip |= 1; /* bit 0 is mgt flag */
29802 s->mgt_hrs = 0;
29804 /* build payload is only thing that sets mgt.vn. Use mgt_vn for UI
29805 to isolate the spurious version changes to mgt.vn
29808 #ifdef USE_CRAZY_MGT_HOLD
29809 /* KHCW fix: too many MGT version changes preventing guide from building */
29810 // if (utsmgt > utsnow) return; /* hold down same as guide timeout */
29811 /* What happens if you don't init the mgt here? it is done elsewhere... */
29812 // init_mg();
29813 /* What happens if you don't clear the guide? EIT clear is enough? */
29814 // init_pgm(); /* clear program guide */
29815 /* KHCW Fix: three minute hold down for next MGT until info cap done */
29816 // utsmgt = utsnow + 180;
29817 #endif
29819 /* Show KHCW breakage. User should know that the station is goofy. */
29820 /* init_mg(); */ /* clear master guide structs */
29822 reset_mg();
29823 reset_atsc(); /* init atsc allocs, reset clears */
29824 init_pgm(); /* clear program guide */
29826 pkt.fmgt = PAY_PARSE;
29827 pkt.mgt++;
29828 /* stay on r[5] */
29829 cni = 1 & *r; /* current/next indicator */
29831 r++; /* r[6] */
29832 sn = *r; /* 8 bit section number */
29834 r++; /* r[7] */
29835 lsn = *r; /* 8 bit last section number */
29837 r++; /* r[8] */
29838 pv = *r; /* 8 bit protocol version */
29839 if (pv != 0) return; /* should always be zero */
29841 r++; /* r[9] */
29842 td = *r << 8; /* 16 bit tables defined */
29843 r++; /* r[10] */
29844 td |= *r;
29846 r++;
29848 mg_idx = 0;
29849 mg_eit = 0;
29850 eitnum = 0;
29852 for (i=0; i<td; i++)
29854 unsigned short tt;
29855 unsigned short ttpid;
29856 unsigned char ttvn;
29857 unsigned short ttdl;
29858 unsigned int nb;
29860 if ( ( 0xE0 != (0xE0 & r[2]))
29861 || ( 0xE0 != (0xE0 & r[4]))
29862 || ( 0xF0 != (0xF0 & r[9]))
29863 ) return;
29865 tt = *r << 8;
29866 r++; /* r[1] */
29867 tt |= *r; /* table type */
29869 r++; /* r[2] */
29870 ttpid = (0x1F & *r) << 8;
29871 r++; /* r[3] */
29872 ttpid |= *r; /* table type PID */
29874 r++; /* r[4] */
29875 ttvn = 0x1F & *r; /* table type version number */
29877 r++; /* r[5] */
29878 nb = *r << 24;
29879 r++; /* r[6] */
29880 nb |= *r << 16;
29881 r++; /* r[7] */
29882 nb |= *r << 8;
29883 r++; /* r[8] */
29884 nb |= *r; /* number of bytes in table */
29886 r++; /* r[9] */
29887 ttdl = (0xF & *r) << 8;
29888 r++; /* r[10] */
29889 ttdl |= *r; /* table type descriptor length */
29891 r++; /* r[11] */
29892 r += ttdl; /* FIXME: skip past tt descriptor */
29893 /* ADDME: registration descriptor parse */
29894 /* descriptors are not present in my local MGTs, see A/65b page 27
29895 mg is raw received order
29897 j = mg_idx;
29898 mg_idx++;
29900 m1 = &mg[j];
29901 m2 = &mgp[ttpid];
29903 m1->tt = tt;
29904 m1->pid = ttpid;
29905 m1->dl = ttdl;
29906 m1->vn = ttvn;
29907 m1->nb = nb;
29908 m1->vn1 = 0xFF;
29909 m1->nb1 = 0;
29911 /* mgp is mg cross-reference by PID for parsing and build mgt text,
29912 but is not useful for table types below 6 because all on same PID 1FFB
29914 m2->tt = tt;
29915 m2->pid = ttpid;
29916 #if 0
29917 m2->vn = ttvn;
29918 m2->nb = nb;
29919 m2->vn1 = 0xFF;
29920 m2->nb1 = 0;
29921 #endif
29923 /* mgs is sorted by table type from entry 0-n, done in build mgt text
29924 set if eit found in mgt
29925 will add them as they occur in test mg ttpid packet
29927 /* set a flag to indicate some events may exist. */
29928 if ( (tt >= 0x100) && (tt <= 0x17F) ) {
29929 mg_eit = ~0;
29930 eitnum++;
29932 /* otherwise it triggers an EIT missing error in build mgt text */
29935 s->mgt_hrs = 3 * eitnum;
29937 /* FIXME: missing MGT registration descriptor parse, see A/65b page 27
29938 13818-1 page 59 section 2.6.9 too. have't ever seen it non-zero. */
29939 /* more reserved bit validate */
29941 if (0xF0 == (0xF0 & r[0])) {
29942 /* A65/b says only one additional descriptor here, so no for loop */
29943 dl = ((0xF & r[0])<<8) | r[1];
29944 mgt_dl = dl;
29945 /* one descriptor starts immediately following length */
29946 r += 2;
29948 /* not parsing mgt descriptors */
29949 /* test_mpeg2_registration_descriptor( r, dl ); */
29950 r += dl;
29953 pkt.fmgt = PAY_GOOD;
29956 /* test for MGT table by PIDs to get table type, but not 1FFB ATSC System */
29957 /* static */
29959 atsc_test_mg_ttpid ( unsigned char *p, unsigned int pid )
29961 unsigned char *r;
29962 unsigned int tt, ttidx;
29963 unsigned char tei;
29964 tei = 1 & (p[1]>>7);
29965 tt = 0;
29967 sanity limit pid, if in mg pid copy, set the index same as pid
29968 inlined method of above using lookup table method that is faster
29969 this is to reduce processing on (anticipated) larger program guides
29971 ttidx = -1;
29972 if ( pid == mgp[ 0x1FFF & pid ].pid ) ttidx = pid;
29973 if (ttidx == -1) return ~0;
29974 tt = mgp[ 0x1FFF & pid ].tt; /* get table type */
29976 r = p+5;
29978 #if 0
29979 /* see A/65b Table 6.3 Table Types: all of these are on PID 1FFB */
29981 /* Terrestrial Virtual Channel Table C1 */
29982 if (tt == 0) return 0;
29984 /* Terrestrial Virtual Channel Table C0 */
29985 if (tt == 1) return 0;
29987 /* Cable Virtual Channel Table C1 */
29988 if (tt == 2) return 0;
29990 /* Cable Virtual Channel Table C0 */
29991 if (tt == 3) return 0;
29993 /* Directed Channel Change Selection Code Table */
29994 if (tt == 5) return 0;
29995 #endif
29997 /* Channel Extended Text Table isn't usually on PID 1FFB but it can be */
29998 if (tt == 4) {
29999 atsc_parse_channel_ett( p );
30000 return 0;
30003 /* Event Information Table */
30004 if ( (tt >= 0x100) && (tt <= 0x17F) ) {
30005 atsc_parse_eit( p, 0x7F & tt );
30006 return 0;
30009 /* Extended Text Table */
30010 if ( (tt >= 0x200) && (tt <= 0x27F) ) {
30011 atsc_parse_ett( p, 0x7F & tt );
30012 return 0;
30015 /* Rating Region Table, a.k.a. V-Chips n Gub'ment Cheese */
30016 if ( (tt >= 0x300) && (tt <= 0x3FF) ) {
30017 atsc_test_rrt( p, 0xFF & tt );
30018 return 0;
30021 /* Directed Channel Change Table */
30022 if ( (tt >= 0x1400) && (tt <= 0x14FF) ) {
30023 atsc_test_dcct( p );
30024 return 0;
30027 return ~0;
30029 /***************************************************************** ATSC MGT */
30033 /***************************************************************** ATSC STT
30034 System Time Table Parse
30035 This would obviate need for ntpd, IF your local stations are accurate.
30036 Time is used for display in bottom right. It is not used for anything
30037 else unless config file Z-line specifies channel[:delta] to sync with.
30038 CRC32 is done for QoS. Use config file Z line non zero to try it.
30040 /* static */
30041 void
30042 atsc_parse_stt ( unsigned char *p )
30044 unsigned char *r;
30045 unsigned short sl = 0;
30046 unsigned char guo = 0;
30047 unsigned short ds = 0;
30048 struct timeval stt_sys;
30050 int diff = 0; /* should be time_t */
30051 int avgdiff; /* should be time_t, will break in 2008 maybe */
30052 int terr;
30054 avgdiff = terr = 0;
30056 pkt.fstt = PAY_READ;
30057 stt.payok = atsc_build_payload( &stt, p, "STT");
30058 if (0 != stt.payok) return;
30059 stt.payoff = 0;
30061 r = stt.payload;
30063 /* STT table ID CD and protocol version 0 */
30064 if ( (ATSC_STT != r[0]) || (0x00 != r[8]) ) terr = ~0;
30066 if (0 != arg_strict) {
30068 if ( (0xF0 != (r[1] & 0xF0)) /* syntax,private,reserved = 1 */
30069 || (0x00 != r[3]) /* table_id_extension msB = 0 */
30070 || (0x00 != r[4]) /* table_id_extension lsB = 0 */
30071 || (0xC1 != (r[5] & 0xC1)) /* reserved,current_next=1 = 0 */
30072 || (0x00 != r[6]) /* section number = 0 */
30073 || (0x00 != r[7]) /* last section number = 0 */
30075 terr = ~0;
30078 /* reserved bits wrong */
30079 if (0 != terr) {
30080 atsc_strict_log( r, "STT bad");
30081 return; /* reserved bits check saves time */
30084 sl = ((0xF & r[1]) <<8) | r[2]; /* 12 bit section length */
30085 stt.crc = calc_crc32( r, sl + 3 );
30086 if (stt.crc != 0) {
30087 pkt.crcstt++;
30088 advrlog( LOG_INFO, "STT bad CRC");
30089 return;
30092 /* version always 0 */
30093 pkt.fstt = PAY_GOOD; /* status indicator */
30094 pkt.stt++;
30096 /* atsc system time value, not unix time */
30097 atsc_stt = (r[9]<<24) | (r[10]<<16) | (r[11]<<8) | r[12];
30099 astt.st = atsc_stt;
30100 astt.guo = r[13]; /* GPS UTC offset */
30101 ds = (r[14]<<8) | r[15]; /* daylight_savings() */
30102 astt.ds = ds; /* A/65b Annex A */
30104 /* NOTE: Don't depend on DST bits. Not all stations set the bits correctly. */
30105 if (0x60 == (0x60 & r[14])) {
30106 astt.dss = 1 & (ds>>15); /* 1 is daylight savings on */
30107 astt.dsd = 0x1F & (ds>>8); /* nz day of month DST flips */
30108 astt.dsh = 0x1F & ds; /* nz hour of day DST flips */
30109 /* NOTE: dsd and dsh only use 5 bits each */
30112 /* GPS UTC offset correction, +- any time Bureau of Standards says so */
30113 atsc_stt -= guo;
30114 /* adjust for difference between ATSC and unix epoch */
30115 atsc_stt += utc_offset;
30117 /* time delta for station delay */
30118 if (cap_zc == ptc[cap_chan].sig.ptc) atsc_stt -= cap_zd;
30119 localtime_r( &atsc_stt, &atsc_stt_tm );
30121 /* this is where the config file Z line is used */
30122 /* adjust for known clock drift from this station */
30123 /* fill stt_tm structure with corrected time */
30124 /* CAPTUREME: DST changeover in realtime, 2 chances a year */
30126 /* various hold downs for config Z-line */
30127 if (0 == cap_zc) return; /* config is Z0 ? */
30128 if (cap_zto > utsnow) return; /* timeout not reached? */
30129 if (cap_zc != ptc[cap_chan].sig.ptc) return; /* Zchan not PTC? */
30130 if (atsc_stt < utc_check) return; /* time is invalid? */
30131 if (CAP_INFO != cap_now) return; /* not info cap? */
30132 if (0 != test_mode) return; /* test mode? */
30134 /* 30 min max update rate, use reload config [_] key to reset it NOW,
30135 If you want to know what the drift is, within the 3hr meridian hit [_].
30137 NTP would be a better solution to be used on volume wakeup code.
30138 If the ATSC times were actually good enough they could be used for
30139 samples to NTP external clock souce plugin, but not good enough yet.
30141 cap_zto = utsnow + 1800;
30143 advrlog( LOG_INFO, "chan %d zc %d stt %d check %d zto %d",
30144 cap_chan, cap_zc, (int)atsc_stt, utc_check, cap_zto);
30146 /* NOTE: this only works if you have this C ptc line GTO value set with
30147 same ptc on Z ptc line. or else it won't look at the stream for STT.
30150 /* cause of bumpiness if used? will be changed real soon anyway */
30151 /* get_time(); */
30153 /* Z config line correction offset. Use - so user can match polarity. */
30154 diff = utsnow - atsc_stt;
30155 /* config is opposite polarity of what observed drift is */
30157 /* Ahead or behind by more than 1s triggers system clock set.
30158 NOTE: KHCW jumps ahead 6 hours every so often. Ignore changes over 2hrs.
30159 This means it won't set the clock if you're too far out.
30161 if (diff < 1) return;
30162 if (diff > SEC3HR) return;
30164 stt_sys.tv_sec = atsc_stt;
30165 stt_sys.tv_usec = 0;
30167 /* NOTE: settimeofday requires root, just like SCHED_FIFO */
30169 /* only root user guide capture is allowed to set the time */
30170 if (0 == euid) {
30171 settimeofday( &stt_sys, NULL );
30173 /* update utsnow and tloc */
30174 get_time();
30176 /* log this so we can see that it's working */
30177 dvrlog( LOG_INFO, "STT%02d %d utsnow %d diff %d",
30178 cap_chan, (int)atsc_stt, utsnow, (int)diff);
30181 /****************************************************************** STT */
30184 /****************************************************************** ATSC */
30185 /* static */
30186 void
30187 atsc_test_table ( unsigned char *p )
30189 unsigned int psi;
30191 pkt.atsc++;
30193 psi = 1 & (p[1]>>6);
30195 /* PSI 1 means it's the start of the payload */
30196 if (psi != 0)
30198 pkt.atsctid = p[5]; /* save table id of payload start */
30199 switch( pkt.atsctid ) {
30201 /* System Time Table */
30202 case ATSC_STT:
30203 atsc_parse_stt( p );
30204 break;
30206 /* Master Guide Table */
30207 case ATSC_MGT:
30208 atsc_parse_mgt( p );
30209 break;
30211 /* Terrestrial Virtual Channel Table */
30212 case ATSC_TVCT:
30213 atsc_parse_vct( p, ATSC_TVCT );
30214 break;
30216 /* Cable Virtual Channel Table */
30217 /* only difference from TVCT is path_select and out-of-band bits for CVCT */
30218 case ATSC_CVCT:
30219 atsc_parse_vct( p, ATSC_CVCT );
30220 break;
30222 /* Rating Region Table: count these and use for QoS */
30223 case ATSC_RRT:
30224 atsc_test_rrt( p, 1 );
30225 break;
30227 /* Directed Channel Change Table: unused and uncounted */
30228 case 0xD3:
30229 atsc_test_dcct( p );
30230 break;
30232 /* Directed Channel Change Selection Code Table: unused and uncounted */
30233 case 0xD4:
30234 atsc_test_dccsct( p );
30235 break;
30237 default:
30238 break;
30240 return;
30243 /* PSI 0 indicates more payload, see what last pkt.tid needed payload.
30244 This works because payloads are sent from start to finish before the
30245 PID changes. Drop out causes CRC32 errors that are caught by parser.
30247 if (psi == 0)
30249 switch( pkt.atsctid ) {
30251 mgt is likely overflow suspect now, as stt/vct handled as single section.
30252 rrt might be further candidate, but rating tricks are for kids. blah.
30253 NOTE: A/65b mentions it can support multiple tables in one payload
30254 but so far not seeing that. maybe need to look closer?
30256 /* should be one packet STT only but just in case */
30257 case ATSC_STT:
30258 atsc_parse_stt( p );
30259 break;
30261 case ATSC_MGT:
30262 atsc_parse_mgt( p );
30263 break;
30265 /* KPXB has multi-packet VCT */
30266 case ATSC_TVCT:
30267 atsc_parse_vct( p, ATSC_TVCT );
30268 break;
30270 /* cable virtual channel table */
30271 case ATSC_CVCT:
30272 atsc_parse_vct( p, ATSC_CVCT );
30273 break;
30275 /* counted for QoS, using built-in RRT */
30276 case ATSC_RRT:
30277 atsc_test_rrt( p, 1 );
30278 break;
30280 /* directed channel change table */
30281 case 0xD3:
30282 atsc_test_dcct( p );
30283 break;
30285 /* directed channel change selection code table */
30286 case 0xD4:
30287 atsc_test_dccsct( p );
30288 break;
30290 default:
30291 break;
30294 return;
30296 /****************************************************************** ATSC */
30300 /****************************************************************** MPEG2
30301 MPEG2 payload related, usefulness yet to be determined for capture.
30302 AFC 2 is afc only, no payload data following
30303 AFC 3 is afc, then payload afterwards
30304 These are interleaved throughout the video and audio stream for timing.
30306 NOTE: ATSC packets should not see these, as there is no need for timing.
30307 All of this is Too Much Information For This Application
30309 /* static */
30310 void
30311 atsc_parse_adaptation_field ( unsigned char *p )
30313 /* NOTE: These flags not used here. They are skipped during capture parse. */
30314 #if 0
30316 unsigned char *r;
30317 unsigned char afl, di, rai, espi, pcr, opcr, sp, tpd;
30318 unsigned char afx = 0;
30319 unsigned long long pcrb, opcrb, pwr, dtsnau;
30320 unsigned short pcrx, opcrx;
30321 char sc; /* splice countdown is signed */
30322 unsigned char tpdl;
30323 unsigned char afxl, ltw, pwrf, ssf, ltwv, ltwo, st;
30325 unsigned char ph, pm, ps; /* pcr h m s */
30326 unsigned int pr, pw; /* pcr remainder, pcr wall time */
30328 pcrb = opcrb = pwr = dtsnau = pcrx = opcrx = 0;
30330 ph = pm = ps = pr = pw = 0;
30332 /* adaptation field starts immediately after continuity counter */
30333 r = p + 4;
30335 afl = *r++;
30337 if (afl == 0) return; /* nothing to do */
30339 di = 1 & (*r>>7); /* discontinuity if set */
30340 rai = 1 & (*r>>6); /* random access if set */
30341 espi = 1 & (*r>>5); /* elementary stream priority if set */
30343 pcr = 1 & (*r>>4); /* has program clock reference */
30344 opcr = 1 & (*r>>3); /* has old program clock reference */
30345 sp = 1 & (*r>>2); /* has splicing point */
30346 tpd = 1 & (*r>>1); /* has transport private data */
30347 afx = 1 & (*r); /* has adaptation field extension (decoder time stamps) */
30349 fprintf( stdout, " FLAGS %02X", *r);
30350 if (0 != di) fprintf( stdout, " DI");
30352 /* PCR is the only flag seen in ATSC. Decoders use it, capture does not. */
30353 if (0 != rai) fprintf( stdout, " RAI");
30354 if (0 != espi) fprintf( stdout, " ESPI");
30355 if (0 != pcr) fprintf( stdout, " PCR");
30356 if (0 != opcr) fprintf( stdout, " OPCR");
30357 if (0 != sp) fprintf( stdout, " SP");
30358 if (0 != tpd) fprintf( stdout, " TPD");
30359 if (0 != afx) fprintf( stdout, " AFX");
30361 /* parsing some but not keeping anything but pcr and opcr
30362 r steps forward with each section done, in spec order
30364 r++;
30366 /* program clock reference, main mpeg system clock */
30367 if (0 != pcr) {
30368 if (0x7E == (0x7E & r[4])) { /* bad reserved should abort? */
30369 /* 33 bit PCR */
30370 pcrb = (*r++)<<25; /* bits 32 . 31 30 29 28 . 27 26 25 */
30371 pcrb |= (*r++)<<17; /* bits 24 . 23 22 21 20 . 19 18 17 */
30372 pcrb |= (*r++)<<9; /* bits 16 . 15 14 13 12 . 11 10 9 */
30373 pcrb |= (*r++)<<1; /* bits 8 . 7 6 5 4 3 2 1 */
30374 pcrb |= 1 & (*r>>7); /* bit 0 */
30376 /* 9 bit PCR extension */
30377 pcrx = (1 & *r++) << 8; /* bit 9 */
30378 pcrx |= 0xFF & *r++;
30382 /* original program clock reference, from previous multiplex or source? */
30383 if (0 != opcr) {
30384 if (0x7E == (0x7E & r[4])) { /* bad reserved should abort? */
30385 /* 33 bit Original PCR */
30386 opcrb = (*r++)<<25; /* bits 32 . 31 30 29 28 . 27 26 25 */
30387 opcrb |= (*r++)<<17; /* bits 24 . 23 22 21 20 . 19 18 17 */
30388 opcrb |= (*r++)<<9; /* bits 16 . 15 14 13 12 . 11 10 9 */
30389 opcrb |= (*r++)<<1; /* bits 8 . 7 6 5 4 3 2 1 */
30390 opcrb |= 1 & (*r>>7); /* bit 0 */
30392 /* 9 bit Original PCR extension */
30393 opcrx = (1 & *r++) << 8; /* bit 9 */
30394 opcrx |= 0xFF & *r++;
30398 /* splicing point */
30399 if (0 != sp) {
30400 sc = *r++; /* 8 bit signed */
30402 /* transport private data, skips and does not keep private data bytes */
30403 if (0 != tpd) {
30404 int i, pdb;
30405 tpdl = *r++;
30406 for (i = 0; i < tpdl; i++) pdb = *r++;
30408 /* adaptation field extension */
30409 if (0 != afx) {
30410 afxl = *r++;
30411 if ( 0x1F == (0x1F & *r) ) { /* reserved */
30412 ltw = 1 & (*r >> 7); /* ltw flag */
30413 pwrf = 1 & (*r >> 6); /* piecewise rate flag */
30414 ssf = 1 & (*r >> 5); /* seamless splice flag */
30415 r++;
30417 if (0 != ltw) {
30418 ltwv = 1 & (*r >> 7); /* 1 bit ltw valid */
30419 ltwo = (0x7F & *r++) << 8; /* 15 bit ltw offset */
30420 ltwo |= 0xFF & *r++;
30422 if (0 != pwrf) {
30423 if (0xC0 == (0xC0 & *r)) { /* reserved */
30424 pwr = 0x3F & *r++; /* 22 bit piecewise rate */
30425 pwr |= (*r++) << 16;
30426 pwr |= (*r++) << 8;
30427 pwr |= *r++;
30432 /* seamless splice, DTS next access unit */
30433 if (0 != ssf) {
30434 if ( (1 == (1 & r[0])) /* marker bits */
30435 || (1 == (1 & r[2]))
30436 || (1 == (1 & r[4]))
30439 /* splice type */
30440 st = 0xF & (*r>>4); /* blech spec mess */
30442 /* dts next access unit */
30443 /* 32 31 30 */
30444 dtsnau = (7 & (*r++ >> 1 )) << 30;
30446 /* 29 28 27 26 . 25 24 23 22 */
30447 dtsnau |= *r++ << 22;
30449 /* 21 20 19 18 . 17 16 15 14 */
30450 dtsnau |= (0x7F & (*r++ >> 1)) << 14;
30452 /* 13 12 11 10 . 9 8 7 6 */
30453 dtsnau |= *r++ << 6;
30455 /* 5 4 3 2 . 1 0 */
30456 dtsnau |= 0x3F & (*r++ >> 1);
30462 if (pcrb != 0) {
30463 pr = pcrb % 90000;
30464 pw = pcrb / 90000;
30466 ps = pw % 60;
30467 pm = (pw / 60 % 60);
30468 ph = (pw / 3600 % 24);
30470 fprintf( stdout, "\n PCR %02d:%02d:%02d.%05d ext %3d",
30471 ph, pm, ps, pr, pcrx );
30474 if (opcrb != 0) {
30475 pr = opcrb % 90000;
30476 pw = opcrb / 90000;
30478 ps = pw % 60;
30479 pm = (pw / 60 % 60);
30480 ph = (pw / 3600 % 24);
30482 fprintf( stdout, "\n OPCR %02d:%02d:%02d.%05d ext %3d",
30483 ph, pm, ps, pr, opcrx);
30486 if (dtsnau != 0) {
30487 pr = dtsnau % 90000;
30488 pw = dtsnau / 90000;
30490 ps = pw % 60;
30491 pm = (pw / 60 % 60);
30492 ph = (pw / 3600 % 24);
30494 fprintf( stdout, "\n DTS NAU %02d:%02d:%02d.%05d",
30495 ph, pm, ps, pr );
30498 /* rest are supposed to be reserved
30499 must be rest of payload if afc=3
30500 or nothing if afc=2
30501 getting kind of odd looking numbers to be length field at p[4+afl]?
30504 #endif
30507 /***************************************************************** MPEG2 */
30511 /****************************************************** TOP LEVEL TRANSPORT
30512 check transport packet for errors and route packet to function
30514 /* static */
30515 void
30516 atsc_test_packet ( unsigned char *p )
30518 unsigned int tei, psi, pri, cc, tsc, afc, vn;
30519 unsigned short pid;
30520 /* minimal transport header bits */
30522 pkt.keep = 0; /* default is packet not validated */
30524 /* each packet validated by each section which sets ~0 if applicable,
30525 unless cap pgm -1 is selected in which case you get everything
30527 // if (-1 == cap_vc) pkt.keep = ~0;
30528 if (-1 == cap_pn) pkt.keep = ~0;
30530 pkt.count++;
30532 tei = 1 & (p[1] >> 7); /* transport error indicator */
30533 pkt.psi = psi = 1 & (p[1] >> 6); /* payload start indicator */
30534 pri = 1 & (p[1] >> 5); /* priority (unused?) */
30535 pid = 0x1FFF & ( (p[1] << 8) | p[2] ); /* part of p1 and p2 are PID */
30536 tsc = 3 & (p[3]>>6); /* MPEG2: p3 transport scramble control */
30537 pkt.afc = afc = 3 & (p[3]>>4); /* MPEG2: p3 adaptation field control */
30538 cc = 15 & p[3]; /* ANY: p3 continuity counter */
30539 vn = 0x1F & (p[10]>>1); /* version p10 for help with cc */
30541 psi_pids[ pid ] += psi;
30543 if (pri != 0) pkt.tsprit++;
30545 /* transport error indicator is counted as error */
30546 if (tei != 0) {
30547 pkt.teiert++;
30548 return;
30551 /* transport scramble control non zero is counted as error */
30552 if (tsc != 0) {
30553 /* single vc cap only counts scramble if it matches Vid or Aud PID */
30554 if (cap_pn > 0) {
30555 if ((pid == cap_pids[2]) || (pid == cap_pids[3]))
30556 pkt.tscert++;
30557 } else {
30558 /* full cap counts all the scrambled packets */
30559 pkt.tscert++;
30561 /* don't try to parse this packet */
30562 return;
30566 /* no reserved adaptation field control */
30567 if (afc == 0) return;
30569 /* if it's NULL packet, nothing more to do */
30570 if (pid == 0x1FFF) {
30571 pkt.null++;
30573 /* -n option: hardware players need nulls */
30574 if (0 != arg_nulls) pkt.keep = ~0;
30576 /* VC cap PID filter converts non matches to nulls, too */
30577 return;
30580 // if (0 != psi) advrlog( LOG_INFO, "%s PS1 PID %04X", WHO, pid);
30582 /* clear adaptation field length */
30583 pkt.afl = 0;
30585 /* adaptation field only, no payload */
30586 if (afc == 2) {
30587 pkt.afl = p[4];
30588 if (0 != pkt.afl) {
30589 atsc_parse_adaptation_field( p );
30593 /* MPEG2 uses but only in PES packets, not for PAT CAT or PMT
30594 ATSC PSIP does not use afc=3 nor afc=2
30596 if (afc == 3) {
30597 pkt.afl = p[4];
30598 if (0 != pkt.afl) {
30599 int paylen = 0;
30600 paylen = p[4+pkt.afl];
30602 fprintf( stdout, "\n PID %04X PSI %d AFC 3 AFL %02X PL %02X",
30603 pid, psi, pkt.afl, paylen );
30605 atsc_parse_adaptation_field( p );
30609 /* NOTE: No duplicate packets seen in ATSC that match 13818-1 criteria..
30610 MPEG2 spec said this about continuity counters:
30611 Continuity counter does not increment on duplicate MPEG2 packet
30612 but PCR and ext are expected to be correct for the new packet.
30614 TODO[?]: Continuity counter allowed to be discontinuous when
30615 adaptation field control discontinuity indicator is set.
30616 This condition has not yet been observed in ATSC.
30618 AFC Continuity Counter
30620 0 reserved does not increment
30621 1 payload only increments
30622 2 afc only does not increment
30623 3 afc & payload increments
30625 See ISO/IEC 13818-1: Tables 2-3 and 2-6.
30629 /* first time around is always wrong, so don't count it */
30630 if (0xFF != last_cc[pid]) {
30632 /* afc only: cc should be same as last cc for this pid */
30633 if (afc == 2) {
30634 if (cc != last_cc[pid]) {
30636 /* cc error total: full cap counts all cc errors */
30637 if (cap_pn < 1) {
30638 pkt.tsccet++;
30639 } else {
30641 /* single vc cap increments cc errtot if pid is member of cap pids */
30642 if (0 == find_cap_pid(pid))
30643 pkt.tsccet++;
30645 cce_pids[ pid ]++;
30647 } else {
30649 /* afc+payload or payload only: cc should be 1 + last (modulo 16) */
30650 if ( cc != ( 0xF & (1 + last_cc[ pid ]) ) ) {
30652 /* cc error total: full cap counts all cc errors */
30653 if (cap_pn < 1) {
30654 pkt.tsccet++;
30655 } else {
30657 /* single vc cap increments cc err tot if pid is member of cap pids */
30658 if (0 == find_cap_pid(pid))
30659 pkt.tsccet++;
30661 cce_pids[ pid ]++;
30666 /* for next time around */
30667 last_cc[pid] = cc;
30669 pids[pid]++; /* tracking pids for dump cap stats */
30672 cable uses PIDs like 0x0880 for MPEG2, so make MPEG2 range end at 0x1000
30673 finally found limit in mpeg spec: 12 [13?] bits, so 0xFFF is last mpeg2 PID
30674 some cable streams seem to have mpeg2 above 0x1000, hmm
30676 //#define MPEG2_TS_PID_LIMIT 0x1000
30678 /* is MGX? ATSC System transport, always PID 1FFB and AFC = 01b & SCR = 00b */
30679 if ( (pid == 0x1FFB) && (afc == 1) ) {
30680 atsc_test_table( p );
30681 return;
30684 /* is MGX? see if PID is on MGT list and handle it if match found */
30685 if (0 == atsc_test_mg_ttpid( p, pid ))
30686 return;
30688 /* FIXME: range too narrow for cable to work properly
30689 MPEG2 transport, PAT CAT PMT and PES only
30693 /* if it's full cap, test it always, otherwise program cap
30694 will test for matching pids in cap_pids, which will
30695 have been set by eventual PAT + PMT or VCT load.
30697 can't use cap_now because it will trigger while fifo has data
30699 #if REMOVEME_TEST_MPEG2
30700 if (cap_pn < 1) {
30701 atsc_test_mpeg2( p, pid );
30702 } else {
30703 if (0 == find_cap_pid( pid ))
30704 atsc_test_mpeg2( p, pid );
30706 #endif
30708 atsc_test_mpeg2( p, pid );
30710 return;
30713 /* called in place of test packet to total stats immediately following */
30714 /* static */
30715 void
30716 atsc_test_packet_stats ( unsigned char *p )
30718 atsc_test_packet( p ); /* set the flags, then total them */
30719 /* transport errors */
30720 pkt.tserrt = pkt.synert + pkt.teiert + pkt.tscert + pkt.tsccet;
30721 /* atsc errors */
30722 pkt.crcats = pkt.crcett + pkt.crceit + pkt.crcmgt
30723 + pkt.crctvct + pkt.crccvct + pkt.crcstt + pkt.crcrrt;
30724 /* mpeg2 errors */
30725 pkt.crcmp2 = pkt.crcpat + pkt.crccat + pkt.crcpmt;
30726 /* sum of all errors */
30727 pkt.errors = pkt.tserrt + pkt.crcats + pkt.crcmp2;
30730 have to call it to update log during user input,
30731 but don't call get time excessively (may be too high for SD VC)
30733 if ( 0 == (pkt.count % 1120)) get_time(); /* cap log needs */
30734 /* utsel defaults to utsets, utsets always seconds since cap start */
30735 utsets = utsnow - utscap;
30737 /* only done if clock changes */
30738 if (utspte != utsets) {
30739 pkt.esec = pkt.errors - pkt.perr; /* errors in past second */
30740 pkt.perr = pkt.errors; /* save current error count for next time */
30741 utspte = utsets;
30743 if (pkt.esec < 30) {
30744 sec_good++; /* error count less than frame rate may be watchable */
30745 } else {
30746 sec_bad++; /* 30 or more errors per second is unwatchable */
30748 /* update cap stat array with per second error count */
30749 /* cable should be around 26000 packets per second */
30750 if (pkt.esec > 65535) pkt.esec = 65535;
30752 if (utsets < CAP_STZ) cap_stats[ utsets ] = 0xFFFF & pkt.esec;
30754 /* moved to fifo input loop? check and DELETEME
30755 * if fifo was more full than last max, keep new max full
30756 if (fill_max2 < fill_max) fill_max2 = fill_max;
30760 /* done elsewhere? check and DELETEME
30761 * if not raw cap, hold down until both PAT and PMT arrive
30762 if ( (-1 < cap_vc) && (0 == pkt.pat) && (0 == pkt.pmt) ) pkt.keep = 0;
30767 /* points to buf of TSBUFZ bytes in length
30768 test sync block might be more descriptive
30770 /* static */
30771 void
30772 atsc_test_sync_block ( unsigned char *p )
30774 int i, j;
30776 pkt.syncb = pkt.align = pkt.skips = pkt.synerr = 0;
30778 /* step thru to find SYNC bytes, inner loop checks last packet */
30779 for ( i = 0; i < (TSBUFZ - TSPZ); )
30781 /* sync here and sync ahead? */
30782 if ( (TSYNC == p[i]) && (TSYNC == p[i+TSPZ]) )
30784 /* two syncs were found, count them */
30785 pkt.syncb++;
30786 pkt.syncb++;
30789 no test here, goal is find alignment of last packet in block
30790 test_packet( &p[i] );
30791 already know p[i] and p[i+188] are sync bytes
30793 j = i+TSPZ;
30795 /* inner loop checks from outer loop offset to last packet */
30796 for ( ; j <= (TSBUFZ-TSPZ); )
30798 /* made it to last aligned packet? WOW. */
30799 if ( j == (TSBUFZ-TSPZ) ) {
30800 /* sync_count++; */
30801 /* no. counted on next block */
30802 break; /* get out so you dont exceed boundary */
30805 /* sync ahead? should be somewhere if align not 0 */
30806 if ( (TSYNC == p[j+TSPZ]) )
30808 /* test_packet( &p[j] ); */
30809 pkt.syncb++; /* sync was found, count it */
30810 j += TSPZ; /* try for next packet */
30811 continue;
30812 } else {
30813 pkt.synerr++;
30814 /* back to outer loop to look for sync bytes */
30815 break;
30819 /* stop outer loop if inner loop finished block */
30820 if (j >= (TSBUFZ-TSPZ) ) break;
30822 /* outer loop continues where sync was expected */
30823 i = j+TSPZ;
30824 /* falls through to continue with outer loop */
30826 } else {
30827 /* no sync here or no sync ahead, retrain bytewise */
30828 pkt.skips++; /* count the bytes skipped */
30829 i++; /* try next byte */
30833 /* align is how many bytes were skipped before sync found */
30834 pkt.align = i;
30835 pkt.align %= 188; /* only need 0-187 */
30838 /* it's possible to get here w/o error or sync, error is 21 if so */
30839 if (pkt.syncb == 0) pkt.synerr = TSPPB;
30841 /* only do sync totals during cap, not at end of cap during flush */
30842 if (cap_now > 0) {
30843 pkt.synert += pkt.synerr; /* total of sync errors found */
30844 pkt.synct += pkt.syncb; /* total of sync bytes found in block */
30845 pkt.alignt += (pkt.align!=0)?1:0; /* bump align total if out */
30848 /************************************************************************ TS */
30852 /* try to establish transport stream packet sync and stream alignment */
30853 /* rewritten for 1.0-rc4. looks better. seems more robust, too.
30854 flow:
30855 read 21 packets
30856 find first sync byte and keep track of offset for alignment
30857 find next sync bytes til end of 21 packets
30858 if first sync byte was aligned to offset 0
30859 and found sync in right place on 21 packets
30860 consider sync good and stream aligned, so get out
30861 otherwise
30862 read non-zero offset number of bytes in to align stream
30863 loop back to read 21 packets until tries exceeds count
30865 /* static */
30866 void
30867 atsc_get_sync ( int count )
30869 int len, tries, errs, aligns, skips, syncs;
30871 /* clear locals */
30872 len = tries = errs = aligns = skips = syncs = 0;
30874 #if 0
30875 /* simulate tupari reported bug. turns out it needs time to fill the fifo */
30876 while(1) nanosleep( &console_read_sleep, NULL );
30877 #endif
30879 /* dummy mode doesn't need sync check */
30880 if (test_mode != 0) {
30881 pkt.syncb = 21;
30882 return;
30885 while ( tries < count )
30889 len = read( in_file, ts_buf, TSBUFZ );
30891 /* NOTE: this should not show up unless read did not block properly */
30892 if (len != TSBUFZ) {
30893 dvrlog( LOG_INFO, "get sync: read %d, not %d",
30894 len, TSBUFZ);
30895 return;
30898 pkt.count = 0; /* use this for error checking below */
30899 atsc_test_sync_block( ts_buf ); /* 21 packet sync check */
30901 /* demoth
30902 advrlog( LOG_INFO, "test sync: sc %d sa %d se %d sk %d",
30903 sync_count, sync_align, sync_error, sync_skips);
30905 if (pkt.align == 0) aligns++; /* count 0 alignments */
30906 tries++;
30907 errs += pkt.synerr;
30908 skips += pkt.skips;
30909 syncs += pkt.syncb;
30911 /* consider sync good at first 0 alignment that has 0 errors
30912 if ( (sync_align == 0) && (sync_error == 0) ) break;
30913 no, try for all 21 syncs
30915 now here's where it gets fun: try to fix the alignment.
30916 sync align is how much it was off last test sync
30917 align only if 21 syncs and no errors and 0 > align < 188
30919 if 21 sync bytes found in block, no errors and needs alignment
30921 if ( (pkt.syncb == 21 ) && (pkt.synerr == 0)
30922 && (pkt.align > 0) && (pkt.align < TSPZ) )
30924 advrlog( LOG_INFO, "SYN%2d: block %d: align %d",
30925 cap_chan, tries, pkt.align);
30926 len = read( in_file, ts_buf, pkt.align );
30927 if ( len != pkt.align ) {
30928 dvrlog( LOG_INFO,
30929 "SYN%2d: block %d: align read %d, not %d",
30930 cap_chan, tries, len, pkt.align);
30931 break;
30936 /* logging eats time, especially on bogged cpu, -l is option */
30938 advrlog( LOG_INFO, "SYN%2d: align %s: %d/%d sc %d se %d sk %d", cap_chan,
30939 (pkt.align == 0)?"OK":"FAIL", aligns, tries,
30940 syncs, errs, skips);
30942 /* don't let get sync screw up total sync err count for rest of cap */
30943 pkt.synert = 0;
30945 /* demoth
30946 advrlog( LOG_INFO, "SYN%2d: sc %d sa %d se %d sk %d",
30947 cap_chan, sync_count, sync_align, sync_error, sync_skips);
30948 console_exit(0);
30952 #if 1
30953 /* FIXME: doesn't flush last block, but bogs less than 188 byte writes */
30954 /* It may be OK by now, but need to test to be sure.
30955 write a packet to out_buf until 21 packets, then write cut_buf
30956 NOTE: CUT_buf not OUT_buf. Bad choice of name and hard to read difference.
30958 /* static */
30959 void
30960 write_pkt_block ( unsigned char *p )
30962 int ok;
30963 char e[256];
30964 /* buffer is either empty from above flush, or not full yet */
30965 pkt.kept++; /* and count the packet */
30966 memcpy( &cut_buf[block_widx], p, TSPZ);
30967 block_widx += TSPZ;
30968 block_widx %= TSBUFZ;
30969 outbc += TSPZ;
30971 /* is buffer full? if so, write it out */
30972 if (0 == block_widx ) {
30973 /* program guide info cap does not write */
30974 if (out_file > 2) {
30975 ok = write( out_file, cut_buf, TSBUFZ);
30976 if (ok != TSBUFZ) {
30977 if (-1 == ok) {
30978 strerror_r( errno, e, sizeof(e) );
30979 } else {
30980 snprintf( e, sizeof(e), "volume full" );
30982 dvrlog( LOG_INFO, "%s stops capture '%s'", WHO, e );
30983 pid_i = 0;
30984 pid_o = 0;
30985 cap_fail = FAIL_NOSPC;
30986 cap_now = CAP_NONE;
30987 return;
30992 #endif
30994 /* static */
30995 void
30996 stream_on( void )
30998 int ir;
30999 ir = -1;
31001 /* DVB turn streaming on, mandatory for DVB */
31002 if ( (0 == test_mode) && (0 == arg_dummy) ) {
31003 ir = ioctl( dmx_file, DMX_START, NULL );
31004 if (0 == ir) {
31005 advrlog( LOG_INFO, "DMX START\n" );
31006 } else {
31007 if (0 == test_mode)
31008 dvrlog( LOG_INFO, "DMX START fail %d\n", ir );
31012 /* wait for fifo to get about 330k. tupari reported bug in usb nano driver */
31013 nanosleep( &signal_loop_sleep, NULL );
31016 /* static */
31017 void
31018 stream_off( void )
31020 int ir;
31021 ir = -1;
31023 /* DVB stream off, mandatory for DVB */
31024 if ( (0 == test_mode) && (0 == arg_dummy) ) {
31025 ir = ioctl( dmx_file, DMX_STOP, NULL );
31026 if (0 == ir) {
31027 advrlog( LOG_INFO, "DMX STOP");
31028 } else {
31029 dvrlog( LOG_INFO, "DMX STOP fail %d", ir);
31034 /* @begin fifo.c */
31035 /********************************************************************** FIFO
31036 * my code to flesh out ingos fifo, critical sections inside mutex locks
31037 * these all give warning: cast discards qualifiers from pointer target type:
31038 * using fifo->mutex as pointer to fifo->mtx to address that
31040 /* static */
31041 void
31042 fifo_atomic_init ( struct fifo_s *fifo )
31044 /* LinuxThreads default attribute is "fast" but is it fast enough? */
31045 pthread_mutex_init( &fifo->mutex, NULL );
31046 fifo->filled = 0;
31049 /* read the current fifo byte count. mutex wait if it's being modified */
31050 /* static */
31051 size_t
31052 fifo_atomic_read ( struct fifo_s *fifo )
31054 #define USE_FIFO_MUTEX_READ
31055 #ifdef USE_FIFO_MUTEX_READ
31056 #warning using FIFO mutex reads
31057 /* hmmm, maybe should have no lock. need read ASAP for FIFO drain? */
31058 size_t count;
31059 pthread_mutex_lock( &fifo->mutex );
31060 count = fifo->filled;
31061 pthread_mutex_unlock( &fifo->mutex );
31062 return count;
31063 #else
31064 return fifo->filled;
31065 #endif
31068 /* lock fifo and decrement the fifo byte count */
31069 /* static */
31070 void
31071 fifo_atomic_sub ( struct fifo_s *fifo, size_t count )
31073 /* while trylock sleep method */
31074 while
31075 ( EBUSY == pthread_mutex_trylock( &fifo->mutex ) )
31077 /* pthread_yield(); */
31078 nanosleep( &atomic_sleep, NULL );
31081 fifo->filled -= count;
31082 pthread_mutex_unlock( &fifo->mutex );
31085 /* lock fifo and increment the fifo byte count */
31086 /* static */
31087 void
31088 fifo_atomic_add ( struct fifo_s *fifo, size_t count )
31090 /* while trylock sleep method */
31091 while
31092 ( EBUSY == pthread_mutex_trylock( &fifo->mutex ) )
31094 /* pthread_yield(); */
31095 nanosleep( &atomic_sleep, NULL );
31098 fifo->filled += count;
31099 pthread_mutex_unlock( &fifo->mutex );
31102 /* This resets the actual FIFO pointers */
31103 /* Atomic init above resets pthread mutex/lock and FIFO byte count */
31104 /* static */
31105 void
31106 fifo_reset ( struct fifo_s *fifo )
31108 if (NULL == fifo) {
31109 dvrlog( LOG_INFO, "FIFO structure not allocated?");
31110 return;
31113 if (NULL == fifo->buffer) {
31114 dvrlog( LOG_INFO, "FIFO init didn't allocate buffer?");
31115 return;
31118 /* Initialize FIFO mutex and structure with start/end pointers */
31119 fifo_atomic_init( fifo );
31120 fifo->max_bytes = fifoz;
31121 fifo->read_ptr = fifo->write_ptr = fifo->buffer;
31122 fifo->end_ptr = fifo->max_bytes + fifo->buffer;
31124 advrlog( LOG_INFO, "FIFO reset, fifoz %d", fifoz);
31127 /* Allocate FIFO buffer size n bytes, store pointer in p->buffer */
31128 /* USE_DYNAMIC not needed here because FIFO is large and of variable size n */
31129 /* TESTME: It is possible that a need could arise for a large static FIFO. */
31130 /* static */
31131 void
31132 init_fifo ( struct fifo_s *fifo, int n )
31134 if (NULL == fifo) return;
31135 if (NULL != fifo->buffer) return; /* already allocated? */
31137 advrlog( LOG_INFO, "FIFO init");
31139 #ifdef USE_DYNAMIC
31140 /* malloc FIFO buffer allocation, fifo reset will init */
31141 // fifo->buffer = imalloc( n, "capture FIFO" );
31143 /* calloc FIFO buffer allocation, fifo reset will init */
31144 fifo->buffer = icalloc( 1, n, "capture FIFO" );
31146 /* This one really is fatal. Log it so the user can see. */
31147 if ( NULL == fifo->buffer ) {
31148 dvrlog( LOG_INFO,"FIFO buffer calloc failed" );
31149 fprintf( stderr, CLS "FIFO buffer calloc failed\n");
31150 console_exit(1);
31152 #else
31153 fifo->buffer = fifo_buffer; /* static fifo */
31154 memset( fifo_buffer, 0, fifoz );
31155 #endif
31156 /* clear the FIFO buffer */
31157 fifo_reset( fifo );
31160 /* frees the FIFO buffer allocated above */
31161 /* static */
31162 void
31163 free_fifo ( struct fifo_s *fifo )
31165 #ifdef USE_DYNAMIC
31166 /* sanity check */
31167 if (NULL == fifo) return; /* NULL is something seriously wrong */
31168 if (NULL == fifo->buffer ) return; /* NULL is not allocated yet */
31170 ifree( fifo->buffer, "fifo buffer" );
31171 fifo->buffer = NULL;
31173 /* fifo itself doesn't need freeing. It is a small pointer structure. */
31174 advrlog( LOG_INFO, "FIFO freed");
31175 #endif
31178 /* not quite the end of my code to flesh out ingos fifo */
31179 /***************************************************************************/
31182 /* WRITEME for UDP BROADCAST: If less than half full, keep filling. else
31183 wait a bit to get closer to half full, but not so long that any data
31184 is lost unless the cpu/io is porked. (ext2/3)
31186 It may not help if media player is always done too soon, either because
31187 it tossed frames rather than decode them late, or is a fast decoder.
31189 This is why UDP broadcast is listed as experimental, because it does not
31190 make the playback as smooth as it could be using file itself as buffer.
31193 /***************************************************************************/
31194 /* start of ingos fifo, modified to try harder to empty if getting full */
31195 /* write count bytes from buffer into fifo. */
31196 /* static */
31197 size_t
31198 fifo_write ( struct fifo_s *fifo, unsigned char * buffer, size_t count )
31200 size_t avail = fifo->max_bytes - fifo_atomic_read( fifo );
31201 size_t todo = count;
31202 size_t part1, part2;
31203 int maxtries;
31205 maxtries = 0;
31207 /* sanity checks */
31208 if (count < 1) return 0;
31209 if (NULL == fifo) {
31210 dvrlog( LOG_INFO, "%s fifo is NULL!", WHO);
31211 return -1;
31213 if (NULL == fifo->buffer) {
31214 dvrlog( LOG_INFO, "%s fifo->buffer is NULL!", WHO);
31215 return -1;
31218 /* moved here to track highest fill before drain */
31219 /* fixes slow show cap stats update */
31220 if ( fifo->filled > fill_max ) {
31221 fill_max = fifo->filled;
31222 if (fill_max2 < fill_max)
31223 fill_max2 = fill_max;
31226 /* this seems to help out when deleting large files on ext2/ext3 */
31227 #if 0
31228 /* if FIFO is half full or more, force yield to starving drain */
31229 if ( fifo->filled > (fifoz >> 1) ) {
31230 pthread_yield();
31231 nanosleep( &dummy_read_sleep, NULL);
31234 /* if FIFO is quarter full or more, force yield to starving drain */
31235 if ( fifo->filled > (fifoz >> 2) ) {
31236 pthread_yield();
31237 nanosleep( &dummy_read_sleep, NULL);
31239 #endif
31241 #if 0
31242 if (0 != test_mode) {
31243 while (todo > avail) {
31244 /* pthread_yield(); */
31245 nanosleep( &atomic_sleep, NULL );
31246 avail = fifo->max_bytes - fifo_atomic_read( fifo );
31249 #endif
31251 /* try to wait a bit for the FIFO to drain */
31252 if (todo > avail) {
31253 /* force output thread to have a go when fifo is full */
31254 while (todo > avail)
31257 /* if capture stops, break out of drain wait loop */
31258 if ( CAP_NONE == cap_now ) return 0;
31259 maxtries++;
31261 /* TWEAK THIS CONSTANT TO MATCH HARDWARE FIFO SIZE AS SET BY DEVICE DRIVER */
31262 /* stock driver should use 21, but more tries should not break anything */
31263 if (maxtries++ > 154) return 0;
31265 /* give back the timeslice so starving FIFO thread can drain */
31266 pthread_yield();
31268 /* WARNING: sleep prevents deadlock */
31269 nanosleep( &atomic_sleep, NULL );
31270 avail = fifo->max_bytes - fifo_atomic_read( fifo );
31273 /* if still no room, not much can be done. logging may make it worse */
31274 if (todo > avail) {
31275 dvrlog( LOG_INFO, "fifo write: FULL!"
31276 " start %p end %p read %p write %p\n",
31277 fifo->buffer, fifo->end_ptr,
31278 fifo->read_ptr, fifo->write_ptr );
31280 /* data was bit-bucketed. caller will increase pkt.synerr */
31281 return -1;
31285 /* this point is reached only if fifo has enough space for request */
31288 /* check for split write at end of fifo */
31289 if ( (fifo->write_ptr + todo) > fifo->end_ptr ) {
31290 /* write will not fit all before end_ptr */
31291 part1 = fifo->end_ptr - fifo->write_ptr;
31292 part2 = todo - part1;
31294 memcpy( fifo->write_ptr, buffer, part1 );
31296 /* part1 to end, part2 to beginning */
31297 buffer += part1;
31299 /* reset write ptr to start of fifo */
31300 fifo->write_ptr = fifo->buffer;
31302 memcpy( fifo->write_ptr, buffer, part2 );
31303 fifo->write_ptr += part2;
31304 } else {
31305 /* write will fit all before end_ptr */
31306 memcpy( fifo->write_ptr, buffer, todo );
31307 fifo->write_ptr += todo;
31310 /* todo is todone by now, only need nukes plus */
31311 fifo_atomic_add( fifo, todo ); /* NOTE: do this last */
31313 return todo;
31316 /* read count bytes from fifo into buffer */
31317 /* static */
31318 size_t
31319 fifo_read ( struct fifo_s *fifo, unsigned char * buffer, size_t count )
31321 size_t avail = fifo_atomic_read( fifo );
31322 size_t todo = count;
31323 size_t part1, part2;
31325 if (NULL == fifo) {
31326 dvrlog( LOG_INFO, "%s fifo is NULL!", WHO);
31327 return -1;
31330 /* sanity check */
31331 if (NULL == fifo->buffer) {
31332 dvrlog( LOG_INFO, "%s fifo->buffer is NULL!", WHO);
31333 return -1;
31336 /* loop on sleep to block caller if can't satisfy request */
31337 while ( avail < todo ) {
31339 /* escape from read wait loop if input thread ended */
31340 if (0 == pid_i) return 0;
31342 /* not enough to do gives back to scheduler until there is */
31343 pthread_yield();
31344 /* if using this, the sleep prevents deadlock */
31345 nanosleep( &atomic_sleep, NULL );
31346 avail = fifo_atomic_read( fifo );
31350 /* check for split read at end of buffer */
31351 if ( (fifo->read_ptr + todo) > fifo->end_ptr )
31353 /* it won't all read before end_ptr */
31354 part1 = fifo->end_ptr - fifo->read_ptr;
31355 part2 = todo - part1;
31357 memcpy( buffer, fifo->read_ptr, part1 );
31359 /* part1 done, move to part2 of buffer and copy */
31360 buffer += part1;
31362 /* reset write ptr to start of fifo */
31363 fifo->read_ptr = fifo->buffer;
31365 memcpy( buffer, fifo->read_ptr, part2 );
31366 fifo->read_ptr += part2;
31368 else
31369 /* read will fit all before end_ptr */
31371 memcpy( buffer, fifo->read_ptr, todo );
31372 fifo->read_ptr += todo;
31375 if ( fifo->filled > fill_max )
31376 fill_max = fifo->filled;
31378 /* todo is todone by now, only need nukes minus */
31379 fifo_atomic_sub( fifo, todo ); /* NOTE: do this last */
31381 /* return what was actually done, not count */
31382 return todo;
31384 /***************************************************************************/
31386 /******************************************************** pthread root ctl */
31387 /* pthread version of setting thread scheduler from within the thread */
31388 /* NOTE: this requires root for it to work properly as user can't set */
31389 /* FIXME: may not be fixable for 2.4.26 due to kernel limitations? */
31391 /* TODO: kick out of this without error if not running as root
31392 at least one user reports:
31393 it can run as a user with some help from nice
31395 /* static */
31396 void
31397 set_pthread_sched ( struct sched_param *sp, int pol, int prio, char *name )
31399 int ok;
31400 char policy_text[4][8] = { "OTHER", "FIFO", "RR", "SNAFU" };
31402 uid = geteuid();
31403 if (0 != uid) {
31404 dvrlog( LOG_INFO, "root needed for changing thread scheduling");
31405 return; /* non-root, let superuser try using nice */
31408 /* get first to init some structures */
31409 // ok = pthread_getschedparam( pthread_self(), &pol, sp );
31411 ok = -1;
31412 pol &= 3; /* limit for text */
31413 sp->sched_priority = 0; /* causes error 22 EINVAL if not 1. odd. */
31415 /* FIXME: prio 1 solid lock-up on 2.4.26 kernel, prio 0 errs */
31416 sp->sched_priority = prio; /* lower = higher priority */
31417 #warning using POSIX SCHED_FIFO priority 1
31419 advrlog( LOG_INFO, "%s pthread sched(%d, %s, %d)",
31420 name,
31421 (int)pthread_self(),
31422 policy_text[pol],
31423 sp->sched_priority ); /* don't show if doesn't change */
31425 ok = pthread_setschedparam( pthread_self(), pol, sp );
31427 if (0 != ok) { /* error check for scheduler prio not changed */
31428 dvrlog( LOG_INFO, "%s pthread err %d tid %d %s pri %d",
31429 name,
31431 (int)pthread_self(),
31432 policy_text[pol],
31433 sp->sched_priority
31435 return; /* not fatal but may cause problems with buffer overruns */
31438 advrlog( LOG_INFO, "%s pthread pid %d tid %d %s pri %d",
31439 name,
31440 getpid(),
31441 (int)pthread_self(),
31442 policy_text[pol],
31443 sp->sched_priority ); /* get to here is ok */
31447 /**************************************** Transport stream FIFO I/O pthreads */
31448 /* both run detached so there should be no stdio called from within thread,
31449 or else the thread will generate SIGPIPE and either die, zombie
31450 or go to sleep depending on SIGPIPE handling in place.
31454 #if 0
31455 /* this should be moved to fifo input loop? */
31457 /* sync test only useful for v4l, and only works with TSBUFZ size above,
31458 because test sync needs 21 packets worth to find possible sync points.
31459 HD2000 v4l had the sync issues, but HD3000 v4l was ok. dvb is ok for both
31461 test_sync_block( out_buf ); /* will set or reset pkt.align */
31462 /* align has number of bytes short that is corrected here */
31463 if (pkt.align != 0) {
31464 fifo_read( ts_fifo, out_buf + TSBUFZ, pkt.align );
31465 frdbc += pkt.align;
31466 advrlog( LOG_INFO, "fifo out: pkt align %d", pkt.align);
31468 #endif
31469 /* read from the device and write to the fifo until cap_now resets */
31470 void *
31471 fifo_input_loop ( void * arg )
31473 int len, ok;
31475 ok = 0;
31476 pid_i = getpid();
31478 advrlog( LOG_INFO, "%s pid_i %d detached", WHO, pid_i);
31480 #ifdef USE_SIGNALS
31481 /* disable control c */
31482 sigint_set( 0 );
31483 #endif
31485 // memset( &fifo_input_param, 0, sizeof( fifo_input_param ) );
31486 set_pthread_sched( &fifo_input_param, SCHED_FIFO, 1, "in ");
31488 stream_on();
31489 /* wait a bit for the driver to wake up */
31490 nanosleep( &guide_loop_sleep, 0);
31492 /* flush enough to purge the old data, at least 512k worth i think.
31493 512k / 188 / 21 ~= 132 blocks of 21 packets each, about 1/5 second */
31496 if (0 == arg_dummy) { /* -r assumes sync is good */
31497 if (0 != cap_now) atsc_get_sync(21);
31500 if (0 != pkt.align) dvrlog( LOG_INFO, "%s SYNC IS BAD", WHO);
31502 /* NOTE: log this to ram disk to prevent buffer overrun from log delay */
31503 advrlog( LOG_INFO, "%s sync %s OK", WHO, (arg_dummy) ?"maybe":"is");
31505 /* save current cap start time in various formats */
31506 get_time();
31507 memcpy( &tloc3, &tloc, sizeof(tloc3) );
31508 utscap = utsnow;
31510 /* getsync poisoned counts that are used by show cap stat, so reset them */
31511 /* don't need to reset the per channel stats, because only set at end of cap */
31512 memset( &pkt, 0, sizeof( struct pkt_s ));
31514 /* good sync is align 0 and no errors. log and fall through if not good */
31515 if ( (pkt.align != 0) || (pkt.synerr != 0) )
31517 /* sync_align should be 0 if it did it correctly */
31518 if (pkt.align != 0) {
31519 dvrlog( LOG_ERR, "%s no syncs align [%d], giving up", WHO,
31520 pkt.align);
31521 stop_capture( "no syncs align", FAIL_TSYNC);
31523 if (pkt.synerr != 0) {
31524 dvrlog( LOG_ERR, "%s sync error [%d], giving up", WHO,
31525 pkt.synerr);
31526 stop_capture( "sync error", FAIL_TSYNC);
31528 /* converse of above test is sync_align == 0 && sync_errors == 0 */
31529 } else {
31530 advrlog( LOG_INFO, "%s sync is good", WHO );
31533 /* input loops until cap now resets or fifo full or end of file for -r */
31534 while (CAP_NONE != cap_now) {
31536 #if USE_SELECT_FIFO
31537 int ok;
31538 fd_set fds;
31539 struct timeval fdtv;
31541 fdtv.tv_sec = 0;
31542 fdtv.tv_usec = 100; /* 1 ms, about 12.9 packets */
31543 FD_ZERO( &fds );
31544 FD_SET( in_file, &fds );
31545 ok = select( in_file+1, &fds, NULL, NULL, &fdtv );
31547 if (-1 == ok) {
31548 stop_capture( "select error", FAIL_IFIFO);
31549 break;
31552 /* timeout should yield to drain */
31553 if (0 == ok) {
31554 pthread_yield();
31555 /* will be locking up without this sleep */
31556 nanosleep( &atomic_read_sleep, NULL );
31558 #endif
31559 len = read( in_file, ts_buf, TSBUFZ );
31561 /* stop the capture if device gives an error */
31562 if (0 == arg_dummy)
31564 /* should never get end of file since it blocks */
31565 if (len == 0) {
31566 cap_fail = FAIL_NONE;
31567 dvrlog( LOG_INFO, "%s got EOF on blocked device?",
31568 WHO);
31569 stop_capture( "fifo in EOF", FAIL_IFIFO);
31570 break;
31574 /* device error */
31575 if (len < 0) {
31577 /* ext2/ext3 large file deletes can cause EOVERFLOW */
31578 if ( EOVERFLOW == errno ) {
31579 /* use SYNC error total counter for overflows */
31580 pkt.synert++;
31582 /* any error other than overflow will abort capture */
31583 } else {
31584 /* don't fill up the dvrlog, kill thread after log */
31585 dvrlog( LOG_INFO, "%s device read error %d '%s'",
31586 WHO, errno, strerror( errno) );
31587 stop_capture( "fifo error", FAIL_IFIFO);
31588 break;
31591 } else {
31592 /* dummy mode will get end of file instead of blocking */
31593 /* any error will signal end of file. is ok for replay */
31594 if (len < 1) {
31595 stop_capture( "fifo in: EOF", FAIL_NONE);
31596 break;
31600 /* -r replay dummy mode has to wait or it will fill fifo.
31601 normal mode can not tolerate very much waiting around */
31602 if (0 != arg_dummy) {
31603 while ( 1 ) {
31604 if (( fifoz - ts_fifo.filled) > len)
31605 break;
31606 /* pthread_yield(); */
31607 nanosleep( &dummy_read_sleep, NULL );
31611 /* to handle signals, don't care about the len as long as more than 0 */
31612 if (len > 0) {
31613 ok = fifo_write( &ts_fifo, ts_buf, len );
31614 if (ok < 0) {
31615 dvrlog( LOG_INFO, "%s fifo_write returns -1\n", WHO);
31616 break;
31619 if (ok > 0) fwrbc += ok;
31621 /* fifo full? only reason why ok wouldn't be len
31622 this could pork the dvrlog, so don't log it.
31623 only indication it's occuring will be pkt.synert increasing
31625 if ( len != ok )
31627 /* logging will make it worse if logging to same volume as capture */
31628 advrlog( LOG_ERR,
31629 "%s fifo full? write len %d != rc %d",
31630 WHO, len, ok );
31632 /* don't fail: count as sync error instead */
31633 /* cap_fail = FAIL_IFIFO; */
31634 pkt.synert++; /* let it add up */
31638 advrlog( LOG_INFO, "%s cap_now %d", WHO, cap_now);
31641 stream_off();
31643 /* thread is done, go back to normal scheduling */
31644 set_pthread_sched( &fifo_input_param, SCHED_OTHER, 0, "in ");
31646 #ifdef USE_SIGNALS
31647 /* control c was disabled during capture, re-enable it */
31648 sigint_set( 1 );
31649 #endif
31651 advrlog( LOG_INFO, "%s pthread_exit, wrote %lld", WHO, fwrbc );
31653 pid_i = 0;
31654 pthread_exit( NULL );
31658 /**************************************** Transport stream FIFO I/O pthreads */
31659 /* read from the fifo and write to a file until cap_now resets */
31660 /* the reader is the only one allowed to close the fifo [ingos notes] */
31661 void *
31662 fifo_output_loop ( void *arg )
31664 int ok;
31665 unsigned char *p;
31667 pid_o = getpid();
31669 ok = -1;
31671 /* demoth, log causes buffer overruns on v4l input device so log to ram */
31672 advrlog( LOG_INFO, "%s pid_o %d detached", WHO, pid_o);
31674 // memset( &fifo_output_param, 0, sizeof( fifo_output_param ) );
31675 set_pthread_sched( &fifo_output_param, SCHED_FIFO, 1, "out");
31677 pkt.align = 0;
31679 /* drain the fifo */
31680 while( 1 )
31682 /* will change to 0x47 if ok */
31683 *out_buf = 0;
31685 /* all this buffer copying is eating a share of the cpu */
31686 ok = fifo_read( &ts_fifo, out_buf, TSPZ);
31687 if (ok > 0) frdbc += ok;
31688 if (ok < 0) {
31689 dvrlog( LOG_INFO, "%s fifo_read returns -1\n", WHO);
31690 break;
31692 /* fifo has nothing to read? */
31693 if (0 == ok) {
31694 /* input thread done? */
31695 if (0 == pid_i) {
31696 advrlog( LOG_INFO, "%s cap_now %d ok %d", WHO, cap_now, ok);
31697 break;
31699 /* input thread not done, sleep a bit and try again for more data */
31700 nanosleep( &dummy_read_sleep, NULL );
31701 continue;
31704 #if 1
31705 /* TESTME: does this ever happen? */
31706 if (ok != TSPZ) {
31707 dvrlog( LOG_INFO, "fifo_read count %d is short", ok);
31708 break;
31710 #endif
31712 /* Count bad sync and good sync. Seems of little use these days, but
31713 users of bt878-based cards, like HD2000, might still find it useful.
31715 if (0x47 != *out_buf) {
31716 pkt.synert++;
31717 } else {
31718 pkt.synct++;
31721 pkt.align = 0;
31723 p = &out_buf[0]; /* assume p is len TSPZ, single packet now */
31724 atsc_test_packet_stats(p); /* statistical wrapper above ATSC layer */
31726 /* -n option:
31727 Substitutes removed packet with NULL packet. This is for Roku1000 or other
31728 hardware players. Continuity Counter is don't care for NULL packets.
31730 if (0 == pkt.keep) {
31731 if (0 != arg_nulls) {
31732 p = nul_buf;
31733 pkt.null++;
31734 pkt.keep = ~0;
31738 /* pkt.keep is set by test packet stats */
31739 if (0 != pkt.keep) {
31741 /* dummy can write */
31742 if (out_file > 2) write_pkt_block( p ); /* build/write cut_buf */
31744 /* UDP live is only UDP that works because ATSC device determines packet rate.
31745 TCP lets the application request set the packet rate but doesn't work live.
31746 Can only do near-live playback of a current capture over TCP.
31749 #ifdef USE_MCAST
31750 if (0 != udp_mcast) {
31751 struct udp_s *u = &udp;
31752 /* write the packet to the socket */
31753 if ( (-1 != u->sock) && (-1 != u->bind))
31754 udp_write_socket( u, p );
31756 #endif
31758 } /* while 1 */
31760 /* flush */
31761 // if (out_file > 2) fsync( out_file );
31763 /* thread is done, go back to normal scheduling */
31764 set_pthread_sched( &fifo_output_param, SCHED_OTHER, 0, "out");
31766 advrlog( LOG_INFO, "%s pthread_exit, read %lld outbc %lld",
31767 WHO, frdbc, outbc);
31769 /* clear output thread pid */
31770 pid_o = 0;
31771 pthread_exit( NULL );
31773 /* @end fifo.c */
31775 /********************************************************* DVB frontend init */
31776 /* /sys/class/dvb/ has list of dvb devices registered. count them */
31777 /* static */
31778 void
31779 get_dvr_count( void )
31781 struct stat s;
31782 char n[256];
31783 int i, j;
31785 j = 0;
31786 for (i = 0; i < 4; i++) {
31787 snprintf( n, sizeof n, "/sys/class/dvb/dvb%d.dvr0", i);
31788 if (0 == stat(n, &s)) j++;
31790 advrlog( LOG_INFO, "detected %d dvb devices", j);
31791 www_dvrs = j;
31792 return;
31796 /* static */
31798 init_dvb_frontend ( int fe_fd, struct dvb_frontend_parameters *frontend)
31800 char d[256];
31801 char *m;
31803 m = "";
31805 if ( (0 != test_mode) || (0 != arg_dummy) ) return 0;
31807 if (ioctl(fe_fd, FE_GET_INFO, &fe_info) < 0) {
31808 dvrlog( LOG_INFO, "FE GET INFO fail\n");
31809 return -1;
31812 if ( FE_ATSC != fe_info.type ) {
31813 dvrlog( LOG_INFO, "FE is not ATSC\n");
31814 return -1;
31817 snprintf( fe_text1, sizeof(fe_text1), "FE is %s", fe_info.name);
31819 /* driver should do this automatically? */
31820 // frontend->inversion = INVERSION_OFF;
31822 memset(d, 0, sizeof(d));
31824 m = "FUBAR";
31826 if (0 != (FE_CAN_QAM_256 & fe_info.caps)) {
31827 snprintf( &d[strlen(d)], 8, "QAM256 ");
31828 m = "QAM256";
31831 if (0 != (FE_CAN_QAM_128 & fe_info.caps)) {
31832 snprintf( &d[strlen(d)], 8, "QAM128 ");
31833 m = "QAM128";
31836 if (0 != (FE_CAN_QAM_64 & fe_info.caps)) {
31837 snprintf( &d[strlen(d)], 7, "QAM64 ");
31838 m = "QAM64";
31841 if (0 != (FE_CAN_QAM_32 & fe_info.caps)) {
31842 snprintf( &d[strlen(d)], 7, "QAM32 ");
31843 m = "QAM32";
31846 if (0 != (FE_CAN_QAM_16 & fe_info.caps)) {
31847 snprintf( &d[strlen(d)], 7, "QAM16 " );
31848 m = "QAM16";
31851 if (0 != (FE_CAN_16VSB & fe_info.caps)) {
31852 snprintf( &d[strlen(d)], 7, "VSB16 " );
31853 m = "VSB16";
31856 if (0 != (FE_CAN_8VSB & fe_info.caps)) {
31857 snprintf( &d[strlen(d)], 6, "VSB8 ");
31858 m = "VSB8";
31861 /* FIXME: cable needs to use some kind of option for this */
31862 if (0 != arg_cable) {
31863 if ( 0 != (FE_CAN_QAM_256 & fe_info.caps)) {
31864 frontend->u.vsb.modulation = QAM_256;
31865 m = "QAM256";
31867 } else {
31868 if ( 0 != (FE_CAN_8VSB & fe_info.caps)) {
31869 frontend->u.vsb.modulation = VSB_8;
31870 m = "VSB8";
31874 snprintf( fe_text2, sizeof(fe_text2), "FE using %s; supports: %s", m, d);
31875 snprintf( fe_text3, sizeof(fe_text3), "%s", m);
31877 return 0;
31880 /* DVB filters packets, but do not need filtering, so turn it off. */
31881 /* should not change for cable, because need PAT+PMT from cable. */
31882 /* static */
31884 init_dvb_filter ( void )
31887 if ( (0 != test_mode) || (0 != arg_dummy) ) return 0;
31889 dmx_filter.pid = 0x2000; /* pass-through mode? */
31890 dmx_filter.input = DMX_IN_FRONTEND;
31892 /* DMX OUT TS TAP sets demux output to be as new transport stream */
31893 dmx_filter.output = DMX_OUT_TS_TAP;
31894 dmx_filter.pes_type = DMX_PES_OTHER;
31896 /* DMX_IMMEDIATE START not used here but in fifo_input */
31897 dmx_filter.flags = 0;
31899 /* set DVB to not filter any packets */
31900 if (ioctl( dmx_file, DMX_SET_PES_FILTER, &dmx_filter) < 0) {
31901 dvrlog( LOG_INFO, "DMX SET PES FILTER fail");
31902 return -1;
31904 advrlog( LOG_INFO, "DMX SET PES FILTER 0x2000");
31905 return 0;
31908 /* This might help with the DViCo nano driver issues reported by tupari:
31909 back-to-back captures corrupt the second capture. Tupari reports closing
31910 and opening the device helps, but that induces too many delays. Instead,
31911 this will test to see if it's the demux device buffer causing the problem.
31912 atscap requires the full stream and sets the demux device accordingly.
31915 /* static */
31916 void
31917 dmx_reset( )
31919 int ok;
31921 advrlog( LOG_INFO, "%s %d", WHO, dmx_file);
31922 if (dmx_file < 3) return;
31923 ok = close( dmx_file );
31924 if (ok < 0) {
31925 dvrlog( LOG_INFO, "%s close %s %d failed", WHO, dmx_name, ok );
31926 return;
31929 dmx_file = open( dmx_name, O_RDWR ); /* ioctl is write? */
31930 if ( dmx_file < 3) {
31931 dvrlog( LOG_INFO, "%s open %s failed", WHO, dmx_name );
31932 return;
31935 advrlog( LOG_INFO, "%s open %s %d OK", WHO, dmx_name, dmx_file);
31936 if ( 0 != ioctl( dmx_file, DMX_SET_BUFFER_SIZE, TSBUFZ ) ) {
31937 dvrlog( LOG_INFO, "%s SET BUFFER SIZE failed", WHO);
31939 if ( 0 != init_dvb_filter() ) {
31940 dvrlog( LOG_INFO, "%s init_dvb_filter failed", WHO);
31945 /* Open the ATSC device(s), put fd or error in:
31946 NOTE: in_file is input, error goes here. DVB extras are input control.
31947 V4L: in_file, 1 file one device.
31948 DVB: fe_file, dmx_file, dvr_file == in_file, 3 files one device.
31950 /* static */
31951 void
31952 open_device ( char *caller )
31955 if (in_file > 2) {
31956 dvrlog( LOG_INFO, "*** %s device fd %d %s already open",
31957 caller, in_file, in_name);
31958 return; /* don't open twice */
31961 advrlog( LOG_INFO, "*** %s open device %s",
31962 caller, in_name);
31964 /* if (test_mode == 0 ) return; */
31965 /* that's enough for dummy mode? */
31967 aprintf( stderr, HOM BG "OPENED %-24s" BN, in_name);
31969 /* error until opened */
31970 in_file = -1;
31971 fe_file = -1;
31972 dmx_file = -1;
31973 dvr_file = -1;
31975 /* clear test mode, will set it if device can't be opened */
31976 test_mode = 0;
31978 /* a fix for replay file name not resetting after cap */
31979 if (0 != arg_dummy) {
31980 snprintf( in_name, sizeof(arg_dummyn)-1, "%s", arg_dummyn);
31981 in_file = open( in_name, O_RDONLY );
31982 if (in_file < 3) {
31983 dvrlog( LOG_INFO, "%s error %d reading %s",
31984 WHO, in_file, in_name );
31986 return; /* file read mode does need to init device? */
31989 advrlog( LOG_INFO, "using DVB");
31990 /* DVB API open method, uses new FE_TUNE_MODE_ONESHOT setting
31991 to stop the constant status reads that are killing driver */
31992 if ( (0 == test_mode) && (0 == arg_dummy) )
31994 memset( &fe_param, 0, sizeof( fe_param ) );
31995 memset( &fe_info, 0, sizeof( fe_info ) );
31996 memset( &dmx_filter, 0, sizeof( dmx_filter ) );
31997 fe_file = open( fe_name, O_RDWR ); /* ioctl is write? */
31999 /* all these have to pass */
32000 if ( fe_file > 2 )
32003 /* dvb_frontend.c defaults to zig-zag mode, we don't want this for ATSC/cable.
32005 All testing so far shows you do not want HD2000/3000 i2c chatter during
32006 capture because of a tendency to desync/fail or51132/or51211 i2c comm.
32007 The microcode is probably designed for only one task at a time. You can
32008 capture or you can signal scan, but both at same time cause problems.
32010 See developer mirror for my v4l-dvb hg cvs patch that has
32011 better i2c handling and automatic reset on i2c failure.
32013 Even with my patch, if you don't set tune mode to one-shot, you
32014 will see more resets than you will if you use the one-shot ioctl. You
32015 may also see increased capture errors without the one-shot ioctl.
32017 Turning off zig-zag mode and constant status check, no ret val:
32018 Reduces HD3000 i2c driver lock-outs; missing from 2.6.12-14.
32019 This ioctl is reported to be missing from 2.6.15.1 too?
32022 #if defined(FE_SET_FRONTEND_TUNE_MODE)
32023 #warning using Linux DVB FE_TUNE_MODE_ONESHOT
32024 ioctl( fe_file, FE_SET_FRONTEND_TUNE_MODE, FE_TUNE_MODE_ONESHOT );
32025 #endif
32026 advrlog( LOG_INFO, "FE OPEN OK %d", fe_file );
32027 if ( 0 == init_dvb_frontend( fe_file, &fe_param ) )
32029 dmx_file = open( dmx_name, O_RDWR ); /* ioctl is write? */
32030 if ( dmx_file > 2)
32032 advrlog( LOG_INFO, "DMX OPEN OK %d", dmx_file);
32033 if ( 0 == ioctl( dmx_file, DMX_SET_BUFFER_SIZE, TSBUFZ ) )
32035 if ( 0 == init_dvb_filter() )
32037 dvr_file = open( dvr_name, O_RDONLY );
32038 if (dvr_file > 2)
32040 dvrlog(LOG_INFO, "DVR OPEN OK %d", dvr_file);
32041 in_file = dvr_file; /* capture will use */
32043 /* DVB DONE */
32044 /* return; */ /* fall through */
32046 } else { /* dvr_file <= 2 */
32047 dvrlog(LOG_INFO, "DVR OPEN %s FAIL", dvr_name);
32048 test_mode = ~0;
32050 } else { /* 0 != init_dvb_filter */
32051 dvrlog( LOG_INFO, "DMX FILTER FAIL" );
32052 test_mode = ~0;
32054 } else { /* 0 != ioctl dmx file bufz */
32055 dvrlog( LOG_INFO, "DMX BUFFER SIZE %d FAIL", TSBUFZ );
32056 test_mode = ~0;
32058 } else { /* dmx file not open */
32059 dvrlog( LOG_INFO, "DMX OPEN %s FAIL", dmx_name);
32060 test_mode = ~0;
32062 } else { /* 0 != init_dvb_frontend */
32063 dvrlog( LOG_INFO, "FE INIT %s FAIL", fe_name);
32064 test_mode = ~0;
32066 } else { /* fe file <= 2 */
32067 dvrlog( LOG_INFO, "FE OPEN %s FAIL", fe_name);
32068 test_mode = ~0;
32072 /* make set_channel re-set the channel */
32073 scan_freq = 0;
32075 /* deprecated: test mode. want it to exit rather than eat another screen */
32076 if (0 != test_mode) {
32077 fprintf( stderr, CLS "Open %s failed\n", fe_name);
32078 if (0 != arg_dummy) return;
32079 console_exit(1); /* open fail goes to exit */
32081 #ifdef USE_POWERDOWN
32082 utspdd = utsnow + USE_POWERDELAY;
32083 #endif
32087 /* build out_name based on capture mode
32089 -o option forces outname regardless of anything else (for scripts)
32090 -r option forces outname and in name to same file name
32092 User cap name is network id.ts, or network id.pn.ts
32093 ABC.ts or ABC.3.ts
32095 MPEG has no concept of minor channel number which is only found in VCT.
32096 If the station stops sending VCT for whatever reason, the fallback to
32097 MPEG-only should still work because the program # is in the timer. The
32098 program number is used to cross ref the correct proto-VC info in MPEG pa[].
32100 Info cap name is Program Guide + Callsign
32101 Program Guide KTRK
32103 Timer cap name is from timer name and flags in timer name.
32105 build outnames is one thing that uses @ (old #). rest are:
32106 get timer add event (user enters from epg)
32107 add search event (atscap enters from search list)
32108 strip timer flags
32109 load config and save config do not process these flags
32111 TODO:
32112 build outnames could check for name existing, and
32113 if so, try name.n++[.pn].ts until it finds a non existing new name
32115 use @ instead of dot and obsolete current $ meaning to current @ meaning.
32116 no one has mentioned using $-weekday in a long time, and is unused here.
32117 The @ sign should make it more obvious the timer is the program @pgm,aud.
32118 . dot could be reused in place of , comma for audio, but starts to look
32120 /* static */
32121 void
32122 build_outnames ( void )
32124 struct sig_s *s;
32125 char *call, *net, n[64];
32126 struct qtimer_s *t;
32128 s = &ptc[cap_chan].sig;
32129 call = s->call;
32130 net = s->sid;
32132 memset( out_name, 0, sizeof(out_name));
32134 /* FIXME: breaks [enter] to cap specific vc */
32136 if (CAP_NONE == cap_now) return;
32139 advrlog( LOG_INFO, "%s cap p %d v %d a %d",
32140 WHO, cap_pn, cap_vc, cap_va);
32142 /* -r dummy mode sets out_name and in_name the same */
32143 if (0 != arg_dummy) {
32144 char nn[256];
32145 filebase( nn, arg_dummyn, F_TFILE );
32147 /* input name mangled to name-1.ts or name.p.ts */
32148 snprintf( out_name, sizeof(out_name), "%s%s%d.ts",
32149 out_path, nn, cap_pn);
32150 snprintf( in_name, sizeof(in_name), "%s", arg_dummyn);
32151 advrlog( LOG_INFO, "dummy outputs %s", out_name);
32152 return;
32155 /* -o outname override also will set output name for dummy mode, do nothing */
32156 /* NOTE: user must supply extension */
32157 if ( 0 != *arg_ofile ) {
32158 /* -p outpath override will set output path */
32159 if (0 == *arg_opath) {
32160 snprintf( out_name, sizeof(out_name)-1, "%s", arg_ofile);
32161 } else {
32162 snprintf( out_name, sizeof(out_name)-1, "%s%s",
32163 arg_opath, arg_ofile);
32165 return;
32168 /* want callsign not network for -s5 feedback, .ts extension */
32169 /* user override above will prevent it getting here */
32170 if (0 != arg_capture) {
32171 snprintf( out_name, sizeof(out_name)-1, "%s%s.ts",
32172 out_path, call);
32173 return;
32176 if (0 != arg_dummy) return;
32178 /* info cap name is Program Guide + Channel */
32179 if (CAP_INFO == cap_now) {
32180 /* cap_pn = cap_vc = cap_va = -1; */ /* set in ts_cap */
32182 snprintf( out_name, sizeof(out_name)-1, "EPG%02d", cap_chan);
32183 advrlog( LOG_INFO, "%s INFO %s", WHO, out_name);
32184 return;
32187 if (cap_pn > 0) cap_va = s->va; /* default audio from config */
32188 if (cap_pn > 0) /* user/timer cap checks audio if program select */
32189 if (-1 == cap_va) /* audio select -1 default is same as audio 0 */
32190 cap_va = 0;
32192 /* user cap sets name to net.ts or net.p.ts or net.p,a.ts if va > 0 */
32193 if (CAP_USER == cap_now) {
32194 *n = 0;
32195 cap_vc = find_vc_pgm( cap_pn, WHO );
32197 /* no VCT yet? */
32198 if (-1 == cap_vc) {
32199 cap_vc = find_pa_pgm( cap_pn, WHO );
32200 /* if cap_vc still -1, parse PAT will have to set it */
32203 /* full cap if cap_vc -1 */
32204 dvrlog( LOG_INFO, "%s user cap p %d v %d a %d",
32205 WHO, cap_pn, cap_vc, cap_va );
32207 if (cap_pn < 1) {
32208 snprintf( n, sizeof(n)-1, "%s.ts", net);
32209 } else {
32210 if (cap_va < 1) {
32211 snprintf( n, sizeof(n)-1, "%s.%d.ts",
32212 net, cap_pn);
32213 } else {
32214 snprintf( n, sizeof(n)-1, "%s.%d,%d.ts",
32215 net, cap_pn, cap_va);
32218 snprintf( out_name, sizeof(out_name)-1, "%s%s", out_path, n);
32219 advrlog( LOG_INFO, "%s USER %s p %d v %d a %d",
32220 WHO, n, cap_pn, cap_vc, cap_va);
32221 return;
32224 /* timer cap parses the flags in timer name to determine how name is built
32225 flags supported are:
32226 $ for -wdy as three char weekday
32227 @ for -mmdd-hhmm as month day hour minute
32228 (changed to @, is URI friendly, # gets truncated by browsers)
32229 .p for specific program p
32230 ,a for specific audio a, needs to follow .p, if used
32233 if (CAP_TIME == cap_now)
32235 char tn[TIMER_NAME_MAX];
32236 char tpn[16], tva[16], twd[16], tmd[16];
32237 char *f1, *f2, *f3, *f4; /* @ $ . , */
32239 t = &timer[timer_rec];
32241 cap_pn = -1; /* default is full cap unless flag given */
32243 memset( tn, 0, sizeof( tn ) );
32244 memset( tpn, 0, sizeof( tpn ) );
32245 memset( tva, 0, sizeof( tva ) );
32246 memset( twd, 0, sizeof( twd ) );
32247 memset( tmd, 0, sizeof( tmd ) );
32249 /* alter the copy of time name */
32250 astrncpy( tn, timer[timer_rec].name, sizeof(tn) );
32251 f1 = strchr( tn, '@' );
32252 f2 = strchr( tn, '$' );
32253 f3 = strchr( tn, '.' );
32254 f4 = strchr( tn, ',' );
32256 /* flags @ $ do not have following parameters, NULL check only */
32257 /* flags . , do have following parameters, NULL check and point to */
32258 if (NULL != f3) *f3++ = 0;
32259 if (NULL != f4) *f4++ = 0;
32261 get_time(); /* time is current */
32264 #if 0
32265 /* these should have been parsed and stored when timer loaded from config?
32266 but vc could still be wrong because of missing nn.vc file.
32268 cap_pn = t->pn;
32269 cap_vc = t->vc;
32270 #endif
32272 /* if . dot flag, set cap pn and cap vc from .program number */
32273 if (NULL != f3) {
32274 cap_pn = atoi( f3 ); /* some cable > 7 */
32275 cap_vc = find_vc_pgm(cap_pn, WHO); /* try to find vc */
32277 /* no VC selected? */
32278 if (-1 == cap_vc) {
32279 if (cap_pn > 0) {
32280 cap_vc = find_pa_pgm(cap_pn, WHO);
32282 /* if no vc loaded yet, parse PAT will have to set cap_vc */
32283 if (-1 == cap_vc) {
32284 dvrlog( LOG_INFO, "%s parse PAT.%d will set cap_vc",
32285 WHO, cap_pn);
32290 #if 0
32291 /* should be cap_pn = 0 to force getting first vc program
32294 /* log override and force single vc0 cap if pn set but vc isn't */
32295 if ((cap_pn > 0) && (-1 == cap_vc)) {
32296 cap_vc = find_vc_pgm( cap_pn, WHO);
32297 if (-1 == cap_vc) {
32298 dvrlog( LOG_INFO, "%s overrides cap_vc0", WHO);
32299 cap_vc = 0;
32302 #endif
32304 /* if can't find vc now, parse VCT or PAT will try to find from .program# */
32305 snprintf( tpn, sizeof(tpn), ".%d", cap_pn); /* asssume p# OK */
32307 /* if , comma flag, set program audio from ,audio number */
32308 if (NULL != f4) {
32309 cap_va = atoi( f4 );
32310 if (cap_vc >= 0) { /* if vc found */
32311 if (cap_va >= vc[cap_vc].acn) /* and audio not OK */
32312 cap_va = 0; /* use main audio */
32314 /* if vc not found yet, have to assume ,audio# is correct */
32315 snprintf( tva, sizeof(tva), ",%d", cap_va); /* assume a# OK */
32319 /* DEBUG: force main audio for all timers */
32320 /* cap_va = 0; *tva = 0; */
32322 if ( ',' != *tva ) { /* if no timer ,audio number */
32324 if (cap_va > 0) /* and user wants non-main */
32325 snprintf( tva, sizeof(tva), ",%d", cap_va); /* override it */
32328 /* if $ flag, generate -Day of week */
32329 if (NULL != f2) {
32330 *f2 = 0; /* truncate for name only */
32331 twd[0] = '-';
32332 memcpy( &twd[1], date_now, 3);
32335 /* if @ flag, generate -mmdd-hhmm */
32336 if (NULL != f1) {
32337 int ok;
32338 time_t es;
32339 struct tm et;
32340 char en[256];
32341 struct stat fs;
32343 *f1 = 0; /* truncate for name only */
32344 es = (time_t)timer[0].start;
32346 localtime_r( &es, &et);
32347 /* Check to see if file already exists at scheduled event start time
32348 and if not, use that name, otherwise use curent time */
32349 snprintf( tmd, sizeof(tmd)-1, "-%02d%02d-%02d%02d",
32350 et.tm_mon+1, et.tm_mday, et.tm_hour, et.tm_min );
32351 snprintf( en, sizeof(en)-1, "%s%s%s%s%s%s.ts",
32352 out_path, tn, twd, tmd, tpn, tva );
32353 ok = stat( en, &fs);
32354 advrlog( LOG_INFO, "build outnames %s exists %d", en, ok);
32356 /* if file exists, use current time instead; need to copy tloc current time,
32357 web users will have to go to cap dir to playback instead of guide href */
32358 if (0 == ok) {
32359 memcpy( &et, &tloc, sizeof( et ));
32360 snprintf( tmd, sizeof(tmd)-1, "-%02d%02d-%02d%02d",
32361 et.tm_mon+1, et.tm_mday, et.tm_hour, et.tm_min );
32365 /* order of flags is this: name-wdy-mmdd-hhmm.p,a.ts */
32366 snprintf( n, sizeof(n)-1, "%s%s%s%s%s.ts", tn, twd, tmd, tpn, tva);
32367 snprintf( out_name, sizeof(out_name)-1, "%s%s", out_path, n);
32368 advrlog( LOG_INFO, "%s TIMER %s p %d v %d a %d",
32369 WHO, n, cap_pn, cap_vc, cap_va);
32370 return;
32373 dvrlog( LOG_INFO, "%s FAIL\n", WHO );
32374 snprintf( out_name, sizeof(out_name)-1, "%sFAIL", out_path);
32375 stop_capture( WHO, FAIL_NONE);
32379 /* static */
32381 chaos_loop ( struct sig_s *s )
32383 int i, k, c, e, aosb;
32384 char m[ 64 ];
32385 struct timespec ns;
32386 struct timespec aos_start;
32387 struct timespec aos_stop;
32388 struct timespec aos_diff;
32390 clock_gettime( clock_method, &aos_start );
32392 aosb = 0;
32393 ns.tv_sec = 0;
32394 ns.tv_nsec = aos_delay << 20;
32395 *m = 0;
32397 scan_freq = 0;
32398 get_signal_lock( s );
32400 c = 0;
32401 e = 0;
32402 for (i=1; i <= AOS_MIN_LOOPS; i++) {
32404 if (0 != arg_cable) {
32405 if (i > 3) break;
32408 get_time();
32410 get_signal_lock( s );
32411 k = console_getch();
32413 nanosleep( &ns, NULL );
32414 /* show */
32416 asnprintf(m, sizeof(m), "%02d ", s->strength);
32417 aosb += s->strength;
32418 c++;
32419 aprintf( stderr, "\033[3;1H" BY "AOS%02d %s" BN CEL,
32420 s->chan, m);
32422 // no, want to collect all points for inner loop
32423 if (s->strength < AOS_MIN_STRENGTH) e++;
32424 if (0 != arg_cable) break;
32427 if (0 == c) c = 1;
32428 aosb /= c;
32429 clock_gettime( clock_method, &aos_stop );
32430 time_diff( &aos_diff, &aos_start, &aos_stop );
32432 if (0 != e)
32433 advrlog( LOG_INFO, "AOS%02d ET: %d.%09d AvgA[%d]: %02d%% [%s]",
32434 s->chan, aos_diff.tv_sec, aos_diff.tv_nsec, c, aosb, m );
32435 return aosb;
32438 /* -a option: Acquisition Of Signal check.
32439 AOS will try to validate the signal strength before capturing.
32440 Will try MAX_AOS_LOOPS times MIN_AOS_LOOPS in total
32441 or stop looping early if signal strength is good.
32443 /* static */
32445 chaos ( struct sig_s *s )
32447 int i, c, e, aos, aosa;
32448 char m[ 64 ];
32449 struct timespec aos_start;
32450 struct timespec aos_stop;
32451 struct timespec aos_diff;
32453 aprintf( stderr, "\033[3;1H" CEL);
32454 clock_gettime( clock_method, &aos_start );
32456 /* AOS */
32457 get_signal_lock( s );
32458 nanosleep( &msg_sleep, NULL ); /* 250ms for 5 scans */
32460 /* -a not specified or -r tard mode specified which doesn't need AOS
32461 or cable where AOS is always 99
32463 if ( (0 == arg_aos)
32464 || (0 != arg_dummy)
32465 || (0 != arg_cable) ) {
32466 aosa = 99;
32467 s->strength = aosa;
32468 return aosa;
32470 aprintf( stderr, "\033[3;1H" BY "AOS%02d" BN CEL, s->chan); /* show */
32471 aos = aosa = 0;
32472 c = 0;
32473 e = 0;
32474 *m = 0;
32475 for (i=1; i<= AOS_MAX_LOOPS; i++) {
32476 get_time();
32477 aos = chaos_loop( s );
32478 aosa += aos;
32479 c++;
32480 asnprintf(m, sizeof(m), "%02d ", aos);
32482 /* 50% is lowest strength for lock indication, if the driver is working */
32483 if (aos < AOS_MIN_STRENGTH) e++;
32484 if (aos >= AOS_MIN_STRENGTH) break;
32485 if (0 != arg_cable) break;
32487 if (0 == c) c = 1;
32488 aos = aosa / c;
32490 /* hold down until start time to get file name closer to start time */
32491 if (CAP_TIME == cap_now) {
32492 while (utsnow < timer[0].start) {
32493 get_time();
32494 nanosleep( &console_read_sleep, NULL); /* 100ms */
32498 clock_gettime( clock_method, &aos_stop );
32499 time_diff( &aos_diff, &aos_start, &aos_stop);
32501 /* for non-info cap, log if it fails */
32502 if (CAP_INFO != cap_now) {
32503 if (0 != e) {
32504 advrlog( LOG_INFO, "AOS%02d ET: %d.%09d AvgB[%d]: %02d%% [%s]",
32505 s->chan, aos_diff.tv_sec, aos_diff.tv_nsec, c, aos, m );
32506 } else {
32507 advrlog( LOG_INFO, "AOS%02d ET: %d.%09d Avg %02d%%",
32508 s->chan, aos_diff.tv_sec, aos_diff.tv_nsec, aos );
32511 return aos;
32515 /* ts capture calls this */
32516 /* static */
32517 void
32518 init_globals ( void )
32520 scan_one = ~0; /* channel lock */
32521 scan_sig = 0; /* turn off signal scan/display */
32523 sequence_idx = 0;
32524 last_sbo = 0;
32525 frame_idx = 0;
32526 last_vpn = 0;
32528 /* keep track of last cap mode for dump cap stats */
32529 cap_prev = cap_now;
32530 cap_pchn = cap_chan;
32531 cap_prvc = cap_vc;
32533 cap_zap = 0; /* no left over zaps */
32534 cap_zapped = 0;
32535 cap_done = 0;
32536 cap_fail = 0; /* it will be set later, maybe */
32537 cap_errors = 0;
32538 cap_etime = 0; /* default is by capture type */
32539 cap_tsid = 0;
32541 display_type = D_TIMERS; /* jump back to timer display */
32542 refresh.timers = 1; /* trigger the timer list */
32543 block_widx = 0; /* reset cut block index */
32545 pgm_done = 0;
32546 pgm.fail = 0;
32547 pgmto = 0; /* reset timeout to fix 'l' twice problem */
32549 fill_max = fill_max2 = 0; /* fifo stats */
32550 utscap = utsets = 0; /* time stats */
32551 sec_good = sec_bad = 0; /* number of seconds good and bad */
32552 frdbc = 0LL;
32553 fwrbc = 0LL;
32554 outbc = 0LL; /* output byte count */
32556 pat_built = 0;
32557 pmt_built = 0;
32559 chan_name[0] = 0;
32561 pkt.pmtrc = 0;
32563 /* dump stat html, dump cap log, save epg */
32564 utsdsh = 0;
32565 utsdcl = 0;
32566 utsdpg = 0;
32568 mgt_vn = 0xFF;
32570 /* -m: set cap_frames to arg_frames in case out-of-frames resets cap_frames */
32571 cap_frames = arg_frames;
32572 utspte = 0;
32574 /* clear event name and description; TODO? use t->ename/edesc instead? */
32575 *epg_name = 0;
32576 *epg_desc = 0;
32578 otsidx = tsidx;
32581 /* save tallies from psi_pids, cce_pids and pids by walking the vc list */
32582 /* static */
32583 void
32584 save_pkt_stats( struct sig_s *s )
32586 if (NULL == s) return;
32587 if (0 == vc_ncs) return;
32589 memcpy( &s->pkt, &pkt, sizeof(struct pkt_s));
32592 /* Create reader/writer threads then wait for cap_now reset.
32593 Sync check is at start of reader thread, will reset cap_now if no sync.
32594 Resetting cap_now will flush the output buffer and terminate r/w threads.
32595 Bottom of this waits for threads to end then delete any files if zap.
32597 2 pthread_joins does not work correctly. pthread spec indicates trying to
32598 start more than one thread and try to join them together causes all kinds
32599 of unspecified behaviour. Replaced join with wait for r/w thread terminate.
32601 /* static */
32602 void
32603 ts_capture ( void )
32605 int ok, i;
32606 char *tc = "";
32607 char f[256];
32608 struct sig_s *s;
32609 int ets = 0;
32610 char *ct;
32611 unsigned int aost; /* strength average of 10 tries */
32612 struct stat64 fs; /* checks for existing files */
32613 int tr; /* thread return */
32614 long long estb;
32615 char *ests;
32617 #ifdef USE_MCAST
32618 struct udp_s *u = &udp;
32619 #endif
32621 pid_c = getpid();
32622 i = 0;
32624 refresh.headers = 1;
32626 /* dump_epg_html( epg, &pgm, pg, WHO); // want html BEFORE it's wiped? */
32627 /* moved epg wipe after dump epg */
32628 init_globals();
32630 /* -s but not -S */
32631 if (0 != arg_chan) cap_chan = arg_chan;
32633 if (CAP_TIME == cap_now)
32635 advrlog( LOG_INFO, "ts cap timer[%d].chan %d %s",
32636 timer_rec,
32637 timer[timer_rec].chan,
32638 timer[timer_rec].name );
32640 /* timer_rec should always be 0 because of sort */
32641 pgm.chan = cap_chan = timer[timer_rec].chan;
32642 *epg_name = 0;
32643 *epg_desc = 0;
32645 /* if exists, copy event name and description for later */
32646 if (0 != *timer[0].ename)
32647 astrncpy( epg_name, timer[0].ename, sizeof(epg_name) );
32648 if (0 != *timer[0].edesc)
32649 astrncpy( epg_desc, timer[0].edesc, sizeof(epg_desc) );
32652 s = &ptc[ cap_chan ].sig;
32653 s->cap_ets = 0;
32655 /* manual cap default, possibly changed by build outnames */
32656 cap_pn = s->pn;
32657 cap_vc = s->vc;
32658 cap_va = s->va;
32660 /* reset the packet counts */
32661 init_stats( s );
32663 /* display the usual stats */
32664 display_type = D_TIMERS;
32665 set_refresh();
32667 /* variable size FIFO when using dynamic allocation */
32668 #ifdef USE_DYNAMIC
32669 fifoz = FIFOZ_EPG;
32670 if (CAP_INFO != cap_now) fifoz = FIFOZ_CAP;
32671 #endif
32673 /* if there is only 1GB (binary) free, the capture can't continue. */
32674 read_capvol_stats();
32675 if (CAP_NONE == cap_now) {
32676 dvrlog( LOG_INFO, "%s false alarm, volume full", WHO );
32677 if (CAP_TIME == cap_prev) reschedule_timer( 0, WHO );
32678 utswuv = -1;
32679 goto capture_exit;
32682 /* Moved post volume check. Saves memory grab until enough to do something. */
32683 init_fifo( &ts_fifo, fifoz );
32685 init_atsc();
32687 #ifdef USE_MCAST
32688 if ( (CAP_USER == cap_now) && (-1 != u->sock) && (-1 != u->bind) ) {
32689 dvrlog( LOG_INFO, "UDP MCAST %s", WHO);
32691 #endif
32693 /* show timer list so user knows a capture event is occurring */
32694 display_type = D_TIMERS;
32696 /* want callsign to show up even if cap fails somehow */
32697 astrncpy( chan_name, s->call, sizeof(chan_name) );
32699 /* WARNING: tupari reports having to close and re-open the device here or else
32700 DVB-API DViCo nano driver will trash the stream. Instead of closing
32701 all DVB devices try to reset demux device only, to see if it helps.
32703 if (0 == arg_cable) dmx_reset();
32705 /* tard mode resets file to start position every cap */
32706 if (0 != arg_dummy) lseek( in_file, 0, SEEK_SET );
32708 get_time();
32710 /* set elsewhere? */
32711 /* memcpy( &tloc3, &tloc, sizeof(tloc3) ); */
32713 /* tuning assists */
32715 /* save cap chan, will stay on this after cap */
32716 scan_pos = get_scanlist_offset( cap_chan );
32717 scan_next = scan_pos;
32719 chid = ptc[ cap_chan ].sig.sid;
32721 scan_freq = 0; /* make sure channel set if slammed into active timer */
32723 /* sets cap pn, cap vc cap va */
32725 /* FIXME: should have VCT data but find_vc_pgm below returns -1, not found.
32726 is normal for cold start with timers but without guide data,
32727 but should always have VCT if guide already saved, as in EPG [enter]
32730 /* set_channel calls set_vc and changes cap_pn/vc/va */
32731 advrlog(LOG_INFO, "%s cap chan %s%d", WHO, s->sid, s->chan);
32732 set_channel( s, WHO );
32734 /* info cap gets full capture but is only temporary, not stored in s-> */
32735 if (CAP_INFO == cap_now) {
32736 cap_pn = -1;
32737 cap_vc = -1;
32738 cap_va = -1;
32741 /* save current guide data. first new data in 7 seconds */
32742 pgm.chan = cap_chan;
32744 /* build the capture filename. it may change farther down */
32745 build_outnames();
32747 /* log start cap in case fails with AOS hang */
32748 advrlog( LOG_INFO, "CAP%02d %s start ", cap_chan, out_name );
32750 if (CAP_NONE == cap_now) goto capture_exit;
32752 *f = 0;
32753 if (out_file > 2)
32754 /* truncate filename for status */
32755 filebase( f, out_name, F_FILE);
32757 pkt.errors = 0;
32759 utsdpg = utsnow + 5; /* don't do it again for57 seconds */
32761 /* clear signal samples and index */
32762 memset( s->smp, 0, sizeof(s->smp));
32763 memset( s->rmp, 0, sizeof(s->rmp));
32764 s->smx = 0;
32766 /* -s is full cap only for now, will override config file pn/va */
32767 /* will need to parse arg_chan to get program number or add option,
32768 to make this usable via cron. until then, cron script will have
32769 to extract the desired program and delete the fullcap file.
32771 if ( (0 != arg_capture) && (0 != arg_chan)) {
32772 cap_pn = -1;
32773 cap_vc = -1;
32774 cap_va = -1;
32775 display_stat = ~0;
32778 /* update console in case startup jumps immediately to capture */
32779 show_headers(0);
32780 show_timers();
32782 /* reset cap stats to known bogus value */
32783 memset( cap_stats, 0xFF, sizeof(cap_stats) );
32785 /* current scan position, csp, gets moved around depending on mode */
32786 // snprintf( csp, 15, "\033[%d;1H", 3 + get_scanlist_offset( cap_chan ) );
32787 if (D_TIMERS == display_type) snprintf( csp, 15, "\033[3;1H");
32789 /* show filename on 1st row 1st col, white is timer, green is manual */
32790 tc = BW;
32791 if (CAP_TIME == cap_now) tc = BR;
32793 s->sig_col = &bc[0][0];
32795 /* estimated time in seconds for timer or cli -s cap, or zero for manual */
32796 get_time();
32797 ets = 0;
32798 if (0 != arg_capture) {
32799 ets = arg_capture;
32800 } else {
32801 if (CAP_TIME == cap_now) {
32802 ets = (timer[timer_rec].start
32803 + (timer[timer_rec].len
32804 - timer[timer_rec].secdt))
32805 - utsnow;
32806 if (ets < 0) ets = 0;
32809 ct = "CAP";
32810 if (test_mode != 0) ct = "TEST";
32812 /***************************** Acquisition Of Signal *************************/
32814 filebase( f, out_name, F_TFILE );
32816 aost = chaos( s );
32818 /******************************** signal is bad? ****************************/
32819 if (aost < AOS_MIN_STRENGTH) {
32820 dvrlog( LOG_INFO, "AOS%02d %2d%% FAIL %s",
32821 cap_chan, aost, f );
32823 /* should redraw timers but doesnt? */
32824 refresh.timers = 1;
32825 display_type = D_TIMERS;
32826 timer_sort = ~0;
32827 /* show_timers(); // let status update do this */
32829 /* don't keep trying if AOS is bad, reschedule it or remove it */
32830 /* cap_now gets changed */
32831 if (CAP_TIME == cap_prev) reschedule_timer( 0, WHO );
32832 stop_capture( WHO, FAIL_AOS );
32833 goto capture_exit;
32836 /**************************** signal is good ****************************/
32839 /* if it's a timer, in case AOS ends early, wait until start of capture */
32840 if ( (CAP_TIME == cap_now) && (timer_idx > 1) ) {
32841 if (T_SEARCH != timer[0].qstat) {
32842 while(utsnow < timer[0].start) {
32843 get_time();
32844 nanosleep( &console_read_sleep, NULL );
32849 /* in case AOS runs over the end of the minute, do it again
32850 to get name closer to actual capture data flow start time
32852 build_outnames();
32854 /* log start cap in case fails with AOS hang */
32855 advrlog( LOG_INFO, "CAP%02d %s", cap_chan, out_name );
32856 filebase( f, out_name, F_TFILE);
32858 if (0 == *out_name) goto capture_exit;
32859 if (CAP_NONE == cap_now) goto capture_exit;
32861 /* default is first audio if none specified */
32862 if (cap_pn > 0)
32863 if (cap_va < 0)
32864 cap_va = 0;
32866 cap_prpn = cap_pn;
32868 advrlog( LOG_INFO, "%s p %d v %d a %d", WHO, cap_pn, cap_vc, cap_va);
32870 /* do this after build outnames or you won't get pgm to vc translation */
32871 init_arrays(); /* reset stats, pgm guide, all that jazz */
32873 /* FIXME: info cap and manual full cap do not need frame data */
32874 if (CAP_INFO != cap_now) {
32875 /* single VC cap is only cap that needs frame data */
32876 if (cap_pn > 0) init_frames();
32878 /* stat check for file, gives error on large file if stat() used */
32879 if (CAP_INFO != cap_now) {
32880 // dvrlog( LOG_INFO, "AOS%02d %2d%% %d OK %s",
32881 // cap_chan, aost, s->freq, f);
32882 dvrlog( LOG_INFO, "AOS%02d %2d%% OK %s",
32883 cap_chan, aost, f);
32885 /* 0 or -1 error (errno set) */
32886 ok = stat64( out_name, &fs );
32887 /* truncate delete filename string */
32888 *del_name = 0;
32890 /* statfs 0 ret means file exists and needs to be renamed */
32891 if (ok == 0) {
32892 char *b;
32893 /* copy to delete filename and replace .ts with .$ */
32894 astrncpy( del_name, out_name, sizeof(del_name) );
32896 b = strrchr(del_name, '.');
32897 if (NULL != b) {
32898 b++;
32899 *b++ = '$';
32900 *b = 0;
32903 /* device not open, dummy mode, dont actually rename anything
32904 also don't rename if arg ofile set, in case live via /tmp/fifo
32906 if (test_mode == 0)
32907 if (*arg_ofile == 0)
32908 rename( out_name, del_name );
32912 /* create at least a blank cap log */
32913 dump_cap_log( WHO );
32915 /* OPEN OUTPUT */
32916 out_file = -1;
32918 if (CAP_INFO != cap_now) {
32920 /* CREAT, TRUNC, and set to owner read/write, everyone else read */
32921 out_file = open( out_name, FILE_WMODE, FILE_PERMS );
32922 if (out_file < 3) {
32923 dvrlog( LOG_ERR, "%s open write fail %s: %s", WHO,
32924 out_name, strerror(errno) );
32926 /* non-fatal error if write file can not be opened */
32927 fprintf( stderr, CLS "%s open write fail %s: %s", WHO,
32928 out_name, strerror(errno) );
32929 stop_capture( WHO, FAIL_OFIFO );
32930 goto capture_exit;
32932 advrlog( LOG_INFO, "out_file open %s %d", out_name, out_file );
32936 /****************************** THREAD_CREATE *****************************/
32938 /************************ input thread detaches ****************************/
32939 /* start device -> fifo input thread, as detached thread */
32940 /* cap_now = 0 stops fifo input loop */
32942 // memset( &fifo_input_thread, 0, sizeof(fifo_input_thread) );
32943 // memset( &fifo_input_attr, 0, sizeof(fifo_input_attr) );
32944 tr = pthread_attr_init( &fifo_input_attr );
32945 tr |= pthread_attr_setdetachstate( &fifo_input_attr,
32946 PTHREAD_CREATE_DETACHED);
32947 /* set now because unknown time until is loaded with thread pid */
32948 pid_i = ~0;
32949 tr |= pthread_create( &fifo_input_thread, &fifo_input_attr,
32950 fifo_input_loop, NULL);
32951 if (0 != tr) {
32952 pid_i = 0;
32953 advrlog( LOG_INFO, "pthread create fifo input error %d", tr );
32954 goto capture_exit;
32957 /* nanosleep( &console_read_sleep, NULL ); */
32959 /************************** output thread detaches ***************************/
32960 /* start fifo -> file output thread */
32961 /* pid_i = 0 and fifo filled zero stops output thread, clears pid_o */
32963 // memset( &fifo_output_thread, 0, sizeof(fifo_output_thread) );
32964 // memset( &fifo_output_attr, 0, sizeof(fifo_output_attr) );
32965 tr = pthread_attr_init( &fifo_output_attr );
32966 tr |= pthread_attr_setdetachstate( &fifo_output_attr,
32967 PTHREAD_CREATE_DETACHED);
32968 /* set now because unknown thread time until loaded from thread pid */
32969 pid_o = ~0;
32970 tr |= pthread_create( &fifo_output_thread, &fifo_output_attr,
32971 fifo_output_loop, NULL );
32972 if (0 != tr) {
32973 advrlog( LOG_INFO, "pthread create fifo output error %d", tr );
32974 pid_o = 0;
32975 goto capture_exit;
32978 /* nanosleep( &console_read_sleep, NULL ); */
32982 clock_gettime( clock_method, &cap_start );
32984 /* now that read and write have their own threads, this one
32985 does not block anymore and can be used for clock/status update
32987 while ( 1 )
32989 show_status( 1, WHO );
32991 /* look for exit or control (sets state flags) */
32992 console_scan();
32994 /* prevent cpu pig, but don't go much under 100ms to keep UI responsive */
32995 nanosleep( &signal_loop_sleep, NULL );
32997 /* stop status loop update when both threads terminate and clear pid_flags */
32998 if ( (0 == pid_o) && (0 == pid_i) ) {
32999 advrlog( LOG_INFO, "%s threads done", WHO);
33000 break;
33003 /* NOTE: if you disable this, it doesn't stop info cap for current event */
33004 /* if info cap adds event from search timer, stop the capture */
33005 if ( (CAP_INFO == cap_prev) && (CAP_INFO != cap_now) ) {
33006 advrlog( LOG_INFO, "info cap stopped");
33007 stop_capture( WHO, FAIL_NONE );
33008 break;
33011 /* if cap limit given and if past it */
33012 if ( (arg_capture != 0 ) && (utsets >= arg_capture) ) {
33013 advrlog( LOG_INFO, "-s time expired");
33014 stop_capture( WHO, FAIL_NONE );
33015 break;
33018 /* until both threads are done */
33020 /************************* end of capture *******************************/
33022 refresh.headers = 1;
33023 cap_done = ~0;
33025 /* Moved capture stop time stamp from fifo input loop because it
33026 does it out of order due to thread latency, apparently.
33028 clock_gettime( clock_method, &cap_stop );
33029 time_diff( &cap_diff, &cap_start, &cap_stop );
33030 advrlog( LOG_INFO, "start %d.%03d stop %d.%03d diff %d.%03d",
33031 cap_start.tv_sec, cap_start.tv_nsec / 1000000,
33032 cap_stop.tv_sec, cap_stop.tv_nsec / 1000000,
33033 cap_diff.tv_sec, cap_diff.tv_nsec / 1000000 );
33035 dump_cap_log( WHO ); /* write name.html capture error log file */
33036 dump_frame_sequence(); /* write name.tsx sequences + frames file */
33038 if (out_file > 2)
33039 advrlog( LOG_INFO, "out_name %s closed %lld bytes",
33040 out_name, outbc);
33042 /* log end of EPG cap too in case fails with AOS hang */
33043 advrlog( LOG_INFO, "CAP%02d %s %lldM errs %d",
33044 cap_chan, out_name + strlen(out_path),
33045 outbc >> 20, pkt.errors);
33047 if (out_file > 2) close( out_file );
33049 aprintf( stderr, BN "%s" CEL, csp);
33051 /* round up the elapsed time seconds and count 0 as 1 */
33052 s->cap_ets = cap_diff.tv_sec;
33053 if (cap_diff.tv_nsec >= 500000000L ) s->cap_ets++;
33054 advrlog( LOG_INFO, "%s cap_diff %d", WHO, cap_diff.tv_sec);
33055 if (s->cap_ets < 1) s->cap_ets = 1;
33057 ests = "";
33058 estb = outbc;
33060 /* base2-base10 (1023 != 999) rounding issue */
33061 if (estb > 1023) {
33062 estb >>= 10; /* might see K for quick start/stop */
33063 ests = "K";
33064 if (estb > 1023) {
33065 estb >>= 10; /* 1M to 999M uses M */
33066 ests = "M";
33067 if (estb > 1023) { /* 1G and above uses G */
33068 estb >>= 10; /* gigs */
33069 ests = "G";
33074 /* cap_now is already 0, test cap prev, guide cap has to chk too */
33075 if ( (CAP_USER == cap_prev) || (CAP_TIME == cap_prev) ) {
33076 /* delete *.$ renamed file and optionally zap current cap file */
33077 remove_renamed();
33078 /* small delay to see it */
33079 nanosleep( &msg_sleep, NULL );
33082 scan_sig = 0;
33083 timer_sort = ~0;
33084 blinky = 0;
33086 /* TESTME: needed? force reload of cap_pn,vc,va from s->pn,vc,va */
33087 /* scan_freq = 0; */
33088 cap_now = CAP_NONE;
33089 refresh.epg = 1; /* show event short uses */
33091 #if 0
33092 /* save any new TSIDs from this capture. NO: find stations saves at end */
33093 if (0 == arg_scan)
33094 if (otsidx != tsidx)
33095 save_tsids();
33096 #endif
33098 /* need to save config in case any new search timers added */
33099 save_config( WHO );
33101 if (0 != cap_zapped) ct = "ZAP";
33103 if (CAP_INFO != cap_prev)
33104 dvrlog( LOG_INFO, "%s%02d %s %s(%d) errs %d QoS %lld%%",
33105 ct, s->chan, f, (0 != cap_fail) ? "FAIL":"OK",
33106 cap_fail, pkt.errors, qost);
33108 /* save guide if QoSt good enough and cap > 15s */
33109 if ( ( (qost > QOS_INFO_SS_LIMIT) && (utsets > 15) )
33110 || (0 != epg_test) /* or doing test epgs */
33111 || (0 != arg_cable) /* or cable user hit [l] key */
33113 save_guide();
33115 /* TODO: turns off the refresh if it was on [DONE?] */
33116 dump_cfg_html( WHO );
33118 /* update packet stats for this channel */
33119 save_pkt_stats( s );
33121 capture_exit:
33122 cap_now = CAP_NONE;
33123 /* This caused no-end-to-cap problems the last time I tried to use it */
33124 while (0 != (pid_o | pid_i)) nanosleep(&atomic_sleep, NULL);
33126 /* do not free until capture threads end, bug found by Peter Knaggs */
33127 free_fifo( &ts_fifo ); // is dynamic even with USE_DYNAMIC ndef
33128 free_frames(); // is dynamic only when USE_DYNAMIC def
33129 free_atsc(); // is dynamic only when USE_DYNMAIC def
33131 /* clear current capture epg name, description and flags */
33132 memset(epg_name, 0, sizeof(epg_name));
33133 memset(epg_desc, 0, sizeof(epg_desc));
33134 cap_done = ~0;
33135 out_file = -1;
33136 scan_sig = 0;
33138 #ifdef USE_POWERDOWN
33139 /* clear the wakeup one-shot to auto-find the next time to wake up */
33140 utswuv = 0;
33141 #endif
33143 /* redraw timers */
33144 last_pktc = pkt.count;
33145 get_time();
33146 display_type = D_TIMERS;
33147 set_refresh();
33148 show_headers(1);
33149 show_status( 0, WHO );
33151 /* hold down test guides if expired in case another timer kicks in */
33152 if (utstpg < (utsnow + PROGRAM_GUIDE_TIMEOUT))
33153 utstpg = utsnow + PROGRAM_GUIDE_TIMEOUT;
33155 pid_c = 0;
33157 #ifdef USE_POWERDOWN
33158 get_time();
33159 utspdd = utsnow + USE_POWERDELAY;
33160 #endif
33163 /* Check to see if any guide file expiration (modify) times have been crossed.
33164 If post modify time, initiate info cap to load a new guide from stream.
33165 If zero modify time, init modify time with stat time from guide.
33166 Otherwise if saved guide file exists for current channel, load it.
33168 The guide file expiration time will be set to the next 3 hour meridian.
33169 Optionally, the guide file will expire half-way to the end of the last event,
33170 or the next 3hr meridian, which ever is greater.
33172 The device number will add futher multiples of seconds to the timeout,
33173 so that the multiple instances will stagger the load instead of
33174 hitting it all at once, to not distract any current playback.
33176 NOTE: There are a lot of conditions that will hold down this function.
33177 It is meant to run when all else is idle.
33180 /* various hold downs prevent test epgs from running, order may be important */
33181 /* static */
33183 test_epgs_hold( char *caller )
33185 int i, j, k;
33186 struct sig_s *s;
33189 //#define USE_SIGSCAN_IGNORES_INFOCAP
33190 #ifdef USE_SIGSCAN_IGNORES_INFOCAP
33191 /* prevents timely auto-epg updates. [not needed with 3hr meridian fixups?] */
33192 if ( 0 != scan_sig ) return -1; /* is not signal scan? */
33193 #endif
33194 if (CAP_NONE != cap_now) return -1; /* is not capturing? */
33195 if ( utsnow < utstpg ) return -1; /* is not expired? */
33196 if ( 0 != pid_c) return -1; /* is not capturing? */
33198 if ( 0 != arg_scan ) return -1; /* is not creating config */
33199 if ( 0 == cf_mt ) return -1; /* is not configured */
33200 if ( D_CHANNELS == display_type)
33201 return -1; /* is not channel list? */
33203 if ( 0 != arg_dummy) return -1; /* is not dummy? */
33204 if ( 0 != test_mode) return -1; /* is not test? */
33206 /* hold down test epgs if timer0 is within 3m of now */
33207 if (timer_idx > 0) {
33208 if (T_SEARCH != timer[0].qstat) { /* not search? */
33209 if ( utsnow >= (timer[0].start - 180) ) {
33210 dvrlog( LOG_INFO, "%s timer0 %s", WHO, timer[0].name );
33212 /* push off next test guides check until well after timer is done */
33213 utstpg = timer[0].start + timer[0].len
33214 + ( (1+arg_devnum) * PROGRAM_GUIDE_TIMEOUT);
33215 return -1;
33220 /* hold down test epgs if timer1 within 3m of now */
33221 /* prevents late start: test epgs trying to run between consecutive timers */
33222 if (timer_idx > 1) {
33223 if (T_SEARCH != timer[1].qstat) { /* not search? */
33224 if ( utsnow >= (timer[1].start - 180) ) { /* within 3m? */
33225 dvrlog( LOG_INFO, "%s timer1 %s", WHO, timer[1].name );
33226 return -1;
33231 /* nothing to do? */
33232 j = 0;
33233 for (i = 0; i < scan_idx; i++ ) {
33234 k = scan_list[i];
33235 s = &ptc[ k ].sig;
33237 /* skip if no auto program guide time out flag */
33238 if (0 == s->pgto) continue;
33240 /* TESTME: is it worth the trouble of consolidating pgto and pgrt? NO?
33241 needs adjustments to load/save config and would confuse user? */
33243 /* has program guide refresh time occurred yet? */
33244 if (utsnow >= s->pgrt) j++;
33247 /* no epgs need refresh? */
33248 if (0 == j) return -1;
33250 advrlog( LOG_INFO, "%s %s is OK to run", WHO, caller);
33251 return 0;
33254 /* the mutex is used to prevent multiple allocs but is it needed? */
33255 /* static */
33256 void
33257 test_epgs ( char *caller )
33259 int spgch, spgmt, fpgmt;
33260 int i, ch, rc;
33261 struct sig_s *s;
33262 struct stat fs;
33263 char f[256];
33264 int ok;
33266 /* FIXME: list of channels already done, but would break import of .epg?
33267 No, cable import is too limited only loads first program... for now.
33268 There would be multiple events listed for broadcast until I make
33269 show program guide be more selective about the events it displays.
33270 This mostly only applies to stations that share same PTC, but the
33271 user wants to have separate listing on channel list for it.
33272 ptcd tells to copy .vc file instead of cap. vc+pa doesn't refer PTC.
33274 unsigned char vcd[ 128 ];
33275 char en[256]; // epg name
33276 char xn[256]; // xlate name
33277 char dn[256]; // dir name
33278 char sc[256]; // system cli
33280 if (0 != arg_scan) return;
33281 memset( vcd, 0xFF, sizeof(vcd) );
33283 /* no guide on cable, but maybe can fetch from elsewhere? */
33284 // if (0 != arg_cable) return;
33286 /* don't do rest if hold downs haven't expired */
33287 if (0 != test_epgs_hold( WHO )) return;
33289 /* is it already running? */
33290 if (EBUSY == pthread_mutex_trylock( &epg_mutex )) return;
33292 advrlog( LOG_INFO, "%s(%s)", WHO, caller);
33294 /* NOTE: don't return until these freed and mutex unlocked */
33295 epg2 = imalloc( sizeof( epg ), "test epg2");
33296 if (NULL == epg2) {
33297 dvrlog( LOG_INFO, "%s failled epg2 malloc", epg2);
33298 goto test_epgs_exit;
33301 epg_test = ~0; /* hold down before setting any local variables */
33303 /* Z parameter wasn't numeric, use ntp_name for ntpdate fetch */
33304 if (0 != *ntp_name) {
33305 memset(sc, 0, sizeof(sc));
33306 snprintf(sc, sizeof(sc)-1, "ntpdate %s &>/dev/null", ntp_name);
33307 rc = 0;
33308 #ifndef USER_TEST
33309 rc = system( sc );
33310 #endif
33311 if (0 != rc)
33312 dvrlog(LOG_INFO, "%s %d = system( %s )", WHO, rc, sc);
33315 /* hold down and mutex tests complete, see if any guides have changed,
33316 from first config channel to last config channel
33318 snprintf( dn, sizeof(dn)-1, "%s%s/pg", out_path, ram_path);
33320 chdir( dn );
33322 /* remove all the pnxf files, if any */
33323 for (i = 0; i < scan_idx; i++) {
33324 /* point to sig struct for this channel */
33325 s = &ptc[ scan_list[i] ].sig;
33326 if (0 == s->pnx) continue;
33327 snprintf( en, sizeof(en), "%02d.epx", s->pnxf);
33328 remove( en );
33331 for (i = 0; i < scan_idx; i++ )
33333 nanosleep( &console_read_sleep, NULL); /* reduce CPU usage */
33335 fpgmt = 0;
33336 ch = scan_list[i];
33337 s = &ptc[ ch ].sig; /* point to sig struct for this channel */
33339 /* subset of scanlist: only channels with program guide time out non-zero */
33340 if (0 == arg_cable) /* want cable to fetch all the vc tsids */
33341 if (0 == s->pgto)
33342 continue;
33344 /* this can still be used for touch but mostly as http blank helper now */
33345 snprintf( f, sizeof(f)-1, "%s%spg/%02d.html", out_path, ram_path, ch);
33346 ok = stat( f, &fs );
33347 if (0 == ok)
33349 fpgmt = (int)fs.st_mtime;
33351 /* NOTE: Refresh can be done even if capturing something on another channel */
33353 /* Refresh html guide is done whenever html file date is zero */
33354 if (0 == fpgmt) {
33355 advrlog( LOG_INFO, "touch dump guides %s %d", f, fpgmt );
33357 /* init_epg that clears first byte of each .name entry would be faster */
33358 memset( &pgm2, 0, sizeof( pgm )); /* tiny array */
33359 memset( &pg2, 0xFF, sizeof( pg )); /* small array */
33361 /* use current hide/aged/match settings for guide refresh */
33362 pgm2.chan = ch;
33363 pgm2.hide = pgm.hide;
33364 pgm2.age = pgm.age;
33365 pgm2.find = pgm.find;
33367 load_epg( ch, epg2, &pgm2, pg2, WHO );
33368 build_pg_epg( epg2, &pgm2, pg2, WHO );
33369 dump_epg_html( epg2, &pgm2, pg2, WHO );
33371 } else {
33372 memset( epg2, 0, sizeof( epg )); /* large array */
33373 memset( &pgm2, 0, sizeof( pgm )); /* tiny array */
33374 memset( pg2, 0xFF, sizeof( pg )); /* small array */
33376 /* file does not exist, can create a blank one now */
33377 pgm2.chan = ch;
33378 build_pg_epg( epg2, &pgm2, pg2, WHO );
33379 dump_epg_html( epg2, &pgm2, pg2, WHO );
33383 /* now check binary version */
33384 fpgmt = 0;
33385 snprintf( f, sizeof(f)-1, "%s%spg/%02d.epg", out_path, ram_path, ch );
33386 ok = stat( f, &fs );
33387 if (0 == ok) fpgmt = (int)fs.st_mtime;
33389 /* init by load config needs to reload all the timeouts from mtime */
33390 if (0 == s->pgmt) s->pgmt = fpgmt;
33392 if (0 == fpgmt) s->pgmt = 0; /* www reload */
33394 text_time(date_next, s->pgmt, 19);
33395 advrlog( LOG_INFO, "APG%02d %s %s",
33396 ch, (utsnow < s->pgmt)?"expires":"EXPIRED", date_next);
33398 get_time();
33400 /* break out if any timers and under 3 minutes to next timer */
33401 if (0 != timer_idx) {
33402 if (T_SEARCH != timer[0].qstat) {
33404 /* TESTME: never interrupted and rescheduled here? log it if so */
33405 if ( utsnow >= timer[0].start - 180) {
33406 epg_test = 0;
33407 utstpg = timer[0].start + timer[0].len;
33408 utstpg += PROGRAM_GUIDE_TIMEOUT;
33409 dvrlog( LOG_INFO, "%s timer0 within 3m", WHO );
33410 break; /* free mallocs above */
33415 /* is guide expired? if so, trigger new guide load */
33416 if ( utsnow >= s->pgmt ) { /* will be = if arrives here on time */
33418 /* trigger new guide load */
33419 pgm.chan = ch;
33420 cap_chan = ch;
33422 /* move scan index to current capture channel so console interface follows */
33423 scan_pos = get_scanlist_offset( ch );
33425 rc = chdir( dn );
33426 if (0 != rc) continue;
33428 cap_fail = FAIL_NONE;
33430 advrlog( LOG_INFO, "APG%02d expired ts cap", ch);
33432 /* should update the PAT+PMT every 3 hours for cable too. cable may
33433 move some stations around but program number should be same. */
33434 cap_now = CAP_INFO;
33435 ts_capture();
33437 /* check for aborts */
33438 if (0 != cap_zapped) break;
33439 if (0 != cap_zap ) break;
33440 if (0 == epg_test) break;
33442 advrlog( LOG_INFO, "APG%02d fail: cap %d pgm %d",
33443 ch, cap_fail,pgm2.fail);
33444 vcd[ s->ptc ] = s->chan;
33446 /* xn is external epg name, en is local epg name */
33447 snprintf( xn, sizeof(en)-1, "%02d.epg", s->pnxf );
33448 snprintf( en, sizeof(xn)-1, "%02d.epg", ch );
33450 /* cable fetches the .epg from remote server */
33451 if ((0 != arg_cable) && (0 != s->pnx)) {
33452 aprintf( stderr, DCA_HEAD3
33453 "wget remote %02d.epg" CEL, s->pnxf);
33454 remove( en );
33456 /* FIXME: should set timeout and retries to 30s/0, or write http_fetch */
33457 snprintf( sc, sizeof(sc)-1,
33458 "wget -q -O %s http://%s/pg/%s",
33459 en, epg_srv, xn);
33460 rc = system( sc );
33461 if (0 != rc)
33462 dvrlog( LOG_INFO, "APG%02d %d = %s",
33463 ch, rc, sc );
33465 /* try to load the remote epg (will translate channel and program number) */
33466 load_epg( ch, epg, &pgm, pg, WHO );
33467 aprintf( stderr, DCA_HEAD3 CEL);
33470 /* redraw timer headers after EPG capture */
33471 show_headers(0);
33473 /* If no epg, or blank, set it to retry at the next EPG meridian
33474 when the EPG may again be available. 2 cases handled here:
33475 1) user may have set C line GTO field for stations w/o EPG
33476 2) station that has guide stops sending it, or has really bad reception
33478 if (0 == pgm.idt)
33480 struct utimbuf u;
33481 FILE *fh;
33482 int t;
33483 t = get_next_meridian();
33485 if (0 != s->pgto)
33486 dvrlog( LOG_INFO,
33487 "APG%02d failed: retry in %dm",
33488 ch, (t - utsnow) / 60 );
33490 /* don't trigger until next 3 hour meridian */
33491 s->pgmt = t;
33492 s->pgrt = t;
33494 memset( &u, 0, sizeof(u));
33496 /* touch, in case file does not exist, want epg status to be blank */
33497 fh = fopen( f, "a");
33498 if (NULL != fh) {
33499 fclose( fh );
33500 u.modtime = (time_t) s->pgmt;
33501 utime( f, &u );
33508 spgch = 0;
33509 spgmt = 0x7FFFFFFE;
33511 /* count backwards to find lowest channel with earliest pgmt first */
33512 for (i = (scan_idx - 1); i >= 0; i--) {
33513 ch = scan_list[i];
33514 s = &ptc[ ch ].sig;
33515 if (0 == s->pgto) continue; /* only the active ones */
33516 if ( (s->pgmt > utsnow) && (s->pgmt <= spgmt) ) {
33517 spgmt = s->pgmt;
33518 spgch = ch;
33522 if ((0 == spgch) || (0x7FFFFFFE == spgmt) || (0 == spgmt)) {
33524 /* push off next test until next 3hr meridian */
33525 advrlog( LOG_INFO, "APGxx %s found nothing", WHO );
33526 utstpg = get_next_meridian();
33527 } else {
33528 utstpg = spgmt;
33529 text_time( date_next, utstpg, 19 );
33530 dvrlog( LOG_INFO, "APG%02d next autoguide %s", spgch, date_next);
33532 epg_to = spgmt;
33533 epg_ch = spgch;
33536 // TESTME is this needed here too? ts capture already set it...
33537 #ifdef USE_POWERDOWN
33538 utspdd = utsnow + USE_POWERDELAY;
33539 #endif
33541 /* Track the date of the last matching spam event seen, */
33542 save_spamlist( WHO );
33544 test_epgs_exit:
33545 if (NULL != epg2) ifree( epg2, "test epg2" );
33546 epg2 = NULL;
33547 epg_test = 0; /* status */
33548 pthread_mutex_unlock( &epg_mutex );
33551 /* show channels in scan_list, and signal strengths (if scan_sig != 0)
33553 /* FIXME: last unscrollable list: this does not support cable, so more than
33554 user_lines channels will make the redraw very ugly. I do not anticipate
33555 that people will have more than 20 channels for local broadcast physical
33556 transmission channels but with packet stats enable, it's only 16 lines.
33558 This also affects web-scan, so be careful about what is changed to make
33559 it do console, i.e. don't skip channels because they don't display.
33561 /* static */
33562 void
33563 show_channels ( void )
33565 int i, j, m, n;
33566 int ch;
33567 int siglocks = 0;
33568 struct sig_s *s;
33570 channel_idx = scan_idx;
33571 show_chanlist();
33573 for (j = 0; j < scan_idx; j++) {
33575 show_status( 1, WHO );
33577 /* scan next moves to next entry in scan list */
33578 if (scan_next > 0) {
33579 j = scan_next % scan_idx; /* sanity limit/wrap */
33580 scan_next = 0;
33583 /* If [TAB] mode, skip channels that don't start and end in display area.
33584 This will make the web interface act strange while viewing what console
33585 is doing. Scan button 2x should kick it out of limited channel mode.
33587 if (D_CHANNELS == display_type) {
33588 if ( (j < channel_offset)
33589 || (j >= (channel_offset + user_lines) ) ) {
33590 console_scan();
33591 continue;
33595 i = channel_offset + j;
33596 if (i >= scan_idx) continue;
33598 scan_pos = i;
33599 ch = scan_list[scan_pos];
33601 /* NOTE: everything uses cap_chan for current channel */
33602 cap_chan = ch;
33603 s = &ptc[ch].sig;
33604 s->lock = 0;
33606 if (ch != pgm.chan) load_guide( ch, WHO );
33608 /* cursor scan position string */
33609 snprintf( csp, 15, "\033[%d;1H", 3 + (j % user_lines));
33610 if (D_TIMERS == display_type)
33611 snprintf( csp, 15, "\033[%d;1H", 3);
33613 /* goal is one channel per second, scan or no scan, use scan_rate to help */
33614 if (scan_sig == 0) n = 1;
33615 n = scan_rate;
33616 if (0 != arg_cable) n = 1;
33618 siglocks = 0;
33619 s->str_avg = 0;
33621 /* this is how many signal scans to show on each channel */
33622 for (m = n; m > -1; m--)
33624 if (fe_file > 2) set_channel( s, WHO );
33626 /* keep signal scans over 100ms cycle time but makes read sig slower */
33627 /* but it does allow control-L for refresh */
33628 console_scan();
33629 nanosleep( &console_read_sleep, NULL);
33631 /* bust out if cap enabled (already below) */
33632 if (CAP_NONE != cap_now) break;
33634 /* only show status on first loop if signal scan disabled */
33635 if (0 == scan_sig) {
33636 show_status( 1, WHO );
33637 // aprintf( stderr, SCI "%s", csp );
33638 aprintf( stderr, "%s", csp );
33640 /* should only kick in the first time through */
33641 // if (0 == m)
33642 show_signal( s, j );
33644 // aprintf( stderr, SCV );
33646 for (i=0; i<16; i++) {
33647 if (CAP_NONE != cap_now) break;
33648 console_scan();
33649 if (scan_next > 0) break;
33650 nanosleep( &guide_loop_sleep, NULL);
33652 if (scan_next == 0) continue;
33656 if (scan_next > 0) {
33658 #if 0
33659 /* obsolete [ and ] keys, but used for 1-9 keys */
33660 /* scan_next: -2 back1, -1 next1, or 1-9 to jump to */
33661 // obsolete [ last ] next channel keys
33662 // refresh.vstats = 1;
33663 if (scan_next < 0 ) {
33664 if (scan_next == -2) {
33665 j--;
33666 if (scan_one == 0) j--;
33667 if ( j < 0 ) j += scan_idx;
33668 scan_next = 0;
33669 break;
33671 /* break advances to next channel */
33672 if (scan_next == -1) {
33673 if (scan_one) j++;
33674 scan_next = 0;
33675 break;
33678 #endif
33680 /* channel jump, from hotkey [or signal?] */
33681 if (scan_next > 0) {
33682 if (scan_next <= scan_idx) {
33683 j = scan_next - 1;
33684 scan_next = 0;
33685 break;
33688 /* it wasn't valid so ignore it */
33689 scan_next = 0;
33690 } /* if scan_next != 0 */
33692 /* get new signal readings if doing signal scan */
33693 // show_status( 1, WHO );
33694 if (0 != scan_sig) get_signal_lock( s ); /* read strength */
33696 /* last loop on this channel when scanning all
33697 gets str_avg substituted for strength
33699 if (0 == scan_one) {
33700 if (0 == m) {
33701 calc_sig_avg(s);
33702 s->strength = s->str_avg;
33704 /* console needs because arg extras sets different display ranges */
33705 if (0 != arg_extras) {
33706 calc_snr_rms(s);
33707 s->snr = s->snr_rms;
33712 /* move the cursor and show the signal reading */
33713 aprintf( stderr, SCI "%s", csp );
33714 if (0 != scan_sig) {
33715 show_signal( s, j );
33718 /* capture start breaks out of channel scan */
33719 if (CAP_NONE != cap_now) break;
33721 // blinks too fast
33722 // if (0 != scan_one) m = m + 1;
33723 if (CAP_NONE != cap_now) break;
33724 } /* for m */
33726 /* FIXME: move this up to use m instead so scan_one doesn't display avg
33727 and make the graph less than real-time looking. also, looping on j
33728 regenerates values that already have been computed for m loop
33730 /* keep on this channel if scan one is mode */
33731 if (0 != scan_one) j = j - 1;
33733 /* capture start breaks out of channel scan */
33734 if (CAP_NONE != cap_now) break;
33735 } /* for j */
33739 /* loop wrapper for show channels so it can return whenever */
33740 /* static */
33741 void
33742 channel_scan ( void )
33744 cap_chan = scan_list[ scan_pos ]; /* restore channel */
33745 load_guide(cap_chan, WHO); /* restore guide */
33746 show_headers(0); /* redraw headers */
33747 if (test_mode != 0) scan_sig = 0; /* dummy mode has no sig */
33749 while (CAP_NONE == cap_now) {
33750 show_channels();
33751 nanosleep( &console_read_sleep, NULL);
33752 if (0 == scan_sig) nanosleep( &signal_sleep, NULL);
33754 refresh.timers = 1; /* needed? */
33757 #define USE_SAMPLE_SH
33758 #ifdef USE_SAMPLE_SH
33759 /* build a script file to run atscap in sample collection mode */
33760 /* find stations calls this if user tells it to call this */
33761 /* static */
33762 void
33763 build_sample_script ( char *location, int s )
33765 FILE *sample_fd = NULL;/* output file descriptor */
33766 char chs[512]; /* 3 digits plus space for 124 channels */
33767 char *n = "/usr/local/bin/get-samples.sh";
33768 int i;
33770 /* sanity limit */
33771 if (s < 1) {
33772 fprintf( stderr, "\nnothing to write?\n");
33773 return;
33775 get_time(); /* sets date_now, minus year and NL */
33777 sample_fd = fopen( n, "w");
33778 if (NULL == sample_fd) {
33779 /* TODO: print error for reason why write failed */
33780 return;
33783 *chs = 0; /* asnprintf uses start of channels[] */
33785 for (i=0; i < scan_idx; i++)
33786 asnprintf( chs, sizeof(chs), "%3d ", scan_list[i]);
33788 fprintf( sample_fd,
33789 "#!/bin/sh\n"
33790 "# Generated by atscap -S, %s. You may need to edit this.\n"
33791 "# This script collects ATSC samples from all of your stations.\n"
33792 "# You may edit LOCATION to reflect the nearest big city.\n"
33793 "# You may edit CHANNELS if you adjust for better reception.\n"
33794 "LOCATION=%s\n"
33795 "CHANNELS=\042%s\042\n"
33796 "cd /dtv\n"
33797 "echo atscap >atscap.txt\n"
33798 "tar cf samples-$LOCATION.tar atscap.txt\n"
33799 "for i in $CHANNELS; do\n"
33800 " echo Collecting samples from channel $i\n"
33801 " atscap -q -i%d -s%d -o$i.ts $i\n"
33802 " if [[ -f $i.ts ]]; then\n"
33803 " atscut -q -m3 -a15 $i.ts >$i-mgt.txt\n"
33804 " atscut -kmgt $i.ts >/dev/null\n"
33805 " tar rf samples-$LOCATION.tar $i-mgt.txt $i-mgt.ts\n"
33806 " rm -f $i-mgt.txt $i-mgt.ts $i.ts\n"
33807 " fi\n"
33808 "done\n"
33809 "rm -f samples-$LOCATION.tar.bz2\n"
33810 "bzip2 samples-$LOCATION.tar\n"
33811 "echo Email samples-$LOCATION.tar.bz2 to inkling@users.sourceforge.net\n"
33812 "echo The curious may inspect the -mgt.ts files with atscut -a15 -m3.\n"
33813 , date_now, location, chs, arg_devnum, s );
33815 fclose( sample_fd );
33816 chmod( n, 0755 );
33818 #endif
33820 /* static */
33822 noyes( void )
33824 char key;
33825 while (1) {
33826 key = console_getch();
33827 nanosleep( &console_read_sleep, NULL );
33828 if (key == 0) continue;
33829 if (('Y' == key) || ('y' == key)) {
33830 fprintf( stderr, "yes\n" );
33831 return 1;
33833 fprintf( stderr, "no\n");
33834 break;
33836 return 0;
33839 /* station found list, one for each VC not scrambled w/ SD/HD video + audio */
33840 struct sf_s {
33841 char call[8];
33842 char sid[4];
33843 short tsix;
33844 unsigned int tsid;
33845 unsigned int pn;
33846 unsigned int ptc;
33847 unsigned int hd;
33851 /* -S option to scan all frequencies for available digital channels */
33852 /* static */
33853 void
33854 find_stations ( void )
33856 FILE *o;
33857 int f, i,j, k, m, n;
33858 // char *ok;
33859 int sigavg, signum, sigmax;
33860 struct sig_s *s;
33861 unsigned char key;
33862 int ui_save = 0;
33863 int ui_epg = 0;
33864 int ui_run = 0;
33865 int ui_sd = 0;
33866 int epc; /* error percent */
33867 struct timespec find_start = {0,0};
33868 struct timespec find_stop = {0,0};
33869 struct timespec find_diff = {0,0};
33871 struct timespec scan_start = {0,0};
33872 struct timespec scan_stop = {0,0};
33873 struct timespec scan_diff = {0,0};
33875 unsigned int atimes[ 128 ];
33876 unsigned long long atime_avg;
33877 unsigned int atime_hr;
33878 time_t atime_av;
33879 time_t atime_at;
33880 time_t scan_tnow;
33882 // char location[16];
33883 // int seconds = 60;
33885 int tf;
33886 struct tsid_s *t;
33887 int ot;
33889 memset(atimes, 0, sizeof(atimes));
33891 ot = tsidx;
33893 /* FIXME: limited by size of ptc[],
33894 but only seeing 38 unscrambled cable PMTs here with comcast
33897 #define SF_MAX 128
33898 struct sf_s sf[ SF_MAX ]; /* 128 * 20 */
33900 /* FIXME: have to set different save file name here */
33901 /* because something else calls save config, but shouldn't */
33902 snprintf( cfg_name, sizeof(cfg_name), "atscap%d.newconf", arg_devnum);
33904 /* want to keep track of how long find stations takes */
33905 clock_gettime( clock_method, &find_start );
33907 i = mkdir( cfg_path, 0777 );/* tupari reported this one */
33909 if ((0 != i) && (EEXIST != errno)) {
33910 fprintf( stderr, CLS "\n %s Error mkdir %s: %s\n\n",
33911 WHO, cfg_path, strerror(errno) );
33912 console_reset();
33913 exit(1);
33916 test_termsize();
33918 if (in_file < 3) {
33919 fprintf(stderr, "\n DVB Device not open\n");
33920 exit(1);
33924 scan_idx = 0;
33926 refresh.timers = 0;
33928 #ifndef SHORT_TEST
33929 #define LOC 2
33930 #define HIC ptc_max
33931 #endif
33933 #ifdef SHORT_TEST
33934 #ifdef USE_CABLE
33935 /* ccast uhf hou range */
33936 #define LOC 70
33937 #define HIC 118
33938 #else
33939 /* bcast uhf hou range */
33940 #define LOC 19
33941 #define HIC 39
33942 #endif
33943 #endif
33945 #ifdef USER_TEST
33946 i = 0;
33948 scan_list[i++] = 5;
33949 scan_list[i++] = 9;
33950 scan_list[i++] = 19;
33951 scan_list[i++] = 27;
33952 scan_list[i++] = 31;
33953 scan_list[i++] = 32;
33954 scan_list[i++] = 35;
33955 scan_list[i++] = 36;
33956 scan_list[i++] = 38;
33957 scan_list[i++] = 42;
33958 scan_list[i++] = 44;
33959 scan_list[i++] = 46;
33960 scan_list[i++] = 48;
33961 scan_list[i++] = 52;
33962 scan_idx = i;
33963 #endif
33965 dvrlog(LOG_INFO, "%s %s lo-chan %d hi-chan %d \n",
33966 WHO, (0 == arg_cable) ? "vsb":"qam", LOC, HIC );
33967 nanosleep(&guide_sleep, NULL);
33969 if (scan_idx < 1)
33971 display_stat = 0;
33972 show_headers(0);
33973 fprintf( stderr, "\033[1;1H"
33974 BW "Looking for intelligent life on %s ... "BN, in_name );
33976 for (i = LOC; i < HIC; i++)
33978 /* control c check, tupari says it's unstoppable! */
33979 key = console_getch();
33980 s = &ptc[i].sig;
33982 /* skip blank frequency list items */
33983 if (s->freq < 1) continue;
33985 /* preload some values in case nothing found for lookup */
33986 snprintf( s->sid, sizeof(s->sid)-1, "%03d", i);
33987 snprintf( s->call, sizeof(s->call)-1, "C%03d", i);
33988 scan_pos = i;
33990 fprintf( stderr, SCP );
33991 show_clock();
33992 fprintf( stderr, "\033[3;1H");
33994 /* force frequency change */
33995 scan_freq = 0;
33997 sigavg = signum = 0;
33998 sigmax = 9;
34000 /* i2c hardware fix for dvb hd3000 driver means no more CPU pig signal scan */
34002 for ( j = 0; j < sigmax; j++ ) {
34004 /* control c check, user complained it's unstoppable! */
34005 key = console_getch();
34007 scan_sig = ~0;
34008 get_signal_lock( s );
34010 show_signal( s, i );
34011 fprintf( stderr, "\n" );
34013 /* 33% or greater */
34014 if ((0 != s->lock) && (s->strength > 33)) {
34015 sigavg += s->strength;
34016 signum++;
34019 fprintf( stderr,
34020 "\033[1;42H" BW
34021 " but only saw TV!" BN );
34022 fprintf( stderr, "\033[%d;1H", 3+j );
34025 if ( (signum > 2) && (0 != s->lock) && (s->strength > 70)) {
34026 fprintf( stderr, CCE );
34027 break;
34031 fprintf( stderr, RCP) ;
34033 /* avoid divide by zero */
34034 if (signum < 1) signum = 1;
34035 sigavg /= signum;
34037 /* 33% or greater */
34038 if (sigavg > 33) {
34039 scan_list[scan_idx++] = i;
34040 fprintf(stderr, "\033[16;1HFOUND:");
34042 for (k = 0; k < scan_idx; k++)
34043 fprintf(stderr, " %3d", scan_list[k] );
34044 } /* else fprintf( stderr, " "); */
34047 /* no reason to keep running, no stations were found */
34048 if (scan_idx < 1) {
34049 fprintf( stderr, BR "\033[24;1H ... no sign of life.\n" BN );
34050 close_device( WHO );
34051 console_reset();
34052 exit(1);
34055 fprintf( stderr, "\033[1;42H" BW " but only saw TV!" BN );
34056 fprintf( stderr, "\033[2;1H\033[J" );
34059 fprintf( stderr,
34060 "\nLCN PTC Status Time TSID Callsign "
34061 "Signal%% Error%% Errors Packets NULLs\n");
34063 f = 0;
34064 m = 0;
34066 get_time();
34067 scan_tnow = tnow;
34069 clock_gettime( clock_method, &scan_start );
34071 for (i=0; i < scan_idx; i++) {
34072 if (f >= SF_MAX) {
34073 console_reset();
34074 fprintf( stderr, BW "\n\n\n recompile with larger SF_MAX\n");
34075 exit(1);
34076 break;
34079 key = console_getch(); /* control c check, user complaint */
34080 s = &ptc[scan_list[i]].sig;
34081 get_time();
34082 fprintf( stderr, "%3d %3d",
34083 i, scan_list[i] );
34085 memset( chan_name, 0, sizeof(chan_name) );
34086 scan_pos = i;
34087 cap_chan = scan_list[i];
34088 pgm.chan = cap_chan;
34090 arg_capture = 3;
34092 fprintf( stderr, " stream");
34094 /* silence */
34095 arg_quiet = ~0;
34097 display_stat = 0;
34098 atsc_stt = 0;
34100 /* make sure channel gets set */
34101 /* scan_freq = 0; */
34103 cap_now = CAP_INFO;
34104 ts_capture();
34106 /* TESTME: needed? */
34107 while (0 != (pid_o | pid_i)) {
34108 // char b;
34109 nanosleep(&console_read_sleep, NULL);
34110 // b = console_getch();
34113 utsets &= 7;
34115 /* Thanks to Peter Knaggs for finding this divide by zero crash. */
34116 if (0 == utsets) utsets = 1;
34118 /* ok to yammer now */
34119 arg_quiet = 0;
34120 aprintf( stderr, " %3ds", utsets);
34122 epc = pkt.errors * 100;
34124 if (0 == pkt.count) pkt.count = 1; /* avoid /0 */
34125 epc /= pkt.count;
34127 if (epc > 99) epc = 100;
34129 /* name is blank? */
34130 if (0 == arg_cable) {
34131 if (0 == *chan_name ) {
34132 fprintf( stderr, "NO CALLSIGN FOUND\n");
34133 } else {
34134 /* name isn't blank */
34135 snprintf( s->call, 8, "%s", chan_name);
34136 snprintf( s->sid, 4, "D%02d", s->major);
34137 fprintf( stderr, " %04X %-8s %3d%% %3d%% %6d %7d %7d",
34138 vc[0].tsid, s->call, s->strength, epc, pkt.errors,
34139 pkt.count, pkt.null );
34142 fprintf( stderr, "\n");
34144 /***************************************************** ATSC Terrestrial */
34145 if (0 == arg_cable) {
34146 n = 0;
34147 for (j = 0; j < vc_ncs; j++) {
34149 if (0 == vc[j].pn) continue;
34150 if (0xFFFF == vc[j].pn) continue;
34152 // fprintf( stderr, "%s %02d.%d %s %s\n", WHO, s->ptc, vc[j].pn, vc[j].name, vc[j].cname);
34153 // continue;
34155 sf[f].ptc = s->chan;
34156 sf[f].pn = vc[j].pn;
34157 sf[f].hd = 0;
34158 sf[f].tsid = vc[j].tsid;
34159 tf = find_tsidx( sf[f].tsid, sf[f].pn, WHO );
34161 /* add to tsid list if not already there.
34162 callsign is TS-TSID if no vc name found. sid is pgm # */
34163 if (tf < 0) {
34164 t = &tsids[tsidx];
34165 t->tsid = sf[f].tsid;
34166 t->ptc = s->chan;
34167 t->pn = sf[f].pn;
34169 *t->call = 0;
34171 /* user may still have to edit callsign & sid in the config file */
34172 if (0 == *t->call)
34173 snprintf(t->call, 8, "%s", vc[j].name);
34175 if (0 == *t->call)
34176 snprintf(t->call, 8, "%s", vc[j].cname);
34178 /* fall back to tsid for callsign if no VCT or Component Name */
34179 if (0 == *t->call)
34180 snprintf(t->call, 8, "TS-%04X", t->tsid);
34182 tsidx++;
34183 if (tsidx >= TSID_MAX) break;
34185 sf[f].tsix = find_tsidx( sf[f].tsid, sf[f].pn, WHO );
34187 fprintf( stderr, "%7s Pgm # %5d %04X ",
34188 "", vc[j].pn, vc[j].tsid );
34190 if (sf[f].tsix >= 0) {
34191 t = &tsids[ sf[f].tsix ];
34192 fprintf( stderr, "%-7s %-3s", t->call, t->sid);
34193 } else {
34194 fprintf( stderr, "%-7s %-3s", s->call, s->sid );
34196 fprintf( stderr, " ES# %d:", vc[j].vctes);
34198 /* only check first two ES in each program map entry */
34199 // for (k = 0; k < vc[j].vctes; k++)
34200 for (k = 0; k < 2; k++)
34202 char dr[16];
34204 if (2 == vc[j].estype[k]) {
34206 if (vc[j].hres > 1000) sf[f].hd = 1;
34208 snprintf(dr, sizeof(dr), " %cD @ %dx%d",
34209 (1==sf[f].hd)?'H':'S',
34210 vc[j].hres, vc[j].vres);
34212 fprintf(stderr, "%-15s", dr);
34215 #ifdef USE_NUMERIC_ES
34216 fprintf( stderr, " %02X #%5d",
34217 vc[j].estype[k], pids[ vc[j].espid[k] ] );
34218 #else
34219 if (0x81 == vc[j].estype[k]) {
34220 fprintf( stderr, " AC3");
34222 /* langcode moved inside audio. video has langcode too but not used much */
34223 if (0 != *vc[j].eslang[k])
34224 if (' ' != *vc[j].eslang[k])
34225 fprintf( stderr, "-%s", vc[j].eslang[k]);
34228 if (0 == pids[vc[j].espid[k]]) {
34229 fprintf( stderr, " NONE");
34230 } else {
34231 fprintf( stderr, " %02d%%",
34232 (100 * pids[ vc[j].espid[k] ])/pkt.count);
34234 #endif
34237 n++;
34238 f++;
34239 fprintf(stderr, "\n");
34241 } else {
34242 n = 0;
34244 /********************************* Cable, only PAT + PMT to work with */
34246 for (j = 0; j < pat_ncs; j++) {
34248 /* bogus or missing program # */
34249 if (0 == pa[j].pn) continue;
34250 if (0xFFFF == pa[j].pn) continue;
34251 /* scrambled */
34252 if (0 != pa[j].ca) continue;
34253 /* no video ES */
34254 if (2 != pa[j].estype[0]) continue;
34255 /* fewer than 2 ES */
34256 if (pa[j].pmtes < 2) continue;
34258 /* video ES has bandwidth too small to be SD */
34259 if (2 == pa[j].estype[0])
34260 if (pids[ pa[j].espid[0] ] < 500) continue;
34262 sf[f].ptc = s->chan;
34263 sf[f].pn = pa[j].pn;
34264 sf[f].hd = 0;
34265 sf[f].tsid = pa[j].tsid;
34266 tf = find_tsidx( sf[f].tsid, sf[f].pn, WHO );
34268 /* add to tsid list if not already there,
34269 callsign is TSID if no vc name found. sid is ptc # */
34270 if (tf < 0) {
34271 t = &tsids[tsidx];
34272 t->tsid = sf[f].tsid;
34273 t->ptc = s->chan;
34274 snprintf( t->sid, 4, "%03d", t->ptc);
34275 t->pn = sf[f].pn;
34277 *t->call = 0;
34278 /* user may still have to edit callsign & sid in the config file */
34279 if (0 == *t->call)
34280 snprintf(t->call, 8, "%s", vc[j].name);
34282 if (0 == *t->call)
34283 snprintf(t->call, 8, "%s", vc[j].cname);
34285 /* fall back to tsid for callsign if no VCT or Component Name */
34286 if (0 == *t->call)
34287 snprintf(t->call, 8, "TS-%04X", t->tsid);
34289 tsidx++;
34290 if (tsidx >= TSID_MAX) {
34291 fprintf(stderr, "\nTSID_MAX exceeded\n");
34292 break;
34296 tf = find_tsidx( sf[f].tsid, sf[f].pn, WHO );
34297 if (tf < 0) break;
34299 sf[f].tsix = tf;
34301 fprintf( stderr, "%7s Pgm # %5d %04X ",
34302 "", pa[j].pn, pa[j].tsid );
34304 t = &tsids[ tf ];
34305 fprintf( stderr, "%-7s %-3s", t->call, t->sid);
34307 /* don't need to see this on the listing */
34308 fprintf( stderr, " ES# %d:", pa[j].pmtes);
34310 /* one video, one audio */
34311 for (k = 0; k < 2; k++)
34313 if (2 == pa[j].estype[k]) {
34314 char dr[16];
34315 if (pa[j].hres > 1000) sf[f].hd = 1;
34316 snprintf( dr, sizeof(dr), " %cD @ %dx%d",
34317 (0==sf[f].hd)?'S':'H',
34318 pa[j].hres, pa[j].vres);
34319 fprintf( stderr, "%-15s", dr);
34322 #ifdef USE_NUMERIC_ES
34323 fprintf( stderr, " %02X #%5d",
34324 pa[j].estype[k], pids[ pa[j].espid[k] ] );
34325 #else
34326 if (0x81 == pa[j].estype[k]) {
34327 fprintf( stderr, " AC3");
34329 /* langcode moved inside audio. video has langcode too but not used much */
34330 if (0 != *pa[j].eslang[k])
34331 if (' ' != *pa[j].eslang[k])
34332 fprintf( stderr, "-%s", pa[j].eslang[k]);
34335 if ( 0 == pids[pa[j].espid[k]] ) {
34336 fprintf( stderr, " NONE");
34337 } else {
34338 fprintf( stderr, " %02d%%",
34339 (100 * pids[ pa[j].espid[k] ])/pkt.count);
34341 #endif
34344 n++;
34345 f++;
34346 fprintf( stderr, "\n");
34350 fprintf( stderr, "\n");
34351 m += n;
34353 if (0 != atsc_stt)
34354 aprintf( stderr, "ATSC time for LCN %d PTC %d is %ld %s\n",
34355 i, scan_list[i], atsc_stt, ctime( &atsc_stt ) );
34357 /* store the time if it is non-zero, adjusted for 3s capture time per loop */
34358 if (0 != atsc_stt) atimes[i] = atsc_stt - (i * 3);
34360 /* end of scan_idx */
34363 clock_gettime( clock_method, &scan_stop );
34364 time_diff( &scan_diff, &scan_start, &scan_stop );
34366 clock_gettime( clock_method, &find_stop );
34367 time_diff( &find_diff, &find_start, &find_stop );
34369 if (NULL == getenv("TZ"))
34370 fprintf(stderr, "No TZ environment variable set. "
34371 "See man page tzselect(8).\n");
34372 j = 0;
34373 atime_avg = 0LL;
34374 for (i = 0; i < 128; i++) {
34375 if (atimes[i] < 1) continue;
34376 j++;
34377 atime_avg += atimes[i];
34380 if (0 == j) j = 1;
34381 atime_avg /= j;
34383 /* At the end of the station cap loop, even with good local ATSC STT averages
34384 computed from good data samples adjusted to the start of cap loop, it will
34385 now be at least scan_diff behind the current time. The point will be moot
34386 if the stations are very far out of time sync they will ruin the averaging.
34387 More processing will be needed to throw out the obvious bogus values.
34389 atime_avg &= 0xFFFFFFFF;
34390 atime_av = atime_at = atime_hr = atime_avg;
34392 /* force to nearest hour */
34393 if (atime_hr > 0) {
34394 atime_hr /= SECHR;
34395 atime_hr *= SECHR;
34398 fprintf( stderr, "Scan loop started %ld %s",
34399 scan_tnow, ctime(&scan_tnow) );
34401 fprintf( stderr, "ATSC Average time %ld %s",
34402 atime_av, ctime(&atime_av) );
34404 atime_at += scan_diff.tv_sec;
34405 fprintf( stderr, "ATSC Average +%3lds %ld %s",
34406 scan_diff.tv_sec, atime_at, ctime(&atime_at) );
34408 get_time();
34409 fprintf( stderr, "Local Current time %ld %s\n",
34410 tnow, ctime(&tnow) );
34412 /* maybe rename this to USE_LIVE_CD_ATSC_STT_PLUS_NTP */
34413 #ifdef USE_ATSC_NTP
34414 if (0 != euid) {
34415 fprintf(stderr, "Only root can set system time.\n");
34416 } else {
34417 char *nt;
34418 struct timeval atime_tv;
34420 /* compute average of reported ATSC STTs and truncate to nearest hour */
34422 /* get ntp time for seconds.
34423 if supervisor, correct hours with ATSC average then add NTP seconds
34424 if user, tell user to set the TZ variable!
34426 // nt = "ntpdate pool.ntp.org &>/dev/null";
34427 nt = "ntpdate pool.ntp.org";
34428 k = system( nt );
34429 if (0 == k) {
34430 get_time();
34431 } else {
34432 fprintf(stderr, "Can't set NTP time via \042%s\042\n", nt);
34433 fprintf(stderr, "Falling back to ATSC average time\n");
34434 atime_hr = atime_av;
34436 memset(&atime_tv, 0, sizeof(atime_tv));
34438 atime_tv.tv_sec = atime_hr;
34440 if (0 == k) {
34441 atime_tv.tv_sec += (tnow % 3600);
34442 fprintf( stderr, "ATSC Hour + NTP seconds is %ld %s",
34443 atime_tv.tv_sec, ctime(&atime_tv.tv_sec) );
34445 settimeofday( &atime_tv, NULL );
34447 #endif
34448 fprintf( stdout, "\nFound %d VCs in %d PTCs in %ld.%09ld seconds.\n\n",
34449 m, scan_idx, find_diff.tv_sec, find_diff.tv_nsec);
34451 *out_name = 0;
34453 #ifdef USER_TEST
34454 show_mem_use();
34455 console_reset();
34456 exit(0);
34457 #endif
34458 /* ask user to save config */
34459 fprintf( stderr, "\nSave configuration" BW " (y/n)? " BN );
34460 ui_save = noyes();
34461 if (0 == ui_save) {
34462 console_reset();
34463 exit(0);
34466 /* ask user to save config */
34467 fprintf( stderr,"\nEnable auto EPG for all stations" BW " (y/n)? " BN);
34468 ui_epg = noyes();
34470 /* most users will want the HD stations only */
34471 fprintf( stderr, "\nKeep non-HDTV stations" BW " (y/n)? " BN );
34472 ui_sd = noyes();
34474 chdir(cfg_path);
34475 snprintf(cfg_name, sizeof(cfg_name), "atscap%d.conf", arg_devnum);
34476 o = fopen(cfg_name, "w");
34477 if (NULL == o) {
34478 fprintf(stderr, CLS "Can not open %s for write.\n", cfg_name);
34479 console_exit(1);
34482 fprintf( o, "# This file will be over-written after startup\n" );
34483 fprintf( o, "F%d\n", arg_frtable);
34485 /* populate HD detected stations first with TSID list info */
34486 j = 0;
34487 for (i = 0; i < m; i++) {
34488 if (0 == sf[i].hd) continue;
34490 tf = find_tsidx( sf[i].tsid, sf[i].pn, WHO );
34491 if (tf < 0) break;
34493 t = &tsids[ tf ];
34494 fprintf( o, "C%03d", t->ptc );
34496 if (0 == *t->call)
34497 snprintf( t->call, 8, "TS-%04X", 0xFFFF & t->tsid);
34498 fprintf( o, ":%s", t->call );
34500 if (0 == *t->sid) {
34501 snprintf( t->sid, 4, "%03d", t->ptc);
34502 t->sid[3] = 0;
34504 fprintf( o, ":%s", t->sid );
34505 fprintf( o, ":%d:%u:0", ui_epg, t->pn );
34506 fprintf( o, " # HD\n" );
34507 j++;
34511 /* populate SD if user wants to keep them */
34512 if (0 != ui_sd) {
34513 for (i = 0; i < m; i++) {
34514 if (0 != sf[i].hd) continue;
34516 tf = find_tsidx( sf[i].tsid, sf[i].pn, WHO );
34517 if (tf < 0) break;
34519 t = &tsids[ tf ];
34520 fprintf( o, "C%03d", t->ptc );
34522 if (0 == *t->call)
34523 snprintf( t->call, 8, "TS-%04X", 0xFFFF & t->tsid);
34524 fprintf( o, ":%s", t->call );
34526 if (0 == *t->sid) {
34527 snprintf( t->sid, 4, "%03d", t->ptc);
34528 t->sid[3] = 0;
34530 fprintf( o, ":%s", t->sid );
34531 fprintf( o, ":%d:%u:0", ui_epg, t->pn );
34532 fprintf( o, " # SD\n" );
34533 j++;
34537 fprintf( o, "\n" );
34539 fflush(o);
34540 fclose(o);
34542 save_tsids();
34544 /* user can stop it after save config to inspect the result */
34545 fprintf( stderr, "\nRun %s now " BW "(y/n)? " BN, NAME );
34546 ui_run = noyes();
34547 if (0 == ui_run) {
34548 close_device( WHO );
34549 console_reset();
34550 exit(0);
34553 /* console exit is not needed anymore? */
34554 arg_scan = 0;
34555 arg_capture = 0;
34556 *out_name = 0;
34557 return;
34562 /* static */
34563 void
34564 usage ( void )
34566 fprintf( stdout, "%s-%s %s %s\n", NAME, VERSION, COPYRIGHT, EMAIL);
34567 fprintf( stdout, "This code is released under the %s Version 2\n", LICENSE);
34568 fprintf( stdout, "New versions may be found here: %s\n\n", WEBPAGE);
34569 fprintf( stdout, "%s", usehelp);
34570 fprintf( stdout, "%s", usehelpx);
34571 fprintf( stdout, "\nThe following keys are defined for console mode:\n\n");
34572 fprintf( stdout, "%s\n", keyhelp);
34573 fprintf( stdout, "%s", usehelpshort);
34574 exit(0);
34577 #ifdef USE_MCAST
34578 /* -u 224-239.x.x.x:1-65535
34579 bogus ip or port 0 disables multicast */
34580 /* static */
34582 parse_arg_mcast ( char *a )
34584 int ok;
34585 char m[32];
34586 char *t;
34587 unsigned int s, p;
34588 struct in_addr mc_addr;
34590 memset( arg_mcaddr, 0, sizeof(arg_mcaddr) );
34591 arg_mcport = 0;
34593 astrncpy( m, a, sizeof(m) );
34594 t = strrchr( m, ':');
34595 if (NULL == t) {
34596 dvrlog( LOG_INFO, "MCAST UDP port missing" );
34597 return -1;
34599 *t = 0;
34600 t++;
34601 p = atoi( t );
34602 if ( (p < 1) || (p > 65535) ) {
34603 dvrlog( LOG_INFO, "MCAST UDP port invalid");
34604 return -1;
34606 if (strlen(m) > 15) {
34607 dvrlog( LOG_INFO, "MCAST IP address too long");
34608 return -1;
34610 t = strchr( m, '.' );
34611 if (NULL == t) {
34612 dvrlog( LOG_INFO, "MCAST IP address bad format");
34613 return -1;
34615 *t = 0;
34616 s = atoi( m );
34617 if ( (s < 224) || (s>239) ) {
34618 dvrlog( LOG_INFO, "MCAST IP adddress error");
34619 return -1;
34621 *t = '.';
34623 /* overkill? */
34624 ok = inet_aton( m, (struct in_addr *)&mc_addr );
34625 if (0 == ok) {
34626 dvrlog( LOG_INFO, "MCAST IP address bad");
34627 return -1;
34630 /* considered ok if gets to here */
34631 astrncpy( arg_mcaddr, m, sizeof(arg_mcaddr) );
34632 arg_mcport = p;
34633 return 0;
34636 #endif
34639 #if USE_DST_CHECK
34640 /* This is a quick check of the new DST rules */
34641 void
34642 tz_check( void )
34644 int i,j;
34645 get_time();
34646 j = utsnow;
34647 j /= 3600;
34648 j *= 3600;
34650 for (i = j; i < j + (35 * 86400); i += 3600 ) {
34651 memcpy( &tloc1, localtime( (time_t *)&i ), sizeof( tloc1 ) );
34652 fprintf( stdout, "localtime isdst %d %s",
34653 tloc1.tm_isdst, ctime( (time_t *)&i ) );
34655 exit(0);
34657 #endif
34660 #ifdef USE_PARSE_ENV
34661 /* These are parsed before parse args to set defaults. CLI will override */
34662 /* DTV_ prefix:
34663 PATH out_path
34664 HOST arg_whost
34665 MASK arg_wmask
34666 PORT arg_wport
34667 SORT idx_sort
34669 /* static */
34670 void
34671 parse_envars ( void )
34673 char *p;
34674 p = getenv("DVR_PATH");
34675 if (NULL != p) {
34676 strncpy( out_path, p, sizeof(out_path));
34678 p = getenv("DVR_HOST");
34679 if (NULL != p) {
34680 strncpy( arg_whost, p, sizeof(arg_whost));
34683 p = getenv("DVR_PORT");
34684 if (NULL != p) {
34685 arg_wport = 0xFFFF & atoi(p);
34688 p = getenv("DVR_SORT");
34689 if (NULL != p) {
34690 idx_sort = 0xF & atoi(p);
34693 /* TODO: unicast UDP */
34694 p = getenv("DVR_UUDP");
34695 if (NULL != p) {
34698 /* TODO: multicast UDP */
34699 p = getenv("DVR_MUDP");
34700 if (NULL != p) {
34703 /* TODO: FIFO size, faster machines can reduce alloc? */
34704 p = getenv("DVR_FIFO");
34705 if (NULL != p) {
34708 #endif
34711 /* verify luser has access to things it will use. called by end of parse args
34713 /* static */
34714 void
34715 test_luser ( void )
34717 struct statfs ds;
34718 struct stat fs;
34719 FILE *f;
34721 char *d;
34722 char *e = "";
34723 char r[256]; /* name of resource that failed */
34724 char *n = r;
34725 int ok, z;
34727 /* -R re-attach doesn't need any checks done except screen access */
34728 if ( (0 != arg_screen) && (0 != arg_sattach) ) return;
34731 aprintf(stderr, CLS BN "Checking access rights...\n");
34733 ok = 0;
34734 z = sizeof(r) - 1;
34736 memset(r, 0, z);
34738 while(1) {
34741 /* if not dummy or test, do device check to see if it exists */
34742 if ((0 == arg_dummy) && (0 == test_mode)) {
34744 /* DVB device names */
34745 /* new udev format is dvb0.dvr0 */
34747 ok = test_var_procpid(arg_devnum, 1);
34748 if (0 != ok) {
34749 snprintf(r, z, "%s%d", NAME, arg_devnum);
34750 e = "instance already running";
34751 break;
34754 d = "/dev/dvb%d.%s";
34755 n = fe_name;
34757 snprintf(fe_name, sizeof(fe_name)-1,
34758 d, arg_devnum, "frontend0" );
34759 ok = stat(fe_name, &fs);
34761 /* try old udev format /dev/dvb/adapter0/frontend0 if new format failed */
34762 if (0 != ok) {
34763 d = "/dev/dvb/adapter%d/%s";
34764 aprintf( stderr, "failed %s\n", n);
34765 snprintf(fe_name, sizeof(fe_name)-1,
34766 d, arg_devnum, "frontend0" );
34768 ok = stat( fe_name, &fs );
34769 if (ok < 0) {
34770 e = "device doesn't exist";
34771 break;
34775 aprintf( stderr, "found %s\n", fe_name);
34778 snprintf( dmx_name, sizeof(dmx_name),
34779 d, arg_devnum, "demux0" );
34780 snprintf( dvr_name, sizeof(dvr_name),
34781 d, arg_devnum, "dvr0" );
34782 snprintf( in_name, sizeof(in_name),
34783 d, arg_devnum, "dvr0" );
34785 ok = open(fe_name, O_RDWR);
34786 aprintf( stderr, "open %d %s\n", ok, fe_name);
34787 if (ok < 3) {
34788 n = fe_name;
34789 e = "device in use";
34790 break;
34792 close(ok);
34794 ok = open(dmx_name, O_RDWR);
34795 aprintf( stderr, "open %d %s\n", ok, dmx_name);
34796 if (ok < 3) {
34797 n = dmx_name;
34798 e = "device in use";
34799 break;
34801 close(ok);
34803 ok = open(dvr_name, O_RDONLY);
34804 aprintf( stderr, "open %d %s\n", ok, dvr_name);
34805 if (ok < 3) {
34806 n = dvr_name;
34807 e = "device in use";
34808 break;
34810 close(ok);
34811 aprintf( stderr, "using dvb%d\n", arg_devnum);
34813 /* TODO: add file permission check here for -r mode:
34814 in_name O_RDONLY
34817 #ifdef USE_DEVOCHK
34818 // abbreviated check, device name setup only
34819 ok = 0;
34820 e = "";
34821 break;
34822 #endif
34825 n = r;
34827 /* check for /etc/atscap/ directory writable */
34828 snprintf( r, z, "/etc/%s/%s%d.chk", NAME, NAME, arg_devnum);
34829 f = fopen(r, "w");
34830 if (NULL == f) { ok = -1; e = "can't write"; break; }
34831 fclose(f);
34832 ok = remove( r );
34833 if (ok < 0) { e = "can't delete"; break; }
34835 /* create /var/run/atscap if needed
34836 TESTME: does it ever get to here?
34838 snprintf( r, z, "/var/run/%s", NAME);
34839 ok = statfs( r, &ds );
34840 if (ok < 0) {
34841 ok = mkdir( r, 0777 );
34842 if (ok < 0) { e = "mkdir"; break; }
34844 aprintf( stderr, "vardir exists %s\n", r);
34846 /* check for /var/run/atscap/ directory writable */
34847 snprintf( r, z, "/var/run/%s/%s%d.chk", NAME, NAME, arg_devnum);
34848 f = fopen(r, "w");
34849 if (NULL == f) { ok = -1; e = "can't write"; break; }
34850 fclose(f);
34851 ok = remove( r );
34852 if (ok < 0) { e = "can't delete"; break; }
34854 /* check for out path and out path + cut */
34855 snprintf( r, z, "%s", out_path);
34856 ok = statfs( r, &ds );
34857 if (ok < 0) {
34858 ok = mkdir( r, 0777 );
34859 if (ok < 0) { e = "mkdir"; break; }
34861 aprintf( stderr, "capdir exists %s\n", r);
34863 /* capture and cut directory check */
34864 snprintf( r, z, "%s%s%d.chk", out_path, NAME, arg_devnum);
34865 f = fopen( r, "w");
34866 if (NULL == f) { ok = -1; e = "write"; break; }
34867 fclose(f);
34868 ok = remove( r );
34869 if (ok < 0) { e = "delete"; break; }
34870 aprintf( stderr, "capdir rwd %s\n", r);
34872 #ifdef USE_WWW
34873 /* cap index won't generate unless cap index.html exists */
34874 snprintf( r, z, "%sindex.html", out_path);
34875 f = fopen( r, "w");
34876 if (NULL == f) { ok = -1; e = "write"; break; }
34877 fclose(f);
34878 #endif
34880 /* XFS_SUPER_MAGIC 0x58465342 */
34881 // if (ds.f_type != XFS_SUPER_MAGIC) {
34882 if (ds.f_type != 0x58465342) {
34883 aprintf( stderr, "XFS handles capturing files better.\n" );
34886 snprintf( r, z, "%spg", out_path);
34887 ok = statfs( r, &ds );
34888 if (ok < 0) {
34889 ok = mkdir( r, 0777 );
34890 if (ok < 0) { e = "root needs to: mkdir"; break; }
34892 aprintf( stderr, "epgdir exists %s\n", r);
34894 snprintf( r, z, "%spg/img", out_path);
34895 ok = statfs( r, &ds );
34896 if (ok < 0) {
34897 ok = mkdir( r, 0777 );
34898 if (ok < 0) { e = "root needs to: mkdir"; break; }
34900 aprintf( stderr, "imgdir exists %s\n", r);
34903 snprintf( r, z, "%scut", out_path);
34904 ok = statfs( r, &ds );
34905 if (ok < 0) {
34906 ok = mkdir( r, 0777 );
34907 if (ok < 0) { e = "root needs to: mkdir"; break; }
34909 aprintf( stderr, "cutdir exists %s\n", r);
34911 #ifdef USE_WWW
34912 /* cut index won't generate unless cut index.html exists */
34913 snprintf( r, z, "%scut/index.html", out_path);
34914 f = fopen( r, "w");
34915 if (NULL == f) { ok = -1; e = "write"; break; }
34916 fclose(f);
34917 #endif
34919 /* XFS_SUPER_MAGIC 0x58465342 */
34920 // if (ds.f_type != XFS_SUPER_MAGIC) {
34921 if (ds.f_type != 0x58465342) {
34922 aprintf( stderr, "XFS handles cutting files better.\n" );
34925 snprintf( r, z, "%scut/%s%d.chk", out_path, NAME, arg_devnum);
34926 f = fopen( n, "w");
34927 if (NULL == f) { ok = -1; e = "write"; break; }
34928 fclose(f);
34930 ok = remove( r );
34931 if (ok < 0) { e = "delete"; break; }
34932 aprintf( stderr, "cutdir rwd %s\n", r);
34934 snprintf( r, z, "%s%s%d.chk", cfg_path, NAME, arg_devnum);
34935 f = fopen( n, "w");
34936 if (NULL == f) { ok = -1; e = "write"; break; }
34937 fclose(f);
34939 ok = remove( r );
34940 if (ok < 0) { e = "delete"; break; }
34941 aprintf( stderr, "cfgdir rwd %s\n", r);
34943 /* if -E option check for existence of ram directory and try writing to it */
34944 if (0 != arg_tmpfs) {
34945 snprintf( r, z, "%s%s", out_path, ram_path);
34946 ok = statfs( n, &ds );
34947 if (ok < 0)
34948 { e = "tmpfs has no mount point at"; break; }
34949 aprintf( stderr, "ramdir exists %s\n", r);
34951 /* TMPFS_MAGIC 0x01021994 */
34952 // if (ds.f_type != TMPFS_MAGIC)
34953 if (ds.f_type != 0x01021994)
34954 { ok = -1; e = "tmpfs not mounted at"; break;}
34956 /* is it usable? */
34957 snprintf( r, z, "%s%s%s%d.chk",
34958 out_path, ram_path, NAME, arg_devnum);
34960 f = fopen( r, "w");
34961 if (NULL == f)
34962 { ok = -1; e = "can't write"; break; }
34964 fclose(f);
34966 ok = remove( r );
34967 if (ok < 0)
34968 { e = "can't delete"; break; }
34970 aprintf( stderr, "tmpdir rwd %s\n", r);
34973 /* www port check */
34974 if (0 != arg_www) {
34975 if ((0 != euid) && (arg_wport < 1024)) {
34976 ok = -1;
34977 e = "TCP port must be > 1023 for luser";
34978 n = "";
34979 aprintf( stderr, "TCP port BAD %d\n", arg_wport);
34980 break;
34982 aprintf( stderr, "TCP port %d OK\n", arg_wport);
34985 ok = 0;
34987 break; /* one time through */
34990 if (0 != uid) {
34991 aprintf( stderr, "User mode may require renicing!\n");
34992 nanosleep( &msg_long_sleep, NULL );
34994 if (0 != ok) {
34995 aprintf( stderr, "Please correct this error:\n");
34996 aprintf( stderr, "%s %s", e, n );
34998 /* keep onscreen for a while in case -W screen mode */
34999 nanosleep( &msg_longer_sleep, NULL);
35000 aprintf( stderr, "exiting %s\n\n", NAME);
35001 exit(1);
35004 aprintf( stderr, "Everything seems OK\n");
35006 #ifndef USER_TEST
35007 if (0 != euid)
35008 nanosleep( &msg_longer_sleep, NULL );
35009 #endif
35011 aprintf( stderr, CLS );
35015 /* getopt blech */
35016 /* static */
35017 void
35018 parse_args ( int argc, char ** argv)
35020 int c;
35021 int chan;
35022 char *t;
35024 c = 0;
35025 astrncpy( arg_name, argv[0], sizeof(arg_name) );
35027 #ifdef USE_PARSE_ENV
35028 parse_envars();
35029 #endif
35032 while ( (c = getopt( argc, argv,
35033 "ADENRSWadehklmnqtvxc:i:o:p:r:s:u:z:w:F:U:") )
35034 != EOF )
35036 switch(c) {
35038 /* ATSC strict mode */
35039 case 'A':
35040 arg_strict = ~0;
35041 break;
35043 /* start session detached with screen */
35044 case 'D':
35045 arg_sdetach = ~0;
35046 arg_screen = ~0; /* -D implies -W */
35047 break;
35049 /* Energy Saver mode uses RAM for EPG/config/log so HDD can auto-spindown.
35050 Compile with USE_POWERDOWN defined and it will also close the device.
35051 You'll need something like hdparm -S5 to tell the drive to auto-spindown,
35052 see man hdparm for other -S settings.
35054 case 'E':
35055 snprintf( ram_path, sizeof(ram_path), "%s", "ram/");
35056 arg_tmpfs = ~0;
35058 /* FIXME: test arg tmpfs after option loop so outpath/rampath can be logged */
35059 #ifdef USE_POWERDOWN
35060 dvrlog( LOG_INFO, "energy saver closes dvb at %ds idle",
35061 USE_POWERDELAY );
35062 #else
35063 dvrlog( LOG_INFO, "tmpfs only. Recompile with USE_POWERDOWN");
35064 #endif
35065 break;
35068 /* toggle boiler-plate default. defaut is on to pause 3s */
35069 /* NOTE: it needs at least 3 seconds to load microccode */
35070 case 'N':
35071 arg_nosplash = ~arg_nosplash;
35072 break;
35074 /* attach a previously running screen session */
35075 case 'R':
35076 arg_sattach = ~0;
35077 arg_screen = ~0; /* -R implies -W */
35078 break;
35080 /* Station scan and config file build */
35081 /* TODO? could use this to set how long to scan each channel */
35082 case 'S':
35083 arg_scan = ~0;
35084 break;
35086 /* frequency table select */
35087 case 'F':
35088 if (NULL != optarg) {
35089 arg_frtable = atoi( optarg );
35090 if (arg_frtable > 0) arg_cable = ~0;
35092 break;
35094 /* user:group */
35095 case 'U':
35096 if (NULL != optarg) {
35097 arg_setuid = atoi(optarg);
35098 t = strchr(optarg, ':');
35099 if (NULL != t) {
35100 t++;
35101 arg_setgid = atoi(t);
35104 break;
35106 /* use screen */
35107 case 'W':
35108 arg_screen = ~0;
35109 break;
35111 /* keep nulls for hardware players */
35112 case 'n':
35113 arg_nulls = ~0;
35114 break;
35116 case 'u':
35118 #ifdef USE_MCAST
35119 /* FIXME: needs device from parse arg mcast */
35120 if (NULL != optarg) {
35121 if (0 == parse_arg_mcast( optarg ) ) {
35122 udp_mcast = ~0;
35123 system( "route add -net 224.0.0.0 "
35124 "netmask 240.0.0.0 dev eth0");
35127 #else
35128 fprintf( stderr, CLS "Recompile with USE_MCAST\n");
35129 exit(1);
35130 #endif
35131 break;
35133 /* count packets for each frame type and keep track of sequence
35134 start offsets [renumber GOP too?] then:
35135 write frame data to to .tsf file
35136 write sequence offsets to .tss file
35138 case 'm':
35139 arg_frames = ~0;
35140 break;
35142 /* errors enumerated, increases size of atscap[0-3].log files */
35143 case 'e':
35144 arg_edetail = ~arg_edetail;
35145 break;
35147 /* version only */
35148 case 'v':
35149 fprintf( stdout, "%s%s", HOM, CCE );
35150 fprintf( stdout, "%s%s", welcome, BN );
35151 fprintf( stdout, "%s%s", boilerplate, BN );
35152 show_mem_use();
35153 exit(0); /* version boiler-plate etc and stop */
35154 break;
35156 /* built-in help */
35157 case 'h':
35158 usage();
35159 break;
35161 /* TARD mode (Test And Replay Demo) */
35162 case 'r':
35163 test_mode = ~0;
35164 arg_dummy = ~0;
35165 if (NULL != optarg) {
35167 getopt was broken last time tried r:: for optional
35168 if you don't supply this parameter, in_name doesn't change.
35169 dummy mode comes up reading /dev/dtv0 but no output
35171 snprintf( in_name, sizeof(in_name)-1, "%s", optarg);
35172 snprintf( out_name, sizeof(out_name)-1, "%s", optarg);
35173 snprintf( arg_dummyn, sizeof(arg_dummyn)-1, "%s", optarg);
35175 break;
35177 /* -a uses AOS check before capture, otherwise pretends AOS always passes */
35178 case 'a':
35179 arg_aos = ~0;
35180 break;
35182 /* FIXME: use QoS overall percentage
35183 zap file if this percentage of error seconds crossed */
35184 case 'z':
35185 arg_zapper = 0;
35186 if (NULL != optarg) {
35187 arg_zapper = atoi( optarg );
35188 if ( (arg_zapper < 0) || (arg_zapper > 100) )
35189 arg_zapper = 99; /* sanity limit */
35191 break;
35193 /* capture for n seconds */
35194 case 's':
35195 fprintf(stdout,
35196 "-s is deprecated and no longer supported.\n");
35197 exit(1);
35198 #if 0
35199 arg_capture = 0;
35200 sscanf( optarg, "%d", &arg_capture);
35201 /* arbitrary 4 hour limit on capture */
35202 if ( (arg_capture < 0) || (arg_capture > 14400) )
35203 arg_capture = 0;
35204 #endif
35205 break;
35207 /* input device -i0...3 or /dev/dtv0...3 */
35208 case 'i':
35209 arg_device = optarg;
35210 arg_devnum = 0;
35212 if ( arg_device != NULL ) arg_devnum = atoi( arg_device );
35213 break;
35215 /* output file */
35216 case 'o':
35217 arg_ofile = optarg;
35218 if (NULL != arg_ofile)
35219 astrncpy( out_name, optarg, sizeof(out_name) );
35220 break;
35222 /* output path */
35223 case 'p':
35224 arg_opath = optarg;
35226 if (NULL == arg_opath) break;
35227 if (strlen(arg_opath) > 1) {
35228 char *p;
35229 /* copy dir */
35230 strncpy( out_path, arg_opath, sizeof(out_path));
35231 p = out_path + (strlen(out_path)-1);
35233 /* add / to end if missing */
35234 if ('/' != *p++) {
35235 *p++ = '/';
35236 *p = 0;
35239 /* www and log needs copy too */
35240 strncpy( log_path, out_path, sizeof(log_path));
35241 strncpy( arg_wroot, out_path, sizeof(arg_wroot));
35243 break;
35245 /* config file path */
35246 case 'c':
35247 arg_cpath = optarg;
35248 if (NULL != arg_cpath)
35249 snprintf( cfg_path, sizeof(cfg_path)-1, "%s%s", arg_cpath,
35250 ( strrchr(arg_cpath, '/') == NULL) ? "/":"" );
35251 break;
35253 /* toggles for compiled defaults */
35255 /* filesystem ext2 delete time computation toggle */
35256 case 'f':
35257 arg_fastfs = ~arg_fastfs;
35258 break;
35260 /* DEBUG parse weekday timers:
35261 no timers loaded from cfg at startup
35262 IT WILL ERASE EXISTING TIMERS SO BEEWARY
35264 case 't':
35265 arg_notimers = ~arg_notimers;
35266 break;
35268 /* klutz mode, stop fingers from stopping program */
35269 case 'k':
35270 arg_nokb = ~arg_nokb;
35271 break;
35273 /* HD2000 LED sense for V4L ATSC using my customized pcHDTV driver 1.07 */
35274 /* also, read signal strength value during capture */
35275 case 'x':
35276 arg_extras = ~arg_extras;
35277 break;
35279 /* verbose logging */
35280 case 'l':
35281 arg_log = ~arg_log;
35282 break;
35284 /* no stdio */
35285 case 'd':
35286 arg_detach = ~arg_detach;
35287 scan_sig = 0; /* user request: -d does not scan */
35288 break;
35290 /* quiet mode, no output, but input is allowed, for [q]uit key */
35291 case 'q':
35292 arg_quiet = ~arg_quiet;
35293 break;
35295 case 'w':
35296 #ifdef USE_WWW
35297 if (NULL != optarg) {
35298 parse_www_bind( optarg );
35299 arg_www = ~0;
35301 #else
35302 fprintf( stderr, CLS "Recompile with USE_WWW.\n\n");
35303 exit(1);
35304 #endif
35306 break;
35310 /* end of toggles for compiled defaults */
35312 default:
35313 break;
35316 /* now that all the args are loaded, it's time to check a few things */
35318 snprintf( cfg_name, sizeof(cfg_name)-1, "%s%d.conf", NAME, arg_devnum );
35320 /* override arg_vapi based on available types
35321 2.6.12 has DVB and ATSC support, but
35322 prior to that is ATSC via V4Lx only
35325 /* last arg(s) used to be channel numbers to put in scan_list,
35326 but now last arg is channel to use for:
35327 -s scriptable single channel capture (cron users)
35329 if (0 != arg_capture) {
35330 chan = 0;
35331 if (optind < argc) {
35332 chan = atoi( argv[ optind ] );
35334 /* if it's valid, add it to the scan list */
35335 if ( (chan >= 0) || (chan < ptc_max) ) {
35336 arg_chan = chan;
35337 dvrlog( LOG_INFO,
35338 "-s caps channel %d for %d seconds",
35339 chan, arg_capture );
35340 } else {
35341 dvrlog( LOG_INFO, "-s invalid channel %d", chan);
35342 console_exit(1);
35347 test_luser();
35348 /* end of parse args */
35352 #ifdef USE_WWW
35353 /* This is the tiny threaded http server code based on my tinyhttp.c 0.5 */
35355 /* TODO
35356 Fix pipelining:
35357 Most browsers seem to have this defaulted to off. It non-issue for now
35358 since the keep-alive appears to be working better and faster. There is
35359 still an issue with returning a blank page but this is known to be
35360 from running out of threads. Click slower and all is good. :>
35362 Will look into using dynamic thread model instead of static model.
35363 The dynamic threads should allow pipelining to work, too.
35366 /* CHANGELOG:
35368 1.1 2007-Dec-29 fixed If-Modified-Since handling
35369 1.0 2006-Dec-20 keep-alive is working better now; added sig cgi
35370 0.9 2006-Sep-22 uintodot snprintf shorted; tts_s names; more media
35371 0.8 2006-Jul-29 layout done; dynamic href; toggle search; index zap
35372 0.7 2006-Jul-14 zap cap; w3c validator flags id=, mozilla is ok
35373 0.6 2006-Jul-12 toggle cgi spam/aged; multiple server hrefs; kasb?
35374 0.5 2006-Jul-02 add/remove timer cgi; keep alive still broken?
35375 0.4 2006-Jun-20 added keep alive but it seems broken?
35376 0.3 2006-Jun-11 fixed connect return check crash on rapid attempts
35377 0.2 2006-Jun-10 added structures to organize things better
35378 0.1 2006-Jun-08 add multi-thread support via pthreads, index.html
35381 /* *************************** http structures *************************** */
35383 struct tts_s { /* thread task state */
35384 pthread_t thttp; /* http thread pthread state */
35385 pthread_attr_t tattr; /* http thread attributes */
35387 pid_t tid; /* process id of thread */
35389 struct sockaddr_in remote_addr; /* remote socket IP address */
35390 struct sockaddr_in local_addr; /* local socket IP address */
35391 unsigned short local_port; /* local inbound port */
35392 int ka; /* requested keep alive seconds */
35393 unsigned int utska; /* keep alive expiration */
35395 long long rseek, /* file byte offset */
35396 obytes; /* TODO: current byte count */
35398 time_t imstime; /* if modified since time */
35400 int accepted, /* fd sock accepted */
35401 infile, /* fd file sendfile */
35402 rtype, /* request: HEAD 0, GET 1 */
35403 rnum, /* response numeric code */
35404 num, /* thread num 0 to ptmax - 1 */
35405 ramz, /* size of data if ram non-null */
35406 htver, /* http version 0=1.0, 1=1.1 */
35407 orate; /* TODO: bytes until osleep */
35409 /* FIXME: change back to 16 char allocation after removing port# */
35410 char remote_name[128], /* remote socket IP name */
35411 remote_number[32], /* remote IP num as text */
35412 local_name[128], /* local socket IP name */
35413 local_number[32], /* local IP num as text */
35414 tsbuf[WWW_TCP_MAX], /* file io buffer */
35415 request[WWW_TCP_MAX], /* http request */
35416 rfile[WWW_TCP_MAX - WWW_ROOT_MAX], /* requested file */
35417 reply[WWW_TCP_MAX], /* http reply */
35418 cgi[WWW_ROOT_MAX], /* data after ? in uri */
35419 cwd[WWW_ROOT_MAX], /* current working dir */
35420 file[WWW_TCP_MAX], /* full path name */
35421 date[32], /* file date */
35422 ctext[32], /* content-type text */
35423 ctype; /* cache type. 0 yes, nz no */
35425 /* not implemented yet */
35426 char *ram; /* non NULL replaces file data */
35427 struct timespec osleep; /* TODO: throughput throttle */
35431 struct wss_s { /* web server state */
35432 pthread_t tws; /* http_start pthread state */
35433 pthread_attr_t twsattr; /* http_start pthread attrs */
35435 struct tts_s *tss; /* http init callocs this */
35436 struct sockaddr_in bind_addr; /* bound address/INADDR_ANY */
35438 unsigned int addr, /* ipv4 address in 32 bits */
35439 mask; /* ipv4 mask in 32 bits */
35441 unsigned short port; /* www port address */
35442 short mtu; /* maximum tx block MTU - 36 */
35444 int ptuse, /* threads in use */
35445 ptmax, /* threads to spawn */
35446 ptz, /* size of one tts_s */
35447 sock, /* www main socket fd */
35448 bound, /* is port bound to socket? */
35449 bind_try, /* how many tries to bind? */
35450 sig; /* interrupt signal condition */
35452 char host[WWW_HOST_MAX], /* very long internet name */
35453 root[WWW_ROOT_MAX]; /* root from out_path or -p */
35456 /* content type:
35457 .extension, type name, val is category where 0 is cacheable reply */
35458 struct ct_s {
35459 char *ext;
35460 char *name;
35461 char val;
35464 #define ALLOW_MAX 8
35465 #define ALLOW_NAME_MAX 128
35466 struct allow_s {
35467 char name[ ALLOW_NAME_MAX ]; /* very long host name, or IP num */
35471 /************************ http globals begin ********************************/
35473 struct wss_s wss; /* web server status */
35474 /* type 0 is images and are browser cacheable, rest are no-cache */
35476 /* even after 10 years, all browsers only support JPEG, PNG and GIF images */
35477 struct ct_s content_type[] = {
35478 { ".png", "image/png", 0 }, /* 0 is image */
35479 { ".jpg", "image/jpg", 0 },
35480 { ".jpeg", "image/jpeg", 0 },
35481 { ".gif", "image/gif", 0 },
35483 { ".txt", "text/plain", 1 }, /* 1 is text */
35484 { ".log", "text/plain", 1 },
35485 { ".html", "text/html", 1 },
35486 { ".css", "text/css", 1 },
35488 /* default capture name is .ts, this will be the usual media name */
35489 { ".ts", "video/mpeg", 2 }, /* 2 is media */
35492 #if USE_WWW_SIGNALS
35493 /* signals */
35494 struct sigaction www_act2[32]; /* only the first 32 signals */
35495 #endif
35497 /* nanosleeps */
35498 struct timespec www_bind_sleep = {1, 0};
35499 struct timespec www_long_sleep = {10, 0};
35500 struct timespec www_tcreate_sleep = {0, 10000000};
35501 /* struct timespec www_write_sleep = {0, 1}; */
35503 int allow_max = 0;
35504 int ha_mt = 0; /* hosts.allow modtime */
35505 struct allow_s allow[ ALLOW_MAX ];
35507 /*************************** httpd globals end ******************************/
35509 /************************* httpd functions begin ****************************/
35510 /* static */
35511 void
35512 http_log ( int tnum, char *fmt, ... )
35514 struct wss_s *w;
35515 struct tts_s *t;
35516 char s[ LOG_CHARS ]; /* , *n; */
35517 va_list ap;
35519 w = &wss;
35520 if (NULL == w) return;
35522 if ((tnum < 0) || (tnum > w->ptmax)) {
35523 dvrlog( LOG_INFO, "%s thread number %d out of bounds");
35524 return;
35527 t = &w->tss[tnum];
35528 memset( s, 0, sizeof(s));
35530 /* Variable arg macro start, pass fmt and ap, variable arg macro end */
35531 va_start(ap, fmt);
35532 vsnprintf( s, sizeof(s)-1, fmt, ap );
35533 va_end(ap);
35535 /* Term at first NL */
35537 /* if (NULL != (n = strchr( s,'\n'))) *n = 0; */
35539 /* Don't log blank lines */
35540 if (0 != strlen(s))
35541 dvrlog( LOG_INFO, "t%02ds%02d %s", t->num, t->accepted, s);
35544 /* Reload the hosts allow list if the modtime of /etc/hosts.allow changes. */
35545 /* The format for hosts.allow, with text after "atscap:" stored:
35546 atscap: 1.2.3.
35547 atscap: .nop.org
35550 /* static */
35551 void
35552 http_load_allows ( void )
35554 FILE *f;
35555 struct allow_s *a;
35556 char fn[256], line[256], service[32], ip[128], *n, *ok;
35557 int i, z;
35558 struct stat fs;
35560 /* RAM tries to keep frequently checked items */
35561 if (0 != arg_tmpfs) {
35562 snprintf( fn, sizeof(fn), "%s%shosts.allow", out_path, ram_path);
35563 } else {
35564 strcpy( fn, "/etc/hosts.allow" );
35567 i = stat( fn, &fs);
35568 if (0 != i) {
35569 /* Peter Knaggs reported this error if no hosts allow exists at all */
35570 advrlog( LOG_INFO, "can't stat %s", fn);
35571 return;
35574 /* modify date unchanged, do nothing */
35575 if (fs.st_mtime == ha_mt) return;
35576 ha_mt = fs.st_mtime;
35578 /* modify date changed, open the changed file */
35579 f = fopen( fn, "r");
35580 if (NULL == f) {
35581 dvrlog( LOG_INFO, "can't open %s", fn);
35582 return;
35585 z = strlen( NAME );
35586 allow_max = 0;
35587 while (1) {
35588 ok = fgets( line, sizeof(line), f );
35589 if (NULL == ok) break;
35590 n = line;
35592 /* strip comment */
35593 if (NULL != (n = strchr(line, '#'))) *n = 0;
35594 /* strip NL */
35595 if (NULL != (n = strchr(line, '\n'))) *n = 0;
35597 /* nothing left keeps looping til eof */
35598 if (0 == n) continue;
35600 /* dvrlog( LOG_INFO, "allow line %s", line ); */
35601 sscanf( line, "%s %s", service, ip);
35603 /* only looking for "atscap:" entries, skip the rest */
35604 if (0 != strncmp(service, NAME, z))
35605 continue;
35607 advrlog( LOG_INFO, "allowing access to %s", ip );
35608 a = &allow[i];
35609 astrncpy( a->name, ip, ALLOW_NAME_MAX );
35610 i++;
35612 /* warn user and stop looping if limit is reached */
35613 if (i >= ALLOW_MAX) {
35614 dvrlog( LOG_INFO, "ALLOW_MAX limit is %d", ALLOW_MAX);
35615 break;
35619 fclose(f);
35620 allow_max = i;
35622 advrlog( LOG_INFO, "allow list has %d item%s",
35623 allow_max, (1 == allow_max)?"":"s");
35626 /* Compare the IP address against entries in /etc/hosts.allow to determine
35627 if the client broswer should be allowed to connect to the web server:
35629 Parameters:
35630 t current web server thread
35631 addr remote IP address to use for reverse DNS/hosts lookup
35633 Returns:
35634 0 if the remote is allowed to access this machine
35635 -1 if no access is allowed
35637 NOTE:
35638 This code does not use the functions listed in "man 3 hosts_access",
35639 but instead implements a subset of what is in "man 5 hosts_access".
35640 There is no keyword support for things like "ALL" or "LOCAL".
35641 "LOCAL" is implied, and "ALL" is a bad idea, band leeches, etc.
35643 /* static */
35645 http_test_allows ( struct tts_s *t, struct sockaddr_in *addr )
35647 int i, ok, z, tnum;
35648 char *name, *test, *dot;
35649 struct hostent *remote;
35651 tnum = t->num;
35652 /* LOCAL is implied */
35653 if (t->local_addr.sin_addr.s_addr == addr->sin_addr.s_addr)
35654 return 0;
35656 /* Track the modtime of /etc/hosts.allow, loads new if changed. */
35657 http_load_allows();
35659 /* AF_INET is the only supported type for gethostbyaddr */
35660 remote = gethostbyaddr( (char *)&addr->sin_addr.s_addr,
35661 sizeof( addr->sin_addr.s_addr),
35662 AF_INET );
35664 /* If the name didn't resolve, will have to compare against IP address. */
35665 if (NULL != remote)
35666 if (' ' != *remote->h_name)
35667 astrncpy( t->remote_name, remote->h_name, sizeof(t->remote_name));
35669 ahttp_log( tnum, "%s resolves to %s", t->remote_number, t->remote_name);
35671 z = 0;
35672 /* Compare partial names in /etc/hosts.allow to full name or IP address. */
35673 for (i = 0; i < allow_max; i++) {
35674 name = t->remote_name; /* set name pointer */
35675 test = allow[i].name; /* set test pointer */
35676 if (NULL == test) break; /* end of list? */
35678 /* If test starts with a dot, check last letters of name, not first. */
35679 dot = strchr( test, '.');
35680 if (dot == test) {
35682 /* Name should always be equal to or longer than test. If not, skip it. */
35683 z = strlen(name) - strlen(test);
35684 if (z < 0) continue;
35686 /* Adjust name pointer to test the last bytes instead of first bytes. */
35687 name += z;
35690 /* Test length is usually equal to length of test string, unless no dot. */
35691 z = strlen(test);
35693 /* If there is no dot in test string, it will compare for exact match. */
35694 if (NULL == dot) z = ALLOW_NAME_MAX;
35696 /* Exact match will allow access. */
35697 ok = strncasecmp( t->remote_number, test, z);
35698 if (0 == ok) {
35699 ahttp_log( tnum, "access allowed to addr %s", t->remote_number);
35700 return 0;
35703 /* Exact match will allow access. */
35704 ok = strncasecmp( name, test, z);
35705 if (0 == ok) {
35706 ahttp_log( tnum, "access allowed to name %s", t->remote_name);
35707 return 0;
35711 return -1;
35714 /* source, destination, mask, 0 is test passed, -1 is not passed */
35715 /* static */
35717 http_test_netmask ( unsigned int d, unsigned int s, unsigned int m )
35719 if (0 == m) return 0;
35721 /* TESTME: */
35722 if ( s == d ) return 0;
35724 advrlog( LOG_INFO, "%s", WHO );
35725 if ( (s & m) != (d & m) ) {
35727 /* Log the Wile E. Haxor bandwidth bums. */
35728 advrlog( LOG_INFO, "%08X %08X %08X DENY", d, s, m);
35729 return -1;
35731 advrlog( LOG_INFO, "%08X %08X %08X ALLOW", d, s, m);
35732 return 0;
35735 /* close accepted socket and log the close */
35736 /* static */
35737 void
35738 http_close ( struct tts_s *t, int *s, char *which )
35740 /* indicates logic error, want this logged */
35741 if (-1 == *s) {
35742 http_log( t->num, "already closed %s", which);
35743 return;
35746 ahttp_log( t->num, "close %s socket %d", which, *s);
35747 close( *s );
35748 *s = -1;
35751 /* static */
35752 void
35753 http_close_sockets( void )
35755 struct wss_s *w;
35756 struct tts_s *t;
35757 int i;
35759 dvrlog( LOG_INFO, "%s", WHO);
35760 w = &wss;
35762 /* shutdown thread sockets */
35763 for (i=0; i < w->ptmax; i++) {
35764 t = &w->tss[ i ];
35766 if (t->accepted > 2) {
35767 http_log( t->num, "closing thread socket %d",
35768 t->accepted);
35769 http_close( t, &t->accepted, "abort");
35771 t->accepted = -1;
35772 nanosleep(&console_read_sleep, NULL);
35775 /* shutdown main socket */
35776 if (w->sock > 2) {
35777 http_log( 0, "closing main socket %d", w->sock);
35778 close( w->sock );
35779 w->sock = -1;
35783 /* bind socket to port, 80 usually */
35784 /* static */
35786 http_bind_socket( struct wss_s *w )
35789 /* nothing above 224, everything else is fair game */
35790 if (223 < (w->addr>>24)) {
35791 dvrlog( LOG_INFO, "invalid IP address for http %s", w->host );
35792 return -1;
35795 /* try to make a streaming socket */
35796 w->sock = socket(AF_INET, SOCK_STREAM, 0);
35797 if (w->sock == -1) {
35798 dvrlog( LOG_INFO, "www socket() failed\n");
35799 return -1;
35800 /* http_c_exit(2); / * bind error returns 2 */
35803 /* bind to local port */
35804 w->bind_addr.sin_family = AF_INET;
35805 w->bind_addr.sin_port = htons(w->port);
35807 /* try any open interface */
35808 if (0 == w->addr) {
35809 w->bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
35810 } else {
35811 w->bind_addr.sin_addr.s_addr = htonl(w->addr);
35813 memset(&w->bind_addr.sin_zero, 0, 8);
35815 w->bound = -1;
35816 w->bind_try = 0;
35817 while (w->bound == -1) {
35818 w->bound = bind( w->sock,
35819 (struct sockaddr*) &w->bind_addr,
35820 sizeof(struct sockaddr));
35822 if (0 == w->bound) break;
35823 dvrlog( LOG_INFO, "bind try %d %08X:%08X:%u fail, sleep 10s",
35824 w->bind_try, w->addr, w->mask, w->port);
35825 fflush(stdin);
35826 nanosleep( &www_long_sleep, NULL);
35827 w->bind_try++;
35828 if (w->bind_try > 30) {
35829 break;
35830 /* http_c_exit(2); */
35834 if (-1 == w->bound) {
35835 dvrlog( LOG_INFO, "bind failed");
35836 close( w->sock );
35837 return -1;
35840 w->addr = ntohl( w->bind_addr.sin_addr.s_addr );
35842 /* will be either 0.0.0.0 (INADDR_ANY) to bind to all interfaces,
35843 or user supplied IP address/netmask to bind to a specific interface
35844 and/or limit connections to be only from a specific subnet.
35845 224 and above are reserved for UDP multicast, hey IPTV!
35847 A glipse of what IPTV could be can be found with the -u option, however
35848 there is no current way to enable UDP multicast from the EPG.
35850 memset( w->host, 0, WWW_HOST_MAX );
35851 /* default host name if nothing found */
35852 uintodot( w->host, w->addr );
35854 dvrlog( LOG_INFO,"TCP httpd %08X:%08X:%u root %s t%d",
35855 w->addr, w->mask, w->port, w->root, w->ptmax );
35857 /* let user know http is bound by setting [w] flag in EPG mask */
35858 refresh.estats = 1;
35859 return 0;
35862 /* static */
35864 http_socket_listen( struct wss_s *w, int backlog )
35866 time_t now;
35868 if ( -1 == ( listen( w->sock, backlog )) ) {
35869 http_log( 0, "listen() failed");
35870 return -1;
35871 /* http_c_exit(3); / * listen error returns 3 */
35873 now = time(NULL);
35874 return 0;
35877 /* Peter Knaggs requested sort by file date */
35878 /* if legend visible. uses headers above filenames to set sort order */
35879 /* static */
35880 void
35881 http_sort_index ( struct globsort_s *g, int count )
35883 int i,j, t;
35884 time_t t1;
35885 time_t t2;
35886 char *n1, *n2;
35887 struct globsort_s s;
35889 if (count < 2) return; /* if nothing to do */
35891 for (i=0; i < (count-1); i++) {
35892 for (j=i+1; j < count; j++) {
35893 t = 0;
35894 t1 = g[i].mt;
35895 n1 = g[i].name;
35897 t2 = g[j].mt;
35898 n2 = g[j].name;
35900 /* flag bit 0: 1 swap to mod time descending order */
35901 if (0 != (1 & idx_sort))
35902 if (t1 > t2) t = ~0;
35903 /* flag bit 1: 1 swap to mod time ascending order */
35904 if (0 != (2 & idx_sort))
35905 if (t1 < t2) t = ~0;
35906 /* flag bit 2: 1 swap to name ascending order */
35907 if (0 != (4 & idx_sort))
35908 if (0 < strncasecmp(n1, n2, 32)) t = ~0;
35909 /* flag bit 3: 1 swap to name descending order */
35910 if (0 != (8 & idx_sort))
35911 if (0 > strncasecmp(n1, n2, 32)) t = ~0;
35912 /* any swap to do? */
35913 if (0 != t) {
35914 memcpy( &s, &g[i], sizeof(s));
35915 memcpy( &g[i], &g[j], sizeof(s));
35916 memcpy( &g[j], &s, sizeof(s));
35923 /* builds index.html in current working directory of thread
35925 atscap0 atscap1 atscap2 atscap3 [cap] free% [cut] free%
35926 ---------------------------------------------------------
35927 PBS UPN FOX CBS ABC NBC WB ETH FUT AZH TUB ZJL UNI TEL
35928 ---------------------------------------------------------
35929 Size Date [+/zap] [log] [edit] Name
35931 4,634M 2007-Jan-06 22:06:44 [+/zap] [log] [edit] Farscape-0106-1200.00.ts
35933 '+' is record indicator red circle or zap if not a new file.
35934 log image or LOG indicates there is a capture log for it.
35935 edit image or EDIT will run xtscut on the file.
35936 name.ts is clickable as mpeg:// href.
35937 zap image or ZAP will delete the file.
35940 /* static */
35942 http_index_html3 ( struct wss_s *w, int tnum, char *caller )
35944 FILE *f; /* index.html file descriptor */
35945 glob_t globt; /* globt glob pointer */
35946 time_t now; /* current time */
35948 long long fz;
35950 char *globa, /* glob wildcard match in ascii */
35953 *ur, /* uri:// filler as mpeg:// */
35955 *st, /* sort type bitmask */
35956 *ei;
35958 char
35959 s[8192], /* html source text string */
35960 ti[256], /* html title text "index of /[cut/]" */
35961 zt[32], /* reusable file size text */
35962 ft[32], /* reusable file time text */
35963 /* reusable file names: */
35964 ln[WWW_ROOT_MAX], /* reusable capture log name */
35965 lu[WWW_TCP_MAX], /* reusable capture log url */
35966 xn[WWW_ROOT_MAX], /* reusable tsx name */
35967 cn[WWW_ROOT_MAX], /* reusable tsc name */
35968 d[WWW_TCP_MAX],
35969 o[WWW_TCP_MAX],
35970 x[WWW_TCP_MAX];
35973 int i, ok, /* counter and reusable return code */
35974 globnum,
35975 gc, /* total globs returned */
35976 fg, /* files globbed for sorting */
35977 chg,
35978 /* reusable file stat return codes: */
35979 tsx, /* 0 is tsx file exists */
35980 tsc, /* 0 is tsc file exists */
35981 clf; /* 0 is cap logfile exists */
35984 struct tm mt; /* reusable file mod time */
35985 struct tts_s *t; /* thread task status */
35986 struct globsort_s *globsort; /* reusable by each thread */
35988 struct stat64 fs, /* reusable file stats data: */
35989 ls, /* capture logfile stat */
35990 xs, /* tsx index file stat */
35991 cs; /* tsc cut file stat */
35994 while (EBUSY == pthread_mutex_trylock( &index_mutex )) return -1;
35995 globsort = NULL;
35996 fg = 0;
35998 t = &w->tss[ tnum ];
35999 i = 0;
36000 ok = -1;
36002 globa = "*.ts"; /* glob all .ts files */
36003 globnum = 0;
36005 /* media type include list by .ts extension */
36006 astrncpy( o, t->cwd, sizeof(o) );
36008 /* get path */
36009 p = NULL;
36010 if (strlen(o) > 1)
36011 p = strrchr( o, '/' );
36012 if (NULL != p) {
36013 *p='?';
36014 p = strrchr(o, '/');
36015 if (NULL != p) {
36016 p++;
36017 *p = 0;
36021 if (strlen(o) < 1)
36022 astrncpy( o, t->cwd, sizeof(o) );
36024 snprintf( d, sizeof(d), "%s%s", w->root, t->cwd );
36026 ok = chdir( d );
36027 if (ok != 0)
36029 dvrlog( LOG_INFO, "%s %s t%d chdir %d %s %s %s",
36030 WHO, caller, tnum, ok, d, w->root, t->cwd );
36031 ok = -1;
36032 goto http_index_exit;
36034 snprintf( x, sizeof(x)-1, "%sindex.html", d );
36035 advrlog( LOG_INFO, "x1 %s", x);
36037 ok = stat64(x, &fs);
36039 /* if file exists, don't overwrite if user write not set */
36040 if (0 == ok) {
36041 if (0 == (S_IWUSR & fs.st_mode)) {
36042 /* serve current file */
36043 dvrlog( LOG_INFO, "user can't update %s", x);
36044 ok = -1;
36045 goto http_index_exit;
36049 /* NOTE:
36050 glob on smbfs does not differentiate file vs dir.
36051 Also some odd issues with symlink display because not handled.
36052 Each glob item has to be checked for being a regular file.
36055 /* glob all .ts and get glob count */
36056 glob( globa, 0, NULL, &globt);
36057 gc = globt.gl_pathc;
36059 /* allocate glob sort */
36060 globsort = icalloc( gc, sizeof( struct globsort_s ), "globsort" );
36062 fg = 0;
36063 /* skip missing or non-regular files, else copy out size, date and name */
36064 for (i = 0; i < gc; i++) {
36065 memset( &fs, 0, sizeof(fs));
36066 ok = stat64( globt.gl_pathv[i], &fs);
36067 if (0 != ok) continue;
36068 if (0 == (S_IFREG & fs.st_mode)) continue;
36070 globsort[fg].fz = (long long) fs.st_size;
36071 globsort[fg].mt = fs.st_mtime;
36072 globsort[fg].name = globt.gl_pathv[i];
36073 fg++;
36076 #define USE_GLOB_SORT_MT
36077 #ifdef USE_GLOB_SORT_MT
36078 /* TODO: do the sort, but could make it a toggle */
36080 /* reverse sort by date */
36081 http_sort_index( globsort, fg );
36082 #endif
36083 chdir( w->root );
36085 f = fopen( x, "w");
36087 /* if can't create the index file for some reason, get out */
36088 if (NULL == f) {
36089 dvrlog( LOG_INFO,
36090 "www t%d didn't create %s%sindex.html",
36091 tnum, w->root, t->cwd);
36092 ok = -1;
36093 goto http_index_exit;
36096 now = time(NULL);
36097 advrlog( LOG_INFO, "open w %s %s files %d", d, x, fg);
36098 snprintf( ti, sizeof(ti)-1, "%s index %s", syslog_name, t->cwd);
36100 /* Write out the HTML header and body headers and optional legend
36101 The index pages do not have auto-refresh. This is intentional
36102 so it will only use CPU when user requests it to refresh.
36104 *s = 0;
36105 build_head_html( s, sizeof(s), 1, ti, 0, WHO); /* non-rel epg href */
36106 fprintf( f, "%s", s);
36108 fprintf( f, "<center>\n");
36109 fprintf( f, "<b>Index of [%s] " NBSP "</b>\n",
36110 (0 == t->cwd[1]) ? "cap":"cut" );
36112 /* get vol free/size and cut free/size */
36113 read_capvol_stats();
36114 read_cutvol_stats();
36115 if ( 0 == t->cwd[1] ) {
36116 if (0 == vol_size) vol_size = 1;
36117 fprintf( f, "Free %lldG (%lld%%) ",
36118 vol_free, (100 * vol_free) / vol_size );
36119 } else {
36120 if (0 == cut_size) cut_size = 1;
36121 fprintf( f, "Free %lldG (%lld%%) ",
36122 cut_free, (100 * cut_free) / cut_size );
36125 fprintf( f, "<a href=\042%s%s?l=1\042>", t->cwd, t->file);
36126 fprintf( f, "%s", (0 == web_legend) ?"Help":"HELP");
36127 fprintf( f, "</a> " NBSP "\n");
36129 fprintf( f, "<a href=\042%s%s?f=1\042>", t->cwd, t->file);
36130 fprintf( f, "%s", "CSS");
36131 fprintf( f, "</a> \n");
36132 fprintf( f, "<br>\n");
36134 if (0 != web_legend) {
36135 fprintf( f, IMG_FMT "%s\n", "NEW", "/pg/img/rec16.png",
36136 16, 16, 4, 4, 0, "New " );
36137 fprintf( f, IMG_FMT "%s\n", "LOG", "/pg/img/log16.png",
36138 16, 16, 4, 4, 0, "Log " );
36139 fprintf( f, IMG_FMT "%s\n", "SEQ", "/pg/img/seq16.png",
36140 16, 16, 4, 4, 0, "Seq+Edit " );
36141 fprintf( f, IMG_FMT "%s\n", "EDIT", "/pg/img/edit16.png",
36142 16, 16, 4, 4, 0, "Edit " );
36143 fprintf( f, IMG_FMT "%s\n", "CUT", "/pg/img/cut16.png",
36144 16, 16, 4, 4, 0, "Cut " );
36145 fprintf( f, IMG_FMT "%s\n", "ZAP", "/pg/img/zap16.png",
36146 16, 16, 4, 4, 0, "Delete " );
36148 fprintf( f, "</center>\n");
36149 fprintf( f, "<hr>\n");
36151 // validator does like this, but screws up the file size text alignment
36152 // fprintf( f, "<code>\n");
36153 // validator doesn't like this
36154 fprintf( f, "<pre>");
36155 if (0 != web_legend) {
36156 fprintf( f, " Size ");
36158 st = "b=1";
36159 if (1 & idx_sort) st = "b=2";
36161 fprintf( f, "%s<a href=\042index.html?%s\042>%s</a>%s",
36162 " ", st, "Date", " ");
36164 st = "b=4";
36165 if (4 & idx_sort) st = "b=8";
36167 fprintf( f, "%s<a class=\042%s\042 "
36168 "href=\042index.html?%s\042>%s</a>\n",
36169 " ", "f3 green", st, "Name");
36171 /* walk globsort index of files */
36172 for ( i = 0; i < fg; i++ ) {
36173 ln[0] = lu[0] = 0;
36174 n = globsort[i].name;
36175 l = "";
36177 /* should not happen, abort loop if any name pointer got zapped */
36178 if (NULL == n) break;
36179 if (0 == *n) break;
36181 /* use mpeg:// type URL */
36182 ur = "mpeg";
36184 /* build the .html capture log name from .ts file name */
36185 snprintf( ln, sizeof(ln)-1, "%s", n );
36186 ln[strlen(ln)-3] = 0; /* strip .ts */
36187 asnprintf( ln, sizeof(ln)-1, ".html");
36189 /* build the .tsx index file name */
36190 snprintf( xn, sizeof(xn)-1, "%s%s%sx", w->root, t->cwd, n );
36191 tsx = stat64( xn, &xs );
36192 advrlog( LOG_INFO, "%s %s %d", WHO, xn, tsx);
36194 /* build the .tsc cut file name */
36195 snprintf( cn, sizeof(xn)-1, "%s%s%sc", w->root, t->cwd, n );
36196 tsc = stat64( cn, &cs );
36197 advrlog( LOG_INFO, "%s %s %d", WHO, cn, tsc);
36198 if (0 == tsc)
36199 if (0 == cs.st_size)
36200 tsc = -1;
36202 /* capture log exists? */
36203 clf = stat64(ln, &ls);
36204 if (0 == clf) {
36205 /* LOG is img alt text */
36206 l = "LOG ";
36207 snprintf( lu, sizeof(lu)-1, "%s", ln);
36210 /* build the time string from file mod time */
36211 localtime_r( &globsort[i].mt, &mt );
36213 /* ft is file time text: YYYY-MMM-DD hh:mm :ss always 55 except manual cap */
36214 snprintf( ft, sizeof(ft), "%04d-%3s-%02d %02d:%02d",
36215 mt.tm_year+1900, mons[mt.tm_mon], mt.tm_mday,
36216 mt.tm_hour, mt.tm_min );
36218 chg = 0;
36220 /* red dot indicates capture is in progress */
36221 if ( (globsort[i].mt + 5) > now) chg = ~0;
36223 /* fz is file size text: xx,xxx max, in binary style megabytes */
36224 fz = (long long) globsort[i].fz;
36225 fz >>= 20LL;
36227 lltoasc( zt, fz );
36228 fprintf( f, "%6sM ", zt );
36230 /* ft is file time text: yyyy-mm-dd hh:mm:ss */
36231 fprintf( f, "%-17s ", ft );
36233 if (0 == chg) {
36234 /* unlink/zap href img */
36235 fprintf( f,
36236 "<a href=\042index.html?u=%s\042>" IMG_FMT "</a>",
36237 n, "DEL ", "/pg/img/zap16.png", 16, 16, 4, 0, 0 );
36238 } else {
36239 /* recording indicator is red button, can only zap from EPG */
36240 fprintf( f, IMG_FMT, "CAP ", "/pg/img/rec16.png",
36241 16, 16, 4, 0, 0 );
36244 if (0 == clf) {
36245 /* log href img */
36246 fprintf( f,
36247 "<a href=\042%s\042>" IMG_FMT "</a>",
36248 lu, "LOG ", "/pg/img/log16.png",
36249 16, 16, 4, 0, 0 );
36250 } else {
36252 /* spacer if no log file */
36253 fprintf( f, IMG_FMT,
36254 " ", "/pg/img/blank16.png",
36255 16, 16, 4, 0, 0 );
36258 /* edit image, four possible images to pick, blank, seq, edit or cut */
36259 ei = "/pg/img/blank16.png"; /* new files get blank */
36261 /* only show these if no change in .ts file in last 5s, and also */
36262 /* only if cwd is cap directory, no editing in other directories */
36263 if ( (0 == chg) && (0 == t->cwd[1]) ) {
36264 l = "SEQ ";
36265 ei = "/pg/img/seq16.png";
36266 if (0 == tsx) {
36267 if (0 == tsc) {
36268 ei = "/pg/img/cut16.png";
36269 l = "CUT ";
36270 } else {
36271 ei = "/pg/img/edit16.png";
36272 l = "XTC ";
36275 fprintf( f,
36276 "<a href=\042xtc://%s%s\042>" IMG_FMT "</a>",
36277 t->cwd, n, l, ei, 16, 16, 4, 0, 0);
36278 } else {
36280 /* cut dir files only get delete img+alt tag */
36281 fprintf( f, IMG_FMT, " ", ei, 16, 16, 4, 0, 0);
36284 /* can't use relative href because using mpeg:// instead of http:// */
36285 /* t->local_name could be from any interface, w->host may be INADDR_ANY */
36286 /* added port so it will work properly with http playback from any port */
36287 fprintf( f, " <a href=\042%s://", ur);
36288 fprintf( f, "%s:%d", t->local_name, arg_wport );
36290 fprintf( f, "%s%s\042>", t->cwd, n );
36291 fprintf( f, "%s</a>", n );
36293 // validator test: doesn't look right without <br> if using <code></code>
36294 // fprintf( f, "<br />" );
36295 fprintf( f, "\n" );
36299 // validator does like this, but screws up the file size text alignment
36300 // fprintf( f, "</code>\n");
36301 // validator doesn't like this
36302 fprintf( f, "</pre>\n");
36305 /* .ts files list was 0, indicate no captures */
36306 if (0 == fg) fprintf( f, "<center><b>No Captures</b></center>\n");
36308 fprintf( f, "<hr>\n");
36310 /* NOTE: This one will pass the validator with <code></code>, but looks wrong
36311 unless using invalid <pre></pre> for file size when it's over 9999M.
36312 Take comfort in fact that apache file list won't pass validator, either.
36314 *s = 0;
36315 build_foot_html(s, sizeof(s), 0); /* no validator png. needs <pre> */
36316 fprintf( f, "%s", s);
36317 fclose( f );
36318 ok = 0;
36320 /* don't forget: free glob memory and unlock mutex */
36321 http_index_exit:
36322 ifree( globsort, "globsort" );
36323 globfree( &globt );
36324 advrlog( LOG_INFO, "www t%d glob: %s found %d files for index.html",
36325 tnum, d, fg);
36326 pthread_mutex_unlock( &index_mutex );
36327 return ok;
36330 /* static */
36332 http_index_html4( struct wss_s *w, int tnum, char *caller )
36334 FILE *f; /* index.html file descriptor */
36335 glob_t globt; /* globt glob pointer */
36336 time_t now; /* current time */
36338 long long fz;
36340 char *globa, /* glob wildcard match in ascii */
36343 *ur, /* uri:// filler as mpeg:// */
36345 *st, /* sort type bitmask */
36346 *ei;
36348 char
36349 s[8192], /* html source text string */
36350 ti[256], /* html title text "index of /[cut/]" */
36351 zt[32], /* reusable file size text */
36352 ft[32], /* reusable file time text */
36353 /* reusable file names: */
36354 ln[WWW_ROOT_MAX], /* reusable capture log name */
36355 lu[WWW_TCP_MAX], /* reusable capture log url */
36356 xn[WWW_ROOT_MAX], /* reusable tsx name */
36357 cn[WWW_ROOT_MAX], /* reusable tsc name */
36358 d[WWW_TCP_MAX],
36359 o[WWW_TCP_MAX],
36360 x[WWW_TCP_MAX];
36363 int i, ok, /* counter and reusable return code */
36364 globnum,
36365 gc, /* total globs returned */
36366 fg, /* files globbed for sorting */
36367 chg,
36368 /* reusable file stat return codes: */
36369 tsx, /* 0 is tsx file exists and not blank */
36370 tsc, /* 0 is tsc file exists */
36371 clf; /* 0 is cap logfile exists */
36374 struct tm mt; /* reusable file mod time */
36375 struct tts_s *t; /* thread task status */
36376 struct globsort_s *globsort; /* reusable by each thread */
36378 struct stat64 fs, /* reusable file stats data: */
36379 ls, /* capture logfile stat */
36380 xs, /* tsx index file stat */
36381 cs; /* tsc cut file stat */
36384 while (EBUSY == pthread_mutex_trylock( &index_mutex )) return -1;
36385 globsort = NULL;
36386 fg = 0;
36388 t = &w->tss[ tnum ];
36389 i = 0;
36390 ok = -1;
36392 globa = "*.ts"; /* glob all .ts files */
36393 globnum = 0;
36395 /* media type include list by .ts extension */
36396 astrncpy( o, t->cwd, sizeof(o) );
36398 /* get path */
36399 p = NULL;
36400 if (strlen(o) > 1)
36401 p = strrchr( o, '/' );
36402 if (NULL != p) {
36403 *p='?';
36404 p = strrchr(o, '/');
36405 if (NULL != p) {
36406 p++;
36407 *p = 0;
36411 if (strlen(o) < 1)
36412 astrncpy( o, t->cwd, sizeof(o) );
36414 snprintf( d, sizeof(d), "%s%s", w->root, t->cwd );
36416 ok = chdir( d );
36417 if (ok != 0)
36419 dvrlog( LOG_INFO, "%s %s t%d chdir %d %s %s %s",
36420 WHO, caller, tnum, ok, d, w->root, t->cwd );
36421 ok = -1;
36422 goto http_index_exit;
36424 snprintf( x, sizeof(x)-1, "%sindex.html", d );
36425 advrlog( LOG_INFO, "x1 %s", x);
36427 ok = stat64(x, &fs);
36429 /* if file exists, don't overwrite if user write not set */
36430 if (0 == ok) {
36431 if (0 == (S_IWUSR & fs.st_mode)) {
36432 /* serve current file */
36433 dvrlog( LOG_INFO, "user can't update %s", x);
36434 ok = -1;
36435 goto http_index_exit;
36439 /* NOTE:
36440 glob on smbfs does not differentiate file vs dir.
36441 Also some odd issues with symlink display because not handled.
36442 Each glob item has to be checked for being a regular file.
36445 /* glob all .ts and get glob count */
36446 glob( globa, 0, NULL, &globt);
36447 gc = globt.gl_pathc;
36449 /* allocate glob sort */
36450 globsort = icalloc( gc, sizeof( struct globsort_s ), "globsort" );
36452 fg = 0;
36453 /* skip missing or non-regular files, else copy out size, date and name */
36454 for (i = 0; i < gc; i++) {
36455 memset( &fs, 0, sizeof(fs));
36456 ok = stat64( globt.gl_pathv[i], &fs);
36457 if (0 != ok) continue;
36458 if (0 == (S_IFREG & fs.st_mode)) continue;
36460 globsort[fg].fz = (long long) fs.st_size;
36461 globsort[fg].mt = fs.st_mtime;
36462 globsort[fg].name = globt.gl_pathv[i];
36463 fg++;
36466 #define USE_GLOB_SORT_MT
36467 #ifdef USE_GLOB_SORT_MT
36468 /* TODO: do the sort, but could make it a toggle */
36470 /* reverse sort by date */
36471 http_sort_index( globsort, fg );
36472 #endif
36473 chdir( w->root );
36475 f = fopen( x, "w");
36477 /* if can't create the index file for some reason, get out */
36478 if (NULL == f) {
36479 dvrlog( LOG_INFO,
36480 "www t%d didn't create %s%sindex.html",
36481 tnum, w->root, t->cwd);
36482 ok = -1;
36483 goto http_index_exit;
36486 now = time(NULL);
36487 advrlog( LOG_INFO, "open w %s %s files %d", d, x, fg);
36488 snprintf( ti, sizeof(ti)-1, "%s index %s", syslog_name, t->cwd);
36490 /* Write out the HTML header and body headers and optional legend
36491 The index pages do not have auto-refresh. This is intentional
36492 so it will only use CPU when user requests it to refresh.
36494 *s = 0;
36496 /* non-rel servers, rel epg hrefs */
36497 build_head_html( s, sizeof(s), 1, ti, 0, WHO);
36498 fprintf( f, "%s", s);
36500 fprintf( f, "<center>\n");
36501 fprintf( f, "<b>Index of [%s] " NBSP "\n",
36502 (0 == t->cwd[1]) ? "cap":"cut" );
36504 /* get vol free/size and cut free/size */
36505 read_capvol_stats();
36506 read_cutvol_stats();
36507 if ( 0 == t->cwd[1] ) {
36508 if (0 == vol_size) vol_size = 1;
36509 fprintf( f, "Free %lldG (%lld%%) ",
36510 vol_free, (100 * vol_free) / vol_size );
36511 } else {
36512 if (0 == cut_size) cut_size = 1;
36513 fprintf( f, "Free %lldG (%lld%%) ",
36514 cut_free, (100 * cut_free) / cut_size );
36516 fprintf( f, "</b>\n");
36518 fprintf( f, "<a class=\042%s\042 href=\042%s%s?l=1\042>",
36519 "green", t->cwd, t->file);
36520 fprintf( f, "%s", (0 == web_legend) ? "Help":"HELP");
36521 fprintf( f, "</a> \n");
36523 fprintf( f, "<a class=\042%s\042 href=\042%s%s?f=0\042>",
36524 "green", t->cwd, t->file);
36525 fprintf( f, "%s", "HTML");
36526 fprintf( f, "</a> \n");
36528 fprintf( f, "<br />\n");
36530 if (0 != web_legend) {
36531 fprintf( f, IMG_FMT "%s\n", "NEW", "/pg/img/rec16.png",
36532 16, 16, 4, 4, 0, "New " );
36533 fprintf( f, IMG_FMT "%s\n", "LOG", "/pg/img/log16.png",
36534 16, 16, 4, 4, 0, "Log " );
36535 fprintf( f, IMG_FMT "%s\n", "SEQ", "/pg/img/seq16.png",
36536 16, 16, 4, 4, 0, "Seq+Edit " );
36537 fprintf( f, IMG_FMT "%s\n", "EDIT", "/pg/img/edit16.png",
36538 16, 16, 4, 4, 0, "Edit " );
36539 fprintf( f, IMG_FMT "%s\n", "CUT", "/pg/img/cut16.png",
36540 16, 16, 4, 4, 0, "Cut " );
36541 fprintf( f, IMG_FMT "%s\n", "ZAP", "/pg/img/zap16.png",
36542 16, 16, 4, 4, 0, "Delete " );
36544 fprintf( f, "</center>\n");
36545 fprintf( f, "<hr />\n");
36547 // validator does like this, but screws up the file size text alignment
36548 // fprintf( f, "<code>\n");
36549 // validator doesn't like <pre> here?
36550 fprintf( f, "<div class=\042%s\042>\n", "f5");
36551 fprintf( f, "<pre>");
36552 if (0 != web_legend) {
36553 fprintf( f, " Size ");
36555 st = "b=1";
36556 if (1 & idx_sort) st = "b=2";
36558 fprintf( f, "%s<a class=\042%s\042 "
36559 "href=\042index.html?%s\042>%s</a>%s",
36560 " ", "f5 green", st, "Date", " ");
36562 st = "b=4";
36563 if (4 & idx_sort) st = "b=8";
36565 fprintf( f, "%s<a class=\042%s\042 "
36566 "href=\042index.html?%s\042>%s</a>\n",
36567 " ", "f5 green", st, " Name");
36569 /* walk globsort index of files */
36570 for ( i = 0; i < fg; i++ ) {
36571 ln[0] = lu[0] = 0;
36572 n = globsort[i].name;
36573 l = "";
36575 /* should not happen, abort loop if any name pointer got zapped */
36576 if (NULL == n) break;
36577 if (0 == *n) break;
36579 /* use mpeg:// type URL */
36580 ur = "mpeg";
36582 /* build the .html capture log name from .ts file name */
36583 snprintf( ln, sizeof(ln)-1, "%s", n );
36584 ln[strlen(ln)-3] = 0; /* strip .ts */
36585 asnprintf( ln, sizeof(ln)-1, ".html");
36587 /* build the .tsx index file name */
36588 snprintf( xn, sizeof(xn)-1, "%s%s%sx", w->root, t->cwd, n );
36589 tsx = stat64( xn, &xs );
36590 advrlog( LOG_INFO, "%s %s %d", WHO, xn, tsx);
36592 /* build the .tsc cut file name */
36593 snprintf( cn, sizeof(xn)-1, "%s%s%sc", w->root, t->cwd, n );
36594 tsc = stat64( cn, &cs );
36595 advrlog( LOG_INFO, "%s %s %d", WHO, cn, tsc);
36596 if (0 == tsc)
36597 if (0 == cs.st_size) tsc = -1;
36599 /* has capture log file? */
36600 clf = stat64(ln, &ls);
36601 if (0 == clf) {
36602 /* log is img alt text */
36603 l = "LOG ";
36604 snprintf( lu, sizeof(lu)-1, "%s", ln);
36607 /* build the time string from file mod time */
36608 localtime_r( &globsort[i].mt, &mt );
36610 /* ft is file time text: YYYY-MMM-DD hh:mm :ss always 55 except manual cap */
36611 snprintf( ft, sizeof(ft), "%04d-%3s-%02d %02d:%02d",
36612 mt.tm_year+1900, mons[mt.tm_mon], mt.tm_mday,
36613 mt.tm_hour, mt.tm_min );
36614 chg = 0;
36616 /* red dot indicates capture is in progress */
36617 if ( (globsort[i].mt + 5) > now) chg = ~0;
36619 /* fz is file size text: xx,xxx max, in binary style megabytes */
36620 fz = (long long) globsort[i].fz;
36621 fz >>= 20LL;
36623 lltoasc( zt, fz );
36624 fprintf( f, "%6sM ", zt );
36626 /* ft is file time text: yyyy-mm-dd hh:mm:ss */
36627 fprintf( f, "%-17s ", ft );
36629 if (0 == chg) {
36630 /* unlink/zap href img */
36631 fprintf( f, "<a class=\042%s\042"
36632 "href=\042index.html?u=%s\042>" IMG_FMT "</a>",
36633 "green", n, "DEL ", "/pg/img/zap16.png",
36634 16, 16, 4, 0, 0 );
36635 } else {
36636 /* recording indicator is red button, can only zap from EPG */
36637 fprintf( f, IMG_FMT, "CAP ",
36638 "/pg/img/rec16.png", 16, 16, 4, 0, 0 );
36641 if (0 == clf) {
36642 /* log href img */
36643 fprintf( f, "<a class=\042%s\042 "
36644 "href=\042%s\042>" IMG_FMT "</a>",
36645 "green", lu, "LOG ",
36646 "/pg/img/log16.png", 16, 16, 4, 0, 0 );
36647 } else {
36649 /* spacer if no log file */
36650 fprintf( f, IMG_FMT,
36651 " ", "/pg/img/blank16.png", 16, 16, 4, 0, 0 );
36654 /* edit image, four possible images to pick, blank, seq, edit or cut */
36655 ei = "/pg/img/blank16.png"; /* new files get blank */
36657 /* only show these if no change in .ts file in last 5s, and also */
36658 /* only if cwd is cap directory, no editing in other directories */
36659 if ( (0 == chg) && (0 == t->cwd[1]) ){
36660 l = "SEQ ";
36661 ei = "/pg/img/seq16.png";
36662 if (0 == tsx) {
36663 if (0 == tsc) {
36664 ei = "/pg/img/cut16.png";
36665 l = "CUT ";
36666 } else {
36667 ei = "/pg/img/edit16.png";
36668 l = "XTC ";
36671 fprintf( f, "<a class=\042%s\042 "
36672 "href=\042xtc://%s%s\042>" IMG_FMT "</a>",
36673 "green", t->cwd, n, l, ei, 16, 16, 4, 0, 0);
36674 } else {
36675 /* cut dir files only get delete img+alt tag */
36676 fprintf( f, IMG_FMT, " ", ei, 16, 16, 4, 0, 0);
36679 /* can't use relative href because using mpeg:// instead of http:// */
36680 /* t->local_name could be from any interface, w->host may be INADDR_ANY */
36681 /* added port so it will work properly with http playback from any port */
36682 fprintf( f, "<a class=\042%s\042 href=\042%s://",
36683 "f5 green", ur);
36684 fprintf( f, "%s:%d", t->local_name, arg_wport );
36686 fprintf( f, "%s%s\042>", t->cwd, n );
36687 fprintf( f, " %s</a>", n );
36689 // validator test: doesn't look right without <br /> if using <code></code>
36690 // fprintf( f, "<br />" );
36691 fprintf( f, "\n" );
36695 // validator does like this, but screws up the file size text alignment
36696 // fprintf( f, "</code>\n");
36697 // validator doesn't like this
36698 fprintf( f, "</pre>\n");
36699 fprintf( f, "</div>\n");
36701 /* .ts files list was 0, indicate no captures */
36702 if (0 == fg) fprintf( f, "<b>No Captures</b>\n");
36704 fprintf( f, "<hr />\n");
36706 /* NOTE: This one will pass the validator with <code></code>, but looks wrong
36707 unless using invalid <pre></pre> for file size when it's over 9999M.
36708 Take comfort in fact that apache file list won't pass validator, either.
36710 *s = 0;
36711 build_foot_html(s, sizeof(s), 0); /* no validator png. needs <pre> */
36712 fprintf( f, "%s", s);
36713 fclose( f );
36714 ok = 0;
36716 /* don't forget: free glob memory and unlock mutex */
36717 http_index_exit:
36718 ifree( globsort, "globsort" );
36719 globfree( &globt );
36720 advrlog( LOG_INFO, "www t%d glob: %s found %d files for index.html",
36721 tnum, d, fg);
36722 pthread_mutex_unlock( &index_mutex );
36723 return ok;
36727 /* builds index.html in current working directory of thread
36729 atscap0 atscap1 atscap2 atscap3 [cap] free% [cut] free%
36730 ---------------------------------------------------------
36731 PBS UPN FOX CBS ABC NBC WB ETH FUT AZH TUB ZJL UNI TEL
36732 ---------------------------------------------------------
36733 Size Date [+/zap] [log] [edit] Name
36735 4,634M 2007-Jan-06 22:06:44 [+/zap] [log] [edit] Farscape-0106-1200.00.ts
36737 '+' is record indicator red circle or zap if not a new file.
36738 log image or LOG indicates there is a capture log for it.
36739 edit image or EDIT will run xtscut on the file.
36740 name.ts is clickable as mpeg:// href.
36741 zap image or ZAP will delete the file.
36744 /* static */
36745 void
36746 http_index_html ( struct wss_s *w, int tnum, char *caller )
36748 if (0 == use_css) {
36749 http_index_html3( w, tnum, caller );
36750 } else {
36751 http_index_html4( w, tnum, caller );
36756 /* look at t->file extension and set t->ctext and t->ctype */
36757 /* static */
36758 void
36759 http_get_content( struct tts_s *t )
36761 int i, j, x, y, z;
36762 char *e, *c, cc;
36763 struct ct_s *ct = &content_type[0];
36765 c = "text/plain"; /* default if no match */
36766 cc = 1; /* default is no cache */
36768 j = sizeof( content_type ) / sizeof (struct ct_s);
36769 x = strlen( t->file ); /* filename length */
36770 for (i = 0; i < j; i++) {
36771 e = ct[ i ].ext; /* ext text */
36772 y = strlen(e); /* ext len */
36773 z = x - y;
36775 if (z > 0) {
36776 if (0 == strncasecmp( &t->file[ z ], e, y)) {
36777 c = ct[ i ].name;
36778 cc = ct[ i ].val;
36779 break;
36784 astrncpy( t->ctext, c, sizeof(t->ctext) ); /* content type text */
36785 t->ctext[31] = 0; /* nul term */
36786 t->ctype = cc; /* 0 is cache, nz is no cache */
36789 /* Reload will eventually dump latest epg if the signal is good.
36790 After a few seconds, there will be an EPG but if the station sends
36791 a few days worth it may take >45s before it is complete.
36792 12 days worth takes about 90s for one station here that sends that much.
36793 ts cap loop saves the current epg (presumably with timers indicated),
36794 so the first update can occur immediately. show cap stats dumps epg.
36796 /* static */
36797 void
36798 http_reload_epg ( int tnum, int ch )
36800 struct sig_s *s;
36802 if (ch < 0) return;
36803 if (ch > ptc_max) return;
36804 if (CAP_NONE != cap_now) return;
36806 s = &ptc[ ch ].sig;
36808 /* fetch one guide from this device if no timers within 180s */
36809 if ( ch == s->chan) {
36810 if (timer_idx > 0) {
36811 /* only search timers left on timer list is ok to cap info */
36812 if (T_SEARCH == timer[0].qstat) {
36813 /* scan_pos for console gets set by ts cap loop */
36814 cap_chan = ch;
36815 cap_now = CAP_INFO;
36816 } else {
36817 /* timer0 more than 3 minutes away is ok to cap info */
36818 if (utsnow < (timer[0].start - 180)) {
36819 cap_chan = ch;
36820 cap_now = CAP_INFO;
36823 /* no timers is ok to cap info */
36824 } else {
36825 cap_chan = ch;
36826 cap_now = CAP_INFO;
36829 /* wait for AOS at least */
36830 nanosleep( &signal_long_sleep, NULL );
36833 /* This is faster than waiting for test guides to get around to it. */
36835 /* NOTE: big epg is malloc'd but rest are static. The only one that needs
36836 to be static is pgm3 to hold the http user filter settings, which
36837 allows it to be different from the console settings.
36839 /* static */
36840 void
36841 http_refresh_epg ( int tnum, int ch )
36844 /* don't forget to free epg3 */
36845 epg3 = imalloc( sizeof( epg ), "http epg3");
36846 if (NULL == epg3) {
36847 http_log( tnum, "%s epg3 malloc failed", WHO);
36848 return;
36851 /* Temporarily re-enabled 20071227 to test for crashes:
36853 It may be better to only allow this after about 15 to 30s
36854 of capture since most stations that are working correctly
36855 will have their baseline 12 hour EPG data (4 EITs) loaded by then.
36857 The 4 baseline EITs will usually repeat more often than the rest, if the
36858 station is sending more than the minimum 4 EITs for ATSC compliance.
36860 If there is a crash here, it could be caused by partial data fill.
36863 #define USE_HTTP_LIVE_EPG
36864 #ifdef USE_HTTP_LIVE_EPG
36865 /* if live, epg is copied in case it's requested for current channel */
36867 /* Peter Knaggs reported a crash that seems to be related to this.
36868 The live EPG can be saved by show_cap_status after 3m instead,
36869 so the user can get a guide update within 5m of start of capture.
36870 If the EPGs are already up-to-date, disabling this should be OK.
36872 if ((CAP_NONE != cap_now) && (ch == cap_chan) && (utsets > 5)) {
36873 memcpy( epg3, epg, sizeof(epg));
36874 memcpy( &pg3, &pg, sizeof(pg));
36875 /* memcpy( &pgm3, &pgm, sizeof(pgm)); */ /* NO: keep web UI settings */
36876 } else
36877 #endif
36880 /* otherwise, load last saved EPG */
36881 memset( epg3, 0, sizeof(epg)); /* EPG data: .5, 3 or 8M sizes */
36882 memset( pg3, 0xFF, sizeof(pg)); /* small: indices 16k */
36883 load_epg( ch, epg3, &pgm3, pg3, WHO );
36886 /* set the channel to requested */
36887 pgm3.chan = ch; /* what is value during cap? */
36889 /* dump epg should be abstracted enough by now to write any epg */
36890 dump_epg_html( epg3, &pgm3, pg3, WHO );
36891 if (NULL != epg3)
36892 ifree( epg3, "http epg3" );
36893 epg3 = NULL;
36896 #if 0
36897 /* when user hits cap log href and href refers to current file, refresh log */
36898 /* no: this one needs more thought. */
36899 /* static */
36900 void
36901 http_refresh_caplog( void )
36903 if (CAP_NONE != cap_now) dump_cap_log();
36905 #endif
36907 /* convert %XX chars to ascii, and + to space */
36908 /* static */
36909 void
36910 http_url_ascii( char *d, char *s )
36912 unsigned char c;
36913 char *e;
36915 e = d;
36917 while (0 != *s) {
36918 *d = *s;
36919 if ('+' == *d) {
36920 *d = ' ';
36921 s++;
36922 d++;
36923 continue;
36926 if ('%' == *d) {
36927 s++;
36928 c = ahex2u( s );
36929 if (0 == c) break;
36930 *d = c;
36931 s += 2;
36932 d++;
36933 continue;
36935 *d = *s;
36936 s++;
36937 d++;
36942 /* convert FORM GET into something parse weekday or volatile timer can do */
36943 /* static */
36944 void
36945 http_form_timer( int tnum, char *t )
36947 int ch, st, hh, mm, lh, lm, ls, wd, dd, z;
36948 char *a, a1, *p[6], *r, *v;
36949 char tn[32], /* timer name */
36950 pn[8], /* program number as text */
36951 c[128], /* url-encoded conversion */
36952 e[64]; /* string for parsers */
36953 struct sig_s *s;
36955 memset( c, 0, sizeof(c) );
36956 http_url_ascii( c, t );
36958 r = c;
36959 z = sizeof(tn);
36961 memset( tn, 0, sizeof(tn) );
36963 build_form_args( p, 6, c );
36965 advrlog( LOG_INFO, "%s %s", t);
36967 advrlog( LOG_INFO, "%s '%s' '%s' '%s' '%s' '%s' '%s'",
36968 WHO, p[0], p[1], p[2], p[3], p[4], p[5] );
36970 ch = st = lm = wd = dd = 0;
36972 The following can't be blank: ch, name, start time, len.
36973 If weekday or date are blank, it's a volatile for today,
36974 or for tomorrow if the start time is past the current time.
36976 if (NULL == p[0]) return;
36977 if (strlen(p[0]) < 1) return;
36979 ch = atoi( p[0] );
36980 if ( (ch < 0) || (ch > scan_idx) ) return;
36981 advrlog( LOG_INFO, "ch %d", ch );
36983 s = &ptc[ ch ].sig;
36984 *pn = 0;
36985 if (s->pn > 0)
36986 snprintf(pn, sizeof(pn), ".%d", s->pn);
36988 advrlog( LOG_INFO, "pn %d", s->pn );
36990 if (NULL == p[1]) return;
36991 if (strlen( p[1]) < 1) return;
36993 /* preserve the append flag */
36994 a1 = 0;
36995 a = strchr(p[1], '@');
36996 if (NULL == a) a = strchr(p[1], '#');
36997 if (NULL != a) a1 = *a;
36999 event_truncate( tn, p[1], 2, sizeof( tn ));
37001 if (a1 > 0) asnprintf( tn, sizeof(tn), "%c", a1);
37003 if (0 == *tn) return;
37004 advrlog( LOG_INFO, "tn %s", tn );
37006 if (NULL == p[2]) return;
37008 // if (strlen(p[2]) < 5) return;
37010 /* start time */
37012 /* no colon assumes 0000-2359, could get fancy & add a/p check too */
37013 v = strchr( p[2], ':' );
37014 if (NULL != v) {
37015 *v = 0;
37016 v++;
37017 hh = atoi( p[2] );
37018 mm = atoi( v );
37019 } else {
37020 hh = atoi( p[2] ) / 100;
37021 mm = atoi( p[2] ) % 100;
37024 if ((hh < 0) || (hh > 23)) return;
37025 if ((mm < 0) || (mm > 59)) return;
37028 st = utsmid + (SECHR * hh) + (SECMIN * mm);
37029 advrlog( LOG_INFO, "hh:mm %02d:%02d st %d", hh, mm, st );
37031 if (NULL == p[3]) return;
37032 if (strlen(p[3]) < 1) return;
37034 /* minutes can have hours with a colon, otherwise it's minutes only */
37035 v = strchr( p[3], ':' );
37036 if (NULL != v) {
37037 *v = 0;
37038 v++;
37039 lh = atoi( p[3] );
37040 lm = atoi( v );
37041 lm += (lh * 60);
37042 } else {
37043 lm = atoi( p[3] );
37044 // lh = atoi( p[3] ) / 100;
37045 // lm = atoi( p[3] ) % 100;
37048 // lm = atoi( p[3] );
37049 ls = SECMIN * lm;
37051 /* limit to one day */
37052 if ( (lm < 1) || (lm > 1440) ) return;
37054 /* FIXME: fails on DST change as next day */
37055 if ( (st+ls) < utsnow) st += SECDAY;
37057 /* scan count instead of ptc max */
37058 if ( (st+ls) < utsnow) return;
37060 if (NULL != p[4]) wd = abtou( p[4] );
37061 advrlog( LOG_INFO, "wd %d", wd );
37063 /* TODO: p[5] future date */
37064 if (NULL != p[5]) dd = atoi( p[5] );
37065 advrlog( LOG_INFO, "dd %d", dd );
37067 memset(e, 0, sizeof(e));
37069 if (0 == wd) {
37070 if (0 == dd) {
37072 /* old volatile format */
37073 snprintf(e, sizeof(e), "V%02d:%010d:%03d:%s%s",
37074 ch, st, lm, tn, pn );
37075 } else {
37077 /* new volatile format */
37078 snprintf(e, sizeof(e), "V%02d:%08d:%02d:%02d:%03d:%s%s",
37079 ch, dd, hh, mm, lm, tn, pn );
37081 parse_volatile_timer( &e[1] );
37083 } else {
37084 if (0 == dd) {
37085 snprintf(e, sizeof(e), "W%02d:%02d:%02d:%03d:%7s:%s%s",
37086 ch, hh, mm, lm, p[4], tn, pn );
37087 parse_weekday_timer( &e[1] );
37088 } else {
37089 dvrlog( LOG_INFO, "%s weekday and date not allowed %s",
37090 WHO, t);
37091 return;
37095 timer_sort = ~0;
37096 sort_timers();
37098 refresh.timers = 1;
37100 /* TESTME: run update now? it runs within 1s */
37101 show_timers();
37103 advrlog( LOG_INFO, "%s %s", WHO, e);
37107 /* add timer can call parse volatile timer, but have to sort list and redraw */
37108 /* static */
37109 void
37110 http_add_epg_timer ( int tnum, char *p )
37112 int ch, st, mt;
37113 struct qtimer_s *t;
37114 char *r;
37116 r = "";
37117 st = 0;
37118 mt = 0;
37119 p++;
37120 ch = atoi( p );
37121 r = strchr( p, ':' );
37122 if (NULL != r) {
37123 r++;
37124 st = atoi( r );
37125 r = strchr( r, ':' );
37128 /* don't add bogus timer */
37129 if (st < 1) return;
37131 /* don't add overlapping timer */
37132 if (CAP_TIME == cap_now ) {
37133 t = &timer[0];
37134 if ( (st >= t->start) && (st < (t->start + t->len)) )
37135 return;
37138 advrlog( LOG_INFO, "%s %d %d %s", WHO, ch, st, r );
37140 /* stop any info cap before parsing the timer */
37141 if (st <= utsnow) {
37142 if (CAP_INFO == cap_now) {
37143 stop_capture( WHO, 0 );
37144 nanosleep( &signal_long_sleep, NULL );
37148 parse_volatile_timer( p );
37149 /* volatile timer needs to be saved and console timer list updated */
37150 display_type = D_TIMERS;
37151 refresh.timers = 1;
37152 sort_timers();
37153 save_config( WHO );
37155 /* current event will wait 4s until cap starts before refreshing page */
37156 // if (st <= utsnow) nanosleep( &msg_longer_sleep, NULL );
37159 /* Extract channel, program number and start time. If match on timer list,
37160 remove it. This only removes the timer and not the capture file.
37161 If a capture is in progress, it will be stopped but not deleted.
37162 If first char of p is V, only timers without daybits will be removed.
37163 If first char of p is W, only timers with daybits will be removed.
37164 If first char of p is neither W nor V, nothing will be done.
37166 /* static */
37167 void
37168 http_del_timer ( int tnum, char *s )
37170 int i, ch, st, lm, ls, pn, tt;
37171 struct qtimer_s *t;
37172 char *p[8], *n, *r;
37174 advrlog( LOG_INFO, "%s s %s", WHO, s);
37175 tt = 0;
37176 if ('V' == *s) tt = 1;
37177 if ('W' == *s) tt = 2;
37179 /* skip 'V' or 'W' char */
37180 s++;
37181 build_args( p, 4, s, ':', WHO );
37183 st = lm = ls = ch = pn = 0;
37184 n = NULL;
37186 /* sanity checks */
37187 if ( NULL == p[0] ) return;
37188 ch = atoi( p[0] );
37189 advrlog( LOG_INFO, "%s ch %d", WHO, ch);
37191 if ((ch < 0) || (ch > ptc_max)) return;
37193 if ( NULL != p[1] ) st = atoi( p[1] );
37194 advrlog( LOG_INFO, "%s st %d", WHO, st);
37196 if ( NULL != p[2] ) lm = atoi( p[2] );
37197 if (lm < 1) return;
37198 ls = SECMIN * lm;
37200 if ((st+ls) < utsnow) return;
37201 advrlog( LOG_INFO, "%s ls %d", WHO, ls);
37203 if ( NULL != p[3] ) n = p[3];
37204 if (0 == *n) return;
37206 advrlog( LOG_INFO, "%s n %s", WHO, n);
37208 r = strrchr( n, '.' );
37209 pn = 0;
37210 if (NULL != r) {
37211 r++;
37212 pn = atoi( r );
37214 if (pn < 1) return;
37215 advrlog( LOG_INFO, "%s pn %d", WHO, pn);
37217 ahttp_log( tnum, "%s tt %d ch %d.%d n %s st %d ls %d",
37218 WHO, tt, ch, pn, n, st, ls);
37220 /* start the loop */
37221 for (i = 0; i < timer_idx; i++) {
37222 t = &timer[i];
37223 if ( (ch == t->chan)
37224 && (pn == t->pn)
37225 && (st == t->start)
37226 && (ls == t->len)
37227 #if 0
37228 /* name could be anything because of extra flags */
37229 && (0 == strncmp( n, t->name, TIMER_NAME_MAX))
37230 #endif
37233 if ((1 == tt) && (0 == t->days)) {
37234 ahttp_log( tnum, "del vtimer[%d] %d.%d %d %d %s",
37235 i, ch, pn, st, ls, n);
37236 delete_timer( i, WHO );
37239 if ((2 == tt) && (0 != t->days)) {
37240 ahttp_log( tnum, "del wtimer[%d] %d.%d %d %d %s",
37241 i, ch, pn, st, ls, n);
37242 delete_timer( i, WHO );
37243 // reschedule_timer( i, WHO );
37248 /* save_config( WHO ); // delete timer did this */
37249 refresh.timers = 1;
37252 /* Extract channel, program number and start time. If match on timer list,
37253 remove it. This is different from http_del timer because it deletes
37254 the .ts capture, the .tsx frame and the .html log files.
37256 /* static */
37257 void
37258 http_zap_timer ( int tnum, char *p )
37260 int ch, st, ls, pn, i;
37261 char *n, *r;
37262 struct qtimer_s *t;
37264 /* only if a timer cap is running, manual cap control requires console */
37265 if (CAP_TIME != cap_now) return;
37266 advrlog( LOG_INFO, "%s %s", WHO, p);
37268 p++;
37269 ch = ls = st = pn = 0;
37271 n = "";
37272 ch = atoi( p );
37273 r = strchr( p, ':');
37274 if (NULL != r) {
37275 r++;
37276 st = atoi( r );
37277 r = strchr( r, ':' );
37278 if (NULL != r) {
37279 r++;
37280 ls = atoi( r );
37281 r = strchr( r, ':' );
37282 if (NULL != r) {
37283 r++;
37284 n = r;
37290 if (0 == *n) return;
37292 r = strrchr( n, '.' );
37293 if (NULL != r) {
37294 r++;
37295 pn = atoi( r );
37298 ahttp_log( tnum, "zap timer[%d] %d.%d %d %d %s",
37299 timer_rec, ch, pn, st, ls, n);
37301 /* check for each match in this order of precedence: channel pgm start */
37302 t = &timer[timer_rec];
37303 if (ch != t->chan) return;
37304 if (pn != t->pn) return;
37305 if (st != t->start) return;
37307 ahttp_log( tnum, "zapping timer %d", timer_rec);
37309 /* if match found. cap stops and timer is removed from RAM + config file */
37310 cap_zap = ~0; /* zap deletes current cap */
37311 /* stop_capture( WHO, 0 ); / / delete timer handles this */
37312 delete_timer( timer_rec, WHO );
37314 /* wait til cap done for guide refresh, is ts capture async thread event */
37315 /* four tries, but two seconds is enough for xfs not ext2/3 */
37316 i = 4;
37317 while( 0 == cap_done) {
37318 nanosleep( &guide_sleep, NULL); /* half second wait */
37319 if (0 == i--) break; /* two second max */
37323 /* static */
37324 void
37325 http_zap_info ( int tnum, int ch )
37327 if (ch == cap_chan) {
37328 if (CAP_INFO == cap_now) {
37329 cap_now = CAP_NONE;
37330 ahttp_log( tnum, "zap info %d %d", WHO, ch, cap_now );
37335 /* static */
37336 void
37337 http_toggle_find ( int tnum, int ch )
37339 pgm3.find = ~pgm3.find;
37340 ahttp_log( tnum, "toggle find %d %d", ch, pgm.find );
37343 /* static */
37344 void
37345 http_toggle_spam ( int tnum, int ch )
37347 pgm3.hide = ~pgm3.hide;
37348 ahttp_log( tnum, "toggle spam %d %d", ch, pgm.hide );
37351 /* static */
37352 void
37353 http_toggle_aged ( int tnum, int ch )
37355 pgm3.age = ~pgm3.age;
37356 ahttp_log( tnum, "toggle aged %d %d", ch, 1 & pgm3.age );
37359 /* Enable/disable signal chart display. saved in .conf on I line */
37360 /* static */
37361 void
37362 http_set_sigpng ( int tnum, int a )
37364 if (0 == a) scan_png = 0;
37365 if (0 != a) scan_png = ~0;
37366 http_log( tnum, "set PNG %d", 1 & scan_png );
37369 /* static */
37370 void
37371 http_toggle_sigsnr ( int tnum )
37373 scan_snr = ~scan_snr;
37374 ahttp_log( tnum, "set SNR %d", 1 & scan_snr );
37377 /* static */
37378 void
37379 http_toggle_sigkhz ( int tnum )
37381 scan_khz = ~scan_khz;
37382 ahttp_log( tnum, "set KHz %d", 1 & scan_khz );
37385 /* toggle signal for all channels on and off. if not capturing. */
37386 /* static */
37387 void
37388 http_scan_all ( int tnum, int op )
37390 if (CAP_NONE != cap_now) return;
37391 #ifdef USE_POWERDOWN
37392 utspdd = utsnow + USE_POWERDELAY;
37393 #endif
37394 if (0 == op) {
37395 scan_sig = 0;
37396 scan_one = ~0;
37397 } else {
37398 scan_sig = ~0;
37399 scan_one = 0;
37401 /* keep show channels display redux from limiting web started scan */
37402 display_type = D_TIMERS;
37403 set_refresh();
37406 /* signal scan on for ch, or scan off if ch is zero */
37407 /* do nothing if in capture, but could use it to toggle -x option? */
37408 /* static */
37409 void
37410 http_scan_one ( int tnum, int ch )
37412 if (CAP_NONE != cap_now) return;
37414 #ifdef USE_POWERDOWN
37415 utspdd = utsnow + USE_POWERDELAY;
37416 #endif
37418 /* toggle on and off, or jump to new channel */
37419 if ((0 != scan_sig) && (cap_chan == ch)) {
37420 /* turn off signal scan */
37421 scan_sig = 0;
37422 } else {
37424 /* scan_next 0 is nop, 1st channel is selected with 1 by console scan() */
37425 scan_next = 1 + get_scanlist_offset( ch );
37426 scan_sig = ~0;
37427 scan_one = ~0;
37428 scan_pos = ch;
37429 cap_chan = ch;
37430 load_guide( ch, WHO );
37433 /* kick console back to timer list so signal scan can run */
37434 display_type = D_TIMERS;
37435 set_refresh();
37438 /* zap, lightning bolt img, delete .ts, .tsx, .html matching file s */
37439 /* static */
37440 void
37441 http_unlink_cap( int tnum, char *s)
37443 int ch;
37444 char f[256], n[256], *r, *p;
37445 struct wss_s *w;
37446 struct tts_s *t;
37448 w = &wss;
37449 t = &w->tss[tnum];
37451 ch = 0;
37452 r = strchr( s, ':' ); /* ch:file.ts updates epg ch; file.ts no epg */
37453 if (NULL != r) { /* has : ? */
37454 ch = atoi(s); /* extract channel */
37455 r++; /* skip to filename */
37456 } else {
37457 r = s; /* file name only */
37460 /* only .ts files */
37461 filebase( n, r, F_TFILE );
37463 p = s;
37464 if (strlen(p) < 4) return;
37465 p += strlen(p);
37466 p -= 3;
37467 if (0 != strncmp(p, ".ts", 4)) return;
37469 /* only if current working directory is / or /cut/ */
37470 if (0 != strncmp( t->cwd, "/", 2))
37471 if (0 != strncmp( t->cwd, "/cut/", 6))
37472 return;
37474 ahttp_log( tnum, "unlink %d %s %s %s .*", ch, w->root, t->cwd, n );
37476 /* NOTE: unlink 0 is not return code for unlink, 0 is channel # if from EPG */
37477 snprintf( f, sizeof(f), "%s%s%s.tsx", w->root, t->cwd, n );
37478 ahttp_log( tnum, "unlink %d %s", ch, f );
37479 unlink( f );
37481 snprintf( f, sizeof(f), "%s%s%s.tsc", w->root, t->cwd, n );
37482 ahttp_log( tnum, "unlink %d %s", ch, f );
37483 unlink( f );
37485 snprintf( f, sizeof(f), "%s%s%s.html", w->root, t->cwd, n );
37486 ahttp_log( tnum, "unlink %d %s", ch, f );
37487 unlink( f );
37489 /* ts file last */
37490 snprintf( f, sizeof(f), "%s%s%s.ts", w->root, t->cwd, n );
37491 ahttp_log( tnum, "unlink %d %s", ch, f );
37492 unlink( f );
37493 sync();
37495 /* wait a second so the new index.html will reflect free space */
37496 nanosleep( &msg_sleep, NULL );
37499 /* t has ch:name to add or remove from search list */
37500 /* static */
37501 void
37502 http_search( int tnum, char *t )
37504 int i, j, ch;
37505 char *n;
37507 ch = atoi( t );
37508 n = strchr(t, ':');
37509 if (NULL == n) return;
37510 n++;
37512 i = find_search_timer( n );
37513 if (-1 == i) {
37515 /* if not found, add it */
37516 j = add_search( n, ch );
37517 advrlog( LOG_INFO, "%s add search %s %d", WHO, n, search_idx);
37518 if (-1 != j) {
37519 sort_searchlist();
37521 } else {
37523 /* if found, remove it */
37524 delete_search_event( i, WHO );
37525 advrlog( LOG_INFO, "%s del search %s %d", WHO, n, search_idx);
37527 save_config( WHO );
37529 display_type = D_TIMERS;
37530 refresh.timers = 1;
37533 /* p is trunc event name:time to add or remove from spam list */
37534 /* FIXME? build epg href is broken with ch:name right now */
37535 /* static */
37536 void
37537 http_spam( int tnum, char *p )
37539 int i, t;
37540 char *s;
37542 s = strchr(p, ':');
37543 if (NULL == s) return;
37544 *s = 0;
37545 s++;
37546 sscanf( s, "%d", &t);
37548 i = test_spam_name( p );
37549 if (-1 == i) {
37551 /* if not found, add it */
37552 add_spam( p, t );
37553 sort_spamlist( 0 ); /* sort by name for fast lookup */
37554 } else {
37556 /* if found, remove it */
37557 delete_spam( i );
37559 save_spamlist( WHO );
37562 /* toggle auto-epg status for ch and dump html stats to reflect it */
37563 /* static */
37564 void
37565 http_auto_epg( int tnum, int ch )
37567 struct sig_s *s;
37569 s = &ptc[ ch ].sig;
37571 if (ch != s->chan) return;
37573 /* toggle */
37574 if (0 == s->pgto) {
37575 /* add to auto epg list */
37576 s->pgto = 1; /* 3hr timeout */
37577 s->pgmt = 0;
37579 /* triggers immediate test and load if expired */
37580 utstpg = 0;
37581 } else {
37583 /* remove from auto epg list */
37584 s->pgto = 0;
37585 s->pgmt = 0;
37588 /* dump_html_stats( WHO ); */ /* request does this now */
37589 advrlog( LOG_INFO, "%s ch %02d to %d", WHO, ch, s->pgto);
37590 utstpg = 0;
37593 /* static */
37594 void
37595 http_toggle_weekdays( char *p )
37597 int ch, i, days;
37598 char n[TIMER_NAME_MAX], *r, *s;
37599 struct qtimer_s *t;
37601 advrlog( LOG_INFO, "%s1 p %s", WHO, p);
37603 /* need all 3: ch:name:weedaybits */
37605 /* strip channel */
37606 r = strchr(p, ':');
37607 if (NULL == r) return;
37608 *r = 0;
37609 r++;
37610 ch = atoi(p);
37612 /* strip any thing following name */
37613 astrncpy(n, r, sizeof(n));
37614 advrlog( LOG_INFO, "%s2 n %s", WHO, n);
37615 s = strchr(n, ':');
37616 if (NULL != s) *s = 0;
37618 /* strip weekday bits */
37619 s = strchr(r, ':');
37620 if (NULL == s) return;
37621 *s = 0;
37622 s++;
37624 days = atoi(s);
37626 dvrlog( LOG_INFO, "%s3 n %s c %d d %d ", WHO, n, ch, days);
37628 for (i=0; i<timer_idx; i++) {
37629 t = &timer[i];
37630 if (T_SEARCH == t->qstat) continue;
37631 if (0 == t->days) continue;
37632 if (0 != strncmp(n, t->name, TIMER_NAME_MAX)) continue;
37634 /* XOR the new day bits */
37635 days ^= t->days;
37637 /* don't let it remove the only weekday, thus converting it to volatile */
37638 if (0 == days) break;
37639 t->days = days;
37641 /* need to save the changes */
37642 save_config( WHO );
37644 calc_first_weekday(t);
37645 t->future = calc_future_date(i);
37646 display_type = D_TIMERS;
37647 timer_sort = 1;
37648 sort_timers();
37650 /* update console too */
37651 refresh.timers = 1;
37652 show_timers();
37653 break; /* only process first set day bit */
37657 /* static */
37658 void
37659 http_set_vc( char *p )
37661 char *r;
37662 int vn, va, ch;
37663 struct sig_s *s;
37665 refresh.vstats = 1;
37667 ch = cap_chan;
37668 r = p;
37669 va = 0;
37670 #if 0
37671 /* FIXME: http desync problem, make url specify channel to change vc/va.
37672 It's not really a problem, more of a limitation during capture.
37674 ch = atoi(r);
37675 r = strchr(p, ':');
37676 if (NULL != r) {
37677 r++;
37678 *r = 0;
37680 #endif
37681 s = &ptc[ch].sig;
37683 vn = atoi(p);
37685 /* always can toggle mpeg and atsc stats */
37686 if (-2 == vn) s->ms = ~s->ms;
37687 if (-3 == vn) s->as = ~s->as;
37689 /* only idle can change capture vc via web ui */
37690 if (CAP_NONE != cap_now) return;
37692 r = strchr(p, ',');
37693 if (NULL != r) {
37694 r++;
37695 va = atoi(r);
37698 /* TODO: set full cap stat, no href for it yet. has dubious value. */
37699 if (-1 == vn) {
37700 s->pn = s->vc = s->va = cap_pn = cap_vc = cap_va = -1;
37703 /* FIXME: check for valid program # or valid vc structure at least */
37704 if (vn >= 0) {
37705 s->vc = vn;
37706 s->pn = vc[vn].pn;
37707 s->va = va;
37709 /* set next manual capture parameters (only affects console for now) */
37710 cap_vc = vn;
37711 cap_pn = s->pn;
37712 cap_va = va;
37716 /* static */
37717 void
37718 http_epg_setday( char *p )
37720 int ch, dn;
37721 char *r;
37722 struct sig_s *s;
37724 ch = atoi( p );
37725 if ((ch >= ptc_max) || (ch < 0)) return;
37726 s = &ptc[ch].sig;
37728 /* set to today in case the parse fails */
37729 s->yday = tloc.tm_yday;
37730 r = strchr( p, ':');
37731 if (NULL == r) return;
37732 r++;
37733 dn = atoi(r);
37734 if ( (dn < 0) || (dn > 365) ) return;
37735 s->yday = dn;
37736 advrlog( LOG_INFO, "%s %d:%d", WHO, ch, dn);
37740 /*****************************************************************************
37741 Pseudo-CGI interface. Normally, CGI runs an executable but that's not used
37742 here since these commands change internal settings that a script can't.
37744 All of these should do some kind of page refresh so user sees update.
37746 Trying to use single letters to keep the generated HTML fairly small.
37747 Most of these return HTTP 200 or 201.
37749 201 is used when the user change has data immediately available.
37751 202 is used when doing EPG load; unknown time until data ready.
37753 The following options after ? are defined, one per request:
37754 (n is numeric, s is string)
37756 a=s Add volatile timer s from EPG
37757 b=n bits to set for sorting index.html
37758 c=n[,an] -2 & -3 stats detail MPEG/ATSC, n sets VC, an sets audio num
37759 d=s Delete timer s
37760 e=n Toggle auto-epg for guide n
37761 f=n toggle HTML format: 0 HTML 3.2, 1-3 HTML 4.01 + CSS
37762 g=n Get guide n, tries to start info cap. uses 202 return.
37763 h=n Toggle hide spam for guide n
37765 i unassigned
37767 j=s Toggle junk status for s (add/remove from spam list)
37768 k=n 0 hide 1 show uptime, memory and stats with vc select too
37769 l=n toggle EPG legend: 0 hide, 1 show
37770 m=n Toggle search match for guide n
37771 n=n day number to show for EPGs
37772 o=n Toggle hide aged for guide n
37773 p=n PNG signal chart toggle 0 off, 1 on
37774 q=n Toggle SNR vs Strength%
37775 r=n Refresh guide n, saves new version of guide
37776 s=s search add/remove
37778 t=s FORM GET for weekday or volatile future timer, non EPG
37780 u=s Unlink file s (only *.ts in / or cut/ allowed)
37781 v=n Start signal scan for channel n, 0 stops scan.
37782 w=n signal scan all toggle disable 0 enable 1
37783 x=n stop info cap (should be same as zap?)
37784 y=n toggle weekdays for weekday timer matching n
37785 z=n zap filename (.ts .tsc .tsx .html)
37787 TODO:
37788 combine o m h into m for mask
37792 /* CGI request is in t->cgi, figure out what to do with it */
37793 /* static */
37794 void
37795 http_cgi( struct tts_s *t )
37797 char *p;
37798 // char *r;
37799 char c;
37800 int ch, op;
37802 if (NULL == t) return;
37803 p = t->cgi;
37804 if (0 == *p) return;
37806 if ('?' == *p) p++;
37808 #if USE_CGI_NO_EXTRA_PARMS
37809 r = strchr(p, '&');
37810 if (NULL != r) *r = 0;
37811 r = p + strlen(p);
37812 #endif
37813 op = 0;
37815 ahttp_log( t->num, "%s %s", WHO, p);
37816 ch = 0;
37817 c = *p;
37818 p++;
37820 /* only allowing single char options */
37821 if ('=' != *p) return;
37822 p++;
37824 /* s is before = char, p is after */
37825 switch ( c ) {
37827 /* HTTP t->rnum:
37828 201 very short delay before reading the requested file
37829 202 is content created but long delay before part of it arrives
37833 /* set epg grid index */
37834 case 'i':
37835 op = atoi( p );
37836 op /= 3600;
37837 op *= 3600;
37838 epg_gs = op;
37839 break;
37842 /* add timer */
37843 case 'a':
37844 http_add_epg_timer( t->num, p );
37845 t->rnum = 201;
37846 break;
37848 /* cap/cut dir index.html sorted by date or name */
37849 case 'b':
37850 op = atoi( p );
37851 idx_sort = op;
37852 advrlog( LOG_INFO, "http idx sort %0X", op);
37853 t->rnum = 201;
37854 break;
37856 /* set virtual channel to 0-n, or:
37857 -1 sets full cap,
37858 -2 enables mpeg stats render
37859 -3 enables atsc stats render
37860 if v channel has a comma, it uses value after comma for v audio ch
37862 case 'c':
37863 http_set_vc( p );
37864 refresh.vstats = 1;
37865 refresh.estats = 1;
37867 advrlog( LOG_INFO, "http idx sort %0X", op);
37868 t->rnum = 201;
37869 break;
37871 /* delete timer */
37872 case 'd':
37873 http_del_timer( t->num, p );
37874 t->rnum = 201;
37875 break;
37877 /* toggle auto guide */
37878 case 'e':
37879 ch = atoi( p );
37880 http_auto_epg( t->num, ch);
37881 t->rnum= 201;
37882 break;
37884 /* set HTML format */
37885 case 'f':
37886 op = atoi( p );
37888 /* bit 4 is all/one-day-pgm toggle */
37889 if (0 != (16 & op)) epg_all = ~epg_all;
37891 /* bit 3 is srollbar toggle */
37892 if (0 != (8 & op)) epg_sb = ~epg_sb;
37894 /* bit 2 is grid setting */
37895 if (0 != (4 & op)) epg_grd = ~epg_grd;
37897 /* bits 0 and 1 are 0=no css, 1=large, 2=small, 3=tiny */
37898 if (0 == (0x1C & op)) use_css = 3 & op;
37899 save_config( WHO );
37900 t->rnum = 201;
37901 break;
37903 /* get guide */
37904 case 'g':
37905 ch = atoi( p );
37906 http_reload_epg( t->num, ch );
37907 /* accepted: content created but long delay before part of it arrives */
37908 t->rnum = 202; /* was 202, trying 201 now? */
37909 #ifdef USE_POWERDOWN
37910 utspdd = utsnow + 180 + USE_POWERDELAY;
37911 #endif
37912 break;
37915 /* filter toggle spam/junk match hidden */
37916 case 'h':
37917 ch = atoi( p );
37918 if (ch >= ptc_max) break;
37919 http_toggle_spam( t->num, ch );
37920 t->rnum = 201;
37921 break;
37923 /* toggle event junk status */
37924 case 'j':
37925 http_spam( t->num, p);
37926 t->rnum = 201;
37927 break;
37929 /* extended statistics toggle */
37930 case 'k':
37931 op = atoi( p );
37932 web_stats = op;
37933 t->rnum = 202;
37934 break;
37936 /* PNG legend toggle doesn't care which html page is being displayed */
37937 case 'l':
37938 op = atoi( p );
37939 if (0 != (1 & op)) web_legend = ~web_legend;
37940 if (0 != (2 & op)) web_config = ~web_config;
37941 advrlog( LOG_INFO, "op %d config %d legend %d",
37942 op, web_config, web_legend);
37943 t->rnum = 201;
37944 break;
37946 /* filter toggle timer/search match hidden */
37947 case 'm':
37948 ch = atoi( p );
37949 if (ch >= ptc_max) break;
37950 http_toggle_find( t->num, ch );
37951 t->rnum = 201;
37952 break;
37954 /* set day# to use for EPG, or zero for all days */
37955 case 'n':
37956 http_epg_setday( p );
37957 t->rnum = 201;
37958 break;
37960 /* filter toggle aged/expired hidden */
37961 case 'o':
37962 ch = atoi( p );
37963 if (ch >= ptc_max) break;
37964 http_toggle_aged( t->num, ch );
37965 t->rnum = 201;
37966 break;
37968 case 'p':
37969 op = atoi( p );
37970 http_set_sigpng( t->num, op );
37971 t->rnum = 202;
37972 break;
37974 case 'q':
37975 op = atoi( p );
37976 if (1 & op) http_toggle_sigsnr( t->num );
37977 if (2 & op) http_toggle_sigkhz( t->num );
37978 t->rnum = 202;
37979 break;
37981 #if 0
37982 /* this is now equivalent to requesting ch.html. is for href img */
37983 /* refresh guide to update 'current' and 'aged' status */
37984 case 'r':
37985 ch = atoi( p );
37986 if (ch >= ptc_max) break;
37987 t->rnum = 201;
37988 break;
37989 #endif
37991 /* toggle event search status */
37992 case 's':
37993 http_search( t->num, p );
37994 t->rnum = 201;
37995 break;
37997 /* timer for any or all weekdays *or* volatile timer for any future event */
37998 case 't':
37999 p--;
38000 p--;
38001 http_form_timer( t->num, p );
38002 t->rnum = 202;
38003 break;
38005 /* delete capture .ts file, .tsx frame data file and .html log file */
38006 case 'u':
38007 t->rnum = 201;
38008 http_unlink_cap( t->num, p);
38009 break;
38011 /* start or stop signal scan for one channel, if not capturing */
38012 case 'v':
38013 op = atoi( p );
38014 http_scan_one( t->num, op );
38015 t->rnum = 202;
38016 break;
38018 /* toggle scanning all channels */
38019 case 'w':
38020 op = atoi( p );
38021 http_scan_all( t->num, op );
38022 t->rnum = 202;
38023 break;
38025 /* interrupt info cap */
38026 case 'x':
38027 ch = atoi( p );
38028 http_zap_info( t->num, ch );
38029 t->rnum = 201;
38030 break;
38032 /* toggle weekday bits */
38033 case 'y':
38034 http_toggle_weekdays(p);
38035 advrlog( LOG_INFO, "http toggle weekdays %s", p);
38036 t->rnum = 201;
38037 break;
38039 /* zap is same as delete timer plus unlink capture */
38040 case 'z':
38041 http_zap_timer(t->num, p);
38042 t->rnum = 201;
38043 break;
38045 default:
38046 break;
38050 /* accept a TCP connection, if it passes test_host_allow check */
38051 /* static */
38053 http_accept ( struct wss_s *w, int tnum )
38055 int ok;
38056 struct tts_s *t;
38057 time_t now;
38058 // socklen_t z = sizeof(struct sockaddr_in);
38059 socklen_t z;
38061 z = sizeof(struct sockaddr);
38063 t = &w->tss[ tnum ];
38065 ahttp_log( tnum, "%s blocks... z %d, tnum %d", WHO, z, tnum);
38067 /* blocks and waits to accept a connection */
38068 t->accepted = accept( w->sock, (struct sockaddr *)&t->remote_addr, &z );
38070 ahttp_log( tnum, "%s unblocks z %d", WHO, z);
38072 if (-1 == t->accepted) {
38073 http_log( tnum, "accept() failed %d", tnum, errno);
38074 return -1;
38077 uintodot( t->remote_number, ntohl(t->remote_addr.sin_addr.s_addr) );
38078 /* copy number to name in case address does not resolve */
38079 astrncpy( t->remote_name, t->remote_number, sizeof(t->remote_name) );
38081 ahttp_log( tnum, "%s remote is %08X %s", WHO,
38082 ntohl(t->remote_addr.sin_addr.s_addr),
38083 t->remote_name);
38085 z = sizeof(struct sockaddr_in);
38087 /* get the local socket IP address for netmask test */
38088 ok = getsockname( t->accepted,
38089 (struct sockaddr_in *) &t->local_addr, &z );
38090 if (0 != ok) {
38091 http_log( tnum, "accept failed, no local IP address?");
38092 return -1;
38095 /* fill in name from -whost arg */
38096 astrncpy( t->local_name, arg_whost, sizeof(t->local_name) );
38098 ahttp_log( tnum, "%s local is %08X %s", WHO,
38099 ntohl(t->local_addr.sin_addr.s_addr),
38100 t->local_name);
38102 /* accept time */
38103 now = time(NULL);
38104 ctime_r( &now, t->date );
38105 t->date[16] = 0;
38107 /* remote and local address same doesn't need access control */
38108 if (t->remote_addr.sin_addr.s_addr == t->local_addr.sin_addr.s_addr) {
38109 strncpy(t->remote_name, t->local_name, sizeof(t->remote_name));
38110 ahttp_log( t->num, "accept localhost");
38111 return 0;
38114 /* not same address, check netmask for access control */
38115 if ( 0 == http_test_netmask( htonl(t->remote_addr.sin_addr.s_addr),
38116 htonl(t->local_addr.sin_addr.s_addr), w->mask) ) {
38117 /* if subnet mask non-zero, but passes netmask test, don't check hosts.allow */
38118 if (0 != w->mask) {
38119 ahttp_log( t->num, "accept subnet %s", t->remote_name);
38120 return 0;
38124 /* not in subnet or subnet mask 0, check hosts.allow for access control */
38125 if (0 == http_test_allows( t, &t->remote_addr )) {
38126 ahttp_log( t->num, "accept name %s", t->remote_name);
38127 return 0;
38130 /* slam the door closed, no popup in mozilla saying "no data" */
38131 http_log( t->num, "accept refused %s (%s)",
38132 t->remote_name, t->remote_number );
38134 shutdown( t->accepted, SHUT_RDWR );
38135 close( t->accepted );
38136 t->accepted = -1;
38137 return -1;
38140 /* store If Modified Since GMT date as local adjusted time_t for 304 return */
38141 /* static */
38142 void
38143 http_ims( struct tts_s *t, char *p )
38145 struct tm imtm;
38146 char *r, d[64];
38148 memset( &imtm, 0, sizeof(imtm) );
38149 memset( &d, 0, sizeof(d) );
38150 astrncpy( d, p, sizeof(d) );
38152 r = strchr(d, '\n');
38153 if (NULL != r) *r = 0;
38154 r = strchr(d, '\r');
38155 if (NULL != r) *r = 0;
38156 r = strptime( d, "%a, %d %b %Y %T %Z", &imtm );
38157 if (NULL != r) t->imstime = mktime( &imtm ) + utsgmt;
38158 // ahttp_log( t->num, "%s:%d %d %s", WHO, WHERE, t->imstime, d );
38162 /* Read HTTP request and validate it.
38163 Only GET is supported. This makes t->cwd and t->file.
38164 Returns 0 for ok, or -1 if request was ignored.
38166 /* static */
38168 http_request ( struct wss_s *w, int tnum )
38170 int i,
38171 recvd,
38173 rfs,
38174 ch, /* channel number */
38175 a; /* alpha channel for sig png */
38177 char *p,
38179 *rt,
38180 *s[4],
38181 req[ WWW_TCP_MAX ],
38182 fn[ WWW_TCP_MAX ],
38183 rfile[ WWW_TCP_MAX - WWW_ROOT_MAX ],
38184 rver[ 16 ];
38186 struct stat64 fs;
38187 struct tts_s *t;
38188 struct timeval rfto;
38189 fd_set rfds;
38191 t = &w->tss[ tnum ];
38193 if (NULL == t) {
38194 dvrlog( LOG_INFO, "%s has null thread %d pointer", WHO, tnum);
38195 return -1;
38198 /* default is this if nothing else done */
38199 t->rnum = 200;
38201 strcpy( t->cwd, "/" );
38202 strcpy( t->file, "index.html");
38204 recvd = 0;
38205 t->ka = t->utska - utsnow;
38206 while ( utsnow < t->utska ) {
38207 if (-1 == t->accepted) return -1;
38209 t->ka = t->utska - utsnow;
38210 if (t->ka < 0) t->ka = 0;
38211 if (0 == t->ka) return -1;
38213 rfto.tv_sec = t->ka;
38214 rfto.tv_usec = 0;
38216 /* select is 1 if ok to read data */
38217 FD_ZERO( &rfds );
38218 FD_SET( t->accepted, &rfds );
38219 rfs = select( t->accepted+1, &rfds, NULL, NULL, &rfto);
38221 /* stop looping once read data becomes available */
38222 if (1 != rfs) {
38223 /* CPU redux */
38224 nanosleep( &console_read_sleep, NULL );
38225 continue;
38227 if (!FD_ISSET(t->accepted, &rfds)) return -1;
38229 /* get HTTP request */
38230 recvd = recv(t->accepted, t->request, WWW_TCP_MAX, 0);
38231 if (recvd > 0) break;
38232 return -1;
38235 if (0 == recvd) return -1;
38237 /* NUL term the request */
38239 t->request[recvd] = '\0';
38240 astrncpy( req, t->request, sizeof(req));
38242 /* break apart request into 3 individual strings */
38243 p = req;
38244 ahttp_log( tnum, "Request: %s", p);
38246 /* request should have NL [or CR/NL?], trunc everything after it */
38247 r = strchr( p, '\n');
38248 if (NULL == r) {
38249 t->request[32] = 0; // truncate the bogus request
38250 http_log( tnum, "ignoring %s", t->request);
38251 return -1;
38253 *r = 0;
38254 r = strchr( p, '\r'); // truncate NL and CR
38255 if (NULL != r) *r = 0;
38257 for (i = 0; i < 3; i++) {
38258 s[i] = p;
38259 if (0 == *p) break;
38260 r = strchr( s[i], ' ');
38261 if (NULL == r) break;
38262 *r = 0;
38263 r++;
38264 p = r;
38267 /* not three parameters? */
38268 if (i < 2) {
38269 http_log( tnum, "ignoring %s", req, t->rfile);
38270 return -1;
38273 /* req is already loaded with first parm */
38274 astrncpy( rfile, s[1], sizeof(rfile));
38275 astrncpy( rver, s[2], sizeof(rver));
38277 /* extract file request: add root, add index.html if request is path delim */
38278 // sscanf(t->request, "%s %s %s", req, t->rfile, rver);
38280 astrncpy( t->rfile, rfile, sizeof(rfile) );
38282 /* don't log the image GETs, is TMI */
38283 if (NULL == strstr( t->rfile, ".png" )) {
38284 ahttp_log( t->num, "%s %s %s %s",
38285 t->remote_number, req, rfile, rver );
38288 /* HTTP 1.1 if greater than 1.1, else HTTP 1.0 */
38289 t->htver = 0; /* default is old style for old clients */
38290 if (0 == strncmp(rver, "HTTP/1.", 7) ) {
38291 t->htver = atoi( &rver[7] );
38292 if (t->htver > 1) t->htver = 1; /* used for response control */
38295 ahttp_log( t->num, "Request version: [%s], reply HTTP/1.%d",
38296 rver, t->htver);
38298 /* limit output to headers only, if not GET, for link-checkers */
38299 t->rtype = 0;
38301 if (0 == strncmp( req, "GET", 4)) t->rtype = 1;
38303 if (0 == t->rtype) {
38304 http_log( tnum, "ignoring %s %s", req, t->rfile);
38305 return -1;
38308 r = rfile;
38310 /* any ../ badness? */
38311 p = strstr( r, "../" );
38312 if (NULL != p) {
38313 dvrlog( LOG_INFO, "remove ../ from %s", r);
38314 /* remove it */
38315 while (NULL != p) {
38316 p = r + 3;
38317 /* any more left? FIXME: trailing ../ without / prior is ok? NO */
38318 r = strstr( p, "../" );
38319 if (r != NULL) continue;
38320 /* set r to last p */
38321 r = p;
38322 p = NULL;
38323 /* stop the loop */
38324 dvrlog( LOG_INFO, "removed ../ now %s", r);
38325 break;
38329 /* skip initial duplicate /'s */
38330 while ( ('/' == r[0]) && ('/' == r[1]) ) r++;
38332 /* reset cgi string */
38333 *t->cgi = 0;
38335 /* CGI parse re-uses p, it gets re-used again */
38336 p = strchr(r, '?');
38338 if (NULL != p) {
38339 if (strlen(p) > 1) { /* need more than ? by itself */
38340 astrncpy( t->cgi, p, WWW_ROOT_MAX); /* true, has cgi */
38343 /* remove cgi from request */
38344 *p = 0;
38345 /* set to null so re-use will force segfault if not re-used correctly */
38346 p = NULL;
38349 if (0 != *t->cgi) ahttp_log( t->num, "CGI %s", t->cgi);
38351 advrlog( LOG_INFO, "request remaining %s", r);
38353 if (strlen(r) > 0)
38356 /* if last char is path delim then entire request is new dir + index.html */
38357 if ( '/' == r[ strlen(r) - 1 ] ) {
38358 astrncpy( t->cwd, r, sizeof( t->cwd) );
38359 r = "index.html";
38360 astrncpy( t->file, r, sizeof( t->file ));
38363 /* any path left must be new dir */
38364 p = strrchr(r, '/');
38365 if (NULL != p) {
38366 *p = 0;
38367 /* copy directory and put ending / back in */
38368 snprintf( t->cwd, sizeof(t->cwd), "%s/", r);
38369 /* point to file */
38370 r = p;
38371 r++;
38374 /* copy file */
38375 astrncpy( t->file, r, sizeof( t->file ) );
38377 } else { /* blank request gets /index.html */
38378 strcpy( t->cwd, "/" );
38379 strcpy( t->file, "index.html" );
38382 /* hosts.allow should not be retrievable */
38383 if (NULL != strstr(t->file, "hosts.allow")) {
38384 strcpy( t->cwd, "/" );
38385 strcpy( t->file, "index.html" );
38388 /* filename and path should be parsed into t-> by this point */
38390 snprintf(fn, sizeof(fn), "%s%s%s", w->root, t->cwd, t->file);
38392 /* atscap* and pg/ in ram, fix dir for test */
38393 if (0 != arg_tmpfs) {
38394 if ( (NULL != strstr( t->cwd, "pg/" ))
38395 || (NULL != strstr( t->file, NAME )) )
38397 snprintf( fn, sizeof(fn), "%s/ram%s%s", w->root, t->cwd, t->file);
38401 ok = stat64( fn, &fs );
38402 ahttp_log( tnum, "%s:%d stat64 %d %s", WHO, __LINE__, ok, fn);
38404 /* stat worked, root+cwd is good, otherwise fix name to /index.html */
38405 if (0 != ok) {
38406 strcpy( t->cwd, "/");
38407 strcpy( t->file, "index.html" );
38408 t->rnum = 302; /* redirect to root if dir not found */
38412 /* full content is default */
38413 t->rseek = 0LL;
38415 /* is partial content request? mplayer can use stream slider, xine can not */
38416 rt = "Range: bytes=";
38417 p = strstr( t->request, rt );
38418 if (NULL != p) {
38419 p+= strlen(rt);
38420 sscanf( p, "%lld", &t->rseek);
38423 ahttp_log( tnum, "requests %s %s seek %lld", t->cwd, t->file, t->rseek);
38425 t->imstime = 0;
38426 rt = "If-Modified-Since: ";
38427 p = strstr( t->request, rt );
38428 if (NULL != p) {
38429 p += strlen(rt);
38430 http_ims( t, p );
38433 /* default is off for HTTP 1.0 */
38434 t->ka = 0;
38436 /* my default keep alive is 5 seconds for HTTP 1.1 */
38437 if (t->htver > 0) t->ka = 5;
38439 rt = "Connection: ";
38440 p = strstr( t->request, rt);
38441 if (NULL != p) {
38442 p += strlen(rt);
38444 /* HTTP 1.1 defaults to persistent connection, close is now provided */
38445 if (NULL != strstr(p, "close")) t->ka = 0;
38447 /* HTTP 1.0 compatibility, old buggy ka, give it 15 clicks */
38448 if (NULL != strstr(p, "Keep-Alive"))
38449 t->ka = 5;
38452 /* client requests different keep alive? */
38453 rt = "Keep-Alive: ";
38454 p = strstr( t->request, rt );
38455 if (NULL != p) {
38456 p += strlen(rt);
38457 t->ka = atoi(p);
38458 ahttp_log( tnum, "client request %d keepalive", t->ka);
38461 /* keepalive limit */
38462 if (t->ka > 1) t->ka = 1;
38464 /* tells select when to quit http request */
38465 t->utska = utsnow + t->ka;
38466 ahttp_log( tnum, "keepalive %d expires %u", t->ka, t->utska-utsnow);
38468 /* cwd is always / if not specified */
38469 if (0 == *t->cwd) strcpy( t->cwd, "/");
38471 #ifdef USE_PNG
38472 /* request is for signal strength .png, create latest */
38473 if (0 == strncasecmp( t->cwd, "/pg/", strlen(t->cwd))) {
38474 if (NULL != strstr( t->file, ".png")) {
38476 /* skip the devnum- */
38477 ch = atoi( &t->file[2] );
38478 if ((ch >= 0) && (ch < ptc_max)) {
38479 a = 0x6F;
38481 /* PNG OPACITY */
38483 /* scan all or current channel needs more opacity */
38484 if ( ((0 != scan_sig) && (0 == scan_one))
38485 || (ch == cap_chan) ) a = 0xDF;
38487 build_sig_png( ch, a, WHO );
38491 #endif
38493 /* handle cgi before serving file it might refer to */
38494 if ('?' == *t->cgi)
38495 http_cgi( t );
38497 /* individual root file handling, index and top level nav for each server */
38498 ok = strncmp( t->cwd, "/", 2 ); /* count the nul too */
38499 advrlog( LOG_INFO, "strncmp t->cwd / %d '%s' '%s'", ok, t->cwd, "/" );
38501 /* breaks w->root only security? */
38502 /* if (0 == ok) */
38504 char *x, *y;
38506 /* if file is index.html, need to build a current one */
38507 ok = strncmp( t->file, "index.html", 11); /* count the NUL too */
38508 advrlog( LOG_INFO, "strncmp t->file %d '%s' index.html", ok, t->file );
38509 if (0 == ok) {
38510 http_index_html( w, tnum, WHO );
38513 /* if file is atscap[arg_dev].html, need to build a current one */
38514 x = NAME;
38515 if ( 0 == strncmp( t->file, x, strlen(x)) ) {
38516 y = ".html";
38517 if ( 0 == strncmp( &t->file[1+strlen(x)], y, strlen(y)) ) {
38518 if ( ( '0' + arg_devnum) == t->file[6] ) {
38519 dump_cfg_html( WHO );
38525 #if 0
38526 /* failsafe */
38527 if (0 == *t->cwd) {
38528 dvrlog( LOG_INFO, "cwd blank!");
38529 strcpy( t->cwd, "/" );
38531 #endif
38533 advrlog( LOG_INFO, "%s:%d %s %s %s", WHO, WHERE, w->root, t->cwd, t->file);
38535 /* auto-refresh every time guide html requested in case href changed. */
38536 /* move between instances easier with update of epg to current instance */
38537 if ( (NULL != strstr( t->cwd, "/pg/"))
38538 && (NULL != strstr( t->file, ".html")) )
38541 /* is it the big grid or a single epg? */
38542 if (0 == strncmp( t->file, "grid", 4 )) {
38543 dump_epg_grid();
38544 } else {
38545 /* NOTE: is why callsign.html doesn't work */
38546 ch = atoi( t->file );
38547 if ((ch >= 0) && (ch < ptc_max))
38548 http_refresh_epg( tnum, ch );
38552 snprintf( fn, sizeof(fn), "%s%s%s", w->root, t->cwd, t->file);
38554 /* request is for atscap* and pg/. may be in ram, fix dir for test */
38555 if (0 != arg_tmpfs) {
38556 if ( (NULL != strstr( t->cwd, "pg/" ))
38557 || (NULL != strstr( t->file, NAME )) )
38559 snprintf( fn, sizeof(fn), "%s/ram%s%s", w->root, t->cwd, t->file);
38563 /* see again if the file is actually there */
38564 ok = stat64(fn, &fs);
38565 advrlog( LOG_INFO, "%s:%d stat64 %d %s", WHO, __LINE__, ok, fn);
38566 if (0 == ok) {
38568 /* is a regular file no error */
38569 if (0 != (fs.st_mode & S_IFREG)) {
38570 ahttp_log( tnum, "%s is regular file", fn );
38572 /* set 304 response type */
38573 if (0 != t->imstime) {
38574 if ( fs.st_mtime == t->imstime )
38575 t->rnum = 304;
38577 return 0;
38580 /* is a directory, adjust names and make file index.html */
38581 if (0 != (fs.st_mode & S_IFDIR)) {
38582 snprintf( fn, sizeof(fn), "%s%s/", t->cwd, t->file );
38583 astrncpy( t->cwd, fn, sizeof(t->cwd) );
38584 strcpy( t->file, "index.html" );
38585 return 0;
38589 dvrlog( LOG_INFO, "req%d failed %s", tnum, t->rfile);
38591 /* all else is an error */
38592 strcpy( t->cwd, "/");
38593 strcpy( t->file, "index.html" );
38594 chdir( w->root );
38596 t->rnum = 302; /* smooth redirect */
38597 ok = -1;
38598 /* t->rnum = 404; // confuses mozilla? */
38599 http_log( tnum, "%s returns %d", WHO, ok);
38600 return ok; /* will close socket */
38603 /* sends a file. .png files get Expires: tomorrow sent in header */
38604 /* subset of rfc 2616 http 1.1 sec 8.1.2.1 compliance, single connection */
38605 /* static */
38607 http_response ( struct wss_s *w, int tnum )
38609 time_t utnow, utexp;
38610 long long start, end;
38611 long long size, count;
38612 int ok, len;
38614 char utcnow[32], utcmod[32], utcexp[32];
38615 char fn[ WWW_TCP_MAX ];
38617 struct tts_s *t;
38618 char *server;
38620 struct timespec e0 = {0,0};
38621 struct timespec e1 = {0,0};
38622 struct timespec et = {0,0};
38623 struct stat64 fs;
38625 #ifdef USE_CGI_WAIT
38626 struct timespec ns = { 0, 100000000 }; /* 100ms */
38627 char ft[256];
38628 int u, z;
38629 #endif
38632 t = &w->tss[ tnum ];
38634 utnow = time(NULL);
38635 http_text_time( utcnow, &utnow );
38637 snprintf( fn, sizeof(fn), "%s%s%s", w->root, t->cwd, t->file );
38638 if (0 != arg_tmpfs) {
38639 if ( (NULL != strstr( t->cwd, "pg/" ))
38640 || (NULL != strstr( t->file, NAME )) )
38642 snprintf( fn, sizeof(fn), "%s/ram%s%s", w->root, t->cwd, t->file );
38646 /* test to make sure it's a regular file, failsafe for get request */
38647 ok = stat64( fn, &fs );
38648 ahttp_log( tnum, "%s:%d stat64 %d %s", WHO, __LINE__, ok, fn);
38650 /* don't log img files, is TMI */
38651 if (NULL == strstr( fn, ".png" ))
38652 ahttp_log( t->num, "%s %d %s ka %d", WHO, t->rnum, fn, t->ka );
38654 /* asnprint wants first char nul if new string */
38655 *t->reply = 0;
38658 if (304 == t->rnum) {
38659 asnprintf( t->reply, WWW_TCP_MAX, "HTTP/1.1 304 Not Modified\n");
38661 if (0 == t->ka) {
38662 asnprintf( t->reply, WWW_TCP_MAX, "Connection: close\n\n");
38663 } else {
38665 /* spec does not define this very well for http 1.1, not at all for 1.0 */
38666 /* NOTE: keep the connection open even through errors */
38667 asnprintf( t->reply, WWW_TCP_MAX, "Connection: Keep-Alive\n\n");
38670 // ahttp_log( t->num, "%s:%d %s", WHO, WHERE, t->reply );
38672 /* don't use SIGPIPE handler */
38673 ok = send( t->accepted, t->reply, strlen(t->reply), MSG_NOSIGNAL );
38675 if (0 == t->ka)
38676 http_close( t, &t->accepted, "304" );
38677 return 0;
38681 /* NOTE: This doesn't work right. Mozilla tries to save a file, odd.
38682 The work around is file not found closes connection; no http response.
38685 /* 404 Not Found */
38686 if (404 == t->rnum) {
38687 asnprintf( t->reply, WWW_TCP_MAX, "HTTP/1.1 404 Not Found\n");
38689 if (0 == t->ka) {
38690 asnprintf( t->reply, WWW_TCP_MAX, "Connection: close\n\n");
38691 } else {
38693 /* spec does not define this very well for http 1.1, not at all for 1.0 */
38694 /* NOTE: keep the connection open even through errors */
38695 asnprintf( t->reply, WWW_TCP_MAX, "Connection: Keep-Alive\n\n");
38698 ahttp_log( t->num, t->reply );
38700 /* don't use SIGPIPE handler */
38701 ok = send( t->accepted, t->reply, strlen(t->reply), MSG_NOSIGNAL );
38703 if (0 == t->ka)
38704 http_close( t, &t->accepted, "404" );
38705 return -1;
38708 /* 302 redirects to /index.html */
38709 if (302 == t->rnum) {
38710 #ifdef WWW_STRICT_HTTP11
38711 /* older clients might not respond to 303 */
38712 snprintf( t->reply, WWW_TCP_MAX,
38713 "HTTP/1.1 303 See Other\n"
38714 "Location: http://%s:%d/index.html\n"
38715 "Cache-Control: no-cache\n",
38716 t->local_name, t->local_port);
38717 #else
38718 /* RFC 2616 section 10.3.3-4, pp 40-41 */
38719 /* older clients should treat 302 same as new 303 */
38720 snprintf( t->reply, WWW_TCP_MAX,
38721 "HTTP/1.1 302 Found\n"
38722 "Location: http://%s:%d/index.html\n"
38723 "Cache-Control: no-cache\n",
38724 t->local_name, t->local_port);
38726 #endif
38727 if (0 == t->ka) {
38728 asnprintf( t->reply, WWW_TCP_MAX, "Connection: close\n\n");
38729 } else {
38730 asnprintf( t->reply, WWW_TCP_MAX, "Connection: Keep-Alive\n\n");
38733 ahttp_log( t->num, t->reply );
38734 ok = send( t->accepted, t->reply, strlen(t->reply), MSG_NOSIGNAL );
38735 if (0 == t->ka)
38736 http_close( t, &t->accepted, "302" );
38737 return 0;
38741 /* FIXME: is this directory check needed? http request should 302 redirect */
38742 /* dir enable should test for dirs, but not rewriting apache here */
38743 if (0 == (S_IFREG & fs.st_mode)) {
38745 #ifdef WWW_STRICT_HTTP11
38746 /* older clients might not respond to 303 */
38747 snprintf( t->reply, WWW_TCP_MAX,
38748 "HTTP/1.1 303 See Other\n"
38749 "Location: http://%s:%d/index.html\n"
38750 "Cache-Control: no-cache\n",
38751 t->local_name, t->local_port);
38752 #else
38753 /* RFC 2616 section 10.3.3-4, pp 40-41 */
38754 /* older clients should treat 302 same as new 303 */
38755 snprintf( t->reply, WWW_TCP_MAX,
38756 "HTTP/1.1 302 Found\n"
38757 "Location: http://%s:%d/index.html\n"
38758 "Cache-Control: no-cache\n",
38759 t->local_name, t->local_port);
38760 #endif
38761 if (0 == t->ka) {
38762 asnprintf( t->reply, WWW_TCP_MAX, "Connection: close\n\n");
38763 } else {
38764 asnprintf( t->reply, WWW_TCP_MAX, "Connection: Keep-Alive\n\n");
38767 ahttp_log( t->num, t->reply );
38768 ok = send( t->accepted, t->reply, strlen(t->reply), MSG_NOSIGNAL );
38769 if (0 == t->ka)
38770 http_close( t, &t->accepted, "302" );
38771 return 0;
38774 /* CGI */
38775 /* Do not want CGI request to leave the request in browser uri entry,
38776 but seems no easy way to prevent document view from changing?
38778 if (0 != *t->cgi) {
38780 /* Go back to what brought us here to remove the browser uri cgi data */
38781 snprintf( t->reply, WWW_TCP_MAX, "HTTP/1.1 ");
38783 /* #define USE_303 */
38784 #ifdef USE_303
38785 asnprintf( t->reply, WWW_TCP_MAX, "303 See Other\n");
38786 #else
38787 asnprintf( t->reply, WWW_TCP_MAX, "302 Found\n");
38788 #endif
38790 /* #define USE_FULL_LOCATION */
38791 #ifdef USE_FULL_LOCATION
38792 asnprintf( t->reply, WWW_TCP_MAX, "Content-Location: "
38793 "http://%s:%d%s%s\n",
38794 t->local_name, t->local_port, t->cwd, t->file);
38795 asnprintf( t->reply, WWW_TCP_MAX, "Location: "
38796 "http://%s:%d%s%s\n",
38797 t->local_name, t->local_port, t->cwd, t->file);
38798 #else
38799 asnprintf( t->reply, WWW_TCP_MAX, "Content-Location: %s%s\n",
38800 t->cwd, t->file);
38801 asnprintf( t->reply, WWW_TCP_MAX, "Location: %s%s\n",
38802 t->cwd, t->file);
38803 #endif
38805 /* always close after redirect */
38806 if (0 == t->ka) {
38807 asnprintf( t->reply, WWW_TCP_MAX, "Connection: close\n");
38808 } else {
38809 asnprintf( t->reply, WWW_TCP_MAX, "Connection: Keep-Alive\n");
38812 asnprintf( t->reply, WWW_TCP_MAX, "\n");
38814 /* Send the response as 302 redirect to remove cgi from browser URI. */
38815 ahttp_log( t->num, t->reply );
38816 ok = send( t->accepted, t->reply, strlen(t->reply), MSG_NOSIGNAL );
38817 if (0 == t->ka) {
38818 /* http_close( t, &t->accepted, "302" ); */
38819 return -1; /* error return will close socket */
38823 http_get_content( t ); /* set t->ctext and t->ctype */
38826 /* Last-Modified */
38827 memset( utcmod, 0, sizeof(utcmod));
38828 http_text_time( utcmod, &fs.st_mtime);
38830 #if 0
38832 /* Cache image files. Want everything else not cached because
38833 old program guide data should not sit in any proxy, anywhere.
38835 Request: If-Modified-Since: date
38836 Reply: 304 NOT MODIFIED and return 0 if date matches file mod time
38837 Date: and Last Modified: need to be sent, too:
38839 my httpdate code for remote clock sync will need it
38841 if (0 == t->ctype) { /* is cacheable? */
38842 if (0 == strncmp( utcmod, t->ims, sizeof(utcmod))) { /* mod since? */
38843 snprintf( t->reply, WWW_TCP_MAX,
38844 "HTTP/1.1 304 Not Modified %s\n"
38845 "Date: %s\n"
38846 "Last Modified: %s\n", t->file, utcnow, utcmod );
38847 if (0 == t->ka) {
38848 asnprintf( t->reply,WWW_TCP_MAX, "Connection: close\n\n");
38849 } else {
38850 asnprintf( t->reply,WWW_TCP_MAX, "Connection: Keep-Alive\n\n");
38853 ahttp_log( t->num, t->reply );
38854 ok = send( t->accepted, t->reply, strlen(t->reply), MSG_NOSIGNAL );
38855 if (0 == t->ka)
38856 http_close( t, &t->accepted, "304" ); /* not persistent */
38857 return 0;
38860 #endif
38862 /* Range error */
38863 size = fs.st_size;
38864 if (t->rseek >= fs.st_size) {
38865 snprintf( t->reply, WWW_TCP_MAX,
38866 "HTTP/1.1 416 Range Error\n"
38867 "Cache-Control: no-cache\n"
38868 "Content-Length: %lld\n", size);
38870 if (0 == t->ka) {
38871 asnprintf( t->reply,WWW_TCP_MAX, "Connection: close\n\n");
38872 } else {
38873 asnprintf( t->reply,WWW_TCP_MAX, "Connection: Keep-Alive\n\n");
38876 ahttp_log( t->num, t->reply );
38877 ok = send( t->accepted, t->reply, strlen(t->reply), MSG_NOSIGNAL );
38878 if (0 == t->ka)
38879 http_close( t, &t->accepted, "416" );
38880 return -1;
38883 size = fs.st_size;
38885 /* open file */
38886 clock_gettime( clock_method, &e0 );
38887 t->infile = open( fn, O_RDONLY );
38889 if (t->infile < 3) {
38891 /* 404 here is error opening it for read only */
38892 snprintf( t->reply, WWW_TCP_MAX,
38893 "HTTP/1.1 404 Not Found\n"
38894 "Cache-Control: no-cache\n" );
38896 if (0 == t->ka) {
38897 asnprintf( t->reply,WWW_TCP_MAX, "Connection: close\n\n");
38898 } else {
38899 asnprintf( t->reply,WWW_TCP_MAX, "Connection: Keep-Alive\n\n");
38902 ahttp_log( t->num, t->reply );
38903 ok = send( t->accepted, t->reply, strlen(t->reply), MSG_NOSIGNAL );
38904 return -1;
38907 advrlog( LOG_INFO, "%s t%d open %2d seek %lld %s\n",
38908 WHO, tnum, t->infile, t->rseek, fn);
38910 count = 0LL;
38911 end = size - 1LL;
38912 start = t->rseek;
38914 *t->reply = 0;
38916 if (0 != t->rseek) { /* wget with -c continue option */
38917 asnprintf( t->reply, WWW_TCP_MAX, "HTTP/1.1 206 Partial Content\n" );
38918 asnprintf( t->reply, WWW_TCP_MAX, "Content-Length: %lld\n", size-start);
38919 asnprintf( t->reply, WWW_TCP_MAX, "Content-Range: %lld-%lld/%lld\n",
38920 start, end, size);
38921 } else {
38922 asnprintf( t->reply, WWW_TCP_MAX, "HTTP/1.1 %03d OK\n", t->rnum);
38923 asnprintf( t->reply, WWW_TCP_MAX, "Content-Length: %lld\n", size);
38925 asnprintf( t->reply, WWW_TCP_MAX, "Content-Type: %s\n", t->ctext);
38926 asnprintf( t->reply, WWW_TCP_MAX, "Accept-Ranges: bytes\n" );
38927 asnprintf( t->reply, WWW_TCP_MAX, "Last-Modified: %s\n", utcmod );
38929 #if USE_20X_LOCATION
38930 /* this breaks xine for some reason, maybe needs Content-Location: */
38933 asnprintf( t->reply, WWW_TCP_MAX, "Location: %s%s\n",
38934 t->cwd, t->file);
38936 asnprintf( t->reply, WWW_TCP_MAX, "Content-Location: %s%s\n",
38937 t->cwd, t->file);
38938 #endif
38940 /* if expiration date is in the future, browser cache should not get it.
38941 if expiration date is in the past:
38942 if-modified-since, see above, limits what is returned
38944 server = NAME "-" VER "-" LASTEDIT;
38946 asnprintf( t->reply, WWW_TCP_MAX, "Server: %s\n", server);
38947 asnprintf( t->reply, WWW_TCP_MAX, "Host: %s\n", t->local_name);
38948 asnprintf( t->reply, WWW_TCP_MAX, "Date: %s\n", utcnow);
38950 /* images need Expires: header for browser cache to work,
38951 depending on browser http level */
38952 if (0 == t->ctype) {
38953 utexp = utnow + 86400; /* 24 hours ahead */
38954 http_text_time( utcexp, &utexp );
38956 asnprintf( t->reply, WWW_TCP_MAX, "Expires: %s\n", utcexp);
38958 /* FIXME: this will make it always redraw the images, not very efficient,
38959 but for ch.png signal image, it might improve initial load.
38961 asnprintf( t->reply, WWW_TCP_MAX, "Cache-Control: must-revalidate\n");
38964 } else {
38966 /* everything else gets no-cache */
38967 asnprintf( t->reply, WWW_TCP_MAX, "Cache-Control: no-cache\n");
38970 #if 1
38971 if (0 == t->ka) {
38972 asnprintf( t->reply, WWW_TCP_MAX, "Connection: close\n\n");
38973 } else {
38974 asnprintf( t->reply, WWW_TCP_MAX, "Connection: Keep-Alive\n\n");
38976 #endif
38978 // ahttp_log( tnum, "%s:%d %s", WHO, WHERE, t->reply );
38980 ok = send( t->accepted, t->reply, strlen(t->reply), MSG_NOSIGNAL );
38981 if (ok != strlen( t->reply)) return -1;
38982 if (0LL != t->rseek) {
38983 if (t->rseek >= size) t->rseek = 0LL;
38984 count = lseek( t->infile, t->rseek, SEEK_SET );
38987 /* at least one block to try */
38988 ok = 1;
38990 /* FIXME: check for receiving close connection to terminate transfer early */
38991 /* ahttp_log( t->num, "Sending %d bytes...", size); */
38993 /* use the memory string instead? it has to be broken down to w->mtu chunks */
38994 if ((NULL != t->ram) && (t->ramz > 0)) {
38995 dvrlog( LOG_INFO, "%s uses memory", t->file);
38996 #if 0
38997 count = 0;
38998 while(1) {
38999 len = t->ramz - count;
39000 if (len <= 0) break;
39001 if (len > w->mtu) len = w->mtu;
39002 ok = send( t->accepted, &t->ram[count], len, MSG_NOSIGNAL );
39003 count += len;
39005 // ifree(t->ram);
39006 t->ramz = -1;
39008 dvrlog( LOG_INFO, "%s sent %d", t->file, t->ramz);
39009 #endif
39011 } else {
39013 /* is not using memory string, consider above string commented until tested */
39014 advrlog( LOG_INFO, "tcp send blkz %d", w->mtu);
39015 while (ok > 0) {
39016 if (0 == t->rtype) {
39017 ahttp_log( t->num, "headers only");
39018 break; /* only sending headers */
39020 if (count < 0LL) break;
39021 len = read( t->infile, t->tsbuf, w->mtu );
39023 /* EOF or error */
39024 if (len < 1) break;
39026 /* NOTE: byte count return from read handles EOF last block len correctly */
39027 count += len;
39028 ok = -1;
39029 if (t->accepted < 3) break;
39031 ok = send( t->accepted, t->tsbuf, len, MSG_NOSIGNAL );
39032 /* ahttp_log( t->num, "send() returns %d", ok); */
39034 #if 0
39035 /* cpu usage redux. also reduces 1g enet r8169 speed from 62m/s to 58m/s */
39036 if ( (count & 0xFF) == 0) {
39037 pthread_yield();
39038 nanosleep( &www_write_sleep, NULL );
39040 #endif
39043 /* ahttp_log( t->num, "SENT %d bytes", count); */
39045 if (ok < 0) t->rtype = 3;
39047 if (0 == count) count = 1;
39049 if (1 == t->rtype) {
39050 if (start > 0) {
39051 t->rtype = 3;
39055 if ((t->rtype < 1) || (t->rtype > 3)) t->rtype = 0;
39057 /* TODO: rate computation to set refresh delay. pretzel logic */
39058 clock_gettime( clock_method, &e1 );
39059 time_diff( &et, &e0, &e1 );
39061 ahttp_log(t->num, "ETA %d.%09d %s",(int)et.tv_sec, (int)et.tv_nsec, fn);
39063 close( t->infile );
39065 if (0 == t->ka) {
39066 ahttp_log(t->num, "http/1.%d closing socket %d t->ka=%d EOF",
39067 t->htver, t->accepted, t->ka);
39068 http_close( t, &t->accepted, "EOF");
39069 } else {
39070 ahttp_log(t->num, "http/1.%d keeping socket %d t->ka=%d EOF",
39071 t->htver, t->accepted, t->ka);
39074 return 0;
39077 /* Each invocation runs/blocks/sleeps until SIGINT kills all of them. */
39078 /* static */
39079 void *
39080 http_loop( void * targ )
39082 struct tts_s *t = (struct tts_s *) targ;
39083 struct wss_s *w;
39084 int tnum, ok1, ok2, ok3;
39086 w = &wss;
39088 tnum = t->num;
39090 chdir( w->root ); /* jail */
39092 /* this loop runs detached so it can die asynchronously */
39093 advrlog( LOG_INFO, "%s%d detached %d", WHO, tnum, getpid() );
39095 w->ptuse++;
39096 w->sig = -1;
39098 t->tid = getpid();
39099 t->accepted = -1;
39101 while( 1 ) {
39102 if (0 == t->tid) break;
39104 /* Set mtu size to a multiple of transport packet size.
39105 See http://www2.rad.com/networks/2006/pmtu/detect.htm
39106 Bob and Alice have overkill for the local net design of this application,
39107 but the ICMP fragmentation detection is a great idea for using routers.
39110 t->rnum = 0; /* reply # cleared until changed */
39112 /* FIXME? default to keepalive off, should be on: is done already? */
39113 t->local_port = w->port;
39115 memset( t->file, 0, WWW_TCP_MAX);
39116 memset( t->cwd, 0, WWW_TCP_MAX);
39118 astrncpy( t->file, "index.html", 16 );
39119 astrncpy( t->cwd, "/", 4);
39121 ok1 = ok2 = ok3 = 0;
39123 ok1 = http_accept( w, tnum );
39124 t->utska = utsnow + 5;
39126 /* keep reading/responding until keep alive expires */
39127 while (0 == ok1) {
39128 if (-1 == t->accepted) ok1 = -1;
39129 if (t->utska < utsnow) ok1 = -1;
39130 /* aborted connect should close */
39131 if (0 != ok1) break;
39133 /* aborted request should close connection, client or host can do it */
39134 ok2 = http_request( w, tnum );
39135 if (0 == ok2) {
39136 /* TODO: pipeline mode */
39137 ok3 = http_response( w, tnum );
39138 if (0 != ok3) break;
39139 /* look for next request or stop looping if no keep alive */
39140 continue;
39141 } else break;
39143 /* aborted response should close the connection */
39146 /* close connection */
39147 if (t->accepted > 2) {
39148 if (0 != ok1)
39149 http_close( t, &t->accepted, "accept");
39150 if (0 != ok2)
39151 http_close( t, &t->accepted, "request");
39152 if (0 != ok3)
39153 http_close( t, &t->accepted, "response");
39155 /* nanosleep( &www_bind_sleep, NULL); */
39159 dvrlog( LOG_INFO, "%s %d terminating %d",
39160 WHO, tnum, getpid() );
39162 w->ptuse--;
39163 t->tid = 0;
39164 return NULL;
39167 /* create up to w->ptmax threads, or none */
39168 /* static */
39170 http_create_threads( struct wss_s *w )
39172 int i, ok;
39173 struct tts_s *t;
39175 if (0 == w->ptmax ) return -1;
39177 for (i=0; i < w->ptmax; i++) {
39178 t = &w->tss[ i ];
39179 t->num = i;
39181 /* all have to return 0 or nothing done */
39182 memset( &t->thttp, 0, sizeof(pthread_t) );
39183 ok = pthread_attr_init( &t->tattr );
39184 ok |= pthread_attr_setdetachstate( &t->tattr,
39185 PTHREAD_CREATE_DETACHED );
39186 ok |= pthread_create( &t->thttp, &t->tattr,
39187 http_loop, (void *) &w->tss[i] );
39189 /* no errors */
39190 if (0 == ok) {
39191 advrlog( LOG_INFO, "www t%d created\n", i);
39192 strcpy( t->cwd, "/");
39193 strcpy( t->file, "index.html");
39196 /* an error, shutdown server */
39197 if (0 != ok) {
39198 http_close_sockets();
39199 return -1;
39202 /* nanosleep( &www_tcreate_sleep, NULL); */
39204 return 0;
39207 /* wait for bind socket and create threads.
39208 this is done as a thread itself so it does not interfere
39209 with the startup of any current timers */
39210 /* static */
39211 void *
39212 http_start ( void * targ )
39214 struct wss_s *w;
39215 struct ifreq ifr;
39216 int ioc, mtu, tb;
39218 w = &wss;
39220 w->bind_try = 1;
39222 if (0 != http_bind_socket( w )) return NULL;
39224 epg_www = ~0;
39226 /* set socket to listen for activity; TESTME: backlog is 2 */
39227 if (0 == http_socket_listen( w, 2 ) ) {
39228 http_create_threads( w );
39229 nanosleep( &www_bind_sleep, NULL);
39232 /* FIXME: query 'eth0' until can figure out how to query by address */
39233 ifr.ifr_addr.sa_family = AF_INET;
39234 astrncpy( ifr.ifr_name, "eth0", sizeof(ifr.ifr_name) );
39235 ioc = ioctl( w->sock, SIOCGIFMTU, &ifr);
39237 /* normal ethernet default if ioctl fails */
39238 mtu = 1492;
39239 if (0 == ioc) mtu = ifr.ifr_mtu;
39240 advrlog( LOG_INFO, "%d SIOCGIFMTU %d", ioc, mtu);
39242 /* compute w->mtu setting from compiled default to override above */
39243 tb = mtu;
39244 tb /= 188;
39246 /* at least one packet will be sent regardless of how small mtu is */
39247 if (tb < 1) tb = 1;
39248 tb *= 188;
39250 /* limit http response to use w->mtu or WWW_TCP_MAX chunks */
39251 w->mtu = WWW_TCP_MAX;
39252 if (tb < WWW_TCP_MAX) w->mtu = tb;
39254 advrlog( LOG_INFO, "using MTU %d (%d*188)", w->mtu,w->mtu/188);
39255 return NULL;
39258 /* static */
39259 void
39260 http_init ( void )
39262 int ok;
39263 struct wss_s *w;
39264 struct tts_s *t;
39265 char *p;
39267 w = &wss;
39269 w->ptuse = 0; /* threads in use */
39270 w->ptmax = arg_wthreads; /* threads to spawn */
39271 w->ptz = sizeof( struct tts_s );
39273 w->sock = -1; /* main socket state */
39274 w->bound = -1;
39275 w->port = arg_wport;
39276 w->mask = arg_wmask;
39277 w->addr = arg_waddr;
39278 w->bind_try = 0;
39280 /* Thread task state structure allocation. */
39281 /* Valgrind says no one frees this, whoops */
39282 /* Here's why:
39283 SIG* handler so far has no way of finding thread ID, so no way yet
39284 to synchronize the thread destruction until only main left.
39285 Leaving this thread for valgrind to complain about solves segfault.
39286 dvrlog thread is also left allocated and is source of other unfreed bytes.
39287 An ithread allocation list similar to imalloc could help this.
39290 w->tss = (struct tts_s *)icalloc( w->ptmax, w->ptz, "http tts" );
39292 if (NULL == w->tss) {
39293 dvrlog( LOG_INFO,
39294 "%s failed tts_s calloc %d = (%dx%d)\n", WHO,
39295 w->ptmax * w->ptz, w->ptmax, w->ptz );
39296 return;
39299 get_time();
39300 http_log( 0, "HTTP Started" );
39302 /* out_path is smaller than w->root */
39303 astrncpy( w->root, out_path, sizeof(w->root) );
39305 /* remove multiple following // */
39306 if (strlen( w->root) > 1) {
39307 p = w->root + strlen(w->root);
39308 p--;
39309 while('/' == *p) *p-- = 0;
39310 } else {
39311 strcpy( w->root, "/dtv");
39312 http_log( LOG_INFO, "www root jailed to %s", w->root);
39316 t = &w->tss[0];
39317 strcpy( t->cwd, "/" );
39318 strcpy( t->file, "index.html" );
39320 /* need to create the initial index html file */
39321 http_index_html( w, 0, WHO );
39323 /* need to create the server status and configuration html file */
39324 dump_cfg_html( WHO );
39326 /* http start binds and listen, then starts threads to handle connects */
39327 /* It's done as a thread so it doesn't interfere with current timer start. */
39329 memset( &w->tws, 0, sizeof(pthread_t) );
39330 ok = pthread_attr_init( &w->twsattr );
39331 ok |= pthread_attr_setdetachstate( &w->twsattr,
39332 PTHREAD_CREATE_DETACHED );
39333 ok |= pthread_create( &w->tws, &w->twsattr,
39334 http_start, NULL );
39336 if (0 != ok) dvrlog( LOG_INFO, "http_start thread failed");
39337 nanosleep( &www_tcreate_sleep, NULL );
39339 #if 0
39340 /* initial epg filters */
39341 pgm3.hide = pgm.hide;
39342 pgm3.age = pgm.age = ~0;
39343 pgm3.find = pgm.find;
39344 #endif
39348 /* USE_WWW end */
39349 #endif
39351 /* parse args, open video device
39352 toggle between timer/channel list or capture
39354 /************************* httpd functions end *****************************/
39356 /* mutex init for non-fifo, fifo is init elsewhere */
39357 /* static */
39358 void
39359 init_mutex( void )
39361 pthread_mutex_init( &dvrlog_mutex, NULL );
39362 pthread_mutex_init( &caplog_mutex, NULL );
39363 pthread_mutex_init( &tsid_mutex, NULL );
39364 pthread_mutex_init( &grid_mutex, NULL );
39365 pthread_mutex_init( &epg_mutex, NULL );
39366 pthread_mutex_init( &spam_mutex, NULL );
39368 #ifdef USE_PNG
39369 pthread_mutex_init( &png_mutex, NULL );
39370 #endif
39372 #ifdef USE_GNU_BACKTRACE
39373 pthread_mutex_init( &bt_mutex, NULL );
39374 #endif
39379 /**************************** main start ***********************************/
39381 main( int argc, char ** argv, char ** envp )
39383 int i;
39384 char *sty;
39385 char n[64];
39386 FILE *f;
39388 udp_mcast = 0; /* shut up compiler */
39390 /* clear log */
39391 memset(dvrlog_list, 0, sizeof(dvrlog_list));
39392 dvrlog_idx = 0;
39394 uid = getuid();
39395 euid = geteuid();
39396 gid = getgid();
39397 egid = getegid();
39399 init_allocs();
39401 init_mutex(); /* logfile and epg mutex init */
39403 get_time();
39405 #ifdef USE_POWERDOWN
39406 utspdd = utsnow + USE_POWERDELAY;
39407 #endif
39409 test_clock_res(); /* find most accurate clock */
39411 #ifndef USE_DYNAMIC
39412 /* only allocate the big 9M FIFO once at the start if not USE_DYNAMIC */
39413 init_fifo( &ts_fifo, fifoz );
39414 #endif
39416 pid_m = getpid();
39418 #ifdef USE_SIGNALS
39419 signal_init(); /* sigaction setup for control c trap */
39420 #endif
39421 signal_timeout = 0;
39423 /* in case starts up in middle of a timer, will get something */
39424 cap_vc = cap_va = 0;
39426 /* set utc_offset for ATSC timestamp vs Unix timestamp difference */
39427 atsc_calc_epoch();
39429 /* save current DST status to prevent extra config load from DST change */
39430 get_time();
39431 pisdst = tloc.tm_isdst;
39433 /* config z line, if Z set force time update asap. zero does not? utszto? */
39434 /* cap_zto = utsnow - 1801; // make time update trigger asap */
39436 utc_up = utsnow; /* uptime */
39438 /* hold down test guides, user sanity and if start in middle of event */
39439 /* -1 for arg_devnum will make it timeout immediately */
39440 utstpg = utsnow + (PROGRAM_GUIDE_TIMEOUT * arg_devnum);
39442 parse_args( argc, argv );
39444 /* initialize the frequency tables to ATSC (default) or various QAM256 modes
39445 because might be doing non-forked interactive modes, i.e.
39446 not using screen or bash script with -s option
39448 init_channels();
39450 set_title();
39452 #ifdef SCREEN
39453 sty = getenv( "STY" );
39454 /* detach mode doesn't need screen, screen has more flexible detach */
39455 if ((arg_detach == 0) && (arg_screen != 0) )
39457 /* STY set means screen is already running, else needs to run */
39458 if (NULL == sty) {
39459 /* copy parms to argx, first is screen, last is null */
39460 char *argx[32];
39461 char argn[8]; /* device num */
39462 int k;
39463 snprintf( argn, sizeof(argn)-1, "dtv%d", arg_devnum);
39464 /* start screen with parameters passed to pchdtvr */
39465 k = 0;
39467 -R option to reattach a screen session matching the device number
39468 it's a one way ticket if there is an error, so show the parms
39470 if (arg_sattach != 0) {
39471 argx[k++] = "screen";
39472 argx[k++] = "-d";
39473 argx[k++] = "-r";
39474 argx[k++] = argn;
39475 argx[k++] = NULL;
39476 fprintf( stdout, "re-attach execvp: %s %s %s %s\n",
39477 argx[0], argx[1], argx[2], argx[3]);
39478 asyslog( LOG_INFO, "screen reattaching %s with %s %s %s %s",
39479 argn, argx[0], argx[1], argx[2], argx[3]);
39481 i = execvp( "screen", argx );
39482 syslog( LOG_INFO, "screen de/reattach failed, return %d", i);
39483 /* should only see if error in exec like file not found? */
39484 /* this fall through should never happen */
39485 uses_screen = 0;
39486 in_screen = 0;
39487 console_reset();
39488 fprintf(stderr,
39489 CLS BW "GNU screen not found for reattach.\n" BN);
39490 exit(1);
39491 } else {
39492 asyslog( LOG_INFO, "creating new screen named %s", argn);
39493 k = 0;
39494 argx[k++] = "screen";
39495 argx[k++] = "-a";
39496 if (arg_sdetach != 0) {
39497 argx[k++] = "-d"; /* -d detaches screen with no wait */
39498 argx[k++] = "-m";
39500 argx[k++] = "-S";
39501 argx[k++] = argn;
39502 for (i=0; i<argc; i++) if ( (i+k) < 32 ) argx[i+k] = argv[i];
39503 if ( (i+k) < 32) argx[i+k] = NULL;
39504 argx[31] = NULL;
39505 asyslog( LOG_INFO, "%s %s %s %s %s %s...\n", argx[0], argx[1], argx[2], argx[3], argx[4], argx[5]);
39506 fprintf( stdout, "execvp: %s %s %s %s %s %s...\n", argx[0], argx[1], argx[2], argx[3], argx[4], argx[5]);
39507 i = execvp( "screen", argx );
39508 syslog( LOG_INFO, "screen failed, return %d", i);
39509 /* should only see if error in exec like file not found? */
39510 /* this fall through seems to be faulty, so exit now with user warning */
39511 /* not sure why it breaks if it falls through instead the exit placed here */
39512 uses_screen = 0;
39513 in_screen = 0;
39514 console_reset();
39515 fprintf( stderr, CLS BW "GNU screen not found.\n" BN);
39516 exit(1);
39518 } else {
39519 asyslog( LOG_INFO, "GNU screen is running on %s", sty);
39520 uses_screen = ~0;
39521 in_screen = ~0;
39524 #endif
39526 test_termsize(); /* lines and columns */
39528 display_sigtype = SIG_STR;
39530 /* transport stream save path and program guide save path */
39532 char opg[256]; /* file name */
39534 /* make /dtv/ (or -p directory) */
39535 mkdir( out_path, 0777 );
39536 chdir( out_path );
39538 /* make /dtv/ram */
39539 snprintf( opg, sizeof(opg)-1, "%s/ram", out_path);
39540 if (0 != arg_tmpfs) mkdir( opg, 0777 );
39542 /* make /dtv/pg/ */
39543 snprintf( opg, sizeof(opg)-1, "%s/pg", out_path);
39544 mkdir( opg, 0777 );
39546 /* make /dtv/pg/img */
39547 snprintf( opg, sizeof(opg)-1, "%s/pg/img", out_path);
39548 mkdir( opg, 0777 );
39552 /* normal or test log, try device open to find out */
39553 open_device( WHO );
39555 if (( 0 == arg_dummy) && (0 == arg_nosplash)) {
39557 char kb;
39559 /* driver klogs "modulation mode (0) not supported" if wait is too short */
39560 /* this may not be needed anymore ? */
39561 cap_chan = 2;
39562 set_channel( (struct sig_s *) &ptc[2].sig, WHO );
39564 /* NOTE: use stderr or else first user input clears the screen:
39565 as in [r] for reschedule and [t] for timer
39567 aprintf( stderr, HOM CCE );
39568 aprintf( stderr, "%s%s", welcome, BN );
39569 nanosleep( &signal_long_sleep, NULL );
39570 kb = console_getch(); /* control c trap */
39571 aprintf( stderr, "\n%s%s", boilerplate, BN );
39572 nanosleep( &signal_long_sleep, NULL );
39573 kb = console_getch(); /* control c trap */
39574 show_mem_use();
39576 /* this delay may need to be increased for software i2c firmware load */
39577 /* it is barely enough for hardware i2c firmware load of 3s */
39578 /* don't set the channel until ucode loaded or you'll get errors */
39579 nanosleep( &msg_long_sleep, NULL );
39580 kb = console_getch(); /* control c trap */
39581 aprintf( stderr, "%s%s", HOM, CCE );
39584 /* give correct name on assumption it will find a device and open it */
39585 test_mode = 0;
39586 snprintf( syslog_name, sizeof(syslog_name)-1, NAME "%d", arg_devnum );
39588 /* change name on assumption it would not open */
39589 if ( (0 > in_file) || (0 != arg_dummy) ) {
39590 test_mode = ~0;
39591 snprintf( syslog_name, sizeof(syslog_name)-1, NAME "-test" );
39594 /* only force test mode to zero if dummy not overriding */
39595 if ( 0 == arg_dummy ) test_mode = 0;
39597 /* close it in case forking, will open later. set logname */
39598 if (0 != arg_detach) {
39599 close_device( WHO );
39602 /* bring up ram filesystem */
39603 load_tmpfs( );
39605 openlog( syslog_name, LOG_NDELAY, LOG_DAEMON );
39607 syslog( LOG_INFO, "%s %s DVB %s %s",
39608 VERSION, LASTEDIT, in_name, fe_text3 );
39610 #ifndef USE_SYSLOG
39611 init_dvrlog();
39612 #endif
39614 /* let init val of mtime = 0 cause reload */
39615 test_config(0, WHO );
39618 /* -S option sets it up by scanning thru all frequencies for stations */
39619 /* if -S option is not specified, but no config file, auto-scan anyway */
39620 if ((0 != arg_scan) || (0 == scan_idx)){
39621 arg_scan = ~0;
39623 /* is ok to try to continue after scanning now, instead of old restart */
39624 find_stations();
39627 /* blank console */
39628 aprintf( stderr, CLS );
39630 show_headers( 1 );
39632 display_type = D_TIMERS;
39633 refresh.timers = 1;
39634 refresh.pstats = 1;
39635 refresh.estats = 1;
39636 refresh.vstats = 1;
39638 /* initial free stats load, updates @ 443s */
39639 statfs( out_path, &vol_fsp );
39640 statfs( cut_path, &cut_fsp );
39642 /* FIXME: Keep track of this as only thread that listens for signals */
39643 /* FIXME: pthread_self() does not work as id for main() w/ LinuxThreads 0.10 */
39644 /* FIXME: This fails w/ NPTL because all threads have same getpid(). yikes. */
39646 /* NOTE: fork causes some PITA to open device each branch */
39647 /* FIXME: rewrite ts capture to open the device instead */
39648 /* this is a dynamic feature i've been thinking about */
39649 /* but it will require almost 10s for open and AOS, secdt = 10 not 5 */
39651 /* one shot capture for arg_capture seconds */
39652 if (0 != arg_capture) {
39653 cap_now = CAP_USER;
39654 cap_chan = scan_list[0];
39656 if (0 != arg_detach)
39658 pid_t fp;
39659 asyslog( LOG_INFO, "forking main() pid %d", getpid() );
39661 /* no zombies */
39662 fp = fork();
39663 /* two paths of possible execution based on return value
39664 fp = 0 is forked path, otherwise it's parent path exiting
39665 NOTE: BOTH will happen, so both need console_ ? exit(0)
39667 if (0 == fp)
39669 /* new forked path, no ctl'n tty */
39670 pid_m = getpid();
39671 asyslog( LOG_INFO,
39672 "capture forked, main() is now pid %d", pid_m );
39673 /* moved because of fork */
39674 open_device( WHO );
39675 if (in_file > 2) ts_capture();
39676 console_exit(0);
39679 /* old forked path, fork above is detached, so this path exits */
39680 console_exit(0);
39683 /* not arg_detach, do it interactive without config file, then exit */
39684 open_device( WHO );
39685 if (2 < in_file) ts_capture();
39686 console_exit(0);
39689 /* arrival here with arg_detach means timers used, no stdin interaction */
39690 /* daemon mode, control via config file and http interface only */
39691 if (0 != arg_detach)
39693 pid_t fp;
39694 fp = fork();
39695 if (0 == fp)
39697 /* new forked path, will fall thru */
39698 pid_m = getpid();
39699 asyslog( LOG_INFO,
39700 "daemon forked, main() is now pid %d", pid_m );
39701 /* fall thru to below */
39702 } else {
39703 /* old forker must die right here */
39704 console_exit(0);
39708 /* detached fork falls thru to here for timer business as usual */
39709 pid_m = getpid(); /* what is my process id? */
39711 /* moved here because of fork */
39712 if (0 != arg_detach) open_device( WHO );
39714 /* parse args and start up should use syslog, rest use dvrlog */
39715 // nanosleep( &signal_loop_sleep, NULL);
39717 #ifndef USE_SYSLOG
39718 init_dvrlog();
39719 #endif
39721 dvrlog( LOG_INFO, VERSION " " LASTEDIT " DVB %s %s",
39722 in_name, fe_text3 );
39724 log_clock_res();
39726 pid_m = getpid();
39727 advrlog( LOG_INFO, "main pid %d", pid_m );
39728 snprintf(n, sizeof(n), "/var/run/%s/%s%d.pid", NAME, NAME, arg_devnum);
39729 f = fopen(n, "w");
39730 if (NULL != f) {
39731 fprintf( f, "%d\n", pid_m);
39732 fflush(f);
39733 fclose(f);
39734 } else {
39735 dvrlog( LOG_INFO, "write %s failed", n);
39738 get_dvr_count(); /* sets www_dvrs for html header generate */
39740 /* display device info after second device open */
39741 dvrlog( LOG_INFO, "%s", fe_text1);
39742 dvrlog( LOG_INFO, "%s", fe_text2);
39744 #ifdef USE_MCAST
39746 struct udp_s *u = &udp;
39747 /* open socket after second device open */
39748 if (0 != arg_mcport) udp_open_socket( u );
39750 #endif
39752 #ifdef USE_WWW
39753 if (0 != arg_www) http_init();
39754 #endif
39757 #ifdef USE_MMAN
39758 mlockall( MCL_CURRENT );
39759 #endif
39761 /* init some display fields in case startup in middle of timer */
39762 read_capvol_stats();
39763 read_cutvol_stats();
39764 get_time();
39765 utscap = utsnow;
39766 snprintf(csp, 8, "\033[3;1H");
39768 /* TODO: need more than this. have to change all the owners of all the dirs */
39769 if (0 != arg_setuid) {
39770 setgid(arg_setgid);
39771 setuid(arg_setuid);
39773 refresh.headers = 1;
39775 /*************************** main interactive loop ***************************/
39776 while(1)
39778 channel_scan( );
39779 if ( CAP_NONE != cap_now )
39781 /* enable this to debug each loop
39782 while ( console_getch() == 0 );
39784 ts_capture( );
39786 /* enable this to debug each loop
39787 while ( console_getch() == 0 );
39790 /****************************** main stop ***********************************/
39792 /* nothing below here should be seen, log it if it is */
39793 dvrlog( LOG_ERR, "main() should not have returned at line %d", __LINE__ );
39795 fprintf( stdout,
39796 CLS "main() should not have returned at line %d\n",
39797 __LINE__ );
39799 return 0;