1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "camerabinfocus.h"
41#include "camerabinsession.h"
42
43#include <gst/interfaces/photography.h>
44
45#include <QDebug>
46#include <QtCore/qcoreevent.h>
47#include <QtCore/qmetaobject.h>
48
49#include <private/qgstutils_p.h>
50
51#if !GST_CHECK_VERSION(1,0,0)
52typedef GstFocusMode GstPhotographyFocusMode;
53#endif
54
55//#define CAMERABIN_DEBUG 1
56
57QT_BEGIN_NAMESPACE
58
59CameraBinFocus::CameraBinFocus(CameraBinSession *session)
60 :QCameraFocusControl(session),
61#if GST_CHECK_VERSION(1,0,0)
62 QGstreamerBufferProbe(ProbeBuffers),
63#endif
64 m_session(session),
65 m_cameraStatus(QCamera::UnloadedStatus),
66 m_focusMode(QCameraFocus::AutoFocus),
67 m_focusPointMode(QCameraFocus::FocusPointAuto),
68 m_focusStatus(QCamera::Unlocked),
69 m_focusZoneStatus(QCameraFocusZone::Selected),
70 m_focusPoint(0.5, 0.5),
71 m_focusRect(0, 0, 0.3, 0.3)
72{
73 m_focusRect.moveCenter(p: m_focusPoint);
74
75 gst_photography_set_focus_mode(photo: m_session->photography(), mode: GST_PHOTOGRAPHY_FOCUS_MODE_AUTO);
76
77 connect(sender: m_session, SIGNAL(statusChanged(QCamera::Status)),
78 receiver: this, SLOT(_q_handleCameraStatusChange(QCamera::Status)));
79}
80
81CameraBinFocus::~CameraBinFocus()
82{
83}
84
85QCameraFocus::FocusModes CameraBinFocus::focusMode() const
86{
87 return m_focusMode;
88}
89
90void CameraBinFocus::setFocusMode(QCameraFocus::FocusModes mode)
91{
92 GstPhotographyFocusMode photographyMode;
93
94 switch (mode) {
95 case QCameraFocus::AutoFocus:
96 photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_AUTO;
97 break;
98 case QCameraFocus::HyperfocalFocus:
99 photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_HYPERFOCAL;
100 break;
101 case QCameraFocus::InfinityFocus:
102 photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_INFINITY;
103 break;
104 case QCameraFocus::ContinuousFocus:
105 photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_NORMAL;
106 break;
107 case QCameraFocus::MacroFocus:
108 photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_MACRO;
109 break;
110 default:
111 if (mode & QCameraFocus::AutoFocus) {
112 photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_AUTO;
113 break;
114 } else {
115 return;
116 }
117 }
118
119 if (gst_photography_set_focus_mode(photo: m_session->photography(), mode: photographyMode))
120 m_focusMode = mode;
121}
122
123bool CameraBinFocus::isFocusModeSupported(QCameraFocus::FocusModes mode) const
124{
125 switch (mode) {
126 case QCameraFocus::AutoFocus:
127 case QCameraFocus::HyperfocalFocus:
128 case QCameraFocus::InfinityFocus:
129 case QCameraFocus::ContinuousFocus:
130 case QCameraFocus::MacroFocus:
131 return true;
132 default:
133 return mode & QCameraFocus::AutoFocus;
134 }
135}
136
137QCameraFocus::FocusPointMode CameraBinFocus::focusPointMode() const
138{
139 return m_focusPointMode;
140}
141
142void CameraBinFocus::setFocusPointMode(QCameraFocus::FocusPointMode mode)
143{
144 GstElement *source = m_session->cameraSource();
145
146 if (m_focusPointMode == mode || !source)
147 return;
148
149#if GST_CHECK_VERSION(1,0,0)
150 if (m_focusPointMode == QCameraFocus::FocusPointFaceDetection) {
151 g_object_set (G_OBJECT(source), first_property_name: "detect-faces", FALSE, NULL);
152
153 if (GstPad *pad = gst_element_get_static_pad(element: source, name: "vfsrc")) {
154 removeProbeFromPad(pad);
155 gst_object_unref(GST_OBJECT(pad));
156 }
157
158 m_faceResetTimer.stop();
159 m_faceFocusRects.clear();
160
161 QMutexLocker locker(&m_mutex);
162 m_faces.clear();
163 }
164#endif
165
166 if (m_focusPointMode != QCameraFocus::FocusPointAuto)
167 resetFocusPoint();
168
169 switch (mode) {
170 case QCameraFocus::FocusPointAuto:
171 case QCameraFocus::FocusPointCustom:
172 break;
173#if GST_CHECK_VERSION(1,0,0)
174 case QCameraFocus::FocusPointFaceDetection:
175 if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), property_name: "detect-faces")) {
176 if (GstPad *pad = gst_element_get_static_pad(element: source, name: "vfsrc")) {
177 addProbeToPad(pad);
178 g_object_set (G_OBJECT(source), first_property_name: "detect-faces", TRUE, NULL);
179 break;
180 }
181 }
182 return;
183#endif
184 default:
185 return;
186 }
187
188 m_focusPointMode = mode;
189 emit focusPointModeChanged(mode: m_focusPointMode);
190 emit focusZonesChanged();
191}
192
193bool CameraBinFocus::isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const
194{
195 switch (mode) {
196 case QCameraFocus::FocusPointAuto:
197 case QCameraFocus::FocusPointCustom:
198 return true;
199#if GST_CHECK_VERSION(1,0,0)
200 case QCameraFocus::FocusPointFaceDetection:
201 if (GstElement *source = m_session->cameraSource())
202 return g_object_class_find_property(G_OBJECT_GET_CLASS(source), property_name: "detect-faces");
203 return false;
204#endif
205 default:
206 return false;
207 }
208}
209
210QPointF CameraBinFocus::customFocusPoint() const
211{
212 return m_focusPoint;
213}
214
215void CameraBinFocus::setCustomFocusPoint(const QPointF &point)
216{
217 if (m_focusPoint != point) {
218 m_focusPoint = point;
219
220 // Bound the focus point so the focus rect remains entirely within the unit square.
221 m_focusPoint.setX(qBound(min: m_focusRect.width() / 2, val: m_focusPoint.x(), max: 1 - m_focusRect.width() / 2));
222 m_focusPoint.setY(qBound(min: m_focusRect.height() / 2, val: m_focusPoint.y(), max: 1 - m_focusRect.height() / 2));
223
224 if (m_focusPointMode == QCameraFocus::FocusPointCustom) {
225 const QRectF focusRect = m_focusRect;
226 m_focusRect.moveCenter(p: m_focusPoint);
227
228 updateRegionOfInterest(rectangle: m_focusRect);
229
230 if (focusRect != m_focusRect) {
231 emit focusZonesChanged();
232 }
233 }
234
235 emit customFocusPointChanged(point: m_focusPoint);
236 }
237}
238
239QCameraFocusZoneList CameraBinFocus::focusZones() const
240{
241 QCameraFocusZoneList zones;
242
243 if (m_focusPointMode != QCameraFocus::FocusPointFaceDetection) {
244 zones.append(t: QCameraFocusZone(m_focusRect, m_focusZoneStatus));
245#if GST_CHECK_VERSION(1,0,0)
246 } else for (const QRect &face : qAsConst(t: m_faceFocusRects)) {
247 const QRectF normalizedRect(
248 face.x() / qreal(m_viewfinderResolution.width()),
249 face.y() / qreal(m_viewfinderResolution.height()),
250 face.width() / qreal(m_viewfinderResolution.width()),
251 face.height() / qreal(m_viewfinderResolution.height()));
252 zones.append(t: QCameraFocusZone(normalizedRect, m_focusZoneStatus));
253#endif
254 }
255 return zones;
256}
257
258void CameraBinFocus::handleFocusMessage(GstMessage *gm)
259{
260 //it's a sync message, so it's called from non main thread
261 const GstStructure *structure = gst_message_get_structure(message: gm);
262 if (gst_structure_has_name(structure, GST_PHOTOGRAPHY_AUTOFOCUS_DONE)) {
263 gint status = GST_PHOTOGRAPHY_FOCUS_STATUS_NONE;
264 gst_structure_get_int (structure, fieldname: "status", value: &status);
265 QCamera::LockStatus focusStatus = m_focusStatus;
266 QCamera::LockChangeReason reason = QCamera::UserRequest;
267
268 switch (status) {
269 case GST_PHOTOGRAPHY_FOCUS_STATUS_FAIL:
270 focusStatus = QCamera::Unlocked;
271 reason = QCamera::LockFailed;
272 break;
273 case GST_PHOTOGRAPHY_FOCUS_STATUS_SUCCESS:
274 focusStatus = QCamera::Locked;
275 break;
276 case GST_PHOTOGRAPHY_FOCUS_STATUS_NONE:
277 break;
278 case GST_PHOTOGRAPHY_FOCUS_STATUS_RUNNING:
279 focusStatus = QCamera::Searching;
280 break;
281 default:
282 break;
283 }
284
285 static int signalIndex = metaObject()->indexOfSlot(
286 slot: "_q_setFocusStatus(QCamera::LockStatus,QCamera::LockChangeReason)");
287 metaObject()->method(index: signalIndex).invoke(object: this,
288 connectionType: Qt::QueuedConnection,
289 Q_ARG(QCamera::LockStatus,focusStatus),
290 Q_ARG(QCamera::LockChangeReason,reason));
291 }
292}
293
294void CameraBinFocus::_q_setFocusStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason)
295{
296#ifdef CAMERABIN_DEBUG
297 qDebug() << Q_FUNC_INFO << "Current:"
298 << m_focusStatus
299 << "New:"
300 << status << reason;
301#endif
302
303 if (m_focusStatus != status) {
304 m_focusStatus = status;
305
306 QCameraFocusZone::FocusZoneStatus zonesStatus =
307 m_focusStatus == QCamera::Locked ?
308 QCameraFocusZone::Focused : QCameraFocusZone::Selected;
309
310 if (m_focusZoneStatus != zonesStatus) {
311 m_focusZoneStatus = zonesStatus;
312 emit focusZonesChanged();
313 }
314
315#if GST_CHECK_VERSION(1,0,0)
316 if (m_focusPointMode == QCameraFocus::FocusPointFaceDetection
317 && m_focusStatus == QCamera::Unlocked) {
318 _q_updateFaces();
319 }
320#endif
321
322 emit _q_focusStatusChanged(status: m_focusStatus, reason);
323 }
324}
325
326void CameraBinFocus::_q_handleCameraStatusChange(QCamera::Status status)
327{
328 m_cameraStatus = status;
329 if (status == QCamera::ActiveStatus) {
330 if (GstPad *pad = gst_element_get_static_pad(element: m_session->cameraSource(), name: "vfsrc")) {
331 if (GstCaps *caps = qt_gst_pad_get_current_caps(pad)) {
332 if (GstStructure *structure = gst_caps_get_structure(caps, index: 0)) {
333 int width = 0;
334 int height = 0;
335 gst_structure_get_int(structure, fieldname: "width", value: &width);
336 gst_structure_get_int(structure, fieldname: "height", value: &height);
337 setViewfinderResolution(QSize(width, height));
338 }
339 gst_caps_unref(caps);
340 }
341 gst_object_unref(GST_OBJECT(pad));
342 }
343 if (m_focusPointMode == QCameraFocus::FocusPointCustom) {
344 updateRegionOfInterest(rectangle: m_focusRect);
345 }
346 } else {
347 _q_setFocusStatus(status: QCamera::Unlocked, reason: QCamera::LockLost);
348
349 resetFocusPoint();
350 }
351}
352
353void CameraBinFocus::_q_startFocusing()
354{
355 _q_setFocusStatus(status: QCamera::Searching, reason: QCamera::UserRequest);
356 gst_photography_set_autofocus(photo: m_session->photography(), TRUE);
357}
358
359void CameraBinFocus::_q_stopFocusing()
360{
361 gst_photography_set_autofocus(photo: m_session->photography(), FALSE);
362 _q_setFocusStatus(status: QCamera::Unlocked, reason: QCamera::UserRequest);
363}
364
365void CameraBinFocus::setViewfinderResolution(const QSize &resolution)
366{
367 if (resolution != m_viewfinderResolution) {
368 m_viewfinderResolution = resolution;
369 if (!resolution.isEmpty()) {
370 const QPointF center = m_focusRect.center();
371 m_focusRect.setWidth(m_focusRect.height() * resolution.height() / resolution.width());
372 m_focusRect.moveCenter(p: center);
373 }
374 }
375}
376
377void CameraBinFocus::resetFocusPoint()
378{
379 const QRectF focusRect = m_focusRect;
380 m_focusPoint = QPointF(0.5, 0.5);
381 m_focusRect.moveCenter(p: m_focusPoint);
382
383 updateRegionOfInterest(rectangles: QVector<QRect>());
384
385 if (focusRect != m_focusRect) {
386 emit customFocusPointChanged(point: m_focusPoint);
387 emit focusZonesChanged();
388 }
389}
390
391static void appendRegion(GValue *regions, int priority, const QRect &rectangle)
392{
393 GstStructure *region = gst_structure_new(
394 name: "region",
395 firstfield: "region-x" , G_TYPE_UINT , rectangle.x(),
396 "region-y" , G_TYPE_UINT, rectangle.y(),
397 "region-w" , G_TYPE_UINT , rectangle.width(),
398 "region-h" , G_TYPE_UINT, rectangle.height(),
399 "region-priority" , G_TYPE_UINT, priority,
400 NULL);
401
402 GValue regionValue = G_VALUE_INIT;
403 g_value_init(value: &regionValue, GST_TYPE_STRUCTURE);
404 gst_value_set_structure(value: &regionValue, structure: region);
405 gst_structure_free(structure: region);
406
407 gst_value_list_append_value(value: regions, append_value: &regionValue);
408 g_value_unset(value: &regionValue);
409}
410
411void CameraBinFocus::updateRegionOfInterest(const QRectF &rectangle)
412{
413 updateRegionOfInterest(rectangles: QVector<QRect>() << QRect(
414 rectangle.x() * m_viewfinderResolution.width(),
415 rectangle.y() * m_viewfinderResolution.height(),
416 rectangle.width() * m_viewfinderResolution.width(),
417 rectangle.height() * m_viewfinderResolution.height()));
418}
419
420void CameraBinFocus::updateRegionOfInterest(const QVector<QRect> &rectangles)
421{
422 if (m_cameraStatus != QCamera::ActiveStatus)
423 return;
424
425 GstElement * const cameraSource = m_session->cameraSource();
426 if (!cameraSource)
427 return;
428
429 GValue regions = G_VALUE_INIT;
430 g_value_init(value: &regions, GST_TYPE_LIST);
431
432 if (rectangles.isEmpty()) {
433 appendRegion(regions: &regions, priority: 0, rectangle: QRect(0, 0, 0, 0));
434 } else {
435 // Add padding around small face rectangles so the auto focus has a reasonable amount
436 // of image to work with.
437 const int minimumDimension = qMin(
438 a: m_viewfinderResolution.width(), b: m_viewfinderResolution.height()) * 0.3;
439 const QRect viewfinderRectangle(QPoint(0, 0), m_viewfinderResolution);
440
441 for (const QRect &rectangle : rectangles) {
442 QRect paddedRectangle(
443 0,
444 0,
445 qMax(a: rectangle.width(), b: minimumDimension),
446 qMax(a: rectangle.height(), b: minimumDimension));
447 paddedRectangle.moveCenter(p: rectangle.center());
448
449 appendRegion(regions: &regions, priority: 1, rectangle: viewfinderRectangle.intersected(other: paddedRectangle));
450 }
451 }
452
453 GstStructure *regionsOfInterest = gst_structure_new(
454 name: "regions-of-interest",
455 firstfield: "frame-width" , G_TYPE_UINT , m_viewfinderResolution.width(),
456 "frame-height" , G_TYPE_UINT, m_viewfinderResolution.height(),
457 NULL);
458 gst_structure_set_value(structure: regionsOfInterest, fieldname: "regions", value: &regions);
459 g_value_unset(value: &regions);
460
461 GstEvent *event = gst_event_new_custom(type: GST_EVENT_CUSTOM_UPSTREAM, structure: regionsOfInterest);
462 gst_element_send_event(element: cameraSource, event);
463}
464
465#if GST_CHECK_VERSION(1,0,0)
466
467void CameraBinFocus::_q_updateFaces()
468{
469 if (m_focusPointMode != QCameraFocus::FocusPointFaceDetection
470 || m_focusStatus != QCamera::Unlocked) {
471 return;
472 }
473
474 QVector<QRect> faces;
475
476 {
477 QMutexLocker locker(&m_mutex);
478 faces = m_faces;
479 }
480
481 if (!faces.isEmpty()) {
482 m_faceResetTimer.stop();
483 m_faceFocusRects = faces;
484 updateRegionOfInterest(rectangles: m_faceFocusRects);
485 emit focusZonesChanged();
486 } else {
487 m_faceResetTimer.start(msec: 500, obj: this);
488 }
489}
490
491void CameraBinFocus::timerEvent(QTimerEvent *event)
492{
493 if (event->timerId() == m_faceResetTimer.timerId()) {
494 m_faceResetTimer.stop();
495
496 if (m_focusStatus == QCamera::Unlocked) {
497 m_faceFocusRects.clear();
498 updateRegionOfInterest(rectangles: m_faceFocusRects);
499 emit focusZonesChanged();
500 }
501 } else {
502 QCameraFocusControl::timerEvent(event);
503 }
504}
505
506bool CameraBinFocus::probeBuffer(GstBuffer *buffer)
507{
508 QVector<QRect> faces;
509
510#if GST_CHECK_VERSION(1,1,3)
511 gpointer state = NULL;
512 const GstMetaInfo *info = GST_VIDEO_REGION_OF_INTEREST_META_INFO;
513
514 while (GstMeta *meta = gst_buffer_iterate_meta(buffer, state: &state)) {
515 if (meta->info->api != info->api)
516 continue;
517
518 GstVideoRegionOfInterestMeta *region = reinterpret_cast<GstVideoRegionOfInterestMeta *>(meta);
519
520 faces.append(t: QRect(region->x, region->y, region->w, region->h));
521 }
522#else
523 Q_UNUSED(buffer);
524#endif
525
526 QMutexLocker locker(&m_mutex);
527
528 if (m_faces != faces) {
529 m_faces = faces;
530
531 static const int signalIndex = metaObject()->indexOfSlot(slot: "_q_updateFaces()");
532 metaObject()->method(index: signalIndex).invoke(object: this, connectionType: Qt::QueuedConnection);
533 }
534
535 return true;
536}
537
538#endif
539
540QT_END_NAMESPACE
541

source code of qtmultimedia/src/plugins/gstreamer/camerabin/camerabinfocus.cpp