From 05efa60f3ac7e5ed5d708b9ae739146e1137e040 Mon Sep 17 00:00:00 2001 From: girst Date: Fri, 22 Dec 2017 08:53:29 +0100 Subject: [PATCH] update to dvb-t2 (new hardware, software) --- README | 28 +- channels.conf | 58 +- html.pl | 21 +- install.sh | 2 + plain.pl | 93 --- serv.sh | 6 +- src/dvbtext-src/dvbtext.c | 3 +- src/freqlist.conf | 3 - src/szap-s2/.hg_archival.txt | 6 + src/szap-s2/.hgignore | 7 + src/szap-s2/.hgtags | 0 src/szap-s2/Makefile | 37 ++ src/szap-s2/README | 62 ++ src/szap-s2/lnb.c | 101 ++++ src/szap-s2/lnb.h | 24 + src/szap-s2/szap-s2.c | 1108 ++++++++++++++++++++++++++++++++++ src/szap-s2/tzap-t2.c | 1089 +++++++++++++++++++++++++++++++++ ttxd.service | 2 +- 18 files changed, 2527 insertions(+), 123 deletions(-) delete mode 100755 plain.pl delete mode 100644 src/freqlist.conf create mode 100644 src/szap-s2/.hg_archival.txt create mode 100644 src/szap-s2/.hgignore create mode 100644 src/szap-s2/.hgtags create mode 100644 src/szap-s2/Makefile create mode 100644 src/szap-s2/README create mode 100644 src/szap-s2/lnb.c create mode 100644 src/szap-s2/lnb.h create mode 100644 src/szap-s2/szap-s2.c create mode 100644 src/szap-s2/tzap-t2.c diff --git a/README b/README index 72a5792..82d1d41 100644 --- a/README +++ b/README @@ -1,45 +1,51 @@ -TELETEXT SERVER -=============== +TELETEXT SERVER - DVB-T2 Version +================================ + +Updated to work with DVB-T2 and the Astrometa DVB-T2 USB Stick; many improvements in `html.pl`. Installation ------------ 1. `./install.sh` as root -2. create `freqlist.conf` (see example in `src/`) -3. `scan -a $ADAPTERNO freqlist.conf > channels.conf` +2. create channels.conf with `w_scan` or others; I'm using VDR format + `w_scan -a /dev/dvb/adapter0/frontend1 -cAT -vv > channels.conf` 4. use `ttxd.service` or `serv.sh` to start the service. Usage ----- 1. run `serv.sh` to tune the TV card and start spooling pages -2. generate an HTML file by running `index.pl` +2. generate an HTML file by running `ORFText.cgi` -Caveats -------- +Notes and Caveats +----------------- + * The Astrometa DVB-T2 is very iffy: make sure firmware (`dvb-demod-mn88473-01.fw`) is in place and monitor dmesg when connecting/tuning (I2C errors, firmware upload). + Only fix seems to be a manual reconnect (TODO: splice relay into 5V wire of USB cable) + * `dvbtext` had the line `if (buf[4+i*46]==2) {` replaced with `if (buf[4+i*46]==3) {` or no vtx files will be generated * The spool directory and DVB adapter number are hardcoded in `dvbtext`; its source is in `./src/dvbtext-src/dvbtext.c`. - * `index.pl`, `html.pl` and `plain.pl` do not support subpages other than 0. * `serv.sh` does not check if the card has been tuned; it simply waits 5 seconds. * The spool directory is `/run/ttxd/spool/`, to avoid disk wear. * The accompaning systemd service is `/etc/systemd/system/ttx.service`, where the names of the processes `dvbtext`, `tzap`, and `thttpd` are hardcoded. * since `killall thttpd` is used to start and stop the service, rename that executable if you already running an instance of `thttpd` for other web services. + * in VDR format, teletext PIDs are directly visible: `awk -F':' '{if ($8 != 0){print $1, " : ", $8}}' channels.conf|column -t -s':'` Notes ----- -Currently, the service is using `/dev/dvb/adapter0` (PCIe 05:00.0) for ORF via DVB-T. +Currently, the service is using `/dev/dvb/adapter0/{frontend1,demux0}` (USB) for ORF via DVB-T2. License ------- (C) 2017 Tobias Girstmair -This software is distributed under the GNU General Purpose License 3. +This software is distributed under the GNU General Public License 3. Included Software ----------------- This package contains software of third parties (excluded from license): * dvbtext - Dave Chapman, GNU GPL v2+ (software sightly adapted) - * vtx2ascii - Gerd, Martin Buck (No license supplied) (software sightly adapted) + * vtx2ascii - Gerd, Martin Buck, GNU GPL v2(+?) (software sightly adapted) * thttpd - Jef Poskanzer, 2 clause BSD (modified) + * tzap-t2 - Igor M. Liplianin, GNU GPL v2+ (used as-is) diff --git a/channels.conf b/channels.conf index e25e7a9..15e3d75 100644 --- a/channels.conf +++ b/channels.conf @@ -1,4 +1,54 @@ -ORF1:634000000:INVERSION_AUTO:BANDWIDTH_8_MHZ:FEC_2_3:FEC_AUTO:QAM_16:TRANSMISSION_MODE_8K:GUARD_INTERVAL_1_4:HIERARCHY_NONE:6010:6011:10101 -ORF2 K:634000000:INVERSION_AUTO:BANDWIDTH_8_MHZ:FEC_2_3:FEC_AUTO:QAM_16:TRANSMISSION_MODE_8K:GUARD_INTERVAL_1_4:HIERARCHY_NONE:6020:6021:10116 -ORF2 T:634000000:INVERSION_AUTO:BANDWIDTH_8_MHZ:FEC_2_3:FEC_AUTO:QAM_16:TRANSMISSION_MODE_8K:GUARD_INTERVAL_1_4:HIERARCHY_NONE:6020:6021:10136 -ORF2T HD:634000000:INVERSION_AUTO:BANDWIDTH_8_MHZ:FEC_2_3:FEC_AUTO:QAM_16:TRANSMISSION_MODE_8K:GUARD_INTERVAL_1_4:HIERARCHY_NONE:6150:0:10150 +#Name:Frequency:Parameters:Source:SRate:VPID:APID:TPID:CA:SID:NID:TID:RID +ZDF HD;ORF:506000:B8G16S1T32P1:T:27500:7010=27:0;7011,7012:7015:69C:14701:8232:701:0 +Das Erste HD;ORF:506000:B8G16S1T32P1:T:27500:7020=27:0;7021,7022:7025:69C:14702:8232:701:0 +ZDFneo HD;ORF:506000:B8G16S1T32P1:T:27500:7030=27:0;7031,7032:7035:69C:14703:8232:701:0 +Eurosport 1;ORF:506000:B8G16S1T32P1:T:27500:7040=27:0;7041:7045:69C:14704:8232:701:0 +Sport 1;ORF:506000:B8G16S1T32P1:T:27500:7050=27:0;7051:7055:69C:14705:8232:701:0 +kabel eins;ORF:506000:B8G16S1T32P1:T:27500:7060=27:0;7061:7065:69C:14706:8232:701:0 +RTL II;ORF:506000:B8G16S1T32P1:T:27500:7070=27:0;7071:7075:69C:14707:8232:701:0 +sixx;ORF:506000:B8G16S1T32P1:T:27500:7080=27:0;7081:7085:69C:14708:8232:701:0 +Playboy TV;ORF:506000:B8G16S1T32P1:T:27500:7090=27:0;7091:0:69C:14709:8232:701:0 +KiKA;ORF:506000:B8G16S1T32P1:T:27500:7100=27:0;7101:7105:69C:14710:8232:701:0 +ATV HD;ATV:530000:B8G32S1T8P0:T:27500:5010=27:0;5011:5015:69C:14501:8232:501:0 +PULS 4;sevenonemedia:530000:B8G32S1T8P0:T:27500:5020=27:0;5021:5025:69C:14502:8232:501:0 +ServusTV HD;ServusTV:530000:B8G32S1T8P0:T:27500:5030=27:0;5031,5032:5035:69C:14503:8232:501:0 +RTL HD;RTL:530000:B8G32S1T8P0:T:27500:5040=27:0;5041:5045:69C:14504:8232:501:0 +3sat HD;3SAT:530000:B8G32S1T8P0:T:27500:5050=27:0;5051,5052:5055:69C:14505:8232:501:0 +SRFzwei HD;-:530000:B8G32S1T8P0:T:27500:5060=27:0;5061:5065:69C:14506:8232:501:0 +SRF 1 ;-:530000:B8G32S1T8P0:T:27500:5070=27:0;5071:5075:69C:14507:8232:501:0 +ATV 2;ATV:530000:B8G32S1T8P0:T:27500:5080=27:0;5081:5085:0:14508:8232:501:0 +Flimmit Zusatzpaket;ORS:530000:B8G32S1T8P0:T:27500:5090=27:0:0:0:14509:8232:501:0 +ATVsmart;ATV:530000:B8G32S1T8P0:T:27500:5100=27:0:0:0:14510:8232:501:0 +ProSieben HD;ORF:570000:B8G16S1T32P1:T:27500:8010=27:0;8011:8015:69C:14801:8232:801:0 +PULS 4 HD;ORF:570000:B8G16S1T32P1:T:27500:8020=27:0;8021:8025:69C:14802:8232:801:0 +SAT.1 HD;ORF:570000:B8G16S1T32P1:T:27500:8030=27:0;8031:8035:69C:14803:8232:801:0 +VOX HD;ORF:570000:B8G16S1T32P1:T:27500:8040=27:0;8041:8045:69C:14804:8232:801:0 +ATV;ORF:570000:B8G16S1T32P1:T:27500:8050=27:0;8051:8055:0:14805:8232:801:0 +4News;ORS:570000:B8G16S1T32P1:T:27500:8060=27:0:0:0:14806:8232:801:0 +Disney Channel;ORF:570000:B8G16S1T32P1:T:27500:8070=27:0;8071:8075:69C:14807:8232:801:0 +Deluxe Music;ORF:570000:B8G16S1T32P1:T:27500:8080=27:0;8081:0:69C:14808:8232:801:0 +CNN;ORF:570000:B8G16S1T32P1:T:27500:8090=27:0;8091:0:69C:14809:8232:801:0 +Radio Maria;ORF:570000:B8G16S1T32P1:T:27500:0:0;8111:0:0:14811:8232:801:0 +Kronehit;-:570000:B8G16S1T32P1:T:27500:0:0;8112:0:69C:14812:8232:801:0 +RADIO OE24;-:570000:B8G16S1T32P1:T:27500:0:0;8113:0:0:14813:8232:801:0 +ARTE HD;ORS:586000:B8G16S1T32P1:T:27500:6010=27:0;6011,6012:6015:69C:14601:8232:601:0 +BR Fernsehen Sued HD;ORS:586000:B8G16S1T32P1:T:27500:6020=27:0;6021,6022:6025:69C:14602:8232:601:0 +Nickelodeon;ORS:586000:B8G16S1T32P1:T:27500:6030=27:0;6031:0:69C:14603:8232:601:0 +RTL NITRO;ORS:586000:B8G16S1T32P1:T:27500:6040=27:0;6041:6045:69C:14604:8232:601:0 +SUPER RTL;ORS:586000:B8G16S1T32P1:T:27500:6050=27:0;6051:6055:69C:14605:8232:601:0 +SAT.1 Gold;ORS:586000:B8G16S1T32P1:T:27500:6060=27:0;6061:6065:69C:14606:8232:601:0 +DMAX;ORS:586000:B8G16S1T32P1:T:27500:6070=27:0;6071:6075:69C:14607:8232:601:0 +PHOENIX;ORS:586000:B8G16S1T32P1:T:27500:6080=27:0;6081:6085:69C:14608:8232:601:0 +n-tv;ORS:586000:B8G16S1T32P1:T:27500:6090=27:0;6091:6095:69C:14609:8232:601:0 +ORF1 HD;ORF:634000:B8S1P0:T:27500:4010=27:0;4011,4012:4015:69C:14401:8232:401:0 +ORF2 T HD;ORF:634000:B8S1P0:T:27500:4000=27:0;4001,4002:4005:69C:14402:8232:401:0 +ORF2 V HD;ORF:634000:B8S1P0:T:27500:4000=27:0;4001,4002:4005:69C:14403:8232:401:0 +ORF III HD;ORF:634000:B8S1P0:T:27500:4050=27:0;4051,4052:4055:69C:14405:8232:401:0 +ORF Sport+ HD;ORF:634000:B8S1P0:T:27500:4060=27:0;4061,4062:4065:69C:14406:8232:401:0 +ORF1;ORF:634000:B8S1P0:T:27500:4070=27:0;4071:4015:0:14407:8232:401:0 +ORF2 W;ORF:634000:B8S1P0:T:27500:4080=27:0;4081:4005:0:14408:8232:401:0 +3-Visual-Radio;ORF:634000:B8S1P0:T:27500:4150=27:0;4130:0:0:14415:8232:401:0 +ORF2 K HD;ORF:634000:B8S1P0:T:27500:4000=27:0;4001,4002:4005:69C:14404:8232:401:0 +Radio sterreich 1;ORF:634000:B8S1P0:T:27500:0:0;4090:0:0:14409:8232:401:0 +Hitradio 3;ORF:634000:B8S1P0:T:27500:0:0;4130:0:0:14413:8232:401:0 +radio FM4;ORF:634000:B8S1P0:T:27500:0:0;4140:0:0:14414:8232:401:0 diff --git a/html.pl b/html.pl index bc3eef8..d799f44 100755 --- a/html.pl +++ b/html.pl @@ -17,7 +17,6 @@ binmode STDOUT, ":encoding(utf8)"; my %meta; my $title; my $text = ""; -# TODO: run tzap/dvbtext in background if not already running my $page = shift; my ($subpage) = $page=~m/\d{3}_(\d{2})/; #requires ppp_ss file name scheme! @parens: https://stackoverflow.com/a/10034105 $subpage += 0; # convert to number to remove leading zero @@ -26,7 +25,7 @@ open (VTX, "./vtx2ascii -a $page |") || die ("Can'r run vtx2ascii"); my $last = ""; my $is_10x = 0; do { - # transliterate from ETSI EN 300 706 G0 German to latin-1 (ÄÖÜäöüß°§): + # transliterate from ETSI EN 300 706 G0 German to latin-1 (AOUaouBoS): tr/[\\]{|}~`@/\N{U+C4}\N{U+D6}\N{U+DC}\N{U+E4}\N{U+F6}\N{U+FC}\N{U+DF}\N{U+B0}\N{U+A7}/; my $line = $_; $line =~ s/^\s+|\s+$//g; @@ -49,19 +48,27 @@ $text .= $last; # substitute hyphenation: # * replace with soft hyphen when it splits a word (between lowercase letters; # still allows line break when necessary. -# * keep, when followed by uppercase letter (e.g. "03-Jährige", "PIN-Nummer") +# * keep, when followed by uppercase letter (e.g. "PIN-Nummer") +# * keep, when after a digit (e.g "30-jaehriges") # * remove in any other case (was: forced hyphenation due to space constraints) # ad _EOL_: linebreaks already stripped in loop above; wouldn't work either way # due to single line regex. INFO: Underscore is in DE-localized teletext charset. +# ad s/und/UND/: otherwise, "foo- und barbaz" will become "foound barbaz" +$text =~ s/\bund\b/_UND_/g; $text =~ s/([[:lower:]])-_EOL_ ([[:lower:]])/\1­\2/g; $text =~ s/([[:alnum:]])-_EOL_ ([[:upper:]])/\1-\2/g; +$text =~ s/([[:digit:]])-_EOL_ ([[:alnum:]])/\1-\2/g; +$text =~ s/_UND_/und/g; $text =~ s/_EOL_//g; # remove ORFText idiosyncrasies -$text =~ s/([[:alnum:]]),([[:alnum:]])/\1, \2/g; # no space after comma to save space -$text =~ s/([[:alnum:]])\.([[:upper:][:digit:]])/\1. \2/g; # no space after period to save space (WARN: breaks URLS like tirol.ORF.at) +$text =~ s/([[:alnum:]]),([[:alpha:]])/\1, \2/g; # no space after comma to save space... +$text =~ s/([[:alpha:]]),([[:alnum:]])/\1, \2/g; # ...but not between numbers +$text =~ s/([[:alnum:]])\.([[:upper:]])/\1. \2/g; # no space after period to save space... (WARN: breaks URLS like tirol.ORF.at) +$text =~ s/([[:alpha:]])\.([[:upper:][:digit:]])/\1. \2/g; # ...but not between numbers +$text =~ s/([[:alnum:]]):([[:alnum:]])/\1: \2/g; # no space after colon to save space... TODO: doesn't work -# adblocker: (keep it 7bit-ASCII; perl processes latin1, but output will be utf8) +# adblocker: (keep it 7bit-ASCII; perl processes latin1, but this script is utf-8 encoded (output will be utf8 due to `binmode` at beginning)) $text =~ s/Kalendarium - t.glich neu \. 734//g; $text =~ s/>>tirol\. ?ORF\.at//g; @@ -69,7 +76,7 @@ my @tmp = split(' ',$meta{'date'}); my $shortdate = substr $tmp[1], 0, 6; my $pagesubpage = $meta{'page'} . ($subpage > 0?".$subpage":""); my $moreinfo = "$meta{'res'} - $meta{'subres'}; $meta{'date'}"; -print "

$pagesubpage: $title
$text

"; +print "

$pagesubpage: $title
$text

"; close (VTX); diff --git a/install.sh b/install.sh index 9f4a2f3..4ebb975 100644 --- a/install.sh +++ b/install.sh @@ -8,8 +8,10 @@ fi sh -c 'cd ./src/dvbtext-src/; make' sh -c 'cd ./src/vtx2ascii-src/; make' sh -c 'cd ./src/thttpd-2.27/; make' +sh -c 'cd ./src/szap-s2/; make' cp ./src/dvbtext-src/dvbtext . cp ./src/vtx2ascii-src/vtx2ascii . cp ./src/thttpd-2.27/thttpd . +cp ./src/szap-s2/tzap-t2 . cp ./ttxd.service /etc/systemd/system/ systemctl enable ttxd.service diff --git a/plain.pl b/plain.pl deleted file mode 100755 index a67eed8..0000000 --- a/plain.pl +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/perl -X - -# (C) 2016-2017 Tobias Girstmair -# Extracts plain text news from ORF Teletext -# uses a modified version of vtx2ascii to decode pages -# from dvbtext's spool directory. - -# Usage: ./plain.pl -# Output: Line 1: Heading (pageNo); following lines are news body - -use strict; -use warnings; -use 5.010; -binmode STDOUT, ":encoding(utf8)"; - -# Seitenformat: -# 100-109: -# Metadaten: 1 -# Subressort: 2 -# Ressort/Sparte: 3 -# Leer 4 -# Related: 5 -# Leer 6 -# Titel: 7 -# Text: 8-24 -# -# 112-899: -# Metadaten: 1 -# Subressort: 2 -# Ressort/Sparte: 3 -# Leer 4 -# Titel: 5 -# Text: 6-24 -# - -my %meta; -my $title; -my $text = ""; -# TODO: run tzap/dvbtext in background if not already running -my $page = shift; -my $subp = 0; #shift; #TODO: could be undefined -# run through vtx2ascii (has been modified to output correct ISO 8859-1 without national replacements) -open (VTX, "./vtx2ascii -a $page |") || die ("Can'r run vtx2ascii"); -my $last = ""; -my $is_10x = 0; -do { - # transliterate from ETSI EN 300 706 G0 German to UTF-8: - tr/[\\]{|}~/\N{U+C4}\N{U+D6}\N{U+DC}\N{U+E4}\N{U+F6}\N{U+FC}\N{U+DF}/; - my $line = $_; - $line =~ s/^\s+|\s+$//g; - chomp ($line); - - given ($.) { - when (1) { %meta = parse_metadata ($line) ; $is_10x = ($meta{'page'}<110) } - when (2) { $meta{'subres'} = $line } - when (3) { $meta{'res'} = $line } - when (4) {} - when (5 + (1*$is_10x)) { $title = $line } - when (4 + (1*$is_10x)) {} - when (4 + (3*$is_10x)) { $title .=$line } - default { $text .= $last ."|EOL|". ($last eq ""?"":($line eq ""?"\n":" ")) } - } - $last = $line unless $. == (5+(2*$is_10x)); -} while (); -$text .= $last; - -$text =~ s/([[:lower:]])-\|EOL\| ([[:lower:]])/\1\2/g; #remove hyphenation only when in between lowercase letters -$text =~ s/\|EOL\|//g; #remove hyphenation only when in between lowercase letters - -#ADBlocker -$text =~ s/ORF TELETEXT jetzt auch als App gratis im App-Store für iOS . Android//g; -$text =~ s/Weidenrinde bei R.ckenschmerzen >652 Onlineshop: www\.hafesan\.at//g; - -#DEBUG: -print STDERR "Page: $meta{'page'}\tChannel: $meta{'channel'}\tDate: $meta{'date'}\n"; -print STDERR "Ressort: $meta{'res'}\tSubressort: $meta{'subres'}\n"; -print STDERR "is_10x: ", $is_10x?"yes":"no", "\n"; - -print $title, " ($meta{'page'})\n", $text; - -close (VTX); - -sub parse_metadata { - my @elems = split ' ', @_[0]; - - my %retval = ( - 'page' => shift @elems, - 'channel' => shift @elems, - 'date' => join (' ', @elems) ##date doesnt work TODO - ); - - return %retval; -} diff --git a/serv.sh b/serv.sh index e11551a..002a68b 100755 --- a/serv.sh +++ b/serv.sh @@ -2,12 +2,12 @@ cd /opt/ttxd/ -killall tzap dvbtext thttpd +killall tzap-t2 dvbtext thttpd mkdir -p /run/ttxd/spool/{1,2,3,4,5,6,7,8} -tzap -a 0 -c channels.conf ORF1 &>/dev/null& +./tzap-t2 -a0 -f1 -V -c channels.vdr "ORF1;ORF" &>/dev/null& sleep 5 # TODO: detect FE_HAS_LOCK -./dvbtext 6015 6025 & # 6015=ORF1, 6025=ORF2 +./dvbtext 4015 4005 & # 4015=ORF1, 4005=ORF2 ./thttpd -p 8080 -d /opt/ttxd/ -c '**.cgi' diff --git a/src/dvbtext-src/dvbtext.c b/src/dvbtext-src/dvbtext.c index 15cb1b7..abf875f 100644 --- a/src/dvbtext-src/dvbtext.c +++ b/src/dvbtext-src/dvbtext.c @@ -269,7 +269,8 @@ int main(int argc, char **argv) // fprintf(stderr,"data_identifier =0x%02x\n",buf[0x31]); for (i=0;i<4;i++) { - if (buf[4+i*46]==2) { + if (buf[4+i*46]==3) { + //if (buf[4+i*46]==2) { for (j=(8+i*46);j<(50+i*46);j++) { buf[j]=invtab[buf[j]]; } // fprintf(stderr,"data[%d].data_unit_id =0x%02x\n",i,buf[0x4+i*46]); // fprintf(stderr,"data[%d].data_unit_length=0x%02x\n",i,buf[0x5+i*46]); diff --git a/src/freqlist.conf b/src/freqlist.conf deleted file mode 100644 index aa2bff5..0000000 --- a/src/freqlist.conf +++ /dev/null @@ -1,3 +0,0 @@ -# DVB-T Lienz-Rauchkofel -# T freq bw fec_hi fec_lo mod transmission-mode guard-interval hierarchy -T 634000000 8MHz 2/3 NONE QAM16 8k 1/4 NONE diff --git a/src/szap-s2/.hg_archival.txt b/src/szap-s2/.hg_archival.txt new file mode 100644 index 0000000..c56a18e --- /dev/null +++ b/src/szap-s2/.hg_archival.txt @@ -0,0 +1,6 @@ +repo: 6681000d4f0be03d3cfa77563742f1cc45304632 +node: 69ff3584caf9b46f7a551b39b9f9956f8461377c +branch: default +latesttag: null +latesttagdistance: 31 +changessincelatesttag: 32 diff --git a/src/szap-s2/.hgignore b/src/szap-s2/.hgignore new file mode 100644 index 0000000..7fed81a --- /dev/null +++ b/src/szap-s2/.hgignore @@ -0,0 +1,7 @@ +.pc +\.rej$ +\.orig$ +~$ +\.[oad]$ +\.diff$ +\.patch$ diff --git a/src/szap-s2/.hgtags b/src/szap-s2/.hgtags new file mode 100644 index 0000000..e69de29 diff --git a/src/szap-s2/Makefile b/src/szap-s2/Makefile new file mode 100644 index 0000000..d377af5 --- /dev/null +++ b/src/szap-s2/Makefile @@ -0,0 +1,37 @@ +CC=gcc + +SRC=lnb.c szap-s2.c tzap-t2.c +HED=lnb.h +OBJ1=lnb.o szap-s2.o +OBJ2=tzap-t2.o lnb.o + +BIND=/usr/local/bin/ +INCLUDE=-I../s2/linux/include + +TARGET1=szap-s2 +TARGET2=tzap-t2 + +all: $(TARGET1) $(TARGET2) + +$(TARGET1): $(OBJ1) + $(CC) $(CFLG) $(OBJ1) -o $(TARGET1) $(CLIB) + +$(TARGET2): $(OBJ2) + $(CC) $(CFLG) $(OBJ2) -o $(TARGET2) $(CLIB) + +$(OBJ): $(HED) + +install: all + cp $(TARGET1) $(BIND) + cp $(TARGET2) $(BIND) + +uninstall: + rm $(BIND)$(TARGET1) + rm $(BIND)$(TARGET2) + +clean: + rm -f $(OBJ1) $(TARGET1) *~ + rm -f $(OBJ2) $(TARGET2) *~ + +%.o: %.c + $(CC) $(INCLUDE) -c $< -o $@ diff --git a/src/szap-s2/README b/src/szap-s2/README new file mode 100644 index 0000000..ea1c952 --- /dev/null +++ b/src/szap-s2/README @@ -0,0 +1,62 @@ +szap-s2 -- simple zapping tool for the Linux DVB S2 API + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +szap-s2 requires Linux DVB driver API version 5.0. + +If compiler not found DVB headers in ../s2/linux/include/linux/dvb, +then it looks at usual location /usr/include/linux/dvb. + +Install as follows: + +(Option) make + make install +Uninstall + make uninstall + +location of channel list file is ~/.szap/channels.conf + +one line of the szap channel file has the following format: +name:frequency_MHz:polarization[coderate][delivery][modulation][rolloff]:sat_no:symbolrate:vpid:apid:service_id +one line of the VDR channel file has the following format: +name:frequency_MHz:polarization[coderate][delivery][modulation][rolloff]:sat_no:symbolrate:vpid:apid:?:?:service_id:?:?:? + +usage: +szap -q + list known channels +szap [options] {-n channel-number|channel_name} + zap to channel via number or full name (case insensitive) + -a number : use given adapter (default 0) + -f number : use given frontend (default 0) + -d number : use given demux (default 0) + -c file : read channels list from 'file' + -V : use vdr channels list file format (default zap) + -b : enable Audio Bypass (default no) + -x : exit after tuning + -H : human readable output + -D : params debug + -r : set up /dev/dvb/adapterX/dvr0 for TS recording + -l lnb-type (DVB-S Only) (use -l help to print types) or + -l low[,high[,switch]] in Mhz + -i : run interactively, allowing you to type in channel names + -p : add pat and pmt to TS recording (implies -r) + or -n numbers for zapping + -S : delivery system type DVB-S=0, DVB-S2=1 + -M : modulation 1=BPSK 2=QPSK 5=8PSK + -C : fec 0=NONE 12=1/2 23=2/3 34=3/4 35=3/5 45=4/5 56=5/6 67=6/7 89=8/9 910=9/10 999=AUTO + -O : rolloff 35=0.35 25=0.25 20=0.20 0=UNKNOWN + -m : input stream 0-255 + + +Igor M. Liplianin (liplianin@me.by) diff --git a/src/szap-s2/lnb.c b/src/szap-s2/lnb.c new file mode 100644 index 0000000..9052d1c --- /dev/null +++ b/src/szap-s2/lnb.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include "lnb.h" + +static char *univ_desc[] = { + "Europe", + "10800 to 11800 MHz and 11600 to 12700 Mhz", + "Dual LO, loband 9750, hiband 10600 MHz", + (char *)NULL }; + +static char *dbs_desc[] = { + "Expressvu, North America", + "12200 to 12700 MHz", + "Single LO, 11250 MHz", + (char *)NULL }; + +static char *standard_desc[] = { + "10945 to 11450 Mhz", + "Single LO, 10000 Mhz", + (char *)NULL }; + +static char *enhan_desc[] = { + "Astra", + "10700 to 11700 MHz", + "Single LO, 9750 MHz", + (char *)NULL }; + +static char *cband_desc[] = { + "Big Dish", + "3700 to 4200 MHz", + "Single LO, 5150 Mhz", + (char *)NULL }; + +static struct lnb_types_st lnbs[] = { + {"UNIVERSAL", univ_desc, 9750, 10600, 11700 }, + {"DBS", dbs_desc, 11250, 0, 0 }, + {"STANDARD", standard_desc, 10000, 0, 0 }, + {"ENHANCED", enhan_desc, 9750, 0, 0 }, + {"C-BAND", cband_desc, 5150, 0, 0 } +}; + +/* Enumerate through standard types of LNB's until NULL returned. + * Increment curno each time + */ + +struct lnb_types_st * +lnb_enum(int curno) +{ + if (curno >= (int) (sizeof(lnbs) / sizeof(lnbs[0]))) + return (struct lnb_types_st *)NULL; + return &lnbs[curno]; +} + +/* Decode an lnb type, for example given on a command line + * If alpha and standard type, e.g. "Universal" then match that + * otherwise low[,high[,switch]] + */ + +int +lnb_decode(char *str, struct lnb_types_st *lnbp) +{ +int i; +char *cp, *np; + + memset(lnbp, 0, sizeof(*lnbp)); + cp = str; + while(*cp && isspace(*cp)) + cp++; + if (isalpha(*cp)) { + for (i = 0; i < (int)(sizeof(lnbs) / sizeof(lnbs[0])); i++) { + if (!strcasecmp(lnbs[i].name, cp)) { + *lnbp = lnbs[i]; + return 1; + } + } + return -1; + } + if (*cp == '\0' || !isdigit(*cp)) + return -1; + lnbp->low_val = strtoul(cp, &np, 0); + if (lnbp->low_val == 0) + return -1; + cp = np; + while(*cp && (isspace(*cp) || *cp == ',')) + cp++; + if (*cp == '\0') + return 1; + if (!isdigit(*cp)) + return -1; + lnbp->high_val = strtoul(cp, &np, 0); + cp = np; + while(*cp && (isspace(*cp) || *cp == ',')) + cp++; + if (*cp == '\0') + return 1; + if (!isdigit(*cp)) + return -1; + lnbp->switch_val = strtoul(cp, NULL, 0); + return 1; +} diff --git a/src/szap-s2/lnb.h b/src/szap-s2/lnb.h new file mode 100644 index 0000000..f78b7a6 --- /dev/null +++ b/src/szap-s2/lnb.h @@ -0,0 +1,24 @@ + +struct lnb_types_st { + char *name; + char **desc; + unsigned long low_val; + unsigned long high_val; /* zero indicates no hiband */ + unsigned long switch_val; /* zero indicates no hiband */ +}; + +/* Enumerate through standard types of LNB's until NULL returned. + * Increment curno each time + */ + +struct lnb_types_st * +lnb_enum(int curno); + +/* Decode an lnb type, for example given on a command line + * If alpha and standard type, e.g. "Universal" then match that + * otherwise low[,high[,switch]] + */ + +int +lnb_decode(char *str, struct lnb_types_st *lnbp); + diff --git a/src/szap-s2/szap-s2.c b/src/szap-s2/szap-s2.c new file mode 100644 index 0000000..d661758 --- /dev/null +++ b/src/szap-s2/szap-s2.c @@ -0,0 +1,1108 @@ +/* szap-s2 -- simple zapping tool for the Linux DVB S2 API + * + * Copyright (C) 2008 Igor M. Liplianin (liplianin@me.by) + * Copyright (C) 2013 CrazyCat (crazycat69@narod.ru) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include "lnb.h" + +#if DVB_API_VERSION < 5 || DVB_API_VERSION_MINOR < 2 +#error szap-s2 requires Linux DVB driver API version 5.2 and newer! +#endif + +#ifndef DTV_STREAM_ID + #define DTV_STREAM_ID DTV_ISDBS_TS_ID +#endif + +#ifndef NO_STREAM_ID_FILTER + #define NO_STREAM_ID_FILTER (~0U) +#endif + +#ifndef TRUE +#define TRUE (1==1) +#endif +#ifndef FALSE +#define FALSE (1==0) +#endif + +/* location of channel list file */ +#define CHANNEL_FILE "channels.conf" + +/* one line of the szap channel file has the following format: + * ^name:frequency_MHz:polarization:sat_no:symbolrate:vpid:apid:service_id$ + * one line of the VDR channel file has the following format: + * ^name:frequency_MHz:polarization[coderate][delivery][modulation][rolloff]:sat_no:symbolrate:vpid:apid:?:?:service_id:?:?:?$ + */ + + +#define FRONTENDDEVICE "/dev/dvb/adapter%d/frontend%d" +#define DEMUXDEVICE "/dev/dvb/adapter%d/demux%d" +#define AUDIODEVICE "/dev/dvb/adapter%d/audio%d" + +struct t_channel_parameter_map { + int user_value; + int driver_value; + const char *user_string; + }; +/* --- Channel Parameter Maps From VDR---*/ + +static struct t_channel_parameter_map inversion_values[] = { + { 0, INVERSION_OFF, "off" }, + { 1, INVERSION_ON, "on" }, + { 999, INVERSION_AUTO }, + { -1 } + }; + +static struct t_channel_parameter_map coderate_values[] = { + { 0, FEC_NONE, "none" }, + { 12, FEC_1_2, "1/2" }, +// { 13, FEC_1_3, "1/3" }, +// { 14, FEC_1_4, "1/4" }, + { 23, FEC_2_3, "2/3" }, +// { 25, FEC_2_5, "2/5" }, + { 34, FEC_3_4, "3/4" }, + { 35, FEC_3_5, "3/5" }, + { 45, FEC_4_5, "4/5" }, + { 56, FEC_5_6, "5/6" }, + { 67, FEC_6_7, "6/7" }, + { 78, FEC_7_8, "7/8" }, + { 89, FEC_8_9, "8/9" }, + { 910, FEC_9_10, "9/10" }, + { 999, FEC_AUTO, "auto" }, + { -1 } + }; + +static struct t_channel_parameter_map modulation_values[] = { + // { 0, NONE, "none" }, + // { 4, QAM_4, "QAM4" }, + { 16, QAM_16, "QAM16" }, + { 32, QAM_32, "QAM32" }, + { 64, QAM_64, "QAM64" }, + { 128, QAM_128, "QAM128" }, + { 256, QAM_256, "QAM256" }, +// { 512, QAM_512, "QAM512" }, +// {1024, QAM_1024, "QAM1024" }, +// { 1, BPSK, "BPSK" }, + { 2, QPSK, "QPSK" }, +// { 3, OQPSK, "OQPSK" }, + { 5, PSK_8, "8PSK" }, + { 6, APSK_16, "16APSK" }, + { 7, APSK_32, "32APSK" }, +// { 8, OFDM, "OFDM" }, +// { 9, COFDM, "COFDM" }, + { 10, VSB_8, "VSB8" }, + { 11, VSB_16, "VSB16" }, + { 998, QAM_AUTO, "QAMAUTO" }, +// { 999, AUTO }, + { -1 } + }; + +static struct t_channel_parameter_map system_values[] = { + { 0, SYS_DVBS, "DVB-S" }, + { 1, SYS_DVBS2, "DVB-S2" }, + { -1 } + }; + + +static struct t_channel_parameter_map rolloff_values[] = { + // { 0, ROLLOFF_AUTO, "auto"}, + { 20, ROLLOFF_20, "0.20" }, + { 25, ROLLOFF_25, "0.25" }, + { 35, ROLLOFF_35, "0.35" }, + { -1 } + }; + +static int user_index(int value, const struct t_channel_parameter_map * map) +{ + const struct t_channel_parameter_map *umap = map; + while (umap && umap->user_value != -1) { + if (umap->user_value == value) + return umap - map; + umap++; + } + return -1; +}; + +static int driver_index(int value, const struct t_channel_parameter_map *map) +{ + const struct t_channel_parameter_map *umap = map; + while (umap && umap->user_value != -1) { + if (umap->driver_value == value) + return umap - map; + umap++; + } + return -1; +}; + +static int map_to_user(int value, const struct t_channel_parameter_map *map, char **string) +{ + int n = driver_index(value, map); + if (n >= 0) { + if (string) + *string = (char *)map[n].user_string; + return map[n].user_value; + } + return -1; +} + +static int map_to_driver(int value, const struct t_channel_parameter_map *map) +{ + int n = user_index(value, map); + if (n >= 0) + return map[n].driver_value; + return -1; +} + +static struct lnb_types_st lnb_type; + +static int exit_after_tuning; +static int interactive; + +static char *usage_str = + "\nusage: szap-s2 -q\n" + " list known channels\n" + " szap-s2 [options] {-n channel-number|channel_name}\n" + " zap to channel via number or full name (case insensitive)\n" + " -a number : use given adapter (default 0)\n" + " -f number : use given frontend (default 0)\n" + " -d number : use given demux (default 0)\n" + " -c file : read channels list from 'file'\n" + " -V : use VDR channels list file format (default zap)\n" + " -b : enable Audio Bypass (default no)\n" + " -x : exit after tuning\n" + " -H : human readable output\n" + " -D : params debug\n" + " -r : set up /dev/dvb/adapterX/dvr0 for TS recording\n" + " -l lnb-type (DVB-S Only) (use -l help to print types) or \n" + " -l low[,high[,switch]] in Mhz\n" + " -i : run interactively, allowing you to type in channel names\n" + " -p : add pat and pmt to TS recording (implies -r)\n" + " or -n numbers for zapping\n" + " -t : add teletext to TS recording (needs -V)\n" + " -S : delivery system type DVB-S=0, DVB-S2=1\n" + " -M : modulation 1=BPSK 2=QPSK 5=8PSK\n" + " -C : fec 0=NONE 12=1/2 23=2/3 34=3/4 35=3/5 45=4/5 56=5/6 67=6/7 89=8/9 910=9/10 999=AUTO\n" + " -O : rolloff 35=0.35 25=0.25 20=0.20 0=UNKNOWN\n" + " -m : input stream 0-255\n"; + +static int set_demux(int dmxfd, int pid, int pes_type, int dvr) +{ + struct dmx_pes_filter_params pesfilter; + + if (pid < 0 || pid >= 0x1fff) /* ignore this pid to allow radio services */ + return TRUE; + + if (dvr) { + int buffersize = 64 * 1024; + if (ioctl(dmxfd, DMX_SET_BUFFER_SIZE, buffersize) == -1) + perror("DMX_SET_BUFFER_SIZE failed"); + } + + pesfilter.pid = pid; + pesfilter.input = DMX_IN_FRONTEND; + pesfilter.output = dvr ? DMX_OUT_TS_TAP : DMX_OUT_DECODER; + pesfilter.pes_type = pes_type; + pesfilter.flags = DMX_IMMEDIATE_START; + + if (ioctl(dmxfd, DMX_SET_PES_FILTER, &pesfilter) == -1) { + fprintf(stderr, "DMX_SET_PES_FILTER failed " + "(PID = 0x%04x): %d %m\n", pid, errno); + + return FALSE; + } + + return TRUE; +} + +int get_pmt_pid(char *dmxdev, int sid) +{ + int patfd, count; + int pmt_pid = 0; + int patread = 0; + int section_length; + unsigned char buft[4096]; + unsigned char *buf = buft; + struct dmx_sct_filter_params f; + + memset(&f, 0, sizeof(f)); + f.pid = 0; + f.filter.filter[0] = 0x00; + f.filter.mask[0] = 0xff; + f.timeout = 0; + f.flags = DMX_IMMEDIATE_START | DMX_CHECK_CRC; + + if ((patfd = open(dmxdev, O_RDWR)) < 0) { + perror("openening pat demux failed"); + return -1; + } + + if (ioctl(patfd, DMX_SET_FILTER, &f) == -1) { + perror("ioctl DMX_SET_FILTER failed"); + close(patfd); + return -1; + } + + while (!patread) { + if (((count = read(patfd, buf, sizeof(buft))) < 0) && errno == EOVERFLOW) + count = read(patfd, buf, sizeof(buft)); + if (count < 0) { + perror("read_sections: read error"); + close(patfd); + return -1; + } + + section_length = ((buf[1] & 0x0f) << 8) | buf[2]; + if (count != section_length + 3) + continue; + + buf += 8; + section_length -= 8; + + patread = 1; /* assumes one section contains the whole pat */ + while (section_length > 0) { + int service_id = (buf[0] << 8) | buf[1]; + if (service_id == sid) { + pmt_pid = ((buf[2] & 0x1f) << 8) | buf[3]; + section_length = 0; + } + buf += 4; + section_length -= 4; + } + } + close(patfd); + return pmt_pid; +} + +struct diseqc_cmd { + struct dvb_diseqc_master_cmd cmd; + uint32_t wait; +}; + +void diseqc_send_msg(int fd, fe_sec_voltage_t v, struct diseqc_cmd *cmd, + fe_sec_tone_mode_t t, fe_sec_mini_cmd_t b) +{ + if (ioctl(fd, FE_SET_TONE, SEC_TONE_OFF) == -1) + perror("FE_SET_TONE failed"); + if (ioctl(fd, FE_SET_VOLTAGE, v) == -1) + perror("FE_SET_VOLTAGE failed"); + usleep(15 * 1000); + if (ioctl(fd, FE_DISEQC_SEND_MASTER_CMD, &cmd->cmd) == -1) + perror("FE_DISEQC_SEND_MASTER_CMD failed"); + usleep(cmd->wait * 1000); + usleep(15 * 1000); + if (ioctl(fd, FE_DISEQC_SEND_BURST, b) == -1) + perror("FE_DISEQC_SEND_BURST failed"); + usleep(15 * 1000); + if (ioctl(fd, FE_SET_TONE, t) == -1) + perror("FE_SET_TONE failed"); + +} + + + + +/* digital satellite equipment control, + * specification is available from http://www.eutelsat.com/ + */ +static int diseqc(int secfd, int sat_no, int pol_vert, int hi_band) +{ + struct diseqc_cmd cmd = + { {{0xe0, 0x10, 0x38, 0xf0, 0x00, 0x00}, 4}, 0 }; + + /** + * param: high nibble: reset bits, low nibble set bits, + * bits are: option, position, polarizaion, band + */ + cmd.cmd.msg[3] = + 0xf0 | (((sat_no * 4) & 0x0f) | (hi_band ? 1 : 0) | (pol_vert ? 0 : 2)); + + diseqc_send_msg(secfd, pol_vert ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18, + &cmd, hi_band ? SEC_TONE_ON : SEC_TONE_OFF, + (sat_no / 4) % 2 ? SEC_MINI_B : SEC_MINI_A); + + return TRUE; +} + +static int do_tune(int fefd, unsigned int ifreq, unsigned int sr, enum fe_delivery_system delsys, + int modulation, int fec, int rolloff, int stream_id) +{ + struct dvb_frontend_event ev; + struct dtv_property p[] = { + { .cmd = DTV_DELIVERY_SYSTEM, .u.data = delsys }, + { .cmd = DTV_FREQUENCY, .u.data = ifreq }, + { .cmd = DTV_MODULATION, .u.data = modulation }, + { .cmd = DTV_SYMBOL_RATE, .u.data = sr }, + { .cmd = DTV_INNER_FEC, .u.data = fec }, + { .cmd = DTV_INVERSION, .u.data = INVERSION_AUTO }, + { .cmd = DTV_ROLLOFF, .u.data = rolloff }, + { .cmd = DTV_PILOT, .u.data = PILOT_AUTO }, + { .cmd = DTV_STREAM_ID, .u.data = stream_id }, + { .cmd = DTV_TUNE }, + }; + struct dtv_properties cmdseq = { + .num = sizeof(p)/sizeof(p[0]), + .props = p + }; + + /* discard stale QPSK events */ + while (1) { + if (ioctl(fefd, FE_GET_EVENT, &ev) == -1) + break; + } + + if ((delsys != SYS_DVBS) && (delsys != SYS_DVBS2)) + return -EINVAL; + + if ((ioctl(fefd, FE_SET_PROPERTY, &cmdseq)) == -1) { + perror("FE_SET_PROPERTY failed"); + return FALSE; + } + + return TRUE; +} + + +static +int check_frontend (int fe_fd, int dvr, int human_readable, int params_debug, + int hiband) +{ + (void)dvr; + fe_status_t status; + uint16_t snr, signal; + uint32_t ber, uncorrected_blocks; + int timeout = 0; + char *field; + struct dtv_property p[] = { + { .cmd = DTV_DELIVERY_SYSTEM }, + { .cmd = DTV_MODULATION }, + { .cmd = DTV_INNER_FEC }, + { .cmd = DTV_ROLLOFF }, + { .cmd = DTV_FREQUENCY }, + { .cmd = DTV_SYMBOL_RATE }, + }; + struct dtv_properties cmdseq = { + .num = sizeof(p)/sizeof(p[0]), + .props = p + }; + + do { + if (ioctl(fe_fd, FE_READ_STATUS, &status) == -1) + perror("FE_READ_STATUS failed"); + /* some frontends might not support all these ioctls, thus we + * avoid printing errors + */ + if (ioctl(fe_fd, FE_READ_SIGNAL_STRENGTH, &signal) == -1) + signal = -2; + if (ioctl(fe_fd, FE_READ_SNR, &snr) == -1) + snr = -2; + if (ioctl(fe_fd, FE_READ_BER, &ber) == -1) + ber = -2; + if (ioctl(fe_fd, FE_READ_UNCORRECTED_BLOCKS, &uncorrected_blocks) == -1) + uncorrected_blocks = -2; + + if (human_readable) { + printf ("status %02x | signal %3u%% | snr %3u%% | ber %d | unc %d | ", + status, (signal * 100) / 0xffff, (snr * 100) / 0xffff, ber, uncorrected_blocks); + } else { + printf ("status %02x | signal %04x | snr %04x | ber %08x | unc %08x | ", + status, signal, snr, ber, uncorrected_blocks); + } + if (status & FE_HAS_LOCK) + printf("FE_HAS_LOCK"); + printf("\n"); + + if (exit_after_tuning && ((status & FE_HAS_LOCK) || (++timeout >= 10))) + break; + + usleep(1000000); + } while (1); + + if ((status & FE_HAS_LOCK) == 0) + return 0; + + if ((ioctl(fe_fd, FE_GET_PROPERTY, &cmdseq)) == -1) { + perror("FE_GET_PROPERTY failed"); + return 0; + } + /* printout found parameters here */ + if (params_debug){ + printf("delivery 0x%x, ", p[0].u.data); + printf("modulation 0x%x\n", p[1].u.data); + printf("coderate 0x%x, ", p[2].u.data); + printf("rolloff 0x%x\n", p[3].u.data); + printf("intermediate frequency %d,", p[4].u.data); + } else { + field = NULL; + map_to_user(p[0].u.data, system_values, &field); + printf("delivery %s, ", field); + field = NULL; + map_to_user(p[1].u.data, modulation_values, &field); + printf("modulation %s\n", field); + field = NULL; + map_to_user(p[2].u.data, coderate_values, &field); + printf("coderate %s, ", field); + field = NULL; + map_to_user(p[3].u.data, rolloff_values, &field); + printf("rolloff %s\n", field); + if (hiband) + printf("frequency %lu,",lnb_type.high_val + p[4].u.data); + else + printf("frequency %lu,",lnb_type.low_val + p[4].u.data); + + } + + printf("symbol_rate %d\n", p[5].u.data); + + return 0; +} + +static +int zap_to(unsigned int adapter, unsigned int frontend, unsigned int demux, + unsigned int sat_no, unsigned int freq, unsigned int pol, + unsigned int sr, unsigned int vpid, unsigned int apid, + unsigned int tpid, int sid, + int dvr, int rec_psi, int bypass, unsigned int delivery, + int modulation, int fec, int rolloff, int stream_id, int human_readable, + int params_debug) +{ + struct dtv_property p[] = { + { .cmd = DTV_CLEAR }, + }; + + struct dtv_properties cmdseq = { + .num = sizeof(p)/sizeof(p[0]), + .props = p + }; + + char fedev[128], dmxdev[128], auddev[128]; + static int fefd, dmxfda, dmxfdv, dmxfdt = -1, audiofd = -1, patfd, pmtfd; + int pmtpid; + uint32_t ifreq; + int hiband, result; + + if (!fefd) { + snprintf(fedev, sizeof(fedev), FRONTENDDEVICE, adapter, frontend); + snprintf(dmxdev, sizeof(dmxdev), DEMUXDEVICE, adapter, demux); + snprintf(auddev, sizeof(auddev), AUDIODEVICE, adapter, demux); + printf("using '%s' and '%s'\n", fedev, dmxdev); + + if ((fefd = open(fedev, O_RDWR | O_NONBLOCK)) < 0) { + perror("opening frontend failed"); + return FALSE; + } + + if ((dmxfdv = open(dmxdev, O_RDWR)) < 0) { + perror("opening video demux failed"); + close(fefd); + return FALSE; + } + + if ((dmxfda = open(dmxdev, O_RDWR)) < 0) { + perror("opening audio demux failed"); + close(fefd); + return FALSE; + } + + if ((dmxfdt = open(dmxdev, O_RDWR)) < 0) { + perror("opening teletext demux failed"); + close(fefd); + return FALSE; + } + + if (dvr == 0) /* DMX_OUT_DECODER */ + audiofd = open(auddev, O_RDWR); + + if (rec_psi){ + if ((patfd = open(dmxdev, O_RDWR)) < 0) { + perror("opening pat demux failed"); + close(audiofd); + close(dmxfda); + close(dmxfdv); + close(dmxfdt); + close(fefd); + return FALSE; + } + + if ((pmtfd = open(dmxdev, O_RDWR)) < 0) { + perror("opening pmt demux failed"); + close(patfd); + close(audiofd); + close(dmxfda); + close(dmxfdv); + close(dmxfdt); + close(fefd); + return FALSE; + } + } + } + + + hiband = 0; + if (lnb_type.switch_val && lnb_type.high_val && + freq >= lnb_type.switch_val) + hiband = 1; + + if (hiband) + ifreq = freq - lnb_type.high_val; + else { + if (freq < lnb_type.low_val) + ifreq = lnb_type.low_val - freq; + else + ifreq = freq - lnb_type.low_val; + } + result = FALSE; + + if ((ioctl(fefd, FE_SET_PROPERTY, &cmdseq)) == -1) { + perror("FE_SET_PROPERTY DTV_CLEAR failed"); + return FALSE; + } + + if (diseqc(fefd, sat_no, pol, hiband)) + if (do_tune(fefd, ifreq, sr, delivery, modulation, fec, rolloff, stream_id)) + if (set_demux(dmxfdv, vpid, DMX_PES_VIDEO, dvr)) + if (audiofd >= 0) + (void)ioctl(audiofd, AUDIO_SET_BYPASS_MODE, bypass); + if (set_demux(dmxfda, apid, DMX_PES_AUDIO, dvr)) { + if (rec_psi) { + pmtpid = get_pmt_pid(dmxdev, sid); + if (pmtpid < 0) { + result = FALSE; + } + if (pmtpid == 0) { + fprintf(stderr,"couldn't find pmt-pid for sid %04x\n",sid); + result = FALSE; + } + if (set_demux(patfd, 0, DMX_PES_OTHER, dvr)) + if (set_demux(pmtfd, pmtpid, DMX_PES_OTHER, dvr)) + result = TRUE; + } else { + result = TRUE; + } + } + + if (tpid != -1 && !set_demux(dmxfdt, tpid, DMX_PES_TELETEXT, dvr)) { + fprintf(stderr, "set_demux DMX_PES_TELETEXT failed\n"); + } + + check_frontend (fefd, dvr, human_readable, params_debug, hiband); + + if (!interactive) { + close(patfd); + close(pmtfd); + if (audiofd >= 0) + close(audiofd); + close(dmxfda); + close(dmxfdv); + close(dmxfdt); + close(fefd); + } + + return result; +} +static char *parse_parameter(const char *s, int *value, const struct t_channel_parameter_map *map) +{ + if (*++s) { + char *p = NULL; + errno = 0; + int n = strtol(s, &p, 10); + if (!errno && p != s) { + value[0] = map_to_driver(n, map); + if (value[0] >= 0) + return p; + } + } + fprintf(stderr, "ERROR: invalid value for parameter '%C'\n", *(s - 1)); + return NULL; +} + +static int read_channels(const char *filename, int list_channels, + uint32_t chan_no, const char *chan_name, + unsigned int adapter, unsigned int frontend, + unsigned int demux, int dvr, int rec_psi, + int bypass, unsigned int delsys, + int modulation, int fec, int rolloff, int stream_id, + int human_readable, int params_debug, + int use_vdr_format, int use_tpid) +{ + FILE *cfp; + char buf[4096]; + char inp[256]; + char *field, *tmp, *p; + unsigned int line; + unsigned int freq, pol, sat_no, sr, vpid, apid, tpid, sid; + int ret; + int trash; +again: + line = 0; + if (!(cfp = fopen(filename, "r"))) { + fprintf(stderr, "error opening channel list '%s': %d %m\n", + filename, errno); + return FALSE; + } + + if (interactive) { + fprintf(stderr, "\n>>> "); + if (!fgets(inp, sizeof(inp), stdin)) { + printf("\n"); + return -1; + } + if (inp[0] == '-' && inp[1] == 'n') { + chan_no = strtoul(inp+2, NULL, 0); + chan_name = NULL; + if (!chan_no) { + fprintf(stderr, "bad channel number\n"); + goto again; + } + } else { + p = strchr(inp, '\n'); + if (p) + *p = '\0'; + chan_name = inp; + chan_no = 0; + } + } + + while (!feof(cfp)) { + if (fgets(buf, sizeof(buf), cfp)) { + line++; + + if (chan_no && chan_no != line) + continue; + + tmp = buf; + field = strsep(&tmp, ":"); + + if (!field) + goto syntax_err; + + if (list_channels) { + printf("%03u %s\n", line, field); + continue; + } + + if (chan_name && strcasecmp(chan_name, field) != 0) + continue; + + printf("zapping to %d '%s':\n", line, field); + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + freq = strtoul(field, NULL, 0); + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + while (field && *field) { + switch (toupper(*field)) { + case 'C': + if (fec == -1) + field = parse_parameter(field, &fec, coderate_values); + else + field = parse_parameter(field, &trash, coderate_values); + break; + case 'H': + pol = 0; + *field++; + break; + case 'I':/* ignore */ + field = parse_parameter(field, &ret, inversion_values); + break; + case 'L': + pol = 0; + *field++; + break; + case 'M': + if (modulation == -1) + field = parse_parameter(field, &modulation, modulation_values); + else + field = parse_parameter(field, &trash, modulation_values); + break; + case 'Z': + case 'O': + if (rolloff == -1) + field = parse_parameter(field, &rolloff, rolloff_values); + else + field = parse_parameter(field, &trash, rolloff_values); + break; + case 'P': + stream_id = strtol(++field, &field, 10); + break; + case 'R': + pol = 1; + *field++; + break; + case 'S': + if (delsys == -1) + field = parse_parameter(field, &delsys, system_values); + else + field = parse_parameter(field, &trash, system_values); + break; + case 'V': + pol = 1; + *field++; + break; + default: + goto syntax_err; + } + } + /* default values for empty parameters */ + if (fec == -1) + fec = FEC_AUTO; + + if (modulation == -1) + modulation = QPSK; + + if (delsys == -1) + delsys = SYS_DVBS; + + if (rolloff == -1) + rolloff = ROLLOFF_35; + +#if 0 + if (stream_id<0 || stream_id>255) + stream_id = NO_STREAM_ID_FILTER; +#endif + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + sat_no = strtoul(field, NULL, 0); + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + sr = strtoul(field, NULL, 0) * 1000; + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + vpid = strtoul(field, NULL, 0); + if (!vpid) + vpid = 0x1fff; + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + p = strchr(field, ';'); + + if (p) { + *p = '\0'; + p++; + if (bypass) { + if (!p || !*p) + goto syntax_err; + field = p; + } + } + + apid = strtoul(field, NULL, 0); + if (!apid) + apid = 0x1fff; + + tpid = -1; + if (use_vdr_format) { + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + if (use_tpid) + tpid = strtoul(field, NULL, 0); + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + strtoul(field, NULL, 0); + } + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + sid = strtoul(field, NULL, 0); + + fclose(cfp); + if (params_debug){ + printf("delivery 0x%x, ", delsys); + } else { + field = NULL; + map_to_user(delsys, system_values, &field); + printf("delivery %s, ", field); + } + + if (params_debug){ + printf("modulation 0x%x\n", modulation); + } else { + field = NULL; + map_to_user(modulation, modulation_values, &field); + printf("modulation %s\n", field); + } + + printf("sat %u, frequency %u MHz %c, symbolrate %u, ", + sat_no, freq, pol ? 'V' : 'H', sr); + + if (params_debug){ + printf("coderate 0x%x, ", fec); + } else { + field = NULL; + map_to_user(fec, coderate_values, &field); + printf("coderate %s, ", field); + } + + if (params_debug){ + printf("rolloff 0x%x stream_id %d\n" + "vpid 0x%04x, apid 0x%04x, sid 0x%04x\n", rolloff, stream_id, vpid, apid, sid); + } else { + field = NULL; + map_to_user(rolloff, rolloff_values, &field); + printf("rolloff %s stream_id %d\n" + "vpid 0x%04x, apid 0x%04x, sid 0x%04x\n", field, stream_id, vpid, apid, sid); + } + + ret = zap_to(adapter, frontend, demux, sat_no, freq * 1000, + pol, sr, vpid, apid, tpid, sid, dvr, rec_psi, bypass, + delsys, modulation, fec, rolloff, stream_id, human_readable, + params_debug); + + if (interactive) + goto again; + + if (ret) + return TRUE; + + return FALSE; + +syntax_err: + fprintf(stderr, "syntax error in line %u: '%s'\n", line, buf); + } else if (ferror(cfp)) { + fprintf(stderr, "error reading channel list '%s': %d %m\n", + filename, errno); + fclose(cfp); + return FALSE; + } else + break; + } + + fclose(cfp); + + if (!list_channels) { + fprintf(stderr, "channel not found\n"); + + if (!interactive) + return FALSE; + } + if (interactive) + goto again; + + return TRUE; +} + +static void handle_sigint(int sig) +{ + fprintf(stderr, "Interrupted by SIGINT!\n"); + exit(2); +} + +void +bad_usage(char *pname, int prlnb) +{ + int i; + struct lnb_types_st *lnbp; + char **cp; + + if (!prlnb) { + fprintf (stderr, usage_str, pname); + } else { + i = 0; + fprintf(stderr, "-l or -l low[,high[,switch]] in Mhz\nwhere is:\n"); + while(NULL != (lnbp = lnb_enum(i))) { + fprintf (stderr, "%s\n", lnbp->name); + for (cp = lnbp->desc; *cp ; cp++) { + fprintf (stderr, " %s\n", *cp); + } + i++; + } + } +} + +int main(int argc, char *argv[]) +{ + const char *home; + char chanfile[2 * PATH_MAX]; + int list_channels = 0; + unsigned int chan_no = 0; + const char *chan_name = NULL; + unsigned int adapter = 0, frontend = 0, demux = 0, dvr = 0, rec_psi = 0; + int bypass = 0; + int opt, copt = 0; + int human_readable = 0; + int params_debug = 0; + int use_vdr_format = 0; + int use_tpid = 0; + + + int delsys = -1; + int modulation = -1; + int fec = -1; + int rolloff = -1; + int stream_id = NO_STREAM_ID_FILTER; + + lnb_type = *lnb_enum(0); + while ((opt = getopt(argc, argv, "M:m:C:O:HDVhqrpn:a:f:d:S:c:l:xib")) != -1) { + switch (opt) { + case '?': + case 'h': + default: + bad_usage(argv[0], 0); + break; + case 'C': + parse_parameter(--optarg, &fec, coderate_values); + break; + case 'M': + parse_parameter(--optarg, &modulation, modulation_values); + break; + case 'Z': + case 'O': + parse_parameter(--optarg, &rolloff, rolloff_values); + break; + case 'm': + stream_id = strtol(optarg, NULL, 0); + break; + case 'S': + parse_parameter(--optarg, &delsys, system_values); + break; + case 'b': + bypass = 1; + break; + case 'q': + list_channels = 1; + break; + case 'r': + dvr = 1; + break; + case 'n': + chan_no = strtoul(optarg, NULL, 0); + break; + case 'a': + adapter = strtoul(optarg, NULL, 0); + break; + case 'f': + frontend = strtoul(optarg, NULL, 0); + break; + case 'p': + rec_psi = 1; + break; + case 'd': + demux = strtoul(optarg, NULL, 0); + break; + case 'c': + copt = 1; + strncpy(chanfile, optarg, sizeof(chanfile)); + break; + case 'l': + if (lnb_decode(optarg, &lnb_type) < 0) { + bad_usage(argv[0], 1); + return -1; + } + break; + case 'x': + exit_after_tuning = 1; + break; + case 'H': + human_readable = 1; + break; + case 'D': + params_debug = 1; + break; + case 'V': + use_vdr_format = 1; + break; + case 't': + use_tpid = 1; + break; + case 'i': + interactive = 1; + exit_after_tuning = 1; + } + } + lnb_type.low_val *= 1000; /* convert to kiloherz */ + lnb_type.high_val *= 1000; /* convert to kiloherz */ + lnb_type.switch_val *= 1000; /* convert to kiloherz */ + if (optind < argc) + chan_name = argv[optind]; + if (chan_name && chan_no) { + bad_usage(argv[0], 0); + return -1; + } + if (list_channels && (chan_name || chan_no)) { + bad_usage(argv[0], 0); + return -1; + } + if (!list_channels && !chan_name && !chan_no && !interactive) { + bad_usage(argv[0], 0); + return -1; + } + + if (!copt) { + if (!(home = getenv("HOME"))) { + fprintf(stderr, "error: $HOME not set\n"); + return TRUE; + } + snprintf(chanfile, sizeof(chanfile), + "%s/.szap/%i/%s", home, adapter, CHANNEL_FILE); + if (access(chanfile, R_OK)) + snprintf(chanfile, sizeof(chanfile), + "%s/.szap/%s", home, CHANNEL_FILE); + } + + printf("reading channels from file '%s'\n", chanfile); + + if (rec_psi) + dvr=1; + + signal(SIGINT, handle_sigint); + + if (!read_channels(chanfile, list_channels, chan_no, chan_name, + adapter, frontend, demux, dvr, rec_psi, bypass, delsys, + modulation, fec, rolloff, stream_id, human_readable, params_debug, + use_vdr_format, use_tpid)) + + return TRUE; + + return FALSE; +} diff --git a/src/szap-s2/tzap-t2.c b/src/szap-s2/tzap-t2.c new file mode 100644 index 0000000..8f1873f --- /dev/null +++ b/src/szap-s2/tzap-t2.c @@ -0,0 +1,1089 @@ +/* tzap-t2 -- simple zapping tool for the Linux DVB S2 API + * + * Copyright (C) 2008 Igor M. Liplianin (liplianin@me.by) + * Copyright (C) 2013 CrazyCat (crazycat69@narod.ru) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include "lnb.h" + +#if DVB_API_VERSION < 5 || DVB_API_VERSION_MINOR < 2 +#error szap-s2 requires Linux DVB driver API version 5.2 and newer! +#endif + +#ifndef DTV_STREAM_ID + #define DTV_STREAM_ID DTV_ISDBS_TS_ID +#endif + +#ifndef NO_STREAM_ID_FILTER + #define NO_STREAM_ID_FILTER (~0U) +#endif + +#ifndef TRUE +#define TRUE (1==1) +#endif +#ifndef FALSE +#define FALSE (1==0) +#endif + +/* location of channel list file */ +#define CHANNEL_FILE "channels.conf" + +/* one line of the szap channel file has the following format: + * ^name:frequency_KHz:inversion:bandwidth:coderate_hp:coderate_lp:modulation:transmission_mode:guard_interval:hierarchy:vpid:apid:service_id$ + * one line of the VDR channel file has the following format: + * ^name:frequency_KHz:[bandwidth][delivery][modulation][coderate_hp][coderate_lp][transmission_mode][guard_interval][hierarchy]]plp_id]:vpid:apid:?:?:service_id:?:?:?$ + */ + + +#define FRONTENDDEVICE "/dev/dvb/adapter%d/frontend%d" +#define DEMUXDEVICE "/dev/dvb/adapter%d/demux%d" +#define AUDIODEVICE "/dev/dvb/adapter%d/audio%d" + +struct t_channel_parameter_map { + int user_value; + int driver_value; + const char *user_string; + }; +/* --- Channel Parameter Maps From VDR---*/ + +static struct t_channel_parameter_map inversion_values[] = { + { 0, INVERSION_OFF, "off" }, + { 1, INVERSION_ON, "on" }, + { 999, INVERSION_AUTO }, + { -1 } + }; + +static struct t_channel_parameter_map bw_values [] = { + { 6, BANDWIDTH_6_MHZ, "6MHz" }, + { 7, BANDWIDTH_7_MHZ, "7MHz" }, + { 8, BANDWIDTH_8_MHZ, "8MHz" }, + { 5, BANDWIDTH_6_MHZ, "5MHz" }, + { 10, BANDWIDTH_10_MHZ, "10MHz" }, + { 999, BANDWIDTH_AUTO, "auto" }, + { -1 } + }; + +static struct t_channel_parameter_map coderate_values[] = { + { 0, FEC_NONE, "none" }, + { 12, FEC_1_2, "1/2" }, + { 23, FEC_2_3, "2/3" }, + { 34, FEC_3_4, "3/4" }, + { 35, FEC_3_5, "3/5" }, + { 45, FEC_4_5, "4/5" }, + { 56, FEC_5_6, "5/6" }, + { 67, FEC_6_7, "6/7" }, + { 78, FEC_7_8, "7/8" }, + { 89, FEC_8_9, "8/9" }, + { 910, FEC_9_10, "9/10" }, + { 999, FEC_AUTO, "auto" }, + { -1 } + }; + +static struct t_channel_parameter_map modulation_values[] = { + // { 0, NONE, "none" }, + { 2, QPSK, "QPSK" }, + { 10, VSB_8, "VSB8" }, + { 11, VSB_16, "VSB16" }, + { 16, QAM_16, "QAM16" }, + { 64, QAM_64, "QAM64" }, + { 128, QAM_128, "QAM128" }, + { 256, QAM_256, "QAM256" }, + { 999, QAM_16 }, + { -1 } + }; + +static struct t_channel_parameter_map mode_values [] = { + { 2, TRANSMISSION_MODE_2K, "2k" }, + { 8, TRANSMISSION_MODE_2K, "8k" }, + { 999, TRANSMISSION_MODE_AUTO, "auto" }, + { 4, TRANSMISSION_MODE_4K, "4k" }, + { 1, TRANSMISSION_MODE_1K, "1k" }, + { 16, TRANSMISSION_MODE_16K, "16k" }, + { 32, TRANSMISSION_MODE_32K, "32k" }, + { -1 } + }; + +static struct t_channel_parameter_map guard_values [] = { + { 32, GUARD_INTERVAL_1_32, "1/32" }, + { 16, GUARD_INTERVAL_1_16, "1/16" }, + { 8, GUARD_INTERVAL_1_8, "1/8" }, + { 4, GUARD_INTERVAL_1_4, "1/4" }, + { 999, GUARD_INTERVAL_AUTO, "auto" }, + { 1128, GUARD_INTERVAL_1_128, "1/128" }, + { 19128, GUARD_INTERVAL_19_128, "19/128" }, + { 19256, GUARD_INTERVAL_19_256, "19/256" }, + { -1 } + }; + +static struct t_channel_parameter_map hierarchy_values [] = { + { 0, HIERARCHY_NONE, "none" }, + { 1, HIERARCHY_NONE, "1" }, + { 2, HIERARCHY_NONE, "2" }, + { 4, HIERARCHY_NONE, "4" }, + { 999, HIERARCHY_NONE, "auto" }, + { -1 } + }; + +static struct t_channel_parameter_map system_values[] = { + { 0, SYS_DVBT, "DVB-T" }, + { 1, SYS_DVBT2, "DVB-T2" }, + { -1 } + }; + +static int user_index(int value, const struct t_channel_parameter_map * map) +{ + const struct t_channel_parameter_map *umap = map; + while (umap && umap->user_value != -1) { + if (umap->user_value == value) + return umap - map; + umap++; + } + return -1; +}; + +static int driver_index(int value, const struct t_channel_parameter_map *map) +{ + const struct t_channel_parameter_map *umap = map; + while (umap && umap->user_value != -1) { + if (umap->driver_value == value) + return umap - map; + umap++; + } + return -1; +}; + +static int map_to_user(int value, const struct t_channel_parameter_map *map, char **string) +{ + int n = driver_index(value, map); + if (n >= 0) { + if (string) + *string = (char *)map[n].user_string; + return map[n].user_value; + } + return -1; +} + +static int map_to_driver(int value, const struct t_channel_parameter_map *map) +{ + int n = user_index(value, map); + if (n >= 0) + return map[n].driver_value; + return -1; +} + +static struct lnb_types_st lnb_type; + +static int exit_after_tuning; +static int interactive; + +static char *usage_str = + "\nusage: tzap-t2 -q\n" + " list known channels\n" + " tzap-t2 [options] {-n channel-number|channel_name}\n" + " zap to channel via number or full name (case insensitive)\n" + " -a number : use given adapter (default 0)\n" + " -f number : use given frontend (default 0)\n" + " -d number : use given demux (default 0)\n" + " -c file : read channels list from 'file'\n" + " -V : use VDR channels list file format (default zap)\n" + " -b : enable Audio Bypass (default no)\n" + " -x : exit after tuning\n" + " -H : human readable output\n" + " -D : params debug\n" + " -r : set up /dev/dvb/adapterX/dvr0 for TS recording\n" + " -i : run interactively, allowing you to type in channel names\n" + " -p : add pat and pmt to TS recording (implies -r)\n" + " or -n numbers for zapping\n" + " -t : add teletext to TS recording (needs -V)\n" + " -S : delivery system type DVB-T=0, DVB-T2=1\n" + " -M : modulation 2=QPSK 16=16QAM 64=64QAM 256=256QAM\n" + " -C : fec 0=NONE 12=1/2 23=2/3 34=3/4 35=3/5 45=4/5 56=5/6 67=6/7 89=8/9 910=9/10 999=AUTO\n" + " -m : physical layer pipe 0-255\n"; + +static int set_demux(int dmxfd, int pid, int pes_type, int dvr) +{ + struct dmx_pes_filter_params pesfilter; + + if (pid < 0 || pid >= 0x1fff) /* ignore this pid to allow radio services */ + return TRUE; + + if (dvr) { + int buffersize = 64 * 1024; + if (ioctl(dmxfd, DMX_SET_BUFFER_SIZE, buffersize) == -1) + perror("DMX_SET_BUFFER_SIZE failed"); + } + + pesfilter.pid = pid; + pesfilter.input = DMX_IN_FRONTEND; + pesfilter.output = dvr ? DMX_OUT_TS_TAP : DMX_OUT_DECODER; + pesfilter.pes_type = pes_type; + pesfilter.flags = DMX_IMMEDIATE_START; + + if (ioctl(dmxfd, DMX_SET_PES_FILTER, &pesfilter) == -1) { + fprintf(stderr, "DMX_SET_PES_FILTER failed " + "(PID = 0x%04x): %d %m\n", pid, errno); + + return FALSE; + } + + return TRUE; +} + +int get_pmt_pid(char *dmxdev, int sid) +{ + int patfd, count; + int pmt_pid = 0; + int patread = 0; + int section_length; + unsigned char buft[4096]; + unsigned char *buf = buft; + struct dmx_sct_filter_params f; + + memset(&f, 0, sizeof(f)); + f.pid = 0; + f.filter.filter[0] = 0x00; + f.filter.mask[0] = 0xff; + f.timeout = 0; + f.flags = DMX_IMMEDIATE_START | DMX_CHECK_CRC; + + if ((patfd = open(dmxdev, O_RDWR)) < 0) { + perror("openening pat demux failed"); + return -1; + } + + if (ioctl(patfd, DMX_SET_FILTER, &f) == -1) { + perror("ioctl DMX_SET_FILTER failed"); + close(patfd); + return -1; + } + + while (!patread) { + if (((count = read(patfd, buf, sizeof(buft))) < 0) && errno == EOVERFLOW) + count = read(patfd, buf, sizeof(buft)); + if (count < 0) { + perror("read_sections: read error"); + close(patfd); + return -1; + } + + section_length = ((buf[1] & 0x0f) << 8) | buf[2]; + if (count != section_length + 3) + continue; + + buf += 8; + section_length -= 8; + + patread = 1; /* assumes one section contains the whole pat */ + while (section_length > 0) { + int service_id = (buf[0] << 8) | buf[1]; + if (service_id == sid) { + pmt_pid = ((buf[2] & 0x1f) << 8) | buf[3]; + section_length = 0; + } + buf += 4; + section_length -= 4; + } + } + close(patfd); + return pmt_pid; +} + +static int do_tune(int fefd, unsigned int freq, unsigned int bw, enum fe_delivery_system delsys, + int modulation, int fec_hp, int fec_lp, int mode, int guard, int hierarchy, int stream_id) +{ + struct dvb_frontend_event ev; + struct dtv_property p[] = { + { .cmd = DTV_DELIVERY_SYSTEM, .u.data = delsys }, + { .cmd = DTV_FREQUENCY, .u.data = freq }, + { .cmd = DTV_BANDWIDTH_HZ, .u.data = bw }, + { .cmd = DTV_MODULATION, .u.data = modulation }, + { .cmd = DTV_CODE_RATE_HP, .u.data = fec_hp }, + { .cmd = DTV_CODE_RATE_LP, .u.data = fec_lp }, + { .cmd = DTV_INVERSION, .u.data = INVERSION_AUTO }, + { .cmd = DTV_TRANSMISSION_MODE, .u.data = mode }, + { .cmd = DTV_GUARD_INTERVAL, .u.data = guard }, + { .cmd = DTV_HIERARCHY, .u.data = hierarchy }, + { .cmd = DTV_STREAM_ID, .u.data = stream_id }, + { .cmd = DTV_TUNE }, + }; + struct dtv_properties cmdseq = { + .num = sizeof(p)/sizeof(p[0]), + .props = p + }; + + /* discard stale QPSK events */ + while (1) { + if (ioctl(fefd, FE_GET_EVENT, &ev) == -1) + break; + } + + if ((delsys != SYS_DVBT) && (delsys != SYS_DVBT2)) + return -EINVAL; + + if ((ioctl(fefd, FE_SET_PROPERTY, &cmdseq)) == -1) { + perror("FE_SET_PROPERTY failed"); + return FALSE; + } + + return TRUE; +} + + +static +int check_frontend (int fe_fd, int dvr, int human_readable, int params_debug) +{ + (void)dvr; + fe_status_t status; + uint16_t snr, signal; + uint32_t ber, uncorrected_blocks; + int timeout = 0; + char *field; + struct dtv_property p[] = { + { .cmd = DTV_FREQUENCY }, + { .cmd = DTV_BANDWIDTH_HZ }, + { .cmd = DTV_DELIVERY_SYSTEM }, + { .cmd = DTV_MODULATION }, + { .cmd = DTV_CODE_RATE_HP }, + { .cmd = DTV_CODE_RATE_LP }, + { .cmd = DTV_TRANSMISSION_MODE }, + { .cmd = DTV_GUARD_INTERVAL }, + { .cmd = DTV_HIERARCHY }, + }; + struct dtv_properties cmdseq = { + .num = sizeof(p)/sizeof(p[0]), + .props = p + }; + + do { + if (ioctl(fe_fd, FE_READ_STATUS, &status) == -1) + perror("FE_READ_STATUS failed"); + /* some frontends might not support all these ioctls, thus we + * avoid printing errors + */ + if (ioctl(fe_fd, FE_READ_SIGNAL_STRENGTH, &signal) == -1) + signal = -2; + if (ioctl(fe_fd, FE_READ_SNR, &snr) == -1) + snr = -2; + if (ioctl(fe_fd, FE_READ_BER, &ber) == -1) + ber = -2; + if (ioctl(fe_fd, FE_READ_UNCORRECTED_BLOCKS, &uncorrected_blocks) == -1) + uncorrected_blocks = -2; + + if (human_readable) { + printf ("status %02x | signal %3u%% | snr %3u%% | ber %d | unc %d | ", + status, (signal * 100) / 0xffff, (snr * 100) / 0xffff, ber, uncorrected_blocks); + } else { + printf ("status %02x | signal %04x | snr %04x | ber %08x | unc %08x | ", + status, signal, snr, ber, uncorrected_blocks); + } + if (status & FE_HAS_LOCK) + printf("FE_HAS_LOCK"); + printf("\n"); + + if (exit_after_tuning && ((status & FE_HAS_LOCK) || (++timeout >= 10))) + break; + + usleep(1000000); + } while (1); + + if ((status & FE_HAS_LOCK) == 0) + return 0; + + if ((ioctl(fe_fd, FE_GET_PROPERTY, &cmdseq)) == -1) { + perror("FE_GET_PROPERTY failed"); + return 0; + } + /* printout found parameters here */ + if (params_debug){ + printf("frequency %d, ", p[0].u.data); + printf("bandwidth %d, ", p[1].u.data); + printf("delivery %d, ", p[2].u.data); + printf("modulation %d, ", p[3].u.data); + printf("coderate hp %d, ", p[4].u.data); + printf("coderate lp %d, ", p[5].u.data); + printf("mode %d, ", p[6].u.data); + printf("guard %d, ", p[7].u.data); + printf("hierarchy %d", p[8].u.data); + } else { + printf("frequency %d, ",p[0].u.data); + field = NULL; + map_to_user(p[1].u.data, bw_values, &field); + printf("bandwidth %s, ", field); + field = NULL; + map_to_user(p[2].u.data, system_values, &field); + printf("delivery %s, ", field); + field = NULL; + map_to_user(p[3].u.data, modulation_values, &field); + printf("modulation %s, ", field); + field = NULL; + map_to_user(p[4].u.data, coderate_values, &field); + printf("coderate-hp %s, ", field); + field = NULL; + map_to_user(p[5].u.data, coderate_values, &field); + printf("coderate-lp %s, ", field); + field = NULL; + map_to_user(p[6].u.data, mode_values, &field); + printf("mode %s, ", field); + field = NULL; + map_to_user(p[7].u.data, guard_values, &field); + printf("guard %s, ", field); + field = NULL; + map_to_user(p[8].u.data, hierarchy_values, &field); + printf("hierarchy %s", field); + } + + printf("\n"); + + return 0; +} + +static +int zap_to(unsigned int adapter, unsigned int frontend, unsigned int demux, + unsigned int freq, unsigned int bw, + unsigned int vpid, unsigned int apid, + unsigned int tpid, int sid, + int dvr, int rec_psi, int bypass, unsigned int delivery, + int modulation, int fec_hp, int fec_lp, int mode, int guard, int hierarchy, int stream_id, int human_readable, + int params_debug) +{ + struct dtv_property p[] = { + { .cmd = DTV_CLEAR }, + }; + + struct dtv_properties cmdseq = { + .num = sizeof(p)/sizeof(p[0]), + .props = p + }; + + char fedev[128], dmxdev[128], auddev[128]; + static int fefd, dmxfda, dmxfdv, dmxfdt = -1, audiofd = -1, patfd, pmtfd; + int pmtpid; + int result; + + if (!fefd) { + snprintf(fedev, sizeof(fedev), FRONTENDDEVICE, adapter, frontend); + snprintf(dmxdev, sizeof(dmxdev), DEMUXDEVICE, adapter, demux); + snprintf(auddev, sizeof(auddev), AUDIODEVICE, adapter, demux); + printf("using '%s' and '%s'\n", fedev, dmxdev); + + if ((fefd = open(fedev, O_RDWR | O_NONBLOCK)) < 0) { + perror("opening frontend failed"); + return FALSE; + } + + if ((dmxfdv = open(dmxdev, O_RDWR)) < 0) { + perror("opening video demux failed"); + close(fefd); + return FALSE; + } + + if ((dmxfda = open(dmxdev, O_RDWR)) < 0) { + perror("opening audio demux failed"); + close(fefd); + return FALSE; + } + + if ((dmxfdt = open(dmxdev, O_RDWR)) < 0) { + perror("opening teletext demux failed"); + close(fefd); + return FALSE; + } + + if (dvr == 0) /* DMX_OUT_DECODER */ + audiofd = open(auddev, O_RDWR); + + if (rec_psi){ + if ((patfd = open(dmxdev, O_RDWR)) < 0) { + perror("opening pat demux failed"); + close(audiofd); + close(dmxfda); + close(dmxfdv); + close(dmxfdt); + close(fefd); + return FALSE; + } + + if ((pmtfd = open(dmxdev, O_RDWR)) < 0) { + perror("opening pmt demux failed"); + close(patfd); + close(audiofd); + close(dmxfda); + close(dmxfdv); + close(dmxfdt); + close(fefd); + return FALSE; + } + } + } + + + result = FALSE; + + if ((ioctl(fefd, FE_SET_PROPERTY, &cmdseq)) == -1) { + perror("FE_SET_PROPERTY DTV_CLEAR failed"); + return FALSE; + } + + switch(bw) + { + case BANDWIDTH_5_MHZ: bw = 5000000; break; + case BANDWIDTH_6_MHZ: bw = 6000000; break; + case BANDWIDTH_7_MHZ: bw = 7000000; break; + case BANDWIDTH_8_MHZ: bw = 8000000; break; + case BANDWIDTH_10_MHZ: bw = 10000000; break; + case BANDWIDTH_AUTO: + default: bw = 0; + } + + if (do_tune(fefd, freq, bw, delivery, modulation, fec_hp, fec_lp, mode, guard, hierarchy, stream_id)) + if (set_demux(dmxfdv, vpid, DMX_PES_VIDEO, dvr)) + if (audiofd >= 0) + (void)ioctl(audiofd, AUDIO_SET_BYPASS_MODE, bypass); + if (set_demux(dmxfda, apid, DMX_PES_AUDIO, dvr)) { + if (rec_psi) { + pmtpid = get_pmt_pid(dmxdev, sid); + if (pmtpid < 0) { + result = FALSE; + } + if (pmtpid == 0) { + fprintf(stderr,"couldn't find pmt-pid for sid %04x\n",sid); + result = FALSE; + } + if (set_demux(patfd, 0, DMX_PES_OTHER, dvr)) + if (set_demux(pmtfd, pmtpid, DMX_PES_OTHER, dvr)) + result = TRUE; + } else { + result = TRUE; + } + } + + if (tpid != -1 && !set_demux(dmxfdt, tpid, DMX_PES_TELETEXT, dvr)) { + fprintf(stderr, "set_demux DMX_PES_TELETEXT failed\n"); + } + + check_frontend (fefd, dvr, human_readable, params_debug); + + if (!interactive) { + close(patfd); + close(pmtfd); + if (audiofd >= 0) + close(audiofd); + close(dmxfda); + close(dmxfdv); + close(dmxfdt); + close(fefd); + } + + return result; +} +static char *parse_parameter(const char *s, int *value, const struct t_channel_parameter_map *map) +{ + if (*++s) { + char *p = NULL; + errno = 0; + int n = strtol(s, &p, 10); + if (!errno && p != s) { + value[0] = map_to_driver(n, map); + if (value[0] >= 0) + return p; + } + } + fprintf(stderr, "ERROR: invalid value for parameter '%C'\n", *(s - 1)); + return NULL; +} + +static int read_channels(const char *filename, int list_channels, + uint32_t chan_no, const char *chan_name, + unsigned int adapter, unsigned int frontend, + unsigned int demux, int dvr, int rec_psi, + int bypass, unsigned int delsys, + int modulation, int fec_hp, int fec_lp, int mode, int guard, int hierarchy, int stream_id, int human_readable, + int params_debug, int use_vdr_format, int use_tpid) +{ + FILE *cfp; + char buf[4096]; + char inp[256]; + char *field, *tmp, *p; + unsigned int line; + unsigned int freq = 0, bw = 0, vpid, apid, tpid, sid; + int ret; + int trash; +again: + line = 0; + if (!(cfp = fopen(filename, "r"))) { + fprintf(stderr, "error opening channel list '%s': %d %m\n", + filename, errno); + return FALSE; + } + + if (interactive) { + fprintf(stderr, "\n>>> "); + if (!fgets(inp, sizeof(inp), stdin)) { + printf("\n"); + return -1; + } + if (inp[0] == '-' && inp[1] == 'n') { + chan_no = strtoul(inp+2, NULL, 0); + chan_name = NULL; + if (!chan_no) { + fprintf(stderr, "bad channel number\n"); + goto again; + } + } else { + p = strchr(inp, '\n'); + if (p) + *p = '\0'; + chan_name = inp; + chan_no = 0; + } + } + + while (!feof(cfp)) { + if (fgets(buf, sizeof(buf), cfp)) { + line++; + + if (chan_no && chan_no != line) + continue; + + tmp = buf; + field = strsep(&tmp, ":"); + + if (!field) + goto syntax_err; + + if (list_channels) { + printf("%03u %s\n", line, field); + continue; + } + + if (chan_name && strcasecmp(chan_name, field) != 0) + continue; + + printf("zapping to %d '%s':\n", line, field); + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + freq = strtoul(field, NULL, 0); + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + while (field && *field) { + switch (toupper(*field)) { + case 'B': + field = parse_parameter(field, &bw, bw_values); + break; + case 'M': + if (modulation == -1) + field = parse_parameter(field, &modulation, modulation_values); + else + field = parse_parameter(field, &trash, modulation_values); + break; + case 'I':/* ignore */ + field = parse_parameter(field, &ret, inversion_values); + break; + case 'C': + if (fec_hp == -1) + field = parse_parameter(field, &fec_hp, coderate_values); + else + field = parse_parameter(field, &trash, coderate_values); + break; + case 'D': + if (fec_lp == -1) + field = parse_parameter(field, &fec_lp, coderate_values); + else + field = parse_parameter(field, &trash, coderate_values); + break; + case 'T': + if (mode == -1) + field = parse_parameter(field, &mode, mode_values); + else + field = parse_parameter(field, &trash, mode_values); + break; + case 'G': + if (guard == -1) + field = parse_parameter(field, &guard, guard_values); + else + field = parse_parameter(field, &trash, guard_values); + break; + case 'Y': + if (hierarchy == -1) + field = parse_parameter(field, &hierarchy, hierarchy_values); + else + field = parse_parameter(field, &trash, hierarchy_values); + break; + case 'P': + stream_id = strtol(++field, &field, 10); + break; + case 'S': + if (delsys == -1) + field = parse_parameter(field, &delsys, system_values); + else + field = parse_parameter(field, &trash, system_values); + break; + default: + goto syntax_err; + } + } + /* default values for empty parameters */ + if (modulation == -1) + modulation = QAM_16; + + if (delsys == -1) + delsys = SYS_DVBT; + + if (fec_hp == -1) + fec_hp = FEC_AUTO; + + if (fec_lp == -1) + fec_lp = FEC_AUTO; + + if (mode == -1) + mode = TRANSMISSION_MODE_AUTO; + + if (guard == -1) + guard = GUARD_INTERVAL_AUTO; + + if (hierarchy == -1) + hierarchy = HIERARCHY_NONE; + + if (stream_id<0 || stream_id>255) + stream_id = NO_STREAM_ID_FILTER; + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + vpid = strtoul(field, NULL, 0); + if (!vpid) + vpid = 0x1fff; + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + p = strchr(field, ';'); + + if (p) { + *p = '\0'; + p++; + if (bypass) { + if (!p || !*p) + goto syntax_err; + field = p; + } + } + + apid = strtoul(field, NULL, 0); + if (!apid) + apid = 0x1fff; + + tpid = -1; + if (use_vdr_format) { + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + if (use_tpid) + tpid = strtoul(field, NULL, 0); + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + strtoul(field, NULL, 0); + } + + if (!(field = strsep(&tmp, ":"))) + goto syntax_err; + + sid = strtoul(field, NULL, 0); + + fclose(cfp); + + field = NULL; + map_to_user(bw, bw_values, &field); + printf("frequency %u KHz, bandwidth %s, ", + freq, field); + + if (params_debug){ + printf("delivery %d, ", delsys); + } else { + field = NULL; + map_to_user(delsys, system_values, &field); + printf("delivery %s, ", field); + } + + if (params_debug){ + printf("modulation %d\n", modulation); + } else { + field = NULL; + map_to_user(modulation, modulation_values, &field); + printf("modulation %s\n", field); + } + + if (params_debug){ + printf("coderate-hp %d, ", fec_hp); + } else { + field = NULL; + map_to_user(fec_hp, coderate_values, &field); + printf("coderate-hp %s, ", field); + } + + if (params_debug){ + printf("coderate-lp %d, ", fec_hp); + } else { + field = NULL; + map_to_user(fec_lp, coderate_values, &field); + printf("coderate-lp %s, ", field); + } + + if (params_debug){ + printf("mode %d, ", mode); + } else { + field = NULL; + map_to_user(mode, mode_values, &field); + printf("mode %s, ", field); + } + + if (params_debug){ + printf("guard %d, ", mode); + } else { + field = NULL; + map_to_user(guard, guard_values, &field); + printf("guard %s, ", field); + } + + if (params_debug){ + printf("hierarchy %d, ", mode); + } else { + field = NULL; + map_to_user(hierarchy, hierarchy_values, &field); + printf("hierarchy %s, ", field); + } + + printf("plp_id %d\n", stream_id); + + printf("vpid 0x%04x, apid 0x%04x, sid 0x%04x\n", vpid, apid, sid); + + ret = zap_to(adapter, frontend, demux, freq * 1000, bw, + vpid, apid, tpid, sid, dvr, rec_psi, bypass, + delsys, modulation, fec_hp, fec_lp, mode, guard, hierarchy, stream_id, human_readable, + params_debug); + + if (interactive) + goto again; + + if (ret) + return TRUE; + + return FALSE; + +syntax_err: + fprintf(stderr, "syntax error in line %u: '%s'\n", line, buf); + } else if (ferror(cfp)) { + fprintf(stderr, "error reading channel list '%s': %d %m\n", + filename, errno); + fclose(cfp); + return FALSE; + } else + break; + } + + fclose(cfp); + + if (!list_channels) { + fprintf(stderr, "channel not found\n"); + + if (!interactive) + return FALSE; + } + if (interactive) + goto again; + + return TRUE; +} + +static void handle_sigint(int sig) +{ + fprintf(stderr, "Interrupted by SIGINT!\n"); + exit(2); +} + +void +bad_usage(char *pname) +{ + fprintf (stderr, usage_str, pname); +} + +int main(int argc, char *argv[]) +{ + const char *home; + char chanfile[2 * PATH_MAX]; + int list_channels = 0; + unsigned int chan_no = 0; + const char *chan_name = NULL; + unsigned int adapter = 0, frontend = 0, demux = 0, dvr = 0, rec_psi = 0; + int bypass = 0; + int opt, copt = 0; + int human_readable = 0; + int params_debug = 0; + int use_vdr_format = 0; + int use_tpid = 0; + + + int delsys = -1; + int modulation = -1; + int fec = -1; + int stream_id = NO_STREAM_ID_FILTER; + + while ((opt = getopt(argc, argv, "M:m:C:O:HDVhqrpn:a:f:d:S:c:l:xib")) != -1) { + switch (opt) { + case '?': + case 'h': + default: + bad_usage(argv[0]); + break; + case 'C': + parse_parameter(--optarg, &fec, coderate_values); + break; + case 'M': + parse_parameter(--optarg, &modulation, modulation_values); + break; + case 'm': + stream_id = strtol(optarg, NULL, 0); + break; + case 'S': + parse_parameter(--optarg, &delsys, system_values); + break; + case 'b': + bypass = 1; + break; + case 'q': + list_channels = 1; + break; + case 'r': + dvr = 1; + break; + case 'n': + chan_no = strtoul(optarg, NULL, 0); + break; + case 'a': + adapter = strtoul(optarg, NULL, 0); + break; + case 'f': + frontend = strtoul(optarg, NULL, 0); + break; + case 'p': + rec_psi = 1; + break; + case 'd': + demux = strtoul(optarg, NULL, 0); + break; + case 'c': + copt = 1; + strncpy(chanfile, optarg, sizeof(chanfile)); + break; + case 'x': + exit_after_tuning = 1; + break; + case 'H': + human_readable = 1; + break; + case 'D': + params_debug = 1; + break; + case 'V': + use_vdr_format = 1; + break; + case 't': + use_tpid = 1; + break; + case 'i': + interactive = 1; + exit_after_tuning = 1; + } + } + if (optind < argc) + chan_name = argv[optind]; + if (chan_name && chan_no) { + bad_usage(argv[0]); + return -1; + } + if (list_channels && (chan_name || chan_no)) { + bad_usage(argv[0]); + return -1; + } + if (!list_channels && !chan_name && !chan_no && !interactive) { + bad_usage(argv[0]); + return -1; + } + + if (!copt) { + if (!(home = getenv("HOME"))) { + fprintf(stderr, "error: $HOME not set\n"); + return TRUE; + } + snprintf(chanfile, sizeof(chanfile), + "%s/.tzap/%i/%s", home, adapter, CHANNEL_FILE); + if (access(chanfile, R_OK)) + snprintf(chanfile, sizeof(chanfile), + "%s/.tzap/%s", home, CHANNEL_FILE); + } + + printf("reading channels from file '%s'\n", chanfile); + + if (rec_psi) + dvr=1; + + signal(SIGINT, handle_sigint); + + if (!read_channels(chanfile, list_channels, chan_no, chan_name, + adapter, frontend, demux, dvr, rec_psi, bypass, delsys, + modulation, fec, -1, -1, -1, -1, stream_id, human_readable, params_debug, + use_vdr_format, use_tpid)) + + return TRUE; + + return FALSE; +} diff --git a/ttxd.service b/ttxd.service index f0133b8..9229e2e 100644 --- a/ttxd.service +++ b/ttxd.service @@ -4,7 +4,7 @@ After=network.target [Service] ExecStart=/opt/ttxd/serv.sh -ExecStop=/usr/bin/killall dvbtext tzap thttpd +ExecStop=/usr/bin/killall dvbtext tzap-t2 thttpd KillMode=process Type=forking -- 2.39.3