/* * @(#)RTPManager.java 1.9 02/08/21 * * Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved. */ package javax.media.rtp; import javax.media.protocol.*; import javax.media.format.*; import java.net.*; import java.util.*; import java.io.*; import javax.media.rtp.event.*; import javax.media.rtp.rtcp.*; import javax.media.Controls; import javax.media.Format; import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.util.Enumeration; import java.util.Hashtable; import com.sun.media.Log; /** * The interface implemented by the RTPManager. This is the * starting point for creating, maintaining and closing an RTP * session. * <p> * <b>1. Unicast Session</b> * <br> * The following code fragment illustrates how to create a unicast * session: * <pre> * import java.net.*; * import javax.media.rtp.*; * * // create the RTP Manager * RTPManager rtpManager = RTPManager.newInstance(); * * // create the local endpoint for the local interface on * // any local port * SessionAddress localAddress = new SessionAddress(); * * // initialize the RTPManager * rtpManager.initialize( localAddress); * * // add the ReceiveStreamListener if you need to receive data * // and do other application specific stuff * // ... * * // specify the remote endpoint of this unicast session * // the address string and port numbers in the following lines * // need to be replaced with your values. * InetAddress ipAddress = InetAddress.getByName( "168.1.2.3"); * * SessionAddress remoteAddress = new SessionAddress( ipAddress, 3000); * * // open the connection * rtpManager.addTarget( remoteAddress); * * // create a send stream for the output data source of a processor * // and start it * DataSource dataOutput = createDataSource(); * * SendStream sendStream = rtpSession.createSendStream( dataOutput, 1); * sendStream.start(); * * // send data and do other application specific stuff, * // ... * * // close the connection if no longer needed. * rtpManager.removeTarget( remoteAddress, "client disconnected."); * * // call dispose at the end of the life-cycle of this RTPManager so * // it is prepared to be garbage-collected. * rtpManager.dispose(); * </pre> * <b>2. Multi-Unicast Session</b> * <br> * Creating multi-unicast sessions is similar to the example above. After * creating and starting the SendStream new remote endpoints may be added * by subsequent addTarget calls: * <pre> * addTarget( remoteAddress2); * addTarget( remoteAddress3); * </pre> * * <b>3. Multicast Session</b> * <br> * Creating and participating in multicast sessions also works similar * to the unicast example. Instead of specifying local and remote endpoints * a multicast session address needs to be created and passed into the * initialize and addTarget calls. Everything else follows the unicast * example. * <pre> * //... * * // create a multicast address for 224.1.1.0 and ports 3000/3001 * IPAddress ipAddress = InetAddress.getByName( "224.1.1.0"); * * SessionAddress multiAddress = new SessionAddress( ipAddress, 3000); * * // initialize the RTPManager * rtpManager.initialize( multiAddress); * * // add the target * rtpManager.addTarget( multiAddress); * * // ... */ public abstract class RTPManager implements Controls { private static boolean jdkInit = false; private static Method forName3ArgsM; private static Method getSystemClassLoaderM; private static ClassLoader systemClassLoader; private static Method getContextClassLoaderM; /** * This method is used to add a dynamic payload to format * mapping to the RTPManager. The RTPManager maintains * all static payload numbers and their correspnding formats as * mentioned in the Audio/Video profile document. Using the plugin * packethandler interface, a user may plugin his own packetizer or * depacketizer to handle RTP streams of a proprietary format using * dynamic payload numbers as specified in the AV profile. Before * streaming dynamic payloads, a Format object needs to * be created for each of the dynamic payload types and associated * with a dynamic payload number. * @param format The Format to be associated with this dynamic * payload number. * @param payload The RTP payload number * @see Format */ abstract public void addFormat(Format format, int payload); /** * Adds a ReceiveStreamListener. This listener listens to all the * events that notify state transitions for a particular * ReceiveStream. */ abstract public void addReceiveStreamListener( ReceiveStreamListener listener); /** * Adds a RemoteListener to the session. This listener listens * to all remote RTP events. Currently, these include * ReceiverReportEvent, ReceiveSenderReportEvent and * RemoteCollisionEvent. This interface would be usefuly for an RTCP * monitor that does not wish to receive any particular stream * transitionEvents but just wants to monitor the session quality * and statistics. */ abstract public void addRemoteListener( RemoteListener listener); /** * Adds a SendStreamListener. This listener listens to all the * events that notify state transitions for a particular * SendStream. */ abstract public void addSendStreamListener(SendStreamListener listener); /** * Adds a SessionListener. A SessionListener will receive * events that pertain to the Session as a whole. Currently, * these include the NewParticipantEvent and * LocalCollisionEvent. Events are notified in the * update(SessionEvent) method which must be implemented by all * SessionListeners. * @classname SessionListener */ abstract public void addSessionListener(SessionListener listener); /** * Closes all open streams associated with the endpoint defined * by remoteAddress. * <P> * * @param remoteAddress The RTP session address of a remote end * point for this session. i.e. the IP address/port of a remote * host * * @param reason A string that RTCP will send out to other * participants as the reason the local participant has quit the * session.This RTCP packet will go out with the default SSRC of the * session. If supplied as null, a default reason will be supplied * by the RTPManager. * <P> */ abstract public void removeTarget( SessionAddress remoteAddress, String reason) throws InvalidSessionAddressException; /** * Closes the open streams associated with all remote endpoints * that have been added previously by subsequent addTarget() calls. <p> * * @param reason A string that RTCP will send out to other * participants as the reason the local participant has quit the * session.This RTCP packet will go out with the default SSRC of the * session. If supplied as null, a default reason will be supplied * by the RTPManager. <P> */ abstract public void removeTargets(String reason); /** * This method is used to create a sending stream within the RTP * session. For each time the call is made, a new sending stream * will be created. This stream will use the SDES items as entered * in the initialize() call for all its RTCP messages. Each stream * is sent out with a new SSRC (Synchronisation SouRCe * identifier), but from the same participant i.e. local * participant. <BR> * * @param dataSource This is the PushOutputDataSource or * PullOutputDataSource which is the output data source of the * Processor. This data source may contain more than one * stream. The stream which is used in creating this RTP * stream is specified in the next parameter of stream.<BR> * * @param streamIndex The index of the sourcestream from which * data is sent out on this RTP stream. An index of 1 would indicate the first * sourcestream of this data source should be used to create the RTP * stream. If the index is set to zero, it would indicate a RTP * mixer operation is desired. i.e. all the streams of this * data source must be mixed into one single stream from one single * SSRC. <BR> * * Note: The RTP payload that is used to send this stream is found * from the format set on the SourceStream of the data source * supplied. <BR> * If the sourcestream has no format set or has a * format for which a packetizer plugin cannot be found in the session * manager's database, an UnsupportedFormatException will be thrown * by the RTPManager. <BR> * * Note on PullDataSources supplied to the RTPManager: * In most cases, it is expected that the data source supplied to the * RTPManager for stream creation would be a * PushDataSource. In cases that the data source is a PullDataSource, * it MUST have a format set on its SourceStreams. This is the only * way for RTPManger to determine the RTP payload to use in * the header of the stream as well as the bitrate to pulldata from * this data source. <BR> * * @return The SendStream created by the RTPManager.<BR> * * @exception UnsupportedFormatException * (javax.media.format.UnsupportedFormatException ). This * exception is thrown if the format is not set on the sourcestream * or a RTP payload cannot be located for the format set on the * sourcestream. * @exception IOException * Thrown for two possible reasons which will be specified in the * message part of the exception * 1) If the session was initiated with zero rtcpBandwidthFraction which * implied that this participant could not send out any RTP/RTCP * data or control messages. i.e. it could not also create any send * streams and was just a passive listener for this session. * 2) If there was any problem opening the sending sockets * @classname SendStream * */ abstract public SendStream createSendStream(DataSource dataSource, int streamIndex) throws UnsupportedFormatException, IOException; /** * Releases all objects allocated in the course of the session and prepares * the RTPManager to be garbage-collected. This method should be called at * the end of any RTP session. */ abstract public void dispose(); /** * Returns a vector of all the active (data sending) * participants. These participants may be remote and/or the local participant. */ abstract public Vector getActiveParticipants(); /** * Returns all the participants of this session. */ abstract public Vector getAllParticipants(); /** * This method will provide access to overall data and control * messsage reception statistics for this session. Statistics on * data from individual sources is available from the * getSourceReceptionStats() method of the ReceiveStream interface. * @return The GlobalReceptionStats for this session * @classname ReceiveStream */ abstract public GlobalReceptionStats getGlobalReceptionStats(); /** * This method will provide access to overall data and control * messsage transmission statistics for this session. Statistics on * data from individual sources is available from the * getSourceTransmissionStats() method of the SendStream interface. * @return The GlobalTransmissionStats for this session * @classname SendStream */ abstract public GlobalTransmissionStats getGlobalTransmissionStats(); /** * Retrieves the local participant. */ abstract public LocalParticipant getLocalParticipant(); /** * Returns all the passive participants. These participants will * include the local participant and some remote participants that * do not send any data. */ abstract public Vector getPassiveParticipants(); /** Returns the ReceiveStreams created by the * RTPManager. These are streams formed when the RTPManager * detects a new source of RTP data. * ReceiveStreams returned are a snapshot of the current state in the * RTPManager and the ReceiveStreamListener interface may be used * to get notified of additional streams. */ abstract public Vector getReceiveStreams(); /** * Returns a Vector of all the remote participants in the * session.This vector is simply a snapshot of the current state in * the RTPManager. The SessionListener interface can be * used to get notified of additional participants for the * Session. <P> */ abstract public Vector getRemoteParticipants(); /** Returns the SendStreams created by the * RTPManager. * SendStreams returned are a snapshot of the current state in the * RTPSesion and the SendStreamListener interface may be used * to get notified of additional streams. */ abstract public Vector getSendStreams(); /** * Initializes the session. Once this method has been called, the * session is "initialized" and this method cannot be called again. <P> * * @param localAddress Encapsulates the *local* control and data * addresses to be used for the session. If either InetAddress * contained in this parameter is null, a default local address * will be chosen. The ports do not necessarily need to be specified * (i.e. they may be the ANY_PORT constant); the RTPManager will pick * appropriate ports in that case. <P> * If the session joins a multicast group, the localAddress will be ignored. * The multicast address will be taken from the addTarget() call. */ abstract public void initialize( SessionAddress localAddress) throws InvalidSessionAddressException, IOException; /** * Initializes the session. Once this method has been called, the * session is "initialized" and this method cannot be called again. <P> * * @param localAddresses An array of local session adresses. * In most cases the address will contain a single session address, * but for multi-homed systems (systems with more than one IP interface) * there may be several local adresses specified in this parameter. <p> * * @param sourceDescription An array of SourceDescription * objects containing information to send in RTCP SDES packets * for the local participant. This information can be changed by * calling setSourceDescription() on the local Participant * object. * * @param rtcpBandwidthFraction The fraction of the session bandwidth * that the RTPManager must use when sending out RTCP reports. * * @param rtcpSenderBandwidthFraction The fraction of the * rtcpBandwidthFraction that the RTPManager must use to send out RTCP Sender * reports from the local participant. The remaining fraction of the * rtcp_bw is used for sending out RTCP Receiver reports. <P> * * @param encryptionInfo the encryption information to be used in * this session. * * Note : The rtcpBandwidthFraction is set to zero for a * non-participating observer of this session. In this case * the application will receive both RTP and RTCP messages, but will * not send out any RTCP feedback reports. * This is equivalent to setting the outgoing RTP/RTCP * bandwidth of this application to zero, implying that this * application may NOT send out any data or control streams and can * thus not make a call to createSendStream(). If it does, it will * receive an exception. Further, this application is NOT considered * a Participant since it does not send out any RTCP * information. Consequently, this client will NOT appear in the * list of Participants for this session.<P> * * Init called a second time or thereafter will return * without doing anything, since the session had already been * initialized. If parameters to init() are different from * before, the user must note that the new parameters will ignored * as a result of no action being performed. <p> * * @exception InvalidSessionAddressException This exception is * thrown if the local control and data addresses given in * parameter localAddress do not belong to one of the localhost * interfaces. * @exception IOException * * @classname SessionAddress * @classname SourceDescription */ abstract public void initialize( SessionAddress localAddresses[], SourceDescription sourceDescription[], double rtcpBandwidthFraction, double rtcpSenderBandwidthFraction, EncryptionInfo encryptionInfo) throws InvalidSessionAddressException, IOException; /** * Initializes the session. Once this method has been called, the * session is "initialized" and this method cannot be called again. <P> * * @param connector An implementation of the RTPConnector interface that * allows the developer to connect the RTPManager to any type of transport. * By default, RTP is streamed over UDP. If an RTPConnector is present, the * RTPManager will use the connector's send and receive methods to send or * receive data. * Please note: the methods addTarget, removeTarget and removeTargets cannot * be used in conjunction with an RTPConnector since these tasks will be handled * directly by the connector object. * * @classname RTPConnector */ abstract public void initialize( RTPConnector connector); /** * This method opens the session, causing RTCP reports to be * generated and callbacks to be made through the * SessionListener interface. This method must be called after * session initialization and prior to the creation of any streams * on a session. * * @param remoteAddress The RTP session address of a remote end * point for this session. i.e. the IP address/port of a remote * host * @exception InvalidSessionAddressException This exception is * thrown if the remote control and data addresses given in * parameter localAddress are not valid session addresses. * <P> * @classname SessionAddress * */ abstract public void addTarget( SessionAddress remoteAddress) throws InvalidSessionAddressException, IOException; /** * Removes a ReceiveStreamListener. * @classname ReceiveStreamListener */ abstract public void removeReceiveStreamListener( ReceiveStreamListener listener); /** * Removes a RemoteListener. * @classname RTPRemoteListener */ abstract public void removeRemoteListener( RemoteListener listener); /** * Removes a SendStreamListener. * @classname SendStreamListener */ abstract public void removeSendStreamListener(SendStreamListener listener); /** * Removes a SessionListener. * @classname SessionListener */ abstract public void removeSessionListener(SessionListener listener); /** * Create an <CODE>RTPManager</CODE> object for the underlying * implementation class. */ public static RTPManager newInstance() { RTPManager rtpManager= null; Enumeration SessionList = getRTPManagerList().elements(); while( SessionList.hasMoreElements()) { String protoClassName = (String)SessionList.nextElement(); try { Class protoClass = getClassForName(protoClassName); rtpManager = (RTPManager)protoClass.newInstance(); } catch (ClassNotFoundException e) { // System.out.println( "class def not found."); } catch (InstantiationException e) { String err = "Error instantiating class: " + protoClassName + " : " + e; Log.error(e); } catch (IllegalAccessException e) { System.out.println( "illegal access."); } catch (Exception e) { String err = "Error instantiating class: " + protoClassName + " : " + e; Log.error(e); } catch (Error e) { String err = "Error instantiating class: " + protoClassName + " : " + e; Log.error(e); } if( rtpManager != null) { break; } } return rtpManager; } /** * Build a list of <CODE>RTPManager</CODE> implementation classes. * The implemenation class must be named 'RTPSessionMgr' and is * required to extend from javax.media.rtp.RTPManager. * <p> * The first name in the list will always be: * <blockquote><pre> * media.rtp.RTPSessionMgr * </pre></blockquote> * <p> * * Each additional name looks like: * <blockquote><pre> * com.<company>.media.rtp.RTPSessionMgr * </pre></blockquote> * for every <CODE><company></CODE> in the * company-list. */ static public Vector getRTPManagerList() { // The first element is the name of the protocol handler ... String sourceName = "media.rtp.RTPSessionMgr"; return buildClassList(getProtocolPrefixList(), sourceName); } // This is a Package private class // This is a Package private class static Class getClassForName(String className) throws ClassNotFoundException { /** * Note: if we don't want this functionality * just replace it with Class.forName(className) */ try { return Class.forName(className); } catch (Exception e) { if (!checkIfJDK12()) { throw new ClassNotFoundException(e.getMessage()); } } catch (Error e) { if (!checkIfJDK12()) { throw e; } } /** * In jdk1.2 application, when you have jmf.jar in the ext directory and * you want to access a class that is not in jmf.jar but is in the CLASSPATH, * you have to load it using the the system class loader. */ try { return (Class) forName3ArgsM.invoke(Class.class, new Object[] { className, new Boolean(true), systemClassLoader}); } catch (Throwable e) { } /** * In jdk1.2 applet, when you have jmf.jar in the ext directory and * you want to access a class that is not in jmf.jar but applet codebase, * you have to load it using the the context class loader. */ try { // TODO: may need to invoke RuntimePermission("getClassLoader") privilege ClassLoader contextClassLoader = (ClassLoader) getContextClassLoaderM.invoke(Thread.currentThread(), null); return (Class) forName3ArgsM.invoke(Class.class, new Object[] { className, new Boolean(true), contextClassLoader}); } catch (Exception e) { throw new ClassNotFoundException(e.getMessage()); } catch (Error e) { throw e; } } /** * Build a list of complete class names. *<p> * * For each element of the prefix-list * the following element is added to the list: * <blockquote><pre> * <prefix>.<name> * </pre></blockquote> * These are added to the list in the same order as the prefixes appear * in the prefix-list. * </ol> * * @param prefixList The list of prefixes to prepend to the class name. * @param name The name of the class to build the list for. * @return A vector of class name strings. */ static Vector buildClassList(Vector prefixList, String name) { // New list which has the name as the first element ... Vector classList = new Vector(); // Try and instance one directly from the classpath // if it's there. // $jdr: This has been objected to as confusing, // the argument for it's inclusion is that it // gives the user (via the classpath) a way // of modifying the search list at run time // for all applications. classList.addElement(name); // ... for each prefix append the name and put it // in the class list ... Enumeration prefix = prefixList.elements(); while( prefix.hasMoreElements()) { String prefixName = (String)prefix.nextElement(); classList.addElement(prefixName + "." + name); } // ... done return classList; } static Vector getProtocolPrefixList() { return (Vector) javax.media.PackageManager.getProtocolPrefixList().clone(); } private static boolean checkIfJDK12() { if (jdkInit) return (forName3ArgsM != null); jdkInit = true; try { forName3ArgsM = Class.class.getMethod("forName", new Class[] { String.class, boolean.class, ClassLoader.class }); getSystemClassLoaderM = ClassLoader.class.getMethod("getSystemClassLoader", null); // TODO: may need to invoke RuntimePermission("getClassLoader") privilege systemClassLoader = (ClassLoader) getSystemClassLoaderM.invoke(ClassLoader.class, null); getContextClassLoaderM = Thread.class.getMethod("getContextClassLoader", null); return true; } catch (Throwable t) { forName3ArgsM = null; return false; } } }