mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
537 Commits
Author | SHA1 | Date | |
---|---|---|---|
38db6d7365 | |||
781d864b9f | |||
8cbcb35bf4 | |||
b63ec9cead | |||
aa6f87f467 | |||
eec7a761e8 | |||
125edff298 | |||
0813976a5a | |||
4642504b83 | |||
5b760532c2 | |||
69bcdade50 | |||
bd3df48c0b | |||
faf3158609 | |||
b766be1880 | |||
b3edcfa05c | |||
19e148363c | |||
7f6f178177 | |||
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 | |||
a99efd27d2 | |||
9b62a543f6 | |||
5a797e1170 | |||
77475b57b0 | |||
dd296e4408 | |||
1be877132c | |||
7e79fd9721 | |||
5d78aecabb | |||
9fbc894fdb | |||
4930e666ac | |||
e5e337a0a5 | |||
704e604b8f | |||
b5d3cdc9b1 | |||
b8b6b913a9 | |||
f461a40a6d |
6
.github/workflows/build-gnutls.yml
vendored
6
.github/workflows/build-gnutls.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: install packages
|
- name: install packages
|
||||||
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev
|
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev libsrtp2-dev
|
||||||
- name: submodules
|
- name: submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: cmake
|
- name: cmake
|
||||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
|
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||||
- name: make
|
- name: make
|
||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: submodules
|
- name: submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: cmake
|
- name: cmake
|
||||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
|
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||||
- name: make
|
- name: make
|
||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
|
16
.github/workflows/build-nice.yml
vendored
16
.github/workflows/build-nice.yml
vendored
@ -7,7 +7,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
build-linux:
|
build-media:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@ -21,4 +21,18 @@ jobs:
|
|||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
run: ./build/tests
|
run: ./build/tests
|
||||||
|
build-no-media:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: install packages
|
||||||
|
run: sudo apt update && sudo apt install libgnutls28-dev libnice-dev
|
||||||
|
- name: submodules
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
- name: cmake
|
||||||
|
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_NICE=1 -DNO_MEDIA=1 -DWARNINGS_AS_ERRORS=1
|
||||||
|
- name: make
|
||||||
|
run: (cd build; make -j2)
|
||||||
|
- name: test
|
||||||
|
run: ./build/tests
|
||||||
|
|
||||||
|
7
.github/workflows/build-openssl.yml
vendored
7
.github/workflows/build-openssl.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: install packages
|
- name: install packages
|
||||||
run: sudo apt update && sudo apt install libssl-dev
|
run: sudo apt update && sudo apt install libssl-dev libsrtp2-dev
|
||||||
- name: submodules
|
- name: submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: cmake
|
- name: cmake
|
||||||
run: cmake -B build -DUSE_GNUTLS=0 -DWARNINGS_AS_ERRORS=1
|
run: cmake -B build -DUSE_GNUTLS=0 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||||
- name: make
|
- name: make
|
||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: submodules
|
- name: submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: cmake
|
- name: cmake
|
||||||
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1
|
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||||
env:
|
env:
|
||||||
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
||||||
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
||||||
@ -52,6 +52,7 @@ jobs:
|
|||||||
- name: nmake
|
- name: nmake
|
||||||
run: |
|
run: |
|
||||||
cd build
|
cd build
|
||||||
|
set CL=/MP
|
||||||
nmake
|
nmake
|
||||||
- name: test
|
- name: test
|
||||||
run: build/tests.exe
|
run: build/tests.exe
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,6 +5,7 @@ node_modules/
|
|||||||
*.a
|
*.a
|
||||||
*.so
|
*.so
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
tests
|
/tests
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -10,3 +10,6 @@
|
|||||||
[submodule "deps/json"]
|
[submodule "deps/json"]
|
||||||
path = deps/json
|
path = deps/json
|
||||||
url = https://github.com/nlohmann/json.git
|
url = https://github.com/nlohmann/json.git
|
||||||
|
[submodule "deps/libsrtp"]
|
||||||
|
path = deps/libsrtp
|
||||||
|
url = https://github.com/cisco/libsrtp.git
|
||||||
|
179
CMakeLists.txt
179
CMakeLists.txt
@ -1,16 +1,20 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.7)
|
||||||
project(libdatachannel
|
project(libdatachannel
|
||||||
DESCRIPTION "WebRTC Data Channels Library"
|
VERSION 0.11.10
|
||||||
VERSION 0.9.1
|
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||||
option(USE_NICE "Use libnice instead of libjuice" OFF)
|
option(USE_NICE "Use libnice instead of libjuice" OFF)
|
||||||
|
option(USE_SYSTEM_SRTP "Use system libSRTP" OFF)
|
||||||
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
||||||
|
option(NO_MEDIA "Disable media transport support" OFF)
|
||||||
option(NO_EXAMPLES "Disable examples" OFF)
|
option(NO_EXAMPLES "Disable examples" OFF)
|
||||||
option(NO_TESTS "Disable tests build" OFF)
|
option(NO_TESTS "Disable tests build" OFF)
|
||||||
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||||
|
option(RSA_KEY_BITS_2048 "Use 2048-bit RSA key instead of 3072-bit" OFF)
|
||||||
|
option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
|
||||||
|
|
||||||
if(USE_NICE)
|
if(USE_NICE)
|
||||||
option(USE_JUICE "Use libjuice" OFF)
|
option(USE_JUICE "Use libjuice" OFF)
|
||||||
@ -19,17 +23,21 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_GNUTLS)
|
if(USE_GNUTLS)
|
||||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
option(USE_NETTLE "Use Nettle in libjuice" ON)
|
||||||
else()
|
else()
|
||||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
|
option(USE_NETTLE "Use Nettle in libjuice" OFF)
|
||||||
|
if(NOT USE_SYSTEM_SRTP)
|
||||||
|
option(ENABLE_OPENSSL "Enable OpenSSL crypto engine for SRTP" ON)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||||
|
set(BUILD_SHARED_LIBS OFF) # to force usrsctp to be built static
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||||
if (MSVC)
|
if(MSVC)
|
||||||
add_definitions(-DNOMINMAX)
|
add_definitions(-DNOMINMAX)
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||||
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
||||||
@ -50,13 +58,39 @@ set(LIBDATACHANNEL_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/log.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/log.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/message.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/message.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcp.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/logcounter.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpreceivingsession.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/track.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/track.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtppacketizationconfig.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpsrreporter.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtppacketizer.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/opusrtppacketizer.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/opuspacketizationhandler.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtppacketizer.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/h264packetizationhandler.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/mediachainablehandler.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/mediahandlerelement.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/mediahandlerrootelement.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpnackresponder.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(LIBDATACHANNEL_PRIVATE_HEADERS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlssrtptransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/logcounter.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/transport.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
||||||
@ -68,6 +102,14 @@ set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/wstransport.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/wstransport.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(LIBDATACHANNEL_WEBSOCKET_PRIVATE_HEADERS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/base64.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tcptransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tlstransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/verifiedtlstransport.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/wstransport.hpp
|
||||||
|
)
|
||||||
|
|
||||||
set(LIBDATACHANNEL_HEADERS
|
set(LIBDATACHANNEL_HEADERS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp
|
||||||
@ -75,7 +117,8 @@ set(LIBDATACHANNEL_HEADERS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcp.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandler.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpreceivingsession.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/include.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/include.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/init.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/init.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/log.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/log.hpp
|
||||||
@ -85,13 +128,27 @@ set(LIBDATACHANNEL_HEADERS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizationconfig.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpsrreporter.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizer.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/opusrtppacketizer.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/opuspacketizationhandler.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtppacketizer.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264packetizationhandler.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediachainablehandler.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandlerelement.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandlerrootelement.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpnackresponder.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TESTS_SOURCES
|
set(TESTS_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/turn_connectivity.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/track.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/track.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_connectivity.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_connectivity.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_track.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_track.cpp
|
||||||
@ -99,6 +156,24 @@ set(TESTS_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/benchmark.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/benchmark.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(TESTS_UWP_RESOURCES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/Logo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/package.appxManifest
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/SmallLogo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/SmallLogo44x44.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/SplashScreen.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/StoreLogo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/tests/Windows_TemporaryKey.pfx)
|
||||||
|
|
||||||
|
set(BENCHMARK_UWP_RESOURCES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/Logo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/package.appxManifest
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/SmallLogo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/SmallLogo44x44.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/SplashScreen.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/StoreLogo.png
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/uwp/benchmark/Windows_TemporaryKey.pfx)
|
||||||
|
|
||||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
@ -107,32 +182,40 @@ set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
|||||||
add_subdirectory(deps/plog)
|
add_subdirectory(deps/plog)
|
||||||
|
|
||||||
option(sctp_build_programs 0)
|
option(sctp_build_programs 0)
|
||||||
|
option(sctp_build_shared_lib 0)
|
||||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||||
if (MSYS OR MINGW)
|
if (MSYS OR MINGW)
|
||||||
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||||
target_compile_definitions(usrsctp-static PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
|
||||||
endif()
|
endif()
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||||
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
|
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
|
||||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
|
|
||||||
endif()
|
endif()
|
||||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
|
||||||
|
|
||||||
if (NO_WEBSOCKET)
|
if (NO_WEBSOCKET)
|
||||||
add_library(datachannel SHARED
|
add_library(datachannel SHARED
|
||||||
${LIBDATACHANNEL_SOURCES})
|
${LIBDATACHANNEL_SOURCES}
|
||||||
|
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_HEADERS})
|
||||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
||||||
${LIBDATACHANNEL_SOURCES})
|
${LIBDATACHANNEL_SOURCES}
|
||||||
|
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_HEADERS})
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
||||||
else()
|
else()
|
||||||
add_library(datachannel SHARED
|
add_library(datachannel SHARED
|
||||||
${LIBDATACHANNEL_SOURCES}
|
${LIBDATACHANNEL_SOURCES}
|
||||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_WEBSOCKET_SOURCES}
|
||||||
|
${LIBDATACHANNEL_WEBSOCKET_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_HEADERS})
|
||||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
||||||
${LIBDATACHANNEL_SOURCES}
|
${LIBDATACHANNEL_SOURCES}
|
||||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_WEBSOCKET_SOURCES}
|
||||||
|
${LIBDATACHANNEL_WEBSOCKET_PRIVATE_HEADERS}
|
||||||
|
${LIBDATACHANNEL_HEADERS})
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
||||||
endif()
|
endif()
|
||||||
@ -148,21 +231,27 @@ target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includ
|
|||||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
target_link_libraries(datachannel PUBLIC Threads::Threads plog::plog)
|
target_link_libraries(datachannel PUBLIC Threads::Threads plog::plog)
|
||||||
target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic)
|
target_link_libraries(datachannel PRIVATE Usrsctp::Usrsctp)
|
||||||
|
|
||||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
|
target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
|
||||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
|
target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
|
target_link_libraries(datachannel PUBLIC ws2_32) # winsock2
|
||||||
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
target_link_libraries(datachannel-static PUBLIC ws2_32) # winsock2
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(SRTP)
|
if(NO_MEDIA)
|
||||||
if(SRTP_FOUND)
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
|
||||||
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
|
||||||
|
else()
|
||||||
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||||
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||||
|
if(USE_SYSTEM_SRTP)
|
||||||
|
find_package(SRTP REQUIRED)
|
||||||
if(NOT TARGET SRTP::SRTP)
|
if(NOT TARGET SRTP::SRTP)
|
||||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||||
set_target_properties(SRTP::SRTP PROPERTIES
|
set_target_properties(SRTP::SRTP PROPERTIES
|
||||||
@ -170,15 +259,17 @@ if(SRTP_FOUND)
|
|||||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||||
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
message(STATUS "LibSRTP found, compiling with media transport")
|
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=1)
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=1)
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
|
||||||
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
||||||
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
||||||
else()
|
else()
|
||||||
message(STATUS "LibSRTP NOT found, compiling WITHOUT media transport")
|
add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
|
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=0)
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
|
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=0)
|
||||||
|
target_link_libraries(datachannel PRIVATE srtp2)
|
||||||
|
target_link_libraries(datachannel-static PRIVATE srtp2)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_GNUTLS)
|
if (USE_GNUTLS)
|
||||||
@ -217,6 +308,16 @@ else()
|
|||||||
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(RSA_KEY_BITS_2048)
|
||||||
|
target_compile_definitions(datachannel PUBLIC RSA_KEY_BITS_2048)
|
||||||
|
target_compile_definitions(datachannel-static PUBLIC RSA_KEY_BITS_2048)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CAPI_STDCALL)
|
||||||
|
target_compile_definitions(datachannel PUBLIC CAPI_STDCALL)
|
||||||
|
target_compile_definitions(datachannel-static PUBLIC CAPI_STDCALL)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||||
|
|
||||||
@ -240,39 +341,45 @@ endif()
|
|||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
if(NOT NO_TESTS)
|
if(NOT NO_TESTS)
|
||||||
|
if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
||||||
|
# Add resource files needed for UWP apps.
|
||||||
|
add_executable(datachannel-tests ${TESTS_SOURCES} ${TESTS_UWP_RESOURCES})
|
||||||
|
else()
|
||||||
add_executable(datachannel-tests ${TESTS_SOURCES})
|
add_executable(datachannel-tests ${TESTS_SOURCES})
|
||||||
|
endif()
|
||||||
set_target_properties(datachannel-tests PROPERTIES
|
set_target_properties(datachannel-tests PROPERTIES
|
||||||
VERSION ${PROJECT_VERSION}
|
VERSION ${PROJECT_VERSION}
|
||||||
CXX_STANDARD 17)
|
CXX_STANDARD 17)
|
||||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||||
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(datachannel-tests datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-tests datachannel)
|
target_link_libraries(datachannel-tests datachannel)
|
||||||
endif()
|
|
||||||
|
|
||||||
# Benchmark
|
# Benchmark
|
||||||
|
if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
||||||
|
# Add resource files needed for UWP apps.
|
||||||
|
add_executable(datachannel-benchmark test/benchmark.cpp ${BENCHMARK_UWP_RESOURCES})
|
||||||
|
else()
|
||||||
add_executable(datachannel-benchmark test/benchmark.cpp)
|
add_executable(datachannel-benchmark test/benchmark.cpp)
|
||||||
|
endif()
|
||||||
set_target_properties(datachannel-benchmark PROPERTIES
|
set_target_properties(datachannel-benchmark PROPERTIES
|
||||||
VERSION ${PROJECT_VERSION}
|
VERSION ${PROJECT_VERSION}
|
||||||
CXX_STANDARD 17)
|
CXX_STANDARD 17)
|
||||||
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
||||||
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
|
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
|
||||||
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(datachannel-benchmark datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-benchmark datachannel)
|
target_link_libraries(datachannel-benchmark datachannel)
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
if(NOT NO_EXAMPLES)
|
if(NOT NO_EXAMPLES AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
||||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||||
add_subdirectory(deps/json)
|
add_subdirectory(deps/json)
|
||||||
add_subdirectory(examples/client)
|
add_subdirectory(examples/client)
|
||||||
|
if(NOT NO_MEDIA)
|
||||||
add_subdirectory(examples/media)
|
add_subdirectory(examples/media)
|
||||||
|
add_subdirectory(examples/sfu-media)
|
||||||
|
add_subdirectory(examples/streamer)
|
||||||
|
endif()
|
||||||
add_subdirectory(examples/copy-paste)
|
add_subdirectory(examples/copy-paste)
|
||||||
add_subdirectory(examples/copy-paste-capi)
|
add_subdirectory(examples/copy-paste-capi)
|
||||||
endif()
|
endif()
|
||||||
|
8
Jamfile
8
Jamfile
@ -15,9 +15,9 @@ lib libdatachannel
|
|||||||
: # requirements
|
: # requirements
|
||||||
<cxxstd>17
|
<cxxstd>17
|
||||||
<include>./include/rtc
|
<include>./include/rtc
|
||||||
<define>USE_NICE=0
|
|
||||||
<define>RTC_ENABLE_MEDIA=0
|
<define>RTC_ENABLE_MEDIA=0
|
||||||
<define>RTC_ENABLE_WEBSOCKET=0
|
<define>RTC_ENABLE_WEBSOCKET=0
|
||||||
|
<define>USE_NICE=0
|
||||||
<toolset>msvc:<define>WIN32_LEAN_AND_MEAN
|
<toolset>msvc:<define>WIN32_LEAN_AND_MEAN
|
||||||
<toolset>msvc:<define>NOMINMAX
|
<toolset>msvc:<define>NOMINMAX
|
||||||
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS
|
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS
|
||||||
@ -32,6 +32,8 @@ lib libdatachannel
|
|||||||
<link>static
|
<link>static
|
||||||
: # usage requirements
|
: # usage requirements
|
||||||
<include>./include
|
<include>./include
|
||||||
|
<define>RTC_ENABLE_MEDIA=0
|
||||||
|
<define>RTC_ENABLE_WEBSOCKET=0
|
||||||
<library>/libdatachannel//plog
|
<library>/libdatachannel//plog
|
||||||
<toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
<toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||||
<toolset>clang:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
<toolset>clang:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||||
@ -94,7 +96,7 @@ rule make_libusrsctp ( targets * : sources * : properties * )
|
|||||||
}
|
}
|
||||||
actions make_libusrsctp
|
actions make_libusrsctp
|
||||||
{
|
{
|
||||||
(cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC -Wno-unknown-warning-option -Wno-format-truncation" .. && make -j2 usrsctp-static)
|
(cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC -Wno-unknown-warning-option -Wno-format-truncation" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 .. && make -j2 usrsctp)
|
||||||
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
|
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
|
||||||
}
|
}
|
||||||
rule make_libusrsctp_msvc ( targets * : sources * : properties * )
|
rule make_libusrsctp_msvc ( targets * : sources * : properties * )
|
||||||
@ -109,7 +111,7 @@ actions make_libusrsctp_msvc
|
|||||||
cd $(CWD)/deps/usrsctp
|
cd $(CWD)/deps/usrsctp
|
||||||
mkdir $(BUILD_DIR)
|
mkdir $(BUILD_DIR)
|
||||||
cd $(BUILD_DIR)
|
cd $(BUILD_DIR)
|
||||||
cmake -G "Visual Studio 16 2019" ..
|
cmake -G "Visual Studio 16 2019" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 ..
|
||||||
msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
|
msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
|
||||||
cd %OLDD%
|
cd %OLDD%
|
||||||
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<)
|
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<)
|
||||||
|
29
Makefile
29
Makefile
@ -10,6 +10,7 @@ LDFLAGS=-pthread
|
|||||||
LIBS=
|
LIBS=
|
||||||
LOCALLIBS=libusrsctp.a
|
LOCALLIBS=libusrsctp.a
|
||||||
USRSCTP_DIR=deps/usrsctp
|
USRSCTP_DIR=deps/usrsctp
|
||||||
|
SRTP_DIR=deps/libsrtp
|
||||||
JUICE_DIR=deps/libjuice
|
JUICE_DIR=deps/libjuice
|
||||||
PLOG_DIR=deps/plog
|
PLOG_DIR=deps/plog
|
||||||
|
|
||||||
@ -38,15 +39,22 @@ ifneq ($(USE_GNUTLS), 0)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
USE_SRTP ?= 0
|
NO_MEDIA ?= 0
|
||||||
ifneq ($(USE_SRTP), 0)
|
USE_SYSTEM_SRTP ?= 0
|
||||||
|
ifeq ($(NO_MEDIA), 0)
|
||||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=1
|
CPPFLAGS+=-DRTC_ENABLE_MEDIA=1
|
||||||
|
ifneq ($(USE_SYSTEM_SRTP), 0)
|
||||||
|
CPPFLAGS+=-DRTC_SYSTEM_SRTP=1
|
||||||
LIBS+=srtp
|
LIBS+=srtp
|
||||||
|
else
|
||||||
|
CPPFLAGS+=-DRTC_SYSTEM_SRTP=0
|
||||||
|
INCLUDES+=-I$(SRTP_DIR)/include
|
||||||
|
LOCALLIBS+=libsrtp2.a
|
||||||
|
endif
|
||||||
else
|
else
|
||||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=0
|
CPPFLAGS+=-DRTC_ENABLE_MEDIA=0
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
NO_WEBSOCKET ?= 0
|
NO_WEBSOCKET ?= 0
|
||||||
ifeq ($(NO_WEBSOCKET), 0)
|
ifeq ($(NO_WEBSOCKET), 0)
|
||||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=1
|
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=1
|
||||||
@ -54,8 +62,8 @@ else
|
|||||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=0
|
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=0
|
||||||
endif
|
endif
|
||||||
|
|
||||||
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
|
INCLUDES+=$(if $(LIBS),$(shell pkg-config --cflags $(LIBS)),)
|
||||||
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
LDLIBS+=$(LOCALLIBS) $(if $(LIBS),$(shell pkg-config --libs $(LIBS)),)
|
||||||
|
|
||||||
SRCS=$(shell printf "%s " src/*.cpp)
|
SRCS=$(shell printf "%s " src/*.cpp)
|
||||||
OBJS=$(subst .cpp,.o,$(SRCS))
|
OBJS=$(subst .cpp,.o,$(SRCS))
|
||||||
@ -73,7 +81,7 @@ test/%.o: test/%.cpp
|
|||||||
|
|
||||||
-include $(subst .cpp,.d,$(SRCS))
|
-include $(subst .cpp,.d,$(SRCS))
|
||||||
|
|
||||||
$(NAME).a: $(OBJS)
|
$(NAME).a: $(LOCALLIBS) $(OBJS)
|
||||||
$(AR) crf $@ $(OBJS)
|
$(AR) crf $@ $(OBJS)
|
||||||
|
|
||||||
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
||||||
@ -97,15 +105,22 @@ dist-clean: clean
|
|||||||
-$(RM) src/*~
|
-$(RM) src/*~
|
||||||
-$(RM) test/*~
|
-$(RM) test/*~
|
||||||
-cd $(USRSCTP_DIR) && make clean
|
-cd $(USRSCTP_DIR) && make clean
|
||||||
|
-cd $(SRTP_DIR) && make clean
|
||||||
-cd $(JUICE_DIR) && make clean
|
-cd $(JUICE_DIR) && make clean
|
||||||
|
|
||||||
libusrsctp.a:
|
libusrsctp.a:
|
||||||
cd $(USRSCTP_DIR) && \
|
cd $(USRSCTP_DIR) && \
|
||||||
./bootstrap && \
|
./bootstrap && \
|
||||||
./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS) -Wno-error=format-truncation" && \
|
./configure --enable-static --disable-debug CFLAGS="-fPIC" && \
|
||||||
make
|
make
|
||||||
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
||||||
|
|
||||||
|
libsrtp2.a:
|
||||||
|
cd $(SRTP_DIR) && \
|
||||||
|
./configure && \
|
||||||
|
make
|
||||||
|
cp $(SRTP_DIR)/libsrtp2.a .
|
||||||
|
|
||||||
libjuice.a:
|
libjuice.a:
|
||||||
ifneq ($(USE_GNUTLS), 0)
|
ifneq ($(USE_GNUTLS), 0)
|
||||||
cd $(JUICE_DIR) && make USE_NETTLE=1
|
cd $(JUICE_DIR) && make USE_NETTLE=1
|
||||||
|
41
README.md
41
README.md
@ -19,17 +19,17 @@ The WebRTC stack has been tested to be compatible with Firefox and Chromium.
|
|||||||
|
|
||||||
Protocol stack:
|
Protocol stack:
|
||||||
- SCTP-based Data Channels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13))
|
- SCTP-based Data Channels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13))
|
||||||
- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26)) with [libSRTP](https://github.com/cisco/libsrtp)
|
- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26))
|
||||||
- DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261))
|
- DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261))
|
||||||
- ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)) with STUN ([RFC5389](https://tools.ietf.org/html/rfc5389))
|
- ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)) with STUN ([RFC8489](https://tools.ietf.org/html/rfc8489)) and its extension TURN ([RFC8656](https://tools.ietf.org/html/rfc8656))
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
- Full IPv6 support
|
- Full IPv6 support
|
||||||
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
|
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
|
||||||
- JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26))
|
- JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26))
|
||||||
- SRTP and SRTCP key derivation from DTLS ([RFC5764](https://tools.ietf.org/html/rfc5764))
|
|
||||||
- Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
|
- Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
|
||||||
- TURN relaying ([RFC5766](https://tools.ietf.org/html/rfc5766)) with [libnice](https://github.com/libnice/libnice) as ICE backend
|
- SRTP and SRTCP key derivation from DTLS ([RFC5764](https://tools.ietf.org/html/rfc5764))
|
||||||
|
- Differentiated Services QoS ([draft-ietf-tsvwg-rtcweb-qos-18](https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18))
|
||||||
|
|
||||||
Note only SDP BUNDLE mode is supported for media multiplexing ([draft-ietf-mmusic-sdp-bundle-negotiation-54](https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54)). The behavior is equivalent to the JSEP bundle-only policy: the library always negociates one unique network component, where SRTP media streams are multiplexed with SRTCP control packets ([RFC5761](https://tools.ietf.org/html/rfc5761)) and SCTP/DTLS data traffic ([RFC5764](https://tools.ietf.org/html/rfc5764)).
|
Note only SDP BUNDLE mode is supported for media multiplexing ([draft-ietf-mmusic-sdp-bundle-negotiation-54](https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54)). The behavior is equivalent to the JSEP bundle-only policy: the library always negociates one unique network component, where SRTP media streams are multiplexed with SRTCP control packets ([RFC5761](https://tools.ietf.org/html/rfc5761)) and SCTP/DTLS data traffic ([RFC5764](https://tools.ietf.org/html/rfc5764)).
|
||||||
|
|
||||||
@ -53,10 +53,11 @@ Dependencies:
|
|||||||
Submodules:
|
Submodules:
|
||||||
- libjuice: https://github.com/paullouisageneau/libjuice
|
- libjuice: https://github.com/paullouisageneau/libjuice
|
||||||
- usrsctp: https://github.com/sctplab/usrsctp
|
- usrsctp: https://github.com/sctplab/usrsctp
|
||||||
|
- libsrtp: https://github.com/cisco/libsrtp
|
||||||
|
|
||||||
Optional dependencies:
|
Optional dependencies:
|
||||||
- libnice: https://nice.freedesktop.org/ (only if selected as ICE backend instead of libjuice)
|
- libnice: https://nice.freedesktop.org/ (if selected as ICE backend instead of libjuice)
|
||||||
- libSRTP: https://github.com/cisco/libsrtp (only necessary for supporting media transport)
|
- libsrtp: https://github.com/cisco/libsrtp (if selected instead of the submodule)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ $ git submodule update --init --recursive
|
|||||||
|
|
||||||
The CMake library targets `libdatachannel` and `libdatachannel-static` respectively correspond to the shared and static libraries. The default target will build tests and examples. The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
The CMake library targets `libdatachannel` and `libdatachannel-static` respectively correspond to the shared and static libraries. The default target will build tests and examples. The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
||||||
|
|
||||||
On Windows, the DLL resulting from the shared library build only exposes the C API, use the static library for the C++ API.
|
If you only need Data Channels, the option `NO_MEDIA` allows to make the library lighter by removing media support. Similarly, `NO_WEBSOCKET` removes WebSocket support.
|
||||||
|
|
||||||
#### POSIX-compliant operating systems (including Linux and Apple macOS)
|
#### POSIX-compliant operating systems (including Linux and Apple macOS)
|
||||||
```bash
|
```bash
|
||||||
@ -81,6 +82,29 @@ $ cd build
|
|||||||
$ make -j2
|
$ make -j2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Apple macOS with XCode project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cmake -B "$BUILD_DIR" -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode
|
||||||
|
```
|
||||||
|
|
||||||
|
Xcode project is generated in *build/* directory.
|
||||||
|
|
||||||
|
##### Solving **Could NOT find OpenSSL** error
|
||||||
|
|
||||||
|
You need to add OpenSSL root directory if your build fails with the following message:
|
||||||
|
|
||||||
|
```
|
||||||
|
Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the
|
||||||
|
system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY
|
||||||
|
OPENSSL_INCLUDE_DIR)
|
||||||
|
```
|
||||||
|
|
||||||
|
for example:
|
||||||
|
```bash
|
||||||
|
$ cmake -B build -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl\@1.1/1.1.1h/
|
||||||
|
```
|
||||||
|
|
||||||
#### Microsoft Windows with MinGW cross-compilation
|
#### Microsoft Windows with MinGW cross-compilation
|
||||||
```bash
|
```bash
|
||||||
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
|
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
|
||||||
@ -99,6 +123,8 @@ $ nmake
|
|||||||
|
|
||||||
The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
||||||
|
|
||||||
|
If you only need Data Channels, the option `NO_MEDIA` removes media support. Similarly, `NO_WEBSOCKET` removes WebSocket support.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ make USE_GNUTLS=1 USE_NICE=0
|
$ make USE_GNUTLS=1 USE_NICE=0
|
||||||
```
|
```
|
||||||
@ -202,5 +228,6 @@ ws->open("wss://my.websocket/service");
|
|||||||
## External resources
|
## External resources
|
||||||
- Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
- Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
||||||
- Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
|
- Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
|
||||||
|
- Unity wrapper for Windows 10 and Hololens: [datachannel-unity](https://github.com/hanseuljun/datachannel-unity)
|
||||||
- WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
|
- WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
|
||||||
|
|
||||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: da29d725ab...7a9f10e4f7
1
deps/libsrtp
vendored
Submodule
1
deps/libsrtp
vendored
Submodule
Submodule deps/libsrtp added at f379f48412
2
deps/usrsctp
vendored
2
deps/usrsctp
vendored
Submodule deps/usrsctp updated: 0db9691000...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)
|
- [signaling-server-rust](signaling-server-rust) contains a similar signaling server in Rust (see [lerouxrgd/datachannel-rs](https://github.com/lerouxrgd/datachannel-rs) for Rust wrappers)
|
||||||
|
|
||||||
- [media](media) is a copy/paste demo to send the webcam from your browser into gstreamer.
|
- [media](media) is a copy/paste demo to send the webcam from your browser into gstreamer.
|
||||||
|
- [streamer](streamer) streams h264 and opus samples to web browsers (signaling-server-python is required).
|
||||||
|
|
||||||
Additionally, it contains two debugging tools for libdatachannel with copy-pasting as signaling:
|
Additionally, it contains two debugging tools for libdatachannel with copy-pasting as signaling:
|
||||||
- [copy-paste](copy-paste) using the C++ API
|
- [copy-paste](copy-paste) using the C++ API
|
||||||
|
@ -4,19 +4,21 @@ if(POLICY CMP0079)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
|
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
|
||||||
target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
|
target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
|
||||||
else()
|
else()
|
||||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
|
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_target_properties(datachannel-client PROPERTIES
|
set_target_properties(datachannel-client PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME client)
|
OUTPUT_NAME client)
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(datachannel-client datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-client datachannel)
|
|
||||||
endif()
|
|
||||||
target_link_libraries(datachannel-client datachannel nlohmann_json)
|
target_link_libraries(datachannel-client datachannel nlohmann_json)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET datachannel-client POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-client>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# getopt-for-windows
|
|
||||||
getopt.h and getopt.c is very often used in linux, to make it easy for windows user, two files were extracted from glibc. In order to make it works properly in windows, some modification was done and you may compare the change using original source files. Enjoy it!
|
|
||||||
|
|
||||||
Source: https://github.com/Chunde/getopt-for-windows. IMPORTANT: getopt.[ch] are likely not safe for Linux due to conflict with existing getopt.[ch]. They are thus NOT in CMakeFiles.txt and instead both files are #include only on Windows.
|
|
@ -21,6 +21,7 @@ Revisions:
|
|||||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||||
|
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||||
|
|
||||||
**DISCLAIMER**
|
**DISCLAIMER**
|
||||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||||
@ -35,7 +36,11 @@ PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
|||||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||||
#define _CRT_SECURE_NO_WARNINGS
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
@ -52,12 +57,6 @@ int opterr = 1;
|
|||||||
int optopt = '?';
|
int optopt = '?';
|
||||||
enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER };
|
enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER };
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Ansi structures and functions follow
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
static struct _getopt_data_a
|
static struct _getopt_data_a
|
||||||
{
|
{
|
||||||
int optind;
|
int optind;
|
||||||
@ -512,462 +511,3 @@ int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, con
|
|||||||
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0);
|
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Unicode Structures and Functions
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
static struct _getopt_data_w
|
|
||||||
{
|
|
||||||
int optind;
|
|
||||||
int opterr;
|
|
||||||
int optopt;
|
|
||||||
wchar_t *optarg;
|
|
||||||
int __initialized;
|
|
||||||
wchar_t *__nextchar;
|
|
||||||
enum ENUM_ORDERING __ordering;
|
|
||||||
int __posixly_correct;
|
|
||||||
int __first_nonopt;
|
|
||||||
int __last_nonopt;
|
|
||||||
} getopt_data_w;
|
|
||||||
wchar_t *optarg_w;
|
|
||||||
|
|
||||||
static void exchange_w(wchar_t **argv, struct _getopt_data_w *d)
|
|
||||||
{
|
|
||||||
int bottom = d->__first_nonopt;
|
|
||||||
int middle = d->__last_nonopt;
|
|
||||||
int top = d->optind;
|
|
||||||
wchar_t *tem;
|
|
||||||
while (top > middle && middle > bottom)
|
|
||||||
{
|
|
||||||
if (top - middle > middle - bottom)
|
|
||||||
{
|
|
||||||
int len = middle - bottom;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
tem = argv[bottom + i];
|
|
||||||
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
|
||||||
argv[top - (middle - bottom) + i] = tem;
|
|
||||||
}
|
|
||||||
top -= len;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int len = top - middle;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
tem = argv[bottom + i];
|
|
||||||
argv[bottom + i] = argv[middle + i];
|
|
||||||
argv[middle + i] = tem;
|
|
||||||
}
|
|
||||||
bottom += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
|
||||||
d->__last_nonopt = d->optind;
|
|
||||||
}
|
|
||||||
static const wchar_t *_getopt_initialize_w (const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct)
|
|
||||||
{
|
|
||||||
d->__first_nonopt = d->__last_nonopt = d->optind;
|
|
||||||
d->__nextchar = NULL;
|
|
||||||
d->__posixly_correct = posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT");
|
|
||||||
if (optstring[0] == L'-')
|
|
||||||
{
|
|
||||||
d->__ordering = RETURN_IN_ORDER;
|
|
||||||
++optstring;
|
|
||||||
}
|
|
||||||
else if (optstring[0] == L'+')
|
|
||||||
{
|
|
||||||
d->__ordering = REQUIRE_ORDER;
|
|
||||||
++optstring;
|
|
||||||
}
|
|
||||||
else if (d->__posixly_correct)
|
|
||||||
d->__ordering = REQUIRE_ORDER;
|
|
||||||
else
|
|
||||||
d->__ordering = PERMUTE;
|
|
||||||
return optstring;
|
|
||||||
}
|
|
||||||
int _getopt_internal_r_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct)
|
|
||||||
{
|
|
||||||
int print_errors = d->opterr;
|
|
||||||
if (argc < 1)
|
|
||||||
return -1;
|
|
||||||
d->optarg = NULL;
|
|
||||||
if (d->optind == 0 || !d->__initialized)
|
|
||||||
{
|
|
||||||
if (d->optind == 0)
|
|
||||||
d->optind = 1;
|
|
||||||
optstring = _getopt_initialize_w (optstring, d, posixly_correct);
|
|
||||||
d->__initialized = 1;
|
|
||||||
}
|
|
||||||
else if (optstring[0] == L'-' || optstring[0] == L'+')
|
|
||||||
optstring++;
|
|
||||||
if (optstring[0] == L':')
|
|
||||||
print_errors = 0;
|
|
||||||
if (d->__nextchar == NULL || *d->__nextchar == L'\0')
|
|
||||||
{
|
|
||||||
if (d->__last_nonopt > d->optind)
|
|
||||||
d->__last_nonopt = d->optind;
|
|
||||||
if (d->__first_nonopt > d->optind)
|
|
||||||
d->__first_nonopt = d->optind;
|
|
||||||
if (d->__ordering == PERMUTE)
|
|
||||||
{
|
|
||||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
|
||||||
exchange_w((wchar_t **) argv, d);
|
|
||||||
else if (d->__last_nonopt != d->optind)
|
|
||||||
d->__first_nonopt = d->optind;
|
|
||||||
while (d->optind < argc && (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
|
|
||||||
d->optind++;
|
|
||||||
d->__last_nonopt = d->optind;
|
|
||||||
}
|
|
||||||
if (d->optind != argc && !wcscmp(argv[d->optind], L"--"))
|
|
||||||
{
|
|
||||||
d->optind++;
|
|
||||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
|
||||||
exchange_w((wchar_t **) argv, d);
|
|
||||||
else if (d->__first_nonopt == d->__last_nonopt)
|
|
||||||
d->__first_nonopt = d->optind;
|
|
||||||
d->__last_nonopt = argc;
|
|
||||||
d->optind = argc;
|
|
||||||
}
|
|
||||||
if (d->optind == argc)
|
|
||||||
{
|
|
||||||
if (d->__first_nonopt != d->__last_nonopt)
|
|
||||||
d->optind = d->__first_nonopt;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if ((argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
|
|
||||||
{
|
|
||||||
if (d->__ordering == REQUIRE_ORDER)
|
|
||||||
return -1;
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == L'-'));
|
|
||||||
}
|
|
||||||
if (longopts != NULL && (argv[d->optind][1] == L'-' || (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1])))))
|
|
||||||
{
|
|
||||||
wchar_t *nameend;
|
|
||||||
unsigned int namelen;
|
|
||||||
const struct option_w *p;
|
|
||||||
const struct option_w *pfound = NULL;
|
|
||||||
struct option_list
|
|
||||||
{
|
|
||||||
const struct option_w *p;
|
|
||||||
struct option_list *next;
|
|
||||||
} *ambig_list = NULL;
|
|
||||||
int exact = 0;
|
|
||||||
int indfound = -1;
|
|
||||||
int option_index;
|
|
||||||
for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++);
|
|
||||||
namelen = (unsigned int)(nameend - d->__nextchar);
|
|
||||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
|
||||||
if (!wcsncmp(p->name, d->__nextchar, namelen))
|
|
||||||
{
|
|
||||||
if (namelen == (unsigned int)wcslen(p->name))
|
|
||||||
{
|
|
||||||
pfound = p;
|
|
||||||
indfound = option_index;
|
|
||||||
exact = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (pfound == NULL)
|
|
||||||
{
|
|
||||||
pfound = p;
|
|
||||||
indfound = option_index;
|
|
||||||
}
|
|
||||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
|
||||||
{
|
|
||||||
struct option_list *newp = (struct option_list*)alloca(sizeof(*newp));
|
|
||||||
newp->p = p;
|
|
||||||
newp->next = ambig_list;
|
|
||||||
ambig_list = newp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ambig_list != NULL && !exact)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
struct option_list first;
|
|
||||||
first.p = pfound;
|
|
||||||
first.next = ambig_list;
|
|
||||||
ambig_list = &first;
|
|
||||||
fwprintf(stderr, L"%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
fwprintf (stderr, L" '--%s'", ambig_list->p->name);
|
|
||||||
ambig_list = ambig_list->next;
|
|
||||||
}
|
|
||||||
while (ambig_list != NULL);
|
|
||||||
fputwc (L'\n', stderr);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
d->optind++;
|
|
||||||
d->optopt = 0;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
if (pfound != NULL)
|
|
||||||
{
|
|
||||||
option_index = indfound;
|
|
||||||
d->optind++;
|
|
||||||
if (*nameend)
|
|
||||||
{
|
|
||||||
if (pfound->has_arg)
|
|
||||||
d->optarg = nameend + 1;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
if (argv[d->optind - 1][1] == L'-')
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
d->optopt = pfound->val;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pfound->has_arg == 1)
|
|
||||||
{
|
|
||||||
if (d->optind < argc)
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr,L"%s: option '--%s' requires an argument\n",argv[0], pfound->name);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
d->optopt = pfound->val;
|
|
||||||
return optstring[0] == L':' ? L':' : L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
if (longind != NULL)
|
|
||||||
*longind = option_index;
|
|
||||||
if (pfound->flag)
|
|
||||||
{
|
|
||||||
*(pfound->flag) = pfound->val;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return pfound->val;
|
|
||||||
}
|
|
||||||
if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
if (argv[d->optind][1] == L'-')
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: unrecognized option '--%s'\n",argv[0], d->__nextchar);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d->__nextchar = (wchar_t *)L"";
|
|
||||||
d->optind++;
|
|
||||||
d->optopt = 0;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
wchar_t c = *d->__nextchar++;
|
|
||||||
wchar_t *temp = (wchar_t*)wcschr(optstring, c);
|
|
||||||
if (*d->__nextchar == L'\0')
|
|
||||||
++d->optind;
|
|
||||||
if (temp == NULL || c == L':' || c == L';')
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c);
|
|
||||||
}
|
|
||||||
d->optopt = c;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
if (temp[0] == L'W' && temp[1] == L';')
|
|
||||||
{
|
|
||||||
wchar_t *nameend;
|
|
||||||
const struct option_w *p;
|
|
||||||
const struct option_w *pfound = NULL;
|
|
||||||
int exact = 0;
|
|
||||||
int ambig = 0;
|
|
||||||
int indfound = 0;
|
|
||||||
int option_index;
|
|
||||||
if (longopts == NULL)
|
|
||||||
goto no_longs;
|
|
||||||
if (*d->__nextchar != L'\0')
|
|
||||||
{
|
|
||||||
d->optarg = d->__nextchar;
|
|
||||||
d->optind++;
|
|
||||||
}
|
|
||||||
else if (d->optind == argc)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
|
|
||||||
}
|
|
||||||
d->optopt = c;
|
|
||||||
if (optstring[0] == L':')
|
|
||||||
c = L':';
|
|
||||||
else
|
|
||||||
c = L'?';
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != L'='; nameend++);
|
|
||||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
|
||||||
if (!wcsncmp(p->name, d->__nextchar, nameend - d->__nextchar))
|
|
||||||
{
|
|
||||||
if ((unsigned int) (nameend - d->__nextchar) == wcslen(p->name))
|
|
||||||
{
|
|
||||||
pfound = p;
|
|
||||||
indfound = option_index;
|
|
||||||
exact = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (pfound == NULL)
|
|
||||||
{
|
|
||||||
pfound = p;
|
|
||||||
indfound = option_index;
|
|
||||||
}
|
|
||||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
|
||||||
ambig = 1;
|
|
||||||
}
|
|
||||||
if (ambig && !exact)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '-W %s' is ambiguous\n",argv[0], d->optarg);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
d->optind++;
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
if (pfound != NULL)
|
|
||||||
{
|
|
||||||
option_index = indfound;
|
|
||||||
if (*nameend)
|
|
||||||
{
|
|
||||||
if (pfound->has_arg)
|
|
||||||
d->optarg = nameend + 1;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
return L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pfound->has_arg == 1)
|
|
||||||
{
|
|
||||||
if (d->optind < argc)
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr, L"%s: option '-W %s' requires an argument\n",argv[0], pfound->name);
|
|
||||||
}
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
return optstring[0] == L':' ? L':' : L'?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
d->optarg = NULL;
|
|
||||||
d->__nextchar += wcslen(d->__nextchar);
|
|
||||||
if (longind != NULL)
|
|
||||||
*longind = option_index;
|
|
||||||
if (pfound->flag)
|
|
||||||
{
|
|
||||||
*(pfound->flag) = pfound->val;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return pfound->val;
|
|
||||||
}
|
|
||||||
no_longs:
|
|
||||||
d->__nextchar = NULL;
|
|
||||||
return L'W';
|
|
||||||
}
|
|
||||||
if (temp[1] == L':')
|
|
||||||
{
|
|
||||||
if (temp[2] == L':')
|
|
||||||
{
|
|
||||||
if (*d->__nextchar != L'\0')
|
|
||||||
{
|
|
||||||
d->optarg = d->__nextchar;
|
|
||||||
d->optind++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
d->optarg = NULL;
|
|
||||||
d->__nextchar = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (*d->__nextchar != L'\0')
|
|
||||||
{
|
|
||||||
d->optarg = d->__nextchar;
|
|
||||||
d->optind++;
|
|
||||||
}
|
|
||||||
else if (d->optind == argc)
|
|
||||||
{
|
|
||||||
if (print_errors)
|
|
||||||
{
|
|
||||||
fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
|
|
||||||
}
|
|
||||||
d->optopt = c;
|
|
||||||
if (optstring[0] == L':')
|
|
||||||
c = L':';
|
|
||||||
else
|
|
||||||
c = L'?';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
d->optarg = argv[d->optind++];
|
|
||||||
d->__nextchar = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int _getopt_internal_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct)
|
|
||||||
{
|
|
||||||
int result;
|
|
||||||
getopt_data_w.optind = optind;
|
|
||||||
getopt_data_w.opterr = opterr;
|
|
||||||
result = _getopt_internal_r_w (argc, argv, optstring, longopts,longind, long_only, &getopt_data_w,posixly_correct);
|
|
||||||
optind = getopt_data_w.optind;
|
|
||||||
optarg_w = getopt_data_w.optarg;
|
|
||||||
optopt = getopt_data_w.optopt;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int getopt_w (int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW
|
|
||||||
{
|
|
||||||
return _getopt_internal_w (argc, argv, optstring, (const struct option_w *) 0, (int *) 0, 0, 0);
|
|
||||||
}
|
|
||||||
int getopt_long_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
|
|
||||||
{
|
|
||||||
return _getopt_internal_w (argc, argv, options, long_options, opt_index, 0, 0);
|
|
||||||
}
|
|
||||||
int getopt_long_only_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
|
|
||||||
{
|
|
||||||
return _getopt_internal_w (argc, argv, options, long_options, opt_index, 1, 0);
|
|
||||||
}
|
|
||||||
int _getopt_long_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
|
|
||||||
{
|
|
||||||
return _getopt_internal_r_w (argc, argv, options, long_options, opt_index,0, d, 0);
|
|
||||||
}
|
|
||||||
int _getopt_long_only_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
|
|
||||||
{
|
|
||||||
return _getopt_internal_r_w (argc, argv, options, long_options, opt_index, 1, d, 0);
|
|
||||||
}
|
|
@ -21,6 +21,7 @@ Revisions:
|
|||||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||||
|
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||||
|
|
||||||
**DISCLAIMER**
|
**DISCLAIMER**
|
||||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||||
@ -79,7 +80,6 @@ EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|||||||
#define ARG_OPT 2 /*Argument Optional*/
|
#define ARG_OPT 2 /*Argument Optional*/
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <wchar.h>
|
|
||||||
|
|
||||||
_BEGIN_EXTERN_C
|
_BEGIN_EXTERN_C
|
||||||
|
|
||||||
@ -87,7 +87,6 @@ _BEGIN_EXTERN_C
|
|||||||
extern _GETOPT_API int opterr;
|
extern _GETOPT_API int opterr;
|
||||||
extern _GETOPT_API int optopt;
|
extern _GETOPT_API int optopt;
|
||||||
|
|
||||||
// Ansi
|
|
||||||
struct option_a
|
struct option_a
|
||||||
{
|
{
|
||||||
const char* name;
|
const char* name;
|
||||||
@ -100,19 +99,6 @@ _BEGIN_EXTERN_C
|
|||||||
extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||||
extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||||
|
|
||||||
// Unicode
|
|
||||||
struct option_w
|
|
||||||
{
|
|
||||||
const wchar_t* name;
|
|
||||||
int has_arg;
|
|
||||||
int *flag;
|
|
||||||
int val;
|
|
||||||
};
|
|
||||||
extern _GETOPT_API wchar_t *optarg_w;
|
|
||||||
extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW;
|
|
||||||
extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
|
||||||
extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
|
||||||
|
|
||||||
_END_EXTERN_C
|
_END_EXTERN_C
|
||||||
|
|
||||||
#undef _BEGIN_EXTERN_C
|
#undef _BEGIN_EXTERN_C
|
||||||
@ -120,17 +106,10 @@ _END_EXTERN_C
|
|||||||
#undef _GETOPT_THROW
|
#undef _GETOPT_THROW
|
||||||
#undef _GETOPT_API
|
#undef _GETOPT_API
|
||||||
|
|
||||||
#ifdef _UNICODE
|
|
||||||
#define getopt getopt_w
|
|
||||||
#define getopt_long getopt_long_w
|
|
||||||
#define getopt_long_only getopt_long_only_w
|
|
||||||
#define option option_w
|
|
||||||
#define optarg optarg_w
|
|
||||||
#else
|
|
||||||
#define getopt getopt_a
|
#define getopt getopt_a
|
||||||
#define getopt_long getopt_long_a
|
#define getopt_long getopt_long_a
|
||||||
#define getopt_long_only getopt_long_only_a
|
#define getopt_long_only getopt_long_only_a
|
||||||
#define option option_a
|
#define option option_a
|
||||||
#define optarg optarg_a
|
#define optarg optarg_a
|
||||||
#endif
|
|
||||||
#endif // __GETOPT_H_
|
#endif // __GETOPT_H_
|
||||||
|
@ -23,15 +23,18 @@
|
|||||||
|
|
||||||
#include "rtc/rtc.hpp"
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
#include "parse_cl.h"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <future>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <stdexcept>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "parse_cl.h"
|
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -45,49 +48,50 @@ unordered_map<string, shared_ptr<PeerConnection>> peerConnectionMap;
|
|||||||
unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
|
unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
|
||||||
|
|
||||||
string localId;
|
string localId;
|
||||||
bool echoDataChannelMessages = false;
|
|
||||||
|
|
||||||
shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||||
weak_ptr<WebSocket> wws, string id);
|
weak_ptr<WebSocket> wws, string id);
|
||||||
void confirmOnStdout(bool echoed, string id, string type, size_t length);
|
|
||||||
string randomId(size_t length);
|
string randomId(size_t length);
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) try {
|
||||||
Cmdline *params = nullptr;
|
auto params = std::make_unique<Cmdline>(argc, argv);
|
||||||
try {
|
|
||||||
params = new Cmdline(argc, argv);
|
|
||||||
} catch (const std::range_error&e) {
|
|
||||||
std::cout<< e.what() << '\n';
|
|
||||||
delete params;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtc::InitLogger(LogLevel::Debug);
|
rtc::InitLogger(LogLevel::Info);
|
||||||
|
|
||||||
Configuration config;
|
Configuration config;
|
||||||
string stunServer = "";
|
string stunServer = "";
|
||||||
if (params->stunServer().substr(0,5).compare("stun:") != 0) {
|
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 = "stun:";
|
||||||
}
|
}
|
||||||
stunServer += params->stunServer() + ":" + to_string(params->stunPort());
|
stunServer += params->stunServer() + ":" + to_string(params->stunPort());
|
||||||
cout << "Stun server is " << stunServer << endl;
|
cout << "Stun server is " << stunServer << endl;
|
||||||
config.iceServers.emplace_back(stunServer);
|
config.iceServers.emplace_back(stunServer);
|
||||||
|
}
|
||||||
|
|
||||||
localId = randomId(4);
|
localId = randomId(4);
|
||||||
cout << "The local ID is: " << localId << endl;
|
cout << "The local ID is: " << localId << endl;
|
||||||
|
|
||||||
echoDataChannelMessages = params->echoDataChannelMessages();
|
|
||||||
cout << "Received data channel messages will be "
|
|
||||||
<< (echoDataChannelMessages ? "echoed back to sender" : "printed to stdout") << endl;
|
|
||||||
|
|
||||||
auto ws = make_shared<WebSocket>();
|
auto ws = make_shared<WebSocket>();
|
||||||
|
|
||||||
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
std::promise<void> wsPromise;
|
||||||
|
auto wsFuture = wsPromise.get_future();
|
||||||
|
|
||||||
|
ws->onOpen([&wsPromise]() {
|
||||||
|
cout << "WebSocket connected, signaling ready" << endl;
|
||||||
|
wsPromise.set_value();
|
||||||
|
});
|
||||||
|
|
||||||
|
ws->onError([&wsPromise](string s) {
|
||||||
|
cout << "WebSocket error" << endl;
|
||||||
|
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
|
||||||
|
});
|
||||||
|
|
||||||
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
||||||
|
|
||||||
ws->onError([](const string &error) { cout << "WebSocket failed: " << error << endl; });
|
|
||||||
|
|
||||||
ws->onMessage([&](variant<binary, string> data) {
|
ws->onMessage([&](variant<binary, string> data) {
|
||||||
if (!holds_alternative<string>(data))
|
if (!holds_alternative<string>(data))
|
||||||
return;
|
return;
|
||||||
@ -125,7 +129,7 @@ int main(int argc, char **argv) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
string wsPrefix = "";
|
string wsPrefix = "";
|
||||||
if (params->webSocketServer().substr(0,5).compare("ws://") != 0) {
|
if (params->webSocketServer().substr(0, 5).compare("ws://") != 0) {
|
||||||
wsPrefix = "ws://";
|
wsPrefix = "ws://";
|
||||||
}
|
}
|
||||||
const string url = wsPrefix + params->webSocketServer() + ":" +
|
const string url = wsPrefix + params->webSocketServer() + ":" +
|
||||||
@ -134,11 +138,7 @@ int main(int argc, char **argv) {
|
|||||||
ws->open(url);
|
ws->open(url);
|
||||||
|
|
||||||
cout << "Waiting for signaling to be connected..." << endl;
|
cout << "Waiting for signaling to be connected..." << endl;
|
||||||
while (!ws->isOpen()) {
|
wsFuture.get();
|
||||||
if (ws->isClosed())
|
|
||||||
return 1;
|
|
||||||
this_thread::sleep_for(100ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
string id;
|
string id;
|
||||||
@ -166,33 +166,28 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||||
|
|
||||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
|
dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
|
||||||
static bool firstMessage = true;
|
if (holds_alternative<string>(data))
|
||||||
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
|
cout << "Message from " << id << " received: " << get<string>(data) << endl;
|
||||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
else
|
||||||
firstMessage = false;
|
cout << "Binary message from " << id
|
||||||
} else if (echoDataChannelMessages) {
|
<< " received, size=" << get<binary>(data).size() << endl;
|
||||||
bool echoed = false;
|
|
||||||
if (auto dc = wdc.lock()) {
|
|
||||||
dc->send(message);
|
|
||||||
echoed = true;
|
|
||||||
}
|
|
||||||
confirmOnStdout(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
|
||||||
get<string>(message).length());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dataChannelMap.emplace(id, dc);
|
dataChannelMap.emplace(id, dc);
|
||||||
|
|
||||||
this_thread::sleep_for(1s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cout << "Cleaning up..." << endl;
|
cout << "Cleaning up..." << endl;
|
||||||
|
|
||||||
dataChannelMap.clear();
|
dataChannelMap.clear();
|
||||||
peerConnectionMap.clear();
|
peerConnectionMap.clear();
|
||||||
delete params;
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cout << "Error: " << e.what() << std::endl;
|
||||||
|
dataChannelMap.clear();
|
||||||
|
peerConnectionMap.clear();
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and setup a PeerConnection
|
// Create and setup a PeerConnection
|
||||||
@ -229,20 +224,12 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
|||||||
|
|
||||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||||
|
|
||||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
|
dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
|
||||||
static bool firstMessage = true;
|
if (holds_alternative<string>(data))
|
||||||
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
|
cout << "Message from " << id << " received: " << get<string>(data) << endl;
|
||||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
else
|
||||||
firstMessage = false;
|
cout << "Binary message from " << id
|
||||||
} else if (echoDataChannelMessages) {
|
<< " received, size=" << get<binary>(data).size() << endl;
|
||||||
bool echoed = false;
|
|
||||||
if (auto dc = wdc.lock()) {
|
|
||||||
dc->send(message);
|
|
||||||
echoed = true;
|
|
||||||
}
|
|
||||||
confirmOnStdout(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
|
||||||
get<string>(message).length());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dc->send("Hello from " + localId);
|
dc->send("Hello from " + localId);
|
||||||
@ -254,19 +241,6 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
|||||||
return pc;
|
return pc;
|
||||||
};
|
};
|
||||||
|
|
||||||
void confirmOnStdout(bool echoed, string id, string type, size_t length) {
|
|
||||||
static long count = 0;
|
|
||||||
static long freq = 100;
|
|
||||||
if (!(++count%freq)) {
|
|
||||||
cout << "Received " << count << " pings in total from host " << id << ", most recent of type "
|
|
||||||
<< type << " and " << (echoed ? "" : "un") << "successfully echoed most recent ping of size "
|
|
||||||
<< length << " back to " << id << endl;
|
|
||||||
if (count >= (freq * 10) && freq < 1000000) {
|
|
||||||
freq *= 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to generate a random ID
|
// Helper function to generate a random ID
|
||||||
string randomId(size_t length) {
|
string randomId(size_t length) {
|
||||||
static const string characters(
|
static const string characters(
|
||||||
|
@ -43,31 +43,34 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
|
|||||||
|
|
||||||
static struct option long_options[] =
|
static struct option long_options[] =
|
||||||
{
|
{
|
||||||
|
{"noStun", no_argument, NULL, 'n'},
|
||||||
{"stunServer", required_argument, NULL, 's'},
|
{"stunServer", required_argument, NULL, 's'},
|
||||||
{"stunPort", required_argument, NULL, 't'},
|
{"stunPort", required_argument, NULL, 't'},
|
||||||
{"webSocketServer", required_argument, NULL, 'w'},
|
{"webSocketServer", required_argument, NULL, 'w'},
|
||||||
{"webSocketPort", required_argument, NULL, 'x'},
|
{"webSocketPort", required_argument, NULL, 'x'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"version", no_argument, NULL, 'v'},
|
|
||||||
{NULL, 0, NULL, 0}
|
{NULL, 0, NULL, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
_program_name += argv[0];
|
_program_name += argv[0];
|
||||||
|
|
||||||
/* default values */
|
/* default values */
|
||||||
|
_n = false;
|
||||||
_s = "stun.l.google.com";
|
_s = "stun.l.google.com";
|
||||||
_t = 19302;
|
_t = 19302;
|
||||||
_w = "localhost";
|
_w = "localhost";
|
||||||
_x = 8000;
|
_x = 8000;
|
||||||
_e = false;
|
|
||||||
_h = false;
|
_h = false;
|
||||||
_v = false;
|
|
||||||
|
|
||||||
optind = 0;
|
optind = 0;
|
||||||
while ((c = getopt_long (argc, argv, "s:t:w:x:ehv", long_options, &optind)) != - 1)
|
while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1)
|
||||||
{
|
{
|
||||||
switch (c)
|
switch (c)
|
||||||
{
|
{
|
||||||
|
case 'n':
|
||||||
|
_n = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
_s = optarg;
|
_s = optarg;
|
||||||
break;
|
break;
|
||||||
@ -108,20 +111,11 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'e':
|
|
||||||
_e = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
_h = true;
|
_h = true;
|
||||||
this->usage (EXIT_SUCCESS);
|
this->usage (EXIT_SUCCESS);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'v':
|
|
||||||
_v = true;
|
|
||||||
this->version (EXIT_SUCCESS);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
this->usage (EXIT_FAILURE);
|
this->usage (EXIT_FAILURE);
|
||||||
|
|
||||||
@ -146,10 +140,10 @@ void Cmdline::usage (int status)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::cout << "\
|
std::cout << "\
|
||||||
usage: " << _program_name << " [ -estwxhv ] \n\
|
usage: " << _program_name << " [ -enstwxhv ] \n\
|
||||||
libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\
|
libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\
|
||||||
[ -e ] [ --echo ] (type=FLAG)\n\
|
[ -n ] [ --noStun ] (type=FLAG)\n\
|
||||||
Echo data channel messages back to sender rather than putting to stdout.\n\
|
Do NOT use a stun server (overrides -s and -t).\n\
|
||||||
[ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\
|
[ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\
|
||||||
Stun server URL or IP address.\n\
|
Stun server URL or IP address.\n\
|
||||||
[ -t ] [ --stunPort ] (type=INTEGER, range=0...65535, default=19302)\n\
|
[ -t ] [ --stunPort ] (type=INTEGER, range=0...65535, default=19302)\n\
|
||||||
@ -159,15 +153,8 @@ libdatachannel client implementing WebRTC Data Channels with WebSocket signaling
|
|||||||
[ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\
|
[ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\
|
||||||
Web socket server port.\n\
|
Web socket server port.\n\
|
||||||
[ -h ] [ --help ] (type=FLAG)\n\
|
[ -h ] [ --help ] (type=FLAG)\n\
|
||||||
Display this help and exit.\n\
|
Display this help and exit.\n";
|
||||||
[ -v ] [ --version ] (type=FLAG)\n\
|
|
||||||
Output version information and exit.\n";
|
|
||||||
}
|
}
|
||||||
exit (status);
|
exit (status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cmdline::version (int status)
|
|
||||||
{
|
|
||||||
std::cout << _program_name << " v0.5\n";
|
|
||||||
exit (status);
|
|
||||||
}
|
|
||||||
|
@ -34,13 +34,12 @@ class Cmdline
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
/* parameters */
|
/* parameters */
|
||||||
|
bool _n;
|
||||||
std::string _s;
|
std::string _s;
|
||||||
int _t;
|
int _t;
|
||||||
std::string _w;
|
std::string _w;
|
||||||
int _x;
|
int _x;
|
||||||
bool _e;
|
|
||||||
bool _h;
|
bool _h;
|
||||||
bool _v;
|
|
||||||
|
|
||||||
/* other stuff to keep track of */
|
/* other stuff to keep track of */
|
||||||
std::string _program_name;
|
std::string _program_name;
|
||||||
@ -54,19 +53,15 @@ public:
|
|||||||
/* usage function */
|
/* usage function */
|
||||||
void usage (int status);
|
void usage (int status);
|
||||||
|
|
||||||
/* version function */
|
|
||||||
void version (int status);
|
|
||||||
|
|
||||||
/* return next (non-option) parameter */
|
/* return next (non-option) parameter */
|
||||||
int next_param () { return _optind; }
|
int next_param () { return _optind; }
|
||||||
|
|
||||||
|
bool noStun () const { return _n; }
|
||||||
std::string stunServer () const { return _s; }
|
std::string stunServer () const { return _s; }
|
||||||
int stunPort () const { return _t; }
|
int stunPort () const { return _t; }
|
||||||
std::string webSocketServer () const { return _w; }
|
std::string webSocketServer () const { return _w; }
|
||||||
int webSocketPort () const { return _x; }
|
int webSocketPort () const { return _x; }
|
||||||
bool echoDataChannelMessages () const { return _e; }
|
|
||||||
bool h () const { return _h; }
|
bool h () const { return _h; }
|
||||||
bool v () const { return _v; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -13,3 +13,15 @@ set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
|
|||||||
OUTPUT_NAME answerer)
|
OUTPUT_NAME answerer)
|
||||||
target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)
|
target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET datachannel-copy-paste-capi-offerer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-copy-paste-capi-offerer>
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET datachannel-copy-paste-capi-answerer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-copy-paste-capi-answerer>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
@ -41,16 +41,16 @@ typedef struct {
|
|||||||
bool connected;
|
bool connected;
|
||||||
} Peer;
|
} Peer;
|
||||||
|
|
||||||
static void dataChannelCallback(int dc, void *ptr);
|
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr);
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||||
static void stateChangeCallback(rtcState state, void *ptr);
|
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||||
static void closedCallback(void *ptr);
|
static void RTC_API closedCallback(int id, void *ptr);
|
||||||
static void messageCallback(const char *message, int size, void *ptr);
|
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||||
static void deletePeer(Peer *peer);
|
static void deletePeer(Peer *peer);
|
||||||
|
|
||||||
char* state_print(rtcState state);
|
char *state_print(rtcState state);
|
||||||
char *rtcGatheringState_print(rtcGatheringState state);
|
char *rtcGatheringState_print(rtcGatheringState state);
|
||||||
|
|
||||||
int all_space(const char *str);
|
int all_space(const char *str);
|
||||||
@ -88,7 +88,8 @@ int main(int argc, char **argv) {
|
|||||||
bool exit = false;
|
bool exit = false;
|
||||||
while (!exit) {
|
while (!exit) {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("***************************************************************************************\n");
|
printf("***********************************************************************************"
|
||||||
|
"****\n");
|
||||||
printf("* 0: Exit /"
|
printf("* 0: Exit /"
|
||||||
" 1: Enter remote description /"
|
" 1: Enter remote description /"
|
||||||
" 2: Enter remote candidate /"
|
" 2: Enter remote candidate /"
|
||||||
@ -118,27 +119,25 @@ int main(int argc, char **argv) {
|
|||||||
char *line = NULL;
|
char *line = NULL;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
size_t read = 0;
|
size_t read = 0;
|
||||||
char *sdp = (char*) malloc(sizeof(char));
|
char *sdp = (char *)malloc(sizeof(char));
|
||||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||||
strcat(sdp, line);
|
strcat(sdp, line);
|
||||||
|
|
||||||
}
|
}
|
||||||
printf("%s\n",sdp);
|
printf("%s\n", sdp);
|
||||||
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
||||||
free(sdp);
|
free(sdp);
|
||||||
free(line);
|
free(line);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
// Parse Candidate
|
// Parse Candidate
|
||||||
printf("[Candidate]: ");
|
printf("[Candidate]: ");
|
||||||
char* candidate = NULL;
|
char *candidate = NULL;
|
||||||
size_t candidate_size = 0;
|
size_t candidate_size = 0;
|
||||||
|
|
||||||
if(getline(&candidate, &candidate_size, stdin)) {
|
if (getline(&candidate, &candidate_size, stdin)) {
|
||||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
rtcAddRemoteCandidate(peer->pc, candidate, NULL);
|
||||||
free(candidate);
|
free(candidate);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -150,15 +149,15 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
case 3: {
|
case 3: {
|
||||||
// Send Message
|
// Send Message
|
||||||
if(!peer->connected) {
|
if (!peer->connected) {
|
||||||
printf("** Channel is not Open **");
|
printf("** Channel is not Open **");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("[Message]: ");
|
printf("[Message]: ");
|
||||||
char* message = NULL;
|
char *message = NULL;
|
||||||
size_t message_size = 0;
|
size_t message_size = 0;
|
||||||
|
|
||||||
if(getline(&message, &message_size, stdin)) {
|
if (getline(&message, &message_size, stdin)) {
|
||||||
rtcSendMessage(peer->dc, message, -1);
|
rtcSendMessage(peer->dc, message, -1);
|
||||||
free(message);
|
free(message);
|
||||||
} else {
|
} else {
|
||||||
@ -169,7 +168,7 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
case 4: {
|
case 4: {
|
||||||
// Connection Info
|
// Connection Info
|
||||||
if(!peer->connected) {
|
if (!peer->connected) {
|
||||||
printf("** Channel is not Open **");
|
printf("** Channel is not Open **");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -194,35 +193,32 @@ int main(int argc, char **argv) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
printf("Candidate %s: %s\n", "answerer", cand);
|
printf("Candidate %s: %s\n", "answerer", cand);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->state = state;
|
peer->state = state;
|
||||||
printf("State %s: %s\n", "answerer", state_print(state));
|
printf("State %s: %s\n", "answerer", state_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->gatheringState = state;
|
peer->gatheringState = state;
|
||||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void closedCallback(void *ptr) {
|
static void RTC_API closedCallback(int id, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->connected = false;
|
peer->connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void messageCallback(const char *message, int size, void *ptr) {
|
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||||
if (size < 0) { // negative size indicates a null-terminated string
|
if (size < 0) { // negative size indicates a null-terminated string
|
||||||
printf("Message %s: %s\n", "answerer", message);
|
printf("Message %s: %s\n", "answerer", message);
|
||||||
} else {
|
} else {
|
||||||
@ -230,6 +226,17 @@ static void messageCallback(const char *message, int size, void *ptr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr) {
|
||||||
|
Peer *peer = (Peer *)ptr;
|
||||||
|
peer->dc = dc;
|
||||||
|
peer->connected = true;
|
||||||
|
rtcSetClosedCallback(dc, closedCallback);
|
||||||
|
rtcSetMessageCallback(dc, messageCallback);
|
||||||
|
char buffer[256];
|
||||||
|
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||||
|
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||||
|
}
|
||||||
|
|
||||||
static void deletePeer(Peer *peer) {
|
static void deletePeer(Peer *peer) {
|
||||||
if (peer) {
|
if (peer) {
|
||||||
if (peer->dc)
|
if (peer->dc)
|
||||||
@ -240,17 +247,6 @@ static void deletePeer(Peer *peer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dataChannelCallback(int dc, void *ptr) {
|
|
||||||
Peer *peer = (Peer *)ptr;
|
|
||||||
peer->dc = dc;
|
|
||||||
peer->connected = true;
|
|
||||||
rtcSetClosedCallback(dc, closedCallback);
|
|
||||||
rtcSetMessageCallback(dc, messageCallback);
|
|
||||||
char buffer[256];
|
|
||||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
|
||||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *state_print(rtcState state) {
|
char *state_print(rtcState state) {
|
||||||
char *str = NULL;
|
char *str = NULL;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
|
@ -41,13 +41,13 @@ typedef struct {
|
|||||||
bool connected;
|
bool connected;
|
||||||
} Peer;
|
} Peer;
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||||
static void stateChangeCallback(rtcState state, void *ptr);
|
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||||
static void openCallback(void *ptr);
|
static void RTC_API openCallback(int id, void *ptr);
|
||||||
static void closedCallback(void *ptr);
|
static void RTC_API closedCallback(int id, void *ptr);
|
||||||
static void messageCallback(const char *message, int size, void *ptr);
|
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||||
static void deletePeer(Peer *peer);
|
static void deletePeer(Peer *peer);
|
||||||
|
|
||||||
char *state_print(rtcState state);
|
char *state_print(rtcState state);
|
||||||
@ -55,7 +55,7 @@ char *rtcGatheringState_print(rtcGatheringState state);
|
|||||||
|
|
||||||
int all_space(const char *str);
|
int all_space(const char *str);
|
||||||
|
|
||||||
int main(int argc, char **argv){
|
int main(int argc, char **argv) {
|
||||||
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
||||||
|
|
||||||
// Create peer
|
// Create peer
|
||||||
@ -79,7 +79,7 @@ int main(int argc, char **argv){
|
|||||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||||
|
|
||||||
// Since this is the offere, we will create a datachannel
|
// Since we are the offerer, we will create a datachannel
|
||||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||||
rtcSetOpenCallback(peer->dc, openCallback);
|
rtcSetOpenCallback(peer->dc, openCallback);
|
||||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||||
@ -91,7 +91,8 @@ int main(int argc, char **argv){
|
|||||||
while (!exit) {
|
while (!exit) {
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("***************************************************************************************\n");
|
printf("***********************************************************************************"
|
||||||
|
"****\n");
|
||||||
printf("* 0: Exit /"
|
printf("* 0: Exit /"
|
||||||
" 1: Enter remote description /"
|
" 1: Enter remote description /"
|
||||||
" 2: Enter remote candidate /"
|
" 2: Enter remote candidate /"
|
||||||
@ -119,53 +120,49 @@ int main(int argc, char **argv){
|
|||||||
// Parse Description
|
// Parse Description
|
||||||
printf("[Description]: ");
|
printf("[Description]: ");
|
||||||
|
|
||||||
|
|
||||||
char *line = NULL;
|
char *line = NULL;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
size_t read = 0;
|
size_t read = 0;
|
||||||
char *sdp = (char*) malloc(sizeof(char));
|
char *sdp = (char *)malloc(sizeof(char));
|
||||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||||
strcat(sdp, line);
|
strcat(sdp, line);
|
||||||
|
|
||||||
}
|
}
|
||||||
printf("%s\n",sdp);
|
printf("%s\n", sdp);
|
||||||
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
||||||
free(sdp);
|
free(sdp);
|
||||||
free(line);
|
free(line);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
// Parse Candidate
|
// Parse Candidate
|
||||||
printf("[Candidate]: ");
|
printf("[Candidate]: ");
|
||||||
char* candidate = NULL;
|
char *candidate = NULL;
|
||||||
size_t candidate_size = 0;
|
size_t candidate_size = 0;
|
||||||
if(getline(&candidate, &candidate_size, stdin)) {
|
if (getline(&candidate, &candidate_size, stdin)) {
|
||||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
rtcAddRemoteCandidate(peer->pc, candidate, NULL);
|
||||||
free(candidate);
|
free(candidate);
|
||||||
|
|
||||||
}else {
|
} else {
|
||||||
printf("Error reading line\n");
|
printf("Error reading line\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 3: {
|
case 3: {
|
||||||
// Send Message
|
// Send Message
|
||||||
if(!peer->connected) {
|
if (!peer->connected) {
|
||||||
printf("** Channel is not Open **");
|
printf("** Channel is not Open **");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("[Message]: ");
|
printf("[Message]: ");
|
||||||
char* message = NULL;
|
char *message = NULL;
|
||||||
size_t message_size = 0;
|
size_t message_size = 0;
|
||||||
if(getline(&message, &message_size, stdin)) {
|
if (getline(&message, &message_size, stdin)) {
|
||||||
rtcSendMessage(peer->dc, message, -1);
|
rtcSendMessage(peer->dc, message, -1);
|
||||||
free(message);
|
free(message);
|
||||||
}else {
|
} else {
|
||||||
printf("Error reading line\n");
|
printf("Error reading line\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -174,7 +171,7 @@ int main(int argc, char **argv){
|
|||||||
}
|
}
|
||||||
case 4: {
|
case 4: {
|
||||||
// Connection Info
|
// Connection Info
|
||||||
if(!peer->connected) {
|
if (!peer->connected) {
|
||||||
printf("** Channel is not Open **");
|
printf("** Channel is not Open **");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -199,44 +196,40 @@ int main(int argc, char **argv){
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
printf("Candidate %s: %s\n", "offerer", cand);
|
printf("Candidate %s: %s\n", "offerer", cand);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->state = state;
|
peer->state = state;
|
||||||
printf("State %s: %s\n", "offerer", state_print(state));
|
printf("State %s: %s\n", "offerer", state_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->gatheringState = state;
|
peer->gatheringState = state;
|
||||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void openCallback(void *ptr) {
|
static void RTC_API openCallback(int id, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->connected = true;
|
peer->connected = true;
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||||
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
printf("DataChannel %s: Received with label \"%s\"\n", "offerer", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void closedCallback(void *ptr) {
|
static void RTC_API closedCallback(int id, void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->connected = false;
|
peer->connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void messageCallback(const char *message, int size, void *ptr) {
|
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
if (size < 0) { // negative size indicates a null-terminated string
|
if (size < 0) { // negative size indicates a null-terminated string
|
||||||
printf("Message %s: %s\n", "offerer", message);
|
printf("Message %s: %s\n", "offerer", message);
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,19 +4,23 @@ add_executable(datachannel-copy-paste-offerer offerer.cpp)
|
|||||||
set_target_properties(datachannel-copy-paste-offerer PROPERTIES
|
set_target_properties(datachannel-copy-paste-offerer PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME offerer)
|
OUTPUT_NAME offerer)
|
||||||
if(WIN32)
|
target_link_libraries(datachannel-copy-paste-offerer datachannel)
|
||||||
target_link_libraries(datachannel-copy-paste-offerer datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-copy-paste-offerer datachannel)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(datachannel-copy-paste-answerer answerer.cpp)
|
add_executable(datachannel-copy-paste-answerer answerer.cpp)
|
||||||
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
|
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME answerer)
|
OUTPUT_NAME answerer)
|
||||||
if(WIN32)
|
target_link_libraries(datachannel-copy-paste-answerer datachannel)
|
||||||
target_link_libraries(datachannel-copy-paste-answerer datachannel-static) # DLL exports only the C API
|
|
||||||
else()
|
|
||||||
target_link_libraries(datachannel-copy-paste-answerer datachannel)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET datachannel-copy-paste-offerer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-copy-paste-offerer>
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET datachannel-copy-paste-answerer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-copy-paste-answerer>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
|||||||
cout << "** Channel is not Open ** ";
|
cout << "** Channel is not Open ** ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
CandidateInfo local, remote;
|
Candidate local, remote;
|
||||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
cout << "Local: " << local << endl;
|
||||||
<< local.transportType << endl;
|
cout << "Remote: " << remote << endl;
|
||||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
|
||||||
<< " " << remote.transportType << endl;
|
|
||||||
cout << "Bytes Sent:" << pc->bytesSent()
|
cout << "Bytes Sent:" << pc->bytesSent()
|
||||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||||
if (rtt.has_value())
|
if (rtt.has_value())
|
||||||
|
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
|||||||
cout << "** Channel is not Open ** ";
|
cout << "** Channel is not Open ** ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
CandidateInfo local, remote;
|
Candidate local, remote;
|
||||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
cout << "Local: " << local << endl;
|
||||||
<< local.transportType << endl;
|
cout << "Remote: " << remote << endl;
|
||||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
|
||||||
<< " " << remote.transportType << endl;
|
|
||||||
cout << "Bytes Sent:" << pc->bytesSent()
|
cout << "Bytes Sent:" << pc->bytesSent()
|
||||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||||
if (rtt.has_value())
|
if (rtt.has_value())
|
||||||
|
@ -4,11 +4,12 @@ add_executable(datachannel-media main.cpp)
|
|||||||
set_target_properties(datachannel-media PROPERTIES
|
set_target_properties(datachannel-media PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME media)
|
OUTPUT_NAME media)
|
||||||
|
target_link_libraries(datachannel-media datachannel nlohmann_json)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(datachannel-media datachannel-static) # DLL exports only the C API
|
add_custom_command(TARGET datachannel-media POST_BUILD
|
||||||
else()
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
target_link_libraries(datachannel-media datachannel)
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-media>
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(datachannel-media datachannel nlohmann_json)
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* libdatachannel client example
|
* libdatachannel client example
|
||||||
* Copyright (c) 2020 Staz M
|
* Copyright (c) 2020 Staz Modrzynski
|
||||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@ -67,7 +67,7 @@ int main() {
|
|||||||
|
|
||||||
auto track = pc->addTrack(media);
|
auto track = pc->addTrack(media);
|
||||||
|
|
||||||
auto session = std::make_shared<rtc::RtcpSession>();
|
auto session = std::make_shared<rtc::RtcpReceivingSession>();
|
||||||
track->setRtcpHandler(session);
|
track->setRtcpHandler(session);
|
||||||
|
|
||||||
track->onMessage(
|
track->onMessage(
|
||||||
|
15
examples/sfu-media/CMakeLists.txt
Normal file
15
examples/sfu-media/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
|
||||||
|
add_executable(datachannel-sfu-media main.cpp)
|
||||||
|
set_target_properties(datachannel-sfu-media PROPERTIES
|
||||||
|
CXX_STANDARD 17
|
||||||
|
OUTPUT_NAME sfu-media)
|
||||||
|
target_link_libraries(datachannel-sfu-media datachannel nlohmann_json)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET datachannel-sfu-media POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:datachannel-sfu-media>
|
||||||
|
)
|
||||||
|
endif()
|
134
examples/sfu-media/main.cpp
Normal file
134
examples/sfu-media/main.cpp
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel client example
|
||||||
|
* Copyright (c) 2020 Staz Modrzynski
|
||||||
|
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||||
|
|
||||||
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
struct Receiver {
|
||||||
|
std::shared_ptr<rtc::PeerConnection> conn;
|
||||||
|
std::shared_ptr<rtc::Track> track;
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
std::vector<std::shared_ptr<Receiver>> receivers;
|
||||||
|
|
||||||
|
try {
|
||||||
|
rtc::InitLogger(rtc::LogLevel::Info);
|
||||||
|
|
||||||
|
auto pc = std::make_shared<rtc::PeerConnection>();
|
||||||
|
pc->onStateChange(
|
||||||
|
[](rtc::PeerConnection::State state) { std::cout << "State: " << state << std::endl; });
|
||||||
|
pc->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
|
||||||
|
std::cout << "Gathering State: " << state << std::endl;
|
||||||
|
if (state == rtc::PeerConnection::GatheringState::Complete) {
|
||||||
|
auto description = pc->localDescription();
|
||||||
|
json message = {{"type", description->typeString()},
|
||||||
|
{"sdp", std::string(description.value())}};
|
||||||
|
std::cout << "Please copy/paste this offer to the SENDER: " << message << std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rtc::Description::Video media("video", rtc::Description::Direction::RecvOnly);
|
||||||
|
media.addH264Codec(96);
|
||||||
|
media.setBitrate(
|
||||||
|
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
||||||
|
|
||||||
|
auto track = pc->addTrack(media);
|
||||||
|
pc->setLocalDescription();
|
||||||
|
|
||||||
|
auto session = std::make_shared<rtc::RtcpReceivingSession>();
|
||||||
|
track->setRtcpHandler(session);
|
||||||
|
|
||||||
|
const rtc::SSRC targetSSRC = 4;
|
||||||
|
|
||||||
|
track->onMessage(
|
||||||
|
[&receivers, targetSSRC](rtc::binary message) {
|
||||||
|
// This is an RTP packet
|
||||||
|
auto rtp = (rtc::RTP *)message.data();
|
||||||
|
rtp->setSsrc(targetSSRC);
|
||||||
|
for (auto pc : receivers) {
|
||||||
|
if (pc->track != nullptr && pc->track->isOpen()) {
|
||||||
|
pc->track->send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
// Set the SENDERS Answer
|
||||||
|
{
|
||||||
|
std::cout << "Please copy/paste the answer provided by the SENDER: " << std::endl;
|
||||||
|
std::string sdp;
|
||||||
|
std::getline(std::cin, sdp);
|
||||||
|
std::cout << "Got answer" << sdp << std::endl;
|
||||||
|
json j = json::parse(sdp);
|
||||||
|
rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
|
||||||
|
pc->setRemoteDescription(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each receiver
|
||||||
|
while (true) {
|
||||||
|
auto pc = std::make_shared<Receiver>();
|
||||||
|
pc->conn = std::make_shared<rtc::PeerConnection>();
|
||||||
|
pc->conn->onStateChange([](rtc::PeerConnection::State state) {
|
||||||
|
std::cout << "State: " << state << std::endl;
|
||||||
|
});
|
||||||
|
pc->conn->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
|
||||||
|
std::cout << "Gathering State: " << state << std::endl;
|
||||||
|
if (state == rtc::PeerConnection::GatheringState::Complete) {
|
||||||
|
auto description = pc->conn->localDescription();
|
||||||
|
json message = {{"type", description->typeString()},
|
||||||
|
{"sdp", std::string(description.value())}};
|
||||||
|
std::cout << "Please copy/paste this offer to the RECEIVER: " << message
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rtc::Description::Video media("video", rtc::Description::Direction::SendOnly);
|
||||||
|
media.addH264Codec(96);
|
||||||
|
media.setBitrate(
|
||||||
|
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
||||||
|
|
||||||
|
media.addSSRC(targetSSRC, "video-send");
|
||||||
|
|
||||||
|
pc->track = pc->conn->addTrack(media);
|
||||||
|
pc->conn->setLocalDescription();
|
||||||
|
|
||||||
|
pc->track->onMessage([](rtc::binary var) {}, nullptr);
|
||||||
|
|
||||||
|
std::cout << "Please copy/paste the answer provided by the RECEIVER: " << std::endl;
|
||||||
|
std::string sdp;
|
||||||
|
std::getline(std::cin, sdp);
|
||||||
|
std::cout << "Got answer" << sdp << std::endl;
|
||||||
|
json j = json::parse(sdp);
|
||||||
|
rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
|
||||||
|
pc->conn->setRemoteDescription(answer);
|
||||||
|
|
||||||
|
receivers.push_back(pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cerr << "Error: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
87
examples/sfu-media/main.html
Normal file
87
examples/sfu-media/main.html
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>libdatachannel media example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div style="display:inline-block; width:40%;">
|
||||||
|
<h1>SENDER</h1>
|
||||||
|
<p id="send-help">Please enter the offer provided to you by the application: </p>
|
||||||
|
<textarea style="width:100%;" id=send-text rows="50"></textarea>
|
||||||
|
<button id=send-btn>Submit</button>
|
||||||
|
</div>
|
||||||
|
<div style="display:inline-block; width:40%;">
|
||||||
|
<h1>RECEIVER</h1>
|
||||||
|
<p id="recv-help">Please enter the offer provided to you by the application: </p>
|
||||||
|
<textarea id=recv-text style="width:100%;" rows="50"></textarea>
|
||||||
|
<button id=recv-btn>Submit</button>
|
||||||
|
</div>
|
||||||
|
<div id="videos">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.querySelector('#send-btn').addEventListener('click', async () => {
|
||||||
|
let offer = JSON.parse(document.querySelector('#send-text').value);
|
||||||
|
rtc = new RTCPeerConnection({
|
||||||
|
// Recommended for libdatachannel
|
||||||
|
bundlePolicy: "max-bundle",
|
||||||
|
});
|
||||||
|
|
||||||
|
rtc.onicegatheringstatechange = (state) => {
|
||||||
|
if (rtc.iceGatheringState === 'complete') {
|
||||||
|
// We only want to provide an answer once all of our candidates have been added to the SDP.
|
||||||
|
let answer = rtc.localDescription;
|
||||||
|
document.querySelector('#send-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
|
||||||
|
document.querySelector('#send-help').value = 'Please paste the answer in the application.';
|
||||||
|
alert('Please paste the answer in the application.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await rtc.setRemoteDescription(offer);
|
||||||
|
|
||||||
|
let media = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: {
|
||||||
|
width: 1280,
|
||||||
|
height: 720
|
||||||
|
}
|
||||||
|
});
|
||||||
|
media.getTracks().forEach(track => rtc.addTrack(track, media));
|
||||||
|
let answer = await rtc.createAnswer();
|
||||||
|
await rtc.setLocalDescription(answer);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#recv-btn').addEventListener('click', async () => {
|
||||||
|
let offer = JSON.parse(document.querySelector('#recv-text').value);
|
||||||
|
rtc = new RTCPeerConnection({
|
||||||
|
// Recommended for libdatachannel
|
||||||
|
bundlePolicy: "max-bundle",
|
||||||
|
});
|
||||||
|
|
||||||
|
rtc.onicegatheringstatechange = (state) => {
|
||||||
|
if (rtc.iceGatheringState === 'complete') {
|
||||||
|
// We only want to provide an answer once all of our candidates have been added to the SDP.
|
||||||
|
let answer = rtc.localDescription;
|
||||||
|
document.querySelector('#recv-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
|
||||||
|
document.querySelector('#recv-help').value = 'Please paste the answer in the application.';
|
||||||
|
alert('Please paste the answer in the application.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let trackCount = 0;
|
||||||
|
rtc.ontrack = (ev) => {
|
||||||
|
let thisID = trackCount++;
|
||||||
|
|
||||||
|
document.querySelector("#videos").innerHTML += "<video width=100% height=100% id='video-" + thisID + "'></video>";
|
||||||
|
let tracks = [];
|
||||||
|
rtc.getReceivers().forEach(recv => tracks.push(recv.track));
|
||||||
|
document.querySelector("#video-" + thisID).srcObject = new MediaStream(tracks);
|
||||||
|
document.querySelector("#video-" + thisID).play();
|
||||||
|
};
|
||||||
|
await rtc.setRemoteDescription(offer);
|
||||||
|
let answer = await rtc.createAnswer();
|
||||||
|
await rtc.setLocalDescription(answer);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
75
examples/streamer/ArgParser.cpp
Normal file
75
examples/streamer/ArgParser.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ArgParser.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
ArgParser::ArgParser(std::vector<std::pair<std::string, std::string>> options, std::vector<std::pair<std::string, std::string>> flags) {
|
||||||
|
for(auto option: options) {
|
||||||
|
this->options.insert(option.first);
|
||||||
|
this->options.insert(option.second);
|
||||||
|
shortToLongMap.emplace(option.first, option.second);
|
||||||
|
shortToLongMap.emplace(option.second, option.second);
|
||||||
|
}
|
||||||
|
for(auto flag: flags) {
|
||||||
|
this->flags.insert(flag.first);
|
||||||
|
this->flags.insert(flag.second);
|
||||||
|
shortToLongMap.emplace(flag.first, flag.second);
|
||||||
|
shortToLongMap.emplace(flag.second, flag.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> ArgParser::toKey(std::string prefixedKey) {
|
||||||
|
if (prefixedKey.find("--") == 0) {
|
||||||
|
return prefixedKey.substr(2, prefixedKey.length());
|
||||||
|
} else if (prefixedKey.find("-") == 0) {
|
||||||
|
return prefixedKey.substr(1, prefixedKey.length());
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArgParser::parse(int argc, char **argv, std::function<bool (std::string, std::string)> onOption, std::function<bool (std::string)> onFlag) {
|
||||||
|
std::optional<std::string> currentOption = std::nullopt;
|
||||||
|
for(int i = 1; i < argc; i++) {
|
||||||
|
std::string current = argv[i];
|
||||||
|
auto optKey = toKey(current);
|
||||||
|
if (!currentOption.has_value() && optKey.has_value() && flags.find(optKey.value()) != flags.end()) {
|
||||||
|
auto check = onFlag(shortToLongMap.at(optKey.value()));
|
||||||
|
if (!check) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!currentOption.has_value() && optKey.has_value() && options.find(optKey.value()) != options.end()) {
|
||||||
|
currentOption = optKey.value();
|
||||||
|
} else if (currentOption.has_value()) {
|
||||||
|
auto check = onOption(shortToLongMap.at(currentOption.value()), current);
|
||||||
|
if (!check) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOption = std::nullopt;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Unrecognized option " << current << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentOption.has_value()) {
|
||||||
|
std::cerr << "Missing value for " << currentOption.value() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
41
examples/streamer/ArgParser.hpp
Normal file
41
examples/streamer/ArgParser.hpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ArgParser_hpp
|
||||||
|
#define ArgParser_hpp
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
struct ArgParser {
|
||||||
|
private:
|
||||||
|
std::set<std::string> options{};
|
||||||
|
std::set<std::string> flags{};
|
||||||
|
std::unordered_map<std::string, std::string> shortToLongMap{};
|
||||||
|
public:
|
||||||
|
ArgParser(std::vector<std::pair<std::string, std::string>> options, std::vector<std::pair<std::string, std::string>> flags);
|
||||||
|
std::optional<std::string> toKey(std::string prefixedKey);
|
||||||
|
bool parse(int argc, char **argv, std::function<bool (std::string, std::string)> onOption, std::function<bool (std::string)> onFlag);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* ArgParser_hpp */
|
40
examples/streamer/CMakeLists.txt
Normal file
40
examples/streamer/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
if(POLICY CMP0079)
|
||||||
|
cmake_policy(SET CMP0079 NEW)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(streamer
|
||||||
|
main.cpp
|
||||||
|
dispatchqueue.cpp
|
||||||
|
dispatchqueue.hpp
|
||||||
|
h264fileparser.cpp
|
||||||
|
h264fileparser.hpp
|
||||||
|
helpers.cpp
|
||||||
|
helpers.hpp
|
||||||
|
opusfileparser.cpp
|
||||||
|
opusfileparser.hpp
|
||||||
|
fileparser.cpp
|
||||||
|
fileparser.hpp
|
||||||
|
stream.cpp
|
||||||
|
stream.hpp
|
||||||
|
ArgParser.cpp
|
||||||
|
ArgParser.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_compile_definitions(streamer PUBLIC STATIC_GETOPT)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_target_properties(streamer PROPERTIES
|
||||||
|
CXX_STANDARD 17
|
||||||
|
OUTPUT_NAME streamer)
|
||||||
|
|
||||||
|
target_link_libraries(streamer datachannel nlohmann_json)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(TARGET streamer POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||||
|
$<TARGET_FILE_DIR:streamer>
|
||||||
|
)
|
||||||
|
endif()
|
32
examples/streamer/README.md
Normal file
32
examples/streamer/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Streaming H264 and opus
|
||||||
|
|
||||||
|
This example streams H264 and opus<sup id="a1">[1](#f1)</sup> samples to the connected browser client.
|
||||||
|
|
||||||
|
## Starting signaling server
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ python3 ../signaling-server-python/signaling-server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Starting php
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ php -S 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can open demo at [127.0.0.1:8080](127.0.0.1:8080).
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
- `-a` Directory with OPUS samples (default: *../../../../examples/streamer/samples/opus/*).
|
||||||
|
- `-b` Directory with H264 samples (default: *../../../../examples/streamer/samples/h264/*).
|
||||||
|
- `-d` Signaling server IP address (default: 127.0.0.1).
|
||||||
|
- `-p` Signaling server port (default: 8000).
|
||||||
|
- `-v` Enable debug logs.
|
||||||
|
- `-h` Print this help and exit.
|
||||||
|
|
||||||
|
## Generating H264 and Opus samples
|
||||||
|
|
||||||
|
You can generate H264 and Opus sample with *samples/generate_h264.py* and *samples/generate_opus.py* respectively. This require ffmpeg, python3 and kaitaistruct library to be installed. Use `-h`/`--help` to learn more about arguments.
|
||||||
|
|
||||||
|
<b id="f1">1</b> Opus samples are generated from music downloaded at [bensound](https://www.bensound.com). [↩](#a1)
|
207
examples/streamer/client.js
Normal file
207
examples/streamer/client.js
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/** @type {RTCPeerConnection} */
|
||||||
|
let rtc;
|
||||||
|
const iceConnectionLog = document.getElementById('ice-connection-state'),
|
||||||
|
iceGatheringLog = document.getElementById('ice-gathering-state'),
|
||||||
|
signalingLog = document.getElementById('signaling-state'),
|
||||||
|
dataChannelLog = document.getElementById('data-channel');
|
||||||
|
|
||||||
|
function randomString(len) {
|
||||||
|
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let randomString = '';
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const randomPoz = Math.floor(Math.random() * charSet.length);
|
||||||
|
randomString += charSet.substring(randomPoz, randomPoz + 1);
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const receiveID = randomString(10);
|
||||||
|
const websocket = new WebSocket('ws://127.0.0.1:8000/' + receiveID);
|
||||||
|
websocket.onopen = function () {
|
||||||
|
document.getElementById('start').disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// data channel
|
||||||
|
let dc = null, dcTimeout = null;
|
||||||
|
|
||||||
|
function createPeerConnection() {
|
||||||
|
const config = {
|
||||||
|
sdpSemantics: 'unified-plan',
|
||||||
|
bundlePolicy: "max-bundle",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.getElementById('use-stun').checked) {
|
||||||
|
config.iceServers = [{urls: ['stun:stun.l.google.com:19302']}];
|
||||||
|
}
|
||||||
|
|
||||||
|
let pc = new RTCPeerConnection(config);
|
||||||
|
|
||||||
|
// register some listeners to help debugging
|
||||||
|
pc.addEventListener('icegatheringstatechange', function () {
|
||||||
|
iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState;
|
||||||
|
}, false);
|
||||||
|
iceGatheringLog.textContent = pc.iceGatheringState;
|
||||||
|
|
||||||
|
pc.addEventListener('iceconnectionstatechange', function () {
|
||||||
|
iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState;
|
||||||
|
}, false);
|
||||||
|
iceConnectionLog.textContent = pc.iceConnectionState;
|
||||||
|
|
||||||
|
pc.addEventListener('signalingstatechange', function () {
|
||||||
|
signalingLog.textContent += ' -> ' + pc.signalingState;
|
||||||
|
}, false);
|
||||||
|
signalingLog.textContent = pc.signalingState;
|
||||||
|
|
||||||
|
// connect audio / video
|
||||||
|
pc.addEventListener('track', function (evt) {
|
||||||
|
document.getElementById('media').style.display = 'block';
|
||||||
|
const videoTag = document.getElementById('video');
|
||||||
|
videoTag.srcObject = evt.streams[0];
|
||||||
|
videoTag.play();
|
||||||
|
});
|
||||||
|
|
||||||
|
let time_start = null;
|
||||||
|
|
||||||
|
function current_stamp() {
|
||||||
|
if (time_start === null) {
|
||||||
|
time_start = new Date().getTime();
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return new Date().getTime() - time_start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.ondatachannel = function (event) {
|
||||||
|
dc = event.channel;
|
||||||
|
dc.onopen = function () {
|
||||||
|
dataChannelLog.textContent += '- open\n';
|
||||||
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||||
|
};
|
||||||
|
dc.onmessage = function (evt) {
|
||||||
|
|
||||||
|
dataChannelLog.textContent += '< ' + evt.data + '\n';
|
||||||
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||||
|
|
||||||
|
dcTimeout = setTimeout(function () {
|
||||||
|
if (dc == null && dcTimeout != null) {
|
||||||
|
dcTimeout = null;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const message = 'Pong ' + current_stamp();
|
||||||
|
dataChannelLog.textContent += '> ' + message + '\n';
|
||||||
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||||
|
dc.send(message);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
dc.onclose = function () {
|
||||||
|
clearTimeout(dcTimeout);
|
||||||
|
dcTimeout = null;
|
||||||
|
dataChannelLog.textContent += '- close\n';
|
||||||
|
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendAnswer(pc) {
|
||||||
|
return pc.createAnswer()
|
||||||
|
.then((answer) => rtc.setLocalDescription(answer))
|
||||||
|
.then(function () {
|
||||||
|
// wait for ICE gathering to complete
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
if (pc.iceGatheringState === 'complete') {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
function checkState() {
|
||||||
|
if (pc.iceGatheringState === 'complete') {
|
||||||
|
pc.removeEventListener('icegatheringstatechange', checkState);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.addEventListener('icegatheringstatechange', checkState);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
|
const answer = pc.localDescription;
|
||||||
|
|
||||||
|
document.getElementById('answer-sdp').textContent = answer.sdp;
|
||||||
|
|
||||||
|
return websocket.send(JSON.stringify(
|
||||||
|
{
|
||||||
|
id: "server",
|
||||||
|
type: answer.type,
|
||||||
|
sdp: answer.sdp,
|
||||||
|
}));
|
||||||
|
}).catch(function (e) {
|
||||||
|
alert(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOffer(offer) {
|
||||||
|
rtc = createPeerConnection();
|
||||||
|
return rtc.setRemoteDescription(offer)
|
||||||
|
.then(() => sendAnswer(rtc));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendStreamRequest() {
|
||||||
|
websocket.send(JSON.stringify(
|
||||||
|
{
|
||||||
|
id: "server",
|
||||||
|
type: "streamRequest",
|
||||||
|
receiver: receiveID,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
document.getElementById('start').style.display = 'none';
|
||||||
|
document.getElementById('stop').style.display = 'inline-block';
|
||||||
|
document.getElementById('media').style.display = 'block';
|
||||||
|
sendStreamRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
document.getElementById('stop').style.display = 'none';
|
||||||
|
document.getElementById('media').style.display = 'none';
|
||||||
|
document.getElementById('start').style.display = 'inline-block';
|
||||||
|
|
||||||
|
// close data channel
|
||||||
|
if (dc) {
|
||||||
|
dc.close();
|
||||||
|
dc = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close transceivers
|
||||||
|
if (rtc.getTransceivers) {
|
||||||
|
rtc.getTransceivers().forEach(function (transceiver) {
|
||||||
|
if (transceiver.stop) {
|
||||||
|
transceiver.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// close local audio / video
|
||||||
|
rtc.getSenders().forEach(function (sender) {
|
||||||
|
const track = sender.track;
|
||||||
|
if (track !== null) {
|
||||||
|
sender.track.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// close peer connection
|
||||||
|
setTimeout(function () {
|
||||||
|
rtc.close();
|
||||||
|
rtc = null;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
websocket.onmessage = async function (evt) {
|
||||||
|
const received_msg = evt.data;
|
||||||
|
const object = JSON.parse(received_msg);
|
||||||
|
if (object.type == "offer") {
|
||||||
|
document.getElementById('offer-sdp').textContent = object.sdp;
|
||||||
|
await handleOffer(object)
|
||||||
|
}
|
||||||
|
}
|
94
examples/streamer/dispatchqueue.cpp
Normal file
94
examples/streamer/dispatchqueue.cpp
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "dispatchqueue.hpp"
|
||||||
|
|
||||||
|
DispatchQueue::DispatchQueue(std::string name, size_t threadCount) :
|
||||||
|
name{std::move(name)}, threads(threadCount) {
|
||||||
|
for(size_t i = 0; i < threads.size(); i++)
|
||||||
|
{
|
||||||
|
threads[i] = std::thread(&DispatchQueue::dispatchThreadHandler, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue::~DispatchQueue() {
|
||||||
|
// Signal to dispatch threads that it's time to wrap up
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
quit = true;
|
||||||
|
lock.unlock();
|
||||||
|
condition.notify_all();
|
||||||
|
|
||||||
|
// Wait for threads to finish before we exit
|
||||||
|
for(size_t i = 0; i < threads.size(); i++)
|
||||||
|
{
|
||||||
|
if(threads[i].joinable())
|
||||||
|
{
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DispatchQueue::removePending() {
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
queue = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void DispatchQueue::dispatch(const fp_t& op) {
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
queue.push(op);
|
||||||
|
|
||||||
|
// Manual unlocking is done before notifying, to avoid waking up
|
||||||
|
// the waiting thread only to block again (see notify_one for details)
|
||||||
|
lock.unlock();
|
||||||
|
condition.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DispatchQueue::dispatch(fp_t&& op) {
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
queue.push(std::move(op));
|
||||||
|
|
||||||
|
// Manual unlocking is done before notifying, to avoid waking up
|
||||||
|
// the waiting thread only to block again (see notify_one for details)
|
||||||
|
lock.unlock();
|
||||||
|
condition.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DispatchQueue::dispatchThreadHandler(void) {
|
||||||
|
std::unique_lock<std::mutex> lock(lockMutex);
|
||||||
|
do {
|
||||||
|
//Wait until we have data or a quit signal
|
||||||
|
condition.wait(lock, [this]{
|
||||||
|
return (queue.size() || quit);
|
||||||
|
});
|
||||||
|
|
||||||
|
//after wait, we own the lock
|
||||||
|
if(!quit && queue.size())
|
||||||
|
{
|
||||||
|
auto op = std::move(queue.front());
|
||||||
|
queue.pop();
|
||||||
|
|
||||||
|
//unlock now that we're done messing with the queue
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
op();
|
||||||
|
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
} while (!quit);
|
||||||
|
}
|
59
examples/streamer/dispatchqueue.hpp
Normal file
59
examples/streamer/dispatchqueue.hpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef dispatchqueue_hpp
|
||||||
|
#define dispatchqueue_hpp
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <queue>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class DispatchQueue {
|
||||||
|
typedef std::function<void(void)> fp_t;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DispatchQueue(std::string name, size_t threadCount = 1);
|
||||||
|
~DispatchQueue();
|
||||||
|
|
||||||
|
// dispatch and copy
|
||||||
|
void dispatch(const fp_t& op);
|
||||||
|
// dispatch and move
|
||||||
|
void dispatch(fp_t&& op);
|
||||||
|
|
||||||
|
void removePending();
|
||||||
|
|
||||||
|
// Deleted operations
|
||||||
|
DispatchQueue(const DispatchQueue& rhs) = delete;
|
||||||
|
DispatchQueue& operator=(const DispatchQueue& rhs) = delete;
|
||||||
|
DispatchQueue(DispatchQueue&& rhs) = delete;
|
||||||
|
DispatchQueue& operator=(DispatchQueue&& rhs) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name;
|
||||||
|
std::mutex lockMutex;
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
std::queue<fp_t> queue;
|
||||||
|
std::condition_variable condition;
|
||||||
|
bool quit = false;
|
||||||
|
|
||||||
|
void dispatchThreadHandler(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* dispatchqueue_hpp */
|
59
examples/streamer/fileparser.cpp
Normal file
59
examples/streamer/fileparser.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fileparser.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
FileParser::FileParser(string directory, string extension, uint32_t samplesPerSecond, bool loop): sampleDuration_us(1000 * 1000 / samplesPerSecond), StreamSource() {
|
||||||
|
this->directory = directory;
|
||||||
|
this->extension = extension;
|
||||||
|
this->loop = loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileParser::start() {
|
||||||
|
sampleTime_us = -sampleDuration_us;
|
||||||
|
loadNextSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileParser::stop() {
|
||||||
|
StreamSource::stop();
|
||||||
|
counter = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileParser::loadNextSample() {
|
||||||
|
string frame_id = to_string(++counter);
|
||||||
|
|
||||||
|
string url = directory + "/sample-" + frame_id + extension;
|
||||||
|
ifstream source(url, ios_base::binary);
|
||||||
|
if (!source) {
|
||||||
|
if (loop && counter > 0) {
|
||||||
|
loopTimestampOffset = sampleTime_us;
|
||||||
|
counter = -1;
|
||||||
|
loadNextSample();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sample = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<uint8_t> fileContents((std::istreambuf_iterator<char>(source)), std::istreambuf_iterator<char>());
|
||||||
|
sample = *reinterpret_cast<vector<byte> *>(&fileContents);
|
||||||
|
sampleTime_us += sampleDuration_us;
|
||||||
|
}
|
40
examples/streamer/fileparser.hpp
Normal file
40
examples/streamer/fileparser.hpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef fileparser_hpp
|
||||||
|
#define fileparser_hpp
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "stream.hpp"
|
||||||
|
|
||||||
|
class FileParser: public StreamSource {
|
||||||
|
std::string directory;
|
||||||
|
std::string extension;
|
||||||
|
uint32_t counter = -1;
|
||||||
|
bool loop;
|
||||||
|
uint64_t loopTimestampOffset = 0;
|
||||||
|
public:
|
||||||
|
const uint64_t sampleDuration_us;
|
||||||
|
virtual void start();
|
||||||
|
virtual void stop();
|
||||||
|
FileParser(std::string directory, std::string extension, uint32_t samplesPerSecond, bool loop);
|
||||||
|
virtual void loadNextSample();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* fileparser_hpp */
|
70
examples/streamer/h264fileparser.cpp
Normal file
70
examples/streamer/h264fileparser.cpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "h264fileparser.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
H264FileParser::H264FileParser(string directory, uint32_t fps, bool loop): FileParser(directory, ".h264", fps, loop) { }
|
||||||
|
|
||||||
|
void H264FileParser::loadNextSample() {
|
||||||
|
FileParser::loadNextSample();
|
||||||
|
|
||||||
|
unsigned long long i = 0;
|
||||||
|
while (i < sample.size()) {
|
||||||
|
assert(i + 4 < sample.size());
|
||||||
|
auto lengthPtr = (uint32_t *) (sample.data() + i);
|
||||||
|
uint32_t length = ntohl(*lengthPtr);
|
||||||
|
auto naluStartIndex = i + 4;
|
||||||
|
auto naluEndIndex = naluStartIndex + length;
|
||||||
|
assert(naluEndIndex <= sample.size());
|
||||||
|
auto header = reinterpret_cast<rtc::NalUnitHeader *>(sample.data() + naluStartIndex);
|
||||||
|
auto type = header->unitType();
|
||||||
|
switch (type) {
|
||||||
|
case 7:
|
||||||
|
previousUnitType7 = {sample.begin() + i, sample.begin() + naluEndIndex};
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
previousUnitType8 = {sample.begin() + i, sample.begin() + naluEndIndex};;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
previousUnitType5 = {sample.begin() + i, sample.begin() + naluEndIndex};;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i = naluEndIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<byte> H264FileParser::initialNALUS() {
|
||||||
|
vector<byte> units{};
|
||||||
|
if (previousUnitType7.has_value()) {
|
||||||
|
auto nalu = previousUnitType7.value();
|
||||||
|
units.insert(units.end(), nalu.begin(), nalu.end());
|
||||||
|
}
|
||||||
|
if (previousUnitType8.has_value()) {
|
||||||
|
auto nalu = previousUnitType8.value();
|
||||||
|
units.insert(units.end(), nalu.begin(), nalu.end());
|
||||||
|
}
|
||||||
|
if (previousUnitType5.has_value()) {
|
||||||
|
auto nalu = previousUnitType5.value();
|
||||||
|
units.insert(units.end(), nalu.begin(), nalu.end());
|
||||||
|
}
|
||||||
|
return units;
|
||||||
|
}
|
36
examples/streamer/h264fileparser.hpp
Normal file
36
examples/streamer/h264fileparser.hpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef h264fileparser_hpp
|
||||||
|
#define h264fileparser_hpp
|
||||||
|
|
||||||
|
#include "fileparser.hpp"
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
class H264FileParser: public FileParser {
|
||||||
|
std::optional<std::vector<std::byte>> previousUnitType5 = std::nullopt;
|
||||||
|
std::optional<std::vector<std::byte>> previousUnitType7 = std::nullopt;
|
||||||
|
std::optional<std::vector<std::byte>> previousUnitType8 = std::nullopt;
|
||||||
|
|
||||||
|
public:
|
||||||
|
H264FileParser(std::string directory, uint32_t fps, bool loop);
|
||||||
|
void loadNextSample() override;
|
||||||
|
std::vector<std::byte> initialNALUS();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* h264fileparser_hpp */
|
87
examples/streamer/helpers.cpp
Normal file
87
examples/streamer/helpers.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
// taken from https://stackoverflow.com/questions/10905892/equivalent-of-gettimeday-for-windows
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
struct timezone {
|
||||||
|
int tz_minuteswest;
|
||||||
|
int tz_dsttime;
|
||||||
|
};
|
||||||
|
int gettimeofday(struct timeval *tv, struct timezone *tz)
|
||||||
|
{
|
||||||
|
if (tv) {
|
||||||
|
FILETIME filetime; /* 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 00:00 UTC */
|
||||||
|
ULARGE_INTEGER x;
|
||||||
|
ULONGLONG usec;
|
||||||
|
static const ULONGLONG epoch_offset_us = 11644473600000000ULL; /* microseconds betweeen Jan 1,1601 and Jan 1,1970 */
|
||||||
|
|
||||||
|
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
|
||||||
|
GetSystemTimePreciseAsFileTime(&filetime);
|
||||||
|
#else
|
||||||
|
GetSystemTimeAsFileTime(&filetime);
|
||||||
|
#endif
|
||||||
|
x.LowPart = filetime.dwLowDateTime;
|
||||||
|
x.HighPart = filetime.dwHighDateTime;
|
||||||
|
usec = x.QuadPart / 10 - epoch_offset_us;
|
||||||
|
tv->tv_sec = (time_t)(usec / 1000000ULL);
|
||||||
|
tv->tv_usec = (long)(usec % 1000000ULL);
|
||||||
|
}
|
||||||
|
if (tz) {
|
||||||
|
TIME_ZONE_INFORMATION timezone;
|
||||||
|
GetTimeZoneInformation(&timezone);
|
||||||
|
tz->tz_minuteswest = timezone.Bias;
|
||||||
|
tz->tz_dsttime = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace rtc;
|
||||||
|
|
||||||
|
ClientTrackData::ClientTrackData(shared_ptr<Track> track, shared_ptr<RtcpSrReporter> sender) {
|
||||||
|
this->track = track;
|
||||||
|
this->sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::setState(State state) {
|
||||||
|
std::unique_lock lock(_mutex);
|
||||||
|
this->state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client::State Client::getState() {
|
||||||
|
std::shared_lock lock(_mutex);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientTrack::ClientTrack(string id, shared_ptr<ClientTrackData> trackData) {
|
||||||
|
this->id = id;
|
||||||
|
this->trackData = trackData;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t currentTimeInMicroSeconds() {
|
||||||
|
struct timeval time;
|
||||||
|
gettimeofday(&time, NULL);
|
||||||
|
return uint64_t(time.tv_sec) * 1000 * 1000 + time.tv_usec;
|
||||||
|
}
|
63
examples/streamer/helpers.hpp
Normal file
63
examples/streamer/helpers.hpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef helpers_hpp
|
||||||
|
#define helpers_hpp
|
||||||
|
|
||||||
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
struct ClientTrackData {
|
||||||
|
std::shared_ptr<rtc::Track> track;
|
||||||
|
std::shared_ptr<rtc::RtcpSrReporter> sender;
|
||||||
|
|
||||||
|
ClientTrackData(std::shared_ptr<rtc::Track> track, std::shared_ptr<rtc::RtcpSrReporter> sender);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Client {
|
||||||
|
enum class State {
|
||||||
|
Waiting,
|
||||||
|
WaitingForVideo,
|
||||||
|
WaitingForAudio,
|
||||||
|
Ready
|
||||||
|
};
|
||||||
|
const std::shared_ptr<rtc::PeerConnection> & peerConnection = _peerConnection;
|
||||||
|
Client(std::shared_ptr<rtc::PeerConnection> pc) {
|
||||||
|
_peerConnection = pc;
|
||||||
|
}
|
||||||
|
std::optional<std::shared_ptr<ClientTrackData>> video;
|
||||||
|
std::optional<std::shared_ptr<ClientTrackData>> audio;
|
||||||
|
std::optional<std::shared_ptr<rtc::DataChannel>> dataChannel{};
|
||||||
|
void setState(State state);
|
||||||
|
State getState();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_mutex _mutex;
|
||||||
|
State state = State::Waiting;
|
||||||
|
std::string id;
|
||||||
|
std::shared_ptr<rtc::PeerConnection> _peerConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClientTrack {
|
||||||
|
std::string id;
|
||||||
|
std::shared_ptr<ClientTrackData> trackData;
|
||||||
|
ClientTrack(std::string id, std::shared_ptr<ClientTrackData> trackData);
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t currentTimeInMicroSeconds();
|
||||||
|
|
||||||
|
#endif /* helpers_hpp */
|
72
examples/streamer/index.html
Normal file
72
examples/streamer/index.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>libdatachannel media example</title>
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#media {
|
||||||
|
max-width: 1280px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||||
|
|
||||||
|
<h2>Options</h2>
|
||||||
|
|
||||||
|
<div class="option">
|
||||||
|
<input id="use-stun" type="checkbox"/>
|
||||||
|
<label for="use-stun">Use STUN server</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="start" onclick="start()" disabled>Start</button>
|
||||||
|
<button id="stop" style="display: none" onclick="stop()">Stop</button>
|
||||||
|
|
||||||
|
<h2>State</h2>
|
||||||
|
<p>
|
||||||
|
ICE gathering state: <span id="ice-gathering-state"></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
ICE connection state: <span id="ice-connection-state"></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Signaling state: <span id="signaling-state"></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="media" style="display: none">
|
||||||
|
<h2>Media</h2>
|
||||||
|
<video id="video" autoplay playsinline></video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Data channel</h2>
|
||||||
|
<pre id="data-channel" style="height: 200px;"></pre>
|
||||||
|
|
||||||
|
<h2>SDP</h2>
|
||||||
|
|
||||||
|
<h3>Offer</h3>
|
||||||
|
<pre id="offer-sdp"></pre>
|
||||||
|
|
||||||
|
<h3>Answer</h3>
|
||||||
|
<pre id="answer-sdp"></pre>
|
||||||
|
|
||||||
|
<script src="client.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
480
examples/streamer/main.cpp
Normal file
480
examples/streamer/main.cpp
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel client example
|
||||||
|
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||||
|
* Copyright (c) 2019 Murat Dogan
|
||||||
|
* Copyright (c) 2020 Will Munn
|
||||||
|
* Copyright (c) 2020 Nico Chatzi
|
||||||
|
* Copyright (c) 2020 Lara Mackey
|
||||||
|
* Copyright (c) 2020 Erik Cota-Robles
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nlohmann/json.hpp"
|
||||||
|
|
||||||
|
#include "h264fileparser.hpp"
|
||||||
|
#include "opusfileparser.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "ArgParser.hpp"
|
||||||
|
|
||||||
|
using namespace rtc;
|
||||||
|
using namespace std;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||||
|
|
||||||
|
/// all connected clients
|
||||||
|
unordered_map<string, shared_ptr<Client>> clients{};
|
||||||
|
|
||||||
|
/// Creates peer connection and client representation
|
||||||
|
/// @param config Configuration
|
||||||
|
/// @param wws Websocket for signaling
|
||||||
|
/// @param id Client ID
|
||||||
|
/// @returns Client
|
||||||
|
shared_ptr<Client> createPeerConnection(const Configuration &config,
|
||||||
|
weak_ptr<WebSocket> wws,
|
||||||
|
string id);
|
||||||
|
|
||||||
|
/// Creates stream
|
||||||
|
/// @param h264Samples Directory with H264 samples
|
||||||
|
/// @param fps Video FPS
|
||||||
|
/// @param opusSamples Directory with opus samples
|
||||||
|
/// @returns Stream object
|
||||||
|
shared_ptr<Stream> createStream(const string h264Samples, const unsigned fps, const string opusSamples);
|
||||||
|
|
||||||
|
/// Add client to stream
|
||||||
|
/// @param client Client
|
||||||
|
/// @param adding_video True if adding video
|
||||||
|
void addToStream(shared_ptr<Client> client, bool isAddingVideo);
|
||||||
|
|
||||||
|
/// Start stream
|
||||||
|
void startStream();
|
||||||
|
|
||||||
|
/// Main dispatch queue
|
||||||
|
DispatchQueue MainThread("Main");
|
||||||
|
|
||||||
|
/// Audio and video stream
|
||||||
|
optional<shared_ptr<Stream>> avStream = nullopt;
|
||||||
|
|
||||||
|
const string defaultRootDirectory = "../../../../examples/streamer/samples/";
|
||||||
|
const string defaultH264SamplesDirectory = defaultRootDirectory + "h264/";
|
||||||
|
string h264SamplesDirectory = defaultH264SamplesDirectory;
|
||||||
|
const string defaultOpusSamplesDirectory = defaultRootDirectory + "opus/";
|
||||||
|
string opusSamplesDirectory = defaultOpusSamplesDirectory;
|
||||||
|
const string defaultIPAddress = "127.0.0.1";
|
||||||
|
const uint16_t defaultPort = 8000;
|
||||||
|
string ip_address = defaultIPAddress;
|
||||||
|
uint16_t port = defaultPort;
|
||||||
|
|
||||||
|
/// Incomming message handler for websocket
|
||||||
|
/// @param message Incommint message
|
||||||
|
/// @param config Configuration
|
||||||
|
/// @param ws Websocket
|
||||||
|
void wsOnMessage(json message, Configuration config, shared_ptr<WebSocket> ws) {
|
||||||
|
auto it = message.find("id");
|
||||||
|
if (it == message.end())
|
||||||
|
return;
|
||||||
|
string id = it->get<string>();
|
||||||
|
it = message.find("type");
|
||||||
|
if (it == message.end())
|
||||||
|
return;
|
||||||
|
string type = it->get<string>();
|
||||||
|
|
||||||
|
if (type == "streamRequest") {
|
||||||
|
shared_ptr<Client> c = createPeerConnection(config, make_weak_ptr(ws), id);
|
||||||
|
clients.emplace(id, c);
|
||||||
|
} else if (type == "answer") {
|
||||||
|
shared_ptr<Client> c;
|
||||||
|
if (auto jt = clients.find(id); jt != clients.end()) {
|
||||||
|
auto pc = clients.at(id)->peerConnection;
|
||||||
|
auto sdp = message["sdp"].get<string>();
|
||||||
|
auto description = Description(sdp, type);
|
||||||
|
pc->setRemoteDescription(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) try {
|
||||||
|
bool enableDebugLogs = false;
|
||||||
|
bool printHelp = false;
|
||||||
|
int c = 0;
|
||||||
|
auto parser = ArgParser({{"a", "audio"}, {"b", "video"}, {"d", "ip"}, {"p","port"}}, {{"h", "help"}, {"v", "verbose"}});
|
||||||
|
auto parsingResult = parser.parse(argc, argv, [](string key, string value) {
|
||||||
|
if (key == "audio") {
|
||||||
|
opusSamplesDirectory = value + "/";
|
||||||
|
} else if (key == "video") {
|
||||||
|
h264SamplesDirectory = value + "/";
|
||||||
|
} else if (key == "ip") {
|
||||||
|
ip_address = value;
|
||||||
|
} else if (key == "port") {
|
||||||
|
port = atoi(value.data());
|
||||||
|
} else {
|
||||||
|
cerr << "Invalid option --" << key << " with value " << value << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, [&enableDebugLogs, &printHelp](string flag){
|
||||||
|
if (flag == "verbose") {
|
||||||
|
enableDebugLogs = true;
|
||||||
|
} else if (flag == "help") {
|
||||||
|
printHelp = true;
|
||||||
|
} else {
|
||||||
|
cerr << "Invalid flag --" << flag << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (!parsingResult) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (printHelp) {
|
||||||
|
cout << "usage: stream-h264 [-a opus_samples_folder] [-b h264_samples_folder] [-d ip_address] [-p port] [-v] [-h]" << endl
|
||||||
|
<< "Arguments:" << endl
|
||||||
|
<< "\t -a " << "Directory with opus samples (default: " << defaultOpusSamplesDirectory << ")." << endl
|
||||||
|
<< "\t -b " << "Directory with H264 samples (default: " << defaultH264SamplesDirectory << ")." << endl
|
||||||
|
<< "\t -d " << "Signaling server IP address (default: " << defaultIPAddress << ")." << endl
|
||||||
|
<< "\t -p " << "Signaling server port (default: " << defaultPort << ")." << endl
|
||||||
|
<< "\t -v " << "Enable debug logs." << endl
|
||||||
|
<< "\t -h " << "Print this help and exit." << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (enableDebugLogs) {
|
||||||
|
InitLogger(LogLevel::Debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration config;
|
||||||
|
string stunServer = "stun:stun.l.google.com:19302";
|
||||||
|
cout << "Stun server is " << stunServer << endl;
|
||||||
|
config.iceServers.emplace_back(stunServer);
|
||||||
|
|
||||||
|
|
||||||
|
string localId = "server";
|
||||||
|
cout << "The local ID is: " << localId << endl;
|
||||||
|
|
||||||
|
auto ws = make_shared<WebSocket>();
|
||||||
|
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
||||||
|
|
||||||
|
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
||||||
|
|
||||||
|
ws->onError([](const string &error) { cout << "WebSocket failed: " << error << endl; });
|
||||||
|
|
||||||
|
ws->onMessage([&](variant<binary, string> data) {
|
||||||
|
if (!holds_alternative<string>(data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
json message = json::parse(get<string>(data));
|
||||||
|
MainThread.dispatch([message, config, ws]() {
|
||||||
|
wsOnMessage(message, config, ws);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const string url = "ws://" + ip_address + ":" + to_string(port) + "/" + localId;
|
||||||
|
cout << "Url is " << url << endl;
|
||||||
|
ws->open(url);
|
||||||
|
|
||||||
|
cout << "Waiting for signaling to be connected..." << endl;
|
||||||
|
while (!ws->isOpen()) {
|
||||||
|
if (ws->isClosed())
|
||||||
|
return 1;
|
||||||
|
this_thread::sleep_for(100ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
string id;
|
||||||
|
cout << "Enter to exit" << endl;
|
||||||
|
cin >> id;
|
||||||
|
cin.ignore();
|
||||||
|
cout << "exiting" << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "Cleaning up..." << endl;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cout << "Error: " << e.what() << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<ClientTrackData> addVideo(const shared_ptr<PeerConnection> pc, const uint8_t payloadType, const uint32_t ssrc, const string cname, const string msid, const function<void (void)> onOpen) {
|
||||||
|
auto video = Description::Video(cname);
|
||||||
|
video.addH264Codec(payloadType);
|
||||||
|
video.addSSRC(ssrc, cname, msid, cname);
|
||||||
|
auto track = pc->addTrack(video);
|
||||||
|
// create RTP configuration
|
||||||
|
auto rtpConfig = shared_ptr<RtpPacketizationConfig>(new RtpPacketizationConfig(ssrc, cname, payloadType, H264RtpPacketizer::defaultClockRate));
|
||||||
|
// create packetizer
|
||||||
|
auto packetizer = shared_ptr<H264RtpPacketizer>(new H264RtpPacketizer(H264RtpPacketizer::Separator::Length, rtpConfig));
|
||||||
|
// create H264 handler
|
||||||
|
shared_ptr<H264PacketizationHandler> h264Handler(new H264PacketizationHandler(packetizer));
|
||||||
|
// add RTCP SR handler
|
||||||
|
auto srReporter = make_shared<RtcpSrReporter>(rtpConfig);
|
||||||
|
h264Handler->addToChain(srReporter);
|
||||||
|
// add RTCP NACK handler
|
||||||
|
auto nackResponder = make_shared<RtcpNackResponder>();
|
||||||
|
h264Handler->addToChain(nackResponder);
|
||||||
|
// set handler
|
||||||
|
track->setRtcpHandler(h264Handler);
|
||||||
|
track->onOpen(onOpen);
|
||||||
|
auto trackData = make_shared<ClientTrackData>(track, srReporter);
|
||||||
|
return trackData;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<ClientTrackData> addAudio(const shared_ptr<PeerConnection> pc, const uint8_t payloadType, const uint32_t ssrc, const string cname, const string msid, const function<void (void)> onOpen) {
|
||||||
|
auto audio = Description::Audio(cname);
|
||||||
|
audio.addOpusCodec(payloadType);
|
||||||
|
audio.addSSRC(ssrc, cname, msid, cname);
|
||||||
|
auto track = pc->addTrack(audio);
|
||||||
|
// create RTP configuration
|
||||||
|
auto rtpConfig = shared_ptr<RtpPacketizationConfig>(new RtpPacketizationConfig(ssrc, cname, payloadType, OpusRtpPacketizer::defaultClockRate));
|
||||||
|
// create packetizer
|
||||||
|
auto packetizer = make_shared<OpusRtpPacketizer>(rtpConfig);
|
||||||
|
// create opus handler
|
||||||
|
auto opusHandler = make_shared<OpusPacketizationHandler>(packetizer);
|
||||||
|
// add RTCP SR handler
|
||||||
|
auto srReporter = make_shared<RtcpSrReporter>(rtpConfig);
|
||||||
|
opusHandler->addToChain(srReporter);
|
||||||
|
// add RTCP NACK handler
|
||||||
|
auto nackResponder = make_shared<RtcpNackResponder>();
|
||||||
|
opusHandler->addToChain(nackResponder);
|
||||||
|
// set handler
|
||||||
|
track->setRtcpHandler(opusHandler);
|
||||||
|
track->onOpen(onOpen);
|
||||||
|
auto trackData = make_shared<ClientTrackData>(track, srReporter);
|
||||||
|
return trackData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and setup a PeerConnection
|
||||||
|
shared_ptr<Client> createPeerConnection(const Configuration &config,
|
||||||
|
weak_ptr<WebSocket> wws,
|
||||||
|
string id) {
|
||||||
|
|
||||||
|
auto pc = make_shared<PeerConnection>(config);
|
||||||
|
shared_ptr<Client> client(new Client(pc));
|
||||||
|
|
||||||
|
pc->onStateChange([id](PeerConnection::State state) {
|
||||||
|
cout << "State: " << state << endl;
|
||||||
|
if (state == PeerConnection::State::Disconnected ||
|
||||||
|
state == PeerConnection::State::Failed ||
|
||||||
|
state == PeerConnection::State::Closed) {
|
||||||
|
// remove disconnected client
|
||||||
|
MainThread.dispatch([id]() {
|
||||||
|
clients.erase(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pc->onGatheringStateChange(
|
||||||
|
[wpc = make_weak_ptr(pc), id, wws](PeerConnection::GatheringState state) {
|
||||||
|
cout << "Gathering State: " << state << endl;
|
||||||
|
if (state == PeerConnection::GatheringState::Complete) {
|
||||||
|
if(auto pc = wpc.lock()) {
|
||||||
|
auto description = pc->localDescription();
|
||||||
|
json message = {
|
||||||
|
{"id", id},
|
||||||
|
{"type", description->typeString()},
|
||||||
|
{"sdp", string(description.value())}
|
||||||
|
};
|
||||||
|
// Gathering complete, send answer
|
||||||
|
if (auto ws = wws.lock()) {
|
||||||
|
ws->send(message.dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client->video = addVideo(pc, 102, 1, "video-stream", "stream1", [id, wc = make_weak_ptr(client)]() {
|
||||||
|
MainThread.dispatch([wc]() {
|
||||||
|
if (auto c = wc.lock()) {
|
||||||
|
addToStream(c, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cout << "Video from " << id << " opened" << endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
client->audio = addAudio(pc, 111, 2, "audio-stream", "stream1", [id, wc = make_weak_ptr(client)]() {
|
||||||
|
MainThread.dispatch([wc]() {
|
||||||
|
if (auto c = wc.lock()) {
|
||||||
|
addToStream(c, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cout << "Audio from " << id << " opened" << endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto dc = pc->addDataChannel("ping-pong");
|
||||||
|
dc->onOpen([id, wdc = make_weak_ptr(dc)]() {
|
||||||
|
if (auto dc = wdc.lock()) {
|
||||||
|
dc->send("Ping");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dc->onMessage(nullptr, [id, wdc = make_weak_ptr(dc)](string msg) {
|
||||||
|
cout << "Message from " << id << " received: " << msg << endl;
|
||||||
|
if (auto dc = wdc.lock()) {
|
||||||
|
dc->send("Ping");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client->dataChannel = dc;
|
||||||
|
|
||||||
|
pc->setLocalDescription();
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create stream
|
||||||
|
shared_ptr<Stream> createStream(const string h264Samples, const unsigned fps, const string opusSamples) {
|
||||||
|
// video source
|
||||||
|
auto video = make_shared<H264FileParser>(h264Samples, fps, true);
|
||||||
|
// audio source
|
||||||
|
auto audio = make_shared<OPUSFileParser>(opusSamples, true);
|
||||||
|
|
||||||
|
auto stream = make_shared<Stream>(video, audio);
|
||||||
|
// set callback responsible for sample sending
|
||||||
|
stream->onSample([ws = make_weak_ptr(stream)](Stream::StreamSourceType type, uint64_t sampleTime, rtc::binary sample) {
|
||||||
|
vector<ClientTrack> tracks{};
|
||||||
|
string streamType = type == Stream::StreamSourceType::Video ? "video" : "audio";
|
||||||
|
// get track for given type
|
||||||
|
function<optional<shared_ptr<ClientTrackData>> (shared_ptr<Client>)> getTrackData = [type](shared_ptr<Client> client) {
|
||||||
|
return type == Stream::StreamSourceType::Video ? client->video : client->audio;
|
||||||
|
};
|
||||||
|
// get all clients with Ready state
|
||||||
|
for(auto id_client: clients) {
|
||||||
|
auto id = id_client.first;
|
||||||
|
auto client = id_client.second;
|
||||||
|
auto optTrackData = getTrackData(client);
|
||||||
|
if (client->getState() == Client::State::Ready && optTrackData.has_value()) {
|
||||||
|
auto trackData = optTrackData.value();
|
||||||
|
tracks.push_back(ClientTrack(id, trackData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!tracks.empty()) {
|
||||||
|
auto message = make_message(move(sample));
|
||||||
|
for (auto clientTrack: tracks) {
|
||||||
|
auto client = clientTrack.id;
|
||||||
|
auto trackData = clientTrack.trackData;
|
||||||
|
// sample time is in us, we need to convert it to seconds
|
||||||
|
auto elapsedSeconds = double(sampleTime) / (1000 * 1000);
|
||||||
|
auto rtpConfig = trackData->sender->rtpConfig;
|
||||||
|
// get elapsed time in clock rate
|
||||||
|
uint32_t elapsedTimestamp = rtpConfig->secondsToTimestamp(elapsedSeconds);
|
||||||
|
|
||||||
|
// set new timestamp
|
||||||
|
rtpConfig->timestamp = rtpConfig->startTimestamp + elapsedTimestamp;
|
||||||
|
|
||||||
|
// get elapsed time in clock rate from last RTCP sender report
|
||||||
|
auto reportElapsedTimestamp = rtpConfig->timestamp - trackData->sender->previousReportedTimestamp;
|
||||||
|
// check if last report was at least 1 second ago
|
||||||
|
if (rtpConfig->timestampToSeconds(reportElapsedTimestamp) > 1) {
|
||||||
|
trackData->sender->setNeedsToReport();
|
||||||
|
}
|
||||||
|
cout << "Sending " << streamType << " sample with size: " << to_string(message->size()) << " to " << client << endl;
|
||||||
|
bool send = false;
|
||||||
|
try {
|
||||||
|
// send sample
|
||||||
|
send = trackData->track->send(*message);
|
||||||
|
} catch (...) {
|
||||||
|
send = false;
|
||||||
|
}
|
||||||
|
if (!send) {
|
||||||
|
cerr << "Unable to send "<< streamType << " packet" << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MainThread.dispatch([ws]() {
|
||||||
|
if (clients.empty()) {
|
||||||
|
// we have no clients, stop the stream
|
||||||
|
if (auto stream = ws.lock()) {
|
||||||
|
stream->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start stream
|
||||||
|
void startStream() {
|
||||||
|
shared_ptr<Stream> stream;
|
||||||
|
if (avStream.has_value()) {
|
||||||
|
stream = avStream.value();
|
||||||
|
if (stream->isRunning) {
|
||||||
|
// stream is already running
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stream = createStream(h264SamplesDirectory, 30, opusSamplesDirectory);
|
||||||
|
avStream = stream;
|
||||||
|
}
|
||||||
|
stream->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send previous key frame so browser can show something to user
|
||||||
|
/// @param stream Stream
|
||||||
|
/// @param video Video track data
|
||||||
|
void sendInitialNalus(shared_ptr<Stream> stream, shared_ptr<ClientTrackData> video) {
|
||||||
|
auto h264 = dynamic_cast<H264FileParser *>(stream->video.get());
|
||||||
|
auto initialNalus = h264->initialNALUS();
|
||||||
|
|
||||||
|
// send previous NALU key frame so users don't have to wait to see stream works
|
||||||
|
if (!initialNalus.empty()) {
|
||||||
|
const double frameDuration_s = double(h264->sampleDuration_us) / (1000 * 1000);
|
||||||
|
const uint32_t frameTimestampDuration = video->sender->rtpConfig->secondsToTimestamp(frameDuration_s);
|
||||||
|
video->sender->rtpConfig->timestamp = video->sender->rtpConfig->startTimestamp - frameTimestampDuration * 2;
|
||||||
|
video->track->send(initialNalus);
|
||||||
|
video->sender->rtpConfig->timestamp += frameTimestampDuration;
|
||||||
|
// Send initial NAL units again to start stream in firefox browser
|
||||||
|
video->track->send(initialNalus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add client to stream
|
||||||
|
/// @param client Client
|
||||||
|
/// @param adding_video True if adding video
|
||||||
|
void addToStream(shared_ptr<Client> client, bool isAddingVideo) {
|
||||||
|
if (client->getState() == Client::State::Waiting) {
|
||||||
|
client->setState(isAddingVideo ? Client::State::WaitingForAudio : Client::State::WaitingForVideo);
|
||||||
|
} else if ((client->getState() == Client::State::WaitingForAudio && !isAddingVideo)
|
||||||
|
|| (client->getState() == Client::State::WaitingForVideo && isAddingVideo)) {
|
||||||
|
|
||||||
|
// Audio and video tracks are collected now
|
||||||
|
assert(client->video.has_value() && client->audio.has_value());
|
||||||
|
|
||||||
|
auto video = client->video.value();
|
||||||
|
auto audio = client->audio.value();
|
||||||
|
|
||||||
|
auto currentTime_us = double(currentTimeInMicroSeconds());
|
||||||
|
auto currentTime_s = currentTime_us / (1000 * 1000);
|
||||||
|
|
||||||
|
// set start time of stream
|
||||||
|
video->sender->rtpConfig->setStartTime(currentTime_s, RtpPacketizationConfig::EpochStart::T1970);
|
||||||
|
audio->sender->rtpConfig->setStartTime(currentTime_s, RtpPacketizationConfig::EpochStart::T1970);
|
||||||
|
|
||||||
|
// start stat recording of RTCP SR
|
||||||
|
video->sender->startRecording();
|
||||||
|
audio->sender->startRecording();
|
||||||
|
|
||||||
|
if (avStream.has_value()) {
|
||||||
|
sendInitialNalus(avStream.value(), video);
|
||||||
|
}
|
||||||
|
|
||||||
|
client->setState(Client::State::Ready);
|
||||||
|
}
|
||||||
|
if (client->getState() == Client::State::Ready) {
|
||||||
|
startStream();
|
||||||
|
}
|
||||||
|
}
|
23
examples/streamer/opusfileparser.cpp
Normal file
23
examples/streamer/opusfileparser.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "opusfileparser.hpp"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
OPUSFileParser::OPUSFileParser(string directory, bool loop, uint32_t samplesPerSecond): FileParser(directory, ".opus", samplesPerSecond, loop) { }
|
32
examples/streamer/opusfileparser.hpp
Normal file
32
examples/streamer/opusfileparser.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* libdatachannel streamer example
|
||||||
|
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef opusfileparser_hpp
|
||||||
|
#define opusfileparser_hpp
|
||||||
|
|
||||||
|
#include "fileparser.hpp"
|
||||||
|
|
||||||
|
class OPUSFileParser: public FileParser {
|
||||||
|
static const uint32_t defaultSamplesPerSecond = 50;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OPUSFileParser(std::string directory, bool loop, uint32_t samplesPerSecond = OPUSFileParser::defaultSamplesPerSecond);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* opusfileparser_hpp */
|
BIN
examples/streamer/samples/bensound-creativeminds.mp3
Normal file
BIN
examples/streamer/samples/bensound-creativeminds.mp3
Normal file
Binary file not shown.
BIN
examples/streamer/samples/candle.mov
Normal file
BIN
examples/streamer/samples/candle.mov
Normal file
Binary file not shown.
115
examples/streamer/samples/generate_h264.py
Executable file
115
examples/streamer/samples/generate_h264.py
Executable file
@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import getopt
|
||||||
|
import sys
|
||||||
|
import glob
|
||||||
|
from functools import reduce
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
|
||||||
|
class H264ByteStream:
|
||||||
|
@staticmethod
|
||||||
|
def nalu_type(nalu: bytes) -> int:
|
||||||
|
return nalu[0] & 0x1F
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def merge_sample(sample: List[bytes]) -> bytes:
|
||||||
|
result = bytes()
|
||||||
|
for nalu in sample:
|
||||||
|
result += len(nalu).to_bytes(4, byteorder='big') + nalu
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reduce_nalus_to_samples(samples: List[List[bytes]], current: bytes) -> List[List[bytes]]:
|
||||||
|
last_nalus = samples[-1]
|
||||||
|
samples[-1] = last_nalus + [current]
|
||||||
|
if H264ByteStream.nalu_type(current) in [1, 5]:
|
||||||
|
samples.append([])
|
||||||
|
return samples
|
||||||
|
|
||||||
|
def __init__(self, file_name: str):
|
||||||
|
with open(file_name, "rb") as file:
|
||||||
|
byte_stream = file.read()
|
||||||
|
long_split = byte_stream.split(b"\x00\x00\x00\x01")
|
||||||
|
splits = reduce(lambda acc, x: acc + x.split(b"\x00\x00\x01"), long_split, [])
|
||||||
|
nalus = filter(lambda x: len(x) > 0, splits)
|
||||||
|
self.samples = list(
|
||||||
|
filter(lambda x: len(x) > 0, reduce(H264ByteStream.reduce_nalus_to_samples, nalus, [[]])))
|
||||||
|
|
||||||
|
|
||||||
|
def generate(input_file: str, output_dir: str, max_samples: Optional[int], fps: Optional[int]):
|
||||||
|
if output_dir[-1] != "/":
|
||||||
|
output_dir += "/"
|
||||||
|
if os.path.isdir(output_dir):
|
||||||
|
files_to_delete = glob.glob(output_dir + "*.h264")
|
||||||
|
if len(files_to_delete) > 0:
|
||||||
|
print("Remove following files?")
|
||||||
|
for file in files_to_delete:
|
||||||
|
print(file)
|
||||||
|
response = input("Remove files? [y/n] ").lower()
|
||||||
|
if response != "y" and response != "yes":
|
||||||
|
print("Cancelling...")
|
||||||
|
return
|
||||||
|
print("Removing files")
|
||||||
|
for file in files_to_delete:
|
||||||
|
os.remove(file)
|
||||||
|
else:
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
video_stream_file = "_video_stream.h264"
|
||||||
|
if os.path.isfile(video_stream_file):
|
||||||
|
os.remove(video_stream_file)
|
||||||
|
|
||||||
|
fps_line = "" if fps is None else "-filter:v fps=fps={} ".format(fps)
|
||||||
|
command = 'ffmpeg -i {} -an -vcodec libx264 -preset slow -profile baseline {}{}'.format(input_file, fps_line,
|
||||||
|
video_stream_file)
|
||||||
|
os.system(command)
|
||||||
|
|
||||||
|
data = H264ByteStream(video_stream_file)
|
||||||
|
index = 0
|
||||||
|
for sample in data.samples[:max_samples]:
|
||||||
|
name = "{}sample-{}.h264".format(output_dir, index)
|
||||||
|
index += 1
|
||||||
|
with open(name, 'wb') as file:
|
||||||
|
merged_sample = H264ByteStream.merge_sample(sample)
|
||||||
|
file.write(merged_sample)
|
||||||
|
os.remove(video_stream_file)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
input_file = None
|
||||||
|
default_output_dir = "h264/"
|
||||||
|
output_dir = default_output_dir
|
||||||
|
max_samples = None
|
||||||
|
fps = None
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(argv, "hi:o:m:f:", ["help", "ifile=", "odir=", "max=", "fps"])
|
||||||
|
except getopt.GetoptError:
|
||||||
|
print('generate_h264.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-f <fps>] [-h]')
|
||||||
|
sys.exit(2)
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt in ("-h", "--help"):
|
||||||
|
print("Usage: generate_h264.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-f <fps>] [-h]")
|
||||||
|
print("Arguments:")
|
||||||
|
print("\t-i,--ifile: Input file")
|
||||||
|
print("\t-o,--odir: Output directory (default: " + default_output_dir + ")")
|
||||||
|
print("\t-m,--max: Maximum generated samples")
|
||||||
|
print("\t-f,--fps: Output fps")
|
||||||
|
print("\t-h,--help: Print this help and exit")
|
||||||
|
sys.exit()
|
||||||
|
elif opt in ("-i", "--ifile"):
|
||||||
|
input_file = arg
|
||||||
|
elif opt in ("-o", "--ofile"):
|
||||||
|
output_dir = arg
|
||||||
|
elif opt in ("-m", "--max"):
|
||||||
|
max_samples = int(arg)
|
||||||
|
elif opt in ("-f", "--fps"):
|
||||||
|
fps = int(arg)
|
||||||
|
if input_file is None:
|
||||||
|
print("Missing argument -i")
|
||||||
|
sys.exit(2)
|
||||||
|
generate(input_file, output_dir, max_samples, fps)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1:])
|
142
examples/streamer/samples/generate_opus.py
Executable file
142
examples/streamer/samples/generate_opus.py
Executable file
@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from kaitaistruct import KaitaiStruct, ValidationNotEqualError
|
||||||
|
import os
|
||||||
|
import getopt
|
||||||
|
import sys
|
||||||
|
import glob
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
|
class Ogg(KaitaiStruct):
|
||||||
|
"""Ogg is a popular media container format, which provides basic
|
||||||
|
streaming / buffering mechanisms and is content-agnostic. Most
|
||||||
|
popular codecs that are used within Ogg streams are Vorbis (thus
|
||||||
|
making Ogg/Vorbis streams) and Theora (Ogg/Theora).
|
||||||
|
|
||||||
|
Ogg stream is a sequence Ogg pages. They can be read sequentially,
|
||||||
|
or one can jump into arbitrary stream location and scan for "OggS"
|
||||||
|
sync code to find the beginning of a new Ogg page and continue
|
||||||
|
decoding the stream contents from that one.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, _io, _parent=None, _root=None):
|
||||||
|
KaitaiStruct.__init__(self, _io)
|
||||||
|
self._parent = _parent
|
||||||
|
self._root = _root if _root else self
|
||||||
|
self._read()
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
self.pages = []
|
||||||
|
i = 0
|
||||||
|
while not self._io.is_eof():
|
||||||
|
self.pages.append(Ogg.Page(self._io, self, self._root))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
class Page(KaitaiStruct):
|
||||||
|
"""Ogg page is a basic unit of data in an Ogg bitstream, usually
|
||||||
|
it's around 4-8 KB, with a maximum size of 65307 bytes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, _io, _parent=None, _root=None):
|
||||||
|
KaitaiStruct.__init__(self, _io)
|
||||||
|
self._parent = _parent
|
||||||
|
self._root = _root if _root else self
|
||||||
|
self._read()
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
self.sync_code = self._io.read_bytes(4)
|
||||||
|
if not self.sync_code == b"\x4F\x67\x67\x53":
|
||||||
|
raise ValidationNotEqualError(b"\x4F\x67\x67\x53", self.sync_code, self._io,
|
||||||
|
u"/types/page/seq/0")
|
||||||
|
self.version = self._io.read_bytes(1)
|
||||||
|
if not self.version == b"\x00":
|
||||||
|
raise ValidationNotEqualError(b"\x00", self.version, self._io, u"/types/page/seq/1")
|
||||||
|
self.reserved1 = self._io.read_bits_int_be(5)
|
||||||
|
self.is_end_of_stream = self._io.read_bits_int_be(1) != 0
|
||||||
|
self.is_beginning_of_stream = self._io.read_bits_int_be(1) != 0
|
||||||
|
self.is_continuation = self._io.read_bits_int_be(1) != 0
|
||||||
|
self._io.align_to_byte()
|
||||||
|
self.granule_pos = self._io.read_u8le()
|
||||||
|
self.bitstream_serial = self._io.read_u4le()
|
||||||
|
self.page_seq_num = self._io.read_u4le()
|
||||||
|
self.crc32 = self._io.read_u4le()
|
||||||
|
self.num_segments = self._io.read_u1()
|
||||||
|
self.len_segments = [None] * self.num_segments
|
||||||
|
for i in range(self.num_segments):
|
||||||
|
self.len_segments[i] = self._io.read_u1()
|
||||||
|
|
||||||
|
self.segments = [None] * self.num_segments
|
||||||
|
for i in range(self.num_segments):
|
||||||
|
self.segments[i] = self._io.read_bytes(self.len_segments[i])
|
||||||
|
|
||||||
|
|
||||||
|
def generate(input_file: str, output_dir: str, max_samples: int):
|
||||||
|
if output_dir[-1] != "/":
|
||||||
|
output_dir += "/"
|
||||||
|
if os.path.isdir(output_dir):
|
||||||
|
files_to_delete = glob.glob(output_dir + "*.opus")
|
||||||
|
if len(files_to_delete) > 0:
|
||||||
|
print("Remove following files?")
|
||||||
|
for file in files_to_delete:
|
||||||
|
print(file)
|
||||||
|
response = input("Remove files? [y/n] ").lower()
|
||||||
|
if response != "y" and response != "yes":
|
||||||
|
print("Cancelling...")
|
||||||
|
return
|
||||||
|
print("Removing files")
|
||||||
|
for file in files_to_delete:
|
||||||
|
os.remove(file)
|
||||||
|
else:
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
audio_stream_file = "_audio_stream.ogg"
|
||||||
|
if os.path.isfile(audio_stream_file):
|
||||||
|
os.remove(audio_stream_file)
|
||||||
|
os.system('ffmpeg -i {} -vn -ar 48000 -ac 2 -vbr off -acodec libopus -ab 64k {}'.format(input_file, audio_stream_file))
|
||||||
|
|
||||||
|
data = Ogg.from_file(audio_stream_file)
|
||||||
|
index = 0
|
||||||
|
valid_pages = data.pages[2:]
|
||||||
|
segments = list(reduce(lambda x, y: x + y.segments, valid_pages, []))[:max_samples]
|
||||||
|
for segment in segments:
|
||||||
|
name = "{}sample-{}.opus".format(output_dir, index)
|
||||||
|
index += 1
|
||||||
|
with open(name, 'wb') as file:
|
||||||
|
assert len(list(segment)) == 160
|
||||||
|
file.write(segment)
|
||||||
|
os.remove(audio_stream_file)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
input_file = None
|
||||||
|
default_output_dir = "opus/"
|
||||||
|
output_dir = default_output_dir
|
||||||
|
max_samples = None
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(argv, "hi:o:m:", ["help", "ifile=", "odir=", "max="])
|
||||||
|
except getopt.GetoptError:
|
||||||
|
print('generate_opus.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-h]')
|
||||||
|
sys.exit(2)
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt in ("-h", "--help"):
|
||||||
|
print("Usage: generate_opus.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-h]")
|
||||||
|
print("Arguments:")
|
||||||
|
print("\t-i,--ifile: Input file")
|
||||||
|
print("\t-o,--odir: Output directory (default: " + default_output_dir + ")")
|
||||||
|
print("\t-m,--max: Maximum generated samples")
|
||||||
|
print("\t-h,--help: Print this help and exit")
|
||||||
|
sys.exit()
|
||||||
|
elif opt in ("-i", "--ifile"):
|
||||||
|
input_file = arg
|
||||||
|
elif opt in ("-o", "--ofile"):
|
||||||
|
output_dir = arg
|
||||||
|
elif opt in ("-m", "--max"):
|
||||||
|
max_samples = int(arg)
|
||||||
|
if input_file is None:
|
||||||
|
print("Missing argument -i")
|
||||||
|
sys.exit(2)
|
||||||
|
generate(input_file, output_dir, max_samples)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1:])
|
BIN
examples/streamer/samples/h264/sample-0.h264
Normal file
BIN
examples/streamer/samples/h264/sample-0.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-1.h264
Normal file
BIN
examples/streamer/samples/h264/sample-1.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-10.h264
Normal file
BIN
examples/streamer/samples/h264/sample-10.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-100.h264
Normal file
BIN
examples/streamer/samples/h264/sample-100.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-101.h264
Normal file
BIN
examples/streamer/samples/h264/sample-101.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-102.h264
Normal file
BIN
examples/streamer/samples/h264/sample-102.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-103.h264
Normal file
BIN
examples/streamer/samples/h264/sample-103.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-104.h264
Normal file
BIN
examples/streamer/samples/h264/sample-104.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-105.h264
Normal file
BIN
examples/streamer/samples/h264/sample-105.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-106.h264
Normal file
BIN
examples/streamer/samples/h264/sample-106.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-107.h264
Normal file
BIN
examples/streamer/samples/h264/sample-107.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-108.h264
Normal file
BIN
examples/streamer/samples/h264/sample-108.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-109.h264
Normal file
BIN
examples/streamer/samples/h264/sample-109.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-11.h264
Normal file
BIN
examples/streamer/samples/h264/sample-11.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-110.h264
Normal file
BIN
examples/streamer/samples/h264/sample-110.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-111.h264
Normal file
BIN
examples/streamer/samples/h264/sample-111.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-112.h264
Normal file
BIN
examples/streamer/samples/h264/sample-112.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-113.h264
Normal file
BIN
examples/streamer/samples/h264/sample-113.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-114.h264
Normal file
BIN
examples/streamer/samples/h264/sample-114.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-115.h264
Normal file
BIN
examples/streamer/samples/h264/sample-115.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-116.h264
Normal file
BIN
examples/streamer/samples/h264/sample-116.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-117.h264
Normal file
BIN
examples/streamer/samples/h264/sample-117.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-118.h264
Normal file
BIN
examples/streamer/samples/h264/sample-118.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-119.h264
Normal file
BIN
examples/streamer/samples/h264/sample-119.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-12.h264
Normal file
BIN
examples/streamer/samples/h264/sample-12.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-120.h264
Normal file
BIN
examples/streamer/samples/h264/sample-120.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-121.h264
Normal file
BIN
examples/streamer/samples/h264/sample-121.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-122.h264
Normal file
BIN
examples/streamer/samples/h264/sample-122.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-123.h264
Normal file
BIN
examples/streamer/samples/h264/sample-123.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-124.h264
Normal file
BIN
examples/streamer/samples/h264/sample-124.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-125.h264
Normal file
BIN
examples/streamer/samples/h264/sample-125.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-126.h264
Normal file
BIN
examples/streamer/samples/h264/sample-126.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-127.h264
Normal file
BIN
examples/streamer/samples/h264/sample-127.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-128.h264
Normal file
BIN
examples/streamer/samples/h264/sample-128.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-129.h264
Normal file
BIN
examples/streamer/samples/h264/sample-129.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-13.h264
Normal file
BIN
examples/streamer/samples/h264/sample-13.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-130.h264
Normal file
BIN
examples/streamer/samples/h264/sample-130.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-131.h264
Normal file
BIN
examples/streamer/samples/h264/sample-131.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-132.h264
Normal file
BIN
examples/streamer/samples/h264/sample-132.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-133.h264
Normal file
BIN
examples/streamer/samples/h264/sample-133.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-134.h264
Normal file
BIN
examples/streamer/samples/h264/sample-134.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-135.h264
Normal file
BIN
examples/streamer/samples/h264/sample-135.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-136.h264
Normal file
BIN
examples/streamer/samples/h264/sample-136.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-137.h264
Normal file
BIN
examples/streamer/samples/h264/sample-137.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-138.h264
Normal file
BIN
examples/streamer/samples/h264/sample-138.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-139.h264
Normal file
BIN
examples/streamer/samples/h264/sample-139.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-14.h264
Normal file
BIN
examples/streamer/samples/h264/sample-14.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-140.h264
Normal file
BIN
examples/streamer/samples/h264/sample-140.h264
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user