public RTCPReportSampler(Guid rtpStreamId, uint syncSource, IPEndPoint remoteEndPoint, UInt16 startSequenceNumber, DateTime startTime, long bytesReceived) { m_rtpStreamId = rtpStreamId; m_syncSource = syncSource; m_remoteEndPoint = remoteEndPoint; m_windowStartSeqNum = startSequenceNumber; m_windowLastSeqNum = startSequenceNumber; m_windowSecondLastSeqNum = startSequenceNumber; m_lastSampleTime = DateTime.Now; logger.Debug("New RTCP report created for " + syncSource + " for stream from " + IPSocket.GetSocketString(remoteEndPoint) + ", start seq num=" + startSequenceNumber + "."); //resultsLogger.Info("StartTime,StartTimestamp,EndTime,EndTimestamp,Duration(ms),StartSeqNum,EndSeqNum,TotalPackets,TotalBytes,TransmissionRate(bps),Drops,Duplicates"); RTPReceiveRecord measurement = new RTPReceiveRecord(startTime, startSequenceNumber, bytesReceived, 0, true, false); m_rcvdSeqNums.Add(startSequenceNumber, measurement); }
/// <summary> /// All times passed into this method should already be UTC. /// </summary> private RTCPReport Sample(UInt16 sampleStartSequenceNumber, UInt16 sampleEndSequenceNumber, TimeSpan sampleDuration) { try { RTCPReport sample = new RTCPReport(m_rtpStreamId, m_syncSource, m_remoteEndPoint); sample.ReportNumber = m_reportNumber; sample.SampleStartTime = DateTime.MinValue; //sample.SampleEndTime = DateTime.MinValue; sample.StartSequenceNumber = sampleStartSequenceNumber; sample.Duration = Convert.ToUInt64(sampleDuration.TotalMilliseconds); double jitterTotal = 0; //double transitTotal = 0; int endSequence = (sampleEndSequenceNumber < sampleStartSequenceNumber) ? sampleEndSequenceNumber + UInt16.MaxValue + 1 : sampleEndSequenceNumber; // logger.Debug("Sampling range " + sampleStartSequenceNumber + " to " + endSequence); for (int index = sampleStartSequenceNumber; index <= endSequence; index++) { UInt16 testSeqNum = (index > UInt16.MaxValue) ? Convert.ToUInt16((index % UInt16.MaxValue) - 1) : Convert.ToUInt16(index); //logger.Debug("Sampling " + testSeqNum + "."); if (m_rcvdSeqNums.ContainsKey(testSeqNum)) { RTPReceiveRecord measurement = m_rcvdSeqNums[testSeqNum]; //sample.SampleEndTime = measurement.SendTime; if (sample.SampleStartTime == DateTime.MinValue) { sample.SampleStartTime = measurement.ReceiveTime; } sample.SampleEndTime = measurement.ReceiveTime; sample.EndSequenceNumber = measurement.SequenceNumber; sample.TotalPackets++; sample.BytesReceived += (uint)measurement.RTPBytes; if (measurement.Duplicates > 0) { logger.Debug("Duplicates for " + testSeqNum + " number " + measurement.Duplicates); sample.Duplicates += (uint)measurement.Duplicates; } if (!measurement.InSequence) { logger.Debug("OutOfOrder for " + testSeqNum); sample.OutOfOrder++; } else { // It is possible for the jitter to be negative as an average measurement is being used for the transit time and // some transits could be slightly less than the average. //double jitter = Math.Abs(measurement.ReceiveTime.Subtract(measurement.SendTime).TotalMilliseconds - averageTransitTime); jitterTotal += measurement.Jitter; //transitTotal += measurement.AverageTransit; if (measurement.Jitter > sample.JitterMaximum) { //logger.Debug("Jitter max set to " + measurement.Jitter + " for sequence number " + measurement.SequenceNumber); sample.JitterMaximum = (uint)measurement.Jitter; } } if (measurement.JitterBufferDiscard) { logger.Debug("Jitter discard for " + testSeqNum); sample.JitterDiscards++; } // Remove the measurement from the buffer. // Remove the RTP measurements now that they have been sampled. lock (m_rcvdSeqNums) { //logger.Debug("Removing " + index); m_rcvdSeqNums.Remove(testSeqNum); } } else { logger.Debug("Packet drop for " + index); sample.PacketsLost++; } } // Calculate the average jitter. if (sample.TotalPackets > 0) { sample.JitterAverage = Convert.ToUInt32(jitterTotal / sample.TotalPackets); } // Calculate the average transit. //if (sample.TotalPackets > 0) //{ // sample.AverageTransitTime = Convert.ToUInt32(transitTotal / sample.TotalPackets); //} // Calculate the transmission rate. double packetRate = 0; if (sampleDuration.TotalMilliseconds > 0) { sample.TransmissionRate = Convert.ToUInt32(sample.BytesReceived * 8 / sampleDuration.TotalSeconds); packetRate = sample.TotalPackets / sampleDuration.TotalSeconds; } string rtcpReport = String.Format(RTCP_FORMAT_STRING, new object[] { sample.SyncSource, sample.SampleStartTime.ToString("HH:mm:ss:fff"), sample.SampleEndTime.ToString("HH:mm:ss:fff"), sampleDuration.TotalMilliseconds.ToString("0"), sample.StartSequenceNumber.ToString(), sample.EndSequenceNumber.ToString(), sample.TotalPackets.ToString(), sample.JitterMaximum.ToString("0"), sample.JitterAverage.ToString("0.##"), sample.AverageTransitTime.ToString("0.##"), packetRate.ToString("0.##"), sample.BytesReceived.ToString(), sample.TransmissionRate.ToString("0.##"), sample.PacketsLost.ToString(), sample.JitterDiscards.ToString(), sample.Duplicates.ToString(), sample.OutOfOrder.ToString() }); logger.Info(rtcpReport); //logger.Info("start=" + sample.SampleStartTime.ToString("HH:mm:ss:fff") + ",end=" + sample.SampleEndTime.ToString("HH:mm:ss:fff") + ",dur=" + sampleDuration.TotalMilliseconds.ToString("0") + "ms" + // ",seqnnumstart=" + sample.StartSequenceNumber + ",seqnumend=" + sample.EndSequenceNumber + ",pktstotal=" + sample.TotalPackets + "p,pktrate=" + packetRate.ToString("0.##") + "pps,bytestotal=" + sample.BytesReceived + "B,bw=" + sample.TransmissionRate.ToString("0.##") + "Kbps,jitteravg=" + // sample.JitterAverage.ToString("0.##") + ",jittermax=" + sample.JitterMaximum.ToString("0.##") + ",pktslost=" + sample.PacketsLost + ",jitterdiscards=" + sample.JitterDiscards + ",duplicates=" + sample.Duplicates + ",outoforder=" + sample.OutOfOrder); return(sample); } catch (Exception excp) { logger.Error("Exception Sample. " + excp.Message); return(null); } finally { m_reportNumber++; } }
/// <summary> /// A sample is taken if the last RTP measurement recorded is 4x the sample time since the last last smaple was taken. /// The 4x is needed in order to be able to take into account late arriving packets as out of order rather then as drops. /// </summary> /// <param name="sequenceNumber"></param> private RTCPReport CheckForAvailableSample(UInt16 sequenceNumber) { try { //logger.Debug("Check for available sample " + sequenceNumber + "."); RTCPReport sample = null; RTPReceiveRecord measurement = m_rcvdSeqNums[sequenceNumber]; //logger.Debug("window start seq num=" + m_windowStartSeqNum); RTPReceiveRecord startSampleMeasuerment = m_rcvdSeqNums[m_windowStartSeqNum]; UInt16 endSampleSeqNum = 0; UInt16 endSampleSeqNumMinusOne = 0; int samplesAvailable = 0; DateTime sampleCutOffTime; bool sampleAvailable = false; // Determine whether a sample of the RTP stream measurements should be taken. if (DateTime.Now.Subtract(m_lastSampleTime).TotalMilliseconds > (4 * ReportSampleDuration)) { // Make the sample a random size between N and 2N int randomElement = Crypto.GetRandomInt(ReportSampleDuration, 2 * ReportSampleDuration); int sampleDuration = ReportSampleDuration + randomElement; sampleCutOffTime = m_lastSampleTime.AddMilliseconds(sampleDuration); //logger.Debug("Sample duration=" + sampleDuration + "ms, cut off time=" + sampleCutOffTime.ToString("HH:mm:ss:fff") + "."); // Get the list of RTP measurements from last time a sample was taken up to the last receive within the window. int endSeqNum = (sequenceNumber < m_windowStartSeqNum) ? sequenceNumber + UInt16.MaxValue + 1: sequenceNumber; //logger.Debug("Checking for sample from " + m_windowStartSeqNum + " to " + endSeqNum + "."); for (int seqNum = m_windowStartSeqNum; seqNum <= endSeqNum; seqNum++) { UInt16 testSeqNum = (seqNum > UInt16.MaxValue) ? Convert.ToUInt16((seqNum % UInt16.MaxValue) - 1) : Convert.ToUInt16(seqNum); if (m_rcvdSeqNums.ContainsKey(testSeqNum)) { //logger.Debug(testSeqNum + " " + m_rcvdSeqNums[testSeqNum].ReceiveTime.ToString("ss:fff") + "<" + sampleCutOffTime.ToString("ss:fff") + "."); if (m_rcvdSeqNums[testSeqNum].ReceiveTime < sampleCutOffTime) { samplesAvailable++; endSampleSeqNum = testSeqNum; } else { endSampleSeqNumMinusOne = endSampleSeqNum; endSampleSeqNum = testSeqNum; sampleAvailable = true; break; } } } } /*if (m_rcvdSeqNums.Count > 200) * { * endSampleSeqNum = m_windowSecondLastSeqNum; * sampleAvailable = true; * }*/ if (sampleAvailable) { //logger.Debug(samplesAvailable + " ready for RTCP sampling, start seq num=" + m_windowStartSeqNum + " to " + endSampleSeqNumMinusOne + "."); TimeSpan measurementsSampleDuration = m_rcvdSeqNums[endSampleSeqNumMinusOne].ReceiveTime.Subtract(m_rcvdSeqNums[m_windowStartSeqNum].ReceiveTime); //logger.Debug("Sample available start seq num=" + m_windowStartSeqNum + " end seq num=" + endSampleSeqNumMinusOne + ", " + measurementsSampleDuration.TotalMilliseconds.ToString("0") + "."); sample = Sample(m_windowStartSeqNum, endSampleSeqNumMinusOne, measurementsSampleDuration); m_windowStartSeqNum = endSampleSeqNum; m_lastSampleTime = m_rcvdSeqNums[endSampleSeqNum].ReceiveTime; } return(sample); } catch (Exception excp) { logger.Error("Exception CheckForAvailableSample. " + excp.Message); return(null); } }
/// <summary> /// Records the arrival of a new RTP packet and periodically samples the mesaurements to record the characteristics of the RTP stream. /// </summary> /// <param name="sequenceNumber">RTP header sequence number, monotonically increasing in RTP stream.</param> /// <param name="sendTime">The remote time at which the RTP packet was sent.</param> /// <param name="receiveTime">The local time at which the RTP listener received the packet.</param> /// <param name="bytesReceived">Number of bytes received.</param> public void RecordRTPReceive(DateTime receiveTime, UInt16 sequenceNumber, long bytesReceived, uint jitter) { try { //logger.Debug("RecordRTPReceive " + sequenceNumber + "."); if (m_rcvdSeqNums.ContainsKey(sequenceNumber)) { logger.Debug("duplicate " + sequenceNumber + "."); m_rcvdSeqNums[sequenceNumber].Duplicates = m_rcvdSeqNums[sequenceNumber].Duplicates + 1; } //else if(sequenceNumber < m_windowStartSeqNum) //{ // OutsideWindow++; //} else { bool inSequence = (m_windowLastSeqNum != UInt16.MaxValue) ? sequenceNumber == m_windowLastSeqNum + 1 : sequenceNumber == 0; //bool inSequence = (Math.Abs(sequenceNumber - m_windowLastSeqNum) > (UInt16.MaxValue / 2)) ? sequenceNumber == m_windowLastSeqNum + 1 : sequenceNumber == 0; //logger.Debug(sequenceNumber + " in sequence=" + inSequence + "."); /*int startJitterBufferSeq = (m_windowLastSeqNum - JitterBufferSamples >= 0) ? m_windowLastSeqNum - JitterBufferSamples : m_windowLastSeqNum + 65535 - JitterBufferSamples; * int endJitterBufferSeq = (m_windowLastSeqNum + JitterBufferSamples <= 65535) ? m_windowLastSeqNum + JitterBufferSamples : m_windowLastSeqNum + JitterBufferSamples - 65535; * bool jitterDiscard = false; * * if(endJitterBufferSeq > startJitterBufferSeq) * { * jitterDiscard = !(sequenceNumber >= startJitterBufferSeq && sequenceNumber <= endJitterBufferSeq); * } * else * { * jitterDiscard = !(sequenceNumber >= startJitterBufferSeq || sequenceNumber <= endJitterBufferSeq); * }*/ m_windowSecondLastSeqNum = m_windowLastSeqNum; m_windowLastSeqNum = sequenceNumber; // Add measurement to buffer. /*DateTime utcSendTime = sendTime.ToUniversalTime(); * DateTime utcReceiveTime = receiveTime.ToUniversalTime(); * double transitTime = (utcSendTime < utcReceiveTime) ? utcReceiveTime.Subtract(utcSendTime).TotalMilliseconds : utcSendTime.Subtract(utcReceiveTime).TotalMilliseconds; */ //if (m_latestInterArrivalTimes.Count > TRANSITTIMES_QUEUE_LENGTH) //{ // m_latestInterArrivalTimes.Dequeue(); //} //m_latestInterArrivalTimes.Enqueue(interArrivalMilliseconds); //int avgArrivalTime = GetAverageTransitMilliseconds(); //logger.Debug("Avg transit time=" + avgTransitTime + "ms, " + sequenceNumber); //double jitterAbs = Math.Abs(interArrivalMilliseconds - avgArrivalTime); //logger.Debug("jitterAbs=" + jitterAbs + "ms, " + sequenceNumber); //int jitter = 0; //if (jitterAbs > 1) //{ // jitter = Convert.ToInt32(jitterAbs); //} bool jitterDiscard = false; if (jitter >= JitterBufferMilliseconds) { logger.Debug("jitter discard " + sequenceNumber + "."); jitterDiscard = true; } //logger.Debug("jitter=" + jitter + "ms, avg transit=" + avgArrivalTime); //resultsLogger.Info(sequenceNumber + "," + bytesReceived + "," + avgTransitTime + "," + jitter + "," + inSequence + "," + jitterDiscard + ",[" + startJitterBufferSeq + "<=" + sequenceNumber + "<=" + endJitterBufferSeq + "]"); //RTPReceiveRecord measurement = new RTPReceiveRecord(localSendTime, utcReceiveTime, sequenceNumber, bytesReceived); RTPReceiveRecord measurement = new RTPReceiveRecord(receiveTime, sequenceNumber, bytesReceived, jitter, inSequence, jitterDiscard); //logger.Debug("adding measurement for " + measurement.SequenceNumber + "."); lock (m_rcvdSeqNums) { if (m_rcvdSeqNums.ContainsKey(sequenceNumber)) { logger.Warn("RecordRTPReceive having to remove measurement for " + sequenceNumber + " in order to accomodate new RTP measurement."); m_rcvdSeqNums.Remove(sequenceNumber); } m_rcvdSeqNums.Add(sequenceNumber, measurement); } } //return CheckForAvailableSample(sequenceNumber); } catch (Exception excp) { logger.Error("Exception RecordRTPReceive for " + sequenceNumber + ". " + excp.Message); } }