aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2011-04-15 15:40:09 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2011-04-15 15:40:09 +0000
commite1c4a3b40604db10037b59b482ff219609354165 (patch)
treef71d640613754cbbd5a87484ab49e416c0e861a0 /src
parent807a04ef3b864ac8f0e0f170176db7dd629f742a (diff)
downloadqxmpp-e1c4a3b40604db10037b59b482ff219609354165.tar.gz
overhaul RTP / jingle stack to allow video support
Diffstat (limited to 'src')
-rw-r--r--src/QXmppCallManager.cpp859
-rw-r--r--src/QXmppCallManager.h18
-rw-r--r--src/QXmppRtpChannel.cpp569
-rw-r--r--src/QXmppRtpChannel.h126
-rw-r--r--src/QXmppStun.cpp75
-rw-r--r--src/QXmppStun.h9
6 files changed, 1131 insertions, 525 deletions
diff --git a/src/QXmppCallManager.cpp b/src/QXmppCallManager.cpp
index 6e4fdebd..36b219d7 100644
--- a/src/QXmppCallManager.cpp
+++ b/src/QXmppCallManager.cpp
@@ -35,29 +35,49 @@
static int typeId = qRegisterMetaType<QXmppCall::State>();
-const int RTP_COMPONENT = 1;
-const int RTCP_COMPONENT = 2;
+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);
+ 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);
QXmppCall::Direction direction;
QString jid;
+ QString ownJid;
+ QXmppCallManager *manager;
+ QList<QXmppJingleIq> requests;
QString sid;
QXmppCall::State state;
- QString contentCreator;
- QString contentName;
-
- QList<QXmppJingleIq> requests;
- // ICE-UDP
- QXmppIceConnection *connection;
-
- // RTP
- QXmppRtpChannel *audioChannel;
+ // Media streams
+ QList<Stream*> streams;
+ QIODevice::OpenMode audioMode;
+ QIODevice::OpenMode videoMode;
private:
QXmppCall *q;
@@ -67,11 +87,8 @@ class QXmppCallManagerPrivate
{
public:
QXmppCallManagerPrivate(QXmppCallManager *qq);
- bool checkPayloadTypes(QXmppCall *call, const QList<QXmppJinglePayloadType> &remotePayloadTypes);
QXmppCall *findCall(const QString &sid) const;
QXmppCall *findCall(const QString &sid, QXmppCall::Direction direction) const;
- bool sendAck(const QXmppJingleIq &iq);
- bool sendRequest(QXmppCall *call, const QXmppJingleIq &iq);
QList<QXmppCall*> calls;
QHostAddress stunHost;
@@ -87,10 +104,316 @@ private:
QXmppCallPrivate::QXmppCallPrivate(QXmppCall *qq)
: state(QXmppCall::OfferState),
+ audioMode(QIODevice::NotOpen),
+ videoMode(QIODevice::NotOpen),
q(qq)
{
}
+QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByMedia(const QString &media)
+{
+ foreach (Stream *stream, streams)
+ if (stream->media == media)
+ return stream;
+ return 0;
+}
+
+QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByName(const QString &name)
+{
+ foreach (Stream *stream, streams)
+ if (stream->name == name)
+ return stream;
+ return 0;
+}
+
+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->terminate();
+ 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());
+ foreach (const QXmppJingleCandidate &candidate, content.transportCandidates())
+ stream->connection->addRemoteCandidate(candidate);
+
+ // perform ICE negotiation
+ if (!content.transportCandidates().isEmpty())
+ stream->connection->connectToHost();
+ return true;
+}
+
+void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq)
+{
+ 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(iq.content().name());
+ if (!stream ||
+ !handleDescription(stream, iq.content()) ||
+ !handleTransport(stream, iq.content())) {
+
+ // terminate call
+ QXmppJingleIq iq;
+ iq.setTo(q->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionTerminate);
+ iq.setSid(q->sid());
+ iq.reason().setType(QXmppJingleIq::Reason::FailedApplication);
+ sendRequest(iq);
+
+ q->terminate();
+ return;
+ }
+
+ // check for call establishment
+ setState(QXmppCall::ConnectingState);
+ q->updateOpenMode();
+
+ } 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->terminate();
+
+ } else if (iq.action() == QXmppJingleIq::ContentAccept) {
+
+ // send ack
+ sendAck(iq);
+
+ // check content description and transport
+ QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name());
+ if (!stream ||
+ !handleDescription(stream, iq.content()) ||
+ !handleTransport(stream, iq.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(iq.content().name());
+ if (stream)
+ return;
+
+ // create media stream
+ stream = createStream(iq.content().descriptionMedia());
+ if (!stream)
+ return;
+ stream->creator = iq.content().creator();
+ stream->name = iq.content().name();
+
+ // check content description
+ if (!handleDescription(stream, iq.content()) ||
+ !handleTransport(stream, iq.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.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+
+ // description
+ iq.content().setDescriptionMedia(stream->media);
+ foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes())
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ sendRequest(iq);
+
+ } else if (iq.action() == QXmppJingleIq::TransportInfo) {
+
+ // send ack
+ sendAck(iq);
+
+ // check content transport
+ QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name());
+ if (!stream ||
+ !handleTransport(stream, iq.content())) {
+ // FIXME: what action?
+ return;
+ }
+
+ }
+}
+
+QXmppCallPrivate::Stream *QXmppCallPrivate::createStream(const QString &media)
+{
+ Q_ASSERT(manager);
+
+ Stream *stream = new Stream;
+ stream->media = media;
+
+ // RTP channel
+ QObject *channelObject = 0;
+ if (media == AUDIO_MEDIA) {
+ QXmppRtpAudioChannel *audioChannel = new QXmppRtpAudioChannel(q);
+ stream->channel = audioChannel;
+ channelObject = audioChannel;
+ } else if (media == VIDEO_MEDIA) {
+ QXmppRtpVideoChannel *videoChannel = new QXmppRtpVideoChannel(q);
+ stream->channel = videoChannel;
+ channelObject = videoChannel;
+ } else {
+ q->warning(QString("Unsupported media type %1").arg(media));
+ delete stream;
+ return 0;
+ }
+
+ // 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
+ bool check = QObject::connect(stream->connection, SIGNAL(localCandidatesChanged()),
+ q, SLOT(localCandidatesChanged()));
+ Q_ASSERT(check);
+
+ check = QObject::connect(stream->connection, SIGNAL(connected()),
+ q, SLOT(updateOpenMode()));
+ Q_ASSERT(check);
+
+ check = QObject::connect(stream->connection, SIGNAL(disconnected()),
+ q, SLOT(hangup()));
+ Q_ASSERT(check);
+
+ if (channelObject) {
+ QXmppIceComponent *rtpComponent = stream->connection->component(RTP_COMPONENT);
+
+ check = QObject::connect(rtpComponent, SIGNAL(datagramReceived(QByteArray)),
+ channelObject, SLOT(datagramReceived(QByteArray)));
+ Q_ASSERT(check);
+
+ check = QObject::connect(channelObject, SIGNAL(sendDatagram(QByteArray)),
+ rtpComponent, SLOT(sendDatagram(QByteArray)));
+ Q_ASSERT(check);
+ }
+ return stream;
+}
+
+/// 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()
+{
+ QXmppJingleIq iq;
+ iq.setTo(jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionInitiate);
+ iq.setInitiator(ownJid);
+ iq.setSid(sid);
+
+ // create audio stream
+ QXmppCallPrivate::Stream *stream = findStreamByMedia(AUDIO_MEDIA);
+ Q_ASSERT(stream);
+ iq.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+ iq.content().setSenders("both");
+
+ // description
+ iq.content().setDescriptionMedia(stream->media);
+ foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes())
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ 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)
@@ -106,47 +429,20 @@ QXmppCall::QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCa
d = new QXmppCallPrivate(this);
d->direction = direction;
d->jid = jid;
- d->contentCreator = QLatin1String("initiator");
- d->contentName = QLatin1String("voice");
-
- // ICE connection
- bool iceControlling = (d->direction == OutgoingDirection);
- d->connection = new QXmppIceConnection(iceControlling, this);
- d->connection->setStunServer(parent->d->stunHost, parent->d->stunPort);
- d->connection->setTurnServer(parent->d->turnHost, parent->d->turnPort);
- d->connection->setTurnUser(parent->d->turnUser);
- d->connection->setTurnPassword(parent->d->turnPassword);
- d->connection->addComponent(RTP_COMPONENT);
- d->connection->addComponent(RTCP_COMPONENT);
- d->connection->bind(QXmppIceComponent::discoverAddresses());
-
- bool check = connect(d->connection, SIGNAL(localCandidatesChanged()),
- this, SIGNAL(localCandidatesChanged()));
- Q_ASSERT(check);
-
- check = connect(d->connection, SIGNAL(connected()),
- this, SLOT(updateOpenMode()));
- Q_ASSERT(check);
+ d->ownJid = parent->client()->configuration().jid();
+ d->manager = parent;
- check = connect(d->connection, SIGNAL(disconnected()),
- this, SLOT(hangup()));
- Q_ASSERT(check);
-
- // RTP channel
- d->audioChannel = new QXmppRtpChannel(this);
- QXmppIceComponent *rtpComponent = d->connection->component(RTP_COMPONENT);
-
- check = connect(rtpComponent, SIGNAL(datagramReceived(QByteArray)),
- d->audioChannel, SLOT(datagramReceived(QByteArray)));
- Q_ASSERT(check);
-
- check = connect(d->audioChannel, SIGNAL(sendDatagram(QByteArray)),
- rtpComponent, SLOT(sendDatagram(QByteArray)));
- Q_ASSERT(check);
+ // create audio stream
+ QXmppCallPrivate::Stream *stream = d->createStream(AUDIO_MEDIA);
+ stream->creator = QLatin1String("initiator");
+ stream->name = QLatin1String("voice");
+ d->streams << stream;
}
QXmppCall::~QXmppCall()
{
+ foreach (QXmppCallPrivate::Stream *stream, d->streams)
+ delete stream;
delete d;
}
@@ -156,7 +452,37 @@ QXmppCall::~QXmppCall()
void QXmppCall::accept()
{
if (d->direction == IncomingDirection && d->state == OfferState)
+ {
+ 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.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+
+ // description
+ iq.content().setDescriptionMedia(stream->media);
+ foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes())
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ d->sendRequest(iq);
+
+ // check for call establishment
d->setState(QXmppCall::ConnectingState);
+ updateOpenMode();
+ }
}
/// Returns the RTP channel for the audio data.
@@ -165,9 +491,21 @@ void QXmppCall::accept()
/// instance using a QAudioOutput and a QAudioInput.
///
-QXmppRtpChannel *QXmppCall::audioChannel() const
+QXmppRtpAudioChannel *QXmppCall::audioChannel() const
{
- return d->audioChannel;
+ QXmppCallPrivate::Stream *stream = d->findStreamByMedia(AUDIO_MEDIA);
+ Q_ASSERT(stream);
+ return (QXmppRtpAudioChannel*)stream->channel;
+}
+
+/// Returns the RTP channel for the video data.
+///
+
+QXmppRtpVideoChannel *QXmppCall::videoChannel() const
+{
+ QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
+ Q_ASSERT(stream);
+ return (QXmppRtpVideoChannel*)stream->channel;
}
void QXmppCall::terminate()
@@ -177,8 +515,10 @@ void QXmppCall::terminate()
d->state = QXmppCall::FinishedState;
- d->audioChannel->close();
- d->connection->close();
+ foreach (QXmppCallPrivate::Stream *stream, d->streams) {
+ stream->channel->close();
+ stream->connection->close();
+ }
// emit signals later
QTimer::singleShot(0, this, SLOT(terminated()));
@@ -207,9 +547,59 @@ void QXmppCall::hangup()
d->state == QXmppCall::FinishedState)
return;
+ // hangup up call
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionTerminate);
+ iq.setSid(d->sid);
+ d->sendRequest(iq);
+
+ // close streams
+ foreach (QXmppCallPrivate::Stream *stream, d->streams) {
+ stream->channel->close();
+ stream->connection->close();
+ }
+
+ // schedule forceful termination in 5s
+ QTimer::singleShot(5000, this, SLOT(terminate()));
d->setState(QXmppCall::DisconnectingState);
- d->audioChannel->close();
- d->connection->close();
+}
+
+/// 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());
+ QXmppCallPrivate::Stream *stream = 0;
+ foreach (QXmppCallPrivate::Stream *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.setInitiator(d->ownJid);
+ iq.setSid(d->sid);
+
+ iq.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ d->sendRequest(iq);
}
/// Returns the remote party's JID.
@@ -222,12 +612,28 @@ QString QXmppCall::jid() const
void QXmppCall::updateOpenMode()
{
- // determine mode
- if (d->audioChannel->isOpen() && d->connection->isConnected() && d->state != ActiveState)
+ // determine audio mode
+ QXmppCallPrivate::Stream *stream = d->findStreamByMedia(AUDIO_MEDIA);
+ if (stream &&
+ (stream->channel->openMode() & QIODevice::ReadWrite) &&
+ stream->connection->isConnected() &&
+ d->state == ConnectingState)
{
d->setState(ActiveState);
emit connected();
}
+
+ // determine video mode
+ stream = d->findStreamByMedia(VIDEO_MEDIA);
+ QIODevice::OpenMode mode = QIODevice::NotOpen;
+ if (stream) {
+ if (stream->connection->isConnected())
+ mode = stream->channel->openMode() & QIODevice::ReadWrite;
+ }
+ if (mode != d->videoMode) {
+ d->videoMode = mode;
+ emit videoModeChanged(mode);
+ }
}
/// Returns the call's session identifier.
@@ -247,6 +653,43 @@ QXmppCall::State QXmppCall::state() const
return d->state;
}
+void QXmppCall::startVideo()
+{
+ QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
+ if (stream)
+ return;
+
+ // create video stream
+ stream = d->createStream(VIDEO_MEDIA);
+ stream->creator = QLatin1String("initiator");
+ stream->name = QLatin1String("webcam");
+ d->streams << stream;
+
+ // build request
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentAdd);
+ iq.setInitiator(d->ownJid);
+ iq.setSid(d->sid);
+ iq.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+ iq.content().setSenders("both");
+
+ // description
+ iq.content().setDescriptionMedia(stream->media);
+ foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes())
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ d->sendRequest(iq);
+}
+
QXmppCallManagerPrivate::QXmppCallManagerPrivate(QXmppCallManager *qq)
: stunPort(0),
turnPort(0),
@@ -270,27 +713,6 @@ QXmppCall *QXmppCallManagerPrivate::findCall(const QString &sid, QXmppCall::Dire
return 0;
}
-/// Sends an acknowledgement for a Jingle IQ.
-///
-
-bool QXmppCallManagerPrivate::sendAck(const QXmppJingleIq &iq)
-{
- QXmppIq ack;
- ack.setId(iq.id());
- ack.setTo(iq.from());
- ack.setType(QXmppIq::Result);
- return q->client()->sendPacket(ack);
-}
-
-/// Sends a Jingle IQ and adds it to outstanding requests.
-///
-
-bool QXmppCallManagerPrivate::sendRequest(QXmppCall *call, const QXmppJingleIq &iq)
-{
- call->d->requests << iq;
- return q->client()->sendPacket(iq);
-}
-
/// Constructs a QXmppCallManager object to handle incoming and outgoing
/// Voice-Over-IP calls.
///
@@ -313,6 +735,7 @@ QStringList QXmppCallManager::discoveryFeatures() const
<< ns_jingle // XEP-0166 : Jingle
<< ns_jingle_rtp // XEP-0167 : Jingle RTP Sessions
<< ns_jingle_rtp_audio
+ << ns_jingle_rtp_video
<< ns_jingle_ice_udp; // XEP-0176 : Jingle ICE-UDP Transport Method
}
@@ -349,6 +772,11 @@ void QXmppCallManager::setClient(QXmppClient *client)
QXmppCall *QXmppCallManager::call(const QString &jid)
{
+ if (jid == client()->configuration().jid()) {
+ warning("Refusing to call self");
+ return 0;
+ }
+
QXmppCall *call = new QXmppCall(jid, QXmppCall::OutgoingDirection, this);
call->d->sid = generateStanzaHash();
@@ -356,34 +784,8 @@ QXmppCall *QXmppCallManager::call(const QString &jid)
d->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(localCandidatesChanged()),
- this, SLOT(localCandidatesChanged()));
-
- QXmppJingleIq iq;
- iq.setTo(jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::SessionInitiate);
- iq.setInitiator(client()->configuration().jid());
- iq.setSid(call->sid());
- iq.content().setCreator(call->d->contentCreator);
- iq.content().setName(call->d->contentName);
- iq.content().setSenders("both");
-
- // description
- iq.content().setDescriptionMedia("audio");
- foreach (const QXmppJinglePayloadType &payload, call->d->audioChannel->localPayloadTypes())
- iq.content().addPayloadType(payload);
-
- // transport
- iq.content().setTransportUser(call->d->connection->localUser());
- iq.content().setTransportPassword(call->d->connection->localPassword());
- foreach (const QXmppJingleCandidate &candidate, call->d->connection->localCandidates())
- iq.content().addTransportCandidate(candidate);
-
- d->sendRequest(call, iq);
+ call->d->sendInvite();
return call;
}
@@ -392,86 +794,6 @@ void QXmppCallManager::callDestroyed(QObject *object)
d->calls.removeAll(static_cast<QXmppCall*>(object));
}
-void QXmppCallManager::callStateChanged(QXmppCall::State state)
-{
- QXmppCall *call = qobject_cast<QXmppCall*>(sender());
- if (!call || !d->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->sid());
- d->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(client()->configuration().jid());
- iq.setSid(call->sid());
- iq.content().setCreator(call->d->contentCreator);
- iq.content().setName(call->d->contentName);
-
- // description
- iq.content().setDescriptionMedia("audio");
- foreach (const QXmppJinglePayloadType &payload, call->d->audioChannel->localPayloadTypes())
- iq.content().addPayloadType(payload);
-
- // transport
- iq.content().setTransportUser(call->d->connection->localUser());
- iq.content().setTransportPassword(call->d->connection->localPassword());
- foreach (const QXmppJingleCandidate &candidate, call->d->connection->localCandidates())
- iq.content().addTransportCandidate(candidate);
-
- d->sendRequest(call, iq);
-
- // perform ICE negotiation
- call->d->connection->connectToHost();
- }
-}
-
-/// Determine common payload types for a call.
-///
-
-bool QXmppCallManagerPrivate::checkPayloadTypes(QXmppCall *call, const QList<QXmppJinglePayloadType> &remotePayloadTypes)
-{
- call->d->audioChannel->setRemotePayloadTypes(remotePayloadTypes);
- if (!call->d->audioChannel->isOpen()) {
- q->warning(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->updateOpenMode();
- return true;
- }
-}
-
/// Handles acknowledgements
///
@@ -481,33 +803,8 @@ void QXmppCallManager::iqReceived(const QXmppIq &ack)
return;
// find request
- bool found = false;
- QXmppCall *call = 0;
- QXmppJingleIq request;
- foreach (call, d->calls)
- {
- for (int i = 0; i < call->d->requests.size(); i++)
- {
- if (ack.id() == call->d->requests[i].id())
- {
- request = call->d->requests.takeAt(i);
- found = true;
- break;
- }
- }
- if (found)
- break;
- }
- if (!found)
- return;
-
- // process acknowledgement
- debug(QString("Received ACK for packet %1").arg(ack.id()));
- if (request.action() == QXmppJingleIq::SessionTerminate)
- {
- // terminate
- call->terminate();
- }
+ foreach (QXmppCall *call, d->calls)
+ call->d->handleAck(ack);
}
/// Handle Jingle IQs.
@@ -523,19 +820,29 @@ void QXmppCallManager::jingleIqReceived(const QXmppJingleIq &iq)
// build call
QXmppCall *call = new QXmppCall(iq.from(), QXmppCall::IncomingDirection, this);
call->d->sid = iq.sid();
- call->d->contentCreator = iq.content().creator();
- call->d->contentName = iq.content().name();
- call->d->connection->setRemoteUser(iq.content().transportUser());
- call->d->connection->setRemotePassword(iq.content().transportPassword());
- foreach (const QXmppJingleCandidate &candidate, iq.content().transportCandidates())
- call->d->connection->addRemoteCandidate(candidate);
+
+ QXmppCallPrivate::Stream *stream = call->d->findStreamByMedia(iq.content().descriptionMedia());
+ if (!stream)
+ return;
+ stream->creator = iq.content().creator();
+ stream->name = iq.content().name();
// send ack
- d->sendAck(iq);
+ call->d->sendAck(iq);
+
+ // check content description and transport
+ if (!call->d->handleDescription(stream, iq.content()) ||
+ !call->d->handleTransport(stream, iq.content())) {
+
+ // 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);
+ call->d->sendRequest(iq);
- // determine common payload types
- if (!d->checkPayloadTypes(call, iq.content().payloadTypes()))
- {
delete call;
return;
}
@@ -544,10 +851,6 @@ void QXmppCallManager::jingleIqReceived(const QXmppJingleIq &iq)
d->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(localCandidatesChanged()),
- this, SLOT(localCandidatesChanged()));
// send ringing indication
QXmppJingleIq ringing;
@@ -556,112 +859,24 @@ void QXmppCallManager::jingleIqReceived(const QXmppJingleIq &iq)
ringing.setAction(QXmppJingleIq::SessionInfo);
ringing.setSid(call->sid());
ringing.setRinging(true);
- d->sendRequest(call, ringing);
+ call->d->sendRequest(ringing);
// notify user
emit callReceived(call);
+ return;
- } else if (iq.action() == QXmppJingleIq::SessionAccept) {
- QXmppCall *call = d->findCall(iq.sid(), QXmppCall::OutgoingDirection);
- if (!call)
- {
- warning(QString("Remote party %1 accepted unknown call %2").arg(iq.from(), iq.sid()));
- return;
- }
-
- // send ack
- d->sendAck(iq);
-
- // determine common payload types
- if (!d->checkPayloadTypes(call, iq.content().payloadTypes()))
- {
- delete call;
- return;
- }
-
- // perform ICE negotiation
- if (!iq.content().transportCandidates().isEmpty())
- {
- call->d->connection->setRemoteUser(iq.content().transportUser());
- call->d->connection->setRemotePassword(iq.content().transportPassword());
- foreach (const QXmppJingleCandidate &candidate, iq.content().transportCandidates())
- call->d->connection->addRemoteCandidate(candidate);
- }
- call->d->connection->connectToHost();
-
- } else if (iq.action() == QXmppJingleIq::SessionInfo) {
-
- QXmppCall *call = d->findCall(iq.sid());
- if (!call)
- return;
-
- // notify user
- QTimer::singleShot(0, call, SIGNAL(ringing()));
-
- } else if (iq.action() == QXmppJingleIq::SessionTerminate) {
+ } else {
+ // for all other requests, require a valid call
QXmppCall *call = d->findCall(iq.sid());
- if (!call)
- {
- warning(QString("Remote party %1 terminated unknown call %2").arg(iq.from(), iq.sid()));
+ if (!call) {
+ warning(QString("Remote party %1 sent a request for an unknown call %2").arg(iq.from(), iq.sid()));
return;
}
-
- info(QString("Remote party %1 terminated call %2").arg(iq.from(), iq.sid()));
-
- // send ack
- d->sendAck(iq);
-
- // terminate
- call->terminate();
-
- } else if (iq.action() == QXmppJingleIq::TransportInfo) {
- QXmppCall *call = d->findCall(iq.sid());
- if (!call)
- {
- warning(QString("Remote party %1 sent transports for unknown call %2").arg(iq.from(), iq.sid()));
- return;
- }
-
- // send ack
- d->sendAck(iq);
-
- // perform ICE negotiation
- call->d->connection->setRemoteUser(iq.content().transportUser());
- call->d->connection->setRemotePassword(iq.content().transportPassword());
- foreach (const QXmppJingleCandidate &candidate, iq.content().transportCandidates())
- call->d->connection->addRemoteCandidate(candidate);
+ call->d->handleRequest(iq);
}
}
-/// Sends a transport-info to inform the remote party of new local candidates.
-///
-
-void QXmppCallManager::localCandidatesChanged()
-{
- QXmppCall *call = qobject_cast<QXmppCall*>(sender());
- if (!call || !d->calls.contains(call))
- return;
-
- QXmppJingleIq iq;
- iq.setTo(call->jid());
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::TransportInfo);
- iq.setInitiator(client()->configuration().jid());
- iq.setSid(call->sid());
-
- iq.content().setCreator(call->d->contentCreator);
- iq.content().setName(call->d->contentName);
-
- // transport
- iq.content().setTransportUser(call->d->connection->localUser());
- iq.content().setTransportPassword(call->d->connection->localPassword());
- foreach (const QXmppJingleCandidate &candidate, call->d->connection->localCandidates())
- iq.content().addTransportCandidate(candidate);
-
- d->sendRequest(call, iq);
-}
-
/// Sets the STUN server to use to determine server-reflexive addresses
/// and ports.
///
diff --git a/src/QXmppCallManager.h b/src/QXmppCallManager.h
index 39fedcac..4a6117c3 100644
--- a/src/QXmppCallManager.h
+++ b/src/QXmppCallManager.h
@@ -39,7 +39,8 @@ class QXmppIq;
class QXmppJingleCandidate;
class QXmppJingleIq;
class QXmppJinglePayloadType;
-class QXmppRtpChannel;
+class QXmppRtpAudioChannel;
+class QXmppRtpVideoChannel;
/// \brief The QXmppCall class represents a Voice-Over-IP call to a remote party.
///
@@ -77,7 +78,8 @@ public:
QString sid() const;
QXmppCall::State state() const;
- QXmppRtpChannel *audioChannel() const;
+ QXmppRtpAudioChannel *audioChannel() const;
+ QXmppRtpVideoChannel *videoChannel() const;
signals:
/// \brief This signal is emitted when a call is connected.
@@ -93,21 +95,22 @@ signals:
/// instead use deleteLater().
void finished();
- /// \cond
- void localCandidatesChanged();
- /// \endcond
-
/// \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 video channel changes.
+ void videoModeChanged(QIODevice::OpenMode mode);
+
public slots:
void accept();
void hangup();
+ void startVideo();
private slots:
+ void localCandidatesChanged();
void terminate();
void terminated();
void updateOpenMode();
@@ -174,14 +177,13 @@ protected:
private slots:
void callDestroyed(QObject *object);
- void callStateChanged(QXmppCall::State state);
void iqReceived(const QXmppIq &iq);
void jingleIqReceived(const QXmppJingleIq &iq);
- void localCandidatesChanged();
private:
QXmppCallManagerPrivate *d;
friend class QXmppCall;
+ friend class QXmppCallPrivate;
friend class QXmppCallManagerPrivate;
};
diff --git a/src/QXmppRtpChannel.cpp b/src/QXmppRtpChannel.cpp
index a9e85ccc..37c1847d 100644
--- a/src/QXmppRtpChannel.cpp
+++ b/src/QXmppRtpChannel.cpp
@@ -40,6 +40,61 @@
const quint8 RTP_VERSION = 0x02;
+/// Creates a new RTP channel.
+
+QXmppRtpChannel::QXmppRtpChannel()
+ : m_outgoingPayloadNumbered(false)
+{
+}
+
+/// Returns the local payload types.
+///
+
+QList<QXmppJinglePayloadType> QXmppRtpChannel::localPayloadTypes()
+{
+ m_outgoingPayloadNumbered = true;
+ return m_outgoingPayloadTypes;
+}
+
+/// Sets the remote payload types.
+///
+/// \param remotePayloadTypes
+
+void QXmppRtpChannel::setRemotePayloadTypes(const QList<QXmppJinglePayloadType> &remotePayloadTypes)
+{
+ QList<QXmppJinglePayloadType> commonOutgoingTypes;
+ QList<QXmppJinglePayloadType> commonIncomingTypes;
+
+ foreach (const QXmppJinglePayloadType &incomingType, remotePayloadTypes) {
+ // check we support this payload type
+ int outgoingIndex = m_outgoingPayloadTypes.indexOf(incomingType);
+ if (outgoingIndex < 0)
+ continue;
+ QXmppJinglePayloadType outgoingType = m_outgoingPayloadTypes[outgoingIndex];
+
+ // be kind and try to adopt the other agent's numbering
+ if (!m_outgoingPayloadNumbered && outgoingType.id() > 95) {
+ outgoingType.setId(incomingType.id());
+ }
+ commonIncomingTypes << incomingType;
+ commonOutgoingTypes << outgoingType;
+ }
+ if (commonOutgoingTypes.isEmpty()) {
+ qWarning("QXmppRtpChannel could not negociate a common codec");
+ return;
+ }
+ m_incomingPayloadTypes = commonIncomingTypes;
+ m_outgoingPayloadTypes = commonOutgoingTypes;
+ m_outgoingPayloadNumbered = true;
+
+ // call hook
+ payloadTypesChanged();
+}
+
+void QXmppRtpChannel::payloadTypesChanged()
+{
+}
+
enum CodecId {
G711u = 0,
GSM = 3,
@@ -54,36 +109,36 @@ enum CodecId {
struct ToneInfo
{
- QXmppRtpChannel::Tone tone;
+ QXmppRtpAudioChannel::Tone tone;
quint32 incomingStart;
quint32 outgoingStart;
bool finished;
};
-static QPair<int, int> toneFreqs(QXmppRtpChannel::Tone tone)
+static QPair<int, int> toneFreqs(QXmppRtpAudioChannel::Tone tone)
{
switch (tone) {
- case QXmppRtpChannel::Tone_1: return qMakePair(697, 1209);
- case QXmppRtpChannel::Tone_2: return qMakePair(697, 1336);
- case QXmppRtpChannel::Tone_3: return qMakePair(697, 1477);
- case QXmppRtpChannel::Tone_A: return qMakePair(697, 1633);
- case QXmppRtpChannel::Tone_4: return qMakePair(770, 1209);
- case QXmppRtpChannel::Tone_5: return qMakePair(770, 1336);
- case QXmppRtpChannel::Tone_6: return qMakePair(770, 1477);
- case QXmppRtpChannel::Tone_B: return qMakePair(770, 1633);
- case QXmppRtpChannel::Tone_7: return qMakePair(852, 1209);
- case QXmppRtpChannel::Tone_8: return qMakePair(852, 1336);
- case QXmppRtpChannel::Tone_9: return qMakePair(852, 1477);
- case QXmppRtpChannel::Tone_C: return qMakePair(852, 1633);
- case QXmppRtpChannel::Tone_Star: return qMakePair(941, 1209);
- case QXmppRtpChannel::Tone_0: return qMakePair(941, 1336);
- case QXmppRtpChannel::Tone_Pound: return qMakePair(941, 1477);
- case QXmppRtpChannel::Tone_D: return qMakePair(941, 1633);
+ case QXmppRtpAudioChannel::Tone_1: return qMakePair(697, 1209);
+ case QXmppRtpAudioChannel::Tone_2: return qMakePair(697, 1336);
+ case QXmppRtpAudioChannel::Tone_3: return qMakePair(697, 1477);
+ case QXmppRtpAudioChannel::Tone_A: return qMakePair(697, 1633);
+ case QXmppRtpAudioChannel::Tone_4: return qMakePair(770, 1209);
+ case QXmppRtpAudioChannel::Tone_5: return qMakePair(770, 1336);
+ case QXmppRtpAudioChannel::Tone_6: return qMakePair(770, 1477);
+ case QXmppRtpAudioChannel::Tone_B: return qMakePair(770, 1633);
+ case QXmppRtpAudioChannel::Tone_7: return qMakePair(852, 1209);
+ case QXmppRtpAudioChannel::Tone_8: return qMakePair(852, 1336);
+ case QXmppRtpAudioChannel::Tone_9: return qMakePair(852, 1477);
+ case QXmppRtpAudioChannel::Tone_C: return qMakePair(852, 1633);
+ case QXmppRtpAudioChannel::Tone_Star: return qMakePair(941, 1209);
+ case QXmppRtpAudioChannel::Tone_0: return qMakePair(941, 1336);
+ case QXmppRtpAudioChannel::Tone_Pound: return qMakePair(941, 1477);
+ case QXmppRtpAudioChannel::Tone_D: return qMakePair(941, 1633);
}
return qMakePair(0, 0);
}
-QByteArray renderTone(QXmppRtpChannel::Tone tone, int clockrate, quint32 clockTick, qint64 samples)
+QByteArray renderTone(QXmppRtpAudioChannel::Tone tone, int clockrate, quint32 clockTick, qint64 samples)
{
QPair<int,int> tf = toneFreqs(tone);
const float clockMult = 2.0 * M_PI / float(clockrate);
@@ -99,12 +154,11 @@ QByteArray renderTone(QXmppRtpChannel::Tone tone, int clockrate, quint32 clockTi
return chunk;
}
-class QXmppRtpChannelPrivate
+class QXmppRtpAudioChannelPrivate
{
public:
- QXmppRtpChannelPrivate();
+ QXmppRtpAudioChannelPrivate(QXmppRtpAudioChannel *qq);
QXmppCodec *codecForPayloadType(const QXmppJinglePayloadType &payloadType);
- QList<QXmppJinglePayloadType> supportedPayloadTypes() const;
// signals
bool signalsEmitted;
@@ -122,24 +176,26 @@ public:
// position of the head of the incoming buffer, in bytes
qint64 incomingPos;
quint16 incomingSequence;
- QXmppJinglePayloadType incomingTonesType;
QByteArray outgoingBuffer;
quint16 outgoingChunk;
QXmppCodec *outgoingCodec;
bool outgoingMarker;
- QList<QXmppJinglePayloadType> outgoingPayloadTypes;
+ bool outgoingPayloadNumbered;
quint16 outgoingSequence;
quint32 outgoingStamp;
QTimer *outgoingTimer;
QList<ToneInfo> outgoingTones;
QXmppJinglePayloadType outgoingTonesType;
- quint32 ssrc;
+ quint32 outgoingSsrc;
QXmppJinglePayloadType payloadType;
+
+private:
+ QXmppRtpAudioChannel *q;
};
-QXmppRtpChannelPrivate::QXmppRtpChannelPrivate()
+QXmppRtpAudioChannelPrivate::QXmppRtpAudioChannelPrivate(QXmppRtpAudioChannel *qq)
: signalsEmitted(false),
writtenSinceLastEmit(0),
incomingBuffering(true),
@@ -149,20 +205,20 @@ QXmppRtpChannelPrivate::QXmppRtpChannelPrivate()
incomingSequence(0),
outgoingCodec(0),
outgoingMarker(true),
+ outgoingPayloadNumbered(false),
outgoingSequence(1),
outgoingStamp(0),
- ssrc(0)
+ outgoingSsrc(0),
+ q(qq)
{
- qRegisterMetaType<QXmppRtpChannel::Tone>("QXmppRtpChannel::Tone");
-
- outgoingPayloadTypes = supportedPayloadTypes();
- ssrc = qrand();
+ qRegisterMetaType<QXmppRtpAudioChannel::Tone>("QXmppRtpAudioChannel::Tone");
+ outgoingSsrc = qrand();
}
/// Returns the audio codec for the given payload type.
///
-QXmppCodec *QXmppRtpChannelPrivate::codecForPayloadType(const QXmppJinglePayloadType &payloadType)
+QXmppCodec *QXmppRtpAudioChannelPrivate::codecForPayloadType(const QXmppJinglePayloadType &payloadType)
{
if (payloadType.id() == G711u)
return new QXmppG711uCodec(payloadType.clockrate());
@@ -175,12 +231,23 @@ QXmppCodec *QXmppRtpChannelPrivate::codecForPayloadType(const QXmppJinglePayload
return 0;
}
-/// Returns the list of supported payload types.
+/// Creates a new RTP audio channel.
///
+/// \param parent
-QList<QXmppJinglePayloadType> QXmppRtpChannelPrivate::supportedPayloadTypes() const
+QXmppRtpAudioChannel::QXmppRtpAudioChannel(QObject *parent)
+ : QIODevice(parent)
{
- QList<QXmppJinglePayloadType> payloads;
+ d = new QXmppRtpAudioChannelPrivate(this);
+ QXmppLoggable *logParent = qobject_cast<QXmppLoggable*>(parent);
+ if (logParent) {
+ connect(this, SIGNAL(logMessage(QXmppLogger::MessageType,QString)),
+ logParent, SIGNAL(logMessage(QXmppLogger::MessageType,QString)));
+ }
+ d->outgoingTimer = new QTimer(this);
+ connect(d->outgoingTimer, SIGNAL(timeout()), this, SLOT(writeDatagram()));
+
+ // set supported codecs
QXmppJinglePayloadType payload;
#ifdef QXMPP_USE_SPEEX
@@ -188,61 +255,47 @@ QList<QXmppJinglePayloadType> QXmppRtpChannelPrivate::supportedPayloadTypes() co
payload.setChannels(1);
payload.setName("speex");
payload.setClockrate(8000);
- payloads << payload;
+ m_outgoingPayloadTypes << payload;
#endif
payload.setId(G711u);
payload.setChannels(1);
payload.setName("PCMU");
payload.setClockrate(8000);
- payloads << payload;
+ m_outgoingPayloadTypes << payload;
payload.setId(G711a);
payload.setChannels(1);
payload.setName("PCMA");
payload.setClockrate(8000);
- payloads << payload;
+ m_outgoingPayloadTypes << payload;
+ QMap<QString, QString> parameters;
+ parameters.insert("events", "0-15");
payload.setId(101);
payload.setChannels(1);
payload.setName("telephone-event");
payload.setClockrate(8000);
- payloads << payload;
-
- return payloads;
-}
-
-/// Creates a new RTP channel.
-///
-/// \param parent
-
-QXmppRtpChannel::QXmppRtpChannel(QObject *parent)
- : QIODevice(parent),
- d(new QXmppRtpChannelPrivate)
-{
- QXmppLoggable *logParent = qobject_cast<QXmppLoggable*>(parent);
- if (logParent) {
- connect(this, SIGNAL(logMessage(QXmppLogger::MessageType,QString)),
- logParent, SIGNAL(logMessage(QXmppLogger::MessageType,QString)));
- }
- d->outgoingTimer = new QTimer(this);
- connect(d->outgoingTimer, SIGNAL(timeout()), this, SLOT(writeDatagram()));
+ payload.setParameters(parameters);
+ m_outgoingPayloadTypes << payload;
}
-/// Destroys an RTP channel.
+/// Destroys an RTP audio channel.
///
-QXmppRtpChannel::~QXmppRtpChannel()
+QXmppRtpAudioChannel::~QXmppRtpAudioChannel()
{
foreach (QXmppCodec *codec, d->incomingCodecs)
delete codec;
+ if (d->outgoingCodec)
+ delete d->outgoingCodec;
delete d;
}
/// Returns the number of bytes that are available for reading.
///
-qint64 QXmppRtpChannel::bytesAvailable() const
+qint64 QXmppRtpAudioChannel::bytesAvailable() const
{
return d->incomingBuffer.size();
}
@@ -250,7 +303,7 @@ qint64 QXmppRtpChannel::bytesAvailable() const
/// Closes the RTP channel.
///
-void QXmppRtpChannel::close()
+void QXmppRtpAudioChannel::close()
{
d->outgoingTimer->stop();
QIODevice::close();
@@ -260,11 +313,11 @@ void QXmppRtpChannel::close()
///
/// \param ba
-void QXmppRtpChannel::datagramReceived(const QByteArray &ba)
+void QXmppRtpAudioChannel::datagramReceived(const QByteArray &ba)
{
if (ba.size() < 12 || (quint8(ba.at(0)) >> 6) != RTP_VERSION)
{
- warning("QXmppRtpChannel::datagramReceived got an invalid RTP packet");
+ warning("QXmppRtpAudioChannel::datagramReceived got an invalid RTP packet");
return;
}
@@ -284,7 +337,7 @@ void QXmppRtpChannel::datagramReceived(const QByteArray &ba)
#ifdef QXMPP_DEBUG_RTP
const bool marker = marker_type & 0x80;
- logReceived(QString("RTP packet seq %1 stamp %2 marker %3 type %4 size %5").arg(
+ logReceived(QString("RTP audio packet seq %1 stamp %2 marker %3 type %4 size %5").arg(
QString::number(sequence),
QString::number(stamp),
QString::number(marker),
@@ -292,15 +345,6 @@ void QXmppRtpChannel::datagramReceived(const QByteArray &ba)
QString::number(packetLength)));
#endif
- // check type
- QXmppCodec *codec = d->incomingCodecs.value(type);
- if (!codec) {
- warning(QString("RTP packet seq %1 has unknown type %2")
- .arg(QString::number(sequence))
- .arg(QString::number(type)));
- return;
- }
-
// check sequence number
#if 0
if (d->incomingSequence && sequence != d->incomingSequence + 1)
@@ -310,6 +354,25 @@ void QXmppRtpChannel::datagramReceived(const QByteArray &ba)
#endif
d->incomingSequence = sequence;
+ // get or create codec
+ QXmppCodec *codec = 0;
+ if (!d->incomingCodecs.contains(type)) {
+ foreach (const QXmppJinglePayloadType &payload, m_incomingPayloadTypes) {
+ if (type == payload.id()) {
+ codec = d->codecForPayloadType(payload);
+ break;
+ }
+ }
+ if (codec)
+ d->incomingCodecs.insert(type, codec);
+ else
+ warning(QString("Could not find codec for RTP type %1").arg(QString::number(type)));
+ } else {
+ codec = d->incomingCodecs.value(type);
+ }
+ if (!codec)
+ return;
+
// determine packet's position in the buffer (in bytes)
qint64 packetOffset = 0;
if (!d->incomingBuffer.isEmpty())
@@ -353,7 +416,7 @@ void QXmppRtpChannel::datagramReceived(const QByteArray &ba)
emit readyRead();
}
-void QXmppRtpChannel::emitSignals()
+void QXmppRtpAudioChannel::emitSignals()
{
emit bytesWritten(d->writtenSinceLastEmit);
d->writtenSinceLastEmit = 0;
@@ -363,12 +426,17 @@ void QXmppRtpChannel::emitSignals()
/// Returns true, as the RTP channel is a sequential device.
///
-bool QXmppRtpChannel::isSequential() const
+bool QXmppRtpAudioChannel::isSequential() const
{
return true;
}
-qint64 QXmppRtpChannel::readData(char * data, qint64 maxSize)
+QIODevice::OpenMode QXmppRtpAudioChannel::openMode() const
+{
+ return QIODevice::openMode();
+}
+
+qint64 QXmppRtpAudioChannel::readData(char * data, qint64 maxSize)
{
// if we are filling the buffer, return empty samples
if (d->incomingBuffering)
@@ -385,7 +453,7 @@ qint64 QXmppRtpChannel::readData(char * data, qint64 maxSize)
if (readSize < maxSize)
{
#ifdef QXMPP_DEBUG_RTP
- debug(QString("QXmppRtpChannel::readData missing %1 bytes").arg(QString::number(maxSize - readSize)));
+ debug(QString("QXmppRtpAudioChannel::readData missing %1 bytes").arg(QString::number(maxSize - readSize)));
#endif
memset(data + readSize, 0, maxSize - readSize);
}
@@ -411,22 +479,52 @@ qint64 QXmppRtpChannel::readData(char * data, qint64 maxSize)
/// You can use this to determine the QAudioFormat to use with your
/// QAudioInput/QAudioOutput.
-QXmppJinglePayloadType QXmppRtpChannel::payloadType() const
+QXmppJinglePayloadType QXmppRtpAudioChannel::payloadType() const
{
return d->payloadType;
}
-/// Returns the local payload types.
-///
-
-QList<QXmppJinglePayloadType> QXmppRtpChannel::localPayloadTypes() const
+void QXmppRtpAudioChannel::payloadTypesChanged()
{
- return d->outgoingPayloadTypes;
+ // delete incoming codecs
+ foreach (QXmppCodec *codec, d->incomingCodecs)
+ delete codec;
+ d->incomingCodecs.clear();
+
+ // delete outgoing codec
+ if (d->outgoingCodec) {
+ delete d->outgoingCodec;
+ d->outgoingCodec = 0;
+ }
+
+ // create outgoing codec
+ foreach (const QXmppJinglePayloadType &outgoingType, m_outgoingPayloadTypes) {
+ // check for telephony events
+ if (outgoingType.name() == "telephone-event") {
+ d->outgoingTonesType = outgoingType;
+ }
+ else if (!d->outgoingCodec) {
+ QXmppCodec *codec = d->codecForPayloadType(outgoingType);
+ if (codec) {
+ d->payloadType = outgoingType;
+ d->outgoingCodec = codec;
+ }
+ }
+ }
+
+ // size in bytes of an decoded packet
+ d->outgoingChunk = SAMPLE_BYTES * d->payloadType.ptime() * d->payloadType.clockrate() / 1000;
+ d->outgoingTimer->setInterval(d->payloadType.ptime());
+
+ d->incomingMinimum = d->outgoingChunk * 5;
+ d->incomingMaximum = d->outgoingChunk * 15;
+
+ open(QIODevice::ReadWrite | QIODevice::Unbuffered);
}
/// Returns the position in the received audio data.
-qint64 QXmppRtpChannel::pos() const
+qint64 QXmppRtpAudioChannel::pos() const
{
return d->incomingPos;
}
@@ -438,7 +536,7 @@ qint64 QXmppRtpChannel::pos() const
///
/// \param pos
-bool QXmppRtpChannel::seek(qint64 pos)
+bool QXmppRtpAudioChannel::seek(qint64 pos)
{
qint64 delta = pos - d->incomingPos;
if (delta < 0)
@@ -449,74 +547,11 @@ bool QXmppRtpChannel::seek(qint64 pos)
return true;
}
-/// Sets the remote payload types.
-///
-/// \param remotePayloadTypes
-
-void QXmppRtpChannel::setRemotePayloadTypes(const QList<QXmppJinglePayloadType> &remotePayloadTypes)
-{
- QList<QXmppJinglePayloadType> commonPayloadTypes;
-
- foreach (const QXmppJinglePayloadType &payloadType, remotePayloadTypes) {
-
- // check we support this payload type
- int index = d->outgoingPayloadTypes.indexOf(payloadType);
- if (index < 0)
- continue;
- commonPayloadTypes << d->outgoingPayloadTypes[index];
-
- // check for telephony events
- if (payloadType.name() == "telephone-event") {
- d->incomingTonesType = payloadType;
- d->outgoingTonesType = d->outgoingPayloadTypes[index];
- continue;
- }
-
- // create codec for this payload type
- QXmppCodec *codec = d->codecForPayloadType(payloadType);
- if (!codec)
- continue;
-
- if (commonPayloadTypes.size() == 1) {
-
- // store outgoing codec
- d->payloadType = d->outgoingPayloadTypes[index];
- d->outgoingCodec = codec;
-
- } else if (payloadType.ptime() != d->payloadType.ptime() ||
- payloadType.clockrate() != d->payloadType.clockrate()) {
-
- warning(QString("QXmppRtpChannel skipping payload due to ptime or clockrate mismatch : %1 (%2)")
- .arg(QString::number(payloadType.id()))
- .arg(payloadType.name()));
- delete codec;
- continue;
- }
-
- // store incoming codec
- d->incomingCodecs[payloadType.id()] = codec;
- }
- d->outgoingPayloadTypes = commonPayloadTypes;
- if (d->outgoingPayloadTypes.isEmpty()) {
- warning("QXmppRtpChannel could not negociate a common codec");
- return;
- }
-
- // size in bytes of an decoded packet
- d->outgoingChunk = SAMPLE_BYTES * d->payloadType.ptime() * d->payloadType.clockrate() / 1000;
- d->outgoingTimer->setInterval(d->payloadType.ptime());
-
- d->incomingMinimum = d->outgoingChunk * 5;
- d->incomingMaximum = d->outgoingChunk * 15;
-
- open(QIODevice::ReadWrite | QIODevice::Unbuffered);
-}
-
/// Starts sending the specified DTMF tone.
///
/// \param tone
-void QXmppRtpChannel::startTone(QXmppRtpChannel::Tone tone)
+void QXmppRtpAudioChannel::startTone(QXmppRtpAudioChannel::Tone tone)
{
ToneInfo info;
info.tone = tone;
@@ -530,7 +565,7 @@ void QXmppRtpChannel::startTone(QXmppRtpChannel::Tone tone)
///
/// \param tone
-void QXmppRtpChannel::stopTone(QXmppRtpChannel::Tone tone)
+void QXmppRtpAudioChannel::stopTone(QXmppRtpAudioChannel::Tone tone)
{
for (int i = 0; i < d->outgoingTones.size(); ++i) {
if (d->outgoingTones[i].tone == tone) {
@@ -540,11 +575,10 @@ void QXmppRtpChannel::stopTone(QXmppRtpChannel::Tone tone)
}
}
-qint64 QXmppRtpChannel::writeData(const char * data, qint64 maxSize)
+qint64 QXmppRtpAudioChannel::writeData(const char * data, qint64 maxSize)
{
- if (!d->outgoingCodec)
- {
- warning("QXmppRtpChannel::writeData before codec was set");
+ if (!d->outgoingCodec) {
+ warning("QXmppRtpAudioChannel::writeData before codec was set");
return -1;
}
@@ -557,7 +591,7 @@ qint64 QXmppRtpChannel::writeData(const char * data, qint64 maxSize)
return maxSize;
}
-void QXmppRtpChannel::writeDatagram()
+void QXmppRtpAudioChannel::writeDatagram()
{
// read audio chunk
QByteArray chunk;
@@ -586,13 +620,13 @@ void QXmppRtpChannel::writeDatagram()
stream << marker_type;
stream << d->outgoingSequence;
stream << info.outgoingStart;
- stream << d->ssrc;
+ stream << d->outgoingSsrc;
stream << quint8(info.tone);
stream << quint8(info.finished ? 0x80 : 0x00);
stream << quint16(d->outgoingStamp + packetTicks - info.outgoingStart);
#ifdef QXMPP_DEBUG_RTP
- logSent(QString("RTP packet seq %1 stamp %2 marker %3 type %4 size %5").arg(
+ logSent(QString("RTP audio packet seq %1 stamp %2 marker %3 type %4 size %5").arg(
QString::number(d->outgoingSequence),
QString::number(d->outgoingStamp),
QString::number(marker_type & 0x80 != 0),
@@ -628,7 +662,7 @@ void QXmppRtpChannel::writeDatagram()
stream << marker_type;
stream << d->outgoingSequence;
stream << d->outgoingStamp;
- stream << d->ssrc;
+ stream << d->outgoingSsrc;
// encode audio chunk
QDataStream input(chunk);
@@ -636,7 +670,7 @@ void QXmppRtpChannel::writeDatagram()
const qint64 packetTicks = d->outgoingCodec->encode(input, stream);
#ifdef QXMPP_DEBUG_RTP
- logSent(QString("RTP packet seq %1 stamp %2 marker %3 type %4 size %5").arg(
+ logSent(QString("RTP audio packet seq %1 stamp %2 marker %3 type %4 size %5").arg(
QString::number(d->outgoingSequence),
QString::number(d->outgoingStamp),
QString::number(marker_type & 0x80 != 0),
@@ -655,3 +689,218 @@ void QXmppRtpChannel::writeDatagram()
QMetaObject::invokeMethod(this, "emitSignals", Qt::QueuedConnection);
}
}
+
+class QXmppRtpVideoChannelPrivate
+{
+public:
+ QXmppRtpVideoChannelPrivate();
+ QMap<int, QXmppVideoDecoder*> decoders;
+ QList<QXmppVideoEncoder*> encoders;
+ QXmppVideoEncoder *encoder;
+ QList<QXmppVideoFrame> frames;
+
+ // local
+ QXmppVideoFormat outgoingFormat;
+ quint8 outgoingId;
+ quint16 outgoingSequence;
+ quint32 outgoingStamp;
+ quint32 outgoingSsrc;
+};
+
+QXmppRtpVideoChannelPrivate::QXmppRtpVideoChannelPrivate()
+ : encoder(0),
+ outgoingId(0),
+ outgoingSequence(1),
+ outgoingStamp(0),
+ outgoingSsrc(0)
+{
+ outgoingSsrc = qrand();
+}
+
+QXmppRtpVideoChannel::QXmppRtpVideoChannel(QObject *parent)
+ : QXmppLoggable(parent)
+{
+ d = new QXmppRtpVideoChannelPrivate;
+ d->outgoingFormat.setFrameSize(QSize(320, 240));
+ d->outgoingFormat.setPixelFormat(QXmppVideoFrame::Format_YUV420P);
+
+ // set supported codecs
+#ifdef QXMPP_USE_THEORA
+ QXmppVideoEncoder *encoder = new QXmppTheoraEncoder;
+ encoder->setFormat(d->outgoingFormat);
+ QXmppJinglePayloadType payload;
+ payload.setId(96);
+ payload.setName("theora");
+ payload.setClockrate(90000);
+ payload.setParameters(encoder->parameters());
+ m_outgoingPayloadTypes << payload;
+ delete encoder;
+#endif
+}
+
+QXmppRtpVideoChannel::~QXmppRtpVideoChannel()
+{
+ foreach (QXmppVideoDecoder *decoder, d->decoders)
+ delete decoder;
+ if (d->encoder)
+ delete d->encoder;
+ delete d;
+}
+
+/// Closes the RTP channel.
+///
+
+void QXmppRtpVideoChannel::close()
+{
+}
+
+/// Processes an incoming RTP video packet.
+///
+/// \param ba
+
+void QXmppRtpVideoChannel::datagramReceived(const QByteArray &ba)
+{
+ if (ba.size() < 12 || (quint8(ba.at(0)) >> 6) != RTP_VERSION)
+ {
+ warning("QXmppRtpVideoChannel::datagramReceived got an invalid RTP packet");
+ return;
+ }
+
+ // parse RTP header
+ QDataStream stream(ba);
+ quint8 version, marker_type;
+ quint32 ssrc;
+ quint16 sequence;
+ quint32 stamp;
+ stream >> version;
+ stream >> marker_type;
+ stream >> sequence;
+ stream >> stamp;
+ stream >> ssrc;
+ const quint8 type = marker_type & 0x7f;
+ const qint64 packetLength = ba.size() - 12;
+
+#ifdef QXMPP_DEBUG_RTP
+ const bool marker = marker_type & 0x80;
+ logReceived(QString("RTP video packet seq %1 stamp %2 marker %3 type %4 size %5").arg(
+ QString::number(sequence),
+ QString::number(stamp),
+ QString::number(marker),
+ QString::number(type),
+ QString::number(packetLength)));
+#endif
+
+ // get codec
+ QXmppVideoDecoder *decoder = d->decoders.value(type);
+ if (!decoder)
+ return;
+ d->frames << decoder->handlePacket(stream);
+}
+
+QXmppVideoFormat QXmppRtpVideoChannel::decoderFormat() const
+{
+ if (d->decoders.isEmpty())
+ return QXmppVideoFormat();
+ const int key = d->decoders.keys().first();
+ return d->decoders.value(key)->format();
+}
+
+QXmppVideoFormat QXmppRtpVideoChannel::encoderFormat() const
+{
+ return d->outgoingFormat;
+}
+
+void QXmppRtpVideoChannel::setEncoderFormat(const QXmppVideoFormat &format)
+{
+ if (d->encoder && !d->encoder->setFormat(format))
+ return;
+ d->outgoingFormat = format;
+}
+
+QIODevice::OpenMode QXmppRtpVideoChannel::openMode() const
+{
+ QIODevice::OpenMode mode = QIODevice::NotOpen;
+ if (!d->decoders.isEmpty())
+ mode |= QIODevice::ReadOnly;
+ if (d->encoder)
+ mode |= QIODevice::WriteOnly;
+ return mode;
+}
+
+void QXmppRtpVideoChannel::payloadTypesChanged()
+{
+ // refresh decoders
+ foreach (QXmppVideoDecoder *decoder, d->decoders)
+ delete decoder;
+ d->decoders.clear();
+ foreach (const QXmppJinglePayloadType &payload, m_incomingPayloadTypes) {
+ QXmppVideoDecoder *decoder = 0;
+#ifdef QXMPP_USE_THEORA
+ if (payload.name().toLower() == "theora")
+ decoder = new QXmppTheoraDecoder;
+#endif
+ if (decoder) {
+ decoder->setParameters(payload.parameters());
+ d->decoders.insert(payload.id(), decoder);
+ }
+ }
+
+ // refresh encoder
+ if (d->encoder) {
+ delete d->encoder;
+ d->encoder = 0;
+ }
+ foreach (const QXmppJinglePayloadType &payload, m_outgoingPayloadTypes) {
+ QXmppVideoEncoder *encoder = 0;
+#ifdef QXMPP_USE_THEORA
+ if (payload.name().toLower() == "theora")
+ encoder = new QXmppTheoraEncoder;
+#endif
+ if (encoder) {
+ encoder->setFormat(d->outgoingFormat);
+ d->encoder = encoder;
+ d->outgoingId = payload.id();
+ break;
+ }
+ }
+}
+
+QList<QXmppVideoFrame> QXmppRtpVideoChannel::readFrames()
+{
+ const QList<QXmppVideoFrame> frames = d->frames;
+ d->frames.clear();
+ return frames;
+}
+
+void QXmppRtpVideoChannel::writeFrame(const QXmppVideoFrame &frame)
+{
+ if (!d->encoder) {
+ warning("QXmppRtpVideoChannel::writeData before codec was set");
+ return;
+ }
+
+ const quint8 marker_type = d->outgoingId;
+ QByteArray packet;
+ foreach (const QByteArray &payload, d->encoder->handleFrame(frame)) {
+ packet.clear();
+ QDataStream stream(&packet, QIODevice::WriteOnly);
+ stream << quint8(RTP_VERSION << 6);
+ stream << marker_type;
+ stream << d->outgoingSequence;
+ stream << d->outgoingStamp;
+ stream << d->outgoingSsrc;
+ stream.writeRawData(payload.constData(), payload.size());
+#if QXMPP_DEBUG_RTP
+ logSent(QString("RTP video packet seq %1 stamp %2 marker %3 type %4 size %5").arg(
+ QString::number(d->outgoingSequence),
+ QString::number(d->outgoingStamp),
+ QString::number(marker_type & 0x80 != 0),
+ QString::number(marker_type & 0x7f),
+ QString::number(packet.size() - 12)));
+#endif
+
+ emit sendDatagram(packet);
+ d->outgoingSequence++;
+ }
+}
+
diff --git a/src/QXmppRtpChannel.h b/src/QXmppRtpChannel.h
index 881b31df..9b02aa2a 100644
--- a/src/QXmppRtpChannel.h
+++ b/src/QXmppRtpChannel.h
@@ -25,21 +25,42 @@
#define QXMPPRTPCHANNEL_H
#include <QIODevice>
+#include <QSize>
+#include "QXmppJingleIq.h"
#include "QXmppLogger.h"
class QXmppCodec;
class QXmppJinglePayloadType;
-class QXmppRtpChannelPrivate;
+class QXmppRtpAudioChannelPrivate;
+class QXmppRtpVideoChannelPrivate;
-/// \brief The QXmppRtpChannel class represents an RTP channel to a remote party.
+class QXmppRtpChannel
+{
+public:
+ QXmppRtpChannel();
+
+ virtual void close() = 0;
+ virtual QIODevice::OpenMode openMode() const = 0;
+ QList<QXmppJinglePayloadType> localPayloadTypes();
+ void setRemotePayloadTypes(const QList<QXmppJinglePayloadType> &remotePayloadTypes);
+
+protected:
+ virtual void payloadTypesChanged();
+
+ QList<QXmppJinglePayloadType> m_incomingPayloadTypes;
+ QList<QXmppJinglePayloadType> m_outgoingPayloadTypes;
+ bool m_outgoingPayloadNumbered;
+};
+
+/// \brief The QXmppRtpAudioChannel class represents an RTP audio channel to a remote party.
///
/// It acts as a QIODevice so that you can read / write audio samples, for
/// instance using a QAudioOutput and a QAudioInput.
///
/// \note THIS API IS NOT FINALIZED YET
-class QXmppRtpChannel : public QIODevice
+class QXmppRtpAudioChannel : public QIODevice, public QXmppRtpChannel
{
Q_OBJECT
@@ -64,18 +85,16 @@ public:
Tone_D ///< Tone for the D key.
};
- QXmppRtpChannel(QObject *parent = 0);
- ~QXmppRtpChannel();
+ QXmppRtpAudioChannel(QObject *parent = 0);
+ ~QXmppRtpAudioChannel();
QXmppJinglePayloadType payloadType() const;
- QList<QXmppJinglePayloadType> localPayloadTypes() const;
- void setRemotePayloadTypes(const QList<QXmppJinglePayloadType> &remotePayloadTypes);
-
/// \cond
qint64 bytesAvailable() const;
void close();
bool isSequential() const;
+ QIODevice::OpenMode openMode() const;
qint64 pos() const;
bool seek(qint64 pos);
/// \endcond
@@ -89,8 +108,8 @@ signals:
public slots:
void datagramReceived(const QByteArray &ba);
- void startTone(QXmppRtpChannel::Tone tone);
- void stopTone(QXmppRtpChannel::Tone tone);
+ void startTone(QXmppRtpAudioChannel::Tone tone);
+ void stopTone(QXmppRtpAudioChannel::Tone tone);
protected:
/// \cond
@@ -114,6 +133,7 @@ protected:
emit logMessage(QXmppLogger::SentMessage, qxmpp_loggable_trace(message));
}
+ void payloadTypesChanged();
qint64 readData(char * data, qint64 maxSize);
qint64 writeData(const char * data, qint64 maxSize);
/// \endcond
@@ -123,7 +143,91 @@ private slots:
void writeDatagram();
private:
- QXmppRtpChannelPrivate * const d;
+ friend class QXmppRtpAudioChannelPrivate;
+ QXmppRtpAudioChannelPrivate * d;
+};
+
+class QXmppVideoPlane
+{
+public:
+ QByteArray data;
+ int width;
+ int height;
+ int stride;
+};
+
+class QXmppVideoFrame
+{
+public:
+ enum PixelFormat {
+ Format_YUV420P = 18,
+ };
+
+ QXmppVideoPlane planes[3];
+};
+
+class QXmppVideoFormat
+{
+public:
+ QSize frameSize() const {
+ return m_frameSize;
+ }
+
+ void setFrameSize(const QSize &frameSize) {
+ m_frameSize = frameSize;
+ }
+
+ QXmppVideoFrame::PixelFormat pixelFormat() const {
+ return m_pixelFormat;
+ }
+
+ void setPixelFormat(QXmppVideoFrame::PixelFormat pixelFormat) {
+ m_pixelFormat = pixelFormat;
+ }
+
+private:
+ QSize m_frameSize;
+ QXmppVideoFrame::PixelFormat m_pixelFormat;
+};
+
+
+/// \brief The QXmppRtpVideoChannel class represents an RTP video channel to a remote party.
+///
+/// \note THIS API IS NOT FINALIZED YET
+
+class QXmppRtpVideoChannel : public QXmppLoggable, public QXmppRtpChannel
+{
+ Q_OBJECT
+
+public:
+ QXmppRtpVideoChannel(QObject *parent = 0);
+ ~QXmppRtpVideoChannel();
+
+ // incoming stream
+ QXmppVideoFormat decoderFormat() const;
+ QList<QXmppVideoFrame> readFrames();
+
+ // outgoing stream
+ QXmppVideoFormat encoderFormat() const;
+ void setEncoderFormat(const QXmppVideoFormat &format);
+ void writeFrame(const QXmppVideoFrame &frame);
+
+ QIODevice::OpenMode openMode() const;
+ void close();
+
+signals:
+ /// \brief This signal is emitted when a datagram needs to be sent.
+ void sendDatagram(const QByteArray &ba);
+
+public slots:
+ void datagramReceived(const QByteArray &ba);
+
+protected:
+ void payloadTypesChanged();
+
+private:
+ friend class QXmppRtpVideoChannelPrivate;
+ QXmppRtpVideoChannelPrivate * d;
};
#endif
diff --git a/src/QXmppStun.cpp b/src/QXmppStun.cpp
index 2fa37076..95a04ba8 100644
--- a/src/QXmppStun.cpp
+++ b/src/QXmppStun.cpp
@@ -1234,13 +1234,19 @@ void QXmppTurnAllocation::disconnectFromHost()
void QXmppTurnAllocation::readyRead()
{
- const qint64 size = socket->pendingDatagramSize();
+ QByteArray buffer;
QHostAddress remoteHost;
quint16 remotePort;
+ while (socket->hasPendingDatagrams()) {
+ const qint64 size = socket->pendingDatagramSize();
+ buffer.resize(size);
+ socket->readDatagram(buffer.data(), buffer.size(), &remoteHost, &remotePort);
+ handleDatagram(buffer, remoteHost, remotePort);
+ }
+}
- QByteArray buffer(size, 0);
- socket->readDatagram(buffer.data(), buffer.size(), &remoteHost, &remotePort);
-
+void QXmppTurnAllocation::handleDatagram(const QByteArray &buffer, const QHostAddress &remoteHost, quint16 remotePort)
+{
// demultiplex channel data
if (buffer.size() >= 4 && (buffer[0] & 0xc0) == 0x40) {
QDataStream stream(buffer);
@@ -1560,12 +1566,12 @@ QString QXmppIceComponent::Pair::toString() const
/// \param controlling
/// \param parent
-QXmppIceComponent::QXmppIceComponent(bool controlling, QObject *parent)
+QXmppIceComponent::QXmppIceComponent(QObject *parent)
: QXmppLoggable(parent),
m_component(0),
m_activePair(0),
m_fallbackPair(0),
- m_iceControlling(controlling),
+ m_iceControlling(false),
m_peerReflexivePriority(0),
m_stunPort(0),
m_stunTries(0),
@@ -1708,6 +1714,11 @@ bool QXmppIceComponent::isConnected() const
return m_activePair != 0;
}
+void QXmppIceComponent::setIceControlling(bool controlling)
+{
+ m_iceControlling = controlling;
+}
+
/// Returns the list of local candidates.
QList<QXmppJingleCandidate> QXmppIceComponent::localCandidates() const
@@ -1851,7 +1862,13 @@ void QXmppIceComponent::setSockets(QList<QUdpSocket*> sockets)
QXmppJingleCandidate candidate;
candidate.setComponent(m_component);
candidate.setFoundation(foundation++);
- candidate.setHost(socket->localAddress());
+ // remove scope ID from IPv6 non-link local addresses
+ QHostAddress addr(socket->localAddress());
+ if (addr.protocol() == QAbstractSocket::IPv6Protocol &&
+ !isIPv6LinkLocalAddress(addr)) {
+ addr.setScopeId(QString());
+ }
+ candidate.setHost(addr);
candidate.setId(generateStanzaHash(10));
candidate.setPort(socket->localPort());
candidate.setProtocol("udp");
@@ -1922,12 +1939,15 @@ void QXmppIceComponent::readyRead()
if (!socket)
return;
- const qint64 size = socket->pendingDatagramSize();
+ QByteArray buffer;
QHostAddress remoteHost;
quint16 remotePort;
- QByteArray buffer(size, 0);
- socket->readDatagram(buffer.data(), buffer.size(), &remoteHost, &remotePort);
- handleDatagram(buffer, remoteHost, remotePort, socket);
+ while (socket->hasPendingDatagrams()) {
+ const qint64 size = socket->pendingDatagramSize();
+ buffer.resize(size);
+ socket->readDatagram(buffer.data(), buffer.size(), &remoteHost, &remotePort);
+ handleDatagram(buffer, remoteHost, remotePort, socket);
+ }
}
void QXmppIceComponent::handleDatagram(const QByteArray &buffer, const QHostAddress &remoteHost, quint16 remotePort, QUdpSocket *socket)
@@ -2153,21 +2173,25 @@ QList<QHostAddress> QXmppIceComponent::discoverAddresses()
foreach (const QNetworkAddressEntry &entry, interface.addressEntries())
{
- if ((entry.ip().protocol() != QAbstractSocket::IPv4Protocol &&
- entry.ip().protocol() != QAbstractSocket::IPv6Protocol) ||
+ QHostAddress ip = entry.ip();
+ if ((ip.protocol() != QAbstractSocket::IPv4Protocol &&
+ ip.protocol() != QAbstractSocket::IPv6Protocol) ||
entry.netmask().isNull() ||
entry.netmask() == QHostAddress::Broadcast)
continue;
#ifdef Q_OS_MAC
// FIXME: on Mac OS X, sending IPv6 UDP packets fails
- if (entry.ip().protocol() == QAbstractSocket::IPv6Protocol)
+ if (ip.protocol() == QAbstractSocket::IPv6Protocol)
continue;
#endif
- QHostAddress ip = entry.ip();
- if (isIPv6LinkLocalAddress(ip))
- ip.setScopeId(interface.name());
+ // FIXME: for now skip IPv6 link-local addresses, seems to upset
+ // clients such as empathy
+ if (isIPv6LinkLocalAddress(ip)) {
+ ip.setScopeId(interface.name());
+ continue;
+ }
addresses << ip;
}
}
@@ -2190,7 +2214,7 @@ QList<QUdpSocket*> QXmppIceComponent::reservePorts(const QList<QHostAddress> &ad
return sockets;
const int expectedSize = addresses.size() * count;
- quint16 port = 40000;
+ quint16 port = 49152;
while (sockets.size() != expectedSize) {
// reserve first port (even number)
if (port % 2)
@@ -2269,9 +2293,9 @@ qint64 QXmppIceComponent::writeStun(const QXmppStunMessage &message, QXmppIceCom
/// \param controlling
/// \param parent
-QXmppIceConnection::QXmppIceConnection(bool controlling, QObject *parent)
+QXmppIceConnection::QXmppIceConnection(QObject *parent)
: QXmppLoggable(parent),
- m_controlling(controlling),
+ m_iceControlling(false),
m_stunPort(0)
{
bool check;
@@ -2311,8 +2335,9 @@ void QXmppIceConnection::addComponent(int component)
return;
}
- QXmppIceComponent *socket = new QXmppIceComponent(m_controlling, this);
+ QXmppIceComponent *socket = new QXmppIceComponent(this);
socket->setComponent(component);
+ socket->setIceControlling(m_iceControlling);
socket->setLocalUser(m_localUser);
socket->setLocalPassword(m_localPassword);
socket->setStunServer(m_stunHost, m_stunPort);
@@ -2388,6 +2413,7 @@ void QXmppIceConnection::connectToHost()
m_connectTimer->start();
}
+
/// Returns true if ICE negotiation completed, false otherwise.
bool QXmppIceConnection::isConnected() const
@@ -2398,6 +2424,13 @@ bool QXmppIceConnection::isConnected() const
return true;
}
+void QXmppIceConnection::setIceControlling(bool controlling)
+{
+ m_iceControlling = controlling;
+ foreach (QXmppIceComponent *socket, m_components.values())
+ socket->setIceControlling(controlling);
+}
+
/// Returns the list of local HOST CANDIDATES candidates by iterating
/// over the available network interfaces.
diff --git a/src/QXmppStun.h b/src/QXmppStun.h
index a30872bb..1792d3cb 100644
--- a/src/QXmppStun.h
+++ b/src/QXmppStun.h
@@ -239,6 +239,7 @@ private slots:
void writeStun(const QXmppStunMessage &message);
private:
+ void handleDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port);
void setState(AllocationState state);
QUdpSocket *socket;
@@ -274,8 +275,9 @@ class QXmppIceComponent : public QXmppLoggable
Q_OBJECT
public:
- QXmppIceComponent(bool controlling, QObject *parent=0);
+ QXmppIceComponent(QObject *parent=0);
~QXmppIceComponent();
+ void setIceControlling(bool controlling);
void setStunServer(const QHostAddress &host, quint16 port);
void setTurnServer(const QHostAddress &host, quint16 port);
void setTurnUser(const QString &user);
@@ -379,10 +381,11 @@ class QXmppIceConnection : public QXmppLoggable
Q_OBJECT
public:
- QXmppIceConnection(bool controlling, QObject *parent = 0);
+ QXmppIceConnection(QObject *parent = 0);
QXmppIceComponent *component(int component);
void addComponent(int component);
+ void setIceControlling(bool controlling);
QList<QXmppJingleCandidate> localCandidates() const;
QString localUser() const;
@@ -422,7 +425,7 @@ private slots:
private:
QTimer *m_connectTimer;
- bool m_controlling;
+ bool m_iceControlling;
QMap<int, QXmppIceComponent*> m_components;
QString m_localUser;
QString m_localPassword;