diff options
| author | Niels Ole Salscheider <niels_ole@salscheider-online.de> | 2019-05-17 14:30:02 -0700 |
|---|---|---|
| committer | LNJ <lnj@kaidan.im> | 2020-03-16 22:22:59 +0100 |
| commit | 90036fc2cf5918c028f043edff7f5d38d1efb4cc (patch) | |
| tree | 4818d8c4e6ec3778e2dd8a2e356faf1b9e062902 /src | |
| parent | c67ccc6d939b8f1efd118f92baea997fe1b7f1a6 (diff) | |
| download | qxmpp-90036fc2cf5918c028f043edff7f5d38d1efb4cc.tar.gz | |
Port QXmppCallManager to use GStreamer
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 70 | ||||
| -rw-r--r-- | src/base/QXmppCodec.cpp | 1433 | ||||
| -rw-r--r-- | src/base/QXmppCodec_p.h | 243 | ||||
| -rw-r--r-- | src/base/QXmppRtcpPacket.cpp | 660 | ||||
| -rw-r--r-- | src/base/QXmppRtcpPacket.h | 169 | ||||
| -rw-r--r-- | src/base/QXmppRtpChannel.cpp | 999 | ||||
| -rw-r--r-- | src/base/QXmppRtpChannel.h | 304 | ||||
| -rw-r--r-- | src/base/QXmppRtpPacket.cpp | 224 | ||||
| -rw-r--r-- | src/base/QXmppRtpPacket.h | 75 | ||||
| -rw-r--r-- | src/client/QXmppCall.cpp | 739 | ||||
| -rw-r--r-- | src/client/QXmppCall.h | 121 | ||||
| -rw-r--r-- | src/client/QXmppCallManager.cpp | 655 | ||||
| -rw-r--r-- | src/client/QXmppCallManager.h | 112 | ||||
| -rw-r--r-- | src/client/QXmppCallManager_p.h | 63 | ||||
| -rw-r--r-- | src/client/QXmppCallStream.cpp | 361 | ||||
| -rw-r--r-- | src/client/QXmppCallStream.h | 66 | ||||
| -rw-r--r-- | src/client/QXmppCallStream_p.h | 103 | ||||
| -rw-r--r-- | src/client/QXmppCall_p.h | 133 |
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> ¶meters) -{ - 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> ¶meters) -{ - 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> ¶meters) = 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> ¶meters); - -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> ¶meters); - -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 |
