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
qwindowsaudiodevices.cpp
Go to the documentation of this file.
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
5
6#include <QtCore/qdebug.h>
7#include <QtCore/qmap.h>
8#include <QtCore/private/qcomobject_p.h>
9#include <QtCore/private/qsystemerror_p.h>
10
11#include <QtMultimedia/qmediadevices.h>
12#include <QtMultimedia/private/qcomtaskresource_p.h>
13#include <QtMultimedia/private/qwindowsaudiodevice_p.h>
14#include <QtMultimedia/private/qwindowsaudiosink_p.h>
15#include <QtMultimedia/private/qwindowsaudiosource_p.h>
16#include <QtMultimedia/private/qwindows_propertystore_p.h>
17
18#include <audioclient.h>
19#include <functiondiscoverykeys_devpkey.h>
20#include <mmdeviceapi.h>
21
23
25{
26 ComPtr<IMMDeviceEnumerator> m_enumerator;
27 QWindowsAudioDevices *m_windowsMediaDevices;
28 QMap<QString, DWORD> m_deviceState;
29
30public:
32 ComPtr<IMMDeviceEnumerator> enumerator,
33 QMap<QString, DWORD> &&deviceState)
35 m_windowsMediaDevices(windowsMediaDevices),
37 {}
38
40 {
41 if (role == ERole::eMultimedia)
43
44 return S_OK;
45 }
46
57
70
72 {
74 // If either the old state or the new state is active emit device change
77 }
78 it.value() = newState;
79 }
80
81 return S_OK;
82 }
83
88
90 {
91 // windowsMediaDevice may be deleted as we are executing the callback
92 if (flow == EDataFlow::eCapture) {
94 } else if (flow == EDataFlow::eRender) {
96 }
97 }
98
112
113private:
114 // Destructor is not public. Caller should call Release.
116};
117
120{
121 auto hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
122 IID_PPV_ARGS(&m_deviceEnumerator));
123
124 if (FAILED(hr)) {
125 qWarning("Failed to instantiate IMMDeviceEnumerator (%s)."
126 "Audio device change notification will be disabled",
127 qPrintable(QSystemError::windowsComString(hr)));
128 return;
129 }
130
131 QMap<QString, DWORD> devState;
132 ComPtr<IMMDeviceCollection> devColl;
133 UINT count = 0;
134
135 if (SUCCEEDED(m_deviceEnumerator->EnumAudioEndpoints(EDataFlow::eAll, DEVICE_STATEMASK_ALL, devColl.GetAddressOf()))
136 && SUCCEEDED(devColl->GetCount(&count)))
137 {
138 for (UINT i = 0; i < count; i++) {
139 ComPtr<IMMDevice> device;
140 DWORD state = 0;
141 QComTaskResource<WCHAR> id;
142
143 if (SUCCEEDED(devColl->Item(i, device.GetAddressOf()))
144 && SUCCEEDED(device->GetState(&state))
145 && SUCCEEDED(device->GetId(id.address()))) {
146 devState.insert(QString::fromWCharArray(id.get()), state);
147 }
148 }
149 }
150
151
152 m_notificationClient = makeComObject<CMMNotificationClient>(this, m_deviceEnumerator, std::move(devState));
153 m_deviceEnumerator->RegisterEndpointNotificationCallback(m_notificationClient.Get());
154}
155
157{
158 if (m_deviceEnumerator) {
159 // Note: Calling UnregisterEndpointNotificationCallback after CoUninitialize
160 // will abruptly terminate application, preventing remaining destructors from
161 // being called (QTBUG-120198).
162 m_deviceEnumerator->UnregisterEndpointNotificationCallback(m_notificationClient.Get());
163 }
164
165 m_deviceEnumerator.Reset();
166 m_notificationClient.Reset();
167}
168
169static std::optional<QString> getDeviceId(const ComPtr<IMMDevice> &dev)
170{
171 Q_ASSERT(dev);
172 QComTaskResource<WCHAR> id;
173 HRESULT status = dev->GetId(id.address());
174 if (FAILED(status)) {
175 qWarning() << "IMMDevice::GetId failed" << QSystemError::windowsComString(status);
176 return {};
177 }
178 return QString::fromWCharArray(id.get());
179}
180
181QList<QAudioDevice> QWindowsAudioDevices::availableDevices(QAudioDevice::Mode mode) const
182{
183 using QtMultimediaPrivate::PropertyStoreHelper;
184 if (!m_deviceEnumerator)
185 return {};
186
187 const bool audioOut = mode == QAudioDevice::Output;
188 const auto dataFlow = audioOut ? EDataFlow::eRender : EDataFlow::eCapture;
189
190 const auto defaultAudioDeviceID = [&, this]() -> std::optional<QString> {
191 ComPtr<IMMDevice> dev;
192 if (SUCCEEDED(m_deviceEnumerator->GetDefaultAudioEndpoint(dataFlow, ERole::eMultimedia,
193 dev.GetAddressOf())))
194 return getDeviceId(dev);
195
196 return std::nullopt;
197 }();
198
199 QList<QAudioDevice> devices;
200
201 ComPtr<IMMDeviceCollection> allActiveDevices;
202 HRESULT result = m_deviceEnumerator->EnumAudioEndpoints(dataFlow, DEVICE_STATE_ACTIVE,
203 allActiveDevices.GetAddressOf());
204
205 if (FAILED(result)) {
206 qWarning() << "IMMDeviceEnumerator::EnumAudioEndpoints failed"
207 << QSystemError::windowsComString(result);
208 return devices;
209 }
210
211 UINT numberOfDevices;
212 result = allActiveDevices->GetCount(&numberOfDevices);
213 if (FAILED(result)) {
214 qWarning() << "IMMDeviceCollection::GetCount failed"
215 << QSystemError::windowsComString(result);
216 return devices;
217 }
218
219 for (UINT index = 0; index != numberOfDevices; ++index) {
220 ComPtr<IMMDevice> device;
221 result = allActiveDevices->Item(index, device.GetAddressOf());
222 if (FAILED(result)) {
223 qWarning() << "IMMDeviceCollection::Item" << QSystemError::windowsComString(result);
224 continue;
225 }
226
227 std::optional<QString> deviceId = getDeviceId(device);
228 if (!deviceId)
229 continue;
230
231 q23::expected<PropertyStoreHelper, QString> props = PropertyStoreHelper::open(device);
232 if (!props) {
233 qWarning() << "OpenPropertyStore failed" << props.error();
234 continue;
235 }
236
237 std::optional<QString> friendlyName = props->getString(PKEY_Device_FriendlyName);
238 if (!friendlyName) {
239 qWarning() << "Cannot read property store";
240 continue;
241 }
242
243 auto dev = std::make_unique<QWindowsAudioDevice>(deviceId->toUtf8(), device, *friendlyName,
244 mode);
245 dev->isDefault = deviceId == defaultAudioDeviceID;
246 devices.append(QAudioDevicePrivate::createQAudioDevice(std::move(dev)));
247 }
248
249 return devices;
250}
251
253{
254 return availableDevices(QAudioDevice::Input);
255}
256
258{
259 return availableDevices(QAudioDevice::Output);
260}
261
263 const QAudioFormat &fmt,
264 QObject *parent)
265{
266 return new QtWASAPI::QWindowsAudioSource(device, fmt, parent);
267}
268
270 const QAudioFormat &fmt, QObject *parent)
271{
272 return new QtWASAPI::QWindowsAudioSink(device, fmt, parent);
273}
274
275QT_END_NAMESPACE
CMMNotificationClient(QWindowsAudioDevices *windowsMediaDevices, ComPtr< IMMDeviceEnumerator > enumerator, QMap< QString, DWORD > &&deviceState)
QList< QAudioDevice > findAudioInputs() const override
QPlatformAudioSource * createAudioSource(const QAudioDevice &, const QAudioFormat &, QObject *parent) override
QPlatformAudioSink * createAudioSink(const QAudioDevice &, const QAudioFormat &, QObject *parent) override
QList< QAudioDevice > findAudioOutputs() const override
static std::optional< QString > getDeviceId(const ComPtr< IMMDevice > &dev)