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