mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
179 Commits
Author | SHA1 | Date | |
---|---|---|---|
d262583879 | |||
100669ad0d | |||
7bda5bd77c | |||
52030ca62d | |||
959756526a | |||
1ce27423fe | |||
a0bd65a814 | |||
a7e4b56178 | |||
acaed8ca57 | |||
5728f813d1 | |||
d4d58579d8 | |||
a7620cca16 | |||
61d0cd064f | |||
f5a6342c60 | |||
478f9516cd | |||
08da5e10b2 | |||
c6cd711d74 | |||
4428e3dc3e | |||
fe4afcef85 | |||
03b64e765c | |||
c639174f39 | |||
ce7bd9f4d0 | |||
c4c150a60b | |||
e295fa6072 | |||
470d145a9a | |||
ea12530d30 | |||
329b133383 | |||
e1833cf34b | |||
a0f17cb64b | |||
8a5c38c2d8 | |||
7cddb83ce4 | |||
bb12c071cf | |||
4e3ea69073 | |||
b79c886480 | |||
08da691911 | |||
754568506a | |||
7ac351d1b9 | |||
6d5cecbca1 | |||
fb40e05bab | |||
3710a96fb9 | |||
a1a8ac5203 | |||
4db9f03dd4 | |||
256170adfe | |||
9a4b436e7f | |||
5928b8d206 | |||
1542c78ec5 | |||
cb73fa0c1a | |||
1ceb0fd292 | |||
7c14d940ef | |||
5da67f6ca3 | |||
28a2868edc | |||
3210814648 | |||
1facc8a02f | |||
09818c4ab8 | |||
9e49fcc654 | |||
e6919bdbf8 | |||
749fa32059 | |||
938bd4dd43 | |||
c43e82b8cb | |||
adb733d19a | |||
01085e4492 | |||
43d31caee3 | |||
9aee655fcd | |||
b8b5110653 | |||
e1087ce8b0 | |||
f41baa5c51 | |||
9ee9734473 | |||
3745ff5f7a | |||
3acddc6897 | |||
5d0c62e4bc | |||
06faefd1d6 | |||
4629e57931 | |||
52218272db | |||
f20faed180 | |||
2bae3f62c4 | |||
6eb663d1c5 | |||
a5cc76bb85 | |||
8419de1f1c | |||
e34a3bb8ae | |||
5b66ab2d08 | |||
769f26e94e | |||
31c154e9d9 | |||
a56b036940 | |||
d0e8b63ef0 | |||
d91401775e | |||
735cb538f7 | |||
e23d0723d1 | |||
bb530ef44d | |||
7a219b23d5 | |||
2cde11e9ea | |||
0bbb0233b7 | |||
533beda26a | |||
1427c9e1e4 | |||
6d670726ea | |||
7981fed7f4 | |||
f2caa8048f | |||
92a4ed2a81 | |||
62522d2c25 | |||
3f9565b55d | |||
7a06e48281 | |||
0610bf741f | |||
e79a6f08e3 | |||
4d407cf15e | |||
226e849915 | |||
068fdf60f9 | |||
74ada01114 | |||
cd0be9d510 | |||
b39fe51365 | |||
cf9e57564e | |||
d9aa1818b2 | |||
1d27f5b876 | |||
28e3fad254 | |||
b32a8b884b | |||
7751a9bd6b | |||
3dac651b02 | |||
f6f1efb33f | |||
435d471ad8 | |||
7aa2fdda6f | |||
67e125b22f | |||
f143582de0 | |||
4226a96640 | |||
d305c4c126 | |||
25b3e13d27 | |||
abec5fc219 | |||
684b7ba925 | |||
d8515b6362 | |||
62da885028 | |||
ff2e83bbdc | |||
8f9e8e718e | |||
c6bee7b0d4 | |||
4d93303be8 | |||
1620ddfb03 | |||
452b742adc | |||
244c834992 | |||
ffe202a6a2 | |||
ea87e5ae09 | |||
4259b4e968 | |||
6aff5dc5bc | |||
99bae7f830 | |||
7598d992dc | |||
6380038584 | |||
6144bca0f7 | |||
6ec129f8f8 | |||
be394b7185 | |||
f008b5b447 | |||
5482912e18 | |||
fcc4eaf78b | |||
cca0742973 | |||
495b389e05 | |||
e83494df09 | |||
7bf87c6989 | |||
cb591de15f | |||
eb4540e319 | |||
5d34439cb7 | |||
b19e9077af | |||
4ff010b20b | |||
442e50825c | |||
9f2801b7b9 | |||
3b0bf3a152 | |||
fe4a9ec453 | |||
dc1d14adf1 | |||
14918c16e8 | |||
a023acfa33 | |||
f098019c1f | |||
a67ca9da9b | |||
613ebf2564 | |||
dc6427770c | |||
81e0a05a1a | |||
9ea613f05f | |||
eb4a764648 | |||
baf8a3adce | |||
d9aec59352 | |||
3ff5801512 | |||
fcc2577e11 | |||
b4865f26e4 | |||
fc6d5afdd9 | |||
7a49c0b88b | |||
679c0ccd2e | |||
12098e7c41 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,4 +7,5 @@ node_modules/
|
|||||||
compile_commands.json
|
compile_commands.json
|
||||||
tests
|
tests
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.7)
|
||||||
project(libdatachannel
|
project(libdatachannel
|
||||||
VERSION 0.10.2
|
VERSION 0.11.0
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
|
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
|
||||||
|
|
||||||
@ -33,10 +33,11 @@ endif()
|
|||||||
|
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||||
|
set(BUILD_SHARED_LIBS OFF) # to force usrsctp to be built static
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||||
if (MSVC)
|
if(MSVC)
|
||||||
add_definitions(-DNOMINMAX)
|
add_definitions(-DNOMINMAX)
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||||
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
||||||
@ -57,13 +58,22 @@ set(LIBDATACHANNEL_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/log.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/log.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/message.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/message.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcp.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/logcounter.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpreceivingsession.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/track.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/track.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtppacketizationconfig.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpsenderreporter.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtppacketizer.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/opusrtppacketizer.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/opuspacketizationhandler.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtppacketizer.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/h264packetizationhandler.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
||||||
@ -82,7 +92,8 @@ set(LIBDATACHANNEL_HEADERS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcp.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcphandler.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpreceivingsession.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/include.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/include.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/init.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/init.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/log.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/log.hpp
|
||||||
@ -95,11 +106,20 @@ set(LIBDATACHANNEL_HEADERS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizationconfig.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpsenderreporter.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizer.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/opusrtppacketizer.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/opuspacketizationhandler.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtppacketizer.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264packetizationhandler.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TESTS_SOURCES
|
set(TESTS_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/turn_connectivity.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/track.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/track.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_connectivity.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_connectivity.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_track.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_track.cpp
|
||||||
@ -163,8 +183,8 @@ target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
|
|||||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp)
|
target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(datachannel PRIVATE ws2_32) # winsock2
|
target_link_libraries(datachannel PUBLIC ws2_32) # winsock2
|
||||||
target_link_libraries(datachannel-static PRIVATE ws2_32) # winsock2
|
target_link_libraries(datachannel-static PUBLIC ws2_32) # winsock2
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NO_MEDIA)
|
if(NO_MEDIA)
|
||||||
@ -272,11 +292,7 @@ if(NOT NO_TESTS)
|
|||||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||||
endif()
|
endif()
|
||||||
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
if(WIN32)
|
target_link_libraries(datachannel-tests datachannel)
|
||||||
target_link_libraries(datachannel-tests datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-tests datachannel)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Benchmark
|
# Benchmark
|
||||||
add_executable(datachannel-benchmark test/benchmark.cpp)
|
add_executable(datachannel-benchmark test/benchmark.cpp)
|
||||||
@ -288,11 +304,7 @@ if(NOT NO_TESTS)
|
|||||||
endif()
|
endif()
|
||||||
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
|
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
|
||||||
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
if(WIN32)
|
target_link_libraries(datachannel-benchmark datachannel)
|
||||||
target_link_libraries(datachannel-benchmark datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-benchmark datachannel)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
@ -300,8 +312,11 @@ if(NOT NO_EXAMPLES AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
|||||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||||
add_subdirectory(deps/json)
|
add_subdirectory(deps/json)
|
||||||
add_subdirectory(examples/client)
|
add_subdirectory(examples/client)
|
||||||
|
if(NOT NO_MEDIA)
|
||||||
add_subdirectory(examples/media)
|
add_subdirectory(examples/media)
|
||||||
add_subdirectory(examples/sfu-media)
|
add_subdirectory(examples/sfu-media)
|
||||||
|
add_subdirectory(examples/streamer)
|
||||||
|
endif()
|
||||||
add_subdirectory(examples/copy-paste)
|
add_subdirectory(examples/copy-paste)
|
||||||
add_subdirectory(examples/copy-paste-capi)
|
add_subdirectory(examples/copy-paste-capi)
|
||||||
endif()
|
endif()
|
||||||
|
8
Jamfile
8
Jamfile
@ -15,9 +15,9 @@ lib libdatachannel
|
|||||||
: # requirements
|
: # requirements
|
||||||
<cxxstd>17
|
<cxxstd>17
|
||||||
<include>./include/rtc
|
<include>./include/rtc
|
||||||
<define>USE_NICE=0
|
|
||||||
<define>RTC_ENABLE_MEDIA=0
|
<define>RTC_ENABLE_MEDIA=0
|
||||||
<define>RTC_ENABLE_WEBSOCKET=0
|
<define>RTC_ENABLE_WEBSOCKET=0
|
||||||
|
<define>USE_NICE=0
|
||||||
<toolset>msvc:<define>WIN32_LEAN_AND_MEAN
|
<toolset>msvc:<define>WIN32_LEAN_AND_MEAN
|
||||||
<toolset>msvc:<define>NOMINMAX
|
<toolset>msvc:<define>NOMINMAX
|
||||||
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS
|
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS
|
||||||
@ -32,6 +32,8 @@ lib libdatachannel
|
|||||||
<link>static
|
<link>static
|
||||||
: # usage requirements
|
: # usage requirements
|
||||||
<include>./include
|
<include>./include
|
||||||
|
<define>RTC_ENABLE_MEDIA=0
|
||||||
|
<define>RTC_ENABLE_WEBSOCKET=0
|
||||||
<library>/libdatachannel//plog
|
<library>/libdatachannel//plog
|
||||||
<toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
<toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||||
<toolset>clang:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
<toolset>clang:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||||
@ -94,7 +96,7 @@ rule make_libusrsctp ( targets * : sources * : properties * )
|
|||||||
}
|
}
|
||||||
actions make_libusrsctp
|
actions make_libusrsctp
|
||||||
{
|
{
|
||||||
(cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC -Wno-unknown-warning-option -Wno-format-truncation" .. && make -j2 usrsctp-static)
|
(cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC -Wno-unknown-warning-option -Wno-format-truncation" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 .. && make -j2 usrsctp)
|
||||||
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
|
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
|
||||||
}
|
}
|
||||||
rule make_libusrsctp_msvc ( targets * : sources * : properties * )
|
rule make_libusrsctp_msvc ( targets * : sources * : properties * )
|
||||||
@ -109,7 +111,7 @@ actions make_libusrsctp_msvc
|
|||||||
cd $(CWD)/deps/usrsctp
|
cd $(CWD)/deps/usrsctp
|
||||||
mkdir $(BUILD_DIR)
|
mkdir $(BUILD_DIR)
|
||||||
cd $(BUILD_DIR)
|
cd $(BUILD_DIR)
|
||||||
cmake -G "Visual Studio 16 2019" ..
|
cmake -G "Visual Studio 16 2019" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 ..
|
||||||
msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
|
msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
|
||||||
cd %OLDD%
|
cd %OLDD%
|
||||||
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<)
|
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<)
|
||||||
|
29
Makefile
29
Makefile
@ -10,6 +10,7 @@ LDFLAGS=-pthread
|
|||||||
LIBS=
|
LIBS=
|
||||||
LOCALLIBS=libusrsctp.a
|
LOCALLIBS=libusrsctp.a
|
||||||
USRSCTP_DIR=deps/usrsctp
|
USRSCTP_DIR=deps/usrsctp
|
||||||
|
SRTP_DIR=deps/libsrtp
|
||||||
JUICE_DIR=deps/libjuice
|
JUICE_DIR=deps/libjuice
|
||||||
PLOG_DIR=deps/plog
|
PLOG_DIR=deps/plog
|
||||||
|
|
||||||
@ -38,15 +39,22 @@ ifneq ($(USE_GNUTLS), 0)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
USE_SRTP ?= 0
|
NO_MEDIA ?= 0
|
||||||
ifneq ($(USE_SRTP), 0)
|
USE_SYSTEM_SRTP ?= 0
|
||||||
|
ifeq ($(NO_MEDIA), 0)
|
||||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=1
|
CPPFLAGS+=-DRTC_ENABLE_MEDIA=1
|
||||||
|
ifneq ($(USE_SYSTEM_SRTP), 0)
|
||||||
|
CPPFLAGS+=-DRTC_SYSTEM_SRTP=1
|
||||||
LIBS+=srtp
|
LIBS+=srtp
|
||||||
|
else
|
||||||
|
CPPFLAGS+=-DRTC_SYSTEM_SRTP=0
|
||||||
|
INCLUDES+=-I$(SRTP_DIR)/include
|
||||||
|
LOCALLIBS+=libsrtp2.a
|
||||||
|
endif
|
||||||
else
|
else
|
||||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=0
|
CPPFLAGS+=-DRTC_ENABLE_MEDIA=0
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
NO_WEBSOCKET ?= 0
|
NO_WEBSOCKET ?= 0
|
||||||
ifeq ($(NO_WEBSOCKET), 0)
|
ifeq ($(NO_WEBSOCKET), 0)
|
||||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=1
|
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=1
|
||||||
@ -54,8 +62,8 @@ else
|
|||||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=0
|
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=0
|
||||||
endif
|
endif
|
||||||
|
|
||||||
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
|
INCLUDES+=$(if $(LIBS),$(shell pkg-config --cflags $(LIBS)),)
|
||||||
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
LDLIBS+=$(LOCALLIBS) $(if $(LIBS),$(shell pkg-config --libs $(LIBS)),)
|
||||||
|
|
||||||
SRCS=$(shell printf "%s " src/*.cpp)
|
SRCS=$(shell printf "%s " src/*.cpp)
|
||||||
OBJS=$(subst .cpp,.o,$(SRCS))
|
OBJS=$(subst .cpp,.o,$(SRCS))
|
||||||
@ -73,7 +81,7 @@ test/%.o: test/%.cpp
|
|||||||
|
|
||||||
-include $(subst .cpp,.d,$(SRCS))
|
-include $(subst .cpp,.d,$(SRCS))
|
||||||
|
|
||||||
$(NAME).a: $(OBJS)
|
$(NAME).a: $(LOCALLIBS) $(OBJS)
|
||||||
$(AR) crf $@ $(OBJS)
|
$(AR) crf $@ $(OBJS)
|
||||||
|
|
||||||
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
||||||
@ -97,15 +105,22 @@ dist-clean: clean
|
|||||||
-$(RM) src/*~
|
-$(RM) src/*~
|
||||||
-$(RM) test/*~
|
-$(RM) test/*~
|
||||||
-cd $(USRSCTP_DIR) && make clean
|
-cd $(USRSCTP_DIR) && make clean
|
||||||
|
-cd $(SRTP_DIR) && make clean
|
||||||
-cd $(JUICE_DIR) && make clean
|
-cd $(JUICE_DIR) && make clean
|
||||||
|
|
||||||
libusrsctp.a:
|
libusrsctp.a:
|
||||||
cd $(USRSCTP_DIR) && \
|
cd $(USRSCTP_DIR) && \
|
||||||
./bootstrap && \
|
./bootstrap && \
|
||||||
./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS) -Wno-error=format-truncation" && \
|
./configure --enable-static --disable-debug CFLAGS="-fPIC" && \
|
||||||
make
|
make
|
||||||
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
||||||
|
|
||||||
|
libsrtp2.a:
|
||||||
|
cd $(SRTP_DIR) && \
|
||||||
|
./configure && \
|
||||||
|
make
|
||||||
|
cp $(SRTP_DIR)/libsrtp2.a .
|
||||||
|
|
||||||
libjuice.a:
|
libjuice.a:
|
||||||
ifneq ($(USE_GNUTLS), 0)
|
ifneq ($(USE_GNUTLS), 0)
|
||||||
cd $(JUICE_DIR) && make USE_NETTLE=1
|
cd $(JUICE_DIR) && make USE_NETTLE=1
|
||||||
|
10
README.md
10
README.md
@ -21,15 +21,15 @@ Protocol stack:
|
|||||||
- SCTP-based Data Channels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13))
|
- 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))
|
- 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))
|
- 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))
|
- ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)) with STUN ([RFC8489](https://tools.ietf.org/html/rfc8489)) and its extension TURN ([RFC8656](https://tools.ietf.org/html/rfc8656))
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
- Full IPv6 support
|
- Full IPv6 support
|
||||||
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
|
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
|
||||||
- JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26))
|
- JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26))
|
||||||
- SRTP and SRTCP key derivation from DTLS ([RFC5764](https://tools.ietf.org/html/rfc5764))
|
|
||||||
- Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
|
- Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
|
||||||
- TURN relaying ([RFC5766](https://tools.ietf.org/html/rfc5766)) with [libnice](https://github.com/libnice/libnice) as ICE backend
|
- SRTP and SRTCP key derivation from DTLS ([RFC5764](https://tools.ietf.org/html/rfc5764))
|
||||||
|
- Differentiated Services QoS ([draft-ietf-tsvwg-rtcweb-qos-18](https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18))
|
||||||
|
|
||||||
Note only SDP BUNDLE mode is supported for media multiplexing ([draft-ietf-mmusic-sdp-bundle-negotiation-54](https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54)). The behavior is equivalent to the JSEP bundle-only policy: the library always negociates one unique network component, where SRTP media streams are multiplexed with SRTCP control packets ([RFC5761](https://tools.ietf.org/html/rfc5761)) and SCTP/DTLS data traffic ([RFC5764](https://tools.ietf.org/html/rfc5764)).
|
Note only SDP BUNDLE mode is supported for media multiplexing ([draft-ietf-mmusic-sdp-bundle-negotiation-54](https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54)). The behavior is equivalent to the JSEP bundle-only policy: the library always negociates one unique network component, where SRTP media streams are multiplexed with SRTCP control packets ([RFC5761](https://tools.ietf.org/html/rfc5761)) and SCTP/DTLS data traffic ([RFC5764](https://tools.ietf.org/html/rfc5764)).
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ $ git submodule update --init --recursive
|
|||||||
|
|
||||||
The CMake library targets `libdatachannel` and `libdatachannel-static` respectively correspond to the shared and static libraries. The default target will build tests and examples. The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
The CMake library targets `libdatachannel` and `libdatachannel-static` respectively correspond to the shared and static libraries. The default target will build tests and examples. The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
||||||
|
|
||||||
On Windows, the DLL resulting from the shared library build only exposes the C API, use the static library for the C++ API.
|
If you only need Data Channels, the option `NO_MEDIA` allows to make the library lighter by removing media support. Similarly, `NO_WEBSOCKET` removes WebSocket support.
|
||||||
|
|
||||||
#### POSIX-compliant operating systems (including Linux and Apple macOS)
|
#### POSIX-compliant operating systems (including Linux and Apple macOS)
|
||||||
```bash
|
```bash
|
||||||
@ -123,6 +123,8 @@ $ nmake
|
|||||||
|
|
||||||
The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
||||||
|
|
||||||
|
If you only need Data Channels, the option `NO_MEDIA` removes media support. Similarly, `NO_WEBSOCKET` removes WebSocket support.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ make USE_GNUTLS=1 USE_NICE=0
|
$ make USE_GNUTLS=1 USE_NICE=0
|
||||||
```
|
```
|
||||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: 7afe3940dd...7a9f10e4f7
2
deps/libsrtp
vendored
2
deps/libsrtp
vendored
Submodule deps/libsrtp updated: d02d21111e...f379f48412
@ -8,6 +8,7 @@ This directory contains different WebRTC clients and compatible WebSocket + JSON
|
|||||||
- [signaling-server-rust](signaling-server-rust) contains a similar signaling server in Rust (see [lerouxrgd/datachannel-rs](https://github.com/lerouxrgd/datachannel-rs) for Rust wrappers)
|
- [signaling-server-rust](signaling-server-rust) contains a similar signaling server in Rust (see [lerouxrgd/datachannel-rs](https://github.com/lerouxrgd/datachannel-rs) for Rust wrappers)
|
||||||
|
|
||||||
- [media](media) is a copy/paste demo to send the webcam from your browser into gstreamer.
|
- [media](media) is a copy/paste demo to send the webcam from your browser into gstreamer.
|
||||||
|
- [streamer](streamer) streams h264 and opus samples to web browsers (signaling-server-python is required).
|
||||||
|
|
||||||
Additionally, it contains two debugging tools for libdatachannel with copy-pasting as signaling:
|
Additionally, it contains two debugging tools for libdatachannel with copy-pasting as signaling:
|
||||||
- [copy-paste](copy-paste) using the C++ API
|
- [copy-paste](copy-paste) using the C++ API
|
||||||
|
@ -9,14 +9,9 @@ target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
|
|||||||
else()
|
else()
|
||||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
|
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_target_properties(datachannel-client PROPERTIES
|
set_target_properties(datachannel-client PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME client)
|
OUTPUT_NAME client)
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(datachannel-client datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-client datachannel)
|
|
||||||
endif()
|
|
||||||
target_link_libraries(datachannel-client datachannel nlohmann_json)
|
target_link_libraries(datachannel-client datachannel nlohmann_json)
|
||||||
|
|
||||||
|
@ -23,17 +23,18 @@
|
|||||||
|
|
||||||
#include "rtc/rtc.hpp"
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
#include "parse_cl.h"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <future>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <thread>
|
|
||||||
#include <future>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "parse_cl.h"
|
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -47,25 +48,23 @@ unordered_map<string, shared_ptr<PeerConnection>> peerConnectionMap;
|
|||||||
unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
|
unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
|
||||||
|
|
||||||
string localId;
|
string localId;
|
||||||
bool echoDataChannelMessages = false;
|
|
||||||
|
|
||||||
shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||||
weak_ptr<WebSocket> wws, string id);
|
weak_ptr<WebSocket> wws, string id);
|
||||||
void printReceived(bool echoed, string id, string type, size_t length);
|
|
||||||
string randomId(size_t length);
|
string randomId(size_t length);
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv) try {
|
int main(int argc, char **argv) try {
|
||||||
auto params = std::make_unique<Cmdline>(argc, argv);
|
auto params = std::make_unique<Cmdline>(argc, argv);
|
||||||
|
|
||||||
rtc::InitLogger(LogLevel::Debug);
|
rtc::InitLogger(LogLevel::Info);
|
||||||
|
|
||||||
Configuration config;
|
Configuration config;
|
||||||
string stunServer = "";
|
string stunServer = "";
|
||||||
if (params->noStun()) {
|
if (params->noStun()) {
|
||||||
cout << "No STUN server is configured. Only local hosts and public IP addresses supported." << endl;
|
cout << "No STUN server is configured. Only local hosts and public IP addresses supported."
|
||||||
|
<< endl;
|
||||||
} else {
|
} else {
|
||||||
if (params->stunServer().substr(0,5).compare("stun:") != 0) {
|
if (params->stunServer().substr(0, 5).compare("stun:") != 0) {
|
||||||
stunServer = "stun:";
|
stunServer = "stun:";
|
||||||
}
|
}
|
||||||
stunServer += params->stunServer() + ":" + to_string(params->stunPort());
|
stunServer += params->stunServer() + ":" + to_string(params->stunPort());
|
||||||
@ -76,10 +75,6 @@ int main(int argc, char **argv) try {
|
|||||||
localId = randomId(4);
|
localId = randomId(4);
|
||||||
cout << "The local ID is: " << localId << endl;
|
cout << "The local ID is: " << localId << endl;
|
||||||
|
|
||||||
echoDataChannelMessages = params->echoDataChannelMessages();
|
|
||||||
cout << "Received data channel messages will be "
|
|
||||||
<< (echoDataChannelMessages ? "echoed back to sender" : "printed to stdout") << endl;
|
|
||||||
|
|
||||||
auto ws = make_shared<WebSocket>();
|
auto ws = make_shared<WebSocket>();
|
||||||
|
|
||||||
std::promise<void> wsPromise;
|
std::promise<void> wsPromise;
|
||||||
@ -134,11 +129,11 @@ int main(int argc, char **argv) try {
|
|||||||
});
|
});
|
||||||
|
|
||||||
string wsPrefix = "";
|
string wsPrefix = "";
|
||||||
if (params->webSocketServer().substr(0,5).compare("ws://") != 0) {
|
if (params->webSocketServer().substr(0, 5).compare("ws://") != 0) {
|
||||||
wsPrefix = "ws://";
|
wsPrefix = "ws://";
|
||||||
}
|
}
|
||||||
const string url = wsPrefix + params->webSocketServer() + ":" +
|
const string url = wsPrefix + params->webSocketServer() + ":" +
|
||||||
to_string(params->webSocketPort()) + "/" + localId;
|
to_string(params->webSocketPort()) + "/" + localId;
|
||||||
cout << "Url is " << url << endl;
|
cout << "Url is " << url << endl;
|
||||||
ws->open(url);
|
ws->open(url);
|
||||||
|
|
||||||
@ -171,20 +166,12 @@ int main(int argc, char **argv) try {
|
|||||||
|
|
||||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||||
|
|
||||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
|
dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
|
||||||
static bool firstMessage = true;
|
if (holds_alternative<string>(data))
|
||||||
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
|
cout << "Message from " << id << " received: " << get<string>(data) << endl;
|
||||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
else
|
||||||
firstMessage = false;
|
cout << "Binary message from " << id
|
||||||
} else if (echoDataChannelMessages) {
|
<< " received, size=" << get<binary>(data).size() << endl;
|
||||||
bool echoed = false;
|
|
||||||
if (auto dc = wdc.lock()) {
|
|
||||||
dc->send(message);
|
|
||||||
echoed = true;
|
|
||||||
}
|
|
||||||
printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
|
||||||
get<string>(message).length());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dataChannelMap.emplace(id, dc);
|
dataChannelMap.emplace(id, dc);
|
||||||
@ -237,20 +224,12 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
|||||||
|
|
||||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||||
|
|
||||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
|
dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
|
||||||
static bool firstMessage = true;
|
if (holds_alternative<string>(data))
|
||||||
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
|
cout << "Message from " << id << " received: " << get<string>(data) << endl;
|
||||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
else
|
||||||
firstMessage = false;
|
cout << "Binary message from " << id
|
||||||
} else if (echoDataChannelMessages) {
|
<< " received, size=" << get<binary>(data).size() << endl;
|
||||||
bool echoed = false;
|
|
||||||
if (auto dc = wdc.lock()) {
|
|
||||||
dc->send(message);
|
|
||||||
echoed = true;
|
|
||||||
}
|
|
||||||
printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
|
||||||
get<string>(message).length());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dc->send("Hello from " + localId);
|
dc->send("Hello from " + localId);
|
||||||
@ -262,20 +241,6 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
|||||||
return pc;
|
return pc;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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 " << 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) {
|
|
||||||
freq *= 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to generate a random ID
|
// Helper function to generate a random ID
|
||||||
string randomId(size_t length) {
|
string randomId(size_t length) {
|
||||||
static const string characters(
|
static const string characters(
|
||||||
|
@ -43,38 +43,30 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
|
|||||||
|
|
||||||
static struct option long_options[] =
|
static struct option long_options[] =
|
||||||
{
|
{
|
||||||
{"echo", no_argument, NULL, 'e'},
|
|
||||||
{"noStun", no_argument, NULL, 'n'},
|
{"noStun", no_argument, NULL, 'n'},
|
||||||
{"stunServer", required_argument, NULL, 's'},
|
{"stunServer", required_argument, NULL, 's'},
|
||||||
{"stunPort", required_argument, NULL, 't'},
|
{"stunPort", required_argument, NULL, 't'},
|
||||||
{"webSocketServer", required_argument, NULL, 'w'},
|
{"webSocketServer", required_argument, NULL, 'w'},
|
||||||
{"webSocketPort", required_argument, NULL, 'x'},
|
{"webSocketPort", required_argument, NULL, 'x'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"version", no_argument, NULL, 'v'},
|
|
||||||
{NULL, 0, NULL, 0}
|
{NULL, 0, NULL, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
_program_name += argv[0];
|
_program_name += argv[0];
|
||||||
|
|
||||||
/* default values */
|
/* default values */
|
||||||
_e = false;
|
|
||||||
_n = false;
|
_n = false;
|
||||||
_s = "stun.l.google.com";
|
_s = "stun.l.google.com";
|
||||||
_t = 19302;
|
_t = 19302;
|
||||||
_w = "localhost";
|
_w = "localhost";
|
||||||
_x = 8000;
|
_x = 8000;
|
||||||
_h = false;
|
_h = false;
|
||||||
_v = false;
|
|
||||||
|
|
||||||
optind = 0;
|
optind = 0;
|
||||||
while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1)
|
while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1)
|
||||||
{
|
{
|
||||||
switch (c)
|
switch (c)
|
||||||
{
|
{
|
||||||
case 'e':
|
|
||||||
_e = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'n':
|
case 'n':
|
||||||
_n = true;
|
_n = true;
|
||||||
break;
|
break;
|
||||||
@ -124,11 +116,6 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
|
|||||||
this->usage (EXIT_SUCCESS);
|
this->usage (EXIT_SUCCESS);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'v':
|
|
||||||
_v = true;
|
|
||||||
this->version (EXIT_SUCCESS);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
this->usage (EXIT_FAILURE);
|
this->usage (EXIT_FAILURE);
|
||||||
|
|
||||||
@ -155,8 +142,6 @@ void Cmdline::usage (int status)
|
|||||||
std::cout << "\
|
std::cout << "\
|
||||||
usage: " << _program_name << " [ -enstwxhv ] \n\
|
usage: " << _program_name << " [ -enstwxhv ] \n\
|
||||||
libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\
|
libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\
|
||||||
[ -e ] [ --echo ] (type=FLAG)\n\
|
|
||||||
Echo data channel messages back to sender rather than putting to stdout.\n\
|
|
||||||
[ -n ] [ --noStun ] (type=FLAG)\n\
|
[ -n ] [ --noStun ] (type=FLAG)\n\
|
||||||
Do NOT use a stun server (overrides -s and -t).\n\
|
Do NOT use a stun server (overrides -s and -t).\n\
|
||||||
[ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\
|
[ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\
|
||||||
@ -168,15 +153,8 @@ libdatachannel client implementing WebRTC Data Channels with WebSocket signaling
|
|||||||
[ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\
|
[ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\
|
||||||
Web socket server port.\n\
|
Web socket server port.\n\
|
||||||
[ -h ] [ --help ] (type=FLAG)\n\
|
[ -h ] [ --help ] (type=FLAG)\n\
|
||||||
Display this help and exit.\n\
|
Display this help and exit.\n";
|
||||||
[ -v ] [ --version ] (type=FLAG)\n\
|
|
||||||
Output version information and exit.\n";
|
|
||||||
}
|
}
|
||||||
exit (status);
|
exit (status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cmdline::version (int status)
|
|
||||||
{
|
|
||||||
std::cout << _program_name << " v0.5\n";
|
|
||||||
exit (status);
|
|
||||||
}
|
|
||||||
|
@ -34,14 +34,12 @@ class Cmdline
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
/* parameters */
|
/* parameters */
|
||||||
bool _e;
|
|
||||||
bool _n;
|
bool _n;
|
||||||
std::string _s;
|
std::string _s;
|
||||||
int _t;
|
int _t;
|
||||||
std::string _w;
|
std::string _w;
|
||||||
int _x;
|
int _x;
|
||||||
bool _h;
|
bool _h;
|
||||||
bool _v;
|
|
||||||
|
|
||||||
/* other stuff to keep track of */
|
/* other stuff to keep track of */
|
||||||
std::string _program_name;
|
std::string _program_name;
|
||||||
@ -55,20 +53,15 @@ public:
|
|||||||
/* usage function */
|
/* usage function */
|
||||||
void usage (int status);
|
void usage (int status);
|
||||||
|
|
||||||
/* version function */
|
|
||||||
void version (int status);
|
|
||||||
|
|
||||||
/* return next (non-option) parameter */
|
/* return next (non-option) parameter */
|
||||||
int next_param () { return _optind; }
|
int next_param () { return _optind; }
|
||||||
|
|
||||||
bool echoDataChannelMessages () const { return _e; }
|
|
||||||
bool noStun () const { return _n; }
|
bool noStun () const { return _n; }
|
||||||
std::string stunServer () const { return _s; }
|
std::string stunServer () const { return _s; }
|
||||||
int stunPort () const { return _t; }
|
int stunPort () const { return _t; }
|
||||||
std::string webSocketServer () const { return _w; }
|
std::string webSocketServer () const { return _w; }
|
||||||
int webSocketPort () const { return _x; }
|
int webSocketPort () const { return _x; }
|
||||||
bool h () const { return _h; }
|
bool h () const { return _h; }
|
||||||
bool v () const { return _v; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -137,7 +137,7 @@ int main(int argc, char **argv) {
|
|||||||
size_t candidate_size = 0;
|
size_t candidate_size = 0;
|
||||||
|
|
||||||
if (getline(&candidate, &candidate_size, stdin)) {
|
if (getline(&candidate, &candidate_size, stdin)) {
|
||||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
rtcAddRemoteCandidate(peer->pc, candidate, NULL);
|
||||||
free(candidate);
|
free(candidate);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,7 +140,7 @@ int main(int argc, char **argv) {
|
|||||||
char *candidate = NULL;
|
char *candidate = NULL;
|
||||||
size_t candidate_size = 0;
|
size_t candidate_size = 0;
|
||||||
if (getline(&candidate, &candidate_size, stdin)) {
|
if (getline(&candidate, &candidate_size, stdin)) {
|
||||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
rtcAddRemoteCandidate(peer->pc, candidate, NULL);
|
||||||
free(candidate);
|
free(candidate);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,19 +4,11 @@ add_executable(datachannel-copy-paste-offerer offerer.cpp)
|
|||||||
set_target_properties(datachannel-copy-paste-offerer PROPERTIES
|
set_target_properties(datachannel-copy-paste-offerer PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME offerer)
|
OUTPUT_NAME offerer)
|
||||||
if(WIN32)
|
target_link_libraries(datachannel-copy-paste-offerer datachannel)
|
||||||
target_link_libraries(datachannel-copy-paste-offerer datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-copy-paste-offerer datachannel)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(datachannel-copy-paste-answerer answerer.cpp)
|
add_executable(datachannel-copy-paste-answerer answerer.cpp)
|
||||||
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
|
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME answerer)
|
OUTPUT_NAME answerer)
|
||||||
if(WIN32)
|
target_link_libraries(datachannel-copy-paste-answerer datachannel)
|
||||||
target_link_libraries(datachannel-copy-paste-answerer datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-copy-paste-answerer datachannel)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
@ -4,11 +4,5 @@ add_executable(datachannel-media main.cpp)
|
|||||||
set_target_properties(datachannel-media PROPERTIES
|
set_target_properties(datachannel-media PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME media)
|
OUTPUT_NAME media)
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(datachannel-media datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-media datachannel)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_link_libraries(datachannel-media datachannel nlohmann_json)
|
target_link_libraries(datachannel-media datachannel nlohmann_json)
|
||||||
|
|
||||||
|
@ -4,12 +4,5 @@ add_executable(datachannel-sfu-media main.cpp)
|
|||||||
set_target_properties(datachannel-sfu-media PROPERTIES
|
set_target_properties(datachannel-sfu-media PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME sfu-media)
|
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)
|
target_link_libraries(datachannel-sfu-media datachannel nlohmann_json)
|
||||||
|
|
||||||
|
75
examples/streamer/ArgParser.cpp
Normal file
75
examples/streamer/ArgParser.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ArgParser.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
ArgParser::ArgParser(std::vector<std::pair<std::string, std::string>> options, std::vector<std::pair<std::string, std::string>> flags) {
|
||||||
|
for(auto option: options) {
|
||||||
|
this->options.insert(option.first);
|
||||||
|
this->options.insert(option.second);
|
||||||
|
shortToLongMap.emplace(option.first, option.second);
|
||||||
|
shortToLongMap.emplace(option.second, option.second);
|
||||||
|
}
|
||||||
|
for(auto flag: flags) {
|
||||||
|
this->flags.insert(flag.first);
|
||||||
|
this->flags.insert(flag.second);
|
||||||
|
shortToLongMap.emplace(flag.first, flag.second);
|
||||||
|
shortToLongMap.emplace(flag.second, flag.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> ArgParser::toKey(std::string prefixedKey) {
|
||||||
|
if (prefixedKey.find("--") == 0) {
|
||||||
|
return prefixedKey.substr(2, prefixedKey.length());
|
||||||
|
} else if (prefixedKey.find("-") == 0) {
|
||||||
|
return prefixedKey.substr(1, prefixedKey.length());
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArgParser::parse(int argc, char **argv, std::function<bool (std::string, std::string)> onOption, std::function<bool (std::string)> onFlag) {
|
||||||
|
std::optional<std::string> currentOption = std::nullopt;
|
||||||
|
for(int i = 1; i < argc; i++) {
|
||||||
|
std::string current = argv[i];
|
||||||
|
auto optKey = toKey(current);
|
||||||
|
if (!currentOption.has_value() && optKey.has_value() && flags.find(optKey.value()) != flags.end()) {
|
||||||
|
auto check = onFlag(shortToLongMap.at(optKey.value()));
|
||||||
|
if (!check) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!currentOption.has_value() && optKey.has_value() && options.find(optKey.value()) != options.end()) {
|
||||||
|
currentOption = optKey.value();
|
||||||
|
} else if (currentOption.has_value()) {
|
||||||
|
auto check = onOption(shortToLongMap.at(currentOption.value()), current);
|
||||||
|
if (!check) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOption = std::nullopt;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Unrecognized option " << current << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentOption.has_value()) {
|
||||||
|
std::cerr << "Missing value for " << currentOption.value() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
41
examples/streamer/ArgParser.hpp
Normal file
41
examples/streamer/ArgParser.hpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ArgParser_hpp
|
||||||
|
#define ArgParser_hpp
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
struct ArgParser {
|
||||||
|
private:
|
||||||
|
std::set<std::string> options{};
|
||||||
|
std::set<std::string> flags{};
|
||||||
|
std::unordered_map<std::string, std::string> shortToLongMap{};
|
||||||
|
public:
|
||||||
|
ArgParser(std::vector<std::pair<std::string, std::string>> options, std::vector<std::pair<std::string, std::string>> flags);
|
||||||
|
std::optional<std::string> toKey(std::string prefixedKey);
|
||||||
|
bool parse(int argc, char **argv, std::function<bool (std::string, std::string)> onOption, std::function<bool (std::string)> onFlag);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* ArgParser_hpp */
|
19
examples/streamer/CMakeLists.txt
Normal file
19
examples/streamer/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
if(POLICY CMP0079)
|
||||||
|
cmake_policy(SET CMP0079 NEW)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_executable(streamer main.cpp dispatchqueue.cpp dispatchqueue.hpp h264fileparser.cpp h264fileparser.hpp helpers.cpp helpers.hpp opusfileparser.cpp opusfileparser.hpp fileparser.cpp fileparser.hpp stream.cpp stream.hpp ArgParser.hpp ArgParser.cpp)
|
||||||
|
target_compile_definitions(streamer PUBLIC STATIC_GETOPT)
|
||||||
|
else()
|
||||||
|
add_executable(streamer main.cpp dispatchqueue.cpp dispatchqueue.hpp h264fileparser.cpp h264fileparser.hpp helpers.cpp helpers.hpp opusfileparser.cpp opusfileparser.hpp fileparser.cpp fileparser.hpp stream.cpp stream.hpp ArgParser.hpp ArgParser.cpp)
|
||||||
|
endif()
|
||||||
|
set_target_properties(streamer PROPERTIES
|
||||||
|
CXX_STANDARD 17
|
||||||
|
OUTPUT_NAME streamer)
|
||||||
|
|
||||||
|
target_link_libraries(streamer datachannel)
|
||||||
|
|
||||||
|
target_link_libraries(streamer datachannel nlohmann_json)
|
||||||
|
|
32
examples/streamer/README.md
Normal file
32
examples/streamer/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Streaming H264 and opus
|
||||||
|
|
||||||
|
This example streams H264 and opus<sup id="a1">[1](#f1)</sup> samples to the connected browser client.
|
||||||
|
|
||||||
|
## Starting signaling server
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ python3 ../signaling-server-python/signaling-server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Starting php
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ php -S 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can open demo at [127.0.0.1:8080](127.0.0.1:8080).
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
- `-a` Directory with OPUS samples (default: *../../../../examples/streamer/samples/opus/*).
|
||||||
|
- `-b` Directory with H264 samples (default: *../../../../examples/streamer/samples/h264/*).
|
||||||
|
- `-d` Signaling server IP address (default: 127.0.0.1).
|
||||||
|
- `-p` Signaling server port (default: 8000).
|
||||||
|
- `-v` Enable debug logs.
|
||||||
|
- `-h` Print this help and exit.
|
||||||
|
|
||||||
|
## Generating H264 and Opus samples
|
||||||
|
|
||||||
|
You can generate H264 and Opus sample with *samples/generate_h264.py* and *samples/generate_opus.py* respectively. This require ffmpeg, python3 and kaitaistruct library to be installed. Use `-h`/`--help` to learn more about arguments.
|
||||||
|
|
||||||
|
<b id="f1">1</b> Opus samples are generated from music downloaded at [bensound](https://www.bensound.com). [↩](#a1)
|
209
examples/streamer/client.js
Normal file
209
examples/streamer/client.js
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
/** @type {RTCPeerConnection} */
|
||||||
|
let rtc;
|
||||||
|
const iceConnectionLog = document.getElementById('ice-connection-state'),
|
||||||
|
iceGatheringLog = document.getElementById('ice-gathering-state'),
|
||||||
|
signalingLog = document.getElementById('signaling-state'),
|
||||||
|
dataChannelLog = document.getElementById('data-channel');
|
||||||
|
|
||||||
|
function randomString(len) {
|
||||||
|
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let randomString = '';
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const randomPoz = Math.floor(Math.random() * charSet.length);
|
||||||
|
randomString += charSet.substring(randomPoz, randomPoz + 1);
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const receiveID = randomString(10);
|
||||||
|
const websocket = new WebSocket('ws://127.0.0.1:8000/' + receiveID);
|
||||||
|
websocket.onopen = function () {
|
||||||
|
document.getElementById('start').disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// data channel
|
||||||
|
let dc = null, dcTimeout = null;
|
||||||
|
|
||||||
|
function createPeerConnection() {
|
||||||
|
const config = {
|
||||||
|
sdpSemantics: 'unified-plan',
|
||||||
|
bundlePolicy: "max-bundle",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.getElementById('use-stun').checked) {
|
||||||
|
config.iceServers = [{urls: ['stun:stun.l.google.com:19302']}];
|
||||||
|
}
|
||||||
|
|
||||||
|
let pc = new RTCPeerConnection(config);
|
||||||
|
|
||||||
|
// register some listeners to help debugging
|
||||||
|
pc.addEventListener('icegatheringstatechange', function () {
|
||||||
|
iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState;
|
||||||
|
}, false);
|
||||||
|
iceGatheringLog.textContent = pc.iceGatheringState;
|
||||||
|
|
||||||
|
pc.addEventListener('iceconnectionstatechange', function () {
|
||||||
|
iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState;
|
||||||
|
}, false);
|
||||||
|
iceConnectionLog.textContent = pc.iceConnectionState;
|
||||||
|
|
||||||
|
pc.addEventListener('signalingstatechange', function () {
|
||||||
|
signalingLog.textContent += ' -> ' + pc.signalingState;
|
||||||
|
}, false);
|
||||||
|
signalingLog.textContent = pc.signalingState;
|
||||||
|
|
||||||
|
// connect audio / video
|
||||||
|
pc.addEventListener('track', function (evt) {
|
||||||
|
if (evt.track.kind == 'video') {
|
||||||
|
document.getElementById('media').style.display = 'block';
|
||||||
|
document.getElementById('video').srcObject = evt.streams[0];
|
||||||
|
} else {
|
||||||
|
document.getElementById('audio').srcObject = evt.streams[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let time_start = null;
|
||||||
|
|
||||||
|
function current_stamp() {
|
||||||
|
if (time_start === null) {
|
||||||
|
time_start = new Date().getTime();
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return new Date().getTime() - time_start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.ondatachannel = function (event) {
|
||||||
|
dc = event.channel;
|
||||||
|
dc.onopen = function () {
|
||||||
|
dataChannelLog.textContent += '- open\n';
|
||||||
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||||
|
};
|
||||||
|
dc.onmessage = function (evt) {
|
||||||
|
|
||||||
|
dataChannelLog.textContent += '< ' + evt.data + '\n';
|
||||||
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||||
|
|
||||||
|
dcTimeout = setTimeout(function () {
|
||||||
|
if (dc == null && dcTimeout != null) {
|
||||||
|
dcTimeout = null;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const message = 'Pong ' + current_stamp();
|
||||||
|
dataChannelLog.textContent += '> ' + message + '\n';
|
||||||
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||||
|
dc.send(message);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
dc.onclose = function () {
|
||||||
|
clearTimeout(dcTimeout);
|
||||||
|
dcTimeout = null;
|
||||||
|
dataChannelLog.textContent += '- close\n';
|
||||||
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendAnswer(pc) {
|
||||||
|
return pc.createAnswer()
|
||||||
|
.then((answer) => rtc.setLocalDescription(answer))
|
||||||
|
.then(function () {
|
||||||
|
// wait for ICE gathering to complete
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
if (pc.iceGatheringState === 'complete') {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
function checkState() {
|
||||||
|
if (pc.iceGatheringState === 'complete') {
|
||||||
|
pc.removeEventListener('icegatheringstatechange', checkState);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.addEventListener('icegatheringstatechange', checkState);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
|
const answer = pc.localDescription;
|
||||||
|
|
||||||
|
document.getElementById('answer-sdp').textContent = answer.sdp;
|
||||||
|
|
||||||
|
return websocket.send(JSON.stringify(
|
||||||
|
{
|
||||||
|
id: "server",
|
||||||
|
type: answer.type,
|
||||||
|
sdp: answer.sdp,
|
||||||
|
}));
|
||||||
|
}).catch(function (e) {
|
||||||
|
alert(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOffer(offer) {
|
||||||
|
rtc = createPeerConnection();
|
||||||
|
return rtc.setRemoteDescription(offer)
|
||||||
|
.then(() => sendAnswer(rtc));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendStreamRequest() {
|
||||||
|
websocket.send(JSON.stringify(
|
||||||
|
{
|
||||||
|
id: "server",
|
||||||
|
type: "streamRequest",
|
||||||
|
receiver: receiveID,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
document.getElementById('start').style.display = 'none';
|
||||||
|
document.getElementById('stop').style.display = 'inline-block';
|
||||||
|
document.getElementById('media').style.display = 'block';
|
||||||
|
sendStreamRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
document.getElementById('stop').style.display = 'none';
|
||||||
|
document.getElementById('media').style.display = 'none';
|
||||||
|
document.getElementById('start').style.display = 'inline-block';
|
||||||
|
|
||||||
|
// close data channel
|
||||||
|
if (dc) {
|
||||||
|
dc.close();
|
||||||
|
dc = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close transceivers
|
||||||
|
if (rtc.getTransceivers) {
|
||||||
|
rtc.getTransceivers().forEach(function (transceiver) {
|
||||||
|
if (transceiver.stop) {
|
||||||
|
transceiver.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// close local audio / video
|
||||||
|
rtc.getSenders().forEach(function (sender) {
|
||||||
|
const track = sender.track;
|
||||||
|
if (track !== null) {
|
||||||
|
sender.track.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// close peer connection
|
||||||
|
setTimeout(function () {
|
||||||
|
rtc.close();
|
||||||
|
rtc = null;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
websocket.onmessage = async function (evt) {
|
||||||
|
const received_msg = evt.data;
|
||||||
|
const object = JSON.parse(received_msg);
|
||||||
|
if (object.type == "offer") {
|
||||||
|
document.getElementById('offer-sdp').textContent = object.sdp;
|
||||||
|
await handleOffer(object)
|
||||||
|
}
|
||||||
|
}
|
94
examples/streamer/dispatchqueue.cpp
Normal file
94
examples/streamer/dispatchqueue.cpp
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "dispatchqueue.hpp"
|
||||||
|
|
||||||
|
DispatchQueue::DispatchQueue(std::string name, size_t threadCount) :
|
||||||
|
name{std::move(name)}, threads(threadCount) {
|
||||||
|
for(size_t i = 0; i < threads.size(); i++)
|
||||||
|
{
|
||||||
|
threads[i] = std::thread(&DispatchQueue::dispatchThreadHandler, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue::~DispatchQueue() {
|
||||||
|
// Signal to dispatch threads that it's time to wrap up
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
quit = true;
|
||||||
|
lock.unlock();
|
||||||
|
condition.notify_all();
|
||||||
|
|
||||||
|
// Wait for threads to finish before we exit
|
||||||
|
for(size_t i = 0; i < threads.size(); i++)
|
||||||
|
{
|
||||||
|
if(threads[i].joinable())
|
||||||
|
{
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DispatchQueue::removePending() {
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
queue = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void DispatchQueue::dispatch(const fp_t& op) {
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
queue.push(op);
|
||||||
|
|
||||||
|
// Manual unlocking is done before notifying, to avoid waking up
|
||||||
|
// the waiting thread only to block again (see notify_one for details)
|
||||||
|
lock.unlock();
|
||||||
|
condition.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DispatchQueue::dispatch(fp_t&& op) {
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
queue.push(std::move(op));
|
||||||
|
|
||||||
|
// Manual unlocking is done before notifying, to avoid waking up
|
||||||
|
// the waiting thread only to block again (see notify_one for details)
|
||||||
|
lock.unlock();
|
||||||
|
condition.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DispatchQueue::dispatchThreadHandler(void) {
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
do {
|
||||||
|
//Wait until we have data or a quit signal
|
||||||
|
condition.wait(lock, [this]{
|
||||||
|
return (queue.size() || quit);
|
||||||
|
});
|
||||||
|
|
||||||
|
//after wait, we own the lock
|
||||||
|
if(!quit && queue.size())
|
||||||
|
{
|
||||||
|
auto op = std::move(queue.front());
|
||||||
|
queue.pop();
|
||||||
|
|
||||||
|
//unlock now that we're done messing with the queue
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
op();
|
||||||
|
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
} while (!quit);
|
||||||
|
}
|
59
examples/streamer/dispatchqueue.hpp
Normal file
59
examples/streamer/dispatchqueue.hpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef dispatchqueue_hpp
|
||||||
|
#define dispatchqueue_hpp
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <queue>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class DispatchQueue {
|
||||||
|
typedef std::function<void(void)> fp_t;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DispatchQueue(std::string name, size_t threadCount = 1);
|
||||||
|
~DispatchQueue();
|
||||||
|
|
||||||
|
// dispatch and copy
|
||||||
|
void dispatch(const fp_t& op);
|
||||||
|
// dispatch and move
|
||||||
|
void dispatch(fp_t&& op);
|
||||||
|
|
||||||
|
void removePending();
|
||||||
|
|
||||||
|
// Deleted operations
|
||||||
|
DispatchQueue(const DispatchQueue& rhs) = delete;
|
||||||
|
DispatchQueue& operator=(const DispatchQueue& rhs) = delete;
|
||||||
|
DispatchQueue(DispatchQueue&& rhs) = delete;
|
||||||
|
DispatchQueue& operator=(DispatchQueue&& rhs) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name;
|
||||||
|
std::mutex lockMutex;
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
std::queue<fp_t> queue;
|
||||||
|
std::condition_variable condition;
|
||||||
|
bool quit = false;
|
||||||
|
|
||||||
|
void dispatchThreadHandler(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* dispatchqueue_hpp */
|
59
examples/streamer/fileparser.cpp
Normal file
59
examples/streamer/fileparser.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fileparser.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
FileParser::FileParser(string directory, string extension, uint32_t samplesPerSecond, bool loop): sampleDuration_us(1000 * 1000 / samplesPerSecond), StreamSource() {
|
||||||
|
this->directory = directory;
|
||||||
|
this->extension = extension;
|
||||||
|
this->loop = loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileParser::start() {
|
||||||
|
sampleTime_us = -sampleDuration_us;
|
||||||
|
loadNextSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileParser::stop() {
|
||||||
|
StreamSource::stop();
|
||||||
|
counter = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileParser::loadNextSample() {
|
||||||
|
string frame_id = to_string(++counter);
|
||||||
|
|
||||||
|
string url = directory + "/sample-" + frame_id + extension;
|
||||||
|
ifstream source(url, ios_base::binary);
|
||||||
|
if (!source) {
|
||||||
|
if (loop && counter > 0) {
|
||||||
|
loopTimestampOffset = sampleTime_us;
|
||||||
|
counter = -1;
|
||||||
|
loadNextSample();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sample = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<uint8_t> fileContents((std::istreambuf_iterator<char>(source)), std::istreambuf_iterator<char>());
|
||||||
|
sample = *reinterpret_cast<vector<byte> *>(&fileContents);
|
||||||
|
sampleTime_us += sampleDuration_us;
|
||||||
|
}
|
40
examples/streamer/fileparser.hpp
Normal file
40
examples/streamer/fileparser.hpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef fileparser_hpp
|
||||||
|
#define fileparser_hpp
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "stream.hpp"
|
||||||
|
|
||||||
|
class FileParser: public StreamSource {
|
||||||
|
std::string directory;
|
||||||
|
std::string extension;
|
||||||
|
uint32_t counter = -1;
|
||||||
|
bool loop;
|
||||||
|
uint64_t loopTimestampOffset = 0;
|
||||||
|
public:
|
||||||
|
const uint64_t sampleDuration_us;
|
||||||
|
virtual void start();
|
||||||
|
virtual void stop();
|
||||||
|
FileParser(std::string directory, std::string extension, uint32_t samplesPerSecond, bool loop);
|
||||||
|
virtual void loadNextSample();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* fileparser_hpp */
|
70
examples/streamer/h264fileparser.cpp
Normal file
70
examples/streamer/h264fileparser.cpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "h264fileparser.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
H264FileParser::H264FileParser(string directory, uint32_t fps, bool loop): FileParser(directory, ".h264", fps, loop) { }
|
||||||
|
|
||||||
|
void H264FileParser::loadNextSample() {
|
||||||
|
FileParser::loadNextSample();
|
||||||
|
|
||||||
|
unsigned long long i = 0;
|
||||||
|
while (i < sample.size()) {
|
||||||
|
assert(i + 4 < sample.size());
|
||||||
|
auto lengthPtr = (uint32_t *) (sample.data() + i);
|
||||||
|
uint32_t length = ntohl(*lengthPtr);
|
||||||
|
auto naluStartIndex = i + 4;
|
||||||
|
auto naluEndIndex = naluStartIndex + length;
|
||||||
|
assert(naluEndIndex <= sample.size());
|
||||||
|
auto header = reinterpret_cast<rtc::NalUnitHeader *>(sample.data() + naluStartIndex);
|
||||||
|
auto type = header->unitType();
|
||||||
|
switch (type) {
|
||||||
|
case 7:
|
||||||
|
previousUnitType7 = {sample.begin() + i, sample.begin() + naluEndIndex};
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
previousUnitType8 = {sample.begin() + i, sample.begin() + naluEndIndex};;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
previousUnitType5 = {sample.begin() + i, sample.begin() + naluEndIndex};;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i = naluEndIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<byte> H264FileParser::initialNALUS() {
|
||||||
|
vector<byte> units{};
|
||||||
|
if (previousUnitType7.has_value()) {
|
||||||
|
auto nalu = previousUnitType7.value();
|
||||||
|
units.insert(units.end(), nalu.begin(), nalu.end());
|
||||||
|
}
|
||||||
|
if (previousUnitType8.has_value()) {
|
||||||
|
auto nalu = previousUnitType8.value();
|
||||||
|
units.insert(units.end(), nalu.begin(), nalu.end());
|
||||||
|
}
|
||||||
|
if (previousUnitType5.has_value()) {
|
||||||
|
auto nalu = previousUnitType5.value();
|
||||||
|
units.insert(units.end(), nalu.begin(), nalu.end());
|
||||||
|
}
|
||||||
|
return units;
|
||||||
|
}
|
36
examples/streamer/h264fileparser.hpp
Normal file
36
examples/streamer/h264fileparser.hpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef h264fileparser_hpp
|
||||||
|
#define h264fileparser_hpp
|
||||||
|
|
||||||
|
#include "fileparser.hpp"
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
class H264FileParser: public FileParser {
|
||||||
|
std::optional<std::vector<std::byte>> previousUnitType5 = std::nullopt;
|
||||||
|
std::optional<std::vector<std::byte>> previousUnitType7 = std::nullopt;
|
||||||
|
std::optional<std::vector<std::byte>> previousUnitType8 = std::nullopt;
|
||||||
|
|
||||||
|
public:
|
||||||
|
H264FileParser(std::string directory, uint32_t fps, bool loop);
|
||||||
|
void loadNextSample() override;
|
||||||
|
std::vector<std::byte> initialNALUS();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* h264fileparser_hpp */
|
87
examples/streamer/helpers.cpp
Normal file
87
examples/streamer/helpers.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
// taken from https://stackoverflow.com/questions/10905892/equivalent-of-gettimeday-for-windows
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
struct timezone {
|
||||||
|
int tz_minuteswest;
|
||||||
|
int tz_dsttime;
|
||||||
|
};
|
||||||
|
int gettimeofday(struct timeval *tv, struct timezone *tz)
|
||||||
|
{
|
||||||
|
if (tv) {
|
||||||
|
FILETIME filetime; /* 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 00:00 UTC */
|
||||||
|
ULARGE_INTEGER x;
|
||||||
|
ULONGLONG usec;
|
||||||
|
static const ULONGLONG epoch_offset_us = 11644473600000000ULL; /* microseconds betweeen Jan 1,1601 and Jan 1,1970 */
|
||||||
|
|
||||||
|
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
|
||||||
|
GetSystemTimePreciseAsFileTime(&filetime);
|
||||||
|
#else
|
||||||
|
GetSystemTimeAsFileTime(&filetime);
|
||||||
|
#endif
|
||||||
|
x.LowPart = filetime.dwLowDateTime;
|
||||||
|
x.HighPart = filetime.dwHighDateTime;
|
||||||
|
usec = x.QuadPart / 10 - epoch_offset_us;
|
||||||
|
tv->tv_sec = (time_t)(usec / 1000000ULL);
|
||||||
|
tv->tv_usec = (long)(usec % 1000000ULL);
|
||||||
|
}
|
||||||
|
if (tz) {
|
||||||
|
TIME_ZONE_INFORMATION timezone;
|
||||||
|
GetTimeZoneInformation(&timezone);
|
||||||
|
tz->tz_minuteswest = timezone.Bias;
|
||||||
|
tz->tz_dsttime = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace rtc;
|
||||||
|
|
||||||
|
ClientTrackData::ClientTrackData(shared_ptr<Track> track, shared_ptr<RtcpSenderReporter> sender) {
|
||||||
|
this->track = track;
|
||||||
|
this->sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::setState(State state) {
|
||||||
|
std::unique_lock lock(_mutex);
|
||||||
|
this->state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client::State Client::getState() {
|
||||||
|
std::shared_lock lock(_mutex);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientTrack::ClientTrack(string id, shared_ptr<ClientTrackData> trackData) {
|
||||||
|
this->id = id;
|
||||||
|
this->trackData = trackData;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t currentTimeInMicroSeconds() {
|
||||||
|
struct timeval time;
|
||||||
|
gettimeofday(&time, NULL);
|
||||||
|
return uint64_t(time.tv_sec) * 1000 * 1000 + time.tv_usec;
|
||||||
|
}
|
63
examples/streamer/helpers.hpp
Normal file
63
examples/streamer/helpers.hpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef helpers_hpp
|
||||||
|
#define helpers_hpp
|
||||||
|
|
||||||
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
struct ClientTrackData {
|
||||||
|
std::shared_ptr<rtc::Track> track;
|
||||||
|
std::shared_ptr<rtc::RtcpSenderReporter> sender;
|
||||||
|
|
||||||
|
ClientTrackData(std::shared_ptr<rtc::Track> track, std::shared_ptr<rtc::RtcpSenderReporter> sender);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Client {
|
||||||
|
enum class State {
|
||||||
|
Waiting,
|
||||||
|
WaitingForVideo,
|
||||||
|
WaitingForAudio,
|
||||||
|
Ready
|
||||||
|
};
|
||||||
|
const std::shared_ptr<rtc::PeerConnection> & peerConnection = _peerConnection;
|
||||||
|
Client(std::shared_ptr<rtc::PeerConnection> pc) {
|
||||||
|
_peerConnection = pc;
|
||||||
|
}
|
||||||
|
std::optional<std::shared_ptr<ClientTrackData>> video;
|
||||||
|
std::optional<std::shared_ptr<ClientTrackData>> audio;
|
||||||
|
std::optional<std::shared_ptr<rtc::DataChannel>> dataChannel{};
|
||||||
|
void setState(State state);
|
||||||
|
State getState();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_mutex _mutex;
|
||||||
|
State state = State::Waiting;
|
||||||
|
std::string id;
|
||||||
|
std::shared_ptr<rtc::PeerConnection> _peerConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClientTrack {
|
||||||
|
std::string id;
|
||||||
|
std::shared_ptr<ClientTrackData> trackData;
|
||||||
|
ClientTrack(std::string id, std::shared_ptr<ClientTrackData> trackData);
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t currentTimeInMicroSeconds();
|
||||||
|
|
||||||
|
#endif /* helpers_hpp */
|
73
examples/streamer/index.html
Normal file
73
examples/streamer/index.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>libdatachannel media example</title>
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#media {
|
||||||
|
max-width: 1280px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||||
|
|
||||||
|
<h2>Options</h2>
|
||||||
|
|
||||||
|
<div class="option">
|
||||||
|
<input id="use-stun" type="checkbox"/>
|
||||||
|
<label for="use-stun">Use STUN server</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="start" onclick="start()" disabled>Start</button>
|
||||||
|
<button id="stop" style="display: none" onclick="stop()">Stop</button>
|
||||||
|
|
||||||
|
<h2>State</h2>
|
||||||
|
<p>
|
||||||
|
ICE gathering state: <span id="ice-gathering-state"></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
ICE connection state: <span id="ice-connection-state"></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Signaling state: <span id="signaling-state"></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="media" style="display: none">
|
||||||
|
<h2>Media</h2>
|
||||||
|
<audio id="audio" autoplay></audio>
|
||||||
|
<video id="video" autoplay playsinline></video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Data channel</h2>
|
||||||
|
<pre id="data-channel" style="height: 200px;"></pre>
|
||||||
|
|
||||||
|
<h2>SDP</h2>
|
||||||
|
|
||||||
|
<h3>Offer</h3>
|
||||||
|
<pre id="offer-sdp"></pre>
|
||||||
|
|
||||||
|
<h3>Answer</h3>
|
||||||
|
<pre id="answer-sdp"></pre>
|
||||||
|
|
||||||
|
<script src="client.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
468
examples/streamer/main.cpp
Normal file
468
examples/streamer/main.cpp
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel client example
|
||||||
|
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||||
|
* Copyright (c) 2019 Murat Dogan
|
||||||
|
* Copyright (c) 2020 Will Munn
|
||||||
|
* Copyright (c) 2020 Nico Chatzi
|
||||||
|
* Copyright (c) 2020 Lara Mackey
|
||||||
|
* Copyright (c) 2020 Erik Cota-Robles
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nlohmann/json.hpp"
|
||||||
|
|
||||||
|
#include "h264fileparser.hpp"
|
||||||
|
#include "opusfileparser.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "ArgParser.hpp"
|
||||||
|
|
||||||
|
using namespace rtc;
|
||||||
|
using namespace std;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||||
|
|
||||||
|
/// all connected clients
|
||||||
|
unordered_map<string, shared_ptr<Client>> clients{};
|
||||||
|
|
||||||
|
/// Creates peer connection and client representation
|
||||||
|
/// @param config Configuration
|
||||||
|
/// @param wws Websocket for signaling
|
||||||
|
/// @param id Client ID
|
||||||
|
/// @returns Client
|
||||||
|
shared_ptr<Client> createPeerConnection(const Configuration &config,
|
||||||
|
weak_ptr<WebSocket> wws,
|
||||||
|
string id);
|
||||||
|
|
||||||
|
/// Creates stream
|
||||||
|
/// @param h264Samples Directory with H264 samples
|
||||||
|
/// @param fps Video FPS
|
||||||
|
/// @param opusSamples Directory with opus samples
|
||||||
|
/// @returns Stream object
|
||||||
|
shared_ptr<Stream> createStream(const string h264Samples, const unsigned fps, const string opusSamples);
|
||||||
|
|
||||||
|
/// Add client to stream
|
||||||
|
/// @param client Client
|
||||||
|
/// @param adding_video True if adding video
|
||||||
|
void addToStream(shared_ptr<Client> client, bool isAddingVideo);
|
||||||
|
|
||||||
|
/// Start stream
|
||||||
|
void startStream();
|
||||||
|
|
||||||
|
/// Main dispatch queue
|
||||||
|
DispatchQueue MainThread("Main");
|
||||||
|
|
||||||
|
/// Audio and video stream
|
||||||
|
optional<shared_ptr<Stream>> avStream = nullopt;
|
||||||
|
|
||||||
|
const string defaultRootDirectory = "../../../../examples/streamer/samples/";
|
||||||
|
const string defaultH264SamplesDirectory = defaultRootDirectory + "h264/";
|
||||||
|
string h264SamplesDirectory = defaultH264SamplesDirectory;
|
||||||
|
const string defaultOpusSamplesDirectory = defaultRootDirectory + "opus/";
|
||||||
|
string opusSamplesDirectory = defaultOpusSamplesDirectory;
|
||||||
|
const string defaultIPAddress = "127.0.0.1";
|
||||||
|
const uint16_t defaultPort = 8000;
|
||||||
|
string ip_address = defaultIPAddress;
|
||||||
|
uint16_t port = defaultPort;
|
||||||
|
|
||||||
|
/// Incomming message handler for websocket
|
||||||
|
/// @param message Incommint message
|
||||||
|
/// @param config Configuration
|
||||||
|
/// @param ws Websocket
|
||||||
|
void wsOnMessage(json message, Configuration config, shared_ptr<WebSocket> ws) {
|
||||||
|
auto it = message.find("id");
|
||||||
|
if (it == message.end())
|
||||||
|
return;
|
||||||
|
string id = it->get<string>();
|
||||||
|
it = message.find("type");
|
||||||
|
if (it == message.end())
|
||||||
|
return;
|
||||||
|
string type = it->get<string>();
|
||||||
|
|
||||||
|
if (type == "streamRequest") {
|
||||||
|
shared_ptr<Client> c = createPeerConnection(config, make_weak_ptr(ws), id);
|
||||||
|
clients.emplace(id, c);
|
||||||
|
} else if (type == "answer") {
|
||||||
|
shared_ptr<Client> c;
|
||||||
|
if (auto jt = clients.find(id); jt != clients.end()) {
|
||||||
|
auto pc = clients.at(id)->peerConnection;
|
||||||
|
auto sdp = message["sdp"].get<string>();
|
||||||
|
auto description = Description(sdp, type);
|
||||||
|
pc->setRemoteDescription(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) try {
|
||||||
|
bool enableDebugLogs = false;
|
||||||
|
bool printHelp = false;
|
||||||
|
int c = 0;
|
||||||
|
auto parser = ArgParser({{"a", "audio"}, {"b", "video"}, {"d", "ip"}, {"p","port"}}, {{"h", "help"}, {"v", "verbose"}});
|
||||||
|
auto parsingResult = parser.parse(argc, argv, [](string key, string value) {
|
||||||
|
if (key == "audio") {
|
||||||
|
opusSamplesDirectory = value + "/";
|
||||||
|
} else if (key == "video") {
|
||||||
|
h264SamplesDirectory = value + "/";
|
||||||
|
} else if (key == "ip") {
|
||||||
|
ip_address = value;
|
||||||
|
} else if (key == "port") {
|
||||||
|
port = atoi(value.data());
|
||||||
|
} else {
|
||||||
|
cerr << "Invalid option --" << key << " with value " << value << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, [&enableDebugLogs, &printHelp](string flag){
|
||||||
|
if (flag == "verbose") {
|
||||||
|
enableDebugLogs = true;
|
||||||
|
} else if (flag == "help") {
|
||||||
|
printHelp = true;
|
||||||
|
} else {
|
||||||
|
cerr << "Invalid flag --" << flag << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (!parsingResult) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (printHelp) {
|
||||||
|
cout << "usage: stream-h264 [-a opus_samples_folder] [-b h264_samples_folder] [-d ip_address] [-p port] [-v] [-h]" << endl
|
||||||
|
<< "Arguments:" << endl
|
||||||
|
<< "\t -a " << "Directory with opus samples (default: " << defaultOpusSamplesDirectory << ")." << endl
|
||||||
|
<< "\t -b " << "Directory with H264 samples (default: " << defaultH264SamplesDirectory << ")." << endl
|
||||||
|
<< "\t -d " << "Signaling server IP address (default: " << defaultIPAddress << ")." << endl
|
||||||
|
<< "\t -p " << "Signaling server port (default: " << defaultPort << ")." << endl
|
||||||
|
<< "\t -v " << "Enable debug logs." << endl
|
||||||
|
<< "\t -h " << "Print this help and exit." << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (enableDebugLogs) {
|
||||||
|
InitLogger(LogLevel::Debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration config;
|
||||||
|
string stunServer = "stun:stun.l.google.com:19302";
|
||||||
|
cout << "Stun server is " << stunServer << endl;
|
||||||
|
config.iceServers.emplace_back(stunServer);
|
||||||
|
|
||||||
|
|
||||||
|
string localId = "server";
|
||||||
|
cout << "The local ID is: " << localId << endl;
|
||||||
|
|
||||||
|
auto ws = make_shared<WebSocket>();
|
||||||
|
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
json message = json::parse(get<string>(data));
|
||||||
|
MainThread.dispatch([message, config, ws]() {
|
||||||
|
wsOnMessage(message, config, ws);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const string url = "ws://" + ip_address + ":" + to_string(port) + "/" + 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
string id;
|
||||||
|
cout << "Enter to exit" << endl;
|
||||||
|
cin >> id;
|
||||||
|
cin.ignore();
|
||||||
|
cout << "exiting" << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "Cleaning up..." << endl;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cout << "Error: " << e.what() << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<ClientTrackData> addVideo(const shared_ptr<PeerConnection> pc, const uint8_t payloadType, const uint32_t ssrc, const string cname, const string msid, const function<void (void)> onOpen) {
|
||||||
|
auto video = Description::Video(cname);
|
||||||
|
video.addH264Codec(payloadType);
|
||||||
|
video.addSSRC(ssrc, cname, msid);
|
||||||
|
auto track = pc->addTrack(video);
|
||||||
|
// create RTP configuration
|
||||||
|
auto rtpConfig = shared_ptr<RtpPacketizationConfig>(new RtpPacketizationConfig(ssrc, cname, payloadType, H264RtpPacketizer::defaultClockRate));
|
||||||
|
// create packetizer
|
||||||
|
auto packetizer = shared_ptr<H264RtpPacketizer>(new H264RtpPacketizer(rtpConfig));
|
||||||
|
// create H264 and RTCP SP handler
|
||||||
|
shared_ptr<H264PacketizationHandler> h264Handler(new H264PacketizationHandler(H264PacketizationHandler::Separator::Length, packetizer));
|
||||||
|
// set handler
|
||||||
|
track->setRtcpHandler(h264Handler);
|
||||||
|
track->onOpen(onOpen);
|
||||||
|
auto trackData = make_shared<ClientTrackData>(track, h264Handler);
|
||||||
|
return trackData;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<ClientTrackData> addAudio(const shared_ptr<PeerConnection> pc, const uint8_t payloadType, const uint32_t ssrc, const string cname, const string msid, const function<void (void)> onOpen) {
|
||||||
|
auto audio = Description::Audio(cname);
|
||||||
|
audio.addOpusCodec(payloadType);
|
||||||
|
audio.addSSRC(ssrc, cname, msid);
|
||||||
|
auto track = pc->addTrack(audio);
|
||||||
|
// create RTP configuration
|
||||||
|
auto rtpConfig = shared_ptr<RtpPacketizationConfig>(new RtpPacketizationConfig(ssrc, cname, payloadType, OpusRtpPacketizer::defaultClockRate));
|
||||||
|
// create packetizer
|
||||||
|
auto packetizer = make_shared<OpusRtpPacketizer>(rtpConfig);
|
||||||
|
// create opus and RTCP SP handler
|
||||||
|
auto opusHandler = make_shared<OpusPacketizationHandler>(packetizer);
|
||||||
|
// set handler
|
||||||
|
track->setRtcpHandler(opusHandler);
|
||||||
|
track->onOpen(onOpen);
|
||||||
|
auto trackData = make_shared<ClientTrackData>(track, opusHandler);
|
||||||
|
return trackData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and setup a PeerConnection
|
||||||
|
shared_ptr<Client> createPeerConnection(const Configuration &config,
|
||||||
|
weak_ptr<WebSocket> wws,
|
||||||
|
string id) {
|
||||||
|
|
||||||
|
auto pc = make_shared<PeerConnection>(config);
|
||||||
|
shared_ptr<Client> client(new Client(pc));
|
||||||
|
|
||||||
|
pc->onStateChange([id](PeerConnection::State state) {
|
||||||
|
cout << "State: " << state << endl;
|
||||||
|
if (state == PeerConnection::State::Disconnected ||
|
||||||
|
state == PeerConnection::State::Failed ||
|
||||||
|
state == PeerConnection::State::Closed) {
|
||||||
|
// remove disconnected client
|
||||||
|
MainThread.dispatch([id]() {
|
||||||
|
clients.erase(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pc->onGatheringStateChange(
|
||||||
|
[wpc = make_weak_ptr(pc), id, wws](PeerConnection::GatheringState state) {
|
||||||
|
cout << "Gathering State: " << state << endl;
|
||||||
|
if (state == PeerConnection::GatheringState::Complete) {
|
||||||
|
if(auto pc = wpc.lock()) {
|
||||||
|
auto description = pc->localDescription();
|
||||||
|
json message = {
|
||||||
|
{"id", id},
|
||||||
|
{"type", description->typeString()},
|
||||||
|
{"sdp", string(description.value())}
|
||||||
|
};
|
||||||
|
// Gathering complete, send answer
|
||||||
|
if (auto ws = wws.lock()) {
|
||||||
|
ws->send(message.dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client->video = addVideo(pc, 102, 1, "video-stream", "stream1", [id, wc = make_weak_ptr(client)]() {
|
||||||
|
MainThread.dispatch([wc]() {
|
||||||
|
if (auto c = wc.lock()) {
|
||||||
|
addToStream(c, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cout << "Video from " << id << " opened" << endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
client->audio = addAudio(pc, 111, 2, "audio-stream", "stream1", [id, wc = make_weak_ptr(client)]() {
|
||||||
|
MainThread.dispatch([wc]() {
|
||||||
|
if (auto c = wc.lock()) {
|
||||||
|
addToStream(c, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cout << "Audio from " << id << " opened" << endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto dc = pc->addDataChannel("ping-pong");
|
||||||
|
dc->onOpen([id, wdc = make_weak_ptr(dc)]() {
|
||||||
|
if (auto dc = wdc.lock()) {
|
||||||
|
dc->send("Ping");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dc->onMessage(nullptr, [id, wdc = make_weak_ptr(dc)](string msg) {
|
||||||
|
cout << "Message from " << id << " received: " << msg << endl;
|
||||||
|
if (auto dc = wdc.lock()) {
|
||||||
|
dc->send("Ping");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client->dataChannel = dc;
|
||||||
|
|
||||||
|
pc->setLocalDescription();
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create stream
|
||||||
|
shared_ptr<Stream> createStream(const string h264Samples, const unsigned fps, const string opusSamples) {
|
||||||
|
// video source
|
||||||
|
auto video = make_shared<H264FileParser>(h264Samples, fps, true);
|
||||||
|
// audio source
|
||||||
|
auto audio = make_shared<OPUSFileParser>(opusSamples, true);
|
||||||
|
|
||||||
|
auto stream = make_shared<Stream>(video, audio);
|
||||||
|
// set callback responsible for sample sending
|
||||||
|
stream->onSample([ws = make_weak_ptr(stream)](Stream::StreamSourceType type, uint64_t sampleTime, rtc::binary sample) {
|
||||||
|
vector<ClientTrack> tracks{};
|
||||||
|
string streamType = type == Stream::StreamSourceType::Video ? "video" : "audio";
|
||||||
|
// get track for given type
|
||||||
|
function<optional<shared_ptr<ClientTrackData>> (shared_ptr<Client>)> getTrackData = [type](shared_ptr<Client> client) {
|
||||||
|
return type == Stream::StreamSourceType::Video ? client->video : client->audio;
|
||||||
|
};
|
||||||
|
// get all clients with Ready state
|
||||||
|
for(auto id_client: clients) {
|
||||||
|
auto id = id_client.first;
|
||||||
|
auto client = id_client.second;
|
||||||
|
auto optTrackData = getTrackData(client);
|
||||||
|
if (client->getState() == Client::State::Ready && optTrackData.has_value()) {
|
||||||
|
auto trackData = optTrackData.value();
|
||||||
|
tracks.push_back(ClientTrack(id, trackData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!tracks.empty()) {
|
||||||
|
auto message = make_message(move(sample));
|
||||||
|
for (auto clientTrack: tracks) {
|
||||||
|
auto client = clientTrack.id;
|
||||||
|
auto trackData = clientTrack.trackData;
|
||||||
|
// sample time is in us, we need to convert it to seconds
|
||||||
|
auto elapsedSeconds = double(sampleTime) / (1000 * 1000);
|
||||||
|
auto rtpConfig = trackData->sender->rtpConfig;
|
||||||
|
// get elapsed time in clock rate
|
||||||
|
uint32_t elapsedTimestamp = rtpConfig->secondsToTimestamp(elapsedSeconds);
|
||||||
|
|
||||||
|
// set new timestamp
|
||||||
|
rtpConfig->timestamp = rtpConfig->startTimestamp + elapsedTimestamp;
|
||||||
|
|
||||||
|
// get elapsed time in clock rate from last RTCP sender report
|
||||||
|
auto reportElapsedTimestamp = rtpConfig->timestamp - trackData->sender->previousReportedTimestamp;
|
||||||
|
// check if last report was at least 1 second ago
|
||||||
|
if (rtpConfig->timestampToSeconds(reportElapsedTimestamp) > 1) {
|
||||||
|
trackData->sender->setNeedsToReport();
|
||||||
|
}
|
||||||
|
cout << "Sending " << streamType << " sample with size: " << to_string(message->size()) << " to " << client << endl;
|
||||||
|
bool send = false;
|
||||||
|
try {
|
||||||
|
// send sample
|
||||||
|
send = trackData->track->send(*message);
|
||||||
|
} catch (...) {
|
||||||
|
send = false;
|
||||||
|
}
|
||||||
|
if (!send) {
|
||||||
|
cerr << "Unable to send "<< streamType << " packet" << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MainThread.dispatch([ws]() {
|
||||||
|
if (clients.empty()) {
|
||||||
|
// we have no clients, stop the stream
|
||||||
|
if (auto stream = ws.lock()) {
|
||||||
|
stream->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start stream
|
||||||
|
void startStream() {
|
||||||
|
shared_ptr<Stream> stream;
|
||||||
|
if (avStream.has_value()) {
|
||||||
|
stream = avStream.value();
|
||||||
|
if (stream->isRunning) {
|
||||||
|
// stream is already running
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stream = createStream(h264SamplesDirectory, 30, opusSamplesDirectory);
|
||||||
|
avStream = stream;
|
||||||
|
}
|
||||||
|
stream->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send previous key frame so browser can show something to user
|
||||||
|
/// @param stream Stream
|
||||||
|
/// @param video Video track data
|
||||||
|
void sendInitialNalus(shared_ptr<Stream> stream, shared_ptr<ClientTrackData> video) {
|
||||||
|
auto h264 = dynamic_cast<H264FileParser *>(stream->video.get());
|
||||||
|
auto initialNalus = h264->initialNALUS();
|
||||||
|
|
||||||
|
// send previous NALU key frame so users don't have to wait to see stream works
|
||||||
|
if (!initialNalus.empty()) {
|
||||||
|
const double frameDuration_s = double(h264->sampleDuration_us) / (1000 * 1000);
|
||||||
|
const uint32_t frameTimestampDuration = video->sender->rtpConfig->secondsToTimestamp(frameDuration_s);
|
||||||
|
video->sender->rtpConfig->timestamp = video->sender->rtpConfig->startTimestamp - frameTimestampDuration * 2;
|
||||||
|
video->track->send(initialNalus);
|
||||||
|
video->sender->rtpConfig->timestamp += frameTimestampDuration;
|
||||||
|
// Send initial NAL units again to start stream in firefox browser
|
||||||
|
video->track->send(initialNalus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add client to stream
|
||||||
|
/// @param client Client
|
||||||
|
/// @param adding_video True if adding video
|
||||||
|
void addToStream(shared_ptr<Client> client, bool isAddingVideo) {
|
||||||
|
if (client->getState() == Client::State::Waiting) {
|
||||||
|
client->setState(isAddingVideo ? Client::State::WaitingForAudio : Client::State::WaitingForVideo);
|
||||||
|
} else if ((client->getState() == Client::State::WaitingForAudio && !isAddingVideo)
|
||||||
|
|| (client->getState() == Client::State::WaitingForVideo && isAddingVideo)) {
|
||||||
|
|
||||||
|
// Audio and video tracks are collected now
|
||||||
|
assert(client->video.has_value() && client->audio.has_value());
|
||||||
|
|
||||||
|
auto video = client->video.value();
|
||||||
|
auto audio = client->audio.value();
|
||||||
|
|
||||||
|
auto currentTime_us = double(currentTimeInMicroSeconds());
|
||||||
|
auto currentTime_s = currentTime_us / (1000 * 1000);
|
||||||
|
|
||||||
|
// set start time of stream
|
||||||
|
video->sender->rtpConfig->setStartTime(currentTime_s, RtpPacketizationConfig::EpochStart::T1970);
|
||||||
|
audio->sender->rtpConfig->setStartTime(currentTime_s, RtpPacketizationConfig::EpochStart::T1970);
|
||||||
|
|
||||||
|
// start stat recording of RTCP SR
|
||||||
|
video->sender->startRecording();
|
||||||
|
audio->sender->startRecording();
|
||||||
|
|
||||||
|
if (avStream.has_value()) {
|
||||||
|
sendInitialNalus(avStream.value(), video);
|
||||||
|
}
|
||||||
|
|
||||||
|
client->setState(Client::State::Ready);
|
||||||
|
}
|
||||||
|
if (client->getState() == Client::State::Ready) {
|
||||||
|
startStream();
|
||||||
|
}
|
||||||
|
}
|
23
examples/streamer/opusfileparser.cpp
Normal file
23
examples/streamer/opusfileparser.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "opusfileparser.hpp"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
OPUSFileParser::OPUSFileParser(string directory, bool loop, uint32_t samplesPerSecond): FileParser(directory, ".opus", samplesPerSecond, loop) { }
|
32
examples/streamer/opusfileparser.hpp
Normal file
32
examples/streamer/opusfileparser.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef opusfileparser_hpp
|
||||||
|
#define opusfileparser_hpp
|
||||||
|
|
||||||
|
#include "fileparser.hpp"
|
||||||
|
|
||||||
|
class OPUSFileParser: public FileParser {
|
||||||
|
static const uint32_t defaultSamplesPerSecond = 50;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OPUSFileParser(std::string directory, bool loop, uint32_t samplesPerSecond = OPUSFileParser::defaultSamplesPerSecond);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* opusfileparser_hpp */
|
BIN
examples/streamer/samples/bensound-creativeminds.mp3
Normal file
BIN
examples/streamer/samples/bensound-creativeminds.mp3
Normal file
Binary file not shown.
BIN
examples/streamer/samples/candle.mov
Normal file
BIN
examples/streamer/samples/candle.mov
Normal file
Binary file not shown.
115
examples/streamer/samples/generate_h264.py
Executable file
115
examples/streamer/samples/generate_h264.py
Executable file
@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import getopt
|
||||||
|
import sys
|
||||||
|
import glob
|
||||||
|
from functools import reduce
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
|
||||||
|
class H264ByteStream:
|
||||||
|
@staticmethod
|
||||||
|
def nalu_type(nalu: bytes) -> int:
|
||||||
|
return nalu[0] & 0x1F
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def merge_sample(sample: List[bytes]) -> bytes:
|
||||||
|
result = bytes()
|
||||||
|
for nalu in sample:
|
||||||
|
result += len(nalu).to_bytes(4, byteorder='big') + nalu
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reduce_nalus_to_samples(samples: List[List[bytes]], current: bytes) -> List[List[bytes]]:
|
||||||
|
last_nalus = samples[-1]
|
||||||
|
samples[-1] = last_nalus + [current]
|
||||||
|
if H264ByteStream.nalu_type(current) in [1, 5]:
|
||||||
|
samples.append([])
|
||||||
|
return samples
|
||||||
|
|
||||||
|
def __init__(self, file_name: str):
|
||||||
|
with open(file_name, "rb") as file:
|
||||||
|
byte_stream = file.read()
|
||||||
|
long_split = byte_stream.split(b"\x00\x00\x00\x01")
|
||||||
|
splits = reduce(lambda acc, x: acc + x.split(b"\x00\x00\x01"), long_split, [])
|
||||||
|
nalus = filter(lambda x: len(x) > 0, splits)
|
||||||
|
self.samples = list(
|
||||||
|
filter(lambda x: len(x) > 0, reduce(H264ByteStream.reduce_nalus_to_samples, nalus, [[]])))
|
||||||
|
|
||||||
|
|
||||||
|
def generate(input_file: str, output_dir: str, max_samples: Optional[int], fps: Optional[int]):
|
||||||
|
if output_dir[-1] != "/":
|
||||||
|
output_dir += "/"
|
||||||
|
if os.path.isdir(output_dir):
|
||||||
|
files_to_delete = glob.glob(output_dir + "*.h264")
|
||||||
|
if len(files_to_delete) > 0:
|
||||||
|
print("Remove following files?")
|
||||||
|
for file in files_to_delete:
|
||||||
|
print(file)
|
||||||
|
response = input("Remove files? [y/n] ").lower()
|
||||||
|
if response != "y" and response != "yes":
|
||||||
|
print("Cancelling...")
|
||||||
|
return
|
||||||
|
print("Removing files")
|
||||||
|
for file in files_to_delete:
|
||||||
|
os.remove(file)
|
||||||
|
else:
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
video_stream_file = "_video_stream.h264"
|
||||||
|
if os.path.isfile(video_stream_file):
|
||||||
|
os.remove(video_stream_file)
|
||||||
|
|
||||||
|
fps_line = "" if fps is None else "-filter:v fps=fps={} ".format(fps)
|
||||||
|
command = 'ffmpeg -i {} -an -vcodec libx264 -preset slow -profile baseline {}{}'.format(input_file, fps_line,
|
||||||
|
video_stream_file)
|
||||||
|
os.system(command)
|
||||||
|
|
||||||
|
data = H264ByteStream(video_stream_file)
|
||||||
|
index = 0
|
||||||
|
for sample in data.samples[:max_samples]:
|
||||||
|
name = "{}sample-{}.h264".format(output_dir, index)
|
||||||
|
index += 1
|
||||||
|
with open(name, 'wb') as file:
|
||||||
|
merged_sample = H264ByteStream.merge_sample(sample)
|
||||||
|
file.write(merged_sample)
|
||||||
|
os.remove(video_stream_file)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
input_file = None
|
||||||
|
default_output_dir = "h264/"
|
||||||
|
output_dir = default_output_dir
|
||||||
|
max_samples = None
|
||||||
|
fps = None
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(argv, "hi:o:m:f:", ["help", "ifile=", "odir=", "max=", "fps"])
|
||||||
|
except getopt.GetoptError:
|
||||||
|
print('generate_h264.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-f <fps>] [-h]')
|
||||||
|
sys.exit(2)
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt in ("-h", "--help"):
|
||||||
|
print("Usage: generate_h264.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-f <fps>] [-h]")
|
||||||
|
print("Arguments:")
|
||||||
|
print("\t-i,--ifile: Input file")
|
||||||
|
print("\t-o,--odir: Output directory (default: " + default_output_dir + ")")
|
||||||
|
print("\t-m,--max: Maximum generated samples")
|
||||||
|
print("\t-f,--fps: Output fps")
|
||||||
|
print("\t-h,--help: Print this help and exit")
|
||||||
|
sys.exit()
|
||||||
|
elif opt in ("-i", "--ifile"):
|
||||||
|
input_file = arg
|
||||||
|
elif opt in ("-o", "--ofile"):
|
||||||
|
output_dir = arg
|
||||||
|
elif opt in ("-m", "--max"):
|
||||||
|
max_samples = int(arg)
|
||||||
|
elif opt in ("-f", "--fps"):
|
||||||
|
fps = int(arg)
|
||||||
|
if input_file is None:
|
||||||
|
print("Missing argument -i")
|
||||||
|
sys.exit(2)
|
||||||
|
generate(input_file, output_dir, max_samples, fps)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1:])
|
142
examples/streamer/samples/generate_opus.py
Executable file
142
examples/streamer/samples/generate_opus.py
Executable file
@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from kaitaistruct import KaitaiStruct, ValidationNotEqualError
|
||||||
|
import os
|
||||||
|
import getopt
|
||||||
|
import sys
|
||||||
|
import glob
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
|
class Ogg(KaitaiStruct):
|
||||||
|
"""Ogg is a popular media container format, which provides basic
|
||||||
|
streaming / buffering mechanisms and is content-agnostic. Most
|
||||||
|
popular codecs that are used within Ogg streams are Vorbis (thus
|
||||||
|
making Ogg/Vorbis streams) and Theora (Ogg/Theora).
|
||||||
|
|
||||||
|
Ogg stream is a sequence Ogg pages. They can be read sequentially,
|
||||||
|
or one can jump into arbitrary stream location and scan for "OggS"
|
||||||
|
sync code to find the beginning of a new Ogg page and continue
|
||||||
|
decoding the stream contents from that one.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, _io, _parent=None, _root=None):
|
||||||
|
KaitaiStruct.__init__(self, _io)
|
||||||
|
self._parent = _parent
|
||||||
|
self._root = _root if _root else self
|
||||||
|
self._read()
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
self.pages = []
|
||||||
|
i = 0
|
||||||
|
while not self._io.is_eof():
|
||||||
|
self.pages.append(Ogg.Page(self._io, self, self._root))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
class Page(KaitaiStruct):
|
||||||
|
"""Ogg page is a basic unit of data in an Ogg bitstream, usually
|
||||||
|
it's around 4-8 KB, with a maximum size of 65307 bytes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, _io, _parent=None, _root=None):
|
||||||
|
KaitaiStruct.__init__(self, _io)
|
||||||
|
self._parent = _parent
|
||||||
|
self._root = _root if _root else self
|
||||||
|
self._read()
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
self.sync_code = self._io.read_bytes(4)
|
||||||
|
if not self.sync_code == b"\x4F\x67\x67\x53":
|
||||||
|
raise ValidationNotEqualError(b"\x4F\x67\x67\x53", self.sync_code, self._io,
|
||||||
|
u"/types/page/seq/0")
|
||||||
|
self.version = self._io.read_bytes(1)
|
||||||
|
if not self.version == b"\x00":
|
||||||
|
raise ValidationNotEqualError(b"\x00", self.version, self._io, u"/types/page/seq/1")
|
||||||
|
self.reserved1 = self._io.read_bits_int_be(5)
|
||||||
|
self.is_end_of_stream = self._io.read_bits_int_be(1) != 0
|
||||||
|
self.is_beginning_of_stream = self._io.read_bits_int_be(1) != 0
|
||||||
|
self.is_continuation = self._io.read_bits_int_be(1) != 0
|
||||||
|
self._io.align_to_byte()
|
||||||
|
self.granule_pos = self._io.read_u8le()
|
||||||
|
self.bitstream_serial = self._io.read_u4le()
|
||||||
|
self.page_seq_num = self._io.read_u4le()
|
||||||
|
self.crc32 = self._io.read_u4le()
|
||||||
|
self.num_segments = self._io.read_u1()
|
||||||
|
self.len_segments = [None] * self.num_segments
|
||||||
|
for i in range(self.num_segments):
|
||||||
|
self.len_segments[i] = self._io.read_u1()
|
||||||
|
|
||||||
|
self.segments = [None] * self.num_segments
|
||||||
|
for i in range(self.num_segments):
|
||||||
|
self.segments[i] = self._io.read_bytes(self.len_segments[i])
|
||||||
|
|
||||||
|
|
||||||
|
def generate(input_file: str, output_dir: str, max_samples: int):
|
||||||
|
if output_dir[-1] != "/":
|
||||||
|
output_dir += "/"
|
||||||
|
if os.path.isdir(output_dir):
|
||||||
|
files_to_delete = glob.glob(output_dir + "*.opus")
|
||||||
|
if len(files_to_delete) > 0:
|
||||||
|
print("Remove following files?")
|
||||||
|
for file in files_to_delete:
|
||||||
|
print(file)
|
||||||
|
response = input("Remove files? [y/n] ").lower()
|
||||||
|
if response != "y" and response != "yes":
|
||||||
|
print("Cancelling...")
|
||||||
|
return
|
||||||
|
print("Removing files")
|
||||||
|
for file in files_to_delete:
|
||||||
|
os.remove(file)
|
||||||
|
else:
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
audio_stream_file = "_audio_stream.ogg"
|
||||||
|
if os.path.isfile(audio_stream_file):
|
||||||
|
os.remove(audio_stream_file)
|
||||||
|
os.system('ffmpeg -i {} -vn -ar 48000 -ac 2 -vbr off -acodec libopus -ab 64k {}'.format(input_file, audio_stream_file))
|
||||||
|
|
||||||
|
data = Ogg.from_file(audio_stream_file)
|
||||||
|
index = 0
|
||||||
|
valid_pages = data.pages[2:]
|
||||||
|
segments = list(reduce(lambda x, y: x + y.segments, valid_pages, []))[:max_samples]
|
||||||
|
for segment in segments:
|
||||||
|
name = "{}sample-{}.opus".format(output_dir, index)
|
||||||
|
index += 1
|
||||||
|
with open(name, 'wb') as file:
|
||||||
|
assert len(list(segment)) == 160
|
||||||
|
file.write(segment)
|
||||||
|
os.remove(audio_stream_file)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
input_file = None
|
||||||
|
default_output_dir = "opus/"
|
||||||
|
output_dir = default_output_dir
|
||||||
|
max_samples = None
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(argv, "hi:o:m:", ["help", "ifile=", "odir=", "max="])
|
||||||
|
except getopt.GetoptError:
|
||||||
|
print('generate_opus.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-h]')
|
||||||
|
sys.exit(2)
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt in ("-h", "--help"):
|
||||||
|
print("Usage: generate_opus.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-h]")
|
||||||
|
print("Arguments:")
|
||||||
|
print("\t-i,--ifile: Input file")
|
||||||
|
print("\t-o,--odir: Output directory (default: " + default_output_dir + ")")
|
||||||
|
print("\t-m,--max: Maximum generated samples")
|
||||||
|
print("\t-h,--help: Print this help and exit")
|
||||||
|
sys.exit()
|
||||||
|
elif opt in ("-i", "--ifile"):
|
||||||
|
input_file = arg
|
||||||
|
elif opt in ("-o", "--ofile"):
|
||||||
|
output_dir = arg
|
||||||
|
elif opt in ("-m", "--max"):
|
||||||
|
max_samples = int(arg)
|
||||||
|
if input_file is None:
|
||||||
|
print("Missing argument -i")
|
||||||
|
sys.exit(2)
|
||||||
|
generate(input_file, output_dir, max_samples)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1:])
|
BIN
examples/streamer/samples/h264/sample-0.h264
Normal file
BIN
examples/streamer/samples/h264/sample-0.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-1.h264
Normal file
BIN
examples/streamer/samples/h264/sample-1.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-10.h264
Normal file
BIN
examples/streamer/samples/h264/sample-10.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-100.h264
Normal file
BIN
examples/streamer/samples/h264/sample-100.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-101.h264
Normal file
BIN
examples/streamer/samples/h264/sample-101.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-102.h264
Normal file
BIN
examples/streamer/samples/h264/sample-102.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-103.h264
Normal file
BIN
examples/streamer/samples/h264/sample-103.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-104.h264
Normal file
BIN
examples/streamer/samples/h264/sample-104.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-105.h264
Normal file
BIN
examples/streamer/samples/h264/sample-105.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-106.h264
Normal file
BIN
examples/streamer/samples/h264/sample-106.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-107.h264
Normal file
BIN
examples/streamer/samples/h264/sample-107.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-108.h264
Normal file
BIN
examples/streamer/samples/h264/sample-108.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-109.h264
Normal file
BIN
examples/streamer/samples/h264/sample-109.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-11.h264
Normal file
BIN
examples/streamer/samples/h264/sample-11.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-110.h264
Normal file
BIN
examples/streamer/samples/h264/sample-110.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-111.h264
Normal file
BIN
examples/streamer/samples/h264/sample-111.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-112.h264
Normal file
BIN
examples/streamer/samples/h264/sample-112.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-113.h264
Normal file
BIN
examples/streamer/samples/h264/sample-113.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-114.h264
Normal file
BIN
examples/streamer/samples/h264/sample-114.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-115.h264
Normal file
BIN
examples/streamer/samples/h264/sample-115.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-116.h264
Normal file
BIN
examples/streamer/samples/h264/sample-116.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-117.h264
Normal file
BIN
examples/streamer/samples/h264/sample-117.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-118.h264
Normal file
BIN
examples/streamer/samples/h264/sample-118.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-119.h264
Normal file
BIN
examples/streamer/samples/h264/sample-119.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-12.h264
Normal file
BIN
examples/streamer/samples/h264/sample-12.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-120.h264
Normal file
BIN
examples/streamer/samples/h264/sample-120.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-121.h264
Normal file
BIN
examples/streamer/samples/h264/sample-121.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-122.h264
Normal file
BIN
examples/streamer/samples/h264/sample-122.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-123.h264
Normal file
BIN
examples/streamer/samples/h264/sample-123.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-124.h264
Normal file
BIN
examples/streamer/samples/h264/sample-124.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-125.h264
Normal file
BIN
examples/streamer/samples/h264/sample-125.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-126.h264
Normal file
BIN
examples/streamer/samples/h264/sample-126.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-127.h264
Normal file
BIN
examples/streamer/samples/h264/sample-127.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-128.h264
Normal file
BIN
examples/streamer/samples/h264/sample-128.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-129.h264
Normal file
BIN
examples/streamer/samples/h264/sample-129.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-13.h264
Normal file
BIN
examples/streamer/samples/h264/sample-13.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-130.h264
Normal file
BIN
examples/streamer/samples/h264/sample-130.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-131.h264
Normal file
BIN
examples/streamer/samples/h264/sample-131.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-132.h264
Normal file
BIN
examples/streamer/samples/h264/sample-132.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-133.h264
Normal file
BIN
examples/streamer/samples/h264/sample-133.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-134.h264
Normal file
BIN
examples/streamer/samples/h264/sample-134.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-135.h264
Normal file
BIN
examples/streamer/samples/h264/sample-135.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-136.h264
Normal file
BIN
examples/streamer/samples/h264/sample-136.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-137.h264
Normal file
BIN
examples/streamer/samples/h264/sample-137.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-138.h264
Normal file
BIN
examples/streamer/samples/h264/sample-138.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-139.h264
Normal file
BIN
examples/streamer/samples/h264/sample-139.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-14.h264
Normal file
BIN
examples/streamer/samples/h264/sample-14.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-140.h264
Normal file
BIN
examples/streamer/samples/h264/sample-140.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-141.h264
Normal file
BIN
examples/streamer/samples/h264/sample-141.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-142.h264
Normal file
BIN
examples/streamer/samples/h264/sample-142.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-143.h264
Normal file
BIN
examples/streamer/samples/h264/sample-143.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-144.h264
Normal file
BIN
examples/streamer/samples/h264/sample-144.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-145.h264
Normal file
BIN
examples/streamer/samples/h264/sample-145.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-146.h264
Normal file
BIN
examples/streamer/samples/h264/sample-146.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-147.h264
Normal file
BIN
examples/streamer/samples/h264/sample-147.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-148.h264
Normal file
BIN
examples/streamer/samples/h264/sample-148.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-149.h264
Normal file
BIN
examples/streamer/samples/h264/sample-149.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-15.h264
Normal file
BIN
examples/streamer/samples/h264/sample-15.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-150.h264
Normal file
BIN
examples/streamer/samples/h264/sample-150.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-151.h264
Normal file
BIN
examples/streamer/samples/h264/sample-151.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-152.h264
Normal file
BIN
examples/streamer/samples/h264/sample-152.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-153.h264
Normal file
BIN
examples/streamer/samples/h264/sample-153.h264
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user