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 "MockGStreamerAudioCaptureSource.h"
26
27#include "GStreamerAudioStreamDescription.h"
28#include "MockRealtimeAudioSource.h"
29
30#include <gst/app/gstappsrc.h>
31
32namespace WebCore {
33
34static const double s_Tau = 2 * M_PI;
35static const double s_BipBopDuration = 0.07;
36static const double s_BipBopVolume = 0.5;
37static const double s_BipFrequency = 1500;
38static const double s_BopFrequency = 500;
39static const double s_HumFrequency = 150;
40static const double s_HumVolume = 0.1;
41
42class WrappedMockRealtimeAudioSource : public MockRealtimeAudioSource {
43public:
44 WrappedMockRealtimeAudioSource(String&& deviceID, String&& name, String&& hashSalt)
45 : MockRealtimeAudioSource(WTFMove(deviceID), WTFMove(name), WTFMove(hashSalt))
46 , m_src(nullptr)
47 {
48 }
49
50 void start(GRefPtr<GstElement> src)
51 {
52 m_src = src;
53 if (m_streamFormat)
54 gst_app_src_set_caps(GST_APP_SRC(m_src.get()), m_streamFormat->caps());
55 MockRealtimeAudioSource::start();
56 }
57
58 void addHum(float amplitude, float frequency, float sampleRate, uint64_t start, float *p, uint64_t count)
59 {
60 float humPeriod = sampleRate / frequency;
61 for (uint64_t i = start, end = start + count; i < end; ++i) {
62 float a = amplitude * sin(i * s_Tau / humPeriod);
63 a += *p;
64 *p++ = a;
65 }
66 }
67
68 void render(Seconds delta)
69 {
70 ASSERT(m_src);
71
72 uint32_t totalFrameCount = GST_ROUND_UP_16(static_cast<size_t>(delta.seconds() * sampleRate()));
73 uint32_t frameCount = std::min(totalFrameCount, m_maximiumFrameCount);
74 while (frameCount) {
75 uint32_t bipBopStart = m_samplesRendered % m_bipBopBuffer.size();
76 uint32_t bipBopRemain = m_bipBopBuffer.size() - bipBopStart;
77 uint32_t bipBopCount = std::min(frameCount, bipBopRemain);
78
79 GstBuffer* buffer = gst_buffer_new_allocate(nullptr, bipBopCount * m_streamFormat->bytesPerFrame(), nullptr);
80 {
81 auto map = GstMappedBuffer::create(buffer, GST_MAP_WRITE);
82
83 if (!muted()) {
84 memcpy(map->data(), &m_bipBopBuffer[bipBopStart], sizeof(float) * bipBopCount);
85 addHum(s_HumVolume, s_HumFrequency, sampleRate(), m_samplesRendered, (float*)map->data(), bipBopCount);
86 } else
87 memset(map->data(), 0, sizeof(float) * bipBopCount);
88 }
89
90 gst_app_src_push_buffer(GST_APP_SRC(m_src.get()), buffer);
91 m_samplesRendered += bipBopCount;
92 totalFrameCount -= bipBopCount;
93 frameCount = std::min(totalFrameCount, m_maximiumFrameCount);
94 }
95 }
96
97 void settingsDidChange(OptionSet<RealtimeMediaSourceSettings::Flag> settings)
98 {
99 if (settings.contains(RealtimeMediaSourceSettings::Flag::SampleRate)) {
100 GstAudioInfo info;
101 auto rate = sampleRate();
102 size_t sampleCount = 2 * rate;
103
104 m_maximiumFrameCount = WTF::roundUpToPowerOfTwo(renderInterval().seconds() * sampleRate());
105 gst_audio_info_set_format(&info, GST_AUDIO_FORMAT_F32LE, rate, 1, nullptr);
106 m_streamFormat = GStreamerAudioStreamDescription(info);
107
108 if (m_src)
109 gst_app_src_set_caps(GST_APP_SRC(m_src.get()), m_streamFormat->caps());
110
111 m_bipBopBuffer.grow(sampleCount);
112 m_bipBopBuffer.fill(0);
113
114 size_t bipBopSampleCount = ceil(s_BipBopDuration * rate);
115 size_t bipStart = 0;
116 size_t bopStart = rate;
117
118 addHum(s_BipBopVolume, s_BipFrequency, rate, 0, static_cast<float*>(m_bipBopBuffer.data() + bipStart), bipBopSampleCount);
119 addHum(s_BipBopVolume, s_BopFrequency, rate, 0, static_cast<float*>(m_bipBopBuffer.data() + bopStart), bipBopSampleCount);
120 }
121
122 MockRealtimeAudioSource::settingsDidChange(settings);
123 }
124
125 GRefPtr<GstElement> m_src;
126 Optional<GStreamerAudioStreamDescription> m_streamFormat;
127 Vector<float> m_bipBopBuffer;
128 uint32_t m_maximiumFrameCount;
129 uint64_t m_samplesEmitted { 0 };
130 uint64_t m_samplesRendered { 0 };
131};
132
133CaptureSourceOrError MockRealtimeAudioSource::create(String&& deviceID,
134 String&& name, String&& hashSalt, const MediaConstraints* constraints)
135{
136 auto source = adoptRef(*new MockGStreamerAudioCaptureSource(WTFMove(deviceID), WTFMove(name), WTFMove(hashSalt)));
137
138 if (constraints && source->applyConstraints(*constraints))
139 return { };
140
141 return CaptureSourceOrError(WTFMove(source));
142}
143
144Optional<RealtimeMediaSource::ApplyConstraintsError> MockGStreamerAudioCaptureSource::applyConstraints(const MediaConstraints& constraints)
145{
146 m_wrappedSource->applyConstraints(constraints);
147 return GStreamerAudioCaptureSource::applyConstraints(constraints);
148}
149
150void MockGStreamerAudioCaptureSource::applyConstraints(const MediaConstraints& constraints, ApplyConstraintsHandler&& completionHandler)
151{
152 m_wrappedSource->applyConstraints(constraints, WTFMove(completionHandler));
153}
154
155MockGStreamerAudioCaptureSource::MockGStreamerAudioCaptureSource(String&& deviceID, String&& name, String&& hashSalt)
156 : GStreamerAudioCaptureSource(String { deviceID }, String { name }, String { hashSalt })
157 , m_wrappedSource(std::make_unique<WrappedMockRealtimeAudioSource>(WTFMove(deviceID), WTFMove(name), WTFMove(hashSalt)))
158{
159 m_wrappedSource->addObserver(*this);
160}
161
162MockGStreamerAudioCaptureSource::~MockGStreamerAudioCaptureSource()
163{
164 m_wrappedSource->removeObserver(*this);
165}
166
167void MockGStreamerAudioCaptureSource::stopProducingData()
168{
169 m_wrappedSource->stop();
170
171 GStreamerAudioCaptureSource::stopProducingData();
172}
173
174void MockGStreamerAudioCaptureSource::startProducingData()
175{
176 GStreamerAudioCaptureSource::startProducingData();
177 static_cast<WrappedMockRealtimeAudioSource*>(m_wrappedSource.get())->start(capturer()->source());
178}
179
180const RealtimeMediaSourceSettings& MockGStreamerAudioCaptureSource::settings()
181{
182 return m_wrappedSource->settings();
183}
184
185const RealtimeMediaSourceCapabilities& MockGStreamerAudioCaptureSource::capabilities()
186{
187 m_capabilities = m_wrappedSource->capabilities();
188 m_currentSettings = m_wrappedSource->settings();
189 return m_wrappedSource->capabilities();
190}
191
192void MockGStreamerAudioCaptureSource::captureFailed()
193{
194 stop();
195 RealtimeMediaSource::captureFailed();
196}
197
198} // namespace WebCore
199
200#endif // ENABLE(MEDIA_STREAM) && USE(LIBWEBRTC) && USE(GSTREAMER)
201