mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 07:35:30 +00:00
Compare commits
248 Commits
Author | SHA1 | Date | |
---|---|---|---|
0fcafad9c7 | |||
aab876d346 | |||
11ec8f7247 | |||
1597c9ae6f | |||
b093c4c3d5 | |||
447624322c | |||
422713cbdc | |||
d3d4187021 | |||
f2dd46e589 | |||
5b5debf260 | |||
86c3f914fb | |||
6a1fff13c1 | |||
91a854aa5b | |||
1181fdc599 | |||
fe3d92cebf | |||
c06d77bd8e | |||
c20aebbac2 | |||
9bd12567e6 | |||
bce5faf8ba | |||
64a5d6ecb0 | |||
ed606b7d7e | |||
4a526f66b6 | |||
f8dcfc32ed | |||
cfb10e52e6 | |||
d390c04ca9 | |||
7856ad45fd | |||
fc95b397a6 | |||
db74daae1d | |||
96d6b84c30 | |||
776ff342c0 | |||
519e81727a | |||
2d403247fb | |||
cfddfae1c5 | |||
31b5d6f84a | |||
44bf8b99ef | |||
344895ced1 | |||
9e22770281 | |||
3e2f4a2ac7 | |||
a4fe5cb874 | |||
ece1bd9352 | |||
b7a682cc50 | |||
d47492a54e | |||
fcf33d32a0 | |||
a5eb653064 | |||
ae2abfebad | |||
da4bf1fb49 | |||
82fab04721 | |||
fec3b1ad8b | |||
eb61f6cb3c | |||
fe6cf79f02 | |||
a7bc03c4b3 | |||
06b46aba91 | |||
f83843d054 | |||
e7d45db210 | |||
7a388bdffe | |||
8cb00f81ee | |||
37ebe8cc58 | |||
5eaed06b01 | |||
973f58ec8b | |||
6cb9dd8bad | |||
556104e8d5 | |||
d38ce2b575 | |||
e8628b203f | |||
ffa4e4bb20 | |||
79155d618e | |||
be8ffae0fe | |||
1a723c59aa | |||
6e7c082a7c | |||
037b9c9703 | |||
202d2cb5e4 | |||
03e9eca7d1 | |||
875defd17f | |||
97e23d00be | |||
de47fec19b | |||
c85b9c31fe | |||
be96e8b1fa | |||
2bcdab027c | |||
2eca2c4040 | |||
26cd6e4a59 | |||
be7a6324dd | |||
a7d5ba0232 | |||
3e53706869 | |||
dcb0a0282e | |||
99e78b68a4 | |||
5bb0979c3a | |||
40bed4e740 | |||
8f0c91d1cb | |||
de4192747c | |||
dfe8436954 | |||
7d0d0ea229 | |||
30ca8fb9c3 | |||
4f89e479bc | |||
3faf053bbd | |||
dc91d2cb6d | |||
626ecaa5bb | |||
a88c2dd1bf | |||
ed89fe3e94 | |||
1b73c7fb45 | |||
d563d63c89 | |||
e25bda7108 | |||
12579825a9 | |||
00cccc1e7b | |||
f1fa2abd6e | |||
476528b464 | |||
bc0b14288b | |||
02e4ed5221 | |||
5e876a4686 | |||
3ac3a98e26 | |||
25077878a1 | |||
704d6ab15f | |||
5ce699d33b | |||
e91d721b20 | |||
752c4bf5a1 | |||
bb73da2351 | |||
02105f5da3 | |||
7fac0a7618 | |||
69e5cab0a5 | |||
b220c5df99 | |||
73273d6e81 | |||
61fe8732a6 | |||
9f12c19a02 | |||
72016a7d26 | |||
f40c899b4f | |||
496163dbbe | |||
c0756aaa05 | |||
2cee070cee | |||
5fec28e9b7 | |||
bbec827fef | |||
35e3c7ee3a | |||
0c47c66bb1 | |||
a3cc74c8f1 | |||
db19eded61 | |||
de73af4b80 | |||
3e70af915f | |||
97311230d0 | |||
e966710988 | |||
198d0746b9 | |||
7782f6a5fd | |||
cb71695364 | |||
3af0e3b38b | |||
89e84e7b81 | |||
68bb97b3a3 | |||
41cf60c18b | |||
01eddaca13 | |||
6ec75d7c70 | |||
6221855f27 | |||
a8490f5e1c | |||
55d3336465 | |||
d59c209c20 | |||
46c4bf3ff0 | |||
0227e1c755 | |||
456365bc7c | |||
789ecd7977 | |||
45bbe642fc | |||
2eb523e2ea | |||
5adc62b970 | |||
fcb8d7b3df | |||
1cb53362d1 | |||
0fad9c0a16 | |||
16208d00ca | |||
8dbcd155e5 | |||
46dd2fb515 | |||
129c8b187a | |||
036b972fbe | |||
51bbaa99e1 | |||
37804c0327 | |||
e1f60cd34d | |||
6d6ab9eeb7 | |||
05b7141478 | |||
c3c77010f6 | |||
0a46aa2c6d | |||
e4057c48f6 | |||
d26fa30655 | |||
137d4e3e8e | |||
1a992708a0 | |||
ff76ec1998 | |||
5df78ee3c3 | |||
549e194bb3 | |||
65631f06a6 | |||
3c1b411a76 | |||
8d8374e826 | |||
41194229d3 | |||
fc1f54a0e4 | |||
8fb4997967 | |||
efa6eb8c34 | |||
944e80e85c | |||
2cb3186661 | |||
10d3e2e3d6 | |||
a4afa7382d | |||
efe252dee3 | |||
fd1ca035f9 | |||
62973b6e2f | |||
501fa45560 | |||
1a8d8ec2f9 | |||
b3b4c27bb4 | |||
44eaaf038a | |||
c38edd089b | |||
8ea828f1b0 | |||
35da6636aa | |||
363827594b | |||
cbc027f144 | |||
ebc6a4b65c | |||
37d47d28a8 | |||
46878519c0 | |||
84c298f4f8 | |||
23aed2b844 | |||
df62d6d51c | |||
26241f00b7 | |||
873d14c824 | |||
4953a112ad | |||
c31e1bf0be | |||
98ddba192f | |||
b02b30eea8 | |||
324d97a9b7 | |||
0a1dd4db01 | |||
b1de9acb20 | |||
960300a7cd | |||
3f084d7527 | |||
64096d599c | |||
552e443ef1 | |||
52cb8d68a0 | |||
372e2b7a1f | |||
a92e63720c | |||
8d121c086e | |||
b538e454aa | |||
60d09d5c6f | |||
266159fe41 | |||
458decb12d | |||
635c2e5513 | |||
adc4223617 | |||
326ae27ad1 | |||
6d33d19816 | |||
734efb391a | |||
cba864507f | |||
9b62a543f6 | |||
5a797e1170 | |||
77475b57b0 | |||
dd296e4408 | |||
1be877132c | |||
7e79fd9721 | |||
5d78aecabb | |||
9fbc894fdb | |||
4930e666ac | |||
e5e337a0a5 | |||
704e604b8f | |||
b5d3cdc9b1 | |||
b8b6b913a9 | |||
f461a40a6d |
6
.github/workflows/build-gnutls.yml
vendored
6
.github/workflows/build-gnutls.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev libsrtp2-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
@ -30,7 +30,7 @@ jobs:
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
|
16
.github/workflows/build-nice.yml
vendored
16
.github/workflows/build-nice.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build-linux:
|
||||
build-media:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -21,4 +21,18 @@ jobs:
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
run: ./build/tests
|
||||
build-no-media:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev libnice-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_NICE=1 -DNO_MEDIA=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
run: ./build/tests
|
||||
|
||||
|
7
.github/workflows/build-openssl.yml
vendored
7
.github/workflows/build-openssl.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libssl-dev
|
||||
run: sudo apt update && sudo apt install libssl-dev libsrtp2-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -DWARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
@ -30,7 +30,7 @@ jobs:
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||
env:
|
||||
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
||||
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
||||
@ -52,6 +52,7 @@ jobs:
|
||||
- name: nmake
|
||||
run: |
|
||||
cd build
|
||||
set CL=/MP
|
||||
nmake
|
||||
- name: test
|
||||
run: build/tests.exe
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -10,3 +10,6 @@
|
||||
[submodule "deps/json"]
|
||||
path = deps/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "deps/libsrtp"]
|
||||
path = deps/libsrtp
|
||||
url = https://github.com/cisco/libsrtp.git
|
||||
|
@ -1,16 +1,20 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
project(libdatachannel
|
||||
DESCRIPTION "WebRTC Data Channels Library"
|
||||
VERSION 0.9.2
|
||||
VERSION 0.10.1
|
||||
LANGUAGES CXX)
|
||||
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
|
||||
|
||||
# Options
|
||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||
option(USE_NICE "Use libnice instead of libjuice" OFF)
|
||||
option(USE_SYSTEM_SRTP "Use system libSRTP" OFF)
|
||||
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
||||
option(NO_MEDIA "Disable media transport support" OFF)
|
||||
option(NO_EXAMPLES "Disable examples" OFF)
|
||||
option(NO_TESTS "Disable tests build" OFF)
|
||||
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||
option(RSA_KEY_BITS_2048 "Use 2048-bit RSA key instead of 3072-bit" OFF)
|
||||
option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
|
||||
|
||||
if(USE_NICE)
|
||||
option(USE_JUICE "Use libjuice" OFF)
|
||||
@ -19,9 +23,12 @@ else()
|
||||
endif()
|
||||
|
||||
if(USE_GNUTLS)
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
||||
option(USE_NETTLE "Use Nettle in libjuice" ON)
|
||||
else()
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
|
||||
option(USE_NETTLE "Use Nettle in libjuice" OFF)
|
||||
if(NOT USE_SYSTEM_SRTP)
|
||||
option(ENABLE_OPENSSL "Enable OpenSSL crypto engine for SRTP" ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
@ -85,6 +92,7 @@ set(LIBDATACHANNEL_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp
|
||||
)
|
||||
@ -107,17 +115,15 @@ set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
||||
add_subdirectory(deps/plog)
|
||||
|
||||
option(sctp_build_programs 0)
|
||||
option(sctp_build_shared_lib 0)
|
||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||
if (MSYS OR MINGW)
|
||||
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||
target_compile_definitions(usrsctp-static PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||
endif()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
|
||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
|
||||
endif()
|
||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
||||
|
||||
if (NO_WEBSOCKET)
|
||||
add_library(datachannel SHARED
|
||||
@ -148,37 +154,45 @@ target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includ
|
||||
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 PUBLIC Threads::Threads plog::plog)
|
||||
target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic)
|
||||
target_link_libraries(datachannel PRIVATE Usrsctp::Usrsctp)
|
||||
|
||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/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 PUBLIC Threads::Threads plog::plog)
|
||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
|
||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
|
||||
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
||||
target_link_libraries(datachannel PRIVATE ws2_32) # winsock2
|
||||
target_link_libraries(datachannel-static PRIVATE ws2_32) # winsock2
|
||||
endif()
|
||||
|
||||
find_package(SRTP)
|
||||
if(SRTP_FOUND)
|
||||
if(NOT TARGET SRTP::SRTP)
|
||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||
set_target_properties(SRTP::SRTP PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
||||
endif()
|
||||
message(STATUS "LibSRTP found, compiling with media transport")
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
||||
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
||||
else()
|
||||
message(STATUS "LibSRTP NOT found, compiling WITHOUT media transport")
|
||||
if(NO_MEDIA)
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
|
||||
else()
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
if(USE_SYSTEM_SRTP)
|
||||
find_package(SRTP REQUIRED)
|
||||
if(NOT TARGET SRTP::SRTP)
|
||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||
set_target_properties(SRTP::SRTP PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
||||
endif()
|
||||
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=1)
|
||||
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=1)
|
||||
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
||||
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
||||
else()
|
||||
add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=0)
|
||||
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=0)
|
||||
target_link_libraries(datachannel PRIVATE srtp2)
|
||||
target_link_libraries(datachannel-static PRIVATE srtp2)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (USE_GNUTLS)
|
||||
@ -217,6 +231,16 @@ else()
|
||||
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||
endif()
|
||||
|
||||
if(RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel PUBLIC RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel-static PUBLIC RSA_KEY_BITS_2048)
|
||||
endif()
|
||||
|
||||
if(CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel PUBLIC CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel-static PUBLIC CAPI_STDCALL)
|
||||
endif()
|
||||
|
||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||
|
||||
@ -244,7 +268,9 @@ if(NOT NO_TESTS)
|
||||
set_target_properties(datachannel-tests PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") # Prevent a bug in manifest generation for UWP
|
||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||
endif()
|
||||
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel-tests datachannel-static) # DLL exports only the C API
|
||||
@ -257,7 +283,9 @@ if(NOT NO_TESTS)
|
||||
set_target_properties(datachannel-benchmark PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") # Prevent a bug in manifest generation for UWP
|
||||
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
||||
endif()
|
||||
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
|
||||
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
if(WIN32)
|
||||
@ -268,11 +296,12 @@ if(NOT NO_TESTS)
|
||||
endif()
|
||||
|
||||
# Examples
|
||||
if(NOT NO_EXAMPLES)
|
||||
if(NOT NO_EXAMPLES AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
add_subdirectory(deps/json)
|
||||
add_subdirectory(examples/client)
|
||||
add_subdirectory(examples/media)
|
||||
add_subdirectory(examples/sfu-media)
|
||||
add_subdirectory(examples/copy-paste)
|
||||
add_subdirectory(examples/copy-paste-capi)
|
||||
endif()
|
||||
|
31
README.md
31
README.md
@ -19,7 +19,7 @@ The WebRTC stack has been tested to be compatible with Firefox and Chromium.
|
||||
|
||||
Protocol stack:
|
||||
- SCTP-based Data Channels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13))
|
||||
- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26)) with [libSRTP](https://github.com/cisco/libsrtp)
|
||||
- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26))
|
||||
- DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261))
|
||||
- ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)) with STUN ([RFC5389](https://tools.ietf.org/html/rfc5389))
|
||||
|
||||
@ -53,10 +53,11 @@ Dependencies:
|
||||
Submodules:
|
||||
- libjuice: https://github.com/paullouisageneau/libjuice
|
||||
- usrsctp: https://github.com/sctplab/usrsctp
|
||||
- libsrtp: https://github.com/cisco/libsrtp
|
||||
|
||||
Optional dependencies:
|
||||
- libnice: https://nice.freedesktop.org/ (only if selected as ICE backend instead of libjuice)
|
||||
- libSRTP: https://github.com/cisco/libsrtp (only necessary for supporting media transport)
|
||||
- libnice: https://nice.freedesktop.org/ (if selected as ICE backend instead of libjuice)
|
||||
- libsrtp: https://github.com/cisco/libsrtp (if selected instead of the submodule)
|
||||
|
||||
## Building
|
||||
|
||||
@ -81,6 +82,29 @@ $ cd build
|
||||
$ make -j2
|
||||
```
|
||||
|
||||
#### Apple macOS with XCode project
|
||||
|
||||
```bash
|
||||
$ cmake -B "$BUILD_DIR" -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode
|
||||
```
|
||||
|
||||
Xcode project is generated in *build/* directory.
|
||||
|
||||
##### Solving **Could NOT find OpenSSL** error
|
||||
|
||||
You need to add OpenSSL root directory if your build fails with the following message:
|
||||
|
||||
```
|
||||
Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the
|
||||
system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY
|
||||
OPENSSL_INCLUDE_DIR)
|
||||
```
|
||||
|
||||
for example:
|
||||
```bash
|
||||
$ cmake -B build -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl\@1.1/1.1.1h/
|
||||
```
|
||||
|
||||
#### Microsoft Windows with MinGW cross-compilation
|
||||
```bash
|
||||
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
|
||||
@ -202,5 +226,6 @@ ws->open("wss://my.websocket/service");
|
||||
## External resources
|
||||
- Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
||||
- Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
|
||||
- Unity wrapper for Windows 10 and Hololens: [datachannel-unity](https://github.com/hanseuljun/datachannel-unity)
|
||||
- WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
|
||||
|
||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: 33612d14ae...7afe3940dd
1
deps/libsrtp
vendored
Submodule
1
deps/libsrtp
vendored
Submodule
Submodule deps/libsrtp added at d02d21111e
2
deps/usrsctp
vendored
2
deps/usrsctp
vendored
Submodule deps/usrsctp updated: 0db9691000...2e754d5822
@ -21,6 +21,7 @@ Revisions:
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
@ -35,7 +36,11 @@ PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
@ -52,12 +57,6 @@ int opterr = 1;
|
||||
int optopt = '?';
|
||||
enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER };
|
||||
|
||||
//
|
||||
//
|
||||
// Ansi structures and functions follow
|
||||
//
|
||||
//
|
||||
|
||||
static struct _getopt_data_a
|
||||
{
|
||||
int optind;
|
||||
@ -512,462 +511,3 @@ int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, con
|
||||
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Unicode Structures and Functions
|
||||
//
|
||||
//
|
||||
|
||||
static struct _getopt_data_w
|
||||
{
|
||||
int optind;
|
||||
int opterr;
|
||||
int optopt;
|
||||
wchar_t *optarg;
|
||||
int __initialized;
|
||||
wchar_t *__nextchar;
|
||||
enum ENUM_ORDERING __ordering;
|
||||
int __posixly_correct;
|
||||
int __first_nonopt;
|
||||
int __last_nonopt;
|
||||
} getopt_data_w;
|
||||
wchar_t *optarg_w;
|
||||
|
||||
static void exchange_w(wchar_t **argv, struct _getopt_data_w *d)
|
||||
{
|
||||
int bottom = d->__first_nonopt;
|
||||
int middle = d->__last_nonopt;
|
||||
int top = d->optind;
|
||||
wchar_t *tem;
|
||||
while (top > middle && middle > bottom)
|
||||
{
|
||||
if (top - middle > middle - bottom)
|
||||
{
|
||||
int len = middle - bottom;
|
||||
int i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
||||
argv[top - (middle - bottom) + i] = tem;
|
||||
}
|
||||
top -= len;
|
||||
}
|
||||
else
|
||||
{
|
||||
int len = top - middle;
|
||||
int i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[middle + i];
|
||||
argv[middle + i] = tem;
|
||||
}
|
||||
bottom += len;
|
||||
}
|
||||
}
|
||||
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
static const wchar_t *_getopt_initialize_w (const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct)
|
||||
{
|
||||
d->__first_nonopt = d->__last_nonopt = d->optind;
|
||||
d->__nextchar = NULL;
|
||||
d->__posixly_correct = posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT");
|
||||
if (optstring[0] == L'-')
|
||||
{
|
||||
d->__ordering = RETURN_IN_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (optstring[0] == L'+')
|
||||
{
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (d->__posixly_correct)
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
else
|
||||
d->__ordering = PERMUTE;
|
||||
return optstring;
|
||||
}
|
||||
int _getopt_internal_r_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct)
|
||||
{
|
||||
int print_errors = d->opterr;
|
||||
if (argc < 1)
|
||||
return -1;
|
||||
d->optarg = NULL;
|
||||
if (d->optind == 0 || !d->__initialized)
|
||||
{
|
||||
if (d->optind == 0)
|
||||
d->optind = 1;
|
||||
optstring = _getopt_initialize_w (optstring, d, posixly_correct);
|
||||
d->__initialized = 1;
|
||||
}
|
||||
else if (optstring[0] == L'-' || optstring[0] == L'+')
|
||||
optstring++;
|
||||
if (optstring[0] == L':')
|
||||
print_errors = 0;
|
||||
if (d->__nextchar == NULL || *d->__nextchar == L'\0')
|
||||
{
|
||||
if (d->__last_nonopt > d->optind)
|
||||
d->__last_nonopt = d->optind;
|
||||
if (d->__first_nonopt > d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
if (d->__ordering == PERMUTE)
|
||||
{
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_w((wchar_t **) argv, d);
|
||||
else if (d->__last_nonopt != d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
while (d->optind < argc && (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
|
||||
d->optind++;
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
if (d->optind != argc && !wcscmp(argv[d->optind], L"--"))
|
||||
{
|
||||
d->optind++;
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_w((wchar_t **) argv, d);
|
||||
else if (d->__first_nonopt == d->__last_nonopt)
|
||||
d->__first_nonopt = d->optind;
|
||||
d->__last_nonopt = argc;
|
||||
d->optind = argc;
|
||||
}
|
||||
if (d->optind == argc)
|
||||
{
|
||||
if (d->__first_nonopt != d->__last_nonopt)
|
||||
d->optind = d->__first_nonopt;
|
||||
return -1;
|
||||
}
|
||||
if ((argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
|
||||
{
|
||||
if (d->__ordering == REQUIRE_ORDER)
|
||||
return -1;
|
||||
d->optarg = argv[d->optind++];
|
||||
return 1;
|
||||
}
|
||||
d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == L'-'));
|
||||
}
|
||||
if (longopts != NULL && (argv[d->optind][1] == L'-' || (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1])))))
|
||||
{
|
||||
wchar_t *nameend;
|
||||
unsigned int namelen;
|
||||
const struct option_w *p;
|
||||
const struct option_w *pfound = NULL;
|
||||
struct option_list
|
||||
{
|
||||
const struct option_w *p;
|
||||
struct option_list *next;
|
||||
} *ambig_list = NULL;
|
||||
int exact = 0;
|
||||
int indfound = -1;
|
||||
int option_index;
|
||||
for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++);
|
||||
namelen = (unsigned int)(nameend - d->__nextchar);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!wcsncmp(p->name, d->__nextchar, namelen))
|
||||
{
|
||||
if (namelen == (unsigned int)wcslen(p->name))
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
exact = 1;
|
||||
break;
|
||||
}
|
||||
else if (pfound == NULL)
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
||||
{
|
||||
struct option_list *newp = (struct option_list*)alloca(sizeof(*newp));
|
||||
newp->p = p;
|
||||
newp->next = ambig_list;
|
||||
ambig_list = newp;
|
||||
}
|
||||
}
|
||||
if (ambig_list != NULL && !exact)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
struct option_list first;
|
||||
first.p = pfound;
|
||||
first.next = ambig_list;
|
||||
ambig_list = &first;
|
||||
fwprintf(stderr, L"%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]);
|
||||
do
|
||||
{
|
||||
fwprintf (stderr, L" '--%s'", ambig_list->p->name);
|
||||
ambig_list = ambig_list->next;
|
||||
}
|
||||
while (ambig_list != NULL);
|
||||
fputwc (L'\n', stderr);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return L'?';
|
||||
}
|
||||
if (pfound != NULL)
|
||||
{
|
||||
option_index = indfound;
|
||||
d->optind++;
|
||||
if (*nameend)
|
||||
{
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
if (argv[d->optind - 1][1] == L'-')
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name);
|
||||
}
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return L'?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr,L"%s: option '--%s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return optstring[0] == L':' ? L':' : L'?';
|
||||
}
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
if (pfound->flag)
|
||||
{
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
if (argv[d->optind][1] == L'-')
|
||||
{
|
||||
fwprintf(stderr, L"%s: unrecognized option '--%s'\n",argv[0], d->__nextchar);
|
||||
}
|
||||
else
|
||||
{
|
||||
fwprintf(stderr, L"%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar);
|
||||
}
|
||||
}
|
||||
d->__nextchar = (wchar_t *)L"";
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return L'?';
|
||||
}
|
||||
}
|
||||
{
|
||||
wchar_t c = *d->__nextchar++;
|
||||
wchar_t *temp = (wchar_t*)wcschr(optstring, c);
|
||||
if (*d->__nextchar == L'\0')
|
||||
++d->optind;
|
||||
if (temp == NULL || c == L':' || c == L';')
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
return L'?';
|
||||
}
|
||||
if (temp[0] == L'W' && temp[1] == L';')
|
||||
{
|
||||
wchar_t *nameend;
|
||||
const struct option_w *p;
|
||||
const struct option_w *pfound = NULL;
|
||||
int exact = 0;
|
||||
int ambig = 0;
|
||||
int indfound = 0;
|
||||
int option_index;
|
||||
if (longopts == NULL)
|
||||
goto no_longs;
|
||||
if (*d->__nextchar != L'\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == L':')
|
||||
c = L':';
|
||||
else
|
||||
c = L'?';
|
||||
return c;
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != L'='; nameend++);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!wcsncmp(p->name, d->__nextchar, nameend - d->__nextchar))
|
||||
{
|
||||
if ((unsigned int) (nameend - d->__nextchar) == wcslen(p->name))
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
exact = 1;
|
||||
break;
|
||||
}
|
||||
else if (pfound == NULL)
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
||||
ambig = 1;
|
||||
}
|
||||
if (ambig && !exact)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '-W %s' is ambiguous\n",argv[0], d->optarg);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
d->optind++;
|
||||
return L'?';
|
||||
}
|
||||
if (pfound != NULL)
|
||||
{
|
||||
option_index = indfound;
|
||||
if (*nameend)
|
||||
{
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
return L'?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '-W %s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
return optstring[0] == L':' ? L':' : L'?';
|
||||
}
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
if (pfound->flag)
|
||||
{
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
no_longs:
|
||||
d->__nextchar = NULL;
|
||||
return L'W';
|
||||
}
|
||||
if (temp[1] == L':')
|
||||
{
|
||||
if (temp[2] == L':')
|
||||
{
|
||||
if (*d->__nextchar != L'\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*d->__nextchar != L'\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == L':')
|
||||
c = L':';
|
||||
else
|
||||
c = L'?';
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
int _getopt_internal_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct)
|
||||
{
|
||||
int result;
|
||||
getopt_data_w.optind = optind;
|
||||
getopt_data_w.opterr = opterr;
|
||||
result = _getopt_internal_r_w (argc, argv, optstring, longopts,longind, long_only, &getopt_data_w,posixly_correct);
|
||||
optind = getopt_data_w.optind;
|
||||
optarg_w = getopt_data_w.optarg;
|
||||
optopt = getopt_data_w.optopt;
|
||||
return result;
|
||||
}
|
||||
int getopt_w (int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_w (argc, argv, optstring, (const struct option_w *) 0, (int *) 0, 0, 0);
|
||||
}
|
||||
int getopt_long_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_w (argc, argv, options, long_options, opt_index, 0, 0);
|
||||
}
|
||||
int getopt_long_only_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_w (argc, argv, options, long_options, opt_index, 1, 0);
|
||||
}
|
||||
int _getopt_long_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
|
||||
{
|
||||
return _getopt_internal_r_w (argc, argv, options, long_options, opt_index,0, d, 0);
|
||||
}
|
||||
int _getopt_long_only_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
|
||||
{
|
||||
return _getopt_internal_r_w (argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||
}
|
@ -21,6 +21,7 @@ Revisions:
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
@ -79,7 +80,6 @@ EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
#define ARG_OPT 2 /*Argument Optional*/
|
||||
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
_BEGIN_EXTERN_C
|
||||
|
||||
@ -87,7 +87,6 @@ _BEGIN_EXTERN_C
|
||||
extern _GETOPT_API int opterr;
|
||||
extern _GETOPT_API int optopt;
|
||||
|
||||
// Ansi
|
||||
struct option_a
|
||||
{
|
||||
const char* name;
|
||||
@ -100,19 +99,6 @@ _BEGIN_EXTERN_C
|
||||
extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||
|
||||
// Unicode
|
||||
struct option_w
|
||||
{
|
||||
const wchar_t* name;
|
||||
int has_arg;
|
||||
int *flag;
|
||||
int val;
|
||||
};
|
||||
extern _GETOPT_API wchar_t *optarg_w;
|
||||
extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
||||
|
||||
_END_EXTERN_C
|
||||
|
||||
#undef _BEGIN_EXTERN_C
|
||||
@ -120,17 +106,10 @@ _END_EXTERN_C
|
||||
#undef _GETOPT_THROW
|
||||
#undef _GETOPT_API
|
||||
|
||||
#ifdef _UNICODE
|
||||
#define getopt getopt_w
|
||||
#define getopt_long getopt_long_w
|
||||
#define getopt_long_only getopt_long_only_w
|
||||
#define option option_w
|
||||
#define optarg optarg_w
|
||||
#else
|
||||
#define getopt getopt_a
|
||||
#define getopt_long getopt_long_a
|
||||
#define getopt_long_only getopt_long_only_a
|
||||
#define option option_a
|
||||
#define optarg optarg_a
|
||||
#endif
|
||||
#define getopt getopt_a
|
||||
#define getopt_long getopt_long_a
|
||||
#define getopt_long_only getopt_long_only_a
|
||||
#define option option_a
|
||||
#define optarg optarg_a
|
||||
|
||||
#endif // __GETOPT_H_
|
||||
|
@ -30,6 +30,8 @@
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <future>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include "parse_cl.h"
|
||||
|
||||
@ -49,25 +51,19 @@ bool echoDataChannelMessages = false;
|
||||
|
||||
shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
weak_ptr<WebSocket> wws, string id);
|
||||
void confirmOnStdout(bool echoed, string id, string type, size_t length);
|
||||
void printReceived(bool echoed, string id, string type, size_t length);
|
||||
string randomId(size_t length);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Cmdline *params = nullptr;
|
||||
try {
|
||||
params = new Cmdline(argc, argv);
|
||||
} catch (const std::range_error&e) {
|
||||
std::cout<< e.what() << '\n';
|
||||
delete params;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) try {
|
||||
auto params = std::make_unique<Cmdline>(argc, argv);
|
||||
|
||||
rtc::InitLogger(LogLevel::Debug);
|
||||
|
||||
Configuration config;
|
||||
string stunServer = "";
|
||||
if (params->noStun()) {
|
||||
cout << "No stun server is configured. Only local hosts and public IP addresses suported." << endl;
|
||||
cout << "No STUN server is configured. Only local hosts and public IP addresses supported." << endl;
|
||||
} else {
|
||||
if (params->stunServer().substr(0,5).compare("stun:") != 0) {
|
||||
stunServer = "stun:";
|
||||
@ -86,12 +82,21 @@ int main(int argc, char **argv) {
|
||||
|
||||
auto ws = make_shared<WebSocket>();
|
||||
|
||||
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
||||
std::promise<void> wsPromise;
|
||||
auto wsFuture = wsPromise.get_future();
|
||||
|
||||
ws->onOpen([&wsPromise]() {
|
||||
cout << "WebSocket connected, signaling ready" << endl;
|
||||
wsPromise.set_value();
|
||||
});
|
||||
|
||||
ws->onError([&wsPromise](string s) {
|
||||
cout << "WebSocket error" << endl;
|
||||
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
|
||||
});
|
||||
|
||||
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
||||
|
||||
ws->onError([](const string &error) { cout << "WebSocket failed: " << error << endl; });
|
||||
|
||||
|
||||
ws->onMessage([&](variant<binary, string> data) {
|
||||
if (!holds_alternative<string>(data))
|
||||
return;
|
||||
@ -136,13 +141,9 @@ int main(int argc, char **argv) {
|
||||
to_string(params->webSocketPort()) + "/" + localId;
|
||||
cout << "Url is " << url << endl;
|
||||
ws->open(url);
|
||||
|
||||
|
||||
cout << "Waiting for signaling to be connected..." << endl;
|
||||
while (!ws->isOpen()) {
|
||||
if (ws->isClosed())
|
||||
return 1;
|
||||
this_thread::sleep_for(100ms);
|
||||
}
|
||||
wsFuture.get();
|
||||
|
||||
while (true) {
|
||||
string id;
|
||||
@ -181,22 +182,25 @@ int main(int argc, char **argv) {
|
||||
dc->send(message);
|
||||
echoed = true;
|
||||
}
|
||||
confirmOnStdout(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
||||
get<string>(message).length());
|
||||
printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
||||
get<string>(message).length());
|
||||
}
|
||||
});
|
||||
|
||||
dataChannelMap.emplace(id, dc);
|
||||
|
||||
this_thread::sleep_for(1s);
|
||||
}
|
||||
|
||||
cout << "Cleaning up..." << endl;
|
||||
|
||||
dataChannelMap.clear();
|
||||
peerConnectionMap.clear();
|
||||
delete params;
|
||||
return 0;
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cout << "Error: " << e.what() << std::endl;
|
||||
dataChannelMap.clear();
|
||||
peerConnectionMap.clear();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create and setup a PeerConnection
|
||||
@ -244,7 +248,7 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
dc->send(message);
|
||||
echoed = true;
|
||||
}
|
||||
confirmOnStdout(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
||||
printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
||||
get<string>(message).length());
|
||||
}
|
||||
});
|
||||
@ -258,11 +262,12 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
return pc;
|
||||
};
|
||||
|
||||
void confirmOnStdout(bool echoed, string id, string type, size_t length) {
|
||||
// Helper function to print received pings
|
||||
void printReceived(bool echoed, string id, string type, size_t length) {
|
||||
static long count = 0;
|
||||
static long freq = 100;
|
||||
if (!(++count%freq)) {
|
||||
cout << "Received " << count << " pings in total from host " << id << ", most recent of type "
|
||||
cout << "Received " << count << " pings in total from " << id << ", most recent of type "
|
||||
<< type << " and " << (echoed ? "" : "un") << "successfully echoed most recent ping of size "
|
||||
<< length << " back to " << id << endl;
|
||||
if (count >= (freq * 10) && freq < 1000000) {
|
||||
|
@ -34,23 +34,23 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr);
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
static void closedCallback(void *ptr);
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||
static void RTC_API closedCallback(int id, void *ptr);
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
char* state_print(rtcState state);
|
||||
char *state_print(rtcState state);
|
||||
char *rtcGatheringState_print(rtcGatheringState state);
|
||||
|
||||
int all_space(const char *str);
|
||||
@ -66,189 +66,185 @@ int main(int argc, char **argv) {
|
||||
if (!peer) {
|
||||
fprintf(stderr, "Error allocating memory for peer\n");
|
||||
return -1;
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
printf("Peer created\n");
|
||||
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
|
||||
rtcSetUserPointer(peer->dc, NULL);
|
||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||
rtcSetUserPointer(peer->dc, NULL);
|
||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||
|
||||
sleep(1);
|
||||
sleep(1);
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
printf("\n");
|
||||
printf("***********************************************************************************"
|
||||
"****\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
int command = -1;
|
||||
int c;
|
||||
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char*) malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
||||
strcat(sdp, line);
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char *)malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||
strcat(sdp, line);
|
||||
}
|
||||
printf("%s\n", sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char *candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
|
||||
}
|
||||
printf("%s\n",sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
if (getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char* candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if(getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char *message = NULL;
|
||||
size_t message_size = 0;
|
||||
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
if (getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char* message = NULL;
|
||||
size_t message_size = 0;
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "answerer", cand);
|
||||
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
printf("Candidate %s: %s\n", "answerer", cand);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "answerer", state_print(state));
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "answerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
static void RTC_API closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "answerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
||||
}
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "answerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
||||
}
|
||||
}
|
||||
|
||||
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
rtcSetClosedCallback(dc, closedCallback);
|
||||
rtcSetMessageCallback(dc, messageCallback);
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||
}
|
||||
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
rtcSetClosedCallback(dc, closedCallback);
|
||||
rtcSetMessageCallback(dc, messageCallback);
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
char *state_print(rtcState state) {
|
||||
|
@ -34,20 +34,20 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
static void openCallback(void *ptr);
|
||||
static void closedCallback(void *ptr);
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||
static void RTC_API openCallback(int id, void *ptr);
|
||||
static void RTC_API closedCallback(int id, void *ptr);
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
char *state_print(rtcState state);
|
||||
@ -55,7 +55,7 @@ char *rtcGatheringState_print(rtcGatheringState state);
|
||||
|
||||
int all_space(const char *str);
|
||||
|
||||
int main(int argc, char **argv){
|
||||
int main(int argc, char **argv) {
|
||||
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
||||
|
||||
// Create peer
|
||||
@ -66,192 +66,185 @@ int main(int argc, char **argv){
|
||||
if (!peer) {
|
||||
fprintf(stderr, "Error allocating memory for peer\n");
|
||||
return -1;
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
printf("Peer created\n");
|
||||
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
|
||||
// Since this is the offere, we will create a datachannel
|
||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||
rtcSetOpenCallback(peer->dc, openCallback);
|
||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||
rtcSetMessageCallback(peer->dc, messageCallback);
|
||||
// Since we are the offerer, we will create a datachannel
|
||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||
rtcSetOpenCallback(peer->dc, openCallback);
|
||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||
rtcSetMessageCallback(peer->dc, messageCallback);
|
||||
|
||||
sleep(1);
|
||||
sleep(1);
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
printf("\n");
|
||||
printf("***********************************************************************************"
|
||||
"****\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
int command = -1;
|
||||
int c;
|
||||
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char *)malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||
strcat(sdp, line);
|
||||
}
|
||||
printf("%s\n", sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char *candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
if (getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char*) malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
||||
strcat(sdp, line);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
printf("%s\n",sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char *message = NULL;
|
||||
size_t message_size = 0;
|
||||
if (getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char* candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
if(getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char* message = NULL;
|
||||
size_t message_size = 0;
|
||||
if(getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "offerer", cand);
|
||||
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
printf("Candidate %s: %s\n", "offerer", cand);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "offerer", state_print(state));
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "offerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
||||
static void RTC_API openCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "offerer", buffer);
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
static void RTC_API closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "offerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "offerer", size);
|
||||
}
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "offerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "offerer", size);
|
||||
}
|
||||
}
|
||||
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
char *state_print(rtcState state) {
|
||||
|
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
Candidate local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Local: " << local << endl;
|
||||
cout << "Remote: " << remote << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
|
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
Candidate local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Local: " << local << endl;
|
||||
cout << "Remote: " << remote << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* libdatachannel client example
|
||||
* Copyright (c) 2020 Staz M
|
||||
* Copyright (c) 2020 Staz Modrzynski
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -67,7 +67,7 @@ int main() {
|
||||
|
||||
auto track = pc->addTrack(media);
|
||||
|
||||
auto session = std::make_shared<rtc::RtcpSession>();
|
||||
auto session = std::make_shared<rtc::RtcpReceivingSession>();
|
||||
track->setRtcpHandler(session);
|
||||
|
||||
track->onMessage(
|
||||
|
15
examples/sfu-media/CMakeLists.txt
Normal file
15
examples/sfu-media/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
add_executable(datachannel-sfu-media main.cpp)
|
||||
set_target_properties(datachannel-sfu-media PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME sfu-media)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel-sfu-media datachannel-static) # DLL exports only the C API
|
||||
else()
|
||||
target_link_libraries(datachannel-sfu-media datachannel)
|
||||
endif()
|
||||
|
||||
target_link_libraries(datachannel-sfu-media datachannel nlohmann_json)
|
||||
|
134
examples/sfu-media/main.cpp
Normal file
134
examples/sfu-media/main.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* libdatachannel client example
|
||||
* Copyright (c) 2020 Staz Modrzynski
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
struct Receiver {
|
||||
std::shared_ptr<rtc::PeerConnection> conn;
|
||||
std::shared_ptr<rtc::Track> track;
|
||||
};
|
||||
int main() {
|
||||
std::vector<std::shared_ptr<Receiver>> receivers;
|
||||
|
||||
try {
|
||||
rtc::InitLogger(rtc::LogLevel::Info);
|
||||
|
||||
auto pc = std::make_shared<rtc::PeerConnection>();
|
||||
pc->onStateChange(
|
||||
[](rtc::PeerConnection::State state) { std::cout << "State: " << state << std::endl; });
|
||||
pc->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
|
||||
std::cout << "Gathering State: " << state << std::endl;
|
||||
if (state == rtc::PeerConnection::GatheringState::Complete) {
|
||||
auto description = pc->localDescription();
|
||||
json message = {{"type", description->typeString()},
|
||||
{"sdp", std::string(description.value())}};
|
||||
std::cout << "Please copy/paste this offer to the SENDER: " << message << std::endl;
|
||||
}
|
||||
});
|
||||
|
||||
rtc::Description::Video media("video", rtc::Description::Direction::RecvOnly);
|
||||
media.addH264Codec(96);
|
||||
media.setBitrate(
|
||||
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
||||
|
||||
auto track = pc->addTrack(media);
|
||||
pc->setLocalDescription();
|
||||
|
||||
auto session = std::make_shared<rtc::RtcpReceivingSession>();
|
||||
track->setRtcpHandler(session);
|
||||
|
||||
const rtc::SSRC targetSSRC = 4;
|
||||
|
||||
track->onMessage(
|
||||
[&receivers, targetSSRC](rtc::binary message) {
|
||||
// This is an RTP packet
|
||||
auto rtp = (rtc::RTP *)message.data();
|
||||
rtp->setSsrc(targetSSRC);
|
||||
for (auto pc : receivers) {
|
||||
if (pc->track != nullptr && pc->track->isOpen()) {
|
||||
pc->track->send(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
nullptr);
|
||||
|
||||
// Set the SENDERS Answer
|
||||
{
|
||||
std::cout << "Please copy/paste the answer provided by the SENDER: " << std::endl;
|
||||
std::string sdp;
|
||||
std::getline(std::cin, sdp);
|
||||
std::cout << "Got answer" << sdp << std::endl;
|
||||
json j = json::parse(sdp);
|
||||
rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
|
||||
pc->setRemoteDescription(answer);
|
||||
}
|
||||
|
||||
// For each receiver
|
||||
while (true) {
|
||||
auto pc = std::make_shared<Receiver>();
|
||||
pc->conn = std::make_shared<rtc::PeerConnection>();
|
||||
pc->conn->onStateChange([](rtc::PeerConnection::State state) {
|
||||
std::cout << "State: " << state << std::endl;
|
||||
});
|
||||
pc->conn->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
|
||||
std::cout << "Gathering State: " << state << std::endl;
|
||||
if (state == rtc::PeerConnection::GatheringState::Complete) {
|
||||
auto description = pc->conn->localDescription();
|
||||
json message = {{"type", description->typeString()},
|
||||
{"sdp", std::string(description.value())}};
|
||||
std::cout << "Please copy/paste this offer to the RECEIVER: " << message
|
||||
<< std::endl;
|
||||
}
|
||||
});
|
||||
rtc::Description::Video media("video", rtc::Description::Direction::SendOnly);
|
||||
media.addH264Codec(96);
|
||||
media.setBitrate(
|
||||
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
||||
|
||||
media.addSSRC(targetSSRC, "video-send");
|
||||
|
||||
pc->track = pc->conn->addTrack(media);
|
||||
pc->conn->setLocalDescription();
|
||||
|
||||
pc->track->onMessage([](rtc::binary var) {}, nullptr);
|
||||
|
||||
std::cout << "Please copy/paste the answer provided by the RECEIVER: " << std::endl;
|
||||
std::string sdp;
|
||||
std::getline(std::cin, sdp);
|
||||
std::cout << "Got answer" << sdp << std::endl;
|
||||
json j = json::parse(sdp);
|
||||
rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
|
||||
pc->conn->setRemoteDescription(answer);
|
||||
|
||||
receivers.push_back(pc);
|
||||
}
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
87
examples/sfu-media/main.html
Normal file
87
examples/sfu-media/main.html
Normal file
@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>libdatachannel media example</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div style="display:inline-block; width:40%;">
|
||||
<h1>SENDER</h1>
|
||||
<p id="send-help">Please enter the offer provided to you by the application: </p>
|
||||
<textarea style="width:100%;" id=send-text rows="50"></textarea>
|
||||
<button id=send-btn>Submit</button>
|
||||
</div>
|
||||
<div style="display:inline-block; width:40%;">
|
||||
<h1>RECEIVER</h1>
|
||||
<p id="recv-help">Please enter the offer provided to you by the application: </p>
|
||||
<textarea id=recv-text style="width:100%;" rows="50"></textarea>
|
||||
<button id=recv-btn>Submit</button>
|
||||
</div>
|
||||
<div id="videos">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
document.querySelector('#send-btn').addEventListener('click', async () => {
|
||||
let offer = JSON.parse(document.querySelector('#send-text').value);
|
||||
rtc = new RTCPeerConnection({
|
||||
// Recommended for libdatachannel
|
||||
bundlePolicy: "max-bundle",
|
||||
});
|
||||
|
||||
rtc.onicegatheringstatechange = (state) => {
|
||||
if (rtc.iceGatheringState === 'complete') {
|
||||
// We only want to provide an answer once all of our candidates have been added to the SDP.
|
||||
let answer = rtc.localDescription;
|
||||
document.querySelector('#send-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
|
||||
document.querySelector('#send-help').value = 'Please paste the answer in the application.';
|
||||
alert('Please paste the answer in the application.');
|
||||
}
|
||||
}
|
||||
await rtc.setRemoteDescription(offer);
|
||||
|
||||
let media = await navigator.mediaDevices.getUserMedia({
|
||||
video: {
|
||||
width: 1280,
|
||||
height: 720
|
||||
}
|
||||
});
|
||||
media.getTracks().forEach(track => rtc.addTrack(track, media));
|
||||
let answer = await rtc.createAnswer();
|
||||
await rtc.setLocalDescription(answer);
|
||||
});
|
||||
|
||||
document.querySelector('#recv-btn').addEventListener('click', async () => {
|
||||
let offer = JSON.parse(document.querySelector('#recv-text').value);
|
||||
rtc = new RTCPeerConnection({
|
||||
// Recommended for libdatachannel
|
||||
bundlePolicy: "max-bundle",
|
||||
});
|
||||
|
||||
rtc.onicegatheringstatechange = (state) => {
|
||||
if (rtc.iceGatheringState === 'complete') {
|
||||
// We only want to provide an answer once all of our candidates have been added to the SDP.
|
||||
let answer = rtc.localDescription;
|
||||
document.querySelector('#recv-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
|
||||
document.querySelector('#recv-help').value = 'Please paste the answer in the application.';
|
||||
alert('Please paste the answer in the application.');
|
||||
}
|
||||
}
|
||||
let trackCount = 0;
|
||||
rtc.ontrack = (ev) => {
|
||||
let thisID = trackCount++;
|
||||
|
||||
document.querySelector("#videos").innerHTML += "<video width=100% height=100% id='video-" + thisID + "'></video>";
|
||||
let tracks = [];
|
||||
rtc.getReceivers().forEach(recv => tracks.push(recv.track));
|
||||
document.querySelector("#video-" + thisID).srcObject = new MediaStream(tracks);
|
||||
document.querySelector("#video-" + thisID).play();
|
||||
};
|
||||
await rtc.setRemoteDescription(offer);
|
||||
let answer = await rtc.createAnswer();
|
||||
await rtc.setLocalDescription(answer);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -55,7 +55,7 @@ function openSignaling(url) {
|
||||
const ws = new WebSocket(url);
|
||||
ws.onopen = () => resolve(ws);
|
||||
ws.onerror = () => reject(new Error('WebSocket error'));
|
||||
ws.onclosed = () => console.error('WebSocket disconnected');
|
||||
ws.onclose = () => console.error('WebSocket disconnected');
|
||||
ws.onmessage = (e) => {
|
||||
if(typeof(e.data) != 'string') return;
|
||||
const message = JSON.parse(e.data);
|
||||
|
@ -25,38 +25,50 @@
|
||||
|
||||
namespace rtc {
|
||||
|
||||
enum class CandidateType { Host = 0, ServerReflexive, PeerReflexive, Relayed };
|
||||
enum class CandidateTransportType { Udp = 0, TcpActive, TcpPassive, TcpSo };
|
||||
struct CandidateInfo {
|
||||
string address;
|
||||
int port;
|
||||
CandidateType type;
|
||||
CandidateTransportType transportType;
|
||||
};
|
||||
|
||||
class Candidate {
|
||||
public:
|
||||
Candidate(string candidate, string mid = "");
|
||||
enum class Family { Unresolved, Ipv4, Ipv6 };
|
||||
enum class Type { Unknown, Host, ServerReflexive, PeerReflexive, Relayed };
|
||||
enum class TransportType { Unknown, Udp, TcpActive, TcpPassive, TcpSo, TcpUnknown };
|
||||
|
||||
Candidate();
|
||||
Candidate(string candidate);
|
||||
Candidate(string candidate, string mid);
|
||||
|
||||
void hintMid(string mid);
|
||||
|
||||
enum class ResolveMode { Simple, Lookup };
|
||||
bool resolve(ResolveMode mode = ResolveMode::Simple);
|
||||
bool isResolved() const;
|
||||
|
||||
string candidate() const;
|
||||
string mid() const;
|
||||
operator string() const;
|
||||
|
||||
bool isResolved() const;
|
||||
Family family() const;
|
||||
Type type() const;
|
||||
TransportType transportType() const;
|
||||
std::optional<string> address() const;
|
||||
std::optional<uint16_t> port() const;
|
||||
std::optional<uint32_t> priority() const;
|
||||
|
||||
private:
|
||||
string mCandidate;
|
||||
string mMid;
|
||||
bool mIsResolved;
|
||||
std::optional<string> mMid;
|
||||
|
||||
// Extracted on resolution
|
||||
Family mFamily;
|
||||
Type mType;
|
||||
TransportType mTransportType;
|
||||
string mAddress;
|
||||
uint16_t mPort;
|
||||
uint32_t mPriority;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -54,7 +54,8 @@ public:
|
||||
|
||||
// Extended API
|
||||
virtual std::optional<message_variant> receive() = 0; // only if onMessage unset
|
||||
virtual size_t availableAmount() const; // total size available to receive
|
||||
virtual std::optional<message_variant> peek() = 0; // only if onMessage unset
|
||||
virtual size_t availableAmount() const; // total size available to receive
|
||||
void onAvailable(std::function<void()> callback);
|
||||
|
||||
protected:
|
||||
@ -81,4 +82,3 @@ private:
|
||||
} // namespace rtc
|
||||
|
||||
#endif // RTC_CHANNEL_H
|
||||
|
||||
|
@ -36,15 +36,14 @@ namespace rtc {
|
||||
class SctpTransport;
|
||||
class PeerConnection;
|
||||
|
||||
class DataChannel final : public std::enable_shared_from_this<DataChannel>, public Channel {
|
||||
class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel {
|
||||
public:
|
||||
DataChannel(std::weak_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||
string protocol, Reliability reliability);
|
||||
DataChannel(std::weak_ptr<PeerConnection> pc, std::weak_ptr<SctpTransport> transport,
|
||||
unsigned int stream);
|
||||
~DataChannel();
|
||||
DataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream, string label, string protocol,
|
||||
Reliability reliability);
|
||||
virtual ~DataChannel();
|
||||
|
||||
unsigned int stream() const;
|
||||
uint16_t stream() const;
|
||||
uint16_t id() const;
|
||||
string label() const;
|
||||
string protocol() const;
|
||||
Reliability reliability() const;
|
||||
@ -62,18 +61,19 @@ public:
|
||||
// Extended API
|
||||
size_t availableAmount() const override;
|
||||
std::optional<message_variant> receive() override;
|
||||
std::optional<message_variant> peek() override;
|
||||
|
||||
private:
|
||||
protected:
|
||||
virtual void open(std::shared_ptr<SctpTransport> transport);
|
||||
virtual void processOpenMessage(message_ptr message);
|
||||
void remoteClose();
|
||||
void open(std::shared_ptr<SctpTransport> transport);
|
||||
bool outgoing(message_ptr message);
|
||||
void incoming(message_ptr message);
|
||||
void processOpenMessage(message_ptr message);
|
||||
|
||||
const std::weak_ptr<PeerConnection> mPeerConnection;
|
||||
std::weak_ptr<SctpTransport> mSctpTransport;
|
||||
|
||||
unsigned int mStream;
|
||||
uint16_t mStream;
|
||||
string mLabel;
|
||||
string mProtocol;
|
||||
std::shared_ptr<Reliability> mReliability;
|
||||
@ -81,11 +81,27 @@ private:
|
||||
std::atomic<bool> mIsOpen = false;
|
||||
std::atomic<bool> mIsClosed = false;
|
||||
|
||||
private:
|
||||
Queue<message_ptr> mRecvQueue;
|
||||
|
||||
friend class PeerConnection;
|
||||
};
|
||||
|
||||
class NegociatedDataChannel final : public DataChannel {
|
||||
public:
|
||||
NegociatedDataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream, string label,
|
||||
string protocol, Reliability reliability);
|
||||
NegociatedDataChannel(std::weak_ptr<PeerConnection> pc, std::weak_ptr<SctpTransport> transport,
|
||||
uint16_t stream);
|
||||
~NegociatedDataChannel();
|
||||
|
||||
private:
|
||||
void open(std::shared_ptr<SctpTransport> transport) override;
|
||||
void processOpenMessage(message_ptr message) override;
|
||||
|
||||
friend class PeerConnection;
|
||||
};
|
||||
|
||||
template <typename Buffer> std::pair<const byte *, size_t> to_bytes(const Buffer &buf) {
|
||||
using T = typename std::remove_pointer<decltype(buf.data())>::type;
|
||||
using E = typename std::conditional<std::is_void<T>::value, byte, T>::type;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||
* Copyright (c) 2020 Staz M
|
||||
* Copyright (c) 2020 Staz Modrzynski
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -34,21 +34,19 @@ namespace rtc {
|
||||
|
||||
class Description {
|
||||
public:
|
||||
enum class Type { Unspec = 0, Offer = 1, Answer = 2 };
|
||||
enum class Role { ActPass = 0, Passive = 1, Active = 2 };
|
||||
enum class Type { Unspec, Offer, Answer, Pranswer, Rollback };
|
||||
enum class Role { ActPass, Passive, Active };
|
||||
enum class Direction { SendOnly, RecvOnly, SendRecv, Inactive, Unknown };
|
||||
|
||||
Description(const string &sdp, const string &typeString = "");
|
||||
Description(const string &sdp, Type type);
|
||||
Description(const string &sdp, Type type, Role role);
|
||||
Description(const string &sdp, Type type = Type::Unspec, Role role = Role::ActPass);
|
||||
Description(const string &sdp, string typeString);
|
||||
|
||||
Type type() const;
|
||||
string typeString() const;
|
||||
Role role() const;
|
||||
string roleString() const;
|
||||
string bundleMid() const;
|
||||
string iceUfrag() const;
|
||||
string icePwd() const;
|
||||
std::optional<string> iceUfrag() const;
|
||||
std::optional<string> icePwd() const;
|
||||
std::optional<string> fingerprint() const;
|
||||
bool ended() const;
|
||||
|
||||
@ -56,6 +54,7 @@ public:
|
||||
void setFingerprint(string fingerprint);
|
||||
|
||||
void addCandidate(Candidate candidate);
|
||||
void addCandidates(std::vector<Candidate> candidates);
|
||||
void endCandidates();
|
||||
std::vector<Candidate> extractCandidates();
|
||||
|
||||
@ -74,10 +73,14 @@ public:
|
||||
void setDirection(Direction dir);
|
||||
|
||||
operator string() const;
|
||||
string generateSdp(string_view eol) const;
|
||||
string generateSdp(string_view eol, string_view addr, string_view port) const;
|
||||
|
||||
virtual void parseSdpLine(string_view line);
|
||||
|
||||
std::vector<string>::iterator beginAttributes();
|
||||
std::vector<string>::iterator endAttributes();
|
||||
std::vector<string>::iterator removeAttribute(std::vector<string>::iterator iterator);
|
||||
|
||||
protected:
|
||||
Entry(const string &mline, string mid, Direction dir = Direction::Unknown);
|
||||
virtual string generateSdpLines(string_view eol) const;
|
||||
@ -94,8 +97,7 @@ public:
|
||||
struct Application : public Entry {
|
||||
public:
|
||||
Application(string mid = "data");
|
||||
Application(const Application &other) = default;
|
||||
Application(Application &&other) = default;
|
||||
virtual ~Application() = default;
|
||||
|
||||
string description() const override;
|
||||
Application reciprocate() const;
|
||||
@ -121,8 +123,6 @@ public:
|
||||
public:
|
||||
Media(const string &sdp);
|
||||
Media(const string &mline, string mid, Direction dir = Direction::SendOnly);
|
||||
Media(const Media &other) = default;
|
||||
Media(Media &&other) = default;
|
||||
virtual ~Media() = default;
|
||||
|
||||
string description() const override;
|
||||
@ -130,10 +130,11 @@ public:
|
||||
|
||||
void removeFormat(const string &fmt);
|
||||
|
||||
void addVideoCodec(int payloadType, const string &codec);
|
||||
void addH264Codec(int payloadType);
|
||||
void addVP8Codec(int payloadType);
|
||||
void addVP9Codec(int payloadType);
|
||||
void addSSRC(uint32_t ssrc, std::string name);
|
||||
void addSSRC(uint32_t ssrc);
|
||||
void replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, string name);
|
||||
bool hasSSRC(uint32_t ssrc);
|
||||
std::vector<uint32_t> getSSRCs();
|
||||
|
||||
void setBitrate(int bitrate);
|
||||
int getBitrate() const;
|
||||
@ -142,16 +143,13 @@ public:
|
||||
|
||||
virtual void parseSdpLine(string_view line) override;
|
||||
|
||||
private:
|
||||
virtual string generateSdpLines(string_view eol) const override;
|
||||
|
||||
int mBas = -1;
|
||||
|
||||
struct RTPMap {
|
||||
RTPMap(string_view mline);
|
||||
RTPMap() {}
|
||||
|
||||
void removeFB(const string &string);
|
||||
void addFB(const string &string);
|
||||
void addAttribute(std::string attr) { fmtps.emplace_back(attr); }
|
||||
|
||||
int pt;
|
||||
string format;
|
||||
@ -160,26 +158,51 @@ public:
|
||||
|
||||
std::vector<string> rtcpFbs;
|
||||
std::vector<string> fmtps;
|
||||
|
||||
static int parsePT(string_view view);
|
||||
void setMLine(string_view view);
|
||||
};
|
||||
|
||||
std::map<int, RTPMap>::iterator beginMaps();
|
||||
std::map<int, RTPMap>::iterator endMaps();
|
||||
std::map<int, RTPMap>::iterator removeMap(std::map<int, RTPMap>::iterator iterator);
|
||||
|
||||
private:
|
||||
virtual string generateSdpLines(string_view eol) const override;
|
||||
|
||||
int mBas = -1;
|
||||
|
||||
Media::RTPMap &getFormat(int fmt);
|
||||
Media::RTPMap &getFormat(const string &fmt);
|
||||
|
||||
std::map<int, RTPMap> mRtpMap;
|
||||
std::vector<uint32_t> mSsrcs;
|
||||
|
||||
public:
|
||||
void addRTPMap(const RTPMap &map);
|
||||
};
|
||||
|
||||
class Audio : public Media {
|
||||
public:
|
||||
Audio(string mid = "audio", Direction dir = Direction::SendOnly);
|
||||
|
||||
void addAudioCodec(int payloadType, const string &codec);
|
||||
void addOpusCodec(int payloadType);
|
||||
};
|
||||
|
||||
class Video : public Media {
|
||||
public:
|
||||
Video(string mid = "video", Direction dir = Direction::SendOnly);
|
||||
|
||||
void addVideoCodec(int payloadType, const string &codec);
|
||||
void addH264Codec(int payloadType);
|
||||
void addVP8Codec(int payloadType);
|
||||
void addVP9Codec(int payloadType);
|
||||
};
|
||||
|
||||
bool hasApplication() const;
|
||||
bool hasAudioOrVideo() const;
|
||||
bool hasMid(string_view mid) const;
|
||||
|
||||
int addMedia(Media media);
|
||||
int addMedia(Application application);
|
||||
@ -187,13 +210,17 @@ public:
|
||||
int addVideo(string mid = "video", Direction dir = Direction::SendOnly);
|
||||
int addAudio(string mid = "audio", Direction dir = Direction::SendOnly);
|
||||
|
||||
std::variant<Media *, Application *> media(int index);
|
||||
std::variant<const Media *, const Application *> media(int index) const;
|
||||
int mediaCount() const;
|
||||
std::variant<Media *, Application *> media(unsigned int index);
|
||||
std::variant<const Media *, const Application *> media(unsigned int index) const;
|
||||
unsigned int mediaCount() const;
|
||||
|
||||
Application *application();
|
||||
|
||||
static Type stringToType(const string &typeString);
|
||||
static string typeToString(Type type);
|
||||
|
||||
private:
|
||||
std::optional<Candidate> defaultCandidate() const;
|
||||
std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
|
||||
void removeApplication();
|
||||
|
||||
@ -201,8 +228,9 @@ private:
|
||||
|
||||
// Session-level attributes
|
||||
Role mRole;
|
||||
string mUsername;
|
||||
string mSessionId;
|
||||
string mIceUfrag, mIcePwd;
|
||||
std::optional<string> mIceUfrag, mIcePwd;
|
||||
std::optional<string> mFingerprint;
|
||||
|
||||
// Entries
|
||||
@ -212,14 +240,12 @@ private:
|
||||
// Candidates
|
||||
std::vector<Candidate> mCandidates;
|
||||
bool mEnded = false;
|
||||
|
||||
static Type stringToType(const string &typeString);
|
||||
static string typeToString(Type type);
|
||||
static string roleToString(Role role);
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Description &description);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::Description::Type type);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::Description::Role role);
|
||||
|
||||
#endif
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0602
|
||||
#define _WIN32_WINNT 0x0602 // Windows 8
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -62,7 +62,7 @@ using std::uint8_t;
|
||||
const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
|
||||
const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length
|
||||
|
||||
const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
|
||||
const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
|
||||
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
|
||||
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
|
||||
|
||||
@ -72,7 +72,7 @@ const int THREADPOOL_SIZE = 4; // Number of threads in the global thread pool
|
||||
|
||||
// overloaded helper
|
||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
||||
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
// weak_ptr bind helper
|
||||
template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t, Args &&... _args) {
|
||||
@ -85,12 +85,29 @@ template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t,
|
||||
};
|
||||
}
|
||||
|
||||
template <typename... P> class synchronized_callback {
|
||||
// scope_guard helper
|
||||
class scope_guard {
|
||||
public:
|
||||
scope_guard(std::function<void()> func) : function(std::move(func)) {}
|
||||
scope_guard(scope_guard &&other) = delete;
|
||||
scope_guard(const scope_guard &) = delete;
|
||||
void operator=(const scope_guard &) = delete;
|
||||
|
||||
~scope_guard() {
|
||||
if (function)
|
||||
function();
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> function;
|
||||
};
|
||||
|
||||
template <typename... Args> class synchronized_callback {
|
||||
public:
|
||||
synchronized_callback() = default;
|
||||
synchronized_callback(synchronized_callback &&cb) { *this = std::move(cb); }
|
||||
synchronized_callback(const synchronized_callback &cb) { *this = cb; }
|
||||
synchronized_callback(std::function<void(P...)> func) { *this = std::move(func); }
|
||||
synchronized_callback(std::function<void(Args...)> func) { *this = std::move(func); }
|
||||
~synchronized_callback() { *this = nullptr; }
|
||||
|
||||
synchronized_callback &operator=(synchronized_callback &&cb) {
|
||||
@ -106,16 +123,16 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
synchronized_callback &operator=(std::function<void(P...)> func) {
|
||||
synchronized_callback &operator=(std::function<void(Args...)> func) {
|
||||
std::lock_guard lock(mutex);
|
||||
callback = std::move(func);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator()(P... args) const {
|
||||
void operator()(Args... args) const {
|
||||
std::lock_guard lock(mutex);
|
||||
if (callback)
|
||||
callback(args...);
|
||||
callback(std::move(args)...);
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
@ -123,10 +140,14 @@ public:
|
||||
return callback ? true : false;
|
||||
}
|
||||
|
||||
std::function<void(Args...)> wrap() const {
|
||||
return [this](Args... args) { (*this)(std::move(args)...); };
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(P...)> callback;
|
||||
std::function<void(Args...)> callback;
|
||||
mutable std::recursive_mutex mutex;
|
||||
};
|
||||
}
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -49,6 +49,6 @@ enum class LogLevel { // Don't change, it must match plog severity
|
||||
|
||||
void InitLogger(LogLevel level);
|
||||
void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr);
|
||||
}
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -50,6 +50,13 @@ class SctpTransport;
|
||||
using certificate_ptr = std::shared_ptr<Certificate>;
|
||||
using future_certificate_ptr = std::shared_future<certificate_ptr>;
|
||||
|
||||
struct DataChannelInit {
|
||||
Reliability reliability = {};
|
||||
bool negotiated = false;
|
||||
std::optional<uint16_t> id = nullopt;
|
||||
string protocol = "";
|
||||
};
|
||||
|
||||
class PeerConnection final : public std::enable_shared_from_this<PeerConnection> {
|
||||
public:
|
||||
enum class State : int {
|
||||
@ -67,7 +74,15 @@ public:
|
||||
Complete = RTC_GATHERING_COMPLETE
|
||||
};
|
||||
|
||||
PeerConnection(void);
|
||||
enum class SignalingState : int {
|
||||
Stable = RTC_SIGNALING_STABLE,
|
||||
HaveLocalOffer = RTC_SIGNALING_HAVE_LOCAL_OFFER,
|
||||
HaveRemoteOffer = RTC_SIGNALING_HAVE_REMOTE_OFFER,
|
||||
HaveLocalPranswer = RTC_SIGNALING_HAVE_LOCAL_PRANSWER,
|
||||
HaveRemotePranswer = RTC_SIGNALING_HAVE_REMOTE_PRANSWER,
|
||||
} rtcSignalingState;
|
||||
|
||||
PeerConnection();
|
||||
PeerConnection(const Configuration &config);
|
||||
~PeerConnection();
|
||||
|
||||
@ -76,6 +91,7 @@ public:
|
||||
const Configuration *config() const;
|
||||
State state() const;
|
||||
GatheringState gatheringState() const;
|
||||
SignalingState signalingState() const;
|
||||
bool hasLocalDescription() const;
|
||||
bool hasRemoteDescription() const;
|
||||
bool hasMedia() const;
|
||||
@ -83,23 +99,24 @@ public:
|
||||
std::optional<Description> remoteDescription() const;
|
||||
std::optional<string> localAddress() const;
|
||||
std::optional<string> remoteAddress() const;
|
||||
bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
|
||||
|
||||
void setLocalDescription(Description::Type type = Description::Type::Unspec);
|
||||
|
||||
void setLocalDescription();
|
||||
void setRemoteDescription(Description description);
|
||||
void addRemoteCandidate(Candidate candidate);
|
||||
|
||||
std::shared_ptr<DataChannel> addDataChannel(string label, string protocol = "",
|
||||
Reliability reliability = {});
|
||||
std::shared_ptr<DataChannel> addDataChannel(string label, DataChannelInit init = {});
|
||||
|
||||
// Equivalent to calling addDataChannel() and setLocalDescription()
|
||||
std::shared_ptr<DataChannel> createDataChannel(string label, string protocol = "",
|
||||
Reliability reliability = {});
|
||||
std::shared_ptr<DataChannel> createDataChannel(string label, DataChannelInit init = {});
|
||||
|
||||
void onDataChannel(std::function<void(std::shared_ptr<DataChannel> dataChannel)> callback);
|
||||
void onLocalDescription(std::function<void(Description description)> callback);
|
||||
void onLocalCandidate(std::function<void(Candidate candidate)> callback);
|
||||
void onStateChange(std::function<void(State state)> callback);
|
||||
void onGatheringStateChange(std::function<void(GatheringState state)> callback);
|
||||
void onSignalingStateChange(std::function<void(SignalingState state)> callback);
|
||||
|
||||
// Stats
|
||||
void clearStats();
|
||||
@ -111,11 +128,8 @@ public:
|
||||
std::shared_ptr<Track> addTrack(Description::Media description);
|
||||
void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
|
||||
|
||||
// libnice only
|
||||
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
|
||||
|
||||
private:
|
||||
std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
|
||||
std::shared_ptr<IceTransport> initIceTransport();
|
||||
std::shared_ptr<DtlsTransport> initDtlsTransport();
|
||||
std::shared_ptr<SctpTransport> initSctpTransport();
|
||||
void closeTransports();
|
||||
@ -125,9 +139,10 @@ private:
|
||||
void forwardMessage(message_ptr message);
|
||||
void forwardMedia(message_ptr message);
|
||||
void forwardBufferedAmount(uint16_t stream, size_t amount);
|
||||
std::optional<std::string> getMidFromSsrc(uint32_t ssrc);
|
||||
|
||||
std::shared_ptr<DataChannel> emplaceDataChannel(Description::Role role, string label,
|
||||
string protocol, Reliability reliability);
|
||||
DataChannelInit init);
|
||||
std::shared_ptr<DataChannel> findDataChannel(uint16_t stream);
|
||||
void iterateDataChannels(std::function<void(std::shared_ptr<DataChannel> channel)> func);
|
||||
void openDataChannels();
|
||||
@ -137,12 +152,16 @@ private:
|
||||
void incomingTrack(Description::Media description);
|
||||
void openTracks();
|
||||
|
||||
void validateRemoteDescription(const Description &description);
|
||||
void processLocalDescription(Description description);
|
||||
void processLocalCandidate(Candidate candidate);
|
||||
void processRemoteDescription(Description description);
|
||||
void processRemoteCandidate(Candidate candidate);
|
||||
void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
|
||||
void triggerTrack(std::shared_ptr<Track> track);
|
||||
bool changeState(State state);
|
||||
bool changeGatheringState(GatheringState state);
|
||||
bool changeSignalingState(SignalingState state);
|
||||
|
||||
void resetCallbacks();
|
||||
|
||||
@ -154,32 +173,38 @@ private:
|
||||
const std::unique_ptr<Processor> mProcessor;
|
||||
|
||||
std::optional<Description> mLocalDescription, mRemoteDescription;
|
||||
std::optional<Description> mCurrentLocalDescription;
|
||||
mutable std::mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
||||
|
||||
std::shared_ptr<IceTransport> mIceTransport;
|
||||
std::shared_ptr<DtlsTransport> mDtlsTransport;
|
||||
std::shared_ptr<SctpTransport> mSctpTransport;
|
||||
|
||||
std::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels; // by stream ID
|
||||
std::unordered_map<string, std::weak_ptr<Track>> mTracks; // by mid
|
||||
std::unordered_map<uint16_t, std::weak_ptr<DataChannel>> mDataChannels; // by stream ID
|
||||
std::unordered_map<string, std::weak_ptr<Track>> mTracks; // by mid
|
||||
std::vector<std::weak_ptr<Track>> mTrackLines; // by SDP order
|
||||
std::shared_mutex mDataChannelsMutex, mTracksMutex;
|
||||
|
||||
std::unordered_map<unsigned int, string> mMidFromPayloadType; // cache
|
||||
std::unordered_map<uint32_t, string> mMidFromSsrc; // cache
|
||||
|
||||
std::atomic<State> mState;
|
||||
std::atomic<GatheringState> mGatheringState;
|
||||
std::atomic<SignalingState> mSignalingState;
|
||||
std::atomic<bool> mNegotiationNeeded;
|
||||
|
||||
synchronized_callback<std::shared_ptr<DataChannel>> mDataChannelCallback;
|
||||
synchronized_callback<Description> mLocalDescriptionCallback;
|
||||
synchronized_callback<Candidate> mLocalCandidateCallback;
|
||||
synchronized_callback<State> mStateChangeCallback;
|
||||
synchronized_callback<GatheringState> mGatheringStateChangeCallback;
|
||||
synchronized_callback<SignalingState> mSignalingStateChangeCallback;
|
||||
synchronized_callback<std::shared_ptr<Track>> mTrackCallback;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::SignalingState state);
|
||||
|
||||
#endif
|
||||
|
@ -38,6 +38,7 @@ public:
|
||||
~Queue();
|
||||
|
||||
void stop();
|
||||
bool running() const;
|
||||
bool empty() const;
|
||||
bool full() const;
|
||||
size_t size() const; // elements
|
||||
@ -80,6 +81,11 @@ template <typename T> void Queue<T>::stop() {
|
||||
mPushCondition.notify_all();
|
||||
}
|
||||
|
||||
template <typename T> bool Queue<T>::running() const {
|
||||
std::lock_guard lock(mMutex);
|
||||
return !mQueue.empty() || !mStopping;
|
||||
}
|
||||
|
||||
template <typename T> bool Queue<T>::empty() const {
|
||||
std::lock_guard lock(mMutex);
|
||||
return mQueue.empty();
|
||||
@ -164,4 +170,3 @@ template <typename T> std::optional<T> Queue<T>::popImpl() {
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -37,4 +37,3 @@ struct Reliability {
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -25,8 +25,14 @@ extern "C" {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define RTC_EXPORT __declspec(dllexport)
|
||||
#ifdef CAPI_STDCALL
|
||||
#define RTC_API __stdcall
|
||||
#else
|
||||
#define RTC_API
|
||||
#endif
|
||||
#else // not WIN32
|
||||
#define RTC_EXPORT
|
||||
#define RTC_API
|
||||
#endif
|
||||
|
||||
#ifndef RTC_ENABLE_WEBSOCKET
|
||||
@ -53,6 +59,14 @@ typedef enum {
|
||||
RTC_GATHERING_COMPLETE = 2
|
||||
} rtcGatheringState;
|
||||
|
||||
typedef enum {
|
||||
RTC_SIGNALING_STABLE = 0,
|
||||
RTC_SIGNALING_HAVE_LOCAL_OFFER = 1,
|
||||
RTC_SIGNALING_HAVE_REMOTE_OFFER = 2,
|
||||
RTC_SIGNALING_HAVE_LOCAL_PRANSWER = 3,
|
||||
RTC_SIGNALING_HAVE_REMOTE_PRANSWER = 4,
|
||||
} rtcSignalingState;
|
||||
|
||||
typedef enum { // Don't change, it must match plog severity
|
||||
RTC_LOG_NONE = 0,
|
||||
RTC_LOG_FATAL = 1,
|
||||
@ -64,8 +78,10 @@ typedef enum { // Don't change, it must match plog severity
|
||||
} rtcLogLevel;
|
||||
|
||||
#define RTC_ERR_SUCCESS 0
|
||||
#define RTC_ERR_INVALID -1 // invalid argument
|
||||
#define RTC_ERR_FAILURE -2 // runtime error
|
||||
#define RTC_ERR_INVALID -1 // invalid argument
|
||||
#define RTC_ERR_FAILURE -2 // runtime error
|
||||
#define RTC_ERR_NOT_AVAIL -3 // element not available
|
||||
#define RTC_ERR_TOO_SMALL -4 // buffer too small
|
||||
|
||||
typedef struct {
|
||||
const char **iceServers;
|
||||
@ -81,22 +97,34 @@ typedef struct {
|
||||
unsigned int maxRetransmits; // ignored if reliable
|
||||
} rtcReliability;
|
||||
|
||||
typedef void (*rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
|
||||
typedef void (*rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
||||
typedef void (*rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
||||
typedef void (*rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
|
||||
typedef void (*rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
||||
typedef void (*rtcDataChannelCallbackFunc)(int dc, void *ptr);
|
||||
typedef void (*rtcTrackCallbackFunc)(int tr, void *ptr);
|
||||
typedef void (*rtcOpenCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcClosedCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcErrorCallbackFunc)(const char *error, void *ptr);
|
||||
typedef void (*rtcMessageCallbackFunc)(const char *message, int size, void *ptr);
|
||||
typedef void (*rtcBufferedAmountLowCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcAvailableCallbackFunc)(void *ptr);
|
||||
typedef struct {
|
||||
rtcReliability reliability;
|
||||
const char *protocol; // empty string if NULL
|
||||
bool negotiated;
|
||||
bool manualStream;
|
||||
uint16_t stream; // numeric ID 0-65534, ignored if manualStream is false
|
||||
} rtcDataChannelInit;
|
||||
|
||||
typedef void(RTC_API *rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
|
||||
typedef void(RTC_API *rtcDescriptionCallbackFunc)(int pc, const char *sdp, const char *type,
|
||||
void *ptr);
|
||||
typedef void(RTC_API *rtcCandidateCallbackFunc)(int pc, const char *cand, const char *mid,
|
||||
void *ptr);
|
||||
typedef void(RTC_API *rtcStateChangeCallbackFunc)(int pc, rtcState state, void *ptr);
|
||||
typedef void(RTC_API *rtcGatheringStateCallbackFunc)(int pc, rtcGatheringState state, void *ptr);
|
||||
typedef void(RTC_API *rtcSignalingStateCallbackFunc)(int pc, rtcSignalingState state, void *ptr);
|
||||
typedef void(RTC_API *rtcDataChannelCallbackFunc)(int pc, int dc, void *ptr);
|
||||
typedef void(RTC_API *rtcTrackCallbackFunc)(int pc, int tr, void *ptr);
|
||||
typedef void(RTC_API *rtcOpenCallbackFunc)(int id, void *ptr);
|
||||
typedef void(RTC_API *rtcClosedCallbackFunc)(int id, void *ptr);
|
||||
typedef void(RTC_API *rtcErrorCallbackFunc)(int id, const char *error, void *ptr);
|
||||
typedef void(RTC_API *rtcMessageCallbackFunc)(int id, const char *message, int size, void *ptr);
|
||||
typedef void(RTC_API *rtcBufferedAmountLowCallbackFunc)(int id, void *ptr);
|
||||
typedef void(RTC_API *rtcAvailableCallbackFunc)(int id, void *ptr);
|
||||
|
||||
// Log
|
||||
RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb); // NULL cb to log to stdout
|
||||
// NULL cb on the first call will log to stdout
|
||||
RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb);
|
||||
|
||||
// User pointer
|
||||
RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
|
||||
@ -109,25 +137,33 @@ RTC_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc
|
||||
RTC_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb);
|
||||
|
||||
RTC_EXPORT int rtcSetLocalDescription(int pc);
|
||||
RTC_EXPORT int rtcSetLocalDescription(int pc, const char *type);
|
||||
RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
||||
RTC_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
|
||||
|
||||
RTC_EXPORT int rtcGetLocalDescription(int pc, char *buffer, int size);
|
||||
RTC_EXPORT int rtcGetRemoteDescription(int pc, char *buffer, int size);
|
||||
|
||||
RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
|
||||
RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
||||
|
||||
RTC_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote,
|
||||
int remoteSize);
|
||||
|
||||
// DataChannel
|
||||
RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
|
||||
RTC_EXPORT int rtcAddDataChannel(int pc, const char *label); // returns dc id
|
||||
RTC_EXPORT int rtcAddDataChannelExt(int pc, const char *label, const char *protocol,
|
||||
const rtcReliability *reliability); // returns dc id
|
||||
RTC_EXPORT int rtcAddDataChannelEx(int pc, const char *label,
|
||||
const rtcDataChannelInit *init); // returns dc id
|
||||
// Equivalent to calling rtcAddDataChannel() and rtcSetLocalDescription()
|
||||
RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
|
||||
RTC_EXPORT int rtcCreateDataChannelExt(int pc, const char *label, const char *protocol,
|
||||
const rtcReliability *reliability); // returns dc id
|
||||
RTC_EXPORT int rtcCreateDataChannelEx(int pc, const char *label,
|
||||
const rtcDataChannelInit *init); // returns dc id
|
||||
RTC_EXPORT int rtcDeleteDataChannel(int dc);
|
||||
|
||||
RTC_EXPORT int rtcGetDataChannelStream(int dc);
|
||||
RTC_EXPORT int rtcGetDataChannelLabel(int dc, char *buffer, int size);
|
||||
RTC_EXPORT int rtcGetDataChannelProtocol(int dc, char *buffer, int size);
|
||||
RTC_EXPORT int rtcGetDataChannelReliability(int dc, rtcReliability *reliability);
|
||||
|
@ -27,4 +27,3 @@
|
||||
|
||||
// C API
|
||||
#include "rtc.h"
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Staz M
|
||||
* Copyright (c) 2020 Staz Modrzynski
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
@ -20,35 +20,67 @@
|
||||
#ifndef RTC_RTCP_H
|
||||
#define RTC_RTCP_H
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "include.hpp"
|
||||
#include "log.hpp"
|
||||
#include "message.hpp"
|
||||
#include "rtp.hpp"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
typedef uint32_t SSRC;
|
||||
|
||||
class RtcpHandler {
|
||||
protected:
|
||||
/**
|
||||
* Use this callback when trying to send custom data (such as RTCP) to the client.
|
||||
*/
|
||||
synchronized_callback<rtc::message_ptr> outgoingCallback;
|
||||
|
||||
public:
|
||||
virtual void onOutgoing(std::function<void(rtc::message_ptr)> cb) = 0;
|
||||
virtual std::optional<rtc::message_ptr> incoming(rtc::message_ptr ptr) = 0;
|
||||
/**
|
||||
* Called when there is traffic coming from the peer
|
||||
* @param ptr
|
||||
* @return
|
||||
*/
|
||||
virtual rtc::message_ptr incoming(rtc::message_ptr ptr) = 0;
|
||||
|
||||
/**
|
||||
* Called when there is traffic that needs to be sent to the peer
|
||||
* @param ptr
|
||||
* @return
|
||||
*/
|
||||
virtual rtc::message_ptr outgoing(rtc::message_ptr ptr) = 0;
|
||||
|
||||
/**
|
||||
* This callback is used to send traffic back to the peer.
|
||||
* This callback skips calling the track's methods.
|
||||
* @param cb
|
||||
*/
|
||||
void onOutgoing(const std::function<void(rtc::message_ptr)> &cb);
|
||||
|
||||
virtual bool requestKeyframe() { return false; }
|
||||
};
|
||||
|
||||
// An RtcpSession can be plugged into a Track to handle the whole RTCP session
|
||||
class RtcpSession : public RtcpHandler {
|
||||
public:
|
||||
void onOutgoing(std::function<void(rtc::message_ptr)> cb) override;
|
||||
class Track;
|
||||
|
||||
// An RtcpSession can be plugged into a Track to handle the whole RTCP session
|
||||
class RtcpReceivingSession : public RtcpHandler {
|
||||
public:
|
||||
rtc::message_ptr incoming(rtc::message_ptr ptr) override;
|
||||
rtc::message_ptr outgoing(rtc::message_ptr ptr) override;
|
||||
bool send(rtc::message_ptr ptr);
|
||||
|
||||
std::optional<rtc::message_ptr> incoming(rtc::message_ptr ptr) override;
|
||||
void requestBitrate(unsigned int newBitrate);
|
||||
|
||||
private:
|
||||
bool requestKeyframe() override;
|
||||
|
||||
protected:
|
||||
void pushREMB(unsigned int bitrate);
|
||||
void pushRR(unsigned int lastSR_delay);
|
||||
void tx(message_ptr msg);
|
||||
|
||||
void pushPLI();
|
||||
|
||||
unsigned int mRequestedBitrate = 0;
|
||||
synchronized_callback<rtc::message_ptr> mTxCallback;
|
||||
SSRC mSsrc = 0;
|
||||
uint32_t mGreatestSeqNo = 0;
|
||||
uint64_t mSyncRTPTS, mSyncNTPTS;
|
||||
|
505
include/rtc/rtp.hpp
Normal file
505
include/rtc/rtp.hpp
Normal file
@ -0,0 +1,505 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Staz Modrzynski
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef RTC_RTP_HPP
|
||||
#define RTC_RTP_HPP
|
||||
|
||||
#include <rtc/log.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#ifndef htonll
|
||||
#define htonll(x) \
|
||||
((uint64_t)htonl(((uint64_t)(x)&0xFFFFFFFF) << 32) | (uint64_t)htonl((uint64_t)(x) >> 32))
|
||||
#endif
|
||||
#ifndef ntohll
|
||||
#define ntohll(x) htonll(x)
|
||||
#endif
|
||||
|
||||
namespace rtc {
|
||||
|
||||
typedef uint32_t SSRC;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct RTP {
|
||||
private:
|
||||
uint8_t _first;
|
||||
uint8_t _payloadType;
|
||||
uint16_t _seqNumber;
|
||||
uint32_t _timestamp;
|
||||
SSRC _ssrc;
|
||||
|
||||
public:
|
||||
SSRC csrc[16];
|
||||
|
||||
inline uint8_t version() const { return _first >> 6; }
|
||||
inline bool padding() const { return (_first >> 5) & 0x01; }
|
||||
inline bool extension() const { return (_first >> 4) & 0x01; }
|
||||
inline uint8_t csrcCount() const { return _first & 0x0F; }
|
||||
inline uint8_t marker() const { return _payloadType & 0b10000000; }
|
||||
inline uint8_t payloadType() const { return _payloadType & 0b01111111; }
|
||||
inline uint16_t seqNumber() const { return ntohs(_seqNumber); }
|
||||
inline uint32_t timestamp() const { return ntohl(_timestamp); }
|
||||
inline uint32_t ssrc() const { return ntohl(_ssrc); }
|
||||
|
||||
inline size_t getSize() const {
|
||||
return ((char *)&csrc) - ((char *)this) + sizeof(SSRC) * csrcCount();
|
||||
}
|
||||
|
||||
char *getBody() const { return ((char *)&csrc) + sizeof(SSRC) * csrcCount(); }
|
||||
|
||||
inline void setSeqNumber(uint16_t newSeqNo) { _seqNumber = htons(newSeqNo); }
|
||||
inline void setPayloadType(uint8_t newPayloadType) {
|
||||
_payloadType = (_payloadType & 0b10000000u) | (0b01111111u & newPayloadType);
|
||||
}
|
||||
inline void setSsrc(uint32_t ssrc) { _ssrc = htonl(ssrc); }
|
||||
|
||||
void setTimestamp(uint32_t i) { _timestamp = htonl(i); }
|
||||
|
||||
void log() {
|
||||
PLOG_VERBOSE << "RTP V: " << (int) version()
|
||||
<< " P: " << (padding() ? "P" : " ")
|
||||
<< " X: " << (extension() ? "X" : " ")
|
||||
<< " CC: " << (int) csrcCount()
|
||||
<< " M: " << (marker() ? "M" : " ")
|
||||
<< " PT: " << (int) payloadType()
|
||||
<< " SEQNO: " << seqNumber()
|
||||
<< " TS: " << timestamp();
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_ReportBlock {
|
||||
SSRC ssrc;
|
||||
|
||||
private:
|
||||
uint32_t _fractionLostAndPacketsLost; // fraction lost is 8-bit, packets lost is 24-bit
|
||||
uint16_t _seqNoCycles;
|
||||
uint16_t _highestSeqNo;
|
||||
uint32_t _jitter;
|
||||
uint32_t _lastReport;
|
||||
uint32_t _delaySinceLastReport;
|
||||
|
||||
public:
|
||||
inline void preparePacket(SSRC ssrc, [[maybe_unused]] unsigned int packetsLost,
|
||||
[[maybe_unused]] unsigned int totalPackets, uint16_t highestSeqNo,
|
||||
uint16_t seqNoCycles, uint32_t jitter, uint64_t lastSR_NTP,
|
||||
uint64_t lastSR_DELAY) {
|
||||
setSeqNo(highestSeqNo, seqNoCycles);
|
||||
setJitter(jitter);
|
||||
setSSRC(ssrc);
|
||||
|
||||
// Middle 32 bits of NTP Timestamp
|
||||
// this->lastReport = lastSR_NTP >> 16u;
|
||||
setNTPOfSR(uint64_t(lastSR_NTP));
|
||||
setDelaySinceSR(uint32_t(lastSR_DELAY));
|
||||
|
||||
// The delay, expressed in units of 1/65536 seconds
|
||||
// this->delaySinceLastReport = lastSR_DELAY;
|
||||
}
|
||||
|
||||
inline void setSSRC(SSRC ssrc) { this->ssrc = htonl(ssrc); }
|
||||
inline SSRC getSSRC() const { return ntohl(ssrc); }
|
||||
|
||||
inline void setPacketsLost([[maybe_unused]] unsigned int packetsLost,
|
||||
[[maybe_unused]] unsigned int totalPackets) {
|
||||
// TODO Implement loss percentages.
|
||||
_fractionLostAndPacketsLost = 0;
|
||||
}
|
||||
inline unsigned int getLossPercentage() const {
|
||||
// TODO Implement loss percentages.
|
||||
return 0;
|
||||
}
|
||||
inline unsigned int getPacketLostCount() const {
|
||||
// TODO Implement total packets lost.
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline uint16_t seqNoCycles() const { return ntohs(_seqNoCycles); }
|
||||
inline uint16_t highestSeqNo() const { return ntohs(_highestSeqNo); }
|
||||
inline uint32_t jitter() const { return ntohl(_jitter); }
|
||||
|
||||
inline void setSeqNo(uint16_t highestSeqNo, uint16_t seqNoCycles) {
|
||||
_highestSeqNo = htons(highestSeqNo);
|
||||
_seqNoCycles = htons(seqNoCycles);
|
||||
}
|
||||
|
||||
inline void setJitter(uint32_t jitter) { _jitter = htonl(jitter); }
|
||||
|
||||
inline void setNTPOfSR(uint64_t ntp) { _lastReport = htonll(ntp >> 16u); }
|
||||
inline uint32_t getNTPOfSR() const { return ntohl(_lastReport) << 16u; }
|
||||
|
||||
inline void setDelaySinceSR(uint32_t sr) {
|
||||
// The delay, expressed in units of 1/65536 seconds
|
||||
_delaySinceLastReport = htonl(sr);
|
||||
}
|
||||
inline uint32_t getDelaySinceSR() const { return ntohl(_delaySinceLastReport); }
|
||||
|
||||
inline void log() const {
|
||||
PLOG_VERBOSE << "RTCP report block: "
|
||||
<< "ssrc="
|
||||
<< ntohl(ssrc)
|
||||
// TODO: Implement these reports
|
||||
// << ", fractionLost=" << fractionLost
|
||||
// << ", packetsLost=" << packetsLost
|
||||
<< ", highestSeqNo=" << highestSeqNo() << ", seqNoCycles=" << seqNoCycles()
|
||||
<< ", jitter=" << jitter() << ", lastSR=" << getNTPOfSR()
|
||||
<< ", lastSRDelay=" << getDelaySinceSR();
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_HEADER {
|
||||
private:
|
||||
uint8_t _first;
|
||||
uint8_t _payloadType;
|
||||
uint16_t _length;
|
||||
|
||||
public:
|
||||
inline uint8_t version() const { return _first >> 6; }
|
||||
inline bool padding() const { return (_first >> 5) & 0x01; }
|
||||
inline uint8_t reportCount() const { return _first & 0x0F; }
|
||||
inline uint8_t payloadType() const { return _payloadType; }
|
||||
inline uint16_t length() const { return ntohs(_length); }
|
||||
inline size_t lengthInBytes() const { return (1 + length()) * 4; }
|
||||
|
||||
inline void setPayloadType(uint8_t type) { _payloadType = type; }
|
||||
inline void setReportCount(uint8_t count) {
|
||||
_first = (_first & 0b11100000u) | (count & 0b00011111u);
|
||||
}
|
||||
inline void setLength(uint16_t length) { _length = htons(length); }
|
||||
|
||||
inline void prepareHeader(uint8_t payloadType, uint8_t reportCount, uint16_t length) {
|
||||
_first = 0b10000000; // version 2, no padding
|
||||
setReportCount(reportCount);
|
||||
setPayloadType(payloadType);
|
||||
setLength(length);
|
||||
}
|
||||
|
||||
inline void log() const {
|
||||
PLOG_VERBOSE << "RTCP header: "
|
||||
<< "version=" << unsigned(version()) << ", padding=" << padding()
|
||||
<< ", reportCount=" << unsigned(reportCount())
|
||||
<< ", payloadType=" << unsigned(payloadType()) << ", length=" << length();
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_FB_HEADER {
|
||||
RTCP_HEADER header;
|
||||
SSRC packetSender;
|
||||
SSRC mediaSource;
|
||||
|
||||
[[nodiscard]] SSRC getPacketSenderSSRC() const { return ntohl(packetSender); }
|
||||
|
||||
[[nodiscard]] SSRC getMediaSourceSSRC() const { return ntohl(mediaSource); }
|
||||
|
||||
void setPacketSenderSSRC(SSRC ssrc) { this->packetSender = htonl(ssrc); }
|
||||
|
||||
void setMediaSourceSSRC(SSRC ssrc) { this->mediaSource = htonl(ssrc); }
|
||||
|
||||
void log() {
|
||||
header.log();
|
||||
PLOG_VERBOSE << "FB: "
|
||||
<< " packet sender: " << getPacketSenderSSRC()
|
||||
<< " media source: " << getMediaSourceSSRC();
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_SR {
|
||||
RTCP_HEADER header;
|
||||
SSRC _senderSSRC;
|
||||
|
||||
private:
|
||||
uint64_t _ntpTimestamp;
|
||||
uint32_t _rtpTimestamp;
|
||||
uint32_t _packetCount;
|
||||
uint32_t _octetCount;
|
||||
|
||||
RTCP_ReportBlock _reportBlocks;
|
||||
|
||||
public:
|
||||
inline void preparePacket(SSRC senderSSRC, uint8_t reportCount) {
|
||||
unsigned int length =
|
||||
((sizeof(header) + 24 + reportCount * sizeof(RTCP_ReportBlock)) / 4) - 1;
|
||||
header.prepareHeader(200, reportCount, uint16_t(length));
|
||||
this->_senderSSRC = htonl(senderSSRC);
|
||||
}
|
||||
|
||||
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
|
||||
inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
|
||||
|
||||
[[nodiscard]] inline size_t getSize() const {
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
return sizeof(uint32_t) * (1 + size_t(header.length()));
|
||||
}
|
||||
|
||||
inline uint64_t ntpTimestamp() const { return ntohll(_ntpTimestamp); }
|
||||
inline uint32_t rtpTimestamp() const { return ntohl(_rtpTimestamp); }
|
||||
inline uint32_t packetCount() const { return ntohl(_packetCount); }
|
||||
inline uint32_t octetCount() const { return ntohl(_octetCount); }
|
||||
inline uint32_t senderSSRC() const { return ntohl(_senderSSRC); }
|
||||
|
||||
inline void setNtpTimestamp(uint32_t ts) { _ntpTimestamp = htonll(ts); }
|
||||
inline void setRtpTimestamp(uint32_t ts) { _rtpTimestamp = htonl(ts); }
|
||||
|
||||
inline void log() const {
|
||||
header.log();
|
||||
PLOG_VERBOSE << "RTCP SR: "
|
||||
<< " SSRC=" << senderSSRC() << ", NTP_TS=" << ntpTimestamp()
|
||||
<< ", RTP_TS=" << rtpTimestamp() << ", packetCount=" << packetCount()
|
||||
<< ", octetCount=" << octetCount();
|
||||
|
||||
for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
|
||||
getReportBlock(i)->log();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_RR {
|
||||
RTCP_HEADER header;
|
||||
SSRC _senderSSRC;
|
||||
|
||||
private:
|
||||
RTCP_ReportBlock _reportBlocks;
|
||||
|
||||
public:
|
||||
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
|
||||
inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
|
||||
|
||||
inline SSRC senderSSRC() const { return ntohl(_senderSSRC); }
|
||||
inline void setSenderSSRC(SSRC ssrc) { this->_senderSSRC = htonl(ssrc); }
|
||||
|
||||
[[nodiscard]] inline size_t getSize() const {
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
return sizeof(uint32_t) * (1 + size_t(header.length()));
|
||||
}
|
||||
|
||||
inline void preparePacket(SSRC senderSSRC, uint8_t reportCount) {
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
size_t length = (sizeWithReportBlocks(reportCount) / 4) - 1;
|
||||
header.prepareHeader(201, reportCount, uint16_t(length));
|
||||
this->_senderSSRC = htonl(senderSSRC);
|
||||
}
|
||||
|
||||
inline static size_t sizeWithReportBlocks(uint8_t reportCount) {
|
||||
return sizeof(header) + 4 + size_t(reportCount) * sizeof(RTCP_ReportBlock);
|
||||
}
|
||||
|
||||
inline bool isSenderReport() { return header.payloadType() == 200; }
|
||||
|
||||
inline bool isReceiverReport() { return header.payloadType() == 201; }
|
||||
|
||||
inline void log() const {
|
||||
header.log();
|
||||
PLOG_VERBOSE << "RTCP RR: "
|
||||
<< " SSRC=" << ntohl(_senderSSRC);
|
||||
|
||||
for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
|
||||
getReportBlock(i)->log();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_REMB {
|
||||
RTCP_FB_HEADER header;
|
||||
|
||||
/*! \brief Unique identifier ('R' 'E' 'M' 'B') */
|
||||
char id[4];
|
||||
|
||||
/*! \brief Num SSRC, Br Exp, Br Mantissa (bit mask) */
|
||||
uint32_t bitrate;
|
||||
|
||||
SSRC ssrc[1];
|
||||
|
||||
[[nodiscard]] unsigned int getSize() const {
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
return sizeof(uint32_t) * (1 + header.header.length());
|
||||
}
|
||||
|
||||
void preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int bitrate) {
|
||||
|
||||
// Report Count becomes the format here.
|
||||
header.header.prepareHeader(206, 15, 0);
|
||||
|
||||
// Always zero.
|
||||
header.setMediaSourceSSRC(0);
|
||||
|
||||
header.setPacketSenderSSRC(senderSSRC);
|
||||
|
||||
id[0] = 'R';
|
||||
id[1] = 'E';
|
||||
id[2] = 'M';
|
||||
id[3] = 'B';
|
||||
|
||||
setBitrate(numSSRC, bitrate);
|
||||
}
|
||||
|
||||
void setBitrate(unsigned int numSSRC, unsigned int bitrate) {
|
||||
unsigned int exp = 0;
|
||||
while (bitrate > pow(2, 18) - 1) {
|
||||
exp++;
|
||||
bitrate /= 2;
|
||||
}
|
||||
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
header.header.setLength(
|
||||
uint16_t((offsetof(RTCP_REMB, ssrc) / sizeof(uint32_t)) - 1 + numSSRC));
|
||||
|
||||
this->bitrate = htonl((numSSRC << (32u - 8u)) | (exp << (32u - 8u - 6u)) | bitrate);
|
||||
}
|
||||
|
||||
void setSsrc(int iterator, SSRC newSssrc) { ssrc[iterator] = htonl(newSssrc); }
|
||||
|
||||
size_t static inline sizeWithSSRCs(int count) {
|
||||
return sizeof(RTCP_REMB) + (count - 1) * sizeof(SSRC);
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_PLI {
|
||||
RTCP_FB_HEADER header;
|
||||
|
||||
void preparePacket(SSRC messageSSRC) {
|
||||
header.header.prepareHeader(206, 1, 2);
|
||||
header.setPacketSenderSSRC(messageSSRC);
|
||||
header.setMediaSourceSSRC(messageSSRC);
|
||||
}
|
||||
|
||||
void print() { header.log(); }
|
||||
|
||||
[[nodiscard]] static unsigned int size() { return sizeof(RTCP_FB_HEADER); }
|
||||
};
|
||||
|
||||
struct RTCP_FIR_PART {
|
||||
uint32_t ssrc;
|
||||
uint8_t seqNo;
|
||||
uint8_t dummy1;
|
||||
uint16_t dummy2;
|
||||
};
|
||||
|
||||
struct RTCP_FIR {
|
||||
RTCP_FB_HEADER header;
|
||||
RTCP_FIR_PART parts[1];
|
||||
|
||||
void preparePacket(SSRC messageSSRC, uint8_t seqNo) {
|
||||
header.header.prepareHeader(206, 4, 2 + 2 * 1);
|
||||
header.setPacketSenderSSRC(messageSSRC);
|
||||
header.setMediaSourceSSRC(messageSSRC);
|
||||
parts[0].ssrc = htonl(messageSSRC);
|
||||
parts[0].seqNo = seqNo;
|
||||
}
|
||||
|
||||
void print() { header.log(); }
|
||||
|
||||
[[nodiscard]] static unsigned int size() {
|
||||
return sizeof(RTCP_FB_HEADER) + sizeof(RTCP_FIR_PART);
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_NACK_PART {
|
||||
uint16_t pid;
|
||||
uint16_t blp;
|
||||
};
|
||||
|
||||
class RTCP_NACK {
|
||||
public:
|
||||
RTCP_FB_HEADER header;
|
||||
RTCP_NACK_PART parts[1];
|
||||
|
||||
public:
|
||||
void preparePacket(SSRC ssrc, unsigned int discreteSeqNoCount) {
|
||||
header.header.prepareHeader(205, 1, 2 + discreteSeqNoCount);
|
||||
header.setMediaSourceSSRC(ssrc);
|
||||
header.setPacketSenderSSRC(ssrc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a packet to the list of missing packets.
|
||||
* @param fciCount The number of FCI fields that are present in this packet.
|
||||
* Let the number start at zero and let this function grow the number.
|
||||
* @param fciPID The seq no of the active FCI. It will be initialized automatically, and will
|
||||
* change automatically.
|
||||
* @param missingPacket The seq no of the missing packet. This will be added to the queue.
|
||||
* @return true if the packet has grown, false otherwise.
|
||||
*/
|
||||
bool addMissingPacket(unsigned int *fciCount, uint16_t *fciPID, const uint16_t &missingPacket) {
|
||||
if (*fciCount == 0 || missingPacket < *fciPID || missingPacket > (*fciPID + 16)) {
|
||||
parts[*fciCount].pid = htons(missingPacket);
|
||||
parts[*fciCount].blp = 0;
|
||||
*fciPID = missingPacket;
|
||||
(*fciCount)++;
|
||||
return true;
|
||||
} else {
|
||||
// TODO SPEEED!
|
||||
parts[(*fciCount) - 1].blp = htons(ntohs(parts[(*fciCount) - 1].blp) |
|
||||
(1u << (unsigned int)(missingPacket - *fciPID)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] static unsigned int getSize(unsigned int discreteSeqNoCount) {
|
||||
return offsetof(RTCP_NACK, parts) + sizeof(RTCP_NACK_PART) * discreteSeqNoCount;
|
||||
}
|
||||
|
||||
[[nodiscard]] unsigned int getSeqNoCount() { return header.header.length() - 2; }
|
||||
};
|
||||
|
||||
class RTP_RTX {
|
||||
private:
|
||||
RTP header;
|
||||
|
||||
public:
|
||||
size_t copyTo(RTP *dest, size_t totalSize, uint8_t originalPayloadType) {
|
||||
memmove((char *)dest, (char *)this, header.getSize());
|
||||
dest->setSeqNumber(getOriginalSeqNo());
|
||||
dest->setPayloadType(originalPayloadType);
|
||||
memmove(dest->getBody(), getBody(), getBodySize(totalSize));
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint16_t getOriginalSeqNo() const {
|
||||
return ntohs(*(uint16_t *)(header.getBody()));
|
||||
}
|
||||
|
||||
char *getBody() { return header.getBody() + sizeof(uint16_t); }
|
||||
|
||||
size_t getBodySize(size_t totalSize) { return totalSize - ((char *)getBody() - (char *)this); }
|
||||
|
||||
RTP &getHeader() { return header; }
|
||||
|
||||
size_t normalizePacket(size_t totalSize, SSRC originalSSRC, uint8_t originalPayloadType) {
|
||||
header.setSeqNumber(getOriginalSeqNo());
|
||||
header.setSsrc(originalSSRC);
|
||||
header.setPayloadType(originalPayloadType);
|
||||
// TODO, the -12 is the size of the header (which is variable!)
|
||||
memmove(header.getBody(), header.getBody() + sizeof(uint16_t),
|
||||
totalSize - 12 - sizeof(uint16_t));
|
||||
return totalSize - sizeof(uint16_t);
|
||||
}
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
}; // namespace rtc
|
||||
|
||||
#endif
|
@ -43,6 +43,8 @@ public:
|
||||
string mid() const;
|
||||
Description::Media description() const;
|
||||
|
||||
void setDescription(Description::Media description);
|
||||
|
||||
void close(void) override;
|
||||
bool send(message_variant data) override;
|
||||
bool send(const byte *data, size_t size);
|
||||
@ -54,9 +56,13 @@ public:
|
||||
// Extended API
|
||||
size_t availableAmount() const override;
|
||||
std::optional<message_variant> receive() override;
|
||||
std::optional<message_variant> peek() override;
|
||||
|
||||
bool requestKeyframe();
|
||||
|
||||
// RTCP handler
|
||||
void setRtcpHandler(std::shared_ptr<RtcpHandler> handler);
|
||||
std::shared_ptr<RtcpHandler> getRtcpHandler();
|
||||
|
||||
private:
|
||||
#if RTC_ENABLE_MEDIA
|
||||
@ -79,4 +85,3 @@ private:
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -66,6 +66,7 @@ public:
|
||||
|
||||
// Extended API
|
||||
std::optional<message_variant> receive() override;
|
||||
std::optional<message_variant> peek() override;
|
||||
size_t availableAmount() const override; // total size available to receive
|
||||
|
||||
private:
|
||||
|
@ -25,8 +25,7 @@ namespace rtc {
|
||||
using std::to_integer;
|
||||
|
||||
string to_base64(const binary &data) {
|
||||
static const char tab[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
static const char tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
string out;
|
||||
out.reserve(3 * ((data.size() + 3) / 4));
|
||||
|
@ -21,14 +21,15 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
@ -47,23 +48,50 @@ inline bool hasprefix(const string &str, const string &prefix) {
|
||||
|
||||
namespace rtc {
|
||||
|
||||
Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
|
||||
Candidate::Candidate()
|
||||
: mFamily(Family::Unresolved), mType(Type::Unknown), mTransportType(TransportType::Unknown),
|
||||
mPort(0), mPriority(0) {}
|
||||
|
||||
Candidate::Candidate(string candidate) : Candidate() {
|
||||
const std::array prefixes{"a=", "candidate:"};
|
||||
for (const string &prefix : prefixes)
|
||||
if (hasprefix(candidate, prefix))
|
||||
candidate.erase(0, prefix.size());
|
||||
|
||||
mCandidate = std::move(candidate);
|
||||
mMid = std::move(mid);
|
||||
}
|
||||
|
||||
Candidate::Candidate(string candidate, string mid) : Candidate(std::move(candidate)) {
|
||||
if (!mid.empty())
|
||||
mMid.emplace(std::move(mid));
|
||||
}
|
||||
|
||||
void Candidate::hintMid(string mid) {
|
||||
if (!mMid)
|
||||
mMid.emplace(std::move(mid));
|
||||
}
|
||||
|
||||
bool Candidate::resolve(ResolveMode mode) {
|
||||
if (mIsResolved)
|
||||
using TypeMap_t = std::unordered_map<string, Type>;
|
||||
using TcpTypeMap_t = std::unordered_map<string, TransportType>;
|
||||
|
||||
static const TypeMap_t TypeMap = {{"host", Type::Host},
|
||||
{"srflx", Type::ServerReflexive},
|
||||
{"prflx", Type::PeerReflexive},
|
||||
{"relay", Type::Relayed}};
|
||||
|
||||
static const TcpTypeMap_t TcpTypeMap = {{"active", TransportType::TcpActive},
|
||||
{"passive", TransportType::TcpPassive},
|
||||
{"so", TransportType::TcpSo}};
|
||||
|
||||
if (mFamily != Family::Unresolved)
|
||||
return true;
|
||||
|
||||
if (mCandidate.empty())
|
||||
throw std::logic_error("Candidate is empty");
|
||||
|
||||
PLOG_VERBOSE << "Resolving candidate (mode="
|
||||
<< (mode == ResolveMode::Simple ? "simple" : "lookup")
|
||||
<< "): " << mCandidate;
|
||||
<< (mode == ResolveMode::Simple ? "simple" : "lookup") << "): " << mCandidate;
|
||||
|
||||
// See RFC 8445 for format
|
||||
std::istringstream iss(mCandidate);
|
||||
@ -75,16 +103,37 @@ bool Candidate::resolve(ResolveMode mode) {
|
||||
string left;
|
||||
std::getline(iss, left);
|
||||
|
||||
if (auto it = TypeMap.find(type); it != TypeMap.end())
|
||||
mType = it->second;
|
||||
else
|
||||
mType = Type::Unknown;
|
||||
|
||||
if (transport == "UDP" || transport == "udp") {
|
||||
mTransportType = TransportType::Udp;
|
||||
} else if (transport == "TCP" || transport == "tcp") {
|
||||
std::istringstream iss(left);
|
||||
string tcptype_, tcptype;
|
||||
if (iss >> tcptype_ >> tcptype && tcptype_ == "tcptype") {
|
||||
if (auto it = TcpTypeMap.find(tcptype); it != TcpTypeMap.end())
|
||||
mTransportType = it->second;
|
||||
else
|
||||
mTransportType = TransportType::TcpUnknown;
|
||||
|
||||
} else {
|
||||
mTransportType = TransportType::TcpUnknown;
|
||||
}
|
||||
} else {
|
||||
mTransportType = TransportType::Unknown;
|
||||
}
|
||||
|
||||
// Try to resolve the node
|
||||
struct addrinfo hints = {};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
if (transport == "UDP" || transport == "udp") {
|
||||
if (mTransportType == TransportType::Udp) {
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
}
|
||||
|
||||
if (transport == "TCP" || transport == "tcp") {
|
||||
} else if (mTransportType != TransportType::Unknown) {
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
}
|
||||
@ -102,13 +151,18 @@ bool Candidate::resolve(ResolveMode mode) {
|
||||
if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
|
||||
MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
|
||||
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
|
||||
|
||||
mAddress = nodebuffer;
|
||||
mPort = uint16_t(std::stoul(servbuffer));
|
||||
mFamily = p->ai_family == AF_INET6 ? Family::Ipv6 : Family::Ipv4;
|
||||
|
||||
const char sp{' '};
|
||||
std::ostringstream oss;
|
||||
oss << foundation << sp << component << sp << transport << sp << priority;
|
||||
oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
|
||||
oss << left;
|
||||
mCandidate = oss.str();
|
||||
mIsResolved = true;
|
||||
|
||||
PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
|
||||
break;
|
||||
}
|
||||
@ -119,14 +173,12 @@ bool Candidate::resolve(ResolveMode mode) {
|
||||
}
|
||||
}
|
||||
|
||||
return mIsResolved;
|
||||
return mFamily != Family::Unresolved;
|
||||
}
|
||||
|
||||
bool Candidate::isResolved() const { return mIsResolved; }
|
||||
|
||||
string Candidate::candidate() const { return "candidate:" + mCandidate; }
|
||||
|
||||
string Candidate::mid() const { return mMid; }
|
||||
string Candidate::mid() const { return mMid.value_or("0"); }
|
||||
|
||||
Candidate::operator string() const {
|
||||
std::ostringstream line;
|
||||
@ -134,38 +186,60 @@ Candidate::operator string() const {
|
||||
return line.str();
|
||||
}
|
||||
|
||||
bool Candidate::isResolved() const { return mFamily != Family::Unresolved; }
|
||||
|
||||
Candidate::Family Candidate::family() const { return mFamily; }
|
||||
|
||||
Candidate::Type Candidate::type() const { return mType; }
|
||||
|
||||
Candidate::TransportType Candidate::transportType() const { return mTransportType; }
|
||||
|
||||
std::optional<string> Candidate::address() const {
|
||||
return isResolved() ? std::make_optional(mAddress) : nullopt;
|
||||
}
|
||||
|
||||
std::optional<uint16_t> Candidate::port() const {
|
||||
return isResolved() ? std::make_optional(mPort) : nullopt;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> Candidate::priority() const {
|
||||
return isResolved() ? std::make_optional(mPriority) : nullopt;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
|
||||
return out << std::string(candidate);
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type) {
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type) {
|
||||
switch (type) {
|
||||
case rtc::CandidateType::Host:
|
||||
return out << "Host";
|
||||
case rtc::CandidateType::PeerReflexive:
|
||||
return out << "PeerReflexive";
|
||||
case rtc::CandidateType::Relayed:
|
||||
return out << "Relayed";
|
||||
case rtc::CandidateType::ServerReflexive:
|
||||
return out << "ServerReflexive";
|
||||
case rtc::Candidate::Type::Host:
|
||||
return out << "host";
|
||||
case rtc::Candidate::Type::PeerReflexive:
|
||||
return out << "peer_reflexive";
|
||||
case rtc::Candidate::Type::ServerReflexive:
|
||||
return out << "server_reflexive";
|
||||
case rtc::Candidate::Type::Relayed:
|
||||
return out << "relayed";
|
||||
default:
|
||||
return out << "Unknown";
|
||||
return out << "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType) {
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType) {
|
||||
switch (transportType) {
|
||||
case rtc::CandidateTransportType::TcpActive:
|
||||
return out << "TcpActive";
|
||||
case rtc::CandidateTransportType::TcpPassive:
|
||||
return out << "TcpPassive";
|
||||
case rtc::CandidateTransportType::TcpSo:
|
||||
return out << "TcpSo";
|
||||
case rtc::CandidateTransportType::Udp:
|
||||
return out << "Udp";
|
||||
case rtc::Candidate::TransportType::Udp:
|
||||
return out << "UDP";
|
||||
case rtc::Candidate::TransportType::TcpActive:
|
||||
return out << "TCP_active";
|
||||
case rtc::Candidate::TransportType::TcpPassive:
|
||||
return out << "TCP_passive";
|
||||
case rtc::Candidate::TransportType::TcpSo:
|
||||
return out << "TCP_so";
|
||||
case rtc::Candidate::TransportType::TcpUnknown:
|
||||
return out << "TCP_unknown";
|
||||
default:
|
||||
return out << "Unknown";
|
||||
return out << "unknown";
|
||||
}
|
||||
}
|
||||
|
349
src/capi.cpp
349
src/capi.cpp
@ -195,12 +195,42 @@ template <typename F> int wrap(F func) {
|
||||
return RTC_ERR_SUCCESS; \
|
||||
})
|
||||
|
||||
class plog_appender : public plog::IAppender {
|
||||
public:
|
||||
plog_appender(rtcLogCallbackFunc cb = nullptr) { set_callback(cb); }
|
||||
int copyAndReturn(string s, char *buffer, int size) {
|
||||
if (!buffer)
|
||||
return int(s.size() + 1);
|
||||
|
||||
void set_callback(rtcLogCallbackFunc cb) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (size < int(s.size()))
|
||||
return RTC_ERR_TOO_SMALL;
|
||||
|
||||
std::copy(s.begin(), s.end(), buffer);
|
||||
buffer[s.size()] = '\0';
|
||||
return int(s.size() + 1);
|
||||
}
|
||||
|
||||
int copyAndReturn(binary b, char *buffer, int size) {
|
||||
if (!buffer)
|
||||
return int(b.size());
|
||||
|
||||
if (size < int(b.size()))
|
||||
return RTC_ERR_TOO_SMALL;
|
||||
|
||||
auto data = reinterpret_cast<const char *>(b.data());
|
||||
std::copy(data, data + b.size(), buffer);
|
||||
buffer[b.size()] = '\0';
|
||||
return int(b.size());
|
||||
}
|
||||
|
||||
class plogAppender : public plog::IAppender {
|
||||
public:
|
||||
plogAppender(rtcLogCallbackFunc cb = nullptr) { setCallback(cb); }
|
||||
|
||||
plogAppender(plogAppender &&appender) : callback(nullptr) {
|
||||
std::lock_guard lock(appender.callbackMutex);
|
||||
std::swap(appender.callback, callback);
|
||||
}
|
||||
|
||||
void setCallback(rtcLogCallbackFunc cb) {
|
||||
std::lock_guard lock(callbackMutex);
|
||||
callback = cb;
|
||||
}
|
||||
|
||||
@ -215,7 +245,7 @@ public:
|
||||
#else
|
||||
std::string str = formatted;
|
||||
#endif
|
||||
std::lock_guard lock(mutex);
|
||||
std::lock_guard lock(callbackMutex);
|
||||
if (callback)
|
||||
callback(static_cast<rtcLogLevel>(record.getSeverity()), str.c_str());
|
||||
else
|
||||
@ -224,18 +254,24 @@ public:
|
||||
|
||||
private:
|
||||
rtcLogCallbackFunc callback;
|
||||
std::mutex callbackMutex;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
|
||||
static std::optional<plog_appender> appender;
|
||||
if (appender)
|
||||
appender->set_callback(cb);
|
||||
else if (cb)
|
||||
appender.emplace(plog_appender(cb));
|
||||
|
||||
InitLogger(static_cast<plog::Severity>(level), appender ? &appender.value() : nullptr);
|
||||
static std::optional<plogAppender> appender;
|
||||
const auto severity = static_cast<plog::Severity>(level);
|
||||
std::lock_guard lock(mutex);
|
||||
if (appender) {
|
||||
appender->setCallback(cb);
|
||||
InitLogger(severity, nullptr); // change the severity
|
||||
} else if (cb) {
|
||||
appender.emplace(plogAppender(cb));
|
||||
InitLogger(severity, &appender.value());
|
||||
} else {
|
||||
InitLogger(severity, nullptr); // log to stdout
|
||||
}
|
||||
}
|
||||
|
||||
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
|
||||
@ -268,45 +304,49 @@ int rtcDeletePeerConnection(int pc) {
|
||||
});
|
||||
}
|
||||
|
||||
int rtcAddDataChannel(int pc, const char *label) {
|
||||
return rtcAddDataChannelExt(pc, label, nullptr, nullptr);
|
||||
}
|
||||
int rtcAddDataChannel(int pc, const char *label) { return rtcAddDataChannelEx(pc, label, nullptr); }
|
||||
|
||||
int rtcAddDataChannelExt(int pc, const char *label, const char *protocol,
|
||||
const rtcReliability *reliability) {
|
||||
int rtcAddDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init) {
|
||||
return WRAP({
|
||||
Reliability r = {};
|
||||
if (reliability) {
|
||||
r.unordered = reliability->unordered;
|
||||
DataChannelInit dci = {};
|
||||
if (init) {
|
||||
auto *reliability = &init->reliability;
|
||||
dci.reliability.unordered = reliability->unordered;
|
||||
if (reliability->unreliable) {
|
||||
if (reliability->maxPacketLifeTime > 0) {
|
||||
r.type = Reliability::Type::Timed;
|
||||
r.rexmit = milliseconds(reliability->maxPacketLifeTime);
|
||||
dci.reliability.type = Reliability::Type::Timed;
|
||||
dci.reliability.rexmit = milliseconds(reliability->maxPacketLifeTime);
|
||||
} else {
|
||||
r.type = Reliability::Type::Rexmit;
|
||||
r.rexmit = int(reliability->maxRetransmits);
|
||||
dci.reliability.type = Reliability::Type::Rexmit;
|
||||
dci.reliability.rexmit = int(reliability->maxRetransmits);
|
||||
}
|
||||
} else {
|
||||
r.type = Reliability::Type::Reliable;
|
||||
dci.reliability.type = Reliability::Type::Reliable;
|
||||
}
|
||||
|
||||
dci.negotiated = init->negotiated;
|
||||
dci.id = init->manualStream ? std::make_optional(init->stream) : nullopt;
|
||||
dci.protocol = init->protocol ? init->protocol : "";
|
||||
}
|
||||
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
int dc = emplaceDataChannel(peerConnection->addDataChannel(
|
||||
string(label ? label : ""), string(protocol ? protocol : ""), r));
|
||||
int dc = emplaceDataChannel(
|
||||
peerConnection->addDataChannel(string(label ? label : ""), std::move(dci)));
|
||||
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
rtcSetUserPointer(dc, *ptr);
|
||||
|
||||
return dc;
|
||||
});
|
||||
}
|
||||
|
||||
int rtcCreateDataChannel(int pc, const char *label) {
|
||||
return rtcCreateDataChannelExt(pc, label, nullptr, nullptr);
|
||||
return rtcCreateDataChannelEx(pc, label, nullptr);
|
||||
}
|
||||
|
||||
int rtcCreateDataChannelExt(int pc, const char *label, const char *protocol,
|
||||
const rtcReliability *reliability) {
|
||||
int dc = rtcAddDataChannelExt(pc, label, protocol, reliability);
|
||||
rtcSetLocalDescription(pc);
|
||||
int rtcCreateDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init) {
|
||||
int dc = rtcAddDataChannelEx(pc, label, init);
|
||||
rtcSetLocalDescription(pc, NULL);
|
||||
return dc;
|
||||
}
|
||||
|
||||
@ -334,6 +374,7 @@ int rtcAddTrack(int pc, const char *mediaDescriptionSdp) {
|
||||
int tr = emplaceTrack(peerConnection->addTrack(std::move(media)));
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
rtcSetUserPointer(tr, *ptr);
|
||||
|
||||
return tr;
|
||||
});
|
||||
}
|
||||
@ -355,19 +396,7 @@ int rtcDeleteTrack(int tr) {
|
||||
int rtcGetTrackDescription(int tr, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto track = getTrack(tr);
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer for buffer");
|
||||
|
||||
string description(track->description());
|
||||
const char *data = description.data();
|
||||
size = std::min(size - 1, int(description.size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return int(size + 1);
|
||||
return copyAndReturn(track->description(), buffer, size);
|
||||
});
|
||||
}
|
||||
|
||||
@ -411,7 +440,7 @@ int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
|
||||
if (cb)
|
||||
peerConnection->onLocalDescription([pc, cb](Description desc) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(string(desc).c_str(), desc.typeString().c_str(), *ptr);
|
||||
cb(pc, string(desc).c_str(), desc.typeString().c_str(), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalDescription(nullptr);
|
||||
@ -424,7 +453,7 @@ int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
|
||||
if (cb)
|
||||
peerConnection->onLocalCandidate([pc, cb](Candidate cand) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(cand.candidate().c_str(), cand.mid().c_str(), *ptr);
|
||||
cb(pc, cand.candidate().c_str(), cand.mid().c_str(), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalCandidate(nullptr);
|
||||
@ -437,7 +466,7 @@ int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
|
||||
if (cb)
|
||||
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(static_cast<rtcState>(state), *ptr);
|
||||
cb(pc, static_cast<rtcState>(state), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onStateChange(nullptr);
|
||||
@ -450,7 +479,20 @@ int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb)
|
||||
if (cb)
|
||||
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(static_cast<rtcGatheringState>(state), *ptr);
|
||||
cb(pc, static_cast<rtcGatheringState>(state), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (cb)
|
||||
peerConnection->onSignalingStateChange([pc, cb](PeerConnection::SignalingState state) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(pc, static_cast<rtcSignalingState>(state), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
@ -465,7 +507,7 @@ int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
|
||||
int dc = emplaceDataChannel(dataChannel);
|
||||
if (auto ptr = getUserPointer(pc)) {
|
||||
rtcSetUserPointer(dc, *ptr);
|
||||
cb(dc, *ptr);
|
||||
cb(pc, dc, *ptr);
|
||||
}
|
||||
});
|
||||
else
|
||||
@ -481,7 +523,7 @@ int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
|
||||
int tr = emplaceTrack(track);
|
||||
if (auto ptr = getUserPointer(pc)) {
|
||||
rtcSetUserPointer(tr, *ptr);
|
||||
cb(tr, *ptr);
|
||||
cb(pc, tr, *ptr);
|
||||
}
|
||||
});
|
||||
else
|
||||
@ -489,10 +531,11 @@ int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetLocalDescription(int pc) {
|
||||
int rtcSetLocalDescription(int pc, const char *type) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
peerConnection->setLocalDescription();
|
||||
peerConnection->setLocalDescription(type ? Description::stringToType(type)
|
||||
: Description::Type::Unspec);
|
||||
});
|
||||
}
|
||||
|
||||
@ -518,23 +561,36 @@ int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetLocalDescription(int pc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (auto desc = peerConnection->localDescription())
|
||||
return copyAndReturn(string(*desc), buffer, size);
|
||||
else
|
||||
return RTC_ERR_NOT_AVAIL;
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetRemoteDescription(int pc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (auto desc = peerConnection->remoteDescription())
|
||||
return copyAndReturn(string(*desc), buffer, size);
|
||||
else
|
||||
return RTC_ERR_NOT_AVAIL;
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer for buffer");
|
||||
|
||||
if (auto addr = peerConnection->localAddress()) {
|
||||
const char *data = addr->data();
|
||||
size = std::min(size - 1, int(addr->size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
if (auto addr = peerConnection->localAddress())
|
||||
return copyAndReturn(std::move(*addr), buffer, size);
|
||||
else
|
||||
return RTC_ERR_NOT_AVAIL;
|
||||
});
|
||||
}
|
||||
|
||||
@ -542,57 +598,52 @@ int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
if (auto addr = peerConnection->remoteAddress())
|
||||
return copyAndReturn(std::move(*addr), buffer, size);
|
||||
else
|
||||
return RTC_ERR_NOT_AVAIL;
|
||||
});
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer for buffer");
|
||||
int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (auto addr = peerConnection->remoteAddress()) {
|
||||
const char *data = addr->data();
|
||||
size = std::min(size - 1, int(addr->size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return int(size + 1);
|
||||
}
|
||||
Candidate localCand;
|
||||
Candidate remoteCand;
|
||||
if (!peerConnection->getSelectedCandidatePair(&localCand, &remoteCand))
|
||||
return RTC_ERR_NOT_AVAIL;
|
||||
|
||||
int localRet = copyAndReturn(string(localCand), local, localSize);
|
||||
if (localRet < 0)
|
||||
return localRet;
|
||||
|
||||
int remoteRet = copyAndReturn(string(remoteCand), remote, remoteSize);
|
||||
if (remoteRet < 0)
|
||||
return remoteRet;
|
||||
|
||||
return std::max(localRet, remoteRet);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetDataChannelStream(int dc) {
|
||||
return WRAP({
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
return int(dataChannel->id());
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer for buffer");
|
||||
|
||||
string label = dataChannel->label();
|
||||
const char *data = label.data();
|
||||
size = std::min(size - 1, int(label.size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return int(size + 1);
|
||||
return copyAndReturn(dataChannel->label(), buffer, size);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetDataChannelProtocol(int dc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer for buffer");
|
||||
|
||||
string protocol = dataChannel->protocol();
|
||||
const char *data = protocol.data();
|
||||
size = std::min(size - 1, int(protocol.size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return int(size + 1);
|
||||
return copyAndReturn(dataChannel->protocol(), buffer, size);
|
||||
});
|
||||
}
|
||||
|
||||
@ -603,19 +654,19 @@ int rtcGetDataChannelReliability(int dc, rtcReliability *reliability) {
|
||||
if (!reliability)
|
||||
throw std::invalid_argument("Unexpected null pointer for reliability");
|
||||
|
||||
Reliability r = dataChannel->reliability();
|
||||
Reliability dcr = dataChannel->reliability();
|
||||
std::memset(reliability, 0, sizeof(*reliability));
|
||||
reliability->unordered = r.unordered;
|
||||
if (r.type == Reliability::Type::Timed) {
|
||||
reliability->unordered = dcr.unordered;
|
||||
if (dcr.type == Reliability::Type::Timed) {
|
||||
reliability->unreliable = true;
|
||||
reliability->maxPacketLifeTime = unsigned(std::get<milliseconds>(r.rexmit).count());
|
||||
} else if (r.type == Reliability::Type::Rexmit) {
|
||||
reliability->maxPacketLifeTime = unsigned(std::get<milliseconds>(dcr.rexmit).count());
|
||||
} else if (dcr.type == Reliability::Type::Rexmit) {
|
||||
reliability->unreliable = true;
|
||||
reliability->maxRetransmits = unsigned(std::get<int>(r.rexmit));
|
||||
reliability->maxRetransmits = unsigned(std::get<int>(dcr.rexmit));
|
||||
} else {
|
||||
reliability->unreliable = false;
|
||||
}
|
||||
return 0;
|
||||
return RTC_ERR_SUCCESS;
|
||||
});
|
||||
}
|
||||
|
||||
@ -625,7 +676,7 @@ int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
|
||||
if (cb)
|
||||
channel->onOpen([id, cb]() {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(*ptr);
|
||||
cb(id, *ptr);
|
||||
});
|
||||
else
|
||||
channel->onOpen(nullptr);
|
||||
@ -638,7 +689,7 @@ int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
|
||||
if (cb)
|
||||
channel->onClosed([id, cb]() {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(*ptr);
|
||||
cb(id, *ptr);
|
||||
});
|
||||
else
|
||||
channel->onClosed(nullptr);
|
||||
@ -651,7 +702,7 @@ int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
|
||||
if (cb)
|
||||
channel->onError([id, cb](string error) {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(error.c_str(), *ptr);
|
||||
cb(id, error.c_str(), *ptr);
|
||||
});
|
||||
else
|
||||
channel->onError(nullptr);
|
||||
@ -665,11 +716,11 @@ int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
|
||||
channel->onMessage(
|
||||
[id, cb](binary b) {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
|
||||
cb(id, reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
|
||||
},
|
||||
[id, cb](string s) {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(s.c_str(), -int(s.size() + 1), *ptr);
|
||||
cb(id, s.c_str(), -int(s.size() + 1), *ptr);
|
||||
});
|
||||
else
|
||||
channel->onMessage(nullptr);
|
||||
@ -716,7 +767,7 @@ int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb)
|
||||
if (cb)
|
||||
channel->onBufferedAmountLow([id, cb]() {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(*ptr);
|
||||
cb(id, *ptr);
|
||||
});
|
||||
else
|
||||
channel->onBufferedAmountLow(nullptr);
|
||||
@ -731,12 +782,12 @@ int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
if (cb)
|
||||
channel->onOpen([id, cb]() {
|
||||
channel->onAvailable([id, cb]() {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(*ptr);
|
||||
cb(id, *ptr);
|
||||
});
|
||||
else
|
||||
channel->onOpen(nullptr);
|
||||
channel->onAvailable(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
@ -747,34 +798,38 @@ int rtcReceiveMessage(int id, char *buffer, int *size) {
|
||||
if (!size)
|
||||
throw std::invalid_argument("Unexpected null pointer for size");
|
||||
|
||||
if (!buffer && *size != 0)
|
||||
throw std::invalid_argument("Unexpected null pointer for buffer");
|
||||
*size = std::abs(*size);
|
||||
|
||||
if (auto message = channel->receive())
|
||||
return std::visit( //
|
||||
overloaded{ //
|
||||
[&](binary b) {
|
||||
if (*size > 0) {
|
||||
*size = std::min(*size, int(b.size()));
|
||||
auto data = reinterpret_cast<const char *>(b.data());
|
||||
std::copy(data, data + *size, buffer);
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
[&](string s) {
|
||||
if (*size > 0) {
|
||||
int len = std::min(*size - 1, int(s.size()));
|
||||
if (len >= 0) {
|
||||
std::copy(s.data(), s.data() + len, buffer);
|
||||
buffer[len] = '\0';
|
||||
}
|
||||
*size = -(len + 1);
|
||||
}
|
||||
return 1;
|
||||
}},
|
||||
*message);
|
||||
else
|
||||
return 0;
|
||||
auto message = channel->peek();
|
||||
if (!message)
|
||||
return RTC_ERR_NOT_AVAIL;
|
||||
|
||||
return std::visit( //
|
||||
overloaded{
|
||||
[&](binary b) {
|
||||
int ret = copyAndReturn(std::move(b), buffer, *size);
|
||||
if (ret >= 0) {
|
||||
channel->receive(); // discard
|
||||
*size = ret;
|
||||
return RTC_ERR_SUCCESS;
|
||||
} else {
|
||||
*size = int(b.size());
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
[&](string s) {
|
||||
int ret = copyAndReturn(std::move(s), buffer, *size);
|
||||
if (ret >= 0) {
|
||||
channel->receive(); // discard
|
||||
*size = -ret;
|
||||
return RTC_ERR_SUCCESS;
|
||||
} else {
|
||||
*size = -int(s.size() + 1);
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
},
|
||||
*message);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,11 @@ certificate_ptr make_certificate_impl(string commonName) {
|
||||
unique_ptr<gnutls_x509_crt_t, decltype(&free_crt)> crt(new_crt(), free_crt);
|
||||
unique_ptr<gnutls_x509_privkey_t, decltype(&free_privkey)> privkey(new_privkey(), free_privkey);
|
||||
|
||||
#ifdef RSA_KEY_BITS_2048
|
||||
const unsigned int bits = 2048;
|
||||
#else
|
||||
const unsigned int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_HIGH);
|
||||
#endif
|
||||
gnutls::check(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_RSA, bits, 0),
|
||||
"Unable to generate key pair");
|
||||
|
||||
@ -149,22 +153,23 @@ Certificate::Certificate(string crt_pem, string key_pem) {
|
||||
mFingerprint = make_fingerprint(mX509.get());
|
||||
}
|
||||
|
||||
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)
|
||||
: mX509(std::move(x509)), mPKey(std::move(pkey)) {
|
||||
mFingerprint = make_fingerprint(mX509.get());
|
||||
}
|
||||
|
||||
string Certificate::fingerprint() const { return mFingerprint; }
|
||||
|
||||
std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const { return {mX509.get(), mPKey.get()}; }
|
||||
std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const {
|
||||
return {mX509.get(), mPKey.get()};
|
||||
}
|
||||
|
||||
string make_fingerprint(X509 *x509) {
|
||||
const size_t size = 32;
|
||||
unsigned char buffer[size];
|
||||
unsigned int len = size;
|
||||
if (!X509_digest(x509, EVP_sha256(), buffer, &len))
|
||||
throw std::runtime_error("X509 fingerprint error");
|
||||
unsigned char buffer[size];
|
||||
unsigned int len = size;
|
||||
if (!X509_digest(x509, EVP_sha256(), buffer, &len))
|
||||
throw std::runtime_error("X509 fingerprint error");
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << std::hex << std::uppercase << std::setfill('0');
|
||||
@ -182,15 +187,19 @@ certificate_ptr make_certificate_impl(string commonName) {
|
||||
shared_ptr<X509> x509(X509_new(), X509_free);
|
||||
shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
|
||||
|
||||
unique_ptr<RSA, decltype(&RSA_free)> rsa(RSA_new(), RSA_free);
|
||||
unique_ptr<RSA, decltype(&RSA_free)> rsa(RSA_new(), RSA_free);
|
||||
unique_ptr<BIGNUM, decltype(&BN_free)> exponent(BN_new(), BN_free);
|
||||
unique_ptr<BIGNUM, decltype(&BN_free)> serial_number(BN_new(), BN_free);
|
||||
unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
|
||||
unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
|
||||
|
||||
if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
|
||||
throw std::runtime_error("Unable allocate structures for certificate generation");
|
||||
|
||||
const int bits = 4096;
|
||||
#ifdef RSA_KEY_BITS_2048
|
||||
const int bits = 2048;
|
||||
#else
|
||||
const int bits = 3072;
|
||||
#endif
|
||||
const unsigned int e = 65537; // 2^16 + 1
|
||||
|
||||
if (!pkey || !rsa || !exponent || !BN_set_word(exponent.get(), e) ||
|
||||
|
@ -26,13 +26,9 @@ size_t Channel::bufferedAmount() const { return mBufferedAmount; }
|
||||
|
||||
size_t Channel::availableAmount() const { return 0; }
|
||||
|
||||
void Channel::onOpen(std::function<void()> callback) {
|
||||
mOpenCallback = callback;
|
||||
}
|
||||
void Channel::onOpen(std::function<void()> callback) { mOpenCallback = callback; }
|
||||
|
||||
void Channel::onClosed(std::function<void()> callback) {
|
||||
mClosedCallback = callback;
|
||||
}
|
||||
void Channel::onClosed(std::function<void()> callback) { mClosedCallback = callback; }
|
||||
|
||||
void Channel::onError(std::function<void(string error)> callback) { mErrorCallback = callback; }
|
||||
|
||||
@ -57,9 +53,7 @@ void Channel::onBufferedAmountLow(std::function<void()> callback) {
|
||||
|
||||
void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; }
|
||||
|
||||
void Channel::onAvailable(std::function<void()> callback) {
|
||||
mAvailableCallback = callback;
|
||||
}
|
||||
void Channel::onAvailable(std::function<void()> callback) { mAvailableCallback = callback; }
|
||||
|
||||
void Channel::triggerOpen() { mOpenCallback(); }
|
||||
|
||||
@ -96,4 +90,3 @@ void Channel::resetCallbacks() {
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
|
@ -72,24 +72,18 @@ struct CloseMessage {
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
DataChannel::DataChannel(weak_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||
DataChannel::DataChannel(weak_ptr<PeerConnection> pc, uint16_t stream, string label,
|
||||
string protocol, Reliability reliability)
|
||||
: mPeerConnection(pc), mStream(stream), mLabel(std::move(label)),
|
||||
mProtocol(std::move(protocol)),
|
||||
mReliability(std::make_shared<Reliability>(std::move(reliability))),
|
||||
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
||||
|
||||
DataChannel::DataChannel(weak_ptr<PeerConnection> pc, weak_ptr<SctpTransport> transport,
|
||||
unsigned int stream)
|
||||
: mPeerConnection(pc), mSctpTransport(transport), mStream(stream),
|
||||
mReliability(std::make_shared<Reliability>()),
|
||||
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
||||
DataChannel::~DataChannel() { close(); }
|
||||
|
||||
DataChannel::~DataChannel() {
|
||||
close();
|
||||
}
|
||||
uint16_t DataChannel::stream() const { return mStream; }
|
||||
|
||||
unsigned int DataChannel::stream() const { return mStream; }
|
||||
uint16_t DataChannel::id() const { return uint16_t(mStream); }
|
||||
|
||||
string DataChannel::label() const { return mLabel; }
|
||||
|
||||
@ -123,14 +117,29 @@ bool DataChannel::send(const byte *data, size_t size) {
|
||||
|
||||
std::optional<message_variant> DataChannel::receive() {
|
||||
while (auto next = mRecvQueue.tryPop()) {
|
||||
message_ptr message = std::move(*next);
|
||||
if (message->type == Message::Control) {
|
||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||
if (!message->empty() && raw[0] == MESSAGE_CLOSE)
|
||||
remoteClose();
|
||||
} else {
|
||||
message_ptr message = *next;
|
||||
if (message->type != Message::Control)
|
||||
return to_variant(std::move(*message));
|
||||
}
|
||||
|
||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||
if (!message->empty() && raw[0] == MESSAGE_CLOSE)
|
||||
remoteClose();
|
||||
}
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::optional<message_variant> DataChannel::peek() {
|
||||
while (auto next = mRecvQueue.peek()) {
|
||||
message_ptr message = *next;
|
||||
if (message->type != Message::Control)
|
||||
return to_variant(std::move(*message));
|
||||
|
||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||
if (!message->empty() && raw[0] == MESSAGE_CLOSE)
|
||||
remoteClose();
|
||||
|
||||
mRecvQueue.tryPop();
|
||||
}
|
||||
|
||||
return nullopt;
|
||||
@ -156,6 +165,87 @@ size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
|
||||
void DataChannel::open(shared_ptr<SctpTransport> transport) {
|
||||
mSctpTransport = transport;
|
||||
|
||||
if (!mIsOpen.exchange(true))
|
||||
triggerOpen();
|
||||
}
|
||||
|
||||
void DataChannel::processOpenMessage(message_ptr) {
|
||||
PLOG_WARNING << "Received an open message for a user-negotiated DataChannel, ignoring";
|
||||
}
|
||||
|
||||
bool DataChannel::outgoing(message_ptr message) {
|
||||
if (mIsClosed)
|
||||
throw std::runtime_error("DataChannel is closed");
|
||||
|
||||
if (message->size() > maxMessageSize())
|
||||
throw std::runtime_error("Message size exceeds limit");
|
||||
|
||||
auto transport = mSctpTransport.lock();
|
||||
if (!transport)
|
||||
throw std::runtime_error("DataChannel transport is not open");
|
||||
|
||||
// Before the ACK has been received on a DataChannel, all messages must be sent ordered
|
||||
message->reliability = mIsOpen ? mReliability : nullptr;
|
||||
message->stream = mStream;
|
||||
return transport->send(message);
|
||||
}
|
||||
|
||||
void DataChannel::incoming(message_ptr message) {
|
||||
if (!message)
|
||||
return;
|
||||
|
||||
switch (message->type) {
|
||||
case Message::Control: {
|
||||
if (message->size() == 0)
|
||||
break; // Ignore
|
||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||
switch (raw[0]) {
|
||||
case MESSAGE_OPEN:
|
||||
processOpenMessage(message);
|
||||
break;
|
||||
case MESSAGE_ACK:
|
||||
if (!mIsOpen.exchange(true)) {
|
||||
triggerOpen();
|
||||
}
|
||||
break;
|
||||
case MESSAGE_CLOSE:
|
||||
// The close message will be processed in-order in receive()
|
||||
mRecvQueue.push(message);
|
||||
triggerAvailable(mRecvQueue.size());
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Message::String:
|
||||
case Message::Binary:
|
||||
mRecvQueue.push(message);
|
||||
triggerAvailable(mRecvQueue.size());
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NegociatedDataChannel::NegociatedDataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream,
|
||||
string label, string protocol, Reliability reliability)
|
||||
: DataChannel(pc, stream, std::move(label), std::move(protocol), std::move(reliability)) {}
|
||||
|
||||
NegociatedDataChannel::NegociatedDataChannel(std::weak_ptr<PeerConnection> pc,
|
||||
std::weak_ptr<SctpTransport> transport,
|
||||
uint16_t stream)
|
||||
: DataChannel(pc, stream, "", "", {}) {
|
||||
mSctpTransport = transport;
|
||||
}
|
||||
|
||||
NegociatedDataChannel::~NegociatedDataChannel() {}
|
||||
|
||||
void NegociatedDataChannel::open(shared_ptr<SctpTransport> transport) {
|
||||
mSctpTransport = transport;
|
||||
|
||||
uint8_t channelType;
|
||||
uint32_t reliabilityParameter;
|
||||
switch (mReliability->type) {
|
||||
@ -195,62 +285,7 @@ void DataChannel::open(shared_ptr<SctpTransport> transport) {
|
||||
transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||
}
|
||||
|
||||
bool DataChannel::outgoing(message_ptr message) {
|
||||
if (mIsClosed)
|
||||
throw std::runtime_error("DataChannel is closed");
|
||||
|
||||
if (message->size() > maxMessageSize())
|
||||
throw std::runtime_error("Message size exceeds limit");
|
||||
|
||||
auto transport = mSctpTransport.lock();
|
||||
if (!transport)
|
||||
throw std::runtime_error("DataChannel transport is not open");
|
||||
|
||||
// Before the ACK has been received on a DataChannel, all messages must be sent ordered
|
||||
message->reliability = mIsOpen ? mReliability : nullptr;
|
||||
message->stream = mStream;
|
||||
return transport->send(message);
|
||||
}
|
||||
|
||||
void DataChannel::incoming(message_ptr message) {
|
||||
if (!message)
|
||||
return;
|
||||
|
||||
switch (message->type) {
|
||||
case Message::Control: {
|
||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||
switch (raw[0]) {
|
||||
case MESSAGE_OPEN:
|
||||
processOpenMessage(message);
|
||||
break;
|
||||
case MESSAGE_ACK:
|
||||
if (!mIsOpen.exchange(true)) {
|
||||
triggerOpen();
|
||||
}
|
||||
break;
|
||||
case MESSAGE_CLOSE:
|
||||
// The close message will be processed in-order in receive()
|
||||
mRecvQueue.push(message);
|
||||
triggerAvailable(mRecvQueue.size());
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Message::String:
|
||||
case Message::Binary:
|
||||
mRecvQueue.push(message);
|
||||
triggerAvailable(mRecvQueue.size());
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DataChannel::processOpenMessage(message_ptr message) {
|
||||
void NegociatedDataChannel::processOpenMessage(message_ptr message) {
|
||||
auto transport = mSctpTransport.lock();
|
||||
if (!transport)
|
||||
throw std::runtime_error("DataChannel has no transport");
|
||||
@ -292,8 +327,8 @@ void DataChannel::processOpenMessage(message_ptr message) {
|
||||
|
||||
transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||
|
||||
mIsOpen = true;
|
||||
triggerOpen();
|
||||
if (!mIsOpen.exchange(true))
|
||||
triggerOpen();
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||
* Copyright (c) 2020 Staz M
|
||||
* Copyright (c) 2020 Staz Modrzynski
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -26,6 +26,7 @@
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::size_t;
|
||||
@ -65,20 +66,10 @@ template <typename T> T to_integer(string_view s) {
|
||||
|
||||
namespace rtc {
|
||||
|
||||
Description::Description(const string &sdp, const string &typeString)
|
||||
: Description(sdp, stringToType(typeString)) {}
|
||||
|
||||
Description::Description(const string &sdp, Type type) : Description(sdp, type, Role::ActPass) {}
|
||||
|
||||
Description::Description(const string &sdp, Type type, Role role)
|
||||
: mType(Type::Unspec), mRole(role) {
|
||||
hintType(type);
|
||||
|
||||
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||
std::default_random_engine generator(seed);
|
||||
std::uniform_int_distribution<uint32_t> uniform;
|
||||
mSessionId = std::to_string(uniform(generator));
|
||||
|
||||
int index = -1;
|
||||
std::shared_ptr<Entry> current;
|
||||
std::istringstream ss(sdp);
|
||||
@ -89,14 +80,14 @@ Description::Description(const string &sdp, Type type, Role role)
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
// Media description line (aka m-line)
|
||||
if (match_prefix(line, "m=")) {
|
||||
++index;
|
||||
string mline = line.substr(2);
|
||||
current = createEntry(std::move(mline), std::to_string(index), Direction::Unknown);
|
||||
if (match_prefix(line, "m=")) { // Media description line (aka m-line)
|
||||
current = createEntry(line.substr(2), std::to_string(++index), Direction::Unknown);
|
||||
|
||||
// Attribute line
|
||||
} else if (match_prefix(line, "a=")) {
|
||||
} else if (match_prefix(line, "o=")) { // Origin line
|
||||
std::istringstream origin(line.substr(2));
|
||||
origin >> mUsername >> mSessionId;
|
||||
|
||||
} else if (match_prefix(line, "a=")) { // Attribute line
|
||||
string attr = line.substr(2);
|
||||
auto [key, value] = parse_pair(attr);
|
||||
|
||||
@ -115,7 +106,7 @@ Description::Description(const string &sdp, Type type, Role role)
|
||||
mFingerprint->begin(),
|
||||
[](char c) { return char(std::toupper(c)); });
|
||||
} else {
|
||||
PLOG_WARNING << "Unknown SDP fingerprint type: " << value;
|
||||
PLOG_WARNING << "Unknown SDP fingerprint format: " << value;
|
||||
}
|
||||
} else if (key == "ice-ufrag") {
|
||||
mIceUfrag = value;
|
||||
@ -134,29 +125,35 @@ Description::Description(const string &sdp, Type type, Role role)
|
||||
}
|
||||
}
|
||||
|
||||
if (mIceUfrag.empty())
|
||||
throw std::invalid_argument("Missing ice-ufrag parameter in SDP description");
|
||||
if (mUsername.empty())
|
||||
mUsername = "rtc";
|
||||
|
||||
if (mIcePwd.empty())
|
||||
throw std::invalid_argument("Missing ice-pwd parameter in SDP description");
|
||||
if (mSessionId.empty()) {
|
||||
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||
std::default_random_engine generator(seed);
|
||||
std::uniform_int_distribution<uint32_t> uniform;
|
||||
mSessionId = std::to_string(uniform(generator));
|
||||
}
|
||||
}
|
||||
|
||||
Description::Description(const string &sdp, string typeString)
|
||||
: Description(sdp, !typeString.empty() ? stringToType(typeString) : Type::Unspec,
|
||||
Role::ActPass) {}
|
||||
|
||||
Description::Type Description::type() const { return mType; }
|
||||
|
||||
string Description::typeString() const { return typeToString(mType); }
|
||||
|
||||
Description::Role Description::role() const { return mRole; }
|
||||
|
||||
string Description::roleString() const { return roleToString(mRole); }
|
||||
|
||||
string Description::bundleMid() const {
|
||||
// Get the mid of the first media
|
||||
return !mEntries.empty() ? mEntries[0]->mid() : "0";
|
||||
}
|
||||
|
||||
string Description::iceUfrag() const { return mIceUfrag; }
|
||||
std::optional<string> Description::iceUfrag() const { return mIceUfrag; }
|
||||
|
||||
string Description::icePwd() const { return mIcePwd; }
|
||||
std::optional<string> Description::icePwd() const { return mIcePwd; }
|
||||
|
||||
std::optional<string> Description::fingerprint() const { return mFingerprint; }
|
||||
|
||||
@ -175,9 +172,17 @@ void Description::setFingerprint(string fingerprint) {
|
||||
}
|
||||
|
||||
void Description::addCandidate(Candidate candidate) {
|
||||
candidate.hintMid(bundleMid());
|
||||
mCandidates.emplace_back(std::move(candidate));
|
||||
}
|
||||
|
||||
void Description::addCandidates(std::vector<Candidate> candidates) {
|
||||
for (Candidate candidate : candidates) {
|
||||
candidate.hintMid(bundleMid());
|
||||
mCandidates.emplace_back(std::move(candidate));
|
||||
}
|
||||
}
|
||||
|
||||
void Description::endCandidates() { mEnded = true; }
|
||||
|
||||
std::vector<Candidate> Description::extractCandidates() {
|
||||
@ -194,7 +199,7 @@ string Description::generateSdp(string_view eol) const {
|
||||
|
||||
// Header
|
||||
sdp << "v=0" << eol;
|
||||
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "s=-" << eol;
|
||||
sdp << "t=0 0" << eol;
|
||||
|
||||
@ -217,20 +222,29 @@ string Description::generateSdp(string_view eol) const {
|
||||
|
||||
// Session-level attributes
|
||||
sdp << "a=msid-semantic:WMS *" << eol;
|
||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
||||
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
||||
sdp << "a=setup:" << mRole << eol;
|
||||
|
||||
if (mIceUfrag)
|
||||
sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
|
||||
if (mIcePwd)
|
||||
sdp << "a=ice-pwd:" << *mIcePwd << eol;
|
||||
if (!mEnded)
|
||||
sdp << "a=ice-options:trickle" << eol;
|
||||
|
||||
if (mFingerprint)
|
||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||
|
||||
auto cand = defaultCandidate();
|
||||
const string addr = cand && cand->isResolved()
|
||||
? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
|
||||
" " + *cand->address())
|
||||
: "IP4 0.0.0.0";
|
||||
const string port = std::to_string(
|
||||
cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
|
||||
|
||||
// Entries
|
||||
bool first = true;
|
||||
for (const auto &entry : mEntries) {
|
||||
sdp << entry->generateSdp(eol);
|
||||
sdp << entry->generateSdp(eol, addr, port);
|
||||
|
||||
if (std::exchange(first, false)) {
|
||||
// Candidates
|
||||
@ -250,23 +264,32 @@ string Description::generateApplicationSdp(string_view eol) const {
|
||||
|
||||
// Header
|
||||
sdp << "v=0" << eol;
|
||||
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "s=-" << eol;
|
||||
sdp << "t=0 0" << eol;
|
||||
|
||||
auto cand = defaultCandidate();
|
||||
const string addr = cand && cand->isResolved()
|
||||
? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
|
||||
" " + *cand->address())
|
||||
: "IP4 0.0.0.0";
|
||||
const string port = std::to_string(
|
||||
cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
|
||||
|
||||
// Application
|
||||
auto app = mApplication ? mApplication : std::make_shared<Application>();
|
||||
sdp << app->generateSdp(eol);
|
||||
sdp << app->generateSdp(eol, addr, port);
|
||||
|
||||
// Session-level attributes
|
||||
sdp << "a=msid-semantic:WMS *" << eol;
|
||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
||||
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
||||
sdp << "a=setup:" << mRole << eol;
|
||||
|
||||
if (mIceUfrag)
|
||||
sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
|
||||
if (mIcePwd)
|
||||
sdp << "a=ice-pwd:" << *mIcePwd << eol;
|
||||
if (!mEnded)
|
||||
sdp << "a=ice-options:trickle" << eol;
|
||||
|
||||
if (mFingerprint)
|
||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||
|
||||
@ -280,6 +303,21 @@ string Description::generateApplicationSdp(string_view eol) const {
|
||||
return sdp.str();
|
||||
}
|
||||
|
||||
std::optional<Candidate> Description::defaultCandidate() const {
|
||||
// Return the first host candidate with highest priority, favoring IPv4
|
||||
std::optional<Candidate> result;
|
||||
for (const auto &c : mCandidates) {
|
||||
if (c.type() == Candidate::Type::Host) {
|
||||
if (!result ||
|
||||
(result->family() == Candidate::Family::Ipv6 &&
|
||||
c.family() == Candidate::Family::Ipv4) ||
|
||||
(result->family() == c.family() && result->priority() < c.priority()))
|
||||
result.emplace(c);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
shared_ptr<Description::Entry> Description::createEntry(string mline, string mid, Direction dir) {
|
||||
string type = mline.substr(0, mline.find(' '));
|
||||
if (type == "application") {
|
||||
@ -315,6 +353,14 @@ bool Description::hasAudioOrVideo() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Description::hasMid(string_view mid) const {
|
||||
for (const auto &entry : mEntries)
|
||||
if (entry->mid() == mid)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Description::addMedia(Media media) {
|
||||
mEntries.emplace_back(std::make_shared<Media>(std::move(media)));
|
||||
return int(mEntries.size()) - 1;
|
||||
@ -339,8 +385,9 @@ int Description::addAudio(string mid, Direction dir) {
|
||||
return addMedia(Audio(std::move(mid), dir));
|
||||
}
|
||||
|
||||
std::variant<Description::Media *, Description::Application *> Description::media(int index) {
|
||||
if (index < 0 || index >= int(mEntries.size()))
|
||||
std::variant<Description::Media *, Description::Application *>
|
||||
Description::media(unsigned int index) {
|
||||
if (index >= mEntries.size())
|
||||
throw std::out_of_range("Media index out of range");
|
||||
|
||||
const auto &entry = mEntries[index];
|
||||
@ -358,8 +405,8 @@ std::variant<Description::Media *, Description::Application *> Description::medi
|
||||
}
|
||||
|
||||
std::variant<const Description::Media *, const Description::Application *>
|
||||
Description::media(int index) const {
|
||||
if (index < 0 || index >= int(mEntries.size()))
|
||||
Description::media(unsigned int index) const {
|
||||
if (index >= mEntries.size())
|
||||
throw std::out_of_range("Media index out of range");
|
||||
|
||||
const auto &entry = mEntries[index];
|
||||
@ -376,7 +423,7 @@ Description::media(int index) const {
|
||||
}
|
||||
}
|
||||
|
||||
int Description::mediaCount() const { return int(mEntries.size()); }
|
||||
unsigned int Description::mediaCount() const { return unsigned(mEntries.size()); }
|
||||
|
||||
Description::Entry::Entry(const string &mline, string mid, Direction dir)
|
||||
: mMid(std::move(mid)), mDirection(dir) {
|
||||
@ -390,13 +437,12 @@ Description::Entry::Entry(const string &mline, string mid, Direction dir)
|
||||
|
||||
void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
|
||||
|
||||
Description::Entry::operator string() const { return generateSdp("\r\n"); }
|
||||
Description::Entry::operator string() const { return generateSdp("\r\n", "IP4 0.0.0.0", "9"); }
|
||||
|
||||
string Description::Entry::generateSdp(string_view eol) const {
|
||||
string Description::Entry::generateSdp(string_view eol, string_view addr, string_view port) const {
|
||||
std::ostringstream sdp;
|
||||
// Port 9 is the discard protocol
|
||||
sdp << "m=" << type() << ' ' << 9 << ' ' << description() << eol;
|
||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
||||
sdp << "m=" << type() << ' ' << port << ' ' << description() << eol;
|
||||
sdp << "c=IN " << addr << eol;
|
||||
sdp << generateSdpLines(eol);
|
||||
|
||||
return sdp.str();
|
||||
@ -425,8 +471,11 @@ string Description::Entry::generateSdpLines(string_view eol) const {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const auto &attr : mAttributes)
|
||||
sdp << "a=" << attr << eol;
|
||||
for (const auto &attr : mAttributes) {
|
||||
if (attr.find("extmap") == std::string::npos &&
|
||||
attr.find("rtcp-rsize") == std::string::npos)
|
||||
sdp << "a=" << attr << eol;
|
||||
}
|
||||
|
||||
return sdp.str();
|
||||
}
|
||||
@ -452,6 +501,36 @@ void Description::Entry::parseSdpLine(string_view line) {
|
||||
mAttributes.emplace_back(line.substr(2));
|
||||
}
|
||||
}
|
||||
std::vector<string>::iterator Description::Entry::beginAttributes() { return mAttributes.begin(); }
|
||||
std::vector<string>::iterator Description::Entry::endAttributes() { return mAttributes.end(); }
|
||||
std::vector<string>::iterator
|
||||
Description::Entry::removeAttribute(std::vector<string>::iterator it) {
|
||||
return mAttributes.erase(it);
|
||||
}
|
||||
|
||||
void Description::Media::addSSRC(uint32_t ssrc, std::string name) {
|
||||
mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + name);
|
||||
mSsrcs.emplace_back(ssrc);
|
||||
}
|
||||
|
||||
void Description::Media::replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, std::string name) {
|
||||
auto it = mAttributes.begin();
|
||||
while (it != mAttributes.end()) {
|
||||
if (it->find("ssrc:" + std::to_string(oldSSRC)) == 0) {
|
||||
it = mAttributes.erase(it);
|
||||
} else
|
||||
it++;
|
||||
}
|
||||
mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + name);
|
||||
}
|
||||
|
||||
void Description::Media::addSSRC(uint32_t ssrc) {
|
||||
mAttributes.emplace_back("ssrc:" + std::to_string(ssrc));
|
||||
}
|
||||
|
||||
bool Description::Media::hasSSRC(uint32_t ssrc) {
|
||||
return std::find(mSsrcs.begin(), mSsrcs.end(), ssrc) != mSsrcs.end();
|
||||
}
|
||||
|
||||
Description::Application::Application(string mid)
|
||||
: Entry("application 9 UDP/DTLS/SCTP", std::move(mid), Direction::SendRecv) {}
|
||||
@ -515,8 +594,7 @@ Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown
|
||||
}
|
||||
|
||||
Description::Media::Media(const string &mline, string mid, Direction dir)
|
||||
: Entry(mline, std::move(mid), dir) {
|
||||
}
|
||||
: Entry(mline, std::move(mid), dir) {}
|
||||
|
||||
string Description::Media::description() const {
|
||||
std::ostringstream desc;
|
||||
@ -600,24 +678,57 @@ void Description::Media::removeFormat(const string &fmt) {
|
||||
}
|
||||
}
|
||||
|
||||
void Description::Media::addVideoCodec(int payloadType, const string &codec) {
|
||||
void Description::Video::addVideoCodec(int payloadType, const string &codec) {
|
||||
RTPMap map(std::to_string(payloadType) + ' ' + codec + "/90000");
|
||||
map.addFB("nack");
|
||||
map.addFB("nack pli");
|
||||
// map.addFB("nack fir");
|
||||
map.addFB("goog-remb");
|
||||
if (codec == "H264") {
|
||||
// Use Constrained Baseline profile Level 4.2 (necessary for Firefox)
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#Supported_video_codecs
|
||||
// TODO: Should be 42E0 but 42C0 appears to be more compatible. Investigate this.
|
||||
map.fmtps.emplace_back("profile-level-id=42E02A;level-asymmetry-allowed=1");
|
||||
map.fmtps.emplace_back(
|
||||
"profile-level-id=4de01f;packetization-mode=1;level-asymmetry-allowed=1");
|
||||
|
||||
// Because certain Android devices don't like me, let us just negotiate some random
|
||||
{
|
||||
RTPMap map(std::to_string(payloadType + 1) + ' ' + codec + "/90000");
|
||||
map.addFB("nack");
|
||||
map.addFB("nack pli");
|
||||
// map.addFB("nack fir");
|
||||
map.addFB("goog-remb");
|
||||
addRTPMap(map);
|
||||
}
|
||||
}
|
||||
mRtpMap.emplace(map.pt, map);
|
||||
addRTPMap(map);
|
||||
|
||||
// // RTX Packets
|
||||
/* TODO
|
||||
* TIL that Firefox does not properly support the negotiation of RTX! It works, but doesn't
|
||||
* negotiate the SSRC so we have no idea what SSRC is RTX going to be. Three solutions: One) we
|
||||
* don't negotitate it and (maybe) break RTX support with Edge. Two) we do negotiate it and
|
||||
* rebuild the original packet before we send it distribute it to each track. Three) we complain
|
||||
* to mozilla. This one probably won't do much.
|
||||
*/
|
||||
// RTPMap rtx(std::to_string(payloadType+1) + " rtx/90000");
|
||||
// // TODO rtx-time is how long can a request be stashed for before needing to resend it.
|
||||
// Needs to be parameterized rtx.addAttribute("apt=" + std::to_string(payloadType) +
|
||||
// ";rtx-time=3000"); addRTPMap(rtx);
|
||||
}
|
||||
|
||||
void Description::Media::addH264Codec(int pt) { addVideoCodec(pt, "H264"); }
|
||||
void Description::Audio::addAudioCodec(int payloadType, const string &codec) {
|
||||
// TODO This 48000/2 should be parameterized
|
||||
RTPMap map(std::to_string(payloadType) + ' ' + codec + "/48000/2");
|
||||
map.fmtps.emplace_back("maxaveragebitrate=96000; stereo=1; sprop-stereo=1; useinbandfec=1");
|
||||
addRTPMap(map);
|
||||
}
|
||||
|
||||
void Description::Media::addVP8Codec(int payloadType) { addVideoCodec(payloadType, "VP8"); }
|
||||
void Description::Video::addH264Codec(int pt) { addVideoCodec(pt, "H264"); }
|
||||
|
||||
void Description::Media::addVP9Codec(int payloadType) { addVideoCodec(payloadType, "VP9"); }
|
||||
void Description::Video::addVP8Codec(int payloadType) { addVideoCodec(payloadType, "VP8"); }
|
||||
|
||||
void Description::Video::addVP9Codec(int payloadType) { addVideoCodec(payloadType, "VP9"); }
|
||||
|
||||
void Description::Media::setBitrate(int bitrate) { mBas = bitrate; }
|
||||
|
||||
@ -644,8 +755,10 @@ string Description::Media::generateSdpLines(string_view eol) const {
|
||||
sdp << '/' << map.encParams;
|
||||
sdp << eol;
|
||||
|
||||
for (const auto &val : map.rtcpFbs)
|
||||
sdp << "a=rtcp-fb:" << map.pt << ' ' << val << eol;
|
||||
for (const auto &val : map.rtcpFbs) {
|
||||
if (val != "transport-cc")
|
||||
sdp << "a=rtcp-fb:" << map.pt << ' ' << val << eol;
|
||||
}
|
||||
for (const auto &val : map.fmtps)
|
||||
sdp << "a=fmtp:" << map.pt << ' ' << val << eol;
|
||||
}
|
||||
@ -659,29 +772,32 @@ void Description::Media::parseSdpLine(string_view line) {
|
||||
auto [key, value] = parse_pair(attr);
|
||||
|
||||
if (key == "rtpmap") {
|
||||
Description::Media::RTPMap map(value);
|
||||
int pt = map.pt;
|
||||
mRtpMap.emplace(pt, std::move(map));
|
||||
auto pt = Description::Media::RTPMap::parsePT(value);
|
||||
auto it = mRtpMap.find(pt);
|
||||
if (it == mRtpMap.end()) {
|
||||
it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap(value))).first;
|
||||
} else {
|
||||
it->second.setMLine(value);
|
||||
}
|
||||
} else if (key == "rtcp-fb") {
|
||||
size_t p = value.find(' ');
|
||||
int pt = to_integer<int>(value.substr(0, p));
|
||||
auto it = mRtpMap.find(pt);
|
||||
if (it == mRtpMap.end()) {
|
||||
PLOG_WARNING << "rtcp-fb applied before the corresponding rtpmap, ignoring";
|
||||
} else {
|
||||
it->second.rtcpFbs.emplace_back(value.substr(p + 1));
|
||||
it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap())).first;
|
||||
}
|
||||
it->second.rtcpFbs.emplace_back(value.substr(p + 1));
|
||||
} else if (key == "fmtp") {
|
||||
size_t p = value.find(' ');
|
||||
int pt = to_integer<int>(value.substr(0, p));
|
||||
auto it = mRtpMap.find(pt);
|
||||
if (it == mRtpMap.end()) {
|
||||
PLOG_WARNING << "fmtp applied before the corresponding rtpmap, ignoring";
|
||||
} else {
|
||||
it->second.fmtps.emplace_back(value.substr(p + 1));
|
||||
}
|
||||
if (it == mRtpMap.end())
|
||||
it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap())).first;
|
||||
it->second.fmtps.emplace_back(value.substr(p + 1));
|
||||
} else if (key == "rtcp-mux") {
|
||||
// always added
|
||||
} else if (key == "ssrc") {
|
||||
mSsrcs.emplace_back(std::stoul((std::string)value));
|
||||
} else {
|
||||
Entry::parseSdpLine(line);
|
||||
}
|
||||
@ -692,7 +808,55 @@ void Description::Media::parseSdpLine(string_view line) {
|
||||
}
|
||||
}
|
||||
|
||||
Description::Media::RTPMap::RTPMap(string_view mline) {
|
||||
void Description::Media::addRTPMap(const Description::Media::RTPMap &map) {
|
||||
mRtpMap.emplace(map.pt, map);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> Description::Media::getSSRCs() {
|
||||
std::vector<uint32_t> vec;
|
||||
for (auto &val : mAttributes) {
|
||||
PLOG_DEBUG << val;
|
||||
if (val.find("ssrc:") == 0) {
|
||||
vec.emplace_back(std::stoul((std::string)val.substr(5, val.find(" "))));
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::map<int, Description::Media::RTPMap>::iterator Description::Media::beginMaps() {
|
||||
return mRtpMap.begin();
|
||||
}
|
||||
|
||||
std::map<int, Description::Media::RTPMap>::iterator Description::Media::endMaps() {
|
||||
return mRtpMap.end();
|
||||
}
|
||||
|
||||
std::map<int, Description::Media::RTPMap>::iterator
|
||||
Description::Media::removeMap(std::map<int, Description::Media::RTPMap>::iterator iterator) {
|
||||
return mRtpMap.erase(iterator);
|
||||
}
|
||||
|
||||
Description::Media::RTPMap::RTPMap(string_view mline) { setMLine(mline); }
|
||||
|
||||
void Description::Media::RTPMap::removeFB(const string &str) {
|
||||
auto it = rtcpFbs.begin();
|
||||
while (it != rtcpFbs.end()) {
|
||||
if (it->find(str) != std::string::npos) {
|
||||
it = rtcpFbs.erase(it);
|
||||
} else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void Description::Media::RTPMap::addFB(const string &str) { rtcpFbs.emplace_back(str); }
|
||||
|
||||
int Description::Media::RTPMap::parsePT(string_view view) {
|
||||
size_t p = view.find(' ');
|
||||
|
||||
return to_integer<int>(view.substr(0, p));
|
||||
}
|
||||
|
||||
void Description::Media::RTPMap::setMLine(string_view mline) {
|
||||
size_t p = mline.find(' ');
|
||||
|
||||
this->pt = to_integer<int>(mline.substr(0, p));
|
||||
@ -710,56 +874,43 @@ Description::Media::RTPMap::RTPMap(string_view mline) {
|
||||
this->clockRate = to_integer<int>(line);
|
||||
else {
|
||||
this->clockRate = to_integer<int>(line.substr(0, spl));
|
||||
this->encParams = line.substr(spl);
|
||||
this->encParams = line.substr(spl + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Description::Media::RTPMap::removeFB(const string &str) {
|
||||
auto it = rtcpFbs.begin();
|
||||
while (it != rtcpFbs.end()) {
|
||||
if (it->find(str) != std::string::npos) {
|
||||
it = rtcpFbs.erase(it);
|
||||
} else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void Description::Media::RTPMap::addFB(const string &str) { rtcpFbs.emplace_back(str); }
|
||||
|
||||
Description::Audio::Audio(string mid, Direction dir)
|
||||
: Media("audio 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
|
||||
|
||||
void Description::Audio::addOpusCodec(int payloadType) { addAudioCodec(payloadType, "OPUS"); }
|
||||
|
||||
Description::Video::Video(string mid, Direction dir)
|
||||
: Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
|
||||
|
||||
Description::Type Description::stringToType(const string &typeString) {
|
||||
if (typeString == "offer")
|
||||
return Type::Offer;
|
||||
else if (typeString == "answer")
|
||||
return Type::Answer;
|
||||
else
|
||||
return Type::Unspec;
|
||||
using TypeMap_t = std::unordered_map<string, Type>;
|
||||
static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
|
||||
{"offer", Type::Offer},
|
||||
{"answer", Type::Answer},
|
||||
{"pranswer", Type::Pranswer},
|
||||
{"rollback", Type::Rollback}};
|
||||
auto it = TypeMap.find(typeString);
|
||||
return it != TypeMap.end() ? it->second : Type::Unspec;
|
||||
}
|
||||
|
||||
string Description::typeToString(Type type) {
|
||||
switch (type) {
|
||||
case Type::Unspec:
|
||||
return "unspec";
|
||||
case Type::Offer:
|
||||
return "offer";
|
||||
case Type::Answer:
|
||||
return "answer";
|
||||
case Type::Pranswer:
|
||||
return "pranswer";
|
||||
case Type::Rollback:
|
||||
return "rollback";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
string Description::roleToString(Role role) {
|
||||
switch (role) {
|
||||
case Role::Active:
|
||||
return "active";
|
||||
case Role::Passive:
|
||||
return "passive";
|
||||
default:
|
||||
return "actpass";
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@ -768,3 +919,25 @@ string Description::roleToString(Role role) {
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
|
||||
return out << std::string(description);
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, rtc::Description::Type type) {
|
||||
return out << rtc::Description::typeToString(type);
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, rtc::Description::Role role) {
|
||||
using Role = rtc::Description::Role;
|
||||
const char *str;
|
||||
// Used for SDP generation, do not change
|
||||
switch (role) {
|
||||
case Role::Active:
|
||||
str = "active";
|
||||
break;
|
||||
case Role::Passive:
|
||||
str = "passive";
|
||||
break;
|
||||
default:
|
||||
str = "actpass";
|
||||
break;
|
||||
}
|
||||
return out << str;
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int size = message->size();
|
||||
int size = int(message->size());
|
||||
PLOG_VERBOSE << "Send size=" << size;
|
||||
|
||||
// The RTP header has a minimum size of 12 bytes
|
||||
@ -109,16 +109,31 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
|
||||
if (srtp_err_status_t err = srtp_protect_rtcp(mSrtpOut, message->data(), &size)) {
|
||||
if (err == srtp_err_status_replay_fail)
|
||||
throw std::runtime_error("SRTCP packet is a replay");
|
||||
else
|
||||
else if (err == srtp_err_status_no_ctx) {
|
||||
auto ssrc = ((RTCP_SR *)message->data())->senderSSRC();
|
||||
PLOG_INFO << "Adding SSRC to SRTCP: " << ssrc;
|
||||
addSSRC(ssrc);
|
||||
if ((err = srtp_protect_rtcp(mSrtpOut, message->data(), &size)))
|
||||
throw std::runtime_error("SRTCP protect error, status=" +
|
||||
to_string(static_cast<int>(err)));
|
||||
} else {
|
||||
throw std::runtime_error("SRTCP protect error, status=" +
|
||||
to_string(static_cast<int>(err)));
|
||||
}
|
||||
}
|
||||
PLOG_VERBOSE << "Protected SRTCP packet, size=" << size;
|
||||
} else {
|
||||
if (srtp_err_status_t err = srtp_protect(mSrtpOut, message->data(), &size)) {
|
||||
if (err == srtp_err_status_replay_fail)
|
||||
throw std::runtime_error("SRTP packet is a replay");
|
||||
else
|
||||
throw std::runtime_error("Outgoing SRTP packet is a replay");
|
||||
else if (err == srtp_err_status_no_ctx) {
|
||||
auto ssrc = ((RTP *)message->data())->ssrc();
|
||||
PLOG_INFO << "Adding SSRC to RTP: " << ssrc;
|
||||
addSSRC(ssrc);
|
||||
if ((err = srtp_protect_rtcp(mSrtpOut, message->data(), &size)))
|
||||
throw std::runtime_error("SRTCP protect error, status=" +
|
||||
to_string(static_cast<int>(err)));
|
||||
} else
|
||||
throw std::runtime_error("SRTP protect error, status=" +
|
||||
to_string(static_cast<int>(err)));
|
||||
}
|
||||
@ -127,7 +142,6 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
|
||||
|
||||
message->resize(size);
|
||||
return outgoing(message);
|
||||
// return DtlsTransport::send(message);
|
||||
}
|
||||
|
||||
void DtlsSrtpTransport::incoming(message_ptr message) {
|
||||
@ -137,7 +151,7 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
|
||||
return;
|
||||
}
|
||||
|
||||
int size = message->size();
|
||||
int size = int(message->size());
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
@ -174,13 +188,23 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
|
||||
PLOG_WARNING << "Incoming SRTCP packet is a replay";
|
||||
else if (err == srtp_err_status_auth_fail)
|
||||
PLOG_WARNING << "Incoming SRTCP packet failed authentication check";
|
||||
else
|
||||
PLOG_WARNING << "SRTCP unprotect error, status=" << err;
|
||||
else if (err == srtp_err_status_no_ctx) {
|
||||
auto ssrc = ((RTCP_SR *)message->data())->senderSSRC();
|
||||
PLOG_INFO << "Adding SSRC to RTCP: " << ssrc;
|
||||
addSSRC(ssrc);
|
||||
if ((err = srtp_unprotect_rtcp(mSrtpIn, message->data(), &size)))
|
||||
throw std::runtime_error("SRTCP unprotect error, status=" +
|
||||
to_string(static_cast<int>(err)));
|
||||
} else {
|
||||
PLOG_WARNING << "SRTCP unprotect error, status=" << err
|
||||
<< " SSRC=" << ((RTCP_SR *)message->data())->senderSSRC();
|
||||
}
|
||||
return;
|
||||
}
|
||||
PLOG_VERBOSE << "Unprotected SRTCP packet, size=" << size;
|
||||
message->type = Message::Type::Control;
|
||||
message->stream = to_integer<uint8_t>(*(message->begin() + 1)); // Payload Type
|
||||
auto rtp = (RTCP_SR *)message->data();
|
||||
message->stream = rtp->senderSSRC();
|
||||
} else {
|
||||
PLOG_VERBOSE << "Incoming SRTP packet, size=" << size;
|
||||
if (srtp_err_status_t err = srtp_unprotect(mSrtpIn, message->data(), &size)) {
|
||||
@ -188,13 +212,22 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
|
||||
PLOG_WARNING << "Incoming SRTP packet is a replay";
|
||||
else if (err == srtp_err_status_auth_fail)
|
||||
PLOG_WARNING << "Incoming SRTP packet failed authentication check";
|
||||
else
|
||||
PLOG_WARNING << "SRTP unprotect error, status=" << err;
|
||||
else if (err == srtp_err_status_no_ctx) {
|
||||
auto ssrc = ((RTP *)message->data())->ssrc();
|
||||
PLOG_INFO << "Adding SSRC to RTP: " << ssrc;
|
||||
addSSRC(ssrc);
|
||||
if ((err = srtp_unprotect(mSrtpIn, message->data(), &size)))
|
||||
throw std::runtime_error("SRTCP unprotect error, status=" +
|
||||
to_string(static_cast<int>(err)));
|
||||
} else
|
||||
PLOG_WARNING << "SRTP unprotect error, status=" << err
|
||||
<< " SSRC=" << ((RTP *)message->data())->ssrc();
|
||||
return;
|
||||
}
|
||||
PLOG_VERBOSE << "Unprotected SRTP packet, size=" << size;
|
||||
message->type = Message::Type::Binary;
|
||||
message->stream = value2; // Payload Type
|
||||
auto rtp = (RTP *)message->data();
|
||||
message->stream = rtp->ssrc();
|
||||
}
|
||||
|
||||
message->resize(size);
|
||||
@ -209,6 +242,8 @@ void DtlsSrtpTransport::postHandshake() {
|
||||
if (mInitDone)
|
||||
return;
|
||||
|
||||
static_assert(SRTP_AES_ICM_128_KEY_LEN_WSALT == SRTP_AES_128_KEY_LEN + SRTP_SALT_LEN);
|
||||
|
||||
const size_t materialLen = SRTP_AES_ICM_128_KEY_LEN_WSALT * 2;
|
||||
unsigned char material[materialLen];
|
||||
const unsigned char *clientKey, *clientSalt, *serverKey, *serverSalt;
|
||||
@ -257,21 +292,41 @@ void DtlsSrtpTransport::postHandshake() {
|
||||
serverSalt = clientSalt + SRTP_SALT_LEN;
|
||||
#endif
|
||||
|
||||
unsigned char clientSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
|
||||
std::memcpy(clientSessionKey, clientKey, SRTP_AES_128_KEY_LEN);
|
||||
std::memcpy(clientSessionKey + SRTP_AES_128_KEY_LEN, clientSalt, SRTP_SALT_LEN);
|
||||
std::memcpy(mClientSessionKey, clientKey, SRTP_AES_128_KEY_LEN);
|
||||
std::memcpy(mClientSessionKey + SRTP_AES_128_KEY_LEN, clientSalt, SRTP_SALT_LEN);
|
||||
|
||||
unsigned char serverSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
|
||||
std::memcpy(serverSessionKey, serverKey, SRTP_AES_128_KEY_LEN);
|
||||
std::memcpy(serverSessionKey + SRTP_AES_128_KEY_LEN, serverSalt, SRTP_SALT_LEN);
|
||||
std::memcpy(mServerSessionKey, serverKey, SRTP_AES_128_KEY_LEN);
|
||||
std::memcpy(mServerSessionKey + SRTP_AES_128_KEY_LEN, serverSalt, SRTP_SALT_LEN);
|
||||
|
||||
// Add SSRC=1 as an inbound because that is what Chrome does.
|
||||
srtp_policy_t inbound = {};
|
||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
|
||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
|
||||
inbound.ssrc.type = ssrc_specific;
|
||||
inbound.ssrc.value = 1;
|
||||
inbound.key = mIsClient ? mServerSessionKey : mClientSessionKey;
|
||||
inbound.next = nullptr;
|
||||
|
||||
if (srtp_err_status_t err = srtp_add_stream(mSrtpIn, &inbound)) {
|
||||
throw std::runtime_error("SRTP add inbound stream failed, status=" +
|
||||
to_string(static_cast<int>(err)));
|
||||
}
|
||||
|
||||
mInitDone = true;
|
||||
}
|
||||
|
||||
void DtlsSrtpTransport::addSSRC(uint32_t ssrc) {
|
||||
if (!mInitDone)
|
||||
throw std::logic_error("Attempted to add SSRC before SRTP keying material is derived");
|
||||
|
||||
srtp_policy_t inbound = {};
|
||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
|
||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
|
||||
inbound.ssrc.type = ssrc_any_inbound;
|
||||
inbound.ssrc.value = 0;
|
||||
inbound.key = mIsClient ? serverSessionKey : clientSessionKey;
|
||||
inbound.ssrc.type = ssrc_specific;
|
||||
inbound.ssrc.value = ssrc;
|
||||
inbound.key = mIsClient ? mServerSessionKey : mClientSessionKey;
|
||||
inbound.next = nullptr;
|
||||
inbound.allow_repeat_tx = true;
|
||||
|
||||
if (srtp_err_status_t err = srtp_add_stream(mSrtpIn, &inbound))
|
||||
throw std::runtime_error("SRTP add inbound stream failed, status=" +
|
||||
@ -280,16 +335,15 @@ void DtlsSrtpTransport::postHandshake() {
|
||||
srtp_policy_t outbound = {};
|
||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp);
|
||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp);
|
||||
outbound.ssrc.type = ssrc_any_outbound;
|
||||
outbound.ssrc.value = 0;
|
||||
outbound.key = mIsClient ? clientSessionKey : serverSessionKey;
|
||||
outbound.ssrc.type = ssrc_specific;
|
||||
outbound.ssrc.value = ssrc;
|
||||
outbound.key = mIsClient ? mClientSessionKey : mServerSessionKey;
|
||||
outbound.next = nullptr;
|
||||
outbound.allow_repeat_tx = true;
|
||||
|
||||
if (srtp_err_status_t err = srtp_add_stream(mSrtpOut, &outbound))
|
||||
throw std::runtime_error("SRTP add outbound stream failed, status=" +
|
||||
to_string(static_cast<int>(err)));
|
||||
|
||||
mInitDone = true;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -24,7 +24,13 @@
|
||||
|
||||
#if RTC_ENABLE_MEDIA
|
||||
|
||||
#if RTC_SYSTEM_SRTP
|
||||
#include <srtp2/srtp.h>
|
||||
#else
|
||||
#include "srtp.h"
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
@ -39,6 +45,7 @@ public:
|
||||
~DtlsSrtpTransport();
|
||||
|
||||
bool sendMedia(message_ptr message);
|
||||
void addSSRC(uint32_t ssrc);
|
||||
|
||||
private:
|
||||
void incoming(message_ptr message) override;
|
||||
@ -47,7 +54,10 @@ private:
|
||||
message_callback mSrtpRecvCallback;
|
||||
|
||||
srtp_t mSrtpIn, mSrtpOut;
|
||||
bool mInitDone = false;
|
||||
|
||||
std::atomic<bool> mInitDone = false;
|
||||
unsigned char mClientSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
|
||||
unsigned char mServerSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -177,8 +177,8 @@ void DtlsTransport::runRecvLoop() {
|
||||
// Receive loop
|
||||
try {
|
||||
PLOG_INFO << "DTLS handshake finished";
|
||||
changeState(State::Connected);
|
||||
postHandshake();
|
||||
changeState(State::Connected);
|
||||
|
||||
const size_t bufferSize = maxMtu;
|
||||
char buffer[bufferSize];
|
||||
@ -435,7 +435,7 @@ void DtlsTransport::runRecvLoop() {
|
||||
|
||||
const size_t bufferSize = maxMtu;
|
||||
byte buffer[bufferSize];
|
||||
while (true) {
|
||||
while (mIncomingQueue.running()) {
|
||||
// Process pending messages
|
||||
while (auto next = mIncomingQueue.tryPop()) {
|
||||
message_ptr message = std::move(*next);
|
||||
@ -453,8 +453,8 @@ void DtlsTransport::runRecvLoop() {
|
||||
SSL_set_mtu(mSsl, maxMtu + 1);
|
||||
|
||||
PLOG_INFO << "DTLS handshake finished";
|
||||
changeState(State::Connected);
|
||||
postHandshake();
|
||||
changeState(State::Connected);
|
||||
}
|
||||
} else {
|
||||
ret = SSL_read(mSsl, buffer, bufferSize);
|
||||
@ -492,8 +492,7 @@ void DtlsTransport::runRecvLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!mIncomingQueue.wait(duration))
|
||||
break; // queue is stopped
|
||||
mIncomingQueue.wait(duration);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "DTLS recv: " << e.what();
|
||||
@ -576,4 +575,3 @@ long DtlsTransport::BioMethodCtrl(BIO * /*bio*/, int cmd, long /*num*/, void * /
|
||||
#endif
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
|
@ -92,4 +92,3 @@ protected:
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -46,11 +46,12 @@ using std::chrono::system_clock;
|
||||
|
||||
namespace rtc {
|
||||
|
||||
IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
candidate_callback candidateCallback, state_callback stateChangeCallback,
|
||||
IceTransport::IceTransport(const Configuration &config, candidate_callback candidateCallback,
|
||||
state_callback stateChangeCallback,
|
||||
gathering_state_callback gatheringStateChangeCallback)
|
||||
: Transport(nullptr, std::move(stateChangeCallback)), mRole(role), mMid("0"),
|
||||
mGatheringState(GatheringState::New), mCandidateCallback(std::move(candidateCallback)),
|
||||
: Transport(nullptr, std::move(stateChangeCallback)), mRole(Description::Role::ActPass),
|
||||
mMid("0"), mGatheringState(GatheringState::New),
|
||||
mCandidateCallback(std::move(candidateCallback)),
|
||||
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
|
||||
mAgent(nullptr, nullptr) {
|
||||
|
||||
@ -60,7 +61,8 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
}
|
||||
|
||||
juice_log_level_t level;
|
||||
switch (plog::get()->getMaxSeverity()) {
|
||||
auto logger = plog::get();
|
||||
switch (logger ? logger->getMaxSeverity() : plog::none) {
|
||||
case plog::none:
|
||||
level = JUICE_LOG_LEVEL_NONE;
|
||||
break;
|
||||
@ -129,9 +131,7 @@ IceTransport::~IceTransport() {
|
||||
mAgent.reset();
|
||||
}
|
||||
|
||||
bool IceTransport::stop() {
|
||||
return Transport::stop();
|
||||
}
|
||||
bool IceTransport::stop() { return Transport::stop(); }
|
||||
|
||||
Description::Role IceTransport::role() const { return mRole; }
|
||||
|
||||
@ -140,12 +140,20 @@ Description IceTransport::getLocalDescription(Description::Type type) const {
|
||||
if (juice_get_local_description(mAgent.get(), sdp, JUICE_MAX_SDP_STRING_LEN) < 0)
|
||||
throw std::runtime_error("Failed to generate local SDP");
|
||||
|
||||
return Description(string(sdp), type, mRole);
|
||||
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
|
||||
// setup:actpass.
|
||||
// See https://tools.ietf.org/html/rfc5763#section-5
|
||||
return Description(string(sdp), type,
|
||||
type == Description::Type::Offer ? Description::Role::ActPass : mRole);
|
||||
}
|
||||
|
||||
void IceTransport::setRemoteDescription(const Description &description) {
|
||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||
: Description::Role::Active;
|
||||
if (mRole == Description::Role::ActPass)
|
||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||
: Description::Role::Active;
|
||||
if (mRole == description.role())
|
||||
throw std::logic_error("Incompatible roles with remote description");
|
||||
|
||||
mMid = description.bundleMid();
|
||||
if (juice_set_remote_description(mAgent.get(),
|
||||
description.generateApplicationSdp("\r\n").c_str()) < 0)
|
||||
@ -186,6 +194,24 @@ std::optional<string> IceTransport::getRemoteAddress() const {
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote) {
|
||||
char sdpLocal[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
|
||||
char sdpRemote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
|
||||
if (juice_get_selected_candidates(mAgent.get(), sdpLocal, JUICE_MAX_CANDIDATE_SDP_STRING_LEN,
|
||||
sdpRemote, JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0) {
|
||||
if (local) {
|
||||
*local = Candidate(sdpLocal, mMid);
|
||||
local->resolve(Candidate::ResolveMode::Simple);
|
||||
}
|
||||
if (remote) {
|
||||
*remote = Candidate(sdpRemote, mMid);
|
||||
remote->resolve(Candidate::ResolveMode::Simple);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IceTransport::send(message_ptr message) {
|
||||
auto s = state();
|
||||
if (!message || (s != State::Connected && s != State::Completed))
|
||||
@ -297,11 +323,12 @@ void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
|
||||
|
||||
namespace rtc {
|
||||
|
||||
IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
candidate_callback candidateCallback, state_callback stateChangeCallback,
|
||||
IceTransport::IceTransport(const Configuration &config, candidate_callback candidateCallback,
|
||||
state_callback stateChangeCallback,
|
||||
gathering_state_callback gatheringStateChangeCallback)
|
||||
: Transport(nullptr, std::move(stateChangeCallback)), mRole(role), mMid("0"),
|
||||
mGatheringState(GatheringState::New), mCandidateCallback(std::move(candidateCallback)),
|
||||
: Transport(nullptr, std::move(stateChangeCallback)), mRole(Description::Role::ActPass),
|
||||
mMid("0"), mGatheringState(GatheringState::New),
|
||||
mCandidateCallback(std::move(candidateCallback)),
|
||||
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
|
||||
mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr) {
|
||||
|
||||
@ -507,12 +534,21 @@ Description IceTransport::getLocalDescription(Description::Type type) const {
|
||||
|
||||
std::unique_ptr<gchar[], void (*)(void *)> sdp(nice_agent_generate_local_sdp(mNiceAgent.get()),
|
||||
g_free);
|
||||
return Description(string(sdp.get()), type, mRole);
|
||||
|
||||
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
|
||||
// setup:actpass.
|
||||
// See https://tools.ietf.org/html/rfc5763#section-5
|
||||
return Description(string(sdp.get()), type,
|
||||
type == Description::Type::Offer ? Description::Role::ActPass : mRole);
|
||||
}
|
||||
|
||||
void IceTransport::setRemoteDescription(const Description &description) {
|
||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||
: Description::Role::Active;
|
||||
if (mRole == Description::Role::ActPass)
|
||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||
: Description::Role::Active;
|
||||
if (mRole == description.role())
|
||||
throw std::logic_error("Incompatible roles with remote description");
|
||||
|
||||
mMid = description.bundleMid();
|
||||
mTrickleTimeout = !description.ended() ? 30s : 0s;
|
||||
|
||||
@ -715,58 +751,28 @@ void IceTransport::LogCallback(const gchar * /*logDomain*/, GLogLevelFlags logLe
|
||||
PLOG(severity) << "nice: " << message;
|
||||
}
|
||||
|
||||
bool IceTransport::getSelectedCandidatePair(CandidateInfo *localInfo, CandidateInfo *remoteInfo) {
|
||||
NiceCandidate *local, *remote;
|
||||
gboolean result = nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote);
|
||||
|
||||
if (!result)
|
||||
bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote) {
|
||||
NiceCandidate *niceLocal, *niceRemote;
|
||||
if (!nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &niceLocal, &niceRemote))
|
||||
return false;
|
||||
|
||||
char ipaddr[INET6_ADDRSTRLEN];
|
||||
nice_address_to_string(&local->addr, ipaddr);
|
||||
localInfo->address = std::string(ipaddr);
|
||||
localInfo->port = nice_address_get_port(&local->addr);
|
||||
localInfo->type = IceTransport::NiceTypeToCandidateType(local->type);
|
||||
localInfo->transportType =
|
||||
IceTransport::NiceTransportTypeToCandidateTransportType(local->transport);
|
||||
gchar *sdpLocal = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceLocal);
|
||||
if (local)
|
||||
*local = Candidate(sdpLocal, mMid);
|
||||
g_free(sdpLocal);
|
||||
|
||||
nice_address_to_string(&remote->addr, ipaddr);
|
||||
remoteInfo->address = std::string(ipaddr);
|
||||
remoteInfo->port = nice_address_get_port(&remote->addr);
|
||||
remoteInfo->type = IceTransport::NiceTypeToCandidateType(remote->type);
|
||||
remoteInfo->transportType =
|
||||
IceTransport::NiceTransportTypeToCandidateTransportType(remote->transport);
|
||||
gchar *sdpRemote = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceRemote);
|
||||
if (remote)
|
||||
*remote = Candidate(sdpRemote, mMid);
|
||||
g_free(sdpRemote);
|
||||
|
||||
if (local)
|
||||
local->resolve(Candidate::ResolveMode::Simple);
|
||||
if (remote)
|
||||
remote->resolve(Candidate::ResolveMode::Simple);
|
||||
return true;
|
||||
}
|
||||
|
||||
CandidateType IceTransport::NiceTypeToCandidateType(NiceCandidateType type) {
|
||||
switch (type) {
|
||||
case NiceCandidateType::NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
|
||||
return CandidateType::PeerReflexive;
|
||||
case NiceCandidateType::NICE_CANDIDATE_TYPE_RELAYED:
|
||||
return CandidateType::Relayed;
|
||||
case NiceCandidateType::NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
|
||||
return CandidateType::ServerReflexive;
|
||||
default:
|
||||
return CandidateType::Host;
|
||||
}
|
||||
}
|
||||
|
||||
CandidateTransportType
|
||||
IceTransport::NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type) {
|
||||
switch (type) {
|
||||
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE:
|
||||
return CandidateTransportType::TcpActive;
|
||||
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE:
|
||||
return CandidateTransportType::TcpPassive;
|
||||
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_SO:
|
||||
return CandidateTransportType::TcpSo;
|
||||
default:
|
||||
return CandidateTransportType::Udp;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -20,8 +20,8 @@
|
||||
#define RTC_ICE_TRANSPORT_H
|
||||
|
||||
#include "candidate.hpp"
|
||||
#include "description.hpp"
|
||||
#include "configuration.hpp"
|
||||
#include "description.hpp"
|
||||
#include "include.hpp"
|
||||
#include "peerconnection.hpp"
|
||||
#include "transport.hpp"
|
||||
@ -45,8 +45,8 @@ public:
|
||||
using candidate_callback = std::function<void(const Candidate &candidate)>;
|
||||
using gathering_state_callback = std::function<void(GatheringState state)>;
|
||||
|
||||
IceTransport(const Configuration &config, Description::Role role,
|
||||
candidate_callback candidateCallback, state_callback stateChangeCallback,
|
||||
IceTransport(const Configuration &config, candidate_callback candidateCallback,
|
||||
state_callback stateChangeCallback,
|
||||
gathering_state_callback gatheringStateChangeCallback);
|
||||
~IceTransport();
|
||||
|
||||
@ -63,9 +63,7 @@ public:
|
||||
bool stop() override;
|
||||
bool send(message_ptr message) override; // false if dropped
|
||||
|
||||
#if USE_NICE
|
||||
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
|
||||
#endif
|
||||
bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
|
||||
|
||||
private:
|
||||
bool outgoing(message_ptr message) override;
|
||||
@ -113,9 +111,6 @@ private:
|
||||
static gboolean TimeoutCallback(gpointer userData);
|
||||
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
|
||||
gpointer user_data);
|
||||
static CandidateType NiceTypeToCandidateType(NiceCandidateType type);
|
||||
static CandidateTransportType
|
||||
NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -142,4 +142,3 @@ Init::~Init() {
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "plog/Log.h"
|
||||
#include "plog/Logger.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
|
||||
@ -31,6 +33,8 @@ void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level))
|
||||
void InitLogger(plog::Severity severity, plog::IAppender *appender) {
|
||||
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
|
||||
static plog::Logger<0> *logger = nullptr;
|
||||
static std::mutex mutex;
|
||||
std::lock_guard lock(mutex);
|
||||
if (!logger) {
|
||||
logger = &plog::init(severity, appender ? appender : &consoleAppender);
|
||||
PLOG_DEBUG << "Logger initialized";
|
||||
@ -40,5 +44,4 @@ void InitLogger(plog::Severity severity, plog::IAppender *appender) {
|
||||
logger->addAppender(appender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,8 @@
|
||||
|
||||
namespace rtc {
|
||||
|
||||
Processor::Processor(size_t limit) : mTasks(limit) {}
|
||||
|
||||
Processor::~Processor() { join(); }
|
||||
|
||||
void Processor::join() {
|
||||
@ -29,16 +31,13 @@ void Processor::join() {
|
||||
|
||||
void Processor::schedule() {
|
||||
std::unique_lock lock(mMutex);
|
||||
if (mTasks.empty()) {
|
||||
if (auto next = mTasks.tryPop()) {
|
||||
ThreadPool::Instance().enqueue(std::move(*next));
|
||||
} else {
|
||||
// No more tasks
|
||||
mPending = false;
|
||||
mCondition.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadPool::Instance().enqueue(std::move(mTasks.front()));
|
||||
mTasks.pop();
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "include.hpp"
|
||||
#include "init.hpp"
|
||||
#include "threadpool.hpp"
|
||||
#include "queue.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <future>
|
||||
@ -34,7 +35,7 @@ namespace rtc {
|
||||
// Processed tasks in order by delegating them to the thread pool
|
||||
class Processor final {
|
||||
public:
|
||||
Processor() = default;
|
||||
Processor(size_t limit = 0);
|
||||
~Processor();
|
||||
|
||||
Processor(const Processor &) = delete;
|
||||
@ -44,8 +45,7 @@ public:
|
||||
|
||||
void join();
|
||||
|
||||
template <class F, class... Args>
|
||||
auto enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...>;
|
||||
template <class F, class... Args> void enqueue(F &&f, Args &&... args);
|
||||
|
||||
protected:
|
||||
void schedule();
|
||||
@ -53,38 +53,27 @@ protected:
|
||||
// Keep an init token
|
||||
const init_token mInitToken = Init::Token();
|
||||
|
||||
std::queue<std::function<void()>> mTasks;
|
||||
Queue<std::function<void()>> mTasks;
|
||||
bool mPending = false; // true iff a task is pending in the thread pool
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
std::condition_variable mCondition;
|
||||
};
|
||||
|
||||
template <class F, class... Args>
|
||||
auto Processor::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
|
||||
template <class F, class... Args> void Processor::enqueue(F &&f, Args &&... args) {
|
||||
std::unique_lock lock(mMutex);
|
||||
using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
|
||||
auto task = std::make_shared<std::packaged_task<R()>>(
|
||||
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
|
||||
std::future<R> result = task->get_future();
|
||||
|
||||
auto bundle = [this, task = std::move(task)]() {
|
||||
try {
|
||||
(*task)();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << "Unhandled exception in task: " << e.what();
|
||||
}
|
||||
schedule(); // chain the next task
|
||||
auto bound = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
auto task = [this, bound = std::move(bound)]() mutable {
|
||||
scope_guard guard(std::bind(&Processor::schedule, this)); // chain the next task
|
||||
return bound();
|
||||
};
|
||||
|
||||
if (!mPending) {
|
||||
ThreadPool::Instance().enqueue(std::move(bundle));
|
||||
ThreadPool::Instance().enqueue(std::move(task));
|
||||
mPending = true;
|
||||
} else {
|
||||
mTasks.emplace(std::move(bundle));
|
||||
mTasks.push(std::move(task));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
359
src/rtcp.cpp
359
src/rtcp.cpp
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Staz M
|
||||
* Copyright (c) 2020 Staz Modrzynski
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
#include "rtcp.hpp"
|
||||
|
||||
#include "track.hpp"
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
@ -28,310 +29,11 @@
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#ifndef htonll
|
||||
#define htonll(x) \
|
||||
((uint64_t)htonl(((uint64_t)(x)&0xFFFFFFFF) << 32) | (uint64_t)htonl((uint64_t)(x) >> 32))
|
||||
#endif
|
||||
#ifndef ntohll
|
||||
#define ntohll(x) htonll(x)
|
||||
#endif
|
||||
|
||||
namespace rtc {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
rtc::message_ptr RtcpReceivingSession::outgoing(rtc::message_ptr ptr) { return ptr; }
|
||||
|
||||
struct RTP {
|
||||
private:
|
||||
uint8_t _first;
|
||||
uint8_t _payloadType;
|
||||
uint16_t _seqNumber;
|
||||
uint32_t _timestamp;
|
||||
|
||||
public:
|
||||
SSRC ssrc;
|
||||
SSRC csrc[16];
|
||||
|
||||
inline uint8_t version() const { return _first >> 6; }
|
||||
inline bool padding() const { return (_first >> 5) & 0x01; }
|
||||
inline uint8_t csrcCount() const { return _first & 0x0F; }
|
||||
inline uint8_t payloadType() const { return _payloadType; }
|
||||
inline uint16_t seqNumber() const { return ntohs(_seqNumber); }
|
||||
inline uint32_t timestamp() const { return ntohl(_timestamp); }
|
||||
};
|
||||
|
||||
struct RTCP_ReportBlock {
|
||||
SSRC ssrc;
|
||||
|
||||
private:
|
||||
uint32_t _fractionLostAndPacketsLost; // fraction lost is 8-bit, packets lost is 24-bit
|
||||
uint16_t _seqNoCycles;
|
||||
uint16_t _highestSeqNo;
|
||||
uint32_t _jitter;
|
||||
uint32_t _lastReport;
|
||||
uint32_t _delaySinceLastReport;
|
||||
|
||||
public:
|
||||
inline void preparePacket(SSRC ssrc_, [[maybe_unused]] unsigned int packetsLost,
|
||||
[[maybe_unused]] unsigned int totalPackets, uint16_t highestSeqNo,
|
||||
uint16_t seqNoCycles, uint32_t jitter, uint64_t lastSR_NTP,
|
||||
uint64_t lastSR_DELAY) {
|
||||
setSeqNo(highestSeqNo, seqNoCycles);
|
||||
setJitter(jitter);
|
||||
setSSRC(ssrc_);
|
||||
|
||||
// Middle 32 bits of NTP Timestamp
|
||||
// _lastReport = lastSR_NTP >> 16u;
|
||||
setNTPOfSR(uint32_t(lastSR_NTP));
|
||||
setDelaySinceSR(uint32_t(lastSR_DELAY));
|
||||
|
||||
// The delay, expressed in units of 1/65536 seconds
|
||||
// _delaySinceLastReport = lastSR_DELAY;
|
||||
}
|
||||
|
||||
inline void setSSRC(SSRC ssrc_) { ssrc = htonl(ssrc_); }
|
||||
inline SSRC getSSRC() const { return ntohl(ssrc); }
|
||||
|
||||
inline void setPacketsLost([[maybe_unused]] unsigned int packetsLost,
|
||||
[[maybe_unused]] unsigned int totalPackets) {
|
||||
// TODO Implement loss percentages.
|
||||
_fractionLostAndPacketsLost = 0;
|
||||
}
|
||||
inline unsigned int getLossPercentage() const {
|
||||
// TODO Implement loss percentages.
|
||||
return 0;
|
||||
}
|
||||
inline unsigned int getPacketLostCount() const {
|
||||
// TODO Implement total packets lost.
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline uint16_t seqNoCycles() const { return ntohs(_seqNoCycles); }
|
||||
inline uint16_t highestSeqNo() const { return ntohs(_highestSeqNo); }
|
||||
inline uint32_t jitter() const { return ntohl(_jitter); }
|
||||
|
||||
inline void setSeqNo(uint16_t highestSeqNo, uint16_t seqNoCycles) {
|
||||
_highestSeqNo = htons(highestSeqNo);
|
||||
_seqNoCycles = htons(seqNoCycles);
|
||||
}
|
||||
|
||||
inline void setJitter(uint32_t jitter) { _jitter = htonl(jitter); }
|
||||
|
||||
inline void setNTPOfSR(uint32_t ntp) { _lastReport = htonl(ntp >> 16u); }
|
||||
inline uint32_t getNTPOfSR() const { return ntohl(_lastReport) << 16u; }
|
||||
|
||||
inline void setDelaySinceSR(uint32_t sr) {
|
||||
// The delay, expressed in units of 1/65536 seconds
|
||||
_delaySinceLastReport = htonl(sr);
|
||||
}
|
||||
inline uint32_t getDelaySinceSR() const { return ntohl(_delaySinceLastReport); }
|
||||
|
||||
inline void log() const {
|
||||
PLOG_DEBUG << "RTCP report block: "
|
||||
<< "ssrc="
|
||||
<< ntohl(ssrc)
|
||||
// TODO: Implement these reports
|
||||
// << ", fractionLost=" << fractionLost
|
||||
// << ", packetsLost=" << packetsLost
|
||||
<< ", highestSeqNo=" << highestSeqNo() << ", seqNoCycles=" << seqNoCycles()
|
||||
<< ", jitter=" << jitter() << ", lastSR=" << getNTPOfSR()
|
||||
<< ", lastSRDelay=" << getDelaySinceSR();
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_HEADER {
|
||||
private:
|
||||
uint8_t _first;
|
||||
uint8_t _payloadType;
|
||||
uint16_t _length;
|
||||
|
||||
public:
|
||||
inline uint8_t version() const { return _first >> 6; }
|
||||
inline bool padding() const { return (_first >> 5) & 0x01; }
|
||||
inline uint8_t reportCount() const { return _first & 0x0F; }
|
||||
inline uint8_t payloadType() const { return _payloadType; }
|
||||
inline uint16_t length() const { return ntohs(_length); }
|
||||
|
||||
inline void setPayloadType(uint8_t type) { _payloadType = type; }
|
||||
inline void setReportCount(uint8_t count) { _first = (_first & 0xF0) | (count & 0x0F); }
|
||||
inline void setLength(uint16_t length) { _length = htons(length); }
|
||||
|
||||
inline void prepareHeader(uint8_t payloadType, uint8_t reportCount, uint16_t length) {
|
||||
_first = 0x02 << 6; // version 2, no padding
|
||||
setReportCount(reportCount);
|
||||
setPayloadType(payloadType);
|
||||
setLength(length);
|
||||
}
|
||||
|
||||
inline void log() const {
|
||||
PLOG_DEBUG << "RTCP header: "
|
||||
<< "version=" << unsigned(version()) << ", padding=" << padding()
|
||||
<< ", reportCount=" << unsigned(reportCount())
|
||||
<< ", payloadType=" << unsigned(payloadType()) << ", length=" << length();
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_SR {
|
||||
RTCP_HEADER header;
|
||||
SSRC senderSsrc;
|
||||
|
||||
private:
|
||||
uint64_t _ntpTimestamp;
|
||||
uint32_t _rtpTimestamp;
|
||||
uint32_t _packetCount;
|
||||
uint32_t _octetCount;
|
||||
|
||||
RTCP_ReportBlock _reportBlocks;
|
||||
|
||||
public:
|
||||
inline void preparePacket(SSRC senderSsrc_, uint8_t reportCount) {
|
||||
unsigned int length =
|
||||
((sizeof(header) + 24 + reportCount * sizeof(RTCP_ReportBlock)) / 4) - 1;
|
||||
header.prepareHeader(200, reportCount, uint16_t(length));
|
||||
senderSsrc = htonl(senderSsrc_);
|
||||
}
|
||||
|
||||
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
|
||||
inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
|
||||
|
||||
[[nodiscard]] inline size_t getSize() const {
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
return sizeof(uint32_t) * (1 + size_t(header.length()));
|
||||
}
|
||||
|
||||
inline uint32_t ntpTimestamp() const { return ntohll(_ntpTimestamp); }
|
||||
inline uint32_t rtpTimestamp() const { return ntohl(_rtpTimestamp); }
|
||||
inline uint32_t packetCount() const { return ntohl(_packetCount); }
|
||||
inline uint32_t octetCount() const { return ntohl(_octetCount); }
|
||||
|
||||
inline void setNtpTimestamp(uint32_t ts) { _ntpTimestamp = htonll(ts); }
|
||||
inline void setRtpTimestamp(uint32_t ts) { _rtpTimestamp = htonl(ts); }
|
||||
|
||||
inline void log() const {
|
||||
header.log();
|
||||
PLOG_DEBUG << "RTCP SR: "
|
||||
<< " SSRC=" << ntohl(senderSsrc) << ", NTP_TS=" << ntpTimestamp()
|
||||
<< ", RTP_TS=" << rtpTimestamp() << ", packetCount=" << packetCount()
|
||||
<< ", octetCount=" << octetCount();
|
||||
|
||||
for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
|
||||
getReportBlock(i)->log();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_RR {
|
||||
RTCP_HEADER header;
|
||||
SSRC senderSsrc;
|
||||
|
||||
private:
|
||||
RTCP_ReportBlock _reportBlocks;
|
||||
|
||||
public:
|
||||
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
|
||||
inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
|
||||
|
||||
inline SSRC getSenderSSRC() const { return ntohl(senderSsrc); }
|
||||
inline void setSenderSSRC(SSRC ssrc) { senderSsrc = htonl(ssrc); }
|
||||
|
||||
[[nodiscard]] inline size_t getSize() const {
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
return sizeof(uint32_t) * (1 + size_t(header.length()));
|
||||
}
|
||||
|
||||
inline void preparePacket(SSRC ssrc, uint8_t reportCount) {
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
size_t length = (sizeWithReportBlocks(reportCount) / 4) - 1;
|
||||
header.prepareHeader(201, reportCount, uint16_t(length));
|
||||
senderSsrc = htonl(ssrc);
|
||||
}
|
||||
|
||||
inline static size_t sizeWithReportBlocks(uint8_t reportCount) {
|
||||
return sizeof(header) + 4 + size_t(reportCount) * sizeof(RTCP_ReportBlock);
|
||||
}
|
||||
|
||||
inline void log() const {
|
||||
header.log();
|
||||
PLOG_DEBUG << "RTCP RR: "
|
||||
<< " SSRC=" << ntohl(senderSsrc);
|
||||
|
||||
for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
|
||||
getReportBlock(i)->log();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RTCP_REMB {
|
||||
RTCP_HEADER header;
|
||||
SSRC senderSsrc;
|
||||
SSRC mediaSourceSSRC;
|
||||
|
||||
// Unique identifier
|
||||
const char id[4] = {'R', 'E', 'M', 'B'};
|
||||
|
||||
// Num SSRC, Br Exp, Br Mantissa (bit mask)
|
||||
uint32_t bitrate;
|
||||
|
||||
SSRC ssrc[1];
|
||||
|
||||
[[nodiscard]] inline size_t getSize() const {
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
return sizeof(uint32_t) * (1 + size_t(header.length()));
|
||||
}
|
||||
|
||||
inline void preparePacket(SSRC senderSsrc_, unsigned int numSSRC, unsigned int br) {
|
||||
// Report Count becomes the format here.
|
||||
header.prepareHeader(206, 15, 0);
|
||||
|
||||
// Always zero.
|
||||
mediaSourceSSRC = 0;
|
||||
|
||||
senderSsrc = htonl(senderSsrc_);
|
||||
setBitrate(numSSRC, br);
|
||||
}
|
||||
|
||||
inline void setBitrate(unsigned int numSSRC, unsigned int br) {
|
||||
unsigned int exp = 0;
|
||||
while (br > pow(2, 18) - 1) {
|
||||
exp++;
|
||||
br /= 2;
|
||||
}
|
||||
|
||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||
header.setLength(uint16_t(((sizeof(header) + 4 * 2 + 4 + 4) / 4) - 1 + numSSRC));
|
||||
|
||||
bitrate = htonl((numSSRC << (32u - 8u)) | (exp << (32u - 8u - 6u)) | br);
|
||||
}
|
||||
|
||||
// TODO Make this work
|
||||
// uint64_t getBitrate() const{
|
||||
// uint32_t ntohed = ntohl(bitrate);
|
||||
// uint64_t bitrate = ntohed & (unsigned int)(pow(2, 18)-1);
|
||||
// unsigned int exp = ntohed & ((unsigned int)( (pow(2, 6)-1)) << (32u-8u-6u));
|
||||
// return bitrate * pow(2,exp);
|
||||
// }
|
||||
//
|
||||
// uint8_t getNumSSRCS() const {
|
||||
// return ntohl(bitrate) & (((unsigned int) pow(2,8)-1) << (32u-8u));
|
||||
// }
|
||||
|
||||
inline void setSSRC(uint8_t iterator, SSRC ssrc_) { ssrc[iterator] = htonl(ssrc_); }
|
||||
|
||||
inline void log() const {
|
||||
header.log();
|
||||
PLOG_DEBUG << "RTCP REMB: "
|
||||
<< " SSRC=" << ntohl(senderSsrc);
|
||||
}
|
||||
|
||||
static unsigned int sizeWithSSRCs(int numSSRC) {
|
||||
return (sizeof(header) + 4 * 2 + 4 + 4) + sizeof(SSRC) * numSSRC;
|
||||
}
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
void RtcpSession::onOutgoing(std::function<void(rtc::message_ptr)> cb) { mTxCallback = cb; }
|
||||
|
||||
std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
|
||||
rtc::message_ptr RtcpReceivingSession::incoming(rtc::message_ptr ptr) {
|
||||
if (ptr->type == rtc::Message::Type::Binary) {
|
||||
auto rtp = reinterpret_cast<const RTP *>(ptr->data());
|
||||
|
||||
@ -339,12 +41,12 @@ std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
|
||||
if (rtp->version() != 2) {
|
||||
PLOG_WARNING << "RTP packet is not version 2";
|
||||
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
if (rtp->payloadType() == 201 || rtp->payloadType() == 200) {
|
||||
PLOG_WARNING << "RTP packet has a payload type indicating RR/SR";
|
||||
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO Implement the padding bit
|
||||
@ -352,13 +54,7 @@ std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
|
||||
PLOG_WARNING << "Padding processing not implemented";
|
||||
}
|
||||
|
||||
mSsrc = ntohl(rtp->ssrc);
|
||||
|
||||
uint32_t seqNo = rtp->seqNumber();
|
||||
// uint32_t rtpTS = rtp->getTS();
|
||||
|
||||
if (mGreatestSeqNo < seqNo)
|
||||
mGreatestSeqNo = seqNo;
|
||||
mSsrc = rtp->ssrc();
|
||||
|
||||
return ptr;
|
||||
}
|
||||
@ -367,11 +63,11 @@ std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
|
||||
auto rr = reinterpret_cast<const RTCP_RR *>(ptr->data());
|
||||
if (rr->header.payloadType() == 201) {
|
||||
// RR
|
||||
mSsrc = rr->getSenderSSRC();
|
||||
mSsrc = rr->senderSSRC();
|
||||
rr->log();
|
||||
} else if (rr->header.payloadType() == 200) {
|
||||
// SR
|
||||
mSsrc = rr->getSenderSSRC();
|
||||
mSsrc = rr->senderSSRC();
|
||||
auto sr = reinterpret_cast<const RTCP_SR *>(ptr->data());
|
||||
mSyncRTPTS = sr->rtpTimestamp();
|
||||
mSyncNTPTS = sr->ntpTimestamp();
|
||||
@ -382,28 +78,27 @@ std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
|
||||
if (mRequestedBitrate > 0)
|
||||
pushREMB(mRequestedBitrate);
|
||||
}
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RtcpSession::requestBitrate(unsigned int newBitrate) {
|
||||
void RtcpReceivingSession::requestBitrate(unsigned int newBitrate) {
|
||||
mRequestedBitrate = newBitrate;
|
||||
|
||||
PLOG_DEBUG << "[GOOG-REMB] Requesting bitrate: " << newBitrate << std::endl;
|
||||
pushREMB(newBitrate);
|
||||
}
|
||||
|
||||
void RtcpSession::pushREMB(unsigned int bitrate) {
|
||||
void RtcpReceivingSession::pushREMB(unsigned int bitrate) {
|
||||
rtc::message_ptr msg =
|
||||
rtc::make_message(RTCP_REMB::sizeWithSSRCs(1), rtc::Message::Type::Control);
|
||||
auto remb = reinterpret_cast<RTCP_REMB *>(msg->data());
|
||||
remb->preparePacket(mSsrc, 1, bitrate);
|
||||
remb->setSSRC(0, mSsrc);
|
||||
remb->log();
|
||||
remb->setSsrc(0, mSsrc);
|
||||
|
||||
tx(msg);
|
||||
send(msg);
|
||||
}
|
||||
|
||||
void RtcpSession::pushRR(unsigned int lastSR_delay) {
|
||||
void RtcpReceivingSession::pushRR(unsigned int lastSR_delay) {
|
||||
auto msg = rtc::make_message(RTCP_RR::sizeWithReportBlocks(1), rtc::Message::Type::Control);
|
||||
auto rr = reinterpret_cast<RTCP_RR *>(msg->data());
|
||||
rr->preparePacket(mSsrc, 1);
|
||||
@ -411,16 +106,32 @@ void RtcpSession::pushRR(unsigned int lastSR_delay) {
|
||||
lastSR_delay);
|
||||
rr->log();
|
||||
|
||||
tx(msg);
|
||||
send(msg);
|
||||
}
|
||||
|
||||
void RtcpSession::tx(message_ptr msg) {
|
||||
bool RtcpReceivingSession::send(message_ptr msg) {
|
||||
try {
|
||||
mTxCallback(msg);
|
||||
outgoingCallback(std::move(msg));
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
LOG_DEBUG << "RTCP tx failed: " << e.what();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
bool RtcpReceivingSession::requestKeyframe() {
|
||||
pushPLI();
|
||||
return true; // TODO Make this false when it is impossible (i.e. Opus).
|
||||
}
|
||||
|
||||
void RtcpReceivingSession::pushPLI() {
|
||||
auto msg = rtc::make_message(rtc::RTCP_PLI::size(), rtc::Message::Type::Control);
|
||||
auto *pli = (rtc::RTCP_PLI *)msg->data();
|
||||
pli->preparePacket(mSsrc);
|
||||
send(msg);
|
||||
}
|
||||
|
||||
void RtcpHandler::onOutgoing(const std::function<void(rtc::message_ptr)> &cb) {
|
||||
this->outgoingCallback = synchronized_callback<rtc::message_ptr>(cb);
|
||||
}
|
||||
} // namespace rtc
|
||||
|
@ -100,11 +100,12 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
Instances.insert(this);
|
||||
}
|
||||
|
||||
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
|
||||
&SctpTransport::SendCallback, 0, this);
|
||||
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, nullptr, nullptr, 0, nullptr);
|
||||
if (!mSock)
|
||||
throw std::runtime_error("Could not create SCTP socket, errno=" + std::to_string(errno));
|
||||
|
||||
usrsctp_set_upcall(mSock, &SctpTransport::UpcallCallback, this);
|
||||
|
||||
if (usrsctp_set_non_blocking(mSock, 1))
|
||||
throw std::runtime_error("Unable to set non-blocking mode, errno=" + std::to_string(errno));
|
||||
|
||||
@ -122,6 +123,10 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(av)))
|
||||
throw std::runtime_error("Could not set socket option SCTP_ENABLE_STREAM_RESET, errno=" +
|
||||
std::to_string(errno));
|
||||
int on = 1;
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RECVRCVINFO, &on, sizeof(on)))
|
||||
throw std::runtime_error("Could set socket option SCTP_RECVRCVINFO, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
struct sctp_event se = {};
|
||||
se.se_assoc_id = SCTP_ALL_ASSOC;
|
||||
@ -148,7 +153,7 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
|
||||
struct sctp_paddrparams spp = {};
|
||||
#if USE_PMTUD
|
||||
// Enabled SCTP path MTU discovery
|
||||
// Enable SCTP path MTU discovery
|
||||
spp.spp_flags = SPP_PMTUD_ENABLE;
|
||||
#else
|
||||
// Fall back to a safe MTU value.
|
||||
@ -225,6 +230,7 @@ bool SctpTransport::stop() {
|
||||
|
||||
void SctpTransport::close() {
|
||||
if (mSock) {
|
||||
mProcessor.join();
|
||||
usrsctp_close(mSock);
|
||||
mSock = nullptr;
|
||||
}
|
||||
@ -232,7 +238,7 @@ void SctpTransport::close() {
|
||||
|
||||
void SctpTransport::connect() {
|
||||
if (!mSock)
|
||||
return;
|
||||
throw std::logic_error("Attempted SCTP connect with closed socket");
|
||||
|
||||
PLOG_DEBUG << "SCTP connecting";
|
||||
changeState(State::Connecting);
|
||||
@ -305,7 +311,7 @@ void SctpTransport::incoming(message_ptr message) {
|
||||
// to be sent on our side (i.e. the local INIT) before proceeding.
|
||||
if (!mWrittenOnce) { // test the atomic boolean is not set first to prevent a lock contention
|
||||
std::unique_lock lock(mWriteMutex);
|
||||
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || state() != State::Connected; });
|
||||
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce.load(); });
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
@ -319,6 +325,59 @@ void SctpTransport::incoming(message_ptr message) {
|
||||
usrsctp_conninput(this, message->data(), message->size(), 0);
|
||||
}
|
||||
|
||||
void SctpTransport::doRecv() {
|
||||
std::lock_guard lock(mRecvMutex);
|
||||
try {
|
||||
while (true) {
|
||||
const size_t bufferSize = 65536;
|
||||
byte buffer[bufferSize];
|
||||
socklen_t fromlen = 0;
|
||||
struct sctp_rcvinfo info = {};
|
||||
socklen_t infolen = sizeof(info);
|
||||
unsigned int infotype = 0;
|
||||
int flags = 0;
|
||||
ssize_t len = usrsctp_recvv(mSock, buffer, bufferSize, nullptr, &fromlen, &info,
|
||||
&infolen, &infotype, &flags);
|
||||
if (len < 0) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == ECONNRESET)
|
||||
break;
|
||||
else
|
||||
throw std::runtime_error("SCTP recv failed, errno=" + std::to_string(errno));
|
||||
}
|
||||
|
||||
PLOG_VERBOSE << "SCTP recv, len=" << len;
|
||||
|
||||
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
|
||||
// therefore partial notifications and messages need to be handled separately.
|
||||
if (flags & MSG_NOTIFICATION) {
|
||||
// SCTP event notification
|
||||
mPartialNotification.insert(mPartialNotification.end(), buffer, buffer + len);
|
||||
if (flags & MSG_EOR) {
|
||||
// Notification is complete, process it
|
||||
auto notification =
|
||||
reinterpret_cast<union sctp_notification *>(mPartialNotification.data());
|
||||
processNotification(notification, mPartialNotification.size());
|
||||
mPartialNotification.clear();
|
||||
}
|
||||
} else {
|
||||
// SCTP message
|
||||
mPartialMessage.insert(mPartialMessage.end(), buffer, buffer + len);
|
||||
if (flags & MSG_EOR) {
|
||||
// Message is complete, process it
|
||||
if (infotype != SCTP_RECVV_RCVINFO)
|
||||
throw std::runtime_error("Missing SCTP recv info");
|
||||
|
||||
processData(std::move(mPartialMessage), info.rcv_sid,
|
||||
PayloadId(ntohl(info.rcv_ppid)));
|
||||
mPartialMessage.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
bool SctpTransport::trySendQueue() {
|
||||
// Requires mSendMutex to be locked
|
||||
while (auto next = mSendQueue.peek()) {
|
||||
@ -472,46 +531,19 @@ bool SctpTransport::safeFlush() {
|
||||
}
|
||||
}
|
||||
|
||||
int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*addr*/,
|
||||
const byte *data, size_t len, struct sctp_rcvinfo info, int flags) {
|
||||
try {
|
||||
PLOG_VERBOSE << "Handle recv, len=" << len;
|
||||
if (!len)
|
||||
return 0; // Ignore
|
||||
void SctpTransport::handleUpcall() {
|
||||
if(!mSock)
|
||||
return;
|
||||
|
||||
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
|
||||
// therefore partial notifications and messages need to be handled separately.
|
||||
if (flags & MSG_NOTIFICATION) {
|
||||
// SCTP event notification
|
||||
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
|
||||
if (flags & MSG_EOR) {
|
||||
// Notification is complete, process it
|
||||
processNotification(
|
||||
reinterpret_cast<const union sctp_notification *>(mPartialNotification.data()),
|
||||
mPartialNotification.size());
|
||||
mPartialNotification.clear();
|
||||
}
|
||||
} else {
|
||||
// SCTP message
|
||||
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
|
||||
if (flags & MSG_EOR) {
|
||||
// Message is complete, process it
|
||||
processData(std::move(mPartialMessage), info.rcv_sid,
|
||||
PayloadId(htonl(info.rcv_ppid)));
|
||||
mPartialMessage.clear();
|
||||
}
|
||||
}
|
||||
PLOG_VERBOSE << "Handle upcall";
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "SCTP recv: " << e.what();
|
||||
return -1;
|
||||
}
|
||||
return 0; // success
|
||||
}
|
||||
int events = usrsctp_get_events(mSock);
|
||||
|
||||
int SctpTransport::handleSend(size_t free) {
|
||||
PLOG_VERBOSE << "Handle send, free=" << free;
|
||||
return safeFlush() ? 0 : -1;
|
||||
if (events & SCTP_EVENT_READ)
|
||||
mProcessor.enqueue(&SctpTransport::doRecv, this);
|
||||
|
||||
if (events & SCTP_EVENT_WRITE)
|
||||
mProcessor.enqueue(&SctpTransport::safeFlush, this);
|
||||
}
|
||||
|
||||
int SctpTransport::handleWrite(byte *data, size_t len, uint8_t /*tos*/, uint8_t /*set_df*/) {
|
||||
@ -701,31 +733,14 @@ std::optional<milliseconds> SctpTransport::rtt() {
|
||||
return milliseconds(status.sstat_primary.spinfo_srtt);
|
||||
}
|
||||
|
||||
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
|
||||
size_t len, struct sctp_rcvinfo recv_info, int flags,
|
||||
void *ulp_info) {
|
||||
auto *transport = static_cast<SctpTransport *>(ulp_info);
|
||||
|
||||
std::shared_lock lock(InstancesMutex);
|
||||
if (Instances.find(transport) == Instances.end()) {
|
||||
free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret =
|
||||
transport->handleRecv(sock, addr, static_cast<const byte *>(data), len, recv_info, flags);
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SctpTransport::SendCallback(struct socket *, uint32_t sb_free, void *ulp_info) {
|
||||
auto *transport = static_cast<SctpTransport *>(ulp_info);
|
||||
void SctpTransport::UpcallCallback(struct socket *, void *arg, int /* flags */) {
|
||||
auto *transport = static_cast<SctpTransport *>(arg);
|
||||
|
||||
std::shared_lock lock(InstancesMutex);
|
||||
if (Instances.find(transport) == Instances.end())
|
||||
return -1;
|
||||
return;
|
||||
|
||||
return transport->handleSend(size_t(sb_free));
|
||||
transport->handleUpcall();
|
||||
}
|
||||
|
||||
int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) {
|
||||
|
@ -21,8 +21,10 @@
|
||||
|
||||
#include "include.hpp"
|
||||
#include "peerconnection.hpp"
|
||||
#include "processor.hpp"
|
||||
#include "queue.hpp"
|
||||
#include "transport.hpp"
|
||||
#include "processor.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
@ -35,7 +37,7 @@
|
||||
|
||||
namespace rtc {
|
||||
|
||||
class SctpTransport : public Transport {
|
||||
class SctpTransport final : public Transport {
|
||||
public:
|
||||
static void Init();
|
||||
static void Cleanup();
|
||||
@ -76,15 +78,14 @@ private:
|
||||
void close();
|
||||
void incoming(message_ptr message) override;
|
||||
|
||||
void doRecv();
|
||||
bool trySendQueue();
|
||||
bool trySendMessage(message_ptr message);
|
||||
void updateBufferedAmount(uint16_t streamId, long delta);
|
||||
void sendReset(uint16_t streamId);
|
||||
bool safeFlush();
|
||||
|
||||
int handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data, size_t len,
|
||||
struct sctp_rcvinfo recv_info, int flags);
|
||||
int handleSend(size_t free);
|
||||
void handleUpcall();
|
||||
int handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||
|
||||
void processData(binary &&data, uint16_t streamId, PayloadId ppid);
|
||||
@ -93,14 +94,15 @@ private:
|
||||
const uint16_t mPort;
|
||||
struct socket *mSock;
|
||||
|
||||
std::mutex mSendMutex;
|
||||
Processor mProcessor;
|
||||
std::mutex mRecvMutex, mSendMutex;
|
||||
Queue<message_ptr> mSendQueue;
|
||||
std::map<uint16_t, size_t> mBufferedAmount;
|
||||
amount_callback mBufferedAmountCallback;
|
||||
|
||||
std::mutex mWriteMutex;
|
||||
std::condition_variable mWrittenCondition;
|
||||
std::atomic<bool> mWritten = false; // written outside lock
|
||||
std::atomic<bool> mWritten = false; // written outside lock
|
||||
std::atomic<bool> mWrittenOnce = false; // same
|
||||
|
||||
binary mPartialMessage, mPartialNotification;
|
||||
@ -109,9 +111,7 @@ private:
|
||||
// Stats
|
||||
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
|
||||
|
||||
static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
||||
struct sctp_rcvinfo recv_info, int flags, void *ulp_info);
|
||||
static int SendCallback(struct socket *sock, uint32_t sb_free, void *ulp_info);
|
||||
static void UpcallCallback(struct socket *sock, void *arg, int flags);
|
||||
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||
|
||||
static std::unordered_set<SctpTransport *> Instances;
|
||||
|
@ -58,11 +58,7 @@ void ThreadPool::run() {
|
||||
|
||||
bool ThreadPool::runOne() {
|
||||
if (auto task = dequeue()) {
|
||||
try {
|
||||
task();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << "Unhandled exception in task: " << e.what();
|
||||
}
|
||||
task();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -79,4 +75,3 @@ std::function<void()> ThreadPool::dequeue() {
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
|
@ -73,8 +73,15 @@ template <class F, class... Args>
|
||||
auto ThreadPool::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
|
||||
std::unique_lock lock(mMutex);
|
||||
using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
|
||||
auto task = std::make_shared<std::packaged_task<R()>>(
|
||||
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
|
||||
auto bound = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
auto task = std::make_shared<std::packaged_task<R()>>([bound = std::move(bound)]() mutable {
|
||||
try {
|
||||
return bound();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
throw;
|
||||
}
|
||||
});
|
||||
std::future<R> result = task->get_future();
|
||||
|
||||
mTasks.emplace([task = std::move(task), token = Init::Token()]() { return (*task)(); });
|
||||
|
@ -127,4 +127,3 @@ bool check(SSL *ssl, int ret, const string &message) {
|
||||
} // namespace rtc::openssl
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -56,12 +56,12 @@ gnutls_datum_t make_datum(char *data, size_t size);
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#ifndef BIO_EOF
|
||||
#define BIO_EOF -1
|
||||
|
@ -62,10 +62,10 @@ TlsTransport::TlsTransport(shared_ptr<TcpTransport> lower, string host, state_ca
|
||||
gnutls::check(gnutls_priority_set_direct(mSession, priorities, &err_pos),
|
||||
"Failed to set TLS priorities");
|
||||
|
||||
PLOG_VERBOSE << "Server Name Indication: " << mHost;
|
||||
PLOG_VERBOSE << "Server Name Indication: " << mHost;
|
||||
gnutls_server_name_set(mSession, GNUTLS_NAME_DNS, mHost.data(), mHost.size());
|
||||
|
||||
gnutls_session_set_ptr(mSession, this);
|
||||
gnutls_session_set_ptr(mSession, this);
|
||||
gnutls_transport_set_ptr(mSession, this);
|
||||
gnutls_transport_set_push_function(mSession, WriteCallback);
|
||||
gnutls_transport_set_pull_function(mSession, ReadCallback);
|
||||
@ -110,7 +110,7 @@ bool TlsTransport::send(message_ptr message) {
|
||||
|
||||
PLOG_VERBOSE << "Send size=" << message->size();
|
||||
|
||||
if(message->size() == 0)
|
||||
if (message->size() == 0)
|
||||
return true;
|
||||
|
||||
ssize_t ret;
|
||||
@ -207,10 +207,10 @@ ssize_t TlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_
|
||||
message_ptr &message = t->mIncomingMessage;
|
||||
size_t &position = t->mIncomingMessagePosition;
|
||||
|
||||
if(message && position >= message->size())
|
||||
if (message && position >= message->size())
|
||||
message.reset();
|
||||
|
||||
if(!message) {
|
||||
if (!message) {
|
||||
position = 0;
|
||||
while (auto next = t->mIncomingQueue.pop()) {
|
||||
message = *next;
|
||||
@ -221,15 +221,14 @@ ssize_t TlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_
|
||||
}
|
||||
}
|
||||
|
||||
if(message) {
|
||||
if (message) {
|
||||
size_t available = message->size() - position;
|
||||
ssize_t len = std::min(maxlen, available);
|
||||
std::memcpy(data, message->data() + position, len);
|
||||
position+= len;
|
||||
position += len;
|
||||
gnutls_transport_set_errno(t->mSession, 0);
|
||||
return len;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Closed
|
||||
gnutls_transport_set_errno(t->mSession, 0);
|
||||
return 0;
|
||||
|
@ -32,6 +32,13 @@ string Track::mid() const { return mMediaDescription.mid(); }
|
||||
|
||||
Description::Media Track::description() const { return mMediaDescription; }
|
||||
|
||||
void Track::setDescription(Description::Media description) {
|
||||
if (description.mid() != mMediaDescription.mid())
|
||||
throw std::logic_error("Media description mid does not match track mid");
|
||||
|
||||
mMediaDescription = std::move(description);
|
||||
}
|
||||
|
||||
void Track::close() {
|
||||
mIsClosed = true;
|
||||
resetCallbacks();
|
||||
@ -51,6 +58,13 @@ std::optional<message_variant> Track::receive() {
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::optional<message_variant> Track::peek() {
|
||||
if (auto next = mRecvQueue.peek())
|
||||
return to_variant(std::move(**next));
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool Track::isOpen(void) const {
|
||||
#if RTC_ENABLE_MEDIA
|
||||
return !mIsClosed && mDtlsSrtpTransport.lock();
|
||||
@ -65,9 +79,7 @@ size_t Track::maxMessageSize() const {
|
||||
return 65535 - 12 - 4; // SRTP/UDP
|
||||
}
|
||||
|
||||
size_t Track::availableAmount() const {
|
||||
return mRecvQueue.amount();
|
||||
}
|
||||
size_t Track::availableAmount() const { return mRecvQueue.amount(); }
|
||||
|
||||
#if RTC_ENABLE_MEDIA
|
||||
void Track::open(shared_ptr<DtlsSrtpTransport> transport) {
|
||||
@ -77,10 +89,20 @@ void Track::open(shared_ptr<DtlsSrtpTransport> transport) {
|
||||
#endif
|
||||
|
||||
bool Track::outgoing(message_ptr message) {
|
||||
|
||||
if (mRtcpHandler) {
|
||||
message = mRtcpHandler->outgoing(message);
|
||||
if (!message)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto direction = mMediaDescription.direction();
|
||||
if (direction == Description::Direction::RecvOnly ||
|
||||
direction == Description::Direction::Inactive)
|
||||
throw std::runtime_error("Track media direction does not allow sending");
|
||||
if ((direction == Description::Direction::RecvOnly ||
|
||||
direction == Description::Direction::Inactive) &&
|
||||
message->type != Message::Control) {
|
||||
PLOG_WARNING << "Track media direction does not allow transmission, dropping";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mIsClosed)
|
||||
throw std::runtime_error("Track is closed");
|
||||
@ -105,11 +127,9 @@ void Track::incoming(message_ptr message) {
|
||||
return;
|
||||
|
||||
if (mRtcpHandler) {
|
||||
auto opt = mRtcpHandler->incoming(message);
|
||||
if (!opt)
|
||||
message = mRtcpHandler->incoming(message);
|
||||
if (!message)
|
||||
return;
|
||||
|
||||
message = *opt;
|
||||
}
|
||||
|
||||
auto direction = mMediaDescription.direction();
|
||||
@ -117,6 +137,7 @@ void Track::incoming(message_ptr message) {
|
||||
direction == Description::Direction::Inactive) &&
|
||||
message->type != Message::Control) {
|
||||
PLOG_WARNING << "Track media direction does not allow reception, dropping";
|
||||
return;
|
||||
}
|
||||
|
||||
// Tail drop if queue is full
|
||||
@ -128,21 +149,29 @@ void Track::incoming(message_ptr message) {
|
||||
}
|
||||
|
||||
void Track::setRtcpHandler(std::shared_ptr<RtcpHandler> handler) {
|
||||
if (mRtcpHandler)
|
||||
mRtcpHandler->onOutgoing(nullptr);
|
||||
|
||||
mRtcpHandler = std::move(handler);
|
||||
if (mRtcpHandler) {
|
||||
mRtcpHandler->onOutgoing([&]([[maybe_unused]] message_ptr message) {
|
||||
mRtcpHandler->onOutgoing([&]([[maybe_unused]] const rtc::message_ptr &message) {
|
||||
#if RTC_ENABLE_MEDIA
|
||||
if (auto transport = mDtlsSrtpTransport.lock())
|
||||
transport->sendMedia(message);
|
||||
auto transport = mDtlsSrtpTransport.lock();
|
||||
if (!transport)
|
||||
throw std::runtime_error("Track transport is not open");
|
||||
|
||||
return transport->sendMedia(message);
|
||||
#else
|
||||
PLOG_WARNING << "Ignoring RTCP send (not compiled with SRTP support)";
|
||||
PLOG_WARNING << "Ignoring track send (not compiled with SRTP support)";
|
||||
return false;
|
||||
#endif
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
bool Track::requestKeyframe() {
|
||||
if (mRtcpHandler)
|
||||
return mRtcpHandler->requestKeyframe();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<RtcpHandler> Track::getRtcpHandler() { return mRtcpHandler; }
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -36,8 +36,7 @@ public:
|
||||
using state_callback = std::function<void(State state)>;
|
||||
|
||||
Transport(std::shared_ptr<Transport> lower = nullptr, state_callback callback = nullptr)
|
||||
: mLower(std::move(lower)), mStateChangeCallback(std::move(callback)) {
|
||||
}
|
||||
: mLower(std::move(lower)), mStateChangeCallback(std::move(callback)) {}
|
||||
|
||||
virtual ~Transport() { stop(); }
|
||||
|
||||
@ -48,15 +47,18 @@ public:
|
||||
return false;
|
||||
|
||||
// We don't want incoming() to be called by the lower layer anymore
|
||||
if (mLower)
|
||||
if (mLower) {
|
||||
PLOG_VERBOSE << "Unregistering incoming callback";
|
||||
mLower->onRecv(nullptr);
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void registerIncoming() {
|
||||
if (mLower)
|
||||
if (mLower) {
|
||||
PLOG_VERBOSE << "Registering incoming callback";
|
||||
mLower->onRecv(std::bind(&Transport::incoming, this, _1));
|
||||
}
|
||||
}
|
||||
|
||||
void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }
|
||||
|
@ -28,7 +28,6 @@ using std::weak_ptr;
|
||||
|
||||
namespace rtc {
|
||||
|
||||
|
||||
VerifiedTlsTransport::VerifiedTlsTransport(shared_ptr<TcpTransport> lower, string host,
|
||||
state_callback callback)
|
||||
: TlsTransport(std::move(lower), std::move(host), std::move(callback)) {
|
||||
@ -48,4 +47,3 @@ VerifiedTlsTransport::~VerifiedTlsTransport() {}
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -51,31 +51,45 @@ WebSocket::~WebSocket() {
|
||||
WebSocket::State WebSocket::readyState() const { return mState; }
|
||||
|
||||
void WebSocket::open(const string &url) {
|
||||
PLOG_VERBOSE << "Opening WebSocket to URL: " << url;
|
||||
|
||||
if (mState != State::Closed)
|
||||
throw std::runtime_error("WebSocket must be closed before opening");
|
||||
throw std::logic_error("WebSocket must be closed before opening");
|
||||
|
||||
static const char *rs = R"(^(([^:\/?#]+):)?(//([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)";
|
||||
static std::regex regex(rs, std::regex::extended);
|
||||
// Modified regex from RFC 3986, see https://tools.ietf.org/html/rfc3986#appendix-B
|
||||
static const char *rs =
|
||||
R"(^(([^:.@/?#]+):)?(/{0,2}((([^:@]*)(:([^@]*))?)@)?(([^:/?#]*)(:([^/?#]*))?))?([^?#]*)(\?([^#]*))?(#(.*))?)";
|
||||
|
||||
std::smatch match;
|
||||
if (!std::regex_match(url, match, regex))
|
||||
throw std::invalid_argument("Malformed WebSocket URL: " + url);
|
||||
static const std::regex r(rs, std::regex::extended);
|
||||
|
||||
mScheme = match[2];
|
||||
if (mScheme != "ws" && mScheme != "wss")
|
||||
std::smatch m;
|
||||
if (!std::regex_match(url, m, r) || m[10].length() == 0)
|
||||
throw std::invalid_argument("Invalid WebSocket URL: " + url);
|
||||
|
||||
mScheme = m[2];
|
||||
if (mScheme.empty())
|
||||
mScheme = "ws";
|
||||
else if (mScheme != "ws" && mScheme != "wss")
|
||||
throw std::invalid_argument("Invalid WebSocket scheme: " + mScheme);
|
||||
|
||||
mHost = match[4];
|
||||
if (auto pos = mHost.find(':'); pos != string::npos) {
|
||||
mHostname = mHost.substr(0, pos);
|
||||
mService = mHost.substr(pos + 1);
|
||||
} else {
|
||||
mHostname = mHost;
|
||||
mHostname = m[10];
|
||||
mService = m[12];
|
||||
if (mService.empty()) {
|
||||
mService = mScheme == "ws" ? "80" : "443";
|
||||
mHost = mHostname;
|
||||
} else {
|
||||
mHost = mHostname + ':' + mService;
|
||||
}
|
||||
|
||||
mPath = match[5];
|
||||
if (string query = match[7]; !query.empty())
|
||||
while (!mHostname.empty() && mHostname.front() == '[')
|
||||
mHostname.erase(mHostname.begin());
|
||||
while (!mHostname.empty() && mHostname.back() == ']')
|
||||
mHostname.pop_back();
|
||||
|
||||
mPath = m[13];
|
||||
if (mPath.empty())
|
||||
mPath += '/';
|
||||
if (string query = m[15]; !query.empty())
|
||||
mPath += "?" + query;
|
||||
|
||||
changeState(State::Connecting);
|
||||
@ -111,13 +125,24 @@ size_t WebSocket::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
|
||||
|
||||
std::optional<message_variant> WebSocket::receive() {
|
||||
while (auto next = mRecvQueue.tryPop()) {
|
||||
message_ptr message = std::move(*next);
|
||||
message_ptr message = *next;
|
||||
if (message->type != Message::Control)
|
||||
return to_variant(std::move(*message));
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::optional<message_variant> WebSocket::peek() {
|
||||
while (auto next = mRecvQueue.peek()) {
|
||||
message_ptr message = *next;
|
||||
if (message->type != Message::Control)
|
||||
return to_variant(std::move(*message));
|
||||
|
||||
mRecvQueue.tryPop();
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
size_t WebSocket::availableAmount() const { return mRecvQueue.amount(); }
|
||||
|
||||
bool WebSocket::changeState(State state) { return mState.exchange(state) != state; }
|
||||
@ -145,6 +170,7 @@ void WebSocket::incoming(message_ptr message) {
|
||||
}
|
||||
|
||||
shared_ptr<TcpTransport> WebSocket::initTcpTransport() {
|
||||
PLOG_VERBOSE << "Starting TCP transport";
|
||||
using State = TcpTransport::State;
|
||||
try {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
@ -191,6 +217,7 @@ shared_ptr<TcpTransport> WebSocket::initTcpTransport() {
|
||||
}
|
||||
|
||||
shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
|
||||
PLOG_VERBOSE << "Starting TLS transport";
|
||||
using State = TlsTransport::State;
|
||||
try {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
@ -224,12 +251,13 @@ shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
|
||||
if (!mConfig.disableTlsVerification) {
|
||||
PLOG_WARNING << "TLS certificate verification with root CA is not supported on Windows";
|
||||
}
|
||||
transport = std::make_shared<TlsTransport>(lower, mHost, stateChangeCallback);
|
||||
transport = std::make_shared<TlsTransport>(lower, mHostname, stateChangeCallback);
|
||||
#else
|
||||
if (mConfig.disableTlsVerification)
|
||||
transport = std::make_shared<TlsTransport>(lower, mHost, stateChangeCallback);
|
||||
transport = std::make_shared<TlsTransport>(lower, mHostname, stateChangeCallback);
|
||||
else
|
||||
transport = std::make_shared<VerifiedTlsTransport>(lower, mHost, stateChangeCallback);
|
||||
transport =
|
||||
std::make_shared<VerifiedTlsTransport>(lower, mHostname, stateChangeCallback);
|
||||
#endif
|
||||
|
||||
std::atomic_store(&mTlsTransport, transport);
|
||||
@ -248,6 +276,7 @@ shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
|
||||
}
|
||||
|
||||
shared_ptr<WsTransport> WebSocket::initWsTransport() {
|
||||
PLOG_VERBOSE << "Starting WebSocket transport";
|
||||
using State = WsTransport::State;
|
||||
try {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
@ -326,6 +355,6 @@ void WebSocket::closeTransports() {
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -17,9 +17,9 @@
|
||||
*/
|
||||
|
||||
#include "wstransport.hpp"
|
||||
#include "base64.hpp"
|
||||
#include "tcptransport.hpp"
|
||||
#include "tlstransport.hpp"
|
||||
#include "base64.hpp"
|
||||
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
|
||||
@ -58,6 +58,12 @@ WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string p
|
||||
onRecv(recvCallback);
|
||||
|
||||
PLOG_DEBUG << "Initializing WebSocket transport";
|
||||
|
||||
if (mHost.empty())
|
||||
throw std::invalid_argument("WebSocket HTTP host cannot be empty");
|
||||
|
||||
if (mPath.empty())
|
||||
throw std::invalid_argument("WebSocket HTTP path cannot be empty");
|
||||
}
|
||||
|
||||
WsTransport::~WsTransport() { stop(); }
|
||||
@ -147,7 +153,7 @@ void WsTransport::close() {
|
||||
}
|
||||
|
||||
bool WsTransport::sendHttpRequest() {
|
||||
PLOG_DEBUG << "Sending WebSocket HTTP request";
|
||||
PLOG_DEBUG << "Sending WebSocket HTTP request for path " << mPath;
|
||||
changeState(State::Connecting);
|
||||
|
||||
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||
|
@ -169,9 +169,9 @@ size_t benchmark(milliseconds duration) {
|
||||
|
||||
pc1->close();
|
||||
pc2->close();
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
rtc::Cleanup();
|
||||
this_thread::sleep_for(1s);
|
||||
return goodput;
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,12 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#include <unistd.h> // for sleep
|
||||
#endif
|
||||
|
||||
#define BUFFER_SIZE 4096
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
rtcSignalingState signalingState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
@ -40,33 +43,39 @@ typedef struct {
|
||||
static Peer *peer1 = NULL;
|
||||
static Peer *peer2 = NULL;
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcSetRemoteDescription(other->pc, sdp, type);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcAddRemoteCandidate(other->pc, cand, mid);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
static void RTC_API signalingStateCallback(int pc, rtcSignalingState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->signalingState = state;
|
||||
printf("Signaling state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void RTC_API openCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
printf("DataChannel %d: Open\n", peer == peer1 ? 1 : 2);
|
||||
@ -75,12 +84,12 @@ static void openCallback(void *ptr) {
|
||||
rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
static void RTC_API closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %d: %s\n", peer == peer1 ? 1 : 2, message);
|
||||
@ -89,7 +98,7 @@ static void messageCallback(const char *message, int size, void *ptr) {
|
||||
}
|
||||
}
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr) {
|
||||
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
@ -178,20 +187,81 @@ int test_capi_connectivity_main() {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (peer1->signalingState != RTC_SIGNALING_STABLE ||
|
||||
peer2->signalingState != RTC_SIGNALING_STABLE) {
|
||||
fprintf(stderr, "Signaling state is not stable\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!peer1->connected || !peer2->connected) {
|
||||
fprintf(stderr, "DataChannel is not connected\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer1->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer1->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
if (rtcGetLocalAddress(peer2->pc, buffer, 256) >= 0)
|
||||
printf("Local address 2: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer2->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 2: %s\n", buffer);
|
||||
char buffer[BUFFER_SIZE];
|
||||
char buffer2[BUFFER_SIZE];
|
||||
|
||||
if (rtcGetLocalDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetLocalDescription failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local description 1: %s\n", buffer);
|
||||
|
||||
if (rtcGetRemoteDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetRemoteDescription failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Remote description 1: %s\n", buffer);
|
||||
|
||||
if (rtcGetLocalDescription(peer2->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetLocalDescription failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local description 2: %s\n", buffer);
|
||||
|
||||
if (rtcGetRemoteDescription(peer2->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetRemoteDescription failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Remote description 2: %s\n", buffer);
|
||||
|
||||
if (rtcGetLocalAddress(peer1->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetLocalAddress failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
|
||||
if (rtcGetRemoteAddress(peer1->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetRemoteAddress failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
if (rtcGetLocalAddress(peer2->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetLocalAddress failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local address 2: %s\n", buffer);
|
||||
|
||||
if (rtcGetRemoteAddress(peer2->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetRemoteAddress failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Remote address 2: %s\n", buffer);
|
||||
|
||||
if (rtcGetSelectedCandidatePair(peer1->pc, buffer, BUFFER_SIZE, buffer2, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetSelectedCandidatePair failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local candidate 1: %s\n", buffer);
|
||||
printf("Remote candidate 1: %s\n", buffer2);
|
||||
|
||||
if (rtcGetSelectedCandidatePair(peer2->pc, buffer, BUFFER_SIZE, buffer2, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetSelectedCandidatePair failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local candidate 2: %s\n", buffer);
|
||||
printf("Remote candidate 2: %s\n", buffer2);
|
||||
|
||||
deletePeer(peer1);
|
||||
sleep(1);
|
||||
@ -200,7 +270,7 @@ int test_capi_connectivity_main() {
|
||||
|
||||
// You may call rtcCleanup() when finished to free static resources
|
||||
rtcCleanup();
|
||||
sleep(2);
|
||||
sleep(1);
|
||||
|
||||
printf("Success\n");
|
||||
return 0;
|
||||
|
@ -43,44 +43,44 @@ static Peer *peer2 = NULL;
|
||||
static const char *mediaDescription = "video 9 UDP/TLS/RTP/SAVPF\r\n"
|
||||
"a=mid:video\r\n";
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcSetRemoteDescription(other->pc, sdp, type);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcAddRemoteCandidate(other->pc, cand, mid);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
static void RTC_API openCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
printf("Track %d: Open\n", peer == peer1 ? 1 : 2);
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
static void RTC_API closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void trackCallback(int tr, void *ptr) {
|
||||
static void RTC_API trackCallback(int pc, int tr, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->tr = tr;
|
||||
peer->connected = true;
|
||||
@ -156,7 +156,7 @@ int test_capi_track_main() {
|
||||
rtcSetClosedCallback(peer1->tr, closedCallback);
|
||||
|
||||
// Initiate the handshake
|
||||
rtcSetLocalDescription(peer1->pc);
|
||||
rtcSetLocalDescription(peer1->pc, NULL);
|
||||
|
||||
attempts = 10;
|
||||
while ((!peer2->connected || !peer1->connected) && attempts--)
|
||||
@ -179,7 +179,7 @@ int test_capi_track_main() {
|
||||
|
||||
// You may call rtcCleanup() when finished to free static resources
|
||||
rtcCleanup();
|
||||
sleep(2);
|
||||
sleep(1);
|
||||
|
||||
printf("Success\n");
|
||||
return 0;
|
||||
|
@ -69,6 +69,10 @@ void test_connectivity() {
|
||||
cout << "Gathering state 1: " << state << endl;
|
||||
});
|
||||
|
||||
pc1->onSignalingStateChange([](PeerConnection::SignalingState state) {
|
||||
cout << "Signaling state 1: " << state << endl;
|
||||
});
|
||||
|
||||
pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](Description sdp) {
|
||||
auto pc1 = wpc1.lock();
|
||||
if (!pc1)
|
||||
@ -91,6 +95,10 @@ void test_connectivity() {
|
||||
cout << "Gathering state 2: " << state << endl;
|
||||
});
|
||||
|
||||
pc2->onSignalingStateChange([](PeerConnection::SignalingState state) {
|
||||
cout << "Signaling state 2: " << state << endl;
|
||||
});
|
||||
|
||||
shared_ptr<DataChannel> dc2;
|
||||
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
||||
cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
|
||||
@ -125,6 +133,7 @@ void test_connectivity() {
|
||||
}
|
||||
});
|
||||
|
||||
// Wait a bit
|
||||
int attempts = 10;
|
||||
shared_ptr<DataChannel> adc2;
|
||||
while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
|
||||
@ -146,6 +155,90 @@ void test_connectivity() {
|
||||
if (auto addr = pc2->remoteAddress())
|
||||
cout << "Remote address 2: " << *addr << endl;
|
||||
|
||||
Candidate local, remote;
|
||||
if (pc1->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local candidate 1: " << local << endl;
|
||||
cout << "Remote candidate 1: " << remote << endl;
|
||||
}
|
||||
if (pc2->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local candidate 2: " << local << endl;
|
||||
cout << "Remote candidate 2: " << remote << endl;
|
||||
}
|
||||
|
||||
// Try to open a second data channel with another label
|
||||
shared_ptr<DataChannel> second2;
|
||||
pc2->onDataChannel([&second2](shared_ptr<DataChannel> dc) {
|
||||
cout << "Second DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
|
||||
if (dc->label() != "second") {
|
||||
cerr << "Wrong second DataChannel label" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
dc->onMessage([](variant<binary, string> message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Second Message 2: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
|
||||
dc->send("Send hello from 2");
|
||||
|
||||
std::atomic_store(&second2, dc);
|
||||
});
|
||||
|
||||
auto second1 = pc1->createDataChannel("second");
|
||||
second1->onOpen([wsecond1 = make_weak_ptr(dc1)]() {
|
||||
auto second1 = wsecond1.lock();
|
||||
if (!second1)
|
||||
return;
|
||||
|
||||
cout << "Second DataChannel 1: Open" << endl;
|
||||
second1->send("Second hello from 1");
|
||||
});
|
||||
dc1->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Second Message 1: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
|
||||
// Wait a bit
|
||||
attempts = 10;
|
||||
shared_ptr<DataChannel> asecond2;
|
||||
while (
|
||||
(!(asecond2 = std::atomic_load(&second2)) || !asecond2->isOpen() || !second1->isOpen()) &&
|
||||
attempts--)
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
if (!asecond2 || !asecond2->isOpen() || !second1->isOpen())
|
||||
throw runtime_error("Second DataChannel is not open");
|
||||
|
||||
// Try to open a negotiated channel
|
||||
DataChannelInit init;
|
||||
init.negotiated = true;
|
||||
init.id = 42;
|
||||
auto negotiated1 = pc1->createDataChannel("negotiated", init);
|
||||
auto negotiated2 = pc2->createDataChannel("negoctated", init);
|
||||
|
||||
if (!negotiated1->isOpen() || !negotiated2->isOpen())
|
||||
throw runtime_error("Negociated DataChannel is not open");
|
||||
|
||||
std::atomic<bool> received = false;
|
||||
negotiated2->onMessage([&received](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Second Message 2: " << get<string>(message) << endl;
|
||||
received = true;
|
||||
}
|
||||
});
|
||||
|
||||
negotiated1->send("Hello from negotiated channel");
|
||||
|
||||
// Wait a bit
|
||||
attempts = 5;
|
||||
while (!received && attempts--)
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
if (!received)
|
||||
throw runtime_error("Negociated DataChannel failed");
|
||||
|
||||
// Delay close of peer 2 to check closing works properly
|
||||
pc1->close();
|
||||
this_thread::sleep_for(1s);
|
||||
@ -154,7 +247,7 @@ void test_connectivity() {
|
||||
|
||||
// You may call rtc::Cleanup() when finished to free static resources
|
||||
rtc::Cleanup();
|
||||
this_thread::sleep_for(2s);
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
cout << "Success" << endl;
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ int main(int argc, char **argv) {
|
||||
cerr << "WebRTC connectivity test failed: " << e.what() << endl;
|
||||
return -1;
|
||||
}
|
||||
this_thread::sleep_for(1s);
|
||||
try {
|
||||
cout << endl << "*** Running WebRTC C API connectivity test..." << endl;
|
||||
test_capi_connectivity();
|
||||
@ -59,6 +60,7 @@ int main(int argc, char **argv) {
|
||||
return -1;
|
||||
}
|
||||
#if RTC_ENABLE_MEDIA
|
||||
this_thread::sleep_for(1s);
|
||||
try {
|
||||
cout << endl << "*** Running WebRTC Track test..." << endl;
|
||||
test_track();
|
||||
@ -77,6 +79,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
#endif
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
this_thread::sleep_for(1s);
|
||||
try {
|
||||
cout << endl << "*** Running WebSocket test..." << endl;
|
||||
test_websocket();
|
||||
@ -86,6 +89,7 @@ int main(int argc, char **argv) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
this_thread::sleep_for(1s);
|
||||
try {
|
||||
cout << endl << "*** Running WebRTC benchmark..." << endl;
|
||||
test_benchmark();
|
||||
|
@ -92,9 +92,10 @@ void test_track() {
|
||||
});
|
||||
|
||||
shared_ptr<Track> t2;
|
||||
pc2->onTrack([&t2](shared_ptr<Track> t) {
|
||||
string newTrackMid;
|
||||
pc2->onTrack([&t2, &newTrackMid](shared_ptr<Track> t) {
|
||||
cout << "Track 2: Received with mid \"" << t->mid() << "\"" << endl;
|
||||
if (t->mid() != "test") {
|
||||
if (t->mid() != newTrackMid) {
|
||||
cerr << "Wrong track mid" << endl;
|
||||
return;
|
||||
}
|
||||
@ -102,7 +103,9 @@ void test_track() {
|
||||
std::atomic_store(&t2, t);
|
||||
});
|
||||
|
||||
auto t1 = pc1->addTrack(Description::Video("test"));
|
||||
// Test opening a track
|
||||
newTrackMid = "test";
|
||||
auto t1 = pc1->addTrack(Description::Video(newTrackMid));
|
||||
|
||||
pc1->setLocalDescription();
|
||||
|
||||
@ -118,6 +121,20 @@ void test_track() {
|
||||
if (!at2 || !at2->isOpen() || !t1->isOpen())
|
||||
throw runtime_error("Track is not open");
|
||||
|
||||
// Test renegotiation
|
||||
newTrackMid = "added";
|
||||
t1 = pc1->addTrack(Description::Video(newTrackMid));
|
||||
|
||||
pc1->setLocalDescription();
|
||||
|
||||
attempts = 10;
|
||||
t2.reset();
|
||||
while ((!(at2 = std::atomic_load(&t2)) || !at2->isOpen() || !t1->isOpen()) && attempts--)
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
if (!at2 || !at2->isOpen() || !t1->isOpen())
|
||||
throw runtime_error("Renegotiated track is not open");
|
||||
|
||||
// TODO: Test sending RTP packets in track
|
||||
|
||||
// Delay close of peer 2 to check closing works properly
|
||||
@ -128,7 +145,7 @@ void test_track() {
|
||||
|
||||
// You may call rtc::Cleanup() when finished to free static resources
|
||||
rtc::Cleanup();
|
||||
this_thread::sleep_for(2s);
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
cout << "Success" << endl;
|
||||
}
|
||||
|
@ -20,11 +20,11 @@
|
||||
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
@ -56,14 +56,14 @@ void test_websocket() {
|
||||
ws->onMessage([&received, &myMessage](variant<binary, string> message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
string str = std::move(get<string>(message));
|
||||
if((received = (str == myMessage)))
|
||||
if ((received = (str == myMessage)))
|
||||
cout << "WebSocket: Received expected message" << endl;
|
||||
else
|
||||
cout << "WebSocket: Received UNEXPECTED message" << endl;
|
||||
}
|
||||
});
|
||||
|
||||
ws->open("wss://echo.websocket.org/");
|
||||
ws->open("wss://echo.websocket.org:443/");
|
||||
|
||||
int attempts = 10;
|
||||
while ((!ws->isOpen() || !received) && attempts--)
|
||||
@ -72,11 +72,11 @@ void test_websocket() {
|
||||
if (!ws->isOpen())
|
||||
throw runtime_error("WebSocket is not open");
|
||||
|
||||
if(!received)
|
||||
if (!received)
|
||||
throw runtime_error("Expected message not received");
|
||||
|
||||
ws->close();
|
||||
this_thread::sleep_for(2s);
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
// You may call rtc::Cleanup() when finished to free static resources
|
||||
rtc::Cleanup();
|
||||
@ -86,4 +86,3 @@ void test_websocket() {
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
Reference in New Issue
Block a user