diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2011-04-23 15:47:51 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2011-04-23 15:47:51 +0000 |
| commit | 02ae50991c9d00f171bcc1ef105cbe52f37b39f7 (patch) | |
| tree | 38b366313f4e91c0e2612f5f533d00dbee2b72c4 /src | |
| parent | d0928bc5a77468731fc2067bfc1802e70932eb97 (diff) | |
| download | qxmpp-02ae50991c9d00f171bcc1ef105cbe52f37b39f7.tar.gz | |
* add a QXmppPacket class to avoid repeating RTP parsing code
* improve RTP video support
Diffstat (limited to 'src')
| -rw-r--r-- | src/QXmppCodec.cpp | 166 | ||||
| -rw-r--r-- | src/QXmppCodec.h | 4 | ||||
| -rw-r--r-- | src/QXmppRtpChannel.cpp | 370 | ||||
| -rw-r--r-- | src/QXmppRtpChannel.h | 70 |
4 files changed, 439 insertions, 171 deletions
diff --git a/src/QXmppCodec.cpp b/src/QXmppCodec.cpp index 434a8526..7daa00d5 100644 --- a/src/QXmppCodec.cpp +++ b/src/QXmppCodec.cpp @@ -394,18 +394,64 @@ bool QXmppTheoraDecoderPrivate::decodeFrame(const QByteArray &buffer, QXmppVideo return false; } - for (int i = 0; i < 3; ++i) { - QXmppVideoPlane *plane = &frame->planes[i]; - plane->width = ycbcr_buffer[i].width; - plane->height = ycbcr_buffer[i].height; - plane->stride = ycbcr_buffer[i].stride; - plane->data.resize(plane->stride * plane->height); - memcpy(plane->data.data(), ycbcr_buffer[i].data, plane->data.size()); + if (info.pixel_fmt == TH_PF_420) { + if (!frame->isValid()) { + const int bytes = ycbcr_buffer[0].stride * ycbcr_buffer[0].height + + ycbcr_buffer[1].stride * ycbcr_buffer[1].height + + ycbcr_buffer[2].stride * ycbcr_buffer[2].height; + + *frame = QXmppVideoFrame(bytes, + QSize(ycbcr_buffer[0].width, ycbcr_buffer[0].height), + ycbcr_buffer[0].stride, + QXmppVideoFrame::Format_YUV420P); + } + uchar *output = frame->bits(); + for (int i = 0; i < 3; ++i) { + const int length = ycbcr_buffer[i].stride * ycbcr_buffer[i].height; + memcpy(output, ycbcr_buffer[i].data, length); + output += length; + } + return true; + } else if (info.pixel_fmt == TH_PF_422) { + if (!frame->isValid()) { + const int bytes = ycbcr_buffer[0].width * ycbcr_buffer[0].height * 2; + + *frame = QXmppVideoFrame(bytes, + QSize(ycbcr_buffer[0].width, ycbcr_buffer[0].height), + ycbcr_buffer[0].width * 2, + QXmppVideoFrame::Format_YUYV); + } + + // YUV 4:2:2 packing + const int width = ycbcr_buffer[0].width; + const int height = ycbcr_buffer[0].height; + const int y_stride = ycbcr_buffer[0].stride; + const int c_stride = ycbcr_buffer[1].stride; + const uchar *y_row = ycbcr_buffer[0].data; + const uchar *cb_row = ycbcr_buffer[1].data; + const uchar *cr_row = ycbcr_buffer[2].data; + uchar *output = frame->bits(); + for (int y = 0; y < height; ++y) { + const uchar *y_ptr = y_row; + const uchar *cb_ptr = cb_row; + const uchar *cr_ptr = cr_row; + for (int x = 0; x < width; x += 2) { + *(output++) = *(y_ptr++); + *(output++) = *(cb_ptr++); + *(output++) = *(y_ptr++); + *(output++) = *(cr_ptr++); + } + y_row += y_stride; + cb_row += c_stride; + cr_row += c_stride; + } + return true; + } else { + qWarning("Theora decoder received an unsupported frame format"); + return false; } - return true; } - QXmppTheoraDecoder::QXmppTheoraDecoder() { d = new QXmppTheoraDecoderPrivate; @@ -430,17 +476,23 @@ QXmppVideoFormat QXmppTheoraDecoder::format() const { QXmppVideoFormat format; format.setFrameSize(QSize(d->info.frame_width, d->info.frame_height)); - if (d->info.pixel_fmt == TH_PF_420) { + if (d->info.pixel_fmt == TH_PF_420) format.setPixelFormat(QXmppVideoFrame::Format_YUV420P); - } + else if (d->info.pixel_fmt == TH_PF_422) + format.setPixelFormat(QXmppVideoFrame::Format_YUYV); + else + format.setPixelFormat(QXmppVideoFrame::Format_Invalid); + if (d->info.fps_denominator > 0) + format.setFrameRate(qreal(d->info.fps_numerator) / qreal(d->info.fps_denominator)); return format; } -QList<QXmppVideoFrame> QXmppTheoraDecoder::handlePacket(QDataStream &stream) +QList<QXmppVideoFrame> QXmppTheoraDecoder::handlePacket(const QByteArray &ba) { QList<QXmppVideoFrame> frames; // theora deframing: draft-ietf-avt-rtp-theora-00 + QDataStream stream(ba); quint32 theora_header; stream >> theora_header; @@ -472,6 +524,7 @@ QList<QXmppVideoFrame> QXmppTheoraDecoder::handlePacket(QDataStream &stream) stream.readRawData(d->packetBuffer.data(), packetLength); if (d->ctx && d->decodeFrame(d->packetBuffer, &frame)) frames << frame; + d->packetBuffer.resize(0); } } else { // fragments @@ -596,7 +649,7 @@ bool QXmppTheoraDecoder::setParameters(const QMap<QString, QString> ¶meters) d->info.target_bitrate, d->info.quality, d->info.keyframe_granule_shift); - if (d->info.pixel_fmt != TH_PF_420) { + if (d->info.pixel_fmt != TH_PF_420 && d->info.pixel_fmt != TH_PF_422) { qWarning("Theora frames have an unsupported pixel format %d", d->info.pixel_fmt); return false; } @@ -619,7 +672,9 @@ public: th_info info; th_setup_info *setup_info; th_enc_ctx *ctx; + th_ycbcr_buffer ycbcr_buffer; + QByteArray buffer; QByteArray configuration; QByteArray ident; }; @@ -659,12 +714,13 @@ QXmppTheoraEncoder::~QXmppTheoraEncoder() bool QXmppTheoraEncoder::setFormat(const QXmppVideoFormat &format) { - if (format.pixelFormat() == QXmppVideoFrame::Format_YUV420P) { - d->info.pixel_fmt = TH_PF_420; - } else { + const QXmppVideoFrame::PixelFormat pixelFormat = format.pixelFormat(); + if ((pixelFormat != QXmppVideoFrame::Format_YUV420P) && + (pixelFormat != QXmppVideoFrame::Format_YUYV)) { qWarning("Theora decoder does not support the given format"); return false; } + d->info.frame_width = format.frameSize().width(); d->info.frame_height = format.frameSize().height(); d->info.pic_height = format.frameSize().height(); @@ -676,10 +732,36 @@ bool QXmppTheoraEncoder::setFormat(const QXmppVideoFormat &format) d->info.quality = 48; d->info.keyframe_granule_shift = 6; - // frame rate - d->info.fps_numerator = 30; + // FIXME: how do we handle floating point frame rates? + d->info.fps_numerator = format.frameRate(); d->info.fps_denominator = 1; + if (pixelFormat == QXmppVideoFrame::Format_YUV420P) { + d->info.pixel_fmt = TH_PF_420; + d->ycbcr_buffer[0].width = d->info.frame_width; + d->ycbcr_buffer[0].height = d->info.frame_height; + d->ycbcr_buffer[1].width = d->ycbcr_buffer[0].width / 2; + d->ycbcr_buffer[1].height = d->ycbcr_buffer[0].height / 2; + d->ycbcr_buffer[2].width = d->ycbcr_buffer[1].width; + d->ycbcr_buffer[2].height = d->ycbcr_buffer[1].height; + } else if (pixelFormat == QXmppVideoFrame::Format_YUYV) { + d->info.pixel_fmt = TH_PF_422; + d->buffer.resize(d->info.frame_width * d->info.frame_height * 2); + d->ycbcr_buffer[0].width = d->info.frame_width; + d->ycbcr_buffer[0].height = d->info.frame_height; + d->ycbcr_buffer[0].stride = d->info.frame_width; + d->ycbcr_buffer[0].data = (uchar*) d->buffer.data(); + d->ycbcr_buffer[1].width = d->ycbcr_buffer[0].width / 2; + d->ycbcr_buffer[1].height = d->ycbcr_buffer[0].height; + d->ycbcr_buffer[1].stride = d->ycbcr_buffer[0].stride / 2; + d->ycbcr_buffer[1].data = d->ycbcr_buffer[0].data + d->ycbcr_buffer[0].stride * d->ycbcr_buffer[0].height; + d->ycbcr_buffer[2].width = d->ycbcr_buffer[1].width; + d->ycbcr_buffer[2].height = d->ycbcr_buffer[1].height; + d->ycbcr_buffer[2].stride = d->ycbcr_buffer[1].stride; + d->ycbcr_buffer[2].data = d->ycbcr_buffer[1].data + d->ycbcr_buffer[1].stride * d->ycbcr_buffer[1].height; + } + + // create encoder if (d->ctx) { th_encode_free(d->ctx); d->ctx = 0; @@ -741,15 +823,41 @@ QList<QByteArray> QXmppTheoraEncoder::handleFrame(const QXmppVideoFrame &frame) QList<QByteArray> packets; const int PACKET_MAX = 1388; - th_ycbcr_buffer ycbcr_buffer; - for (int i = 0; i < 3; ++i) { - const QXmppVideoPlane *plane = &frame.planes[i]; - ycbcr_buffer[i].width = plane->width; - ycbcr_buffer[i].height = plane->height; - ycbcr_buffer[i].stride = plane->stride; - ycbcr_buffer[i].data = (unsigned char*)plane->data.constData(); + if (!d->ctx) + return packets; + + if (d->info.pixel_fmt == TH_PF_420) { + d->ycbcr_buffer[0].stride = frame.bytesPerLine(); + d->ycbcr_buffer[0].data = (unsigned char*) frame.bits(); + d->ycbcr_buffer[1].stride = d->ycbcr_buffer[0].stride / 2; + d->ycbcr_buffer[1].data = d->ycbcr_buffer[0].data + d->ycbcr_buffer[0].stride * d->ycbcr_buffer[0].height; + d->ycbcr_buffer[2].stride = d->ycbcr_buffer[1].stride; + d->ycbcr_buffer[2].data = d->ycbcr_buffer[1].data + d->ycbcr_buffer[1].stride * d->ycbcr_buffer[1].height; + } else if (d->info.pixel_fmt = TH_PF_422) { + // YUV 4:2:2 unpacking + const int width = frame.width(); + const int height = frame.height(); + const int stride = frame.bytesPerLine(); + const uchar *row = frame.bits(); + uchar *y_out = d->ycbcr_buffer[0].data; + uchar *cb_out = d->ycbcr_buffer[1].data; + uchar *cr_out = d->ycbcr_buffer[2].data; + for (int y = 0; y < height; ++y) { + const uchar *ptr = row; + for (int x = 0; x < width; x += 2) { + *(y_out++) = *(ptr++); + *(cb_out++) = *(ptr++); + *(y_out++) = *(ptr++); + *(cr_out++) = *(ptr++); + } + row += stride; + } + } else { + qWarning("Theora encoder received an unsupported frame format"); + return packets; } - if (th_encode_ycbcr_in(d->ctx, ycbcr_buffer) != 0) { + + if (th_encode_ycbcr_in(d->ctx, d->ycbcr_buffer) != 0) { qWarning("Theora encoder could not handle frame"); return packets; } @@ -767,7 +875,7 @@ QList<QByteArray> QXmppTheoraEncoder::handleFrame(const QXmppVideoFrame &frame) if (size <= PACKET_MAX) { // no fragmentation stream.device()->reset(); - payload.clear(); + payload.resize(0); d->writeFrame(stream, theora_frag, 1, data, size); packets << payload; } else { @@ -775,12 +883,12 @@ QList<QByteArray> QXmppTheoraEncoder::handleFrame(const QXmppVideoFrame &frame) theora_frag = 1; while (size) { const int length = qMin(PACKET_MAX, size); - payload.clear(); stream.device()->reset(); + payload.resize(0); d->writeFrame(stream, theora_frag, 0, data, length); data += length; size -= length; - theora_frag = (size < length) ? 3 : 2; + theora_frag = (size > PACKET_MAX) ? 2 : 3; packets << payload; } } diff --git a/src/QXmppCodec.h b/src/QXmppCodec.h index 4b31fcff..1d52cdbd 100644 --- a/src/QXmppCodec.h +++ b/src/QXmppCodec.h @@ -110,7 +110,7 @@ class QXmppVideoDecoder { public: virtual QXmppVideoFormat format() const = 0; - virtual QList<QXmppVideoFrame> handlePacket(QDataStream &input) = 0; + virtual QList<QXmppVideoFrame> handlePacket(const QByteArray &ba) = 0; virtual bool setParameters(const QMap<QString, QString> ¶meters) = 0; }; @@ -133,7 +133,7 @@ public: ~QXmppTheoraDecoder(); QXmppVideoFormat format() const; - QList<QXmppVideoFrame> handlePacket(QDataStream &input); + QList<QXmppVideoFrame> handlePacket(const QByteArray &ba); bool setParameters(const QMap<QString, QString> ¶meters); private: diff --git a/src/QXmppRtpChannel.cpp b/src/QXmppRtpChannel.cpp index 37c1847d..732246eb 100644 --- a/src/QXmppRtpChannel.cpp +++ b/src/QXmppRtpChannel.cpp @@ -36,10 +36,86 @@ #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() @@ -315,87 +391,68 @@ void QXmppRtpAudioChannel::close() void QXmppRtpAudioChannel::datagramReceived(const QByteArray &ba) { - if (ba.size() < 12 || (quint8(ba.at(0)) >> 6) != RTP_VERSION) - { - warning("QXmppRtpAudioChannel::datagramReceived got an invalid RTP packet"); + QXmppRtpPacket packet; + if (!packet.decode(ba)) 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 audio 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))); + logReceived(packet.toString()); #endif // check sequence number #if 0 - if (d->incomingSequence && sequence != d->incomingSequence + 1) + if (d->incomingSequence && packet.sequence != d->incomingSequence + 1) warning(QString("RTP packet seq %1 is out of order, previous was %2") - .arg(QString::number(sequence)) + .arg(QString::number(packet.sequence)) .arg(QString::number(d->incomingSequence))); #endif - d->incomingSequence = sequence; + d->incomingSequence = packet.sequence; // get or create codec QXmppCodec *codec = 0; - if (!d->incomingCodecs.contains(type)) { + if (!d->incomingCodecs.contains(packet.type)) { foreach (const QXmppJinglePayloadType &payload, m_incomingPayloadTypes) { - if (type == payload.id()) { + if (packet.type == payload.id()) { codec = d->codecForPayloadType(payload); break; } } if (codec) - d->incomingCodecs.insert(type, codec); + d->incomingCodecs.insert(packet.type, codec); else - warning(QString("Could not find codec for RTP type %1").arg(QString::number(type))); + warning(QString("Could not find codec for RTP type %1").arg(QString::number(packet.type))); } else { - codec = d->incomingCodecs.value(type); + 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 = stamp * SAMPLE_BYTES - d->incomingPos; - if (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(stamp)) + .arg(QString::number(packet.stamp)) .arg(QString::number(d->incomingPos))); +#endif return; } } else { - d->incomingPos = stamp * SAMPLE_BYTES + (d->incomingPos % SAMPLE_BYTES); + 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(stream, output); + codec->decode(input, output); // check whether we are running late if (d->incomingBuffer.size() > d->incomingMaximum) @@ -404,8 +461,10 @@ void QXmppRtpAudioChannel::datagramReceived(const QByteArray &ba) 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; } @@ -596,7 +655,9 @@ 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); @@ -610,30 +671,22 @@ void QXmppRtpAudioChannel::writeDatagram() if (d->outgoingTonesType.id()) { // send RFC 2833 DTMF - QByteArray header; - QDataStream stream(&header, QIODevice::WriteOnly); - quint8 marker_type = d->outgoingTonesType.id(); - if (info.outgoingStart == d->outgoingStamp) - marker_type |= 0x80; - - stream << quint8(RTP_VERSION << 6); - stream << marker_type; - stream << d->outgoingSequence; - stream << info.outgoingStart; - stream << d->outgoingSsrc; - - stream << quint8(info.tone); - stream << quint8(info.finished ? 0x80 : 0x00); - stream << quint16(d->outgoingStamp + packetTicks - info.outgoingStart); + 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(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), - QString::number(marker_type & 0x7f), - QString::number(header.size() - 12))); + logSent(packet.toString()); #endif - emit sendDatagram(header); + emit sendDatagram(packet.encode()); d->outgoingSequence++; d->outgoingStamp += packetTicks; @@ -650,34 +703,30 @@ void QXmppRtpAudioChannel::writeDatagram() if (sendAudio) { // send audio data - QByteArray header; - QDataStream stream(&header, QIODevice::WriteOnly); - quint8 marker_type = d->payloadType.id(); + QXmppRtpPacket packet; + packet.version = RTP_VERSION; if (d->outgoingMarker) { - marker_type |= 0x80; - d->outgoingMarker= false; + packet.marker = true; + d->outgoingMarker = false; + } else { + packet.marker = false; } - stream << quint8(RTP_VERSION << 6); - stream << marker_type; - stream << d->outgoingSequence; - stream << d->outgoingStamp; - stream << d->outgoingSsrc; + 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); - const qint64 packetTicks = d->outgoingCodec->encode(input, stream); + QDataStream output(&packet.payload, QIODevice::WriteOnly); + const qint64 packetTicks = d->outgoingCodec->encode(input, output); #ifdef QXMPP_DEBUG_RTP - 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), - QString::number(marker_type & 0x7f), - QString::number(header.size() - 12))); + logSent(packet.toString()); #endif - emit sendDatagram(header); + emit sendDatagram(packet.encode()); d->outgoingSequence++; d->outgoingStamp += packetTicks; } @@ -690,12 +739,100 @@ void QXmppRtpAudioChannel::writeDatagram() } } +/** 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; - QList<QXmppVideoEncoder*> encoders; QXmppVideoEncoder *encoder; QList<QXmppVideoFrame> frames; @@ -721,8 +858,9 @@ 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_YUV420P); + d->outgoingFormat.setPixelFormat(QXmppVideoFrame::Format_YUYV); // set supported codecs #ifdef QXMPP_USE_THEORA @@ -760,41 +898,19 @@ void QXmppRtpVideoChannel::close() 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"); + QXmppRtpPacket packet; + if (!packet.decode(ba)) 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))); + logReceived(packet.toString()); #endif // get codec - QXmppVideoDecoder *decoder = d->decoders.value(type); + QXmppVideoDecoder *decoder = d->decoders.value(packet.type); if (!decoder) return; - d->frames << decoder->handlePacket(stream); + d->frames << decoder->handlePacket(packet.payload); } QXmppVideoFormat QXmppRtpVideoChannel::decoderFormat() const @@ -875,32 +991,24 @@ QList<QXmppVideoFrame> QXmppRtpVideoChannel::readFrames() void QXmppRtpVideoChannel::writeFrame(const QXmppVideoFrame &frame) { if (!d->encoder) { - warning("QXmppRtpVideoChannel::writeData before codec was set"); + warning("QXmppRtpVideoChannel::writeFrame before codec was set"); return; } - const quint8 marker_type = d->outgoingId; - QByteArray packet; + 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.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))); + packet.sequence = d->outgoingSequence++; + packet.stamp = d->outgoingStamp; + packet.payload = payload; +#ifdef QXMPP_DEBUG_RTP + logSent(packet.toString()); #endif - - emit sendDatagram(packet); - d->outgoingSequence++; + emit sendDatagram(packet.encode()); } + d->outgoingStamp += 1; } diff --git a/src/QXmppRtpChannel.h b/src/QXmppRtpChannel.h index 9b02aa2a..0407520d 100644 --- a/src/QXmppRtpChannel.h +++ b/src/QXmppRtpChannel.h @@ -35,6 +35,26 @@ class QXmppJinglePayloadType; class QXmppRtpAudioChannelPrivate; class QXmppRtpVideoChannelPrivate; +/// \brief The QXmppRtpPacket class represents an RTP packet. +/// + +class QXmppRtpPacket +{ +public: + bool decode(const QByteArray &ba); + QByteArray encode() const; + QString toString() const; + + quint8 version; + bool marker; + quint8 type; + quint32 ssrc; + QList<quint32> csrc; + quint16 sequence; + quint32 stamp; + QByteArray payload; +}; + class QXmppRtpChannel { public: @@ -147,28 +167,59 @@ private: QXmppRtpAudioChannelPrivate * d; }; -class QXmppVideoPlane -{ -public: - QByteArray data; - int width; - int height; - int stride; -}; +/// \brief The QXmppVideoFrame class provides a representation of a frame of video data. +/// +/// \note THIS API IS NOT FINALIZED YET class QXmppVideoFrame { public: enum PixelFormat { + Format_Invalid = 0, Format_YUV420P = 18, + Format_YUYV = 21, }; - QXmppVideoPlane planes[3]; + QXmppVideoFrame(); + QXmppVideoFrame(int bytes, const QSize &size, int bytesPerLine, PixelFormat format); + uchar *bits(); + const uchar *bits() const; + int bytesPerLine() const; + int height() const; + bool isValid() const; + int mappedBytes() const; + PixelFormat pixelFormat() const; + QSize size() const; + int width() const; + +private: + int m_bytesPerLine; + QByteArray m_data; + int m_height; + int m_mappedBytes; + PixelFormat m_pixelFormat; + int m_width; }; class QXmppVideoFormat { public: + int frameHeight() const { + return m_frameSize.height(); + } + + int frameWidth() const { + return m_frameSize.width(); + } + + qreal frameRate() const { + return m_frameRate; + } + + void setFrameRate(qreal frameRate) { + m_frameRate = frameRate; + } + QSize frameSize() const { return m_frameSize; } @@ -186,6 +237,7 @@ public: } private: + qreal m_frameRate; QSize m_frameSize; QXmppVideoFrame::PixelFormat m_pixelFormat; }; |
