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 "GStreamerAudioCaptureSource.h"
26
27#include "GStreamerAudioData.h"
28#include "GStreamerAudioStreamDescription.h"
29#include "GStreamerCaptureDeviceManager.h"
30
31#include <gst/app/gstappsink.h>
32#include <gst/gst.h>
33#include <wtf/NeverDestroyed.h>
34
35namespace WebCore {
36
37static CapabilityValueOrRange defaultVolumeCapability()
38{
39 return CapabilityValueOrRange(0.0, 1.0);
40}
41const static RealtimeMediaSourceCapabilities::EchoCancellation defaultEchoCancellationCapability = RealtimeMediaSourceCapabilities::EchoCancellation::ReadWrite;
42
43GST_DEBUG_CATEGORY(webkit_audio_capture_source_debug);
44#define GST_CAT_DEFAULT webkit_audio_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_audio_capture_source_debug, "webkitaudiocapturesource", 0, "WebKit Audio Capture Source.");
51 });
52}
53
54class GStreamerAudioCaptureSourceFactory : public AudioCaptureFactory {
55public:
56 CaptureSourceOrError createAudioCaptureSource(const CaptureDevice& device, String&& hashSalt, const MediaConstraints* constraints) final
57 {
58 return GStreamerAudioCaptureSource::create(String { device.persistentId() }, WTFMove(hashSalt), constraints);
59 }
60private:
61 CaptureDeviceManager& audioCaptureDeviceManager() final { return GStreamerAudioCaptureDeviceManager::singleton(); }
62};
63
64static GStreamerAudioCaptureSourceFactory& libWebRTCAudioCaptureSourceFactory()
65{
66 static NeverDestroyed<GStreamerAudioCaptureSourceFactory> factory;
67 return factory.get();
68}
69
70CaptureSourceOrError GStreamerAudioCaptureSource::create(String&& deviceID, String&& hashSalt, const MediaConstraints* constraints)
71{
72 auto device = GStreamerAudioCaptureDeviceManager::singleton().gstreamerDeviceWithUID(deviceID);
73 if (!device) {
74 auto errorMessage = makeString("GStreamerAudioCaptureSource::create(): GStreamer did not find the device: ", deviceID, '.');
75 return CaptureSourceOrError(WTFMove(errorMessage));
76 }
77
78 auto source = adoptRef(*new GStreamerAudioCaptureSource(device.value(), WTFMove(hashSalt)));
79
80 if (constraints) {
81 if (auto result = source->applyConstraints(*constraints))
82 return WTFMove(result->badConstraint);
83 }
84 return CaptureSourceOrError(WTFMove(source));
85}
86
87AudioCaptureFactory& GStreamerAudioCaptureSource::factory()
88{
89 return libWebRTCAudioCaptureSourceFactory();
90}
91
92GStreamerAudioCaptureSource::GStreamerAudioCaptureSource(GStreamerCaptureDevice device, String&& hashSalt)
93 : RealtimeMediaSource(RealtimeMediaSource::Type::Audio, String { device.persistentId() }, String { device.label() }, WTFMove(hashSalt))
94 , m_capturer(std::make_unique<GStreamerAudioCapturer>(device))
95{
96 initializeGStreamerDebug();
97}
98
99GStreamerAudioCaptureSource::GStreamerAudioCaptureSource(String&& deviceID, String&& name, String&& hashSalt)
100 : RealtimeMediaSource(RealtimeMediaSource::Type::Audio, WTFMove(deviceID), WTFMove(name), WTFMove(hashSalt))
101 , m_capturer(std::make_unique<GStreamerAudioCapturer>())
102{
103 initializeGStreamerDebug();
104}
105
106GStreamerAudioCaptureSource::~GStreamerAudioCaptureSource()
107{
108}
109
110void GStreamerAudioCaptureSource::startProducingData()
111{
112 m_capturer->setupPipeline();
113 m_capturer->setSampleRate(sampleRate());
114 g_signal_connect(m_capturer->sink(), "new-sample", G_CALLBACK(newSampleCallback), this);
115 m_capturer->play();
116}
117
118GstFlowReturn GStreamerAudioCaptureSource::newSampleCallback(GstElement* sink, GStreamerAudioCaptureSource* source)
119{
120 auto sample = adoptGRef(gst_app_sink_pull_sample(GST_APP_SINK(sink)));
121
122 // FIXME - figure out a way to avoid copying (on write) the data.
123 GstBuffer* buf = gst_sample_get_buffer(sample.get());
124 auto frames(std::unique_ptr<GStreamerAudioData>(new GStreamerAudioData(WTFMove(sample))));
125 auto streamDesc(std::unique_ptr<GStreamerAudioStreamDescription>(new GStreamerAudioStreamDescription(frames->getAudioInfo())));
126
127 source->audioSamplesAvailable(
128 MediaTime(GST_TIME_AS_USECONDS(GST_BUFFER_PTS(buf)), G_USEC_PER_SEC),
129 *frames, *streamDesc, gst_buffer_get_size(buf) / frames->getAudioInfo().bpf);
130
131 return GST_FLOW_OK;
132}
133
134void GStreamerAudioCaptureSource::stopProducingData()
135{
136 g_signal_handlers_disconnect_by_func(m_capturer->sink(), reinterpret_cast<gpointer>(newSampleCallback), this);
137 m_capturer->stop();
138}
139
140const RealtimeMediaSourceCapabilities& GStreamerAudioCaptureSource::capabilities()
141{
142 if (m_capabilities)
143 return m_capabilities.value();
144
145 uint i;
146 GRefPtr<GstCaps> caps = m_capturer->caps();
147 int minSampleRate = 0, maxSampleRate = 0;
148 for (i = 0; i < gst_caps_get_size(caps.get()); i++) {
149 int capabilityMinSampleRate = 0, capabilityMaxSampleRate = 0;
150 GstStructure* str = gst_caps_get_structure(caps.get(), i);
151
152 // Only accept raw audio for now.
153 if (!gst_structure_has_name(str, "audio/x-raw"))
154 continue;
155
156 gst_structure_get(str, "rate", GST_TYPE_INT_RANGE, &capabilityMinSampleRate, &capabilityMaxSampleRate, nullptr);
157 if (i > 0) {
158 minSampleRate = std::min(minSampleRate, capabilityMinSampleRate);
159 maxSampleRate = std::max(maxSampleRate, capabilityMaxSampleRate);
160 } else {
161 minSampleRate = capabilityMinSampleRate;
162 maxSampleRate = capabilityMaxSampleRate;
163 }
164 }
165
166 RealtimeMediaSourceCapabilities capabilities(settings().supportedConstraints());
167 capabilities.setDeviceId(hashedId());
168 capabilities.setEchoCancellation(defaultEchoCancellationCapability);
169 capabilities.setVolume(defaultVolumeCapability());
170 capabilities.setSampleRate(CapabilityValueOrRange(minSampleRate, maxSampleRate));
171 m_capabilities = WTFMove(capabilities);
172
173 return m_capabilities.value();
174}
175
176void GStreamerAudioCaptureSource::settingsDidChange(OptionSet<RealtimeMediaSourceSettings::Flag> settings)
177{
178 if (settings.contains(RealtimeMediaSourceSettings::Flag::SampleRate))
179 m_capturer->setSampleRate(sampleRate());
180}
181
182const RealtimeMediaSourceSettings& GStreamerAudioCaptureSource::settings()
183{
184 if (!m_currentSettings) {
185 RealtimeMediaSourceSettings settings;
186 settings.setDeviceId(hashedId());
187
188 RealtimeMediaSourceSupportedConstraints supportedConstraints;
189 supportedConstraints.setSupportsDeviceId(true);
190 supportedConstraints.setSupportsEchoCancellation(true);
191 supportedConstraints.setSupportsVolume(true);
192 supportedConstraints.setSupportsSampleRate(true);
193 settings.setSupportedConstraints(supportedConstraints);
194
195 m_currentSettings = WTFMove(settings);
196 }
197
198 m_currentSettings->setVolume(volume());
199 m_currentSettings->setSampleRate(sampleRate());
200 m_currentSettings->setEchoCancellation(echoCancellation());
201
202 return m_currentSettings.value();
203}
204
205} // namespace WebCore
206
207#endif // ENABLE(MEDIA_STREAM) && USE(LIBWEBRTC) && USE(GSTREAMER)
208