Dynamic Bandwidth Detection (BWCheck)

Below is a bandwidth checker from forum contributor Roger. It is a Wowza Pro/Java version of the bandwidth checker discussed in this article:

http://www.adobe.com/devnet/flashcom/articles/flvideo_bandwidth.html

Java code:

  • Download a built version of this code from wms-plugin-bwcheck.zip

  • Unzip and copy the wms-plugin-bwcheck.jar file (not the entire folder) into the [install-dir]/lib folder and restart the server

    package com.wowza.wms.plugin.bwcheck;
    import java.util.*;
    import com.wowza.util.*;
    import com.wowza.wms.amf.*;
    import com.wowza.wms.application.*;
    import com.wowza.wms.client.*;
    import com.wowza.wms.logging.*;
    import com.wowza.wms.module.*;
    import com.wowza.wms.request.*;
    /**
     * <p>New and improved bandwidth checker.</p>
     * @author Wowza Media Systems (Roger Littin)
     *
     */
    public class ModuleBWCheck2 extends ModuleBase
    {
    	private int bwCheckPayloadSize = 1800;
    	private int bwCheckMaxLoops = 6;
    	private long bwCheckMaxTime = 1000;
      
    	public void onAppStart(IApplicationInstance appInstance)
    	{
    		WMSProperties props = appInstance.getProperties();
    		if (props != null)
    		{
    			this.bwCheckPayloadSize = props.getPropertyInt("bwCheckPayloadSize", bwCheckPayloadSize);
    			this.bwCheckMaxLoops = props.getPropertyInt("bwCheckMaxLoops", bwCheckMaxLoops);
    			this.bwCheckMaxTime = props.getPropertyLong("bwCheckMaxTime", bwCheckMaxTime);
    		}
      	
    		getLogger().info("ModuleBWCheck2.onAppStart: bwCheckPayloadSize: "+this.bwCheckPayloadSize );
    		getLogger().info("ModuleBWCheck2.onAppStart: bwCheckMaxLoops: "+this.bwCheckMaxLoops );
    		getLogger().info("ModuleBWCheck2.onAppStart: bwCheckMaxTime: "+this.bwCheckMaxTime );
    	}
      
    	public void onConnect(IClient client, RequestFunction function, AMFDataList params)
    	{
    		boolean autoSenseBW = params.getBoolean(PARAM1);
    		if (autoSenseBW)
    			calculateClientBw(client);
    		else
    			client.call("onBWDone", null);
    	}
    	public void checkBandwidth(IClient client, RequestFunction function, AMFDataList params)
    	{
    		calculateClientBw(client);
    	}
    	private void calculateClientBw(IClient client)
    	{
    		final AMFDataArray payload = new AMFDataArray();
    		for (int i = 0; i < bwCheckPayloadSize; i++)
    		{
    			payload.add(new AMFDataItem((double) Math.random()));
    		}
    		class ResultObj implements IModuleCallResult
    		{
    			IOPerformanceCounter totalStats;
    			long bytesOutStart;
    			double latency = 0;
    			int cumLatency = 1;
    			int count = 0;
    			int sent = 0;
    			double kbitDown = 0;
    			double deltaDown = 0;
    			double deltaTime = 0;
    			long start = 0;
    			List<Long> pakSent = new ArrayList<Long>();
    			List<Long> pakRecv = new ArrayList<Long>();
    			public ResultObj(IClient client)
    			{
    				totalStats = client.getTotalIOPerformanceCounter();
    			}
    			public void onResult(IClient client, RequestFunction function, AMFDataList params)
    			{
    				Long now = new Long(System.currentTimeMillis());
    				pakRecv.add(now);
    				if (count == 0)
    				{
    					latency = now - pakSent.get(0);
    					latency = Math.min(latency, 800);
    					System.out.println("latency: " + latency);
    					bytesOutStart = totalStats.getMessagesOutBytes();
    					start = System.currentTimeMillis();
    				}
    				Long timePassed = (now - start);
    				count++;
    				if ((count >= 1 && count <= bwCheckMaxLoops) && (timePassed < bwCheckMaxTime))
    				{
    					pakSent.add(sent++, now);
    					cumLatency++;
    					client.call("onBWCheck", this, payload);
    				}
    				// 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;
    						}
    					}
    					// tidy up
    					// and compute bandwidth
    					deltaDown = (double) ((totalStats.getMessagesOutBytes() - bytesOutStart) * 8) / 1000; // bytes
    					// to
    					// kbits
    					deltaTime = (double) ((now - start) - (latency * cumLatency)) / 1000;
    					if (deltaTime <= 0)
    					{
    						deltaTime = (double) (now - start) / 1000;
    					}
    					kbitDown = Math.round(deltaDown / deltaTime); // kbits / sec
    					WMSLoggerFactory.getLogger(null).info("onBWDone: " + this.kbitDown);
    					client.call("onBWDone", null, this.kbitDown, this.deltaDown, this.deltaTime, this.latency);
    				}
    			}
    		}
    		class FirstResult implements IModuleCallResult
    		{
    			public void onResult(IClient client, RequestFunction function, AMFDataList params)
    			{
    				ResultObj res = new ResultObj(client);
    				long now = System.currentTimeMillis();
    				res.pakSent.add(res.sent++, now);
    				client.call("onBWCheck", res, "");
    			}
    		}
    		client.call("onBwCheck", new FirstResult(), payload); // First call to
    		// client is
    		// ignored.
    	}
    	public void onClientBWCheck(IClient client, RequestFunction function, AMFDataList params)
    	{
    		getLogger().info("onClientBWCheck");
    		AMFDataObj statValues = new AMFDataObj();
    		IOPerformanceCounter stats = 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(client, params, statValues);
    	}
    }
    
    

    Configuration:

  • Create a folder named [install-dir]/applications/bwcheck

  • Create a folder named [install-dir]/conf/bwcheck

  • Copy [install-dir]/conf/Application.xml into new bwcheck config folder

  • Edit copied Application.xml file and remove the module reference to ModuleFLVPlayback and add the module reference below as the last entry in the section (it is important that it is the last entry):

    <Module>
    	<Name>BWCheck</Name>
    	<Description>BWCheck</Description>
    	<Class>com.wowza.wms.plugin.bwcheck.ModuleBWCheck2</Class>
    </Module>
    
    

    Note: To learn how to compile and link this code into a customer server module, download and install the Wowza IDE and follow the instructions in the included User’s Guide.

    There are AS2 and AS3 Flash client examples in the examples folder of Wowza Server:

    [install-dir]/examples/BWCheck/client

    and

    [install-dir]/examples/BWCheck/clientAS2

    bwchecker.fla is a working example which outputs the results of repeated bandwidth checking.

    bwcheck.fla is just the essential Actionscript that you can use to integrate into other Flash applications.

    Charlie

Below is a link to a simple Flash movie for testing the connection speed to the Wowza Pro server from a client machine. If you configure your server as prescribed in the previous post, this tool will just work out of the box. It calls the server side bandwidth detection code every 5 seconds and returns the results. Its very useful for debugging client bandwidth issue like stuttering movies or poor video chat performance.

BWChecker.zip

To install, unzip. To run, double click bwchecker.html and change the connection string if needed.

The results are returned in kilobits per seconds which is the same units used to determine the bit rate of your media content. So if you are consistently seeing values of 600Kbps or greater, then this connection should be able to support content encoded at combine rate of ~500Kbps (400Kbps video, 96Kbps audio).

Enjoy,

Charlie

Take a look at the IOPerformanceCounter interface that is attached the IMediaStream interface. You can get the IMediaStream interface by calling:

IApplicationInstance appInstance;
MediaStreamMap streams = appInstance.getStreams();
IMediaStream stream = streams.getStream(streamName);
IOPerformanceCounter ioPerformance = stream.getMediaIOPerformance();

Through the IOPerformanceCounter you can monitor the bits that travel in and out of the publishing stream. So if you monitor this over time you can calculate the rate.

Make sense?

Charlie

Because the FLVPlayback module is just a fake bandwidth check that is require by the FLVPlayback component. This interfers with the real bandwidth checker.

Charlie

You can certainly modify the code to suit your needs. Remember that this system is testing the end to end bandwidth between your machine and the Wowza Media Server. So you might not get full line speed based on that path. Also, the way that latency is substracted from the calculation can throw off the calculation. If the latency is high I believe it lowers the calculated bandwidth.

Charlie

I sent a note to Roger who wrote this code.

Charlie

Is anyone familiar with a tool that allows me to mimic a slower connection speed on my own computer – in other words, to reduce the actual speed at which I connect so I can test how the bandwidth checking works for various connection speeds?

Hi,

I am testing this bandwidth checker in conjunction with wowza streaming server so that I can automatically give low bandwidth users a lower quality video.

The bandwidth values that the checker is giving out seem to be a little sporadic and I can’t work out if this is something to do with my setup or if this is usual.

As an example, the first five results that were picked up on one attempt were 783, 2483. 12375, 2774, 3576. I am worried that someone with an acceptable connection will be given the lower quality result due to an unreliable return. I have checked it on a couple of connections and it seems to be the same.

As a test I have altered the included app to create a running average on every check. As you can see below, it does find an average of 3777 after a while but I can’t really make the user wait to find this average.

Is the module designed to just check once?

If someone could advise me as to whether I have made a mistake somewhere, perhaps it could be the wowza server being busy affecting the speed? perhaps there is another method that would be more reliable?

, 2483, 2483, 5780.33333333333, 5028.75, 4738.2, 4647, 4494, 4180.875, 3831.33333333333, 5884.5, 5573.81818181818, 5388.16666666667, 5101.84615384615, 4890.85714285714, 4830, 5169.25, 5043.64705882353, 4933.94444444444, 4959.21052631579, 4841.6, 4770.38095238095, 4743.04545454546, 4721.04347826087, 4686.75, 4642.32, 4525.46153846154, 4423.33333333333, 4354.03571428571, 4256.72413793103, 4202.93333333333, 4134.06451612903, 4180.15625, 4304.81818181818, 4207.11764705882, 4125, 4167.33333333333, 4318.08108108108, 4349.94736842105, 4291.64102564103, 4281.8, 4192.73170731707, 4143.07142857143, 4334.51162790698, 4283.11363636364, 4235.4, 4206.32608695652, 4249.53191489362, 4234.16666666667, 4169.57142857143, 4104.7, 4487.45098039216, 4412.05769230769, 4340.94339622641, 4279.7962962963, 4218.23636363636, 4242.35714285714, 4181.68421052632, 4137.06896551724, 4084.52542372881, 4037.06666666667, 4142.67213114754, 4080.17741935484, 4041.85714285714, 3988.125, 3945.49230769231, 3900.42424242424, 3855.98507462687, 3849.13235294118, 3806.65217391304, 3765.47142857143, 3729.2676056338, 3681.68055555556, 3640.72602739726, 3609.44594594595, 3644.61333333333, 3638.35526315789, 3623.66233766234, 3622.84615384615, 3588.48101265823, 3576.4375, 3571.56790123457, 3614.43902439024, 3578.72289156626, 3586.28571428571, 3608.23529411765, 3625.13953488372, 3619.6091954023, 3614.14772727273, 3722.01123595506, 4093.15555555556, 4093.74725274725, 4091.19565217391, 4051.20430107527, 4014.12765957447, 3983.05263157895, 3949.5625, 3930.21649484536, 3909.66326530612, 3878.57575757576, 3859.68, 3841.25742574257, 3834.04901960784, 3806.93203883495, 3792.05769230769, 3804.79047619048, 3782.69811320755, 3755.98130841121, 3730.36111111111, 3713.97247706422, 3758.95454545455, 3776.36036036036, 3799.23214285714, 3795.34513274336, 3771.35964912281, 3777.74782608696, 3776.43965517241, 3767.8717948718, 3765.56779661017, 3773.1512605042, 3764.90833333333, 3747.2479338843, 3733.30327868852,

Has anyone made progress on an upload speed test? It doesn’t look that hard, but I have no desire to reinvent the wheel if it can be avoided.

Thanks,

Tom

We don’t have a BWCheck for that yet. The methodology would be the same. Obviously, the client would be sending data to the server. You could certainly modify the BWCheck code (server and client) above to create a BWCheckUpstream test. I can help support you in doing this.

Charlie

No, I don’t think anyone has had a go at extending the BWChecker to test upload performance.

Charlie

Also, set the ObjectEndoding to AMF0.

Charlie

The getMessagesLossBytes will always be 0 when publishing since the throttling is happening on the client side. Wowza Pro has no idea if packets are being dropped on the server side.

Charlie

I don’t know of any way of getting that information.

Charlie

There shouldn’t be. Maybe Roger will chime in with more info.

Charlie

I think the problem is obvious isn’t it. Your client-side method is:

public function onBWDone(kbitDown:Number, deltaDown:Number, deltaTime:Number, latency:Number):void

It is looking for a version of this method with no parameter.

public function onBWDone():void

Charlie

When I try to use this tool it sits on “testing performance…” indefinitely.

Sorry, I didn’t see the top post since I was linked directly to the second post. I have it working now.

-Aaron

I will have a look at it and put something up later.

Roger.

I did start to look into it and then got swamped with other things. Will take another look at it.

Roger.