aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2011-04-23 15:47:51 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2011-04-23 15:47:51 +0000
commit02ae50991c9d00f171bcc1ef105cbe52f37b39f7 (patch)
tree38b366313f4e91c0e2612f5f533d00dbee2b72c4 /src
parentd0928bc5a77468731fc2067bfc1802e70932eb97 (diff)
downloadqxmpp-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.cpp166
-rw-r--r--src/QXmppCodec.h4
-rw-r--r--src/QXmppRtpChannel.cpp370
-rw-r--r--src/QXmppRtpChannel.h70
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> &parameters)
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> &parameters) = 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> &parameters);
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;
};