aboutsummaryrefslogtreecommitdiff
path: root/src/base/QXmppCodec.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/base/QXmppCodec.cpp')
-rw-r--r--src/base/QXmppCodec.cpp1218
1 files changed, 1218 insertions, 0 deletions
diff --git a/src/base/QXmppCodec.cpp b/src/base/QXmppCodec.cpp
new file mode 100644
index 00000000..a614ce01
--- /dev/null
+++ b/src/base/QXmppCodec.cpp
@@ -0,0 +1,1218 @@
+/*
+ * 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.
+ *
+ */
+
+/*
+ * G.711 based on reference implementation by Sun Microsystems, Inc.
+ */
+
+#include <QDataStream>
+#include <QDebug>
+#include <QSize>
+
+#include "QXmppCodec.h"
+#include "QXmppRtpChannel.h"
+
+#include <cstring>
+
+#ifdef QXMPP_USE_SPEEX
+#include <speex/speex.h>
+#endif
+
+#ifdef QXMPP_USE_THEORA
+#include <theora/theoradec.h>
+#include <theora/theoraenc.h>
+#endif
+
+#ifdef QXMPP_USE_VPX
+#define VPX_CODEC_DISABLE_COMPAT 1
+#include <vpx/vpx_decoder.h>
+#include <vpx/vpx_encoder.h>
+#include <vpx/vp8cx.h>
+#include <vpx/vp8dx.h>
+#endif
+
+#define BIAS (0x84) /* Bias for linear code. */
+#define CLIP 8159
+
+#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */
+#define QUANT_MASK (0xf) /* Quantization field mask. */
+#define NSEGS (8) /* Number of A-law segments. */
+#define SEG_SHIFT (4) /* Left shift for segment number. */
+#define SEG_MASK (0x70) /* Segment field mask. */
+
+enum FragmentType {
+ NoFragment = 0,
+ StartFragment,
+ MiddleFragment,
+ EndFragment,
+};
+
+static qint16 seg_aend[8] = {0x1F, 0x3F, 0x7F, 0xFF,
+ 0x1FF, 0x3FF, 0x7FF, 0xFFF};
+static qint16 seg_uend[8] = {0x3F, 0x7F, 0xFF, 0x1FF,
+ 0x3FF, 0x7FF, 0xFFF, 0x1FFF};
+
+static qint16 search(qint16 val, qint16 *table, qint16 size)
+{
+ qint16 i;
+
+ for (i = 0; i < size; i++) {
+ if (val <= *table++)
+ return (i);
+ }
+ return (size);
+}
+
+/*
+ * linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
+ *
+ * Accepts a 16-bit integer and encodes it as A-law data.
+ *
+ * Linear Input Code Compressed Code
+ * ------------------------ ---------------
+ * 0000000wxyza 000wxyz
+ * 0000001wxyza 001wxyz
+ * 000001wxyzab 010wxyz
+ * 00001wxyzabc 011wxyz
+ * 0001wxyzabcd 100wxyz
+ * 001wxyzabcde 101wxyz
+ * 01wxyzabcdef 110wxyz
+ * 1wxyzabcdefg 111wxyz
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+quint8 linear2alaw(qint16 pcm_val)
+{
+ qint16 mask;
+ qint16 seg;
+ quint8 aval;
+
+ pcm_val = pcm_val >> 3;
+
+ if (pcm_val >= 0) {
+ mask = 0xD5; /* sign (7th) bit = 1 */
+ } else {
+ mask = 0x55; /* sign bit = 0 */
+ pcm_val = -pcm_val - 1;
+ }
+
+ /* Convert the scaled magnitude to segment number. */
+ seg = search(pcm_val, seg_aend, 8);
+
+ /* Combine the sign, segment, and quantization bits. */
+
+ if (seg >= 8) /* out of range, return maximum value. */
+ return (quint8) (0x7F ^ mask);
+ else {
+ aval = (quint8) seg << SEG_SHIFT;
+ if (seg < 2)
+ aval |= (pcm_val >> 1) & QUANT_MASK;
+ else
+ aval |= (pcm_val >> seg) & QUANT_MASK;
+ return (aval ^ mask);
+ }
+}
+
+/*
+ * alaw2linear() - Convert an A-law value to 16-bit linear PCM
+ *
+ */
+qint16 alaw2linear(quint8 a_val)
+{
+ qint16 t;
+ qint16 seg;
+
+ a_val ^= 0x55;
+
+ t = (a_val & QUANT_MASK) << 4;
+ seg = ((qint16)a_val & SEG_MASK) >> SEG_SHIFT;
+ switch (seg) {
+ case 0:
+ t += 8;
+ break;
+ case 1:
+ t += 0x108;
+ break;
+ default:
+ t += 0x108;
+ t <<= seg - 1;
+ }
+ return ((a_val & SIGN_BIT) ? t : -t);
+}
+
+/*
+ * linear2ulaw() - Convert a linear PCM value to u-law
+ *
+ * In order to simplify the encoding process, the original linear magnitude
+ * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
+ * (33 - 8191). The result can be seen in the following encoding table:
+ *
+ * Biased Linear Input Code Compressed Code
+ * ------------------------ ---------------
+ * 00000001wxyza 000wxyz
+ * 0000001wxyzab 001wxyz
+ * 000001wxyzabc 010wxyz
+ * 00001wxyzabcd 011wxyz
+ * 0001wxyzabcde 100wxyz
+ * 001wxyzabcdef 101wxyz
+ * 01wxyzabcdefg 110wxyz
+ * 1wxyzabcdefgh 111wxyz
+ *
+ * Each biased linear code has a leading 1 which identifies the segment
+ * number. The value of the segment number is equal to 7 minus the number
+ * of leading 0's. The quantization interval is directly available as the
+ * four bits wxyz. * The trailing bits (a - h) are ignored.
+ *
+ * Ordinarily the complement of the resulting code word is used for
+ * transmission, and so the code word is complemented before it is returned.
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+quint8 linear2ulaw(qint16 pcm_val)
+{
+ qint16 mask;
+ qint16 seg;
+ quint8 uval;
+
+ /* Get the sign and the magnitude of the value. */
+ pcm_val = pcm_val >> 2;
+ if (pcm_val < 0) {
+ pcm_val = -pcm_val;
+ mask = 0x7F;
+ } else {
+ mask = 0xFF;
+ }
+ if (pcm_val > CLIP) pcm_val = CLIP; /* clip the magnitude */
+ pcm_val += (BIAS >> 2);
+
+ /* Convert the scaled magnitude to segment number. */
+ seg = search(pcm_val, seg_uend, 8);
+
+ /*
+ * Combine the sign, segment, quantization bits;
+ * and complement the code word.
+ */
+ if (seg >= 8) /* out of range, return maximum value. */
+ return (quint8) (0x7F ^ mask);
+ else {
+ uval = (quint8) (seg << 4) | ((pcm_val >> (seg + 1)) & 0xF);
+ return (uval ^ mask);
+ }
+}
+
+/*
+ * ulaw2linear() - Convert a u-law value to 16-bit linear PCM
+ *
+ * First, a biased linear code is derived from the code word. An unbiased
+ * output can then be obtained by subtracting 33 from the biased code.
+ *
+ * Note that this function expects to be passed the complement of the
+ * original code word. This is in keeping with ISDN conventions.
+ */
+qint16 ulaw2linear(quint8 u_val)
+{
+ qint16 t;
+
+ /* Complement to obtain normal u-law value. */
+ u_val = ~u_val;
+
+ /*
+ * Extract and bias the quantization bits. Then
+ * shift up by the segment number and subtract out the bias.
+ */
+ t = ((u_val & QUANT_MASK) << 3) + BIAS;
+ t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
+
+ return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
+}
+
+QXmppG711aCodec::QXmppG711aCodec(int clockrate)
+{
+ m_frequency = clockrate;
+}
+
+qint64 QXmppG711aCodec::encode(QDataStream &input, QDataStream &output)
+{
+ qint64 samples = 0;
+ qint16 pcm;
+ while (!input.atEnd())
+ {
+ input >> pcm;
+ output << linear2alaw(pcm);
+ ++samples;
+ }
+ return samples;
+}
+
+qint64 QXmppG711aCodec::decode(QDataStream &input, QDataStream &output)
+{
+ qint64 samples = 0;
+ quint8 g711;
+ while (!input.atEnd())
+ {
+ input >> g711;
+ output << alaw2linear(g711);
+ ++samples;
+ }
+ return samples;
+}
+
+QXmppG711uCodec::QXmppG711uCodec(int clockrate)
+{
+ m_frequency = clockrate;
+}
+
+qint64 QXmppG711uCodec::encode(QDataStream &input, QDataStream &output)
+{
+ qint64 samples = 0;
+ qint16 pcm;
+ while (!input.atEnd())
+ {
+ input >> pcm;
+ output << linear2ulaw(pcm);
+ ++samples;
+ }
+ return samples;
+}
+
+qint64 QXmppG711uCodec::decode(QDataStream &input, QDataStream &output)
+{
+ qint64 samples = 0;
+ quint8 g711;
+ while (!input.atEnd())
+ {
+ input >> g711;
+ output << ulaw2linear(g711);
+ ++samples;
+ }
+ return samples;
+}
+
+#ifdef QXMPP_USE_SPEEX
+QXmppSpeexCodec::QXmppSpeexCodec(int clockrate)
+{
+ const SpeexMode *mode = &speex_nb_mode;
+ if (clockrate == 32000)
+ mode = &speex_uwb_mode;
+ else if (clockrate == 16000)
+ mode = &speex_wb_mode;
+ else if (clockrate == 8000)
+ mode = &speex_nb_mode;
+ else
+ qWarning() << "QXmppSpeexCodec got invalid clockrate" << clockrate;
+
+ // encoder
+ encoder_bits = new SpeexBits;
+ speex_bits_init(encoder_bits);
+ encoder_state = speex_encoder_init(mode);
+
+ // decoder
+ decoder_bits = new SpeexBits;
+ speex_bits_init(decoder_bits);
+ decoder_state = speex_decoder_init(mode);
+
+ // get frame size in samples
+ speex_encoder_ctl(encoder_state, SPEEX_GET_FRAME_SIZE, &frame_samples);
+}
+
+QXmppSpeexCodec::~QXmppSpeexCodec()
+{
+ delete encoder_bits;
+ delete decoder_bits;
+}
+
+qint64 QXmppSpeexCodec::encode(QDataStream &input, QDataStream &output)
+{
+ QByteArray pcm_buffer(frame_samples * 2, 0);
+ const int length = input.readRawData(pcm_buffer.data(), pcm_buffer.size());
+ if (length != pcm_buffer.size())
+ {
+ qWarning() << "Read only read" << length << "bytes";
+ return 0;
+ }
+ speex_bits_reset(encoder_bits);
+ speex_encode_int(encoder_state, (short*)pcm_buffer.data(), encoder_bits);
+ QByteArray speex_buffer(speex_bits_nbytes(encoder_bits), 0);
+ speex_bits_write(encoder_bits, speex_buffer.data(), speex_buffer.size());
+ output.writeRawData(speex_buffer.data(), speex_buffer.size());
+ return frame_samples;
+}
+
+qint64 QXmppSpeexCodec::decode(QDataStream &input, QDataStream &output)
+{
+ const int length = input.device()->bytesAvailable();
+ QByteArray speex_buffer(length, 0);
+ input.readRawData(speex_buffer.data(), speex_buffer.size());
+ speex_bits_read_from(decoder_bits, speex_buffer.data(), speex_buffer.size());
+ QByteArray pcm_buffer(frame_samples * 2, 0);
+ speex_decode_int(decoder_state, decoder_bits, (short*)pcm_buffer.data());
+ output.writeRawData(pcm_buffer.data(), pcm_buffer.size());
+ return frame_samples;
+}
+
+#endif
+
+#ifdef QXMPP_USE_THEORA
+
+class QXmppTheoraDecoderPrivate
+{
+public:
+ bool decodeFrame(const QByteArray &buffer, QXmppVideoFrame *frame);
+
+ th_comment comment;
+ th_info info;
+ th_setup_info *setup_info;
+ th_dec_ctx *ctx;
+
+ QByteArray packetBuffer;
+};
+
+bool QXmppTheoraDecoderPrivate::decodeFrame(const QByteArray &buffer, QXmppVideoFrame *frame)
+{
+ if (!ctx)
+ return false;
+
+ ogg_packet packet;
+ packet.packet = (unsigned char*) buffer.data();
+ packet.bytes = buffer.size();
+ packet.b_o_s = 1;
+ packet.e_o_s = 0;
+ packet.granulepos = -1;
+ packet.packetno = 0;
+ if (th_decode_packetin(ctx, &packet, 0) != 0) {
+ qWarning("Theora packet could not be decoded");
+ return false;
+ }
+
+ th_ycbcr_buffer ycbcr_buffer;
+ if (th_decode_ycbcr_out(ctx, ycbcr_buffer) != 0) {
+ qWarning("Theora packet has no Y'CbCr");
+ return false;
+ }
+
+ 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;
+ }
+}
+
+QXmppTheoraDecoder::QXmppTheoraDecoder()
+{
+ d = new QXmppTheoraDecoderPrivate;
+ th_comment_init(&d->comment);
+ th_info_init(&d->info);
+ d->setup_info = 0;
+ d->ctx = 0;
+}
+
+QXmppTheoraDecoder::~QXmppTheoraDecoder()
+{
+ th_comment_clear(&d->comment);
+ th_info_clear(&d->info);
+ if (d->setup_info)
+ th_setup_free(d->setup_info);
+ if (d->ctx)
+ th_decode_free(d->ctx);
+ delete d;
+}
+
+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)
+ 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(const QXmppRtpPacket &packet)
+{
+ QList<QXmppVideoFrame> frames;
+
+ // theora deframing: draft-ietf-avt-rtp-theora-00
+ QDataStream stream(packet.payload);
+ quint32 theora_header;
+ stream >> theora_header;
+
+ quint32 theora_ident = (theora_header >> 8) & 0xffffff;
+ Q_UNUSED(theora_ident);
+ quint8 theora_frag = (theora_header & 0xc0) >> 6;
+ quint8 theora_type = (theora_header & 0x30) >> 4;
+ quint8 theora_packets = (theora_header & 0x0f);
+
+ //qDebug("ident: 0x%08x, F: %d, TDT: %d, packets: %d", theora_ident, theora_frag, theora_type, theora_packets);
+
+ // We only handle raw theora data
+ if (theora_type != 0)
+ return frames;
+
+ QXmppVideoFrame frame;
+ quint16 packetLength;
+
+ if (theora_frag == NoFragment) {
+ // unfragmented packet(s)
+ for (int i = 0; i < theora_packets; ++i) {
+ stream >> packetLength;
+ if (packetLength > stream.device()->bytesAvailable()) {
+ qWarning("Theora unfragmented packet has an invalid length");
+ return frames;
+ }
+
+ d->packetBuffer.resize(packetLength);
+ stream.readRawData(d->packetBuffer.data(), packetLength);
+ if (d->decodeFrame(d->packetBuffer, &frame))
+ frames << frame;
+ d->packetBuffer.resize(0);
+ }
+ } else {
+ // fragments
+ stream >> packetLength;
+ if (packetLength > stream.device()->bytesAvailable()) {
+ qWarning("Theora packet has an invalid length");
+ return frames;
+ }
+
+ int pos;
+ if (theora_frag == StartFragment) {
+ // start fragment
+ pos = 0;
+ d->packetBuffer.resize(packetLength);
+ } else {
+ // continuation or end fragment
+ pos = d->packetBuffer.size();
+ d->packetBuffer.resize(pos + packetLength);
+ }
+ stream.readRawData(d->packetBuffer.data() + pos, packetLength);
+
+ if (theora_frag == EndFragment) {
+ // end fragment
+ if (d->decodeFrame(d->packetBuffer, &frame))
+ frames << frame;
+ d->packetBuffer.resize(0);
+ }
+ }
+ return frames;
+}
+
+bool QXmppTheoraDecoder::setParameters(const QMap<QString, QString> &parameters)
+{
+ QByteArray config = QByteArray::fromBase64(parameters.value("configuration").toAscii());
+ QDataStream stream(config);
+ const QIODevice *device = stream.device();
+
+ if (device->bytesAvailable() < 4) {
+ qWarning("Theora configuration is too small");
+ return false;
+ }
+
+ // Process packed headers
+ int done = 0;
+ quint32 header_count;
+ stream >> header_count;
+ for (quint32 i = 0; i < header_count; ++i) {
+ if (device->bytesAvailable() < 6) {
+ qWarning("Theora configuration is too small");
+ return false;
+ }
+ QByteArray ident(3, 0);
+ quint16 length;
+ quint8 h_count;
+
+ stream.readRawData(ident.data(), ident.size());
+ stream >> length;
+ stream >> h_count;
+#ifdef QXMPP_DEBUG_THEORA
+ qDebug("Theora packed header %u ident=%s bytes=%u count=%u", i, ident.toHex().data(), length, h_count);
+#endif
+
+ // get header sizes
+ QList<qint64> h_sizes;
+ for (int h = 0; h < h_count; ++h) {
+ quint16 h_size = 0;
+ quint8 b;
+ do {
+ if (device->bytesAvailable() < 1) {
+ qWarning("Theora configuration is too small");
+ return false;
+ }
+ stream >> b;
+ h_size = (h_size << 7) | (b & 0x7f);
+ } while (b & 0x80);
+ h_sizes << h_size;
+#ifdef QXMPP_DEBUG_THEORA
+ qDebug("Theora header %d size %u", h_sizes.size() - 1, h_sizes.last());
+#endif
+ length -= h_size;
+ }
+ h_sizes << length;
+#ifdef QXMPP_DEBUG_THEORA
+ qDebug("Theora header %d size %u", h_sizes.size() - 1, h_sizes.last());
+#endif
+
+ // decode headers
+ ogg_packet packet;
+ packet.b_o_s = 1;
+ packet.e_o_s = 0;
+ packet.granulepos = -1;
+ packet.packetno = 0;
+
+ foreach (int h_size, h_sizes) {
+ if (device->bytesAvailable() < h_size) {
+ qWarning("Theora configuration is too small");
+ return false;
+ }
+
+ packet.packet = (unsigned char*) (config.data() + device->pos());
+ packet.bytes = h_size;
+ int ret = th_decode_headerin(&d->info, &d->comment, &d->setup_info, &packet);
+ if (ret < 0) {
+ qWarning("Theora header could not be decoded");
+ return false;
+ }
+ done += ret;
+ stream.skipRawData(h_size);
+ }
+ }
+
+ // check for completion
+ if (done < 3) {
+ qWarning("Theora configuration did not contain enough headers");
+ return false;
+ }
+
+#ifdef QXMPP_DEBUG_THEORA
+ qDebug("Theora frame_width %i, frame_height %i, colorspace %i, pixel_fmt: %i, target_bitrate: %i, quality: %i, keyframe_granule_shift: %i",
+ d->info.frame_width,
+ d->info.frame_height,
+ d->info.colorspace,
+ d->info.pixel_fmt,
+ d->info.target_bitrate,
+ d->info.quality,
+ d->info.keyframe_granule_shift);
+#endif
+ 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;
+ }
+ if (d->ctx)
+ th_decode_free(d->ctx);
+ d->ctx = th_decode_alloc(&d->info, d->setup_info);
+ if (!d->ctx) {
+ qWarning("Theora decoder could not be allocated");
+ return false;
+ }
+ return true;
+}
+
+class QXmppTheoraEncoderPrivate
+{
+public:
+ void writeFragment(QDataStream &stream, FragmentType frag_type, quint8 theora_packets, const char *data, quint16 length);
+
+ th_comment comment;
+ th_info info;
+ th_setup_info *setup_info;
+ th_enc_ctx *ctx;
+ th_ycbcr_buffer ycbcr_buffer;
+
+ QByteArray buffer;
+ QByteArray configuration;
+ QByteArray ident;
+};
+
+void QXmppTheoraEncoderPrivate::writeFragment(QDataStream &stream, FragmentType frag_type, quint8 theora_packets, const char *data, quint16 length)
+{
+ // theora framing: draft-ietf-avt-rtp-theora-00
+ const quint8 theora_type = 0; // raw data
+ stream.writeRawData(ident.constData(), ident.size());
+ stream << quint8(((frag_type << 6) & 0xc0) |
+ ((theora_type << 4) & 0x30) |
+ (theora_packets & 0x0f));
+ stream << quint16(length);
+ stream.writeRawData(data, length);
+}
+
+QXmppTheoraEncoder::QXmppTheoraEncoder()
+{
+ d = new QXmppTheoraEncoderPrivate;
+ d->ident = QByteArray("\xc3\x45\xae");
+ th_comment_init(&d->comment);
+ th_info_init(&d->info);
+ d->setup_info = 0;
+ d->ctx = 0;
+}
+
+QXmppTheoraEncoder::~QXmppTheoraEncoder()
+{
+ th_comment_clear(&d->comment);
+ th_info_clear(&d->info);
+ if (d->setup_info)
+ th_setup_free(d->setup_info);
+ if (d->ctx)
+ th_encode_free(d->ctx);
+ delete d;
+}
+
+bool QXmppTheoraEncoder::setFormat(const QXmppVideoFormat &format)
+{
+ const QXmppVideoFrame::PixelFormat pixelFormat = format.pixelFormat();
+ if ((pixelFormat != QXmppVideoFrame::Format_YUV420P) &&
+ (pixelFormat != QXmppVideoFrame::Format_YUYV)) {
+ qWarning("Theora encoder 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();
+ d->info.pic_width = format.frameSize().width();
+ d->info.pic_x = 0;
+ d->info.pic_y = 0;
+ d->info.colorspace = TH_CS_UNSPECIFIED;
+ d->info.target_bitrate = 0;
+ d->info.quality = 48;
+ d->info.keyframe_granule_shift = 6;
+
+ // 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;
+ }
+ d->ctx = th_encode_alloc(&d->info);
+ if (!d->ctx) {
+ qWarning("Theora encoder could not be allocated");
+ return false;
+ }
+
+ // fetch headers
+ QList<QByteArray> headers;
+ ogg_packet packet;
+ while (th_encode_flushheader(d->ctx, &d->comment, &packet) > 0)
+ headers << QByteArray((const char*)packet.packet, packet.bytes);
+
+ // store configuration
+ d->configuration.clear();
+ QDataStream stream(&d->configuration, QIODevice::WriteOnly);
+ stream << quint32(1);
+
+ quint16 length = 0;
+ foreach (const QByteArray &header, headers)
+ length += header.size();
+
+ quint8 h_count = headers.size() - 1;
+ stream.writeRawData(d->ident.constData(), d->ident.size());
+ stream << length;
+ stream << h_count;
+#ifdef QXMPP_DEBUG_THEORA
+ qDebug("Theora packed header %u ident=%s bytes=%u count=%u", 0, d->ident.toHex().data(), length, h_count);
+#endif
+
+ // write header sizes
+ for (int h = 0; h < h_count; ++h) {
+ quint16 h_size = headers[h].size();
+ do {
+ quint8 b = (h_size & 0x7f);
+ h_size >>= 7;
+ if (h_size)
+ b |= 0x80;
+ stream << b;
+ } while (h_size);
+ }
+
+ // write headers
+ for (int h = 0; h < headers.size(); ++h) {
+#ifdef QXMPP_DEBUG_THEORA
+ qDebug("Header %d size %d", h, headers[h].size());
+#endif
+ stream.writeRawData(headers[h].data(), headers[h].size());
+ }
+
+ return true;
+}
+
+QList<QByteArray> QXmppTheoraEncoder::handleFrame(const QXmppVideoFrame &frame)
+{
+ QList<QByteArray> packets;
+ const int PACKET_MAX = 1388;
+
+ 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, d->ycbcr_buffer) != 0) {
+ qWarning("Theora encoder could not handle frame");
+ return packets;
+ }
+
+ QByteArray payload;
+ ogg_packet packet;
+ while (th_encode_packetout(d->ctx, 0, &packet) > 0) {
+#ifdef QXMPP_DEBUG_THEORA
+ qDebug("Theora encoded packet %d bytes", packet.bytes);
+#endif
+ QDataStream stream(&payload, QIODevice::WriteOnly);
+ const char *data = (const char*) packet.packet;
+ int size = packet.bytes;
+ if (size <= PACKET_MAX) {
+ // no fragmentation
+ stream.device()->reset();
+ payload.resize(0);
+ d->writeFragment(stream, NoFragment, 1, data, size);
+ packets << payload;
+ } else {
+ // fragmentation
+ FragmentType frag_type = StartFragment;
+ while (size) {
+ const int length = qMin(PACKET_MAX, size);
+ stream.device()->reset();
+ payload.resize(0);
+ d->writeFragment(stream, frag_type, 0, data, length);
+ data += length;
+ size -= length;
+ frag_type = (size > PACKET_MAX) ? MiddleFragment : EndFragment;
+ packets << payload;
+ }
+ }
+ }
+
+ return packets;
+}
+
+QMap<QString, QString> QXmppTheoraEncoder::parameters() const
+{
+ QMap<QString, QString> params;
+ if (d->ctx) {
+ params.insert("delivery-method", "inline");
+ params.insert("configuration", d->configuration.toBase64());
+ }
+ return params;
+}
+
+#endif
+
+#ifdef QXMPP_USE_VPX
+
+class QXmppVpxDecoderPrivate
+{
+public:
+ bool decodeFrame(const QByteArray &buffer, QXmppVideoFrame *frame);
+
+ vpx_codec_ctx_t codec;
+ QByteArray packetBuffer;
+};
+
+bool QXmppVpxDecoderPrivate::decodeFrame(const QByteArray &buffer, QXmppVideoFrame *frame)
+{
+ if (vpx_codec_decode(&codec, (const uint8_t*)buffer.constData(), buffer.size(), NULL, 0) != VPX_CODEC_OK) {
+ qWarning("Vpx packet could not be decoded: %s", vpx_codec_error_detail(&codec));
+ return false;
+ }
+
+ vpx_codec_iter_t iter = NULL;
+ vpx_image_t *img;
+ while ((img = vpx_codec_get_frame(&codec, &iter))) {
+ if (img->fmt == VPX_IMG_FMT_I420) {
+ if (!frame->isValid()) {
+ const int bytes = img->d_w * img->d_h * 3 / 2;
+
+ *frame = QXmppVideoFrame(bytes,
+ QSize(img->d_w, img->d_h),
+ img->d_w,
+ QXmppVideoFrame::Format_YUV420P);
+ }
+ uchar *output = frame->bits();
+
+ for (int i = 0; i < 3; ++i) {
+ uchar *input = img->planes[i];
+ const int div = (i == 0) ? 1 : 2;
+ for (unsigned int y = 0; y < img->d_h / div; ++y) {
+ memcpy(output, input, img->d_w / div);
+ input += img->stride[i];
+ output += img->d_w / div;
+ }
+ }
+ } else {
+ qWarning("Vpx decoder received an unsupported frame format: %d", img->fmt);
+ }
+ }
+
+ return true;
+}
+
+QXmppVpxDecoder::QXmppVpxDecoder()
+{
+ d = new QXmppVpxDecoderPrivate;
+ if (vpx_codec_dec_init(&d->codec, vpx_codec_vp8_dx(), NULL, 0) != VPX_CODEC_OK) {
+ qWarning("Vpx decoder could not be initialised");
+ }
+}
+
+QXmppVpxDecoder::~QXmppVpxDecoder()
+{
+ vpx_codec_destroy(&d->codec);
+ delete d;
+}
+
+QXmppVideoFormat QXmppVpxDecoder::format() const
+{
+ QXmppVideoFormat format;
+ format.setFrameRate(15.0);
+ format.setFrameSize(QSize(320, 240));
+ format.setPixelFormat(QXmppVideoFrame::Format_YUV420P);
+ return format;
+}
+
+QList<QXmppVideoFrame> QXmppVpxDecoder::handlePacket(const QXmppRtpPacket &packet)
+{
+ QList<QXmppVideoFrame> frames;
+
+ // vp8 deframing: http://tools.ietf.org/html/draft-westin-payload-vp8-00
+ QDataStream stream(packet.payload);
+ quint8 vpx_header;
+ stream >> vpx_header;
+
+ const bool have_id = (vpx_header & 0x10) != 0;
+ const quint8 frag_type = (vpx_header & 0x6) >> 1;
+ if (have_id) {
+ qWarning("Vpx decoder does not support pictureId yet");
+ return frames;
+ }
+
+ const int packetLength = packet.payload.size() - 1;
+#ifdef QXMPP_DEBUG_VPX
+ qDebug("Vpx fragment FI: %d, size %d", frag_type, packetLength);
+#endif
+
+ QXmppVideoFrame frame;
+
+ if (frag_type == NoFragment) {
+ // unfragmented packet
+ if (d->decodeFrame(packet.payload.mid(1), &frame))
+ frames << frame;
+ d->packetBuffer.resize(0);
+ } else {
+ // fragments
+ if (frag_type == StartFragment) {
+ // start fragment
+ d->packetBuffer = packet.payload.mid(1);
+ } else {
+ // continuation or end fragment
+ const int packetPos = d->packetBuffer.size();
+ d->packetBuffer.resize(packetPos + packetLength);
+ stream.readRawData(d->packetBuffer.data() + packetPos, packetLength);
+ }
+
+ if (frag_type == EndFragment) {
+ // end fragment
+ if (d->decodeFrame(d->packetBuffer, &frame))
+ frames << frame;
+ d->packetBuffer.resize(0);
+ }
+
+ }
+
+ return frames;
+}
+
+bool QXmppVpxDecoder::setParameters(const QMap<QString, QString> &parameters)
+{
+ return true;
+}
+
+class QXmppVpxEncoderPrivate
+{
+public:
+ void writeFragment(QDataStream &stream, FragmentType frag_type, const char *data, quint16 length);
+
+ vpx_codec_ctx_t codec;
+ vpx_codec_enc_cfg_t cfg;
+ vpx_image_t *imageBuffer;
+ int frameCount;
+};
+
+void QXmppVpxEncoderPrivate::writeFragment(QDataStream &stream, FragmentType frag_type, const char *data, quint16 length)
+{
+ // vp8 framing: http://tools.ietf.org/html/draft-westin-payload-vp8-00
+#ifdef QXMPP_DEBUG_VPX
+ qDebug("Vpx encoder writing packet frag: %i, size: %u", frag_type, length);
+#endif
+ stream << quint8(((frag_type << 1) & 0x6) |
+ (frag_type == NoFragment || frag_type == StartFragment));
+ stream.writeRawData(data, length);
+}
+
+QXmppVpxEncoder::QXmppVpxEncoder()
+{
+ d = new QXmppVpxEncoderPrivate;
+ d->frameCount = 0;
+ d->imageBuffer = 0;
+ vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &d->cfg, 0);
+}
+
+QXmppVpxEncoder::~QXmppVpxEncoder()
+{
+ vpx_codec_destroy(&d->codec);
+ if (d->imageBuffer)
+ vpx_img_free(d->imageBuffer);
+ delete d;
+}
+
+bool QXmppVpxEncoder::setFormat(const QXmppVideoFormat &format)
+{
+ const QXmppVideoFrame::PixelFormat pixelFormat = format.pixelFormat();
+ if (pixelFormat != QXmppVideoFrame::Format_YUYV) {
+ qWarning("Vpx encoder does not support the given format");
+ return false;
+ }
+
+ d->cfg.rc_target_bitrate = format.frameSize().width() * format.frameSize().height() * d->cfg.rc_target_bitrate / d->cfg.g_w / d->cfg.g_h;
+ d->cfg.g_w = format.frameSize().width();
+ d->cfg.g_h = format.frameSize().height();
+ if (vpx_codec_enc_init(&d->codec, vpx_codec_vp8_cx(), &d->cfg, 0) != VPX_CODEC_OK) {
+ qWarning("Vpx encoder could not be initialised");
+ return false;
+ }
+
+ d->imageBuffer = vpx_img_alloc(NULL, VPX_IMG_FMT_I420,
+ format.frameSize().width(), format.frameSize().height(), 1);
+ return true;
+}
+
+QList<QByteArray> QXmppVpxEncoder::handleFrame(const QXmppVideoFrame &frame)
+{
+ const int PACKET_MAX = 1388;
+ QList<QByteArray> packets;
+
+ // try to encode frame
+ if (frame.pixelFormat() == QXmppVideoFrame::Format_YUYV) {
+ // YUYV -> YUV420P
+ const int width = frame.width();
+ const int height = frame.height();
+ const int stride = frame.bytesPerLine();
+ const uchar *row = frame.bits();
+ uchar *y_row = d->imageBuffer->planes[VPX_PLANE_Y];
+ uchar *cb_row = d->imageBuffer->planes[VPX_PLANE_U];
+ uchar *cr_row = d->imageBuffer->planes[VPX_PLANE_V];
+ for (int y = 0; y < height; y += 2) {
+ // odd row
+ const uchar *ptr = row;
+ uchar *y_out = y_row;
+ uchar *cb_out = cb_row;
+ uchar *cr_out = cr_row;
+ for (int x = 0; x < width; x += 2) {
+ *(y_out++) = *(ptr++);
+ *(cb_out++) = *(ptr++);
+ *(y_out++) = *(ptr++);
+ *(cr_out++) = *(ptr++);
+ }
+ row += stride;
+ y_row += d->imageBuffer->stride[VPX_PLANE_Y];
+ cb_row += d->imageBuffer->stride[VPX_PLANE_U];
+ cr_row += d->imageBuffer->stride[VPX_PLANE_V];
+
+ // even row
+ ptr = row;
+ y_out = y_row;
+ for (int x = 0; x < width; x += 2) {
+ *(y_out++) = *(ptr++);
+ ptr++;
+ *(y_out++) = *(ptr++);
+ ptr++;
+ }
+ row += stride;
+ y_row += d->imageBuffer->stride[VPX_PLANE_Y];
+ }
+ } else {
+ qWarning("Vpx encoder does not support the given format");
+ return packets;
+ }
+
+ if (vpx_codec_encode(&d->codec, d->imageBuffer, d->frameCount, 1, 0, VPX_DL_REALTIME) != VPX_CODEC_OK) {
+ qWarning("Vpx encoder could not handle frame: %s", vpx_codec_error_detail(&d->codec));
+ return packets;
+ }
+
+ // extract data
+ QByteArray payload;
+ vpx_codec_iter_t iter = NULL;
+ const vpx_codec_cx_pkt_t *pkt;
+ while ((pkt = vpx_codec_get_cx_data(&d->codec, &iter))) {
+ if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
+#ifdef QXMPP_DEBUG_VPX
+ qDebug("Vpx encoded packet %lu bytes", pkt->data.frame.sz);
+#endif
+ QDataStream stream(&payload, QIODevice::WriteOnly);
+ const char *data = (const char*) pkt->data.frame.buf;
+ int size = pkt->data.frame.sz;
+ if (size <= PACKET_MAX) {
+ // no fragmentation
+ stream.device()->reset();
+ payload.resize(0);
+ d->writeFragment(stream, NoFragment, data, size);
+ packets << payload;
+ } else {
+ // fragmentation
+ FragmentType frag_type = StartFragment;
+ while (size) {
+ const int length = qMin(PACKET_MAX, size);
+ stream.device()->reset();
+ payload.resize(0);
+ d->writeFragment(stream, frag_type, data, length);
+ data += length;
+ size -= length;
+ frag_type = (size > PACKET_MAX) ? MiddleFragment : EndFragment;
+ packets << payload;
+ }
+ }
+ }
+ }
+ d->frameCount++;
+
+ return packets;
+}
+
+QMap<QString, QString> QXmppVpxEncoder::parameters() const
+{
+ return QMap<QString, QString>();
+}
+
+#endif