mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
489 Commits
Author | SHA1 | Date | |
---|---|---|---|
10567074c3 | |||
524c56dee9 | |||
efe12f0b73 | |||
82568e3aa0 | |||
7082129b54 | |||
24f8016e4e | |||
98d926a7bf | |||
ffb589d498 | |||
4db6afe9e2 | |||
bdb59905dd | |||
c2b181c6da | |||
3f53365564 | |||
95dfa1015d | |||
1f20f8f1e7 | |||
b2d1a41f7e | |||
9e2e7a7722 | |||
1e02fa34c3 | |||
882c605876 | |||
36a88c605a | |||
09dfc39fd9 | |||
986e5f985f | |||
3e2b0c43ef | |||
557b293934 | |||
7730496bf9 | |||
2b7dc4c529 | |||
b5699239cc | |||
9f3b004756 | |||
8a61043bd7 | |||
93eaa67f5c | |||
690653f8ef | |||
ab392fe0da | |||
669800b39a | |||
4ba8c9e3e8 | |||
1b74ebb0f4 | |||
433d5fbe7f | |||
3204a77e89 | |||
b347afae14 | |||
0482953062 | |||
b5589dbd57 | |||
c23fb10725 | |||
dbfade4eb3 | |||
289b71bc8e | |||
e43c3730a6 | |||
7b206899a4 | |||
c2c57b16df | |||
9805b2fcb5 | |||
569a317bf0 | |||
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 | |||
ee3bc9694b | |||
0c0ba77de5 | |||
8729e0d2aa | |||
12098e7c41 | |||
90eb610bfe | |||
08ddfa1276 | |||
87df64a002 | |||
5af414d0df | |||
2443c72350 | |||
f033e4ab8f | |||
1a6dcdce6f | |||
100039eba8 | |||
e2005c789a | |||
819566b4c1 | |||
82caab8906 | |||
802516b2db | |||
0fcafad9c7 | |||
aab876d346 | |||
11ec8f7247 | |||
1597c9ae6f | |||
b093c4c3d5 | |||
447624322c | |||
422713cbdc | |||
d3d4187021 | |||
f2dd46e589 | |||
5b5debf260 | |||
86c3f914fb | |||
6a1fff13c1 | |||
91a854aa5b | |||
1181fdc599 | |||
fe3d92cebf | |||
c06d77bd8e | |||
c20aebbac2 | |||
9bd12567e6 | |||
bce5faf8ba | |||
64a5d6ecb0 | |||
ed606b7d7e | |||
4a526f66b6 | |||
f8dcfc32ed | |||
cfb10e52e6 | |||
d390c04ca9 | |||
7856ad45fd | |||
fc95b397a6 | |||
db74daae1d | |||
96d6b84c30 | |||
776ff342c0 | |||
519e81727a | |||
2d403247fb | |||
cfddfae1c5 | |||
31b5d6f84a | |||
44bf8b99ef | |||
344895ced1 | |||
9e22770281 | |||
3e2f4a2ac7 | |||
a4fe5cb874 | |||
ece1bd9352 | |||
b7a682cc50 | |||
d47492a54e | |||
fcf33d32a0 | |||
a5eb653064 | |||
ae2abfebad | |||
da4bf1fb49 | |||
82fab04721 | |||
fec3b1ad8b | |||
eb61f6cb3c | |||
fe6cf79f02 | |||
a7bc03c4b3 | |||
06b46aba91 | |||
f83843d054 | |||
e7d45db210 | |||
7a388bdffe | |||
8cb00f81ee | |||
37ebe8cc58 | |||
5eaed06b01 | |||
973f58ec8b | |||
6cb9dd8bad | |||
556104e8d5 | |||
d38ce2b575 | |||
e8628b203f | |||
ffa4e4bb20 | |||
79155d618e | |||
be8ffae0fe | |||
1a723c59aa | |||
6e7c082a7c | |||
037b9c9703 | |||
202d2cb5e4 | |||
03e9eca7d1 | |||
875defd17f | |||
97e23d00be | |||
de47fec19b | |||
c85b9c31fe | |||
be96e8b1fa | |||
2bcdab027c | |||
2eca2c4040 | |||
26cd6e4a59 | |||
be7a6324dd | |||
a7d5ba0232 | |||
3e53706869 | |||
dcb0a0282e | |||
99e78b68a4 | |||
5bb0979c3a | |||
40bed4e740 | |||
8f0c91d1cb | |||
de4192747c | |||
dfe8436954 | |||
7d0d0ea229 | |||
30ca8fb9c3 | |||
4f89e479bc | |||
3faf053bbd | |||
dc91d2cb6d | |||
626ecaa5bb | |||
a88c2dd1bf | |||
ed89fe3e94 | |||
1b73c7fb45 | |||
d563d63c89 | |||
e25bda7108 | |||
12579825a9 | |||
00cccc1e7b | |||
f1fa2abd6e | |||
476528b464 | |||
bc0b14288b | |||
02e4ed5221 | |||
5e876a4686 | |||
3ac3a98e26 | |||
25077878a1 | |||
704d6ab15f | |||
5ce699d33b | |||
e91d721b20 | |||
752c4bf5a1 | |||
bb73da2351 | |||
02105f5da3 | |||
7fac0a7618 | |||
69e5cab0a5 | |||
b220c5df99 | |||
73273d6e81 | |||
61fe8732a6 | |||
9f12c19a02 | |||
72016a7d26 | |||
f40c899b4f | |||
496163dbbe | |||
c0756aaa05 | |||
2cee070cee | |||
5fec28e9b7 | |||
bbec827fef | |||
35e3c7ee3a | |||
0c47c66bb1 | |||
a3cc74c8f1 | |||
db19eded61 | |||
de73af4b80 | |||
3e70af915f | |||
97311230d0 | |||
e966710988 | |||
198d0746b9 | |||
7782f6a5fd | |||
cb71695364 | |||
3af0e3b38b | |||
89e84e7b81 | |||
68bb97b3a3 | |||
41cf60c18b | |||
01eddaca13 | |||
6ec75d7c70 | |||
6221855f27 | |||
a8490f5e1c | |||
55d3336465 | |||
d59c209c20 | |||
46c4bf3ff0 | |||
0227e1c755 | |||
456365bc7c | |||
789ecd7977 | |||
45bbe642fc | |||
2eb523e2ea | |||
5adc62b970 | |||
fcb8d7b3df | |||
1cb53362d1 | |||
0fad9c0a16 | |||
16208d00ca | |||
8dbcd155e5 | |||
46dd2fb515 | |||
129c8b187a | |||
036b972fbe | |||
51bbaa99e1 | |||
37804c0327 | |||
e1f60cd34d | |||
6d6ab9eeb7 | |||
05b7141478 | |||
c3c77010f6 | |||
0a46aa2c6d | |||
e4057c48f6 | |||
d26fa30655 | |||
137d4e3e8e | |||
1a992708a0 | |||
ff76ec1998 | |||
5df78ee3c3 | |||
549e194bb3 | |||
65631f06a6 | |||
3c1b411a76 | |||
8d8374e826 | |||
41194229d3 | |||
fc1f54a0e4 | |||
8fb4997967 | |||
efa6eb8c34 | |||
944e80e85c | |||
2cb3186661 | |||
10d3e2e3d6 | |||
a4afa7382d | |||
efe252dee3 | |||
fd1ca035f9 | |||
62973b6e2f | |||
501fa45560 | |||
1a8d8ec2f9 | |||
b3b4c27bb4 | |||
44eaaf038a | |||
c38edd089b | |||
8ea828f1b0 | |||
35da6636aa | |||
363827594b | |||
cbc027f144 | |||
ebc6a4b65c | |||
37d47d28a8 | |||
46878519c0 | |||
84c298f4f8 | |||
23aed2b844 | |||
df62d6d51c | |||
26241f00b7 | |||
873d14c824 | |||
4953a112ad | |||
c31e1bf0be | |||
98ddba192f | |||
b02b30eea8 | |||
324d97a9b7 | |||
0a1dd4db01 | |||
b1de9acb20 | |||
960300a7cd | |||
3f084d7527 | |||
64096d599c | |||
552e443ef1 | |||
52cb8d68a0 | |||
372e2b7a1f | |||
a92e63720c | |||
8d121c086e | |||
b538e454aa | |||
60d09d5c6f | |||
266159fe41 | |||
458decb12d | |||
635c2e5513 | |||
adc4223617 | |||
326ae27ad1 | |||
6d33d19816 | |||
734efb391a | |||
cba864507f | |||
9b62a543f6 | |||
5a797e1170 | |||
77475b57b0 | |||
dd296e4408 | |||
1be877132c | |||
7e79fd9721 | |||
5d78aecabb | |||
9fbc894fdb | |||
4930e666ac | |||
e5e337a0a5 | |||
704e604b8f | |||
b5d3cdc9b1 | |||
b8b6b913a9 | |||
f461a40a6d |
6
.github/workflows/build-gnutls.yml
vendored
6
.github/workflows/build-gnutls.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: install packages
|
- name: install packages
|
||||||
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev
|
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev libsrtp2-dev
|
||||||
- name: submodules
|
- name: submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: cmake
|
- name: cmake
|
||||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
|
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||||
- name: make
|
- name: make
|
||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: submodules
|
- name: submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: cmake
|
- name: cmake
|
||||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
|
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||||
- name: make
|
- name: make
|
||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
|
16
.github/workflows/build-nice.yml
vendored
16
.github/workflows/build-nice.yml
vendored
@ -7,7 +7,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
build-linux:
|
build-media:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@ -21,4 +21,18 @@ jobs:
|
|||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
run: ./build/tests
|
run: ./build/tests
|
||||||
|
build-no-media:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: install packages
|
||||||
|
run: sudo apt update && sudo apt install libgnutls28-dev libnice-dev
|
||||||
|
- name: submodules
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
- name: cmake
|
||||||
|
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_NICE=1 -DNO_MEDIA=1 -DWARNINGS_AS_ERRORS=1
|
||||||
|
- name: make
|
||||||
|
run: (cd build; make -j2)
|
||||||
|
- name: test
|
||||||
|
run: ./build/tests
|
||||||
|
|
||||||
|
7
.github/workflows/build-openssl.yml
vendored
7
.github/workflows/build-openssl.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: install packages
|
- name: install packages
|
||||||
run: sudo apt update && sudo apt install libssl-dev
|
run: sudo apt update && sudo apt install libssl-dev libsrtp2-dev
|
||||||
- name: submodules
|
- name: submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: cmake
|
- name: cmake
|
||||||
run: cmake -B build -DUSE_GNUTLS=0 -DWARNINGS_AS_ERRORS=1
|
run: cmake -B build -DUSE_GNUTLS=0 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||||
- name: make
|
- name: make
|
||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: submodules
|
- name: submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: cmake
|
- name: cmake
|
||||||
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1
|
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||||
env:
|
env:
|
||||||
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
||||||
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
||||||
@ -52,6 +52,7 @@ jobs:
|
|||||||
- name: nmake
|
- name: nmake
|
||||||
run: |
|
run: |
|
||||||
cd build
|
cd build
|
||||||
|
set CL=/MP
|
||||||
nmake
|
nmake
|
||||||
- name: test
|
- name: test
|
||||||
run: build/tests.exe
|
run: build/tests.exe
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,6 +5,7 @@ node_modules/
|
|||||||
*.a
|
*.a
|
||||||
*.so
|
*.so
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
tests
|
/tests
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -10,3 +10,6 @@
|
|||||||
[submodule "deps/json"]
|
[submodule "deps/json"]
|
||||||
path = deps/json
|
path = deps/json
|
||||||
url = https://github.com/nlohmann/json.git
|
url = https://github.com/nlohmann/json.git
|
||||||
|
[submodule "deps/libsrtp"]
|
||||||
|
path = deps/libsrtp
|
||||||
|
url = https://github.com/cisco/libsrtp.git
|
||||||
|
179
CMakeLists.txt
179
CMakeLists.txt
@ -1,16 +1,20 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.7)
|
||||||
project(libdatachannel
|
project(libdatachannel
|
||||||
DESCRIPTION "WebRTC Data Channels Library"
|
VERSION 0.11.2
|
||||||
VERSION 0.9.2
|
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||||
option(USE_NICE "Use libnice instead of libjuice" OFF)
|
option(USE_NICE "Use libnice instead of libjuice" OFF)
|
||||||
|
option(USE_SYSTEM_SRTP "Use system libSRTP" OFF)
|
||||||
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
||||||
|
option(NO_MEDIA "Disable media transport support" OFF)
|
||||||
option(NO_EXAMPLES "Disable examples" OFF)
|
option(NO_EXAMPLES "Disable examples" OFF)
|
||||||
option(NO_TESTS "Disable tests build" OFF)
|
option(NO_TESTS "Disable tests build" OFF)
|
||||||
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||||
|
option(RSA_KEY_BITS_2048 "Use 2048-bit RSA key instead of 3072-bit" OFF)
|
||||||
|
option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
|
||||||
|
|
||||||
if(USE_NICE)
|
if(USE_NICE)
|
||||||
option(USE_JUICE "Use libjuice" OFF)
|
option(USE_JUICE "Use libjuice" OFF)
|
||||||
@ -19,17 +23,21 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_GNUTLS)
|
if(USE_GNUTLS)
|
||||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
option(USE_NETTLE "Use Nettle in libjuice" ON)
|
||||||
else()
|
else()
|
||||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
|
option(USE_NETTLE "Use Nettle in libjuice" OFF)
|
||||||
|
if(NOT USE_SYSTEM_SRTP)
|
||||||
|
option(ENABLE_OPENSSL "Enable OpenSSL crypto engine for SRTP" ON)
|
||||||
|
endif()
|
||||||
endif()
|
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)
|
||||||
@ -50,13 +58,39 @@ 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/rtcpsrreporter.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
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/mediachainablehandler.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/mediahandlerelement.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/mediahandlerrootelement.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpnackresponder.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(LIBDATACHANNEL_PRIVATE_HEADERS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlssrtptransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/logcounter.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/transport.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
||||||
@ -68,6 +102,14 @@ set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/wstransport.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/wstransport.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(LIBDATACHANNEL_WEBSOCKET_PRIVATE_HEADERS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/base64.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tcptransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tlstransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/verifiedtlstransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/wstransport.hpp
|
||||||
|
)
|
||||||
|
|
||||||
set(LIBDATACHANNEL_HEADERS
|
set(LIBDATACHANNEL_HEADERS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp
|
||||||
@ -75,7 +117,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/mediahandler.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
|
||||||
@ -85,13 +128,27 @@ set(LIBDATACHANNEL_HEADERS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/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/rtcpsrreporter.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
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediachainablehandler.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandlerelement.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandlerrootelement.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpnackresponder.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
|
||||||
@ -99,6 +156,24 @@ set(TESTS_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/benchmark.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/benchmark.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(TESTS_UWP_RESOURCES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/Logo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/package.appxManifest
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/SmallLogo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/SmallLogo44x44.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/SplashScreen.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/StoreLogo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/Windows_TemporaryKey.pfx)
|
||||||
|
|
||||||
|
set(BENCHMARK_UWP_RESOURCES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/Logo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/package.appxManifest
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/SmallLogo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/SmallLogo44x44.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/SplashScreen.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/StoreLogo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/Windows_TemporaryKey.pfx)
|
||||||
|
|
||||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
@ -107,32 +182,40 @@ set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
|||||||
add_subdirectory(deps/plog)
|
add_subdirectory(deps/plog)
|
||||||
|
|
||||||
option(sctp_build_programs 0)
|
option(sctp_build_programs 0)
|
||||||
|
option(sctp_build_shared_lib 0)
|
||||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||||
if (MSYS OR MINGW)
|
if (MSYS OR MINGW)
|
||||||
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||||
target_compile_definitions(usrsctp-static PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
|
||||||
endif()
|
endif()
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||||
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
|
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
|
||||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
|
|
||||||
endif()
|
endif()
|
||||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
|
||||||
|
|
||||||
if (NO_WEBSOCKET)
|
if (NO_WEBSOCKET)
|
||||||
add_library(datachannel SHARED
|
add_library(datachannel SHARED
|
||||||
${LIBDATACHANNEL_SOURCES})
|
${LIBDATACHANNEL_SOURCES}
|
||||||
|
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_HEADERS})
|
||||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
||||||
${LIBDATACHANNEL_SOURCES})
|
${LIBDATACHANNEL_SOURCES}
|
||||||
|
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_HEADERS})
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
||||||
else()
|
else()
|
||||||
add_library(datachannel SHARED
|
add_library(datachannel SHARED
|
||||||
${LIBDATACHANNEL_SOURCES}
|
${LIBDATACHANNEL_SOURCES}
|
||||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_WEBSOCKET_SOURCES}
|
||||||
|
${LIBDATACHANNEL_WEBSOCKET_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_HEADERS})
|
||||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
||||||
${LIBDATACHANNEL_SOURCES}
|
${LIBDATACHANNEL_SOURCES}
|
||||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_WEBSOCKET_SOURCES}
|
||||||
|
${LIBDATACHANNEL_WEBSOCKET_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_HEADERS})
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
||||||
endif()
|
endif()
|
||||||
@ -148,21 +231,27 @@ target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includ
|
|||||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
target_link_libraries(datachannel PUBLIC Threads::Threads plog::plog)
|
target_link_libraries(datachannel PUBLIC Threads::Threads plog::plog)
|
||||||
target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic)
|
target_link_libraries(datachannel PRIVATE Usrsctp::Usrsctp)
|
||||||
|
|
||||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
|
target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
|
||||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
|
target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
|
target_link_libraries(datachannel PUBLIC ws2_32) # winsock2
|
||||||
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
target_link_libraries(datachannel-static PUBLIC ws2_32) # winsock2
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(SRTP)
|
if(NO_MEDIA)
|
||||||
if(SRTP_FOUND)
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
|
||||||
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
|
||||||
|
else()
|
||||||
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||||
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||||
|
if(USE_SYSTEM_SRTP)
|
||||||
|
find_package(SRTP REQUIRED)
|
||||||
if(NOT TARGET SRTP::SRTP)
|
if(NOT TARGET SRTP::SRTP)
|
||||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||||
set_target_properties(SRTP::SRTP PROPERTIES
|
set_target_properties(SRTP::SRTP PROPERTIES
|
||||||
@ -170,15 +259,17 @@ if(SRTP_FOUND)
|
|||||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||||
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
message(STATUS "LibSRTP found, compiling with media transport")
|
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=1)
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=1)
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
|
||||||
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
||||||
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
||||||
else()
|
else()
|
||||||
message(STATUS "LibSRTP NOT found, compiling WITHOUT media transport")
|
add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
|
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=0)
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
|
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=0)
|
||||||
|
target_link_libraries(datachannel PRIVATE srtp2)
|
||||||
|
target_link_libraries(datachannel-static PRIVATE srtp2)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_GNUTLS)
|
if (USE_GNUTLS)
|
||||||
@ -217,6 +308,16 @@ else()
|
|||||||
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(RSA_KEY_BITS_2048)
|
||||||
|
target_compile_definitions(datachannel PUBLIC RSA_KEY_BITS_2048)
|
||||||
|
target_compile_definitions(datachannel-static PUBLIC RSA_KEY_BITS_2048)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CAPI_STDCALL)
|
||||||
|
target_compile_definitions(datachannel PUBLIC CAPI_STDCALL)
|
||||||
|
target_compile_definitions(datachannel-static PUBLIC CAPI_STDCALL)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||||
|
|
||||||
@ -240,39 +341,45 @@ endif()
|
|||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
if(NOT NO_TESTS)
|
if(NOT NO_TESTS)
|
||||||
|
if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
||||||
|
# Add resource files needed for UWP apps.
|
||||||
|
add_executable(datachannel-tests ${TESTS_SOURCES} ${TESTS_UWP_RESOURCES})
|
||||||
|
else()
|
||||||
add_executable(datachannel-tests ${TESTS_SOURCES})
|
add_executable(datachannel-tests ${TESTS_SOURCES})
|
||||||
|
endif()
|
||||||
set_target_properties(datachannel-tests PROPERTIES
|
set_target_properties(datachannel-tests PROPERTIES
|
||||||
VERSION ${PROJECT_VERSION}
|
VERSION ${PROJECT_VERSION}
|
||||||
CXX_STANDARD 17)
|
CXX_STANDARD 17)
|
||||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||||
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-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-tests datachannel)
|
target_link_libraries(datachannel-tests datachannel)
|
||||||
endif()
|
|
||||||
|
|
||||||
# Benchmark
|
# Benchmark
|
||||||
|
if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
||||||
|
# Add resource files needed for UWP apps.
|
||||||
|
add_executable(datachannel-benchmark test/benchmark.cpp ${BENCHMARK_UWP_RESOURCES})
|
||||||
|
else()
|
||||||
add_executable(datachannel-benchmark test/benchmark.cpp)
|
add_executable(datachannel-benchmark test/benchmark.cpp)
|
||||||
|
endif()
|
||||||
set_target_properties(datachannel-benchmark PROPERTIES
|
set_target_properties(datachannel-benchmark PROPERTIES
|
||||||
VERSION ${PROJECT_VERSION}
|
VERSION ${PROJECT_VERSION}
|
||||||
CXX_STANDARD 17)
|
CXX_STANDARD 17)
|
||||||
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
||||||
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-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-benchmark datachannel)
|
target_link_libraries(datachannel-benchmark datachannel)
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
if(NOT NO_EXAMPLES)
|
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/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
|
||||||
|
41
README.md
41
README.md
@ -19,17 +19,17 @@ The WebRTC stack has been tested to be compatible with Firefox and Chromium.
|
|||||||
|
|
||||||
Protocol stack:
|
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)) with [libSRTP](https://github.com/cisco/libsrtp)
|
- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26))
|
||||||
- DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261))
|
- 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)).
|
||||||
|
|
||||||
@ -53,10 +53,11 @@ Dependencies:
|
|||||||
Submodules:
|
Submodules:
|
||||||
- libjuice: https://github.com/paullouisageneau/libjuice
|
- libjuice: https://github.com/paullouisageneau/libjuice
|
||||||
- usrsctp: https://github.com/sctplab/usrsctp
|
- usrsctp: https://github.com/sctplab/usrsctp
|
||||||
|
- libsrtp: https://github.com/cisco/libsrtp
|
||||||
|
|
||||||
Optional dependencies:
|
Optional dependencies:
|
||||||
- libnice: https://nice.freedesktop.org/ (only if selected as ICE backend instead of libjuice)
|
- libnice: https://nice.freedesktop.org/ (if selected as ICE backend instead of libjuice)
|
||||||
- libSRTP: https://github.com/cisco/libsrtp (only necessary for supporting media transport)
|
- libsrtp: https://github.com/cisco/libsrtp (if selected instead of the submodule)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
@ -72,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
|
||||||
@ -81,6 +82,29 @@ $ cd build
|
|||||||
$ make -j2
|
$ make -j2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Apple macOS with XCode project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cmake -B "$BUILD_DIR" -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode
|
||||||
|
```
|
||||||
|
|
||||||
|
Xcode project is generated in *build/* directory.
|
||||||
|
|
||||||
|
##### Solving **Could NOT find OpenSSL** error
|
||||||
|
|
||||||
|
You need to add OpenSSL root directory if your build fails with the following message:
|
||||||
|
|
||||||
|
```
|
||||||
|
Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the
|
||||||
|
system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY
|
||||||
|
OPENSSL_INCLUDE_DIR)
|
||||||
|
```
|
||||||
|
|
||||||
|
for example:
|
||||||
|
```bash
|
||||||
|
$ cmake -B build -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl\@1.1/1.1.1h/
|
||||||
|
```
|
||||||
|
|
||||||
#### Microsoft Windows with MinGW cross-compilation
|
#### Microsoft Windows with MinGW cross-compilation
|
||||||
```bash
|
```bash
|
||||||
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
|
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
|
||||||
@ -99,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
|
||||||
```
|
```
|
||||||
@ -202,5 +228,6 @@ ws->open("wss://my.websocket/service");
|
|||||||
## External resources
|
## External resources
|
||||||
- Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
- Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
||||||
- Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
|
- Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
|
||||||
|
- Unity wrapper for Windows 10 and Hololens: [datachannel-unity](https://github.com/hanseuljun/datachannel-unity)
|
||||||
- WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
|
- WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
|
||||||
|
|
||||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: 33612d14ae...7a9f10e4f7
1
deps/libsrtp
vendored
Submodule
1
deps/libsrtp
vendored
Submodule
Submodule deps/libsrtp added at f379f48412
2
deps/usrsctp
vendored
2
deps/usrsctp
vendored
Submodule deps/usrsctp updated: 0db9691000...2e754d5822
@ -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
|
||||||
|
@ -4,19 +4,21 @@ if(POLICY CMP0079)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
|
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
|
||||||
target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
|
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)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET datachannel-client POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-client>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
@ -21,6 +21,7 @@ Revisions:
|
|||||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||||
|
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||||
|
|
||||||
**DISCLAIMER**
|
**DISCLAIMER**
|
||||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||||
@ -35,7 +36,11 @@ PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
|||||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||||
#define _CRT_SECURE_NO_WARNINGS
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
@ -52,12 +57,6 @@ int opterr = 1;
|
|||||||
int optopt = '?';
|
int optopt = '?';
|
||||||
enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER };
|
enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER };
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Ansi structures and functions follow
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
static struct _getopt_data_a
|
static struct _getopt_data_a
|
||||||
{
|
{
|
||||||
int optind;
|
int optind;
|
||||||
@ -512,462 +511,3 @@ int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, con
|
|||||||
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0);
|
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Unicode Structures and Functions
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
static struct _getopt_data_w
|
|
||||||
{
|
|
||||||
int optind;
|
|
||||||
int opterr;
|
|
||||||
int optopt;
|
|
||||||
wchar_t *optarg;
|
|
||||||
int __initialized;
|
|
||||||
wchar_t *__nextchar;
|
|
||||||
enum ENUM_ORDERING __ordering;
|
|
||||||
int __posixly_correct;
|
|
||||||
int __first_nonopt;
|
|
||||||
int __last_nonopt;
|
|
||||||
} getopt_data_w;
|
|
||||||
wchar_t *optarg_w;
|
|
||||||
|
|
||||||
static void exchange_w(wchar_t **argv, struct _getopt_data_w *d)
|
|
||||||
{
|
|
||||||
int bottom = d->__first_nonopt;
|
|
||||||
int middle = d->__last_nonopt;
|
|
||||||
int top = d->optind;
|
|
||||||
wchar_t *tem;
|
|
||||||
while (top > middle && middle > bottom)
|
|
||||||
{
|
|
||||||
if (top - middle > middle - bottom)
|
|
||||||
{
|
|
||||||
int len = middle - bottom;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
tem = argv[bottom + i];
|
|
||||||
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
|
||||||
argv[top - (middle - bottom) + i] = tem;
|
|
||||||
}
|
|
||||||
top -= len;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int len = top - middle;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
tem = argv[bottom + i];
|
|
||||||
argv[bottom + i] = argv[middle + i];
|
|
||||||
argv[middle + i] = tem;
|
|
||||||
}
|
|
||||||
bottom += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
|
||||||
d->__last_nonopt = d->optind;
|
|
||||||
}
|
|
||||||
static const wchar_t *_getopt_initialize_w (const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct)
|
|
||||||
{
|
|
||||||
d->__first_nonopt = d->__last_nonopt = d->optind;
|
|
||||||
d->__nextchar = NULL;
|
|
||||||
d->__posixly_correct = posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT");
|
|
||||||
if (optstring[0] == L'-')
|
|
||||||
{
|
|
||||||
d->__ordering = RETURN_IN_ORDER;
|
|
||||||
++optstring;
|
|
||||||
}
|
|
||||||
else if (optstring[0] == L'+')
|
|
||||||
{
|
|
||||||
d->__ordering = REQUIRE_ORDER;
|
|
||||||
++optstring;
|
|
||||||
}
|
|
||||||
else if (d->__posixly_correct)
|
|
||||||
d->__ordering = REQUIRE_ORDER;
|
|
||||||
else
|
|
||||||
d->__ordering = PERMUTE;
|
|
||||||
return optstring;
|
|
||||||
}
|
|
||||||
int _getopt_internal_r_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct)
|
|
||||||
{
|
|
||||||
int print_errors = d->opterr;
|
|
||||||
if (argc < 1)
|
|
||||||
return -1;
|
|
||||||
d->optarg = NULL;
|
|
||||||
if (d->optind == 0 || !d->__initialized)
|
|
||||||
{
|
|
||||||
if (d->optind == 0)
|
|
||||||
d->optind = 1;
|
|
||||||
optstring = _getopt_initialize_w (optstring, d, posixly_correct);
|
|
||||||
d->__initialized = 1;
|
|
||||||
}
|
|
||||||
else if (optstring[0] == L'-' || optstring[0] == L'+')
|
|
||||||
optstring++;
|
|
||||||
if (optstring[0] == L':')
|
|
||||||
print_errors = 0;
|
|
||||||
if (d->__nextchar == NULL || *d->__nextchar == L'\0')
|
|
||||||
{
|
|
||||||
if (d->__last_nonopt > d->optind)
|
|
||||||
d->__last_nonopt = d->optind;
|
|
||||||
if (d->__first_nonopt > d->optind)
|
|
||||||
d->__first_nonopt = d->optind;
|
|
||||||
if (d->__ordering == PERMUTE)
|
|
||||||
{
|
|
||||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
|
||||||
exchange_w((wchar_t **) argv, d);
|
|
||||||
else if (d->__last_nonopt != d->optind)
|
|
||||||
d->__first_nonopt = d->optind;
|
|
||||||
while (d->optind < argc && (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
|
|
||||||
d->optind++;
|
|
||||||
d->__last_nonopt = d->optind;
|
|
||||||
}
|
|
||||||
if (d->optind != argc && !wcscmp(argv[d->optind], L"--"))
|
|
||||||
{
|
|
||||||
d->optind++;
|
|
||||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
|
||||||
exchange_w((wchar_t **) argv, d);
|
|
||||||
else if (d->__first_nonopt == d->__last_nonopt)
|
|
||||||
d->__first_nonopt = d->optind;
|
|
||||||
d->__last_nonopt = argc;
|
|
||||||
d->optind = argc;
|
|
||||||
}
|
|
||||||
if (d->optind == argc)
|
|
||||||
{
|
|
||||||
if (d->__first_nonopt != d->__last_nonopt)
|
|
||||||
d->optind = d->__first_nonopt;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if ((argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
|
|
||||||
{
|
|
||||||
if (d->__ordering == REQUIRE_ORDER)
|
|
||||||
return -1;
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == L'-'));
|
|
||||||
}
|
|
||||||
if (longopts != NULL && (argv[d->optind][1] == L'-' || (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1])))))
|
|
||||||
{
|
|
||||||
wchar_t *nameend;
|
|
||||||
unsigned int namelen;
|
|
||||||
const struct option_w *p;
|
|
||||||
const struct option_w *pfound = NULL;
|
|
||||||
struct option_list
|
|
||||||
{
|
|
||||||
const struct option_w *p;
|
|
||||||
struct option_list *next;
|
|
||||||
} *ambig_list = NULL;
|
|
||||||
int exact = 0;
|
|
||||||
int indfound = -1;
|
|
||||||
int option_index;
|
|
||||||
for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++);
|
|
||||||
namelen = (unsigned int)(nameend - d->__nextchar);
|
|
||||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
|
||||||
if (!wcsncmp(p->name, d->__nextchar, namelen))
|
|
||||||
{
|
|
||||||
if (namelen == (unsigned int)wcslen(p->name))
|
|
||||||
{
|
|
||||||
pfound = p;
|
|
||||||
indfound = option_index;
|
|
||||||
exact = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (pfound == NULL)
|
|
||||||
{
|
|
||||||
pfound = p;
|
|
||||||
indfound = option_index;
|
|
||||||
}
|
|
||||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
|
||||||
{
|
|
||||||
struct option_list *newp = (struct option_list*)alloca(sizeof(*newp));
|
|
||||||
newp->p = p;
|
|
||||||
newp->next = ambig_list;
|
|
||||||
ambig_list = newp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ambig_list != NULL && !exact)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
struct option_list first;
|
|
||||||
first.p = pfound;
|
|
||||||
first.next = ambig_list;
|
|
||||||
ambig_list = &first;
|
|
||||||
fwprintf(stderr, L"%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
fwprintf (stderr, L" '--%s'", ambig_list->p->name);
|
|
||||||
ambig_list = ambig_list->next;
|
|
||||||
}
|
|
||||||
while (ambig_list != NULL);
|
|
||||||
fputwc (L'\n', stderr);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
d->optind++;
|
|
||||||
d->optopt = 0;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
if (pfound != NULL)
|
|
||||||
{
|
|
||||||
option_index = indfound;
|
|
||||||
d->optind++;
|
|
||||||
if (*nameend)
|
|
||||||
{
|
|
||||||
if (pfound->has_arg)
|
|
||||||
d->optarg = nameend + 1;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
if (argv[d->optind - 1][1] == L'-')
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
d->optopt = pfound->val;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pfound->has_arg == 1)
|
|
||||||
{
|
|
||||||
if (d->optind < argc)
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr,L"%s: option '--%s' requires an argument\n",argv[0], pfound->name);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
d->optopt = pfound->val;
|
|
||||||
return optstring[0] == L':' ? L':' : L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
if (longind != NULL)
|
|
||||||
*longind = option_index;
|
|
||||||
if (pfound->flag)
|
|
||||||
{
|
|
||||||
*(pfound->flag) = pfound->val;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return pfound->val;
|
|
||||||
}
|
|
||||||
if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
if (argv[d->optind][1] == L'-')
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: unrecognized option '--%s'\n",argv[0], d->__nextchar);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d->__nextchar = (wchar_t *)L"";
|
|
||||||
d->optind++;
|
|
||||||
d->optopt = 0;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
wchar_t c = *d->__nextchar++;
|
|
||||||
wchar_t *temp = (wchar_t*)wcschr(optstring, c);
|
|
||||||
if (*d->__nextchar == L'\0')
|
|
||||||
++d->optind;
|
|
||||||
if (temp == NULL || c == L':' || c == L';')
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c);
|
|
||||||
}
|
|
||||||
d->optopt = c;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
if (temp[0] == L'W' && temp[1] == L';')
|
|
||||||
{
|
|
||||||
wchar_t *nameend;
|
|
||||||
const struct option_w *p;
|
|
||||||
const struct option_w *pfound = NULL;
|
|
||||||
int exact = 0;
|
|
||||||
int ambig = 0;
|
|
||||||
int indfound = 0;
|
|
||||||
int option_index;
|
|
||||||
if (longopts == NULL)
|
|
||||||
goto no_longs;
|
|
||||||
if (*d->__nextchar != L'\0')
|
|
||||||
{
|
|
||||||
d->optarg = d->__nextchar;
|
|
||||||
d->optind++;
|
|
||||||
}
|
|
||||||
else if (d->optind == argc)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
|
|
||||||
}
|
|
||||||
d->optopt = c;
|
|
||||||
if (optstring[0] == L':')
|
|
||||||
c = L':';
|
|
||||||
else
|
|
||||||
c = L'?';
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != L'='; nameend++);
|
|
||||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
|
||||||
if (!wcsncmp(p->name, d->__nextchar, nameend - d->__nextchar))
|
|
||||||
{
|
|
||||||
if ((unsigned int) (nameend - d->__nextchar) == wcslen(p->name))
|
|
||||||
{
|
|
||||||
pfound = p;
|
|
||||||
indfound = option_index;
|
|
||||||
exact = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (pfound == NULL)
|
|
||||||
{
|
|
||||||
pfound = p;
|
|
||||||
indfound = option_index;
|
|
||||||
}
|
|
||||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
|
||||||
ambig = 1;
|
|
||||||
}
|
|
||||||
if (ambig && !exact)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '-W %s' is ambiguous\n",argv[0], d->optarg);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
d->optind++;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
if (pfound != NULL)
|
|
||||||
{
|
|
||||||
option_index = indfound;
|
|
||||||
if (*nameend)
|
|
||||||
{
|
|
||||||
if (pfound->has_arg)
|
|
||||||
d->optarg = nameend + 1;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pfound->has_arg == 1)
|
|
||||||
{
|
|
||||||
if (d->optind < argc)
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '-W %s' requires an argument\n",argv[0], pfound->name);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
return optstring[0] == L':' ? L':' : L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
d->optarg = NULL;
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
if (longind != NULL)
|
|
||||||
*longind = option_index;
|
|
||||||
if (pfound->flag)
|
|
||||||
{
|
|
||||||
*(pfound->flag) = pfound->val;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return pfound->val;
|
|
||||||
}
|
|
||||||
no_longs:
|
|
||||||
d->__nextchar = NULL;
|
|
||||||
return L'W';
|
|
||||||
}
|
|
||||||
if (temp[1] == L':')
|
|
||||||
{
|
|
||||||
if (temp[2] == L':')
|
|
||||||
{
|
|
||||||
if (*d->__nextchar != L'\0')
|
|
||||||
{
|
|
||||||
d->optarg = d->__nextchar;
|
|
||||||
d->optind++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
d->optarg = NULL;
|
|
||||||
d->__nextchar = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (*d->__nextchar != L'\0')
|
|
||||||
{
|
|
||||||
d->optarg = d->__nextchar;
|
|
||||||
d->optind++;
|
|
||||||
}
|
|
||||||
else if (d->optind == argc)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
|
|
||||||
}
|
|
||||||
d->optopt = c;
|
|
||||||
if (optstring[0] == L':')
|
|
||||||
c = L':';
|
|
||||||
else
|
|
||||||
c = L'?';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
d->__nextchar = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int _getopt_internal_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct)
|
|
||||||
{
|
|
||||||
int result;
|
|
||||||
getopt_data_w.optind = optind;
|
|
||||||
getopt_data_w.opterr = opterr;
|
|
||||||
result = _getopt_internal_r_w (argc, argv, optstring, longopts,longind, long_only, &getopt_data_w,posixly_correct);
|
|
||||||
optind = getopt_data_w.optind;
|
|
||||||
optarg_w = getopt_data_w.optarg;
|
|
||||||
optopt = getopt_data_w.optopt;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int getopt_w (int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW
|
|
||||||
{
|
|
||||||
return _getopt_internal_w (argc, argv, optstring, (const struct option_w *) 0, (int *) 0, 0, 0);
|
|
||||||
}
|
|
||||||
int getopt_long_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
|
|
||||||
{
|
|
||||||
return _getopt_internal_w (argc, argv, options, long_options, opt_index, 0, 0);
|
|
||||||
}
|
|
||||||
int getopt_long_only_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
|
|
||||||
{
|
|
||||||
return _getopt_internal_w (argc, argv, options, long_options, opt_index, 1, 0);
|
|
||||||
}
|
|
||||||
int _getopt_long_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
|
|
||||||
{
|
|
||||||
return _getopt_internal_r_w (argc, argv, options, long_options, opt_index,0, d, 0);
|
|
||||||
}
|
|
||||||
int _getopt_long_only_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
|
|
||||||
{
|
|
||||||
return _getopt_internal_r_w (argc, argv, options, long_options, opt_index, 1, d, 0);
|
|
||||||
}
|
|
@ -21,6 +21,7 @@ Revisions:
|
|||||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||||
|
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||||
|
|
||||||
**DISCLAIMER**
|
**DISCLAIMER**
|
||||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||||
@ -79,7 +80,6 @@ EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|||||||
#define ARG_OPT 2 /*Argument Optional*/
|
#define ARG_OPT 2 /*Argument Optional*/
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <wchar.h>
|
|
||||||
|
|
||||||
_BEGIN_EXTERN_C
|
_BEGIN_EXTERN_C
|
||||||
|
|
||||||
@ -87,7 +87,6 @@ _BEGIN_EXTERN_C
|
|||||||
extern _GETOPT_API int opterr;
|
extern _GETOPT_API int opterr;
|
||||||
extern _GETOPT_API int optopt;
|
extern _GETOPT_API int optopt;
|
||||||
|
|
||||||
// Ansi
|
|
||||||
struct option_a
|
struct option_a
|
||||||
{
|
{
|
||||||
const char* name;
|
const char* name;
|
||||||
@ -100,19 +99,6 @@ _BEGIN_EXTERN_C
|
|||||||
extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||||
extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||||
|
|
||||||
// Unicode
|
|
||||||
struct option_w
|
|
||||||
{
|
|
||||||
const wchar_t* name;
|
|
||||||
int has_arg;
|
|
||||||
int *flag;
|
|
||||||
int val;
|
|
||||||
};
|
|
||||||
extern _GETOPT_API wchar_t *optarg_w;
|
|
||||||
extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW;
|
|
||||||
extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
|
||||||
extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
|
||||||
|
|
||||||
_END_EXTERN_C
|
_END_EXTERN_C
|
||||||
|
|
||||||
#undef _BEGIN_EXTERN_C
|
#undef _BEGIN_EXTERN_C
|
||||||
@ -120,17 +106,10 @@ _END_EXTERN_C
|
|||||||
#undef _GETOPT_THROW
|
#undef _GETOPT_THROW
|
||||||
#undef _GETOPT_API
|
#undef _GETOPT_API
|
||||||
|
|
||||||
#ifdef _UNICODE
|
|
||||||
#define getopt getopt_w
|
|
||||||
#define getopt_long getopt_long_w
|
|
||||||
#define getopt_long_only getopt_long_only_w
|
|
||||||
#define option option_w
|
|
||||||
#define optarg optarg_w
|
|
||||||
#else
|
|
||||||
#define getopt getopt_a
|
#define getopt getopt_a
|
||||||
#define getopt_long getopt_long_a
|
#define getopt_long getopt_long_a
|
||||||
#define getopt_long_only getopt_long_only_a
|
#define getopt_long_only getopt_long_only_a
|
||||||
#define option option_a
|
#define option option_a
|
||||||
#define optarg optarg_a
|
#define optarg optarg_a
|
||||||
#endif
|
|
||||||
#endif // __GETOPT_H_
|
#endif // __GETOPT_H_
|
||||||
|
@ -23,15 +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 <stdexcept>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "parse_cl.h"
|
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -45,31 +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 confirmOnStdout(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) {
|
int main(int argc, char **argv) try {
|
||||||
Cmdline *params = nullptr;
|
auto params = std::make_unique<Cmdline>(argc, argv);
|
||||||
try {
|
|
||||||
params = new Cmdline(argc, argv);
|
|
||||||
} catch (const std::range_error&e) {
|
|
||||||
std::cout<< e.what() << '\n';
|
|
||||||
delete params;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 suported." << 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());
|
||||||
@ -80,18 +75,23 @@ int main(int argc, char **argv) {
|
|||||||
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>();
|
||||||
|
|
||||||
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
std::promise<void> wsPromise;
|
||||||
|
auto wsFuture = wsPromise.get_future();
|
||||||
|
|
||||||
|
ws->onOpen([&wsPromise]() {
|
||||||
|
cout << "WebSocket connected, signaling ready" << endl;
|
||||||
|
wsPromise.set_value();
|
||||||
|
});
|
||||||
|
|
||||||
|
ws->onError([&wsPromise](string s) {
|
||||||
|
cout << "WebSocket error" << endl;
|
||||||
|
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
|
||||||
|
});
|
||||||
|
|
||||||
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
||||||
|
|
||||||
ws->onError([](const string &error) { cout << "WebSocket failed: " << error << endl; });
|
|
||||||
|
|
||||||
ws->onMessage([&](variant<binary, string> data) {
|
ws->onMessage([&](variant<binary, string> data) {
|
||||||
if (!holds_alternative<string>(data))
|
if (!holds_alternative<string>(data))
|
||||||
return;
|
return;
|
||||||
@ -129,7 +129,7 @@ int main(int argc, char **argv) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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() + ":" +
|
||||||
@ -138,11 +138,7 @@ int main(int argc, char **argv) {
|
|||||||
ws->open(url);
|
ws->open(url);
|
||||||
|
|
||||||
cout << "Waiting for signaling to be connected..." << endl;
|
cout << "Waiting for signaling to be connected..." << endl;
|
||||||
while (!ws->isOpen()) {
|
wsFuture.get();
|
||||||
if (ws->isClosed())
|
|
||||||
return 1;
|
|
||||||
this_thread::sleep_for(100ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
string id;
|
string id;
|
||||||
@ -170,33 +166,28 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
confirmOnStdout(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
|
||||||
get<string>(message).length());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dataChannelMap.emplace(id, dc);
|
dataChannelMap.emplace(id, dc);
|
||||||
|
|
||||||
this_thread::sleep_for(1s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cout << "Cleaning up..." << endl;
|
cout << "Cleaning up..." << endl;
|
||||||
|
|
||||||
dataChannelMap.clear();
|
dataChannelMap.clear();
|
||||||
peerConnectionMap.clear();
|
peerConnectionMap.clear();
|
||||||
delete params;
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cout << "Error: " << e.what() << std::endl;
|
||||||
|
dataChannelMap.clear();
|
||||||
|
peerConnectionMap.clear();
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and setup a PeerConnection
|
// Create and setup a PeerConnection
|
||||||
@ -233,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;
|
|
||||||
}
|
|
||||||
confirmOnStdout(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
|
||||||
get<string>(message).length());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dc->send("Hello from " + localId);
|
dc->send("Hello from " + localId);
|
||||||
@ -258,19 +241,6 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
|||||||
return pc;
|
return pc;
|
||||||
};
|
};
|
||||||
|
|
||||||
void confirmOnStdout(bool echoed, string id, string type, size_t length) {
|
|
||||||
static long count = 0;
|
|
||||||
static long freq = 100;
|
|
||||||
if (!(++count%freq)) {
|
|
||||||
cout << "Received " << count << " pings in total from host " << id << ", most recent of type "
|
|
||||||
<< 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
|
||||||
|
@ -13,3 +13,15 @@ set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
|
|||||||
OUTPUT_NAME answerer)
|
OUTPUT_NAME answerer)
|
||||||
target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)
|
target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET datachannel-copy-paste-capi-offerer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-copy-paste-capi-offerer>
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET datachannel-copy-paste-capi-answerer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-copy-paste-capi-answerer>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
@ -41,16 +41,16 @@ typedef struct {
|
|||||||
bool connected;
|
bool connected;
|
||||||
} Peer;
|
} Peer;
|
||||||
|
|
||||||
static void dataChannelCallback(int dc, void *ptr);
|
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr);
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||||
static void stateChangeCallback(rtcState state, void *ptr);
|
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||||
static void closedCallback(void *ptr);
|
static void RTC_API closedCallback(int id, void *ptr);
|
||||||
static void messageCallback(const char *message, int size, void *ptr);
|
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||||
static void deletePeer(Peer *peer);
|
static void deletePeer(Peer *peer);
|
||||||
|
|
||||||
char* state_print(rtcState state);
|
char *state_print(rtcState state);
|
||||||
char *rtcGatheringState_print(rtcGatheringState state);
|
char *rtcGatheringState_print(rtcGatheringState state);
|
||||||
|
|
||||||
int all_space(const char *str);
|
int all_space(const char *str);
|
||||||
@ -88,7 +88,8 @@ int main(int argc, char **argv) {
|
|||||||
bool exit = false;
|
bool exit = false;
|
||||||
while (!exit) {
|
while (!exit) {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("***************************************************************************************\n");
|
printf("***********************************************************************************"
|
||||||
|
"****\n");
|
||||||
printf("* 0: Exit /"
|
printf("* 0: Exit /"
|
||||||
" 1: Enter remote description /"
|
" 1: Enter remote description /"
|
||||||
" 2: Enter remote candidate /"
|
" 2: Enter remote candidate /"
|
||||||
@ -118,27 +119,25 @@ int main(int argc, char **argv) {
|
|||||||
char *line = NULL;
|
char *line = NULL;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
size_t read = 0;
|
size_t read = 0;
|
||||||
char *sdp = (char*) malloc(sizeof(char));
|
char *sdp = (char *)malloc(sizeof(char));
|
||||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||||
strcat(sdp, line);
|
strcat(sdp, line);
|
||||||
|
|
||||||
}
|
}
|
||||||
printf("%s\n",sdp);
|
printf("%s\n", sdp);
|
||||||
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
||||||
free(sdp);
|
free(sdp);
|
||||||
free(line);
|
free(line);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
// Parse Candidate
|
// Parse Candidate
|
||||||
printf("[Candidate]: ");
|
printf("[Candidate]: ");
|
||||||
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 {
|
||||||
@ -150,15 +149,15 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
case 3: {
|
case 3: {
|
||||||
// Send Message
|
// Send Message
|
||||||
if(!peer->connected) {
|
if (!peer->connected) {
|
||||||
printf("** Channel is not Open **");
|
printf("** Channel is not Open **");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("[Message]: ");
|
printf("[Message]: ");
|
||||||
char* message = NULL;
|
char *message = NULL;
|
||||||
size_t message_size = 0;
|
size_t message_size = 0;
|
||||||
|
|
||||||
if(getline(&message, &message_size, stdin)) {
|
if (getline(&message, &message_size, stdin)) {
|
||||||
rtcSendMessage(peer->dc, message, -1);
|
rtcSendMessage(peer->dc, message, -1);
|
||||||
free(message);
|
free(message);
|
||||||
} else {
|
} else {
|
||||||
@ -169,7 +168,7 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
case 4: {
|
case 4: {
|
||||||
// Connection Info
|
// Connection Info
|
||||||
if(!peer->connected) {
|
if (!peer->connected) {
|
||||||
printf("** Channel is not Open **");
|
printf("** Channel is not Open **");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -194,35 +193,32 @@ int main(int argc, char **argv) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
printf("Candidate %s: %s\n", "answerer", cand);
|
printf("Candidate %s: %s\n", "answerer", cand);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->state = state;
|
peer->state = state;
|
||||||
printf("State %s: %s\n", "answerer", state_print(state));
|
printf("State %s: %s\n", "answerer", state_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->gatheringState = state;
|
peer->gatheringState = state;
|
||||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void closedCallback(void *ptr) {
|
static void RTC_API closedCallback(int id, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->connected = false;
|
peer->connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void messageCallback(const char *message, int size, void *ptr) {
|
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||||
if (size < 0) { // negative size indicates a null-terminated string
|
if (size < 0) { // negative size indicates a null-terminated string
|
||||||
printf("Message %s: %s\n", "answerer", message);
|
printf("Message %s: %s\n", "answerer", message);
|
||||||
} else {
|
} else {
|
||||||
@ -230,6 +226,17 @@ static void messageCallback(const char *message, int size, void *ptr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr) {
|
||||||
|
Peer *peer = (Peer *)ptr;
|
||||||
|
peer->dc = dc;
|
||||||
|
peer->connected = true;
|
||||||
|
rtcSetClosedCallback(dc, closedCallback);
|
||||||
|
rtcSetMessageCallback(dc, messageCallback);
|
||||||
|
char buffer[256];
|
||||||
|
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||||
|
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||||
|
}
|
||||||
|
|
||||||
static void deletePeer(Peer *peer) {
|
static void deletePeer(Peer *peer) {
|
||||||
if (peer) {
|
if (peer) {
|
||||||
if (peer->dc)
|
if (peer->dc)
|
||||||
@ -240,17 +247,6 @@ static void deletePeer(Peer *peer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dataChannelCallback(int dc, void *ptr) {
|
|
||||||
Peer *peer = (Peer *)ptr;
|
|
||||||
peer->dc = dc;
|
|
||||||
peer->connected = true;
|
|
||||||
rtcSetClosedCallback(dc, closedCallback);
|
|
||||||
rtcSetMessageCallback(dc, messageCallback);
|
|
||||||
char buffer[256];
|
|
||||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
|
||||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *state_print(rtcState state) {
|
char *state_print(rtcState state) {
|
||||||
char *str = NULL;
|
char *str = NULL;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
|
@ -41,13 +41,13 @@ typedef struct {
|
|||||||
bool connected;
|
bool connected;
|
||||||
} Peer;
|
} Peer;
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||||
static void stateChangeCallback(rtcState state, void *ptr);
|
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||||
static void openCallback(void *ptr);
|
static void RTC_API openCallback(int id, void *ptr);
|
||||||
static void closedCallback(void *ptr);
|
static void RTC_API closedCallback(int id, void *ptr);
|
||||||
static void messageCallback(const char *message, int size, void *ptr);
|
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||||
static void deletePeer(Peer *peer);
|
static void deletePeer(Peer *peer);
|
||||||
|
|
||||||
char *state_print(rtcState state);
|
char *state_print(rtcState state);
|
||||||
@ -55,7 +55,7 @@ char *rtcGatheringState_print(rtcGatheringState state);
|
|||||||
|
|
||||||
int all_space(const char *str);
|
int all_space(const char *str);
|
||||||
|
|
||||||
int main(int argc, char **argv){
|
int main(int argc, char **argv) {
|
||||||
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
||||||
|
|
||||||
// Create peer
|
// Create peer
|
||||||
@ -79,7 +79,7 @@ int main(int argc, char **argv){
|
|||||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||||
|
|
||||||
// Since this is the offere, we will create a datachannel
|
// Since we are the offerer, we will create a datachannel
|
||||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||||
rtcSetOpenCallback(peer->dc, openCallback);
|
rtcSetOpenCallback(peer->dc, openCallback);
|
||||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||||
@ -91,7 +91,8 @@ int main(int argc, char **argv){
|
|||||||
while (!exit) {
|
while (!exit) {
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("***************************************************************************************\n");
|
printf("***********************************************************************************"
|
||||||
|
"****\n");
|
||||||
printf("* 0: Exit /"
|
printf("* 0: Exit /"
|
||||||
" 1: Enter remote description /"
|
" 1: Enter remote description /"
|
||||||
" 2: Enter remote candidate /"
|
" 2: Enter remote candidate /"
|
||||||
@ -119,53 +120,49 @@ int main(int argc, char **argv){
|
|||||||
// Parse Description
|
// Parse Description
|
||||||
printf("[Description]: ");
|
printf("[Description]: ");
|
||||||
|
|
||||||
|
|
||||||
char *line = NULL;
|
char *line = NULL;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
size_t read = 0;
|
size_t read = 0;
|
||||||
char *sdp = (char*) malloc(sizeof(char));
|
char *sdp = (char *)malloc(sizeof(char));
|
||||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||||
strcat(sdp, line);
|
strcat(sdp, line);
|
||||||
|
|
||||||
}
|
}
|
||||||
printf("%s\n",sdp);
|
printf("%s\n", sdp);
|
||||||
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
||||||
free(sdp);
|
free(sdp);
|
||||||
free(line);
|
free(line);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
// Parse Candidate
|
// Parse Candidate
|
||||||
printf("[Candidate]: ");
|
printf("[Candidate]: ");
|
||||||
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 {
|
||||||
printf("Error reading line\n");
|
printf("Error reading line\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 3: {
|
case 3: {
|
||||||
// Send Message
|
// Send Message
|
||||||
if(!peer->connected) {
|
if (!peer->connected) {
|
||||||
printf("** Channel is not Open **");
|
printf("** Channel is not Open **");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("[Message]: ");
|
printf("[Message]: ");
|
||||||
char* message = NULL;
|
char *message = NULL;
|
||||||
size_t message_size = 0;
|
size_t message_size = 0;
|
||||||
if(getline(&message, &message_size, stdin)) {
|
if (getline(&message, &message_size, stdin)) {
|
||||||
rtcSendMessage(peer->dc, message, -1);
|
rtcSendMessage(peer->dc, message, -1);
|
||||||
free(message);
|
free(message);
|
||||||
}else {
|
} else {
|
||||||
printf("Error reading line\n");
|
printf("Error reading line\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -174,7 +171,7 @@ int main(int argc, char **argv){
|
|||||||
}
|
}
|
||||||
case 4: {
|
case 4: {
|
||||||
// Connection Info
|
// Connection Info
|
||||||
if(!peer->connected) {
|
if (!peer->connected) {
|
||||||
printf("** Channel is not Open **");
|
printf("** Channel is not Open **");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -199,44 +196,40 @@ int main(int argc, char **argv){
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
printf("Candidate %s: %s\n", "offerer", cand);
|
printf("Candidate %s: %s\n", "offerer", cand);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->state = state;
|
peer->state = state;
|
||||||
printf("State %s: %s\n", "offerer", state_print(state));
|
printf("State %s: %s\n", "offerer", state_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->gatheringState = state;
|
peer->gatheringState = state;
|
||||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void openCallback(void *ptr) {
|
static void RTC_API openCallback(int id, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->connected = true;
|
peer->connected = true;
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||||
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
printf("DataChannel %s: Received with label \"%s\"\n", "offerer", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void closedCallback(void *ptr) {
|
static void RTC_API closedCallback(int id, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->connected = false;
|
peer->connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void messageCallback(const char *message, int size, void *ptr) {
|
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
if (size < 0) { // negative size indicates a null-terminated string
|
if (size < 0) { // negative size indicates a null-terminated string
|
||||||
printf("Message %s: %s\n", "offerer", message);
|
printf("Message %s: %s\n", "offerer", message);
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,19 +4,23 @@ 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()
|
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET datachannel-copy-paste-offerer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-copy-paste-offerer>
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET datachannel-copy-paste-answerer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-copy-paste-answerer>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
|||||||
cout << "** Channel is not Open ** ";
|
cout << "** Channel is not Open ** ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
CandidateInfo local, remote;
|
Candidate local, remote;
|
||||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
cout << "Local: " << local << endl;
|
||||||
<< local.transportType << endl;
|
cout << "Remote: " << remote << endl;
|
||||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
|
||||||
<< " " << remote.transportType << endl;
|
|
||||||
cout << "Bytes Sent:" << pc->bytesSent()
|
cout << "Bytes Sent:" << pc->bytesSent()
|
||||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||||
if (rtt.has_value())
|
if (rtt.has_value())
|
||||||
|
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
|||||||
cout << "** Channel is not Open ** ";
|
cout << "** Channel is not Open ** ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
CandidateInfo local, remote;
|
Candidate local, remote;
|
||||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
cout << "Local: " << local << endl;
|
||||||
<< local.transportType << endl;
|
cout << "Remote: " << remote << endl;
|
||||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
|
||||||
<< " " << remote.transportType << endl;
|
|
||||||
cout << "Bytes Sent:" << pc->bytesSent()
|
cout << "Bytes Sent:" << pc->bytesSent()
|
||||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||||
if (rtt.has_value())
|
if (rtt.has_value())
|
||||||
|
@ -4,11 +4,12 @@ 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)
|
||||||
|
target_link_libraries(datachannel-media datachannel nlohmann_json)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(datachannel-media datachannel-static) # DLL exports only the C API
|
add_custom_command(TARGET datachannel-media POST_BUILD
|
||||||
else()
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
target_link_libraries(datachannel-media datachannel)
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-media>
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(datachannel-media datachannel nlohmann_json)
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* libdatachannel client example
|
* libdatachannel client example
|
||||||
* Copyright (c) 2020 Staz M
|
* Copyright (c) 2020 Staz Modrzynski
|
||||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@ -67,7 +67,7 @@ int main() {
|
|||||||
|
|
||||||
auto track = pc->addTrack(media);
|
auto track = pc->addTrack(media);
|
||||||
|
|
||||||
auto session = std::make_shared<rtc::RtcpSession>();
|
auto session = std::make_shared<rtc::RtcpReceivingSession>();
|
||||||
track->setRtcpHandler(session);
|
track->setRtcpHandler(session);
|
||||||
|
|
||||||
track->onMessage(
|
track->onMessage(
|
||||||
|
15
examples/sfu-media/CMakeLists.txt
Normal file
15
examples/sfu-media/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
|
||||||
|
add_executable(datachannel-sfu-media main.cpp)
|
||||||
|
set_target_properties(datachannel-sfu-media PROPERTIES
|
||||||
|
CXX_STANDARD 17
|
||||||
|
OUTPUT_NAME sfu-media)
|
||||||
|
target_link_libraries(datachannel-sfu-media datachannel nlohmann_json)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET datachannel-sfu-media POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-sfu-media>
|
||||||
|
)
|
||||||
|
endif()
|
134
examples/sfu-media/main.cpp
Normal file
134
examples/sfu-media/main.cpp
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel client example
|
||||||
|
* Copyright (c) 2020 Staz Modrzynski
|
||||||
|
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||||
|
|
||||||
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
struct Receiver {
|
||||||
|
std::shared_ptr<rtc::PeerConnection> conn;
|
||||||
|
std::shared_ptr<rtc::Track> track;
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
std::vector<std::shared_ptr<Receiver>> receivers;
|
||||||
|
|
||||||
|
try {
|
||||||
|
rtc::InitLogger(rtc::LogLevel::Info);
|
||||||
|
|
||||||
|
auto pc = std::make_shared<rtc::PeerConnection>();
|
||||||
|
pc->onStateChange(
|
||||||
|
[](rtc::PeerConnection::State state) { std::cout << "State: " << state << std::endl; });
|
||||||
|
pc->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
|
||||||
|
std::cout << "Gathering State: " << state << std::endl;
|
||||||
|
if (state == rtc::PeerConnection::GatheringState::Complete) {
|
||||||
|
auto description = pc->localDescription();
|
||||||
|
json message = {{"type", description->typeString()},
|
||||||
|
{"sdp", std::string(description.value())}};
|
||||||
|
std::cout << "Please copy/paste this offer to the SENDER: " << message << std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rtc::Description::Video media("video", rtc::Description::Direction::RecvOnly);
|
||||||
|
media.addH264Codec(96);
|
||||||
|
media.setBitrate(
|
||||||
|
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
||||||
|
|
||||||
|
auto track = pc->addTrack(media);
|
||||||
|
pc->setLocalDescription();
|
||||||
|
|
||||||
|
auto session = std::make_shared<rtc::RtcpReceivingSession>();
|
||||||
|
track->setRtcpHandler(session);
|
||||||
|
|
||||||
|
const rtc::SSRC targetSSRC = 4;
|
||||||
|
|
||||||
|
track->onMessage(
|
||||||
|
[&receivers, targetSSRC](rtc::binary message) {
|
||||||
|
// This is an RTP packet
|
||||||
|
auto rtp = (rtc::RTP *)message.data();
|
||||||
|
rtp->setSsrc(targetSSRC);
|
||||||
|
for (auto pc : receivers) {
|
||||||
|
if (pc->track != nullptr && pc->track->isOpen()) {
|
||||||
|
pc->track->send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
// Set the SENDERS Answer
|
||||||
|
{
|
||||||
|
std::cout << "Please copy/paste the answer provided by the SENDER: " << std::endl;
|
||||||
|
std::string sdp;
|
||||||
|
std::getline(std::cin, sdp);
|
||||||
|
std::cout << "Got answer" << sdp << std::endl;
|
||||||
|
json j = json::parse(sdp);
|
||||||
|
rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
|
||||||
|
pc->setRemoteDescription(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each receiver
|
||||||
|
while (true) {
|
||||||
|
auto pc = std::make_shared<Receiver>();
|
||||||
|
pc->conn = std::make_shared<rtc::PeerConnection>();
|
||||||
|
pc->conn->onStateChange([](rtc::PeerConnection::State state) {
|
||||||
|
std::cout << "State: " << state << std::endl;
|
||||||
|
});
|
||||||
|
pc->conn->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
|
||||||
|
std::cout << "Gathering State: " << state << std::endl;
|
||||||
|
if (state == rtc::PeerConnection::GatheringState::Complete) {
|
||||||
|
auto description = pc->conn->localDescription();
|
||||||
|
json message = {{"type", description->typeString()},
|
||||||
|
{"sdp", std::string(description.value())}};
|
||||||
|
std::cout << "Please copy/paste this offer to the RECEIVER: " << message
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rtc::Description::Video media("video", rtc::Description::Direction::SendOnly);
|
||||||
|
media.addH264Codec(96);
|
||||||
|
media.setBitrate(
|
||||||
|
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
||||||
|
|
||||||
|
media.addSSRC(targetSSRC, "video-send");
|
||||||
|
|
||||||
|
pc->track = pc->conn->addTrack(media);
|
||||||
|
pc->conn->setLocalDescription();
|
||||||
|
|
||||||
|
pc->track->onMessage([](rtc::binary var) {}, nullptr);
|
||||||
|
|
||||||
|
std::cout << "Please copy/paste the answer provided by the RECEIVER: " << std::endl;
|
||||||
|
std::string sdp;
|
||||||
|
std::getline(std::cin, sdp);
|
||||||
|
std::cout << "Got answer" << sdp << std::endl;
|
||||||
|
json j = json::parse(sdp);
|
||||||
|
rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
|
||||||
|
pc->conn->setRemoteDescription(answer);
|
||||||
|
|
||||||
|
receivers.push_back(pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cerr << "Error: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
87
examples/sfu-media/main.html
Normal file
87
examples/sfu-media/main.html
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>libdatachannel media example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div style="display:inline-block; width:40%;">
|
||||||
|
<h1>SENDER</h1>
|
||||||
|
<p id="send-help">Please enter the offer provided to you by the application: </p>
|
||||||
|
<textarea style="width:100%;" id=send-text rows="50"></textarea>
|
||||||
|
<button id=send-btn>Submit</button>
|
||||||
|
</div>
|
||||||
|
<div style="display:inline-block; width:40%;">
|
||||||
|
<h1>RECEIVER</h1>
|
||||||
|
<p id="recv-help">Please enter the offer provided to you by the application: </p>
|
||||||
|
<textarea id=recv-text style="width:100%;" rows="50"></textarea>
|
||||||
|
<button id=recv-btn>Submit</button>
|
||||||
|
</div>
|
||||||
|
<div id="videos">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.querySelector('#send-btn').addEventListener('click', async () => {
|
||||||
|
let offer = JSON.parse(document.querySelector('#send-text').value);
|
||||||
|
rtc = new RTCPeerConnection({
|
||||||
|
// Recommended for libdatachannel
|
||||||
|
bundlePolicy: "max-bundle",
|
||||||
|
});
|
||||||
|
|
||||||
|
rtc.onicegatheringstatechange = (state) => {
|
||||||
|
if (rtc.iceGatheringState === 'complete') {
|
||||||
|
// We only want to provide an answer once all of our candidates have been added to the SDP.
|
||||||
|
let answer = rtc.localDescription;
|
||||||
|
document.querySelector('#send-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
|
||||||
|
document.querySelector('#send-help').value = 'Please paste the answer in the application.';
|
||||||
|
alert('Please paste the answer in the application.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await rtc.setRemoteDescription(offer);
|
||||||
|
|
||||||
|
let media = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: {
|
||||||
|
width: 1280,
|
||||||
|
height: 720
|
||||||
|
}
|
||||||
|
});
|
||||||
|
media.getTracks().forEach(track => rtc.addTrack(track, media));
|
||||||
|
let answer = await rtc.createAnswer();
|
||||||
|
await rtc.setLocalDescription(answer);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#recv-btn').addEventListener('click', async () => {
|
||||||
|
let offer = JSON.parse(document.querySelector('#recv-text').value);
|
||||||
|
rtc = new RTCPeerConnection({
|
||||||
|
// Recommended for libdatachannel
|
||||||
|
bundlePolicy: "max-bundle",
|
||||||
|
});
|
||||||
|
|
||||||
|
rtc.onicegatheringstatechange = (state) => {
|
||||||
|
if (rtc.iceGatheringState === 'complete') {
|
||||||
|
// We only want to provide an answer once all of our candidates have been added to the SDP.
|
||||||
|
let answer = rtc.localDescription;
|
||||||
|
document.querySelector('#recv-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
|
||||||
|
document.querySelector('#recv-help').value = 'Please paste the answer in the application.';
|
||||||
|
alert('Please paste the answer in the application.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let trackCount = 0;
|
||||||
|
rtc.ontrack = (ev) => {
|
||||||
|
let thisID = trackCount++;
|
||||||
|
|
||||||
|
document.querySelector("#videos").innerHTML += "<video width=100% height=100% id='video-" + thisID + "'></video>";
|
||||||
|
let tracks = [];
|
||||||
|
rtc.getReceivers().forEach(recv => tracks.push(recv.track));
|
||||||
|
document.querySelector("#video-" + thisID).srcObject = new MediaStream(tracks);
|
||||||
|
document.querySelector("#video-" + thisID).play();
|
||||||
|
};
|
||||||
|
await rtc.setRemoteDescription(offer);
|
||||||
|
let answer = await rtc.createAnswer();
|
||||||
|
await rtc.setLocalDescription(answer);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
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 */
|
40
examples/streamer/CMakeLists.txt
Normal file
40
examples/streamer/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
if(POLICY CMP0079)
|
||||||
|
cmake_policy(SET CMP0079 NEW)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
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.cpp
|
||||||
|
ArgParser.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_compile_definitions(streamer PUBLIC STATIC_GETOPT)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_target_properties(streamer PROPERTIES
|
||||||
|
CXX_STANDARD 17
|
||||||
|
OUTPUT_NAME streamer)
|
||||||
|
|
||||||
|
target_link_libraries(streamer datachannel nlohmann_json)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET streamer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:streamer>
|
||||||
|
)
|
||||||
|
endif()
|
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)
|
207
examples/streamer/client.js
Normal file
207
examples/streamer/client.js
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/** @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) {
|
||||||
|
document.getElementById('media').style.display = 'block';
|
||||||
|
const videoTag = document.getElementById('video');
|
||||||
|
videoTag.srcObject = evt.streams[0];
|
||||||
|
videoTag.play();
|
||||||
|
});
|
||||||
|
|
||||||
|
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<RtcpSrReporter> 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::RtcpSrReporter> sender;
|
||||||
|
|
||||||
|
ClientTrackData(std::shared_ptr<rtc::Track> track, std::shared_ptr<rtc::RtcpSrReporter> 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 */
|
72
examples/streamer/index.html
Normal file
72
examples/streamer/index.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<!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>
|
||||||
|
<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>
|
480
examples/streamer/main.cpp
Normal file
480
examples/streamer/main.cpp
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
/*
|
||||||
|
* 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, cname);
|
||||||
|
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(H264RtpPacketizer::Separator::Length, rtpConfig));
|
||||||
|
// create H264 handler
|
||||||
|
shared_ptr<H264PacketizationHandler> h264Handler(new H264PacketizationHandler(packetizer));
|
||||||
|
// add RTCP SR handler
|
||||||
|
auto srReporter = make_shared<RtcpSrReporter>(rtpConfig);
|
||||||
|
h264Handler->addToChain(srReporter);
|
||||||
|
// add RTCP NACK handler
|
||||||
|
auto nackResponder = make_shared<RtcpNackResponder>();
|
||||||
|
h264Handler->addToChain(nackResponder);
|
||||||
|
// set handler
|
||||||
|
track->setRtcpHandler(h264Handler);
|
||||||
|
track->onOpen(onOpen);
|
||||||
|
auto trackData = make_shared<ClientTrackData>(track, srReporter);
|
||||||
|
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, cname);
|
||||||
|
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 handler
|
||||||
|
auto opusHandler = make_shared<OpusPacketizationHandler>(packetizer);
|
||||||
|
// add RTCP SR handler
|
||||||
|
auto srReporter = make_shared<RtcpSrReporter>(rtpConfig);
|
||||||
|
opusHandler->addToChain(srReporter);
|
||||||
|
// add RTCP NACK handler
|
||||||
|
auto nackResponder = make_shared<RtcpNackResponder>();
|
||||||
|
opusHandler->addToChain(nackResponder);
|
||||||
|
// set handler
|
||||||
|
track->setRtcpHandler(opusHandler);
|
||||||
|
track->onOpen(onOpen);
|
||||||
|
auto trackData = make_shared<ClientTrackData>(track, srReporter);
|
||||||
|
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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user