All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.red5.server.stream.bandwidth.ServerClientDetection Maven / Gradle / Ivy

There is a newer version: 2.3.3.1
Show newest version
/*
 * RED5 Open Source Media Server - https://github.com/Red5/
 * 
 * Copyright 2006-2016 by respective authors (see below). All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.red5.server.stream.bandwidth;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

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.service.Call;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Calculates the bandwidth between the client and server. The checks originate from the server.
 * 
 * @see FMS 3.5 Bandwidth Doc
 * 
 * @author The Red5 Project
 * @author Dan Rossi
 * @author Paul Gregoire
 */
public class ServerClientDetection implements IPendingServiceCallback, IBandwidthDetection {

    private static Logger log = LoggerFactory.getLogger(ServerClientDetection.class);

    // maximum latency alloted for in milliseconds
    private static final double LATENCY_MAX = 1000d;

    // minimum latency alloted for in milliseconds
    private static final double LATENCY_MIN = 10d;

    private IConnection conn;

    private volatile double latency;

    private volatile double cumLatency = 1;

    private double kbitDown;

    private double deltaDown;

    private double deltaTime;

    // current bytes written on the connection
    private long startBytesWritten;

    // start time using nanos
    private long startTime;

    // time passed overall 
    private long timePassed;

    private AtomicInteger packetsSent = new AtomicInteger(0);

    private AtomicInteger packetsReceived = new AtomicInteger(0);

    private byte[] payload = new byte[1024];

    private byte[] payload1 = new byte[1024 * 32];

    public void checkBandwidth(IConnection conn) {
        calculateClientBw(conn);
    }

    public void calculateClientBw(IConnection conn) {
        log.debug("calculateClientBw: {} ", conn);
        // set local connection ref
        this.conn = conn;
        // get random generator
        Random rnd = new Random();
        rnd.nextBytes(payload);
        rnd.nextBytes(payload1);
        // get the current bytes written on the connection
        startBytesWritten = conn.getWrittenBytes();
        // start time using nanos
        startTime = System.nanoTime();
        log.debug("Starting bandwidth check at {} ns", startTime);
        callBWCheck("");
    }

    /**
     * Handle callback from service call.
     */
    public void resultReceived(IPendingServiceCall call) {
        // if we aren't connection, skip any further testing
        if (Call.STATUS_NOT_CONNECTED != call.getStatus()) {
            // receive time using nanos
            long now = System.nanoTime();
            // increment received
            int received = packetsReceived.incrementAndGet();
            log.debug("Call time stamps - write: {} read: {}", call.getWriteTime(), call.getReadTime());
            // time passed is in milliseconds
            timePassed = (now - startTime) / 1000000;
            log.debug("Received count: {} sent: {} timePassed: {} ms", new Object[] { received, packetsSent.get(), timePassed });
            switch (received) {
                case 1:
                    // first packet is used to test latency
                    latency = Math.max(Math.min(timePassed, LATENCY_MAX), LATENCY_MIN);
                    log.debug("Receive latency: {}", latency);
                    // We now have a latency figure so can start sending test data.
                    // Second call. 1st packet sent
                    log.debug("Sending first payload at {} ns", now);
                    callBWCheck(payload); // 1k	
                    break;
                case 2:
                    log.debug("Sending second payload at {} ns", now);
                    // increment cumulative latency
                    cumLatency++;
                    callBWCheck(payload1); // 32k
                    break;
                default:
                    log.debug("Doing calculations at {} ns", now);
                    // increment cumulative latency
                    cumLatency++;
                    // bytes to kbits
                    deltaDown = ((conn.getWrittenBytes() - startBytesWritten) * 8) / 1000d;
                    log.debug("Delta kbits: {}", deltaDown);
                    // total dl time - latency for each packet sent in secs
                    deltaTime = (timePassed - (latency * cumLatency));
                    if (deltaTime <= 0) {
                        deltaTime = (timePassed + latency);
                    }
                    log.debug("Delta time: {} ms", deltaTime);
                    // calculate kbit/s
                    kbitDown = Math.round(deltaDown / (deltaTime / 1000d));
                    log.debug("onBWDone: kbitDown: {} deltaDown: {} deltaTime: {} latency: {} ", new Object[] { kbitDown, deltaDown, deltaTime, latency });
                    callBWDone();
            }
        } else {
            log.debug("Pending call skipped due to being no longer connected");
        }
    }

    private void callBWCheck(Object payload) {
        if (log.isTraceEnabled()) {
            log.trace("callBWCheck: {}", payload);
        } else {
            log.debug("callBWCheck");
        }
        IConnection conn = Red5.getConnectionLocal();
        Map statsValues = new HashMap();
        statsValues.put("count", packetsReceived.get());
        statsValues.put("sent", packetsSent.get());
        statsValues.put("timePassed", timePassed);
        statsValues.put("latency", latency);
        statsValues.put("cumLatency", cumLatency);
        statsValues.put("payload", payload);
        if (conn instanceof IServiceCapableConnection) {
            log.debug("Invoking onBWCheck on the client");
            // increment sent counter
            packetsSent.incrementAndGet();
            // invoke on the client
            ((IServiceCapableConnection) conn).invoke("onBWCheck", new Object[] { statsValues }, this);
        }
    }

    private void callBWDone() {
        log.debug("callBWDone");
        IConnection conn = Red5.getConnectionLocal();
        Map statsValues = new HashMap();
        statsValues.put("kbitDown", kbitDown);
        statsValues.put("deltaDown", deltaDown);
        statsValues.put("deltaTime", deltaTime);
        statsValues.put("latency", latency);
        if (conn instanceof IServiceCapableConnection) {
            log.debug("Invoking onBWDone on the client");
            // invoke on the client
            ((IServiceCapableConnection) conn).invoke("onBWDone", new Object[] { statsValues });
            // adjust bandwidth to mbit/s
            int mbits = (int) ((kbitDown / 1000d) * 1000000);
            log.debug("Setting bandwidth to {} mbit/s", mbits);
            // tell the flash player how fast we want data and how fast we shall send it
            conn.setBandwidth(mbits);
        }
    }

    public void onServerClientBWCheck() {
        log.debug("onServerClientBWCheck");
        calculateClientBw(Red5.getConnectionLocal());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy