Qt 6.x
The Qt SDK
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
24
25class CMMNotificationClient : public IMMNotificationClient
26{
27 LONG m_cRef;
28 ComPtr<IMMDeviceEnumerator> m_enumerator;
29 QWindowsMediaDevices *m_windowsMediaDevices;
30 QMap<QString, DWORD> m_deviceState;
31
32public:
35 QMap<QString, DWORD> &&deviceState) :
36 m_cRef(1),
37 m_enumerator(enumerator),
38 m_windowsMediaDevices(windowsMediaDevices),
39 m_deviceState(deviceState)
40 {}
41
43
44 // IUnknown methods -- AddRef, Release, and QueryInterface
45 ULONG STDMETHODCALLTYPE AddRef() override
46 {
47 return InterlockedIncrement(&m_cRef);
48 }
49
50 ULONG STDMETHODCALLTYPE Release() override
51 {
52 ULONG ulRef = InterlockedDecrement(&m_cRef);
53 if (0 == ulRef) {
54 delete this;
55 }
56 return ulRef;
57 }
58
59 HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) override
60 {
61 if (IID_IUnknown == riid) {
62 AddRef();
63 *ppvInterface = (IUnknown*)this;
64 } else if (__uuidof(IMMNotificationClient) == riid) {
65 AddRef();
66 *ppvInterface = (IMMNotificationClient*)this;
67 } else {
68 *ppvInterface = NULL;
69 return E_NOINTERFACE;
70 }
71 return S_OK;
72 }
73
74 HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR) override
75 {
76 if (role == ERole::eMultimedia)
78
79 return S_OK;
80 }
81
82 HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR deviceID) override
83 {
84 auto it = m_deviceState.find(QString::fromWCharArray(deviceID));
85 if (it == std::end(m_deviceState)) {
86 m_deviceState.insert(QString::fromWCharArray(deviceID), DEVICE_STATE_ACTIVE);
88 }
89
90 return S_OK;
91 };
92
93 HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR deviceID) override
94 {
95 auto key = QString::fromWCharArray(deviceID);
96 auto it = m_deviceState.find(key);
97 if (it != std::end(m_deviceState)) {
98 if (it.value() == DEVICE_STATE_ACTIVE)
100 m_deviceState.remove(key);
101 }
102
103 return S_OK;
104 }
105
106 HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR deviceID, DWORD newState) override
107 {
108 if (auto it = m_deviceState.find(QString::fromWCharArray(deviceID)); it != std::end(m_deviceState)) {
109 // If either the old state or the new state is active emit device change
110 if ((it.value() == DEVICE_STATE_ACTIVE) != (newState == DEVICE_STATE_ACTIVE)) {
111 emitAudioDevicesChanged(deviceID);
112 }
113 it.value() = newState;
114 }
115
116 return S_OK;
117 }
118
119 HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) override
120 {
121 return S_OK;
122 }
123
124 void emitAudioDevicesChanged(EDataFlow flow)
125 {
126 // windowsMediaDevice may be deleted as we are executing the callback
127 if (flow == EDataFlow::eCapture) {
128 emit m_windowsMediaDevices->audioInputsChanged();
129 } else if (flow == EDataFlow::eRender) {
130 emit m_windowsMediaDevices->audioOutputsChanged();
131 }
132 }
133
134 void emitAudioDevicesChanged(LPCWSTR deviceID)
135 {
137 ComPtr<IMMEndpoint> endpoint;
138 EDataFlow flow;
139
140 if (SUCCEEDED(m_enumerator->GetDevice(deviceID, device.GetAddressOf()))
141 && SUCCEEDED(device->QueryInterface(__uuidof(IMMEndpoint), (void**)endpoint.GetAddressOf()))
142 && SUCCEEDED(endpoint->GetDataFlow(&flow)))
143 {
145 }
146 }
147};
148
151{
152 CoInitialize(nullptr);
153
154 auto hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
155 CLSCTX_INPROC_SERVER,__uuidof(IMMDeviceEnumerator),
156 (void**)&m_deviceEnumerator);
157
158 if (SUCCEEDED(hr)) {
159 QMap<QString, DWORD> devState;
161 UINT count = 0;
162
163 if (SUCCEEDED(m_deviceEnumerator->EnumAudioEndpoints(EDataFlow::eAll, DEVICE_STATEMASK_ALL, devColl.GetAddressOf()))
164 && SUCCEEDED(devColl->GetCount(&count)))
165 {
166 for (UINT i = 0; i < count; i++) {
168 DWORD state = 0;
170
171 if (SUCCEEDED(devColl->Item(i, device.GetAddressOf()))
172 && SUCCEEDED(device->GetState(&state))
173 && SUCCEEDED(device->GetId(id.address()))) {
174 devState.insert(QString::fromWCharArray(id.get()), state);
175 }
176 }
177 }
178
179
180 m_notificationClient =
181 makeComObject<CMMNotificationClient>(this, m_deviceEnumerator, std::move(devState));
182 m_deviceEnumerator->RegisterEndpointNotificationCallback(m_notificationClient.Get());
183
184 } else {
185 qWarning() << "Audio device change notification disabled";
186 }
187}
188
190{
191 if (m_deviceEnumerator) {
192 m_deviceEnumerator->UnregisterEndpointNotificationCallback(m_notificationClient.Get());
193 }
194 if (m_warmUpAudioClient) {
195 HRESULT hr = m_warmUpAudioClient->Stop();
196 if (FAILED(hr)) {
197 qWarning() << "Failed to stop audio engine" << hr;
198 }
199 }
200
201 m_deviceEnumerator.Reset();
202 m_notificationClient.Reset();
203 m_warmUpAudioClient.Reset();
204
205 CoUninitialize();
206}
207
208QList<QAudioDevice> QWindowsMediaDevices::availableDevices(QAudioDevice::Mode mode) const
209{
210 const auto audioOut = mode == QAudioDevice::Output;
211
212 const auto defaultAudioDeviceID = [this, audioOut]{
213 const auto dataFlow = audioOut ? EDataFlow::eRender : EDataFlow::eCapture;
216 QString sid;
217
218 if (SUCCEEDED(m_deviceEnumerator->GetDefaultAudioEndpoint(dataFlow, ERole::eMultimedia, dev.GetAddressOf()))) {
219 if (dev && SUCCEEDED(dev->GetId(id.address()))) {
220 sid = QString::fromWCharArray(id.get());
221 }
222 }
223 return sid.toUtf8();
224 }();
225
227
228 auto waveDevices = audioOut ? waveOutGetNumDevs() : waveInGetNumDevs();
229
230 for (auto waveID = 0u; waveID < waveDevices; waveID++) {
231 auto wave = IntToPtr(waveID);
232 auto waveMessage = [wave, audioOut](UINT msg, auto p0, auto p1) {
233 return audioOut ? waveOutMessage((HWAVEOUT)wave, msg, (DWORD_PTR)p0, (DWORD_PTR)p1)
234 : waveInMessage((HWAVEIN)wave, msg, (DWORD_PTR)p0, (DWORD_PTR)p1);
235 };
236
237 size_t len = 0;
238 if (waveMessage(DRV_QUERYFUNCTIONINSTANCEIDSIZE, &len, 0) != MMSYSERR_NOERROR)
239 continue;
240
242 if (waveMessage(DRV_QUERYFUNCTIONINSTANCEID, id.data(), len) != MMSYSERR_NOERROR)
243 continue;
244
247 if (FAILED(m_deviceEnumerator->GetDevice(id.data(), device.GetAddressOf()))
248 || FAILED(device->OpenPropertyStore(STGM_READ, props.GetAddressOf()))) {
249 continue;
250 }
251
252 PROPVARIANT varName;
253 PropVariantInit(&varName);
254
255 if (SUCCEEDED(props->GetValue(QMM_PKEY_Device_FriendlyName, &varName))) {
256 auto description = QString::fromWCharArray(varName.pwszVal);
257 auto strID = QString::fromWCharArray(id.data()).toUtf8();
258
259 auto dev = new QWindowsAudioDeviceInfo(strID, device, waveID, description, mode);
260 dev->isDefault = strID == defaultAudioDeviceID;
261
262 devices.append(dev->create());
263 }
264 PropVariantClear(&varName);
265 }
266
267 return devices;
268}
269
271{
272 return availableDevices(QAudioDevice::Input);
273}
274
276{
277 return availableDevices(QAudioDevice::Output);
278}
279
282{
283 const auto *devInfo = static_cast<const QWindowsAudioDeviceInfo *>(deviceInfo.handle());
284 return new QWindowsAudioSource(devInfo->immDev(), parent);
285}
286
289{
290 const auto *devInfo = static_cast<const QWindowsAudioDeviceInfo *>(deviceInfo.handle());
291 return new QWindowsAudioSink(devInfo->immDev(), parent);
292}
293
295{
296 if (m_isAudioClientWarmedUp.exchange(true))
297 return;
298
299 ComPtr<IMMDeviceEnumerator> deviceEnumerator;
300 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
301 __uuidof(IMMDeviceEnumerator),
302 reinterpret_cast<void **>(deviceEnumerator.GetAddressOf()));
303 if (FAILED(hr)) {
304 qWarning() << "Failed to create device enumerator" << hr;
305 return;
306 }
307
309 hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.GetAddressOf());
310 if (FAILED(hr)) {
311 qWarning() << "Failed to retrieve default audio endpoint" << hr;
312 return;
313 }
314
315 hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_ALL, nullptr,
316 reinterpret_cast<void **>(m_warmUpAudioClient.GetAddressOf()));
317 if (FAILED(hr)) {
318 qWarning() << "Failed to activate audio engine" << hr;
319 return;
320 }
321
323 UINT32 currentPeriodInFrames = 0;
324 hr = m_warmUpAudioClient->GetCurrentSharedModeEnginePeriod(deviceFormat.address(),
325 &currentPeriodInFrames);
326 if (FAILED(hr)) {
327 qWarning() << "Failed to retrieve the current format and periodicity of the audio engine"
328 << hr;
329 return;
330 }
331
332 UINT32 defaultPeriodInFrames = 0;
333 UINT32 fundamentalPeriodInFrames = 0;
334 UINT32 minPeriodInFrames = 0;
335 UINT32 maxPeriodInFrames = 0;
336 hr = m_warmUpAudioClient->GetSharedModeEnginePeriod(deviceFormat.get(), &defaultPeriodInFrames,
337 &fundamentalPeriodInFrames,
338 &minPeriodInFrames, &maxPeriodInFrames);
339 if (FAILED(hr)) {
340 qWarning() << "Failed to retrieve the range of periodicities supported by the audio engine"
341 << hr;
342 return;
343 }
344
345 hr = m_warmUpAudioClient->InitializeSharedAudioStream(
346 AUDCLNT_SHAREMODE_SHARED, minPeriodInFrames, deviceFormat.get(), nullptr);
347 if (FAILED(hr)) {
348 qWarning() << "Failed to initialize audio engine stream" << hr;
349 return;
350 }
351
352 hr = m_warmUpAudioClient->Start();
353 if (FAILED(hr))
354 qWarning() << "Failed to start audio engine" << hr;
355}
356
IOBluetoothDevice * device
void emitAudioDevicesChanged(EDataFlow flow)
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR) override
ULONG STDMETHODCALLTYPE Release() override
ULONG STDMETHODCALLTYPE AddRef() override
void emitAudioDevicesChanged(LPCWSTR deviceID)
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR deviceID, DWORD newState) override
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) override
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR deviceID) override
CMMNotificationClient(QWindowsMediaDevices *windowsMediaDevices, ComPtr< IMMDeviceEnumerator > enumerator, QMap< QString, DWORD > &&deviceState)
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
Definition qlist.h:74
Definition qmap.h:186
iterator insert(const Key &key, const T &value)
Definition qmap.h:687
size_type remove(const Key &key)
Definition qmap.h:299
iterator find(const Key &key)
Definition qmap.h:640
\inmodule QtCore
Definition qobject.h:90
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:311
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromWCharArray(const wchar_t *string, qsizetype size=-1)
Definition qstring.h:1164
QByteArray toUtf8() const &
Definition qstring.h:563
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:162
GLenum mode
GLuint64 key
GLenum GLuint id
[7]
GLenum GLenum GLsizei count
GLenum GLuint GLsizei const GLenum * props
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLsizei len
#define emit
IUIViewSettingsInterop __RPC__in REFIID riid
long HRESULT
const PROPERTYKEY QMM_PKEY_Device_FriendlyName
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent