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(GSTREAMER)
25#include "GStreamerCaptureDeviceManager.h"
26
27#include "GStreamerCommon.h"
28
29namespace WebCore {
30
31static gint sortDevices(gconstpointer a, gconstpointer b)
32{
33 GstDevice* adev = GST_DEVICE(a), *bdev = GST_DEVICE(b);
34 GUniquePtr<GstStructure> aprops(gst_device_get_properties(adev));
35 GUniquePtr<GstStructure> bprops(gst_device_get_properties(bdev));
36 gboolean aIsDefault = FALSE, bIsDefault = FALSE;
37
38 gst_structure_get_boolean(aprops.get(), "is-default", &aIsDefault);
39 gst_structure_get_boolean(bprops.get(), "is-default", &bIsDefault);
40
41 if (aIsDefault == bIsDefault) {
42 GUniquePtr<char> aName(gst_device_get_display_name(adev));
43 GUniquePtr<char> bName(gst_device_get_display_name(bdev));
44
45 return g_strcmp0(aName.get(), bName.get());
46 }
47
48 return aIsDefault > bIsDefault ? -1 : 1;
49}
50
51GStreamerAudioCaptureDeviceManager& GStreamerAudioCaptureDeviceManager::singleton()
52{
53 static NeverDestroyed<GStreamerAudioCaptureDeviceManager> manager;
54 return manager;
55}
56
57GStreamerVideoCaptureDeviceManager& GStreamerVideoCaptureDeviceManager::singleton()
58{
59 static NeverDestroyed<GStreamerVideoCaptureDeviceManager> manager;
60 return manager;
61}
62
63GStreamerDisplayCaptureDeviceManager& GStreamerDisplayCaptureDeviceManager::singleton()
64{
65 static NeverDestroyed<GStreamerDisplayCaptureDeviceManager> manager;
66 return manager;
67}
68
69Optional<GStreamerCaptureDevice> GStreamerCaptureDeviceManager::gstreamerDeviceWithUID(const String& deviceID)
70{
71 captureDevices();
72
73 for (auto& device : m_gstreamerDevices) {
74 if (device.persistentId() == deviceID)
75 return device;
76 }
77 return WTF::nullopt;
78}
79
80const Vector<CaptureDevice>& GStreamerCaptureDeviceManager::captureDevices()
81{
82 initializeGStreamer();
83 if (m_devices.isEmpty())
84 refreshCaptureDevices();
85
86 return m_devices;
87}
88
89void GStreamerCaptureDeviceManager::addDevice(GRefPtr<GstDevice>&& device)
90{
91 GUniquePtr<GstStructure> properties(gst_device_get_properties(device.get()));
92 const char* klass = gst_structure_get_string(properties.get(), "device.class");
93
94 if (klass && !g_strcmp0(klass, "monitor"))
95 return;
96
97 CaptureDevice::DeviceType type = deviceType();
98 GUniquePtr<char> deviceClassChar(gst_device_get_device_class(device.get()));
99 String deviceClass(String(deviceClassChar.get()));
100 if (type == CaptureDevice::DeviceType::Microphone && !deviceClass.startsWith("Audio"))
101 return;
102 if (type == CaptureDevice::DeviceType::Camera && !deviceClass.startsWith("Video"))
103 return;
104
105 // This isn't really a UID but should be good enough (libwebrtc
106 // itself does that at least for pulseaudio devices).
107 GUniquePtr<char> deviceName(gst_device_get_display_name(device.get()));
108 gboolean isDefault = FALSE;
109 gst_structure_get_boolean(properties.get(), "is-default", &isDefault);
110
111 String identifier = makeString(isDefault ? "default: " : "", deviceName.get());
112
113 auto gstCaptureDevice = GStreamerCaptureDevice(WTFMove(device), identifier, type, identifier);
114 gstCaptureDevice.setEnabled(true);
115 m_gstreamerDevices.append(WTFMove(gstCaptureDevice));
116 // FIXME: We need a CaptureDevice copy in other vector just for captureDevices API.
117 auto captureDevice = CaptureDevice(identifier, type, identifier);
118 captureDevice.setEnabled(true);
119 m_devices.append(WTFMove(captureDevice));
120}
121
122void GStreamerCaptureDeviceManager::refreshCaptureDevices()
123{
124 if (!m_deviceMonitor) {
125 m_deviceMonitor = adoptGRef(gst_device_monitor_new());
126
127 CaptureDevice::DeviceType type = deviceType();
128 if (type == CaptureDevice::DeviceType::Camera) {
129 GRefPtr<GstCaps> caps = adoptGRef(gst_caps_new_empty_simple("video/x-raw"));
130 gst_device_monitor_add_filter(m_deviceMonitor.get(), "Video/Source", caps.get());
131 } else if (type == CaptureDevice::DeviceType::Microphone) {
132 GRefPtr<GstCaps> caps = adoptGRef(gst_caps_new_empty_simple("audio/x-raw"));
133 gst_device_monitor_add_filter(m_deviceMonitor.get(), "Audio/Source", caps.get());
134 } else
135 return;
136 }
137
138 // FIXME: Add monitor for added/removed messages on the bus.
139 if (!gst_device_monitor_start(m_deviceMonitor.get())) {
140 GST_WARNING_OBJECT(m_deviceMonitor.get(), "Could not start device monitor");
141 m_deviceMonitor = nullptr;
142
143 return;
144 }
145
146 GList* devices = g_list_sort(gst_device_monitor_get_devices(m_deviceMonitor.get()), sortDevices);
147 while (devices) {
148 GRefPtr<GstDevice> device = adoptGRef(GST_DEVICE_CAST(devices->data));
149
150 addDevice(WTFMove(device));
151 devices = g_list_delete_link(devices, devices);
152 }
153
154 gst_device_monitor_stop(m_deviceMonitor.get());
155}
156
157} // namespace WebCore
158
159#endif // ENABLE(MEDIA_STREAM) && USE(GSTREAMER)
160