6#include <QtCore/qdebug.h>
7#include <QtCore/private/qcomobject_p.h>
8#include <QtCore/private/qsystemerror_p.h>
10#include <QtMultimedia/qmediadevices.h>
11#include <QtMultimedia/private/qcomtaskresource_p.h>
12#include <QtMultimedia/private/qwindowsaudiodevice_p.h>
13#include <QtMultimedia/private/qwindowsaudiosink_p.h>
14#include <QtMultimedia/private/qwindowsaudiosource_p.h>
15#include <QtMultimedia/private/qwindows_propertystore_p.h>
17#include <audioclient.h>
18#include <functiondiscoverykeys_devpkey.h>
19#include <mmdeviceapi.h>
29 0x1f, 0xae, 0xfc, 0x6c, 2);
35enum class DeviceState : uint8_t {
42constexpr DeviceState asDeviceState(DWORD state)
45 case DEVICE_STATE_ACTIVE:
46 return DeviceState::active;
47 case DEVICE_STATE_DISABLED:
48 return DeviceState::disabled;
49 case DEVICE_STATE_NOTPRESENT:
50 return DeviceState::notPresent;
51 case DEVICE_STATE_UNPLUGGED:
52 return DeviceState::unplugged;
54 Q_UNREACHABLE_RETURN(DeviceState::notPresent);
69 ComPtr<IMMDevice> device;
73 std::map<QString, DeviceRecord> m_deviceMap;
77 ComPtr<IMMDeviceEnumerator> enumerator)
80 ComPtr<IMMDeviceCollection> devColl;
83 if (SUCCEEDED(m_enumerator->EnumAudioEndpoints(EDataFlow::eAll, DEVICE_STATEMASK_ALL,
84 devColl.GetAddressOf()))
85 && SUCCEEDED(devColl->GetCount(&count))) {
86 for (UINT i = 0; i < count; i++) {
87 ComPtr<IMMDevice> device;
88 if (FAILED(devColl->Item(i, device.GetAddressOf())))
91 auto enumerateResult = enumerateDevice(device);
95 auto idResult = deviceId(enumerateResult->device);
99 m_deviceMap.emplace(std::move(*idResult), std::move(*enumerateResult));
104 qRegisterMetaType<ComPtr<IMMDevice>>();
247 auto hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
nullptr, CLSCTX_INPROC_SERVER,
248 IID_PPV_ARGS(&m_deviceEnumerator));
251 qWarning(
"Failed to instantiate IMMDeviceEnumerator (%s)."
252 "Audio device change notification will be disabled",
253 qPrintable(QSystemError::windowsComString(hr)));
257 m_notificationClient = makeComObject<QtWASAPI::CMMNotificationClient>(
this, m_deviceEnumerator);
258 m_deviceEnumerator->RegisterEndpointNotificationCallback(m_notificationClient.Get());
260 connect(m_notificationClient.Get(), &QtWASAPI::CMMNotificationClient::audioDeviceAdded,
this,
262 onAudioInputsChanged();
263 onAudioOutputsChanged();
265 connect(m_notificationClient.Get(), &QtWASAPI::CMMNotificationClient::audioDeviceRemoved,
this,
266 [
this](ComPtr<IMMDevice> device) {
268 std::lock_guard lock(m_cacheMutex);
269 m_cachedDevices.erase(device);
271 onAudioInputsChanged();
272 onAudioOutputsChanged();
274 connect(m_notificationClient.Get(), &QtWASAPI::CMMNotificationClient::audioDeviceDefaultChanged,
275 this, [
this](QAudioDevice::Mode mode, ComPtr<IMMDevice> device) {
277 std::lock_guard lock(m_cacheMutex);
279 for (
auto &entry : m_cachedDevices) {
280 if (entry.second.mode() != mode)
283 auto handle = QAudioDevicePrivate::handle<QWindowsAudioDevice>(entry.second);
286 std::unique_ptr<QAudioDevicePrivate> newPrivate = handle->clone();
287 newPrivate->isDefault = entry.first == device;
289 entry.second = QAudioDevicePrivate::createQAudioDevice(std::move(newPrivate));
294 case QAudioDevice::Input:
295 onAudioInputsChanged();
297 case QAudioDevice::Output:
298 onAudioOutputsChanged();
304 connect(m_notificationClient.Get(),
305 &QtWASAPI::CMMNotificationClient::audioDevicePropertyChanged,
this,
306 [
this](ComPtr<IMMDevice> device) {
308 std::lock_guard lock(m_cacheMutex);
309 m_cachedDevices.erase(device);
312 onAudioInputsChanged();
313 onAudioOutputsChanged();
319 if (m_deviceEnumerator) {
323 m_deviceEnumerator->UnregisterEndpointNotificationCallback(m_notificationClient.Get());
326 m_deviceEnumerator.Reset();
327 m_notificationClient.Reset();
333 QComTaskResource<WCHAR> id;
334 HRESULT status = dev->GetId(id.address());
335 if (FAILED(status)) {
336 qWarning() <<
"IMMDevice::GetId failed" << QSystemError::windowsComString(status);
339 return QString::fromWCharArray(id.get());
343 std::optional<QString> defaultAudioDeviceID)
345 using QtMultimediaPrivate::PropertyStoreHelper;
347 std::optional<QString> deviceId = getDeviceId(device);
351 q23::expected<PropertyStoreHelper, QString> props = PropertyStoreHelper::open(device);
353 qWarning() <<
"OpenPropertyStore failed" << props.error();
357 std::optional<QString> friendlyName = props->getString(PKEY_Device_FriendlyName);
359 qWarning() <<
"Cannot read property store";
363 std::optional<QUuid> deviceContainerId = props->getGUID(PKEY_Device_ContainerIdQt);
364 if (!deviceContainerId) {
365 qWarning() <<
"Cannot read property store";
369 std::optional<uint32_t> formFactor = props->getUInt32(PKEY_AudioEndpoint_FormFactor);
371 qWarning() <<
"Cannot infer form factor";
375 auto dev = std::make_unique<QWindowsAudioDevice>(deviceId->toUtf8(), device, *friendlyName,
377 EndpointFormFactor(*formFactor), mode);
378 dev->isDefault = deviceId == defaultAudioDeviceID;
379 return QAudioDevicePrivate::createQAudioDevice(std::move(dev));
384 if (!m_deviceEnumerator)
387 const bool audioOut = mode == QAudioDevice::Output;
388 const auto dataFlow = audioOut ? EDataFlow::eRender : EDataFlow::eCapture;
390 const auto defaultAudioDeviceID = [&,
this]() -> std::optional<QString> {
391 ComPtr<IMMDevice> dev;
392 if (SUCCEEDED(m_deviceEnumerator->GetDefaultAudioEndpoint(dataFlow, ERole::eMultimedia,
393 dev.GetAddressOf())))
394 return getDeviceId(dev);
399 QList<QAudioDevice> devices;
401 ComPtr<IMMDeviceCollection> allActiveDevices;
402 HRESULT result = m_deviceEnumerator->EnumAudioEndpoints(dataFlow, DEVICE_STATE_ACTIVE,
403 allActiveDevices.GetAddressOf());
405 if (FAILED(result)) {
406 qWarning() <<
"IMMDeviceEnumerator::EnumAudioEndpoints failed"
407 << QSystemError::windowsComString(result);
411 UINT numberOfDevices;
412 result = allActiveDevices->GetCount(&numberOfDevices);
413 if (FAILED(result)) {
414 qWarning() <<
"IMMDeviceCollection::GetCount failed"
415 << QSystemError::windowsComString(result);
419 for (UINT index = 0; index != numberOfDevices; ++index) {
420 ComPtr<IMMDevice> device;
421 result = allActiveDevices->Item(index, device.GetAddressOf());
422 if (FAILED(result)) {
423 qWarning() <<
"IMMDeviceCollection::Item" << QSystemError::windowsComString(result);
428 std::lock_guard lock(m_cacheMutex);
429 auto cachedDevice = m_cachedDevices.find(device);
430 if (cachedDevice != m_cachedDevices.end()) {
431 devices.append(cachedDevice->second);
436 std::optional<QAudioDevice> audioDevice =
437 asQAudioDevice(device, mode, defaultAudioDeviceID);
440 devices.append(*audioDevice);
441 std::lock_guard lock(m_cacheMutex);
442 m_cachedDevices.emplace(device, *audioDevice);
446 auto deviceOrder = [](
const QAudioDevice &lhs,
const QAudioDevice &rhs) {
447 auto lhsHandle = QAudioDevicePrivate::handle<QWindowsAudioDevice>(lhs);
448 auto rhsHandle = QAudioDevicePrivate::handle<QWindowsAudioDevice>(rhs);
449 auto lhsKey =
std::tie(lhsHandle->m_device_ContainerId, lhsHandle->m_formFactor,
450 lhsHandle->description);
451 auto rhsKey =
std::tie(rhsHandle->m_device_ContainerId, rhsHandle->m_formFactor,
452 rhsHandle->description);
453 return lhsKey < rhsKey;
456 std::sort(devices.begin(), devices.end(), deviceOrder);
462 return availableDevices(QAudioDevice::Input);
467 return availableDevices(QAudioDevice::Output);
471 const QAudioFormat &fmt,
474 return new QtWASAPI::QWindowsAudioSource(device, fmt, parent);
478 const QAudioFormat &fmt, QObject *parent)
480 return new QtWASAPI::QWindowsAudioSink(device, fmt, parent);
485#include "qwindowsaudiodevices.moc"
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
virtual ~QWindowsAudioDevices()
void audioDeviceDefaultChanged(QAudioDevice::Mode, ComPtr< IMMDevice >)
void audioDeviceRemoved(ComPtr< IMMDevice >)
CMMNotificationClient(QWindowsAudioDevices *windowsMediaDevices, ComPtr< IMMDeviceEnumerator > enumerator)
void audioDevicePropertyChanged(ComPtr< IMMDevice >)
QT_BEGIN_NAMESPACE DEFINE_PROPERTYKEY(PKEY_Device_ContainerIdQt, 0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c, 2)
static std::optional< QString > getDeviceId(const ComPtr< IMMDevice > &dev)
static std::optional< QAudioDevice > asQAudioDevice(ComPtr< IMMDevice > device, QAudioDevice::Mode mode, std::optional< QString > defaultAudioDeviceID)