Skip to content

Commit a1f6b0b

Browse files
committed
[GStreamer][WebAudio] Garbled rendering when the internal audio mixer is enabled
https://bugs.webkit.org/show_bug.cgi?id=298235 Reviewed by Xabier Rodriguez-Calvar. When the WEBKIT_GST_ENABLE_AUDIO_MIXER=1 environment variable is set, audio buffers are sent to a separate pipeline using interaudiosink. That process-wide singleton pipeline is then in charge of audio mixing and rendering to an actual audio device. For WebAudio it wasn't working properly, leading to garbled rendering, due to the audiomixer sink pad corresponding to the WebAudio source not using the same sample rate and also interaudiosrc using a sample period-size too big. We now align these values between the WebAudio sink and its matching interaudiosrc audiomixer branch. The Rialto WebAudio sink configuration was also moved to the Rialto quirk and the version check removed (I couldn't find the reasoning behind that). * Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp: (WebCore::AudioDestinationGStreamer::AudioDestinationGStreamer): * Source/WebCore/platform/graphics/gstreamer/GStreamerAudioMixer.cpp: (WebCore::GStreamerAudioMixer::registerProducer): (WebCore::GStreamerAudioMixer::configureSourcePeriodTime): * Source/WebCore/platform/graphics/gstreamer/GStreamerAudioMixer.h: * Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp: (WebCore::createPlatformAudioSink): * Source/WebCore/platform/graphics/gstreamer/WebKitAudioSinkGStreamer.cpp: (webKitAudioSinkConfigure): (webKitAudioSinkChangeState): (webKitAudioSinkConstructed): (webkitAudioSinkNew): * Source/WebCore/platform/graphics/gstreamer/WebKitAudioSinkGStreamer.h: * Source/WebCore/platform/gstreamer/GStreamerQuirkRialto.cpp: (WebCore::GStreamerQuirkRialto::createWebAudioSink): * Source/WebCore/platform/gstreamer/GStreamerQuirks.cpp: (WebCore::GStreamerQuirksManager::createWebAudioSink): Canonical link: https://commits.webkit.org/299489@main
1 parent 47b1605 commit a1f6b0b

File tree

8 files changed

+101
-36
lines changed

8 files changed

+101
-36
lines changed

Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -165,24 +165,10 @@ AudioDestinationGStreamer::AudioDestinationGStreamer(const CreationOptions& opti
165165

166166
gst_bin_add_many(GST_BIN_CAST(m_pipeline.get()), m_src.get(), audioConvert, audioResample, clockSync, audioSink.get(), nullptr);
167167

168-
// Link src pads from webkitAudioSrc to clocksync ! audioConvert ! audioResample ! [capsfilter !] autoaudiosink.
168+
// Link src pads from webkitAudioSrc to clocksync ! audioConvert ! audioResample ! audiosink.
169169
gst_element_link_pads_full(m_src.get(), "src", clockSync, "sink", GST_PAD_LINK_CHECK_NOTHING);
170170
gst_element_link_pads_full(clockSync, "src", audioConvert, "sink", GST_PAD_LINK_CHECK_NOTHING);
171171
gst_element_link_pads_full(audioConvert, "src", audioResample, "sink", GST_PAD_LINK_CHECK_NOTHING);
172-
173-
if (!webkitGstCheckVersion(1, 20, 4)) {
174-
// Force audio conversion to 'interleaved' format (by audioconvert element).
175-
// 1) Some platform sinks don't support non-interleaved audio without special caps (rialtowebaudiosink).
176-
// 2) Interaudio sink/src doesn't fully support non-interleaved audio (webkit audio sink)
177-
// 3) audiomixer doesn't support non-interleaved audio in output pipeline (webkit audio sink)
178-
GstElement* capsFilter = makeGStreamerElement("capsfilter"_s);
179-
GRefPtr<GstCaps> caps = adoptGRef(gst_caps_new_simple("audio/x-raw", "layout", G_TYPE_STRING, "interleaved", nullptr));
180-
g_object_set(capsFilter, "caps", caps.get(), nullptr);
181-
gst_bin_add(GST_BIN_CAST(m_pipeline.get()), capsFilter);
182-
gst_element_link_pads_full(audioResample, "src", capsFilter, "sink", GST_PAD_LINK_CHECK_NOTHING);
183-
gst_element_link_pads_full(capsFilter, "src", audioSink.get(), "sink", GST_PAD_LINK_CHECK_NOTHING);
184-
return;
185-
}
186172
gst_element_link_pads_full(audioResample, "src", audioSink.get(), "sink", GST_PAD_LINK_CHECK_NOTHING);
187173
}
188174

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ void GStreamerAudioMixer::ensureState(GstStateChange stateChange)
8686
}
8787
}
8888

89-
GRefPtr<GstPad> GStreamerAudioMixer::registerProducer(GstElement* interaudioSink)
89+
GRefPtr<GstPad> GStreamerAudioMixer::registerProducer(GstElement* interaudioSink, std::optional<int> forcedSampleRate)
9090
{
91-
GstElement* src = makeGStreamerElement("interaudiosrc"_s);
91+
auto name = StringView::fromLatin1(GST_ELEMENT_NAME(interaudioSink));
92+
GstElement* src = makeGStreamerElement("interaudiosrc"_s, name.toStringWithoutCopying());
93+
9294
g_object_set(src, "channel", GST_ELEMENT_NAME(interaudioSink), nullptr);
9395
g_object_set(interaudioSink, "channel", GST_ELEMENT_NAME(interaudioSink), nullptr);
9496

@@ -98,6 +100,14 @@ GRefPtr<GstPad> GStreamerAudioMixer::registerProducer(GstElement* interaudioSink
98100
gst_bin_add_many(GST_BIN_CAST(bin), audioResample, audioConvert, nullptr);
99101
gst_element_link(audioConvert, audioResample);
100102

103+
if (forcedSampleRate) {
104+
auto capsfilter = gst_element_factory_make("capsfilter", nullptr);
105+
auto caps = adoptGRef(gst_caps_new_simple("audio/x-raw", "rate", G_TYPE_INT, *forcedSampleRate, nullptr));
106+
g_object_set(capsfilter, "caps", caps.get(), nullptr);
107+
gst_bin_add(GST_BIN_CAST(bin), capsfilter);
108+
gst_element_link(audioResample, capsfilter);
109+
}
110+
101111
if (auto pad = adoptGRef(gst_bin_find_unlinked_pad(GST_BIN_CAST(bin), GST_PAD_SRC)))
102112
gst_element_add_pad(GST_ELEMENT_CAST(bin), gst_ghost_pad_new("src", pad.get()));
103113
if (auto pad = adoptGRef(gst_bin_find_unlinked_pad(GST_BIN_CAST(bin), GST_PAD_SINK)))
@@ -148,6 +158,15 @@ void GStreamerAudioMixer::unregisterProducer(const GRefPtr<GstPad>& mixerPad)
148158
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN_CAST(m_pipeline.get()), GST_DEBUG_GRAPH_SHOW_ALL, "audio-mixer-after-producer-unregistration");
149159
}
150160

161+
void GStreamerAudioMixer::configureSourcePeriodTime(StringView sourceName, uint64_t periodTime)
162+
{
163+
auto src = adoptGRef(gst_bin_get_by_name(GST_BIN_CAST(m_pipeline.get()), sourceName.toStringWithoutCopying().ascii().data()));
164+
if (!src) [[unlikely]]
165+
return;
166+
167+
g_object_set(src.get(), "period-time", periodTime, nullptr);
168+
}
169+
151170
#undef GST_CAT_DEFAULT
152171

153172
} // namespace WebCore

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ class GStreamerAudioMixer {
3333
static GStreamerAudioMixer& singleton();
3434

3535
void ensureState(GstStateChange);
36-
GRefPtr<GstPad> registerProducer(GstElement*);
36+
GRefPtr<GstPad> registerProducer(GstElement*, std::optional<int> forcedSampleRate);
3737
void unregisterProducer(const GRefPtr<GstPad>&);
3838

39+
void configureSourcePeriodTime(StringView sourceName, uint64_t periodTime);
40+
3941
private:
4042
GStreamerAudioMixer();
4143

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,14 +1026,12 @@ IGNORE_WARNINGS_END
10261026

10271027
GstElement* /* (transfer floating) */ createPlatformAudioSink(const String& role)
10281028
{
1029-
GstElement* audioSink = webkitAudioSinkNew();
1029+
GstElement* audioSink = webkitAudioSinkNew(role);
10301030
if (!audioSink) {
10311031
// This means the WebKit audio sink configuration failed. It can happen for the following reasons:
10321032
// - audio mixing was not requested using the WEBKIT_GST_ENABLE_AUDIO_MIXER
10331033
// - audio mixing was requested using the WEBKIT_GST_ENABLE_AUDIO_MIXER but the audio mixer
10341034
// runtime requirements are not fullfilled.
1035-
// - the sink was created for the WPE port, audio mixing was not requested and no
1036-
// WPEBackend-FDO audio receiver has been registered at runtime.
10371035
audioSink = createAutoAudioSink(role);
10381036
}
10391037

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

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,21 @@
2222

2323
#if USE(GSTREAMER)
2424

25+
#include "AudioUtilities.h"
2526
#include "GStreamerAudioMixer.h"
2627
#include "GStreamerCommon.h"
2728
#include <wtf/glib/WTFGType.h>
2829

30+
#if ENABLE(WEB_AUDIO)
31+
#include "AudioDestination.h"
32+
#endif
33+
2934
using namespace WebCore;
3035

3136
struct _WebKitAudioSinkPrivate {
3237
GRefPtr<GstElement> interAudioSink;
3338
GRefPtr<GstPad> mixerPad;
39+
String role;
3440
};
3541

3642
enum {
@@ -65,6 +71,36 @@ static bool webKitAudioSinkConfigure(WebKitAudioSink* sink)
6571
gst_bin_add(GST_BIN_CAST(sink), sink->priv->interAudioSink.get());
6672
auto targetPad = adoptGRef(gst_element_get_static_pad(sink->priv->interAudioSink.get(), "sink"));
6773
gst_element_add_pad(GST_ELEMENT_CAST(sink), webkitGstGhostPadFromStaticTemplate(&audioSinkTemplate, "sink"_s, targetPad.get()));
74+
75+
if (sink->priv->role != "webaudio"_s)
76+
return true;
77+
78+
// Match the interaudiosrc period-time with the WebAudio renderQuantumSize applied to the
79+
// sample rate, otherwise the samples created by the source will have clipping, leading to
80+
// garbled rendering. For this to work the sample rate also needs to match between
81+
// webkitaudiosink and the caps negotiated on the audiomixer sink pad (this is handled in
82+
// webKitAudioSinkChangeState()).
83+
gst_pad_add_probe(targetPad.get(), GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, reinterpret_cast<GstPadProbeCallback>(+[](GstPad* pad, GstPadProbeInfo* info, gpointer) -> GstPadProbeReturn {
84+
auto event = GST_PAD_PROBE_INFO_EVENT(info);
85+
if (GST_EVENT_TYPE(event) != GST_EVENT_CAPS)
86+
return GST_PAD_PROBE_OK;
87+
88+
GstCaps* caps;
89+
gst_event_parse_caps(event, &caps);
90+
91+
if (gst_caps_is_empty(caps) || gst_caps_is_any(caps)) [[unlikely]]
92+
return GST_PAD_PROBE_OK;
93+
94+
auto structure = gst_caps_get_structure(caps, 0);
95+
auto sampleRate = gstStructureGet<int>(structure, "rate");
96+
if (!sampleRate) [[unlikely]]
97+
return GST_PAD_PROBE_OK;
98+
99+
auto sink = adoptGRef(gst_pad_get_parent_element(pad));
100+
uint64_t periodTime = gst_util_uint64_scale_ceil(AudioUtilities::renderQuantumSize, GST_SECOND, *sampleRate);
101+
GStreamerAudioMixer::singleton().configureSourcePeriodTime(StringView::fromLatin1(GST_ELEMENT_NAME(sink.get())), periodTime);
102+
return GST_PAD_PROBE_OK;
103+
}), nullptr, nullptr);
68104
return true;
69105
}
70106
return false;
@@ -116,8 +152,14 @@ static GstStateChangeReturn webKitAudioSinkChangeState(GstElement* element, GstS
116152
GST_DEBUG_OBJECT(sink, "Handling %s transition", gst_state_change_get_name(stateChange));
117153

118154
auto& mixer = GStreamerAudioMixer::singleton();
119-
if (priv->interAudioSink && stateChange == GST_STATE_CHANGE_NULL_TO_READY)
120-
priv->mixerPad = mixer.registerProducer(priv->interAudioSink.get());
155+
if (priv->interAudioSink && stateChange == GST_STATE_CHANGE_NULL_TO_READY) {
156+
std::optional<int> forcedSampleRate;
157+
#if ENABLE(WEB_AUDIO)
158+
if (priv->role == "webaudio"_s)
159+
forcedSampleRate = AudioDestination::hardwareSampleRate();
160+
#endif
161+
priv->mixerPad = mixer.registerProducer(priv->interAudioSink.get(), forcedSampleRate);
162+
}
121163

122164
if (priv->mixerPad)
123165
mixer.ensureState(stateChange);
@@ -135,10 +177,10 @@ static GstStateChangeReturn webKitAudioSinkChangeState(GstElement* element, GstS
135177
static void webKitAudioSinkConstructed(GObject* object)
136178
{
137179
G_OBJECT_CLASS(webkit_audio_sink_parent_class)->constructed(object);
138-
IGNORE_WARNINGS_BEGIN("cast-align")
180+
IGNORE_WARNINGS_BEGIN("cast-align");
139181
GST_OBJECT_FLAG_SET(GST_OBJECT_CAST(object), GST_ELEMENT_FLAG_SINK);
140182
gst_bin_set_suppressed_flags(GST_BIN_CAST(object), static_cast<GstElementFlags>(GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK));
141-
IGNORE_WARNINGS_END
183+
IGNORE_WARNINGS_END;
142184
}
143185

144186
static void webkit_audio_sink_class_init(WebKitAudioSinkClass* klass)
@@ -164,15 +206,18 @@ static void webkit_audio_sink_class_init(WebKitAudioSinkClass* klass)
164206
eklass->change_state = GST_DEBUG_FUNCPTR(webKitAudioSinkChangeState);
165207
}
166208

167-
GstElement* /* (transfer floating) */ webkitAudioSinkNew()
209+
GstElement* /* (transfer floating) */ webkitAudioSinkNew(const String& role)
168210
{
169-
auto* sink = GST_ELEMENT_CAST(g_object_new(WEBKIT_TYPE_AUDIO_SINK, nullptr));
170-
if (!webKitAudioSinkConfigure(WEBKIT_AUDIO_SINK(sink))) {
171-
gst_object_unref(sink);
211+
auto element = GST_ELEMENT_CAST(g_object_new(WEBKIT_TYPE_AUDIO_SINK, nullptr));
212+
auto audioSink = WEBKIT_AUDIO_SINK(element);
213+
214+
audioSink->priv->role = role;
215+
if (!webKitAudioSinkConfigure(audioSink)) {
216+
gst_object_unref(element);
172217
return nullptr;
173218
}
174-
ASSERT(g_object_is_floating(sink));
175-
return sink;
219+
ASSERT(g_object_is_floating(element));
220+
return element;
176221
}
177222

178223
#undef GST_CAT_DEFAULT

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@ GType webkit_audio_sink_get_type(void);
4848

4949
G_END_DECLS
5050

51-
GstElement* webkitAudioSinkNew();
51+
GstElement* webkitAudioSinkNew(const String&);
5252

5353
#endif // USE(GSTREAMER)

Source/WebCore/platform/gstreamer/GStreamerQuirkRialto.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,29 @@ GstElement* GStreamerQuirkRialto::createAudioSink()
9494
return sink;
9595
}
9696

97-
GstElement* GStreamerQuirkRialto::createWebAudioSink()
97+
GstElement* /* transfer floating */ GStreamerQuirkRialto::createWebAudioSink()
9898
{
99-
if (GstElement* sink = webkitAudioSinkNew())
99+
if (GstElement* sink = webkitAudioSinkNew("webaudio"_s))
100100
return sink;
101101

102102
auto sink = makeGStreamerElement("rialtowebaudiosink"_s);
103103
RELEASE_ASSERT_WITH_MESSAGE(sink, "rialtowebaudiosink should be available in the system but it is not");
104-
return sink;
104+
105+
// Force audio conversion to 'interleaved' format. The rialtowebaudiosink doesn't support
106+
// non-interleaved audio without special caps, which seems like a bug in that sink's caps
107+
// template and/or caps negotiation implementation.
108+
auto bin = gst_bin_new(nullptr);
109+
auto capsFilter = gst_element_factory_make("capsfilter", nullptr);
110+
auto caps = adoptGRef(gst_caps_new_simple("audio/x-raw", "layout", G_TYPE_STRING, "interleaved", nullptr));
111+
g_object_set(capsFilter, "caps", caps.get(), nullptr);
112+
113+
gst_bin_add_many(GST_BIN_CAST(bin), capsFilter, sink, nullptr);
114+
gst_element_link(capsFilter, sink);
115+
116+
auto pad = adoptGRef(gst_element_get_static_pad(capsFilter, "sink"));
117+
gst_element_add_pad(bin, gst_ghost_pad_new("sink", pad.get()));
118+
119+
return bin;
105120
}
106121

107122
std::optional<bool> GStreamerQuirkRialto::isHardwareAccelerated(GstElementFactory* factory)

Source/WebCore/platform/gstreamer/GStreamerQuirks.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ GstElement* GStreamerQuirksManager::createWebAudioSink()
168168
}
169169

170170
GST_DEBUG("Quirks didn't specify a WebAudioSink, falling back to default sink");
171-
return createPlatformAudioSink("music"_s);
171+
return createPlatformAudioSink("webaudio"_s);
172172
}
173173

174174
GstElement* GStreamerQuirksManager::createHolePunchVideoSink(bool isLegacyPlaybin, const MediaPlayer* player)

0 commit comments

Comments
 (0)