diff options
Diffstat (limited to 'src/client/QXmppCallManager.cpp')
| -rw-r--r-- | src/client/QXmppCallManager.cpp | 655 |
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); |
