aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2010-07-18 10:55:26 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2010-07-18 10:55:26 +0000
commitbc608214ed38789670bff940abc50083317e73d9 (patch)
tree13b7a6046b9dc4aae4952fe9cad989e20a10e2e9 /source
parent4f5e71e25599c10f439f92b825ff0a247ed8ba7b (diff)
downloadqxmpp-bc608214ed38789670bff940abc50083317e73d9.tar.gz
add intial support for Jingle RTP Sessions
Diffstat (limited to 'source')
-rw-r--r--source/QXmppCallManager.cpp810
-rw-r--r--source/QXmppCallManager.h211
-rw-r--r--source/QXmppClient.cpp11
-rw-r--r--source/QXmppClient.h3
-rw-r--r--source/source.pro2
5 files changed, 1037 insertions, 0 deletions
diff --git a/source/QXmppCallManager.cpp b/source/QXmppCallManager.cpp
new file mode 100644
index 00000000..09c49d43
--- /dev/null
+++ b/source/QXmppCallManager.cpp
@@ -0,0 +1,810 @@
+/*
+ * Copyright (C) 2010 Bolloré telecom
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/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 <QTimer>
+
+#include "QXmppCallManager.h"
+#include "QXmppCodec.h"
+#include "QXmppConstants.h"
+#include "QXmppJingleIq.h"
+#include "QXmppStream.h"
+#include "QXmppStun.h"
+#include "QXmppUtils.h"
+
+const quint8 RTP_VERSION = 0x02;
+
+enum CodecId {
+ G711u = 0,
+ GSM = 3,
+ G723 = 4,
+ G711a = 8,
+ G722 = 9,
+ L16Stereo = 10,
+ L16Mono = 11,
+ G728 = 15,
+ G729 = 18,
+};
+
+#define SAMPLE_BYTES 2
+
+QXmppCall::QXmppCall(const QString &jid, QXmppCall::Direction direction, QObject *parent)
+ : QIODevice(parent),
+ m_direction(direction),
+ m_jid(jid),
+ m_state(OfferState),
+ m_signalsEmitted(false),
+ m_writtenSinceLastEmit(0),
+ m_buffered(false),
+ m_codec(0),
+ m_incomingBuffering(true),
+ m_incomingMinimum(0),
+ m_incomingSequence(0),
+ m_incomingStamp(0),
+ m_outgoingMarker(true),
+ m_outgoingSequence(0),
+ m_outgoingStamp(0)
+{
+ bool iceControlling = (m_direction == OutgoingDirection);
+
+ // RTP socket
+ m_socket = new QXmppStunSocket(iceControlling, this);
+ m_socket->setComponent(1);
+
+ bool check = connect(m_socket, SIGNAL(logMessage(QXmppLogger::MessageType, QString)),
+ this, SIGNAL(logMessage(QXmppLogger::MessageType, QString)));
+ Q_ASSERT(check);
+
+ check = connect(m_socket, SIGNAL(connected()),
+ this, SLOT(updateOpenMode()));
+ Q_ASSERT(check);
+
+ check = connect(m_socket, SIGNAL(datagramReceived(QByteArray)),
+ this, SLOT(datagramReceived(QByteArray)));
+ Q_ASSERT(check);
+
+ // RTCP socket
+ m_rtcpSocket = new QXmppStunSocket(iceControlling, this);
+ m_rtcpSocket->setComponent(2);
+ m_rtcpSocket->setLocalUser(m_socket->localUser());
+ m_rtcpSocket->setLocalPassword(m_socket->localPassword());
+
+ check = connect(m_rtcpSocket, SIGNAL(logMessage(QXmppLogger::MessageType, QString)),
+ this, SIGNAL(logMessage(QXmppLogger::MessageType, QString)));
+ Q_ASSERT(check);
+}
+
+/// Call this method if you wish to accept an incoming call.
+///
+
+void QXmppCall::accept()
+{
+ if (m_direction == IncomingDirection && m_state == OfferState)
+ setState(QXmppCall::ConnectingState);
+}
+
+qint64 QXmppCall::bytesAvailable() const
+{
+ return m_incomingBuffer.size();
+}
+
+void QXmppCall::terminate()
+{
+ if (m_state == FinishedState)
+ return;
+
+ m_state = QXmppCall::FinishedState;
+
+ close();
+ m_socket->close();
+
+ // emit signals later
+ QTimer::singleShot(0, this, SLOT(terminated()));
+}
+
+void QXmppCall::terminated()
+{
+ emit openModeChanged(openMode());
+ emit stateChanged(m_state);
+ emit finished();
+}
+
+void QXmppCall::connectToHost()
+{
+ m_socket->connectToHost();
+ m_rtcpSocket->connectToHost();
+}
+
+/// Returns the call's direction.
+///
+
+QXmppCall::Direction QXmppCall::direction() const
+{
+ return m_direction;
+}
+
+void QXmppCall::emitSignals()
+{
+ emit bytesWritten(m_writtenSinceLastEmit);
+ m_writtenSinceLastEmit = 0;
+ m_signalsEmitted = false;
+}
+
+void QXmppCall::hangup()
+{
+ if (m_state != QXmppCall::FinishedState)
+ setState(QXmppCall::DisconnectingState);
+}
+
+bool QXmppCall::isSequential() const
+{
+ return true;
+}
+
+QString QXmppCall::jid() const
+{
+ return m_jid;
+}
+
+QList<QXmppJingleCandidate> QXmppCall::localCandidates() const
+{
+ return m_socket->localCandidates() + m_rtcpSocket->localCandidates();
+}
+
+/// Returns the negociated payload type.
+///
+/// You can use this to determine the QAudioFormat to use with your
+/// QAudioInput/QAudioOutput.
+
+QXmppJinglePayloadType QXmppCall::payloadType() const
+{
+ return m_payloadType;
+}
+
+void QXmppCall::setPayloadType(const QXmppJinglePayloadType &payloadType)
+{
+ m_payloadType = payloadType;
+ if (payloadType.id() == G711u)
+ m_codec = new QXmppG711uCodec(payloadType.clockrate());
+ else if (payloadType.id() == G711a)
+ m_codec = new QXmppG711aCodec(payloadType.clockrate());
+#ifdef QXMPP_USE_SPEEX
+ else if (payloadType.name().toLower() == "speex")
+ m_codec = new QXmppSpeexCodec(payloadType.clockrate());
+#endif
+ else
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("QXmppCall got an unknown codec : %1 (%2)")
+ .arg(QString::number(payloadType.id()))
+ .arg(payloadType.name()));
+ return;
+ }
+
+ // size in bytes of an unencoded packet
+ m_outgoingChunk = SAMPLE_BYTES * payloadType.ptime() * payloadType.clockrate() / 1000;
+
+ // initial number of bytes to buffer
+ m_incomingMinimum = m_outgoingChunk * 5;
+
+ updateOpenMode();
+}
+
+void QXmppCall::addRemoteCandidates(const QList<QXmppJingleCandidate> &candidates)
+{
+ foreach (const QXmppJingleCandidate &candidate, candidates)
+ {
+ m_socket->addRemoteCandidate(candidate);
+ m_rtcpSocket->addRemoteCandidate(candidate);
+ }
+}
+
+void QXmppCall::setRemoteUser(const QString &user)
+{
+ m_socket->setRemoteUser(user);
+ m_rtcpSocket->setRemoteUser(user);
+}
+
+void QXmppCall::setRemotePassword(const QString &password)
+{
+ m_socket->setRemotePassword(password);
+ m_rtcpSocket->setRemotePassword(password);
+}
+
+void QXmppCall::updateOpenMode()
+{
+ // determine mode
+ QIODevice::OpenMode mode = QIODevice::NotOpen;
+ if (m_codec && m_socket->isConnected())
+ {
+ // update state
+ setState(ActiveState);
+
+ // check whether we are buffering
+ if (m_incomingBuffer.size() >= m_incomingMinimum)
+ m_incomingBuffering = false;
+ if (m_incomingBuffering)
+ mode = QIODevice::WriteOnly;
+ else
+ mode = QIODevice::ReadWrite;
+ }
+
+ // update mode
+ if (mode != openMode())
+ {
+ open(mode);
+ emit openModeChanged(mode);
+ }
+}
+
+void QXmppCall::datagramReceived(const QByteArray &buffer)
+{
+ if (!m_codec)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QLatin1String("QXmppCall:datagramReceived before codec selection"));
+ return;
+ }
+
+ if (buffer.size() < 12 || (quint8(buffer.at(0)) >> 6) != RTP_VERSION)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QLatin1String("QXmppCall::datagramReceived got an invalid RTP packet"));
+ return;
+ }
+
+ // parse RTP header
+ QDataStream stream(buffer);
+ quint8 version, type;
+ quint32 ssrc;
+ quint16 sequence;
+ quint32 stamp;
+ stream >> version;
+ stream >> type;
+ stream >> sequence;
+ stream >> stamp;
+ stream >> ssrc;
+ const qint64 packetLength = buffer.size() - 12;
+
+#ifdef QXMPP_DEBUG_RTP
+ emit logMessage(QXmppLogger::ReceivedMessage,
+ QString("RTP packet seq %1 stamp %2 size %3")
+ .arg(QString::number(sequence))
+ .arg(QString::number(stamp))
+ .arg(QString::number(packetLength)));
+#endif
+
+ // check sequence number
+ if (sequence != m_incomingSequence + 1)
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("RTP packet seq %1 is out of order, previous was %2")
+ .arg(QString::number(sequence))
+ .arg(QString::number(m_incomingSequence)));
+ m_incomingSequence = sequence;
+
+ // determine packet's position in the buffer (in bytes)
+ qint64 packetOffset = 0;
+ if (!buffer.isEmpty())
+ {
+ packetOffset = (stamp - m_incomingStamp) * SAMPLE_BYTES;
+ if (packetOffset < 0)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("RTP packet stamp %1 is too old, buffer start is %2")
+ .arg(QString::number(stamp))
+ .arg(QString::number(m_incomingStamp)));
+ return;
+ }
+ } else {
+ m_incomingStamp = stamp;
+ }
+
+ // allocate space for new packet
+ if (packetOffset + packetLength > m_incomingBuffer.size())
+ m_incomingBuffer += QByteArray(packetOffset + packetLength - m_incomingBuffer.size(), 0);
+ QDataStream output(&m_incomingBuffer, QIODevice::WriteOnly);
+ output.device()->seek(packetOffset);
+ output.setByteOrder(QDataStream::LittleEndian);
+ m_codec->decode(stream, output);
+
+ updateOpenMode();
+ emit readyRead();
+}
+
+/// Returns the session identifier for this call.
+
+QString QXmppCall::sid() const
+{
+ return m_sid;
+}
+
+QXmppCall::State QXmppCall::state() const
+{
+ return m_state;
+}
+
+void QXmppCall::setState(QXmppCall::State state)
+{
+ if (m_state != state)
+ {
+ m_state = state;
+ emit stateChanged(m_state);
+ }
+}
+
+qint64 QXmppCall::readData(char * data, qint64 maxSize)
+{
+ qint64 readSize = qMin(maxSize, qint64(m_incomingBuffer.size()));
+ memcpy(data, m_incomingBuffer.constData(), readSize);
+ m_incomingBuffer.remove(0, readSize);
+ if (readSize < maxSize)
+ {
+ emit logMessage(QXmppLogger::InformationMessage,
+ QString("QXmppCall::readData missing %1 bytes").arg(QString::number(maxSize - readSize)));
+ memset(data + readSize, 0, maxSize - readSize);
+ }
+ m_incomingStamp += readSize / SAMPLE_BYTES;
+ return maxSize;
+}
+
+qint64 QXmppCall::writeData(const char * data, qint64 maxSize)
+{
+ if (!m_codec)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QLatin1String("QXmppCall::writeData before codec was set"));
+ return -1;
+ }
+
+ m_outgoingBuffer.append(data, maxSize);
+ while (m_outgoingBuffer.size() >= m_outgoingChunk)
+ {
+ QByteArray header;
+ QDataStream stream(&header, QIODevice::WriteOnly);
+ quint8 version = RTP_VERSION << 6;
+ stream << version;
+ quint8 type = m_payloadType.id();
+ if (m_outgoingMarker)
+ {
+ type |= 0x80;
+ m_outgoingMarker= false;
+ }
+ stream << type;
+ stream << ++m_outgoingSequence;
+ stream << m_outgoingStamp;
+ const quint32 ssrc = 0;
+ stream << ssrc;
+
+ QByteArray chunk = m_outgoingBuffer.left(m_outgoingChunk);
+ QDataStream input(chunk);
+ input.setByteOrder(QDataStream::LittleEndian);
+ m_outgoingStamp += m_codec->encode(input, stream);
+
+ if (m_socket->writeDatagram(header) < 0)
+ emit logMessage(QXmppLogger::WarningMessage,
+ QLatin1String("QXmppCall:writeData could not send audio data"));
+#ifdef QXMPP_DEBUG_RTP
+ else
+ emit logMessage(QXmppLogger::SentMessage,
+ QString("RTP packet seq %1 stamp %2 size %3")
+ .arg(QString::number(m_outgoingSequence))
+ .arg(QString::number(m_outgoingStamp))
+ .arg(QString::number(header.size() - 12)));
+#endif
+
+ m_outgoingBuffer.remove(0, chunk.size());
+ }
+
+ m_writtenSinceLastEmit += maxSize;
+ if (!m_signalsEmitted && !signalsBlocked()) {
+ m_signalsEmitted = true;
+ QMetaObject::invokeMethod(this, "emitSignals", Qt::QueuedConnection);
+ }
+
+ return maxSize;
+}
+
+QXmppCallManager::QXmppCallManager(QXmppStream *stream, QObject *parent)
+ : QObject(parent), m_stream(stream)
+{
+ // setup logging
+ bool check = connect(this, SIGNAL(logMessage(QXmppLogger::MessageType, QString)),
+ m_stream, SIGNAL(logMessage(QXmppLogger::MessageType, QString)));
+ Q_ASSERT(check);
+
+ check = connect(stream, SIGNAL(iqReceived(QXmppIq)),
+ this, SLOT(iqReceived(QXmppIq)));
+ Q_ASSERT(check);
+
+ check = connect(stream, SIGNAL(jingleIqReceived(QXmppJingleIq)),
+ this, SLOT(jingleIqReceived(QXmppJingleIq)));
+ Q_ASSERT(check);
+}
+
+/// Initiates a new outgoing call to the specified recipient.
+///
+/// \param jid
+
+QXmppCall *QXmppCallManager::call(const QString &jid)
+{
+ QXmppCall *call = new QXmppCall(jid, QXmppCall::OutgoingDirection, this);
+ call->m_sid = generateStanzaHash();
+ m_calls << call;
+ connect(call, SIGNAL(destroyed(QObject*)), this, SLOT(callDestroyed(QObject*)));
+ connect(call, SIGNAL(stateChanged(QXmppCall::State)),
+ this, SLOT(callStateChanged(QXmppCall::State)));
+ connect(call, SIGNAL(logMessage(QXmppLogger::MessageType, QString)),
+ this, SIGNAL(logMessage(QXmppLogger::MessageType, QString)));
+
+ QXmppJingleIq iq;
+ iq.setTo(jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionInitiate);
+ iq.setInitiator(m_stream->configuration().jid());
+ iq.setSid(call->m_sid);
+ iq.content().setCreator("initiator");
+ iq.content().setName("voice");
+ iq.content().setSenders("both");
+
+ // description
+ iq.content().setDescriptionMedia("audio");
+ foreach (const QXmppJinglePayloadType &payload, localPayloadTypes())
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(call->m_socket->localUser());
+ iq.content().setTransportPassword(call->m_socket->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, call->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ sendRequest(call, iq);
+
+ return call;
+}
+
+void QXmppCallManager::callDestroyed(QObject *object)
+{
+ m_calls.removeAll(static_cast<QXmppCall*>(object));
+}
+
+void QXmppCallManager::callStateChanged(QXmppCall::State state)
+{
+ QXmppCall *call = qobject_cast<QXmppCall*>(sender());
+ if (!call || !m_calls.contains(call))
+ return;
+
+#if 0
+ // disconnect from the signal
+ disconnect(call, SIGNAL(stateChanged(QXmppCall::State)),
+ this, SLOT(callStateChanged(QXmppCall::State)));
+#endif
+
+ if (state == QXmppCall::DisconnectingState)
+ {
+ // hangup up call
+ QXmppJingleIq iq;
+ iq.setTo(call->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionTerminate);
+ iq.setSid(call->m_sid);
+ sendRequest(call, iq);
+
+ // schedule forceful termination in 5s
+ QTimer::singleShot(5000, call, SLOT(terminate()));
+ }
+ else if (state == QXmppCall::ConnectingState &&
+ call->direction() == QXmppCall::IncomingDirection)
+ {
+ // accept incoming call
+ QXmppJingleIq iq;
+ iq.setTo(call->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionAccept);
+ iq.setResponder(m_stream->configuration().jid());
+ iq.setSid(call->m_sid);
+ iq.content().setCreator("initiator");
+ iq.content().setName(call->m_contentName);
+
+ // description
+ iq.content().setDescriptionMedia("audio");
+ foreach (const QXmppJinglePayloadType &payload, call->m_commonPayloadTypes)
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(call->m_socket->localUser());
+ iq.content().setTransportPassword(call->m_socket->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, call->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ sendRequest(call, iq);
+
+ // perform ICE negotiation
+ call->connectToHost();
+ }
+}
+
+/// Determine common payload types for a call.
+///
+
+bool QXmppCallManager::checkPayloadTypes(QXmppCall *call, const QList<QXmppJinglePayloadType> &remotePayloadTypes)
+{
+ foreach (const QXmppJinglePayloadType &payload, localPayloadTypes())
+ {
+ int payloadIndex = remotePayloadTypes.indexOf(payload);
+ if (payloadIndex >= 0)
+ call->m_commonPayloadTypes << remotePayloadTypes[payloadIndex];
+ }
+ if (call->m_commonPayloadTypes.isEmpty())
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("Remote party %1 did not provide any known payload types for call %2").arg(call->jid(), call->sid()));
+
+ // terminate call
+ QXmppJingleIq iq;
+ iq.setTo(call->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionTerminate);
+ iq.setSid(call->sid());
+ iq.reason().setType(QXmppJingleIq::Reason::FailedApplication);
+ sendRequest(call, iq);
+ return false;
+ } else {
+ call->setPayloadType(call->m_commonPayloadTypes.first());
+ return true;
+ }
+}
+
+QXmppCall *QXmppCallManager::findCall(const QString &sid) const
+{
+ foreach (QXmppCall *call, m_calls)
+ if (call->m_sid == sid)
+ return call;
+ return 0;
+}
+
+QXmppCall *QXmppCallManager::findCall(const QString &sid, QXmppCall::Direction direction) const
+{
+ foreach (QXmppCall *call, m_calls)
+ if (call->m_sid == sid && call->m_direction == direction)
+ return call;
+ return 0;
+}
+
+/// Returns the list of locally supported payload types.
+///
+
+QList<QXmppJinglePayloadType> QXmppCallManager::localPayloadTypes() const
+{
+ QList<QXmppJinglePayloadType> payloads;
+ QXmppJinglePayloadType payload;
+
+#ifdef QXMPP_USE_SPEEX
+ payload.setId(96);
+ payload.setChannels(1);
+ payload.setName("SPEEX");
+ payload.setClockrate(16000);
+ payloads << payload;
+
+ payload.setId(97);
+ payload.setChannels(1);
+ payload.setName("SPEEX");
+ payload.setClockrate(8000);
+ payloads << payload;
+#endif
+
+ payload.setId(G711u);
+ payload.setChannels(1);
+ payload.setName("PCMU");
+ payload.setClockrate(8000);
+ payloads << payload;
+
+ payload.setId(G711a);
+ payload.setChannels(1);
+ payload.setName("PCMA");
+ payload.setClockrate(8000);
+ payloads << payload;
+
+ return payloads;
+}
+
+/// Handles acknowledgements
+///
+
+void QXmppCallManager::iqReceived(const QXmppIq &ack)
+{
+ if (ack.type() != QXmppIq::Result)
+ return;
+
+ // find request
+ bool found = false;
+ QXmppCall *call;
+ QXmppJingleIq request;
+ foreach (call, m_calls)
+ {
+ for (int i = 0; i < call->m_requests.size(); i++)
+ {
+ if (ack.id() == call->m_requests[i].id())
+ {
+ request = call->m_requests.takeAt(i);
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ break;
+ }
+ if (!found)
+ return;
+
+ // process acknowledgement
+ emit logMessage(QXmppLogger::DebugMessage, QString("Received ACK for packet %1").arg(ack.id()));
+ if (request.action() == QXmppJingleIq::SessionTerminate)
+ {
+ // terminate
+ call->terminate();
+ }
+}
+
+/// Handle Jingle IQs.
+///
+
+void QXmppCallManager::jingleIqReceived(const QXmppJingleIq &iq)
+{
+ if (iq.type() != QXmppIq::Set)
+ return;
+
+ if (iq.action() == QXmppJingleIq::SessionInitiate)
+ {
+ // build call
+ QXmppCall *call = new QXmppCall(iq.from(), QXmppCall::IncomingDirection, this);
+ call->m_sid = iq.sid();
+ call->m_contentName = iq.content().name();
+ call->setRemoteUser(iq.content().transportUser());
+ call->setRemotePassword(iq.content().transportPassword());
+ call->addRemoteCandidates(iq.content().transportCandidates());
+
+ // send ack
+ sendAck(iq);
+
+ // determine common payload types
+ if (!checkPayloadTypes(call, iq.content().payloadTypes()))
+ {
+ delete call;
+ return;
+ }
+
+ // register call
+ m_calls.append(call);
+ connect(call, SIGNAL(stateChanged(QXmppCall::State)),
+ this, SLOT(callStateChanged(QXmppCall::State)));
+ connect(call, SIGNAL(logMessage(QXmppLogger::MessageType, QString)),
+ this, SIGNAL(logMessage(QXmppLogger::MessageType, QString)));
+
+ // send ringing indication
+ QXmppJingleIq ringing;
+ ringing.setTo(call->jid());
+ ringing.setType(QXmppIq::Set);
+ ringing.setAction(QXmppJingleIq::SessionInfo);
+ ringing.setSid(call->sid());
+ ringing.setRinging(true);
+ sendRequest(call, ringing);
+
+ // notify user
+ emit callReceived(call);
+
+ } else if (iq.action() == QXmppJingleIq::SessionAccept) {
+ QXmppCall *call = findCall(iq.sid(), QXmppCall::OutgoingDirection);
+ if (!call)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("Remote party %1 accepted unknown call %2").arg(iq.from(), iq.sid()));
+ return;
+ }
+
+ // send ack
+ sendAck(iq);
+
+ // determine common payload types
+ if (!checkPayloadTypes(call, iq.content().payloadTypes()))
+ {
+ delete call;
+ return;
+ }
+
+ // perform ICE negotiation
+ if (!iq.content().transportCandidates().isEmpty())
+ {
+ call->setRemoteUser(iq.content().transportUser());
+ call->setRemotePassword(iq.content().transportPassword());
+ call->addRemoteCandidates(iq.content().transportCandidates());
+ }
+ call->connectToHost();
+
+ } else if (iq.action() == QXmppJingleIq::SessionInfo) {
+
+ QXmppCall *call = findCall(iq.sid());
+ if (!call)
+ return;
+
+ // notify user
+ QTimer::singleShot(0, call, SIGNAL(ringing()));
+
+ } else if (iq.action() == QXmppJingleIq::SessionTerminate) {
+
+ QXmppCall *call = findCall(iq.sid());
+ if (!call)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("Remote party %1 terminated unknown call %2").arg(iq.from(), iq.sid()));
+ return;
+ }
+
+ emit logMessage(QXmppLogger::InformationMessage,
+ QString("Remote party %1 terminated call %2").arg(iq.from(), iq.sid()));
+
+ // send ack
+ sendAck(iq);
+
+ // terminate
+ call->terminate();
+
+ } else if (iq.action() == QXmppJingleIq::TransportInfo) {
+ QXmppCall *call = findCall(iq.sid());
+ if (!call)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("Remote party %1 sent transports for unknown call %2").arg(iq.from(), iq.sid()));
+ return;
+ }
+
+ // send ack
+ sendAck(iq);
+
+ // perform ICE negotiation
+ call->setRemoteUser(iq.content().transportUser());
+ call->setRemotePassword(iq.content().transportPassword());
+ call->addRemoteCandidates(iq.content().transportCandidates());
+ call->connectToHost();
+ }
+}
+
+/// Sends an acknowledgement for a Jingle IQ.
+///
+
+bool QXmppCallManager::sendAck(const QXmppJingleIq &iq)
+{
+ QXmppIq ack;
+ ack.setId(iq.id());
+ ack.setTo(iq.from());
+ ack.setType(QXmppIq::Result);
+ return m_stream->sendPacket(ack);
+}
+
+/// Sends a Jingle IQ and adds it to outstanding requests.
+///
+
+bool QXmppCallManager::sendRequest(QXmppCall *call, const QXmppJingleIq &iq)
+{
+ call->m_requests << iq;
+ return m_stream->sendPacket(iq);
+}
+
diff --git a/source/QXmppCallManager.h b/source/QXmppCallManager.h
new file mode 100644
index 00000000..a16e3717
--- /dev/null
+++ b/source/QXmppCallManager.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2010 Bolloré telecom
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/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_H
+#define QXMPPCALLMANAGER_H
+
+#include <QObject>
+#include <QIODevice>
+
+#include "QXmppJingleIq.h"
+#include "QXmppLogger.h"
+
+class QXmppCodec;
+class QXmppJingleCandidate;
+class QXmppJingleIq;
+class QXmppJinglePayloadType;
+class QXmppStream;
+class QXmppStunSocket;
+
+/// The QXmppCall class represents a Voice-Over-IP call to a remote party.
+///
+/// It acts as a QIODevice so that you can read / write audio samples, for
+/// instance using a QAudioInput and/or QAudioOutput.
+///
+/// \note: THIS API IS NOT FINALIZED YET
+
+class QXmppCall : public QIODevice
+{
+ Q_OBJECT
+
+public:
+ enum Direction
+ {
+ IncomingDirection, ///< The call is incoming.
+ OutgoingDirection, ///< The call is outgoing.
+ };
+
+ enum State
+ {
+ OfferState = 0,
+ ConnectingState = 1,
+ ActiveState = 2,
+ DisconnectingState = 3,
+ FinishedState = 4,
+ };
+
+ QXmppCall::Direction direction() const;
+ QString jid() const;
+ QString sid() const;
+ QXmppCall::State state() const;
+
+ QXmppJinglePayloadType payloadType() const;
+ qint64 bytesAvailable() const;
+ bool isSequential() const;
+
+signals:
+ /// 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();
+
+ /// This signal is emitted when the remote party is ringing.
+ void ringing();
+
+ /// This signal is emitted to send logging messages.
+ void logMessage(QXmppLogger::MessageType type, const QString &msg);
+
+ /// This signal is emitted when the call state.
+ void stateChanged(QXmppCall::State state);
+
+ /// This signal is emitted when the read/write mode changes.
+ ///
+ /// You can connect to this signal to know when you can bind a
+ /// QAudioInput and/or QAudioOutput to the call.
+ void openModeChanged(QIODevice::OpenMode);
+
+public slots:
+ void accept();
+ void hangup();
+
+private slots:
+ void datagramReceived(const QByteArray &datagram);
+ void emitSignals();
+ void updateOpenMode();
+ void terminate();
+ void terminated();
+
+protected:
+ qint64 readData(char * data, qint64 maxSize);
+ qint64 writeData(const char * data, qint64 maxSize);
+
+private:
+ QXmppCall(const QString &jid, QXmppCall::Direction direction, QObject *parent);
+ void connectToHost();
+ QList<QXmppJingleCandidate> localCandidates() const;
+ void setPayloadType(const QXmppJinglePayloadType &type);
+ void addRemoteCandidates(const QList<QXmppJingleCandidate> &candidates);
+ void setRemoteUser(const QString &user);
+ void setRemotePassword(const QString &password);
+ void setState(QXmppCall::State state);
+
+ Direction m_direction;
+ QString m_jid;
+ QString m_sid;
+ State m_state;
+ QString m_contentName;
+ QXmppJinglePayloadType m_payloadType;
+
+ QList<QXmppJingleIq> m_requests;
+ QList<QXmppJinglePayloadType> m_commonPayloadTypes;
+
+ // ICE-UDP
+ QXmppStunSocket *m_socket;
+ QXmppStunSocket *m_rtcpSocket;
+
+ // signals
+ bool m_signalsEmitted;
+ qint64 m_writtenSinceLastEmit;
+
+ // RTP
+ bool m_buffered;
+ QXmppCodec *m_codec;
+
+ QByteArray m_incomingBuffer;
+ bool m_incomingBuffering;
+ int m_incomingMinimum;
+ quint16 m_incomingSequence;
+ quint32 m_incomingStamp;
+
+ quint16 m_outgoingChunk;
+ QByteArray m_outgoingBuffer;
+ bool m_outgoingMarker;
+ quint16 m_outgoingSequence;
+ quint32 m_outgoingStamp;
+
+ friend class QXmppCallManager;
+};
+
+/// \brief The QXmppCallManager class provides support for making and
+/// receiving voice calls.
+///
+/// Session initiation is performed as described by XEP-0166: Jingle,
+/// XEP-0167: Jingle RTP Sessions and XEP-0176: Jingle ICE-UDP Transport
+/// Method.
+///
+/// The data stream is connected using Interactive Connectivity Establishment
+/// (RFC 5245) and data is transfered using Real Time Protocol (RFC 3550)
+/// packets.
+///
+/// \note: THIS API IS NOT FINALIZED YET
+
+class QXmppCallManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ QXmppCallManager(QXmppStream *stream, QObject *parent = 0);
+ QXmppCall *call(const QString &jid);
+
+signals:
+ /// This signal is emitted when a new incoming call is received.
+ ///
+ /// To accept the call, invoke the call's QXmppCall::accept() method.
+ /// To refuse the call, invoke the call's QXmppCall::abort() method.
+ void callReceived(QXmppCall *call);
+
+ /// This signal is emitted to send logging messages.
+ void logMessage(QXmppLogger::MessageType type, const QString &msg);
+
+private slots:
+ void callDestroyed(QObject *object);
+ void callStateChanged(QXmppCall::State state);
+ void iqReceived(const QXmppIq &iq);
+ void jingleIqReceived(const QXmppJingleIq &iq);
+
+private:
+ bool checkPayloadTypes(QXmppCall *call, const QList<QXmppJinglePayloadType> &remotePayloadTypes);
+ QXmppCall *findCall(const QString &sid) const;
+ QXmppCall *findCall(const QString &sid, QXmppCall::Direction direction) const;
+ QList<QXmppJinglePayloadType> localPayloadTypes() const;
+ bool sendAck(const QXmppJingleIq &iq);
+ bool sendRequest(QXmppCall *call, const QXmppJingleIq &iq);
+
+ QList<QXmppCall*> m_calls;
+
+ // reference to xmpp stream (no ownership)
+ QXmppStream* m_stream;
+};
+
+#endif
diff --git a/source/QXmppClient.cpp b/source/QXmppClient.cpp
index f15bcc7d..ab725ad8 100644
--- a/source/QXmppClient.cpp
+++ b/source/QXmppClient.cpp
@@ -29,6 +29,7 @@
#include "QXmppMessage.h"
#include "QXmppArchiveManager.h"
+#include "QXmppCallManager.h"
#include "QXmppInvokable.h"
#include "QXmppMucManager.h"
#include "QXmppReconnectionManager.h"
@@ -101,6 +102,7 @@ QXmppClient::QXmppClient(QObject *parent)
// create managers
m_roster = new QXmppRoster(m_stream, this);
m_archiveManager = new QXmppArchiveManager(m_stream, this);
+ m_callManager = new QXmppCallManager(m_stream, this);
m_mucManager = new QXmppMucManager(m_stream, this);
m_transferManager = new QXmppTransferManager(m_stream, this);
m_vCardManager = new QXmppVCardManager(m_stream, this);
@@ -546,6 +548,15 @@ QXmppArchiveManager& QXmppClient::archiveManager()
return *m_archiveManager;
}
+/// Returns the reference to QXmppCallManager, implementation of XEP-0166.
+/// http://xmpp.org/extensions/xep-0166.html
+///
+
+QXmppCallManager& QXmppClient::callManager()
+{
+ return *m_callManager;
+}
+
/// Returns the reference to QXmppMucManager, implementation of XEP-0045.
/// http://xmpp.org/extensions/xep-045.html
///
diff --git a/source/QXmppClient.h b/source/QXmppClient.h
index 55bd5297..de1683ae 100644
--- a/source/QXmppClient.h
+++ b/source/QXmppClient.h
@@ -50,6 +50,7 @@ class QXmppArchiveManager;
class QXmppDiscoveryIq;
class QXmppVCardManager;
class QXmppTransferManager;
+class QXmppCallManager;
/// \brief The QXmppClient class is the main class for using QXmpp.
///
@@ -104,6 +105,7 @@ public:
bool isConnected() const;
QXmppArchiveManager& archiveManager();
+ QXmppCallManager& callManager();
QXmppMucManager& mucManager();
QXmppTransferManager& transferManager();
@@ -227,6 +229,7 @@ private:
///< TCP socket and XMPP protocol
QXmppPresence m_clientPresence; ///< Stores the current presence of the connected client
QXmppArchiveManager *m_archiveManager; ///< Pointer to the archive manager
+ QXmppCallManager *m_callManager; ///< Pointer to the call manager
QXmppMucManager* m_mucManager; ///< Pointer to the multi-user chat manager
QXmppReconnectionManager* m_reconnectionManager; ///< Pointer to the reconnection manager
QXmppRoster *m_roster; ///< Pointer to the roster manager
diff --git a/source/source.pro b/source/source.pro
index 287bc918..f00d91c9 100644
--- a/source/source.pro
+++ b/source/source.pro
@@ -22,6 +22,7 @@ HEADERS += QXmppUtils.h \
QXmppArchiveManager.h \
QXmppBind.h \
QXmppByteStreamIq.h \
+ QXmppCallManager.h \
QXmppClient.h \
QXmppCodec.h \
QXmppConfiguration.h \
@@ -64,6 +65,7 @@ SOURCES += QXmppUtils.cpp \
QXmppArchiveManager.cpp \
QXmppBind.cpp \
QXmppByteStreamIq.cpp \
+ QXmppCallManager.cpp \
QXmppClient.cpp \
QXmppCodec.cpp \
QXmppConfiguration.cpp \