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 | |
58 | QT_BEGIN_NAMESPACE |
59 | |
60 | CameraBinImageCapture::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 | |
78 | CameraBinImageCapture::~CameraBinImageCapture() |
79 | { |
80 | } |
81 | |
82 | bool CameraBinImageCapture::isReadyForCapture() const |
83 | { |
84 | return m_ready; |
85 | } |
86 | |
87 | int 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 | |
103 | void CameraBinImageCapture::cancelCapture() |
104 | { |
105 | } |
106 | |
107 | void 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) |
120 | GstPadProbeReturn CameraBinImageCapture::encoderEventProbe( |
121 | GstPad *, GstPadProbeInfo *info, gpointer user_data) |
122 | { |
123 | GstEvent * const event = gst_pad_probe_info_get_event(info); |
124 | #else |
125 | gboolean 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 | |
169 | void 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 | |
181 | bool 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 | |
224 | void CameraBinImageCapture::MuxerProbe::probeCaps(GstCaps *caps) |
225 | { |
226 | capture->m_jpegResolution = QGstUtils::capsCorrectedResolution(caps); |
227 | } |
228 | |
229 | bool 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 | |
287 | bool 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 | |
380 | QT_END_NAMESPACE |
381 | |