Merge pull request #1219 from ctrlaltdavid/dev/webrtc-signaling

WebRTC Signaling Channel
This commit is contained in:
Kalila 2021-06-02 05:44:03 -04:00 committed by GitHub
commit 229164c5b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 268 additions and 21 deletions

View file

@ -948,7 +948,7 @@ In an international environment English is the preferred language.
#### [4.3.2] Use // for all comments, including multi-line comments.
An exception to this rule applies for jsdoc or Doxygen comments.
An exception to this rule applies to JSDoc and Doxygen comments.
```cpp
// Comment spanning
@ -1008,3 +1008,11 @@ These types of comments are explicitly not allowed. If you need to break up sect
//--------------------------------------------------------------------------------
```
#### [4.3.6] Doxygen comments should use "///"
Use the `///` style of [Doxygen](https://www.doxygen.nl/index.html) comments when documenting public interfaces.
Some editors can automatically create a Doxygen documentation stub if you type `///` in the line above the item to be
documented.
**Visual Studio:** To configure Visual Studio's Doxygen commenting behavior, search for "Doxygen" in Tools > Options.

View file

@ -165,7 +165,11 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_gatekeeper(this),
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this)
#ifdef WEBRTC_DATA_CHANNEL
_webrtcSignalingServer(QHostAddress::AnyIPv4, DEFAULT_DOMAIN_SERVER_WS_PORT, this),
#endif
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT,
QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this)
{
if (_parentPID != -1) {
watchParentProcess(_parentPID);

View file

@ -27,6 +27,8 @@
#include <Assignment.h>
#include <HTTPSConnection.h>
#include <LimitedNodeList.h>
#include <shared/WebRTC.h>
#include <webrtc/WebRTCSignalingServer.h>
#include "AssetsBackupHandler.h"
#include "DomainGatekeeper.h"
@ -311,6 +313,10 @@ private:
std::unordered_map<int, std::unique_ptr<QTemporaryFile>> _pendingContentFiles;
QThread _assetClientThread;
#ifdef WEBRTC_DATA_CHANNEL
WebRTCSignalingServer _webrtcSignalingServer;
#endif
};

View file

@ -4,6 +4,7 @@
//
// Created by Stephen Birarda on 1/22/13.
// Copyright 2013 High Fidelity, Inc.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -363,7 +364,7 @@ AudioClient::AudioClient() {
configureReverb();
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
configureWebrtc();
#endif
@ -1142,7 +1143,7 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) {
}
}
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
static void deinterleaveToFloat(const int16_t* src, float* const* dst, int numFrames, int numChannels) {
for (int i = 0; i < numFrames; i++) {
@ -1261,7 +1262,7 @@ void AudioClient::processWebrtcNearEnd(int16_t* samples, int numFrames, int numC
}
}
#endif // WEBRTC_ENABLED
#endif // WEBRTC_AUDIO
void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
// If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here.
@ -1462,7 +1463,7 @@ void AudioClient::handleMicAudioInput() {
}
isClipping = (_timeSinceLastClip >= 0.0f) && (_timeSinceLastClip < 2.0f); // 2 second hold time
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
if (_isAECEnabled) {
processWebrtcNearEnd(inputAudioSamples.get(), inputSamplesRequired / _inputFormat.channelCount(),
_inputFormat.channelCount(), _inputFormat.sampleRate());
@ -2420,7 +2421,7 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
// limit the audio
_audio->_audioLimiter.render(mixBuffer, scratchBuffer, framesPopped);
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
if (_audio->_isAECEnabled) {
_audio->processWebrtcFarEnd(scratchBuffer, framesPopped, OUTPUT_CHANNEL_COUNT, _audio->_outputFormat.sampleRate());
}

View file

@ -4,6 +4,7 @@
//
// Created by Stephen Birarda on 1/22/13.
// Copyright 2013 High Fidelity, Inc.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -56,6 +57,11 @@
#include "AudioFileWav.h"
#include "HifiAudioDeviceInfo.h"
#if defined(WEBRTC_AUDIO)
# include <modules/audio_processing/include/audio_processing.h>
# include "modules/audio_processing/audio_processing_impl.h"
#endif
#ifdef _WIN32
#pragma warning( push )
#pragma warning( disable : 4273 )
@ -450,7 +456,7 @@ private:
void updateReverbOptions();
void handleLocalEchoAndReverb(QByteArray& inputByteArray);
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
static const int WEBRTC_SAMPLE_RATE_MAX = 96000;
static const int WEBRTC_CHANNELS_MAX = 2;
static const int WEBRTC_FRAMES_MAX = webrtc::AudioProcessing::kChunkSizeMs * WEBRTC_SAMPLE_RATE_MAX / 1000;

View file

@ -1,9 +1,13 @@
set(TARGET_NAME networking)
setup_hifi_library(Network)
setup_hifi_library(Network WebSockets)
link_hifi_libraries(shared platform)
target_openssl()
target_tbb()
if (WIN32)
# WEBRTC TODO: Add UNIX.
target_webrtc()
endif ()
if (WIN32)
# we need ws2_32.lib on windows, but it's static so we don't bubble it up

View file

@ -40,7 +40,15 @@ const unsigned short DEFAULT_DOMAIN_SERVER_PORT =
? QProcessEnvironment::systemEnvironment()
.value("HIFI_DOMAIN_SERVER_PORT")
.toUShort()
: 40102;
: 40102; // UDP
const unsigned short DEFAULT_DOMAIN_SERVER_WS_PORT =
QProcessEnvironment::systemEnvironment()
.contains("VIRCADIA_DOMAIN_SERVER_WS_PORT")
? QProcessEnvironment::systemEnvironment()
.value("VIRCADIA_DOMAIN_SERVER_WS_PORT")
.toUShort()
: 40102; // TCP
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT =
QProcessEnvironment::systemEnvironment()

View file

@ -16,3 +16,4 @@ Q_LOGGING_CATEGORY(networking_ice, "hifi.networking.ice")
Q_LOGGING_CATEGORY(resourceLog, "hifi.networking.resource")
Q_LOGGING_CATEGORY(asset_client, "hifi.networking.asset_client")
Q_LOGGING_CATEGORY(messages_client, "hifi.networking.messages_client")
Q_LOGGING_CATEGORY(networking_webrtc, "hifi.networking.webrtc")

View file

@ -19,5 +19,6 @@ Q_DECLARE_LOGGING_CATEGORY(networking)
Q_DECLARE_LOGGING_CATEGORY(networking_ice)
Q_DECLARE_LOGGING_CATEGORY(asset_client)
Q_DECLARE_LOGGING_CATEGORY(messages_client)
Q_DECLARE_LOGGING_CATEGORY(networking_webrtc)
#endif // hifi_NetworkLogging_h

View file

@ -409,7 +409,6 @@ void NodeList::sendDomainServerCheckIn() {
if (domainPacketType == PacketType::DomainConnectRequest) {
#if (PR_BUILD || DEV_BUILD)
// #######
if (_shouldSendNewerVersion) {
domainPacket->setVersion(versionForPacketType(domainPacketType) + 1);
}

View file

@ -0,0 +1,96 @@
//
// WebRTCSignalingServer.cpp
// libraries/networking/src/webrtc
//
// Created by David Rowe on 16 May 2021.
// Copyright 2021 Vircadia contributors.
//
#include "WebRTCSignalingServer.h"
#if defined(WEBRTC_DATA_CHANNEL)
#include <QtCore>
#include <QWebSocket>
#include "../NetworkLogging.h"
#include "../NodeType.h"
const int WEBRTC_SOCKET_CHECK_INTERVAL_IN_MS = 30000;
WebRTCSignalingServer::WebRTCSignalingServer(const QHostAddress& address, quint16 port, QObject* parent) :
QObject(parent),
_address(address),
_port(port),
_webSocketServer(new QWebSocketServer(QStringLiteral("WebRTC Signaling Server"), QWebSocketServer::NonSecureMode, this))
{
connect(_webSocketServer, &QWebSocketServer::newConnection, this, &WebRTCSignalingServer::newWebSocketConnection);
bindSocket();
// Automatically recover from network interruptions.
_isWebSocketServerListeningTimer = new QTimer(this);
connect(_isWebSocketServerListeningTimer, &QTimer::timeout, this, &WebRTCSignalingServer::checkWebSocketServerIsListening);
_isWebSocketServerListeningTimer->start(WEBRTC_SOCKET_CHECK_INTERVAL_IN_MS);
}
void WebRTCSignalingServer::checkWebSocketServerIsListening() {
if (!_webSocketServer->isListening()) {
qCWarning(networking_webrtc) << "WebSocket on port " << QString::number(_port) << " is no longer listening";
_webSockets.clear();
bindSocket();
}
}
void WebRTCSignalingServer::bindSocket() {
if (!_webSocketServer->listen(_address, _port)) {
qCWarning(networking_webrtc) << "Failed to open WebSocket for WebRTC signaling.";
}
}
void WebRTCSignalingServer::webSocketTextMessageReceived(const QString& message) {
auto source = qobject_cast<QWebSocket*>(sender());
if (source) {
QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object();
// WEBRTC TODO: Move domain server echoing into domain server.
if (json.keys().contains("echo") && json.value("to").toString() == QString(QChar(NodeType::DomainServer))) {
// Domain server echo request - echo message back to sender.
json.remove("to");
json.insert("from", QString(QChar(NodeType::DomainServer)));
QString echo = QJsonDocument(json).toJson();
source->sendTextMessage(echo);
} else {
// WebRTC message or assignment client echo request. (Send both to target.)
json.insert("from", source->peerPort());
emit messageReceived(json);
}
} else {
qCWarning(networking_webrtc) << "Failed to find WebSocket for incoming WebRTC signaling message.";
}
}
void WebRTCSignalingServer::sendMessage(const QJsonObject& message) {
quint16 destinationPort = message.value("to").toInt();
if (_webSockets.contains(destinationPort)) {
_webSockets.value(destinationPort)->sendTextMessage(QString(QJsonDocument(message).toJson()));
} else {
qCWarning(networking_webrtc) << "Failed to find WebSocket for outgoing WebRTC signaling message.";
}
}
void WebRTCSignalingServer::webSocketDisconnected() {
auto source = qobject_cast<QWebSocket*>(sender());
if (source) {
_webSockets.remove(source->peerPort());
}
}
void WebRTCSignalingServer::newWebSocketConnection() {
auto webSocket = _webSocketServer->nextPendingConnection();
connect(webSocket, &QWebSocket::textMessageReceived, this, &WebRTCSignalingServer::webSocketTextMessageReceived);
connect(webSocket, &QWebSocket::disconnected, this, &WebRTCSignalingServer::webSocketDisconnected);
_webSockets.insert(webSocket->peerPort(), webSocket);
}
#endif // WEBRTC_DATA_CHANNEL

View file

@ -0,0 +1,104 @@
//
// WebRTCSignalingServer.h
// libraries/networking/src/webrtc
//
// Provides a signaling channel for setting up WebRTC connections between the Web app and the domain servers and mixers.
//
// Created by David Rowe on 16 May 2021.
// Copyright 2021 Vircadia contributors.
//
#ifndef vircadia_SignalingServer_h
#define vircadia_SignalingServer_h
#include <shared/WebRTC.h>
#if defined(WEBRTC_DATA_CHANNEL)
#include <QObject>
#include <QtCore/QTimer>
#include <QWebSocketServer>
#include "../HifiSockAddr.h"
/// @brief WebRTC signaling server that Interface clients can use to initiate WebRTC connections to the domain server and
/// assignment clients.
///
/// @details The signaling server is expected to be hosted in the domain server. It provides a WebSocket for Interface clients
/// to use in the WebRTC signaling handshake process to establish WebRTC data channel connections to each of the domain server
/// and the assignment clients (i.e., separate WebRTC data channels for each but only a single signaling WebSocket). The
/// assignment client signaling messages are expected to be relayed - by the domain server - via Vircadia protocol messages on
/// the UDP connections between the domain server and assignment clients.
///
/// Additionally, for debugging purposes, instead of containing a WebRTC payload a signaling message may be an echo request.
/// This is bounced back to the client from the WebRTCSignalingServer if the domain server was the target, otherwise it is
/// expected to be bounced back upon receipt by the relevant assignment client.
///
/// The signaling messages are sent and received as JSON objects, with `to` and `from` fields in addition to either the WebRTC
/// signaling `data` payload or an `echo` request:
///
/// | Interface -> Server ||
/// | -------- | -----------------------|
/// | `to` | NodeType |
/// | `from` | WebSocket port number* |
/// | [`data`] | WebRTC payload |
/// | [`echo`] | Echo request |
/// * The `from` field is filled in by the WebRTCSignalingServer.
///
/// | Server -> Interface ||
/// | -------- | --------------------- |
/// | `to` | WebSocket port number |
/// | `from` | NodeType |
/// | [`data`] | WebRTC payload |
/// | [`echo`] | Echo request |
///
class WebRTCSignalingServer : public QObject {
Q_OBJECT
public:
/// @brief Constructs a new WebRTCSignalingServer.
/// @param address The IP address to use for the WebSocket.
/// @param port The port to use for the WebSocket.
/// @param parent Qt parent object.
WebRTCSignalingServer(const QHostAddress& address, quint16 port, QObject* parent = nullptr);
public slots:
/// @brief Send a WebRTC signaling channel message to an Interface client.
/// @param message The message to send to the Interface client. Includes details of the sender and the destination in
/// addition to the WebRTC signaling channel payload.
void sendMessage(const QJsonObject& message);
signals:
/// @brief A WebRTC signaling channel message was received from an Interface client.
/// @param message The message received from the Interface client. Includes details of the sender and the destination in
/// addition to the WebRTC signaling channel payload.\n
/// Not emitted if the message was an echo request for the domain server.
void messageReceived(const QJsonObject& message);
private slots:
void newWebSocketConnection();
void webSocketTextMessageReceived(const QString& message);
void webSocketDisconnected();
private:
void checkWebSocketServerIsListening();
void bindSocket();
QWebSocketServer* _webSocketServer;
QHostAddress _address;
const quint16 _port;
QHash<quint16, QWebSocket*> _webSockets; // client WebSocket port, client WebSocket object
QTimer* _isWebSocketServerListeningTimer;
};
#endif // WEBRTC_DATA_CHANNEL
#endif // vircadia_SignalingServer_h

View file

@ -3,6 +3,7 @@
// libraries/shared/src/shared/
//
// Copyright 2019 High Fidelity, Inc.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -11,26 +12,29 @@
#ifndef hifi_WebRTC_h
#define hifi_WebRTC_h
#ifndef QSYSTEMDETECTION_H
#include <qsystemdetection.h>
#endif
// WEBRTC_AUDIO: WebRTC audio features, e.g., echo canceling.
// WEBRTC_DATA_CHANNEL: WebRTC client-server connections in parallel with UDP.
#if defined(Q_OS_MAC)
# define WEBRTC_ENABLED 1
# define WEBRTC_AUDIO 1
# define WEBRTC_POSIX 1
#elif defined(Q_OS_WIN)
# define WEBRTC_ENABLED 1
# define WEBRTC_AUDIO 1
# define WEBRTC_DATA_CHANNEL 1
# define WEBRTC_WIN 1
# define NOMINMAX 1
# define WIN32_LEAN_AND_MEAN 1
#elif defined(Q_OS_ANDROID)
// I don't yet have a working libwebrtc for android
// # define WEBRTC_ENABLED 1
// # define WEBRTC_AUDIO 1
// # define WEBRTC_POSIX 1
#elif defined(Q_OS_LINUX)
# define WEBRTC_ENABLED 1
# define WEBRTC_AUDIO 1
# define WEBRTC_POSIX 1
#endif
#if defined(WEBRTC_ENABLED)
# include <modules/audio_processing/include/audio_processing.h>
# include "modules/audio_processing/audio_processing_impl.h"
#endif
#endif // hifi_WebRTC_h

View file

@ -12,6 +12,11 @@ Make a `/build/doxygen/` directory.
If you want to run Doxygen from a command prompt, add the Doxygen install's `/bin` directory to your system PATH.
## Documenting
See section 4.3.6 of the [*Coding Standard*](../../CODING_STANDARD.md).
## Running
### Using the Doxywizard GUI

@ -1 +1 @@
Subproject commit d2e383cab811018422433fe0d8f224e53e0506cf
Subproject commit ac1f13a39c702ee54bf2cda8bc35e5d34f7f0756