diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index b15e7ee75..448fa75cc 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -14,6 +14,7 @@ list(APPEND _LINPHONEAPP_SOURCES core/call-history/CallHistoryProxy.cpp core/camera/CameraGui.cpp core/camera/CameraDummy.cpp + core/camera/PreviewManager.cpp core/fps-counter/FPSCounter.cpp core/friend/FriendCore.cpp core/friend/FriendGui.cpp diff --git a/Linphone/core/camera/CameraGui.cpp b/Linphone/core/camera/CameraGui.cpp index 4dd4bfc0d..f4a8acc25 100644 --- a/Linphone/core/camera/CameraGui.cpp +++ b/Linphone/core/camera/CameraGui.cpp @@ -25,6 +25,7 @@ #include "CameraDummy.hpp" #include "CameraGui.hpp" +#include "PreviewManager.hpp" #include "core/App.hpp" #include "core/call/CallCore.hpp" #include "core/call/CallGui.hpp" @@ -33,9 +34,6 @@ DEFINE_ABSTRACT_OBJECT(CameraGui) -QMutex CameraGui::gPreviewCounterMutex; -int CameraGui::gPreviewCounter = 0; - // ============================================================================= CameraGui::CameraGui(QQuickItem *parent) : QQuickFramebufferObject(parent) { mustBeInMainThread(getClassName()); @@ -52,7 +50,6 @@ CameraGui::~CameraGui() { mustBeInMainThread("~" + getClassName()); mRefreshTimer.stop(); mIsDeleting = true; - deactivatePreview(); setWindowIdLocation(None); } @@ -72,30 +69,15 @@ QQuickFramebufferObject::Renderer *CameraGui::createRenderer(bool resetWindowId) // A renderer is mandatory, we cannot wait async. switch (getSourceLocation()) { case CorePreview: { - auto f = [qmlName = mQmlName, &renderer, resetWindowId]() { - qInfo() << "[Camera] (" << qmlName << ") Setting Camera to Preview"; - auto coreModel = CoreModel::getInstance(); - if (coreModel) { - auto core = coreModel->getCore(); - if (!core) return; - if (resetWindowId) { - renderer = (QQuickFramebufferObject::Renderer *)core->getNativePreviewWindowId(); - if (renderer) core->setNativePreviewWindowId(NULL); - } else { - renderer = (QQuickFramebufferObject::Renderer *)core->createNativePreviewWindowId(); - if (renderer) core->setNativePreviewWindowId(renderer); - } - } - }; - if (mIsDeleting) { - App::postModelBlock(f); - } else App::postModelSync(f); + if (resetWindowId) PreviewManager::getInstance()->unsubscribe(this); + else renderer = PreviewManager::getInstance()->subscribe(this); } break; case Call: { auto f = [qmlName = mQmlName, callGui = mCallGui, &renderer, resetWindowId]() { auto call = callGui->getCore()->getModel()->getMonitor(); if (call) { - qInfo() << "[Camera] (" << qmlName << ") Setting Camera to CallModel"; + qInfo() << "[Camera] (" << qmlName << ") " << (resetWindowId ? "Resetting" : "Setting") + << " Camera to CallModel"; if (resetWindowId) { renderer = (QQuickFramebufferObject::Renderer *)call->getNativeVideoWindowId(); if (renderer) call->setNativeVideoWindowId(NULL); @@ -113,7 +95,8 @@ QQuickFramebufferObject::Renderer *CameraGui::createRenderer(bool resetWindowId) auto f = [qmlName = mQmlName, participantDeviceGui = mParticipantDeviceGui, &renderer, resetWindowId]() { auto device = participantDeviceGui->getCore()->getModel()->getMonitor(); if (device) { - qInfo() << "[Camera] (" << qmlName << ") Setting Camera to ParticipantDeviceModel"; + qInfo() << "[Camera] (" << qmlName << ") " << (resetWindowId ? "Resetting" : "Setting") + << " Camera to ParticipantDeviceModel"; if (resetWindowId) { } else { renderer = (QQuickFramebufferObject::Renderer *)device->createNativeVideoWindowId(); @@ -217,27 +200,6 @@ CameraGui::WindowIdLocation CameraGui::getSourceLocation() const { return mWindowIdLocation; } -void CameraGui::activatePreview() { - gPreviewCounterMutex.lock(); - if (++gPreviewCounter == 1) { - auto f = []() { CoreModel::getInstance()->getCore()->enableVideoPreview(true); }; - if (mIsDeleting) App::postModelBlock(f); - else App::postModelSync(f); - } - gPreviewCounterMutex.unlock(); -} - -void CameraGui::deactivatePreview() { - gPreviewCounterMutex.lock(); - if (getSourceLocation() == CorePreview) { - if (--gPreviewCounter == 0) { - auto f = []() { CoreModel::getInstance()->getCore()->enableVideoPreview(false); }; - if (mIsDeleting) App::postModelBlock(f); - else App::postModelSync(f); - } - } - gPreviewCounterMutex.unlock(); -} void CameraGui::setWindowIdLocation(const WindowIdLocation &location) { if (mWindowIdLocation != location) { qDebug() << log() @@ -245,10 +207,10 @@ void CameraGui::setWindowIdLocation(const WindowIdLocation &location) { .arg(mQmlName) .arg(mWindowIdLocation) .arg(location); - if (mWindowIdLocation == CorePreview) deactivatePreview(); + if (mWindowIdLocation == CorePreview) PreviewManager::getInstance()->unsubscribe(this); resetWindowId(); // Location change: Reset old window ID. mWindowIdLocation = location; - if (mWindowIdLocation == CorePreview) activatePreview(); + if (mWindowIdLocation == CorePreview) PreviewManager::getInstance()->subscribe(this); update(); // if (mWindowIdLocation == WindowIdLocation::CorePreview) { // mLastVideoDefinition = diff --git a/Linphone/core/camera/CameraGui.hpp b/Linphone/core/camera/CameraGui.hpp index 764b90de8..f45f4bfb8 100644 --- a/Linphone/core/camera/CameraGui.hpp +++ b/Linphone/core/camera/CameraGui.hpp @@ -55,9 +55,6 @@ public: Q_INVOKABLE void resetWindowId() const; // const to be used from createRenderer() void checkVideoDefinition(); - static QMutex gPreviewCounterMutex; - static int gPreviewCounter; - bool getIsReady() const; void setIsReady(bool isReady); void isReady(); @@ -74,8 +71,6 @@ public: WindowIdLocation getSourceLocation() const; void setWindowIdLocation(const WindowIdLocation &location); - void activatePreview(); - void deactivatePreview(); void updateWindowIdLocation(); void removeParticipantDeviceModel(); void removeCallModel(); diff --git a/Linphone/core/camera/PreviewManager.cpp b/Linphone/core/camera/PreviewManager.cpp new file mode 100644 index 000000000..b3b70231a --- /dev/null +++ b/Linphone/core/camera/PreviewManager.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "../App.hpp" +#include "PreviewManager.hpp" + +// ============================================================================= +PreviewManager *PreviewManager::gInstance = nullptr; + +PreviewManager::PreviewManager(QObject *parent) : QObject(parent) { +} + +PreviewManager::~PreviewManager() { +} + +PreviewManager *PreviewManager::getInstance() { + if (gInstance) return gInstance; + else { + gInstance = new PreviewManager(); + return gInstance; + } +} + +QQuickFramebufferObject::Renderer *PreviewManager::subscribe(const CameraGui *candidate) { + QQuickFramebufferObject::Renderer *renderer = nullptr; + mCounterMutex.lock(); + + if (mCandidates.size() == 0) { + activate(); + } + auto itCandidate = + std::find_if(mCandidates.begin(), mCandidates.end(), + [candidate](const QPair &item) { + return item.first == candidate; + }); + if (itCandidate == mCandidates.end()) { + connect(candidate, &QObject::destroyed, this, qOverload(&PreviewManager::unsubscribe)); + mCandidates.append({candidate, nullptr}); + itCandidate = mCandidates.end() - 1; + } + App::postModelSync([&renderer, isFirst = (itCandidate == mCandidates.begin())]() { + renderer = + (QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId(); + if (isFirst) CoreModel::getInstance()->getCore()->setNativePreviewWindowId(renderer); + }); + itCandidate->second = renderer; + mCounterMutex.unlock(); + return renderer; +} + +void PreviewManager::unsubscribe(const CameraGui *candidate) { // If nullptr, Use of sender() + mCounterMutex.lock(); + auto itCandidate = std::find_if(mCandidates.begin(), mCandidates.end(), + [candidate = (candidate ? candidate : sender())]( + const QPair &item) { + return item.first == candidate; + }); + if (itCandidate != mCandidates.end()) { + disconnect(candidate, nullptr, this, nullptr); + if (mCandidates.size() == 1) { + mCandidates.erase(itCandidate); + deactivate(); + } else if (mCandidates.begin() == itCandidate) { + mCandidates.erase(itCandidate); + qWarning() << "Update " << mCandidates.first().first->getQmlName(); + App::postModelSync([renderer = mCandidates.first().second]() { + CoreModel::getInstance()->getCore()->setNativePreviewWindowId(renderer); + }); + } else { + mCandidates.erase(itCandidate); + } + } + mCounterMutex.unlock(); +} + +void PreviewManager::unsubscribe(QObject *sender) { + unsubscribe(dynamic_cast(sender)); +} + +void PreviewManager::activate() { + App::postModelSync([]() { CoreModel::getInstance()->getCore()->enableVideoPreview(true); }); +} + +void PreviewManager::deactivate() { + App::postModelSync([]() { CoreModel::getInstance()->getCore()->enableVideoPreview(false); }); +} diff --git a/Linphone/core/camera/PreviewManager.hpp b/Linphone/core/camera/PreviewManager.hpp new file mode 100644 index 000000000..b1870f71d --- /dev/null +++ b/Linphone/core/camera/PreviewManager.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PREVIEW_MANAGER_H_ +#define PREVIEW_MANAGER_H_ + +#include "CameraGui.hpp" +#include +#include +#include +#include + +// Manage the SDK preview as a singleton. +// The goal is to process the limitation that only one preview can be displayed. +// On asynchronized application, the destruction of a previous Preview can be done AFTER the creation on a new Preview +// Sticker. + +// ============================================================================= + +class PreviewManager : public QObject { + Q_OBJECT +public: + PreviewManager(QObject *parent = nullptr); + virtual ~PreviewManager(); + + static PreviewManager *getInstance(); + + QQuickFramebufferObject::Renderer *subscribe(const CameraGui *candidate); + void unsubscribe(const CameraGui *candidate); + + void activate(); + void deactivate(); +public slots: + void unsubscribe(QObject *sender); + +private: + QMutex mCounterMutex; + QList> mCandidates; + static PreviewManager *gInstance; + QQuickFramebufferObject::Renderer *mPreviewRenderer = nullptr; +}; + +#endif