Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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 "avfcameradebug_p.h"
5#include "avfimagecapture_p.h"
8#include "avfcamera_p.h"
10#include "avfcamerarenderer_p.h"
11#include "private/qmediastoragelocation_p.h"
12#include <private/qplatformimagecapture_p.h>
13#include <private/qmemoryvideobuffer_p.h>
14
15#include <QtCore/qurl.h>
16#include <QtCore/qfile.h>
17#include <QtCore/qbuffer.h>
18#include <QtConcurrent/qtconcurrentrun.h>
19#include <QtGui/qimagereader.h>
20
21#import <AVFoundation/AVFoundation.h>
22
24
26 : QPlatformImageCapture(parent)
27{
28 m_stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
29
30 NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
31 AVVideoCodecTypeJPEG, AVVideoCodecKey, nil];
32
33 [m_stillImageOutput setOutputSettings:outputSettings];
34 [outputSettings release];
35}
36
38{
39 [m_stillImageOutput release];
40}
41
43{
44 return m_cameraControl && m_videoConnection && m_cameraControl->isActive();
45}
46
47void AVFImageCapture::updateReadyStatus()
48{
49 if (m_ready != isReadyForCapture()) {
50 m_ready = !m_ready;
51 qCDebug(qLcCamera) << "ReadyToCapture status changed:" << m_ready;
53 }
54}
55
56int AVFImageCapture::doCapture(const QString &actualFileName)
57{
58 if (!m_session) {
60 Q_ARG(int, m_lastCaptureId),
63 return -1;
64 }
65 if (!isReadyForCapture()) {
67 Q_ARG(int, m_lastCaptureId),
70 return -1;
71 }
72 m_lastCaptureId++;
73
74 bool captureToBuffer = actualFileName.isEmpty();
75
77 m_requestsMutex.lock();
78 m_captureRequests.enqueue(request);
79 m_requestsMutex.unlock();
80
81 QString fileName(actualFileName);
82
83 [m_stillImageOutput captureStillImageAsynchronouslyFromConnection:m_videoConnection
84 completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
85
86 if (error) {
87 QStringList messageParts;
88 messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]);
89 messageParts << QString::fromUtf8([[error localizedFailureReason] UTF8String]);
90 messageParts << QString::fromUtf8([[error localizedRecoverySuggestion] UTF8String]);
91
92 QString errorMessage = messageParts.join(QChar(u' '));
93 qCDebug(qLcCamera) << "Image capture failed:" << errorMessage;
94
96 Q_ARG(int, request.captureId),
99 return;
100 }
101
102 // Wait for the preview to be generated before saving the JPEG (but only
103 // if we have AVFCameraRenderer attached).
104 // It is possible to stop camera immediately after trying to capture an
105 // image; this can result in a blocked callback's thread, waiting for a
106 // new viewfinder's frame to arrive/semaphore to be released. It is also
107 // unspecified on which thread this callback gets executed, (probably it's
108 // not the same thread that initiated a capture and stopped the camera),
109 // so we cannot reliably check the camera's status. Instead, we wait
110 // with a timeout and treat a failure to acquire a semaphore as an error.
111 if (!m_session->videoOutput() || request.previewReady->tryAcquire(1, 1000)) {
112 qCDebug(qLcCamera) << "Image capture completed";
113
114 NSData *nsJpgData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
115 QByteArray jpgData = QByteArray::fromRawData((const char *)[nsJpgData bytes], [nsJpgData length]);
116
117 if (captureToBuffer) {
118 QBuffer data(&jpgData);
119 QImageReader reader(&data, "JPEG");
120 QSize size = reader.size();
121 QVideoFrame frame(new QMemoryVideoBuffer(QByteArray(jpgData.constData(), jpgData.size()), -1),
123 QMetaObject::invokeMethod(this, "imageAvailable", Qt::QueuedConnection,
124 Q_ARG(int, request.captureId),
126 } else {
128 if (f.open(QFile::WriteOnly)) {
129 if (f.write(jpgData) != -1) {
131 Q_ARG(int, request.captureId),
133 } else {
135 Q_ARG(int, request.captureId),
137 Q_ARG(QString, f.errorString()));
138 }
139 } else {
140 QString errorMessage = tr("Could not open destination file:\n%1").arg(fileName);
142 Q_ARG(int, request.captureId),
145 }
146 }
147 } else {
148 const QLatin1String errorMessage("Image capture failed: timed out waiting"
149 " for a preview frame.");
150 qCDebug(qLcCamera) << errorMessage;
152 Q_ARG(int, request.captureId),
155 }
156 }];
157
158 return request.captureId;
159}
160
162{
164
165 qCDebug(qLcCamera) << "Capture image to" << actualFileName;
166 return doCapture(actualFileName);
167}
168
173
174void AVFImageCapture::onNewViewfinderFrame(const QVideoFrame &frame)
175{
176 QMutexLocker locker(&m_requestsMutex);
177
178 if (m_captureRequests.isEmpty())
179 return;
180
181 CaptureRequest request = m_captureRequests.dequeue();
182 Q_EMIT imageExposed(request.captureId);
183
184 (void) QtConcurrent::run(&AVFImageCapture::makeCapturePreview, this,
185 request,
186 frame,
187 0 /* rotation */);
188}
189
190void AVFImageCapture::onCameraChanged()
191{
192 auto camera = m_service ? static_cast<AVFCamera *>(m_service->camera()) : nullptr;
193
194 if (camera == m_cameraControl)
195 return;
196
197 m_cameraControl = camera;
198
199 if (m_cameraControl)
200 connect(m_cameraControl, SIGNAL(activeChanged(bool)), this, SLOT(updateReadyStatus()));
201 updateReadyStatus();
202}
203
204void AVFImageCapture::makeCapturePreview(CaptureRequest request,
205 const QVideoFrame &frame,
206 int rotation)
207{
209 transform.rotate(rotation);
210
211 Q_EMIT imageCaptured(request.captureId, frame.toImage().transformed(transform));
212
213 request.previewReady->release();
214}
215
216void AVFImageCapture::updateCaptureConnection()
217{
218 if (m_session && m_session->videoCaptureDevice()) {
219 qCDebug(qLcCamera) << Q_FUNC_INFO;
220 AVCaptureSession *captureSession = m_session->captureSession();
221
222 if (![captureSession.outputs containsObject:m_stillImageOutput]) {
223 if ([captureSession canAddOutput:m_stillImageOutput]) {
224 [captureSession beginConfiguration];
225 // Lock the video capture device to make sure the active format is not reset
226 const AVFConfigurationLock lock(m_session->videoCaptureDevice());
227 [captureSession addOutput:m_stillImageOutput];
228 m_videoConnection = [m_stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
229 [captureSession commitConfiguration];
230 updateReadyStatus();
231 }
232 } else {
233 m_videoConnection = [m_stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
234 }
235 }
236}
237
238
240{
242
243 if (!videoCaptureDeviceIsValid())
244 return settings;
245
246 AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
247 if (!captureDevice.activeFormat) {
248 qCDebug(qLcCamera) << Q_FUNC_INFO << "no active format";
249 return settings;
250 }
251
252 QSize res(qt_device_format_resolution(captureDevice.activeFormat));
253#ifdef Q_OS_IOS
254 if (!m_service->avfImageCaptureControl() || !m_service->avfImageCaptureControl()->stillImageOutput()) {
255 qCDebug(qLcCamera) << Q_FUNC_INFO << "no still image output";
256 return settings;
257 }
258
259 AVCaptureStillImageOutput *stillImageOutput = m_service->avfImageCaptureControl()->stillImageOutput();
260 if (stillImageOutput.highResolutionStillImageOutputEnabled)
261 res = qt_device_format_high_resolution(captureDevice.activeFormat);
262#endif
263 if (res.isNull() || !res.isValid()) {
264 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to exctract the image resolution";
265 return settings;
266 }
267
268 settings.setResolution(res);
269 settings.setFormat(QImageCapture::JPEG);
270
271 return settings;
272}
273
275{
276 if (m_settings == settings)
277 return;
278
279 m_settings = settings;
281}
282
284{
285 if (!videoCaptureDeviceIsValid())
286 return false;
287
288 AVFCameraSession *session = m_service->session();
289 if (!session)
290 return false;
291
292 if (!m_service->imageCapture()
293 || !m_service->avfImageCaptureControl()->stillImageOutput()) {
294 qCDebug(qLcCamera) << Q_FUNC_INFO << "no still image output";
295 return false;
296 }
297
298 if (m_settings.format() != QImageCapture::UnspecifiedFormat && m_settings.format() != QImageCapture::JPEG) {
299 qCDebug(qLcCamera) << Q_FUNC_INFO << "unsupported format:" << m_settings.format();
300 return false;
301 }
302
303 QSize res(m_settings.resolution());
304 if (res.isNull()) {
305 qCDebug(qLcCamera) << Q_FUNC_INFO << "invalid resolution:" << res;
306 return false;
307 }
308
309 if (!res.isValid()) {
310 // Invalid == default value.
311 // Here we could choose the best format available, but
312 // activeFormat is already equal to 'preset high' by default,
313 // which is good enough, otherwise we can end in some format with low framerates.
314 return false;
315 }
316
317 bool activeFormatChanged = false;
318
319 AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
320 AVCaptureDeviceFormat *match = qt_find_best_resolution_match(captureDevice, res,
321 m_service->session()->defaultCodec());
322
323 if (!match) {
324 qCDebug(qLcCamera) << Q_FUNC_INFO << "unsupported resolution:" << res;
325 return false;
326 }
327
328 activeFormatChanged = qt_set_active_format(captureDevice, match, true);
329
330#ifdef Q_OS_IOS
331 AVCaptureStillImageOutput *imageOutput = m_service->avfImageCaptureControl()->stillImageOutput();
332 if (res == qt_device_format_high_resolution(captureDevice.activeFormat))
333 imageOutput.highResolutionStillImageOutputEnabled = YES;
334 else
335 imageOutput.highResolutionStillImageOutputEnabled = NO;
336#endif
337
338 return activeFormatChanged;
339}
340
342{
343 AVFCameraService *captureSession = static_cast<AVFCameraService *>(session);
344 if (m_service == captureSession)
345 return;
346
347 m_service = captureSession;
348 if (!m_service) {
349 m_session->disconnect(this);
350 if (m_cameraControl)
351 m_cameraControl->disconnect(this);
352 m_session = nullptr;
353 m_cameraControl = nullptr;
354 m_videoConnection = nil;
355 } else {
356 m_session = m_service->session();
357 Q_ASSERT(m_session);
358
359 connect(m_service, &AVFCameraService::cameraChanged, this, &AVFImageCapture::onCameraChanged);
360 connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(updateCaptureConnection()));
362 this, &AVFImageCapture::onNewViewfinderFrame);
363 }
364
365 updateCaptureConnection();
366 onCameraChanged();
367 updateReadyStatus();
368}
369
370bool AVFImageCapture::videoCaptureDeviceIsValid() const
371{
372 if (!m_service || !m_service->session() || !m_service->session()->videoCaptureDevice())
373 return false;
374
375 AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
376 if (!captureDevice.formats || !captureDevice.formats.count)
377 return false;
378
379 return true;
380}
381
382#include "moc_avfimagecapture_p.cpp"
QSize qt_device_format_resolution(AVCaptureDeviceFormat *format)
QSize qt_device_format_high_resolution(AVCaptureDeviceFormat *format)
bool qt_set_active_format(AVCaptureDevice *captureDevice, AVCaptureDeviceFormat *format, bool preserveFps)
AVCaptureDeviceFormat * qt_find_best_resolution_match(AVCaptureDevice *captureDevice, const QSize &request, FourCharCode filter, bool stillImage)
AVFCameraSession * session() const
AVFImageCapture * avfImageCaptureControl() const
QPlatformImageCapture * imageCapture() override
QPlatformCamera * camera() override
AVCaptureSession * captureSession() const
AVCaptureDevice * videoCaptureDevice() const
AVFCameraRenderer * videoOutput() const
void newViewfinderFrame(const QVideoFrame &frame)
FourCharCode defaultCodec()
AVFImageCapture(QImageCapture *parent=nullptr)
int doCapture(const QString &fileName)
AVCaptureStillImageOutput * stillImageOutput() const
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
bool isActive() const override
\inmodule QtCore \reentrant
Definition qbuffer.h:16
\inmodule QtCore
Definition qbytearray.h:57
static QByteArray fromRawData(const char *data, qsizetype size)
Constructs a QByteArray that uses the first size bytes of the data array.
Definition qbytearray.h:409
\inmodule QtCore
\inmodule QtCore
Definition qfile.h:93
\inmodule QtMultimedia
QImageCapture::FileFormat format() const
The QImageReader class provides a format independent interface for reading images from files or other...
QSize size() const
Returns the size of the image, without actually reading the image contents.
The QMemoryVideoBuffer class provides a system memory allocated video data buffer.
\inmodule QtCore
Definition qmutex.h:313
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:289
void lock() noexcept
Locks the mutex.
Definition qmutex.h:286
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
void imageCaptured(int requestId, const QImage &preview)
static QString msgImageCaptureNotSet()
void imageExposed(int requestId)
void readyForCaptureChanged(bool ready)
static QSharedPointer create(Args &&...arguments)
This is an overloaded member function, provided for convenience. It differs from the above function o...
\inmodule QtCore
Definition qsize.h:25
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
QTransform & rotate(qreal a, Qt::Axis axis=Qt::ZAxis, qreal distanceToPlane=1024.0f)
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:27
QCamera * camera
Definition camera.cpp:19
Q_MULTIMEDIA_EXPORT QString generateFileName(const QString &requestedName, QStandardPaths::StandardLocation type, const QString &extension)
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
@ QueuedConnection
#define Q_FUNC_INFO
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define qCDebug(category,...)
#define SLOT(a)
Definition qobjectdefs.h:52
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
#define SIGNAL(a)
Definition qobjectdefs.h:53
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint GLenum GLsizei length
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLfloat GLfloat f
GLuint GLenum GLenum transform
GLuint res
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define tr(X)
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define Q_EMIT
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
static QString errorMessage(QUrlPrivate::ErrorCode errorCode, const QString &errorSource, qsizetype errorPosition)
Definition qurl.cpp:3517
QSettings settings("MySoft", "Star Runner")
[0]
QObject::connect nullptr
sem release()
QReadWriteLock lock
[0]
QFrame frame
[0]
QNetworkRequest request(url)
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...