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/qcameradevice_p.h>
9#include <private/qmemoryvideobuffer_p.h>
10#include <private/qwindowsmultimediautils_p.h>
11#include <private/qvideoframe_p.h>
12#include <private/qcomobject_p.h>
13#include <private/qwmf_support_p.h>
14
15#include <QtCore/private/qsystemerror_p.h>
16
17#include <mfapi.h>
18#include <mfidl.h>
19#include <mferror.h>
20#include <mfreadwrite.h>
21
22#include <system_error>
23#include <utility>
24
25QT_BEGIN_NAMESPACE
26
27namespace QFFmpeg {
28
29qsizetype indexOfClosestCameraFormat(QSpan<const QCameraFormat> candidates,
30 const QCameraFormat &requested)
31{
32 qsizetype closest = -1;
33 std::pair<float, float> closestDistance{};
34
35 for (qsizetype i = 0; i != candidates.size(); ++i) {
36 const QCameraFormat &candidate = candidates[i];
37 if (candidate.pixelFormat() != requested.pixelFormat()
38 || candidate.resolution() != requested.resolution()) {
39 continue;
40 }
41
42 const std::pair distance{ qAbs(candidate.maxFrameRate() - requested.maxFrameRate()),
43 qAbs(candidate.minFrameRate() - requested.minFrameRate()) };
44 if (closest < 0 || distance < closestDistance) {
45 closest = i;
46 closestDistance = distance;
47 }
48 }
49
50 return closest;
51}
52
53namespace {
54
55class CameraReaderCallback : public QComObject<IMFSourceReaderCallback>
56{
57public:
58 //from IMFSourceReaderCallback
59 STDMETHODIMP OnReadSample(HRESULT status, DWORD, DWORD, LONGLONG timestamp, IMFSample *sample) override;
60 STDMETHODIMP OnFlush(DWORD) override;
61 STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) override { return S_OK; }
62
63 void setActiveCamera(ActiveCamera *activeCamera)
64 {
65 QMutexLocker locker(&m_mutex);
66 m_activeCamera = activeCamera;
67 }
68private:
69 // Destructor is not public. Caller should call Release.
70 ~CameraReaderCallback() override = default;
71
72 ActiveCamera *m_activeCamera = nullptr;
73 QMutex m_mutex;
74};
75
76ComPtr<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
96ComPtr<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,
103 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
104 if (SUCCEEDED(hr)) {
105 hr = sourceAttributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
106 reinterpret_cast<LPCWSTR>(deviceId.utf16()));
107 if (SUCCEEDED(hr)) {
108 hr = MFCreateDeviceSource(sourceAttributes.Get(), mediaSource.GetAddressOf());
109 if (SUCCEEDED(hr))
110 return mediaSource;
111 }
112 }
113 }
114 qWarning() << "Failed to create camera IMFMediaSource" << hr;
115 return mediaSource;
116}
117
118int calculateVideoFrameStride(IMFMediaType *videoType, int width)
119{
120 Q_ASSERT(videoType);
121
122 GUID subtype = GUID_NULL;
123 HRESULT hr = videoType->GetGUID(MF_MT_SUBTYPE, &subtype);
124 if (SUCCEEDED(hr)) {
125 LONG stride = 0;
126 hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &stride);
127 if (SUCCEEDED(hr))
128 return int(qAbs(stride));
129 }
130
131 qWarning() << "Failed to calculate video stride" << QSystemError::windowsComString(hr);
132 return 0;
133}
134
135bool setCameraReaderFormat(IMFSourceReader *sourceReader, IMFMediaType *videoType)
136{
137 Q_ASSERT(sourceReader);
138 Q_ASSERT(videoType);
139
140 HRESULT hr = sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr,
141 videoType);
142 if (FAILED(hr))
143 qWarning() << "Failed to set video format" << QSystemError::windowsComString(hr);
144
145 return SUCCEEDED(hr);
146}
147
148ComPtr<IMFMediaType> findVideoType(IMFSourceReader *reader, const QCameraFormat &format)
149{
150 std::vector<ComPtr<IMFMediaType>> mediaTypes;
151 std::vector<QCameraFormat> candidates;
152
153 for (DWORD i = 0;; ++i) {
154 ComPtr<IMFMediaType> mediaType;
155 HRESULT hr = reader->GetNativeMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, i,
156 mediaType.GetAddressOf());
157 if (FAILED(hr))
158 break;
159
160 const std::optional<QCameraFormat> candidate =
161 QWMF::cameraFormatFromMediaType(mediaType.Get());
162 if (!candidate)
163 continue;
164
165 mediaTypes.push_back(std::move(mediaType));
166 candidates.push_back(std::move(*candidate));
167 }
168
169 const qsizetype index = indexOfClosestCameraFormat(candidates, format);
170 if (index < 0)
171 return {};
172
173 return mediaTypes.at(index);
174}
175
176} // namespace
177
179{
180public:
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(QString::fromUtf8(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
200 bool setFormat(const QCameraFormat &format)
201 {
202 flush();
203
204 auto videoType = findVideoType(m_reader.Get(), format);
205 if (videoType) {
206 if (setCameraReaderFormat(m_reader.Get(), videoType.Get())) {
207 m_frameFormat = { format.resolution(), format.pixelFormat() };
208 m_frameFormat.setColorRange(QCameraFormatPrivate::getColorRange(format));
209 m_frameFormat.setColorSpace(QCameraFormatPrivate::getColorSpace(format));
210 m_videoFrameStride =
211 calculateVideoFrameStride(videoType.Get(), format.resolution().width());
212 }
213 }
214
215 m_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, nullptr, nullptr, nullptr,
216 nullptr);
217 return true;
218 }
219
220 void onReadSample(HRESULT status, LONGLONG timestamp, IMFSample *sample)
221 {
222 if (FAILED(status)) {
223 const std::string msg{ std::system_category().message(status) };
224 m_windowsCamera.updateError(QCamera::CameraError, QString::fromStdString(msg));
225 return;
226 }
227
228 if (sample) {
229 ComPtr<IMFMediaBuffer> mediaBuffer;
230 if (SUCCEEDED(sample->ConvertToContiguousBuffer(mediaBuffer.GetAddressOf()))) {
231
232 auto result = QWMF::withLockedBuffer(mediaBuffer,
233 [&](QSpan<BYTE> data, QSpan<BYTE> /*max*/) {
234 QByteArray bytes(data);
235 auto buffer = std::make_unique<QMemoryVideoBuffer>(std::move(bytes),
236 m_videoFrameStride);
237 QVideoFrame frame =
238 QVideoFramePrivate::createFrame(std::move(buffer), m_frameFormat);
239
240 // WMF uses 100-nanosecond units, Qt uses microseconds
241 frame.setStartTime(timestamp / 10);
242
243 LONGLONG duration = -1;
244 if (SUCCEEDED(sample->GetSampleDuration(&duration)))
245 frame.setEndTime((timestamp + duration) / 10);
246
247 return frame;
248 });
249
250 if (result)
251 emit m_windowsCamera.newVideoFrame(result.value());
252 }
253 }
254
255 m_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, nullptr,
256 nullptr, nullptr, nullptr);
257 }
258
259 void onFlush()
260 {
261 m_flushWait.release();
262 }
263
265 {
266 flush();
267 if (m_readerCallback)
268 m_readerCallback->setActiveCamera(nullptr);
269 }
270
271private:
272 explicit ActiveCamera(QWindowsCamera &wc) : m_windowsCamera(wc), m_flushWait(0) { }
273
274 void flush()
275 {
276 if (m_reader && SUCCEEDED(m_reader->Flush(MF_SOURCE_READER_FIRST_VIDEO_STREAM))) {
277 m_flushWait.acquire();
278 }
279 }
280
281 QWindowsCamera &m_windowsCamera;
282
283 QSemaphore m_flushWait;
284
285 ComPtr<IMFMediaSource> m_source;
286 ComPtr<IMFSourceReader> m_reader;
287 ComPtr<CameraReaderCallback> m_readerCallback;
288
289 QVideoFrameFormat m_frameFormat;
290 int m_videoFrameStride = 0;
291};
292
293STDMETHODIMP CameraReaderCallback::OnReadSample(HRESULT status, DWORD, DWORD, LONGLONG timestamp, IMFSample *sample)
294{
295 QMutexLocker locker(&m_mutex);
296 if (m_activeCamera)
297 m_activeCamera->onReadSample(status, timestamp, sample);
298
299 return S_OK;
300}
301
302STDMETHODIMP CameraReaderCallback::OnFlush(DWORD)
303{
304 QMutexLocker locker(&m_mutex);
305 if (m_activeCamera)
306 m_activeCamera->onFlush();
307 return S_OK;
308}
309
312{
313 m_cameraDevice = camera ? camera->cameraDevice() : QCameraDevice{};
314}
315
320
321void QWindowsCamera::setActive(bool active)
322{
323 if (bool(m_active) == active)
324 return;
325
326 if (active) {
327 if (m_cameraDevice.isNull())
328 return;
329
330 if (m_cameraFormat.isNull())
331 m_cameraFormat = findBestCameraFormat(m_cameraDevice);
332
333 m_active = ActiveCamera::create(*this, m_cameraDevice, m_cameraFormat);
334 if (m_active)
335 activeChanged(true);
336
337 } else {
338 m_active.reset();
339 emit activeChanged(false);
340 }
341}
342
343void QWindowsCamera::setCamera(const QCameraDevice &camera)
344{
345 bool active = bool(m_active);
346 if (active)
347 setActive(false);
348 m_cameraDevice = camera;
349 m_cameraFormat = {};
350 if (active)
351 setActive(true);
352}
353
354bool QWindowsCamera::setCameraFormat(const QCameraFormat &format)
355{
356 if (format.isNull())
357 return false;
358
359 bool ok = m_active ? m_active->setFormat(format) : true;
360 if (ok)
361 m_cameraFormat = format;
362
363 return ok;
364}
365
366} // namespace QFFmpeg
367
368QT_END_NAMESPACE
void onReadSample(HRESULT status, LONGLONG timestamp, IMFSample *sample)
bool setFormat(const QCameraFormat &format)
static std::unique_ptr< ActiveCamera > create(QWindowsCamera &wc, const QCameraDevice &device, const QCameraFormat &format)
bool setCameraFormat(const QCameraFormat &) override
void setCamera(const QCameraDevice &camera) override
QWindowsCamera(QCamera *parent)
void setActive(bool active) override
\inmodule QtCore
Definition qmutex.h:346
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
qsizetype indexOfClosestCameraFormat(QSpan< const QCameraFormat > candidates, const QCameraFormat &requested)