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__
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
36 ATSC Capture Application Program
37 Copyright (c) 2004-2008 by inkling@users.sourceforge.net
41 /* COPYRIGHT FAIR USE INTENT DISCLAIMER ***************************************
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: *
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. *
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: *
53 * SONY CORP. V. UNIVERSAL CITY STUDIOS *
54 * 464 U.S. 417, 104 S. Ct 774, 78L. Ed 2d 574 (1984) *
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.
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 */
147 #define WWW_HOST "dtv"
151 #define WWW_PORT 1380
155 #define WWW_THREADS 3
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
189 #warning using POWERDOWN delay 300s
190 #define USE_POWERDELAY 300
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 */
198 /* only define this if layout can't be achieved through proper means */
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 */
226 /* set these before includes to trigger different methods */
232 /* #define _LARGEFILE_SOURCE */
233 /* fseeko64 ftello64 */
234 /* #define _LARGEFILE64_SOURCE */
235 /* which one to use by default */
236 #define _FILE_OFFSET_BITS 64
239 /* linuxthreads docs say #define _REENTRANT for thread-safe glibc functions. */
241 #include <sys/select.h>
254 #include <sys/ioctl.h>
258 #include <sys/time.h>
260 #include <sys/mount.h>
262 /* kb blinkies ioctl vals */
263 #include <linux/kd.h>
265 /* will need for signal actions, sigterm mostly */
268 #warning using POSIX signals
272 /* statfs to get free space */
273 // #include <sys/vfs.h>
274 #include <sys/statfs.h>
277 /* openlog syslog closelog syslog(3) */
280 #warning using Linux DVB
281 #include <linux/dvb/frontend.h>
282 #include <linux/dvb/dmx.h>
284 /* make atscap-udp */
286 #warning using TCP for HTTP
287 #include <sys/socket.h>
288 #include <netinet/in.h>
290 #include <arpa/inet.h>
295 #ifdef USE_GNU_BACKTRACE
296 #warning Using GNU backtrace()
297 #include <execinfo.h>
301 #include <ucontext.h>
306 /* non-break space, won't split on this */
307 #define NBSP " "
310 #warning using PNG for signal chart
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
339 #warning using UDP for MULTICAST
340 #include <sys/socket.h>
341 #include <netinet/in.h>
342 #include <arpa/inet.h>
346 /* POSIX unistd.h sets _POSIX_MEMLOCK, unistd.h is above */
347 #ifdef _POSIX_MEMLOCK
349 #include <sys/mman.h>
350 #warning using POSIX mlockall()/munlockall()
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 */
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
383 #define O_STREAMING 0
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 */
395 /* transport stream sync byte */
398 /* transport stream packets per block */
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 */
424 /* standard minute */
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:
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()
449 #define KR_UP 0x1B5B41
452 #define KR_DN 0x1B5B42
455 #define KR_RT 0x1B5B43
458 #define KR_LF 0x1B5B44
461 /* things get a bit odder here. may have to redefine if not xterm/aterm. */
463 #define KR_HM 0x1B5B317E
466 #define KR_IN 0x1B5B327E
469 #define KR_DL 0x1B5B337E
472 #define KR_EN 0x1B5B347E
475 #define KR_PU 0x1B5B357E
478 #define KR_PD 0x1B5B367E
481 /* these color definitions assume linux vt102/xterm + ecma48 support */
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 */
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 */
499 #define HOM "\033[1;1H"
501 #define DCARC "\033[%d;%dH"
503 /* version+email then last cap status */
506 #define DCA_SPACE "\033[1;32H"
508 #define DCA_CLOCK "\033[1;59H"
510 #define DCA_HEAD2 "\033[2;1H"
512 #define DCA_INPUT "\033[2;1H"
514 #define DCA_SIG "\033[2;18H"
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"
521 #define DCA_HEAD4 "\033[4;1H"
523 #define DCA_HELP "\033[5;1H"
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"
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 */
556 /* set cursor invisible and visible
560 #define SCI "\033[?25l"
561 #define SCV "\033[?25h"
563 /* dumbterm or printer use has been limited by direct cursor address */
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"
578 #define BW "\033[1;37m"
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"
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"
602 /* bar is 0-49, 2% increments */
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
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"
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 */
671 BN
,BB
,BM
,BR
,BY
,BG
,BC
,BW
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"
679 BW NAME
" is free GPL software. Any use" BR
"*" BW
" implies acceptance of the "
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"
685 BR
"*USE " NAME
" AT YOUR OWN RISK. " NAME
" IS DISTRIBUTED WITHOUT ANY WARRANTY.\n"
688 /* every option program needs one of these */
691 " "NAME
" {-options}\n"
693 " -h Help? This is it! Good Luck!\n"
695 " -v Show version, boilerplate and buffer use\n"
697 " -i # Input device #, default is 0 for /dev/dvb/adapter0\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"
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"
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"
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"
718 " -o file Output filename override default of MMDD-hhmm-CH.ts\n"
719 " Mostly useful for single capture from cron script.\n"
722 " -p path Capture output path override, default is /dtv/.\n"
723 " Remember you can symlink /dtv/ anywhere you like.\n"
726 " -g seconds change default guide load/display timeout.\n"
728 " -z nn [99] Zap cap if QoS drops below this value for a minute.\n"
730 " -k Klutz Keyboardo mode, disables stdin. Use for demos.\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"
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"
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"
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"
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"
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"
761 " GNU SCREEN OPTIONS ARE CONSIDERED EXPERIMENTAL BUT WORK FINE.\n"
763 " -W Use GNU screen to manage the session. Default: no.\n"
765 " -D Use GNU screen to start detached. Default: no.\n"
767 " -R Use GNU screen to re-attach session. Default: no.\n"
769 "********************************* EXPERIMENTAL ****************************\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"
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"
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"
789 "***************** EXPERIMENTAL TCP HTTP WEB SERVER EXAMPLES ***************\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"
797 " /etc/hosts.allow:\n"
798 " atscap: 192.168.1.\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"
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"
814 " NOTE: xine http:// doesn't do Range: but mplayer reportedly does.\n"
819 "************** EXPERIMENTAL ************* UDP MULTICAST SERVER ************\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"
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"
830 " You have to set the Virtual Program Number because most\n"
831 " hardware players can't understand multiplexed streams.\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"
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"
841 " UDP works live because ATSC dvb device is the throttle.\n"
842 " With TCP + software playback, the decoder is the throttle.\n"
846 " For capture system [as root]:\n"
847 " # " NAME
" -u 224.1.2.3:1234\n"
849 " For player system(s) [as user]:\n"
850 " $ xine udp://224.1.2.3.4:1234\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"
859 "************** EXPERIMENTAL **************** MISCELLANY *******************\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"
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"
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"
875 " -e Extra error detail toggle off. This will not fill\n"
876 " the .html capture log with error per second detail.\n"
878 "************** FILE SYSTEMS ***********************************************\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"
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"
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"
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"
894 " NOTE: USB storage: use separate controllers to make it faster.\n"
896 "***************************************************************************\n"
899 "************************************ TODO *********************************\n"
900 "ENVIRONMENT VARIABLES: [TODO: need to write parse envars()...]\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"
907 " If someone wants to write these, go ahead! It's far down my own list.\n"
908 "***************************************************************************\n"
915 "EXAMPLE OPTION USAGE:\n"
917 " Check device number X for stations and create "
918 "/etc/" NAME
"/atscapX.conf:\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"
926 " Multi-card users may save time by copying the first config file.\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"
932 "\t\t" NAME
" -i0 -w 1.2.3.1:24:1080:5\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"
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"
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"
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"
967 char *programskeyhelp
=
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"
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
=
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"
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 */
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"
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"
1021 char *caplogkeyhelp
=
1023 BG
" Capture Log " BN
"shows the current capture error status.\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"
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 */
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"
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"
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"
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"
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
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 */
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 */
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 */
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 */
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 */
1202 int fpmt
; /* found PMT program map table after PAT */
1205 int keep
; /* write current packet if PID's match */
1206 int kept
; /* count writes */
1209 int esec
; /* current errors - previous errors */
1211 int audps
; /* one shot payload start indicator */
1212 int vidps
; /* same but not used, sequence_idx instead */
1217 /* program guide masks and measurements */
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 */
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 */
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 */
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 */
1279 /* FIXME: needs pgm number too? */
1282 /* -1 is unknown: b0 mgt b1 vct b2 ett b3 eit b4 rrt */
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 */
1302 char *name
; /* file name pointer to globtmp allocation */
1303 time_t mt
; /* file modification time */
1304 long long fz
; /* file size */
1309 volatile int sig_val
= 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"
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" };
1334 char *sgmls
[SGML_MAX
] = { "HTML", "HTML", "Big", "BIG",
1335 "Small", "SMALL", "Tiny", "TINY" };
1337 /* HTML4 + CSS stylesheet color definitions */
1339 char *colors
[COLORZ
] = { "black", "red", "green", "blue",
1340 "cyan", "magenta", "yellow", "grey",
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 */
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 */
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,
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,
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,
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,
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,
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,
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
=
1523 char *header_timers
=
1524 BY
"T # " BG
"Status Ch Name "
1525 "Day Date Time Len " BN
"SMTWTFS " BC
"Nextday"
1528 /* filled in with 3 colors and "sid.chan call" by show program guide */
1529 char *header_guide
=
1534 BM
"Events" BN
" on "
1537 BG
"Description" BN SCI
;
1539 /* each line is 2% */
1540 char sigbar
[ SCALE_BAR
+ 1] [ SCALE_BAR
+ 1] = {
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 */
1615 /* combination smp/up support via mutex locks */
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 */
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
;
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 */
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 */
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 */
1770 #define FAIL_TSYNC 1
1771 #define FAIL_IFIFO 2
1772 #define FAIL_OFIFO 3
1774 #define FAIL_NOSPC 5
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 */
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 */
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 */
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 */
1849 char csp
[16]; /* current scan position DCA string */
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 */
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 */
1877 /* queue status text */
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 */
1916 int utsgmt
= 0; /* seconds east or west of greenwich, west is negative */
1923 /* ATSC epoch time offset, including seconds west, set by calc_epoch */
1925 int utc_check
; /* 2007-Jan-1 set by calc_epoch */
1926 /* uptime: when program started in unix epoch seconds */
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 */
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 */
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 */
1992 char arg_wroot
[ WWW_ROOT_MAX
] = "/dtv"; /* www root */
1993 int www_dvrs
= WWW_DVBS
;
1996 int arg_mcport
= 0; /* -u IP:port, port field */
1997 char arg_mcaddr
[16]; /* -m IP:port, IP field */
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
2011 int arg_nosplash
= ~0; /* -N no splash or device load delay */
2013 int arg_nosplash
= 0; /* -N no splash or device load delay */
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 */
2022 #warning using Cable default frtable 2
2023 int arg_frtable
= 2; /* comcast works with table 2 */
2025 int arg_frtable
= 0; /* default is 0, ATSC broadcast */
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 */
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 */
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)
2060 unsigned short *frames
;
2062 unsigned short frames
[ FRAME_MAX
];
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)
2070 unsigned int *sequences
;
2072 unsigned int sequences
[ SEQUENCE_MAX
];
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 */
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,
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,
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
2446 #warning using EXTERNAL kerning header + func
2448 #warning using internal kerning header + func
2449 unsigned char kern_p256_8859_1
[ 256 ] = {
2451 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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,
2467 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2469 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2471 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2473 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2475 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2477 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2479 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
2481 179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,
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.
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",
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.
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 */
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.
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
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 */
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 */
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
2732 /* EITn maximum count, in the spec */
2734 /* ETTn maximum count, in the spec */
2736 /* virtual channel count, vc[8] limits to 8 */
2738 /* program guide size */
2739 /* VC # is top level multiplex for each entry in pgm */
2741 #define PGZ (1 * EIZ * PEZ)
2743 #define PGZ (VCZ * EIZ * PEZ)
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
2763 #warning using full EPG
2768 #warning using tiny EPG
2771 /* rating text limit */
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.
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 */
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 */
2850 struct event_s
*epg3
= NULL
; /* http epg pointer */
2851 struct event_s
*epg4
= NULL
; /* http epg grid pointer */
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 */
2859 short pg3
[ PGZ
]; /* index for dump epg */
2860 short pg4
[ PGZ
]; /* index for dump epg */
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 */
2867 struct pgm_s pgm3
; /* http refresh copy */
2868 struct pgm_s pgm4
; /* http refresh copy */
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
2894 unsigned int st
; /* system time */
2895 unsigned char guo
; /* gps utc offset */
2896 unsigned short ds
; /* daylight savings flag */
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 */
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 */
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 */
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 */
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)
2957 unsigned int payoff
;
2959 unsigned int afl_payoff
; /* where section starts after afc */
2960 unsigned char payload
[ MPEG2_MAX
]; /* don't know size limit */
2962 /************************************************************** MPEG2 Audio */
2963 /* A/52 Audio Payload */
2964 #define A52_MAX 65536
2966 unsigned int payoff
;
2968 unsigned int afl_payoff
; /* where section starts after afc */
2969 unsigned char payload
[ A52_MAX
]; /* dont know size limit */
2971 /****************************************************************************/
2973 unsigned char new_pat
[188]; /* no descriptors */
2974 unsigned char new_pmt
[376]; /* has descriptors */
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 */
2995 /* specs says reserved in some places so don't need all 0x2000
2996 has big gaps, wastes memory, but a much faster lookup.
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
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)
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 */
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) */
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
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 */
3081 /* 2 options for custom HD3000 driver, bit error rate/s and modulator freq */
3082 #ifndef USE_DVB_EXPERIMENTAL
3083 #define SIG_MODREG 4
3085 #warning using DVB EXPERIMENTAL driver
3088 #define SIG_MODREG 6
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 */
3102 struct sockaddr_in addr
;
3108 #define ALLOC_LIMIT 32
3109 #define ALLOC_CHARS 32
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
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[] */
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 */
3157 char *clock_text
[ RTC_MAX
] = {
3159 "PROCESS_CPUTIME_ID",
3163 clockid_t clock_ids
[ RTC_MAX
] = {
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 */
3181 png_bytep row_pointers
[SIG_PNG_H
];
3185 int fifoz
= FIFOZ_CAP
;
3188 unsigned char fifo_buffer
[FIFOZ_CAP
];
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 */
3220 void http_close_sockets(void);
3221 void http_load_allows(void);
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.
3239 astrncpy ( char *d
, char *s
, unsigned int n
)
3244 if ( (NULL
== d
) || (NULL
== s
) ) {
3246 dvrlog( LOG_INFO
, "astrncpy() %p %p NULL", d
, s
);
3250 /* log 0 and > 65535 len */
3251 if ( (0 == n
) || (n
> 65535) ) {
3253 dvrlog( LOG_INFO
, "astrncpy() %d chars", n
);
3258 /* count auto decrements, pointers auto increment */
3259 while (c
-- > 0) if (0 == (*t
++ = *s
++)) break;
3265 /* debug: used to find missing parameters. comment this for normal use */
3266 /* #define asnprintf snprintf */
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.
3275 asnprintf ( char *a
, size_t b
, const char *fmt
, ... )
3282 if (NULL
== a
) return;
3285 memset(t
, 0, sizeof(t
));
3287 /* variable arg macro start, pass fmt and ap, variable arg macro end */
3288 vsnprintf( t
, sizeof(t
)-1, fmt
, ap
);
3294 /* bit bucket does not overflow? */
3295 if ( (la
+ lt
+ 1) < b
) {
3298 astrncpy( p
, t
, lt
+ 1 );
3300 /* if bit bucket does overflow, silently fail and don't exceed boundary */
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
3313 refresh
.channels
= 1;
3314 refresh
.headers
= 1;
3315 refresh
.volstats
= 1;
3329 #ifdef USE_ASCII_XLATE
3330 /* step thru t until 0 and convert chars > 128 to ASCII table[char-128] */
3333 ascii_xlate( unsigned char *table
, unsigned char *t
)
3336 if (*t
> 128) *t
= table
[ *t
- 128 ];
3337 if ('.' == *t
) *t
= ' ';
3338 if ('`' == *t
) *t
= ' ';
3339 if ('\'' == *t
) *t
= ' ';
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 */
3349 #define dvrlog syslog
3350 #warning using syslog
3354 #warning using dvrlog
3355 /* replacement for syslog */
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
);
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
);
3378 /* log file is open */
3379 snprintf( &dvrlog_list
[ dvrlog_idx
* LOG_CHARS
], LOG_CHARS
-1,
3380 "%s %s\n", &date_now
[4], m
);
3383 pthread_mutex_unlock( &dvrlog_mutex
);
3390 /* log queue drain thread */
3392 dvrlog_loop ( void * arg
)
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
);
3410 fflush( dvrlog_fd
);
3411 pthread_mutex_unlock( &dvrlog_mutex
);
3413 nanosleep( &atomic_sleep
, NULL
);
3418 /* start threads for dvrlog */
3421 init_dvrlog ( void )
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? */
3434 ok
= stat( dvrlog_name
, &f
);
3437 localtime_r( &f
.st_mtime
, &t
);
3438 snprintf( n
, sizeof(dvrlog_name
)-1,
3439 "%s.%04d%02d%02d-%02d%02d%02d",
3443 t
.tm_mday
, t
.tm_hour
, t
.tm_min
, t
.tm_sec
);
3444 rename( dvrlog_name
, n
);
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
);
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 */
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",
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 */
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
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 ' '
3514 truncate_text_kern_box ( unsigned char *s
,
3516 int x
, int p
, int y
, int u
, int f
)
3518 unsigned char *c
, *d
;
3519 int i
, r
, t
, v
, w
, z
;
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 */
3550 /* NOTE: trailing space gets counted */
3551 if (' ' == b
) break;
3554 if (w
>= t
) { /* word too big for line? */
3556 if (i
>= y
) { /* last line? */
3558 /* truncate after last word that fit */
3562 w
= z
; /* next line, end of word */
3565 d
= c
; /* next word */
3567 /* FIXME: non-wrappable word stops here and loses logical line count */
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:
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
3595 if no / in source, path is loaded with ./
3597 F_PFILE path file base ./x
3598 F_TFILE file base name x
3599 F_FILE file name only x.conf
3604 filebase ( char *d
, char *s
, int o
)
3610 /* limit option flags to non-bogus */
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 */
3622 /* path only, with / */
3625 t
= strrchr( s
, '/' );
3628 t
= strrchr( d
, '/');
3634 /* path and file, without last .ext */
3637 t
= strrchr( s
, '/' );
3638 if (NULL
== t
) d
+= 2;
3640 t
= strrchr( d
, '.');
3641 if (NULL
!= t
) *t
= 0; /* truncate at last . */
3644 /* truncated file only, without last .ext */
3646 t
= strrchr( s
, '/' ); /* skip path */
3651 /* truncate at last . */
3652 t
= strrchr( d
, '.');
3653 if (NULL
!= t
) *t
= 0;
3655 /* no path, truncate at last . */
3657 t
= strrchr( d
, '.');
3658 if (NULL
!= t
) *t
= 0;
3662 /* file only with .ext */
3664 t
= strrchr( s
, '/' );
3674 dvrlog( LOG_INFO
, "filebase unknown parse type %u\n", o
);
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
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
;
3700 if (NULL
== d
) return -1;
3701 if (NULL
== s
) return -1;
3702 if (0 == *d
) return -1;
3703 if (0 == *s
) return -1;
3709 /* glob needed for * in filename? */
3710 p
= strchr( s
, '*' );
3711 if (NULL
== p
) p
= strchr( s
, '?' );
3712 if (NULL
!= p
) g
= ~0;
3715 /* WARNING: no returns until globt is freed, only continues and breaks */
3716 glob( s
, 0, NULL
, &globt
);
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
);
3735 if ('/' == *p
) *p
= 0;
3737 /* destination exists? */
3738 rc
= stat64( no
, &fs
);
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 */
3751 dvrlog( LOG_INFO
, "%s can't stat %s", WHO
, ni
);
3755 /* clear other time stamps */
3756 memset( &ut
, 0, sizeof(ut
) );
3757 ut
.modtime
= fs
.st_mtime
;
3758 ut
.actime
= fs
.st_atime
;
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
);
3767 dvrlog( LOG_INFO
, "%s can't open %s err %d", WHO
, ni
, errno
);
3772 /* copy, mode is octal */
3774 o
= open( no
, FILE_WMODE
, 0644 );
3776 /* append. seeks end of file instead of O_APPEND flag with NFS problems */
3778 o
= open( no
, O_RDWR
| O_CREAT
, 0644 );
3781 dvrlog( LOG_INFO
, "%s can't open %s", WHO
, no
);
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
);
3796 r
= read( i
, buf
, z
);
3798 w
= write( o
, buf
, r
);
3801 dvrlog( LOG_INFO
, "%s can't write %s", WHO
, no
);
3807 dvrlog( LOG_INFO
, "%s wrote %d of %d to %s",
3814 advrlog( LOG_INFO
, "%s EOF %s", WHO
, ni
);
3820 dvrlog( LOG_INFO
, "%s can't read %s", WHO
, no
);
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. :>
3841 /* don't forget to free what glob allocated */
3842 if (0 != g
) globfree( &globt
);
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 */
3853 test_var_procpid( int d
, int v
)
3856 char n
[64], s
[1024];
3859 snprintf(n
, sizeof(n
), "/var/run/%s/%s%d.pid", NAME
, NAME
, d
);
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
);
3869 snprintf(n
, sizeof(n
), "/proc/%d/cmdline", p
);
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
);
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;
3888 /* version that doesn't use system() */
3891 save_tmpfs ( char *caller
)
3893 char s
[512], d
[512], r
[512], o
[1024];
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
);
3943 /* remove ram log to avoid appending duplicate data */
3944 snprintf( s
, sizeof(s
), "%s%s%d.log", r
, NAME
, arg_devnum
);
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.
3955 char s
[512], d
[512], r
[512];
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
);
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
);
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 );
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
4014 dump_backtrace( char **bts
, size_t z
, void *addr
)
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 */
4025 char has_name
, has_addr
, has_func
;
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 );
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
);
4054 /* start at 1, because 0 is always signal_handler */
4055 for (y
= 1; y
< z
; y
++) {
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;
4070 if (0 != has_name
) {
4071 astrncpy( n
, bts
[ y
], sizeof(n
));
4072 if (NULL
!= strstr( 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;
4090 if (0 != has_addr
) {
4091 p
= strchr( t
, '[' ); /* point to addr */
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;
4109 if (0 != has_func
) {
4110 p
= strchr( t
, '(' );
4113 r
= strchr( p
, ')' );
4116 astrncpy( s
, p
, sizeof(s
) );
4121 /* atscap default install location */
4122 #define USE_PREFIX_BIN "/usr/local/bin/"
4126 fprintf( f
, "# %s\n", bts
[ y
] );
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
);
4144 nanosleep( &console_read_sleep
, NULL
);
4150 fprintf( stdout
, "\n");
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.
4160 /* signal_handler ( int sigval ) */ /* old style */
4161 signal_handler( int sigval
, siginfo_t
*info
, void *secret
)
4163 #ifdef USE_GNU_BACKTRACE
4168 #ifdef USE_GNU_BACKTRACE_SCRIPT
4169 char **bts
= (char **) 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
];
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
];
4195 sig_val
= sigval
; /* save signal for exit processing */
4200 /* ignore sigpipe, is likely to be web server aborted connection */
4205 /* only sets flag for signal test called from console scan */
4219 #ifdef USE_GNU_BACKTRACE
4221 z
= backtrace( bt
, BTZ
);
4224 /* addr will be arch dependent on x86 and derivatives with newer GCC only */
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 */
4237 f
= open( n
, O_RDWR
| O_CREAT
, 0644 );
4240 write( f
, t
, strlen(t
) );
4242 /* use btfd.sh to extract line numbers */
4243 backtrace_symbols_fd( bt
, z
, f
);
4258 #ifdef USE_GNU_BACKTRACE
4259 /* if backtrace or backtrace_symbols fail, give some exit indication */
4260 z
= backtrace( bt
, BTZ
);
4262 fprintf( stderr
, CLS BN SCV
4263 "Fatal error at addr %p, no backtrace.\n", addr
);
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.
4274 #ifdef USE_GNU_BACKTRACE_SCRIPT
4275 /* Save backtrace to addr2line script. Is problematic if stack is crashed. */
4276 bts
= backtrace_symbols( bt
, z
);
4278 fprintf( stderr
, CLS BN SCV
4279 "Fatal error at addr %p, no backtrace.\n", addr
);
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
);
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
);
4294 dump_backtrace( bts
, z
, addr
); /* does not return, exit252 */
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 */
4308 f
= open( n
, O_RDWR
| O_CREAT
| O_TRUNC
, 0644 );
4310 write( f
, t
, strlen(t
) );
4311 backtrace_symbols_fd( bt
, z
, f
);
4316 /* USE_GNU_BACKTRACE_SCRIPT */
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
);
4328 dvrlog( LOG_INFO
, "SIG%s at addr %p\n",
4329 sig_text
[sig_val
], addr
);
4330 fflush( dvrlog_fd
);
4331 fclose( dvrlog_fd
);
4340 /* the rest get logged */
4343 dvrlog( LOG_INFO
, "%s %s(%d) tid %d ignored",
4344 WHO
, sig_text
[ sig_val
], sigval
, getpid() );
4346 dvrlog( LOG_INFO
, "%s %d", WHO
, sig_val
);
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
);
4359 /* global signal init. got rid of -ansi -pedantic porting errors */
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 */
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
);
4426 /* insert delays each time it is called for debugging ui */
4429 aprintf ( FILE *f
, const char *fmt
, ... )
4432 struct timespec print_sleep
= {0,100000000};
4436 if ((arg_detach
!= 0) || (arg_quiet
!= 0)) return;
4437 memset(t
, 0, sizeof(t
));
4440 /* variable arg macro start, pass fmt and ap, variable arg macro end */
4441 vsnprintf( t
, sizeof(t
)-1, fmt
, ap
);
4443 fprintf( f
, "%s", t
);
4444 nanosleep( &print_sleep
, NULL
);
4449 /* functions to track malloc, calloc and free usage */
4451 /* unused compiler error avoid */
4454 imalloc ( size_t z
, char *id
)
4459 for (i
= 0; i
< ALLOC_LIMIT
; i
++)
4460 if (NULL
== alloc_list
[i
].p
)
4463 if (ALLOC_LIMIT
== i
) {
4464 fprintf(stderr
, CLS
"malloc %s %d failed: alloc limit\n", id
, z
);
4471 fprintf(stderr
, CLS
"malloc %s %d failed\n", id
, z
);
4475 alloc_list
[ i
].p
= p
;
4476 alloc_list
[ i
].z
= z
;
4477 astrncpy( alloc_list
[ i
].n
, id
, ALLOC_CHARS
);
4483 icalloc ( size_t n
, size_t z
, char *id
)
4488 for (i
= 0; i
< ALLOC_LIMIT
; i
++)
4489 if (NULL
== alloc_list
[i
].p
)
4492 if (ALLOC_LIMIT
== i
) {
4493 fprintf(stderr
, CLS
"calloc %s %d failed: alloc limit\n", id
, z
);
4500 fprintf(stderr
, CLS
"calloc %s %d failed\n", id
, z
);
4503 alloc_list
[ i
].p
= p
;
4504 alloc_list
[ i
].z
= z
* n
;
4505 astrncpy( alloc_list
[ i
].n
, id
, ALLOC_CHARS
);
4509 /* incremental realloc, does not shrink only expands */
4510 /* this behaviour is different from normal realloc which sets z size */
4513 irealloc( void *p
, int z
, char *id
)
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
)
4524 if (ALLOC_LIMIT
== i
) {
4525 fprintf( stderr
, CLS
4526 "irealloc %p %d not found for %s\n", p
, z
, id
);
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
);
4541 return alloc_list
[i
].p
;
4546 ifree ( void *p
, char *n
)
4551 fprintf( stderr
, CLS
"ifree can not free NULL pointer %s\n",n
);
4556 for ( i
= 0; i
< ALLOC_LIMIT
; i
++ )
4557 if ( p
== alloc_list
[ i
].p
)
4560 if (ALLOC_LIMIT
== i
) {
4561 fprintf(stderr
, CLS
"ifree can not find pointer %s %p\n", n
, p
);
4566 /* found a match? */
4568 memset( alloc_list
[i
].n
, 0, ALLOC_CHARS
);
4569 alloc_list
[i
].p
= NULL
;
4570 alloc_list
[i
].z
= 0;
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).
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
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 */
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 */
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 */
4664 astrncpy( d
, c
, COMMA_MAX
); /* commas won't fit */
4667 astrncpy( d
, c
, COMMA_MAX
); /* no commas needed */
4672 /* ascii binary to unsigned */
4673 /* NOTE: this is where weekday bits get reversed */
4681 if (p
== NULL
) return 0;
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;
4690 if (b
== 0) return 0;
4692 while (*p
) i
|= ( *p
++ & 1 ) << --b
;
4696 /* unsigned to ascii binary,
4697 p is output, b is number of bits, i is value
4701 utoab ( char *p
, unsigned int b
, unsigned int i
)
4706 *p
++ = 48 + ( (i
& 1<<--j
) ? 1:0 );
4708 *p
= 0; /* null term string */
4713 /* FIXME: should not include port. adjust size back to 16 after port remove */
4716 uintodot( char *d
, unsigned int s
)
4718 if (NULL
== d
) return;
4719 snprintf( d
, 22, "%d.%d.%d.%d:%d",
4724 0xFFFF & arg_wport
);
4727 /* returns 0 if address was good */
4730 dotouint ( unsigned int *d
, char *p
)
4733 char *p0
, *p1
, *p2
, *p3
;
4737 memset( s
, 0, sizeof(s
) );
4738 astrncpy( s
, p
, sizeof(s
) );
4740 if (NULL
== s
) return -1;
4741 if (NULL
== d
) return -1;
4743 p0
= p1
= p2
= p3
= "";
4745 p1
= strchr( p0
, '.' );
4749 p2
= strchr( p1
, '.');
4753 p3
= strchr( p2
, '.');
4762 if (0 != ok
) return ok
;
4766 if ((a0
< 0) || (a0
> 255)) return -1;
4769 if ((a1
< 0) || (a1
> 255)) return -1;
4772 if ((a2
< 0) || (a2
> 255)) return -1;
4775 if ((a3
< 0) || (a3
> 255)) return -1;
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. */
4798 if ( ((c
>= '1') && (c
<= '9'))
4799 || ((c
>= 'A') && (c
<= 'Z'))
4800 || ((c
>= 'a') && (c
<= 'z'))
4801 || (c
>= 128) ) return ~0;
4810 unsigned char c
, c1
, c2
;
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';
4830 /* build a list of cgi form pointers up to n pointers from string s */
4834 build_form_args( char **p
, int n
, char *s
)
4839 for (i
= 0; i
< n
; i
++) p
[i
] = NULL
;
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;
4849 t
= strchr( p
[i
], '&' );
4850 if (NULL
== t
) break;
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 */
4861 build_args( char **p
, int n
, char *s
, char c
, char *caller
)
4866 advrlog( LOG_INFO
, "%s %s %s", caller
, WHO
, s
);
4868 for (i
= 0; i
< n
; i
++) p
[i
] = NULL
;
4873 for (i
= 0; i
< n
; i
++) {
4879 if (NULL
== t
) break;
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.
4896 tnow
= time( NULL
);
4897 localtime_r( &tnow
, &tloc
);
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 */
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 )
4919 /* failsafe value is next 3hr meridian */
4922 /* convert to earliest 3hr meridian */
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;
4937 /* tt is int representation of time_t */
4938 /* d should be 32 bytes, trun should be less than 31 for nul term */
4941 text_time ( char *d
, int tt
, int trun
) {
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 */
4955 test_clock_res ( void )
4957 struct timespec res
;
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
)) {
4976 /* is clock res non-zero? */
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
);
4991 /* use last lowest value in l for the syslog */
4992 clock_method
= clock_ids
[j
];
4997 /* want this done after banner */
5000 log_clock_res ( void )
5006 t
= clock_text
[clock_idx
];
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 */
5018 init_rrt( struct payload_s
*p
)
5023 /* clears entries in mg* */
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 */
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 */
5056 init_pat_pmt( struct payload_s
*a
, struct payload_s
*m
)
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;
5069 /* clear all virtual channel data and set version numbers to invalid */
5072 init_vct ( struct payload_s
*v
, char *caller
)
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 */
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 */
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;
5113 /* channel change resets pid totals for that channel */
5118 memset( pids
, 0, sizeof(pids
) );
5122 /* ts capture loop calls this at start */
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 */
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
) );
5152 rrt
.payok
= 1; /* no payload yet */
5154 for (i
=0; i
< EIZ
; i
++) {
5155 eit
[i
].vn
= 0xFF; /* set version to invalid */
5157 eit
[i
].payok
= 1; /* no payload yet */
5163 /* init ATSC structures */
5169 z
= sizeof( struct payload_s
);
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 */
5177 /* clear the locks and data areas then set table version numbers invalid */
5181 /* Free the ATSC tables allocated above */
5187 #warning using dynamic EIT & ETT free
5189 ifree( eit
, "eit" );/* 4k * 128 Event Info Table */
5193 ifree( ett
, "ett" );/* 4k * 128 Event Text Table */
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. */
5202 init_frames ( void )
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) );
5215 /* don't process frames if can't allocate space */
5216 advrlog( LOG_INFO
, "%s failed frames calloc", WHO
);
5220 sequences
= icalloc( SEQUENCE_MAX
, sizeof( int ), "sequences[]");
5221 if (NULL
!= sequences
) {
5222 advrlog( LOG_INFO
, "icalloc %d sequences",
5223 SEQUENCE_MAX
* sizeof(int) );
5225 /* don't process sequences if can't allocate space */
5226 dvrlog( LOG_INFO
, "%s failed sequences calloc", WHO
);
5230 #warning using static frames[] & sequences[]
5237 /* Frames and sequences are only needed during capture. */
5240 free_frames ( void )
5243 if (NULL
!= sequences
) ifree( sequences
, "sequences" );
5245 if (NULL
!= frames
) ifree( frames
, "frames" );
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?
5266 clear all the arrays
5267 set .vn in all the arrays to 255 to prevent version 0 = version 0 no update
5271 init_arrays ( void )
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
) );
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
);
5296 /* reset frame counts */
5297 memset( pict
, 0, sizeof(pict
));
5302 /************************************************************* ffmpeg CRC32 */
5305 calc_crc32 ( unsigned char *data
, unsigned int len
)
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
++) ];
5316 /************************************************************* ffmpeg CRC32 */
5320 /************************************************************** UDP MULTICAST
5321 open socket, 0 if ok, not zero if not ok
5325 udp_open_socket ( struct udp_s
*u
)
5334 u
->sock
= socket( AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
5336 dvrlog( LOG_INFO
, "socket open error");
5340 /* set socket reusable (initiator of group has to do this) */
5341 ok
= setsockopt( u
->sock
,
5342 SOL_SOCKET
, SO_REUSEADDR
, &yes
, sizeof(yes
) );
5344 dvrlog( LOG_INFO
, "socket reuse error");
5348 /* set socket multicast loopback off (disables local listen) */
5350 sok
= setsockopt( u
->sock
,
5351 IPPROTO_IP
, IP_MULTICAST_LOOP
, &no
, sizeof(no
) );
5353 dvrlog( LOG_INFO
, "socket loop disable error");
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
) );
5368 dvrlog( LOG_INFO
, "socket mcast group error");
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
));
5382 dvrlog( LOG_INFO
, "socket bind error");
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
) );
5393 /************************************************************** UDP MULTICAST
5394 write a transport packet to a UDP multicast socket
5398 udp_write_socket ( struct udp_s
*u
, unsigned char *p
)
5403 sendok
= sendto( u
->sock
, p
, 188,
5405 sizeof(struct sockaddr
) );
5412 udp_close_socket ( struct udp_s
*u
)
5414 if (u
->sock
> 2) close( u
->sock
);
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.
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
;
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 */
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
);
5465 /* get tree offset for char p from order-1 tree byte offset table */
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 */
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
);
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
) )
5496 /* handle Escape to 8 bit mode */
5497 i
++; /* point to msb of uncompressed byte */
5500 /* get current byte */
5506 b
= s
[ (i
>> 3) + 1];
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 */
5518 /* out of space gets nul term and exits */
5524 d
++; /* else move to next char */
5526 /* nul term exits */
5527 if ( c
== 0 ) break;
5533 /* "Sun, 01 Jan 1995 00:00:00 GMT" */
5534 /* gcc circa 1994 and later only */
5537 http_text_time( char *d
, time_t *t
)
5541 strftime( d
, 30, "%a, %d %b %Y %T GMT", &ut
);
5548 /* Root Mean Square the SNR samples stored in the signal history */
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 */
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 */
5563 a
*= a
; /* square */
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 );
5574 /* average the FE locked strength samples stored in the signal history */
5577 calc_sig_avg( struct sig_s
*s
)
5583 for (i
= 0; i
< SIG_PNG_W
; i
++)
5585 if (s
->smp
[i
] < 1) continue; /* don't count zero readings */
5590 if (c
> 0) a
/= c
; /* avoid div by 0 error */
5594 /* return index of tsid list with matching tsid and program number, or -1 */
5597 find_tsidx ( unsigned short id
, unsigned short pn
, char *caller
)
5602 for (i
= 0; i
< tsidx
; i
++) {
5604 if ((id
== t
->tsid
) && (pn
== t
->pn
)) {
5611 /* return index of tsid list with matching ptc and program number, or -1 */
5614 find_tsid_ptc( int pc
, unsigned short pn
)
5619 for (i
= 0; i
< tsidx
; 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
);
5627 advrlog( LOG_INFO
, "TSID PTC %03d.%d not found", pc
, pn
);
5633 /* stop capture, set reason and set display to timers */
5636 stop_capture ( char *caller
, int fail
)
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;
5648 /* wait for output fifo flush and thread termination */
5649 while (0 != pid_o
) nanosleep( &console_read_sleep
, NULL
);
5652 display_type
= D_TIMERS
;
5658 read_cutvol_stats( void )
5664 ok
= statfs( cut_path
, &cut_fsp
);
5666 strerror_r(errno
, e
, sizeof(e
));
5667 advrlog( LOG_INFO
, "%s statfs returns %d %s", WHO
, errno
, e
);
5669 cut_free
= (cut_fsp
.f_bsize
* cut_fsp
.f_bavail
) >> 30;
5670 cut_size
= (cut_fsp
.f_bsize
* cut_fsp
.f_blocks
) >> 30;
5676 read_capvol_stats( void )
5682 ok
= statfs( out_path
, &vol_fsp
);
5684 strerror_r(errno
, e
, sizeof(e
));
5685 advrlog( LOG_INFO
, "%s statfs returns %d %s", WHO
, errno
, e
);
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. */
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.
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
5731 build_head_html3 ( char *d
, int z
, int flags
, char *title
, int r
, char *caller
)
5735 char *c
; /* font color */
5740 /* force 1s delayed update of free space */
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
);
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",
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 "
5773 "bgcolor=\042%s\042 "
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) */
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 */
5807 asnprintf( d
, z
, "<a href=\042http://%s:%u/%s%d.html\042>",
5808 arg_whost
, (0xFFFC & arg_wport
) + i
, NAME
, i
);
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 );
5818 if (i
== arg_devnum
) {
5819 fz
= 4; c
= "#FFFFFF";
5822 asnprintf(d
, z
, "<font size=\042%d\042 color=\042%s\042>",
5824 asnprintf(d
, z
, "%s%d </font>", NAME
, i
);
5825 asnprintf(d
, z
, "</a>\n");
5828 /* volume available indication, cap directory is always available */
5831 asnprintf( d
, z
, "<a href=\042%s\042>%s </a>\n",
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",
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
;
5859 if (0 != (4 & flags
)) {
5860 asnprintf(d
,z
, "<a href=\042%02d.html\042>", s
->chan
);
5862 asnprintf(d
,z
, "<a href=\042/pg/%02d.html\042>", s
->chan
);
5873 /* in EPG page, current EPG channel is white */
5874 if (s
->chan
== pgm3
.chan
) {
5879 asnprintf(d
, z
, "<font size=\042%d\042 color=\042%s\042>",
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
);
5913 /* in server page, current scan channel is white */
5914 if (s
->chan
== cap_chan
) {
5919 asnprintf(d
, z
, "<font size=\042%d\042 color=\042%s\042>",
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
++) {
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");
5949 /***************************************************** signal SNR RMS */
5950 if (0 != scan_snr
) {
5951 asnprintf(d
, z
, " <tr>\n");
5952 for (i
= 0; i
< scan_idx
; i
++) {
5954 s
= &ptc
[scan_list
[i
]].sig
;
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
++) {
5968 s
= &ptc
[scan_list
[i
]].sig
;
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 */
5982 if (0 != scan_png
) iz
= 48;
5984 asnprintf(d
, z
, " <tr>\n");
5985 for (i
= 0; i
< scan_idx
; i
++) {
5989 s
= &ptc
[scan_list
[i
]].sig
;
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))
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
++) {
6013 s
= &ptc
[scan_list
[i
]].sig
;
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 */
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
) {
6033 if (CAP_NONE
!= cap_now
) {
6035 n
= "pg/img/epglive24.png";
6036 if (0 != scan_png
) n
= "pg/img/epglive.png";
6038 /* no cap, is sig scan? */
6039 if (0 != scan_sig
) {
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>",
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>",
6081 asnprintf(d
, z
, "%s", (0 == scan_khz
)?"freq":"FREQ");
6082 asnprintf(d
, z
, "</a> \n");
6085 asnprintf(d
, z
, "<a href=\042%s%d.html?q=1\042>",
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");
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");
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",
6117 asnprintf(d
, z
, "Auto\n");
6119 asnprintf(d
, z
, IMG_FMT
, "OFF", "pg/img/epgoff24.png",
6121 asnprintf(d
, z
, "Off\n");
6123 asnprintf( d
, z
, " STATUS: ");
6124 asnprintf(d
, z
, IMG_FMT
, "CAP", "pg/img/epglive24.png",
6126 asnprintf(d
, z
, "Cap\n");
6128 asnprintf(d
, z
, IMG_FMT
, "SCAN", "pg/img/scanon24.png",
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 */
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 */
6177 build_snav_html4 ( char *d
, int z
, int flags
)
6180 char *a
; /* font and color class for href */
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");
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");
6199 for (i
= 0; i
< www_dvrs
; i
++) {
6201 #ifdef USE_MULTI_CARD
6202 ok
= test_var_procpid(i
, 0);
6205 // asnprintf( d, z, "<li class=\042%s\042>", "snbl1");
6206 a
= "snba0"; /* smaller font and grey */
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>",
6219 (0xFFFC & arg_wport
) + i
, NAME
, i
);
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 );
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 */
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.
6271 build_cnav_html4 ( char *d
, int z
, int flags
)
6274 char *a
, *c
, *i0
, *i1
;
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 */
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
))
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
);
6320 if (0 != (4 & flags
)) {
6321 asnprintf(d
, z
, "<a class=\042%s\042 "
6322 "href=\042%02d.html\042>",
6325 asnprintf(d
, z
, "<a class=\042%s\042 "
6326 "href=\042/pg/%02d.html\042>",
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
6334 if (cap_chan
== s
->chan
) {
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
6354 build_sigs_html4 ( char *d
, int z
, int flags
)
6358 char *a
, *b
, *e
, *f
, *c
, *i0
, *i1
, hr
[64];
6369 /* epg doesn't use this format, but uses similar */
6370 if (0 == (2 & flags
)) return;
6372 asnprintf(d
, z
, "\n<!-- %s -->\n", WHO
);
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 */
6393 /* auto epg style */
6394 if (0 != s
->pgto
) c
= "cnta2";
6396 /* current channel overrides auto-epg style */
6398 if (cap_chan
== s
->chan
) {
6401 /* TODO: move italics to style sheet once decided what gets italics */
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 */
6418 #ifdef USE_DVB_EXPERIMENTAL
6419 /* drift kilocycles */
6420 if (0 != scan_khz
) {
6421 asnprintf(d
, z
, "<li class=\042%s %s\042> %-dkc"
6423 b
, f
, s
->fohz
/ 1000);
6427 if (0 != scan_snr
) {
6429 #ifdef USE_DB_PERCENT
6430 /* N.nn instead of N dB */
6431 asnprintf(d
, z
, "<li class=\042%s %s\042> %d.%02d "
6433 b
, f
, 0xFF & (s
->snr_rms
>> 8),
6434 ((0xFF & s
->snr_rms
) * 100) >> 8 );
6436 /* N dB instead of n.nn */
6437 asnprintf(d
, z
, "<li class=\042%s %s\042> %ddB "
6439 b
, f
, 0xFF & (s
->snr_rms
>> 8) );
6443 /* this value is average of signal strength, reflects graph below it.
6444 it gets a channel select href if not using png graph
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",
6462 /* if png graph is enabled it gets href for channel select */
6465 /* no background image */
6469 /* epg type background image */
6470 if ((cap_chan
== s
->chan
) && (0 != scan_sig
)) {
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 />"
6483 b
, c
, NAME
, arg_devnum
, v
, e
, a
,
6484 s
->chan
, "1", arg_devnum
, s
->chan
);
6487 /****************************************************** Auto EPG control */
6492 e
= "epgon.png"; a
= "APG ON";
6494 if ((CAP_NONE
!= cap_now
) && (cap_chan
== s
->chan
)) {
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 "
6503 "src=\042/pg/img/%s\042 />"
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 */
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
);
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 */
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
);
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");
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
;
6565 asnprintf(d
, z
, "<meta http-equiv=\042%s\042 "
6566 "content=\042%d\042>\n",
6569 asnprintf(d
, z
, "<link rel=\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 */
6583 build_head_xhtml( char *d
, int z
, int flags
, char *title
, int r
, char *caller
)
6587 /* xhtml wants " />" for end tag on certain things */
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
);
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",
6613 "text/html;charset=ISO-8859-1", t
);
6616 asnprintf(d
, z
,"<meta http-equiv=\042%s\042 "
6617 "content=\042%d\042%s>\n",
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
);
6626 asnprintf(d
, z
, "<link rel=\042%s\042 "
6628 "type=\042%s\042%s>\n",
6629 "stylesheet", "/pg/img/style.css", "text/css", t
);
6631 if (NULL
!= title
) asnprintf(d
, z
, "<title>%s</title>", title
);
6632 asnprintf(d
, z
, "</head>\n");
6634 /************************************************** end of build_head_xhtml */
6638 build_head_html ( char *d
, int z
, int flags
, char *title
, int r
, char *caller
)
6641 build_head_html3( d
, z
, flags
, title
, r
, caller
);
6643 if (0 == (8 & flags
)) {
6644 build_head_html4( d
, z
, flags
, title
, r
, caller
);
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.
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 */
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 */
6689 build_foot_html4 ( char *d
, int z
, int v
)
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");
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 */
6727 asnprintf(d
, z
, "<img alt=\042%s\042 src=\042%s\042 />\n",
6728 "HTML401", "/pg/img/valid-html401-blue.png");
6732 asnprintf(d
, z
, "<img alt=\042%s\042 src=\042%s\042 />\n",
6733 "XHTML10", "/pg/img/valid-xhtml10-blue.png");
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
) {
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
);
6761 asnprintf(d
, z
, "</center>\n");
6762 asnprintf(d
, z
, "</div>\n");
6763 asnprintf(d
, z
, "</body>\n");
6764 asnprintf(d
, z
, "</html>\n");
6769 build_foot_html( char *d
, int z
, int v
)
6772 build_foot_html3( d
, z
, v
);
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 */
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 */
6789 char dn
[ 32 ], c
[256], p
[256], uri
[256],
6790 *c0
, *c1
, *c2
, *c3
, *c4
, *c5
, *c6
, *c7
,
6791 *u
, *up
; /* text fields */
6795 s
= &ptc
[ cap_chan
].sig
;
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 */
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
));
6823 /* spares if needed */
6828 t
= (time_t) utsnow
;
6833 asnprintf( d
, z
, "<center>\n");
6835 // validator doesn't like this
6836 // asnprintf( d, z, "<font color=\042%s\042>\n", "#FFFFFF");
6840 hh
= (tu
/ SECHR
) % 24;
6841 mm
= (tu
/ SECMIN
) % 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? */
6853 if ((CAP_INFO
== cap_now
) || (CAP_INFO
== cap_prev
))
6856 /* FIXME: presumes *p is 0 if info cap
6857 TODO: don't show blank link
6862 mm
= (ets
/ SECMIN
) % 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%");
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
= "***";
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;
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;
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;
6924 /* bit count is packet count * 1504, rate is per ets */
6929 if (CAP_NONE
== cap_now
) ln
= 0;
6932 if (CAP_NONE
== cap_now
) ln
= 0;
6935 /* look up name from tsids. if not found, fallback to vc.cname */
6936 k
= find_tsidx( vc
[i
].tsid
, vc
[i
].pn
, WHO
);
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
++) {
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
= "***";
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;
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;
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;
6976 /* bit count is packet count * 1504, rate is per ets */
6981 if (CAP_NONE
== cap_now
) ln
= 0;
6984 if (CAP_NONE
!= cap_now
) ln
= 0;
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>",
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 */
7004 /* divide by 8192 not 8000 */
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"
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");
7039 asnprintf( d
, z
, "\n");
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
;
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
;
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
;
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
;
7111 " C Virtual Channel: %7s %7s %12s %12s %8s\n",
7112 c0
, c1
, c2
, c3
, c4
);
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
;
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
;
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
;
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
;
7188 /* divide by 8192 not 8000 */
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");
7202 asnprintf( d
, z
, "\n");
7204 asnprintf( d
, z
, " Tables Errors Packets Rate bits/s KBytes/s\n");
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
;
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
;
7239 asnprintf( d
, z
, " Program Map: %7s %7s %12s %12s %8s\n",
7240 c0
, c1
, c2
, c3
, c4
);
7243 lltoasc( c1
, 0LL ); /* FIXME: walk vc for all video CC */
7244 lltoasc( c2
, s
->pkt
.vid
);
7245 ln
= (long long) s
->pkt
.vid
;
7255 asnprintf( d
, z
, " MPEG Video: %7s %7s %12s %12s %8s\n",
7256 c0
, c1
, c2
, c3
, c4
);
7259 lltoasc( c1
, 0LL ); /* FIXME: walk vc for all audio CC */
7260 lltoasc( c2
, s
->pkt
.aud
);
7262 ln
= (long long) s
->pkt
.aud
;
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;
7278 ln
= (long long) cce_pids
[ 0x1FFF ];
7281 ln
= (long long) s
->pkt
.null
;
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
;
7306 /* divide by 8192 not 8000 */
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");
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
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]
7339 build_stats_dl (char *d
, int z
, char *dc
, char *uc
, char *lc
, char *c
, int n
)
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>";
7352 asnprintf(d
, z
, div
, dc
);
7353 asnprintf(d
, z
, ul
, uc
);
7357 for (i
= 0; i
< n
; i
++) {
7361 /* check for href for list title. title is centered. href is at c[224] or \0 */
7365 asnprintf(d
, z
, lh
, t
, uc
, c
);
7367 snprintf(hr
, sizeof(hr
)-1, ah
, "cfsv", s
, c
);
7368 asnprintf(d
, z
, lh
, t
, uc
, hr
);
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 */
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 */
7398 /* Build capture stats as html into string at s, with maximum size z. */
7399 /* CSS styles: tss tsl tsul tsli0 (normal) tsli1 (red) */
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 */
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
;
7420 s
= &ptc
[ cap_chan
].sig
;
7422 /* no cap shows last cap values, live cap shows current values */
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 */
7453 /* reserved for future use */
7457 t
= (time_t) utsnow
;
7461 if (ets
<1) ets
= 1;
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
;
7471 hh
= (tu
/ SECHR
) % 24;
7472 mm
= (tu
/ SECMIN
) % 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? */
7484 if ((CAP_INFO
== cap_now
) || (CAP_INFO
== cap_prev
))
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");
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
7503 hh
= utsets
/ SECHR
;
7504 mm
= (utsets
/ SECMIN
) % 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 */
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);
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);
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);
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);
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);
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
;
7584 n
>>= 13; /* NOTE: KB is /1024 not /1000 */
7587 if ((0 != s
->as
) && (p
->stt
> 0))
7588 build_stats_dl(d
, z
, "tsl", tc
[k
], "cyan0", c
, 6);
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
;
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
) {
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
;
7625 if ((0 != s
->as
) && (p
->tvctp
> 0))
7626 build_stats_dl(d
, z
, "tsl", tc
[k
], "cyan0", c
, 6);
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
;
7646 if ((0 != s
->as
) && (p
->cvctp
> 0))
7647 build_stats_dl(d
, z
, "tsl", tc
[k
], "cyan0", c
, 6);
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
;
7665 if ((0 != s
->as
) && (p
->eitp
> 0))
7666 build_stats_dl(d
, z
, "tsl", tc
[k
], "cyan0", c
, 6);
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
;
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
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
;
7702 if ((0 != s
->as
) && (p
->rrtp
> 0))
7703 build_stats_dl(d
, z
, "tsl", tc
[k
], "cyan0", c
, 6);
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
;
7716 snprintf(c0
, 31, "ATSC");
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");
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";
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
;
7772 if ((0 != s
->ms
) && (p
->patp
> 0))
7773 build_stats_dl(d
, z
, "tsl", tc
[k
], "green0", c
, 6);
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
;
7790 if ((0 != s
->ms
) && (p
->catp
> 0))
7791 build_stats_dl(d
, z
, "tsl", tc
[k
], "green0", c
, 6);
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
;
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 */
7814 if (0 != p
->vid
) k
= AT_OK
;
7815 snprintf(c0
, 31, "VID");
7818 lltoasc( c3
, p
->vid
);
7819 n
= (long long) p
->vid
;
7827 if ((0 != s
->ms
) && (p
->vid
> 0))
7828 build_stats_dl(d
, z
, "tsl", tc
[k
], "green0", c
, 6);
7831 if (0 != p
->aud
) k
= AT_OK
;
7832 snprintf(c0
, 31, "AUD");
7834 lltoasc( c2
, 0LL ); /* FIXME: walk vc for all audio CC */
7835 lltoasc( c3
, p
->aud
);
7836 n
= (long long) p
->aud
;
7844 if ((0 != s
->ms
) && (p
->aud
> 0))
7845 build_stats_dl(d
, z
, "tsl", tc
[k
], "green0", c
, 6);
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;
7856 n
= (long long) cce_pids
[ 0x1FFF ];
7858 n
= (long long) p
->null
;
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");
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 */
7910 memset( c
, 0, sizeof(c
));
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
))
7929 if ( (CAP_NONE
== cap_now
)
7930 && ((s
->pn
== vc
[i
].pn
) || (-1 == s
->pn
)) )
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 */
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 */
7947 snprintf(c0
, 31, "%s", vc
[i
].cname
);
7950 snprintf(c0
, 31, "%s", s
->call
);
7955 tf
= find_tsidx( vc
[i
].tsid
, vc
[i
].pn
, WHO
);
7957 snprintf(c0
, 31, "%s", tsids
[ tf
].call
);
7961 snprintf(c0
, 31, "%d.%d", s
->ptc
, vc
[i
].pn
);
7962 // snprintf(c0, 31, "%04X.%d", vc[i].tsid, vc[i].pn);
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);
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
);
7982 if (CAP_NONE
!= cap_now
) {
7983 n
= (long long) psi_pids
[ vc
[i
].espid
[0] ];
7986 /* errors from cce_pids[ vc[i].espid[0] ] */
7987 n
= cce_pids
[ vc
[i
].espid
[0] ];
7990 /* packet counts from pids[ vc[i].espid[0] ] */
7991 n
= (long long) pids
[ vc
[i
].espid
[0] ];
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);
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");
8028 if ( (CAP_NONE
!= cap_now
)
8029 && (CAP_INFO
!= cap_now
) ) {
8032 && (cap_pn
== vc
[i
].pn
)
8033 && (cap_va
!= (j
- 1)) )
8039 if ( (CAP_NONE
== cap_now
)
8040 && (s
->pn
== vc
[i
].pn
)
8041 && (s
->va
== (j
- 1)) )
8044 if ( (CAP_NONE
== cap_now
)
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
] ];
8062 /* continuity counter errors */
8063 n
= (long long) cce_pids
[ vc
[i
].espid
[j
] ];
8066 /* counts from pids[ vc[i].espid[j] ] */
8067 n
= (long long) pids
[ vc
[i
].espid
[j
] ];
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;
8094 /* http video program audio select, only if more than one audio */
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);
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
);
8115 build_stats_dl(d
, z
, "tsl", "white1", sp
, c
, 6);
8119 asnprintf(d
, z
, "</div>\n\n"); /* tss */
8120 asnprintf(d
, z
, "</div>\n\n"); /* f5 at top */
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 "
8138 build_epg_filter_html4( char *d
, int z
)
8141 char r
[256], *lt
, *a
[3], *b
[3], *c
[2], *s
, *f
, *h
;
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) */
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 */
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
);
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
);
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
);
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>",
8200 (0 != j
) ? " More " : " Less ");
8201 asnprintf(d
, z
, "</div>\n");
8203 if (0 == web_config
) return;
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
++) {
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>",
8220 asnprintf(d
, z
, a
[j
]);
8222 asnprintf(d
, z
, "<img class=\042cfgbi black\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
);
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
);
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 */
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.
8275 build_cfg_buttons_html4( char *d
, int z
)
8278 char *a
[3], *b
[3], *c
[2], *s
, *f
, *h
;
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) */
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)
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");
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");
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
) {
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");
8355 /* Signal Display as SNR or Strength % toggle */
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 */
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 */
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
) {
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:
8437 Enable/disable auto-epg for each channel
8438 Scan each channel or all channels
8441 CSS server option buttons
8443 footer with valid HTML+CSS imgs
8448 dump_cfg_html ( char *caller
)
8451 char d
[32768], n
[256];
8454 if (0 != arg_scan
) return;
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
);
8465 dvrlog( LOG_INFO
, "%s write fail %s", WHO
, n
);
8469 /* scan all gets 15s refresh, otherwise don't refresh */
8471 // if ((CAP_NONE == cap_now) && (0 != scan_sig) && (0 == scan_one))
8472 if ((CAP_NONE
== cap_now
) && (0 != scan_sig
))
8475 snprintf( n
, sizeof(n
), "%s%d", NAME
, arg_devnum
);
8477 /* html 3 header has sig charts, html4 header does not */
8479 build_head_html( d
, z
, 7, n
, r
, WHO
);
8480 fprintf( f
, "%s", d
);
8484 build_cfg_buttons_html4( d
, z
);
8485 fprintf( f
, "%s", d
);
8489 if (0 != web_stats
) build_html_stats( d
, z
);
8490 fprintf( f
, "%s", d
);
8493 build_foot_html( d
, z
, 5); /* HTML validated */
8494 fprintf( f
, "%s", d
);
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?
8508 event_truncate ( char *d
, char *s
, int w
, int c
)
8510 unsigned int wc
, z
, z1
;
8513 char *lang
= "spa"; /* FIXME: get lang or blank from caller */
8517 if ( ( 0 == *s
) || ( w
< 1 ) || ( c
< 1 )
8518 || ( NULL
== d
) || ( NULL
== s
) )
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 */
8527 /* add space to end, if it will fit */
8529 if (z
< (PNZ
-1)) { p
[z
] = ' '; p
[z
+1] = 0; }
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 )) {
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 )) {
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 */
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 */
8627 config file /etc/atscap/atscap.spam
8628 e is pointer to the event to be checked
8631 -1 is not on spam list
8632 0-n is spam list index if found
8636 test_spam_event ( struct event_s
*e1
)
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
));
8651 if ((c1
>= 'A') && (c1
<= 'Z')) c1
|= 0x20; /* set bit 5 */
8653 for (i
= 0; i
< spam_idx
; i
++) {
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
);
8666 /* keep last time spam matched */
8668 /* keep last event time that spam matched */
8676 pthread_mutex_unlock( &spam_mutex
);
8681 /* strip timer or search list flags, : # $ @ . and , */
8682 /* only do this to copies of timer and search names, not the originals */
8685 strip_timer_flags ( char *n
)
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 */
8704 find_search_event ( char *t
, int wdb
, int chan
, char *caller
)
8707 char n
[ SEARCH_NAME_MAX
],
8708 r
[ SEARCH_NAME_MAX
],
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
);
8724 if ((c1
>= 'A' && c1
<='Z')) c1
|= 0x20;
8726 for (i
= 0; i
< search_idx
; i
++) {
8727 s
= &search_list
[i
];
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? */
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? */
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 */
8773 /* delete search event is only caller */
8774 /* return index of volatile timer that matches name to t, or return -1 */
8777 find_event_timer ( char *p
)
8781 char n
[ TIMER_NAME_MAX
],
8782 s
[ TIMER_NAME_MAX
],
8785 if (NULL
== p
) return -1;
8786 if (0 == *p
) return -1;
8789 astrncpy( s
, p
, sizeof(s
) );
8790 strip_timer_flags( s
);
8793 if ((c1
>= 'A' && c1
<='Z')) c1
|= 0x20;
8795 for (i
= 0; i
< timer_idx
; 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;
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
);
8823 /* no matching volatile timer found */
8827 /* return index of search entry match, or -1 if no match */
8830 find_search_timer ( char *m
)
8834 char n
[ TIMER_NAME_MAX
],
8835 s
[ TIMER_NAME_MAX
],
8838 astrncpy( s
, m
, sizeof(s
) );
8839 strip_timer_flags( s
);
8843 /* first char to lower case */
8844 if ((c1
>= 'A' && c1
<='Z')) c1
|= 0x20;
8846 for (i
= 0; i
< timer_idx
; 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 */
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
;
8872 /* return timer index if timer matches epg event e, else return -1 */
8875 find_timer_epg ( struct event_s
*e
)
8880 if (timer_idx
< 1) return -1; /* nothing to do */
8881 for (i
=0; i
<timer_idx
; 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
))
8894 /* exact timer match */
8895 if (t
->start
!= e
->st
) continue;
8896 if (t
->len
!= e
->ls
) continue;
8903 /* TESTME: is this redundant? other code elsewhere does the same? */
8904 /* update timer description from epg if timer matches epg event */
8907 update_timer_epg( void )
8913 for (i
= 0; i
< pgm
.idt
; i
++) {
8916 if (j
>= PGZ
) continue;
8918 k
= find_timer_epg( &epg
[j
] );
8920 if (-1 == k
) continue;
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
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
8942 parse_search_event ( char *c
)
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");
8961 if (timer_idx
>= TIMER_LIST_MAX
) {
8962 dvrlog( LOG_INFO
,"increase TIMER_LIST_MAX for more searches");
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], '.' );
8979 /* remove program number from name, extracting program number */
8985 /* rest are optional parameters */
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
);
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 */
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 */
9037 delete_timer ( int i
, char *caller
)
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 */
9050 /* stop capture if deleting the current timer */
9051 if ( (CAP_TIME
== cap_now
) && (i
== timer_rec
) ) {
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
++) {
9067 memcpy( t
, u
, sizeof(struct qtimer_s
) );
9070 timer_idx
--; /* one less timer now */
9072 /* delete needs to redraw list */
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
9085 add_search ( char *p
, int ch
)
9090 if (SEARCH_LIST_MAX
<= search_idx
) {
9091 dvrlog( LOG_INFO
, "search list full, increase SEARCH_LIST_MAX");
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
);
9103 if (TIMER_LIST_MAX
<= timer_idx
) {
9104 dvrlog( LOG_INFO
, "timer list full, increase TIMER_LIST_MAX");
9108 t
= &timer
[ timer_idx
];
9111 if (-1 == find_search_timer( p
)) {
9112 astrncpy( t
->name
, p
, TIMER_NAME_MAX
);
9116 t
->qstat
= T_SEARCH
;
9123 /* delete an entry in search_list array */
9126 delete_search ( int n
)
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 */
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:
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.
9166 delete_search_event ( int tm
, char *caller
) {
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 */
9188 /* remove search event(s) on timer list with matching length */
9190 j
= find_search_timer( n
);
9191 advrlog( LOG_INFO
, "find search timer %d", j
);
9193 delete_timer( j
, "remove search" );
9197 /* reset test for multiple timer search match loop */
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) */
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 */
9212 /* weekday bits means manual timer. only delete if no weekday bits */
9215 delete_timer( j
, "remove volatile" );
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 */
9231 /*********************************************************** PAT PMT VID AUD
9232 * cap pids are: PAT, PMT, VID, AUD
9233 * returns 0 if match, -1 otherwise
9237 find_cap_pid ( unsigned int pid
)
9241 for (i
= 0; i
< 4; i
++)
9242 if ( pid
== cap_pids
[i
] )
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
9254 find_vc_pgm ( int pgn
, char *caller
)
9257 for (i
=0; i
< vc_ncs
; i
++) if ( pgn
== vc
[i
].pn
) return i
;
9261 /* find vc# for this Video ES PID */
9263 find_vc_vespid ( unsigned short pid
)
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
])
9275 /* find pa# for this Video ES PID */
9277 find_pa_vespid ( unsigned short pid
)
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
])
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.
9297 find_pa_pgm ( int pgn
, char *caller
)
9301 for (i
=0; i
< pat_ncs
; i
++) {
9302 if ( pgn
== pa
[i
].pn
) {
9303 advrlog( LOG_INFO
, "%s finds pa[%d].pn %04X",
9312 /* see if pid is a pmt on the program association list, return index or -1 */
9315 find_pa_pmtpid ( short pid
)
9318 for (i
= 0; i
< pat_ncs
; i
++) if (pid
== pa
[i
].pmtpid
) return i
;
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
9332 find_vct_pgm ( int pgm
, char *caller
)
9336 for (i
=0; i
< vct_ncs
; i
++) {
9337 if ( pgm
== vc
[i
].pn
) {
9338 advrlog( LOG_INFO
, "%s finds vc[%d].pn %04X",
9348 /* if single program capture, set the pids based on cap_pn cap_vc cap_va */
9351 build_cap_pids ( char *caller
)
9355 s
= &ptc
[cap_chan
].sig
;
9358 if (CAP_NONE
== cap_now
) {
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 */
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);
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 */
9396 set_vc ( char *caller
)
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
9416 cap_vc
= find_vc_pgm( cap_pn
, WHO
);
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
9436 time_diff ( struct timespec
*d
, struct timespec
*a
, struct timespec
*b
)
9440 /* FIXME: portability issue? only long in the whole program */
9445 if (a
->tv_nsec
> nsec
) { /* borrow */
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
9461 word_wrap_indent ( FILE *f
, char *s
)
9466 int ts
, ns
, cc
, cl
, i
, i1
;
9468 if (NULL
== f
) return;
9472 /* column limit, needs to account for non word chars after tab */
9475 memset( indent
, 0, sizeof(indent
));
9476 t
= strchr( s
, '|'); /* first | is tab stop */
9479 memset( indent
, ' ', ts
);
9484 for (i
= 0; i
< i1
; i
++) {
9485 /* if this char not a space, find next space */
9487 t
= strchr( &s
[i
], ' ');
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:
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.
9524 sort_pgx (struct event_s
*e
, struct pgm_s
*p
, short *pgx
, char *caller
)
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
) {
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
) {
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.
9574 build_pg_epg (struct event_s
*e
, struct pgm_s
*p
, short *pgx
, char *caller
)
9577 int letmid
= 0; /* last etmid seen */
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 */
9593 /* Async operation on live epg[] skips locked EITs until sort_eit_epg done */
9595 if (CAP_NONE
!= cap_now
) {
9597 if (0 != epg_locks
[ j
/ PEZ
])
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 );
9608 if (0 != e1
->etmid
) {
9610 /* idt has total entries vs current filter list idx */
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 */
9624 if ( test_spam_event( e1
) >= 0 ) continue;
9626 /* skip aged out programs */
9628 if ( utsnow
> (e1
->st
+ e1
->ls
) ) {
9633 /* skip what doesn't match timer list, might be a cpu pig if called too much */
9635 if ( -1 == find_timer_epg( e1
)) {
9640 /* count events that passed above tests */
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. */
9664 show_vc_selection ( void )
9667 char vcn
[4] = "ALL"; /* virtual channel number */
9668 char vca
[4] = "ALL"; /* virtual channal audio number */
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
);
9684 if (CAP_NONE
== cap_now
) {
9688 svc
= find_vc_pgm( svp
, WHO
);
9691 snprintf( vcn
, sizeof(vcn
), " %1d ", svc
);
9692 snprintf( vca
, sizeof(vca
), " %1d ", sva
);
9694 /* use vct channel name info by default if available */
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
);
9707 snprintf(vcy
, sizeof(vcy
), "%s %2d.%d,%d",
9708 s
->sid
, s
->ptc
, svp
, sva
);
9711 snprintf( vcx
, sizeof(vcx
), "%s", s
->call
);
9712 snprintf( vcy
, sizeof(vcy
), "%s", s
->sid
);
9715 aprintf( stderr
, "%s", dca
.vstat
); /* set curpos */
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
);
9724 for (i
= 0; i
< 4; i
++) {
9726 if (0 == cap_pids
[i
]) {
9727 aprintf( stderr
, " " );
9729 aprintf( stderr
, " %04X", cap_pids
[ i
] );
9732 aprintf( stderr
, " %04X", cap_pids
[ i
] );
9736 aprintf( stderr
, " ALL");
9740 /* #define USE_RATE */
9741 /* shows subset of ATSC and MPEG Transport packet stats that are counted */
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 */
9756 static int ovcv
, ovca
; /* old values from last time thru */
9758 int i
, vcv
, vca
; /* bits per second */
9760 s
= &ptc
[cap_chan
].sig
;
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;
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
9790 /* "%sALN: %8d\n" / same as synert */
9792 , (columns
- 80) >> 1
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
9815 , (columns
- 80) >> 1
9818 // , cavc, (0==arg_cable)?p->tvct:p->cvct
9819 , cavc
, p
->tvct
+ p
->cvct
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
9839 , (columns
- 80) >> 1
9841 // , cvct, (0==arg_cable)?p->crctvct:p->crccvct
9842 , cvct
, p
->crctvct
+ p
->crccvct
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
9861 "%sPMT: %9d" BN
" " BW
9865 , (columns
- 80) >> 1
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 */
9887 aprintf( stderr
, BG DCARC
9892 /* show current bit rate for single program capture */
9900 , (columns
- 80) >> 1
9901 , p
->crcmp2
, p
->crcpat
, p
->crcpmt
9906 /* display legends, optional clear screen */
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 */
9925 s
= "SYN "; /* loss of sync, failed test sync */
9929 s
= "IFE "; /* input fifo error, error in loop */
9932 if (0 != arg_dummy) {
9934 cap_fail = FAIL_NONE;
9940 s
= "OFE "; /* output fifo error, error in loop */
9943 s
= "QOS "; /* transport stream errors too high */
9946 s
= "FUL "; /* volume full */
9949 s
= "QOS "; /* program guide load failed */
9952 s
= "AOS "; /* no signal */
9956 /* put version or last file opened on screen */
9957 if (0 == strlen(out_name
)) {
9958 aprintf( stderr
, HOM
"%s" BN CEL
, header_version
);
9961 /* extract filebase only */
9962 filebase(p
, out_name
, F_TFILE
);
9964 /* if (0 != strlen(p))
9967 /* ok, fail or user zap */
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
);
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
)
10000 aprintf( stderr
, DCA_SIG
" Strength%% ");
10004 aprintf( stderr
, DCA_SIG
" SNR dB " );
10008 aprintf( stderr
, DCA_SIG
" Freq Hz " );
10012 aprintf( stderr
, DCA_SIG
" Wavelen\042 " );
10015 #ifdef USE_DVB_EXPERIMENTAL
10017 aprintf( stderr
, DCA_SIG
" FreqDrift " );
10021 aprintf( stderr
, DCA_SIG
" BitErr1s " );
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. */
10040 test_termsize ( void ) {
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 */
10051 p
= getenv( "COLUMNS" );
10057 /* include <termio.h> defines 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
);
10063 lines
= ttyz
.ws_row
;
10064 columns
= ttyz
.ws_col
;
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");
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 */
10130 /* if event e1 is already on timer list, return 0, else return nz */
10133 search_timers ( struct event_s
*e1
)
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
++ ) {
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
);
10177 /* no timer matches the event */
10182 /* return day of week as day bits for event program guide entry pgo.
10183 Sunday is b6, Monday is b5 ... Saturday is b0
10187 get_pgm_wday ( struct event_s
*e1
)
10193 if (NULL
== e1
) return 0;
10196 localtime_r( &start
, <
);
10199 return (1<<(6-day
));
10202 /* e1 is pointer to one EPG event_s to use for new timer */
10205 add_search_event_timer ( struct event_s
*e1
)
10209 struct qtimer_s
*t
;
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",
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
);
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
++) {
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 */
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 */
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 */
10272 /* Add the timer to the end of of the timer list */
10273 t
= &timer
[ timer_idx
];
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 */
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?)
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 */
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 */
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 */
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 */
10368 } else { /* is filtered by days */
10369 if (0 != (wd
& get_pgm_wday( e1
)) )
10374 if (ch
== e1
->chan
) { /* if filtered by channel */
10375 if (0 == wd
) { /* and not filtered by days */
10377 } else { /* if filtered by channel and days */
10378 if (0 != (wd
& get_pgm_wday( e1
)))
10383 /* if program match selected, and no match, do not add it */
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 */
10397 refresh
.timers
= 1;
10403 /* these get some abstraction but still depends on vc and pa structures */
10404 /* load vc and pa structures:
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 */
10424 if (ch
> ptc_max
) { /* boundary check */
10425 dvrlog( LOG_INFO
, "%s %d >= ptc max %d",
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
);
10437 ok
= stat( n
, &fs
);
10440 /* this one isn't very important */
10441 advrlog( LOG_INFO
, "%s stat %s error %d", WHO
, n
, errno
);
10445 f
= fopen( n
, "r");
10447 dvrlog( LOG_INFO
, "%s read %s has error %d",
10449 return; /* no vct found */
10452 /* read vct_ncs and pat_ncs */
10453 r
= fread( ncv
, sizeof( int ), 1, f
);
10455 dvrlog( LOG_INFO
,"%s error %d rd vctncs %s",WHO
,r
,n
);
10460 r
= fread( ncp
, sizeof( int ), 1, f
);
10462 dvrlog( LOG_INFO
,"%s error %d rd patncs %s",WHO
,r
,n
);
10467 /* read vc and pa */
10468 r
= fread( v
, sizeof( vc
), 1, f
); /* FIXME: vc needs abstraction */
10470 dvrlog( LOG_INFO
, "%s error %d rd vc %s",WHO
,r
,n
);
10475 r
= fread( p
, sizeof( pa
), 1, f
); /* FIXME: pa needs abstraction */
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 */
10483 vcn
= find_pa_pgm( s
->pn
, WHO
);
10484 if (vcn
>= 0) s
->hres
= pa
[vcn
].hres
;
10490 /* Open ch.epg file, read indices into i, then read events into e.
10492 pgx[ ... ] index values for e[], -1 terminates list
10493 e[ pgx[ ... ] ] epg entries for each event_s
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)
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 */
10516 char n
[256], g
[256]; /* file name for input */
10517 unsigned short i
, j
;
10520 unsigned int et
; /* last event end time */
10521 int fdn
, cdn
; /* first and last day numbers */
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
);
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");
10544 dvrlog( LOG_INFO
, "%s error reading %s", WHO
, n
);
10545 p
->fail
= PG_FAIL_NF
;
10546 return; /* file problem */
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] */
10565 // p->idt = j; /* event index count */
10566 // p->idx = j; /* event index count */
10569 advrlog( LOG_INFO
, "%s %s empty", WHO
, n
);
10571 p
->fail
= PG_FAIL_GE
;
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
) {
10592 r
= fread( &e2
, sizeof(struct event_s
), 1, f
);
10596 /* program number translate set? */
10599 /* skip if no translate match */
10600 if (e2
.pn
!= s
->pnx
) continue;
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
);
10619 /* if any first day, see if s->yday within range of epg
10620 if not in range, set s->yday to today
10624 /* did year meridian occur between first day num and current day num? */
10627 /* same year, does day need to be fixed because it's out of range? */
10628 if ((s
->yday
< fdn
) || (s
->yday
> cdn
)) {
10630 advrlog(LOG_INFO
, "%s same year fix s->yday %d",
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
)) {
10640 advrlog(LOG_INFO
, "%s next year fix s->yday %d",
10645 if (s
->yday
< fdn
) {
10647 advrlog(LOG_INFO
, "%s this year fix s->yday %d",
10655 advrlog(LOG_INFO
, "%s no fdn, s->yday %d is today",
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 */
10672 /* some kind of time should have been set by at least one event */
10674 /* if no time set, guide is blank */
10675 p
->fail
= PG_FAIL_BG
;
10677 /* last entry read is used for guide expired determination */
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 */
10691 load_guide ( int ch
, char *caller
)
10693 char n
[256]; /* file name */
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",
10709 s
= &ptc
[ ch
].sig
;
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
) {
10726 pkt
.feit
= PAY_GOOD
; /* cheat event status display */
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 */
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
;
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? */
10759 advrlog( LOG_INFO
, "EPG%02d %s blank", ch
, n
);
10760 p
->fail
= PG_FAIL_BG
;
10764 build_pg_epg( epg
, &pgm
, pg
, WHO
);
10768 advrlog( LOG_INFO
, "EPG%02d build pg %d/%d", ch
, p
->idx
, p
->idt
);
10771 /* build filtered pg from full epg */
10773 advrlog( LOG_INFO
, "EPG%02d %s %s filtered %d/%d",
10774 ch
, WHO
, caller
, p
->idx
, p
->idt
);
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
;
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
);
10794 /* set physical transmission channel from s->chan
10795 and virtual channel and audio from s->vc and s->va
10799 set_channel ( struct sig_s
*s
, char *caller
)
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 */
10810 dvrlog( LOG_INFO
, "%s %d has freq %f", WHO
, s
->chan
, s
->freq
);
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
);
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
);
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
);
10836 "FE %d SET FRONTEND %d Hz %d fail %d",
10837 fe_file
, f
, s
->freq
, ir
);
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. */
10846 ns
.tv_nsec
= aos_delay
* 1000000;
10847 nanosleep(&ns
, NULL
);
10850 dvrlog( LOG_INFO
, "%s FE %d FILE NOT OPEN?",
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. */
10866 test_timer_wakeup_devices ( struct qtimer_s
*t
)
10875 if (0 == arg_tmpfs
) return;
10877 /* is awake already ? */
10878 if (-1 == utswuv
) return;
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 ? */
10889 utswuv
= t
->start
- 30;
10890 advrlog( LOG_INFO
, "utswuv %d in %d", utswuv
, utswuv
- utsnow
);
10894 s
= &ptc
[t
->chan
].sig
;
10896 /* One-shot test wakeup until capture stops and clears the flip-flop. */
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" );
10906 /* does the delay cause an error? lets see it, if so */
10907 dvrlog( LOG_INFO
, "hdd wakeup error %d %s", errno
, strerror(errno
) );
10911 /* may need to actually write something other than dirent to spin-up */
10912 fprintf(f
, "%s", "awaken");
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.
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
);
10938 check given timer q to see if it's current
10939 return -1 if not current, or q if current
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 */
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
);
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 */
10975 if ( (utsnow
>= tstart
) && (utsnow
< tstop
) )
10976 return q
; /* this timer might be active */
10981 /* add up all the weekdays and lengths for each timer, return # of sec */
10984 total_timers ( void )
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 )
11013 /* no $ or #, must be overwriting, so add one timer only */
11014 /* makes timers look wrong if any $ or #, but they are correct */
11019 /* no weekdays, only a single timer that does not reschedule */
11026 /* total number of timers */
11027 if (0 != arg_capture
)
11029 /* no timer, but some time to calc for usage */
11031 return arg_capture
; /* -s # of seconds */
11033 /* got number of timers and number of seconds used */
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
11047 calc_future_date ( int r
)
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
) {
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 */
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
) )
11085 t
+= SECDAY
; /* add a day */
11091 /* compute next weekday timer start time from now */
11092 /* store result in timer[r] */
11095 calc_first_weekday( struct qtimer_s
*t
)
11098 struct tm f
; /* current event or future start */
11101 if (T_SEARCH
== t
->qstat
) return;
11102 if (0 == t
->days
) return;
11105 /* start at current day meridian */
11108 /* parse weekday timer sets h and m */
11109 t
->start
+= (t
->h
* SECHR
) + (t
->m
* SECMIN
);
11111 /* start at current weekday */
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
))
11126 /* add a day, try next day bit and keep within weekday bit shift range */
11127 t
->start
+= SECDAY
;
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
) {
11152 /* sort search list */
11155 sort_searchlist ( void )
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 );
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 */
11179 sort_spamlist ( int st
)
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
++) {
11190 k
= strncasecmp( spam_list
[i
].name
,
11194 k
= spam_list
[j
].st
- spam_list
[i
].st
;
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
11214 sort_timers ( void )
11217 struct qtimer_s tmp
;
11219 if (timer_idx
< 2) return;
11221 advrlog( LOG_INFO
, "%s", WHO
);
11223 z
= sizeof(struct qtimer_s
);
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 */
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
) ) {
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
,
11261 k
= 1; /* flag it for swap */
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
) )
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 */
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
11298 int i
, z
, today
= 0;
11299 char *d
, *s
, *wt
= "SMTWTFS";
11305 for ( i
=0; i
< 7; i
++) {
11307 if ( ( timer
[ tq
].start
>= utsmid
)
11308 & ( timer
[ tq
].start
< utsmid
+ SECDAY
)
11309 & ( tloc
.tm_wday
== i
) )
11312 /* highlight current day of week */
11313 if ( today
!= 0 ) asnprintf( d
, z
, BY
);
11316 if ( bascii
[ i
] & 1 ) s
= wt
+ i
;
11317 asnprintf( d
, z
, "%c", *s
);
11320 if ( today
!= 0 ) asnprintf( d
, z
, BK
);
11324 /* close the atsc device(s) */
11327 close_device ( char *caller
)
11330 dvrlog( LOG_INFO
, "*** %s device %s already closed",
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;
11348 aprintf( stderr
, HOM BR
"CLOSED %-24s" BN
, in_name
);
11351 /* put the console back to a usable state */
11354 console_reset ( void )
11356 struct termios modes
;
11359 /* no stdio in detached mode or it will block */
11360 if (0 != arg_detach
)
11364 fcntl( 0, F_GETFL
, &f
);
11366 fcntl( 0, F_SETFL
, f
);
11368 /* indicate the console will need init before nonblock nonecho use */
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 */
11383 show_mem_static ( void )
11385 long long i
, t
, m
, v
;
11391 aprintf( stdout
, BG
"Static memory:\n" );
11394 i
+= sizeof(pg
) + sizeof(pg1
) + sizeof(pg2
);
11395 i
+= sizeof(pgm
) + sizeof(pgm1
) + sizeof(pgm2
);
11401 aprintf( stdout
, " %20s %16s (%d events x %u)\n",
11402 "EPG + helpers", d
, PGZ
, sizeof(struct event_s
) );
11405 i
= sizeof(mgt
) + sizeof(vct
); /* 2 more payload_s */
11406 i
+= sizeof(stt
) + sizeof(rrt
); /* 2 more payload_s */
11408 i
+= v
; /* vc helper is pretty big */
11410 /* TODO: shrink these to one or two, kind of big at 81920 bytes each */
11412 m
+= sizeof(mgp
); /* kind of large */
11413 m
+= sizeof(mgs
); /* dynamic now */
11416 /* VCT MGT STT RRT tables static, EIT and ETT tables dynamic */
11419 /* VCT MGT STT RRT EIT ETT tables static */
11425 aprintf( stdout
, " %20s %16s (%d tables + vc %lld + mg* %lld)\n",
11426 "ATSC + helpers", d
, c
, v
, m
);
11429 i
= sizeof(pat
) + sizeof(cat
); /* 3 payload_s */
11431 v
= sizeof(pa
); /* pa helper is pretty big */
11434 m
= sizeof(last_cc
); /* plus PID trackers, 104k */
11435 m
+= sizeof(cce_pids
);
11436 m
+= sizeof(psi_pids
);
11441 aprintf( stdout
, " %20s %16s (%d tables + pa %lld + pids %lld)\n",
11442 "MPEG + helpers", d
, 3, v
, m
);
11445 i
= sizeof(cap_text
) + sizeof(cap_stats
);
11447 aprintf( stdout
, " %20s %16s (%u text %u errlog)\n",
11448 "Capture Stats", d
, sizeof(cap_text
) , sizeof(cap_stats
) );
11451 i
= sizeof(dvrlog_list
) + sizeof(tvct_text
) + sizeof(mgt_text
);
11453 aprintf( stdout
, " %20s %16s\n", "Text + helpers", d
);
11456 i
= sizeof(ptc
) + sizeof(timer
);
11458 aprintf( stdout
, " %20s %16s\n", "Chans & Timers", d
);
11461 i
= sizeof( spam_list
) + sizeof( search_list
);
11463 aprintf( stdout
, " %20s %16s\n", "Search & Spam", d
);
11466 #ifndef USE_DYNAMIC
11467 i
= sizeof( fifo_buffer
);
11469 aprintf( stdout
, " %20s %16s\n", "Capture FIFO", d
);
11472 i
= sizeof( frames
);
11474 aprintf( stdout
, " %20s %16s\n", "frames[]", d
);
11477 i
= sizeof( sequences
);
11479 aprintf( stdout
, " %20s %16s\n", "sequences[]", d
);
11483 aprintf( stdout
, " %20s " BW
"%16s bytes\n", "Total static:", d
);
11491 /* sum the dynamic tables in use and report to user */
11494 show_mem_dynamic ( void )
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
;
11508 aprintf( stdout
, " %20s %16s\n", alloc_list
[ j
].n
, d
);
11514 aprintf( stdout
, " %20s " BW
"%16s bytes\n"BN
, "Total dynamic:", d
);
11520 show_mem_use ( void )
11525 t
= show_mem_static();
11526 t
+= show_mem_dynamic();
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 */
11537 console_exit ( int errorcode
)
11539 struct timespec ns
;
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(); */
11553 munlockall(); /* clean exit */
11557 if (0 != arg_mcport
) udp_close_socket( &udp
);
11561 /* if (0 != arg_www) http_close_sockets(); */
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 */
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
) {
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? */
11590 aprintf( stderr
, "\nExit code %d\n\n", errorcode
);
11595 snprintf(n
, sizeof(n
), "/var/run/%s/%s%d.pid", NAME
, NAME
, arg_devnum
);
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.
11612 save_spamlist ( char *caller
)
11622 while (EBUSY
== pthread_mutex_trylock( &spam_mutex
))
11623 nanosleep( &atomic_sleep
, NULL
);
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");
11635 dvrlog( LOG_INFO
, "%s can't write %s", WHO
, n
);
11636 pthread_mutex_unlock( &spam_mutex
);
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
);
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
);
11660 i
= stat( n
, &fs
);
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 */
11676 save_config ( char *caller
)
11680 struct qtimer_s
*t
;
11681 char ba
[16]; /* binary to ascii output string */
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" );
11698 fprintf( stderr
, CLS
"Can't open %s for write --", fn
);
11699 dvrlog( LOG_ERR
, "can't write config %s", fn
);
11707 fprintf( o
, "# Configuration %s created %s\n#\n", fn
, date_now
);
11709 "# You may edit this file but only valid settings are kept.\n#\n"
11714 "# SYNOPSIS: [optional]\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"
11721 "# nameX is event name and [optional #/@ flags] and [.program number].\n"
11722 "# After the '.program', ',1' can sometimes be specified for SAP.\n"
11724 "# For V and W lines, nameX with #/@ will append -weekday/-mmdd.\n"
11726 "# days are 1111111 for SMTWTFS, 0 = no cap that day.\n"
11728 "# LCN = logical channel number, is C-line load order starting with 0.\n"
11730 "# AnameX:channel:days\n"
11731 "# Search for name[.pgn] [on logical channel] [for match on days]\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"
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"
11747 "# I lines, user interface settings. Can be omitted. Updates on next save.\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"
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"
11761 "# Wchannel:hh:mm:len:days:nameX\n"
11762 "# channel = LCN index, daily start time, len in minutes, days to cap\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"
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"
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"
11779 "# atscap_conf(5)\n"
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
);
11789 /* save web user interface config */
11790 fprintf( o
, "I%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d\n\n",
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
);
11823 if (search_idx
> 0) fprintf( o
, "\n");
11825 /* save channels on the scan list */
11826 for (i
=0; i
<scan_idx
; i
++) {
11828 s
= &ptc
[ ch
].sig
;
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
);
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
);
11843 fprintf( o
, ":%d:%d",
11844 s
->pnx
, (s
->pnxf
< 0)?ch
:s
->pnxf
);
11846 fprintf( o
, "\n" );
11849 /* save timers that match the channel */
11850 for (j
= 0; j
< timer_idx
; 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
);
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",
11886 advrlog( LOG_INFO
, "%s wtimer W%02d:%02d:%02d:%03d:%s:%s",
11899 /* Save Time source */
11900 fprintf( o
, "# Zch gets time from PTC ch. Zhost gets time from ntpd\n");
11902 fprintf( o
, "Z%d", cap_zc
);
11903 if (0 != cap_zd
) fprintf( o
, ":%d", cap_zd
);
11905 if (0 != *ntp_name
) {
11906 fprintf( o
, "Z%s", ntp_name
);
11911 fprintf( o
, "\n\n");
11916 /* make configuration auto-reload not trigger, no need for it */
11917 i
= stat( fn
, &st
); /* get file time AFTER close */
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.
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;
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 */
11972 show_timers ( void )
11975 int timer_check
= 0; /* -1 if current time not within timer range */
11978 char *sc
, *tc
, *rc
; /* status, timer, reschedule colors */
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
);
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 );
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
);
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; */
12077 /* if this changes, timer refresh is set for list update */
12078 oldstatus
= t
->qstat
;
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
) {
12092 if (0 == t
->chan
) chan_rec
[0] = 0;
12094 /* advrlog( LOG_INFO, "%s T_SEARCH0 %s", WHO, t->name); */
12098 /* weekday bits set? */
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 */
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
;
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 */
12142 /* no cap in progress, ok to activate */
12147 t
->qstat
= T_ACTIVE
; /* only visible until AOS done */
12148 advrlog( LOG_INFO
, "%s T_ACTIVE0 %s", WHO
,
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 */
12158 /* manual cap already in progress, ignore this timer */
12160 t
->qstat
= T_IGNORE
;
12161 advrlog( LOG_INFO
, "%s T_IGNORE0 %s", WHO
,
12163 /* force list update, manual timer ignored */
12164 /* refresh.timers = 1; */
12167 /* triggers after ACTIVE, which displays until cap starts */
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
,
12174 cap_chan
= t
->chan
;
12176 tc
= BW
; /* current active timer is bright white */
12178 t
->qstat
= T_IGNORE
;
12179 advrlog( LOG_INFO
, "%s T_IGNORE1 %s", WHO
,
12184 /* info cap needs to stop when timer activates */
12186 t
->qstat
= T_ACTIVE
;
12187 advrlog( LOG_INFO
, "%s T_ACTIVE1 %s", WHO
,
12189 stop_capture( "show timers2", FAIL_NONE
);
12191 /* this isn't working? maybe needs a hold down until cap stopped */
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 */
12205 advrlog( LOG_INFO
, "%s T_DONE0 %s", WHO
,
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",
12219 t
->future
= calc_future_date( i
);
12220 t
->start
+= t
->future
;
12222 /* list needs resorting since it's aged to bottom */
12224 refresh
.timers
= 1;
12225 /* otherwise this is a volatile timer that needs removal */
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 */
12234 advrlog( LOG_INFO
, "%s volatile timer %s deleted",
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],
12242 sizeof(struct qtimer_s
) );
12245 refresh
.timers
= 1;
12247 /* update config to remove the expired timer */
12250 /* volatile got deleted, see if next on list is search timer */
12251 if (T_SEARCH
== t
->qstat
) {
12257 if (0 == t
->chan
) chan_rec
[0] = 0;
12258 advrlog( LOG_INFO
, "%s T_SEARCH0 %s", WHO
,
12261 refresh
.timers
= 1;
12262 /* loop needs to restart or else it will corrupt the first search entry */
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
);
12281 t
->future
= calc_future_date( i
);
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
,
12294 t
->qstat
= T_FUTURE
;
12296 /* advrlog( LOG_INFO, "%s T_FUTURE0 %s", WHO, t->name); */
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); */
12314 /* if more than 1 timer */
12316 struct qtimer_s
*u
;
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
;
12328 /* blink red when within 30 minutes of activation
12329 red is within 1 hour
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
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 */
12365 refresh
.timers
= 1;
12370 snprintf( chan_rec
, sizeof(chan_rec
)-1, "%2d", t
->chan
);
12372 /* round timer len up to nearest minute */
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;
12390 memcpy( &tloc1
, localtime( &trec1
), sizeof( tloc1
) );
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 */
12400 dst
= '+'; /* spring forward */
12402 /* add hour for correct display time */
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
;
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 */
12432 aprintf( stderr
, BK
"%s %s%16s" BN
,
12438 /* volatile timer will try to fill in with partial episode description */
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 */
12462 remove_renamed ( void )
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
);
12486 /* del_name is a one shot, so clear it */
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
);
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
);
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 */
12542 /* want to close the device properly if possible */
12545 signal_test ( void )
12547 if (0 == sig_val
) return; /* nothing to do? */
12549 advrlog( LOG_INFO
, "received SIG%s", sig_text
[sig_val
] );
12554 /* these three indicate user requested program terminate */
12559 dvrlog( LOG_INFO
, "exiting on signal %d", sig_val
);
12560 stop_capture( WHO
, FAIL_NONE
);
12563 /* GNU screen changed the term size, or xterm was resized */
12565 dvrlog( LOG_INFO
, "%s tty dimension changed", WHO
);
12570 dvrlog( LOG_INFO
, "%s broken pipe or socket", WHO
);
12573 /* even with SEGV still want to try to close device properly if possible */
12580 dvrlog( LOG_INFO
, "%s signal %s ignored",
12581 WHO
, sig_text
[sig_val
] );
12589 /* proto this one rather than esc handling below */
12591 /* non-blocking non-echoing single-char stdin, from mzplay.c */
12594 console_getch ( void ) {
12598 signal_test(); /* any flags worth noticing? */
12599 if (0 != sig_kill
) console_exit(0);
12601 if (0 != arg_detach
) return 0;
12603 if (console_init
) {
12604 struct termios modes
;
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" );
12619 fcntl( 0, F_GETFL
, &f
);
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); */
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
12636 console_scan_esc ( unsigned char *kb
)
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); */
12655 /* "ESC [" and 4 choices for arrows are A B C D */
12657 /* no chars to clear after these */
12659 *kb
= KB_UP
; /* UP ARROW */
12664 *kb
= KB_DN
; /* DOWN ARROW */
12669 *kb
= KB_RT
; /* RIGHT ARROW */
12679 *kb
= KB_HM
; /* HOME */
12684 *kb
= KB_EN
; /* END */
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 */
12701 case '7': /* newer aterm doing this? */
12703 *kb
= KB_HM
; /* HOME */
12707 *kb
= KB_IN
; /* INSERT */
12711 *kb
= KB_DL
; /* DELETE */
12714 case '8': /* newer aterm doing this? */
12716 *kb
= KB_EN
; /* END */
12720 *kb
= KB_PU
; /* PAGE UP */
12723 *kb
= KB_PD
; /* PAGE DOWN */
12726 *kb
= 0; /* try to prevent false triggers if no conditions met */
12733 /* wrapper to return keyboard special keys as single byte */
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
;
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) */
12756 get_scanlist_offset ( unsigned int channel
)
12759 for (i
=0; i
<scan_idx
; i
++)
12760 if (scan_list
[i
] == channel
)
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.
12782 get_signal_lock ( struct sig_s
*s
)
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
);
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
);
12807 s
->strength
= 100; /* leave color normal to alert it's fake */
12808 scan_rate
= 10; /* was 9 */
12813 if (test_mode
!= 0) {
12814 clock_gettime( clock_method
, &sig_stop
);
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 */
12838 dvrlog( LOG_INFO
, "FE READ STATUS fail %d", ir
);
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
);
12854 dvrlog( LOG_INFO
, "FE READ STRENGTH PTC %02d FAILED %d",
12860 ir
= ioctl( fe_file
, FE_READ_SNR
, &s
->snr
);
12861 advrlog( LOG_INFO
, "%s FE READ SNR dB %2d.%02d ",
12864 (0xFF & s
->snr
* 100) >> 8 );
12867 dvrlog( LOG_INFO
, "FE READ SNR PTC %02d FAILED %d",
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
);
12878 s
->freqr
= fe_param
.frequency
;
12879 s
->fohz
= s
->freqr
- s
->freq
;
12882 ir
= ioctl( fe_file
, FE_READ_BER
, &fe_ber
);
12884 if (0 == ir
) s
->ber
= fe_ber
;
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
;
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
);
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
);
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);
12929 if (test_mode
== 0)
12930 advrlog( LOG_INFO
, "%s: signal scan rate bogus", WHO
);
12936 manual timer validate
12937 check list of current timers for tstart to tstart+tlen
12940 1 if time overlap with existing timer
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
12947 validate_timer ( int tstart
, int tlen
)
12950 struct qtimer_s
*t
;
12954 /* FIXME: not implemented yet because I can move intersecting timers */
12956 for (i
=0; i
< timer_idx
; i
++) {
12958 /* first check for tstart intersecting a timer */
12959 if ( (tstart
>= t
->start
)
12960 && (tstart
<= (t
->start
+ t
->len
) ) )
12963 /* then check for tstop intersecting a timer */
12964 tstop
= tstart
+ tlen
;
12965 if ( (tstop
>= t
->start
)
12966 && (tstop
<= (t
->start
+ t
->len
) ) )
12969 /* if the sets do not intersect, return no error */
12973 /* clear all the timers */
12976 reset_timers ( void )
12979 memset( timer
, 0, sizeof(timer
) );
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
12993 get_timer_reschedule ( void )
12998 unsigned int req_r1
;
12999 unsigned int req_r2
;
13003 /* turn echo and blocking back on, get input */
13006 aprintf( stderr
, SCV BL
13007 "%s%sReschedule which timer(s) (0-%d) ?"BN
" " CEL
,
13008 dca
.hstat
, warn
, timer_idx
-1 );
13012 fgets( req_in
, sizeof(req_in
)-1, stdin
);
13014 /* dummy read to reset back to nonblock/nonecho */
13017 /* redraw header and return if <ENTER> */
13018 if ( req_in
[0] == 10 ) {
13020 /* aprintf( stderr, "%s%s" CEL, dca.hstat, header2 ); */
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
);
13038 p
= strchr( req_r
, '-' ); /* - means range */
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
)
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
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 */
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
13079 timer_reschedule( void )
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.
13104 if (CAP_NONE
== cap_now
) return; /* no cap no zap */
13105 /* tell ts capture to remove the files when capture terminated */
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 */
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
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:
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.
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.
13155 get_timer_add ( void )
13158 char timer_in
[64]; /* timer input string */
13159 char nbuf
[32]; /* timer name buffer */
13163 int ttime
, ttlen
, ttchan
;
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
);
13178 /* turn echo and blocking back on, get input */
13181 aprintf( stderr
, SCV BL
13182 "%s%sCh Hh Mm Len Wd Name or +Search:Ch:Wd >"
13183 BN
" " CEL
, dca
.hstat
, warn
);
13185 fgets( timer_in
, sizeof(timer_in
)-1, stdin
);
13187 /* dummy read to reset back to nonblock/nonecho */
13190 /* do nothing if <ENTER> */
13191 if ( timer_in
[0] == 10 ) {
13193 /* aprintf( stderr, "%s%s", dca.hstat, header2); */
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] );
13212 save_config( WHO
);
13214 refresh
.timers
= 1;
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;
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 */
13237 /* loop until a good value is input, or [ENTER] to exit */
13242 dvrlog( LOG_INFO
, "add volatile timer");
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 */
13254 /* current scan/cap channel */
13257 /* make sure it's only 7 bits */
13261 /* NO: use IGNORE status to warn user. */
13262 /* make sure no timers in list conflict */
13264 ok
= validate_timer( ttime
, ttlen
); /* nop for now */
13265 if (ok
!= 0) return;
13269 if (timer_idx
>= TIMER_LIST_MAX
) {
13270 dvrlog( LOG_INFO
, "%s recompile with larger TIMER_MAX", WHO
);
13275 /* fix midnight if it wrapped during input */
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
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 */
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.
13297 et
= utsmid
+ ttime
+ ttlen
;
13299 /* This adds logic path c). Logic paths a) and b) already done. */
13300 if (utsnow
> et
) wdo
= SECDAY
;
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 */
13312 timer
[timer_idx
].future
= calc_future_date( timer_idx
);
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
);
13325 astrncpy( timer
[timer_idx
].name
, nbuf
, TIMER_NAME_MAX
);
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 */
13340 /* user input to remove timer */
13343 get_timer_delete ( void )
13348 unsigned int del_r1
;
13349 unsigned int del_r2
;
13353 /* turn echo and blocking back on, get input */
13356 aprintf( stderr
, SCV BL
13357 "%s%sDelete which timer(s) (0-%d) ?"BN
" " CEL
,
13358 dca
.hstat
, warn
, timer_idx
-1 );
13362 fgets( del_in
, sizeof(del_in
)-1, stdin
);
13364 /* dummy read to reset back to nonblock/nonecho */
13367 /* redraw header and return if <ENTER> */
13368 if ( del_in
[0] == 10 ) {
13370 /* aprintf( stderr, "%s%s" CEL, dca.hstat, header2 ); */
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
);
13387 p
= strchr( del_r
, '-'); /* - means range */
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
)
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 */
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 */
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
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
13438 move_timer ( int tm
, int tc
)
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 */
13461 snprintf( o
, sizeof(o
)-1, "W%02d:%02d:%02d:%03d:%s:%s",
13465 timer
[tm
].len
/ 60,
13468 } else { /* handle volatile timers */
13470 snprintf( o
, sizeof(o
)-1, "V%02d:%010d:%05d:%s",
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 */
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 */
13491 move_search_event ( int tm
, int tc
)
13497 char o
[1024]; /* want at least 16 entries of 64 chars for move */
13500 int i
, tf
; /* counter, timer found */
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");
13514 tf
= 0; /* no event search or event timer found */
13516 /* copy search event */
13517 snprintf( p
, 1 + SEARCH_NAME_MAX
, "A%s", tn
);
13519 if (0 != timer
[tm
].chan
) { /* channel limit */
13521 snprintf( p
, 4, ":%02d", timer
[tm
].chan
);
13522 if (0 != timer
[tm
].days
) { /* weekday limit */
13523 utoab( wd
, 7, timer
[tm
].days
);
13526 snprintf( p
, 9, ":%7s", wd
);
13530 advrlog( LOG_INFO
, "%s", o
); /* first one uses o, rest use p */
13532 snprintf( p
, 2, "\n");
13535 /* copy any volatile timers that match search event */
13536 for (i
= 0; i
< timer_idx
; i
++) {
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 */
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",
13557 advrlog( LOG_INFO
, "%s", p
);
13560 /* don't want to repunch this. for now, don't move weekdays w/ search */
13562 if (0 != timer
[i
].days
) {
13563 /* is weekday event, set up weekday bit string, len minutes */
13564 utoab( wd
, 7, timer
[i
].days
);
13567 snprintf( p
, 64, "T%02d:%02d:%02d:%03d:%s:%s\n",
13574 advrlog( LOG_INFO
, "%s", p
);
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
);
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
);
13600 dvrlog( LOG_INFO
, "error appending %s", fo
);
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.
13613 get_timer_move ( void )
13616 char *warn
= BW
; /* no warn color first time */
13620 /* turn echo and blocking back on, get input */
13623 aprintf( stderr
, SCV BL
13624 "%s%sMove timer nn to config n <tt c> ?"BN
" " CEL
,
13629 fgets( timer_in
, sizeof(timer_in
)-1, stdin
);
13631 /* dummy read to reset back to nonblock/nonecho */
13634 /* redraw header and return if <ENTER> */
13635 if ( timer_in
[0] == 10 ) {
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 */
13666 if (T_SEARCH
!= timer
[tm
].qstat
) {
13667 move_timer( tm
, tc
);
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" );
13678 refresh
.timers
= 1;
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
13698 parse_channel ( char *t
)
13701 struct sig_s
*s
= NULL
; /* sig_s member of ptc */
13703 char *p
[8], tf
[256]; /* parameters, touch file */
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;
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",
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
);
13745 /* save index in scan_list and increment, starting at logical ch 0 */
13747 // was = ch for physical channel numbering, can't revert it easily now
13750 s
->freq
= frequencies
[ ch
];
13752 /* set defaults in case parse fails */
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
) {
13802 snprintf( n
, sizeof(n
)-1, "%s%spg/%02d.epg",
13803 out_path
, ram_path
, s
->chan
);
13804 ok
= stat( n
, &st
);
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");
13824 dvrlog( LOG_INFO
, "can't open %s", tf
);
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);
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");
13842 dvrlog( LOG_INFO
, "can't open %s", tf
);
13848 advrlog( LOG_INFO
, "%s %d stop", WHO
, ch
);
13849 advrlog( LOG_INFO
, "\n");
13853 /* -w name:netmask:port:threads
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
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
;
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 */
13879 port
= WWW_PORT
+ arg_devnum
; /* each instance defaults to new port */
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
) );
13892 /* address required, 0.0.0.0 is INADDR_ANY */
13894 p2
= strchr( p1
, ':');
13898 /* host name or IP address */
13899 astrncpy( host
, p1
, sizeof(host
) );
13901 /* netmask optional 0-32 or dotted notation, default no mask */
13903 p3
= strchr( p2
, ':');
13907 /* port optional 0-65535, default 80+dev# */
13909 p4
= strchr( p3
, ':');
13913 /* threads optional, default 3 */
13920 /* if mask not blank */
13922 /* mask can be dotted notation or number of bits */
13923 if (NULL
!= strchr( p2
, '.')) {
13924 /* xlate dotted version */
13925 dotouint( &mask
, p2
);
13927 /* mask can be xlated from bit count */
13928 if ((j
> 0) && (j
< 33)) {
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
;
13952 i
= stat64( root
, &fs
);
13954 if (S_IFDIR
& fs
.st_mode
) {
13955 advrlog( LOG_INFO
, "using %s for www root\n", root
);
13957 astrncpy( root
, WWW_ROOT
, WWW_ROOT_MAX
);
13960 astrncpy( root
, WWW_ROOT
, WWW_ROOT_MAX
);
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
) );
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.
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
);
13989 /* network byte order */
13990 addr
= 0xFF & localhost
->h_addr
[0];
13992 addr
|= 0xFF & localhost
->h_addr
[1];
13994 addr
|= 0xFF & localhost
->h_addr
[2];
13996 addr
|= 0xFF & localhost
->h_addr
[3];
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
14007 validate_config (void)
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;
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
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;
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 */
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");
14074 load config calls this to remove duplicate timers
14075 check for exact duplicate and return 0 if match else return non zero
14079 test_timer_dup ( char *caller
, char *n
, unsigned int st
, unsigned int ls
)
14082 struct qtimer_s
*t
;
14084 for (i
=0; i
< timer_idx
; i
++) { /* step thru known timers */
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 */
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 */
14106 find_lcn_ptc_pn( int ch
, int pn
)
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;
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
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
;
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
);
14152 /* mangle s1 instead of s */
14153 ac
= as
= y
= m
= d
= hh
= mm
= al
= i
= pn
= 0;
14156 if (timer_idx
>= TIMER_LIST_MAX
) {
14157 dvrlog(LOG_INFO
, "rebuild with larger TIMER_LIST_MAX for %s", s
);
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
);
14178 dvrlog(LOG_INFO
, "Bad config V-line: %s", s
);
14182 if ( NULL
== p
[0] ) return;
14185 /* start uts or YYYYMMDD */
14186 if ( NULL
== p
[1] ) return;
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;
14197 if (NULL
== p
[3]) return;
14199 if (NULL
!= p
[4]) ei
= atoi(p
[4]);
14201 /***** new human readble format 20060820 */
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;
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;
14225 if (NULL
== p
[5]) return;
14226 n
= p
[5]; /* name */
14229 memset( &tmv
, 0, sizeof( tmv
));
14231 /* fill in time structure to convert to new as, al should be correct */
14236 tmv
.tm_mon
= m
- 1;
14237 tmv
.tm_year
= y
- 1900;
14239 as
= mktime( &tmv
); /* convert to uts */
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 */
14249 localtime_r( &at
, &tmv
);
14251 /* parse didn't work */
14253 dvrlog( LOG_INFO
, "%s blank name %s", WHO
, s
);
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
);
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
);
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 */
14281 t
->qstat
= T_FUBAR
;
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
, '.');
14301 t
->vc
= find_vc_pgm( pn
, WHO
);
14304 /* parse progam audio number from timer name */
14305 r
= strchr( n
, ',');
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
);
14319 dvrlog( LOG_INFO
, "%s range error: %s", WHO
, s
);
14324 /* parse timer in config file format
14325 ignores timers with no weekday bits set
14326 or crazy channel/hour/minute/runtime
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 */
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
);
14352 /* try the obvious disqualify */
14353 if ( strlen(s
) > 64 ) {
14354 advrlog( LOG_INFO
, "timer too long, ignoring %s", s
);
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;
14365 if ((ac
< 0) || (ac
>= ptc_max
)) return;
14367 if (NULL
== p
[1]) return;
14369 if ((ah
< 0) || (ah
> 23)) return;
14371 if (NULL
== p
[2]) return;
14373 if ((am
< 0) || (am
> 59)) return;
14375 if (NULL
== p
[3]) return;
14377 if ((ar
< 0) || (ar
> 1440)) return;
14379 if (NULL
== p
[4]) return;
14382 if (NULL
== p
[5]) return;
14384 if (0 == *an
) return;
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? */
14400 as
= (SECHR
* ah
) + (SECMIN
* am
);
14405 /* channel offset adjust */
14407 t
->qstat
= T_FUBAR
;
14415 /* timer starts today, but may get corrected later */
14416 t
->start
= utsmid
+as
;
14418 calc_first_weekday(t
); // FIXME
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 */
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 */
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 */
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
);
14449 dvrlog( LOG_INFO
, "%s range error: %s\n", WHO
, s
);
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
14463 find_config ( void )
14466 char n
[256]; /* ridiculously long path */
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 */
14476 /* check for -c config overriding filename or pathname first */
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 */
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
)) )
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 */
14516 advrlog( LOG_INFO
, "-c config is %s%s", cfg_path
, cfg_name
);
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
);
14534 /* still looking for config, try /etc/atscap/ */
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
);
14545 /* anything found? */
14547 advrlog( LOG_INFO
, "config is %s%s", cfg_path
, cfg_name
);
14549 dvrlog( LOG_INFO
, "no config found");
14555 /* return -1 if not found, or spam list index if found */
14558 test_spam_name( char *p
)
14564 if (NULL
== p
) return -1;
14565 if (0 == *p
) return -1;
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
++) {
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
)) {
14586 /* remove spam list entry n, move entries n+1 down one.
14587 n ranges from 0 to (spam_idx-1) */
14590 delete_spam( int n
)
14593 struct spam_s
*s1
, *s2
;
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
) );
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 */
14622 add_spam( char *p
, int st
)
14626 if (SPAM_LIST_MAX
<= spam_idx
) {
14627 dvrlog( LOG_INFO
, "spam list full, increase SPAM_LIST_MAX");
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
);
14637 /* store actual copied count not original length */
14638 s
->len
= strlen( s
->name
);
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 */
14649 load_spamlist( void )
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 */
14659 struct stat fs
; /* stat64 not needed for small file */
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
);
14671 advrlog( LOG_INFO
, "can't stat %s", sf
);
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
);
14692 f
= fopen( sf
, "r" );
14694 /* TODO: log error reason */
14696 dvrlog( LOG_INFO
, "can't open %s", sf
);
14697 pthread_mutex_unlock( &spam_mutex
);
14702 /* reset spam list */
14704 memset( spam_list
, 0, sizeof( spam_list
));
14706 /* EOF is end of list, or MAX is */
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 */
14722 ss
= strchr( si
, ':'); /* point to time */
14724 /* : is [?]optional last seen spam time. if not given, is now. See below. */
14726 *ss
= 0; /* remove : */
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 */
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
);
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?
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
14777 parse_ztime ( char *t
)
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
);
14798 /* has PTC number and not NTP name */
14800 if ((ptch
< 1) || (ptch
> ptc_max
)) return;
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
)
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 */
14823 parse_webcfg( char *t
)
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 */
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
);
14871 remove_tsid( int tx
)
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
++) {
14888 memcpy(t0
, t1
, sizeof(struct tsid_s
));
14892 pthread_mutex_unlock( &tsid_mutex
);
14895 /* sort by ptc then pn */
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
++) {
14912 if (t0
->ptc
> t1
->ptc
)
14914 if (t0
->ptc
== t1
->ptc
)
14915 if (t0
->pn
> t1
->pn
)
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 */
14943 while (EBUSY
== pthread_mutex_trylock( &tsid_mutex
))
14944 nanosleep( &atomic_sleep
, NULL
);
14948 snprintf( n
, sizeof(n
), "%satscap%d.tsid.%s",
14949 cfg_path
, arg_devnum
, (0 == arg_cable
)?"vsb":"qam");
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
++) {
14960 fprintf(o
, "0x%04X:%03d:%02d:%s:%s\n",
14961 t
->tsid
, t
->ptc
, t
->pn
, t
->call
, t
->sid
);
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.
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");
14998 dvrlog( LOG_INFO
, "TSID file %s not opened", n
);
15002 while (EBUSY
== pthread_mutex_trylock( &tsid_mutex
))
15003 nanosleep( &atomic_sleep
, NULL
);
15007 /* i is line counter */
15011 if (tsidx
>= TSID_MAX
) {
15012 dvrlog( LOG_INFO
, "%s too many TSIDs (%d)", tsidx
);
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
);
15026 if (NULL
== r
) break;
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
);
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 */
15098 /* pick which ptc list to use from 6 available */
15101 init_channels( void )
15106 /* force to 0 ATSC 8VSB in case any checks below fail */
15107 /* use 8VSB mode */
15110 /* FUTURE FIXME: by 2009 this will be 52 */
15113 aos_delay
= AOS_VSB_DELAY
;
15115 /* use QAM256 mode */
15116 if (arg_frtable
> 0) {
15119 aos_delay
= AOS_QAM_DELAY
;
15124 switch( arg_frtable
) {
15127 frequencies
= ptc_atsc_center
; /* was ptc_broadcast */
15128 ptc_max
= ATSC_MAX
;
15132 frequencies
= ptc_cable_eia542_hrc
;
15136 frequencies
= ptc_cable_eia542_irc
;
15140 frequencies
= ptc_cable_hrc
;
15144 frequencies
= ptc_cable_irc
;
15148 frequencies
= ptc_cable_std
;
15153 HOM CCE BR
"bad frequency table selection %d\n" BN
,
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
++) {
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;
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
));
15187 parse_freqlist( char * t
)
15191 if (NULL
== t
) return;
15192 if (0 == *t
) return;
15194 dvrlog( LOG_INFO
, "%s (%s)", WHO
, t
);
15198 if (n
>= 6) return;
15200 if (n
> 0) arg_cable
= ~0;
15205 /* show clock tests config file every second, calls this if cfg mod time chg */
15208 load_config ( char *caller
)
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 */
15218 /* creating config, no need to load it */
15219 if ( 0 != arg_scan
) return;
15223 advrlog(LOG_INFO
, "%s start from %s\n", WHO
, caller
);
15228 nanosleep(&msg_sleep
, NULL
); /* 1/4s sleep for file update after mt chg */
15235 /* if ( D_TIMERS == display_type ) */
15236 aprintf( stderr
, "\033[5;1H" CCE
);
15239 for ( i
= 0; i
< scan_idx
; i
++ ) {
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" );
15256 dvrlog( LOG_INFO
, "load config failed %s", fn
);
15257 /* alert user there still was a problem with the config file */
15259 fprintf( stderr
, CLS
"load config %s failed\n", fn
);
15262 /* fatal, enter warn/test mode, if not doing console exit instead
15269 /* clear the timer list */
15272 /* clear the search list */
15273 memset( search_list
, 0, sizeof(search_list
) );
15276 memset( scan_list
, 0, sizeof(scan_list
) );
15278 /* add or remove channels loses the current index */
15283 while (NULL
!= c
) {
15284 memset(tt
, 0, sizeof(tt
));
15285 c
= fgets( tt
, 128, o
); /* c gets NULL at EOF */
15287 advrlog( LOG_INFO
, "%s EOF %s", WHO
, fn
);
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;
15300 if (0 == *tt
) continue;
15302 advrlog( LOG_INFO
, "%s:%d [%s]", WHO
, WHERE
, tt
);
15310 /* there is now an order requirement, C lines before W or V */
15312 /* automatic search event */
15314 parse_search_event( t
);
15317 /* broadcast or cable channel */
15319 parse_channel( t
);
15322 /* last set -F option */
15324 parse_freqlist( t
);
15327 /* user interface format string */
15337 /* volatile timer */
15339 parse_volatile_timer( t
);
15342 /* new mnemonic for Weekday instead of T for Timer */
15343 /* weekday timer */
15345 parse_weekday_timer( t
);
15348 /* ATSC system time channel select */
15353 /* ignore the other lines */
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);
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
);
15375 /* fixup weekday and dst issues, toss overlaps, and sort */
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
);
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 */
15404 /* put it back so nothing gets confused */
15405 cap_chan
= pgm
.chan
= pch
;
15407 /* TESTME: is this why guide for KUHT showing up on KHCW? */
15408 load_guide( pch
, WHO
);
15410 advrlog( LOG_INFO
, "%s stop\n", WHO
);
15413 /* show vc selection calls this to count filtered events on each program */
15416 count_pg_pgm( int pn
)
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 */
15423 for (i
= 0; i
< pgm
.idx
; i
++)
15424 if (pn
== epg
[ pg
[ i
] ].pn
)
15430 /* similar to load epg, but in reverse */
15433 save_epg ( int ch
, struct event_s
*e
, struct pgm_s
*p
, short *pgx
,
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
;
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
);
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");
15468 dvrlog( LOG_INFO
, "%s writing %s error %s",
15469 WHO
, n
, strerror(errno
) );
15473 /* write whole pgx structure, j items */
15474 w
= fwrite( pgx
, sizeof( short ), j
, f
);
15476 dvrlog( LOG_INFO
, "%s error %d wr idx %s", WHO
, w
, n
);
15481 /* term list with short -1 */
15482 w
= fwrite( &m1
, sizeof( short ), 1, f
);
15484 dvrlog( LOG_INFO
, "%s -1 wr %s is %d", WHO
, n
, w
);
15489 for (i
= 0; i
< j
; i
++) {
15492 if (0xFFFF == k
) break;
15497 w
= fwrite( e1
, sizeof(struct event_s
), 1, f
);
15500 dvrlog( LOG_INFO
, "%s error %d wr %s event %d",
15508 /* update callers mtime */
15512 /* similar to load vct, but in reverse:
15513 ncv is number of channls in VCT
15514 ncp is number of channels in PAT
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.
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 */
15529 if (0 != arg_scan
) return;
15531 if (ch
> ptc_max
) { /* boundary check */
15532 dvrlog( LOG_INFO
, "%s %d >= ptc max %d",
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");
15542 dvrlog( LOG_INFO
, "%s writing %s error %s",
15543 WHO
, n
, strerror(errno
) );
15547 /* write vct_ncs and pat_ncs */
15548 w
= fwrite( ncv
, sizeof( int ), 1, f
);
15550 dvrlog( LOG_INFO
,"%s error %d wr vctncs %s",WHO
, w
, n
);
15554 w
= fwrite( ncp
, sizeof( int ), 1, f
);
15556 dvrlog( LOG_INFO
,"%s error %d wr patncs %s",WHO
, w
, n
);
15561 /* write vc and pa */
15562 w
= fwrite( v
, sizeof( vc
), 1, f
); /* FIXME: vc needs abstraction */
15564 dvrlog( LOG_INFO
, "%s error %d wr vc %s",WHO
,w
,n
);
15568 w
= fwrite( p
, sizeof( pa
), 1, f
); /* FIXME: pa needs abstraction */
15570 dvrlog( LOG_INFO
, "%s error %d wr pa %s",WHO
,w
,n
);
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
15585 test_ts_file( char *d
, char *base
, struct event_s
*e1
, struct stat64
*fs
)
15587 int ok
, mn
, dy
, h
, m
, p
;
15591 /* check is name-mmdd-hhmm.pn.ts format */
15592 tany
= (time_t)e1
->st
;
15595 localtime_r( &tany
, &tne
);
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 */
15608 advrlog( LOG_INFO
, "html stat64 %s = %d", d
, ok
);
15612 /* leave these out for now. requires further thought on need or use */
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 */
15618 advrlog( LOG_INFO
, "html stat64 %s = %d", d
, ok
);
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 */
15626 advrlog( LOG_INFO
, "html stat64 %s = %d", d
, ok
);
15637 write_png_error( png_structp png_ptr
, png_const_charp msg
)
15640 dvrlog( LOG_INFO
, "%s", msg
);
15641 img
= png_get_error_ptr( png_ptr
);
15643 dvrlog( LOG_INFO
, "write_png jmpbuf not recoverable!");
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 */
15653 write_png( struct img_s
*img
, char *n
)
15657 png_structp png_ptr
;
15658 png_infop info_ptr
;
15659 png_bytepp row_pointers
;
15661 ok
= ~0; /* nz is not ok */
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");
15681 dvrlog( LOG_INFO
, "can't open %s for writing", n
);
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 */
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
);
15718 advrlog( LOG_INFO
, "wrote %s", n
);
15722 dvrlog( LOG_INFO
, "libpng error %s", n
);
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
);
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.
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 */
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 */
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
;
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
++ ) {
15791 if (k
> SIG_PNG_W
) k
-= SIG_PNG_W
;
15793 /* colorize the sample according to signal strength */
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 */
15810 /* normalize strength/100 to sigbar pixels y / height */
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
;
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?
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
);
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"
15855 build_epg_form( char *d
, int z
, int ch
)
15860 s
= &ptc
[ ch
].sig
;
15862 asnprintf(d
, z
, "\n<!-- %s -->\n", WHO
);
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",
15883 /* is s->pn ever zero? won't display anything if so */
15885 asnprintf(d
, z
, "%s.%d\n", s
->sid
, s
->pn
);
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 */
15921 build_epg_legend ( char *d
, int z
)
15925 "epgon24", "scanon", "spam24", "unspam24", "searchadd", "searchdel",
15926 "timeradd", "timerdel", "zap24", "timerstop", "log24", "edit24"
15930 "EPG", "Sig", "Junk+", "Junk-", "Find+", "Find-",
15931 "Add", "Del", "Zap", "Stop", "Log", "Edit"
15934 char *c0
, *c1
; /* center legend color list */
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 */
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
);
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
);
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
);
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 "
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 */
15999 /* check for following:
16000 spam is on spamlist
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
16016 test_event_status ( struct event_s
*e
)
16019 int ok
, es
; /* event status bits */
16020 char s
[64], t
[64]; /* truncated name */
16021 int d
, c
; /* daybits and channel */
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
;
16031 if (-1 != find_timer_epg(e
)) es
|= E_TIME
;
16033 /* sunday is bit 6 */
16034 d
= 1 << (6 - e
->stm
.tm_wday
);
16037 if (-1 != find_search_event( e
->tname
, d
, c
, WHO
))
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
;
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
);
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 */
16087 /* [timer entry form], station png, status text and sigchart */
16090 build_epg_top ( FILE *wf
, char *d
, int z
, struct pgm_s
*p
)
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 */
16107 /* text mode sizing is default */
16109 if (J_LARGE
== use_css
) ef
= "large";
16110 if (J_SMALL
== use_css
) ef
= "small";
16111 if (J_TINY
== use_css
) ef
= "tiny";
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
);
16131 /* TODO: if regular file ... */
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",
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: ");
16156 if (CAP_NONE
!= cap_now
) {
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
) ) {
16176 s1
= &ptc
[ cap_chan
].sig
;
16178 "<a href=\042/pg/%02d.html\042> %s </a>",
16179 cap_chan
, s1
->sid
);
16183 /* Idle shows last capture fail status for this channel, if not sig scan */
16185 if (0 == scan_sig
) {
16186 if (s
->cap_fail
> 0) {
16187 asnprintf(d
, z
,"%s FAIL", cap_fails
[s
->cap_fail
] );
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");
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
);
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
);
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>",
16236 asnprintf(d
, z
, "<img class=\042%s\042 "
16237 "alt=\042%s%d\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>",
16251 asnprintf(d
, z
, IMGX_FMT
, "EPG", "img/epgon.png", 0);
16252 asnprintf(d
, z
, "</a>\n");
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>",
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");
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
) {
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");
16284 asnprintf(d
, z
, "SNR: %d.%02d dB RMS\n",
16285 0xFF & (s
->snr_rms
>> 8),
16286 ((0xFF & s
->snr_rms
) * 100 ) >> 8 );
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");
16297 asnprintf(d
, z
, "Sig: ");
16298 asnprintf(d
, z
, "%02d%% Avg\n", s
->str_avg
);
16303 if (s
->chan
== cap_chan
)
16304 asnprintf(d
, z
, "@ %d/s", sr
);
16308 asnprintf(d
, z
, "<br />\n");
16310 /* show error rate during capture, or blank line */
16311 if (CAP_NONE
!= cap_now
) {
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");
16321 /* no errors gets a blank line */
16322 asnprintf(d
, z
, "<br />\n");
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 */
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"
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
) )
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
);
16380 for (j
=0; j
< b
; j
++) {
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 */
16395 /* current 30m filler timeframe is indicated by white description */
16398 sr
= (e
->st
+ (e
->ls
- SECDT
)) - utsnow
;
16399 if (sr
< 0) sr
= 0;
16401 mm
= (sr
- (hh
* 3600)) / 60;
16403 snprintf( a
, sizeof(a
), "%02d:%02d:%02d left",
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>",
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
16433 build_epg_timers ( FILE *wf
, char *d
, int z
, char *g
, int ch
)
16436 struct qtimer_s
*t
;
16437 struct tm estm
; /* event start time tm */
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
;
16449 if (1 != use_css
) return; /* only large tiles */
16451 /* count the number of weekday timers */
16453 for (i
=0; i
<timer_idx
; 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;
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
);
16482 for (i
=0; i
< timer_idx
; i
++)
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;
16506 /* not specified or ignore gets red */
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");
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",
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
],
16560 asnprintf(d
, z
, "</dt>\n");
16564 // if (0 != epg_sb) sb = " epgvsb";
16566 asnprintf(d
, z
, "<dd class=\"dd_%s %s%d%s\">\n",
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 ");
16589 asnprintf(d
, z
, " %04X.%04X",
16590 0xFFFF & (t
->etmid
>>16), 0xFFFF & t
->etmid
);
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 */
16602 /* cyan: weekday bits match */
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 */
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
) );
16616 asnprintf(d
, z
, "<img alt=\"%s\""
16619 "0", "wdb0", "img/norec16.png");
16621 asnprintf(d
, z
, "<img alt=\"%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
);
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 */
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 */
16699 /* FIXME: scan conflict wrong epg top */
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;
16710 snprintf( n
, sizeof(n
), "%s%s/pg/%02d.html",
16711 out_path
, ram_path
, ch
);
16714 advrlog( LOG_INFO
, "%s(%s) %d", WHO
, caller
, ch
);
16716 /* refresh is every 30m (or sooner if signal scanning this channel?) */
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;
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
));
16747 /* calc start weekday of year */
16748 wdo
= tloc
.tm_wday
- (tloc
.tm_yday
% 7);
16749 if (wdo
< 0) 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 */
16762 for (j
= 0; j
< pvc
; j
++) {
16763 if (pv
[j
] == e1
->pn
) {
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
] );
16774 if (VCZ
== pvc
) break;
16779 /**************************************** build list of days */
16781 for (i
= 0; i
< p
->idx
; i
++) {
16782 e1
= &ev
[ pgx
[ i
] ];
16783 if (pdn
!= e1
->stm
.tm_yday
) {
16785 /* duplicate check */
16787 for (j
= 0; j
< nde
; j
++) {
16788 if ( nd
[ j
] == e1
->stm
.tm_yday
) {
16793 if (0 == k
) continue;
16795 nd
[ nde
] = e1
->stm
.tm_yday
;
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) ) {
16806 dvrlog( LOG_INFO
, "%s ch %d invalid pvc %d idx %d",
16807 WHO
, p
->chan
, pvc
, p
->idx
);
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
);
16819 advrlog( LOG_INFO
, "EPG%02d has ncs %d, days %d, events %d",
16820 ch
, pvc
, nde
, p
->idx
);
16827 /* refresh is every 30m, or sooner if signal scanning this channel */
16828 if ((0 != scan_one
) && (0 != scan_sig
) && (p
->chan
== cap_chan
))
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 */
16840 build_epg_top(wf
, h
, sizeof(h
), p
);
16843 // mo = p->hide | p->age | p->find;
16847 /* displayed event count */
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) {
16858 if (pvc
> 0) cpn
= pv
[0];
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",
16868 /* center is pgm place-holder */
16870 asnprintf(d
, z
, "<center id=\042pgm%d\042>\n", cpn
);
16873 asnprintf(d
, z
, "<center>\n", cpn
);
16875 asnprintf(d
, z
, "<hr id=\042pgm%dday%d\042 />\n",
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 ");
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) ) {
16895 asnprintf(d
, z
, " | \n");
16896 for (j
= 0; j
< pvc
; j
++) {
16899 /* full cap shows all programs, new logical chan cap shows only this pgm# */
16900 if ( (s
->pn
> 0) && (s
->pn
!= pv
[j
]) )
16903 /* current program does not get href */
16904 if (cpn
== pv
[ j
]) k
= 0;
16907 /* TODO: can the VC also be selected with correct cgi? */
16909 "<a class=\042%s\042 "
16910 "href=\042#pgm%d\042>",
16911 "f4 green", pv
[j
]);
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");
16932 // asnprintf(d, z, "wdo %d, ", wdo);
16934 for (j
= 0; j
< nde
; j
++) {
16939 if (nde
> 2) wdt
= days
;
16943 /* if next year-day number is less than first one, year meridian occurred */
16944 if ( (j
> 0) && (nd
[0] > nd
[j
]) ) {
16946 // if (0 == (e1->stm.tm_year & 3 )) lyo++;
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 */
16956 asnprintf( d
, z
, "<a class=\042%s\042 "
16957 "href=\042?n=%d:%d\042>",
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");
16973 if (0 != *d
) fprintf( wf
, "%s", d
);
16976 /***************** build list of events: p->idx is fewer loops than E*P*V */
16977 for (i
= 0; i
< p
->idx
; i
++) {
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.
16992 if ((s
->pn
> 0) && (cpn
!= s
->pn
))
16995 /* moved EPG days divider here before the further calculations */
16997 /* if year day set, only 1 divider, otherwise dividers for each day */
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) */
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",
17015 /* new program number or day number gets new primetime header */
17020 /* center is pgm place-holder */
17021 asnprintf(d
, z
, "<center id=\042pgm%d\042>\n", cpn
);
17024 /* still needs to be centered */
17025 asnprintf(d
, z
, "<center>\n", cpn
);
17028 asnprintf(d
, z
, "<hr id=\042pgm%dday%d\042 />\n",
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"
17036 " border=\042%d\042></a>\n | \n",
17037 "f4 green", "#top", "TOP", "/pg/img/top24.png", 0);
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 ");
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)) {
17055 asnprintf(d
, z
, " | \n");
17057 for (j
= 0; j
< pvc
; j
++) {
17060 /* full cap shows all programs, new logical chan cap shows only this pgm# */
17062 if ( (s
->pn
> 0) && (s
->pn
!= pv
[j
]) )
17065 /* current program does not get href */
17066 if (cpn
== pv
[ j
]) k
= 0;
17069 "<a class=\042%s\042 "
17070 "href=\042#pgm%d\042>",
17071 "f4 green", pv
[j
]);
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
, " ");
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
;
17094 for (j
= 0; j
< nde
; j
++) {
17099 if (nde
> 1) wdt
= days
;
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
]) ) {
17107 /* leap year starts 2 weekdays later */
17108 // if (0 == (3 & e1->stm.tm_year)) lyo++;
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>",
17124 asnprintf( d
, z
, " %s \n",
17125 wdt
[ (nd
[j
] + wdo
+ lyo
) % 7 ]);
17127 if (0 != k
) asnprintf(d
, z
, "</a>\n" );
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>",
17135 asnprintf( d
, z
, " %s \n",
17136 wdt
[ (nd
[j
] + wdo
+ lyo
) % 7 ]);
17138 if (0 != k
) asnprintf(d
, z
, "</a>\n" );
17144 asnprintf(d
, z
, "</center>\n");
17145 asnprintf(d
, z
, "</div>\n");
17147 /* if ppn pdn diff */
17151 /**************************************************** EPG divider primetime */
17154 /* draw primetime line at first timeslot starting at PRIMETIME HOUR or later */
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? */
17164 /* needs id for primetime in href local jump list */
17165 asnprintf(d
, z
,"<div class=\042%s\042>\n",
17167 asnprintf(d
, z
,"<br class=\042%s\042 />\n",
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> | ",
17181 "Prime Time %s.%d | %s, %s %d %d\n",
17183 daysl
[ e1
->stm
.tm_wday
],
17184 mons
[ e1
->stm
.tm_mon
],
17186 1900 + e1
->stm
.tm_year
);
17188 asnprintf(d
, z
, "</center>\n");
17190 asnprintf(d
, z
, "</div>\n");
17195 /* if divider is not blank, write it */
17196 if (0 != *d
) fprintf(wf
, "%s\n", d
);
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
) )
17205 if ( (s
->pn
> 0) && (e1
->pn
!= s
->pn
) )
17211 /* if not using find & has set day filter & event doesn't match selected day,
17216 if (e1
->stm
.tm_yday
!= s
->yday
)
17221 /*************************************************** build one event */
17223 astrncpy( tn
, e1
->tname
, PNZ
);
17224 astrncpy( en
, e1
->name
, PNZ
);
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);
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);
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);
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.
17295 && (0 == (es & E_NOW)))
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 */
17312 /* es & E_NOW is set during entire event if it's current */
17313 if (0 != (es
& E_NOW
)) ec
= 8; /* white */
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 */
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 */
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
;
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 */
17372 /* want kern truncated description with no scrollbars */
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
)) )
17381 /* scrollbars get the non kern-truncated version */
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 */
17397 if (use_css
> 1) iz
= 16;
17401 snprintf( a
, sizeof(a
), "spam%d.png", iz
);
17404 snprintf( u
, sizeof(u
),
17405 "/pg/%02d.html?j=%s:%u#p%dt%d", ch
, e1
->tname
, 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 */
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
);
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
);
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
);
17442 snprintf(a
, sizeof(a
), "timerdel%d.png", iz
);
17443 if (0 != (es
& E_NOW
)) {
17445 snprintf(a
, sizeof(a
), "timerstop%d.png", iz
);
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
,
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
);
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
,
17470 snprintf( a
, sizeof(a
), "xine%d.png", iz
);
17472 asnprintf(d
, z
, b
, u
, g
, c
, a
);
17475 snprintf( a
, sizeof(a
), "blank%d.png", iz
);
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
);
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
);
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
,
17522 /* unlink only works from index.html */
17523 if (0 != (es
& E_MTX
))
17524 snprintf( u
, sizeof(u
),
17526 "%s-%02d%02d-%02d%02d.%d.ts",
17534 asnprintf(d
, z
, b
, u
, g
, c
, a
);
17538 /* this will only fit if no .ts* buttons */
17539 if (0 == (es
& E_TS
)) {
17541 if (0 != (es
& E_MOVIE
)) {
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
);
17551 if (2 == use_css
) {
17552 // asnprintf(d, z, "%02d%02d %dm",
17553 asnprintf(d
, z
, "%02d:%02d",
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
)) {
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;
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) )
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 */
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 */
17614 asnprintf(d
, z
, "No %s EPG events on %s.%d",
17615 ne
, s
->sid
, s
->pn
);
17617 asnprintf(d
, z
, "No %s EPG events on %s",
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 */
17637 /* xhtml 1.0 transitional validated */
17642 build_foot_html( h
, sizeof(h
), w3cv
); /* validated */
17643 fprintf( wf
, "%s\n", h
);
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
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. */
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 */
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 */
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 */
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; */
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";
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 */
17789 text_time( dt
, (int)fs
.st_mtime
, 20);
17792 /* file does not exist, use now for date time string */
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
);
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" );
17818 dvrlog( LOG_INFO
, "couldn't open output %s", wn
);
17822 /* Tab separated and visual formatted text file output */
17823 #ifdef USE_EXTRA_OUTPUT
17824 gf
= fopen( gn
, "w" );
17826 dvrlog( LOG_INFO
, "couldn't open output %s", wn
);
17829 df
= fopen( dn
, "w" );
17831 dvrlog( LOG_INFO
, "couldn't open output %s", wn
);
17835 /* all three have to open or nothing happens */
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.
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.
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.
17856 Refresh is variable and can be set by the following conditions:
17859 Refresh rate is managed to reduce CPU load during capture.
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.
17870 Refresh is x seconds until next 30m frame expires.
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.
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 */
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 */
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 */
17922 st
/= 1800; /* number of 30m slots */
17923 st
*= 1800; /* this 30m slot */
17924 st
+= 1800; /* next 30m slot */
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
) {
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.
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
);
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 */
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
);
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");
18003 #warning using EPG update
18004 /* see above for refresh setting */
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
);
18017 if (0 != s
->pgto
) {
18018 fprintf( wf
, "Guide expired" );
18020 fprintf( wf
, "Non-auto EPG" );
18025 fprintf( wf
, "<br>\n");
18028 if (0 == s
->pgto
) gt
= "OLD";
18029 if (CAP_NONE
!= cap_now
)
18030 if (p
->chan
== cap_chan
)
18033 /* future mtime for saved or last ctime for live */
18034 fprintf( wf
, "%s %s ", gt
, &dt
[11]); /* created on */
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");
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 */
18091 STATUS: IDLE SCAN SCANALL CAPTURE
18094 fprintf( wf
, "Status: ");
18098 if (CAP_NONE
!= cap_now
) {
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
);
18121 /* Idle shows last capture fail status for this channel, if not sig scan */
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
);
18132 fprintf( wf
, "<br>\n");
18134 /* show last sig avg */
18137 if (0 == scan_sig
) sr
= 0;
18138 if (CAP_NONE
!= cap_now
) sr
= 1;
18141 fprintf( wf
, "SNR: ");
18143 fprintf( wf
, "%d.%02d RMS\n", 0xFF & (s
->snr_rms
>> 8),
18144 0xFF & ((s
->snr_rms
* 100) >> 8) );
18146 fprintf( wf
, "Sig: ");
18148 fprintf( wf
, "%02d%% Avg\n", s
->str_avg
);
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
) {
18160 erate
= 100.0 * (double) pkt
.errors
/ (double) pkt
.count
;
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");
18175 /* no errors gets a blank line */
18176 fprintf( wf
, "<br>\n");
18179 fprintf( wf
, "<br>\n");
18183 /* other buttons may be variable, put them to right of signal graph */
18185 /* if (CAP_NONE != cap_now) */ /* no, show the signal graph always */
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");
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>",
18202 fprintf( wf
, IMG_FMT
,
18203 "EPG", "img/epgon.png", 48, 48, 4, 4, 0);
18204 fprintf( wf
, "</a>\n");
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>",
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 */
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");
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"); */
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>",
18255 fprintf( wf
, IMG_FMT
,
18256 "MANUAL", "img/capall.png", 48, 48, 4, 4, 0);
18257 fprintf( wf
, "</a>\n");
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>",
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>",
18275 fprintf( wf
, IMG_FMT
,
18276 "CAPVCX", "img/capvc0.png", 48, 48, 4, 4, 0);
18277 fprintf( wf
, "</a>\n");
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>",
18285 fprintf( wf
, IMG_FMT
,
18286 "STOP", "img/capoff.png", 48, 48, 4, 4, 0);
18287 fprintf( wf
, "</a>\n");
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");
18299 for (i
= 0; i
< SGML_FMT
; i
++ ) {
18301 if (use_css
!= i
) j
= 1;
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");
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 */
18322 for (i
= 0; i
< PGZ
; i
++){
18324 if (0 == *e1
->name
) continue; /* skip blanks */
18325 if ( (utsnow
>= e1
->st
) && (utsnow
< (e1
->st
+ e1
->ls
)) ) {
18331 /* show current event time status too
18332 denoted as mm:ssr for remaining
18339 localtime_r( &utr
, &e1tm
);
18342 utr
-= utsnow
; /* seconds remaining */
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? */
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
) {
18362 description same as name is considered blank description
18364 if (0 != strncasecmp( e1
->name
, e1
->desc
, strlen(e1
->desc
)) )
18367 astrncpy( ed
, e1
->desc
, PDZ
);
18369 fprintf( wf
, "<%s %s %s>%s </%s>\n",
18370 f
, h2
, c7
, ed
, f
);
18372 } /* description blank doesn't generate html */
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"); */
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 */
18515 /* if no epg available, create this as stub page for user to try later */
18517 if (NULL
!= gf
) { fflush( gf
); fclose( gf
); }
18518 if (NULL
!= df
) { fflush( df
); fclose( df
); }
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");
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 */
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
] );
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
;
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) ) {
18582 dvrlog( LOG_INFO
, "%s ch %d invalid pvc %d idx %d",
18583 WHO
, p
->chan
, pvc
, p
->idx
);
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
);
18593 advrlog( LOG_INFO
, "%s ch %d pgms %d days %d",
18594 WHO
, p
->chan
, pvc
, nde
);
18599 /* use long day names if guide has less than one week of data */
18610 /* want 16 days and 2 pgms to fit in 960 pixel wide browser window */
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):
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 */
18659 char *et
= ""; /* event status text */
18660 char *jf
= ""; /* spam add or del png */
18661 char *efz
= ""; /* eventname font size */
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
);
18675 ename
= ""; /* full eventname ptr */
18676 edesc
= ""; /* full event desc ptr */
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 */
18698 || (e1
->pn
> 65534)
18700 advrlog( LOG_INFO
, "%s %s is invalid", WHO
, n
);
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
) {
18719 advrlog( LOG_INFO
, "%s %s is spam", WHO
, n
);
18720 continue; /* skip spam */
18724 /* this should never display if build pg epg worked */
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 */
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 */
18759 localtime_r( &st
, &stm
);
18760 asctime_r( &stm
, dt
);
18765 /* strftime( w, sizeof(w)-1, "%A", &stm); */
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 */
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>",
18786 /* fat space after primetime href */
18787 fprintf( wf
, "<a href=\042#pgm%dpt%d\042>PT %s</a>",
18790 /* show virtual channel sid + minor program nav hrefs if more than one */
18792 fprintf( wf
, "| %s ", NBSP
);
18793 for (j
= 0; j
< pvc
; j
++) {
18796 /* current program does not get href */
18797 if (pn
== pv
[ j
]) a
= 0;
18799 fprintf( wf
, "<a href=\042#pgm%d\042>", pv
[j
]);
18801 fprintf( wf
, "%s.%d ", s
->sid
, pv
[j
] );
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
++) {
18814 /* current day does not get href */
18815 if (cdn
== nd
[j
]) a
= 0;
18818 fprintf( wf
, "<a href=\042#pgm%dday%d\042>",
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) {
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");
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 */
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 */
18868 if ( 0 == strncasecmp( ename
, edesc
, strlen(edesc
)) )
18871 /* if desc not blank, add it to text and database files */
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
);
18893 fprintf( df
, "%s", t
); /* sql table */
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 */
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
);
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 */
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
);
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
);
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 */
18954 fprintf( wf
, "<img "
18957 "align=\042%s\042 "
18962 wh
, en
, "left", 160, 90, 8, 8);
18963 if (E_TS
& es
) fprintf( wf
, "</a>\n");
18966 /* removed line break after event name
18967 fprintf( wf, "<br>\n");
18970 if (E_SPAM
& es
) ec
= c4
; /* spam gets time as low light */
18971 if (E_TS
& es
) ec
= c3
; /* yellow for captures */
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 */
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
,
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",
19002 wds
[ stm
.tm_wday
], stm
.tm_hour
, stm
.tm_min
,
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 */
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 */
19021 fprintf( gf
, "\n%d %s %4dm | %s", pn
, dt
, e1
->ls
/60, n
);
19024 /* give small description of status */
19039 if ( (E_AGED
| E_SPAM
) == ((E_AGED
| E_SPAM
) & es
) )
19051 if ((E_SPAM
| E_NOW
) == ((E_SPAM
| E_AGED
| E_NOW
) & es
))
19058 /* icon and aged is enough? */
19059 if (E_MOVIE
& es
) {
19064 if ((E_MOVIE
| E_AGED
) == ((E_SPAM
| E_AGED
| E_NOW
| E_MOVIE
) & es
))
19075 fprintf( wf
, "<%s %s %s>%s </font>\n",
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";
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 */
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 */
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
)) {
19126 fprintf( wf
, "<a href=\042%02d.html?d=%s\042>",
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 */
19137 "<a href=\042%02d.html?d=%s\042>",
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>",
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 */
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 */
19165 /* FIXME: same ip/port concerns as above */
19166 fprintf( wf
, "<a href=\042%02d.html?a=%s\042>",
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>",
19180 fprintf( wf
, IMG_FMT
, "ZAP", "img/zap24.png", iz
, iz
, 4, 4, 0);
19181 fprintf( wf
, "</a>\n");
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? */
19188 /* mod time expired? */
19190 /* sometimes you want to delete a near current cap */
19191 if ((E_AGED
& es
) || (CAP_NONE
== cap_now
))
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");
19206 /* secondary media player icon, not done but is very easy to do */
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");
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 */
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
) {
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" );
19269 build_foot_html( h
, sizeof(h
), 1); /* validated */
19270 fprintf( wf
, "%s", h
);
19277 #ifdef USE_EXTRA_OUTPUT
19284 if (NULL
!= gf
) fprintf( gf
, "\n");
19291 advrlog( LOG_INFO
, "%s done", WHO
);
19296 dump_epg_html ( struct event_s
*e
, struct pgm_s
*p
, short *pgx
, char *caller
)
19299 build_pg_epg( e
, p
, pgx
, WHO
);
19302 if (p
->idx
>= PGZ
) {
19303 dvrlog( LOG_INFO
, "%s chan %02d has bad event index %d",
19304 WHO
, p
->chan
, p
->idx
);
19308 if (0 == use_css
) {
19309 dump_epg_html3( e
, p
, pgx
, caller
);
19311 dump_epg_html4( e
, p
, pgx
, caller
);
19316 /* dump 3 hour grid starting at epg_gs */
19318 dump_epg_grid_html4( void )
19321 struct event_s
*e1
;
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
;
19331 d
= icalloc( z
, 1, "epg grid4" );
19333 epg4
= imalloc( sizeof( epg
), "http epg4");
19335 /* 3 hour meridian */
19336 un
= utsnow
/ SEC3HR
;
19339 /* 1 hour meridian */
19340 un1
= utsnow
/ SECHR
;
19347 /* epg_gs not set or trying to go back before the current EPG meridian */
19348 if (r1
< un
) r1
= un
;
19351 r2
= (r1
+ SEC3HR
) - 1;
19353 r
= 1800 - (utsnow
% 1800);
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
);
19364 asnprintf(d
, z
, "<a href=\042/pg/grid%d.html?i=%d\042>",
19365 arg_devnum
, r1
- 10800 );
19366 asnprintf(d
, z
, "%s", "<<" );
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", "<" );
19372 asnprintf(d
, z
, "</a>\n");
19376 text_time( n
, r1
, 16 );
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",
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", ">" );
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", ">>" );
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");
19424 asnprintf(d
, z
, "<th class=\042%s\042 width=\042%s\042>SID\n",
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>",
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
++) {
19445 memset(&pgm4
, 0, sizeof(pgm4
));
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
)
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",
19469 asnprintf(d
, z
, "<tr valign=\042%s\042>\n", "baseline");
19472 asnprintf(d
, z
, "<th class=\042%s\042 width=\042%s\042>",
19475 asnprintf(d
, z
, "<a href=\042/pg/%02d.html\042>%s\n",
19478 asnprintf(d
, z
, "</a>\n");
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;
19493 if (e1
->st
< r1
) r
= (e1
->st
+ e1
->ls
) - r1
;
19495 // if (r > m) continue;
19497 // w = (100 * r) / m;
19501 /* skip expired events */
19502 if (0 == w
) continue;
19503 if (w
< 6) w
= 6; /* 10m is smallest division */
19507 es
= test_event_status( e1
);
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 */
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
);
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
);
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");
19558 asnprintf(d
, z
, "<td class=\042%s\042 width=\042%d%%\042>",
19560 asnprintf(d
, z
, "n/a\n");
19564 "<td class=\042%s\042 width=\042%d%%\042>",
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
);
19593 ifree(d
, "epg grid4");
19596 /* dump 3 hour grid starting at epg_gs */
19598 dump_epg_grid_html3( void )
19601 struct event_s
*e1
;
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
;
19611 d
= icalloc( z
, 1, "epg grid3" );
19613 epg4
= imalloc( sizeof( epg
), "http epg4");
19615 un
= utsnow
/ SEC3HR
;
19618 un1
= utsnow
/ SECHR
;
19625 /* epg_gs not set or trying to go back before the current hour */
19626 if (r1
< un
) r1
= un
;
19629 r2
= (r1
+ SEC3HR
) - 1;
19631 r
= 1800 - (utsnow
% 1800);
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",
19640 asnprintf(d
, z
, "<tr>\n");
19642 asnprintf(d
, z
, "<th width=\042%s\042>", SIDZ
);
19645 asnprintf(d
, z
, "<a href=\042/pg/grid%d.html?i=%d\042>",
19646 arg_devnum
, r1
- 3600 );
19647 asnprintf(d
, z
, "%s", "<" );
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", "<<" );
19653 asnprintf(d
, z
, "</a>\n");
19657 text_time(n
, r1
, 16);
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",
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", ">" );
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", ">>" );
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",
19698 asnprintf(d
, z
, "<tr>\n");
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
++) {
19720 memset(&pgm4
, 0, sizeof(pgm4
));
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
)
19738 asnprintf(d
, z
, "<table"
19740 " cellspacing=0 cellpadding=3"
19741 " width=\042%s\042>\n",
19744 asnprintf(d
, z
, "<tr valign=\042%s\042>\n", "top");
19747 asnprintf(d
, z
, "<th width=\042%s\042>", SIDZ
);
19749 asnprintf(d
, z
, "<a href=\042/pg/%02d.html\042>%s</a>\n",
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;
19765 if (e1
->st
< r1
) r
= (e1
->st
+ e1
->ls
) - r1
;
19767 if (r
> m
) continue;
19769 // w = (100 * r) / m;
19773 /* skip expired events */
19774 if (0 == w
) continue;
19775 if (w
< 6) w
= 6; /* 10m is smallest division */
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 */
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
);
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
);
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>",
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");
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);
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
);
19865 ifree(d
, "epg grid3");
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();
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 */
19892 test_config ( int force
, char *caller
)
19894 char t
[256]; /* test filename */
19898 if (0 != arg_scan
) return;
19900 /* TESTME: other optimizations have made this obsolete? */
19901 if ((0 != utstcf
) && (utstcf
> utsnow
)) return;
19902 utstcf
= utsnow
+ 1;
19905 advrlog( LOG_INFO
, "%s(%s)", WHO
, caller
);
19907 /* does nothing but eat stack cycles if atscap.spam mtime does not change */
19911 http_load_allows();
19914 /* is now part of config with auto-scheduling search events */
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! */
19932 /* 0 if exists, or -1 error (errno set) */
19933 ok
= stat( t
, &st
);
19936 /* FIXME? clear cf_no every time the file is found? */
19939 /* return if no force load and config file modify time unchanged */
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.
19966 show_volume_status ( void )
19969 long long tb
= 0LL;
19970 long long tt
= 0LL;
19971 long long ts
= 0LL;
19972 long long fg
= 0LL;
19976 if (0 == refresh
.volstats
) return;
19978 refresh
.volstats
= 0;
19982 /* green = more than 3 hours free space left */
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 */
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)"
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();
20035 if open and idle and past power down time, close the dvb device
20039 test_powerdown( void )
20044 /* dont power down during scan */
20047 /* no cap and test epgs is idle too? */
20048 && (CAP_NONE
== cap_now
)
20051 /* if no channel scan: irritating. trying only if no sig scan at all */
20052 // && (D_CHANNELS != display_type)
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");
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 */
20087 show_clock ( void )
20092 /* clock/timers update 1 per second */
20093 if ((0 == refresh
.clock
) && (ptnow
== tnow
)) return;
20100 if (0 != tloc
.tm_isdst
) tc
= BY
;
20101 aprintf( stderr
, SCI
"%s%s%s", dca
.wstat
, tc
, date_now
);
20103 #ifdef USE_POWERDOWN
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.
20119 show_FE_status ( void )
20124 s
= &ptc
[ cap_chan
].sig
;
20126 memset(u
, ' ', sizeof(u
));
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 */
20144 find_vc_src ( unsigned int src
)
20147 for (i
=0; i
< vc_ncs
; i
++)
20148 if ( src
== vc
[i
].src
)
20155 /* not needed yet */
20157 /* return -1 if no matching timer etmid found, or 0-n for timer index */
20160 find_timer_etmid ( unsigned int etmid
)
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
;
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 */
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
;
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 */
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. */
20204 save_guide ( void )
20206 char n
[256], t
[32]; /* file name */
20209 struct utimbuf gt
; /* guide file time stamp */
20212 if (0 != test_mode
) return;
20213 if (0 != arg_scan
) return;
20216 s
= &ptc
[ pgm
.chan
].sig
;
20217 memset( >
, 0, sizeof(struct utimbuf
) ); /* clear all times */
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 */
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
);
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();
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.
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
, >
);
20274 advrlog( LOG_INFO
, "EPG%02d expires %d events on %s",
20275 pgm
.chan
, pgm1
.idx
, &t
[4] );
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.
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 */
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 */
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 */
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 */
20351 /* five seconds per line for error detail, reuse etm */
20353 if (etm
< 1) etm
= 1; /* at least one line */
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 */
20363 dvrlog( LOG_INFO
, "%s failed caplog calloc", WHO
);
20364 pthread_mutex_unlock( &caplog_mutex
);
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.
20376 if (0 == cap_done
) r
= 15;
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 */
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. */
20396 /* input fifo error, error in loop */
20401 /* output fifo error, error in loop */
20406 /* -z QoS bad threshold reached. is same as FAIL_QOS now? */
20411 /* show cap stat triggers this */
20416 /* not sure how to trigger this */
20418 ft
= "VFE FAIL"; /* volume full error */
20421 /* ts capture loop triggers this */
20426 /* what did i forget? */
20428 ft
= "???"; /* unhandled */
20432 /* past tense if file was zapped, but then no reason to log? */
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
);
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;
20490 /* tloc3 is start cap timeval */
20491 s1
+= tloc3
.tm_sec
;
20497 m1
+= tloc3
.tm_min
;
20503 h1
+= tloc3
.tm_hour
;
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
) {
20512 c
= "cltc0"; // cap log text color 0
20514 for (j
= 0; j
< 60; j
++) {
20516 if ( ets
<= k
) break;
20517 if ( k
>= CAP_STZ
) break;
20520 /* average errors */
20521 t
/= j
; /* /60 assumes whole minute */
20522 if (t
> 10) c
= "cltc1";
20523 if (t
> 30) c
= "cltc2";
20526 // if (t > 1000) c = "cltc1";
20527 // if (t > 2000) c = "cltc2";
20528 asnprintf(s
, z
, "<div class=\042cltf0 %s\042>", c
);
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
++) {
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
];
20547 if ((t
> 0) && (t
< 62)) { /* 1-9 a-z A-Z, ASCII */
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 */
20564 if (t
< 36 ) { /* 36 is start of A-Z bads */
20565 sg
++; /* seconds good */
20567 sb
++; /* seconds bad */
20570 asnprintf( s
, z
, "%c", t1
);
20573 #ifdef USE_CSS_CAPLOG
20574 if (0 != use_css
) asnprintf(s
,z
, "</div>");
20577 asnprintf( s
, z
, "\n");
20581 if (ets
< 1) ets
= 1;
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",
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 */
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
) {
20632 if (cap_vc
>= 0) { /* pmt load sets this */
20633 t
= find_tsidx( cap_tsid
, cap_pn
, WHO
);
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;
20660 i
= sequence_idx
/ ets
;
20661 r
= sequence_idx
% ets
;
20663 asnprintf( s
, z
, " Types I %d (%d), ", sequence_idx
, i
);
20668 asnprintf( s
, z
, "P %d (%d), ", pict
[2], 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
++) {
20680 ppc
= pkt
.count
/100;
20681 if (ppc
< 1) ppc
= 1; /* avoid /0s */
20682 if (ets
< 1) ets
= 1;
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) {
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");
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
)) {
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");
20725 /* MPEG2 packet summary */
20726 asnprintf( s
, z
, "\nMPEG2 packets %d:\n", pk
);
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
);
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 "
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 "
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
,
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");
20779 for (i
= 0; i
< ets
; i
++) {
20780 if (i
>= CAP_STZ
) break;
20781 if (0 != cap_stats
[ i
] ) {
20783 /* 5 entries per line */
20786 asnprintf( s
, z
, "%02d:%02d:%02d %-5d",
20793 asnprintf( s
, z
, "\n");
20795 asnprintf( s
, z
, " ");
20801 asnprintf( s
, z
, "</pre>\n");
20805 asnprintf( s
, z
, "<p>\n");
20807 /* turn off the big font if CSS. is OK in HTML3 */
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");
20825 fprintf( f
, "%s", s
);
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.
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 */
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 */
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 */
20879 advrlog( LOG_INFO
, "APG%02d found %3d EITs %3d ETTs in %ds",
20880 cap_chan
, pkt
.eit
, pkt
.ett
, utsets
);
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
20895 show_cap_status ( void )
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 */
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 */
20910 char *qtc
, *qc
; /* qos/t colors */
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
);
20925 refresh
.pstats
= 1; /* so stats will update during cap */
20929 if (tnow == ptnow) return;
20932 if ( utscap
== 0 ) return; /* hold down */
20935 /* filename shortcut w/o path or extension */
20936 filebase( n
, out_name
, F_TFILE
);
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;
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 */
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) ) {
20972 snprintf( qfc
, sizeof(qfc
)-1,
20973 "timer qos fail ets %d qost %lld",
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 */
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 */
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
);
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
))
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) {
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];
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
);
21080 if (0 != test_mode
) {
21081 snprintf( d
, sizeof(d
)-1, "%s", f
);
21087 /* show current capture file */
21088 aprintf(stderr
, DCA0
"%s%-32s", tnd
, d
);
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
);
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
) );
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
;
21137 /* field align test toggles values to max and min */
21139 lltoasc( pe
, 999999999 );
21140 lltoasc( pt
, 999999999 );
21141 lltoasc( pk
, 999999999 );
21149 /* display rate limiter */
21153 /* 9 digs pkt/err total exceeding 2 hours (46 million pkts/hr) */
21155 "%s%3s %3d " /* net and ptc */
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 */
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;
21194 /* these reset after displaying current value */
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.
21207 calc_secdt ( void )
21209 long long file_size
;
21210 long long file_time
;
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
);
21231 advrlog( LOG_INFO
,"couldn't stat %s for delete", del_name
);
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 */
21241 "calc secdt computed at %lld seconds", file_time);
21243 timer
[0].secdt
= 5 + file_time
;
21249 timer
[0].secdt
= 5;
21252 /* program guide flags, bottom middle */
21255 show_event_mask( void )
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 */
21271 show_event_short ( void )
21275 char nvc
[16]; /* network or network and vc program # */
21279 /* TESTME: want it to update during capture or only info cap? */
21280 if ( (0 == refresh
.estats
) && (CAP_NONE
== cap_now
) )
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
);
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
);
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 */
21320 snprintf( pgn
, sizeof(pgn
)-1, "No Guide for %s", nvc
);
21324 snprintf( pgn
, sizeof(pgn
)-1, "Old Guide for %s", nvc
);
21328 snprintf( pgn
, sizeof(pgn
)-1, "Empty Guide for %s", nvc
);
21332 snprintf( pgn
, sizeof(pgn
)-1, "Blank Guide for %s", nvc
);
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 */
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
21355 show_atsc_stats ( void )
21358 /* if (display_stat == 0) return; */
21359 if (CAP_NONE
== cap_now
) return; /* data valid during cap only */
21360 aprintf( stderr
, "%s", dca
.astat
);
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 /***********************************/
21369 if (0 != pkt
.fpat
) {
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 /***********************************/
21382 if (0 != pkt
.pmt
) {
21385 if (0 != pkt
.pmtrc
) s
= BY
;
21387 aprintf( stderr
, "%s%s", s
, t
);
21388 /***********************************/
21392 if (0 != pkt
.fvct
) {
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 /***********************************/
21404 if (0 != pkt
.fmgt
) {
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 /***********************************/
21416 if (0 != pkt
.feit
) {
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 /***********************************/
21428 if (0 != pkt
.fett
) {
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 /***********************************/
21440 if (0 != pkt
.frrt
) {
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 */
21455 if (0 != udp_mcast
) {
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
) {
21472 if (0 != astt
.dss
) s
= BY
; /* ATSC daylight savings in yellow */
21476 asctime_r( &atsc_stt_tm
, d
);
21478 /* don't need year and time zone */
21481 snprintf( &o
[1], sizeof(o
)-1, "%u ", abs(atsc_stt
- utsnow
) );
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
);
21493 o
[3] = ( o
[0] == '+') ? 'H':'L';
21494 s
= BR
; /* red means time is offscale */
21497 if (0 == (atsc_stt
- utsnow
)) {
21505 aprintf( stderr
, "%sZ %s %-3s", s
, d
, o
);
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 */
21518 show_status ( int wait
, char *caller
)
21522 /* clock/timers update 1 per second */
21523 if ( (0 != wait
) && (ptnow
== tnow
) ) return;
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
) ) {
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... ");
21592 aprintf( stderr
, "NO DEVICE " BN
" " );
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 */
21606 show_uptime ( void )
21608 int up
, d
, h
, m
, s
, f
, a
, i
;
21612 ss
= &ptc
[ cap_chan
].sig
;
21614 up
= utsnow
- utc_up
;
21623 f
= open( "/etc/hosts", O_RDONLY
);
21627 dvrlog( LOG_INFO
, "%s file counter needs /etc/hosts", WHO
);
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
);
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
21667 show_keyhelp ( char *keyhelptext
)
21670 int old_stat
, old_type
;
21673 /* save pkt stats display status */
21675 old_stat
= display_stat
;
21676 old_type
= display_type
;
21678 display_type
= D_HELP
;
21683 aprintf( stderr
, DCA_HELP CCE
);
21686 aprintf( stderr
, BN
"%s", keyhelptext
);
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
);
21701 /* m key shows memory usage details, or refreshes it */
21703 if ('m' == k
) m
= ~m
;
21706 aprintf( stderr
, DCA_HELP CCE
"\n" );
21710 /* any key but m will get out */
21711 if ('m' != k
) if (0 != k
) break;
21713 /* reset by timer activity instead */
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); */
21733 /* sort copy of master guide table list by table type */
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
++) {
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 */
21762 add_mgt_text ( char *s
)
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
);
21777 /* show mgt table, has list of pids, like VCT, EIT & ETT pids */
21780 build_mgt_text ( void )
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 */
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 */
21798 /* (non compliant missing ETT0 KTMD fix) */
21799 one_eit
= one_ett
= one_rrt
= ~0;
21801 memcpy( mgs
, mg
, sizeof(mgs
) );
21804 memset( s
, 0, sizeof(s
));
21805 memset( tx
, 0, sizeof(tx
));
21806 memset( mgt_text
, 0, sizeof( mgt_text
));
21809 /* hold down until version not 0xFF, i.e. a live MGT */
21810 if (mgt_vn
== 0xFF) {
21811 display_type
= D_TIMERS
;
21815 snprintf( s
, sizeof(s
)-1,
21816 BW
"MGT Version %02X DLen %02X" BN
, mgt_vn
, mgt_dl
);
21820 snprintf( s
, sizeof(s
)-1,
21821 BM
"Next update is scheduled for %s", date_next
);
21825 /* reset number of bytes total for tables
21826 (does not take into account multi-EIT/ETT)
21831 for (i
= 0; i
< mg_idx
; i
++) {
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 */
21845 /* parsed data from last table of each type */
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 */
21861 add_mgt_text( BY
" Terrestrial Virtual Channel0");
21863 snprintf( tx
, sizeof(tx
)-1, "TVCTC0");
21866 add_mgt_text( BY
" Terrestrial Virtual Channel1");
21868 snprintf( tx
, sizeof(tx
)-1, "TVCTC1");
21871 add_mgt_text( BY
" Cable Virtual Channel0");
21873 snprintf( tx
, sizeof(tx
)-1, "CVCTC0");
21876 add_mgt_text( BY
" Cable Virtual Channel1");
21878 snprintf( tx
, sizeof(tx
)-1, "CVCTC1");
21881 add_mgt_text( BN
" PTC Extended Text");
21883 snprintf( tx
, sizeof(tx
)-1, "PTCET ");
21886 /* have never seen this implemented in broadcast from 2004 through 2007 */
21888 add_mgt_text( BM
" Directed Channel Change Selection Code Table:");
21890 snprintf( tx
, sizeof(tx
)-1, "DCCSCT");
21893 /* single entry tables do not need to loop on multiple values */
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
; }
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
);
21916 snprintf( s
, sizeof(s
)-1,
21917 "%s %s%c Table ID %04X PID %04X NB %s%04X%s"
21919 tc
, tx
, po
, tt
, pid
, ze
, nb
, tc
,
21928 if ( (tt
>= 0x100) && (tt
< 0x17F) ) {
21931 if (0 != one_eit
) add_mgt_text( BG
" Event Information Tables:");
21934 /* FIXME: make decision: * is payok = 0 color is PAY_GOOD or vice versa */
21935 /* EIT is unmarked pending when payok clear */
21937 if (eit
[0x7F & tt
].payok
== 0) {
21942 if (CAP_NONE
== cap_now
) {
21944 if (0xFF != mgs
[i
].vn
) {
21950 snprintf( tx
, sizeof(tx
)-1, "EIT-%02X%c", 0x7F & tt
, po
);
21954 if ( (tt
>= 0x200) && (tt
<= 0x27F) ) {
21957 if ( 0 != one_ett
) add_mgt_text( BC
" Extended Text Tables:");
21960 /* ETT is unmarked pending when both EIT and ETT payok clear */
21962 if ( ett
[ 0x7F & tt
].payok
== 0) {
21963 if ( eit
[ 0x7F & tt
].payok
== 0) {
21970 if (CAP_NONE
== cap_now
) {
21972 if (0xFF != mgs
[i
].vn
) {
21978 snprintf( tx
, sizeof(tx
)-1, "ETT-%02X%c", 0x7F & tt
, po
);
21982 if ( (tt
>= 0x301) && (tt
<= 0x3FF) ) {
21985 if ( 0 != one_rrt
) add_mgt_text( BM
" Rating Region Tables:");
21988 /* RRT is unmarked pending when payok clear */
21989 if (rrt
.payok
== 0) {
21994 if (CAP_NONE
== cap_now
) {
21995 if (0xFF != mgs
[i
].vn
) {
22001 snprintf( tx
, sizeof(tx
)-1, "RRT-%02X%c", 0xFF & tt
, po
);
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:");
22010 snprintf( tx
, sizeof(tx
)-1, "DCCT-%02X", 0xFF & tt
);
22015 if (tt
> 0x2000) return; /* sanity check */
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) ) {
22042 sprintf( p
, " Hours %3d", 3 * (tt
- 0xFF));
22047 snprintf( s
, sizeof(s
)-1,
22048 BW
" Master Guide Table: MGT bytes %08X RX bytes %08X", nbt
, nbt1
);
22051 add_mgt_text( BR BL
"No EIT in MGT, Program Guide is Disabled" BN
);
22057 /* Shows Master Guide Table to see what is available with ATSC PSIP */
22060 show_master_guide_table ( void )
22064 int mo
; /* mgt list offset */
22066 char *tt
, pgm_id
[64];
22069 vn
= mgt_vn
; /* vn when guide started */
22074 /* nothing to do */
22075 if (pkt
.fmgt
== 0) {
22076 dvrlog( LOG_INFO
, "no master guide to show, pkt.fmgt 0");
22081 if (mgt_vn
!= 0xFF) {
22082 dvrlog( LOG_INFO
, "show master guide calls build mgt text");
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;
22099 if (0 != refresh
.mgt
) {
22100 if (CAP_NONE
== cap_now
) {
22101 tt
= "ATSC MASTER GUIDE TABLE";
22102 tp
= columns
- strlen(tt
);
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
);
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] );
22127 /* end of list redraws header and put cursor where needs to be */
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
22140 build_mgt_text(); /* might eat cpu */
22145 if (refresh
.mgt
< 0) refresh
.mgt
= 0;
22147 console_getch_fn( &key
);
22148 nanosleep( &console_read_sleep
, NULL
);
22149 if (0 == key
) continue;
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 */
22178 add_tvct_text ( char *s
)
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
);
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
22202 build_vct_mss ( unsigned char *r
, char *t
)
22204 unsigned int nstr
, nseg
, comp
, mode
, nchr
;
22206 char d
[512]; /* text temp destination */
22210 t
[0] = 0; /* make it blank at the least */
22211 for (i
= 0; i
< nstr
; i
++) {
22212 memcpy(lang
, &r
[1], 3);
22216 for (j
= 0; j
< nseg
; j
++ ) {
22222 /* no compression and unicode page 0 */
22223 if ( (0 == comp
) && (0 == mode
) ) {
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
);
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.
22263 build_mpeg2_descriptor ( unsigned char *r
, unsigned int indent
)
22270 unsigned char n
, l
;
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 );
22289 r
+= 2; /* descriptor data starts here */
22291 /********************************************************** MPEG descriptors */
22295 t
= "Video Stream";
22297 unsigned char mfr
, frc
, m1o
, cpf
, spf
;
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 */
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";
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 */
22344 /* check reserved bits */
22345 if (0x1F == (0x1F & r
[2])) {
22346 unsigned char pli
, cf
, fre
;
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",
22354 add_tvct_text( s
);
22361 /* MPEG audio not seen in ATSC, see case 0x81 for A/52 audio instead */
22363 t
= "MPEG2 Audio Stream";
22366 /* not seen in ATSC */
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
);
22381 /* more or less where you can cut. usually alignment type 1 */
22383 t
= "Stream Alignment";
22386 unsigned char at
= 0;
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
);
22401 /* not seen in ATSC, but sure could be useful */
22403 t
= "Target Background Grid";
22404 break; /* write me if ever seen */
22406 /* not seen in ATSC, but sure could be useful */
22408 t
= "Video Window";
22409 break; /* write me if ever seen */
22411 /* not seen in ATSC ? */
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" );
22429 t
= "ISO 639 Language(s)";
22431 /* no reserved bits to check */
22433 unsigned char at
; /* audio type */
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 );
22446 /* last byte is audio type */
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
);
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",
22473 (0==ecri
)?"NO":"YES",
22475 add_tvct_text( s
);
22481 t
= "Multiplex Buffer Utilization";
22484 unsigned short ltwl
, ltwu
;
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 */
22502 snprintf( s
, sizeof(s
)-1,
22503 "%s Legal Time Window Lower Bound %u Upper Bound %u",
22505 add_tvct_text( s
);
22513 snprintf(s
, sizeof(s
)-1,"%s%sTag %02X DLen %02X %s",
22515 add_tvct_text( s
);
22520 t
= "Maximum Bitrate";
22521 if ( 0xC0 == (0xC0 & r
[0]) ) {
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
);
22534 /* private data, not in 13818-1 draft 1994 jun 10, in nov 13 */
22537 unsigned char pd
[16];
22538 t
= "Private Data";
22539 if (0 != l
) memcpy( pd
, r
, l
);
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
);
22548 /* smoothing buffer, not in 13818-1 draft 1994 jun 10, in nov 13 */
22550 t
= "Smoothing Buffer";
22552 unsigned int sblr
, sbz
;
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
);
22570 t
= "System Target Decoder";
22574 t
= "I P B Frame Layout";
22577 /************************************************************ ATSC specific */
22579 t
= "SCTE-35 Cue ID";
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);
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";
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 */
22611 if (0x20 == (0x20 & brc
)) brt1
= "Maximum";
22612 brc
&= 0x1F; /* limit it now brt1 has been extracted */
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 */
22637 if (0 == sm
) smt
= "Not Indicated";
22638 if (1 == sm
) smt
= "No Dolby Surround";
22639 if (2 == sm
) smt
= "Dolby Surround";
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";
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
);
22686 t
= "Caption Service";
22687 if ( 0xC0 == (0xC0 & r
[0]) ) { /* reserved bits check */
22688 unsigned char nos
, cct
, l21f
, csn
, er
, war
;
22691 char cctx
[64]; /* extra info */
22692 char ccty
[64]; /* type */
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
);
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);
22714 /* closed captioning for show virtual channels */
22716 /* EIA/CEA-608-B. that's NTSC */
22717 if ( 0x3E == (0x3E & r
[0]) ) { /* reserved */
22720 snprintf(ccty
, sizeof(ccty
)-1,
22722 "Type NTSC Line21 %s Field",
22724 (l21f
== 0) ? "Even" : " Odd" );
22727 /* EIA-708-A is ATSC ATVCC */
22730 snprintf(ccty
, sizeof(ccty
)-1,
22731 " ATVCC # %d [%s] Type %s",
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
);
22742 snprintf( s
, sizeof(s
)-1, "%s%s", ccty
, cctx
);
22743 add_tvct_text( s
);
22751 t
= "Content Advisory";
22752 if ( 0xC0 == (0xC0 & r
[0]) ) {
22753 unsigned char rrc
, rr
, rd
, rdj
, rv
, rdl
;
22754 add_tvct_text( s
);
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",
22769 add_tvct_text( s
);
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",
22779 add_tvct_text( s
);
22787 build_vct_mss( r
, rt
);
22788 snprintf(s
, sizeof(s
)-1, "%s Rating: %s", it
, rt
);
22789 add_tvct_text( s
);
22797 /* parsed in VCT but warn with blink if they show as mpeg descriptor */
22809 /* end of parsed in VCT descriptors */
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
);
22820 /* warn with blink for these unsupported and so far unseen types */
22822 t
= BL
"DCC Departing Request" BN
;
22826 t
= BL
"DCC Arriving Request" BN
;
22829 /* warn with blink to advertise the imminent peril to the right of fair use */
22831 t
= BR BL
"Redistribution Control - Fair Use Rights In Dire Jeopardy" BN
;
22833 /****************************************************************************/
22835 /* handle 0-1, 16-63, 64 and rest not done above */
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";
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 */
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
;
22871 char *c
; /* color based on cap vc & va */
22872 char *p
; /* es type, vct or pat */
22873 unsigned char dtag
, dlen
;
22877 s
= &ptc
[ cap_chan
].sig
;
22881 if (CAP_NONE
== cap_now
) {
22885 svc
= find_vc_pgm( svp
, WHO
);
22887 /* NO NTSC or Hidden
22888 if ( (0 == vc[n].pn) || (65535 == vc[n].pn) )
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;
22911 c
= BG
; /* cap all overrides colors */
22914 if (svp
== vc
[n
].pn
)
22915 c
= BG
; /* cap program overrides color */
22919 /* red indicates missing PMT PID */
22921 if (0 == strncmp( c
, BG
, 8))
22922 if (0 == pids
[vc
[n
].pmtpid
]) c
= BR
;
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 */
22941 /* red indicates missing PMT PID */
22943 if (0 == strncmp( c
, BG
, 8))
22944 if (0 == pids
[vc
[n
].pmtpid
]) c
= BR
;
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 );
22969 j
+= dlen
; /* to next descriptor until pilen */
22971 add_tvct_text( " " );
22975 for (i
=0; i
<numes
; i
++) {
22977 return; /* 8 VC limit */
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
];
22990 /* full cap colors yellow unparsed non-video non-audio */
22991 if ( (-1 == svp
) && (0x02 != est
) && (0x81 != est
) )
22994 if (i
>= vc
[n
].vctes
) {
22999 snprintf( estx
, sizeof(estx
)-1, "%s A/90 Type [%02X]", p
, est
);
23002 snprintf( estx
, sizeof(estx
)-1, "%s MPEG Video # %d", p
, n
);
23006 snprintf( estx
, sizeof(estx
)-1, "%s A/52 Audio # %d", p
, a
);
23007 if ( ( -1 < svp
) && (n
== svc
) && (a
!= sva
) ) c
= BN
;
23009 /* color audio being capped, but reset color if not this vc */
23012 /* red indicates missing ES PIDs */
23014 if (0 == strncmp( c
, BG
, 8))
23015 if (0 == pids
[vc
[n
].espid
[i
]]) c
= BR
;
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);
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
23052 build_vct_descriptor ( unsigned char *r
, unsigned int n
, unsigned short vcdlen
)
23054 unsigned char dtag
, dlen
;
23056 unsigned short len
= vcdlen
;
23058 /* should not be any descriptors for HIDDEN or NTSC?
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]))
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
,
23077 add_tvct_text( s
);
23080 while ( len
> 0 ) {
23081 if ((0 == r
[0]) || (0 == r
[1])) return; /* bad descriptor */
23090 /* extended channel name descriptor is mulitple string structure */
23092 if (0 != vct_dtl
) {
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
);
23102 /* service location descriptor */
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
);
23113 /* time shifted service descriptor, nop currently */
23115 snprintf( s
, sizeof(s
)-1,
23116 " %sTag %02X DLen %02X "
23117 "Time Shifted Service:", BY
, dtag
, dlen
);
23118 add_tvct_text( s
);
23121 /* alert for anything not right */
23123 snprintf( s
, sizeof(s
)-1,
23124 BY
" *Tag %02X DLen %02X ignored" BN
, dtag
, dlen
);
23125 add_tvct_text( s
);
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.
23143 build_vc_text ( void )
23145 unsigned short sl
, adl
;
23147 /* unsigned char cni, sn, lsn, pv, ncs; */
23148 unsigned char i
, j
;
23151 char t
[256]; /* temp text string */
23152 char *c
; /* color capturable vcs */
23156 s
= &ptc
[ cap_chan
].sig
;
23159 if (CAP_NONE
== cap_now
) {
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; */
23178 /* if ( (0 < pkt.tvct) && (0 < pkt.pat) ) return; */
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
23197 /* right now have 50 lines to work with. bump tvct_text if need more */
23198 memset( tvct_text
, 0, sizeof(tvct_text
) );
23200 /* ncs = vct_ncs; */
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
);
23208 if (0 != vct_ncs
) {
23209 snprintf( t
, sizeof(t
)-1,
23210 BW
"VCT Vn %02X SLen %03X Channels %d" BN
,
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 */
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
))
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;
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");
23288 if (0 == vc
[i
].pn
) {
23289 snprintf( ct
, sizeof(ct
)-1, " HIDDEN ");
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 */
23298 snprintf( pt
, sizeof(pt
)-1, "%d", vc
[i
].pn
);
23299 snprintf( ct
, sizeof(ct
)-1, BW
"Pgm %-5s %s", pt
, c
);
23303 /* TSID/callsign lookup not done in ATSC. Uses component name or VCT name */
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",
23317 vc
[i
].hide
, vc
[i
].hideg
, st
, /* vc[i].svct, */
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 */
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
23347 /* && (i == pat_ncs) */
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 */
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( " " );
23398 if (0 == vc
[i
].pmtes
) {
23399 add_tvct_text( " " );
23404 /* fallback to PAT and PMT for Elementary Stream info */
23405 if (0 != vc
[i
].pmtpid
) {
23407 /* red indicates missing PMT PID */
23409 if (0 == strncmp( c
, BG
, 8))
23410 if ( 0 == pids
[vc
[i
].pmtpid
] ) c
= BR
;
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 );
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
) {
23441 /* step thru known PMT stream types and show descriptors */
23442 for (j
= 0; j
< vc
[i
].pmtes
; j
++ ) {
23445 est
= vc
[i
].estype
[j
];
23449 /* treat everything unparsed as generic A/90 data type */
23450 if ( (-1 == svc
) && (0x02 != est
) && (0x81 != est
) )
23453 snprintf( estx
, sizeof(estx
)-1,
23454 " PMT A/90 Type [%02X]", est
);
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
);
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 */
23476 if (0 == strncmp( c
, BG
, 8))
23477 if (0 == pids
[vc
[i
].espid
[j
]]) c
= BR
;
23480 snprintf( t
, sizeof(t
)-1,
23481 "%s %s [%3s] ES # %01X Type %02X ES PID %04X "
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);
23495 k
+= dlen
; /* to next descriptor until pilen */
23497 if ( 0 != vc
[i
].esilen
[j
] ) add_tvct_text( " " );
23501 add_tvct_text( " " );
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];
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 */
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 */
23534 find_next_vc( int n
)
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
);
23548 advrlog( LOG_INFO
, "%s fallback %d", WHO
, 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)
23559 show_virtual_channels ( void )
23562 unsigned char key
= 0;
23565 int svc
, sva
, svp
, ovnc
;
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 */
23576 if (CAP_NONE
== cap_now
) {
23580 svc
= find_vc_pgm( svp
, WHO
);
23583 dvrlog( LOG_INFO
, "%s vc ncs %d", WHO
, vc_ncs
);
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;
23595 for (i
= 0; i
< user_lines
;) {
23597 if ( D_VCT
!= display_type
) {
23598 if (0 != ovnc
) vct_ncs
= ovnc
; /* have an ACME cigar! */
23602 if (0 != refresh
.vct
) {
23603 if (CAP_NONE
== cap_now
) {
23604 tt
= "ATSC VIRTUAL CHANNEL TABLE";
23605 tp
= columns
- strlen(tt
);
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
);
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] );
23631 show_status( 1, WHO
);
23634 if (CAP_NONE
== cap_now
) {
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
);
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
);
23648 aprintf( stderr
, BG
"Full Capture" BN CEL
);
23651 aprintf( stderr
, BN
"%s[?] help", dca
.ustat
);
23652 /* hold down further redraws */
23656 if (refresh
.vct
< 0) refresh
.vct
= 0;
23658 /* end of page? wait for key and process it */
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
;
23668 /* any key refreshes */
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'))) {
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",
23690 pat_built
= 0; /* force a new PAT */
23693 dvrlog( LOG_INFO
, "vc can't select %d", vcs
);
23695 nanosleep( &console_read_sleep
, NULL
); /* 100ms */
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) )
23708 advrlog( LOG_INFO
, "va select vc%d", svc
);
23710 /* wait til user sets vc first */
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
23720 sva
%= vc
[svc
].acn
;
23721 s
->va
= cap_va
= sva
;
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
);
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;
23745 /* if key was handled, show select and save config */
23747 refresh
.epg
= 1; /* bottom left epg count refresh */
23748 refresh
.vstats
= 1; /* line above bottom */
23751 if (CAP_NONE
== cap_now
) {
23755 dvrlog( LOG_INFO
, "cap p %d v%d a%d",
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 */
23768 if (CAP_INFO
== cap_now
) {
23769 stop_capture( WHO
, 0 );
23770 display_type
= D_TIMERS
;
23771 if (0 != ovnc
) vct_ncs
= ovnc
;
23776 /* toggle vct_ncs to zero and back for mpeg-only test display */
23781 vo
= 0; /* redraw list from start */
23785 vo
= 0; /* redraw list froms start */
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
;
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 */
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
23840 get_timer_add_event ( void )
23842 struct qtimer_s
*t
;
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 */
23864 memset( pgm_in
, 0, sizeof( pgm_in
));
23869 if (timer_idx
>= TIMER_LIST_MAX
) {
23872 "Timers full. Recompile with larger TIMER_LIST_MAX." CEL
,
23874 nanosleep( &msg_long_sleep
, NULL
);
23878 /* input needs cursor visible */
23879 aprintf( stderr
, SCV BL
23880 "%s%sEPG# index[-index2][ SMTWTFS bits] to add ?" BN
" " CEL
,
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
) {
23893 /* quick exit is blank line */
23894 if ( 0 == *pgm_in
) {
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
);
23908 p
= strchr( pgm_in
, '-'); /* dash indicates range */
23911 pgm_r2
= atoi( p
);
23916 p
= strchr( pgm_in
, ' '); /* space indicates weekday bits */
23922 /* is ascii binary? hmm no isbinary()? */
23924 if ( ( '0' == *wdb
) || ('1' == *wdb
) ) pgm_wd
= abtou( wdb
);
23928 /* user input error check */
23929 if ( (pgm_r1
>= pgm
.idx
)
23930 || (pgm_r2
>= pgm
.idx
)
23931 || (pgm_r2
< pgm_r1
)
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 */
23941 pgo
= pg
[ pgm_add
];
23944 /* copy out the event name for mangling */
23945 memcpy( n
, e
->name
, sizeof(pgm_name
) );
23949 /* vc not used anymore. use pgm instead in case of VCT gone PAT fallback */
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 */
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
) )
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
) {
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
;
23989 astrncpy( t
->name
, pgm_name
, TIMER_NAME_MAX
);
23991 /* if DST, adjust program start non DST, other functions adjust... */
23993 memcpy( &tloc2
, localtime( &trec2
), sizeof( tloc2
) );
23996 /* dst changover from now to timer? no, don't do this */
23997 if ( tloc
.tm_isdst
!= tloc2
.tm_isdst
) {
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 */
24015 t
->chan
= cap_chan
;
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 */
24023 while( t
->start
>= utsnow
)
24028 /* use program number not timer.vc in case VCT gone and PAT only fallback */
24030 /* timer.vc not used anymore */
24034 t
->qstat
= T_FUTURE
;
24039 if (0 != *e
->name
) {
24040 astrncpy( t
->ename
, e
->name
, PNZ
);
24041 if (0 != *e
->desc
) {
24042 astrncpy( t
->edesc
, e
->desc
, PDZ
);
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
);
24057 /* sort moved outside of range loop */
24061 save_config( WHO
);
24065 refresh
.timers
= 1;
24067 display_type
= D_TIMERS
;
24071 /* get program index [range] and toggle the spam status for the event(s) */
24074 get_spam_toggle ( void )
24076 struct event_s
*e1
;
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 */
24088 memset( pgm_in
, 0, sizeof( pgm_in
));
24090 if (spam_idx
>= SPAM_LIST_MAX
) {
24093 "Spam list full. Recompile with larger SPAM_LIST_MAX." CEL
,
24095 nanosleep( &msg_long_sleep
, NULL
);
24099 /* input needs cursor visible */
24100 aprintf( stderr
, SCV BL
24101 "%s%sEPG# index[-index2] for junk toggle?" BN
" " CEL
,
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
) {
24114 /* quick exit is blank line */
24115 if ( 0 == *pgm_in
) {
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
);
24130 p
= strchr( pgm_in
, '-'); /* dash indicates range */
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 */
24142 /* user input error check */
24143 if ((pgm_r1
>= pgm
.idx
) || (pgm_r2
>= pgm
.idx
) || (pgm_r2
< pgm_r1
))
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
);
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 */
24165 save_spamlist( WHO
);
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 */
24180 find_current_epg_pgm( void )
24186 s
= &ptc
[cap_chan
].sig
;
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
);
24202 advrlog( LOG_INFO
, "%s found %s.%d",
24203 WHO
, e
->name
, e
->pn
);
24204 j
= i
; /* event match */
24205 break; /* loop is done */
24216 /* show programs found so far, scroller removes need for word wrap */
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 */
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 */
24257 if (D_EPG
!= display_type
) return; /* don't display if not EPG */
24259 evc
= pgn
= lc
= po
= 0;
24262 s1
= &ptc
[ pgm
.chan
].sig
;
24269 /* current cap sets program view to same as cap, may be all when CAP_INFO */
24270 if (CAP_NONE
!= cap_now
) {
24274 /* FIXME: idle sets to selected program, but not showing VC reset s->pn -1 ?
24276 // if (s1->pn > 0) pgm.pn = s1->pn;
24280 /* build filtered (or not) pg list from master epg list */
24281 build_pg_epg( epg
, &pgm
, pg
, WHO
);
24284 po
= find_current_epg_pgm();
24285 if (-1 == po
) po
= 0;
24288 /* no current program if not visible as highlight (stale guide) */
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 */
24301 if (0 != refresh
.epg
)
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
);
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
);
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 */
24339 memset( name
, 0, sizeof(name
) );
24340 memset( desc
, 0, sizeof(desc
) );
24341 memset( d
, ' ', sizeof(d
) );
24344 /* color for future show more than 12 hours */
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? */
24359 /* needed to be somewhere in the inner loop */
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 */
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";
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 */
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
,
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" );
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
,
24450 if ( 0 == e1->dchr )
24451 snprintf( desc, sizeof(desc) - 1, "%s", "n/a");
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
);
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
;
24479 snprintf( pn
, sizeof(pn
)-1, "HIDE");
24482 if (pgn
>= 65535) {
24483 snprintf( pn
, sizeof(pn
)-1, "NTSC");
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;
24500 if (so
>= PDZ
) so
= PDZ
- 30;
24506 /* truncate description by number of columns left to display it */
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
);
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) */
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
);
24539 } /* end of refresh.epg != 0 */
24545 /* rest of what happens when i == 0 */
24547 /* check pg list after loop iterations */
24551 if ( 0 != refresh
.epg
) {
24552 build_pg_epg(epg
, &pgm
, pg
, WHO
);
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 */
24569 save_config( WHO
); /* save any timers added and epg */
24573 /* any key refreshes */
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
);
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
;
24597 cap_now
= CAP_INFO
;
24598 advrlog( LOG_INFO
, "load request from EPG");
24599 display_type
= D_TIMERS
;
24604 if ( 's' == key
) {
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
)
24622 && (T_SEARCH
!= timer
[0].qstat
) )
24624 if ( utsnow
>= timer
[0].start
- timer
[0].secdt
)
24625 stop_capture( WHO
, FAIL_NONE
);
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
);
24641 /* age out, match, hidden, sort and numeric toggles */
24642 if ( 'a' == key
) {
24643 pgm
.age
= ~pgm
.age
;
24646 if ( 'm' == key
) {
24647 pgm
.find
= ~pgm
.find
;
24650 if ( 'h' == key
) {
24651 pgm
.hide
= ~pgm
.hide
;
24654 if ( 'n' == key
) {
24655 pgm
.etmids
= ~pgm
.etmids
;
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();
24664 refresh
.estats
= 1;
24665 show_event_short();
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
)) {
24677 sig
= &ptc
[ pgm
.chan
].sig
;
24679 /* user overrides last zap program guide timer hold down */
24682 /* find current program for this time or do nothing */
24683 cp
= find_current_epg_pgm();
24685 advrlog( LOG_INFO
, "%s add search event timer epg[%04X]",
24687 add_search_event_timer( &epg
[ pg
[ cp
]] );
24689 save_config( WHO
);
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) */
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 */
24745 show_signal ( struct sig_s
*s
, int i
)
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 */
24780 /* w key toggles this through strength% peak% mhz or wavelen */
24783 /* bar is blank unless it has a real signal */
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 */
24803 snprintf( t1
, 11, " %3d%% ", s
->strength
);
24806 /* frequency to device */
24808 snprintf( t1
, 10, "%9d", s
->freq
);
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
);
24821 /* 2.2 decimal from 8.8 binary */
24823 snprintf( t1
, 11, " %2d.%02d dB", 0xFF & (s
->snr
>>8),
24824 ((0xFF & s
->snr
) * 100) >> 8 );
24827 #ifdef USE_DVB_EXPERIMENTAL
24828 /* frequency returned by new driver */
24830 /* only want 5 digits */
24831 snprintf( t1
, 11, " %5d", s
->ber
);
24834 /* frequency offset calc from new driver return, 6 digits + sign */
24836 snprintf( t1
, 11, "%+6d Hz", s
->fohz
);
24846 /* only change numeric to error text on signal scan */
24847 if ( SIG_STR
== display_sigtype
) {
24849 if (0 == s
->lock
) {
24856 /* test with real device */
24857 if ( (0 == arg_dummy
) && (0 != test_mode
) ) {
24861 /* no scan overrides value and sigbar */
24862 if (0 == scan_sig
) {
24864 t1
= " SCAN OFF" SCV
; /* value */
24866 t2
= ""; /* sigbar */
24867 if ( (0 != arg_dummy
) && (0 != test_mode
) ) {
24869 t1
= " SCAN ON " SCV
;
24874 /* no scan overrides sigbar
24875 if (0 == scan_sig) {
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
;
24889 /* signal strength same as bar color */
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 *********** */
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 */
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 */
24923 add_cap_text( char *s
)
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
);
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
));
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");
24956 snprintf( r
, sizeof(r
)-1, BN
" Wall Cap 0 1 2 3 4 5");
24958 snprintf( r
, sizeof(r
)-1, BN
"hh:mm:ss " BW
"hh:mm:" BN
"012345678901234567890123456789012345678901234567890123456789");
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;
24976 /* tloc3 is start cap timeval */
24977 s1
+= tloc3
.tm_sec
;
24983 m1
+= tloc3
.tm_min
;
24989 h1
+= tloc3
.tm_hour
;
24992 /* show wall clock and capture clock to the minute */
24993 snprintf( hms
, sizeof(hms
)-1, BN
"%02d:%02d:%02d " BW
"%02d:%02d " BN
,
24996 memset( ltc
, 0, sizeof(ltc
)); /* reset color so first test will set */
24997 memset(r
, 0, sizeof(r
)); /* clear it for this round */
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 */
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 */
25034 sg
++; /* count good seconds */
25036 sb
++; /* count bad seconds */
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 */
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 */
25061 r
[k
] = t1
; /* error indication or . for no error */
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 */
25073 show_caplog ( void )
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
);
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 */
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 */
25117 aprintf( stderr
, "\033[%d;1H" "%s" CEL
, 5+i
, &cap_text
[j
][0] );
25124 /* end of list redraws header, looks for key and sleeps if no key */
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
);
25139 build_cap_text(); /* might eat cpu */
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 */
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
);
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.
25188 dump_frame_sequence ( void )
25193 struct frame_s frm
;
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");
25219 /* expand and write sequence data */
25221 for (i
= 0; i
< sequence_idx
; i
++) {
25222 seq
+= 0xFFFFFFFFLL
& sequences
[i
];
25223 ok
= fwrite( &seq
, sizeof(seq
), 1, f
);
25225 dvrlog( LOG_INFO
, "failed to write sequences\n");
25231 ok
= fwrite( &seq
, sizeof(seq
), 1, f
);
25233 dvrlog( LOG_INFO
, "failed to write sequence terminator\n");
25238 /* expand and write frame data */
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
);
25245 dvrlog( LOG_INFO
, "failed to write frames\n");
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.
25269 show_chanlist ( void )
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 */
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
25326 console_scan ( void )
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 */
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 */
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 ********************************/
25374 for (i
=0; i
<search_idx
; i
++)
25375 dvrlog(LOG_INFO
, "%s", search_list
[i
].name
);
25380 /* DEBUG: manual force test guides */
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");
25392 udp_mcast
= ~udp_mcast
;
25398 if (utsets
< 1) break;
25400 display_type
= D_LOG
;
25403 display_type
= D_TIMERS
;
25405 show_status( 0, WHO
);
25410 if (0 == pkt
.fmgt
) break;
25412 display_type
= D_MGT
;
25414 show_master_guide_table();
25415 display_type
= D_TIMERS
;
25417 show_status( 0, WHO
);
25420 /* Virtual Channel Table display */
25423 dvrlog( LOG_INFO
, "%s no VCT to display", WHO
);
25426 display_type
= D_VCT
;
25428 show_virtual_channels();
25429 display_type
= D_TIMERS
;
25431 show_status( 0, WHO
);
25434 /* Program Guide */
25437 scan_sig
= 0; /* sig scan bogs program guide */
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) {
25449 display_type
= D_EPG
;
25451 show_program_guide();
25453 /* in case any new timers added */
25454 /* dump_epg_html( epg, &pgm, pg, WHO ); */
25455 display_type
= D_TIMERS
;
25457 show_status( 0, WHO
); /* no wait for timer list */
25460 /* load program guide from current channel */
25462 /* only if no cap already in progress */
25463 if (CAP_NONE
== cap_now
)
25464 cap_now
= CAP_INFO
;
25468 /* toggle ATSC/MPEG2 table and packet counts */
25470 refresh
.pstats
= 1;
25471 display_stat
= ~display_stat
;
25472 test_termsize(); /* pkt stat changes number of lines to display */
25474 show_status( 0, WHO
);
25477 /* toggle timer time display between user and timer mode */
25479 if (CAP_TIME
== cap_now
) {
25480 cap_etime
= ~cap_etime
;
25481 refresh
.headers
= 1;
25483 /* show_status( 0, WHO ); */
25487 /* DEL (BS backspace key in console and aterm) (stop manual cap) */
25489 if ( (CAP_INFO
== cap_now
) || (CAP_USER
== cap_now
) ) {
25490 stop_capture( WHO
, FAIL_NONE
);
25495 /* DEL (BS backspace key in xterm) (stop manual cap) */
25497 if ( (CAP_INFO
== cap_now
) || (CAP_USER
== cap_now
) ) {
25498 stop_capture( WHO
, FAIL_NONE
);
25503 /* override timer, switches to manual cap mode */
25505 if (CAP_TIME
== cap_now
) {
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;
25514 cap_now
= CAP_USER
;
25518 /* reschedule weekday or remove volatile timer. stops cap if index is 0 */
25520 timer_reschedule();
25521 /* timer may reschedule or expire, so refresh timer list */
25522 display_type
= D_TIMERS
;
25527 /* same as reschedule for active timer 0 and also deletes the capture */
25530 /* timer may reschedule or expire, so refresh timer list */
25531 display_type
= D_TIMERS
;
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...
25543 /* wait one minute before doing next test epg */
25544 if (utsnow
> utstpg
) utstpg
+= PROGRAM_GUIDE_TIMEOUT
;
25549 /* clear all timers */
25552 aprintf(stderr
, "\033[%d;1H" CCE
, 4); /* keep header */
25557 /* too easy to hit instead of load guide */
25559 /* toggle quiet mode on or off */
25561 arg_quiet
= ~arg_quiet
;
25563 refresh
.timers
= 1;
25569 /* toggle timer/channel list */
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;
25579 if ( D_CHANNELS
== display_type
) {
25580 display_type
= D_TIMERS
;
25585 /* clear from start of line 3 to end */
25586 snprintf( csp
, 16, "\033[3;1H");
25587 aprintf( stderr
, "%s" CCE
, csp
);
25590 /* enable signal scan for all channels */
25591 if ( D_CHANNELS
== display_type
)
25595 /* draw initial channel signal list display */
25597 /* will be updated automatically by signal scan */
25600 show_status( 0, WHO
); /* will call show_timers() */
25603 /* FF ^L clears tube, redraws headers */
25605 set_title(); /* FIXME: doesn't work over ssh + screen */
25609 /* will increment to first channel */
25610 /* scan_next = scan_idx; */
25611 /* scan_one = 0; */
25612 show_status( 0, WHO
);
25616 /* set a timer for the current channel */
25624 /* delete a timer by # */
25627 if (timer_idx
> 0) {
25628 get_timer_delete();
25633 /* move a timer by # to config by # */
25635 if (timer_idx
> 0) {
25643 if (CAP_NONE
== cap_now
) /* prevent accidental quit */
25644 // if (0 != arg_tmpfs) save_tmpfs( WHO );
25648 /* shift for both of these to prevent accidents */
25649 /* input configuration file */
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 */
25657 /* output configuration file */
25659 advrlog( LOG_INFO
, "user saves cfg" );
25660 save_config( WHO
);
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
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;
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;
25685 if (D_CHANNELS
== display_type
) {
25686 if (channel_offset
> user_lines
) {
25687 channel_offset
-= user_lines
;
25689 channel_offset
= 0;
25691 scan_pos
= channel_offset
;
25692 refresh
.channels
= 1;
25696 if (D_TIMERS
== display_type
) {
25698 if (timer_offset
> user_lines
) {
25699 timer_offset
-= user_lines
;
25703 if (timer_offset
< 0) timer_offset
= 0;
25704 refresh
.timers
= 1;
25710 case '\'': /* single quote */
25711 if (D_CHANNELS
== display_type
) {
25712 if (channel_offset
< (channel_idx
- user_lines
))
25714 if (channel_offset
>= channel_idx
)
25715 channel_offset
= channel_idx
- user_lines
;
25716 scan_pos
= channel_offset
;
25717 refresh
.channels
= 1;
25721 if (D_TIMERS
== display_type
) {
25722 if (timer_offset
< (timer_idx
- user_lines
))
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;
25733 if (D_CHANNELS
== display_type
) {
25734 if ((channel_offset
+user_lines
) < (channel_idx
-user_lines
)) {
25735 channel_offset
+= user_lines
;
25737 channel_offset
= channel_idx
- user_lines
;
25739 scan_pos
= channel_offset
;
25740 refresh
.channels
= 1;
25744 if (D_TIMERS
== display_type
) {
25745 if ((timer_offset
+ user_lines
) < (timer_idx
- user_lines
)) {
25746 timer_offset
+= user_lines
;
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;
25759 if (D_CHANNELS
== display_type
) {
25760 channel_offset
= 0;
25761 refresh
.channels
= 1;
25765 if (D_TIMERS
== display_type
) {
25767 refresh
.timers
= 1;
25773 if (D_CHANNELS
== display_type
) {
25774 channel_offset
= channel_idx
- user_lines
;
25775 refresh
.channels
= 1;
25778 if (D_TIMERS
== display_type
) {
25779 timer_offset
= timer_idx
- user_lines
;
25780 refresh
.timers
= 1;
25785 /* keyboard help */
25788 advrlog( LOG_INFO
, "user needs help" );
25790 refresh
.timers
= 0;
25791 show_keyhelp( timerskeyhelp
);
25793 show_status( 0, WHO
);
25796 /* range checks would go here if there were any for this mode */
25799 advrlog( LOG_INFO
, "console any scan ignores key 0x%X", kb
);
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
) {
25812 #ifdef USE_POWERDOWN
25813 /* close device within 2s, so you can run something else on dvb device */
25817 utspdd
= utsnow
+ 2;
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. */
25827 cap_now
= CAP_USER
;
25834 /* ENTER key: full cap or vid+aud selected in VCT Guide. */
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)) {
25843 cp
= find_current_epg_pgm();
25846 "%s add search event timer epg[%04X]",
25849 add_search_event_timer( &epg
[ pg
[ cp
]] );
25851 save_config( WHO
);
25853 refresh
.timers
= 1;
25858 cap_now
= CAP_USER
;
25863 /* this is different than 'x' above that resets signal stats only */
25864 /* reset peaks, refresh */
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
;
25877 s
->lock
= s
->lock_sum
= 0;
25883 /* SP (spacebar key) channel lock toggle */
25885 scan_one
= ~scan_one
;
25886 /* if unlocking, turn on scan, timer may disable it later */
25887 if (scan_one
== 0) scan_sig
= ~0;
25890 /* auto EPG toggle, new feature for pchdtvr 1.0 */
25893 s
= &ptc
[ scan_list
[scan_pos
] ].sig
;
25895 if (0 != s
->pgto
) {
25899 s
->pgto
= 1; /* default is 3 hours */
25901 /* utstpg = utsnow + PROGRAM_GUIDE_TIMEOUT; */
25903 advrlog( LOG_INFO
, "APG%02d AGTO %d", s
->chan
, s
->pgto
);
25904 save_config( WHO
);
25908 /* toggle snr display header/value change */
25911 /* -x goes thru entire list including regular basic info */
25912 display_sigtype
%= SIG_MODREG
;
25914 refresh
.channels
= 1;
25915 refresh
.headers
= 1;
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
25938 /* plus one for scan_next to be 1-n instead of 0-n */
25939 scan_next
= kb
- '0'; /* 1 - 9 */
25942 if (scan_next
> scan_idx
) break;
25945 // if (scan_next >= scan_idx) scan_next %= scan_idx;
25947 /* lock and scan for signal */
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
;
25959 // scan_sig = 0; // doesnt change channels correctly
25961 /* channel change resets bottom line table found indicators */
25969 if (scan_next
!= 0) {
25970 cap_chan
= scan_list
[ scan_next
- 1 ];
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 */
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 */
25996 /* second group of channel hotkeys are alpha A-Z */
25997 #ifdef USE_ISOC_PEDANTIC
26028 scan_next
= kb
- 'A'; /* 11 - 36 */
26032 if (scan_next
> scan_idx
) break;
26034 /* lock and scan for signal */
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
;
26046 // scan_sig = 0; // doesn't change channels correctly
26049 /* do not reset cap pids */
26050 /* cap_vc = cap_va = -1; */
26051 /* cap_pidx = 0; */
26054 /* channel change resets bottom line indicators */
26062 if (0 != scan_next
) {
26063 cap_chan
= scan_list
[ scan_next
- 1 ];
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 */
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 */
26089 advrlog( LOG_INFO
, "console chan scan ignored key 0x%X", kb
);
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
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 */
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
);
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 */
26128 atsc_strict_log ( unsigned char *r
, char *n
)
26132 if (0 == arg_strict
) return;
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.
26159 atsc_build_payload ( struct payload_s
*s
, unsigned char *p
, char *n
)
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 */
26176 r
++; /* skip past offset byte */
26177 /* atsc_strict_log( r, n ); */
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 ) {
26197 /* payload start indicator no */
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 );
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 ) {
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
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
;
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);
26302 dvrlog( LOG_INFO
, "%s chk_crc %08X", WHO
, chk_crc
);
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.
26317 atsc_parse_pat ( unsigned char *p
)
26321 unsigned char vn
, cni
, sn
, lsn
;
26322 unsigned short tsi1
, pgn
, netpid
, pmtpid
;
26323 unsigned int pat_crc
, vcn
;
26326 struct tsid_s
*tsn
;
26329 s
= &ptc
[cap_chan
].sig
;
26332 pkt
.fpat
= PAY_READ
;
26333 pat
.payok
= atsc_build_payload( &pat
, p
, "PAT" );
26334 if (0 != pat
.payok
) return;
26338 /* pat is one packet usually, so can get away with this */
26339 /* point to table id */
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 */
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 */
26366 pat_crc
= calc_crc32( r
, sl
+3 ); /* want crc to validate it */
26367 if (0 != pat_crc
) {
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 */
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);
26401 /* don't update rest of table if no version change to save processing */
26407 pkt
.fpat
= PAY_PARSE
;
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
;
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 */
26434 && (1 == (tsi1
& 1))
26437 tsx
= find_tsidx( tsi1
, pgn
, WHO
);
26439 /* -1 is not found */
26443 if (pgn
== s
->pn
) {
26444 snprintf( tsn
->call
, 8, "%s", s
->call
);
26446 snprintf( tsn
->sid
, 4, "%s", s
->sid
);
26450 tsn
= &tsids
[tsidx
];
26458 if (pgn
== s
->pn
) {
26459 snprintf( tsn
->call
, 8, "%s", s
->call
);
26461 snprintf( tsn
->sid
, 4, "%s", s
->sid
);
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
);
26470 /* is found, update the callsign and sid */
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?
26482 netpid
= 0x1FFF & ( (r
[2]<<8) | r
[3] );
26483 dvrlog( LOG_INFO
, "PAT%02d obsoleted NIT %04X", cap_chan
, netpid
);
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 */
26512 if (cap_pn
== pgn
) {
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
) {
26521 cap_pids
[1] = pmtpid
;
26523 /* if found, put in cap_pids for keeping */
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 */
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
);
26555 advrlog( LOG_INFO
, "PAT adds pn.%04X missing from VCT, vc_ncs %d",
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
;
26563 /* TESTME: This belongs under strict ATSC checking? FOX KRIV gets these? */
26564 /* if (0 != arg_strict) */
26566 if (pgn
!= vc
[vcn
].pn
)
26568 "PAT%02d PGM %04X doesn't match VCT %04X",
26569 cap_chan
, pgn
, vc
[vcn
].pn
);
26571 if (pmtpid
!= vc
[vcn
].pmtpid
)
26573 "PAT%02d PMT %04X doesn't match VCT %04X",
26574 cap_chan
, pmtpid
, vc
[vcn
].pmtpid
);
26576 if (tsi1
!= vc
[vcn
].tsid
)
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 */
26591 r
+= 4; /* skip to next program number and PID */
26594 if (vct_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
26613 atsc_test_cat ( unsigned char *p
, unsigned short pid
)
26617 unsigned int cat_crc
= 0;
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
);
26630 /* point to table id */
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) {
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
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
;
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.
26684 dvrlog( LOG_INFO
, "Not building multi-packet PMT");
26688 memset( new_pmt
, 0, sizeof(new_pmt
));
26690 advrlog( LOG_INFO
, "%s %04X PMT oldsl %03X newsl %03X",
26691 WHO
, pid
, osl
, 13);
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 */
26707 /*step thru descriptors */
26708 for ( i
= 0; i
< c
; ) {
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 */
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",
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)) ) {
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 */
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 */
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 */
26751 /* no reserved bits stops loop */
26752 advrlog( LOG_INFO
, "%s %04X no reserved bits",
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));
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 */
26784 advrlog( LOG_INFO
, "PMT%02d %04X v %04X a %04X",
26785 cap_chan
, pid
, cap_pids
[2], cap_pids
[3]);
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);
26797 dvrlog( LOG_INFO
, "%s %04X chk_crc %08X",
26798 WHO
, pid
, chk_crc
);
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?
26812 atsc_parse_mss_cname ( unsigned char *r
, int n
)
26814 unsigned int nstr
, nseg
, comp
, mode
, nchr
;
26820 d
= t
; /* stupid compiler tricks */
26824 memset( t
, 0, sizeof(t
));
26826 for (i
= 0; i
< nstr
; i
++) {
26827 memcpy(lang
, &r
[1], 3);
26831 for (j
= 0; j
< nseg
; j
++) {
26836 /* nchr should never be above 255 with no compression */
26837 if ( (0 == comp
) && (0 == mode
) ) {
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 */
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.
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
;
26880 struct payload_s
*pmp
;
26886 pmtpa
= pan
; /* program association index */
26889 /* single program cap needs one PAT as first packet */
26892 /* hold down output until PAT seen */
26893 if (0 == pkt
.pat
) {
26899 pid
= 0x1FFF & ((p
[1] << 8) | p
[2]);
26903 pkt
.fpmt
= PAY_READ
;
26905 pmp
->payok
= atsc_build_payload( pmp
, p
, "PMT" );
26906 if (0 != pmp
->payok
) return;
26910 /* point to table id */
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 */
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 */
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];
26944 pmt_crc0
|= r
[ sl
];
26946 pmt_crc0
|= r
[ sl
+ 1 ];
26948 pmt_crc0
|= r
[ sl
+ 2 ];
26950 pmt_crc
= calc_crc32( r
, sl
+3 ); /* full crc validate = 0 */
26952 /* does computed match received? */
26953 if (pmt_crc0
!= pmt_crc
) {
26955 dvrlog( LOG_INFO
, "PMT PID %04X bad CRC32 RCVD %08X CALC %08X",
26956 pid
, pmt_crc0
, pmt_crc
);
26961 pgn
|= r
[4]; /* 16 bit program number */
26963 vn
= 0x1F & (r
[5] >> 1);
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 */
26989 /* will see this error if PMT arrives before PAT. this ok */
26991 advrlog( LOG_INFO
, "ch %02d PMT %04X no PAT pa[].pn stop",
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
);
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
)
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 );
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",
27034 #ifdef USE_MEPG_REBUILD
27035 // if (0 != pmt_built)
27037 return; /* rebuild if need to */
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
)
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 */
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.
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 */
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",
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",
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 */
27125 advrlog( LOG_INFO
, "%s %04X MPEG not 1st ES", WHO
, pid
);
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 */
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
])
27153 stm
= ~0; /* stream type is match */
27155 /* no vct, so pmt has to fill in */
27156 if (vct_ncs
< 1) stm
= ~0;
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) ) {
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
);
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 */
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 */
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",
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",
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 */
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
);
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
])
27260 /* no vct, so pmt has to fill in */
27261 if (vct_ncs
< 1) stm
= ~0;
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) ) {
27272 vc
[ pmtvc
].esilen
[ numes
] = esilen
;
27273 /* copy ES descriptor to vc[] for display */
27274 memcpy( &pa
[ pmtpa
].esdesc
[ numes
][0], r
, esilen
);
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
;
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 };
27293 brc
= 0x1F & ( b
[1] >> 2); /* bit rate code */
27294 if (brc
> 19) brc
= 19;
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 */
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;
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 */
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",
27350 if (0 != arg_strict
) {
27351 if (0xF0 != (0xF0 & r
[3])) {
27352 dvrlog( LOG_INFO
, "%s %04X A/90 ES rb[3] fail %02X",
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
) ) {
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" */
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
;
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
);
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;
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 */
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
;
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 */
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.
27483 atsc_test_mpeg2_video_pes ( unsigned char *p
, unsigned int pid
)
27485 unsigned char psi
, pct
;
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
) ) {
27503 /* if psi set, check for sequence */
27505 /* payload start set should mean SEQ+I, P or B picture */
27508 /* only if found pa index for video es pid */
27509 pav
= find_pa_vespid( pid
);
27510 vcv
= find_vc_vespid( pid
);
27513 advrlog( LOG_INFO
, "%s ESPID %04X PS1 SEQ", WHO
, pid
);
27514 for (i
= 4; i
< 188; i
++) {
27516 /* look for start code */
27517 if (0x00000100UL
!= (0xFFFFFF00UL
& b
)) continue;
27518 /* Sequence Start code 000001B3 */
27519 if ( 0x1B3 == b
) {
27521 /* point to SEQ data */
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;
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]) {
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 */
27571 for (i
= 4; i
< 188; 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
;
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[].
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
;
27613 last_vpn
= pkt
.vcvid
;
27615 dvrlog( LOG_INFO
, "use larger FRAME_MAX");
27616 dvrlog( LOG_INFO
, ".tsx write disabled");
27618 break; /* save cycles, stop looping */
27620 /* break because using Sequence as boundary instead of Intra picture */
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
) ) {
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
;
27644 last_vpn
= pkt
.vcvid
;
27646 dvrlog( LOG_INFO
, "use larger FRAME_MAX");
27647 dvrlog( LOG_INFO
, ".tsx write disabled");
27649 break; /* save cycles, stop looping */
27653 /* break when picture type found */
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
) {
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.
27676 atsc_test_a52_audio_pes ( unsigned char *p
, unsigned int pid
)
27679 psi
= 1 & (p
[1]>>6);
27681 /* count single vc audio packets separately */
27685 /* hold down until pid matches cap_pids[3] (implies vc set or will be set) */
27686 if (pid
!= cap_pids
[3]) {
27691 /* hold down until AFTER first video sequence */
27692 if (0 == sequence_idx
) {
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 */
27707 atsc_test_vc_vid( short pid
)
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
] )
27720 /* test pid against vc[] audio ES PIDs, z if match, nz otherwise */
27723 atsc_test_vc_aud( short pid
)
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
] )
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..
27745 atsc_test_mpeg2 ( unsigned char *p
, unsigned short pid
)
27753 psi
= 1 & (p
[1]>>6);
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
) {
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 */
27771 /***************************************************************** MPEG2 PES */
27774 /********************************************************* MPEG2 PAT/CAT/PMT */
27776 /* Program Association Table ID 0 in PID 0 */
27780 atsc_parse_pat( p
); /* has Program Map PIDs */
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 */
27788 atsc_test_cat( p
, pid
); /* crc check at least */
27792 /* Program Map Table ID 2 in variable PID packet byte 5 */
27793 pan
= find_pa_pmtpid( pid
);
27795 /* has video and audio PIDs & descriptors */
27796 atsc_parse_pmt( p
, pan
);
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
);
27818 pkt
.pes
++; /* count overall PES */
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
) {
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
) {
27844 /* have video, ok to add audio */
27846 /* fall thru to test a52 */
27850 /* not cap pid[2 or 3], don't keep it */
27855 atsc_test_a52_audio_pes( p
, pid
);
27856 if (0 != pkt
.keep
) pkt
.pes
++;
27859 /***************************************************************** MPEG2 PES */
27862 /***************************************************************** ATSC MGT */
27865 atsc_find_mg_tt( unsigned int tt
)
27870 for (i
= 0; i
< mg_idx
; i
++)
27871 if (tt
== mg
[i
].tt
)
27877 /***************************************************************** ATSC DCCSCT
27878 not using directed channel change selection code table
27882 atsc_test_dccsct ( unsigned char *p
)
27888 /***************************************************************** ATSC DCCT
27889 not using directed channel change table
27893 atsc_test_dcct ( unsigned char *p
)
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
27908 atsc_test_rrt ( unsigned char *p
, int tn
)
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
;
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;
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 */
27947 /* reserved bits wrong */
27949 atsc_strict_log( r
, "RRT BAD TT/RB");
27952 /* 12 bit section length */
27954 sl
= (0xF & *r
) << 8;
27960 r
++; /* r[5] version */
27961 vn
= 0x1F & (*r
>> 1);
27963 rrt_crc
= calc_crc32( rrt
.payload
, sl
+3 );
27965 if (rrt_crc
!= 0) {
27967 advrlog( LOG_INFO
, "RRT bad CRC");
27968 pkt
.frrt
= PAY_DROP
;
27972 /* because it's not parsed, flag it as good and done */
27973 pkt
.frrt
= PAY_GOOD
;
27974 s
= &ptc
[cap_chan
].sig
;
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
);
27985 if (sl
> mg
[ mi
].nb1
)
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
28002 atsc_parse_mss_ett ( unsigned char *r
, unsigned int n
, unsigned int etmid
, int pgo
)
28005 unsigned int dstr
, dseg
, dcomp
, dmode
, dchr
;
28011 /* e is event, t is description buffer */
28017 for (i
= 0; i
< dstr
; i
++) {
28018 memcpy(lang
, &r
[1], 3);
28022 for (j
= 0; j
< dseg
; j
++) {
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) ) {
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 */
28066 astrncpy( e
->dlang
, lang
, 4 );
28073 /******************************************************************** ATSC ETT
28074 process ett packet and copy the description to EPG if ETMID found
28078 atsc_parse_ett ( unsigned char *p
, unsigned int n
)
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 */
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;
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
;
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 */
28132 /* reserved bits wrong */
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 */
28147 sl
= (0xF & *r
) << 8;
28151 ett_crc
= calc_crc32( ett
[n
].payload
, sl
+3 );
28152 if (ett_crc
!= 0) {
28154 advrlog( LOG_INFO
, "%s bad CRC", t
);
28155 pkt
.fett
= PAY_DROP
;
28159 /* ett table id extension */
28160 etidx
= (r
[3] << 8) | r
[4]; /* should be 0 */
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.
28170 /* two extra branch predictions */
28172 advrlog( LOG_INFO
, "ETT-%02X tid ext %04X not 0", n
, etidx
);
28177 vn
= 0x1F & (*r
>> 1);
28179 pkt
.fett
= PAY_PARSE
; /* indicator for parse */
28182 cni
= 1 & *r
; /* current next indicator */
28184 sn
= *r
; /* section number */
28186 lsn
= *r
; /* last section number */
28190 if (pv
!= 0) return; /* protocol version 0 only */
28192 /* etmid in 32 bits, top 16 are src */
28203 /* start of text to extract */
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 */
28211 advrlog( LOG_INFO
, "ETT-%02X VN %02X ETMID %08X ETIDX %04X",
28212 n
, vn
, etmid
, etidx
);
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
;
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
);
28235 if (sl
> mg
[ mi
].nb1
)
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
);
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 */
28256 atsc_sort_eit_epg ( unsigned int pgo
)
28258 int i
, j
, p
, pi
, pj
, v
, e
, z
;
28260 char s1
[32], s2
[32];
28262 z
= sizeof(struct event_s
);
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 # */
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. */
28280 for (i
= 0; i
< (PEZ
- 1); i
++) {
28282 for (j
= i
+ 1; j
< PEZ
; 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
) {
28289 "swap pgo[%04X] %s <> pgo[%04X] %s",
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
28314 atsc_test_eit_rr_mss ( unsigned char *r
, unsigned int pgo
)
28316 unsigned int nstr
, nseg
, comp
, mode
, nchr
;
28322 t
= epg
[pgo
].rating
;
28325 /* get mss number of strings, only using the last one seen */
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 */
28333 /* get number of segments for this string. not one, not handled */
28336 for (j
= 0; j
< nseg
; j
++)
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) ) {
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
] = '-';
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
28387 atsc_parse_mss_eit ( unsigned char *r
, unsigned int pgo
, unsigned int etmid
,
28388 unsigned int est
, unsigned int els
)
28391 unsigned int nstr
, nseg
, comp
, mode
, nchr
, sr
, i
, j
;
28405 for (i
= 0; i
< nstr
; i
++)
28407 memcpy(lang
, &r
[1], 3);
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
++)
28422 t
[0] = 0; /* need a blank if nothing done */
28424 /* no compression, unicode page 0 */
28425 if ((comp
== 0) && (mode
== 0) && (nchr
> 0) ) {
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.
28458 e
->etmid
= etmid
; /* event text message id */
28459 e
->st
= est
; /* event start time */
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 */
28475 dvrlog( LOG_INFO
, "pgo %04X time %u etmid %08X %s", pgo
, est
, etmid
, s
);
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 */
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
;
28500 e
->va
= 0; /* first audio only */
28504 /* not needed unless nseg > 1, which is never is, so far
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 */
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
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
;
28542 epg
[pgo
].rating
[0] = 0;
28544 while ( dlen
> 0 ) {
28549 /* r points to start of descriptor data */
28552 /* useful? yes, to waste space in program guide */
28558 /* MPEG video descriptor. Probably not used in EIT, but could be
28559 used for MPEG parameters to determine SD or HD or widescreen.
28562 dvrlog( LOG_INFO
, "EIT %02X Event %d %s has MPEG desc?",
28563 eitn
, evn
, epg
[pgo
].name
);
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.
28584 /* Caption service descriptor. This seems to actually work per EIT event. */
28586 if ( 0xC0 == (0xC0 & r
[0]) ) { /* reserved bits check */
28587 unsigned char nos
, cct
, l21f
, csn
, er
, war
;
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);
28602 /* see if it's EIA-708-A ATVCC */
28604 /* add CC to description if ATVCC type found */
28607 /* don't care about EIA/CEA-608-B. that's NTSC */
28608 if ( 0x3E == (0x3E & q
[0]) ) { /* reserved */
28612 if ( (0x3F == (0x3F & q
[1])) && (0xFF == q
[2]) ) {
28613 er
= 1 & (q
[1]>>7);
28614 war
= 1 & (q
[1]>>6);
28622 /* content advisory descriptor */
28624 q
= r
; /* dont want to change r */
28625 if ( 0xC0 == (0xC0 & q
[0]) ) {
28626 unsigned char rrc
, rr
, rd
, rdj
, rv
, rdl
;
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 */
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 */
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 */
28659 /* add rr text to epg[].rating */
28660 atsc_test_eit_rr_mss( q
, pgo
);
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.
28671 dvrlog( LOG_INFO
, "Ch %02X EIT-%02X has RC", cap_chan
, eitn
);
28674 /* needs parsing if shows up here */
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
28700 atsc_parse_eit ( unsigned char *p
, unsigned int n
)
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 */
28716 time_t est
; /* event start time */
28719 if (NULL
== eit
) return; /* prevent [l] [p] [enter] crash */
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;
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
;
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? */
28760 /* reserved bits wrong */
28768 atsc_strict_log( r
, t
);
28773 /* 12 bit section length */
28775 sl
= (0xF & *r
) << 8;
28779 eit_crc
= calc_crc32( eit
[n
].payload
, sl
+3);
28781 if (eit_crc
!= 0) {
28783 advrlog( LOG_INFO
, "%s bad CRC", t
);
28784 pkt
.feit
= PAY_DROP
;
28788 /* 16 bit source id, used to compute etmid and to check versions */
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;
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 */
28805 v
= find_vc_src( sr
); /* find vc from src pgm */
28807 dvrlog( LOG_INFO
, "%s EIT-%02X can't find vc for src %04X",
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;
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
);
28832 if (sl
> mg
[ mi
].nb1
)
28836 eit_vn
[n
][v
% VCZ
] = vn
;
28838 pkt
.feit
= PAY_PARSE
; /* set indicator to parse */
28841 s
= &ptc
[cap_chan
].sig
;
28846 if ( -1 < cap_vc ) {
28847 if ( CAP_TIME == cap_now ) return;
28848 if ( CAP_USER == cap_now ) return;
28852 cni
= 1 & *r
; /* current next indicator */
28854 /* multiple sections are supported by the spec but not by this code */
28856 sn
= *r
; /* section number */
28858 lsn
= *r
; /* last section number */
28862 if (pv
!= 0) return ; /* protocol version 0 only */
28865 numevs
= *r
; /* number of events in section */
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
);
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]))
28893 eid
= (0x3F & *r
) << 8; /* Event ID */
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 */
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] */
28925 /* recalibrate for unix time */
28928 n
&= 0x7f; /* sanity limit eit-n */
28930 v
= find_vc_src( sr
); /* sr already set above */
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
);
28955 /* events have additional descriptors if these reserved bits set */
28956 if ( 0xF0 == (0xF0 & r
[0]) )
28959 dlen
= ((0xF & r
[0])<<8) | r
[1];
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
);
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 */
28989 /***************************************************************** ATSC ETM MSS
28990 A/65b Table 6.38 Multiple String Structure
28991 does Huffman decomp
28995 atsc_parse_channel_etm_mss ( unsigned char *r
)
28997 unsigned int nstr
, nseg
, comp
, mode
, nchr
;
29003 return; /* no point in wasting cpu cycles on unused data */
29007 memset( t
, 0, sizeof(t
));
29009 for (i
= 0; i
< nstr
; i
++) {
29010 memcpy(lang
, &r
[1], 3);
29014 for (j
= 0; j
< nseg
; j
++) {
29019 if ( (0 == comp
) && (0 == mode
) ) {
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.
29047 atsc_parse_channel_ett ( unsigned char *p
)
29050 unsigned int sl
, idx
, vn
, etmid
, pid
;
29053 ctt
.payok
= atsc_build_payload( &ctt
, p
, "PTCETT");
29054 if (0 != ctt
.payok
) return;
29061 pid
= 0x1FFF & ( (p
[1] << 8) | p
[2] );
29063 /* all must be valid or nothing done */
29065 ( 0xF0 != (0xF0 & r
[1]) )
29066 || ( 0xC1 != (0xC1 & r
[5]) )
29067 || ( 0x00 != r
[8] )
29071 /* reserved bits wrong */
29073 atsc_strict_log( r
, "PTCETT bad");
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 );
29095 if (sl
> mg
[ mi
].nb1
)
29099 /* rest is unused because some stations put really long useless text in */
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
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
;
29123 if (0xE0 != (0xE0 & r
[0])) return; /* reserved bits */
29124 pcrpid
= ((0x1F & r
[0]) << 8) | r
[1];
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
;
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];
29146 memcpy( eslang
, 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
));
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];
29166 memcpy( eslang
, r
, 3);
29169 if (0x81 != est
) continue; /* skip video done above */
29171 vc
[n
].estype
[j
] = est
;
29172 vc
[n
].espid
[j
] = espid
;
29173 memcpy( &vc
[n
].eslang
[j
][0], eslang
, sizeof(eslang
));
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];
29187 memcpy( eslang
, 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
));
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.
29209 atsc_parse_mss_vct ( unsigned char *r
)
29211 unsigned int nstr
, nseg
, comp
, mode
, nchr
;
29216 /* return; */ /* see build vct text for where this is parsed */
29218 memset( t
, 0, sizeof(t
) );
29220 for (i
= 0; i
< nstr
; i
++) {
29221 memcpy(lang
, &r
[1], 3);
29225 for (j
= 0; j
< nseg
; j
++ ) {
29232 if ( (0 == comp
) && (0 == mode
) && (nchr
> 0) ) {
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
);
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.
29265 atsc_parse_vct_descriptor ( unsigned char *r
, unsigned int n
, unsigned int len
)
29267 unsigned char dtag
, dlen
;
29271 while ( len
> 0 ) {
29277 /* Extended Channel Name descriptor is multiple string structure */
29279 /* atsc_parse_mss_vct( r ); // no useful information, is nop */
29282 /* service location descriptor, equivalent to MPEG PMT with ES PIDs */
29284 atsc_parse_vct_svloc( r
, n
);
29287 /* time shifted service descriptor, log if seen but still ignores it */
29289 dvrlog( LOG_INFO
, "TSSD ignored");
29292 /* ignore anything else. see table of where descriptors are allowed */
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?
29312 atsc_parse_vct ( unsigned char *p
, int tn
)
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
;
29324 struct tsid_s
*tsn
;
29328 if (ATSC_CVCT
== tn
) {
29340 #ifdef USE_MPEG_ONLY
29341 #warning using MPEG ONLY --- NO ATSC PSIP
29342 return; /* no ATSC guide with this */
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;
29356 if (ATSC_TVCT
== tn
) {
29357 /* TVCT table ID C8, protocol version 0 */
29358 if ( (ATSC_TVCT
!= r
[0]) || (0x00 != r
[8]) ) terr
= ~0;
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 */
29375 /* reserved bits wrong */
29377 advrlog( LOG_INFO
, "%s:%d %s reserved bits not set", WHO
, __LINE__
, t
);
29378 atsc_strict_log( r
, t
);
29380 return; /* check reserved bits saves time */
29384 sl
= (0xF & *r
) << 8;
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) {
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 */
29400 tsi0
= *r
<< 8; /* transport stream id */
29405 vn
= 0x1F & (*r
>> 1); /* version number limits processing */
29408 if (vn
== vct
.vn
) { /* version hasn't changed */
29409 pkt
.fvct
= PAY_GOOD
;
29411 /* pkt.tvct++; */ /* only count new ones */
29415 /* set a bit to indicate channel has VCT */
29416 s
= &ptc
[ cap_chan
].sig
;
29420 mi
= atsc_find_mg_tt( c
);
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 */
29435 sn
= *r
; /* section number */
29437 lsn
= *r
; /* last section number */
29439 pv
= *r
; /* protocol version */
29440 if (pv
!= 0) return; /* only supporting protocol 0 */
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
) {
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.
29466 for (j
= 0; j
< vct_ncs
; j
++)
29469 unsigned char mode
;
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",
29483 /* pmt may have to check for non-zero instead of here */
29486 /* 7 shorts of UTF-16 big endian, but only want bottom 8 bits */
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 */
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 */
29516 for (k
=0; k
< 7; k
++) {
29517 if (0 == isalnum(chan_name
[k
]))
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 );
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;
29545 major
|= 0x3F & (*r
>>2); /* major channel number 10 bits */
29546 s
->major
= major
; /* update station list */
29548 minor
= (3 & *r
) << 8;
29550 minor
|= *r
; /* minor channel number 10 bits */
29553 mode
= *r
; /* modulation mode 8 bits */
29562 freq
|= *r
; /* frequency 32 bits (obsoleted) */
29567 tsi1
|= *r
; /* transport stream ID 16 bits */
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
];
29585 if (0 != has_name
) {
29586 snprintf( tsn
->call
, 8, "%s", n
);
29588 snprintf( tsn
->call
, 8, "TS-%04X", tsi1
);
29590 snprintf( tsn
->sid
, 8, "%03d", s
->ptc
);
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
);
29602 dvrlog( LOG_INFO
, "VCT/PAT Program mismatch %s", WHO
);
29603 return; /* nothing left to do if it's too broken */
29608 /* force vct to set the order for vc[]. parse pat/pmt will have to adjust */
29611 vc
[i
].major
= major
;
29612 vc
[i
].minor
= minor
;
29614 vc
[i
].freq
= freq
; /* obsoleted */
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 */
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 */
29637 vc
[i
].svct
= 0x3F & *r
; /* service type */
29640 sr
= *r
<< 8; /* KPXB has large killer source id */
29647 vc
[i
].vcdlen
= (3 & *r
) << 8;
29649 vc
[i
].vcdlen
|= *r
; /* descriptor length */
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 */
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? */
29675 /* not doing anything with add'l descriptors, shouldn't bother */
29676 for ( j
= 0; j
< adl
; j
++) {
29677 asnprintf( t
, 80, " %02X", *r
);
29680 dvrlog( LOG_INFO
, "Add'l hex:%s", t
);
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
);
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.
29714 atsc_parse_mgt ( unsigned char *p
)
29718 unsigned short sl
, tidx
, td
, dl
, i
, j
, pid
;
29719 unsigned char vn
, cni
, sn
, lsn
, pv
;
29722 struct mg_s
*m1
, *m2
;
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;
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 */
29753 /* reserved bits wrong */
29755 atsc_strict_log( r
, "MGT bad" );
29757 return; /* check reserved bits saves time */
29760 /* 12 bit section length */
29762 sl
= (0xF & *r
) << 8;
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) {
29773 advrlog( LOG_INFO
, "MGT bad CRC");
29774 pkt
.fmgt
= PAY_DROP
;
29781 tidx
|= *r
; /* table id extension / 0 */
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 */
29795 /* refresh the mgt in case user is in mgt display */
29798 s
= &ptc
[ cap_chan
].sig
;
29800 s
->psip
|= 1; /* bit 0 is mgt flag */
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... */
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;
29819 /* Show KHCW breakage. User should know that the station is goofy. */
29820 /* init_mg(); */ /* clear master guide structs */
29823 reset_atsc(); /* init atsc allocs, reset clears */
29824 init_pgm(); /* clear program guide */
29826 pkt
.fmgt
= PAY_PARSE
;
29829 cni
= 1 & *r
; /* current/next indicator */
29832 sn
= *r
; /* 8 bit section number */
29835 lsn
= *r
; /* 8 bit last section number */
29838 pv
= *r
; /* 8 bit protocol version */
29839 if (pv
!= 0) return; /* should always be zero */
29842 td
= *r
<< 8; /* 16 bit tables defined */
29852 for (i
=0; i
<td
; i
++)
29855 unsigned short ttpid
;
29856 unsigned char ttvn
;
29857 unsigned short ttdl
;
29860 if ( ( 0xE0 != (0xE0 & r
[2]))
29861 || ( 0xE0 != (0xE0 & r
[4]))
29862 || ( 0xF0 != (0xF0 & r
[9]))
29867 tt
|= *r
; /* table type */
29870 ttpid
= (0x1F & *r
) << 8;
29872 ttpid
|= *r
; /* table type PID */
29875 ttvn
= 0x1F & *r
; /* table type version number */
29884 nb
|= *r
; /* number of bytes in table */
29887 ttdl
= (0xF & *r
) << 8;
29889 ttdl
|= *r
; /* table type descriptor length */
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
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
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) ) {
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];
29945 /* one descriptor starts immediately following length */
29948 /* not parsing mgt descriptors */
29949 /* test_mpeg2_registration_descriptor( r, dl ); */
29953 pkt
.fmgt
= PAY_GOOD
;
29956 /* test for MGT table by PIDs to get table type, but not 1FFB ATSC System */
29959 atsc_test_mg_ttpid ( unsigned char *p
, unsigned int pid
)
29962 unsigned int tt
, ttidx
;
29964 tei
= 1 & (p
[1]>>7);
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
29972 if ( pid
== mgp
[ 0x1FFF & pid
].pid
) ttidx
= pid
;
29973 if (ttidx
== -1) return ~0;
29974 tt
= mgp
[ 0x1FFF & pid
].tt
; /* get table type */
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;
29997 /* Channel Extended Text Table isn't usually on PID 1FFB but it can be */
29999 atsc_parse_channel_ett( p
);
30003 /* Event Information Table */
30004 if ( (tt
>= 0x100) && (tt
<= 0x17F) ) {
30005 atsc_parse_eit( p
, 0x7F & tt
);
30009 /* Extended Text Table */
30010 if ( (tt
>= 0x200) && (tt
<= 0x27F) ) {
30011 atsc_parse_ett( p
, 0x7F & tt
);
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
);
30021 /* Directed Channel Change Table */
30022 if ( (tt
>= 0x1400) && (tt
<= 0x14FF) ) {
30023 atsc_test_dcct( p
);
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.
30042 atsc_parse_stt ( unsigned char *p
)
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 */
30054 avgdiff
= terr
= 0;
30056 pkt
.fstt
= PAY_READ
;
30057 stt
.payok
= atsc_build_payload( &stt
, p
, "STT");
30058 if (0 != stt
.payok
) return;
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 */
30078 /* reserved bits wrong */
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) {
30088 advrlog( LOG_INFO
, "STT bad CRC");
30092 /* version always 0 */
30093 pkt
.fstt
= PAY_GOOD
; /* status indicator */
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 */
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 */
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 */
30171 settimeofday( &stt_sys
, NULL
);
30173 /* update utsnow and tloc */
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 */
30187 atsc_test_table ( unsigned char *p
)
30193 psi
= 1 & (p
[1]>>6);
30195 /* PSI 1 means it's the start of the payload */
30198 pkt
.atsctid
= p
[5]; /* save table id of payload start */
30199 switch( pkt
.atsctid
) {
30201 /* System Time Table */
30203 atsc_parse_stt( p
);
30206 /* Master Guide Table */
30208 atsc_parse_mgt( p
);
30211 /* Terrestrial Virtual Channel Table */
30213 atsc_parse_vct( p
, ATSC_TVCT
);
30216 /* Cable Virtual Channel Table */
30217 /* only difference from TVCT is path_select and out-of-band bits for CVCT */
30219 atsc_parse_vct( p
, ATSC_CVCT
);
30222 /* Rating Region Table: count these and use for QoS */
30224 atsc_test_rrt( p
, 1 );
30227 /* Directed Channel Change Table: unused and uncounted */
30229 atsc_test_dcct( p
);
30232 /* Directed Channel Change Selection Code Table: unused and uncounted */
30234 atsc_test_dccsct( p
);
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.
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 */
30258 atsc_parse_stt( p
);
30262 atsc_parse_mgt( p
);
30265 /* KPXB has multi-packet VCT */
30267 atsc_parse_vct( p
, ATSC_TVCT
);
30270 /* cable virtual channel table */
30272 atsc_parse_vct( p
, ATSC_CVCT
);
30275 /* counted for QoS, using built-in RRT */
30277 atsc_test_rrt( p
, 1 );
30280 /* directed channel change table */
30282 atsc_test_dcct( p
);
30285 /* directed channel change selection code table */
30287 atsc_test_dccsct( p
);
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
30311 atsc_parse_adaptation_field ( unsigned char *p
)
30313 /* NOTE: These flags not used here. They are skipped during capture parse. */
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 */
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
30366 /* program clock reference, main mpeg system clock */
30368 if (0x7E == (0x7E & r
[4])) { /* bad reserved should abort? */
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? */
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 */
30400 sc
= *r
++; /* 8 bit signed */
30402 /* transport private data, skips and does not keep private data bytes */
30406 for (i
= 0; i
< tpdl
; i
++) pdb
= *r
++;
30408 /* adaptation field extension */
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 */
30418 ltwv
= 1 & (*r
>> 7); /* 1 bit ltw valid */
30419 ltwo
= (0x7F & *r
++) << 8; /* 15 bit ltw offset */
30420 ltwo
|= 0xFF & *r
++;
30423 if (0xC0 == (0xC0 & *r
)) { /* reserved */
30424 pwr
= 0x3F & *r
++; /* 22 bit piecewise rate */
30425 pwr
|= (*r
++) << 16;
30426 pwr
|= (*r
++) << 8;
30432 /* seamless splice, DTS next access unit */
30434 if ( (1 == (1 & r
[0])) /* marker bits */
30435 || (1 == (1 & r
[2]))
30436 || (1 == (1 & r
[4]))
30440 st
= 0xF & (*r
>>4); /* blech spec mess */
30442 /* dts next access unit */
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);
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
);
30475 pr
= opcrb
% 90000;
30476 pw
= opcrb
/ 90000;
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
);
30487 pr
= dtsnau
% 90000;
30488 pw
= dtsnau
/ 90000;
30491 pm
= (pw
/ 60 % 60);
30492 ph
= (pw
/ 3600 % 24);
30494 fprintf( stdout
, "\n DTS NAU %02d:%02d:%02d.%05d",
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]?
30507 /***************************************************************** MPEG2 */
30511 /****************************************************** TOP LEVEL TRANSPORT
30512 check transport packet for errors and route packet to function
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;
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 */
30551 /* transport scramble control non zero is counted as error */
30553 /* single vc cap only counts scramble if it matches Vid or Aud PID */
30555 if ((pid
== cap_pids
[2]) || (pid
== cap_pids
[3]))
30558 /* full cap counts all the scrambled packets */
30561 /* don't try to parse this packet */
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) {
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 */
30580 // if (0 != psi) advrlog( LOG_INFO, "%s PS1 PID %04X", WHO, pid);
30582 /* clear adaptation field length */
30585 /* adaptation field only, no payload */
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
30598 if (0 != pkt
.afl
) {
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 */
30634 if (cc
!= last_cc
[pid
]) {
30636 /* cc error total: full cap counts all cc errors */
30641 /* single vc cap increments cc errtot if pid is member of cap pids */
30642 if (0 == find_cap_pid(pid
))
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 */
30657 /* single vc cap increments cc err tot if pid is member of cap pids */
30658 if (0 == find_cap_pid(pid
))
30666 /* for next time around */
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
);
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
))
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
30701 atsc_test_mpeg2( p
, pid
);
30703 if (0 == find_cap_pid( pid
))
30704 atsc_test_mpeg2( p
, pid
);
30708 atsc_test_mpeg2( p
, pid
);
30713 /* called in place of test packet to total stats immediately following */
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
;
30722 pkt
.crcats
= pkt
.crcett
+ pkt
.crceit
+ pkt
.crcmgt
30723 + pkt
.crctvct
+ pkt
.crccvct
+ pkt
.crcstt
+ pkt
.crcrrt
;
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 */
30743 if (pkt
.esec
< 30) {
30744 sec_good
++; /* error count less than frame rate may be watchable */
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
30772 atsc_test_sync_block ( unsigned char *p
)
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 */
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
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 */
30814 /* back to outer loop to look for sync bytes */
30819 /* stop outer loop if inner loop finished block */
30820 if (j
>= (TSBUFZ
-TSPZ
) ) break;
30822 /* outer loop continues where sync was expected */
30824 /* falls through to continue with outer loop */
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 */
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 */
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.
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
30862 read non-zero offset number of bytes in to align stream
30863 loop back to read 21 packets until tries exceeds count
30867 atsc_get_sync ( int count
)
30869 int len
, tries
, errs
, aligns
, skips
, syncs
;
30872 len
= tries
= errs
= aligns
= skips
= syncs
= 0;
30875 /* simulate tupari reported bug. turns out it needs time to fill the fifo */
30876 while(1) nanosleep( &console_read_sleep
, NULL
);
30879 /* dummy mode doesn't need sync check */
30880 if (test_mode
!= 0) {
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",
30898 pkt
.count
= 0; /* use this for error checking below */
30899 atsc_test_sync_block( ts_buf
); /* 21 packet sync check */
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 */
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
) {
30929 "SYN%2d: block %d: align read %d, not %d",
30930 cap_chan
, tries
, len
, pkt
.align
);
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 */
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);
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.
30960 write_pkt_block ( unsigned char *p
)
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
;
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
) {
30978 strerror_r( errno
, e
, sizeof(e
) );
30980 snprintf( e
, sizeof(e
), "volume full" );
30982 dvrlog( LOG_INFO
, "%s stops capture '%s'", WHO
, e
);
30985 cap_fail
= FAIL_NOSPC
;
30986 cap_now
= CAP_NONE
;
31001 /* DVB turn streaming on, mandatory for DVB */
31002 if ( (0 == test_mode
) && (0 == arg_dummy
) ) {
31003 ir
= ioctl( dmx_file
, DMX_START
, NULL
);
31005 advrlog( LOG_INFO
, "DMX START\n" );
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
);
31023 /* DVB stream off, mandatory for DVB */
31024 if ( (0 == test_mode
) && (0 == arg_dummy
) ) {
31025 ir
= ioctl( dmx_file
, DMX_STOP
, NULL
);
31027 advrlog( LOG_INFO
, "DMX STOP");
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
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
);
31049 /* read the current fifo byte count. mutex wait if it's being modified */
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? */
31059 pthread_mutex_lock( &fifo
->mutex
);
31060 count
= fifo
->filled
;
31061 pthread_mutex_unlock( &fifo
->mutex
);
31064 return fifo
->filled
;
31068 /* lock fifo and decrement the fifo byte count */
31071 fifo_atomic_sub ( struct fifo_s
*fifo
, size_t count
)
31073 /* while trylock sleep method */
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 */
31088 fifo_atomic_add ( struct fifo_s
*fifo
, size_t count
)
31090 /* while trylock sleep method */
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 */
31106 fifo_reset ( struct fifo_s
*fifo
)
31108 if (NULL
== fifo
) {
31109 dvrlog( LOG_INFO
, "FIFO structure not allocated?");
31113 if (NULL
== fifo
->buffer
) {
31114 dvrlog( LOG_INFO
, "FIFO init didn't allocate buffer?");
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. */
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");
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");
31153 fifo
->buffer
= fifo_buffer
; /* static fifo */
31154 memset( fifo_buffer
, 0, fifoz
);
31156 /* clear the FIFO buffer */
31157 fifo_reset( fifo
);
31160 /* frees the FIFO buffer allocated above */
31163 free_fifo ( struct fifo_s
*fifo
)
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");
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. */
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
;
31207 /* sanity checks */
31208 if (count
< 1) return 0;
31209 if (NULL
== fifo
) {
31210 dvrlog( LOG_INFO
, "%s fifo is NULL!", WHO
);
31213 if (NULL
== fifo
->buffer
) {
31214 dvrlog( LOG_INFO
, "%s fifo->buffer is NULL!", WHO
);
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 */
31228 /* if FIFO is half full or more, force yield to starving drain */
31229 if ( fifo
->filled
> (fifoz
>> 1) ) {
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) ) {
31237 nanosleep( &dummy_read_sleep
, NULL
);
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
);
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;
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 */
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 */
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 */
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
;
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 */
31316 /* read count bytes from fifo into buffer */
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
);
31331 if (NULL
== fifo
->buffer
) {
31332 dvrlog( LOG_INFO
, "%s fifo->buffer is NULL!", WHO
);
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 */
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 */
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
;
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 */
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
31397 set_pthread_sched ( struct sched_param
*sp
, int pol
, int prio
, char *name
)
31400 char policy_text
[4][8] = { "OTHER", "FIFO", "RR", "SNAFU" };
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 );
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)",
31421 (int)pthread_self(),
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",
31431 (int)pthread_self(),
31435 return; /* not fatal but may cause problems with buffer overruns */
31438 advrlog( LOG_INFO
, "%s pthread pid %d tid %d %s pri %d",
31441 (int)pthread_self(),
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.
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
);
31469 /* read from the device and write to the fifo until cap_now resets */
31471 fifo_input_loop ( void * arg
)
31478 advrlog( LOG_INFO
, "%s pid_i %d detached", WHO
, pid_i
);
31481 /* disable control c */
31485 // memset( &fifo_input_param, 0, sizeof( fifo_input_param ) );
31486 set_pthread_sched( &fifo_input_param
, SCHED_FIFO
, 1, "in ");
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 */
31507 memcpy( &tloc3
, &tloc
, sizeof(tloc3
) );
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
,
31521 stop_capture( "no syncs align", FAIL_TSYNC
);
31523 if (pkt
.synerr
!= 0) {
31524 dvrlog( LOG_ERR
, "%s sync error [%d], giving up", WHO
,
31526 stop_capture( "sync error", FAIL_TSYNC
);
31528 /* converse of above test is sync_align == 0 && sync_errors == 0 */
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
31539 struct timeval fdtv
;
31542 fdtv
.tv_usec
= 100; /* 1 ms, about 12.9 packets */
31544 FD_SET( in_file
, &fds
);
31545 ok
= select( in_file
+1, &fds
, NULL
, NULL
, &fdtv
);
31548 stop_capture( "select error", FAIL_IFIFO
);
31552 /* timeout should yield to drain */
31555 /* will be locking up without this sleep */
31556 nanosleep( &atomic_read_sleep
, NULL
);
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 */
31566 cap_fail
= FAIL_NONE
;
31567 dvrlog( LOG_INFO
, "%s got EOF on blocked device?",
31569 stop_capture( "fifo in EOF", FAIL_IFIFO
);
31577 /* ext2/ext3 large file deletes can cause EOVERFLOW */
31578 if ( EOVERFLOW
== errno
) {
31579 /* use SYNC error total counter for overflows */
31582 /* any error other than overflow will abort capture */
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
);
31592 /* dummy mode will get end of file instead of blocking */
31593 /* any error will signal end of file. is ok for replay */
31595 stop_capture( "fifo in: EOF", FAIL_NONE
);
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
) {
31604 if (( fifoz
- ts_fifo
.filled
) > len
)
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 */
31613 ok
= fifo_write( &ts_fifo
, ts_buf
, len
);
31615 dvrlog( LOG_INFO
, "%s fifo_write returns -1\n", WHO
);
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
31627 /* logging will make it worse if logging to same volume as capture */
31629 "%s fifo full? write len %d != rc %d",
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
);
31643 /* thread is done, go back to normal scheduling */
31644 set_pthread_sched( &fifo_input_param
, SCHED_OTHER
, 0, "in ");
31647 /* control c was disabled during capture, re-enable it */
31651 advrlog( LOG_INFO
, "%s pthread_exit, wrote %lld", WHO
, fwrbc
);
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] */
31662 fifo_output_loop ( void *arg
)
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");
31679 /* drain the fifo */
31682 /* will change to 0x47 if ok */
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
;
31689 dvrlog( LOG_INFO
, "%s fifo_read returns -1\n", WHO
);
31692 /* fifo has nothing to read? */
31694 /* input thread done? */
31696 advrlog( LOG_INFO
, "%s cap_now %d ok %d", WHO
, cap_now
, ok
);
31699 /* input thread not done, sleep a bit and try again for more data */
31700 nanosleep( &dummy_read_sleep
, NULL
);
31705 /* TESTME: does this ever happen? */
31707 dvrlog( LOG_INFO
, "fifo_read count %d is short", ok
);
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
) {
31723 p
= &out_buf
[0]; /* assume p is len TSPZ, single packet now */
31724 atsc_test_packet_stats(p
); /* statistical wrapper above ATSC layer */
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
) {
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.
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
);
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 */
31771 pthread_exit( NULL
);
31775 /********************************************************* DVB frontend init */
31776 /* /sys/class/dvb/ has list of dvb devices registered. count them */
31779 get_dvr_count( void )
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
);
31798 init_dvb_frontend ( int fe_fd
, struct dvb_frontend_parameters
*frontend
)
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");
31812 if ( FE_ATSC
!= fe_info
.type
) {
31813 dvrlog( LOG_INFO
, "FE is not ATSC\n");
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
));
31826 if (0 != (FE_CAN_QAM_256
& fe_info
.caps
)) {
31827 snprintf( &d
[strlen(d
)], 8, "QAM256 ");
31831 if (0 != (FE_CAN_QAM_128
& fe_info
.caps
)) {
31832 snprintf( &d
[strlen(d
)], 8, "QAM128 ");
31836 if (0 != (FE_CAN_QAM_64
& fe_info
.caps
)) {
31837 snprintf( &d
[strlen(d
)], 7, "QAM64 ");
31841 if (0 != (FE_CAN_QAM_32
& fe_info
.caps
)) {
31842 snprintf( &d
[strlen(d
)], 7, "QAM32 ");
31846 if (0 != (FE_CAN_QAM_16
& fe_info
.caps
)) {
31847 snprintf( &d
[strlen(d
)], 7, "QAM16 " );
31851 if (0 != (FE_CAN_16VSB
& fe_info
.caps
)) {
31852 snprintf( &d
[strlen(d
)], 7, "VSB16 " );
31856 if (0 != (FE_CAN_8VSB
& fe_info
.caps
)) {
31857 snprintf( &d
[strlen(d
)], 6, "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
;
31868 if ( 0 != (FE_CAN_8VSB
& fe_info
.caps
)) {
31869 frontend
->u
.vsb
.modulation
= VSB_8
;
31874 snprintf( fe_text2
, sizeof(fe_text2
), "FE using %s; supports: %s", m
, d
);
31875 snprintf( fe_text3
, sizeof(fe_text3
), "%s", m
);
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. */
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");
31904 advrlog( LOG_INFO
, "DMX SET PES FILTER 0x2000");
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.
31921 advrlog( LOG_INFO
, "%s %d", WHO
, dmx_file
);
31922 if (dmx_file
< 3) return;
31923 ok
= close( dmx_file
);
31925 dvrlog( LOG_INFO
, "%s close %s %d failed", WHO
, dmx_name
, ok
);
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
);
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.
31952 open_device ( char *caller
)
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",
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 */
31975 /* clear test mode, will set it if device can't be opened */
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
);
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 */
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
);
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? */
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
);
32040 dvrlog(LOG_INFO
, "DVR OPEN OK %d", dvr_file
);
32041 in_file
= dvr_file
; /* capture will use */
32044 /* return; */ /* fall through */
32046 } else { /* dvr_file <= 2 */
32047 dvrlog(LOG_INFO
, "DVR OPEN %s FAIL", dvr_name
);
32050 } else { /* 0 != init_dvb_filter */
32051 dvrlog( LOG_INFO
, "DMX FILTER FAIL" );
32054 } else { /* 0 != ioctl dmx file bufz */
32055 dvrlog( LOG_INFO
, "DMX BUFFER SIZE %d FAIL", TSBUFZ
);
32058 } else { /* dmx file not open */
32059 dvrlog( LOG_INFO
, "DMX OPEN %s FAIL", dmx_name
);
32062 } else { /* 0 != init_dvb_frontend */
32063 dvrlog( LOG_INFO
, "FE INIT %s FAIL", fe_name
);
32066 } else { /* fe file <= 2 */
32067 dvrlog( LOG_INFO
, "FE OPEN %s FAIL", fe_name
);
32072 /* make set_channel re-set the channel */
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
;
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
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
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)
32109 load config and save config do not process these flags
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
32122 build_outnames ( void )
32125 char *call
, *net
, n
[64];
32126 struct qtimer_s
*t
;
32128 s
= &ptc
[cap_chan
].sig
;
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
) {
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
);
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
);
32162 snprintf( out_name
, sizeof(out_name
)-1, "%s%s",
32163 arg_opath
, arg_ofile
);
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",
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
);
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 */
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
) {
32195 cap_vc
= find_vc_pgm( cap_pn
, WHO
);
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
);
32208 snprintf( n
, sizeof(n
)-1, "%s.ts", net
);
32211 snprintf( n
, sizeof(n
)-1, "%s.%d.ts",
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
);
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 */
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.
32272 /* if . dot flag, set cap pn and cap vc from .program number */
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
) {
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",
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
);
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 */
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 */
32330 *f2
= 0; /* truncate for name only */
32332 memcpy( &twd
[1], date_now
, 3);
32335 /* if @ flag, generate -mmdd-hhmm */
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 */
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
);
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
);
32381 chaos_loop ( struct sig_s
*s
)
32383 int i
, k
, c
, e
, aosb
;
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
);
32394 ns
.tv_nsec
= aos_delay
<< 20;
32398 get_signal_lock( s
);
32402 for (i
=1; i
<= AOS_MIN_LOOPS
; i
++) {
32404 if (0 != arg_cable
) {
32410 get_signal_lock( s
);
32411 k
= console_getch();
32413 nanosleep( &ns
, NULL
);
32416 asnprintf(m
, sizeof(m
), "%02d ", s
->strength
);
32417 aosb
+= s
->strength
;
32419 aprintf( stderr
, "\033[3;1H" BY
"AOS%02d %s" BN CEL
,
32422 // no, want to collect all points for inner loop
32423 if (s
->strength
< AOS_MIN_STRENGTH
) e
++;
32424 if (0 != arg_cable
) break;
32429 clock_gettime( clock_method
, &aos_stop
);
32430 time_diff( &aos_diff
, &aos_start
, &aos_stop
);
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
);
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.
32445 chaos ( struct sig_s
*s
)
32447 int i
, c
, e
, aos
, aosa
;
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
);
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
) ) {
32467 s
->strength
= aosa
;
32470 aprintf( stderr
, "\033[3;1H" BY
"AOS%02d" BN CEL
, s
->chan
); /* show */
32475 for (i
=1; i
<= AOS_MAX_LOOPS
; i
++) {
32477 aos
= chaos_loop( s
);
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;
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
) {
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
) {
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
);
32507 advrlog( LOG_INFO
, "AOS%02d ET: %d.%09d Avg %02d%%",
32508 s
->chan
, aos_diff
.tv_sec
, aos_diff
.tv_nsec
, aos
);
32515 /* ts capture calls this */
32518 init_globals ( void )
32520 scan_one
= ~0; /* channel lock */
32521 scan_sig
= 0; /* turn off signal scan/display */
32528 /* keep track of last cap mode for dump cap stats */
32529 cap_prev
= cap_now
;
32530 cap_pchn
= cap_chan
;
32533 cap_zap
= 0; /* no left over zaps */
32536 cap_fail
= 0; /* it will be set later, maybe */
32538 cap_etime
= 0; /* default is by capture type */
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 */
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 */
32554 outbc
= 0LL; /* output byte count */
32563 /* dump stat html, dump cap log, save epg */
32570 /* -m: set cap_frames to arg_frames in case out-of-frames resets cap_frames */
32571 cap_frames
= arg_frames
;
32574 /* clear event name and description; TODO? use t->ename/edesc instead? */
32581 /* save tallies from psi_pids, cce_pids and pids by walking the vc list */
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.
32603 ts_capture ( void )
32611 unsigned int aost
; /* strength average of 10 tries */
32612 struct stat64 fs
; /* checks for existing files */
32613 int tr
; /* thread return */
32618 struct udp_s
*u
= &udp
;
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 */
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",
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
;
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
;
32655 /* manual cap default, possibly changed by build outnames */
32660 /* reset the packet counts */
32663 /* display the usual stats */
32664 display_type
= D_TIMERS
;
32667 /* variable size FIFO when using dynamic allocation */
32670 if (CAP_INFO
!= cap_now
) fifoz
= FIFOZ_CAP
;
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
);
32682 /* Moved post volume check. Saves memory grab until enough to do something. */
32683 init_fifo( &ts_fifo
, fifoz
);
32688 if ( (CAP_USER
== cap_now
) && (-1 != u
->sock
) && (-1 != u
->bind
) ) {
32689 dvrlog( LOG_INFO
, "UDP MCAST %s", WHO
);
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
);
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
) {
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 */
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
;
32754 /* truncate filename for status */
32755 filebase( f
, out_name
, F_FILE
);
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
));
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
)) {
32778 /* update console in case startup jumps immediately to capture */
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 */
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 */
32798 if (0 != arg_capture
) {
32801 if (CAP_TIME
== cap_now
) {
32802 ets
= (timer
[timer_rec
].start
32803 + (timer
[timer_rec
].len
32804 - timer
[timer_rec
].secdt
))
32806 if (ets
< 0) ets
= 0;
32810 if (test_mode
!= 0) ct
= "TEST";
32812 /***************************** Acquisition Of Signal *************************/
32814 filebase( f
, out_name
, F_TFILE
);
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
;
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
);
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
) {
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
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 */
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 */
32890 /* statfs 0 ret means file exists and needs to be renamed */
32893 /* copy to delete filename and replace .ts with .$ */
32894 astrncpy( del_name
, out_name
, sizeof(del_name
) );
32896 b
= strrchr(del_name
, '.');
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
);
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
);
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 */
32949 tr
|= pthread_create( &fifo_input_thread
, &fifo_input_attr
,
32950 fifo_input_loop
, NULL
);
32953 advrlog( LOG_INFO
, "pthread create fifo input error %d", tr
);
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 */
32970 tr
|= pthread_create( &fifo_output_thread
, &fifo_output_attr
,
32971 fifo_output_loop
, NULL
);
32973 advrlog( LOG_INFO
, "pthread create fifo output error %d", tr
);
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
32989 show_status( 1, WHO
);
32991 /* look for exit or control (sets state flags) */
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
);
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
);
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
);
33018 /* until both threads are done */
33020 /************************* end of capture *******************************/
33022 refresh
.headers
= 1;
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 */
33039 advrlog( LOG_INFO
, "out_name %s closed %lld bytes",
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;
33060 /* base2-base10 (1023 != 999) rounding issue */
33062 estb
>>= 10; /* might see K for quick start/stop */
33065 estb
>>= 10; /* 1M to 999M uses M */
33067 if (estb
> 1023) { /* 1G and above uses G */
33068 estb
>>= 10; /* gigs */
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 */
33078 /* small delay to see it */
33079 nanosleep( &msg_sleep
, NULL
);
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 */
33092 /* save any new TSIDs from this capture. NO: find stations saves at end */
33094 if (otsidx
!= tsidx
)
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 */
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
);
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
));
33138 #ifdef USE_POWERDOWN
33139 /* clear the wakeup one-shot to auto-find the next time to wake up */
33143 /* redraw timers */
33144 last_pktc
= pkt
.count
;
33146 display_type
= D_TIMERS
;
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
;
33157 #ifdef USE_POWERDOWN
33159 utspdd
= utsnow
+ USE_POWERDELAY
;
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 */
33183 test_epgs_hold( char *caller
)
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? */
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
);
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
);
33231 /* nothing to do? */
33233 for (i
= 0; i
< scan_idx
; i
++ ) {
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
);
33254 /* the mutex is used to prevent multiple allocs but is it needed? */
33257 test_epgs ( char *caller
)
33259 int spgch
, spgmt
, fpgmt
;
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
);
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
);
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
);
33331 for (i
= 0; i
< scan_idx
; i
++ )
33333 nanosleep( &console_read_sleep
, NULL
); /* reduce CPU usage */
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 */
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
);
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 */
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 */
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
);
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 */
33378 build_pg_epg( epg2
, &pgm2
, pg2
, WHO
);
33379 dump_epg_html( epg2
, &pgm2
, pg2
, WHO
);
33383 /* now check binary version */
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
);
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) {
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 */
33422 /* move scan index to current capture channel so console interface follows */
33423 scan_pos
= get_scanlist_offset( ch
);
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
;
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
);
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",
33462 dvrlog( LOG_INFO
, "APG%02d %d = %s",
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 */
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
33483 t
= get_next_meridian();
33487 "APG%02d failed: retry in %dm",
33488 ch
, (t
- utsnow
) / 60 );
33490 /* don't trigger until next 3 hour meridian */
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");
33500 u
.modtime
= (time_t) s
->pgmt
;
33509 spgmt
= 0x7FFFFFFE;
33511 /* count backwards to find lowest channel with earliest pgmt first */
33512 for (i
= (scan_idx
- 1); i
>= 0; i
--) {
33514 s
= &ptc
[ ch
].sig
;
33515 if (0 == s
->pgto
) continue; /* only the active ones */
33516 if ( (s
->pgmt
> utsnow
) && (s
->pgmt
<= spgmt
) ) {
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();
33529 text_time( date_next
, utstpg
, 19 );
33530 dvrlog( LOG_INFO
, "APG%02d next autoguide %s", spgch
, date_next
);
33536 // TESTME is this needed here too? ts capture already set it...
33537 #ifdef USE_POWERDOWN
33538 utspdd
= utsnow
+ USE_POWERDELAY
;
33541 /* Track the date of the last matching spam event seen, */
33542 save_spamlist( WHO
);
33545 if (NULL
!= epg2
) ifree( epg2
, "test epg2" );
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.
33563 show_channels ( void )
33570 channel_idx
= scan_idx
;
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 */
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
) ) ) {
33595 i
= channel_offset
+ j
;
33596 if (i
>= scan_idx
) continue;
33599 ch
= scan_list
[scan_pos
];
33601 /* NOTE: everything uses cap_chan for current channel */
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;
33616 if (0 != arg_cable
) n
= 1;
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 */
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 */
33642 show_signal( s
, j
);
33644 // aprintf( stderr, SCV );
33646 for (i
=0; i
<16; i
++) {
33647 if (CAP_NONE
!= cap_now
) break;
33649 if (scan_next
> 0) break;
33650 nanosleep( &guide_loop_sleep
, NULL
);
33652 if (scan_next
== 0) continue;
33656 if (scan_next
> 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) {
33666 if (scan_one
== 0) j
--;
33667 if ( j
< 0 ) j
+= scan_idx
;
33671 /* break advances to next channel */
33672 if (scan_next
== -1) {
33680 /* channel jump, from hotkey [or signal?] */
33681 if (scan_next
> 0) {
33682 if (scan_next
<= scan_idx
) {
33688 /* it wasn't valid so ignore it */
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
) {
33702 s
->strength
= s
->str_avg
;
33704 /* console needs because arg extras sets different display ranges */
33705 if (0 != arg_extras
) {
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;
33722 // if (0 != scan_one) m = m + 1;
33723 if (CAP_NONE
!= cap_now
) break;
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;
33739 /* loop wrapper for show channels so it can return whenever */
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
) {
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 */
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";
33772 fprintf( stderr
, "\nnothing to write?\n");
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 */
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
,
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"
33795 "CHANNELS=\042%s\042\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"
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
);
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" );
33833 fprintf( stderr
, "no\n");
33839 /* station found list, one for each VC not scrambled w/ SD/HD video + audio */
33851 /* -S option to scan all frequencies for available digital channels */
33854 find_stations ( void )
33857 int f
, i
,j
, k
, m
, n
;
33859 int sigavg
, signum
, sigmax
;
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
;
33882 // char location[16];
33883 // int seconds = 60;
33889 memset(atimes
, 0, sizeof(atimes
));
33893 /* FIXME: limited by size of ptc[],
33894 but only seeing 38 unscrambled cable PMTs here with comcast
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
) );
33919 fprintf(stderr
, "\n DVB Device not open\n");
33926 refresh
.timers
= 0;
33930 #define HIC ptc_max
33935 /* ccast uhf hou range */
33939 /* bcast uhf hou range */
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;
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
);
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();
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
);
33990 fprintf( stderr
, SCP
);
33992 fprintf( stderr
, "\033[3;1H");
33994 /* force frequency change */
33997 sigavg
= signum
= 0;
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();
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
;
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
);
34031 fprintf( stderr
, RCP
) ;
34033 /* avoid divide by zero */
34034 if (signum
< 1) signum
= 1;
34037 /* 33% or greater */
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
);
34055 fprintf( stderr
, "\033[1;42H" BW
" but only saw TV!" BN
);
34056 fprintf( stderr
, "\033[2;1H\033[J" );
34060 "\nLCN PTC Status Time TSID Callsign "
34061 "Signal%% Error%% Errors Packets NULLs\n");
34069 clock_gettime( clock_method
, &scan_start
);
34071 for (i
=0; i
< scan_idx
; i
++) {
34074 fprintf( stderr
, BW
"\n\n\n recompile with larger SF_MAX\n");
34079 key
= console_getch(); /* control c check, user complaint */
34080 s
= &ptc
[scan_list
[i
]].sig
;
34082 fprintf( stderr
, "%3d %3d",
34085 memset( chan_name
, 0, sizeof(chan_name
) );
34087 cap_chan
= scan_list
[i
];
34088 pgm
.chan
= cap_chan
;
34092 fprintf( stderr
, " stream");
34100 /* make sure channel gets set */
34101 /* scan_freq = 0; */
34103 cap_now
= CAP_INFO
;
34106 /* TESTME: needed? */
34107 while (0 != (pid_o
| pid_i
)) {
34109 nanosleep(&console_read_sleep
, NULL
);
34110 // b = console_getch();
34115 /* Thanks to Peter Knaggs for finding this divide by zero crash. */
34116 if (0 == utsets
) utsets
= 1;
34118 /* ok to yammer now */
34120 aprintf( stderr
, " %3ds", utsets
);
34122 epc
= pkt
.errors
* 100;
34124 if (0 == pkt
.count
) pkt
.count
= 1; /* avoid /0 */
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");
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
) {
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);
34155 sf
[f
].ptc
= s
->chan
;
34156 sf
[f
].pn
= vc
[j
].pn
;
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 # */
34165 t
->tsid
= sf
[f
].tsid
;
34171 /* user may still have to edit callsign & sid in the config file */
34173 snprintf(t
->call
, 8, "%s", vc
[j
].name
);
34176 snprintf(t
->call
, 8, "%s", vc
[j
].cname
);
34178 /* fall back to tsid for callsign if no VCT or Component Name */
34180 snprintf(t
->call
, 8, "TS-%04X", t
->tsid
);
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
);
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
++)
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
] ] );
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");
34231 fprintf( stderr
, " %02d%%",
34232 (100 * pids
[ vc
[j
].espid
[k
] ])/pkt
.count
);
34239 fprintf(stderr
, "\n");
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;
34252 if (0 != pa
[j
].ca
) continue;
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
;
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 # */
34272 t
->tsid
= sf
[f
].tsid
;
34274 snprintf( t
->sid
, 4, "%03d", t
->ptc
);
34278 /* user may still have to edit callsign & sid in the config file */
34280 snprintf(t
->call
, 8, "%s", vc
[j
].name
);
34283 snprintf(t
->call
, 8, "%s", vc
[j
].cname
);
34285 /* fall back to tsid for callsign if no VCT or Component Name */
34287 snprintf(t
->call
, 8, "TS-%04X", t
->tsid
);
34290 if (tsidx
>= TSID_MAX
) {
34291 fprintf(stderr
, "\nTSID_MAX exceeded\n");
34296 tf
= find_tsidx( sf
[f
].tsid
, sf
[f
].pn
, WHO
);
34301 fprintf( stderr
, "%7s Pgm # %5d %04X ",
34302 "", pa
[j
].pn
, pa
[j
].tsid
);
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
]) {
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
] ] );
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");
34338 fprintf( stderr
, " %02d%%",
34339 (100 * pids
[ pa
[j
].espid
[k
] ])/pkt
.count
);
34346 fprintf( stderr
, "\n");
34350 fprintf( stderr
, "\n");
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");
34374 for (i
= 0; i
< 128; i
++) {
34375 if (atimes
[i
] < 1) continue;
34377 atime_avg
+= atimes
[i
];
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) {
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
) );
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
34415 fprintf(stderr
, "Only root can set system time.\n");
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";
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
;
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
);
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
);
34458 /* ask user to save config */
34459 fprintf( stderr
, "\nSave configuration" BW
" (y/n)? " BN
);
34461 if (0 == ui_save
) {
34466 /* ask user to save config */
34467 fprintf( stderr
,"\nEnable auto EPG for all stations" BW
" (y/n)? " BN
);
34470 /* most users will want the HD stations only */
34471 fprintf( stderr
, "\nKeep non-HDTV stations" BW
" (y/n)? " BN
);
34475 snprintf(cfg_name
, sizeof(cfg_name
), "atscap%d.conf", arg_devnum
);
34476 o
= fopen(cfg_name
, "w");
34478 fprintf(stderr
, CLS
"Can not open %s for write.\n", cfg_name
);
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 */
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
);
34494 fprintf( o
, "C%03d", t
->ptc
);
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
);
34504 fprintf( o
, ":%s", t
->sid
);
34505 fprintf( o
, ":%d:%u:0", ui_epg
, t
->pn
);
34506 fprintf( o
, " # HD\n" );
34511 /* populate SD if user wants to keep them */
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
);
34520 fprintf( o
, "C%03d", t
->ptc
);
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
);
34530 fprintf( o
, ":%s", t
->sid
);
34531 fprintf( o
, ":%d:%u:0", ui_epg
, t
->pn
);
34532 fprintf( o
, " # SD\n" );
34537 fprintf( o
, "\n" );
34544 /* user can stop it after save config to inspect the result */
34545 fprintf( stderr
, "\nRun %s now " BW
"(y/n)? " BN
, NAME
);
34548 close_device( WHO
);
34553 /* console exit is not needed anymore? */
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
);
34578 /* -u 224-239.x.x.x:1-65535
34579 bogus ip or port 0 disables multicast */
34582 parse_arg_mcast ( char *a
)
34588 struct in_addr mc_addr
;
34590 memset( arg_mcaddr
, 0, sizeof(arg_mcaddr
) );
34593 astrncpy( m
, a
, sizeof(m
) );
34594 t
= strrchr( m
, ':');
34596 dvrlog( LOG_INFO
, "MCAST UDP port missing" );
34602 if ( (p
< 1) || (p
> 65535) ) {
34603 dvrlog( LOG_INFO
, "MCAST UDP port invalid");
34606 if (strlen(m
) > 15) {
34607 dvrlog( LOG_INFO
, "MCAST IP address too long");
34610 t
= strchr( m
, '.' );
34612 dvrlog( LOG_INFO
, "MCAST IP address bad format");
34617 if ( (s
< 224) || (s
>239) ) {
34618 dvrlog( LOG_INFO
, "MCAST IP adddress error");
34624 ok
= inet_aton( m
, (struct in_addr
*)&mc_addr
);
34626 dvrlog( LOG_INFO
, "MCAST IP address bad");
34630 /* considered ok if gets to here */
34631 astrncpy( arg_mcaddr
, m
, sizeof(arg_mcaddr
) );
34640 /* This is a quick check of the new DST rules */
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
) );
34660 #ifdef USE_PARSE_ENV
34661 /* These are parsed before parse args to set defaults. CLI will override */
34671 parse_envars ( void )
34674 p
= getenv("DVR_PATH");
34676 strncpy( out_path
, p
, sizeof(out_path
));
34678 p
= getenv("DVR_HOST");
34680 strncpy( arg_whost
, p
, sizeof(arg_whost
));
34683 p
= getenv("DVR_PORT");
34685 arg_wport
= 0xFFFF & atoi(p
);
34688 p
= getenv("DVR_SORT");
34690 idx_sort
= 0xF & atoi(p
);
34693 /* TODO: unicast UDP */
34694 p
= getenv("DVR_UUDP");
34698 /* TODO: multicast UDP */
34699 p
= getenv("DVR_MUDP");
34703 /* TODO: FIFO size, faster machines can reduce alloc? */
34704 p
= getenv("DVR_FIFO");
34711 /* verify luser has access to things it will use. called by end of parse args
34715 test_luser ( void )
34723 char r
[256]; /* name of resource that failed */
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");
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);
34749 snprintf(r
, z
, "%s%d", NAME
, arg_devnum
);
34750 e
= "instance already running";
34754 d
= "/dev/dvb%d.%s";
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 */
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
);
34770 e
= "device doesn't exist";
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
);
34789 e
= "device in use";
34794 ok
= open(dmx_name
, O_RDWR
);
34795 aprintf( stderr
, "open %d %s\n", ok
, dmx_name
);
34798 e
= "device in use";
34803 ok
= open(dvr_name
, O_RDONLY
);
34804 aprintf( stderr
, "open %d %s\n", ok
, dvr_name
);
34807 e
= "device in use";
34811 aprintf( stderr
, "using dvb%d\n", arg_devnum
);
34813 /* TODO: add file permission check here for -r mode:
34818 // abbreviated check, device name setup only
34827 /* check for /etc/atscap/ directory writable */
34828 snprintf( r
, z
, "/etc/%s/%s%d.chk", NAME
, NAME
, arg_devnum
);
34830 if (NULL
== f
) { ok
= -1; e
= "can't write"; break; }
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
);
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
);
34849 if (NULL
== f
) { ok
= -1; e
= "can't write"; break; }
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
);
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; }
34869 if (ok
< 0) { e
= "delete"; break; }
34870 aprintf( stderr
, "capdir rwd %s\n", r
);
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; }
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
);
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
);
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
);
34906 ok
= mkdir( r
, 0777 );
34907 if (ok
< 0) { e
= "root needs to: mkdir"; break; }
34909 aprintf( stderr
, "cutdir exists %s\n", r
);
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; }
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; }
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; }
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
);
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");
34962 { ok
= -1; e
= "can't write"; break; }
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)) {
34977 e
= "TCP port must be > 1023 for luser";
34979 aprintf( stderr
, "TCP port BAD %d\n", arg_wport
);
34982 aprintf( stderr
, "TCP port %d OK\n", arg_wport
);
34987 break; /* one time through */
34991 aprintf( stderr
, "User mode may require renicing!\n");
34992 nanosleep( &msg_long_sleep
, NULL
);
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
);
35004 aprintf( stderr
, "Everything seems OK\n");
35008 nanosleep( &msg_longer_sleep
, NULL
);
35011 aprintf( stderr
, CLS
);
35018 parse_args ( int argc
, char ** argv
)
35025 astrncpy( arg_name
, argv
[0], sizeof(arg_name
) );
35027 #ifdef USE_PARSE_ENV
35032 while ( (c
= getopt( argc
, argv
,
35033 "ADENRSWadehklmnqtvxc:i:o:p:r:s:u:z:w:F:U:") )
35038 /* ATSC strict mode */
35043 /* start session detached with screen */
35046 arg_screen
= ~0; /* -D implies -W */
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.
35055 snprintf( ram_path
, sizeof(ram_path
), "%s", "ram/");
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",
35063 dvrlog( LOG_INFO
, "tmpfs only. Recompile with USE_POWERDOWN");
35068 /* toggle boiler-plate default. defaut is on to pause 3s */
35069 /* NOTE: it needs at least 3 seconds to load microccode */
35071 arg_nosplash
= ~arg_nosplash
;
35074 /* attach a previously running screen session */
35077 arg_screen
= ~0; /* -R implies -W */
35080 /* Station scan and config file build */
35081 /* TODO? could use this to set how long to scan each channel */
35086 /* frequency table select */
35088 if (NULL
!= optarg
) {
35089 arg_frtable
= atoi( optarg
);
35090 if (arg_frtable
> 0) arg_cable
= ~0;
35096 if (NULL
!= optarg
) {
35097 arg_setuid
= atoi(optarg
);
35098 t
= strchr(optarg
, ':');
35101 arg_setgid
= atoi(t
);
35111 /* keep nulls for hardware players */
35119 /* FIXME: needs device from parse arg mcast */
35120 if (NULL
!= optarg
) {
35121 if (0 == parse_arg_mcast( optarg
) ) {
35123 system( "route add -net 224.0.0.0 "
35124 "netmask 240.0.0.0 dev eth0");
35128 fprintf( stderr
, CLS
"Recompile with USE_MCAST\n");
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
35142 /* errors enumerated, increases size of atscap[0-3].log files */
35144 arg_edetail
= ~arg_edetail
;
35149 fprintf( stdout
, "%s%s", HOM
, CCE
);
35150 fprintf( stdout
, "%s%s", welcome
, BN
);
35151 fprintf( stdout
, "%s%s", boilerplate
, BN
);
35153 exit(0); /* version boiler-plate etc and stop */
35156 /* built-in help */
35161 /* TARD mode (Test And Replay Demo) */
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
);
35177 /* -a uses AOS check before capture, otherwise pretends AOS always passes */
35182 /* FIXME: use QoS overall percentage
35183 zap file if this percentage of error seconds crossed */
35186 if (NULL
!= optarg
) {
35187 arg_zapper
= atoi( optarg
);
35188 if ( (arg_zapper
< 0) || (arg_zapper
> 100) )
35189 arg_zapper
= 99; /* sanity limit */
35193 /* capture for n seconds */
35196 "-s is deprecated and no longer supported.\n");
35200 sscanf( optarg
, "%d", &arg_capture
);
35201 /* arbitrary 4 hour limit on capture */
35202 if ( (arg_capture
< 0) || (arg_capture
> 14400) )
35207 /* input device -i0...3 or /dev/dtv0...3 */
35209 arg_device
= optarg
;
35212 if ( arg_device
!= NULL
) arg_devnum
= atoi( arg_device
);
35217 arg_ofile
= optarg
;
35218 if (NULL
!= arg_ofile
)
35219 astrncpy( out_name
, optarg
, sizeof(out_name
) );
35224 arg_opath
= optarg
;
35226 if (NULL
== arg_opath
) break;
35227 if (strlen(arg_opath
) > 1) {
35230 strncpy( out_path
, arg_opath
, sizeof(out_path
));
35231 p
= out_path
+ (strlen(out_path
)-1);
35233 /* add / to end if missing */
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
));
35245 /* config file path */
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
) ? "/":"" );
35253 /* toggles for compiled defaults */
35255 /* filesystem ext2 delete time computation toggle */
35257 arg_fastfs
= ~arg_fastfs
;
35260 /* DEBUG parse weekday timers:
35261 no timers loaded from cfg at startup
35262 IT WILL ERASE EXISTING TIMERS SO BEEWARY
35265 arg_notimers
= ~arg_notimers
;
35268 /* klutz mode, stop fingers from stopping program */
35270 arg_nokb
= ~arg_nokb
;
35273 /* HD2000 LED sense for V4L ATSC using my customized pcHDTV driver 1.07 */
35274 /* also, read signal strength value during capture */
35276 arg_extras
= ~arg_extras
;
35279 /* verbose logging */
35281 arg_log
= ~arg_log
;
35286 arg_detach
= ~arg_detach
;
35287 scan_sig
= 0; /* user request: -d does not scan */
35290 /* quiet mode, no output, but input is allowed, for [q]uit key */
35292 arg_quiet
= ~arg_quiet
;
35297 if (NULL
!= optarg
) {
35298 parse_www_bind( optarg
);
35302 fprintf( stderr
, CLS
"Recompile with USE_WWW.\n\n");
35310 /* end of toggles for compiled defaults */
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
) {
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
) ) {
35338 "-s caps channel %d for %d seconds",
35339 chan
, arg_capture
);
35341 dvrlog( LOG_INFO
, "-s invalid channel %d", chan
);
35348 /* end of parse args */
35353 /* This is the tiny threaded http server code based on my tinyhttp.c 0.5 */
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.
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 */
35457 .extension, type name, val is category where 0 is cacheable reply */
35464 #define ALLOW_MAX 8
35465 #define ALLOW_NAME_MAX 128
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
35494 struct sigaction www_act2
[32]; /* only the first 32 signals */
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}; */
35504 int ha_mt
= 0; /* hosts.allow modtime */
35505 struct allow_s allow
[ ALLOW_MAX
];
35507 /*************************** httpd globals end ******************************/
35509 /************************* httpd functions begin ****************************/
35512 http_log ( int tnum
, char *fmt
, ... )
35516 char s
[ LOG_CHARS
]; /* , *n; */
35520 if (NULL
== w
) return;
35522 if ((tnum
< 0) || (tnum
> w
->ptmax
)) {
35523 dvrlog( LOG_INFO
, "%s thread number %d out of bounds");
35528 memset( s
, 0, sizeof(s
));
35530 /* Variable arg macro start, pass fmt and ap, variable arg macro end */
35532 vsnprintf( s
, sizeof(s
)-1, fmt
, 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:
35552 http_load_allows ( void )
35556 char fn
[256], line
[256], service
[32], ip
[128], *n
, *ok
;
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
);
35564 strcpy( fn
, "/etc/hosts.allow" );
35567 i
= stat( fn
, &fs
);
35569 /* Peter Knaggs reported this error if no hosts allow exists at all */
35570 advrlog( LOG_INFO
, "can't stat %s", fn
);
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");
35581 dvrlog( LOG_INFO
, "can't open %s", fn
);
35585 z
= strlen( NAME
);
35588 ok
= fgets( line
, sizeof(line
), f
);
35589 if (NULL
== ok
) break;
35592 /* strip comment */
35593 if (NULL
!= (n
= strchr(line
, '#'))) *n
= 0;
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
))
35607 advrlog( LOG_INFO
, "allowing access to %s", ip
);
35609 astrncpy( a
->name
, ip
, ALLOW_NAME_MAX
);
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
);
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:
35630 t current web server thread
35631 addr remote IP address to use for reverse DNS/hosts lookup
35634 0 if the remote is allowed to access this machine
35635 -1 if no access is allowed
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.
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
;
35652 /* LOCAL is implied */
35653 if (t
->local_addr
.sin_addr
.s_addr
== addr
->sin_addr
.s_addr
)
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
),
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
);
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
, '.');
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. */
35690 /* Test length is usually equal to length of test string, unless no dot. */
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
);
35699 ahttp_log( tnum
, "access allowed to addr %s", t
->remote_number
);
35703 /* Exact match will allow access. */
35704 ok
= strncasecmp( name
, test
, z
);
35706 ahttp_log( tnum
, "access allowed to name %s", t
->remote_name
);
35714 /* source, destination, mask, 0 is test passed, -1 is not passed */
35717 http_test_netmask ( unsigned int d
, unsigned int s
, unsigned int m
)
35719 if (0 == m
) return 0;
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
);
35731 advrlog( LOG_INFO
, "%08X %08X %08X ALLOW", d
, s
, m
);
35735 /* close accepted socket and log the close */
35738 http_close ( struct tts_s
*t
, int *s
, char *which
)
35740 /* indicates logic error, want this logged */
35742 http_log( t
->num
, "already closed %s", which
);
35746 ahttp_log( t
->num
, "close %s socket %d", which
, *s
);
35753 http_close_sockets( void )
35759 dvrlog( LOG_INFO
, "%s", WHO
);
35762 /* shutdown thread sockets */
35763 for (i
=0; i
< w
->ptmax
; i
++) {
35766 if (t
->accepted
> 2) {
35767 http_log( t
->num
, "closing thread socket %d",
35769 http_close( t
, &t
->accepted
, "abort");
35772 nanosleep(&console_read_sleep
, NULL
);
35775 /* shutdown main socket */
35777 http_log( 0, "closing main socket %d", w
->sock
);
35783 /* bind socket to port, 80 usually */
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
);
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");
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
);
35811 w
->bind_addr
.sin_addr
.s_addr
= htonl(w
->addr
);
35813 memset(&w
->bind_addr
.sin_zero
, 0, 8);
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
);
35826 nanosleep( &www_long_sleep
, NULL
);
35828 if (w
->bind_try
> 30) {
35830 /* http_c_exit(2); */
35834 if (-1 == w
->bound
) {
35835 dvrlog( LOG_INFO
, "bind failed");
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;
35864 http_socket_listen( struct wss_s
*w
, int backlog
)
35868 if ( -1 == ( listen( w
->sock
, backlog
)) ) {
35869 http_log( 0, "listen() failed");
35871 /* http_c_exit(3); / * listen error returns 3 */
35877 /* Peter Knaggs requested sort by file date */
35878 /* if legend visible. uses headers above filenames to set sort order */
35881 http_sort_index ( struct globsort_s
*g
, int count
)
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
++) {
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? */
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.
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 */
35950 char *globa
, /* glob wildcard match in ascii */
35953 *ur
, /* uri:// filler as mpeg:// */
35955 *st
, /* sort type bitmask */
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 */
35973 int i
, ok
, /* counter and reusable return code */
35975 gc
, /* total globs returned */
35976 fg
, /* files globbed for sorting */
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;
35998 t
= &w
->tss
[ tnum
];
36002 globa
= "*.ts"; /* glob all .ts files */
36005 /* media type include list by .ts extension */
36006 astrncpy( o
, t
->cwd
, sizeof(o
) );
36011 p
= strrchr( o
, '/' );
36014 p
= strrchr(o
, '/');
36022 astrncpy( o
, t
->cwd
, sizeof(o
) );
36024 snprintf( d
, sizeof(d
), "%s%s", w
->root
, t
->cwd
);
36029 dvrlog( LOG_INFO
, "%s %s t%d chdir %d %s %s %s",
36030 WHO
, caller
, tnum
, ok
, d
, w
->root
, t
->cwd
);
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 */
36041 if (0 == (S_IWUSR
& fs
.st_mode
)) {
36042 /* serve current file */
36043 dvrlog( LOG_INFO
, "user can't update %s", x
);
36045 goto http_index_exit
;
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" );
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
];
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
);
36085 f
= fopen( x
, "w");
36087 /* if can't create the index file for some reason, get out */
36090 "www t%d didn't create %s%sindex.html",
36091 tnum
, w
->root
, t
->cwd
);
36093 goto http_index_exit
;
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.
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
);
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 ");
36159 if (1 & idx_sort
) st
= "b=2";
36161 fprintf( f
, "%s<a href=\042index.html?%s\042>%s</a>%s",
36162 " ", st
, "Date", " ");
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
++ ) {
36174 n
= globsort
[i
].name
;
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 */
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
);
36199 if (0 == cs
.st_size
)
36202 /* capture log exists? */
36203 clf
= stat64(ln
, &ls
);
36205 /* LOG is img alt text */
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
);
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
;
36228 fprintf( f
, "%6sM ", zt
);
36230 /* ft is file time text: yyyy-mm-dd hh:mm:ss */
36231 fprintf( f
, "%-17s ", ft
);
36234 /* unlink/zap href img */
36236 "<a href=\042index.html?u=%s\042>" IMG_FMT
"</a>",
36237 n
, "DEL ", "/pg/img/zap16.png", 16, 16, 4, 0, 0 );
36239 /* recording indicator is red button, can only zap from EPG */
36240 fprintf( f
, IMG_FMT
, "CAP ", "/pg/img/rec16.png",
36247 "<a href=\042%s\042>" IMG_FMT
"</a>",
36248 lu
, "LOG ", "/pg/img/log16.png",
36252 /* spacer if no log file */
36253 fprintf( f
, IMG_FMT
,
36254 " ", "/pg/img/blank16.png",
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]) ) {
36265 ei
= "/pg/img/seq16.png";
36268 ei
= "/pg/img/cut16.png";
36271 ei
= "/pg/img/edit16.png";
36276 "<a href=\042xtc://%s%s\042>" IMG_FMT
"</a>",
36277 t
->cwd
, n
, l
, ei
, 16, 16, 4, 0, 0);
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.
36315 build_foot_html(s
, sizeof(s
), 0); /* no validator png. needs <pre> */
36316 fprintf( f
, "%s", s
);
36320 /* don't forget: free glob memory and unlock mutex */
36322 ifree( globsort
, "globsort" );
36323 globfree( &globt
);
36324 advrlog( LOG_INFO
, "www t%d glob: %s found %d files for index.html",
36326 pthread_mutex_unlock( &index_mutex
);
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 */
36340 char *globa
, /* glob wildcard match in ascii */
36343 *ur
, /* uri:// filler as mpeg:// */
36345 *st
, /* sort type bitmask */
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 */
36363 int i
, ok
, /* counter and reusable return code */
36365 gc
, /* total globs returned */
36366 fg
, /* files globbed for sorting */
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;
36388 t
= &w
->tss
[ tnum
];
36392 globa
= "*.ts"; /* glob all .ts files */
36395 /* media type include list by .ts extension */
36396 astrncpy( o
, t
->cwd
, sizeof(o
) );
36401 p
= strrchr( o
, '/' );
36404 p
= strrchr(o
, '/');
36412 astrncpy( o
, t
->cwd
, sizeof(o
) );
36414 snprintf( d
, sizeof(d
), "%s%s", w
->root
, t
->cwd
);
36419 dvrlog( LOG_INFO
, "%s %s t%d chdir %d %s %s %s",
36420 WHO
, caller
, tnum
, ok
, d
, w
->root
, t
->cwd
);
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 */
36431 if (0 == (S_IWUSR
& fs
.st_mode
)) {
36432 /* serve current file */
36433 dvrlog( LOG_INFO
, "user can't update %s", x
);
36435 goto http_index_exit
;
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" );
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
];
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
);
36475 f
= fopen( x
, "w");
36477 /* if can't create the index file for some reason, get out */
36480 "www t%d didn't create %s%sindex.html",
36481 tnum
, w
->root
, t
->cwd
);
36483 goto http_index_exit
;
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.
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
);
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 ");
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", " ");
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
++ ) {
36572 n
= globsort
[i
].name
;
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 */
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
);
36597 if (0 == cs
.st_size
) tsc
= -1;
36599 /* has capture log file? */
36600 clf
= stat64(ln
, &ls
);
36602 /* log is img alt text */
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
);
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
;
36624 fprintf( f
, "%6sM ", zt
);
36626 /* ft is file time text: yyyy-mm-dd hh:mm:ss */
36627 fprintf( f
, "%-17s ", ft
);
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",
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 );
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 );
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]) ){
36661 ei
= "/pg/img/seq16.png";
36664 ei
= "/pg/img/cut16.png";
36667 ei
= "/pg/img/edit16.png";
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);
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://",
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.
36711 build_foot_html(s
, sizeof(s
), 0); /* no validator png. needs <pre> */
36712 fprintf( f
, "%s", s
);
36716 /* don't forget: free glob memory and unlock mutex */
36718 ifree( globsort
, "globsort" );
36719 globfree( &globt
);
36720 advrlog( LOG_INFO
, "www t%d glob: %s found %d files for index.html",
36722 pthread_mutex_unlock( &index_mutex
);
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.
36746 http_index_html ( struct wss_s
*w
, int tnum
, char *caller
)
36748 if (0 == use_css
) {
36749 http_index_html3( w
, tnum
, caller
);
36751 http_index_html4( w
, tnum
, caller
);
36756 /* look at t->file extension and set t->ctext and t->ctype */
36759 http_get_content( struct tts_s
*t
)
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 */
36776 if (0 == strncasecmp( &t
->file
[ z
], e
, y
)) {
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.
36798 http_reload_epg ( int tnum
, int ch
)
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 */
36815 cap_now
= CAP_INFO
;
36817 /* timer0 more than 3 minutes away is ok to cap info */
36818 if (utsnow
< (timer
[0].start
- 180)) {
36820 cap_now
= CAP_INFO
;
36823 /* no timers is ok to cap info */
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.
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
);
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 */
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
);
36892 ifree( epg3
, "http epg3" );
36897 /* when user hits cap log href and href refers to current file, refresh log */
36898 /* no: this one needs more thought. */
36901 http_refresh_caplog( void )
36903 if (CAP_NONE
!= cap_now
) dump_cap_log();
36907 /* convert %XX chars to ascii, and + to space */
36910 http_url_ascii( char *d
, char *s
)
36942 /* convert FORM GET into something parse weekday or volatile timer can do */
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 */
36955 memset( c
, 0, sizeof(c
) );
36956 http_url_ascii( c
, t
);
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;
36980 if ( (ch
< 0) || (ch
> scan_idx
) ) return;
36981 advrlog( LOG_INFO
, "ch %d", ch
);
36983 s
= &ptc
[ ch
].sig
;
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 */
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;
37012 /* no colon assumes 0000-2359, could get fancy & add a/p check too */
37013 v
= strchr( p
[2], ':' );
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], ':' );
37044 // lh = atoi( p[3] ) / 100;
37045 // lm = atoi( p[3] ) % 100;
37048 // lm = atoi( p[3] );
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
));
37072 /* old volatile format */
37073 snprintf(e
, sizeof(e
), "V%02d:%010d:%03d:%s%s",
37074 ch
, st
, lm
, tn
, pn
);
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] );
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] );
37089 dvrlog( LOG_INFO
, "%s weekday and date not allowed %s",
37098 refresh
.timers
= 1;
37100 /* TESTME: run update now? it runs within 1s */
37103 advrlog( LOG_INFO
, "%s %s", WHO
, e
);
37107 /* add timer can call parse volatile timer, but have to sort list and redraw */
37110 http_add_epg_timer ( int tnum
, char *p
)
37113 struct qtimer_s
*t
;
37121 r
= strchr( p
, ':' );
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
) {
37134 if ( (st
>= t
->start
) && (st
< (t
->start
+ t
->len
)) )
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;
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.
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
);
37176 if ('V' == *s
) tt
= 1;
37177 if ('W' == *s
) tt
= 2;
37179 /* skip 'V' or 'W' char */
37181 build_args( p
, 4, s
, ':', WHO
);
37183 st
= lm
= ls
= ch
= pn
= 0;
37186 /* sanity checks */
37187 if ( NULL
== p
[0] ) return;
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;
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
, '.' );
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
++) {
37223 if ( (ch
== t
->chan
)
37225 && (st
== t
->start
)
37228 /* name could be anything because of extra flags */
37229 && (0 == strncmp( n
, t
->name
, TIMER_NAME_MAX
))
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.
37258 http_zap_timer ( int tnum
, char *p
)
37260 int ch
, st
, ls
, pn
, i
;
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
);
37269 ch
= ls
= st
= pn
= 0;
37273 r
= strchr( p
, ':');
37277 r
= strchr( r
, ':' );
37281 r
= strchr( r
, ':' );
37290 if (0 == *n
) return;
37292 r
= strrchr( n
, '.' );
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 */
37317 while( 0 == cap_done
) {
37318 nanosleep( &guide_sleep
, NULL
); /* half second wait */
37319 if (0 == i
--) break; /* two second max */
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
);
37337 http_toggle_find ( int tnum
, int ch
)
37339 pgm3
.find
= ~pgm3
.find
;
37340 ahttp_log( tnum
, "toggle find %d %d", ch
, pgm
.find
);
37345 http_toggle_spam ( int tnum
, int ch
)
37347 pgm3
.hide
= ~pgm3
.hide
;
37348 ahttp_log( tnum
, "toggle spam %d %d", ch
, pgm
.hide
);
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 */
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
);
37371 http_toggle_sigsnr ( int tnum
)
37373 scan_snr
= ~scan_snr
;
37374 ahttp_log( tnum
, "set SNR %d", 1 & scan_snr
);
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. */
37388 http_scan_all ( int tnum
, int op
)
37390 if (CAP_NONE
!= cap_now
) return;
37391 #ifdef USE_POWERDOWN
37392 utspdd
= utsnow
+ USE_POWERDELAY
;
37401 /* keep show channels display redux from limiting web started scan */
37402 display_type
= D_TIMERS
;
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? */
37410 http_scan_one ( int tnum
, int ch
)
37412 if (CAP_NONE
!= cap_now
) return;
37414 #ifdef USE_POWERDOWN
37415 utspdd
= utsnow
+ USE_POWERDELAY
;
37418 /* toggle on and off, or jump to new channel */
37419 if ((0 != scan_sig
) && (cap_chan
== ch
)) {
37420 /* turn off signal scan */
37424 /* scan_next 0 is nop, 1st channel is selected with 1 by console scan() */
37425 scan_next
= 1 + get_scanlist_offset( ch
);
37430 load_guide( ch
, WHO
);
37433 /* kick console back to timer list so signal scan can run */
37434 display_type
= D_TIMERS
;
37438 /* zap, lightning bolt img, delete .ts, .tsx, .html matching file s */
37441 http_unlink_cap( int tnum
, char *s
)
37444 char f
[256], n
[256], *r
, *p
;
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 */
37457 r
= s
; /* file name only */
37460 /* only .ts files */
37461 filebase( n
, r
, F_TFILE
);
37464 if (strlen(p
) < 4) return;
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))
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
);
37481 snprintf( f
, sizeof(f
), "%s%s%s.tsc", w
->root
, t
->cwd
, n
);
37482 ahttp_log( tnum
, "unlink %d %s", ch
, f
);
37485 snprintf( f
, sizeof(f
), "%s%s%s.html", w
->root
, t
->cwd
, n
);
37486 ahttp_log( tnum
, "unlink %d %s", ch
, f
);
37490 snprintf( f
, sizeof(f
), "%s%s%s.ts", w
->root
, t
->cwd
, n
);
37491 ahttp_log( tnum
, "unlink %d %s", ch
, f
);
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 */
37502 http_search( int tnum
, char *t
)
37508 n
= strchr(t
, ':');
37509 if (NULL
== n
) return;
37512 i
= find_search_timer( n
);
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
);
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 */
37537 http_spam( int tnum
, char *p
)
37542 s
= strchr(p
, ':');
37543 if (NULL
== s
) return;
37546 sscanf( s
, "%d", &t
);
37548 i
= test_spam_name( p
);
37551 /* if not found, add it */
37553 sort_spamlist( 0 ); /* sort by name for fast lookup */
37556 /* if found, remove it */
37559 save_spamlist( WHO
);
37562 /* toggle auto-epg status for ch and dump html stats to reflect it */
37565 http_auto_epg( int tnum
, int ch
)
37569 s
= &ptc
[ ch
].sig
;
37571 if (ch
!= s
->chan
) return;
37574 if (0 == s
->pgto
) {
37575 /* add to auto epg list */
37576 s
->pgto
= 1; /* 3hr timeout */
37579 /* triggers immediate test and load if expired */
37583 /* remove from auto epg list */
37588 /* dump_html_stats( WHO ); */ /* request does this now */
37589 advrlog( LOG_INFO
, "%s ch %02d to %d", WHO
, ch
, s
->pgto
);
37595 http_toggle_weekdays( char *p
)
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;
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;
37626 dvrlog( LOG_INFO
, "%s3 n %s c %d d %d ", WHO
, n
, ch
, days
);
37628 for (i
=0; i
<timer_idx
; 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 */
37637 /* don't let it remove the only weekday, thus converting it to volatile */
37638 if (0 == days
) break;
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
;
37650 /* update console too */
37651 refresh
.timers
= 1;
37653 break; /* only process first set day bit */
37659 http_set_vc( char *p
)
37665 refresh
.vstats
= 1;
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.
37675 r
= strchr(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
, ',');
37698 /* TODO: set full cap stat, no href for it yet. has dubious value. */
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 */
37709 /* set next manual capture parameters (only affects console for now) */
37718 http_epg_setday( char *p
)
37725 if ((ch
>= ptc_max
) || (ch
< 0)) return;
37728 /* set to today in case the parse fails */
37729 s
->yday
= tloc
.tm_yday
;
37730 r
= strchr( p
, ':');
37731 if (NULL
== r
) return;
37734 if ( (dn
< 0) || (dn
> 365) ) return;
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
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
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)
37788 combine o m h into m for mask
37792 /* CGI request is in t->cgi, figure out what to do with it */
37795 http_cgi( struct tts_s
*t
)
37802 if (NULL
== t
) return;
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;
37815 ahttp_log( t
->num
, "%s %s", WHO
, p
);
37820 /* only allowing single char options */
37821 if ('=' != *p
) return;
37824 /* s is before = char, p is after */
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 */
37844 http_add_epg_timer( t
->num
, p
);
37848 /* cap/cut dir index.html sorted by date or name */
37852 advrlog( LOG_INFO
, "http idx sort %0X", op
);
37856 /* set virtual channel to 0-n, or:
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
37864 refresh
.vstats
= 1;
37865 refresh
.estats
= 1;
37867 advrlog( LOG_INFO
, "http idx sort %0X", op
);
37873 http_del_timer( t
->num
, p
);
37877 /* toggle auto guide */
37880 http_auto_epg( t
->num
, ch
);
37884 /* set HTML format */
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
);
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
;
37915 /* filter toggle spam/junk match hidden */
37918 if (ch
>= ptc_max
) break;
37919 http_toggle_spam( t
->num
, ch
);
37923 /* toggle event junk status */
37925 http_spam( t
->num
, p
);
37929 /* extended statistics toggle */
37936 /* PNG legend toggle doesn't care which html page is being displayed */
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
);
37946 /* filter toggle timer/search match hidden */
37949 if (ch
>= ptc_max
) break;
37950 http_toggle_find( t
->num
, ch
);
37954 /* set day# to use for EPG, or zero for all days */
37956 http_epg_setday( p
);
37960 /* filter toggle aged/expired hidden */
37963 if (ch
>= ptc_max
) break;
37964 http_toggle_aged( t
->num
, ch
);
37970 http_set_sigpng( t
->num
, op
);
37976 if (1 & op
) http_toggle_sigsnr( t
->num
);
37977 if (2 & op
) http_toggle_sigkhz( t
->num
);
37982 /* this is now equivalent to requesting ch.html. is for href img */
37983 /* refresh guide to update 'current' and 'aged' status */
37986 if (ch
>= ptc_max
) break;
37991 /* toggle event search status */
37993 http_search( t
->num
, p
);
37997 /* timer for any or all weekdays *or* volatile timer for any future event */
38001 http_form_timer( t
->num
, p
);
38005 /* delete capture .ts file, .tsx frame data file and .html log file */
38008 http_unlink_cap( t
->num
, p
);
38011 /* start or stop signal scan for one channel, if not capturing */
38014 http_scan_one( t
->num
, op
);
38018 /* toggle scanning all channels */
38021 http_scan_all( t
->num
, op
);
38025 /* interrupt info cap */
38028 http_zap_info( t
->num
, ch
);
38032 /* toggle weekday bits */
38034 http_toggle_weekdays(p
);
38035 advrlog( LOG_INFO
, "http toggle weekdays %s", p
);
38039 /* zap is same as delete timer plus unlink capture */
38041 http_zap_timer(t
->num
, p
);
38050 /* accept a TCP connection, if it passes test_host_allow check */
38053 http_accept ( struct wss_s
*w
, int tnum
)
38058 // socklen_t z = sizeof(struct sockaddr_in);
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
);
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
),
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
);
38091 http_log( tnum
, "accept failed, no local IP address?");
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
),
38104 ctime_r( &now
, t
->date
);
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");
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
);
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
);
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
);
38140 /* store If Modified Since GMT date as local adjusted time_t for 304 return */
38143 http_ims( struct tts_s
*t
, char *p
)
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.
38168 http_request ( struct wss_s
*w
, int tnum
)
38174 ch
, /* channel number */
38175 a
; /* alpha channel for sig png */
38181 req
[ WWW_TCP_MAX
],
38183 rfile
[ WWW_TCP_MAX
- WWW_ROOT_MAX
],
38188 struct timeval rfto
;
38191 t
= &w
->tss
[ tnum
];
38194 dvrlog( LOG_INFO
, "%s has null thread %d pointer", WHO
, tnum
);
38198 /* default is this if nothing else done */
38201 strcpy( t
->cwd
, "/" );
38202 strcpy( t
->file
, "index.html");
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
;
38216 /* select is 1 if ok to read data */
38218 FD_SET( t
->accepted
, &rfds
);
38219 rfs
= select( t
->accepted
+1, &rfds
, NULL
, NULL
, &rfto
);
38221 /* stop looping once read data becomes available */
38224 nanosleep( &console_read_sleep
, NULL
);
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;
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 */
38244 ahttp_log( tnum
, "Request: %s", p
);
38246 /* request should have NL [or CR/NL?], trunc everything after it */
38247 r
= strchr( p
, '\n');
38249 t
->request
[32] = 0; // truncate the bogus request
38250 http_log( tnum
, "ignoring %s", t
->request
);
38254 r
= strchr( p
, '\r'); // truncate NL and CR
38255 if (NULL
!= r
) *r
= 0;
38257 for (i
= 0; i
< 3; i
++) {
38259 if (0 == *p
) break;
38260 r
= strchr( s
[i
], ' ');
38261 if (NULL
== r
) break;
38267 /* not three parameters? */
38269 http_log( tnum
, "ignoring %s", req
, t
->rfile
);
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",
38298 /* limit output to headers only, if not GET, for link-checkers */
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
);
38310 /* any ../ badness? */
38311 p
= strstr( r
, "../" );
38313 dvrlog( LOG_INFO
, "remove ../ from %s", r
);
38315 while (NULL
!= p
) {
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 */
38323 /* stop the loop */
38324 dvrlog( LOG_INFO
, "removed ../ now %s", r
);
38329 /* skip initial duplicate /'s */
38330 while ( ('/' == r
[0]) && ('/' == r
[1]) ) r
++;
38332 /* reset cgi string */
38335 /* CGI parse re-uses p, it gets re-used again */
38336 p
= strchr(r
, '?');
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 */
38345 /* set to null so re-use will force segfault if not re-used correctly */
38349 if (0 != *t
->cgi
) ahttp_log( t
->num
, "CGI %s", t
->cgi
);
38351 advrlog( LOG_INFO
, "request remaining %s", r
);
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
) );
38360 astrncpy( t
->file
, r
, sizeof( t
->file
));
38363 /* any path left must be new dir */
38364 p
= strrchr(r
, '/');
38367 /* copy directory and put ending / back in */
38368 snprintf( t
->cwd
, sizeof(t
->cwd
), "%s/", r
);
38369 /* point to 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 */
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 */
38415 /* is partial content request? mplayer can use stream slider, xine can not */
38416 rt
= "Range: bytes=";
38417 p
= strstr( t
->request
, rt
);
38420 sscanf( p
, "%lld", &t
->rseek
);
38423 ahttp_log( tnum
, "requests %s %s seek %lld", t
->cwd
, t
->file
, t
->rseek
);
38426 rt
= "If-Modified-Since: ";
38427 p
= strstr( t
->request
, rt
);
38433 /* default is off for HTTP 1.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
);
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"))
38452 /* client requests different keep alive? */
38453 rt
= "Keep-Alive: ";
38454 p
= strstr( t
->request
, rt
);
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
, "/");
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
)) {
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
);
38493 /* handle cgi before serving file it might refer to */
38494 if ('?' == *t
->cgi
)
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? */
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
);
38510 http_index_html( w
, tnum
, WHO
);
38513 /* if file is atscap[arg_dev].html, need to build a current one */
38515 if ( 0 == strncmp( t
->file
, x
, strlen(x
)) ) {
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
);
38527 if (0 == *t
->cwd
) {
38528 dvrlog( LOG_INFO
, "cwd blank!");
38529 strcpy( t
->cwd
, "/" );
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 )) {
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
);
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
)
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" );
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" );
38596 t
->rnum
= 302; /* smooth redirect */
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 */
38607 http_response ( struct wss_s
*w
, int tnum
)
38609 time_t utnow
, utexp
;
38610 long long start
, end
;
38611 long long size
, count
;
38614 char utcnow
[32], utcmod
[32], utcexp
[32];
38615 char fn
[ WWW_TCP_MAX
];
38620 struct timespec e0
= {0,0};
38621 struct timespec e1
= {0,0};
38622 struct timespec et
= {0,0};
38625 #ifdef USE_CGI_WAIT
38626 struct timespec ns
= { 0, 100000000 }; /* 100ms */
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 */
38658 if (304 == t
->rnum
) {
38659 asnprintf( t
->reply
, WWW_TCP_MAX
, "HTTP/1.1 304 Not Modified\n");
38662 asnprintf( t
->reply
, WWW_TCP_MAX
, "Connection: close\n\n");
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
);
38676 http_close( t
, &t
->accepted
, "304" );
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");
38690 asnprintf( t
->reply
, WWW_TCP_MAX
, "Connection: close\n\n");
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
);
38704 http_close( t
, &t
->accepted
, "404" );
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
);
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
);
38728 asnprintf( t
->reply
, WWW_TCP_MAX
, "Connection: close\n\n");
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
);
38736 http_close( t
, &t
->accepted
, "302" );
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
);
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
);
38762 asnprintf( t
->reply
, WWW_TCP_MAX
, "Connection: close\n\n");
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
);
38770 http_close( t
, &t
->accepted
, "302" );
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 */
38785 asnprintf( t
->reply
, WWW_TCP_MAX
, "303 See Other\n");
38787 asnprintf( t
->reply
, WWW_TCP_MAX
, "302 Found\n");
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
);
38799 asnprintf( t
->reply
, WWW_TCP_MAX
, "Content-Location: %s%s\n",
38801 asnprintf( t
->reply
, WWW_TCP_MAX
, "Location: %s%s\n",
38805 /* always close after redirect */
38807 asnprintf( t
->reply
, WWW_TCP_MAX
, "Connection: close\n");
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
);
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
);
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"
38846 "Last Modified: %s\n", t
->file
, utcnow
, utcmod
);
38848 asnprintf( t
->reply
,WWW_TCP_MAX
, "Connection: close\n\n");
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
);
38856 http_close( t
, &t
->accepted
, "304" ); /* not persistent */
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
);
38871 asnprintf( t
->reply
,WWW_TCP_MAX
, "Connection: close\n\n");
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
);
38879 http_close( t
, &t
->accepted
, "416" );
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" );
38897 asnprintf( t
->reply
,WWW_TCP_MAX
, "Connection: close\n\n");
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
);
38907 advrlog( LOG_INFO
, "%s t%d open %2d seek %lld %s\n",
38908 WHO
, tnum
, t
->infile
, t
->rseek
, fn
);
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",
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",
38936 asnprintf( t
->reply
, WWW_TCP_MAX
, "Content-Location: %s%s\n",
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");
38966 /* everything else gets no-cache */
38967 asnprintf( t
->reply
, WWW_TCP_MAX
, "Cache-Control: no-cache\n");
38972 asnprintf( t
->reply
, WWW_TCP_MAX
, "Connection: close\n\n");
38974 asnprintf( t
->reply
, WWW_TCP_MAX
, "Connection: Keep-Alive\n\n");
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 */
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
);
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
);
39008 dvrlog( LOG_INFO
, "%s sent %d", t
->file
, t
->ramz
);
39013 /* is not using memory string, consider above string commented until tested */
39014 advrlog( LOG_INFO
, "tcp send blkz %d", w
->mtu
);
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
);
39024 if (len
< 1) break;
39026 /* NOTE: byte count return from read handles EOF last block len correctly */
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); */
39035 /* cpu usage redux. also reduces 1g enet r8169 speed from 62m/s to 58m/s */
39036 if ( (count
& 0xFF) == 0) {
39038 nanosleep( &www_write_sleep
, NULL
);
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
) {
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
);
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");
39070 ahttp_log(t
->num
, "http/1.%d keeping socket %d t->ka=%d EOF",
39071 t
->htver
, t
->accepted
, t
->ka
);
39077 /* Each invocation runs/blocks/sleeps until SIGINT kills all of them. */
39080 http_loop( void * targ
)
39082 struct tts_s
*t
= (struct tts_s
*) targ
;
39084 int tnum
, ok1
, ok2
, ok3
;
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() );
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 */
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
);
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 */
39143 /* aborted response should close the connection */
39146 /* close connection */
39147 if (t
->accepted
> 2) {
39149 http_close( t
, &t
->accepted
, "accept");
39151 http_close( t
, &t
->accepted
, "request");
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() );
39167 /* create up to w->ptmax threads, or none */
39170 http_create_threads( struct wss_s
*w
)
39175 if (0 == w
->ptmax
) return -1;
39177 for (i
=0; i
< w
->ptmax
; 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
] );
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 */
39198 http_close_sockets();
39202 /* nanosleep( &www_tcreate_sleep, NULL); */
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 */
39212 http_start ( void * targ
)
39222 if (0 != http_bind_socket( w
)) return NULL
;
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 */
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 */
39246 /* at least one packet will be sent regardless of how small mtu is */
39247 if (tb
< 1) tb
= 1;
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);
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 */
39275 w
->port
= arg_wport
;
39276 w
->mask
= arg_wmask
;
39277 w
->addr
= arg_waddr
;
39280 /* Thread task state structure allocation. */
39281 /* Valgrind says no one frees this, whoops */
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
) {
39294 "%s failed tts_s calloc %d = (%dx%d)\n", WHO
,
39295 w
->ptmax
* w
->ptz
, w
->ptmax
, w
->ptz
);
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
);
39309 while('/' == *p
) *p
-- = 0;
39311 strcpy( w
->root
, "/dtv");
39312 http_log( LOG_INFO
, "www root jailed to %s", w
->root
);
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
);
39340 /* initial epg filters */
39341 pgm3
.hide
= pgm
.hide
;
39342 pgm3
.age
= pgm
.age
= ~0;
39343 pgm3
.find
= pgm
.find
;
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 */
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
);
39369 pthread_mutex_init( &png_mutex
, NULL
);
39372 #ifdef USE_GNU_BACKTRACE
39373 pthread_mutex_init( &bt_mutex
, NULL
);
39379 /**************************** main start ***********************************/
39381 main( int argc
, char ** argv
, char ** envp
)
39388 udp_mcast
= 0; /* shut up compiler */
39391 memset(dvrlog_list
, 0, sizeof(dvrlog_list
));
39401 init_mutex(); /* logfile and epg mutex init */
39405 #ifdef USE_POWERDOWN
39406 utspdd
= utsnow
+ USE_POWERDELAY
;
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
);
39419 signal_init(); /* sigaction setup for control c trap */
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 */
39429 /* save current DST status to prevent extra config load from DST change */
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
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 */
39459 /* copy parms to argx, first is screen, last is null */
39461 char argn
[8]; /* device num */
39463 snprintf( argn
, sizeof(argn
)-1, "dtv%d", arg_devnum
);
39464 /* start screen with parameters passed to pchdtvr */
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";
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 */
39489 CLS BW
"GNU screen not found for reattach.\n" BN
);
39492 asyslog( LOG_INFO
, "creating new screen named %s", argn
);
39494 argx
[k
++] = "screen";
39496 if (arg_sdetach
!= 0) {
39497 argx
[k
++] = "-d"; /* -d detaches screen with no wait */
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
;
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 */
39515 fprintf( stderr
, CLS BW
"GNU screen not found.\n" BN
);
39519 asyslog( LOG_INFO
, "GNU screen is running on %s", sty
);
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 );
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
)) {
39559 /* driver klogs "modulation mode (0) not supported" if wait is too short */
39560 /* this may not be needed anymore ? */
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 */
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 */
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
) ) {
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 */
39605 openlog( syslog_name
, LOG_NDELAY
, LOG_DAEMON
);
39607 syslog( LOG_INFO
, "%s %s DVB %s %s",
39608 VERSION
, LASTEDIT
, in_name
, fe_text3
);
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
)){
39623 /* is ok to try to continue after scanning now, instead of old restart */
39627 /* blank console */
39628 aprintf( stderr
, CLS
);
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
)
39659 asyslog( LOG_INFO
, "forking main() pid %d", getpid() );
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)
39669 /* new forked path, no ctl'n tty */
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();
39679 /* old forked path, fork above is detached, so this path exits */
39683 /* not arg_detach, do it interactive without config file, then exit */
39684 open_device( WHO
);
39685 if (2 < in_file
) ts_capture();
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
)
39697 /* new forked path, will fall thru */
39700 "daemon forked, main() is now pid %d", pid_m
);
39701 /* fall thru to below */
39703 /* old forker must die right here */
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);
39721 dvrlog( LOG_INFO
, VERSION
" " LASTEDIT
" DVB %s %s",
39722 in_name
, fe_text3
);
39727 advrlog( LOG_INFO
, "main pid %d", pid_m
);
39728 snprintf(n
, sizeof(n
), "/var/run/%s/%s%d.pid", NAME
, NAME
, arg_devnum
);
39731 fprintf( f
, "%d\n", pid_m
);
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
);
39746 struct udp_s
*u
= &udp
;
39747 /* open socket after second device open */
39748 if (0 != arg_mcport
) udp_open_socket( u
);
39753 if (0 != arg_www
) http_init();
39758 mlockall( MCL_CURRENT
);
39761 /* init some display fields in case startup in middle of timer */
39762 read_capvol_stats();
39763 read_cutvol_stats();
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 ***************************/
39779 if ( CAP_NONE
!= cap_now
)
39781 /* enable this to debug each loop
39782 while ( console_getch() == 0 );
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__
);
39796 CLS
"main() should not have returned at line %d\n",