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