From b7066362761815f6a2cbb1a9511f9c44b45838a6 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Thu, 6 Mar 2025 22:06:14 +0100 Subject: [PATCH 01/10] Add voice call example This is useful for testing the CallManager and the upcoming DTLS-SRTP support. --- examples/CMakeLists.txt | 4 +- examples/audio_call/AudioCall.cpp | 149 +++++++++++++++++++++++++++++ examples/audio_call/CMakeLists.txt | 25 +++++ 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 examples/audio_call/AudioCall.cpp create mode 100644 examples/audio_call/CMakeLists.txt diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8f83ce27f..29225cf99 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -22,4 +22,6 @@ add_simple_example(8_server) add_subdirectory(example_5_rpcInterface) add_subdirectory(example_6_rpcClient) add_subdirectory(example_9_vCard) - +if(WITH_GSTREAMER) + add_subdirectory(audio_call) +endif() diff --git a/examples/audio_call/AudioCall.cpp b/examples/audio_call/AudioCall.cpp new file mode 100644 index 000000000..99ccda896 --- /dev/null +++ b/examples/audio_call/AudioCall.cpp @@ -0,0 +1,149 @@ +// SPDX-FileCopyrightText: 2025 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include +#include +#include + +#include +#include + +#include +#include +#ifdef Q_OS_UNIX +#include +#endif + +using namespace std::chrono_literals; + +#ifdef Q_OS_UNIX +void handleSignal(int signal) +{ + if (signal == SIGINT) { + // print newline + qDebug() << ""; + if (QCoreApplication::instance()) { + QCoreApplication::instance()->quit(); + } + } +} +#endif + +void setupCallStream(QXmppCall *call) +{ + auto *gstPipeline = call->pipeline(); + auto *stream = call->audioStream(); + + qDebug() << "[Call] Setup call stream" << stream->media(); + if (stream->media() == u"audio") { + // output receiving audio + stream->setReceivePadCallback([gstPipeline](GstPad *receivePad) { + GstElement *output = gst_parse_bin_from_description("audioresample ! audioconvert ! autoaudiosink", true, nullptr); + if (!gst_bin_add(GST_BIN(gstPipeline), output)) { + qFatal("Failed to add input to pipeline"); + return; + } + + gst_pad_link(receivePad, gst_element_get_static_pad(output, "sink")); + gst_element_sync_state_with_parent(output); + + qDebug() << "[Call] receive pad set"; + }); + + // record and send microphone + stream->setSendPadCallback([gstPipeline](GstPad *sendPad) { + GstElement *output = gst_parse_bin_from_description("autoaudiosrc ! audioconvert ! audioresample ! queue max-size-time=1000000", true, nullptr); + if (!gst_bin_add(GST_BIN(gstPipeline), output)) { + qFatal("Failed to add input to pipeline"); + return; + } + + gst_pad_link(gst_element_get_static_pad(output, "src"), sendPad); + gst_element_sync_state_with_parent(output); + + qDebug() << "[Call] send pad set"; + }); + } +}; + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + +#ifdef Q_OS_UNIX + // set signal handler for SIGINT (CTRL+C) + std::signal(SIGINT, handleSignal); +#endif + + QXmppClient client; + auto *rosterManager = client.findExtension(); + auto *callManager = client.addNewExtension(); + client.logger()->setLoggingType(QXmppLogger::StdoutLogging); + client.logger()->setMessageTypes(QXmppLogger::MessageType::AnyMessage); + + // client config + QXmppConfiguration config; + config.setJid(qEnvironmentVariable("QXMPP_JID")); + config.setPassword(qEnvironmentVariable("QXMPP_PASSWORD")); + config.setResourcePrefix("Call"); + + // call manager config + callManager->setStunServer(QHostAddress(QStringLiteral("stun.nextcloud.com")), 443); + // callManager->setTurnServer(); + // callManager->setTurnUser(client.configuration().jid()); + // callManager->setTurnUser(client.configuration().password()); + + client.connectToServer(config); + + // on connect + QObject::connect(&client, &QXmppClient::connected, &app, [&app, &config, rosterManager, callManager] { + // wait 1 second for presence of other clients to arrive + QTimer::singleShot(1s, [&app, &config, rosterManager, callManager] { + // other resources of our account + auto otherResources = rosterManager->getResources(config.jidBare()); + otherResources.removeOne(config.resource()); + if (otherResources.isEmpty()) { + qDebug() << "[Call] No other clients to call on this account."; + return; + } + + // call first JID + auto *call = callManager->call(config.jidBare() + u'/' + otherResources.first()); + Q_ASSERT(call != nullptr); + + QObject::connect(call, &QXmppCall::connected, &app, [call]() { + qDebug() << "[Call] Call to" << call->jid() << "connected!"; + setupCallStream(call); + }); + + QObject::connect(call, &QXmppCall::ringing, [call]() { + qDebug() << "[Call] Ringing" << call->jid() << "..."; + }); + QObject::connect(call, &QXmppCall::finished, [call]() { + qDebug() << "[Call] Call with" << call->jid() << "ended."; + }); + }); + }); + + // on call + QObject::connect(callManager, &QXmppCallManager::callReceived, &app, [&app](QXmppCall *call) { + qDebug() << "[Call] Received incoming call from" << call->jid() << "-" << "Accepting."; + call->accept(); + if (call->audioStream()) { + setupCallStream(call); + } + + QObject::connect(call, &QXmppCall::streamCreated, call, [call](QXmppCallStream *stream) { + setupCallStream(call); + }); + }); + + // disconnect from server to avoid having multiple open dead sessions when testing + QObject::connect(&app, &QCoreApplication::aboutToQuit, &app, [&client]() { + qDebug() << "Closing connection..."; + client.disconnectFromServer(); + }); + + return app.exec(); +} diff --git a/examples/audio_call/CMakeLists.txt b/examples/audio_call/CMakeLists.txt new file mode 100644 index 000000000..95a5372cd --- /dev/null +++ b/examples/audio_call/CMakeLists.txt @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2025 Linus Jahn +# +# SPDX-License-Identifier: CC0-1.0 + +find_package(GStreamer REQUIRED) +find_package(GLIB2 REQUIRED) +find_package(GObject REQUIRED) + +add_executable(audio_call AudioCall.cpp) + +target_link_libraries(audio_call + PUBLIC + ${QXMPP_TARGET} + ${GLIB2_LIBRARIES} + ${GOBJECT_LIBRARIES} + ${GSTREAMER_LIBRARY} +) + +target_include_directories(audio_call + PUBLIC + ${QXMPP_TARGET} + ${GLIB2_INCLUDE_DIR} + ${GOBJECT_INCLUDE_DIR} + ${GSTREAMER_INCLUDE_DIRS} +) From 6b56e1cdb3feb3d1eb29d09866fc28410c4877b3 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Wed, 19 Mar 2025 22:33:56 +0100 Subject: [PATCH 02/10] Fix by deleting QXmppCall --- examples/audio_call/AudioCall.cpp | 48 +++++++++++++++++-------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/examples/audio_call/AudioCall.cpp b/examples/audio_call/AudioCall.cpp index 99ccda896..4f9c3e16a 100644 --- a/examples/audio_call/AudioCall.cpp +++ b/examples/audio_call/AudioCall.cpp @@ -96,10 +96,31 @@ int main(int argc, char *argv[]) client.connectToServer(config); + auto setupCall = [&app, callManager](QXmppCall *call) { + if (call->audioStream()) { + setupCallStream(call); + } + + QObject::connect(call, &QXmppCall::streamCreated, call, [call](QXmppCallStream *stream) { + setupCallStream(call); + }); + + QObject::connect(call, &QXmppCall::connected, &app, [=]() { + qDebug() << "[Call] Call to" << call->jid() << "connected!"; + }); + QObject::connect(call, &QXmppCall::ringing, [=]() { + qDebug() << "[Call] Ringing" << call->jid() << "..."; + }); + QObject::connect(call, &QXmppCall::finished, [=]() { + qDebug() << "[Call] Call with" << call->jid() << "ended. (Deleting)"; + call->deleteLater(); + }); + }; + // on connect - QObject::connect(&client, &QXmppClient::connected, &app, [&app, &config, rosterManager, callManager] { + QObject::connect(&client, &QXmppClient::connected, &app, [=, &config] { // wait 1 second for presence of other clients to arrive - QTimer::singleShot(1s, [&app, &config, rosterManager, callManager] { + QTimer::singleShot(1s, [=, &config] { // other resources of our account auto otherResources = rosterManager->getResources(config.jidBare()); otherResources.removeOne(config.resource()); @@ -112,31 +133,16 @@ int main(int argc, char *argv[]) auto *call = callManager->call(config.jidBare() + u'/' + otherResources.first()); Q_ASSERT(call != nullptr); - QObject::connect(call, &QXmppCall::connected, &app, [call]() { - qDebug() << "[Call] Call to" << call->jid() << "connected!"; - setupCallStream(call); - }); - - QObject::connect(call, &QXmppCall::ringing, [call]() { - qDebug() << "[Call] Ringing" << call->jid() << "..."; - }); - QObject::connect(call, &QXmppCall::finished, [call]() { - qDebug() << "[Call] Call with" << call->jid() << "ended."; - }); + setupCall(call); }); }); - // on call - QObject::connect(callManager, &QXmppCallManager::callReceived, &app, [&app](QXmppCall *call) { + // on incoming call + QObject::connect(callManager, &QXmppCallManager::callReceived, &app, [=](QXmppCall *call) { qDebug() << "[Call] Received incoming call from" << call->jid() << "-" << "Accepting."; call->accept(); - if (call->audioStream()) { - setupCallStream(call); - } - QObject::connect(call, &QXmppCall::streamCreated, call, [call](QXmppCallStream *stream) { - setupCallStream(call); - }); + setupCall(call); }); // disconnect from server to avoid having multiple open dead sessions when testing From c1e931fe9c537c952ad13aaf00773d01d1dc90a8 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 18 Mar 2025 13:46:51 +0100 Subject: [PATCH 03/10] Call: Use new QString-literals and QStringViews --- src/client/QXmppCall.cpp | 21 +++++++++++---------- src/client/QXmppCallStream_p.h | 4 ++-- src/client/QXmppCall_p.h | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/client/QXmppCall.cpp b/src/client/QXmppCall.cpp index 52b649526..dd57ccca9 100644 --- a/src/client/QXmppCall.cpp +++ b/src/client/QXmppCall.cpp @@ -95,21 +95,22 @@ void QXmppCallPrivate::padAdded(GstPad *pad) if (nameParts.size() < 4) { return; } - if (nameParts[0] == QLatin1String("send") && - nameParts[1] == QLatin1String("rtp") && - nameParts[2] == QLatin1String("src")) { + if (nameParts[0] == u"send" && + nameParts[1] == u"rtp" && + nameParts[2] == u"src") { if (nameParts.size() != 4) { return; } int sessionId = nameParts[3].toInt(); auto stream = findStreamById(sessionId); stream->d->addRtpSender(pad); - } else if (nameParts[0] == QLatin1String("recv") || - nameParts[1] == QLatin1String("rtp") || - nameParts[2] == QLatin1String("src")) { + } else if (nameParts[0] == u"recv" || + nameParts[1] == u"rtp" || + nameParts[2] == u"src") { if (nameParts.size() != 6) { return; } + int sessionId = nameParts[3].toInt(); int pt = nameParts[5].toInt(); auto stream = findStreamById(sessionId); @@ -174,7 +175,7 @@ void QXmppCallPrivate::filterGStreamerFormats(QList &formats) } } -QXmppCallStream *QXmppCallPrivate::findStreamByMedia(const QString &media) +QXmppCallStream *QXmppCallPrivate::findStreamByMedia(QStringView media) { for (auto stream : std::as_const(streams)) { if (stream->media() == media) { @@ -184,7 +185,7 @@ QXmppCallStream *QXmppCallPrivate::findStreamByMedia(const QString &media) return nullptr; } -QXmppCallStream *QXmppCallPrivate::findStreamByName(const QString &name) +QXmppCallStream *QXmppCallPrivate::findStreamByName(QStringView name) { for (auto stream : std::as_const(streams)) { if (stream->name() == name) { @@ -721,8 +722,8 @@ void QXmppCall::addVideo() } // create video stream - QLatin1String creator = (d->direction == QXmppCall::OutgoingDirection) ? QLatin1String("initiator") : QLatin1String("responder"); - stream = d->createStream(VIDEO_MEDIA, creator, QLatin1String("webcam")); + QString creator = (d->direction == QXmppCall::OutgoingDirection) ? u"initiator"_s : u"responder"_s; + stream = d->createStream(VIDEO_MEDIA.toString(), creator, u"webcam"_s); d->streams << stream; // build request diff --git a/src/client/QXmppCallStream_p.h b/src/client/QXmppCallStream_p.h index f898f0f60..59dd61f91 100644 --- a/src/client/QXmppCallStream_p.h +++ b/src/client/QXmppCallStream_p.h @@ -29,8 +29,8 @@ class QXmppIceConnection; static const int RTP_COMPONENT = 1; static const int RTCP_COMPONENT = 2; -static const QLatin1String AUDIO_MEDIA("audio"); -static const QLatin1String VIDEO_MEDIA("video"); +constexpr QStringView AUDIO_MEDIA = u"audio"; +constexpr QStringView VIDEO_MEDIA = u"video"; class QXmppCallStreamPrivate : public QObject { diff --git a/src/client/QXmppCall_p.h b/src/client/QXmppCall_p.h index 8b2f3e5c0..f9f43740b 100644 --- a/src/client/QXmppCall_p.h +++ b/src/client/QXmppCall_p.h @@ -56,8 +56,8 @@ class QXmppCallPrivate : public QObject void filterGStreamerFormats(QList &formats); QXmppCallStream *createStream(const QString &media, const QString &creator, const QString &name); - QXmppCallStream *findStreamByMedia(const QString &media); - QXmppCallStream *findStreamByName(const QString &name); + QXmppCallStream *findStreamByMedia(QStringView media); + QXmppCallStream *findStreamByName(QStringView name); QXmppCallStream *findStreamById(const int id); QXmppJingleIq::Content localContent(QXmppCallStream *stream) const; From 7270318906a137df9b1476cb7c93f2cd1fabe8f0 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 18 Mar 2025 13:53:58 +0100 Subject: [PATCH 04/10] Call: Use connect() with lambda captures i/o sender() --- src/client/QXmppCall.cpp | 17 ++--------------- src/client/QXmppCall.h | 4 ++-- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/client/QXmppCall.cpp b/src/client/QXmppCall.cpp index dd57ccca9..ba03cb14a 100644 --- a/src/client/QXmppCall.cpp +++ b/src/client/QXmppCall.cpp @@ -443,7 +443,7 @@ QXmppCallStream *QXmppCallPrivate::createStream(const QString &media, const QStr // connect signals QObject::connect(stream->d->connection, &QXmppIceConnection::localCandidatesChanged, - q, &QXmppCall::localCandidatesChanged); + q, [this, stream]() { q->onLocalCandidatesChanged(stream); }); QObject::connect(stream->d->connection, &QXmppIceConnection::disconnected, q, &QXmppCall::hangup); @@ -656,21 +656,8 @@ void QXmppCall::hangup() /// /// Sends a transport-info to inform the remote party of new local candidates. /// -void QXmppCall::localCandidatesChanged() +void QXmppCall::onLocalCandidatesChanged(QXmppCallStream *stream) { - // find the stream - QXmppIceConnection *conn = qobject_cast(sender()); - QXmppCallStream *stream = nullptr; - for (auto ptr : std::as_const(d->streams)) { - if (ptr->d->connection == conn) { - stream = ptr; - break; - } - } - if (!stream) { - return; - } - QXmppJingleIq iq; iq.setTo(d->jid); iq.setType(QXmppIq::Set); diff --git a/src/client/QXmppCall.h b/src/client/QXmppCall.h index 6650c4a00..ba4f1e9cf 100644 --- a/src/client/QXmppCall.h +++ b/src/client/QXmppCall.h @@ -82,8 +82,8 @@ class QXMPP_EXPORT QXmppCall : public QXmppLoggable Q_SLOT void addVideo(); private: - Q_SLOT void localCandidatesChanged(); - Q_SLOT void terminated(); + void onLocalCandidatesChanged(QXmppCallStream *stream); + void terminated(); QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent); From fd0242e99cc2a667f228da355c86d5cbbea7fec6 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 18 Mar 2025 15:00:58 +0100 Subject: [PATCH 05/10] Call: Use new connect syntax --- src/client/QXmppCall.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/QXmppCall.cpp b/src/client/QXmppCall.cpp index ba03cb14a..eb46171f1 100644 --- a/src/client/QXmppCall.cpp +++ b/src/client/QXmppCall.cpp @@ -322,7 +322,9 @@ void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq) } else if (iq.action() == QXmppJingleIq::SessionInfo) { // notify user - QTimer::singleShot(0, q, SIGNAL(ringing())); + QTimer::singleShot(0, q, [this]() { + Q_EMIT q->ringing(); + }); } else if (iq.action() == QXmppJingleIq::SessionTerminate) { From b591db84cbf3083f58cffc0babafdf3d084ae338 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 18 Mar 2025 17:37:29 +0100 Subject: [PATCH 06/10] Call: Use chrono literals for QTimer --- src/client/QXmppCall.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client/QXmppCall.cpp b/src/client/QXmppCall.cpp index eb46171f1..76002b3b1 100644 --- a/src/client/QXmppCall.cpp +++ b/src/client/QXmppCall.cpp @@ -17,11 +17,16 @@ #include "StringLiterals.h" +#include + +// gstreamer #include #include #include +using namespace std::chrono_literals; + /// \cond QXmppCallPrivate::QXmppCallPrivate(QXmppCall *qq) : direction(QXmppCall::IncomingDirection), @@ -547,7 +552,7 @@ void QXmppCallPrivate::terminate(QXmppJingleIq::Reason::Type reasonType) setState(QXmppCall::DisconnectingState); // schedule forceful termination in 5s - QTimer::singleShot(5000, q, &QXmppCall::terminated); + QTimer::singleShot(5s, q, &QXmppCall::terminated); } /// \endcond From 185e0806ed1d8e58784bdb51914d1244f7bc687a Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 18 Mar 2025 17:38:00 +0100 Subject: [PATCH 07/10] CallStream: Readability: Use 100'000'000 instead of 100000000 --- src/client/QXmppCallStream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/QXmppCallStream.cpp b/src/client/QXmppCallStream.cpp index 0a7881cd4..596027080 100644 --- a/src/client/QXmppCallStream.cpp +++ b/src/client/QXmppCallStream.cpp @@ -97,7 +97,7 @@ QXmppCallStreamPrivate::QXmppCallStreamPrivate(QXmppCallStream *parent, GstEleme // We need frequent RTCP reports for the bandwidth controller GstElement *rtpSession; g_signal_emit_by_name(rtpbin, "get-session", static_cast(id), &rtpSession); - g_object_set(rtpSession, "rtcp-min-interval", 100000000, nullptr); + g_object_set(rtpSession, "rtcp-min-interval", 100'000'000, nullptr); gst_element_sync_state_with_parent(iceReceiveBin); gst_element_sync_state_with_parent(iceSendBin); From be96225212f5e7370907ab8498e35825912c1805 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 18 Mar 2025 18:49:45 +0100 Subject: [PATCH 08/10] Call: Use std ranges to modernize find functions --- src/client/QXmppCall.cpp | 68 ++++++++++++++++++---------------------- src/client/QXmppCall_p.h | 7 +++-- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/src/client/QXmppCall.cpp b/src/client/QXmppCall.cpp index 76002b3b1..fba5e2ca0 100644 --- a/src/client/QXmppCall.cpp +++ b/src/client/QXmppCall.cpp @@ -18,6 +18,7 @@ #include "StringLiterals.h" #include +#include // gstreamer #include @@ -153,7 +154,7 @@ GstCaps *QXmppCallPrivate::ptMap(uint sessionId, uint pt) return nullptr; } -bool QXmppCallPrivate::isFormatSupported(const QString &codecName) const +bool QXmppCallPrivate::isFormatSupported(const QString &codecName) { GstElementFactory *factory; factory = gst_element_factory_find(codecName.toLatin1().data()); @@ -164,67 +165,60 @@ bool QXmppCallPrivate::isFormatSupported(const QString &codecName) const return true; } +bool QXmppCallPrivate::isCodecSupported(const GstCodec &codec) +{ + return isFormatSupported(codec.gstPay) && + isFormatSupported(codec.gstDepay) && + isFormatSupported(codec.gstEnc) && + isFormatSupported(codec.gstDec); +} + void QXmppCallPrivate::filterGStreamerFormats(QList &formats) { - auto it = formats.begin(); - while (it != formats.end()) { - bool supported = isFormatSupported(it->gstPay) && - isFormatSupported(it->gstDepay) && - isFormatSupported(it->gstEnc) && - isFormatSupported(it->gstDec); - if (!supported) { - it = formats.erase(it); - } else { - ++it; - } - } + auto removedRange = std::ranges::remove_if(formats, std::not_fn(isCodecSupported)); + formats.erase(removedRange.begin(), removedRange.end()); } QXmppCallStream *QXmppCallPrivate::findStreamByMedia(QStringView media) { - for (auto stream : std::as_const(streams)) { - if (stream->media() == media) { - return stream; - } + if (auto stream = std::ranges::find(streams, media, &QXmppCallStream::media); + stream != streams.end()) { + return *stream; } return nullptr; } QXmppCallStream *QXmppCallPrivate::findStreamByName(QStringView name) { - for (auto stream : std::as_const(streams)) { - if (stream->name() == name) { - return stream; - } + if (auto stream = std::ranges::find(streams, name, &QXmppCallStream::name); + stream != streams.end()) { + return *stream; } return nullptr; } -QXmppCallStream *QXmppCallPrivate::findStreamById(const int id) +QXmppCallStream *QXmppCallPrivate::findStreamById(int id) { - for (auto stream : std::as_const(streams)) { - if (stream->id() == id) { - return stream; - } + if (auto stream = std::ranges::find(streams, id, &QXmppCallStream::id); + stream != streams.end()) { + return *stream; } return nullptr; } void QXmppCallPrivate::handleAck(const QXmppIq &ack) { - const QString id = ack.id(); - for (int i = 0; i < requests.size(); ++i) { - if (id == requests[i].id()) { - // process acknowledgement - const QXmppJingleIq request = requests.takeAt(i); - q->debug(u"Received ACK for packet %1"_s.arg(id)); + auto request = std::ranges::find(requests, ack.id(), &QXmppJingleIq::id); + if (request != requests.end()) { + // process acknowledgement + q->debug(u"Received ACK for packet %1"_s.arg(ack.id())); - // handle termination - if (request.action() == QXmppJingleIq::SessionTerminate) { - q->terminated(); - } - return; + // handle termination + if (request->action() == QXmppJingleIq::SessionTerminate) { + q->terminated(); } + + requests.erase(request); } } diff --git a/src/client/QXmppCall_p.h b/src/client/QXmppCall_p.h index f9f43740b..2e768e8e1 100644 --- a/src/client/QXmppCall_p.h +++ b/src/client/QXmppCall_p.h @@ -52,13 +52,14 @@ class QXmppCallPrivate : public QObject void ssrcActive(uint sessionId, uint ssrc); void padAdded(GstPad *pad); GstCaps *ptMap(uint sessionId, uint pt); - bool isFormatSupported(const QString &codecName) const; - void filterGStreamerFormats(QList &formats); + static bool isFormatSupported(const QString &codecName); + static bool isCodecSupported(const GstCodec &codec); + static void filterGStreamerFormats(QList &formats); QXmppCallStream *createStream(const QString &media, const QString &creator, const QString &name); QXmppCallStream *findStreamByMedia(QStringView media); QXmppCallStream *findStreamByName(QStringView name); - QXmppCallStream *findStreamById(const int id); + QXmppCallStream *findStreamById(int id); QXmppJingleIq::Content localContent(QXmppCallStream *stream) const; void handleAck(const QXmppIq &iq); From a1538d20b0ec7176cd57d9c7a8ee5ebf2bc3d16c Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 18 Mar 2025 19:04:30 +0100 Subject: [PATCH 09/10] Call::terminate: Use sendIq() task instead of manual IQ ack handling CallPrivate stores all sent jingle requests internally to look for acks and to trigger forceful termination upon the ack to SessionTerminate. This commit replaces this by using the newer sendIq() API and a QXmppTask. --- src/client/QXmppCall.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/client/QXmppCall.cpp b/src/client/QXmppCall.cpp index fba5e2ca0..d91bc0cf9 100644 --- a/src/client/QXmppCall.cpp +++ b/src/client/QXmppCall.cpp @@ -13,6 +13,7 @@ #include "QXmppConstants_p.h" #include "QXmppJingleIq.h" #include "QXmppStun.h" +#include "QXmppTask.h" #include "QXmppUtils.h" #include "StringLiterals.h" @@ -212,12 +213,6 @@ void QXmppCallPrivate::handleAck(const QXmppIq &ack) if (request != requests.end()) { // process acknowledgement q->debug(u"Received ACK for packet %1"_s.arg(ack.id())); - - // handle termination - if (request->action() == QXmppJingleIq::SessionTerminate) { - q->terminated(); - } - requests.erase(request); } } @@ -542,9 +537,14 @@ void QXmppCallPrivate::terminate(QXmppJingleIq::Reason::Type reasonType) iq.setAction(QXmppJingleIq::SessionTerminate); iq.setSid(sid); iq.reason().setType(reasonType); - sendRequest(iq); + setState(QXmppCall::DisconnectingState); + manager->client()->sendIq(std::move(iq)).then(q, [this](auto result) { + // terminate on both success or error + q->terminated(); + }); + // schedule forceful termination in 5s QTimer::singleShot(5s, q, &QXmppCall::terminated); } From 837f311d86f61d067869d2251b22507b38ec7911 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 18 Mar 2025 19:42:47 +0100 Subject: [PATCH 10/10] Call: Remove own IQ ack handling Currently the acks (or missing acks) are not used. Also, the result handling from Client::sendIq() should rather be used. --- src/client/QXmppCall.cpp | 33 +++++++-------------------------- src/client/QXmppCallManager.cpp | 24 ++---------------------- src/client/QXmppCallManager.h | 1 - src/client/QXmppCall_p.h | 5 +---- 4 files changed, 10 insertions(+), 53 deletions(-) diff --git a/src/client/QXmppCall.cpp b/src/client/QXmppCall.cpp index d91bc0cf9..7aadc7497 100644 --- a/src/client/QXmppCall.cpp +++ b/src/client/QXmppCall.cpp @@ -207,16 +207,6 @@ QXmppCallStream *QXmppCallPrivate::findStreamById(int id) return nullptr; } -void QXmppCallPrivate::handleAck(const QXmppIq &ack) -{ - auto request = std::ranges::find(requests, ack.id(), &QXmppJingleIq::id); - if (request != requests.end()) { - // process acknowledgement - q->debug(u"Received ACK for packet %1"_s.arg(ack.id())); - requests.erase(request); - } -} - bool QXmppCallPrivate::handleDescription(QXmppCallStream *stream, const QXmppJingleIq::Content &content) { stream->d->payloadTypes = content.payloadTypes(); @@ -372,7 +362,7 @@ void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq) iq.setAction(QXmppJingleIq::ContentReject); iq.setSid(q->sid()); iq.reason().setType(QXmppJingleIq::Reason::FailedApplication); - sendRequest(iq); + manager->client()->sendIq(std::move(iq)); streams.removeAll(stream); delete stream; return; @@ -385,7 +375,7 @@ void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq) iq.setAction(QXmppJingleIq::ContentAccept); iq.setSid(q->sid()); iq.addContent(localContent(stream)); - sendRequest(iq); + manager->client()->sendIq(std::move(iq)); } else if (iq.action() == QXmppJingleIq::TransportInfo) { @@ -481,7 +471,7 @@ bool QXmppCallPrivate::sendAck(const QXmppJingleIq &iq) return manager->client()->sendPacket(ack); } -bool QXmppCallPrivate::sendInvite() +void QXmppCallPrivate::sendInvite() { // create audio stream QXmppCallStream *stream = findStreamByMedia(AUDIO_MEDIA); @@ -494,16 +484,7 @@ bool QXmppCallPrivate::sendInvite() iq.setInitiator(ownJid); iq.setSid(sid); iq.addContent(localContent(stream)); - return sendRequest(iq); -} - -/// -/// Sends a Jingle IQ and adds it to outstanding requests. -/// -bool QXmppCallPrivate::sendRequest(const QXmppJingleIq &iq) -{ - requests << iq; - return manager->client()->sendPacket(iq); + manager->client()->send(std::move(iq)); } void QXmppCallPrivate::setState(QXmppCall::State newState) @@ -587,7 +568,7 @@ void QXmppCall::accept() iq.setResponder(d->ownJid); iq.setSid(d->sid); iq.addContent(d->localContent(stream)); - d->sendRequest(iq); + d->manager->client()->sendIq(std::move(iq)); // notify user Q_EMIT d->manager->callStarted(this); @@ -665,7 +646,7 @@ void QXmppCall::onLocalCandidatesChanged(QXmppCallStream *stream) iq.setAction(QXmppJingleIq::TransportInfo); iq.setSid(d->sid); iq.addContent(d->localContent(stream)); - d->sendRequest(iq); + d->manager->client()->sendIq(std::move(iq)); } /// @@ -721,5 +702,5 @@ void QXmppCall::addVideo() iq.setAction(QXmppJingleIq::ContentAdd); iq.setSid(d->sid); iq.addContent(d->localContent(stream)); - d->sendRequest(iq); + d->manager->client()->sendIq(std::move(iq)); } diff --git a/src/client/QXmppCallManager.cpp b/src/client/QXmppCallManager.cpp index 5da0218f3..eddd254f2 100644 --- a/src/client/QXmppCallManager.cpp +++ b/src/client/QXmppCallManager.cpp @@ -10,6 +10,7 @@ #include "QXmppClient.h" #include "QXmppConstants_p.h" #include "QXmppJingleIq.h" +#include "QXmppTask.h" #include "QXmppUtils.h" #include "StringLiterals.h" @@ -95,9 +96,6 @@ void QXmppCallManager::onRegistered(QXmppClient *client) connect(client, &QXmppClient::disconnected, this, &QXmppCallManager::_q_disconnected); - connect(client, &QXmppClient::iqReceived, - this, &QXmppCallManager::_q_iqReceived); - connect(client, &QXmppClient::presenceReceived, this, &QXmppCallManager::_q_presenceReceived); } @@ -107,9 +105,6 @@ void QXmppCallManager::onUnregistered(QXmppClient *client) disconnect(client, &QXmppClient::disconnected, this, &QXmppCallManager::_q_disconnected); - disconnect(client, &QXmppClient::iqReceived, - this, &QXmppCallManager::_q_iqReceived); - disconnect(client, &QXmppClient::presenceReceived, this, &QXmppCallManager::_q_presenceReceived); } @@ -228,21 +223,6 @@ void QXmppCallManager::_q_disconnected() } } -/// -/// Handles acknowledgements. -/// -void QXmppCallManager::_q_iqReceived(const QXmppIq &ack) -{ - if (ack.type() != QXmppIq::Result) { - return; - } - - // find request - for (auto *call : std::as_const(d->calls)) { - call->d->handleAck(ack); - } -} - /// /// Handles a Jingle IQ. /// @@ -292,7 +272,7 @@ void QXmppCallManager::_q_jingleIqReceived(const QXmppJingleIq &iq) ringing.setType(QXmppIq::Set); ringing.setSid(call->sid()); ringing.setRtpSessionState(QXmppJingleIq::RtpSessionStateRinging()); - call->d->sendRequest(ringing); + client()->sendIq(std::move(ringing)); // notify user Q_EMIT callReceived(call); diff --git a/src/client/QXmppCallManager.h b/src/client/QXmppCallManager.h index 015c5bcff..f7f5b1042 100644 --- a/src/client/QXmppCallManager.h +++ b/src/client/QXmppCallManager.h @@ -82,7 +82,6 @@ public Q_SLOTS: private Q_SLOTS: void _q_callDestroyed(QObject *object); void _q_disconnected(); - void _q_iqReceived(const QXmppIq &iq); void _q_jingleIqReceived(const QXmppJingleIq &iq); void _q_presenceReceived(const QXmppPresence &presence); diff --git a/src/client/QXmppCall_p.h b/src/client/QXmppCall_p.h index 2e768e8e1..33a48dff2 100644 --- a/src/client/QXmppCall_p.h +++ b/src/client/QXmppCall_p.h @@ -62,21 +62,18 @@ class QXmppCallPrivate : public QObject QXmppCallStream *findStreamById(int id); QXmppJingleIq::Content localContent(QXmppCallStream *stream) const; - void handleAck(const QXmppIq &iq); bool handleDescription(QXmppCallStream *stream, const QXmppJingleIq::Content &content); void handleRequest(const QXmppJingleIq &iq); bool handleTransport(QXmppCallStream *stream, const QXmppJingleIq::Content &content); void setState(QXmppCall::State state); bool sendAck(const QXmppJingleIq &iq); - bool sendInvite(); - bool sendRequest(const QXmppJingleIq &iq); + void sendInvite(); void terminate(QXmppJingleIq::Reason::Type reasonType); QXmppCall::Direction direction; QString jid; QString ownJid; QXmppCallManager *manager; - QList requests; QString sid; QXmppCall::State state;