| 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 | |