diff -r c9ff263c29ad -r e684f11070d5 src/jlibrtp/RTPSession.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jlibrtp/RTPSession.java Sat Mar 14 22:15:41 2009 +0100 @@ -0,0 +1,1053 @@ +/** + * Java RTP Library (jlibrtp) + * Copyright (C) 2006 Arne Kepp + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package jlibrtp; + +import java.net.DatagramSocket; +import java.net.MulticastSocket; +import java.net.DatagramPacket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Iterator; +import java.util.concurrent.locks.*; +import java.util.Random; +import java.util.Enumeration; +/** + * The RTPSession object is the core of jlibrtp. + * + * One should be instantiated for every communication channel, i.e. if you send voice and video, you should create one for each. + * + * The instance holds a participant database, as well as other information about the session. When the application registers with the session, the necessary threads for receiving and processing RTP packets are spawned. + * + * RTP Packets are sent synchronously, all other operations are asynchronous. + * + * @author Arne Kepp + */ +public class RTPSession { + /** + * The debug level is final to avoid compilation of if-statements.
+ * 0 provides no debugging information, 20 provides everything
+ * Debug output is written to System.out
+ * Debug level for RTP related things. + */ + final static public int rtpDebugLevel = 0; + /** + * The debug level is final to avoid compilation of if-statements.
+ * 0 provides no debugging information, 20 provides everything
+ * Debug output is written to System.out
+ * Debug level for RTCP related things. + */ + final static public int rtcpDebugLevel = 0; + + /** RTP unicast socket */ + protected DatagramSocket rtpSock = null; + /** RTP multicast socket */ + protected MulticastSocket rtpMCSock = null; + /** RTP multicast group */ + protected InetAddress mcGroup = null; + + // Internal state + /** Whether this session is a multicast session or not */ + protected boolean mcSession = false; + /** Current payload type, can be changed by application */ + protected int payloadType = 0; + /** SSRC of this session */ + protected long ssrc; + /** The last timestamp when we sent something */ + protected long lastTimestamp = 0; + /** Current sequence number */ + protected int seqNum = 0; + /** Number of packets sent by this session */ + protected int sentPktCount = 0; + /** Number of octets sent by this session */ + protected int sentOctetCount = 0; + + /** The random seed */ + protected Random random = null; + + /** Session bandwidth in BYTES per second */ + protected int bandwidth = 8000; + + /** By default we do not return packets from strangers in unicast mode */ + protected boolean naiveReception = false; + + /** Should the library attempt frame reconstruction? */ + protected boolean frameReconstruction = true; + + /** Maximum number of packets used for reordering */ + protected int pktBufBehavior = 3; + + /** Participant database */ + protected ParticipantDatabase partDb = new ParticipantDatabase(this); + /** Handle to application interface for RTP */ + protected RTPAppIntf appIntf = null; + /** Handle to application interface for RTCP (optional) */ + protected RTCPAppIntf rtcpAppIntf = null; + /** Handle to application interface for AVPF, RFC 4585 (optional) */ + protected RTCPAVPFIntf rtcpAVPFIntf = null; + /** Handle to application interface for debugging */ + protected DebugAppIntf debugAppIntf = null; + + /** The RTCP session associated with this RTP Session */ + protected RTCPSession rtcpSession = null; + /** The thread for receiving RTP packets */ + protected RTPReceiverThread recvThrd = null; + /** The thread for invoking callbacks for RTP packets */ + protected AppCallerThread appCallerThrd = null; + + /** Lock to protect the packet buffers */ + final protected Lock pktBufLock = new ReentrantLock(); + /** Condition variable, to tell the */ + final protected Condition pktBufDataReady = pktBufLock.newCondition(); + + /** Enough is enough, set to true when you want to quit. */ + protected boolean endSession = false; + /** Only one registered application, please */ + protected boolean registered = false; + /** We're busy resolving a SSRC conflict, please try again later */ + protected boolean conflict = false; + /** Number of conflicts observed, exessive number suggests loop in network */ + protected int conflictCount = 0; + + /** SDES CNAME */ + protected String cname = null; + /** SDES The participant's real name */ + public String name = null; + /** SDES The participant's email */ + public String email = null; + /** SDES The participant's phone number */ + public String phone = null; + /** SDES The participant's location*/ + public String loc = null; + /** SDES The tool the participants is using */ + public String tool = null; + /** SDES A note */ + public String note = null; + /** SDES A priv string, loosely defined */ + public String priv = null; + + // RFC 4585 stuff. This should live on RTCPSession, but we need to have this + // infromation ready by the time the RTCP Session starts + // 0 = RFC 3550 , -1 = ACK , 1 = Immediate feedback, 2 = Early RTCP, + protected int rtcpMode = 0; + protected int fbEarlyThreshold = -1; // group size, immediate -> early transition point + protected int fbRegularThreshold = -1; // group size, early -> regular transition point + protected int minInterval = 5000; // minimum interval + protected int fbMaxDelay = 1000; // how long the information is useful + // RTCP bandwidth + protected int rtcpBandwidth = -1; + + + /** + * Returns an instance of a unicast RTP session. + * Following this you should adjust any settings and then register your application. + * + * The sockets should have external ip addresses, else your CNAME automatically + * generated CNAMe will be bad. + * + * @param rtpSocket UDP socket to receive RTP communication on + * @param rtcpSocket UDP socket to receive RTCP communication on, null if none. + */ + public RTPSession(DatagramSocket rtpSocket, DatagramSocket rtcpSocket) { + mcSession = false; + rtpSock = rtpSocket; + this.generateCNAME(); + this.generateSsrc(); + this.rtcpSession = new RTCPSession(this,rtcpSocket); + + // The sockets are not always imediately available? + try { Thread.sleep(1); } catch (InterruptedException e) { System.out.println("RTPSession sleep failed"); } + } + + /** + * Returns an instance of a multicast RTP session. + * Following this you should register your application. + * + * The sockets should have external ip addresses, else your CNAME automatically + * generated CNAMe will be bad. + * + * @param rtpSock a multicast socket to receive RTP communication on + * @param rtcpSock a multicast socket to receive RTP communication on + * @param multicastGroup the multicast group that we want to communicate with. + */ + public RTPSession(MulticastSocket rtpSock, MulticastSocket rtcpSock, InetAddress multicastGroup) throws Exception { + mcSession = true; + rtpMCSock =rtpSock; + mcGroup = multicastGroup; + rtpMCSock.joinGroup(mcGroup); + rtcpSock.joinGroup(mcGroup); + this.generateCNAME(); + this.generateSsrc(); + this.rtcpSession = new RTCPSession(this,rtcpSock,mcGroup); + + // The sockets are not always imediately available? + try { Thread.sleep(1); } catch (InterruptedException e) { System.out.println("RTPSession sleep failed"); } + } + + /** + * Registers an application (RTPAppIntf) with the RTP session. + * The session will call receiveData() on the supplied instance whenever data has been received. + * + * Following this you should set the payload type and add participants to the session. + * + * @param rtpApp an object that implements the RTPAppIntf-interface + * @param rtcpApp an object that implements the RTCPAppIntf-interface (optional) + * @return -1 if this RTPSession-instance already has an application registered. + */ + public int RTPSessionRegister(RTPAppIntf rtpApp, RTCPAppIntf rtcpApp, DebugAppIntf debugApp) { + if(registered) { + System.out.println("RTPSessionRegister(): Can\'t register another application!"); + return -1; + } else { + registered = true; + generateSeqNum(); + if(RTPSession.rtpDebugLevel > 0) { + System.out.println("-> RTPSessionRegister"); + } + this.appIntf = rtpApp; + this.rtcpAppIntf = rtcpApp; + this.debugAppIntf = debugApp; + + recvThrd = new RTPReceiverThread(this); + appCallerThrd = new AppCallerThread(this, rtpApp); + recvThrd.start(); + appCallerThrd.start(); + rtcpSession.start(); + return 0; + } + } + + /** + * Send data to all participants registered as receivers, using the current timeStamp, + * dynamic sequence number and the current payload type specified for the session. + * + * @param buf A buffer of bytes, less than 1496 bytes + * @return null if there was a problem, {RTP Timestamp, Sequence number} otherwise + */ + public long[] sendData(byte[] buf) { + byte[][] tmp = {buf}; + long[][] ret = this.sendData(tmp, null, null, -1, null); + + if(ret != null) + return ret[0]; + + return null; + } + + /** + * Send data to all participants registered as receivers, using the specified timeStamp, + * sequence number and the current payload type specified for the session. + * + * @param buf A buffer of bytes, less than 1496 bytes + * @param rtpTimestamp the RTP timestamp to be used in the packet + * @param seqNum the sequence number to be used in the packet + * @return null if there was a problem, {RTP Timestamp, Sequence number} otherwise + */ + public long[] sendData(byte[] buf, long rtpTimestamp, long seqNum) { + byte[][] tmp = {buf}; + long[][] ret = this.sendData(tmp, null, null, -1, null); + + if(ret != null) + return ret[0]; + + return null; + } + + /** + * Send data to all participants registered as receivers, using the current timeStamp and + * payload type. The RTP timestamp will be the same for all the packets. + * + * @param buffers A buffer of bytes, should not bed padded and less than 1500 bytes on most networks. + * @param csrcArray an array with the SSRCs of contributing sources + * @param markers An array indicating what packets should be marked. Rarely anything but the first one + * @param rtpTimestamp The RTP timestamp to be applied to all packets + * @param seqNumbers An array with the sequence number associated with each byte[] + * @return null if there was a problem sending the packets, 2-dim array with {RTP Timestamp, Sequence number} + */ + public long[][] sendData(byte[][] buffers, long[] csrcArray, boolean[] markers, long rtpTimestamp, long[] seqNumbers) { + if(RTPSession.rtpDebugLevel > 5) { + System.out.println("-> RTPSession.sendData(byte[])"); + } + + // Same RTP timestamp for all + if(rtpTimestamp < 0) + rtpTimestamp = System.currentTimeMillis(); + + // Return values + long[][] ret = new long[buffers.length][2]; + + for(int i=0; i 1500) { + System.out.println("RTPSession.sendData() called with buffer exceeding 1500 bytes ("+buf.length+")"); + } + + // Get the return values + ret[i][0] = rtpTimestamp; + if(seqNumbers == null) { + ret[i][1] = getNextSeqNum(); + } else { + ret[i][1] = seqNumbers[i]; + } + // Create a new RTP Packet + RtpPkt pkt = new RtpPkt(rtpTimestamp,this.ssrc,(int) ret[i][1],this.payloadType,buf); + + if(csrcArray != null) + pkt.setCsrcs(csrcArray); + + pkt.setMarked(marker); + + // Creates a raw packet + byte[] pktBytes = pkt.encode(); + + //System.out.println(Integer.toString(StaticProcs.bytesToUIntInt(pktBytes, 2))); + + // Pre-flight check, are resolving an SSRC conflict? + if(this.conflict) { + System.out.println("RTPSession.sendData() called while trying to resolve conflict."); + return null; + } + + + if(this.mcSession) { + DatagramPacket packet = null; + + + try { + packet = new DatagramPacket(pktBytes,pktBytes.length,this.mcGroup,this.rtpMCSock.getPort()); + } catch (Exception e) { + System.out.println("RTPSession.sendData() packet creation failed."); + e.printStackTrace(); + return null; + } + + try { + rtpMCSock.send(packet); + //Debug + if(this.debugAppIntf != null) { + this.debugAppIntf.packetSent(1, (InetSocketAddress) packet.getSocketAddress(), + new String("Sent multicast RTP packet of size " + packet.getLength() + + " to " + packet.getSocketAddress().toString() + " via " + + rtpMCSock.getLocalSocketAddress().toString())); + } + } catch (Exception e) { + System.out.println("RTPSession.sendData() multicast failed."); + e.printStackTrace(); + return null; + } + + } else { + // Loop over recipients + Iterator iter = partDb.getUnicastReceivers(); + while(iter.hasNext()) { + InetSocketAddress receiver = iter.next().rtpAddress; + DatagramPacket packet = null; + + if(RTPSession.rtpDebugLevel > 15) { + System.out.println(" Sending to " + receiver.toString()); + } + + try { + packet = new DatagramPacket(pktBytes,pktBytes.length,receiver); + } catch (Exception e) { + System.out.println("RTPSession.sendData() packet creation failed."); + e.printStackTrace(); + return null; + } + + //Actually send the packet + try { + rtpSock.send(packet); + //Debug + if(this.debugAppIntf != null) { + this.debugAppIntf.packetSent(0, (InetSocketAddress) packet.getSocketAddress(), + new String("Sent unicast RTP packet of size " + packet.getLength() + + " to " + packet.getSocketAddress().toString() + " via " + + rtpSock.getLocalSocketAddress().toString())); + } + } catch (Exception e) { + System.out.println("RTPSession.sendData() unicast failed."); + e.printStackTrace(); + return null; + } + } + } + + //Update our stats + this.sentPktCount++; + this.sentOctetCount++; + + if(RTPSession.rtpDebugLevel > 5) { + System.out.println("<- RTPSession.sendData(byte[]) " + pkt.getSeqNumber()); + } + } + + return ret; + } + + /** + * Send RTCP App packet to receiver specified by ssrc + * + * + * + * Return values: + * 0 okay + * -1 no RTCP session established + * -2 name is not byte[4]; + * -3 data is not byte[x], where x = 4*y for syme y + * -4 type is not a 5 bit unsigned integer + * + * Note that a return value of 0 does not guarantee delivery. + * The participant must also exist in the participant database, + * otherwise the message will eventually be deleted. + * + * @param ssrc of the participant you want to reach + * @param type the RTCP App packet subtype, default 0 + * @param name the ASCII (in byte[4]) representation + * @param data the data itself + * @return 0 if okay, negative value otherwise (see above) + */ + + public int sendRTCPAppPacket(long ssrc, int type, byte[] name, byte[] data) { + if(this.rtcpSession == null) + return -1; + + if(name.length != 4) + return -2; + + if(data.length % 4 != 0) + return -3; + + if(type > 63 || type < 0 ) + return -4; + + RtcpPktAPP pkt = new RtcpPktAPP(ssrc, type, name, data); + this.rtcpSession.addToAppQueue(ssrc, pkt); + + return 0; + } + /** + * Add a participant object to the participant database. + * + * If packets have already been received from this user, we will try to update the automatically inserted participant with the information provided here. + * + * @param p A participant. + */ + public int addParticipant(Participant p) { + //For now we make all participants added this way persistent + p.unexpected = false; + return this.partDb.addParticipant(0, p); + } + + /** + * Remove a participant from the database. All buffered packets will be destroyed. + * + * @param p A participant. + */ + public void removeParticipant(Participant p) { + partDb.removeParticipant(p); + } + + public Iterator getUnicastReceivers() { + return partDb.getUnicastReceivers(); + } + + public Enumeration getParticipants() { + return partDb.getParticipants(); + } + + /** + * End the RTP Session. This will halt all threads and send bye-messages to other participants. + * + * RTCP related threads may require several seconds to wake up and terminate. + */ + public void endSession() { + this.endSession = true; + + // No more RTP packets, please + if(this.mcSession) { + this.rtpMCSock.close(); + } else { + this.rtpSock.close(); + } + + // Signal the thread that pushes data to application + this.pktBufLock.lock(); + try { this.pktBufDataReady.signalAll(); } finally { + this.pktBufLock.unlock(); + } + // Interrupt what may be sleeping + this.rtcpSession.senderThrd.interrupt(); + + // Give things a chance to cool down. + try { Thread.sleep(50); } catch (Exception e){ }; + + this.appCallerThrd.interrupt(); + + // Give things a chance to cool down. + try { Thread.sleep(50); } catch (Exception e){ }; + + if(this.rtcpSession != null) { + // No more RTP packets, please + if(this.mcSession) { + this.rtcpSession.rtcpMCSock.close(); + } else { + this.rtcpSession.rtcpSock.close(); + } + } + } + + + /** + * Check whether this session is ending. + * + * @return true if session and associated threads are terminating. + */ + boolean isEnding() { + return this.endSession; + } + + /** + * Overrides CNAME, used for outgoing RTCP packets. + * + * @param cname a string, e.g. username@hostname. Must be unique for session. + */ + public void CNAME(String cname) { + this.cname = cname; + } + + /** + * Get the current CNAME, used for outgoing SDES packets + */ + public String CNAME() { + return this.cname; + } + + public long getSsrc() { + return this.ssrc; + } + + private void generateCNAME() { + String hostname; + + if(this.mcSession) { + hostname = this.rtpMCSock.getLocalAddress().getCanonicalHostName(); + } else { + hostname = this.rtpSock.getLocalAddress().getCanonicalHostName(); + } + + //if(hostname.equals("0.0.0.0") && System.getenv("HOSTNAME") != null) { + // hostname = System.getenv("HOSTNAME"); + //} + + cname = System.getProperty("user.name") + "@" + hostname; + } + + /** + * Change the RTP socket of the session. + * Peers must be notified through SIP or other signalling protocol. + * Only valid if this is a unicast session to begin with. + * + * @param newSock integer for new port number, check it is free first. + */ + public int updateRTPSock(DatagramSocket newSock) { + if(!mcSession) { + rtpSock = newSock; + return 0; + } else { + System.out.println("Can't switch from multicast to unicast."); + return -1; + } + } + + /** + * Change the RTCP socket of the session. + * Peers must be notified through SIP or other signalling protocol. + * Only valid if this is a unicast session to begin with. + * + * @param newSock the new unicast socket for RTP communication. + */ + public int updateRTCPSock(DatagramSocket newSock) { + if(!mcSession) { + this.rtcpSession.rtcpSock = newSock; + return 0; + } else { + System.out.println("Can't switch from multicast to unicast."); + return -1; + } + } + + /** + * Change the RTP multicast socket of the session. + * Peers must be notified through SIP or other signalling protocol. + * Only valid if this is a multicast session to begin with. + * + * @param newSock the new multicast socket for RTP communication. + */ + public int updateRTPSock(MulticastSocket newSock) { + if(mcSession) { + this.rtpMCSock = newSock; + return 0; + } else { + System.out.println("Can't switch from unicast to multicast."); + return -1; + } + } + + /** + * Change the RTCP multicast socket of the session. + * Peers must be notified through SIP or other signalling protocol. + * Only valid if this is a multicast session to begin with. + * + * @param newSock the new multicast socket for RTCP communication. + */ + public int updateRTCPSock(MulticastSocket newSock) { + if(mcSession) { + this.rtcpSession.rtcpMCSock = newSock; + return 0; + } else { + System.out.println("Can't switch from unicast to multicast."); + return -1; + } + } + + /** + * Update the payload type used for the session. It is represented as a 7 bit integer, whose meaning must be negotiated elsewhere (see IETF RFCs 3550 and 3551) + * + * @param payloadT an integer representing the payload type of any subsequent packets that are sent. + */ + public int payloadType(int payloadT) { + if(payloadT > 128 || payloadT < 0) { + return -1; + } else { + this.payloadType = payloadT; + return this.payloadType; + } + } + + /** + * Get the payload type that is currently used for outgoing RTP packets. + * + * @return payload type as integer + */ + public int payloadType() { + return this.payloadType; + } + + /** + * Should packets from unknown participants be returned to the application? This can be dangerous. + * + * @param doAccept packets from participants not added by the application. + */ + public void naivePktReception(boolean doAccept) { + naiveReception = doAccept; + } + + /** + * Are packets from unknown participants returned to the application? + * + * @return whether we accept packets from participants not added by the application. + */ + public boolean naivePktReception() { + return naiveReception; + } + + /** + * Set the number of RTP packets that should be buffered when a packet is + * missing or received out of order. Setting this number high increases + * the chance of correctly reordering packets, but increases latency when + * a packet is dropped by the network. + * + * Packets that arrive in order are not affected, they are passed straight + * to the application. + * + * The maximum delay is numberofPackets * packet rate , where the packet rate + * depends on the codec and profile used by the sender. + * + * Valid values: + * >0 - The maximum number of packets (based on RTP Timestamp) that may accumulate + * 0 - All valid packets received in order will be given to the application + * -1 - All valid packets will be given to the application + * + * @param behavior the be + * @return the behavior set, unchanged in the case of a erroneous value + */ + public int packetBufferBehavior(int behavior) { + if(behavior > -2) { + this.pktBufBehavior = behavior; + // Signal the thread that pushes data to application + this.pktBufLock.lock(); + try { this.pktBufDataReady.signalAll(); } finally { + this.pktBufLock.unlock(); + } + return this.pktBufBehavior; + } else { + return this.pktBufBehavior; + } + } + + /** + * The number of RTP packets that should be buffered when a packet is + * missing or received out of order. A high number increases the chance + * of correctly reordering packets, but increases latency when a packet is + * dropped by the network. + * + * A negative value disables the buffering, out of order packets will simply be dropped. + * + * @return the maximum number of packets that can accumulate before the first is returned + */ + public int packetBufferBehavior() { + return this.pktBufBehavior; + } + + /** + * Set whether the stack should operate in RFC 4585 mode. + * + * This will automatically call adjustPacketBufferBehavior(-1), + * i.e. disable all RTP packet buffering in jlibrtp, + * and disable frame reconstruction + * + * @param rtcpAVPFIntf the in + */ + public int registerAVPFIntf(RTCPAVPFIntf rtcpAVPFIntf, int maxDelay, int earlyThreshold, int regularThreshold ) { + if(this.rtcpSession != null) { + this.packetBufferBehavior(-1); + this.frameReconstruction = false; + this.rtcpAVPFIntf = rtcpAVPFIntf; + this.fbEarlyThreshold = earlyThreshold; + this.fbRegularThreshold = regularThreshold; + return 0; + } else { + return -1; + } + } + + /** + * Unregisters the RTCP AVPF interface, thereby going from + * RFC 4585 mode to RFC 3550 + * + * You still have to adjust packetBufferBehavior() and + * frameReconstruction. + * + */ + public void unregisterAVPFIntf() { + this.fbEarlyThreshold = -1; + this.fbRegularThreshold = -1; + this.rtcpAVPFIntf = null; + } + + /** + * Enable / disable frame reconstruction in the packet buffers. + * This is only relevant if getPacketBufferBehavior > 0; + * + * Default is true. + */ + public void frameReconstruction(boolean toggle) { + this.frameReconstruction = toggle; + } + + /** + * Whether the packet buffer will attempt to reconstruct + * packet automatically. + * + * @return the status + */ + public boolean frameReconstruction() { + return this.frameReconstruction; + } + + /** + * The bandwidth currently allocated to the session, + * in bytes per second. The default is 8000. + * + * This value is not enforced and currently only + * used to calculate the RTCP interval to ensure the + * control messages do not exceed 5% of the total bandwidth + * described here. + * + * Since the actual value may change a conservative + * estimate should be used to avoid RTCP flooding. + * + * see rtcpBandwidth(void) + * + * @return current bandwidth setting + */ + public int sessionBandwidth() { + return this.bandwidth; + } + + /** + * Set the bandwidth of the session. + * + * See sessionBandwidth(void) for details. + * + * @param bandwidth the new value requested, in bytes per second + * @return the actual value set + */ + public int sessionBandwidth(int bandwidth) { + if(bandwidth < 1) { + this.bandwidth = 8000; + } else { + this.bandwidth = bandwidth; + } + return this.bandwidth; + } + + + /** + * RFC 3550 dictates that 5% of the total bandwidth, + * as set by sessionBandwidth, should be dedicated + * to RTCP traffic. This + * + * This should normally not be done, but is permissible in + * conjunction with feedback (RFC 4585) and possibly + * other profiles. + * + * Also see sessionBandwidth(void) + * + * @return current RTCP bandwidth setting, -1 means not in use + */ + public int rtcpBandwidth() { + return this.rtcpBandwidth; + } + + /** + * Set the RTCP bandwidth, see rtcpBandwidth(void) for details. + * + * This function must be + * + * @param bandwidth the new value requested, in bytes per second or -1 to disable + * @return the actual value set + */ + public int rtcpBandwidth(int bandwidth) { + if(bandwidth < -1) { + this.rtcpBandwidth = -1; + } else { + this.rtcpBandwidth = bandwidth; + } + return this.rtcpBandwidth; + } + + /********************************************* Feedback message stuff ***************************************/ + + /** + * Adds a Picture Loss Indication to the feedback queue + * + * @param ssrcMediaSource + * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant + */ + public int fbPictureLossIndication(long ssrcMediaSource) { + int ret = 0; + + if(this.rtcpAVPFIntf == null) + return -1; + + RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource); + pkt.makePictureLossIndication(); + ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); + if(ret == 0) + this.rtcpSession.wakeSenderThread(ssrcMediaSource); + return ret; + } + + /** + * Adds a Slice Loss Indication to the feedback queue + * + * @param ssrcMediaSource + * @param sliFirst macroblock (MB) address of the first lost macroblock + * @param sliNumber number of lost macroblocks + * @param sliPictureId six least significant bits of the codec-specific identif + * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant + */ + public int fbSlicLossIndication(long ssrcMediaSource, int[] sliFirst, int[] sliNumber, int[] sliPictureId) { + int ret = 0; + if(this.rtcpAVPFIntf == null) + return -1; + + RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource); + pkt.makeSliceLossIndication(sliFirst, sliNumber, sliPictureId); + + ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); + if(ret == 0) + this.rtcpSession.wakeSenderThread(ssrcMediaSource); + return ret; + } + + /** + * Adds a Reference Picture Selection Indication to the feedback queue + * + * @param ssrcMediaSource + * @param bitPadding number of padded bits at end of bitString + * @param payloadType RTP payload type for codec + * @param bitString RPSI information as natively defined by the video codec + * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant + */ + public int fbRefPictureSelIndic(long ssrcMediaSource, int bitPadding, int payloadType, byte[] bitString) { + int ret = 0; + + if(this.rtcpAVPFIntf == null) + return -1; + + RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource); + pkt.makeRefPictureSelIndic(bitPadding, payloadType, bitString); + ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); + if(ret == 0) + this.rtcpSession.wakeSenderThread(ssrcMediaSource); + return ret; + } + + /** + * Adds a Picture Loss Indication to the feedback queue + * + * @param ssrcMediaSource + * @param bitString the original application message + * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant + */ + public int fbAppLayerFeedback(long ssrcMediaSource, byte[] bitString) { + int ret = 0; + + if(this.rtcpAVPFIntf == null) + return -1; + + RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource); + pkt.makeAppLayerFeedback(bitString); + ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); + if(ret == 0) + this.rtcpSession.wakeSenderThread(ssrcMediaSource); + return ret; + } + + + /** + * Adds a RTP Feedback packet to the feedback queue. + * + * These are mostly used for NACKs. + * + * @param ssrcMediaSource + * @param FMT the Feedback Message Subtype + * @param PID RTP sequence numbers of lost packets + * @param BLP bitmask of following lost packets, shared index with PID + * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant + */ + public int fbPictureLossIndication(long ssrcMediaSource, int FMT, int[] PID, int[] BLP) { + int ret = 0; + + if(this.rtcpAVPFIntf == null) + return -1; + + RtcpPktRTPFB pkt = new RtcpPktRTPFB(this.ssrc, ssrcMediaSource, FMT, PID, BLP); + ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); + if(ret == 0) + this.rtcpSession.wakeSenderThread(ssrcMediaSource); + return ret; + } + + /** + * Fetches the next sequence number for RTP packets. + * @return the next sequence number + */ + private int getNextSeqNum() { + seqNum++; + // 16 bit number + if(seqNum > 65536) { + seqNum = 0; + } + return seqNum; + } + + /** + * Initializes a random variable + * + */ + private void createRandom() { + this.random = new Random(System.currentTimeMillis() + Thread.currentThread().getId() + - Thread.currentThread().hashCode() + this.cname.hashCode()); + } + + + /** + * Generates a random sequence number + */ + private void generateSeqNum() { + if(this.random == null) + createRandom(); + + seqNum = this.random.nextInt(); + if(seqNum < 0) + seqNum = -seqNum; + while(seqNum > 65535) { + seqNum = seqNum / 10; + } + } + + /** + * Generates a random SSRC + */ + private void generateSsrc() { + if(this.random == null) + createRandom(); + + // Set an SSRC + this.ssrc = this.random.nextInt(); + if(this.ssrc < 0) { + this.ssrc = this.ssrc * -1; + } + } + + /** + * Resolve an SSRC conflict. + * + * Also increments the SSRC conflict counter, after 5 conflicts + * it is assumed there is a loop somewhere and the session will + * terminate. + * + */ + protected void resolveSsrcConflict() { + System.out.println("!!!!!!! Beginning SSRC conflict resolution !!!!!!!!!"); + this.conflictCount++; + + if(this.conflictCount < 5) { + //Don't send any more regular packets out until we have this sorted out. + this.conflict = true; + + //Send byes + rtcpSession.sendByes(); + + //Calculate the next delay + rtcpSession.calculateDelay(); + + //Generate a new Ssrc for ourselves + generateSsrc(); + + //Get the SDES packets out faster + rtcpSession.initial = true; + + this.conflict = false; + System.out.println("SSRC conflict resolution complete"); + + } else { + System.out.println("Too many conflicts. There is probably a loop in the network."); + this.endSession(); + } + } +}