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 "camerabinimagecapture.h"
41#include "camerabincontrol.h"
42#include "camerabincapturedestination.h"
43#include "camerabincapturebufferformat.h"
44#include "camerabinsession.h"
45#include "camerabinresourcepolicy.h"
46#include <private/qgstvideobuffer_p.h>
47#include <private/qvideosurfacegstsink_p.h>
48#include <private/qgstutils_p.h>
49#include <QtMultimedia/qmediametadata.h>
50#include <QtCore/qdebug.h>
51#include <QtCore/qbuffer.h>
52#include <QtGui/qimagereader.h>
53
54//#define DEBUG_CAPTURE
55
56#define IMAGE_DONE_SIGNAL "image-done"
57
58QT_BEGIN_NAMESPACE
59
60CameraBinImageCapture::CameraBinImageCapture(CameraBinSession *session)
61 :QCameraImageCaptureControl(session)
62 , m_encoderProbe(this)
63 , m_muxerProbe(this)
64 , m_session(session)
65 , m_jpegEncoderElement(0)
66 , m_metadataMuxerElement(0)
67 , m_requestId(0)
68 , m_ready(false)
69{
70 connect(asender: m_session, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateState()));
71 connect(sender: m_session, SIGNAL(imageExposed(int)), receiver: this, SIGNAL(imageExposed(int)));
72 connect(sender: m_session, SIGNAL(imageCaptured(int,QImage)), receiver: this, SIGNAL(imageCaptured(int,QImage)));
73 connect(sender: m_session->cameraControl()->resourcePolicy(), SIGNAL(canCaptureChanged()), receiver: this, SLOT(updateState()));
74
75 m_session->bus()->installMessageFilter(filter: this);
76}
77
78CameraBinImageCapture::~CameraBinImageCapture()
79{
80}
81
82bool CameraBinImageCapture::isReadyForCapture() const
83{
84 return m_ready;
85}
86
87int CameraBinImageCapture::capture(const QString &fileName)
88{
89 m_requestId++;
90
91 if (!m_ready) {
92 emit error(id: m_requestId, error: QCameraImageCapture::NotReadyError, errorString: tr(s: "Camera not ready"));
93 return m_requestId;
94 }
95
96#ifdef DEBUG_CAPTURE
97 qDebug() << Q_FUNC_INFO << m_requestId << fileName;
98#endif
99 m_session->captureImage(requestId: m_requestId, fileName);
100 return m_requestId;
101}
102
103void CameraBinImageCapture::cancelCapture()
104{
105}
106
107void CameraBinImageCapture::updateState()
108{
109 bool ready = m_session->status() == QCamera::ActiveStatus
110 && m_session->cameraControl()->resourcePolicy()->canCapture();
111 if (m_ready != ready) {
112#ifdef DEBUG_CAPTURE
113 qDebug() << "readyForCaptureChanged" << ready;
114#endif
115 emit readyForCaptureChanged(ready: m_ready = ready);
116 }
117}
118
119#if GST_CHECK_VERSION(1,0,0)
120GstPadProbeReturn CameraBinImageCapture::encoderEventProbe(
121 GstPad *, GstPadProbeInfo *info, gpointer user_data)
122{
123 GstEvent * const event = gst_pad_probe_info_get_event(info);
124#else
125gboolean CameraBinImageCapture::encoderEventProbe(
126 GstElement *, GstEvent *event, gpointer user_data)
127{
128#endif
129 CameraBinImageCapture * const self = static_cast<CameraBinImageCapture *>(user_data);
130 if (event && GST_EVENT_TYPE(event) == GST_EVENT_TAG) {
131 GstTagList *gstTags;
132 gst_event_parse_tag(event, taglist: &gstTags);
133 QMap<QByteArray, QVariant> extendedTags = QGstUtils::gstTagListToMap(list: gstTags);
134
135#ifdef DEBUG_CAPTURE
136 qDebug() << QString(gst_structure_to_string(gst_event_get_structure(event))).right(768);
137 qDebug() << "Capture event probe" << extendedTags;
138#endif
139
140 QVariantMap tags;
141 tags[QMediaMetaData::ISOSpeedRatings] = extendedTags.value(akey: "capturing-iso-speed");
142 tags[QMediaMetaData::DigitalZoomRatio] = extendedTags.value(akey: "capturing-digital-zoom-ratio");
143 tags[QMediaMetaData::ExposureTime] = extendedTags.value(akey: "capturing-shutter-speed");
144 tags[QMediaMetaData::WhiteBalance] = extendedTags.value(akey: "capturing-white-balance");
145 tags[QMediaMetaData::Flash] = extendedTags.value(akey: "capturing-flash-fired");
146 tags[QMediaMetaData::FocalLengthIn35mmFilm] = extendedTags.value(akey: "capturing-focal-length");
147 tags[QMediaMetaData::MeteringMode] = extendedTags.value(akey: "capturing-metering-mode");
148 tags[QMediaMetaData::ExposureMode] = extendedTags.value(akey: "capturing-exposure-mode");
149 tags[QMediaMetaData::FNumber] = extendedTags.value(akey: "capturing-focal-ratio");
150 tags[QMediaMetaData::ExposureMode] = extendedTags.value(akey: "capturing-exposure-mode");
151
152 for (auto i = tags.cbegin(), end = tags.cend(); i != end; ++i) {
153 if (i.value().isValid()) {
154 QMetaObject::invokeMethod(obj: self, member: "imageMetadataAvailable",
155 type: Qt::QueuedConnection,
156 Q_ARG(int, self->m_requestId),
157 Q_ARG(QString, i.key()),
158 Q_ARG(QVariant, i.value()));
159 }
160 }
161 }
162#if GST_CHECK_VERSION(1,0,0)
163 return GST_PAD_PROBE_OK;
164#else
165 return TRUE;
166#endif
167}
168
169void CameraBinImageCapture::EncoderProbe::probeCaps(GstCaps *caps)
170{
171#if GST_CHECK_VERSION(1,0,0)
172 capture->m_bufferFormat = QGstUtils::formatForCaps(caps, info: &capture->m_videoInfo);
173#else
174 int bytesPerLine = 0;
175 QVideoSurfaceFormat format = QGstUtils::formatForCaps(caps, &bytesPerLine);
176 capture->m_bytesPerLine = bytesPerLine;
177 capture->m_bufferFormat = format;
178#endif
179}
180
181bool CameraBinImageCapture::EncoderProbe::probeBuffer(GstBuffer *buffer)
182{
183 CameraBinSession * const session = capture->m_session;
184
185#ifdef DEBUG_CAPTURE
186 qDebug() << "Uncompressed buffer probe";
187#endif
188
189 QCameraImageCapture::CaptureDestinations destination =
190 session->captureDestinationControl()->captureDestination();
191 QVideoFrame::PixelFormat format = session->captureBufferFormatControl()->bufferFormat();
192
193 if (destination & QCameraImageCapture::CaptureToBuffer) {
194 if (format != QVideoFrame::Format_Jpeg) {
195#ifdef DEBUG_CAPTURE
196 qDebug() << "imageAvailable(uncompressed):" << format;
197#endif
198#if GST_CHECK_VERSION(1,0,0)
199 QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, capture->m_videoInfo);
200#else
201 QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, capture->m_bytesPerLine);
202#endif
203
204 QVideoFrame frame(
205 videoBuffer,
206 capture->m_bufferFormat.frameSize(),
207 capture->m_bufferFormat.pixelFormat());
208
209 QMetaObject::invokeMethod(obj: capture, member: "imageAvailable",
210 type: Qt::QueuedConnection,
211 Q_ARG(int, capture->m_requestId),
212 Q_ARG(QVideoFrame, frame));
213 }
214 }
215
216 //keep the buffer if capture to file or jpeg buffer capture was reuqsted
217 bool keepBuffer = (destination & QCameraImageCapture::CaptureToFile) ||
218 ((destination & QCameraImageCapture::CaptureToBuffer) &&
219 format == QVideoFrame::Format_Jpeg);
220
221 return keepBuffer;
222}
223
224void CameraBinImageCapture::MuxerProbe::probeCaps(GstCaps *caps)
225{
226 capture->m_jpegResolution = QGstUtils::capsCorrectedResolution(caps);
227}
228
229bool CameraBinImageCapture::MuxerProbe::probeBuffer(GstBuffer *buffer)
230{
231 CameraBinSession * const session = capture->m_session;
232
233 QCameraImageCapture::CaptureDestinations destination =
234 session->captureDestinationControl()->captureDestination();
235
236 if ((destination & QCameraImageCapture::CaptureToBuffer) &&
237 session->captureBufferFormatControl()->bufferFormat() == QVideoFrame::Format_Jpeg) {
238
239 QSize resolution = capture->m_jpegResolution;
240 //if resolution is not presented in caps, try to find it from encoded jpeg data:
241#if GST_CHECK_VERSION(1,0,0)
242 GstMapInfo mapInfo;
243 if (resolution.isEmpty() && gst_buffer_map(buffer, info: &mapInfo, flags: GST_MAP_READ)) {
244 QBuffer data;
245 data.setData(adata: reinterpret_cast<const char*>(mapInfo.data), alen: mapInfo.size);
246
247 QImageReader reader(&data, "JPEG");
248 resolution = reader.size();
249
250 gst_buffer_unmap(buffer, info: &mapInfo);
251 }
252
253 GstVideoInfo info;
254 gst_video_info_set_format(
255 info: &info, format: GST_VIDEO_FORMAT_ENCODED, width: resolution.width(), height: resolution.height());
256 QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, info);
257#else
258 if (resolution.isEmpty()) {
259 QBuffer data;
260 data.setData(reinterpret_cast<const char*>(GST_BUFFER_DATA(buffer)), GST_BUFFER_SIZE(buffer));
261 QImageReader reader(&data, "JPEG");
262 resolution = reader.size();
263 }
264
265 QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer,
266 -1); //bytesPerLine is not available for jpegs
267#endif
268
269
270 QVideoFrame frame(videoBuffer,
271 resolution,
272 QVideoFrame::Format_Jpeg);
273 QMetaObject::invokeMethod(obj: capture, member: "imageAvailable",
274 type: Qt::QueuedConnection,
275 Q_ARG(int, capture->m_requestId),
276 Q_ARG(QVideoFrame, frame));
277 }
278
279
280 // Theoretically we could drop the buffer here when don't want to capture to file but that
281 // prevents camerabin from recognizing that capture has been completed and returning
282 // to its idle state.
283 return true;
284}
285
286
287bool CameraBinImageCapture::processBusMessage(const QGstreamerMessage &message)
288{
289 //Install metadata event and buffer probes
290
291 //The image capture pipiline is built dynamically,
292 //it's necessary to wait until jpeg encoder is added to pipeline
293
294 GstMessage *gm = message.rawMessage();
295 if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_STATE_CHANGED) {
296 GstState oldState;
297 GstState newState;
298 GstState pending;
299 gst_message_parse_state_changed(message: gm, oldstate: &oldState, newstate: &newState, pending: &pending);
300
301 if (newState == GST_STATE_READY) {
302 GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(gm));
303 if (!element)
304 return false;
305
306 gchar *name = gst_element_get_name(element);
307 QString elementName = QString::fromLatin1(str: name);
308 g_free(mem: name);
309#if !GST_CHECK_VERSION(1,0,0)
310 GstElementClass *elementClass = GST_ELEMENT_GET_CLASS(element);
311 QString elementLongName = elementClass->details.longname;
312#endif
313 if (elementName.contains(s: "jpegenc") && element != m_jpegEncoderElement) {
314 m_jpegEncoderElement = element;
315 GstPad *sinkpad = gst_element_get_static_pad(element, name: "sink");
316
317 //metadata event probe is installed before jpeg encoder
318 //to emit metadata available signal as soon as possible.
319#ifdef DEBUG_CAPTURE
320 qDebug() << "install metadata probe";
321#endif
322#if GST_CHECK_VERSION(1,0,0)
323 gst_pad_add_probe(
324 pad: sinkpad, mask: GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, callback: encoderEventProbe, user_data: this, NULL);
325#else
326 gst_pad_add_event_probe(sinkpad, G_CALLBACK(encoderEventProbe), this);
327#endif
328#ifdef DEBUG_CAPTURE
329 qDebug() << "install uncompressed buffer probe";
330#endif
331 m_encoderProbe.addProbeToPad(pad: sinkpad, downstream: true);
332
333 gst_object_unref(object: sinkpad);
334 } else if ((elementName.contains(s: "jifmux")
335#if !GST_CHECK_VERSION(1,0,0)
336 || elementLongName == QLatin1String("JPEG stream muxer")
337#endif
338 || elementName.startsWith(s: "metadatamux"))
339 && element != m_metadataMuxerElement) {
340 //Jpeg encoded buffer probe is added after jifmux/metadatamux
341 //element to ensure the resulting jpeg buffer contains capture metadata
342 m_metadataMuxerElement = element;
343
344 GstPad *srcpad = gst_element_get_static_pad(element, name: "src");
345#ifdef DEBUG_CAPTURE
346 qDebug() << "install jpeg buffer probe";
347#endif
348 m_muxerProbe.addProbeToPad(pad: srcpad);
349
350 gst_object_unref(object: srcpad);
351 }
352 }
353 } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) {
354 if (GST_MESSAGE_SRC(gm) == (GstObject *)m_session->cameraBin()) {
355 const GstStructure *structure = gst_message_get_structure(message: gm);
356
357 if (gst_structure_has_name (structure, name: "image-done")) {
358 const gchar *fileName = gst_structure_get_string (structure, fieldname: "filename");
359#ifdef DEBUG_CAPTURE
360 qDebug() << "Image saved" << fileName;
361#endif
362
363 if (m_session->captureDestinationControl()->captureDestination() & QCameraImageCapture::CaptureToFile) {
364 emit imageSaved(requestId: m_requestId, fileName: QString::fromUtf8(str: fileName));
365 } else {
366#ifdef DEBUG_CAPTURE
367 qDebug() << Q_FUNC_INFO << "Dropped saving file" << fileName;
368#endif
369 QFileInfo info(QString::fromUtf8(str: fileName));
370 if (info.exists() && info.isFile())
371 QFile(info.absoluteFilePath()).remove();
372 }
373 }
374 }
375 }
376
377 return false;
378}
379
380QT_END_NAMESPACE
381

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