Commit 229b2cf4 authored by Olav Kvittem's avatar Olav Kvittem

No commit message

No commit message
parent 778fbbe0
Qstream Computes packet stream quality statistics for UDP/RTP from network or captured files(pcap)
see http://software.uninett.no/qstream
#!/usr/bin/perl
# compute packet stream quality statistics for UDP/RTP from network or captured files(pcap)
# use Socket;
use IO::Socket qw(:DEFAULT :crlf);
use IO::Socket::INET;
use IO::Socket::Multicast;
use IO::Select;
use Net::Pcap;
use NetPacket::Ethernet qw(:strip);
use NetPacket::IP;
use NetPacket::IP qw(:protos);
use NetPacket::UDP;
use Net::RTP;
use Net::RTP::Packet;
use Time::HiRes qw/ sleep time tv_interval gettimeofday/;
use Data::Dumper;
use constant MTU => 1580;
$DEFAULT_PORT = 1955;
my $timeout=1 ; # frac seconds to wait for receiving packets
require "newgetopt.pl";
$usage="$0 [option]... [file...|ip/|:port]...
-list list flows in files
-dump dump data part to file
-format [full|pretty] print full numbers foror short pretty numbers
-net open network stream rather than file(s)
-rtp look forinto rtp headers (default just udp)
-mpeg decode mpeg-2 transport stream and look for sequence and mediatime
-pcap listen to all multicast groups with pcap at the same time
-period seconds - period in seconds between stats output
-nperiod number of periodes
-last seconds - exit after seconds
-packets packets - exit after packets
-src source - limit stats to given source
-flow_key flow key - limit stats to this flow(src_ip:src_port->dest_ip:dest_port)
-flow_no flow no - limit stats to this flow no from -list
-flow_min numb - discard flows with less packets that numb
-nohead - no headings for batch use
-id name - report name instead of flow_key in flows
-sum name - make a summary of all flows to name rather than per flow
-verbose - more details packet distances and sizes
-debug - print debugging info
-help/h this message
\n";
@opts=('list', 'fullformat', 'net', 'rtp', 'mpeg', 'pcap', 'nperiod=s', 'period=s', 'last=s', 'packets=s', 'src=s', 'flow_key=s', 'flow_no=s', 'flow_min=s', 'nohead', 'dump=s', 'format=s', 'id=s', 'sum=s', 'verbose', 'v', 'help', 'h', 'debug');
&NGetOpt(@opts) || die $usage;
die $usage if $opt_h or $opt_help;
$continous= ! $opt_packets;
$opt_v=$opt_v or $opt_verbose;
my @streams=@ARGV;
my $format = $opt_format || "pretty";
my %hdr=();
my $err="";
my $us=0; # current packet time in us
my $nkill=0;
my $nint=0;
my $n_packets=0;
my $endstream=0;
$SIG{INT} = sub { $uninterrupted=0; # return if $nint++ < 1;
&display_stats(); die "End after interrupt.\n";exit(0) };
$SIG{KILL} = sub { $uninterrupted=0; # return if $nkill++ < 1;
&display_stats(); die "End after kill.\n";exit(0) };
$SIG{ALRM} = sub {
# die "No packets received" if $n_packets < 1;
# $uninterrupted=0;
&display_stats();
$endstream=1;
print "endstream=1" if $opt_debug;
};
if ($opt_dump){
open DUMP, ">$opt_dump";
}
# $wanted_flow = $opt_flow_key;
if ($opt_pcap){ # listen in parallell
alarm($opt_last) if $opt_last;
$uninterrupted=1;
$endstream=0;
eat_pcap_streams(@streams);
&display_stats();
} else { # listen serially
foreach $id (@streams) {
alarm($opt_last) if $opt_last;
$uninterrupted=1;
$endstream=0;
init_file($id);
eat_stream($id);
if (! ( $opt_list || $opt_sum)){
&display_stats() if !$endstream;
}
last if ! $uninterrupted;
}
}
&list_flows if $opt_list;
&display_stats() if $opt_sum;
exit(0);
#------------------------------------------------------------------------------
# unpack uri's to ip-address
sub prepare_ip {
my $uri=shift;
$uri =~ s/^(udp|rtp)://;
$uri =~ s#//##;
die "Unsupported streaming $1" if $uri =~/^(\w:)/;
my $multicast = $uri =~ s/^@//;
my ($host, $port)=split(/[\/:]/, $uri);
die $usage . "undefined address inn $host" unless (defined $host);
$port = $DEFAULT_PORT unless (defined $port);
my $address=$host;
if ($host !~ /^\d+\.\d+\.\d+\.\d+$/){ # hostname
my $padr=gethostbyname($host) || die "Gethostbyname - $host - $padr : $!";
$address=Socket::inet_ntoa($padr);
}
my @nib=split(/\./,$address);
$multicast = $multicast || $nib[0] >= 224 && $nib[0] <= 240;
return ($address, $port, $multicast);
}
sub eat_stream {
my $id=shift;
$tstart{$id}=[gettimeofday];
if ($opt_net && $opt_rtp){
# Create RTP socket
my $elapsed=0;
my $t0=0.0;
$n_packets=0;
my ($address, $port, $multicast) = prepare_ip($id);
my $rtp = new Net::RTP( LocalPort=>$port,
LocalAddr=>$address ) || die "Failed to create RTP socket: $!";
my $select=IO::Select->new($rtp->fileno);
my $tjoined=[gettimeofday];
if ($opt_align){ # align to $opt_period
}
while ($uninterrupted && !($opt_packets && $n_packets > $opt_packets)) {
my $packet;
last if ! $uninterrupted || $endstream;
if ($select->can_read($timeout) && ($packet = $rtp->recv())){
my $ssrc = $packet->ssrc();
$cur_id=$packet->source_ip . ", $address:$port";
if ( !$file{$cur_id}){
push(@myflows, $cur_id);
$file{$cur_id}=$id;
}
$n_packets++;
next if $opt_src and $opt_src ne $cur_id;
$tc=[gettimeofday]; # current flow time
if( $t0){
$elapsed=tv_interval ( $t0, $tc);
} else {
$t0 = $tc;
$elapsed=0.0;
&init_stats($cur_id);
}
$us=$elapsed*10**6;
$tjoined{$cur_id} = $tjoined if !$tjoined{$cur_id};
# No stats for that SSRC yet?
&pkt_stats($cur_id, $packet->encode, $elapsed * 10**6, $packet->size());
if ($opt_dump){
print DUMP $packet;
}
} else { # anticipate interrupted by alarm
# warn "Failed to receive $n_packets packets from $address:$port : $!";
# next; #### exit
}
}
# &display_stats();
# $mc->shutdown; # gives error message
} elsif ($opt_net) { # udp stream
# Create UDP socket
my $elapsed=0;
my $t0=0.0;
$n_packets=0;
my ($address, $port, $multicast) = prepare_ip($id);
my $mc;
if ( $multicast){
$mc = IO::Socket::Multicast->new(LocalPort=>$port, LocalAddr=>$address, ReuseAddr=>1) ||
die "Could not connect to port $port : $!";
$mc->mcast_add($address) || die "could not join $address:$port : $!";
} else { # unicast
$mc = IO::Socket::INET->new( LocalAddr=> $address, LocalPort => $port);
}
my $tjoined=[gettimeofday];
my $select=IO::Select->new($mc->fileno);
if ($opt_align){ # align to $opt_period
}
while ($uninterrupted && ! $endstream && !($opt_packets && $n_packets > $opt_packets)){
if ($select->can_read($timeout) && $mc->recv($packet, 1560)) {
last if ! $uninterrupted || $endstream;
$n_packets++;
my $ssrc = $mc->peerhost;
my $cur_id = $mc->peerhost . ":" . $mc->peerport . "->$address:$port";
if ( !$file{$cur_id}){
push(@myflows, $cur_id);
$file{$cur_id}=$id;
}
next if $opt_src and $opt_src ne $cur_id;
$tc=[gettimeofday]; # current flow time
if( $t0){
$elapsed=tv_interval ( $t0, $tc);
} else {
$t0 = $tc;
$elapsed=0.0;
&init_stats($cur_id);
}
$us=$elapsed*10**6;
$tjoined{$cur_id} = $tjoined if !$tjoined{$cur_id};
# No stats for that SSRC yet?
&pkt_stats($cur_id, $packet, $elapsed * 10**6, length($packet));
if ($opt_dump){
print DUMP $packet;
}
} else { # assume that we got an interrupt
# die "Failed to receive packet: $!";
}
}
# &display_stats();
# $mc->mcast_drop($address);
} else { # files
my $f=$id;
my $pcap;
my $tmp_file;
my $pcap_file=$f;
if ($f =~ /\.gz$|\.Z/){ # gzipped ?
$tmp_file="/tmp/pcap$$";
$pcap_file=$tmp_file;
system( "/bin/zcat $f > $tmp_file") ; # rc code ? || die "Could not unpack $f to $tmp_file : $!";
}
if ($pcap=Net::Pcap::open_offline($pcap_file, \$err)){
# my $select=IO::Select->new(Net::Pcap::fileno($pcap));
until ( ! $uninterrupted || $endstream || ($opt_packets && $n_packets > $opt_packets)
|| ! (my $pkt=Net::Pcap::next($pcap, \%hdr) ) ) {
eat_pcap($f, $pcap, $pkt);
$n_packets++;
}
} else { # open failed
if ($format ne "full"){ # always put out data when format full
die "Could not open $f : $err";
}
}
undef $t0;
unlink $tmpfile if $tmp_file;
} # of files
} # of eat_stream
sub eat_pcap_streams{ # open groups and listen on pcap
my @streams=@_;
my $filter="( ";
my $mc = IO::Socket::Multicast->new(LocalPort=>$DEFAULT_PORT, ReuseAddr=>1) ||
die "Could not connect to port $port : $!";
foreach $id (@streams){ # listen to all groups
my ($host, $port) = split(/[\/:]/, $id);
die $usage . "undefined address inn $id" unless (defined $host);
$port = $DEFAULT_PORT unless (defined $port);
my $address=$host;
if ($host !~ /^\d+\.\d+\.\d+\.\d+$/){ # hostname
my $padr=gethostbyname($host);
$address=Socket::inet_ntoa($padr);
}
$mc->mcast_add($address) || die "could not join $address:$port : $!";
$filter .= " or " if $filter ne "( ";
$filter .= "host $address";
}
$tjoined{$f}=[gettimeofday];
$filter .= " )";
# $filter .= " and ip multicast";
# $filter ="ip";
my ($net, $netmask, $filter_t, $err, $dev);
# printf $filter;
$dev = Net::Pcap::lookupdev(\$err) || die "Could not lookupdev $dev : $err : $!";
my $r = Net::Pcap::lookupnet( $dev, \$net, \$netmask, \$err);
die "Failed lookupnet for $dev : $r : $err : $!" if $r != 0;
$pcap=Net::Pcap::open_live( $dev, 100, 1, 0,\$err) || die "Could not open $dev : $err";
$r=Net::Pcap::compile($pcap, \$filter_t, $filter, 0, $netmask);
die "Could not compile filter : $r : $filter : $!)" if $r != 0;
$r=Net::Pcap::setfilter($pcap, $filter_t);
die "Could not set pcap filter : $r : $filter : $! " if $r != 0 ;
my $select=IO::Select->new( Net::Pcap::fileno($pcap));
while ($select->can_read($timeout) && (my $pkt=Net::Pcap::next($pcap, \%hdr)) ) { # get all packets
last if ! $uninterrupted || $endstream;
eat_pcap("pcap", $pcap, $pkt);
$n_packets++;
last if $opt_packets && $n_packets > $opt_packets;
}
}
sub eat_pcap { # process pcap packets
my ($f, $pcap, $pkt)=@_;
my $ip=null;
my $rtp = new Net::RTP::Packet(); # spare rtp obj
if (Net::Pcap::datalink($pcap) == 1){ # ethernet
$ip = NetPacket::IP->decode(eth_strip($pkt));
} elsif (Net::Pcap::datalink($pcap) == 113) { # DLT_LINUX_SLL
my ($head, $data)=unpack('a16a*', $pkt);
$ip = NetPacket::IP->decode($data);
} else {
die "Invalid link layer type : ".Net::Pcap::datalink($pcap);
}
next if $ip->{proto} != IP_PROTO_UDP;
my $udp = NetPacket::UDP->decode($ip->{data});
my $dlen = $ip->{len} - $ip->{hlen}*4;
my $flow_key;
if ($opt_sum){
$flow_key=$opt_sum;
} else {
$flow_key=sprintf "%s:%d->%s:%d",
$ip->{src_ip}, $udp->{src_port}, $ip->{dest_ip}, $udp->{dest_port};
}
$flow_pkts{$flow_key}++;
if ($flow_pkts{$flow_key} <= 1) { # new flow
push(@flows, $flow_key); #
$flow_no++;
$file{$flow_key}=$f;
$source{$flow_key} =sprintf "%s:%d", $ip->{src_ip}, $udp->{src_port};
}
next if $opt_list; # just count packets per flow
next if $opt_flow_key && $flow_key !~ $wanted_flow;
next if $opt_flow_no && $opt_flow_no !~ /\b$flow_no\b/;
if ($flow_pkts{$flow_key} <= 1) { #
&init_stats($flow_key);
push (@myflows, $flow_key);
# printf "$flow_key\n";
}
$tc= [$hdr{tv_sec}, $hdr{tv_usec}];
if (! defined($t0)) {
$t0=$tc;
}
$tjoined{$f}=$tc if !$tjoined{$f};
$tjoined{$flow_key} = $tjoined{$f} if !exists($tjoined{$flow_key});
$us = tv_interval ( $t0, $tc) * 10**6 ; # us relative packet time
# fixate on first flow to appear
# if ( $first_port) {
# next if $udp->{dest_port} != $first_port;
# } else {
# $first_port = $udp->{dest_port};
# }
&pkt_stats($flow_key, $udp->{data}, $us, $dlen);
}
sub init_stats {
my $f=shift;
$pinterval=0;
$npkt{$f}=$sumbyte{$f}=$ssbyte{$f}=0;
$first_sec=0;
delete $pmtime{$f}; delete $pus{$f} ; delete $first_us{$f}; delete $last_us{$f};
delete $prev_1s{$f}; delete $prev_100ms{$f};
delete $per1s{$f}; delete $per100ms{$f};
delete $sum1s{$f}; delete $sum100ms{$f};
$n100ms{$f}=0; $sum100ms{$f}=0; $ss100bps{$f}; $akk100ms{$f}=0;
$sumbyte{$f}=$sumps{$f}=0; # sum byte per second
$maxbps{$f}=0; $max100ms{$f}=0;
$dup{$f}=$lost{$f}=$late{$f}=0;
$maxbyte{$f}=0; $minbyte{$f}=2**31;
$sumgap{$f}=$ssgap{$f}=0;
$ngap{$f}=0; $maxgap{$f}=0, $mingap{$f}=2**63-5;
$njitter{$f}=$sumjitter{$f}=$ssjitter{$f}=0;
$maxjitter{$f}=-2**63-5; $minjitter{$f}=2**63-5; delete $est_jitter{$f}; delete $pest_jitter{$f};
$first_port = 0; $n_seq=-1;
delete $tstart{$f}; delete $tend{$f};
}
sub init_file {
undef $t0;
my $f=shift;
undef $tjoined; delete $tjoined{$f}; delete $setuptime{$f}; delete($joined{$file{$f}});
delete $file{$f};
@myflows=() if !$opt_sum;
$flow_pkts{$f}=0; # new flow on new file
undef $pus;
undef $psec;
delete($pmtime{$f});
delete($pseq_num{$f});
delete($pus{$f});
}
sub pkt_stats {
my $f = shift; # file or stream name
my $packet = shift;
my $rtp;
my $us = shift;
my $dlen = shift;
my $rtp = new Net::RTP::Packet();
if (! exists($tstart{$f})){
$tstart{$f} = $tc;
$first_us{$f}=$prev1s_us{$f}=$prevt100ms_us{$f}=$us;
if (! exists($setuptime{$f}) ){
if ( $pinterval){
$setuptime{$f}=0;
} else {
$setuptime{$f}=tv_interval ( $tjoined{$f}, $tstart{$f}) * 10**3;
}
printf "setup $f : %.3f\n", $setuptime{$f} if $opt_debug;
}
}
$tend{$f} = $tc;
$last_us{$f}=$us;
# next if $dlen < 20; # typically small background noice packets added
$npkt{$f}++;
$sumbyte{$f}+=$dlen; $sum1s{$f}+=$dlen; $sum100ms{$f}+=$dlen;
$ssbyte{$f}+=$dlen**2;
$maxbyte{$f}= $dlen if $dlen > $maxbyte{$f};
$minbyte{$f}= $dlen if $dlen < $minbyte{$f};
$sumps{$f}+=$dlen; # sum per second
if ($opt_dump){
print DUMP $packet;
}
if ($opt_rtp){
if ( $rtp->decode($packet) ) { #assume RTP
# printf "RTP: %10s %5d %5d\n", $rtp->ssrc(), $rtp->timestamp(), $rtp->size();
# $dlen = $rtp->size();
} else {
print "??? $f # $npkt{$f} $dlen B\n";
if ($opt_debug){
my $n=0;
foreach $w( unpack("C*", $packet)) {
printf "%0X",$w;
if ($n > 0){
print " " if $n % 4 == 0;
print "\n" if $n % 32 == 0;
}
$n++;
}
printf "\n";
}
next;
}
# Calculate next number in sequence
$n_seq = $rtp->seq_num()+1;
if ($n_seq > 65535) {
$n_seq=0;
}
$mtime=$rtp->timestamp() * &time_unit($rtp->payload_type); # mediatime in us
}
if (defined($pus{$f})) {
$gap = $us - $pus{$f}; # packet time gap
$ngap{$f}++;
$sumgap{$f} += $gap;
$ssgap{$f} += $gap**2;
$maxgap{$f}= $gap if $gap > $maxgap{$f};
$mingap{$f}= $gap if $gap < $mingap{$f};
if ($opt_rtp){
my $seq_diff= $rtp->seq_num - $pseq_num{$f};
# printf "%0d-%0d, ", $seq_diff, $n_seq - $rtp->seq_num;
if ($seq_diff != 1) {
# printf "seq %d,", $rtp->seq_num;
if ($seq_diff == 0) { # Duplicated
$dup{$f} ++;
} elsif ($seq_diff < 0) { # Out Of Order
$late{$f}++;
# $lost{$f}--;
} else { # Lost
$lost{$f}+= $seq_diff - 1;
$nloss{$f}++;
$sumloss{$f} += $seq_diff;
$ssumloss{$f} += $seq_diff**2;
$maxloss{$f} = $seq_diff if $seq_diff > $maxloss{$f};
}
} elsif ( $mtime <= $pmtime{$f}){
$ntimeerr{$f}++;
} else { # the jitter can be measured
# compute jitter relative to previous packet
$mgap= ($mtime - $pmtime{$f} ); # media time gap in samples 8khz=us
$jitter=abs($gap - $mgap);
#ok compute jitter relative to start
#ok $mgap= ($mtime - $first_mtime); # media time gap in us
#ok $jitter=abs($us - $mtime - $first_jitter);
$njitter{$f}++;
$sumjitter{$f} += $jitter;
$ssjitter{$f} += $jitter**2;
$maxjitter{$f}= $jitter if $jitter > $maxjitter{$f};
$minjitter{$f}= $jitter if $jitter < $minjitter{$f};
# rfc 3550
if (defined($est_jitter{$f})){
$est_jitter{$f} = $est_jitter{$f} + ( $jitter - $est_jitter{$f} )/16;
} else { # inital value
$est_jitter{$f} = $jitter;
}
printf "%5d %6d %5d %5d %5d %5d %5d\n",$rtp->seq_num,$us,$rtp->timestamp(), $gap, $mgap, $jitter, $first_jitter if $opt_debug;
}
} elsif ($opt_mpeg){ # incomplete code under exploration
# meg transport stream layout http://en.wikipedia.org/wiki/Transport_stream
my ($flag, $b2, $b3, $rest)= unpack "H2n1C1C*", $packet;
# my ($ts_err, $payload, $ts_pri)=split( //, unpack "B3", $b2); # does not work
my ($ts_err, $payload, $ts_pri) = ( $b2 & 0x8000 , $b2 & 0x4000, $b2 & 0x2000);
my $pid= $b2 & 0x1F00;
# my ($scramble1, $scramble2, $adapt, $data) = split( //, unpack "B4", $b3); # not working
my ($scramble1, $scramble2, $adapt, $data) = ( $b3 & 0x80, $b3 & 0x40, $b3 & 0x20, $b3 & 0x10);
my $contin= $b3 & 0x0F;
# printf "%5d %5s %5d %5X %5d %5d %s\n", $npkt{$f}, $flag, $pid, $b2, $data, $contin, unpack("H*",$packet)
# if $payload > 0 ; # $contin == 13; #$ts_pri > 0 ; # $b2 > 256;
if ($payload && $pid > 0){ # PES follows http://en.wikipedia.org/wiki/Packetized_Elementary_Stream
my ($ts_prefix, $code, $id, $pes_lth, $pes_rest )= unpack("H8H6C1C2C*", $packet);
# printf "%5d %5s %5d %5X %5d %5d %s\n", $npkt{$f}, $flag, $pid, $code, $data, $id, unpack("H*",$packet);
if ($code == 0x000001){
if ( $id >= 0xE0 ){ # video PES-header
my ($ts_prefix, $pes_head, $pes_opt, $head_lth)=unpack("H8H6C2C1", $packet);
if ($pes_opt & 0x0080){ # PTS ind
printf "%5d %5s %5d %5X %5d %5d %s\n", $npkt{$f}, $flag, $pid, $id, $pes_lth, $head_lth, unpack("H*",$packet);
}
}
}
}
if ($adapt == 1 ) { # adaption field coming
my ($length, $aflags, $rest2)=unpack "C1C1C*", $rest;
my ($dif, $raif, $espif, $pcrf, $opcrf, $splicef, $tpdf, $afextf)=split(//, $aflags);
if ($pcrf == 1){
;
}
}
}
&burst($f);
}
if ($opt_period){
$interval=int($us/10**6 / $opt_period);
if ($interval > $pinterval){
&display_stats() ;
$pinterval=$interval;
$pinterval{$f}=$interval;
$endstream = $opt_nperiod && $interval >= $opt_nperiod;
}
}
$pus=$us;
$psec=$sec;
$pmtime{$f}=$mtime if $opt_rtp;
$pseq_num{$f}=$rtp->seq_num if $opt_rtp;
$pus{$f}=$us;
}
sub burst {
my $f=shift;
my $span=$last_us{$f}-$first_us{$f};
my $per1s=int($span/10**6);
my $per100ms=int($span/10**5);
if ( $per1s > $per1s{$f} ){ # period expired
my $per=($last_us{$f}-$prev_1s{$f})/10**6;
if ($per>0){
my $bps = $sum1s{$f} * 8 / $per;
$maxbps{$f} = $bps if $bps > $maxbps{$f};
$per1s{$f} = $per1s;
$prev_1s{$f} = $last_us{$f};
$sum1s{$f}=0;
}
}
if ( $per100ms > $per100ms{$f} ){ # period expired
my $per=($last_us{$f}-$prev_100ms{$f})/10**6;
if ($per>0){
my $bps = $sum100ms{$f} * 8 / $per;
$max100ms{$f} = $bps if $bps > $max100ms{$f};
$n100ms{$f}++;
$ss100bps{$f}+= $bps**2;
$akk100ms{$f}+=$bps;
$per100ms{$f} = $per100ms;
$prev_100ms{$f} = $last_us{$f};
$sum100ms{$f}=0;
# print "$bps ";
}
}
}
sub display_stats{
# my $f = shift;
my $media="file";
# $media = "host, group" if $opt_net;
$media = "host:port" if $opt_net;
@myflows=@streams if ($#myflows < 0); # no response
foreach $f (sort @myflows) {
my $id=$f;
next if $npkt{$f} < $opt_flow_min;
my $source=$f;
$source=$source{$f} if ! $opt_v && $source{$f} ne "";
$source=$opt_id if $opt_id;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($tstart{$f}->[0]);
my $time=sprintf "%02d:%02d:%02d", $hour, $min, $sec;
if ($format eq "full"){
$time.=sprintf ".%06d", $tstart{$f}->[1];
} else {
my $time=sprintf "%02d:%02d:%02d", $hour, $min, $sec;
}
my $date=sprintf "%4d-%02d-%02d", $year+1900, $mon+1, $mday;
my $span=&tv_interval($tstart{$f}, $tend{$f}); # second
&burst($f); # in case a second is interrupted
# printf "dus %d min %.2f\n", $us-$pus, $max100ms{$f};
# $max100ms{$f}=-1 if $max100ms{$f} > 2**61;
# $maxbps{$f}=-1 if ! $maxbps{$f} > 0;
if ($opt_v) {
if ($date ne $pdate) {
if (! $opt_nohead){
print "\nDate $date packet size(byte) packet distance(ms) ";
print " time sequence loss packet jitter(ms) " if $opt_rtp;
print " thrust(bps) source\n";
printf "%-8s %6s %6s %4s%4s %4s %4s %4s %6s %4s %4s %4s %4s",
"time","span", "setup",
"numb ", "avg", "sdv", "min", "max",
"numb", "avg", "sdv", "min", "max";
printf " %5s %5s %5s %5s %5s %5s %5s",
"stamp", "dup", "late", "lost", "gaps", "avg", "sdv", if $opt_rtp;
printf " %6s %4s %4s %4s %4s %4s ", "numb", "avg", "sdv", "min", "max", "rfc" if $opt_rtp;
printf " %5s %5s %5s %5s %-15s\n",
"avg", "1s", "100ms", "sdv", $media;
}
}
if ( $npkt{$f} >= 0 && $ngap{$f}>= 0) {
if (! $opt_rtp) { $minjitter{$f}=0; $maxjitter{$f}=0;}
if ($ngap{$f} < 1 ){
$mingap{$f}=0; $maxgap{$f}=0;
$max100ms{$f}=0; $maxbps{$f}=0;
}
# my $dup=&lesbar($dup{$f}+$ntimeerr{$f});
# $dup .= "T", if $ntimeerr{$f} > 0;
# $dup .= "S", if $dup{$f} > 0;
printf "%8s %6s %6s %8s",
$time, &lesbars($span,3), &lesbars($setuptime{$f},3), &lesbar($npkt{$f},8);
my $n=$npkt{$f}; $n=1 if $npkt{$f} <1; # divide by zero protection
printf " %4s %4s %4d %4d ", &lesbar($sumbyte{$f}/$n,4),
&lesbar(sdv($npkt{$f}, $sumbyte{$f}, $ssbyte{$f})),
$minbyte{$f}, $maxbyte{$f};
printf " %6s %4s %4s %4s %4s",
&lesbar($ngap{$f},6), &lesbars( avg( $sumgap{$f}, $ngap{$f}) / 1000 ),
&lesbars(sdv($ngap{$f}, $sumgap{$f},$ssgap{$f})/1000),
&lesbars($mingap{$f}/1000), &lesbars($maxgap{$f}/1000);
printf " %5s %5s %5s %5s %5s %5s %5s",
&lesbar($ntimeerr{$f}), &lesbar($dup{$f}), &lesbar($late{$f}), &lesbar($lost{$f}),
&lesbar($nloss{$f}), &lesbar( avg( $sumloss{$f}, $nloss{$f}) ),
&lesbar( sdv($lost{$f}, $sumloss{$f}, $ssumloss{$f}) )
if $opt_rtp;
printf " %6s %4.1f %4.1f %4.1f %4.1f %4.1f ",
&lesbar($njitter{$f}),
avg($sumjitter{$f}, $njitter{$f})/1000,
sdv($njitter{$f}, $sumjitter{$f}, $ssjitter{$f})/1000,
$minjitter{$f}/1000, $maxjitter{$f}/1000,
$est_jitter{$f}/1000
if $opt_rtp;
printf " %5s %5s %5s %5s %s\n",