Revision control

Copy as Markdown

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qwindowsvideodevices_p.h"
#include <QtCore/quuid.h>
#include <QtCore/private/qcomptr_p.h>
#include <QtCore/private/qfunctions_win_p.h>
#include <QtMultimedia/private/qcameradevice_p.h>
#include <QtMultimedia/private/qcomtaskresource_p.h>
#include <QtMultimedia/private/qwindowsmultimediautils_p.h>
#include <dbt.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mferror.h>
#include <ks.h>
// MinGW-13.1 workaround
#ifndef KSCATEGORY_SENSOR_CAMERA
static constexpr GUID KSCATEGORY_SENSOR_CAMERA = {
0x24e552d7, 0x6523, 0x47f7, { 0xa6, 0x47, 0xd3, 0x46, 0x5b, 0xf1, 0xf5, 0xca }
};
#endif
QT_BEGIN_NAMESPACE
namespace {
LRESULT QT_WIN_CALLBACK deviceNotificationWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_DEVICECHANGE) {
auto b = (PDEV_BROADCAST_HDR)lParam;
if (b && b->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
auto wmd = reinterpret_cast<QWindowsVideoDevices *>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (wmd) {
if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
wmd->onVideoInputsChanged();
}
}
}
}
return 1;
}
LPCWSTR getWindowsClassName()
{
// each Qt instance should have its own window class name to prevent name clashes when multiple
// qt instances are used in the same process.
// appending a uuid is good enough to ensure that.
static const std::wstring singleton =
QString(QStringLiteral("QWindowsMediaDevicesMessageWindow_")
+ QUuid::createUuid().toString())
.toStdWString();
Q_ASSERT(singleton.size() < 256 && "The maximum length for lpszClassName is 256");
return singleton.c_str();
}
HWND createMessageOnlyWindow()
{
WNDCLASSEX wx = {};
wx.cbSize = sizeof(WNDCLASSEX);
wx.lpfnWndProc = deviceNotificationWndProc;
wx.hInstance = GetModuleHandle(nullptr);
wx.lpszClassName = getWindowsClassName();
if (!RegisterClassEx(&wx))
return nullptr;
auto hwnd = CreateWindowEx(0, getWindowsClassName(), TEXT("Message"), 0, 0, 0, 0, 0,
HWND_MESSAGE, nullptr, nullptr, nullptr);
if (!hwnd) {
UnregisterClass(getWindowsClassName(), GetModuleHandle(nullptr));
return nullptr;
}
return hwnd;
}
} // namespace
QWindowsVideoDevices::QWindowsVideoDevices(QPlatformMediaIntegration *integration)
: QPlatformVideoDevices(integration)
{
qt_win_ensureComInitializedOnThisThread();
m_videoDeviceMsgWindow = createMessageOnlyWindow();
if (m_videoDeviceMsgWindow) {
SetWindowLongPtr(m_videoDeviceMsgWindow, GWLP_USERDATA, (LONG_PTR)this);
DEV_BROADCAST_DEVICEINTERFACE di = {};
di.dbcc_size = sizeof(di);
di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
di.dbcc_classguid = KSCATEGORY_VIDEO_CAMERA;
m_videoDeviceNotification =
RegisterDeviceNotification(m_videoDeviceMsgWindow, &di, DEVICE_NOTIFY_WINDOW_HANDLE);
if (!m_videoDeviceNotification) {
DestroyWindow(m_videoDeviceMsgWindow);
m_videoDeviceMsgWindow = nullptr;
UnregisterClass(getWindowsClassName(), GetModuleHandle(nullptr));
}
}
if (!m_videoDeviceNotification) {
qWarning() << "Video device change notification disabled";
}
}
QWindowsVideoDevices::~QWindowsVideoDevices()
{
if (m_videoDeviceNotification) {
UnregisterDeviceNotification(m_videoDeviceNotification);
}
if (m_videoDeviceMsgWindow) {
DestroyWindow(m_videoDeviceMsgWindow);
UnregisterClass(getWindowsClassName(), GetModuleHandle(nullptr));
}
}
static QString getString(IMFActivate *device, const IID &id)
{
QComTaskResource<WCHAR> str;
UINT32 length = 0;
HRESULT hr = device->GetAllocatedString(id, str.address(), &length);
if (SUCCEEDED(hr)) {
return QString::fromWCharArray(str.get());
} else {
return {};
}
}
static std::optional<QCameraDevice> createCameraDevice(const QWindowsMediaFoundation &wmf,
IMFActivate *device)
{
auto info = std::make_unique<QCameraDevicePrivate>();
info->description = getString(device, MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME);
info->id = getString(device, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK).toUtf8();
ComPtr<IMFMediaSource> source;
HRESULT hr = device->ActivateObject(IID_PPV_ARGS(source.GetAddressOf()));
if (FAILED(hr))
return {};
ComPtr<IMFSourceReader> reader;
hr = wmf.mfCreateSourceReaderFromMediaSource(source.Get(), NULL, reader.GetAddressOf());
if (FAILED(hr))
return {};
QList<QSize> photoResolutions;
QList<QCameraFormat> videoFormats;
for (DWORD i = 0;; ++i) {
// Loop through the supported formats for the video device
ComPtr<IMFMediaType> mediaFormat;
hr = reader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, i,
mediaFormat.GetAddressOf());
if (FAILED(hr))
break;
auto maybeCamera = QWMF::cameraFormatFromMediaType(mediaFormat.Get());
if (maybeCamera) {
videoFormats << *maybeCamera;
photoResolutions << maybeCamera->resolution();
}
}
info->videoFormats = videoFormats;
info->photoResolutions = photoResolutions;
return info.release()->create();
}
static QList<QCameraDevice> readCameraDevices(const QWindowsMediaFoundation &wmf,
IMFAttributes *attr)
{
QList<QCameraDevice> cameras;
UINT32 count = 0;
IMFActivate **devicesRaw = nullptr;
HRESULT hr = wmf.mfEnumDeviceSources(attr, &devicesRaw, &count);
if (SUCCEEDED(hr)) {
QComTaskResource<IMFActivate *[], QComDeleter> devices(devicesRaw, count);
for (UINT32 i = 0; i < count; i++) {
IMFActivate *device = devices[i];
if (device) {
auto maybeCamera = createCameraDevice(wmf, device);
if (maybeCamera)
cameras << *maybeCamera;
}
}
}
return cameras;
}
QList<QCameraDevice> QWindowsVideoDevices::findVideoInputs() const
{
if (!m_wmf)
return {};
QList<QCameraDevice> cameras;
ComPtr<IMFAttributes> attr;
HRESULT hr = m_wmf->mfCreateAttributes(attr.GetAddressOf(), 2);
if (FAILED(hr))
return {};
hr = attr->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (SUCCEEDED(hr)) {
cameras << readCameraDevices(*m_wmf, attr.Get());
hr = attr->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_CATEGORY,
KSCATEGORY_SENSOR_CAMERA);
if (SUCCEEDED(hr))
cameras << readCameraDevices(*m_wmf, attr.Get());
}
return cameras;
}
QT_END_NAMESPACE