diff --git a/CMakeLists.txt b/CMakeLists.txt index 679c861f1b12db09d3a304eab0120fb9e45cd4e9..a7de6d5a64151eb42367a2b4a8b55e0430d32cc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ set(QT_MODULES WebEngineQuick WebChannel WebEngineWidgets + Multimedia ) find_package(Qt6 COMPONENTS ${QT_MODULES} REQUIRED) foreach(MODULE ${QT_MODULES}) @@ -101,14 +102,11 @@ set(COMMON_SOURCES ${SRC_DIR}/main.cpp ${SRC_DIR}/smartlistmodel.cpp ${SRC_DIR}/utils.cpp - ${SRC_DIR}/rendermanager.cpp ${SRC_DIR}/mainapplication.cpp ${SRC_DIR}/messagesadapter.cpp ${SRC_DIR}/accountadapter.cpp ${SRC_DIR}/calladapter.cpp ${SRC_DIR}/conversationsadapter.cpp - ${SRC_DIR}/distantrenderer.cpp - ${SRC_DIR}/previewrenderer.cpp ${SRC_DIR}/avadapter.cpp ${SRC_DIR}/contactadapter.cpp ${SRC_DIR}/pluginadapter.cpp @@ -140,6 +138,7 @@ set(COMMON_SOURCES ${SRC_DIR}/currentaccount.cpp ${SRC_DIR}/videodevices.cpp ${SRC_DIR}/previewengine.cpp + ${SRC_DIR}/videoprovider.cpp ) set(COMMON_HEADERS @@ -152,7 +151,6 @@ set(COMMON_HEADERS ${SRC_DIR}/version.h ${SRC_DIR}/accountlistmodel.h ${SRC_DIR}/instancemanager.h - ${SRC_DIR}/rendermanager.h ${SRC_DIR}/connectivitymonitor.h ${SRC_DIR}/jamiavatartheme.h ${SRC_DIR}/mainapplication.h @@ -161,8 +159,6 @@ set(COMMON_HEADERS ${SRC_DIR}/accountadapter.h ${SRC_DIR}/calladapter.h ${SRC_DIR}/conversationsadapter.h - ${SRC_DIR}/distantrenderer.h - ${SRC_DIR}/previewrenderer.h ${SRC_DIR}/qmladapterbase.h ${SRC_DIR}/avadapter.h ${SRC_DIR}/contactadapter.h @@ -198,8 +194,20 @@ set(COMMON_HEADERS ${SRC_DIR}/currentaccount.h ${SRC_DIR}/videodevices.h ${SRC_DIR}/previewengine.h + ${SRC_DIR}/videoprovider.h ) +# For avframe dependency on Windows/macOS. +if(NOT DEFINED LIBAV_INCLUDE_PATH) + set(LIBJAMI_CONTRIB_DIR "${PROJECT_SOURCE_DIR}/../daemon/contrib/") + if(WIN32) + set(LIBAV_INCLUDE_PATH ${LIBJAMI_CONTRIB_DIR}/build/ffmpeg/Build/win32/x64/include/) + else() + set(LIBAV_INCLUDE_PATH ${LIBJAMI_CONTRIB_DIR}/native/ffmpeg) + endif() +endif() +include_directories(${LIBAV_INCLUDE_PATH}) + if(MSVC) set(WINDOWS_SYS_LIBS Shell32.lib diff --git a/qml.qrc b/qml.qrc index ce5c7ae71d07eb42f55ec3d0849e2751b89bed99..9dca240a6e4954c3a9cbfcf83364d66a35004950 100644 --- a/qml.qrc +++ b/qml.qrc @@ -5,6 +5,8 @@ <file>src/constant/JamiQmlUtils.qml</file> <file>src/constant/JamiStrings.qml</file> <file>src/constant/JamiTheme.qml</file> + <file>src/commoncomponents/VideoView.qml</file> + <file>src/commoncomponents/LocalVideo.qml</file> <file>src/commoncomponents/SettingParaCombobox.qml</file> <file>src/commoncomponents/PreferenceItemDelegate.qml</file> <file>src/commoncomponents/PasswordDialog.qml</file> diff --git a/src/avadapter.cpp b/src/avadapter.cpp index 94c73db1e5cbfa2ce9db9bb50e05f6ac19c70b47..c46836e9fbd19088eb2c5d3056e7a145d8ee26f5 100644 --- a/src/avadapter.cpp +++ b/src/avadapter.cpp @@ -40,11 +40,10 @@ AvAdapter::AvAdapter(LRCInstance* instance, QObject* parent) &lrc::api::AVModel::audioDeviceEvent, this, &AvAdapter::onAudioDeviceEvent); - connect(&lrcInstance_->avModel(), &lrc::api::AVModel::rendererStarted, [this](const QString&) { - auto callId = lrcInstance_->getCurrentCallId(); - set_currentRenderingDeviceType( - lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type); - }); + connect(&lrcInstance_->avModel(), + &lrc::api::AVModel::rendererStarted, + this, + &AvAdapter::onRendererStarted); } // The top left corner of primary screen is (0, 0). @@ -101,8 +100,6 @@ AvAdapter::shareEntireScreen(int screenNumber) resource, lrc::api::NewCallModel::MediaRequestType::SCREENSHARING, false); - set_currentRenderingDeviceType( - lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type); } void @@ -122,14 +119,12 @@ AvAdapter::shareAllScreens() resource, lrc::api::NewCallModel::MediaRequestType::SCREENSHARING, false); - set_currentRenderingDeviceType( - lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type); } void AvAdapter::captureScreen(int screenNumber) { - auto futureResult = QtConcurrent::run([this, screenNumber]() { + std::ignore = QtConcurrent::run([this, screenNumber]() { QScreen* screen = QGuiApplication::screens().at(screenNumber); if (!screen) return; @@ -149,7 +144,7 @@ AvAdapter::captureScreen(int screenNumber) void AvAdapter::captureAllScreens() { - auto futureResult = QtConcurrent::run([this]() { + std::ignore = QtConcurrent::run([this]() { auto screens = QGuiApplication::screens(); QList<QPixmap> scrs; @@ -191,8 +186,6 @@ AvAdapter::shareFile(const QString& filePath) filePath, lrc::api::NewCallModel::MediaRequestType::FILESHARING, false); - set_currentRenderingDeviceType( - lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type); } } @@ -218,8 +211,6 @@ AvAdapter::shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned heig resource, lrc::api::NewCallModel::MediaRequestType::SCREENSHARING, false); - set_currentRenderingDeviceType( - lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type); }); #else auto resource = lrcInstance_->getCurrentCallModel()->getDisplay(getScreenNumber(), @@ -234,8 +225,6 @@ AvAdapter::shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned heig resource, lrc::api::NewCallModel::MediaRequestType::SCREENSHARING, false); - set_currentRenderingDeviceType( - lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type); #endif } @@ -250,8 +239,6 @@ AvAdapter::shareWindow(const QString& windowId) resource, lrc::api::NewCallModel::MediaRequestType::SCREENSHARING, false); - set_currentRenderingDeviceType( - lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type); } QString @@ -304,7 +291,6 @@ AvAdapter::stopSharing() lrcInstance_->avModel().getCurrentVideoCaptureDevice(), lrc::api::NewCallModel::MediaRequestType::CAMERA, muteCamera_); - set_currentRenderingDeviceType(lrc::api::video::DeviceType::CAMERA); } } @@ -329,6 +315,16 @@ AvAdapter::onAudioDeviceEvent() Q_EMIT audioDeviceListChanged(inputs, outputs); } +void +AvAdapter::onRendererStarted(const QString& id) +{ + auto callId = lrcInstance_->getCurrentCallId(); + auto callModel = lrcInstance_->getCurrentCallModel(); + auto renderDevice = callModel->getCurrentRenderedDevice(callId); + set_currentRenderingDeviceId(id); + set_currentRenderingDeviceType(renderDevice.type); +} + int AvAdapter::getScreenNumber() const { diff --git a/src/avadapter.h b/src/avadapter.h index c10e725e590f79fbb27c4c8cda1e471a2cbc89c6..a21dfad3fe0ff8a5505ea09e57161059012f79d9 100644 --- a/src/avadapter.h +++ b/src/avadapter.h @@ -29,7 +29,10 @@ class AvAdapter final : public QmlAdapterBase { Q_OBJECT + // TODO: currentRenderingDeviceType is only used in QML to check if + // we're sharing or not, so it should maybe just be a boolean. QML_RO_PROPERTY(lrc::api::video::DeviceType, currentRenderingDeviceType) + QML_RO_PROPERTY(QString, currentRenderingDeviceId) QML_PROPERTY(bool, muteCamera) QML_RO_PROPERTY(QStringList, windowsNames) QML_RO_PROPERTY(QList<QVariant>, windowsIds) @@ -90,6 +93,7 @@ protected: private Q_SLOTS: void onAudioDeviceEvent(); + void onRendererStarted(const QString& id); private: // Get screens arrangement rect relative to primary screen. diff --git a/src/calladapter.cpp b/src/calladapter.cpp index 27c23404bad729fe9041915ad724b19b3c3aedbd..3f4c1773b8ffcb1139dff25be84b595418b03ab2 100644 --- a/src/calladapter.cpp +++ b/src/calladapter.cpp @@ -185,7 +185,6 @@ CallAdapter::onCallStatusChanged(const QString& callId, int code) case lrc::api::call::Status::PEER_BUSY: case lrc::api::call::Status::TIMEOUT: case lrc::api::call::Status::TERMINATING: { - lrcInstance_->renderer()->removeDistantRenderer(callId); const auto& convInfo = lrcInstance_->getConversationFromCallId(callId); if (convInfo.uid.isEmpty()) { return; @@ -285,7 +284,7 @@ void CallAdapter::onCallAddedToConference(const QString& callId, const QString& confId) { Q_UNUSED(callId) - lrcInstance_->renderer()->addDistantRenderer(confId); + Q_UNUSED(confId) saveConferenceSubcalls(); } @@ -450,9 +449,9 @@ CallAdapter::updateCall(const QString& convUid, const QString& accountId, bool f if (convInfo.uid == lrcInstance_->get_selectedConvUid()) { auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); - if (accInfo.profileInfo.type != lrc::api::profile::Type::SIP) + if (accInfo.profileInfo.type != lrc::api::profile::Type::SIP) { accInfo.callModel->setCurrentCall(call->id); - lrcInstance_->renderer()->addDistantRenderer(call->id); + } } updateCallOverlay(convInfo); diff --git a/src/commoncomponents/LocalVideo.qml b/src/commoncomponents/LocalVideo.qml new file mode 100644 index 0000000000000000000000000000000000000000..caca6c081562fd034c7fc0fc1466fd6f0d2a75de --- /dev/null +++ b/src/commoncomponents/LocalVideo.qml @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +import QtQuick +import QtMultimedia +import Qt5Compat.GraphicalEffects + +import net.jami.Adapters 1.1 + +VideoView { + id: root + + crop: true + + function startWithId(id, force = false) { + if (id.length === 0) { + VideoDevices.stopDevice(rendererId) + rendererId = id + } else { + rendererId = VideoDevices.startDevice(id, force) + } + } + + onVisibleChanged: { + const id = visible ? VideoDevices.getDefaultDevice() : "" + startWithId(id) + } +} + + diff --git a/src/commoncomponents/PhotoboothView.qml b/src/commoncomponents/PhotoboothView.qml index c9b7137bfa4862257502d23107a592312dfd54e1..828ab91faa25fec3e4ca41437eed4940d4892f18 100644 --- a/src/commoncomponents/PhotoboothView.qml +++ b/src/commoncomponents/PhotoboothView.qml @@ -39,8 +39,7 @@ Item { height: boothLayout.height function startBooth() { - preview.deviceId = VideoDevices.getDefaultDevice() - preview.rendererId = VideoDevices.startDevice(preview.deviceId) + preview.startWithId(VideoDevices.getDefaultDevice()) isPreviewing = true } @@ -130,17 +129,19 @@ Item { showPresenceIndicator: false } - PhotoboothPreviewRender { + LocalVideo { id: preview anchors.fill: parent anchors.margins: 1 - property string deviceId: VideoDevices.getDefaultDevice() - rendererId: "" - visible: isPreviewing - lrcInstance: LRCInstance + + rendererId: VideoDevices.getDefaultDevice() + + function takePhoto() { + return videoProvider.captureVideoFrame(videoSink) + } layer.enabled: true layer.effect: OpacityMask { @@ -150,11 +151,6 @@ Item { radius: avatarSize / 2 } } - - onRenderingStopped: { - if (root.visible) - stopBooth() - } } Rectangle { diff --git a/src/commoncomponents/VideoView.qml b/src/commoncomponents/VideoView.qml new file mode 100644 index 0000000000000000000000000000000000000000..ecfb5c3d8adfb864e3b4c024882118392a1d8868 --- /dev/null +++ b/src/commoncomponents/VideoView.qml @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +import QtQuick +import QtMultimedia +import Qt5Compat.GraphicalEffects + +Item { + id: root + + property string rendererId + property alias videoSink: videoOutput.videoSink + property alias underlayItems: rootUnderlayItem.children + property real invAspectRatio: (videoOutput.sourceRect.height + / videoOutput.sourceRect.width) || + 0.5625 // 16:9 default + property bool crop: false + + // This rect describes the actual rendered content rectangle + // as the VideoOutput component may use PreserveAspectFit + // (pillarbox/letterbox). + property rect contentRect: videoOutput.contentRect + property real xScale: contentRect.width / videoOutput.sourceRect.width + property real yScale: contentRect.height / videoOutput.sourceRect.height + + onRendererIdChanged: { + videoProvider.unregisterSink(videoSink) + if (rendererId.length !== 0) { + videoProvider.registerSink(rendererId, videoSink) + } + } + + Rectangle { + id: bgRect + anchors.fill: parent + color: "black" + } + + Item { + id: rootUnderlayItem + anchors.fill: parent + } + + VideoOutput { + id: videoOutput + + antialiasing: true + anchors.fill: parent + opacity: videoProvider.activeRenderers[rendererId] !== undefined + visible: opacity + + fillMode: crop ? + VideoOutput.PreserveAspectCrop : + VideoOutput.PreserveAspectFit + + Behavior on opacity { NumberAnimation { duration: 150 } } + + Component.onDestruction: videoProvider.unregisterSink(videoSink) + + layer.enabled: opacity + layer.effect: FastBlur { + source: videoOutput + anchors.fill: root + radius: (1. - opacity) * 100 + } + } +} diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml index 38b7a41df687cdb4559fa76fe063eec60a4073a6..722f829ae09cf238b1c1814ae151b52c4823a73b 100644 --- a/src/constant/JamiStrings.qml +++ b/src/constant/JamiStrings.qml @@ -184,6 +184,7 @@ Item { property string previewUnavailable: qsTr("Preview unavailable") property string screenSharing: qsTr("Screen Sharing") property string selectScreenSharingFPS: qsTr("Select screen sharing frame rate (frames per second)") + property string noVideo: qsTr("no video") // BackupKeyPage property string backupAccountInfos: qsTr("Your account only exists on this device. " + diff --git a/src/distantrenderer.cpp b/src/distantrenderer.cpp deleted file mode 100644 index f98163061dfa0c0926bbd0204b59d84d6a6cb615..0000000000000000000000000000000000000000 --- a/src/distantrenderer.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2019-2022 Savoir-faire Linux Inc. - * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> - * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * - * 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 <http://www.gnu.org/licenses/>. - */ - -#include "distantrenderer.h" - -#include "lrcinstance.h" - -DistantRenderer::DistantRenderer(QQuickItem* parent) - : QQuickPaintedItem(parent) -{ - setAntialiasing(true); - setFillColor(Qt::black); - setRenderTarget(QQuickPaintedItem::FramebufferObject); - setPerformanceHint(QQuickPaintedItem::FastFBOResizing); - - connect(this, &DistantRenderer::lrcInstanceChanged, [this] { - if (lrcInstance_) { - connect(lrcInstance_->renderer(), - &RenderManager::distantFrameUpdated, - [this](const QString& id) { - if (rendererId_ == id) - update(QRect(0, 0, width(), height())); - }); - - connect(lrcInstance_->renderer(), - &RenderManager::distantRenderingStopped, - [this](const QString& id) { - if (rendererId_ == id) - update(QRect(0, 0, width(), height())); - }); - } - }); -} - -DistantRenderer::~DistantRenderer() {} - -void -DistantRenderer::setRendererId(const QString& id) -{ - rendererId_ = id; - // Note: Force a paint to update frame as we change the renderer - update(QRect(0, 0, width(), height())); -} - -QString -DistantRenderer::rendererId() -{ - return rendererId_; -} - -QString -DistantRenderer::takePhoto(int size) -{ - if (auto previewImage = lrcInstance_->renderer()->getPreviewFrame(rendererId_)) { - return Utils::byteArrayToBase64String(Utils::QImageToByteArray(previewImage->copy())); - } - return {}; -} - -int -DistantRenderer::getXOffset() const -{ - return xOffset_; -} - -int -DistantRenderer::getYOffset() const -{ - return yOffset_; -} - -double -DistantRenderer::getScaledWidth() const -{ - return scaledWidth_; -} - -double -DistantRenderer::getScaledHeight() const -{ - return scaledHeight_; -} - -void -DistantRenderer::paint(QPainter* painter) -{ - lrcInstance_->renderer()->drawFrame(rendererId_, [this, painter](QImage* distantImage) { - if (distantImage) { - painter->setRenderHint(QPainter::Antialiasing); - painter->setRenderHint(QPainter::SmoothPixmapTransform); -#if defined(Q_OS_MACOS) - - auto scaledDistant = distantImage - ->scaled(size().toSize(), - Qt::KeepAspectRatio, - Qt::SmoothTransformation) - .rgbSwapped(); -#else - auto scaledDistant = distantImage->scaled(size().toSize(), - Qt::KeepAspectRatio, - Qt::SmoothTransformation); -#endif - auto tempScaledWidth = static_cast<int>(scaledWidth_ * 1000); - auto tempScaledHeight = static_cast<int>(scaledHeight_ * 1000); - auto tempXOffset = xOffset_; - auto tempYOffset = yOffset_; - scaledWidth_ = static_cast<double>(scaledDistant.width()) - / static_cast<double>(distantImage->width()); - scaledHeight_ = static_cast<double>(scaledDistant.height()) - / static_cast<double>(distantImage->height()); - xOffset_ = (width() - scaledDistant.width()) / 2; - yOffset_ = (height() - scaledDistant.height()) / 2; - if (tempXOffset != xOffset_ or tempYOffset != yOffset_ - or static_cast<int>(scaledWidth_ * 1000) != tempScaledWidth - or static_cast<int>(scaledHeight_ * 1000) != tempScaledHeight) { - Q_EMIT offsetChanged(); - } - painter->drawImage(QRect(xOffset_, - yOffset_, - scaledDistant.width(), - scaledDistant.height()), - scaledDistant); - } - }); -} diff --git a/src/distantrenderer.h b/src/distantrenderer.h deleted file mode 100644 index 7474600218955b94698a652f814be4de5f9db83c..0000000000000000000000000000000000000000 --- a/src/distantrenderer.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2020-2022 Savoir-faire Linux Inc. - * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> - * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * - * 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 <http://www.gnu.org/licenses/>. - */ - -#pragma once - -#include <QtQuick> - -class LRCInstance; - -/* - * Use QQuickPaintedItem so that QPainter apis can be used. - * Note: Old video pipeline. - */ -class DistantRenderer : public QQuickPaintedItem -{ - Q_OBJECT - Q_PROPERTY(LRCInstance* lrcInstance MEMBER lrcInstance_ NOTIFY lrcInstanceChanged) - Q_PROPERTY(QString rendererId READ rendererId WRITE setRendererId) - -public: - explicit DistantRenderer(QQuickItem* parent = nullptr); - ~DistantRenderer(); - - Q_INVOKABLE void setRendererId(const QString& id); - Q_INVOKABLE QString rendererId(); - Q_INVOKABLE int getXOffset() const; - Q_INVOKABLE int getYOffset() const; - Q_INVOKABLE double getScaledWidth() const; - Q_INVOKABLE double getScaledHeight() const; - Q_INVOKABLE QString takePhoto(int size); - -Q_SIGNALS: - void offsetChanged(); - void lrcInstanceChanged(); - -protected: - // LRCInstance pointer (set in qml) - LRCInstance* lrcInstance_ {nullptr}; - -private: - void paint(QPainter* painter); - - /* - * Unique DistantRenderId for each call. - */ - QString rendererId_; - int xOffset_ {0}; - int yOffset_ {0}; - double scaledWidth_ {0}; - double scaledHeight_ {0}; -}; diff --git a/src/lrcinstance.cpp b/src/lrcinstance.cpp index 2318d37cf7bee15abe8cdc31d51cb195e5c47ec7..619f506a83b9ba66fde1eb2e9370b26da06f67d8 100644 --- a/src/lrcinstance.cpp +++ b/src/lrcinstance.cpp @@ -33,7 +33,6 @@ LRCInstance::LRCInstance(migrateCallback willMigrateCb, ConnectivityMonitor* connectivityMonitor, bool muteDring) : lrc_(std::make_unique<Lrc>(willMigrateCb, didMigrateCb, muteDring)) - , renderer_(std::make_unique<RenderManager>(lrc_->getAVModel())) , updateManager_(std::make_unique<UpdateManager>(updateUrl, connectivityMonitor, this)) , threadPool_(new QThreadPool(this)) { @@ -68,12 +67,6 @@ LRCInstance::LRCInstance(migrateCallback willMigrateCb, } }; -RenderManager* -LRCInstance::renderer() -{ - return renderer_.get(); -} - UpdateManager* LRCInstance::getUpdateManager() { @@ -400,7 +393,6 @@ LRCInstance::makeConversationPermanent(const QString& convId, const QString& acc void LRCInstance::finish() { - renderer_.reset(); lrc_.reset(); } diff --git a/src/lrcinstance.h b/src/lrcinstance.h index 915a3dc36f582860aa7059624c8ff3da9c3f169b..1421a2a3415a0ac7b4b8c3cb6a59f46ff8bc9813 100644 --- a/src/lrcinstance.h +++ b/src/lrcinstance.h @@ -25,7 +25,6 @@ #endif #include "updatemanager.h" -#include "rendermanager.h" #include "qtutils.h" #include "utils.h" @@ -71,7 +70,6 @@ public: void finish(); - RenderManager* renderer(); UpdateManager* getUpdateManager(); NewAccountModel& accountModel(); @@ -138,7 +136,6 @@ Q_SIGNALS: private: std::unique_ptr<Lrc> lrc_; - std::unique_ptr<RenderManager> renderer_; std::unique_ptr<UpdateManager> updateManager_; QString selectedConvUid_; diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp index 3c526b93b59902a823d3dbcb2c8dedb5defc90b7..8865f834c37187abff6acc136141c280f578295d 100644 --- a/src/mainapplication.cpp +++ b/src/mainapplication.cpp @@ -26,6 +26,7 @@ #include "connectivitymonitor.h" #include "systemtray.h" #include "previewengine.h" +#include "videoprovider.h" #include <QAction> #include <QCommandLineParser> @@ -386,6 +387,9 @@ MainApplication::initQmlLayer() &screenInfo_, this); + auto videoProvider = new VideoProvider(lrcInstance_->avModel(), this); + engine_->rootContext()->setContextProperty("videoProvider", videoProvider); + engine_->load(QUrl(QStringLiteral("qrc:/src/MainApplicationWindow.qml"))); } diff --git a/src/mainview/components/OngoingCallPage.qml b/src/mainview/components/OngoingCallPage.qml index 13f6ded2f3adbecfb2a910d8e958e3cee18bdb3a..0a391aff2454a8b1ff124545f5aab21048d527e9 100644 --- a/src/mainview/components/OngoingCallPage.qml +++ b/src/mainview/components/OngoingCallPage.qml @@ -44,6 +44,13 @@ Rectangle { property alias callId: distantRenderer.rendererId property var linkedWebview: null property string callPreviewId: "" + property bool sharingActive: AvAdapter.currentRenderingDeviceType === Video.DeviceType.DISPLAY + || AvAdapter.currentRenderingDeviceType === Video.DeviceType.FILE + + onSharingActiveChanged: { + const deviceId = AvAdapter.currentRenderingDeviceId + previewRenderer.startWithId(deviceId, true) + } color: "black" @@ -166,36 +173,35 @@ Rectangle { callOverlay.openCallViewContextMenuInPos(mouse.x, mouse.y) } - DistantRenderer { + VideoView { id: distantRenderer anchors.centerIn: parent anchors.fill: parent z: -1 - lrcInstance: LRCInstance visible: !root.isAudioOnly - onOffsetChanged: { + // Update overlays if the internal or visual geometry changes. + // Use Qt.callLater to combine the events in the queue since these + // signals can be emitted together. + property real area: width * height + function updateParticipantsLayer() { callOverlay.participantsLayer.update(CallAdapter.getConferencesInfos()) } + onAreaChanged: Qt.callLater(updateParticipantsLayer) + onContentRectChanged: Qt.callLater(updateParticipantsLayer) } - VideoCallPreviewRenderer { + LocalVideo { id: previewRenderer - lrcInstance: LRCInstance visible: !callOverlay.isAudioOnly && !callOverlay.isConferenceCall && !callOverlay.isVideoMuted && !callOverlay.isPaused && ((VideoDevices.listSize !== 0 && AvAdapter.currentRenderingDeviceType === Video.DeviceType.CAMERA) || AvAdapter.currentRenderingDeviceType !== Video.DeviceType.CAMERA ) rendererId: root.callPreviewId - onVisibleChanged: { - if (!visible && !AccountAdapter.hasVideoCall()) { - VideoDevices.stopDevice(rendererId, true) - } - } - + height: width * invAspectRatio width: Math.max(callPageMainRect.width / 5, JamiTheme.minimumPreviewWidth) x: callPageMainRect.width - previewRenderer.width - previewMargin y: previewMarginYTop @@ -262,13 +268,6 @@ Rectangle { radius: JamiTheme.primaryRadius } } - - onWidthChanged: { - previewRenderer.height = previewRenderer.width * previewImageScalingFactor - } - onPreviewImageScalingFactorChanged: { - previewRenderer.height = previewRenderer.width * previewImageScalingFactor - } } CallOverlay { diff --git a/src/mainview/components/ParticipantsLayer.qml b/src/mainview/components/ParticipantsLayer.qml index 10aa2b1165f0c70117979d16ff4fde1371de383c..301652434fd60f30e76b8cac120f4dfd73587936 100644 --- a/src/mainview/components/ParticipantsLayer.qml +++ b/src/mainview/components/ParticipantsLayer.qml @@ -27,10 +27,10 @@ Item { // returns true if participant is not fully maximized function showMaximize(pX, pY, pW, pH) { // Hack: -1 offset added to avoid problems with odd sizes - return (pX - distantRenderer.getXOffset() !== 0 - || pY - distantRenderer.getYOffset() !== 0 - || pW < (distantRenderer.width - distantRenderer.getXOffset() * 2 - 1) - || pH < (distantRenderer.height - distantRenderer.getYOffset() * 2 - 1)) + return (pX - distantRenderer.contentRect.x !== 0 + || pY - distantRenderer.contentRect.y !== 0 + || pW < (distantRenderer.width - distantRenderer.contentRect.x * 2 - 1) + || pH < (distantRenderer.height - distantRenderer.contentRect.y * 2 - 1)) } function update(infos) { @@ -48,25 +48,13 @@ Item { var participant = infos.find(e => e.uri === participantOverlays[p].uri); if (participant) { // Update participant's information - var newX = Math.trunc(distantRenderer.getXOffset() - + participant.x * distantRenderer.getScaledWidth()) - var newY = Math.trunc(distantRenderer.getYOffset() - + participant.y * distantRenderer.getScaledHeight()) - - var newWidth = Math.ceil(participant.w * distantRenderer.getScaledWidth()) - var newHeight = Math.ceil(participant.h * distantRenderer.getScaledHeight()) - - var newVisible = participant.w !== 0 && participant.h !== 0 - if (participantOverlays[p].x !== newX) - participantOverlays[p].x = newX - if (participantOverlays[p].y !== newY) - participantOverlays[p].y = newY - if (participantOverlays[p].width !== newWidth) - participantOverlays[p].width = newWidth - if (participantOverlays[p].height !== newHeight) - participantOverlays[p].height = newHeight - if (participantOverlays[p].visible !== newVisible) - participantOverlays[p].visible = newVisible + participantOverlays[p].x = Math.trunc(distantRenderer.contentRect.x + + participant.x * distantRenderer.xScale) + participantOverlays[p].y = Math.trunc(distantRenderer.contentRect.y + + participant.y * distantRenderer.yScale) + participantOverlays[p].width = Math.ceil(participant.w * distantRenderer.xScale) + participantOverlays[p].height = Math.ceil(participant.h * distantRenderer.yScale) + participantOverlays[p].visible = participant.w !== 0 && participant.h !== 0 showMax = showMaximize(participantOverlays[p].x, participantOverlays[p].y, @@ -100,13 +88,16 @@ Item { for (var infoVariant in infos) { // Only create overlay for new participants if (!currentUris.includes(infos[infoVariant].uri)) { - var hover = participantComponent.createObject(root, { - x: Math.trunc(distantRenderer.getXOffset() + infos[infoVariant].x * distantRenderer.getScaledWidth()), - y: Math.trunc(distantRenderer.getYOffset() + infos[infoVariant].y * distantRenderer.getScaledHeight()), - width: Math.ceil(infos[infoVariant].w * distantRenderer.getScaledWidth()), - height: Math.ceil(infos[infoVariant].h * distantRenderer.getScaledHeight()), - visible: infos[infoVariant].w !== 0 && infos[infoVariant].h !== 0 - }) + const infoObj = { + x: Math.trunc(distantRenderer.contentRect.x + + infos[infoVariant].x * distantRenderer.xScale), + y: Math.trunc(distantRenderer.contentRect.y + + infos[infoVariant].y * distantRenderer.yScale), + width: Math.ceil(infos[infoVariant].w * distantRenderer.xScale), + height: Math.ceil(infos[infoVariant].h * distantRenderer.yScale), + visible: infos[infoVariant].w !== 0 && infos[infoVariant].h !== 0 + } + var hover = participantComponent.createObject(root, infoObj) if (!hover) { console.log("Error when creating the hover") return diff --git a/src/mainview/components/RecordBox.qml b/src/mainview/components/RecordBox.qml index 9b160957c7eb8f6028506566b6e28c44bb5c06ca..75679e3c64cb756d4d4c586369a74ed327a502ed 100644 --- a/src/mainview/components/RecordBox.qml +++ b/src/mainview/components/RecordBox.qml @@ -58,8 +58,7 @@ Rectangle { updateState(RecordBox.States.INIT) if (isVideo) { - previewWidget.deviceId = VideoDevices.getDefaultDevice() - previewWidget.rendererId = VideoDevices.startDevice(previewWidget.deviceId) + previewWidget.startWithId(VideoDevices.getDefaultDevice()) } } @@ -245,29 +244,19 @@ Rectangle { color: JamiTheme.blackColor radius: 5 - PreviewRenderer { + LocalVideo { id: previewWidget anchors.fill: rectBox - anchors.centerIn: rectBox - property string deviceId: VideoDevices.getDefaultDevice() - rendererId: VideoDevices.getDefaultDevice() + anchors.margins: 1 - lrcInstance: LRCInstance + rendererId: VideoDevices.getDefaultDevice() layer.enabled: true layer.effect: OpacityMask { maskSource: rectBox } } - - onVisibleChanged: { - if (visible) { - previewWidget.deviceId = VideoDevices.getDefaultDevice() - previewWidget.rendererId = VideoDevices.startDevice(previewWidget.deviceId) - } else - VideoDevices.stopDevice(previewWidget.deviceId) - } } Label { diff --git a/src/mainview/components/SelectScreen.qml b/src/mainview/components/SelectScreen.qml index f6e362e7df7f07f461b2f589424a5d94f51b6224..517b47eb4a2f50dee7c0a3eb72c5ac29e29579a9 100644 --- a/src/mainview/components/SelectScreen.qml +++ b/src/mainview/components/SelectScreen.qml @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2022 Savoir-faire Linux Inc. * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> * * 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 @@ -50,11 +50,12 @@ Window { // How many rows the ScrollView should have. function calculateRepeaterModel() { screens = [] - for (var idx in Qt.application.screens) { + var idx + for (idx in Qt.application.screens) { screens.push(qsTr("Screen") + " " + idx) } AvAdapter.getListWindows() - for (var idx in AvAdapter.windowsNames) { + for (idx in AvAdapter.windowsNames) { screens.push(AvAdapter.windowsNames[idx]) } @@ -153,7 +154,7 @@ Window { color: JamiTheme.textColor } - PreviewRenderer { + VideoView { id: screenPreview anchors.top: screenName.bottom @@ -162,17 +163,17 @@ Window { height: screenItem.height - 50 width: screenItem.width - 50 - lrcInstance: LRCInstance - Component.onDestruction: { - if (screenPreview.rendererId !== "" && screenPreview.rendererId !== currentPreview) - VideoDevices.stopDevice(screenPreview.rendererId, true) + if (rendererId !== "" && rendererId !== currentPreview) { + VideoDevices.stopDevice(rendererId, true) + } } Component.onCompleted: { if (visible) { - var rendId = AvAdapter.getSharingResource(index, "") - if (rendId !== "") - screenPreview.rendererId = VideoDevices.startDevice(rendId, true) + const rId = AvAdapter.getSharingResource(index, "") + if (rId !== "") { + rendererId = VideoDevices.startDevice(rId, true) + } } } } @@ -226,7 +227,7 @@ Window { color: JamiTheme.textColor } - PreviewRenderer { + VideoView { id: screenShotAll anchors.top: screenNameAll.bottom @@ -235,17 +236,17 @@ Window { height: screenSelectionRectAll.height - 50 width: screenSelectionRectAll.width - 50 - lrcInstance: LRCInstance - Component.onDestruction: { - if (screenShotAll.rendererId !== "" && screenShotAll.rendererId !== currentPreview) - VideoDevices.stopDevice(screenShotAll.rendererId, true) + if (rendererId !== "" && rendererId !== currentPreview) { + VideoDevices.stopDevice(rendererId, true) + } } Component.onCompleted: { if (visible) { - var rendId = AvAdapter.getSharingResource(-1, "") - if (rendId !== "") - screenShotAll.rendererId = VideoDevices.startDevice(rendId, true) + const rId = AvAdapter.getSharingResource(-1, "") + if (rId !== "") { + rendererId = VideoDevices.startDevice(rId, true) + } } } } @@ -305,7 +306,7 @@ Window { color: JamiTheme.textColor } - PreviewRenderer { + VideoView { id: screenPreview2 anchors.top: screenName2.bottom @@ -316,17 +317,17 @@ Window { height: screenItem2.height - 60 width: screenItem2.width - 50 - lrcInstance: LRCInstance - Component.onDestruction: { - if (screenPreview2.rendererId !== "" && screenPreview2.rendererId !== currentPreview) - VideoDevices.stopDevice(screenPreview2.rendererId, true) + if (rendererId !== "" && rendererId !== currentPreview) { + VideoDevices.stopDevice(rendererId, true) + } } Component.onCompleted: { if (visible) { - var rendId = AvAdapter.getSharingResource(-2, AvAdapter.windowsIds[index - Qt.application.screens.length]) - if (rendId !== "") - screenPreview2.rendererId = VideoDevices.startDevice(rendId, true) + const rId = AvAdapter.getSharingResource(-2, AvAdapter.windowsIds[index - Qt.application.screens.length]) + if (rId !== "") { + rendererId = VideoDevices.startDevice(rId, true) + } } } } diff --git a/src/previewrenderer.cpp b/src/previewrenderer.cpp deleted file mode 100644 index 7c419fa1f536dc90c92eda49f7ea915a9710822a..0000000000000000000000000000000000000000 --- a/src/previewrenderer.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2020-2022 Savoir-faire Linux Inc. - * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> - * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * - * 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 <http://www.gnu.org/licenses/>. - */ - -#include "previewrenderer.h" - -PreviewRenderer::PreviewRenderer(QQuickItem* parent) - : QQuickPaintedItem(parent) -{ - setAntialiasing(true); - setFillColor(Qt::black); - setRenderTarget(QQuickPaintedItem::FramebufferObject); - setPerformanceHint(QQuickPaintedItem::FastFBOResizing); - - connect(this, &PreviewRenderer::lrcInstanceChanged, [this] { - if (lrcInstance_) - previewFrameUpdatedConnection_ = connect(lrcInstance_->renderer(), - &RenderManager::distantFrameUpdated, - [this](const QString& id) { - if (rendererId_ == id && isVisible()) - update(QRect(0, 0, width(), height())); - }); - }); -} - -PreviewRenderer::~PreviewRenderer() -{ - disconnect(previewFrameUpdatedConnection_); -} - -void -PreviewRenderer::paint(QPainter* painter) -{ - lrcInstance_->renderer()->drawFrame(rendererId_, [this, painter](QImage* previewImage) { - if (previewImage) { - painter->setRenderHint(QPainter::Antialiasing); - painter->setRenderHint(QPainter::SmoothPixmapTransform); - - auto aspectRatio = static_cast<qreal>(previewImage->width()) - / static_cast<qreal>(previewImage->height()); - auto previewHeight = aspectRatio < 1 ? height() : width() / aspectRatio; - auto previewWidth = aspectRatio < 1 ? previewHeight * aspectRatio : width(); - - /* Instead of setting fixed size, we could get an x offset for the preview - * but this would render the horizontal spacers in the parent widget useless. - * e.g. - * auto parent = qobject_cast<QWidget*>(this->parent()); - * auto xPos = (parent->width() - previewWidth) / 2; - * setGeometry(QRect(QPoint(xPos, this->pos().y()), - * QSize(previewWidth, previewHeight))); - */ - setWidth(previewWidth); - setHeight(previewHeight); - - // If the given size is empty, this function returns a null image. - QImage scaledPreview; -#if defined(Q_OS_MACOS) - - scaledPreview = previewImage - ->scaled(size().toSize(), - Qt::KeepAspectRatio, - Qt::SmoothTransformation) - .rgbSwapped(); -#else - scaledPreview = previewImage->scaled(size().toSize(), - Qt::KeepAspectRatio, - Qt::SmoothTransformation); -#endif - painter->drawImage(QRect(0, 0, scaledPreview.width(), scaledPreview.height()), - scaledPreview); - } else { - paintBackground(painter); - } - }); -} - -void -PreviewRenderer::paintBackground(QPainter* painter) -{ - QBrush brush(Qt::black); - QPainterPath path; - path.addRect(QRect(0, 0, width(), height())); - painter->fillPath(path, brush); -} - -VideoCallPreviewRenderer::VideoCallPreviewRenderer(QQuickItem* parent) - : PreviewRenderer(parent) -{ - setProperty("previewImageScalingFactor", 1.0); -} - -VideoCallPreviewRenderer::~VideoCallPreviewRenderer() {} - -void -VideoCallPreviewRenderer::paint(QPainter* painter) -{ - lrcInstance_->renderer()->drawFrame(get_rendererId(), [this, painter](QImage* previewImage) { - if (previewImage) { - auto scalingFactor = static_cast<qreal>(previewImage->height()) - / static_cast<qreal>(previewImage->width()); - setProperty("previewImageScalingFactor", scalingFactor); - QImage scaledPreview; -#if defined(Q_OS_MACOS) - scaledPreview = previewImage->scaled(size().toSize(), Qt::KeepAspectRatio).rgbSwapped(); -#else - scaledPreview = previewImage->scaled(size().toSize(), Qt::KeepAspectRatio); -#endif - painter->drawImage(QRect(0, 0, scaledPreview.width(), scaledPreview.height()), - scaledPreview); - } - }); -} - -PhotoboothPreviewRender::PhotoboothPreviewRender(QQuickItem* parent) - : PreviewRenderer(parent) -{ - connect(this, &PreviewRenderer::lrcInstanceChanged, [this] { - if (lrcInstance_) - connect(lrcInstance_->renderer(), - &RenderManager::distantRenderingStopped, - this, - &PhotoboothPreviewRender::renderingStopped, - Qt::UniqueConnection); - }); -} - -QString -PhotoboothPreviewRender::takePhoto(int size) -{ - if (auto previewImage = lrcInstance_->renderer()->getPreviewFrame(get_rendererId())) { -#if defined(Q_OS_MACOS) - return Utils::byteArrayToBase64String( - Utils::QImageToByteArray(previewImage->copy().rgbSwapped())); -#else - return Utils::byteArrayToBase64String(Utils::QImageToByteArray(previewImage->copy())); -#endif - } - return {}; -} - -void -PhotoboothPreviewRender::paint(QPainter* painter) -{ - painter->setRenderHint(QPainter::Antialiasing, true); - - lrcInstance_->renderer()->drawFrame(get_rendererId(), [this, painter](QImage* previewImage) { - if (previewImage) { - QImage scaledPreview; -#if defined(Q_OS_MACOS) - - scaledPreview = Utils::getCirclePhoto(*previewImage, - height() <= width() ? height() : width()) - .rgbSwapped(); -#else - scaledPreview = Utils::getCirclePhoto(*previewImage, - height() <= width() ? height() : width()); -#endif - painter->drawImage(QRect(0, 0, scaledPreview.width(), scaledPreview.height()), - scaledPreview); - } - }); -} diff --git a/src/previewrenderer.h b/src/previewrenderer.h deleted file mode 100644 index 1e78fe087e5bba58abeb81f2ce8d98eb034e4957..0000000000000000000000000000000000000000 --- a/src/previewrenderer.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2020-2022 Savoir-faire Linux Inc. - * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> - * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * - * 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 <https://www.gnu.org/licenses/>. - */ - -#pragma once - -#include <QtQuick> - -#include "lrcinstance.h" - -/* - * Use QQuickPaintedItem so that QPainter apis can be used. - * Note: Old video pipeline. - */ -class PreviewRenderer : public QQuickPaintedItem -{ - Q_OBJECT - Q_PROPERTY(LRCInstance* lrcInstance MEMBER lrcInstance_ NOTIFY lrcInstanceChanged) - QML_PROPERTY(QString, rendererId); - -public: - explicit PreviewRenderer(QQuickItem* parent = nullptr); - virtual ~PreviewRenderer(); - -Q_SIGNALS: - void lrcInstanceChanged(); - -protected: - void paint(QPainter* painter) override; - void paintBackground(QPainter* painter); - - // LRCInstance pointer (set in qml) - LRCInstance* lrcInstance_ {nullptr}; - -private: - QMetaObject::Connection previewFrameUpdatedConnection_; -}; - -class VideoCallPreviewRenderer : public PreviewRenderer -{ - Q_OBJECT - Q_PROPERTY(qreal previewImageScalingFactor MEMBER previewImageScalingFactor_ NOTIFY - previewImageScalingFactorChanged) -public: - explicit VideoCallPreviewRenderer(QQuickItem* parent = nullptr); - ~VideoCallPreviewRenderer(); - -Q_SIGNALS: - void previewImageScalingFactorChanged(); - -private: - void paint(QPainter* painter) override final; - - qreal previewImageScalingFactor_; -}; - -class PhotoboothPreviewRender : public PreviewRenderer -{ - Q_OBJECT -public: - explicit PhotoboothPreviewRender(QQuickItem* parent = nullptr); - ~PhotoboothPreviewRender() = default; - - Q_INVOKABLE QString takePhoto(int size); - -Q_SIGNALS: - void renderingStopped(const QString id); - -private: - void paint(QPainter* painter) override final; -}; diff --git a/src/qmlregister.cpp b/src/qmlregister.cpp index fd4dab1223076cf8ba53796e76693f1393b2248d..527d7ccd2a66b32d9088769ae08b574949613a33 100644 --- a/src/qmlregister.cpp +++ b/src/qmlregister.cpp @@ -48,11 +48,9 @@ #include "avatarregistry.h" #include "appsettingsmanager.h" #include "mainapplication.h" -#include "distantrenderer.h" #include "namedirectory.h" #include "updatemanager.h" #include "pluginlistpreferencemodel.h" -#include "previewrenderer.h" #include "version.h" #include "wizardviewstepmodel.h" @@ -167,12 +165,6 @@ registerTypes(QQmlEngine* engine, QML_REGISTERNAMESPACE(NS_MODELS, FilesToSend::staticMetaObject, "FilesToSend"); QML_REGISTERNAMESPACE(NS_MODELS, MessageList::staticMetaObject, "MessageList"); - // QQuickItems - QML_REGISTERTYPE(NS_MODELS, PreviewRenderer); - QML_REGISTERTYPE(NS_MODELS, VideoCallPreviewRenderer); - QML_REGISTERTYPE(NS_MODELS, DistantRenderer); - QML_REGISTERTYPE(NS_MODELS, PhotoboothPreviewRender) - // Qml singleton components QML_REGISTERSINGLETONTYPE_URL(NS_CONSTANTS, "qrc:/src/constant/JamiTheme.qml", JamiTheme); QML_REGISTERSINGLETONTYPE_URL(NS_MODELS, "qrc:/src/constant/JamiQmlUtils.qml", JamiQmlUtils); diff --git a/src/rendermanager.cpp b/src/rendermanager.cpp deleted file mode 100644 index cea11bc522490d1700a88f8f1d5f2321fa48f6d5..0000000000000000000000000000000000000000 --- a/src/rendermanager.cpp +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2019-2022 Savoir-faire Linux Inc. - * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> - * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * - * 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 <http://www.gnu.org/licenses/>. - */ - -#include "rendermanager.h" - -#include <stdexcept> - -using namespace lrc::api; - -FrameWrapper::FrameWrapper(AVModel& avModel, const QString& id) - : avModel_(avModel) - , id_(id) - , isRendering_(false) -{} - -FrameWrapper::~FrameWrapper() -{ - avModel_.stopPreview(id_); -} - -void -FrameWrapper::connectStartRendering() -{ - QObject::disconnect(renderConnections_.started); - renderConnections_.started = QObject::connect(&avModel_, - &AVModel::rendererStarted, - this, - &FrameWrapper::slotRenderingStarted); -} - -bool -FrameWrapper::startRendering() -{ - if (isRendering()) - return true; - - try { - renderer_ = const_cast<video::Renderer*>(&avModel_.getRenderer(id_)); - } catch (std::out_of_range& e) { - qWarning() << e.what(); - return false; - } - - QObject::disconnect(renderConnections_.updated); - QObject::disconnect(renderConnections_.stopped); - - renderConnections_.updated = QObject::connect(&avModel_, - &AVModel::frameUpdated, - this, - &FrameWrapper::slotFrameUpdated); - - renderConnections_.stopped = QObject::connect(&avModel_, - &AVModel::rendererStopped, - this, - &FrameWrapper::slotRenderingStopped, - Qt::DirectConnection); - - return true; -} - -void -FrameWrapper::stopRendering() -{ - isRendering_ = false; -} - -QImage* -FrameWrapper::getFrame() -{ - if (image_.get()) { - return isRendering_ ? (image_.get()->isNull() ? nullptr : image_.get()) : nullptr; - } - return nullptr; -} - -bool -FrameWrapper::isRendering() -{ - return isRendering_; -} - -bool -FrameWrapper::frameMutexTryLock() -{ - return mutex_.tryLock(); -} - -void -FrameWrapper::frameMutexUnlock() -{ - mutex_.unlock(); -} - -void -FrameWrapper::slotRenderingStarted(const QString& id) -{ - if (id != id_) { - return; - } - - if (!startRendering()) { - qWarning() << "Couldn't start rendering for id: " << id_; - return; - } - - isRendering_ = true; - - Q_EMIT renderingStarted(id); -} - -void -FrameWrapper::slotFrameUpdated(const QString& id) -{ - if (id != id_) { - return; - } - - if (!renderer_ || !renderer_->isRendering()) { - return; - } - - { - QMutexLocker lock(&mutex_); - - frame_ = renderer_->currentFrame(); - - unsigned int width = renderer_->size().width(); - unsigned int height = renderer_->size().height(); - unsigned int size; - QImage::Format imageFormat; - if (renderer_->useDirectRenderer()) { - size = frame_.storage.size(); - imageFormat = QImage::Format_ARGB32_Premultiplied; - } else { - size = frame_.size; - imageFormat = QImage::Format_ARGB32; - } - /* - * If the frame is empty or not the expected size, - * do nothing and keep the last rendered QImage. - */ - if (size != 0 && size == width * height * 4) { - if (renderer_->useDirectRenderer()) { - buffer_ = std::move(frame_.storage); - } else { - // TODO remove this path. storage should work everywhere - // https://git.jami.net/savoirfairelinux/jami-libclient/-/issues/492 - buffer_.resize(size); - std::move(frame_.ptr, frame_.ptr + size, buffer_.begin()); - } - image_.reset(new QImage((uchar*) buffer_.data(), width, height, imageFormat)); - } - } - Q_EMIT frameUpdated(id); -} - -void -FrameWrapper::slotRenderingStopped(const QString& id) -{ - if (id != id_) { - return; - } - isRendering_ = false; - - QObject::disconnect(renderConnections_.updated); - - renderer_ = nullptr; - - { - QMutexLocker lock(&mutex_); - image_.reset(); - } - - Q_EMIT renderingStopped(id); -} - -RenderManager::RenderManager(AVModel& avModel) - : avModel_(avModel) -{} - -RenderManager::~RenderManager() -{ - for (auto& dfw : distantFrameWrapperMap_) { - dfw.second.reset(); - } -} - -void -RenderManager::stopPreviewing(const QString& id) -{ - auto dfwIt = distantFrameWrapperMap_.find(id); - if (dfwIt != distantFrameWrapperMap_.end()) { - dfwIt->second->stopRendering(); - avModel_.stopPreview(id); - } -} - -const QString -RenderManager::startPreviewing(const QString& id, bool force) -{ - auto dfwIt = distantFrameWrapperMap_.find(id); - if (dfwIt != distantFrameWrapperMap_.end()) { - if (dfwIt->second->isRendering() && !force) { - return dfwIt->second->getId(); - } - - if (dfwIt->second->isRendering()) { - avModel_.stopPreview(id); - } - return avModel_.startPreview(id); - } - return ""; -} - -void -RenderManager::addDistantRenderer(const QString& id) -{ - /* - * Check if a FrameWrapper with this id exists. - */ - auto dfwIt = distantFrameWrapperMap_.find(id); - if (dfwIt != distantFrameWrapperMap_.end()) { - if (!dfwIt->second->startRendering()) { - qWarning() << "Couldn't start rendering for id: " << id; - } - } else { - auto dfw = std::make_unique<FrameWrapper>(avModel_, id); - - /* - * Connect this to the FrameWrapper. - */ - distantConnectionMap_[id].updated = QObject::connect(dfw.get(), - &FrameWrapper::frameUpdated, - [this](const QString& id) { - Q_EMIT distantFrameUpdated(id); - }); - distantConnectionMap_[id].stopped = QObject::connect(dfw.get(), - &FrameWrapper::renderingStopped, - [this](const QString& id) { - Q_EMIT distantRenderingStopped(id); - }); - - /* - * Connect FrameWrapper to avmodel. - */ - dfw->connectStartRendering(); - try { - /* - * If the renderer has already started, then start the slot. - */ - if (avModel_.getRenderer(id).isRendering()) - dfw->slotRenderingStarted(id); - } catch (...) { - } - - /* - * Add to map. - */ - distantFrameWrapperMap_.insert(std::make_pair(id, std::move(dfw))); - } -} - -void -RenderManager::removeDistantRenderer(const QString& id) -{ - auto dfwIt = distantFrameWrapperMap_.find(id); - if (dfwIt != distantFrameWrapperMap_.end()) { - /* - * Disconnect FrameWrapper from this. - */ - auto dcIt = distantConnectionMap_.find(id); - if (dcIt != distantConnectionMap_.end()) { - QObject::disconnect(dcIt->second.started); - QObject::disconnect(dcIt->second.updated); - QObject::disconnect(dcIt->second.stopped); - } - - /* - * Erase. - */ - distantFrameWrapperMap_.erase(dfwIt); - } -} - -void -RenderManager::drawFrame(const QString& id, DrawFrameCallback cb) -{ - auto dfwIt = distantFrameWrapperMap_.find(id); - if (dfwIt != distantFrameWrapperMap_.end()) { - if (dfwIt->second->frameMutexTryLock()) { - cb(dfwIt->second->getFrame()); - dfwIt->second->frameMutexUnlock(); - } - } -} - -QImage* -RenderManager::getPreviewFrame(const QString& id) -{ - auto dfwIt = distantFrameWrapperMap_.find(id); - if (dfwIt != distantFrameWrapperMap_.end()) { - return dfwIt->second->getFrame(); - } - return {}; -} diff --git a/src/rendermanager.h b/src/rendermanager.h deleted file mode 100644 index a5e577668fa5b40a9d6a5b8db54aea18ca64bbe8..0000000000000000000000000000000000000000 --- a/src/rendermanager.h +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2019-2022 Savoir-faire Linux Inc. - * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> - * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * - * 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 <https://www.gnu.org/licenses/>. - */ - -#pragma once - -#include "api/avmodel.h" -#include "api/lrc.h" - -#include <QImage> -#include <QMutex> -#include <QObject> - -using namespace lrc::api; - -/* - * This class acts as a QImage rendering sink and manages - * signal/slot connections to it's underlying (AVModel) renderer - * corresponding to the object's renderer id. - * A QImage pointer is provisioned and updated once rendering - * starts. - */ - -struct RenderConnections -{ - QMetaObject::Connection started, stopped, updated; -}; - -class FrameWrapper final : public QObject -{ - Q_OBJECT; - -public: - FrameWrapper(AVModel& avModel, const QString& id); - ~FrameWrapper(); - - /* - * Reconnect the started rendering connection for this object. - */ - void connectStartRendering(); - - /* - * Get a pointer to the renderer and reconnect the update/stopped - * rendering connections for this object. - * @return whether the start succeeded or not - */ - bool startRendering(); - - /* - * Locally disable frame access to this FrameWrapper - */ - void stopRendering(); - - /* - * Get the most recently rendered frame as a QImage. - * @return the rendered image of this object's id - */ - QImage* getFrame(); - - /* - * Check if the object is updating actively. - */ - bool isRendering(); - - bool frameMutexTryLock(); - - void frameMutexUnlock(); - - const QString getId() - { - return id_; - } - -Q_SIGNALS: - /* - * Emitted once in slotRenderingStarted. - * @param id of the renderer - */ - void renderingStarted(const QString& id); - /* - * Emitted each time a frame is ready to be displayed. - * @param id of the renderer - */ - void frameUpdated(const QString& id); - /* - * Emitted once in slotRenderingStopped. - * @param id of the renderer - */ - void renderingStopped(const QString& id); - -public Q_SLOTS: - /* - * Used to listen to AVModel::rendererStarted. - * @param id of the renderer - */ - void slotRenderingStarted(const QString& id); - /* - * Used to listen to AVModel::frameUpdated. - * @param id of the renderer - */ - void slotFrameUpdated(const QString& id); - /* - * Used to listen to AVModel::renderingStopped. - * @param id of the renderer - */ - void slotRenderingStopped(const QString& id); - -private: - /* - * The id of the renderer. - */ - QString id_; - - /* - * A pointer to the lrc renderer object. - */ - video::Renderer* renderer_; - - /* - * A local copy of the renderer's current frame. - */ - video::Frame frame_; - - /* - * A the frame's storage data used to set the image. - */ - std::vector<uint8_t> buffer_; - - /* - * The frame's paint ready QImage. - */ - std::unique_ptr<QImage> image_; - - /* - * Used to protect the buffer during QImage creation routine. - */ - QMutex mutex_; - - /* - * True if the object is rendering - */ - std::atomic_bool isRendering_; - - /* - * Convenience ref to avmodel - */ - AVModel& avModel_; - - /* - * Connections to the underlying renderer signals in avmodel - */ - RenderConnections renderConnections_; -}; - -/** - * RenderManager filters signals and ecapsulates preview and distant - * frame wrappers, providing access to QImages for each and simplified - * start/stop mechanisms for renderers. It should contain as much - * renderer control logic as possible and prevent ui widgets from directly - * interfacing the rendering logic. - */ -class RenderManager final : public QObject -{ - Q_OBJECT; - -public: - explicit RenderManager(AVModel& avModel); - ~RenderManager(); - - using DrawFrameCallback = std::function<void(QImage*)>; - - /* - * Start capturing and rendering preview frames. - * @param force if the capture device should be started - */ - const QString startPreviewing(const QString& id, bool force = false); - /* - * Stop capturing. - */ - void stopPreviewing(const QString& id); - /* - * Add and connect a distant renderer for a given id - * to a FrameWrapper object - * @param id - */ - void addDistantRenderer(const QString& id); - /* - * Disconnect and remove a FrameWrapper object connected to a - * distant renderer for a given id - * @param id - */ - void removeDistantRenderer(const QString& id); - /* - * Frame will be provided in the callback thread safely - * @param id - * @param cb - */ - void drawFrame(const QString& id, DrawFrameCallback cb); - - /* - * Get the most recently rendered preview frame as a QImage (none thread safe). - * @return the rendered preview image - */ - QImage* getPreviewFrame(const QString& id = ""); - -Q_SIGNALS: - /* - * Emitted when a distant renderer has a new frame ready for a given id. - */ - void distantFrameUpdated(const QString& id); - - /* - * Emitted when a distant renderer is stopped for a given id. - */ - void distantRenderingStopped(const QString& id); - -private: - /* - * Distant for each call/conf/conversation. - */ - std::map<QString, std::unique_ptr<FrameWrapper>> distantFrameWrapperMap_; - std::map<QString, RenderConnections> distantConnectionMap_; - - /* - * Convenience ref to avmodel. - */ - AVModel& avModel_; -}; diff --git a/src/settingsview/components/VideoSettings.qml b/src/settingsview/components/VideoSettings.qml index 5545843dd75b1804e4111d06c53afd37223ed801..f03cfb257b50f0271e675e5f673edd53234ea558 100644 --- a/src/settingsview/components/VideoSettings.qml +++ b/src/settingsview/components/VideoSettings.qml @@ -35,30 +35,16 @@ ColumnLayout { property int itemWidth function startPreviewing(force = false) { - if (root.visible) { - previewWidget.deviceId = VideoDevices.getDefaultDevice() - previewWidget.rendererId = VideoDevices.startDevice(previewWidget.deviceId, force) + if (!visible) { + return } - } - - function updatePreviewRatio() { - var resolution = VideoDevices.defaultRes - if (resolution.length !== 0) { - var resVec = resolution.split("x") - var ratio = resVec[1] / resVec[0] - if (ratio) { - aspectRatio = ratio - } else { - console.error("Could not scale recording video preview") - } - } - + const deviceId = VideoDevices.getDefaultDevice() + previewWidget.startWithId(deviceId, force) } onVisibleChanged: { if (visible) { hardwareAccelControl.checked = AvAdapter.getHardwareAcceleration() - updatePreviewRatio() if (previewWidget.visible) startPreviewing(true) } else { @@ -70,14 +56,11 @@ ColumnLayout { target: VideoDevices function onDefaultResChanged() { - updatePreviewRatio() - if (previewWidget.visible) - startPreviewing(true) + startPreviewing(true) } function onDefaultFpsChanged() { - if (previewWidget.visible) - startPreviewing(true) + startPreviewing(true) } function onDeviceAvailable() { @@ -207,12 +190,10 @@ ColumnLayout { // video Preview Rectangle { - id: rectBox - visible: VideoDevices.listSize !== 0 Layout.alignment: Qt.AlignHCenter - Layout.preferredHeight: width * aspectRatio + Layout.preferredHeight: width * previewWidget.invAspectRatio Layout.minimumWidth: 200 Layout.maximumWidth: 400 @@ -221,28 +202,19 @@ ColumnLayout { color: JamiTheme.primaryForegroundColor - DistantRenderer { + LocalVideo { id: previewWidget - anchors.fill: rectBox - property string deviceId: VideoDevices.getDefaultDevice() - rendererId: VideoDevices.getDefaultDevice() + anchors.fill: parent - lrcInstance: LRCInstance - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: rectBox + underlayItems: Text { + anchors.centerIn: parent + font.pointSize: 18 + font.capitalization: Font.AllUppercase + color: "white" + text: JamiStrings.noVideo } } - - onVisibleChanged: { - if (visible) { - VideoDevices.stopDevice(previewWidget.deviceId) - startPreviewing(true) - } else - VideoDevices.stopDevice(previewWidget.deviceId) - } } Label { diff --git a/src/videodevices.cpp b/src/videodevices.cpp index 5cf90e76d9bc90343e301ce82c0a5d2232004c36..ff962588e4a4fbd1ebccf67ab94d8b18c120602c 100644 --- a/src/videodevices.cpp +++ b/src/videodevices.cpp @@ -174,10 +174,8 @@ VideoFormatFpsModel::roleNames() const int VideoFormatFpsModel::getCurrentIndex() const { - QString currentDeviceId = videoDevices_->get_defaultId(); float currentFps = videoDevices_->get_defaultFps(); auto resultList = match(index(0, 0), FPS, QVariant(currentFps)); - return resultList.size() > 0 ? resultList[0].row() : 0; } @@ -291,21 +289,26 @@ VideoDevices::getDefaultDevice() } QString -VideoDevices::startDevice(const QString& deviceId, bool force) +VideoDevices::startDevice(const QString& id, bool force) { - if (deviceId.isEmpty()) + if (id.isEmpty()) return {}; - lrcInstance_->renderer()->addDistantRenderer(deviceId); + auto& avModel = lrcInstance_->avModel(); + if (avModel.hasRenderer(id)) { + if (!force) { + return id; + } + avModel.stopPreview(id); + } deviceOpen_ = true; - return lrcInstance_->renderer()->startPreviewing(deviceId, force); + return avModel.startPreview(id); } void -VideoDevices::stopDevice(const QString& deviceId, bool force) +VideoDevices::stopDevice(const QString& id, bool force) { - if (!deviceId.isEmpty() && (!lrcInstance_->hasActiveCall(true) || force)) { - lrcInstance_->renderer()->stopPreviewing(deviceId); - lrcInstance_->renderer()->removeDistantRenderer(deviceId); + if (!id.isEmpty() && (!lrcInstance_->hasActiveCall(true) || force)) { + lrcInstance_->avModel().stopPreview(id); deviceOpen_ = false; } } @@ -481,17 +484,6 @@ VideoDevices::onVideoDeviceEvent() Q_EMIT deviceListChanged(currentDeviceListSize); } else if (deviceOpen_) { updateData(); - - // Use QueuedConnection to make sure that it happens at the event loop of current device - Utils::oneShotConnect( - lrcInstance_->renderer(), - &RenderManager::distantRenderingStopped, - this, - [this, cb](const QString& id) { - if (this->getDefaultDevice() == id) - cb(); - }, - Qt::QueuedConnection); } else { cb(); } diff --git a/src/videoprovider.cpp b/src/videoprovider.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c3d4f0e0e82642a32826703fbffa0d03032976a5 --- /dev/null +++ b/src/videoprovider.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "videoprovider.h" + +using namespace lrc::api; + +static bool +mapVideoFrame(QVideoFrame* videoFrame) +{ + if (!videoFrame || !videoFrame->isValid() + || (!videoFrame->isMapped() && !videoFrame->map(QVideoFrame::WriteOnly))) { + return false; + } + return true; +} + +VideoProvider::VideoProvider(AVModel& avModel, QObject* parent) + : QObject(parent) + , avModel_(avModel) +{ + connect(&avModel_, &AVModel::rendererStarted, this, &VideoProvider::onRendererStarted); + connect(&avModel_, &AVModel::frameBufferRequested, this, &VideoProvider::onFrameBufferRequested); + connect(&avModel_, &AVModel::frameUpdated, this, &VideoProvider::onFrameUpdated); + connect(&avModel_, &AVModel::rendererStopped, this, &VideoProvider::onRendererStopped); +} + +void +VideoProvider::registerSink(const QString& id, QVideoSink* obj) +{ + QMutexLocker lk(&framesObjsMutex_); + auto it = framesObjects_.find(id); + if (it == framesObjects_.end()) { + auto fo = std::make_unique<FrameObject>(); + fo->subscribers.insert(obj); + framesObjects_.emplace(id, std::move(fo)); + return; + } + it->second->subscribers.insert(obj); +} + +void +VideoProvider::unregisterSink(QVideoSink* obj) +{ + QMutexLocker lk(&framesObjsMutex_); + for (auto& frameObjIt : qAsConst(framesObjects_)) { + auto& subs = frameObjIt.second->subscribers; + auto it = subs.constFind(obj); + if (it != subs.cend()) { + subs.erase(it); + } + } +} + +QString +VideoProvider::captureVideoFrame(QVideoSink* obj) +{ + QImage img; + QVideoFrame currentFrame = obj->videoFrame(); + FrameObject* frameObj {nullptr}; + QMutexLocker lk(&framesObjsMutex_); + for (auto& frameObjIt : qAsConst(framesObjects_)) { + auto& subs = frameObjIt.second->subscribers; + auto it = subs.constFind(obj); + if (it != subs.cend()) { + frameObj = frameObjIt.second.get(); + } + } + if (frameObj) { + QMutexLocker lk(&frameObj->mutex); + auto imageFormat = QVideoFrameFormat::imageFormatFromPixelFormat( + QVideoFrameFormat::Format_RGBA8888); + img = QImage(currentFrame.bits(0), + currentFrame.width(), + currentFrame.height(), + currentFrame.bytesPerLine(0), + imageFormat); + } + return Utils::byteArrayToBase64String(Utils::QImageToByteArray(img)); +} + +void +VideoProvider::onRendererStarted(const QString& id) +{ + auto size = avModel_.getRendererSize(id); + // This slot is queued, the renderer may have been destroyed. + if (size.width() == 0 || size.height() == 0) { + return; + } + auto pixelFormat = avModel_.useDirectRenderer() ? QVideoFrameFormat::Format_RGBA8888 + : QVideoFrameFormat::Format_BGRA8888; + auto frameFormat = QVideoFrameFormat(size, pixelFormat); + { + QMutexLocker lk(&framesObjsMutex_); + auto it = framesObjects_.find(id); + if (it == framesObjects_.end()) { + auto fo = std::make_unique<FrameObject>(); + fo->videoFrame = std::make_unique<QVideoFrame>(frameFormat); + framesObjects_.emplace(id, std::move(fo)); + } else { + it->second->videoFrame.reset(new QVideoFrame(frameFormat)); + } + } + + activeRenderers_[id] = size; + Q_EMIT activeRenderersChanged(); +} + +void +VideoProvider::onFrameBufferRequested(const QString& id, AVFrame* avframe) +{ + QMutexLocker framesLk(&framesObjsMutex_); + auto it = framesObjects_.find(id); + if (it == framesObjects_.end()) { + return; + } + if (it->second->subscribers.empty()) { + return; + } + QMutexLocker lk(&it->second->mutex); + auto videoFrame = it->second->videoFrame.get(); + if (!mapVideoFrame(videoFrame)) { + qWarning() << "QVideoFrame can't be mapped" << id; + return; + } + // The ownership of avframe structure remains the subscriber(jamid), and + // the videoFrame instance is owned by the VideoProvider(client). The + // avframe structure contains only a description of the QVideoFrame + // underlying buffer. + // TODO: ideally, the colorspace format should likely come from jamid and + // be the decoded format. + avframe->format = AV_PIX_FMT_RGBA; + avframe->width = videoFrame->width(); + avframe->height = videoFrame->height(); + avframe->data[0] = (uint8_t*) videoFrame->bits(0); + avframe->linesize[0] = videoFrame->bytesPerLine(0); +} + +void +VideoProvider::onFrameUpdated(const QString& id) +{ + QMutexLocker framesLk(&framesObjsMutex_); + auto it = framesObjects_.find(id); + if (it == framesObjects_.end()) { + return; + } + if (it->second->subscribers.empty()) { + return; + } + QMutexLocker lk(&it->second->mutex); + auto videoFrame = it->second->videoFrame.get(); + if (videoFrame == nullptr) { + qWarning() << "QVideoFrame has not been initialized."; + return; + } + if (!avModel_.useDirectRenderer()) { + // Shared memory renderering. + if (!mapVideoFrame(videoFrame)) { + qWarning() << "QVideoFrame can't be mapped" << id; + return; + } + auto frame = avModel_.getRendererFrame(id); + std::memcpy(videoFrame->bits(0), frame.ptr, frame.size); + } + if (videoFrame->isMapped()) { + videoFrame->unmap(); + for (const auto& sink : qAsConst(it->second->subscribers)) { + sink->setVideoFrame(*videoFrame); + Q_EMIT sink->videoFrameChanged(*videoFrame); + } + } +} + +void +VideoProvider::onRendererStopped(const QString& id) +{ + QMutexLocker framesLk(&framesObjsMutex_); + auto it = framesObjects_.find(id); + if (it == framesObjects_.end()) { + return; + } + + activeRenderers_.remove(id); + Q_EMIT activeRenderersChanged(); + + QMutexLocker lk(&it->second->mutex); + if (it->second->subscribers.empty()) { + lk.unlock(); + framesObjects_.erase(it); + return; + } + it->second->videoFrame.reset(); +} diff --git a/src/videoprovider.h b/src/videoprovider.h new file mode 100644 index 0000000000000000000000000000000000000000..02d5a9b9b0422b55a6fb8382da5d2aeb1a36a0e8 --- /dev/null +++ b/src/videoprovider.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * 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 <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "utils.h" +#include "qtutils.h" + +#include "api/avmodel.h" + +extern "C" { +#include "libavutil/frame.h" +} + +#include <QVideoSink> +#include <QVideoFrame> +#include <QQmlEngine> +#include <QMutex> + +using namespace lrc::api; + +class VideoProvider final : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_PROPERTY(QVariantMap, activeRenderers) +public: + explicit VideoProvider(AVModel& avModel, QObject* parent = nullptr); + ~VideoProvider() = default; + + Q_INVOKABLE void registerSink(const QString& id, QVideoSink* obj); + Q_INVOKABLE void unregisterSink(QVideoSink* obj); + Q_INVOKABLE QString captureVideoFrame(QVideoSink* obj); + +private Q_SLOTS: + void onRendererStarted(const QString& id); + void onFrameBufferRequested(const QString& id, AVFrame* avframe); + void onFrameUpdated(const QString& id); + void onRendererStopped(const QString& id); + +private: + AVModel& avModel_; + + struct FrameObject + { + std::unique_ptr<QVideoFrame> videoFrame; + QMutex mutex; + QSet<QVideoSink*> subscribers; + }; + std::map<QString, std::unique_ptr<FrameObject>> framesObjects_; + QMutex framesObjsMutex_; +};