2023-02-22 01:48:49 +03:00
|
|
|
// SPDX-FileCopyrightText: Nheko Contributors
|
2021-03-05 02:35:15 +03:00
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2023-03-11 16:36:51 +03:00
|
|
|
#include <QGuiApplication>
|
2020-09-22 19:07:36 +03:00
|
|
|
#include <QQmlEngine>
|
2020-10-27 20:14:06 +03:00
|
|
|
#include <QQuickItem>
|
|
|
|
#include <algorithm>
|
2020-07-16 22:44:07 +03:00
|
|
|
#include <cctype>
|
2020-11-25 04:18:13 +03:00
|
|
|
#include <chrono>
|
2020-10-27 20:14:06 +03:00
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
|
|
|
#include <optional>
|
|
|
|
#include <string_view>
|
2020-11-25 04:18:13 +03:00
|
|
|
#include <thread>
|
2020-10-27 20:14:06 +03:00
|
|
|
#include <utility>
|
2020-07-16 22:44:07 +03:00
|
|
|
|
2021-02-18 23:55:29 +03:00
|
|
|
#include "CallDevices.h"
|
2020-10-28 00:26:46 +03:00
|
|
|
#include "ChatPage.h"
|
2020-07-11 02:19:48 +03:00
|
|
|
#include "Logging.h"
|
2020-10-27 20:14:06 +03:00
|
|
|
#include "UserSettingsPage.h"
|
2020-08-01 21:31:10 +03:00
|
|
|
#include "WebRTCSession.h"
|
2023-03-11 16:36:51 +03:00
|
|
|
#include "voip/ScreenCastPortal.h"
|
2020-07-11 02:19:48 +03:00
|
|
|
|
2020-08-14 02:03:27 +03:00
|
|
|
#ifdef GSTREAMER_AVAILABLE
|
2024-05-10 22:22:58 +03:00
|
|
|
#include "MainWindow.h"
|
2020-08-01 21:31:10 +03:00
|
|
|
extern "C"
|
|
|
|
{
|
2023-11-10 01:55:42 +03:00
|
|
|
#include "gst/gl/gstgldisplay.h"
|
2020-07-11 02:19:48 +03:00
|
|
|
#include "gst/gst.h"
|
|
|
|
#include "gst/sdp/sdp.h"
|
|
|
|
|
|
|
|
#define GST_USE_UNSTABLE_API
|
|
|
|
#include "gst/webrtc/webrtc.h"
|
|
|
|
}
|
2022-02-21 20:33:19 +03:00
|
|
|
|
|
|
|
#if !GST_CHECK_VERSION(1, 20, 0)
|
|
|
|
#define gst_element_request_pad_simple gst_element_get_request_pad
|
|
|
|
#endif
|
|
|
|
|
2020-08-14 02:03:27 +03:00
|
|
|
#endif
|
2020-07-11 02:19:48 +03:00
|
|
|
|
2020-10-27 20:14:06 +03:00
|
|
|
// https://github.com/vector-im/riot-web/issues/10173
|
2020-10-31 00:23:56 +03:00
|
|
|
#define STUN_SERVER "stun://turn.matrix.org:3478"
|
2020-10-27 20:14:06 +03:00
|
|
|
|
2021-02-18 23:55:29 +03:00
|
|
|
using webrtc::CallType;
|
2023-03-11 16:36:51 +03:00
|
|
|
using webrtc::ScreenShareType;
|
2020-09-22 19:07:36 +03:00
|
|
|
using webrtc::State;
|
2020-07-23 04:15:45 +03:00
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
WebRTCSession::WebRTCSession()
|
2021-07-03 23:45:36 +03:00
|
|
|
: devices_(CallDevices::instance())
|
2020-07-23 04:15:45 +03:00
|
|
|
{
|
2023-06-19 02:38:40 +03:00
|
|
|
// qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject,
|
|
|
|
// "im.nheko",
|
|
|
|
// 1,
|
|
|
|
// 0,
|
|
|
|
// "CallType",
|
|
|
|
// QStringLiteral("Can't instantiate enum"));
|
2021-02-18 23:55:29 +03:00
|
|
|
|
2023-06-19 02:38:40 +03:00
|
|
|
// qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject,
|
|
|
|
// "im.nheko",
|
|
|
|
// 1,
|
|
|
|
// 0,
|
|
|
|
// "ScreenShareType",
|
|
|
|
// QStringLiteral("Can't instantiate enum"));
|
2023-03-11 16:36:51 +03:00
|
|
|
|
2023-06-19 02:38:40 +03:00
|
|
|
// qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject,
|
|
|
|
// "im.nheko",
|
|
|
|
// 1,
|
|
|
|
// 0,
|
|
|
|
// "WebRTCState",
|
|
|
|
// QStringLiteral("Can't instantiate enum"));
|
2020-09-22 19:07:36 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
|
|
|
|
init();
|
2020-07-23 04:15:45 +03:00
|
|
|
}
|
|
|
|
|
2020-07-11 02:19:48 +03:00
|
|
|
bool
|
|
|
|
WebRTCSession::init(std::string *errorMessage)
|
|
|
|
{
|
2020-08-14 02:03:27 +03:00
|
|
|
#ifdef GSTREAMER_AVAILABLE
|
2021-09-18 01:22:33 +03:00
|
|
|
if (initialised_)
|
|
|
|
return true;
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
GError *error = nullptr;
|
|
|
|
if (!gst_init_check(nullptr, nullptr, &error)) {
|
|
|
|
std::string strError("WebRTC: failed to initialise GStreamer: ");
|
|
|
|
if (error) {
|
|
|
|
strError += error->message;
|
|
|
|
g_error_free(error);
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->error(strError);
|
|
|
|
if (errorMessage)
|
|
|
|
*errorMessage = strError;
|
2020-08-14 02:03:27 +03:00
|
|
|
return false;
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
initialised_ = true;
|
|
|
|
gchar *version = gst_version_string();
|
|
|
|
nhlog::ui()->info("WebRTC: initialised {}", version);
|
|
|
|
g_free(version);
|
|
|
|
devices_.init();
|
|
|
|
return true;
|
|
|
|
#else
|
|
|
|
(void)errorMessage;
|
|
|
|
return false;
|
2020-08-14 02:03:27 +03:00
|
|
|
#endif
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-08-14 02:03:27 +03:00
|
|
|
#ifdef GSTREAMER_AVAILABLE
|
2020-08-01 21:31:10 +03:00
|
|
|
namespace {
|
2020-10-27 20:14:06 +03:00
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
std::string localsdp_;
|
2022-06-27 19:09:31 +03:00
|
|
|
std::vector<mtx::events::voip::CallCandidates::Candidate> localcandidates_;
|
2021-02-20 19:26:53 +03:00
|
|
|
bool haveAudioStream_ = false;
|
|
|
|
bool haveVideoStream_ = false;
|
|
|
|
GstPad *localPiPSinkPad_ = nullptr;
|
|
|
|
GstPad *remotePiPSinkPad_ = nullptr;
|
2020-10-27 20:14:06 +03:00
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
gboolean
|
|
|
|
newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
WebRTCSession *session = static_cast<WebRTCSession *>(user_data);
|
|
|
|
switch (GST_MESSAGE_TYPE(msg)) {
|
|
|
|
case GST_MESSAGE_EOS:
|
|
|
|
nhlog::ui()->error("WebRTC: end of stream");
|
|
|
|
session->end();
|
|
|
|
break;
|
|
|
|
case GST_MESSAGE_ERROR:
|
|
|
|
GError *error;
|
|
|
|
gchar *debug;
|
|
|
|
gst_message_parse_error(msg, &error, &debug);
|
|
|
|
nhlog::ui()->error(
|
|
|
|
"WebRTC: error from element {}: {}", GST_OBJECT_NAME(msg->src), error->message);
|
|
|
|
g_clear_error(&error);
|
|
|
|
g_free(debug);
|
|
|
|
session->end();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return TRUE;
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
GstWebRTCSessionDescription *
|
|
|
|
parseSDP(const std::string &sdp, GstWebRTCSDPType type)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstSDPMessage *msg;
|
|
|
|
gst_sdp_message_new(&msg);
|
2022-10-26 02:10:35 +03:00
|
|
|
if (gst_sdp_message_parse_buffer((guint8 *)sdp.c_str(), static_cast<guint>(sdp.size()), msg) ==
|
|
|
|
GST_SDP_OK) {
|
2021-09-18 01:22:33 +03:00
|
|
|
return gst_webrtc_session_description_new(type, msg);
|
|
|
|
} else {
|
|
|
|
nhlog::ui()->error("WebRTC: failed to parse remote session description");
|
|
|
|
gst_sdp_message_free(msg);
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
void
|
|
|
|
setLocalDescription(GstPromise *promise, gpointer webrtc)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
const GstStructure *reply = gst_promise_get_reply(promise);
|
|
|
|
gboolean isAnswer = gst_structure_id_has_field(reply, g_quark_from_string("answer"));
|
|
|
|
GstWebRTCSessionDescription *gstsdp = nullptr;
|
|
|
|
gst_structure_get(
|
|
|
|
reply, isAnswer ? "answer" : "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &gstsdp, nullptr);
|
|
|
|
gst_promise_unref(promise);
|
|
|
|
g_signal_emit_by_name(webrtc, "set-local-description", gstsdp, nullptr);
|
|
|
|
|
|
|
|
gchar *sdp = gst_sdp_message_as_text(gstsdp->sdp);
|
|
|
|
localsdp_ = std::string(sdp);
|
|
|
|
g_free(sdp);
|
|
|
|
gst_webrtc_session_description_free(gstsdp);
|
|
|
|
|
|
|
|
nhlog::ui()->debug(
|
|
|
|
"WebRTC: local description set ({}):\n{}", isAnswer ? "answer" : "offer", localsdp_);
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
void
|
|
|
|
createOffer(GstElement *webrtc)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
// create-offer first, then set-local-description
|
|
|
|
GstPromise *promise = gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
|
|
|
|
g_signal_emit_by_name(webrtc, "create-offer", nullptr, promise);
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2020-08-01 21:31:10 +03:00
|
|
|
createAnswer(GstPromise *promise, gpointer webrtc)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
// create-answer first, then set-local-description
|
|
|
|
gst_promise_unref(promise);
|
|
|
|
promise = gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
|
|
|
|
g_signal_emit_by_name(webrtc, "create-answer", nullptr, promise);
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-08-03 05:27:05 +03:00
|
|
|
void
|
|
|
|
iceGatheringStateChanged(GstElement *webrtc,
|
|
|
|
GParamSpec *pspec G_GNUC_UNUSED,
|
|
|
|
gpointer user_data G_GNUC_UNUSED)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstWebRTCICEGatheringState newState;
|
|
|
|
g_object_get(webrtc, "ice-gathering-state", &newState, nullptr);
|
|
|
|
if (newState == GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE) {
|
|
|
|
nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete");
|
|
|
|
if (WebRTCSession::instance().isOffering()) {
|
|
|
|
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
|
|
|
|
emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
|
|
|
|
} else {
|
|
|
|
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
|
|
|
|
emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
|
2020-08-03 05:27:05 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-08-03 05:27:05 +03:00
|
|
|
}
|
|
|
|
|
2020-07-11 02:19:48 +03:00
|
|
|
void
|
2020-08-01 21:31:10 +03:00
|
|
|
addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED,
|
|
|
|
guint mlineIndex,
|
|
|
|
gchar *candidate,
|
|
|
|
gpointer G_GNUC_UNUSED)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->debug("WebRTC: local candidate: (m-line:{}):{}", mlineIndex, candidate);
|
|
|
|
localcandidates_.push_back({std::string() /*max-bundle*/, (uint16_t)mlineIndex, candidate});
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
void
|
|
|
|
iceConnectionStateChanged(GstElement *webrtc,
|
|
|
|
GParamSpec *pspec G_GNUC_UNUSED,
|
|
|
|
gpointer user_data G_GNUC_UNUSED)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstWebRTCICEConnectionState newState;
|
|
|
|
g_object_get(webrtc, "ice-connection-state", &newState, nullptr);
|
|
|
|
switch (newState) {
|
|
|
|
case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
|
|
|
|
nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking");
|
|
|
|
emit WebRTCSession::instance().stateChanged(State::CONNECTING);
|
|
|
|
break;
|
|
|
|
case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
|
|
|
|
nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed");
|
|
|
|
emit WebRTCSession::instance().stateChanged(State::ICEFAILED);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-10-27 20:14:06 +03:00
|
|
|
// https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1164
|
|
|
|
struct KeyFrameRequestData
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstElement *pipe = nullptr;
|
|
|
|
GstElement *decodebin = nullptr;
|
|
|
|
gint packetsLost = 0;
|
|
|
|
guint timerid = 0;
|
|
|
|
std::string statsField;
|
2020-10-27 20:14:06 +03:00
|
|
|
} keyFrameRequestData_;
|
|
|
|
|
|
|
|
void
|
|
|
|
sendKeyFrameRequest()
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstPad *sinkpad = gst_element_get_static_pad(keyFrameRequestData_.decodebin, "sink");
|
|
|
|
if (!gst_pad_push_event(sinkpad,
|
|
|
|
gst_event_new_custom(GST_EVENT_CUSTOM_UPSTREAM,
|
|
|
|
gst_structure_new_empty("GstForceKeyUnit"))))
|
|
|
|
nhlog::ui()->error("WebRTC: key frame request failed");
|
|
|
|
else
|
|
|
|
nhlog::ui()->debug("WebRTC: sent key frame request");
|
|
|
|
|
|
|
|
gst_object_unref(sinkpad);
|
2020-10-27 20:14:06 +03:00
|
|
|
}
|
|
|
|
|
2020-07-11 02:19:48 +03:00
|
|
|
void
|
2020-10-27 20:14:06 +03:00
|
|
|
testPacketLoss_(GstPromise *promise, gpointer G_GNUC_UNUSED)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
const GstStructure *reply = gst_promise_get_reply(promise);
|
|
|
|
gint packetsLost = 0;
|
|
|
|
GstStructure *rtpStats;
|
|
|
|
if (!gst_structure_get(
|
|
|
|
reply, keyFrameRequestData_.statsField.c_str(), GST_TYPE_STRUCTURE, &rtpStats, nullptr)) {
|
|
|
|
nhlog::ui()->error("WebRTC: get-stats: no field: {}", keyFrameRequestData_.statsField);
|
2020-10-27 20:14:06 +03:00
|
|
|
gst_promise_unref(promise);
|
2021-09-18 01:22:33 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
gst_structure_get_int(rtpStats, "packets-lost", &packetsLost);
|
|
|
|
gst_structure_free(rtpStats);
|
|
|
|
gst_promise_unref(promise);
|
|
|
|
if (packetsLost > keyFrameRequestData_.packetsLost) {
|
|
|
|
nhlog::ui()->debug("WebRTC: inbound video lost packet count: {}", packetsLost);
|
|
|
|
keyFrameRequestData_.packetsLost = packetsLost;
|
|
|
|
sendKeyFrameRequest();
|
|
|
|
}
|
2020-10-27 20:14:06 +03:00
|
|
|
}
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2020-10-27 20:14:06 +03:00
|
|
|
gboolean
|
|
|
|
testPacketLoss(gpointer G_GNUC_UNUSED)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (keyFrameRequestData_.pipe) {
|
|
|
|
GstElement *webrtc = gst_bin_get_by_name(GST_BIN(keyFrameRequestData_.pipe), "webrtcbin");
|
|
|
|
GstPromise *promise = gst_promise_new_with_change_func(testPacketLoss_, nullptr, nullptr);
|
|
|
|
g_signal_emit_by_name(webrtc, "get-stats", nullptr, promise);
|
|
|
|
gst_object_unref(webrtc);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
return FALSE;
|
2020-10-27 20:14:06 +03:00
|
|
|
}
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2020-10-27 20:14:06 +03:00
|
|
|
void
|
|
|
|
setWaitForKeyFrame(GstBin *decodebin G_GNUC_UNUSED, GstElement *element, gpointer G_GNUC_UNUSED)
|
|
|
|
{
|
2023-01-31 19:40:58 +03:00
|
|
|
// Unconditionally enable keyframe wait and requesting keyframes, so that we do that for
|
|
|
|
// every decode, not just vp8 decoding
|
|
|
|
g_object_set(element, "wait-for-keyframe", TRUE, "request-keyframe", TRUE, nullptr);
|
2020-10-27 20:14:06 +03:00
|
|
|
}
|
|
|
|
|
2020-11-09 18:51:17 +03:00
|
|
|
GstElement *
|
|
|
|
newAudioSinkChain(GstElement *pipe)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstElement *queue = gst_element_factory_make("queue", nullptr);
|
|
|
|
GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
|
|
|
|
GstElement *resample = gst_element_factory_make("audioresample", nullptr);
|
|
|
|
GstElement *sink = gst_element_factory_make("autoaudiosink", nullptr);
|
|
|
|
gst_bin_add_many(GST_BIN(pipe), queue, convert, resample, sink, nullptr);
|
|
|
|
gst_element_link_many(queue, convert, resample, sink, nullptr);
|
|
|
|
gst_element_sync_state_with_parent(queue);
|
|
|
|
gst_element_sync_state_with_parent(convert);
|
|
|
|
gst_element_sync_state_with_parent(resample);
|
|
|
|
gst_element_sync_state_with_parent(sink);
|
|
|
|
return queue;
|
2020-11-09 18:51:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
GstElement *
|
|
|
|
newVideoSinkChain(GstElement *pipe)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
// use compositor for now; acceleration needs investigation
|
2024-05-10 22:22:58 +03:00
|
|
|
GstElement *queue = gst_element_factory_make("queue", nullptr);
|
|
|
|
|
|
|
|
auto graphicsApi = MainWindow::instance()->graphicsApi();
|
|
|
|
GstElement *compositor = gst_element_factory_make(
|
|
|
|
graphicsApi == QSGRendererInterface::OpenGL ? "compositor" : "d3d11compositor", "compositor");
|
2021-09-18 01:22:33 +03:00
|
|
|
g_object_set(compositor, "background", 1, nullptr);
|
2024-05-10 22:22:58 +03:00
|
|
|
switch (graphicsApi) {
|
|
|
|
case QSGRendererInterface::OpenGL: {
|
2024-05-18 00:55:33 +03:00
|
|
|
GstElement *qmlglsink = gst_element_factory_make("qml6glsink", nullptr);
|
|
|
|
GstElement *glsinkbin = gst_element_factory_make("glsinkbin", nullptr);
|
2024-05-10 22:22:58 +03:00
|
|
|
|
|
|
|
g_object_set(qmlglsink, "widget", WebRTCSession::instance().getVideoItem(), nullptr);
|
|
|
|
g_object_set(glsinkbin, "sink", qmlglsink, nullptr);
|
2024-05-18 00:55:33 +03:00
|
|
|
gst_bin_add_many(GST_BIN(pipe), queue, compositor, glsinkbin, nullptr);
|
|
|
|
gst_element_link_many(queue, compositor, glsinkbin, nullptr);
|
2024-05-10 22:22:58 +03:00
|
|
|
|
|
|
|
gst_element_sync_state_with_parent(queue);
|
|
|
|
gst_element_sync_state_with_parent(compositor);
|
|
|
|
gst_element_sync_state_with_parent(glsinkbin);
|
|
|
|
|
|
|
|
// to propagate context (hopefully)
|
|
|
|
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);
|
|
|
|
}
|
2023-11-10 01:55:42 +03:00
|
|
|
}
|
2024-05-10 22:22:58 +03:00
|
|
|
} 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;
|
2023-11-10 01:55:42 +03:00
|
|
|
}
|
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
return queue;
|
2020-11-09 18:51:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<int, int>
|
|
|
|
getResolution(GstPad *pad)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
std::pair<int, int> ret;
|
|
|
|
GstCaps *caps = gst_pad_get_current_caps(pad);
|
|
|
|
const GstStructure *s = gst_caps_get_structure(caps, 0);
|
|
|
|
gst_structure_get_int(s, "width", &ret.first);
|
|
|
|
gst_structure_get_int(s, "height", &ret.second);
|
|
|
|
gst_caps_unref(caps);
|
|
|
|
return ret;
|
2020-11-09 18:51:17 +03:00
|
|
|
}
|
|
|
|
|
2021-02-22 00:30:10 +03:00
|
|
|
std::pair<int, int>
|
|
|
|
getResolution(GstElement *pipe, const gchar *elementName, const gchar *padName)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstElement *element = gst_bin_get_by_name(GST_BIN(pipe), elementName);
|
|
|
|
GstPad *pad = gst_element_get_static_pad(element, padName);
|
|
|
|
auto ret = getResolution(pad);
|
|
|
|
gst_object_unref(pad);
|
|
|
|
gst_object_unref(element);
|
|
|
|
return ret;
|
2021-02-22 00:30:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<int, int>
|
2021-02-25 01:07:01 +03:00
|
|
|
getPiPDimensions(const std::pair<int, int> &resolution, int fullWidth, double scaleFactor)
|
2021-02-22 00:30:10 +03:00
|
|
|
{
|
2022-10-26 02:10:35 +03:00
|
|
|
double pipWidth = fullWidth * scaleFactor;
|
|
|
|
double pipHeight = static_cast<double>(resolution.second) / resolution.first * pipWidth;
|
|
|
|
return {
|
|
|
|
static_cast<int>(std::ceil(pipWidth)),
|
|
|
|
static_cast<int>(std::ceil(pipHeight)),
|
|
|
|
};
|
2021-02-22 00:30:10 +03:00
|
|
|
}
|
|
|
|
|
2020-11-09 18:51:17 +03:00
|
|
|
void
|
2021-02-20 19:26:53 +03:00
|
|
|
addLocalPiP(GstElement *pipe, const std::pair<int, int> &videoCallSize)
|
2020-11-09 18:51:17 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
// embed localUser's camera into received video (CallType::VIDEO)
|
|
|
|
// OR embed screen share into received video (CallType::SCREEN)
|
|
|
|
GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe), "videosrctee");
|
|
|
|
if (!tee)
|
|
|
|
return;
|
|
|
|
|
|
|
|
GstElement *queue = gst_element_factory_make("queue", nullptr);
|
|
|
|
gst_bin_add(GST_BIN(pipe), queue);
|
|
|
|
gst_element_link(tee, queue);
|
|
|
|
gst_element_sync_state_with_parent(queue);
|
|
|
|
gst_object_unref(tee);
|
|
|
|
|
|
|
|
GstElement *compositor = gst_bin_get_by_name(GST_BIN(pipe), "compositor");
|
2022-02-21 20:33:19 +03:00
|
|
|
localPiPSinkPad_ = gst_element_request_pad_simple(compositor, "sink_%u");
|
2021-09-18 01:22:33 +03:00
|
|
|
g_object_set(localPiPSinkPad_, "zorder", 2, nullptr);
|
|
|
|
|
|
|
|
bool isVideo = WebRTCSession::instance().callType() == CallType::VIDEO;
|
|
|
|
const gchar *element = isVideo ? "camerafilter" : "screenshare";
|
|
|
|
const gchar *pad = isVideo ? "sink" : "src";
|
|
|
|
auto resolution = getResolution(pipe, element, pad);
|
|
|
|
auto pipSize = getPiPDimensions(resolution, videoCallSize.first, 0.25);
|
|
|
|
nhlog::ui()->debug("WebRTC: local picture-in-picture: {}x{}", pipSize.first, pipSize.second);
|
|
|
|
g_object_set(localPiPSinkPad_, "width", pipSize.first, "height", pipSize.second, nullptr);
|
|
|
|
gint offset = videoCallSize.first / 80;
|
|
|
|
g_object_set(localPiPSinkPad_, "xpos", offset, "ypos", offset, nullptr);
|
|
|
|
|
|
|
|
GstPad *srcpad = gst_element_get_static_pad(queue, "src");
|
|
|
|
if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, localPiPSinkPad_)))
|
|
|
|
nhlog::ui()->error("WebRTC: failed to link local PiP elements");
|
|
|
|
gst_object_unref(srcpad);
|
|
|
|
gst_object_unref(compositor);
|
2020-11-09 18:51:17 +03:00
|
|
|
}
|
|
|
|
|
2021-02-20 19:26:53 +03:00
|
|
|
void
|
|
|
|
addRemotePiP(GstElement *pipe)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
// embed localUser's camera into screen image being shared
|
|
|
|
if (remotePiPSinkPad_) {
|
|
|
|
auto camRes = getResolution(pipe, "camerafilter", "sink");
|
|
|
|
auto shareRes = getResolution(pipe, "screenshare", "src");
|
|
|
|
auto pipSize = getPiPDimensions(camRes, shareRes.first, 0.2);
|
|
|
|
nhlog::ui()->debug(
|
|
|
|
"WebRTC: screen share picture-in-picture: {}x{}", pipSize.first, pipSize.second);
|
|
|
|
|
|
|
|
gint offset = shareRes.first / 100;
|
|
|
|
g_object_set(remotePiPSinkPad_, "zorder", 2, nullptr);
|
|
|
|
g_object_set(remotePiPSinkPad_, "width", pipSize.first, "height", pipSize.second, nullptr);
|
|
|
|
g_object_set(remotePiPSinkPad_,
|
|
|
|
"xpos",
|
|
|
|
shareRes.first - pipSize.first - offset,
|
|
|
|
"ypos",
|
|
|
|
shareRes.second - pipSize.second - offset,
|
|
|
|
nullptr);
|
|
|
|
}
|
2021-02-20 19:26:53 +03:00
|
|
|
}
|
|
|
|
|
2021-02-22 00:30:10 +03:00
|
|
|
void
|
|
|
|
addLocalVideo(GstElement *pipe)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstElement *queue = newVideoSinkChain(pipe);
|
|
|
|
GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe), "videosrctee");
|
2022-02-21 20:33:19 +03:00
|
|
|
GstPad *srcpad = gst_element_request_pad_simple(tee, "src_%u");
|
2021-09-18 01:22:33 +03:00
|
|
|
GstPad *sinkpad = gst_element_get_static_pad(queue, "sink");
|
|
|
|
if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, sinkpad)))
|
|
|
|
nhlog::ui()->error("WebRTC: failed to link videosrctee -> video sink chain");
|
|
|
|
gst_object_unref(srcpad);
|
2021-02-22 00:30:10 +03:00
|
|
|
}
|
|
|
|
|
2020-10-27 20:14:06 +03:00
|
|
|
void
|
|
|
|
linkNewPad(GstElement *decodebin, GstPad *newpad, GstElement *pipe)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
|
|
|
|
GstCaps *sinkcaps = gst_pad_get_current_caps(sinkpad);
|
|
|
|
const GstStructure *structure = gst_caps_get_structure(sinkcaps, 0);
|
|
|
|
|
|
|
|
gchar *mediaType = nullptr;
|
|
|
|
guint ssrc = 0;
|
|
|
|
gst_structure_get(
|
|
|
|
structure, "media", G_TYPE_STRING, &mediaType, "ssrc", G_TYPE_UINT, &ssrc, nullptr);
|
|
|
|
gst_caps_unref(sinkcaps);
|
|
|
|
gst_object_unref(sinkpad);
|
|
|
|
|
|
|
|
WebRTCSession *session = &WebRTCSession::instance();
|
|
|
|
GstElement *queue = nullptr;
|
|
|
|
if (!std::strcmp(mediaType, "audio")) {
|
|
|
|
nhlog::ui()->debug("WebRTC: received incoming audio stream");
|
|
|
|
haveAudioStream_ = true;
|
|
|
|
queue = newAudioSinkChain(pipe);
|
|
|
|
} else if (!std::strcmp(mediaType, "video")) {
|
|
|
|
nhlog::ui()->debug("WebRTC: received incoming video stream");
|
|
|
|
if (!session->getVideoItem()) {
|
|
|
|
g_free(mediaType);
|
|
|
|
nhlog::ui()->error("WebRTC: video call item not set");
|
|
|
|
return;
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
haveVideoStream_ = true;
|
|
|
|
keyFrameRequestData_.statsField =
|
|
|
|
std::string("rtp-inbound-stream-stats_") + std::to_string(ssrc);
|
|
|
|
queue = newVideoSinkChain(pipe);
|
|
|
|
auto videoCallSize = getResolution(newpad);
|
|
|
|
nhlog::ui()->info(
|
|
|
|
"WebRTC: incoming video resolution: {}x{}", videoCallSize.first, videoCallSize.second);
|
|
|
|
addLocalPiP(pipe, videoCallSize);
|
|
|
|
} else {
|
|
|
|
g_free(mediaType);
|
|
|
|
nhlog::ui()->error("WebRTC: unknown pad type: {}", GST_PAD_NAME(newpad));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
GstPad *queuepad = gst_element_get_static_pad(queue, "sink");
|
|
|
|
if (queuepad) {
|
|
|
|
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
|
|
|
|
nhlog::ui()->error("WebRTC: unable to link new pad");
|
|
|
|
else {
|
|
|
|
if (session->callType() == CallType::VOICE ||
|
|
|
|
(haveAudioStream_ && (haveVideoStream_ || session->isRemoteVideoRecvOnly()))) {
|
|
|
|
emit session->stateChanged(State::CONNECTED);
|
|
|
|
if (haveVideoStream_) {
|
|
|
|
keyFrameRequestData_.pipe = pipe;
|
|
|
|
keyFrameRequestData_.decodebin = decodebin;
|
|
|
|
keyFrameRequestData_.timerid =
|
|
|
|
g_timeout_add_seconds(3, testPacketLoss, nullptr);
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
addRemotePiP(pipe);
|
|
|
|
if (session->isRemoteVideoRecvOnly())
|
|
|
|
addLocalVideo(pipe);
|
|
|
|
}
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
gst_object_unref(queuepad);
|
|
|
|
}
|
|
|
|
g_free(mediaType);
|
2024-04-05 04:57:28 +03:00
|
|
|
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipe), GST_DEBUG_GRAPH_SHOW_VERBOSE, "newpad");
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2020-08-01 21:31:10 +03:00
|
|
|
addDecodeBin(GstElement *webrtc G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (GST_PAD_DIRECTION(newpad) != GST_PAD_SRC)
|
|
|
|
return;
|
|
|
|
|
|
|
|
nhlog::ui()->debug("WebRTC: received incoming stream");
|
|
|
|
GstElement *decodebin = gst_element_factory_make("decodebin", nullptr);
|
|
|
|
// hardware decoding needs investigation; eg rendering fails if vaapi plugin installed
|
|
|
|
g_object_set(decodebin, "force-sw-decoders", TRUE, nullptr);
|
|
|
|
g_signal_connect(decodebin, "pad-added", G_CALLBACK(linkNewPad), pipe);
|
|
|
|
g_signal_connect(decodebin, "element-added", G_CALLBACK(setWaitForKeyFrame), nullptr);
|
|
|
|
gst_bin_add(GST_BIN(pipe), decodebin);
|
|
|
|
gst_element_sync_state_with_parent(decodebin);
|
|
|
|
GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
|
|
|
|
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, sinkpad)))
|
|
|
|
nhlog::ui()->error("WebRTC: unable to link decodebin");
|
|
|
|
gst_object_unref(sinkpad);
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-10-27 20:14:06 +03:00
|
|
|
bool
|
2020-10-30 02:17:10 +03:00
|
|
|
contains(std::string_view str1, std::string_view str2)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
return std::search(str1.cbegin(),
|
|
|
|
str1.cend(),
|
|
|
|
str2.cbegin(),
|
|
|
|
str2.cend(),
|
|
|
|
[](unsigned char c1, unsigned char c2) {
|
|
|
|
return std::tolower(c1) == std::tolower(c2);
|
|
|
|
}) != str1.cend();
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2020-07-26 01:11:11 +03:00
|
|
|
|
2020-10-27 20:14:06 +03:00
|
|
|
bool
|
|
|
|
getMediaAttributes(const GstSDPMessage *sdp,
|
|
|
|
const char *mediaType,
|
|
|
|
const char *encoding,
|
|
|
|
int &payloadType,
|
2021-02-18 23:55:29 +03:00
|
|
|
bool &recvOnly,
|
|
|
|
bool &sendOnly)
|
2020-08-01 21:31:10 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
payloadType = -1;
|
|
|
|
recvOnly = false;
|
|
|
|
sendOnly = false;
|
|
|
|
for (guint mlineIndex = 0; mlineIndex < gst_sdp_message_medias_len(sdp); ++mlineIndex) {
|
|
|
|
const GstSDPMedia *media = gst_sdp_message_get_media(sdp, mlineIndex);
|
|
|
|
if (!std::strcmp(gst_sdp_media_get_media(media), mediaType)) {
|
|
|
|
recvOnly = gst_sdp_media_get_attribute_val(media, "recvonly") != nullptr;
|
|
|
|
sendOnly = gst_sdp_media_get_attribute_val(media, "sendonly") != nullptr;
|
|
|
|
const gchar *rtpval = nullptr;
|
|
|
|
for (guint n = 0; n == 0 || rtpval; ++n) {
|
|
|
|
rtpval = gst_sdp_media_get_attribute_val_n(media, "rtpmap", n);
|
|
|
|
if (rtpval && contains(rtpval, encoding)) {
|
|
|
|
payloadType = std::atoi(rtpval);
|
|
|
|
break;
|
2020-10-27 20:14:06 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
return true;
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
return false;
|
2020-10-27 20:14:06 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2023-03-11 16:36:51 +03:00
|
|
|
WebRTCSession::havePlugins(bool isVideo,
|
|
|
|
bool isScreenshare,
|
|
|
|
ScreenShareType screenShareType,
|
|
|
|
std::string *errorMessage)
|
2020-10-27 20:14:06 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!initialised_ && !init(errorMessage))
|
|
|
|
return false;
|
2020-10-27 20:14:06 +03:00
|
|
|
|
2024-05-10 22:22:58 +03:00
|
|
|
static constexpr std::initializer_list<const char *> audio_elements = {"audioconvert",
|
|
|
|
"audioresample",
|
|
|
|
"autoaudiosink",
|
|
|
|
"capsfilter",
|
|
|
|
"decodebin",
|
|
|
|
"opusenc",
|
|
|
|
"queue",
|
|
|
|
"rtpopuspay",
|
|
|
|
"volume",
|
|
|
|
"webrtcbin",
|
|
|
|
"nicesrc",
|
|
|
|
"nicesink"};
|
|
|
|
|
|
|
|
static constexpr std::initializer_list<const char *> gl_video_elements = {
|
2023-02-08 02:54:02 +03:00
|
|
|
"compositor",
|
|
|
|
"glsinkbin",
|
|
|
|
"glupload",
|
2023-11-02 02:38:13 +03:00
|
|
|
"qml6glsink",
|
2023-02-08 02:54:02 +03:00
|
|
|
"rtpvp8pay",
|
|
|
|
"tee",
|
|
|
|
"videoconvert",
|
|
|
|
"videoscale",
|
|
|
|
"vp8enc",
|
|
|
|
};
|
|
|
|
|
2024-05-10 22:22:58 +03:00
|
|
|
static constexpr std::initializer_list<const char *> d3d11_video_elements = {
|
|
|
|
"compositor",
|
|
|
|
"d3d11colorconvert",
|
|
|
|
"d3d11videosink",
|
|
|
|
"d3d11upload",
|
|
|
|
"qml6d3d11sink",
|
|
|
|
"rtpvp8pay",
|
|
|
|
"tee",
|
|
|
|
"videoconvert",
|
|
|
|
"videoscale",
|
|
|
|
"vp8enc",
|
|
|
|
};
|
|
|
|
|
2023-02-08 02:54:02 +03:00
|
|
|
std::string strError("Missing GStreamer elements: ");
|
2021-09-18 01:22:33 +03:00
|
|
|
GstRegistry *registry = gst_registry_get();
|
2023-02-08 02:54:02 +03:00
|
|
|
|
|
|
|
auto check_plugins = [&strError,
|
|
|
|
registry](const std::initializer_list<const char *> &elements) {
|
|
|
|
bool havePlugins = true;
|
|
|
|
for (const auto &element : elements) {
|
|
|
|
GstPluginFeature *plugin =
|
|
|
|
gst_registry_find_feature(registry, element, GST_TYPE_ELEMENT_FACTORY);
|
|
|
|
if (!plugin) {
|
|
|
|
havePlugins = false;
|
|
|
|
strError += std::string(element) + " ";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
gst_object_unref(plugin);
|
2020-10-27 20:14:06 +03:00
|
|
|
}
|
2023-02-08 02:54:02 +03:00
|
|
|
|
|
|
|
return havePlugins;
|
|
|
|
};
|
|
|
|
|
|
|
|
haveVoicePlugins_ = check_plugins(audio_elements);
|
|
|
|
|
|
|
|
// check both elements at once
|
2024-05-10 22:22:58 +03:00
|
|
|
if (isVideo) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2023-03-11 16:36:51 +03:00
|
|
|
|
|
|
|
bool haveScreensharePlugins = false;
|
|
|
|
if (isScreenshare) {
|
|
|
|
haveScreensharePlugins = check_plugins({"videorate"});
|
|
|
|
if (haveScreensharePlugins) {
|
|
|
|
if (QGuiApplication::platformName() == QStringLiteral("wayland")) {
|
|
|
|
haveScreensharePlugins = check_plugins({"waylandsink"});
|
2024-05-10 22:22:58 +03:00
|
|
|
} else if (QGuiApplication::platformName() == QStringLiteral("windows")) {
|
|
|
|
haveScreensharePlugins = check_plugins({"d3d11videosink"});
|
2023-03-11 16:36:51 +03:00
|
|
|
} else {
|
|
|
|
haveScreensharePlugins = check_plugins({"ximagesink"});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (haveScreensharePlugins) {
|
|
|
|
if (screenShareType == ScreenShareType::X11) {
|
|
|
|
haveScreensharePlugins = check_plugins({"ximagesrc"});
|
2024-05-10 22:22:58 +03:00
|
|
|
} else if (screenShareType == ScreenShareType::D3D11) {
|
|
|
|
haveScreensharePlugins =
|
|
|
|
check_plugins({"d3d11screencapturesrc", "d3d11download", "d3d11convert"});
|
2023-03-11 16:36:51 +03:00
|
|
|
} else {
|
|
|
|
haveScreensharePlugins = check_plugins({"pipewiresrc"});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-08 02:54:02 +03:00
|
|
|
|
|
|
|
if (!haveVoicePlugins_ || (isVideo && !haveVideoPlugins_) ||
|
2023-03-11 16:36:51 +03:00
|
|
|
(isScreenshare && !haveScreensharePlugins)) {
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->error(strError);
|
|
|
|
if (errorMessage)
|
|
|
|
*errorMessage = strError;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-03-11 16:36:51 +03:00
|
|
|
if (isVideo || isScreenshare) {
|
2024-05-10 22:22:58 +03:00
|
|
|
switch (MainWindow::instance()->graphicsApi()) {
|
|
|
|
case QSGRendererInterface::OpenGL: {
|
|
|
|
// 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;
|
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
return true;
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2020-07-11 02:19:48 +03:00
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
bool
|
2023-03-11 16:36:51 +03:00
|
|
|
WebRTCSession::createOffer(CallType callType,
|
|
|
|
ScreenShareType screenShareType,
|
|
|
|
uint32_t shareWindowId)
|
2020-08-01 21:31:10 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
clear();
|
2023-03-11 16:36:51 +03:00
|
|
|
isOffering_ = true;
|
|
|
|
callType_ = callType;
|
|
|
|
screenShareType_ = screenShareType;
|
|
|
|
shareWindowId_ = shareWindowId;
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
// opus and vp8 rtp payload types must be defined dynamically
|
|
|
|
// therefore from the range [96-127]
|
|
|
|
// see for example https://tools.ietf.org/html/rfc7587
|
|
|
|
constexpr int opusPayloadType = 111;
|
|
|
|
constexpr int vp8PayloadType = 96;
|
|
|
|
return startPipeline(opusPayloadType, vp8PayloadType);
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2020-07-11 02:19:48 +03:00
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
bool
|
|
|
|
WebRTCSession::acceptOffer(const std::string &sdp)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->debug("WebRTC: received offer:\n{}", sdp);
|
|
|
|
if (state_ != State::DISCONNECTED)
|
|
|
|
return false;
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
clear();
|
|
|
|
GstWebRTCSessionDescription *offer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_OFFER);
|
|
|
|
if (!offer)
|
|
|
|
return false;
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
int opusPayloadType;
|
|
|
|
bool recvOnly;
|
|
|
|
bool sendOnly;
|
|
|
|
if (getMediaAttributes(offer->sdp, "audio", "opus", opusPayloadType, recvOnly, sendOnly)) {
|
|
|
|
if (opusPayloadType == -1) {
|
|
|
|
nhlog::ui()->error("WebRTC: remote audio offer - no opus encoding");
|
|
|
|
gst_webrtc_session_description_free(offer);
|
|
|
|
return false;
|
2020-10-27 20:14:06 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
} else {
|
|
|
|
nhlog::ui()->error("WebRTC: remote offer - no audio media");
|
|
|
|
gst_webrtc_session_description_free(offer);
|
|
|
|
return false;
|
|
|
|
}
|
2020-10-27 20:14:06 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
int vp8PayloadType;
|
|
|
|
bool isVideo = getMediaAttributes(
|
|
|
|
offer->sdp, "video", "vp8", vp8PayloadType, isRemoteVideoRecvOnly_, isRemoteVideoSendOnly_);
|
|
|
|
if (isVideo && vp8PayloadType == -1) {
|
|
|
|
nhlog::ui()->error("WebRTC: remote video offer - no vp8 encoding");
|
|
|
|
gst_webrtc_session_description_free(offer);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
callType_ = isVideo ? CallType::VIDEO : CallType::VOICE;
|
2020-10-27 20:14:06 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!startPipeline(opusPayloadType, vp8PayloadType)) {
|
|
|
|
gst_webrtc_session_description_free(offer);
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
// avoid a race that sometimes leaves the generated answer without media tracks (a=ssrc
|
|
|
|
// lines)
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
2020-11-25 04:18:13 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
// set-remote-description first, then create-answer
|
|
|
|
GstPromise *promise = gst_promise_new_with_change_func(createAnswer, webrtc_, nullptr);
|
|
|
|
g_signal_emit_by_name(webrtc_, "set-remote-description", offer, promise);
|
|
|
|
gst_webrtc_session_description_free(offer);
|
2024-04-05 04:57:28 +03:00
|
|
|
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipe_), GST_DEBUG_GRAPH_SHOW_VERBOSE, "accept");
|
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
return true;
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2022-10-14 16:49:05 +03:00
|
|
|
bool
|
|
|
|
WebRTCSession::acceptNegotiation(const std::string &sdp)
|
|
|
|
{
|
|
|
|
nhlog::ui()->debug("WebRTC: received negotiation offer:\n{}", sdp);
|
|
|
|
if (state_ == State::DISCONNECTED)
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
bool
|
|
|
|
WebRTCSession::acceptAnswer(const std::string &sdp)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->debug("WebRTC: received answer:\n{}", sdp);
|
|
|
|
if (state_ != State::OFFERSENT)
|
|
|
|
return false;
|
2020-10-27 20:14:06 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
|
|
|
|
if (!answer) {
|
|
|
|
end();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (callType_ != CallType::VOICE) {
|
|
|
|
int unused;
|
|
|
|
if (!getMediaAttributes(
|
|
|
|
answer->sdp, "video", "vp8", unused, isRemoteVideoRecvOnly_, isRemoteVideoSendOnly_))
|
|
|
|
isRemoteVideoRecvOnly_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_signal_emit_by_name(webrtc_, "set-remote-description", answer, nullptr);
|
|
|
|
gst_webrtc_session_description_free(answer);
|
|
|
|
return true;
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-07-26 17:59:50 +03:00
|
|
|
void
|
2020-08-01 21:31:10 +03:00
|
|
|
WebRTCSession::acceptICECandidates(
|
2022-06-27 19:09:31 +03:00
|
|
|
const std::vector<mtx::events::voip::CallCandidates::Candidate> &candidates)
|
2020-07-26 17:59:50 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (state_ >= State::INITIATED) {
|
|
|
|
for (const auto &c : candidates) {
|
|
|
|
nhlog::ui()->debug(
|
|
|
|
"WebRTC: remote candidate: (m-line:{}):{}", c.sdpMLineIndex, c.candidate);
|
|
|
|
if (!c.candidate.empty()) {
|
|
|
|
g_signal_emit_by_name(
|
|
|
|
webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str());
|
|
|
|
}
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-07-26 17:59:50 +03:00
|
|
|
}
|
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
bool
|
2020-10-27 20:14:06 +03:00
|
|
|
WebRTCSession::startPipeline(int opusPayloadType, int vp8PayloadType)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (state_ != State::DISCONNECTED)
|
|
|
|
return false;
|
2021-02-21 01:14:22 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
emit stateChanged(State::INITIATING);
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!createPipeline(opusPayloadType, vp8PayloadType)) {
|
|
|
|
end();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
webrtc_ = gst_bin_get_by_name(GST_BIN(pipe_), "webrtcbin");
|
|
|
|
|
|
|
|
if (ChatPage::instance()->userSettings()->useStunServer()) {
|
|
|
|
nhlog::ui()->info("WebRTC: setting STUN server: {}", STUN_SERVER);
|
|
|
|
g_object_set(webrtc_, "stun-server", STUN_SERVER, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &uri : turnServers_) {
|
|
|
|
nhlog::ui()->info("WebRTC: setting TURN server: {}", uri);
|
|
|
|
gboolean udata;
|
|
|
|
g_signal_emit_by_name(webrtc_, "add-turn-server", uri.c_str(), (gpointer)(&udata));
|
|
|
|
}
|
|
|
|
if (turnServers_.empty())
|
|
|
|
nhlog::ui()->warn("WebRTC: no TURN server provided");
|
|
|
|
|
|
|
|
// generate the offer when the pipeline goes to PLAYING
|
|
|
|
if (isOffering_)
|
|
|
|
g_signal_connect(webrtc_, "on-negotiation-needed", G_CALLBACK(::createOffer), nullptr);
|
|
|
|
|
|
|
|
// on-ice-candidate is emitted when a local ICE candidate has been gathered
|
|
|
|
g_signal_connect(webrtc_, "on-ice-candidate", G_CALLBACK(addLocalICECandidate), nullptr);
|
|
|
|
|
|
|
|
// capture ICE failure
|
|
|
|
g_signal_connect(
|
|
|
|
webrtc_, "notify::ice-connection-state", G_CALLBACK(iceConnectionStateChanged), nullptr);
|
|
|
|
|
|
|
|
// incoming streams trigger pad-added
|
|
|
|
gst_element_set_state(pipe_, GST_STATE_READY);
|
|
|
|
g_signal_connect(webrtc_, "pad-added", G_CALLBACK(addDecodeBin), pipe_);
|
|
|
|
|
|
|
|
// capture ICE gathering completion
|
|
|
|
g_signal_connect(
|
|
|
|
webrtc_, "notify::ice-gathering-state", G_CALLBACK(iceGatheringStateChanged), nullptr);
|
|
|
|
|
|
|
|
// webrtcbin lifetime is the same as that of the pipeline
|
|
|
|
gst_object_unref(webrtc_);
|
|
|
|
|
|
|
|
// start the pipeline
|
|
|
|
GstStateChangeReturn ret = gst_element_set_state(pipe_, GST_STATE_PLAYING);
|
|
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
|
|
|
nhlog::ui()->error("WebRTC: unable to start pipeline");
|
|
|
|
end();
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2024-04-05 04:57:28 +03:00
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipe_), GST_DEBUG_GRAPH_SHOW_VERBOSE, "start");
|
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
|
|
|
|
busWatchId_ = gst_bus_add_watch(bus, newBusMessage, this);
|
|
|
|
gst_object_unref(bus);
|
|
|
|
emit stateChanged(State::INITIATED);
|
|
|
|
return true;
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
bool
|
2020-10-27 20:14:06 +03:00
|
|
|
WebRTCSession::createPipeline(int opusPayloadType, int vp8PayloadType)
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
GstDevice *device = devices_.audioDevice();
|
|
|
|
if (!device)
|
|
|
|
return false;
|
2020-08-06 00:56:44 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
GstElement *source = gst_device_create_element(device, nullptr);
|
|
|
|
GstElement *volume = gst_element_factory_make("volume", "srclevel");
|
|
|
|
GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
|
|
|
|
GstElement *resample = gst_element_factory_make("audioresample", nullptr);
|
|
|
|
GstElement *queue1 = gst_element_factory_make("queue", nullptr);
|
|
|
|
GstElement *opusenc = gst_element_factory_make("opusenc", nullptr);
|
|
|
|
GstElement *rtp = gst_element_factory_make("rtpopuspay", nullptr);
|
|
|
|
GstElement *queue2 = gst_element_factory_make("queue", nullptr);
|
|
|
|
GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr);
|
|
|
|
|
|
|
|
GstCaps *rtpcaps = gst_caps_new_simple("application/x-rtp",
|
|
|
|
"media",
|
|
|
|
G_TYPE_STRING,
|
|
|
|
"audio",
|
|
|
|
"encoding-name",
|
|
|
|
G_TYPE_STRING,
|
|
|
|
"OPUS",
|
|
|
|
"payload",
|
|
|
|
G_TYPE_INT,
|
|
|
|
opusPayloadType,
|
|
|
|
nullptr);
|
|
|
|
g_object_set(capsfilter, "caps", rtpcaps, nullptr);
|
|
|
|
gst_caps_unref(rtpcaps);
|
|
|
|
|
|
|
|
GstElement *webrtcbin = gst_element_factory_make("webrtcbin", "webrtcbin");
|
|
|
|
g_object_set(webrtcbin, "bundle-policy", GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE, nullptr);
|
|
|
|
|
|
|
|
pipe_ = gst_pipeline_new(nullptr);
|
|
|
|
gst_bin_add_many(GST_BIN(pipe_),
|
|
|
|
source,
|
|
|
|
volume,
|
|
|
|
convert,
|
|
|
|
resample,
|
|
|
|
queue1,
|
|
|
|
opusenc,
|
|
|
|
rtp,
|
|
|
|
queue2,
|
|
|
|
capsfilter,
|
|
|
|
webrtcbin,
|
|
|
|
nullptr);
|
|
|
|
|
|
|
|
if (!gst_element_link_many(source,
|
|
|
|
volume,
|
|
|
|
convert,
|
|
|
|
resample,
|
|
|
|
queue1,
|
|
|
|
opusenc,
|
|
|
|
rtp,
|
|
|
|
queue2,
|
|
|
|
capsfilter,
|
|
|
|
webrtcbin,
|
|
|
|
nullptr)) {
|
|
|
|
nhlog::ui()->error("WebRTC: failed to link audio pipeline elements");
|
|
|
|
return false;
|
|
|
|
}
|
2020-11-09 18:51:17 +03:00
|
|
|
|
2024-04-05 04:57:28 +03:00
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipe_), GST_DEBUG_GRAPH_SHOW_VERBOSE, "created");
|
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
return callType_ == CallType::VOICE || isRemoteVideoSendOnly_
|
|
|
|
? true
|
|
|
|
: addVideoPipeline(vp8PayloadType);
|
2020-10-27 20:14:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WebRTCSession::addVideoPipeline(int vp8PayloadType)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
// allow incoming video calls despite localUser having no webcam
|
|
|
|
if (callType_ == CallType::VIDEO && !devices_.haveCamera())
|
|
|
|
return !isOffering_;
|
|
|
|
|
|
|
|
auto settings = ChatPage::instance()->userSettings();
|
|
|
|
GstElement *camerafilter = nullptr;
|
|
|
|
GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr);
|
|
|
|
GstElement *tee = gst_element_factory_make("tee", "videosrctee");
|
|
|
|
gst_bin_add_many(GST_BIN(pipe_), videoconvert, tee, nullptr);
|
2023-03-11 16:36:51 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (callType_ == CallType::VIDEO || (settings->screenSharePiP() && devices_.haveCamera())) {
|
|
|
|
std::pair<int, int> resolution;
|
|
|
|
std::pair<int, int> frameRate;
|
|
|
|
GstDevice *device = devices_.videoDevice(resolution, frameRate);
|
|
|
|
if (!device)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
GstElement *camera = gst_device_create_element(device, nullptr);
|
|
|
|
GstCaps *caps = gst_caps_new_simple("video/x-raw",
|
|
|
|
"width",
|
|
|
|
G_TYPE_INT,
|
|
|
|
resolution.first,
|
|
|
|
"height",
|
|
|
|
G_TYPE_INT,
|
|
|
|
resolution.second,
|
|
|
|
"framerate",
|
|
|
|
GST_TYPE_FRACTION,
|
|
|
|
frameRate.first,
|
|
|
|
frameRate.second,
|
|
|
|
nullptr);
|
|
|
|
camerafilter = gst_element_factory_make("capsfilter", "camerafilter");
|
|
|
|
g_object_set(camerafilter, "caps", caps, nullptr);
|
|
|
|
gst_caps_unref(caps);
|
2021-02-20 19:26:53 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
gst_bin_add_many(GST_BIN(pipe_), camera, camerafilter, nullptr);
|
|
|
|
if (!gst_element_link_many(camera, videoconvert, camerafilter, nullptr)) {
|
|
|
|
nhlog::ui()->error("WebRTC: failed to link camera elements");
|
|
|
|
return false;
|
2021-02-20 19:26:53 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
if (callType_ == CallType::VIDEO && !gst_element_link(camerafilter, tee)) {
|
|
|
|
nhlog::ui()->error("WebRTC: failed to link camerafilter -> tee");
|
|
|
|
return false;
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (callType_ == CallType::SCREEN) {
|
|
|
|
nhlog::ui()->debug("WebRTC: screen share frame rate: {} fps",
|
|
|
|
settings->screenShareFrameRate());
|
|
|
|
nhlog::ui()->debug("WebRTC: screen share picture-in-picture: {}",
|
|
|
|
settings->screenSharePiP());
|
|
|
|
nhlog::ui()->debug("WebRTC: screen share request remote camera: {}",
|
|
|
|
settings->screenShareRemoteVideo());
|
|
|
|
nhlog::ui()->debug("WebRTC: screen share hide mouse cursor: {}",
|
|
|
|
settings->screenShareHideCursor());
|
|
|
|
|
2023-03-11 16:36:51 +03:00
|
|
|
GstElement *screencastsrc = nullptr;
|
|
|
|
|
|
|
|
if (screenShareType_ == ScreenShareType::X11) {
|
|
|
|
GstElement *ximagesrc = gst_element_factory_make("ximagesrc", "screenshare");
|
|
|
|
if (!ximagesrc) {
|
|
|
|
nhlog::ui()->error("WebRTC: failed to create ximagesrc");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
g_object_set(ximagesrc, "use-damage", FALSE, nullptr);
|
|
|
|
g_object_set(ximagesrc, "xid", shareWindowId_, nullptr);
|
|
|
|
g_object_set(ximagesrc, "show-pointer", !settings->screenShareHideCursor(), nullptr);
|
|
|
|
g_object_set(ximagesrc, "do-timestamp", (gboolean)1, nullptr);
|
|
|
|
|
|
|
|
gst_bin_add(GST_BIN(pipe_), ximagesrc);
|
|
|
|
screencastsrc = ximagesrc;
|
2024-05-10 22:22:58 +03:00
|
|
|
} 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;
|
2023-03-11 16:36:51 +03:00
|
|
|
} else {
|
|
|
|
ScreenCastPortal &sc_portal = ScreenCastPortal::instance();
|
|
|
|
GstElement *pipewiresrc = gst_element_factory_make("pipewiresrc", "screenshare");
|
|
|
|
if (!pipewiresrc) {
|
|
|
|
nhlog::ui()->error("WebRTC: failed to create pipewiresrc");
|
|
|
|
gst_object_unref(pipe_);
|
|
|
|
pipe_ = nullptr;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const ScreenCastPortal::Stream *stream = sc_portal.getStream();
|
|
|
|
if (stream == nullptr) {
|
|
|
|
nhlog::ui()->error("xdg-desktop-portal stream not started");
|
|
|
|
gst_object_unref(pipe_);
|
|
|
|
pipe_ = nullptr;
|
|
|
|
return false;
|
|
|
|
}
|
2023-03-30 00:01:53 +03:00
|
|
|
g_object_set(pipewiresrc, "fd", (gint)stream->fd.fileDescriptor(), nullptr);
|
2023-03-11 16:36:51 +03:00
|
|
|
std::string path = std::to_string(stream->nodeId);
|
|
|
|
g_object_set(pipewiresrc, "path", path.c_str(), nullptr);
|
|
|
|
g_object_set(pipewiresrc, "do-timestamp", (gboolean)1, nullptr);
|
|
|
|
gst_bin_add(GST_BIN(pipe_), pipewiresrc);
|
|
|
|
GstElement *videorate = gst_element_factory_make("videorate", nullptr);
|
|
|
|
gst_bin_add(GST_BIN(pipe_), videorate);
|
|
|
|
if (!gst_element_link(pipewiresrc, videorate)) {
|
|
|
|
nhlog::ui()->error("WebRTC: failed to link pipewiresrc -> videorate");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
screencastsrc = videorate;
|
2021-02-18 23:55:29 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
GstCaps *caps = gst_caps_new_simple("video/x-raw",
|
2023-03-11 16:36:51 +03:00
|
|
|
"format",
|
|
|
|
G_TYPE_STRING,
|
|
|
|
"I420", // For vp8enc
|
2021-09-18 01:22:33 +03:00
|
|
|
"framerate",
|
|
|
|
GST_TYPE_FRACTION,
|
|
|
|
settings->screenShareFrameRate(),
|
|
|
|
1,
|
|
|
|
nullptr);
|
|
|
|
GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr);
|
|
|
|
g_object_set(capsfilter, "caps", caps, nullptr);
|
|
|
|
gst_caps_unref(caps);
|
2023-03-11 16:36:51 +03:00
|
|
|
gst_bin_add(GST_BIN(pipe_), capsfilter);
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
if (settings->screenSharePiP() && devices_.haveCamera()) {
|
|
|
|
GstElement *compositor = gst_element_factory_make("compositor", nullptr);
|
|
|
|
g_object_set(compositor, "background", 1, nullptr);
|
|
|
|
gst_bin_add(GST_BIN(pipe_), compositor);
|
2023-03-11 16:36:51 +03:00
|
|
|
if (!gst_element_link_many(screencastsrc, compositor, capsfilter, tee, nullptr)) {
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->error("WebRTC: failed to link screen share elements");
|
|
|
|
return false;
|
|
|
|
}
|
2021-02-18 23:55:29 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
GstPad *srcpad = gst_element_get_static_pad(camerafilter, "src");
|
2022-02-21 20:33:19 +03:00
|
|
|
remotePiPSinkPad_ = gst_element_request_pad_simple(compositor, "sink_%u");
|
2021-09-18 01:22:33 +03:00
|
|
|
if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, remotePiPSinkPad_))) {
|
|
|
|
nhlog::ui()->error("WebRTC: failed to link camerafilter -> compositor");
|
|
|
|
gst_object_unref(srcpad);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
gst_object_unref(srcpad);
|
2023-03-11 16:36:51 +03:00
|
|
|
} else if (!gst_element_link_many(screencastsrc, videoconvert, capsfilter, tee, nullptr)) {
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->error("WebRTC: failed to link screen share elements");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GstElement *queue = gst_element_factory_make("queue", nullptr);
|
|
|
|
GstElement *vp8enc = gst_element_factory_make("vp8enc", nullptr);
|
|
|
|
g_object_set(vp8enc, "deadline", 1, nullptr);
|
|
|
|
g_object_set(vp8enc, "error-resilient", 1, nullptr);
|
|
|
|
GstElement *rtpvp8pay = gst_element_factory_make("rtpvp8pay", nullptr);
|
|
|
|
GstElement *rtpqueue = gst_element_factory_make("queue", nullptr);
|
|
|
|
GstElement *rtpcapsfilter = gst_element_factory_make("capsfilter", nullptr);
|
|
|
|
GstCaps *rtpcaps = gst_caps_new_simple("application/x-rtp",
|
|
|
|
"media",
|
|
|
|
G_TYPE_STRING,
|
|
|
|
"video",
|
|
|
|
"encoding-name",
|
|
|
|
G_TYPE_STRING,
|
|
|
|
"VP8",
|
|
|
|
"payload",
|
|
|
|
G_TYPE_INT,
|
|
|
|
vp8PayloadType,
|
|
|
|
nullptr);
|
|
|
|
g_object_set(rtpcapsfilter, "caps", rtpcaps, nullptr);
|
|
|
|
gst_caps_unref(rtpcaps);
|
|
|
|
|
|
|
|
gst_bin_add_many(GST_BIN(pipe_), queue, vp8enc, rtpvp8pay, rtpqueue, rtpcapsfilter, nullptr);
|
|
|
|
|
|
|
|
GstElement *webrtcbin = gst_bin_get_by_name(GST_BIN(pipe_), "webrtcbin");
|
|
|
|
if (!gst_element_link_many(
|
|
|
|
tee, queue, vp8enc, rtpvp8pay, rtpqueue, rtpcapsfilter, webrtcbin, nullptr)) {
|
|
|
|
nhlog::ui()->error("WebRTC: failed to link rtp video elements");
|
2020-10-27 20:14:06 +03:00
|
|
|
gst_object_unref(webrtcbin);
|
2021-09-18 01:22:33 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (callType_ == CallType::SCREEN &&
|
|
|
|
!ChatPage::instance()->userSettings()->screenShareRemoteVideo()) {
|
|
|
|
GArray *transceivers;
|
|
|
|
g_signal_emit_by_name(webrtcbin, "get-transceivers", &transceivers);
|
|
|
|
GstWebRTCRTPTransceiver *transceiver =
|
|
|
|
g_array_index(transceivers, GstWebRTCRTPTransceiver *, 1);
|
2021-11-19 20:19:16 +03:00
|
|
|
g_object_set(
|
|
|
|
transceiver, "direction", GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, nullptr);
|
2021-09-18 01:22:33 +03:00
|
|
|
g_array_unref(transceivers);
|
|
|
|
}
|
|
|
|
|
|
|
|
gst_object_unref(webrtcbin);
|
2024-04-05 04:57:28 +03:00
|
|
|
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipe_), GST_DEBUG_GRAPH_SHOW_VERBOSE, "addvideo");
|
2021-09-18 01:22:33 +03:00
|
|
|
return true;
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-12-17 20:45:54 +03:00
|
|
|
bool
|
2021-02-22 00:30:10 +03:00
|
|
|
WebRTCSession::haveLocalPiP() const
|
2020-12-17 20:45:54 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (state_ >= State::INITIATED) {
|
|
|
|
if (callType_ == CallType::VOICE || isRemoteVideoRecvOnly_)
|
|
|
|
return false;
|
|
|
|
else if (callType_ == CallType::SCREEN)
|
|
|
|
return true;
|
|
|
|
else {
|
|
|
|
GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe_), "videosrctee");
|
|
|
|
if (tee) {
|
|
|
|
gst_object_unref(tee);
|
|
|
|
return true;
|
|
|
|
}
|
2020-12-17 20:45:54 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
return false;
|
2020-12-17 20:45:54 +03:00
|
|
|
}
|
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
bool
|
2020-09-25 17:26:36 +03:00
|
|
|
WebRTCSession::isMicMuted() const
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (state_ < State::INITIATED)
|
|
|
|
return false;
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
|
|
|
|
gboolean muted;
|
|
|
|
g_object_get(srclevel, "mute", &muted, nullptr);
|
|
|
|
gst_object_unref(srclevel);
|
|
|
|
return muted;
|
2020-09-25 17:26:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WebRTCSession::toggleMicMute()
|
2020-07-11 02:19:48 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (state_ < State::INITIATED)
|
|
|
|
return false;
|
2020-08-01 21:31:10 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
|
|
|
|
gboolean muted;
|
|
|
|
g_object_get(srclevel, "mute", &muted, nullptr);
|
|
|
|
g_object_set(srclevel, "mute", !muted, nullptr);
|
|
|
|
gst_object_unref(srclevel);
|
2024-04-05 04:57:28 +03:00
|
|
|
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipe_), GST_DEBUG_GRAPH_SHOW_VERBOSE, "togglemute");
|
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
return !muted;
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
|
|
|
|
2020-11-09 18:51:17 +03:00
|
|
|
void
|
2021-02-22 00:30:10 +03:00
|
|
|
WebRTCSession::toggleLocalPiP()
|
2020-11-09 18:51:17 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (localPiPSinkPad_) {
|
|
|
|
guint zorder;
|
|
|
|
g_object_get(localPiPSinkPad_, "zorder", &zorder, nullptr);
|
|
|
|
g_object_set(localPiPSinkPad_, "zorder", zorder ? 0 : 2, nullptr);
|
|
|
|
}
|
2020-11-09 18:51:17 +03:00
|
|
|
}
|
|
|
|
|
2021-02-20 19:26:53 +03:00
|
|
|
void
|
|
|
|
WebRTCSession::clear()
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
callType_ = webrtc::CallType::VOICE;
|
|
|
|
isOffering_ = false;
|
|
|
|
isRemoteVideoRecvOnly_ = false;
|
|
|
|
isRemoteVideoSendOnly_ = false;
|
|
|
|
videoItem_ = nullptr;
|
|
|
|
pipe_ = nullptr;
|
|
|
|
webrtc_ = nullptr;
|
|
|
|
busWatchId_ = 0;
|
|
|
|
shareWindowId_ = 0;
|
|
|
|
haveAudioStream_ = false;
|
|
|
|
haveVideoStream_ = false;
|
|
|
|
localPiPSinkPad_ = nullptr;
|
|
|
|
remotePiPSinkPad_ = nullptr;
|
|
|
|
localsdp_.clear();
|
|
|
|
localcandidates_.clear();
|
2021-02-20 19:26:53 +03:00
|
|
|
}
|
|
|
|
|
2020-08-01 21:31:10 +03:00
|
|
|
void
|
|
|
|
WebRTCSession::end()
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->debug("WebRTC: ending session");
|
|
|
|
keyFrameRequestData_ = KeyFrameRequestData{};
|
|
|
|
if (pipe_) {
|
2024-04-05 04:57:28 +03:00
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipe_), GST_DEBUG_GRAPH_SHOW_VERBOSE, "end");
|
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
gst_element_set_state(pipe_, GST_STATE_NULL);
|
|
|
|
gst_object_unref(pipe_);
|
|
|
|
pipe_ = nullptr;
|
|
|
|
if (busWatchId_) {
|
|
|
|
g_source_remove(busWatchId_);
|
|
|
|
busWatchId_ = 0;
|
2020-08-01 21:31:10 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-11-09 18:51:17 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
clear();
|
|
|
|
if (state_ != State::DISCONNECTED)
|
|
|
|
emit stateChanged(State::DISCONNECTED);
|
2020-07-11 02:19:48 +03:00
|
|
|
}
|
2020-08-06 00:56:44 +03:00
|
|
|
|
2020-10-27 20:14:06 +03:00
|
|
|
#else
|
|
|
|
|
2020-08-14 02:03:27 +03:00
|
|
|
bool
|
2023-03-11 16:36:51 +03:00
|
|
|
WebRTCSession::havePlugins(bool, bool, ScreenShareType, std::string *)
|
2020-08-14 02:03:27 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
return false;
|
2020-08-14 02:03:27 +03:00
|
|
|
}
|
|
|
|
|
2020-12-17 20:45:54 +03:00
|
|
|
bool
|
2021-02-22 00:30:10 +03:00
|
|
|
WebRTCSession::haveLocalPiP() const
|
2020-12-17 20:45:54 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
return false;
|
2020-12-17 20:45:54 +03:00
|
|
|
}
|
|
|
|
|
2021-11-22 02:32:49 +03:00
|
|
|
// clang-format off
|
|
|
|
// clang-format < 12 is buggy on this
|
|
|
|
bool
|
2023-03-11 16:36:51 +03:00
|
|
|
WebRTCSession::createOffer(webrtc::CallType,
|
|
|
|
ScreenShareType screenShareType,
|
|
|
|
uint32_t)
|
2021-11-22 02:32:49 +03:00
|
|
|
{
|
2023-03-11 16:36:51 +03:00
|
|
|
(void)screenShareType;
|
2021-11-22 02:32:49 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// clang-format on
|
2020-08-14 02:03:27 +03:00
|
|
|
|
2022-10-14 16:49:05 +03:00
|
|
|
bool
|
|
|
|
WebRTCSession::acceptNegotiation(const std::string &)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-08-14 02:03:27 +03:00
|
|
|
bool
|
2020-10-27 20:14:06 +03:00
|
|
|
WebRTCSession::acceptOffer(const std::string &)
|
2020-08-14 02:03:27 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
return false;
|
2020-08-14 02:03:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2020-10-27 20:14:06 +03:00
|
|
|
WebRTCSession::acceptAnswer(const std::string &)
|
2020-08-14 02:03:27 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
return false;
|
2020-08-14 02:03:27 +03:00
|
|
|
}
|
|
|
|
|
2020-10-27 20:14:06 +03:00
|
|
|
void
|
2022-06-30 13:33:44 +03:00
|
|
|
WebRTCSession::acceptICECandidates(
|
|
|
|
const std::vector<mtx::events::voip::CallCandidates::Candidate> &)
|
2022-09-25 21:05:08 +03:00
|
|
|
{
|
|
|
|
}
|
2020-10-27 20:14:06 +03:00
|
|
|
|
2020-08-14 02:03:27 +03:00
|
|
|
bool
|
2020-09-25 18:10:45 +03:00
|
|
|
WebRTCSession::isMicMuted() const
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
return false;
|
2020-09-25 18:10:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WebRTCSession::toggleMicMute()
|
2020-08-14 02:03:27 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
return false;
|
2020-08-14 02:03:27 +03:00
|
|
|
}
|
|
|
|
|
2020-11-09 18:51:17 +03:00
|
|
|
void
|
2021-02-22 00:30:10 +03:00
|
|
|
WebRTCSession::toggleLocalPiP()
|
2022-09-25 21:05:08 +03:00
|
|
|
{
|
|
|
|
}
|
2020-11-09 18:51:17 +03:00
|
|
|
|
2020-08-14 02:03:27 +03:00
|
|
|
void
|
|
|
|
WebRTCSession::end()
|
2022-09-25 21:05:08 +03:00
|
|
|
{
|
|
|
|
}
|
2020-08-14 02:03:27 +03:00
|
|
|
|
|
|
|
#endif
|
2024-03-16 03:24:33 +03:00
|
|
|
|
|
|
|
#include "moc_WebRTCSession.cpp"
|