Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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
5#include "qsemaphore.h"
6#include "qmutex.h"
7
8#include <private/qmemoryvideobuffer_p.h>
9#include <private/qwindowsmultimediautils_p.h>
10#include <private/qvideoframe_p.h>
11#include <private/qcomobject_p.h>
12#include <private/qwmf_support_p.h>
13
14#include <QtCore/private/qsystemerror_p.h>
15
16#include <mfapi.h>
17#include <mfidl.h>
18#include <mferror.h>
19#include <mfreadwrite.h>
20
21#include <system_error>
22
23QT_BEGIN_NAMESPACE
24
25using namespace QWindowsMultimediaUtils;
26
28{
29public:
30 //from IMFSourceReaderCallback
31 STDMETHODIMP OnReadSample(HRESULT status, DWORD, DWORD, LONGLONG timestamp, IMFSample *sample) override;
33 STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) override { return S_OK; }
34
35 void setActiveCamera(ActiveCamera *activeCamera)
36 {
37 QMutexLocker locker(&m_mutex);
38 m_activeCamera = activeCamera;
39 }
40private:
41 // Destructor is not public. Caller should call Release.
43
44 ActiveCamera *m_activeCamera = nullptr;
45 QMutex m_mutex;
46};
47
48static ComPtr<IMFSourceReader> createCameraReader(IMFMediaSource *mediaSource,
49 const ComPtr<CameraReaderCallback> &callback)
50{
51 ComPtr<IMFSourceReader> sourceReader;
52 ComPtr<IMFAttributes> readerAttributes;
53
54 HRESULT hr = MFCreateAttributes(readerAttributes.GetAddressOf(), 1);
55 if (SUCCEEDED(hr)) {
56 hr = readerAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback.Get());
57 if (SUCCEEDED(hr)) {
58 hr = MFCreateSourceReaderFromMediaSource(mediaSource, readerAttributes.Get(), sourceReader.GetAddressOf());
59 if (SUCCEEDED(hr))
60 return sourceReader;
61 }
62 }
63
64 qWarning() << "Failed to create camera IMFSourceReader" << hr;
65 return sourceReader;
66}
67
68static ComPtr<IMFMediaSource> createCameraSource(const QString &deviceId)
69{
70 ComPtr<IMFMediaSource> mediaSource;
71 ComPtr<IMFAttributes> sourceAttributes;
72 HRESULT hr = MFCreateAttributes(sourceAttributes.GetAddressOf(), 2);
73 if (SUCCEEDED(hr)) {
74 hr = sourceAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
75 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
76 if (SUCCEEDED(hr)) {
77 hr = sourceAttributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
78 reinterpret_cast<LPCWSTR>(deviceId.utf16()));
79 if (SUCCEEDED(hr)) {
80 hr = MFCreateDeviceSource(sourceAttributes.Get(), mediaSource.GetAddressOf());
81 if (SUCCEEDED(hr))
82 return mediaSource;
83 }
84 }
85 }
86 qWarning() << "Failed to create camera IMFMediaSource" << hr;
87 return mediaSource;
88}
89
90static int calculateVideoFrameStride(IMFMediaType *videoType, int width)
91{
92 Q_ASSERT(videoType);
93
94 GUID subtype = GUID_NULL;
95 HRESULT hr = videoType->GetGUID(MF_MT_SUBTYPE, &subtype);
96 if (SUCCEEDED(hr)) {
97 LONG stride = 0;
98 hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &stride);
99 if (SUCCEEDED(hr))
100 return int(qAbs(stride));
101 }
102
103 qWarning() << "Failed to calculate video stride" << QSystemError::windowsComString(hr);
104 return 0;
105}
106
107static bool setCameraReaderFormat(IMFSourceReader *sourceReader, IMFMediaType *videoType)
108{
109 Q_ASSERT(sourceReader);
110 Q_ASSERT(videoType);
111
112 HRESULT hr = sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr,
113 videoType);
114 if (FAILED(hr))
115 qWarning() << "Failed to set video format" << QSystemError::windowsComString(hr);
116
117 return SUCCEEDED(hr);
118}
119
120static ComPtr<IMFMediaType> findVideoType(IMFSourceReader *reader,
121 const QCameraFormat &format)
122{
123 for (DWORD i = 0;; ++i) {
124 ComPtr<IMFMediaType> candidate;
125 HRESULT hr = reader->GetNativeMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, i,
126 candidate.GetAddressOf());
127 if (FAILED(hr))
128 break;
129
130 GUID subtype = GUID_NULL;
131 if (FAILED(candidate->GetGUID(MF_MT_SUBTYPE, &subtype)))
132 continue;
133
134 if (format.pixelFormat() != pixelFormatFromMediaSubtype(subtype))
135 continue;
136
137 UINT32 width = 0u;
138 UINT32 height = 0u;
139 if (FAILED(MFGetAttributeSize(candidate.Get(), MF_MT_FRAME_SIZE, &width, &height)))
140 continue;
141
142 if (format.resolution() != QSize{ int(width), int(height) })
143 continue;
144
145 return candidate;
146 }
147 return {};
148}
149
151public:
152 static std::unique_ptr<ActiveCamera> create(QWindowsCamera &wc, const QCameraDevice &device, const QCameraFormat &format)
153 {
154 auto ac = std::unique_ptr<ActiveCamera>(new ActiveCamera(wc));
155 ac->m_source = createCameraSource(QString::fromUtf8(device.id()));
156 if (!ac->m_source)
157 return {};
158
159 ac->m_readerCallback = makeComObject<CameraReaderCallback>();
160 ac->m_readerCallback->setActiveCamera(ac.get());
161 ac->m_reader = createCameraReader(ac->m_source.Get(), ac->m_readerCallback);
162 if (!ac->m_reader)
163 return {};
164
165 if (!ac->setFormat(format))
166 return {};
167
168 return ac;
169 }
170
171 bool setFormat(const QCameraFormat &format)
172 {
173 flush();
174
175 auto videoType = findVideoType(m_reader.Get(), format);
176 if (videoType) {
177 if (setCameraReaderFormat(m_reader.Get(), videoType.Get())) {
178 m_frameFormat = { format.resolution(), format.pixelFormat() };
179 m_videoFrameStride =
180 calculateVideoFrameStride(videoType.Get(), format.resolution().width());
181 }
182 }
183
184 m_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, nullptr, nullptr, nullptr,
185 nullptr);
186 return true;
187 }
188
189 void onReadSample(HRESULT status, LONGLONG timestamp, IMFSample *sample)
190 {
191 if (FAILED(status)) {
192 const std::string msg{ std::system_category().message(status) };
193 m_windowsCamera.updateError(QCamera::CameraError, QString::fromStdString(msg));
194 return;
195 }
196
197 if (sample) {
198 ComPtr<IMFMediaBuffer> mediaBuffer;
199 if (SUCCEEDED(sample->ConvertToContiguousBuffer(mediaBuffer.GetAddressOf()))) {
200
201 auto result = QWMF::withLockedBuffer(mediaBuffer,
202 [&](QSpan<BYTE> data, QSpan<BYTE> /*max*/) {
203 QByteArray bytes(data);
204 auto buffer = std::make_unique<QMemoryVideoBuffer>(std::move(bytes),
205 m_videoFrameStride);
206 QVideoFrame frame =
207 QVideoFramePrivate::createFrame(std::move(buffer), m_frameFormat);
208
209 // WMF uses 100-nanosecond units, Qt uses microseconds
210 frame.setStartTime(timestamp / 10);
211
212 LONGLONG duration = -1;
213 if (SUCCEEDED(sample->GetSampleDuration(&duration)))
214 frame.setEndTime((timestamp + duration) / 10);
215
216 return frame;
217 });
218
219 if (result)
220 emit m_windowsCamera.newVideoFrame(result.value());
221 }
222 }
223
224 m_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, nullptr,
225 nullptr, nullptr, nullptr);
226 }
227
228 void onFlush()
229 {
230 m_flushWait.release();
231 }
232
234 {
235 flush();
236 if (m_readerCallback)
237 m_readerCallback->setActiveCamera(nullptr);
238 }
239
240private:
241 explicit ActiveCamera(QWindowsCamera &wc) : m_windowsCamera(wc), m_flushWait(0) {};
242
243 void flush()
244 {
245 if (m_reader && SUCCEEDED(m_reader->Flush(MF_SOURCE_READER_FIRST_VIDEO_STREAM))) {
246 m_flushWait.acquire();
247 }
248 }
249
250 QWindowsCamera &m_windowsCamera;
251
252 QSemaphore m_flushWait;
253
254 ComPtr<IMFMediaSource> m_source;
255 ComPtr<IMFSourceReader> m_reader;
256 ComPtr<CameraReaderCallback> m_readerCallback;
257
258 QVideoFrameFormat m_frameFormat;
259 int m_videoFrameStride = 0;
260};
261
262STDMETHODIMP CameraReaderCallback::OnReadSample(HRESULT status, DWORD, DWORD, LONGLONG timestamp, IMFSample *sample)
263{
264 QMutexLocker locker(&m_mutex);
265 if (m_activeCamera)
266 m_activeCamera->onReadSample(status, timestamp, sample);
267
268 return status;
269}
270
272{
273 QMutexLocker locker(&m_mutex);
274 if (m_activeCamera)
275 m_activeCamera->onFlush();
276 return S_OK;
277}
278
279QWindowsCamera::QWindowsCamera(QCamera *camera)
280 : QPlatformCamera(camera)
281{
282 m_cameraDevice = camera ? camera->cameraDevice() : QCameraDevice{};
283}
284
289
290void QWindowsCamera::setActive(bool active)
291{
292 if (bool(m_active) == active)
293 return;
294
295 if (active) {
296 if (m_cameraDevice.isNull())
297 return;
298
299 if (m_cameraFormat.isNull())
300 m_cameraFormat = findBestCameraFormat(m_cameraDevice);
301
302 m_active = ActiveCamera::create(*this, m_cameraDevice, m_cameraFormat);
303 if (m_active)
304 activeChanged(true);
305
306 } else {
307 m_active.reset();
308 emit activeChanged(false);
309 }
310}
311
312void QWindowsCamera::setCamera(const QCameraDevice &camera)
313{
314 bool active = bool(m_active);
315 if (active)
316 setActive(false);
317 m_cameraDevice = camera;
318 m_cameraFormat = {};
319 if (active)
320 setActive(true);
321}
322
323bool QWindowsCamera::setCameraFormat(const QCameraFormat &format)
324{
325 if (format.isNull())
326 return false;
327
328 bool ok = m_active ? m_active->setFormat(format) : true;
329 if (ok)
330 m_cameraFormat = format;
331
332 return ok;
333}
334
335QT_END_NAMESPACE
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 OnEvent(DWORD, IMFMediaEvent *) override
STDMETHODIMP OnFlush(DWORD) override
void setActiveCamera(ActiveCamera *activeCamera)
~CameraReaderCallback() override=default
\inmodule QtCore
Definition qmutex.h:346
bool setCameraFormat(const QCameraFormat &) override
void setCamera(const QCameraDevice &camera) override
void setActive(bool active) override
~QWindowsCamera() override
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)