diff options
Diffstat (limited to 'src/client')
| -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 |
9 files changed, 1599 insertions, 754 deletions
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 |
