Wowza Community

Dynamic Bandwidth Detection (BWCheck)

https://www.wowza.com/docs/how-to-test-server-to-client-bandwidth-for-rtmp-clients

It seems the sample code is not up to date with the jar file… when using the jar the bw is reported to the client once… when using the code above compiled into a module… the bw call fires twice.

Also… one small suggestion… :slight_smile:

When using the jar out of the box it might be nice to wrap the onConnect check in a try catch so that this error doesn’t occur if the second param isn’t passed:

invoke(onConnect): java.lang.ClassCastException: com.wowza.wms.amf.AMFDataObj cannot be cast to com.wowza.wms.amf.AMFDataItem: com.wowza.wms.amf.AMFDataList.getBoolean(null:-1)

Thanks!

Jake

So I am a little new to the scene and confused :slight_smile: So I looked at the guide here, and also at http://www.longtailvideo.com/support/tutorials/Bitrate-Switching and am not sure if these go hand in hand, or if you choose one or the other.

As I understood it, Wowza accepted a single file in, and then would re-encode it on the fly for connections of lower bandwidth. I installed the .jar file, added the code into the Application.xml file and can see that it is calling the bwcheck module in the logs, but I have a few customers complaining that the stream is unable to keep up with their connection.

I am also not at all sure what the code below is… Do I need to change anything or is it the source code for the .jar in case I wanted to change some settings? A little more clarification on the original post would help out the newbies like me. Am supposed to make the changes that I made below changing the com.wowza to com.coursesaver?

When building the project in IDE, do I need the ModuleServerSide.java imported? I tried with and without and am getting the error posted in the photo: http://www.coursesaver.com/personal/error.jpg

Sorry to have such a multi part post, I am just trying as much as I can and adding info along the way as I run into problems.

package com.coursesaver.wms.plugin.bwcheck;
import java.util.*;
import com.coursesaver.util.*;
import com.coursesaver.wms.amf.*;
import com.coursesaver.wms.application.*;
import com.coursesaver.wms.client.*;
import com.coursesaver.wms.logging.*;
import com.coursesaver.wms.module.*;
import com.coursesaver.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);
	}
}

I have udpated all of it with the latest, greatest code that I have.

Charlie

Jake,

I think twice is normal. Handle it like this example (adapted from JW Player):

/** Receiving the bandwidth check result. **/
		public function onBWDone(... rest):void {
			if (rest.length > 0) {
				trace(rest[0]);
				//forward({bandwidth: rest[0]}, 'bandwidth');
			}
		};

Richard

No, this is not needed for that. The main thing you need is video versions that have properly aligned keyframe timecodes. Follow this tutorial:

http://www.longtailvideo.com/support/tutorials/Bitrate-Switching

Richard

For most purposes “kbitDown” is the number you need. This is the measured bandwidth of server to the client. You don’t want to stream to this client with a higher bitrate stream that kbitDown. Ideally you would stream something less than that.

Richard

“latency” is how long will it take to start playing

The speed at which it will play once it starts is indicated by kbitDown

Richard

There is a kbitDown number and latency number. One is not the other. Obviously this is about re-inventing a way to do Dynamic Streaming, because you said yesterday that you don’t think the QoS metrics built-in to Flash using the NetStream.info object are adequate. I really don’t agree with that premise. I suggest you backup and take a better look at NetStream.info object. Here is another Adobe article:

http://www.adobe.com/devnet/flashmediaserver/articles/dynstream_actionscript.html

Richard

You must have had someone else in mind when sending this message, Richard.

Oh, whoops, sorry about that. Please excuse me.

Yes, it is in seconds: kbs = kilobits per second. I think the forumula would be:

Latency + fileSize in kilobits / kbitdown

Richard

I think it is 1024.

Richard

But seems like video duration is all you really need. That could be different than your calculations, because Wowza throttles delivery to bitrate.

You can get duration in the metadata in the NetStream NetStatusEvent handler, or more directly by doing:

netconnection.call("getStreamLength, responderName,“example.flv”);

Richard

You could fake one, but the result set is returned all at once.

Richard

By result set, I mean the bandwidth numbers that are received by onBWDone. I don’t know how to do what you want exactly. I would just fake it, i.e, start up your display when bandwidth checking starts, increment the progress bar until the onBWDone function receives the result set, then have it go to 100% and disappear. The real purpose of a progress bar in this case is providing feedback to the user that something is happening. It doesn’t have to measure the progress accurately to do that.

Richard

Also, the “progress” is mostly on the server, so it’s not exactly available client-side the way you are thinking about it. But I suppose you could react to onBWcheck being triggered, and onBWdone gets called once prior to getting the final result. So the progress bar could key off that and increment in thirds. Something like that.

Richard

Just setup BWCheck in your application following the guide in the top of this thread. You add a jar file to the Wowza lib folder, and add a Module to the Application.xml. Then the code that handles BWCheck in the player will work.

Richard

Install the Developer edition and the examples folder will be there.

http://wowza.com/store.htm

Richard

There is a pre-built version of this module you can download from the first post in this thread, so you don’t need to build this code in the IDE.

JW Player’s dynamic streaming does use the BWCheck if it is present, and it is good idea to have it, it will work better. It does this at connection time only to decide which level to start with, then uses other methods (NetStream.info object/QoS metrics) during the streaming session to change levels.

As I understood it, Wowza accepted a single file in, and then would re-encode it on the fly for connections of lower bandwidth

No, this does not happen. Wowza does no transcoding. You need multiple versions of a video or live stream to do this. (I’m curious what you read that gave you that understanding?)

Each version of video or live stream has to be keyframe and timecode aligned. FMLE is known to do this for live streams.

Richard

Yes, do follow the JW Player dynamic streaming tutorial. It’s a client-side solution. The only server-side aspect is the BWCheck module and the stream variations.

You can use remap a stream in an application module or use StreamNameAlias possibly to help with your use case:

Override play remap stream name

http://www.wowza.com/forums/showthread.php?t=6876

StreamNameAlias

http://www.wowza.com/community/t/-/47

We’ve been testing Expression encoder recently and it seems to be producing correctly aligned versions.

Richard

The BWCheck works with Flowplayer. It works with that example and they have simpler version that just does the connection time BWCheck.

Richard