/**
* 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.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 org.sipdroid.net.tools.DataFramePool;
import org.sipdroid.net.tools.DatagramPool;
import org.sipdroid.net.tools.PktBufNodePool;
import org.sipdroid.net.tools.RtpPktPool;
import android.util.Log;
/**
* 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.</br> 0
* provides no debugging information, 20 provides everything </br> Debug
* output is written to System.out</br> Debug level for RTP related things.
*/
final static public int rtpDebugLevel = 1;
/**
* The debug level is final to avoid compilation of if-statements.</br> 0
* provides no debugging information, 20 provides everything </br> Debug
* output is written to System.out</br> Debug level for RTCP related things.
*/
final static public int rtcpDebugLevel = 1;
/** 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);
/** First participant */
protected Participant firstPart;
/** 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 <b>unicast</b> 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 <b>multicast</b> 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;
}
}
public AppCallerThread getAppCallerThrd() {
return appCallerThrd;
}
public RTPReceiverThread getRTPRecvThrd() {
return recvThrd;
}
/**
* 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 < buffers.length; i++) {
byte[] buf = buffers[i];
boolean marker = false;
if (markers != null)
marker = markers[i];
if (buf.length > 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 = RtpPktPool.getInstance().borrowPkt();
pkt.initPacket(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<Participant> 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;
}
public void sendData(DatagramPacket packet, RtpPkt pkt) {
if (RTPSession.rtpDebugLevel > 5) {
System.out.println("-> RTPSession.sendData(byte[])");
}
pkt.setTimeStamp(System.currentTimeMillis());
pkt.setSsrc(ssrc);
pkt.setSeqNumber(getNextSeqNum());
// Creates a raw packet
pkt.writeHeader();
// Pre-flight check, are resolving an SSRC conflict?
if (this.conflict) {
System.out
.println("RTPSession.sendData() called while trying to resolve conflict.");
return;
}
if (this.mcSession) {
try {
packet.setPort(this.rtpMCSock.getPort());
packet.setAddress(this.mcGroup);
} catch (Exception e) {
System.out
.println("RTPSession.sendData() packet creation failed.");
e.printStackTrace();
return;
}
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;
}
} else {
try {
packet.setSocketAddress(firstPart.rtpAddress);
} catch (Exception e) {
System.out
.println("RTPSession.sendData() packet creation failed.");
e.printStackTrace();
return;
}
// Actually send the packet
try {
rtpSock.send(packet);
//Log.d("RTP", "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;
}
}
// Update our stats
this.sentPktCount++;
this.sentOctetCount++;
if (RTPSession.rtpDebugLevel > 5) {
System.out.println("<- RTPSession.sendData(byte[]) "
+ pkt.getSeqNumber());
}
}
/**
* 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
firstPart = p;
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<Participant> getUnicastReceivers() {
return partDb.getUnicastReceivers();
}
public Enumeration<Participant> 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();
}
}
DatagramPool.removeInstance();
PktBufNodePool.removeInstance();
DataFramePool.removeInstance();
RtpPktPool.removeInstance();
}
/**
* 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 <a
* href="http://www.ietf.org/rfc/rfc3550.txt">3550</a> and <a
* href="http://www.ietf.org/rfc/rfc3550.txt">3551</a>)
*
* @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();
}
}
}