dynamicRTPPayloadType
: dynamicRTPPayloadTypes.entrySet())
{
@SuppressWarnings("unchecked")
MediaFormatImpl extends Format> mediaFormatImpl
= (MediaFormatImpl extends Format>)
dynamicRTPPayloadType.getValue();
rtpManager.addFormat( mediaFormatImpl.getFormat(),
dynamicRTPPayloadType.getKey());
}
}
}
/**
* Notifies this MediaStream implementation that its
* RTPConnector instance has changed from a specific old value to a
* specific new value. Allows extenders to override and perform additional
* processing after this MediaStream has changed its
* RTPConnector instance.
*
* @param oldValue the RTPConnector of this MediaStream
* implementation before it got changed to newValue
* @param newValue the current RTPConnector of this
* MediaStream which replaced oldValue
*/
protected void rtpConnectorChanged(
RTPTransformConnector oldValue,
RTPTransformConnector newValue)
{
zrtpControl.setConnector(newValue);
// Register the transform engines that we will be using in this stream.
rtpConnector.setEngine(createTransformEngineChain());
}
/**
* Sets the StreamConnector to be used by this instance for sending
* and receiving media.
*
* @param connector the StreamConnector to be used by this instance
* for sending and receiving media
*/
public void setConnector(StreamConnector connector)
{
if (connector == null)
throw new NullPointerException("connector");
if (rtpConnector != null)
{
// Is the StreamConnector really changing?
if (rtpConnector.getConnector() == connector)
return;
}
RTPTransformConnector oldValue = rtpConnector;
rtpConnector
= new RTPTransformConnector(connector)
{
@Override
protected TransformOutputStream createDataOutputStream()
throws IOException
{
TransformOutputStream dataOutputStream
= super.createDataOutputStream();
if (dataOutputStream != null)
configureDataOutputStream(dataOutputStream);
return dataOutputStream;
}
@Override
protected TransformInputStream createDataInputStream()
throws IOException
{
TransformInputStream dataInputStream
= super.createDataInputStream();
if (dataInputStream != null)
configureDataInputStream(dataInputStream);
return dataInputStream;
}
};
rtpConnectorChanged(oldValue, rtpConnector);
}
/**
* Sets the MediaDevice that this stream should use to play back
* and capture media.
*
* Note: Also resets any previous direction set with
* {@link #setDirection(MediaDirection)} to the direction of the specified
* MediaDevice.
*
*
* @param device the MediaDevice that this stream should use to
* play back and capture media
* @see MediaStream#setDevice(MediaDevice)
*/
public void setDevice(MediaDevice device)
{
if (device == null)
throw new NullPointerException("device");
// Require AbstractMediaDevice for MediaDeviceSession support.
AbstractMediaDevice abstractMediaDevice = (AbstractMediaDevice) device;
if ((deviceSession == null) || (deviceSession.getDevice() != device))
{
MediaDeviceSession oldValue = deviceSession;
MediaDirection startedDirection;
if (deviceSession != null)
{
startedDirection = deviceSession.getStartedDirection();
deviceSession.removePropertyChangeListener(
deviceSessionPropertyChangeListener);
deviceSession.close();
deviceSession = null;
}
else
startedDirection = MediaDirection.INACTIVE;
deviceSession = abstractMediaDevice.createSession();
deviceSession.addPropertyChangeListener(
deviceSessionPropertyChangeListener);
/*
* Setting a new device resets any previously-set direction.
* Otherwise, we risk not being able to set a new device if it is
* mandatory for the new device to fully cover any previously-set
* direction.
*/
direction = null;
MediaDeviceSession newValue = deviceSession;
deviceSessionChanged(oldValue, newValue);
if (deviceSession != null)
{
deviceSession.setMute(mute);
deviceSession.start(startedDirection);
synchronized (receiveStreamSyncRoot)
{
if (receiveStream != null)
deviceSession.setReceiveStream(receiveStream);
}
}
}
}
/**
* Sets the direction in which media in this MediaStream is to be
* streamed. If this MediaStream is not currently started, calls to
* {@link #start()} later on will start it only in the specified
* direction. If it is currently started in a direction different
* than the specified, directions other than the specified will be stopped.
*
* @param direction the MediaDirection in which this
* MediaStream is to stream media when it is started
* @see MediaStream#setDirection(MediaDirection)
*/
public void setDirection(MediaDirection direction)
{
if (direction == null)
throw new NullPointerException("direction");
/*
* Make sure that the specified direction is in accord with the
* direction of the MediaDevice of this instance.
*/
MediaDeviceSession deviceSession = getDeviceSession();
MediaDirection deviceDirection
= (deviceSession == null)
? MediaDirection.INACTIVE
: deviceSession.getDevice().getDirection();
if (!deviceDirection.and(direction).equals(direction))
throw new IllegalArgumentException("direction");
this.direction = direction;
switch (this.direction)
{
case INACTIVE:
stop(MediaDirection.SENDRECV);
return;
case RECVONLY:
stop(MediaDirection.SENDONLY);
break;
case SENDONLY:
stop(MediaDirection.RECVONLY);
break;
case SENDRECV:
break;
default:
// Don't know what it may be (in the future) so ignore it.
return;
}
if (started)
start(this.direction);
}
/**
* Sets the MediaFormat that this MediaStream should
* transmit in.
*
* @param format the MediaFormat that this MediaStream
* should transmit in
* @see MediaStream#setFormat(MediaFormat)
*/
public void setFormat(MediaFormat format)
{
setAdvancedAttributes(format.getAdvancedAttributes());
handleAttributes(format.getAdvancedAttributes());
handleAttributes(format.getFormatParameters());
getDeviceSession().setFormat(format);
}
/**
* Handles attributes contained in MediaFormat.
*
* @param attrs the attributes list to handle
*/
protected void handleAttributes(Map attrs)
{
}
/**
* Causes this MediaStream to stop transmitting the media being fed
* from this stream's MediaDevice and transmit "silence" instead.
* "Silence" for video is understood as video data which is not the captured
* video data and may represent, for example, a black image.
*
* @param mute true to have this MediaStream transmit
* "silence" instead of the actual media data that it captures from its
* MediaDevice; false to transmit actual media data
* captured from the MediaDevice of this MediaStream
* @see MediaStream#setMute(boolean)
*/
public void setMute(boolean mute)
{
if (this.mute != mute)
{
this.mute = mute;
MediaDeviceSession deviceSession = getDeviceSession();
if (deviceSession != null)
deviceSession.setMute(this.mute);
}
}
/**
* Returns the target of this MediaStream to which it is to send
* and from which it is to receive data (e.g. RTP) and control data (e.g.
* RTCP).
*
* @return the MediaStreamTarget describing the data
* (e.g. RTP) and the control data (e.g. RTCP) locations to which this
* MediaStream is to send and from which it is to receive
* @see MediaStream#setTarget(MediaStreamTarget)
*/
public MediaStreamTarget getTarget()
{
return rtpConnectorTarget;
}
/**
* Sets the target of this MediaStream to which it is to send and
* from which it is to receive data (e.g. RTP) and control data (e.g. RTCP).
*
* @param target the MediaStreamTarget describing the data
* (e.g. RTP) and the control data (e.g. RTCP) locations to which this
* MediaStream is to send and from which it is to receive
* @see MediaStream#setTarget(MediaStreamTarget)
*/
public void setTarget(MediaStreamTarget target)
{
// Short-circuit if setting the same target.
if (target == null)
{
if (rtpConnectorTarget == null)
return;
}
else if (target.equals(rtpConnectorTarget))
return;
rtpConnector.removeTargets();
rtpConnectorTarget = null;
boolean targetIsSet;
if (target != null)
{
InetSocketAddress dataAddr = target.getDataAddress();
InetSocketAddress controlAddr = target.getControlAddress();
try
{
rtpConnector
.addTarget(
new SessionAddress(
dataAddr.getAddress(),
dataAddr.getPort(),
controlAddr.getAddress(),
controlAddr.getPort()));
targetIsSet = true;
}
catch (IOException ioe)
{
// TODO
targetIsSet = false;
logger.error("Failed to set target " + target, ioe);
}
}
else
targetIsSet = true;
if (targetIsSet)
{
rtpConnectorTarget = target;
if (logger.isTraceEnabled())
logger
.trace(
"Set target of "
+ getClass().getSimpleName()
+ " with hashCode "
+ hashCode()
+ " to "
+ target);
}
}
/**
* Starts capturing media from this stream's MediaDevice and then
* streaming it through the local StreamConnector toward the
* stream's target address and port. Also puts the MediaStream in a
* listening state which make it play all media received from the
* StreamConnector on the stream's MediaDevice.
*
* @see MediaStream#start()
*/
public void start()
{
start(getDirection());
started = true;
}
/**
* Starts the processing of media in this instance in a specific direction.
*
* @param direction a MediaDirection value which represents the
* direction of the processing of media to be started. For example,
* {@link MediaDirection#SENDRECV} to start both capture and playback of
* media in this instance or {@link MediaDirection#SENDONLY} to only start
* the capture of media in this instance
*/
private void start(MediaDirection direction)
{
if (direction == null)
throw new NullPointerException("direction");
if (direction.allowsSending()
&& ((startedDirection == null)
|| !startedDirection.allowsSending()))
{
startSendStreams();
getDeviceSession().start(MediaDirection.SENDONLY);
if (MediaDirection.RECVONLY.equals(startedDirection))
startedDirection = MediaDirection.SENDRECV;
else if (startedDirection == null)
startedDirection = MediaDirection.SENDONLY;
}
if (direction.allowsReceiving()
&& ((startedDirection == null)
|| !startedDirection.allowsReceiving()))
{
startReceiveStreams();
getDeviceSession().start(MediaDirection.RECVONLY);
if (MediaDirection.SENDONLY.equals(startedDirection))
startedDirection = MediaDirection.SENDRECV;
else if (startedDirection == null)
startedDirection = MediaDirection.RECVONLY;
}
}
/**
* Starts the ReceiveStreams that this instance is receiving from
* its remote peer. By design, a MediaStream instance is associated
* with a single ReceiveStream at a time. However, the
* ReceiveStreams are created by RTPManager and it tracks
* multiple ReceiveStreams. In practice, the RTPManager of
* this MediaStreamImpl will have a single ReceiveStream
* in its list.
*/
@SuppressWarnings("unchecked")
private void startReceiveStreams()
{
RTPManager rtpManager = getRTPManager();
Iterable receiveStreams;
try
{
receiveStreams = rtpManager.getReceiveStreams();
}
catch (Exception ex)
{
/*
* It appears that in early call states when there are no streams, a
* NullPointerException could be thrown. Make sure we handle it
* gracefully.
*/
if (logger.isTraceEnabled())
logger.trace("Failed to retrieve receive streams", ex);
receiveStreams = null;
}
if (receiveStreams != null)
{
for (ReceiveStream receiveStream : receiveStreams)
{
try
{
DataSource receiveStreamDataSource
= receiveStream.getDataSource();
/*
* For an unknown reason, the stream DataSource can be null
* at the end of the Call after re-INVITEs have been
* handled.
*/
if (receiveStreamDataSource != null)
receiveStreamDataSource.start();
}
catch (IOException ioex)
{
logger.warn(
"Failed to start stream " + receiveStream,
ioex);
}
}
}
}
/**
* Starts the SendStreams of the RTPManager of this
* MediaStreamImpl.
*/
private void startSendStreams()
{
/*
* Until it's clear that the SendStreams are required (i.e. we've
* negotiated to send), they will not be created. Otherwise, their
* creation isn't only illogical but also causes the CaptureDevice to
* be used.
*/
if (!sendStreamsAreCreated)
createSendStreams();
RTPManager rtpManager = getRTPManager();
@SuppressWarnings("unchecked")
Iterable sendStreams = rtpManager.getSendStreams();
if (sendStreams != null)
{
for (SendStream sendStream : sendStreams)
{
try
{
// TODO Are we sure we want to connect here?
sendStream.getDataSource().connect();
sendStream.start();
sendStream.getDataSource().start();
if (logger.isTraceEnabled())
{
logger.trace(
"Started SendStream with hashCode "
+ sendStream.hashCode());
}
}
catch (IOException ioe)
{
logger
.warn("Failed to start stream " + sendStream, ioe);
}
}
}
}
/**
* Stops all streaming and capturing in this MediaStream and closes
* and releases all open/allocated devices/resources. Has no effect if this
* MediaStream is already closed and is simply ignored.
*
* @see MediaStream#stop()
*/
public void stop()
{
stop(MediaDirection.SENDRECV);
started = false;
}
/**
* Stops the processing of media in this instance in a specific direction.
*
* @param direction a MediaDirection value which represents the
* direction of the processing of media to be stopped. For example,
* {@link MediaDirection#SENDRECV} to stop both capture and playback of
* media in this instance or {@link MediaDirection#SENDONLY} to only stop
* the capture of media in this instance
*/
private void stop(MediaDirection direction)
{
if (direction == null)
throw new NullPointerException("direction");
if (rtpManager == null)
return;
if ((MediaDirection.SENDRECV.equals(direction)
|| MediaDirection.SENDONLY.equals(direction))
&& (MediaDirection.SENDRECV.equals(startedDirection)
|| MediaDirection.SENDONLY.equals(startedDirection)))
{
stopSendStreams(false);
if (deviceSession != null)
deviceSession.stop(MediaDirection.SENDONLY);
if (MediaDirection.SENDRECV.equals(startedDirection))
startedDirection = MediaDirection.RECVONLY;
else if (MediaDirection.SENDONLY.equals(startedDirection))
startedDirection = null;
}
if ((MediaDirection.SENDRECV.equals(direction)
|| MediaDirection.RECVONLY.equals(direction))
&& (MediaDirection.SENDRECV.equals(startedDirection)
|| MediaDirection.RECVONLY.equals(startedDirection)))
{
stopReceiveStreams();
if (deviceSession != null)
deviceSession.stop(MediaDirection.RECVONLY);
if (MediaDirection.SENDRECV.equals(startedDirection))
startedDirection = MediaDirection.SENDONLY;
else if (MediaDirection.RECVONLY.equals(startedDirection))
startedDirection = null;
}
}
/**
* Stops the ReceiveStreams that this instance is receiving from
* its remote peer. By design, a MediaStream instance is associated
* with a single ReceiveStream at a time. However, the
* ReceiveStreams are created by RTPManager and it tracks
* multiple ReceiveStreams. In practice, the RTPManager of
* this MediaStreamImpl will have a single ReceiveStream
* in its list.
*/
@SuppressWarnings("unchecked")
private void stopReceiveStreams()
{
Iterable receiveStreams;
try
{
receiveStreams = rtpManager.getReceiveStreams();
}
catch (Exception ex)
{
/*
* It appears that in early call states when there are no streams, a
* NullPointerException could be thrown. Make sure we handle it
* gracefully.
*/
if (logger.isTraceEnabled())
logger.trace("Failed to retrieve receive streams", ex);
receiveStreams = null;
}
if (receiveStreams != null)
{
for (ReceiveStream receiveStream : receiveStreams)
{
try
{
DataSource receiveStreamDataSource
= receiveStream.getDataSource();
/*
* For an unknown reason, the stream DataSource can be null
* at the end of the Call after re-INVITEs have been
* handled.
*/
if (receiveStreamDataSource != null)
receiveStreamDataSource.stop();
}
catch (IOException ioex)
{
logger.warn("Failed to stop stream " + receiveStream, ioex);
}
}
}
}
/**
* Stops the SendStreams that this instance is sending to its
* remote peer and optionally closes them.
*
* @param close true to close the SendStreams that this
* instance is sending to its remote peer after stopping them;
* false to only stop them
* @return the SendStreams which were stopped
*/
private Iterable stopSendStreams(boolean close)
{
if (rtpManager == null)
return null;
@SuppressWarnings("unchecked")
Iterable sendStreams = rtpManager.getSendStreams();
Iterable stoppedSendStreams
= stopSendStreams(sendStreams, close);
if (close)
sendStreamsAreCreated = false;
return stoppedSendStreams;
}
/**
* Stops specific SendStreams and optionally closes them.
*
* @param sendStreams the SendStreams to be stopped and optionally
* closed
* @param close true to close the specified SendStreams
* after stopping them; false to only stop them
* @return the stopped SendStreams
*/
private Iterable stopSendStreams(
Iterable sendStreams,
boolean close)
{
if (sendStreams == null)
return null;
for (SendStream sendStream : sendStreams)
try
{
sendStream.getDataSource().stop();
sendStream.stop();
if (close)
try
{
sendStream.close();
}
catch (NullPointerException npe)
{
/*
* Sometimes com.sun.media.rtp.RTCPTransmitter#bye() may
* throw NullPointerException but it does not seem to be
* guaranteed because it does not happen while debugging
* and stopping at a breakpoint on SendStream#close().
* One of the cases in which it appears upon call
* hang-up is if we do not close the "old" SendStreams
* upon reinvite(s). Though we are now closing such
* SendStreams, ignore the exception here just in case
* because we already ignore IOExceptions.
*/
logger
.error(
"Failed to close stream " + sendStream,
npe);
}
}
catch (IOException ioe)
{
logger.warn("Failed to stop stream " + sendStream, ioe);
}
return sendStreams;
}
/**
* Returns a human-readable representation of a specific DataSource
* instance in the form of a String value.
*
* @param dataSource the DataSource to return a human-readable
* representation of
* @return a String value which gives a human-readable
* representation of the specified dataSource
*/
public static String toString(DataSource dataSource)
{
StringBuffer str = new StringBuffer();
str.append(dataSource.getClass().getSimpleName());
str.append(" with hashCode ");
str.append(dataSource.hashCode());
MediaLocator locator = dataSource.getLocator();
if (locator != null)
{
str.append(" and locator ");
str.append(locator);
}
return str.toString();
}
/**
* Notifies this ReceiveStreamListener that the RTPManager
* it is registered with has generated an event related to a ReceiveStream.
*
* @param event the ReceiveStreamEvent which specifies the
* ReceiveStream that is the cause of the event and the very type
* of the event
* @see ReceiveStreamListener#update(ReceiveStreamEvent)
*/
public void update(ReceiveStreamEvent event)
{
if (event instanceof NewReceiveStreamEvent)
{
ReceiveStream receiveStream = event.getReceiveStream();
if (receiveStream != null)
{
long receiveStreamSSRC = receiveStream.getSSRC();
if (logger.isTraceEnabled())
{
logger.trace(
"Received new ReceiveStream with ssrc "
+ receiveStreamSSRC);
}
setRemoteSourceID(receiveStreamSSRC);
synchronized (receiveStreamSyncRoot)
{
if (this.receiveStream != receiveStream)
{
this.receiveStream = receiveStream;
MediaDeviceSession deviceSession = getDeviceSession();
if (deviceSession != null)
deviceSession.setReceiveStream(this.receiveStream);
}
}
}
}
else if (event instanceof TimeoutEvent)
{
ReceiveStream receiveStream = event.getReceiveStream();
/*
* If we recreate streams, we will already have restarted
* zrtpControl. But when on the other end someone recreates his
* streams, we will receive a ByeEvent (which extends TimeoutEvent)
* and then we must also restart our ZRTP. This happens, for
* example, when we are already in a call and the remote peer
* converts his side of the call into a conference call.
*/
if(!zrtpRestarted)
restartZrtpControl();
if (receiveStream != null)
{
synchronized (receiveStreamSyncRoot)
{
if (this.receiveStream == receiveStream)
{
this.receiveStream = null;
MediaDeviceSession deviceSession = getDeviceSession();
if (deviceSession != null)
deviceSession.setReceiveStream(null);
}
}
}
}
}
/**
* Notifies this SendStreamListener that the RTPManager it
* is registered with has generated an event related to a SendStream.
*
* @param event the SendStreamEvent which specifies the
* SendStream that is the cause of the event and the very type of
* the event
* @see SendStreamListener#update(SendStreamEvent)
*/
public void update(SendStreamEvent event)
{
// TODO Auto-generated method stub
}
/**
* Notifies this SessionListener that the RTPManager it is
* registered with has generated an event which pertains to the session as a
* whole and does not belong to a ReceiveStream or a
* SendStream or a remote participant necessarily.
*
* @param event the SessionEvent which specifies the source and the
* very type of the event
* @see SessionListener#update(SessionEvent)
*/
public void update(SessionEvent event)
{
// TODO Auto-generated method stub
}
/**
* Sets the local SSRC identifier and fires the corresponding
* PropertyChangeEvent.
*
* @param ssrc the SSRC identifier that this stream will be using in
* outgoing RTP packets from now on.
*/
protected void setLocalSourceID(long ssrc)
{
Long oldValue = this.localSourceID;
this.localSourceID = ssrc;
firePropertyChange(PNAME_LOCAL_SSRC, oldValue, ssrc);
}
/**
* Sets the remote SSRC identifier and fires the corresponding
* PropertyChangeEvent.
*
* @param ssrc the SSRC identifier that this stream will be using in
* outgoing RTP packets from now on.
*/
protected void setRemoteSourceID(long ssrc)
{
Long oldValue = this.remoteSourceID;
this.remoteSourceID = ssrc;
firePropertyChange(PNAME_REMOTE_SSRC, oldValue, ssrc);
}
/**
* Returns the list of CSRC identifiers for all parties currently known
* to contribute to the media that this stream is sending toward its remote
* counter part. In other words, the method returns the list of CSRC IDs
* that this stream will include in outgoing RTP packets. This method will
* return an null in case this stream is not part of a mixed
* conference call.
*
* @return a long[] array of CSRC IDs representing parties that are
* currently known to contribute to the media that this stream is sending
* or an null in case this MediaStream is not part of a
* conference call.
*/
public long[] getLocalContributingSourceIDs()
{
return localContributingSourceIDList;
}
/**
* Returns the List of CSRC identifiers representing the parties
* contributing to the stream that we are receiving from this
* MediaStream's remote party.
*
* @return a List of CSRC identifiers representing the parties
* contributing to the stream that we are receiving from this
* MediaStream's remote party.
*/
public long[] getRemoteContributingSourceIDs()
{
long[] remoteSsrcList = getDeviceSession().getRemoteSSRCList();
// TODO implement
return remoteSsrcList;
}
/**
* Used to set the priority of the receive/send streams. Underling
* implementations can override this and return different than
* current default value.
*
* @return the priority for the current thread.
*/
protected int getPriority()
{
return Thread.currentThread().getPriority();
}
}