Windows screenshare/video call support, general call improvements (#1725)

* Initial support for d3d11 qml video item

* Windows screenshare support, D3D11

* misc fixes

* further window visibility checks

* preview updates

* fix qml preview

* fix compositor

* add libnice plugin dep

* re-run formatter

* final formatter fix [skip ci]

* fix tumbleweed build

---------

Co-authored-by: Joseph Donofry <rubberduckie3554@gmail.com>
This commit is contained in:
checkraisefold 2024-05-10 12:22:58 -07:00 committed by GitHub
parent cc75c930fc
commit e7d28b96da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 252 additions and 65 deletions

View file

@ -25,8 +25,8 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "macos deployment target")
option(HUNTER_ENABLED "Enable Hunter package manager" OFF) option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
include("cmake/HunterGate.cmake") include("cmake/HunterGate.cmake")
HunterGate( HunterGate(
URL "https://github.com/cpp-pm/hunter/archive/v0.25.2.tar.gz" URL "https://github.com/cpp-pm/hunter/archive/v0.25.5.tar.gz"
SHA1 "560c000d5b6c972d41c2caf44f24189c868cc404" SHA1 "a20151e4c0740ee7d0f9994476856d813cdead29"
LOCAL LOCAL
) )
@ -618,6 +618,9 @@ endif()
if(VOIP) if(VOIP)
include(FindPkgConfig) include(FindPkgConfig)
pkg_check_modules(GSTREAMER REQUIRED IMPORTED_TARGET gstreamer-sdp-1.0>=1.18 gstreamer-webrtc-1.0>=1.18 gstreamer-gl-1.0) pkg_check_modules(GSTREAMER REQUIRED IMPORTED_TARGET gstreamer-sdp-1.0>=1.18 gstreamer-webrtc-1.0>=1.18 gstreamer-gl-1.0)
if (WIN32)
pkg_check_modules(GSTREAMER REQUIRED IMPORTED_TARGET gstreamer-d3d11-1.0)
endif()
endif() endif()
if(X11 AND NOT WIN32 AND NOT APPLE AND NOT HAIKU) if(X11 AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
@ -807,6 +810,7 @@ set(QML_SOURCES
resources/qml/voip/PlaceCall.qml resources/qml/voip/PlaceCall.qml
resources/qml/voip/ScreenShare.qml resources/qml/voip/ScreenShare.qml
resources/qml/voip/VideoCall.qml resources/qml/voip/VideoCall.qml
resources/qml/voip/VideoCallD3D11.qml
resources/qml/delegates/EncryptionEnabled.qml resources/qml/delegates/EncryptionEnabled.qml
resources/qml/ui/TimelineEffects.qml resources/qml/ui/TimelineEffects.qml
) )
@ -839,6 +843,7 @@ qt_add_translations(nheko RESOURCE_PREFIX "/translations" TS_FILES
if(WIN32) if(WIN32)
target_compile_definitions(nheko PRIVATE WIN32_LEAN_AND_MEAN) target_compile_definitions(nheko PRIVATE WIN32_LEAN_AND_MEAN)
target_link_libraries(nheko PRIVATE Qt6::DBus)
if(MSVC) if(MSVC)
target_compile_options(nheko PUBLIC "/Zc:__cplusplus") target_compile_options(nheko PUBLIC "/Zc:__cplusplus")
endif() endif()

View file

@ -119,7 +119,7 @@ Item {
searchString: topBar.searchString searchString: topBar.searchString
} }
Loader { Loader {
source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? "voip/VideoCall.qml" : "" source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? (Qt.platform.os != "windows" ? "voip/VideoCall.qml" : "voip/VideoCallD3D11.qml") : ""
onLoaded: TimelineManager.setVideoCallItem() onLoaded: TimelineManager.setVideoCallItem()
} }

View file

@ -63,7 +63,7 @@ Popup {
} }
ComboBox { ComboBox {
visible: CallManager.screenShareType == Voip.X11 visible: CallManager.screenShareType == Voip.X11 || CallManager.screenShareType == Voip.D3D11
id: windowCombo id: windowCombo
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import org.freedesktop.gstreamer.Qt6D3D11VideoItem 1.0
GstD3D11Qt6VideoItem {
objectName: "videoCallItem"
}

View file

@ -34,6 +34,10 @@
#include <xcb/xcb_ewmh.h> #include <xcb/xcb_ewmh.h>
#endif #endif
#ifdef Q_OS_WINDOWS
#include <Windows.h>
#endif
#ifdef GSTREAMER_AVAILABLE #ifdef GSTREAMER_AVAILABLE
extern "C" extern "C"
{ {
@ -82,12 +86,12 @@ CallManager::CallManager(QObject *parent)
{ {
#ifdef GSTREAMER_AVAILABLE #ifdef GSTREAMER_AVAILABLE
std::string errorMessage; std::string errorMessage;
if (session_.havePlugins(true, true, ScreenShareType::XDP, &errorMessage)) {
screenShareTypes_.push_back(ScreenShareType::XDP);
screenShareType_ = ScreenShareType::XDP;
}
if (std::getenv("DISPLAY")) { if (QGuiApplication::platformName() == QStringLiteral("windows") &&
session_.havePlugins(true, true, ScreenShareType::D3D11, &errorMessage)) {
screenShareType_ = ScreenShareType::D3D11;
screenShareTypes_.push_back(ScreenShareType::D3D11);
} else if (std::getenv("DISPLAY")) {
screenShareTypes_.push_back(ScreenShareType::X11); screenShareTypes_.push_back(ScreenShareType::X11);
if (QGuiApplication::platformName() != QStringLiteral("wayland")) { if (QGuiApplication::platformName() != QStringLiteral("wayland")) {
// Selected by default // Selected by default
@ -96,6 +100,12 @@ CallManager::CallManager(QObject *parent)
std::swap(screenShareTypes_[0], screenShareTypes_[1]); std::swap(screenShareTypes_[0], screenShareTypes_[1]);
} }
} }
if (QGuiApplication::platformName() != QStringLiteral("windows") &&
session_.havePlugins(true, true, ScreenShareType::XDP, &errorMessage)) {
screenShareTypes_.push_back(ScreenShareType::XDP);
screenShareType_ = ScreenShareType::XDP;
}
#endif #endif
connect( connect(
@ -254,7 +264,8 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w
#ifdef GSTREAMER_AVAILABLE #ifdef GSTREAMER_AVAILABLE
if (callType == CallType::SCREEN) { if (callType == CallType::SCREEN) {
if (screenShareType_ == ScreenShareType::X11) { if (screenShareType_ == ScreenShareType::X11 ||
screenShareType_ == ScreenShareType::D3D11) {
if (windows_.empty() || windowIndex >= windows_.size()) { if (windows_.empty() || windowIndex >= windows_.size()) {
nhlog::ui()->error("WebRTC: window index out of range"); nhlog::ui()->error("WebRTC: window index out of range");
return; return;
@ -270,8 +281,8 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w
#endif #endif
if (haveCallInvite_) { if (haveCallInvite_) {
nhlog::ui()->debug( nhlog::ui()->debug("WebRTC: Discarding outbound call for inbound call. "
"WebRTC: Discarding outbound call for inbound call. localUser is polite party"); "localUser is polite party");
if (callParty_ == callee->user_id) { if (callParty_ == callee->user_id) {
if (callType == callType_) if (callType == callType_)
acceptInvite(); acceptInvite();
@ -305,7 +316,8 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w
playRingtone(QUrl(QStringLiteral("qrc:/media/media/ringback.ogg")), true); playRingtone(QUrl(QStringLiteral("qrc:/media/media/ringback.ogg")), true);
uint32_t shareWindowId = uint32_t shareWindowId =
callType == CallType::SCREEN && screenShareType_ == ScreenShareType::X11 callType == CallType::SCREEN &&
(screenShareType_ == ScreenShareType::X11 || screenShareType_ == ScreenShareType::D3D11)
? windows_[windowIndex].second ? windows_[windowIndex].second
: 0; : 0;
if (!session_.createOffer(callType, screenShareType_, shareWindowId)) { if (!session_.createOffer(callType, screenShareType_, shareWindowId)) {
@ -337,7 +349,7 @@ callHangUpReasonString(CallHangUp::Reason reason)
return "User"; return "User";
} }
} }
} } // namespace
void void
CallManager::hangUp(CallHangUp::Reason reason) CallManager::hangUp(CallHangUp::Reason reason)
@ -511,8 +523,8 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
void void
CallManager::acceptInvite() CallManager::acceptInvite()
{ {
// if call was accepted/rejected elsewhere and m.call.select_answer is received // if call was accepted/rejected elsewhere and m.call.select_answer is
// before acceptInvite // received before acceptInvite
if (!haveCallInvite_) if (!haveCallInvite_)
return; return;
@ -699,7 +711,8 @@ CallManager::handleEvent(const RoomEvent<CallReject> &callRejectEvent)
if (callRejectEvent.content.call_id == callid_) { if (callRejectEvent.content.call_id == callid_) {
if (session_.state() == webrtc::State::OFFERSENT) { if (session_.state() == webrtc::State::OFFERSENT) {
// only accept reject if webrtc is in OFFERSENT state, else call has been accepted // only accept reject if webrtc is in OFFERSENT state, else call has been
// accepted
emit newMessage( emit newMessage(
roomid_, roomid_,
CallSelectAnswer{ CallSelectAnswer{
@ -861,7 +874,7 @@ bool
CallManager::screenShareReady() const CallManager::screenShareReady() const
{ {
#ifdef GSTREAMER_AVAILABLE #ifdef GSTREAMER_AVAILABLE
if (screenShareType_ == ScreenShareType::X11) { if (screenShareType_ == ScreenShareType::X11 || screenShareType_ == ScreenShareType::D3D11) {
return true; return true;
} else { } else {
return ScreenCastPortal::instance().ready(); return ScreenCastPortal::instance().ready();
@ -875,12 +888,15 @@ QStringList
CallManager::screenShareTypeList() CallManager::screenShareTypeList()
{ {
QStringList ret; QStringList ret;
ret.reserve(2); ret.reserve(3);
for (ScreenShareType type : screenShareTypes_) { for (ScreenShareType type : screenShareTypes_) {
switch (type) { switch (type) {
case ScreenShareType::X11: case ScreenShareType::X11:
ret.append(tr("X11")); ret.append(tr("X11"));
break; break;
case ScreenShareType::D3D11:
ret.append("DirectX 11");
break;
case ScreenShareType::XDP: case ScreenShareType::XDP:
ret.append(tr("PipeWire")); ret.append(tr("PipeWire"));
break; break;
@ -893,8 +909,10 @@ CallManager::screenShareTypeList()
QStringList QStringList
CallManager::windowList() CallManager::windowList()
{ {
if (!(std::find(screenShareTypes_.begin(), screenShareTypes_.end(), ScreenShareType::X11) != if (std::find(screenShareTypes_.begin(), screenShareTypes_.end(), ScreenShareType::X11) ==
screenShareTypes_.end())) { screenShareTypes_.end() &&
std::find(screenShareTypes_.begin(), screenShareTypes_.end(), ScreenShareType::D3D11) ==
screenShareTypes_.end()) {
return {}; return {};
} }
@ -949,6 +967,32 @@ CallManager::windowList()
} }
xcb_ewmh_get_windows_reply_wipe(&clients); xcb_ewmh_get_windows_reply_wipe(&clients);
} }
#endif
#ifdef Q_OS_WINDOWS
for (HWND windowHandle = GetTopWindow(nullptr); windowHandle != nullptr;
windowHandle = GetNextWindow(windowHandle, GW_HWNDNEXT)) {
if (!IsWindowVisible(windowHandle))
continue;
int titleLength = GetWindowTextLengthW(windowHandle);
if (titleLength == 0)
continue;
if (GetWindowLong(windowHandle, GWL_EXSTYLE) & WS_EX_TOOLWINDOW)
continue;
TITLEBARINFO titleInfo;
titleInfo.cbSize = sizeof(titleInfo);
GetTitleBarInfo(windowHandle, &titleInfo);
if (titleInfo.rgstate[0] & STATE_SYSTEM_INVISIBLE)
continue;
wchar_t *windowTitle = new wchar_t[titleLength + 1];
GetWindowTextW(windowHandle, windowTitle, titleLength + 1);
windows_.push_back(
{QString::fromWCharArray(windowTitle), reinterpret_cast<uint64_t>(windowHandle)});
}
#endif #endif
QStringList ret; QStringList ret;
assert(windows_.size() < std::numeric_limits<int>::max()); assert(windows_.size() < std::numeric_limits<int>::max());
@ -1007,11 +1051,13 @@ make_preview_sink()
{ {
if (QGuiApplication::platformName() == QStringLiteral("wayland")) { if (QGuiApplication::platformName() == QStringLiteral("wayland")) {
return gst_element_factory_make("waylandsink", nullptr); return gst_element_factory_make("waylandsink", nullptr);
} else if (QGuiApplication::platformName() == QStringLiteral("windows")) {
return gst_element_factory_make("d3d11videosink", nullptr);
} else { } else {
return gst_element_factory_make("ximagesink", nullptr); return gst_element_factory_make("ximagesink", nullptr);
} }
} }
} } // namespace
#endif #endif
void void
@ -1032,6 +1078,12 @@ CallManager::previewWindow(unsigned int index) const
return; return;
} }
if (screenShareType_ == ScreenShareType::D3D11 &&
(windows_.empty() || index >= windows_.size())) {
nhlog::ui()->error("D3D11 screencast not available");
return;
}
auto settings = ChatPage::instance()->userSettings(); auto settings = ChatPage::instance()->userSettings();
pipe_ = gst_pipeline_new(nullptr); pipe_ = gst_pipeline_new(nullptr);
@ -1066,6 +1118,19 @@ CallManager::previewWindow(unsigned int index) const
gst_bin_add(GST_BIN(pipe_), ximagesrc); gst_bin_add(GST_BIN(pipe_), ximagesrc);
screencastsrc = ximagesrc; screencastsrc = ximagesrc;
} else if (screenShareType_ == ScreenShareType::D3D11) {
GstElement *d3d11screensrc = gst_element_factory_make("d3d11screencapturesrc", nullptr);
if (!d3d11screensrc) {
nhlog::ui()->error("Failed to create d3d11screencapturesrc");
gst_object_unref(pipe_);
pipe_ = nullptr;
return;
}
g_object_set(d3d11screensrc, "window-handle", windows_[index].second, nullptr);
g_object_set(d3d11screensrc, "show-cursor", !settings->screenShareHideCursor(), nullptr);
gst_bin_add(GST_BIN(pipe_), d3d11screensrc);
screencastsrc = d3d11screensrc;
} else { } else {
ScreenCastPortal &sc_portal = ScreenCastPortal::instance(); ScreenCastPortal &sc_portal = ScreenCastPortal::instance();
const ScreenCastPortal::Stream *stream = sc_portal.getStream(); const ScreenCastPortal::Stream *stream = sc_portal.getStream();
@ -1171,6 +1236,6 @@ getTurnURIs(const mtx::responses::TurnServer &turnServer)
} }
return ret; return ret;
} }
} } // namespace
#include "moc_CallManager.cpp" #include "moc_CallManager.cpp"

View file

@ -133,7 +133,11 @@ private:
QTimer turnServerTimer_; QTimer turnServerTimer_;
QMediaPlayer player_; QMediaPlayer player_;
std::vector<webrtc::ScreenShareType> screenShareTypes_; std::vector<webrtc::ScreenShareType> screenShareTypes_;
#ifndef Q_OS_WINDOWS
std::vector<std::pair<QString, uint32_t>> windows_; std::vector<std::pair<QString, uint32_t>> windows_;
#else
std::vector<std::pair<QString, uint64_t>> windows_;
#endif
std::vector<std::string> rejectCallPartyIDs_; std::vector<std::string> rejectCallPartyIDs_;
template<typename T> template<typename T>

View file

@ -23,6 +23,7 @@
#include "voip/ScreenCastPortal.h" #include "voip/ScreenCastPortal.h"
#ifdef GSTREAMER_AVAILABLE #ifdef GSTREAMER_AVAILABLE
#include "MainWindow.h"
extern "C" extern "C"
{ {
#include "gst/gl/gstgldisplay.h" #include "gst/gl/gstgldisplay.h"
@ -330,33 +331,66 @@ GstElement *
newVideoSinkChain(GstElement *pipe) newVideoSinkChain(GstElement *pipe)
{ {
// use compositor for now; acceleration needs investigation // use compositor for now; acceleration needs investigation
GstElement *queue = gst_element_factory_make("queue", nullptr); GstElement *queue = gst_element_factory_make("queue", nullptr);
GstElement *compositor = gst_element_factory_make("compositor", "compositor");
GstElement *glupload = gst_element_factory_make("glupload", nullptr); auto graphicsApi = MainWindow::instance()->graphicsApi();
GstElement *qmlglsink = gst_element_factory_make("qml6glsink", nullptr); GstElement *compositor = gst_element_factory_make(
GstElement *glsinkbin = gst_element_factory_make("glsinkbin", nullptr); graphicsApi == QSGRendererInterface::OpenGL ? "compositor" : "d3d11compositor", "compositor");
g_object_set(compositor, "background", 1, nullptr); g_object_set(compositor, "background", 1, nullptr);
g_object_set(qmlglsink, "widget", WebRTCSession::instance().getVideoItem(), nullptr); switch (graphicsApi) {
g_object_set(glsinkbin, "sink", qmlglsink, nullptr); case QSGRendererInterface::OpenGL: {
gst_bin_add_many(GST_BIN(pipe), queue, compositor, glupload, glsinkbin, nullptr); GstElement *glupload = gst_element_factory_make("glupload", nullptr);
gst_element_link_many(queue, compositor, glupload, glsinkbin, nullptr); GstElement *glcolorconvert = gst_element_factory_make("glcolorconvert", nullptr);
gst_element_sync_state_with_parent(queue); GstElement *qmlglsink = gst_element_factory_make("qml6glsink", nullptr);
gst_element_sync_state_with_parent(compositor); GstElement *glsinkbin = gst_element_factory_make("glsinkbin", nullptr);
gst_element_sync_state_with_parent(glupload);
gst_element_sync_state_with_parent(glsinkbin);
// to propagate context (hopefully) g_object_set(qmlglsink, "widget", WebRTCSession::instance().getVideoItem(), nullptr);
gst_element_set_state(qmlglsink, GST_STATE_READY); g_object_set(glsinkbin, "sink", qmlglsink, nullptr);
gst_bin_add_many(
GST_BIN(pipe), queue, compositor, glupload, glcolorconvert, glsinkbin, nullptr);
gst_element_link_many(queue, compositor, glupload, glcolorconvert, glsinkbin, nullptr);
// Workaround: On wayland, when egl is used, gstreamer might terminate the display connection. gst_element_sync_state_with_parent(queue);
// Prevent that by "leaking" a reference to the display. See gst_element_sync_state_with_parent(compositor);
// https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3743 gst_element_sync_state_with_parent(glupload);
if (QGuiApplication::platformName() == QStringLiteral("wayland")) { gst_element_sync_state_with_parent(glcolorconvert);
auto context = gst_element_get_context(qmlglsink, "gst.gl.GLDisplay"); gst_element_sync_state_with_parent(glsinkbin);
if (context) {
GstGLDisplay *display; // to propagate context (hopefully)
gst_context_get_gl_display(context, &display); gst_element_set_state(qmlglsink, GST_STATE_READY);
// Workaround: On wayland, when egl is used, gstreamer might terminate the display
// connection. Prevent that by "leaking" a reference to the display. See
// https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3743
if (QGuiApplication::platformName() == QStringLiteral("wayland")) {
auto context = gst_element_get_context(qmlglsink, "gst.gl.GLDisplay");
if (context) {
GstGLDisplay *display;
gst_context_get_gl_display(context, &display);
}
} }
} break;
case QSGRendererInterface::Direct3D11: {
GstElement *d3d11upload = gst_element_factory_make("d3d11upload", nullptr);
GstElement *d3d11colorconvert = gst_element_factory_make("d3d11colorconvert", nullptr);
GstElement *qmld3d11sink = gst_element_factory_make("qml6d3d11sink", nullptr);
g_object_set(qmld3d11sink, "widget", WebRTCSession::instance().getVideoItem(), nullptr);
gst_bin_add_many(
GST_BIN(pipe), queue, d3d11upload, compositor, d3d11colorconvert, qmld3d11sink, nullptr);
gst_element_link_many(
queue, d3d11upload, compositor, d3d11colorconvert, qmld3d11sink, nullptr);
gst_element_sync_state_with_parent(queue);
gst_element_sync_state_with_parent(compositor);
gst_element_sync_state_with_parent(d3d11upload);
gst_element_sync_state_with_parent(d3d11colorconvert);
// to propagate context (hopefully)
gst_element_set_state(qmld3d11sink, GST_STATE_READY);
} break;
default:
break;
} }
return queue; return queue;
@ -606,20 +640,20 @@ WebRTCSession::havePlugins(bool isVideo,
if (!initialised_ && !init(errorMessage)) if (!initialised_ && !init(errorMessage))
return false; return false;
static constexpr std::initializer_list<const char *> audio_elements = { static constexpr std::initializer_list<const char *> audio_elements = {"audioconvert",
"audioconvert", "audioresample",
"audioresample", "autoaudiosink",
"autoaudiosink", "capsfilter",
"capsfilter", "decodebin",
"decodebin", "opusenc",
"opusenc", "queue",
"queue", "rtpopuspay",
"rtpopuspay", "volume",
"volume", "webrtcbin",
"webrtcbin", "nicesrc",
}; "nicesink"};
static constexpr std::initializer_list<const char *> video_elements = { static constexpr std::initializer_list<const char *> gl_video_elements = {
"compositor", "compositor",
"glsinkbin", "glsinkbin",
"glupload", "glupload",
@ -631,6 +665,19 @@ WebRTCSession::havePlugins(bool isVideo,
"vp8enc", "vp8enc",
}; };
static constexpr std::initializer_list<const char *> d3d11_video_elements = {
"compositor",
"d3d11colorconvert",
"d3d11videosink",
"d3d11upload",
"qml6d3d11sink",
"rtpvp8pay",
"tee",
"videoconvert",
"videoscale",
"vp8enc",
};
std::string strError("Missing GStreamer elements: "); std::string strError("Missing GStreamer elements: ");
GstRegistry *registry = gst_registry_get(); GstRegistry *registry = gst_registry_get();
@ -654,8 +701,19 @@ WebRTCSession::havePlugins(bool isVideo,
haveVoicePlugins_ = check_plugins(audio_elements); haveVoicePlugins_ = check_plugins(audio_elements);
// check both elements at once // check both elements at once
if (isVideo) if (isVideo) {
haveVideoPlugins_ = check_plugins(video_elements); switch (MainWindow::instance()->graphicsApi()) {
case QSGRendererInterface::OpenGL:
haveVideoPlugins_ = check_plugins(gl_video_elements);
break;
case QSGRendererInterface::Direct3D11:
haveVideoPlugins_ = check_plugins(d3d11_video_elements);
break;
default:
haveVideoPlugins_ = false;
break;
}
}
bool haveScreensharePlugins = false; bool haveScreensharePlugins = false;
if (isScreenshare) { if (isScreenshare) {
@ -663,6 +721,8 @@ WebRTCSession::havePlugins(bool isVideo,
if (haveScreensharePlugins) { if (haveScreensharePlugins) {
if (QGuiApplication::platformName() == QStringLiteral("wayland")) { if (QGuiApplication::platformName() == QStringLiteral("wayland")) {
haveScreensharePlugins = check_plugins({"waylandsink"}); haveScreensharePlugins = check_plugins({"waylandsink"});
} else if (QGuiApplication::platformName() == QStringLiteral("windows")) {
haveScreensharePlugins = check_plugins({"d3d11videosink"});
} else { } else {
haveScreensharePlugins = check_plugins({"ximagesink"}); haveScreensharePlugins = check_plugins({"ximagesink"});
} }
@ -670,6 +730,9 @@ WebRTCSession::havePlugins(bool isVideo,
if (haveScreensharePlugins) { if (haveScreensharePlugins) {
if (screenShareType == ScreenShareType::X11) { if (screenShareType == ScreenShareType::X11) {
haveScreensharePlugins = check_plugins({"ximagesrc"}); haveScreensharePlugins = check_plugins({"ximagesrc"});
} else if (screenShareType == ScreenShareType::D3D11) {
haveScreensharePlugins =
check_plugins({"d3d11screencapturesrc", "d3d11download", "d3d11convert"});
} else { } else {
haveScreensharePlugins = check_plugins({"pipewiresrc"}); haveScreensharePlugins = check_plugins({"pipewiresrc"});
} }
@ -685,9 +748,19 @@ WebRTCSession::havePlugins(bool isVideo,
} }
if (isVideo || isScreenshare) { if (isVideo || isScreenshare) {
// load qmlglsink to register GStreamer's GstGLVideoItem QML type switch (MainWindow::instance()->graphicsApi()) {
GstElement *qmlglsink = gst_element_factory_make("qml6glsink", nullptr); case QSGRendererInterface::OpenGL: {
gst_object_unref(qmlglsink); // load qmlglsink to register GStreamer's GstGLVideoItem QML type
GstElement *qmlglsink = gst_element_factory_make("qml6glsink", nullptr);
gst_object_unref(qmlglsink);
} break;
case QSGRendererInterface::Direct3D11: {
GstElement *qmld3d11sink = gst_element_factory_make("qml6d3d11sink", nullptr);
gst_object_unref(qmld3d11sink);
} break;
default:
break;
}
} }
return true; return true;
} }
@ -1027,6 +1100,36 @@ WebRTCSession::addVideoPipeline(int vp8PayloadType)
gst_bin_add(GST_BIN(pipe_), ximagesrc); gst_bin_add(GST_BIN(pipe_), ximagesrc);
screencastsrc = ximagesrc; screencastsrc = ximagesrc;
} else if (screenShareType_ == ScreenShareType::D3D11) {
GstElement *d3d11screensrc =
gst_element_factory_make("d3d11screencapturesrc", "screenshare");
if (!d3d11screensrc) {
nhlog::ui()->error("WebRTC: failed to create d3d11screencapturesrc");
gst_object_unref(pipe_);
pipe_ = nullptr;
return false;
}
g_object_set(
d3d11screensrc, "window-handle", static_cast<guint64>(shareWindowId_), nullptr);
g_object_set(
d3d11screensrc, "show-cursor", !settings->screenShareHideCursor(), nullptr);
g_object_set(d3d11screensrc, "do-timestamp", (gboolean)1, nullptr);
gst_bin_add(GST_BIN(pipe_), d3d11screensrc);
GstElement *d3d11convert = gst_element_factory_make("d3d11convert", nullptr);
gst_bin_add(GST_BIN(pipe_), d3d11convert);
if (!gst_element_link(d3d11screensrc, d3d11convert)) {
nhlog::ui()->error("WebRTC: failed to link d3d11screencapturesrc -> d3d11convert");
return false;
}
GstElement *d3d11download = gst_element_factory_make("d3d11download", nullptr);
gst_bin_add(GST_BIN(pipe_), d3d11download);
if (!gst_element_link(d3d11convert, d3d11download)) {
nhlog::ui()->error("WebRTC: failed to link d3d11convert -> d3d11download");
return false;
}
screencastsrc = d3d11download;
} else { } else {
ScreenCastPortal &sc_portal = ScreenCastPortal::instance(); ScreenCastPortal &sc_portal = ScreenCastPortal::instance();
GstElement *pipewiresrc = gst_element_factory_make("pipewiresrc", "screenshare"); GstElement *pipewiresrc = gst_element_factory_make("pipewiresrc", "screenshare");

View file

@ -31,7 +31,8 @@ Q_ENUM_NS(CallType)
enum class ScreenShareType enum class ScreenShareType
{ {
X11, X11,
XDP XDP,
D3D11
}; };
Q_ENUM_NS(ScreenShareType) Q_ENUM_NS(ScreenShareType)