2
0
Эх сурвалжийг харах

Merge remote-tracking branch 'origin/master' into dependency-descriptor

melpon 1 жил өмнө
parent
commit
005820eb54

+ 9 - 3
CMakeLists.txt

@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.7)
 project(libdatachannel
-	VERSION 0.20.2
+	VERSION 0.21.0
 	LANGUAGES CXX)
 set(PROJECT_DESCRIPTION "C/C++ WebRTC network library featuring Data Channels, Media Transport, and WebSockets")
 
@@ -88,6 +88,7 @@ set(LIBDATACHANNEL_SOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/src/rtp.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/plihandler.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/pacinghandler.cpp
 )
 
 set(LIBDATACHANNEL_HEADERS
@@ -125,6 +126,7 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpnackresponder.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/utils.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/plihandler.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/pacinghandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/version.h
 )
 
@@ -344,7 +346,9 @@ else()
 		target_link_libraries(datachannel PRIVATE libSRTP::srtp2)
 		target_link_libraries(datachannel-static PRIVATE libSRTP::srtp2)
 	else()
-		add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
+		if(NOT TARGET srtp2)
+			add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
+		endif()
 		target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=0)
 		target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=0)
 		target_link_libraries(datachannel PRIVATE srtp2)
@@ -373,7 +377,9 @@ if (USE_GNUTLS)
 		target_link_libraries(datachannel-static PRIVATE Nettle::Nettle)
 	endif()
 elseif(USE_MBEDTLS)
-	find_package(MbedTLS 3 REQUIRED)
+	if(NOT TARGET MbedTLS::MbedTLS)
+		find_package(MbedTLS 3 REQUIRED)
+	endif()
 	target_compile_definitions(datachannel PRIVATE USE_MBEDTLS=1)
 	target_compile_definitions(datachannel-static PRIVATE USE_MBEDTLS=1)
 	target_link_libraries(datachannel PRIVATE MbedTLS::MbedTLS)

+ 1 - 1
deps/libjuice

@@ -1 +1 @@
-Subproject commit 0b6f958baba55e1a4eb31ec2137f62b2e07382ae
+Subproject commit 502da067489073ecaef892e6639d0adf63ba1ac6

+ 6 - 3
examples/streamer/h264fileparser.cpp

@@ -11,6 +11,7 @@
 #include "rtc/rtc.hpp"
 
 #include <fstream>
+#include <cstring>
 
 #ifdef _WIN32
 #include <winsock2.h>
@@ -29,7 +30,9 @@ void H264FileParser::loadNextSample() {
     while (i < sample.size()) {
         assert(i + 4 < sample.size());
         auto lengthPtr = (uint32_t *) (sample.data() + i);
-        uint32_t length = ntohl(*lengthPtr);
+        uint32_t length;
+        std::memcpy(&length, lengthPtr, sizeof(uint32_t));
+        length = ntohl(length);
         auto naluStartIndex = i + 4;
         auto naluEndIndex = naluStartIndex + length;
         assert(naluEndIndex <= sample.size());
@@ -50,8 +53,8 @@ void H264FileParser::loadNextSample() {
     }
 }
 
-vector<byte> H264FileParser::initialNALUS() {
-    vector<byte> units{};
+vector<std::byte> H264FileParser::initialNALUS() {
+    vector<std::byte> units{};
     if (previousUnitType7.has_value()) {
         auto nalu = previousUnitType7.value();
         units.insert(units.end(), nalu.begin(), nalu.end());

+ 1 - 0
include/rtc/configuration.hpp

@@ -75,6 +75,7 @@ struct RTC_CPP_EXPORT Configuration {
 	bool enableIceTcp = false;    // libnice only
 	bool enableIceUdpMux = false; // libjuice only
 	bool disableAutoNegotiation = false;
+	bool disableAutoGathering = false;
 	bool forceMediaTransport = false;
 
 	// Port range

+ 2 - 1
include/rtc/frameinfo.hpp

@@ -14,7 +14,8 @@
 namespace rtc {
 
 struct RTC_CPP_EXPORT FrameInfo {
-	FrameInfo(uint32_t timestamp) : timestamp(timestamp){};
+	FrameInfo(uint8_t payloadType, uint32_t timestamp) : payloadType(payloadType), timestamp(timestamp){};
+	uint8_t payloadType; // Indicates codec of the frame
 	uint32_t timestamp = 0; // RTP Timestamp
 };
 

+ 7 - 2
include/rtc/h264rtpdepacketizer.hpp

@@ -15,6 +15,7 @@
 #include "common.hpp"
 #include "mediahandler.hpp"
 #include "message.hpp"
+#include "nalunit.hpp"
 #include "rtp.hpp"
 
 #include <iterator>
@@ -24,16 +25,20 @@ namespace rtc {
 /// RTP depacketization for H264
 class RTC_CPP_EXPORT H264RtpDepacketizer : public MediaHandler {
 public:
-	H264RtpDepacketizer() = default;
+	using Separator = NalUnit::Separator;
+
+	H264RtpDepacketizer(Separator separator = Separator::LongStartSequence);
 	virtual ~H264RtpDepacketizer() = default;
 
 	void incoming(message_vector &messages, const message_callback &send) override;
 
 private:
 	std::vector<message_ptr> mRtpBuffer;
+	const NalUnit::Separator mSeparator;
 
+	void addSeparator(binary &accessUnit);
 	message_vector buildFrames(message_vector::iterator firstPkt, message_vector::iterator lastPkt,
-	                           uint32_t timestamp);
+	                           uint8_t payloadType, uint32_t timestamp);
 };
 
 } // namespace rtc

+ 49 - 0
include/rtc/pacinghandler.hpp

@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2024 Sean DuBois <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef RTC_PACING_HANDLER_H
+#define RTC_PACING_HANDLER_H
+
+#if RTC_ENABLE_MEDIA
+
+#include "mediahandler.hpp"
+#include "utils.hpp"
+
+#include <atomic>
+#include <queue>
+
+namespace rtc {
+
+// Paced sending of RTP packets. It takes a stream of RTP packets that can have an uneven bitrate
+// and delivers them in a smoother manner by sending a fixed size of them on an interval
+class RTC_CPP_EXPORT PacingHandler : public MediaHandler {
+public:
+	PacingHandler(double bitsPerSecond, std::chrono::milliseconds sendInterval);
+
+	void outgoing(message_vector &messages, const message_callback &send) override;
+
+private:
+	std::atomic<bool> mHaveScheduled = false;
+
+	double mBytesPerSecond;
+	double mBudget;
+
+	std::chrono::milliseconds mSendInterval;
+	std::chrono::time_point<std::chrono::high_resolution_clock> mLastRun;
+
+	std::mutex mMutex;
+	std::queue<message_ptr> mRtpBuffer;
+
+	void schedule(const message_callback &send);
+};
+
+} // namespace rtc
+
+#endif // RTC_ENABLE_MEDIA
+
+#endif // RTC_PACING_HANDLER_H

+ 1 - 0
include/rtc/peerconnection.hpp

@@ -93,6 +93,7 @@ public:
 	void setLocalDescription(Description::Type type = Description::Type::Unspec);
 	void setRemoteDescription(Description description);
 	void addRemoteCandidate(Candidate candidate);
+	void gatherLocalCandidates(std::vector<IceServer> additionalIceServers = {});
 
 	void setMediaHandler(shared_ptr<MediaHandler> handler);
 	shared_ptr<MediaHandler> getMediaHandler();

+ 3 - 0
include/rtc/rtc.h

@@ -341,6 +341,9 @@ typedef struct {
 	// AV1 only
 	rtcObuPacketization obuPacketization; // OBU paketization for AV1 samples
 
+	uint8_t playoutDelayId;
+	uint16_t playoutDelayMin;
+	uint16_t playoutDelayMax;
 } rtcPacketizerInit;
 
 // Deprecated, do not use

+ 1 - 0
include/rtc/rtc.hpp

@@ -35,6 +35,7 @@
 #include "h265rtppacketizer.hpp"
 #include "mediahandler.hpp"
 #include "plihandler.hpp"
+#include "pacinghandler.hpp"
 #include "rtcpnackresponder.hpp"
 #include "rtcpreceivingsession.hpp"
 #include "rtcpsrreporter.hpp"

+ 0 - 19
include/rtc/rtp.hpp

@@ -358,25 +358,6 @@ struct RTC_CPP_EXPORT RtpRtx {
 	size_t copyTo(RtpHeader *dest, size_t totalSize, uint8_t originalPayloadType);
 };
 
-// For backward compatibility, do not use
-using RTP_ExtensionHeader [[deprecated]] = RtpExtensionHeader;
-using RTP [[deprecated]] = RtpHeader;
-using RTCP_ReportBlock [[deprecated]] = RtcpReportBlock;
-using RTCP_HEADER [[deprecated]] = RtcpHeader;
-using RTCP_FB_HEADER [[deprecated]] = RtcpFbHeader;
-using RTCP_SR [[deprecated]] = RtcpSr;
-using RTCP_SDES_ITEM [[deprecated]] = RtcpSdesItem;
-using RTCP_SDES_CHUNK [[deprecated]] = RtcpSdesChunk;
-using RTCP_SDES [[deprecated]] = RtcpSdes;
-using RTCP_RR [[deprecated]] = RtcpRr;
-using RTCP_REMB [[deprecated]] = RtcpRemb;
-using RTCP_PLI [[deprecated]] = RtcpPli;
-using RTCP_FIR_PART [[deprecated]] = RtcpFirPart;
-using RTCP_FIR [[deprecated]] = RtcpFir;
-using RTCP_NACK_PART [[deprecated]] = RtcpNackPart;
-using RTCP_NACK [[deprecated]] = RtcpNack;
-using RTP_RTX [[deprecated]] = RtpRtx;
-
 #pragma pack(pop)
 
 } // namespace rtc

+ 8 - 0
include/rtc/rtppacketizationconfig.hpp

@@ -69,7 +69,15 @@ public:
 		std::bitset<32> activeChains;
 		FrameDependencyStructure structure;
 	};
+
 	optional<DependencyDescriptorContext> dependencyDescriptorContext;
+	// the negotiated ID of the playout delay header extension
+	// https://webrtc.googlesource.com/src/+/main/docs/native-code/rtp-hdrext/playout-delay/README.md
+	uint8_t playoutDelayId;
+
+	// Minimum/maxiumum playout delay, in 10ms intervals. A value of 10 would equal a 100ms delay
+	uint16_t playoutDelayMin;
+	uint16_t playoutDelayMax;
 
 	/// Construct RTP configuration used in packetization process
 	/// @param ssrc SSRC of source

+ 3 - 3
include/rtc/version.h

@@ -2,8 +2,8 @@
 #define RTC_VERSION_H
 
 #define RTC_VERSION_MAJOR 0
-#define RTC_VERSION_MINOR 20
-#define RTC_VERSION_PATCH 2
-#define RTC_VERSION "0.20.2"
+#define RTC_VERSION_MINOR 21
+#define RTC_VERSION_PATCH 0
+#define RTC_VERSION "0.21.0"
 
 #endif

+ 3 - 0
src/capi.cpp

@@ -275,6 +275,9 @@ createRtpPacketizationConfig(const rtcPacketizationHandlerInit *init) {
 	                                                       init->payloadType, init->clockRate);
 	config->sequenceNumber = init->sequenceNumber;
 	config->timestamp = init->timestamp;
+	config->playoutDelayId = init->playoutDelayId;
+	config->playoutDelayMin = init->playoutDelayMin;
+	config->playoutDelayMax = init->playoutDelayMax;
 	return config;
 }
 

+ 69 - 46
src/h264rtpdepacketizer.cpp

@@ -10,78 +10,93 @@
 
 #include "h264rtpdepacketizer.hpp"
 #include "nalunit.hpp"
-#include "track.hpp"
 
-#include "impl/logcounter.hpp"
-
-#include <cmath>
-#include <utility>
-
-#ifdef _WIN32
-#include <winsock2.h>
-#else
-#include <arpa/inet.h>
-#endif
+#include "impl/internals.hpp"
 
 namespace rtc {
 
-const unsigned long stapaHeaderSize = 1;
-const auto fuaHeaderSize = 2;
+const binary naluLongStartCode = {byte{0}, byte{0}, byte{0}, byte{1}};
+const binary naluShortStartCode = {byte{0}, byte{0}, byte{1}};
 
 const uint8_t naluTypeSTAPA = 24;
 const uint8_t naluTypeFUA = 28;
 
+H264RtpDepacketizer::H264RtpDepacketizer(Separator separator) : mSeparator(separator) {
+	if (separator != Separator::StartSequence && separator != Separator::LongStartSequence &&
+	    separator != Separator::ShortStartSequence) {
+		throw std::invalid_argument("Invalid separator");
+	}
+}
+
+void H264RtpDepacketizer::addSeparator(binary &accessUnit) {
+	if (mSeparator == Separator::StartSequence || mSeparator == Separator::LongStartSequence) {
+		accessUnit.insert(accessUnit.end(), naluLongStartCode.begin(), naluLongStartCode.end());
+	} else if (mSeparator == Separator::ShortStartSequence) {
+		accessUnit.insert(accessUnit.end(), naluShortStartCode.begin(), naluShortStartCode.end());
+	} else {
+		throw std::invalid_argument("Invalid separator");
+	}
+}
+
 message_vector H264RtpDepacketizer::buildFrames(message_vector::iterator begin,
-                                                message_vector::iterator end, uint32_t timestamp) {
+                                                message_vector::iterator end, uint8_t payloadType,
+                                                uint32_t timestamp) {
 	message_vector out = {};
-	auto fua_buffer = std::vector<std::byte>{};
-	auto frameInfo = std::make_shared<FrameInfo>(timestamp);
+	auto accessUnit = binary{};
+	auto frameInfo = std::make_shared<FrameInfo>(payloadType, timestamp);
+	auto nFrags = 0;
 
-	for (auto it = begin; it != end; it++) {
+	for (auto it = begin; it != end; ++it) {
 		auto pkt = it->get();
 		auto pktParsed = reinterpret_cast<const rtc::RtpHeader *>(pkt->data());
-		auto headerSize =
-		    sizeof(rtc::RtpHeader) + pktParsed->csrcCount() + pktParsed->getExtensionHeaderSize();
-		auto nalUnitHeader = NalUnitHeader{std::to_integer<uint8_t>(pkt->at(headerSize))};
+		auto rtpHeaderSize = pktParsed->getSize() + pktParsed->getExtensionHeaderSize();
+		auto rtpPaddingSize = 0;
 
-		if (fua_buffer.size() != 0 || nalUnitHeader.unitType() == naluTypeFUA) {
-			if (fua_buffer.size() == 0) {
-				fua_buffer.push_back(std::byte(0));
-			}
+		if (pktParsed->padding()) {
+			rtpPaddingSize = std::to_integer<uint8_t>(pkt->at(pkt->size() - 1));
+		}
 
-			auto nalUnitFragmentHeader =
-			    NalUnitFragmentHeader{std::to_integer<uint8_t>(pkt->at(headerSize + 1))};
+		if (pkt->size() == rtpHeaderSize + rtpPaddingSize) {
+			PLOG_VERBOSE << "H.264 RTP packet has empty payload";
+			continue;
+		}
 
-			std::copy(pkt->begin() + headerSize + fuaHeaderSize, pkt->end(),
-			          std::back_inserter(fua_buffer));
+		auto nalUnitHeader = NalUnitHeader{std::to_integer<uint8_t>(pkt->at(rtpHeaderSize))};
 
-			if (nalUnitFragmentHeader.isEnd()) {
-				fua_buffer.at(0) =
-				    std::byte(nalUnitHeader.idc() | nalUnitFragmentHeader.unitType());
+		if (nalUnitHeader.unitType() == naluTypeFUA) {
+			auto nalUnitFragmentHeader = NalUnitFragmentHeader{
+			    std::to_integer<uint8_t>(pkt->at(rtpHeaderSize + sizeof(NalUnitHeader)))};
 
-				out.push_back(
-				    make_message(std::move(fua_buffer), Message::Binary, 0, nullptr, frameInfo));
-				fua_buffer.clear();
+			if (nFrags++ == 0) {
+				addSeparator(accessUnit);
+				accessUnit.emplace_back(
+				    byte(nalUnitHeader.idc() | nalUnitFragmentHeader.unitType()));
 			}
+
+			accessUnit.insert(accessUnit.end(),
+			                  pkt->begin() + rtpHeaderSize + sizeof(NalUnitHeader) +
+			                      sizeof(NalUnitFragmentHeader),
+			                  pkt->end());
 		} else if (nalUnitHeader.unitType() > 0 && nalUnitHeader.unitType() < 24) {
-			out.push_back(make_message(pkt->begin() + headerSize, pkt->end(), Message::Binary, 0,
-			                           nullptr, frameInfo));
+			addSeparator(accessUnit);
+			accessUnit.insert(accessUnit.end(), pkt->begin() + rtpHeaderSize, pkt->end());
 		} else if (nalUnitHeader.unitType() == naluTypeSTAPA) {
-			auto currOffset = stapaHeaderSize + headerSize;
+			auto currOffset = rtpHeaderSize + sizeof(NalUnitHeader);
 
-			while (currOffset < pkt->size()) {
-				auto naluSize =
-				    uint16_t(pkt->at(currOffset)) << 8 | uint8_t(pkt->at(currOffset + 1));
+			while (currOffset + sizeof(uint16_t) < pkt->size()) {
+				auto naluSize = std::to_integer<uint16_t>(pkt->at(currOffset)) << 8 |
+				                std::to_integer<uint16_t>(pkt->at(currOffset + 1));
 
-				currOffset += 2;
+				currOffset += sizeof(uint16_t);
 
 				if (pkt->size() < currOffset + naluSize) {
-					throw std::runtime_error("STAP-A declared size is larger then buffer");
+					throw std::runtime_error("H264 STAP-A declared size is larger than buffer");
 				}
 
-				out.push_back(make_message(pkt->begin() + currOffset,
-				                           pkt->begin() + currOffset + naluSize, Message::Binary, 0,
-				                           nullptr, frameInfo));
+				addSeparator(accessUnit);
+				accessUnit.insert(accessUnit.end(), pkt->begin() + currOffset,
+				                  pkt->begin() + currOffset + naluSize);
+
 				currOffset += naluSize;
 			}
 		} else {
@@ -89,6 +104,11 @@ message_vector H264RtpDepacketizer::buildFrames(message_vector::iterator begin,
 		}
 	}
 
+	if (!accessUnit.empty()) {
+		out.emplace_back(
+		    make_message(std::move(accessUnit), Message::Binary, 0, nullptr, frameInfo));
+	}
+
 	return out;
 }
 
@@ -111,6 +131,7 @@ void H264RtpDepacketizer::incoming(message_vector &messages, const message_callb
 	               messages.end());
 
 	while (mRtpBuffer.size() != 0) {
+		uint8_t payload_type = 0;
 		uint32_t current_timestamp = 0;
 		size_t packets_in_timestamp = 0;
 
@@ -119,6 +140,8 @@ void H264RtpDepacketizer::incoming(message_vector &messages, const message_callb
 
 			if (current_timestamp == 0) {
 				current_timestamp = p->timestamp();
+				payload_type =
+				    p->payloadType(); // should all be the same for data of the same codec
 			} else if (current_timestamp != p->timestamp()) {
 				break;
 			}
@@ -133,7 +156,7 @@ void H264RtpDepacketizer::incoming(message_vector &messages, const message_callb
 		auto begin = mRtpBuffer.begin();
 		auto end = mRtpBuffer.begin() + (packets_in_timestamp - 1);
 
-		auto frames = buildFrames(begin, end + 1, current_timestamp);
+		auto frames = buildFrames(begin, end + 1, payload_type, current_timestamp);
 		messages.insert(messages.end(), frames.begin(), frames.end());
 		mRtpBuffer.erase(mRtpBuffer.begin(), mRtpBuffer.begin() + packets_in_timestamp);
 	}

+ 3 - 2
src/h264rtppacketizer.cpp

@@ -32,8 +32,9 @@ shared_ptr<NalUnits> H264RtpPacketizer::splitMessage(binary_ptr message) {
 				LOG_WARNING << "Invalid NAL Unit data (incomplete length), ignoring!";
 				break;
 			}
-			auto lengthPtr = (uint32_t *)(message->data() + index);
-			uint32_t length = ntohl(*lengthPtr);
+			uint32_t length;
+			std::memcpy(&length, message->data() + index, sizeof(uint32_t));
+			length = ntohl(length);
 			auto naluStartIndex = index + 4;
 			auto naluEndIndex = naluStartIndex + length;
 

+ 3 - 2
src/h265rtppacketizer.cpp

@@ -32,8 +32,9 @@ shared_ptr<H265NalUnits> H265RtpPacketizer::splitMessage(binary_ptr message) {
 				LOG_WARNING << "Invalid NAL Unit data (incomplete length), ignoring!";
 				break;
 			}
-			auto lengthPtr = (uint32_t *)(message->data() + index);
-			uint32_t length = ntohl(*lengthPtr);
+			uint32_t length;
+			std::memcpy(&length, message->data() + index, sizeof(uint32_t));
+			length = ntohl(length);
 			auto naluStartIndex = index + 4;
 			auto naluEndIndex = naluStartIndex + length;
 

+ 103 - 76
src/impl/icetransport.cpp

@@ -117,27 +117,6 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 		}
 	}
 
-	juice_turn_server_t turn_servers[MAX_TURN_SERVERS_COUNT];
-	std::memset(turn_servers, 0, sizeof(turn_servers));
-
-	// Add TURN servers
-	int k = 0;
-	for (auto &server : servers) {
-		if (!server.hostname.empty() && server.type == IceServer::Type::Turn) {
-			if (server.port == 0)
-				server.port = 3478; // TURN UDP port
-			PLOG_INFO << "Using TURN server \"" << server.hostname << ":" << server.port << "\"";
-			turn_servers[k].host = server.hostname.c_str();
-			turn_servers[k].username = server.username.c_str();
-			turn_servers[k].password = server.password.c_str();
-			turn_servers[k].port = server.port;
-			if (++k >= MAX_TURN_SERVERS_COUNT)
-				break;
-		}
-	}
-	jconfig.turn_servers = k > 0 ? turn_servers : nullptr;
-	jconfig.turn_servers_count = k;
-
 	// Bind address
 	if (config.bindAddress) {
 		jconfig.bind_address = config.bindAddress->c_str();
@@ -154,6 +133,39 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 	mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy);
 	if (!mAgent)
 		throw std::runtime_error("Failed to create the ICE agent");
+
+	// Add TURN servers
+	for (const auto &server : servers)
+		if (!server.hostname.empty() && server.type != IceServer::Type::Stun)
+			addIceServer(server);
+}
+
+void IceTransport::addIceServer(IceServer server) {
+	if (server.hostname.empty())
+		return;
+
+	if (server.type != IceServer::Type::Turn) {
+		PLOG_WARNING << "Only TURN servers are supported as additional ICE servers";
+		return;
+	}
+
+	if (mTurnServersAdded >= MAX_TURN_SERVERS_COUNT)
+		return;
+
+	if (server.port == 0)
+		server.port = 3478; // TURN UDP port
+
+	PLOG_INFO << "Using TURN server \"" << server.hostname << ":" << server.port << "\"";
+	juice_turn_server_t turn_server = {};
+	turn_server.host = server.hostname.c_str();
+	turn_server.username = server.username.c_str();
+	turn_server.password = server.password.c_str();
+	turn_server.port = server.port;
+
+	if (juice_add_turn_server(mAgent.get(), &turn_server) != 0)
+		throw std::runtime_error("Failed to add TURN server");
+
+	++mTurnServersAdded;
 }
 
 IceTransport::~IceTransport() {
@@ -210,9 +222,13 @@ bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
 	return juice_add_remote_candidate(mAgent.get(), string(candidate).c_str()) >= 0;
 }
 
-void IceTransport::gatherLocalCandidates(string mid) {
+void IceTransport::gatherLocalCandidates(string mid, std::vector<IceServer> additionalIceServers) {
 	mMid = std::move(mid);
 
+	std::shuffle(additionalIceServers.begin(), additionalIceServers.end(), utils::random_engine());
+	for (const auto &server : additionalIceServers)
+		addIceServer(server);
+
 	// Change state now as candidates calls can be synchronous
 	changeGatheringState(GatheringState::InProgress);
 
@@ -534,59 +550,9 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 	}
 
 	// Add TURN servers
-	for (auto &server : servers) {
-		if (server.hostname.empty())
-			continue;
-		if (server.type != IceServer::Type::Turn)
-			continue;
-		if (server.port == 0)
-			server.port = server.relayType == IceServer::RelayType::TurnTls ? 5349 : 3478;
-
-		struct addrinfo hints = {};
-		hints.ai_family = AF_UNSPEC;
-		hints.ai_socktype =
-		    server.relayType == IceServer::RelayType::TurnUdp ? SOCK_DGRAM : SOCK_STREAM;
-		hints.ai_protocol =
-		    server.relayType == IceServer::RelayType::TurnUdp ? IPPROTO_UDP : IPPROTO_TCP;
-		hints.ai_flags = AI_ADDRCONFIG;
-		struct addrinfo *result = nullptr;
-		if (getaddrinfo(server.hostname.c_str(), std::to_string(server.port).c_str(), &hints,
-		                &result) != 0) {
-			PLOG_WARNING << "Unable to resolve TURN server address: " << server.hostname << ':'
-			             << server.port;
-			continue;
-		}
-
-		for (auto p = result; p; p = p->ai_next) {
-			if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
-				char nodebuffer[MAX_NUMERICNODE_LEN];
-				char servbuffer[MAX_NUMERICSERV_LEN];
-				if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
-				                servbuffer, MAX_NUMERICSERV_LEN,
-				                NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
-					PLOG_INFO << "Using TURN server \"" << server.hostname << ":" << server.port
-					          << "\"";
-					NiceRelayType niceRelayType;
-					switch (server.relayType) {
-					case IceServer::RelayType::TurnTcp:
-						niceRelayType = NICE_RELAY_TYPE_TURN_TCP;
-						break;
-					case IceServer::RelayType::TurnTls:
-						niceRelayType = NICE_RELAY_TYPE_TURN_TLS;
-						break;
-					default:
-						niceRelayType = NICE_RELAY_TYPE_TURN_UDP;
-						break;
-					}
-					nice_agent_set_relay_info(mNiceAgent.get(), mStreamId, 1, nodebuffer,
-					                          std::stoul(servbuffer), server.username.c_str(),
-					                          server.password.c_str(), niceRelayType);
-				}
-			}
-		}
-
-		freeaddrinfo(result);
-	}
+	for (const auto &server : servers)
+		if (!server.hostname.empty() && server.type != IceServer::Type::Stun)
+			addIceServer(server);
 
 	g_signal_connect(G_OBJECT(mNiceAgent.get()), "component-state-changed",
 	                 G_CALLBACK(StateChangeCallback), this);
@@ -603,6 +569,63 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 	                       RecvCallback, this);
 }
 
+void IceTransport::addIceServer(IceServer server) {
+	if (server.hostname.empty())
+		return;
+
+	if (server.type != IceServer::Type::Turn) {
+		PLOG_WARNING << "Only TURN servers are supported as additional ICE servers";
+		return;
+	}
+
+	if (server.port == 0)
+		server.port = server.relayType == IceServer::RelayType::TurnTls ? 5349 : 3478;
+
+	struct addrinfo hints = {};
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype =
+	    server.relayType == IceServer::RelayType::TurnUdp ? SOCK_DGRAM : SOCK_STREAM;
+	hints.ai_protocol =
+	    server.relayType == IceServer::RelayType::TurnUdp ? IPPROTO_UDP : IPPROTO_TCP;
+	hints.ai_flags = AI_ADDRCONFIG;
+	struct addrinfo *result = nullptr;
+	if (getaddrinfo(server.hostname.c_str(), std::to_string(server.port).c_str(), &hints,
+	                &result) != 0) {
+		PLOG_WARNING << "Unable to resolve TURN server address: " << server.hostname << ':'
+		             << server.port;
+		return;
+	}
+
+	for (auto p = result; p; p = p->ai_next) {
+		if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
+			char nodebuffer[MAX_NUMERICNODE_LEN];
+			char servbuffer[MAX_NUMERICSERV_LEN];
+			if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN, servbuffer,
+			                MAX_NUMERICSERV_LEN, NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
+				PLOG_INFO << "Using TURN server \"" << server.hostname << ":" << server.port
+				          << "\"";
+				NiceRelayType niceRelayType;
+				switch (server.relayType) {
+				case IceServer::RelayType::TurnTcp:
+					niceRelayType = NICE_RELAY_TYPE_TURN_TCP;
+					break;
+				case IceServer::RelayType::TurnTls:
+					niceRelayType = NICE_RELAY_TYPE_TURN_TLS;
+					break;
+				default:
+					niceRelayType = NICE_RELAY_TYPE_TURN_UDP;
+					break;
+				}
+				nice_agent_set_relay_info(mNiceAgent.get(), mStreamId, 1, nodebuffer,
+				                          std::stoul(servbuffer), server.username.c_str(),
+				                          server.password.c_str(), niceRelayType);
+			}
+		}
+	}
+
+	freeaddrinfo(result);
+}
+
 IceTransport::~IceTransport() {
 	PLOG_DEBUG << "Destroying ICE transport";
 	nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(MainLoop.get()),
@@ -684,9 +707,13 @@ bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
 	return ret > 0;
 }
 
-void IceTransport::gatherLocalCandidates(string mid) {
+void IceTransport::gatherLocalCandidates(string mid, std::vector<IceServer> additionalIceServers) {
 	mMid = std::move(mid);
 
+	std::shuffle(additionalIceServers.begin(), additionalIceServers.end(), utils::random_engine());
+	for (const auto &server : additionalIceServers)
+		addIceServer(server);
+
 	// Change state now as candidates calls can be synchronous
 	changeGatheringState(GatheringState::InProgress);
 

+ 4 - 1
src/impl/icetransport.hpp

@@ -50,7 +50,7 @@ public:
 	Description getLocalDescription(Description::Type type) const;
 	void setRemoteDescription(const Description &description);
 	bool addRemoteCandidate(const Candidate &candidate);
-	void gatherLocalCandidates(string mid);
+	void gatherLocalCandidates(string mid, std::vector<IceServer> additionalIceServers = {});
 
 	optional<string> getLocalAddress() const;
 	optional<string> getRemoteAddress() const;
@@ -69,6 +69,8 @@ private:
 	void processGatheringDone();
 	void processTimeout();
 
+	void addIceServer(IceServer server);
+
 	Description::Role mRole;
 	string mMid;
 	std::chrono::milliseconds mTrickleTimeout;
@@ -79,6 +81,7 @@ private:
 
 #if !USE_NICE
 	unique_ptr<juice_agent_t, void (*)(juice_agent_t *)> mAgent;
+	int mTurnServersAdded = 0;
 
 	static void StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr);
 	static void CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr);

+ 1 - 1
src/impl/tls.cpp

@@ -101,7 +101,7 @@ bool check(int ret, const string &message) {
 	if (ret < 0) {
 		if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE ||
 		    ret == MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS || ret == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS ||
-		    ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)
+		    ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY || ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
 			return false;
 
 		const size_t bufferSize = 1024;

+ 1 - 0
src/impl/tlstransport.cpp

@@ -323,6 +323,7 @@ TlsTransport::TlsTransport(variant<shared_ptr<TcpTransport>, shared_ptr<HttpProx
 
 	PLOG_DEBUG << "Initializing TLS transport (MbedTLS)";
 
+	psa_crypto_init();
 	mbedtls_entropy_init(&mEntropy);
 	mbedtls_ctr_drbg_init(&mDrbg);
 	mbedtls_ssl_init(&mSsl);

+ 10 - 2
src/impl/track.cpp

@@ -142,7 +142,11 @@ void Track::incoming(message_ptr message) {
 
 	message_vector messages{std::move(message)};
 	if (auto handler = getMediaHandler())
-		handler->incomingChain(messages, [this](message_ptr m) { transportSend(m); });
+		handler->incomingChain(messages, [this, weak_this = weak_from_this()](message_ptr m) {
+			if (auto locked = weak_this.lock()) {
+				transportSend(m);
+			}
+		});
 
 	for (auto &m : messages) {
 		// Tail drop if queue is full
@@ -175,7 +179,11 @@ bool Track::outgoing(message_ptr message) {
 
 	if (handler) {
 		message_vector messages{std::move(message)};
-		handler->outgoingChain(messages, [this](message_ptr m) { transportSend(m); });
+		handler->outgoingChain(messages, [this, weak_this = weak_from_this()](message_ptr m) {
+			if (auto locked = weak_this.lock()) {
+				transportSend(m);
+			}
+		});
 		bool ret = false;
 		for (auto &m : messages)
 			ret = transportSend(std::move(m));

+ 1 - 1
src/impl/verifiedtlstransport.cpp

@@ -36,7 +36,7 @@ VerifiedTlsTransport::VerifiedTlsTransport(
 				// *cacert is a PEM content
 				mbedtls::check(mbedtls_x509_crt_parse(
 				    &mCaCert, reinterpret_cast<const unsigned char *>(cacert->c_str()),
-				    cacert->size()));
+				    cacert->size() + 1));
 			}
 			mbedtls_ssl_conf_ca_chain(&mConf, &mCaCert, NULL);
 		}

+ 72 - 0
src/pacinghandler.cpp

@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2024 Sean DuBois <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#if RTC_ENABLE_MEDIA
+
+#include <memory>
+
+#include "pacinghandler.hpp"
+
+#include "impl/internals.hpp"
+#include "impl/threadpool.hpp"
+
+namespace rtc {
+
+PacingHandler::PacingHandler(double bitsPerSecond, std::chrono::milliseconds sendInterval)
+    : mBytesPerSecond(bitsPerSecond / 8), mBudget(0.), mSendInterval(sendInterval){};
+
+void PacingHandler::schedule(const message_callback &send) {
+	if (!mHaveScheduled.exchange(true)) {
+		return;
+	}
+
+	impl::ThreadPool::Instance().schedule(mSendInterval, [this, weak_this = weak_from_this(),
+	                                                      send]() {
+		if (auto locked = weak_this.lock()) {
+			const std::lock_guard<std::mutex> lock(mMutex);
+			mHaveScheduled.store(false);
+
+			// Update the budget and cap it
+			auto newBudget =
+			    std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - mLastRun)
+			        .count() *
+			    mBytesPerSecond;
+			auto maxBudget = std::chrono::duration<double>(mSendInterval).count() * mBytesPerSecond;
+			mBudget = std::min(mBudget + newBudget, maxBudget);
+			mLastRun = std::chrono::high_resolution_clock::now();
+
+			// Send packets while there is budget, allow a single partial packet over budget
+			while (!mRtpBuffer.empty() && mBudget > 0) {
+				auto size = int(mRtpBuffer.front()->size());
+				send(std::move(mRtpBuffer.front()));
+				mRtpBuffer.pop();
+				mBudget -= size;
+			}
+
+			if (!mRtpBuffer.empty()) {
+				schedule(send);
+			}
+		}
+	});
+}
+
+void PacingHandler::outgoing(message_vector &messages, const message_callback &send) {
+
+	std::lock_guard<std::mutex> lock(mMutex);
+
+	for (auto &m : messages) {
+		mRtpBuffer.push(std::move(m));
+	}
+	messages.clear();
+
+	schedule(send);
+}
+
+} // namespace rtc
+
+#endif /* RTC_ENABLE_MEDIA */

+ 14 - 1
src/peerconnection.cpp

@@ -146,11 +146,24 @@ void PeerConnection::setLocalDescription(Description::Type type) {
 	impl()->changeSignalingState(newSignalingState);
 	signalingLock.unlock();
 
-	if (impl()->gatheringState == GatheringState::New) {
+	if (impl()->gatheringState == GatheringState::New && !impl()->config.disableAutoGathering) {
 		iceTransport->gatherLocalCandidates(impl()->localBundleMid());
 	}
 }
 
+void PeerConnection::gatherLocalCandidates(std::vector<IceServer> additionalIceServers) {
+	auto iceTransport = impl()->getIceTransport();
+	if (!iceTransport) {
+		throw std::logic_error("No IceTransport. Local Description has not been set");
+	}
+
+	if (impl()->gatheringState == GatheringState::New) {
+		iceTransport->gatherLocalCandidates(impl()->localBundleMid(), additionalIceServers);
+	} else {
+		PLOG_WARNING << "Candidates gathering already started";
+	}
+}
+
 void PeerConnection::setRemoteDescription(Description description) {
 	std::unique_lock signalingLock(impl()->signalingMutex);
 	PLOG_VERBOSE << "Setting remote description: " << string(description);

+ 1 - 1
src/rtpdepacketizer.cpp

@@ -36,7 +36,7 @@ void RtpDepacketizer::incoming([[maybe_unused]] message_vector &messages,
 		auto headerSize = sizeof(rtc::RtpHeader) + pkt->csrcCount() + pkt->getExtensionHeaderSize();
 		result.push_back(make_message(message->begin() + headerSize, message->end(),
 		                              Message::Binary, 0, nullptr,
-		                              std::make_shared<FrameInfo>(pkt->timestamp())));
+		                              std::make_shared<FrameInfo>(pkt->payloadType(), pkt->timestamp())));
 	}
 
 	messages.swap(result);

+ 20 - 0
src/rtppacketizer.cpp

@@ -47,6 +47,11 @@ message_ptr RtpPacketizer::packetize(shared_ptr<binary> payload, bool mark) {
 	if (setVideoRotation)
 		rtpExtHeaderSize += headerSize + 1;
 
+	const bool setPlayoutDelay = (rtpConfig->playoutDelayId > 0 && rtpConfig->playoutDelayId < 15);
+
+	if (setPlayoutDelay)
+		rtpExtHeaderSize += 4;
+
 	if (rtpConfig->mid.has_value())
 		rtpExtHeaderSize += headerSize + rtpConfig->mid->length();
 
@@ -116,6 +121,21 @@ message_ptr RtpPacketizer::packetize(shared_ptr<binary> payload, bool mark) {
 			offset += extHeader->writeHeader(
 			    twoByteHeader, offset, rtpConfig->dependencyDescriptorId, buf.get(), sizeBytes);
 		}
+
+		if (setPlayoutDelay) {
+			uint16_t min = rtpConfig->playoutDelayMin & 0xFFF;
+			uint16_t max = rtpConfig->playoutDelayMax & 0xFFF;
+
+			// 12 bits for min + 12 bits for max
+			byte data[] = {
+				byte((min >> 4) & 0xFF), 
+				byte(((min & 0xF) << 4) | ((max >> 8) & 0xF)),
+				byte(max & 0xFF)
+			};
+
+			extHeader->writeOneByteHeader(offset, rtpConfig->playoutDelayId, data, 3);
+			offset += 4;
+		}
 	}
 
 	rtp->preparePacket();