Code:Server bandwidthdetection

From Red5Tutorials

Jump to: navigation, search

Port of bandwidth detection from FMS to Red5 by Dan Rossi (2007/02/08)

Dan writes: Hi there ive taken the initiative because i cant seem to find any other solution out there to port the FMS code. Ive prob made alot of mistakes in terms of the HashMap and arrays. I had to also port the client.getStats method from FMS which returns information in methods returned from red5 but not easily accessible via variables, maybe there is a better way of accessing this until the stats API is avail ?

Basically all it does is send a packet array and calculate the result time to work out the download rate or something like that ? Ive yet to manage to get the right calculation for the download rate as i was testing it locally, i dont think that one is right. If someone wants to clean it up go ahead :)

Port of bandwidth detection from FMS to Red5 by Dan Rossi (2007/03/23)

Dan Writes: "Hi it seems there has been an update on the wowza list to the bandwidth detection source, the current implementation sends packets 3 times which creates a tiny lag on startup, the calculations are more correct, so ive migrated and merged some of the code. Full credits here

http://www.wowzamedia.com/forums/showthread.php?t=169

Ive supplied a quick flex example and tarball source code aswell as the flex source, it would be good to find out how to optimise it a little better, maybe a better way to send AMF packet byte data, aswell as storing the hashmaps with the stats information for the beginning and end. Finally it would be good to implement into a bean you just add into the xml config and it does the bandwidth detection in the onconnect method ?

If you keep refreshing you may see that the numbers vary so its not enitely exact. It would be good to know if there is a network framework that is capable of doing these kind of calculations ? As this code still seems abit clunky and can be better as it was a port from the FMS code to begin with anyway.

http://www.electroteque.org:5080/bwcheck/bwcheck.html http://www.electroteque.org:5080/bwcheck/bwcheck.mxml http://www.electroteque.org:5080/bwcheck/bwcheck.tar.gz "


Calling example

public boolean appConnect(IConnection conn) {
       
        BandwidthDetection detect = new BandwidthDetection();
        detect.checkBandwidth(conn);
}

Class IBandwidthDetection.java

import org.red5.server.api.IConnection;

public interface IBandwidthDetection {
       
   public void checkBandwidth(IConnection p_client);
       
   public void calculateClientBw(IConnection p_client);
}

Class BandwidthDetection.java:

import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.stream.IStreamCapableConnection;

import org.apache.log4j.Logger;

import java.util.*;

import java.util.Map;
import java.util.HashMap;

public class BandwidthDetection implements IPendingServiceCallback, IBandwidthDetection {
   
   IConnection client = null;
   double latency = 0;
   double cumLatency = 1;
   int count = 0;
   int sent = 0;
   double kbitDown = 0;
   double deltaDown = 0;
   double deltaTime = 0;
   
   List<Long> pakSent = new ArrayList<Long>();
   List<Long> pakRecv = new ArrayList<Long>();
   
   private Map<String, Long> beginningValues;
   private double[] payload = new double[1200];
   private double[] payload_1 = new double[12000];
   private double[] payload_2 = new double[12000];
   
   private static final Logger log = Logger.getLogger(BandwidthDetection.class.getName());
   
   public BandwidthDetection()
   {
       
   }
   
   public void checkBandwidth(IConnection p_client)
   {
       this.calculateClientBw(p_client);
   }
   
   public void calculateClientBw(IConnection p_client)
   {
       for (int i=0; i<1200; i++){
           payload[i] = Math.random();
       }
       
       p_client.setAttribute("payload", payload);
       
       for (int i=0; i<12000; i++){
           payload_1[i] = Math.random();
       }
       
       p_client.setAttribute("payload_1", payload_1);
       
       for (int i=0; i<12000; i++){
           payload_2[i] = Math.random();
       }
       
       p_client.setAttribute("payload_2", payload_2);
       
       final IStreamCapableConnection beginningStats = this.getStats();
       final Long start = new Long(System.nanoTime()/1000000); //new Long(System.currentTimeMillis());
       
       this.client = p_client;
       beginningValues = new HashMap<String, Long>();
       beginningValues.put("b_down", beginningStats.getWrittenBytes());
       beginningValues.put("b_up", beginningStats.getReadBytes());
       beginningValues.put("time", start);
       
       this.pakSent.add(start);
       this.sent++;
       this.callBWCheck("");
   }
   
   /**
    * Handle callback from service call. 
    */
   public void resultReceived(IPendingServiceCall call) { 
       Long now = new Long(System.nanoTime()/1000000); //new Long(System.currentTimeMillis());
       this.pakRecv.add(now);
       Long timePassed = (now - this.beginningValues.get("time"));
       this.count++;
       
       if (count == 1) {
           latency = Math.min(timePassed, 800);
           latency = Math.max(latency, 10);
           
           log.info("count: "+count+ " sent: "+sent+" timePassed: "+timePassed+" latency: "+latency);
           
           // We now have a latency figure so can start sending test data.
           // Second call.  1st packet sent
           pakSent.add(now);
           sent++;
           
           this.callBWCheck(this.client.getAttribute("payload"));
       }
       // To run a very quick test, uncomment the following if statement and comment out the next 3 if statements.
       /*
       else if (count == 2 && (timePassed < 2000)) {
           pakSent.add(now1);
           sent++;
           cumLatency++;
           this.callBWCheck(this.client.getAttribute("payload"));
       }
       */
       // The following will progressivly increase the size of the packets been sent until 1 second has elapsed.
       else if ((count > 1 && count < 3) && (timePassed < 1000)) {
           pakSent.add(now);
           sent++;
           cumLatency++;
           this.callBWCheck(this.client.getAttribute("payload"));
       } else if ((count >=3 && count < 6) && (timePassed < 1000)) {
           pakSent.add(now);
           sent++;
           cumLatency++;
           this.callBWCheck(this.client.getAttribute("payload_1"));
       } else if (count >= 6 && (timePassed < 1000)) {
           pakSent.add(now);
           sent++;
           cumLatency++;
           this.callBWCheck(this.client.getAttribute("payload_2"));
       }
       // Time elapsed now do the calcs
       else if (sent == count) {
           // see if we need to normalize latency
           if (latency >= 100) {
               // make sure satelite and modem is detected properly
               if (pakRecv.get(1) - pakRecv.get(0) > 1000) {
                   latency = 100; 
               }
           }
   
           this.client.removeAttribute("payload");
           this.client.removeAttribute("payload_1");
           this.client.removeAttribute("payload_2");
           
           final IStreamCapableConnection endStats = this.getStats();           
           deltaDown = (endStats.getWrittenBytes() - beginningValues.get("b_down")) * 8 / 1000; // bytes to kbits
           deltaTime = ((now - beginningValues.get("time")) - (latency * cumLatency)) / 1000; // total dl time - latency for each packet sent in secs
           if (deltaTime <= 0) {
               deltaTime = (now - beginningValues.get("time")) / 1000;
           }
           kbitDown = Math.round(deltaDown / deltaTime); // kbits / sec
           
           log.info("onBWDone: kbitDown = " + kbitDown + ", deltaDown= " + deltaDown + ", deltaTime = " + deltaTime + ", latency = " + this.latency);
           
           this.callBWDone(this.kbitDown, this.deltaDown, this.deltaTime, this.latency);                                 
       }
   }
   
   private void callBWCheck(Object params)
   {
       IConnection conn = Red5.getConnectionLocal();
       
       if (conn instanceof IServiceCapableConnection) {
           ((IServiceCapableConnection) conn).invoke("onBWCheck", new Object[]{params}, this);
       }
   }
   
   private void callBWDone(double kbitDown, double deltaDown, double deltaTime, double latency)
   {
       IConnection conn = Red5.getConnectionLocal();
               
       if (conn instanceof IServiceCapableConnection) {
           ((IServiceCapableConnection) conn).invoke("onBWDone", new Object[]{kbitDown,  deltaDown, deltaTime, latency});
       }
   }
   
   private IStreamCapableConnection getStats()
   {
       IConnection conn = Red5.getConnectionLocal();
       if (conn instanceof IStreamCapableConnection) {
           return (IStreamCapableConnection) conn;
       }
       return null;
   }
}

Client Side:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="onAppComplete(event)" width="602" height="211">
    <mx:Script>
        <![CDATA[
           import mx.events.FlexEvent;
           import flash.net.NetConnection;
            
           private var nc:NetConnection;
           private var serverURL:String = "rtmp://www.electroteque.org/bwcheck";
            
           public function onAppComplete(event:FlexEvent):void
           {
               nc = new NetConnection();
               nc.client = this;
               nc.connect(serverURL);	
           }
           
           public function onBWCheck(obj:Object):void
           {
               trace("Checking Bandwidth");
               log.data = "Checking Bandwidth ..... \n\n";
           }
           
           public function onBWDone(kBDown:String, deltaDown:String, deltaTime:String, latency:String):void
           {
               trace("KBDown: " + kBDown + " Delta Down: " + deltaDown + " Delta Time: " + deltaTime + " Latency: " + latency);
               log.data += "KBDown: " + kBDown + " Delta Down: " + deltaDown + " Delta Time: " + deltaTime + " Latency: " + latency;
           }
       ]]>
   </mx:Script>
   <mx:TextArea x="0" y="0" width="602" height="211" id="log"/>
</mx:Application>