aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNiels Ole Salscheider <niels_ole@salscheider-online.de>2019-05-17 14:30:02 -0700
committerLNJ <lnj@kaidan.im>2020-03-16 22:22:59 +0100
commit90036fc2cf5918c028f043edff7f5d38d1efb4cc (patch)
tree4818d8c4e6ec3778e2dd8a2e356faf1b9e062902 /src
parentc67ccc6d939b8f1efd118f92baea997fe1b7f1a6 (diff)
downloadqxmpp-90036fc2cf5918c028f043edff7f5d38d1efb4cc.tar.gz
Port QXmppCallManager to use GStreamer
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt70
-rw-r--r--src/base/QXmppCodec.cpp1433
-rw-r--r--src/base/QXmppCodec_p.h243
-rw-r--r--src/base/QXmppRtcpPacket.cpp660
-rw-r--r--src/base/QXmppRtcpPacket.h169
-rw-r--r--src/base/QXmppRtpChannel.cpp999
-rw-r--r--src/base/QXmppRtpChannel.h304
-rw-r--r--src/base/QXmppRtpPacket.cpp224
-rw-r--r--src/base/QXmppRtpPacket.h75
-rw-r--r--src/client/QXmppCall.cpp739
-rw-r--r--src/client/QXmppCall.h121
-rw-r--r--src/client/QXmppCallManager.cpp655
-rw-r--r--src/client/QXmppCallManager.h112
-rw-r--r--src/client/QXmppCallManager_p.h63
-rw-r--r--src/client/QXmppCallStream.cpp361
-rw-r--r--src/client/QXmppCallStream.h66
-rw-r--r--src/client/QXmppCallStream_p.h103
-rw-r--r--src/client/QXmppCall_p.h133
18 files changed, 1630 insertions, 4900 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 04eeeee4..489ccd0b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,3 +1,5 @@
+option(BUILD_SHARED "Build SHARED library" ON)
+
add_definitions(-DQXMPP_BUILD)
# disable Q_FOREACH()
@@ -45,9 +47,6 @@ set(INSTALL_HEADER_FILES
base/QXmppResultSet.h
base/QXmppRosterIq.h
base/QXmppRpcIq.h
- base/QXmppRtcpPacket.h
- base/QXmppRtpChannel.h
- base/QXmppRtpPacket.h
base/QXmppSessionIq.h
base/QXmppSocks.h
base/QXmppStanza.h
@@ -62,7 +61,6 @@ set(INSTALL_HEADER_FILES
# Client
client/QXmppArchiveManager.h
client/QXmppBookmarkManager.h
- client/QXmppCallManager.h
client/QXmppCarbonManager.h
client/QXmppClient.h
client/QXmppClientExtension.h
@@ -105,7 +103,6 @@ set(SOURCE_FILES
base/QXmppBitsOfBinaryIq.cpp
base/QXmppBookmarkSet.cpp
base/QXmppByteStreamIq.cpp
- base/QXmppCodec.cpp
base/QXmppConstants.cpp
base/QXmppDataForm.cpp
base/QXmppDiscoveryIq.cpp
@@ -130,9 +127,6 @@ set(SOURCE_FILES
base/QXmppResultSet.cpp
base/QXmppRosterIq.cpp
base/QXmppRpcIq.cpp
- base/QXmppRtcpPacket.cpp
- base/QXmppRtpChannel.cpp
- base/QXmppRtpPacket.cpp
base/QXmppSasl.cpp
base/QXmppSessionIq.cpp
base/QXmppSocks.cpp
@@ -151,7 +145,6 @@ set(SOURCE_FILES
client/QXmppDiscoveryManager.cpp
client/QXmppArchiveManager.cpp
client/QXmppBookmarkManager.cpp
- client/QXmppCallManager.cpp
client/QXmppCarbonManager.cpp
client/QXmppClient.cpp
client/QXmppClientExtension.cpp
@@ -184,35 +177,24 @@ set(SOURCE_FILES
server/QXmppServerPlugin.cpp
)
-option(WITH_SPEEX "Support the Speex codec" OFF)
-option(WITH_OPUS "Support the Opus codec" OFF)
-option(WITH_THEORA "Support the Theora codec" OFF)
-option(WITH_VPX "Support the VPX codec" OFF)
-option(BUILD_SHARED "Build SHARED library" ON)
+if(WITH_GSTREAMER)
+ find_package(GStreamer REQUIRED)
+ find_package(GLIB2 REQUIRED)
+ find_package(GObject REQUIRED)
-if(WITH_SPEEX)
- find_package(Speex REQUIRED)
- include_directories(${Speex_INCLUDE_DIRS})
- set(MULTIMEDIA_LIBS ${MULTIMEDIA_LIBS} ${Speex_LIBRARIES})
- add_definitions(-DQXMPP_USE_SPEEX)
-endif()
-if(WITH_OPUS)
- find_package(Opus REQUIRED)
- include_directories(${Opus_INCLUDE_DIRS})
- set(MULTIMEDIA_LIBS ${MULTIMEDIA_LIBS} ${Opus_LIBRARIES})
- add_definitions(-DQXMPP_USE_OPUS)
-endif()
-if(WITH_THEORA)
- find_package(Theora REQUIRED)
- include_directories(${Theora_INCLUDE_DIRS})
- set(MULTIMEDIA_LIBS ${MULTIMEDIA_LIBS} ${Theora_LIBRARIES})
- add_definitions(-DQXMPP_USE_THEORA)
-endif()
-if(WITH_VPX)
- find_package(VPX REQUIRED)
- include_directories(${VPX_INCLUDE_DIRS})
- set(MULTIMEDIA_LIBS ${MULTIMEDIA_LIBS} ${VPX_LIBRARIES})
- add_definitions(-DQXMPP_USE_VPX)
+ set(INSTALL_HEADER_FILES
+ ${INSTALL_HEADER_FILES}
+ client/QXmppCall.h
+ client/QXmppCallManager.h
+ client/QXmppCallStream.h
+ )
+
+ set(SOURCE_FILES
+ ${SOURCE_FILES}
+ client/QXmppCall.cpp
+ client/QXmppCallManager.cpp
+ client/QXmppCallStream.cpp
+ )
endif()
if(BUILD_SHARED)
@@ -234,6 +216,9 @@ target_include_directories(qxmpp
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/server>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/base>
$<INSTALL_INTERFACE:include/qxmpp>
+ ${GLIB2_INCLUDE_DIR}
+ ${GOBJECT_INCLUDE_DIR}
+ ${GSTREAMER_INCLUDE_DIRS}
)
target_link_libraries(qxmpp
@@ -241,10 +226,17 @@ target_link_libraries(qxmpp
Qt5::Core
Qt5::Network
Qt5::Xml
- PRIVATE
- ${MULTIMEDIA_LIBS}
)
+if(WITH_GSTREAMER)
+ target_link_libraries(qxmpp
+ PUBLIC
+ ${GLIB2_LIBRARIES}
+ ${GOBJECT_LIBRARIES}
+ ${GSTREAMER_LIBRARY}
+ )
+endif()
+
install(
TARGETS qxmpp
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
diff --git a/src/base/QXmppCodec.cpp b/src/base/QXmppCodec.cpp
deleted file mode 100644
index d6396bee..00000000
--- a/src/base/QXmppCodec.cpp
+++ /dev/null
@@ -1,1433 +0,0 @@
-/*
- * Copyright (C) 2008-2020 The QXmpp developers
- *
- * Author:
- * Jeremy Lainé
- *
- * Source:
- * https://github.com/qxmpp-project/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 "QXmppCodec_p.h"
-#include "QXmppRtpChannel.h"
-#include "QXmppRtpPacket.h"
-
-#include <cstring>
-
-#include <QDataStream>
-#include <QDebug>
-#include <QSize>
-#include <QThread>
-
-#ifdef QXMPP_USE_SPEEX
-#include <speex/speex.h>
-#endif
-
-#ifdef QXMPP_USE_OPUS
-#include <opus/opus.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/vp8cx.h>
-#include <vpx/vp8dx.h>
-#include <vpx/vpx_decoder.h>
-#include <vpx/vpx_encoder.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. */
-
-// Distance (in frames) between two key frames (video only).
-#define GOPSIZE 32
-
-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.
- */
-static 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
- *
- */
-static 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.
- */
-static 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.
- */
-static 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));
-}
-
-QXmppCodec::~QXmppCodec()
-{
-}
-
-QXmppVideoDecoder::~QXmppVideoDecoder()
-{
-}
-
-QXmppVideoEncoder::~QXmppVideoEncoder()
-{
-}
-
-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_OPUS
-QXmppOpusCodec::QXmppOpusCodec(int clockrate, int channels) : sampleRate(clockrate),
- nChannels(channels)
-{
- int error;
- encoder = opus_encoder_create(clockrate, channels, OPUS_APPLICATION_VOIP, &error);
-
- if (encoder || error == OPUS_OK) {
- // Add some options for error correction.
- opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(1));
- opus_encoder_ctl(encoder, OPUS_SET_PACKET_LOSS_PERC(20));
- opus_encoder_ctl(encoder, OPUS_SET_DTX(1));
-#ifdef OPUS_SET_PREDICTION_DISABLED
- opus_encoder_ctl(encoder, OPUS_SET_PREDICTION_DISABLED(1));
-#endif
- } else
- qCritical() << "Opus encoder initialization error:" << opus_strerror(error);
-
- // Here, clockrate is synonym of sampleRate.
- decoder = opus_decoder_create(clockrate, channels, &error);
-
- if (!encoder || error != OPUS_OK)
- qCritical() << "Opus decoder initialization error:" << opus_strerror(error);
-
- // Opus only supports fixed frame durations from 2.5ms to 60ms.
- //
- // NOTE: https://mf4.xiph.org/jenkins/view/opus/job/opus/ws/doc/html/group__opus__encoder.html
- validFrameSize << 2.5e-3 << 5e-3 << 10e-3 << 20e-3 << 40e-3 << 60e-3;
-
- // so now, calculate the equivalent number of samples to process in each
- // frame.
- //
- // nSamples = t * sampleRate
- for (int i = 0; i < validFrameSize.size(); i++)
- validFrameSize[i] *= clockrate;
-
- // Maxmimum number of samples for the audio buffer.
- nSamples = validFrameSize.last();
-}
-
-QXmppOpusCodec::~QXmppOpusCodec()
-{
- if (encoder) {
- opus_encoder_destroy(encoder);
- encoder = NULL;
- }
-
- if (decoder) {
- opus_decoder_destroy(decoder);
- decoder = NULL;
- }
-}
-
-qint64 QXmppOpusCodec::encode(QDataStream &input, QDataStream &output)
-{
- // Read an audio frame.
- QByteArray pcm_buffer(input.device()->bytesAvailable(), 0);
- int length = input.readRawData(pcm_buffer.data(), pcm_buffer.size());
-
- // and append it to the sample buffer.
- sampleBuffer.append(pcm_buffer.left(length));
-
- // Get the maximum number of samples to encode. It must be a number
- // accepted by the Opus encoder
- int samples = readWindow(sampleBuffer.size());
-
- if (samples < 1)
- return 0;
-
- // The encoded stream is supposed to be smaller than the raw stream, so
- QByteArray opus_buffer(sampleBuffer.size(), 0);
-
- length = opus_encode(encoder,
- (opus_int16 *)sampleBuffer.constData(),
- samples,
- (uchar *)opus_buffer.data(),
- opus_buffer.size());
-
- if (length < 1)
- qWarning() << "Opus encoding error:" << opus_strerror(length);
- else
- // Write the encoded stream to the output.
- output.writeRawData(opus_buffer.constData(), length);
-
- // Remove the frame from the sample buffer.
- sampleBuffer.remove(0, samples * nChannels * 2);
-
- if (length < 1)
- return 0;
-
- return samples;
-}
-
-qint64 QXmppOpusCodec::decode(QDataStream &input, QDataStream &output)
-{
- QByteArray opus_buffer(input.device()->bytesAvailable(), 0);
- int length = input.readRawData(opus_buffer.data(), opus_buffer.size());
-
- if (length < 1)
- return 0;
-
- // Audio frame is nSamples at maximum, so
- QByteArray pcm_buffer(nSamples * nChannels * 2, 0);
-
- // The last argumment must be 1 to enable FEC, but I don't why it results
- // in a SIGSEV.
- int samples = opus_decode(decoder,
- (uchar *)opus_buffer.constData(),
- length,
- (opus_int16 *)pcm_buffer.data(),
- pcm_buffer.size(),
- 0);
-
- if (samples < 1) {
- qWarning() << "Opus decoding error:" << opus_strerror(samples);
-
- return 0;
- }
-
- // Write the audio frame to the output.
- output.writeRawData(pcm_buffer.constData(), samples * nChannels * 2);
-
- return samples;
-}
-
-int QXmppOpusCodec::readWindow(int bufferSize)
-{
- // WARNING: We are expecting 2 bytes signed samples, but this is wrong since
- // input stream can have a different sample formats.
-
- // Get the number of frames in the buffer.
- int samples = bufferSize / nChannels / 2;
-
- // Find an appropriate number of samples to read, according to Opus specs.
- for (int i = validFrameSize.size() - 1; i >= 0; i--)
- if (validFrameSize[i] <= samples)
- return validFrameSize[i];
-
- return 0;
-}
-
-#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").toLatin1());
- 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;
-
- for (const auto 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;
- for (const auto &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)
-{
- // With the VPX_DL_REALTIME option, tries to decode the frame as quick as
- // possible, if not possible discard it.
- if (vpx_codec_decode(&codec,
- (const uint8_t *)buffer.constData(),
- buffer.size(),
- NULL,
- VPX_DL_REALTIME) != 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;
- vpx_codec_flags_t flags = 0;
-
- // Enable FEC if codec support it.
- if (vpx_codec_get_caps(vpx_codec_vp8_dx()) & VPX_CODEC_CAP_ERROR_CONCEALMENT)
- flags |= VPX_CODEC_USE_ERROR_CONCEALMENT;
-
- if (vpx_codec_dec_init(&d->codec,
- vpx_codec_vp8_dx(),
- NULL,
- flags) != 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;
- const QByteArray payload = packet.payload();
-
- // vp8 deframing: http://tools.ietf.org/html/draft-westin-payload-vp8-00
- QDataStream stream(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 = payload.size() - 1;
-#ifdef QXMPP_DEBUG_VPX
- qDebug("Vpx fragment FI: %d, size %d", frag_type, packetLength);
-#endif
-
- QXmppVideoFrame frame;
- static quint16 sequence = 0;
-
- // If the incoming packet sequence is wrong discard all packets until a
- // complete keyframe arrives.
- // If a partition of a keyframe is missing, discard it until a next
- // keyframe.
- //
- // NOTE: https://tools.ietf.org/html/draft-ietf-payload-vp8-13#section-4.3
- // Sections: 4.3, 4.5, 4.5.1
-
- if (frag_type == NoFragment) {
- // unfragmented packet
- if ((payload[1] & 0x1) == 0 // is key frame
- || packet.sequence() == sequence) {
- if (d->decodeFrame(payload.mid(1), &frame))
- frames << frame;
-
- sequence = packet.sequence() + 1;
- }
-
- d->packetBuffer.resize(0);
- } else {
- // fragments
- if (frag_type == StartFragment) {
- // start fragment
- if ((payload[1] & 0x1) == 0 // is key frame
- || packet.sequence() == sequence) {
- d->packetBuffer = payload.mid(1);
- sequence = packet.sequence() + 1;
- }
- } else {
- // continuation or end fragment
- if (packet.sequence() == sequence) {
- 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);
- }
- }
-
- sequence++;
- }
- }
- }
-
- return frames;
-}
-
-bool QXmppVpxDecoder::setParameters(const QMap<QString, QString> &parameters)
-{
- Q_UNUSED(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(uint clockrate)
-{
- d = new QXmppVpxEncoderPrivate;
- d->frameCount = 0;
- d->imageBuffer = 0;
- vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &d->cfg, 0);
-
- // Set the encoding threads number to use
- int nThreads = QThread::idealThreadCount();
-
- if (nThreads > 0)
- d->cfg.g_threads = nThreads - 1;
-
- // Make stream error resiliant
- d->cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS;
-
- d->cfg.g_pass = VPX_RC_ONE_PASS;
- d->cfg.kf_mode = VPX_KF_AUTO;
-
- // Reduce GOP size
- if (d->cfg.kf_max_dist > GOPSIZE)
- d->cfg.kf_max_dist = GOPSIZE;
-
- // Here, clockrate is synonym of bitrate.
- d->cfg.rc_target_bitrate = clockrate / 1000;
-}
-
-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.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
diff --git a/src/base/QXmppCodec_p.h b/src/base/QXmppCodec_p.h
deleted file mode 100644
index d9a8e3c2..00000000
--- a/src/base/QXmppCodec_p.h
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2008-2020 The QXmpp developers
- *
- * Author:
- * Jeremy Lainé
- *
- * Source:
- * https://github.com/qxmpp-project/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.
- *
- */
-
-#ifndef QXMPPCODEC_H
-#define QXMPPCODEC_H
-
-#include "QXmppGlobal.h"
-
-#include <QMap>
-
-class QXmppRtpPacket;
-class QXmppVideoFormat;
-class QXmppVideoFrame;
-
-/// \brief The QXmppCodec class is the base class for audio codecs capable of
-/// encoding and decoding audio samples.
-///
-/// Samples must be 16-bit little endian.
-
-class QXMPP_AUTOTEST_EXPORT QXmppCodec
-{
-public:
- virtual ~QXmppCodec();
-
- /// Reads samples from the input stream, encodes them and writes the
- /// encoded data to the output stream.
- virtual qint64 encode(QDataStream &input, QDataStream &output) = 0;
-
- /// Reads encoded data from the input stream, decodes it and writes the
- /// decoded samples to the output stream.
- virtual qint64 decode(QDataStream &input, QDataStream &output) = 0;
-};
-
-/// \internal
-///
-/// The QXmppG711aCodec class represent a G.711 a-law PCM codec.
-
-class QXmppG711aCodec : public QXmppCodec
-{
-public:
- QXmppG711aCodec(int clockrate);
-
- qint64 encode(QDataStream &input, QDataStream &output) override;
- qint64 decode(QDataStream &input, QDataStream &output) override;
-
-private:
- int m_frequency;
-};
-
-/// \internal
-///
-/// The QXmppG711uCodec class represent a G.711 u-law PCM codec.
-
-class QXmppG711uCodec : public QXmppCodec
-{
-public:
- QXmppG711uCodec(int clockrate);
-
- qint64 encode(QDataStream &input, QDataStream &output) override;
- qint64 decode(QDataStream &input, QDataStream &output) override;
-
-private:
- int m_frequency;
-};
-
-#ifdef QXMPP_USE_SPEEX
-typedef struct SpeexBits SpeexBits;
-
-/// \internal
-///
-/// The QXmppSpeexCodec class represent a SPEEX codec.
-
-class QXMPP_AUTOTEST_EXPORT QXmppSpeexCodec : public QXmppCodec
-{
-public:
- QXmppSpeexCodec(int clockrate);
- ~QXmppSpeexCodec();
-
- qint64 encode(QDataStream &input, QDataStream &output);
- qint64 decode(QDataStream &input, QDataStream &output);
-
-private:
- SpeexBits *encoder_bits;
- void *encoder_state;
- SpeexBits *decoder_bits;
- void *decoder_state;
- int frame_samples;
-};
-#endif
-
-#ifdef QXMPP_USE_OPUS
-typedef struct OpusEncoder OpusEncoder;
-typedef struct OpusDecoder OpusDecoder;
-
-/// \internal
-///
-/// The QXmppOpusCodec class represent a Opus codec.
-
-class QXMPP_AUTOTEST_EXPORT QXmppOpusCodec : public QXmppCodec
-{
-public:
- QXmppOpusCodec(int clockrate, int channels);
- ~QXmppOpusCodec();
-
- qint64 encode(QDataStream &input, QDataStream &output);
- qint64 decode(QDataStream &input, QDataStream &output);
-
-private:
- OpusEncoder *encoder;
- OpusDecoder *decoder;
- int sampleRate;
- int nChannels;
- QList<float> validFrameSize;
- int nSamples;
- QByteArray sampleBuffer;
-
- int readWindow(int bufferSize);
-};
-#endif
-
-/// \brief The QXmppVideoDecoder class is the base class for video decoders.
-///
-
-class QXMPP_AUTOTEST_EXPORT QXmppVideoDecoder
-{
-public:
- virtual ~QXmppVideoDecoder();
-
- /// Returns the format of the video stream.
- virtual QXmppVideoFormat format() const = 0;
-
- /// Handles an RTP \a packet and returns a list of decoded video frames.
- virtual QList<QXmppVideoFrame> handlePacket(const QXmppRtpPacket &packet) = 0;
-
- /// Sets the video stream's \a parameters.
- virtual bool setParameters(const QMap<QString, QString> &parameters) = 0;
-};
-
-/// \brief The QXmppVideoEncoder class is the base class for video encoders.
-///
-
-class QXMPP_AUTOTEST_EXPORT QXmppVideoEncoder
-{
-public:
- virtual ~QXmppVideoEncoder();
-
- /// Sets the \a format of the video stream.
- virtual bool setFormat(const QXmppVideoFormat &format) = 0;
-
- /// Handles a video \a frame and returns a list of RTP packet payloads.
- virtual QList<QByteArray> handleFrame(const QXmppVideoFrame &frame) = 0;
-
- /// Returns the video stream's parameters.
- virtual QMap<QString, QString> parameters() const = 0;
-};
-
-#ifdef QXMPP_USE_THEORA
-class QXmppTheoraDecoderPrivate;
-class QXmppTheoraEncoderPrivate;
-
-class QXMPP_AUTOTEST_EXPORT QXmppTheoraDecoder : public QXmppVideoDecoder
-{
-public:
- QXmppTheoraDecoder();
- ~QXmppTheoraDecoder();
-
- QXmppVideoFormat format() const;
- QList<QXmppVideoFrame> handlePacket(const QXmppRtpPacket &packet);
- bool setParameters(const QMap<QString, QString> &parameters);
-
-private:
- QXmppTheoraDecoderPrivate *d;
-};
-
-class QXMPP_AUTOTEST_EXPORT QXmppTheoraEncoder : public QXmppVideoEncoder
-{
-public:
- QXmppTheoraEncoder();
- ~QXmppTheoraEncoder();
-
- bool setFormat(const QXmppVideoFormat &format);
- QList<QByteArray> handleFrame(const QXmppVideoFrame &frame);
- QMap<QString, QString> parameters() const;
-
-private:
- QXmppTheoraEncoderPrivate *d;
-};
-#endif
-
-#ifdef QXMPP_USE_VPX
-class QXmppVpxDecoderPrivate;
-class QXmppVpxEncoderPrivate;
-
-class QXMPP_AUTOTEST_EXPORT QXmppVpxDecoder : public QXmppVideoDecoder
-{
-public:
- QXmppVpxDecoder();
- ~QXmppVpxDecoder();
-
- QXmppVideoFormat format() const;
- QList<QXmppVideoFrame> handlePacket(const QXmppRtpPacket &packet);
- bool setParameters(const QMap<QString, QString> &parameters);
-
-private:
- QXmppVpxDecoderPrivate *d;
-};
-
-class QXMPP_AUTOTEST_EXPORT QXmppVpxEncoder : public QXmppVideoEncoder
-{
-public:
- QXmppVpxEncoder(uint clockrate = 0);
- ~QXmppVpxEncoder();
-
- bool setFormat(const QXmppVideoFormat &format);
- QList<QByteArray> handleFrame(const QXmppVideoFrame &frame);
- QMap<QString, QString> parameters() const;
-
-private:
- QXmppVpxEncoderPrivate *d;
-};
-#endif
-
-#endif
diff --git a/src/base/QXmppRtcpPacket.cpp b/src/base/QXmppRtcpPacket.cpp
deleted file mode 100644
index 5ddb96f0..00000000
--- a/src/base/QXmppRtcpPacket.cpp
+++ /dev/null
@@ -1,660 +0,0 @@
-/*
- * Copyright (C) 2008-2020 The QXmpp developers
- *
- * Author:
- * Jeremy Lainé
- *
- * Source:
- * https://github.com/qxmpp-project/qxmpp
- *
- * This file is a part of QXmpp library.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- */
-
-#include "QXmppRtcpPacket.h"
-
-#include <QDataStream>
-#include <QDebug>
-
-#define RTP_VERSION 2
-
-enum DescriptionType {
- CnameType = 1,
- NameType = 2
-};
-
-class QXmppRtcpPacketPrivate : public QSharedData
-{
-public:
- QXmppRtcpPacketPrivate();
-
- /// Number of report blocks.
- quint8 count;
- /// Payload type.
- quint8 type;
- /// Raw payload data.
- QByteArray payload;
-
- QString goodbyeReason;
- QList<quint32> goodbyeSsrcs;
- QXmppRtcpSenderInfo senderInfo;
- QList<QXmppRtcpReceiverReport> receiverReports;
- QList<QXmppRtcpSourceDescription> sourceDescriptions;
- quint32 ssrc;
-};
-
-class QXmppRtcpReceiverReportPrivate : public QSharedData
-{
-public:
- QXmppRtcpReceiverReportPrivate();
- bool read(QDataStream &stream);
- void write(QDataStream &stream) const;
-
- quint32 ssrc;
- quint8 fractionLost;
- quint32 totalLost;
- quint32 highestSequence;
- quint32 jitter;
- quint32 lsr;
- quint32 dlsr;
-};
-
-class QXmppRtcpSenderInfoPrivate : public QSharedData
-{
-public:
- QXmppRtcpSenderInfoPrivate();
- bool read(QDataStream &stream);
- void write(QDataStream &stream) const;
-
- quint64 ntpStamp;
- quint32 rtpStamp;
- quint32 packetCount;
- quint32 octetCount;
-};
-
-class QXmppRtcpSourceDescriptionPrivate : public QSharedData
-{
-public:
- QXmppRtcpSourceDescriptionPrivate();
- bool read(QDataStream &stream);
- void write(QDataStream &stream) const;
-
- quint32 ssrc;
- QString cname;
- QString name;
-};
-
-static bool readPadding(QDataStream &stream, int dataLength)
-{
- if (dataLength % 4) {
- QByteArray buffer;
- buffer.resize(4 - dataLength % 4);
- if (stream.readRawData(buffer.data(), buffer.size()) != buffer.size())
- return false;
- if (buffer != QByteArray(buffer.size(), '\0'))
- return false;
- }
- return true;
-}
-
-static void writePadding(QDataStream &stream, int dataLength)
-{
- if (dataLength % 4) {
- const QByteArray buffer = QByteArray(4 - dataLength % 4, '\0');
- stream.writeRawData(buffer.constData(), buffer.size());
- }
-}
-
-/// Constructs an empty RTCP packet
-
-QXmppRtcpPacket::QXmppRtcpPacket()
- : d(new QXmppRtcpPacketPrivate())
-{
-}
-
-/// Constructs a copy of other.
-///
-/// \param other
-
-QXmppRtcpPacket::QXmppRtcpPacket(const QXmppRtcpPacket &other)
- : d(other.d)
-{
-}
-
-QXmppRtcpPacket::~QXmppRtcpPacket()
-{
-}
-
-/// Parses an RTCP packet.
-///
-/// \param ba
-bool QXmppRtcpPacket::decode(const QByteArray &ba)
-{
- QDataStream stream(ba);
- return read(stream);
-}
-
-/// Encodes an RTCP packet.
-
-QByteArray QXmppRtcpPacket::encode() const
-{
- QByteArray ba;
- ba.resize(4 + d->payload.size());
-
- QDataStream stream(&ba, QIODevice::WriteOnly);
- write(stream);
- return ba;
-}
-
-bool QXmppRtcpPacket::read(QDataStream &stream)
-{
- quint8 tmp, type;
- quint16 len;
-
- // fixed header
- stream >> tmp;
- stream >> type;
- stream >> len;
- if (stream.status() != QDataStream::Ok)
- return false;
-
- // check version
- if ((tmp >> 6) != RTP_VERSION)
- return false;
-
- const int payloadLength = len << 2;
- d->count = (tmp & 0x1f);
- d->type = type;
- d->payload.resize(payloadLength);
- if (stream.readRawData(d->payload.data(), payloadLength) != payloadLength)
- return false;
-
- QDataStream s(d->payload);
- d->goodbyeReason.clear();
- d->goodbyeSsrcs.clear();
- d->receiverReports.clear();
- d->senderInfo = QXmppRtcpSenderInfo();
- d->sourceDescriptions.clear();
- d->ssrc = 0;
- if (d->type == Goodbye) {
- quint32 ssrc;
- for (int i = 0; i < d->count; ++i) {
- s >> ssrc;
- if (stream.status() != QDataStream::Ok)
- return false;
- d->goodbyeSsrcs << ssrc;
- }
- quint8 reasonLength;
- s >> reasonLength;
- if (reasonLength) {
- QByteArray buffer;
- buffer.resize(reasonLength);
- if (s.readRawData(buffer.data(), buffer.size()) != buffer.size())
- return false;
- if (!readPadding(s, 1 + buffer.size()))
- return false;
- d->goodbyeReason = QString::fromUtf8(buffer);
- }
- } else if (d->type == ReceiverReport || d->type == SenderReport) {
- s >> d->ssrc;
- if (d->type == SenderReport && !d->senderInfo.d->read(s))
- return false;
- for (int i = 0; i < d->count; ++i) {
- QXmppRtcpReceiverReport receiverReport;
- if (!receiverReport.d->read(s))
- return false;
- d->receiverReports << receiverReport;
- }
- } else if (d->type == SourceDescription) {
- for (int i = 0; i < d->count; ++i) {
- QXmppRtcpSourceDescription desc;
- if (!desc.d->read(s))
- return false;
- d->sourceDescriptions << desc;
- }
- }
- return true;
-}
-
-void QXmppRtcpPacket::write(QDataStream &stream) const
-{
- QByteArray payload;
- quint8 count;
-
- QDataStream s(&payload, QIODevice::WriteOnly);
- if (d->type == Goodbye) {
- count = d->goodbyeSsrcs.size();
- for (const auto ssrc : d->goodbyeSsrcs)
- s << ssrc;
- if (!d->goodbyeReason.isEmpty()) {
- const QByteArray reason = d->goodbyeReason.toUtf8();
- s << quint8(reason.size());
- s.writeRawData(reason.constData(), reason.size());
- writePadding(s, 1 + reason.size());
- }
- } else if (d->type == ReceiverReport || d->type == SenderReport) {
- count = d->receiverReports.size();
- s << d->ssrc;
- if (d->type == SenderReport)
- d->senderInfo.d->write(s);
- for (const auto &report : d->receiverReports)
- report.d->write(s);
- } else if (d->type == SourceDescription) {
- count = d->sourceDescriptions.size();
- for (const auto &desc : d->sourceDescriptions)
- desc.d->write(s);
- } else {
- count = d->count;
- payload = d->payload;
- }
-
- stream << quint8((RTP_VERSION << 6) | (count & 0x1f));
- stream << d->type;
- stream << quint16(payload.size() >> 2);
- stream.writeRawData(payload.constData(), payload.size());
-}
-
-QString QXmppRtcpPacket::goodbyeReason() const
-{
- return d->goodbyeReason;
-}
-
-void QXmppRtcpPacket::setGoodbyeReason(const QString &goodbyeReason)
-{
- d->goodbyeReason = goodbyeReason;
-}
-
-QList<quint32> QXmppRtcpPacket::goodbyeSsrcs() const
-{
- return d->goodbyeSsrcs;
-}
-
-void QXmppRtcpPacket::setGoodbyeSsrcs(const QList<quint32> &goodbyeSsrcs)
-{
- d->goodbyeSsrcs = goodbyeSsrcs;
-}
-
-QList<QXmppRtcpReceiverReport> QXmppRtcpPacket::receiverReports() const
-{
- return d->receiverReports;
-}
-
-void QXmppRtcpPacket::setReceiverReports(const QList<QXmppRtcpReceiverReport> &reports)
-{
- d->receiverReports = reports;
-}
-
-QXmppRtcpSenderInfo QXmppRtcpPacket::senderInfo() const
-{
- return d->senderInfo;
-}
-
-void QXmppRtcpPacket::setSenderInfo(const QXmppRtcpSenderInfo &senderInfo)
-{
- d->senderInfo = senderInfo;
-}
-
-QList<QXmppRtcpSourceDescription> QXmppRtcpPacket::sourceDescriptions() const
-{
- return d->sourceDescriptions;
-}
-
-void QXmppRtcpPacket::setSourceDescriptions(const QList<QXmppRtcpSourceDescription> &descriptions)
-{
- d->sourceDescriptions = descriptions;
-}
-
-/// Returns the RTCP packet's source SSRC.
-///
-/// This is only applicable for Sender Reports or Receiver Reports.
-
-quint32 QXmppRtcpPacket::ssrc() const
-{
- return d->ssrc;
-}
-
-/// Sets the RTCP packet's source SSRC.
-///
-/// This is only applicable for Sender Reports or Receiver Reports.
-
-void QXmppRtcpPacket::setSsrc(quint32 ssrc)
-{
- d->ssrc = ssrc;
-}
-
-/// Returns the RTCP packet type.
-
-quint8 QXmppRtcpPacket::type() const
-{
- return d->type;
-}
-
-/// Sets the RTCP packet type.
-///
-/// \param type
-
-void QXmppRtcpPacket::setType(quint8 type)
-{
- d->type = type;
-}
-
-QXmppRtcpPacketPrivate::QXmppRtcpPacketPrivate()
- : count(0), type(0), ssrc(0)
-{
-}
-
-/// Constructs an empty receiver report.
-
-QXmppRtcpReceiverReport::QXmppRtcpReceiverReport()
- : d(new QXmppRtcpReceiverReportPrivate())
-{
-}
-
-/// Constructs a copy of other.
-///
-/// \param other
-
-QXmppRtcpReceiverReport::QXmppRtcpReceiverReport(const QXmppRtcpReceiverReport &other)
- : d(other.d)
-{
-}
-
-QXmppRtcpReceiverReport::~QXmppRtcpReceiverReport()
-{
-}
-
-quint32 QXmppRtcpReceiverReport::dlsr() const
-{
- return d->dlsr;
-}
-
-void QXmppRtcpReceiverReport::setDlsr(quint32 dlsr)
-{
- d->dlsr = dlsr;
-}
-
-quint8 QXmppRtcpReceiverReport::fractionLost() const
-{
- return d->fractionLost;
-}
-
-void QXmppRtcpReceiverReport::setFractionLost(quint8 fractionLost)
-{
- d->fractionLost = fractionLost;
-}
-
-quint32 QXmppRtcpReceiverReport::jitter() const
-{
- return d->jitter;
-}
-
-void QXmppRtcpReceiverReport::setJitter(quint32 jitter)
-{
- d->jitter = jitter;
-}
-
-quint32 QXmppRtcpReceiverReport::lsr() const
-{
- return d->lsr;
-}
-
-void QXmppRtcpReceiverReport::setLsr(quint32 lsr)
-{
- d->lsr = lsr;
-}
-
-quint32 QXmppRtcpReceiverReport::ssrc() const
-{
- return d->ssrc;
-}
-
-void QXmppRtcpReceiverReport::setSsrc(quint32 ssrc)
-{
- d->ssrc = ssrc;
-}
-
-quint32 QXmppRtcpReceiverReport::totalLost() const
-{
- return d->totalLost;
-}
-
-void QXmppRtcpReceiverReport::setTotalLost(quint32 totalLost)
-{
- d->totalLost = totalLost;
-}
-
-QXmppRtcpReceiverReportPrivate::QXmppRtcpReceiverReportPrivate()
- : ssrc(0), fractionLost(0), totalLost(0), highestSequence(0), jitter(0), lsr(0), dlsr(0)
-{
-}
-
-bool QXmppRtcpReceiverReportPrivate::read(QDataStream &stream)
-{
- quint32 tmp;
- stream >> ssrc;
- stream >> tmp;
- fractionLost = (tmp >> 24) & 0xff;
- totalLost = tmp & 0xffffff;
- stream >> highestSequence;
- stream >> jitter;
- stream >> lsr;
- stream >> dlsr;
- return stream.status() == QDataStream::Ok;
-}
-
-void QXmppRtcpReceiverReportPrivate::write(QDataStream &stream) const
-{
- stream << ssrc;
- stream << quint32((fractionLost << 24) | (totalLost & 0xffffff));
- stream << highestSequence;
- stream << jitter;
- stream << lsr;
- stream << dlsr;
-}
-
-/// Constructs an empty sender report.
-
-QXmppRtcpSenderInfo::QXmppRtcpSenderInfo()
- : d(new QXmppRtcpSenderInfoPrivate())
-{
-}
-
-/// Constructs a copy of other.
-///
-/// \param other
-
-QXmppRtcpSenderInfo::QXmppRtcpSenderInfo(const QXmppRtcpSenderInfo &other)
- : d(other.d)
-{
-}
-
-QXmppRtcpSenderInfo::~QXmppRtcpSenderInfo()
-{
-}
-
-quint64 QXmppRtcpSenderInfo::ntpStamp() const
-{
- return d->ntpStamp;
-}
-
-void QXmppRtcpSenderInfo::setNtpStamp(quint64 ntpStamp)
-{
- d->ntpStamp = ntpStamp;
-}
-
-quint32 QXmppRtcpSenderInfo::rtpStamp() const
-{
- return d->rtpStamp;
-}
-
-void QXmppRtcpSenderInfo::setRtpStamp(quint32 rtpStamp)
-{
- d->rtpStamp = rtpStamp;
-}
-
-quint32 QXmppRtcpSenderInfo::octetCount() const
-{
- return d->octetCount;
-}
-
-void QXmppRtcpSenderInfo::setOctetCount(quint32 count)
-{
- d->octetCount = count;
-}
-
-quint32 QXmppRtcpSenderInfo::packetCount() const
-{
- return d->packetCount;
-}
-
-void QXmppRtcpSenderInfo::setPacketCount(quint32 count)
-{
- d->packetCount = count;
-}
-
-QXmppRtcpSenderInfoPrivate::QXmppRtcpSenderInfoPrivate()
- : ntpStamp(0), rtpStamp(0), packetCount(0), octetCount(0)
-{
-}
-
-bool QXmppRtcpSenderInfoPrivate::read(QDataStream &stream)
-{
- stream >> ntpStamp;
- stream >> rtpStamp;
- stream >> packetCount;
- stream >> octetCount;
- return stream.status() == QDataStream::Ok;
-}
-
-void QXmppRtcpSenderInfoPrivate::write(QDataStream &stream) const
-{
- stream << ntpStamp;
- stream << rtpStamp;
- stream << packetCount;
- stream << octetCount;
-}
-
-/// Constructs an empty source description
-
-QXmppRtcpSourceDescription::QXmppRtcpSourceDescription()
- : d(new QXmppRtcpSourceDescriptionPrivate())
-{
-}
-
-/// Constructs a copy of other.
-///
-/// \param other
-
-QXmppRtcpSourceDescription::QXmppRtcpSourceDescription(const QXmppRtcpSourceDescription &other)
- : d(other.d)
-{
-}
-
-QXmppRtcpSourceDescription::~QXmppRtcpSourceDescription()
-{
-}
-
-QString QXmppRtcpSourceDescription::cname() const
-{
- return d->cname;
-}
-
-void QXmppRtcpSourceDescription::setCname(const QString &cname)
-{
- d->cname = cname;
-}
-
-QString QXmppRtcpSourceDescription::name() const
-{
- return d->name;
-}
-
-void QXmppRtcpSourceDescription::setName(const QString &name)
-{
- d->name = name;
-}
-
-quint32 QXmppRtcpSourceDescription::ssrc() const
-{
- return d->ssrc;
-}
-
-void QXmppRtcpSourceDescription::setSsrc(quint32 ssrc)
-{
- d->ssrc = ssrc;
-}
-
-QXmppRtcpSourceDescriptionPrivate::QXmppRtcpSourceDescriptionPrivate()
- : ssrc(0)
-{
-}
-
-bool QXmppRtcpSourceDescriptionPrivate::read(QDataStream &stream)
-{
- QByteArray buffer;
- quint8 itemType, itemLength;
- quint16 chunkLength = 0;
-
- stream >> ssrc;
- if (stream.status() != QDataStream::Ok)
- return false;
- while (true) {
- stream >> itemType;
- if (stream.status() != QDataStream::Ok)
- return false;
- if (!itemType) {
- chunkLength++;
- break;
- }
-
- stream >> itemLength;
- if (stream.status() != QDataStream::Ok)
- return false;
-
- buffer.resize(itemLength);
- if (stream.readRawData(buffer.data(), itemLength) != itemLength)
- return false;
- chunkLength += itemLength + 2;
-
- if (itemType == CnameType)
- cname = QString::fromUtf8(buffer);
- else if (itemType == NameType)
- name = QString::fromUtf8(buffer);
- }
- return readPadding(stream, chunkLength);
-}
-
-void QXmppRtcpSourceDescriptionPrivate::write(QDataStream &stream) const
-{
- QByteArray buffer;
- quint16 chunkLength = 0;
-
- stream << ssrc;
- if (!cname.isEmpty()) {
- buffer = cname.toUtf8();
- stream << quint8(CnameType);
- stream << quint8(buffer.size());
- stream.writeRawData(buffer.constData(), buffer.size());
- chunkLength += 2 + buffer.size();
- }
- if (!name.isEmpty()) {
- buffer = name.toUtf8();
- stream << quint8(NameType);
- stream << quint8(buffer.size());
- stream.writeRawData(buffer.constData(), buffer.size());
- chunkLength += 2 + buffer.size();
- }
- stream << quint8(0);
- chunkLength++;
- writePadding(stream, chunkLength);
-}
diff --git a/src/base/QXmppRtcpPacket.h b/src/base/QXmppRtcpPacket.h
deleted file mode 100644
index a2041672..00000000
--- a/src/base/QXmppRtcpPacket.h
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2008-2020 The QXmpp developers
- *
- * Author:
- * Jeremy Lainé
- *
- * Source:
- * https://github.com/qxmpp-project/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.
- *
- */
-
-#ifndef QXMPPRTCPPACKET_H
-#define QXMPPRTCPPACKET_H
-
-#include "QXmppGlobal.h"
-
-#include <QSharedDataPointer>
-
-class QXmppRtcpPacketPrivate;
-class QXmppRtcpReceiverReport;
-class QXmppRtcpReceiverReportPrivate;
-class QXmppRtcpSenderInfo;
-class QXmppRtcpSenderInfoPrivate;
-class QXmppRtcpSourceDescription;
-class QXmppRtcpSourceDescriptionPrivate;
-
-/// \internal
-///
-/// The QXmppRtcpPacket class represents an RTCP packet.
-
-class QXMPP_EXPORT QXmppRtcpPacket
-{
-public:
- enum Type {
- SenderReport = 200,
- ReceiverReport = 201,
- SourceDescription = 202,
- Goodbye = 203,
- };
-
- QXmppRtcpPacket();
- QXmppRtcpPacket(const QXmppRtcpPacket &other);
- ~QXmppRtcpPacket();
-
- bool decode(const QByteArray &ba);
- QByteArray encode() const;
-
- bool read(QDataStream &stream);
- void write(QDataStream &stream) const;
-
- QString goodbyeReason() const;
- void setGoodbyeReason(const QString &goodbyeReason);
-
- QList<quint32> goodbyeSsrcs() const;
- void setGoodbyeSsrcs(const QList<quint32> &goodbyeSsrcs);
-
- QList<QXmppRtcpReceiverReport> receiverReports() const;
- void setReceiverReports(const QList<QXmppRtcpReceiverReport> &reports);
-
- QXmppRtcpSenderInfo senderInfo() const;
- void setSenderInfo(const QXmppRtcpSenderInfo &senderInfo);
-
- QList<QXmppRtcpSourceDescription> sourceDescriptions() const;
- void setSourceDescriptions(const QList<QXmppRtcpSourceDescription> &descriptions);
-
- quint32 ssrc() const;
- void setSsrc(quint32 ssrc);
-
- quint8 type() const;
- void setType(quint8 type);
-
-private:
- QSharedDataPointer<QXmppRtcpPacketPrivate> d;
-};
-
-/// \internal
-
-class QXMPP_EXPORT QXmppRtcpReceiverReport
-{
-public:
- QXmppRtcpReceiverReport();
- QXmppRtcpReceiverReport(const QXmppRtcpReceiverReport &other);
- ~QXmppRtcpReceiverReport();
-
- quint32 dlsr() const;
- void setDlsr(quint32 dlsr);
-
- quint8 fractionLost() const;
- void setFractionLost(quint8 fractionLost);
-
- quint32 jitter() const;
- void setJitter(quint32 jitter);
-
- quint32 lsr() const;
- void setLsr(quint32 lsr);
-
- quint32 ssrc() const;
- void setSsrc(quint32 ssrc);
-
- quint32 totalLost() const;
- void setTotalLost(quint32 totalLost);
-
-private:
- friend class QXmppRtcpPacket;
- QSharedDataPointer<QXmppRtcpReceiverReportPrivate> d;
-};
-
-/// \internal
-
-class QXMPP_EXPORT QXmppRtcpSenderInfo
-{
-public:
- QXmppRtcpSenderInfo();
- QXmppRtcpSenderInfo(const QXmppRtcpSenderInfo &other);
- ~QXmppRtcpSenderInfo();
-
- quint64 ntpStamp() const;
- void setNtpStamp(quint64 ntpStamp);
-
- quint32 rtpStamp() const;
- void setRtpStamp(quint32 rtpStamp);
-
- quint32 octetCount() const;
- void setOctetCount(quint32 count);
-
- quint32 packetCount() const;
- void setPacketCount(quint32 count);
-
-private:
- friend class QXmppRtcpPacket;
- QSharedDataPointer<QXmppRtcpSenderInfoPrivate> d;
-};
-
-/// \internal
-
-class QXMPP_EXPORT QXmppRtcpSourceDescription
-{
-public:
- QXmppRtcpSourceDescription();
- QXmppRtcpSourceDescription(const QXmppRtcpSourceDescription &other);
- ~QXmppRtcpSourceDescription();
-
- QString cname() const;
- void setCname(const QString &name);
-
- QString name() const;
- void setName(const QString &name);
-
- quint32 ssrc() const;
- void setSsrc(const quint32 ssrc);
-
-private:
- friend class QXmppRtcpPacket;
- QSharedDataPointer<QXmppRtcpSourceDescriptionPrivate> d;
-};
-
-#endif
diff --git a/src/base/QXmppRtpChannel.cpp b/src/base/QXmppRtpChannel.cpp
deleted file mode 100644
index 848cd3d3..00000000
--- a/src/base/QXmppRtpChannel.cpp
+++ /dev/null
@@ -1,999 +0,0 @@
-/*
- * Copyright (C) 2008-2020 The QXmpp developers
- *
- * Author:
- * Jeremy Lainé
- *
- * Source:
- * https://github.com/qxmpp-project/qxmpp
- *
- * This file is a part of QXmpp library.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- */
-
-#include "QXmppRtpChannel.h"
-
-#include "QXmppCodec_p.h"
-#include "QXmppJingleIq.h"
-#include "QXmppRtpPacket.h"
-
-#include <cmath>
-
-#include <QDataStream>
-#include <QMetaType>
-#include <QTimer>
-
-#ifndef M_PI
-#define M_PI 3.14159265358979323846264338327950288
-#endif
-
-//#define QXMPP_DEBUG_RTP
-//#define QXMPP_DEBUG_RTP_BUFFER
-#define SAMPLE_BYTES 2
-
-/// Creates a new RTP channel.
-
-QXmppRtpChannel::QXmppRtpChannel()
- : m_outgoingPayloadNumbered(false)
-{
- m_outgoingSsrc = qrand();
-}
-
-/// 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;
-
- for (const auto &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 negotiate a common codec");
- return;
- }
- m_incomingPayloadTypes = commonIncomingTypes;
- m_outgoingPayloadTypes = commonOutgoingTypes;
- m_outgoingPayloadNumbered = true;
-
- // call hook
- payloadTypesChanged();
-}
-
-/// Returns the local SSRC.
-
-quint32 QXmppRtpChannel::localSsrc() const
-{
- return m_outgoingSsrc;
-}
-
-/// Sets the local SSRC.
-///
-/// \param ssrc
-
-void QXmppRtpChannel::setLocalSsrc(quint32 ssrc)
-{
- m_outgoingSsrc = ssrc;
-}
-
-enum CodecId {
- G711u = 0,
- GSM = 3,
- G723 = 4,
- G711a = 8,
- G722 = 9,
- L16Stereo = 10,
- L16Mono = 11,
- G728 = 15,
- G729 = 18
-};
-
-struct ToneInfo {
- QXmppRtpAudioChannel::Tone tone;
- quint32 incomingStart;
- quint32 outgoingStart;
- bool finished;
-};
-
-static QPair<int, int> toneFreqs(QXmppRtpAudioChannel::Tone tone)
-{
- switch (tone) {
- case QXmppRtpAudioChannel::Tone_1:
- return qMakePair(697, 1209);
- case QXmppRtpAudioChannel::Tone_2:
- return qMakePair(697, 1336);
- case QXmppRtpAudioChannel::Tone_3:
- return qMakePair(697, 1477);
- case QXmppRtpAudioChannel::Tone_A:
- return qMakePair(697, 1633);
- case QXmppRtpAudioChannel::Tone_4:
- return qMakePair(770, 1209);
- case QXmppRtpAudioChannel::Tone_5:
- return qMakePair(770, 1336);
- case QXmppRtpAudioChannel::Tone_6:
- return qMakePair(770, 1477);
- case QXmppRtpAudioChannel::Tone_B:
- return qMakePair(770, 1633);
- case QXmppRtpAudioChannel::Tone_7:
- return qMakePair(852, 1209);
- case QXmppRtpAudioChannel::Tone_8:
- return qMakePair(852, 1336);
- case QXmppRtpAudioChannel::Tone_9:
- return qMakePair(852, 1477);
- case QXmppRtpAudioChannel::Tone_C:
- return qMakePair(852, 1633);
- case QXmppRtpAudioChannel::Tone_Star:
- return qMakePair(941, 1209);
- case QXmppRtpAudioChannel::Tone_0:
- return qMakePair(941, 1336);
- case QXmppRtpAudioChannel::Tone_Pound:
- return qMakePair(941, 1477);
- case QXmppRtpAudioChannel::Tone_D:
- return qMakePair(941, 1633);
- }
- return qMakePair(0, 0);
-}
-
-QByteArray renderTone(QXmppRtpAudioChannel::Tone tone, int clockrate, quint32 clockTick, qint64 samples)
-{
- QPair<int, int> tf = toneFreqs(tone);
- const float clockMult = 2.0 * M_PI / float(clockrate);
- QByteArray chunk;
- chunk.reserve(samples * SAMPLE_BYTES);
- QDataStream output(&chunk, QIODevice::WriteOnly);
- output.setByteOrder(QDataStream::LittleEndian);
- for (quint32 i = 0; i < samples; ++i) {
- quint16 val = 16383.0 * (sin(clockMult * clockTick * tf.first) + sin(clockMult * clockTick * tf.second));
- output << val;
- clockTick++;
- }
- return chunk;
-}
-
-class QXmppRtpAudioChannelPrivate
-{
-public:
- QXmppRtpAudioChannelPrivate();
- QXmppCodec *codecForPayloadType(const QXmppJinglePayloadType &payloadType);
-
- // signals
- bool signalsEmitted;
- qint64 writtenSinceLastEmit;
-
- // RTP
- QHostAddress remoteHost;
- quint16 remotePort;
-
- QByteArray incomingBuffer;
- bool incomingBuffering;
- QMap<int, QXmppCodec *> incomingCodecs;
- int incomingMinimum;
- int incomingMaximum;
- // position of the head of the incoming buffer, in bytes
- qint64 incomingPos;
- quint16 incomingSequence;
-
- QByteArray outgoingBuffer;
- quint16 outgoingChunk;
- QXmppCodec *outgoingCodec;
- bool outgoingMarker;
- bool outgoingPayloadNumbered;
- quint16 outgoingSequence;
- quint32 outgoingStamp;
- QTimer *outgoingTimer;
- QList<ToneInfo> outgoingTones;
- QXmppJinglePayloadType outgoingTonesType;
-
- QXmppJinglePayloadType payloadType;
-};
-
-QXmppRtpAudioChannelPrivate::QXmppRtpAudioChannelPrivate()
- : signalsEmitted(false), writtenSinceLastEmit(0), incomingBuffering(true), incomingMinimum(0), incomingMaximum(0), incomingPos(0), incomingSequence(0), outgoingCodec(nullptr), outgoingMarker(true), outgoingPayloadNumbered(false), outgoingSequence(1), outgoingStamp(0), outgoingTimer(nullptr)
-{
- qRegisterMetaType<QXmppRtpAudioChannel::Tone>("QXmppRtpAudioChannel::Tone");
-}
-
-/// Returns the audio codec for the given payload type.
-///
-
-QXmppCodec *QXmppRtpAudioChannelPrivate::codecForPayloadType(const QXmppJinglePayloadType &payloadType)
-{
- if (payloadType.id() == G711u)
- return new QXmppG711uCodec(payloadType.clockrate());
- else if (payloadType.id() == G711a)
- return new QXmppG711aCodec(payloadType.clockrate());
-#ifdef QXMPP_USE_SPEEX
- else if (payloadType.name().toLower() == "speex")
- return new QXmppSpeexCodec(payloadType.clockrate());
-#endif
-#ifdef QXMPP_USE_OPUS
- else if (payloadType.name().toLower() == "opus")
- return new QXmppOpusCodec(payloadType.clockrate(), payloadType.channels());
-#endif
- return nullptr;
-}
-
-/// Constructs a new RTP audio channel with the given \a parent.
-
-QXmppRtpAudioChannel::QXmppRtpAudioChannel(QObject *parent)
- : QIODevice(parent), d(new QXmppRtpAudioChannelPrivate())
-{
- auto *logParent = qobject_cast<QXmppLoggable *>(parent);
- if (logParent) {
- connect(this, &QXmppRtpAudioChannel::logMessage,
- logParent, &QXmppLoggable::logMessage);
- }
- d->outgoingTimer = new QTimer(this);
- connect(d->outgoingTimer, &QTimer::timeout, this, &QXmppRtpAudioChannel::writeDatagram);
-
- // set supported codecs
- QXmppJinglePayloadType payload;
-
-#ifdef QXMPP_USE_OPUS
- payload.setId(100); // NOTE: I don't know if this Id is ok for Opus.
- payload.setChannels(1);
- payload.setName("opus");
- payload.setClockrate(8000);
- m_outgoingPayloadTypes << payload;
-#endif
-
-#ifdef QXMPP_USE_SPEEX
- payload.setId(96);
- payload.setChannels(1);
- payload.setName("speex");
- payload.setClockrate(8000);
- m_outgoingPayloadTypes << payload;
-#endif
-
- payload.setId(G711u);
- payload.setChannels(1);
- payload.setName("PCMU");
- payload.setClockrate(8000);
- m_outgoingPayloadTypes << payload;
-
- payload.setId(G711a);
- payload.setChannels(1);
- payload.setName("PCMA");
- payload.setClockrate(8000);
- m_outgoingPayloadTypes << payload;
-
- QMap<QString, QString> parameters;
- parameters.insert("events", "0-15");
- payload.setId(101);
- payload.setChannels(1);
- payload.setName("telephone-event");
- payload.setClockrate(8000);
- payload.setParameters(parameters);
- m_outgoingPayloadTypes << payload;
-}
-
-/// Destroys an RTP audio channel.
-///
-
-QXmppRtpAudioChannel::~QXmppRtpAudioChannel()
-{
- qDeleteAll(d->incomingCodecs);
-
- if (d->outgoingCodec)
- delete d->outgoingCodec;
- delete d;
-}
-
-/// Returns the number of bytes that are available for reading.
-
-qint64 QXmppRtpAudioChannel::bytesAvailable() const
-{
- return QIODevice::bytesAvailable() + d->incomingBuffer.size();
-}
-
-/// Closes the RTP audio channel.
-
-void QXmppRtpAudioChannel::close()
-{
- d->outgoingTimer->stop();
- QIODevice::close();
-}
-
-/// Processes an incoming RTP packet.
-///
-/// \param ba
-
-void QXmppRtpAudioChannel::datagramReceived(const QByteArray &ba)
-{
- QXmppRtpPacket packet;
- if (!packet.decode(ba))
- return;
-
-#ifdef QXMPP_DEBUG_RTP
- logReceived(packet.toString());
-#endif
-
- // check sequence number
-#if 0
- if (d->incomingSequence && packet.sequence() != d->incomingSequence + 1)
- warning(QString("RTP packet seq %1 is out of order, previous was %2")
- .arg(QString::number(packet.sequence()))
- .arg(QString::number(d->incomingSequence)));
-#endif
- d->incomingSequence = packet.sequence();
-
- // get or create codec
- QXmppCodec *codec = nullptr;
- const quint8 packetType = packet.type();
- if (!d->incomingCodecs.contains(packetType)) {
- for (const auto &payload : m_incomingPayloadTypes) {
- if (packetType == payload.id()) {
- codec = d->codecForPayloadType(payload);
- break;
- }
- }
- if (codec)
- d->incomingCodecs.insert(packetType, codec);
- else
- warning(QString("Could not find codec for RTP type %1").arg(QString::number(packetType)));
- } else {
- codec = d->incomingCodecs.value(packetType);
- }
- if (!codec)
- return;
-
- // determine packet's position in the buffer (in bytes)
- qint64 packetOffset = 0;
- if (!d->incomingBuffer.isEmpty()) {
- packetOffset = packet.stamp() * SAMPLE_BYTES - d->incomingPos;
- if (packetOffset < 0) {
-#ifdef QXMPP_DEBUG_RTP_BUFFER
- warning(QString("RTP packet stamp %1 is too old, buffer start is %2")
- .arg(QString::number(packet.stamp()))
- .arg(QString::number(d->incomingPos)));
-#endif
- return;
- }
- } else {
- d->incomingPos = packet.stamp() * SAMPLE_BYTES + (d->incomingPos % SAMPLE_BYTES);
- }
-
- // allocate space for new packet
- // FIXME: this is wrong, we want the decoded data size!
- const qint64 packetLength = packet.payload().size();
- if (packetOffset + packetLength > d->incomingBuffer.size())
- d->incomingBuffer += QByteArray(packetOffset + packetLength - d->incomingBuffer.size(), 0);
- QDataStream input(packet.payload());
- QDataStream output(&d->incomingBuffer, QIODevice::WriteOnly);
- output.device()->seek(packetOffset);
- output.setByteOrder(QDataStream::LittleEndian);
- codec->decode(input, output);
-
- // check whether we are running late
- if (d->incomingBuffer.size() > d->incomingMaximum) {
- qint64 droppedSize = d->incomingBuffer.size() - d->incomingMinimum;
- const int remainder = droppedSize % SAMPLE_BYTES;
- if (remainder)
- droppedSize -= remainder;
-#ifdef QXMPP_DEBUG_RTP_BUFFER
- warning(QString("Incoming RTP buffer is too full, dropping %1 bytes")
- .arg(QString::number(droppedSize)));
-#endif
- d->incomingBuffer.remove(0, droppedSize);
- d->incomingPos += droppedSize;
- }
- // check whether we have filled the initial buffer
- if (d->incomingBuffer.size() >= d->incomingMinimum)
- d->incomingBuffering = false;
- if (!d->incomingBuffering)
- emit readyRead();
-}
-
-void QXmppRtpAudioChannel::emitSignals()
-{
- emit bytesWritten(d->writtenSinceLastEmit);
- d->writtenSinceLastEmit = 0;
- d->signalsEmitted = false;
-}
-
-/// Returns true, as the RTP channel is a sequential device.
-///
-
-bool QXmppRtpAudioChannel::isSequential() const
-{
- return true;
-}
-
-/// Returns the mode in which the channel has been opened.
-
-QIODevice::OpenMode QXmppRtpAudioChannel::openMode() const
-{
- return QIODevice::openMode();
-}
-
-/// Returns the RTP channel's payload type.
-///
-/// You can use this to determine the QAudioFormat to use with your
-/// QAudioInput/QAudioOutput.
-
-QXmppJinglePayloadType QXmppRtpAudioChannel::payloadType() const
-{
- return d->payloadType;
-}
-
-/// \cond
-qint64 QXmppRtpAudioChannel::readData(char *data, qint64 maxSize)
-{
- // if we are filling the buffer, return empty samples
- if (d->incomingBuffering) {
- // FIXME: if we are asked for a non-integer number of samples,
- // we will return junk on next read as we don't increment d->incomingPos
- memset(data, 0, maxSize);
- return maxSize;
- }
-
- qint64 readSize = qMin(maxSize, qint64(d->incomingBuffer.size()));
- memcpy(data, d->incomingBuffer.constData(), readSize);
- d->incomingBuffer.remove(0, readSize);
- if (readSize < maxSize) {
-#ifdef QXMPP_DEBUG_RTP
- debug(QString("QXmppRtpAudioChannel::readData missing %1 bytes").arg(QString::number(maxSize - readSize)));
-#endif
- memset(data + readSize, 0, maxSize - readSize);
- }
-
- // add local DTMF echo
- if (!d->outgoingTones.isEmpty()) {
- const int headOffset = d->incomingPos % SAMPLE_BYTES;
- const int samples = (headOffset + maxSize + SAMPLE_BYTES - 1) / SAMPLE_BYTES;
- const QByteArray chunk = renderTone(
- d->outgoingTones[0].tone,
- d->payloadType.clockrate(),
- d->incomingPos / SAMPLE_BYTES - d->outgoingTones[0].incomingStart,
- samples);
- memcpy(data, chunk.constData() + headOffset, maxSize);
- }
-
- d->incomingPos += maxSize;
- return maxSize;
-}
-
-void QXmppRtpAudioChannel::payloadTypesChanged()
-{
- // delete incoming codecs
- qDeleteAll(d->incomingCodecs);
-
- // delete outgoing codec
- if (d->outgoingCodec) {
- delete d->outgoingCodec;
- d->outgoingCodec = nullptr;
- }
-
- // create outgoing codec
- for (const auto &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);
-}
-/// \endcond
-
-/// Returns the position in the received audio data.
-
-qint64 QXmppRtpAudioChannel::pos() const
-{
- return d->incomingPos;
-}
-
-/// Seeks in the received audio data.
-///
-/// Seeking backwards will result in empty samples being added at the start
-/// of the buffer.
-///
-/// \param pos
-
-bool QXmppRtpAudioChannel::seek(qint64 pos)
-{
- qint64 delta = pos - d->incomingPos;
- if (delta < 0)
- d->incomingBuffer.prepend(QByteArray(-delta, 0));
- else
- d->incomingBuffer.remove(0, delta);
- d->incomingPos = pos;
- return true;
-}
-
-/// Starts sending the specified DTMF tone.
-///
-/// \param tone
-
-void QXmppRtpAudioChannel::startTone(QXmppRtpAudioChannel::Tone tone)
-{
- ToneInfo info;
- info.tone = tone;
- info.incomingStart = d->incomingPos / SAMPLE_BYTES;
- info.outgoingStart = d->outgoingStamp;
- info.finished = false;
- d->outgoingTones << info;
-}
-
-/// Stops sending the specified DTMF tone.
-///
-/// \param tone
-
-void QXmppRtpAudioChannel::stopTone(QXmppRtpAudioChannel::Tone tone)
-{
- for (auto &outgoingTone : d->outgoingTones) {
- if (outgoingTone.tone == tone) {
- outgoingTone.finished = true;
- break;
- }
- }
-}
-
-/// \cond
-qint64 QXmppRtpAudioChannel::writeData(const char *data, qint64 maxSize)
-{
- if (!d->outgoingCodec) {
- warning("QXmppRtpAudioChannel::writeData before codec was set");
- return -1;
- }
-
- d->outgoingBuffer += QByteArray::fromRawData(data, maxSize);
-
- // start sending audio chunks
- if (!d->outgoingTimer->isActive())
- d->outgoingTimer->start();
-
- return maxSize;
-}
-/// \endcond
-
-void QXmppRtpAudioChannel::writeDatagram()
-{
- // read audio chunk
- QByteArray chunk;
- if (d->outgoingBuffer.size() < d->outgoingChunk) {
-#ifdef QXMPP_DEBUG_RTP_BUFFER
- warning("Outgoing RTP buffer is starved");
-#endif
- chunk = QByteArray(d->outgoingChunk, 0);
- } else {
- chunk = d->outgoingBuffer.left(d->outgoingChunk);
- d->outgoingBuffer.remove(0, d->outgoingChunk);
- }
-
- bool sendAudio = true;
- if (!d->outgoingTones.isEmpty()) {
- const quint32 packetTicks = (d->payloadType.clockrate() * d->payloadType.ptime()) / 1000;
- const ToneInfo info = d->outgoingTones[0];
-
- if (d->outgoingTonesType.id()) {
- // send RFC 2833 DTMF
- QXmppRtpPacket packet;
- packet.setMarker(info.outgoingStart == d->outgoingStamp);
- packet.setType(d->outgoingTonesType.id());
- packet.setSequence(d->outgoingSequence);
- packet.setStamp(info.outgoingStart);
- packet.setSsrc(localSsrc());
-
- QByteArray payload;
- QDataStream output(&payload, QIODevice::WriteOnly);
- output << quint8(info.tone);
- output << quint8(info.finished ? 0x80 : 0x00);
- output << quint16(d->outgoingStamp + packetTicks - info.outgoingStart);
- packet.setPayload(payload);
-#ifdef QXMPP_DEBUG_RTP
- logSent(packet.toString());
-#endif
- emit sendDatagram(packet.encode());
- d->outgoingSequence++;
- d->outgoingStamp += packetTicks;
-
- sendAudio = false;
- } else {
- // generate in-band DTMF
- chunk = renderTone(info.tone, d->payloadType.clockrate(), d->outgoingStamp - info.outgoingStart, packetTicks);
- }
-
- // if the tone is finished, remove it
- if (info.finished)
- d->outgoingTones.removeFirst();
- }
-
- if (sendAudio) {
- // send audio data
- QXmppRtpPacket packet;
- if (d->outgoingMarker) {
- packet.setMarker(true);
- d->outgoingMarker = false;
- } else {
- packet.setMarker(false);
- }
- packet.setType(d->payloadType.id());
- packet.setSequence(d->outgoingSequence);
- packet.setStamp(d->outgoingStamp);
- packet.setSsrc(localSsrc());
-
- // encode audio chunk
- QDataStream input(chunk);
- input.setByteOrder(QDataStream::LittleEndian);
- QByteArray payload;
- QDataStream output(&payload, QIODevice::WriteOnly);
- const qint64 packetTicks = d->outgoingCodec->encode(input, output);
- packet.setPayload(payload);
-
-#ifdef QXMPP_DEBUG_RTP
- logSent(packet.toString());
-#endif
- emit sendDatagram(packet.encode());
- d->outgoingSequence++;
- d->outgoingStamp += packetTicks;
- }
-
- // queue signals
- d->writtenSinceLastEmit += chunk.size();
- if (!d->signalsEmitted && !signalsBlocked()) {
- d->signalsEmitted = true;
- QMetaObject::invokeMethod(this, "emitSignals", Qt::QueuedConnection);
- }
-}
-
-/** Constructs a null video frame.
- */
-QXmppVideoFrame::QXmppVideoFrame()
- : m_bytesPerLine(0),
- m_height(0),
- m_mappedBytes(0),
- m_pixelFormat(Format_Invalid),
- m_width(0)
-{
-}
-
-/** Constructs a video frame of the given pixel format and size in pixels.
- *
- * @param bytes
- * @param size
- * @param bytesPerLine
- * @param format
- */
-QXmppVideoFrame::QXmppVideoFrame(int bytes, const QSize &size, int bytesPerLine, PixelFormat format)
- : m_bytesPerLine(bytesPerLine),
- m_height(size.height()),
- m_mappedBytes(bytes),
- m_pixelFormat(format),
- m_width(size.width())
-{
- m_data.resize(bytes);
-}
-
-/// Returns a pointer to the start of the frame data buffer.
-
-uchar *QXmppVideoFrame::bits()
-{
- return (uchar *)m_data.data();
-}
-
-/// Returns a pointer to the start of the frame data buffer.
-
-const uchar *QXmppVideoFrame::bits() const
-{
- return (const uchar *)m_data.constData();
-}
-
-/// Returns the number of bytes in a scan line.
-
-int QXmppVideoFrame::bytesPerLine() const
-{
- return m_bytesPerLine;
-}
-
-/// Returns the height of a video frame.
-
-int QXmppVideoFrame::height() const
-{
- return m_height;
-}
-
-/// Returns true if the frame is valid.
-
-bool QXmppVideoFrame::isValid() const
-{
- return m_pixelFormat != Format_Invalid &&
- m_height > 0 && m_width > 0 &&
- m_mappedBytes > 0;
-}
-
-/// Returns the number of bytes occupied by the mapped frame data.
-
-int QXmppVideoFrame::mappedBytes() const
-{
- return m_mappedBytes;
-}
-
-/// Returns the color format of a video frame.
-
-QXmppVideoFrame::PixelFormat QXmppVideoFrame::pixelFormat() const
-{
- return m_pixelFormat;
-}
-
-/// Returns the size of a video frame.
-
-QSize QXmppVideoFrame::size() const
-{
- return QSize(m_width, m_height);
-}
-
-/// Returns the width of a video frame.
-
-int QXmppVideoFrame::width() const
-{
- return m_width;
-}
-
-class QXmppRtpVideoChannelPrivate
-{
-public:
- QXmppRtpVideoChannelPrivate();
- QMap<int, QXmppVideoDecoder *> decoders;
- QXmppVideoEncoder *encoder;
- QList<QXmppVideoFrame> frames;
-
- // local
- QXmppVideoFormat outgoingFormat;
- quint8 outgoingId;
- quint16 outgoingSequence;
- quint32 outgoingStamp;
-};
-
-QXmppRtpVideoChannelPrivate::QXmppRtpVideoChannelPrivate()
- : encoder(nullptr),
- outgoingId(0),
- outgoingSequence(1),
- outgoingStamp(0)
-{
-}
-
-/// Constructs a new RTP video channel with the given \a parent.
-
-QXmppRtpVideoChannel::QXmppRtpVideoChannel(QObject *parent)
- : QXmppLoggable(parent)
-{
- d = new QXmppRtpVideoChannelPrivate;
- d->outgoingFormat.setFrameRate(15.0);
- d->outgoingFormat.setFrameSize(QSize(320, 240));
- d->outgoingFormat.setPixelFormat(QXmppVideoFrame::Format_YUYV);
-
- // set supported codecs
- QXmppVideoEncoder *encoder;
- QXmppJinglePayloadType payload;
- Q_UNUSED(encoder);
- Q_UNUSED(payload);
-
-#ifdef QXMPP_USE_VPX
- encoder = new QXmppVpxEncoder;
- encoder->setFormat(d->outgoingFormat);
- payload.setId(96);
- payload.setName("vp8");
- payload.setClockrate(256000);
- payload.setParameters(encoder->parameters());
- m_outgoingPayloadTypes << payload;
- delete encoder;
-#endif
-
-#ifdef QXMPP_USE_THEORA
- encoder = new QXmppTheoraEncoder;
- encoder->setFormat(d->outgoingFormat);
- payload.setId(97);
- payload.setName("theora");
- payload.setClockrate(90000);
- payload.setParameters(encoder->parameters());
- m_outgoingPayloadTypes << payload;
- delete encoder;
-#endif
-}
-
-QXmppRtpVideoChannel::~QXmppRtpVideoChannel()
-{
- qDeleteAll(d->decoders);
- if (d->encoder)
- delete d->encoder;
- delete d;
-}
-
-/// Closes the RTP video channel.
-
-void QXmppRtpVideoChannel::close()
-{
-}
-
-/// Processes an incoming RTP video packet.
-///
-/// \param ba
-
-void QXmppRtpVideoChannel::datagramReceived(const QByteArray &ba)
-{
- QXmppRtpPacket packet;
- if (!packet.decode(ba))
- return;
-
-#ifdef QXMPP_DEBUG_RTP
- logReceived(packet.toString());
-#endif
-
- // get codec
- QXmppVideoDecoder *decoder = d->decoders.value(packet.type());
- if (!decoder)
- return;
- d->frames << decoder->handlePacket(packet);
-}
-
-/// Returns the video format used by the encoder.
-
-QXmppVideoFormat QXmppRtpVideoChannel::decoderFormat() const
-{
- if (d->decoders.isEmpty())
- return QXmppVideoFormat();
- const int key = d->decoders.keys().first();
- return d->decoders.value(key)->format();
-}
-
-/// Returns the video format used by the encoder.
-
-QXmppVideoFormat QXmppRtpVideoChannel::encoderFormat() const
-{
- return d->outgoingFormat;
-}
-
-/// Sets the video format used by the encoder.
-
-void QXmppRtpVideoChannel::setEncoderFormat(const QXmppVideoFormat &format)
-{
- if (d->encoder && !d->encoder->setFormat(format))
- return;
- d->outgoingFormat = format;
-}
-
-/// Returns the mode in which the channel has been opened.
-
-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;
-}
-
-/// \cond
-void QXmppRtpVideoChannel::payloadTypesChanged()
-{
- // refresh decoders
- qDeleteAll(d->decoders);
- d->decoders.clear();
-
- for (const auto &payload : qAsConst(m_incomingPayloadTypes)) {
- QXmppVideoDecoder *decoder = nullptr;
- if (false) {
- }
-#ifdef QXMPP_USE_THEORA
- else if (payload.name().toLower() == "theora")
- decoder = new QXmppTheoraDecoder;
-#endif
-#ifdef QXMPP_USE_VPX
- else if (payload.name().toLower() == "vp8")
- decoder = new QXmppVpxDecoder;
-#endif
- if (decoder) {
- decoder->setParameters(payload.parameters());
- d->decoders.insert(payload.id(), decoder);
- }
- }
-
- // refresh encoder
- if (d->encoder) {
- delete d->encoder;
- d->encoder = nullptr;
- }
- for (const auto &payload : m_outgoingPayloadTypes) {
- QXmppVideoEncoder *encoder = nullptr;
- if (false) {
- }
-#ifdef QXMPP_USE_THEORA
- else if (payload.name().toLower() == "theora")
- encoder = new QXmppTheoraEncoder;
-#endif
-#ifdef QXMPP_USE_VPX
- else if (payload.name().toLower() == "vp8") {
- encoder = new QXmppVpxEncoder(payload.clockrate());
- }
-#endif
- if (encoder) {
- encoder->setFormat(d->outgoingFormat);
- d->encoder = encoder;
- d->outgoingId = payload.id();
- break;
- }
- }
-}
-/// \endcond
-
-/// Decodes buffered RTP packets and returns a list of video frames.
-
-QList<QXmppVideoFrame> QXmppRtpVideoChannel::readFrames()
-{
- const QList<QXmppVideoFrame> frames = d->frames;
- d->frames.clear();
- return frames;
-}
-
-/// Encodes a video \a frame and sends RTP packets.
-
-void QXmppRtpVideoChannel::writeFrame(const QXmppVideoFrame &frame)
-{
- if (!d->encoder) {
- warning("QXmppRtpVideoChannel::writeFrame before codec was set");
- return;
- }
-
- QXmppRtpPacket packet;
- packet.setMarker(false);
- packet.setType(d->outgoingId);
- packet.setSsrc(localSsrc());
- for (const auto &payload : d->encoder->handleFrame(frame)) {
- packet.setSequence(d->outgoingSequence++);
- packet.setStamp(d->outgoingStamp);
- packet.setPayload(payload);
-#ifdef QXMPP_DEBUG_RTP
- logSent(packet.toString());
-#endif
- emit sendDatagram(packet.encode());
- }
- d->outgoingStamp += 1;
-}
diff --git a/src/base/QXmppRtpChannel.h b/src/base/QXmppRtpChannel.h
deleted file mode 100644
index cc934a59..00000000
--- a/src/base/QXmppRtpChannel.h
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2008-2020 The QXmpp developers
- *
- * Author:
- * Jeremy Lainé
- *
- * Source:
- * https://github.com/qxmpp-project/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.
- *
- */
-
-#ifndef QXMPPRTPCHANNEL_H
-#define QXMPPRTPCHANNEL_H
-
-#include "QXmppJingleIq.h"
-#include "QXmppLogger.h"
-
-#include <QIODevice>
-#include <QSize>
-
-class QXmppCodec;
-class QXmppJinglePayloadType;
-class QXmppRtpAudioChannelPrivate;
-class QXmppRtpVideoChannelPrivate;
-
-class QXMPP_EXPORT QXmppRtpChannel
-{
-public:
- QXmppRtpChannel();
-
- /// Closes the RTP channel.
- virtual void close() = 0;
-
- /// Returns the mode in which the channel has been opened.
- virtual QIODevice::OpenMode openMode() const = 0;
-
- QList<QXmppJinglePayloadType> localPayloadTypes();
- void setRemotePayloadTypes(const QList<QXmppJinglePayloadType> &remotePayloadTypes);
-
- quint32 localSsrc() const;
- void setLocalSsrc(quint32 ssrc);
-
-protected:
- /// \cond
- virtual void payloadTypesChanged() = 0;
-
- QList<QXmppJinglePayloadType> m_incomingPayloadTypes;
- QList<QXmppJinglePayloadType> m_outgoingPayloadTypes;
- bool m_outgoingPayloadNumbered;
- /// \endcond
-
-private:
- quint32 m_outgoingSsrc;
-};
-
-/// \brief The QXmppRtpAudioChannel class represents an RTP audio channel to a remote party.
-///
-/// It acts as a QIODevice so that you can read / write audio samples, for
-/// instance using a QAudioOutput and a QAudioInput.
-///
-/// \note THIS API IS NOT FINALIZED YET
-
-class QXMPP_EXPORT QXmppRtpAudioChannel : public QIODevice, public QXmppRtpChannel
-{
- Q_OBJECT
-
-public:
- /// This enum is used to describe a DTMF tone.
- enum Tone {
- Tone_0 = 0, ///< Tone for the 0 key.
- Tone_1, ///< Tone for the 1 key.
- Tone_2, ///< Tone for the 2 key.
- Tone_3, ///< Tone for the 3 key.
- Tone_4, ///< Tone for the 4 key.
- Tone_5, ///< Tone for the 5 key.
- Tone_6, ///< Tone for the 6 key.
- Tone_7, ///< Tone for the 7 key.
- Tone_8, ///< Tone for the 8 key.
- Tone_9, ///< Tone for the 9 key.
- Tone_Star, ///< Tone for the * key.
- Tone_Pound, ///< Tone for the # key.
- Tone_A, ///< Tone for the A key.
- Tone_B, ///< Tone for the B key.
- Tone_C, ///< Tone for the C key.
- Tone_D ///< Tone for the D key.
- };
- Q_ENUM(Tone)
-
- QXmppRtpAudioChannel(QObject *parent = nullptr);
- ~QXmppRtpAudioChannel() override;
-
- qint64 bytesAvailable() const override;
- void close() override;
- bool isSequential() const override;
- QIODevice::OpenMode openMode() const override;
- QXmppJinglePayloadType payloadType() const;
- qint64 pos() const override;
- bool seek(qint64 pos) override;
-
-Q_SIGNALS:
- /// \brief This signal is emitted when a datagram needs to be sent.
- void sendDatagram(const QByteArray &ba);
-
- /// \brief This signal is emitted to send logging messages.
- void logMessage(QXmppLogger::MessageType type, const QString &msg);
-
-public Q_SLOTS:
- void datagramReceived(const QByteArray &ba);
- void startTone(QXmppRtpAudioChannel::Tone tone);
- void stopTone(QXmppRtpAudioChannel::Tone tone);
-
-protected:
- /// \cond
- void debug(const QString &message)
- {
- emit logMessage(QXmppLogger::DebugMessage, qxmpp_loggable_trace(message));
- }
-
- void warning(const QString &message)
- {
- emit logMessage(QXmppLogger::WarningMessage, qxmpp_loggable_trace(message));
- }
-
- void logReceived(const QString &message)
- {
- emit logMessage(QXmppLogger::ReceivedMessage, qxmpp_loggable_trace(message));
- }
-
- void logSent(const QString &message)
- {
- emit logMessage(QXmppLogger::SentMessage, qxmpp_loggable_trace(message));
- }
-
- void payloadTypesChanged() override;
- qint64 readData(char *data, qint64 maxSize) override;
- qint64 writeData(const char *data, qint64 maxSize) override;
- /// \endcond
-
-private Q_SLOTS:
- void emitSignals();
- void writeDatagram();
-
-private:
- friend class QXmppRtpAudioChannelPrivate;
- QXmppRtpAudioChannelPrivate *d;
-};
-
-/// \brief The QXmppVideoFrame class provides a representation of a frame of video data.
-///
-/// \note THIS API IS NOT FINALIZED YET
-
-class QXMPP_EXPORT QXmppVideoFrame
-{
-public:
- /// This enum describes a pixel format.
- enum PixelFormat {
- Format_Invalid = 0, ///< The frame is invalid.
- Format_RGB32 = 3, ///< The frame stored using a 32-bit RGB format (0xffRRGGBB).
- Format_RGB24 = 4, ///< The frame is stored using a 24-bit RGB format (8-8-8).
- Format_YUV420P = 18, ///< The frame is stored using an 8-bit per component planar
- ///< YUV format with the U and V planes horizontally and
- ///< vertically sub-sampled, i.e. the height and width of the
- ///< U and V planes are half that of the Y plane.
- Format_UYVY = 20, ///< The frame is stored using an 8-bit per component packed
- ///< YUV format with the U and V planes horizontally
- ///< sub-sampled (U-Y-V-Y), i.e. two horizontally adjacent
- ///< pixels are stored as a 32-bit macropixel which has a Y
- ///< value for each pixel and common U and V values.
- Format_YUYV = 21 ///< The frame is stored using an 8-bit per component packed
- ///< YUV format with the U and V planes horizontally
- ///< sub-sampled (Y-U-Y-V), i.e. two horizontally adjacent
- ///< pixels are stored as a 32-bit macropixel which has a Y
- ///< value for each pixel and common U and V values.
- };
-
- 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 QXMPP_EXPORT QXmppVideoFormat
-{
-public:
- QXmppVideoFormat()
- : m_frameRate(15.0), m_frameSize(QSize(320, 240)), m_pixelFormat(QXmppVideoFrame::Format_YUYV)
- {
- }
-
- 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;
- }
-
- void setFrameSize(const QSize &frameSize)
- {
- m_frameSize = frameSize;
- }
-
- QXmppVideoFrame::PixelFormat pixelFormat() const
- {
- return m_pixelFormat;
- }
-
- void setPixelFormat(QXmppVideoFrame::PixelFormat pixelFormat)
- {
- m_pixelFormat = pixelFormat;
- }
-
-private:
- qreal m_frameRate;
- QSize m_frameSize;
- QXmppVideoFrame::PixelFormat m_pixelFormat;
-};
-
-/// \brief The QXmppRtpVideoChannel class represents an RTP video channel to a remote party.
-///
-/// \note THIS API IS NOT FINALIZED YET
-
-class QXMPP_EXPORT QXmppRtpVideoChannel : public QXmppLoggable, public QXmppRtpChannel
-{
- Q_OBJECT
-
-public:
- QXmppRtpVideoChannel(QObject *parent = nullptr);
- ~QXmppRtpVideoChannel() override;
-
- void close() override;
- QIODevice::OpenMode openMode() const override;
-
- // incoming stream
- QXmppVideoFormat decoderFormat() const;
- QList<QXmppVideoFrame> readFrames();
-
- // outgoing stream
- QXmppVideoFormat encoderFormat() const;
- void setEncoderFormat(const QXmppVideoFormat &format);
- void writeFrame(const QXmppVideoFrame &frame);
-
-Q_SIGNALS:
- /// \brief This signal is emitted when a datagram needs to be sent.
- void sendDatagram(const QByteArray &ba);
-
-public Q_SLOTS:
- void datagramReceived(const QByteArray &ba);
-
-protected:
- /// \cond
- void payloadTypesChanged() override;
- /// \endcond
-
-private:
- friend class QXmppRtpVideoChannelPrivate;
- QXmppRtpVideoChannelPrivate *d;
-};
-
-#endif
diff --git a/src/base/QXmppRtpPacket.cpp b/src/base/QXmppRtpPacket.cpp
deleted file mode 100644
index 0a467a7f..00000000
--- a/src/base/QXmppRtpPacket.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2008-2020 The QXmpp developers
- *
- * Author:
- * Jeremy Lainé
- *
- * Source:
- * https://github.com/qxmpp-project/qxmpp
- *
- * This file is a part of QXmpp library.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- */
-
-#include "QXmppRtpPacket.h"
-
-#include <QDataStream>
-#include <QSharedData>
-
-#define RTP_VERSION 2
-
-class QXmppRtpPacketPrivate : public QSharedData
-{
-public:
- QXmppRtpPacketPrivate();
-
- /// Marker flag.
- bool marker;
- /// Payload type.
- quint8 type;
- /// Synchronization source.
- quint32 ssrc;
- /// Contributing sources.
- QList<quint32> csrc;
- /// Sequence number.
- quint16 sequence;
- /// Timestamp.
- quint32 stamp;
- /// Raw payload data.
- QByteArray payload;
-};
-
-QXmppRtpPacketPrivate::QXmppRtpPacketPrivate()
- : marker(false), type(0), ssrc(0), sequence(0), stamp(0)
-{
-}
-
-/// Constructs an empty RTP packet
-
-QXmppRtpPacket::QXmppRtpPacket()
- : d(new QXmppRtpPacketPrivate())
-{
-}
-
-/// Constructs a copy of other.
-///
-/// \param other
-///
-QXmppRtpPacket::QXmppRtpPacket(const QXmppRtpPacket &other)
- : d(other.d)
-{
-}
-
-QXmppRtpPacket::~QXmppRtpPacket()
-{
-}
-
-/// Assigns the other packet to this one.
-///
-/// \param other
-///
-QXmppRtpPacket &QXmppRtpPacket::operator=(const QXmppRtpPacket &other)
-{
- d = other.d;
- return *this;
-}
-
-/// 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;
- const quint8 cc = (tmp & 0xf);
- const int hlen = 12 + 4 * cc;
- if ((tmp >> 6) != RTP_VERSION || ba.size() < hlen)
- return false;
- stream >> tmp;
- d->marker = (tmp >> 7);
- d->type = tmp & 0x7f;
- stream >> d->sequence;
- stream >> d->stamp;
- stream >> d->ssrc;
-
- // contributing source IDs
- d->csrc.clear();
- quint32 src;
- for (int i = 0; i < cc; ++i) {
- stream >> src;
- d->csrc << src;
- }
-
- // retrieve payload
- d->payload = ba.right(ba.size() - hlen);
- return true;
-}
-
-/// Encodes an RTP packet.
-
-QByteArray QXmppRtpPacket::encode() const
-{
- Q_ASSERT(d->csrc.size() < 16);
-
- // fixed header
- QByteArray ba;
- ba.resize(d->payload.size() + 12 + 4 * d->csrc.size());
- QDataStream stream(&ba, QIODevice::WriteOnly);
- stream << quint8((RTP_VERSION << 6) |
- (d->csrc.size() & 0xf));
- stream << quint8((d->type & 0x7f) | (d->marker << 7));
- stream << d->sequence;
- stream << d->stamp;
- stream << d->ssrc;
-
- // contributing source ids
- for (const auto &src : d->csrc)
- stream << src;
-
- stream.writeRawData(d->payload.constData(), d->payload.size());
- return ba;
-}
-
-QList<quint32> QXmppRtpPacket::csrc() const
-{
- return d->csrc;
-}
-
-void QXmppRtpPacket::setCsrc(const QList<quint32> &csrc)
-{
- d->csrc = csrc;
-}
-
-bool QXmppRtpPacket::marker() const
-{
- return d->marker;
-}
-
-void QXmppRtpPacket::setMarker(bool marker)
-{
- d->marker = marker;
-}
-
-QByteArray QXmppRtpPacket::payload() const
-{
- return d->payload;
-}
-
-void QXmppRtpPacket::setPayload(const QByteArray &payload)
-{
- d->payload = payload;
-}
-
-quint32 QXmppRtpPacket::ssrc() const
-{
- return d->ssrc;
-}
-
-void QXmppRtpPacket::setSsrc(quint32 ssrc)
-{
- d->ssrc = ssrc;
-}
-
-quint16 QXmppRtpPacket::sequence() const
-{
- return d->sequence;
-}
-
-void QXmppRtpPacket::setSequence(quint16 sequence)
-{
- d->sequence = sequence;
-}
-
-quint32 QXmppRtpPacket::stamp() const
-{
- return d->stamp;
-}
-
-void QXmppRtpPacket::setStamp(quint32 stamp)
-{
- d->stamp = stamp;
-}
-
-quint8 QXmppRtpPacket::type() const
-{
- return d->type;
-}
-
-void QXmppRtpPacket::setType(quint8 type)
-{
- d->type = type;
-}
-
-/// 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(d->sequence), QString::number(d->stamp), QString::number(d->marker), QString::number(d->type), QString::number(d->payload.size()));
-}
diff --git a/src/base/QXmppRtpPacket.h b/src/base/QXmppRtpPacket.h
deleted file mode 100644
index dd479eeb..00000000
--- a/src/base/QXmppRtpPacket.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2008-2020 The QXmpp developers
- *
- * Author:
- * Jeremy Lainé
- *
- * Source:
- * https://github.com/qxmpp-project/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.
- *
- */
-
-#ifndef QXMPPRTPPACKET_H
-#define QXMPPRTPPACKET_H
-
-#include "QXmppGlobal.h"
-
-#include <QSharedDataPointer>
-
-class QXmppRtpPacketPrivate;
-
-/// \internal
-///
-/// The QXmppRtpPacket class represents an RTP packet.
-
-class QXMPP_EXPORT QXmppRtpPacket
-{
-public:
- QXmppRtpPacket();
- QXmppRtpPacket(const QXmppRtpPacket &other);
- ~QXmppRtpPacket();
-
- QXmppRtpPacket &operator=(const QXmppRtpPacket &other);
-
- bool decode(const QByteArray &ba);
- QByteArray encode() const;
- QString toString() const;
-
- QList<quint32> csrc() const;
- void setCsrc(const QList<quint32> &csrc);
-
- bool marker() const;
- void setMarker(bool marker);
-
- QByteArray payload() const;
- void setPayload(const QByteArray &payload);
-
- quint16 sequence() const;
- void setSequence(quint16 sequence);
-
- quint32 ssrc() const;
- void setSsrc(quint32 ssrc);
-
- quint32 stamp() const;
- void setStamp(quint32 stamp);
-
- quint8 type() const;
- void setType(quint8 type);
-
-private:
- QSharedDataPointer<QXmppRtpPacketPrivate> d;
-};
-
-#endif
diff --git a/src/client/QXmppCall.cpp b/src/client/QXmppCall.cpp
new file mode 100644
index 00000000..caf5584b
--- /dev/null
+++ b/src/client/QXmppCall.cpp
@@ -0,0 +1,739 @@
+/*
+ * Copyright (C) 2008-2020 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppCall.h"
+
+#include "QXmppCallManager.h"
+#include "QXmppCallManager_p.h"
+#include "QXmppCallStream.h"
+#include "QXmppCallStream_p.h"
+#include "QXmppCall_p.h"
+#include "QXmppClient.h"
+#include "QXmppConstants_p.h"
+#include "QXmppJingleIq.h"
+#include "QXmppStun.h"
+#include "QXmppUtils.h"
+
+#include <gst/gst.h>
+
+#include <QDomElement>
+#include <QTimer>
+
+QXmppCallPrivate::QXmppCallPrivate(QXmppCall *qq)
+ : direction(QXmppCall::IncomingDirection),
+ manager(0),
+ state(QXmppCall::ConnectingState),
+ nextId(0),
+ q(qq)
+{
+ qRegisterMetaType<QXmppCall::State>();
+
+ filterGStreamerFormats(videoCodecs);
+ filterGStreamerFormats(audioCodecs);
+
+ pipeline = gst_pipeline_new(nullptr);
+ if (!pipeline) {
+ qFatal("Failed to create pipeline");
+ return;
+ }
+ rtpbin = gst_element_factory_make("rtpbin", nullptr);
+ if (!rtpbin) {
+ qFatal("Failed to create rtpbin");
+ return;
+ }
+ // We do not want to build up latency over time
+ g_object_set(rtpbin, "drop-on-latency", true, "async-handling", true, "latency", 25, nullptr);
+ if (!gst_bin_add(GST_BIN(pipeline), rtpbin)) {
+ qFatal("Could not add rtpbin to the pipeline");
+ }
+ g_signal_connect_swapped(rtpbin, "pad-added",
+ G_CALLBACK(+[](QXmppCallPrivate *p, GstPad *pad) {
+ p->padAdded(pad);
+ }),
+ this);
+ g_signal_connect_swapped(rtpbin, "request-pt-map",
+ G_CALLBACK(+[](QXmppCallPrivate *p, uint sessionId, uint pt) {
+ p->ptMap(sessionId, pt);
+ }),
+ this);
+ g_signal_connect_swapped(rtpbin, "on-ssrc-active",
+ G_CALLBACK(+[](QXmppCallPrivate *p, uint sessionId, uint ssrc) {
+ p->ssrcActive(sessionId, ssrc);
+ }),
+ this);
+
+ if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+ qFatal("Unable to set the pipeline to the playing state");
+ return;
+ }
+}
+
+QXmppCallPrivate::~QXmppCallPrivate()
+{
+ if (gst_element_set_state(pipeline, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
+ qFatal("Unable to set the pipeline to the null state");
+ }
+ for (auto stream : streams) {
+ delete stream;
+ }
+ gst_object_unref(pipeline);
+}
+
+void QXmppCallPrivate::ssrcActive(uint sessionId, uint ssrc)
+{
+ Q_UNUSED(ssrc)
+ GstElement *rtpSession;
+ g_signal_emit_by_name(rtpbin, "get-session", static_cast<uint>(sessionId), &rtpSession);
+ // TODO: implement bitrate controller
+}
+
+void QXmppCallPrivate::padAdded(GstPad *pad)
+{
+ auto nameParts = QString(gst_pad_get_name(pad)).split("_");
+ if (nameParts.size() < 4) {
+ return;
+ }
+ if (nameParts[0] == QLatin1String("send") &&
+ nameParts[1] == QLatin1String("rtp") &&
+ nameParts[2] == QLatin1String("src")) {
+ if (nameParts.size() != 4) {
+ return;
+ }
+ int sessionId = nameParts[3].toInt();
+ auto stream = findStreamById(sessionId);
+ stream->d->addRtpSender(pad);
+ } else if (nameParts[0] == QLatin1String("recv") ||
+ nameParts[1] == QLatin1String("rtp") ||
+ nameParts[2] == QLatin1String("src")) {
+ if (nameParts.size() != 6) {
+ return;
+ }
+ int sessionId = nameParts[3].toInt();
+ int pt = nameParts[5].toInt();
+ auto stream = findStreamById(sessionId);
+ if (stream->media() == VIDEO_MEDIA) {
+ for (auto &codec : videoCodecs) {
+ if (codec.pt == pt) {
+ stream->d->addDecoder(pad, codec);
+ return;
+ }
+ }
+ } else if (stream->media() == AUDIO_MEDIA) {
+ for (auto &codec : audioCodecs) {
+ if (codec.pt == pt) {
+ stream->d->addDecoder(pad, codec);
+ return;
+ }
+ }
+ }
+ }
+}
+
+GstCaps *QXmppCallPrivate::ptMap(uint sessionId, uint pt)
+{
+ auto stream = findStreamById(sessionId);
+ for (auto &payloadType : stream->d->payloadTypes) {
+ if (payloadType.id() == pt) {
+ return gst_caps_new_simple("application/x-rtp",
+ "media", G_TYPE_STRING, stream->media().toLatin1().data(),
+ "clock-rate", G_TYPE_INT, payloadType.clockrate(),
+ "encoding-name", G_TYPE_STRING, payloadType.name().toLatin1().data(),
+ nullptr);
+ }
+ }
+ q->warning(QString("Remote party %1 transmits wrong %2 payload for call %3").arg(jid, stream->media(), sid));
+ return nullptr;
+}
+
+bool QXmppCallPrivate::isFormatSupported(const QString &codecName) const
+{
+ GstElementFactory *factory;
+ factory = gst_element_factory_find(codecName.toLatin1().data());
+ if (!factory) {
+ return false;
+ }
+ g_object_unref(factory);
+ return true;
+}
+
+void QXmppCallPrivate::filterGStreamerFormats(QList<GstCodec> &formats)
+{
+ auto it = formats.begin();
+ while (it != formats.end()) {
+ bool supported = isFormatSupported(it->gstPay) &&
+ isFormatSupported(it->gstDepay) &&
+ isFormatSupported(it->gstEnc) &&
+ isFormatSupported(it->gstDec);
+ if (!supported) {
+ it = formats.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+QXmppCallStream *QXmppCallPrivate::findStreamByMedia(const QString &media)
+{
+ for (auto stream : streams) {
+ if (stream->media() == media) {
+ return stream;
+ }
+ }
+ return nullptr;
+}
+
+QXmppCallStream *QXmppCallPrivate::findStreamByName(const QString &name)
+{
+ for (auto stream : streams) {
+ if (stream->name() == name) {
+ return stream;
+ }
+ }
+ return nullptr;
+}
+
+QXmppCallStream *QXmppCallPrivate::findStreamById(const int id)
+{
+ for (auto stream : streams) {
+ if (stream->id() == id) {
+ return stream;
+ }
+ }
+ return nullptr;
+}
+
+void QXmppCallPrivate::handleAck(const QXmppIq &ack)
+{
+ const QString id = ack.id();
+ for (int i = 0; i < requests.size(); ++i) {
+ if (id == requests[i].id()) {
+ // process acknowledgement
+ const QXmppJingleIq request = requests.takeAt(i);
+ q->debug(QString("Received ACK for packet %1").arg(id));
+
+ // handle termination
+ if (request.action() == QXmppJingleIq::SessionTerminate)
+ q->terminated();
+ return;
+ }
+ }
+}
+
+bool QXmppCallPrivate::handleDescription(QXmppCallStream *stream, const QXmppJingleIq::Content &content)
+{
+ stream->d->payloadTypes = content.payloadTypes();
+ auto it = stream->d->payloadTypes.begin();
+ bool foundCandidate = false;
+ while (it != stream->d->payloadTypes.end()) {
+ bool dynamic = it->id() >= 96;
+ bool supported = false;
+ auto codecs = stream->media() == AUDIO_MEDIA ? audioCodecs : videoCodecs;
+ for (auto &codec : codecs) {
+ if (dynamic) {
+ if (codec.name == it->name() &&
+ codec.clockrate == it->clockrate() &&
+ codec.channels == it->channels()) {
+ if (!foundCandidate) {
+ stream->d->addEncoder(codec);
+ foundCandidate = true;
+ }
+ supported = true;
+ /* Adopt id from other side. */
+ codec.pt = it->id();
+ }
+ } else {
+ if (codec.pt == it->id() &&
+ codec.clockrate == it->clockrate() &&
+ codec.channels == it->channels()) {
+ if (!foundCandidate) {
+ stream->d->addEncoder(codec);
+ foundCandidate = true;
+ }
+ supported = true;
+ /* Keep our name just to be sure */
+ codec.name = it->name();
+ }
+ }
+ }
+
+ if (!supported) {
+ it = stream->d->payloadTypes.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ if (stream->d->payloadTypes.empty()) {
+ q->warning(QString("Remote party %1 did not provide any known %2 payloads for call %3").arg(jid, stream->media(), sid));
+ return false;
+ }
+
+ return true;
+}
+
+bool QXmppCallPrivate::handleTransport(QXmppCallStream *stream, const QXmppJingleIq::Content &content)
+{
+ stream->d->connection->setRemoteUser(content.transportUser());
+ stream->d->connection->setRemotePassword(content.transportPassword());
+ for (const QXmppJingleCandidate &candidate : content.transportCandidates()) {
+ stream->d->connection->addRemoteCandidate(candidate);
+ }
+
+ // perform ICE negotiation
+ if (!content.transportCandidates().isEmpty()) {
+ stream->d->connection->connectToHost();
+ }
+ return true;
+}
+
+void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq)
+{
+ const QXmppJingleIq::Content content = iq.contents().isEmpty() ? QXmppJingleIq::Content() : iq.contents().first();
+
+ if (iq.action() == QXmppJingleIq::SessionAccept) {
+
+ if (direction == QXmppCall::IncomingDirection) {
+ q->warning("Ignoring Session-Accept for an incoming call");
+ return;
+ }
+
+ // send ack
+ sendAck(iq);
+
+ // check content description and transport
+ QXmppCallStream *stream = findStreamByName(content.name());
+ if (!stream ||
+ !handleDescription(stream, content) ||
+ !handleTransport(stream, content)) {
+
+ // terminate call
+ terminate(QXmppJingleIq::Reason::FailedApplication);
+ return;
+ }
+
+ // check for call establishment
+ setState(QXmppCall::ActiveState);
+
+ } else if (iq.action() == QXmppJingleIq::SessionInfo) {
+
+ // notify user
+ QTimer::singleShot(0, q, SIGNAL(ringing()));
+
+ } else if (iq.action() == QXmppJingleIq::SessionTerminate) {
+
+ // send ack
+ sendAck(iq);
+
+ // terminate
+ q->info(QString("Remote party %1 terminated call %2").arg(iq.from(), iq.sid()));
+ q->terminated();
+
+ } else if (iq.action() == QXmppJingleIq::ContentAccept) {
+
+ // send ack
+ sendAck(iq);
+
+ // check content description and transport
+ QXmppCallStream *stream = findStreamByName(content.name());
+ if (!stream ||
+ !handleDescription(stream, content) ||
+ !handleTransport(stream, content)) {
+
+ // FIXME: what action?
+ return;
+ }
+
+ } else if (iq.action() == QXmppJingleIq::ContentAdd) {
+
+ // send ack
+ sendAck(iq);
+
+ // check media stream does not exist yet
+ QXmppCallStream *stream = findStreamByName(content.name());
+ if (stream)
+ return;
+
+ // create media stream
+ stream = createStream(content.descriptionMedia(), content.creator(), content.name());
+ if (!stream)
+ return;
+ streams << stream;
+
+ // check content description
+ if (!handleDescription(stream, content) ||
+ !handleTransport(stream, content)) {
+
+ QXmppJingleIq iq;
+ iq.setTo(q->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentReject);
+ iq.setSid(q->sid());
+ iq.reason().setType(QXmppJingleIq::Reason::FailedApplication);
+ sendRequest(iq);
+ streams.removeAll(stream);
+ delete stream;
+ return;
+ }
+
+ // accept content
+ QXmppJingleIq iq;
+ iq.setTo(q->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentAccept);
+ iq.setSid(q->sid());
+ iq.addContent(localContent(stream));
+ sendRequest(iq);
+
+ } else if (iq.action() == QXmppJingleIq::TransportInfo) {
+
+ // send ack
+ sendAck(iq);
+
+ // check content transport
+ QXmppCallStream *stream = findStreamByName(content.name());
+ if (!stream ||
+ !handleTransport(stream, content)) {
+ // FIXME: what action?
+ return;
+ }
+ }
+}
+
+QXmppCallStream *QXmppCallPrivate::createStream(const QString &media, const QString &creator, const QString &name)
+{
+ bool check;
+ Q_UNUSED(check);
+ Q_ASSERT(manager);
+
+ if (media != AUDIO_MEDIA && media != VIDEO_MEDIA) {
+ q->warning(QString("Unsupported media type %1").arg(media));
+ return nullptr;
+ }
+
+ if (!isFormatSupported("rtpbin")) {
+ q->warning("The rtpbin GStreamer plugin is missing. Calls are not possible.");
+ return nullptr;
+ }
+
+ QXmppCallStream *stream = new QXmppCallStream(pipeline, rtpbin, media, creator, name, ++nextId);
+
+ // Fill local payload payload types
+ auto &codecs = media == AUDIO_MEDIA ? audioCodecs : videoCodecs;
+ for (auto &codec : codecs) {
+ QXmppJinglePayloadType payloadType;
+ payloadType.setId(codec.pt);
+ payloadType.setName(codec.name);
+ payloadType.setChannels(codec.channels);
+ payloadType.setClockrate(codec.clockrate);
+ stream->d->payloadTypes.append(payloadType);
+ }
+
+ // ICE connection
+ stream->d->connection->setIceControlling(direction == QXmppCall::OutgoingDirection);
+ stream->d->connection->setStunServer(manager->d->stunHost, manager->d->stunPort);
+ stream->d->connection->setTurnServer(manager->d->turnHost, manager->d->turnPort);
+ stream->d->connection->setTurnUser(manager->d->turnUser);
+ stream->d->connection->setTurnPassword(manager->d->turnPassword);
+ stream->d->connection->bind(QXmppIceComponent::discoverAddresses());
+
+ // connect signals
+ check = QObject::connect(stream->d->connection, SIGNAL(localCandidatesChanged()),
+ q, SLOT(localCandidatesChanged()));
+ Q_ASSERT(check);
+
+ check = QObject::connect(stream->d->connection, SIGNAL(disconnected()),
+ q, SLOT(hangup()));
+ Q_ASSERT(check);
+
+ Q_EMIT q->streamCreated(stream);
+
+ return stream;
+}
+
+QXmppJingleIq::Content QXmppCallPrivate::localContent(QXmppCallStream *stream) const
+{
+ QXmppJingleIq::Content content;
+ content.setCreator(stream->creator());
+ content.setName(stream->name());
+ content.setSenders("both");
+
+ // description
+ content.setDescriptionMedia(stream->media());
+ content.setDescriptionSsrc(stream->d->localSsrc);
+ content.setPayloadTypes(stream->d->payloadTypes);
+
+ // transport
+ content.setTransportUser(stream->d->connection->localUser());
+ content.setTransportPassword(stream->d->connection->localPassword());
+ content.setTransportCandidates(stream->d->connection->localCandidates());
+
+ return content;
+}
+
+/// Sends an acknowledgement for a Jingle IQ.
+///
+
+bool QXmppCallPrivate::sendAck(const QXmppJingleIq &iq)
+{
+ QXmppIq ack;
+ ack.setId(iq.id());
+ ack.setTo(iq.from());
+ ack.setType(QXmppIq::Result);
+ return manager->client()->sendPacket(ack);
+}
+
+bool QXmppCallPrivate::sendInvite()
+{
+ // create audio stream
+ QXmppCallStream *stream = findStreamByMedia(AUDIO_MEDIA);
+ Q_ASSERT(stream);
+
+ QXmppJingleIq iq;
+ iq.setTo(jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionInitiate);
+ iq.setInitiator(ownJid);
+ iq.setSid(sid);
+ iq.addContent(localContent(stream));
+ return sendRequest(iq);
+}
+
+/// Sends a Jingle IQ and adds it to outstanding requests.
+///
+
+bool QXmppCallPrivate::sendRequest(const QXmppJingleIq &iq)
+{
+ requests << iq;
+ return manager->client()->sendPacket(iq);
+}
+
+void QXmppCallPrivate::setState(QXmppCall::State newState)
+{
+ if (state != newState) {
+ state = newState;
+ Q_EMIT q->stateChanged(state);
+
+ if (state == QXmppCall::ActiveState)
+ Q_EMIT q->connected();
+ else if (state == QXmppCall::FinishedState)
+ Q_EMIT q->finished();
+ }
+}
+
+/// Request graceful call termination
+
+void QXmppCallPrivate::terminate(QXmppJingleIq::Reason::Type reasonType)
+{
+ if (state == QXmppCall::DisconnectingState ||
+ state == QXmppCall::FinishedState)
+ return;
+
+ // hangup call
+ QXmppJingleIq iq;
+ iq.setTo(jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionTerminate);
+ iq.setSid(sid);
+ iq.reason().setType(reasonType);
+ sendRequest(iq);
+ setState(QXmppCall::DisconnectingState);
+
+ // schedule forceful termination in 5s
+ QTimer::singleShot(5000, q, SLOT(terminated()));
+}
+
+QXmppCall::QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent)
+ : QXmppLoggable(parent)
+{
+ d = new QXmppCallPrivate(this);
+ d->direction = direction;
+ d->jid = jid;
+ d->ownJid = parent->client()->configuration().jid();
+ d->manager = parent;
+}
+
+QXmppCall::~QXmppCall()
+{
+ delete d;
+}
+
+/// Call this method if you wish to accept an incoming call.
+///
+
+void QXmppCall::accept()
+{
+ if (d->direction == IncomingDirection && d->state == ConnectingState) {
+ Q_ASSERT(d->streams.size() == 1);
+ QXmppCallStream *stream = d->streams.first();
+
+ // accept incoming call
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionAccept);
+ iq.setResponder(d->ownJid);
+ iq.setSid(d->sid);
+ iq.addContent(d->localContent(stream));
+ d->sendRequest(iq);
+
+ // notify user
+ d->manager->callStarted(this);
+
+ // check for call establishment
+ d->setState(QXmppCall::ActiveState);
+ }
+}
+
+/// Returns the GStreamer pipeline.
+///
+/// \since QXmpp 1.3
+
+GstElement *QXmppCall::pipeline() const
+{
+ return d->pipeline;
+}
+
+/// Returns the RTP stream for the audio data.
+///
+/// \since QXmpp 1.2
+
+QXmppCallStream *QXmppCall::audioStream() const
+{
+ return d->findStreamByMedia(AUDIO_MEDIA);
+}
+
+/// Returns the RTP stream for the video data.
+///
+/// \since QXmpp 1.2
+
+QXmppCallStream *QXmppCall::videoStream() const
+{
+ return d->findStreamByMedia(VIDEO_MEDIA);
+}
+
+void QXmppCall::terminated()
+{
+ // close streams
+ for (auto stream : d->streams) {
+ stream->d->connection->close();
+ }
+
+ // update state
+ d->setState(QXmppCall::FinishedState);
+}
+
+/// Returns the call's direction.
+///
+
+QXmppCall::Direction QXmppCall::direction() const
+{
+ return d->direction;
+}
+
+/// Hangs up the call.
+///
+
+void QXmppCall::hangup()
+{
+ d->terminate(QXmppJingleIq::Reason::None);
+}
+
+/// Sends a transport-info to inform the remote party of new local candidates.
+///
+
+void QXmppCall::localCandidatesChanged()
+{
+ // find the stream
+ QXmppIceConnection *conn = qobject_cast<QXmppIceConnection *>(sender());
+ QXmppCallStream *stream = 0;
+ for (auto ptr : d->streams) {
+ if (ptr->d->connection == conn) {
+ stream = ptr;
+ break;
+ }
+ }
+ if (!stream)
+ return;
+
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::TransportInfo);
+ iq.setSid(d->sid);
+ iq.addContent(d->localContent(stream));
+ d->sendRequest(iq);
+}
+
+/// Returns the remote party's JID.
+///
+
+QString QXmppCall::jid() const
+{
+ return d->jid;
+}
+
+/// Returns the call's session identifier.
+///
+
+QString QXmppCall::sid() const
+{
+ return d->sid;
+}
+
+/// Returns the call's state.
+///
+/// \sa stateChanged()
+
+QXmppCall::State QXmppCall::state() const
+{
+ return d->state;
+}
+
+/// Starts sending video to the remote party.
+
+void QXmppCall::addVideo()
+{
+ if (d->state != QXmppCall::ActiveState) {
+ warning("Cannot add video, call is not active");
+ return;
+ }
+
+ QXmppCallStream *stream = d->findStreamByMedia(VIDEO_MEDIA);
+ if (stream) {
+ return;
+ }
+
+ // create video stream
+ QLatin1String creator = (d->direction == QXmppCall::OutgoingDirection) ? QLatin1String("initiator") : QLatin1String("responder");
+ stream = d->createStream(VIDEO_MEDIA, creator, QLatin1String("webcam"));
+ d->streams << stream;
+
+ // build request
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentAdd);
+ iq.setSid(d->sid);
+ iq.addContent(d->localContent(stream));
+ d->sendRequest(iq);
+}
diff --git a/src/client/QXmppCall.h b/src/client/QXmppCall.h
new file mode 100644
index 00000000..8bc91b71
--- /dev/null
+++ b/src/client/QXmppCall.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2008-2020 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * https://github.com/qxmpp-project/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.
+ *
+ */
+
+#ifndef QXMPPCALL_H
+#define QXMPPCALL_H
+
+#include "QXmppCallStream.h"
+#include "QXmppClientExtension.h"
+#include "QXmppLogger.h"
+
+#include <QMetaType>
+#include <QObject>
+
+class QHostAddress;
+class QXmppCallPrivate;
+class QXmppCallManager;
+class QXmppCallManagerPrivate;
+
+/// \brief The QXmppCall class represents a Voice-Over-IP call to a remote party.
+///
+/// \note THIS API IS NOT FINALIZED YET
+
+class QXMPP_EXPORT QXmppCall : public QXmppLoggable
+{
+ Q_OBJECT
+ Q_PROPERTY(Direction direction READ direction CONSTANT)
+ Q_PROPERTY(QString jid READ jid CONSTANT)
+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
+
+public:
+ /// This enum is used to describe the direction of a call.
+ enum Direction {
+ IncomingDirection, ///< The call is incoming.
+ OutgoingDirection ///< The call is outgoing.
+ };
+ Q_ENUM(Direction)
+
+ /// This enum is used to describe the state of a call.
+ enum State {
+ ConnectingState = 0, ///< The call is being connected.
+ ActiveState = 1, ///< The call is active.
+ DisconnectingState = 2, ///< The call is being disconnected.
+ FinishedState = 3 ///< The call is finished.
+ };
+ Q_ENUM(State)
+
+ ~QXmppCall();
+
+ QXmppCall::Direction direction() const;
+ QString jid() const;
+ QString sid() const;
+ QXmppCall::State state() const;
+
+ GstElement *pipeline() const;
+ QXmppCallStream *audioStream() const;
+ QXmppCallStream *videoStream() const;
+
+signals:
+ /// \brief This signal is emitted when a call is connected.
+ ///
+ /// Once this signal is emitted, you can connect a QAudioOutput and
+ /// QAudioInput to the call. You can determine the appropriate clockrate
+ /// and the number of channels by calling payloadType().
+ void connected();
+
+ /// \brief This signal is emitted when a call is finished.
+ ///
+ /// Note: Do not delete the call in the slot connected to this signal,
+ /// instead use deleteLater().
+ void finished();
+
+ /// \brief This signal is emitted when the remote party is ringing.
+ void ringing();
+
+ /// \brief This signal is emitted when the call state changes.
+ void stateChanged(QXmppCall::State state);
+
+ /// \brief This signal is emitted when a stream is created.
+ void streamCreated(QXmppCallStream *stream);
+
+public slots:
+ void accept();
+ void hangup();
+ void addVideo();
+
+private slots:
+ void localCandidatesChanged();
+ void terminated();
+
+private:
+ QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent);
+
+ QXmppCallPrivate *d;
+ friend class QXmppCallManager;
+ friend class QXmppCallManagerPrivate;
+ friend class QXmppCallPrivate;
+};
+
+Q_DECLARE_METATYPE(QXmppCall::State)
+
+#endif
diff --git a/src/client/QXmppCallManager.cpp b/src/client/QXmppCallManager.cpp
index 6ce6687a..8ba97c2d 100644
--- a/src/client/QXmppCallManager.cpp
+++ b/src/client/QXmppCallManager.cpp
@@ -23,659 +23,27 @@
#include "QXmppCallManager.h"
+#include "QXmppCall.h"
+#include "QXmppCallManager_p.h"
+#include "QXmppCall_p.h"
#include "QXmppClient.h"
#include "QXmppConstants_p.h"
#include "QXmppJingleIq.h"
-#include "QXmppRtpChannel.h"
#include "QXmppStun.h"
#include "QXmppUtils.h"
+#include <gst/gst.h>
+
#include <QDomElement>
#include <QTimer>
-static const int RTP_COMPONENT = 1;
-static const int RTCP_COMPONENT = 2;
-
-static const QLatin1String AUDIO_MEDIA("audio");
-static const QLatin1String VIDEO_MEDIA("video");
-
-class QXmppCallPrivate
-{
-public:
- class Stream
- {
- public:
- QXmppRtpChannel *channel;
- QXmppIceConnection *connection;
- QString creator;
- QString media;
- QString name;
- };
-
- QXmppCallPrivate(QXmppCall *qq);
- Stream *createStream(const QString &media);
- Stream *findStreamByMedia(const QString &media);
- Stream *findStreamByName(const QString &name);
- QXmppJingleIq::Content localContent(QXmppCallPrivate::Stream *stream) const;
-
- void handleAck(const QXmppIq &iq);
- bool handleDescription(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content);
- void handleRequest(const QXmppJingleIq &iq);
- bool handleTransport(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content);
- void setState(QXmppCall::State state);
- bool sendAck(const QXmppJingleIq &iq);
- bool sendInvite();
- bool sendRequest(const QXmppJingleIq &iq);
- void terminate(QXmppJingleIq::Reason::Type reasonType);
-
- QXmppCall::Direction direction;
- QString jid;
- QString ownJid;
- QXmppCallManager *manager;
- QList<QXmppJingleIq> requests;
- QString sid;
- QXmppCall::State state;
-
- // Media streams
- bool sendVideo;
- QList<Stream *> streams;
- QIODevice::OpenMode audioMode;
- QIODevice::OpenMode videoMode;
-
-private:
- QXmppCall *q;
-};
-
-class QXmppCallManagerPrivate
-{
-public:
- QXmppCallManagerPrivate(QXmppCallManager *qq);
- QXmppCall *findCall(const QString &sid) const;
- QXmppCall *findCall(const QString &sid, QXmppCall::Direction direction) const;
-
- QList<QXmppCall *> calls;
- QHostAddress stunHost;
- quint16 stunPort;
- QHostAddress turnHost;
- quint16 turnPort;
- QString turnUser;
- QString turnPassword;
-
-private:
- QXmppCallManager *q;
-};
-
-QXmppCallPrivate::QXmppCallPrivate(QXmppCall *qq)
- : direction(QXmppCall::IncomingDirection),
- manager(nullptr),
- state(QXmppCall::ConnectingState),
- sendVideo(false),
- audioMode(QIODevice::NotOpen),
- videoMode(QIODevice::NotOpen),
- q(qq)
-{
- qRegisterMetaType<QXmppCall::State>();
-}
-
-QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByMedia(const QString &media)
-{
- for (auto *stream : streams)
- if (stream->media == media)
- return stream;
- return nullptr;
-}
-
-QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByName(const QString &name)
-{
- for (auto *stream : streams)
- if (stream->name == name)
- return stream;
- return nullptr;
-}
-
-void QXmppCallPrivate::handleAck(const QXmppIq &ack)
-{
- const QString id = ack.id();
- for (int i = 0; i < requests.size(); ++i) {
- if (id == requests[i].id()) {
- // process acknowledgement
- const QXmppJingleIq request = requests.takeAt(i);
- q->debug(QString("Received ACK for packet %1").arg(id));
-
- // handle termination
- if (request.action() == QXmppJingleIq::SessionTerminate)
- q->terminated();
- return;
- }
- }
-}
-
-bool QXmppCallPrivate::handleDescription(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content)
-{
- stream->channel->setRemotePayloadTypes(content.payloadTypes());
- if (!(stream->channel->openMode() & QIODevice::ReadWrite)) {
- q->warning(QString("Remote party %1 did not provide any known %2 payloads for call %3").arg(jid, stream->media, sid));
- return false;
- }
- q->updateOpenMode();
- return true;
-}
-
-bool QXmppCallPrivate::handleTransport(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content)
-{
- stream->connection->setRemoteUser(content.transportUser());
- stream->connection->setRemotePassword(content.transportPassword());
- const auto &candidates = content.transportCandidates();
- for (const auto &candidate : candidates)
- stream->connection->addRemoteCandidate(candidate);
-
- // perform ICE negotiation
- if (!content.transportCandidates().isEmpty())
- stream->connection->connectToHost();
- return true;
-}
-
-void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq)
-{
- const QXmppJingleIq::Content content = iq.contents().isEmpty() ? QXmppJingleIq::Content() : iq.contents().first();
-
- if (iq.action() == QXmppJingleIq::SessionAccept) {
-
- if (direction == QXmppCall::IncomingDirection) {
- q->warning("Ignoring Session-Accept for an incoming call");
- return;
- }
-
- // send ack
- sendAck(iq);
-
- // check content description and transport
- QXmppCallPrivate::Stream *stream = findStreamByName(content.name());
- if (!stream ||
- !handleDescription(stream, content) ||
- !handleTransport(stream, content)) {
-
- // terminate call
- terminate(QXmppJingleIq::Reason::FailedApplication);
- return;
- }
-
- // check for call establishment
- setState(QXmppCall::ActiveState);
-
- } else if (iq.action() == QXmppJingleIq::SessionInfo) {
-
- // notify user
- QTimer::singleShot(0, q, &QXmppCall::ringing);
-
- } else if (iq.action() == QXmppJingleIq::SessionTerminate) {
-
- // send ack
- sendAck(iq);
-
- // terminate
- q->info(QString("Remote party %1 terminated call %2").arg(iq.from(), iq.sid()));
- q->terminated();
-
- } else if (iq.action() == QXmppJingleIq::ContentAccept) {
-
- // send ack
- sendAck(iq);
-
- // check content description and transport
- QXmppCallPrivate::Stream *stream = findStreamByName(content.name());
- if (!stream ||
- !handleDescription(stream, content) ||
- !handleTransport(stream, content)) {
-
- // FIXME: what action?
- return;
- }
-
- } else if (iq.action() == QXmppJingleIq::ContentAdd) {
-
- // send ack
- sendAck(iq);
-
- // check media stream does not exist yet
- QXmppCallPrivate::Stream *stream = findStreamByName(content.name());
- if (stream)
- return;
-
- // create media stream
- stream = createStream(content.descriptionMedia());
- if (!stream)
- return;
- stream->creator = content.creator();
- stream->name = content.name();
-
- // check content description
- if (!handleDescription(stream, content) ||
- !handleTransport(stream, content)) {
-
- QXmppJingleIq iq;
- iq.setTo(q->jid());
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::ContentReject);
- iq.setSid(q->sid());
- iq.reason().setType(QXmppJingleIq::Reason::FailedApplication);
- sendRequest(iq);
- delete stream;
- return;
- }
- streams << stream;
-
- // accept content
- QXmppJingleIq iq;
- iq.setTo(q->jid());
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::ContentAccept);
- iq.setSid(q->sid());
- iq.addContent(localContent(stream));
- sendRequest(iq);
-
- } else if (iq.action() == QXmppJingleIq::TransportInfo) {
-
- // send ack
- sendAck(iq);
-
- // check content transport
- QXmppCallPrivate::Stream *stream = findStreamByName(content.name());
- if (!stream ||
- !handleTransport(stream, content)) {
- // FIXME: what action?
- return;
- }
- }
-}
-
-QXmppCallPrivate::Stream *QXmppCallPrivate::createStream(const QString &media)
-{
- bool check;
- Q_UNUSED(check)
- Q_ASSERT(manager);
-
- auto *stream = new Stream;
- stream->media = media;
-
- // RTP channel
- QObject *channelObject = nullptr;
- if (media == AUDIO_MEDIA) {
- auto *audioChannel = new QXmppRtpAudioChannel(q);
- stream->channel = audioChannel;
- channelObject = audioChannel;
- } else if (media == VIDEO_MEDIA) {
- auto *videoChannel = new QXmppRtpVideoChannel(q);
- stream->channel = videoChannel;
- channelObject = videoChannel;
- } else {
- q->warning(QString("Unsupported media type %1").arg(media));
- delete stream;
- return nullptr;
- }
-
- // ICE connection
- stream->connection = new QXmppIceConnection(q);
- stream->connection->setIceControlling(direction == QXmppCall::OutgoingDirection);
- stream->connection->setStunServer(manager->d->stunHost, manager->d->stunPort);
- stream->connection->setTurnServer(manager->d->turnHost, manager->d->turnPort);
- stream->connection->setTurnUser(manager->d->turnUser);
- stream->connection->setTurnPassword(manager->d->turnPassword);
- stream->connection->addComponent(RTP_COMPONENT);
- stream->connection->addComponent(RTCP_COMPONENT);
- stream->connection->bind(QXmppIceComponent::discoverAddresses());
-
- // connect signals
- QObject::connect(stream->connection, &QXmppIceConnection::localCandidatesChanged,
- q, &QXmppCall::localCandidatesChanged);
-
- QObject::connect(stream->connection, &QXmppIceConnection::connected,
- q, &QXmppCall::updateOpenMode);
-
- QObject::connect(q, &QXmppCall::stateChanged,
- q, &QXmppCall::updateOpenMode);
-
- QObject::connect(stream->connection, &QXmppIceConnection::disconnected,
- q, &QXmppCall::hangup);
-
- if (channelObject) {
- QXmppIceComponent *rtpComponent = stream->connection->component(RTP_COMPONENT);
-
- QObject::connect(rtpComponent, SIGNAL(datagramReceived(QByteArray)),
- channelObject, SLOT(datagramReceived(QByteArray)));
-
- QObject::connect(channelObject, SIGNAL(sendDatagram(QByteArray)),
- rtpComponent, SLOT(sendDatagram(QByteArray)));
- }
- return stream;
-}
-
-QXmppJingleIq::Content QXmppCallPrivate::localContent(QXmppCallPrivate::Stream *stream) const
-{
- QXmppJingleIq::Content content;
- content.setCreator(stream->creator);
- content.setName(stream->name);
- content.setSenders("both");
-
- // description
- content.setDescriptionMedia(stream->media);
- content.setDescriptionSsrc(stream->channel->localSsrc());
- content.setPayloadTypes(stream->channel->localPayloadTypes());
-
- // transport
- content.setTransportUser(stream->connection->localUser());
- content.setTransportPassword(stream->connection->localPassword());
- content.setTransportCandidates(stream->connection->localCandidates());
-
- return content;
-}
-
-/// Sends an acknowledgement for a Jingle IQ.
-///
-
-bool QXmppCallPrivate::sendAck(const QXmppJingleIq &iq)
-{
- QXmppIq ack;
- ack.setId(iq.id());
- ack.setTo(iq.from());
- ack.setType(QXmppIq::Result);
- return manager->client()->sendPacket(ack);
-}
-
-bool QXmppCallPrivate::sendInvite()
-{
- // create audio stream
- QXmppCallPrivate::Stream *stream = findStreamByMedia(AUDIO_MEDIA);
- Q_ASSERT(stream);
-
- QXmppJingleIq iq;
- iq.setTo(jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::SessionInitiate);
- iq.setInitiator(ownJid);
- iq.setSid(sid);
- iq.addContent(localContent(stream));
- return sendRequest(iq);
-}
-
-/// Sends a Jingle IQ and adds it to outstanding requests.
-///
-
-bool QXmppCallPrivate::sendRequest(const QXmppJingleIq &iq)
-{
- requests << iq;
- return manager->client()->sendPacket(iq);
-}
-
-void QXmppCallPrivate::setState(QXmppCall::State newState)
-{
- if (state != newState) {
- state = newState;
- emit q->stateChanged(state);
-
- if (state == QXmppCall::ActiveState)
- emit q->connected();
- else if (state == QXmppCall::FinishedState)
- emit q->finished();
- }
-}
-
-/// Request graceful call termination
-
-void QXmppCallPrivate::terminate(QXmppJingleIq::Reason::Type reasonType)
-{
- if (state == QXmppCall::DisconnectingState ||
- state == QXmppCall::FinishedState)
- return;
-
- // hangup call
- QXmppJingleIq iq;
- iq.setTo(jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::SessionTerminate);
- iq.setSid(sid);
- iq.reason().setType(reasonType);
- sendRequest(iq);
- setState(QXmppCall::DisconnectingState);
-
- // schedule forceful termination in 5s
- QTimer::singleShot(5000, q, &QXmppCall::terminated);
-}
-
-QXmppCall::QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent)
- : QXmppLoggable(parent)
-{
- d = new QXmppCallPrivate(this);
- d->direction = direction;
- d->jid = jid;
- d->ownJid = parent->client()->configuration().jid();
- d->manager = parent;
-
- // create audio stream
- QXmppCallPrivate::Stream *stream = d->createStream(AUDIO_MEDIA);
- stream->creator = QLatin1String("initiator");
- stream->name = QLatin1String("voice");
- d->streams << stream;
-}
-
-QXmppCall::~QXmppCall()
-{
- qDeleteAll(d->streams);
- delete d;
-}
-
-/// Call this method if you wish to accept an incoming call.
-///
-
-void QXmppCall::accept()
-{
- if (d->direction == IncomingDirection && d->state == ConnectingState) {
- Q_ASSERT(d->streams.size() == 1);
- QXmppCallPrivate::Stream *stream = d->streams.first();
-
- // accept incoming call
- QXmppJingleIq iq;
- iq.setTo(d->jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::SessionAccept);
- iq.setResponder(d->ownJid);
- iq.setSid(d->sid);
- iq.addContent(d->localContent(stream));
- d->sendRequest(iq);
-
- // notify user
- d->manager->callStarted(this);
-
- // check for call establishment
- d->setState(QXmppCall::ActiveState);
- }
-}
-
-/// Returns the RTP channel for the audio data.
-///
-/// It acts as a QIODevice so that you can read / write audio samples, for
-/// instance using a QAudioOutput and a QAudioInput.
-///
-
-QXmppRtpAudioChannel *QXmppCall::audioChannel() const
-{
- QXmppCallPrivate::Stream *stream = d->findStreamByMedia(AUDIO_MEDIA);
- if (stream)
- return static_cast<QXmppRtpAudioChannel *>(stream->channel);
- else
- return nullptr;
-}
-
-QIODevice::OpenMode QXmppCall::audioMode() const
-{
- return d->audioMode;
-}
-
-/// Returns the RTP channel for the video data.
-///
-
-QXmppRtpVideoChannel *QXmppCall::videoChannel() const
-{
- QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
- if (stream)
- return static_cast<QXmppRtpVideoChannel *>(stream->channel);
- else
- return nullptr;
-}
-
-QIODevice::OpenMode QXmppCall::videoMode() const
-{
- return d->videoMode;
-}
-
-void QXmppCall::terminated()
-{
- // close streams
- for (auto *stream : d->streams) {
- stream->channel->close();
- stream->connection->close();
- }
-
- // update state
- d->setState(QXmppCall::FinishedState);
-}
-
-QXmppCall::Direction QXmppCall::direction() const
-{
- return d->direction;
-}
-
-/// Hangs up the call.
-///
-
-void QXmppCall::hangup()
-{
- d->terminate(QXmppJingleIq::Reason::None);
-}
-
-/// Sends a transport-info to inform the remote party of new local candidates.
-///
-
-void QXmppCall::localCandidatesChanged()
-{
- // find the stream
- auto *conn = qobject_cast<QXmppIceConnection *>(sender());
- QXmppCallPrivate::Stream *stream = nullptr;
- for (auto *ptr : d->streams) {
- if (ptr->connection == conn) {
- stream = ptr;
- break;
- }
- }
- if (!stream)
- return;
-
- QXmppJingleIq iq;
- iq.setTo(d->jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::TransportInfo);
- iq.setSid(d->sid);
- iq.addContent(d->localContent(stream));
- d->sendRequest(iq);
-}
-
-QString QXmppCall::jid() const
-{
- return d->jid;
-}
-
-void QXmppCall::updateOpenMode()
-{
- QXmppCallPrivate::Stream *stream;
- QIODevice::OpenMode mode;
-
- // determine audio mode
- mode = QIODevice::NotOpen;
- stream = d->findStreamByMedia(AUDIO_MEDIA);
- if (d->state == QXmppCall::ActiveState && stream && stream->connection->isConnected())
- mode = stream->channel->openMode() & QIODevice::ReadWrite;
- if (mode != d->audioMode) {
- d->audioMode = mode;
- emit audioModeChanged(mode);
- }
-
- // determine video mode
- mode = QIODevice::NotOpen;
- stream = d->findStreamByMedia(VIDEO_MEDIA);
- if (d->state == QXmppCall::ActiveState && stream && stream->connection->isConnected()) {
- mode |= (stream->channel->openMode() & QIODevice::ReadOnly);
- if (d->sendVideo)
- mode |= (stream->channel->openMode() & QIODevice::WriteOnly);
- }
- if (mode != d->videoMode) {
- d->videoMode = mode;
- emit videoModeChanged(mode);
- }
-}
-
-/// Returns the call's session identifier.
-///
-
-QString QXmppCall::sid() const
-{
- return d->sid;
-}
-
-QXmppCall::State QXmppCall::state() const
-{
- return d->state;
-}
-
-/// Starts sending video to the remote party.
-
-void QXmppCall::startVideo()
-{
- if (d->state != QXmppCall::ActiveState) {
- warning("Cannot start video, call is not active");
- return;
- }
-
- d->sendVideo = true;
- QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
- if (stream) {
- updateOpenMode();
- return;
- }
-
- // create video stream
- stream = d->createStream(VIDEO_MEDIA);
- stream->creator = (d->direction == QXmppCall::OutgoingDirection) ? QLatin1String("initiator") : QLatin1String("responder");
- stream->name = QLatin1String("webcam");
- d->streams << stream;
-
- // build request
- QXmppJingleIq iq;
- iq.setTo(d->jid);
- iq.setType(QXmppIq::Set);
- iq.setAction(QXmppJingleIq::ContentAdd);
- iq.setSid(d->sid);
- iq.addContent(d->localContent(stream));
- d->sendRequest(iq);
-}
-
-/// Stops sending video to the remote party.
-
-void QXmppCall::stopVideo()
-{
- if (!d->sendVideo)
- return;
-
- d->sendVideo = false;
- QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
- if (stream)
- updateOpenMode();
-}
-
QXmppCallManagerPrivate::QXmppCallManagerPrivate(QXmppCallManager *qq)
: stunPort(0),
turnPort(0),
q(qq)
{
+ // Initialize GStreamer
+ gst_init(nullptr, nullptr);
}
QXmppCall *QXmppCallManagerPrivate::findCall(const QString &sid) const
@@ -769,7 +137,9 @@ QXmppCall *QXmppCallManager::call(const QString &jid)
return nullptr;
}
- auto *call = new QXmppCall(jid, QXmppCall::OutgoingDirection, this);
+ QXmppCall *call = new QXmppCall(jid, QXmppCall::OutgoingDirection, this);
+ QXmppCallStream *stream = call->d->createStream("audio", "initiator", "microphone");
+ call->d->streams << stream;
call->d->sid = QXmppUtils::generateStanzaHash();
// register call
@@ -867,11 +237,10 @@ void QXmppCallManager::_q_jingleIqReceived(const QXmppJingleIq &iq)
call->d->sid = iq.sid();
const QXmppJingleIq::Content content = iq.contents().isEmpty() ? QXmppJingleIq::Content() : iq.contents().first();
- QXmppCallPrivate::Stream *stream = call->d->findStreamByMedia(content.descriptionMedia());
+ QXmppCallStream *stream = call->d->createStream(content.descriptionMedia(), content.creator(), content.name());
if (!stream)
return;
- stream->creator = content.creator();
- stream->name = content.name();
+ call->d->streams << stream;
// send ack
call->d->sendAck(iq);
diff --git a/src/client/QXmppCallManager.h b/src/client/QXmppCallManager.h
index 8b611f89..719d1e36 100644
--- a/src/client/QXmppCallManager.h
+++ b/src/client/QXmppCallManager.h
@@ -24,6 +24,7 @@
#ifndef QXMPPCALLMANAGER_H
#define QXMPPCALLMANAGER_H
+#include "QXmppCall.h"
#include "QXmppClientExtension.h"
#include "QXmppLogger.h"
@@ -32,121 +33,12 @@
#include <QObject>
class QHostAddress;
-class QXmppCallPrivate;
-class QXmppCallManager;
class QXmppCallManagerPrivate;
class QXmppIq;
class QXmppJingleCandidate;
class QXmppJingleIq;
class QXmppJinglePayloadType;
class QXmppPresence;
-class QXmppRtpAudioChannel;
-class QXmppRtpVideoChannel;
-
-/// \brief The QXmppCall class represents a Voice-Over-IP call to a remote party.
-///
-/// To get the QIODevice from which you can read / write audio samples, call
-/// audioChannel().
-///
-/// \note THIS API IS NOT FINALIZED YET
-
-class QXMPP_EXPORT QXmppCall : public QXmppLoggable
-{
- Q_OBJECT
- Q_FLAGS(QIODevice::OpenModeFlag QIODevice::OpenMode)
- Q_PROPERTY(Direction direction READ direction CONSTANT)
- Q_PROPERTY(QString jid READ jid CONSTANT)
- Q_PROPERTY(State state READ state NOTIFY stateChanged)
- Q_PROPERTY(QIODevice::OpenMode audioMode READ audioMode NOTIFY audioModeChanged)
- Q_PROPERTY(QIODevice::OpenMode videoMode READ videoMode NOTIFY videoModeChanged)
-
-public:
- /// This enum is used to describe the direction of a call.
- enum Direction {
- IncomingDirection, ///< The call is incoming.
- OutgoingDirection ///< The call is outgoing.
- };
- Q_ENUM(Direction)
-
- /// This enum is used to describe the state of a call.
- enum State {
- ConnectingState = 0, ///< The call is being connected.
- ActiveState = 1, ///< The call is active.
- DisconnectingState = 2, ///< The call is being disconnected.
- FinishedState = 3 ///< The call is finished.
- };
- Q_ENUM(State)
-
- ~QXmppCall() override;
-
- // documentation needs to be here, see https://stackoverflow.com/questions/49192523/
- /// Returns the call's direction.
- QXmppCall::Direction direction() const;
- /// Returns the remote party's JID.
- QString jid() const;
- ///
- /// Returns the call's state.
- ///
- /// \sa stateChanged()
- ///
- QXmppCall::State state() const;
-
- QString sid() const;
-
- QXmppRtpAudioChannel *audioChannel() const;
- QXmppRtpVideoChannel *videoChannel() const;
-
- // documentation needs to be here, see https://stackoverflow.com/questions/49192523/
- /// Returns the audio mode.
- QIODevice::OpenMode audioMode() const;
- /// Returns the video mode.
- QIODevice::OpenMode videoMode() const;
-
-Q_SIGNALS:
- /// \brief This signal is emitted when a call is connected.
- ///
- /// Once this signal is emitted, you can connect a QAudioOutput and
- /// QAudioInput to the call. You can determine the appropriate clockrate
- /// and the number of channels by calling payloadType().
- void connected();
-
- /// \brief This signal is emitted when a call is finished.
- ///
- /// Note: Do not delete the call in the slot connected to this signal,
- /// instead use deleteLater().
- void finished();
-
- /// \brief This signal is emitted when the remote party is ringing.
- void ringing();
-
- /// \brief This signal is emitted when the call state changes.
- void stateChanged(QXmppCall::State state);
-
- /// \brief This signal is emitted when the audio channel changes.
- void audioModeChanged(QIODevice::OpenMode mode);
-
- /// \brief This signal is emitted when the video channel changes.
- void videoModeChanged(QIODevice::OpenMode mode);
-
-public Q_SLOTS:
- void accept();
- void hangup();
- void startVideo();
- void stopVideo();
-
-private Q_SLOTS:
- void localCandidatesChanged();
- void terminated();
- void updateOpenMode();
-
-private:
- QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent);
-
- QXmppCallPrivate *d;
- friend class QXmppCallManager;
- friend class QXmppCallManagerPrivate;
- friend class QXmppCallPrivate;
-};
/// \brief The QXmppCallManager class provides support for making and
/// receiving voice calls.
@@ -218,6 +110,4 @@ private:
friend class QXmppCallManagerPrivate;
};
-Q_DECLARE_METATYPE(QXmppCall::State)
-
#endif
diff --git a/src/client/QXmppCallManager_p.h b/src/client/QXmppCallManager_p.h
new file mode 100644
index 00000000..32b38dc0
--- /dev/null
+++ b/src/client/QXmppCallManager_p.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008-2019 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * https://github.com/qxmpp-project/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.
+ *
+ */
+
+#ifndef QXMPPCALLMANAGER_P_H
+#define QXMPPCALLMANAGER_P_H
+
+#include "QXmppCall.h"
+
+#include <QHostAddress>
+#include <QList>
+
+class QXmppCallManager;
+
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QXmpp API.
+// This header file may change from version to version without notice,
+// or even be removed.
+//
+// We mean it.
+//
+
+class QXmppCallManagerPrivate
+{
+public:
+ QXmppCallManagerPrivate(QXmppCallManager *qq);
+ QXmppCall *findCall(const QString &sid) const;
+ QXmppCall *findCall(const QString &sid, QXmppCall::Direction direction) const;
+
+ QList<QXmppCall *> calls;
+ QHostAddress stunHost;
+ quint16 stunPort;
+ QHostAddress turnHost;
+ quint16 turnPort;
+ QString turnUser;
+ QString turnPassword;
+
+private:
+ QXmppCallManager *q;
+};
+
+#endif
diff --git a/src/client/QXmppCallStream.cpp b/src/client/QXmppCallStream.cpp
new file mode 100644
index 00000000..4a8f55a2
--- /dev/null
+++ b/src/client/QXmppCallStream.cpp
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2020 The QXmpp developers
+ *
+ * Author:
+ * Niels Ole Salscheider
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppCallStream.h"
+
+#include "QXmppCallStream_p.h"
+#include "QXmppCall_p.h"
+#include "QXmppStun.h"
+
+#include <cstring>
+#include <gst/gst.h>
+
+QXmppCallStreamPrivate::QXmppCallStreamPrivate(QXmppCallStream *parent, GstElement *pipeline_,
+ GstElement *rtpbin_, QString media_, QString creator_,
+ QString name_, int id_)
+ : QObject(parent),
+ q(parent),
+ pipeline(pipeline_),
+ rtpbin(rtpbin_),
+ sendPad(nullptr),
+ receivePad(nullptr),
+ encoderBin(nullptr),
+ decoderBin(nullptr),
+ sendPadCB(nullptr),
+ receivePadCB(nullptr),
+ media(media_),
+ creator(creator_),
+ name(name_),
+ id(id_)
+{
+ localSsrc = qrand();
+
+ iceReceiveBin = gst_bin_new(QStringLiteral("receive_%1").arg(id).toLatin1().data());
+ iceSendBin = gst_bin_new(QStringLiteral("send_%1").arg(id).toLatin1().data());
+ gst_bin_add_many(GST_BIN(pipeline), iceReceiveBin, iceSendBin, nullptr);
+
+ internalRtpPad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SINK);
+ internalRtcpPad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SINK);
+ if (!gst_element_add_pad(iceSendBin, internalRtpPad) ||
+ !gst_element_add_pad(iceSendBin, internalRtcpPad)) {
+ qFatal("Failed to add pads to send bin");
+ }
+
+ connection = new QXmppIceConnection(this);
+ connection->addComponent(RTP_COMPONENT);
+ connection->addComponent(RTCP_COMPONENT);
+ apprtpsink = gst_element_factory_make("appsink", nullptr);
+ apprtcpsink = gst_element_factory_make("appsink", nullptr);
+ if (!apprtpsink || !apprtcpsink) {
+ qFatal("Failed to create appsinks");
+ }
+
+ g_signal_connect_swapped(apprtpsink, "new-sample",
+ G_CALLBACK(+[](QXmppCallStreamPrivate *p, GstElement *appsink) -> GstFlowReturn {
+ return p->sendDatagram(appsink, RTP_COMPONENT);
+ }),
+ this);
+ g_signal_connect_swapped(apprtcpsink, "new-sample",
+ G_CALLBACK(+[](QXmppCallStreamPrivate *p, GstElement *appsink) -> GstFlowReturn {
+ return p->sendDatagram(appsink, RTCP_COMPONENT);
+ }),
+ this);
+
+ apprtpsrc = gst_element_factory_make("appsrc", nullptr);
+ apprtcpsrc = gst_element_factory_make("appsrc", nullptr);
+ if (!apprtpsrc || !apprtcpsrc) {
+ qFatal("Failed to create appsrcs");
+ }
+
+ // TODO check these parameters
+ g_object_set(apprtpsink, "emit-signals", true, "async", false, "max-buffers", 1, "drop", true, nullptr);
+ g_object_set(apprtcpsink, "emit-signals", true, "async", false, nullptr);
+ g_object_set(apprtpsrc, "is-live", true, "max-latency", 5000000, nullptr);
+ g_object_set(apprtcpsrc, "is-live", true, nullptr);
+
+ connect(connection->component(RTP_COMPONENT), &QXmppIceComponent::datagramReceived,
+ [&](const QByteArray &datagram) { datagramReceived(datagram, apprtpsrc); });
+ connect(connection->component(RTCP_COMPONENT), &QXmppIceComponent::datagramReceived,
+ [&](const QByteArray &datagram) { datagramReceived(datagram, apprtcpsrc); });
+
+ if (!gst_bin_add(GST_BIN(iceReceiveBin), apprtpsrc) ||
+ !gst_bin_add(GST_BIN(iceReceiveBin), apprtcpsrc)) {
+ qFatal("Failed to add appsrcs to receive bin");
+ }
+
+ if (!gst_element_link_pads(apprtpsrc, "src", rtpbin, QStringLiteral("recv_rtp_sink_%1").arg(id).toLatin1().data()) ||
+ !gst_element_link_pads(apprtcpsrc, "src", rtpbin, QStringLiteral("recv_rtcp_sink_%1").arg(id).toLatin1().data())) {
+ qFatal("Failed to link receive pads");
+ }
+
+ // We need frequent RTCP reports for the bandwidth controller
+ GstElement *rtpSession;
+ g_signal_emit_by_name(rtpbin, "get-session", static_cast<uint>(id), &rtpSession);
+ g_object_set(rtpSession, "rtcp-min-interval", 100000000, nullptr);
+
+ gst_element_sync_state_with_parent(iceReceiveBin);
+ gst_element_sync_state_with_parent(iceSendBin);
+}
+
+QXmppCallStreamPrivate::~QXmppCallStreamPrivate()
+{
+ connection->close();
+
+ // Remove elements from pipeline
+ if ((encoderBin && !gst_bin_remove(GST_BIN(pipeline), encoderBin)) ||
+ (decoderBin && !gst_bin_remove(GST_BIN(pipeline), decoderBin)) ||
+ !gst_bin_remove(GST_BIN(pipeline), iceSendBin) ||
+ !gst_bin_remove(GST_BIN(pipeline), iceReceiveBin)) {
+ qFatal("Failed to remove bins from pipeline");
+ }
+}
+
+GstFlowReturn QXmppCallStreamPrivate::sendDatagram(GstElement *appsink, int component)
+{
+ GstSample *sample;
+ g_signal_emit_by_name(appsink, "pull-sample", &sample);
+ if (!sample) {
+ qFatal("Could not get sample");
+ return GST_FLOW_ERROR;
+ }
+
+ GstMapInfo mapInfo;
+ GstBuffer *buffer = gst_sample_get_buffer(sample);
+ if (!buffer) {
+ qFatal("Could not get buffer");
+ return GST_FLOW_ERROR;
+ }
+ if (!gst_buffer_map(buffer, &mapInfo, GST_MAP_READ)) {
+ qFatal("Could not map buffer");
+ return GST_FLOW_ERROR;
+ }
+ QByteArray datagram;
+ datagram.resize(mapInfo.size);
+ std::memcpy(datagram.data(), mapInfo.data, mapInfo.size);
+ gst_buffer_unmap(buffer, &mapInfo);
+ gst_sample_unref(sample);
+
+ if (connection->component(component)->isConnected() &&
+ connection->component(component)->sendDatagram(datagram) != datagram.size()) {
+ return GST_FLOW_ERROR;
+ }
+ return GST_FLOW_OK;
+}
+
+void QXmppCallStreamPrivate::datagramReceived(const QByteArray &datagram, GstElement *appsrc)
+{
+ GstBuffer *buffer = gst_buffer_new_and_alloc(datagram.size());
+ GstMapInfo mapInfo;
+ if (!gst_buffer_map(buffer, &mapInfo, GST_MAP_WRITE)) {
+ qFatal("Could not map buffer");
+ return;
+ }
+ std::memcpy(mapInfo.data, datagram.data(), mapInfo.size);
+ gst_buffer_unmap(buffer, &mapInfo);
+ GstFlowReturn ret;
+ g_signal_emit_by_name(appsrc, "push-buffer", buffer, &ret);
+ gst_buffer_unref(buffer);
+}
+
+void QXmppCallStreamPrivate::addEncoder(QXmppCallPrivate::GstCodec &codec)
+{
+ // Remove old encoder and payloader if they exist
+ if (encoderBin) {
+ if (!gst_bin_remove(GST_BIN(pipeline), encoderBin)) {
+ qFatal("Failed to remove existing encoder bin");
+ }
+ }
+ encoderBin = gst_bin_new(QStringLiteral("encoder_%1").arg(id).toLatin1().data());
+ if (!gst_bin_add(GST_BIN(pipeline), encoderBin)) {
+ qFatal("Failed to add encoder bin to wrapper");
+ return;
+ }
+
+ sendPad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SINK);
+ gst_element_add_pad(encoderBin, sendPad);
+
+ // Create new elements
+ GstElement *queue = gst_element_factory_make("queue", nullptr);
+ if (!queue) {
+ qFatal("Failed to create queue");
+ return;
+ }
+
+ GstElement *pay = gst_element_factory_make(codec.gstPay.toLatin1().data(), nullptr);
+ if (!pay) {
+ qFatal("Failed to create payloader");
+ return;
+ }
+ g_object_set(pay, "pt", codec.pt, "ssrc", localSsrc, nullptr);
+
+ GstElement *encoder = gst_element_factory_make(codec.gstEnc.toLatin1().data(), nullptr);
+ if (!encoder) {
+ qFatal("Failed to create encoder");
+ return;
+ }
+ for (auto &encProp : codec.encProps) {
+ g_object_set(encoder, encProp.name.toLatin1().data(), encProp.value, nullptr);
+ }
+
+ gst_bin_add_many(GST_BIN(encoderBin), queue, encoder, pay, nullptr);
+
+ if (!gst_element_link_pads(pay, "src", rtpbin, QStringLiteral("send_rtp_sink_%1").arg(id).toLatin1().data()) ||
+ !gst_element_link_many(queue, encoder, pay, nullptr)) {
+ qFatal("Could not link all encoder pads");
+ return;
+ }
+
+ if (!gst_ghost_pad_set_target(GST_GHOST_PAD(sendPad), gst_element_get_static_pad(queue, "sink"))) {
+ qFatal("Failed to set send pad");
+ return;
+ }
+
+ if (sendPadCB) {
+ sendPadCB(sendPad);
+ }
+
+ gst_element_sync_state_with_parent(encoderBin);
+
+ addRtcpSender(gst_element_get_request_pad(rtpbin, QStringLiteral("send_rtcp_src_%1").arg(id).toLatin1().data()));
+}
+
+void QXmppCallStreamPrivate::addDecoder(GstPad *pad, QXmppCallPrivate::GstCodec &codec)
+{
+ // Remove old decoder and depayloader if they exist
+ if (decoderBin) {
+ if (!gst_bin_remove(GST_BIN(pipeline), decoderBin)) {
+ qFatal("Failed to remove existing decoder bin");
+ }
+ }
+ decoderBin = gst_bin_new(QStringLiteral("decoder_%1").arg(id).toLatin1().data());
+ if (!gst_bin_add(GST_BIN(pipeline), decoderBin)) {
+ qFatal("Failed to add decoder bin to wrapper");
+ return;
+ }
+
+ receivePad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SRC);
+ internalReceivePad = gst_ghost_pad_new_no_target(nullptr, GST_PAD_SINK);
+ gst_element_add_pad(decoderBin, receivePad);
+ gst_element_add_pad(decoderBin, internalReceivePad);
+
+ // Create new elements
+ GstElement *depay = gst_element_factory_make(codec.gstDepay.toLatin1().data(), nullptr);
+ if (!depay) {
+ qFatal("Failed to create depayloader");
+ return;
+ }
+
+ GstElement *decoder = gst_element_factory_make(codec.gstDec.toLatin1().data(), nullptr);
+ if (!decoder) {
+ qFatal("Failed to create decoder");
+ return;
+ }
+
+ GstElement *queue = gst_element_factory_make("queue", nullptr);
+ if (!queue) {
+ qFatal("Failed to create queue");
+ return;
+ }
+
+ gst_bin_add_many(GST_BIN(decoderBin), depay, decoder, queue, nullptr);
+
+ if (!gst_ghost_pad_set_target(GST_GHOST_PAD(internalReceivePad), gst_element_get_static_pad(depay, "sink")) ||
+ gst_pad_link(pad, internalReceivePad) != GST_PAD_LINK_OK ||
+ !gst_element_link_many(depay, decoder, queue, nullptr) ||
+ !gst_ghost_pad_set_target(GST_GHOST_PAD(receivePad), gst_element_get_static_pad(queue, "src"))) {
+ qFatal("Could not link all decoder pads");
+ return;
+ }
+
+ gst_element_sync_state_with_parent(decoderBin);
+
+ if (receivePadCB) {
+ receivePadCB(receivePad);
+ }
+}
+
+void QXmppCallStreamPrivate::addRtpSender(GstPad *pad)
+{
+ if (!gst_bin_add(GST_BIN(iceSendBin), apprtpsink)) {
+ qFatal("Failed to add rtp sink to send bin");
+ }
+ gst_element_sync_state_with_parent(apprtpsink);
+ if (!gst_ghost_pad_set_target(GST_GHOST_PAD(internalRtpPad), gst_element_get_static_pad(apprtpsink, "sink")) ||
+ gst_pad_link(pad, internalRtpPad) != GST_PAD_LINK_OK) {
+ qFatal("Failed to link rtp pads");
+ }
+}
+
+void QXmppCallStreamPrivate::addRtcpSender(GstPad *pad)
+{
+ if (!gst_bin_add(GST_BIN(iceSendBin), apprtcpsink)) {
+ qFatal("Failed to add rtcp sink to send bin");
+ }
+ gst_element_sync_state_with_parent(apprtcpsink);
+ if (!gst_ghost_pad_set_target(GST_GHOST_PAD(internalRtcpPad), gst_element_get_static_pad(apprtcpsink, "sink")) ||
+ gst_pad_link(pad, internalRtcpPad) != GST_PAD_LINK_OK) {
+ qFatal("Failed to link rtcp pads");
+ }
+}
+
+QXmppCallStream::QXmppCallStream(GstElement *pipeline, GstElement *rtpbin,
+ QString media, QString creator, QString name, int id)
+{
+ d = new QXmppCallStreamPrivate(this, pipeline, rtpbin, media, creator, name, id);
+}
+
+QString QXmppCallStream::creator() const
+{
+ return d->creator;
+}
+
+QString QXmppCallStream::media() const
+{
+ return d->media;
+}
+
+QString QXmppCallStream::name() const
+{
+ return d->name;
+}
+
+int QXmppCallStream::id() const
+{
+ return d->id;
+}
+
+void QXmppCallStream::setReceivePadCallback(void (*cb)(GstPad *))
+{
+ d->receivePadCB = cb;
+ if (d->receivePad) {
+ d->receivePadCB(d->receivePad);
+ }
+}
+
+void QXmppCallStream::setSendPadCallback(void (*cb)(GstPad *))
+{
+ d->sendPadCB = cb;
+ if (d->sendPad) {
+ d->sendPadCB(d->sendPad);
+ }
+}
diff --git a/src/client/QXmppCallStream.h b/src/client/QXmppCallStream.h
new file mode 100644
index 00000000..a9103384
--- /dev/null
+++ b/src/client/QXmppCallStream.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The QXmpp developers
+ *
+ * Author:
+ * Niels Ole Salscheider
+ *
+ * Source:
+ * https://github.com/qxmpp-project/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.
+ *
+ */
+
+#ifndef QXMPPCALLSTREAM_H
+#define QXMPPCALLSTREAM_H
+
+#include <QXmppGlobal.h>
+
+#include <gst/gst.h>
+
+#include <QObject>
+
+class QXmppCallStreamPrivate;
+class QXmppIceConnection;
+class QXmppCall;
+class QXmppCallPrivate;
+
+/// \brief The QXmppCallStream class represents an RTP stream in a VoIP call.
+///
+/// \note THIS API IS NOT FINALIZED YET
+///
+/// \since QXmpp 1.3
+
+class QXMPP_EXPORT QXmppCallStream : public QObject
+{
+ Q_OBJECT
+
+public:
+ QString creator() const;
+ QString media() const;
+ QString name() const;
+ int id() const;
+ void setReceivePadCallback(void (*cb)(GstPad *));
+ void setSendPadCallback(void (*cb)(GstPad *));
+
+private:
+ QXmppCallStream(GstElement *pipeline, GstElement *rtpbin,
+ QString media, QString creator, QString name, int id);
+
+ QXmppCallStreamPrivate *d;
+
+ friend class QXmppCall;
+ friend class QXmppCallPrivate;
+};
+
+#endif
diff --git a/src/client/QXmppCallStream_p.h b/src/client/QXmppCallStream_p.h
new file mode 100644
index 00000000..568460da
--- /dev/null
+++ b/src/client/QXmppCallStream_p.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008-2020 The QXmpp developers
+ *
+ * Authors:
+ * Niels Ole Salscheider
+ *
+ * Source:
+ * https://github.com/qxmpp-project/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.
+ *
+ */
+
+#ifndef QXMPPCALLSTREAM_P_H
+#define QXMPPCALLSTREAM_P_H
+
+#include "QXmppCall_p.h"
+#include "QXmppJingleIq.h"
+
+#include <gst/gst.h>
+
+#include <QList>
+#include <QObject>
+#include <QString>
+
+class QXmppIceConnection;
+
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QXmpp API.
+// This header file may change from version to version without notice,
+// or even be removed.
+//
+// We mean it.
+//
+
+static const int RTP_COMPONENT = 1;
+static const int RTCP_COMPONENT = 2;
+
+static const QLatin1String AUDIO_MEDIA("audio");
+static const QLatin1String VIDEO_MEDIA("video");
+
+class QXmppCallStreamPrivate : public QObject
+{
+ Q_OBJECT
+
+public:
+ QXmppCallStreamPrivate(QXmppCallStream *parent, GstElement *pipeline_, GstElement *rtpbin_,
+ QString media_, QString creator_, QString name_, int id_);
+ ~QXmppCallStreamPrivate();
+
+ GstFlowReturn sendDatagram(GstElement *appsink, int component);
+ void datagramReceived(const QByteArray &datagram, GstElement *appsrc);
+
+ void addEncoder(QXmppCallPrivate::GstCodec &codec);
+ void addDecoder(GstPad *pad, QXmppCallPrivate::GstCodec &codec);
+ void addRtpSender(GstPad *pad);
+ void addRtcpSender(GstPad *pad);
+
+ QXmppCallStream *q;
+
+ quint32 localSsrc;
+
+ GstElement *pipeline;
+ GstElement *rtpbin;
+ GstPad *sendPad;
+ GstPad *receivePad;
+ GstPad *internalReceivePad;
+ GstPad *internalRtpPad;
+ GstPad *internalRtcpPad;
+ GstElement *encoderBin;
+ GstElement *decoderBin;
+ GstElement *iceReceiveBin;
+ GstElement *iceSendBin;
+ GstElement *apprtpsrc;
+ GstElement *apprtcpsrc;
+ GstElement *apprtpsink;
+ GstElement *apprtcpsink;
+
+ void (*sendPadCB)(GstPad *);
+ void (*receivePadCB)(GstPad *);
+
+ QXmppIceConnection *connection;
+ QString media;
+ QString creator;
+ QString name;
+ int id;
+
+ QList<QXmppJinglePayloadType> payloadTypes;
+};
+
+#endif
diff --git a/src/client/QXmppCall_p.h b/src/client/QXmppCall_p.h
new file mode 100644
index 00000000..b3e052c8
--- /dev/null
+++ b/src/client/QXmppCall_p.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2008-2020 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * https://github.com/qxmpp-project/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.
+ *
+ */
+
+#ifndef QXMPPCALL_P_H
+#define QXMPPCALL_P_H
+
+#include "QXmppCall.h"
+#include "QXmppJingleIq.h"
+
+#include <gst/gst.h>
+
+#include <QList>
+
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QXmpp API.
+// This header file may change from version to version without notice,
+// or even be removed.
+//
+// We mean it.
+//
+
+class QXmppCallStream;
+
+class QXmppCallPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ struct GstCodec {
+ int pt;
+ QString name;
+ int channels;
+ int clockrate;
+ QString gstPay;
+ QString gstDepay;
+ QString gstEnc;
+ QString gstDec;
+ struct Property {
+ QString name;
+ int value;
+ };
+ // Use e.g. gst-inspect-1.0 x264enc to find good encoder settings for live streaming
+ QList<Property> encProps;
+ };
+
+ QXmppCallPrivate(QXmppCall *qq);
+ ~QXmppCallPrivate();
+
+ void ssrcActive(uint sessionId, uint ssrc);
+ void padAdded(GstPad *pad);
+ GstCaps *ptMap(uint sessionId, uint pt);
+ bool isFormatSupported(const QString &codecName) const;
+ void filterGStreamerFormats(QList<GstCodec> &formats);
+
+ QXmppCallStream *createStream(const QString &media, const QString &creator, const QString &name);
+ QXmppCallStream *findStreamByMedia(const QString &media);
+ QXmppCallStream *findStreamByName(const QString &name);
+ QXmppCallStream *findStreamById(const int id);
+ QXmppJingleIq::Content localContent(QXmppCallStream *stream) const;
+
+ void handleAck(const QXmppIq &iq);
+ bool handleDescription(QXmppCallStream *stream, const QXmppJingleIq::Content &content);
+ void handleRequest(const QXmppJingleIq &iq);
+ bool handleTransport(QXmppCallStream *stream, const QXmppJingleIq::Content &content);
+ void setState(QXmppCall::State state);
+ bool sendAck(const QXmppJingleIq &iq);
+ bool sendInvite();
+ bool sendRequest(const QXmppJingleIq &iq);
+ void terminate(QXmppJingleIq::Reason::Type reasonType);
+
+ QXmppCall::Direction direction;
+ QString jid;
+ QString ownJid;
+ QXmppCallManager *manager;
+ QList<QXmppJingleIq> requests;
+ QString sid;
+ QXmppCall::State state;
+
+ GstElement *pipeline;
+ GstElement *rtpbin;
+
+ // Media streams
+ QList<QXmppCallStream *> streams;
+ int nextId;
+
+ // Supported codecs
+ QList<GstCodec> videoCodecs = {
+ { .pt = 100, .name = "H264", .channels = 1, .clockrate = 90000, .gstPay = "rtph264pay", .gstDepay = "rtph264depay", .gstEnc = "x264enc", .gstDec = "avdec_h264", .encProps = { { "tune", 4 }, { "speed-preset", 3 }, {"byte-stream", true}, { "bitrate", 512 } } },
+ { .pt = 99, .name = "VP8", .channels = 1, .clockrate = 90000, .gstPay = "rtpvp8pay", .gstDepay = "rtpvp8depay", .gstEnc = "vp8enc", .gstDec = "vp8dec", .encProps = { { "deadline", 20000 }, { "target-bitrate", 512000 } } },
+ // vp9enc and x265enc seem to be very slow. Give them a lower priority for now.
+ { .pt = 102, .name = "H265", .channels = 1, .clockrate = 90000, .gstPay = "rtph265pay", .gstDepay = "rtph265depay", .gstEnc = "x265enc", .gstDec = "avdec_h265", .encProps = { { "tune", 4 }, { "speed-preset", 3 }, { "bitrate", 512 } } },
+ { .pt = 101, .name = "VP9", .channels = 1, .clockrate = 90000, .gstPay = "rtpvp9pay", .gstDepay = "rtpvp9depay", .gstEnc = "vp9enc", .gstDec = "vp9dec", .encProps = { { "deadline", 20000 }, { "target-bitrate", 512000 } } }
+ };
+
+ QList<GstCodec> audioCodecs = {
+ { .pt = 98, .name = "OPUS", .channels = 2, .clockrate = 48000, .gstPay = "rtpopuspay", .gstDepay = "rtpopusdepay", .gstEnc = "opusenc", .gstDec = "opusdec" },
+ { .pt = 98, .name = "OPUS", .channels = 1, .clockrate = 48000, .gstPay = "rtpopuspay", .gstDepay = "rtpopusdepay", .gstEnc = "opusenc", .gstDec = "opusdec" },
+ { .pt = 97, .name = "SPEEX", .channels = 1, .clockrate = 48000, .gstPay = "rtpspeexpay", .gstDepay = "rtpspeexdepay", .gstEnc = "speexenc", .gstDec = "speexdec" },
+ { .pt = 97, .name = "SPEEX", .channels = 1, .clockrate = 44100, .gstPay = "rtpspeexpay", .gstDepay = "rtpspeexdepay", .gstEnc = "speexenc", .gstDec = "speexdec" },
+ { .pt = 96, .name = "AAC", .channels = 2, .clockrate = 48000, .gstPay = "rtpmp4apay", .gstDepay = "rtpmp4adepay", .gstEnc = "avenc_aac", .gstDec = "avdec_aac" },
+ { .pt = 96, .name = "AAC", .channels = 2, .clockrate = 44100, .gstPay = "rtpmp4apay", .gstDepay = "rtpmp4adepay", .gstEnc = "avenc_aac", .gstDec = "avdec_aac" },
+ { .pt = 96, .name = "AAC", .channels = 1, .clockrate = 48000, .gstPay = "rtpmp4apay", .gstDepay = "rtpmp4adepay", .gstEnc = "avenc_aac", .gstDec = "avdec_aac" },
+ { .pt = 96, .name = "AAC", .channels = 1, .clockrate = 44100, .gstPay = "rtpmp4apay", .gstDepay = "rtpmp4adepay", .gstEnc = "avenc_aac", .gstDec = "avdec_aac" },
+ { .pt = 8, .name = "PCMA", .channels = 1, .clockrate = 8000, .gstPay = "rtppcmapay", .gstDepay = "rtppcmadepay", .gstEnc = "alawenc", .gstDec = "alawdec" },
+ { .pt = 0, .name = "PCMU", .channels = 1, .clockrate = 8000, .gstPay = "rtppcmupay", .gstDepay = "rtppcmudepay", .gstEnc = "mulawenc", .gstDec = "mulawdec" }
+ };
+
+private:
+ QXmppCall *q;
+};
+
+#endif