/*
* Mobicents, Communications Middleware
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party
* contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program 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 distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
*
* Boston, MA 02110-1301 USA
*/
package org.mobicents.media.server;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Set;
import java.util.Vector;
import javax.sdp.MediaDescription;
import javax.sdp.SdpException;
import javax.sdp.SdpFactory;
import javax.sdp.SessionDescription;
import org.apache.log4j.Logger;
import org.mobicents.media.Format;
import org.mobicents.media.MediaSource;
import org.mobicents.media.server.impl.resource.Demultiplexer;
import org.mobicents.media.server.impl.resource.Multiplexer;
import org.mobicents.media.server.impl.rtp.RtpFactory;
import org.mobicents.media.server.impl.rtp.RtpSocket;
import org.mobicents.media.server.impl.rtp.RtpSocketListener;
import org.mobicents.media.server.impl.rtp.sdp.AVProfile;
import org.mobicents.media.server.impl.rtp.sdp.RTPFormat;
import org.mobicents.media.server.impl.rtp.sdp.RTPFormatParser;
import org.mobicents.media.server.spi.Connection;
import org.mobicents.media.server.spi.ConnectionMode;
import org.mobicents.media.server.spi.ConnectionState;
import org.mobicents.media.server.spi.ResourceUnavailableException;
/**
*
* @author kulikov
*/
public class RtpConnectionImpl extends ConnectionImpl implements RtpSocketListener {
private SdpFactory sdpFactory;
private String localDescriptor;
private String remoteDescriptor;
private HashMap<String, RtpSocket> rtpSockets = new HashMap<String, RtpSocket>();
private Demultiplexer demux;
private Multiplexer mux;
private Format[] formats;
private final static Logger logger = Logger.getLogger(RtpConnectionImpl.class);
public RtpConnectionImpl(EndpointImpl endpoint, ConnectionMode mode) throws ResourceUnavailableException {
super(endpoint, mode);
sdpFactory = endpoint.getSdpFactory();
Hashtable<String, RtpFactory> factories = endpoint.getRtpFactory();
Set<String> mediaTypes = factories.keySet();
//Creating RTP sockets for each media type
for (String mediaType : mediaTypes) {
RtpSocket rtpSocket = factories.get(mediaType).getRTPSocket();
rtpSocket.getReceiveStream().setEndpoint(endpoint);
rtpSocket.getReceiveStream().setConnection(this);
rtpSocket.getSendStream().setEndpoint(endpoint);
rtpSocket.getSendStream().setConnection(this);
rtpSockets.put(mediaType, rtpSocket);
if (logger.isDebugEnabled()) {
logger.debug("Endpoint=" + endpoint.getLocalName() +
", index=" + getIndex() + ", allocated RTP socket[" + mediaType + "], RTP formats: " + rtpSocket.getRtpMap());
}
}
//Incoming data should be multiplexed into single stream
//Outgoing stream, up side down, have to be demultiplexed
//and streamed by different RTP sockets
demux = new Demultiplexer("Demultiplexer[RTP]");
demux.setEndpoint(endpoint);
demux.setConnection(this);
mux = new Multiplexer("Multiplexer[RTP]");
mux.setEndpoint(endpoint);
mux.setConnection(this);
// join multiplexer and rtp receive stream
Collection<RtpSocket> sockets = rtpSockets.values();
for (RtpSocket socket : sockets) {
mux.connect(socket.getReceiveStream());
socket.setListener(this);
}
//creating tx channel using endpoint's factory
try {
txChannel = endpoint.createTxChannel(this);
rxChannel = endpoint.createRxChannel(this);
} catch (Exception e) {
throw new ResourceUnavailableException(e);
}
//checks connecttion mode and which channels were actualy created
if (mode == ConnectionMode.SEND_ONLY || mode == ConnectionMode.SEND_RECV) {
if (txChannel == null) {
throw new ResourceUnavailableException("Mode not supported");
}
}
if (mode == ConnectionMode.RECV_ONLY || mode == ConnectionMode.SEND_RECV) {
if (rxChannel == null) {
throw new ResourceUnavailableException("Mode not supported");
}
}
//determine formats supported Endpoint
//and connect channels with MUX/DEMUX
Format[] rxFormats = new Format[0];
if (rxChannel != null) {
rxFormats = rxChannel.getInputFormats();
if (logger.isDebugEnabled()) {
logger.debug("Endpoint=" + endpoint.getLocalName() +
", index=" + getIndex() + ", rx formats=[" +
getSupportedFormatList(rxFormats) + "]");
}
rxChannel.connect(mux.getOutput());
}
Format[] txFormats = new Format[0];
if (txChannel != null) {
txFormats = txChannel.getOutputFormats();
if (logger.isDebugEnabled()) {
logger.debug("Endpoint=" + endpoint.getLocalName() +
", index=" + getIndex() + ", tx formats=[" +
getSupportedFormatList(txFormats) + "]");
}
txChannel.connect(demux.getInput());
}
//merge supported formats and create local descriptor
formats = this.mergeFormats(rxFormats, txFormats);
createLocalDescriptor(formats);
setMode(mode);
setState(ConnectionState.HALF_OPEN);
if (logger.isDebugEnabled()) {
String fs = "";
for (Format fmt: formats) {
fs = fs.length() == 0 ? fmt.toString() : fs + ";" + fmt.toString();
}
logger.info("Endpoint=" + endpoint.getLocalName() +
", index=" + getIndex() +
", successfully created, endpoint's formats{" + fs + "}, state=" + getState());
}
}
private String createLocalDescriptor(Format[] supported) {
SessionDescription sdp = null;
String userName = "MediaServer";
long sessionID = System.currentTimeMillis() & 0xffffff;
long sessionVersion = sessionID;
String networkType = javax.sdp.Connection.IN;
String addressType = javax.sdp.Connection.IP4;
String address = rtpSockets.get("audio").getLocalAddress();
try {
sdp = sdpFactory.createSessionDescription();
sdp.setVersion(sdpFactory.createVersion(0));
sdp.setOrigin(sdpFactory.createOrigin(userName, sessionID, sessionVersion, networkType, addressType,
address));
sdp.setSessionName(sdpFactory.createSessionName("session"));
sdp.setConnection(sdpFactory.createConnection(networkType, addressType, address));
Vector descriptions = new Vector();
// encode formats
Set<String> mediaTypes = rtpSockets.keySet();
for (String mediaType : mediaTypes) {
RtpSocket rtpSocket = rtpSockets.get(mediaType);
HashMap<Integer, Format> rtpMap = rtpSocket.getRtpMap();
HashMap<Integer, Format> subset = subset(rtpMap, supported);
if (logger.isDebugEnabled()) {
logger.debug("Endpoint=" + getEndpoint().getLocalName() +
", index=" + getIndex() + ", RTP format subset[" + mediaType + "]: " + subset);
}
int port = rtpSocket.getLocalPort();
descriptions.add(createMediaDescription(mediaType, port, subset));
}
sdp.setMediaDescriptions(descriptions);
} catch (SdpException e) {
}
localDescriptor = sdp.toString();
return localDescriptor;
}
public String getLocalDescriptor() {
if (getState() == ConnectionState.NULL || getState() == ConnectionState.CLOSED) {
throw new IllegalStateException("State is " + getState());
}
return this.localDescriptor;
}
public String getRemoteDescriptor() {
return this.remoteDescriptor;
}
public void setRemoteDescriptor(String descriptor) throws SdpException, IOException, ResourceUnavailableException {
this.remoteDescriptor = descriptor;
if (getState() != ConnectionState.HALF_OPEN && getState() != ConnectionState.OPEN) {
throw new IllegalStateException("State is " + getState());
}
SessionDescription sdp = sdpFactory.createSessionDescription(descriptor);
// add peer to RTP socket
Format[] supported = formats;
javax.sdp.Connection conn = null;
Vector<MediaDescription> mediaDescriptions = sdp.getMediaDescriptions(false);
for (MediaDescription md : mediaDescriptions) {
String mediaType = md.getMedia().getMediaType();
RtpSocket rtpSocket = rtpSockets.get(mediaType);
//skip unnsupported media
if (rtpSocket == null) {
continue;
}
HashMap<Integer, Format> offer = RTPFormatParser.getFormats(md);
HashMap<Integer, Format> subset = this.subset(offer, supported);
if (logger.isDebugEnabled()) {
logger.debug("Endpoint=" + getEndpoint().getLocalName() +
", index=" + getIndex() + " offer= " + offer + ", supported="
+ this.getSupportedFormatList(supported) + ", subset=" + subset);
}
if (subset.isEmpty()) {
throw new IOException("Codecs are not negotiated");
}
subset = this.prefferAudio(subset);
if (logger.isDebugEnabled()) {
logger.debug("Endpoint=" + getEndpoint().getLocalName() +
", index=" + getIndex() + " selected preffered = " + subset);
}
conn = md.getConnection();
if(conn == null){
//Use session-level if media-level "c=" field is not defined
conn = sdp.getConnection();
}
InetAddress address = InetAddress.getByName(conn.getAddress());
int port = md.getMedia().getMediaPort();
rtpSocket.setPeer(address, port);
updateRtpMap(rtpSocket, subset);
Format[] negotiated = new Format[subset.size()];
subset.values().toArray(negotiated);
this.createLocalDescriptor(negotiated);
//This is done in constructor
//rtpSocket.getReceiveStream().connect(mux);
demux.connect(rtpSocket.getSendStream());
rtpSocket.getReceiveStream().start();
rtpSocket.getSendStream().start();
}
demux.start();
mux.getOutput().start();
setState(ConnectionState.OPEN);
if (logger.isDebugEnabled()) {
logger.debug("Endpoint=" + getEndpoint().getLocalName() +
", index=" + getIndex() + " state = " + getState());
}
}
public void setOtherParty(Connection other) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
private int[] getFormatList(HashMap<Integer, Format> fmts) {
int[] list = new int[fmts.size()];
int i = 0;
for (Integer key : fmts.keySet()) {
list[i++] = key;
}
return list;
}
private HashMap subset(HashMap<Integer, Format> map, Format[] fmts) {
HashMap<Integer, Format> subset = new HashMap();
for (Integer k : map.keySet()) {
Format rf = map.get(k);
for (Format f : fmts) {
if (f.matches(rf)) {
subset.put(k, rf);
}
}
}
return subset;
}
private void updateRtpMap(RtpSocket rtpSocket, HashMap<Integer, Format> offer) {
//rtpSocket.getRtpMap().clear();
//rtpSocket.getRtpMap().putAll(offer);
rtpSocket.setRtpMap(offer);
}
private MediaDescription createMediaDescription(String mediaType, int port, HashMap<Integer, Format> formats)
throws SdpException {
int[] fmtList = getFormatList(formats);
MediaDescription md = sdpFactory.createMediaDescription(mediaType, port, 1, "RTP/AVP", fmtList);
// set attributes for formats
Vector attributes = new Vector();
for (int i = 0; i < fmtList.length; i++) {
RTPFormat format = (RTPFormat) formats.get(fmtList[i]);
attributes.addAll(format.encode());
}
// generate descriptor
md.setAttributes(attributes);
return md;
}
@Override
protected void close() {
int count = ((EndpointImpl) getEndpoint()).getConnections().size();
if (count == 0) {
//Connection stops endpoint.source if no more connections
//channel is responsable for media path only
MediaSource source = ((EndpointImpl) getEndpoint()).getSource();
if (source != null) {
source.stop();
}
}
Collection<RtpSocket> sockets = rtpSockets.values();
for (RtpSocket socket : sockets) {
socket.getReceiveStream().stop();
socket.getSendStream().stop();
mux.disconnect(socket.getReceiveStream());
demux.disconnect(socket.getSendStream());
socket.getReceiveStream().setEndpoint(null);
socket.getSendStream().setEndpoint(null);
socket.getReceiveStream().setConnection(null);
socket.getSendStream().setConnection(null);
socket.release();
}
rtpSockets.clear();
if (rxChannel != null) {
rxChannel.disconnect(mux.getOutput());
// rxChannel.setConnection(null);
}
if (txChannel != null) {
txChannel.disconnect(demux.getInput());
// txChannel.setConnection(null);
}
mux.setConnection(null);
demux.setConnection(null);
mux = null;
demux = null;
super.close();
}
public void error(Exception e) {
getEndpoint().deleteConnection(this.getId());
}
private boolean isContains(ArrayList<Format> list, Format fmt) {
for (Format format : list) {
if (format.matches(fmt)) return true;
}
return false;
}
private Format[] mergeFormats(Format[] f1, Format[] f2) {
ArrayList<Format> list = new ArrayList();
for (Format f : f1) {
list.add(f);
}
for (Format f : f2) {
if (!isContains(list, f)) {
list.add(f);
}
}
Format[] res = new Format[list.size()];
list.toArray(res);
return res;
}
/**
* Exclude all codecs except preffered one.
*
* @param formats
*/
private HashMap<Integer, Format> prefferAudio(HashMap<Integer, Format> formats) {
//we will try to to use PCMU
HashMap<Integer, Format> preffered = new HashMap();
Collection<Integer> list = formats.keySet();
boolean found = false;
for (Integer key : list) {
Format fmt = formats.get(key);
if (!found) {
if (!fmt.matches(AVProfile.DTMF)) {
preffered.put(key, fmt);
found = true;
}
}
if (fmt.matches(AVProfile.DTMF)) {
preffered.put(key, fmt);
}
}
return preffered;
}
public long getPacketsReceived(String media) {
return rtpSockets.get(media).getReceiveStream().getPacketsTransmitted();
}
public long getPacketsTransmitted(String media) {
return rtpSockets.get(media).getSendStream().getBytesReceived();
}
protected String getSupportedFormatList(Format[] formats) {
String s = "";
for (int i = 0; i < formats.length; i++) {
s += formats[i] + ";";
}
return s;
}
@Override
public String toString() {
return "RTP Connection [" + getEndpoint().getLocalName() + ", idx=" + getIndex() + "]";
}
}