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.*;
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.*;
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.*;
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 indicator which determines whether RTCP feedback Picture Loss
* Indication messages are to be used.
*/

Boris Grozev
committed
private static final boolean USE_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
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
/* 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]);
}
token = null;
mSingle = null;
mRange = null;
m = null;
pRecvRange = null;
pSendSingle = null;
pRecvSingle = null;
pSendRange = null;
return res;
}
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
/**
* 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();
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
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
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
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);
}
});
FormatInfo preferredFormat =
new FormatInfo(new Dimension(preferredWidth, preferredHeight));
Dimension closestAspect = null;
// lets choose the closest size to the preferred one,
// finding the first sutable 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
= (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();
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
/**
* 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);
}

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);
}
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
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
/**
* 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);
}
/**
* 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);
}
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
}
}
/**
* 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);
}
/**
* 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>.
*
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
589
* @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);

Boris Grozev
committed
newVideoMediaDeviceSession.setRtcpFeedbackPLI(USE_PLI);
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
/*
* 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
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
/**
* 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;
}
/**
* 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;
}
else if(key.equals("imageattr"))
{

Lyubomir Marinov
committed
/*
* If the width and height attributes have been collected
* into outputSize, do not override the Dimension they have
* specified.
*/
if((attrs.containsKey("width")
|| attrs.containsKey("height"))
&& (outputSize != null))
{
continue;
}
Dimension res[] = parseSendRecvResolution(value);
if(res != null)
{
outputSize = res[1];
qualityControl.setRemoteSendMaxPreset(

Lyubomir Marinov
committed
new QualityPreset(res[0]));
qualityControl.setRemoteReceiveResolution(outputSize);

Lyubomir Marinov
committed
((VideoMediaDeviceSession)getDeviceSession())
.setOutputSize(outputSize);
}
}
else if(key.equals("CIF"))
{
Dimension dim = new Dimension(352, 288);

Lyubomir Marinov
committed
if((outputSize == null)
|| ((outputSize.width < dim.width)
&& (outputSize.height < dim.height)))

Lyubomir Marinov
committed
((VideoMediaDeviceSession)getDeviceSession())
.setOutputSize(outputSize);
}
}
else if(key.equals("QCIF"))
{
Dimension dim = new Dimension(176, 144);

Lyubomir Marinov
committed
if((outputSize == null)
|| ((outputSize.width < dim.width)
&& (outputSize.height < dim.height)))

Lyubomir Marinov
committed
((VideoMediaDeviceSession)getDeviceSession())
.setOutputSize(outputSize);

Lyubomir Marinov
committed
else if(key.equals("VGA")) // X-Lite sends it.
{
Dimension dim = new Dimension(640, 480);

Lyubomir Marinov
committed
if((outputSize == null)
|| ((outputSize.width < dim.width)
&& (outputSize.height < dim.height)))

Lyubomir Marinov
committed
// X-Lite does not display anything if we send 640x480.

Lyubomir Marinov
committed
((VideoMediaDeviceSession)getDeviceSession())
.setOutputSize(outputSize);
}
}
else if(key.equals("CUSTOM"))
{
String args[] = value.split(",");
if(args.length < 3)
continue;
try
{

Lyubomir Marinov
committed
Dimension dim
= new Dimension(
Integer.parseInt(args[0]),
Integer.parseInt(args[1]));
if((outputSize == null)
|| ((outputSize.width < dim.width)
&& (outputSize.height < dim.height)))

Lyubomir Marinov
committed
((VideoMediaDeviceSession)getDeviceSession())
.setOutputSize(outputSize);
}
}
catch(Exception e)
{
}
}
else if (key.equals("width"))
{
width = value;
if(height != null)
{

Lyubomir Marinov
committed
outputSize
= new Dimension(
Integer.parseInt(width),
Integer.parseInt(height));
((VideoMediaDeviceSession)getDeviceSession())
.setOutputSize(outputSize);
}
}
else if (key.equals("height"))
{
height = value;
if(width != null)
{

Lyubomir Marinov
committed
outputSize
= new Dimension(
Integer.parseInt(width),
Integer.parseInt(height));
((VideoMediaDeviceSession)getDeviceSession())
.setOutputSize(outputSize);
}
}
}
}
}
/**

Lyubomir Marinov
committed
* Move origin of a partial desktop streaming <tt>MediaDevice</tt>.

Lyubomir Marinov
committed
* @param x new x coordinate origin
* @param y new y coordinate origin

Lyubomir Marinov
committed
public void movePartialDesktopStreaming(int x, int y)

Lyubomir Marinov
committed
MediaDeviceImpl dev = (MediaDeviceImpl) getDevice();

Lyubomir Marinov
committed
if (!DeviceSystem.LOCATOR_PROTOCOL_IMGSTREAMING.equals(
dev.getCaptureDeviceInfoLocatorProtocol()))

Lyubomir Marinov
committed
return;

Lyubomir Marinov
committed
DataSource captureDevice = getDeviceSession().getCaptureDevice();
Object imgStreamingControl
= captureDevice.getControl(ImgStreamingControl.class.getName());

Lyubomir Marinov
committed
if (imgStreamingControl == null)
return;

Lyubomir Marinov
committed
// Makes the screen detection with a point inside a real screen i.e.
// x and y are both greater than or equal to 0.