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/QXmppRtpChannel.cpp | |
| parent | 807a04ef3b864ac8f0e0f170176db7dd629f742a (diff) | |
| download | qxmpp-e1c4a3b40604db10037b59b482ff219609354165.tar.gz | |
overhaul RTP / jingle stack to allow video support
Diffstat (limited to 'src/QXmppRtpChannel.cpp')
| -rw-r--r-- | src/QXmppRtpChannel.cpp | 569 |
1 files changed, 409 insertions, 160 deletions
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++; + } +} + |
