Skip to content

Commit 11db264

Browse files
ntrrgcvivienne-w
authored andcommitted
[MSE][GStreamer] Workaround basesink's lack of support for position queries between a non-resetting flush and a pre-roll
https://bugs.webkit.org/show_bug.cgi?id=248683 Reviewed by Xabier Rodriguez-Calvar. Workaround for upstream GStreamer patch projected to land in GStreamer 1.24.0: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3471 basesink: Support position queries after non-resetting flushes Briefly explained, before the fix GStreamer sinks will fail to answer position queries after a FLUSH_STOP(reset_time=FALSE) until they receive a new segment, even though the sinks in this situation will preserve the previous segment. This is particularly a problem for streams with quality changes, such as MSE, but potentially also native adaptive streaming implementations, as the currentTime will suddenly reset to zero, and unlike seeks, quality change flushes generally occur automatically without user intervention. The bug means that currentTime can reset to zero seemingly randomly when a stream is flushed for reason other than a seek, notably in MSE, and most notably after WebKit/WebKit#3966 finally gets landed. This has been seen in failures for the test media/media-source/media-source-seek-unbuffered.html -- although that test has several simultaneous unrelated issues that need work on. To demonstrate the issue in a more isolated case, a new test case has been added: imported/w3c/web-platform-tests/media-source/mediasource-remove-preserves-currentTime.html As of writing there are no plans currently for the patch to be officially backported into the GStreamer 1.22.x series due to concerns over making changes on critical paths of critical elements. Therefore, in order to not suffer this problem in WebKit with any currently released versions of GStreamer, we need a workaround. This is a difficult bug to work around: you can add probes that capture queries flowing through a pipeline, but you cannot add a probe that captures a query that is sent directly to an element without going through any pads (e.g. with gst_element_query_position()). Unfortunately this is more often than not how position queries are made. Since we cannot monkey-patch or probe the position queries for basesink directly, the workaround is instead based on analysis and manipulation of the basesink internals so that gst_base_sink_get_position() won't fail in the desired case, by tweaking basesink->have_newsegment inside a probe for the FLUSH_STOP event. I went through lots of different approaches for this workaround over weeks, and the workaround method proposed was the final one that was able to fix the issue without introducing any regressions in LayoutTests. This is the second workaround for an issue in GStreamer sinks that takes advantage of re-registering elements. To keep things clean, both workarounds have been grouped in a new file GStreamerSinksWorkarounds.cpp, and refactored into classes with similar interfaces. Also, now the workarounds can be enabled and disabled independently with new environment variables: * WEBKIT_GST_WORKAROUND_APP_SINK_FLUSH_CAPS * WEBKIT_GST_WORKAROUND_BASE_SINK_POSITION_FLUSH Allowed values are: ForceEnable, ForceDisabled and UseIfNeeded (case-insensitive). The latter enables each workaround only for GStreamer versions known to need it. UseIfNeeded is the default value for both workarounds, but this can be changed at build time if desired by setting the following macros: * WEBKIT_GST_WORKAROUND_APP_SINK_FLUSH_CAPS_DEFAULT_MODE * WEBKIT_GST_WORKAROUND_BASE_SINK_POSITION_FLUSH_DEFAULT_MODE * LayoutTests/imported/w3c/web-platform-tests/media-source/mediasource-remove-preserves-currentTime.html: Added. * LayoutTests/imported/w3c/web-platform-tests/media-source/mediasource-remove-preserves-currentTime-expected.txt: Added. * Source/WebCore/platform/GStreamer.cmake: * Source/WebCore/platform/graphics/gstreamer/AppSinkWorkaround.cpp: Removed. * Source/WebCore/platform/graphics/gstreamer/AppSinkWorkaround.h: Removed. * Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.cpp: (WTF::adoptGRef): (WTF::refGPtr<GstBaseSink>): (WTF::derefGPtr<GstBaseSink>): * Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.h: * Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp: (WebCore::ensureGStreamerInitialized): * Source/WebCore/platform/graphics/gstreamer/GStreamerSinksWorkarounds.cpp: Added. (WebCore::getWorkAroundModeFromEnvironment): (WebCore::BaseSinkPositionFlushWorkaroundProbe::isNeeded): (WebCore::BaseSinkPositionFlushWorkaroundProbe::installIfNeeded): (WebCore::BaseSinkPositionFlushWorkaroundProbe::checkIsNeeded): (WebCore::BaseSinkPositionFlushWorkaroundProbe::initializeIsNeeded): (WebCore::BaseSinkPositionFlushWorkaroundProbe::flushIsNonResetting): (WebCore::BaseSinkPositionFlushWorkaroundProbe::probe): (WebCore::BaseSinkPositionFlushWorkaroundProbe::newUserData): (WebCore::BaseSinkPositionFlushWorkaroundProbe::deleteUserData): (WebCore::AppSinkFlushCapsWorkaroundProbe::isNeeded): (WebCore::AppSinkFlushCapsWorkaroundProbe::installIfNeeded): (WebCore::AppSinkFlushCapsWorkaroundProbe::checkIsNeeded): (WebCore::AppSinkFlushCapsWorkaroundProbe::initializeIsNeeded): (WebCore::AppSinkFlushCapsWorkaroundProbe::probe): (WebCore::AppSinkFlushCapsWorkaroundProbe::newUserData): (WebCore::AppSinkFlushCapsWorkaroundProbe::deleteUserData): (WebCore::registerAppsinkWithWorkaroundsIfNeededCallOnce): (WebCore::registerAppsinkWithWorkaroundsIfNeeded): (WebCore::installBaseSinkPositionFlushWorkaroundIfNeeded): (webkitAppSinkWithWorkAroundsConstructed): (webkit_app_sink_with_workarounds_class_init): * Source/WebCore/platform/graphics/gstreamer/GStreamerSinksWorkarounds.h: Added. * Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp: (WebCore::MediaPlayerPrivateGStreamer::gstreamerPositionFromSinks const): * Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp: (webKitMediaSrcStreamFlush): * Tools/Scripts/webkitpy/style/checker.py: Canonical link: https://commits.webkit.org/264476@main
1 parent dc705bc commit 11db264

13 files changed

Lines changed: 495 additions & 202 deletions
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
PASS Test removing coded frames during playback doesn't regress currentTime
3+
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!DOCTYPE html>
2+
<!-- Copyright © 2023 Igalia S.L. -->
3+
<!-- Copyright © 2023 Metrological -->
4+
<html>
5+
<head>
6+
<meta name="charset" content="UTF-8">
7+
<title>Test currentTime doesn't regress after erasing the current playback position.</title>
8+
<meta name="timeout" content="long">
9+
<link rel="author" title="Alicia Boya García" href="mailto:aboya@igalia.com">
10+
<script src="/resources/testharness.js"></script>
11+
<script src="/resources/testharnessreport.js"></script>
12+
<script src="mediasource-util.js"></script>
13+
</head>
14+
<body>
15+
<div id="log"></div>
16+
<script>
17+
18+
function waitForCurrentTimeToReach(test, mediaElement, targetTime, callback)
19+
{
20+
let lastTime = mediaElement.currentTime;
21+
const timeUpdateHandler = test.step_func(function _timeUpdateHandler() {
22+
let newTime = mediaElement.currentTime;
23+
assert_greater_than_equal(newTime, lastTime);
24+
lastTime = newTime;
25+
if (newTime >= targetTime) {
26+
mediaElement.removeEventListener('timeupdate', timeUpdateHandler);
27+
callback();
28+
}
29+
});
30+
mediaElement.addEventListener('timeupdate', timeUpdateHandler);
31+
}
32+
33+
function testCurrentTimeDoesNotRegress(test, mediaElement, initialTime, callback)
34+
{
35+
let numChecksLeft = 10; // 0.5 seconds
36+
let lastTime = initialTime;
37+
const intervalId = setInterval(test.step_func(function() {
38+
let newTime = mediaElement.currentTime;
39+
assert_greater_than_equal(newTime, lastTime);
40+
lastTime = newTime;
41+
if (numChecksLeft-- <= 0) {
42+
clearInterval(intervalId);
43+
callback();
44+
}
45+
}, 50));
46+
}
47+
48+
mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
49+
{
50+
test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer');
51+
sourceBuffer.appendBuffer(mediaData)
52+
53+
test.waitForExpectedEvents(function() {
54+
assert_equals(mediaElement.currentTime, 0);
55+
test.expectEvent(mediaElement, 'playing', 'Playing triggered');
56+
mediaElement.play();
57+
waitForCurrentTimeToReach(test, mediaElement, 0.1, test.step_func(whenPlaybackIsMoving));
58+
})
59+
60+
function whenPlaybackIsMoving()
61+
{
62+
testCurrentTimeDoesNotRegress(test, mediaElement, mediaElement.currentTime, () => test.done())
63+
sourceBuffer.remove(0, 10);
64+
}
65+
}, "Test removing coded frames during playback doesn't regress currentTime");
66+
67+
</script>
68+
</body>
69+
</html>

Source/WebCore/platform/GStreamer.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ if (ENABLE_VIDEO OR ENABLE_WEB_AUDIO)
2626

2727
Modules/webaudio/MediaStreamAudioSourceGStreamer.cpp
2828

29-
platform/graphics/gstreamer/AppSinkWorkaround.cpp
29+
platform/graphics/gstreamer/GStreamerSinksWorkarounds.cpp
3030
platform/graphics/gstreamer/AudioTrackPrivateGStreamer.cpp
3131
platform/graphics/gstreamer/DMABufVideoSinkGStreamer.cpp
3232
platform/graphics/gstreamer/GLVideoSinkGStreamer.cpp

Source/WebCore/platform/graphics/gstreamer/AppSinkWorkaround.cpp

Lines changed: 0 additions & 144 deletions
This file was deleted.

Source/WebCore/platform/graphics/gstreamer/AppSinkWorkaround.h

Lines changed: 0 additions & 52 deletions
This file was deleted.

Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,26 @@ template <> void derefGPtr<WebKitVideoSink>(WebKitVideoSink* ptr)
506506
gst_object_unref(GST_OBJECT(ptr));
507507
}
508508

509+
template <> GRefPtr<GstBaseSink> adoptGRef(GstBaseSink* ptr)
510+
{
511+
ASSERT(!ptr || !g_object_is_floating(ptr));
512+
return GRefPtr<GstBaseSink>(ptr, GRefPtrAdopt);
513+
}
514+
515+
template <> GstBaseSink* refGPtr<GstBaseSink>(GstBaseSink* ptr)
516+
{
517+
if (ptr)
518+
gst_object_ref_sink(GST_OBJECT(ptr));
519+
520+
return ptr;
521+
}
522+
523+
template <> void derefGPtr<GstBaseSink>(GstBaseSink* ptr)
524+
{
525+
if (ptr)
526+
gst_object_unref(GST_OBJECT(ptr));
527+
}
528+
509529
template <> GRefPtr<WebKitWebSrc> adoptGRef(WebKitWebSrc* ptr)
510530
{
511531
ASSERT(!ptr || !g_object_is_floating(ptr));

Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
typedef struct _WebKitVideoSink WebKitVideoSink;
2929
struct WebKitWebSrc;
30+
typedef struct _GstBaseSink GstBaseSink;
3031

3132
#if USE(GSTREAMER_GL)
3233
typedef struct _GstGLDisplay GstGLDisplay;
@@ -59,6 +60,10 @@ template<> GRefPtr<GstElement> adoptGRef(GstElement* ptr);
5960
template<> GstElement* refGPtr<GstElement>(GstElement* ptr);
6061
template<> void derefGPtr<GstElement>(GstElement* ptr);
6162

63+
template<> GRefPtr<GstBaseSink> adoptGRef(GstBaseSink* ptr);
64+
template<> GstBaseSink* refGPtr<GstBaseSink>(GstBaseSink* ptr);
65+
template<> void derefGPtr<GstBaseSink>(GstBaseSink* ptr);
66+
6267
template<> GRefPtr<GstPad> adoptGRef(GstPad* ptr);
6368
template<> GstPad* refGPtr<GstPad>(GstPad* ptr);
6469
template<> void derefGPtr<GstPad>(GstPad* ptr);

Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323

2424
#if USE(GSTREAMER)
2525

26-
#include "AppSinkWorkaround.h"
2726
#include "ApplicationGLib.h"
2827
#include "DMABufVideoSinkGStreamer.h"
2928
#include "GLVideoSinkGStreamer.h"
3029
#include "GStreamerAudioMixer.h"
3130
#include "GStreamerRegistryScanner.h"
31+
#include "GStreamerSinksWorkarounds.h"
3232
#include "GUniquePtrGStreamer.h"
3333
#include "GstAllocatorFastMalloc.h"
3434
#include "IntSize.h"
@@ -307,8 +307,7 @@ bool ensureGStreamerInitialized()
307307
gst_mpegts_initialize();
308308
#endif
309309

310-
// Workaround for https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2413
311-
registerAppsinkWorkaroundIfNeeded();
310+
registerAppsinkWithWorkaroundsIfNeeded();
312311
#endif
313312
});
314313
return isGStreamerInitialized;

0 commit comments

Comments
 (0)