mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
554 Commits
Author | SHA1 | Date | |
---|---|---|---|
2db14a29a9 | |||
5cbbba2e12 | |||
93aef867d0 | |||
e99ba3c5d8 | |||
65dba2c299 | |||
6ef8f1e1a7 | |||
56dbcaad97 | |||
d748016446 | |||
e543d789a4 | |||
90e59435c0 | |||
785c3b8149 | |||
c37c88543d | |||
011bfbe46f | |||
de2ac6c0c2 | |||
75619babd7 | |||
fe9a34905b | |||
b88f1f5e72 | |||
ab7d7fefe0 | |||
e592fcf217 | |||
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 | |||
3432b233ff | |||
ef9bfe811b | |||
949e1de9cd | |||
57c52cf7ae | |||
eaac06546e | |||
9e38d08c0b | |||
47db28617a | |||
de8c4a55cf | |||
08d94e59c7 | |||
e8a6698abd | |||
7348b2b350 | |||
71e5d2bf43 | |||
7188bc9d8d | |||
49180e826e | |||
c6fbbb499f | |||
7b565f0f0e | |||
a7cc637aa2 | |||
824bb93b0a | |||
a99efd27d2 | |||
6266e16644 | |||
edd4fcfcc0 | |||
30dc229477 | |||
d288885a89 | |||
bd19d9d5b4 | |||
a284af2000 | |||
88e8d028b4 | |||
8cab2b9d87 | |||
9b62a543f6 | |||
5a797e1170 | |||
14f8858d3d | |||
62f7642de9 | |||
77475b57b0 | |||
98048a178e | |||
26eb9bf300 | |||
d4ff4a3e0d | |||
488e143e1f | |||
4d5c5ef404 | |||
628742c2f3 | |||
2492a42151 | |||
4a0b6e99d4 | |||
65946fe254 | |||
3286b5c05e | |||
ac8e02604d | |||
dd296e4408 | |||
1be877132c | |||
7e79fd9721 | |||
5d78aecabb | |||
9fbc894fdb | |||
4930e666ac | |||
e5e337a0a5 | |||
704e604b8f | |||
b5d3cdc9b1 | |||
b8b6b913a9 | |||
f461a40a6d | |||
ddf71ffdac | |||
5dd57204dd | |||
9a622a5e44 | |||
42e79ce921 | |||
cdde6ad36e | |||
c03e615483 |
6
.github/workflows/build-gnutls.yml
vendored
6
.github/workflows/build-gnutls.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev libsrtp2-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
@ -30,7 +30,7 @@ jobs:
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
|
16
.github/workflows/build-nice.yml
vendored
16
.github/workflows/build-nice.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build-linux:
|
||||
build-media:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -21,4 +21,18 @@ jobs:
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
run: ./build/tests
|
||||
build-no-media:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev libnice-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_NICE=1 -DNO_MEDIA=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
run: ./build/tests
|
||||
|
||||
|
7
.github/workflows/build-openssl.yml
vendored
7
.github/workflows/build-openssl.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libssl-dev
|
||||
run: sudo apt update && sudo apt install libssl-dev libsrtp2-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -DWARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
@ -30,7 +30,7 @@ jobs:
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||
env:
|
||||
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
||||
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
||||
@ -52,6 +52,7 @@ jobs:
|
||||
- name: nmake
|
||||
run: |
|
||||
cd build
|
||||
set CL=/MP
|
||||
nmake
|
||||
- name: test
|
||||
run: build/tests.exe
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,6 +5,7 @@ node_modules/
|
||||
*.a
|
||||
*.so
|
||||
compile_commands.json
|
||||
tests
|
||||
/tests
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -10,3 +10,6 @@
|
||||
[submodule "deps/json"]
|
||||
path = deps/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "deps/libsrtp"]
|
||||
path = deps/libsrtp
|
||||
url = https://github.com/cisco/libsrtp.git
|
||||
|
201
CMakeLists.txt
201
CMakeLists.txt
@ -1,16 +1,20 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
project(libdatachannel
|
||||
DESCRIPTION "WebRTC Data Channels Library"
|
||||
VERSION 0.9.0
|
||||
VERSION 0.11.6
|
||||
LANGUAGES CXX)
|
||||
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
|
||||
|
||||
# Options
|
||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||
option(USE_NICE "Use libnice instead of libjuice" OFF)
|
||||
option(USE_SYSTEM_SRTP "Use system libSRTP" OFF)
|
||||
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
||||
option(NO_MEDIA "Disable media transport support" OFF)
|
||||
option(NO_EXAMPLES "Disable examples" OFF)
|
||||
option(NO_TESTS "Disable tests build" OFF)
|
||||
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||
option(RSA_KEY_BITS_2048 "Use 2048-bit RSA key instead of 3072-bit" OFF)
|
||||
option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
|
||||
|
||||
if(USE_NICE)
|
||||
option(USE_JUICE "Use libjuice" OFF)
|
||||
@ -19,17 +23,21 @@ else()
|
||||
endif()
|
||||
|
||||
if(USE_GNUTLS)
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
||||
option(USE_NETTLE "Use Nettle in libjuice" ON)
|
||||
else()
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
|
||||
option(USE_NETTLE "Use Nettle in libjuice" OFF)
|
||||
if(NOT USE_SYSTEM_SRTP)
|
||||
option(ENABLE_OPENSSL "Enable OpenSSL crypto engine for SRTP" ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||
set(BUILD_SHARED_LIBS OFF) # to force usrsctp to be built static
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||
if (MSVC)
|
||||
if(MSVC)
|
||||
add_definitions(-DNOMINMAX)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
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/message.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/threadpool.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/track.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.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
|
||||
@ -68,6 +102,14 @@ set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
||||
${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
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.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/datachannel.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/init.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/rtc.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp
|
||||
${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
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/main.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/capi_connectivity.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_track.cpp
|
||||
@ -99,6 +156,24 @@ set(TESTS_SOURCES
|
||||
${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(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
@ -107,32 +182,40 @@ set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
||||
add_subdirectory(deps/plog)
|
||||
|
||||
option(sctp_build_programs 0)
|
||||
option(sctp_build_shared_lib 0)
|
||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||
if (MSYS OR MINGW)
|
||||
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||
target_compile_definitions(usrsctp-static PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||
endif()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
|
||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
|
||||
endif()
|
||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
||||
|
||||
if (NO_WEBSOCKET)
|
||||
add_library(datachannel SHARED
|
||||
${LIBDATACHANNEL_SOURCES})
|
||||
${LIBDATACHANNEL_SOURCES}
|
||||
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||
${LIBDATACHANNEL_HEADERS})
|
||||
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-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
||||
else()
|
||||
add_library(datachannel SHARED
|
||||
${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
|
||||
${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-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
||||
endif()
|
||||
@ -148,37 +231,45 @@ target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includ
|
||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_link_libraries(datachannel PUBLIC Threads::Threads plog::plog)
|
||||
target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic)
|
||||
target_link_libraries(datachannel PRIVATE Usrsctp::Usrsctp)
|
||||
|
||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
|
||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
|
||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
|
||||
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
||||
target_link_libraries(datachannel PUBLIC ws2_32) # winsock2
|
||||
target_link_libraries(datachannel-static PUBLIC ws2_32) # winsock2
|
||||
endif()
|
||||
|
||||
find_package(SRTP)
|
||||
if(SRTP_FOUND)
|
||||
if(NOT TARGET SRTP::SRTP)
|
||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||
set_target_properties(SRTP::SRTP PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
||||
endif()
|
||||
message(STATUS "LibSRTP found, compiling with media transport")
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
||||
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
||||
else()
|
||||
message(STATUS "LibSRTP NOT found, compiling WITHOUT media transport")
|
||||
if(NO_MEDIA)
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
|
||||
else()
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
if(USE_SYSTEM_SRTP)
|
||||
find_package(SRTP REQUIRED)
|
||||
if(NOT TARGET SRTP::SRTP)
|
||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||
set_target_properties(SRTP::SRTP PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
||||
endif()
|
||||
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=1)
|
||||
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=1)
|
||||
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
||||
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
||||
else()
|
||||
add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=0)
|
||||
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=0)
|
||||
target_link_libraries(datachannel PRIVATE srtp2)
|
||||
target_link_libraries(datachannel-static PRIVATE srtp2)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (USE_GNUTLS)
|
||||
@ -217,6 +308,16 @@ else()
|
||||
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||
endif()
|
||||
|
||||
if(RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel PUBLIC RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel-static PUBLIC RSA_KEY_BITS_2048)
|
||||
endif()
|
||||
|
||||
if(CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel PUBLIC CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel-static PUBLIC CAPI_STDCALL)
|
||||
endif()
|
||||
|
||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||
|
||||
@ -240,39 +341,45 @@ endif()
|
||||
|
||||
# Tests
|
||||
if(NOT NO_TESTS)
|
||||
add_executable(datachannel-tests ${TESTS_SOURCES})
|
||||
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})
|
||||
endif()
|
||||
set_target_properties(datachannel-tests PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||
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)
|
||||
endif()
|
||||
target_link_libraries(datachannel-tests datachannel)
|
||||
|
||||
# Benchmark
|
||||
add_executable(datachannel-benchmark test/benchmark.cpp)
|
||||
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)
|
||||
endif()
|
||||
set_target_properties(datachannel-benchmark PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
||||
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
|
||||
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)
|
||||
endif()
|
||||
target_link_libraries(datachannel-benchmark datachannel)
|
||||
endif()
|
||||
|
||||
# Examples
|
||||
if(NOT NO_EXAMPLES)
|
||||
if(NOT NO_EXAMPLES AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
add_subdirectory(deps/json)
|
||||
add_subdirectory(examples/client)
|
||||
if(NOT NO_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-capi)
|
||||
endif()
|
||||
|
8
Jamfile
8
Jamfile
@ -15,9 +15,9 @@ lib libdatachannel
|
||||
: # requirements
|
||||
<cxxstd>17
|
||||
<include>./include/rtc
|
||||
<define>USE_NICE=0
|
||||
<define>RTC_ENABLE_MEDIA=0
|
||||
<define>RTC_ENABLE_WEBSOCKET=0
|
||||
<define>USE_NICE=0
|
||||
<toolset>msvc:<define>WIN32_LEAN_AND_MEAN
|
||||
<toolset>msvc:<define>NOMINMAX
|
||||
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS
|
||||
@ -32,6 +32,8 @@ lib libdatachannel
|
||||
<link>static
|
||||
: # usage requirements
|
||||
<include>./include
|
||||
<define>RTC_ENABLE_MEDIA=0
|
||||
<define>RTC_ENABLE_WEBSOCKET=0
|
||||
<library>/libdatachannel//plog
|
||||
<toolset>gcc:<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
|
||||
{
|
||||
(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 $(<)
|
||||
}
|
||||
rule make_libusrsctp_msvc ( targets * : sources * : properties * )
|
||||
@ -109,7 +111,7 @@ actions make_libusrsctp_msvc
|
||||
cd $(CWD)/deps/usrsctp
|
||||
mkdir $(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)
|
||||
cd %OLDD%
|
||||
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<)
|
||||
|
29
Makefile
29
Makefile
@ -10,6 +10,7 @@ LDFLAGS=-pthread
|
||||
LIBS=
|
||||
LOCALLIBS=libusrsctp.a
|
||||
USRSCTP_DIR=deps/usrsctp
|
||||
SRTP_DIR=deps/libsrtp
|
||||
JUICE_DIR=deps/libjuice
|
||||
PLOG_DIR=deps/plog
|
||||
|
||||
@ -38,15 +39,22 @@ ifneq ($(USE_GNUTLS), 0)
|
||||
endif
|
||||
endif
|
||||
|
||||
USE_SRTP ?= 0
|
||||
ifneq ($(USE_SRTP), 0)
|
||||
NO_MEDIA ?= 0
|
||||
USE_SYSTEM_SRTP ?= 0
|
||||
ifeq ($(NO_MEDIA), 0)
|
||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=1
|
||||
ifneq ($(USE_SYSTEM_SRTP), 0)
|
||||
CPPFLAGS+=-DRTC_SYSTEM_SRTP=1
|
||||
LIBS+=srtp
|
||||
else
|
||||
CPPFLAGS+=-DRTC_SYSTEM_SRTP=0
|
||||
INCLUDES+=-I$(SRTP_DIR)/include
|
||||
LOCALLIBS+=libsrtp2.a
|
||||
endif
|
||||
else
|
||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=0
|
||||
endif
|
||||
|
||||
|
||||
NO_WEBSOCKET ?= 0
|
||||
ifeq ($(NO_WEBSOCKET), 0)
|
||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=1
|
||||
@ -54,8 +62,8 @@ else
|
||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=0
|
||||
endif
|
||||
|
||||
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
|
||||
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
||||
INCLUDES+=$(if $(LIBS),$(shell pkg-config --cflags $(LIBS)),)
|
||||
LDLIBS+=$(LOCALLIBS) $(if $(LIBS),$(shell pkg-config --libs $(LIBS)),)
|
||||
|
||||
SRCS=$(shell printf "%s " src/*.cpp)
|
||||
OBJS=$(subst .cpp,.o,$(SRCS))
|
||||
@ -73,7 +81,7 @@ test/%.o: test/%.cpp
|
||||
|
||||
-include $(subst .cpp,.d,$(SRCS))
|
||||
|
||||
$(NAME).a: $(OBJS)
|
||||
$(NAME).a: $(LOCALLIBS) $(OBJS)
|
||||
$(AR) crf $@ $(OBJS)
|
||||
|
||||
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
||||
@ -97,15 +105,22 @@ dist-clean: clean
|
||||
-$(RM) src/*~
|
||||
-$(RM) test/*~
|
||||
-cd $(USRSCTP_DIR) && make clean
|
||||
-cd $(SRTP_DIR) && make clean
|
||||
-cd $(JUICE_DIR) && make clean
|
||||
|
||||
libusrsctp.a:
|
||||
cd $(USRSCTP_DIR) && \
|
||||
./bootstrap && \
|
||||
./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS) -Wno-error=format-truncation" && \
|
||||
./configure --enable-static --disable-debug CFLAGS="-fPIC" && \
|
||||
make
|
||||
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
||||
|
||||
libsrtp2.a:
|
||||
cd $(SRTP_DIR) && \
|
||||
./configure && \
|
||||
make
|
||||
cp $(SRTP_DIR)/libsrtp2.a .
|
||||
|
||||
libjuice.a:
|
||||
ifneq ($(USE_GNUTLS), 0)
|
||||
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:
|
||||
- SCTP-based Data Channels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13))
|
||||
- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26)) with [libSRTP](https://github.com/cisco/libsrtp)
|
||||
- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26))
|
||||
- DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261))
|
||||
- ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)) with STUN ([RFC5389](https://tools.ietf.org/html/rfc5389))
|
||||
- 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:
|
||||
- Full IPv6 support
|
||||
- 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))
|
||||
- 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))
|
||||
- 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)).
|
||||
|
||||
@ -53,10 +53,11 @@ Dependencies:
|
||||
Submodules:
|
||||
- libjuice: https://github.com/paullouisageneau/libjuice
|
||||
- usrsctp: https://github.com/sctplab/usrsctp
|
||||
- libsrtp: https://github.com/cisco/libsrtp
|
||||
|
||||
Optional dependencies:
|
||||
- libnice: https://nice.freedesktop.org/ (only if selected as ICE backend instead of libjuice)
|
||||
- libSRTP: https://github.com/cisco/libsrtp (only necessary for supporting media transport)
|
||||
- libnice: https://nice.freedesktop.org/ (if selected as ICE backend instead of libjuice)
|
||||
- libsrtp: https://github.com/cisco/libsrtp (if selected instead of the submodule)
|
||||
|
||||
## Building
|
||||
|
||||
@ -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.
|
||||
|
||||
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)
|
||||
```bash
|
||||
@ -81,6 +82,29 @@ $ cd build
|
||||
$ make -j2
|
||||
```
|
||||
|
||||
#### Apple macOS with XCode project
|
||||
|
||||
```bash
|
||||
$ cmake -B "$BUILD_DIR" -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode
|
||||
```
|
||||
|
||||
Xcode project is generated in *build/* directory.
|
||||
|
||||
##### Solving **Could NOT find OpenSSL** error
|
||||
|
||||
You need to add OpenSSL root directory if your build fails with the following message:
|
||||
|
||||
```
|
||||
Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the
|
||||
system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY
|
||||
OPENSSL_INCLUDE_DIR)
|
||||
```
|
||||
|
||||
for example:
|
||||
```bash
|
||||
$ cmake -B build -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl\@1.1/1.1.1h/
|
||||
```
|
||||
|
||||
#### Microsoft Windows with MinGW cross-compilation
|
||||
```bash
|
||||
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
|
||||
@ -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.
|
||||
|
||||
If you only need Data Channels, the option `NO_MEDIA` removes media support. Similarly, `NO_WEBSOCKET` removes WebSocket support.
|
||||
|
||||
```bash
|
||||
$ make USE_GNUTLS=1 USE_NICE=0
|
||||
```
|
||||
@ -202,5 +228,6 @@ ws->open("wss://my.websocket/service");
|
||||
## External resources
|
||||
- Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
||||
- Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
|
||||
- Unity wrapper for Windows 10 and Hololens: [datachannel-unity](https://github.com/hanseuljun/datachannel-unity)
|
||||
- WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
|
||||
|
||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: ddc3064890...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: ffed0925f2...07f871bda2
@ -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)
|
||||
|
||||
- [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:
|
||||
- [copy-paste](copy-paste) using the C++ API
|
||||
|
@ -3,15 +3,22 @@ if(POLICY CMP0079)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
endif()
|
||||
|
||||
add_executable(datachannel-client main.cpp)
|
||||
if(WIN32)
|
||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
|
||||
target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
|
||||
else()
|
||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
|
||||
endif()
|
||||
|
||||
set_target_properties(datachannel-client PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
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)
|
||||
|
||||
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()
|
||||
|
513
examples/client/getopt.cpp
Normal file
513
examples/client/getopt.cpp
Normal file
@ -0,0 +1,513 @@
|
||||
/* Getopt for Microsoft C
|
||||
This code is a modification of the Free Software Foundation, Inc.
|
||||
Getopt library for parsing command line argument the purpose was
|
||||
to provide a Microsoft Visual C friendly derivative. This code
|
||||
provides functionality for both Unicode and Multibyte builds.
|
||||
|
||||
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
Version: 1.0
|
||||
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||
and POSIXLY_CORRECT environment flag
|
||||
License: LGPL
|
||||
|
||||
Revisions:
|
||||
|
||||
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include "getopt.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define _GETOPT_THROW throw()
|
||||
#else
|
||||
#define _GETOPT_THROW
|
||||
#endif
|
||||
|
||||
int optind = 1;
|
||||
int opterr = 1;
|
||||
int optopt = '?';
|
||||
enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER };
|
||||
|
||||
static struct _getopt_data_a
|
||||
{
|
||||
int optind;
|
||||
int opterr;
|
||||
int optopt;
|
||||
char *optarg;
|
||||
int __initialized;
|
||||
char *__nextchar;
|
||||
enum ENUM_ORDERING __ordering;
|
||||
int __posixly_correct;
|
||||
int __first_nonopt;
|
||||
int __last_nonopt;
|
||||
} getopt_data_a;
|
||||
char *optarg_a;
|
||||
|
||||
static void exchange_a(char **argv, struct _getopt_data_a *d)
|
||||
{
|
||||
int bottom = d->__first_nonopt;
|
||||
int middle = d->__last_nonopt;
|
||||
int top = d->optind;
|
||||
char *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 char *_getopt_initialize_a (const char *optstring, struct _getopt_data_a *d, int posixly_correct)
|
||||
{
|
||||
d->__first_nonopt = d->__last_nonopt = d->optind;
|
||||
d->__nextchar = NULL;
|
||||
d->__posixly_correct = posixly_correct | !!getenv("POSIXLY_CORRECT");
|
||||
if (optstring[0] == '-')
|
||||
{
|
||||
d->__ordering = RETURN_IN_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (optstring[0] == '+')
|
||||
{
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (d->__posixly_correct)
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
else
|
||||
d->__ordering = PERMUTE;
|
||||
return optstring;
|
||||
}
|
||||
int _getopt_internal_r_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *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_a (optstring, d, posixly_correct);
|
||||
d->__initialized = 1;
|
||||
}
|
||||
else if (optstring[0] == '-' || optstring[0] == '+')
|
||||
optstring++;
|
||||
if (optstring[0] == ':')
|
||||
print_errors = 0;
|
||||
if (d->__nextchar == NULL || *d->__nextchar == '\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_a ((char **) argv, d);
|
||||
else if (d->__last_nonopt != d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
while (d->optind < argc && (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
|
||||
d->optind++;
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
if (d->optind != argc && !strcmp(argv[d->optind], "--"))
|
||||
{
|
||||
d->optind++;
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_a((char **) 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] != '-' || argv[d->optind][1] == '\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] == '-'));
|
||||
}
|
||||
if (longopts != NULL && (argv[d->optind][1] == '-' || (long_only && (argv[d->optind][2] || !strchr(optstring, argv[d->optind][1])))))
|
||||
{
|
||||
char *nameend;
|
||||
unsigned int namelen;
|
||||
const struct option_a *p;
|
||||
const struct option_a *pfound = NULL;
|
||||
struct option_list
|
||||
{
|
||||
const struct option_a *p;
|
||||
struct option_list *next;
|
||||
} *ambig_list = NULL;
|
||||
int exact = 0;
|
||||
int indfound = -1;
|
||||
int option_index;
|
||||
for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++);
|
||||
namelen = (unsigned int)(nameend - d->__nextchar);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!strncmp(p->name, d->__nextchar, namelen))
|
||||
{
|
||||
if (namelen == (unsigned int)strlen(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;
|
||||
fprintf (stderr, "%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]);
|
||||
do
|
||||
{
|
||||
fprintf (stderr, " '--%s'", ambig_list->p->name);
|
||||
ambig_list = ambig_list->next;
|
||||
}
|
||||
while (ambig_list != NULL);
|
||||
fputc ('\n', stderr);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return '?';
|
||||
}
|
||||
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] == '-')
|
||||
{
|
||||
fprintf(stderr, "%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name);
|
||||
}
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr,"%s: option '--%s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return optstring[0] == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
d->__nextchar += strlen(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] == '-' || strchr(optstring, *d->__nextchar) == NULL)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
if (argv[d->optind][1] == '-')
|
||||
{
|
||||
fprintf(stderr, "%s: unrecognized option '--%s'\n",argv[0], d->__nextchar);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar);
|
||||
}
|
||||
}
|
||||
d->__nextchar = (char *)"";
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
{
|
||||
char c = *d->__nextchar++;
|
||||
char *temp = (char*)strchr(optstring, c);
|
||||
if (*d->__nextchar == '\0')
|
||||
++d->optind;
|
||||
if (temp == NULL || c == ':' || c == ';')
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
return '?';
|
||||
}
|
||||
if (temp[0] == 'W' && temp[1] == ';')
|
||||
{
|
||||
char *nameend;
|
||||
const struct option_a *p;
|
||||
const struct option_a *pfound = NULL;
|
||||
int exact = 0;
|
||||
int ambig = 0;
|
||||
int indfound = 0;
|
||||
int option_index;
|
||||
if (longopts == NULL)
|
||||
goto no_longs;
|
||||
if (*d->__nextchar != '\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
c = ':';
|
||||
else
|
||||
c = '?';
|
||||
return c;
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; nameend++);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!strncmp(p->name, d->__nextchar, nameend - d->__nextchar))
|
||||
{
|
||||
if ((unsigned int) (nameend - d->__nextchar) == strlen(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)
|
||||
{
|
||||
fprintf(stderr, "%s: option '-W %s' is ambiguous\n",argv[0], d->optarg);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optind++;
|
||||
return '?';
|
||||
}
|
||||
if (pfound != NULL)
|
||||
{
|
||||
option_index = indfound;
|
||||
if (*nameend)
|
||||
{
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: option '-W %s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
return optstring[0] == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar += strlen(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 'W';
|
||||
}
|
||||
if (temp[1] == ':')
|
||||
{
|
||||
if (temp[2] == ':')
|
||||
{
|
||||
if (*d->__nextchar != '\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*d->__nextchar != '\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
c = ':';
|
||||
else
|
||||
c = '?';
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
int _getopt_internal_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct)
|
||||
{
|
||||
int result;
|
||||
getopt_data_a.optind = optind;
|
||||
getopt_data_a.opterr = opterr;
|
||||
result = _getopt_internal_r_a (argc, argv, optstring, longopts,longind, long_only, &getopt_data_a,posixly_correct);
|
||||
optind = getopt_data_a.optind;
|
||||
optarg_a = getopt_data_a.optarg;
|
||||
optopt = getopt_data_a.optopt;
|
||||
return result;
|
||||
}
|
||||
int getopt_a (int argc, char *const *argv, const char *optstring) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_a (argc, argv, optstring, (const struct option_a *) 0, (int *) 0, 0, 0);
|
||||
}
|
||||
int getopt_long_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_a (argc, argv, options, long_options, opt_index, 0, 0);
|
||||
}
|
||||
int getopt_long_only_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_a (argc, argv, options, long_options, opt_index, 1, 0);
|
||||
}
|
||||
int _getopt_long_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d)
|
||||
{
|
||||
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index,0, d, 0);
|
||||
}
|
||||
int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d)
|
||||
{
|
||||
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||
}
|
||||
|
115
examples/client/getopt.h
Normal file
115
examples/client/getopt.h
Normal file
@ -0,0 +1,115 @@
|
||||
/* Getopt for Microsoft C
|
||||
This code is a modification of the Free Software Foundation, Inc.
|
||||
Getopt library for parsing command line argument the purpose was
|
||||
to provide a Microsoft Visual C friendly derivative. This code
|
||||
provides functionality for both Unicode and Multibyte builds.
|
||||
|
||||
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
Version: 1.0
|
||||
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||
and POSIXLY_CORRECT environment flag
|
||||
License: LGPL
|
||||
|
||||
Revisions:
|
||||
|
||||
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
#ifndef __GETOPT_H_
|
||||
#define __GETOPT_H_
|
||||
|
||||
#ifdef _GETOPT_API
|
||||
#undef _GETOPT_API
|
||||
#endif
|
||||
|
||||
#if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT)
|
||||
#error "The preprocessor definitions of EXPORTS_GETOPT and STATIC_GETOPT can only be used individually"
|
||||
#elif defined(STATIC_GETOPT)
|
||||
#pragma message("Warning static builds of getopt violate the Lesser GNU Public License")
|
||||
#define _GETOPT_API
|
||||
#elif defined(EXPORTS_GETOPT)
|
||||
#pragma message("Exporting getopt library")
|
||||
#define _GETOPT_API __declspec(dllexport)
|
||||
#else
|
||||
#pragma message("Importing getopt library")
|
||||
#define _GETOPT_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
// Change behavior for C\C++
|
||||
#ifdef __cplusplus
|
||||
#define _BEGIN_EXTERN_C extern "C" {
|
||||
#define _END_EXTERN_C }
|
||||
#define _GETOPT_THROW throw()
|
||||
#else
|
||||
#define _BEGIN_EXTERN_C
|
||||
#define _END_EXTERN_C
|
||||
#define _GETOPT_THROW
|
||||
#endif
|
||||
|
||||
// Standard GNU options
|
||||
#define null_argument 0 /*Argument Null*/
|
||||
#define no_argument 0 /*Argument Switch Only*/
|
||||
#define required_argument 1 /*Argument Required*/
|
||||
#define optional_argument 2 /*Argument Optional*/
|
||||
|
||||
// Shorter Options
|
||||
#define ARG_NULL 0 /*Argument Null*/
|
||||
#define ARG_NONE 0 /*Argument Switch Only*/
|
||||
#define ARG_REQ 1 /*Argument Required*/
|
||||
#define ARG_OPT 2 /*Argument Optional*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
_BEGIN_EXTERN_C
|
||||
|
||||
extern _GETOPT_API int optind;
|
||||
extern _GETOPT_API int opterr;
|
||||
extern _GETOPT_API int optopt;
|
||||
|
||||
struct option_a
|
||||
{
|
||||
const char* name;
|
||||
int has_arg;
|
||||
int *flag;
|
||||
int val;
|
||||
};
|
||||
extern _GETOPT_API char *optarg_a;
|
||||
extern _GETOPT_API int getopt_a(int argc, char *const *argv, const char *optstring) _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;
|
||||
|
||||
_END_EXTERN_C
|
||||
|
||||
#undef _BEGIN_EXTERN_C
|
||||
#undef _END_EXTERN_C
|
||||
#undef _GETOPT_THROW
|
||||
#undef _GETOPT_API
|
||||
|
||||
#define getopt getopt_a
|
||||
#define getopt_long getopt_long_a
|
||||
#define getopt_long_only getopt_long_only_a
|
||||
#define option option_a
|
||||
#define optarg optarg_a
|
||||
|
||||
#endif // __GETOPT_H_
|
@ -5,6 +5,7 @@
|
||||
* Copyright (c) 2020 Will Munn
|
||||
* Copyright (c) 2020 Nico Chatzi
|
||||
* Copyright (c) 2020 Lara Mackey
|
||||
* Copyright (c) 2020 Erik Cota-Robles
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -22,12 +23,16 @@
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
#include "parse_cl.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
@ -48,23 +53,45 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
weak_ptr<WebSocket> wws, string id);
|
||||
string randomId(size_t length);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
rtc::InitLogger(LogLevel::Debug);
|
||||
int main(int argc, char **argv) try {
|
||||
auto params = std::make_unique<Cmdline>(argc, argv);
|
||||
|
||||
rtc::InitLogger(LogLevel::Info);
|
||||
|
||||
Configuration config;
|
||||
config.iceServers.emplace_back("stun:stun.l.google.com:19302"); // change to your STUN server
|
||||
string stunServer = "";
|
||||
if (params->noStun()) {
|
||||
cout << "No STUN server is configured. Only local hosts and public IP addresses supported."
|
||||
<< endl;
|
||||
} else {
|
||||
if (params->stunServer().substr(0, 5).compare("stun:") != 0) {
|
||||
stunServer = "stun:";
|
||||
}
|
||||
stunServer += params->stunServer() + ":" + to_string(params->stunPort());
|
||||
cout << "Stun server is " << stunServer << endl;
|
||||
config.iceServers.emplace_back(stunServer);
|
||||
}
|
||||
|
||||
localId = randomId(4);
|
||||
cout << "The local ID is: " << localId << endl;
|
||||
|
||||
auto ws = make_shared<WebSocket>();
|
||||
|
||||
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
||||
std::promise<void> wsPromise;
|
||||
auto wsFuture = wsPromise.get_future();
|
||||
|
||||
ws->onOpen([&wsPromise]() {
|
||||
cout << "WebSocket connected, signaling ready" << endl;
|
||||
wsPromise.set_value();
|
||||
});
|
||||
|
||||
ws->onError([&wsPromise](string s) {
|
||||
cout << "WebSocket error" << endl;
|
||||
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
|
||||
});
|
||||
|
||||
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
||||
|
||||
ws->onError([](const string &error) { cout << "WebSocket failed: " << error << endl; });
|
||||
|
||||
ws->onMessage([&](variant<binary, string> data) {
|
||||
if (!holds_alternative<string>(data))
|
||||
return;
|
||||
@ -101,15 +128,17 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
});
|
||||
|
||||
const string url = "ws://localhost:8000/" + localId;
|
||||
string wsPrefix = "";
|
||||
if (params->webSocketServer().substr(0, 5).compare("ws://") != 0) {
|
||||
wsPrefix = "ws://";
|
||||
}
|
||||
const string url = wsPrefix + params->webSocketServer() + ":" +
|
||||
to_string(params->webSocketPort()) + "/" + localId;
|
||||
cout << "Url is " << url << endl;
|
||||
ws->open(url);
|
||||
|
||||
cout << "Waiting for signaling to be connected..." << endl;
|
||||
while (!ws->isOpen()) {
|
||||
if (ws->isClosed())
|
||||
return 1;
|
||||
this_thread::sleep_for(100ms);
|
||||
}
|
||||
wsFuture.get();
|
||||
|
||||
while (true) {
|
||||
string id;
|
||||
@ -137,16 +166,15 @@ int main(int argc, char **argv) {
|
||||
|
||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||
|
||||
dc->onMessage([id](const variant<binary, string> &message) {
|
||||
if (!holds_alternative<string>(message))
|
||||
return;
|
||||
|
||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
|
||||
if (holds_alternative<string>(data))
|
||||
cout << "Message from " << id << " received: " << get<string>(data) << endl;
|
||||
else
|
||||
cout << "Binary message from " << id
|
||||
<< " received, size=" << get<binary>(data).size() << endl;
|
||||
});
|
||||
|
||||
dataChannelMap.emplace(id, dc);
|
||||
|
||||
this_thread::sleep_for(1s);
|
||||
}
|
||||
|
||||
cout << "Cleaning up..." << endl;
|
||||
@ -154,6 +182,12 @@ int main(int argc, char **argv) {
|
||||
dataChannelMap.clear();
|
||||
peerConnectionMap.clear();
|
||||
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
|
||||
@ -190,11 +224,12 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
|
||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||
|
||||
dc->onMessage([id](const variant<binary, string> &message) {
|
||||
if (!holds_alternative<string>(message))
|
||||
return;
|
||||
|
||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
|
||||
if (holds_alternative<string>(data))
|
||||
cout << "Message from " << id << " received: " << get<string>(data) << endl;
|
||||
else
|
||||
cout << "Binary message from " << id
|
||||
<< " received, size=" << get<binary>(data).size() << endl;
|
||||
});
|
||||
|
||||
dc->send("Hello from " + localId);
|
||||
|
160
examples/client/parse_cl.cpp
Normal file
160
examples/client/parse_cl.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
/******************************************************************************
|
||||
**
|
||||
** parse_cl.cpp
|
||||
**
|
||||
** Thu Aug 6 19:42:25 2020
|
||||
** Linux 5.4.0-42-generic (#46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020) x86_64
|
||||
** cerik@Erik-VBox-Ubuntu (Erik Cota-Robles)
|
||||
**
|
||||
** Copyright (c) 2020 Erik Cota-Robles
|
||||
**
|
||||
** Definition of command line parser class
|
||||
**
|
||||
** Automatically created by genparse v0.9.3
|
||||
**
|
||||
** See http://genparse.sourceforge.net for details and updates
|
||||
**
|
||||
**
|
||||
******************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
#include "getopt.h"
|
||||
#else
|
||||
#include <getopt.h>
|
||||
#endif
|
||||
|
||||
#include "parse_cl.h"
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
**
|
||||
** Cmdline::Cmdline ()
|
||||
**
|
||||
** Constructor method.
|
||||
**
|
||||
**--------------------------------------------------------------------------*/
|
||||
|
||||
Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::string )
|
||||
{
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int c;
|
||||
|
||||
static struct option long_options[] =
|
||||
{
|
||||
{"noStun", no_argument, NULL, 'n'},
|
||||
{"stunServer", required_argument, NULL, 's'},
|
||||
{"stunPort", required_argument, NULL, 't'},
|
||||
{"webSocketServer", required_argument, NULL, 'w'},
|
||||
{"webSocketPort", required_argument, NULL, 'x'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
_program_name += argv[0];
|
||||
|
||||
/* default values */
|
||||
_n = false;
|
||||
_s = "stun.l.google.com";
|
||||
_t = 19302;
|
||||
_w = "localhost";
|
||||
_x = 8000;
|
||||
_h = false;
|
||||
|
||||
optind = 0;
|
||||
while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'n':
|
||||
_n = true;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
_s = optarg;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
_t = atoi (optarg);
|
||||
if (_t < 0)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: t must be >= 0";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
if (_t > 65535)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: t must be <= 65535";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
_w = optarg;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
_x = atoi (optarg);
|
||||
if (_x < 0)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: x must be >= 0";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
if (_x > 65535)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: x must be <= 65535";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
_h = true;
|
||||
this->usage (EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
default:
|
||||
this->usage (EXIT_FAILURE);
|
||||
|
||||
}
|
||||
} /* while */
|
||||
|
||||
_optind = optind;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
**
|
||||
** Cmdline::usage () and version()
|
||||
**
|
||||
** Print out usage (or version) information, then exit.
|
||||
**
|
||||
**--------------------------------------------------------------------------*/
|
||||
|
||||
void Cmdline::usage (int status)
|
||||
{
|
||||
if (status != EXIT_SUCCESS)
|
||||
std::cerr << "Try `" << _program_name << " --help' for more information.\n";
|
||||
else
|
||||
{
|
||||
std::cout << "\
|
||||
usage: " << _program_name << " [ -enstwxhv ] \n\
|
||||
libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\
|
||||
[ -n ] [ --noStun ] (type=FLAG)\n\
|
||||
Do NOT use a stun server (overrides -s and -t).\n\
|
||||
[ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\
|
||||
Stun server URL or IP address.\n\
|
||||
[ -t ] [ --stunPort ] (type=INTEGER, range=0...65535, default=19302)\n\
|
||||
Stun server port.\n\
|
||||
[ -w ] [ --webSocketServer ] (type=STRING, default=localhost)\n\
|
||||
Web socket server URL or IP address.\n\
|
||||
[ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\
|
||||
Web socket server port.\n\
|
||||
[ -h ] [ --help ] (type=FLAG)\n\
|
||||
Display this help and exit.\n";
|
||||
}
|
||||
exit (status);
|
||||
}
|
||||
|
67
examples/client/parse_cl.h
Normal file
67
examples/client/parse_cl.h
Normal file
@ -0,0 +1,67 @@
|
||||
/******************************************************************************
|
||||
**
|
||||
** parse_cl.h
|
||||
**
|
||||
** Thu Aug 6 19:42:25 2020
|
||||
** Linux 5.4.0-42-generic (#46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020) x86_64
|
||||
** cerik@Erik-VBox-Ubuntu (Erik Cota-Robles)
|
||||
**
|
||||
** Copyright (c) 2020 Erik Cota-Robles
|
||||
**
|
||||
** Header file for command line parser class
|
||||
**
|
||||
** Automatically created by genparse v0.9.3
|
||||
**
|
||||
** See http://genparse.sourceforge.net for details and updates
|
||||
**
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef CMDLINE_H
|
||||
#define CMDLINE_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
**
|
||||
** class Cmdline
|
||||
**
|
||||
** command line parser class
|
||||
**
|
||||
**--------------------------------------------------------------------------*/
|
||||
|
||||
class Cmdline
|
||||
{
|
||||
private:
|
||||
/* parameters */
|
||||
bool _n;
|
||||
std::string _s;
|
||||
int _t;
|
||||
std::string _w;
|
||||
int _x;
|
||||
bool _h;
|
||||
|
||||
/* other stuff to keep track of */
|
||||
std::string _program_name;
|
||||
int _optind;
|
||||
|
||||
public:
|
||||
/* constructor and destructor */
|
||||
Cmdline (int, char **); // ISO C++17 not allowed: throw (std::string);
|
||||
~Cmdline (){}
|
||||
|
||||
/* usage function */
|
||||
void usage (int status);
|
||||
|
||||
/* return next (non-option) parameter */
|
||||
int next_param () { return _optind; }
|
||||
|
||||
bool noStun () const { return _n; }
|
||||
std::string stunServer () const { return _s; }
|
||||
int stunPort () const { return _t; }
|
||||
std::string webSocketServer () const { return _w; }
|
||||
int webSocketPort () const { return _x; }
|
||||
bool h () const { return _h; }
|
||||
};
|
||||
|
||||
#endif
|
@ -13,3 +13,15 @@ set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
|
||||
OUTPUT_NAME answerer)
|
||||
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()
|
||||
|
@ -34,23 +34,23 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr);
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
static void closedCallback(void *ptr);
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||
static void RTC_API closedCallback(int id, void *ptr);
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
char* state_print(rtcState state);
|
||||
char *state_print(rtcState state);
|
||||
char *rtcGatheringState_print(rtcGatheringState state);
|
||||
|
||||
int all_space(const char *str);
|
||||
@ -66,189 +66,185 @@ int main(int argc, char **argv) {
|
||||
if (!peer) {
|
||||
fprintf(stderr, "Error allocating memory for peer\n");
|
||||
return -1;
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
printf("Peer created\n");
|
||||
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
|
||||
rtcSetUserPointer(peer->dc, NULL);
|
||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||
rtcSetUserPointer(peer->dc, NULL);
|
||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||
|
||||
sleep(1);
|
||||
sleep(1);
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
printf("\n");
|
||||
printf("***********************************************************************************"
|
||||
"****\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
int command = -1;
|
||||
int c;
|
||||
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char*) malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
||||
strcat(sdp, line);
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char *)malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||
strcat(sdp, line);
|
||||
}
|
||||
printf("%s\n", sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char *candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
|
||||
}
|
||||
printf("%s\n",sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
if (getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, NULL);
|
||||
free(candidate);
|
||||
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char* candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if(getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char *message = NULL;
|
||||
size_t message_size = 0;
|
||||
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
if (getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char* message = NULL;
|
||||
size_t message_size = 0;
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "answerer", cand);
|
||||
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
printf("Candidate %s: %s\n", "answerer", cand);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "answerer", state_print(state));
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "answerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
static void RTC_API closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "answerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
||||
}
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "answerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
||||
}
|
||||
}
|
||||
|
||||
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
rtcSetClosedCallback(dc, closedCallback);
|
||||
rtcSetMessageCallback(dc, messageCallback);
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||
}
|
||||
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
rtcSetClosedCallback(dc, closedCallback);
|
||||
rtcSetMessageCallback(dc, messageCallback);
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
char *state_print(rtcState state) {
|
||||
|
@ -34,20 +34,20 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
static void openCallback(void *ptr);
|
||||
static void closedCallback(void *ptr);
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||
static void RTC_API openCallback(int id, void *ptr);
|
||||
static void RTC_API closedCallback(int id, void *ptr);
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
char *state_print(rtcState state);
|
||||
@ -55,7 +55,7 @@ char *rtcGatheringState_print(rtcGatheringState state);
|
||||
|
||||
int all_space(const char *str);
|
||||
|
||||
int main(int argc, char **argv){
|
||||
int main(int argc, char **argv) {
|
||||
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
||||
|
||||
// Create peer
|
||||
@ -66,192 +66,185 @@ int main(int argc, char **argv){
|
||||
if (!peer) {
|
||||
fprintf(stderr, "Error allocating memory for peer\n");
|
||||
return -1;
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
printf("Peer created\n");
|
||||
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
|
||||
// Since this is the offere, we will create a datachannel
|
||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||
rtcSetOpenCallback(peer->dc, openCallback);
|
||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||
rtcSetMessageCallback(peer->dc, messageCallback);
|
||||
// Since we are the offerer, we will create a datachannel
|
||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||
rtcSetOpenCallback(peer->dc, openCallback);
|
||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||
rtcSetMessageCallback(peer->dc, messageCallback);
|
||||
|
||||
sleep(1);
|
||||
sleep(1);
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
printf("\n");
|
||||
printf("***********************************************************************************"
|
||||
"****\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
int command = -1;
|
||||
int c;
|
||||
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char *)malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||
strcat(sdp, line);
|
||||
}
|
||||
printf("%s\n", sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char *candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
if (getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, NULL);
|
||||
free(candidate);
|
||||
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char*) malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
||||
strcat(sdp, line);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
printf("%s\n",sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char *message = NULL;
|
||||
size_t message_size = 0;
|
||||
if (getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char* candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
if(getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char* message = NULL;
|
||||
size_t message_size = 0;
|
||||
if(getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "offerer", cand);
|
||||
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
printf("Candidate %s: %s\n", "offerer", cand);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "offerer", state_print(state));
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "offerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
||||
static void RTC_API openCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "offerer", buffer);
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
static void RTC_API closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "offerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "offerer", size);
|
||||
}
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "offerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "offerer", size);
|
||||
}
|
||||
}
|
||||
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
char *state_print(rtcState state) {
|
||||
|
@ -4,19 +4,23 @@ add_executable(datachannel-copy-paste-offerer offerer.cpp)
|
||||
set_target_properties(datachannel-copy-paste-offerer PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME offerer)
|
||||
if(WIN32)
|
||||
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()
|
||||
target_link_libraries(datachannel-copy-paste-offerer datachannel)
|
||||
|
||||
add_executable(datachannel-copy-paste-answerer answerer.cpp)
|
||||
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME answerer)
|
||||
if(WIN32)
|
||||
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()
|
||||
target_link_libraries(datachannel-copy-paste-answerer datachannel)
|
||||
|
||||
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 ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
Candidate local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Local: " << local << endl;
|
||||
cout << "Remote: " << remote << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
|
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
Candidate local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Local: " << local << endl;
|
||||
cout << "Remote: " << remote << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
|
@ -4,11 +4,12 @@ add_executable(datachannel-media main.cpp)
|
||||
set_target_properties(datachannel-media PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME media)
|
||||
target_link_libraries(datachannel-media datachannel nlohmann_json)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel-media datachannel-static) # DLL exports only the C API
|
||||
else()
|
||||
target_link_libraries(datachannel-media datachannel)
|
||||
add_custom_command(TARGET datachannel-media POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||
$<TARGET_FILE_DIR:datachannel-media>
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(datachannel-media datachannel nlohmann_json)
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* libdatachannel client example
|
||||
* Copyright (c) 2020 Staz M
|
||||
* Copyright (c) 2020 Staz Modrzynski
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -67,13 +67,13 @@ int main() {
|
||||
|
||||
auto track = pc->addTrack(media);
|
||||
|
||||
auto session = std::make_shared<rtc::RtcpSession>();
|
||||
auto session = std::make_shared<rtc::RtcpReceivingSession>();
|
||||
track->setRtcpHandler(session);
|
||||
|
||||
track->onMessage(
|
||||
[session, sock, addr](rtc::binary message) {
|
||||
// This is an RTP packet
|
||||
sendto(sock, reinterpret_cast<const char *>(message.data()), message.size(), 0,
|
||||
sendto(sock, reinterpret_cast<const char *>(message.data()), int(message.size()), 0,
|
||||
reinterpret_cast<const struct sockaddr *>(&addr), sizeof(addr));
|
||||
},
|
||||
nullptr);
|
||||
|
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>
|
@ -63,16 +63,20 @@ async def handle_websocket(websocket, path):
|
||||
print('Client {} disconnected'.format(client_id))
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
|
||||
# Usage: ./server.py [[host:]port] [SSL certificate file]
|
||||
endpoint_or_port = sys.argv[1] if len(sys.argv) > 1 else "8000"
|
||||
ssl_cert = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
|
||||
endpoint = endpoint_or_port if ':' in endpoint_or_port else "127.0.0.1:" + endpoint_or_port
|
||||
|
||||
if ssl_cert:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.load_cert_chain(ssl_cert)
|
||||
else:
|
||||
ssl_context = None
|
||||
|
||||
print('Listening on port {}'.format(port))
|
||||
start_server = websockets.serve(handle_websocket, '127.0.0.1', port, ssl=ssl_context)
|
||||
print('Listening on {}'.format(endpoint))
|
||||
host, port = endpoint.rsplit(':', 1)
|
||||
start_server = websockets.serve(handle_websocket, host, int(port), ssl=ssl_context)
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
asyncio.get_event_loop().run_forever()
|
||||
|
2
examples/signaling-server-rust/Cargo.lock
generated
2
examples/signaling-server-rust/Cargo.lock
generated
@ -348,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
||||
|
||||
[[package]]
|
||||
name = "libdatachannel_signaling_example"
|
||||
name = "libdatachannel_signaling_server_example"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
|
@ -92,7 +92,9 @@ async fn handle(clients: ClientsMap, stream: TcpStream) {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
let service = env::args().nth(1).unwrap_or("8000".to_string());
|
||||
let endpoint = format!("127.0.0.1:{}", service);
|
||||
let endpoint = if service.contains(':') { service } else { format!("127.0.0.1:{}", service) };
|
||||
|
||||
println!("Listening on {}", endpoint);
|
||||
|
||||
let mut listener = TcpListener::bind(endpoint)
|
||||
.await.expect("Listener binding failed");
|
||||
|
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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user