aboutsummaryrefslogtreecommitdiff
path: root/src/client/QXmppCallManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/QXmppCallManager.cpp')
-rw-r--r--src/client/QXmppCallManager.cpp655
1 files changed, 12 insertions, 643 deletions
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);