Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgstreameraudiooutput.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <common/qgstreameraudiooutput_p.h>
5
6#include <QtCore/qloggingcategory.h>
7#include <QtCore/qversionnumber.h>
8#include <QtMultimedia/qaudiodevice.h>
9#include <QtMultimedia/qaudiooutput.h>
10
11#include <common/qgstpipeline_p.h>
12#include <audio/qgstreameraudiodevice_p.h>
13
14#if QT_CONFIG(pulseaudio)
15# include <pulse/version.h>
16#endif
17
19
20namespace {
21
22Q_STATIC_LOGGING_CATEGORY(qLcMediaAudioOutput, "qt.multimedia.audiooutput")
23
25 using namespace Qt::Literals;
26
27 if constexpr (QT_CONFIG(pulseaudio))
28 return "pulsesink"_L1;
29 else if constexpr (QT_CONFIG(alsa))
30 return "alsasink"_L1;
31 else
32 return "autoaudiosink"_L1;
33}();
34
35[[maybe_unused]] bool sinkHasDeviceProperty(const QGstElement &element)
36{
37 using namespace Qt::Literals;
38 QLatin1String elementType = element.typeName();
39
40 if constexpr (QT_CONFIG(pulseaudio))
41 return elementType == "GstPulseSink"_L1;
42 if constexpr (0 && QT_CONFIG(alsa)) // alsasrc has a "device" property, but it cannot be changed
43 // during playback
44 return elementType == "GstAlsaSink"_L1;
45
46 return false;
47}
48
50{
51#if QT_CONFIG(pulseaudio)
52 static std::once_flag versionCheckGuard;
53
54 std::call_once(versionCheckGuard, [] {
55 QVersionNumber paVersion = QVersionNumber::fromString(pa_get_library_version());
56 QVersionNumber firstBadVersion(15, 99);
57 QVersionNumber firstGoodVersion(16, 2);
58 if (paVersion >= firstBadVersion && paVersion < firstGoodVersion) {
59 qWarning() << "Pulseaudio v16 detected. It has known issues, that can cause GStreamer "
60 "to freeze.";
61 // Note: gstreamer requires these two patches to work correctly:
62 // https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/745
63 // https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/764
64 }
65 });
66#endif
67}
68
69} // namespace
70
71q23::expected<QPlatformAudioOutput *, QString> QGstreamerAudioOutput::create(QAudioOutput *parent)
72{
73 static const auto error = qGstErrorMessageIfElementsNotAvailable(
74 "audioconvert", "audioresample", "volume", "autoaudiosink");
75 if (error)
76 return q23::unexpected{ *error };
77
78 return new QGstreamerAudioOutput(parent);
79}
80
81QGstreamerAudioOutput::QGstreamerAudioOutput(QAudioOutput *parent)
82 : QObject(parent),
83 QPlatformAudioOutput(parent),
84 m_audioOutputBin(QGstBin::create("audioOutput")),
85 m_audioQueue{
86 QGstElement::createFromFactory("queue", "audioQueue"),
87 },
88 m_audioConvert{
89 QGstElement::createFromFactory("audioconvert", "audioConvert"),
90 },
91 m_audioResample{
92 QGstElement::createFromFactory("audioresample", "audioResample"),
93 },
94 m_audioVolume{
95 QGstElement::createFromFactory("volume", "volume"),
96 },
97 m_audioSink{
98 QGstElement::createFromFactory(defaultSinkName.constData(), "audiosink"),
99 }
100{
101 m_audioOutputBin.add(m_audioQueue, m_audioConvert, m_audioResample, m_audioVolume, m_audioSink);
102 qLinkGstElements(m_audioQueue, m_audioConvert, m_audioResample, m_audioVolume, m_audioSink);
103
104 m_audioOutputBin.addGhostPad(m_audioQueue, "sink");
105
107}
108
109QGstElement QGstreamerAudioOutput::createGstElement()
110{
111 const auto *customDevice =
112 QAudioDevicePrivate::handle<QGStreamerCustomAudioDeviceInfo>(m_audioDevice);
113
114 if (customDevice) {
115 qCDebug(qLcMediaAudioOutput)
116 << "requesting custom audio sink element: " << customDevice->id;
117
118 QGstElement element =
119 QGstBin::createFromPipelineDescription(customDevice->id, /*name=*/nullptr,
120 /*ghostUnlinkedPads=*/true);
121 if (element)
122 return element;
123
124 qCWarning(qLcMediaAudioOutput) << "Cannot create audio sink element:" << customDevice->id;
125 }
126
127 const QByteArray &id = m_audioDevice.id();
128 if constexpr (QT_CONFIG(pulseaudio) || QT_CONFIG(alsa)) {
129 QGstElement newSink =
130 QGstElement::createFromFactory(defaultSinkName.constData(), "audiosink");
131 if (newSink) {
132 newSink.set("device", id.constData());
133 if (!m_sinkIsAsync)
134 newSink.set("async", false);
135 return newSink;
136 }
137
138 qWarning() << "Cannot create" << defaultSinkName;
139 }
140 qCWarning(qLcMediaAudioOutput) << "Invalid audio device:" << m_audioDevice.id();
141 qCWarning(qLcMediaAudioOutput)
142 << "Failed to create a gst element for the audio device, using a default audio sink";
143 return QGstElement::createFromFactory("autoaudiosink", "audiosink");
144}
145
147{
148 m_audioOutputBin.setStateSync(GST_STATE_NULL);
149}
150
152{
153 m_audioVolume.set("volume", volume);
154}
155
157{
158 m_audioVolume.set("mute", muted);
159}
160
162{
163 m_sinkIsAsync = isAsync;
164 if (m_audioSink)
165 m_audioSink.set("async", m_sinkIsAsync);
166}
167
168void QGstreamerAudioOutput::setAudioDevice(const QAudioDevice &device)
169{
170 if (device == m_audioDevice)
171 return;
172 qCDebug(qLcMediaAudioOutput) << "setAudioDevice" << device.description() << device.isNull();
173
174 m_audioDevice = device;
175
176 // NOTE: ideally we could set the `device` property on the pulsesink. however that seems to
177 // cause the pipeline to stall in rare occassions. so we need to force the creation of a new
178 // sink
179 constexpr bool forceNewSinkCreation = true;
180 if constexpr (!forceNewSinkCreation) {
181 if (sinkHasDeviceProperty(m_audioSink) && !isCustomAudioDevice(m_audioDevice)) {
182 m_audioSink.set("device", m_audioDevice.id().constData());
183 return;
184 }
185 }
186
187 QGstElement newSink = createGstElement();
188
189 m_audioVolume.src().modifyPipelineInIdleProbe([&] {
190 qUnlinkGstElements(m_audioVolume, m_audioSink);
191 m_audioOutputBin.stopAndRemoveElements(m_audioSink);
192 m_audioSink = std::move(newSink);
193 m_audioOutputBin.add(m_audioSink);
194 m_audioSink.syncStateWithParent();
195 qLinkGstElements(m_audioVolume, m_audioSink);
196 });
197}
198
199QT_END_NAMESPACE
static QGstBin createFromPipelineDescription(const char *pipelineDescription, const char *name=nullptr, bool ghostUnlinkedPads=false)
Definition qgst.cpp:1321
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
Definition qgst.cpp:956
void setAudioDevice(const QAudioDevice &) override
void setVolume(float) override
constexpr QLatin1String defaultSinkName
bool sinkHasDeviceProperty(const QGstElement &element)