/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2014, Telestax Inc and individual contributors * by the @authors tag. * * This program is free software: you can redistribute it and/or modify * under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * */ package org.restcomm.media.rtcp; import java.util.Date; import java.util.List; import org.apache.commons.net.ntp.TimeStamp; import org.apache.log4j.Logger; import org.restcomm.media.rtp.statistics.RtpMember; import org.restcomm.media.rtp.statistics.RtpStatistics; /** * Factory for building RTCP packets * * @author Henrique Rosa (henrique.rosa@telestax.com) * */ public class RtcpPacketFactory { public static final Logger logger = Logger.getLogger(RtcpPacketFactory.class); /** * Builds a packet containing an RTCP Sender Report. * * @param statistics * The statistics of the RTP session * @return The RTCP packet */ private static RtcpSenderReport buildSenderReport(RtpStatistics statistics, boolean padding) { /* * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * header |V=2|P| RC | PT=SR=200 | length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SSRC of sender | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * sender | NTP timestamp, most significant word | * info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NTP timestamp, least significant word | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RTP timestamp | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | sender's packet count | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | sender's octet count | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * report | SSRC_1 (SSRC of first source) | * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * 1 | fraction lost | cumulative number of packets lost | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | extended highest sequence number received | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | interarrival jitter | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | last SR (LSR) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | delay since last SR (DLSR) | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * report | SSRC_2 (SSRC of second source) | * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * 2 : ... : * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * | profile-specific extensions | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ long ssrc = statistics.getSsrc(); long currentTime = statistics.getCurrentTime(); TimeStamp ntpTs = new TimeStamp(new Date(currentTime)); long ntpSec = ntpTs.getSeconds(); long ntpFrac = ntpTs.getFraction(); long elapsedTime = statistics.getCurrentTime() - statistics.getRtpSentOn(); long rtpTs = statistics.getRtpTimestamp() + statistics.getRtpTime(elapsedTime); long psent = statistics.getRtpPacketsSent(); long osent = statistics.getRtpOctetsSent(); RtcpSenderReport senderReport = new RtcpSenderReport(padding, ssrc, ntpSec, ntpFrac, rtpTs, psent, osent); // Add receiver reports for each registered member List<Long> members = statistics.getMembersList(); for (Long memberSsrc : members) { if (ssrc != memberSsrc) { RtpMember memberStats = statistics.getMember(memberSsrc.longValue()); RtcpReportBlock rcvrReport = buildSubReceiverReport(memberStats); senderReport.addReceiverReport(rcvrReport); } } return senderReport; } /** * Builds a packet containing an RTCP Receiver Report * * @param statistics * The statistics of the RTP session * @return The RTCP packet */ private static RtcpReceiverReport buildReceiverReport(RtpStatistics statistics, boolean padding) { RtcpReceiverReport report = new RtcpReceiverReport(padding, statistics.getSsrc()); long ssrc = statistics.getSsrc(); // Add receiver reports for each registered member List<Long> members = statistics.getMembersList(); for (Long memberSsrc : members) { if (ssrc != memberSsrc) { RtpMember memberStats = statistics.getMember(memberSsrc.longValue()); RtcpReportBlock rcvrReport = buildSubReceiverReport(memberStats); report.addReceiverReport(rcvrReport); } } return report; } private static RtcpSdes buildSdes(RtpStatistics statistics, boolean padding) { RtcpSdes sdes = new RtcpSdes(padding); RtcpSdesChunk chunk = new RtcpSdesChunk(statistics.getSsrc()); RtcpSdesItem cname = new RtcpSdesItem(RtcpSdesItem.RTCP_SDES_CNAME, statistics.getCname()); // RtcpSdesItem end = new RtcpSdesItem(RtcpSdesItem.RTCP_SDES_END, null); chunk.addRtcpSdesItem(cname); // chunk.addRtcpSdesItem(end); sdes.addRtcpSdesChunk(chunk); return sdes; } private static RtcpReportBlock buildSubReceiverReport(RtpMember statistics) { long ssrc = statistics.getSsrc(); int fraction = (int) statistics.getFractionLost(); int lost = (int) statistics.getPacketsLost(); int seqNumCycle = statistics.getSequenceCycle(); long lastSeq = statistics.getExtHighSequence(); int jitter = (int) statistics.getJitter(); long lsr = statistics.getLastSR(); long dlsr = statistics.getLastSRdelay(); return new RtcpReportBlock(ssrc, fraction, lost, seqNumCycle, lastSeq, jitter, lsr, dlsr); } /** * Builds a packet containing an RTCP Report. * * RTP receivers provide reception quality feedback using RTCP report * packets which may take one of two forms depending upon whether or not the * receiver is also a sender. The only difference between the sender report * (SR) and receiver report (RR) forms, besides the packet type code, is * that the sender report includes a 20-byte sender information section for * use by active senders. The SR is issued if a site has sent any data * packets during the interval since issuing the last report or the previous * one, otherwise the RR is issued. * * Both the SR and RR forms include zero or more reception report blocks, * one for each of the synchronization sources from which this receiver has * received RTP data packets since the last report. Reports are not issued * for contributing sources listed in the CSRC list. Each reception report * block provides statistics about the data received from the particular * source indicated in that block. * * Since a maximum of 31 reception report blocks will fit in an SR or RR * packet, additional RR packets SHOULD be stacked after the initial SR or * RR packet as needed to contain the reception reports for all sources * heard during the interval since the last report. If there are too many * sources to fit all the necessary RR packets into one compound RTCP packet * without exceeding the MTU of the network path, then only the subset that * will fit into one MTU SHOULD be included in each interval. The subsets * SHOULD be selected round-robin across multiple intervals so that all * sources are reported. * * @param statistics * The statistics of the RTP session * @return The RTCP packet containing the RTCP Report (SS or RR). */ public static RtcpPacket buildReport(RtpStatistics statistics) { // TODO Validate padding boolean padding = false; // Build the initial report packet RtcpReport report; if(statistics.hasSent()) { report = buildSenderReport(statistics, padding); } else { report = buildReceiverReport(statistics, padding); } // Build the SDES packet containing the CNAME RtcpSdes sdes = buildSdes(statistics, padding); // Build the compound packet return new RtcpPacket(report, sdes); } /** * Builds a packet containing an RTCP BYE message. * * @param statistics * The statistics of the RTP session * @return The RTCP packet */ public static RtcpPacket buildBye(RtpStatistics statistics) { // TODO Validate padding boolean padding = false; // Build the initial report packet RtcpReport report; if(statistics.hasSent()) { report = buildSenderReport(statistics, padding); } else { report = buildReceiverReport(statistics, padding); } // Build the SDES packet containing the CNAME RtcpSdes sdes = buildSdes(statistics, padding); // Build the BYE RtcpBye bye = new RtcpBye(padding); bye.addSsrc(statistics.getSsrc()); // Build the compound packet return new RtcpPacket(report, sdes, bye); } public static RtcpPacket buildPacket(RtcpPacketType packetType, RtpStatistics statistics) { switch (packetType) { case RTCP_REPORT: return buildReport(statistics); case RTCP_BYE: return buildBye(statistics); default: throw new IllegalArgumentException("Unsupported RTCP packet type."); } } }