• How to check bandwidth from client to server (to test uplink to be used by a live stream encoder)

    This article describes how to measure bandwidth from a client to Wowza Media Server® (client-to-server bandwidth). This is useful at a client that will publish high bitrate streams to Wowza Media Server, for example, to see if the uplink bandwidth is adequate.

    Note: The standard BWCheck module measures server-to-client bandwidth.

    There are two parts:


    Server-side application module


    Code:
    package com.wowza.wms.plugin.bwcheck;
    
    import com.wowza.util.IOPerformanceCounter;
    import com.wowza.wms.amf.*;
    import com.wowza.wms.client.*;
    import com.wowza.wms.module.*;
    import com.wowza.wms.request.*;
    
    public class ModuleClientBWCheck extends ModuleBase {
    	
    	static public void onClientBWCheck(IClient p_client, RequestFunction function, AMFDataList params) {
            getLogger().info("onClientBWCheck");
            AMFDataObj statValues = new AMFDataObj();;
            
            IOPerformanceCounter stats = p_client.getTotalIOPerformanceCounter();
            
            statValues.put("cOutBytes", new AMFDataItem(stats.getMessagesInBytes()));
            statValues.put("cInBytes", new AMFDataItem(stats.getMessagesOutBytes()));
            statValues.put("time", new AMFDataItem(params.getLong(PARAM1)));
            
            sendResult(p_client, params, statValues);
        }
    }
    A compiled version of this module is included in the Wowza Modules Collection (wms-plugin-collection.zip).

    To install the module

    1. Download wms-plugin-collection.zip and extract the contents from the compressed (zipped) folder.
    2. Copy the /lib/wms-plugin-collection.jar file in the package to the [install-dir]/lib folder.
    3. If Wowza Media Server is running, restart the server.

    To configure the module

    1. Create a folder named [install-dir]/applications/[app-name].
    2. Create a folder named [install-dir]/conf/[app-name].
    3. Copy the [install-dir]/conf/Application.xml file to the new [install-dir]/conf/[app-name] folder.
    4. Open the [install-dir]/conf/[app-name]/Application.xml file in a text editor and add the following module reference as the last entry in the <Modules> section (it is important that it be the last entry):
      Code:
      <Module>
      	<Name>ClientBWCheck</Name>
      	<Description>ClientBWCheck</Description>
      	<Class>com.wowza.wms.plugin.collection.module.ModuleClientBWCheck</Class>
      </Module>

    Client-side applications


    Variables in the following client code can be accessed via res.kbitUp, res.deltaUp, res.deltaTime, and res.latency.

    Flex 4 AS3 version


    A built version of this client is included in clientBWCheck.zip.

    ClientBWCheck.mxml
    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <local:cbwc xmlns:fx="http://ns.adobe.com/mxml/2009" 
    			   xmlns:local="*"
    			   xmlns:s="library://ns.adobe.com/flex/spark" 
    			   xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%">
    	
    	<mx:Image source="http://www.wowzamedia.com/img/header.gif" x="0" y="0"/>
    	
    	<s:VGroup x="10" y="70" horizontalAlign="left">
    		<s:HGroup>
    			<s:Button label="Connect: " click="mainInit()"/>
    			<mx:TextInput id="wowzaAddress" text="rtmp://laketk.com:1935/upbwcheck" width="380"/>
    		</s:HGroup>
    		<mx:Button label="Run" id="runButton" click="start()" enabled="{isConnected}"/>
    
    		<mx:TextArea id="output" width="400" height="400"  editable="false" contentBackgroundColor="#E7E2E2"/>
    	</s:VGroup>
    	
    	<mx:TabNavigator x="540" y="120" >
    		<mx:VBox label="Graph" horizontalAlign="center">
    			<mx:ColumnChart  id="chart" dataProvider="{chartData}">
    				<mx:horizontalAxis>
    					<mx:CategoryAxis 
    						dataProvider="{chartData}" 
    						categoryField="testNumber"/>
    				</mx:horizontalAxis>
    				
    				<mx:series>
    					<mx:ColumnSeries 
    						yField="kbitUp"
    						displayName="kbitUp"
    						labelField="kbitUp"
    						labelPosition = "inside"
    						/>
    				</mx:series>
    			</mx:ColumnChart>	
    			<mx:Legend direction="horizontal" dataProvider="{chart}"/>
    		</mx:VBox>
    		<mx:Box label="Data">
    			<mx:DataGrid dataProvider="{chartData}" width="600" height="400">
    				<mx:columns>
    					<mx:DataGridColumn dataField="testNumber"/>					
    					<mx:DataGridColumn dataField="kbitUp"/>
    					<mx:DataGridColumn dataField="KBytes"/>
    					<mx:DataGridColumn dataField="latency"/>
    					<mx:DataGridColumn dataField="deltaUp"/>
    					<mx:DataGridColumn dataField="deltaTime"/>
    				</mx:columns>
    			</mx:DataGrid>
    		</mx:Box>	
    	</mx:TabNavigator>
    	
    </local:cbwc>
    cbwc.as
    Code:
    package
    {
    	import flash.events.MouseEvent;
    	import flash.events.NetStatusEvent;
    	import flash.net.NetConnection;
    	import flash.net.Responder;
    	import flash.system.Security;
    	
    	import mx.charts.ColumnChart;
    	import mx.collections.ArrayCollection;
    	import mx.controls.Button;
    	import mx.controls.DataGrid;
    	import mx.controls.Text;
    	import mx.controls.TextArea;
    	import mx.controls.TextInput;
    	import mx.events.FlexEvent;
    	
    	import spark.components.Application;
    	
    	public class cbwc extends Application
    	{
    		Security.LOCAL_TRUSTED;
    		
    		public var nc:NetConnection;
    		private var data:Object = new Object();
    		
    		private var testNumber:int = 0;
    		
    		public var output:TextArea;
    		public var wowzaAddress:TextInput;
    		public var runButton:Button;
    		
    		private var run:Boolean = false;
    		
    		[Bindable]
    		public var isConnected:Boolean = false;
    		
    		public var chart:ColumnChart = new ColumnChart();
    		
    		[Bindable]
    		public var chartData:ArrayCollection = new ArrayCollection();
    			
    		public function mainInit():void
    		{
    			nc = new NetConnection();
    			
    			nc.addEventListener(NetStatusEvent.NET_STATUS,function(infoObject:NetStatusEvent):void
    			{
    				output.text = infoObject.info.code;
    				
    				if (infoObject.info.code == "NetConnection.Connect.Success")
    				{
    					isConnected = true;
    					testNumber = 0;
    					chartData = new ArrayCollection()
    				}
    			});
    			
    			var clientObj:Object = new Object();
    			
    			
    			nc.addEventListener(NetStatusEvent.NET_STATUS, function(infoObject:NetStatusEvent):void 
    			{
    				trace (infoObject.info.code);
    				if (infoObject.info.code == "NetConnection.Connect.Success") {
    					isConnected = true;
    				}	
    			});
    			nc.connect(wowzaAddress.text);
    		}
    		
    		public function start():void
    		{
    			if (run)
    			{
    				run = false;
    				runButton.label = "Run";
    			}
    			else
    			{	
    				runButton.label = "Stop";
    				run = true;
    				doClientBWCheck();		
    			}
    		}
    		private function doClientBWCheck():void
    		{
    			output.text = "";
    			
    			data.latency = 0;
    			data.cumLatency = 1;
    			data.bwTime = 0;
    			data.count = 0;
    			data.sent = 0;
    			data.kbitUp = 0;
    			data.deltaUp = 0;
    			data.deltaTime = 0;
    			data.pakSent = new Array();
    			data.pakRecv = new Array();
    			data.beginningValues = {};
    			
    			nc.call("onClientBWCheck", new Responder(bwResponse));
    		}
    		
    		private function bwResponse(p_res:Object):void
    		{
    			var deltaTime:Number;
    			var deltaUp:Number;
    			var kbitUp:Number;
    			var payload:Array = new Array();
    			for (var i:int=0; i<1200; i++){
    				payload[i] = Math.random();	//16K approx
    			}
    			
    			var now:Number = (new Date()).getTime()/1;
    			if(data.sent == 0) {
    				data.beginningValues = p_res;
    				data.beginningValues.time = now;
    				data.pakSent[data.sent++] = now;
    				nc.call("onClientBWCheck", new Responder(bwResponse), now);
    			} else {
    				data.pakRecv[data.count] = now;
    				trace( "Packet interval = " + (data.pakRecv[data.count] - data.pakSent[data.count])*1  );
    				output.text += "Packet interval = " + (data.pakRecv[data.count] - data.pakSent[data.count])*1  ;
    				data.count++;
    				var timePassed:Number = (now - data.beginningValues.time);
    				
    				if (data.count == 1) {
    					data.latency = Math.min(timePassed, 800);
    					data.latency = Math.max(data.latency, 10);
    					data.overhead = p_res.cOutBytes - data.beginningValues.cOutBytes;
    					trace("overhead: "+data.overhead);
    					data.pakSent[data.sent++] = now;
    					nc.call("onClientBWCheck", new Responder(bwResponse), now, payload);
    				}
    				trace("count: "+data.count+ " sent: "+data.sent+" timePassed: "+timePassed+" latency: "+data.latency);
    				
    				output.text += "count: "+data.count+ " sent: "+data.sent+" timePassed: "+timePassed+" latency: "+data.latency;
    				
    				// If we have a hi-speed network with low latency send more to determine
    				// better bandwidth numbers, send no more than 6 packets
    				if ( (data.count >= 1) && (timePassed<1000))
    				{
    					data.pakSent[data.sent++] = now;
    					data.cumLatency++;
    					nc.call("onClientBWCheck", new Responder(bwResponse), now, payload);
    				} else if ( data.sent == data.count ) {	
    					// See if we need to normalize latency
    					if ( data.latency >= 100 )
    					{ // make sure we detect sattelite and modem correctly
    						if (  data.pakRecv[1] - data.pakRecv[0] > 1000 )
    						{
    							data.latency = 100;
    						}
    					}
    					payload = new Array();
    					// Got back responses for all the packets compute the bandwidth.
    					var stats:Object = p_res;
    					deltaUp = (stats.cOutBytes - data.beginningValues.cOutBytes)*8/1000;
    					deltaTime = ((now - data.beginningValues.time) - (data.latency * data.cumLatency) )/1000;
    					if ( deltaTime <= 0 )
    						deltaTime = (now - data.beginningValues.time)/1000;
    					
    					kbitUp = Math.round(deltaUp/deltaTime);
    					trace("onBWDone: kbitUp = " + kbitUp + ", deltaUp= " + deltaUp + ", deltaTime = " + deltaTime + ", latency = " + data.latency + " KBytes " + (stats.cOutBytes - data.beginningValues.cOutBytes)/1024) ;
    					output.text = "kbitUp = " + kbitUp + ", deltaUp= " + deltaUp + ", deltaTime = " + deltaTime + ", latency = " + data.latency + " KBytes " + (stats.cOutBytes - data.beginningValues.cOutBytes)/1024  + "/n/n" + output.text;	
    					
    					var obj:Object = new Object();
    					obj.kbitUp = kbitUp;
    					obj.deltaUp = deltaUp;
    					obj.deltaTime = deltaTime;
    					obj.latency = data.latency;
    					obj.KBytes = (stats.cOutBytes - data.beginningValues.cOutBytes)/1024;
    					obj.testNumber = "Test # " + ++testNumber;
    					chartData.addItemAt(obj,0);
    					
    					if (run)
    						doClientBWCheck();
    					
    				}
    			}
    		}
    	}
    }

    Flash AS2 version


    Code:
    function doClientBWCheck() {
    	nc.call("onClientBWCheck", res, null);
    	trace("testing CtoS performance...");
    	
    }
    
    var nc:NetConnection = new NetConnection();
    nc.onStatus = function(info) {
    	trace (info.code);
    	if (info.code == "NetConnection.Connect.Success") {
    		doClientBWCheck();		
    	}
    }
    nc.connect("rtmp://localhost/bwcheck");
    
    var payload = new Array();
    for (var i=0; i<1200; i++){
    	payload[i] = Math.random();	//16K approx
    }
    
    var res = new Object();
    res.latency = 0;
    res.cumLatency = 1;
    res.bwTime = 0;
    res.count = 0;
    res.sent = 0;
    res.kbitUp = 0;
    res.deltaUp = 0;
    res.deltaTime = 0;
    //res.client = p_client;
    //var stats = p_client.getStats();
    res.pakSent = new Array();
    res.pakRecv = new Array();
    res.beginningValues = {};
    
    res.onResult = function(p_res) {
    	trace ("ClientBWResult: ");
    	var now = (new Date()).getTime()/1;
    	if(this.sent == 0) {
    		this.beginningValues = p_res;
    		this.beginningValues.time = now;
    		this.pakSent[res.sent++] = now;
    		nc.call("onClientBWCheck", this, now);
    	} else {
    		this.pakRecv[this.count] = now;
    		trace( "Packet interval = " + (this.pakRecv[this.count] - this.pakSent[this.count])*1  );
    		this.count++;
    		var timePassed = (now - this.beginningValues.time);
    
    		if (this.count == 1) {
    			this.latency = Math.min(timePassed, 800);
    			this.latency = Math.max(this.latency, 10);
    			this.overhead = p_res.cOutBytes - this.beginningValues.cOutBytes;
    			trace("overhead: "+this.overhead);
    			this.pakSent[res.sent++] = now;
    			nc.call("onClientBWCheck", res, now, payload);
    		}
    		trace("count: "+this.count+ " sent: "+this.sent+" timePassed: "+timePassed+" latency: "+this.latency);
    	
    		// If we have a hi-speed network with low latency send more to determine
    		// better bandwidth numbers, send no more than 6 packets
    		if ( (this.count >= 1) && (timePassed<1000))
    		{
    			this.pakSent[res.sent++] = now;
    			this.cumLatency++;
    			nc.call("onClientBWCheck", res, now, payload);
    		} else if ( this.sent == this.count ) {	
    			// See if we need to normalize latency
    			if ( this.latency >= 100 )
    			{ // make sure we detect sattelite and modem correctly
    				if (  this.pakRecv[1] - this.pakRecv[0] > 1000 )
    				{
    					this.latency = 100;
    				}
    			}
    			delete payload;
    			// Got back responses for all the packets compute the bandwidth.
    			var stats = p_res;
    			deltaUp = (stats.cOutBytes - this.beginningValues.cOutBytes)*8/1000;
    			deltaTime = ((now - this.beginningValues.time) - (this.latency * this.cumLatency) )/1000;
    			if ( deltaTime <= 0 )
    				deltaTime = (now - this.beginningValues.time)/1000;
    			
    			kbitUp = Math.round(deltaUp/deltaTime);
    	
    			trace("onBWDone: kbitUp = " + kbitUp + ", deltaUp= " + deltaUp + ", deltaTime = " + deltaTime + ", latency = " + this.latency + " KBytes " + (stats.cOutBytes - this.beginningValues.cOutBytes)/1024) ;
    		}
    	}
    }

    Comments 2 Comments
    1. leaveluck2heaven -
      We followed the directions from above..
      ********
      copied /lib/wms-plugin-collection.jar from the package to the Wowza /lib folder. Then restart Wowza. Add the following Module last in the /conf/[app-name]/Application.xml Modules list
      ******
      How ever when we try to connect to following.
      SERVER/upbwcheck

      We receive the following error...
      Level: error Code: NetConnection.Connect.Rejected
      Level: status Code: NetConnection.Connect.Closed

      Very confused here.
    1. rrlanham -
      Do you have ModuleSecureToken or ModuleRTMPAuthenticate enabled in your Application.xml /Modules list?

      Richard