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 <QtMultimedia/qaudiodevice.h>
8#include <QtMultimedia/qaudiooutput.h>
9
10#include <common/qgstpipeline_p.h>
11#include <audio/qgstreameraudiodevice_p.h>
12
13#if QT_CONFIG(pulseaudio)
14# include <pulse/version.h>
15#endif
16
17QT_BEGIN_NAMESPACE
18
19namespace {
20
21Q_LOGGING_CATEGORY(qLcMediaAudioOutput, "qt.multimedia.audiooutput")
22
23constexpr QLatin1String defaultSinkName = [] {
24 using namespace Qt::Literals;
25
26 if constexpr (QT_CONFIG(pulseaudio))
27 return "pulsesink"_L1;
28 else if constexpr (QT_CONFIG(alsa))
29 return "alsasink"_L1;
30 else
31 return "autoaudiosink"_L1;
32}();
33
34[[maybe_unused]] bool sinkHasDeviceProperty(const QGstElement &element)
35{
36 using namespace Qt::Literals;
37 QLatin1String elementType = element.typeName();
38
39 if constexpr (QT_CONFIG(pulseaudio))
40 return elementType == "GstPulseSink"_L1;
41 if constexpr (0 && QT_CONFIG(alsa)) // alsasrc has a "device" property, but it cannot be changed
42 // during playback
43 return elementType == "GstAlsaSink"_L1;
44
45 return false;
46}
47
48void pulseVersionSanityCheck()
49{
50#if QT_CONFIG(pulseaudio)
51 static std::once_flag versionCheckGuard;
52
53 std::call_once(once&: versionCheckGuard, f: [] {
54 QVersionNumber paVersion = QVersionNumber::fromString(string: pa_get_library_version());
55 QVersionNumber firstBadVersion(15, 99);
56 QVersionNumber firstGoodVersion(16, 2);
57 if (paVersion >= firstBadVersion && paVersion < firstGoodVersion) {
58 qWarning() << "Pulseaudio v16 detected. It has known issues, that can cause GStreamer "
59 "to freeze.";
60 // Note: gstreamer requires these two patches to work correctly:
61 // https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/745
62 // https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/764
63 }
64 });
65#endif
66}
67
68} // namespace
69
70QMaybe<QPlatformAudioOutput *> QGstreamerAudioOutput::create(QAudioOutput *parent)
71{
72 static const auto error = qGstErrorMessageIfElementsNotAvailable(
73 arg: "audioconvert", args: "audioresample", args: "volume", args: "autoaudiosink");
74 if (error)
75 return *error;
76
77 return new QGstreamerAudioOutput(parent);
78}
79
80QGstreamerAudioOutput::QGstreamerAudioOutput(QAudioOutput *parent)
81 : QObject(parent),
82 QPlatformAudioOutput(parent),
83 m_audioOutputBin(QGstBin::create(name: "audioOutput")),
84 m_audioQueue{
85 QGstElement::createFromFactory(factory: "queue", name: "audioQueue"),
86 },
87 m_audioConvert{
88 QGstElement::createFromFactory(factory: "audioconvert", name: "audioConvert"),
89 },
90 m_audioResample{
91 QGstElement::createFromFactory(factory: "audioresample", name: "audioResample"),
92 },
93 m_audioVolume{
94 QGstElement::createFromFactory(factory: "volume", name: "volume"),
95 },
96 m_audioSink{
97 QGstElement::createFromFactory(factory: defaultSinkName.constData(), name: "audiosink"),
98 }
99{
100 m_audioOutputBin.add(ts: m_audioQueue, ts: m_audioConvert, ts: m_audioResample, ts: m_audioVolume, ts: m_audioSink);
101 qLinkGstElements(ts: m_audioQueue, ts: m_audioConvert, ts: m_audioResample, ts: m_audioVolume, ts: m_audioSink);
102
103 m_audioOutputBin.addGhostPad(child: m_audioQueue, name: "sink");
104
105 pulseVersionSanityCheck();
106}
107
108QGstElement QGstreamerAudioOutput::createGstElement()
109{
110 const auto *customDeviceInfo =
111 dynamic_cast<const QGStreamerCustomAudioDeviceInfo *>(m_audioDevice.handle());
112
113 if (customDeviceInfo) {
114 qCDebug(qLcMediaAudioOutput)
115 << "requesting custom audio sink element: " << customDeviceInfo->id;
116
117 QGstElement element =
118 QGstBin::createFromPipelineDescription(pipelineDescription: customDeviceInfo->id, /*name=*/nullptr,
119 /*ghostUnlinkedPads=*/true);
120 if (element)
121 return element;
122
123 qCWarning(qLcMediaAudioOutput)
124 << "Cannot create audio sink element:" << customDeviceInfo->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(factory: defaultSinkName.constData(), name: "audiosink");
131 if (newSink) {
132 newSink.set(property: "device", str: id.constData());
133 if (!m_sinkIsAsync)
134 newSink.set(property: "async", b: false);
135 return newSink;
136 }
137
138 qWarning() << "Cannot create" << defaultSinkName;
139 } else {
140 auto *deviceInfo = dynamic_cast<const QGStreamerAudioDeviceInfo *>(m_audioDevice.handle());
141 if (deviceInfo && deviceInfo->gstDevice) {
142 QGstElement element = QGstElement::createFromDevice(deviceInfo->gstDevice, name: "audiosink");
143 if (!m_sinkIsAsync)
144 element.set(property: "async", b: false);
145 if (element)
146 return element;
147 }
148 }
149 qCWarning(qLcMediaAudioOutput) << "Invalid audio device:" << m_audioDevice.id();
150 qCWarning(qLcMediaAudioOutput)
151 << "Failed to create a gst element for the audio device, using a default audio sink";
152 return QGstElement::createFromFactory(factory: "autoaudiosink", name: "audiosink");
153}
154
155QGstreamerAudioOutput::~QGstreamerAudioOutput()
156{
157 m_audioOutputBin.setStateSync(state: GST_STATE_NULL);
158}
159
160void QGstreamerAudioOutput::setVolume(float volume)
161{
162 m_audioVolume.set(property: "volume", d: volume);
163}
164
165void QGstreamerAudioOutput::setMuted(bool muted)
166{
167 m_audioVolume.set(property: "mute", b: muted);
168}
169
170void QGstreamerAudioOutput::setAsync(bool isAsync)
171{
172 m_sinkIsAsync = isAsync;
173 if (m_audioSink)
174 m_audioSink.set(property: "async", b: m_sinkIsAsync);
175}
176
177void QGstreamerAudioOutput::setAudioDevice(const QAudioDevice &device)
178{
179 if (device == m_audioDevice)
180 return;
181 qCDebug(qLcMediaAudioOutput) << "setAudioDevice" << device.description() << device.isNull();
182
183 m_audioDevice = device;
184
185 // NOTE: ideally we could set the `device` property on the pulsesink. however that seems to
186 // cause the pipeline to stall in rare occassions. so we need to force the creation of a new
187 // sink
188 constexpr bool forceNewSinkCreation = true;
189 if constexpr (!forceNewSinkCreation) {
190 if (sinkHasDeviceProperty(element: m_audioSink) && !isCustomAudioDevice(device: m_audioDevice)) {
191 m_audioSink.set(property: "device", str: m_audioDevice.id().constData());
192 return;
193 }
194 }
195
196 QGstElement newSink = createGstElement();
197
198 m_audioVolume.src().modifyPipelineInIdleProbe(f: [&] {
199 qUnlinkGstElements(ts: m_audioVolume, ts: m_audioSink);
200 m_audioOutputBin.stopAndRemoveElements(ts&: m_audioSink);
201 m_audioSink = std::move(newSink);
202 m_audioOutputBin.add(ts: m_audioSink);
203 m_audioSink.syncStateWithParent();
204 qLinkGstElements(ts: m_audioVolume, ts: m_audioSink);
205 });
206}
207
208QT_END_NAMESPACE
209

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtmultimedia/src/plugins/multimedia/gstreamer/common/qgstreameraudiooutput.cpp