File: GStreamerSinksWorkarounds.cpp

package info (click to toggle)
webkit2gtk 2.48.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 429,764 kB
  • sloc: cpp: 3,697,587; javascript: 194,444; ansic: 169,997; python: 46,499; asm: 19,295; ruby: 18,528; perl: 16,602; xml: 4,650; yacc: 2,360; sh: 2,098; java: 1,993; lex: 1,327; pascal: 366; makefile: 298
file content (339 lines) | stat: -rw-r--r-- 15,057 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*
 *  Copyright (C) 2023 Igalia S.L
 *  Copyright (C) 2023 Metrological Group B.V.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


#include "config.h"
#include "GStreamerSinksWorkarounds.h"

#if USE(GSTREAMER)

#include "GStreamerCommon.h"
#include <gst/base/gstbasesink.h>
#include <gst/gst.h>
#include <gst/pbutils/gstpluginsbaseversion.h>
#include <mutex>
#include <wtf/PrintStream.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/WTFGType.h>

GST_DEBUG_CATEGORY(webkit_workarounds_debug);
#define GST_CAT_DEFAULT webkit_workarounds_debug

namespace WebCore {

enum class WorkaroundMode {
    UseIfNeeded, // default
    ForceEnable,
    ForceDisable,
};

// These environment variables can be used to enable or disable workarounds at build time, if desired.
// This is useful when running patched GStreamer versions, for instance.
#ifndef WEBKIT_GST_WORKAROUND_APP_SINK_FLUSH_CAPS_DEFAULT_MODE
#define WEBKIT_GST_WORKAROUND_APP_SINK_FLUSH_CAPS_DEFAULT_MODE "UseIfNeeded"
#endif
#ifndef WEBKIT_GST_WORKAROUND_BASE_SINK_POSITION_FLUSH_DEFAULT_MODE
#define WEBKIT_GST_WORKAROUND_BASE_SINK_POSITION_FLUSH_DEFAULT_MODE "UseIfNeeded"
#endif

static WorkaroundMode getWorkAroundModeFromEnvironment(const char* environmentVariableName, const char* defaultValue)
{
    const char* textValue = getenv(environmentVariableName);
    if (!textValue)
        textValue = defaultValue;

    if (!g_ascii_strcasecmp(textValue, "UseIfNeeded"))
        return WorkaroundMode::UseIfNeeded;
    if (!g_ascii_strcasecmp(textValue, "ForceEnable"))
        return WorkaroundMode::ForceEnable;
    if (!g_ascii_strcasecmp(textValue, "ForceDisable"))
        return WorkaroundMode::ForceDisable;
    GST_ERROR("Invalid value for %s: '%s'. Accepted values are 'UseIfNeeded', 'ForceEnable' and 'ForceDisable'. Defaulting to `UseIfNeeded`...", environmentVariableName, textValue);
    return WorkaroundMode::UseIfNeeded;
}

// Workaround for: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3471 basesink: Support position queries after non-resetting flushes.
// Fix merged in 1.23, shipped in GStreamer 1.24.0.

class BaseSinkPositionFlushWorkaroundProbe {
public:
    static bool isNeeded()
    {
        static std::once_flag onceFlag;
        std::call_once(onceFlag, initializeIsNeeded);
        return s_isNeeded;
    }

    static void installIfNeeded(GstBaseSink* basesink)
    {
        ASSERT(GST_IS_BASE_SINK(basesink));
        if (!isNeeded())
            return;

        GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(GST_ELEMENT(basesink), "sink"));
        GST_DEBUG_OBJECT(pad.get(), "Installing BaseSinkPositionFlushWorkaroundProbe.");
        gst_pad_add_probe(pad.get(), static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_EVENT_FLUSH),
            probe, newUserData(), deleteUserData);
    }

private:
    static bool s_isNeeded;

    bool m_isHandlingFlushStop { false };

    static bool checkIsNeeded()
    {
#ifndef GST_DISABLE_GST_DEBUG
        GUniquePtr<char> versionString(gst_version_string());
        GST_DEBUG("BaseSinkPositionFlushWorkaroundProbe: running %s, the bug was fixed in 1.24.", versionString.get());
#endif
        WorkaroundMode mode = getWorkAroundModeFromEnvironment("WEBKIT_GST_WORKAROUND_BASE_SINK_POSITION_FLUSH", WEBKIT_GST_WORKAROUND_BASE_SINK_POSITION_FLUSH_DEFAULT_MODE);
        if (mode == WorkaroundMode::ForceEnable) {
            GST_DEBUG("BaseSinkPositionFlushWorkaroundProbe: forcing workaround to be enabled.");
            return true;
        }
        if (mode == WorkaroundMode::ForceDisable) {
            GST_DEBUG("BaseSinkPositionFlushWorkaroundProbe: forcing workaround to be disabled.");
            return false;
        }

        return !webkitGstCheckVersion(1, 24, 0);
    }

    static void initializeIsNeeded()
    {
        s_isNeeded = checkIsNeeded();
        GST_DEBUG("BaseSinkPositionFlushWorkaroundProbe is%s needed in this system.", s_isNeeded ? "" : " NOT");
    }

    static bool flushIsNonResetting(GstEvent* event)
    {
        gboolean resetTime = TRUE;
        gst_event_parse_flush_stop(event, &resetTime);
        return !resetTime;
    }

    static GstPadProbeReturn probe(GstPad* pad, GstPadProbeInfo* info, void* userData)
    {
        auto* self = static_cast<BaseSinkPositionFlushWorkaroundProbe*>(userData);
        GRefPtr<GstBaseSink> basesink = adoptGRef(GST_BASE_SINK(gst_pad_get_parent(pad)));
        GST_TRACE_OBJECT(pad, "m_isHandlingFlushStop: %s, pad is flushing: %s, received: %" GST_PTR_FORMAT, boolForPrinting(self->m_isHandlingFlushStop), boolForPrinting(GST_PAD_IS_FLUSHING(pad)), info->data);
        if (!self->m_isHandlingFlushStop && (info->type & GST_PAD_PROBE_TYPE_EVENT_FLUSH) && GST_EVENT_TYPE(GST_PAD_PROBE_INFO_EVENT(info)) == GST_EVENT_FLUSH_STOP
            && flushIsNonResetting(GST_PAD_PROBE_INFO_EVENT(info)) && basesink->segment.format != GST_FORMAT_UNDEFINED)
        {
            GST_DEBUG_OBJECT(pad, "Received non-resetting FLUSH_STOP while we have a valid segment... propagating the FLUSH_STOP.");

            self->m_isHandlingFlushStop = true;
            gst_pad_send_event(pad, GST_EVENT(info->data));
            self->m_isHandlingFlushStop = false;

            // For non-resetting FLUSH_STOP events basesink preserves the segment but still sets have_newsegment to FALSE.
            // Until the fix was introduced, this makes gst_base_sink_get_position() fail.
            // The workaround consists on setting have_newsegment to TRUE in that case after the FLUSH_STOP is handled
            // internally by basesink.
            //
            // This is a reasonably safe workaround, as the only other place where have_newsegment is read is in a warning
            // message in gst_base_sink_chain_unlocked() for when a buffer is pushed without a segment, something that
            // shouldn't happen in the first place.
            GST_DEBUG_OBJECT(pad, "Non-resetting FLUSH_STOP has been handled by this element and its downstream. Now force position queries to work by setting basesink->have_newsegment to TRUE.");
            basesink->have_newsegment = TRUE;

            return GST_PAD_PROBE_HANDLED;
        }
        return GST_PAD_PROBE_OK;
    }

    static void* newUserData() { return new BaseSinkPositionFlushWorkaroundProbe; }
    static void deleteUserData(void* self) { delete static_cast<BaseSinkPositionFlushWorkaroundProbe*>(self); }
};

bool BaseSinkPositionFlushWorkaroundProbe::s_isNeeded;

// Workaround for https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2413 appsink: Fix race condition on caps handling
// Fix landed upstream in 1.20.3, 1.21.1, 1.22.0.

class AppSinkFlushCapsWorkaroundProbe {
public:
    static bool isNeeded()
    {
        static std::once_flag onceFlag;
        std::call_once(onceFlag, initializeIsNeeded);
        return s_isNeeded;
    }

    static void installIfNeeded(GstAppSink* appsink)
    {
        ASSERT(GST_IS_APP_SINK(appsink));
        if (!isNeeded())
            return;

        GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(GST_ELEMENT(appsink), "sink"));
        GST_DEBUG_OBJECT(pad.get(), "Installing AppSinkFlushCapsWorkaroundProbe.");
        gst_pad_add_probe(pad.get(), static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_FLUSH | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM),
            probe, newUserData(), deleteUserData);
    }

private:
    static bool s_isNeeded;

    // Must only be read and written with the pad lock.
    bool m_needsResendCaps { false };

    static bool checkIsNeeded()
    {
        // Instantiate an appsink once to force gst-plugins-base to load.
        GRefPtr<GstElementFactory> factoryAppSink = adoptGRef(gst_element_factory_find("appsink"));
        if (!factoryAppSink) {
            WTFLogAlways("GStreamer element appsink not found. Please install it.");
            return false;
        }

#ifndef GST_DISABLE_GST_DEBUG
        GUniquePtr<char> version(gst_plugins_base_version_string());
        GST_DEBUG("AppSinkFlushCapsWorkaroundProbe: gst-plugins-base version is %s, bug was fixed in 1.21.1 and backported to 1.20.3.", version.get());
#endif
        WorkaroundMode mode = getWorkAroundModeFromEnvironment("WEBKIT_GST_WORKAROUND_APP_SINK_FLUSH_CAPS", WEBKIT_GST_WORKAROUND_APP_SINK_FLUSH_CAPS_DEFAULT_MODE);
        if (mode == WorkaroundMode::ForceEnable) {
            GST_DEBUG("AppSinkFlushCapsWorkaroundProbe: forcing workaround to be enabled.");
            return true;
        }
        if (mode == WorkaroundMode::ForceDisable) {
            GST_DEBUG("AppSinkFlushCapsWorkaroundProbe: forcing workaround to be disabled.");
            return false;
        }

        guint major, minor, micro;
        gst_plugins_base_version(&major, &minor, &micro, nullptr);

        if (major < 1)
            return true;
        if (major > 1)
            return false;

        if (minor < 20)
            return true;
        if (minor >= 22)
            return false;
        if (minor == 21)
            return micro < 1; // Fix landed in 1.21.1 https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2413

        ASSERT(minor == 20);
        return micro < 3; // Fix was backported to 1.20.3 https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2442
    }

    static void initializeIsNeeded()
    {
        s_isNeeded = checkIsNeeded();
        GST_DEBUG("AppSinkFlushCapsWorkaroundProbe is%s needed in this system.", s_isNeeded ? "" : " NOT");
    }

    static GstPadProbeReturn probe(GstPad* pad, GstPadProbeInfo* info, void* userData)
    {
        auto* self = static_cast<AppSinkFlushCapsWorkaroundProbe*>(userData);

        // Changes to the flushing flag of a pad only occur while the pad lock is held.
        // By holding it, we can reliably prevent the flushing flag from changing during the execution of our code.
        // The pad lock also ensures there are no data races over `priv->needsResendCaps`.
        GstObjectLocker padLocker(pad);
        bool willResendCaps = false;
        if ((info->type & GST_PAD_PROBE_TYPE_EVENT_FLUSH) && GST_EVENT_TYPE(info->data) == GST_EVENT_FLUSH_STOP) {
            GST_TRACE_OBJECT(pad, "Flush event received, setting needsResendCaps = true");
            self->m_needsResendCaps = true;
        } else if (!GST_PAD_IS_FLUSHING(pad) && (info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) && GST_EVENT_TYPE(info->data) == GST_EVENT_CAPS) {
            GST_TRACE_OBJECT(pad, "Caps event received, setting needsResendCaps = false");
            self->m_needsResendCaps = false;
        } else if (!GST_PAD_IS_FLUSHING(pad) && (info->type & GST_PAD_PROBE_TYPE_BUFFER) && self->m_needsResendCaps) {
            GST_DEBUG_OBJECT(pad, "Buffer received, but first need to resend pad caps to workaround bug. Will resend caps.");
            willResendCaps = true;
        }
        padLocker.unlockEarly();

        if (willResendCaps) {
            GRefPtr<GstCaps> caps = adoptGRef(gst_pad_get_current_caps(pad));
            GST_DEBUG_OBJECT(pad, "Sending stored pad caps to appsink: %" GST_PTR_FORMAT, caps.get());
            // This will cause a recursive call to appsinkWorkaroundProbe() which will also set `needsResendCaps` to false.
            [[maybe_unused]] bool wereCapsSent = gst_pad_send_event(pad, gst_event_new_caps(caps.get()));
            GST_DEBUG_OBJECT(pad, "wereCapsSent = %s. Returning from the probe so that the buffer is sent: %" GST_PTR_FORMAT, boolForPrinting(wereCapsSent), info->data);
        }

        return GST_PAD_PROBE_OK;
    }

    static void* newUserData() { return new AppSinkFlushCapsWorkaroundProbe; }
    static void deleteUserData(void* self) { delete static_cast<AppSinkFlushCapsWorkaroundProbe*>(self); }
};

bool AppSinkFlushCapsWorkaroundProbe::s_isNeeded;

static void registerAppsinkWithWorkaroundsIfNeededCallOnce()
{
    // If any workarounds are needed in this system, override GStreamer appsink for a version containing any needed workarounds.
    GST_DEBUG_CATEGORY_INIT(webkit_workarounds_debug, "webkitworkarounds", 0, "WebKit GStreamer Workarounds");
    GST_DEBUG("Checking for potentially needed GStreamer workarounds...");
    bool doesNeedWorkarounds = BaseSinkPositionFlushWorkaroundProbe::isNeeded() || AppSinkFlushCapsWorkaroundProbe::isNeeded();
    GST_DEBUG("WebKitAppsinkWithWorkarounds WILL%s be registered.", doesNeedWorkarounds ? "" : " NOT");
    if (!doesNeedWorkarounds)
        return;

    // Here is some quirkiness: We need to load both appsrc and appsink, as both will cause plugin_init() to
    // be called, which will register both elements.
    // If we don't ensure the appsrc factory has been loaded before registering our appsink override, appsink
    // will be re-registered by the "app" plugin next time an appsrc is requested, overwriting our workaround.
    GRefPtr<GstElement>(gst_element_factory_make("appsink", "preload-dummy-appsink"));
    GRefPtr<GstElement>(gst_element_factory_make("appsrc", "preload-dummy-appsrc"));

    gst_element_register(nullptr, "appsink", GST_RANK_PRIMARY + 1000, WEBKIT_TYPE_APP_SINK_WITH_WORKAROUNDS);
}

void registerAppsinkWithWorkaroundsIfNeeded()
{
    static std::once_flag onceFlag;
    std::call_once(onceFlag, registerAppsinkWithWorkaroundsIfNeededCallOnce);
}

void installBaseSinkPositionFlushWorkaroundIfNeeded(GstBaseSink* basesink)
{
    return BaseSinkPositionFlushWorkaroundProbe::installIfNeeded(basesink);
}

} // namespace WebCore

struct WebKitAppSinkWithWorkaroundsPrivate {
};

WEBKIT_DEFINE_TYPE(WebKitAppSinkWithWorkarounds, webkit_app_sink_with_workarounds, GST_TYPE_APP_SINK);

static void webkitAppSinkWithWorkAroundsConstructed(GObject* object)
{
    G_OBJECT_CLASS(webkit_app_sink_with_workarounds_parent_class)->constructed(object);

    GST_DEBUG_OBJECT(object, "WebKitAppSinkWithWorkarounds instantiated.");
    WebCore::AppSinkFlushCapsWorkaroundProbe::installIfNeeded(GST_APP_SINK(object));
    WebCore::installBaseSinkPositionFlushWorkaroundIfNeeded(GST_BASE_SINK(object));
}

static void webkit_app_sink_with_workarounds_class_init(WebKitAppSinkWithWorkaroundsClass* klass)
{
    auto* gobjectClass = G_OBJECT_CLASS(klass);
    gobjectClass->constructed = webkitAppSinkWithWorkAroundsConstructed;
}

#undef GST_CAT_DEFAULT

#endif // USE(GSTREAMER)