Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qwindowsmediadevices.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#include "qmediadevices.h"
6#include "qvarlengtharray.h"
7
11#include "qcomtaskresource_p.h"
12
13#include <mmsystem.h>
14#include <mmddk.h>
15#include <mfobjects.h>
16#include <mfidl.h>
17#include <mferror.h>
18#include <mmdeviceapi.h>
19#include <qwindowsmfdefs_p.h>
20
21#include <QtCore/qmap.h>
22#include <private/qcomobject_p.h>
23#include <private/qsystemerror_p.h>
24
26
27class CMMNotificationClient : public QComObject<IMMNotificationClient>
28{
29 ComPtr<IMMDeviceEnumerator> m_enumerator;
30 QWindowsMediaDevices *m_windowsMediaDevices;
31 QMap<QString, DWORD> m_deviceState;
32
33public:
35 ComPtr<IMMDeviceEnumerator> enumerator,
36 QMap<QString, DWORD> &&deviceState)
37 : m_enumerator(enumerator),
38 m_windowsMediaDevices(windowsMediaDevices),
39 m_deviceState(deviceState)
40 {}
41
42 HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR) override
43 {
44 if (role == ERole::eMultimedia)
46
47 return S_OK;
48 }
49
50 HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR deviceID) override
51 {
52 auto it = m_deviceState.find(QString::fromWCharArray(deviceID));
53 if (it == std::end(m_deviceState)) {
54 m_deviceState.insert(QString::fromWCharArray(deviceID), DEVICE_STATE_ACTIVE);
56 }
57
58 return S_OK;
59 };
60
61 HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR deviceID) override
62 {
63 auto key = QString::fromWCharArray(deviceID);
64 auto it = m_deviceState.find(key);
65 if (it != std::end(m_deviceState)) {
66 if (it.value() == DEVICE_STATE_ACTIVE)
68 m_deviceState.remove(key);
69 }
70
71 return S_OK;
72 }
73
74 HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR deviceID, DWORD newState) override
75 {
76 if (auto it = m_deviceState.find(QString::fromWCharArray(deviceID)); it != std::end(m_deviceState)) {
77 // If either the old state or the new state is active emit device change
78 if ((it.value() == DEVICE_STATE_ACTIVE) != (newState == DEVICE_STATE_ACTIVE)) {
80 }
81 it.value() = newState;
82 }
83
84 return S_OK;
85 }
86
87 HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) override
88 {
89 return S_OK;
90 }
91
92 void emitAudioDevicesChanged(EDataFlow flow)
93 {
94 // windowsMediaDevice may be deleted as we are executing the callback
95 if (flow == EDataFlow::eCapture) {
96 emit m_windowsMediaDevices->audioInputsChanged();
97 } else if (flow == EDataFlow::eRender) {
98 emit m_windowsMediaDevices->audioOutputsChanged();
99 }
100 }
101
102 void emitAudioDevicesChanged(LPCWSTR deviceID)
103 {
104 ComPtr<IMMDevice> device;
105 ComPtr<IMMEndpoint> endpoint;
106 EDataFlow flow;
107
108 if (SUCCEEDED(m_enumerator->GetDevice(deviceID, device.GetAddressOf()))
109 && SUCCEEDED(device->QueryInterface(__uuidof(IMMEndpoint), (void**)endpoint.GetAddressOf()))
110 && SUCCEEDED(endpoint->GetDataFlow(&flow)))
111 {
113 }
114 }
115
116private:
117 // Destructor is not public. Caller should call Release.
118 ~CMMNotificationClient() override = default;
119};
120
123{
124 auto hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
125 CLSCTX_INPROC_SERVER,__uuidof(IMMDeviceEnumerator),
126 (void**)&m_deviceEnumerator);
127
128 if (FAILED(hr)) {
129 qWarning("Failed to instantiate IMMDeviceEnumerator (%s)."
130 "Audio device change notification will be disabled",
131 qPrintable(QSystemError::windowsComString(hr)));
132 return;
133 }
134
135 QMap<QString, DWORD> devState;
136 ComPtr<IMMDeviceCollection> devColl;
137 UINT count = 0;
138
139 if (SUCCEEDED(m_deviceEnumerator->EnumAudioEndpoints(EDataFlow::eAll, DEVICE_STATEMASK_ALL, devColl.GetAddressOf()))
140 && SUCCEEDED(devColl->GetCount(&count)))
141 {
142 for (UINT i = 0; i < count; i++) {
143 ComPtr<IMMDevice> device;
144 DWORD state = 0;
145 QComTaskResource<WCHAR> id;
146
147 if (SUCCEEDED(devColl->Item(i, device.GetAddressOf()))
148 && SUCCEEDED(device->GetState(&state))
149 && SUCCEEDED(device->GetId(id.address()))) {
150 devState.insert(QString::fromWCharArray(id.get()), state);
151 }
152 }
153 }
154
155
156 m_notificationClient = makeComObject<CMMNotificationClient>(this, m_deviceEnumerator, std::move(devState));
157 m_deviceEnumerator->RegisterEndpointNotificationCallback(m_notificationClient.Get());
158}
159
161{
162 if (m_deviceEnumerator) {
163 // Note: Calling UnregisterEndpointNotificationCallback after CoUninitialize
164 // will abruptly terminate application, preventing remaining destructors from
165 // being called (QTBUG-120198).
166 m_deviceEnumerator->UnregisterEndpointNotificationCallback(m_notificationClient.Get());
167 }
168 if (m_warmUpAudioClient) {
169 HRESULT hr = m_warmUpAudioClient->Stop();
170 if (FAILED(hr)) {
171 qWarning() << "Failed to stop audio engine" << hr;
172 }
173 }
174
175 m_deviceEnumerator.Reset();
176 m_notificationClient.Reset();
177 m_warmUpAudioClient.Reset();
178}
179
180QList<QAudioDevice> QWindowsMediaDevices::availableDevices(QAudioDevice::Mode mode) const
181{
182 if (!m_deviceEnumerator)
183 return {};
184
185 const auto audioOut = mode == QAudioDevice::Output;
186
187 const auto defaultAudioDeviceID = [this, audioOut]{
188 const auto dataFlow = audioOut ? EDataFlow::eRender : EDataFlow::eCapture;
189 ComPtr<IMMDevice> dev;
190 QComTaskResource<WCHAR> id;
191 QString sid;
192
193 if (SUCCEEDED(m_deviceEnumerator->GetDefaultAudioEndpoint(dataFlow, ERole::eMultimedia, dev.GetAddressOf()))) {
194 if (dev && SUCCEEDED(dev->GetId(id.address()))) {
195 sid = QString::fromWCharArray(id.get());
196 }
197 }
198 return sid.toUtf8();
199 }();
200
201 QList<QAudioDevice> devices;
202
203 auto waveDevices = audioOut ? waveOutGetNumDevs() : waveInGetNumDevs();
204
205 for (auto waveID = 0u; waveID < waveDevices; waveID++) {
206 auto wave = IntToPtr(waveID);
207 auto waveMessage = [wave, audioOut](UINT msg, auto p0, auto p1) {
208 return audioOut ? waveOutMessage((HWAVEOUT)wave, msg, (DWORD_PTR)p0, (DWORD_PTR)p1)
209 : waveInMessage((HWAVEIN)wave, msg, (DWORD_PTR)p0, (DWORD_PTR)p1);
210 };
211
212 size_t len = 0;
213 if (waveMessage(DRV_QUERYFUNCTIONINSTANCEIDSIZE, &len, 0) != MMSYSERR_NOERROR)
214 continue;
215
216 QVarLengthArray<WCHAR> id(len);
217 if (waveMessage(DRV_QUERYFUNCTIONINSTANCEID, id.data(), len) != MMSYSERR_NOERROR)
218 continue;
219
220 ComPtr<IMMDevice> device;
221 ComPtr<IPropertyStore> props;
222 if (FAILED(m_deviceEnumerator->GetDevice(id.data(), device.GetAddressOf()))
223 || FAILED(device->OpenPropertyStore(STGM_READ, props.GetAddressOf()))) {
224 continue;
225 }
226
227 PROPVARIANT varName;
228 PropVariantInit(&varName);
229
230 if (SUCCEEDED(props->GetValue(QMM_PKEY_Device_FriendlyName, &varName))) {
231 auto description = QString::fromWCharArray(varName.pwszVal);
232 auto strID = QString::fromWCharArray(id.data()).toUtf8();
233
234 auto dev = new QWindowsAudioDeviceInfo(strID, device, waveID, description, mode);
235 dev->isDefault = strID == defaultAudioDeviceID;
236
237 devices.append(dev->create());
238 }
239 PropVariantClear(&varName);
240 }
241
242 return devices;
243}
244
245QList<QAudioDevice> QWindowsMediaDevices::audioInputs() const
246{
247 return availableDevices(QAudioDevice::Input);
248}
249
250QList<QAudioDevice> QWindowsMediaDevices::audioOutputs() const
251{
252 return availableDevices(QAudioDevice::Output);
253}
254
256 QObject *parent)
257{
258 const auto *devInfo = static_cast<const QWindowsAudioDeviceInfo *>(deviceInfo.handle());
259 return new QWindowsAudioSource(devInfo->immDev(), parent);
260}
261
263 QObject *parent)
264{
265 const auto *devInfo = static_cast<const QWindowsAudioDeviceInfo *>(deviceInfo.handle());
266 return new QWindowsAudioSink(devInfo->immDev(), parent);
267}
268
270{
271 static bool isDisableAudioPrepareSet = false;
272 static const int disableAudioPrepare =
273 qEnvironmentVariableIntValue("QT_DISABLE_AUDIO_PREPARE", &isDisableAudioPrepareSet);
274
275 return !isDisableAudioPrepareSet || disableAudioPrepare == 0;
276}
277
279{
281 return;
282
283 if (m_isAudioClientWarmedUp.exchange(true))
284 return;
285
286 ComPtr<IMMDeviceEnumerator> deviceEnumerator;
287 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
288 __uuidof(IMMDeviceEnumerator),
289 reinterpret_cast<void **>(deviceEnumerator.GetAddressOf()));
290 if (FAILED(hr)) {
291 qWarning() << "Failed to create device enumerator" << hr;
292 return;
293 }
294
295 ComPtr<IMMDevice> device;
296 hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.GetAddressOf());
297 if (FAILED(hr)) {
298 if (hr != E_NOTFOUND)
299 qWarning() << "Failed to retrieve default audio endpoint" << hr;
300 return;
301 }
302
303 hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_ALL, nullptr,
304 reinterpret_cast<void **>(m_warmUpAudioClient.GetAddressOf()));
305 if (FAILED(hr)) {
306 qWarning() << "Failed to activate audio engine" << hr;
307 return;
308 }
309
310 QComTaskResource<WAVEFORMATEX> deviceFormat;
311 UINT32 currentPeriodInFrames = 0;
312 hr = m_warmUpAudioClient->GetCurrentSharedModeEnginePeriod(deviceFormat.address(),
313 &currentPeriodInFrames);
314 if (FAILED(hr)) {
315 qWarning() << "Failed to retrieve the current format and periodicity of the audio engine"
316 << hr;
317 return;
318 }
319
320 UINT32 defaultPeriodInFrames = 0;
321 UINT32 fundamentalPeriodInFrames = 0;
322 UINT32 minPeriodInFrames = 0;
323 UINT32 maxPeriodInFrames = 0;
324 hr = m_warmUpAudioClient->GetSharedModeEnginePeriod(deviceFormat.get(), &defaultPeriodInFrames,
325 &fundamentalPeriodInFrames,
326 &minPeriodInFrames, &maxPeriodInFrames);
327 if (FAILED(hr)) {
328 qWarning() << "Failed to retrieve the range of periodicities supported by the audio engine"
329 << hr;
330 return;
331 }
332
333 hr = m_warmUpAudioClient->InitializeSharedAudioStream(
334 AUDCLNT_SHAREMODE_SHARED, minPeriodInFrames, deviceFormat.get(), nullptr);
335 if (FAILED(hr)) {
336 qWarning() << "Failed to initialize audio engine stream" << hr;
337 return;
338 }
339
340 hr = m_warmUpAudioClient->Start();
341 if (FAILED(hr))
342 qWarning() << "Failed to start audio engine" << hr;
343}
344
IOBluetoothDevice * device
void emitAudioDevicesChanged(EDataFlow flow)
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR) override
void emitAudioDevicesChanged(LPCWSTR deviceID)
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR deviceID, DWORD newState) override
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR deviceID) override
CMMNotificationClient(QWindowsMediaDevices *windowsMediaDevices, ComPtr< IMMDeviceEnumerator > enumerator, QMap< QString, DWORD > &&deviceState)
~CMMNotificationClient() override=default
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) override
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR deviceID) override
The QAudioDevice class provides an information about audio devices and their functionality.
Mode
Describes the mode of this device.
const QAudioDevicePrivate * handle() const
\inmodule QtCore
Definition qobject.h:103
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromWCharArray(const wchar_t *string, qsizetype size=-1)
Definition qstring.h:1309
QPlatformAudioSink * createAudioSink(const QAudioDevice &deviceInfo, QObject *parent) override
QList< QAudioDevice > audioInputs() const override
QList< QAudioDevice > audioOutputs() const override
QPlatformAudioSource * createAudioSource(const QAudioDevice &deviceInfo, QObject *parent) override
QPixmap p1
[0]
QSet< QString >::iterator it
else opt state
[0]
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
Combined button and popup list for selecting options.
static QDBusError::ErrorType get(const char *name)
EGLDeviceEXT * devices
#define DWORD_PTR
#define qWarning
Definition qlogging.h:166
GLenum mode
GLuint64 key
GLenum GLuint id
[7]
GLenum GLenum GLsizei count
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLsizei const GLenum * props
GLenum GLsizei len
#define qPrintable(string)
Definition qstring.h:1531
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define emit
long HRESULT
static bool isPrepareAudioEnabled()
const PROPERTYKEY QMM_PKEY_Device_FriendlyName