Browse Source

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

melpon 4 months ago
parent
commit
a81c918d39
80 changed files with 1864 additions and 795 deletions
  1. 3 1
      .github/workflows/build-nomedia.yml
  2. 5 3
      .github/workflows/build-openssl.yml
  3. 1 1
      .github/workflows/check-version.yml
  4. 1 1
      BUILDING.md
  5. 89 47
      CMakeLists.txt
  6. 34 3
      DOC.md
  7. 2 2
      Jamfile
  8. 2 2
      README.md
  9. 2 2
      cmake/Modules/FindLibJuice.cmake
  10. 1 1
      deps/libjuice
  11. 4 17
      examples/streamer/main.cpp
  12. 11 10
      include/rtc/av1rtppacketizer.hpp
  13. 0 1
      include/rtc/common.hpp
  14. 5 4
      include/rtc/description.hpp
  15. 11 3
      include/rtc/frameinfo.hpp
  16. 2 0
      include/rtc/h264rtpdepacketizer.hpp
  17. 8 9
      include/rtc/h264rtppacketizer.hpp
  18. 12 4
      include/rtc/h265nalunit.hpp
  19. 51 0
      include/rtc/h265rtpdepacketizer.hpp
  20. 9 10
      include/rtc/h265rtppacketizer.hpp
  21. 47 0
      include/rtc/iceudpmuxlistener.hpp
  22. 20 4
      include/rtc/message.hpp
  23. 27 57
      include/rtc/nalunit.hpp
  24. 13 2
      include/rtc/peerconnection.hpp
  25. 35 0
      include/rtc/rembhandler.hpp
  26. 14 4
      include/rtc/rtc.h
  27. 3 0
      include/rtc/rtc.hpp
  28. 4 4
      include/rtc/rtcpnackresponder.hpp
  29. 7 4
      include/rtc/rtcpsrreporter.hpp
  30. 2 0
      include/rtc/rtp.hpp
  31. 6 2
      include/rtc/rtpdepacketizer.hpp
  32. 3 3
      include/rtc/rtppacketizationconfig.hpp
  33. 20 4
      include/rtc/rtppacketizer.hpp
  34. 3 1
      include/rtc/track.hpp
  35. 3 3
      include/rtc/version.h
  36. 34 3
      pages/content/pages/reference.md
  37. 70 91
      src/av1rtppacketizer.cpp
  38. 45 13
      src/capi.cpp
  39. 30 26
      src/description.cpp
  40. 1 1
      src/global.cpp
  41. 7 5
      src/h264rtpdepacketizer.cpp
  42. 35 49
      src/h264rtppacketizer.cpp
  43. 60 34
      src/h265nalunit.cpp
  44. 194 0
      src/h265rtpdepacketizer.cpp
  45. 36 50
      src/h265rtppacketizer.cpp
  46. 29 0
      src/iceudpmuxlistener.cpp
  47. 32 8
      src/impl/certificate.cpp
  48. 3 1
      src/impl/certificate.hpp
  49. 4 1
      src/impl/datachannel.cpp
  50. 8 4
      src/impl/dtlstransport.cpp
  51. 1 1
      src/impl/dtlstransport.hpp
  52. 42 13
      src/impl/icetransport.cpp
  53. 12 2
      src/impl/icetransport.hpp
  54. 62 0
      src/impl/iceudpmuxlistener.cpp
  55. 45 0
      src/impl/iceudpmuxlistener.hpp
  56. 206 120
      src/impl/peerconnection.cpp
  57. 17 8
      src/impl/peerconnection.hpp
  58. 4 3
      src/impl/pollservice.cpp
  59. 1 0
      src/impl/queue.hpp
  60. 1 1
      src/impl/sctptransport.cpp
  61. 1 1
      src/impl/sctptransport.hpp
  62. 17 14
      src/impl/tcptransport.cpp
  63. 5 1
      src/impl/tls.cpp
  64. 3 0
      src/impl/tlstransport.cpp
  65. 1 1
      src/impl/tlstransport.hpp
  66. 16 14
      src/impl/track.cpp
  67. 3 2
      src/impl/track.hpp
  68. 6 3
      src/impl/websocket.cpp
  69. 13 9
      src/impl/wshandshake.cpp
  70. 5 2
      src/message.cpp
  71. 103 33
      src/nalunit.cpp
  72. 72 27
      src/peerconnection.cpp
  73. 47 0
      src/rembhandler.cpp
  74. 8 9
      src/rtcpnackresponder.cpp
  75. 27 15
      src/rtcpsrreporter.cpp
  76. 9 1
      src/rtp.cpp
  77. 13 3
      src/rtpdepacketizer.cpp
  78. 45 13
      src/rtppacketizer.cpp
  79. 13 4
      src/track.cpp
  80. 18 0
      test/capi_track.cpp

+ 3 - 1
.github/workflows/build-nomedia.yml

@@ -36,5 +36,7 @@ jobs:
         set CL=/MP
         set CL=/MP
         nmake
         nmake
     - name: test
     - name: test
-      run: build/tests.exe
+      run: |
+        cd build
+        ./tests
 
 

+ 5 - 3
.github/workflows/build-openssl.yml

@@ -24,13 +24,13 @@ jobs:
     steps:
     steps:
     - uses: actions/checkout@v2
     - uses: actions/checkout@v2
     - name: install packages
     - name: install packages
-      run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew reinstall openssl@1.1
+      run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew reinstall openssl@3
     - name: submodules
     - name: submodules
       run: git submodule update --init --recursive --depth 1
       run: git submodule update --init --recursive --depth 1
     - name: cmake
     - name: cmake
       run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
       run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
       env:
       env:
-        OPENSSL_ROOT_DIR: /usr/local/opt/openssl@1.1
+        OPENSSL_ROOT_DIR: /usr/local/opt/openssl@3
     - name: make
     - name: make
       run: (cd build; make -j2)
       run: (cd build; make -j2)
     - name: test
     - name: test
@@ -52,5 +52,7 @@ jobs:
         set CL=/MP
         set CL=/MP
         nmake
         nmake
     - name: test
     - name: test
-      run: build/tests.exe
+      run: |
+        cd build
+        ./tests
 
 

+ 1 - 1
.github/workflows/check-version.yml

@@ -14,7 +14,7 @@ jobs:
     - name: submodules
     - name: submodules
       run: git submodule update --init --recursive --depth 1
       run: git submodule update --init --recursive --depth 1
     - name: cmake
     - name: cmake
-      run: cmake -B build -DUSE_GNUTLS=0 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
+      run: cmake -B build -DUSE_GNUTLS=0 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1 -DRTC_UPDATE_VERSION_HEADER=1
     - name: check diff
     - name: check diff
       run: |
       run: |
         if ! git diff --exit-code
         if ! git diff --exit-code

+ 1 - 1
BUILDING.md

@@ -70,7 +70,7 @@ $ make -j2
 ### Microsoft Windows with Microsoft Visual C++
 ### Microsoft Windows with Microsoft Visual C++
 
 
 ```bash
 ```bash
-$ cmake -B build -G "NMake Makefiles"
+$ cmake -B build -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
 $ cd build
 $ cd build
 $ nmake
 $ nmake
 ```
 ```

+ 89 - 47
CMakeLists.txt

@@ -1,16 +1,18 @@
-cmake_minimum_required(VERSION 3.7)
+cmake_minimum_required(VERSION 3.13)
 project(libdatachannel
 project(libdatachannel
-	VERSION 0.21.1
+	VERSION 0.22.6
 	LANGUAGES CXX)
 	LANGUAGES CXX)
 set(PROJECT_DESCRIPTION "C/C++ WebRTC network library featuring Data Channels, Media Transport, and WebSockets")
 set(PROJECT_DESCRIPTION "C/C++ WebRTC network library featuring Data Channels, Media Transport, and WebSockets")
 
 
 include(GNUInstallDirs)
 include(GNUInstallDirs)
 
 
 # Options
 # Options
+option(BUILD_SHARED_LIBS "Build shared library" ON)
+option(BUILD_SHARED_DEPS_LIBS "Build submodules as shared libraries" OFF)
 option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
 option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
 option(USE_MBEDTLS "Use Mbed TLS instead of OpenSSL" OFF)
 option(USE_MBEDTLS "Use Mbed TLS instead of OpenSSL" OFF)
 option(USE_NICE "Use libnice instead of libjuice" OFF)
 option(USE_NICE "Use libnice instead of libjuice" OFF)
-option(PREFER_SYSTEM_LIB "Prefer system libraries over deps folder" OFF)
+option(PREFER_SYSTEM_LIB "Prefer system libraries over submodules" OFF)
 option(USE_SYSTEM_SRTP "Use system libSRTP" ${PREFER_SYSTEM_LIB})
 option(USE_SYSTEM_SRTP "Use system libSRTP" ${PREFER_SYSTEM_LIB})
 option(USE_SYSTEM_JUICE "Use system libjuice" ${PREFER_SYSTEM_LIB})
 option(USE_SYSTEM_JUICE "Use system libjuice" ${PREFER_SYSTEM_LIB})
 option(USE_SYSTEM_USRSCTP "Use system libusrsctp" ${PREFER_SYSTEM_LIB})
 option(USE_SYSTEM_USRSCTP "Use system libusrsctp" ${PREFER_SYSTEM_LIB})
@@ -23,6 +25,7 @@ option(NO_TESTS "Disable tests build" OFF)
 option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
 option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
 option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
 option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
 option(SCTP_DEBUG "Enable SCTP debugging output to verbose log" OFF)
 option(SCTP_DEBUG "Enable SCTP debugging output to verbose log" OFF)
+option(RTC_UPDATE_VERSION_HEADER "Enable updating the version header" OFF)
 
 
 if (USE_GNUTLS AND USE_MBEDTLS)
 if (USE_GNUTLS AND USE_MBEDTLS)
 	message(FATAL_ERROR "Both USE_MBEDTLS and USE_GNUTLS cannot be enabled at the same time")
 	message(FATAL_ERROR "Both USE_MBEDTLS and USE_GNUTLS cannot be enabled at the same time")
@@ -48,7 +51,6 @@ endif()
 
 
 list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
 list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)
-set(BUILD_SHARED_LIBS OFF) # to force usrsctp to be built static
 
 
 if(WIN32)
 if(WIN32)
 	add_definitions(-DWIN32_LEAN_AND_MEAN)
 	add_definitions(-DWIN32_LEAN_AND_MEAN)
@@ -65,6 +67,7 @@ set(LIBDATACHANNEL_SOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/src/datachannel.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/datachannel.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/dependencydescriptor.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/dependencydescriptor.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/iceudpmuxlistener.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/mediahandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/mediahandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/global.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/global.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/message.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/message.cpp
@@ -81,6 +84,7 @@ set(LIBDATACHANNEL_SOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtpdepacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtpdepacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h265rtppacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h265rtppacketizer.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/h265rtpdepacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h265nalunit.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h265nalunit.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/av1rtppacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/av1rtppacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpnackresponder.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpnackresponder.cpp
@@ -88,6 +92,7 @@ set(LIBDATACHANNEL_SOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/plihandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/plihandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/pacinghandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/pacinghandler.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/rembhandler.cpp
 )
 )
 
 
 set(LIBDATACHANNEL_HEADERS
 set(LIBDATACHANNEL_HEADERS
@@ -97,6 +102,7 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/dependencydescriptor.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/dependencydescriptor.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/iceudpmuxlistener.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpreceivingsession.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpreceivingsession.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/common.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/common.hpp
@@ -119,12 +125,14 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtpdepacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtpdepacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265rtppacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265rtppacketizer.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265rtpdepacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265nalunit.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265nalunit.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/av1rtppacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/av1rtppacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpnackresponder.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpnackresponder.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/utils.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/utils.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/plihandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/plihandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/pacinghandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/pacinghandler.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rembhandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/version.h
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/version.h
 )
 )
 
 
@@ -135,6 +143,7 @@ set(LIBDATACHANNEL_IMPL_SOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/dtlssrtptransport.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/dtlssrtptransport.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/dtlstransport.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/dtlstransport.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/icetransport.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/icetransport.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/iceudpmuxlistener.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/init.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/init.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/peerconnection.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/peerconnection.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/logcounter.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/logcounter.cpp
@@ -167,6 +176,7 @@ set(LIBDATACHANNEL_IMPL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/dtlssrtptransport.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/dtlssrtptransport.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/dtlstransport.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/dtlstransport.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/icetransport.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/icetransport.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/iceudpmuxlistener.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/init.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/init.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/internals.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/internals.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/peerconnection.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/impl/peerconnection.hpp
@@ -229,6 +239,52 @@ set(BENCHMARK_UWP_RESOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/Windows_TemporaryKey.pfx
 	${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/Windows_TemporaryKey.pfx
 )
 )
 
 
+if(RTC_UPDATE_VERSION_HEADER)
+	configure_file (
+		${PROJECT_SOURCE_DIR}/cmake/version.h.in
+		${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/version.h
+	)
+endif()
+
+add_library(datachannel
+	${LIBDATACHANNEL_SOURCES}
+	${LIBDATACHANNEL_HEADERS}
+	${LIBDATACHANNEL_IMPL_SOURCES}
+	${LIBDATACHANNEL_IMPL_HEADERS})
+set_target_properties(datachannel PROPERTIES
+	VERSION ${PROJECT_VERSION}
+	SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
+	CXX_STANDARD 17
+	CXX_VISIBILITY_PRESET default)
+if(APPLE)
+	set_target_properties(datachannel PROPERTIES
+		VERSION ${PROJECT_VERSION_MAJOR}
+		SOVERSION ${PROJECT_VERSION_MAJOR})
+endif()
+
+add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
+	${LIBDATACHANNEL_SOURCES}
+	${LIBDATACHANNEL_HEADERS}
+	${LIBDATACHANNEL_IMPL_SOURCES}
+	${LIBDATACHANNEL_IMPL_HEADERS})
+set_target_properties(datachannel-static PROPERTIES
+	VERSION ${PROJECT_VERSION}
+	CXX_STANDARD 17)
+
+target_compile_definitions(datachannel PRIVATE RTC_EXPORTS)
+if (NOT BUILD_SHARED_LIBS)
+	target_compile_definitions(datachannel PUBLIC RTC_STATIC)
+endif()
+target_compile_definitions(datachannel-static PRIVATE RTC_EXPORTS)
+target_compile_definitions(datachannel-static PUBLIC RTC_STATIC)
+
+if(BUILD_SHARED_LIBS AND NOT BUILD_SHARED_DEPS_LIBS)
+	set(BUILD_SHARED_LIBS OFF)
+	set(INSTALL_DEPS_LIBS OFF)
+else()
+	set(INSTALL_DEPS_LIBS ON)
+endif()
+
 set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
 set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
 set(THREADS_PREFER_PTHREAD_FLAG TRUE)
 set(THREADS_PREFER_PTHREAD_FLAG TRUE)
 find_package(Threads REQUIRED)
 find_package(Threads REQUIRED)
@@ -258,58 +314,38 @@ else()
 		target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
 		target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
 	endif()
 	endif()
 	add_library(Usrsctp::Usrsctp ALIAS usrsctp)
 	add_library(Usrsctp::Usrsctp ALIAS usrsctp)
-endif()
 
 
-configure_file (
-    ${PROJECT_SOURCE_DIR}/cmake/version.h.in
-	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/version.h
-)
-
-add_library(datachannel SHARED
-	${LIBDATACHANNEL_SOURCES}
-	${LIBDATACHANNEL_HEADERS}
-	${LIBDATACHANNEL_IMPL_SOURCES}
-	${LIBDATACHANNEL_IMPL_HEADERS})
-set_target_properties(datachannel PROPERTIES
-	VERSION ${PROJECT_VERSION}
-	SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
-	CXX_STANDARD 17
-	CXX_VISIBILITY_PRESET default)
-
-if(APPLE)
-	set_target_properties(datachannel PROPERTIES
-		VERSION ${PROJECT_VERSION_MAJOR}
-		SOVERSION ${PROJECT_VERSION_MAJOR})
+	if(INSTALL_DEPS_LIBS)
+		install(TARGETS usrsctp EXPORT LibDataChannelTargets)
+		# Fix directories
+		set_target_properties(usrsctp PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "")
+		target_include_directories(usrsctp INTERFACE
+			$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/deps/usrsctp/usrsctplib>
+			$<INSTALL_INTERFACE:>)
+	endif()
 endif()
 endif()
 
 
-target_compile_definitions(datachannel PRIVATE RTC_EXPORTS)
-
-add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
-	${LIBDATACHANNEL_SOURCES}
-	${LIBDATACHANNEL_HEADERS}
-	${LIBDATACHANNEL_IMPL_SOURCES}
-	${LIBDATACHANNEL_IMPL_HEADERS})
-set_target_properties(datachannel-static PROPERTIES
-	VERSION ${PROJECT_VERSION}
-	CXX_STANDARD 17)
-target_compile_definitions(datachannel-static PRIVATE RTC_EXPORTS)
-target_compile_definitions(datachannel-static PUBLIC RTC_STATIC)
-
 target_include_directories(datachannel PUBLIC
 target_include_directories(datachannel PUBLIC
     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
     $<INSTALL_INTERFACE:include>)
     $<INSTALL_INTERFACE:include>)
-target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
-target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
-target_link_libraries(datachannel PRIVATE Threads::Threads)
-target_link_libraries(datachannel PRIVATE Usrsctp::Usrsctp plog::plog)
+target_include_directories(datachannel PRIVATE
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc
+	${CMAKE_CURRENT_SOURCE_DIR}/src)
+target_link_libraries(datachannel PRIVATE
+	Threads::Threads
+	Usrsctp::Usrsctp
+	$<BUILD_INTERFACE:plog::plog>)
 
 
 target_include_directories(datachannel-static PUBLIC
 target_include_directories(datachannel-static PUBLIC
     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
     $<INSTALL_INTERFACE:include>)
     $<INSTALL_INTERFACE:include>)
-target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
-target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
-target_link_libraries(datachannel-static PRIVATE Threads::Threads)
-target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp plog::plog)
+target_include_directories(datachannel-static PRIVATE
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc
+	${CMAKE_CURRENT_SOURCE_DIR}/src)
+target_link_libraries(datachannel-static PRIVATE
+	Threads::Threads
+	Usrsctp::Usrsctp
+	$<BUILD_INTERFACE:plog::plog>)
 
 
 if(WIN32)
 if(WIN32)
 	target_link_libraries(datachannel PUBLIC ws2_32) # winsock2
 	target_link_libraries(datachannel PUBLIC ws2_32) # winsock2
@@ -346,6 +382,9 @@ else()
 	else()
 	else()
 		if(NOT TARGET srtp2)
 		if(NOT TARGET srtp2)
 			add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
 			add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
+			if(INSTALL_DEPS_LIBS)
+				install(TARGETS srtp2 EXPORT LibDataChannelTargets)
+			endif()
 		endif()
 		endif()
 		target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=0)
 		target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=0)
 		target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=0)
 		target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=0)
@@ -423,9 +462,12 @@ else()
 		target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuice)
 		target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuice)
 	else()
 	else()
 		add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
 		add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
+		if(INSTALL_DEPS_LIBS)
+			install(TARGETS juice EXPORT LibDataChannelTargets)
+		endif()
 		target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_JUICE=0)
 		target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_JUICE=0)
 		target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_JUICE=0)
 		target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_JUICE=0)
-		target_link_libraries(datachannel PRIVATE LibJuice::LibJuiceStatic)
+		target_link_libraries(datachannel PRIVATE LibJuice::LibJuice)
 		target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
 		target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
 	endif()
 	endif()
 endif()
 endif()

+ 34 - 3
DOC.md

@@ -207,7 +207,9 @@ Initiates the handshake process. Following this call, the local description call
 Arguments:
 Arguments:
 
 
 - `pc`: the Peer Connection identifier
 - `pc`: the Peer Connection identifier
-- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for automatic (recommended).
+
+Warning: This function expects the optional type for the local description and not an SDP description. It is not possible to set an existing SDP description.
 
 
 #### rtcSetRemoteDescription
 #### rtcSetRemoteDescription
 
 
@@ -220,7 +222,8 @@ Sets the remote description received from the remote peer by the user's method o
 Arguments:
 Arguments:
 
 
 - `pc`: the Peer Connection identifier
 - `pc`: the Peer Connection identifier
-- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+- `sdp`: the remote description in SDP format
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for automatic (not recommended).
 
 
 If the remote description is an offer and `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically answer by calling `rtcSetLocalDescription` internally. Otherwise, the user must call it to answer the remote description.
 If the remote description is an offer and `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically answer by calling `rtcSetLocalDescription` internally. Otherwise, the user must call it to answer the remote description.
 
 
@@ -296,7 +299,7 @@ Return value: the length of the string copied in buffer (including the terminati
 
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
-#### rtcGetRemoteDescription
+#### rtcGetRemoteDescriptionType
 
 
 ```
 ```
 int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
 int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
@@ -314,6 +317,22 @@ Return value: the length of the string copied in buffer (including the terminati
 
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
+#### rtcCreateOffer/rtcCreateAnswer
+
+```
+int rtcCreateOffer(int pc, char *buffer, int size)
+int rtcCreateAnswer(int pc, char *buffer, int size)
+```
+
+Create a local offer or answer description in SDP format. These functions are intended only for specific use cases where the application needs to generate a description without setting it. It is useless to call them before `rtcSetLocalDescription` as it doesn't expect the user to supply a description.
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the description
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
 #### rtcGetLocalAddress
 #### rtcGetLocalAddress
 
 
@@ -371,6 +390,18 @@ Return value: the maximun length of strings copied in buffers (including the ter
 
 
 If `local`, `remote`, or both, are `NULL`, the corresponding candidate is not copied, but the maximum length is still returned.
 If `local`, `remote`, or both, are `NULL`, the corresponding candidate is not copied, but the maximum length is still returned.
 
 
+#### rtcIsNegotiationNeeded
+```
+bool rtcIsNegotiationNeeded(int pc);
+```
+
+Return true if negotiation needs to be started or restarted, for instance to signal new tracks. If so, the user may call `rtcSetLocalDescription()` to start it.
+
+Arguments:
+- `pc`: the Peer Connection identifier
+
+Return value: true if negotiation is needed
+
 #### rtcGetMaxDataChannelStream
 #### rtcGetMaxDataChannelStream
 ```
 ```
 int rtcGetMaxDataChannelStream(int pc);
 int rtcGetMaxDataChannelStream(int pc);

+ 2 - 2
Jamfile

@@ -103,7 +103,7 @@ rule make_libusrsctp ( targets * : sources * : properties * )
 }
 }
 actions make_libusrsctp
 actions make_libusrsctp
 {
 {
-    (cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC -Wno-unknown-warning-option -Wno-format-truncation" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 -Dsctp_inet=0 -Dsctp_inet6=0 .. && make -j2 usrsctp)
+    (cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC" -Dsctp_werror=0 -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 -Dsctp_inet=0 -Dsctp_inet6=0 .. && make -j2 usrsctp)
     cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
     cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
 }
 }
 rule make_libusrsctp_msvc ( targets * : sources * : properties * )
 rule make_libusrsctp_msvc ( targets * : sources * : properties * )
@@ -118,7 +118,7 @@ actions make_libusrsctp_msvc
     cd $(CWD)/deps/usrsctp
     cd $(CWD)/deps/usrsctp
     mkdir $(BUILD_DIR)
     mkdir $(BUILD_DIR)
     cd $(BUILD_DIR)
     cd $(BUILD_DIR)
-    cmake -G "Visual Studio 16 2019" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 ..
+    cmake -G "Visual Studio 16 2019" -Dsctp_werror=0 -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 -Dsctp_inet=0 -Dsctp_inet6=0 ..
     msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
     msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
     cd %OLDD%
     cd %OLDD%
     cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<)
     cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<)

+ 2 - 2
README.md

@@ -10,7 +10,7 @@
 [![Gitter](https://badges.gitter.im/libdatachannel/community.svg)](https://gitter.im/libdatachannel/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 [![Gitter](https://badges.gitter.im/libdatachannel/community.svg)](https://gitter.im/libdatachannel/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 [![Discord](https://img.shields.io/discord/903257095539925006?logo=discord)](https://discord.gg/jXAP8jp3Nn)
 [![Discord](https://img.shields.io/discord/903257095539925006?logo=discord)](https://discord.gg/jXAP8jp3Nn)
 
 
-libdatachannel is a standalone implementation of WebRTC Data Channels, WebRTC Media Transport, and WebSockets in C++17 with C bindings for POSIX platforms (including GNU/Linux, Android, FreeBSD, Apple macOS and iOS) and Microsoft Windows. WebRTC is a W3C and IETF standard enabling real-time peer-to-peer data and media exchange between two devices.
+libdatachannel is a standalone implementation of WebRTC Data Channels, WebRTC Media Transport, and WebSockets in C++ with C bindings for multiple platforms, including GNU/Linux, Android, FreeBSD, Apple macOS, iOS, and Microsoft Windows. WebRTC is a W3C and IETF standard enabling real-time peer-to-peer data and media exchange between two devices.
 
 
 The library aims at being both straightforward and lightweight with minimal external dependencies, to enable direct connectivity between native applications and web browsers without the pain of importing Google's bloated [reference library](https://webrtc.googlesource.com/src/). The interface consists of somewhat simplified versions of the JavaScript WebRTC and WebSocket APIs present in browsers, in order to ease the design of cross-environment applications.
 The library aims at being both straightforward and lightweight with minimal external dependencies, to enable direct connectivity between native applications and web browsers without the pain of importing Google's bloated [reference library](https://webrtc.googlesource.com/src/). The interface consists of somewhat simplified versions of the JavaScript WebRTC and WebSocket APIs present in browsers, in order to ease the design of cross-environment applications.
 
 
@@ -22,7 +22,7 @@ The WebRTC stack is fully compatible with browsers like Firefox and Chromium, se
 
 
 libdatachannel is licensed under MPL 2.0 since version 0.18, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE) (previous versions were licensed under LGPLv2.1 or later).
 libdatachannel is licensed under MPL 2.0 since version 0.18, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE) (previous versions were licensed under LGPLv2.1 or later).
 
 
-libdatachannel is available on [AUR](https://aur.archlinux.org/packages/libdatachannel/), [vcpkg](https://vcpkg.io/en/getting-started), and [FreeBSD ports](https://www.freshports.org/www/libdatachannel). Bindings are available for [Rust](https://crates.io/crates/datachannel) and [Node.js](https://www.npmjs.com/package/node-datachannel).
+libdatachannel is available on [AUR](https://aur.archlinux.org/packages/libdatachannel/), [vcpkg](https://vcpkg.io/en/getting-started), [conan](https://conan.io/center/recipes/libdatachannel), and [FreeBSD ports](https://www.freshports.org/www/libdatachannel). Bindings are available for [Rust](https://crates.io/crates/datachannel) and [Node.js](https://www.npmjs.com/package/node-datachannel).
 
 
 ## Dependencies
 ## Dependencies
 
 

+ 2 - 2
cmake/Modules/FindLibJuice.cmake

@@ -9,8 +9,8 @@ if (NOT TARGET LibJuice::LibJuice)
         add_library(LibJuice::LibJuice UNKNOWN IMPORTED)
         add_library(LibJuice::LibJuice UNKNOWN IMPORTED)
         set_target_properties(LibJuice::LibJuice PROPERTIES
         set_target_properties(LibJuice::LibJuice PROPERTIES
             IMPORTED_LOCATION "${JUICE_LIBRARY}"
             IMPORTED_LOCATION "${JUICE_LIBRARY}"
-            INTERFACE_INCLUDE_DIRECTORIES "${JUICE_INCLUDE_DIRS}"
-            INTERFACE_LINK_LIBRARIES "${JUICE_LIBRARIES}"
+            INTERFACE_INCLUDE_DIRECTORIES "${JUICE_INCLUDE_DIR}"
+            INTERFACE_LINK_LIBRARIES "${JUICE_LIBRARY}"
                 IMPORTED_LINK_INTERFACE_LANGUAGES "C")
                 IMPORTED_LINK_INTERFACE_LANGUAGES "C")
     endif ()
     endif ()
 endif ()
 endif ()

+ 1 - 1
deps/libjuice

@@ -1 +1 @@
-Subproject commit 2de35247f0b15fa385406f3e2020d0e3d4d5cfcc
+Subproject commit 77daa8befd828046169adbb012d8de928bfdcdd4

+ 4 - 17
examples/streamer/main.cpp

@@ -20,6 +20,8 @@
 #include "helpers.hpp"
 #include "helpers.hpp"
 #include "ArgParser.hpp"
 #include "ArgParser.hpp"
 
 
+#include <chrono>
+
 using namespace rtc;
 using namespace rtc;
 using namespace std;
 using namespace std;
 using namespace std::chrono_literals;
 using namespace std::chrono_literals;
@@ -208,7 +210,7 @@ shared_ptr<ClientTrackData> addVideo(const shared_ptr<PeerConnection> pc, const
     video.addSSRC(ssrc, cname, msid, cname);
     video.addSSRC(ssrc, cname, msid, cname);
     auto track = pc->addTrack(video);
     auto track = pc->addTrack(video);
     // create RTP configuration
     // create RTP configuration
-    auto rtpConfig = make_shared<RtpPacketizationConfig>(ssrc, cname, payloadType, H264RtpPacketizer::defaultClockRate);
+    auto rtpConfig = make_shared<RtpPacketizationConfig>(ssrc, cname, payloadType, H264RtpPacketizer::ClockRate);
     // create packetizer
     // create packetizer
     auto packetizer = make_shared<H264RtpPacketizer>(NalUnit::Separator::Length, rtpConfig);
     auto packetizer = make_shared<H264RtpPacketizer>(NalUnit::Separator::Length, rtpConfig);
     // add RTCP SR handler
     // add RTCP SR handler
@@ -351,26 +353,11 @@ shared_ptr<Stream> createStream(const string h264Samples, const unsigned fps, co
             for (auto clientTrack: tracks) {
             for (auto clientTrack: tracks) {
                 auto client = clientTrack.id;
                 auto client = clientTrack.id;
                 auto trackData = clientTrack.trackData;
                 auto trackData = clientTrack.trackData;
-                auto rtpConfig = trackData->sender->rtpConfig;
-
-                // sample time is in us, we need to convert it to seconds
-                auto elapsedSeconds = double(sampleTime) / (1000 * 1000);
-                // get elapsed time in clock rate
-                uint32_t elapsedTimestamp = rtpConfig->secondsToTimestamp(elapsedSeconds);
-                // set new timestamp
-                rtpConfig->timestamp = rtpConfig->startTimestamp + elapsedTimestamp;
-
-                // get elapsed time in clock rate from last RTCP sender report
-                auto reportElapsedTimestamp = rtpConfig->timestamp - trackData->sender->lastReportedTimestamp();
-                // check if last report was at least 1 second ago
-                if (rtpConfig->timestampToSeconds(reportElapsedTimestamp) > 1) {
-                    trackData->sender->setNeedsToReport();
-                }
 
 
                 cout << "Sending " << streamType << " sample with size: " << to_string(sample.size()) << " to " << client << endl;
                 cout << "Sending " << streamType << " sample with size: " << to_string(sample.size()) << " to " << client << endl;
                 try {
                 try {
                     // send sample
                     // send sample
-                    trackData->track->send(sample);
+                    trackData->track->sendFrame(sample, std::chrono::duration<double, std::micro>(sampleTime));
                 } catch (const std::exception &e) {
                 } catch (const std::exception &e) {
                     cerr << "Unable to send "<< streamType << " packet: " << e.what() << endl;
                     cerr << "Unable to send "<< streamType << " packet: " << e.what() << endl;
                 }
                 }

+ 11 - 10
include/rtc/av1rtppacketizer.hpp

@@ -20,8 +20,8 @@ namespace rtc {
 // RTP packetization of AV1 payload
 // RTP packetization of AV1 payload
 class RTC_CPP_EXPORT AV1RtpPacketizer final : public RtpPacketizer {
 class RTC_CPP_EXPORT AV1RtpPacketizer final : public RtpPacketizer {
 public:
 public:
-	// Default clock rate for AV1 in RTP
-	inline static const uint32_t defaultClockRate = 90 * 1000;
+	inline static const uint32_t ClockRate = VideoClockRate;
+	[[deprecated("Use ClockRate")]] inline static const uint32_t defaultClockRate = ClockRate;
 
 
 	// Define how OBUs are seperated in a AV1 Sample
 	// Define how OBUs are seperated in a AV1 Sample
 	enum class Packetization {
 	enum class Packetization {
@@ -33,17 +33,18 @@ public:
 	// @note RTP configuration is used in packetization process which may change some configuration
 	// @note RTP configuration is used in packetization process which may change some configuration
 	// properties such as sequence number.
 	// properties such as sequence number.
 	AV1RtpPacketizer(Packetization packetization, shared_ptr<RtpPacketizationConfig> rtpConfig,
 	AV1RtpPacketizer(Packetization packetization, shared_ptr<RtpPacketizationConfig> rtpConfig,
-	                 uint16_t maxFragmentSize = NalUnits::defaultMaximumFragmentSize);
-
-	void outgoing(message_vector &messages, const message_callback &send) override;
+	                 size_t maxFragmentSize = DefaultMaxFragmentSize);
 
 
 private:
 private:
-	shared_ptr<NalUnits> splitMessage(binary_ptr message);
-	std::vector<shared_ptr<binary>> packetizeObu(binary_ptr message, uint16_t maxFragmentSize);
+	static std::vector<binary> extractTemporalUnitObus(const binary &data);
+
+	std::vector<binary> fragment(binary data) override;
+	std::vector<binary> fragmentObu(const binary &data);
+
+	const Packetization mPacketization;
+	const size_t mMaxFragmentSize;
 
 
-	const uint16_t maxFragmentSize;
-	const Packetization packetization;
-	std::shared_ptr<binary> sequenceHeader;
+	std::unique_ptr<binary> mSequenceHeader;
 };
 };
 
 
 // For backward compatibility, do not use
 // For backward compatibility, do not use

+ 0 - 1
include/rtc/common.hpp

@@ -67,7 +67,6 @@ using std::variant;
 using std::weak_ptr;
 using std::weak_ptr;
 
 
 using binary = std::vector<byte>;
 using binary = std::vector<byte>;
-using binary_ptr = shared_ptr<binary>;
 using message_variant = variant<binary, string>;
 using message_variant = variant<binary, string>;
 
 
 using std::int16_t;
 using std::int16_t;

+ 5 - 4
include/rtc/description.hpp

@@ -66,9 +66,10 @@ public:
 	bool ended() const;
 	bool ended() const;
 
 
 	void hintType(Type type);
 	void hintType(Type type);
-	void setFingerprint(CertificateFingerprint f);
 	void addIceOption(string option);
 	void addIceOption(string option);
 	void removeIceOption(const string &option);
 	void removeIceOption(const string &option);
+	void setIceAttribute(string ufrag, string pwd);
+	void setFingerprint(CertificateFingerprint f);
 
 
 	std::vector<string> attributes() const;
 	std::vector<string> attributes() const;
 	void addAttribute(string attr);
 	void addAttribute(string attr);
@@ -281,9 +282,9 @@ public:
 	int addAudio(string mid = "audio", Direction dir = Direction::SendOnly);
 	int addAudio(string mid = "audio", Direction dir = Direction::SendOnly);
 	void clearMedia();
 	void clearMedia();
 
 
-	variant<Media *, Application *> media(unsigned int index);
-	variant<const Media *, const Application *> media(unsigned int index) const;
-	unsigned int mediaCount() const;
+	variant<Media *, Application *> media(int index);
+	variant<const Media *, const Application *> media(int index) const;
+	int mediaCount() const;
 
 
 	const Application *application() const;
 	const Application *application() const;
 	Application *application();
 	Application *application();

+ 11 - 3
include/rtc/frameinfo.hpp

@@ -11,12 +11,20 @@
 
 
 #include "common.hpp"
 #include "common.hpp"
 
 
+#include <chrono>
+
 namespace rtc {
 namespace rtc {
 
 
 struct RTC_CPP_EXPORT FrameInfo {
 struct RTC_CPP_EXPORT FrameInfo {
-	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
+	FrameInfo(uint32_t timestamp) : timestamp(timestamp) {};
+	template<typename Period = std::ratio<1>> FrameInfo(std::chrono::duration<double, Period> timestamp) : timestampSeconds(timestamp) {};
+
+	[[deprecated]] FrameInfo(uint8_t payloadType, uint32_t timestamp) : timestamp(timestamp), payloadType(payloadType) {};
+
+	uint32_t timestamp = 0;
+	uint8_t payloadType = 0;
+
+	optional<std::chrono::duration<double>> timestampSeconds;
 };
 };
 
 
 } // namespace rtc
 } // namespace rtc

+ 2 - 0
include/rtc/h264rtpdepacketizer.hpp

@@ -27,6 +27,8 @@ class RTC_CPP_EXPORT H264RtpDepacketizer : public MediaHandler {
 public:
 public:
 	using Separator = NalUnit::Separator;
 	using Separator = NalUnit::Separator;
 
 
+	inline static const uint32_t ClockRate = 90 * 1000;
+
 	H264RtpDepacketizer(Separator separator = Separator::LongStartSequence);
 	H264RtpDepacketizer(Separator separator = Separator::LongStartSequence);
 	virtual ~H264RtpDepacketizer() = default;
 	virtual ~H264RtpDepacketizer() = default;
 
 

+ 8 - 9
include/rtc/h264rtppacketizer.hpp

@@ -22,8 +22,8 @@ class RTC_CPP_EXPORT H264RtpPacketizer final : public RtpPacketizer {
 public:
 public:
 	using Separator = NalUnit::Separator;
 	using Separator = NalUnit::Separator;
 
 
-	/// Default clock rate for H264 in RTP
-	inline static const uint32_t defaultClockRate = 90 * 1000;
+	inline static const uint32_t ClockRate = VideoClockRate;
+	[[deprecated("Use ClockRate")]] inline static const uint32_t defaultClockRate = ClockRate;
 
 
 	/// Constructs h264 payload packetizer with given RTP configuration.
 	/// Constructs h264 payload packetizer with given RTP configuration.
 	/// @note RTP configuration is used in packetization process which may change some configuration
 	/// @note RTP configuration is used in packetization process which may change some configuration
@@ -32,20 +32,19 @@ public:
 	/// @param rtpConfig RTP configuration
 	/// @param rtpConfig RTP configuration
 	/// @param maxFragmentSize maximum size of one NALU fragment
 	/// @param maxFragmentSize maximum size of one NALU fragment
 	H264RtpPacketizer(Separator separator, shared_ptr<RtpPacketizationConfig> rtpConfig,
 	H264RtpPacketizer(Separator separator, shared_ptr<RtpPacketizationConfig> rtpConfig,
-	                  uint16_t maxFragmentSize = NalUnits::defaultMaximumFragmentSize);
+	                  size_t maxFragmentSize = DefaultMaxFragmentSize);
 
 
 	// For backward compatibility, do not use
 	// For backward compatibility, do not use
 	[[deprecated]] H264RtpPacketizer(
 	[[deprecated]] H264RtpPacketizer(
 	    shared_ptr<RtpPacketizationConfig> rtpConfig,
 	    shared_ptr<RtpPacketizationConfig> rtpConfig,
-	    uint16_t maxFragmentSize = NalUnits::defaultMaximumFragmentSize);
-
-	void outgoing(message_vector &messages, const message_callback &send) override;
+	    size_t maxFragmentSize = DefaultMaxFragmentSize);
 
 
 private:
 private:
-	shared_ptr<NalUnits> splitMessage(binary_ptr message);
+	std::vector<binary> fragment(binary data) override;
+	std::vector<NalUnit> splitFrame(const binary &frame);
 
 
-	const uint16_t maxFragmentSize;
-	const Separator separator;
+	const Separator mSeparator;
+	const size_t mMaxFragmentSize;
 };
 };
 
 
 // For backward compatibility, do not use
 // For backward compatibility, do not use

+ 12 - 4
include/rtc/h265nalunit.hpp

@@ -15,6 +15,7 @@
 #include "nalunit.hpp"
 #include "nalunit.hpp"
 
 
 #include <cassert>
 #include <cassert>
+#include <vector>
 
 
 namespace rtc {
 namespace rtc {
 
 
@@ -72,8 +73,13 @@ struct RTC_CPP_EXPORT H265NalUnitFragmentHeader {
 
 
 #pragma pack(pop)
 #pragma pack(pop)
 
 
-/// Nal unit
+struct H265NalUnitFragment;
+
+/// NAL unit
 struct RTC_CPP_EXPORT H265NalUnit : NalUnit {
 struct RTC_CPP_EXPORT H265NalUnit : NalUnit {
+	static std::vector<binary> GenerateFragments(const std::vector<H265NalUnit> &nalus,
+	                                             size_t maxFragmentSize);
+
 	H265NalUnit(const H265NalUnit &unit) = default;
 	H265NalUnit(const H265NalUnit &unit) = default;
 	H265NalUnit(size_t size, bool includingHeader = true)
 	H265NalUnit(size_t size, bool includingHeader = true)
 	    : NalUnit(size, includingHeader, NalUnit::Type::H265) {}
 	    : NalUnit(size, includingHeader, NalUnit::Type::H265) {}
@@ -104,6 +110,8 @@ struct RTC_CPP_EXPORT H265NalUnit : NalUnit {
 		insert(end(), payload.begin(), payload.end());
 		insert(end(), payload.begin(), payload.end());
 	}
 	}
 
 
+	std::vector<H265NalUnitFragment> generateFragments(size_t maxFragmentSize) const;
+
 protected:
 protected:
 	const H265NalUnitHeader *header() const {
 	const H265NalUnitHeader *header() const {
 		assert(size() >= H265_NAL_HEADER_SIZE);
 		assert(size() >= H265_NAL_HEADER_SIZE);
@@ -116,9 +124,9 @@ protected:
 	}
 	}
 };
 };
 
 
-/// Nal unit fragment A
+/// NAL unit fragment
 struct RTC_CPP_EXPORT H265NalUnitFragment : H265NalUnit {
 struct RTC_CPP_EXPORT H265NalUnitFragment : H265NalUnit {
-	static std::vector<shared_ptr<H265NalUnitFragment>> fragmentsFrom(shared_ptr<H265NalUnit> nalu,
+	[[deprecated]] static std::vector<shared_ptr<H265NalUnitFragment>> fragmentsFrom(shared_ptr<H265NalUnit> nalu,
 	                                                                  uint16_t maxFragmentSize);
 	                                                                  uint16_t maxFragmentSize);
 
 
 	enum class FragmentType { Start, Middle, End };
 	enum class FragmentType { Start, Middle, End };
@@ -171,7 +179,7 @@ protected:
 	}
 	}
 };
 };
 
 
-class RTC_CPP_EXPORT H265NalUnits : public std::vector<shared_ptr<H265NalUnit>> {
+class [[deprecated]] RTC_CPP_EXPORT H265NalUnits : public std::vector<shared_ptr<H265NalUnit>> {
 public:
 public:
 	static const uint16_t defaultMaximumFragmentSize =
 	static const uint16_t defaultMaximumFragmentSize =
 	    uint16_t(RTC_DEFAULT_MTU - 12 - 8 - 40); // SRTP/UDP/IPv6
 	    uint16_t(RTC_DEFAULT_MTU - 12 - 8 - 40); // SRTP/UDP/IPv6

+ 51 - 0
include/rtc/h265rtpdepacketizer.hpp

@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2020 Staz Modrzynski
+ * Copyright (c) 2020-2024 Paul-Louis Ageneau
+ * Copyright (c) 2024 Robert Edmonds
+ *
+ * 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_H265_RTP_DEPACKETIZER_H
+#define RTC_H265_RTP_DEPACKETIZER_H
+
+#if RTC_ENABLE_MEDIA
+
+#include "common.hpp"
+#include "h265nalunit.hpp"
+#include "mediahandler.hpp"
+#include "message.hpp"
+#include "rtp.hpp"
+
+#include <iterator>
+
+namespace rtc {
+
+/// RTP depacketization for H265
+class RTC_CPP_EXPORT H265RtpDepacketizer : public MediaHandler {
+public:
+	using Separator = NalUnit::Separator;
+
+	inline static const uint32_t ClockRate = 90 * 1000;
+
+	H265RtpDepacketizer(Separator separator = Separator::LongStartSequence);
+	virtual ~H265RtpDepacketizer() = default;
+
+	void incoming(message_vector &messages, const message_callback &send) override;
+
+private:
+	std::vector<message_ptr> mRtpBuffer;
+	const NalUnit::Separator separator;
+
+	void addSeparator(binary& accessUnit);
+	message_vector buildFrames(message_vector::iterator firstPkt, message_vector::iterator lastPkt,
+	                           uint8_t payloadType, uint32_t timestamp);
+};
+
+} // namespace rtc
+
+#endif // RTC_ENABLE_MEDIA
+
+#endif // RTC_H265_RTP_DEPACKETIZER_H

+ 9 - 10
include/rtc/h265rtppacketizer.hpp

@@ -21,8 +21,8 @@ class RTC_CPP_EXPORT H265RtpPacketizer final : public RtpPacketizer {
 public:
 public:
 	using Separator = NalUnit::Separator;
 	using Separator = NalUnit::Separator;
 
 
-	// Default clock rate for H265 in RTP
-	inline static const uint32_t defaultClockRate = 90 * 1000;
+	inline static const uint32_t ClockRate = VideoClockRate;
+	[[deprecated("Use ClockRate")]] inline static const uint32_t defaultClockRate = ClockRate;
 
 
 	// Constructs h265 payload packetizer with given RTP configuration.
 	// Constructs h265 payload packetizer with given RTP configuration.
 	// @note RTP configuration is used in packetization process which may change some configuration
 	// @note RTP configuration is used in packetization process which may change some configuration
@@ -31,19 +31,18 @@ public:
 	// @param rtpConfig  RTP configuration
 	// @param rtpConfig  RTP configuration
 	// @param maxFragmentSize maximum size of one NALU fragment
 	// @param maxFragmentSize maximum size of one NALU fragment
 	H265RtpPacketizer(Separator separator, shared_ptr<RtpPacketizationConfig> rtpConfig,
 	H265RtpPacketizer(Separator separator, shared_ptr<RtpPacketizationConfig> rtpConfig,
-	                  uint16_t maxFragmentSize = H265NalUnits::defaultMaximumFragmentSize);
+	                  size_t maxFragmentSize = DefaultMaxFragmentSize);
 
 
-	// for backward compatibility
+	// For backward compatibility, do not use
 	[[deprecated]] H265RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
 	[[deprecated]] H265RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
-	                  uint16_t maxFragmentSize = H265NalUnits::defaultMaximumFragmentSize);
-
-	void outgoing(message_vector &messages, const message_callback &send) override;
+	                  size_t maxFragmentSize = DefaultMaxFragmentSize);
 
 
 private:
 private:
-	shared_ptr<H265NalUnits> splitMessage(binary_ptr message);
+	std::vector<binary> fragment(binary data) override;
+	std::vector<H265NalUnit> splitFrame(const binary &frame);
 
 
-	const uint16_t maxFragmentSize;
-	const NalUnit::Separator separator;
+	const NalUnit::Separator mSeparator;
+	const size_t mMaxFragmentSize;
 };
 };
 
 
 // For backward compatibility, do not use
 // For backward compatibility, do not use

+ 47 - 0
include/rtc/iceudpmuxlistener.hpp

@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2025 Alex Potsides
+ * Copyright (c) 2025 Paul-Louis Ageneau
+ *
+ * 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_ICE_UDP_MUX_LISTENER_H
+#define RTC_ICE_UDP_MUX_LISTENER_H
+
+#include "common.hpp"
+
+namespace rtc {
+
+namespace impl {
+
+struct IceUdpMuxListener;
+
+} // namespace impl
+
+struct IceUdpMuxRequest { // TODO change name
+	string localUfrag;
+	string remoteUfrag;
+	string remoteAddress;
+	uint16_t remotePort;
+};
+
+class RTC_CPP_EXPORT IceUdpMuxListener final : private CheshireCat<impl::IceUdpMuxListener> {
+public:
+	IceUdpMuxListener(uint16_t port, optional<string> bindAddress = nullopt);
+	~IceUdpMuxListener();
+
+	void stop();
+
+	uint16_t port() const;
+
+	void OnUnhandledStunRequest(std::function<void(IceUdpMuxRequest)> callback);
+
+private:
+	using CheshireCat<impl::IceUdpMuxListener>::impl;
+};
+
+} // namespace rtc
+
+#endif

+ 20 - 4
include/rtc/message.hpp

@@ -46,11 +46,26 @@ inline size_t message_size_func(const message_ptr &m) {
 
 
 template <typename Iterator>
 template <typename Iterator>
 message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Message::Binary,
 message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Message::Binary,
-                         unsigned int stream = 0, shared_ptr<Reliability> reliability = nullptr,
-                         shared_ptr<FrameInfo> frameInfo = nullptr) {
+                         unsigned int stream = 0, shared_ptr<Reliability> reliability = nullptr) {
 	auto message = std::make_shared<Message>(begin, end, type);
 	auto message = std::make_shared<Message>(begin, end, type);
 	message->stream = stream;
 	message->stream = stream;
 	message->reliability = reliability;
 	message->reliability = reliability;
+	return message;
+}
+
+template <typename Iterator>
+message_ptr make_message(Iterator begin, Iterator end, shared_ptr<FrameInfo> frameInfo) {
+	auto message = std::make_shared<Message>(begin, end);
+	message->frameInfo = frameInfo;
+	return message;
+}
+
+// For backward compatibiity, do not use
+template <typename Iterator>
+[[deprecated]] message_ptr make_message(Iterator begin, Iterator end, Message::Type type,
+                         unsigned int stream, shared_ptr<FrameInfo> frameInfo) {
+	auto message = std::make_shared<Message>(begin, end, type);
+	message->stream = stream;
 	message->frameInfo = frameInfo;
 	message->frameInfo = frameInfo;
 	return message;
 	return message;
 }
 }
@@ -61,8 +76,9 @@ RTC_CPP_EXPORT message_ptr make_message(size_t size, Message::Type type = Messag
 
 
 RTC_CPP_EXPORT message_ptr make_message(binary &&data, Message::Type type = Message::Binary,
 RTC_CPP_EXPORT message_ptr make_message(binary &&data, Message::Type type = Message::Binary,
                                         unsigned int stream = 0,
                                         unsigned int stream = 0,
-                                        shared_ptr<Reliability> reliability = nullptr,
-                                        shared_ptr<FrameInfo> frameInfo = nullptr);
+                                        shared_ptr<Reliability> reliability = nullptr);
+
+RTC_CPP_EXPORT message_ptr make_message(binary &&data, shared_ptr<FrameInfo> frameInfo);
 
 
 RTC_CPP_EXPORT message_ptr make_message(size_t size, message_ptr orig);
 RTC_CPP_EXPORT message_ptr make_message(size_t size, message_ptr orig);
 
 

+ 27 - 57
include/rtc/nalunit.hpp

@@ -13,6 +13,7 @@
 
 
 #include "common.hpp"
 #include "common.hpp"
 
 
+#include <vector>
 #include <cassert>
 #include <cassert>
 
 
 namespace rtc {
 namespace rtc {
@@ -61,15 +62,31 @@ enum NalUnitStartSequenceMatch {
 
 
 static const size_t H264_NAL_HEADER_SIZE = 1;
 static const size_t H264_NAL_HEADER_SIZE = 1;
 static const size_t H265_NAL_HEADER_SIZE = 2;
 static const size_t H265_NAL_HEADER_SIZE = 2;
-/// Nal unit
+
+struct NalUnitFragmentA;
+
+/// NAL unit
 struct RTC_CPP_EXPORT NalUnit : binary {
 struct RTC_CPP_EXPORT NalUnit : binary {
+	static std::vector<binary> GenerateFragments(const std::vector<NalUnit> &nalus,
+	                                             size_t maxFragmentSize);
+
+	enum class Separator {
+		Length = RTC_NAL_SEPARATOR_LENGTH, // first 4 bytes are NAL unit length
+		LongStartSequence = RTC_NAL_SEPARATOR_LONG_START_SEQUENCE,   // 0x00, 0x00, 0x00, 0x01
+		ShortStartSequence = RTC_NAL_SEPARATOR_SHORT_START_SEQUENCE, // 0x00, 0x00, 0x01
+		StartSequence = RTC_NAL_SEPARATOR_START_SEQUENCE, // LongStartSequence or ShortStartSequence
+	};
+
+	static NalUnitStartSequenceMatch StartSequenceMatchSucc(NalUnitStartSequenceMatch match,
+	                                                 std::byte _byte, Separator separator);
+
 	enum class Type { H264, H265 };
 	enum class Type { H264, H265 };
 
 
 	NalUnit(const NalUnit &unit) = default;
 	NalUnit(const NalUnit &unit) = default;
 	NalUnit(size_t size, bool includingHeader = true, Type type = Type::H264)
 	NalUnit(size_t size, bool includingHeader = true, Type type = Type::H264)
-	    : binary(size + (includingHeader
-	                         ? 0
-	                         : (type == Type::H264 ? H264_NAL_HEADER_SIZE : H265_NAL_HEADER_SIZE))) {}
+	    : binary(size + (includingHeader ? 0
+	                                     : (type == Type::H264 ? H264_NAL_HEADER_SIZE
+	                                                           : H265_NAL_HEADER_SIZE))) {}
 	NalUnit(binary &&data) : binary(std::move(data)) {}
 	NalUnit(binary &&data) : binary(std::move(data)) {}
 	NalUnit(Type type = Type::H264)
 	NalUnit(Type type = Type::H264)
 	    : binary(type == Type::H264 ? H264_NAL_HEADER_SIZE : H265_NAL_HEADER_SIZE) {}
 	    : binary(type == Type::H264 ? H264_NAL_HEADER_SIZE : H265_NAL_HEADER_SIZE) {}
@@ -94,56 +111,7 @@ struct RTC_CPP_EXPORT NalUnit : binary {
 		insert(end(), payload.begin(), payload.end());
 		insert(end(), payload.begin(), payload.end());
 	}
 	}
 
 
-	/// NAL unit separator
-	enum class Separator {
-		Length = RTC_NAL_SEPARATOR_LENGTH, // first 4 bytes are NAL unit length
-		LongStartSequence = RTC_NAL_SEPARATOR_LONG_START_SEQUENCE,   // 0x00, 0x00, 0x00, 0x01
-		ShortStartSequence = RTC_NAL_SEPARATOR_SHORT_START_SEQUENCE, // 0x00, 0x00, 0x01
-		StartSequence = RTC_NAL_SEPARATOR_START_SEQUENCE, // LongStartSequence or ShortStartSequence
-	};
-
-	static NalUnitStartSequenceMatch StartSequenceMatchSucc(NalUnitStartSequenceMatch match,
-	                                                        std::byte _byte, Separator separator) {
-		assert(separator != Separator::Length);
-		auto byte = (uint8_t)_byte;
-		auto detectShort =
-		    separator == Separator::ShortStartSequence || separator == Separator::StartSequence;
-		auto detectLong =
-		    separator == Separator::LongStartSequence || separator == Separator::StartSequence;
-		switch (match) {
-		case NUSM_noMatch:
-			if (byte == 0x00) {
-				return NUSM_firstZero;
-			}
-			break;
-		case NUSM_firstZero:
-			if (byte == 0x00) {
-				return NUSM_secondZero;
-			}
-			break;
-		case NUSM_secondZero:
-			if (byte == 0x00 && detectLong) {
-				return NUSM_thirdZero;
-			} else if (byte == 0x00 && detectShort) {
-				return NUSM_secondZero;
-			} else if (byte == 0x01 && detectShort) {
-				return NUSM_shortMatch;
-			}
-			break;
-		case NUSM_thirdZero:
-			if (byte == 0x00 && detectLong) {
-				return NUSM_thirdZero;
-			} else if (byte == 0x01 && detectLong) {
-				return NUSM_longMatch;
-			}
-			break;
-		case NUSM_shortMatch:
-			return NUSM_shortMatch;
-		case NUSM_longMatch:
-			return NUSM_longMatch;
-		}
-		return NUSM_noMatch;
-	}
+	std::vector<NalUnitFragmentA> generateFragments(size_t maxFragmentSize) const;
 
 
 protected:
 protected:
 	const NalUnitHeader *header() const {
 	const NalUnitHeader *header() const {
@@ -159,8 +127,9 @@ protected:
 
 
 /// Nal unit fragment A
 /// Nal unit fragment A
 struct RTC_CPP_EXPORT NalUnitFragmentA : NalUnit {
 struct RTC_CPP_EXPORT NalUnitFragmentA : NalUnit {
-	static std::vector<shared_ptr<NalUnitFragmentA>> fragmentsFrom(shared_ptr<NalUnit> nalu,
-	                                                               uint16_t maxFragmentSize);
+	// For backward compatibility, do not use
+	[[deprecated]] static std::vector<shared_ptr<NalUnitFragmentA>>
+	fragmentsFrom(shared_ptr<NalUnit> nalu, uint16_t maxFragmentSize);
 
 
 	enum class FragmentType { Start, Middle, End };
 	enum class FragmentType { Start, Middle, End };
 
 
@@ -212,7 +181,8 @@ protected:
 	}
 	}
 };
 };
 
 
-class RTC_CPP_EXPORT NalUnits : public std::vector<shared_ptr<NalUnit>> {
+// For backward compatibility, do not use
+class [[deprecated]] RTC_CPP_EXPORT NalUnits : public std::vector<shared_ptr<NalUnit>> {
 public:
 public:
 	static const uint16_t defaultMaximumFragmentSize =
 	static const uint16_t defaultMaximumFragmentSize =
 	    uint16_t(RTC_DEFAULT_MTU - 12 - 8 - 40); // SRTP/UDP/IPv6
 	    uint16_t(RTC_DEFAULT_MTU - 12 - 8 - 40); // SRTP/UDP/IPv6

+ 13 - 2
include/rtc/peerconnection.hpp

@@ -35,6 +35,11 @@ struct RTC_CPP_EXPORT DataChannelInit {
 	string protocol = "";
 	string protocol = "";
 };
 };
 
 
+struct RTC_CPP_EXPORT LocalDescriptionInit {
+    optional<string> iceUfrag;
+    optional<string> icePwd;
+};
+
 class RTC_CPP_EXPORT PeerConnection final : CheshireCat<impl::PeerConnection> {
 class RTC_CPP_EXPORT PeerConnection final : CheshireCat<impl::PeerConnection> {
 public:
 public:
 	enum class State : int {
 	enum class State : int {
@@ -81,6 +86,7 @@ public:
 	IceState iceState() const;
 	IceState iceState() const;
 	GatheringState gatheringState() const;
 	GatheringState gatheringState() const;
 	SignalingState signalingState() const;
 	SignalingState signalingState() const;
+	bool negotiationNeeded() const;
 	bool hasMedia() const;
 	bool hasMedia() const;
 	optional<Description> localDescription() const;
 	optional<Description> localDescription() const;
 	optional<Description> remoteDescription() const;
 	optional<Description> remoteDescription() const;
@@ -90,10 +96,14 @@ public:
 	uint16_t maxDataChannelId() const;
 	uint16_t maxDataChannelId() const;
 	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
 	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
 
 
-	void setLocalDescription(Description::Type type = Description::Type::Unspec);
+	void setLocalDescription(Description::Type type = Description::Type::Unspec, LocalDescriptionInit init = {});
+	void gatherLocalCandidates(std::vector<IceServer> additionalIceServers = {});
 	void setRemoteDescription(Description description);
 	void setRemoteDescription(Description description);
 	void addRemoteCandidate(Candidate candidate);
 	void addRemoteCandidate(Candidate candidate);
-	void gatherLocalCandidates(std::vector<IceServer> additionalIceServers = {});
+
+	// For specific use cases only
+	Description createOffer();
+	Description createAnswer();
 
 
 	void setMediaHandler(shared_ptr<MediaHandler> handler);
 	void setMediaHandler(shared_ptr<MediaHandler> handler);
 	shared_ptr<MediaHandler> getMediaHandler();
 	shared_ptr<MediaHandler> getMediaHandler();
@@ -113,6 +123,7 @@ public:
 	void onSignalingStateChange(std::function<void(SignalingState state)> callback);
 	void onSignalingStateChange(std::function<void(SignalingState state)> callback);
 
 
 	void resetCallbacks();
 	void resetCallbacks();
+	CertificateFingerprint remoteFingerprint();
 
 
 	// Stats
 	// Stats
 	void clearStats();
 	void clearStats();

+ 35 - 0
include/rtc/rembhandler.hpp

@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2024 Vladimir Voronin
+ *
+ * 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_REMB_RESPONDER_H
+#define RTC_REMB_RESPONDER_H
+
+#if RTC_ENABLE_MEDIA
+
+#include "mediahandler.hpp"
+#include "utils.hpp"
+
+namespace rtc {
+
+/// Responds to REMB messages sent by the receiver.
+class RTC_CPP_EXPORT RembHandler final : public MediaHandler {
+    rtc::synchronized_callback<unsigned int> mOnRemb;
+
+public:
+	/// Constructs the RembResponder object to notify whenever a bitrate
+	/// @param onRemb The callback that gets called whenever a bitrate by the receiver
+    RembHandler(std::function<void(unsigned int)> onRemb);
+
+	void incoming(message_vector &messages, const message_callback &send) override;
+};
+
+}
+
+#endif // RTC_ENABLE_MEDIA
+
+#endif // RTC_REMB_RESPONDER_H

+ 14 - 4
include/rtc/rtc.h

@@ -170,6 +170,7 @@ typedef void *(RTC_API *rtcInterceptorCallbackFunc)(int pc, const char *message,
 typedef void(RTC_API *rtcBufferedAmountLowCallbackFunc)(int id, void *ptr);
 typedef void(RTC_API *rtcBufferedAmountLowCallbackFunc)(int id, void *ptr);
 typedef void(RTC_API *rtcAvailableCallbackFunc)(int id, void *ptr);
 typedef void(RTC_API *rtcAvailableCallbackFunc)(int id, void *ptr);
 typedef void(RTC_API *rtcPliHandlerCallbackFunc)(int tr, void *ptr);
 typedef void(RTC_API *rtcPliHandlerCallbackFunc)(int tr, void *ptr);
+typedef void(RTC_API *rtcRembHandlerCallbackFunc)(int tr, unsigned int bitrate, void *ptr);
 
 
 // Log
 // Log
 
 
@@ -210,7 +211,7 @@ RTC_C_EXPORT int rtcSetIceStateChangeCallback(int pc, rtcIceStateChangeCallbackF
 RTC_C_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
 RTC_C_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
 RTC_C_EXPORT int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb);
 RTC_C_EXPORT int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb);
 
 
-RTC_C_EXPORT int rtcSetLocalDescription(int pc, const char *type);
+RTC_C_EXPORT int rtcSetLocalDescription(int pc, const char *type); // type may be NULL
 RTC_C_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
 RTC_C_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
 RTC_C_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
 RTC_C_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
 
 
@@ -220,12 +221,18 @@ RTC_C_EXPORT int rtcGetRemoteDescription(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetLocalDescriptionType(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetLocalDescriptionType(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetRemoteDescriptionType(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetRemoteDescriptionType(int pc, char *buffer, int size);
 
 
+// For specific use cases only
+RTC_C_EXPORT int rtcCreateOffer(int pc, char *buffer, int size);
+RTC_C_EXPORT int rtcCreateAnswer(int pc, char *buffer, int size);
+
 RTC_C_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 
 
 RTC_C_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote,
 RTC_C_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote,
                                              int remoteSize);
                                              int remoteSize);
 
 
+RTC_C_EXPORT bool rtcIsNegotiationNeeded(int pc);
+
 RTC_C_EXPORT int rtcGetMaxDataChannelStream(int pc);
 RTC_C_EXPORT int rtcGetMaxDataChannelStream(int pc);
 RTC_C_EXPORT int rtcGetRemoteMaxMessageSize(int pc);
 RTC_C_EXPORT int rtcGetRemoteMaxMessageSize(int pc);
 
 
@@ -409,6 +416,9 @@ RTC_C_EXPORT int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPackets
 // Chain PliHandler on track
 // Chain PliHandler on track
 RTC_C_EXPORT int rtcChainPliHandler(int tr, rtcPliHandlerCallbackFunc cb);
 RTC_C_EXPORT int rtcChainPliHandler(int tr, rtcPliHandlerCallbackFunc cb);
 
 
+// Chain RembHandler on track
+RTC_C_EXPORT int rtcChainRembHandler(int tr, rtcRembHandlerCallbackFunc cb);
+
 // Transform seconds to timestamp using track's clock rate, result is written to timestamp
 // Transform seconds to timestamp using track's clock rate, result is written to timestamp
 RTC_C_EXPORT int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp);
 RTC_C_EXPORT int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp);
 
 
@@ -424,9 +434,6 @@ RTC_C_EXPORT int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp);
 // Get timestamp of last RTCP SR, result is written to timestamp
 // Get timestamp of last RTCP SR, result is written to timestamp
 RTC_C_EXPORT int rtcGetLastTrackSenderReportTimestamp(int id, uint32_t *timestamp);
 RTC_C_EXPORT int rtcGetLastTrackSenderReportTimestamp(int id, uint32_t *timestamp);
 
 
-// Set NeedsToReport flag in RtcpSrReporter handler identified by given track id
-RTC_C_EXPORT int rtcSetNeedsToSendRtcpSr(int id);
-
 // Get all available payload types for given codec and stores them in buffer, does nothing if
 // Get all available payload types for given codec and stores them in buffer, does nothing if
 // buffer is NULL
 // buffer is NULL
 int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size);
 int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size);
@@ -444,6 +451,9 @@ int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer,
 int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize,
 int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize,
                       rtcSsrcForTypeInit *init);
                       rtcSsrcForTypeInit *init);
 
 
+// For backward compatibility, do not use
+RTC_C_EXPORT RTC_DEPRECATED int rtcSetNeedsToSendRtcpSr(int id);
+
 #endif // RTC_ENABLE_MEDIA
 #endif // RTC_ENABLE_MEDIA
 
 
 #if RTC_ENABLE_WEBSOCKET
 #if RTC_ENABLE_WEBSOCKET

+ 3 - 0
include/rtc/rtc.hpp

@@ -16,6 +16,7 @@
 #include "datachannel.hpp"
 #include "datachannel.hpp"
 #include "peerconnection.hpp"
 #include "peerconnection.hpp"
 #include "track.hpp"
 #include "track.hpp"
+#include "iceudpmuxlistener.hpp"
 
 
 #if RTC_ENABLE_WEBSOCKET
 #if RTC_ENABLE_WEBSOCKET
 
 
@@ -33,8 +34,10 @@
 #include "h264rtppacketizer.hpp"
 #include "h264rtppacketizer.hpp"
 #include "h264rtpdepacketizer.hpp"
 #include "h264rtpdepacketizer.hpp"
 #include "h265rtppacketizer.hpp"
 #include "h265rtppacketizer.hpp"
+#include "h265rtpdepacketizer.hpp"
 #include "mediahandler.hpp"
 #include "mediahandler.hpp"
 #include "plihandler.hpp"
 #include "plihandler.hpp"
+#include "rembhandler.hpp"
 #include "pacinghandler.hpp"
 #include "pacinghandler.hpp"
 #include "rtcpnackresponder.hpp"
 #include "rtcpnackresponder.hpp"
 #include "rtcpreceivingsession.hpp"
 #include "rtcpreceivingsession.hpp"

+ 4 - 4
include/rtc/rtcpnackresponder.hpp

@@ -33,8 +33,8 @@ private:
 
 
 		/// Packet storage element
 		/// Packet storage element
 		struct RTC_CPP_EXPORT Element {
 		struct RTC_CPP_EXPORT Element {
-			Element(binary_ptr packet, uint16_t sequenceNumber, shared_ptr<Element> next = nullptr);
-			const binary_ptr packet;
+			Element(message_ptr packet, uint16_t sequenceNumber, shared_ptr<Element> next = nullptr);
+			const message_ptr packet;
 			const uint16_t sequenceNumber;
 			const uint16_t sequenceNumber;
 			/// Pointer to newer element
 			/// Pointer to newer element
 			shared_ptr<Element> next = nullptr;
 			shared_ptr<Element> next = nullptr;
@@ -59,11 +59,11 @@ private:
 		Storage(size_t _maxSize);
 		Storage(size_t _maxSize);
 
 
 		/// Returns packet with given sequence number
 		/// Returns packet with given sequence number
-		optional<binary_ptr> get(uint16_t sequenceNumber);
+		message_ptr get(uint16_t sequenceNumber);
 
 
 		/// Stores packet
 		/// Stores packet
 		/// @param packet Packet
 		/// @param packet Packet
-		void store(binary_ptr packet);
+		void store(message_ptr packet);
 	};
 	};
 
 
 	const shared_ptr<Storage> mStorage;
 	const shared_ptr<Storage> mStorage;

+ 7 - 4
include/rtc/rtcpsrreporter.hpp

@@ -12,17 +12,20 @@
 #if RTC_ENABLE_MEDIA
 #if RTC_ENABLE_MEDIA
 
 
 #include "mediahandler.hpp"
 #include "mediahandler.hpp"
-#include "rtppacketizationconfig.hpp"
 #include "rtp.hpp"
 #include "rtp.hpp"
+#include "rtppacketizationconfig.hpp"
+
+#include <chrono>
 
 
 namespace rtc {
 namespace rtc {
 
 
 class RTC_CPP_EXPORT RtcpSrReporter final : public MediaHandler {
 class RTC_CPP_EXPORT RtcpSrReporter final : public MediaHandler {
 public:
 public:
 	RtcpSrReporter(shared_ptr<RtpPacketizationConfig> rtpConfig);
 	RtcpSrReporter(shared_ptr<RtpPacketizationConfig> rtpConfig);
+	~RtcpSrReporter();
 
 
 	uint32_t lastReportedTimestamp() const;
 	uint32_t lastReportedTimestamp() const;
-	void setNeedsToReport();
+	[[deprecated]] void setNeedsToReport();
 
 
 	void outgoing(message_vector &messages, const message_callback &send) override;
 	void outgoing(message_vector &messages, const message_callback &send) override;
 
 
@@ -30,13 +33,13 @@ public:
 	const shared_ptr<RtpPacketizationConfig> rtpConfig;
 	const shared_ptr<RtpPacketizationConfig> rtpConfig;
 
 
 private:
 private:
-	void addToReport(RtpHeader *rtp, uint32_t rtpSize);
+	void addToReport(RtpHeader *header, size_t size);
 	message_ptr getSenderReport(uint32_t timestamp);
 	message_ptr getSenderReport(uint32_t timestamp);
 
 
 	uint32_t mPacketCount = 0;
 	uint32_t mPacketCount = 0;
 	uint32_t mPayloadOctets = 0;
 	uint32_t mPayloadOctets = 0;
 	uint32_t mLastReportedTimestamp = 0;
 	uint32_t mLastReportedTimestamp = 0;
-	bool mNeedsToReport = false;
+	std::chrono::steady_clock::time_point mLastReportTime;
 };
 };
 
 
 } // namespace rtc
 } // namespace rtc

+ 2 - 0
include/rtc/rtp.hpp

@@ -278,6 +278,8 @@ struct RTC_CPP_EXPORT RtcpRemb {
 	void preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int in_bitrate);
 	void preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int in_bitrate);
 	void setBitrate(unsigned int numSSRC, unsigned int in_bitrate);
 	void setBitrate(unsigned int numSSRC, unsigned int in_bitrate);
 	void setSsrc(int iterator, SSRC newSssrc);
 	void setSsrc(int iterator, SSRC newSssrc);
+	unsigned int getNumSSRC();
+	unsigned int getBitrate();
 };
 };
 
 
 struct RTC_CPP_EXPORT RtcpPli {
 struct RTC_CPP_EXPORT RtcpPli {

+ 6 - 2
include/rtc/rtpdepacketizer.hpp

@@ -18,10 +18,14 @@ namespace rtc {
 
 
 class RTC_CPP_EXPORT RtpDepacketizer : public MediaHandler {
 class RTC_CPP_EXPORT RtpDepacketizer : public MediaHandler {
 public:
 public:
-	RtpDepacketizer() = default;
-	virtual ~RtpDepacketizer() = default;
+	RtpDepacketizer();
+	RtpDepacketizer(uint32_t clockRate);
+	virtual ~RtpDepacketizer();
 
 
 	virtual void incoming(message_vector &messages, const message_callback &send) override;
 	virtual void incoming(message_vector &messages, const message_callback &send) override;
+
+private:
+	uint32_t mClockRate;
 };
 };
 
 
 using OpusRtpDepacketizer = RtpDepacketizer;
 using OpusRtpDepacketizer = RtpDepacketizer;

+ 3 - 3
include/rtc/rtppacketizationconfig.hpp

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

+ 20 - 4
include/rtc/rtppacketizer.hpp

@@ -20,6 +20,12 @@ namespace rtc {
 /// RTP packetizer
 /// RTP packetizer
 class RTC_CPP_EXPORT RtpPacketizer : public MediaHandler {
 class RTC_CPP_EXPORT RtpPacketizer : public MediaHandler {
 public:
 public:
+	/// Default maximum fragment size (for video packetizers)
+	inline static const size_t DefaultMaxFragmentSize = RTC_DEFAULT_MAX_FRAGMENT_SIZE;
+
+	/// Clock rate for video in RTP
+	inline static const uint32_t VideoClockRate = 90 * 1000;
+
 	/// Constructs packetizer with given RTP configuration
 	/// Constructs packetizer with given RTP configuration
 	/// @note RTP configuration is used in packetization process which may change some configuration
 	/// @note RTP configuration is used in packetization process which may change some configuration
 	/// properties such as sequence number.
 	/// properties such as sequence number.
@@ -34,11 +40,19 @@ public:
 	const shared_ptr<RtpPacketizationConfig> rtpConfig;
 	const shared_ptr<RtpPacketizationConfig> rtpConfig;
 
 
 protected:
 protected:
-	/// Creates RTP packet for given payload
-	/// @note This function increase sequence number after packetization.
+	/// Fragment data into payloads
+	/// Default implementation returns data as a single payload
+	/// @param message Input data
+	virtual std::vector<binary> fragment(binary data);
+
+	/// Creates an RTP packet for a payload
+	/// @note This function increases the sequence number.
 	/// @param payload RTP payload
 	/// @param payload RTP payload
-	/// @param setMark Set marker flag in RTP packet if true
-	virtual message_ptr packetize(shared_ptr<binary> payload, bool mark);
+	/// @param mark Set marker flag in RTP packet if true
+	virtual message_ptr packetize(const binary &payload, bool mark);
+
+	// For backward compatibility, do not use
+	[[deprecated]] virtual message_ptr packetize(shared_ptr<binary> payload, bool mark);
 
 
 private:
 private:
 	static const auto RtpHeaderSize = 12;
 	static const auto RtpHeaderSize = 12;
@@ -60,6 +74,8 @@ public:
 // Audio RTP packetizers
 // Audio RTP packetizers
 using OpusRtpPacketizer = AudioRtpPacketizer<48000>;
 using OpusRtpPacketizer = AudioRtpPacketizer<48000>;
 using AACRtpPacketizer = AudioRtpPacketizer<48000>;
 using AACRtpPacketizer = AudioRtpPacketizer<48000>;
+using PCMARtpPacketizer = AudioRtpPacketizer<8000>;
+using PCMURtpPacketizer = AudioRtpPacketizer<8000>;
 
 
 // Dummy wrapper for backward compatibility, do not use
 // Dummy wrapper for backward compatibility, do not use
 class RTC_CPP_EXPORT PacketizationHandler final : public MediaHandler {
 class RTC_CPP_EXPORT PacketizationHandler final : public MediaHandler {

+ 3 - 1
include/rtc/track.hpp

@@ -41,7 +41,9 @@ public:
 	bool isClosed(void) const override;
 	bool isClosed(void) const override;
 	size_t maxMessageSize() const override;
 	size_t maxMessageSize() const override;
 
 
-	void onFrame(std::function<void(binary data, FrameInfo frame)> callback);
+	void sendFrame(binary data, FrameInfo info);
+	void sendFrame(const byte *data, size_t size, FrameInfo info);
+	void onFrame(std::function<void(binary data, FrameInfo info)> callback);
 
 
 	bool requestKeyframe();
 	bool requestKeyframe();
 	bool requestBitrate(unsigned int bitrate);
 	bool requestBitrate(unsigned int bitrate);

+ 3 - 3
include/rtc/version.h

@@ -2,8 +2,8 @@
 #define RTC_VERSION_H
 #define RTC_VERSION_H
 
 
 #define RTC_VERSION_MAJOR 0
 #define RTC_VERSION_MAJOR 0
-#define RTC_VERSION_MINOR 21
-#define RTC_VERSION_PATCH 1
-#define RTC_VERSION "0.21.1"
+#define RTC_VERSION_MINOR 22
+#define RTC_VERSION_PATCH 6
+#define RTC_VERSION "0.22.6"
 
 
 #endif
 #endif

+ 34 - 3
pages/content/pages/reference.md

@@ -210,7 +210,9 @@ Initiates the handshake process. Following this call, the local description call
 Arguments:
 Arguments:
 
 
 - `pc`: the Peer Connection identifier
 - `pc`: the Peer Connection identifier
-- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for automatic (recommended).
+
+Warning: This function expects the optional type for the local description and not an SDP description. It is not possible to set an existing SDP description.
 
 
 #### rtcSetRemoteDescription
 #### rtcSetRemoteDescription
 
 
@@ -223,7 +225,8 @@ Sets the remote description received from the remote peer by the user's method o
 Arguments:
 Arguments:
 
 
 - `pc`: the Peer Connection identifier
 - `pc`: the Peer Connection identifier
-- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+- `sdp`: the remote description in SDP format
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for automatic (not recommended).
 
 
 If the remote description is an offer and `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically answer by calling `rtcSetLocalDescription` internally. Otherwise, the user must call it to answer the remote description.
 If the remote description is an offer and `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically answer by calling `rtcSetLocalDescription` internally. Otherwise, the user must call it to answer the remote description.
 
 
@@ -299,7 +302,7 @@ Return value: the length of the string copied in buffer (including the terminati
 
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
-#### rtcGetRemoteDescription
+#### rtcGetRemoteDescriptionType
 
 
 ```
 ```
 int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
 int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
@@ -317,6 +320,22 @@ Return value: the length of the string copied in buffer (including the terminati
 
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
+#### rtcCreateOffer/rtcCreateAnswer
+
+```
+int rtcCreateOffer(int pc, char *buffer, int size)
+int rtcCreateAnswer(int pc, char *buffer, int size)
+```
+
+Create a local offer or answer description in SDP format. These functions are intended only for specific use cases where the application needs to generate a description without setting it. It is useless to call them before `rtcSetLocalDescription` as it doesn't expect the user to supply a description.
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the description
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
 #### rtcGetLocalAddress
 #### rtcGetLocalAddress
 
 
@@ -374,6 +393,18 @@ Return value: the maximun length of strings copied in buffers (including the ter
 
 
 If `local`, `remote`, or both, are `NULL`, the corresponding candidate is not copied, but the maximum length is still returned.
 If `local`, `remote`, or both, are `NULL`, the corresponding candidate is not copied, but the maximum length is still returned.
 
 
+#### rtcIsNegotiationNeeded
+```
+bool rtcIsNegotiationNeeded(int pc);
+```
+
+Return true if negotiation needs to be started or restarted, for instance to signal new tracks. If so, the user may call `rtcSetLocalDescription()` to start it.
+
+Arguments:
+- `pc`: the Peer Connection identifier
+
+Return value: true if negotiation is needed
+
 #### rtcGetMaxDataChannelStream
 #### rtcGetMaxDataChannelStream
 ```
 ```
 int rtcGetMaxDataChannelStream(int pc);
 int rtcGetMaxDataChannelStream(int pc);

+ 70 - 91
src/av1rtppacketizer.cpp

@@ -12,6 +12,8 @@
 
 
 #include "impl/internals.hpp"
 #include "impl/internals.hpp"
 
 
+#include <algorithm>
+
 namespace rtc {
 namespace rtc {
 
 
 const auto payloadHeaderSize = 1;
 const auto payloadHeaderSize = 1;
@@ -38,34 +40,39 @@ const auto oneByteLeb128Size = 1;
 const uint8_t sevenLsbBitmask = 0b01111111;
 const uint8_t sevenLsbBitmask = 0b01111111;
 const uint8_t msbBitmask = 0b10000000;
 const uint8_t msbBitmask = 0b10000000;
 
 
-std::vector<binary_ptr> extractTemporalUnitObus(binary_ptr message) {
-	std::vector<shared_ptr<binary>> obus{};
+AV1RtpPacketizer::AV1RtpPacketizer(Packetization packetization,
+                                   shared_ptr<RtpPacketizationConfig> rtpConfig,
+                                   size_t maxFragmentSize)
+    : RtpPacketizer(rtpConfig), mPacketization(packetization), mMaxFragmentSize(maxFragmentSize) {}
+
+std::vector<binary> AV1RtpPacketizer::extractTemporalUnitObus(const binary &data) {
+	std::vector<binary> obus;
 
 
-	if (message->size() <= 2 || (message->at(0) != obuTemporalUnitDelimiter.at(0)) ||
-	    (message->at(1) != obuTemporalUnitDelimiter.at(1))) {
-		return obus;
+	if (data.size() <= 2 || (data.at(0) != obuTemporalUnitDelimiter.at(0)) ||
+	    (data.at(1) != obuTemporalUnitDelimiter.at(1))) {
+		return {};
 	}
 	}
 
 
-	size_t messageIndex = 2;
-	while (messageIndex < message->size()) {
-		if ((message->at(messageIndex) & obuHasSizeMask) == byte(0)) {
+	size_t index = 2;
+	while (index < data.size()) {
+		if ((data.at(index) & obuHasSizeMask) == byte(0)) {
 			return obus;
 			return obus;
 		}
 		}
 
 
-		if ((message->at(messageIndex) & obuHasExtensionMask) != byte(0)) {
-			messageIndex++;
+		if ((data.at(index) & obuHasExtensionMask) != byte(0)) {
+			index++;
 		}
 		}
 
 
 		// https://aomediacodec.github.io/av1-spec/#leb128
 		// https://aomediacodec.github.io/av1-spec/#leb128
 		uint32_t obuLength = 0;
 		uint32_t obuLength = 0;
 		uint8_t leb128Size = 0;
 		uint8_t leb128Size = 0;
 		while (leb128Size < 8) {
 		while (leb128Size < 8) {
-			auto leb128Index = messageIndex + leb128Size + obuHeaderSize;
-			if (message->size() < leb128Index) {
+			auto leb128Index = index + leb128Size + obuHeaderSize;
+			if (data.size() < leb128Index) {
 				break;
 				break;
 			}
 			}
 
 
-			auto leb128_byte = uint8_t(message->at(leb128Index));
+			auto leb128_byte = uint8_t(data.at(leb128Index));
 
 
 			obuLength |= ((leb128_byte & sevenLsbBitmask) << (leb128Size * 7));
 			obuLength |= ((leb128_byte & sevenLsbBitmask) << (leb128Size * 7));
 			leb128Size++;
 			leb128Size++;
@@ -75,16 +82,31 @@ std::vector<binary_ptr> extractTemporalUnitObus(binary_ptr message) {
 			}
 			}
 		}
 		}
 
 
-		obus.push_back(std::make_shared<binary>(message->begin() + messageIndex,
-		                                        message->begin() + messageIndex + obuHeaderSize +
-		                                            leb128Size + obuLength));
+		obus.emplace_back(data.begin() + index,
+		                  data.begin() + index + obuHeaderSize + leb128Size + obuLength);
 
 
-		messageIndex += obuHeaderSize + leb128Size + obuLength;
+		index += obuHeaderSize + leb128Size + obuLength;
 	}
 	}
 
 
 	return obus;
 	return obus;
 }
 }
 
 
+std::vector<binary> AV1RtpPacketizer::fragment(binary data) {
+	if (mPacketization == AV1RtpPacketizer::Packetization::TemporalUnit) {
+		std::vector<binary> result;
+		auto obus = extractTemporalUnitObus(data);
+		for (auto obu : obus) {
+			auto fragments = fragmentObu(obu);
+			result.reserve(result.size() + fragments.size());
+			for(auto &fragment : fragments)
+				fragments.push_back(std::move(fragment));
+		}
+		return result;
+	} else {
+		return fragmentObu(data);
+	}
+}
+
 /*
 /*
  *  0 1 2 3 4 5 6 7
  *  0 1 2 3 4 5 6 7
  * +-+-+-+-+-+-+-+-+
  * +-+-+-+-+-+-+-+-+
@@ -116,114 +138,71 @@ std::vector<binary_ptr> extractTemporalUnitObus(binary_ptr message) {
  *
  *
  **/
  **/
 
 
-std::vector<binary_ptr> AV1RtpPacketizer::packetizeObu(binary_ptr message,
-                                                       uint16_t maxFragmentSize) {
-
-	std::vector<shared_ptr<binary>> payloads{};
-	size_t messageIndex = 0;
+std::vector<binary> AV1RtpPacketizer::fragmentObu(const binary &data) {
+	std::vector<binary> payloads;
 
 
-	if (message->size() < 1) {
-		return payloads;
-	}
+	if (data.size() < 1)
+		return {};
 
 
 	// Cache sequence header and packetize with next OBU
 	// Cache sequence header and packetize with next OBU
-	auto frameType = (message->at(0) & obuFrameTypeMask) >> obuFrameTypeBitshift;
+	auto frameType = (data.at(0) & obuFrameTypeMask) >> obuFrameTypeBitshift;
 	if (frameType == obuFrameTypeSequenceHeader) {
 	if (frameType == obuFrameTypeSequenceHeader) {
-		sequenceHeader = std::make_shared<binary>(message->begin(), message->end());
-		return payloads;
+		mSequenceHeader = std::make_unique<binary>(data.begin(), data.end());
+		return {};
 	}
 	}
 
 
-	size_t messageRemaining = message->size();
-	while (messageRemaining > 0) {
-		auto obuCount = 1;
-		auto metadataSize = payloadHeaderSize;
+	size_t index = 0;
+	size_t remaining = data.size();
+	while (remaining > 0) {
+		size_t obuCount = 1;
+		size_t metadataSize = payloadHeaderSize;
 
 
-		if (sequenceHeader != nullptr) {
+		if (mSequenceHeader) {
 			obuCount++;
 			obuCount++;
-			metadataSize += /* 1 byte leb128 */ 1 + int(sequenceHeader->size());
+			metadataSize += 1 + int(mSequenceHeader->size()); // 1 byte leb128
 		}
 		}
 
 
-		auto payload = std::make_shared<binary>(
-		    std::min(size_t(maxFragmentSize), messageRemaining + metadataSize));
-		auto payloadOffset = payloadHeaderSize;
+		binary payload(std::min(size_t(mMaxFragmentSize), remaining + metadataSize));
+		size_t payloadOffset = payloadHeaderSize;
 
 
-		payload->at(0) = byte(obuCount) << wBitshift;
+		payload.at(0) = byte(obuCount) << wBitshift;
 
 
 		// Packetize cached SequenceHeader
 		// Packetize cached SequenceHeader
 		if (obuCount == 2) {
 		if (obuCount == 2) {
-			payload->at(0) ^= nMask;
-			payload->at(1) = byte(sequenceHeader->size() & sevenLsbBitmask);
+			payload.at(0) ^= nMask;
+			payload.at(1) = byte(mSequenceHeader->size() & sevenLsbBitmask);
 			payloadOffset += oneByteLeb128Size;
 			payloadOffset += oneByteLeb128Size;
 
 
-			std::memcpy(payload->data() + payloadOffset, sequenceHeader->data(),
-			            sequenceHeader->size());
-			payloadOffset += int(sequenceHeader->size());
+			std::memcpy(payload.data() + payloadOffset, mSequenceHeader->data(),
+			            mSequenceHeader->size());
+			payloadOffset += int(mSequenceHeader->size());
 
 
-			sequenceHeader = nullptr;
+			mSequenceHeader = nullptr;
 		}
 		}
 
 
 		// Copy as much of OBU as possible into Payload
 		// Copy as much of OBU as possible into Payload
-		auto payloadRemaining = payload->size() - payloadOffset;
-		std::memcpy(payload->data() + payloadOffset, message->data() + messageIndex,
+		size_t payloadRemaining = payload.size() - payloadOffset;
+		std::memcpy(payload.data() + payloadOffset, data.data() + index,
 		            payloadRemaining);
 		            payloadRemaining);
-		messageRemaining -= payloadRemaining;
-		messageIndex += payloadRemaining;
+		remaining -= payloadRemaining;
+		index += payloadRemaining;
 
 
 		// Does this Fragment contain an OBU that started in a previous payload
 		// Does this Fragment contain an OBU that started in a previous payload
 		if (payloads.size() > 0) {
 		if (payloads.size() > 0) {
-			payload->at(0) ^= zMask;
+			payload.at(0) ^= zMask;
 		}
 		}
 
 
 		// This OBU will be continued in next Payload
 		// This OBU will be continued in next Payload
-		if (messageIndex < message->size()) {
-			payload->at(0) ^= yMask;
+		if (index < data.size()) {
+			payload.at(0) ^= yMask;
 		}
 		}
 
 
-		payloads.push_back(payload);
+		payloads.push_back(std::move(payload));
 	}
 	}
 
 
 	return payloads;
 	return payloads;
 }
 }
 
 
-AV1RtpPacketizer::AV1RtpPacketizer(AV1RtpPacketizer::Packetization packetization,
-                                   shared_ptr<RtpPacketizationConfig> rtpConfig,
-                                   uint16_t maxFragmentSize)
-    : RtpPacketizer(rtpConfig), maxFragmentSize(maxFragmentSize), packetization(packetization) {}
-
-void AV1RtpPacketizer::outgoing(message_vector &messages,
-                                [[maybe_unused]] const message_callback &send) {
-	message_vector result;
-	for (const auto &message : messages) {
-		std::vector<binary_ptr> obus;
-		if (packetization == AV1RtpPacketizer::Packetization::TemporalUnit) {
-			obus = extractTemporalUnitObus(message);
-		} else {
-			obus.push_back(message);
-		}
-
-		std::vector<binary_ptr> fragments;
-		for (auto obu : obus) {
-			auto p = packetizeObu(obu, maxFragmentSize);
-			fragments.insert(fragments.end(), p.begin(), p.end());
-		}
-
-		if (fragments.size() == 0)
-			continue;
-
-		for (size_t i = 0; i < fragments.size(); i++) {
-			if (rtpConfig->dependencyDescriptorContext.has_value()) {
-				auto &ctx = *rtpConfig->dependencyDescriptorContext;
-				ctx.descriptor.startOfFrame = i == 0;
-				ctx.descriptor.endOfFrame = i == fragments.size() - 1;
-			}
-			bool mark = i == fragments.size() - 1;
-			result.push_back(packetize(fragments[i], mark));
-		}
-	}
-
-	messages.swap(result);
-}
-
 } // namespace rtc
 } // namespace rtc
 
 
 #endif /* RTC_ENABLE_MEDIA */
 #endif /* RTC_ENABLE_MEDIA */

+ 45 - 13
src/capi.cpp

@@ -631,6 +631,24 @@ int rtcGetRemoteDescriptionType(int pc, char *buffer, int size) {
 	});
 	});
 }
 }
 
 
+int rtcCreateOffer(int pc, char *buffer, int size) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+
+		auto desc = peerConnection->createOffer();
+		return copyAndReturn(string(desc), buffer, size);
+	});
+}
+
+int rtcCreateAnswer(int pc, char *buffer, int size) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+
+		auto desc = peerConnection->createAnswer();
+		return copyAndReturn(string(desc), buffer, size);
+	});
+}
+
 int rtcGetLocalAddress(int pc, char *buffer, int size) {
 int rtcGetLocalAddress(int pc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
@@ -674,6 +692,11 @@ int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote
 	});
 	});
 }
 }
 
 
+bool rtcIsNegotiationNeeded(int pc) {
+	return wrap([&] { return getPeerConnection(pc)->negotiationNeeded() ? 0 : 1; }) == 0 ? true
+	                                                                                     : false;
+}
+
 int rtcGetMaxDataChannelStream(int pc) {
 int rtcGetMaxDataChannelStream(int pc) {
 	return wrap([&] {
 	return wrap([&] {
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
@@ -1331,6 +1354,18 @@ int rtcChainPliHandler(int tr, rtcPliHandlerCallbackFunc cb) {
 	});
 	});
 }
 }
 
 
+int rtcChainRembHandler(int tr, rtcRembHandlerCallbackFunc cb) {
+	return wrap([&] {
+		auto track = getTrack(tr);
+		auto handler = std::make_shared<RembHandler>([tr, cb](unsigned int bitrate) {
+			if (auto ptr = getUserPointer(tr))
+				cb(tr, bitrate, *ptr);
+		});
+		track->chainMediaHandler(handler);
+		return RTC_ERR_SUCCESS;
+	});
+}
+
 int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp) {
 int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp) {
 	return wrap([&] {
 	return wrap([&] {
 		auto config = getRtpConfig(id);
 		auto config = getRtpConfig(id);
@@ -1379,14 +1414,6 @@ int rtcGetLastTrackSenderReportTimestamp(int id, uint32_t *timestamp) {
 	});
 	});
 }
 }
 
 
-int rtcSetNeedsToSendRtcpSr(int id) {
-	return wrap([id] {
-		auto sender = getRtcpSrReporter(id);
-		sender->setNeedsToReport();
-		return RTC_ERR_SUCCESS;
-	});
-}
-
 int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size) {
 int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
 		auto track = getTrack(tr);
 		auto track = getTrack(tr);
@@ -1428,7 +1455,7 @@ int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer,
 		auto oldSDP = string(sdp);
 		auto oldSDP = string(sdp);
 		auto description = Description(oldSDP, "unspec");
 		auto description = Description(oldSDP, "unspec");
 		auto mediaCount = description.mediaCount();
 		auto mediaCount = description.mediaCount();
-		for (unsigned int i = 0; i < mediaCount; i++) {
+		for (int i = 0; i < mediaCount; i++) {
 			if (std::holds_alternative<Description::Media *>(description.media(i))) {
 			if (std::holds_alternative<Description::Media *>(description.media(i))) {
 				auto media = std::get<Description::Media *>(description.media(i));
 				auto media = std::get<Description::Media *>(description.media(i));
 				auto currentMediaType = lowercased(media->type());
 				auto currentMediaType = lowercased(media->type());
@@ -1449,7 +1476,7 @@ int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, cons
 		auto prevSDP = string(sdp);
 		auto prevSDP = string(sdp);
 		auto description = Description(prevSDP, "unspec");
 		auto description = Description(prevSDP, "unspec");
 		auto mediaCount = description.mediaCount();
 		auto mediaCount = description.mediaCount();
-		for (unsigned int i = 0; i < mediaCount; i++) {
+		for (int i = 0; i < mediaCount; i++) {
 			if (std::holds_alternative<Description::Media *>(description.media(i))) {
 			if (std::holds_alternative<Description::Media *>(description.media(i))) {
 				auto media = std::get<Description::Media *>(description.media(i));
 				auto media = std::get<Description::Media *>(description.media(i));
 				auto currentMediaType = lowercased(media->type());
 				auto currentMediaType = lowercased(media->type());
@@ -1463,6 +1490,11 @@ int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, cons
 	});
 	});
 }
 }
 
 
+int rtcSetNeedsToSendRtcpSr(int) {
+	// Dummy
+	return RTC_ERR_SUCCESS;
+}
+
 #endif // RTC_ENABLE_MEDIA
 #endif // RTC_ENABLE_MEDIA
 
 
 #if RTC_ENABLE_WEBSOCKET
 #if RTC_ENABLE_WEBSOCKET
@@ -1546,7 +1578,7 @@ int rtcGetWebSocketPath(int ws, char *buffer, int size) {
 	});
 	});
 }
 }
 
 
-RTC_C_EXPORT int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config,
+int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config,
                                           rtcWebSocketClientCallbackFunc cb) {
                                           rtcWebSocketClientCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
 		if (!config)
 		if (!config)
@@ -1583,7 +1615,7 @@ RTC_C_EXPORT int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config
 	});
 	});
 }
 }
 
 
-RTC_C_EXPORT int rtcDeleteWebSocketServer(int wsserver) {
+int rtcDeleteWebSocketServer(int wsserver) {
 	return wrap([&] {
 	return wrap([&] {
 		auto webSocketServer = getWebSocketServer(wsserver);
 		auto webSocketServer = getWebSocketServer(wsserver);
 		webSocketServer->onClient(nullptr);
 		webSocketServer->onClient(nullptr);
@@ -1593,7 +1625,7 @@ RTC_C_EXPORT int rtcDeleteWebSocketServer(int wsserver) {
 	});
 	});
 }
 }
 
 
-RTC_C_EXPORT int rtcGetWebSocketServerPort(int wsserver) {
+int rtcGetWebSocketServerPort(int wsserver) {
 	return wrap([&] {
 	return wrap([&] {
 		auto webSocketServer = getWebSocketServer(wsserver);
 		auto webSocketServer = getWebSocketServer(wsserver);
 		return int(webSocketServer->port());
 		return int(webSocketServer->port());

+ 30 - 26
src/description.cpp

@@ -216,6 +216,21 @@ void Description::hintType(Type type) {
 		mType = type;
 		mType = type;
 }
 }
 
 
+void Description::addIceOption(string option) {
+	if (std::find(mIceOptions.begin(), mIceOptions.end(), option) == mIceOptions.end())
+		mIceOptions.emplace_back(std::move(option));
+}
+
+void Description::removeIceOption(const string &option) {
+	mIceOptions.erase(std::remove(mIceOptions.begin(), mIceOptions.end(), option),
+	                  mIceOptions.end());
+}
+
+void Description::setIceAttribute(string ufrag, string pwd) {
+	mIceUfrag = std::move(ufrag);
+	mIcePwd = std::move(pwd);
+}
+
 void Description::setFingerprint(CertificateFingerprint f) {
 void Description::setFingerprint(CertificateFingerprint f) {
 	if (!f.isValid())
 	if (!f.isValid())
 		throw std::invalid_argument("Invalid " +
 		throw std::invalid_argument("Invalid " +
@@ -227,16 +242,6 @@ void Description::setFingerprint(CertificateFingerprint f) {
 	mFingerprint = std::move(f);
 	mFingerprint = std::move(f);
 }
 }
 
 
-void Description::addIceOption(string option) {
-	if (std::find(mIceOptions.begin(), mIceOptions.end(), option) == mIceOptions.end())
-		mIceOptions.emplace_back(std::move(option));
-}
-
-void Description::removeIceOption(const string &option) {
-	mIceOptions.erase(std::remove(mIceOptions.begin(), mIceOptions.end(), option),
-	                  mIceOptions.end());
-}
-
 std::vector<string> Description::Entry::attributes() const { return mAttributes; }
 std::vector<string> Description::Entry::attributes() const { return mAttributes; }
 
 
 void Description::Entry::addAttribute(string attr) {
 void Description::Entry::addAttribute(string attr) {
@@ -310,7 +315,6 @@ string Description::generateSdp(string_view eol) const {
 
 
 	// Session-level attributes
 	// Session-level attributes
 	sdp << "a=msid-semantic:WMS *" << eol;
 	sdp << "a=msid-semantic:WMS *" << eol;
-
 	if (!mIceOptions.empty())
 	if (!mIceOptions.empty())
 		sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
 		sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
 	if (mFingerprint)
 	if (mFingerprint)
@@ -372,28 +376,29 @@ string Description::generateApplicationSdp(string_view eol) const {
 	const uint16_t port =
 	const uint16_t port =
 	    cand && cand->isResolved() ? *cand->port() : 9; // Port 9 is the discard protocol
 	    cand && cand->isResolved() ? *cand->port() : 9; // Port 9 is the discard protocol
 
 
+	// Session-level attributes
+	sdp << "a=msid-semantic:WMS *" << eol;
+	if (!mIceOptions.empty())
+		sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
+
+	for (const auto &attr : mAttributes)
+		sdp << "a=" << attr << eol;
+
 	// Application
 	// Application
 	auto app = mApplication ? mApplication : std::make_shared<Application>();
 	auto app = mApplication ? mApplication : std::make_shared<Application>();
 	sdp << app->generateSdp(eol, addr, port);
 	sdp << app->generateSdp(eol, addr, port);
 
 
-	// Session-level attributes
-	sdp << "a=msid-semantic:WMS *" << eol;
+	// Media-level attributes
 	sdp << "a=setup:" << mRole << eol;
 	sdp << "a=setup:" << mRole << eol;
-
 	if (mIceUfrag)
 	if (mIceUfrag)
 		sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
 		sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
 	if (mIcePwd)
 	if (mIcePwd)
 		sdp << "a=ice-pwd:" << *mIcePwd << eol;
 		sdp << "a=ice-pwd:" << *mIcePwd << eol;
-	if (!mIceOptions.empty())
-		sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
 	if (mFingerprint)
 	if (mFingerprint)
 		sdp << "a=fingerprint:"
 		sdp << "a=fingerprint:"
 		    << CertificateFingerprint::AlgorithmIdentifier(mFingerprint->algorithm) << " "
 		    << CertificateFingerprint::AlgorithmIdentifier(mFingerprint->algorithm) << " "
 		    << mFingerprint->value << eol;
 		    << mFingerprint->value << eol;
 
 
-	for (const auto &attr : mAttributes)
-		sdp << "a=" << attr << eol;
-
 	// Candidates
 	// Candidates
 	for (const auto &candidate : mCandidates)
 	for (const auto &candidate : mCandidates)
 		sdp << string(candidate) << eol;
 		sdp << string(candidate) << eol;
@@ -493,8 +498,8 @@ void Description::clearMedia() {
 	mApplication.reset();
 	mApplication.reset();
 }
 }
 
 
-variant<Description::Media *, Description::Application *> Description::media(unsigned int index) {
-	if (index >= mEntries.size())
+variant<Description::Media *, Description::Application *> Description::media(int index) {
+	if (index < 0 || index >= int(mEntries.size()))
 		throw std::out_of_range("Media index out of range");
 		throw std::out_of_range("Media index out of range");
 
 
 	const auto &entry = mEntries[index];
 	const auto &entry = mEntries[index];
@@ -514,9 +519,8 @@ variant<Description::Media *, Description::Application *> Description::media(uns
 	}
 	}
 }
 }
 
 
-variant<const Description::Media *, const Description::Application *>
-Description::media(unsigned int index) const {
-	if (index >= mEntries.size())
+variant<const Description::Media *, const Description::Application *> Description::media(int index) const {
+	if (index < 0 || index >= int(mEntries.size()))
 		throw std::out_of_range("Media index out of range");
 		throw std::out_of_range("Media index out of range");
 
 
 	const auto &entry = mEntries[index];
 	const auto &entry = mEntries[index];
@@ -536,7 +540,7 @@ Description::media(unsigned int index) const {
 	}
 	}
 }
 }
 
 
-unsigned int Description::mediaCount() const { return unsigned(mEntries.size()); }
+int Description::mediaCount() const { return int(mEntries.size()); }
 
 
 Description::Entry::Entry(const string &mline, string mid, Direction dir)
 Description::Entry::Entry(const string &mline, string mid, Direction dir)
     : mMid(std::move(mid)), mDirection(dir) {
     : mMid(std::move(mid)), mDirection(dir) {
@@ -1340,7 +1344,7 @@ std::string CertificateFingerprint::AlgorithmIdentifier(
 	case CertificateFingerprint::Algorithm::Sha256:
 	case CertificateFingerprint::Algorithm::Sha256:
 		return "sha-256";
 		return "sha-256";
 	case CertificateFingerprint::Algorithm::Sha384:
 	case CertificateFingerprint::Algorithm::Sha384:
-		return "sha-256";
+		return "sha-384";
 	case CertificateFingerprint::Algorithm::Sha512:
 	case CertificateFingerprint::Algorithm::Sha512:
 		return "sha-512";
 		return "sha-512";
 	default:
 	default:

+ 1 - 1
src/global.cpp

@@ -88,7 +88,7 @@ std::shared_future<void> Cleanup() { return impl::Init::Instance().cleanup(); }
 
 
 void SetSctpSettings(SctpSettings s) { impl::Init::Instance().setSctpSettings(std::move(s)); }
 void SetSctpSettings(SctpSettings s) { impl::Init::Instance().setSctpSettings(std::move(s)); }
 
 
-RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, LogLevel level) {
+std::ostream &operator<<(std::ostream &out, LogLevel level) {
 	switch (level) {
 	switch (level) {
 	case LogLevel::Fatal:
 	case LogLevel::Fatal:
 		out << "fatal";
 		out << "fatal";

+ 7 - 5
src/h264rtpdepacketizer.cpp

@@ -14,6 +14,7 @@
 #include "impl/internals.hpp"
 #include "impl/internals.hpp"
 
 
 #include <algorithm>
 #include <algorithm>
+#include <chrono>
 
 
 namespace rtc {
 namespace rtc {
 
 
@@ -43,9 +44,8 @@ void H264RtpDepacketizer::addSeparator(binary &accessUnit) {
 message_vector H264RtpDepacketizer::buildFrames(message_vector::iterator begin,
 message_vector H264RtpDepacketizer::buildFrames(message_vector::iterator begin,
                                                 message_vector::iterator end, uint8_t payloadType,
                                                 message_vector::iterator end, uint8_t payloadType,
                                                 uint32_t timestamp) {
                                                 uint32_t timestamp) {
-	message_vector out = {};
-	auto accessUnit = binary{};
-	auto frameInfo = std::make_shared<FrameInfo>(payloadType, timestamp);
+	message_vector out;
+	binary accessUnit;
 
 
 	for (auto it = begin; it != end; ++it) {
 	for (auto it = begin; it != end; ++it) {
 		auto pkt = it->get();
 		auto pkt = it->get();
@@ -109,8 +109,10 @@ message_vector H264RtpDepacketizer::buildFrames(message_vector::iterator begin,
 	}
 	}
 
 
 	if (!accessUnit.empty()) {
 	if (!accessUnit.empty()) {
-		out.emplace_back(
-		    make_message(std::move(accessUnit), Message::Binary, 0, nullptr, frameInfo));
+		auto frameInfo = std::make_shared<FrameInfo>(timestamp);
+		frameInfo->timestampSeconds = std::chrono::duration<double>(double(timestamp) / double(ClockRate));
+		frameInfo->payloadType = payloadType;
+		out.emplace_back(make_message(std::move(accessUnit), frameInfo));
 	}
 	}
 
 
 	return out;
 	return out;

+ 35 - 49
src/h264rtppacketizer.cpp

@@ -23,37 +23,50 @@
 
 
 namespace rtc {
 namespace rtc {
 
 
-shared_ptr<NalUnits> H264RtpPacketizer::splitMessage(binary_ptr message) {
-	auto nalus = std::make_shared<NalUnits>();
-	if (separator == Separator::Length) {
+H264RtpPacketizer::H264RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
+                                     size_t maxFragmentSize)
+    : RtpPacketizer(std::move(rtpConfig)), mSeparator(Separator::Length), mMaxFragmentSize(maxFragmentSize) {}
+
+H264RtpPacketizer::H264RtpPacketizer(Separator separator,
+                                     shared_ptr<RtpPacketizationConfig> rtpConfig,
+                                     size_t maxFragmentSize)
+    : RtpPacketizer(rtpConfig), mSeparator(separator), mMaxFragmentSize(maxFragmentSize) {}
+
+std::vector<binary> H264RtpPacketizer::fragment(binary data) {
+	return NalUnit::GenerateFragments(splitFrame(data), mMaxFragmentSize);
+}
+
+std::vector<NalUnit> H264RtpPacketizer::splitFrame(const binary &frame) {
+	std::vector<NalUnit> nalus;
+	if (mSeparator == Separator::Length) {
 		size_t index = 0;
 		size_t index = 0;
-		while (index < message->size()) {
-			assert(index + 4 < message->size());
-			if (index + 4 >= message->size()) {
+		while (index < frame.size()) {
+			assert(index + 4 < frame.size());
+			if (index + 4 >= frame.size()) {
 				LOG_WARNING << "Invalid NAL Unit data (incomplete length), ignoring!";
 				LOG_WARNING << "Invalid NAL Unit data (incomplete length), ignoring!";
 				break;
 				break;
 			}
 			}
 			uint32_t length;
 			uint32_t length;
-			std::memcpy(&length, message->data() + index, sizeof(uint32_t));
+			std::memcpy(&length, frame.data() + index, sizeof(uint32_t));
 			length = ntohl(length);
 			length = ntohl(length);
 			auto naluStartIndex = index + 4;
 			auto naluStartIndex = index + 4;
 			auto naluEndIndex = naluStartIndex + length;
 			auto naluEndIndex = naluStartIndex + length;
 
 
-			assert(naluEndIndex <= message->size());
-			if (naluEndIndex > message->size()) {
+			assert(naluEndIndex <= frame.size());
+			if (naluEndIndex > frame.size()) {
 				LOG_WARNING << "Invalid NAL Unit data (incomplete unit), ignoring!";
 				LOG_WARNING << "Invalid NAL Unit data (incomplete unit), ignoring!";
 				break;
 				break;
 			}
 			}
-			auto begin = message->begin() + naluStartIndex;
-			auto end = message->begin() + naluEndIndex;
-			nalus->push_back(std::make_shared<NalUnit>(begin, end));
+			auto begin = frame.begin() + naluStartIndex;
+			auto end = frame.begin() + naluEndIndex;
+			nalus.emplace_back(begin, end);
 			index = naluEndIndex;
 			index = naluEndIndex;
 		}
 		}
 	} else {
 	} else {
 		NalUnitStartSequenceMatch match = NUSM_noMatch;
 		NalUnitStartSequenceMatch match = NUSM_noMatch;
 		size_t index = 0;
 		size_t index = 0;
-		while (index < message->size()) {
-			match = NalUnit::StartSequenceMatchSucc(match, (*message)[index++], separator);
+		while (index < frame.size()) {
+			match = NalUnit::StartSequenceMatchSucc(match, frame[index++], mSeparator);
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 				match = NUSM_noMatch;
 				match = NUSM_noMatch;
 				break;
 				break;
@@ -62,53 +75,26 @@ shared_ptr<NalUnits> H264RtpPacketizer::splitMessage(binary_ptr message) {
 
 
 		size_t naluStartIndex = index;
 		size_t naluStartIndex = index;
 
 
-		while (index < message->size()) {
-			match = NalUnit::StartSequenceMatchSucc(match, (*message)[index], separator);
+		while (index < frame.size()) {
+			match = NalUnit::StartSequenceMatchSucc(match, frame[index], mSeparator);
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 				auto sequenceLength = match == NUSM_longMatch ? 4 : 3;
 				auto sequenceLength = match == NUSM_longMatch ? 4 : 3;
 				size_t naluEndIndex = index - sequenceLength;
 				size_t naluEndIndex = index - sequenceLength;
 				match = NUSM_noMatch;
 				match = NUSM_noMatch;
-				auto begin = message->begin() + naluStartIndex;
-				auto end = message->begin() + naluEndIndex + 1;
-				nalus->push_back(std::make_shared<NalUnit>(begin, end));
+				auto begin = frame.begin() + naluStartIndex;
+				auto end = frame.begin() + naluEndIndex + 1;
+				nalus.emplace_back(begin, end);
 				naluStartIndex = index + 1;
 				naluStartIndex = index + 1;
 			}
 			}
 			index++;
 			index++;
 		}
 		}
-		auto begin = message->begin() + naluStartIndex;
-		auto end = message->end();
-		nalus->push_back(std::make_shared<NalUnit>(begin, end));
+		auto begin = frame.begin() + naluStartIndex;
+		auto end = frame.end();
+		nalus.emplace_back(begin, end);
 	}
 	}
 	return nalus;
 	return nalus;
 }
 }
 
 
-H264RtpPacketizer::H264RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
-                                     uint16_t maxFragmentSize)
-    : RtpPacketizer(std::move(rtpConfig)), maxFragmentSize(maxFragmentSize),
-      separator(Separator::Length) {}
-
-H264RtpPacketizer::H264RtpPacketizer(Separator separator,
-                                     shared_ptr<RtpPacketizationConfig> rtpConfig,
-                                     uint16_t maxFragmentSize)
-    : RtpPacketizer(rtpConfig), maxFragmentSize(maxFragmentSize), separator(separator) {}
-
-void H264RtpPacketizer::outgoing(message_vector &messages, [[maybe_unused]] const message_callback &send) {
-	message_vector result;
-	for(const auto &message : messages) {
-		auto nalus = splitMessage(message);
-		auto fragments = nalus->generateFragments(maxFragmentSize);
-		if (fragments.size() == 0)
-			continue;
-
-		for (size_t i = 0; i < fragments.size() - 1; i++)
-			result.push_back(packetize(fragments[i], false));
-
-		result.push_back(packetize(fragments[fragments.size() - 1], true));
-	}
-
-	messages.swap(result);
-}
-
 } // namespace rtc
 } // namespace rtc
 
 
 #endif /* RTC_ENABLE_MEDIA */
 #endif /* RTC_ENABLE_MEDIA */

+ 60 - 34
src/h265nalunit.cpp

@@ -16,35 +16,39 @@
 
 
 namespace rtc {
 namespace rtc {
 
 
-H265NalUnitFragment::H265NalUnitFragment(FragmentType type, bool forbiddenBit, uint8_t nuhLayerId,
-                                         uint8_t nuhTempIdPlus1, uint8_t unitType, binary data)
-    : H265NalUnit(data.size() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE) {
-	setForbiddenBit(forbiddenBit);
-	setNuhLayerId(nuhLayerId);
-	setNuhTempIdPlus1(nuhTempIdPlus1);
-	fragmentIndicator()->setUnitType(H265NalUnitFragment::nal_type_fu);
-	setFragmentType(type);
-	setUnitType(unitType);
-	copy(data.begin(), data.end(), begin() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE);
+std::vector<binary> H265NalUnit::GenerateFragments(const std::vector<H265NalUnit> &nalus,
+                                                   size_t maxFragmentSize) {
+	std::vector<binary> result;
+	for (auto nalu : nalus) {
+		if (nalu.size() > maxFragmentSize) {
+			auto fragments = nalu.generateFragments(maxFragmentSize);
+			result.insert(result.end(), fragments.begin(), fragments.end());
+		} else {
+			// TODO: check
+			result.push_back(nalu);
+		}
+	}
+	return result;
 }
 }
 
 
-std::vector<shared_ptr<H265NalUnitFragment>>
-H265NalUnitFragment::fragmentsFrom(shared_ptr<H265NalUnit> nalu, uint16_t maxFragmentSize) {
-	assert(nalu->size() > maxFragmentSize);
-	auto fragments_count = ceil(double(nalu->size()) / maxFragmentSize);
-	maxFragmentSize = uint16_t(int(ceil(nalu->size() / fragments_count)));
+std::vector<H265NalUnitFragment> H265NalUnit::generateFragments(size_t maxFragmentSize) const {
+	// TODO: check
+	assert(size() > maxFragmentSize);
+	auto fragments_count = ceil(double(size()) / maxFragmentSize);
+	maxFragmentSize = uint16_t(int(ceil(size() / fragments_count)));
 
 
 	// 3 bytes for FU indicator and FU header
 	// 3 bytes for FU indicator and FU header
 	maxFragmentSize -= (H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE);
 	maxFragmentSize -= (H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE);
-	auto f = nalu->forbiddenBit();
-	uint8_t nuhLayerId = nalu->nuhLayerId() & 0x3F;        // 6 bits
-	uint8_t nuhTempIdPlus1 = nalu->nuhTempIdPlus1() & 0x7; // 3 bits
-	uint8_t naluType = nalu->unitType() & 0x3F;            // 6 bits
-	auto payload = nalu->payload();
-	vector<shared_ptr<H265NalUnitFragment>> result{};
+	bool forbiddenBit = this->forbiddenBit();
+	uint8_t nuhLayerId = this->nuhLayerId() & 0x3F;        // 6 bits
+	uint8_t nuhTempIdPlus1 = this->nuhTempIdPlus1() & 0x7; // 3 bits
+	uint8_t naluType = this->unitType() & 0x3F;            // 6 bits
+	auto payload = this->payload();
+	vector<H265NalUnitFragment> result;
 	uint64_t offset = 0;
 	uint64_t offset = 0;
 	while (offset < payload.size()) {
 	while (offset < payload.size()) {
 		vector<byte> fragmentData;
 		vector<byte> fragmentData;
+		using FragmentType = H265NalUnitFragment::FragmentType;
 		FragmentType fragmentType;
 		FragmentType fragmentType;
 		if (offset == 0) {
 		if (offset == 0) {
 			fragmentType = FragmentType::Start;
 			fragmentType = FragmentType::Start;
@@ -57,14 +61,36 @@ H265NalUnitFragment::fragmentsFrom(shared_ptr<H265NalUnit> nalu, uint16_t maxFra
 			fragmentType = FragmentType::End;
 			fragmentType = FragmentType::End;
 		}
 		}
 		fragmentData = {payload.begin() + offset, payload.begin() + offset + maxFragmentSize};
 		fragmentData = {payload.begin() + offset, payload.begin() + offset + maxFragmentSize};
-		auto fragment = std::make_shared<H265NalUnitFragment>(
-		    fragmentType, f, nuhLayerId, nuhTempIdPlus1, naluType, fragmentData);
-		result.push_back(fragment);
+		result.emplace_back(fragmentType, forbiddenBit, nuhLayerId, nuhTempIdPlus1, naluType,
+		                    fragmentData);
 		offset += maxFragmentSize;
 		offset += maxFragmentSize;
 	}
 	}
 	return result;
 	return result;
 }
 }
 
 
+H265NalUnitFragment::H265NalUnitFragment(FragmentType type, bool forbiddenBit, uint8_t nuhLayerId,
+                                         uint8_t nuhTempIdPlus1, uint8_t unitType, binary data)
+    : H265NalUnit(data.size() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE) {
+	setForbiddenBit(forbiddenBit);
+	setNuhLayerId(nuhLayerId);
+	setNuhTempIdPlus1(nuhTempIdPlus1);
+	fragmentIndicator()->setUnitType(H265NalUnitFragment::nal_type_fu);
+	setFragmentType(type);
+	setUnitType(unitType);
+	copy(data.begin(), data.end(), begin() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE);
+}
+
+std::vector<shared_ptr<H265NalUnitFragment>>
+H265NalUnitFragment::fragmentsFrom(shared_ptr<H265NalUnit> nalu, uint16_t maxFragmentSize) {
+	auto fragments = nalu->generateFragments(maxFragmentSize);
+	std::vector<shared_ptr<H265NalUnitFragment>> result;
+	result.reserve(fragments.size());
+	for (auto fragment : fragments)
+		result.push_back(std::make_shared<H265NalUnitFragment>(std::move(fragment)));
+
+	return result;
+}
+
 void H265NalUnitFragment::setFragmentType(FragmentType type) {
 void H265NalUnitFragment::setFragmentType(FragmentType type) {
 	switch (type) {
 	switch (type) {
 	case FragmentType::Start:
 	case FragmentType::Start:
@@ -82,16 +108,16 @@ void H265NalUnitFragment::setFragmentType(FragmentType type) {
 }
 }
 
 
 std::vector<shared_ptr<binary>> H265NalUnits::generateFragments(uint16_t maxFragmentSize) {
 std::vector<shared_ptr<binary>> H265NalUnits::generateFragments(uint16_t maxFragmentSize) {
-	vector<shared_ptr<binary>> result{};
-	for (auto nalu : *this) {
-		if (nalu->size() > maxFragmentSize) {
-			std::vector<shared_ptr<H265NalUnitFragment>> fragments =
-			    H265NalUnitFragment::fragmentsFrom(nalu, maxFragmentSize);
-			result.insert(result.end(), fragments.begin(), fragments.end());
-		} else {
-			result.push_back(nalu);
-		}
-	}
+	std::vector<H265NalUnit> nalus;
+	for (auto nalu : *this)
+		nalus.push_back(*nalu);
+
+	auto fragments = H265NalUnit::GenerateFragments(nalus, maxFragmentSize);
+	std::vector<shared_ptr<binary>> result;
+	result.reserve(fragments.size());
+	for (auto fragment : fragments)
+		result.push_back(std::make_shared<binary>(std::move(fragment)));
+
 	return result;
 	return result;
 }
 }
 
 

+ 194 - 0
src/h265rtpdepacketizer.cpp

@@ -0,0 +1,194 @@
+/**
+ * Copyright (c) 2023-2024 Paul-Louis Ageneau
+ * Copyright (c) 2024 Robert Edmonds
+ *
+ * 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 "h265rtpdepacketizer.hpp"
+#include "h265nalunit.hpp"
+
+#include "impl/internals.hpp"
+
+#include <algorithm>
+
+namespace rtc {
+
+const binary naluLongStartCode = {byte{0}, byte{0}, byte{0}, byte{1}};
+const binary naluShortStartCode = {byte{0}, byte{0}, byte{1}};
+
+const uint8_t naluTypeAP = 48;
+const uint8_t naluTypeFU = 49;
+
+H265RtpDepacketizer::H265RtpDepacketizer(Separator separator) : separator(separator) {
+	switch (separator) {
+	case Separator::StartSequence: [[fallthrough]];
+	case Separator::LongStartSequence: [[fallthrough]];
+	case Separator::ShortStartSequence:
+		break;
+	case Separator::Length: [[fallthrough]];
+	default:
+		throw std::invalid_argument("Invalid separator");
+	}
+}
+
+void H265RtpDepacketizer::addSeparator(binary& accessUnit)
+{
+	switch (separator) {
+	case Separator::StartSequence: [[fallthrough]];
+	case Separator::LongStartSequence:
+		accessUnit.insert(accessUnit.end(),
+		                  naluLongStartCode.begin(),
+		                  naluLongStartCode.end());
+		break;
+	case Separator::ShortStartSequence:
+		accessUnit.insert(accessUnit.end(),
+		                  naluShortStartCode.begin(),
+		                  naluShortStartCode.end());
+		break;
+	case Separator::Length: [[fallthrough]];
+	default:
+		throw std::invalid_argument("Invalid separator");
+	}
+}
+
+message_vector H265RtpDepacketizer::buildFrames(message_vector::iterator begin,
+                                                message_vector::iterator end,
+                                                uint8_t payloadType, uint32_t timestamp) {
+	message_vector out;
+	binary accessUnit;
+
+	for (auto it = begin; it != end; ++it) {
+		auto pkt = it->get();
+		auto pktParsed = reinterpret_cast<const rtc::RtpHeader *>(pkt->data());
+		auto rtpHeaderSize = pktParsed->getSize() + pktParsed->getExtensionHeaderSize();
+		auto rtpPaddingSize = 0;
+
+		if (pktParsed->padding()) {
+			rtpPaddingSize = std::to_integer<uint8_t>(pkt->at(pkt->size() - 1));
+		}
+
+		if (pkt->size() == rtpHeaderSize + rtpPaddingSize) {
+			PLOG_VERBOSE << "H.265 RTP packet has empty payload";
+			continue;
+		}
+
+		auto nalUnitHeader =
+		    H265NalUnitHeader{std::to_integer<uint8_t>(pkt->at(rtpHeaderSize)),
+		                      std::to_integer<uint8_t>(pkt->at(rtpHeaderSize + 1))};
+
+		if (nalUnitHeader.unitType() == naluTypeFU) {
+			auto nalUnitFragmentHeader = H265NalUnitFragmentHeader{
+			    std::to_integer<uint8_t>(pkt->at(rtpHeaderSize + sizeof(H265NalUnitHeader)))};
+
+			// RFC 7798: "When set to 1, the S bit indicates the start of a fragmented
+			// NAL unit, i.e., the first byte of the FU payload is also the first byte of
+			// the payload of the fragmented NAL unit. When the FU payload is not the start
+			// of the fragmented NAL unit payload, the S bit MUST be set to 0."
+			if (nalUnitFragmentHeader.isStart() || accessUnit.empty()) {
+				addSeparator(accessUnit);
+				nalUnitHeader.setUnitType(nalUnitFragmentHeader.unitType());
+				accessUnit.emplace_back(byte(nalUnitHeader._first));
+				accessUnit.emplace_back(byte(nalUnitHeader._second));
+			}
+
+			accessUnit.insert(accessUnit.end(),
+			                  pkt->begin() + rtpHeaderSize + sizeof(H265NalUnitHeader) +
+			                      sizeof(H265NalUnitFragmentHeader),
+			                  pkt->end());
+		} else if (nalUnitHeader.unitType() == naluTypeAP) {
+			auto currOffset = rtpHeaderSize + sizeof(H265NalUnitHeader);
+
+			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 += sizeof(uint16_t);
+
+				if (pkt->size() < currOffset + naluSize) {
+					throw std::runtime_error("H265 AP declared size is larger than buffer");
+				}
+
+				addSeparator(accessUnit);
+				accessUnit.insert(accessUnit.end(), pkt->begin() + currOffset,
+				                  pkt->begin() + currOffset + naluSize);
+
+				currOffset += naluSize;
+			}
+		} else if (nalUnitHeader.unitType() < naluTypeAP) {
+			// "NAL units with NAL unit type values in the range of 0 to 47, inclusive, may be
+			// passed to the decoder."
+			addSeparator(accessUnit);
+			accessUnit.insert(accessUnit.end(), pkt->begin() + rtpHeaderSize, pkt->end());
+		} else {
+			// "NAL-unit-like structures with NAL unit type values in the range of 48 to 63,
+			// inclusive, MUST NOT be passed to the decoder."
+		}
+	}
+
+	if (!accessUnit.empty()) {
+		auto frameInfo = std::make_shared<FrameInfo>(timestamp);
+		frameInfo->timestampSeconds = std::chrono::duration<double>(double(timestamp) / double(ClockRate));
+		frameInfo->payloadType = payloadType;
+		out.emplace_back(make_message(std::move(accessUnit), frameInfo));
+	}
+
+	return out;
+}
+
+void H265RtpDepacketizer::incoming(message_vector &messages, const message_callback &) {
+	messages.erase(std::remove_if(messages.begin(), messages.end(),
+	                              [&](message_ptr message) {
+		                              if (message->type == Message::Control) {
+			                              return false;
+		                              }
+
+		                              if (message->size() < sizeof(RtpHeader)) {
+			                              PLOG_VERBOSE << "RTP packet is too small, size="
+			                                           << message->size();
+			                              return true;
+		                              }
+
+		                              mRtpBuffer.push_back(std::move(message));
+		                              return true;
+	                              }),
+	               messages.end());
+
+	while (mRtpBuffer.size() != 0) {
+		uint8_t payload_type = 0;
+		uint32_t current_timestamp = 0;
+		size_t packets_in_timestamp = 0;
+
+		for (const auto &pkt : mRtpBuffer) {
+			auto p = reinterpret_cast<const rtc::RtpHeader *>(pkt->data());
+
+			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;
+			}
+
+			packets_in_timestamp++;
+		}
+
+		if (packets_in_timestamp == mRtpBuffer.size()) {
+			break;
+		}
+
+		auto begin = mRtpBuffer.begin();
+		auto end = mRtpBuffer.begin() + (packets_in_timestamp - 1);
+
+		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);
+	}
+}
+
+} // namespace rtc
+
+#endif // RTC_ENABLE_MEDIA

+ 36 - 50
src/h265rtppacketizer.cpp

@@ -22,37 +22,51 @@
 
 
 namespace rtc {
 namespace rtc {
 
 
-shared_ptr<H265NalUnits> H265RtpPacketizer::splitMessage(binary_ptr message) {
-	auto nalus = std::make_shared<H265NalUnits>();
-	if (separator == NalUnit::Separator::Length) {
+H265RtpPacketizer::H265RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
+                                     size_t maxFragmentSize)
+    : RtpPacketizer(std::move(rtpConfig)), mSeparator(NalUnit::Separator::Length),
+      mMaxFragmentSize(maxFragmentSize) {}
+
+H265RtpPacketizer::H265RtpPacketizer(NalUnit::Separator separator,
+                                     shared_ptr<RtpPacketizationConfig> rtpConfig,
+                                     size_t maxFragmentSize)
+    : RtpPacketizer(std::move(rtpConfig)), mSeparator(separator), mMaxFragmentSize(maxFragmentSize) {}
+
+std::vector<binary> H265RtpPacketizer::fragment(binary data) {
+	return H265NalUnit::GenerateFragments(splitFrame(data), mMaxFragmentSize);
+}
+
+std::vector<H265NalUnit> H265RtpPacketizer::splitFrame(const binary &frame) {
+	std::vector<H265NalUnit> nalus;
+	if (mSeparator == NalUnit::Separator::Length) {
 		size_t index = 0;
 		size_t index = 0;
-		while (index < message->size()) {
-			assert(index + 4 < message->size());
-			if (index + 4 >= message->size()) {
+		while (index < frame.size()) {
+			assert(index + 4 < frame.size());
+			if (index + 4 >= frame.size()) {
 				LOG_WARNING << "Invalid NAL Unit data (incomplete length), ignoring!";
 				LOG_WARNING << "Invalid NAL Unit data (incomplete length), ignoring!";
 				break;
 				break;
 			}
 			}
 			uint32_t length;
 			uint32_t length;
-			std::memcpy(&length, message->data() + index, sizeof(uint32_t));
+			std::memcpy(&length, frame.data() + index, sizeof(uint32_t));
 			length = ntohl(length);
 			length = ntohl(length);
 			auto naluStartIndex = index + 4;
 			auto naluStartIndex = index + 4;
 			auto naluEndIndex = naluStartIndex + length;
 			auto naluEndIndex = naluStartIndex + length;
 
 
-			assert(naluEndIndex <= message->size());
-			if (naluEndIndex > message->size()) {
+			assert(naluEndIndex <= frame.size());
+			if (naluEndIndex > frame.size()) {
 				LOG_WARNING << "Invalid NAL Unit data (incomplete unit), ignoring!";
 				LOG_WARNING << "Invalid NAL Unit data (incomplete unit), ignoring!";
 				break;
 				break;
 			}
 			}
-			auto begin = message->begin() + naluStartIndex;
-			auto end = message->begin() + naluEndIndex;
-			nalus->push_back(std::make_shared<H265NalUnit>(begin, end));
+			auto begin = frame.begin() + naluStartIndex;
+			auto end = frame.begin() + naluEndIndex;
+			nalus.emplace_back(begin, end);
 			index = naluEndIndex;
 			index = naluEndIndex;
 		}
 		}
 	} else {
 	} else {
 		NalUnitStartSequenceMatch match = NUSM_noMatch;
 		NalUnitStartSequenceMatch match = NUSM_noMatch;
 		size_t index = 0;
 		size_t index = 0;
-		while (index < message->size()) {
-			match = NalUnit::StartSequenceMatchSucc(match, (*message)[index++], separator);
+		while (index < frame.size()) {
+			match = NalUnit::StartSequenceMatchSucc(match, frame[index++], mSeparator);
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 				match = NUSM_noMatch;
 				match = NUSM_noMatch;
 				break;
 				break;
@@ -61,54 +75,26 @@ shared_ptr<H265NalUnits> H265RtpPacketizer::splitMessage(binary_ptr message) {
 
 
 		size_t naluStartIndex = index;
 		size_t naluStartIndex = index;
 
 
-		while (index < message->size()) {
-			match = NalUnit::StartSequenceMatchSucc(match, (*message)[index], separator);
+		while (index < frame.size()) {
+			match = NalUnit::StartSequenceMatchSucc(match, frame[index], mSeparator);
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 				auto sequenceLength = match == NUSM_longMatch ? 4 : 3;
 				auto sequenceLength = match == NUSM_longMatch ? 4 : 3;
 				size_t naluEndIndex = index - sequenceLength;
 				size_t naluEndIndex = index - sequenceLength;
 				match = NUSM_noMatch;
 				match = NUSM_noMatch;
-				auto begin = message->begin() + naluStartIndex;
-				auto end = message->begin() + naluEndIndex + 1;
-				nalus->push_back(std::make_shared<H265NalUnit>(begin, end));
+				auto begin = frame.begin() + naluStartIndex;
+				auto end = frame.begin() + naluEndIndex + 1;
+				nalus.emplace_back(begin, end);
 				naluStartIndex = index + 1;
 				naluStartIndex = index + 1;
 			}
 			}
 			index++;
 			index++;
 		}
 		}
-		auto begin = message->begin() + naluStartIndex;
-		auto end = message->end();
-		nalus->push_back(std::make_shared<H265NalUnit>(begin, end));
+		auto begin = frame.begin() + naluStartIndex;
+		auto end = frame.end();
+		nalus.emplace_back(begin, end);
 	}
 	}
 	return nalus;
 	return nalus;
 }
 }
 
 
-H265RtpPacketizer::H265RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
-                                     uint16_t maxFragmentSize)
-    : RtpPacketizer(std::move(rtpConfig)), maxFragmentSize(maxFragmentSize),
-      separator(NalUnit::Separator::Length) {}
-
-H265RtpPacketizer::H265RtpPacketizer(NalUnit::Separator separator,
-                                     shared_ptr<RtpPacketizationConfig> rtpConfig,
-                                     uint16_t maxFragmentSize)
-    : RtpPacketizer(std::move(rtpConfig)), maxFragmentSize(maxFragmentSize),
-      separator(separator) {}
-
-void H265RtpPacketizer::outgoing(message_vector &messages, [[maybe_unused]] const message_callback &send) {
-	message_vector result;
-	for (const auto &message : messages) {
-		auto nalus = splitMessage(message);
-		auto fragments = nalus->generateFragments(maxFragmentSize);
-		if (fragments.size() == 0)
-			continue;
-
-		for (size_t i = 0; i < fragments.size() - 1; i++)
-			result.push_back(packetize(fragments[i], false));
-
-		result.push_back(packetize(fragments[fragments.size() - 1], true));
-	}
-
-	messages.swap(result);
-}
-
 } // namespace rtc
 } // namespace rtc
 
 
 #endif /* RTC_ENABLE_MEDIA */
 #endif /* RTC_ENABLE_MEDIA */

+ 29 - 0
src/iceudpmuxlistener.cpp

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2025 Alex Potsides
+ * Copyright (c) 2025 Paul-Louis Ageneau
+ *
+ * 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/.
+ */
+
+#include "iceudpmuxlistener.hpp"
+
+#include "impl/iceudpmuxlistener.hpp"
+
+namespace rtc {
+
+IceUdpMuxListener::IceUdpMuxListener(uint16_t port, optional<string> bindAddress)
+    : CheshireCat<impl::IceUdpMuxListener>(port, std::move(bindAddress)) {}
+
+IceUdpMuxListener::~IceUdpMuxListener() {}
+
+void IceUdpMuxListener::stop() { impl()->stop(); }
+
+uint16_t IceUdpMuxListener::port() const { return impl()->port; }
+
+void IceUdpMuxListener::OnUnhandledStunRequest(std::function<void(IceUdpMuxRequest)> callback) {
+	impl()->unhandledStunRequestCallback = callback;
+}
+
+} // namespace rtc

+ 32 - 8
src/impl/certificate.cpp

@@ -9,6 +9,7 @@
 #include "certificate.hpp"
 #include "certificate.hpp"
 #include "threadpool.hpp"
 #include "threadpool.hpp"
 
 
+#include <algorithm>
 #include <cassert>
 #include <cassert>
 #include <chrono>
 #include <chrono>
 #include <iomanip>
 #include <iomanip>
@@ -384,9 +385,16 @@ Certificate Certificate::FromString(string crt_pem, string key_pem) {
 	BIO *bio = BIO_new(BIO_s_mem());
 	BIO *bio = BIO_new(BIO_s_mem());
 	BIO_write(bio, crt_pem.data(), int(crt_pem.size()));
 	BIO_write(bio, crt_pem.data(), int(crt_pem.size()));
 	auto x509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free);
 	auto x509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free);
-	BIO_free(bio);
-	if (!x509)
+	if (!x509) {
+		BIO_free(bio);
 		throw std::invalid_argument("Unable to import PEM certificate");
 		throw std::invalid_argument("Unable to import PEM certificate");
+	}
+	std::vector<shared_ptr<X509>> chain;
+	while (auto extra =
+	           shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free)) {
+		chain.push_back(std::move(extra));
+	}
+	BIO_free(bio);
 
 
 	bio = BIO_new(BIO_s_mem());
 	bio = BIO_new(BIO_s_mem());
 	BIO_write(bio, key_pem.data(), int(key_pem.size()));
 	BIO_write(bio, key_pem.data(), int(key_pem.size()));
@@ -396,7 +404,7 @@ Certificate Certificate::FromString(string crt_pem, string key_pem) {
 	if (!pkey)
 	if (!pkey)
 		throw std::invalid_argument("Unable to import PEM key");
 		throw std::invalid_argument("Unable to import PEM key");
 
 
-	return Certificate(x509, pkey);
+	return Certificate(x509, pkey, std::move(chain));
 }
 }
 
 
 Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_pem_file,
 Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_pem_file,
@@ -408,9 +416,16 @@ Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_
 		throw std::invalid_argument("Unable to open PEM certificate file");
 		throw std::invalid_argument("Unable to open PEM certificate file");
 
 
 	auto x509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free);
 	auto x509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free);
-	BIO_free(bio);
-	if (!x509)
+	if (!x509) {
+		BIO_free(bio);
 		throw std::invalid_argument("Unable to import PEM certificate from file");
 		throw std::invalid_argument("Unable to import PEM certificate from file");
+	}
+	std::vector<shared_ptr<X509>> chain;
+	while (auto extra =
+	           shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free)) {
+		chain.push_back(std::move(extra));
+	}
+	BIO_free(bio);
 
 
 	bio = openssl::BIO_new_from_file(key_pem_file);
 	bio = openssl::BIO_new_from_file(key_pem_file);
 	if (!bio)
 	if (!bio)
@@ -423,7 +438,7 @@ Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_
 	if (!pkey)
 	if (!pkey)
 		throw std::invalid_argument("Unable to import PEM key from file");
 		throw std::invalid_argument("Unable to import PEM key from file");
 
 
-	return Certificate(x509, pkey);
+	return Certificate(x509, pkey, std::move(chain));
 }
 }
 
 
 Certificate Certificate::Generate(CertificateType type, const string &commonName) {
 Certificate Certificate::Generate(CertificateType type, const string &commonName) {
@@ -514,14 +529,23 @@ Certificate Certificate::Generate(CertificateType type, const string &commonName
 	return Certificate(x509, pkey);
 	return Certificate(x509, pkey);
 }
 }
 
 
-Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey)
-    : mX509(std::move(x509)), mPKey(std::move(pkey)),
+Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey,
+                         std::vector<shared_ptr<X509>> chain)
+    : mX509(std::move(x509)), mPKey(std::move(pkey)), mChain(std::move(chain)),
       mFingerprint(make_fingerprint(mX509.get(), CertificateFingerprint::Algorithm::Sha256)) {}
       mFingerprint(make_fingerprint(mX509.get(), CertificateFingerprint::Algorithm::Sha256)) {}
 
 
 std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const {
 std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const {
 	return {mX509.get(), mPKey.get()};
 	return {mX509.get(), mPKey.get()};
 }
 }
 
 
+std::vector<X509 *> Certificate::chain() const {
+	std::vector<X509 *> v;
+	v.reserve(mChain.size());
+	std::transform(mChain.begin(), mChain.end(), std::back_inserter(v),
+	               [](const auto &c) { return c.get(); });
+	return v;
+}
+
 string make_fingerprint(X509 *x509, CertificateFingerprint::Algorithm fingerprintAlgorithm) {
 string make_fingerprint(X509 *x509, CertificateFingerprint::Algorithm fingerprintAlgorithm) {
 	size_t size = CertificateFingerprint::AlgorithmSize(fingerprintAlgorithm);
 	size_t size = CertificateFingerprint::AlgorithmSize(fingerprintAlgorithm);
 	std::vector<unsigned char> buffer(size);
 	std::vector<unsigned char> buffer(size);

+ 3 - 1
src/impl/certificate.hpp

@@ -34,8 +34,9 @@ public:
 	Certificate(shared_ptr<mbedtls_x509_crt> crt, shared_ptr<mbedtls_pk_context> pk);
 	Certificate(shared_ptr<mbedtls_x509_crt> crt, shared_ptr<mbedtls_pk_context> pk);
 	std::tuple<shared_ptr<mbedtls_x509_crt>, shared_ptr<mbedtls_pk_context>> credentials() const;
 	std::tuple<shared_ptr<mbedtls_x509_crt>, shared_ptr<mbedtls_pk_context>> credentials() const;
 #else // OPENSSL
 #else // OPENSSL
-	Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey);
+	Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey, std::vector<shared_ptr<X509>> chain = {});
 	std::tuple<X509 *, EVP_PKEY *> credentials() const;
 	std::tuple<X509 *, EVP_PKEY *> credentials() const;
+	std::vector<X509 *> chain() const;
 #endif
 #endif
 
 
 	CertificateFingerprint fingerprint() const;
 	CertificateFingerprint fingerprint() const;
@@ -52,6 +53,7 @@ private:
 #else
 #else
 	const shared_ptr<X509> mX509;
 	const shared_ptr<X509> mX509;
 	const shared_ptr<EVP_PKEY> mPKey;
 	const shared_ptr<EVP_PKEY> mPKey;
+	const std::vector<shared_ptr<X509>> mChain;
 #endif
 #endif
 
 
 	const string mFingerprint;
 	const string mFingerprint;

+ 4 - 1
src/impl/datachannel.cpp

@@ -185,9 +185,12 @@ bool DataChannel::outgoing(message_ptr message) {
 		std::shared_lock lock(mMutex);
 		std::shared_lock lock(mMutex);
 		transport = mSctpTransport.lock();
 		transport = mSctpTransport.lock();
 
 
-		if (!transport || mIsClosed)
+		if (mIsClosed)
 			throw std::runtime_error("DataChannel is closed");
 			throw std::runtime_error("DataChannel is closed");
 
 
+		if (!transport)
+			throw std::runtime_error("DataChannel not open");
+
 		if (!mStream.has_value())
 		if (!mStream.has_value())
 			throw std::logic_error("DataChannel has no stream assigned");
 			throw std::logic_error("DataChannel has no stream assigned");
 
 

+ 8 - 4
src/impl/dtlstransport.cpp

@@ -53,7 +53,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr cer
                              verifier_callback verifierCallback, state_callback stateChangeCallback)
                              verifier_callback verifierCallback, state_callback stateChangeCallback)
     : Transport(lower, std::move(stateChangeCallback)), mMtu(mtu), mCertificate(certificate),
     : Transport(lower, std::move(stateChangeCallback)), mMtu(mtu), mCertificate(certificate),
       mFingerprintAlgorithm(fingerprintAlgorithm), mVerifierCallback(std::move(verifierCallback)),
       mFingerprintAlgorithm(fingerprintAlgorithm), mVerifierCallback(std::move(verifierCallback)),
-      mIsClient(lower->role() == Description::Role::Active) {
+      mIsClient(lower->role() == Description::Role::Active),
+      mIncomingQueue(RECV_QUEUE_LIMIT, message_size_func) {
 
 
 	PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)";
 	PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)";
 
 
@@ -380,7 +381,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr cer
                              verifier_callback verifierCallback, state_callback stateChangeCallback)
                              verifier_callback verifierCallback, state_callback stateChangeCallback)
     : Transport(lower, std::move(stateChangeCallback)), mMtu(mtu), mCertificate(certificate),
     : Transport(lower, std::move(stateChangeCallback)), mMtu(mtu), mCertificate(certificate),
       mFingerprintAlgorithm(fingerprintAlgorithm), mVerifierCallback(std::move(verifierCallback)),
       mFingerprintAlgorithm(fingerprintAlgorithm), mVerifierCallback(std::move(verifierCallback)),
-      mIsClient(lower->role() == Description::Role::Active) {
+      mIsClient(lower->role() == Description::Role::Active),
+      mIncomingQueue(RECV_QUEUE_LIMIT, message_size_func) {
 
 
 	PLOG_DEBUG << "Initializing DTLS transport (MbedTLS)";
 	PLOG_DEBUG << "Initializing DTLS transport (MbedTLS)";
 
 
@@ -729,7 +731,9 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr cer
                              verifier_callback verifierCallback, state_callback stateChangeCallback)
                              verifier_callback verifierCallback, state_callback stateChangeCallback)
     : Transport(lower, std::move(stateChangeCallback)), mMtu(mtu), mCertificate(certificate),
     : Transport(lower, std::move(stateChangeCallback)), mMtu(mtu), mCertificate(certificate),
       mFingerprintAlgorithm(fingerprintAlgorithm), mVerifierCallback(std::move(verifierCallback)),
       mFingerprintAlgorithm(fingerprintAlgorithm), mVerifierCallback(std::move(verifierCallback)),
-      mIsClient(lower->role() == Description::Role::Active) {
+      mIsClient(lower->role() == Description::Role::Active),
+      mIncomingQueue(RECV_QUEUE_LIMIT, message_size_func) {
+
 	PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
 	PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
 
 
 	if (!mCertificate)
 	if (!mCertificate)
@@ -757,7 +761,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr cer
 		                   CertificateCallback);
 		                   CertificateCallback);
 		SSL_CTX_set_verify_depth(mCtx, 1);
 		SSL_CTX_set_verify_depth(mCtx, 1);
 
 
-		openssl::check(SSL_CTX_set_cipher_list(mCtx, "ALL:!LOW:!EXP:!RC4:!MD5:@STRENGTH"),
+		openssl::check(SSL_CTX_set_cipher_list(mCtx, "ALL:!SHA256:!SHA384:!aPSK:!ECDSA+SHA1:!ADH:!LOW:!EXP:!MD5:!3DES:!SSLv3:!TLSv1"),
 		               "Failed to set SSL priorities");
 		               "Failed to set SSL priorities");
 
 
 #if OPENSSL_VERSION_NUMBER >= 0x30000000
 #if OPENSSL_VERSION_NUMBER >= 0x30000000

+ 1 - 1
src/impl/dtlstransport.hpp

@@ -78,7 +78,7 @@ protected:
 	mbedtls_ssl_config mConf;
 	mbedtls_ssl_config mConf;
 	mbedtls_ssl_context mSsl;
 	mbedtls_ssl_context mSsl;
 
 
-	std::mutex mSslMutex;
+	std::recursive_mutex mSslMutex;
 
 
 	uint32_t mFinMs = 0, mIntMs = 0;
 	uint32_t mFinMs = 0, mIntMs = 0;
 	std::chrono::time_point<std::chrono::steady_clock> mTimerSetAt;
 	std::chrono::time_point<std::chrono::steady_clock> mTimerSetAt;

+ 42 - 13
src/impl/icetransport.cpp

@@ -140,6 +140,12 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 			addIceServer(server);
 			addIceServer(server);
 }
 }
 
 
+void IceTransport::setIceAttributes(string uFrag, string pwd) {
+	if (juice_set_local_ice_attributes(mAgent.get(), uFrag.c_str(), pwd.c_str()) < 0) {
+		throw std::invalid_argument("Invalid ICE attributes");
+	}
+}
+
 void IceTransport::addIceServer(IceServer server) {
 void IceTransport::addIceServer(IceServer server) {
 	if (server.hostname.empty())
 	if (server.hostname.empty())
 		return;
 		return;
@@ -149,6 +155,11 @@ void IceTransport::addIceServer(IceServer server) {
 		return;
 		return;
 	}
 	}
 
 
+	if (server.relayType != IceServer::RelayType::TurnUdp) {
+		PLOG_WARNING << "TURN transports TCP and TLS are not supported with libjuice";
+		return;
+	}
+
 	if (mTurnServersAdded >= MAX_TURN_SERVERS_COUNT)
 	if (mTurnServersAdded >= MAX_TURN_SERVERS_COUNT)
 		return;
 		return;
 
 
@@ -381,8 +392,22 @@ void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
 
 
 #else // USE_NICE == 1
 #else // USE_NICE == 1
 
 
-unique_ptr<GMainLoop, void (*)(GMainLoop *)> IceTransport::MainLoop(nullptr, nullptr);
-std::thread IceTransport::MainLoopThread;
+IceTransport::MainLoopWrapper *IceTransport::MainLoop = nullptr;
+
+IceTransport::MainLoopWrapper::MainLoopWrapper()
+    : mMainLoop(g_main_loop_new(nullptr, FALSE), g_main_loop_unref) {
+	if (!mMainLoop)
+		throw std::runtime_error("Failed to create the glib main loop");
+
+	mThread = std::thread(g_main_loop_run, mMainLoop.get());
+}
+
+IceTransport::MainLoopWrapper::~MainLoopWrapper() {
+	g_main_loop_quit(mMainLoop.get());
+	mThread.join();
+}
+
+GMainLoop *IceTransport::MainLoopWrapper::get() const { return mMainLoop.get(); }
 
 
 void IceTransport::Init() {
 void IceTransport::Init() {
 	g_log_set_handler("libnice", G_LOG_LEVEL_MASK, LogCallback, nullptr);
 	g_log_set_handler("libnice", G_LOG_LEVEL_MASK, LogCallback, nullptr);
@@ -391,17 +416,12 @@ void IceTransport::Init() {
 		nice_debug_enable(false); // do not output STUN debug messages
 		nice_debug_enable(false); // do not output STUN debug messages
 	}
 	}
 
 
-	MainLoop = decltype(MainLoop)(g_main_loop_new(nullptr, FALSE), g_main_loop_unref);
-	if (!MainLoop)
-		throw std::runtime_error("Failed to create the main loop");
-
-	MainLoopThread = std::thread(g_main_loop_run, MainLoop.get());
+	MainLoop = new MainLoopWrapper;
 }
 }
 
 
 void IceTransport::Cleanup() {
 void IceTransport::Cleanup() {
-	g_main_loop_quit(MainLoop.get());
-	MainLoopThread.join();
-	MainLoop.reset();
+	delete MainLoop;
+	MainLoop = nullptr;
 }
 }
 
 
 static void closeNiceAgentCallback(GObject *niceAgent, GAsyncResult *, gpointer) {
 static void closeNiceAgentCallback(GObject *niceAgent, GAsyncResult *, gpointer) {
@@ -436,7 +456,7 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 	// Create agent
 	// Create agent
 	mNiceAgent = decltype(mNiceAgent)(
 	mNiceAgent = decltype(mNiceAgent)(
 	    nice_agent_new_full(
 	    nice_agent_new_full(
-	        g_main_loop_get_context(MainLoop.get()),
+	        g_main_loop_get_context(MainLoop->get()),
 	        NICE_COMPATIBILITY_RFC5245, // RFC 5245 was obsoleted by RFC 8445 but this should be OK
 	        NICE_COMPATIBILITY_RFC5245, // RFC 5245 was obsoleted by RFC 8445 but this should be OK
 	        flags),
 	        flags),
 	    closeNiceAgent);
 	    closeNiceAgent);
@@ -461,6 +481,10 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 	// the characteristics of the associated data.
 	// the characteristics of the associated data.
 	g_object_set(G_OBJECT(mNiceAgent.get()), "stun-pacing-timer", 25, nullptr);
 	g_object_set(G_OBJECT(mNiceAgent.get()), "stun-pacing-timer", 25, nullptr);
 
 
+	// Enable RFC 7675 ICE consent freshness support (requires libnice 0.1.19)
+	g_object_set(G_OBJECT(mNiceAgent.get()), "keepalive-conncheck", TRUE, nullptr);
+	g_object_set(G_OBJECT(mNiceAgent.get()), "consent-freshness", TRUE, nullptr);
+
 	g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
 	g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
 	g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr);
 	g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr);
 
 
@@ -565,10 +589,15 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 	nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1, config.portRangeBegin,
 	nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1, config.portRangeBegin,
 	                          config.portRangeEnd);
 	                          config.portRangeEnd);
 
 
-	nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(MainLoop.get()),
+	nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(MainLoop->get()),
 	                       RecvCallback, this);
 	                       RecvCallback, this);
 }
 }
 
 
+void IceTransport::setIceAttributes([[maybe_unused]] string uFrag, [[maybe_unused]] string pwd) {
+	PLOG_WARNING
+	    << "Setting custom ICE attributes is not supported with libnice, please use libjuice";
+}
+
 void IceTransport::addIceServer(IceServer server) {
 void IceTransport::addIceServer(IceServer server) {
 	if (server.hostname.empty())
 	if (server.hostname.empty())
 		return;
 		return;
@@ -628,7 +657,7 @@ void IceTransport::addIceServer(IceServer server) {
 
 
 IceTransport::~IceTransport() {
 IceTransport::~IceTransport() {
 	PLOG_DEBUG << "Destroying ICE transport";
 	PLOG_DEBUG << "Destroying ICE transport";
-	nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(MainLoop.get()),
+	nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(MainLoop->get()),
 	                       NULL, NULL);
 	                       NULL, NULL);
 	nice_agent_remove_stream(mNiceAgent.get(), mStreamId);
 	nice_agent_remove_stream(mNiceAgent.get(), mStreamId);
 	mNiceAgent.reset();
 	mNiceAgent.reset();

+ 12 - 2
src/impl/icetransport.hpp

@@ -51,6 +51,7 @@ public:
 	void setRemoteDescription(const Description &description);
 	void setRemoteDescription(const Description &description);
 	bool addRemoteCandidate(const Candidate &candidate);
 	bool addRemoteCandidate(const Candidate &candidate);
 	void gatherLocalCandidates(string mid, std::vector<IceServer> additionalIceServers = {});
 	void gatherLocalCandidates(string mid, std::vector<IceServer> additionalIceServers = {});
+	void setIceAttributes(string uFrag, string pwd);
 
 
 	optional<string> getLocalAddress() const;
 	optional<string> getLocalAddress() const;
 	optional<string> getRemoteAddress() const;
 	optional<string> getRemoteAddress() const;
@@ -89,8 +90,17 @@ private:
 	static void RecvCallback(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
 	static void RecvCallback(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
 	static void LogCallback(juice_log_level_t level, const char *message);
 	static void LogCallback(juice_log_level_t level, const char *message);
 #else
 #else
-	static unique_ptr<GMainLoop, void (*)(GMainLoop *)> MainLoop;
-	static std::thread MainLoopThread;
+	class MainLoopWrapper {
+	public:
+		MainLoopWrapper();
+		~MainLoopWrapper();
+		GMainLoop *get() const;
+
+	private:
+		unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop;
+		std::thread mThread;
+	};
+	static MainLoopWrapper *MainLoop;
 
 
 	unique_ptr<NiceAgent, void (*)(NiceAgent *)> mNiceAgent;
 	unique_ptr<NiceAgent, void (*)(NiceAgent *)> mNiceAgent;
 	uint32_t mStreamId = 0;
 	uint32_t mStreamId = 0;

+ 62 - 0
src/impl/iceudpmuxlistener.cpp

@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2025 Alex Potsides
+ * Copyright (c) 2025 Paul-Louis Ageneau
+ *
+ * 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/.
+ */
+
+#include "iceudpmuxlistener.hpp"
+#include "internals.hpp"
+
+namespace rtc::impl {
+
+#if !USE_NICE
+void IceUdpMuxListener::UnhandledStunRequestCallback(const juice_mux_binding_request *info,
+                                                     void *user_ptr) {
+	auto listener = static_cast<IceUdpMuxListener *>(user_ptr);
+	if (!listener)
+		return;
+
+	IceUdpMuxRequest request;
+	request.localUfrag = info->local_ufrag;
+	request.remoteUfrag = info->remote_ufrag;
+	request.remoteAddress = info->address;
+	request.remotePort = info->port;
+	listener->unhandledStunRequestCallback(std::move(request));
+}
+#endif
+
+IceUdpMuxListener::IceUdpMuxListener(uint16_t port, [[maybe_unused]] optional<string> bindAddress) : port(port) {
+	PLOG_VERBOSE << "Creating IceUdpMuxListener";
+
+#if !USE_NICE
+	PLOG_DEBUG << "Registering ICE UDP mux listener for port " << port;
+	if (juice_mux_listen(bindAddress ? bindAddress->c_str() : NULL, port,
+	                     IceUdpMuxListener::UnhandledStunRequestCallback, this) < 0) {
+		throw std::runtime_error("Failed to register ICE UDP mux listener");
+	}
+#else
+	PLOG_WARNING << "ICE UDP mux is not available with libnice";
+#endif
+}
+
+IceUdpMuxListener::~IceUdpMuxListener() {
+	PLOG_VERBOSE << "Destroying IceUdpMuxListener";
+	stop();
+}
+
+void IceUdpMuxListener::stop() {
+	if (mStopped.exchange(true))
+		return;
+
+#if !USE_NICE
+	PLOG_DEBUG << "Unregistering ICE UDP mux listener for port " << port;
+	if (juice_mux_listen(NULL, port, NULL, NULL) < 0) {
+		PLOG_ERROR << "Failed to unregister ICE UDP mux listener";
+	}
+#endif
+}
+
+} // namespace rtc::impl

+ 45 - 0
src/impl/iceudpmuxlistener.hpp

@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2025 Alex Potsides
+ * Copyright (c) 2025 Paul-Louis Ageneau
+ *
+ * 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_IMPL_ICE_UDP_MUX_LISTENER_H
+#define RTC_IMPL_ICE_UDP_MUX_LISTENER_H
+
+#include "common.hpp"
+
+#include "rtc/iceudpmuxlistener.hpp"
+
+#if !USE_NICE
+#include <juice/juice.h>
+#endif
+
+#include <atomic>
+
+namespace rtc::impl {
+
+struct IceUdpMuxListener final {
+	IceUdpMuxListener(uint16_t port, optional<string> bindAddress = nullopt);
+	~IceUdpMuxListener();
+
+	void stop();
+
+	const uint16_t port;
+	synchronized_callback<IceUdpMuxRequest> unhandledStunRequestCallback;
+
+private:
+#if !USE_NICE
+	static void UnhandledStunRequestCallback(const juice_mux_binding_request *info, void *user_ptr);
+#endif
+
+	std::atomic<bool> mStopped;
+};
+
+}
+
+#endif
+

+ 206 - 120
src/impl/peerconnection.cpp

@@ -46,24 +46,22 @@ static LogCounter
 
 
 const string PemBeginCertificateTag = "-----BEGIN CERTIFICATE-----";
 const string PemBeginCertificateTag = "-----BEGIN CERTIFICATE-----";
 
 
-PeerConnection::PeerConnection(Configuration config_)
-    : config(std::move(config_)) {
+PeerConnection::PeerConnection(Configuration config_) : config(std::move(config_)) {
 	PLOG_VERBOSE << "Creating PeerConnection";
 	PLOG_VERBOSE << "Creating PeerConnection";
 
 
-
 	if (config.certificatePemFile && config.keyPemFile) {
 	if (config.certificatePemFile && config.keyPemFile) {
 		std::promise<certificate_ptr> cert;
 		std::promise<certificate_ptr> cert;
 		cert.set_value(std::make_shared<Certificate>(
 		cert.set_value(std::make_shared<Certificate>(
-			config.certificatePemFile->find(PemBeginCertificateTag) != string::npos
-				? Certificate::FromString(*config.certificatePemFile, *config.keyPemFile)
-				: Certificate::FromFile(*config.certificatePemFile, *config.keyPemFile,
-										config.keyPemPass.value_or(""))));
+		    config.certificatePemFile->find(PemBeginCertificateTag) != string::npos
+		        ? Certificate::FromString(*config.certificatePemFile, *config.keyPemFile)
+		        : Certificate::FromFile(*config.certificatePemFile, *config.keyPemFile,
+		                                config.keyPemPass.value_or(""))));
 		mCertificate = cert.get_future();
 		mCertificate = cert.get_future();
 	} else if (!config.certificatePemFile && !config.keyPemFile) {
 	} else if (!config.certificatePemFile && !config.keyPemFile) {
 		mCertificate = make_certificate(config.certificateType);
 		mCertificate = make_certificate(config.certificateType);
 	} else {
 	} else {
 		throw std::invalid_argument(
 		throw std::invalid_argument(
-			"Either none or both certificate and key PEM files must be specified");
+		    "Either none or both certificate and key PEM files must be specified");
 	}
 	}
 
 
 	if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
 	if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
@@ -87,7 +85,6 @@ PeerConnection::~PeerConnection() {
 }
 }
 
 
 void PeerConnection::close() {
 void PeerConnection::close() {
-	negotiationNeeded = false;
 	if (!closing.exchange(true)) {
 	if (!closing.exchange(true)) {
 		PLOG_VERBOSE << "Closing PeerConnection";
 		PLOG_VERBOSE << "Closing PeerConnection";
 		if (auto transport = std::atomic_load(&mSctpTransport))
 		if (auto transport = std::atomic_load(&mSctpTransport))
@@ -229,9 +226,13 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
 
 
 		PLOG_VERBOSE << "Starting DTLS transport";
 		PLOG_VERBOSE << "Starting DTLS transport";
 
 
-		auto fingerprintAlgorithm = CertificateFingerprint::Algorithm::Sha256;
-		if (auto remote = remoteDescription(); remote && remote->fingerprint()) {
-			fingerprintAlgorithm = remote->fingerprint()->algorithm;
+		CertificateFingerprint::Algorithm fingerprintAlgorithm;
+		{
+			std::lock_guard lock(mRemoteDescriptionMutex);
+			if (mRemoteDescription && mRemoteDescription->fingerprint()) {
+				mRemoteFingerprintAlgorithm = mRemoteDescription->fingerprint()->algorithm;
+			}
+			fingerprintAlgorithm = mRemoteFingerprintAlgorithm;
 		}
 		}
 
 
 		auto lower = std::atomic_load(&mIceTransport);
 		auto lower = std::atomic_load(&mIceTransport);
@@ -439,21 +440,27 @@ void PeerConnection::rollbackLocalDescription() {
 	}
 	}
 }
 }
 
 
-bool PeerConnection::checkFingerprint(const std::string &fingerprint) const {
+bool PeerConnection::checkFingerprint(const std::string &fingerprint) {
 	std::lock_guard lock(mRemoteDescriptionMutex);
 	std::lock_guard lock(mRemoteDescriptionMutex);
-	if (!mRemoteDescription || !mRemoteDescription->fingerprint())
+	mRemoteFingerprint = fingerprint;
+
+	if (!mRemoteDescription || !mRemoteDescription->fingerprint()
+			|| mRemoteFingerprintAlgorithm != mRemoteDescription->fingerprint()->algorithm)
 		return false;
 		return false;
 
 
-	if (config.disableFingerprintVerification)
+	if (config.disableFingerprintVerification) {
+		PLOG_VERBOSE << "Skipping fingerprint validation";
 		return true;
 		return true;
+	}
 
 
 	auto expectedFingerprint = mRemoteDescription->fingerprint()->value;
 	auto expectedFingerprint = mRemoteDescription->fingerprint()->value;
-	if (expectedFingerprint  == fingerprint) {
+	if (expectedFingerprint == fingerprint) {
 		PLOG_VERBOSE << "Valid fingerprint \"" << fingerprint << "\"";
 		PLOG_VERBOSE << "Valid fingerprint \"" << fingerprint << "\"";
 		return true;
 		return true;
 	}
 	}
 
 
-	PLOG_ERROR << "Invalid fingerprint \"" << fingerprint << "\", expected \"" << expectedFingerprint << "\"";
+	PLOG_ERROR << "Invalid fingerprint \"" << fingerprint << "\", expected \""
+	           << expectedFingerprint << "\"";
 	return false;
 	return false;
 }
 }
 
 
@@ -531,11 +538,16 @@ void PeerConnection::forwardMedia([[maybe_unused]] message_ptr message) {
 	if (auto handler = getMediaHandler()) {
 	if (auto handler = getMediaHandler()) {
 		message_vector messages{std::move(message)};
 		message_vector messages{std::move(message)};
 
 
-		handler->incoming(messages, [this](message_ptr message) {
-			auto transport = std::atomic_load(&mDtlsTransport);
-			if (auto srtpTransport = std::dynamic_pointer_cast<DtlsSrtpTransport>(transport))
-				srtpTransport->send(std::move(message));
-		});
+		try {
+			handler->incomingChain(messages, [this](message_ptr message) {
+				auto transport = std::atomic_load(&mDtlsTransport);
+				if (auto srtpTransport = std::dynamic_pointer_cast<DtlsSrtpTransport>(transport))
+					srtpTransport->send(std::move(message));
+			});
+		} catch(const std::exception &e) {
+			PLOG_WARNING << "Exception in global incoming media handler: " << e.what();
+			return;
+		}
 
 
 		for (auto &m : messages)
 		for (auto &m : messages)
 			dispatchMedia(std::move(m));
 			dispatchMedia(std::move(m));
@@ -549,7 +561,7 @@ void PeerConnection::forwardMedia([[maybe_unused]] message_ptr message) {
 void PeerConnection::dispatchMedia([[maybe_unused]] message_ptr message) {
 void PeerConnection::dispatchMedia([[maybe_unused]] message_ptr message) {
 #if RTC_ENABLE_MEDIA
 #if RTC_ENABLE_MEDIA
 	std::shared_lock lock(mTracksMutex); // read-only
 	std::shared_lock lock(mTracksMutex); // read-only
-	if (mTrackLines.size()==1) {
+	if (mTrackLines.size() == 1) {
 		if (auto track = mTrackLines.front().lock())
 		if (auto track = mTrackLines.front().lock())
 			track->incoming(message);
 			track->incoming(message);
 		return;
 		return;
@@ -736,7 +748,7 @@ void PeerConnection::iterateDataChannels(
 	{
 	{
 		std::shared_lock lock(mDataChannelsMutex); // read-only
 		std::shared_lock lock(mDataChannelsMutex); // read-only
 		locked.reserve(mDataChannels.size());
 		locked.reserve(mDataChannels.size());
-		for(auto it = mDataChannels.begin(); it != mDataChannels.end(); ++it) {
+		for (auto it = mDataChannels.begin(); it != mDataChannels.end(); ++it) {
 			auto channel = it->second.lock();
 			auto channel = it->second.lock();
 			if (channel && !channel->isClosed())
 			if (channel && !channel->isClosed())
 				locked.push_back(std::move(channel));
 				locked.push_back(std::move(channel));
@@ -805,7 +817,7 @@ void PeerConnection::iterateTracks(std::function<void(shared_ptr<Track> track)>
 	{
 	{
 		std::shared_lock lock(mTracksMutex); // read-only
 		std::shared_lock lock(mTracksMutex); // read-only
 		locked.reserve(mTrackLines.size());
 		locked.reserve(mTrackLines.size());
-		for(auto it = mTrackLines.begin(); it != mTrackLines.end(); ++it) {
+		for (auto it = mTrackLines.begin(); it != mTrackLines.end(); ++it) {
 			auto track = it->lock();
 			auto track = it->lock();
 			if (track && !track->isClosed())
 			if (track && !track->isClosed())
 				locked.push_back(std::move(track));
 				locked.push_back(std::move(track));
@@ -821,27 +833,58 @@ void PeerConnection::iterateTracks(std::function<void(shared_ptr<Track> track)>
 	}
 	}
 }
 }
 
 
+void PeerConnection::iterateRemoteTracks(std::function<void(shared_ptr<Track> track)> func) {
+	auto remote = remoteDescription();
+	if(!remote)
+		return;
+
+	std::vector<shared_ptr<Track>> locked;
+	{
+		std::shared_lock lock(mTracksMutex); // read-only
+		locked.reserve(remote->mediaCount());
+		for(int i = 0; i < remote->mediaCount(); ++i) {
+			if (std::holds_alternative<Description::Media *>(remote->media(i))) {
+				auto remoteMedia = std::get<Description::Media *>(remote->media(i));
+				if (!remoteMedia->isRemoved())
+					if (auto it = mTracks.find(remoteMedia->mid()); it != mTracks.end())
+						if (auto track = it->second.lock())
+							locked.push_back(std::move(track));
+			}
+		}
+	}
+
+	for (auto &track : locked) {
+		try {
+			func(std::move(track));
+		} catch (const std::exception &e) {
+			PLOG_WARNING << e.what();
+		}
+	}
+}
+
+
 void PeerConnection::openTracks() {
 void PeerConnection::openTracks() {
 #if RTC_ENABLE_MEDIA
 #if RTC_ENABLE_MEDIA
-	if (auto transport = std::atomic_load(&mDtlsTransport)) {
-		auto srtpTransport = std::dynamic_pointer_cast<DtlsSrtpTransport>(transport);
-
-		iterateTracks([&](const shared_ptr<Track> &track) {
-			if (!track->isOpen()) {
-				if (srtpTransport) {
-					track->open(srtpTransport);
-				} else {
-					// A track was added during a latter renegotiation, whereas SRTP transport was
-					// not initialized. This is an optimization to use the library with data
-					// channels only. Set forceMediaTransport to true to initialize the transport
-					// before dynamically adding tracks.
-					auto errorMsg = "The connection has no media transport";
-					PLOG_ERROR << errorMsg;
-					track->triggerError(errorMsg);
-				}
+	auto transport = std::atomic_load(&mDtlsTransport);
+	if (!transport)
+		return;
+
+	auto srtpTransport = std::dynamic_pointer_cast<DtlsSrtpTransport>(transport);
+	iterateRemoteTracks([&](shared_ptr<Track> track) {
+		if(!track->isOpen()) {
+			if (srtpTransport) {
+				track->open(srtpTransport);
+			} else {
+				// A track was added during a latter renegotiation, whereas SRTP transport was
+				// not initialized. This is an optimization to use the library with data
+				// channels only. Set forceMediaTransport to true to initialize the transport
+				// before dynamically adding tracks.
+				auto errorMsg = "The connection has no media transport";
+				PLOG_ERROR << errorMsg;
+				track->triggerError(errorMsg);
 			}
 			}
-		});
-	}
+		}
+	});
 #endif
 #endif
 }
 }
 
 
@@ -864,7 +907,7 @@ void PeerConnection::validateRemoteDescription(const Description &description) {
 		throw std::invalid_argument("Remote description has no media line");
 		throw std::invalid_argument("Remote description has no media line");
 
 
 	int activeMediaCount = 0;
 	int activeMediaCount = 0;
-	for (unsigned int i = 0; i < description.mediaCount(); ++i)
+	for (int i = 0; i < description.mediaCount(); ++i)
 		std::visit(rtc::overloaded{[&](const Description::Application *application) {
 		std::visit(rtc::overloaded{[&](const Description::Application *application) {
 			                           if (!application->isRemoved())
 			                           if (!application->isRemoved())
 				                           ++activeMediaCount;
 				                           ++activeMediaCount;
@@ -882,7 +925,7 @@ void PeerConnection::validateRemoteDescription(const Description &description) {
 	PLOG_VERBOSE << "Remote description looks valid";
 	PLOG_VERBOSE << "Remote description looks valid";
 }
 }
 
 
-void PeerConnection::processLocalDescription(Description description) {
+void PeerConnection::populateLocalDescription(Description &description) const {
 	const uint16_t localSctpPort = DEFAULT_SCTP_PORT;
 	const uint16_t localSctpPort = DEFAULT_SCTP_PORT;
 	const size_t localMaxMessageSize =
 	const size_t localMaxMessageSize =
 	    config.maxMessageSize.value_or(DEFAULT_LOCAL_MAX_MESSAGE_SIZE);
 	    config.maxMessageSize.value_or(DEFAULT_LOCAL_MAX_MESSAGE_SIZE);
@@ -892,7 +935,7 @@ void PeerConnection::processLocalDescription(Description description) {
 
 
 	if (auto remote = remoteDescription()) {
 	if (auto remote = remoteDescription()) {
 		// Reciprocate remote description
 		// Reciprocate remote description
-		for (unsigned int i = 0; i < remote->mediaCount(); ++i)
+		for (int i = 0; i < remote->mediaCount(); ++i) {
 			std::visit( // reciprocate each media
 			std::visit( // reciprocate each media
 			    rtc::overloaded{
 			    rtc::overloaded{
 			        [&](Description::Application *remoteApp) {
 			        [&](Description::Application *remoteApp) {
@@ -907,78 +950,46 @@ void PeerConnection::processLocalDescription(Description description) {
 					                   << app.mid() << "\"";
 					                   << app.mid() << "\"";
 
 
 					        description.addMedia(std::move(app));
 					        description.addMedia(std::move(app));
-					        return;
-				        }
 
 
-				        auto reciprocated = remoteApp->reciprocate();
-				        reciprocated.hintSctpPort(localSctpPort);
-				        reciprocated.setMaxMessageSize(localMaxMessageSize);
+				        } else {
+							auto reciprocated = remoteApp->reciprocate();
+							reciprocated.hintSctpPort(localSctpPort);
+							reciprocated.setMaxMessageSize(localMaxMessageSize);
 
 
-				        PLOG_DEBUG << "Reciprocating application in local description, mid=\""
-				                   << reciprocated.mid() << "\"";
+							PLOG_DEBUG << "Reciprocating application in local description, mid=\""
+								       << reciprocated.mid() << "\"";
 
 
-				        description.addMedia(std::move(reciprocated));
+							description.addMedia(std::move(reciprocated));
+						}
 			        },
 			        },
 			        [&](Description::Media *remoteMedia) {
 			        [&](Description::Media *remoteMedia) {
-				        std::unique_lock lock(mTracksMutex); // we may emplace a track
-				        if (auto it = mTracks.find(remoteMedia->mid()); it != mTracks.end()) {
-					        // Prefer local description
-					        if (auto track = it->second.lock()) {
-						        auto media = track->description();
-
-						        PLOG_DEBUG << "Adding media to local description, mid=\""
-						                   << media.mid() << "\", removed=" << std::boolalpha
-						                   << media.isRemoved();
-
-						        description.addMedia(std::move(media));
-
-					        } else {
-						        auto reciprocated = remoteMedia->reciprocate();
-						        reciprocated.markRemoved();
-
-						        PLOG_DEBUG << "Adding media to local description, mid=\""
-						                   << reciprocated.mid()
-						                   << "\", removed=true (track is destroyed)";
-
-						        description.addMedia(std::move(reciprocated));
-					        }
-					        return;
-				        }
-
-				        auto reciprocated = remoteMedia->reciprocate();
-#if !RTC_ENABLE_MEDIA
-				        if (!reciprocated.isRemoved()) {
-					        // No media support, mark as removed
-					        PLOG_WARNING << "Rejecting track (not compiled with media support)";
-					        reciprocated.markRemoved();
-				        }
-#endif
+				        std::shared_lock lock(mTracksMutex);
+				        auto it = mTracks.find(remoteMedia->mid());
+					    auto track = it != mTracks.end() ? it->second.lock() : nullptr;
+						if(track) {
+							// Prefer local description
+							auto media = track->description();
 
 
-				        PLOG_DEBUG << "Reciprocating media in local description, mid=\""
-				                   << reciprocated.mid() << "\", removed=" << std::boolalpha
-				                   << reciprocated.isRemoved();
+						    PLOG_DEBUG << "Adding media to local description, mid=\""
+						                << media.mid() << "\", removed=" << std::boolalpha
+						                << media.isRemoved();
 
 
-				        // Create incoming track
-				        auto track =
-				            std::make_shared<Track>(weak_from_this(), std::move(reciprocated));
-				        mTracks.emplace(std::make_pair(track->mid(), track));
-				        mTrackLines.emplace_back(track);
-				        triggerTrack(track); // The user may modify the track description
+						    description.addMedia(std::move(media));
 
 
-				        auto handler = getMediaHandler();
-				        if (handler)
-					        handler->media(track->description());
+					    } else {
+							auto reciprocated = remoteMedia->reciprocate();
+							reciprocated.markRemoved();
 
 
-				        if (track->description().isRemoved())
-					        track->close();
+						    PLOG_DEBUG << "Adding media to local description, mid=\""
+						                << reciprocated.mid()
+						                << "\", removed=true (track is destroyed)";
 
 
-				        description.addMedia(track->description());
+						    description.addMedia(std::move(reciprocated));
+					    }
 			        },
 			        },
 			    },
 			    },
 			    remote->media(i));
 			    remote->media(i));
-
-		// We need to update the SSRC cache for newly-created incoming tracks
-		updateTrackSsrcCache(*remote);
+		}
 	}
 	}
 
 
 	if (description.type() == Description::Type::Offer) {
 	if (description.type() == Description::Type::Offer) {
@@ -1018,23 +1029,18 @@ void PeerConnection::processLocalDescription(Description description) {
 				description.addMedia(std::move(app));
 				description.addMedia(std::move(app));
 			}
 			}
 		}
 		}
-
-		// There might be no media at this point if the user created a Track, deleted it,
-		// then called setLocalDescription().
-		if (description.mediaCount() == 0)
-			throw std::runtime_error("No DataChannel or Track to negotiate");
 	}
 	}
 
 
 	// Set local fingerprint (wait for certificate if necessary)
 	// Set local fingerprint (wait for certificate if necessary)
 	description.setFingerprint(mCertificate.get()->fingerprint());
 	description.setFingerprint(mCertificate.get()->fingerprint());
+}
 
 
+void PeerConnection::processLocalDescription(Description description) {
 	PLOG_VERBOSE << "Issuing local description: " << description;
 	PLOG_VERBOSE << "Issuing local description: " << description;
 
 
 	if (description.mediaCount() == 0)
 	if (description.mediaCount() == 0)
 		throw std::logic_error("Local description has no media line");
 		throw std::logic_error("Local description has no media line");
 
 
-	updateTrackSsrcCache(description);
-
 	{
 	{
 		// Set as local description
 		// Set as local description
 		std::lock_guard lock(mLocalDescriptionMutex);
 		std::lock_guard lock(mLocalDescriptionMutex);
@@ -1051,11 +1057,6 @@ void PeerConnection::processLocalDescription(Description description) {
 
 
 	mProcessor.enqueue(&PeerConnection::trigger<Description>, shared_from_this(),
 	mProcessor.enqueue(&PeerConnection::trigger<Description>, shared_from_this(),
 	                   &localDescriptionCallback, std::move(description));
 	                   &localDescriptionCallback, std::move(description));
-
-	// Reciprocated tracks might need to be open
-	if (auto dtlsTransport = std::atomic_load(&mDtlsTransport);
-	    dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
-		mProcessor.enqueue(&PeerConnection::openTracks, shared_from_this());
 }
 }
 
 
 void PeerConnection::processLocalCandidate(Candidate candidate) {
 void PeerConnection::processLocalCandidate(Candidate candidate) {
@@ -1079,6 +1080,40 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
 }
 }
 
 
 void PeerConnection::processRemoteDescription(Description description) {
 void PeerConnection::processRemoteDescription(Description description) {
+	// Create tracks from remote description
+	for (int i = 0; i < description.mediaCount(); ++i) {
+		if (std::holds_alternative<Description::Media *>(description.media(i))) {
+			auto remoteMedia = std::get<Description::Media *>(description.media(i));
+			std::unique_lock lock(mTracksMutex); // we may emplace a track
+			if (auto it = mTracks.find(remoteMedia->mid()); it != mTracks.end())
+				continue;
+
+			PLOG_DEBUG << "New remote track, mid=\"" << remoteMedia->mid() << "\"";
+
+			auto reciprocated = remoteMedia->reciprocate();
+#if !RTC_ENABLE_MEDIA
+			if (!reciprocated.isRemoved()) {
+				// No media support, mark as removed
+				PLOG_WARNING << "Rejecting track (not compiled with media support)";
+				reciprocated.markRemoved();
+			}
+#endif
+
+			// Create incoming track
+			auto track = std::make_shared<Track>(weak_from_this(), std::move(reciprocated));
+			mTracks.emplace(std::make_pair(track->mid(), track));
+			mTrackLines.emplace_back(track);
+			triggerTrack(track); // The user may modify the track description
+
+			auto handler = getMediaHandler();
+			if (handler)
+				handler->media(track->description());
+
+			if (track->description().isRemoved())
+				track->close();
+		}
+	}
+
 	// Update the SSRC cache for existing tracks
 	// Update the SSRC cache for existing tracks
 	updateTrackSsrcCache(description);
 	updateTrackSsrcCache(description);
 
 
@@ -1094,8 +1129,8 @@ void PeerConnection::processRemoteDescription(Description description) {
 		mRemoteDescription->addCandidates(std::move(existingCandidates));
 		mRemoteDescription->addCandidates(std::move(existingCandidates));
 	}
 	}
 
 
+	auto dtlsTransport = std::atomic_load(&mDtlsTransport);
 	if (description.hasApplication()) {
 	if (description.hasApplication()) {
-		auto dtlsTransport = std::atomic_load(&mDtlsTransport);
 		auto sctpTransport = std::atomic_load(&mSctpTransport);
 		auto sctpTransport = std::atomic_load(&mSctpTransport);
 		if (!sctpTransport && dtlsTransport &&
 		if (!sctpTransport && dtlsTransport &&
 		    dtlsTransport->state() == Transport::State::Connected)
 		    dtlsTransport->state() == Transport::State::Connected)
@@ -1103,6 +1138,10 @@ void PeerConnection::processRemoteDescription(Description description) {
 	} else {
 	} else {
 		mProcessor.enqueue(&PeerConnection::remoteCloseDataChannels, shared_from_this());
 		mProcessor.enqueue(&PeerConnection::remoteCloseDataChannels, shared_from_this());
 	}
 	}
+
+	// Reciprocated tracks might need to be open
+	if (dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
+		mProcessor.enqueue(&PeerConnection::openTracks, shared_from_this());
 }
 }
 
 
 void PeerConnection::processRemoteCandidate(Candidate candidate) {
 void PeerConnection::processRemoteCandidate(Candidate candidate) {
@@ -1148,12 +1187,51 @@ string PeerConnection::localBundleMid() const {
 	return mLocalDescription ? mLocalDescription->bundleMid() : "0";
 	return mLocalDescription ? mLocalDescription->bundleMid() : "0";
 }
 }
 
 
+bool PeerConnection::negotiationNeeded() const {
+	auto description = localDescription();
+
+	{
+		std::shared_lock lock(mDataChannelsMutex);
+		if (!mDataChannels.empty() || !mUnassignedDataChannels.empty())
+			if(!description || !description->hasApplication()) {
+				PLOG_DEBUG << "Negotiation needed for data channels";
+				return true;
+			}
+	}
+
+	{
+		std::shared_lock lock(mTracksMutex);
+		for(const auto &[mid, weakTrack] : mTracks)
+			if (auto track = weakTrack.lock())
+				if (!description || !description->hasMid(track->mid())) {
+					PLOG_DEBUG << "Negotiation needed to add track, mid=" << track->mid();
+					return true;
+				}
+
+		if(description) {
+			for(int i = 0; i < description->mediaCount(); ++i) {
+				if (std::holds_alternative<Description::Media *>(description->media(i))) {
+					auto media = std::get<Description::Media *>(description->media(i));
+					if (!media->isRemoved())
+						if (auto it = mTracks.find(media->mid()); it != mTracks.end())
+							if (auto track = it->second.lock(); !track || track->isClosed()) {
+								PLOG_DEBUG << "Negotiation needed to remove track, mid=" << media->mid();
+								return true;
+							}
+				}
+			}
+		}
+	}
+
+	return false;
+}
+
 void PeerConnection::setMediaHandler(shared_ptr<MediaHandler> handler) {
 void PeerConnection::setMediaHandler(shared_ptr<MediaHandler> handler) {
 	std::unique_lock lock(mMediaHandlerMutex);
 	std::unique_lock lock(mMediaHandlerMutex);
 	mMediaHandler = handler;
 	mMediaHandler = handler;
 }
 }
 
 
-shared_ptr<MediaHandler> PeerConnection::getMediaHandler() {
+shared_ptr<MediaHandler> PeerConnection::getMediaHandler() const {
 	std::shared_lock lock(mMediaHandlerMutex);
 	std::shared_lock lock(mMediaHandlerMutex);
 	return mMediaHandler;
 	return mMediaHandler;
 }
 }
@@ -1301,11 +1379,19 @@ void PeerConnection::resetCallbacks() {
 	trackCallback = nullptr;
 	trackCallback = nullptr;
 }
 }
 
 
+CertificateFingerprint PeerConnection::remoteFingerprint() {
+	std::lock_guard lock(mRemoteDescriptionMutex);
+	if (mRemoteFingerprint)
+		return {CertificateFingerprint{mRemoteFingerprintAlgorithm, *mRemoteFingerprint}};
+	else
+		return {};
+}
+
 void PeerConnection::updateTrackSsrcCache(const Description &description) {
 void PeerConnection::updateTrackSsrcCache(const Description &description) {
 	std::unique_lock lock(mTracksMutex); // for safely writing to mTracksBySsrc
 	std::unique_lock lock(mTracksMutex); // for safely writing to mTracksBySsrc
 
 
 	// Setup SSRC -> Track mapping
 	// Setup SSRC -> Track mapping
-	for (unsigned int i = 0; i < description.mediaCount(); ++i)
+	for (int i = 0; i < description.mediaCount(); ++i)
 		std::visit( // ssrc -> track mapping
 		std::visit( // ssrc -> track mapping
 		    rtc::overloaded{
 		    rtc::overloaded{
 		        [&](Description::Application const *) { return; },
 		        [&](Description::Application const *) { return; },

+ 17 - 8
src/impl/peerconnection.hpp

@@ -53,7 +53,7 @@ struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 
 
 	void endLocalCandidates();
 	void endLocalCandidates();
 	void rollbackLocalDescription();
 	void rollbackLocalDescription();
-	bool checkFingerprint(const std::string &fingerprint) const;
+	bool checkFingerprint(const std::string &fingerprint);
 	void forwardMessage(message_ptr message);
 	void forwardMessage(message_ptr message);
 	void forwardMedia(message_ptr message);
 	void forwardMedia(message_ptr message);
 	void forwardBufferedAmount(uint16_t stream, size_t amount);
 	void forwardBufferedAmount(uint16_t stream, size_t amount);
@@ -70,18 +70,22 @@ struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 
 
 	shared_ptr<Track> emplaceTrack(Description::Media description);
 	shared_ptr<Track> emplaceTrack(Description::Media description);
 	void iterateTracks(std::function<void(shared_ptr<Track> track)> func);
 	void iterateTracks(std::function<void(shared_ptr<Track> track)> func);
+	void iterateRemoteTracks(std::function<void(shared_ptr<Track> track)> func);
 	void openTracks();
 	void openTracks();
 	void closeTracks();
 	void closeTracks();
 
 
 	void validateRemoteDescription(const Description &description);
 	void validateRemoteDescription(const Description &description);
+	void populateLocalDescription(Description &description) const;
 	void processLocalDescription(Description description);
 	void processLocalDescription(Description description);
 	void processLocalCandidate(Candidate candidate);
 	void processLocalCandidate(Candidate candidate);
 	void processRemoteDescription(Description description);
 	void processRemoteDescription(Description description);
 	void processRemoteCandidate(Candidate candidate);
 	void processRemoteCandidate(Candidate candidate);
 	string localBundleMid() const;
 	string localBundleMid() const;
 
 
+	bool negotiationNeeded() const;
+
 	void setMediaHandler(shared_ptr<MediaHandler> handler);
 	void setMediaHandler(shared_ptr<MediaHandler> handler);
-	shared_ptr<MediaHandler> getMediaHandler();
+	shared_ptr<MediaHandler> getMediaHandler() const;
 
 
 	void triggerDataChannel(weak_ptr<DataChannel> weakDataChannel);
 	void triggerDataChannel(weak_ptr<DataChannel> weakDataChannel);
 	void triggerTrack(weak_ptr<Track> weakTrack);
 	void triggerTrack(weak_ptr<Track> weakTrack);
@@ -99,6 +103,8 @@ struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 
 
 	void resetCallbacks();
 	void resetCallbacks();
 
 
+	CertificateFingerprint remoteFingerprint();
+
 	// Helper method for asynchronous callback invocation
 	// Helper method for asynchronous callback invocation
 	template <typename... Args> void trigger(synchronized_callback<Args...> *cb, Args... args) {
 	template <typename... Args> void trigger(synchronized_callback<Args...> *cb, Args... args) {
 		try {
 		try {
@@ -113,7 +119,6 @@ struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 	std::atomic<IceState> iceState = IceState::New;
 	std::atomic<IceState> iceState = IceState::New;
 	std::atomic<GatheringState> gatheringState = GatheringState::New;
 	std::atomic<GatheringState> gatheringState = GatheringState::New;
 	std::atomic<SignalingState> signalingState = SignalingState::Stable;
 	std::atomic<SignalingState> signalingState = SignalingState::Stable;
-	std::atomic<bool> negotiationNeeded = false;
 	std::atomic<bool> closing = false;
 	std::atomic<bool> closing = false;
 	std::mutex signalingMutex;
 	std::mutex signalingMutex;
 
 
@@ -134,12 +139,16 @@ private:
 	future_certificate_ptr mCertificate;
 	future_certificate_ptr mCertificate;
 
 
 	Processor mProcessor;
 	Processor mProcessor;
-	optional<Description> mLocalDescription, mRemoteDescription;
+	optional<Description> mLocalDescription;
 	optional<Description> mCurrentLocalDescription;
 	optional<Description> mCurrentLocalDescription;
-	mutable std::mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
+	mutable std::mutex mLocalDescriptionMutex;
 
 
-	shared_ptr<MediaHandler> mMediaHandler;
+	optional<Description> mRemoteDescription;
+	CertificateFingerprint::Algorithm mRemoteFingerprintAlgorithm = CertificateFingerprint::Algorithm::Sha256;
+	optional<string> mRemoteFingerprint;
+	mutable std::mutex mRemoteDescriptionMutex;
 
 
+	shared_ptr<MediaHandler> mMediaHandler;
 	mutable std::shared_mutex mMediaHandlerMutex;
 	mutable std::shared_mutex mMediaHandlerMutex;
 
 
 	shared_ptr<IceTransport> mIceTransport;
 	shared_ptr<IceTransport> mIceTransport;
@@ -148,12 +157,12 @@ private:
 
 
 	std::unordered_map<uint16_t, weak_ptr<DataChannel>> mDataChannels; // by stream ID
 	std::unordered_map<uint16_t, weak_ptr<DataChannel>> mDataChannels; // by stream ID
 	std::vector<weak_ptr<DataChannel>> mUnassignedDataChannels;
 	std::vector<weak_ptr<DataChannel>> mUnassignedDataChannels;
-	std::shared_mutex mDataChannelsMutex;
+	mutable std::shared_mutex mDataChannelsMutex;
 
 
 	std::unordered_map<string, weak_ptr<Track>> mTracks;         // by mid
 	std::unordered_map<string, weak_ptr<Track>> mTracks;         // by mid
 	std::unordered_map<uint32_t, weak_ptr<Track>> mTracksBySsrc; // by SSRC
 	std::unordered_map<uint32_t, weak_ptr<Track>> mTracksBySsrc; // by SSRC
 	std::vector<weak_ptr<Track>> mTrackLines;                    // by SDP order
 	std::vector<weak_ptr<Track>> mTrackLines;                    // by SDP order
-	std::shared_mutex mTracksMutex;
+	mutable std::shared_mutex mTracksMutex;
 
 
 	Queue<shared_ptr<DataChannel>> mPendingDataChannels;
 	Queue<shared_ptr<DataChannel>> mPendingDataChannels;
 	Queue<shared_ptr<Track>> mPendingTracks;
 	Queue<shared_ptr<Track>> mPendingTracks;

+ 4 - 3
src/impl/pollservice.cpp

@@ -189,12 +189,13 @@ void PollService::runLoop() {
 
 
 			} while (ret < 0 && (sockerrno == SEINTR || sockerrno == SEAGAIN));
 			} while (ret < 0 && (sockerrno == SEINTR || sockerrno == SEAGAIN));
 
 
+			if (ret < 0) {
 #ifdef _WIN32
 #ifdef _WIN32
-			if (ret == WSAENOTSOCK)
-				continue; // prepare again as the fd has been removed
+				if (sockerrno == WSAENOTSOCK)
+					continue; // prepare again as the fd has been removed
 #endif
 #endif
-			if (ret < 0)
 				throw std::runtime_error("poll failed, errno=" + std::to_string(sockerrno));
 				throw std::runtime_error("poll failed, errno=" + std::to_string(sockerrno));
+			}
 
 
 			process(pfds);
 			process(pfds);
 		}
 		}

+ 1 - 0
src/impl/queue.hpp

@@ -105,6 +105,7 @@ template <typename T> optional<T> Queue<T>::pop() {
 	mAmount -= mAmountFunction(mQueue.front());
 	mAmount -= mAmountFunction(mQueue.front());
 	optional<T> element{std::move(mQueue.front())};
 	optional<T> element{std::move(mQueue.front())};
 	mQueue.pop();
 	mQueue.pop();
+	mPushCondition.notify_one();
 	return element;
 	return element;
 }
 }
 
 

+ 1 - 1
src/impl/sctptransport.cpp

@@ -82,7 +82,7 @@ private:
 	std::shared_mutex mMutex;
 	std::shared_mutex mMutex;
 };
 };
 
 
-SctpTransport::InstancesSet *SctpTransport::Instances = new InstancesSet;
+std::unique_ptr<SctpTransport::InstancesSet> SctpTransport::Instances = std::make_unique<InstancesSet>();
 
 
 void SctpTransport::Init() {
 void SctpTransport::Init() {
 	usrsctp_init(0, SctpTransport::WriteCallback, SctpTransport::DebugCallback);
 	usrsctp_init(0, SctpTransport::WriteCallback, SctpTransport::DebugCallback);

+ 1 - 1
src/impl/sctptransport.hpp

@@ -127,7 +127,7 @@ private:
 	static void DebugCallback(const char *format, ...);
 	static void DebugCallback(const char *format, ...);
 
 
 	class InstancesSet;
 	class InstancesSet;
-	static InstancesSet *Instances;
+	static std::unique_ptr<InstancesSet> Instances;
 };
 };
 
 
 } // namespace rtc::impl
 } // namespace rtc::impl

+ 17 - 14
src/impl/tcptransport.cpp

@@ -200,23 +200,23 @@ void TcpTransport::resolve() {
 }
 }
 
 
 void TcpTransport::attempt() {
 void TcpTransport::attempt() {
-	std::lock_guard lock(mSendMutex);
+	try {
+		std::lock_guard lock(mSendMutex);
 
 
-	if (state() != State::Connecting)
-		return; // Cancelled
+		if (state() != State::Connecting)
+			return; // Cancelled
 
 
-	if (mSock == INVALID_SOCKET) {
-		::closesocket(mSock);
-		mSock = INVALID_SOCKET;
-	}
+		if (mSock == INVALID_SOCKET) {
+			::closesocket(mSock);
+			mSock = INVALID_SOCKET;
+		}
 
 
-	if (mResolved.empty()) {
-		PLOG_WARNING << "Connection to " << mHostname << ":" << mService << " failed";
-		changeState(State::Failed);
-		return;
-	}
+		if (mResolved.empty()) {
+			PLOG_WARNING << "Connection to " << mHostname << ":" << mService << " failed";
+			changeState(State::Failed);
+			return;
+		}
 
 
-	try {
 		auto [addr, addrlen] = mResolved.front();
 		auto [addr, addrlen] = mResolved.front();
 		mResolved.pop_front();
 		mResolved.pop_front();
 
 
@@ -372,7 +372,9 @@ bool TcpTransport::trySendMessage(message_ptr &message) {
 		int len = ::send(mSock, data, int(size), flags);
 		int len = ::send(mSock, data, int(size), flags);
 		if (len < 0) {
 		if (len < 0) {
 			if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
 			if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
-				message = make_message(message->end() - size, message->end());
+				if (size < message->size())
+					message = make_message(message->end() - size, message->end());
+
 				return false;
 				return false;
 			} else {
 			} else {
 				PLOG_ERROR << "Connection closed, errno=" << sockerrno;
 				PLOG_ERROR << "Connection closed, errno=" << sockerrno;
@@ -427,6 +429,7 @@ void TcpTransport::process(PollService::Event event) {
 		}
 		}
 
 
 		case PollService::Event::Out: {
 		case PollService::Event::Out: {
+			std::lock_guard lock(mSendMutex);
 			if (trySendQueue())
 			if (trySendQueue())
 				setPoll(PollService::Direction::In);
 				setPoll(PollService::Direction::In);
 
 

+ 5 - 1
src/impl/tls.cpp

@@ -158,7 +158,11 @@ void init() {
 
 
 	std::lock_guard lock(mutex);
 	std::lock_guard lock(mutex);
 	if (!std::exchange(done, true)) {
 	if (!std::exchange(done, true)) {
-		OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
+		uint64_t ssl_opts = OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS;
+#ifdef OPENSSL_INIT_NO_ATEXIT
+		ssl_opts |= OPENSSL_INIT_NO_ATEXIT;
+#endif
+		OPENSSL_init_ssl(ssl_opts, nullptr);
 		OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
 		OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
 	}
 	}
 }
 }

+ 3 - 0
src/impl/tlstransport.cpp

@@ -603,6 +603,9 @@ TlsTransport::TlsTransport(variant<shared_ptr<TcpTransport>, shared_ptr<HttpProx
 			auto [x509, pkey] = certificate->credentials();
 			auto [x509, pkey] = certificate->credentials();
 			SSL_CTX_use_certificate(mCtx, x509);
 			SSL_CTX_use_certificate(mCtx, x509);
 			SSL_CTX_use_PrivateKey(mCtx, pkey);
 			SSL_CTX_use_PrivateKey(mCtx, pkey);
+
+			for (auto c : certificate->chain())
+				SSL_CTX_add1_chain_cert(mCtx, c); // add1 increments reference count
 		}
 		}
 
 
 		SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_RENEGOTIATION);
 		SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_RENEGOTIATION);

+ 1 - 1
src/impl/tlstransport.hpp

@@ -72,7 +72,7 @@ protected:
 	mbedtls_ssl_config mConf;
 	mbedtls_ssl_config mConf;
 	mbedtls_ssl_context mSsl;
 	mbedtls_ssl_context mSsl;
 
 
-	std::mutex mSslMutex;
+	std::recursive_mutex mSslMutex;
 	std::atomic<bool> mOutgoingResult = true;
 	std::atomic<bool> mOutgoingResult = true;
 
 
 	message_ptr mIncomingMessage;
 	message_ptr mIncomingMessage;

+ 16 - 14
src/impl/track.cpp

@@ -141,12 +141,18 @@ void Track::incoming(message_ptr message) {
 	}
 	}
 
 
 	message_vector messages{std::move(message)};
 	message_vector messages{std::move(message)};
-	if (auto handler = getMediaHandler())
-		handler->incomingChain(messages, [this, weak_this = weak_from_this()](message_ptr m) {
-			if (auto locked = weak_this.lock()) {
-				transportSend(m);
-			}
-		});
+	if (auto handler = getMediaHandler()) {
+		try {
+			handler->incomingChain(messages, [this, weak_this = weak_from_this()](message_ptr m) {
+				if (auto locked = weak_this.lock()) {
+					transportSend(m);
+				}
+			});
+		} catch (const std::exception &e) {
+			PLOG_WARNING << "Exception in incoming media handler: " << e.what();
+			return;
+		}
+	}
 
 
 	for (auto &m : messages) {
 	for (auto &m : messages) {
 		// Tail drop if queue is full
 		// Tail drop if queue is full
@@ -184,6 +190,7 @@ bool Track::outgoing(message_ptr message) {
 				transportSend(m);
 				transportSend(m);
 			}
 			}
 		});
 		});
+
 		bool ret = false;
 		bool ret = false;
 		for (auto &m : messages)
 		for (auto &m : messages)
 			ret = transportSend(std::move(m));
 			ret = transportSend(std::move(m));
@@ -202,7 +209,7 @@ bool Track::transportSend([[maybe_unused]] message_ptr message) {
 		std::shared_lock lock(mMutex);
 		std::shared_lock lock(mMutex);
 		transport = mDtlsSrtpTransport.lock();
 		transport = mDtlsSrtpTransport.lock();
 		if (!transport)
 		if (!transport)
-			throw std::runtime_error("Track is closed");
+			throw std::runtime_error("Track is not open");
 
 
 		// Set recommended medium-priority DSCP value
 		// Set recommended medium-priority DSCP value
 		// See https://www.rfc-editor.org/rfc/rfc8837.html#section-5
 		// See https://www.rfc-editor.org/rfc/rfc8837.html#section-5
@@ -233,11 +240,6 @@ shared_ptr<MediaHandler> Track::getMediaHandler() {
 	return mMediaHandler;
 	return mMediaHandler;
 }
 }
 
 
-void Track::onFrame(std::function<void(binary data, FrameInfo frame)> callback) {
-	frameCallback = callback;
-	flushPendingMessages();
-}
-
 void Track::flushPendingMessages() {
 void Track::flushPendingMessages() {
 	if (!mOpenTriggered)
 	if (!mOpenTriggered)
 		return;
 		return;
@@ -249,9 +251,9 @@ void Track::flushPendingMessages() {
 
 
 		auto message = next.value();
 		auto message = next.value();
 		try {
 		try {
-			if (message->frameInfo != nullptr && frameCallback) {
+			if (message->frameInfo && frameCallback) {
 				frameCallback(std::move(*message), std::move(*message->frameInfo));
 				frameCallback(std::move(*message), std::move(*message->frameInfo));
-			} else if (message->frameInfo == nullptr && messageCallback) {
+			} else if (!message->frameInfo && messageCallback) {
 				messageCallback(trackMessageToVariant(message));
 				messageCallback(trackMessageToVariant(message));
 			}
 			}
 		} catch (const std::exception &e) {
 		} catch (const std::exception &e) {

+ 3 - 2
src/impl/track.hpp

@@ -41,7 +41,7 @@ public:
 	void flushPendingMessages() override;
 	void flushPendingMessages() override;
 	message_variant trackMessageToVariant(message_ptr message);
 	message_variant trackMessageToVariant(message_ptr message);
 
 
-	void onFrame(std::function<void(binary data, FrameInfo frame)> callback);
+	void sendFrame(binary data, const FrameInfo &frame);
 
 
 	bool isOpen() const;
 	bool isOpen() const;
 	bool isClosed() const;
 	bool isClosed() const;
@@ -61,6 +61,8 @@ public:
 
 
 	bool transportSend(message_ptr message);
 	bool transportSend(message_ptr message);
 
 
+	synchronized_callback<binary, FrameInfo> frameCallback;
+
 private:
 private:
 	const weak_ptr<PeerConnection> mPeerConnection;
 	const weak_ptr<PeerConnection> mPeerConnection;
 #if RTC_ENABLE_MEDIA
 #if RTC_ENABLE_MEDIA
@@ -76,7 +78,6 @@ private:
 
 
 	Queue<message_ptr> mRecvQueue;
 	Queue<message_ptr> mRecvQueue;
 
 
-	synchronized_callback<binary, FrameInfo> frameCallback;
 };
 };
 
 
 } // namespace rtc::impl
 } // namespace rtc::impl

+ 6 - 3
src/impl/websocket.cpp

@@ -252,7 +252,8 @@ shared_ptr<TcpTransport> WebSocket::setTcpTransport(shared_ptr<TcpTransport> tra
 				remoteClose();
 				remoteClose();
 				break;
 				break;
 			case State::Disconnected:
 			case State::Disconnected:
-				remoteClose();
+				if(state == WebSocket::State::Connecting)
+					remoteClose();
 				break;
 				break;
 			default:
 			default:
 				// Ignore
 				// Ignore
@@ -303,7 +304,8 @@ shared_ptr<HttpProxyTransport> WebSocket::initProxyTransport() {
 				remoteClose();
 				remoteClose();
 				break;
 				break;
 			case State::Disconnected:
 			case State::Disconnected:
-				remoteClose();
+				if(state == WebSocket::State::Connecting)
+					remoteClose();
 				break;
 				break;
 			default:
 			default:
 				// Ignore
 				// Ignore
@@ -358,7 +360,8 @@ shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
 				remoteClose();
 				remoteClose();
 				break;
 				break;
 			case State::Disconnected:
 			case State::Disconnected:
-				remoteClose();
+				if(state == WebSocket::State::Connecting)
+					remoteClose();
 				break;
 				break;
 			default:
 			default:
 				// Ignore
 				// Ignore

+ 13 - 9
src/impl/wshandshake.cpp

@@ -64,7 +64,7 @@ string WsHandshake::generateHttpRequest() {
 	             "Host: " +
 	             "Host: " +
 	             mHost +
 	             mHost +
 	             "\r\n"
 	             "\r\n"
-	             "Connection: upgrade\r\n"
+	             "Connection: Upgrade\r\n"
 	             "Upgrade: websocket\r\n"
 	             "Upgrade: websocket\r\n"
 	             "Sec-WebSocket-Version: 13\r\n"
 	             "Sec-WebSocket-Version: 13\r\n"
 	             "Sec-WebSocket-Key: " +
 	             "Sec-WebSocket-Key: " +
@@ -80,12 +80,18 @@ string WsHandshake::generateHttpRequest() {
 
 
 string WsHandshake::generateHttpResponse() {
 string WsHandshake::generateHttpResponse() {
 	std::unique_lock lock(mMutex);
 	std::unique_lock lock(mMutex);
-	const string out = "HTTP/1.1 101 Switching Protocols\r\n"
-	                   "Server: libdatachannel\r\n"
-	                   "Connection: upgrade\r\n"
-	                   "Upgrade: websocket\r\n"
-	                   "Sec-WebSocket-Accept: " +
-	                   computeAcceptKey(mKey) + "\r\n\r\n";
+
+	string out = "HTTP/1.1 101 Switching Protocols\r\n"
+	             "Server: libdatachannel\r\n"
+	             "Connection: Upgrade\r\n"
+	             "Upgrade: websocket\r\n"
+	             "Sec-WebSocket-Accept: " +
+	             computeAcceptKey(mKey) + "\r\n";
+
+	if (!mProtocols.empty())
+		out += "Sec-WebSocket-Protocol: " + utils::implode(mProtocols, ',') + "\r\n";
+
+	out += "\r\n";
 
 
 	return out;
 	return out;
 }
 }
@@ -119,8 +125,6 @@ string WsHandshake::generateHttpError(int responseCode) {
 	const string out = "HTTP/1.1 " + error +
 	const string out = "HTTP/1.1 " + error +
 	                   "\r\n"
 	                   "\r\n"
 	                   "Server: libdatachannel\r\n"
 	                   "Server: libdatachannel\r\n"
-	                   "Connection: upgrade\r\n"
-	                   "Upgrade: websocket\r\n"
 	                   "Content-Type: text/plain\r\n"
 	                   "Content-Type: text/plain\r\n"
 	                   "Content-Length: " +
 	                   "Content-Length: " +
 	                   to_string(error.size()) +
 	                   to_string(error.size()) +

+ 5 - 2
src/message.cpp

@@ -18,11 +18,14 @@ message_ptr make_message(size_t size, Message::Type type, unsigned int stream,
 	return message;
 	return message;
 }
 }
 
 
-message_ptr make_message(binary &&data, Message::Type type, unsigned int stream,
-                         shared_ptr<Reliability> reliability, shared_ptr<FrameInfo> frameInfo) {
+message_ptr make_message(binary &&data, Message::Type type, unsigned int stream, shared_ptr<Reliability> reliability) {
 	auto message = std::make_shared<Message>(std::move(data), type);
 	auto message = std::make_shared<Message>(std::move(data), type);
 	message->stream = stream;
 	message->stream = stream;
 	message->reliability = reliability;
 	message->reliability = reliability;
+	return message;
+}
+message_ptr make_message(binary &&data, shared_ptr<FrameInfo> frameInfo) {
+	auto message = std::make_shared<Message>(std::move(data));
 	message->frameInfo = frameInfo;
 	message->frameInfo = frameInfo;
 	return message;
 	return message;
 }
 }

+ 103 - 33
src/nalunit.cpp

@@ -16,33 +16,38 @@
 
 
 namespace rtc {
 namespace rtc {
 
 
-NalUnitFragmentA::NalUnitFragmentA(FragmentType type, bool forbiddenBit, uint8_t nri,
-                                   uint8_t unitType, binary data)
-    : NalUnit(data.size() + 2) {
-	setForbiddenBit(forbiddenBit);
-	setNRI(nri);
-	fragmentIndicator()->setUnitType(NalUnitFragmentA::nal_type_fu_A);
-	setFragmentType(type);
-	setUnitType(unitType);
-	copy(data.begin(), data.end(), begin() + 2);
+std::vector<binary> NalUnit::GenerateFragments(const std::vector<NalUnit> &nalus,
+                                               size_t maxFragmentSize) {
+	std::vector<binary> result;
+	for (const auto &nalu : nalus) {
+		if (nalu.size() > maxFragmentSize) {
+			auto fragments = nalu.generateFragments(maxFragmentSize);
+			result.insert(result.end(), fragments.begin(), fragments.end());
+		} else {
+			// TODO: check this
+			result.push_back(nalu);
+		}
+	}
+	return result;
 }
 }
 
 
-std::vector<shared_ptr<NalUnitFragmentA>>
-NalUnitFragmentA::fragmentsFrom(shared_ptr<NalUnit> nalu, uint16_t maxFragmentSize) {
-	assert(nalu->size() > maxFragmentSize);
-	auto fragments_count = ceil(double(nalu->size()) / maxFragmentSize);
-	maxFragmentSize = uint16_t(int(ceil(nalu->size() / fragments_count)));
+std::vector<NalUnitFragmentA> NalUnit::generateFragments(size_t maxFragmentSize) const {
+	assert(size() > maxFragmentSize);
+	// TODO: check this
+	auto fragments_count = ceil(double(size()) / maxFragmentSize);
+	maxFragmentSize = uint16_t(int(ceil(size() / fragments_count)));
 
 
 	// 2 bytes for FU indicator and FU header
 	// 2 bytes for FU indicator and FU header
 	maxFragmentSize -= 2;
 	maxFragmentSize -= 2;
-	auto f = nalu->forbiddenBit();
-	uint8_t nri = nalu->nri() & 0x03;
-	uint8_t naluType = nalu->unitType() & 0x1F;
-	auto payload = nalu->payload();
-	vector<shared_ptr<NalUnitFragmentA>> result{};
-	uint64_t offset = 0;
+	auto f = forbiddenBit();
+	uint8_t nri = this->nri() & 0x03;
+	uint8_t unitType = this->unitType() & 0x1F;
+	auto payload = this->payload();
+	size_t offset = 0;
+	std::vector<NalUnitFragmentA> result;
 	while (offset < payload.size()) {
 	while (offset < payload.size()) {
 		vector<byte> fragmentData;
 		vector<byte> fragmentData;
+		using FragmentType = NalUnitFragmentA::FragmentType;
 		FragmentType fragmentType;
 		FragmentType fragmentType;
 		if (offset == 0) {
 		if (offset == 0) {
 			fragmentType = FragmentType::Start;
 			fragmentType = FragmentType::Start;
@@ -55,14 +60,78 @@ NalUnitFragmentA::fragmentsFrom(shared_ptr<NalUnit> nalu, uint16_t maxFragmentSi
 			fragmentType = FragmentType::End;
 			fragmentType = FragmentType::End;
 		}
 		}
 		fragmentData = {payload.begin() + offset, payload.begin() + offset + maxFragmentSize};
 		fragmentData = {payload.begin() + offset, payload.begin() + offset + maxFragmentSize};
-		auto fragment =
-		    std::make_shared<NalUnitFragmentA>(fragmentType, f, nri, naluType, fragmentData);
-		result.push_back(fragment);
+		result.emplace_back(fragmentType, f, nri, unitType, fragmentData);
 		offset += maxFragmentSize;
 		offset += maxFragmentSize;
 	}
 	}
 	return result;
 	return result;
 }
 }
 
 
+NalUnitStartSequenceMatch NalUnit::StartSequenceMatchSucc(NalUnitStartSequenceMatch match,
+                                                          std::byte _byte, Separator separator) {
+	assert(separator != Separator::Length);
+	auto byte = (uint8_t)_byte;
+	auto detectShort =
+	    separator == Separator::ShortStartSequence || separator == Separator::StartSequence;
+	auto detectLong =
+	    separator == Separator::LongStartSequence || separator == Separator::StartSequence;
+	switch (match) {
+	case NUSM_noMatch:
+		if (byte == 0x00) {
+			return NUSM_firstZero;
+		}
+		break;
+	case NUSM_firstZero:
+		if (byte == 0x00) {
+			return NUSM_secondZero;
+		}
+		break;
+	case NUSM_secondZero:
+		if (byte == 0x00 && detectLong) {
+			return NUSM_thirdZero;
+		} else if (byte == 0x00 && detectShort) {
+			return NUSM_secondZero;
+		} else if (byte == 0x01 && detectShort) {
+			return NUSM_shortMatch;
+		}
+		break;
+	case NUSM_thirdZero:
+		if (byte == 0x00 && detectLong) {
+			return NUSM_thirdZero;
+		} else if (byte == 0x01 && detectLong) {
+			return NUSM_longMatch;
+		}
+		break;
+	case NUSM_shortMatch:
+		return NUSM_shortMatch;
+	case NUSM_longMatch:
+		return NUSM_longMatch;
+	}
+	return NUSM_noMatch;
+}
+
+NalUnitFragmentA::NalUnitFragmentA(FragmentType type, bool forbiddenBit, uint8_t nri,
+                                   uint8_t unitType, binary data)
+    : NalUnit(data.size() + 2) {
+	setForbiddenBit(forbiddenBit);
+	setNRI(nri);
+	fragmentIndicator()->setUnitType(NalUnitFragmentA::nal_type_fu_A);
+	setFragmentType(type);
+	setUnitType(unitType);
+	copy(data.begin(), data.end(), begin() + 2);
+}
+
+// For backward compatibility, do not use
+std::vector<shared_ptr<NalUnitFragmentA>>
+NalUnitFragmentA::fragmentsFrom(shared_ptr<NalUnit> nalu, uint16_t maxFragmentSize) {
+	auto fragments = nalu->generateFragments(maxFragmentSize);
+	std::vector<shared_ptr<NalUnitFragmentA>> result;
+	result.reserve(fragments.size());
+	for (auto fragment : fragments)
+		result.push_back(std::make_shared<NalUnitFragmentA>(std::move(fragment)));
+
+	return result;
+}
+
 void NalUnitFragmentA::setFragmentType(FragmentType type) {
 void NalUnitFragmentA::setFragmentType(FragmentType type) {
 	fragmentHeader()->setReservedBit6(false);
 	fragmentHeader()->setReservedBit6(false);
 	switch (type) {
 	switch (type) {
@@ -80,17 +149,18 @@ void NalUnitFragmentA::setFragmentType(FragmentType type) {
 	}
 	}
 }
 }
 
 
+// For backward compatibility, do not use
 std::vector<shared_ptr<binary>> NalUnits::generateFragments(uint16_t maxFragmentSize) {
 std::vector<shared_ptr<binary>> NalUnits::generateFragments(uint16_t maxFragmentSize) {
-	vector<shared_ptr<binary>> result{};
-	for (auto nalu : *this) {
-		if (nalu->size() > maxFragmentSize) {
-			std::vector<shared_ptr<NalUnitFragmentA>> fragments =
-			    NalUnitFragmentA::fragmentsFrom(nalu, maxFragmentSize);
-			result.insert(result.end(), fragments.begin(), fragments.end());
-		} else {
-			result.push_back(nalu);
-		}
-	}
+	std::vector<NalUnit> nalus;
+	for (auto nalu : *this)
+		nalus.push_back(*nalu);
+
+	auto fragments = NalUnit::GenerateFragments(nalus, maxFragmentSize);
+	std::vector<shared_ptr<binary>> result;
+	result.reserve(fragments.size());
+	for (auto fragment : fragments)
+		result.push_back(std::make_shared<binary>(std::move(fragment)));
+
 	return result;
 	return result;
 }
 }
 
 

+ 72 - 27
src/peerconnection.cpp

@@ -61,6 +61,10 @@ PeerConnection::SignalingState PeerConnection::signalingState() const {
 	return impl()->signalingState;
 	return impl()->signalingState;
 }
 }
 
 
+bool PeerConnection::negotiationNeeded() const {
+	return impl()->negotiationNeeded();
+}
+
 optional<Description> PeerConnection::localDescription() const {
 optional<Description> PeerConnection::localDescription() const {
 	return impl()->localDescription();
 	return impl()->localDescription();
 }
 }
@@ -76,11 +80,12 @@ bool PeerConnection::hasMedia() const {
 	return local && local->hasAudioOrVideo();
 	return local && local->hasAudioOrVideo();
 }
 }
 
 
-void PeerConnection::setLocalDescription(Description::Type type) {
+void PeerConnection::setLocalDescription(Description::Type type, LocalDescriptionInit init) {
 	std::unique_lock signalingLock(impl()->signalingMutex);
 	std::unique_lock signalingLock(impl()->signalingMutex);
 	PLOG_VERBOSE << "Setting local description, type=" << Description::typeToString(type);
 	PLOG_VERBOSE << "Setting local description, type=" << Description::typeToString(type);
 
 
 	SignalingState signalingState = impl()->signalingState.load();
 	SignalingState signalingState = impl()->signalingState.load();
+
 	if (type == Description::Type::Rollback) {
 	if (type == Description::Type::Rollback) {
 		if (signalingState == SignalingState::HaveLocalOffer ||
 		if (signalingState == SignalingState::HaveLocalOffer ||
 		    signalingState == SignalingState::HaveLocalPranswer) {
 		    signalingState == SignalingState::HaveLocalPranswer) {
@@ -98,12 +103,6 @@ void PeerConnection::setLocalDescription(Description::Type type) {
 			type = Description::Type::Offer;
 			type = Description::Type::Offer;
 	}
 	}
 
 
-	// Only a local offer resets the negotiation needed flag
-	if (type == Description::Type::Offer && !impl()->negotiationNeeded.exchange(false)) {
-		PLOG_DEBUG << "No negotiation needed";
-		return;
-	}
-
 	// Get the new signaling state
 	// Get the new signaling state
 	SignalingState newSignalingState;
 	SignalingState newSignalingState;
 	switch (signalingState) {
 	switch (signalingState) {
@@ -140,12 +139,29 @@ void PeerConnection::setLocalDescription(Description::Type type) {
 	if (!iceTransport)
 	if (!iceTransport)
 		return; // closed
 		return; // closed
 
 
+	if (init.iceUfrag && init.icePwd) {
+		PLOG_DEBUG << "Setting custom ICE attributes, ufrag=\"" << *init.iceUfrag << "\", pwd=\"" << *init.icePwd << "\"";
+		iceTransport->setIceAttributes(*init.iceUfrag, *init.icePwd);
+	}
+
 	Description local = iceTransport->getLocalDescription(type);
 	Description local = iceTransport->getLocalDescription(type);
+	impl()->populateLocalDescription(local);
+
+	// There might be no media at this point, for instance if the user deleted tracks
+	if (local.mediaCount() == 0)
+		throw std::runtime_error("No DataChannel or Track to negotiate");
+
 	impl()->processLocalDescription(std::move(local));
 	impl()->processLocalDescription(std::move(local));
 
 
 	impl()->changeSignalingState(newSignalingState);
 	impl()->changeSignalingState(newSignalingState);
 	signalingLock.unlock();
 	signalingLock.unlock();
 
 
+	if (!impl()->config.disableAutoNegotiation && newSignalingState == SignalingState::Stable) {
+		// We might need to make a new offer
+		if (impl()->negotiationNeeded())
+			setLocalDescription(Description::Type::Offer);
+	}
+
 	if (impl()->gatheringState == GatheringState::New && !impl()->config.disableAutoGathering) {
 	if (impl()->gatheringState == GatheringState::New && !impl()->config.disableAutoGathering) {
 		iceTransport->gatherLocalCandidates(impl()->localBundleMid());
 		iceTransport->gatherLocalCandidates(impl()->localBundleMid());
 	}
 	}
@@ -153,9 +169,8 @@ void PeerConnection::setLocalDescription(Description::Type type) {
 
 
 void PeerConnection::gatherLocalCandidates(std::vector<IceServer> additionalIceServers) {
 void PeerConnection::gatherLocalCandidates(std::vector<IceServer> additionalIceServers) {
 	auto iceTransport = impl()->getIceTransport();
 	auto iceTransport = impl()->getIceTransport();
-	if (!iceTransport) {
-		throw std::logic_error("No IceTransport. Local Description has not been set");
-	}
+	if (!iceTransport || !localDescription())
+		throw std::logic_error("Local description has not been set before gathering");
 
 
 	if (impl()->gatheringState == GatheringState::New) {
 	if (impl()->gatheringState == GatheringState::New) {
 		iceTransport->gatherLocalCandidates(impl()->localBundleMid(), additionalIceServers);
 		iceTransport->gatherLocalCandidates(impl()->localBundleMid(), additionalIceServers);
@@ -234,7 +249,6 @@ void PeerConnection::setRemoteDescription(Description description) {
 
 
 	// Candidates will be added at the end, extract them for now
 	// Candidates will be added at the end, extract them for now
 	auto remoteCandidates = description.extractCandidates();
 	auto remoteCandidates = description.extractCandidates();
-	auto type = description.type();
 
 
 	auto iceTransport = impl()->initIceTransport();
 	auto iceTransport = impl()->initIceTransport();
 	if (!iceTransport)
 	if (!iceTransport)
@@ -246,14 +260,26 @@ void PeerConnection::setRemoteDescription(Description description) {
 	impl()->changeSignalingState(newSignalingState);
 	impl()->changeSignalingState(newSignalingState);
 	signalingLock.unlock();
 	signalingLock.unlock();
 
 
-	if (type == Description::Type::Offer) {
-		// This is an offer, we need to answer
-		if (!impl()->config.disableAutoNegotiation)
-			setLocalDescription(Description::Type::Answer);
-	}
-
 	for (const auto &candidate : remoteCandidates)
 	for (const auto &candidate : remoteCandidates)
 		addRemoteCandidate(candidate);
 		addRemoteCandidate(candidate);
+
+	if (!impl()->config.disableAutoNegotiation) {
+		switch (newSignalingState) {
+		case SignalingState::Stable:
+			// We might need to make a new offer
+			if (impl()->negotiationNeeded())
+				setLocalDescription(Description::Type::Offer);
+			break;
+
+		case SignalingState::HaveRemoteOffer:
+			// We need to answer
+			setLocalDescription(Description::Type::Answer);
+			break;
+
+		default:
+			break;
+		}
+	}
 }
 }
 
 
 void PeerConnection::addRemoteCandidate(Candidate candidate) {
 void PeerConnection::addRemoteCandidate(Candidate candidate) {
@@ -262,6 +288,26 @@ void PeerConnection::addRemoteCandidate(Candidate candidate) {
 	impl()->processRemoteCandidate(std::move(candidate));
 	impl()->processRemoteCandidate(std::move(candidate));
 }
 }
 
 
+Description PeerConnection::createOffer() {
+	auto iceTransport = impl()->initIceTransport();
+	if (!iceTransport)
+		throw std::runtime_error("Peer connection is closed");
+
+	Description desc = iceTransport->getLocalDescription(rtc::Description::Type::Offer);
+	impl()->populateLocalDescription(desc);
+	return desc;
+}
+
+Description PeerConnection::createAnswer() {
+	auto iceTransport = impl()->initIceTransport();
+	if (!iceTransport)
+		throw std::runtime_error("Peer connection is closed");
+
+	Description desc = iceTransport->getLocalDescription(rtc::Description::Type::Answer);
+	impl()->populateLocalDescription(desc);
+	return desc;
+}
+
 void PeerConnection::setMediaHandler(shared_ptr<MediaHandler> handler) {
 void PeerConnection::setMediaHandler(shared_ptr<MediaHandler> handler) {
 	impl()->setMediaHandler(std::move(handler));
 	impl()->setMediaHandler(std::move(handler));
 };
 };
@@ -284,13 +330,11 @@ shared_ptr<DataChannel> PeerConnection::createDataChannel(string label, DataChan
 	auto channelImpl = impl()->emplaceDataChannel(std::move(label), std::move(init));
 	auto channelImpl = impl()->emplaceDataChannel(std::move(label), std::move(init));
 	auto channel = std::make_shared<DataChannel>(channelImpl);
 	auto channel = std::make_shared<DataChannel>(channelImpl);
 
 
-	// Renegotiation is needed iff the current local description does not have application
-	auto local = impl()->localDescription();
-	if (!local || !local->hasApplication())
-		impl()->negotiationNeeded = true;
-
-	if (!impl()->config.disableAutoNegotiation)
-		setLocalDescription();
+	if (!impl()->config.disableAutoNegotiation && impl()->signalingState.load() == SignalingState::Stable) {
+		// We might need to make a new offer
+		if (impl()->negotiationNeeded())
+			setLocalDescription(Description::Type::Offer);
+	}
 
 
 	return channel;
 	return channel;
 }
 }
@@ -305,9 +349,6 @@ std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description)
 	auto trackImpl = impl()->emplaceTrack(std::move(description));
 	auto trackImpl = impl()->emplaceTrack(std::move(description));
 	auto track = std::make_shared<Track>(trackImpl);
 	auto track = std::make_shared<Track>(trackImpl);
 
 
-	// Renegotiation is needed for the new or updated track
-	impl()->negotiationNeeded = true;
-
 	return track;
 	return track;
 }
 }
 
 
@@ -367,6 +408,10 @@ optional<std::chrono::milliseconds> PeerConnection::rtt() {
 	return sctpTransport ? sctpTransport->rtt() : nullopt;
 	return sctpTransport ? sctpTransport->rtt() : nullopt;
 }
 }
 
 
+CertificateFingerprint PeerConnection::remoteFingerprint() {
+	return impl()->remoteFingerprint();
+}
+
 std::ostream &operator<<(std::ostream &out, PeerConnection::State state) {
 std::ostream &operator<<(std::ostream &out, PeerConnection::State state) {
 	using State = PeerConnection::State;
 	using State = PeerConnection::State;
 	const char *str;
 	const char *str;

+ 47 - 0
src/rembhandler.cpp

@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2024 Vladimir Voronin
+ *
+ * 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/.
+ */
+
+#include "rembhandler.hpp"
+#include "rtp.hpp"
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+
+#if RTC_ENABLE_MEDIA
+
+namespace rtc {
+
+RembHandler::RembHandler(std::function<void(unsigned int)> onRemb) : mOnRemb(onRemb) {}
+
+void RembHandler::incoming(message_vector &messages, [[maybe_unused]] const message_callback &send) {
+	for (const auto &message : messages) {
+		size_t offset = 0;
+		while ((sizeof(RtcpHeader) + offset) <= message->size()) {
+			auto header = reinterpret_cast<RtcpHeader *>(message->data() + offset);
+			uint8_t payload_type = header->payloadType();
+
+			if (payload_type == 206 && header->reportCount() == 15 && header->lengthInBytes() == sizeof(RtcpRemb)) {
+				auto remb = reinterpret_cast<RtcpRemb *>(message->data() + offset);
+
+				if (remb->_id[0] == 'R' && remb->_id[1] == 'E' && remb->_id[2] == 'M' && remb->_id[3] == 'B') {
+					mOnRemb(remb->getBitrate());
+					break;
+				}
+			}
+
+			offset += header->lengthInBytes();
+		}
+	}
+}
+
+} // namespace rtc
+
+#endif // RTC_ENABLE_MEDIA

+ 8 - 9
src/rtcpnackresponder.cpp

@@ -46,10 +46,9 @@ void RtcpNackResponder::incoming(message_vector &messages, const message_callbac
 				                              newMissingSeqenceNumbers.end());
 				                              newMissingSeqenceNumbers.end());
 			}
 			}
 
 
-			for (auto sequenceNumber : missingSequenceNumbers) {
-				if (auto optPacket = mStorage->get(sequenceNumber))
-					send(make_message(*optPacket.value()));
-			}
+			for (auto sequenceNumber : missingSequenceNumbers)
+				if (auto packet = mStorage->get(sequenceNumber))
+					send(packet);
 		}
 		}
 	}
 	}
 }
 }
@@ -61,7 +60,7 @@ void RtcpNackResponder::outgoing(message_vector &messages,
 			mStorage->store(message);
 			mStorage->store(message);
 }
 }
 
 
-RtcpNackResponder::Storage::Element::Element(binary_ptr packet, uint16_t sequenceNumber,
+RtcpNackResponder::Storage::Element::Element(message_ptr packet, uint16_t sequenceNumber,
                                              shared_ptr<Element> next)
                                              shared_ptr<Element> next)
     : packet(packet), sequenceNumber(sequenceNumber), next(next) {}
     : packet(packet), sequenceNumber(sequenceNumber), next(next) {}
 
 
@@ -72,14 +71,14 @@ RtcpNackResponder::Storage::Storage(size_t _maxSize) : maxSize(_maxSize) {
 	storage.reserve(maxSize);
 	storage.reserve(maxSize);
 }
 }
 
 
-optional<binary_ptr> RtcpNackResponder::Storage::get(uint16_t sequenceNumber) {
+message_ptr RtcpNackResponder::Storage::get(uint16_t sequenceNumber) {
 	std::lock_guard lock(mutex);
 	std::lock_guard lock(mutex);
 	auto position = storage.find(sequenceNumber);
 	auto position = storage.find(sequenceNumber);
-	return position != storage.end() ? std::make_optional(storage.at(sequenceNumber)->packet)
-	                                 : nullopt;
+	return position != storage.end() ? storage.at(sequenceNumber)->packet
+	                                 : nullptr;
 }
 }
 
 
-void RtcpNackResponder::Storage::store(binary_ptr packet) {
+void RtcpNackResponder::Storage::store(message_ptr packet) {
 	if (!packet || packet->size() < sizeof(RtpHeader))
 	if (!packet || packet->size() < sizeof(RtpHeader))
 		return;
 		return;
 
 

+ 27 - 15
src/rtcpsrreporter.cpp

@@ -14,6 +14,8 @@
 #include <chrono>
 #include <chrono>
 #include <cmath>
 #include <cmath>
 
 
+using namespace std::chrono_literals;
+
 namespace {
 namespace {
 
 
 // TODO: move to utils
 // TODO: move to utils
@@ -28,16 +30,21 @@ uint64_t ntp_time() {
 
 
 namespace rtc {
 namespace rtc {
 
 
-RtcpSrReporter::RtcpSrReporter(shared_ptr<RtpPacketizationConfig> rtpConfig)
-    : rtpConfig(rtpConfig) {
-	mLastReportedTimestamp = rtpConfig->timestamp;
-}
+RtcpSrReporter::RtcpSrReporter(shared_ptr<RtpPacketizationConfig> rtpConfig) : rtpConfig(rtpConfig) {}
 
 
-void RtcpSrReporter::setNeedsToReport() { mNeedsToReport = true; }
+RtcpSrReporter::~RtcpSrReporter() {}
+
+void RtcpSrReporter::setNeedsToReport() {
+	// Dummy
+}
 
 
 uint32_t RtcpSrReporter::lastReportedTimestamp() const { return mLastReportedTimestamp; }
 uint32_t RtcpSrReporter::lastReportedTimestamp() const { return mLastReportedTimestamp; }
 
 
 void RtcpSrReporter::outgoing(message_vector &messages, const message_callback &send) {
 void RtcpSrReporter::outgoing(message_vector &messages, const message_callback &send) {
+	if (messages.empty())
+		return;
+
+	uint32_t timestamp = 0;
 	for (const auto &message : messages) {
 	for (const auto &message : messages) {
 		if (message->type == Message::Control)
 		if (message->type == Message::Control)
 			continue;
 			continue;
@@ -45,21 +52,27 @@ void RtcpSrReporter::outgoing(message_vector &messages, const message_callback &
 		if (message->size() < sizeof(RtpHeader))
 		if (message->size() < sizeof(RtpHeader))
 			continue;
 			continue;
 
 
-		auto rtp = reinterpret_cast<RtpHeader *>(message->data());
-		addToReport(rtp, uint32_t(message->size()));
+		auto header = reinterpret_cast<RtpHeader *>(message->data());
+		if(header->ssrc() != rtpConfig->ssrc)
+			continue;
+
+		timestamp = header->timestamp();
+
+		addToReport(header, message->size());
 	}
 	}
 
 
-	if (std::exchange(mNeedsToReport, false)) {
-		auto timestamp = rtpConfig->timestamp;
-		auto sr = getSenderReport(timestamp);
-		send(sr);
+	auto now = std::chrono::steady_clock::now();
+	if (now >= mLastReportTime + 1s) {
+		send(getSenderReport(timestamp));
+		mLastReportedTimestamp = timestamp;
+		mLastReportTime = now;
 	}
 	}
 }
 }
 
 
-void RtcpSrReporter::addToReport(RtpHeader *rtp, uint32_t rtpSize) {
+void RtcpSrReporter::addToReport(RtpHeader *header, size_t size) {
 	mPacketCount += 1;
 	mPacketCount += 1;
-	assert(!rtp->padding());
-	mPayloadOctets += rtpSize - uint32_t(rtp->getSize());
+	assert(!header->padding());
+	mPayloadOctets += uint32_t(size - header->getSize());
 }
 }
 
 
 message_ptr RtcpSrReporter::getSenderReport(uint32_t timestamp) {
 message_ptr RtcpSrReporter::getSenderReport(uint32_t timestamp) {
@@ -81,7 +94,6 @@ message_ptr RtcpSrReporter::getSenderReport(uint32_t timestamp) {
 	item->setText(rtpConfig->cname);
 	item->setText(rtpConfig->cname);
 	sdes->preparePacket(1);
 	sdes->preparePacket(1);
 
 
-	mLastReportedTimestamp = timestamp;
 	return msg;
 	return msg;
 }
 }
 
 

+ 9 - 1
src/rtp.cpp

@@ -571,7 +571,7 @@ void RtcpRemb::preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int
 
 
 void RtcpRemb::setBitrate(unsigned int numSSRC, unsigned int in_bitrate) {
 void RtcpRemb::setBitrate(unsigned int numSSRC, unsigned int in_bitrate) {
 	unsigned int exp = 0;
 	unsigned int exp = 0;
-	while (in_bitrate > pow(2, 18) - 1) {
+	while (in_bitrate > 0x3FFFF) {
 		exp++;
 		exp++;
 		in_bitrate /= 2;
 		in_bitrate /= 2;
 	}
 	}
@@ -584,6 +584,14 @@ void RtcpRemb::setBitrate(unsigned int numSSRC, unsigned int in_bitrate) {
 
 
 void RtcpRemb::setSsrc(int iterator, SSRC newSssrc) { _ssrc[iterator] = htonl(newSssrc); }
 void RtcpRemb::setSsrc(int iterator, SSRC newSssrc) { _ssrc[iterator] = htonl(newSssrc); }
 
 
+unsigned int RtcpRemb::getNumSSRC() { return ntohl(_bitrate) >> 24u; }
+
+unsigned int RtcpRemb::getBitrate() {
+	uint32_t br = ntohl(_bitrate);
+	uint8_t exp = (br << 8u) >> 26u;
+	return (br & 0x3FFFF) * static_cast<unsigned int>(pow(exp, 2));
+}
+
 unsigned int RtcpPli::Size() { return sizeof(RtcpFbHeader); }
 unsigned int RtcpPli::Size() { return sizeof(RtcpFbHeader); }
 
 
 void RtcpPli::preparePacket(SSRC messageSSRC) {
 void RtcpPli::preparePacket(SSRC messageSSRC) {

+ 13 - 3
src/rtpdepacketizer.cpp

@@ -18,6 +18,12 @@
 
 
 namespace rtc {
 namespace rtc {
 
 
+RtpDepacketizer::RtpDepacketizer() : mClockRate(0) {}
+
+RtpDepacketizer::RtpDepacketizer(uint32_t clockRate) : mClockRate(clockRate) {}
+
+RtpDepacketizer::~RtpDepacketizer() {}
+
 void RtpDepacketizer::incoming([[maybe_unused]] message_vector &messages,
 void RtpDepacketizer::incoming([[maybe_unused]] message_vector &messages,
                                [[maybe_unused]] const message_callback &send) {
                                [[maybe_unused]] const message_callback &send) {
 	message_vector result;
 	message_vector result;
@@ -34,9 +40,13 @@ void RtpDepacketizer::incoming([[maybe_unused]] message_vector &messages,
 
 
 		auto pkt = reinterpret_cast<const rtc::RtpHeader *>(message->data());
 		auto pkt = reinterpret_cast<const rtc::RtpHeader *>(message->data());
 		auto headerSize = sizeof(rtc::RtpHeader) + pkt->csrcCount() + pkt->getExtensionHeaderSize();
 		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->payloadType(), pkt->timestamp())));
+
+		auto frameInfo = std::make_shared<FrameInfo>(pkt->timestamp());
+		if (mClockRate > 0)
+			frameInfo->timestampSeconds =
+			    std::chrono::duration<double>(double(pkt->timestamp()) / double(mClockRate));
+		frameInfo->payloadType = pkt->payloadType();
+		result.push_back(make_message(message->begin() + headerSize, message->end(), frameInfo));
 	}
 	}
 
 
 	messages.swap(result);
 	messages.swap(result);

+ 45 - 13
src/rtppacketizer.cpp

@@ -19,7 +19,12 @@ RtpPacketizer::RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig) : rtp
 
 
 RtpPacketizer::~RtpPacketizer() {}
 RtpPacketizer::~RtpPacketizer() {}
 
 
-message_ptr RtpPacketizer::packetize(shared_ptr<binary> payload, bool mark) {
+std::vector<binary> RtpPacketizer::fragment(binary data) {
+	// Default implementation
+	return {std::move(data)};
+}
+
+message_ptr RtpPacketizer::packetize(const binary &payload, bool mark) {
 	size_t rtpExtHeaderSize = 0;
 	size_t rtpExtHeaderSize = 0;
 	bool twoByteHeader = false;
 	bool twoByteHeader = false;
 
 
@@ -69,7 +74,7 @@ message_ptr RtpPacketizer::packetize(shared_ptr<binary> payload, bool mark) {
 
 
 	rtpExtHeaderSize = (rtpExtHeaderSize + 3) & ~3;
 	rtpExtHeaderSize = (rtpExtHeaderSize + 3) & ~3;
 
 
-	auto message = make_message(RtpHeaderSize + rtpExtHeaderSize + payload->size());
+	auto message = make_message(RtpHeaderSize + rtpExtHeaderSize + payload.size());
 	auto *rtp = (RtpHeader *)message->data();
 	auto *rtp = (RtpHeader *)message->data();
 	rtp->setPayloadType(rtpConfig->payloadType);
 	rtp->setPayloadType(rtpConfig->payloadType);
 	rtp->setSeqNumber(rtpConfig->sequenceNumber++); // increase sequence number
 	rtp->setSeqNumber(rtpConfig->sequenceNumber++); // increase sequence number
@@ -127,11 +132,8 @@ message_ptr RtpPacketizer::packetize(shared_ptr<binary> payload, bool mark) {
 			uint16_t max = rtpConfig->playoutDelayMax & 0xFFF;
 			uint16_t max = rtpConfig->playoutDelayMax & 0xFFF;
 
 
 			// 12 bits for min + 12 bits for max
 			// 12 bits for min + 12 bits for max
-			byte data[] = {
-				byte((min >> 4) & 0xFF), 
-				byte(((min & 0xF) << 4) | ((max >> 8) & 0xF)),
-				byte(max & 0xFF)
-			};
+			byte data[] = {byte((min >> 4) & 0xFF), byte(((min & 0xF) << 4) | ((max >> 8) & 0xF)),
+			               byte(max & 0xFF)};
 
 
 			extHeader->writeOneByteHeader(offset, rtpConfig->playoutDelayId, data, 3);
 			extHeader->writeOneByteHeader(offset, rtpConfig->playoutDelayId, data, 3);
 			offset += 4;
 			offset += 4;
@@ -140,19 +142,49 @@ message_ptr RtpPacketizer::packetize(shared_ptr<binary> payload, bool mark) {
 
 
 	rtp->preparePacket();
 	rtp->preparePacket();
 
 
-	std::memcpy(message->data() + RtpHeaderSize + rtpExtHeaderSize, payload->data(),
-	            payload->size());
+	std::memcpy(message->data() + RtpHeaderSize + rtpExtHeaderSize, payload.data(), payload.size());
 
 
 	return message;
 	return message;
 }
 }
 
 
+message_ptr RtpPacketizer::packetize(shared_ptr<binary> payload, bool mark) {
+	return packetize(*payload, mark);
+}
+
 void RtpPacketizer::media([[maybe_unused]] const Description::Media &desc) {}
 void RtpPacketizer::media([[maybe_unused]] const Description::Media &desc) {}
 
 
-void RtpPacketizer::outgoing([[maybe_unused]] message_vector &messages,
+void RtpPacketizer::outgoing(message_vector &messages,
                              [[maybe_unused]] const message_callback &send) {
                              [[maybe_unused]] const message_callback &send) {
-	// Default implementation
-	for (auto &message : messages)
-		message = packetize(message, false);
+	message_vector result;
+	for (const auto &message : messages) {
+		if (const auto &frameInfo = message->frameInfo) {
+			if (frameInfo->payloadType && frameInfo->payloadType != rtpConfig->payloadType)
+				continue;
+
+			if (frameInfo->timestampSeconds)
+				rtpConfig->timestamp =
+				    rtpConfig->startTimestamp +
+				    rtpConfig->secondsToTimestamp(
+				        std::chrono::duration<double>(*frameInfo->timestampSeconds).count());
+			else
+				rtpConfig->timestamp = frameInfo->timestamp;
+		}
+
+		auto payloads = fragment(std::move(*message));
+		if (payloads.size() > 0) {
+			for (size_t i = 0; i < payloads.size(); i++) {
+				if (rtpConfig->dependencyDescriptorContext.has_value()) {
+					auto &ctx = *rtpConfig->dependencyDescriptorContext;
+					ctx.descriptor.startOfFrame = i == 0;
+					ctx.descriptor.endOfFrame = i == payloads.size() - 1;
+				}
+				bool mark = i == payloads.size() - 1;
+				result.push_back(packetize(payloads[i], mark));
+			}
+		}
+	}
+
+	messages.swap(result);
 }
 }
 
 
 } // namespace rtc
 } // namespace rtc

+ 13 - 4
src/track.cpp

@@ -40,6 +40,19 @@ bool Track::isClosed(void) const { return impl()->isClosed(); }
 
 
 size_t Track::maxMessageSize() const { return impl()->maxMessageSize(); }
 size_t Track::maxMessageSize() const { return impl()->maxMessageSize(); }
 
 
+void Track::sendFrame(binary data, FrameInfo info) {
+	impl()->outgoing(make_message(std::move(data), std::make_shared<FrameInfo>(std::move(info))));
+}
+
+void Track::sendFrame(const byte *data, size_t size, FrameInfo info) {
+	sendFrame(binary(data, data + size), std::move(info));
+}
+
+void Track::onFrame(std::function<void(binary data, FrameInfo frame)> callback) {
+	impl()->frameCallback = callback;
+	impl()->flushPendingMessages();
+}
+
 void Track::setMediaHandler(shared_ptr<MediaHandler> handler) {
 void Track::setMediaHandler(shared_ptr<MediaHandler> handler) {
 	impl()->setMediaHandler(std::move(handler));
 	impl()->setMediaHandler(std::move(handler));
 }
 }
@@ -70,8 +83,4 @@ bool Track::requestBitrate(unsigned int bitrate) {
 
 
 shared_ptr<MediaHandler> Track::getMediaHandler() { return impl()->getMediaHandler(); }
 shared_ptr<MediaHandler> Track::getMediaHandler() { return impl()->getMediaHandler(); }
 
 
-void Track::onFrame(std::function<void(binary data, FrameInfo frame)> callback) {
-	impl()->onFrame(callback);
-}
-
 } // namespace rtc
 } // namespace rtc

+ 18 - 0
test/capi_track.cpp

@@ -19,6 +19,8 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
 #include <unistd.h> // for sleep
 #include <unistd.h> // for sleep
 #endif
 #endif
 
 
+#define BUFFER_SIZE 4096
+
 typedef struct {
 typedef struct {
 	rtcState state;
 	rtcState state;
 	rtcGatheringState gatheringState;
 	rtcGatheringState gatheringState;
@@ -187,9 +189,25 @@ int test_capi_track_main() {
 		goto error;
 		goto error;
 	}
 	}
 
 
+	// Test createOffer
+	char buffer[BUFFER_SIZE];
+	if (rtcCreateOffer(peer1->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcCreateOffer failed\n");
+		goto error;
+	}
+	if (rtcGetLocalDescription(peer1->pc, buffer, BUFFER_SIZE) >= 0) {
+		fprintf(stderr, "rtcCreateOffer has set the local description\n");
+		goto error;
+	}
+
 	// Initiate the handshake
 	// Initiate the handshake
 	rtcSetLocalDescription(peer1->pc, NULL);
 	rtcSetLocalDescription(peer1->pc, NULL);
 
 
+	if (rtcGetLocalDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetLocalDescription failed\n");
+		goto error;
+	}
+
 	attempts = 10;
 	attempts = 10;
 	while ((!peer2->connected || !peer1->connected) && attempts--)
 	while ((!peer2->connected || !peer1->connected) && attempts--)
 		sleep(1);
 		sleep(1);