diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:51:15 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:51:15 +0000 |
| commit | deb9d6cb53057ca8b90d10d6a3bdc5dcfd1b3ee4 (patch) | |
| tree | d956bad28e28aadc3c83dbf88b3eddb5e1d9a9f4 /src/base/QXmppRtpChannel.cpp | |
| parent | e8a1ad0cc608f12874ba4bafbd8282fa537ec9fb (diff) | |
| download | qxmpp-deb9d6cb53057ca8b90d10d6a3bdc5dcfd1b3ee4.tar.gz | |
move files common to client/server into "base"
Diffstat (limited to 'src/base/QXmppRtpChannel.cpp')
| -rw-r--r-- | src/base/QXmppRtpChannel.cpp | 1042 |
1 files changed, 1042 insertions, 0 deletions
diff --git a/src/base/QXmppRtpChannel.cpp b/src/base/QXmppRtpChannel.cpp new file mode 100644 index 00000000..df26bc30 --- /dev/null +++ b/src/base/QXmppRtpChannel.cpp @@ -0,0 +1,1042 @@ +/* + * Copyright (C) 2008-2011 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 <cmath> + +#include <QDataStream> +#include <QMetaType> +#include <QTimer> + +#include "QXmppCodec.h" +#include "QXmppJingleIq.h" +#include "QXmppRtpChannel.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 +#endif + +//#define QXMPP_DEBUG_RTP +//#define QXMPP_DEBUG_RTP_BUFFER +#define SAMPLE_BYTES 2 + +const quint8 RTP_VERSION = 0x02; + +/// Parses an RTP packet. +/// +/// \param ba + +bool QXmppRtpPacket::decode(const QByteArray &ba) +{ + if (ba.isEmpty()) + return false; + + // fixed header + quint8 tmp; + QDataStream stream(ba); + stream >> tmp; + version = (tmp >> 6); + const quint8 cc = (tmp >> 1) & 0xf; + const int hlen = 12 + 4 * cc; + if (version != RTP_VERSION || ba.size() < hlen) + return false; + stream >> tmp; + marker = (tmp >> 7); + type = tmp & 0x7f; + stream >> sequence; + stream >> stamp; + stream >> ssrc; + + // contributing source IDs + csrc.clear(); + quint32 src; + for (int i = 0; i < cc; ++i) { + stream >> src; + csrc << src; + } + + // retrieve payload + payload = ba.right(ba.size() - hlen); + return true; +} + +/// Encodes an RTP packet. + +QByteArray QXmppRtpPacket::encode() const +{ + Q_ASSERT(csrc.size() < 16); + + // fixed header + QByteArray ba; + ba.resize(payload.size() + 12 + 4 * csrc.size()); + QDataStream stream(&ba, QIODevice::WriteOnly); + stream << quint8(((version & 0x3) << 6) | + ((csrc.size() & 0xf) << 1)); + stream << quint8((type & 0x7f) | (marker << 7)); + stream << sequence; + stream << stamp; + stream << ssrc; + + // contributing source ids + foreach (const quint32 &src, csrc) + stream << src; + + stream.writeRawData(payload.constData(), payload.size()); + return ba; +} + +/// Returns a string representation of the RTP header. + +QString QXmppRtpPacket::toString() const +{ + return 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(payload.size())); +} + +/// 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, + G723 = 4, + G711a = 8, + G722 = 9, + L16Stereo = 10, + L16Mono = 11, + G728 = 15, + G729 = 18, +}; + +struct ToneInfo +{ + QXmppRtpAudioChannel::Tone tone; + quint32 incomingStart; + quint32 outgoingStart; + bool finished; +}; + +static QPair<int, int> toneFreqs(QXmppRtpAudioChannel::Tone tone) +{ + switch (tone) { + 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(QXmppRtpAudioChannel::Tone tone, int clockrate, quint32 clockTick, qint64 samples) +{ + QPair<int,int> tf = toneFreqs(tone); + const float clockMult = 2.0 * M_PI / float(clockrate); + QByteArray chunk; + chunk.reserve(samples * SAMPLE_BYTES); + QDataStream output(&chunk, QIODevice::WriteOnly); + output.setByteOrder(QDataStream::LittleEndian); + for (quint32 i = 0; i < samples; ++i) { + quint16 val = 16383.0 * (sin(clockMult * clockTick * tf.first) + sin(clockMult * clockTick * tf.second)); + output << val; + clockTick++; + } + return chunk; +} + +class QXmppRtpAudioChannelPrivate +{ +public: + QXmppRtpAudioChannelPrivate(QXmppRtpAudioChannel *qq); + QXmppCodec *codecForPayloadType(const QXmppJinglePayloadType &payloadType); + + // signals + bool signalsEmitted; + qint64 writtenSinceLastEmit; + + // RTP + QHostAddress remoteHost; + quint16 remotePort; + + QByteArray incomingBuffer; + bool incomingBuffering; + QMap<int, QXmppCodec*> incomingCodecs; + int incomingMinimum; + int incomingMaximum; + // position of the head of the incoming buffer, in bytes + qint64 incomingPos; + quint16 incomingSequence; + + QByteArray outgoingBuffer; + quint16 outgoingChunk; + QXmppCodec *outgoingCodec; + bool outgoingMarker; + bool outgoingPayloadNumbered; + quint16 outgoingSequence; + quint32 outgoingStamp; + QTimer *outgoingTimer; + QList<ToneInfo> outgoingTones; + QXmppJinglePayloadType outgoingTonesType; + + quint32 outgoingSsrc; + QXmppJinglePayloadType payloadType; + +private: + QXmppRtpAudioChannel *q; +}; + +QXmppRtpAudioChannelPrivate::QXmppRtpAudioChannelPrivate(QXmppRtpAudioChannel *qq) + : signalsEmitted(false), + writtenSinceLastEmit(0), + incomingBuffering(true), + incomingMinimum(0), + incomingMaximum(0), + incomingPos(0), + incomingSequence(0), + outgoingCodec(0), + outgoingMarker(true), + outgoingPayloadNumbered(false), + outgoingSequence(1), + outgoingStamp(0), + outgoingSsrc(0), + q(qq) +{ + qRegisterMetaType<QXmppRtpAudioChannel::Tone>("QXmppRtpAudioChannel::Tone"); + outgoingSsrc = qrand(); +} + +/// Returns the audio codec for the given payload type. +/// + +QXmppCodec *QXmppRtpAudioChannelPrivate::codecForPayloadType(const QXmppJinglePayloadType &payloadType) +{ + if (payloadType.id() == G711u) + return new QXmppG711uCodec(payloadType.clockrate()); + else if (payloadType.id() == G711a) + return new QXmppG711aCodec(payloadType.clockrate()); +#ifdef QXMPP_USE_SPEEX + else if (payloadType.name().toLower() == "speex") + return new QXmppSpeexCodec(payloadType.clockrate()); +#endif + return 0; +} + +/// Creates a new RTP audio channel. +/// +/// \param parent + +QXmppRtpAudioChannel::QXmppRtpAudioChannel(QObject *parent) + : QIODevice(parent) +{ + 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 + payload.setId(96); + payload.setChannels(1); + payload.setName("speex"); + payload.setClockrate(8000); + m_outgoingPayloadTypes << payload; +#endif + + payload.setId(G711u); + payload.setChannels(1); + payload.setName("PCMU"); + payload.setClockrate(8000); + m_outgoingPayloadTypes << payload; + + payload.setId(G711a); + payload.setChannels(1); + payload.setName("PCMA"); + payload.setClockrate(8000); + 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); + payload.setParameters(parameters); + m_outgoingPayloadTypes << payload; +} + +/// Destroys an RTP audio channel. +/// + +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 QXmppRtpAudioChannel::bytesAvailable() const +{ + return d->incomingBuffer.size(); +} + +/// Closes the RTP channel. +/// + +void QXmppRtpAudioChannel::close() +{ + d->outgoingTimer->stop(); + QIODevice::close(); +} + +/// Processes an incoming RTP packet. +/// +/// \param ba + +void QXmppRtpAudioChannel::datagramReceived(const QByteArray &ba) +{ + QXmppRtpPacket packet; + if (!packet.decode(ba)) + return; + +#ifdef QXMPP_DEBUG_RTP + logReceived(packet.toString()); +#endif + + // check sequence number +#if 0 + if (d->incomingSequence && packet.sequence != d->incomingSequence + 1) + warning(QString("RTP packet seq %1 is out of order, previous was %2") + .arg(QString::number(packet.sequence)) + .arg(QString::number(d->incomingSequence))); +#endif + d->incomingSequence = packet.sequence; + + // get or create codec + QXmppCodec *codec = 0; + if (!d->incomingCodecs.contains(packet.type)) { + foreach (const QXmppJinglePayloadType &payload, m_incomingPayloadTypes) { + if (packet.type == payload.id()) { + codec = d->codecForPayloadType(payload); + break; + } + } + if (codec) + d->incomingCodecs.insert(packet.type, codec); + else + warning(QString("Could not find codec for RTP type %1").arg(QString::number(packet.type))); + } else { + codec = d->incomingCodecs.value(packet.type); + } + if (!codec) + return; + + // determine packet's position in the buffer (in bytes) + qint64 packetOffset = 0; + if (!d->incomingBuffer.isEmpty()) { + packetOffset = packet.stamp * SAMPLE_BYTES - d->incomingPos; + if (packetOffset < 0) { +#ifdef QXMPP_DEBUG_RTP_BUFFER + warning(QString("RTP packet stamp %1 is too old, buffer start is %2") + .arg(QString::number(packet.stamp)) + .arg(QString::number(d->incomingPos))); +#endif + return; + } + } else { + d->incomingPos = packet.stamp * SAMPLE_BYTES + (d->incomingPos % SAMPLE_BYTES); + } + + // allocate space for new packet + // FIXME: this is wrong, we want the decoded data size! + qint64 packetLength = packet.payload.size(); + if (packetOffset + packetLength > d->incomingBuffer.size()) + d->incomingBuffer += QByteArray(packetOffset + packetLength - d->incomingBuffer.size(), 0); + QDataStream input(packet.payload); + QDataStream output(&d->incomingBuffer, QIODevice::WriteOnly); + output.device()->seek(packetOffset); + output.setByteOrder(QDataStream::LittleEndian); + codec->decode(input, output); + + // check whether we are running late + if (d->incomingBuffer.size() > d->incomingMaximum) + { + qint64 droppedSize = d->incomingBuffer.size() - d->incomingMinimum; + const int remainder = droppedSize % SAMPLE_BYTES; + if (remainder) + droppedSize -= remainder; +#ifdef QXMPP_DEBUG_RTP_BUFFER + warning(QString("Incoming RTP buffer is too full, dropping %1 bytes") + .arg(QString::number(droppedSize))); +#endif + d->incomingBuffer.remove(0, droppedSize); + d->incomingPos += droppedSize; + } + // check whether we have filled the initial buffer + if (d->incomingBuffer.size() >= d->incomingMinimum) + d->incomingBuffering = false; + if (!d->incomingBuffering) + emit readyRead(); +} + +void QXmppRtpAudioChannel::emitSignals() +{ + emit bytesWritten(d->writtenSinceLastEmit); + d->writtenSinceLastEmit = 0; + d->signalsEmitted = false; +} + +/// Returns true, as the RTP channel is a sequential device. +/// + +bool QXmppRtpAudioChannel::isSequential() const +{ + return true; +} + +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) + { + // FIXME: if we are asked for a non-integer number of samples, + // we will return junk on next read as we don't increment d->incomingPos + 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) + { +#ifdef QXMPP_DEBUG_RTP + debug(QString("QXmppRtpAudioChannel::readData missing %1 bytes").arg(QString::number(maxSize - readSize))); +#endif + memset(data + readSize, 0, maxSize - readSize); + } + + // add local DTMF echo + if (!d->outgoingTones.isEmpty()) { + const int headOffset = d->incomingPos % SAMPLE_BYTES; + const int samples = (headOffset + maxSize + SAMPLE_BYTES - 1) / SAMPLE_BYTES; + const QByteArray chunk = renderTone( + d->outgoingTones[0].tone, + d->payloadType.clockrate(), + d->incomingPos / SAMPLE_BYTES - d->outgoingTones[0].incomingStart, + samples); + memcpy(data, chunk.constData() + headOffset, maxSize); + } + + d->incomingPos += maxSize; + return maxSize; +} + +/// Returns the RTP channel's payload type. +/// +/// You can use this to determine the QAudioFormat to use with your +/// QAudioInput/QAudioOutput. + +QXmppJinglePayloadType QXmppRtpAudioChannel::payloadType() const +{ + return d->payloadType; +} + +void QXmppRtpAudioChannel::payloadTypesChanged() +{ + // 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 QXmppRtpAudioChannel::pos() const +{ + return d->incomingPos; +} + +/// Seeks in the received audio data. +/// +/// Seeking backwards will result in empty samples being added at the start +/// of the buffer. +/// +/// \param pos + +bool QXmppRtpAudioChannel::seek(qint64 pos) +{ + qint64 delta = pos - d->incomingPos; + if (delta < 0) + d->incomingBuffer.prepend(QByteArray(-delta, 0)); + else + d->incomingBuffer.remove(0, delta); + d->incomingPos = pos; + return true; +} + +/// Starts sending the specified DTMF tone. +/// +/// \param tone + +void QXmppRtpAudioChannel::startTone(QXmppRtpAudioChannel::Tone tone) +{ + ToneInfo info; + info.tone = tone; + info.incomingStart = d->incomingPos / SAMPLE_BYTES; + info.outgoingStart = d->outgoingStamp; + info.finished = false; + d->outgoingTones << info; +} + +/// Stops sending the specified DTMF tone. +/// +/// \param tone + +void QXmppRtpAudioChannel::stopTone(QXmppRtpAudioChannel::Tone tone) +{ + for (int i = 0; i < d->outgoingTones.size(); ++i) { + if (d->outgoingTones[i].tone == tone) { + d->outgoingTones[i].finished = true; + break; + } + } +} + +qint64 QXmppRtpAudioChannel::writeData(const char * data, qint64 maxSize) +{ + if (!d->outgoingCodec) { + warning("QXmppRtpAudioChannel::writeData before codec was set"); + return -1; + } + + d->outgoingBuffer += QByteArray::fromRawData(data, maxSize); + + // start sending audio chunks + if (!d->outgoingTimer->isActive()) + d->outgoingTimer->start(); + + return maxSize; +} + +void QXmppRtpAudioChannel::writeDatagram() +{ + // read audio chunk + QByteArray chunk; + if (d->outgoingBuffer.size() < d->outgoingChunk) { +#ifdef QXMPP_DEBUG_RTP_BUFFER + warning("Outgoing RTP buffer is starved"); +#endif + chunk = QByteArray(d->outgoingChunk, 0); + } else { + chunk = d->outgoingBuffer.left(d->outgoingChunk); + d->outgoingBuffer.remove(0, d->outgoingChunk); + } + + bool sendAudio = true; + if (!d->outgoingTones.isEmpty()) { + const quint32 packetTicks = (d->payloadType.clockrate() * d->payloadType.ptime()) / 1000; + const ToneInfo info = d->outgoingTones[0]; + + if (d->outgoingTonesType.id()) { + // send RFC 2833 DTMF + QXmppRtpPacket packet; + packet.version = RTP_VERSION; + packet.marker = (info.outgoingStart == d->outgoingStamp); + packet.type = d->outgoingTonesType.id(); + packet.sequence = d->outgoingSequence; + packet.stamp = info.outgoingStart; + packet.ssrc = d->outgoingSsrc; + + QDataStream output(&packet.payload, QIODevice::WriteOnly); + output << quint8(info.tone); + output << quint8(info.finished ? 0x80 : 0x00); + output << quint16(d->outgoingStamp + packetTicks - info.outgoingStart); +#ifdef QXMPP_DEBUG_RTP + logSent(packet.toString()); +#endif + emit sendDatagram(packet.encode()); + d->outgoingSequence++; + d->outgoingStamp += packetTicks; + + sendAudio = false; + } else { + // generate in-band DTMF + chunk = renderTone(info.tone, d->payloadType.clockrate(), d->outgoingStamp - info.outgoingStart, packetTicks); + } + + // if the tone is finished, remove it + if (info.finished) + d->outgoingTones.removeFirst(); + } + + if (sendAudio) { + // send audio data + QXmppRtpPacket packet; + packet.version = RTP_VERSION; + if (d->outgoingMarker) + { + packet.marker = true; + d->outgoingMarker = false; + } else { + packet.marker = false; + } + packet.type = d->payloadType.id(); + packet.sequence = d->outgoingSequence; + packet.stamp = d->outgoingStamp; + packet.ssrc = d->outgoingSsrc; + + // encode audio chunk + QDataStream input(chunk); + input.setByteOrder(QDataStream::LittleEndian); + QDataStream output(&packet.payload, QIODevice::WriteOnly); + const qint64 packetTicks = d->outgoingCodec->encode(input, output); + +#ifdef QXMPP_DEBUG_RTP + logSent(packet.toString()); +#endif + emit sendDatagram(packet.encode()); + d->outgoingSequence++; + d->outgoingStamp += packetTicks; + } + + // queue signals + d->writtenSinceLastEmit += chunk.size(); + if (!d->signalsEmitted && !signalsBlocked()) { + d->signalsEmitted = true; + QMetaObject::invokeMethod(this, "emitSignals", Qt::QueuedConnection); + } +} + +/** Constructs a null video frame. + */ +QXmppVideoFrame::QXmppVideoFrame() + : m_bytesPerLine(0), + m_height(0), + m_mappedBytes(0), + m_pixelFormat(Format_Invalid), + m_width(0) +{ +} + +/** Constructs a video frame of the given pixel format and size in pixels. + * + * @param bytes + * @param size + * @param bytesPerLine + * @param format + */ +QXmppVideoFrame::QXmppVideoFrame(int bytes, const QSize &size, int bytesPerLine, PixelFormat format) + : m_bytesPerLine(bytesPerLine), + m_height(size.height()), + m_mappedBytes(bytes), + m_pixelFormat(format), + m_width(size.width()) +{ + m_data.resize(bytes); +} + +uchar *QXmppVideoFrame::bits() +{ + return (uchar*)m_data.data(); +} + +const uchar *QXmppVideoFrame::bits() const +{ + return (const uchar*)m_data.constData(); +} + +/** Returns the number of bytes in a scan line. + */ +int QXmppVideoFrame::bytesPerLine() const +{ + return m_bytesPerLine; +} + +/** Returns the height of a video frame. + */ +int QXmppVideoFrame::height() const +{ + return m_height; +} + +/** Returns true if the frame is valid. + */ +bool QXmppVideoFrame::isValid() const +{ + return m_pixelFormat != Format_Invalid && + m_height > 0 && m_width > 0 && + m_mappedBytes > 0; +} + +/** Returns the number of bytes occupied by the mapped frame data. + */ +int QXmppVideoFrame::mappedBytes() const +{ + return m_mappedBytes; +} + +/** Returns the color format of a video frame. + */ +QXmppVideoFrame::PixelFormat QXmppVideoFrame::pixelFormat() const +{ + return m_pixelFormat; +} + +/** Returns the size of a video frame. + */ +QSize QXmppVideoFrame::size() const +{ + return QSize(m_width, m_height); +} + +/** Returns the width of a video frame. + */ +int QXmppVideoFrame::width() const +{ + return m_width; +} + +class QXmppRtpVideoChannelPrivate +{ +public: + QXmppRtpVideoChannelPrivate(); + QMap<int, QXmppVideoDecoder*> decoders; + 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.setFrameRate(15.0); + d->outgoingFormat.setFrameSize(QSize(320, 240)); + d->outgoingFormat.setPixelFormat(QXmppVideoFrame::Format_YUYV); + + // set supported codecs + QXmppVideoEncoder *encoder; + QXmppJinglePayloadType payload; + Q_UNUSED(encoder); + Q_UNUSED(payload); + +#ifdef QXMPP_USE_VPX + encoder = new QXmppVpxEncoder; + encoder->setFormat(d->outgoingFormat); + payload.setId(96); + payload.setName("vp8"); + payload.setClockrate(90000); + payload.setParameters(encoder->parameters()); + m_outgoingPayloadTypes << payload; + delete encoder; +#endif + +#ifdef QXMPP_USE_THEORA + encoder = new QXmppTheoraEncoder; + encoder->setFormat(d->outgoingFormat); + payload.setId(97); + 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) +{ + QXmppRtpPacket packet; + if (!packet.decode(ba)) + return; + +#ifdef QXMPP_DEBUG_RTP + logReceived(packet.toString()); +#endif + + // get codec + QXmppVideoDecoder *decoder = d->decoders.value(packet.type); + if (!decoder) + return; + d->frames << decoder->handlePacket(packet); +} + +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; + if (false) + {} +#ifdef QXMPP_USE_THEORA + else if (payload.name().toLower() == "theora") + decoder = new QXmppTheoraDecoder; +#endif +#ifdef QXMPP_USE_VPX + else if (payload.name().toLower() == "vp8") + decoder = new QXmppVpxDecoder; +#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; + if (false) + {} +#ifdef QXMPP_USE_THEORA + else if (payload.name().toLower() == "theora") + encoder = new QXmppTheoraEncoder; +#endif +#ifdef QXMPP_USE_VPX + else if (payload.name().toLower() == "vp8") { + encoder = new QXmppVpxEncoder; + } +#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::writeFrame before codec was set"); + return; + } + + QXmppRtpPacket packet; + packet.version = RTP_VERSION; + packet.marker = false; + packet.type = d->outgoingId; + packet.ssrc = d->outgoingSsrc; + foreach (const QByteArray &payload, d->encoder->handleFrame(frame)) { + packet.sequence = d->outgoingSequence++; + packet.stamp = d->outgoingStamp; + packet.payload = payload; +#ifdef QXMPP_DEBUG_RTP + logSent(packet.toString()); +#endif + emit sendDatagram(packet.encode()); + } + d->outgoingStamp += 1; +} + |
