Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qwindowscamera.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 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
4#include "qwindowscamera_p.h"
5#include "qsemaphore.h"
6#include "qmutex.h"
7
8#include <private/qmemoryvideobuffer_p.h>
9#include <private/qwindowsmfdefs_p.h>
10#include <private/qwindowsmultimediautils_p.h>
11
12#include <mfapi.h>
13#include <mfidl.h>
14#include <mferror.h>
15#include <mfreadwrite.h>
16
17#include <system_error>
18
20
21using namespace QWindowsMultimediaUtils;
22
23class CameraReaderCallback : public IMFSourceReaderCallback
24{
25public:
26 CameraReaderCallback() : m_cRef(1) {}
28
29 //from IUnknown
30 STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) override
31 {
32 if (!ppvObject)
33 return E_POINTER;
34 if (riid == IID_IMFSourceReaderCallback) {
35 *ppvObject = static_cast<IMFSourceReaderCallback*>(this);
36 } else if (riid == IID_IUnknown) {
37 *ppvObject = static_cast<IUnknown*>(static_cast<IMFSourceReaderCallback*>(this));
38 } else {
39 *ppvObject = nullptr;
40 return E_NOINTERFACE;
41 }
42 AddRef();
43 return S_OK;
44 }
45
46 STDMETHODIMP_(ULONG) AddRef() override
47 {
48 return InterlockedIncrement(&m_cRef);
49 }
50
51 STDMETHODIMP_(ULONG) Release() override
52 {
53 LONG cRef = InterlockedDecrement(&m_cRef);
54 if (cRef == 0) {
55 delete this;
56 }
57 return cRef;
58 }
59
60 //from IMFSourceReaderCallback
61 STDMETHODIMP OnReadSample(HRESULT status, DWORD, DWORD, LONGLONG timestamp, IMFSample *sample) override;
62 STDMETHODIMP OnFlush(DWORD) override;
63 STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) override { return S_OK; }
64
65 void setActiveCamera(ActiveCamera *activeCamera)
66 {
67 QMutexLocker locker(&m_mutex);
68 m_activeCamera = activeCamera;
69 }
70private:
71 LONG m_cRef;
72 ActiveCamera *m_activeCamera = nullptr;
73 QMutex m_mutex;
74};
75
76static ComPtr<IMFSourceReader> createCameraReader(IMFMediaSource *mediaSource,
77 const ComPtr<CameraReaderCallback> &callback)
78{
79 ComPtr<IMFSourceReader> sourceReader;
80 ComPtr<IMFAttributes> readerAttributes;
81
82 HRESULT hr = MFCreateAttributes(readerAttributes.GetAddressOf(), 1);
83 if (SUCCEEDED(hr)) {
84 hr = readerAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback.Get());
85 if (SUCCEEDED(hr)) {
86 hr = MFCreateSourceReaderFromMediaSource(mediaSource, readerAttributes.Get(), sourceReader.GetAddressOf());
87 if (SUCCEEDED(hr))
88 return sourceReader;
89 }
90 }
91
92 qWarning() << "Failed to create camera IMFSourceReader" << hr;
93 return sourceReader;
94}
95
96static ComPtr<IMFMediaSource> createCameraSource(const QString &deviceId)
97{
98 ComPtr<IMFMediaSource> mediaSource;
99 ComPtr<IMFAttributes> sourceAttributes;
100 HRESULT hr = MFCreateAttributes(sourceAttributes.GetAddressOf(), 2);
101 if (SUCCEEDED(hr)) {
102 hr = sourceAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, QMM_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
103 if (SUCCEEDED(hr)) {
104 hr = sourceAttributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
105 reinterpret_cast<LPCWSTR>(deviceId.utf16()));
106 if (SUCCEEDED(hr)) {
107 hr = MFCreateDeviceSource(sourceAttributes.Get(), mediaSource.GetAddressOf());
108 if (SUCCEEDED(hr))
109 return mediaSource;
110 }
111 }
112 }
113 qWarning() << "Failed to create camera IMFMediaSource" << hr;
114 return mediaSource;
115}
116
117static int calculateVideoFrameStride(IMFMediaType *videoType, int width)
118{
119 Q_ASSERT(videoType);
120
121 GUID subtype = GUID_NULL;
122 HRESULT hr = videoType->GetGUID(MF_MT_SUBTYPE, &subtype);
123 if (SUCCEEDED(hr)) {
124 LONG stride = 0;
125 hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &stride);
126 if (SUCCEEDED(hr))
127 return int(qAbs(stride));
128 }
129
130 qWarning() << "Failed to calculate video stride" << errorString(hr);
131 return 0;
132}
133
134static bool setCameraReaderFormat(IMFSourceReader *sourceReader, IMFMediaType *videoType)
135{
136 Q_ASSERT(sourceReader);
137 Q_ASSERT(videoType);
138
139 HRESULT hr = sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr,
140 videoType);
141 if (FAILED(hr))
142 qWarning() << "Failed to set video format" << errorString(hr);
143
144 return SUCCEEDED(hr);
145}
146
147static ComPtr<IMFMediaType> findVideoType(IMFSourceReader *reader,
148 const QCameraFormat &format)
149{
150 for (DWORD i = 0;; ++i) {
151 ComPtr<IMFMediaType> candidate;
152 HRESULT hr = reader->GetNativeMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, i,
153 candidate.GetAddressOf());
154 if (FAILED(hr))
155 break;
156
157 GUID subtype = GUID_NULL;
158 if (FAILED(candidate->GetGUID(MF_MT_SUBTYPE, &subtype)))
159 continue;
160
161 if (format.pixelFormat() != pixelFormatFromMediaSubtype(subtype))
162 continue;
163
164 UINT32 width = 0u;
165 UINT32 height = 0u;
166 if (FAILED(MFGetAttributeSize(candidate.Get(), MF_MT_FRAME_SIZE, &width, &height)))
167 continue;
168
169 if (format.resolution() != QSize{ int(width), int(height) })
170 continue;
171
172 return candidate;
173 }
174 return {};
175}
176
178public:
179 ActiveCamera() = delete;
180
181 static std::unique_ptr<ActiveCamera> create(QWindowsCamera &wc, const QCameraDevice &device, const QCameraFormat &format)
182 {
183 auto ac = std::unique_ptr<ActiveCamera>(new ActiveCamera(wc));
184 ac->m_source = createCameraSource(device.id());
185 if (!ac->m_source)
186 return {};
187
188 ac->m_readerCallback = makeComObject<CameraReaderCallback>();
189 ac->m_readerCallback->setActiveCamera(ac.get());
190 ac->m_reader = createCameraReader(ac->m_source.Get(), ac->m_readerCallback);
191 if (!ac->m_reader)
192 return {};
193
194 if (!ac->setFormat(format))
195 return {};
196
197 return ac;
198 }
199
201 {
202 m_reader->Flush(MF_SOURCE_READER_FIRST_VIDEO_STREAM);
203 m_flushWait.acquire();
204
205 auto videoType = findVideoType(m_reader.Get(), format);
206 if (videoType) {
207 if (setCameraReaderFormat(m_reader.Get(), videoType.Get())) {
208 m_frameFormat = { format.resolution(), format.pixelFormat() };
209 m_videoFrameStride =
210 calculateVideoFrameStride(videoType.Get(), format.resolution().width());
211 }
212 }
213
214 m_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, nullptr, nullptr, nullptr,
215 nullptr);
216 return true;
217 }
218
219 void onReadSample(HRESULT status, LONGLONG timestamp, IMFSample *sample)
220 {
221 if (FAILED(status)) {
222 emit m_windowsCamera.error(int(status), std::system_category().message(status).c_str());
223 return;
224 }
225
226 if (sample) {
227 ComPtr<IMFMediaBuffer> mediaBuffer;
228 if (SUCCEEDED(sample->ConvertToContiguousBuffer(mediaBuffer.GetAddressOf()))) {
229
230 DWORD bufLen = 0;
231 BYTE *buffer = nullptr;
232 if (SUCCEEDED(mediaBuffer->Lock(&buffer, nullptr, &bufLen))) {
233 QByteArray bytes(reinterpret_cast<char*>(buffer), qsizetype(bufLen));
234 QVideoFrame frame(new QMemoryVideoBuffer(bytes, m_videoFrameStride), m_frameFormat);
235
236 // WMF uses 100-nanosecond units, Qt uses microseconds
237 frame.setStartTime(timestamp / 10);
238
239 LONGLONG duration = -1;
240 if (SUCCEEDED(sample->GetSampleDuration(&duration)))
241 frame.setEndTime((timestamp + duration) / 10);
242
243 emit m_windowsCamera.newVideoFrame(frame);
244 mediaBuffer->Unlock();
245 }
246 }
247 }
248
249 m_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, nullptr,
250 nullptr, nullptr, nullptr);
251 }
252
253 void onFlush()
254 {
255 m_flushWait.release();
256 }
257
259 {
260 m_reader->Flush(MF_SOURCE_READER_FIRST_VIDEO_STREAM);
261 m_flushWait.acquire();
262 m_readerCallback->setActiveCamera(nullptr);
263 }
264
265private:
266 explicit ActiveCamera(QWindowsCamera &wc) : m_windowsCamera(wc), m_flushWait(0) {};
267
268 QWindowsCamera &m_windowsCamera;
269
270 QSemaphore m_flushWait;
271
272 ComPtr<IMFMediaSource> m_source;
273 ComPtr<IMFSourceReader> m_reader;
274 ComPtr<CameraReaderCallback> m_readerCallback;
275
276 QVideoFrameFormat m_frameFormat;
277 int m_videoFrameStride = 0;
278};
279
280STDMETHODIMP CameraReaderCallback::OnReadSample(HRESULT status, DWORD, DWORD, LONGLONG timestamp, IMFSample *sample)
281{
282 QMutexLocker locker(&m_mutex);
283 if (m_activeCamera)
284 m_activeCamera->onReadSample(status, timestamp, sample);
285
286 return status;
287}
288
290{
291 QMutexLocker locker(&m_mutex);
292 if (m_activeCamera)
293 m_activeCamera->onFlush();
294 return S_OK;
295}
296
299{
300 m_cameraDevice = camera ? camera->cameraDevice() : QCameraDevice{};
301}
302
304{
306}
307
309{
310 if (bool(m_active) == active)
311 return;
312
313 if (active) {
314 if (m_cameraDevice.isNull())
315 return;
316
317 if (m_cameraFormat.isNull())
318 m_cameraFormat = findBestCameraFormat(m_cameraDevice);
319
320 m_active = ActiveCamera::create(*this, m_cameraDevice, m_cameraFormat);
321 if (m_active)
322 activeChanged(true);
323
324 } else {
325 emit activeChanged(false);
326 m_active.reset();
327 }
328}
329
331{
332 bool active = bool(m_active);
333 if (active)
334 setActive(false);
335 m_cameraDevice = camera;
336 m_cameraFormat = {};
337 if (active)
338 setActive(true);
339}
340
342{
343 if (format.isNull())
344 return false;
345
346 bool ok = m_active ? m_active->setFormat(format) : true;
347 if (ok)
348 m_cameraFormat = format;
349
350 return ok;
351}
352
IOBluetoothDevice * device
ActiveCamera()=delete
bool setFormat(const QCameraFormat &format)
static std::unique_ptr< ActiveCamera > create(QWindowsCamera &wc, const QCameraDevice &device, const QCameraFormat &format)
void onReadSample(HRESULT status, LONGLONG timestamp, IMFSample *sample)
STDMETHODIMP OnReadSample(HRESULT status, DWORD, DWORD, LONGLONG timestamp, IMFSample *sample) override
STDMETHODIMP_(ULONG) Release() override
STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) override
STDMETHODIMP OnFlush(DWORD) override
void setActiveCamera(ActiveCamera *activeCamera)
STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) override
STDMETHODIMP_(ULONG) AddRef() override
\inmodule QtCore
Definition qbytearray.h:57
The QCameraDevice class provides general information about camera devices.
bool isNull() const
Returns true if this QCameraDevice is null or invalid.
The QCameraFormat class describes a video format supported by a camera device. \inmodule QtMultimedia...
bool isNull() const noexcept
Returns true if this is a default constructed QCameraFormat.
The QCamera class provides interface for system camera devices.
Definition qcamera.h:28
QCameraDevice cameraDevice
\qmlproperty cameraDevice QtMultimedia::Camera::cameraDevice
Definition qcamera.h:32
The QMemoryVideoBuffer class provides a system memory allocated video data buffer.
\inmodule QtCore
Definition qmutex.h:317
\inmodule QtCore
Definition qmutex.h:285
QCameraFormat findBestCameraFormat(const QCameraDevice &camera) const
void error(int error, const QString &errorString)
void newVideoFrame(const QVideoFrame &)
void activeChanged(bool)
\inmodule QtCore
Definition qsemaphore.h:16
void acquire(int n=1)
Tries to acquire n resources guarded by the semaphore.
void release(int n=1)
Releases n resources guarded by the semaphore.
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
const ushort * utf16() const
Returns the QString as a '\0\'-terminated array of unsigned shorts.
Definition qstring.cpp:6737
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
QVideoFrameFormat::PixelFormat pixelFormat() const
Returns the pixel format of frames in a video stream.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:26
bool setCameraFormat(const QCameraFormat &) override
void setCamera(const QCameraDevice &camera) override
void setActive(bool active) override
QWindowsCamera(QCamera *parent)
~QWindowsCamera() override
QCamera * camera
Definition camera.cpp:19
static int calculateVideoFrameStride(IMFMediaType *videoType, int width)
static bool setCameraReaderFormat(IMFSourceReader *sourceReader, IMFMediaType *videoType)
static ComPtr< IMFSourceReader > createCameraReader(IMFMediaSource *mediaSource, const ComPtr< CameraReaderCallback > &callback)
static ComPtr< IMFMediaType > findVideoType(IMFSourceReader *reader, const QCameraFormat &format)
static ComPtr< IMFMediaSource > createCameraSource(const QString &deviceId)
Combined button and popup list for selecting options.
Q_MULTIMEDIA_EXPORT QString errorString(HRESULT hr)
Q_MULTIMEDIA_EXPORT QVideoFrameFormat::PixelFormat pixelFormatFromMediaSubtype(const GUID &subtype)
#define qWarning
Definition qlogging.h:162
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLint GLsizei GLsizei height
const void GLsizei GLsizei stride
GLenum GLuint buffer
GLint GLsizei width
GLuint GLsizei const GLchar * message
GLint GLsizei GLsizei GLenum format
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
ptrdiff_t qsizetype
Definition qtypes.h:70
IUIViewSettingsInterop __RPC__in REFIID riid
long HRESULT
const GUID QMM_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
HRESULT WINAPI MFCreateDeviceSource(IMFAttributes *pAttributes, IMFMediaSource **ppSource)
QFrame frame
[0]