Skip to content
Snippets Groups Projects
MediaStreamStatsImpl.java 34.4 KiB
Newer Older
/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jitsi.impl.neomedia;

import java.awt.*;
import java.net.*;
import java.util.List;
import javax.media.rtp.*;
import net.sf.fmj.media.rtp.*;

import org.jitsi.impl.neomedia.device.*;
import org.jitsi.impl.neomedia.rtp.*;
import org.jitsi.service.neomedia.control.*;
import org.jitsi.service.neomedia.rtp.*;
import org.jitsi.util.*;
/**
 * Class used to compute stats concerning a MediaStream.
 *
 * @author Vincent Lucas
 * @author Lyubomir Marinov
 */
public class MediaStreamStatsImpl
    implements MediaStreamStats
{
    /**
     * Enumeration of the direction (DOWNLOAD or UPLOAD) used for the stats.
     */
    private enum StreamDirection
    {
        DOWNLOAD,
        UPLOAD
    }

    /**
     * The <tt>Logger</tt> used by the <tt>MediaStreamImpl</tt> class and its
     * instances for logging output.
     */
    private static final Logger logger
        = Logger.getLogger(MediaStreamStatsImpl.class);
     * Computes an Exponentially Weighted Moving Average (EWMA). Thus, the most
     * recent history has a more preponderant importance in the average
     * computed.
     *
     * @param nbStepSinceLastUpdate The number of step which has not been
     * computed since last update. In our case the number of packets received
     * since the last computation.
     * @param lastValue The value computed during the last update.
     * @param newValue The value newly computed.
     *
     * @return The EWMA average computed.
    private static double computeEWMA(
            long nbStepSinceLastUpdate,
            double lastValue,
            double newValue)
        // For each new packet received the EWMA moves by a 0.1 coefficient.
        double EWMACoeff = 0.01 * nbStepSinceLastUpdate;
        // EWMA must be <= 1.
        if(EWMACoeff > 1)
            EWMACoeff = 1.0;
        return lastValue * (1.0 - EWMACoeff) + newValue * EWMACoeff;
     * Computes the loss rate.
     *
     * @param nbLostAndRecv The number of lost and received packets.
     * @param nbLost The number of lost packets.
     *
     * @return The loss rate in percent.
    private static double computePercentLoss(long nbLostAndRecv, long nbLost)
    {
        return
            (nbLostAndRecv == 0)
                ? 0
                : (((double) 100 * nbLost) / nbLostAndRecv);
    }
     * Computes the bandwidth usage in Kilo bits per seconds.
     *
     * @param nbByteRecv The number of Byte received.
     * @param callNbTimeMsSpent The time spent since the mediaStreamImpl is
     * connected to the endpoint.
     *
     * @return the bandwidth rate computed in Kilo bits per seconds.
    private static double computeRateKiloBitPerSec(
            long nbByteRecv,
            long callNbTimeMsSpent)
    {
        return
            (nbByteRecv == 0)
                ? 0
                : ((nbByteRecv * 8.0 / 1000.0) / (callNbTimeMsSpent / 1000.0));
    }
     * Gets the <tt>JitterBufferControl</tt> of a <tt>ReceiveStream</tt>.
     *
     * @param receiveStream the <tt>ReceiveStream</tt> to get the
     * <tt>JitterBufferControl</tt> of
     * @return the <tt>JitterBufferControl</tt> of <tt>receiveStream</tt>.
    public static JitterBufferControl getJitterBufferControl(
            ReceiveStream receiveStream)
    {
        DataSource ds = receiveStream.getDataSource();

        if (ds instanceof PushBufferDataSource)
        {
            for (PushBufferStream pbs
                    : ((PushBufferDataSource) ds).getStreams())
            {
                JitterBufferControl pqc
                    = (JitterBufferControl)
                        pbs.getControl(JitterBufferControl.class.getName());

                if (pqc != null)
                    return pqc;
            }
        }
        return null;
    }
     * The last jitter received/sent in a RTCP feedback (in RTP timestamp
     * units).
    private double[] jitterRTPTimestampUnits = {0, 0};
     * The source data stream to analyze in order to compute the stats.
    private final MediaStreamImpl mediaStreamImpl;

    /**
     * The last number of received/sent Bytes.
     */
    private long[] nbByte = {0, 0};
    /**
     * The total number of discarded packets
     */
    private long nbDiscarded = 0;

    /**
     * The number of packets for which FEC data was decoded. This is only
     */
    private long nbFec = 0;

     * The last number of download/upload lost packets.
    private long[] nbLost = {0, 0};
     * The last number of received/sent packets.
    private long[] nbPackets = {0, 0};
    /**
     * The last percent of discarded packets
     */
    private double percentDiscarded = 0;

    /**
     * The last download/upload loss rate computed (in %).
     */
    private double[] percentLoss = {0, 0};

    /**
     * The last used bandwidth computed in download/upload (in Kbit/s).
     */
    private double[] rateKiloBitPerSec = {0, 0};

    /**
     * The <tt>RTCPReportListener</tt> which listens to {@link #rtcpReports}
     * about the sending and the receiving of RTCP sender/receiver reports and
     * updates this <tt>MediaStreamStats</tt> with their feedback reports.
    private final RTCPReportListener rtcpReportListener
        = new RTCPReportAdapter()
        {
            /**
             * {@inheritDoc}
             *
             * Updates this <tt>MediaStreamStats</tt> with the received feedback
             * (report).
             */
            @Override
            public void rtcpReportReceived(RTCPReport report)
            {
                List<?> feedbackReports = report.getFeedbackReports();

                if (!feedbackReports.isEmpty())
                {
                    updateNewReceivedFeedback(
                            (RTCPFeedback) feedbackReports.get(0));
                }
            }

            /**
             * {@inheritDoc}
             *
             * Updates this <tt>MediaStreamStats</tt> with the sent feedback
             * (report).
             */
            @Override
            public void rtcpReportSent(RTCPReport report)
            {
                List<?> feedbackReports = report.getFeedbackReports();

                if (!feedbackReports.isEmpty())
                {
                    updateNewSentFeedback(
                            (RTCPFeedback) feedbackReports.get(0));
                }
            }
        };

    /**
     * The detailed statistics about the RTCP reports sent and received by the
     * associated local peer.
     */
    private final RTCPReports rtcpReports = new RTCPReports();
    /**
     * The RTT computed with the RTCP feedback (cf. RFC3550, section 6.4.1,
     * subsection "delay since last SR (DLSR): 32 bits").
     * -1 if the RTT has not been computed yet. Otherwise the RTT in ms.
     */
    private long rttMs = -1;

    /**
     * The last time these stats have been updated.
     */
    private long updateTimeMs;

    /**
     * The last number of sent packets when the last feedback has been received.
     * This counter is used to compute the upload loss rate.
     */
    private long uploadFeedbackNbPackets = 0;

    /**
     * Creates a new instance of stats concerning a MediaStream.
     *
     * @param mediaStreamImpl The MediaStreamImpl used to compute the stats.
     */
    public MediaStreamStatsImpl(MediaStreamImpl mediaStreamImpl)
    {
        this.mediaStreamImpl = mediaStreamImpl;

        getRTCPReports().addRTCPReportListener(rtcpReportListener);
        updateTimeMs = System.currentTimeMillis();
     * Computes the RTT with the data (LSR and DLSR) contained in the last
     * RTCP Sender Report (RTCP feedback). This RTT computation is based on
     * RFC3550, section 6.4.1, subsection "delay since last SR (DLSR): 32
     * bits".
     * @param feedback The last RTCP feedback received by the MediaStream.
     *
     * @return The RTT in milliseconds, or -1 if the RTT is not computable.
    private long computeRTTInMs(RTCPFeedback feedback)
        long now = System.currentTimeMillis();
        long lsr = feedback.getLSR();
        long dlsr = feedback.getDLSR();
        int rtt = RecvSSRCInfo.getRoundTripDelay(now, lsr, dlsr);

        /*
         * If the RTT is greater than a minute, it may signal a bug in the
         * computation. Log such occurrences in order to debug them.
         */
        if ((rtt >= 65536) && logger.isInfoEnabled())
            logger.info(
                    "Stream: " + mediaStreamImpl.getName()
                        + ", RTT computation may be wrong (" + rtt
                        + ">= 65536 milliseconds): now " + now + ", lsr " + lsr
                        + ", dlsr " + dlsr);
        return rtt;
     * Returns the jitter average of this download stream.
     * @return the last jitter average computed (in ms).
    public double getDownloadJitterMs()
        return getJitterMs(StreamDirection.DOWNLOAD);
     * Returns the number of Protocol Data Units (PDU) lost in download since
     * the beginning of the session.
     * @return the number of packets lost for this stream.
    private long getDownloadNbPDULost()
        MediaDeviceSession devSession = mediaStreamImpl.getDeviceSession();
        int nbLost = 0;
        if (devSession != null)
        {
            for(ReceiveStream receiveStream : devSession.getReceiveStreams())
                nbLost += receiveStream.getSourceReceptionStats().getPDUlost();
        }
        return nbLost;
     * Returns the percent loss of the download stream.
     * @return the last loss rate computed (in %).
    public double getDownloadPercentLoss()
        return percentLoss[StreamDirection.DOWNLOAD.ordinal()];
     * Returns the bandwidth used by this download stream.
     * @return the last used download bandwidth computed (in Kbit/s).
    public double getDownloadRateKiloBitPerSec()
        return rateKiloBitPerSec[StreamDirection.DOWNLOAD.ordinal()];
    }

    /**
     * Returns the download video format if this stream downloads a video, or
     * null if not.
     *
     * @return the download video format if this stream downloads a video, or
     * null if not.
     */
    private VideoFormat getDownloadVideoFormat()
    {
        MediaDeviceSession deviceSession = mediaStreamImpl.getDeviceSession();
            (deviceSession instanceof VideoMediaDeviceSession)
                ? ((VideoMediaDeviceSession) deviceSession)
    }

    /**
     * Returns the download video size if this stream downloads a video, or
     * null if not.
     *
     * @return the download video size if this stream downloads a video, or null
     * if not.
     */
    public Dimension getDownloadVideoSize()
    {
        VideoFormat format = getDownloadVideoFormat();

        return (format == null) ? null : format.getSize();
     * Returns the MediaStream enconding.
     * @return the encoding used by the stream.
    public String getEncoding()
        MediaFormat format = mediaStreamImpl.getFormat();
        return (format == null) ? null : format.getEncoding();
     * Returns the MediaStream enconding rate (in Hz)..
     * @return the encoding rate used by the stream.
    public String getEncodingClockRate()
        MediaFormat format = mediaStreamImpl.getFormat();
        return (format == null) ? null : format.getRealUsedClockRateString();
     * Returns the delay in milliseconds introduced by the jitter buffer.
     * Since there might be multiple <tt>ReceiveStreams</tt>, returns the
     * biggest delay found in any of them.
     * @return the delay in milliseconds introduces by the jitter buffer
    public int getJitterBufferDelayMs()
        int delay = 0;
        for(JitterBufferControl pqc : getJitterBufferControls())
          if(pqc.getCurrentDelayMs() > delay)
              delay = pqc.getCurrentDelayMs();
        return delay;
     * Returns the delay in number of packets introduced by the jitter buffer.
     * Since there might be multiple <tt>ReceiveStreams</tt>, returns the
     * biggest delay found in any of them.
     * @return the delay in number of packets introduced by the jitter buffer
    public int getJitterBufferDelayPackets()
        int delay = 0;
        for(JitterBufferControl pqc : getJitterBufferControls())
            if(pqc.getCurrentDelayPackets() > delay)
                delay = pqc.getCurrentDelayPackets();
        return delay;
    }

    /**
     * Returns the jitter average of this upload/download stream.
     *
     * @param streamDirection The stream direction (DOWNLOAD or UPLOAD) of the
     * stream from which this function retrieve the jitter.
     *
     * @return the last jitter average computed (in ms).
     */
    private double getJitterMs(StreamDirection streamDirection)
    {
        MediaFormat format = mediaStreamImpl.getFormat();
        double clockRate;

        if (format == null)
        {
            MediaType mediaType = mediaStreamImpl.getMediaType();

            clockRate = MediaType.VIDEO.equals(mediaType) ? 90000 : -1;
        }
        else
            clockRate = format.getClockRate();

        if (clockRate <= 0)
            return -1;

        // RFC3550 says that concerning the RTP timestamp unit (cf. section 5.1
        // RTP Fixed Header Fields, subsection timestamp: 32 bits):
        // As an example, for fixed-rate audio the timestamp clock would likely
        // increment by one for each sampling period.
        //
        // Thus we take the jitter in RTP timestamp units, convert it to seconds
        // (/ clockRate) and finally converts it to milliseconds  (* 1000).
        return
            (jitterRTPTimestampUnits[streamDirection.ordinal()] / clockRate)
                * 1000.0;
     * Returns the local IP address of the MediaStream.
     * @return the local IP address of the stream.
    public String getLocalIPAddress()
        InetSocketAddress mediaStreamLocalDataAddress
            = mediaStreamImpl.getLocalDataAddress();

        return
            (mediaStreamLocalDataAddress == null)
                ? null
                : mediaStreamLocalDataAddress.getAddress().getHostAddress();
     * Returns the local port of the MediaStream.
     * @return the local port of the stream.
    public int getLocalPort()
        InetSocketAddress mediaStreamLocalDataAddress
            = mediaStreamImpl.getLocalDataAddress();
        return
            (mediaStreamLocalDataAddress == null)
                ? -1
                : mediaStreamLocalDataAddress.getPort();
     * Returns the number of sent/received bytes since the beginning of the
     * session.
     * @param streamDirection The stream direction (DOWNLOAD or UPLOAD) of the
     * stream from which this function retrieve the number of sent/received
     * bytes.
     *
     * @return the number of sent/received bytes for this stream.
    private long getNbBytes(StreamDirection streamDirection)
        StreamRTPManager rtpManager = mediaStreamImpl.queryRTPManager();
        long nbBytes = 0;
        if(rtpManager != null)
        {
            switch(streamDirection)
            {
            case DOWNLOAD:
                nbBytes = rtpManager.getGlobalReceptionStats().getBytesRecd();
                break;
            case UPLOAD:
                nbBytes
                    = rtpManager.getGlobalTransmissionStats().getBytesSent();
                break;
            }
        }
        return nbBytes;
     * Returns the total number of Protocol Data Units (PDU) discarded by the
     * FMJ packet queue since the beginning of the session. It's the sum over
     * all <tt>ReceiveStream</tt>s of the <tt>MediaStream</tt>
     * @return the number of discarded packets.
    public long getNbDiscarded()
        int nbDiscarded = 0;
        for(JitterBufferControl pqc : getJitterBufferControls())
            nbDiscarded =+ pqc.getDiscarded();
        return nbDiscarded;
     * Returns the number of Protocol Data Units (PDU) discarded by the
     * FMJ packet queue since the beginning of the session because it was full.
     * It's the sum over all <tt>ReceiveStream</tt>s of the <tt>MediaStream</tt>
     * @return the number of discarded packets because it was full.
    public int getNbDiscardedFull()
        int nbDiscardedFull = 0;
        for(JitterBufferControl pqc : getJitterBufferControls())
            nbDiscardedFull =+ pqc.getDiscardedFull();
        return nbDiscardedFull;
     * Returns the number of Protocol Data Units (PDU) discarded by the
     * FMJ packet queue since the beginning of the session because they were late.
     * It's the sum over all <tt>ReceiveStream</tt>s of the <tt>MediaStream</tt>
     * @return the number of discarded packets because they were late.
    public int getNbDiscardedLate()
        int nbDiscardedLate = 0;
        for(JitterBufferControl pqc : getJitterBufferControls())
            nbDiscardedLate =+ pqc.getDiscardedLate();
        return nbDiscardedLate;
     * Returns the number of Protocol Data Units (PDU) discarded by the
     * FMJ packet queue since the beginning of the session during resets.
     * It's the sum over all <tt>ReceiveStream</tt>s of the <tt>MediaStream</tt>
     * @return the number of discarded packets during resets.
     */
    public int getNbDiscardedReset()
    {
        int nbDiscardedReset = 0;
        for(JitterBufferControl pqc : getJitterBufferControls())
            nbDiscardedReset =+ pqc.getDiscardedReset();
        return nbDiscardedReset;
    }

    /**
     * Returns the number of Protocol Data Units (PDU) discarded by the
     * FMJ packet queue since the beginning of the session due to shrinking.
     * It's the sum over all <tt>ReceiveStream</tt>s of the <tt>MediaStream</tt>
     * @return the number of discarded packets due to shrinking.
    public int getNbDiscardedShrink()
        int nbDiscardedShrink = 0;
        for(JitterBufferControl pqc : getJitterBufferControls())
            nbDiscardedShrink =+ pqc.getDiscardedShrink();
        return nbDiscardedShrink;
    }
    /**
     * Returns the number of packets for which FEC data was decoded. Currently
     * this is cumulative over all <tt>ReceiveStream</tt>s.
     *
     * @return the number of packets for which FEC data was decoded. Currently
     * this is cumulative over all <tt>ReceiveStream</tt>s.
     *
     * @see org.jitsi.impl.neomedia.MediaStreamStatsImpl#updateNbFec()
     */
    public long getNbFec()
    {
        return nbFec;
    }

    /**
     * Returns the number of Protocol Data Units (PDU) sent/received since the
     * beginning of the session.
     *
     * @param streamDirection The stream direction (DOWNLOAD or UPLOAD) of the
     * stream from which this function retrieve the number of sent/received
     * packets.
     *
     * @return the number of packets sent/received for this stream.
     */
    private long getNbPDU(StreamDirection streamDirection)
    {
        StreamRTPManager rtpManager = mediaStreamImpl.queryRTPManager();
        long nbPDU = 0;

        if(rtpManager != null)
        {
            switch(streamDirection)
            {
            case UPLOAD:
                nbPDU = rtpManager.getGlobalTransmissionStats().getRTPSent();
                break;

            case DOWNLOAD:
                GlobalReceptionStats globalReceptionStats
                    = rtpManager.getGlobalReceptionStats();

                nbPDU
                    = globalReceptionStats.getPacketsRecd()
                        - globalReceptionStats.getRTCPRecd();
    /**
     * Returns the total number of packets that are send or receive for this
     * stream since the stream is created.
     * @return the total number of packets.
     */
    public long getNbPackets()
    {
        return getNbPDU(StreamDirection.DOWNLOAD)
            + nbLost[StreamDirection.DOWNLOAD.ordinal()]
            + uploadFeedbackNbPackets;
    }

    /**
     * Returns the number of lost packets for that stream.
     * @return the number of lost packets.
     */
    public long getNbPacketsLost()
    {
        return nbLost[StreamDirection.DOWNLOAD.ordinal()]
            + nbLost[StreamDirection.UPLOAD.ordinal()];
    }

     * Returns the set of <tt>PacketQueueControls</tt> found for all the
     * <tt>DataSource</tt>s of all the <tt>ReceiveStream</tt>s. The set contains
     * only non-null elements.
     * @return the set of <tt>PacketQueueControls</tt> found for all the
     * <tt>DataSource</tt>s of all the <tt>ReceiveStream</tt>s. The set contains
     * only non-null elements.
    private Set<JitterBufferControl> getJitterBufferControls()
        Set<JitterBufferControl> set = new HashSet<JitterBufferControl>();
        if (mediaStreamImpl.isStarted())
            MediaDeviceSession devSession = mediaStreamImpl.getDeviceSession();

            if (devSession != null)
            {
                for(ReceiveStream receiveStream
                        : devSession.getReceiveStreams())
                {
                    JitterBufferControl pqc
                        = getJitterBufferControl(receiveStream);

                    if(pqc != null)
                        set.add(pqc);
                }
            }
        return set;
     * Returns the number of packets in the first <tt>JitterBufferControl</tt>
     * found via <tt>getJitterBufferControls</tt>.
     * @return the number of packets in the first <tt>JitterBufferControl</tt>
     * found via <tt>getJitterBufferControls</tt>.
    public int getPacketQueueCountPackets()
        for(JitterBufferControl pqc : getJitterBufferControls())
            return pqc.getCurrentPacketCount();
        return 0;
     * Returns the size of the first <tt>JitterBufferControl</tt> found via
     * <tt>getJitterBufferControls</tt>.
     * @return the size of the first <tt>JitterBufferControl</tt> found via
     * <tt>getJitterBufferControls</tt>.
    public int getPacketQueueSize()
        for(JitterBufferControl pqc : getJitterBufferControls())
            return pqc.getCurrentSizePackets();
        return 0;
     * Returns the percent of discarded packets
     * @return the percent of discarded packets
    public double getPercentDiscarded()
        return percentDiscarded;
     * Returns the remote IP address of the MediaStream.
     * @return the remote IP address of the stream.
    public String getRemoteIPAddress()
        MediaStreamTarget mediaStreamTarget = mediaStreamImpl.getTarget();

        // Gets this stream IP address endpoint. Stops if the endpoint is
        // disconnected.
        return
            (mediaStreamTarget == null)
                ? null
                : mediaStreamTarget.getDataAddress().getAddress()
                        .getHostAddress();
     * Returns the remote port of the MediaStream.
     * @return the remote port of the stream.
    public int getRemotePort()
        MediaStreamTarget mediaStreamTarget = mediaStreamImpl.getTarget();

        // Gets this stream port endpoint. Stops if the endpoint is
        // disconnected.
        return
            (mediaStreamTarget == null)
                ? -1
                : mediaStreamTarget.getDataAddress().getPort();
     * {@inheritDoc}
     */
    @Override
    public RTCPReports getRTCPReports()
    {
        return rtcpReports;
    }

    /**
     * Returns the RTT computed with the RTCP feedback (cf. RFC3550, section
     * 6.4.1, subsection "delay since last SR (DLSR): 32 bits").
     * @return The RTT computed with the RTCP feedback. Returns -1 if the RTT
     * has not been computed yet. Otherwise the RTT in ms.
    public long getRttMs()
        return rttMs;
    }
    /**
     * Returns the jitter average of this upload stream.
     *
     * @return the last jitter average computed (in ms).
     */
    public double getUploadJitterMs()
    {
        return getJitterMs(StreamDirection.UPLOAD);
     * Returns the percent loss of the upload stream.
     * @return the last loss rate computed (in %).
     */
    public double getUploadPercentLoss()
    {
        return percentLoss[StreamDirection.UPLOAD.ordinal()];
    }

    /**
     * Returns the bandwidth used by this download stream.
     * @return the last used upload bandwidth computed (in Kbit/s).
    public double getUploadRateKiloBitPerSec()
        return rateKiloBitPerSec[StreamDirection.UPLOAD.ordinal()];
    }
    /**
     * Returns the upload video format if this stream uploads a video, or null
     * if not.
     *
     * @return the upload video format if this stream uploads a video, or null
     * if not.
     */
    private VideoFormat getUploadVideoFormat()
    {
        MediaDeviceSession deviceSession = mediaStreamImpl.getDeviceSession();
        return
            (deviceSession instanceof VideoMediaDeviceSession)
                ? ((VideoMediaDeviceSession) deviceSession)
                    .getSentVideoFormat()
                : null;
    }
    /**
     * Returns the upload video size if this stream uploads a video, or null if
     * not.
     *
     * @return the upload video size if this stream uploads a video, or null if
     * not.
     */
    public Dimension getUploadVideoSize()
    {
        VideoFormat format = getUploadVideoFormat();
        return (format == null) ? null : format.getSize();
    }
    public boolean isAdaptiveBufferEnabled()
    {
        for(JitterBufferControl pcq : getJitterBufferControls())
            if(pcq.isAdaptiveBufferEnabled())
                return true;
        return false;
     * Updates the jitter stream stats with the new feedback sent.
     * @param feedback The last RTCP feedback sent by the MediaStream.
     * @param streamDirection The stream direction (DOWNLOAD or UPLOAD) of the
     * stream from which this function retrieve the jitter.
    private void updateJitterRTPTimestampUnits(
            RTCPFeedback feedback,
            StreamDirection streamDirection)
        // Updates the download jitter in RTP timestamp units. There is no need
        // to compute a jitter average, since (cf. RFC3550, section 6.4.1 SR:
        // Sender Report RTCP Packet, subsection interarrival jitter: 32 bits)
        // the value contained in the RTCP sender report packet contains a mean
        // deviation of the jitter.
        jitterRTPTimestampUnits[streamDirection.ordinal()]
            = feedback.getJitter();
     * Updates the number of discarded packets.
     * @param newNbDiscarded The last update of the number of lost.
     * @param nbSteps The number of elapsed steps since the last number of loss
     * update.
    private void updateNbDiscarded(
            long newNbDiscarded,
            long nbSteps)
        double newPercentDiscarded
            = MediaStreamStatsImpl.computePercentLoss(nbSteps, newNbDiscarded);

        percentDiscarded
            = MediaStreamStatsImpl.computeEWMA(
                    nbSteps,
                    percentDiscarded,
                    newPercentDiscarded);
        // Saves the last update number download lost value.
        nbDiscarded += newNbDiscarded;
    }

    /**
     * Updates the <tt>nbFec</tt> field with the sum of FEC-decoded packets
     * over the different <tt>ReceiveStream</tt>s
     */
    private void updateNbFec()
    {
        MediaDeviceSession devSession = mediaStreamImpl.getDeviceSession();
            for(ReceiveStream receiveStream : devSession.getReceiveStreams())
                        : devSession.getDecoderControls(
                                receiveStream,
                    nbFec += fecDecoderControl.fecPacketsDecoded();
     * Updates the number of loss for a given stream.
     * @param streamDirection The stream direction (DOWNLOAD or UPLOAD) of the
     * stream from which this function updates the stats.
     * @param newNbLost The last update of the number of lost.
     * @param nbSteps The number of elapsed steps since the last number of loss
     * update.
     */
    private void updateNbLoss(
            StreamDirection streamDirection,
            long newNbLost,
        int streamDirectionIndex = streamDirection.ordinal();
        double newPercentLoss
            = MediaStreamStatsImpl.computePercentLoss(nbSteps, newNbLost);
        percentLoss[streamDirectionIndex]
            = MediaStreamStatsImpl.computeEWMA(
                    nbSteps,
                    percentLoss[streamDirectionIndex],
                    newPercentLoss);
        // Saves the last update number download lost value.
        nbLost[streamDirectionIndex] += newNbLost;
     * Updates this stream stats with the new feedback received.
     * @param feedback The last RTCP feedback received by the MediaStream.
    private void updateNewReceivedFeedback(RTCPFeedback feedback)
        StreamDirection streamDirection = StreamDirection.UPLOAD;
        updateJitterRTPTimestampUnits(feedback, streamDirection);
        // Updates the loss rate with the RTCP sender report feedback, since
        // this is the only information source available for the upload stream.
        long uploadNewNbRecv = feedback.getXtndSeqNum();
        long newNbLost
            = feedback.getNumLost() - nbLost[streamDirection.ordinal()];
        long nbSteps = uploadNewNbRecv - uploadFeedbackNbPackets;

        updateNbLoss(streamDirection, newNbLost, nbSteps);

        // Updates the upload loss counters.
        uploadFeedbackNbPackets = uploadNewNbRecv;

        // Computes RTT.
        rttMs = computeRTTInMs(feedback);
     * Updates this stream stats with the new feedback sent.
     * @param feedback The last RTCP feedback sent by the MediaStream.
    private void updateNewSentFeedback(RTCPFeedback feedback)
        updateJitterRTPTimestampUnits(feedback, StreamDirection.DOWNLOAD);

        // No need to update the download loss as we have a more accurate value
        // in the global reception stats, which are updated for each new packet
        // received.
     * Computes and updates information for a specific stream.
    public void updateStats()
        // Gets the current time.
        long currentTimeMs = System.currentTimeMillis();

        // UPdates stats for the download stream.
        updateStreamDirectionStats(StreamDirection.DOWNLOAD, currentTimeMs);
        // UPdates stats for the upload stream.
        updateStreamDirectionStats(StreamDirection.UPLOAD, currentTimeMs);
        // Saves the last update values.
        updateTimeMs = currentTimeMs;
     * Computes and updates information for a specific stream.
     * @param streamDirection The stream direction (DOWNLOAD or UPLOAD) of the
     * stream from which this function updates the stats.
     * @param currentTimeMs The current time in ms.
    private void updateStreamDirectionStats(
            StreamDirection streamDirection,
            long currentTimeMs)
        int streamDirectionIndex = streamDirection.ordinal();

        // Gets the current number of packets correctly received since the
        // beginning of this stream.
        long newNbRecv = getNbPDU(streamDirection);
        // Gets the number of byte received/sent since the beginning of this
        // stream.
        long newNbByte = getNbBytes(streamDirection);

        // Computes the number of update steps which has not been done since
        // last update.
        long nbSteps = newNbRecv - nbPackets[streamDirectionIndex];
        // Even if the remote peer does not send any packets (i.e. is
        // microphone is muted), Jitsi must updates it stats. Thus, Jitsi
        // computes a number of steps equivalent as if Jitsi receives a packet
        // each 20ms (default value).
        if(nbSteps == 0)
            nbSteps = (currentTimeMs - updateTimeMs) / 20;

        // The upload percentLoss is only computed when a new RTCP feedback is
        // received. This is not the case for the download percentLoss which is
        // updated for each new RTP packet received.
        // Computes the loss rate for this stream.
        if(streamDirection == StreamDirection.DOWNLOAD)
            // Gets the current number of losses in download since the beginning
            // of this stream.
            long newNbLost
                = getDownloadNbPDULost() - nbLost[streamDirectionIndex];

            updateNbLoss(streamDirection, newNbLost, nbSteps + newNbLost);

            long newNbDiscarded = getNbDiscarded() - nbDiscarded;
            updateNbDiscarded(newNbDiscarded, nbSteps + newNbDiscarded);

        // Computes the bandwidth used by this stream.
        double newRateKiloBitPerSec
            = MediaStreamStatsImpl.computeRateKiloBitPerSec(
                    newNbByte - nbByte[streamDirectionIndex],
                    currentTimeMs - updateTimeMs);
        rateKiloBitPerSec[streamDirectionIndex]
            = MediaStreamStatsImpl.computeEWMA(
                    nbSteps,
                    rateKiloBitPerSec[streamDirectionIndex],
                    newRateKiloBitPerSec);

        // Saves the last update values.
        nbPackets[streamDirectionIndex] = newNbRecv;
        nbByte[streamDirectionIndex] = newNbByte;

        updateNbFec();