Skip to content
This repository was archived by the owner on Mar 20, 2025. It is now read-only.

Commit 72e6b72

Browse files
committed
[WIP] Add voice call example
This is useful for testing the CallManager and the upcoming DTLS-SRTP support.
1 parent ef60795 commit 72e6b72

File tree

3 files changed

+177
-1
lines changed

3 files changed

+177
-1
lines changed

examples/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ add_simple_example(8_server)
2222
add_subdirectory(example_5_rpcInterface)
2323
add_subdirectory(example_6_rpcClient)
2424
add_subdirectory(example_9_vCard)
25-
25+
if(WITH_GSTREAMER)
26+
add_subdirectory(audio_call)
27+
endif()

examples/audio_call/AudioCall.cpp

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// SPDX-FileCopyrightText: 2025 Linus Jahn <[email protected]>
2+
//
3+
// SPDX-License-Identifier: LGPL-2.1-or-later
4+
5+
#include <QXmppCallManager.h>
6+
#include <QXmppClient.h>
7+
#include <QXmppRosterManager.h>
8+
9+
#include <chrono>
10+
#include <gst/gst.h>
11+
12+
#include <QCoreApplication>
13+
#include <QTimer>
14+
#ifdef Q_OS_UNIX
15+
#include <csignal>
16+
#endif
17+
18+
using namespace std::chrono_literals;
19+
20+
#ifdef Q_OS_UNIX
21+
void handleSignal(int signal)
22+
{
23+
if (signal == SIGINT) {
24+
// print newline
25+
qDebug() << "";
26+
if (QCoreApplication::instance()) {
27+
QCoreApplication::instance()->quit();
28+
}
29+
}
30+
}
31+
#endif
32+
33+
void setupCallStream(QXmppCall *call)
34+
{
35+
auto *gstPipeline = call->pipeline();
36+
auto *stream = call->audioStream();
37+
38+
qDebug() << "[Call] Setup call stream" << stream->media();
39+
if (stream->media() == u"audio") {
40+
// output receiving audio
41+
stream->setReceivePadCallback([gstPipeline](GstPad *receivePad) {
42+
GstElement *output = gst_parse_bin_from_description("audioresample ! audioconvert ! autoaudiosink", true, nullptr);
43+
if (!gst_bin_add(GST_BIN(gstPipeline), output)) {
44+
qFatal("Failed to add input to pipeline");
45+
return;
46+
}
47+
48+
gst_pad_link(receivePad, gst_element_get_static_pad(output, "sink"));
49+
gst_element_sync_state_with_parent(output);
50+
51+
qDebug() << "[Call] receive pad set";
52+
});
53+
54+
// record and send microphone
55+
stream->setSendPadCallback([gstPipeline](GstPad *sendPad) {
56+
GstElement *output = gst_parse_bin_from_description("autoaudiosrc ! audioconvert ! audioresample ! queue max-size-time=1000000", true, nullptr);
57+
if (!gst_bin_add(GST_BIN(gstPipeline), output)) {
58+
qFatal("Failed to add input to pipeline");
59+
return;
60+
}
61+
62+
gst_pad_link(gst_element_get_static_pad(output, "src"), sendPad);
63+
gst_element_sync_state_with_parent(output);
64+
65+
qDebug() << "[Call] send pad set";
66+
});
67+
}
68+
};
69+
70+
int main(int argc, char *argv[])
71+
{
72+
QCoreApplication app(argc, argv);
73+
74+
#ifdef Q_OS_UNIX
75+
// set signal handler for SIGINT (CTRL+C)
76+
std::signal(SIGINT, handleSignal);
77+
#endif
78+
79+
QXmppClient client;
80+
auto *rosterManager = client.findExtension<QXmppRosterManager>();
81+
auto *callManager = client.addNewExtension<QXmppCallManager>();
82+
client.logger()->setLoggingType(QXmppLogger::StdoutLogging);
83+
client.logger()->setMessageTypes(QXmppLogger::MessageType::AnyMessage);
84+
85+
// client config
86+
QXmppConfiguration config;
87+
config.setJid(qEnvironmentVariable("QXMPP_JID"));
88+
config.setPassword(qEnvironmentVariable("QXMPP_PASSWORD"));
89+
config.setResourcePrefix("Call");
90+
91+
// call manager config
92+
callManager->setStunServer(QHostAddress(QStringLiteral("stun.nextcloud.com")), 443);
93+
// callManager->setTurnServer();
94+
// callManager->setTurnUser(client.configuration().jid());
95+
// callManager->setTurnUser(client.configuration().password());
96+
97+
client.connectToServer(config);
98+
99+
// on connect
100+
QObject::connect(&client, &QXmppClient::connected, &app, [&app, &config, rosterManager, callManager] {
101+
// wait 1 second for presence of other clients to arrive
102+
QTimer::singleShot(1s, [&app, &config, rosterManager, callManager] {
103+
// other resources of our account
104+
auto otherResources = rosterManager->getResources(config.jidBare());
105+
otherResources.removeOne(config.resource());
106+
if (otherResources.isEmpty()) {
107+
qDebug() << "[Call] No other clients to call on this account.";
108+
return;
109+
}
110+
111+
// call first JID
112+
auto *call = callManager->call(config.jidBare() + u'/' + otherResources.first());
113+
Q_ASSERT(call != nullptr);
114+
115+
QObject::connect(call, &QXmppCall::connected, &app, [call]() {
116+
qDebug() << "[Call] Call to" << call->jid() << "connected!";
117+
setupCallStream(call);
118+
});
119+
120+
QObject::connect(call, &QXmppCall::ringing, [call]() {
121+
qDebug() << "[Call] Ringing" << call->jid() << "...";
122+
});
123+
QObject::connect(call, &QXmppCall::finished, [call]() {
124+
qDebug() << "[Call] Call with" << call->jid() << "ended.";
125+
});
126+
});
127+
});
128+
129+
// on call
130+
QObject::connect(callManager, &QXmppCallManager::callReceived, &app, [&app](QXmppCall *call) {
131+
qDebug() << "[Call] Received incoming call from" << call->jid() << "-" << "Accepting.";
132+
call->accept();
133+
if (call->audioStream()) {
134+
setupCallStream(call);
135+
}
136+
137+
QObject::connect(call, &QXmppCall::streamCreated, call, [call](QXmppCallStream *stream) {
138+
setupCallStream(call);
139+
});
140+
});
141+
142+
// disconnect from server to avoid having multiple open dead sessions when testing
143+
QObject::connect(&app, &QCoreApplication::aboutToQuit, &app, [&client]() {
144+
qDebug() << "Closing connection...";
145+
client.disconnectFromServer();
146+
});
147+
148+
return app.exec();
149+
}

examples/audio_call/CMakeLists.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# SPDX-FileCopyrightText: 2025 Linus Jahn <[email protected]>
2+
#
3+
# SPDX-License-Identifier: CC0-1.0
4+
5+
find_package(GStreamer REQUIRED)
6+
find_package(GLIB2 REQUIRED)
7+
find_package(GObject REQUIRED)
8+
9+
add_executable(audio_call AudioCall.cpp)
10+
11+
target_link_libraries(audio_call
12+
PUBLIC
13+
${QXMPP_TARGET}
14+
${GLIB2_LIBRARIES}
15+
${GOBJECT_LIBRARIES}
16+
${GSTREAMER_LIBRARY}
17+
)
18+
19+
target_include_directories(audio_call
20+
PUBLIC
21+
${QXMPP_TARGET}
22+
${GLIB2_INCLUDE_DIR}
23+
${GOBJECT_INCLUDE_DIR}
24+
${GSTREAMER_INCLUDE_DIRS}
25+
)

0 commit comments

Comments
 (0)