mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
676 Commits
Author | SHA1 | Date | |
---|---|---|---|
c37c88543d | |||
011bfbe46f | |||
de2ac6c0c2 | |||
75619babd7 | |||
fe9a34905b | |||
b88f1f5e72 | |||
ab7d7fefe0 | |||
e592fcf217 | |||
10567074c3 | |||
524c56dee9 | |||
efe12f0b73 | |||
82568e3aa0 | |||
7082129b54 | |||
24f8016e4e | |||
98d926a7bf | |||
ffb589d498 | |||
4db6afe9e2 | |||
bdb59905dd | |||
c2b181c6da | |||
3f53365564 | |||
95dfa1015d | |||
1f20f8f1e7 | |||
b2d1a41f7e | |||
9e2e7a7722 | |||
1e02fa34c3 | |||
882c605876 | |||
36a88c605a | |||
09dfc39fd9 | |||
986e5f985f | |||
3e2b0c43ef | |||
557b293934 | |||
7730496bf9 | |||
2b7dc4c529 | |||
b5699239cc | |||
9f3b004756 | |||
8a61043bd7 | |||
93eaa67f5c | |||
690653f8ef | |||
ab392fe0da | |||
669800b39a | |||
4ba8c9e3e8 | |||
1b74ebb0f4 | |||
433d5fbe7f | |||
3204a77e89 | |||
b347afae14 | |||
0482953062 | |||
b5589dbd57 | |||
c23fb10725 | |||
dbfade4eb3 | |||
289b71bc8e | |||
e43c3730a6 | |||
7b206899a4 | |||
c2c57b16df | |||
9805b2fcb5 | |||
569a317bf0 | |||
d262583879 | |||
100669ad0d | |||
7bda5bd77c | |||
52030ca62d | |||
959756526a | |||
1ce27423fe | |||
a0bd65a814 | |||
a7e4b56178 | |||
acaed8ca57 | |||
5728f813d1 | |||
d4d58579d8 | |||
a7620cca16 | |||
61d0cd064f | |||
f5a6342c60 | |||
478f9516cd | |||
08da5e10b2 | |||
c6cd711d74 | |||
4428e3dc3e | |||
fe4afcef85 | |||
03b64e765c | |||
c639174f39 | |||
ce7bd9f4d0 | |||
c4c150a60b | |||
e295fa6072 | |||
470d145a9a | |||
ea12530d30 | |||
329b133383 | |||
e1833cf34b | |||
a0f17cb64b | |||
8a5c38c2d8 | |||
7cddb83ce4 | |||
bb12c071cf | |||
4e3ea69073 | |||
b79c886480 | |||
08da691911 | |||
754568506a | |||
7ac351d1b9 | |||
6d5cecbca1 | |||
fb40e05bab | |||
3710a96fb9 | |||
a1a8ac5203 | |||
4db9f03dd4 | |||
256170adfe | |||
9a4b436e7f | |||
5928b8d206 | |||
1542c78ec5 | |||
cb73fa0c1a | |||
1ceb0fd292 | |||
7c14d940ef | |||
5da67f6ca3 | |||
28a2868edc | |||
3210814648 | |||
1facc8a02f | |||
09818c4ab8 | |||
9e49fcc654 | |||
e6919bdbf8 | |||
749fa32059 | |||
938bd4dd43 | |||
c43e82b8cb | |||
adb733d19a | |||
01085e4492 | |||
43d31caee3 | |||
9aee655fcd | |||
b8b5110653 | |||
e1087ce8b0 | |||
f41baa5c51 | |||
9ee9734473 | |||
3745ff5f7a | |||
3acddc6897 | |||
5d0c62e4bc | |||
06faefd1d6 | |||
4629e57931 | |||
52218272db | |||
f20faed180 | |||
2bae3f62c4 | |||
6eb663d1c5 | |||
a5cc76bb85 | |||
8419de1f1c | |||
e34a3bb8ae | |||
5b66ab2d08 | |||
769f26e94e | |||
31c154e9d9 | |||
a56b036940 | |||
d0e8b63ef0 | |||
d91401775e | |||
735cb538f7 | |||
e23d0723d1 | |||
bb530ef44d | |||
7a219b23d5 | |||
2cde11e9ea | |||
0bbb0233b7 | |||
533beda26a | |||
1427c9e1e4 | |||
6d670726ea | |||
7981fed7f4 | |||
f2caa8048f | |||
92a4ed2a81 | |||
62522d2c25 | |||
3f9565b55d | |||
7a06e48281 | |||
0610bf741f | |||
e79a6f08e3 | |||
4d407cf15e | |||
226e849915 | |||
068fdf60f9 | |||
74ada01114 | |||
cd0be9d510 | |||
b39fe51365 | |||
cf9e57564e | |||
d9aa1818b2 | |||
1d27f5b876 | |||
28e3fad254 | |||
b32a8b884b | |||
7751a9bd6b | |||
3dac651b02 | |||
f6f1efb33f | |||
435d471ad8 | |||
7aa2fdda6f | |||
67e125b22f | |||
f143582de0 | |||
4226a96640 | |||
d305c4c126 | |||
25b3e13d27 | |||
abec5fc219 | |||
684b7ba925 | |||
d8515b6362 | |||
62da885028 | |||
ff2e83bbdc | |||
8f9e8e718e | |||
c6bee7b0d4 | |||
4d93303be8 | |||
1620ddfb03 | |||
452b742adc | |||
244c834992 | |||
ffe202a6a2 | |||
ea87e5ae09 | |||
4259b4e968 | |||
6aff5dc5bc | |||
99bae7f830 | |||
7598d992dc | |||
6380038584 | |||
6144bca0f7 | |||
6ec129f8f8 | |||
be394b7185 | |||
f008b5b447 | |||
5482912e18 | |||
fcc4eaf78b | |||
cca0742973 | |||
495b389e05 | |||
e83494df09 | |||
7bf87c6989 | |||
cb591de15f | |||
eb4540e319 | |||
5d34439cb7 | |||
b19e9077af | |||
4ff010b20b | |||
442e50825c | |||
9f2801b7b9 | |||
3b0bf3a152 | |||
fe4a9ec453 | |||
dc1d14adf1 | |||
14918c16e8 | |||
a023acfa33 | |||
f098019c1f | |||
a67ca9da9b | |||
613ebf2564 | |||
dc6427770c | |||
81e0a05a1a | |||
9ea613f05f | |||
eb4a764648 | |||
baf8a3adce | |||
d9aec59352 | |||
3ff5801512 | |||
fcc2577e11 | |||
b4865f26e4 | |||
fc6d5afdd9 | |||
7a49c0b88b | |||
679c0ccd2e | |||
ee3bc9694b | |||
0c0ba77de5 | |||
8729e0d2aa | |||
12098e7c41 | |||
90eb610bfe | |||
08ddfa1276 | |||
87df64a002 | |||
5af414d0df | |||
2443c72350 | |||
f033e4ab8f | |||
1a6dcdce6f | |||
100039eba8 | |||
e2005c789a | |||
819566b4c1 | |||
82caab8906 | |||
802516b2db | |||
0fcafad9c7 | |||
aab876d346 | |||
11ec8f7247 | |||
1597c9ae6f | |||
b093c4c3d5 | |||
447624322c | |||
422713cbdc | |||
d3d4187021 | |||
f2dd46e589 | |||
5b5debf260 | |||
86c3f914fb | |||
6a1fff13c1 | |||
91a854aa5b | |||
1181fdc599 | |||
fe3d92cebf | |||
c06d77bd8e | |||
c20aebbac2 | |||
9bd12567e6 | |||
bce5faf8ba | |||
64a5d6ecb0 | |||
ed606b7d7e | |||
4a526f66b6 | |||
f8dcfc32ed | |||
cfb10e52e6 | |||
d390c04ca9 | |||
7856ad45fd | |||
fc95b397a6 | |||
db74daae1d | |||
96d6b84c30 | |||
776ff342c0 | |||
519e81727a | |||
2d403247fb | |||
cfddfae1c5 | |||
31b5d6f84a | |||
44bf8b99ef | |||
344895ced1 | |||
9e22770281 | |||
3e2f4a2ac7 | |||
a4fe5cb874 | |||
ece1bd9352 | |||
b7a682cc50 | |||
d47492a54e | |||
fcf33d32a0 | |||
a5eb653064 | |||
ae2abfebad | |||
da4bf1fb49 | |||
82fab04721 | |||
fec3b1ad8b | |||
eb61f6cb3c | |||
fe6cf79f02 | |||
a7bc03c4b3 | |||
06b46aba91 | |||
f83843d054 | |||
e7d45db210 | |||
7a388bdffe | |||
8cb00f81ee | |||
37ebe8cc58 | |||
5eaed06b01 | |||
973f58ec8b | |||
6cb9dd8bad | |||
556104e8d5 | |||
d38ce2b575 | |||
e8628b203f | |||
ffa4e4bb20 | |||
79155d618e | |||
be8ffae0fe | |||
1a723c59aa | |||
6e7c082a7c | |||
037b9c9703 | |||
202d2cb5e4 | |||
03e9eca7d1 | |||
875defd17f | |||
97e23d00be | |||
de47fec19b | |||
c85b9c31fe | |||
be96e8b1fa | |||
2bcdab027c | |||
2eca2c4040 | |||
26cd6e4a59 | |||
be7a6324dd | |||
a7d5ba0232 | |||
3e53706869 | |||
dcb0a0282e | |||
99e78b68a4 | |||
5bb0979c3a | |||
40bed4e740 | |||
8f0c91d1cb | |||
de4192747c | |||
dfe8436954 | |||
7d0d0ea229 | |||
30ca8fb9c3 | |||
4f89e479bc | |||
3faf053bbd | |||
dc91d2cb6d | |||
626ecaa5bb | |||
a88c2dd1bf | |||
ed89fe3e94 | |||
1b73c7fb45 | |||
d563d63c89 | |||
e25bda7108 | |||
12579825a9 | |||
00cccc1e7b | |||
f1fa2abd6e | |||
476528b464 | |||
bc0b14288b | |||
02e4ed5221 | |||
5e876a4686 | |||
3ac3a98e26 | |||
25077878a1 | |||
704d6ab15f | |||
5ce699d33b | |||
e91d721b20 | |||
752c4bf5a1 | |||
bb73da2351 | |||
02105f5da3 | |||
7fac0a7618 | |||
69e5cab0a5 | |||
b220c5df99 | |||
73273d6e81 | |||
61fe8732a6 | |||
9f12c19a02 | |||
72016a7d26 | |||
f40c899b4f | |||
496163dbbe | |||
c0756aaa05 | |||
2cee070cee | |||
5fec28e9b7 | |||
bbec827fef | |||
35e3c7ee3a | |||
0c47c66bb1 | |||
a3cc74c8f1 | |||
db19eded61 | |||
de73af4b80 | |||
3e70af915f | |||
97311230d0 | |||
e966710988 | |||
198d0746b9 | |||
7782f6a5fd | |||
cb71695364 | |||
3af0e3b38b | |||
89e84e7b81 | |||
68bb97b3a3 | |||
41cf60c18b | |||
01eddaca13 | |||
6ec75d7c70 | |||
6221855f27 | |||
a8490f5e1c | |||
55d3336465 | |||
d59c209c20 | |||
46c4bf3ff0 | |||
0227e1c755 | |||
456365bc7c | |||
789ecd7977 | |||
45bbe642fc | |||
2eb523e2ea | |||
5adc62b970 | |||
fcb8d7b3df | |||
1cb53362d1 | |||
0fad9c0a16 | |||
16208d00ca | |||
8dbcd155e5 | |||
46dd2fb515 | |||
129c8b187a | |||
036b972fbe | |||
51bbaa99e1 | |||
37804c0327 | |||
e1f60cd34d | |||
6d6ab9eeb7 | |||
05b7141478 | |||
c3c77010f6 | |||
0a46aa2c6d | |||
e4057c48f6 | |||
d26fa30655 | |||
137d4e3e8e | |||
1a992708a0 | |||
ff76ec1998 | |||
5df78ee3c3 | |||
549e194bb3 | |||
65631f06a6 | |||
3c1b411a76 | |||
8d8374e826 | |||
41194229d3 | |||
fc1f54a0e4 | |||
8fb4997967 | |||
efa6eb8c34 | |||
944e80e85c | |||
2cb3186661 | |||
10d3e2e3d6 | |||
a4afa7382d | |||
efe252dee3 | |||
fd1ca035f9 | |||
62973b6e2f | |||
501fa45560 | |||
1a8d8ec2f9 | |||
b3b4c27bb4 | |||
44eaaf038a | |||
c38edd089b | |||
8ea828f1b0 | |||
35da6636aa | |||
363827594b | |||
cbc027f144 | |||
ebc6a4b65c | |||
37d47d28a8 | |||
46878519c0 | |||
84c298f4f8 | |||
23aed2b844 | |||
df62d6d51c | |||
26241f00b7 | |||
873d14c824 | |||
4953a112ad | |||
c31e1bf0be | |||
98ddba192f | |||
b02b30eea8 | |||
324d97a9b7 | |||
0a1dd4db01 | |||
b1de9acb20 | |||
960300a7cd | |||
3f084d7527 | |||
64096d599c | |||
552e443ef1 | |||
52cb8d68a0 | |||
372e2b7a1f | |||
a92e63720c | |||
8d121c086e | |||
b538e454aa | |||
60d09d5c6f | |||
266159fe41 | |||
458decb12d | |||
635c2e5513 | |||
adc4223617 | |||
326ae27ad1 | |||
6d33d19816 | |||
734efb391a | |||
cba864507f | |||
3432b233ff | |||
ef9bfe811b | |||
949e1de9cd | |||
57c52cf7ae | |||
eaac06546e | |||
9e38d08c0b | |||
47db28617a | |||
de8c4a55cf | |||
08d94e59c7 | |||
e8a6698abd | |||
7348b2b350 | |||
71e5d2bf43 | |||
7188bc9d8d | |||
49180e826e | |||
c6fbbb499f | |||
7b565f0f0e | |||
a7cc637aa2 | |||
824bb93b0a | |||
a99efd27d2 | |||
6266e16644 | |||
edd4fcfcc0 | |||
30dc229477 | |||
d288885a89 | |||
bd19d9d5b4 | |||
a284af2000 | |||
88e8d028b4 | |||
8cab2b9d87 | |||
9b62a543f6 | |||
5a797e1170 | |||
14f8858d3d | |||
62f7642de9 | |||
77475b57b0 | |||
98048a178e | |||
26eb9bf300 | |||
d4ff4a3e0d | |||
488e143e1f | |||
4d5c5ef404 | |||
628742c2f3 | |||
2492a42151 | |||
4a0b6e99d4 | |||
65946fe254 | |||
3286b5c05e | |||
ac8e02604d | |||
dd296e4408 | |||
1be877132c | |||
7e79fd9721 | |||
5d78aecabb | |||
9fbc894fdb | |||
4930e666ac | |||
e5e337a0a5 | |||
704e604b8f | |||
b5d3cdc9b1 | |||
b8b6b913a9 | |||
f461a40a6d | |||
ddf71ffdac | |||
5dd57204dd | |||
9a622a5e44 | |||
42e79ce921 | |||
cdde6ad36e | |||
78769ecde2 | |||
430f6dbb1a | |||
1c01246886 | |||
1f3a0f1ba7 | |||
5622b39ed0 | |||
5e36364145 | |||
75b0b6dcdf | |||
8e5b84ccca | |||
a3b96e1abf | |||
9492a6729e | |||
4ecf9795ee | |||
ada13e21d3 | |||
a1721268a2 | |||
6a876dda49 | |||
6c02e7ed2b | |||
09b8a86d8f | |||
d718447e7a | |||
57cb6b3616 | |||
00e6de2e15 | |||
64e49f6e03 | |||
35933853a4 | |||
d64fa4a7a7 | |||
2857eb3787 | |||
7c9e3c4e57 | |||
5cb124f185 | |||
7489e3851e | |||
d323c40c0a | |||
03a78aaa0c | |||
0e31c1d985 | |||
bab30a06c7 | |||
c7d1c6ed54 | |||
7219b8e999 | |||
d9de5080e5 | |||
649a5d0e88 | |||
8323f6b82c | |||
d16f2b9e2f | |||
a43dbdd1b0 | |||
8f9945b4b9 | |||
d60ec4bf59 | |||
e8a4c859e6 | |||
359c19652b | |||
7ff0e309e5 | |||
b2b7bfe32a | |||
96e978a8e7 | |||
ccbd4497ca | |||
d49035f361 | |||
d289ccecf5 | |||
65d1268082 | |||
d1c798c695 | |||
33ff0cd7cc | |||
6ddba307e6 | |||
f720cbac0c | |||
66e05677e4 | |||
810cafe09b | |||
28e9cd5196 | |||
cc954cc74c | |||
6731e46386 | |||
b608a192c0 | |||
a18e9c50c4 | |||
d836b95c5d | |||
66e9b9dd8b | |||
6c3aaf8a77 | |||
31aac35d2c | |||
f1b680df4a | |||
4e426d75e0 | |||
d4f464bf89 | |||
d90a5d628f | |||
7775f5da4f | |||
f15204c370 | |||
fdea83cdfd | |||
f6be5d6db0 | |||
bc27393e62 | |||
71ebf7a62e | |||
24e502712f | |||
749836b1fe | |||
0b055ac17d | |||
09db03ba02 | |||
9c4428810c | |||
3e559f18d9 | |||
159da088a9 | |||
942152dd8c | |||
c001bb3f9d | |||
3c971e05dd | |||
f4ceaaa31b | |||
e6e03d4cfd | |||
0556abd4ee | |||
3a3df80c2c | |||
ac78ba48d9 | |||
6ed692325f | |||
bf044080e8 | |||
22ffaacc5c | |||
91f4c3699c | |||
07abd640fd | |||
f34d2bb2fe | |||
642861f3be | |||
fcd4228b56 | |||
c03e615483 | |||
fed51f354a | |||
29bc19aa12 | |||
0620de5e62 | |||
672287bbcf | |||
d98a056469 | |||
352f640756 | |||
3f5eecb2e0 | |||
b5f8397936 | |||
9a63919925 | |||
e1a95c2eee | |||
b7e1481621 | |||
3deb50e460 | |||
6e733dab0c | |||
4a1c866117 | |||
9c3b3ffb08 | |||
7e5edd1fa6 | |||
48443e9e95 | |||
152eacc22d | |||
a6d2729556 | |||
3f67a10720 | |||
c0ea85025b | |||
b44f90b87f | |||
498e7246a0 | |||
8b1a67760c | |||
381d0ead1a | |||
d1f77ebb10 | |||
6223967bca | |||
e2c42ff73b | |||
980ee303c8 | |||
2967444678 | |||
35cad4e916 | |||
ccc4d61fd3 | |||
aac9b101d8 | |||
8d9eeda6e5 | |||
5eaee49e1e | |||
68fd331a9c | |||
0bb246785b |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1 +1,3 @@
|
||||
github: ['paullouisageneau']
|
||||
custom: ['https://paypal.me/paullouisageneau']
|
||||
|
||||
|
6
.github/workflows/build-gnutls.yml
vendored
6
.github/workflows/build-gnutls.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev libsrtp2-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
@ -30,7 +30,7 @@ jobs:
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
|
18
.github/workflows/build-nice.yml
vendored
18
.github/workflows/build-nice.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build-linux:
|
||||
build-media:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -16,7 +16,21 @@ jobs:
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_NICE=1
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_NICE=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
run: ./build/tests
|
||||
build-no-media:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev libnice-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DUSE_NICE=1 -DNO_MEDIA=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
|
11
.github/workflows/build-openssl.yml
vendored
11
.github/workflows/build-openssl.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libssl-dev
|
||||
run: sudo apt update && sudo apt install libssl-dev libsrtp2-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=0
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
@ -26,11 +26,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: brew reinstall openssl@1.1
|
||||
run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew reinstall openssl@1.1
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=0
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||
env:
|
||||
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
||||
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
||||
@ -48,10 +48,11 @@ jobs:
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -G "NMake Makefiles" -DUSE_GNUTLS=0
|
||||
run: cmake -B build -G "NMake Makefiles" -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1
|
||||
- name: nmake
|
||||
run: |
|
||||
cd build
|
||||
set CL=/MP
|
||||
nmake
|
||||
- name: test
|
||||
run: build/tests.exe
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,6 +5,7 @@ node_modules/
|
||||
*.a
|
||||
*.so
|
||||
compile_commands.json
|
||||
tests
|
||||
/tests
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -10,3 +10,6 @@
|
||||
[submodule "deps/json"]
|
||||
path = deps/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "deps/libsrtp"]
|
||||
path = deps/libsrtp
|
||||
url = https://github.com/cisco/libsrtp.git
|
||||
|
249
CMakeLists.txt
249
CMakeLists.txt
@ -1,15 +1,20 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
project(libdatachannel
|
||||
DESCRIPTION "WebRTC Data Channels Library"
|
||||
VERSION 0.7.2
|
||||
VERSION 0.11.4
|
||||
LANGUAGES CXX)
|
||||
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
|
||||
|
||||
# Options
|
||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||
option(USE_NICE "Use libnice instead of libjuice" OFF)
|
||||
option(USE_SRTP "Enable SRTP for media support" OFF)
|
||||
option(USE_SYSTEM_SRTP "Use system libSRTP" OFF)
|
||||
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
||||
option(NO_MEDIA "Disable media transport support" OFF)
|
||||
option(NO_EXAMPLES "Disable examples" OFF)
|
||||
option(NO_TESTS "Disable tests build" OFF)
|
||||
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||
option(RSA_KEY_BITS_2048 "Use 2048-bit RSA key instead of 3072-bit" OFF)
|
||||
option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
|
||||
|
||||
if(USE_NICE)
|
||||
option(USE_JUICE "Use libjuice" OFF)
|
||||
@ -18,17 +23,21 @@ else()
|
||||
endif()
|
||||
|
||||
if(USE_GNUTLS)
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
||||
option(USE_NETTLE "Use Nettle in libjuice" ON)
|
||||
else()
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
|
||||
option(USE_NETTLE "Use Nettle in libjuice" OFF)
|
||||
if(NOT USE_SYSTEM_SRTP)
|
||||
option(ENABLE_OPENSSL "Enable OpenSSL crypto engine for SRTP" ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||
set(BUILD_SHARED_LIBS OFF) # to force usrsctp to be built static
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||
if (MSVC)
|
||||
if(MSVC)
|
||||
add_definitions(-DNOMINMAX)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
||||
@ -47,12 +56,41 @@ set(LIBDATACHANNEL_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/init.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/log.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/message.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.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/tls.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/track.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtppacketizationconfig.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpsrreporter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtppacketizer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/opusrtppacketizer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/opuspacketizationhandler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtppacketizer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/h264packetizationhandler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/mediachainablehandler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/mediahandlerelement.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/mediahandlerrootelement.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpnackresponder.cpp
|
||||
)
|
||||
|
||||
set(LIBDATACHANNEL_PRIVATE_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlssrtptransport.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/logcounter.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/transport.hpp
|
||||
)
|
||||
|
||||
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
||||
@ -64,6 +102,14 @@ set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/wstransport.cpp
|
||||
)
|
||||
|
||||
set(LIBDATACHANNEL_WEBSOCKET_PRIVATE_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/base64.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/tcptransport.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/tlstransport.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/verifiedtlstransport.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/wstransport.hpp
|
||||
)
|
||||
|
||||
set(LIBDATACHANNEL_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp
|
||||
@ -71,6 +117,8 @@ set(LIBDATACHANNEL_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandler.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpreceivingsession.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/include.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/init.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/log.hpp
|
||||
@ -80,17 +128,52 @@ set(LIBDATACHANNEL_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizationconfig.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpsrreporter.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizer.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/opusrtppacketizer.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/opuspacketizationhandler.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtppacketizer.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264packetizationhandler.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediachainablehandler.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandlerelement.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandlerrootelement.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpnackresponder.hpp
|
||||
)
|
||||
|
||||
set(TESTS_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/turn_connectivity.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/track.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_connectivity.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi_track.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/websocket.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(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
@ -99,32 +182,40 @@ set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
||||
add_subdirectory(deps/plog)
|
||||
|
||||
option(sctp_build_programs 0)
|
||||
option(sctp_build_shared_lib 0)
|
||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||
if (MSYS OR MINGW)
|
||||
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||
target_compile_definitions(usrsctp-static PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||
endif()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
|
||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
|
||||
endif()
|
||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
||||
|
||||
if (NO_WEBSOCKET)
|
||||
add_library(datachannel SHARED
|
||||
${LIBDATACHANNEL_SOURCES})
|
||||
${LIBDATACHANNEL_SOURCES}
|
||||
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||
${LIBDATACHANNEL_HEADERS})
|
||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
||||
${LIBDATACHANNEL_SOURCES})
|
||||
${LIBDATACHANNEL_SOURCES}
|
||||
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||
${LIBDATACHANNEL_HEADERS})
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
||||
else()
|
||||
add_library(datachannel SHARED
|
||||
${LIBDATACHANNEL_SOURCES}
|
||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
||||
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES}
|
||||
${LIBDATACHANNEL_WEBSOCKET_PRIVATE_HEADERS}
|
||||
${LIBDATACHANNEL_HEADERS})
|
||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
||||
${LIBDATACHANNEL_SOURCES}
|
||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
||||
${LIBDATACHANNEL_PRIVATE_HEADERS}
|
||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES}
|
||||
${LIBDATACHANNEL_WEBSOCKET_PRIVATE_HEADERS}
|
||||
${LIBDATACHANNEL_HEADERS})
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
||||
endif()
|
||||
@ -140,35 +231,45 @@ target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includ
|
||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_link_libraries(datachannel PUBLIC Threads::Threads plog::plog)
|
||||
target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic)
|
||||
target_link_libraries(datachannel PRIVATE Usrsctp::Usrsctp)
|
||||
|
||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
|
||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
|
||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
|
||||
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
||||
target_link_libraries(datachannel PUBLIC ws2_32) # winsock2
|
||||
target_link_libraries(datachannel-static PUBLIC ws2_32) # winsock2
|
||||
endif()
|
||||
|
||||
if(USE_SRTP)
|
||||
find_package(SRTP REQUIRED)
|
||||
if(NOT TARGET SRTP::SRTP)
|
||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||
set_target_properties(SRTP::SRTP PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
||||
endif()
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
||||
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
||||
else()
|
||||
if(NO_MEDIA)
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
|
||||
else()
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
if(USE_SYSTEM_SRTP)
|
||||
find_package(SRTP REQUIRED)
|
||||
if(NOT TARGET SRTP::SRTP)
|
||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||
set_target_properties(SRTP::SRTP PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
||||
endif()
|
||||
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=1)
|
||||
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=1)
|
||||
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
||||
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
||||
else()
|
||||
add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=0)
|
||||
target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=0)
|
||||
target_link_libraries(datachannel PRIVATE srtp2)
|
||||
target_link_libraries(datachannel-static PRIVATE srtp2)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (USE_GNUTLS)
|
||||
@ -207,44 +308,78 @@ else()
|
||||
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||
endif()
|
||||
|
||||
if(RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel PUBLIC RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel-static PUBLIC RSA_KEY_BITS_2048)
|
||||
endif()
|
||||
|
||||
if(CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel PUBLIC CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel-static PUBLIC CAPI_STDCALL)
|
||||
endif()
|
||||
|
||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||
|
||||
install(TARGETS datachannel LIBRARY DESTINATION lib)
|
||||
install(FILES ${LIBDATACHANNEL_HEADERS} DESTINATION include/rtc)
|
||||
|
||||
# Tests
|
||||
add_executable(datachannel-tests ${TESTS_SOURCES})
|
||||
set_target_properties(datachannel-tests PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel-tests datachannel-static) # DLL exports only the C API
|
||||
else()
|
||||
target_link_libraries(datachannel-tests datachannel)
|
||||
if(NOT MSVC)
|
||||
target_compile_options(datachannel PRIVATE -Wall -Wextra)
|
||||
target_compile_options(datachannel-static PRIVATE -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
# Benchmark
|
||||
add_executable(datachannel-benchmark test/benchmark.cpp)
|
||||
set_target_properties(datachannel-benchmark PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
||||
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
|
||||
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel-benchmark datachannel-static) # DLL exports only the C API
|
||||
else()
|
||||
if(WARNINGS_AS_ERRORS)
|
||||
if(MSVC)
|
||||
target_compile_options(datachannel PRIVATE /WX)
|
||||
target_compile_options(datachannel-static PRIVATE /WX)
|
||||
else()
|
||||
target_compile_options(datachannel PRIVATE -Werror)
|
||||
target_compile_options(datachannel-static PRIVATE -Werror)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# 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})
|
||||
endif()
|
||||
set_target_properties(datachannel-tests PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_link_libraries(datachannel-tests datachannel)
|
||||
|
||||
# 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)
|
||||
endif()
|
||||
set_target_properties(datachannel-benchmark PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
||||
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
|
||||
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_link_libraries(datachannel-benchmark datachannel)
|
||||
endif()
|
||||
|
||||
# Examples
|
||||
if(NOT NO_EXAMPLES)
|
||||
if(NOT NO_EXAMPLES AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
add_subdirectory(deps/json)
|
||||
add_subdirectory(examples/client)
|
||||
if(NOT NO_MEDIA)
|
||||
add_subdirectory(examples/media)
|
||||
add_subdirectory(examples/sfu-media)
|
||||
add_subdirectory(examples/streamer)
|
||||
endif()
|
||||
add_subdirectory(examples/copy-paste)
|
||||
add_subdirectory(examples/copy-paste-capi)
|
||||
endif()
|
||||
|
8
Jamfile
8
Jamfile
@ -15,9 +15,9 @@ lib libdatachannel
|
||||
: # requirements
|
||||
<cxxstd>17
|
||||
<include>./include/rtc
|
||||
<define>USE_NICE=0
|
||||
<define>RTC_ENABLE_MEDIA=0
|
||||
<define>RTC_ENABLE_WEBSOCKET=0
|
||||
<define>USE_NICE=0
|
||||
<toolset>msvc:<define>WIN32_LEAN_AND_MEAN
|
||||
<toolset>msvc:<define>NOMINMAX
|
||||
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS
|
||||
@ -32,6 +32,8 @@ lib libdatachannel
|
||||
<link>static
|
||||
: # usage requirements
|
||||
<include>./include
|
||||
<define>RTC_ENABLE_MEDIA=0
|
||||
<define>RTC_ENABLE_WEBSOCKET=0
|
||||
<library>/libdatachannel//plog
|
||||
<toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||
<toolset>clang:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||
@ -94,7 +96,7 @@ rule make_libusrsctp ( targets * : sources * : properties * )
|
||||
}
|
||||
actions make_libusrsctp
|
||||
{
|
||||
(cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC -Wno-unknown-warning-option -Wno-format-truncation" .. && make -j2 usrsctp-static)
|
||||
(cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC -Wno-unknown-warning-option -Wno-format-truncation" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 .. && make -j2 usrsctp)
|
||||
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
|
||||
}
|
||||
rule make_libusrsctp_msvc ( targets * : sources * : properties * )
|
||||
@ -109,7 +111,7 @@ actions make_libusrsctp_msvc
|
||||
cd $(CWD)/deps/usrsctp
|
||||
mkdir $(BUILD_DIR)
|
||||
cd $(BUILD_DIR)
|
||||
cmake -G "Visual Studio 16 2019" ..
|
||||
cmake -G "Visual Studio 16 2019" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 ..
|
||||
msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
|
||||
cd %OLDD%
|
||||
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<)
|
||||
|
29
Makefile
29
Makefile
@ -10,6 +10,7 @@ LDFLAGS=-pthread
|
||||
LIBS=
|
||||
LOCALLIBS=libusrsctp.a
|
||||
USRSCTP_DIR=deps/usrsctp
|
||||
SRTP_DIR=deps/libsrtp
|
||||
JUICE_DIR=deps/libjuice
|
||||
PLOG_DIR=deps/plog
|
||||
|
||||
@ -38,15 +39,22 @@ ifneq ($(USE_GNUTLS), 0)
|
||||
endif
|
||||
endif
|
||||
|
||||
USE_SRTP ?= 0
|
||||
ifneq ($(USE_SRTP), 0)
|
||||
NO_MEDIA ?= 0
|
||||
USE_SYSTEM_SRTP ?= 0
|
||||
ifeq ($(NO_MEDIA), 0)
|
||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=1
|
||||
ifneq ($(USE_SYSTEM_SRTP), 0)
|
||||
CPPFLAGS+=-DRTC_SYSTEM_SRTP=1
|
||||
LIBS+=srtp
|
||||
else
|
||||
CPPFLAGS+=-DRTC_SYSTEM_SRTP=0
|
||||
INCLUDES+=-I$(SRTP_DIR)/include
|
||||
LOCALLIBS+=libsrtp2.a
|
||||
endif
|
||||
else
|
||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=0
|
||||
endif
|
||||
|
||||
|
||||
NO_WEBSOCKET ?= 0
|
||||
ifeq ($(NO_WEBSOCKET), 0)
|
||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=1
|
||||
@ -54,8 +62,8 @@ else
|
||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=0
|
||||
endif
|
||||
|
||||
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
|
||||
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
||||
INCLUDES+=$(if $(LIBS),$(shell pkg-config --cflags $(LIBS)),)
|
||||
LDLIBS+=$(LOCALLIBS) $(if $(LIBS),$(shell pkg-config --libs $(LIBS)),)
|
||||
|
||||
SRCS=$(shell printf "%s " src/*.cpp)
|
||||
OBJS=$(subst .cpp,.o,$(SRCS))
|
||||
@ -73,7 +81,7 @@ test/%.o: test/%.cpp
|
||||
|
||||
-include $(subst .cpp,.d,$(SRCS))
|
||||
|
||||
$(NAME).a: $(OBJS)
|
||||
$(NAME).a: $(LOCALLIBS) $(OBJS)
|
||||
$(AR) crf $@ $(OBJS)
|
||||
|
||||
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
||||
@ -97,15 +105,22 @@ dist-clean: clean
|
||||
-$(RM) src/*~
|
||||
-$(RM) test/*~
|
||||
-cd $(USRSCTP_DIR) && make clean
|
||||
-cd $(SRTP_DIR) && make clean
|
||||
-cd $(JUICE_DIR) && make clean
|
||||
|
||||
libusrsctp.a:
|
||||
cd $(USRSCTP_DIR) && \
|
||||
./bootstrap && \
|
||||
./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS) -Wno-error=format-truncation" && \
|
||||
./configure --enable-static --disable-debug CFLAGS="-fPIC" && \
|
||||
make
|
||||
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
||||
|
||||
libsrtp2.a:
|
||||
cd $(SRTP_DIR) && \
|
||||
./configure && \
|
||||
make
|
||||
cp $(SRTP_DIR)/libsrtp2.a .
|
||||
|
||||
libjuice.a:
|
||||
ifneq ($(USE_GNUTLS), 0)
|
||||
cd $(JUICE_DIR) && make USE_NETTLE=1
|
||||
|
61
README.md
61
README.md
@ -1,6 +1,6 @@
|
||||
# libdatachannel - C/C++ WebRTC Data Channels
|
||||
|
||||
libdatachannel is a standalone implementation of WebRTC Data Channels and WebSockets in C++17 with C bindings for POSIX platforms (including Linux and Apple macOS) and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. The interface consists of simplified versions of the JavaScript WebRTC and WebSocket APIs present in browsers, in order to ease the design of cross-environment applications.
|
||||
libdatachannel is a standalone implementation of WebRTC Data Channels, WebRTC Media Transport, and WebSockets in C++17 with C bindings for POSIX platforms (including GNU/Linux, Android, and Apple macOS) and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. The interface consists of simplified versions of the JavaScript WebRTC and WebSocket APIs present in browsers, in order to ease the design of cross-environment applications.
|
||||
It can be compiled with multiple backends:
|
||||
- The security layer can be provided through [OpenSSL](https://www.openssl.org/) or [GnuTLS](https://www.gnutls.org/).
|
||||
- The connectivity for WebRTC can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice).
|
||||
@ -13,22 +13,25 @@ Licensed under LGPLv2, see [LICENSE](https://github.com/paullouisageneau/libdata
|
||||
|
||||
The library aims at implementing the following communication protocols:
|
||||
|
||||
### WebRTC Data Channel
|
||||
### WebRTC Data Channels and Media Transport
|
||||
|
||||
The WebRTC stack has been tested to be compatible with Firefox and Chromium.
|
||||
|
||||
Protocol stack:
|
||||
- SCTP-based Data Channels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13))
|
||||
- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26))
|
||||
- DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261))
|
||||
- ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)) with STUN ([RFC5389](https://tools.ietf.org/html/rfc5389))
|
||||
- ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)) with STUN ([RFC8489](https://tools.ietf.org/html/rfc8489)) and its extension TURN ([RFC8656](https://tools.ietf.org/html/rfc8656))
|
||||
|
||||
Features:
|
||||
- Full IPv6 support
|
||||
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
|
||||
- JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26))
|
||||
- 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 media transport ([RFC3711](https://tools.ietf.org/html/rfc3711)) with [libSRTP](https://github.com/cisco/libsrtp)
|
||||
- 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)).
|
||||
|
||||
### WebSocket
|
||||
|
||||
@ -50,10 +53,11 @@ Dependencies:
|
||||
Submodules:
|
||||
- libjuice: https://github.com/paullouisageneau/libjuice
|
||||
- usrsctp: https://github.com/sctplab/usrsctp
|
||||
- libsrtp: https://github.com/cisco/libsrtp
|
||||
|
||||
Optional dependencies:
|
||||
- libnice: https://nice.freedesktop.org/ (only if selected as ICE backend instead of libjuice)
|
||||
- libSRTP: https://github.com/cisco/libsrtp (only necessary for supporting media transport)
|
||||
- libnice: https://nice.freedesktop.org/ (if selected as ICE backend instead of libjuice)
|
||||
- libsrtp: https://github.com/cisco/libsrtp (if selected instead of the submodule)
|
||||
|
||||
## Building
|
||||
|
||||
@ -69,7 +73,7 @@ $ git submodule update --init --recursive
|
||||
|
||||
The CMake library targets `libdatachannel` and `libdatachannel-static` respectively correspond to the shared and static libraries. The default target will build tests and examples. The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
||||
|
||||
On Windows, the DLL resulting from the shared library build only exposes the C API, use the static library for the C++ API.
|
||||
If you only need Data Channels, the option `NO_MEDIA` allows to make the library lighter by removing media support. Similarly, `NO_WEBSOCKET` removes WebSocket support.
|
||||
|
||||
#### POSIX-compliant operating systems (including Linux and Apple macOS)
|
||||
```bash
|
||||
@ -78,6 +82,29 @@ $ cd build
|
||||
$ make -j2
|
||||
```
|
||||
|
||||
#### Apple macOS with XCode project
|
||||
|
||||
```bash
|
||||
$ cmake -B "$BUILD_DIR" -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode
|
||||
```
|
||||
|
||||
Xcode project is generated in *build/* directory.
|
||||
|
||||
##### Solving **Could NOT find OpenSSL** error
|
||||
|
||||
You need to add OpenSSL root directory if your build fails with the following message:
|
||||
|
||||
```
|
||||
Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the
|
||||
system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY
|
||||
OPENSSL_INCLUDE_DIR)
|
||||
```
|
||||
|
||||
for example:
|
||||
```bash
|
||||
$ cmake -B build -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl\@1.1/1.1.1h/
|
||||
```
|
||||
|
||||
#### Microsoft Windows with MinGW cross-compilation
|
||||
```bash
|
||||
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
|
||||
@ -96,6 +123,8 @@ $ nmake
|
||||
|
||||
The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
|
||||
|
||||
If you only need Data Channels, the option `NO_MEDIA` removes media support. Similarly, `NO_WEBSOCKET` removes WebSocket support.
|
||||
|
||||
```bash
|
||||
$ make USE_GNUTLS=1 USE_NICE=0
|
||||
```
|
||||
@ -104,7 +133,7 @@ $ make USE_GNUTLS=1 USE_NICE=0
|
||||
|
||||
See [examples](https://github.com/paullouisageneau/libdatachannel/blob/master/examples/) for a complete usage example with signaling server (under GPLv2).
|
||||
|
||||
Additionnaly, you might want to have a look at the [C API](https://github.com/paullouisageneau/libdatachannel/blob/dev/include/rtc/rtc.h).
|
||||
Additionnaly, you might want to have a look at the [C API](https://github.com/paullouisageneau/libdatachannel/blob/master/include/rtc/rtc.h).
|
||||
|
||||
### Signal a PeerConnection
|
||||
|
||||
@ -118,12 +147,12 @@ config.iceServers.emplace_back("mystunserver.org:3478");
|
||||
|
||||
auto pc = make_shared<rtc::PeerConnection>(config);
|
||||
|
||||
pc->onLocalDescription([](const rtc::Description &sdp) {
|
||||
pc->onLocalDescription([](rtc::Description sdp) {
|
||||
// Send the SDP to the remote peer
|
||||
MY_SEND_DESCRIPTION_TO_REMOTE(string(sdp));
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const rtc::Candidate &candidate) {
|
||||
pc->onLocalCandidate([](rtc::Candidate candidate) {
|
||||
// Send the candidate to the remote peer
|
||||
MY_SEND_CANDIDATE_TO_REMOTE(candidate.candidate(), candidate.mid());
|
||||
});
|
||||
@ -159,7 +188,7 @@ dc->onOpen([]() {
|
||||
cout << "Open" << endl;
|
||||
});
|
||||
|
||||
dc->onMessage([](const variant<binary, string> &message) {
|
||||
dc->onMessage([](variant<binary, string> message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Received: " << get<string>(message) << endl;
|
||||
}
|
||||
@ -186,7 +215,7 @@ ws->onOpen([]() {
|
||||
cout << "WebSocket open" << endl;
|
||||
});
|
||||
|
||||
ws->onMessage([](const variant<binary, string> &message) {
|
||||
ws->onMessage([](variant<binary, string> message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "WebSocket received: " << get<string>(message) << endl;
|
||||
}
|
||||
@ -197,6 +226,8 @@ ws->open("wss://my.websocket/service");
|
||||
```
|
||||
|
||||
## External resources
|
||||
- Rust wrappers for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
||||
- WebAssembly wrappers compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
|
||||
- Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
||||
- Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
|
||||
- Unity wrapper for Windows 10 and Hololens: [datachannel-unity](https://github.com/hanseuljun/datachannel-unity)
|
||||
- WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
|
||||
|
||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: da7bee0e00...7a9f10e4f7
1
deps/libsrtp
vendored
Submodule
1
deps/libsrtp
vendored
Submodule
Submodule deps/libsrtp added at f379f48412
2
deps/usrsctp
vendored
2
deps/usrsctp
vendored
Submodule deps/usrsctp updated: ffed0925f2...07f871bda2
@ -7,6 +7,9 @@ This directory contains different WebRTC clients and compatible WebSocket + JSON
|
||||
- [signaling-server-python](signaling-server-python) contains a similar signaling server in Python
|
||||
- [signaling-server-rust](signaling-server-rust) contains a similar signaling server in Rust (see [lerouxrgd/datachannel-rs](https://github.com/lerouxrgd/datachannel-rs) for Rust wrappers)
|
||||
|
||||
- [media](media) is a copy/paste demo to send the webcam from your browser into gstreamer.
|
||||
- [streamer](streamer) streams h264 and opus samples to web browsers (signaling-server-python is required).
|
||||
|
||||
Additionally, it contains two debugging tools for libdatachannel with copy-pasting as signaling:
|
||||
- [copy-paste](copy-paste) using the C++ API
|
||||
- [copy-paste-capi](copy-paste-capi) using the C API
|
||||
|
@ -3,15 +3,22 @@ if(POLICY CMP0079)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
endif()
|
||||
|
||||
add_executable(datachannel-client main.cpp)
|
||||
if(WIN32)
|
||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
|
||||
target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
|
||||
else()
|
||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
|
||||
endif()
|
||||
|
||||
set_target_properties(datachannel-client PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME client)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel-client datachannel-static) # DLL exports only the C API
|
||||
else()
|
||||
target_link_libraries(datachannel-client datachannel)
|
||||
endif()
|
||||
target_link_libraries(datachannel-client datachannel nlohmann_json)
|
||||
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET datachannel-client POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||
$<TARGET_FILE_DIR:datachannel-client>
|
||||
)
|
||||
endif()
|
||||
|
513
examples/client/getopt.cpp
Normal file
513
examples/client/getopt.cpp
Normal file
@ -0,0 +1,513 @@
|
||||
/* Getopt for Microsoft C
|
||||
This code is a modification of the Free Software Foundation, Inc.
|
||||
Getopt library for parsing command line argument the purpose was
|
||||
to provide a Microsoft Visual C friendly derivative. This code
|
||||
provides functionality for both Unicode and Multibyte builds.
|
||||
|
||||
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
Version: 1.0
|
||||
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||
and POSIXLY_CORRECT environment flag
|
||||
License: LGPL
|
||||
|
||||
Revisions:
|
||||
|
||||
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include "getopt.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define _GETOPT_THROW throw()
|
||||
#else
|
||||
#define _GETOPT_THROW
|
||||
#endif
|
||||
|
||||
int optind = 1;
|
||||
int opterr = 1;
|
||||
int optopt = '?';
|
||||
enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER };
|
||||
|
||||
static struct _getopt_data_a
|
||||
{
|
||||
int optind;
|
||||
int opterr;
|
||||
int optopt;
|
||||
char *optarg;
|
||||
int __initialized;
|
||||
char *__nextchar;
|
||||
enum ENUM_ORDERING __ordering;
|
||||
int __posixly_correct;
|
||||
int __first_nonopt;
|
||||
int __last_nonopt;
|
||||
} getopt_data_a;
|
||||
char *optarg_a;
|
||||
|
||||
static void exchange_a(char **argv, struct _getopt_data_a *d)
|
||||
{
|
||||
int bottom = d->__first_nonopt;
|
||||
int middle = d->__last_nonopt;
|
||||
int top = d->optind;
|
||||
char *tem;
|
||||
while (top > middle && middle > bottom)
|
||||
{
|
||||
if (top - middle > middle - bottom)
|
||||
{
|
||||
int len = middle - bottom;
|
||||
int i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
||||
argv[top - (middle - bottom) + i] = tem;
|
||||
}
|
||||
top -= len;
|
||||
}
|
||||
else
|
||||
{
|
||||
int len = top - middle;
|
||||
int i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[middle + i];
|
||||
argv[middle + i] = tem;
|
||||
}
|
||||
bottom += len;
|
||||
}
|
||||
}
|
||||
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
static const char *_getopt_initialize_a (const char *optstring, struct _getopt_data_a *d, int posixly_correct)
|
||||
{
|
||||
d->__first_nonopt = d->__last_nonopt = d->optind;
|
||||
d->__nextchar = NULL;
|
||||
d->__posixly_correct = posixly_correct | !!getenv("POSIXLY_CORRECT");
|
||||
if (optstring[0] == '-')
|
||||
{
|
||||
d->__ordering = RETURN_IN_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (optstring[0] == '+')
|
||||
{
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (d->__posixly_correct)
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
else
|
||||
d->__ordering = PERMUTE;
|
||||
return optstring;
|
||||
}
|
||||
int _getopt_internal_r_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct)
|
||||
{
|
||||
int print_errors = d->opterr;
|
||||
if (argc < 1)
|
||||
return -1;
|
||||
d->optarg = NULL;
|
||||
if (d->optind == 0 || !d->__initialized)
|
||||
{
|
||||
if (d->optind == 0)
|
||||
d->optind = 1;
|
||||
optstring = _getopt_initialize_a (optstring, d, posixly_correct);
|
||||
d->__initialized = 1;
|
||||
}
|
||||
else if (optstring[0] == '-' || optstring[0] == '+')
|
||||
optstring++;
|
||||
if (optstring[0] == ':')
|
||||
print_errors = 0;
|
||||
if (d->__nextchar == NULL || *d->__nextchar == '\0')
|
||||
{
|
||||
if (d->__last_nonopt > d->optind)
|
||||
d->__last_nonopt = d->optind;
|
||||
if (d->__first_nonopt > d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
if (d->__ordering == PERMUTE)
|
||||
{
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_a ((char **) argv, d);
|
||||
else if (d->__last_nonopt != d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
while (d->optind < argc && (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
|
||||
d->optind++;
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
if (d->optind != argc && !strcmp(argv[d->optind], "--"))
|
||||
{
|
||||
d->optind++;
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_a((char **) argv, d);
|
||||
else if (d->__first_nonopt == d->__last_nonopt)
|
||||
d->__first_nonopt = d->optind;
|
||||
d->__last_nonopt = argc;
|
||||
d->optind = argc;
|
||||
}
|
||||
if (d->optind == argc)
|
||||
{
|
||||
if (d->__first_nonopt != d->__last_nonopt)
|
||||
d->optind = d->__first_nonopt;
|
||||
return -1;
|
||||
}
|
||||
if ((argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
|
||||
{
|
||||
if (d->__ordering == REQUIRE_ORDER)
|
||||
return -1;
|
||||
d->optarg = argv[d->optind++];
|
||||
return 1;
|
||||
}
|
||||
d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == '-'));
|
||||
}
|
||||
if (longopts != NULL && (argv[d->optind][1] == '-' || (long_only && (argv[d->optind][2] || !strchr(optstring, argv[d->optind][1])))))
|
||||
{
|
||||
char *nameend;
|
||||
unsigned int namelen;
|
||||
const struct option_a *p;
|
||||
const struct option_a *pfound = NULL;
|
||||
struct option_list
|
||||
{
|
||||
const struct option_a *p;
|
||||
struct option_list *next;
|
||||
} *ambig_list = NULL;
|
||||
int exact = 0;
|
||||
int indfound = -1;
|
||||
int option_index;
|
||||
for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++);
|
||||
namelen = (unsigned int)(nameend - d->__nextchar);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!strncmp(p->name, d->__nextchar, namelen))
|
||||
{
|
||||
if (namelen == (unsigned int)strlen(p->name))
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
exact = 1;
|
||||
break;
|
||||
}
|
||||
else if (pfound == NULL)
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
||||
{
|
||||
struct option_list *newp = (struct option_list*)alloca(sizeof(*newp));
|
||||
newp->p = p;
|
||||
newp->next = ambig_list;
|
||||
ambig_list = newp;
|
||||
}
|
||||
}
|
||||
if (ambig_list != NULL && !exact)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
struct option_list first;
|
||||
first.p = pfound;
|
||||
first.next = ambig_list;
|
||||
ambig_list = &first;
|
||||
fprintf (stderr, "%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]);
|
||||
do
|
||||
{
|
||||
fprintf (stderr, " '--%s'", ambig_list->p->name);
|
||||
ambig_list = ambig_list->next;
|
||||
}
|
||||
while (ambig_list != NULL);
|
||||
fputc ('\n', stderr);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return '?';
|
||||
}
|
||||
if (pfound != NULL)
|
||||
{
|
||||
option_index = indfound;
|
||||
d->optind++;
|
||||
if (*nameend)
|
||||
{
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
if (argv[d->optind - 1][1] == '-')
|
||||
{
|
||||
fprintf(stderr, "%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name);
|
||||
}
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr,"%s: option '--%s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return optstring[0] == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
if (pfound->flag)
|
||||
{
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
if (!long_only || argv[d->optind][1] == '-' || strchr(optstring, *d->__nextchar) == NULL)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
if (argv[d->optind][1] == '-')
|
||||
{
|
||||
fprintf(stderr, "%s: unrecognized option '--%s'\n",argv[0], d->__nextchar);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar);
|
||||
}
|
||||
}
|
||||
d->__nextchar = (char *)"";
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
{
|
||||
char c = *d->__nextchar++;
|
||||
char *temp = (char*)strchr(optstring, c);
|
||||
if (*d->__nextchar == '\0')
|
||||
++d->optind;
|
||||
if (temp == NULL || c == ':' || c == ';')
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
return '?';
|
||||
}
|
||||
if (temp[0] == 'W' && temp[1] == ';')
|
||||
{
|
||||
char *nameend;
|
||||
const struct option_a *p;
|
||||
const struct option_a *pfound = NULL;
|
||||
int exact = 0;
|
||||
int ambig = 0;
|
||||
int indfound = 0;
|
||||
int option_index;
|
||||
if (longopts == NULL)
|
||||
goto no_longs;
|
||||
if (*d->__nextchar != '\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
c = ':';
|
||||
else
|
||||
c = '?';
|
||||
return c;
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; nameend++);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!strncmp(p->name, d->__nextchar, nameend - d->__nextchar))
|
||||
{
|
||||
if ((unsigned int) (nameend - d->__nextchar) == strlen(p->name))
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
exact = 1;
|
||||
break;
|
||||
}
|
||||
else if (pfound == NULL)
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
||||
ambig = 1;
|
||||
}
|
||||
if (ambig && !exact)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: option '-W %s' is ambiguous\n",argv[0], d->optarg);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optind++;
|
||||
return '?';
|
||||
}
|
||||
if (pfound != NULL)
|
||||
{
|
||||
option_index = indfound;
|
||||
if (*nameend)
|
||||
{
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: option '-W %s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
return optstring[0] == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
if (pfound->flag)
|
||||
{
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
no_longs:
|
||||
d->__nextchar = NULL;
|
||||
return 'W';
|
||||
}
|
||||
if (temp[1] == ':')
|
||||
{
|
||||
if (temp[2] == ':')
|
||||
{
|
||||
if (*d->__nextchar != '\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*d->__nextchar != '\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
c = ':';
|
||||
else
|
||||
c = '?';
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
int _getopt_internal_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct)
|
||||
{
|
||||
int result;
|
||||
getopt_data_a.optind = optind;
|
||||
getopt_data_a.opterr = opterr;
|
||||
result = _getopt_internal_r_a (argc, argv, optstring, longopts,longind, long_only, &getopt_data_a,posixly_correct);
|
||||
optind = getopt_data_a.optind;
|
||||
optarg_a = getopt_data_a.optarg;
|
||||
optopt = getopt_data_a.optopt;
|
||||
return result;
|
||||
}
|
||||
int getopt_a (int argc, char *const *argv, const char *optstring) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_a (argc, argv, optstring, (const struct option_a *) 0, (int *) 0, 0, 0);
|
||||
}
|
||||
int getopt_long_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_a (argc, argv, options, long_options, opt_index, 0, 0);
|
||||
}
|
||||
int getopt_long_only_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_a (argc, argv, options, long_options, opt_index, 1, 0);
|
||||
}
|
||||
int _getopt_long_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d)
|
||||
{
|
||||
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index,0, d, 0);
|
||||
}
|
||||
int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d)
|
||||
{
|
||||
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||
}
|
||||
|
115
examples/client/getopt.h
Normal file
115
examples/client/getopt.h
Normal file
@ -0,0 +1,115 @@
|
||||
/* Getopt for Microsoft C
|
||||
This code is a modification of the Free Software Foundation, Inc.
|
||||
Getopt library for parsing command line argument the purpose was
|
||||
to provide a Microsoft Visual C friendly derivative. This code
|
||||
provides functionality for both Unicode and Multibyte builds.
|
||||
|
||||
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
Version: 1.0
|
||||
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||
and POSIXLY_CORRECT environment flag
|
||||
License: LGPL
|
||||
|
||||
Revisions:
|
||||
|
||||
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
24/10/2020 - Paul-Louis Ageneau - Removed Unicode version
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
#ifndef __GETOPT_H_
|
||||
#define __GETOPT_H_
|
||||
|
||||
#ifdef _GETOPT_API
|
||||
#undef _GETOPT_API
|
||||
#endif
|
||||
|
||||
#if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT)
|
||||
#error "The preprocessor definitions of EXPORTS_GETOPT and STATIC_GETOPT can only be used individually"
|
||||
#elif defined(STATIC_GETOPT)
|
||||
#pragma message("Warning static builds of getopt violate the Lesser GNU Public License")
|
||||
#define _GETOPT_API
|
||||
#elif defined(EXPORTS_GETOPT)
|
||||
#pragma message("Exporting getopt library")
|
||||
#define _GETOPT_API __declspec(dllexport)
|
||||
#else
|
||||
#pragma message("Importing getopt library")
|
||||
#define _GETOPT_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
// Change behavior for C\C++
|
||||
#ifdef __cplusplus
|
||||
#define _BEGIN_EXTERN_C extern "C" {
|
||||
#define _END_EXTERN_C }
|
||||
#define _GETOPT_THROW throw()
|
||||
#else
|
||||
#define _BEGIN_EXTERN_C
|
||||
#define _END_EXTERN_C
|
||||
#define _GETOPT_THROW
|
||||
#endif
|
||||
|
||||
// Standard GNU options
|
||||
#define null_argument 0 /*Argument Null*/
|
||||
#define no_argument 0 /*Argument Switch Only*/
|
||||
#define required_argument 1 /*Argument Required*/
|
||||
#define optional_argument 2 /*Argument Optional*/
|
||||
|
||||
// Shorter Options
|
||||
#define ARG_NULL 0 /*Argument Null*/
|
||||
#define ARG_NONE 0 /*Argument Switch Only*/
|
||||
#define ARG_REQ 1 /*Argument Required*/
|
||||
#define ARG_OPT 2 /*Argument Optional*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
_BEGIN_EXTERN_C
|
||||
|
||||
extern _GETOPT_API int optind;
|
||||
extern _GETOPT_API int opterr;
|
||||
extern _GETOPT_API int optopt;
|
||||
|
||||
struct option_a
|
||||
{
|
||||
const char* name;
|
||||
int has_arg;
|
||||
int *flag;
|
||||
int val;
|
||||
};
|
||||
extern _GETOPT_API char *optarg_a;
|
||||
extern _GETOPT_API int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||
|
||||
_END_EXTERN_C
|
||||
|
||||
#undef _BEGIN_EXTERN_C
|
||||
#undef _END_EXTERN_C
|
||||
#undef _GETOPT_THROW
|
||||
#undef _GETOPT_API
|
||||
|
||||
#define getopt getopt_a
|
||||
#define getopt_long getopt_long_a
|
||||
#define getopt_long_only getopt_long_only_a
|
||||
#define option option_a
|
||||
#define optarg optarg_a
|
||||
|
||||
#endif // __GETOPT_H_
|
@ -5,6 +5,7 @@
|
||||
* Copyright (c) 2020 Will Munn
|
||||
* Copyright (c) 2020 Nico Chatzi
|
||||
* Copyright (c) 2020 Lara Mackey
|
||||
* Copyright (c) 2020 Erik Cota-Robles
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -22,12 +23,16 @@
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
#include "parse_cl.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
@ -48,24 +53,46 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
weak_ptr<WebSocket> wws, string id);
|
||||
string randomId(size_t length);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
rtc::InitLogger(LogLevel::Debug);
|
||||
int main(int argc, char **argv) try {
|
||||
auto params = std::make_unique<Cmdline>(argc, argv);
|
||||
|
||||
rtc::InitLogger(LogLevel::Info);
|
||||
|
||||
Configuration config;
|
||||
config.iceServers.emplace_back("stun:stun.l.google.com:19302"); // change to your STUN server
|
||||
string stunServer = "";
|
||||
if (params->noStun()) {
|
||||
cout << "No STUN server is configured. Only local hosts and public IP addresses supported."
|
||||
<< endl;
|
||||
} else {
|
||||
if (params->stunServer().substr(0, 5).compare("stun:") != 0) {
|
||||
stunServer = "stun:";
|
||||
}
|
||||
stunServer += params->stunServer() + ":" + to_string(params->stunPort());
|
||||
cout << "Stun server is " << stunServer << endl;
|
||||
config.iceServers.emplace_back(stunServer);
|
||||
}
|
||||
|
||||
localId = randomId(4);
|
||||
cout << "The local ID is: " << localId << endl;
|
||||
|
||||
auto ws = make_shared<WebSocket>();
|
||||
|
||||
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
||||
std::promise<void> wsPromise;
|
||||
auto wsFuture = wsPromise.get_future();
|
||||
|
||||
ws->onOpen([&wsPromise]() {
|
||||
cout << "WebSocket connected, signaling ready" << endl;
|
||||
wsPromise.set_value();
|
||||
});
|
||||
|
||||
ws->onError([&wsPromise](string s) {
|
||||
cout << "WebSocket error" << endl;
|
||||
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
|
||||
});
|
||||
|
||||
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
||||
|
||||
ws->onError([](const string &error) { cout << "WebSocket failed: " << error << endl; });
|
||||
|
||||
ws->onMessage([&](const variant<binary, string> &data) {
|
||||
ws->onMessage([&](variant<binary, string> data) {
|
||||
if (!holds_alternative<string>(data))
|
||||
return;
|
||||
|
||||
@ -101,15 +128,17 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
});
|
||||
|
||||
const string url = "ws://localhost:8000/" + localId;
|
||||
string wsPrefix = "";
|
||||
if (params->webSocketServer().substr(0, 5).compare("ws://") != 0) {
|
||||
wsPrefix = "ws://";
|
||||
}
|
||||
const string url = wsPrefix + params->webSocketServer() + ":" +
|
||||
to_string(params->webSocketPort()) + "/" + localId;
|
||||
cout << "Url is " << url << endl;
|
||||
ws->open(url);
|
||||
|
||||
cout << "Waiting for signaling to be connected..." << endl;
|
||||
while (!ws->isOpen()) {
|
||||
if (ws->isClosed())
|
||||
return 1;
|
||||
this_thread::sleep_for(100ms);
|
||||
}
|
||||
wsFuture.get();
|
||||
|
||||
while (true) {
|
||||
string id;
|
||||
@ -137,16 +166,15 @@ int main(int argc, char **argv) {
|
||||
|
||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||
|
||||
dc->onMessage([id](const variant<binary, string> &message) {
|
||||
if (!holds_alternative<string>(message))
|
||||
return;
|
||||
|
||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
|
||||
if (holds_alternative<string>(data))
|
||||
cout << "Message from " << id << " received: " << get<string>(data) << endl;
|
||||
else
|
||||
cout << "Binary message from " << id
|
||||
<< " received, size=" << get<binary>(data).size() << endl;
|
||||
});
|
||||
|
||||
dataChannelMap.emplace(id, dc);
|
||||
|
||||
this_thread::sleep_for(1s);
|
||||
}
|
||||
|
||||
cout << "Cleaning up..." << endl;
|
||||
@ -154,6 +182,12 @@ int main(int argc, char **argv) {
|
||||
dataChannelMap.clear();
|
||||
peerConnectionMap.clear();
|
||||
return 0;
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cout << "Error: " << e.what() << std::endl;
|
||||
dataChannelMap.clear();
|
||||
peerConnectionMap.clear();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create and setup a PeerConnection
|
||||
@ -166,7 +200,7 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
pc->onGatheringStateChange(
|
||||
[](PeerConnection::GatheringState state) { cout << "Gathering State: " << state << endl; });
|
||||
|
||||
pc->onLocalDescription([wws, id](const Description &description) {
|
||||
pc->onLocalDescription([wws, id](Description description) {
|
||||
json message = {
|
||||
{"id", id}, {"type", description.typeString()}, {"description", string(description)}};
|
||||
|
||||
@ -174,7 +208,7 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
ws->send(message.dump());
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([wws, id](const Candidate &candidate) {
|
||||
pc->onLocalCandidate([wws, id](Candidate candidate) {
|
||||
json message = {{"id", id},
|
||||
{"type", "candidate"},
|
||||
{"candidate", string(candidate)},
|
||||
@ -190,11 +224,12 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
|
||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||
|
||||
dc->onMessage([id](const variant<binary, string> &message) {
|
||||
if (!holds_alternative<string>(message))
|
||||
return;
|
||||
|
||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
|
||||
if (holds_alternative<string>(data))
|
||||
cout << "Message from " << id << " received: " << get<string>(data) << endl;
|
||||
else
|
||||
cout << "Binary message from " << id
|
||||
<< " received, size=" << get<binary>(data).size() << endl;
|
||||
});
|
||||
|
||||
dc->send("Hello from " + localId);
|
||||
|
160
examples/client/parse_cl.cpp
Normal file
160
examples/client/parse_cl.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
/******************************************************************************
|
||||
**
|
||||
** parse_cl.cpp
|
||||
**
|
||||
** Thu Aug 6 19:42:25 2020
|
||||
** Linux 5.4.0-42-generic (#46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020) x86_64
|
||||
** cerik@Erik-VBox-Ubuntu (Erik Cota-Robles)
|
||||
**
|
||||
** Copyright (c) 2020 Erik Cota-Robles
|
||||
**
|
||||
** Definition of command line parser class
|
||||
**
|
||||
** Automatically created by genparse v0.9.3
|
||||
**
|
||||
** See http://genparse.sourceforge.net for details and updates
|
||||
**
|
||||
**
|
||||
******************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
#include "getopt.h"
|
||||
#else
|
||||
#include <getopt.h>
|
||||
#endif
|
||||
|
||||
#include "parse_cl.h"
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
**
|
||||
** Cmdline::Cmdline ()
|
||||
**
|
||||
** Constructor method.
|
||||
**
|
||||
**--------------------------------------------------------------------------*/
|
||||
|
||||
Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::string )
|
||||
{
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int c;
|
||||
|
||||
static struct option long_options[] =
|
||||
{
|
||||
{"noStun", no_argument, NULL, 'n'},
|
||||
{"stunServer", required_argument, NULL, 's'},
|
||||
{"stunPort", required_argument, NULL, 't'},
|
||||
{"webSocketServer", required_argument, NULL, 'w'},
|
||||
{"webSocketPort", required_argument, NULL, 'x'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
_program_name += argv[0];
|
||||
|
||||
/* default values */
|
||||
_n = false;
|
||||
_s = "stun.l.google.com";
|
||||
_t = 19302;
|
||||
_w = "localhost";
|
||||
_x = 8000;
|
||||
_h = false;
|
||||
|
||||
optind = 0;
|
||||
while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'n':
|
||||
_n = true;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
_s = optarg;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
_t = atoi (optarg);
|
||||
if (_t < 0)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: t must be >= 0";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
if (_t > 65535)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: t must be <= 65535";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
_w = optarg;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
_x = atoi (optarg);
|
||||
if (_x < 0)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: x must be >= 0";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
if (_x > 65535)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: x must be <= 65535";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
_h = true;
|
||||
this->usage (EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
default:
|
||||
this->usage (EXIT_FAILURE);
|
||||
|
||||
}
|
||||
} /* while */
|
||||
|
||||
_optind = optind;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
**
|
||||
** Cmdline::usage () and version()
|
||||
**
|
||||
** Print out usage (or version) information, then exit.
|
||||
**
|
||||
**--------------------------------------------------------------------------*/
|
||||
|
||||
void Cmdline::usage (int status)
|
||||
{
|
||||
if (status != EXIT_SUCCESS)
|
||||
std::cerr << "Try `" << _program_name << " --help' for more information.\n";
|
||||
else
|
||||
{
|
||||
std::cout << "\
|
||||
usage: " << _program_name << " [ -enstwxhv ] \n\
|
||||
libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\
|
||||
[ -n ] [ --noStun ] (type=FLAG)\n\
|
||||
Do NOT use a stun server (overrides -s and -t).\n\
|
||||
[ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\
|
||||
Stun server URL or IP address.\n\
|
||||
[ -t ] [ --stunPort ] (type=INTEGER, range=0...65535, default=19302)\n\
|
||||
Stun server port.\n\
|
||||
[ -w ] [ --webSocketServer ] (type=STRING, default=localhost)\n\
|
||||
Web socket server URL or IP address.\n\
|
||||
[ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\
|
||||
Web socket server port.\n\
|
||||
[ -h ] [ --help ] (type=FLAG)\n\
|
||||
Display this help and exit.\n";
|
||||
}
|
||||
exit (status);
|
||||
}
|
||||
|
67
examples/client/parse_cl.h
Normal file
67
examples/client/parse_cl.h
Normal file
@ -0,0 +1,67 @@
|
||||
/******************************************************************************
|
||||
**
|
||||
** parse_cl.h
|
||||
**
|
||||
** Thu Aug 6 19:42:25 2020
|
||||
** Linux 5.4.0-42-generic (#46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020) x86_64
|
||||
** cerik@Erik-VBox-Ubuntu (Erik Cota-Robles)
|
||||
**
|
||||
** Copyright (c) 2020 Erik Cota-Robles
|
||||
**
|
||||
** Header file for command line parser class
|
||||
**
|
||||
** Automatically created by genparse v0.9.3
|
||||
**
|
||||
** See http://genparse.sourceforge.net for details and updates
|
||||
**
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef CMDLINE_H
|
||||
#define CMDLINE_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
**
|
||||
** class Cmdline
|
||||
**
|
||||
** command line parser class
|
||||
**
|
||||
**--------------------------------------------------------------------------*/
|
||||
|
||||
class Cmdline
|
||||
{
|
||||
private:
|
||||
/* parameters */
|
||||
bool _n;
|
||||
std::string _s;
|
||||
int _t;
|
||||
std::string _w;
|
||||
int _x;
|
||||
bool _h;
|
||||
|
||||
/* other stuff to keep track of */
|
||||
std::string _program_name;
|
||||
int _optind;
|
||||
|
||||
public:
|
||||
/* constructor and destructor */
|
||||
Cmdline (int, char **); // ISO C++17 not allowed: throw (std::string);
|
||||
~Cmdline (){}
|
||||
|
||||
/* usage function */
|
||||
void usage (int status);
|
||||
|
||||
/* return next (non-option) parameter */
|
||||
int next_param () { return _optind; }
|
||||
|
||||
bool noStun () const { return _n; }
|
||||
std::string stunServer () const { return _s; }
|
||||
int stunPort () const { return _t; }
|
||||
std::string webSocketServer () const { return _w; }
|
||||
int webSocketPort () const { return _x; }
|
||||
bool h () const { return _h; }
|
||||
};
|
||||
|
||||
#endif
|
@ -13,3 +13,15 @@ set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
|
||||
OUTPUT_NAME answerer)
|
||||
target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)
|
||||
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET datachannel-copy-paste-capi-offerer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||
$<TARGET_FILE_DIR:datachannel-copy-paste-capi-offerer>
|
||||
)
|
||||
add_custom_command(TARGET datachannel-copy-paste-capi-answerer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||
$<TARGET_FILE_DIR:datachannel-copy-paste-capi-answerer>
|
||||
)
|
||||
endif()
|
||||
|
@ -34,23 +34,23 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr);
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
static void closedCallback(void *ptr);
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||
static void RTC_API closedCallback(int id, void *ptr);
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
char* state_print(rtcState state);
|
||||
char *state_print(rtcState state);
|
||||
char *rtcGatheringState_print(rtcGatheringState state);
|
||||
|
||||
int all_space(const char *str);
|
||||
@ -66,189 +66,185 @@ int main(int argc, char **argv) {
|
||||
if (!peer) {
|
||||
fprintf(stderr, "Error allocating memory for peer\n");
|
||||
return -1;
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
printf("Peer created\n");
|
||||
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
|
||||
rtcSetUserPointer(peer->dc, NULL);
|
||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||
rtcSetUserPointer(peer->dc, NULL);
|
||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||
|
||||
sleep(1);
|
||||
sleep(1);
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
printf("\n");
|
||||
printf("***********************************************************************************"
|
||||
"****\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
int command = -1;
|
||||
int c;
|
||||
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char*) malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
||||
strcat(sdp, line);
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char *)malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||
strcat(sdp, line);
|
||||
}
|
||||
printf("%s\n", sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char *candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
|
||||
}
|
||||
printf("%s\n",sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
if (getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, NULL);
|
||||
free(candidate);
|
||||
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char* candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if(getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char *message = NULL;
|
||||
size_t message_size = 0;
|
||||
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
if (getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char* message = NULL;
|
||||
size_t message_size = 0;
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "answerer", cand);
|
||||
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
printf("Candidate %s: %s\n", "answerer", cand);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "answerer", state_print(state));
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "answerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
static void RTC_API closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "answerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
||||
}
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "answerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
||||
}
|
||||
}
|
||||
|
||||
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
rtcSetClosedCallback(dc, closedCallback);
|
||||
rtcSetMessageCallback(dc, messageCallback);
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||
}
|
||||
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
rtcSetClosedCallback(dc, closedCallback);
|
||||
rtcSetMessageCallback(dc, messageCallback);
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
char *state_print(rtcState state) {
|
||||
|
@ -34,20 +34,20 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
static void openCallback(void *ptr);
|
||||
static void closedCallback(void *ptr);
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||
static void RTC_API openCallback(int id, void *ptr);
|
||||
static void RTC_API closedCallback(int id, void *ptr);
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr);
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
char *state_print(rtcState state);
|
||||
@ -55,7 +55,7 @@ char *rtcGatheringState_print(rtcGatheringState state);
|
||||
|
||||
int all_space(const char *str);
|
||||
|
||||
int main(int argc, char **argv){
|
||||
int main(int argc, char **argv) {
|
||||
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
||||
|
||||
// Create peer
|
||||
@ -66,192 +66,185 @@ int main(int argc, char **argv){
|
||||
if (!peer) {
|
||||
fprintf(stderr, "Error allocating memory for peer\n");
|
||||
return -1;
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
printf("Peer created\n");
|
||||
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
|
||||
// Since this is the offere, we will create a datachannel
|
||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||
rtcSetOpenCallback(peer->dc, openCallback);
|
||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||
rtcSetMessageCallback(peer->dc, messageCallback);
|
||||
// Since we are the offerer, we will create a datachannel
|
||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||
rtcSetOpenCallback(peer->dc, openCallback);
|
||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||
rtcSetMessageCallback(peer->dc, messageCallback);
|
||||
|
||||
sleep(1);
|
||||
sleep(1);
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
printf("\n");
|
||||
printf("***********************************************************************************"
|
||||
"****\n");
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
int command = -1;
|
||||
int c;
|
||||
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char *)malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char *)realloc(sdp, (strlen(sdp) + 1) + strlen(line) + 1);
|
||||
strcat(sdp, line);
|
||||
}
|
||||
printf("%s\n", sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char *candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
if (getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, NULL);
|
||||
free(candidate);
|
||||
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char*) malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
||||
strcat(sdp, line);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
printf("%s\n",sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char *message = NULL;
|
||||
size_t message_size = 0;
|
||||
if (getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char* candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
if(getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if (!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char* message = NULL;
|
||||
size_t message_size = 0;
|
||||
if(getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||
static void RTC_API descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "offerer", cand);
|
||||
|
||||
static void RTC_API candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
printf("Candidate %s: %s\n", "offerer", cand);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "offerer", state_print(state));
|
||||
static void RTC_API stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "offerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||
static void RTC_API gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
||||
static void RTC_API openCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "offerer", buffer);
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
static void RTC_API closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "offerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "offerer", size);
|
||||
}
|
||||
static void RTC_API messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "offerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "offerer", size);
|
||||
}
|
||||
}
|
||||
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
char *state_print(rtcState state) {
|
||||
|
@ -4,19 +4,23 @@ add_executable(datachannel-copy-paste-offerer offerer.cpp)
|
||||
set_target_properties(datachannel-copy-paste-offerer PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME offerer)
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel-copy-paste-offerer datachannel-static) # DLL exports only the C API
|
||||
else()
|
||||
target_link_libraries(datachannel-copy-paste-offerer datachannel)
|
||||
endif()
|
||||
target_link_libraries(datachannel-copy-paste-offerer datachannel)
|
||||
|
||||
add_executable(datachannel-copy-paste-answerer answerer.cpp)
|
||||
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME answerer)
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel-copy-paste-answerer datachannel-static) # DLL exports only the C API
|
||||
else()
|
||||
target_link_libraries(datachannel-copy-paste-answerer datachannel)
|
||||
endif()
|
||||
target_link_libraries(datachannel-copy-paste-answerer datachannel)
|
||||
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET datachannel-copy-paste-offerer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||
$<TARGET_FILE_DIR:datachannel-copy-paste-offerer>
|
||||
)
|
||||
add_custom_command(TARGET datachannel-copy-paste-answerer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||
$<TARGET_FILE_DIR:datachannel-copy-paste-answerer>
|
||||
)
|
||||
endif()
|
||||
|
@ -36,12 +36,12 @@ int main(int argc, char **argv) {
|
||||
|
||||
auto pc = std::make_shared<PeerConnection>(config);
|
||||
|
||||
pc->onLocalDescription([](const Description &description) {
|
||||
pc->onLocalDescription([](Description description) {
|
||||
cout << "Local Description (Paste this to the other peer):" << endl;
|
||||
cout << string(description) << endl;
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
||||
pc->onLocalCandidate([](Candidate candidate) {
|
||||
cout << "Local Candidate (Paste this to the other peer after the local description):"
|
||||
<< endl;
|
||||
cout << string(candidate) << endl << endl;
|
||||
@ -60,7 +60,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
|
||||
|
||||
dc->onMessage([](const variant<binary, string> &message) {
|
||||
dc->onMessage([](variant<binary, string> message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "[Received message: " << get<string>(message) << "]" << endl;
|
||||
}
|
||||
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
Candidate local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Local: " << local << endl;
|
||||
cout << "Remote: " << remote << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
|
@ -36,12 +36,12 @@ int main(int argc, char **argv) {
|
||||
|
||||
auto pc = std::make_shared<PeerConnection>(config);
|
||||
|
||||
pc->onLocalDescription([](const Description &description) {
|
||||
pc->onLocalDescription([](Description description) {
|
||||
cout << "Local Description (Paste this to the other peer):" << endl;
|
||||
cout << string(description) << endl;
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
||||
pc->onLocalCandidate([](Candidate candidate) {
|
||||
cout << "Local Candidate (Paste this to the other peer after the local description):"
|
||||
<< endl;
|
||||
cout << string(candidate) << endl << endl;
|
||||
@ -60,7 +60,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
|
||||
|
||||
dc->onMessage([](const variant<binary, string> &message) {
|
||||
dc->onMessage([](variant<binary, string> message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "[Received: " << get<string>(message) << "]" << endl;
|
||||
}
|
||||
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
Candidate local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Local: " << local << endl;
|
||||
cout << "Remote: " << remote << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
|
15
examples/media/CMakeLists.txt
Normal file
15
examples/media/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
add_executable(datachannel-media main.cpp)
|
||||
set_target_properties(datachannel-media PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME media)
|
||||
target_link_libraries(datachannel-media datachannel nlohmann_json)
|
||||
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET datachannel-media POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||
$<TARGET_FILE_DIR:datachannel-media>
|
||||
)
|
||||
endif()
|
19
examples/media/README.md
Normal file
19
examples/media/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Example Webcam from Browser to Port 5000
|
||||
This is an example copy/paste demo to send your webcam from your browser and out port 5000 through the demo application.
|
||||
|
||||
## How to use
|
||||
Open main.html in your browser (you must open it either as HTTPS or as a domain of http://localhost).
|
||||
|
||||
Start the application and copy it's offer into the text box of the web page.
|
||||
|
||||
Copy the answer of the webpage back into the application.
|
||||
|
||||
You will now see RTP traffic on `localhost:5000` of the computer that the application is running on.
|
||||
|
||||
Use the following gstreamer demo pipeline to display the traffic
|
||||
(you might need to wave your hand in front of your camera to force an I-frame).
|
||||
|
||||
```
|
||||
$ gst-launch-1.0 udpsrc address=127.0.0.1 port=5000 caps="application/x-rtp" ! queue ! rtph264depay ! video/x-h264,stream-format=byte-stream ! queue ! avdec_h264 ! queue ! autovideosink
|
||||
```
|
||||
|
97
examples/media/main.cpp
Normal file
97
examples/media/main.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 <utility>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
typedef int SOCKET;
|
||||
#endif
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
int main() {
|
||||
try {
|
||||
rtc::InitLogger(rtc::LogLevel::Debug);
|
||||
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 << message << std::endl;
|
||||
}
|
||||
});
|
||||
|
||||
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
sockaddr_in addr;
|
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
addr.sin_port = htons(5000);
|
||||
addr.sin_family = AF_INET;
|
||||
|
||||
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);
|
||||
|
||||
auto session = std::make_shared<rtc::RtcpReceivingSession>();
|
||||
track->setRtcpHandler(session);
|
||||
|
||||
track->onMessage(
|
||||
[session, sock, addr](rtc::binary message) {
|
||||
// This is an RTP packet
|
||||
sendto(sock, reinterpret_cast<const char *>(message.data()), int(message.size()), 0,
|
||||
reinterpret_cast<const struct sockaddr *>(&addr), sizeof(addr));
|
||||
},
|
||||
nullptr);
|
||||
|
||||
pc->setLocalDescription();
|
||||
|
||||
std::cout << "Expect RTP video traffic on localhost:5000" << std::endl;
|
||||
std::cout << "Please copy/paste the answer provided by the browser: " << 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);
|
||||
std::cout << "Press any key to exit." << std::endl;
|
||||
std::cin >> sdp;
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
45
examples/media/main.html
Normal file
45
examples/media/main.html
Normal file
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>libdatachannel media example</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Please enter the offer provided to you by the application: </p>
|
||||
<textarea cols="50" rows="50"></textarea>
|
||||
<button>Submit</button>
|
||||
|
||||
<script>
|
||||
document.querySelector('button').addEventListener('click', async () => {
|
||||
let offer = JSON.parse(document.querySelector('textarea').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('textarea').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
|
||||
document.querySelector('p').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);
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
15
examples/sfu-media/CMakeLists.txt
Normal file
15
examples/sfu-media/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
add_executable(datachannel-sfu-media main.cpp)
|
||||
set_target_properties(datachannel-sfu-media PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME sfu-media)
|
||||
target_link_libraries(datachannel-sfu-media datachannel nlohmann_json)
|
||||
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET datachannel-sfu-media POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||
$<TARGET_FILE_DIR:datachannel-sfu-media>
|
||||
)
|
||||
endif()
|
134
examples/sfu-media/main.cpp
Normal file
134
examples/sfu-media/main.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* libdatachannel client example
|
||||
* Copyright (c) 2020 Staz Modrzynski
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
struct Receiver {
|
||||
std::shared_ptr<rtc::PeerConnection> conn;
|
||||
std::shared_ptr<rtc::Track> track;
|
||||
};
|
||||
int main() {
|
||||
std::vector<std::shared_ptr<Receiver>> receivers;
|
||||
|
||||
try {
|
||||
rtc::InitLogger(rtc::LogLevel::Info);
|
||||
|
||||
auto pc = std::make_shared<rtc::PeerConnection>();
|
||||
pc->onStateChange(
|
||||
[](rtc::PeerConnection::State state) { std::cout << "State: " << state << std::endl; });
|
||||
pc->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
|
||||
std::cout << "Gathering State: " << state << std::endl;
|
||||
if (state == rtc::PeerConnection::GatheringState::Complete) {
|
||||
auto description = pc->localDescription();
|
||||
json message = {{"type", description->typeString()},
|
||||
{"sdp", std::string(description.value())}};
|
||||
std::cout << "Please copy/paste this offer to the SENDER: " << message << std::endl;
|
||||
}
|
||||
});
|
||||
|
||||
rtc::Description::Video media("video", rtc::Description::Direction::RecvOnly);
|
||||
media.addH264Codec(96);
|
||||
media.setBitrate(
|
||||
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
||||
|
||||
auto track = pc->addTrack(media);
|
||||
pc->setLocalDescription();
|
||||
|
||||
auto session = std::make_shared<rtc::RtcpReceivingSession>();
|
||||
track->setRtcpHandler(session);
|
||||
|
||||
const rtc::SSRC targetSSRC = 4;
|
||||
|
||||
track->onMessage(
|
||||
[&receivers, targetSSRC](rtc::binary message) {
|
||||
// This is an RTP packet
|
||||
auto rtp = (rtc::RTP *)message.data();
|
||||
rtp->setSsrc(targetSSRC);
|
||||
for (auto pc : receivers) {
|
||||
if (pc->track != nullptr && pc->track->isOpen()) {
|
||||
pc->track->send(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
nullptr);
|
||||
|
||||
// Set the SENDERS Answer
|
||||
{
|
||||
std::cout << "Please copy/paste the answer provided by the SENDER: " << std::endl;
|
||||
std::string sdp;
|
||||
std::getline(std::cin, sdp);
|
||||
std::cout << "Got answer" << sdp << std::endl;
|
||||
json j = json::parse(sdp);
|
||||
rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
|
||||
pc->setRemoteDescription(answer);
|
||||
}
|
||||
|
||||
// For each receiver
|
||||
while (true) {
|
||||
auto pc = std::make_shared<Receiver>();
|
||||
pc->conn = std::make_shared<rtc::PeerConnection>();
|
||||
pc->conn->onStateChange([](rtc::PeerConnection::State state) {
|
||||
std::cout << "State: " << state << std::endl;
|
||||
});
|
||||
pc->conn->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
|
||||
std::cout << "Gathering State: " << state << std::endl;
|
||||
if (state == rtc::PeerConnection::GatheringState::Complete) {
|
||||
auto description = pc->conn->localDescription();
|
||||
json message = {{"type", description->typeString()},
|
||||
{"sdp", std::string(description.value())}};
|
||||
std::cout << "Please copy/paste this offer to the RECEIVER: " << message
|
||||
<< std::endl;
|
||||
}
|
||||
});
|
||||
rtc::Description::Video media("video", rtc::Description::Direction::SendOnly);
|
||||
media.addH264Codec(96);
|
||||
media.setBitrate(
|
||||
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
||||
|
||||
media.addSSRC(targetSSRC, "video-send");
|
||||
|
||||
pc->track = pc->conn->addTrack(media);
|
||||
pc->conn->setLocalDescription();
|
||||
|
||||
pc->track->onMessage([](rtc::binary var) {}, nullptr);
|
||||
|
||||
std::cout << "Please copy/paste the answer provided by the RECEIVER: " << std::endl;
|
||||
std::string sdp;
|
||||
std::getline(std::cin, sdp);
|
||||
std::cout << "Got answer" << sdp << std::endl;
|
||||
json j = json::parse(sdp);
|
||||
rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
|
||||
pc->conn->setRemoteDescription(answer);
|
||||
|
||||
receivers.push_back(pc);
|
||||
}
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
87
examples/sfu-media/main.html
Normal file
87
examples/sfu-media/main.html
Normal file
@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>libdatachannel media example</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div style="display:inline-block; width:40%;">
|
||||
<h1>SENDER</h1>
|
||||
<p id="send-help">Please enter the offer provided to you by the application: </p>
|
||||
<textarea style="width:100%;" id=send-text rows="50"></textarea>
|
||||
<button id=send-btn>Submit</button>
|
||||
</div>
|
||||
<div style="display:inline-block; width:40%;">
|
||||
<h1>RECEIVER</h1>
|
||||
<p id="recv-help">Please enter the offer provided to you by the application: </p>
|
||||
<textarea id=recv-text style="width:100%;" rows="50"></textarea>
|
||||
<button id=recv-btn>Submit</button>
|
||||
</div>
|
||||
<div id="videos">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
document.querySelector('#send-btn').addEventListener('click', async () => {
|
||||
let offer = JSON.parse(document.querySelector('#send-text').value);
|
||||
rtc = new RTCPeerConnection({
|
||||
// Recommended for libdatachannel
|
||||
bundlePolicy: "max-bundle",
|
||||
});
|
||||
|
||||
rtc.onicegatheringstatechange = (state) => {
|
||||
if (rtc.iceGatheringState === 'complete') {
|
||||
// We only want to provide an answer once all of our candidates have been added to the SDP.
|
||||
let answer = rtc.localDescription;
|
||||
document.querySelector('#send-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
|
||||
document.querySelector('#send-help').value = 'Please paste the answer in the application.';
|
||||
alert('Please paste the answer in the application.');
|
||||
}
|
||||
}
|
||||
await rtc.setRemoteDescription(offer);
|
||||
|
||||
let media = await navigator.mediaDevices.getUserMedia({
|
||||
video: {
|
||||
width: 1280,
|
||||
height: 720
|
||||
}
|
||||
});
|
||||
media.getTracks().forEach(track => rtc.addTrack(track, media));
|
||||
let answer = await rtc.createAnswer();
|
||||
await rtc.setLocalDescription(answer);
|
||||
});
|
||||
|
||||
document.querySelector('#recv-btn').addEventListener('click', async () => {
|
||||
let offer = JSON.parse(document.querySelector('#recv-text').value);
|
||||
rtc = new RTCPeerConnection({
|
||||
// Recommended for libdatachannel
|
||||
bundlePolicy: "max-bundle",
|
||||
});
|
||||
|
||||
rtc.onicegatheringstatechange = (state) => {
|
||||
if (rtc.iceGatheringState === 'complete') {
|
||||
// We only want to provide an answer once all of our candidates have been added to the SDP.
|
||||
let answer = rtc.localDescription;
|
||||
document.querySelector('#recv-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
|
||||
document.querySelector('#recv-help').value = 'Please paste the answer in the application.';
|
||||
alert('Please paste the answer in the application.');
|
||||
}
|
||||
}
|
||||
let trackCount = 0;
|
||||
rtc.ontrack = (ev) => {
|
||||
let thisID = trackCount++;
|
||||
|
||||
document.querySelector("#videos").innerHTML += "<video width=100% height=100% id='video-" + thisID + "'></video>";
|
||||
let tracks = [];
|
||||
rtc.getReceivers().forEach(recv => tracks.push(recv.track));
|
||||
document.querySelector("#video-" + thisID).srcObject = new MediaStream(tracks);
|
||||
document.querySelector("#video-" + thisID).play();
|
||||
};
|
||||
await rtc.setRemoteDescription(offer);
|
||||
let answer = await rtc.createAnswer();
|
||||
await rtc.setLocalDescription(answer);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -63,16 +63,20 @@ async def handle_websocket(websocket, path):
|
||||
print('Client {} disconnected'.format(client_id))
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
|
||||
# Usage: ./server.py [[host:]port] [SSL certificate file]
|
||||
endpoint_or_port = sys.argv[1] if len(sys.argv) > 1 else "8000"
|
||||
ssl_cert = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
|
||||
endpoint = endpoint_or_port if ':' in endpoint_or_port else "127.0.0.1:" + endpoint_or_port
|
||||
|
||||
if ssl_cert:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.load_cert_chain(ssl_cert)
|
||||
else:
|
||||
ssl_context = None
|
||||
|
||||
print('Listening on port {}'.format(port))
|
||||
start_server = websockets.serve(handle_websocket, '127.0.0.1', port, ssl=ssl_context)
|
||||
print('Listening on {}'.format(endpoint))
|
||||
host, port = endpoint.rsplit(':', 1)
|
||||
start_server = websockets.serve(handle_websocket, host, int(port), ssl=ssl_context)
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
asyncio.get_event_loop().run_forever()
|
||||
|
2
examples/signaling-server-rust/Cargo.lock
generated
2
examples/signaling-server-rust/Cargo.lock
generated
@ -348,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
||||
|
||||
[[package]]
|
||||
name = "libdatachannel_signaling_example"
|
||||
name = "libdatachannel_signaling_server_example"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
|
@ -92,7 +92,9 @@ async fn handle(clients: ClientsMap, stream: TcpStream) {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
let service = env::args().nth(1).unwrap_or("8000".to_string());
|
||||
let endpoint = format!("127.0.0.1:{}", service);
|
||||
let endpoint = if service.contains(':') { service } else { format!("127.0.0.1:{}", service) };
|
||||
|
||||
println!("Listening on {}", endpoint);
|
||||
|
||||
let mut listener = TcpListener::bind(endpoint)
|
||||
.await.expect("Listener binding failed");
|
||||
|
75
examples/streamer/ArgParser.cpp
Normal file
75
examples/streamer/ArgParser.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ArgParser.hpp"
|
||||
#include <iostream>
|
||||
|
||||
ArgParser::ArgParser(std::vector<std::pair<std::string, std::string>> options, std::vector<std::pair<std::string, std::string>> flags) {
|
||||
for(auto option: options) {
|
||||
this->options.insert(option.first);
|
||||
this->options.insert(option.second);
|
||||
shortToLongMap.emplace(option.first, option.second);
|
||||
shortToLongMap.emplace(option.second, option.second);
|
||||
}
|
||||
for(auto flag: flags) {
|
||||
this->flags.insert(flag.first);
|
||||
this->flags.insert(flag.second);
|
||||
shortToLongMap.emplace(flag.first, flag.second);
|
||||
shortToLongMap.emplace(flag.second, flag.second);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> ArgParser::toKey(std::string prefixedKey) {
|
||||
if (prefixedKey.find("--") == 0) {
|
||||
return prefixedKey.substr(2, prefixedKey.length());
|
||||
} else if (prefixedKey.find("-") == 0) {
|
||||
return prefixedKey.substr(1, prefixedKey.length());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool ArgParser::parse(int argc, char **argv, std::function<bool (std::string, std::string)> onOption, std::function<bool (std::string)> onFlag) {
|
||||
std::optional<std::string> currentOption = std::nullopt;
|
||||
for(int i = 1; i < argc; i++) {
|
||||
std::string current = argv[i];
|
||||
auto optKey = toKey(current);
|
||||
if (!currentOption.has_value() && optKey.has_value() && flags.find(optKey.value()) != flags.end()) {
|
||||
auto check = onFlag(shortToLongMap.at(optKey.value()));
|
||||
if (!check) {
|
||||
return false;
|
||||
}
|
||||
} else if (!currentOption.has_value() && optKey.has_value() && options.find(optKey.value()) != options.end()) {
|
||||
currentOption = optKey.value();
|
||||
} else if (currentOption.has_value()) {
|
||||
auto check = onOption(shortToLongMap.at(currentOption.value()), current);
|
||||
if (!check) {
|
||||
return false;
|
||||
}
|
||||
currentOption = std::nullopt;
|
||||
} else {
|
||||
std::cerr << "Unrecognized option " << current << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (currentOption.has_value()) {
|
||||
std::cerr << "Missing value for " << currentOption.value() << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
41
examples/streamer/ArgParser.hpp
Normal file
41
examples/streamer/ArgParser.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ArgParser_hpp
|
||||
#define ArgParser_hpp
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
struct ArgParser {
|
||||
private:
|
||||
std::set<std::string> options{};
|
||||
std::set<std::string> flags{};
|
||||
std::unordered_map<std::string, std::string> shortToLongMap{};
|
||||
public:
|
||||
ArgParser(std::vector<std::pair<std::string, std::string>> options, std::vector<std::pair<std::string, std::string>> flags);
|
||||
std::optional<std::string> toKey(std::string prefixedKey);
|
||||
bool parse(int argc, char **argv, std::function<bool (std::string, std::string)> onOption, std::function<bool (std::string)> onFlag);
|
||||
};
|
||||
|
||||
#endif /* ArgParser_hpp */
|
40
examples/streamer/CMakeLists.txt
Normal file
40
examples/streamer/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
if(POLICY CMP0079)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
endif()
|
||||
|
||||
add_executable(streamer
|
||||
main.cpp
|
||||
dispatchqueue.cpp
|
||||
dispatchqueue.hpp
|
||||
h264fileparser.cpp
|
||||
h264fileparser.hpp
|
||||
helpers.cpp
|
||||
helpers.hpp
|
||||
opusfileparser.cpp
|
||||
opusfileparser.hpp
|
||||
fileparser.cpp
|
||||
fileparser.hpp
|
||||
stream.cpp
|
||||
stream.hpp
|
||||
ArgParser.cpp
|
||||
ArgParser.hpp
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_compile_definitions(streamer PUBLIC STATIC_GETOPT)
|
||||
endif()
|
||||
|
||||
set_target_properties(streamer PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME streamer)
|
||||
|
||||
target_link_libraries(streamer datachannel nlohmann_json)
|
||||
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET streamer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
|
||||
$<TARGET_FILE_DIR:streamer>
|
||||
)
|
||||
endif()
|
32
examples/streamer/README.md
Normal file
32
examples/streamer/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Streaming H264 and opus
|
||||
|
||||
This example streams H264 and opus<sup id="a1">[1](#f1)</sup> samples to the connected browser client.
|
||||
|
||||
## Starting signaling server
|
||||
|
||||
```sh
|
||||
$ python3 ../signaling-server-python/signaling-server.py
|
||||
```
|
||||
|
||||
## Starting php
|
||||
|
||||
```sh
|
||||
$ php -S 127.0.0.1:8080
|
||||
```
|
||||
|
||||
Now you can open demo at [127.0.0.1:8080](127.0.0.1:8080).
|
||||
|
||||
## Arguments
|
||||
|
||||
- `-a` Directory with OPUS samples (default: *../../../../examples/streamer/samples/opus/*).
|
||||
- `-b` Directory with H264 samples (default: *../../../../examples/streamer/samples/h264/*).
|
||||
- `-d` Signaling server IP address (default: 127.0.0.1).
|
||||
- `-p` Signaling server port (default: 8000).
|
||||
- `-v` Enable debug logs.
|
||||
- `-h` Print this help and exit.
|
||||
|
||||
## Generating H264 and Opus samples
|
||||
|
||||
You can generate H264 and Opus sample with *samples/generate_h264.py* and *samples/generate_opus.py* respectively. This require ffmpeg, python3 and kaitaistruct library to be installed. Use `-h`/`--help` to learn more about arguments.
|
||||
|
||||
<b id="f1">1</b> Opus samples are generated from music downloaded at [bensound](https://www.bensound.com). [↩](#a1)
|
207
examples/streamer/client.js
Normal file
207
examples/streamer/client.js
Normal file
@ -0,0 +1,207 @@
|
||||
/** @type {RTCPeerConnection} */
|
||||
let rtc;
|
||||
const iceConnectionLog = document.getElementById('ice-connection-state'),
|
||||
iceGatheringLog = document.getElementById('ice-gathering-state'),
|
||||
signalingLog = document.getElementById('signaling-state'),
|
||||
dataChannelLog = document.getElementById('data-channel');
|
||||
|
||||
function randomString(len) {
|
||||
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let randomString = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
const randomPoz = Math.floor(Math.random() * charSet.length);
|
||||
randomString += charSet.substring(randomPoz, randomPoz + 1);
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
|
||||
const receiveID = randomString(10);
|
||||
const websocket = new WebSocket('ws://127.0.0.1:8000/' + receiveID);
|
||||
websocket.onopen = function () {
|
||||
document.getElementById('start').disabled = false;
|
||||
}
|
||||
|
||||
// data channel
|
||||
let dc = null, dcTimeout = null;
|
||||
|
||||
function createPeerConnection() {
|
||||
const config = {
|
||||
sdpSemantics: 'unified-plan',
|
||||
bundlePolicy: "max-bundle",
|
||||
};
|
||||
|
||||
if (document.getElementById('use-stun').checked) {
|
||||
config.iceServers = [{urls: ['stun:stun.l.google.com:19302']}];
|
||||
}
|
||||
|
||||
let pc = new RTCPeerConnection(config);
|
||||
|
||||
// register some listeners to help debugging
|
||||
pc.addEventListener('icegatheringstatechange', function () {
|
||||
iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState;
|
||||
}, false);
|
||||
iceGatheringLog.textContent = pc.iceGatheringState;
|
||||
|
||||
pc.addEventListener('iceconnectionstatechange', function () {
|
||||
iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState;
|
||||
}, false);
|
||||
iceConnectionLog.textContent = pc.iceConnectionState;
|
||||
|
||||
pc.addEventListener('signalingstatechange', function () {
|
||||
signalingLog.textContent += ' -> ' + pc.signalingState;
|
||||
}, false);
|
||||
signalingLog.textContent = pc.signalingState;
|
||||
|
||||
// connect audio / video
|
||||
pc.addEventListener('track', function (evt) {
|
||||
document.getElementById('media').style.display = 'block';
|
||||
const videoTag = document.getElementById('video');
|
||||
videoTag.srcObject = evt.streams[0];
|
||||
videoTag.play();
|
||||
});
|
||||
|
||||
let time_start = null;
|
||||
|
||||
function current_stamp() {
|
||||
if (time_start === null) {
|
||||
time_start = new Date().getTime();
|
||||
return 0;
|
||||
} else {
|
||||
return new Date().getTime() - time_start;
|
||||
}
|
||||
}
|
||||
|
||||
pc.ondatachannel = function (event) {
|
||||
dc = event.channel;
|
||||
dc.onopen = function () {
|
||||
dataChannelLog.textContent += '- open\n';
|
||||
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||
};
|
||||
dc.onmessage = function (evt) {
|
||||
|
||||
dataChannelLog.textContent += '< ' + evt.data + '\n';
|
||||
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||
|
||||
dcTimeout = setTimeout(function () {
|
||||
if (dc == null && dcTimeout != null) {
|
||||
dcTimeout = null;
|
||||
return
|
||||
}
|
||||
const message = 'Pong ' + current_stamp();
|
||||
dataChannelLog.textContent += '> ' + message + '\n';
|
||||
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||
dc.send(message);
|
||||
}, 1000);
|
||||
}
|
||||
dc.onclose = function () {
|
||||
clearTimeout(dcTimeout);
|
||||
dcTimeout = null;
|
||||
dataChannelLog.textContent += '- close\n';
|
||||
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
|
||||
};
|
||||
}
|
||||
|
||||
return pc;
|
||||
}
|
||||
|
||||
function sendAnswer(pc) {
|
||||
return pc.createAnswer()
|
||||
.then((answer) => rtc.setLocalDescription(answer))
|
||||
.then(function () {
|
||||
// wait for ICE gathering to complete
|
||||
return new Promise(function (resolve) {
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
resolve();
|
||||
} else {
|
||||
function checkState() {
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
pc.removeEventListener('icegatheringstatechange', checkState);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
pc.addEventListener('icegatheringstatechange', checkState);
|
||||
}
|
||||
});
|
||||
}).then(function () {
|
||||
const answer = pc.localDescription;
|
||||
|
||||
document.getElementById('answer-sdp').textContent = answer.sdp;
|
||||
|
||||
return websocket.send(JSON.stringify(
|
||||
{
|
||||
id: "server",
|
||||
type: answer.type,
|
||||
sdp: answer.sdp,
|
||||
}));
|
||||
}).catch(function (e) {
|
||||
alert(e);
|
||||
});
|
||||
}
|
||||
|
||||
function handleOffer(offer) {
|
||||
rtc = createPeerConnection();
|
||||
return rtc.setRemoteDescription(offer)
|
||||
.then(() => sendAnswer(rtc));
|
||||
}
|
||||
|
||||
function sendStreamRequest() {
|
||||
websocket.send(JSON.stringify(
|
||||
{
|
||||
id: "server",
|
||||
type: "streamRequest",
|
||||
receiver: receiveID,
|
||||
}));
|
||||
}
|
||||
|
||||
async function start() {
|
||||
document.getElementById('start').style.display = 'none';
|
||||
document.getElementById('stop').style.display = 'inline-block';
|
||||
document.getElementById('media').style.display = 'block';
|
||||
sendStreamRequest();
|
||||
}
|
||||
|
||||
function stop() {
|
||||
document.getElementById('stop').style.display = 'none';
|
||||
document.getElementById('media').style.display = 'none';
|
||||
document.getElementById('start').style.display = 'inline-block';
|
||||
|
||||
// close data channel
|
||||
if (dc) {
|
||||
dc.close();
|
||||
dc = null;
|
||||
}
|
||||
|
||||
// close transceivers
|
||||
if (rtc.getTransceivers) {
|
||||
rtc.getTransceivers().forEach(function (transceiver) {
|
||||
if (transceiver.stop) {
|
||||
transceiver.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// close local audio / video
|
||||
rtc.getSenders().forEach(function (sender) {
|
||||
const track = sender.track;
|
||||
if (track !== null) {
|
||||
sender.track.stop();
|
||||
}
|
||||
});
|
||||
|
||||
// close peer connection
|
||||
setTimeout(function () {
|
||||
rtc.close();
|
||||
rtc = null;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
|
||||
websocket.onmessage = async function (evt) {
|
||||
const received_msg = evt.data;
|
||||
const object = JSON.parse(received_msg);
|
||||
if (object.type == "offer") {
|
||||
document.getElementById('offer-sdp').textContent = object.sdp;
|
||||
await handleOffer(object)
|
||||
}
|
||||
}
|
94
examples/streamer/dispatchqueue.cpp
Normal file
94
examples/streamer/dispatchqueue.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "dispatchqueue.hpp"
|
||||
|
||||
DispatchQueue::DispatchQueue(std::string name, size_t threadCount) :
|
||||
name{std::move(name)}, threads(threadCount) {
|
||||
for(size_t i = 0; i < threads.size(); i++)
|
||||
{
|
||||
threads[i] = std::thread(&DispatchQueue::dispatchThreadHandler, this);
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue::~DispatchQueue() {
|
||||
// Signal to dispatch threads that it's time to wrap up
|
||||
std::unique_lock<std::mutex> lock(lockMutex);
|
||||
quit = true;
|
||||
lock.unlock();
|
||||
condition.notify_all();
|
||||
|
||||
// Wait for threads to finish before we exit
|
||||
for(size_t i = 0; i < threads.size(); i++)
|
||||
{
|
||||
if(threads[i].joinable())
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DispatchQueue::removePending() {
|
||||
std::unique_lock<std::mutex> lock(lockMutex);
|
||||
queue = {};
|
||||
}
|
||||
|
||||
void DispatchQueue::dispatch(const fp_t& op) {
|
||||
std::unique_lock<std::mutex> lock(lockMutex);
|
||||
queue.push(op);
|
||||
|
||||
// Manual unlocking is done before notifying, to avoid waking up
|
||||
// the waiting thread only to block again (see notify_one for details)
|
||||
lock.unlock();
|
||||
condition.notify_one();
|
||||
}
|
||||
|
||||
void DispatchQueue::dispatch(fp_t&& op) {
|
||||
std::unique_lock<std::mutex> lock(lockMutex);
|
||||
queue.push(std::move(op));
|
||||
|
||||
// Manual unlocking is done before notifying, to avoid waking up
|
||||
// the waiting thread only to block again (see notify_one for details)
|
||||
lock.unlock();
|
||||
condition.notify_one();
|
||||
}
|
||||
|
||||
void DispatchQueue::dispatchThreadHandler(void) {
|
||||
std::unique_lock<std::mutex> lock(lockMutex);
|
||||
do {
|
||||
//Wait until we have data or a quit signal
|
||||
condition.wait(lock, [this]{
|
||||
return (queue.size() || quit);
|
||||
});
|
||||
|
||||
//after wait, we own the lock
|
||||
if(!quit && queue.size())
|
||||
{
|
||||
auto op = std::move(queue.front());
|
||||
queue.pop();
|
||||
|
||||
//unlock now that we're done messing with the queue
|
||||
lock.unlock();
|
||||
|
||||
op();
|
||||
|
||||
lock.lock();
|
||||
}
|
||||
} while (!quit);
|
||||
}
|
59
examples/streamer/dispatchqueue.hpp
Normal file
59
examples/streamer/dispatchqueue.hpp
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef dispatchqueue_hpp
|
||||
#define dispatchqueue_hpp
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
|
||||
class DispatchQueue {
|
||||
typedef std::function<void(void)> fp_t;
|
||||
|
||||
public:
|
||||
DispatchQueue(std::string name, size_t threadCount = 1);
|
||||
~DispatchQueue();
|
||||
|
||||
// dispatch and copy
|
||||
void dispatch(const fp_t& op);
|
||||
// dispatch and move
|
||||
void dispatch(fp_t&& op);
|
||||
|
||||
void removePending();
|
||||
|
||||
// Deleted operations
|
||||
DispatchQueue(const DispatchQueue& rhs) = delete;
|
||||
DispatchQueue& operator=(const DispatchQueue& rhs) = delete;
|
||||
DispatchQueue(DispatchQueue&& rhs) = delete;
|
||||
DispatchQueue& operator=(DispatchQueue&& rhs) = delete;
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
std::mutex lockMutex;
|
||||
std::vector<std::thread> threads;
|
||||
std::queue<fp_t> queue;
|
||||
std::condition_variable condition;
|
||||
bool quit = false;
|
||||
|
||||
void dispatchThreadHandler(void);
|
||||
};
|
||||
|
||||
#endif /* dispatchqueue_hpp */
|
59
examples/streamer/fileparser.cpp
Normal file
59
examples/streamer/fileparser.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fileparser.hpp"
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
FileParser::FileParser(string directory, string extension, uint32_t samplesPerSecond, bool loop): sampleDuration_us(1000 * 1000 / samplesPerSecond), StreamSource() {
|
||||
this->directory = directory;
|
||||
this->extension = extension;
|
||||
this->loop = loop;
|
||||
}
|
||||
|
||||
void FileParser::start() {
|
||||
sampleTime_us = -sampleDuration_us;
|
||||
loadNextSample();
|
||||
}
|
||||
|
||||
void FileParser::stop() {
|
||||
StreamSource::stop();
|
||||
counter = -1;
|
||||
}
|
||||
|
||||
void FileParser::loadNextSample() {
|
||||
string frame_id = to_string(++counter);
|
||||
|
||||
string url = directory + "/sample-" + frame_id + extension;
|
||||
ifstream source(url, ios_base::binary);
|
||||
if (!source) {
|
||||
if (loop && counter > 0) {
|
||||
loopTimestampOffset = sampleTime_us;
|
||||
counter = -1;
|
||||
loadNextSample();
|
||||
return;
|
||||
}
|
||||
sample = {};
|
||||
return;
|
||||
}
|
||||
|
||||
vector<uint8_t> fileContents((std::istreambuf_iterator<char>(source)), std::istreambuf_iterator<char>());
|
||||
sample = *reinterpret_cast<vector<byte> *>(&fileContents);
|
||||
sampleTime_us += sampleDuration_us;
|
||||
}
|
40
examples/streamer/fileparser.hpp
Normal file
40
examples/streamer/fileparser.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef fileparser_hpp
|
||||
#define fileparser_hpp
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "stream.hpp"
|
||||
|
||||
class FileParser: public StreamSource {
|
||||
std::string directory;
|
||||
std::string extension;
|
||||
uint32_t counter = -1;
|
||||
bool loop;
|
||||
uint64_t loopTimestampOffset = 0;
|
||||
public:
|
||||
const uint64_t sampleDuration_us;
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
FileParser(std::string directory, std::string extension, uint32_t samplesPerSecond, bool loop);
|
||||
virtual void loadNextSample();
|
||||
};
|
||||
|
||||
#endif /* fileparser_hpp */
|
70
examples/streamer/h264fileparser.cpp
Normal file
70
examples/streamer/h264fileparser.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "h264fileparser.hpp"
|
||||
#include <fstream>
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
H264FileParser::H264FileParser(string directory, uint32_t fps, bool loop): FileParser(directory, ".h264", fps, loop) { }
|
||||
|
||||
void H264FileParser::loadNextSample() {
|
||||
FileParser::loadNextSample();
|
||||
|
||||
unsigned long long i = 0;
|
||||
while (i < sample.size()) {
|
||||
assert(i + 4 < sample.size());
|
||||
auto lengthPtr = (uint32_t *) (sample.data() + i);
|
||||
uint32_t length = ntohl(*lengthPtr);
|
||||
auto naluStartIndex = i + 4;
|
||||
auto naluEndIndex = naluStartIndex + length;
|
||||
assert(naluEndIndex <= sample.size());
|
||||
auto header = reinterpret_cast<rtc::NalUnitHeader *>(sample.data() + naluStartIndex);
|
||||
auto type = header->unitType();
|
||||
switch (type) {
|
||||
case 7:
|
||||
previousUnitType7 = {sample.begin() + i, sample.begin() + naluEndIndex};
|
||||
break;
|
||||
case 8:
|
||||
previousUnitType8 = {sample.begin() + i, sample.begin() + naluEndIndex};;
|
||||
break;
|
||||
case 5:
|
||||
previousUnitType5 = {sample.begin() + i, sample.begin() + naluEndIndex};;
|
||||
break;
|
||||
}
|
||||
i = naluEndIndex;
|
||||
}
|
||||
}
|
||||
|
||||
vector<byte> H264FileParser::initialNALUS() {
|
||||
vector<byte> units{};
|
||||
if (previousUnitType7.has_value()) {
|
||||
auto nalu = previousUnitType7.value();
|
||||
units.insert(units.end(), nalu.begin(), nalu.end());
|
||||
}
|
||||
if (previousUnitType8.has_value()) {
|
||||
auto nalu = previousUnitType8.value();
|
||||
units.insert(units.end(), nalu.begin(), nalu.end());
|
||||
}
|
||||
if (previousUnitType5.has_value()) {
|
||||
auto nalu = previousUnitType5.value();
|
||||
units.insert(units.end(), nalu.begin(), nalu.end());
|
||||
}
|
||||
return units;
|
||||
}
|
36
examples/streamer/h264fileparser.hpp
Normal file
36
examples/streamer/h264fileparser.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef h264fileparser_hpp
|
||||
#define h264fileparser_hpp
|
||||
|
||||
#include "fileparser.hpp"
|
||||
#include <optional>
|
||||
|
||||
class H264FileParser: public FileParser {
|
||||
std::optional<std::vector<std::byte>> previousUnitType5 = std::nullopt;
|
||||
std::optional<std::vector<std::byte>> previousUnitType7 = std::nullopt;
|
||||
std::optional<std::vector<std::byte>> previousUnitType8 = std::nullopt;
|
||||
|
||||
public:
|
||||
H264FileParser(std::string directory, uint32_t fps, bool loop);
|
||||
void loadNextSample() override;
|
||||
std::vector<std::byte> initialNALUS();
|
||||
};
|
||||
|
||||
#endif /* h264fileparser_hpp */
|
87
examples/streamer/helpers.cpp
Normal file
87
examples/streamer/helpers.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include <ctime>
|
||||
|
||||
#if _WIN32
|
||||
// taken from https://stackoverflow.com/questions/10905892/equivalent-of-gettimeday-for-windows
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
struct timezone {
|
||||
int tz_minuteswest;
|
||||
int tz_dsttime;
|
||||
};
|
||||
int gettimeofday(struct timeval *tv, struct timezone *tz)
|
||||
{
|
||||
if (tv) {
|
||||
FILETIME filetime; /* 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 00:00 UTC */
|
||||
ULARGE_INTEGER x;
|
||||
ULONGLONG usec;
|
||||
static const ULONGLONG epoch_offset_us = 11644473600000000ULL; /* microseconds betweeen Jan 1,1601 and Jan 1,1970 */
|
||||
|
||||
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
|
||||
GetSystemTimePreciseAsFileTime(&filetime);
|
||||
#else
|
||||
GetSystemTimeAsFileTime(&filetime);
|
||||
#endif
|
||||
x.LowPart = filetime.dwLowDateTime;
|
||||
x.HighPart = filetime.dwHighDateTime;
|
||||
usec = x.QuadPart / 10 - epoch_offset_us;
|
||||
tv->tv_sec = (time_t)(usec / 1000000ULL);
|
||||
tv->tv_usec = (long)(usec % 1000000ULL);
|
||||
}
|
||||
if (tz) {
|
||||
TIME_ZONE_INFORMATION timezone;
|
||||
GetTimeZoneInformation(&timezone);
|
||||
tz->tz_minuteswest = timezone.Bias;
|
||||
tz->tz_dsttime = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace rtc;
|
||||
|
||||
ClientTrackData::ClientTrackData(shared_ptr<Track> track, shared_ptr<RtcpSrReporter> sender) {
|
||||
this->track = track;
|
||||
this->sender = sender;
|
||||
}
|
||||
|
||||
void Client::setState(State state) {
|
||||
std::unique_lock lock(_mutex);
|
||||
this->state = state;
|
||||
}
|
||||
|
||||
Client::State Client::getState() {
|
||||
std::shared_lock lock(_mutex);
|
||||
return state;
|
||||
}
|
||||
|
||||
ClientTrack::ClientTrack(string id, shared_ptr<ClientTrackData> trackData) {
|
||||
this->id = id;
|
||||
this->trackData = trackData;
|
||||
}
|
||||
|
||||
uint64_t currentTimeInMicroSeconds() {
|
||||
struct timeval time;
|
||||
gettimeofday(&time, NULL);
|
||||
return uint64_t(time.tv_sec) * 1000 * 1000 + time.tv_usec;
|
||||
}
|
63
examples/streamer/helpers.hpp
Normal file
63
examples/streamer/helpers.hpp
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef helpers_hpp
|
||||
#define helpers_hpp
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
struct ClientTrackData {
|
||||
std::shared_ptr<rtc::Track> track;
|
||||
std::shared_ptr<rtc::RtcpSrReporter> sender;
|
||||
|
||||
ClientTrackData(std::shared_ptr<rtc::Track> track, std::shared_ptr<rtc::RtcpSrReporter> sender);
|
||||
};
|
||||
|
||||
struct Client {
|
||||
enum class State {
|
||||
Waiting,
|
||||
WaitingForVideo,
|
||||
WaitingForAudio,
|
||||
Ready
|
||||
};
|
||||
const std::shared_ptr<rtc::PeerConnection> & peerConnection = _peerConnection;
|
||||
Client(std::shared_ptr<rtc::PeerConnection> pc) {
|
||||
_peerConnection = pc;
|
||||
}
|
||||
std::optional<std::shared_ptr<ClientTrackData>> video;
|
||||
std::optional<std::shared_ptr<ClientTrackData>> audio;
|
||||
std::optional<std::shared_ptr<rtc::DataChannel>> dataChannel{};
|
||||
void setState(State state);
|
||||
State getState();
|
||||
|
||||
private:
|
||||
std::shared_mutex _mutex;
|
||||
State state = State::Waiting;
|
||||
std::string id;
|
||||
std::shared_ptr<rtc::PeerConnection> _peerConnection;
|
||||
};
|
||||
|
||||
struct ClientTrack {
|
||||
std::string id;
|
||||
std::shared_ptr<ClientTrackData> trackData;
|
||||
ClientTrack(std::string id, std::shared_ptr<ClientTrackData> trackData);
|
||||
};
|
||||
|
||||
uint64_t currentTimeInMicroSeconds();
|
||||
|
||||
#endif /* helpers_hpp */
|
72
examples/streamer/index.html
Normal file
72
examples/streamer/index.html
Normal file
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>libdatachannel media example</title>
|
||||
<style>
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.option {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#media {
|
||||
max-width: 1280px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||
|
||||
<h2>Options</h2>
|
||||
|
||||
<div class="option">
|
||||
<input id="use-stun" type="checkbox"/>
|
||||
<label for="use-stun">Use STUN server</label>
|
||||
</div>
|
||||
|
||||
<button id="start" onclick="start()" disabled>Start</button>
|
||||
<button id="stop" style="display: none" onclick="stop()">Stop</button>
|
||||
|
||||
<h2>State</h2>
|
||||
<p>
|
||||
ICE gathering state: <span id="ice-gathering-state"></span>
|
||||
</p>
|
||||
<p>
|
||||
ICE connection state: <span id="ice-connection-state"></span>
|
||||
</p>
|
||||
<p>
|
||||
Signaling state: <span id="signaling-state"></span>
|
||||
</p>
|
||||
|
||||
<div id="media" style="display: none">
|
||||
<h2>Media</h2>
|
||||
<video id="video" autoplay playsinline></video>
|
||||
</div>
|
||||
|
||||
<h2>Data channel</h2>
|
||||
<pre id="data-channel" style="height: 200px;"></pre>
|
||||
|
||||
<h2>SDP</h2>
|
||||
|
||||
<h3>Offer</h3>
|
||||
<pre id="offer-sdp"></pre>
|
||||
|
||||
<h3>Answer</h3>
|
||||
<pre id="answer-sdp"></pre>
|
||||
|
||||
<script src="client.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
480
examples/streamer/main.cpp
Normal file
480
examples/streamer/main.cpp
Normal file
@ -0,0 +1,480 @@
|
||||
/*
|
||||
* libdatachannel client example
|
||||
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||
* Copyright (c) 2019 Murat Dogan
|
||||
* Copyright (c) 2020 Will Munn
|
||||
* Copyright (c) 2020 Nico Chatzi
|
||||
* Copyright (c) 2020 Lara Mackey
|
||||
* Copyright (c) 2020 Erik Cota-Robles
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include "h264fileparser.hpp"
|
||||
#include "opusfileparser.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "ArgParser.hpp"
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
/// all connected clients
|
||||
unordered_map<string, shared_ptr<Client>> clients{};
|
||||
|
||||
/// Creates peer connection and client representation
|
||||
/// @param config Configuration
|
||||
/// @param wws Websocket for signaling
|
||||
/// @param id Client ID
|
||||
/// @returns Client
|
||||
shared_ptr<Client> createPeerConnection(const Configuration &config,
|
||||
weak_ptr<WebSocket> wws,
|
||||
string id);
|
||||
|
||||
/// Creates stream
|
||||
/// @param h264Samples Directory with H264 samples
|
||||
/// @param fps Video FPS
|
||||
/// @param opusSamples Directory with opus samples
|
||||
/// @returns Stream object
|
||||
shared_ptr<Stream> createStream(const string h264Samples, const unsigned fps, const string opusSamples);
|
||||
|
||||
/// Add client to stream
|
||||
/// @param client Client
|
||||
/// @param adding_video True if adding video
|
||||
void addToStream(shared_ptr<Client> client, bool isAddingVideo);
|
||||
|
||||
/// Start stream
|
||||
void startStream();
|
||||
|
||||
/// Main dispatch queue
|
||||
DispatchQueue MainThread("Main");
|
||||
|
||||
/// Audio and video stream
|
||||
optional<shared_ptr<Stream>> avStream = nullopt;
|
||||
|
||||
const string defaultRootDirectory = "../../../../examples/streamer/samples/";
|
||||
const string defaultH264SamplesDirectory = defaultRootDirectory + "h264/";
|
||||
string h264SamplesDirectory = defaultH264SamplesDirectory;
|
||||
const string defaultOpusSamplesDirectory = defaultRootDirectory + "opus/";
|
||||
string opusSamplesDirectory = defaultOpusSamplesDirectory;
|
||||
const string defaultIPAddress = "127.0.0.1";
|
||||
const uint16_t defaultPort = 8000;
|
||||
string ip_address = defaultIPAddress;
|
||||
uint16_t port = defaultPort;
|
||||
|
||||
/// Incomming message handler for websocket
|
||||
/// @param message Incommint message
|
||||
/// @param config Configuration
|
||||
/// @param ws Websocket
|
||||
void wsOnMessage(json message, Configuration config, shared_ptr<WebSocket> ws) {
|
||||
auto it = message.find("id");
|
||||
if (it == message.end())
|
||||
return;
|
||||
string id = it->get<string>();
|
||||
it = message.find("type");
|
||||
if (it == message.end())
|
||||
return;
|
||||
string type = it->get<string>();
|
||||
|
||||
if (type == "streamRequest") {
|
||||
shared_ptr<Client> c = createPeerConnection(config, make_weak_ptr(ws), id);
|
||||
clients.emplace(id, c);
|
||||
} else if (type == "answer") {
|
||||
shared_ptr<Client> c;
|
||||
if (auto jt = clients.find(id); jt != clients.end()) {
|
||||
auto pc = clients.at(id)->peerConnection;
|
||||
auto sdp = message["sdp"].get<string>();
|
||||
auto description = Description(sdp, type);
|
||||
pc->setRemoteDescription(description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) try {
|
||||
bool enableDebugLogs = false;
|
||||
bool printHelp = false;
|
||||
int c = 0;
|
||||
auto parser = ArgParser({{"a", "audio"}, {"b", "video"}, {"d", "ip"}, {"p","port"}}, {{"h", "help"}, {"v", "verbose"}});
|
||||
auto parsingResult = parser.parse(argc, argv, [](string key, string value) {
|
||||
if (key == "audio") {
|
||||
opusSamplesDirectory = value + "/";
|
||||
} else if (key == "video") {
|
||||
h264SamplesDirectory = value + "/";
|
||||
} else if (key == "ip") {
|
||||
ip_address = value;
|
||||
} else if (key == "port") {
|
||||
port = atoi(value.data());
|
||||
} else {
|
||||
cerr << "Invalid option --" << key << " with value " << value << endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, [&enableDebugLogs, &printHelp](string flag){
|
||||
if (flag == "verbose") {
|
||||
enableDebugLogs = true;
|
||||
} else if (flag == "help") {
|
||||
printHelp = true;
|
||||
} else {
|
||||
cerr << "Invalid flag --" << flag << endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!parsingResult) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (printHelp) {
|
||||
cout << "usage: stream-h264 [-a opus_samples_folder] [-b h264_samples_folder] [-d ip_address] [-p port] [-v] [-h]" << endl
|
||||
<< "Arguments:" << endl
|
||||
<< "\t -a " << "Directory with opus samples (default: " << defaultOpusSamplesDirectory << ")." << endl
|
||||
<< "\t -b " << "Directory with H264 samples (default: " << defaultH264SamplesDirectory << ")." << endl
|
||||
<< "\t -d " << "Signaling server IP address (default: " << defaultIPAddress << ")." << endl
|
||||
<< "\t -p " << "Signaling server port (default: " << defaultPort << ")." << endl
|
||||
<< "\t -v " << "Enable debug logs." << endl
|
||||
<< "\t -h " << "Print this help and exit." << endl;
|
||||
return 0;
|
||||
}
|
||||
if (enableDebugLogs) {
|
||||
InitLogger(LogLevel::Debug);
|
||||
}
|
||||
|
||||
Configuration config;
|
||||
string stunServer = "stun:stun.l.google.com:19302";
|
||||
cout << "Stun server is " << stunServer << endl;
|
||||
config.iceServers.emplace_back(stunServer);
|
||||
|
||||
|
||||
string localId = "server";
|
||||
cout << "The local ID is: " << localId << endl;
|
||||
|
||||
auto ws = make_shared<WebSocket>();
|
||||
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
||||
|
||||
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
||||
|
||||
ws->onError([](const string &error) { cout << "WebSocket failed: " << error << endl; });
|
||||
|
||||
ws->onMessage([&](variant<binary, string> data) {
|
||||
if (!holds_alternative<string>(data))
|
||||
return;
|
||||
|
||||
json message = json::parse(get<string>(data));
|
||||
MainThread.dispatch([message, config, ws]() {
|
||||
wsOnMessage(message, config, ws);
|
||||
});
|
||||
});
|
||||
|
||||
const string url = "ws://" + ip_address + ":" + to_string(port) + "/" + localId;
|
||||
cout << "Url is " << url << endl;
|
||||
ws->open(url);
|
||||
|
||||
cout << "Waiting for signaling to be connected..." << endl;
|
||||
while (!ws->isOpen()) {
|
||||
if (ws->isClosed())
|
||||
return 1;
|
||||
this_thread::sleep_for(100ms);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
string id;
|
||||
cout << "Enter to exit" << endl;
|
||||
cin >> id;
|
||||
cin.ignore();
|
||||
cout << "exiting" << endl;
|
||||
break;
|
||||
}
|
||||
|
||||
cout << "Cleaning up..." << endl;
|
||||
return 0;
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cout << "Error: " << e.what() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
shared_ptr<ClientTrackData> addVideo(const shared_ptr<PeerConnection> pc, const uint8_t payloadType, const uint32_t ssrc, const string cname, const string msid, const function<void (void)> onOpen) {
|
||||
auto video = Description::Video(cname);
|
||||
video.addH264Codec(payloadType);
|
||||
video.addSSRC(ssrc, cname, msid, cname);
|
||||
auto track = pc->addTrack(video);
|
||||
// create RTP configuration
|
||||
auto rtpConfig = shared_ptr<RtpPacketizationConfig>(new RtpPacketizationConfig(ssrc, cname, payloadType, H264RtpPacketizer::defaultClockRate));
|
||||
// create packetizer
|
||||
auto packetizer = shared_ptr<H264RtpPacketizer>(new H264RtpPacketizer(H264RtpPacketizer::Separator::Length, rtpConfig));
|
||||
// create H264 handler
|
||||
shared_ptr<H264PacketizationHandler> h264Handler(new H264PacketizationHandler(packetizer));
|
||||
// add RTCP SR handler
|
||||
auto srReporter = make_shared<RtcpSrReporter>(rtpConfig);
|
||||
h264Handler->addToChain(srReporter);
|
||||
// add RTCP NACK handler
|
||||
auto nackResponder = make_shared<RtcpNackResponder>();
|
||||
h264Handler->addToChain(nackResponder);
|
||||
// set handler
|
||||
track->setRtcpHandler(h264Handler);
|
||||
track->onOpen(onOpen);
|
||||
auto trackData = make_shared<ClientTrackData>(track, srReporter);
|
||||
return trackData;
|
||||
}
|
||||
|
||||
shared_ptr<ClientTrackData> addAudio(const shared_ptr<PeerConnection> pc, const uint8_t payloadType, const uint32_t ssrc, const string cname, const string msid, const function<void (void)> onOpen) {
|
||||
auto audio = Description::Audio(cname);
|
||||
audio.addOpusCodec(payloadType);
|
||||
audio.addSSRC(ssrc, cname, msid, cname);
|
||||
auto track = pc->addTrack(audio);
|
||||
// create RTP configuration
|
||||
auto rtpConfig = shared_ptr<RtpPacketizationConfig>(new RtpPacketizationConfig(ssrc, cname, payloadType, OpusRtpPacketizer::defaultClockRate));
|
||||
// create packetizer
|
||||
auto packetizer = make_shared<OpusRtpPacketizer>(rtpConfig);
|
||||
// create opus handler
|
||||
auto opusHandler = make_shared<OpusPacketizationHandler>(packetizer);
|
||||
// add RTCP SR handler
|
||||
auto srReporter = make_shared<RtcpSrReporter>(rtpConfig);
|
||||
opusHandler->addToChain(srReporter);
|
||||
// add RTCP NACK handler
|
||||
auto nackResponder = make_shared<RtcpNackResponder>();
|
||||
opusHandler->addToChain(nackResponder);
|
||||
// set handler
|
||||
track->setRtcpHandler(opusHandler);
|
||||
track->onOpen(onOpen);
|
||||
auto trackData = make_shared<ClientTrackData>(track, srReporter);
|
||||
return trackData;
|
||||
}
|
||||
|
||||
// Create and setup a PeerConnection
|
||||
shared_ptr<Client> createPeerConnection(const Configuration &config,
|
||||
weak_ptr<WebSocket> wws,
|
||||
string id) {
|
||||
|
||||
auto pc = make_shared<PeerConnection>(config);
|
||||
shared_ptr<Client> client(new Client(pc));
|
||||
|
||||
pc->onStateChange([id](PeerConnection::State state) {
|
||||
cout << "State: " << state << endl;
|
||||
if (state == PeerConnection::State::Disconnected ||
|
||||
state == PeerConnection::State::Failed ||
|
||||
state == PeerConnection::State::Closed) {
|
||||
// remove disconnected client
|
||||
MainThread.dispatch([id]() {
|
||||
clients.erase(id);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
pc->onGatheringStateChange(
|
||||
[wpc = make_weak_ptr(pc), id, wws](PeerConnection::GatheringState state) {
|
||||
cout << "Gathering State: " << state << endl;
|
||||
if (state == PeerConnection::GatheringState::Complete) {
|
||||
if(auto pc = wpc.lock()) {
|
||||
auto description = pc->localDescription();
|
||||
json message = {
|
||||
{"id", id},
|
||||
{"type", description->typeString()},
|
||||
{"sdp", string(description.value())}
|
||||
};
|
||||
// Gathering complete, send answer
|
||||
if (auto ws = wws.lock()) {
|
||||
ws->send(message.dump());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client->video = addVideo(pc, 102, 1, "video-stream", "stream1", [id, wc = make_weak_ptr(client)]() {
|
||||
MainThread.dispatch([wc]() {
|
||||
if (auto c = wc.lock()) {
|
||||
addToStream(c, true);
|
||||
}
|
||||
});
|
||||
cout << "Video from " << id << " opened" << endl;
|
||||
});
|
||||
|
||||
client->audio = addAudio(pc, 111, 2, "audio-stream", "stream1", [id, wc = make_weak_ptr(client)]() {
|
||||
MainThread.dispatch([wc]() {
|
||||
if (auto c = wc.lock()) {
|
||||
addToStream(c, false);
|
||||
}
|
||||
});
|
||||
cout << "Audio from " << id << " opened" << endl;
|
||||
});
|
||||
|
||||
auto dc = pc->addDataChannel("ping-pong");
|
||||
dc->onOpen([id, wdc = make_weak_ptr(dc)]() {
|
||||
if (auto dc = wdc.lock()) {
|
||||
dc->send("Ping");
|
||||
}
|
||||
});
|
||||
|
||||
dc->onMessage(nullptr, [id, wdc = make_weak_ptr(dc)](string msg) {
|
||||
cout << "Message from " << id << " received: " << msg << endl;
|
||||
if (auto dc = wdc.lock()) {
|
||||
dc->send("Ping");
|
||||
}
|
||||
});
|
||||
client->dataChannel = dc;
|
||||
|
||||
pc->setLocalDescription();
|
||||
return client;
|
||||
};
|
||||
|
||||
/// Create stream
|
||||
shared_ptr<Stream> createStream(const string h264Samples, const unsigned fps, const string opusSamples) {
|
||||
// video source
|
||||
auto video = make_shared<H264FileParser>(h264Samples, fps, true);
|
||||
// audio source
|
||||
auto audio = make_shared<OPUSFileParser>(opusSamples, true);
|
||||
|
||||
auto stream = make_shared<Stream>(video, audio);
|
||||
// set callback responsible for sample sending
|
||||
stream->onSample([ws = make_weak_ptr(stream)](Stream::StreamSourceType type, uint64_t sampleTime, rtc::binary sample) {
|
||||
vector<ClientTrack> tracks{};
|
||||
string streamType = type == Stream::StreamSourceType::Video ? "video" : "audio";
|
||||
// get track for given type
|
||||
function<optional<shared_ptr<ClientTrackData>> (shared_ptr<Client>)> getTrackData = [type](shared_ptr<Client> client) {
|
||||
return type == Stream::StreamSourceType::Video ? client->video : client->audio;
|
||||
};
|
||||
// get all clients with Ready state
|
||||
for(auto id_client: clients) {
|
||||
auto id = id_client.first;
|
||||
auto client = id_client.second;
|
||||
auto optTrackData = getTrackData(client);
|
||||
if (client->getState() == Client::State::Ready && optTrackData.has_value()) {
|
||||
auto trackData = optTrackData.value();
|
||||
tracks.push_back(ClientTrack(id, trackData));
|
||||
}
|
||||
}
|
||||
if (!tracks.empty()) {
|
||||
auto message = make_message(move(sample));
|
||||
for (auto clientTrack: tracks) {
|
||||
auto client = clientTrack.id;
|
||||
auto trackData = clientTrack.trackData;
|
||||
// sample time is in us, we need to convert it to seconds
|
||||
auto elapsedSeconds = double(sampleTime) / (1000 * 1000);
|
||||
auto rtpConfig = trackData->sender->rtpConfig;
|
||||
// get elapsed time in clock rate
|
||||
uint32_t elapsedTimestamp = rtpConfig->secondsToTimestamp(elapsedSeconds);
|
||||
|
||||
// set new timestamp
|
||||
rtpConfig->timestamp = rtpConfig->startTimestamp + elapsedTimestamp;
|
||||
|
||||
// get elapsed time in clock rate from last RTCP sender report
|
||||
auto reportElapsedTimestamp = rtpConfig->timestamp - trackData->sender->previousReportedTimestamp;
|
||||
// check if last report was at least 1 second ago
|
||||
if (rtpConfig->timestampToSeconds(reportElapsedTimestamp) > 1) {
|
||||
trackData->sender->setNeedsToReport();
|
||||
}
|
||||
cout << "Sending " << streamType << " sample with size: " << to_string(message->size()) << " to " << client << endl;
|
||||
bool send = false;
|
||||
try {
|
||||
// send sample
|
||||
send = trackData->track->send(*message);
|
||||
} catch (...) {
|
||||
send = false;
|
||||
}
|
||||
if (!send) {
|
||||
cerr << "Unable to send "<< streamType << " packet" << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
MainThread.dispatch([ws]() {
|
||||
if (clients.empty()) {
|
||||
// we have no clients, stop the stream
|
||||
if (auto stream = ws.lock()) {
|
||||
stream->stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// Start stream
|
||||
void startStream() {
|
||||
shared_ptr<Stream> stream;
|
||||
if (avStream.has_value()) {
|
||||
stream = avStream.value();
|
||||
if (stream->isRunning) {
|
||||
// stream is already running
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
stream = createStream(h264SamplesDirectory, 30, opusSamplesDirectory);
|
||||
avStream = stream;
|
||||
}
|
||||
stream->start();
|
||||
}
|
||||
|
||||
/// Send previous key frame so browser can show something to user
|
||||
/// @param stream Stream
|
||||
/// @param video Video track data
|
||||
void sendInitialNalus(shared_ptr<Stream> stream, shared_ptr<ClientTrackData> video) {
|
||||
auto h264 = dynamic_cast<H264FileParser *>(stream->video.get());
|
||||
auto initialNalus = h264->initialNALUS();
|
||||
|
||||
// send previous NALU key frame so users don't have to wait to see stream works
|
||||
if (!initialNalus.empty()) {
|
||||
const double frameDuration_s = double(h264->sampleDuration_us) / (1000 * 1000);
|
||||
const uint32_t frameTimestampDuration = video->sender->rtpConfig->secondsToTimestamp(frameDuration_s);
|
||||
video->sender->rtpConfig->timestamp = video->sender->rtpConfig->startTimestamp - frameTimestampDuration * 2;
|
||||
video->track->send(initialNalus);
|
||||
video->sender->rtpConfig->timestamp += frameTimestampDuration;
|
||||
// Send initial NAL units again to start stream in firefox browser
|
||||
video->track->send(initialNalus);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add client to stream
|
||||
/// @param client Client
|
||||
/// @param adding_video True if adding video
|
||||
void addToStream(shared_ptr<Client> client, bool isAddingVideo) {
|
||||
if (client->getState() == Client::State::Waiting) {
|
||||
client->setState(isAddingVideo ? Client::State::WaitingForAudio : Client::State::WaitingForVideo);
|
||||
} else if ((client->getState() == Client::State::WaitingForAudio && !isAddingVideo)
|
||||
|| (client->getState() == Client::State::WaitingForVideo && isAddingVideo)) {
|
||||
|
||||
// Audio and video tracks are collected now
|
||||
assert(client->video.has_value() && client->audio.has_value());
|
||||
|
||||
auto video = client->video.value();
|
||||
auto audio = client->audio.value();
|
||||
|
||||
auto currentTime_us = double(currentTimeInMicroSeconds());
|
||||
auto currentTime_s = currentTime_us / (1000 * 1000);
|
||||
|
||||
// set start time of stream
|
||||
video->sender->rtpConfig->setStartTime(currentTime_s, RtpPacketizationConfig::EpochStart::T1970);
|
||||
audio->sender->rtpConfig->setStartTime(currentTime_s, RtpPacketizationConfig::EpochStart::T1970);
|
||||
|
||||
// start stat recording of RTCP SR
|
||||
video->sender->startRecording();
|
||||
audio->sender->startRecording();
|
||||
|
||||
if (avStream.has_value()) {
|
||||
sendInitialNalus(avStream.value(), video);
|
||||
}
|
||||
|
||||
client->setState(Client::State::Ready);
|
||||
}
|
||||
if (client->getState() == Client::State::Ready) {
|
||||
startStream();
|
||||
}
|
||||
}
|
23
examples/streamer/opusfileparser.cpp
Normal file
23
examples/streamer/opusfileparser.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "opusfileparser.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
OPUSFileParser::OPUSFileParser(string directory, bool loop, uint32_t samplesPerSecond): FileParser(directory, ".opus", samplesPerSecond, loop) { }
|
32
examples/streamer/opusfileparser.hpp
Normal file
32
examples/streamer/opusfileparser.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* libdatachannel streamer example
|
||||
* Copyright (c) 2020 Filip Klembara (in2core)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef opusfileparser_hpp
|
||||
#define opusfileparser_hpp
|
||||
|
||||
#include "fileparser.hpp"
|
||||
|
||||
class OPUSFileParser: public FileParser {
|
||||
static const uint32_t defaultSamplesPerSecond = 50;
|
||||
|
||||
public:
|
||||
OPUSFileParser(std::string directory, bool loop, uint32_t samplesPerSecond = OPUSFileParser::defaultSamplesPerSecond);
|
||||
};
|
||||
|
||||
|
||||
#endif /* opusfileparser_hpp */
|
BIN
examples/streamer/samples/bensound-creativeminds.mp3
Normal file
BIN
examples/streamer/samples/bensound-creativeminds.mp3
Normal file
Binary file not shown.
BIN
examples/streamer/samples/candle.mov
Normal file
BIN
examples/streamer/samples/candle.mov
Normal file
Binary file not shown.
115
examples/streamer/samples/generate_h264.py
Executable file
115
examples/streamer/samples/generate_h264.py
Executable file
@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import getopt
|
||||
import sys
|
||||
import glob
|
||||
from functools import reduce
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class H264ByteStream:
|
||||
@staticmethod
|
||||
def nalu_type(nalu: bytes) -> int:
|
||||
return nalu[0] & 0x1F
|
||||
|
||||
@staticmethod
|
||||
def merge_sample(sample: List[bytes]) -> bytes:
|
||||
result = bytes()
|
||||
for nalu in sample:
|
||||
result += len(nalu).to_bytes(4, byteorder='big') + nalu
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def reduce_nalus_to_samples(samples: List[List[bytes]], current: bytes) -> List[List[bytes]]:
|
||||
last_nalus = samples[-1]
|
||||
samples[-1] = last_nalus + [current]
|
||||
if H264ByteStream.nalu_type(current) in [1, 5]:
|
||||
samples.append([])
|
||||
return samples
|
||||
|
||||
def __init__(self, file_name: str):
|
||||
with open(file_name, "rb") as file:
|
||||
byte_stream = file.read()
|
||||
long_split = byte_stream.split(b"\x00\x00\x00\x01")
|
||||
splits = reduce(lambda acc, x: acc + x.split(b"\x00\x00\x01"), long_split, [])
|
||||
nalus = filter(lambda x: len(x) > 0, splits)
|
||||
self.samples = list(
|
||||
filter(lambda x: len(x) > 0, reduce(H264ByteStream.reduce_nalus_to_samples, nalus, [[]])))
|
||||
|
||||
|
||||
def generate(input_file: str, output_dir: str, max_samples: Optional[int], fps: Optional[int]):
|
||||
if output_dir[-1] != "/":
|
||||
output_dir += "/"
|
||||
if os.path.isdir(output_dir):
|
||||
files_to_delete = glob.glob(output_dir + "*.h264")
|
||||
if len(files_to_delete) > 0:
|
||||
print("Remove following files?")
|
||||
for file in files_to_delete:
|
||||
print(file)
|
||||
response = input("Remove files? [y/n] ").lower()
|
||||
if response != "y" and response != "yes":
|
||||
print("Cancelling...")
|
||||
return
|
||||
print("Removing files")
|
||||
for file in files_to_delete:
|
||||
os.remove(file)
|
||||
else:
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
video_stream_file = "_video_stream.h264"
|
||||
if os.path.isfile(video_stream_file):
|
||||
os.remove(video_stream_file)
|
||||
|
||||
fps_line = "" if fps is None else "-filter:v fps=fps={} ".format(fps)
|
||||
command = 'ffmpeg -i {} -an -vcodec libx264 -preset slow -profile baseline {}{}'.format(input_file, fps_line,
|
||||
video_stream_file)
|
||||
os.system(command)
|
||||
|
||||
data = H264ByteStream(video_stream_file)
|
||||
index = 0
|
||||
for sample in data.samples[:max_samples]:
|
||||
name = "{}sample-{}.h264".format(output_dir, index)
|
||||
index += 1
|
||||
with open(name, 'wb') as file:
|
||||
merged_sample = H264ByteStream.merge_sample(sample)
|
||||
file.write(merged_sample)
|
||||
os.remove(video_stream_file)
|
||||
|
||||
|
||||
def main(argv):
|
||||
input_file = None
|
||||
default_output_dir = "h264/"
|
||||
output_dir = default_output_dir
|
||||
max_samples = None
|
||||
fps = None
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "hi:o:m:f:", ["help", "ifile=", "odir=", "max=", "fps"])
|
||||
except getopt.GetoptError:
|
||||
print('generate_h264.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-f <fps>] [-h]')
|
||||
sys.exit(2)
|
||||
for opt, arg in opts:
|
||||
if opt in ("-h", "--help"):
|
||||
print("Usage: generate_h264.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-f <fps>] [-h]")
|
||||
print("Arguments:")
|
||||
print("\t-i,--ifile: Input file")
|
||||
print("\t-o,--odir: Output directory (default: " + default_output_dir + ")")
|
||||
print("\t-m,--max: Maximum generated samples")
|
||||
print("\t-f,--fps: Output fps")
|
||||
print("\t-h,--help: Print this help and exit")
|
||||
sys.exit()
|
||||
elif opt in ("-i", "--ifile"):
|
||||
input_file = arg
|
||||
elif opt in ("-o", "--ofile"):
|
||||
output_dir = arg
|
||||
elif opt in ("-m", "--max"):
|
||||
max_samples = int(arg)
|
||||
elif opt in ("-f", "--fps"):
|
||||
fps = int(arg)
|
||||
if input_file is None:
|
||||
print("Missing argument -i")
|
||||
sys.exit(2)
|
||||
generate(input_file, output_dir, max_samples, fps)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
142
examples/streamer/samples/generate_opus.py
Executable file
142
examples/streamer/samples/generate_opus.py
Executable file
@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from kaitaistruct import KaitaiStruct, ValidationNotEqualError
|
||||
import os
|
||||
import getopt
|
||||
import sys
|
||||
import glob
|
||||
from functools import reduce
|
||||
|
||||
|
||||
class Ogg(KaitaiStruct):
|
||||
"""Ogg is a popular media container format, which provides basic
|
||||
streaming / buffering mechanisms and is content-agnostic. Most
|
||||
popular codecs that are used within Ogg streams are Vorbis (thus
|
||||
making Ogg/Vorbis streams) and Theora (Ogg/Theora).
|
||||
|
||||
Ogg stream is a sequence Ogg pages. They can be read sequentially,
|
||||
or one can jump into arbitrary stream location and scan for "OggS"
|
||||
sync code to find the beginning of a new Ogg page and continue
|
||||
decoding the stream contents from that one.
|
||||
"""
|
||||
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
KaitaiStruct.__init__(self, _io)
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.pages = []
|
||||
i = 0
|
||||
while not self._io.is_eof():
|
||||
self.pages.append(Ogg.Page(self._io, self, self._root))
|
||||
i += 1
|
||||
|
||||
class Page(KaitaiStruct):
|
||||
"""Ogg page is a basic unit of data in an Ogg bitstream, usually
|
||||
it's around 4-8 KB, with a maximum size of 65307 bytes.
|
||||
"""
|
||||
|
||||
def __init__(self, _io, _parent=None, _root=None):
|
||||
KaitaiStruct.__init__(self, _io)
|
||||
self._parent = _parent
|
||||
self._root = _root if _root else self
|
||||
self._read()
|
||||
|
||||
def _read(self):
|
||||
self.sync_code = self._io.read_bytes(4)
|
||||
if not self.sync_code == b"\x4F\x67\x67\x53":
|
||||
raise ValidationNotEqualError(b"\x4F\x67\x67\x53", self.sync_code, self._io,
|
||||
u"/types/page/seq/0")
|
||||
self.version = self._io.read_bytes(1)
|
||||
if not self.version == b"\x00":
|
||||
raise ValidationNotEqualError(b"\x00", self.version, self._io, u"/types/page/seq/1")
|
||||
self.reserved1 = self._io.read_bits_int_be(5)
|
||||
self.is_end_of_stream = self._io.read_bits_int_be(1) != 0
|
||||
self.is_beginning_of_stream = self._io.read_bits_int_be(1) != 0
|
||||
self.is_continuation = self._io.read_bits_int_be(1) != 0
|
||||
self._io.align_to_byte()
|
||||
self.granule_pos = self._io.read_u8le()
|
||||
self.bitstream_serial = self._io.read_u4le()
|
||||
self.page_seq_num = self._io.read_u4le()
|
||||
self.crc32 = self._io.read_u4le()
|
||||
self.num_segments = self._io.read_u1()
|
||||
self.len_segments = [None] * self.num_segments
|
||||
for i in range(self.num_segments):
|
||||
self.len_segments[i] = self._io.read_u1()
|
||||
|
||||
self.segments = [None] * self.num_segments
|
||||
for i in range(self.num_segments):
|
||||
self.segments[i] = self._io.read_bytes(self.len_segments[i])
|
||||
|
||||
|
||||
def generate(input_file: str, output_dir: str, max_samples: int):
|
||||
if output_dir[-1] != "/":
|
||||
output_dir += "/"
|
||||
if os.path.isdir(output_dir):
|
||||
files_to_delete = glob.glob(output_dir + "*.opus")
|
||||
if len(files_to_delete) > 0:
|
||||
print("Remove following files?")
|
||||
for file in files_to_delete:
|
||||
print(file)
|
||||
response = input("Remove files? [y/n] ").lower()
|
||||
if response != "y" and response != "yes":
|
||||
print("Cancelling...")
|
||||
return
|
||||
print("Removing files")
|
||||
for file in files_to_delete:
|
||||
os.remove(file)
|
||||
else:
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
audio_stream_file = "_audio_stream.ogg"
|
||||
if os.path.isfile(audio_stream_file):
|
||||
os.remove(audio_stream_file)
|
||||
os.system('ffmpeg -i {} -vn -ar 48000 -ac 2 -vbr off -acodec libopus -ab 64k {}'.format(input_file, audio_stream_file))
|
||||
|
||||
data = Ogg.from_file(audio_stream_file)
|
||||
index = 0
|
||||
valid_pages = data.pages[2:]
|
||||
segments = list(reduce(lambda x, y: x + y.segments, valid_pages, []))[:max_samples]
|
||||
for segment in segments:
|
||||
name = "{}sample-{}.opus".format(output_dir, index)
|
||||
index += 1
|
||||
with open(name, 'wb') as file:
|
||||
assert len(list(segment)) == 160
|
||||
file.write(segment)
|
||||
os.remove(audio_stream_file)
|
||||
|
||||
|
||||
def main(argv):
|
||||
input_file = None
|
||||
default_output_dir = "opus/"
|
||||
output_dir = default_output_dir
|
||||
max_samples = None
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "hi:o:m:", ["help", "ifile=", "odir=", "max="])
|
||||
except getopt.GetoptError:
|
||||
print('generate_opus.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-h]')
|
||||
sys.exit(2)
|
||||
for opt, arg in opts:
|
||||
if opt in ("-h", "--help"):
|
||||
print("Usage: generate_opus.py -i <input_files> [-o <output_files>] [-m <max_samples>] [-h]")
|
||||
print("Arguments:")
|
||||
print("\t-i,--ifile: Input file")
|
||||
print("\t-o,--odir: Output directory (default: " + default_output_dir + ")")
|
||||
print("\t-m,--max: Maximum generated samples")
|
||||
print("\t-h,--help: Print this help and exit")
|
||||
sys.exit()
|
||||
elif opt in ("-i", "--ifile"):
|
||||
input_file = arg
|
||||
elif opt in ("-o", "--ofile"):
|
||||
output_dir = arg
|
||||
elif opt in ("-m", "--max"):
|
||||
max_samples = int(arg)
|
||||
if input_file is None:
|
||||
print("Missing argument -i")
|
||||
sys.exit(2)
|
||||
generate(input_file, output_dir, max_samples)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
BIN
examples/streamer/samples/h264/sample-0.h264
Normal file
BIN
examples/streamer/samples/h264/sample-0.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-1.h264
Normal file
BIN
examples/streamer/samples/h264/sample-1.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-10.h264
Normal file
BIN
examples/streamer/samples/h264/sample-10.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-100.h264
Normal file
BIN
examples/streamer/samples/h264/sample-100.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-101.h264
Normal file
BIN
examples/streamer/samples/h264/sample-101.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-102.h264
Normal file
BIN
examples/streamer/samples/h264/sample-102.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-103.h264
Normal file
BIN
examples/streamer/samples/h264/sample-103.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-104.h264
Normal file
BIN
examples/streamer/samples/h264/sample-104.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-105.h264
Normal file
BIN
examples/streamer/samples/h264/sample-105.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-106.h264
Normal file
BIN
examples/streamer/samples/h264/sample-106.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-107.h264
Normal file
BIN
examples/streamer/samples/h264/sample-107.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-108.h264
Normal file
BIN
examples/streamer/samples/h264/sample-108.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-109.h264
Normal file
BIN
examples/streamer/samples/h264/sample-109.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-11.h264
Normal file
BIN
examples/streamer/samples/h264/sample-11.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-110.h264
Normal file
BIN
examples/streamer/samples/h264/sample-110.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-111.h264
Normal file
BIN
examples/streamer/samples/h264/sample-111.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-112.h264
Normal file
BIN
examples/streamer/samples/h264/sample-112.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-113.h264
Normal file
BIN
examples/streamer/samples/h264/sample-113.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-114.h264
Normal file
BIN
examples/streamer/samples/h264/sample-114.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-115.h264
Normal file
BIN
examples/streamer/samples/h264/sample-115.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-116.h264
Normal file
BIN
examples/streamer/samples/h264/sample-116.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-117.h264
Normal file
BIN
examples/streamer/samples/h264/sample-117.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-118.h264
Normal file
BIN
examples/streamer/samples/h264/sample-118.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-119.h264
Normal file
BIN
examples/streamer/samples/h264/sample-119.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-12.h264
Normal file
BIN
examples/streamer/samples/h264/sample-12.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-120.h264
Normal file
BIN
examples/streamer/samples/h264/sample-120.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-121.h264
Normal file
BIN
examples/streamer/samples/h264/sample-121.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-122.h264
Normal file
BIN
examples/streamer/samples/h264/sample-122.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-123.h264
Normal file
BIN
examples/streamer/samples/h264/sample-123.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-124.h264
Normal file
BIN
examples/streamer/samples/h264/sample-124.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-125.h264
Normal file
BIN
examples/streamer/samples/h264/sample-125.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-126.h264
Normal file
BIN
examples/streamer/samples/h264/sample-126.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-127.h264
Normal file
BIN
examples/streamer/samples/h264/sample-127.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-128.h264
Normal file
BIN
examples/streamer/samples/h264/sample-128.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-129.h264
Normal file
BIN
examples/streamer/samples/h264/sample-129.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-13.h264
Normal file
BIN
examples/streamer/samples/h264/sample-13.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-130.h264
Normal file
BIN
examples/streamer/samples/h264/sample-130.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-131.h264
Normal file
BIN
examples/streamer/samples/h264/sample-131.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-132.h264
Normal file
BIN
examples/streamer/samples/h264/sample-132.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-133.h264
Normal file
BIN
examples/streamer/samples/h264/sample-133.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-134.h264
Normal file
BIN
examples/streamer/samples/h264/sample-134.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-135.h264
Normal file
BIN
examples/streamer/samples/h264/sample-135.h264
Normal file
Binary file not shown.
BIN
examples/streamer/samples/h264/sample-136.h264
Normal file
BIN
examples/streamer/samples/h264/sample-136.h264
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user