1 | // Copyright (C) 2021 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/qgstreameraudioinput_p.h> |
5 | |
6 | #include <QtCore/qloggingcategory.h> |
7 | #include <QtMultimedia/qaudiodevice.h> |
8 | #include <QtMultimedia/qaudioinput.h> |
9 | |
10 | #include <audio/qgstreameraudiodevice_p.h> |
11 | #include <common/qgstpipeline_p.h> |
12 | |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | namespace { |
17 | |
18 | Q_LOGGING_CATEGORY(qLcMediaAudioInput, "qt.multimedia.audioinput" ) |
19 | |
20 | constexpr QLatin1String defaultSrcName = [] { |
21 | using namespace Qt::Literals; |
22 | |
23 | if constexpr (QT_CONFIG(pulseaudio)) |
24 | return "pulsesrc"_L1 ; |
25 | else if constexpr (QT_CONFIG(alsa)) |
26 | return "alsasrc"_L1 ; |
27 | else |
28 | return "autoaudiosrc"_L1 ; |
29 | }(); |
30 | |
31 | bool srcHasDeviceProperty(const QGstElement &element) |
32 | { |
33 | using namespace Qt::Literals; |
34 | QLatin1String elementType = element.typeName(); |
35 | |
36 | if constexpr (QT_CONFIG(pulseaudio)) |
37 | return elementType == "GstPulseSrc"_L1 ; |
38 | |
39 | if constexpr (0 && QT_CONFIG(alsa)) // alsasrc has a "device" property, but it cannot be changed |
40 | // during playback |
41 | return elementType == "GstAlsaSrc"_L1 ; |
42 | |
43 | return false; |
44 | } |
45 | |
46 | } // namespace |
47 | |
48 | QMaybe<QPlatformAudioInput *> QGstreamerAudioInput::create(QAudioInput *parent) |
49 | { |
50 | static const auto error = qGstErrorMessageIfElementsNotAvailable(arg: "autoaudiosrc" , args: "volume" ); |
51 | if (error) |
52 | return *error; |
53 | |
54 | return new QGstreamerAudioInput(parent); |
55 | } |
56 | |
57 | QGstreamerAudioInput::QGstreamerAudioInput(QAudioInput *parent) |
58 | : QObject(parent), |
59 | QPlatformAudioInput(parent), |
60 | m_audioInputBin(QGstBin::create(name: "audioInput" )), |
61 | m_audioSrc{ |
62 | QGstElement::createFromFactory(factory: defaultSrcName.constData(), name: "autoaudiosrc" ), |
63 | }, |
64 | m_audioVolume{ |
65 | QGstElement::createFromFactory(factory: "volume" , name: "volume" ), |
66 | } |
67 | { |
68 | m_audioInputBin.add(ts: m_audioSrc, ts: m_audioVolume); |
69 | qLinkGstElements(ts: m_audioSrc, ts: m_audioVolume); |
70 | |
71 | m_audioInputBin.addGhostPad(child: m_audioVolume, name: "src" ); |
72 | } |
73 | |
74 | QGstElement QGstreamerAudioInput::createGstElement() |
75 | { |
76 | const auto *customDeviceInfo = |
77 | dynamic_cast<const QGStreamerCustomAudioDeviceInfo *>(m_audioDevice.handle()); |
78 | |
79 | if (customDeviceInfo) { |
80 | qCDebug(qLcMediaAudioInput) |
81 | << "requesting custom audio src element: " << customDeviceInfo->id; |
82 | |
83 | QGstElement element = QGstBin::createFromPipelineDescription(pipelineDescription: customDeviceInfo->id, |
84 | /*name=*/nullptr, |
85 | /*ghostUnlinkedPads=*/true); |
86 | if (element) |
87 | return element; |
88 | |
89 | qCWarning(qLcMediaAudioInput) |
90 | << "Cannot create audio source element:" << customDeviceInfo->id; |
91 | } |
92 | |
93 | const QByteArray &id = m_audioDevice.id(); |
94 | if constexpr (QT_CONFIG(pulseaudio) || QT_CONFIG(alsa)) { |
95 | QGstElement newSrc = QGstElement::createFromFactory(factory: defaultSrcName.constData(), name: "audiosrc" ); |
96 | if (newSrc) { |
97 | newSrc.set(property: "device" , str: id.constData()); |
98 | return newSrc; |
99 | } |
100 | |
101 | qWarning() << "Cannot create" << defaultSrcName; |
102 | |
103 | } else { |
104 | auto *deviceInfo = dynamic_cast<const QGStreamerAudioDeviceInfo *>(m_audioDevice.handle()); |
105 | if (deviceInfo && deviceInfo->gstDevice) { |
106 | QGstElement element = QGstElement::createFromDevice(deviceInfo->gstDevice, name: "audiosrc" ); |
107 | if (element) |
108 | return element; |
109 | } |
110 | } |
111 | qCWarning(qLcMediaAudioInput) << "Invalid audio device" ; |
112 | qCWarning(qLcMediaAudioInput) |
113 | << "Failed to create a gst element for the audio device, using a default audio source" ; |
114 | return QGstElement::createFromFactory(factory: "autoaudiosrc" , name: "audiosrc" ); |
115 | } |
116 | |
117 | QGstreamerAudioInput::~QGstreamerAudioInput() |
118 | { |
119 | m_audioInputBin.setStateSync(state: GST_STATE_NULL); |
120 | } |
121 | |
122 | void QGstreamerAudioInput::setVolume(float volume) |
123 | { |
124 | m_audioVolume.set(property: "volume" , d: volume); |
125 | } |
126 | |
127 | void QGstreamerAudioInput::setMuted(bool muted) |
128 | { |
129 | m_audioVolume.set(property: "mute" , b: muted); |
130 | } |
131 | |
132 | void QGstreamerAudioInput::setAudioDevice(const QAudioDevice &device) |
133 | { |
134 | if (device == m_audioDevice) |
135 | return; |
136 | qCDebug(qLcMediaAudioInput) << "setAudioDevice" << device.description() << device.isNull(); |
137 | m_audioDevice = device; |
138 | |
139 | if (srcHasDeviceProperty(element: m_audioSrc) && !isCustomAudioDevice(device: m_audioDevice)) { |
140 | m_audioSrc.set(property: "device" , str: m_audioDevice.id().constData()); |
141 | return; |
142 | } |
143 | |
144 | QGstElement newSrc = createGstElement(); |
145 | |
146 | m_audioVolume.sink().modifyPipelineInIdleProbe(f: [&] { |
147 | qUnlinkGstElements(ts: m_audioSrc, ts: m_audioVolume); |
148 | m_audioInputBin.stopAndRemoveElements(ts&: m_audioSrc); |
149 | m_audioSrc = std::move(newSrc); |
150 | m_audioInputBin.add(ts: m_audioSrc); |
151 | qLinkGstElements(ts: m_audioSrc, ts: m_audioVolume); |
152 | m_audioSrc.syncStateWithParent(); |
153 | }); |
154 | } |
155 | |
156 | QT_END_NAMESPACE |
157 | |