private void CheckForSamples() { try { Thread.Sleep(CHECK_FORSAMPLE_PERIOD); // Wait until the first sample is likely to be ready. while (m_checkForSamples) { RTCPReport report = CheckForAvailableSample(m_windowLastSeqNum); if (report != null && RTCPReportReady != null) { try { RTCPReportReady(report); } catch { } } m_checkForSampleEvent.Reset(); m_checkForSampleEvent.WaitOne(CHECK_FORSAMPLE_PERIOD, false); } } catch (Exception excp) { logger.Debug("Exception CheckForSamples. " + excp.Message); } }
private void m_rtcpSampler_RTCPReportReady(RTCPReport rtcpReport) { if (rtcpReport != null && RTCPReportReady != null) { try { RTCPReportReady(rtcpReport); rtcpReport.LastReceivedReportNumber = LastReceivedReportNumber; //if (rtcpReport.ReportNumber % 3 != 0) //{ SendRTCPReport(RTCPReportTypesEnum.RTCP, rtcpReport.GetBytes()); //} } catch (Exception excp) { logger.Error("Exception m_rtcpSampler_RTCPReportReady. " + excp.Message); } } }
/// <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> /// 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++; } }