/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.media;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.media.*;
import javax.media.rtp.*;
import javax.media.control.*;
import javax.media.protocol.*;
import javax.media.rtp.event.*;
import net.java.sip.communicator.service.media.*;
import net.java.sip.communicator.service.media.MediaException;
import net.java.sip.communicator.service.media.event.*;
import net.java.sip.communicator.service.media.event.MediaEvent;
import net.java.sip.communicator.util.*;
/**
* Implementation of a <tt>RtpFlow</tt> which is a bridge for data transmission
* between two end points. Data which transit on this flow are encoded with
* a specified format.
*
* @author Symphorien Wanko
* @author KenLarson
*/
public class RtpFlowImpl
implements RtpFlow,
ReceiveStreamListener,
SessionListener,
ControllerListener
{
/**
* Logger for this class
*/
private static final Logger logger
= Logger.getLogger(RtpFlowImpl.class);
/**
* Our IP address
*/
private String localAddress = null;
/**
* IP to which this <tt>flow send media</tt>
*/
private String remoteAddress = null;
/**
* The local port used by this <tt>RtpFlow</tt>
*/
private int localPort = -1;
/**
* Port for the remote endpoint
*/
private int remotePort = -1;
/**
* Collection of <tt>RtpManager</tt>s used by this <tt>RtpFlow</tt>
*/
private RTPManager rtpMgrs[] = null;
/**
* Data source used for media capture
*/
private DataSource dataSource = null;
/**
* Collection of send streams used by this <tt>RtpFlow</tt>
*/
private final List<SendStream> sendStreams = new ArrayList<SendStream>();
/**
* The media on which this <tt>RtpFlow</tt> depend,
* the one which created us
*/
private MediaServiceImpl mediaService = null;
/**
* Used for controlling media
*/
private MediaControl mediaControl = null;
/**
* Media encoding passed to JMF via the media control
*/
private final Hashtable<String, List<String>> mediaEncoding
= new Hashtable<String, List<String>>();
/**
* A list of listeners registered for media events.
*/
private final List<MediaListener> mediaListeners
= new Vector<MediaListener>();
/**
* Creates an instance of <tt>RtpFlowImpl</tt> for media transmission.
* A flow do not care about session or anything other than transmitting
* media data between two end points, using the given media encoding.
*
* @param mediaServie the media service which created us
* @param localIpAddress local IP address
* @param localPort local port number
* @param remoteIpAddress remote IP address
* @param remotePort remote port number
* @param mediaEncoding media encoding used for data
*
* @throws MediaException if initializing the flow fails.
*/
public RtpFlowImpl(MediaServiceImpl mediaServie,
String localIpAddress,
String remoteIpAddress,
int localPort,
int remotePort,
Hashtable<String, List<String>> mediaEncoding)
throws MediaException
{
this.localAddress = localIpAddress;
this.remoteAddress = remoteIpAddress;
this.localPort = localPort;
this.remotePort = remotePort;
this.mediaService = mediaServie;
this.mediaControl = mediaService.getMediaControl();
this.mediaEncoding.putAll(mediaEncoding);
initialize();
}
/**
* Returns the local port used by this flow.
*
* @return localPort the local port used by this flow.
*/
public int getLocalPort()
{
return localPort;
}
/**
* Returns the local address used by this flow.
*
* @return localAddress the local address port used by this flow.
*/
public String getLocalAddress()
{
return localAddress;
}
/**
* Returns the remote port used by this flow.
*
* @return remotePort the remote port used by this flow.
*/
public int getRemotePort()
{
return remotePort;
}
/**
* Returns the remote address used by this flow.
*
* @return remoteAddress the remote address port used by this flow.
*/
public String getRemoteAddress()
{
return remoteAddress;
}
/**
* This method initializes the <tt>RtpFlow</tt> by creating
* datasource and associated transmitter. We also create session for
* each JMF track here.
*
* @throws MediaException if initialization fails
*/
private void initialize() throws MediaException
{
dataSource = mediaControl.createDataSourceForEncodings(mediaEncoding);
PushBufferDataSource pbds = (PushBufferDataSource) dataSource;
PushBufferStream pbss[] = pbds.getStreams();
rtpMgrs = new RTPManager[pbss.length];
SessionAddress localAddr, destAddr;
InetAddress ipAddr;
SendStream sendStream;
int port;
for (int i = 0; i < pbss.length; i++)
{
try
{
rtpMgrs[i] = RTPManager.newInstance();
CallSessionImpl.registerCustomCodecFormats(rtpMgrs[i]);
port = remotePort + 2 * i;
ipAddr = InetAddress.getByName(remoteAddress);
localAddr = new SessionAddress(InetAddress.
getByName(this.localAddress), localPort);
destAddr = new SessionAddress(ipAddr, port);
rtpMgrs[i].addReceiveStreamListener(this);
rtpMgrs[i].addSessionListener(this);
BufferControl bc = (BufferControl) rtpMgrs[i]
.getControl("javax.media.control.BufferControl");
if (bc != null)
{
int bl = 160;
bc.setBufferLength(bl);
}
try
{
rtpMgrs[i].initialize(localAddr);
}
catch (InvalidSessionAddressException e)
{
// In case the local address is not allowed to read,
// we user another local address
SessionAddress sessAddr = new SessionAddress();
localAddr = new SessionAddress(sessAddr.getDataAddress(),
localPort);
rtpMgrs[i].initialize(localAddr);
}
rtpMgrs[i].addTarget(destAddr);
logger.info("Created RTP session at " + localPort +
" to: " + remoteAddress + " " + port);
sendStream = rtpMgrs[i].createSendStream(dataSource, i);
sendStreams.add(sendStream);
sendStream.start();
}
catch (Exception e)
{
throw new MediaException("Failed to create transmitter"
, MediaException.INTERNAL_ERROR, e);
}
}
}
/**
* Implementation of <tt>start</tt> to send media data.
*/
public void start()
{
mediaControl.startProcessingMedia(this);
}
/**
* Stops the transmission if already started.
* Stops receiving also.
*/
public void stop()
{
RTPManager rtpMgr;
for (int i = 0; i < rtpMgrs.length; i++)
{
rtpMgr = rtpMgrs[i];
rtpMgr.removeReceiveStreamListener(this);
rtpMgr.removeSessionListener(this);
rtpMgr.removeTargets("Session ended.");
rtpMgr.dispose();
}
sendStreams.clear();
mediaControl.stopProcessingMedia(this);
}
/**
* Resume media transmission on this flow
*/
public void resume()
{
logger.info("resuming transmission... ");
for (SendStream sendStream : sendStreams)
{
try
{
sendStream.start();
}
catch (IOException ex)
{
logger.warn("Exception when resuming transmission ", ex);
}
}
}
/**
* Pause media transmission on this flow
*/
public void pause()
{
logger.info("pausing transmission... ");
for (SendStream sendStream : sendStreams)
{
try
{
sendStream.stop();
}
catch (IOException ex)
{
logger.warn("Exception when pausing transmission ", ex);
}
}
}
/**
* Implements update from javax.media.rtp.SessionListener
*
* @param evt received event
*/
public synchronized void update(SessionEvent evt)
{
if (evt instanceof NewParticipantEvent)
{
Participant p = ((NewParticipantEvent) evt).getParticipant();
logger.info("A new participant had just joined: " + p.getCNAME());
}
}
/**
* Implements update from javax.media.rtp.ReceiveStreamListener
*
* @param evt received event
*/
public synchronized void update(ReceiveStreamEvent evt)
{
Participant participant = evt.getParticipant(); // could be null.
ReceiveStream stream = evt.getReceiveStream(); // could be null.
if (evt instanceof RemotePayloadChangeEvent)
{
logger.warn("Received an RTP PayloadChangeEvent," +
" not supported cannot handle payload change.");
}
else if (evt instanceof NewReceiveStreamEvent)
{
try
{
stream = evt.getReceiveStream();
DataSource ds = stream.getDataSource();
// Find out the formats.
RTPControl ctl =
(RTPControl) ds.getControl("javax.jmf.rtp.RTPControl");
if (ctl != null)
{
logger.info("Recevied new RTP stream: " + ctl.getFormat());
}
else
logger.info("Recevied new RTP stream");
if (participant == null)
logger.info("The sender of this stream" +
"had yet to be identified.");
else
{
logger.info("The stream comes from: " +
participant.getCNAME());
}
// create a player by passing datasource to the Media Manager
Player p = javax.media.Manager.createPlayer(ds);
if (p == null)
return;
p.addControllerListener(this);
p.realize();
fireMediaEvent((participant != null) ? participant.getCNAME() : "");
}
catch (Exception e)
{
logger.warn("NewReceiveStreamEvent exception ", e);
return;
}
}
else if (evt instanceof StreamMappedEvent)
{
if (stream != null && stream.getDataSource() != null)
{
DataSource ds = stream.getDataSource();
// Find out the formats.
RTPControl ctl =
(RTPControl) ds.getControl("javax.jmf.rtp.RTPControl");
logger.info("The previously unidentified stream ");
if (ctl != null)
logger.info(": " + ctl.getFormat());
logger.info(" had now been identified as sent by: "
+ participant.getCNAME());
}
}
else if (evt instanceof ByeEvent)
{
logger.info("Got \"bye\" from: " + participant.getCNAME());
}
}
/**
* Implements controllerUpdate from javax.media.rtp.ControllerListener
*
* @param ce received event
*/
public synchronized void controllerUpdate(ControllerEvent ce)
{
Player p = (Player) ce.getSourceController();
if (p == null)
return;
// Get this when the internal players are realized.
if (ce instanceof RealizeCompleteEvent)
{
p.start();
}
else if (ce instanceof ControllerErrorEvent)
{
p.removeControllerListener(this);
logger.warn("Receiver internal error " + ce);
}
}
/**
* Add a listener to be informed of media events hapening
* on this flow.
*/
public void addMediaListener(MediaListener listener)
{
synchronized(mediaListeners)
{
if (!mediaListeners.contains(listener))
mediaListeners.add(listener);
}
}
/**
* Notify listeners that we have received media.
*
* @param from origin of the media
*/
private void fireMediaEvent(String from)
{
MediaEvent mediaEvent = new MediaEvent(this, from);
MediaListener[] listeners;
synchronized(mediaListeners)
{
listeners =
mediaListeners.toArray(new MediaListener[mediaListeners.size()]);
}
for (MediaListener listener : listeners)
{
listener.receivedMediaStream(mediaEvent);
}
}
}