mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-07-23 00:45:29 +02:00
video: use QVideoSink/VideoOutput and QVideoFrame instead of QImage
Removes the rendermanager and framewrapper objects along with any QQuickPaintedItem-based QML render widget classes. This simplifies the video widget stack implementation. The new mechanism uses the VideoOutput component of QtMultimedia. By accessing the VideoOutput's QVideoSink object, we update the mapped buffer data of a sink's QVideoFrame when new frames are published. Updates to frames and component sink subscriptions are managed by a new class called VideoProvider. Gitlab: #500 Also https://git.jami.net/savoirfairelinux/jami-client-qt/-/issues/536 Change-Id: I2391a32294922ea435ab80ac1f876c004ff6c21e
This commit is contained in:
parent
a5adab3f58
commit
e7cc0497ce
27 changed files with 552 additions and 1250 deletions
|
@ -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
|
||||
|
|
2
qml.qrc
2
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>
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
45
src/commoncomponents/LocalVideo.qml
Normal file
45
src/commoncomponents/LocalVideo.qml
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
82
src/commoncomponents/VideoView.qml
Normal file
82
src/commoncomponents/VideoView.qml
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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. " +
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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};
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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")));
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -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 {};
|
||||
}
|
|
@ -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_;
|
||||
};
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
208
src/videoprovider.cpp
Normal file
208
src/videoprovider.cpp
Normal file
|
@ -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();
|
||||
}
|
67
src/videoprovider.h
Normal file
67
src/videoprovider.h
Normal file
|
@ -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_;
|
||||
};
|
Loading…
Add table
Reference in a new issue