Newer
Older
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jitsi.impl.neomedia;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.media.*;
import javax.media.control.*;
import javax.media.format.*;
import javax.media.protocol.*;
import javax.media.rtp.*;
import javax.media.rtp.event.*;
import javax.media.rtp.rtcp.*;
import org.jitsi.impl.neomedia.device.*;
import org.jitsi.impl.neomedia.format.*;
import org.jitsi.impl.neomedia.protocol.*;
import org.jitsi.impl.neomedia.transform.*;
import org.jitsi.impl.neomedia.transform.csrc.*;
import org.jitsi.impl.neomedia.transform.dtmf.*;
import org.jitsi.impl.neomedia.transform.rtcp.*;
import org.jitsi.impl.neomedia.transform.zrtp.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.control.*;
import org.jitsi.service.neomedia.device.*;
import org.jitsi.service.neomedia.format.*;
import org.jitsi.util.*;
/**
* Implements <tt>MediaStream</tt> using JMF.
*
* @author Lyubomir Marinov
* @author Emil Ivov
* @author Sebastien Vincent
*/
public class MediaStreamImpl
extends AbstractMediaStream
implements ReceiveStreamListener,
SendStreamListener,
SessionListener,
RemoteListener
{
/**
* The <tt>Logger</tt> used by the <tt>MediaStreamImpl</tt> class and its
* instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(MediaStreamImpl.class);
/**
* The name of the property indicating the length of our receive buffer.
*/
protected static final String PROPERTY_NAME_RECEIVE_BUFFER_LENGTH

Lyubomir Marinov
committed
= "net.java.sip.communicator.impl.neomedia.RECEIVE_BUFFER_LENGTH";
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/**
* The session with the <tt>MediaDevice</tt> this instance uses for both
* capture and playback of media.
*/
private MediaDeviceSession deviceSession;
/**
* The <tt>PropertyChangeListener</tt> which listens to
* {@link #deviceSession} and changes in the values of its
* {@link MediaDeviceSession#OUTPUT_DATA_SOURCE} property.
*/
private final PropertyChangeListener deviceSessionPropertyChangeListener
= new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent event)
{
String propertyName = event.getPropertyName();
if (MediaDeviceSession.OUTPUT_DATA_SOURCE.equals(propertyName))
deviceSessionOutputDataSourceChanged();
else if (MediaDeviceSession.SSRC_LIST.equals(propertyName))
deviceSessionSsrcListChanged(event);
}
};
/**
* The <tt>MediaDirection</tt> in which this <tt>MediaStream</tt> is allowed
* to stream media.
*/
private MediaDirection direction;
/**
* The <tt>Map</tt> of associations in this <tt>MediaStream</tt> and the
* <tt>RTPManager</tt> it utilizes of (dynamic) RTP payload types to
* <tt>MediaFormat</tt>s.
*/
private final Map<Byte, MediaFormat> dynamicRTPPayloadTypes
= new HashMap<Byte, MediaFormat>();
/**
* The <tt>ReceiveStream</tt>s this instance plays back on its associated
* <tt>MediaDevice</tt>.
*/
private final List<ReceiveStream> receiveStreams
= new LinkedList<ReceiveStream>();
/**
* The <tt>RTPConnector</tt> through which this instance sends and receives
* RTP and RTCP traffic. The instance is a <tt>TransformConnector</tt> in
* order to also enable packet transformations.
*/
private AbstractRTPConnector rtpConnector;
/**
* The one and only <tt>MediaStreamTarget</tt> this instance has added as a
* target in {@link #rtpConnector}.
*/
private MediaStreamTarget rtpConnectorTarget;
/**
* The <tt>RTPManager</tt> which utilizes {@link #rtpConnector} and sends
* and receives RTP and RTCP traffic on behalf of this <tt>MediaStream</tt>.
*/
private StreamRTPManager rtpManager;
/**
* The <tt>RTPTranslator</tt>, if any, which forwards RTP and RTCP traffic
* between this and other <tt>MediaStream</tt>s.
*/
private RTPTranslator rtpTranslator;
/**
* The indicator which determines whether {@link #createSendStreams()} has
* been executed for {@link #rtpManager}. If <tt>true</tt>, the
* <tt>SendStream</tt>s have to be recreated when the <tt>MediaDevice</tt>,
* respectively the <tt>MediaDeviceSession</tt>, of this instance is
* changed.
*/
private boolean sendStreamsAreCreated = false;
/**
* The indicator which determines whether {@link #start()} has been called
* on this <tt>MediaStream</tt> without {@link #stop()} or {@link #close()}.
*/
private boolean started = false;
/**
* The <tt>MediaDirection</tt> in which this instance is started. For
* example, {@link MediaDirection#SENDRECV} if this instances is both
* sending and receiving data (e.g. RTP and RTCP) or
* {@link MediaDirection#SENDONLY} if this instance is only sending data.
*/
private MediaDirection startedDirection;
/**
* The SSRC identifiers of the party that we are exchanging media with.
*/
private final Vector<Long> remoteSourceIDs = new Vector<Long>(1, 1);
/**
* Our own SSRC identifier.
*/
private long localSourceID = -1;
/**
* The list of CSRC IDs contributing to the media that this
* <tt>MediaStream</tt> is sending to its remote party.
*/
private long[] localContributingSourceIDList = null;
/**
* The indicator which determines whether this <tt>MediaStream</tt> is set
* to transmit "silence" instead of the actual media fed from its
* <tt>MediaDevice</tt>.
*/
private boolean mute = false;
/**
* The map of currently active <tt>RTPExtension</tt>s and the IDs that they
* have been assigned for the lifetime of this <tt>MediaStream</tt>.
*/
private final Map<Byte, RTPExtension> activeRTPExtensions
= new Hashtable<Byte, RTPExtension>();
/**
* The engine that we are using in order to add CSRC lists in conference
* calls, send CSRC sound levels, and handle incoming levels and CSRC lists.
*/
private CsrcTransformEngine csrcEngine;
/**
* The <tt>SrtpControl</tt> which controls the SRTP functionality of this
* <tt>MediaStream</tt>.
*/
private final SrtpControl srtpControl;
/**
* Number of received sender reports. Used for logging and debugging only.
*/
private long numberOfReceivedSenderReports = 0;
/**
* The minimum inter arrival jitter value the other party has reported.
*/
private long maxRemoteInterArrivalJitter = 0;
/**
* The maximum inter arrival jitter value the other party has reported.
*/
private long minRemoteInterArrivalJitter = -1;
/**
* Engine chain reading sent RTCP sender reports and stores/prints
* statistics.
*/
private StatisticsEngine statisticsEngine = null;
/**
* The MediaStreamStatsImpl object used to compute the statistics about
* this MediaStreamImpl.
*/
private MediaStreamStatsImpl mediaStreamStatsImpl;
/**
* Initializes a new <tt>MediaStreamImpl</tt> instance which will use the
* specified <tt>MediaDevice</tt> for both capture and playback of media.
* The new instance will not have an associated <tt>StreamConnector</tt> and
* it must be set later for the new instance to be able to exchange media
* with a remote peer.
*
* @param device the <tt>MediaDevice</tt> the new instance is to use for
* both capture and playback of media
* @param srtpControl an existing control instance to control the SRTP
* operations
*/
public MediaStreamImpl(MediaDevice device, SrtpControl srtpControl)
{
this(null, device, srtpControl);
}
/**
* Initializes a new <tt>MediaStreamImpl</tt> instance which will use the
* specified <tt>MediaDevice</tt> for both capture and playback of media
* exchanged via the specified <tt>StreamConnector</tt>.
*
* @param connector the <tt>StreamConnector</tt> the new instance is to use
* for sending and receiving media or <tt>null</tt> if the
* <tt>StreamConnector</tt> of the new instance is to not be set at
* initialization time but specified later on
* @param device the <tt>MediaDevice</tt> the new instance is to use for
* both capture and playback of media exchanged via the specified
* <tt>StreamConnector</tt>
* @param srtpControl an existing control instance to control the ZRTP
* operations or <tt>null</tt> if a new control instance is to be created by
* the new <tt>MediaStreamImpl</tt>
*/
public MediaStreamImpl(
StreamConnector connector,
MediaDevice device,
SrtpControl srtpControl)
{

Lyubomir Marinov
committed
if (device != null)
{
/*
* XXX Set the device early in order to make sure that it is of the
* right type because we do not support just about any MediaDevice
* yet.
*/
setDevice(device);
}
// TODO Add option to disable ZRTP, e.g. by implementing a NullControl.
// If you change the default behavior (initiates a ZrtpControlImpl if
// the srtpControl attribute is null), please accordingly modify the
// CallPeerMediaHandler.initStream function.
this.srtpControl
= (srtpControl == null)

Lyubomir Marinov
committed
? NeomediaServiceUtils.getMediaServiceImpl()
.createZrtpControl()
: srtpControl;
if (connector != null)
setConnector(connector);
this.mediaStreamStatsImpl = new MediaStreamStatsImpl(this);

Lyubomir Marinov
committed
if (logger.isTraceEnabled())
{
logger.trace(
"Created "
+ getClass().getSimpleName()
+ " with hashCode "
+ hashCode());
}
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
}
/**
* Performs any optional configuration on a specific
* <tt>RTPConnectorOuputStream</tt> of an <tt>RTPManager</tt> to be used by
* this <tt>MediaStreamImpl</tt>. Allows extenders to override.
*
* @param dataOutputStream the <tt>RTPConnectorOutputStream</tt> to be used
* by an <tt>RTPManager</tt> of this <tt>MediaStreamImpl</tt> and to be
* configured
*/
protected void configureDataOutputStream(
RTPConnectorOutputStream dataOutputStream)
{
dataOutputStream.setPriority(getPriority());
}
/**
* Performs any optional configuration on a specific
* <tt>RTPConnectorInputStream</tt> of an <tt>RTPManager</tt> to be used by
* this <tt>MediaStreamImpl</tt>. Allows extenders to override.
*
* @param dataInputStream the <tt>RTPConnectorInputStream</tt> to be used
* by an <tt>RTPManager</tt> of this <tt>MediaStreamImpl</tt> and to be
* configured
*/
protected void configureDataInputStream(
RTPConnectorInputStream dataInputStream)
{
dataInputStream.setPriority(getPriority());
}
/**
* Performs any optional configuration on the <tt>BufferControl</tt> of the
* specified <tt>RTPManager</tt> which is to be used as the
* <tt>RTPManager</tt> of this <tt>MediaStreamImpl</tt>. Allows extenders to
* override.
*
* @param rtpManager the <tt>RTPManager</tt> which is to be used by this
* <tt>MediaStreamImpl</tt>
* @param bufferControl the <tt>BufferControl</tt> of <tt>rtpManager</tt> on
* which any optional configuration is to be performed
*/
protected void configureRTPManagerBufferControl(
StreamRTPManager rtpManager,
BufferControl bufferControl)
{
}
/**
* Creates a chain of transform engines for use with this stream. Note
* that this is the only place where the <tt>TransformEngineChain</tt> is
* and should be manipulated to avoid problems with the order of the
* transformers.
*
* @return the <tt>TransformEngineChain</tt> that this stream should be
* using.
*/
private TransformEngineChain createTransformEngineChain()
{
ArrayList<TransformEngine> engineChain
= new ArrayList<TransformEngine>(4);
// CSRCs and audio levels
if (csrcEngine == null)
csrcEngine = new CsrcTransformEngine(this);
engineChain.add(csrcEngine);
// DTMF
DtmfTransformEngine dtmfEngine = createDtmfTransformEngine();
if (dtmfEngine != null)
engineChain.add(dtmfEngine);
// RTCP Statistics
if (statisticsEngine == null)
statisticsEngine = new StatisticsEngine(this);
engineChain.add(statisticsEngine);
// SRTP
engineChain.add(srtpControl.getTransformEngine());
return
new TransformEngineChain(
engineChain.toArray(
new TransformEngine[engineChain.size()]));
}
/**
* A stub that allows audio oriented streams to create and keep a reference
* to a <tt>DtmfTransformEngine</tt>.
*
* @return a <tt>DtmfTransformEngine</tt> if this is an audio oriented
* stream and <tt>null</tt> otherwise.
*/
protected DtmfTransformEngine createDtmfTransformEngine()
{
return null;
}
/**
* Adds a new association in this <tt>MediaStream</tt> of the specified RTP
* payload type with the specified <tt>MediaFormat</tt> in order to allow it
* to report <tt>rtpPayloadType</tt> in RTP flows sending and receiving
* media in <tt>format</tt>. Usually, <tt>rtpPayloadType</tt> will be in the
* range of dynamic RTP payload types.
*
* @param rtpPayloadType the RTP payload type to be associated in this
* <tt>MediaStream</tt> with the specified <tt>MediaFormat</tt>
* @param format the <tt>MediaFormat</tt> to be associated in this
* <tt>MediaStream</tt> with <tt>rtpPayloadType</tt>
* @see MediaStream#addDynamicRTPPayloadType(byte, MediaFormat)
*/
public void addDynamicRTPPayloadType(
byte rtpPayloadType,
MediaFormat format)
{
@SuppressWarnings("unchecked")
MediaFormatImpl<? extends Format> mediaFormatImpl
= (MediaFormatImpl<? extends Format>) format;
synchronized (dynamicRTPPayloadTypes)
{
dynamicRTPPayloadTypes.put(Byte.valueOf(rtpPayloadType), format);
if (rtpManager != null)
rtpManager.addFormat(
mediaFormatImpl.getFormat(),
rtpPayloadType);
}
}
/**
* Maps or updates the mapping between <tt>extensionID</tt> and
* <tt>rtpExtension</tt>. If <tt>rtpExtension</tt>'s <tt>MediaDirection</tt>
* attribute is set to <tt>INACTIVE</tt> the mapping is removed from the
* local extensions table and the extension would not be transmitted or
* handled by this stream's <tt>RTPConnector</tt>.
*
* @param extensionID the ID that is being mapped to <tt>rtpExtension</tt>
* @param rtpExtension the <tt>RTPExtension</tt> that we are mapping.
*/
public void addRTPExtension(byte extensionID, RTPExtension rtpExtension)
{
synchronized (activeRTPExtensions)
{
if(rtpExtension.getDirection() == MediaDirection.INACTIVE)
activeRTPExtensions.remove(extensionID);
else
activeRTPExtensions.put(extensionID, rtpExtension);
}
}

Lyubomir Marinov
committed
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
/**
* Asserts that the state of this instance will remain consistent if a
* specific <tt>MediaDirection</tt> (i.e. <tt>direction</tt>) and a
* <tt>MediaDevice</tt> with a specific <tt>MediaDirection</tt> (i.e.
* <tt>deviceDirection</tt>) are both set on this instance.
*
* @param direction the <tt>MediaDirection</tt> to validate against the
* specified <tt>deviceDirection</tt>
* @param deviceDirection the <tt>MediaDirection</tt> of a
* <tt>MediaDevice</tt> to validate against the specified <tt>direction</tt>
* @param illegalArgumentExceptionMessage the message of the
* <tt>IllegalArgumentException</tt> to be thrown if the state of this
* instance would've been compromised if <tt>direction</tt> and the
* <tt>MediaDevice</tt> associated with <tt>deviceDirection</tt> were both
* set on this instance
* @throws IllegalArgumentException if the state of this instance would've
* been compromised were both <tt>direction</tt> and the
* <tt>MediaDevice</tt> associated with <tt>deviceDirection</tt> set on this
* instance
*/
private void assertDirection(
MediaDirection direction,
MediaDirection deviceDirection,
String illegalArgumentExceptionMessage)
throws IllegalArgumentException
{
if ((direction != null)
&& !direction.and(deviceDirection).equals(direction))
throw new IllegalArgumentException(illegalArgumentExceptionMessage);
}
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
/**
* Returns a map containing all currently active <tt>RTPExtension</tt>s in
* use by this stream.
*
* @return a map containing all currently active <tt>RTPExtension</tt>s in
* use by this stream.
*/
public Map<Byte, RTPExtension> getActiveRTPExtensions()
{
synchronized (activeRTPExtensions)
{
return new HashMap<Byte, RTPExtension>(activeRTPExtensions);
}
}
/**
* Returns the ID currently assigned to a specific RTP extension.
*
* @param rtpExtension the RTP extension to get the currently assigned ID of
* @return the ID currently assigned to the specified RTP extension or
* <tt>-1</tt> if no ID has been defined for this extension so far
*/
public byte getActiveRTPExtensionID(RTPExtension rtpExtension)
{
synchronized (activeRTPExtensions)
{
Set<Map.Entry<Byte, RTPExtension>> extSet
= this.activeRTPExtensions.entrySet();
for (Map.Entry<Byte, RTPExtension> entry : extSet)
{
if (entry.getValue().equals(rtpExtension))
return entry.getKey();
}
}
return -1;
}
/**
* Returns the engine that is responsible for adding the list of CSRC
* identifiers to outgoing RTP packets during a conference.
*
* @return the engine that is responsible for adding the list of CSRC
* identifiers to outgoing RTP packets during a conference.
*/
protected CsrcTransformEngine getCsrcEngine()
{
return csrcEngine;
}
/**
* Releases the resources allocated by this instance in the course of its
* execution and prepares it to be garbage collected.
*
* @see MediaStream#close()
*/
public void close()
{
stop();
closeSendStreams();
srtpControl.cleanup();
if (csrcEngine != null)
{
csrcEngine.close();
csrcEngine = null;
}
if (rtpManager != null)
{
if (logger.isInfoEnabled())
printFlowStatistics(rtpManager);
rtpManager.removeReceiveStreamListener(this);
rtpManager.removeSendStreamListener(this);
rtpManager.removeSessionListener(this);
rtpManager.removeRemoteListener(this);
try
{
rtpManager.dispose();
rtpManager = null;
}
catch (Throwable t)
{
if (t instanceof ThreadDeath)
throw (ThreadDeath) t;
/*
* Analysis of heap dumps and application logs suggests that
* RTPManager#dispose() may throw an exception after a
* NullPointerException has been thrown by SendStream#close() as
* documented in
* #stopSendStreams(Iterable<SendStream>, boolean). It is
* unknown at the time of this writing whether we can do
* anything to prevent the exception here but it is clear that,
* if we let it go through, we will not release at least one
* capture device (i.e. we will at least skip the
* MediaDeviceSession#close() bellow). For example, if the
* exception is thrown for the audio stream in a call, its
* capture device will not be released and any video stream will
* not get its #close() method called at all.
*/
logger.error("Failed to dispose of RTPManager", t);
}
}

Lyubomir Marinov
committed
/*
* XXX Call AbstractRTPConnector#removeTargets() after
* StreamRTPManager#dispose(). Otherwise, the latter will try to send an
* RTCP BYE and there will be no targets to send it to.
*/
if (rtpConnector != null)
rtpConnector.removeTargets();
rtpConnectorTarget = null;
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
if (deviceSession != null)
deviceSession.close();
}
/**
* Closes the <tt>SendStream</tt>s this instance is sending to its remote
* peer.
*/
private void closeSendStreams()
{
stopSendStreams(true);
}
/**
* Creates new <tt>SendStream</tt> instances for the streams of
* {@link #deviceSession} through {@link #rtpManager}.
*/
private void createSendStreams()
{
StreamRTPManager rtpManager = getRTPManager();
MediaDeviceSession deviceSession = getDeviceSession();
DataSource dataSource = deviceSession.getOutputDataSource();
int streamCount;
if (dataSource instanceof PushBufferDataSource)
{
PushBufferStream[] streams
= ((PushBufferDataSource) dataSource).getStreams();
streamCount = (streams == null) ? 0 : streams.length;
}
else if (dataSource instanceof PushDataSource)
{
PushSourceStream[] streams
= ((PushDataSource) dataSource).getStreams();
streamCount = (streams == null) ? 0 : streams.length;
}
else if (dataSource instanceof PullBufferDataSource)
{
PullBufferStream[] streams
= ((PullBufferDataSource) dataSource).getStreams();
streamCount = (streams == null) ? 0 : streams.length;
}
else if (dataSource instanceof PullDataSource)
{
PullSourceStream[] streams
= ((PullDataSource) dataSource).getStreams();
streamCount = (streams == null) ? 0 : streams.length;
}
else
streamCount = (dataSource == null) ? 0 : 1;
/*
* XXX We came up with a scenario in our testing in which G.722 would
* work fine for the first call since the start of the application and
* then it would fail for subsequent calls, JMF would complain that the
* G.722 RTP format is unknown to the RTPManager. Since
* RTPManager#createSendStream(DataSource, int) is one of the cases in
* which the formats registered with the RTPManager are necessary,
* register them (again) just before we use them.
*/
registerCustomCodecFormats(rtpManager);
for (int streamIndex = 0; streamIndex < streamCount; streamIndex++)
{
try
{
SendStream sendStream
= rtpManager.createSendStream(dataSource, streamIndex);
if (logger.isTraceEnabled())
logger
.trace(
"Created SendStream"
+ " with hashCode "
+ sendStream.hashCode()
+ " for "
+ toString(dataSource)
+ " and streamIndex "
+ streamIndex
+ " in RTPManager with hashCode "
+ rtpManager.hashCode());
long localSSRC = sendStream.getSSRC();
if (getLocalSourceID() != localSSRC)
setLocalSourceID(localSSRC);
}
catch (IOException ioe)
{
logger
.error(
"Failed to create send stream for data source "
+ dataSource
+ " and stream index "
+ streamIndex,
ioe);
}
catch (UnsupportedFormatException ufe)
{
logger
.error(
"Failed to create send stream for data source "
+ dataSource
+ " and stream index "
+ streamIndex
+ " because of failed format "
+ ufe.getFailedFormat(),
ufe);
}
}
sendStreamsAreCreated = true;
if (logger.isTraceEnabled())
{
@SuppressWarnings("unchecked")
Vector<SendStream> sendStreams = rtpManager.getSendStreams();
int sendStreamCount
= (sendStreams == null) ? 0 : sendStreams.size();
logger
.trace(
"Total number of SendStreams in RTPManager with hashCode "
+ rtpManager.hashCode()
+ " is "
+ sendStreamCount);
}
}
/**
* Notifies this <tt>MediaStream</tt> that the <tt>MediaDevice</tt> (and
* respectively the <tt>MediaDeviceSession</tt> with it) which this instance
* uses for capture and playback of media has been changed. Allows extenders
* to override and provide additional processing of <tt>oldValue</tt> and
* <tt>newValue</tt>.
*
* @param oldValue the <tt>MediaDeviceSession</tt> with the
* <tt>MediaDevice</tt> this instance used work with
* @param newValue the <tt>MediaDeviceSession</tt> with the
* <tt>MediaDevice</tt> this instance is to work with
*/
protected void deviceSessionChanged(
MediaDeviceSession oldValue,
MediaDeviceSession newValue)
{
recreateSendStreams();
}
/**
* Notifies this instance that the output <tt>DataSource</tt> of its
* <tt>MediaDeviceSession</tt> has changed. Recreates the
* <tt>SendStream</tt>s of this instance as necessary so that it, for
* example, continues streaming after the change if it was streaming before
* the change.
*/
private void deviceSessionOutputDataSourceChanged()
{
recreateSendStreams();
}
/**
* Recalculates the list of CSRC identifiers that this <tt>MediaStream</tt>
* needs to include in RTP packets bound to its interlocutor. The method
* uses the list of SSRC identifiers currently handled by our device
* (possibly a mixer), then removes the SSRC ID of this stream's
* interlocutor. If this turns out to be the only SSRC currently in the list
* we set the list of local CSRC identifiers to null since this is obviously
* a non-conf call and we don't need to be advertising CSRC lists. If that's
* not the case, we also add our own SSRC to the list of IDs and cache the
* entire list.
*
* @param evt the <tt>PropetyChangeEvent</tt> containing the list of SSRC
* identifiers handled by our device session before and after it changed.
*/
private void deviceSessionSsrcListChanged(PropertyChangeEvent evt)
{
long[] ssrcArray = (long[])evt.getNewValue();
// the list is empty
if(ssrcArray == null)
{
this.localContributingSourceIDList = null;
return;
}
int elementsToRemove = 0;
Vector<Long> remoteSourceIDs = this.remoteSourceIDs;
//in case of a conf call the mixer would return all SSRC IDs that are
//currently contributing including this stream's counterpart. We need
//to remove that last one since that's where we will be sending our
//csrc list
for(long csrc : ssrcArray)
if (remoteSourceIDs.contains(csrc))
elementsToRemove ++;
//we don't seem to be in a conf call since the list only contains the
//SSRC id of the party that we are directly interacting with.
if (elementsToRemove >= ssrcArray.length)
{
this.localContributingSourceIDList = null;
return;
}
//prepare the new array. make it big enough to also add the local
//SSRC id but do not make it bigger than 15 since that's the maximum
//for RTP.
int cc = Math.min(ssrcArray.length - elementsToRemove + 1, 15);
long[] csrcArray = new long[cc];
for (int i = 0,j = 0;
(i < ssrcArray.length) && (j < csrcArray.length - 1);
i++)
{
long ssrc = ssrcArray[i];
if (!remoteSourceIDs.contains(ssrc))
{
csrcArray[j] = ssrc;
j++;
}
}
csrcArray[csrcArray.length - 1] = getLocalSourceID();
this.localContributingSourceIDList = csrcArray;
}
/**
* Sets the target of this <tt>MediaStream</tt> to which it is to send and
* from which it is to receive data (e.g. RTP) and control data (e.g. RTCP).
* In contrast to {@link #setTarget(MediaStreamTarget)}, sets the specified
* <tt>target</tt> on this <tt>MediaStreamImpl</tt> even if its current
* <tt>target</tt> is equal to the specified one.
*
* @param target the <tt>MediaStreamTarget</tt> describing the data
* (e.g. RTP) and the control data (e.g. RTCP) locations to which this
* <tt>MediaStream</tt> is to send and from which it is to receive
* @see MediaStreamImpl#setTarget(MediaStreamTarget)
*/
private void doSetTarget(MediaStreamTarget target)
{

Lyubomir Marinov
committed
InetSocketAddress newDataAddr;
InetSocketAddress newControlAddr;

Lyubomir Marinov
committed
if (target == null)
{
newDataAddr = null;
newControlAddr = null;
}
else
{
newDataAddr = target.getDataAddress();
newControlAddr = target.getControlAddress();
}

Lyubomir Marinov
committed
/*
* Invoke AbstractRTPConnector#removeTargets() if the new value does
* actually remove an RTP or RTCP target in comparison to the old value.
* If the new value is equal to the oldValue or adds an RTP or RTCP
* target (i.e. the old value does not specify the respective RTP or
* RTCP target and the new value does), then removeTargets is
* unnecessary and would've needlessly allowed a (tiny) interval of
* (execution) time (between removeTargets and addTarget) without a
* target.
*/
if (rtpConnectorTarget != null)

Lyubomir Marinov
committed
InetSocketAddress oldDataAddr = rtpConnectorTarget.getDataAddress();
boolean removeTargets
= (oldDataAddr == null)
? (newDataAddr != null)
: !oldDataAddr.equals(newDataAddr);
if (!removeTargets)
{
InetSocketAddress oldControlAddr
= rtpConnectorTarget.getControlAddress();
removeTargets
= (oldControlAddr == null)
? (newControlAddr != null)
: !oldControlAddr.equals(newControlAddr);
}
if (removeTargets)
{
rtpConnector.removeTargets();
rtpConnectorTarget = null;
}
}
boolean targetIsSet;

Lyubomir Marinov
committed
if (target == null)
targetIsSet = true;
else
{

Lyubomir Marinov
committed
InetAddress controlInetAddr;
int controlPort;
if (newControlAddr == null)
{
controlInetAddr = null;
controlPort = 0;
}
else
{
controlInetAddr = newControlAddr.getAddress();
controlPort = newControlAddr.getPort();
}
rtpConnector.addTarget(

Lyubomir Marinov
committed
newDataAddr.getAddress(), newDataAddr.getPort(),
controlInetAddr, controlPort));
targetIsSet = true;
}
catch (IOException ioe)
{
// TODO
targetIsSet = false;
logger.error("Failed to set target " + target, ioe);
}
}
if (targetIsSet)
{
rtpConnectorTarget = target;
if (logger.isTraceEnabled())

Lyubomir Marinov
committed
{
logger.trace(
"Set target of " + getClass().getSimpleName()
+ " with hashCode " + hashCode()
+ " to " + target);
}
}
}
/**
* Gets the <tt>MediaDevice</tt> that this stream uses to play back and
* capture media.
*
* @return the <tt>MediaDevice</tt> that this stream uses to play back and
* capture media
* @see MediaStream#getDevice()
*/
public AbstractMediaDevice getDevice()
{
return getDeviceSession().getDevice();
}

Lyubomir Marinov
committed
/**
* Gets the <tt>MediaDirection</tt> of the <tt>device</tt> of this instance
* if any or {@link MediaDirection#INACTIVE}.
*
* @return the <tt>MediaDirection</tt> of the <tt>device</tt> of this
* instance if any or <tt>MediaDirection.INACTIVE</tt>
*/
private MediaDirection getDeviceDirection()
{
MediaDeviceSession deviceSession = getDeviceSession();
return
(deviceSession == null)
? MediaDirection.INACTIVE
: deviceSession.getDevice().getDirection();
}
/**
* Gets the <tt>MediaDeviceSession</tt> which represents the work of this
* <tt>MediaStream</tt> with its associated <tt>MediaDevice</tt>.
*
* @return the <tt>MediaDeviceSession</tt> which represents the work of this
* <tt>MediaStream</tt> with its associated <tt>MediaDevice</tt>
*/
public MediaDeviceSession getDeviceSession()
{
return deviceSession;
}
/**
* Gets the direction in which this <tt>MediaStream</tt> is allowed to
* stream media.
*
* @return the <tt>MediaDirection</tt> in which this <tt>MediaStream</tt> is
* allowed to stream media
* @see MediaStream#getDirection()
*/
public MediaDirection getDirection()
{

Lyubomir Marinov
committed
return (direction == null) ? getDeviceDirection() : direction;
}
/**
* Gets the existing associations in this <tt>MediaStream</tt> of RTP
* payload types to <tt>MediaFormat</tt>s. The returned <tt>Map</tt>
* only contains associations previously added in this instance with