
org.jlibrtp.RTPSession Maven / Gradle / Ivy
The newest version!
/**
* 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 org.jlibrtp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 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 {
/** Logger instance. */
private static final Logger LOGGER =
Logger.getLogger(RTPSession.class.getName());
/** 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
// information 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) { LOGGER.log(Level.WARNING, "RTPSession sleep failed", e); }
}
/**
* 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.
* @throws Exception error creating a session
*/
@SuppressWarnings("deprecation")
public RTPSession(MulticastSocket rtpSock, MulticastSocket rtcpSock, InetAddress multicastGroup) throws Exception {
mcSession = true;
rtpMCSock =rtpSock;
mcGroup = multicastGroup;
// TODO replace deprecated method call
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) { LOGGER.log(Level.WARNING, "RTPSession sleep failed", e); }
}
/**
* 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)
* @param debugApp debugging interface
* @return -1 if this RTPSession-instance already has an application registered.
*/
public int registerRTPSession(RTPAppIntf rtpApp, RTCPAppIntf rtcpApp, DebugAppIntf debugApp) {
if(registered) {
LOGGER.info("RTPSessionRegister(): Can\'t register another application!");
return -1;
} else {
registered = true;
generateSeqNum();
if(LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("-> 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;
}
}
/**
* Checks if an application has been registered with this session.
* @return true
if an application has been registered with
* this session.
*/
protected boolean isRegistered() {
return registered;
}
/**
* 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[] seq = {seqNum};
long[][] ret = this.sendData(tmp, null, null, rtpTimestamp, seq);
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(LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("-> 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) {
LOGGER.warning("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) {
LOGGER.warning("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) {
LOGGER.log(Level.WARNING, "RTPSession.sendData() packet creation failed.", e);
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) {
LOGGER.log(Level.WARNING, "RTPSession.sendData() multicast failed.", e);
return null;
}
} else {
// Loop over recipients
Iterator iter = partDb.getUnicastReceivers();
while(iter.hasNext()) {
InetSocketAddress receiver = iter.next().rtpAddress;
DatagramPacket packet = null;
if(LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest(" Sending to " + receiver.toString());
}
try {
packet = new DatagramPacket(pktBytes,pktBytes.length,receiver);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "RTPSession.sendData() packet creation failed.", e);
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) {
LOGGER.log(Level.WARNING, "RTPSession.sendData() unicast failed.", e);
return null;
}
}
}
//Update our stats
this.sentPktCount++;
this.sentOctetCount++;
if(LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("<- 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.
* @return 0 if OK
*/
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 if (rtcpSession.rtcpSock != null){
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.
* @return the CNAME
*/
public String CNAME() {
return this.cname;
}
/**
* Retrieves the SSRC.
* @return the SSRC
*/
public long getSsrc() {
return this.ssrc;
}
/**
* Generates a CNAME.
*/
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 signaling protocol.
* Only valid if this is a unicast session to begin with.
*
* @param newSock integer for new port number, check it is free first.
* @return 0 if OK
*/
public int updateRTPSock(DatagramSocket newSock) {
if(!mcSession) {
rtpSock = newSock;
return 0;
} else {
LOGGER.warning("Can't switch from multicast to unicast.");
return -1;
}
}
/**
* Change the RTCP socket of the session.
* Peers must be notified through SIP or other signaling protocol.
* Only valid if this is a unicast session to begin with.
*
* @param newSock the new unicast socket for RTP communication.
* @return 0 if OK
*/
public int updateRTCPSock(DatagramSocket newSock) {
if(!mcSession) {
this.rtcpSession.rtcpSock = newSock;
return 0;
} else {
LOGGER.warning("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.
* @return 0 if OK
*/
public int updateRTPSock(MulticastSocket newSock) {
if(mcSession) {
this.rtpMCSock = newSock;
return 0;
} else {
LOGGER.warning("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 signaling protocol.
* Only valid if this is a multicast session to begin with.
*
* @param newSock the new multicast socket for RTCP communication.
* @return 0 if OK
*/
public int updateRTCPSock(MulticastSocket newSock) {
if(mcSession) {
this.rtcpSession.rtcpMCSock = newSock;
return 0;
} else {
LOGGER.warning("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.
* @return 0 if OK
*/
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 interface
* @param maxDelay maximum delay
* @param earlyThreshold early threshold
* @param regularThreshold regular threshold
*/
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.
* @param toggle on or off
*/
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 SSRC of the media source
* @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 SSRC of the media source
* @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 SSRC of the media source
* @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 SSRC of the media source
* @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 SSRC of the media source
* @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() {
LOGGER.info("!!!!!!! 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;
LOGGER.info("SSRC conflict resolution complete");
} else {
LOGGER.severe("Too many conflicts. There is probably a loop in the network.");
this.endSession();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy