/*
* Mobicents Media Gateway
*
* The source code contained in this file is in in the public domain.
* It can be used in any project or product without prior permission,
* license or royalty payments. There is NO WARRANTY OF ANY KIND,
* EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION,
* THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
* AND DATA ACCURACY. We do not warrant or make any representations
* regarding the use of the software or the results thereof, including
* but not limited to the correctness, accuracy, reliability or
* usefulness of the software.
*/
package org.mobicents.media.server.impl.rtp;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import net.java.stun4j.StunAddress;
import net.java.stun4j.StunException;
import net.java.stun4j.client.NetworkConfigurationDiscoveryProcess;
import net.java.stun4j.client.StunDiscoveryReport;
import org.apache.log4j.Logger;
import org.mobicents.media.Format;
import org.mobicents.media.format.AudioFormat;
import org.mobicents.media.format.VideoFormat;
import org.mobicents.media.server.impl.rtp.sdp.RTPAudioFormat;
import org.mobicents.media.server.impl.rtp.sdp.RTPFormat;
import org.mobicents.media.server.impl.rtp.sdp.RTPVideoFormat;
import org.mobicents.media.server.spi.Timer;
/**
*
* @author Oleg Kulikov
*/
public class RtpFactory {
private Transceiver transceiver;
private Integer jitter = 60;
private InetAddress bindAddress;
private int localPort;
protected InetSocketAddress publicAddress;
private String stunHost;
private int stunPort = 3478;
private Timer timer;
private Map<Integer, Format> formatMap;
protected volatile HashMap<SocketAddress, RtpSocket> rtpSockets = new HashMap();
private transient Logger logger = Logger.getLogger(RtpFactory.class);
/**
* Creates RTP Factory instance
*/
public RtpFactory() {
}
public int getLocalPort() {
return localPort;
}
public void setLocalPort(int localPort) {
this.localPort = localPort;
}
/**
* Gets the address of stun server if present.
*
* @return the address of stun server or null if not assigned.
*/
public String getStunAddress() {
return stunHost == null ? null : stunPort == 3478 ? stunHost : (stunHost + ":" + stunPort);
}
/**
* Assigns address of the STUN server.
*
* @param address
* the address of the stun server in format host[:port]. if port
* is not set then default port is used.
*/
public void setStunAddress(String address) {
String tokens[] = address.split(":");
stunHost = tokens[0];
if (tokens.length == 2) {
stunPort = Integer.parseInt(tokens[1]);
}
}
public void start() throws SocketException, IOException, StunException {
InetSocketAddress address = new InetSocketAddress(bindAddress, localPort);
publicAddress = stunHost != null ? getPublicAddress(address) : address;
logger.info("Binding RTP transceiver to " + bindAddress + ":" + localPort);
transceiver = new Transceiver(rtpSockets, bindAddress, localPort);
transceiver.start();
logger.info("Bound RTP transceiver to " + bindAddress + ":" + localPort +
", NAT public address is " + publicAddress);
}
public void stop() {
transceiver.stop();
}
/**
* Gets media processing timer used by RTP socket.
*
* @return timer object.
*/
public Timer getTimer() {
return timer;
}
/**
* Assigns media processing timer.
*
* @param timer
* tmer object.
*/
public void setTimer(Timer timer) {
this.timer = timer;
}
/**
* Gets the IP address to which trunk is bound. All endpoints of the trunk
* use this address for RTP connection.
*
* @return the IP address string to which this trunk is bound.
*/
public String getBindAddress() {
return bindAddress != null ? bindAddress.getHostAddress() : null;
}
/**
* Modify the bind address. All endpoints of the trunk use this address for
* RTP connection.
*
* @param bindAddress
* IP address as string or host name.
*/
public void setBindAddress(String bindAddress) throws UnknownHostException {
this.bindAddress = InetAddress.getByName(bindAddress);
}
/**
* Gets the size of the jitter buffer in milliseconds.
*
* Jitter buffer is used at the receiving ends of a VoIP connection. A
* jitter buffer stores received, time-jittered VoIP packets, that arrive
* within its time window. It then plays stored packets out, in sequence,
* and at a constant rate for subsequent decoding. A jitter buffer is
* typically filled half-way before playing out packets to allow early, or
* late, packet-arrival jitter compensation.
*
* Choosing a large jitter buffer reduces packet dropping from jitter but
* increases VoIP path delay
*
* @return the size of the buffer in milliseconds.
*/
public Integer getJitter() {
return jitter;
}
/**
* Modify size of the jitter buffer.
*
* Jitter buffer is used at the receiving ends of a VoIP connection. A
* jitter buffer stores received, time-jittered VoIP packets, that arrive
* within its time window. It then plays stored packets out, in sequence,
* and at a constant rate for subsequent decoding. A jitter buffer is
* typically filled half-way before playing out packets to allow early, or
* late, packet-arrival jitter compensation.
*
* Choosing a large jitter buffer reduces packet dropping from jitter but
* increases VoIP path delay
*
* @param jitter
* the new buffer's size in milliseconds
*/
public void setJitter(Integer jitter) {
this.jitter = jitter;
}
/**
* Constructs new RTP socket.
*
* @return the RTPSocketInstance.
* @throws StunException
* @throws IOException
* @throws SocketException
* @throws StunException
* @throws IOException
*/
public RtpSocket getRTPSocket() {
RtpSocket rtpSocket = new RtpSocket(transceiver, timer, formatMap, this);
return rtpSocket;
}
public void releaseRTPSocket(RtpSocket rtpSocket) {
rtpSockets.remove(rtpSocket.remoteAddress);
}
public Map<Integer, Format> getFormatMap() {
return this.formatMap;
}
public void setFormatMap(Map<Integer, Format> originalFormatMap) {
this.formatMap = new HashMap<Integer, Format>();
// now we have to switch, cause we use something that extends format,
// without mms will crash
for (Integer payloadType : originalFormatMap.keySet()) {
Format _f = originalFormatMap.get(payloadType);
Format convertedFormat = convert(payloadType, _f);
this.formatMap.put(payloadType, convertedFormat);
}
}
// --- Helper methods.
private Format convert(Integer payloadType, Format _f) {
Format converted = null;
if (_f instanceof AudioFormat) {
AudioFormat af = (AudioFormat) _f;
RTPAudioFormat f = new RTPAudioFormat(payloadType, af.getEncoding(), af.getSampleRate(), af.getSampleSizeInBits(), af.getChannels(), af.getEndian(), af.getSigned());
converted = f;
} else if (_f instanceof VideoFormat) {
VideoFormat vf = (VideoFormat) _f;
RTPVideoFormat f = new RTPVideoFormat(payloadType, vf.getEncoding(), vf.getMaxDataLength(), vf.getFrameRate());
converted = f;
} else if (_f instanceof RTPFormat) {
converted = _f;
} else {
throw new IllegalArgumentException("Unknown media format: " + _f.getClass());
}
return converted;
}
private InetSocketAddress getPublicAddress(InetSocketAddress localAddress) throws StunException {
StunAddress local = new StunAddress(localAddress.getAddress(), localAddress.getPort());
StunAddress stun = new StunAddress(stunHost, stunPort);
// discovery stun server
NetworkConfigurationDiscoveryProcess addressDiscovery = new NetworkConfigurationDiscoveryProcess(local, stun);
try {
addressDiscovery.start();
StunDiscoveryReport report = addressDiscovery.determineAddress();
return report.getPublicAddress().getSocketAddress();
} finally {
addressDiscovery.shutDown();
}
}
}