bd28746065091924f52fac5e6071553b017ce679
[WebKit-https.git] / Source / WebCore / platform / mock / MockRealtimeVideoSource.cpp
1 /*
2  * Copyright (C) 2015-2017 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 "NotImplemented.h"
42 #include "PlatformLayer.h"
43 #include "RealtimeMediaSourceSettings.h"
44 #include <math.h>
45 #include <wtf/CurrentTime.h>
46 #include <wtf/UUID.h>
47 #include <wtf/text/StringView.h>
48
49 namespace WebCore {
50
51 class MockRealtimeVideoSourceFactory : public RealtimeMediaSource::VideoCaptureFactory {
52 public:
53     CaptureSourceOrError createVideoCaptureSource(const String& deviceID, const MediaConstraints* constraints) final {
54         for (auto& device : MockRealtimeMediaSource::videoDevices()) {
55             if (device.persistentId() == deviceID)
56                 return MockRealtimeVideoSource::create(device.label(), constraints);
57         }
58         return { };
59     }
60 };
61
62 #if !PLATFORM(MAC) && !PLATFORM(IOS)
63 CaptureSourceOrError MockRealtimeVideoSource::create(const String& name, const MediaConstraints* constraints)
64 {
65     auto source = adoptRef(*new MockRealtimeVideoSource(name));
66     if (constraints && source->applyConstraints(*constraints))
67         return { };
68
69     return CaptureSourceOrError(WTFMove(source));
70 }
71
72 RefPtr<MockRealtimeVideoSource> MockRealtimeVideoSource::createMuted(const String& name)
73 {
74     auto source = adoptRef(new MockRealtimeVideoSource(name));
75     source->m_muted = true;
76     return source;
77 }
78 #endif
79
80 RealtimeMediaSource::VideoCaptureFactory& MockRealtimeVideoSource::factory()
81 {
82     static NeverDestroyed<MockRealtimeVideoSourceFactory> factory;
83     return factory.get();
84 }
85
86 MockRealtimeVideoSource::MockRealtimeVideoSource(const String& name)
87     : MockRealtimeMediaSource(createCanonicalUUIDString(), RealtimeMediaSource::Type::Video, name)
88     , m_timer(RunLoop::current(), this, &MockRealtimeVideoSource::generateFrame)
89 {
90     setFrameRate(!deviceIndex() ? 30 : 15);
91     setFacingMode(!deviceIndex() ? RealtimeMediaSourceSettings::User : RealtimeMediaSourceSettings::Environment);
92     m_dashWidths.reserveInitialCapacity(2);
93     m_dashWidths.uncheckedAppend(6);
94     m_dashWidths.uncheckedAppend(6);
95 }
96
97 void MockRealtimeVideoSource::startProducingData()
98 {
99     MockRealtimeMediaSource::startProducingData();
100     if (size().isEmpty()) {
101         setWidth(640);
102         setHeight(480);
103     }
104
105     m_startTime = monotonicallyIncreasingTime();
106     m_timer.startRepeating(1_ms * lround(1000 / frameRate()));
107 }
108
109 void MockRealtimeVideoSource::stopProducingData()
110 {
111     MockRealtimeMediaSource::stopProducingData();
112     m_timer.stop();
113     m_elapsedTime += monotonicallyIncreasingTime() - m_startTime;
114     m_startTime = NAN;
115 }
116
117 double MockRealtimeVideoSource::elapsedTime()
118 {
119     if (std::isnan(m_startTime))
120         return m_elapsedTime;
121
122     return m_elapsedTime + (monotonicallyIncreasingTime() - m_startTime);
123 }
124
125 void MockRealtimeVideoSource::updateSettings(RealtimeMediaSourceSettings& settings)
126 {
127     settings.setFacingMode(facingMode());
128     settings.setFrameRate(frameRate());
129     IntSize size = this->size();
130     settings.setWidth(size.width());
131     settings.setHeight(size.height());
132     if (aspectRatio())
133         settings.setAspectRatio(aspectRatio());
134 }
135
136 void MockRealtimeVideoSource::initializeCapabilities(RealtimeMediaSourceCapabilities& capabilities)
137 {
138     if (!deviceIndex())
139         capabilities.addFacingMode(RealtimeMediaSourceSettings::User);
140     else
141         capabilities.addFacingMode(RealtimeMediaSourceSettings::Environment);
142     capabilities.setWidth(CapabilityValueOrRange(320, 1920));
143     capabilities.setHeight(CapabilityValueOrRange(240, 1080));
144     capabilities.setFrameRate(CapabilityValueOrRange(15.0, 60.0));
145     capabilities.setAspectRatio(CapabilityValueOrRange(4 / 3.0, 16 / 9.0));
146 }
147
148 void MockRealtimeVideoSource::initializeSupportedConstraints(RealtimeMediaSourceSupportedConstraints& supportedConstraints)
149 {
150     supportedConstraints.setSupportsWidth(true);
151     supportedConstraints.setSupportsHeight(true);
152     supportedConstraints.setSupportsAspectRatio(true);
153     supportedConstraints.setSupportsFrameRate(true);
154     supportedConstraints.setSupportsFacingMode(true);
155 }
156
157 bool MockRealtimeVideoSource::applyFrameRate(double rate)
158 {
159     if (m_timer.isActive())
160         m_timer.startRepeating(1_ms * lround(1000 / rate));
161
162     updateSampleBuffer();
163     return true;
164 }
165
166 bool MockRealtimeVideoSource::applySize(const IntSize& size)
167 {
168     m_baseFontSize = size.height() * .08;
169     FontCascadeDescription fontDescription;
170     fontDescription.setOneFamily("Courier");
171     fontDescription.setSpecifiedSize(m_baseFontSize);
172     fontDescription.setComputedSize(m_baseFontSize);
173     fontDescription.setWeight(FontSelectionValue(500));
174
175     m_timeFont = FontCascade(fontDescription, 0, 0);
176     m_timeFont.update(nullptr);
177
178     m_bipBopFontSize = m_baseFontSize * 2.5;
179     fontDescription.setSpecifiedSize(m_bipBopFontSize);
180     fontDescription.setComputedSize(m_bipBopFontSize);
181     m_bipBopFont = FontCascade(fontDescription, 0, 0);
182     m_bipBopFont.update(nullptr);
183
184     m_statsFontSize = m_baseFontSize * .5;
185     fontDescription.setSpecifiedSize(m_statsFontSize);
186     fontDescription.setComputedSize(m_statsFontSize);
187     m_statsFont = FontCascade(fontDescription, 0, 0);
188     m_statsFont.update(nullptr);
189
190     m_imageBuffer = nullptr;
191
192     return true;
193 }
194
195 void MockRealtimeVideoSource::drawAnimation(GraphicsContext& context)
196 {
197     float radius = size().width() * .09;
198     FloatPoint location(size().width() * .8, size().height() * .3);
199
200     m_path.clear();
201     m_path.moveTo(location);
202     m_path.addArc(location, radius, 0, 2 * piFloat, false);
203     m_path.closeSubpath();
204     context.setFillColor(Color::white);
205     context.setFillRule(RULE_NONZERO);
206     context.fillPath(m_path);
207
208     float endAngle = piFloat * (((fmod(m_frameNumber, frameRate()) + 0.5) * (2.0 / frameRate())) + 1);
209     m_path.clear();
210     m_path.moveTo(location);
211     m_path.addArc(location, radius, 1.5 * piFloat, endAngle, false);
212     m_path.closeSubpath();
213     context.setFillColor(Color::gray);
214     context.setFillRule(RULE_NONZERO);
215     context.fillPath(m_path);
216 }
217
218 void MockRealtimeVideoSource::drawBoxes(GraphicsContext& context)
219 {
220     static const RGBA32 magenta = 0xffff00ff;
221     static const RGBA32 yellow = 0xffffff00;
222     static const RGBA32 blue = 0xff0000ff;
223     static const RGBA32 red = 0xffff0000;
224     static const RGBA32 green = 0xff008000;
225     static const RGBA32 cyan = 0xFF00FFFF;
226
227     IntSize size = this->size();
228     float boxSize = size.width() * .035;
229     float boxTop = size.height() * .6;
230
231     m_path.clear();
232     FloatRect frameRect(2, 2, size.width() - 3, size.height() - 3);
233     context.setStrokeColor(Color::white);
234     context.setStrokeThickness(3);
235     context.setLineDash(m_dashWidths, 0);
236     m_path.addRect(frameRect);
237     m_path.closeSubpath();
238     context.strokePath(m_path);
239
240     context.setLineDash(DashArray(), 0);
241     m_path.clear();
242     m_path.moveTo(FloatPoint(0, boxTop + boxSize));
243     m_path.addLineTo(FloatPoint(size.width(), boxTop + boxSize));
244     m_path.closeSubpath();
245     context.setStrokeColor(Color::white);
246     context.setStrokeThickness(2);
247     context.strokePath(m_path);
248
249     context.setStrokeThickness(1);
250     float boxLeft = boxSize;
251     m_path.clear();
252     for (unsigned i = 0; i < boxSize / 4; i++) {
253         m_path.moveTo(FloatPoint(boxLeft + 4 * i, boxTop));
254         m_path.addLineTo(FloatPoint(boxLeft + 4 * i, boxTop + boxSize));
255     }
256     boxLeft += boxSize + 2;
257     for (unsigned i = 0; i < boxSize / 4; i++) {
258         m_path.moveTo(FloatPoint(boxLeft, boxTop + 4 * i));
259         m_path.addLineTo(FloatPoint(boxLeft + boxSize - 1, boxTop + 4 * i));
260     }
261     context.setStrokeThickness(3);
262     boxLeft += boxSize + 2;
263     for (unsigned i = 0; i < boxSize / 8; i++) {
264         m_path.moveTo(FloatPoint(boxLeft + 8 * i, boxTop));
265         m_path.addLineTo(FloatPoint(boxLeft + 8 * i, boxTop + boxSize - 1));
266     }
267     boxLeft += boxSize + 2;
268     for (unsigned i = 0; i < boxSize / 8; i++) {
269         m_path.moveTo(FloatPoint(boxLeft, boxTop + 8 * i));
270         m_path.addLineTo(FloatPoint(boxLeft + boxSize - 1, boxTop + 8 * i));
271     }
272
273     boxTop += boxSize + 2;
274     boxLeft = boxSize;
275     Color boxColors[] = { Color::white, yellow, cyan, green, magenta, red, blue };
276     for (unsigned i = 0; i < sizeof(boxColors) / sizeof(boxColors[0]); i++) {
277         context.fillRect(FloatRect(boxLeft, boxTop, boxSize + 1, boxSize + 1), boxColors[i]);
278         boxLeft += boxSize + 1;
279     }
280     context.strokePath(m_path);
281 }
282
283 void MockRealtimeVideoSource::drawText(GraphicsContext& context)
284 {
285     unsigned milliseconds = lround(elapsedTime() * 1000);
286     unsigned seconds = milliseconds / 1000 % 60;
287     unsigned minutes = seconds / 60 % 60;
288     unsigned hours = minutes / 60 % 60;
289
290     IntSize size = this->size();
291     FloatPoint timeLocation(size.width() * .05, size.height() * .15);
292     context.setFillColor(Color::white);
293     context.setTextDrawingMode(TextModeFill);
294     String string = String::format("%02u:%02u:%02u.%03u", hours, minutes, seconds, milliseconds % 1000);
295     context.drawText(m_timeFont, TextRun((StringView(string))), timeLocation);
296
297     string = String::format("%06u", m_frameNumber++);
298     timeLocation.move(0, m_baseFontSize);
299     context.drawText(m_timeFont, TextRun((StringView(string))), timeLocation);
300
301     FloatPoint statsLocation(size.width() * .65, size.height() * .75);
302     string = String::format("Frame rate: %ffps", frameRate());
303     context.drawText(m_statsFont, TextRun((StringView(string))), statsLocation);
304
305     string = String::format("Size: %u x %u", size.width(), size.height());
306     statsLocation.move(0, m_statsFontSize);
307     context.drawText(m_statsFont, TextRun((StringView(string))), statsLocation);
308
309     const char* camera;
310     switch (facingMode()) {
311     case RealtimeMediaSourceSettings::User:
312         camera = "User facing";
313         break;
314     case RealtimeMediaSourceSettings::Environment:
315         camera = "Environment facing";
316         break;
317     case RealtimeMediaSourceSettings::Left:
318         camera = "Left facing";
319         break;
320     case RealtimeMediaSourceSettings::Right:
321         camera = "Right facing";
322         break;
323     case RealtimeMediaSourceSettings::Unknown:
324         camera = "Unknown";
325         break;
326     }
327     string = String::format("Camera: %s", camera);
328     statsLocation.move(0, m_statsFontSize);
329     context.drawText(m_statsFont, TextRun((StringView(string))), statsLocation);
330
331     FloatPoint bipBopLocation(size.width() * .6, size.height() * .6);
332     unsigned frameMod = m_frameNumber % 60;
333     if (frameMod <= 15) {
334         context.setFillColor(Color::cyan);
335         String bip(ASCIILiteral("Bip"));
336         context.drawText(m_bipBopFont, TextRun(StringView(bip)), bipBopLocation);
337     } else if (frameMod > 30 && frameMod <= 45) {
338         context.setFillColor(Color::yellow);
339         String bop(ASCIILiteral("Bop"));
340         context.drawText(m_bipBopFont, TextRun(StringView(bop)), bipBopLocation);
341     }
342 }
343
344 void MockRealtimeVideoSource::generateFrame()
345 {
346     ImageBuffer* buffer = imageBuffer();
347     if (!buffer)
348         return;
349
350     GraphicsContext& context = buffer->context();
351     GraphicsContextStateSaver stateSaver(context);
352
353     IntSize size = this->size();
354     FloatRect frameRect(FloatPoint(), size);
355     context.fillRect(FloatRect(FloatPoint(), size), !deviceIndex() ? Color::black : Color::darkGray);
356
357     if (!m_muted && m_enabled) {
358         drawText(context);
359         drawAnimation(context);
360         drawBoxes(context);
361     }
362
363     updateSampleBuffer();
364 }
365
366 ImageBuffer* MockRealtimeVideoSource::imageBuffer() const
367 {
368     if (m_imageBuffer)
369         return m_imageBuffer.get();
370
371     m_imageBuffer = ImageBuffer::create(size(), Unaccelerated);
372     if (!m_imageBuffer)
373         return nullptr;
374
375     m_imageBuffer->context().setImageInterpolationQuality(InterpolationDefault);
376     m_imageBuffer->context().setStrokeThickness(1);
377
378     return m_imageBuffer.get();
379 }
380
381 } // namespace WebCore
382
383 #endif // ENABLE(MEDIA_STREAM)