Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🕑 Support playout delay extension #134

Merged
merged 14 commits into from
Dec 16, 2021
4 changes: 4 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ if systemd_dep.found()
add_project_arguments('-DSYSTEMD_WATCHDOG_SUPPORT', language: 'cpp')
endif

if get_option('janus_playout_delay_support')
add_project_arguments('-DJANUS_PLAYOUT_DELAY_SUPPORT', language: 'cpp')
endif

deps = [
dependency('glib-2.0'),
dependency('libsrtp2'),
Expand Down
1 change: 1 addition & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
option('systemd_watchdog_support', type : 'feature', value : 'auto') # http://0pointer.de/blog/projects/watchdog.html
option('janus_playout_delay_support', type : 'boolean', value: false)
78 changes: 69 additions & 9 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <unistd.h>
#include <wordexp.h>

using namespace std::chrono_literals;

#pragma region Public methods
void Configuration::Load()
{
Expand Down Expand Up @@ -79,7 +81,7 @@ void Configuration::Load()
// FTL_ORCHESTRATOR_PORT -> OrchestratorPort
if (char* varVal = std::getenv("FTL_ORCHESTRATOR_PORT"))
{
orchestratorPort = std::stoi(varVal);
orchestratorPort = std::stoul(varVal);
}

// FTL_ORCHESTRATOR_PSK -> OrchestratorPsk
Expand Down Expand Up @@ -120,25 +122,49 @@ void Configuration::Load()
// FTL_SERVICE_METADATAREPORTINTERVALMS -> ServiceConnectionMetadataReportInterval
if (char* varVal = std::getenv("FTL_SERVICE_METADATAREPORTINTERVALMS"))
{
serviceConnectionMetadataReportInterval = std::chrono::milliseconds(std::stoi(varVal));
serviceConnectionMetadataReportInterval = std::chrono::milliseconds(std::stoul(varVal));
}

// FTL_MAX_ALLOWED_BITS_PER_SECOND -> MaxAllowedBitsPerSecond
if (char* varVal = std::getenv("FTL_MAX_ALLOWED_BITS_PER_SECOND"))
{
maxAllowedBitsPerSecond = std::stoi(varVal);
maxAllowedBitsPerSecond = std::stoul(varVal);
}

// FTL_ROLLING_SIZE_AVG_MS -> RollingSizeAvgMs
if (char* varVal = std::getenv("FTL_ROLLING_SIZE_AVG_MS"))
{
rollingSizeAvgMs = std::stoi(varVal);
rollingSizeAvgMs = std::stoul(varVal);
}

// FTL_NACK_LOST_PACKETS -> IsNackLostPacketsEnabled
if (char* varVal = std::getenv("FTL_NACK_LOST_PACKETS"))
{
nackLostPackets = std::stoi(varVal);
nackLostPackets = std::stoul(varVal);
}

// FTL_PLAYOUT_DELAY_MIN_MS and FTL_PLAYOUT_DELAY_MAX_MS -> PlayoutDelay
{
char* minVal = std::getenv("FTL_PLAYOUT_DELAY_MIN_MS");
char* maxVal = std::getenv("FTL_PLAYOUT_DELAY_MAX_MS");

if (minVal || maxVal)
{
if (!PLAYOUT_DELAY_SUPPORT)
{
spdlog::warn("Ignoring playout delay configuration, option janus_playout_delay_support is not enabled");
}
else if (minVal && maxVal)
{
auto min = std::chrono::milliseconds(std::stoul(minVal));
auto max = std::chrono::milliseconds(std::stoul(maxVal));
playoutDelay = PlayoutDelay(min, max);
}
else
{
throw InvalidConfigurationException("Both min and max playout delay values must be set together");
}
}
}

// FTL_SERVICE_DUMMY_HMAC_KEY -> DummyHmacKey
Expand Down Expand Up @@ -172,13 +198,13 @@ void Configuration::Load()
// FTL_SERVICE_GLIMESH_PORT -> GlimeshServicePort
if (char* varVal = std::getenv("FTL_SERVICE_GLIMESH_PORT"))
{
glimeshServicePort = std::stoi(varVal);
glimeshServicePort = std::stoul(varVal);
}

// FTL_SERVICE_GLIMESH_HTTPS -> GlimeshServiceUseHttps
if (char* varVal = std::getenv("FTL_SERVICE_GLIMESH_HTTPS"))
{
glimeshServiceUseHttps = std::stoi(varVal);
glimeshServiceUseHttps = std::stoul(varVal);
}

// FTL_SERVICE_GLIMESH_CLIENTID -> GlimeshServiceClientId
Expand All @@ -202,13 +228,13 @@ void Configuration::Load()
// FTL_SERVICE_REST_PORT -> RestServicePort
if (char* varVal = std::getenv("FTL_SERVICE_REST_PORT"))
{
restServicePort = std::stoi(varVal);
restServicePort = std::stoul(varVal);
}

// FTL_SERVICE_REST_HTTPS -> RestServiceUseHttps
if (char* varVal = std::getenv("FTL_SERVICE_REST_HTTPS"))
{
restServiceUseHttps = std::stoi(varVal);
restServiceUseHttps = std::stoul(varVal);
}

// FTL_SERVICE_REST_PATH_BASE -> RestServicePathBase
Expand All @@ -223,6 +249,34 @@ void Configuration::Load()
restServiceAuthToken = std::string(varVal);
}
}

Configuration::PlayoutDelay::PlayoutDelay(std::chrono::milliseconds min_ms, std::chrono::milliseconds max_ms)
{
if (min_ms < 0ms)
{
throw InvalidConfigurationException("Playout delay min must be greater than or equal to 0");
}
if (min_ms > 40950ms)
{
throw InvalidConfigurationException("FTL_PLAYOUT_DELAY_MIN_MS must be less than or equal to 40950ms");
}
if (max_ms < 0ms)
{
throw InvalidConfigurationException("Playout delay max must be greater than or equal to 0");
}
if (max_ms > 40950ms)
{
throw InvalidConfigurationException("FTL_PLAYOUT_DELAY_MAX_MS must be less than or equal to 40950ms");
}
if (min_ms > max_ms)
{
throw InvalidConfigurationException("FTL_PLAYOUT_DELAY_MIN_MS cannot be greater than FTL_PLAYOUT_DELAY_MAX_MS");
}

// Convert validated values to units of 10ms as used in the playout-delay header extension
min = min_ms.count() / 10;
max = max_ms.count() / 10;
}
#pragma endregion

#pragma region Configuration values
Expand Down Expand Up @@ -291,6 +345,11 @@ bool Configuration::IsNackLostPacketsEnabled()
return nackLostPackets;
}

std::optional<Configuration::PlayoutDelay> Configuration::GetPlayoutDelay()
{
return playoutDelay;
}

std::string Configuration::GetGlimeshServiceHostname()
{
return glimeshServiceHostname;
Expand Down Expand Up @@ -340,6 +399,7 @@ std::string Configuration::GetRestServiceAuthToken()
{
return restServiceAuthToken;
}

#pragma endregion

#pragma region Private methods
Expand Down
61 changes: 60 additions & 1 deletion src/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>

Expand All @@ -33,6 +34,42 @@ enum class ServiceConnectionKind
class Configuration
{
public:
/**
* @brief Range of expected delay between server capturing a frame and clients receiving it.
*
* Note min/max delay are in units of 10ms as specified by the playout-delay specification, but
* the constructor takes the chrono::duration type for clarity and convenience.
*
* Sent to clients via an experimental RTP extension only implemented for Chrome. Can be used to
* suggest a bounded range client should delay before rendering a frame. In theory the client
* should determine an appropriate delay to account for network jitter and rendering time.
*
* However, we have seen Chrome be wrong when choosing a delay, and there are other use cases
* where bounding the minimum or maximum delay can be useful. See the RFC for more details.
* https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay
*
* Reasonable values range from 0 to 10,000 milliseconds (rounded to a granularity of 10ms). The
* ideal value depends on expected network delay and jitter clients will experience. Generally
* a minimum of 100ms-400ms and a maximum of a few seconds is a good starting range.
*/
struct PlayoutDelay
{
public:
PlayoutDelay(std::chrono::milliseconds min_ms, std::chrono::milliseconds max_ms);

/* Public methods */
uint16_t MinDelay() {
return min;
}
uint16_t MaxDelay() {
return max;
}

private:
uint16_t min;
uint16_t max;
};

/* Public methods */
void Load();

Expand All @@ -48,6 +85,7 @@ class Configuration
uint32_t GetMaxAllowedBitsPerSecond();
uint32_t GetRollingSizeAvgMs();
bool IsNackLostPacketsEnabled();
std::optional<PlayoutDelay> GetPlayoutDelay();

// Dummy Service Connection Values
std::vector<std::byte> GetDummyHmacKey();
Expand All @@ -68,6 +106,16 @@ class Configuration
std::string GetRestServiceAuthToken();

private:
/* Constants */
// Playout delay configuration can only used if your Janus version supports the playout-delay
// RTP extension, hence the compiler flag. We use this constant to print a warning to the user
// if they set a delay configuration but it is not being used.
#if defined(JANUS_PLAYOUT_DELAY_SUPPORT)
static constexpr bool PLAYOUT_DELAY_SUPPORT = true;
#else
static constexpr bool PLAYOUT_DELAY_SUPPORT = false;
#endif

/* Backing stores */
std::string myHostname;
NodeKind nodeKind = NodeKind::Standalone;
Expand All @@ -80,6 +128,7 @@ class Configuration
uint32_t maxAllowedBitsPerSecond = 0;
uint32_t rollingSizeAvgMs = 2000;
bool nackLostPackets = false;
std::optional<PlayoutDelay> playoutDelay;

// Dummy Service Connection Backing Stores
// "aBcDeFgHiJkLmNoPqRsTuVwXyZ123456"
Expand Down Expand Up @@ -113,4 +162,14 @@ class Configuration
* @brief Takes a hex string of format "010203FF" and converts it to an array of bytes.
*/
std::vector<std::byte> hexStringToByteArray(std::string hexString);
};
};

/**
* @brief Exception describing invalid configuration values
*/
struct InvalidConfigurationException : std::runtime_error
{
InvalidConfigurationException(const char *message) throw() : std::runtime_error(message)
{
}
};
6 changes: 3 additions & 3 deletions src/FtlClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,12 @@ void FtlClient::SetOnClosed(std::function<void()> onClosed)
this->onClosed = onClosed;
}

void FtlClient::RelayPacket(const std::vector<std::byte>& packet)
void FtlClient::RelayPacket(const RtpPacket& packet)
{
if (mediaSocketHandle != 0)
{
size_t writeResult = write(mediaSocketHandle, packet.data(), packet.size());
if (writeResult != packet.size())
size_t writeResult = write(mediaSocketHandle, packet.Bytes.data(), packet.Bytes.size());
if (writeResult != packet.Bytes.size())
{
// TODO: Handle writeResult
}
Expand Down
3 changes: 2 additions & 1 deletion src/FtlClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#pragma once

#include "Rtp/RtpPacket.h"
#include "Utilities/FtlTypes.h"
#include "Utilities/Result.h"

Expand Down Expand Up @@ -79,7 +80,7 @@ class FtlClient
/**
* @brief Relays a packet from an incoming FtlStream
*/
void RelayPacket(const std::vector<std::byte>& packet);
void RelayPacket(const RtpPacket& packet);

private:
/* Private structs */
Expand Down
8 changes: 4 additions & 4 deletions src/FtlMediaConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ FtlMediaConnection::FtlMediaConnection(
const ftl_channel_id_t channelId,
const ftl_stream_id_t streamId,
const ClosedCallback onClosed,
const RtpPacketCallback onRtpPacketBytes,
const RtpPacketCallback onRtpPacket,
const uint32_t rollingSizeAvgMs,
const bool nackLostPackets)
:
Expand All @@ -38,7 +38,7 @@ FtlMediaConnection::FtlMediaConnection(
channelId(channelId),
streamId(streamId),
onClosed(onClosed),
onRtpPacketBytes(onRtpPacketBytes),
onRtpPacket(onRtpPacket),
rollingSizeAvgMs(rollingSizeAvgMs),
nackLostPackets(nackLostPackets),
thread(std::jthread(std::bind(&FtlMediaConnection::threadBody, this, std::placeholders::_1)))
Expand Down Expand Up @@ -577,9 +577,9 @@ void FtlMediaConnection::processAudioVideoRtpPacket(const RtpPacket& rtpPacket,
{
processRtpPacketKeyframe(rtpPacket, dataLock);

if (onRtpPacketBytes)
if (onRtpPacket)
{
onRtpPacketBytes(rtpPacket.Bytes);
onRtpPacket(rtpPacket);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/FtlMediaConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class FtlMediaConnection
public:
/* Public types */
using ClosedCallback = std::function<void(FtlMediaConnection&)>;
using RtpPacketCallback = std::function<void(const std::vector<std::byte>&)>;
using RtpPacketCallback = std::function<void(const RtpPacket&)>;

/* Constructor/Destructor */
FtlMediaConnection(
Expand Down Expand Up @@ -91,7 +91,7 @@ class FtlMediaConnection
const ftl_channel_id_t channelId;
const ftl_stream_id_t streamId;
const ClosedCallback onClosed;
const RtpPacketCallback onRtpPacketBytes;
const RtpPacketCallback onRtpPacket;
const uint32_t rollingSizeAvgMs;
const bool nackLostPackets;
// Stream data
Expand Down
2 changes: 1 addition & 1 deletion src/FtlServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ void FtlServer::eventStreamIdAssigned(std::shared_ptr<FtlServerStreamIdAssignedE
std::move(mediaTransport),
mediaPort,
event->Metadata,
[rtpPacketSink](const std::vector<std::byte> packet)
[rtpPacketSink](const RtpPacket& packet)
{
rtpPacketSink->SendRtpPacket(packet);
});
Expand Down
7 changes: 5 additions & 2 deletions src/JanusFtl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ JanusFtl::JanusFtl(
#else
spdlog::set_level(spdlog::level::info);
#endif
spdlog::flush_on(spdlog::level::err);

configuration = std::make_unique<Configuration>();
configuration->Load();
maxAllowedBitsPerSecond = configuration->GetMaxAllowedBitsPerSecond();
rollingSizeAvgMs = configuration->GetRollingSizeAvgMs();
metadataReportInterval = configuration->GetServiceConnectionMetadataReportInterval();
watchdog = std::make_unique<Watchdog>(configuration->GetServiceConnectionMetadataReportInterval());
playoutDelay = configuration->GetPlayoutDelay();

initVideoDecoders();

Expand Down Expand Up @@ -96,7 +98,7 @@ JanusFtl::~JanusFtl()
void JanusFtl::CreateSession(janus_plugin_session* handle, int* error)
{
std::unique_lock lock(streamDataMutex);
auto session = std::make_unique<JanusSession>(handle, janusCore);
auto session = std::make_unique<JanusSession>(handle, janusCore, playoutDelay);
handle->plugin_handle = session.get();
sessions[handle] = ActiveSession
{
Expand Down Expand Up @@ -881,7 +883,8 @@ std::string JanusFtl::generateSdpOffer(const ActiveSession& session, const Janus
"a=rtcp-fb:" << videoPayloadType << " nack pli\r\n" << // Send us picture-loss-indicators
// "a=rtcp-fb:96 nack goog-remb\r\n" << // Send some congestion indicator thing
"a=sendonly\r\n" <<
"a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid\r\n";
"a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" <<
"a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n";
}
return offerStream.str();
}
Expand Down
Loading