From 5deb9dda60097e8ff092456cfb810dbb57bfceb5 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Wed, 28 Nov 2018 22:14:16 -0500 Subject: [PATCH] [camera] Fix crashes and lockups caused by changing the capture mode during an image capture. Contributes to JB#42936 A camera source may reject changing the mode while it is busy leaving the the camerabin plugin in a state inconsistent with the gstreamer pipeline which can lead to attempts to record video in the image capture mode and ultimately a lockup. Changing any of the source resolutions requires putting the pipeline into the the READY state to apply the changes. If this happened during a capture the state change was deferred and the internal plugin state was restored to active before the capture finished and the state changes were treated as a no-op and the resolution change wasn't applied leading to an attempt to record video with an unsupported resolution set for the image capture mode. So if a change to a lower state is requested and the plugin is unable to process it immediately still defer the change but ensure the pipeline is still dropped to the lowest requested state before being restored to the current requested state so that any changes made on state transitions are applied. --- .../gstreamer/camerabin/camerabincontrol.cpp | 160 +---------- .../gstreamer/camerabin/camerabincontrol.h | 16 -- .../camerabin/camerabinimagecapture.cpp | 4 +- .../gstreamer/camerabin/camerabinrecorder.cpp | 8 +- .../camerabin/camerabinresourcepolicy.h | 2 +- .../gstreamer/camerabin/camerabinsession.cpp | 267 ++++++++++++------ .../gstreamer/camerabin/camerabinsession.h | 20 +- 7 files changed, 200 insertions(+), 277 deletions(-) diff --git a/src/plugins/gstreamer/camerabin/camerabincontrol.cpp b/src/plugins/gstreamer/camerabin/camerabincontrol.cpp index a1cd377f..8eb90134 100644 --- a/src/plugins/gstreamer/camerabin/camerabincontrol.cpp +++ b/src/plugins/gstreamer/camerabin/camerabincontrol.cpp @@ -49,30 +49,10 @@ QT_BEGIN_NAMESPACE CameraBinControl::CameraBinControl(CameraBinSession *session) :QCameraControl(session), - m_session(session), - m_state(QCamera::UnloadedState), - m_reloadPending(false) + m_session(session) { connect(m_session, SIGNAL(statusChanged(QCamera::Status)), this, SIGNAL(statusChanged(QCamera::Status))); - - connect(m_session, SIGNAL(viewfinderChanged()), - SLOT(reloadLater())); - connect(m_session, SIGNAL(readyChanged(bool)), - SLOT(reloadLater())); - connect(m_session, SIGNAL(error(int,QString)), - SLOT(handleCameraError(int,QString))); - - m_resourcePolicy = new CamerabinResourcePolicy(this); - connect(m_resourcePolicy, SIGNAL(resourcesGranted()), - SLOT(handleResourcesGranted())); - connect(m_resourcePolicy, SIGNAL(resourcesDenied()), - SLOT(handleResourcesLost())); - connect(m_resourcePolicy, SIGNAL(resourcesLost()), - SLOT(handleResourcesLost())); - - connect(m_session, SIGNAL(busyChanged(bool)), - SLOT(handleBusyChanged(bool))); } CameraBinControl::~CameraBinControl() @@ -86,17 +66,7 @@ QCamera::CaptureModes CameraBinControl::captureMode() const void CameraBinControl::setCaptureMode(QCamera::CaptureModes mode) { - if (m_session->captureMode() != mode) { - m_session->setCaptureMode(mode); - - if (m_state == QCamera::ActiveState) { - m_resourcePolicy->setResourceSet( - captureMode() == QCamera::CaptureStillImage ? - CamerabinResourcePolicy::ImageCaptureResources : - CamerabinResourcePolicy::VideoCaptureResources); - } - emit captureModeChanged(mode); - } + m_session->setCaptureMode(mode); } bool CameraBinControl::isCaptureModeSupported(QCamera::CaptureModes mode) const @@ -109,59 +79,12 @@ void CameraBinControl::setState(QCamera::State state) #ifdef CAMEABIN_DEBUG qDebug() << Q_FUNC_INFO << ENUM_NAME(QCamera, "State", state); #endif - if (m_state != state) { - m_state = state; - - //special case for stopping the camera while it's busy, - //it should be delayed until the camera is idle - if ((state == QCamera::LoadedState || state == QCamera::UnloadedState) && - m_session->status() == QCamera::ActiveStatus && - m_session->isBusy()) { -#ifdef CAMEABIN_DEBUG - qDebug() << Q_FUNC_INFO << "Camera is busy, QCamera::stop() is delayed"; -#endif - emit stateChanged(m_state); - return; - } - - CamerabinResourcePolicy::ResourceSet resourceSet = CamerabinResourcePolicy::NoResources; - switch (state) { - case QCamera::UnloadedState: - resourceSet = CamerabinResourcePolicy::NoResources; - break; - case QCamera::LoadedState: - resourceSet = CamerabinResourcePolicy::LoadedResources; - break; - case QCamera::ActiveState: - resourceSet = captureMode() == QCamera::CaptureStillImage ? - CamerabinResourcePolicy::ImageCaptureResources : - CamerabinResourcePolicy::VideoCaptureResources; - break; - } - - m_resourcePolicy->setResourceSet(resourceSet); - - if (m_resourcePolicy->isResourcesGranted()) { - //postpone changing to Active if the session is nor ready yet - if (state == QCamera::ActiveState) { - if (m_session->isReady()) { - m_session->setState(state); - } else { -#ifdef CAMEABIN_DEBUG - qDebug() << "Camera session is not ready yet, postpone activating"; -#endif - } - } else - m_session->setState(state); - } - - emit stateChanged(m_state); - } + m_session->setState(state); } QCamera::State CameraBinControl::state() const { - return m_state; + return m_session->requestedState(); } QCamera::Status CameraBinControl::status() const @@ -169,81 +92,6 @@ QCamera::Status CameraBinControl::status() const return m_session->status(); } -void CameraBinControl::reloadLater() -{ -#ifdef CAMEABIN_DEBUG - qDebug() << "CameraBinControl: reload pipeline requested" << ENUM_NAME(QCamera, "State", m_state); -#endif - if (!m_reloadPending && m_state == QCamera::ActiveState) { - m_reloadPending = true; - - if (!m_session->isBusy()) { - m_session->setState(QCamera::LoadedState); - QMetaObject::invokeMethod(this, "delayedReload", Qt::QueuedConnection); - } - } -} - -void CameraBinControl::handleResourcesLost() -{ -#ifdef CAMEABIN_DEBUG - qDebug() << Q_FUNC_INFO << ENUM_NAME(QCamera, "State", m_state); -#endif - m_session->setState(QCamera::UnloadedState); -} - -void CameraBinControl::handleResourcesGranted() -{ -#ifdef CAMEABIN_DEBUG - qDebug() << Q_FUNC_INFO << ENUM_NAME(QCamera, "State", m_state); -#endif - - //camera will be started soon by delayedReload() - if (m_reloadPending && m_state == QCamera::ActiveState) - return; - - if (m_state == QCamera::ActiveState && m_session->isReady()) - m_session->setState(QCamera::ActiveState); - else if (m_state == QCamera::LoadedState) - m_session->setState(QCamera::LoadedState); -} - -void CameraBinControl::handleBusyChanged(bool busy) -{ - if (!busy && m_session->status() == QCamera::ActiveStatus) { - if (m_state == QCamera::LoadedState) { - //handle delayed stop() because of busy camera - m_resourcePolicy->setResourceSet(CamerabinResourcePolicy::LoadedResources); - m_session->setState(QCamera::LoadedState); - } else if (m_state == QCamera::ActiveState && m_reloadPending) { - //handle delayed reload because of busy camera - m_session->setState(QCamera::LoadedState); - QMetaObject::invokeMethod(this, "delayedReload", Qt::QueuedConnection); - } - } -} - -void CameraBinControl::handleCameraError(int errorCode, const QString &errorString) -{ - emit error(errorCode, errorString); - setState(QCamera::UnloadedState); -} - -void CameraBinControl::delayedReload() -{ -#ifdef CAMEABIN_DEBUG - qDebug() << "CameraBinControl: reload pipeline"; -#endif - if (m_reloadPending) { - m_reloadPending = false; - if (m_state == QCamera::ActiveState && - m_session->isReady() && - m_resourcePolicy->isResourcesGranted()) { - m_session->setState(QCamera::ActiveState); - } - } -} - bool CameraBinControl::canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const { Q_UNUSED(status); diff --git a/src/plugins/gstreamer/camerabin/camerabincontrol.h b/src/plugins/gstreamer/camerabin/camerabincontrol.h index 6a6b47d5..7d3ce24e 100644 --- a/src/plugins/gstreamer/camerabin/camerabincontrol.h +++ b/src/plugins/gstreamer/camerabin/camerabincontrol.h @@ -65,29 +65,13 @@ public: bool canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const; bool viewfinderColorSpaceConversion() const; - CamerabinResourcePolicy *resourcePolicy() { return m_resourcePolicy; } - public slots: - void reloadLater(); void setViewfinderColorSpaceConversion(bool enabled); -private slots: - void delayedReload(); - - void handleResourcesGranted(); - void handleResourcesLost(); - - void handleBusyChanged(bool); - void handleCameraError(int error, const QString &errorString); - private: void updateSupportedResolutions(const QString &device); CameraBinSession *m_session; - QCamera::State m_state; - CamerabinResourcePolicy *m_resourcePolicy; - - bool m_reloadPending; }; QT_END_NAMESPACE diff --git a/src/plugins/gstreamer/camerabin/camerabinimagecapture.cpp b/src/plugins/gstreamer/camerabin/camerabinimagecapture.cpp index ccc5d299..348cda98 100644 --- a/src/plugins/gstreamer/camerabin/camerabinimagecapture.cpp +++ b/src/plugins/gstreamer/camerabin/camerabinimagecapture.cpp @@ -64,7 +64,7 @@ CameraBinImageCapture::CameraBinImageCapture(CameraBinSession *session) connect(m_session, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateState())); connect(m_session, SIGNAL(imageExposed(int)), this, SIGNAL(imageExposed(int))); connect(m_session, SIGNAL(imageCaptured(int,QImage)), this, SIGNAL(imageCaptured(int,QImage))); - connect(m_session->cameraControl()->resourcePolicy(), SIGNAL(canCaptureChanged()), this, SLOT(updateState())); + connect(m_session->resourcePolicy(), SIGNAL(canCaptureChanged()), this, SLOT(updateState())); connect(m_session, SIGNAL(handleReadyForCaptureChanged(bool)), this, SLOT(updateState())); m_session->bus()->installMessageFilter(this); @@ -102,7 +102,7 @@ void CameraBinImageCapture::cancelCapture() void CameraBinImageCapture::updateState() { bool ready = m_session->status() == QCamera::ActiveStatus - && m_session->cameraControl()->resourcePolicy()->canCapture() + && m_session->resourcePolicy()->canCapture() && m_session->isReadyForCapture(); if (m_ready != ready) { #ifdef DEBUG_CAPTURE diff --git a/src/plugins/gstreamer/camerabin/camerabinrecorder.cpp b/src/plugins/gstreamer/camerabin/camerabinrecorder.cpp index ad0596a6..944953b0 100644 --- a/src/plugins/gstreamer/camerabin/camerabinrecorder.cpp +++ b/src/plugins/gstreamer/camerabin/camerabinrecorder.cpp @@ -54,7 +54,7 @@ CameraBinRecorder::CameraBinRecorder(CameraBinSession *session) connect(m_session, SIGNAL(durationChanged(qint64)), SIGNAL(durationChanged(qint64))); connect(m_session, SIGNAL(mutedChanged(bool)), this, SIGNAL(mutedChanged(bool))); - connect(m_session->cameraControl()->resourcePolicy(), SIGNAL(canCaptureChanged()), + connect(m_session->resourcePolicy(), SIGNAL(canCaptureChanged()), this, SLOT(updateStatus())); } @@ -93,7 +93,7 @@ void CameraBinRecorder::updateStatus() if (sessionStatus == QCamera::ActiveStatus && m_session->captureMode().testFlag(QCamera::CaptureVideo)) { - if (!m_session->cameraControl()->resourcePolicy()->canCapture()) { + if (!m_session->resourcePolicy()->canCapture()) { m_status = QMediaRecorder::UnavailableStatus; m_state = QMediaRecorder::StoppedState; m_session->stopVideoRecording(); @@ -109,7 +109,7 @@ void CameraBinRecorder::updateStatus() m_state = QMediaRecorder::StoppedState; m_session->stopVideoRecording(); } - m_status = m_session->pendingState() == QCamera::ActiveState + m_status = m_session->requestedState() == QCamera::ActiveState && m_session->captureMode().testFlag(QCamera::CaptureVideo) ? QMediaRecorder::LoadingStatus : QMediaRecorder::UnloadedStatus; @@ -221,7 +221,7 @@ void CameraBinRecorder::setState(QMediaRecorder::State state) if (m_session->status() != QCamera::ActiveStatus) { emit error(QMediaRecorder::ResourceError, tr("Service has not been started")); - } else if (!m_session->cameraControl()->resourcePolicy()->canCapture()) { + } else if (!m_session->resourcePolicy()->canCapture()) { emit error(QMediaRecorder::ResourceError, tr("Recording permissions are not available")); } else { m_session->recordVideo(); diff --git a/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.h b/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.h index c8e0a2a2..c9703661 100644 --- a/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.h +++ b/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.h @@ -53,7 +53,7 @@ public: VideoCaptureResources }; - CamerabinResourcePolicy(QObject *parent); + CamerabinResourcePolicy(QObject *parent = nullptr); ~CamerabinResourcePolicy(); ResourceSet resourceSet() const; diff --git a/src/plugins/gstreamer/camerabin/camerabinsession.cpp b/src/plugins/gstreamer/camerabin/camerabinsession.cpp index a8108c66..52453102 100644 --- a/src/plugins/gstreamer/camerabin/camerabinsession.cpp +++ b/src/plugins/gstreamer/camerabin/camerabinsession.cpp @@ -113,11 +113,15 @@ CameraBinSession::CameraBinSession(GstElementFactory *sourceFactory, QObject *pa :QObject(parent), m_recordingActive(false), m_status(QCamera::UnloadedStatus), - m_pendingState(QCamera::UnloadedState), + m_requestedState(QCamera::UnloadedState), + m_transientState(QCamera::UnloadedState), + m_acceptedState(QCamera::UnloadedState), m_muted(false), - m_busy(false), m_readyForCapture(false), m_captureMode(QCamera::CaptureStillImage), + m_appliedCaptureMode(QCamera::CaptureStillImage), + m_busy(false), + m_reportedReadyForCapture(false), m_audioInputFactory(0), m_videoInputFactory(0), m_viewfinder(0), @@ -187,6 +191,10 @@ CameraBinSession::CameraBinSession(GstElementFactory *sourceFactory, QObject *pa g_object_set(G_OBJECT(m_camerabin), PREVIEW_CAPS_PROPERTY, previewCaps, NULL); gst_caps_unref(previewCaps); + + connect(&m_resourcePolicy, &CamerabinResourcePolicy::resourcesGranted, this, &CameraBinSession::applyState); + connect(&m_resourcePolicy, &CamerabinResourcePolicy::resourcesDenied, this, &CameraBinSession::resourcesLost); + connect(&m_resourcePolicy, &CamerabinResourcePolicy::resourcesLost, this, &CameraBinSession::resourcesLost); } CameraBinSession::~CameraBinSession() @@ -270,7 +278,7 @@ bool CameraBinSession::setupCameraBin() #endif m_viewfinderHasChanged = false; if (!m_viewfinderElement) { - if (m_pendingState == QCamera::ActiveState) + if (m_requestedState == QCamera::ActiveState) qWarning() << "Starting camera without viewfinder available"; m_viewfinderElement = gst_element_factory_make("fakesink", NULL); } @@ -561,22 +569,25 @@ void CameraBinSession::captureImage(int requestId, const QString &fileName) g_signal_emit_by_name(G_OBJECT(m_camerabin), CAPTURE_START, NULL); m_imageFileName = actualFileName; + + updateCaptureStatus(); +} + +void CameraBinSession::updateCaptureStatus() +{ + if (m_reportedReadyForCapture != m_readyForCapture) { + m_readyForCapture = m_reportedReadyForCapture; + emit handleReadyForCaptureChanged(m_readyForCapture); + } } void CameraBinSession::setCaptureMode(QCamera::CaptureModes mode) { - m_captureMode = mode; - - switch (m_captureMode) { - case QCamera::CaptureStillImage: - g_object_set(m_camerabin, MODE_PROPERTY, CAMERABIN_IMAGE_MODE, NULL); - break; - case QCamera::CaptureVideo: - g_object_set(m_camerabin, MODE_PROPERTY, CAMERABIN_VIDEO_MODE, NULL); - break; + if (m_captureMode != mode) { + m_captureMode = mode; + emit m_cameraControl->captureModeChanged(m_captureMode); } - m_recorderControl->updateStatus(); } QUrl CameraBinSession::outputLocation() const @@ -631,32 +642,29 @@ void CameraBinSession::setViewfinder(QObject *viewfinder) viewfinder = 0; if (m_viewfinder != viewfinder) { - bool oldReady = isReady(); - if (m_viewfinder) { disconnect(m_viewfinder, SIGNAL(sinkChanged()), this, SLOT(handleViewfinderChange())); disconnect(m_viewfinder, SIGNAL(readyChanged(bool)), - this, SIGNAL(readyChanged(bool))); + this, SLOT(applyState())); m_busHelper->removeMessageFilter(m_viewfinder); } m_viewfinder = viewfinder; m_viewfinderHasChanged = true; + m_transientState = QCamera::UnloadedState; if (m_viewfinder) { connect(m_viewfinder, SIGNAL(sinkChanged()), this, SLOT(handleViewfinderChange())); connect(m_viewfinder, SIGNAL(readyChanged(bool)), - this, SIGNAL(readyChanged(bool))); + this, SLOT(applyState())); m_busHelper->installMessageFilter(m_viewfinder); } - emit viewfinderChanged(); - if (oldReady != isReady()) - emit readyChanged(isReady()); + applyState(); } } @@ -687,7 +695,9 @@ void CameraBinSession::handleViewfinderChange() //the viewfinder will be reloaded //shortly when the pipeline is started m_viewfinderHasChanged = true; - emit viewfinderChanged(); + m_transientState = QCamera::UnloadedState; + + applyState(); } void CameraBinSession::setStatus(QCamera::Status status) @@ -697,8 +707,6 @@ void CameraBinSession::setStatus(QCamera::Status status) m_status = status; emit statusChanged(m_status); - - setStateHelper(m_pendingState); } QCamera::Status CameraBinSession::status() const @@ -706,60 +714,122 @@ QCamera::Status CameraBinSession::status() const return m_status; } -QCamera::State CameraBinSession::pendingState() const +QCamera::State CameraBinSession::requestedState() const { - return m_pendingState; + return m_requestedState; } void CameraBinSession::setState(QCamera::State newState) { - if (newState == m_pendingState) + if (newState == m_requestedState) return; - m_pendingState = newState; - emit pendingStateChanged(m_pendingState); + if (newState < m_transientState) { + m_transientState = newState; + } + + m_requestedState = newState; + emit pendingStateChanged(m_requestedState); + m_cameraControl->stateChanged(m_requestedState); #if CAMERABIN_DEBUG qDebug() << Q_FUNC_INFO << newState; #endif - setStateHelper(newState); + applyState(); } -void CameraBinSession::setStateHelper(QCamera::State state) +void CameraBinSession::applyState() { - switch (state) { + CamerabinResourcePolicy::ResourceSet resourceSet = CamerabinResourcePolicy::NoResources; + switch (m_requestedState) { case QCamera::UnloadedState: - unload(); + resourceSet = CamerabinResourcePolicy::NoResources; break; case QCamera::LoadedState: - if (m_status == QCamera::ActiveStatus) - stop(); - else if (m_status == QCamera::UnloadedStatus) - load(); + resourceSet = CamerabinResourcePolicy::LoadedResources; break; case QCamera::ActiveState: - // If the viewfinder changed while in the loaded state, we need to reload the pipeline - if (m_status == QCamera::LoadedStatus && !m_viewfinderHasChanged) - start(); - else if (m_status == QCamera::UnloadedStatus || m_viewfinderHasChanged) - load(); + resourceSet = m_captureMode == QCamera::CaptureStillImage + ? CamerabinResourcePolicy::ImageCaptureResources + : CamerabinResourcePolicy::VideoCaptureResources; + break; } + + if (m_resourcePolicy.resourceSet() != resourceSet) { + m_resourcePolicy.setResourceSet(resourceSet); + } + + if (!m_resourcePolicy.isResourcesGranted()) { + m_transientState = QCamera::UnloadedState; + } + + if (m_transientState < m_acceptedState) { + // Ensure that if something tried to unload or stop the pipeline that it happens and + // settings changes are applied. + switch (m_transientState) { + case QCamera::UnloadedState: + unload(); + break; + case QCamera::LoadedState: + stop(); + break; + case QCamera::ActiveState: + // Unreachable + break; + } + } + + if (m_requestedState > m_acceptedState + && !m_busy + && isReady() + && m_resourcePolicy.isResourcesGranted()) { + m_transientState = m_requestedState; + + switch (m_requestedState) { + case QCamera::UnloadedState: + // Unreachable. + break; + case QCamera::LoadedState: + load(); + break; + case QCamera::ActiveState: + if (m_status == QCamera::UnloadedStatus) { + load(); + } else if (m_status == QCamera::LoadedStatus) { + start(); + } + } + } +} + +void CameraBinSession::resourcesLost() +{ + m_transientState = QCamera::UnloadedState; + + applyState(); } void CameraBinSession::setError(int err, const QString &errorString) { - m_pendingState = QCamera::UnloadedState; - emit error(err, errorString); - setStatus(QCamera::UnloadedStatus); + m_requestedState = QCamera::UnloadedState; + m_transientState = QCamera::UnloadedState; + + emit m_cameraControl->error(err, errorString); + + applyState(); + + emit m_cameraControl->stateChanged(m_requestedState); } void CameraBinSession::load() { - if (m_status != QCamera::UnloadedStatus && !m_viewfinderHasChanged) + if (m_status != QCamera::UnloadedStatus) return; - setStatus(QCamera::LoadingStatus); + m_acceptedState = QCamera::LoadedState; + + m_status = QCamera::LoadingStatus; if (!setupCameraBin()) { setError(QCamera::CameraError, QStringLiteral("No camera source available")); @@ -780,23 +850,34 @@ void CameraBinSession::load() setupCaptureResolution(); gst_element_set_state(m_camerabin, GST_STATE_READY); + + emit statusChanged(m_status); } void CameraBinSession::unload() { - if (m_status == QCamera::UnloadedStatus || m_status == QCamera::UnloadingStatus) + if (m_status == QCamera::UnloadedStatus) return; - // We save the recording state in case something reacted to setStatus() and - // stopped recording. - bool wasRecording = m_recordingActive; - - setStatus(QCamera::UnloadingStatus); + bool changed = m_status != QCamera::UnloadingStatus; + m_status = QCamera::UnloadingStatus; if (m_recordingActive) stopVideoRecording(); - else if (!wasRecording) - handleBusyChanged(false); + + if (!m_busy) { + m_acceptedState = QCamera::UnloadedState; + + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + gst_element_set_state(m_camerabin, GST_STATE_NULL); + + m_status = QCamera::UnloadedStatus; + emit statusChanged(m_status); + } else if (changed) { + emit statusChanged(m_status); + } } void CameraBinSession::start() @@ -804,7 +885,20 @@ void CameraBinSession::start() if (m_status != QCamera::LoadedStatus) return; - setStatus(QCamera::StartingStatus); + m_acceptedState = QCamera::ActiveState; + + m_status = QCamera::StartingStatus; + + m_appliedCaptureMode = m_captureMode; + + switch (m_captureMode) { + case QCamera::CaptureStillImage: + g_object_set(m_camerabin, MODE_PROPERTY, CAMERABIN_IMAGE_MODE, NULL); + break; + case QCamera::CaptureVideo: + g_object_set(m_camerabin, MODE_PROPERTY, CAMERABIN_VIDEO_MODE, NULL); + break; + } m_recorderControl->applySettings(); @@ -822,22 +916,30 @@ void CameraBinSession::start() setupCaptureResolution(); gst_element_set_state(m_camerabin, GST_STATE_PLAYING); + + emit statusChanged(m_status); } void CameraBinSession::stop() { - if (m_status != QCamera::ActiveStatus) + if (m_status != QCamera::ActiveStatus && m_status != QCamera::StartingStatus) return; - setStatus(QCamera::StoppingStatus); + m_status = QCamera::StoppingStatus; if (m_recordingActive) stopVideoRecording(); - if (m_viewfinderInterface) - m_viewfinderInterface->stopRenderer(); + if (!m_busy) { + m_acceptedState = QCamera::LoadedState; - gst_element_set_state(m_camerabin, GST_STATE_READY); + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + gst_element_set_state(m_camerabin, GST_STATE_READY); + } + + emit statusChanged(m_status); } bool CameraBinSession::isBusy() const @@ -859,11 +961,8 @@ void CameraBinSession::updateBusyStatus(GObject *o, GParamSpec *p, gpointer d) g_object_get(o, "idle", &idle, NULL); bool busy = !idle; - if (session->m_busy != busy) { - session->m_busy = busy; - QMetaObject::invokeMethod(session, "handleBusyChanged", - Qt::QueuedConnection, - Q_ARG(bool, busy)); + if (session->m_busy.fetchAndStoreRelaxed(busy) != busy) { + QMetaObject::invokeMethod(session, "applyState", Qt::QueuedConnection); } } @@ -875,10 +974,9 @@ void CameraBinSession::updateReadyForCapture(GObject *o, GParamSpec *p, gpointer CameraBinSession *session = reinterpret_cast(d); gboolean ready = false; g_object_get(o, "ready-for-capture", &ready, NULL); - if (session->m_readyForCapture != ready) { - session->m_readyForCapture = ready; - QMetaObject::invokeMethod(session, "handleReadyForCaptureChanged", - Qt::QueuedConnection, Q_ARG(bool, ready)); + + if (session->m_reportedReadyForCapture.fetchAndStoreRelaxed(ready) != ready) { + QMetaObject::invokeMethod(session, "updateCaptureStatus", Qt::QueuedConnection); } } @@ -1087,6 +1185,9 @@ bool CameraBinSession::processBusMessage(const QGstreamerMessage &message) default: break; } + + // Update any chained state changes. + applyState(); } break; default: @@ -1121,6 +1222,10 @@ QString CameraBinSession::currentContainerFormat() const void CameraBinSession::recordVideo() { + if (!m_reportedReadyForCapture || m_appliedCaptureMode != QCamera::CaptureVideo || m_busy) { + return; + } + QString format = currentContainerFormat(); if (format.isEmpty()) format = m_mediaContainerControl->actualContainerFormat(); @@ -1531,28 +1636,4 @@ void CameraBinSession::elementRemoved(GstBin *, GstElement *element, CameraBinSe session->m_muxer = 0; } -void CameraBinSession::handleBusyChanged(bool busy) -{ - // don't do anything if we are not unloading. - // It could be that the camera is starting again while it is being unloaded - if (m_status != QCamera::UnloadingStatus) { - if (m_busy != busy) - emit busyChanged(m_busy = busy); - return; - } - - // Now we can really stop - if (m_viewfinderInterface) - m_viewfinderInterface->stopRenderer(); - - gst_element_set_state(m_camerabin, GST_STATE_NULL); - - if (m_busy != busy) - emit busyChanged(m_busy = busy); - - m_supportedViewfinderSettings.clear(); - - setStatus(QCamera::UnloadedStatus); -} - QT_END_NAMESPACE diff --git a/src/plugins/gstreamer/camerabin/camerabinsession.h b/src/plugins/gstreamer/camerabin/camerabinsession.h index 3493bd01..5f6a6524 100644 --- a/src/plugins/gstreamer/camerabin/camerabinsession.h +++ b/src/plugins/gstreamer/camerabin/camerabinsession.h @@ -49,6 +49,8 @@ #include #include "qcamera.h" +#include "camerabinresourcepolicy.h" + QT_BEGIN_NAMESPACE class QGstreamerMessage; @@ -118,6 +120,7 @@ public: CameraBinLocks *cameraLocksControl(); #endif + CamerabinResourcePolicy *resourcePolicy() { return &m_resourcePolicy; } CameraBinZoom *cameraZoomControl() const { return m_cameraZoomControl; } CameraBinImageProcessing *imageProcessingControl() const { return m_imageProcessingControl; } CameraBinCaptureDestination *captureDestinationControl() const { return m_captureDestinationControl; } @@ -143,7 +146,7 @@ public: void captureImage(int requestId, const QString &fileName); QCamera::Status status() const; - QCamera::State pendingState() const; + QCamera::State requestedState() const; bool isBusy() const; bool isReadyForCapture() const; @@ -182,7 +185,8 @@ public slots: private slots: void handleViewfinderChange(); void setupCaptureResolution(); - void handleBusyChanged(bool busy); + void updateCaptureStatus(); + void applyState(); private: void load(); @@ -191,7 +195,7 @@ private: void stop(); void setStatus(QCamera::Status status); - void setStateHelper(QCamera::State state); + void resourcesLost(); void setError(int error, const QString &errorString); bool setupCameraBin(); @@ -211,16 +215,21 @@ private: bool m_recordingActive; QString m_captureDevice; QCamera::Status m_status; - QCamera::State m_pendingState; + QCamera::State m_requestedState; // The desired state, transitioning to this may be blocked resource policy or outstanding actions. + QCamera::State m_transientState; // A state that must be passed through. If the pipeline is busy and can't be unloaded immediately it should still go through unloaded when it can to apply settings changes. + QCamera::State m_acceptedState; // The current state or the state the pipeline is actively transitioning to. QString m_inputDevice; bool m_muted; - bool m_busy; bool m_readyForCapture; QMediaStorageLocation m_mediaStorageLocation; QCamera::CaptureModes m_captureMode; + QCamera::CaptureModes m_appliedCaptureMode; QMap m_metaData; + QAtomicInteger m_busy; + QAtomicInteger m_reportedReadyForCapture; + QGstreamerElementFactory *m_audioInputFactory; QGstreamerElementFactory *m_videoInputFactory; QObject *m_viewfinder; @@ -229,6 +238,7 @@ private: QCameraViewfinderSettings m_viewfinderSettings; QCameraViewfinderSettings m_actualViewfinderSettings; + CamerabinResourcePolicy m_resourcePolicy; CameraBinControl *m_cameraControl; CameraBinAudioEncoder *m_audioEncodeControl; CameraBinVideoEncoder *m_videoEncodeControl;