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.awt.*;

Lyubomir Marinov
committed
import java.net.*;
import java.util.*;
import java.util.List;
import java.util.regex.*;
import javax.media.*;
import javax.media.control.*;
import javax.media.format.*;
import javax.media.protocol.*;
import org.jitsi.impl.neomedia.control.*;
import org.jitsi.impl.neomedia.device.*;

Lyubomir Marinov
committed
import org.jitsi.impl.neomedia.rtp.*;

Lyubomir Marinov
committed
import org.jitsi.impl.neomedia.rtp.remotebitrateestimator.*;
import org.jitsi.impl.neomedia.rtp.translator.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.QualityControl;
import org.jitsi.service.neomedia.control.*;
import org.jitsi.service.neomedia.control.KeyFrameControl;
import org.jitsi.service.neomedia.device.*;
import org.jitsi.service.neomedia.format.*;

Lyubomir Marinov
committed
import org.jitsi.service.neomedia.rtp.*;
import org.jitsi.util.*;
import org.jitsi.util.event.*;
/**
* Extends <tt>MediaStreamImpl</tt> in order to provide an implementation of
* <tt>VideoMediaStream</tt>.
*
* @author Lyubomir Marinov
* @author Sebastien Vincent
*/
public class VideoMediaStreamImpl
extends MediaStreamImpl
implements VideoMediaStream
{
/**
* The <tt>Logger</tt> used by the <tt>VideoMediaStreamImpl</tt> class and
* its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(VideoMediaStreamImpl.class);
/**
* The <tt>RecurringProcessibleExecutor</tt> to be utilized by the
* <tt>VideoMediaStreamImpl</tt> class and its instances.
*/
private static final RecurringProcessibleExecutor
recurringProcessibleExecutor
= new RecurringProcessibleExecutor();
/**
* The indicator which determines whether RTCP feedback Picture Loss
* Indication messages are to be used.
*/

Lyubomir Marinov
committed
private static final boolean USE_RTCP_FEEDBACK_PLI = true;

Lyubomir Marinov
committed
* Extracts and returns maximum resolution can receive from the image
* attribute.
*
* @param imgattr send/recv resolution string
* @return maximum resolution array (first element is send, second one is
* recv). Elements could be null if image attribute is not present or if
* resolution is a wildcard.

Lyubomir Marinov
committed
public static java.awt.Dimension[] parseSendRecvResolution(String imgattr)
{
java.awt.Dimension res[] = new java.awt.Dimension[2];
String token = null;
Pattern pSendSingle = Pattern.compile("send \\[x=[0-9]+,y=[0-9]+\\]");
Pattern pRecvSingle = Pattern.compile("recv \\[x=[0-9]+,y=[0-9]+\\]");
Pattern pSendRange = Pattern.compile(
"send \\[x=\\[[0-9]+-[0-9]+\\],y=\\[[0-9]+-[0-9]+\\]\\]");
Pattern pRecvRange = Pattern.compile(
"recv \\[x=\\[[0-9]+-[0-9]+\\],y=\\[[0-9]+-[0-9]+\\]\\]");
Pattern pNumeric = Pattern.compile("[0-9]+");
Matcher mSingle = null;
Matcher mRange = null;
Matcher m = null;

Lyubomir Marinov
committed
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
/* resolution (width and height) can be on four forms
*
* - single value [x=1920,y=1200]
* - range of values [x=[800-1024],y=[600-768]]
* - fixed range of values [x=[800,1024],y=[600,768]]
* - range of values with step [x=[800:32:1024],y=[600:32:768]]
*
* For the moment we only support the first two forms.
*/
/* send part */
mSingle = pSendSingle.matcher(imgattr);
mRange = pSendRange.matcher(imgattr);
if(mSingle.find())
{
int val[] = new int[2];
int i = 0;
token = imgattr.substring(mSingle.start(), mSingle.end());
m = pNumeric.matcher(token);
while(m.find() && i < 2)
{
val[i] = Integer.parseInt(token.substring(m.start(), m.end()));
}
res[0] = new java.awt.Dimension(val[0], val[1]);
}
else if(mRange.find()) /* try with range */
{
/* have two value for width and two for height (min-max) */
int val[] = new int[4];
int i = 0;
token = imgattr.substring(mRange.start(), mRange.end());
m = pNumeric.matcher(token);
while(m.find() && i < 4)
{
val[i] = Integer.parseInt(token.substring(m.start(), m.end()));
i++;
}
res[0] = new java.awt.Dimension(val[1], val[3]);
}
/* recv part */
mSingle = pRecvSingle.matcher(imgattr);
mRange = pRecvRange.matcher(imgattr);
if(mSingle.find())
{
int val[] = new int[2];
int i = 0;
token = imgattr.substring(mSingle.start(), mSingle.end());
m = pNumeric.matcher(token);
while(m.find() && i < 2)
{
val[i] = Integer.parseInt(token.substring(m.start(), m.end()));
}
res[1] = new java.awt.Dimension(val[0], val[1]);
}
else if(mRange.find()) /* try with range */
{
/* have two value for width and two for height (min-max) */
int val[] = new int[4];
int i = 0;
token = imgattr.substring(mRange.start(), mRange.end());
m = pNumeric.matcher(token);
while(m.find() && i < 4)
{
val[i] = Integer.parseInt(token.substring(m.start(), m.end()));
i++;
}
res[1] = new java.awt.Dimension(val[1], val[3]);
}
return res;
}
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
/**
* Selects the <tt>VideoFormat</tt> from the list of supported formats of a
* specific video <tt>DataSource</tt> which has a size as close as possible
* to a specific size and sets it as the format of the specified video
* <tt>DataSource</tt>.
*
* @param videoDS the video <tt>DataSource</tt> which is to have its
* supported formats examined and its format changed to the
* <tt>VideoFormat</tt> which is as close as possible to the specified
* <tt>preferredWidth</tt> and <tt>preferredHeight</tt>
* @param preferredWidth the width of the <tt>VideoFormat</tt> to be
* selected
* @param preferredHeight the height of the <tt>VideoFormat</tt> to be
* selected
* @return the size of the <tt>VideoFormat</tt> from the list of supported
* formats of <tt>videoDS</tt> which is as close as possible to
* <tt>preferredWidth</tt> and <tt>preferredHeight</tt> and which has been
* set as the format of <tt>videoDS</tt>
*/
public static Dimension selectVideoSize(
DataSource videoDS,
final int preferredWidth, final int preferredHeight)
{
if (videoDS == null)
return null;
FormatControl formatControl
= (FormatControl) videoDS.getControl(FormatControl.class.getName());
if (formatControl == null)
return null;
Format[] formats = formatControl.getSupportedFormats();
final int count = formats.length;
if (count < 1)
return null;
VideoFormat selectedFormat = null;
if (count == 1)
selectedFormat = (VideoFormat) formats[0];
else
{
class FormatInfo
{
public final double difference;
public final Dimension dimension;

Lyubomir Marinov
committed
public final VideoFormat format;
public FormatInfo(Dimension size)

Lyubomir Marinov
committed
this.format = null;

Lyubomir Marinov
committed
this.dimension = size;
this.difference = getDifference(this.dimension);
}

Lyubomir Marinov
committed
public FormatInfo(VideoFormat format)

Lyubomir Marinov
committed
this.format = format;

Lyubomir Marinov
committed
this.dimension = format.getSize();
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
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
this.difference = getDifference(this.dimension);
}
private double getDifference(Dimension size)
{
int width = (size == null) ? 0 : size.width;
double xScale;
if (width == 0)
xScale = Double.POSITIVE_INFINITY;
else if (width == preferredWidth)
xScale = 1;
else
xScale = (preferredWidth / (double) width);
int height = (size == null) ? 0 : size.height;
double yScale;
if (height == 0)
yScale = Double.POSITIVE_INFINITY;
else if (height == preferredHeight)
yScale = 1;
else
yScale = (preferredHeight / (double) height);
return Math.abs(1 - Math.min(xScale, yScale));
}
}
FormatInfo[] infos = new FormatInfo[count];
for (int i = 0; i < count; i++)
{
FormatInfo info
= infos[i]
= new FormatInfo((VideoFormat) formats[i]);
if (info.difference == 0)
{
selectedFormat = info.format;
break;
}
}
if (selectedFormat == null)
{
Arrays.sort(infos, new Comparator<FormatInfo>()
{
public int compare(FormatInfo info0, FormatInfo info1)
{
return
Double.compare(info0.difference, info1.difference);
}
});
selectedFormat = infos[0].format;
}
/*
* If videoDS states to support any size, use the sizes that we
* support which is closest(or smaller) to the preferred one.
*/
if ((selectedFormat != null)
&& (selectedFormat.getSize() == null))
{
VideoFormat currentFormat
= (VideoFormat) formatControl.getFormat();
Dimension currentSize = null;
int width = preferredWidth;
int height = preferredHeight;
// Try to preserve the aspect ratio
if (currentFormat != null)
currentSize = currentFormat.getSize();
// sort supported resolutions by aspect
FormatInfo[] supportedInfos
= new FormatInfo[
DeviceConfiguration.SUPPORTED_RESOLUTIONS.length];
for (int i = 0; i < supportedInfos.length; i++)
{
supportedInfos[i]
= new FormatInfo(
DeviceConfiguration.SUPPORTED_RESOLUTIONS[i]);
}
Arrays.sort(infos, new Comparator<FormatInfo>()
{
public int compare(FormatInfo info0, FormatInfo info1)
{
return
Double.compare(info0.difference, info1.difference);
}
});

Lyubomir Marinov
committed
FormatInfo preferredFormat
= new FormatInfo(
new Dimension(preferredWidth, preferredHeight));
Dimension closestAspect = null;

Lyubomir Marinov
committed
// Let's choose the closest size to the preferred one, finding
// the first suitable aspect
for(FormatInfo supported : supportedInfos)
{
// find the first matching aspect
if(preferredFormat.difference > supported.difference)
continue;
else if(closestAspect == null)
closestAspect = supported.dimension;
if(supported.dimension.height <= preferredHeight
&& supported.dimension.width <= preferredWidth)
{
currentSize = supported.dimension;
}
}
if(currentSize == null)
currentSize = closestAspect;
if ((currentSize.width > 0) && (currentSize.height > 0))
{
width = currentSize.width;
height = currentSize.height;
}
selectedFormat

Lyubomir Marinov
committed
= (VideoFormat)
new VideoFormat(
null,
new Dimension(width, height),
Format.NOT_SPECIFIED,
null,
Format.NOT_SPECIFIED)
.intersects(selectedFormat);
}
}
Format setFormat = formatControl.setFormat(selectedFormat);
return
(setFormat instanceof VideoFormat)
? ((VideoFormat) setFormat).getSize()
: null;
}
/**
* The <tt>VideoListener</tt> which handles <tt>VideoEvent</tt>s from the
* <tt>MediaDeviceSession</tt> of this instance and fires respective
* <tt>VideoEvent</tt>s from this <tt>VideoMediaStream</tt> to its
* <tt>VideoListener</tt>s.
*/
private VideoListener deviceSessionVideoListener;

Lyubomir Marinov
committed
/**
* The <tt>KeyFrameControl</tt> of this <tt>VideoMediaStream</tt>.
*/
private KeyFrameControl keyFrameControl;
/**
* Negotiated output size of the video stream.
* It may need to scale original capture device stream.
*/
private Dimension outputSize;
/**
* The <tt>QualityControl</tt> of this <tt>VideoMediaStream</tt>.
*/
private final QualityControlImpl qualityControl = new QualityControlImpl();

Lyubomir Marinov
committed
/**
* The <tt>RemoteBitrateEstimator</tt> which computes bitrate estimates for
* the incoming RTP streams.
*/
private final RemoteBitrateEstimator remoteBitrateEstimator
= new RemoteBitrateEstimatorSingleStream(
new RemoteBitrateObserver()
{
@Override
public void onReceiveBitrateChanged(
Collection<Integer> ssrcs,
long bitrate)
{
VideoMediaStreamImpl.this
.remoteBitrateEstimatorOnReceiveBitrateChanged(
ssrcs,
bitrate);
}
},
/* minBitrateBps*/ 0L);
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
/**
* The facility which aids this instance in managing a list of
* <tt>VideoListener</tt>s and firing <tt>VideoEvent</tt>s to them.
* <p>
* Since the <tt>videoNotifierSupport</tt> of this
* <tt>VideoMediaStreamImpl</tt> just forwards the <tt>VideoEvent</tt>s of
* the associated <tt>VideoMediaDeviceSession</tt> at the time of this
* writing, it does not make sense to have <tt>videoNotifierSupport</tt>
* executing asynchronously because it does not know whether it has to wait
* for the delivery of the <tt>VideoEvent</tt>s and thus it has to default
* to waiting anyway.
* </p>
*/
private final VideoNotifierSupport videoNotifierSupport
= new VideoNotifierSupport(this, true);
/**
* Initializes a new <tt>VideoMediaStreamImpl</tt> instance which will use
* the specified <tt>MediaDevice</tt> for both capture and playback of video
* exchanged via the specified <tt>StreamConnector</tt>.
*
* @param connector the <tt>StreamConnector</tt> the new instance is to use
* for sending and receiving video
* @param device the <tt>MediaDevice</tt> the new instance is to use for
* both capture and playback of video exchanged via the specified
* <tt>StreamConnector</tt>
* @param srtpControl a control which is already created, used to control
* the srtp operations.
*/
public VideoMediaStreamImpl(StreamConnector connector, MediaDevice device,
SrtpControl srtpControl)
{
super(connector, device, srtpControl);
// Register the RemoteBitrateEstimator with the
// RecurringProcessibleExecutor.
RemoteBitrateEstimator remoteBitrateEstimator
= getRemoteBitrateEstimator();
if (remoteBitrateEstimator instanceof RecurringProcessible)
{
recurringProcessibleExecutor.registerRecurringProcessible(
(RecurringProcessible) remoteBitrateEstimator);
}

Lyubomir Marinov
committed
/**
* Set remote SSRC.
*
* @param ssrc remote SSRC
*/
@Override
protected void addRemoteSourceID(long ssrc)
{
super.addRemoteSourceID(ssrc);
MediaDeviceSession deviceSession = getDeviceSession();
if (deviceSession instanceof VideoMediaDeviceSession)
((VideoMediaDeviceSession) deviceSession).setRemoteSSRC(ssrc);
}
/**
* Adds a specific <tt>VideoListener</tt> to this <tt>VideoMediaStream</tt>
* in order to receive notifications when visual/video <tt>Component</tt>s
* are being added and removed.
* <p>
* Adding a listener which has already been added does nothing i.e. it is
* not added more than once and thus does not receive one and the same
* <tt>VideoEvent</tt> multiple times.
* </p>
*
* @param listener the <tt>VideoListener</tt> to be notified when
* visual/video <tt>Component</tt>s are being added or removed in this
* <tt>VideoMediaStream</tt>
*/
public void addVideoListener(VideoListener listener)
{
videoNotifierSupport.addVideoListener(listener);
}
/**
* {@inheritDoc}
*/
@Override
public void close()
{
try
{
super.close();
}
finally
{
// Deregister the RemoteBitrateEstimator with the
// RecurringProcessibleExecutor.
RemoteBitrateEstimator remoteBitrateEstimator
= getRemoteBitrateEstimator();
if (remoteBitrateEstimator instanceof RecurringProcessible)
{
recurringProcessibleExecutor.deRegisterRecurringProcessible(
(RecurringProcessible) remoteBitrateEstimator);
}
}
}

Lyubomir Marinov
committed
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
/**
* {@inheritDoc}
*/
@Override
protected void configureDataInputStream(
RTPConnectorInputStream<?> dataInputStream)
{
super.configureDataInputStream(dataInputStream);
/*
* Start listening to the receipt of data/RTP packets in order to notify
* remoteBitrateEstimator.
*/
dataInputStream.addDatagramPacketListener(
new DatagramPacketListener()
{
@Override
public void update(Object source, DatagramPacket p)
{
VideoMediaStreamImpl.this
.dataInputStreamDatagramPacketListenerUpdate(
source,
p);
}
});
}
/**
* Performs any optional configuration on a specific
* <tt>RTPConnectorOuputStream</tt> of an <tt>RTPManager</tt> to be used by
* this <tt>MediaStreamImpl</tt>.
*
* @param dataOutputStream the <tt>RTPConnectorOutputStream</tt> to be used
* by an <tt>RTPManager</tt> of this <tt>MediaStreamImpl</tt> and to be
* configured
*/
@Override
protected void configureDataOutputStream(
RTPConnectorOutputStream dataOutputStream)
{
super.configureDataOutputStream(dataOutputStream);
/*
* XXX Android's current video CaptureDevice is based on MediaRecorder
* which gives no control over the number and the size of the packets,
* frame dropping is not implemented because it is hard since
* MediaRecorder generates encoded video.
*/
if (!OSUtils.IS_ANDROID)
{
int maxBandwidth

Lyubomir Marinov
committed
= NeomediaServiceUtils
.getMediaServiceImpl()
.getDeviceConfiguration()

Boris Grozev
committed
.getVideoRTPPacingThreshold();
// Ignore the case of maxBandwidth > 1000, because in this case
// setMaxPacketsPerMillis fails. Effectively, this means that no
// pacing is performed when the user deliberately set the setting to
// over 1000 (1MByte/s according to the GUI). This is probably close
// to what the user expects, and makes more sense than failing with
// an exception.
// TODO: proper handling of maxBandwidth values >1000
if (maxBandwidth <= 1000)
{
// maximum one packet for X milliseconds(the settings are for
// one second)
dataOutputStream.setMaxPacketsPerMillis(1, 1000 / maxBandwidth);
}
}
}
/**
* 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>.
*
* @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
*/
@Override
protected void configureRTPManagerBufferControl(
StreamRTPManager rtpManager,
BufferControl bufferControl)
{
super.configureRTPManagerBufferControl(rtpManager, bufferControl);
bufferControl.setBufferLength(BufferControl.MAX_VALUE);
}

Lyubomir Marinov
committed
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
/**
* Notifies this <tt>VideoMediaStreamImpl</tt> that a
* <tt>DatagramPacket</tt> was received by the data/RTP input stream of this
* <tt>MediaStreamImpl</tt>.
*
* @param source the source of the event
* @param p the <tt>DatagramPacket</tt> received by the data/RTP input
* stream of this <tt>MediaStreamImpl</tt>
*/
private void dataInputStreamDatagramPacketListenerUpdate(
Object source,
DatagramPacket p)
{
RemoteBitrateEstimator remoteBitrateEstimator
= getRemoteBitrateEstimator();
if (remoteBitrateEstimator != null)
{
// Do the bytes in p resemble (a header of) an RTP packet?
byte[] buf = p.getData();
int off = p.getOffset();
long payloadLenAndOff
= RTPTranslatorImpl.getPayloadLengthAndOffsetIfRTP(
buf,
off,
p.getLength());
if (payloadLenAndOff >= 0)
{
int payloadLen = (int) (payloadLenAndOff >>> 32);
int payloadOff = (int) payloadLenAndOff;
if (payloadOff >= 0)
{
long arrivalTimeMs = System.currentTimeMillis();
long timestamp
= RTPTranslatorImpl.readInt(buf, off + 4) & 0xFFFFFFFFL;
int ssrc = RTPTranslatorImpl.readInt(buf, off + 8);
remoteBitrateEstimator.incomingPacket(
arrivalTimeMs,
payloadLen,
ssrc,
timestamp);
}
}
}
}
/**
* 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. Makes sure that
* the <tt>VideoListener</tt>s of this instance get <tt>VideoEvent</tt>s for
* the new/current <tt>VideoMediaDeviceSession</tt> and not for the old one.
*

Boris Grozev
committed
* Note: this overloaded method gets executed in the
* <tt>MediaStreamImpl</tt> constructor. As a consequence we cannot assume
* proper initialization of the fields specific to
* <tt>VideoMediaStreamImpl</tt>.
*
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
* @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
* @see MediaStreamImpl#deviceSessionChanged(MediaDeviceSession,
* MediaDeviceSession)
*/
@Override
protected void deviceSessionChanged(
MediaDeviceSession oldValue,
MediaDeviceSession newValue)
{
super.deviceSessionChanged(oldValue, newValue);
if (oldValue instanceof VideoMediaDeviceSession)
{
VideoMediaDeviceSession oldVideoMediaDeviceSession
= (VideoMediaDeviceSession) oldValue;
if (deviceSessionVideoListener != null)
oldVideoMediaDeviceSession.removeVideoListener(
deviceSessionVideoListener);
/*
* The oldVideoMediaDeviceSession is being disconnected from this
* VideoMediaStreamImpl so do not let it continue using its
* keyFrameControl.
*/
oldVideoMediaDeviceSession.setKeyFrameControl(null);
}
if (newValue instanceof VideoMediaDeviceSession)
{
VideoMediaDeviceSession newVideoMediaDeviceSession
= (VideoMediaDeviceSession) newValue;
if (deviceSessionVideoListener == null)
{
deviceSessionVideoListener = new VideoListener()
{
/**

Lyubomir Marinov
committed
* {@inheritDoc}

Lyubomir Marinov
committed
* Notifies that a visual <tt>Component</tt> depicting video
* was reported added by the provider this listener is added
* to.
*/
public void videoAdded(VideoEvent e)
{
if (fireVideoEvent(
e.getType(),
e.getVisualComponent(),
e.getOrigin(),
true))
e.consume();
}
/**

Lyubomir Marinov
committed
* {@inheritDoc}

Lyubomir Marinov
committed
* Notifies that a visual <tt>Component</tt> depicting video
* was reported removed by the provider this listener is
* added to.
*/
public void videoRemoved(VideoEvent e)
{
videoAdded(e);
}

Lyubomir Marinov
committed
/**
* {@inheritDoc}
*
* Notifies that a visual <tt>Component</tt> depicting video
* was reported updated by the provider this listener is
* added to.
*/
public void videoUpdate(VideoEvent e)
{
fireVideoEvent(e, true);
}
};
}
newVideoMediaDeviceSession.addVideoListener(
deviceSessionVideoListener);
newVideoMediaDeviceSession.setOutputSize(outputSize);
AbstractRTPConnector rtpConnector = getRTPConnector();
if (rtpConnector != null)
newVideoMediaDeviceSession.setConnector(rtpConnector);

Lyubomir Marinov
committed
newVideoMediaDeviceSession.setRTCPFeedbackPLI(
USE_RTCP_FEEDBACK_PLI);
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
843
844
/*
* The newVideoMediaDeviceSession is being connected to this
* VideoMediaStreamImpl so the key frame-related logic will be
* controlled by the keyFrameControl of this VideoMediaStreamImpl.
*/
newVideoMediaDeviceSession.setKeyFrameControl(getKeyFrameControl());
}
}
/**
* Notifies the <tt>VideoListener</tt>s registered with this
* <tt>VideoMediaStream</tt> about a specific type of change in the
* availability of a specific visual <tt>Component</tt> depicting video.
*
* @param type the type of change as defined by <tt>VideoEvent</tt> in the
* availability of the specified visual <tt>Component</tt> depicting video
* @param visualComponent the visual <tt>Component</tt> depicting video
* which has been added or removed in this <tt>VideoMediaStream</tt>
* @param origin {@link VideoEvent#LOCAL} if the origin of the video is
* local (e.g. it is being locally captured); {@link VideoEvent#REMOTE} if
* the origin of the video is remote (e.g. a remote peer is streaming it)
* @param wait <tt>true</tt> if the call is to wait till the specified
* <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s;
* otherwise, <tt>false</tt>
* @return <tt>true</tt> if this event and, more specifically, the visual
* <tt>Component</tt> it describes have been consumed and should be
* considered owned, referenced (which is important because
* <tt>Component</tt>s belong to a single <tt>Container</tt> at a time);
* otherwise, <tt>false</tt>
*/
protected boolean fireVideoEvent(
int type, Component visualComponent, int origin,
boolean wait)
{
if (logger.isTraceEnabled())
logger
.trace(
"Firing VideoEvent with type "
+ VideoEvent.typeToString(type)
+ " and origin "
+ VideoEvent.originToString(origin));
return
videoNotifierSupport.fireVideoEvent(
type, visualComponent, origin,
wait);
}
/**
* Notifies the <tt>VideoListener</tt>s registered with this instance about
* a specific <tt>VideoEvent</tt>.
*
* @param event the <tt>VideoEvent</tt> to be fired to the
* <tt>VideoListener</tt>s registered with this instance
* @param wait <tt>true</tt> if the call is to wait till the specified
* <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s;
* otherwise, <tt>false</tt>
*/
protected void fireVideoEvent(VideoEvent event, boolean wait)
{
videoNotifierSupport.fireVideoEvent(event, wait);
}

Lyubomir Marinov
committed
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
/**
* Implements {@link VideoMediaStream#getKeyFrameControl()}.
*
* {@inheritDoc}
* @see VideoMediaStream#getKeyFrameControl()
*/
public KeyFrameControl getKeyFrameControl()
{
if (keyFrameControl == null)
keyFrameControl = new KeyFrameControlAdapter();
return keyFrameControl;
}
/**
* Gets the visual <tt>Component</tt>, if any, depicting the video streamed
* from the local peer to the remote peer.
*
* @return the visual <tt>Component</tt> depicting the local video if local
* video is actually being streamed from the local peer to the remote peer;
* otherwise, <tt>null</tt>
*/
public Component getLocalVisualComponent()
{
MediaDeviceSession deviceSession = getDeviceSession();
return
(deviceSession instanceof VideoMediaDeviceSession)
? ((VideoMediaDeviceSession) deviceSession)
.getLocalVisualComponent()
: null;
}
/**
* The priority of the video is 5, which is meant to be higher than
* other threads and lower than the audio one.
* @return video priority.
*/
@Override
protected int getPriority()
{
return 5;
}
/**
* Gets the <tt>QualityControl</tt> of this <tt>VideoMediaStream</tt>.
*
* @return the <tt>QualityControl</tt> of this <tt>VideoMediaStream</tt>
*/
public QualityControl getQualityControl()
{
return qualityControl;
}

Lyubomir Marinov
committed
/**
* {@inheritDoc}
*/
@Override
public RemoteBitrateEstimator getRemoteBitrateEstimator()
{
return remoteBitrateEstimator;
}
/**
* Gets the visual <tt>Component</tt> where video from the remote peer is
* being rendered or <tt>null</tt> if no video is currently being rendered.
*
* @return the visual <tt>Component</tt> where video from the remote peer is
* being rendered or <tt>null</tt> if no video is currently being rendered
* @see VideoMediaStream#getVisualComponent()
*/
@Deprecated
public Component getVisualComponent()
{
List<Component> visualComponents = getVisualComponents();
return visualComponents.isEmpty() ? null : visualComponents.get(0);
}
/**
* Gets the visual <tt>Component</tt>s rendering the <tt>ReceiveStream</tt>
* corresponding to the given ssrc.
*
* @param ssrc the src-id of the receive stream, which visual
* <tt>Component</tt> we're looking for
* @return the visual <tt>Component</tt> rendering the
* <tt>ReceiveStream</tt> corresponding to the given ssrc
*/
public Component getVisualComponent(long ssrc)
{
MediaDeviceSession deviceSession = getDeviceSession();
return
(deviceSession instanceof VideoMediaDeviceSession)
? ((VideoMediaDeviceSession) deviceSession).getVisualComponent(
ssrc)
: null;
}
/**
* Gets a list of the visual <tt>Component</tt>s where video from the remote
* peer is being rendered.
*
* @return a list of the visual <tt>Component</tt>s where video from the
* remote peer is being rendered
* @see VideoMediaStream#getVisualComponents()
*/
public List<Component> getVisualComponents()
{
MediaDeviceSession deviceSession = getDeviceSession();
List<Component> visualComponents;
if (deviceSession instanceof VideoMediaDeviceSession)
{
visualComponents
= ((VideoMediaDeviceSession) deviceSession)
.getVisualComponents();
}
else
visualComponents = Collections.emptyList();
return visualComponents;
}
/**

Lyubomir Marinov
committed
* Handles attributes contained in <tt>MediaFormat</tt>.

Lyubomir Marinov
committed
* @param format the <tt>MediaFormat</tt> to handle the attributes of
* @param attrs the attributes <tt>Map</tt> to handle
*/
@Override
protected void handleAttributes(
MediaFormat format,
Map<String, String> attrs)
{

Lyubomir Marinov
committed
/*
* Iterate over the specified attributes and handle those of them which
* we recognize.

Lyubomir Marinov
committed
/*
* The width and height attributes are separate but they have to be
* collected into a Dimension in order to be handled.
*/
String width = null;
String height = null;

Lyubomir Marinov
committed
for(Map.Entry<String, String> attr : attrs.entrySet())

Lyubomir Marinov
committed
String key = attr.getKey();
String value = attr.getValue();
if(key.equals("rtcp-fb"))
{

Lyubomir Marinov
committed
// if (value.equals("nack pli"))
// USE_PLI = true;