diff --git a/CMakeLists.txt b/CMakeLists.txt
index 679c861f..a7de6d5a 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 ce5c7ae7..9dca240a 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -5,6 +5,8 @@
src/constant/JamiQmlUtils.qml
src/constant/JamiStrings.qml
src/constant/JamiTheme.qml
+ src/commoncomponents/VideoView.qml
+ src/commoncomponents/LocalVideo.qml
src/commoncomponents/SettingParaCombobox.qml
src/commoncomponents/PreferenceItemDelegate.qml
src/commoncomponents/PasswordDialog.qml
diff --git a/src/avadapter.cpp b/src/avadapter.cpp
index 94c73db1..c46836e9 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 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 c10e725e..a21dfad3 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, 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 27c23404..3f4c1773 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 00000000..caca6c08
--- /dev/null
+++ b/src/commoncomponents/LocalVideo.qml
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Andreas Traczyk
+ *
+ * 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 .
+ */
+
+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 c9b7137b..828ab91f 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 00000000..ecfb5c3d
--- /dev/null
+++ b/src/commoncomponents/VideoView.qml
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Andreas Traczyk
+ *
+ * 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 .
+ */
+
+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 38b7a41d..722f829a 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 f9816306..00000000
--- a/src/distantrenderer.cpp
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2019-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk
- * Author: Mingrui Zhang
- *
- * 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 "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(scaledWidth_ * 1000);
- auto tempScaledHeight = static_cast(scaledHeight_ * 1000);
- auto tempXOffset = xOffset_;
- auto tempYOffset = yOffset_;
- scaledWidth_ = static_cast(scaledDistant.width())
- / static_cast(distantImage->width());
- scaledHeight_ = static_cast(scaledDistant.height())
- / static_cast(distantImage->height());
- xOffset_ = (width() - scaledDistant.width()) / 2;
- yOffset_ = (height() - scaledDistant.height()) / 2;
- if (tempXOffset != xOffset_ or tempYOffset != yOffset_
- or static_cast(scaledWidth_ * 1000) != tempScaledWidth
- or static_cast(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 74746002..00000000
--- a/src/distantrenderer.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2020-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk
- * Author: Mingrui Zhang
- *
- * 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 .
- */
-
-#pragma once
-
-#include
-
-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 2318d37c..619f506a 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(willMigrateCb, didMigrateCb, muteDring))
- , renderer_(std::make_unique(lrc_->getAVModel()))
, updateManager_(std::make_unique(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 915a3dc3..1421a2a3 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_;
- std::unique_ptr renderer_;
std::unique_ptr updateManager_;
QString selectedConvUid_;
diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp
index 3c526b93..8865f834 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
#include
@@ -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 13f6ded2..0a391aff 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 10aa2b11..30165243 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 9b160957..75679e3c 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 f6e362e7..517b47eb 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
- * Aline Gondim Santos
+ * Author: Aline Gondim Santos
*
* 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 7c419fa1..00000000
--- a/src/previewrenderer.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2020-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk
- * Author: Mingrui Zhang
- *
- * 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 "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(previewImage->width())
- / static_cast(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(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(previewImage->height())
- / static_cast(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 1e78fe08..00000000
--- a/src/previewrenderer.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2020-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk
- * Author: Mingrui Zhang
- *
- * 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 .
- */
-
-#pragma once
-
-#include
-
-#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 fd4dab12..527d7ccd 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 cea11bc5..00000000
--- a/src/rendermanager.cpp
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2019-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk
- * Author: Mingrui Zhang
- *
- * 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 "rendermanager.h"
-
-#include
-
-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(&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(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 a5e57766..00000000
--- a/src/rendermanager.h
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2019-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk
- * Author: Mingrui Zhang
- *
- * 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 .
- */
-
-#pragma once
-
-#include "api/avmodel.h"
-#include "api/lrc.h"
-
-#include
-#include
-#include
-
-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 buffer_;
-
- /*
- * The frame's paint ready QImage.
- */
- std::unique_ptr 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;
-
- /*
- * 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> distantFrameWrapperMap_;
- std::map distantConnectionMap_;
-
- /*
- * Convenience ref to avmodel.
- */
- AVModel& avModel_;
-};
diff --git a/src/settingsview/components/VideoSettings.qml b/src/settingsview/components/VideoSettings.qml
index 5545843d..f03cfb25 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 5cf90e76..ff962588 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 00000000..c3d4f0e0
--- /dev/null
+++ b/src/videoprovider.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Andreas Traczyk
+ *
+ * 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 "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();
+ 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();
+ fo->videoFrame = std::make_unique(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 00000000..02d5a9b9
--- /dev/null
+++ b/src/videoprovider.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Andreas Traczyk
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#include "utils.h"
+#include "qtutils.h"
+
+#include "api/avmodel.h"
+
+extern "C" {
+#include "libavutil/frame.h"
+}
+
+#include
+#include
+#include
+#include
+
+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 videoFrame;
+ QMutex mutex;
+ QSet subscribers;
+ };
+ std::map> framesObjects_;
+ QMutex framesObjsMutex_;
+};