diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2011-04-15 15:40:09 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2011-04-15 15:40:09 +0000 |
| commit | e1c4a3b40604db10037b59b482ff219609354165 (patch) | |
| tree | f71d640613754cbbd5a87484ab49e416c0e861a0 /src | |
| parent | 807a04ef3b864ac8f0e0f170176db7dd629f742a (diff) | |
| download | qxmpp-e1c4a3b40604db10037b59b482ff219609354165.tar.gz | |
overhaul RTP / jingle stack to allow video support
Diffstat (limited to 'src')
| -rw-r--r-- | src/QXmppCallManager.cpp | 859 | ||||
| -rw-r--r-- | src/QXmppCallManager.h | 18 | ||||
| -rw-r--r-- | src/QXmppRtpChannel.cpp | 569 | ||||
| -rw-r--r-- | src/QXmppRtpChannel.h | 126 | ||||
| -rw-r--r-- | src/QXmppStun.cpp | 75 | ||||
| -rw-r--r-- | src/QXmppStun.h | 9 |
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; |
