Changing settings of a MediaStreamTrack clone should not alter the settings of the...
[WebKit-https.git] / Source / WebCore / platform / mock / MockRealtimeVideoSource.cpp
1 /*
2  * Copyright (C) 2015-2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer
12  *    in the documentation and/or other materials provided with the
13  *    distribution.
14  * 3. Neither the name of Google Inc. nor the names of its contributors
15  *    may be used to endorse or promote products derived from this
16  *    software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "MockRealtimeVideoSource.h"
33
34 #if ENABLE(MEDIA_STREAM)
35 #include "CaptureDevice.h"
36 #include "GraphicsContext.h"
37 #include "ImageBuffer.h"
38 #include "IntRect.h"
39 #include "Logging.h"
40 #include "MediaConstraints.h"
41 #include "MockRealtimeMediaSourceCenter.h"
42 #include "NotImplemented.h"
43 #include "PlatformLayer.h"
44 #include "RealtimeMediaSourceSettings.h"
45 #include "RealtimeVideoSource.h"
46 #include <math.h>
47 #include <wtf/UUID.h>
48 #include <wtf/text/StringConcatenateNumbers.h>
49
50 namespace WebCore {
51
52 #if !PLATFORM(MAC) && !PLATFORM(IOS_FAMILY) && !(USE(GSTREAMER) && USE(LIBWEBRTC))
53 CaptureSourceOrError MockRealtimeVideoSource::create(String&& deviceID, String&& name, String&& hashSalt, const MediaConstraints* constraints)
54 {
55 #ifndef NDEBUG
56     auto device = MockRealtimeMediaSourceCenter::mockDeviceWithPersistentID(deviceID);
57     ASSERT(device);
58     if (!device)
59         return { };
60 #endif
61
62     auto source = adoptRef(*new MockRealtimeVideoSource(WTFMove(deviceID), WTFMove(name), WTFMove(hashSalt)));
63     if (constraints && source->applyConstraints(*constraints))
64         return { };
65
66     return CaptureSourceOrError(RealtimeVideoSource::create(WTFMove(source)));
67 }
68 #endif
69
70 MockRealtimeVideoSource::MockRealtimeVideoSource(String&& deviceID, String&& name, String&& hashSalt)
71     : RealtimeVideoCaptureSource(WTFMove(name), WTFMove(deviceID), WTFMove(hashSalt))
72     , m_emitFrameTimer(RunLoop::current(), this, &MockRealtimeVideoSource::generateFrame)
73 {
74     auto device = MockRealtimeMediaSourceCenter::mockDeviceWithPersistentID(persistentID());
75     ASSERT(device);
76     m_device = *device;
77
78     m_dashWidths.reserveInitialCapacity(2);
79     m_dashWidths.uncheckedAppend(6);
80     m_dashWidths.uncheckedAppend(6);
81
82     if (mockDisplay()) {
83         auto& properties = WTF::get<MockDisplayProperties>(m_device.properties);
84         setIntrinsicSize(properties.defaultSize);
85         setSize(properties.defaultSize);
86         m_fillColor = properties.fillColor;
87         return;
88     }
89
90     auto& properties = WTF::get<MockCameraProperties>(m_device.properties);
91     setFrameRate(properties.defaultFrameRate);
92     setFacingMode(properties.facingMode);
93     m_fillColor = properties.fillColor;
94 }
95
96 bool MockRealtimeVideoSource::supportsSizeAndFrameRate(Optional<int> width, Optional<int> height, Optional<double> rate)
97 {
98     // FIXME: consider splitting mock display into another class so we don't don't have to do this silly dance
99     // because of the RealtimeVideoSource inheritance.
100     if (mockCamera())
101         return RealtimeVideoCaptureSource::supportsSizeAndFrameRate(width, height, rate);
102
103     return RealtimeMediaSource::supportsSizeAndFrameRate(width, height, rate);
104 }
105
106 void MockRealtimeVideoSource::setSizeAndFrameRate(Optional<int> width, Optional<int> height, Optional<double> rate)
107 {
108     // FIXME: consider splitting mock display into another class so we don't don't have to do this silly dance
109     // because of the RealtimeVideoSource inheritance.
110     if (mockCamera()) {
111         RealtimeVideoCaptureSource::setSizeAndFrameRate(width, height, rate);
112         return;
113     }
114
115     RealtimeMediaSource::setSizeAndFrameRate(width, height, rate);
116 }
117
118 void MockRealtimeVideoSource::generatePresets()
119 {
120     ASSERT(mockCamera());
121     setSupportedPresets(WTFMove(WTF::get<MockCameraProperties>(m_device.properties).presets));
122 }
123
124 const RealtimeMediaSourceCapabilities& MockRealtimeVideoSource::capabilities()
125 {
126     if (!m_capabilities) {
127         RealtimeMediaSourceCapabilities capabilities(settings().supportedConstraints());
128
129         if (mockCamera()) {
130             capabilities.addFacingMode(WTF::get<MockCameraProperties>(m_device.properties).facingMode);
131             capabilities.setDeviceId(hashedId());
132             updateCapabilities(capabilities);
133             capabilities.setDeviceId(hashedId());
134         } else {
135             capabilities.setWidth(CapabilityValueOrRange(72, 2880));
136             capabilities.setHeight(CapabilityValueOrRange(45, 1800));
137             capabilities.setFrameRate(CapabilityValueOrRange(.01, 60.0));
138         }
139
140         m_capabilities = WTFMove(capabilities);
141     }
142
143     return m_capabilities.value();
144 }
145
146 const RealtimeMediaSourceSettings& MockRealtimeVideoSource::settings()
147 {
148     if (m_currentSettings)
149         return m_currentSettings.value();
150
151     RealtimeMediaSourceSettings settings;
152     if (mockCamera()) {
153         settings.setFacingMode(facingMode());
154         settings.setDeviceId(hashedId());
155     } else {
156         settings.setDisplaySurface(mockScreen() ? RealtimeMediaSourceSettings::DisplaySurfaceType::Monitor : RealtimeMediaSourceSettings::DisplaySurfaceType::Window);
157         settings.setLogicalSurface(false);
158     }
159     settings.setFrameRate(frameRate());
160     auto& size = this->size();
161     settings.setWidth(size.width());
162     settings.setHeight(size.height());
163     if (aspectRatio())
164         settings.setAspectRatio(aspectRatio());
165
166     RealtimeMediaSourceSupportedConstraints supportedConstraints;
167     supportedConstraints.setSupportsFrameRate(true);
168     supportedConstraints.setSupportsWidth(true);
169     supportedConstraints.setSupportsHeight(true);
170     supportedConstraints.setSupportsAspectRatio(true);
171     if (mockCamera()) {
172         supportedConstraints.setSupportsDeviceId(true);
173         supportedConstraints.setSupportsFacingMode(true);
174     } else {
175         supportedConstraints.setSupportsDisplaySurface(true);
176         supportedConstraints.setSupportsLogicalSurface(true);
177     }
178     settings.setSupportedConstraints(supportedConstraints);
179
180     m_currentSettings = WTFMove(settings);
181
182     return m_currentSettings.value();
183 }
184
185 void MockRealtimeVideoSource::setFrameRateWithPreset(double, RefPtr<VideoPreset> preset)
186 {
187     m_preset = WTFMove(preset);
188     if (preset)
189         setIntrinsicSize(preset->size);
190 }
191
192 IntSize MockRealtimeVideoSource::captureSize() const
193 {
194     return m_preset ? m_preset->size : this->size();
195 }
196
197 void MockRealtimeVideoSource::settingsDidChange(OptionSet<RealtimeMediaSourceSettings::Flag> settings)
198 {
199     m_currentSettings = WTF::nullopt;
200     if (settings.containsAny({ RealtimeMediaSourceSettings::Flag::Width, RealtimeMediaSourceSettings::Flag::Height })) {
201         m_baseFontSize = captureSize().height() * .08;
202         m_bipBopFontSize = m_baseFontSize * 2.5;
203         m_statsFontSize = m_baseFontSize * .5;
204         m_imageBuffer = nullptr;
205     }
206 }
207
208 void MockRealtimeVideoSource::startCaptureTimer()
209 {
210     m_emitFrameTimer.startRepeating(1_s / frameRate());
211 }
212
213 void MockRealtimeVideoSource::startProducingData()
214 {
215     prepareToProduceData();
216     startCaptureTimer();
217     m_startTime = MonotonicTime::now();
218 }
219
220 void MockRealtimeVideoSource::stopProducingData()
221 {
222     m_emitFrameTimer.stop();
223     m_elapsedTime += MonotonicTime::now() - m_startTime;
224     m_startTime = MonotonicTime::nan();
225 }
226
227 Seconds MockRealtimeVideoSource::elapsedTime()
228 {
229     if (std::isnan(m_startTime))
230         return m_elapsedTime;
231
232     return m_elapsedTime + (MonotonicTime::now() - m_startTime);
233 }
234
235 void MockRealtimeVideoSource::drawAnimation(GraphicsContext& context)
236 {
237     auto size = captureSize();
238     float radius = size.width() * .09;
239     FloatPoint location(size.width() * .8, size.height() * .3);
240
241     m_path.clear();
242     m_path.moveTo(location);
243     m_path.addArc(location, radius, 0, 2 * piFloat, false);
244     m_path.closeSubpath();
245     context.setFillColor(Color::white);
246     context.setFillRule(WindRule::NonZero);
247     context.fillPath(m_path);
248
249     float endAngle = piFloat * (((fmod(m_frameNumber, frameRate()) + 0.5) * (2.0 / frameRate())) + 1);
250     m_path.clear();
251     m_path.moveTo(location);
252     m_path.addArc(location, radius, 1.5 * piFloat, endAngle, false);
253     m_path.closeSubpath();
254     context.setFillColor(Color::gray);
255     context.setFillRule(WindRule::NonZero);
256     context.fillPath(m_path);
257 }
258
259 void MockRealtimeVideoSource::drawBoxes(GraphicsContext& context)
260 {
261     static const RGBA32 magenta = 0xffff00ff;
262     static const RGBA32 yellow = 0xffffff00;
263     static const RGBA32 blue = 0xff0000ff;
264     static const RGBA32 red = 0xffff0000;
265     static const RGBA32 green = 0xff008000;
266     static const RGBA32 cyan = 0xFF00FFFF;
267
268     IntSize size = captureSize();
269     float boxSize = size.width() * .035;
270     float boxTop = size.height() * .6;
271
272     m_path.clear();
273     FloatRect frameRect(2, 2, size.width() - 3, size.height() - 3);
274     context.setStrokeColor(Color::white);
275     context.setStrokeThickness(3);
276     context.setLineDash(m_dashWidths, 0);
277     m_path.addRect(frameRect);
278     m_path.closeSubpath();
279     context.strokePath(m_path);
280
281     context.setLineDash(DashArray(), 0);
282     m_path.clear();
283     m_path.moveTo(FloatPoint(0, boxTop + boxSize));
284     m_path.addLineTo(FloatPoint(size.width(), boxTop + boxSize));
285     m_path.closeSubpath();
286     context.setStrokeColor(Color::white);
287     context.setStrokeThickness(2);
288     context.strokePath(m_path);
289
290     context.setStrokeThickness(1);
291     float boxLeft = boxSize;
292     m_path.clear();
293     for (unsigned i = 0; i < boxSize / 4; i++) {
294         m_path.moveTo(FloatPoint(boxLeft + 4 * i, boxTop));
295         m_path.addLineTo(FloatPoint(boxLeft + 4 * i, boxTop + boxSize));
296     }
297     boxLeft += boxSize + 2;
298     for (unsigned i = 0; i < boxSize / 4; i++) {
299         m_path.moveTo(FloatPoint(boxLeft, boxTop + 4 * i));
300         m_path.addLineTo(FloatPoint(boxLeft + boxSize - 1, boxTop + 4 * i));
301     }
302     context.setStrokeThickness(3);
303     boxLeft += boxSize + 2;
304     for (unsigned i = 0; i < boxSize / 8; i++) {
305         m_path.moveTo(FloatPoint(boxLeft + 8 * i, boxTop));
306         m_path.addLineTo(FloatPoint(boxLeft + 8 * i, boxTop + boxSize - 1));
307     }
308     boxLeft += boxSize + 2;
309     for (unsigned i = 0; i < boxSize / 8; i++) {
310         m_path.moveTo(FloatPoint(boxLeft, boxTop + 8 * i));
311         m_path.addLineTo(FloatPoint(boxLeft + boxSize - 1, boxTop + 8 * i));
312     }
313
314     boxTop += boxSize + 2;
315     boxLeft = boxSize;
316     Color boxColors[] = { Color::white, yellow, cyan, green, magenta, red, blue };
317     for (unsigned i = 0; i < sizeof(boxColors) / sizeof(boxColors[0]); i++) {
318         context.fillRect(FloatRect(boxLeft, boxTop, boxSize + 1, boxSize + 1), boxColors[i]);
319         boxLeft += boxSize + 1;
320     }
321     context.strokePath(m_path);
322 }
323
324 void MockRealtimeVideoSource::drawText(GraphicsContext& context)
325 {
326     unsigned milliseconds = lround(elapsedTime().milliseconds());
327     unsigned seconds = milliseconds / 1000 % 60;
328     unsigned minutes = seconds / 60 % 60;
329     unsigned hours = minutes / 60 % 60;
330
331     FontCascadeDescription fontDescription;
332     fontDescription.setOneFamily("Courier");
333     fontDescription.setWeight(FontSelectionValue(500));
334
335     fontDescription.setSpecifiedSize(m_baseFontSize);
336     fontDescription.setComputedSize(m_baseFontSize);
337     FontCascade timeFont { FontCascadeDescription { fontDescription }, 0, 0 };
338     timeFont.update(nullptr);
339
340     fontDescription.setSpecifiedSize(m_bipBopFontSize);
341     fontDescription.setComputedSize(m_bipBopFontSize);
342     FontCascade bipBopFont { FontCascadeDescription { fontDescription }, 0, 0 };
343     bipBopFont.update(nullptr);
344
345     fontDescription.setSpecifiedSize(m_statsFontSize);
346     fontDescription.setComputedSize(m_statsFontSize);
347     FontCascade statsFont { WTFMove(fontDescription), 0, 0 };
348     statsFont.update(nullptr);
349
350     IntSize captureSize = this->captureSize();
351     FloatPoint timeLocation(captureSize.width() * .05, captureSize.height() * .15);
352     context.setFillColor(Color::white);
353     context.setTextDrawingMode(TextModeFill);
354     String string = makeString(pad('0', 2, hours), ':', pad('0', 2, minutes), ':', pad('0', 2, seconds), '.', pad('0', 3, milliseconds % 1000));
355     context.drawText(timeFont, TextRun((StringView(string))), timeLocation);
356
357     string = makeString(pad('0', 6, m_frameNumber++));
358     timeLocation.move(0, m_baseFontSize);
359     context.drawText(timeFont, TextRun((StringView(string))), timeLocation);
360
361     FloatPoint statsLocation(captureSize.width() * .45, captureSize.height() * .75);
362     string = makeString("Requested frame rate: ", FormattedNumber::fixedWidth(frameRate(), 1), " fps");
363     context.drawText(statsFont, TextRun((StringView(string))), statsLocation);
364
365     statsLocation.move(0, m_statsFontSize);
366     string = makeString("Observed frame rate: ", FormattedNumber::fixedWidth(observedFrameRate(), 1), " fps");
367     context.drawText(statsFont, TextRun((StringView(string))), statsLocation);
368
369     auto size = this->size();
370     statsLocation.move(0, m_statsFontSize);
371     string = makeString("Size: ", size.width(), " x ", size.height());
372     context.drawText(statsFont, TextRun((StringView(string))), statsLocation);
373
374     if (mockCamera()) {
375         statsLocation.move(0, m_statsFontSize);
376         string = makeString("Preset size: ", captureSize.width(), " x ", captureSize.height());
377         context.drawText(statsFont, TextRun((StringView(string))), statsLocation);
378
379         const char* camera;
380         switch (facingMode()) {
381         case RealtimeMediaSourceSettings::User:
382             camera = "User facing";
383             break;
384         case RealtimeMediaSourceSettings::Environment:
385             camera = "Environment facing";
386             break;
387         case RealtimeMediaSourceSettings::Left:
388             camera = "Left facing";
389             break;
390         case RealtimeMediaSourceSettings::Right:
391             camera = "Right facing";
392             break;
393         case RealtimeMediaSourceSettings::Unknown:
394             camera = "Unknown";
395             break;
396         }
397         string = makeString("Camera: ", camera);
398         statsLocation.move(0, m_statsFontSize);
399         context.drawText(statsFont, TextRun((StringView(string))), statsLocation);
400     } else if (!name().isNull()) {
401         statsLocation.move(0, m_statsFontSize);
402         context.drawText(statsFont, TextRun { name() }, statsLocation);
403     }
404
405     FloatPoint bipBopLocation(captureSize.width() * .6, captureSize.height() * .6);
406     unsigned frameMod = m_frameNumber % 60;
407     if (frameMod <= 15) {
408         context.setFillColor(Color::cyan);
409         String bip("Bip"_s);
410         context.drawText(bipBopFont, TextRun(StringView(bip)), bipBopLocation);
411     } else if (frameMod > 30 && frameMod <= 45) {
412         context.setFillColor(Color::yellow);
413         String bop("Bop"_s);
414         context.drawText(bipBopFont, TextRun(StringView(bop)), bipBopLocation);
415     }
416 }
417
418 void MockRealtimeVideoSource::delaySamples(Seconds delta)
419 {
420     m_delayUntil = MonotonicTime::now() + delta;
421 }
422
423 void MockRealtimeVideoSource::generateFrame()
424 {
425     if (m_delayUntil) {
426         if (m_delayUntil < MonotonicTime::now())
427             return;
428         m_delayUntil = MonotonicTime();
429     }
430
431     ImageBuffer* buffer = imageBuffer();
432     if (!buffer)
433         return;
434
435     GraphicsContext& context = buffer->context();
436     GraphicsContextStateSaver stateSaver(context);
437
438     auto size = captureSize();
439     FloatRect frameRect(FloatPoint(), size);
440
441     context.fillRect(FloatRect(FloatPoint(), size), m_fillColor);
442
443     if (!muted()) {
444         drawText(context);
445         drawAnimation(context);
446         drawBoxes(context);
447     }
448
449     updateSampleBuffer();
450 }
451
452 ImageBuffer* MockRealtimeVideoSource::imageBuffer() const
453 {
454     if (m_imageBuffer)
455         return m_imageBuffer.get();
456
457     m_imageBuffer = ImageBuffer::create(captureSize(), Unaccelerated);
458     if (!m_imageBuffer)
459         return nullptr;
460
461     m_imageBuffer->context().setImageInterpolationQuality(InterpolationDefault);
462     m_imageBuffer->context().setStrokeThickness(1);
463
464     return m_imageBuffer.get();
465 }
466
467 bool MockRealtimeVideoSource::mockDisplayType(CaptureDevice::DeviceType type) const
468 {
469     if (!WTF::holds_alternative<MockDisplayProperties>(m_device.properties))
470         return false;
471
472     return WTF::get<MockDisplayProperties>(m_device.properties).type == type;
473 }
474
475 } // namespace WebCore
476
477 #endif // ENABLE(MEDIA_STREAM)