1/*
2 * Copyright (C) 2018 Metrological Group B.V.
3 * Author: Thibault Saunier <tsaunier@igalia.com>
4 * Author: Alejandro G. Castro <alex@igalia.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * aint with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23
24#if ENABLE(MEDIA_STREAM) && USE(LIBWEBRTC) && USE(GSTREAMER)
25#include "GStreamerVideoCaptureSource.h"
26
27#include "GStreamerCaptureDeviceManager.h"
28#include "MediaSampleGStreamer.h"
29
30#include <gst/app/gstappsink.h>
31#include <webrtc/api/mediastreaminterface.h>
32#include <webrtc/api/peerconnectioninterface.h>
33#include <webrtc/media/base/videocommon.h>
34#include <webrtc/media/engine/webrtcvideocapturer.h>
35#include <webrtc/media/engine/webrtcvideocapturerfactory.h>
36#include <webrtc/modules/video_capture/video_capture_defines.h>
37
38namespace WebCore {
39
40const static int defaultWidth = 640;
41const static int defaultHeight = 480;
42
43GST_DEBUG_CATEGORY(webkit_video_capture_source_debug);
44#define GST_CAT_DEFAULT webkit_video_capture_source_debug
45
46static void initializeGStreamerDebug()
47{
48 static std::once_flag debugRegisteredFlag;
49 std::call_once(debugRegisteredFlag, [] {
50 GST_DEBUG_CATEGORY_INIT(webkit_video_capture_source_debug, "webkitvideocapturesource", 0,
51 "WebKit Video Capture Source.");
52 });
53}
54
55class GStreamerVideoPreset : public VideoPreset {
56public:
57 static Ref<GStreamerVideoPreset> create(IntSize size, Vector<FrameRateRange>&& framerates)
58 {
59 return adoptRef(*new GStreamerVideoPreset(size, WTFMove(framerates)));
60 }
61
62 GStreamerVideoPreset(IntSize size, Vector<FrameRateRange>&& frameRateRanges)
63 : VideoPreset(size, WTFMove(frameRateRanges), GStreamer)
64 {
65 }
66};
67
68class GStreamerVideoCaptureSourceFactory final : public VideoCaptureFactory {
69public:
70 CaptureSourceOrError createVideoCaptureSource(const CaptureDevice& device, String&& hashSalt, const MediaConstraints* constraints) final
71 {
72 return GStreamerVideoCaptureSource::create(String { device.persistentId() }, WTFMove(hashSalt), constraints);
73 }
74private:
75 CaptureDeviceManager& videoCaptureDeviceManager() final { return GStreamerVideoCaptureDeviceManager::singleton(); }
76};
77
78VideoCaptureFactory& libWebRTCVideoCaptureSourceFactory()
79{
80 static NeverDestroyed<GStreamerVideoCaptureSourceFactory> factory;
81 return factory.get();
82}
83
84class GStreamerDisplayCaptureSourceFactory final : public DisplayCaptureFactory {
85public:
86 CaptureSourceOrError createDisplayCaptureSource(const CaptureDevice&, const MediaConstraints*) final
87 {
88 // FIXME: Implement this.
89 return { };
90 }
91private:
92 CaptureDeviceManager& displayCaptureDeviceManager() final { return GStreamerDisplayCaptureDeviceManager::singleton(); }
93};
94
95DisplayCaptureFactory& libWebRTCDisplayCaptureSourceFactory()
96{
97 static NeverDestroyed<GStreamerDisplayCaptureSourceFactory> factory;
98 return factory.get();
99}
100
101CaptureSourceOrError GStreamerVideoCaptureSource::create(String&& deviceID, String&& hashSalt, const MediaConstraints* constraints)
102{
103 auto device = GStreamerVideoCaptureDeviceManager::singleton().gstreamerDeviceWithUID(deviceID);
104 if (!device) {
105 auto errorMessage = makeString("GStreamerVideoCaptureSource::create(): GStreamer did not find the device: ", deviceID, '.');
106 return CaptureSourceOrError(WTFMove(errorMessage));
107 }
108
109 auto source = adoptRef(*new GStreamerVideoCaptureSource(device.value(), WTFMove(hashSalt)));
110
111 if (constraints) {
112 if (auto result = source->applyConstraints(*constraints))
113 return WTFMove(result->badConstraint);
114 }
115 return CaptureSourceOrError(WTFMove(source));
116}
117
118VideoCaptureFactory& GStreamerVideoCaptureSource::factory()
119{
120 return libWebRTCVideoCaptureSourceFactory();
121}
122
123DisplayCaptureFactory& GStreamerVideoCaptureSource::displayFactory()
124{
125 return libWebRTCDisplayCaptureSourceFactory();
126}
127
128GStreamerVideoCaptureSource::GStreamerVideoCaptureSource(String&& deviceID, String&& name, String&& hashSalt, const gchar *source_factory)
129 : RealtimeVideoSource(WTFMove(deviceID), WTFMove(name), WTFMove(hashSalt))
130 , m_capturer(std::make_unique<GStreamerVideoCapturer>(source_factory))
131{
132 initializeGStreamerDebug();
133}
134
135GStreamerVideoCaptureSource::GStreamerVideoCaptureSource(GStreamerCaptureDevice device, String&& hashSalt)
136 : RealtimeVideoSource(String { device.persistentId() }, String { device.label() }, WTFMove(hashSalt))
137 , m_capturer(std::make_unique<GStreamerVideoCapturer>(device))
138{
139 initializeGStreamerDebug();
140}
141
142GStreamerVideoCaptureSource::~GStreamerVideoCaptureSource()
143{
144}
145
146void GStreamerVideoCaptureSource::settingsDidChange(OptionSet<RealtimeMediaSourceSettings::Flag> settings)
147{
148 if (settings.containsAny({ RealtimeMediaSourceSettings::Flag::Width, RealtimeMediaSourceSettings::Flag::Height }))
149 m_capturer->setSize(size().width(), size().height());
150 if (settings.contains(RealtimeMediaSourceSettings::Flag::FrameRate))
151 m_capturer->setFrameRate(frameRate());
152}
153
154void GStreamerVideoCaptureSource::startProducingData()
155{
156 m_capturer->setupPipeline();
157 m_capturer->setSize(size().width(), size().height());
158 m_capturer->setFrameRate(frameRate());
159 g_signal_connect(m_capturer->sink(), "new-sample", G_CALLBACK(newSampleCallback), this);
160 m_capturer->play();
161}
162
163GstFlowReturn GStreamerVideoCaptureSource::newSampleCallback(GstElement* sink, GStreamerVideoCaptureSource* source)
164{
165 auto gstSample = adoptGRef(gst_app_sink_pull_sample(GST_APP_SINK(sink)));
166 auto mediaSample = MediaSampleGStreamer::create(WTFMove(gstSample), WebCore::FloatSize(), String());
167
168 // FIXME - Check how presentationSize is supposed to be used here.
169 callOnMainThread([protectedThis = makeRef(*source), mediaSample = WTFMove(mediaSample)] {
170 protectedThis->videoSampleAvailable(mediaSample.get());
171 });
172
173 return GST_FLOW_OK;
174}
175
176void GStreamerVideoCaptureSource::stopProducingData()
177{
178 g_signal_handlers_disconnect_by_func(m_capturer->sink(), reinterpret_cast<gpointer>(newSampleCallback), this);
179 m_capturer->stop();
180
181 GST_INFO("Reset height and width after stopping source");
182 setSize({ 0, 0 });
183}
184
185const RealtimeMediaSourceCapabilities& GStreamerVideoCaptureSource::capabilities()
186{
187 RealtimeMediaSourceCapabilities capabilities(settings().supportedConstraints());
188
189 capabilities.setDeviceId(hashedId());
190 updateCapabilities(capabilities);
191
192 capabilities.addFacingMode(RealtimeMediaSourceSettings::Unknown);
193
194 m_capabilities = WTFMove(capabilities);
195
196 return m_capabilities.value();
197}
198
199const RealtimeMediaSourceSettings& GStreamerVideoCaptureSource::settings()
200{
201 if (!m_currentSettings) {
202 RealtimeMediaSourceSettings settings;
203 settings.setDeviceId(hashedId());
204
205 RealtimeMediaSourceSupportedConstraints supportedConstraints;
206 supportedConstraints.setSupportsDeviceId(true);
207 supportedConstraints.setSupportsFacingMode(true);
208 supportedConstraints.setSupportsWidth(true);
209 supportedConstraints.setSupportsHeight(true);
210 supportedConstraints.setSupportsAspectRatio(true);
211 supportedConstraints.setSupportsFrameRate(true);
212 settings.setSupportedConstraints(supportedConstraints);
213
214 m_currentSettings = WTFMove(settings);
215 }
216
217 m_currentSettings->setWidth(size().width());
218 m_currentSettings->setHeight(size().height());
219 m_currentSettings->setFrameRate(frameRate());
220 m_currentSettings->setAspectRatio(aspectRatio());
221 m_currentSettings->setFacingMode(facingMode());
222 return m_currentSettings.value();
223}
224
225void GStreamerVideoCaptureSource::generatePresets()
226{
227 Vector<Ref<VideoPreset>> presets;
228 GRefPtr<GstCaps> caps = adoptGRef(m_capturer->caps());
229 for (unsigned i = 0; i < gst_caps_get_size(caps.get()); i++) {
230 GstStructure* str = gst_caps_get_structure(caps.get(), i);
231
232 // Only accept raw video for now.
233 if (!gst_structure_has_name(str, "video/x-raw"))
234 continue;
235
236 int32_t width, height;
237 if (!gst_structure_get(str, "width", G_TYPE_INT, &width, "height", G_TYPE_INT, &height, nullptr)) {
238 GST_INFO("Could not find discret height and width values in %" GST_PTR_FORMAT, str);
239 continue;
240 }
241
242 IntSize size = { width, height };
243 double framerate;
244 Vector<FrameRateRange> frameRates;
245 int32_t minFrameRateNumerator, minFrameRateDenominator, maxFrameRateNumerator, maxFrameRateDenominator, framerateNumerator, framerateDenominator;
246 if (gst_structure_get(str, "framerate", GST_TYPE_FRACTION_RANGE, &minFrameRateNumerator, &minFrameRateDenominator, &maxFrameRateNumerator, &maxFrameRateDenominator, nullptr)) {
247 FrameRateRange range;
248
249 gst_util_fraction_to_double(minFrameRateNumerator, minFrameRateDenominator, &range.minimum);
250 gst_util_fraction_to_double(maxFrameRateNumerator, maxFrameRateDenominator, &range.maximum);
251
252 frameRates.append(range);
253 } else if (gst_structure_get(str, "framerate", GST_TYPE_FRACTION, &framerateNumerator, &framerateDenominator, nullptr)) {
254 gst_util_fraction_to_double(framerateNumerator, framerateDenominator, &framerate);
255 frameRates.append({ framerate, framerate});
256 } else {
257 const GValue* frameRateValues(gst_structure_get_value(str, "framerate"));
258 unsigned frameRatesLength = static_cast<unsigned>(gst_value_list_get_size(frameRateValues));
259
260 for (unsigned j = 0; j < frameRatesLength; j++) {
261 const GValue* val = gst_value_list_get_value(frameRateValues, j);
262
263 ASSERT(val && G_VALUE_TYPE(val) == GST_TYPE_FRACTION);
264 gst_util_fraction_to_double(gst_value_get_fraction_numerator(val),
265 gst_value_get_fraction_denominator(val), &framerate);
266
267 frameRates.append({ framerate, framerate});
268 }
269 }
270
271 presets.append(GStreamerVideoPreset::create(size, WTFMove(frameRates)));
272 }
273
274 if (presets.isEmpty()) {
275 GST_INFO("Could not find any presets for caps: %" GST_PTR_FORMAT " just let anything go out.", caps.get());
276
277 for (auto& size : standardVideoSizes()) {
278 Vector<FrameRateRange> frameRates;
279
280 frameRates.append({ 0, G_MAXDOUBLE});
281 presets.append(GStreamerVideoPreset::create(size, WTFMove(frameRates)));
282 }
283 }
284
285 setSupportedPresets(WTFMove(presets));
286}
287
288} // namespace WebCore
289
290#endif // ENABLE(MEDIA_STREAM) && USE(LIBWEBRTC)
291