/*
* Copyright 2007 Sun Microsystems, Inc.
*
* This file is part of jVoiceBridge.
*
* jVoiceBridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation and distributed hereunder
* to you.
*
* jVoiceBridge 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied this
* code.
*/
package com.sun.voip;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.net.SocketTimeoutException;
import java.net.SocketException;
import java.io.IOException;
/**
* RtpSocket has one DatagramSocket for RTP and one for RTCP.
* RTP and RTCP sockets come in pairs. RTP must use an even port number
* and RTCP uses the RTP port number plus 1.
*
* It would be possible to extend Datagram socket if we had a contructor
* requiring the caller to specify an even port number. Instead we find
* two available DatagramSockets with an even and odd port number.
*
* RTCP is automatically started and received information is optionally printed.
*/
public class RtpSocket {
/*
* The default limit on Solaris is 256kb. To change this limit, use
* ndd /dev/udp and set udp_max_buf to what you want.
*/
public static final int MAX_SEND_BUFFER = (10 * 1024 * 1024); // 10mb
public static final int MAX_RECEIVE_BUFFER = (10 * 1024 * 1024); // 10mb
DatagramSocket rtpDatagramSocket; // Socket for RTP
DatagramSocket rtcpDatagramSocket; // Socket for RTCP
RtcpReceiver rtcpReceiver; // handle RTCP reports
private static int rtpTimeout = 330; // 330 seconds (5 1/2 minutes)
static {
String s = System.getProperty("com.sun.voip.RTP_TIMEOUT");
if (s != null) {
int timeout = rtpTimeout;
try {
timeout = Integer.parseInt(s);
if (timeout < 0) {
Logger.println("Invalid RTP Timeout, using "
+ rtpTimeout);
} else {
rtpTimeout = timeout;
}
} catch (NumberFormatException e) {
Logger.println("Invalid RTP Timeout, using "
+ rtpTimeout);
}
}
}
public RtpSocket(InetAddress ia, int port) throws SocketException {
int p = port;
if ((p & 1) != 0) {
p++;
Logger.println("Port number must be even, using " + p);
}
while (true) {
try {
rtpDatagramSocket = new DatagramSocket(p, ia);
/*
* RTP/RTCP ports come in pairs and we need an even port number
* for RTP.
*/
if ((rtpDatagramSocket.getLocalPort() & 1) != 0) {
continue; // got an odd port number, try again
}
try {
rtcpDatagramSocket = new DatagramSocket(
rtpDatagramSocket.getLocalPort() + 1, ia);
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println(
"RtpSocket: listening for RTP data at local port "
+ rtpDatagramSocket.getLocalPort());
}
break;
} catch (SocketException e) {
/*
* odd port number is in use, try again.
*/
if (p != 0) {
p += 2;
}
} catch (Exception e) {
rtpDatagramSocket.close();
Logger.error(
"RtpSocket: Unable to create control socket! "
+ e.getMessage());
throw e;
}
} catch (SocketException e) {
/*
* port number is in use, try again.
*/
if (p != 0) {
p += 2;
}
continue;
} catch (Exception e) {
Logger.error("RtpSocket: Unable to create RTP/RTCP sockets! "
+ e.getMessage());
throw new SocketException(
"RtpSocket: Unable to create RTP/RTCP sockets!");
}
}
if (p != port) {
System.out.println("RtpSocket: Desired port " + port
+ " is in use. Using " + p + " instead.");
}
try {
rtpDatagramSocket.setReceiveBufferSize(MAX_RECEIVE_BUFFER);
} catch (SocketException e) {
Logger.error("RtpSocket: Unable to set receive buffer size! "
+ e.getMessage());
throw e;
}
try {
rtpDatagramSocket.setSendBufferSize(MAX_SEND_BUFFER);
} catch (SocketException e) {
Logger.error("RtpSocket: Unable to set send buffer size! "
+ e.getMessage());
throw e;
}
try {
rtpDatagramSocket.setSoTimeout(0);
} catch (SocketException e) {
Logger.error("RtpSocket: Unable to set socket timeout! "
+ e.getMessage());
throw e;
}
}
public void startRtcpReceiver() {
/*
* Start the RTCP receiver
*/
rtcpReceiver = new RtcpReceiver(rtcpDatagramSocket, true);
}
/**
* Return the local address for the RTP socket
*
* @return InetSocketAddress local address and port for the socket
*/
public InetSocketAddress getInetSocketAddress() {
return new InetSocketAddress(rtpDatagramSocket.getLocalAddress(),
rtpDatagramSocket.getLocalPort());
}
public DatagramSocket getDatagramSocket() {
return rtpDatagramSocket;
}
/**
* Receives an RtpPacket from this socket. When this method returns,
* the RtpPacket's buffer is filled with the data received.
* The packet also contains the sender's IP address, and the port number
* on the sender's machine.
*
* This method blocks until data is received. The length field
* of the RtpPacket object contains the length of the received message.
* If the message is longer than the packet's length,
* the message is truncated.
*
* @param p the RtpPacket into which to place the incoming data.
*/
public int receive(RtpPacket p) throws IOException {
if (rtpDatagramSocket == null) {
throw new IOException("RtpSocket receive failed, socket closed");
}
rtpDatagramSocket.receive(p.getDatagramPacket());
return p.getDatagramPacket().getLength();
}
/**
* Sends an RtpPacket from this socket. The RtpPacket includes
* information indicating the data to be sent, its length,
* the IP address of the remote host, and the port number
* on the remote host.
*
* @param RtpPacket the packet to be sent
*/
public void send(RtpPacket p) throws IOException {
if (rtpDatagramSocket == null) {
throw new IOException("RtpSocket send failed, socket closed");
}
rtpDatagramSocket.send(p.getDatagramPacket());
}
/**
* Send an Rtcp packet
*/
public void send(RtcpPacket p) throws IOException {
if (rtcpDatagramSocket == null) {
throw new IOException("RtcpSocket send failed, socket closed");
}
rtcpDatagramSocket.send(p.getDatagramPacket());
}
/**
* Set socket timeout
*
* @param timeout millisecond timeout value
*/
public void setSoTimeout(int timeout) throws SocketException {
if (rtpDatagramSocket == null) {
throw new SocketException("rtpDatagramSocket is null");
}
rtpDatagramSocket.setSoTimeout(timeout);
}
public boolean isClosed() {
if (rtpDatagramSocket == null) {
return true;
}
return rtpDatagramSocket.isClosed();
}
public void flushSocket() {
if (rtpDatagramSocket == null) {
return;
}
flushSocket(rtpDatagramSocket);
}
public static void flushSocket(DatagramSocket socket) {
/*
* Flush the socket
*/
int len = RtpPacket.getDataSize(
RtpPacket.PCM_ENCODING, RtpPacket.MAX_SAMPLE_RATE, 2);
len += RtpPacket.HEADER_SIZE;
byte[] data = new byte[len];
DatagramPacket packet = new DatagramPacket(data, len);
int count = 0;
try {
socket.setSoTimeout(1);
while (true) {
try {
socket.receive(packet);
count++;
} catch (SocketTimeoutException e) {
break;
} catch (IOException e) {
Logger.println("Error flushing socket " + e.getMessage());
break;
}
}
} catch (SocketException e) {
Logger.println("Can't flush receiver socket!");
}
if (count > 0) {
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Packets flushed: " + count);
}
}
try {
socket.setSoTimeout(0);
} catch (SocketException e) {
Logger.println("Can't set socket timeout to 0!");
}
}
public static int getRtpTimeout() {
return rtpTimeout;
}
public static void setRtpTimeout(int rtpTimeout) {
RtpSocket.rtpTimeout = rtpTimeout;
}
public DatagramSocket getRtcpDatagramSocket() {
return rtcpDatagramSocket;
}
/**
* Close the RTP socket, stop the RtcpReceiver and close the RTCP socket.
*/
public void close() {
if (rtpDatagramSocket != null) {
rtpDatagramSocket.close();
rtpDatagramSocket = null;
}
if (rtcpReceiver != null) {
rtcpReceiver.end();
rtcpDatagramSocket.close();
rtcpReceiver = null;
}
}
}