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
avfimagecapture.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
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 <QtMultimedia/private/qavfcameradebug_p.h>
7#include <QtMultimedia/private/qavfcamerautility_p.h>
8#include "avfcamera_p.h"
11#include "private/qmediastoragelocation_p.h"
12#include <private/qplatformimagecapture_p.h>
13#include <private/qmemoryvideobuffer_p.h>
14#include <private/qvideoframe_p.h>
15
16#include <QtCore/qurl.h>
17#include <QtCore/qfile.h>
18#include <QtCore/qbuffer.h>
19#include <QtConcurrent/qtconcurrentrun.h>
20#include <QtGui/qimagereader.h>
21
22#import <AVFoundation/AVFoundation.h>
23
24QT_USE_NAMESPACE
25
28{
29 m_stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
30
31 NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
32 AVVideoCodecTypeJPEG, AVVideoCodecKey, nil];
33
34 [m_stillImageOutput setOutputSettings:outputSettings];
35 [outputSettings release];
36}
37
38AVFImageCapture::~AVFImageCapture()
39{
40 [m_stillImageOutput release];
41}
42
44{
45 return m_cameraControl && m_videoConnection && m_cameraControl->isActive();
46}
47
48void AVFImageCapture::updateReadyStatus()
49{
50 if (m_ready != isReadyForCapture()) {
51 m_ready = !m_ready;
52 qCDebug(qLcCamera) << "ReadyToCapture status changed:" << m_ready;
53 Q_EMIT readyForCaptureChanged(m_ready);
54 }
55}
56
57int AVFImageCapture::doCapture(const QString &fileName)
58{
59 if (!m_session) {
60 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
61 Q_ARG(int, m_lastCaptureId),
62 Q_ARG(int, QImageCapture::ResourceError),
63 Q_ARG(QString, QPlatformImageCapture::msgImageCaptureNotSet()));
64 return -1;
65 }
67 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
68 Q_ARG(int, m_lastCaptureId),
69 Q_ARG(int, QImageCapture::NotReadyError),
70 Q_ARG(QString, QPlatformImageCapture::msgCameraNotReady()));
71 return -1;
72 }
73 m_lastCaptureId++;
74
75 bool captureToBuffer = fileName.isEmpty();
76
77 CaptureRequest request = { m_lastCaptureId, QSharedPointer<QSemaphore>::create()};
78 m_requestsMutex.lock();
79 m_captureRequests.enqueue(request);
80 m_requestsMutex.unlock();
81
82 [m_stillImageOutput captureStillImageAsynchronouslyFromConnection:m_videoConnection
83 completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
84
85 if (error) {
86 QStringList messageParts;
87 messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]);
88 messageParts << QString::fromUtf8([[error localizedFailureReason] UTF8String]);
89 messageParts << QString::fromUtf8([[error localizedRecoverySuggestion] UTF8String]);
90
91 QString errorMessage = messageParts.join(QChar(u' '));
92 qCDebug(qLcCamera) << "Image capture failed:" << errorMessage;
93
94 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
95 Q_ARG(int, request.captureId),
96 Q_ARG(int, QImageCapture::ResourceError),
97 Q_ARG(QString, errorMessage));
98 return;
99 }
100
101 // Wait for the preview to be generated before saving the JPEG (but only
102 // if we have AVFCameraRenderer attached).
103 // It is possible to stop camera immediately after trying to capture an
104 // image; this can result in a blocked callback's thread, waiting for a
105 // new viewfinder's frame to arrive/semaphore to be released. It is also
106 // unspecified on which thread this callback gets executed, (probably it's
107 // not the same thread that initiated a capture and stopped the camera),
108 // so we cannot reliably check the camera's status. Instead, we wait
109 // with a timeout and treat a failure to acquire a semaphore as an error.
110 if (!m_session->videoOutput() || request.previewReady->tryAcquire(1, 1000)) {
111 qCDebug(qLcCamera) << "Image capture completed";
112
113 NSData *nsJpgData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
114 QByteArray jpgData = QByteArray::fromRawData((const char *)[nsJpgData bytes], [nsJpgData length]);
115
116 if (captureToBuffer) {
117 QBuffer data(&jpgData);
118 QImageReader reader(&data, "JPEG");
119 QSize size = reader.size();
120 auto buffer = std::make_unique<QMemoryVideoBuffer>(
121 QByteArray(jpgData.constData(), jpgData.size()), -1);
122 QVideoFrame frame = QVideoFramePrivate::createFrame(
123 std::move(buffer), QVideoFrameFormat(size, QVideoFrameFormat::Format_Jpeg));
124 QMetaObject::invokeMethod(this, "imageAvailable", Qt::QueuedConnection,
125 Q_ARG(int, request.captureId),
126 Q_ARG(QVideoFrame, frame));
127 } else {
128 QFile f(fileName);
129 if (f.open(QFile::WriteOnly)) {
130 if (f.write(jpgData) != -1) {
131 QMetaObject::invokeMethod(this, "imageSaved", Qt::QueuedConnection,
132 Q_ARG(int, request.captureId),
133 Q_ARG(QString, fileName));
134 } else {
135 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
136 Q_ARG(int, request.captureId),
137 Q_ARG(int, QImageCapture::OutOfSpaceError),
138 Q_ARG(QString, f.errorString()));
139 }
140 } else {
141 QString errorMessage = tr("Could not open destination file:\n%1").arg(fileName);
142 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
143 Q_ARG(int, request.captureId),
144 Q_ARG(int, QImageCapture::ResourceError),
145 Q_ARG(QString, errorMessage));
146 }
147 }
148 } else {
149 const QLatin1String errorMessage("Image capture failed: timed out waiting"
150 " for a preview frame.");
151 qCDebug(qLcCamera) << errorMessage;
152 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
153 Q_ARG(int, request.captureId),
154 Q_ARG(int, QImageCapture::ResourceError),
155 Q_ARG(QString, errorMessage));
156 }
157 }];
158
159 return request.captureId;
160}
161
162int AVFImageCapture::capture(const QString &fileName)
163{
164 auto actualFileName = QMediaStorageLocation::generateFileName(fileName, QStandardPaths::PicturesLocation, QLatin1String("jpg"));
165
166 qCDebug(qLcCamera) << "Capture image to" << actualFileName;
167 return doCapture(actualFileName);
168}
169
171{
172 return doCapture(QString());
173}
174
175void AVFImageCapture::onNewViewfinderFrame(const QVideoFrame &frame)
176{
177 QMutexLocker locker(&m_requestsMutex);
178
179 if (m_captureRequests.isEmpty())
180 return;
181
182 CaptureRequest request = m_captureRequests.dequeue();
183 Q_EMIT imageExposed(request.captureId);
184
185 (void) QtConcurrent::run(&AVFImageCapture::makeCapturePreview, this,
186 request,
187 frame,
188 0 /* rotation */);
189}
190
191void AVFImageCapture::onCameraChanged()
192{
193 auto camera = m_service ? static_cast<AVFCamera *>(m_service->camera()) : nullptr;
194
195 if (camera == m_cameraControl)
196 return;
197
198 m_cameraControl = camera;
199
200 if (m_cameraControl)
201 connect(m_cameraControl, SIGNAL(activeChanged(bool)), this, SLOT(updateReadyStatus()));
202 updateReadyStatus();
203}
204
205void AVFImageCapture::makeCapturePreview(CaptureRequest request,
206 const QVideoFrame &frame,
207 int rotation)
208{
209 QTransform transform;
210 transform.rotate(rotation);
211
212 Q_EMIT imageCaptured(request.captureId, frame.toImage().transformed(transform));
213
214 request.previewReady->release();
215}
216
217void AVFImageCapture::updateCaptureConnection()
218{
219 if (m_session && m_session->videoCaptureDevice()) {
220 qCDebug(qLcCamera) << Q_FUNC_INFO;
221 AVCaptureSession *captureSession = m_session->captureSession();
222
223 if (![captureSession.outputs containsObject:m_stillImageOutput]) {
224 if ([captureSession canAddOutput:m_stillImageOutput]) {
225 [captureSession beginConfiguration];
226 // Lock the video capture device to make sure the active format is not reset
227 const AVFConfigurationLock lock(m_session->videoCaptureDevice());
228 [captureSession addOutput:m_stillImageOutput];
229 m_videoConnection = [m_stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
230 [captureSession commitConfiguration];
231 updateReadyStatus();
232 }
233 } else {
234 m_videoConnection = [m_stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
235 }
236 }
237}
238
239
241{
242 QImageEncoderSettings settings;
243
244 if (!videoCaptureDeviceIsValid())
245 return settings;
246
247 AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
248 if (!captureDevice.activeFormat) {
249 qCDebug(qLcCamera) << Q_FUNC_INFO << "no active format";
250 return settings;
251 }
252
253 QSize res(qt_device_format_resolution(captureDevice.activeFormat));
254#ifdef Q_OS_IOS
255 if (!m_service->avfImageCaptureControl() || !m_service->avfImageCaptureControl()->stillImageOutput()) {
256 qCDebug(qLcCamera) << Q_FUNC_INFO << "no still image output";
257 return settings;
258 }
259
260 AVCaptureStillImageOutput *stillImageOutput = m_service->avfImageCaptureControl()->stillImageOutput();
261 if (stillImageOutput.highResolutionStillImageOutputEnabled)
262 res = qt_device_format_high_resolution(captureDevice.activeFormat);
263#endif
264 if (res.isNull() || !res.isValid()) {
265 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to exctract the image resolution";
266 return settings;
267 }
268
269 settings.setResolution(res);
270 settings.setFormat(QImageCapture::JPEG);
271
272 return settings;
273}
274
275void AVFImageCapture::setImageSettings(const QImageEncoderSettings &settings)
276{
277 if (m_settings == settings)
278 return;
279
280 m_settings = settings;
282}
283
285{
286 if (!videoCaptureDeviceIsValid())
287 return false;
288
289 AVFCameraSession *session = m_service->session();
290 if (!session)
291 return false;
292
293 if (!m_service->imageCapture()
294 || !m_service->avfImageCaptureControl()->stillImageOutput()) {
295 qCDebug(qLcCamera) << Q_FUNC_INFO << "no still image output";
296 return false;
297 }
298
299 if (m_settings.format() != QImageCapture::UnspecifiedFormat && m_settings.format() != QImageCapture::JPEG) {
300 qCDebug(qLcCamera) << Q_FUNC_INFO << "unsupported format:" << m_settings.format();
301 return false;
302 }
303
304 QSize res(m_settings.resolution());
305 if (res.isNull()) {
306 qCDebug(qLcCamera) << Q_FUNC_INFO << "invalid resolution:" << res;
307 return false;
308 }
309
310 if (!res.isValid()) {
311 // Invalid == default value.
312 // Here we could choose the best format available, but
313 // activeFormat is already equal to 'preset high' by default,
314 // which is good enough, otherwise we can end in some format with low framerates.
315 return false;
316 }
317
318 bool activeFormatChanged = false;
319
320 AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
321 AVCaptureDeviceFormat *match = qt_find_best_resolution_match(captureDevice, res,
322 m_service->session()->defaultCodec());
323
324 if (!match) {
325 qCDebug(qLcCamera) << Q_FUNC_INFO << "unsupported resolution:" << res;
326 return false;
327 }
328
329 activeFormatChanged = qt_set_active_format(captureDevice, match, true);
330
331#ifdef Q_OS_IOS
332 AVCaptureStillImageOutput *imageOutput = m_service->avfImageCaptureControl()->stillImageOutput();
333 if (res == qt_device_format_high_resolution(captureDevice.activeFormat))
334 imageOutput.highResolutionStillImageOutputEnabled = YES;
335 else
336 imageOutput.highResolutionStillImageOutputEnabled = NO;
337#endif
338
339 return activeFormatChanged;
340}
341
342void AVFImageCapture::setCaptureSession(QPlatformMediaCaptureSession *session)
343{
344 AVFCameraService *captureSession = static_cast<AVFCameraService *>(session);
345 if (m_service == captureSession)
346 return;
347
348 m_service = captureSession;
349 if (!m_service) {
350 m_session->disconnect(this);
351 if (m_cameraControl)
352 m_cameraControl->disconnect(this);
353 m_session = nullptr;
354 m_cameraControl = nullptr;
355 m_videoConnection = nil;
356 } else {
357 m_session = m_service->session();
358 Q_ASSERT(m_session);
359
360 connect(m_service, &AVFCameraService::cameraChanged, this, &AVFImageCapture::onCameraChanged);
361 connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(updateCaptureConnection()));
362 connect(m_session, &AVFCameraSession::newViewfinderFrame,
363 this, &AVFImageCapture::onNewViewfinderFrame);
364 }
365
366 updateCaptureConnection();
367 onCameraChanged();
368 updateReadyStatus();
369}
370
371bool AVFImageCapture::videoCaptureDeviceIsValid() const
372{
373 if (!m_service || !m_service->session() || !m_service->session()->videoCaptureDevice())
374 return false;
375
376 AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
377 if (!captureDevice.formats || !captureDevice.formats.count)
378 return false;
379
380 return true;
381}
382
383#include "moc_avfimagecapture_p.cpp"
AVFCameraSession * session() const
AVFImageCapture * avfImageCaptureControl() const
int doCapture(const QString &fileName)
void setCaptureSession(QPlatformMediaCaptureSession *session)
int captureToBuffer() override
QImageEncoderSettings imageSettings() const override
int capture(const QString &fileName) override
bool isReadyForCapture() const override
void setImageSettings(const QImageEncoderSettings &settings) override
\inmodule QtMultimedia
QObject * parent
Definition qobject.h:73