aboutsummaryrefslogtreecommitdiff
path: root/src/QXmppRtpChannel.cpp
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2010-11-14 23:36:52 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2010-11-14 23:36:52 +0000
commit7c973aa48165198f4b298b6a8670015d2e453bb2 (patch)
treeb4ac5ad0647f8faa9a2e6252fd7ca1c4a5a441ea /src/QXmppRtpChannel.cpp
parent83a329cbe139c9980167b9390fcabc1b851e1efc (diff)
downloadqxmpp-7c973aa48165198f4b298b6a8670015d2e453bb2.tar.gz
add QXmppRtpChannel class to move RTP handling out of QXmppCall
Diffstat (limited to 'src/QXmppRtpChannel.cpp')
-rw-r--r--src/QXmppRtpChannel.cpp346
1 files changed, 346 insertions, 0 deletions
diff --git a/src/QXmppRtpChannel.cpp b/src/QXmppRtpChannel.cpp
new file mode 100644
index 00000000..e39d6194
--- /dev/null
+++ b/src/QXmppRtpChannel.cpp
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2008-2010 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QDataStream>
+
+#include "QXmppCodec.h"
+#include "QXmppJingleIq.h"
+#include "QXmppRtpChannel.h"
+
+//#define QXMPP_DEBUG_RTP
+#define SAMPLE_BYTES 2
+
+const quint8 RTP_VERSION = 0x02;
+
+class QXmppRtpChannelPrivate
+{
+public:
+ QXmppRtpChannelPrivate();
+
+ // signals
+ bool signalsEmitted;
+ qint64 writtenSinceLastEmit;
+
+ // RTP
+ QXmppCodec *codec;
+ QHostAddress remoteHost;
+ quint16 remotePort;
+
+ QByteArray incomingBuffer;
+ bool incomingBuffering;
+ int incomingMinimum;
+ int incomingMaximum;
+ quint16 incomingSequence;
+ quint32 incomingStamp;
+
+ quint16 outgoingChunk;
+ QByteArray outgoingBuffer;
+ bool outgoingMarker;
+ quint16 outgoingSequence;
+ quint32 outgoingStamp;
+
+ QXmppJinglePayloadType payloadType;
+};
+
+QXmppRtpChannelPrivate::QXmppRtpChannelPrivate()
+ : signalsEmitted(false),
+ writtenSinceLastEmit(0),
+ codec(0),
+ incomingBuffering(true),
+ incomingMinimum(0),
+ incomingMaximum(0),
+ incomingSequence(0),
+ incomingStamp(0),
+ outgoingMarker(true),
+ outgoingSequence(0),
+ outgoingStamp(0)
+{
+}
+
+/// Creates a new RTP channel.
+///
+/// \param parent
+
+QXmppRtpChannel::QXmppRtpChannel(QObject *parent)
+ : QIODevice(parent),
+ d(new QXmppRtpChannelPrivate)
+{
+}
+
+/// Destroys an RTP channel.
+///
+
+QXmppRtpChannel::~QXmppRtpChannel()
+{
+ delete d;
+}
+
+/// Returns the number of bytes that are available for reading.
+///
+
+qint64 QXmppRtpChannel::bytesAvailable() const
+{
+ return d->incomingBuffer.size();
+}
+
+/// Processes an incoming RTP packet.
+///
+/// \param ba
+void QXmppRtpChannel::datagramReceived(const QByteArray &ba)
+{
+ if (!d->codec)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QLatin1String("QXmppRtpChannel::datagramReceived before codec selection"));
+ return;
+ }
+
+ if (ba.size() < 12 || (quint8(ba.at(0)) >> 6) != RTP_VERSION)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QLatin1String("QXmppRtpChannel::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 bool marker = marker_type & 0x80;
+ const quint8 type = marker_type & 0x7f;
+ const qint64 packetLength = ba.size() - 12;
+
+#ifdef QXMPP_DEBUG_RTP
+ emit logMessage(QXmppLogger::ReceivedMessage,
+ QString("RTP 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
+
+ // check type
+ if (type != d->payloadType.id())
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("RTP packet seq %1 has unknown type %2")
+ .arg(QString::number(sequence))
+ .arg(QString::number(type)));
+ return;
+ }
+
+ // check sequence number
+ if (!marker && sequence != d->incomingSequence + 1)
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("RTP packet seq %1 is out of order, previous was %2")
+ .arg(QString::number(sequence))
+ .arg(QString::number(d->incomingSequence)));
+ d->incomingSequence = sequence;
+
+ // determine packet's position in the buffer (in bytes)
+ qint64 packetOffset = 0;
+ if (!d->incomingBuffer.isEmpty())
+ {
+ packetOffset = (stamp - d->incomingStamp) * SAMPLE_BYTES;
+ if (packetOffset < 0)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("RTP packet stamp %1 is too old, buffer start is %2")
+ .arg(QString::number(stamp))
+ .arg(QString::number(d->incomingStamp)));
+ return;
+ }
+ } else {
+ d->incomingStamp = stamp;
+ }
+
+ // allocate space for new packet
+ if (packetOffset + packetLength > d->incomingBuffer.size())
+ d->incomingBuffer += QByteArray(packetOffset + packetLength - d->incomingBuffer.size(), 0);
+ QDataStream output(&d->incomingBuffer, QIODevice::WriteOnly);
+ output.device()->seek(packetOffset);
+ output.setByteOrder(QDataStream::LittleEndian);
+ d->codec->decode(stream, output);
+
+ // check whether we are running late
+ if (d->incomingBuffer.size() > d->incomingMaximum)
+ {
+ const qint64 droppedSize = d->incomingBuffer.size() - d->incomingMinimum;
+ emit logMessage(QXmppLogger::DebugMessage,
+ QString("RTP buffer is too full, dropping %1 bytes")
+ .arg(QString::number(droppedSize)));
+ d->incomingBuffer = d->incomingBuffer.right(d->incomingMinimum);
+ d->incomingStamp += droppedSize / SAMPLE_BYTES;
+ }
+ // check whether we have filled the initial buffer
+ if (d->incomingBuffer.size() >= d->incomingMinimum)
+ d->incomingBuffering = false;
+ if (!d->incomingBuffering)
+ emit readyRead();
+}
+
+void QXmppRtpChannel::emitSignals()
+{
+ emit bytesWritten(d->writtenSinceLastEmit);
+ d->writtenSinceLastEmit = 0;
+ d->signalsEmitted = false;
+}
+
+/// Returns true, as the RTP channel is a sequential device.
+///
+
+bool QXmppRtpChannel::isSequential() const
+{
+ return true;
+}
+
+qint64 QXmppRtpChannel::readData(char * data, qint64 maxSize)
+{
+ // if we are filling the buffer, return empty samples
+ if (d->incomingBuffering)
+ {
+ memset(data, 0, maxSize);
+ return maxSize;
+ }
+
+ qint64 readSize = qMin(maxSize, qint64(d->incomingBuffer.size()));
+ memcpy(data, d->incomingBuffer.constData(), readSize);
+ d->incomingBuffer.remove(0, readSize);
+ if (readSize < maxSize)
+ {
+ emit logMessage(QXmppLogger::InformationMessage,
+ QString("QXmppRtpChannel::readData missing %1 bytes").arg(QString::number(maxSize - readSize)));
+ memset(data + readSize, 0, maxSize - readSize);
+ }
+ d->incomingStamp += readSize / SAMPLE_BYTES;
+ return maxSize;
+}
+
+/// Returns the RTP channel's payload type.
+///
+/// You can use this to determine the QAudioFormat to use with your
+/// QAudioInput/QAudioOutput.
+
+QXmppJinglePayloadType QXmppRtpChannel::payloadType() const
+{
+ return d->payloadType;
+}
+
+/// Sets the RTP channel's payload type.
+///
+/// \param payloadType
+
+void QXmppRtpChannel::setPayloadType(const QXmppJinglePayloadType &payloadType)
+{
+ d->payloadType = payloadType;
+ if (payloadType.id() == G711u)
+ d->codec = new QXmppG711uCodec(payloadType.clockrate());
+ else if (payloadType.id() == G711a)
+ d->codec = new QXmppG711aCodec(payloadType.clockrate());
+#ifdef QXMPP_USE_SPEEX
+ else if (payloadType.name().toLower() == "speex")
+ d->codec = new QXmppSpeexCodec(payloadType.clockrate());
+#endif
+ else
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QString("QXmppCall got an unknown codec : %1 (%2)")
+ .arg(QString::number(payloadType.id()))
+ .arg(payloadType.name()));
+ return;
+ }
+
+ // size in bytes of an unencoded packet
+ d->outgoingChunk = SAMPLE_BYTES * payloadType.ptime() * payloadType.clockrate() / 1000;
+
+ // initial number of bytes to buffer
+ d->incomingMinimum = d->outgoingChunk * 5;
+ d->incomingMaximum = d->outgoingChunk * 8;
+
+ open(QIODevice::ReadWrite | QIODevice::Unbuffered);
+}
+
+qint64 QXmppRtpChannel::writeData(const char * data, qint64 maxSize)
+{
+ if (!d->codec)
+ {
+ emit logMessage(QXmppLogger::WarningMessage,
+ QLatin1String("QXmppRtpChannel::writeData before codec was set"));
+ return -1;
+ }
+
+ d->outgoingBuffer += QByteArray::fromRawData(data, maxSize);
+ while (d->outgoingBuffer.size() >= d->outgoingChunk)
+ {
+ QByteArray header;
+ QDataStream stream(&header, QIODevice::WriteOnly);
+ quint8 version = RTP_VERSION << 6;
+ stream << version;
+ quint8 marker_type = d->payloadType.id();
+ if (d->outgoingMarker)
+ {
+ marker_type |= 0x80;
+ d->outgoingMarker= false;
+ }
+ stream << marker_type;
+ stream << ++d->outgoingSequence;
+ stream << d->outgoingStamp;
+ const quint32 ssrc = 0;
+ stream << ssrc;
+
+ QByteArray chunk = d->outgoingBuffer.left(d->outgoingChunk);
+ QDataStream input(chunk);
+ input.setByteOrder(QDataStream::LittleEndian);
+ d->outgoingStamp += d->codec->encode(input, stream);
+
+ // FIXME: write data
+#ifdef QXMPP_DEBUG_RTP
+ emit logMessage(QXmppLogger::SentMessage,
+ QString("RTP 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(header.size() - 12)));
+#endif
+ emit sendDatagram(header);
+
+ d->outgoingBuffer.remove(0, chunk.size());
+ }
+
+ d->writtenSinceLastEmit += maxSize;
+ if (!d->signalsEmitted && !signalsBlocked()) {
+ d->signalsEmitted = true;
+ QMetaObject::invokeMethod(this, "emitSignals", Qt::QueuedConnection);
+ }
+
+ return maxSize;
+}