aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/QXmppCall.cpp739
-rw-r--r--src/client/QXmppCall.h121
-rw-r--r--src/client/QXmppCallManager.cpp655
-rw-r--r--src/client/QXmppCallManager.h112
-rw-r--r--src/client/QXmppCallManager_p.h63
-rw-r--r--src/client/QXmppCallStream.cpp361
-rw-r--r--src/client/QXmppCallStream.h66
-rw-r--r--src/client/QXmppCallStream_p.h103
-rw-r--r--src/client/QXmppCall_p.h133
9 files changed, 1599 insertions, 754 deletions
diff --git a/src/client/QXmppCall.cpp b/src/client/QXmppCall.cpp
new file mode 100644
index 00000000..caf5584b
--- /dev/null
+++ b/src/client/QXmppCall.cpp
@@ -0,0 +1,739 @@
+/*
+ * Copyright (C) 2008-2020 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppCall.h"
+
+#include "QXmppCallManager.h"
+#include "QXmppCallManager_p.h"
+#include "QXmppCallStream.h"
+#include "QXmppCallStream_p.h"
+#include "QXmppCall_p.h"
+#include "QXmppClient.h"
+#include "QXmppConstants_p.h"
+#include "QXmppJingleIq.h"
+#include "QXmppStun.h"
+#include "QXmppUtils.h"
+
+#include <gst/gst.h>
+
+#include <QDomElement>
+#include <QTimer>
+
+QXmppCallPrivate::QXmppCallPrivate(QXmppCall *qq)
+ : direction(QXmppCall::IncomingDirection),
+ manager(0),
+ state(QXmppCall::ConnectingState),
+ nextId(0),
+ q(qq)
+{
+ qRegisterMetaType<QXmppCall::State>();
+
+ filterGStreamerFormats(videoCodecs);
+ filterGStreamerFormats(audioCodecs);
+
+ pipeline = gst_pipeline_new(nullptr);
+ if (!pipeline) {
+ qFatal("Failed to create pipeline");
+ return;
+ }
+ rtpbin = gst_element_factory_make("rtpbin", nullptr);
+ if (!rtpbin) {
+ qFatal("Failed to create rtpbin");
+ return;
+ }
+ // We do not want to build up latency over time
+ g_object_set(rtpbin, "drop-on-latency", true, "async-handling", true, "latency", 25, nullptr);
+ if (!gst_bin_add(GST_BIN(pipeline), rtpbin)) {
+ qFatal("Could not add rtpbin to the pipeline");
+ }
+ g_signal_connect_swapped(rtpbin, "pad-added",
+ G_CALLBACK(+[](QXmppCallPrivate *p, GstPad *pad) {
+ p->padAdded(pad);
+ }),
+ this);
+ g_signal_connect_swapped(rtpbin, "request-pt-map",
+ G_CALLBACK(+[](QXmppCallPrivate *p, uint sessionId, uint pt) {
+ p->ptMap(sessionId, pt);
+ }),
+ this);
+ g_signal_connect_swapped(rtpbin, "on-ssrc-active",
+ G_CALLBACK(+[](QXmppCallPrivate *p, uint sessionId, uint ssrc) {
+ p->ssrcActive(sessionId, ssrc);
+ }),
+ this);
+
+ if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+ qFatal("Unable to set the pipeline to the playing state");
+ return;
+ }
+}
+
+QXmppCallPrivate::~QXmppCallPrivate()
+{
+ if (gst_element_set_state(pipeline, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
+ qFatal("Unable to set the pipeline to the null state");
+ }
+ for (auto stream : streams) {
+ delete stream;
+ }
+ gst_object_unref(pipeline);
+}
+
+void QXmppCallPrivate::ssrcActive(uint sessionId, uint ssrc)
+{
+ Q_UNUSED(ssrc)
+ GstElement *rtpSession;
+ g_signal_emit_by_name(rtpbin, "get-session", static_cast<uint>(sessionId), &rtpSession);
+ // TODO: implement bitrate controller
+}
+
+void QXmppCallPrivate::padAdded(GstPad *pad)
+{
+ auto nameParts = QString(gst_pad_get_name(pad)).split("_");
+ if (nameParts.size() < 4) {
+ return;
+ }
+ if (nameParts[0] == QLatin1String("send") &&
+ nameParts[1] == QLatin1String("rtp") &&
+ nameParts[2] == QLatin1String("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")) {
+ if (nameParts.size() != 6) {
+ return;
+ }
+ int sessionId = nameParts[3].toInt();
+ int pt = nameParts[5].toInt();
+ auto stream = findStreamById(sessionId);
+ if (stream->media() == VIDEO_MEDIA) {
+ for (auto &codec : videoCodecs) {
+ if (codec.pt == pt) {
+ stream->d->addDecoder(pad, codec);
+ return;
+ }
+ }
+ } else if (stream->media() == AUDIO_MEDIA) {
+ for (auto &codec : audioCodecs) {
+ if (codec.pt == pt) {
+ stream->d->addDecoder(pad, codec);
+ return;
+ }
+ }
+ }
+ }
+}
+
+GstCaps *QXmppCallPrivate::ptMap(uint sessionId, uint pt)
+{
+ auto stream = findStreamById(sessionId);
+ for (auto &payloadType : stream->d->payloadTypes) {
+ if (payloadType.id() == pt) {
+ return gst_caps_new_simple("application/x-rtp",
+ "media", G_TYPE_STRING, stream->media().toLatin1().data(),
+ "clock-rate", G_TYPE_INT, payloadType.clockrate(),
+ "encoding-name", G_TYPE_STRING, payloadType.name().toLatin1().data(),
+ nullptr);
+ }
+ }
+ q->warning(QString("Remote party %1 transmits wrong %2 payload for call %3").arg(jid, stream->media(), sid));
+ return nullptr;
+}
+
+bool QXmppCallPrivate::isFormatSupported(const QString &codecName) const
+{
+ GstElementFactory *factory;
+ factory = gst_element_factory_find(codecName.toLatin1().data());
+ if (!factory) {
+ return false;
+ }
+ g_object_unref(factory);
+ return true;
+}
+
+void QXmppCallPrivate::filterGStreamerFormats(QList<GstCodec> &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;
+ }
+ }
+}
+
+QXmppCallStream *QXmppCallPrivate::findStreamByMedia(const QString &media)
+{
+ for (auto stream : streams) {
+ if (stream->media() == media) {
+ return stream;
+ }
+ }
+ return nullptr;
+}
+
+QXmppCallStream *QXmppCallPrivate::findStreamByName(const QString &name)
+{
+ for (auto stream : streams) {
+ if (stream->name() == name) {
+ return stream;
+ }
+ }
+ return nullptr;
+}
+
+QXmppCallStream *QXmppCallPrivate::findStreamById(const int id)
+{
+ for (auto stream : streams) {
+ if (stream->id() == id) {
+ 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(QString("Received ACK for packet %1").arg(id));
+
+ // handle termination
+ if (request.action() == QXmppJingleIq::SessionTerminate)
+ q->terminated();
+ return;
+ }
+ }
+}
+
+bool QXmppCallPrivate::handleDescription(QXmppCallStream *stream, const QXmppJingleIq::Content &content)
+{
+ stream->d->payloadTypes = content.payloadTypes();
+ auto it = stream->d->payloadTypes.begin();
+ bool foundCandidate = false;
+ while (it != stream->d->payloadTypes.end()) {
+ bool dynamic = it->id() >= 96;
+ bool supported = false;
+ auto codecs = stream->media() == AUDIO_MEDIA ? audioCodecs : videoCodecs;
+ for (auto &codec : codecs) {
+ if (dynamic) {
+ if (codec.name == it->name() &&
+ codec.clockrate == it->clockrate() &&
+ codec.channels == it->channels()) {
+ if (!foundCandidate) {
+ stream->d->addEncoder(codec);
+ foundCandidate = true;
+ }
+ supported = true;
+ /* Adopt id from other side. */
+ codec.pt = it->id();
+ }
+ } else {
+ if (codec.pt == it->id() &&
+ codec.clockrate == it->clockrate() &&
+ codec.channels == it->channels()) {
+ if (!foundCandidate) {
+ stream->d->addEncoder(codec);
+ foundCandidate = true;
+ }
+ supported = true;
+ /* Keep our name just to be sure */
+ codec.name = it->name();
+ }
+ }
+ }
+
+ if (!supported) {
+ it = stream->d->payloadTypes.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ if (stream->d->payloadTypes.empty()) {
+ q->warning(QString("Remote party %1 did not provide any known %2 payloads for call %3").arg(jid, stream->media(), sid));
+ return false;
+ }
+
+ return true;
+}
+
+bool QXmppCallPrivate::handleTransport(QXmppCallStream *stream, const QXmppJingleIq::Content &content)
+{
+ stream->d->connection->setRemoteUser(content.transportUser());
+ stream->d->connection->setRemotePassword(content.transportPassword());
+ for (const QXmppJingleCandidate &candidate : content.transportCandidates()) {
+ stream->d->connection->addRemoteCandidate(candidate);
+ }
+
+ // perform ICE negotiation
+ if (!content.transportCandidates().isEmpty()) {
+ stream->d->connection->connectToHost();
+ }
+ return true;
+}
+
+void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq)
+{
+ const QXmppJingleIq::Content content = iq.contents().isEmpty() ? QXmppJingleIq::Content() : iq.contents().first();
+
+ if (iq.action() == QXmppJingleIq::SessionAccept) {
+
+ if (direction == QXmppCall::IncomingDirection) {
+ q->warning("Ignoring Session-Accept for an incoming call");
+ return;
+ }
+
+ // send ack
+ sendAck(iq);
+
+ // check content description and transport
+ QXmppCallStream *stream = findStreamByName(content.name());
+ if (!stream ||
+ !handleDescription(stream, content) ||
+ !handleTransport(stream, content)) {
+
+ // terminate call
+ terminate(QXmppJingleIq::Reason::FailedApplication);
+ return;
+ }
+
+ // check for call establishment
+ setState(QXmppCall::ActiveState);
+
+ } else if (iq.action() == QXmppJingleIq::SessionInfo) {
+
+ // notify user
+ QTimer::singleShot(0, q, SIGNAL(ringing()));
+
+ } else if (iq.action() == QXmppJingleIq::SessionTerminate) {
+
+ // send ack
+ sendAck(iq);
+
+ // terminate
+ q->info(QString("Remote party %1 terminated call %2").arg(iq.from(), iq.sid()));
+ q->terminated();
+
+ } else if (iq.action() == QXmppJingleIq::ContentAccept) {
+
+ // send ack
+ sendAck(iq);
+
+ // check content description and transport
+ QXmppCallStream *stream = findStreamByName(content.name());
+ if (!stream ||
+ !handleDescription(stream, content) ||
+ !handleTransport(stream, content)) {
+
+ // FIXME: what action?
+ return;
+ }
+
+ } else if (iq.action() == QXmppJingleIq::ContentAdd) {
+
+ // send ack
+ sendAck(iq);
+
+ // check media stream does not exist yet
+ QXmppCallStream *stream = findStreamByName(content.name());
+ if (stream)
+ return;
+
+ // create media stream
+ stream = createStream(content.descriptionMedia(), content.creator(), content.name());
+ if (!stream)
+ return;
+ streams << stream;
+
+ // check content description
+ if (!handleDescription(stream, content) ||
+ !handleTransport(stream, content)) {
+
+ QXmppJingleIq iq;
+ iq.setTo(q->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentReject);
+ iq.setSid(q->sid());
+ iq.reason().setType(QXmppJingleIq::Reason::FailedApplication);
+ sendRequest(iq);
+ streams.removeAll(stream);
+ delete stream;
+ return;
+ }
+
+ // accept content
+ QXmppJingleIq iq;
+ iq.setTo(q->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentAccept);
+ iq.setSid(q->sid());
+ iq.addContent(localContent(stream));
+ sendRequest(iq);
+
+ } else if (iq.action() == QXmppJingleIq::TransportInfo) {
+
+ // send ack
+ sendAck(iq);
+
+ // check content transport
+ QXmppCallStream *stream = findStreamByName(content.name());
+ if (!stream ||
+ !handleTransport(stream, content)) {
+ // FIXME: what action?
+ return;
+ }
+ }
+}
+
+QXmppCallStream *QXmppCallPrivate::createStream(const QString &media, const QString &creator, const QString &name)
+{
+ bool check;
+ Q_UNUSED(check);
+ Q_ASSERT(manager);
+
+ if (media != AUDIO_MEDIA && media != VIDEO_MEDIA) {
+ q->warning(QString("Unsupported media type %1").arg(media));
+ return nullptr;
+ }
+
+ if (!isFormatSupported("rtpbin")) {
+ q->warning("The rtpbin GStreamer plugin is missing. Calls are not possible.");
+ return nullptr;
+ }
+
+ QXmppCallStream *stream = new QXmppCallStream(pipeline, rtpbin, media, creator, name, ++nextId);
+
+ // Fill local payload payload types
+ auto &codecs = media == AUDIO_MEDIA ? audioCodecs : videoCodecs;
+ for (auto &codec : codecs) {
+ QXmppJinglePayloadType payloadType;
+ payloadType.setId(codec.pt);
+ payloadType.setName(codec.name);
+ payloadType.setChannels(codec.channels);
+ payloadType.setClockrate(codec.clockrate);
+ stream->d->payloadTypes.append(payloadType);
+ }
+
+ // ICE connection
+ stream->d->connection->setIceControlling(direction == QXmppCall::OutgoingDirection);
+ stream->d->connection->setStunServer(manager->d->stunHost, manager->d->stunPort);
+ stream->d->connection->setTurnServer(manager->d->turnHost, manager->d->turnPort);
+ stream->d->connection->setTurnUser(manager->d->turnUser);
+ stream->d->connection->setTurnPassword(manager->d->turnPassword);
+ stream->d->connection->bind(QXmppIceComponent::discoverAddresses());
+
+ // connect signals
+ check = QObject::connect(stream->d->connection, SIGNAL(localCandidatesChanged()),
+ q, SLOT(localCandidatesChanged()));
+ Q_ASSERT(check);
+
+ check = QObject::connect(stream->d->connection, SIGNAL(disconnected()),
+ q, SLOT(hangup()));
+ Q_ASSERT(check);
+
+ Q_EMIT q->streamCreated(stream);
+
+ return stream;
+}
+
+QXmppJingleIq::Content QXmppCallPrivate::localContent(QXmppCallStream *stream) const
+{
+ QXmppJingleIq::Content content;
+ content.setCreator(stream->creator());
+ content.setName(stream->name());
+ content.setSenders("both");
+
+ // description
+ content.setDescriptionMedia(stream->media());
+ content.setDescriptionSsrc(stream->d->localSsrc);
+ content.setPayloadTypes(stream->d->payloadTypes);
+
+ // transport
+ content.setTransportUser(stream->d->connection->localUser());
+ content.setTransportPassword(stream->d->connection->localPassword());
+ content.setTransportCandidates(stream->d->connection->localCandidates());
+
+ return content;
+}
+
+/// Sends an acknowledgement for a Jingle IQ.
+///
+
+bool QXmppCallPrivate::sendAck(const QXmppJingleIq &iq)
+{
+ QXmppIq ack;
+ ack.setId(iq.id());
+ ack.setTo(iq.from());
+ ack.setType(QXmppIq::Result);
+ return manager->client()->sendPacket(ack);
+}
+
+bool QXmppCallPrivate::sendInvite()
+{
+ // create audio stream
+ QXmppCallStream *stream = findStreamByMedia(AUDIO_MEDIA);
+ Q_ASSERT(stream);
+
+ QXmppJingleIq iq;
+ iq.setTo(jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionInitiate);
+ 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);
+}
+
+void QXmppCallPrivate::setState(QXmppCall::State newState)
+{
+ if (state != newState) {
+ state = newState;
+ Q_EMIT q->stateChanged(state);
+
+ if (state == QXmppCall::ActiveState)
+ Q_EMIT q->connected();
+ else if (state == QXmppCall::FinishedState)
+ Q_EMIT q->finished();
+ }
+}
+
+/// Request graceful call termination
+
+void QXmppCallPrivate::terminate(QXmppJingleIq::Reason::Type reasonType)
+{
+ if (state == QXmppCall::DisconnectingState ||
+ state == QXmppCall::FinishedState)
+ return;
+
+ // hangup call
+ QXmppJingleIq iq;
+ iq.setTo(jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionTerminate);
+ iq.setSid(sid);
+ iq.reason().setType(reasonType);
+ sendRequest(iq);
+ setState(QXmppCall::DisconnectingState);
+
+ // schedule forceful termination in 5s
+ QTimer::singleShot(5000, q, SLOT(terminated()));
+}
+
+QXmppCall::QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent)
+ : QXmppLoggable(parent)
+{
+ d = new QXmppCallPrivate(this);
+ d->direction = direction;
+ d->jid = jid;
+ d->ownJid = parent->client()->configuration().jid();
+ d->manager = parent;
+}
+
+QXmppCall::~QXmppCall()
+{
+ delete d;
+}
+
+/// Call this method if you wish to accept an incoming call.
+///
+
+void QXmppCall::accept()
+{
+ if (d->direction == IncomingDirection && d->state == ConnectingState) {
+ Q_ASSERT(d->streams.size() == 1);
+ QXmppCallStream *stream = d->streams.first();
+
+ // accept incoming call
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionAccept);
+ iq.setResponder(d->ownJid);
+ iq.setSid(d->sid);
+ iq.addContent(d->localContent(stream));
+ d->sendRequest(iq);
+
+ // notify user
+ d->manager->callStarted(this);
+
+ // check for call establishment
+ d->setState(QXmppCall::ActiveState);
+ }
+}
+
+/// Returns the GStreamer pipeline.
+///
+/// \since QXmpp 1.3
+
+GstElement *QXmppCall::pipeline() const
+{
+ return d->pipeline;
+}
+
+/// Returns the RTP stream for the audio data.
+///
+/// \since QXmpp 1.2
+
+QXmppCallStream *QXmppCall::audioStream() const
+{
+ return d->findStreamByMedia(AUDIO_MEDIA);
+}
+
+/// Returns the RTP stream for the video data.
+///
+/// \since QXmpp 1.2
+
+QXmppCallStream *QXmppCall::videoStream() const
+{
+ return d->findStreamByMedia(VIDEO_MEDIA);
+}
+
+void QXmppCall::terminated()
+{
+ // close streams
+ for (auto stream : d->streams) {
+ stream->d->connection->close();
+ }
+
+ // update state
+ d->setState(QXmppCall::FinishedState);
+}
+
+/// Returns the call's direction.
+///
+
+QXmppCall::Direction QXmppCall::direction() const
+{
+ return d->direction;
+}
+
+/// Hangs up the call.
+///
+
+void QXmppCall::hangup()
+{
+ d->terminate(QXmppJingleIq::Reason::None);
+}
+
+/// Sends a transport-info to inform the remote party of new local candidates.
+///
+
+void QXmppCall::localCandidatesChanged()
+{
+ // find the stream
+ QXmppIceConnection *conn = qobject_cast<QXmppIceConnection *>(sender());
+ QXmppCallStream *stream = 0;
+ for (auto ptr : d->streams) {
+ if (ptr->d->connection == conn) {
+ stream = ptr;
+ break;
+ }
+ }
+ if (!stream)
+ return;
+
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::TransportInfo);
+ iq.setSid(d->sid);
+ iq.addContent(d->localContent(stream));
+ d->sendRequest(iq);
+}
+
+/// Returns the remote party's JID.
+///
+
+QString QXmppCall::jid() const
+{
+ return d->jid;
+}
+
+/// Returns the call's session identifier.
+///
+
+QString QXmppCall::sid() const
+{
+ return d->sid;
+}
+
+/// Returns the call's state.
+///
+/// \sa stateChanged()
+
+QXmppCall::State QXmppCall::state() const
+{
+ return d->state;
+}
+
+/// Starts sending video to the remote party.
+
+void QXmppCall::addVideo()
+{
+ if (d->state != QXmppCall::ActiveState) {
+ warning("Cannot add video, call is not active");
+ return;
+ }
+
+ QXmppCallStream *stream = d->findStreamByMedia(VIDEO_MEDIA);
+ if (stream) {
+ return;
+ }
+
+ // create video stream
+ QLatin1String creator = (d->direction == QXmppCall::OutgoingDirection) ? QLatin1String("initiator") : QLatin1String("responder");
+ stream = d->createStream(VIDEO_MEDIA, creator, QLatin1String("webcam"));
+ d->streams << stream;
+
+ // build request
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentAdd);
+ iq.setSid(d->sid);
+ iq.addContent(d->localContent(stream));
+ d->sendRequest(iq);
+}
diff --git a/src/client/QXmppCall.h b/src/client/QXmppCall.h
new file mode 100644
index 00000000..8bc91b71
--- /dev/null
+++ b/src/client/QXmppCall.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2008-2020 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPCALL_H
+#define QXMPPCALL_H
+
+#include "QXmppCallStream.h"
+#include "QXmppClientExtension.h"
+#include "QXmppLogger.h"
+
+#include <QMetaType>
+#include <QObject>
+
+class QHostAddress;
+class QXmppCallPrivate;
+class QXmppCallManager;
+class QXmppCallManagerPrivate;
+
+/// \brief The QXmppCall class represents a Voice-Over-IP call to a remote party.
+///
+/// \note THIS API IS NOT FINALIZED YET
+
+class QXMPP_EXPORT QXmppCall : public QXmppLoggable
+{
+ Q_OBJECT
+ Q_PROPERTY(Direction direction READ direction CONSTANT)
+ Q_PROPERTY(QString jid READ jid CONSTANT)
+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
+
+public:
+ /// This enum is used to describe the direction of a call.
+ enum Direction {
+ IncomingDirection, ///< The call is incoming.
+ OutgoingDirection ///< The call is outgoing.
+ };
+ Q_ENUM(Direction)
+
+ /// This enum is used to describe the state of a call.
+ enum State {
+ ConnectingState = 0, ///< The call is being connected.
+ ActiveState = 1, ///< The call is active.
+ DisconnectingState = 2, ///< The call is being disconnected.
+ FinishedState = 3 ///< The call is finished.
+ };
+ Q_ENUM(State)
+
+ ~QXmppCall();
+
+ QXmppCall::Direction direction() const;
+ QString jid() const;
+ QString sid() const;
+ QXmppCall::State state() const;
+
+ GstElement *pipeline() const;
+ QXmppCallStream *audioStream() const;
+ QXmppCallStream *videoStream() const;
+
+signals:
+ /// \brief This signal is emitted when a call is connected.
+ ///
+ /// Once this signal is emitted, you can connect a QAudioOutput and
+ /// QAudioInput to the call. You can determine the appropriate clockrate
+ /// and the number of channels by calling payloadType().
+ void connected();
+
+ /// \brief This signal is emitted when a call is finished.
+ ///
+ /// Note: Do not delete the call in the slot connected to this signal,
+ /// instead use deleteLater().
+ void finished();
+
+ /// \brief This signal is emitted when the remote party is ringing.
+ void ringing();
+
+ /// \brief This signal is emitted when the call state changes.
+ void stateChanged(QXmppCall::State state);
+
+ /// \brief This signal is emitted when a stream is created.
+ void streamCreated(QXmppCallStream *stream);
+
+public slots:
+ void accept();
+ void hangup();
+ void addVideo();
+
+private slots:
+ void localCandidatesChanged();
+ void terminated();
+
+private:
+ QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent);
+
+ QXmppCallPrivate *d;
+ friend class QXmppCallManager;
+ friend class QXmppCallManagerPrivate;
+ friend class QXmppCallPrivate;
+};
+
+Q_DECLARE_METATYPE(QXmppCall::State)
+
+#endif
diff --git a/src/client/QXmppCallManager.cpp b/src/client/QXmppCallManager.cpp
index 6ce6687a..8ba97c2d 100644
--- a/src/client/QXmppCallManager.cpp
+++ b/src/client/QXmppCallManager.cpp
@@ -23,659 +23,27 @@
#include "QXmppCallManager.h"
+#include "QXmppCall.h"
+#include "QXmppCallManager_p.h"
+#include "QXmppCall_p.h"
#include "QXmppClient.h"
#include "QXmppConstants_p.h"
#include "QXmppJingleIq.h"
-#include "QXmppRtpChannel.h"
#include "QXmppStun.h"
#include "QXmppUtils.h"
+#include <gst/gst.h>
+
#include <QDomElement>
#include <QTimer>
-static const int RTP_COMPONENT = 1;
-static const int RTCP_COMPONENT = 2;
-
-static const QLatin1String AUDIO_MEDIA("audio");
-static const QLatin1String VIDEO_MEDIA("video");
-
-class QXmppCallPrivate
-{
-public:
- class Stream
- {
- public:
- QXmppRtpChannel *channel;
- QXmppIceConnection *connection;
- QString creator;
- QString media;
- QString name;
- };
-
- QXmppCallPrivate(QXmppCall *qq);
- Stream *createStream(const QString &media);
- Stream *findStreamByMedia(const QString &media);
- Stream *findStreamByName(const QString &name);
- QXmppJingleIq::Content localContent(QXmppCallPrivate::Stream *stream) const;
-
- void handleAck(const QXmppIq &iq);
- bool handleDescription(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content);
- void handleRequest(const QXmppJingleIq &iq);
- bool handleTransport(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content);
- void setState(QXmppCall::State state);
- bool sendAck(const QXmppJingleIq &iq);
- bool sendInvite();
- bool sendRequest(const QXmppJingleIq &iq);
- void terminate(QXmppJingleIq::Reason::Type reasonType);
-
- QXmppCall::Direction direction;
- QString jid;
- QString ownJid;
- QXmppCallManager *manager;
- QList<QXmppJingleIq> requests;
- QString sid;
- QXmppCall::State state;
-
- // Media streams
- bool sendVideo;
- QList<Stream *> streams;
- QIODevice::OpenMode audioMode;
- QIODevice::OpenMode videoMode;
-
-private:
- QXmppCall *q;
-};
-
-class QXmppCallManagerPrivate
-{
-public:
- QXmppCallManagerPrivate(QXmppCallManager *qq);
- QXmppCall *findCall(const QString &sid) const;
- QXmppCall *findCall(const QString &sid, QXmppCall::Direction direction) const;
-
- QList<QXmppCall *> calls;
- QHostAddress stunHost;
- quint16 stunPort;
- QHostAddress turnHost;
- quint16 turnPort;
- QString turnUser;
- QString turnPassword;
-
-private:
- QXmppCallManager *q;
-};
-
-QXmppCallPrivate::QXmppCallPrivate(QXmppCall *qq)
- : direction(QXmppCall::IncomingDirection),
- manager(nullptr),
- state(QXmppCall::ConnectingState),
- sendVideo(false),
- audioMode(QIODevice::NotOpen),
- videoMode(QIODevice::NotOpen),
- q(qq)
-{
- qRegisterMetaType<QXmppCall::State>();
-}
-
-QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByMedia(const QString &media)
-{
- for (auto *stream : streams)
- if (stream->media == media)
- return stream;
- return nullptr;
-}
-
-QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByName(const QString &name)
-{
- for (auto *stream : streams)
- if (stream->name == name)
- 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(QString("Received ACK for packet %1").arg(id));
-
- // handle termination
- if (request.action() == QXmppJingleIq::SessionTerminate)
- q->terminated();
- return;
- }
- }
-}
-
-bool QXmppCallPrivate::handleDescription(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content)
-{
- stream->channel->setRemotePayloadTypes(content.payloadTypes());
- if (!(stream->channel->openMode() & QIODevice::ReadWrite)) {
- q->warning(QString("Remote party %1 did not provide any known %2 payloads for call %3").arg(jid, stream->media, sid));
- return false;
- }
- q->updateOpenMode();
- return true;
-}
-
-bool QXmppCallPrivate::handleTransport(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content)
-{
- stream->connection->setRemoteUser(content.transportUser());
- stream->connection->setRemotePassword(content.transportPassword());
- const auto &candidates = content.transportCandidates();
- for (const auto &candidate : candidates)
- stream->connection->addRemoteCandidate(candidate);
-
- // perform ICE negotiation
- if (!content.transportCandidates().isEmpty())
- stream->connection->connectToHost();
- return true;
-}
-
-void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq)
-{
- const QXmppJingleIq::Content content = iq.contents().isEmpty() ? QXmppJingleIq::Content() : iq.contents().first();
-
- if (iq.action() == QXmppJingleIq::SessionAccept) {
-
- if (direction == QXmppCall::IncomingDirection) {
- q->warning("Ignoring Session-Accept for an incoming call");
- return;
- }
-
- // send ack
- sendAck(iq);
-
- // check content description and transport
- QXmppCallPrivate::Stream *stream = findStreamByName(content.name());
- if (!stream ||
- !handleDescription(stream, content) ||
- !handleTransport(stream, content)) {
-
- // terminate call
- terminate(QXmppJingleIq::Reason::FailedApplication);
- return;
- }
-
- // check for call establishment
- setState(QXmppCall::ActiveState);
-
- } else if (iq.action() == QXmppJingleIq::SessionInfo) {
-
- // notify user
- QTimer::singleShot(0, q, &QXmppCall::ringing);
-
- } else if (iq.action() == QXmppJingleIq::SessionTerminate) {
-
- // send ack
- sendAck(iq);
-
- // terminate
- q->info(QString("Remote party %1 terminated call %2").arg(iq.from(), iq.sid()));
- q->terminated();
-
- } else if (iq.action() == QXmppJingleIq::ContentAccept) {
-
- // send ack
- sendAck(iq);
-
- // check content description and transport
- QXmppCallPrivate::Stream *stream = findStreamByName(content.name());
- if (!stream ||
- !handleDescription(stream, content) ||
- !handleTransport(stream, content)) {
-
- // FIXME: what action?
- return;
- }
-
- } else if (iq.action() == QXmppJingleIq::ContentAdd) {
-
- // send ack
- sendAck(iq);
-
- // check media stream does not exist yet
- QXmppCallPrivate::Stream *stream = findStreamByName(content.name());
- if (stream)
- return;
-
- // create media stream
- stream = createStream(content.descriptionMedia());
- if (!stream)
- return;
- stream->creator = content.creator();
- stream->name = content.name();
-
- // check content description
- if (!handleDescription(stream, content) ||
- !handleTransport(stream, content)) {
-
- QXmppJingleIq iq;
- iq.setTo(q->jid());
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::ContentReject);
- iq.setSid(q->sid());
- iq.reason().setType(QXmppJingleIq::Reason::FailedApplication);
- sendRequest(iq);
- delete stream;
- return;
- }
- streams << stream;
-
- // accept content
- QXmppJingleIq iq;
- iq.setTo(q->jid());
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::ContentAccept);
- iq.setSid(q->sid());
- iq.addContent(localContent(stream));
- sendRequest(iq);
-
- } else if (iq.action() == QXmppJingleIq::TransportInfo) {
-
- // send ack
- sendAck(iq);
-
- // check content transport
- QXmppCallPrivate::Stream *stream = findStreamByName(content.name());
- if (!stream ||
- !handleTransport(stream, content)) {
- // FIXME: what action?
- return;
- }
- }
-}
-
-QXmppCallPrivate::Stream *QXmppCallPrivate::createStream(const QString &media)
-{
- bool check;
- Q_UNUSED(check)
- Q_ASSERT(manager);
-
- auto *stream = new Stream;
- stream->media = media;
-
- // RTP channel
- QObject *channelObject = nullptr;
- if (media == AUDIO_MEDIA) {
- auto *audioChannel = new QXmppRtpAudioChannel(q);
- stream->channel = audioChannel;
- channelObject = audioChannel;
- } else if (media == VIDEO_MEDIA) {
- auto *videoChannel = new QXmppRtpVideoChannel(q);
- stream->channel = videoChannel;
- channelObject = videoChannel;
- } else {
- q->warning(QString("Unsupported media type %1").arg(media));
- delete stream;
- return nullptr;
- }
-
- // ICE connection
- stream->connection = new QXmppIceConnection(q);
- stream->connection->setIceControlling(direction == QXmppCall::OutgoingDirection);
- stream->connection->setStunServer(manager->d->stunHost, manager->d->stunPort);
- stream->connection->setTurnServer(manager->d->turnHost, manager->d->turnPort);
- stream->connection->setTurnUser(manager->d->turnUser);
- stream->connection->setTurnPassword(manager->d->turnPassword);
- stream->connection->addComponent(RTP_COMPONENT);
- stream->connection->addComponent(RTCP_COMPONENT);
- stream->connection->bind(QXmppIceComponent::discoverAddresses());
-
- // connect signals
- QObject::connect(stream->connection, &QXmppIceConnection::localCandidatesChanged,
- q, &QXmppCall::localCandidatesChanged);
-
- QObject::connect(stream->connection, &QXmppIceConnection::connected,
- q, &QXmppCall::updateOpenMode);
-
- QObject::connect(q, &QXmppCall::stateChanged,
- q, &QXmppCall::updateOpenMode);
-
- QObject::connect(stream->connection, &QXmppIceConnection::disconnected,
- q, &QXmppCall::hangup);
-
- if (channelObject) {
- QXmppIceComponent *rtpComponent = stream->connection->component(RTP_COMPONENT);
-
- QObject::connect(rtpComponent, SIGNAL(datagramReceived(QByteArray)),
- channelObject, SLOT(datagramReceived(QByteArray)));
-
- QObject::connect(channelObject, SIGNAL(sendDatagram(QByteArray)),
- rtpComponent, SLOT(sendDatagram(QByteArray)));
- }
- return stream;
-}
-
-QXmppJingleIq::Content QXmppCallPrivate::localContent(QXmppCallPrivate::Stream *stream) const
-{
- QXmppJingleIq::Content content;
- content.setCreator(stream->creator);
- content.setName(stream->name);
- content.setSenders("both");
-
- // description
- content.setDescriptionMedia(stream->media);
- content.setDescriptionSsrc(stream->channel->localSsrc());
- content.setPayloadTypes(stream->channel->localPayloadTypes());
-
- // transport
- content.setTransportUser(stream->connection->localUser());
- content.setTransportPassword(stream->connection->localPassword());
- content.setTransportCandidates(stream->connection->localCandidates());
-
- return content;
-}
-
-/// Sends an acknowledgement for a Jingle IQ.
-///
-
-bool QXmppCallPrivate::sendAck(const QXmppJingleIq &iq)
-{
- QXmppIq ack;
- ack.setId(iq.id());
- ack.setTo(iq.from());
- ack.setType(QXmppIq::Result);
- return manager->client()->sendPacket(ack);
-}
-
-bool QXmppCallPrivate::sendInvite()
-{
- // create audio stream
- QXmppCallPrivate::Stream *stream = findStreamByMedia(AUDIO_MEDIA);
- Q_ASSERT(stream);
-
- QXmppJingleIq iq;
- iq.setTo(jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::SessionInitiate);
- 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);
-}
-
-void QXmppCallPrivate::setState(QXmppCall::State newState)
-{
- if (state != newState) {
- state = newState;
- emit q->stateChanged(state);
-
- if (state == QXmppCall::ActiveState)
- emit q->connected();
- else if (state == QXmppCall::FinishedState)
- emit q->finished();
- }
-}
-
-/// Request graceful call termination
-
-void QXmppCallPrivate::terminate(QXmppJingleIq::Reason::Type reasonType)
-{
- if (state == QXmppCall::DisconnectingState ||
- state == QXmppCall::FinishedState)
- return;
-
- // hangup call
- QXmppJingleIq iq;
- iq.setTo(jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::SessionTerminate);
- iq.setSid(sid);
- iq.reason().setType(reasonType);
- sendRequest(iq);
- setState(QXmppCall::DisconnectingState);
-
- // schedule forceful termination in 5s
- QTimer::singleShot(5000, q, &QXmppCall::terminated);
-}
-
-QXmppCall::QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent)
- : QXmppLoggable(parent)
-{
- d = new QXmppCallPrivate(this);
- d->direction = direction;
- d->jid = jid;
- d->ownJid = parent->client()->configuration().jid();
- d->manager = parent;
-
- // create audio stream
- QXmppCallPrivate::Stream *stream = d->createStream(AUDIO_MEDIA);
- stream->creator = QLatin1String("initiator");
- stream->name = QLatin1String("voice");
- d->streams << stream;
-}
-
-QXmppCall::~QXmppCall()
-{
- qDeleteAll(d->streams);
- delete d;
-}
-
-/// Call this method if you wish to accept an incoming call.
-///
-
-void QXmppCall::accept()
-{
- if (d->direction == IncomingDirection && d->state == ConnectingState) {
- Q_ASSERT(d->streams.size() == 1);
- QXmppCallPrivate::Stream *stream = d->streams.first();
-
- // accept incoming call
- QXmppJingleIq iq;
- iq.setTo(d->jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::SessionAccept);
- iq.setResponder(d->ownJid);
- iq.setSid(d->sid);
- iq.addContent(d->localContent(stream));
- d->sendRequest(iq);
-
- // notify user
- d->manager->callStarted(this);
-
- // check for call establishment
- d->setState(QXmppCall::ActiveState);
- }
-}
-
-/// Returns the RTP channel for the audio data.
-///
-/// It acts as a QIODevice so that you can read / write audio samples, for
-/// instance using a QAudioOutput and a QAudioInput.
-///
-
-QXmppRtpAudioChannel *QXmppCall::audioChannel() const
-{
- QXmppCallPrivate::Stream *stream = d->findStreamByMedia(AUDIO_MEDIA);
- if (stream)
- return static_cast<QXmppRtpAudioChannel *>(stream->channel);
- else
- return nullptr;
-}
-
-QIODevice::OpenMode QXmppCall::audioMode() const
-{
- return d->audioMode;
-}
-
-/// Returns the RTP channel for the video data.
-///
-
-QXmppRtpVideoChannel *QXmppCall::videoChannel() const
-{
- QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
- if (stream)
- return static_cast<QXmppRtpVideoChannel *>(stream->channel);
- else
- return nullptr;
-}
-
-QIODevice::OpenMode QXmppCall::videoMode() const
-{
- return d->videoMode;
-}
-
-void QXmppCall::terminated()
-{
- // close streams
- for (auto *stream : d->streams) {
- stream->channel->close();
- stream->connection->close();
- }
-
- // update state
- d->setState(QXmppCall::FinishedState);
-}
-
-QXmppCall::Direction QXmppCall::direction() const
-{
- return d->direction;
-}
-
-/// Hangs up the call.
-///
-
-void QXmppCall::hangup()
-{
- d->terminate(QXmppJingleIq::Reason::None);
-}
-
-/// Sends a transport-info to inform the remote party of new local candidates.
-///
-
-void QXmppCall::localCandidatesChanged()
-{
- // find the stream
- auto *conn = qobject_cast<QXmppIceConnection *>(sender());
- QXmppCallPrivate::Stream *stream = nullptr;
- for (auto *ptr : d->streams) {
- if (ptr->connection == conn) {
- stream = ptr;
- break;
- }
- }
- if (!stream)
- return;
-
- QXmppJingleIq iq;
- iq.setTo(d->jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::TransportInfo);
- iq.setSid(d->sid);
- iq.addContent(d->localContent(stream));
- d->sendRequest(iq);
-}
-
-QString QXmppCall::jid() const
-{
- return d->jid;
-}
-
-void QXmppCall::updateOpenMode()
-{
- QXmppCallPrivate::Stream *stream;
- QIODevice::OpenMode mode;
-
- // determine audio mode
- mode = QIODevice::NotOpen;
- stream = d->findStreamByMedia(AUDIO_MEDIA);
- if (d->state == QXmppCall::ActiveState && stream && stream->connection->isConnected())
- mode = stream->channel->openMode() & QIODevice::ReadWrite;
- if (mode != d->audioMode) {
- d->audioMode = mode;
- emit audioModeChanged(mode);
- }
-
- // determine video mode
- mode = QIODevice::NotOpen;
- stream = d->findStreamByMedia(VIDEO_MEDIA);
- if (d->state == QXmppCall::ActiveState && stream && stream->connection->isConnected()) {
- mode |= (stream->channel->openMode() & QIODevice::ReadOnly);
- if (d->sendVideo)
- mode |= (stream->channel->openMode() & QIODevice::WriteOnly);
- }
- if (mode != d->videoMode) {
- d->videoMode = mode;
- emit videoModeChanged(mode);
- }
-}
-
-/// Returns the call's session identifier.
-///
-
-QString QXmppCall::sid() const
-{
- return d->sid;
-}
-
-QXmppCall::State QXmppCall::state() const
-{
- return d->state;
-}
-
-/// Starts sending video to the remote party.
-
-void QXmppCall::startVideo()
-{
- if (d->state != QXmppCall::ActiveState) {
- warning("Cannot start video, call is not active");
- return;
- }
-
- d->sendVideo = true;
- QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
- if (stream) {
- updateOpenMode();
- return;
- }
-
- // create video stream
- stream = d->createStream(VIDEO_MEDIA);
- stream->creator = (d->direction == QXmppCall::OutgoingDirection) ? QLatin1String("initiator") : QLatin1String("responder");
- stream->name = QLatin1String("webcam");
- d->streams << stream;
-
- // build request
- QXmppJingleIq iq;
- iq.setTo(d->jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::ContentAdd);
- iq.setSid(d->sid);
- iq.addContent(d->localContent(stream));
- d->sendRequest(iq);
-}
-
-/// Stops sending video to the remote party.
-
-void QXmppCall::stopVideo()
-{
- if (!d->sendVideo)
- return;
-
- d->sendVideo = false;
- QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
- if (stream)
- updateOpenMode();
-}
-
QXmppCallManagerPrivate::QXmppCallManagerPrivate(QXmppCallManager *qq)
: stunPort(0),
turnPort(0),
q(qq)
{
+ // Initialize GStreamer
+ gst_init(nullptr, nullptr);
}
QXmppCall *QXmppCallManagerPrivate::findCall(const QString &sid) const
@@ -769,7 +137,9 @@ QXmppCall *QXmppCallManager::call(const QString &jid)
return nullptr;
}
- auto *call = new QXmppCall(jid, QXmppCall::OutgoingDirection, this);
+ QXmppCall *call = new QXmppCall(jid, QXmppCall::OutgoingDirection, this);
+ QXmppCallStream *stream = call->d->createStream("audio", "initiator", "microphone");
+ call->d->streams << stream;
call->d->sid = QXmppUtils::generateStanzaHash();
// register call
@@ -867,11 +237,10 @@ void QXmppCallManager::_q_jingleIqReceived(const QXmppJingleIq &iq)
call->d->sid = iq.sid();
const QXmppJingleIq::Content content = iq.contents().isEmpty() ? QXmppJingleIq::Content() : iq.contents().first();
- QXmppCallPrivate::Stream *stream = call->d->findStreamByMedia(content.descriptionMedia());
+ QXmppCallStream *stream = call->d->createStream(content.descriptionMedia(), content.creator(), content.name());
if (!stream)
return;
- stream->creator = content.creator();
- stream->name = content.name();
+ call->d->streams << stream;
// send ack
call->d->sendAck(iq);
diff --git a/src/client/QXmppCallManager.h b/src/client/QXmppCallManager.h
index 8b611f89..719d1e36 100644
--- a/src/client/QXmppCallManager.h
+++ b/src/client/QXmppCallManager.h
@@ -24,6 +24,7 @@
#ifndef QXMPPCALLMANAGER_H
#define QXMPPCALLMANAGER_H
+#include "QXmppCall.h"
#include "QXmppClientExtension.h"
#include "QXmppLogger.h"
@@ -32,121 +33,12 @@
#include <QObject>
class QHostAddress;
-class QXmppCallPrivate;
-class QXmppCallManager;
class QXmppCallManagerPrivate;
class QXmppIq;
class QXmppJingleCandidate;
class QXmppJingleIq;
class QXmppJinglePayloadType;
class QXmppPresence;
-class QXmppRtpAudioChannel;
-class QXmppRtpVideoChannel;
-
-/// \brief The QXmppCall class represents a Voice-Over-IP call to a remote party.
-///
-/// To get the QIODevice from which you can read / write audio samples, call
-/// audioChannel().
-///
-/// \note THIS API IS NOT FINALIZED YET
-
-class QXMPP_EXPORT QXmppCall : public QXmppLoggable
-{
- Q_OBJECT
- Q_FLAGS(QIODevice::OpenModeFlag QIODevice::OpenMode)
- Q_PROPERTY(Direction direction READ direction CONSTANT)
- Q_PROPERTY(QString jid READ jid CONSTANT)
- Q_PROPERTY(State state READ state NOTIFY stateChanged)
- Q_PROPERTY(QIODevice::OpenMode audioMode READ audioMode NOTIFY audioModeChanged)
- Q_PROPERTY(QIODevice::OpenMode videoMode READ videoMode NOTIFY videoModeChanged)
-
-public:
- /// This enum is used to describe the direction of a call.
- enum Direction {
- IncomingDirection, ///< The call is incoming.
- OutgoingDirection ///< The call is outgoing.
- };
- Q_ENUM(Direction)
-
- /// This enum is used to describe the state of a call.
- enum State {
- ConnectingState = 0, ///< The call is being connected.
- ActiveState = 1, ///< The call is active.
- DisconnectingState = 2, ///< The call is being disconnected.
- FinishedState = 3 ///< The call is finished.
- };
- Q_ENUM(State)
-
- ~QXmppCall() override;
-
- // documentation needs to be here, see https://stackoverflow.com/questions/49192523/
- /// Returns the call's direction.
- QXmppCall::Direction direction() const;
- /// Returns the remote party's JID.
- QString jid() const;
- ///
- /// Returns the call's state.
- ///
- /// \sa stateChanged()
- ///
- QXmppCall::State state() const;
-
- QString sid() const;
-
- QXmppRtpAudioChannel *audioChannel() const;
- QXmppRtpVideoChannel *videoChannel() const;
-
- // documentation needs to be here, see https://stackoverflow.com/questions/49192523/
- /// Returns the audio mode.
- QIODevice::OpenMode audioMode() const;
- /// Returns the video mode.
- QIODevice::OpenMode videoMode() const;
-
-Q_SIGNALS:
- /// \brief This signal is emitted when a call is connected.
- ///
- /// Once this signal is emitted, you can connect a QAudioOutput and
- /// QAudioInput to the call. You can determine the appropriate clockrate
- /// and the number of channels by calling payloadType().
- void connected();
-
- /// \brief This signal is emitted when a call is finished.
- ///
- /// Note: Do not delete the call in the slot connected to this signal,
- /// instead use deleteLater().
- void finished();
-
- /// \brief This signal is emitted when the remote party is ringing.
- void ringing();
-
- /// \brief This signal is emitted when the call state changes.
- void stateChanged(QXmppCall::State state);
-
- /// \brief This signal is emitted when the audio channel changes.
- void audioModeChanged(QIODevice::OpenMode mode);
-
- /// \brief This signal is emitted when the video channel changes.
- void videoModeChanged(QIODevice::OpenMode mode);
-
-public Q_SLOTS:
- void accept();
- void hangup();
- void startVideo();
- void stopVideo();
-
-private Q_SLOTS:
- void localCandidatesChanged();
- void terminated();
- void updateOpenMode();
-
-private:
- QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent);
-
- QXmppCallPrivate *d;
- friend class QXmppCallManager;
- friend class QXmppCallManagerPrivate;
- friend class QXmppCallPrivate;
-};
/// \brief The QXmppCallManager class provides support for making and
/// receiving voice calls.
@@ -218,6 +110,4 @@ private:
friend class QXmppCallManagerPrivate;
};
-Q_DECLARE_METATYPE(QXmppCall::State)
-
#endif
diff --git a/src/client/QXmppCallManager_p.h b/src/client/QXmppCallManager_p.h
new file mode 100644
index 00000000..32b38dc0
--- /dev/null
+++ b/src/client/QXmppCallManager_p.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008-2019 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPCALLMANAGER_P_H
+#define QXMPPCALLMANAGER_P_H
+
+#include "QXmppCall.h"
+
+#include <QHostAddress>
+#include <QList>
+
+class QXmppCallManager;
+
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QXmpp API.
+// This header file may change from version to version without notice,
+// or even be removed.
+//
+// We mean it.
+//
+
+class QXmppCallManagerPrivate
+{
+public:
+ QXmppCallManagerPrivate(QXmppCallManager *qq);
+ QXmppCall *findCall(const QString &sid) const;
+ QXmppCall *findCall(const QString &sid, QXmppCall::Direction direction) const;
+
+ QList<QXmppCall *> calls;
+ QHostAddress stunHost;
+ quint16 stunPort;
+ QHostAddress turnHost;
+ quint16 turnPort;
+ QString turnUser;
+ QString turnPassword;
+
+private:
+ QXmppCallManager *q;
+};
+
+#endif
diff --git a/src/client/QXmppCallStream.cpp b/src/client/QXmppCallStream.cpp
new file mode 100644
index 00000000..4a8f55a2
--- /dev/null
+++ b/src/client/QXmppCallStream.cpp
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2020 The QXmpp developers
+ *
+ * Author:
+ * Niels Ole Salscheider
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppCallStream.h"
+
+#include "QXmppCallStream_p.h"
+#include "QXmppCall_p.h"
+#include "QXmppStun.h"
+
+#include <cstring>
+#include <gst/gst.h>
+
+QXmppCallStreamPrivate::QXmppCallStreamPrivate(QXmppCallStream *parent, GstElement *pipeline_,
+ GstElement *rtpbin_, QString media_, QString creator_,
+ QString name_, int id_)
+ : QObject(parent),
+ q(parent),
+ pipeline(pipeline_),
+ rtpbin(rtpbin_),
+ sendPad(nullptr),
+ receivePad(nullptr),
+ encoderBin(nullptr),
+ decoderBin(nullptr),
+ sendPadCB(nullptr),
+ receivePadCB(nullptr),
+ media(media_),
+ creator(creator_),
+ name(name_),
+ id(id_)
+{
+ localSsrc = qrand();
+
+ iceReceiveBin = gst_bin_new(QStringLiteral("receive_%1").arg(id).toLatin1().data());
+ iceSendBin = gst_bin_new(QStringLiteral("send_%1").arg(id).toLatin1().data());
+ gst_bin_add_many(GST_BIN(pipeline), iceReceiveBin, iceSendBin, nullptr);
+
+ internalRtpPad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SINK);
+ internalRtcpPad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SINK);
+ if (!gst_element_add_pad(iceSendBin, internalRtpPad) ||
+ !gst_element_add_pad(iceSendBin, internalRtcpPad)) {
+ qFatal("Failed to add pads to send bin");
+ }
+
+ connection = new QXmppIceConnection(this);
+ connection->addComponent(RTP_COMPONENT);
+ connection->addComponent(RTCP_COMPONENT);
+ apprtpsink = gst_element_factory_make("appsink", nullptr);
+ apprtcpsink = gst_element_factory_make("appsink", nullptr);
+ if (!apprtpsink || !apprtcpsink) {
+ qFatal("Failed to create appsinks");
+ }
+
+ g_signal_connect_swapped(apprtpsink, "new-sample",
+ G_CALLBACK(+[](QXmppCallStreamPrivate *p, GstElement *appsink) -> GstFlowReturn {
+ return p->sendDatagram(appsink, RTP_COMPONENT);
+ }),
+ this);
+ g_signal_connect_swapped(apprtcpsink, "new-sample",
+ G_CALLBACK(+[](QXmppCallStreamPrivate *p, GstElement *appsink) -> GstFlowReturn {
+ return p->sendDatagram(appsink, RTCP_COMPONENT);
+ }),
+ this);
+
+ apprtpsrc = gst_element_factory_make("appsrc", nullptr);
+ apprtcpsrc = gst_element_factory_make("appsrc", nullptr);
+ if (!apprtpsrc || !apprtcpsrc) {
+ qFatal("Failed to create appsrcs");
+ }
+
+ // TODO check these parameters
+ g_object_set(apprtpsink, "emit-signals", true, "async", false, "max-buffers", 1, "drop", true, nullptr);
+ g_object_set(apprtcpsink, "emit-signals", true, "async", false, nullptr);
+ g_object_set(apprtpsrc, "is-live", true, "max-latency", 5000000, nullptr);
+ g_object_set(apprtcpsrc, "is-live", true, nullptr);
+
+ connect(connection->component(RTP_COMPONENT), &QXmppIceComponent::datagramReceived,
+ [&](const QByteArray &datagram) { datagramReceived(datagram, apprtpsrc); });
+ connect(connection->component(RTCP_COMPONENT), &QXmppIceComponent::datagramReceived,
+ [&](const QByteArray &datagram) { datagramReceived(datagram, apprtcpsrc); });
+
+ if (!gst_bin_add(GST_BIN(iceReceiveBin), apprtpsrc) ||
+ !gst_bin_add(GST_BIN(iceReceiveBin), apprtcpsrc)) {
+ qFatal("Failed to add appsrcs to receive bin");
+ }
+
+ if (!gst_element_link_pads(apprtpsrc, "src", rtpbin, QStringLiteral("recv_rtp_sink_%1").arg(id).toLatin1().data()) ||
+ !gst_element_link_pads(apprtcpsrc, "src", rtpbin, QStringLiteral("recv_rtcp_sink_%1").arg(id).toLatin1().data())) {
+ qFatal("Failed to link receive pads");
+ }
+
+ // We need frequent RTCP reports for the bandwidth controller
+ GstElement *rtpSession;
+ g_signal_emit_by_name(rtpbin, "get-session", static_cast<uint>(id), &rtpSession);
+ g_object_set(rtpSession, "rtcp-min-interval", 100000000, nullptr);
+
+ gst_element_sync_state_with_parent(iceReceiveBin);
+ gst_element_sync_state_with_parent(iceSendBin);
+}
+
+QXmppCallStreamPrivate::~QXmppCallStreamPrivate()
+{
+ connection->close();
+
+ // Remove elements from pipeline
+ if ((encoderBin && !gst_bin_remove(GST_BIN(pipeline), encoderBin)) ||
+ (decoderBin && !gst_bin_remove(GST_BIN(pipeline), decoderBin)) ||
+ !gst_bin_remove(GST_BIN(pipeline), iceSendBin) ||
+ !gst_bin_remove(GST_BIN(pipeline), iceReceiveBin)) {
+ qFatal("Failed to remove bins from pipeline");
+ }
+}
+
+GstFlowReturn QXmppCallStreamPrivate::sendDatagram(GstElement *appsink, int component)
+{
+ GstSample *sample;
+ g_signal_emit_by_name(appsink, "pull-sample", &sample);
+ if (!sample) {
+ qFatal("Could not get sample");
+ return GST_FLOW_ERROR;
+ }
+
+ GstMapInfo mapInfo;
+ GstBuffer *buffer = gst_sample_get_buffer(sample);
+ if (!buffer) {
+ qFatal("Could not get buffer");
+ return GST_FLOW_ERROR;
+ }
+ if (!gst_buffer_map(buffer, &mapInfo, GST_MAP_READ)) {
+ qFatal("Could not map buffer");
+ return GST_FLOW_ERROR;
+ }
+ QByteArray datagram;
+ datagram.resize(mapInfo.size);
+ std::memcpy(datagram.data(), mapInfo.data, mapInfo.size);
+ gst_buffer_unmap(buffer, &mapInfo);
+ gst_sample_unref(sample);
+
+ if (connection->component(component)->isConnected() &&
+ connection->component(component)->sendDatagram(datagram) != datagram.size()) {
+ return GST_FLOW_ERROR;
+ }
+ return GST_FLOW_OK;
+}
+
+void QXmppCallStreamPrivate::datagramReceived(const QByteArray &datagram, GstElement *appsrc)
+{
+ GstBuffer *buffer = gst_buffer_new_and_alloc(datagram.size());
+ GstMapInfo mapInfo;
+ if (!gst_buffer_map(buffer, &mapInfo, GST_MAP_WRITE)) {
+ qFatal("Could not map buffer");
+ return;
+ }
+ std::memcpy(mapInfo.data, datagram.data(), mapInfo.size);
+ gst_buffer_unmap(buffer, &mapInfo);
+ GstFlowReturn ret;
+ g_signal_emit_by_name(appsrc, "push-buffer", buffer, &ret);
+ gst_buffer_unref(buffer);
+}
+
+void QXmppCallStreamPrivate::addEncoder(QXmppCallPrivate::GstCodec &codec)
+{
+ // Remove old encoder and payloader if they exist
+ if (encoderBin) {
+ if (!gst_bin_remove(GST_BIN(pipeline), encoderBin)) {
+ qFatal("Failed to remove existing encoder bin");
+ }
+ }
+ encoderBin = gst_bin_new(QStringLiteral("encoder_%1").arg(id).toLatin1().data());
+ if (!gst_bin_add(GST_BIN(pipeline), encoderBin)) {
+ qFatal("Failed to add encoder bin to wrapper");
+ return;
+ }
+
+ sendPad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SINK);
+ gst_element_add_pad(encoderBin, sendPad);
+
+ // Create new elements
+ GstElement *queue = gst_element_factory_make("queue", nullptr);
+ if (!queue) {
+ qFatal("Failed to create queue");
+ return;
+ }
+
+ GstElement *pay = gst_element_factory_make(codec.gstPay.toLatin1().data(), nullptr);
+ if (!pay) {
+ qFatal("Failed to create payloader");
+ return;
+ }
+ g_object_set(pay, "pt", codec.pt, "ssrc", localSsrc, nullptr);
+
+ GstElement *encoder = gst_element_factory_make(codec.gstEnc.toLatin1().data(), nullptr);
+ if (!encoder) {
+ qFatal("Failed to create encoder");
+ return;
+ }
+ for (auto &encProp : codec.encProps) {
+ g_object_set(encoder, encProp.name.toLatin1().data(), encProp.value, nullptr);
+ }
+
+ gst_bin_add_many(GST_BIN(encoderBin), queue, encoder, pay, nullptr);
+
+ if (!gst_element_link_pads(pay, "src", rtpbin, QStringLiteral("send_rtp_sink_%1").arg(id).toLatin1().data()) ||
+ !gst_element_link_many(queue, encoder, pay, nullptr)) {
+ qFatal("Could not link all encoder pads");
+ return;
+ }
+
+ if (!gst_ghost_pad_set_target(GST_GHOST_PAD(sendPad), gst_element_get_static_pad(queue, "sink"))) {
+ qFatal("Failed to set send pad");
+ return;
+ }
+
+ if (sendPadCB) {
+ sendPadCB(sendPad);
+ }
+
+ gst_element_sync_state_with_parent(encoderBin);
+
+ addRtcpSender(gst_element_get_request_pad(rtpbin, QStringLiteral("send_rtcp_src_%1").arg(id).toLatin1().data()));
+}
+
+void QXmppCallStreamPrivate::addDecoder(GstPad *pad, QXmppCallPrivate::GstCodec &codec)
+{
+ // Remove old decoder and depayloader if they exist
+ if (decoderBin) {
+ if (!gst_bin_remove(GST_BIN(pipeline), decoderBin)) {
+ qFatal("Failed to remove existing decoder bin");
+ }
+ }
+ decoderBin = gst_bin_new(QStringLiteral("decoder_%1").arg(id).toLatin1().data());
+ if (!gst_bin_add(GST_BIN(pipeline), decoderBin)) {
+ qFatal("Failed to add decoder bin to wrapper");
+ return;
+ }
+
+ receivePad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SRC);
+ internalReceivePad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SINK);
+ gst_element_add_pad(decoderBin, receivePad);
+ gst_element_add_pad(decoderBin, internalReceivePad);
+
+ // Create new elements
+ GstElement *depay = gst_element_factory_make(codec.gstDepay.toLatin1().data(), nullptr);
+ if (!depay) {
+ qFatal("Failed to create depayloader");
+ return;
+ }
+
+ GstElement *decoder = gst_element_factory_make(codec.gstDec.toLatin1().data(), nullptr);
+ if (!decoder) {
+ qFatal("Failed to create decoder");
+ return;
+ }
+
+ GstElement *queue = gst_element_factory_make("queue", nullptr);
+ if (!queue) {
+ qFatal("Failed to create queue");
+ return;
+ }
+
+ gst_bin_add_many(GST_BIN(decoderBin), depay, decoder, queue, nullptr);
+
+ if (!gst_ghost_pad_set_target(GST_GHOST_PAD(internalReceivePad), gst_element_get_static_pad(depay, "sink")) ||
+ gst_pad_link(pad, internalReceivePad) != GST_PAD_LINK_OK ||
+ !gst_element_link_many(depay, decoder, queue, nullptr) ||
+ !gst_ghost_pad_set_target(GST_GHOST_PAD(receivePad), gst_element_get_static_pad(queue, "src"))) {
+ qFatal("Could not link all decoder pads");
+ return;
+ }
+
+ gst_element_sync_state_with_parent(decoderBin);
+
+ if (receivePadCB) {
+ receivePadCB(receivePad);
+ }
+}
+
+void QXmppCallStreamPrivate::addRtpSender(GstPad *pad)
+{
+ if (!gst_bin_add(GST_BIN(iceSendBin), apprtpsink)) {
+ qFatal("Failed to add rtp sink to send bin");
+ }
+ gst_element_sync_state_with_parent(apprtpsink);
+ if (!gst_ghost_pad_set_target(GST_GHOST_PAD(internalRtpPad), gst_element_get_static_pad(apprtpsink, "sink")) ||
+ gst_pad_link(pad, internalRtpPad) != GST_PAD_LINK_OK) {
+ qFatal("Failed to link rtp pads");
+ }
+}
+
+void QXmppCallStreamPrivate::addRtcpSender(GstPad *pad)
+{
+ if (!gst_bin_add(GST_BIN(iceSendBin), apprtcpsink)) {
+ qFatal("Failed to add rtcp sink to send bin");
+ }
+ gst_element_sync_state_with_parent(apprtcpsink);
+ if (!gst_ghost_pad_set_target(GST_GHOST_PAD(internalRtcpPad), gst_element_get_static_pad(apprtcpsink, "sink")) ||
+ gst_pad_link(pad, internalRtcpPad) != GST_PAD_LINK_OK) {
+ qFatal("Failed to link rtcp pads");
+ }
+}
+
+QXmppCallStream::QXmppCallStream(GstElement *pipeline, GstElement *rtpbin,
+ QString media, QString creator, QString name, int id)
+{
+ d = new QXmppCallStreamPrivate(this, pipeline, rtpbin, media, creator, name, id);
+}
+
+QString QXmppCallStream::creator() const
+{
+ return d->creator;
+}
+
+QString QXmppCallStream::media() const
+{
+ return d->media;
+}
+
+QString QXmppCallStream::name() const
+{
+ return d->name;
+}
+
+int QXmppCallStream::id() const
+{
+ return d->id;
+}
+
+void QXmppCallStream::setReceivePadCallback(void (*cb)(GstPad *))
+{
+ d->receivePadCB = cb;
+ if (d->receivePad) {
+ d->receivePadCB(d->receivePad);
+ }
+}
+
+void QXmppCallStream::setSendPadCallback(void (*cb)(GstPad *))
+{
+ d->sendPadCB = cb;
+ if (d->sendPad) {
+ d->sendPadCB(d->sendPad);
+ }
+}
diff --git a/src/client/QXmppCallStream.h b/src/client/QXmppCallStream.h
new file mode 100644
index 00000000..a9103384
--- /dev/null
+++ b/src/client/QXmppCallStream.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The QXmpp developers
+ *
+ * Author:
+ * Niels Ole Salscheider
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPCALLSTREAM_H
+#define QXMPPCALLSTREAM_H
+
+#include <QXmppGlobal.h>
+
+#include <gst/gst.h>
+
+#include <QObject>
+
+class QXmppCallStreamPrivate;
+class QXmppIceConnection;
+class QXmppCall;
+class QXmppCallPrivate;
+
+/// \brief The QXmppCallStream class represents an RTP stream in a VoIP call.
+///
+/// \note THIS API IS NOT FINALIZED YET
+///
+/// \since QXmpp 1.3
+
+class QXMPP_EXPORT QXmppCallStream : public QObject
+{
+ Q_OBJECT
+
+public:
+ QString creator() const;
+ QString media() const;
+ QString name() const;
+ int id() const;
+ void setReceivePadCallback(void (*cb)(GstPad *));
+ void setSendPadCallback(void (*cb)(GstPad *));
+
+private:
+ QXmppCallStream(GstElement *pipeline, GstElement *rtpbin,
+ QString media, QString creator, QString name, int id);
+
+ QXmppCallStreamPrivate *d;
+
+ friend class QXmppCall;
+ friend class QXmppCallPrivate;
+};
+
+#endif
diff --git a/src/client/QXmppCallStream_p.h b/src/client/QXmppCallStream_p.h
new file mode 100644
index 00000000..568460da
--- /dev/null
+++ b/src/client/QXmppCallStream_p.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008-2020 The QXmpp developers
+ *
+ * Authors:
+ * Niels Ole Salscheider
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPCALLSTREAM_P_H
+#define QXMPPCALLSTREAM_P_H
+
+#include "QXmppCall_p.h"
+#include "QXmppJingleIq.h"
+
+#include <gst/gst.h>
+
+#include <QList>
+#include <QObject>
+#include <QString>
+
+class QXmppIceConnection;
+
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QXmpp API.
+// This header file may change from version to version without notice,
+// or even be removed.
+//
+// We mean it.
+//
+
+static const int RTP_COMPONENT = 1;
+static const int RTCP_COMPONENT = 2;
+
+static const QLatin1String AUDIO_MEDIA("audio");
+static const QLatin1String VIDEO_MEDIA("video");
+
+class QXmppCallStreamPrivate : public QObject
+{
+ Q_OBJECT
+
+public:
+ QXmppCallStreamPrivate(QXmppCallStream *parent, GstElement *pipeline_, GstElement *rtpbin_,
+ QString media_, QString creator_, QString name_, int id_);
+ ~QXmppCallStreamPrivate();
+
+ GstFlowReturn sendDatagram(GstElement *appsink, int component);
+ void datagramReceived(const QByteArray &datagram, GstElement *appsrc);
+
+ void addEncoder(QXmppCallPrivate::GstCodec &codec);
+ void addDecoder(GstPad *pad, QXmppCallPrivate::GstCodec &codec);
+ void addRtpSender(GstPad *pad);
+ void addRtcpSender(GstPad *pad);
+
+ QXmppCallStream *q;
+
+ quint32 localSsrc;
+
+ GstElement *pipeline;
+ GstElement *rtpbin;
+ GstPad *sendPad;
+ GstPad *receivePad;
+ GstPad *internalReceivePad;
+ GstPad *internalRtpPad;
+ GstPad *internalRtcpPad;
+ GstElement *encoderBin;
+ GstElement *decoderBin;
+ GstElement *iceReceiveBin;
+ GstElement *iceSendBin;
+ GstElement *apprtpsrc;
+ GstElement *apprtcpsrc;
+ GstElement *apprtpsink;
+ GstElement *apprtcpsink;
+
+ void (*sendPadCB)(GstPad *);
+ void (*receivePadCB)(GstPad *);
+
+ QXmppIceConnection *connection;
+ QString media;
+ QString creator;
+ QString name;
+ int id;
+
+ QList<QXmppJinglePayloadType> payloadTypes;
+};
+
+#endif
diff --git a/src/client/QXmppCall_p.h b/src/client/QXmppCall_p.h
new file mode 100644
index 00000000..b3e052c8
--- /dev/null
+++ b/src/client/QXmppCall_p.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2008-2020 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPCALL_P_H
+#define QXMPPCALL_P_H
+
+#include "QXmppCall.h"
+#include "QXmppJingleIq.h"
+
+#include <gst/gst.h>
+
+#include <QList>
+
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QXmpp API.
+// This header file may change from version to version without notice,
+// or even be removed.
+//
+// We mean it.
+//
+
+class QXmppCallStream;
+
+class QXmppCallPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ struct GstCodec {
+ int pt;
+ QString name;
+ int channels;
+ int clockrate;
+ QString gstPay;
+ QString gstDepay;
+ QString gstEnc;
+ QString gstDec;
+ struct Property {
+ QString name;
+ int value;
+ };
+ // Use e.g. gst-inspect-1.0 x264enc to find good encoder settings for live streaming
+ QList<Property> encProps;
+ };
+
+ QXmppCallPrivate(QXmppCall *qq);
+ ~QXmppCallPrivate();
+
+ 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<GstCodec> &formats);
+
+ QXmppCallStream *createStream(const QString &media, const QString &creator, const QString &name);
+ QXmppCallStream *findStreamByMedia(const QString &media);
+ QXmppCallStream *findStreamByName(const QString &name);
+ QXmppCallStream *findStreamById(const 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 terminate(QXmppJingleIq::Reason::Type reasonType);
+
+ QXmppCall::Direction direction;
+ QString jid;
+ QString ownJid;
+ QXmppCallManager *manager;
+ QList<QXmppJingleIq> requests;
+ QString sid;
+ QXmppCall::State state;
+
+ GstElement *pipeline;
+ GstElement *rtpbin;
+
+ // Media streams
+ QList<QXmppCallStream *> streams;
+ int nextId;
+
+ // Supported codecs
+ QList<GstCodec> videoCodecs = {
+ { .pt = 100, .name = "H264", .channels = 1, .clockrate = 90000, .gstPay = "rtph264pay", .gstDepay = "rtph264depay", .gstEnc = "x264enc", .gstDec = "avdec_h264", .encProps = { { "tune", 4 }, { "speed-preset", 3 }, {"byte-stream", true}, { "bitrate", 512 } } },
+ { .pt = 99, .name = "VP8", .channels = 1, .clockrate = 90000, .gstPay = "rtpvp8pay", .gstDepay = "rtpvp8depay", .gstEnc = "vp8enc", .gstDec = "vp8dec", .encProps = { { "deadline", 20000 }, { "target-bitrate", 512000 } } },
+ // vp9enc and x265enc seem to be very slow. Give them a lower priority for now.
+ { .pt = 102, .name = "H265", .channels = 1, .clockrate = 90000, .gstPay = "rtph265pay", .gstDepay = "rtph265depay", .gstEnc = "x265enc", .gstDec = "avdec_h265", .encProps = { { "tune", 4 }, { "speed-preset", 3 }, { "bitrate", 512 } } },
+ { .pt = 101, .name = "VP9", .channels = 1, .clockrate = 90000, .gstPay = "rtpvp9pay", .gstDepay = "rtpvp9depay", .gstEnc = "vp9enc", .gstDec = "vp9dec", .encProps = { { "deadline", 20000 }, { "target-bitrate", 512000 } } }
+ };
+
+ QList<GstCodec> audioCodecs = {
+ { .pt = 98, .name = "OPUS", .channels = 2, .clockrate = 48000, .gstPay = "rtpopuspay", .gstDepay = "rtpopusdepay", .gstEnc = "opusenc", .gstDec = "opusdec" },
+ { .pt = 98, .name = "OPUS", .channels = 1, .clockrate = 48000, .gstPay = "rtpopuspay", .gstDepay = "rtpopusdepay", .gstEnc = "opusenc", .gstDec = "opusdec" },
+ { .pt = 97, .name = "SPEEX", .channels = 1, .clockrate = 48000, .gstPay = "rtpspeexpay", .gstDepay = "rtpspeexdepay", .gstEnc = "speexenc", .gstDec = "speexdec" },
+ { .pt = 97, .name = "SPEEX", .channels = 1, .clockrate = 44100, .gstPay = "rtpspeexpay", .gstDepay = "rtpspeexdepay", .gstEnc = "speexenc", .gstDec = "speexdec" },
+ { .pt = 96, .name = "AAC", .channels = 2, .clockrate = 48000, .gstPay = "rtpmp4apay", .gstDepay = "rtpmp4adepay", .gstEnc = "avenc_aac", .gstDec = "avdec_aac" },
+ { .pt = 96, .name = "AAC", .channels = 2, .clockrate = 44100, .gstPay = "rtpmp4apay", .gstDepay = "rtpmp4adepay", .gstEnc = "avenc_aac", .gstDec = "avdec_aac" },
+ { .pt = 96, .name = "AAC", .channels = 1, .clockrate = 48000, .gstPay = "rtpmp4apay", .gstDepay = "rtpmp4adepay", .gstEnc = "avenc_aac", .gstDec = "avdec_aac" },
+ { .pt = 96, .name = "AAC", .channels = 1, .clockrate = 44100, .gstPay = "rtpmp4apay", .gstDepay = "rtpmp4adepay", .gstEnc = "avenc_aac", .gstDec = "avdec_aac" },
+ { .pt = 8, .name = "PCMA", .channels = 1, .clockrate = 8000, .gstPay = "rtppcmapay", .gstDepay = "rtppcmadepay", .gstEnc = "alawenc", .gstDec = "alawdec" },
+ { .pt = 0, .name = "PCMU", .channels = 1, .clockrate = 8000, .gstPay = "rtppcmupay", .gstDepay = "rtppcmudepay", .gstEnc = "mulawenc", .gstDec = "mulawdec" }
+ };
+
+private:
+ QXmppCall *q;
+};
+
+#endif