mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-20 00:51:42 +00:00
Compare commits
1406 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60c2f59051 | ||
|
|
d55ca191b8 | ||
|
|
e8b24717c7 | ||
|
|
182758c05b | ||
|
|
74f7ba04b0 | ||
|
|
997d4fdf47 | ||
|
|
76d9e26633 | ||
|
|
a230c814cb | ||
|
|
12cb77cd7c | ||
|
|
8a9822a96b | ||
|
|
a9371a7586 | ||
|
|
6992f5421f | ||
|
|
43696956d2 | ||
|
|
ae3fd5adac | ||
|
|
404666b298 | ||
|
|
1eb4c99d15 | ||
|
|
910b9f3af7 | ||
|
|
09d90b02fb | ||
|
|
0e1f9c2ed2 | ||
|
|
f156a00117 | ||
|
|
4a6087ed56 | ||
|
|
88a09dd13a | ||
|
|
7d19596367 | ||
|
|
bd05dfa1c7 | ||
|
|
05d6660a6b | ||
|
|
1349dd4bd8 | ||
|
|
fdf298b172 | ||
|
|
453a9047e4 | ||
|
|
e97e7a7611 | ||
|
|
308bdbe298 | ||
|
|
f889b45d59 | ||
|
|
444b899a9f | ||
|
|
f121ccff0d | ||
|
|
bc2c932f92 | ||
|
|
6bdd7ce506 | ||
|
|
c12d7729e3 | ||
|
|
3635116049 | ||
|
|
6105873cbe | ||
|
|
80f87ff8ad | ||
|
|
a2368a6199 | ||
|
|
ae3cb6c83b | ||
|
|
f0f196e5b3 | ||
|
|
7c35355d29 | ||
|
|
75cc09a9e4 | ||
|
|
0e2e180635 | ||
|
|
01d1322066 | ||
|
|
ceed1d74dc | ||
|
|
e1bf623997 | ||
|
|
d46ce13ffe | ||
|
|
300eeb330d | ||
|
|
d817001259 | ||
|
|
8ac4b113a5 | ||
|
|
f3864e9abb | ||
|
|
799c3ea8a6 | ||
|
|
8d95c38e39 | ||
|
|
a7f4e29b73 | ||
|
|
b88369f5e8 | ||
|
|
ce6f1d0588 | ||
|
|
f163216a4c | ||
|
|
c449ef1064 | ||
|
|
6593f4346e | ||
|
|
ce1367a115 | ||
|
|
0d7d880261 | ||
|
|
ca2acec88c | ||
|
|
3721463eb3 | ||
|
|
6e8f8be370 | ||
|
|
d84240d8e9 | ||
|
|
1823cb2b68 | ||
|
|
41596608cc | ||
|
|
0c3b488e18 | ||
|
|
7d7af287f6 | ||
|
|
4119622994 | ||
|
|
d528a77963 | ||
|
|
dab7e1b7a2 | ||
|
|
ab49345460 | ||
|
|
608d82423d | ||
|
|
e0e15eafeb | ||
|
|
0fb24538a7 | ||
|
|
d65547ea74 | ||
|
|
bfe15245a6 | ||
|
|
ff58c5156a | ||
|
|
6d4326a560 | ||
|
|
97ba195b88 | ||
|
|
3d546428ab | ||
|
|
b64dfacff3 | ||
|
|
dcbf02a1ec | ||
|
|
7e1ec28839 | ||
|
|
ef16317f8f | ||
|
|
26e8e587f9 | ||
|
|
0d0492bfcc | ||
|
|
3b33abfc7b | ||
|
|
99033481e0 | ||
|
|
b4901985b7 | ||
|
|
97edb6c68f | ||
|
|
73de3364b7 | ||
|
|
5551981b3f | ||
|
|
90572a3cc5 | ||
|
|
c405188052 | ||
|
|
bea0565ac9 | ||
|
|
0b03454366 | ||
|
|
489701cacc | ||
|
|
a769d56ec1 | ||
|
|
6f0c185a05 | ||
|
|
a60f312d19 | ||
|
|
acb786a791 | ||
|
|
df347e3d80 | ||
|
|
e4aa45f34b | ||
|
|
75cc3cda28 | ||
|
|
45f4472d42 | ||
|
|
69cb53b51b | ||
|
|
70a66fc943 | ||
|
|
9207d788ab | ||
|
|
ef3a31aa20 | ||
|
|
63775362fe | ||
|
|
55493b0c18 | ||
|
|
1696557c46 | ||
|
|
ecd376be4c | ||
|
|
f54c0f70f6 | ||
|
|
9bc998f4b0 | ||
|
|
43d188a429 | ||
|
|
563d5334c9 | ||
|
|
193b0a222c | ||
|
|
76f5d153fa | ||
|
|
19caeca990 | ||
|
|
0abae17653 | ||
|
|
81d10bc656 | ||
|
|
b51b86525d | ||
|
|
ace37c517e | ||
|
|
ac751d3224 | ||
|
|
7c9a3c4d77 | ||
|
|
8be693f55d | ||
|
|
622ae29dff | ||
|
|
e7c98feca2 | ||
|
|
28087424ec | ||
|
|
b6830638df | ||
|
|
fb557f49ea | ||
|
|
746f950a0b | ||
|
|
9a38a3e188 | ||
|
|
2e3e3a47b9 | ||
|
|
e27d6d7bb8 | ||
|
|
1dec0393a1 | ||
|
|
d03b020326 | ||
|
|
71e29b1d91 | ||
|
|
f0b0965f9b | ||
|
|
f774e47c80 | ||
|
|
761933a164 | ||
|
|
99e0f08a6f | ||
|
|
e89d5eb692 | ||
|
|
2501e11886 | ||
|
|
9174f23f36 | ||
|
|
9f6544fa87 | ||
|
|
9a1af132bf | ||
|
|
a8aacd3657 | ||
|
|
8ff81f5a2a | ||
|
|
349e273ecc | ||
|
|
0a2ab4f0d7 | ||
|
|
7c1a7332e1 | ||
|
|
172e438cd6 | ||
|
|
1a7a0db1ff | ||
|
|
11e5faf469 | ||
|
|
c7118f8ade | ||
|
|
7933d2cbe5 | ||
|
|
ce8c71b1f3 | ||
|
|
da9a575526 | ||
|
|
7068551a3e | ||
|
|
bd9484a2f4 | ||
|
|
b9fa3a4398 | ||
|
|
88b93c220e | ||
|
|
0a937bb5b9 | ||
|
|
613d5eda0d | ||
|
|
18c8cce6a7 | ||
|
|
36000ddb36 | ||
|
|
de6d6c9a5c | ||
|
|
6d99c0ac6c | ||
|
|
164e9a2c7d | ||
|
|
f3fb95af70 | ||
|
|
40a9fc44ff | ||
|
|
f0ac048645 | ||
|
|
81498e6af9 | ||
|
|
f1056efa01 | ||
|
|
54fbec27df | ||
|
|
fd82153456 | ||
|
|
7b471d8c62 | ||
|
|
175c6c1f01 | ||
|
|
ac7dfa5042 | ||
|
|
a732854866 | ||
|
|
6171856020 | ||
|
|
d5d744a390 | ||
|
|
8b1274d744 | ||
|
|
26c33a9a56 | ||
|
|
5fad2b1056 | ||
|
|
32ec07ee01 | ||
|
|
15c5a820bf | ||
|
|
e15fabd2e1 | ||
|
|
1aa8446725 | ||
|
|
b1965f74a8 | ||
|
|
0c58a0a705 | ||
|
|
467c9fd686 | ||
|
|
b505bbefd1 | ||
|
|
5c6b78ea2b | ||
|
|
f89274fb13 | ||
|
|
1137582a7a | ||
|
|
e26e3b6aa8 | ||
|
|
456d3e16a6 | ||
|
|
e210073044 | ||
|
|
0068b6aea3 | ||
|
|
12c92a822d | ||
|
|
d2a5aaafdd | ||
|
|
bedef476fd | ||
|
|
59386635e7 | ||
|
|
a846ed062f | ||
|
|
8b5dc53cc7 | ||
|
|
59700882f1 | ||
|
|
025af24523 | ||
|
|
c0ff6198b3 | ||
|
|
67b1c85315 | ||
|
|
0b113ad9ce | ||
|
|
c8482cd6d2 | ||
|
|
d6d5321828 | ||
|
|
8d91343bf5 | ||
|
|
488ba79379 | ||
|
|
de212a9dd0 | ||
|
|
8d8f120cc3 | ||
|
|
57490e0002 | ||
|
|
1de1900dbb | ||
|
|
4a3e43d4a7 | ||
|
|
9e37ee13de | ||
|
|
7b95c5341a | ||
|
|
5283726cce | ||
|
|
0b8f4b5e6c | ||
|
|
a5f7342fce | ||
|
|
6df7425440 | ||
|
|
d7b68f3f95 | ||
|
|
e28295fc7b | ||
|
|
6df500e726 | ||
|
|
83cfb8b19f | ||
|
|
82b25d0ec9 | ||
|
|
b44f27ddfa | ||
|
|
fa78f30e30 | ||
|
|
46ae19d082 | ||
|
|
77124a229e | ||
|
|
1462a5bd46 | ||
|
|
44099d9a21 | ||
|
|
c2ceebfb9c | ||
|
|
0a0d9245e2 | ||
|
|
7bd3f9f0bd | ||
|
|
4801ae2499 | ||
|
|
0e9172f9a7 | ||
|
|
6db5f80430 | ||
|
|
898076f698 | ||
|
|
195e101816 | ||
|
|
97a8f8f47b | ||
|
|
9408fd5176 | ||
|
|
00c2cd7dab | ||
|
|
cbb62fb98f | ||
|
|
a11fcb605d | ||
|
|
139b1720b2 | ||
|
|
011f0ff536 | ||
|
|
75a67202e5 | ||
|
|
186e1dc54b | ||
|
|
f2393feeef | ||
|
|
66e35128c9 | ||
|
|
6933fefe55 | ||
|
|
fedf828120 | ||
|
|
eb64ea9fc7 | ||
|
|
826ed7fe3c | ||
|
|
a2d314b0d9 | ||
|
|
6b37ca9f9a | ||
|
|
0614913f1a | ||
|
|
5fafaf6272 | ||
|
|
397a296e25 | ||
|
|
1d9ed8f458 | ||
|
|
bbb622aaa6 | ||
|
|
8cbe7a6257 | ||
|
|
932308b497 | ||
|
|
9398278250 | ||
|
|
c9a75a119a | ||
|
|
04ee8dbe79 | ||
|
|
7fd7192b16 | ||
|
|
63c1f80d60 | ||
|
|
d54c2665dc | ||
|
|
8aac46542d | ||
|
|
c78e1499d7 | ||
|
|
a34df5589e | ||
|
|
eb2c6e19f8 | ||
|
|
c25d48fd0c | ||
|
|
794403dcea | ||
|
|
d89b7dd412 | ||
|
|
31ced30c1e | ||
|
|
74a306d47a | ||
|
|
5bf5821138 | ||
|
|
eed28a5852 | ||
|
|
435e026c08 | ||
|
|
739267d36d | ||
|
|
85118a023d | ||
|
|
ae72c0b296 | ||
|
|
b72885b4f8 | ||
|
|
70b0d7cb03 | ||
|
|
053ca47d4e | ||
|
|
74efe5aafe | ||
|
|
fe5d7dd6ba | ||
|
|
0b5df467e1 | ||
|
|
80134164a4 | ||
|
|
76b26c2df5 | ||
|
|
f8635e8abf | ||
|
|
4029b5d84d | ||
|
|
4c527b9b08 | ||
|
|
caa03d23a3 | ||
|
|
297863b17a | ||
|
|
1992b67ee3 | ||
|
|
e2314f3528 | ||
|
|
340b26bada | ||
|
|
7f000fecc4 | ||
|
|
76c901ce78 | ||
|
|
57fc614074 | ||
|
|
89d15f061b | ||
|
|
f5b1028b5a | ||
|
|
ad3e2d7d3b | ||
|
|
3818a8b3b6 | ||
|
|
59e8b60267 | ||
|
|
de5d7d3c17 | ||
|
|
ebd41fe0bb | ||
|
|
70960b0251 | ||
|
|
c8afe9bc2f | ||
|
|
1ecaaafa6c | ||
|
|
33a01b3146 | ||
|
|
1d8456e2bf | ||
|
|
b5bacc09a7 | ||
|
|
876c650471 | ||
|
|
3eae079db4 | ||
|
|
122fdf48b2 | ||
|
|
d94f49b3ba | ||
|
|
39670fc9c0 | ||
|
|
6f0d50b9cb | ||
|
|
873b2ed13c | ||
|
|
321c144d21 | ||
|
|
13e1af259c | ||
|
|
9d7792ead0 | ||
|
|
6d93d3c250 | ||
|
|
156822dbc8 | ||
|
|
208f82d6f2 | ||
|
|
a818c7fd47 | ||
|
|
50b13d2f36 | ||
|
|
b7807bf9d2 | ||
|
|
ea7d11b1db | ||
|
|
212f378d08 | ||
|
|
2faba39b58 | ||
|
|
064989f2e4 | ||
|
|
6b1b4796c2 | ||
|
|
1ebb83e0f2 | ||
|
|
9108a93598 | ||
|
|
c7e97f45f5 | ||
|
|
5af85dd1bb | ||
|
|
b20158015c | ||
|
|
b1bb63f9a4 | ||
|
|
056e68f2ae | ||
|
|
f6e4df6a18 | ||
|
|
0cde67143a | ||
|
|
1f9645afe9 | ||
|
|
86285b427f | ||
|
|
e548a5f705 | ||
|
|
8eafe7f325 | ||
|
|
6aebba5452 | ||
|
|
664a3c2463 | ||
|
|
321dfaf0a2 | ||
|
|
8373e0361b | ||
|
|
8c680a26f8 | ||
|
|
d9971a705a | ||
|
|
85fe722f4c | ||
|
|
c04f463b78 | ||
|
|
307da3ad2d | ||
|
|
39f80730de | ||
|
|
35cfe0d92c | ||
|
|
8d218dbca4 | ||
|
|
938f835142 | ||
|
|
1dc55c03dc | ||
|
|
859b457c3d | ||
|
|
8958c4eabd | ||
|
|
7c9517ce5b | ||
|
|
5bf9720ccf | ||
|
|
d1587ed2c1 | ||
|
|
5fcb67aaf5 | ||
|
|
2e5fece594 | ||
|
|
863cfde394 | ||
|
|
c4728d291e | ||
|
|
fb45a5b314 | ||
|
|
86c1f8ae50 | ||
|
|
381c25c573 | ||
|
|
62d5301b1f | ||
|
|
40cc743cc7 | ||
|
|
ecfe95383c | ||
|
|
ba93f79d8b | ||
|
|
dcc2764844 | ||
|
|
cbaff52850 | ||
|
|
a2ecb1027a | ||
|
|
680aabbe7c | ||
|
|
55b0fe0082 | ||
|
|
8b8c3d5462 | ||
|
|
38a7786f22 | ||
|
|
df182f382e | ||
|
|
5cc7573574 | ||
|
|
17401e10f0 | ||
|
|
73439d8213 | ||
|
|
72a79aac53 | ||
|
|
c89678971c | ||
|
|
64b03704f8 | ||
|
|
630fc55bde | ||
|
|
f90e429bf9 | ||
|
|
2e0709f05b | ||
|
|
11d46713d1 | ||
|
|
ef442d775d | ||
|
|
3e6c3c3e98 | ||
|
|
8d57b73b41 | ||
|
|
d8c8643b52 | ||
|
|
adb6098353 | ||
|
|
938d5379e6 | ||
|
|
7cd15aa049 | ||
|
|
f8ed48af98 | ||
|
|
c274d1790f | ||
|
|
6699f86361 | ||
|
|
c2b1ed3edd | ||
|
|
2c27ad073a | ||
|
|
31bb55c319 | ||
|
|
dd4230d743 | ||
|
|
cff475c1fc | ||
|
|
d564e19ef3 | ||
|
|
1d6eb629ad | ||
|
|
406e18663d | ||
|
|
ab70220ecf | ||
|
|
560123ab7d | ||
|
|
4e1964156e | ||
|
|
5ff3d837b6 | ||
|
|
74d7107ac6 | ||
|
|
e21a246a4d | ||
|
|
59a8e9da57 | ||
|
|
795c60da01 | ||
|
|
f3db329115 | ||
|
|
f07799c67b | ||
|
|
eec4f53a65 | ||
|
|
01d8d0c795 | ||
|
|
6729a7d6b1 | ||
|
|
0babbf317c | ||
|
|
337e89cf6e | ||
|
|
1046371349 | ||
|
|
665a6e356a | ||
|
|
b7ea481bf9 | ||
|
|
7a34f5d17d | ||
|
|
e8d5759d95 | ||
|
|
eeae84c715 | ||
|
|
dab6f40b46 | ||
|
|
5c2c5ccd07 | ||
|
|
8cbfe35bd4 | ||
|
|
954f8c40f5 | ||
|
|
6a49173cea | ||
|
|
1043aef7f7 | ||
|
|
f7bcf53059 | ||
|
|
6a159c5bd2 | ||
|
|
a098814ea0 | ||
|
|
a28832e52f | ||
|
|
f84018af5f | ||
|
|
93859eb84f | ||
|
|
e7cf6bf7c5 | ||
|
|
a8fd88840d | ||
|
|
c84bd744c8 | ||
|
|
11b99a016a | ||
|
|
dfa46cbddd | ||
|
|
91151e33bb | ||
|
|
2ed9ae1652 | ||
|
|
fdfea35161 | ||
|
|
47783842e9 | ||
|
|
7f88cf768d | ||
|
|
cb2c9eb6d5 | ||
|
|
4a1e520758 | ||
|
|
b3aa659f93 | ||
|
|
4870d83ad1 | ||
|
|
7210a8fd28 | ||
|
|
0f947613a9 | ||
|
|
ea73c10cd8 | ||
|
|
6d3d52f923 | ||
|
|
dbd1f1781e | ||
|
|
96e9a6989c | ||
|
|
14228d82f3 | ||
|
|
040731447f | ||
|
|
5175829bab | ||
|
|
9d7293734a | ||
|
|
1e5ec362f7 | ||
|
|
7bb3e4efc3 | ||
|
|
67de595c85 | ||
|
|
82152e90fe | ||
|
|
1e7d439899 | ||
|
|
71d06a1a20 | ||
|
|
698feff2ff | ||
|
|
3ef9ffaf34 | ||
|
|
3025a182cc | ||
|
|
febc41c85d | ||
|
|
627ca6db75 | ||
|
|
c80b0de2c4 | ||
|
|
979984fa6b | ||
|
|
57e9fb33d2 | ||
|
|
f5a292dadd | ||
|
|
4e9e7ec8f5 | ||
|
|
c69b8f00d0 | ||
|
|
0542b50f76 | ||
|
|
1077ef9fb7 | ||
|
|
4376c94dc1 | ||
|
|
19f7a5f108 | ||
|
|
4514ff8071 | ||
|
|
b66d72f5c2 | ||
|
|
c6f6b352fb | ||
|
|
882399c65e | ||
|
|
2287281066 | ||
|
|
cd4b7b6cc7 | ||
|
|
da5458a2d2 | ||
|
|
4e0ed79864 | ||
|
|
6767afd400 | ||
|
|
aa2de65bad | ||
|
|
877c7f51c1 | ||
|
|
912b74151f | ||
|
|
f73db4a49b | ||
|
|
cf828ca858 | ||
|
|
7847c69231 | ||
|
|
0e4a1c5dae | ||
|
|
551504e773 | ||
|
|
85e83f1bba | ||
|
|
7eac3fcbda | ||
|
|
e9b0b9d8f6 | ||
|
|
cad1b9413c | ||
|
|
3324bcc5ce | ||
|
|
20c8d29ae9 | ||
|
|
110726c5bf | ||
|
|
7db60f8e7c | ||
|
|
b3f1e74317 | ||
|
|
e301adb22b | ||
|
|
3438a17341 | ||
|
|
e4deda5ccb | ||
|
|
80618a2e64 | ||
|
|
e45b6e0f65 | ||
|
|
86d4899a54 | ||
|
|
9d8a508cd5 | ||
|
|
c9ead29f44 | ||
|
|
f77fd1eca9 | ||
|
|
5a5e753921 | ||
|
|
3e4e5261fe | ||
|
|
cf4afb6feb | ||
|
|
55fa1e5e76 | ||
|
|
c101655419 | ||
|
|
51165ba0aa | ||
|
|
f03b31f433 | ||
|
|
8e797a1a1d | ||
|
|
fd32350dc6 | ||
|
|
b9c8ebeffa | ||
|
|
ec12b0ffe2 | ||
|
|
7fd5947486 | ||
|
|
b68e1c5570 | ||
|
|
ded1beb949 | ||
|
|
a6c7789b5e | ||
|
|
73f8412b42 | ||
|
|
529d7f5fe3 | ||
|
|
a6c1e18aef | ||
|
|
8a37b81f4e | ||
|
|
f81b6fbcd6 | ||
|
|
223e61df2a | ||
|
|
303df626a2 | ||
|
|
9e8ad778dd | ||
|
|
1f18cc5416 | ||
|
|
e68a71022c | ||
|
|
e4a8bff70e | ||
|
|
10982e0275 | ||
|
|
8edc02b06d | ||
|
|
4b2aaee9ea | ||
|
|
9fdb33b6af | ||
|
|
3faecdb353 | ||
|
|
db96f41ad7 | ||
|
|
10fbf17d42 | ||
|
|
ef85809690 | ||
|
|
95bce5d656 | ||
|
|
cedbafeb28 | ||
|
|
7c1d2f4bc4 | ||
|
|
8099d6dbd7 | ||
|
|
8e01b68cf6 | ||
|
|
d707a07f84 | ||
|
|
5ab20bb27c | ||
|
|
8125a179fb | ||
|
|
041563f8ea | ||
|
|
c3f7c85f8a | ||
|
|
12b2efa489 | ||
|
|
5d403a7b49 | ||
|
|
3c88f94b43 | ||
|
|
06636a0e1c | ||
|
|
8416b4e42c | ||
|
|
1a0ed61f78 | ||
|
|
68501759fa | ||
|
|
dd85b1e519 | ||
|
|
2d3cf1bdb1 | ||
|
|
baa8021c79 | ||
|
|
133540f577 | ||
|
|
43c103c0ac | ||
|
|
162ac787b7 | ||
|
|
fa7d5bc023 | ||
|
|
9d45088127 | ||
|
|
7ef56de3f2 | ||
|
|
0d76b3f308 | ||
|
|
92c4c49b01 | ||
|
|
855df5d679 | ||
|
|
c8d9cc7e5b | ||
|
|
1c1fbf14cf | ||
|
|
9e4dc235d7 | ||
|
|
4bd8cf6f5c | ||
|
|
d9c754c30f | ||
|
|
ea028a3822 | ||
|
|
7d9e6b7e22 | ||
|
|
17b5cace5b | ||
|
|
2fa41b23b9 | ||
|
|
9f5f6111d4 | ||
|
|
0782164120 | ||
|
|
5439fe7b16 | ||
|
|
55111ac35f | ||
|
|
f0cc5d9ca8 | ||
|
|
ccfeab5ac9 | ||
|
|
7e011bda6f | ||
|
|
8418c39120 | ||
|
|
4b8ef57a99 | ||
|
|
d910b0b2a2 | ||
|
|
80c8de7d69 | ||
|
|
7f776deae2 | ||
|
|
8209ba8a00 | ||
|
|
25c8e873d0 | ||
|
|
e24db75f08 | ||
|
|
5df7463663 | ||
|
|
eb7331f2ab | ||
|
|
03bd0820cc | ||
|
|
63768166ea | ||
|
|
bd899a7a7c | ||
|
|
301442ee43 | ||
|
|
de9dd1180b | ||
|
|
436ed4d1e3 | ||
|
|
88aa273e55 | ||
|
|
d11d8409a8 | ||
|
|
9385d1b6d8 | ||
|
|
b90653f3e3 | ||
|
|
1243cf04ea | ||
|
|
4f5b5b1377 | ||
|
|
f43675e2bd | ||
|
|
417cf33f90 | ||
|
|
067b1f3ee0 | ||
|
|
3d2f41d081 | ||
|
|
49de28d3d0 | ||
|
|
c34cf11769 | ||
|
|
42facbb07e | ||
|
|
9f97cd61bf | ||
|
|
59133a7d93 | ||
|
|
b484cbba7c | ||
|
|
61e34621cc | ||
|
|
5e9575de66 | ||
|
|
c02d34dbf3 | ||
|
|
8f97637b71 | ||
|
|
878d3a6f4f | ||
|
|
ef25bbde75 | ||
|
|
8c3efa5926 | ||
|
|
e6656326a8 | ||
|
|
cf8cab850b | ||
|
|
db5dfa1746 | ||
|
|
13457d1bf4 | ||
|
|
8f8c4d3d95 | ||
|
|
d47bd1ecbc | ||
|
|
e9fcb5381a | ||
|
|
3e2631f49b | ||
|
|
3c3236c5d5 | ||
|
|
23462ec7df | ||
|
|
ac56b0a33e | ||
|
|
b55c604c0a | ||
|
|
a72f8f3bcd | ||
|
|
27c0405fc9 | ||
|
|
b9e80ecfdc | ||
|
|
a3adb38bef | ||
|
|
593ba37c43 | ||
|
|
4ed78a84ef | ||
|
|
e893662c0a | ||
|
|
5df78c520c | ||
|
|
f13f886886 | ||
|
|
5dd8e4dc7f | ||
|
|
883fd5b062 | ||
|
|
7ddbf20108 | ||
|
|
23135d7a5a | ||
|
|
b91ab0b44f | ||
|
|
9a2ec13ba4 | ||
|
|
78995f5cca | ||
|
|
f225c4773a | ||
|
|
bac4b3d5cb | ||
|
|
e3f99a4a22 | ||
|
|
df7d3261c9 | ||
|
|
95ad4783f1 | ||
|
|
b5b0899226 | ||
|
|
9a0a1baa6b | ||
|
|
6ad9f45d9a | ||
|
|
7ec223d445 | ||
|
|
ed9e837229 | ||
|
|
b9a08e8260 | ||
|
|
58931e1d30 | ||
|
|
976ca80056 | ||
|
|
95e5295666 | ||
|
|
4b16999fbc | ||
|
|
318d23ba1c | ||
|
|
9c4c7c08bf | ||
|
|
9016a9e8b8 | ||
|
|
4c8c41fdc0 | ||
|
|
205d3b1d04 | ||
|
|
1575fff07a | ||
|
|
8c7338f2ba | ||
|
|
459cc65b3f | ||
|
|
3d28275675 | ||
|
|
6f41b3cde7 | ||
|
|
9ffc09a11b | ||
|
|
ced2608afd | ||
|
|
adbca532c0 | ||
|
|
5e38d8f28a | ||
|
|
d6ef2c04a5 | ||
|
|
ce69783871 | ||
|
|
10b13da3da | ||
|
|
88afefe464 | ||
|
|
80abf6bf24 | ||
|
|
94359709a1 | ||
|
|
8501b7c9e2 | ||
|
|
0ce0855a6f | ||
|
|
e1dabbc2d5 | ||
|
|
c08a489e27 | ||
|
|
28a2ba4bf8 | ||
|
|
2b8b8e7403 | ||
|
|
bdaf60b2d6 | ||
|
|
7c2f994a66 | ||
|
|
bb9f5b7491 | ||
|
|
c091d74de4 | ||
|
|
19be09c361 | ||
|
|
57d053a8fb | ||
|
|
8c2a148ed1 | ||
|
|
0e43765c53 | ||
|
|
6fbaf57b99 | ||
|
|
12aee3e022 | ||
|
|
2df00647d5 | ||
|
|
498a8e2b7d | ||
|
|
99395273c7 | ||
|
|
4568156bdf | ||
|
|
ce7611562f | ||
|
|
81fd0eefac | ||
|
|
b5fd15e052 | ||
|
|
ecfc684174 | ||
|
|
4600ea135c | ||
|
|
3b82ba8945 | ||
|
|
c32fc57ce1 | ||
|
|
9a8f66e3e5 | ||
|
|
ef39713219 | ||
|
|
da68fea081 | ||
|
|
1f93a4ab4f | ||
|
|
7a587390ba | ||
|
|
378a145cf7 | ||
|
|
3625f11e08 | ||
|
|
1332ba3ad2 | ||
|
|
558b2ffa41 | ||
|
|
1315a3967d | ||
|
|
00b76d42e0 | ||
|
|
e7c4150c02 | ||
|
|
bc06c843c7 | ||
|
|
b8e9439d32 | ||
|
|
046d385a78 | ||
|
|
5caf75237b | ||
|
|
a19b1b9341 | ||
|
|
0b1e876dcf | ||
|
|
ae8c8ec230 | ||
|
|
ec8c0eb3c2 | ||
|
|
3d898dd8f7 | ||
|
|
df18e178ee | ||
|
|
71a6e08988 | ||
|
|
a05636d9a6 | ||
|
|
5eecbfd9bf | ||
|
|
65514b3028 | ||
|
|
881d55f858 | ||
|
|
cc20d9d0fe | ||
|
|
35aa544415 | ||
|
|
bad4ca4666 | ||
|
|
5fecc57e8a | ||
|
|
e1f4e3d2d7 | ||
|
|
5ace9e4189 | ||
|
|
9945459a41 | ||
|
|
22c11fad36 | ||
|
|
c6b527d452 | ||
|
|
8e9911d330 | ||
|
|
b245725941 | ||
|
|
154ae5dcbf | ||
|
|
bae2037086 | ||
|
|
c0bc8d7028 | ||
|
|
736b5ff424 | ||
|
|
29cd2b11be | ||
|
|
dcf773c3ef | ||
|
|
791cabbc26 | ||
|
|
3c7d8063f6 | ||
|
|
3381340eb5 | ||
|
|
6446b9ef10 | ||
|
|
6c19eb59b2 | ||
|
|
797d049f31 | ||
|
|
0bcafedebf | ||
|
|
7e0e8286eb | ||
|
|
5ae1162378 | ||
|
|
c80c4ae55c | ||
|
|
254b88bdbe | ||
|
|
2657d89178 | ||
|
|
38ec59f03a | ||
|
|
259cf1ff3a | ||
|
|
b12658afc2 | ||
|
|
c23880f829 | ||
|
|
41dd9e4f06 | ||
|
|
3d1f69075a | ||
|
|
dc94a3fac9 | ||
|
|
468cac543f | ||
|
|
d4b48216e8 | ||
|
|
f83ff41e47 | ||
|
|
891ce06312 | ||
|
|
0658ba6f77 | ||
|
|
29d8d1d74c | ||
|
|
8d2efb2838 | ||
|
|
e07a40a16d | ||
|
|
38a3eda6a7 | ||
|
|
6d1ab7606b | ||
|
|
77edf1e103 | ||
|
|
28c8678ea2 | ||
|
|
34acd9d47c | ||
|
|
05ca76ea99 | ||
|
|
d1065f0bd1 | ||
|
|
cf0ec7b9a9 | ||
|
|
46085824ae | ||
|
|
7067179b28 | ||
|
|
4828a69867 | ||
|
|
3fcc808e99 | ||
|
|
15cdd661a4 | ||
|
|
60d9f3de68 | ||
|
|
f87baaa4fc | ||
|
|
fecd8a448f | ||
|
|
fe4ef75cf6 | ||
|
|
87c7a9db54 | ||
|
|
fa2ce40084 | ||
|
|
4eac3fe9a0 | ||
|
|
d7efd62511 | ||
|
|
e10b399f51 | ||
|
|
39a8195386 | ||
|
|
43331419f6 | ||
|
|
c66a55d81a | ||
|
|
9a44a38141 | ||
|
|
5f69cb2a5b | ||
|
|
a5f63c3ae3 | ||
|
|
0053962182 | ||
|
|
c462d0b249 | ||
|
|
778edaeb28 | ||
|
|
273a395a2f | ||
|
|
aa49ebd47f | ||
|
|
c45384b91a | ||
|
|
88fdbc6d3e | ||
|
|
7ca6c5ef34 | ||
|
|
511d551546 | ||
|
|
7ea13f7e03 | ||
|
|
6754703ad1 | ||
|
|
89b8e5435f | ||
|
|
b0ab351f7f | ||
|
|
9b6f4ecd0d | ||
|
|
9142cbb820 | ||
|
|
1f6b386325 | ||
|
|
aad1266a94 | ||
|
|
06a40680aa | ||
|
|
cc2cbf810d | ||
|
|
cf9aa90321 | ||
|
|
d51f6c45e5 | ||
|
|
017ec83ce3 | ||
|
|
6641612de5 | ||
|
|
218718a063 | ||
|
|
353041535c | ||
|
|
e4234f5198 | ||
|
|
d81840d6c6 | ||
|
|
1d336996be | ||
|
|
86c43df8d1 | ||
|
|
5c1b7ecd17 | ||
|
|
011ac1fa22 | ||
|
|
b8e5258cf3 | ||
|
|
56179088bb | ||
|
|
780e9580b7 | ||
|
|
b775b01e0a | ||
|
|
0a8e0e9746 | ||
|
|
89805a44c1 | ||
|
|
9caa0af4d9 | ||
|
|
70531762b9 | ||
|
|
30f4ecd171 | ||
|
|
d56c3f9adf | ||
|
|
73a97c1774 | ||
|
|
8ec2b1d96f | ||
|
|
c858e0391d | ||
|
|
63c9af4578 | ||
|
|
473a87a76b | ||
|
|
4c6f6536b8 | ||
|
|
cb4d8a6555 | ||
|
|
417545d521 | ||
|
|
7f88bd15d1 | ||
|
|
7b3f6eeae2 | ||
|
|
f6df7cad9b | ||
|
|
15cb505163 | ||
|
|
cc1b94afd2 | ||
|
|
a7eca7d4bd | ||
|
|
9047c98e68 | ||
|
|
53d0789660 | ||
|
|
ee24a6f4fc | ||
|
|
70616c48e3 | ||
|
|
d4bbd5cc6f | ||
|
|
1affae956b | ||
|
|
7463e02080 | ||
|
|
d3ed26f7cc | ||
|
|
f711ced4ca | ||
|
|
1ec89fc4f3 | ||
|
|
a92b7298f9 | ||
|
|
de07b01a75 | ||
|
|
4c6c096c50 | ||
|
|
c2ae897b02 | ||
|
|
b2e1291973 | ||
|
|
94c2b65798 | ||
|
|
760d6a26d3 | ||
|
|
3a419768ca | ||
|
|
d8ee44349c | ||
|
|
ee42448504 | ||
|
|
d90cf843e8 | ||
|
|
f6b700ea97 | ||
|
|
a5fe605aae | ||
|
|
7818c3bda3 | ||
|
|
d5e5755ff4 | ||
|
|
8fbc23b1fa | ||
|
|
d05f0820b2 | ||
|
|
dad44cc928 | ||
|
|
2c80587d11 | ||
|
|
80eb056432 | ||
|
|
80e546b79f | ||
|
|
6eba04ed8e | ||
|
|
c606671d27 | ||
|
|
9bb37d5df0 | ||
|
|
e952236e1a | ||
|
|
9a3100b221 | ||
|
|
7e9754acff | ||
|
|
b78592d622 | ||
|
|
052f746c68 | ||
|
|
c4ceec145a | ||
|
|
775386d8b3 | ||
|
|
451c08ef7b | ||
|
|
52c8743b36 | ||
|
|
5c59b9aeb3 | ||
|
|
3e00dabd16 | ||
|
|
c5c069743b | ||
|
|
ba241cd7f9 | ||
|
|
ddb97bfafb | ||
|
|
e415ca66b6 | ||
|
|
85be299da1 | ||
|
|
9dd5bea7a5 | ||
|
|
e71abedefb | ||
|
|
3888d7726a | ||
|
|
fc48944b47 | ||
|
|
2150f5879b | ||
|
|
85156d15ca | ||
|
|
6943d14f93 | ||
|
|
ade4e87d6f | ||
|
|
8007a7b153 | ||
|
|
e8643600f2 | ||
|
|
cb7f1f6e3e | ||
|
|
af7132be82 | ||
|
|
ec080118f4 | ||
|
|
6bcc0e2d82 | ||
|
|
2fd81a7e33 | ||
|
|
2f99f81aa6 | ||
|
|
97f1f1c9c4 | ||
|
|
0fbb2e8a14 | ||
|
|
0158fd35f0 | ||
|
|
80b15b7fe5 | ||
|
|
2acca6eeb1 | ||
|
|
151c89fb5d | ||
|
|
4f22ac4100 | ||
|
|
7c1da2dfcd | ||
|
|
b5d32ec844 | ||
|
|
aec16a934f | ||
|
|
075155b431 | ||
|
|
c25c62e0af | ||
|
|
e653a228fa | ||
|
|
b2831347bc | ||
|
|
7845caa100 | ||
|
|
66d214c8a8 | ||
|
|
277cf2a08f | ||
|
|
7bf44e951c | ||
|
|
c0ce4270f0 | ||
|
|
d6e8de21ef | ||
|
|
1635cba827 | ||
|
|
a786470623 | ||
|
|
4164e670d2 | ||
|
|
062470ef68 | ||
|
|
0b80bd2b09 | ||
|
|
e1515299c2 | ||
|
|
bc99d341fb | ||
|
|
0f9f7309e3 | ||
|
|
e6244289ff | ||
|
|
30fbfe46e6 | ||
|
|
a73166a665 | ||
|
|
d66f65e376 | ||
|
|
b979fc2a67 | ||
|
|
58092c5190 | ||
|
|
6615a34e99 | ||
|
|
50a2612839 | ||
|
|
eb4350033b | ||
|
|
384ad57d21 | ||
|
|
4b516de183 | ||
|
|
c7d9ecbab3 | ||
|
|
b2795af2b8 | ||
|
|
6c6c087e30 | ||
|
|
3c77faaf61 | ||
|
|
6272244a73 | ||
|
|
a532518056 | ||
|
|
320db8df18 | ||
|
|
d1fb8cc209 | ||
|
|
7d33059c20 | ||
|
|
ed0f197d1b | ||
|
|
25ad7a6230 | ||
|
|
5fb31baea6 | ||
|
|
ffb384ebfc | ||
|
|
95098c5496 | ||
|
|
3862668420 | ||
|
|
5ac68f74d4 | ||
|
|
b8ad1de33c | ||
|
|
f80312b86b | ||
|
|
836dc7a880 | ||
|
|
7283293887 | ||
|
|
710bd586d5 | ||
|
|
cb4cc63221 | ||
|
|
838d8abf63 | ||
|
|
94bd629b80 | ||
|
|
73d6e98bf2 | ||
|
|
c90a8c586f | ||
|
|
9092b74f4e | ||
|
|
b3d28c7e3a | ||
|
|
ed92385469 | ||
|
|
1247976a34 | ||
|
|
880bc7db9f | ||
|
|
20f23e0e31 | ||
|
|
d9efcbc7a9 | ||
|
|
5e2a07d58a | ||
|
|
620836e1cb | ||
|
|
af2f54720d | ||
|
|
236ef199aa | ||
|
|
1ea5b21dcf | ||
|
|
e6054f543a | ||
|
|
2aaaed34f9 | ||
|
|
fef8f933d9 | ||
|
|
d8de5e4f19 | ||
|
|
554543c5d0 | ||
|
|
44821c3e3c | ||
|
|
8cc066ecc4 | ||
|
|
2540f102b0 | ||
|
|
8b3f4aa68c | ||
|
|
0537f9d0d7 | ||
|
|
4b0980fb86 | ||
|
|
65e4eeeb7c | ||
|
|
298dc54910 | ||
|
|
fb486e381f | ||
|
|
32001427d4 | ||
|
|
2a43813d14 | ||
|
|
b4c0583896 | ||
|
|
88b985be5d | ||
|
|
7528b6b8bf | ||
|
|
e25d9fc96a | ||
|
|
b1a3ded2fa | ||
|
|
bc6da8effa | ||
|
|
5d26b13553 | ||
|
|
0d7106450b | ||
|
|
60647a2f8c | ||
|
|
76ee093e92 | ||
|
|
9e6635ec13 | ||
|
|
db91e20ec7 | ||
|
|
3cd20d3ff2 | ||
|
|
c2642f39ed | ||
|
|
7182b9a6b5 | ||
|
|
ac9e42af36 | ||
|
|
790862db4b | ||
|
|
bb9988365f | ||
|
|
f10f8558eb | ||
|
|
26c27756af | ||
|
|
c76e60324b | ||
|
|
d98ceb60c3 | ||
|
|
2e050b0540 | ||
|
|
44ba8823d7 | ||
|
|
4c42712b24 | ||
|
|
6a0485a72c | ||
|
|
967400f181 | ||
|
|
a142cc48d3 | ||
|
|
3469f6733c | ||
|
|
2b6c6f280c | ||
|
|
aefeee39aa | ||
|
|
27101979fa | ||
|
|
033520d426 | ||
|
|
16e68c3480 | ||
|
|
94b6073f34 | ||
|
|
8fc2eee2b4 | ||
|
|
969af7b610 | ||
|
|
da66118331 | ||
|
|
424efca1bf | ||
|
|
4d7ea37d56 | ||
|
|
32a1beb772 | ||
|
|
d850b2fd76 | ||
|
|
9f5fe63aaa | ||
|
|
03ee425e5f | ||
|
|
9200b7c78c | ||
|
|
b793810e4a | ||
|
|
9df5f5e27a | ||
|
|
7fbd0e34d3 | ||
|
|
77cf3adf64 | ||
|
|
dd0e3e4abb | ||
|
|
d77b3d788e | ||
|
|
dff6e89b4b | ||
|
|
57b00d3b38 | ||
|
|
898952a2df | ||
|
|
202cb8bb1f | ||
|
|
4592c90e34 | ||
|
|
734e9a4461 | ||
|
|
90959defd9 | ||
|
|
51064f5e75 | ||
|
|
3e8afc14b0 | ||
|
|
424bf51a7b | ||
|
|
75358e6c45 | ||
|
|
e2879b7bf2 | ||
|
|
b80d2f6659 | ||
|
|
4c83259acb | ||
|
|
9c50e9f964 | ||
|
|
a3effbfb9f | ||
|
|
90ce262bb3 | ||
|
|
23448c8277 | ||
|
|
d9fa086ec0 | ||
|
|
3b91fac192 | ||
|
|
e42f9b0603 | ||
|
|
c5d456f3a6 | ||
|
|
6cbc219427 | ||
|
|
4b06e74a14 | ||
|
|
42f973ebe0 | ||
|
|
84e7d5906c | ||
|
|
51eb96903c | ||
|
|
6d8e796932 | ||
|
|
f7e603c7db | ||
|
|
30ad381b6c | ||
|
|
5181c4e5be | ||
|
|
a2ec638db8 | ||
|
|
953a2ce807 | ||
|
|
e1efb7364e | ||
|
|
123ba9c670 | ||
|
|
d3e63e0078 | ||
|
|
21e21d3b8b | ||
|
|
702b6de734 | ||
|
|
6233b3477b | ||
|
|
07942bf422 | ||
|
|
66f9f73cb3 | ||
|
|
2dd9eafa4f | ||
|
|
b1783cc1db | ||
|
|
c5c615b7d3 | ||
|
|
897420a5f4 | ||
|
|
550b04cff8 | ||
|
|
46e0ac1258 | ||
|
|
22d984a6bd | ||
|
|
8d6f96adb9 | ||
|
|
1f431754a9 | ||
|
|
40093df91f | ||
|
|
f3bfee149c | ||
|
|
9b9b41f40e | ||
|
|
a451fd3f04 | ||
|
|
7565207242 | ||
|
|
e516200e09 | ||
|
|
5beaf45773 | ||
|
|
1ca0157768 | ||
|
|
dbe4168d8d | ||
|
|
969916851c | ||
|
|
b00b4cb185 | ||
|
|
2b9e7c6af1 | ||
|
|
0d9aaa86c6 | ||
|
|
28b613d60c | ||
|
|
b971c741d7 | ||
|
|
ecadb117da | ||
|
|
4b7dfc0254 | ||
|
|
a990859db6 | ||
|
|
9c576b10d0 | ||
|
|
b28807d791 | ||
|
|
b488cdd6ff | ||
|
|
302cc064c6 | ||
|
|
6e4d7ca933 | ||
|
|
6c8a5935c9 | ||
|
|
86445b6670 | ||
|
|
330ba39939 | ||
|
|
0f4eecebe6 | ||
|
|
a3dbe9a800 | ||
|
|
822dc45834 | ||
|
|
b8a99690b6 | ||
|
|
48b1120c31 | ||
|
|
c27a1cec48 | ||
|
|
9d4a66a400 | ||
|
|
c846dff524 | ||
|
|
29f73cb5c1 | ||
|
|
a8836b7665 | ||
|
|
96bf5c24b5 | ||
|
|
ab9de550d9 | ||
|
|
9cb9c3beb0 | ||
|
|
2d1bc2f4c7 | ||
|
|
c6bed1f464 | ||
|
|
c41341c7e3 | ||
|
|
f0e31487f0 | ||
|
|
a34143ae75 | ||
|
|
ca9ce0f3a3 | ||
|
|
a5ccafd924 | ||
|
|
9458da0454 | ||
|
|
80247e98e5 | ||
|
|
293d005432 | ||
|
|
a395e3577f | ||
|
|
8d7cdeabbf | ||
|
|
e199e3571b | ||
|
|
e9618df1b5 | ||
|
|
c34ba968f1 | ||
|
|
0cbcfbc3f0 | ||
|
|
492463411d | ||
|
|
cd53feb193 | ||
|
|
00feb67064 | ||
|
|
b3eed38187 | ||
|
|
f683a5c63c | ||
|
|
7d409f071e | ||
|
|
4146db6fc8 | ||
|
|
a86f65db1e | ||
|
|
53a0531def | ||
|
|
7db00575c9 | ||
|
|
5b65bfb64d | ||
|
|
3b0eb56cf6 | ||
|
|
e9073a3cc0 | ||
|
|
538a19fd47 | ||
|
|
84ff80710c | ||
|
|
0dcaf56ed1 | ||
|
|
6703968f73 | ||
|
|
8ec65dbfc8 | ||
|
|
4670ef6ec5 | ||
|
|
35e6400174 | ||
|
|
67de7af7b2 | ||
|
|
3b207ad2c3 | ||
|
|
b5274a495e | ||
|
|
11508b77d1 | ||
|
|
0a10778697 | ||
|
|
ecbf76e94b | ||
|
|
6c0103639c | ||
|
|
c2eb4a766e | ||
|
|
06e8d835dd | ||
|
|
3a71acec52 | ||
|
|
a9af4589ac | ||
|
|
4356e313ec | ||
|
|
afcfdb13a0 | ||
|
|
3a570add4e | ||
|
|
ead125555c | ||
|
|
98ad6d1b43 | ||
|
|
978cdadda0 | ||
|
|
cde7782c21 | ||
|
|
138ebf5b4d | ||
|
|
a127bca0e4 | ||
|
|
83be99e78c | ||
|
|
5b5e3ae6aa | ||
|
|
f6576b18f7 | ||
|
|
9a621e9272 | ||
|
|
c01f2977ac | ||
|
|
683c53babc | ||
|
|
5823e3c780 | ||
|
|
a0d30ffef1 | ||
|
|
fb1b82e239 | ||
|
|
3bc5699ba1 | ||
|
|
18659257d3 | ||
|
|
3c7cb363ba | ||
|
|
787d39851b | ||
|
|
ae070aca70 | ||
|
|
63dd895e6d | ||
|
|
507b083e77 | ||
|
|
13bc05d9ec | ||
|
|
3f24b86875 | ||
|
|
1368f0ec8f | ||
|
|
196fcf791b | ||
|
|
6bccf528d7 | ||
|
|
1fc03803cd | ||
|
|
8fa0e6c914 | ||
|
|
6f940bcaaf | ||
|
|
79f732f239 | ||
|
|
593bc28446 | ||
|
|
9c204533e8 | ||
|
|
4c14f7823a | ||
|
|
4aa2d65bba | ||
|
|
bb5848a033 | ||
|
|
9f728b850e | ||
|
|
b4c65dc210 | ||
|
|
5df62ac172 | ||
|
|
0979c1c9ca | ||
|
|
8bbe5f9fdb | ||
|
|
3cd3d1691e | ||
|
|
6b2fb02bed | ||
|
|
8938a19810 | ||
|
|
158ae61811 | ||
|
|
98d45777c6 | ||
|
|
d059022071 | ||
|
|
c9260ea785 | ||
|
|
c9ca2f902e | ||
|
|
29691e0ac5 | ||
|
|
a3fcce891f | ||
|
|
8db016c223 | ||
|
|
b0802f3e26 | ||
|
|
e4731a4e4e | ||
|
|
67ae9e1ba7 | ||
|
|
d6afbc56c4 | ||
|
|
ffb54872c0 | ||
|
|
ead6b4960f | ||
|
|
21b7acc397 | ||
|
|
173111c64c | ||
|
|
cf18355fe2 | ||
|
|
1cc148848b | ||
|
|
ec54904347 | ||
|
|
0a18d2e57b | ||
|
|
c08e2ed8fc | ||
|
|
2437f06c7f | ||
|
|
9b7e265762 | ||
|
|
c4a7f6ec9b | ||
|
|
d05656b716 | ||
|
|
04789d9ae4 | ||
|
|
77acf1f35b | ||
|
|
412e8034de | ||
|
|
a6162ba990 | ||
|
|
7c51178608 | ||
|
|
899c489124 | ||
|
|
3062e1e740 | ||
|
|
854bfba7c9 | ||
|
|
1d4de091f9 | ||
|
|
440916cf2a | ||
|
|
3f4feb2f5c | ||
|
|
3025a7e51e | ||
|
|
fcbd2751ba | ||
|
|
56f1481c24 | ||
|
|
2ea20b8e81 | ||
|
|
9906116d23 | ||
|
|
555725a05b | ||
|
|
13cbf4e288 | ||
|
|
beb92e6cbf | ||
|
|
4d3fb3c497 | ||
|
|
dbd0c98306 | ||
|
|
53fb702512 | ||
|
|
e640708245 | ||
|
|
8fdaf61ef1 | ||
|
|
a5713252dc | ||
|
|
f5ec5e180d | ||
|
|
27e0f22c04 | ||
|
|
2016bcb37a | ||
|
|
1760d32019 | ||
|
|
9808c6e62b | ||
|
|
071b278b71 | ||
|
|
f4673f3123 | ||
|
|
8ee4c36e02 | ||
|
|
0f876d1989 | ||
|
|
eecf0deeec | ||
|
|
6aa32e8752 | ||
|
|
a55b6565b8 | ||
|
|
c1c0c8d418 | ||
|
|
1634773529 | ||
|
|
547e2ad72b | ||
|
|
ca90648118 | ||
|
|
958fbaa819 | ||
|
|
fa19a54ab7 | ||
|
|
c9c1b7e5d8 | ||
|
|
4d5e5c82d4 | ||
|
|
9bd4a5ecc3 | ||
|
|
09d6619a58 | ||
|
|
e7ba40dcfd | ||
|
|
ae42eee8e1 | ||
|
|
1e9b9b9a3e | ||
|
|
1225d3ef75 | ||
|
|
407ded6b8c | ||
|
|
c7b5266f9a | ||
|
|
5a0fccc9cf | ||
|
|
b66fb68f29 | ||
|
|
cb30bb5d70 | ||
|
|
40ebb9a844 | ||
|
|
b7a87bb6f9 | ||
|
|
e38cce46ad | ||
|
|
f6deafee22 | ||
|
|
8d6a0786c8 | ||
|
|
9ee3070667 | ||
|
|
05e084364a | ||
|
|
69e91244f4 | ||
|
|
e37a9d81f1 | ||
|
|
95d0736bb6 | ||
|
|
de4ba1aa35 | ||
|
|
d7ca3343bc | ||
|
|
cee77a3228 | ||
|
|
f5a82be9e5 | ||
|
|
2a1ccaff1e | ||
|
|
b195dfbe09 | ||
|
|
0aab4c27e1 | ||
|
|
42ad98567c | ||
|
|
c06e625413 | ||
|
|
1a48e75d43 | ||
|
|
a896a3ade5 | ||
|
|
62ef9e1c6b | ||
|
|
047cccda89 | ||
|
|
dbed3fea6f | ||
|
|
b88a9295bf | ||
|
|
c4d4679ed9 | ||
|
|
c2063f3a88 | ||
|
|
03bae15b96 | ||
|
|
842fb957a6 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,11 +1,8 @@
|
||||
# WPIlib Specific
|
||||
|
||||
dependency-reduced-pom.xml
|
||||
/wpilibj/src/shared/java/edu/wpi/first/wpilibj/util/WPILibVersion.java
|
||||
/wpilibc/shared/src/WPILibVersion.cpp
|
||||
/wpilibj/src/main/java/edu/wpi/first/wpilibj/util/WPILibVersion.java
|
||||
/wpilibc/src/main/native/cpp/WPILibVersion.cpp
|
||||
doxygen.log
|
||||
buildcmake/
|
||||
|
||||
# Created by the jenkins test script
|
||||
test-reports
|
||||
@@ -15,6 +12,7 @@ test-reports
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
out/
|
||||
|
||||
# Created by http://www.gitignore.io
|
||||
|
||||
@@ -204,7 +202,6 @@ ipch/
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
36
.styleguide
36
.styleguide
@@ -9,33 +9,31 @@ cppSrcFileInclude {
|
||||
}
|
||||
|
||||
generatedFileExclude {
|
||||
gmock/
|
||||
gtest/
|
||||
ni-libraries/include/
|
||||
ni-libraries/lib/
|
||||
hal/src/main/native/athena/ctre/
|
||||
hal/src/main/native/athena/frccansae/
|
||||
hal/src/main/native/athena/visa/
|
||||
hal/src/main/native/include/ctre/
|
||||
UsageReporting\.h$
|
||||
FRCNetComm\.java$
|
||||
simulation/frc_gazebo_plugins/frcgazebo/
|
||||
simulation/gz_msgs/src/include/simulation/gz_msgs/msgs\.h$
|
||||
}
|
||||
|
||||
modifiableFileExclude {
|
||||
wpilibj/src/arm-linux-jni/
|
||||
wpilibj/src/main/native/cpp/
|
||||
\.patch$
|
||||
\.png$
|
||||
\.py$
|
||||
\.so$
|
||||
}
|
||||
|
||||
includeOtherLibs {
|
||||
^HAL/
|
||||
^llvm/
|
||||
^networktables/
|
||||
^opencv2/
|
||||
^support/
|
||||
repoRootNameOverride {
|
||||
wpilib
|
||||
}
|
||||
|
||||
includeProject {
|
||||
^ctre/
|
||||
includeOtherLibs {
|
||||
^cameraserver/
|
||||
^cscore
|
||||
^hal/
|
||||
^mockdata/
|
||||
^networktables/
|
||||
^ntcore
|
||||
^opencv2/
|
||||
^support/
|
||||
^vision/
|
||||
^wpi/
|
||||
}
|
||||
|
||||
40
.travis.yml
40
.travis.yml
@@ -1,40 +0,0 @@
|
||||
sudo: true
|
||||
dist: trusty
|
||||
language: java
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- deadsnakes
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-6
|
||||
- python3.5
|
||||
|
||||
before_install:
|
||||
- sudo sh -c 'echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-5.0 main" > /etc/apt/sources.list.d/llvm.list'
|
||||
- wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||
- sudo apt-get update -q || true
|
||||
- sudo apt-get install clang-format-5.0 -y
|
||||
|
||||
install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- sudo python3.5 get-pip.py
|
||||
- python3.5 -m pip install --user wpiformat
|
||||
- mkdir -p $HOME/latest-gcc-symlinks # see travis-ci/travis-ci#3668
|
||||
- ln -s /usr/bin/g++-6 $HOME/latest-gcc-symlinks/g++
|
||||
- ln -s /usr/bin/gcc-6 $HOME/latest-gcc-symlinks/gcc
|
||||
- export PATH=$HOME/latest-gcc-symlinks:$PATH
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
||||
script:
|
||||
- python3.5 -m wpiformat -y 2018 -clang 5.0
|
||||
- git --no-pager diff --exit-code HEAD # Ensure formatter made no changes
|
||||
- ./gradlew --no-daemon --console=plain -PskipAthena :hal:halSimSharedLibrary :wpilibc:wpilibcSharedLibrary :wpilibj:wpilibJNISharedSharedLibrary :wpilibj:jar
|
||||
6
.wpilib/wpilib_preferences.json
Normal file
6
.wpilib/wpilib_preferences.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"enableCppIntellisense": true,
|
||||
"currentLanguage": "cpp",
|
||||
"projectYear": "2019",
|
||||
"teamNumber": 0
|
||||
}
|
||||
92
CMakeLists.txt
Normal file
92
CMakeLists.txt
Normal file
@@ -0,0 +1,92 @@
|
||||
# Disable in-source builds to prevent source tree corruption.
|
||||
if(" ${CMAKE_SOURCE_DIR}" STREQUAL " ${CMAKE_BINARY_DIR}")
|
||||
message(FATAL_ERROR "
|
||||
FATAL: In-source builds are not allowed.
|
||||
You should create a separate directory for build files.
|
||||
")
|
||||
endif()
|
||||
|
||||
|
||||
project(allwpilib)
|
||||
cmake_minimum_required(VERSION 3.3.0)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules")
|
||||
|
||||
INCLUDE(CPack)
|
||||
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND MSVC)
|
||||
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Default install dir on windows" FORCE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${CMAKE_BINARY_DIR}/jar)
|
||||
|
||||
# use, i.e. don't skip the full RPATH for the build tree
|
||||
SET(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
|
||||
# when building, don't use the install RPATH already
|
||||
# (but later on when installing)
|
||||
SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
|
||||
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
|
||||
# add the automatically determined parts of the RPATH
|
||||
# which point to directories outside the build tree to the install RPATH
|
||||
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
|
||||
# the RPATH to be used when installing, but only if it's not a system directory
|
||||
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/wpilib/lib" isSystemDir)
|
||||
IF("${isSystemDir}" STREQUAL "-1")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
ENDIF("${isSystemDir}" STREQUAL "-1")
|
||||
|
||||
option(WITHOUT_JAVA "don't include java and JNI in the build" OFF)
|
||||
option(BUILD_SHARED_LIBS "build with shared libs (needed for JNI)" ON)
|
||||
option(WITHOUT_CSCORE "Don't build cscore (removes OpenCV requirement)" OFF)
|
||||
option(WITHOUT_ALLWPILIB "Don't build allwpilib (removes OpenCV requirement)" ON)
|
||||
option(USE_EXTERNAL_HAL "Use a separately built HAL" OFF)
|
||||
set(EXTERNAL_HAL_FILE "" CACHE FILEPATH "Location to look for an external HAL CMake File")
|
||||
|
||||
if (NOT WITHOUT_JAVA AND NOT BUILD_SHARED_LIBS)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build static libs with Java enabled.
|
||||
Static libs requires both BUILD_SHARED_LIBS=OFF and
|
||||
WITHOUT_JAVA=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
set( wpilib_dest wpilib)
|
||||
set( include_dest wpilib/include )
|
||||
set( main_lib_dest wpilib/lib )
|
||||
set( java_lib_dest wpilib/java )
|
||||
set( jni_lib_dest wpilib/jni )
|
||||
|
||||
if (MSVC)
|
||||
set (wpilib_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (wpilib_config_dir share/wpilib)
|
||||
endif()
|
||||
|
||||
add_subdirectory(wpiutil)
|
||||
add_subdirectory(ntcore)
|
||||
|
||||
if (NOT WITHOUT_CSCORE)
|
||||
add_subdirectory(cscore)
|
||||
add_subdirectory(cameraserver)
|
||||
set (CSCORE_DEP_REPLACE "find_dependency(cscore)")
|
||||
set (CAMERASERVER_DEP_REPLACE "find_dependency(cameraserver)")
|
||||
if (NOT WITHOUT_ALLWPILIB)
|
||||
add_subdirectory(hal)
|
||||
add_subdirectory(wpilibj)
|
||||
add_subdirectory(wpilibc)
|
||||
set (HAL_DEP_REPLACE "find_dependency(hal)")
|
||||
set (WPILIBC_DEP_REPLACE "find_dependency(wpilibc)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
configure_file(wpilib-config.cmake.in ${CMAKE_BINARY_DIR}/wpilib-config.cmake )
|
||||
install(FILES ${CMAKE_BINARY_DIR}/wpilib-config.cmake DESTINATION ${wpilib_config_dir})
|
||||
@@ -37,7 +37,7 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system.
|
||||
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system. We currently use clang-format 5.0 with wpiformat.
|
||||
|
||||
While the library should be fully formatted according to the styles, additional elements of the style guide were not followed when the library was initially created. All new code should follow the guidelines. If you are looking for some easy ramp-up tasks, finding areas that don't follow the style guide and fixing them is very welcome.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2017 FIRST
|
||||
Copyright (c) 2009-2018 FIRST
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
102
MavenArtifacts.md
Normal file
102
MavenArtifacts.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# WPILib Maven Artifacts
|
||||
|
||||
WPILib publishes its built artifacts to our Maven server for use by downstream projects. This document explains these locations, and the meanings of artifact names, classifiers, and versions.
|
||||
|
||||
## Repositories
|
||||
We provide two repositories. These repositories are:
|
||||
|
||||
* (Release) http://first.wpi.edu/FRC/roborio/maven/release/
|
||||
* (Development) http://first.wpi.edu/FRC/roborio/maven/development/
|
||||
|
||||
The release repository is where official WPILib releases are pushed.
|
||||
The development repository is where development releases of every commit to [master](https://github.com/wpilibsuite/allwpilib/tree/master) is pushed.
|
||||
|
||||
## Artifact classifiers
|
||||
We provide two base types of artifacts.
|
||||
|
||||
The first types are Java artifacts. These are usually published as `jar` files. Usually, the actual jar file is published with no classifier. The sources are published with the `-sources` classifier, and the javadocs are published with the `-javadoc` classifier.
|
||||
|
||||
The second types are native artifacts. These are usually published as `zip` files (except for the `JNI` artifact types, which are `jar` files. See below for information on this). The `-sources` and `-headers` classifiers contain the sources and headers respecively for the library. Each artifact also contains a classifier for each platform we publish. This platform is in the format `{os}{arch}`. The platform artifact only contains the binaries for a specific platform. In addition, we provide a `-all` classifier. This classifer combines all of the platform artifacts into a single artifact. This is useful for tools that cannot determine what version to use during builds. However, we recommend using the platform specific classifier when possible. Note that the binary artifacts never contain the headers, you always need the `-headers` classifier to get those.
|
||||
|
||||
## Artifact Names
|
||||
|
||||
WPILib builds four different types of artifacts.
|
||||
|
||||
##### C++ Only Libraries
|
||||
When we publish C++ only libraries, they are published with the base artifact name as their artifact name, with a `-cpp` extension. All dependencies for the library are linked as shared libraries to the binary.
|
||||
|
||||
|
||||
Example:
|
||||
```
|
||||
edu.wpi.first.wpilibc:wpilibc-cpp:version:classifier@zip
|
||||
```
|
||||
|
||||
#### Java Only Libraries
|
||||
When we publish Java only libraries, they are published with the base artifact name as their artifact name, with a `-java` extension.
|
||||
|
||||
Example:
|
||||
```
|
||||
edu.wpi.first.wpilibj:wpilibj-java:version
|
||||
```
|
||||
|
||||
#### C++/Java Libraries without JNI
|
||||
For libraries that are both C++ and Java, but without a JNI component, the C++ component is published with the `basename-cpp` artifact name, and the Java component is published with the `basename-java` artifact name.
|
||||
|
||||
Example:
|
||||
```
|
||||
edu.wpi.first.wpiutil:wpiutil-cpp:version:classifier@zip (C++)
|
||||
edu.wpi.first.wpiutil:wpiutil-java:version (Java)
|
||||
```
|
||||
|
||||
#### C++/Java Libraries with JNI
|
||||
For libraries that are both C++ and Java with a JNI component there are three different artifact names. For Java, the component is published as `basename-java`. For C++, the `basename-cpp` artifact contains the C++ artifacts with all dependencies linked as shared libraries to the binary. These binaries DO contain the JNI entry points. The `basename-jni` artifact contains identical C++ binaries to the `-cpp` artifact, however all of its dependencies are statically linked, and only the JNI and C entry points are exported.
|
||||
|
||||
The `-jni` artifact should only be used in cases where you want to create a self contained Java application where the native artifacts are embedded in the jar. Note in an extraction scenario, extending off of the library is never supported, which is why the C++ entry points are not exposed. The name of the library is randomly generated during extraction. For pretty much all cases, and if you ever want to extend from a native library, you should use the `-cpp` artifacts. GradleRIO uses the `-cpp` artifacts for all platforms, even desktop, for this reason.
|
||||
|
||||
Example:
|
||||
```
|
||||
edu.wpi.first.ntcore:ntcore-cpp:version:classifier@zip (C++)
|
||||
edu.wpi.first.ntcore:ntcore-jni:version:classifier (JNI jar library)
|
||||
edu.wpi.first.ntcore:ntcore-java:version (Java)
|
||||
```
|
||||
|
||||
## Provided Artifacts
|
||||
This repository provides the following artifacts. Below each artifact is its dependencies. Note if ever using the `-jni` artifacts, no dependencies are needed for native binaries.
|
||||
|
||||
For C++, if building with static dependencies, the listed order should be the link order in your linker.
|
||||
|
||||
All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
|
||||
* wpiutil
|
||||
|
||||
* hal
|
||||
* wpiutil
|
||||
|
||||
* ntcore
|
||||
* wpiutil
|
||||
|
||||
* cscore
|
||||
* opencv
|
||||
* wpiutil
|
||||
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* opencv
|
||||
* wpiutil
|
||||
|
||||
|
||||
* wpilibj
|
||||
* hal
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpiutil
|
||||
|
||||
|
||||
* wpilibc
|
||||
* hal
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpiutil
|
||||
138
README-CMAKE.md
Normal file
138
README-CMAKE.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# WPILib CMake Support
|
||||
|
||||
WPILib is normally built with Gradle, however for some systems, such a linux based coprocessors, Gradle doesn't work correctly, especially if cscore is needed, which requires OpenCV. We provide the CMake build for these cases. Although it is supported on Windows, these docs will only go over linux builds.
|
||||
|
||||
## Libraries that get built
|
||||
* wpiutil
|
||||
* ntcore
|
||||
* cscore
|
||||
* cameraserver
|
||||
* hal
|
||||
* wpilib
|
||||
|
||||
By default, all libraries except for the HAL and WPILib get built with a default cmake setup. The libraries are built as shared libraries, and include the JNI libraries as well as building the Java jars.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The most common prerequisite is going to be OpenCV. OpenCV needs to be findable by cmake. On systems like the Jetson, this is installed by default. Otherwise, you will need to build cmake from source and install it.
|
||||
|
||||
In addition, if you want JNI and Java, you will need a JDK of at least version 8 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the jdk directory.
|
||||
|
||||
## Build Options
|
||||
|
||||
The following build options are available:
|
||||
|
||||
* WITHOUT_JAVA (OFF Default)
|
||||
* Enabling this option will disable Java and JNI builds. If this is off, `BUILD_SHARED_LIBS` must be on. Otherwise cmake will error.
|
||||
* BUILD_SHARED_LIBS (ON Default)
|
||||
* Disabling this option will cause cmake to build static libraries instead of shared libraries. If this is off, `WITHOUT_JAVA` must be on. Otherwise cmake will error.
|
||||
* WITHOUT_CSCORE (OFF Default)
|
||||
* Enabling this option will cause cscore to not be built. This will also implicitly disable cameraserver, the hal and wpilib as well, irrespective of their specific options. If this is on, the opencv build requirement is removed.
|
||||
* WITHOUT_ALLWPILIB (ON Default)
|
||||
* Disabling this option will build the hal and wpilib during the build. The HAL is the simulator hal, unless the external hal options are used. The cmake build has no capability to build for the RoboRIO.
|
||||
* USE_EXTERNAL_HAL (OFF Default)
|
||||
* TODO
|
||||
* EXTERNAL_HAL_FILE
|
||||
* TODO
|
||||
|
||||
## Build Setup
|
||||
|
||||
The WPILib CMake build does not allow in source builds. Because the `build` directory is used by gradle, we recommend a `buildcmake` directory in the root. This folder is included in the gitignore.
|
||||
|
||||
Once you have a build folder, run cmake configuration in that build directory with the following command.
|
||||
|
||||
```
|
||||
cmake path/to/allwpilib/root
|
||||
```
|
||||
|
||||
If you want to change any of the options, add `-DOPTIONHERE=VALUE` to the cmake command. This will check for any dependencies. If everything works properly this will succeed. If not, please check out the troubleshooting section for help.
|
||||
|
||||
If you want, you can also use `ccmake` in order to visually set these properties as well.
|
||||
https://cmake.org/cmake/help/v3.0/manual/ccmake.1.html
|
||||
Here is the link to the documentation for that program.
|
||||
|
||||
## Building
|
||||
|
||||
Once you have cmake setup. run `make` from the directory you configured cmake in. This will build all libraries possible. If you have a multicore system, we recommend running make with multiple jobs. The usual rule of thumb is 1.5x the number of cores you have. To run a multiple job build, run the following command with x being the number of jobs you want.
|
||||
|
||||
```
|
||||
make -jx
|
||||
```
|
||||
|
||||
## Installing
|
||||
|
||||
After build, the easiest way to use the libraries is to install them. Run the following command to install the libraries. This will install them so that they can be used from external cmake projects.
|
||||
|
||||
```
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Using the installed libraries for C++.
|
||||
|
||||
Using the libraries from C++ is the easiest way to use the built libraries.
|
||||
|
||||
To do so, create a new folder to contain your project. Add the following code below to a `CMakeLists.txt` file in that directory.
|
||||
|
||||
```
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(vision_app) # Project Name Here
|
||||
|
||||
find_package(wpilib REQUIRED)
|
||||
|
||||
add_executable(my_vision_app main.cpp) # exectuable name as first parameter
|
||||
target_link_libraries(my_vision_app cameraserver ntcore cscore wpiutil)
|
||||
```
|
||||
|
||||
If you are using them, `wpilibc` and `hal` should be added before the `cameraserver` declaration in the `target_link_libraries` function.
|
||||
|
||||
Add a `main.cpp` file to contain your code, and create a build folder. Move into the build folder, and run
|
||||
|
||||
```
|
||||
cmake /path/to/folder/containing/CMakeLists
|
||||
```
|
||||
|
||||
After that, run `make`. That will create your executable. Then you should be able to run `./my_vision_app` to run your application.
|
||||
|
||||
|
||||
## Using the installed libraries for Java
|
||||
TODO
|
||||
|
||||
## Troubleshooting
|
||||
Below are some common issues that are run into when building.
|
||||
|
||||
#### Missing OpenCV
|
||||
|
||||
If you are missing OpenCV, you will get an error message similar to this.
|
||||
|
||||
```
|
||||
CMake Error at cscore/CMakeLists.txt:3 (find_package):
|
||||
By not providing "FindOpenCV.cmake" in CMAKE_MODULE_PATH this project has
|
||||
asked CMake to find a package configuration file provided by "OpenCV", but
|
||||
CMake did not find one.
|
||||
|
||||
Could not find a package configuration file provided by "OpenCV" with any
|
||||
of the following names:
|
||||
|
||||
OpenCVConfig.cmake
|
||||
opencv-config.cmake
|
||||
|
||||
Add the installation prefix of "OpenCV" to CMAKE_PREFIX_PATH or set
|
||||
"OpenCV_DIR" to a directory containing one of the above files. If "OpenCV"
|
||||
provides a separate development package or SDK, be sure it has been
|
||||
installed.
|
||||
```
|
||||
|
||||
If you get that, you need make sure opencv was installed, and then reattempt to configure. If that doesn't work, set the `OpenCV_DIR` variable to the directory where you built OpenCV.
|
||||
|
||||
#### Missing Java
|
||||
|
||||
If you are missing Java, you will get a message like the following.
|
||||
```
|
||||
CMake Error at /usr/share/cmake-3.5/Modules/FindPackageHandleStandardArgs.cmake:148 (message):
|
||||
Could NOT find Java (missing: Java_JAVA_EXECUTABLE Java_JAR_EXECUTABLE
|
||||
Java_JAVAC_EXECUTABLE Java_JAVAH_EXECUTABLE Java_JAVADOC_EXECUTABLE)
|
||||
```
|
||||
|
||||
If this happens, make sure you have a JDK of at least version 8 installed, and that your JAVA_HOME variable is set properly to point to the JDK.
|
||||
|
||||
In addition, if you do not need Java, you can disable it with `-DWITHOUT_JAVA=ON`.
|
||||
25
README.md
25
README.md
@@ -1,6 +1,6 @@
|
||||
# WPILib Project
|
||||
|
||||
[](https://travis-ci.org/wpilibsuite/allwpilib)
|
||||
[](https://dev.azure.com/wpilib/wpilib/_build/latest?definitionId=1)
|
||||
|
||||
Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WPILibC projects. These are the core libraries for creating robot programs for the roboRIO.
|
||||
|
||||
@@ -15,7 +15,7 @@ Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WP
|
||||
|
||||
## WPILib Mission
|
||||
|
||||
The WPILib Mission is to enable FIRST Robotics teams to focus on writing game-specific software rather than focussing on hardware details - "raise the floor, don't lower the ceiling". We work to enable teams with limited programming knowledge and/or mentor experience to be as successful as possible, while not hampering the abilities of teams with more advanced programming capabilities. We support Kit of Parts control system components directly in the library. We also strive to keep parity between major features of each language (Java, C++, and NI's LabVIEW), so that teams aren't at a disadvantage for choosing a specific programming language. WPILib is an open source project, licensed under the BSD 3-clause license. You can find a copy of the license [here](license.txt).
|
||||
The WPILib Mission is to enable FIRST Robotics teams to focus on writing game-specific software rather than focusing on hardware details - "raise the floor, don't lower the ceiling". We work to enable teams with limited programming knowledge and/or mentor experience to be as successful as possible, while not hampering the abilities of teams with more advanced programming capabilities. We support Kit of Parts control system components directly in the library. We also strive to keep parity between major features of each language (Java, C++, and NI's LabVIEW), so that teams aren't at a disadvantage for choosing a specific programming language. WPILib is an open source project, licensed under the BSD 3-clause license. You can find a copy of the license [here](LICENSE.txt).
|
||||
|
||||
# Building WPILib
|
||||
|
||||
@@ -24,11 +24,11 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
## Requirements
|
||||
|
||||
- A C++ compiler
|
||||
- On Linux, gcc works fine
|
||||
- On Windows, you need Visual Studio 2015 (the free community edition works fine).
|
||||
- On Linux, GCC works fine
|
||||
- On Windows, you need Visual Studio 2017 (the free community edition works fine).
|
||||
Make sure to select the C++ Programming Language for installation
|
||||
- [ARM Compiler Toolchain](http://first.wpi.edu/FRC/roborio/toolchains/)
|
||||
* Note that for 2017-2018 and beyond, you will need version 5 or greater of gcc
|
||||
- [ARM Compiler Toolchain](https://github.com/wpilibsuite/toolchain-builder/releases)
|
||||
* Note that for 2019 and beyond, you should use version 6 or greater of GCC
|
||||
- Doxygen (Only required if you want to build the C++ documentation)
|
||||
|
||||
## Setup
|
||||
@@ -79,23 +79,18 @@ There are a few tasks other than `build` available. To see them, run the meta-ta
|
||||
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
|
||||
|
||||
CMake is also supported for building. See [README-CMAKE.md](README-CMAKE.md).
|
||||
|
||||
## Publishing
|
||||
|
||||
If you are building to test with the Eclipse plugins or just want to export the build as a Maven-style dependency, simply run the `publish` task. This task will publish all available packages to ~/releases/maven/development. If you need to publish the project to a different repo, you can specify it with `-Prepo=repo_name`. Valid options are:
|
||||
If you are building to test with other dependencies or just want to export the build as a Maven-style dependency, simply run the `publish` task. This task will publish all available packages to ~/releases/maven/development. If you need to publish the project to a different repo, you can specify it with `-Prepo=repo_name`. Valid options are:
|
||||
|
||||
- development - The default repo.
|
||||
- beta - Publishes to ~/releases/maven/beta.
|
||||
- stable - Publishes to ~/releases/maven/stable.
|
||||
- release - Publishes to ~/releases/maven/release.
|
||||
|
||||
The following maven targets a published by this task:
|
||||
|
||||
- edu.wpi.first.wpilib.cmake:cpp-root:1.0.0 - roboRIO C++
|
||||
- edu.wpi.first.wpilibc.simulation:WPILibCSim:0.1.0 - Simulation C++
|
||||
- edu.wpi.first.wpilibj:wpilibJavaFinal:0.1.0-SNAPSHOT - roboRIO Java
|
||||
- edu.wpi.first.wpilibj:wpilibJavaSim:0.1.0-SNAPSHOT - Simulation Java
|
||||
- edu.wpi.first.wpilibj.simulation:SimDS:0.1.0-SNAPSHOT - The driverstation for controlling simulation.
|
||||
- org.gazebosim:JavaGazebo:0.1.0-SNAPSHOT - Gazebo protocol for Java.
|
||||
The maven artifacts are described in [MavenArtifacts.md](MavenArtifacts.md)
|
||||
|
||||
## Structure and Organization
|
||||
|
||||
|
||||
373
ThirdPartyNotices.txt
Normal file
373
ThirdPartyNotices.txt
Normal file
@@ -0,0 +1,373 @@
|
||||
==============================================================================
|
||||
Copyrights and Licenses for Third Party Software Distributed with WPILib
|
||||
==============================================================================
|
||||
The WPILib software contains code written by third parties. The copyrights,
|
||||
license, and restrictions which apply to each piece of software is included
|
||||
later in this file and/or inside of the individual applicable source files.
|
||||
|
||||
The disclaimer of warranty in the WPILib license above applies to all code in
|
||||
WPILib, and nothing in any of the other licenses gives permission to use the
|
||||
names of FIRST nor the names of the WPILib contributors to endorse or promote
|
||||
products derived from this software.
|
||||
|
||||
The following pieces of software have additional or alternate copyrights,
|
||||
licenses, and/or restrictions:
|
||||
|
||||
Program Locations
|
||||
------- ---------
|
||||
RoboRIO Libraries ni-libraries
|
||||
Google Test gtest
|
||||
LLVM wpiutil/src/main/native/include/wpi/{various files}
|
||||
wpiutil/src/main/native/cpp/llvm/
|
||||
wpiutil/src/main/native/cpp/leb128.cpp
|
||||
wpiutil/src/test/native/cpp/leb128Test.cpp
|
||||
JSON for Modern C++ wpiutil/src/main/native/include/wpi/json.h
|
||||
wpiutil/src/main/native/cpp/json_*.cpp
|
||||
wpiutil/src/test/native/cpp/json/
|
||||
libuv wpiutil/src/main/native/include/uv.h
|
||||
wpiutil/src/main/native/include/uv/
|
||||
wpiutil/src/main/native/libuv/
|
||||
sigslot wpiutil/src/main/native/include/wpi/Signal.h
|
||||
wpiutil/src/test/native/cpp/sigslot/
|
||||
tcpsockets wpiutil/src/main/native/cpp/TCP{Stream,Connector,Acceptor}.cpp
|
||||
wpiutil/src/main/native/include/wpi/TCP*.h
|
||||
Optional wpiutil/src/main/native/include/wpi/optional.h
|
||||
wpiutil/src/test/native/cpp/test_optional.cpp
|
||||
Bootstrap wpiutil/src/main/native/resources/bootstrap-*
|
||||
CoreUI wpiutil/src/main/native/resources/coreui-*
|
||||
Feather Icons wpiutil/src/main/native/resources/feather-*
|
||||
jQuery wpiutil/src/main/native/resources/jquery-*
|
||||
popper.js wpiutil/src/main/native/resources/popper-*
|
||||
|
||||
|
||||
==============================================================================
|
||||
Google Test License
|
||||
==============================================================================
|
||||
Copyright 2008, Google Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
LLVM Release License
|
||||
==============================================================================
|
||||
University of Illinois/NCSA
|
||||
Open Source License
|
||||
|
||||
Copyright (c) 2003-2017 University of Illinois at Urbana-Champaign.
|
||||
All rights reserved.
|
||||
|
||||
Developed by:
|
||||
|
||||
LLVM Team
|
||||
|
||||
University of Illinois at Urbana-Champaign
|
||||
|
||||
http://llvm.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal with
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimers.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimers in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the names of the LLVM Team, University of Illinois at
|
||||
Urbana-Champaign, nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this Software without specific
|
||||
prior written permission.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
JSON for Modern C++ License
|
||||
==============================================================================
|
||||
|
||||
__ _____ _____ _____
|
||||
__| | __| | | | JSON for Modern C++
|
||||
| | |__ | | | | | | version 2.1.1
|
||||
|_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
|
||||
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
||||
Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
libuv License
|
||||
==============================================================================
|
||||
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
sigslot License
|
||||
==============================================================================
|
||||
Sigslot, a signal-slot library
|
||||
|
||||
https://github.com/palacaze/sigslot
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Pierre-Antoine Lacaze
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
tcpsockets License
|
||||
==============================================================================
|
||||
Copyright (c) 2013 [Vic Hargrave - http://vichargrave.com]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Optional License
|
||||
==============================================================================
|
||||
Copyright (C) 2011 - 2017 Andrzej Krzemienski.
|
||||
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Bootstrap License
|
||||
==============================================================================
|
||||
Copyright (c) 2011-2018 Twitter, Inc.
|
||||
Copyright (c) 2011-2018 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
CoreUI License
|
||||
==============================================================================
|
||||
Copyright (c) 2018 creativeLabs tukasz Holeczek.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Feather Icons License
|
||||
==============================================================================
|
||||
Copyright (c) 2013-2017 Cole Bemis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
jQuery License
|
||||
==============================================================================
|
||||
Copyright JS Foundation and other contributors, https://js.foundation/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
popper.js License
|
||||
==============================================================================
|
||||
Copyright (c) 2016 Federico Zivolo and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
183
azure-pipelines.yml
Normal file
183
azure-pipelines.yml
Normal file
@@ -0,0 +1,183 @@
|
||||
# Gradle
|
||||
# Build your Java projects and run tests with Gradle using a Gradle wrapper script.
|
||||
# Add steps that analyze code, save build artifacts, deploy, and more:
|
||||
# https://docs.microsoft.com/vsts/pipelines/languages/java
|
||||
|
||||
resources:
|
||||
containers:
|
||||
- container: wpilib2019
|
||||
image: wpilib/roborio-cross-ubuntu:2019-18.04
|
||||
- container: raspbian
|
||||
image: wpilib/raspbian-cross-ubuntu:18.04
|
||||
|
||||
jobs:
|
||||
- job: Linux_Arm
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: wpilib2019
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: false
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PonlyAthena'
|
||||
# checkStyleRunAnalysis: true
|
||||
# pmdRunAnalysis: true
|
||||
|
||||
- job: Linux_Raspbian
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: raspbian
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PonlyRaspbian'
|
||||
# checkStyleRunAnalysis: true
|
||||
# pmdRunAnalysis: true
|
||||
|
||||
- job: Linux
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: wpilib2019
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PskipAthena'
|
||||
# checkStyleRunAnalysis: true
|
||||
# pmdRunAnalysis: true
|
||||
|
||||
- job: Styleguide
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install clang-format-5.0 python3-setuptools -y
|
||||
sudo pip3 install --upgrade pip
|
||||
sudo pip3 install wpiformat
|
||||
git checkout -b master
|
||||
displayName: 'Install Dependencies'
|
||||
- script: |
|
||||
wpiformat -y 2018 -clang 5.0
|
||||
displayName: 'Run WPIFormat'
|
||||
failOnStderr: true
|
||||
- script: |
|
||||
git --no-pager diff --exit-code HEAD # Ensure formatter made no changes
|
||||
displayName: 'Check WPIFormat Output'
|
||||
failOnStderr: true
|
||||
|
||||
- job: CMakeBuild
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: wpilib2019
|
||||
|
||||
steps:
|
||||
- task: CMake@1
|
||||
inputs:
|
||||
cmakeArgs: '-DWITHOUT_ALLWPILIB=OFF ..'
|
||||
- script: |
|
||||
make -j3
|
||||
workingDirectory: 'build'
|
||||
displayName: 'Build'
|
||||
|
||||
- job: Windows_64_Bit
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
steps:
|
||||
- powershell: |
|
||||
mkdir build
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
wget "https://download.java.net/java/ga/jdk11/openjdk-11_windows-x64_bin.zip" -O "build\jdk.zip"
|
||||
displayName: 'Download JDK'
|
||||
- task: JavaToolInstaller@0
|
||||
inputs:
|
||||
jdkSourceOption: localDirectory
|
||||
jdkFile: 'build/jdk.zip'
|
||||
jdkDestinationDirectory: 'build/jdkinst'
|
||||
jdkArchitectureOption: x64
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
# checkStyleRunAnalysis: true
|
||||
# pmdRunAnalysis: true
|
||||
|
||||
- job: Windows_32_Bit
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
steps:
|
||||
- powershell: |
|
||||
mkdir build
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
wget "https://github.com/wpilibsuite/frc-openjdk-windows/releases/download/v11.0.0u28-1/jdk-x86-11.0.0u28-1.zip" -O "build\jdk.zip"
|
||||
displayName: 'Download JDK'
|
||||
- task: JavaToolInstaller@0
|
||||
inputs:
|
||||
jdkSourceOption: localDirectory
|
||||
jdkFile: 'build/jdk.zip'
|
||||
jdkDestinationDirectory: 'build/jdkinst'
|
||||
jdkArchitectureOption: x86
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx1024m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
# checkStyleRunAnalysis: true
|
||||
# pmdRunAnalysis: true
|
||||
|
||||
- job: Mac
|
||||
pool:
|
||||
vmImage: 'xcode9-macos10.13'
|
||||
steps:
|
||||
- script: |
|
||||
mkdir build
|
||||
wget "https://download.java.net/java/ga/jdk11/openjdk-11_osx-x64_bin.tar.gz" -O "build/jdk.tar.gz"
|
||||
sudo tar xvzf build/jdk.tar.gz -C /Library/Java/JavaVirtualMachines/
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/
|
||||
displayName: 'Setup JDK'
|
||||
- script: |
|
||||
rm /Users/vsts/.gradle/init.d/log-gradle-version-plugin.gradle
|
||||
displayName: 'Delete Version init script'
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
javaHomeOption: 'path'
|
||||
jdkDirectory: '/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
# checkStyleRunAnalysis: true
|
||||
# pmdRunAnalysis: true
|
||||
178
build.gradle
178
build.gradle
@@ -1,132 +1,28 @@
|
||||
import edu.wpi.first.nativeutils.NativeUtils
|
||||
import edu.wpi.first.nativeutils.tasks.JNIHeaders
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'gradle.plugin.edu.wpi.first:native-utils:1.5.1'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'net.ltgt.errorprone' version '0.0.10'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2.0'
|
||||
id 'base'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2.3'
|
||||
id 'edu.wpi.first.NativeUtils' version '2.1.2'
|
||||
id 'edu.wpi.first.GradleJni' version '0.3.1'
|
||||
id 'edu.wpi.first.GradleVsCode' version '0.7.1'
|
||||
id 'idea'
|
||||
id 'visual-studio'
|
||||
id 'com.gradle.build-scan' version '2.0.2'
|
||||
id 'net.ltgt.errorprone' version '0.6' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '4.0.3' apply false
|
||||
}
|
||||
|
||||
ext.licenseFile = file("$rootDir/LICENSE.txt")
|
||||
|
||||
ext.getJNIHeadersClass = {
|
||||
return JNIHeaders
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
ext.getClassifier = { binary->
|
||||
return NativeUtils.getClassifier(binary)
|
||||
buildScan {
|
||||
termsOfServiceUrl = 'https://gradle.com/terms-of-service'
|
||||
termsOfServiceAgree = 'yes'
|
||||
|
||||
publishAlways()
|
||||
}
|
||||
|
||||
ext.getPlatformPath = { binary->
|
||||
return NativeUtils.getPlatformPath(binary)
|
||||
}
|
||||
|
||||
ext.createComponentZipTasks = { components, name, base, type, project, func ->
|
||||
def configMap = [:]
|
||||
components.each {
|
||||
if (it in NativeLibrarySpec && it.name == name) {
|
||||
it.binaries.each {
|
||||
def target = getClassifier(it)
|
||||
if (configMap.containsKey(target)) {
|
||||
configMap.get(target).add(it)
|
||||
} else {
|
||||
configMap.put(target, [])
|
||||
configMap.get(target).add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def taskList = []
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
def baseN = base + name
|
||||
configMap.each { key, value ->
|
||||
def task = project.tasks.create(baseN + "-${key}", type) {
|
||||
description = 'Creates component archive for platform ' + key
|
||||
destinationDir = outputsFolder
|
||||
classifier = key
|
||||
baseName = baseN + '-classifier'
|
||||
duplicatesStrategy = 'exclude'
|
||||
|
||||
from(licenseFile) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
func(it, value)
|
||||
}
|
||||
taskList.add(task)
|
||||
|
||||
project.build.dependsOn task
|
||||
|
||||
project.artifacts {
|
||||
task
|
||||
}
|
||||
}
|
||||
return taskList
|
||||
}
|
||||
|
||||
ext.createAllCombined = { list, name, base, type, project ->
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
def baseN = base + name
|
||||
def task = project.tasks.create(baseN + '-all', type) {
|
||||
description = 'Creates component archive for all classifiers'
|
||||
destinationDir = outputsFolder
|
||||
classifier = 'all'
|
||||
baseName = baseN + '-classifier'
|
||||
duplicatesStrategy = 'exclude'
|
||||
|
||||
list.each {
|
||||
it.outputs.files.each {
|
||||
from project.zipTree(it)
|
||||
}
|
||||
dependsOn it
|
||||
}
|
||||
}
|
||||
|
||||
project.build.dependsOn task
|
||||
|
||||
project.artifacts {
|
||||
task
|
||||
}
|
||||
|
||||
return task
|
||||
|
||||
}
|
||||
|
||||
ext.includeStandardZipFormat = { task, value ->
|
||||
value.each { binary->
|
||||
if (binary.buildable) {
|
||||
if (binary instanceof SharedLibraryBinarySpec) {
|
||||
task.dependsOn binary.buildTask
|
||||
task.from(new File(binary.sharedLibraryFile.absolutePath + ".debug")) {
|
||||
into getPlatformPath(binary) + '/shared'
|
||||
}
|
||||
task.from (binary.sharedLibraryFile) {
|
||||
into getPlatformPath(binary) + '/shared'
|
||||
}
|
||||
task.from (binary.sharedLibraryLinkFile) {
|
||||
into getPlatformPath(binary) + '/shared'
|
||||
}
|
||||
} else if (binary instanceof StaticLibraryBinarySpec) {
|
||||
task.dependsOn binary.buildTask
|
||||
task.from (binary.staticLibraryFile) {
|
||||
into getPlatformPath(binary) + '/static'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ext.licenseFile = files("$rootDir/LICENSE.txt", "$rootDir/ThirdPartyNotices.txt")
|
||||
|
||||
// Ensure that the WPILibVersioningPlugin is setup by setting the release type, if releaseType wasn't
|
||||
// already specified on the command line
|
||||
@@ -143,7 +39,7 @@ if (project.hasProperty("publishVersion")) {
|
||||
pubVersion = WPILibVersion.version
|
||||
}
|
||||
|
||||
def outputsFolder = file("$buildDir/outputs")
|
||||
def outputsFolder = file("$buildDir/allOutputs")
|
||||
|
||||
def versionFile = file("$outputsFolder/version.txt")
|
||||
|
||||
@@ -162,41 +58,47 @@ task outputVersions() {
|
||||
}
|
||||
}
|
||||
|
||||
task build() {}
|
||||
task libraryBuild() {}
|
||||
|
||||
build.dependsOn outputVersions
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete buildDir
|
||||
task copyAllOutputs(type: Copy) {
|
||||
destinationDir outputsFolder
|
||||
}
|
||||
|
||||
build.dependsOn copyAllOutputs
|
||||
copyAllOutputs.dependsOn outputVersions
|
||||
|
||||
ext.addTaskToCopyAllOutputs = { task ->
|
||||
copyAllOutputs.dependsOn task
|
||||
copyAllOutputs.inputs.file task.archivePath
|
||||
copyAllOutputs.from task.archivePath
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
def subproj = it
|
||||
|
||||
plugins.withType(NativeComponentPlugin) {
|
||||
subproj.apply plugin: MultiBuilds
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/java/javastyle.gradle"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "8.1"
|
||||
configFile = new File(rootDir, "styleguide/checkstyle.xml")
|
||||
}
|
||||
|
||||
// Disables doclint in java 8.
|
||||
if (JavaVersion.current().isJava8Compatible()) {
|
||||
tasks.withType(Javadoc) {
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
}
|
||||
}
|
||||
ext.setupWpilibRepo = { publishing ->
|
||||
publishing.repositories.maven {
|
||||
url = WPILibVersion.mavenLocalUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.1'
|
||||
wrapper {
|
||||
gradleVersion = '5.0'
|
||||
}
|
||||
|
||||
88
buildSrc/src/main/groovy/ExtraTasks.groovy
Normal file
88
buildSrc/src/main/groovy/ExtraTasks.groovy
Normal file
@@ -0,0 +1,88 @@
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.language.base.internal.ProjectLayout;
|
||||
import org.gradle.language.base.plugins.ComponentModelBasePlugin;
|
||||
import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
|
||||
import org.gradle.model.ModelMap;
|
||||
import org.gradle.model.Mutate;
|
||||
import org.gradle.model.RuleSource;
|
||||
import org.gradle.model.Validate;
|
||||
import org.gradle.nativeplatform.NativeBinarySpec;
|
||||
import org.gradle.nativeplatform.NativeComponentSpec;
|
||||
import org.gradle.nativeplatform.NativeLibrarySpec;
|
||||
import org.gradle.nativeplatform.SharedLibraryBinarySpec;
|
||||
import org.gradle.nativeplatform.StaticLibraryBinarySpec;
|
||||
import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
|
||||
import org.gradle.nativeplatform.toolchain.NativeToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
|
||||
import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
|
||||
import org.gradle.nativeplatform.toolchain.internal.ToolType;
|
||||
import org.gradle.nativeplatform.toolchain.internal.gcc.AbstractGccCompatibleToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.msvcpp.VisualCppToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.tools.ToolRegistry;
|
||||
import org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteBinarySpec;
|
||||
import org.gradle.platform.base.BinarySpec;
|
||||
import org.gradle.platform.base.ComponentSpec;
|
||||
import org.gradle.platform.base.ComponentSpecContainer;
|
||||
import org.gradle.platform.base.ComponentType;
|
||||
import org.gradle.platform.base.TypeBuilder;
|
||||
import org.gradle.nativeplatform.test.tasks.RunTestExecutable;
|
||||
import org.gradle.platform.base.BinaryContainer;
|
||||
import groovy.transform.CompileStatic;
|
||||
|
||||
@CompileStatic
|
||||
class ExtraTasks implements Plugin<Project> {
|
||||
@CompileStatic
|
||||
public void apply(Project project) {
|
||||
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static class Rules extends RuleSource {
|
||||
@Mutate
|
||||
@CompileStatic
|
||||
void createNativeCompileTask(ModelMap<Task> tasks, BinaryContainer binaries) {
|
||||
tasks.create('compileCpp', Task) { oTask ->
|
||||
def task = (Task) oTask
|
||||
task.group = 'build'
|
||||
task.description = 'Uber task to compile all native code for this project'
|
||||
binaries.each { binary ->
|
||||
if (binary instanceof NativeBinarySpec && binary.buildable) {
|
||||
binary.tasks.withType(AbstractNativeSourceCompileTask) { compileTask ->
|
||||
task.dependsOn compileTask
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Mutate
|
||||
@CompileStatic
|
||||
void createNativeTestTask(ModelMap<Task> tasks, BinaryContainer binaries) {
|
||||
tasks.create('testCpp', Task) { oTask ->
|
||||
def task = (Task) oTask
|
||||
task.group = 'build'
|
||||
task.description = 'Uber task to run all native tests for project'
|
||||
binaries.each { binary ->
|
||||
if (binary instanceof GoogleTestTestSuiteBinarySpec && binary.buildable) {
|
||||
binary.tasks.withType(RunTestExecutable) { testTask ->
|
||||
task.dependsOn testTask
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
buildSrc/src/main/groovy/MultiBuilds.groovy
Normal file
95
buildSrc/src/main/groovy/MultiBuilds.groovy
Normal file
@@ -0,0 +1,95 @@
|
||||
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.language.base.internal.ProjectLayout;
|
||||
import org.gradle.language.base.plugins.ComponentModelBasePlugin;
|
||||
import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
|
||||
import org.gradle.model.ModelMap;
|
||||
import org.gradle.model.Mutate;
|
||||
import org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteBinarySpec;
|
||||
import org.gradle.model.RuleSource;
|
||||
import org.gradle.model.Validate;
|
||||
import org.gradle.nativeplatform.NativeExecutableBinarySpec
|
||||
import org.gradle.nativeplatform.NativeBinarySpec;
|
||||
import org.gradle.nativeplatform.NativeComponentSpec;
|
||||
import org.gradle.nativeplatform.NativeLibrarySpec;
|
||||
import org.gradle.nativeplatform.SharedLibraryBinarySpec;
|
||||
import org.gradle.nativeplatform.StaticLibraryBinarySpec;
|
||||
import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
|
||||
import org.gradle.nativeplatform.toolchain.NativeToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
|
||||
import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
|
||||
import org.gradle.nativeplatform.toolchain.internal.ToolType;
|
||||
import org.gradle.nativeplatform.toolchain.internal.gcc.AbstractGccCompatibleToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.msvcpp.VisualCppToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.tools.ToolRegistry;
|
||||
import org.gradle.platform.base.BinarySpec;
|
||||
import org.gradle.platform.base.ComponentSpec;
|
||||
import org.gradle.platform.base.ComponentSpecContainer;
|
||||
import org.gradle.platform.base.BinaryContainer;
|
||||
import org.gradle.platform.base.ComponentType;
|
||||
import org.gradle.platform.base.TypeBuilder;
|
||||
import org.gradle.nativeplatform.tasks.ObjectFilesToBinary;
|
||||
import groovy.transform.CompileStatic;
|
||||
import groovy.transform.CompileDynamic
|
||||
import org.gradle.nativeplatform.BuildTypeContainer
|
||||
|
||||
@CompileStatic
|
||||
class MultiBuilds implements Plugin<Project> {
|
||||
@CompileStatic
|
||||
public void apply(Project project) {
|
||||
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static class Rules extends RuleSource {
|
||||
@Mutate
|
||||
void setupBuildTypes(BuildTypeContainer buildTypes, ProjectLayout projectLayout) {
|
||||
def project = (Project) projectLayout.projectIdentifier
|
||||
if (project.hasProperty('releaseBuild')) {
|
||||
buildTypes.create('debug')
|
||||
} else {
|
||||
buildTypes.create('release')
|
||||
}
|
||||
}
|
||||
|
||||
@CompileDynamic
|
||||
private static void setBuildableFalseDynamically(NativeBinarySpec binary) {
|
||||
binary.buildable = false
|
||||
}
|
||||
|
||||
@Mutate
|
||||
@CompileStatic
|
||||
void disableReleaseGoogleTest(BinaryContainer binaries, ProjectLayout projectLayout) {
|
||||
def project = (Project) projectLayout.projectIdentifier
|
||||
if (project.hasProperty('testRelease')) {
|
||||
return
|
||||
}
|
||||
binaries.withType(GoogleTestTestSuiteBinarySpec) { oSpec ->
|
||||
GoogleTestTestSuiteBinarySpec spec = (GoogleTestTestSuiteBinarySpec) oSpec
|
||||
if (spec.buildType.name == 'release') {
|
||||
Rules.setBuildableFalseDynamically(spec)
|
||||
}
|
||||
}
|
||||
// def crossCompileConfigs = []
|
||||
// for (BuildConfig config : configs) {
|
||||
// if (!BuildConfigRulesBase.isCrossCompile(config)) {
|
||||
// continue
|
||||
// }
|
||||
// crossCompileConfigs << config.architecture
|
||||
// }
|
||||
// if (!crossCompileConfigs.empty) {
|
||||
// binaries.withType(GoogleTestTestSuiteBinarySpec) { oSpec ->
|
||||
// GoogleTestTestSuiteBinarySpec spec = (GoogleTestTestSuiteBinarySpec) oSpec
|
||||
// if (crossCompileConfigs.contains(spec.targetPlatform.architecture.name)) {
|
||||
// setBuildableFalseDynamically(spec)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
113
buildSrc/src/main/groovy/SingleNativeBuild.groovy
Normal file
113
buildSrc/src/main/groovy/SingleNativeBuild.groovy
Normal file
@@ -0,0 +1,113 @@
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.language.base.internal.ProjectLayout;
|
||||
import org.gradle.language.base.plugins.ComponentModelBasePlugin;
|
||||
import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
|
||||
import org.gradle.model.ModelMap;
|
||||
import org.gradle.model.Mutate;
|
||||
import org.gradle.model.RuleSource;
|
||||
import org.gradle.model.Validate;
|
||||
import org.gradle.nativeplatform.NativeBinarySpec;
|
||||
import org.gradle.nativeplatform.NativeComponentSpec;
|
||||
import org.gradle.nativeplatform.NativeLibrarySpec;
|
||||
import org.gradle.nativeplatform.SharedLibraryBinarySpec;
|
||||
import org.gradle.nativeplatform.StaticLibraryBinarySpec;
|
||||
import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
|
||||
import org.gradle.nativeplatform.toolchain.NativeToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
|
||||
import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
|
||||
import org.gradle.nativeplatform.toolchain.internal.ToolType;
|
||||
import org.gradle.nativeplatform.toolchain.internal.gcc.AbstractGccCompatibleToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.msvcpp.VisualCppToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.tools.ToolRegistry;
|
||||
import org.gradle.platform.base.BinarySpec;
|
||||
import org.gradle.platform.base.ComponentSpec;
|
||||
import org.gradle.platform.base.ComponentSpecContainer;
|
||||
import org.gradle.platform.base.BinaryContainer;
|
||||
import org.gradle.platform.base.ComponentType;
|
||||
import org.gradle.platform.base.TypeBuilder;
|
||||
import org.gradle.nativeplatform.tasks.ObjectFilesToBinary;
|
||||
import groovy.transform.CompileStatic;
|
||||
|
||||
@CompileStatic
|
||||
class SingleNativeBuild implements Plugin<Project> {
|
||||
@CompileStatic
|
||||
public void apply(Project project) {
|
||||
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static class Rules extends RuleSource {
|
||||
@Mutate
|
||||
@CompileStatic
|
||||
void setupSingleNativeBuild(ModelMap<Task> tasks, ComponentSpecContainer components, BinaryContainer binaryContainer, ProjectLayout projectLayout) {
|
||||
Project project = (Project) projectLayout.projectIdentifier;
|
||||
|
||||
def nativeName = project.extensions.extraProperties.get('nativeName')
|
||||
|
||||
NativeLibrarySpec base = null
|
||||
def subs = []
|
||||
components.each { component ->
|
||||
if (component.name == "${nativeName}Base") {
|
||||
base = (NativeLibrarySpec) component
|
||||
} else if (component.name == "${nativeName}" || component.name == "${nativeName}JNI") {
|
||||
subs << component
|
||||
}
|
||||
}
|
||||
subs.each {
|
||||
((NativeLibrarySpec) it).binaries.each { oBinary ->
|
||||
if (oBinary.buildable == false) {
|
||||
return
|
||||
}
|
||||
NativeBinarySpec binary = (NativeBinarySpec) oBinary
|
||||
NativeBinarySpec baseBin = null
|
||||
base.binaries.each { oTmpBaseBin ->
|
||||
if (oTmpBaseBin.buildable == false) {
|
||||
return
|
||||
}
|
||||
def tmpBaseBin = (NativeBinarySpec) oTmpBaseBin
|
||||
if (tmpBaseBin.targetPlatform.operatingSystem.name == binary.targetPlatform.operatingSystem.name &&
|
||||
tmpBaseBin.targetPlatform.architecture.name == binary.targetPlatform.architecture.name &&
|
||||
tmpBaseBin.buildType == binary.buildType) {
|
||||
baseBin = tmpBaseBin
|
||||
}
|
||||
}
|
||||
baseBin.tasks.withType(AbstractNativeSourceCompileTask) { oCompileTask ->
|
||||
def compileTask = (AbstractNativeSourceCompileTask) oCompileTask
|
||||
if (binary instanceof SharedLibraryBinarySpec) {
|
||||
def sBinary = (SharedLibraryBinarySpec) binary
|
||||
ObjectFilesToBinary link = (ObjectFilesToBinary) sBinary.tasks.link
|
||||
link.dependsOn compileTask
|
||||
link.inputs.dir compileTask.objectFileDir
|
||||
def tree = project.fileTree(compileTask.objectFileDir)
|
||||
tree.include '**/*.o'
|
||||
tree.include '**/*.obj'
|
||||
link.source tree
|
||||
} else if (binary instanceof StaticLibraryBinarySpec) {
|
||||
def sBinary = (StaticLibraryBinarySpec) binary
|
||||
ObjectFilesToBinary assemble = (ObjectFilesToBinary) sBinary.tasks.createStaticLib
|
||||
assemble.dependsOn compileTask
|
||||
assemble.inputs.dir compileTask.objectFileDir
|
||||
def tree = project.fileTree(compileTask.objectFileDir)
|
||||
tree.include '**/*.o'
|
||||
tree.include '**/*.obj'
|
||||
assemble.source tree
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
cameraserver/.styleguide
Normal file
28
cameraserver/.styleguide
Normal file
@@ -0,0 +1,28 @@
|
||||
cppHeaderFileInclude {
|
||||
\.h$
|
||||
\.inc$
|
||||
}
|
||||
|
||||
cppSrcFileInclude {
|
||||
\.cpp$
|
||||
}
|
||||
|
||||
modifiableFileExclude {
|
||||
\.so$
|
||||
}
|
||||
|
||||
repoRootNameOverride {
|
||||
cameraserver
|
||||
}
|
||||
|
||||
includeOtherLibs {
|
||||
^HAL/
|
||||
^networktables/
|
||||
^opencv2/
|
||||
^support/
|
||||
^wpi/
|
||||
}
|
||||
|
||||
includeGuardRoots {
|
||||
cameraserver/src/main/native/include/
|
||||
}
|
||||
57
cameraserver/CMakeLists.txt
Normal file
57
cameraserver/CMakeLists.txt
Normal file
@@ -0,0 +1,57 @@
|
||||
project(cameraserver)
|
||||
|
||||
find_package( OpenCV REQUIRED )
|
||||
|
||||
# Java bindings
|
||||
if (NOT WITHOUT_JAVA)
|
||||
find_package(Java REQUIRED)
|
||||
include(UseJava)
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-Xlint:unchecked")
|
||||
|
||||
#find java files, copy them locally
|
||||
|
||||
set(OPENCV_JAVA_INSTALL_DIR ${OpenCV_INSTALL_PATH}/share/OpenCV/java/)
|
||||
|
||||
find_file(OPENCV_JAR_FILE NAMES opencv-${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.jar PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin NO_DEFAULT_PATH)
|
||||
|
||||
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
|
||||
|
||||
add_jar(cameraserver_jar ${JAVA_SOURCES} INCLUDE_JARS wpiutil_jar cscore_jar ntcore_jar ${OPENCV_JAR_FILE} OUTPUT_NAME cameraserver)
|
||||
|
||||
get_property(CAMERASERVER_JAR_FILE TARGET cameraserver_jar PROPERTY JAR_FILE)
|
||||
install(FILES ${CAMERASERVER_JAR_FILE} DESTINATION "${java_lib_dest}")
|
||||
|
||||
set_property(TARGET cameraserver_jar PROPERTY FOLDER "java")
|
||||
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE
|
||||
cameraserver_native_src src/main/native/cpp/*.cpp)
|
||||
add_library(cameraserver ${cameraserver_native_src})
|
||||
set_target_properties(cameraserver PROPERTIES DEBUG_POSTFIX "d")
|
||||
target_include_directories(cameraserver PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/cameraserver>)
|
||||
target_link_libraries(cameraserver PUBLIC ntcore cscore wpiutil ${OpenCV_LIBS})
|
||||
|
||||
set_property(TARGET cameraserver PROPERTY FOLDER "libraries")
|
||||
|
||||
install(TARGETS cameraserver EXPORT cameraserver DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cameraserver")
|
||||
|
||||
if (NOT WITHOUT_JAVA AND MSVC)
|
||||
install(TARGETS cameraserver RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
set (cameraserver_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (cameraserver_config_dir share/cameraserver)
|
||||
endif()
|
||||
|
||||
install(FILES cameraserver-config.cmake DESTINATION ${cameraserver_config_dir})
|
||||
install(EXPORT cameraserver DESTINATION ${cameraserver_config_dir})
|
||||
|
||||
file(GLOB multiCameraServer_src multiCameraServer/src/main/native/cpp/*.cpp)
|
||||
add_executable(multiCameraServer ${multiCameraServer_src})
|
||||
target_link_libraries(multiCameraServer cameraserver)
|
||||
80
cameraserver/build.gradle
Normal file
80
cameraserver/build.gradle
Normal file
@@ -0,0 +1,80 @@
|
||||
ext {
|
||||
nativeName = 'cameraserver'
|
||||
devMain = 'edu.wpi.first.cameraserver.DevMain'
|
||||
}
|
||||
|
||||
evaluationDependsOn(':ntcore')
|
||||
evaluationDependsOn(':cscore')
|
||||
evaluationDependsOn(':hal')
|
||||
|
||||
apply from: "${rootDir}/shared/javacpp/setupBuild.gradle"
|
||||
|
||||
dependencies {
|
||||
compile project(':wpiutil')
|
||||
compile project(':ntcore')
|
||||
compile project(':cscore')
|
||||
devCompile project(':wpiutil')
|
||||
devCompile project(':ntcore')
|
||||
devCompile project(':cscore')
|
||||
}
|
||||
|
||||
ext {
|
||||
sharedCvConfigs = [cameraserver : [],
|
||||
cameraserverBase: [],
|
||||
cameraserverDev : [],
|
||||
cameraserverTest: []]
|
||||
staticCvConfigs = [:]
|
||||
useJava = true
|
||||
useCpp = true
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
model {
|
||||
// Exports config is a utility to enable exporting all symbols in a C++ library on windows to a DLL.
|
||||
// This removes the need for DllExport on a library. However, the gradle C++ builder has a bug
|
||||
// where some extra symbols are added that cannot be resolved at link time. This configuration
|
||||
// lets you specify specific symbols to exlude from exporting.
|
||||
exportsConfigs {
|
||||
cameraserver(ExportsConfig) {
|
||||
x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
|
||||
x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
|
||||
}
|
||||
}
|
||||
components {}
|
||||
binaries {
|
||||
all {
|
||||
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
|
||||
return
|
||||
}
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
}
|
||||
tasks {
|
||||
def c = $.components
|
||||
def found = false
|
||||
def systemArch = getCurrentArch()
|
||||
c.each {
|
||||
if (it in NativeExecutableSpec && it.name == "${nativeName}Dev") {
|
||||
it.binaries.each {
|
||||
if (!found) {
|
||||
def arch = it.targetPlatform.architecture.name
|
||||
if (arch == systemArch) {
|
||||
def filePath = it.tasks.install.installDirectory.get().toString() + File.separatorChar + 'lib'
|
||||
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
cameraserver/cameraserver-config.cmake
Normal file
8
cameraserver/cameraserver-config.cmake
Normal file
@@ -0,0 +1,8 @@
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(wpiutil)
|
||||
find_dependency(ntcore)
|
||||
find_dependency(cscore)
|
||||
find_dependency(OpenCV)
|
||||
|
||||
get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
include(${SELF_DIR}/cameraserver.cmake)
|
||||
62
cameraserver/multiCameraServer/build.gradle
Normal file
62
cameraserver/multiCameraServer/build.gradle
Normal file
@@ -0,0 +1,62 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id 'cpp'
|
||||
id 'visual-studio'
|
||||
}
|
||||
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
|
||||
ext {
|
||||
staticCvConfigs = [multiCameraServerCpp: []]
|
||||
useJava = true
|
||||
useCpp = true
|
||||
skipDev = true
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
mainClassName = 'Main'
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.code.gson:gson:2.8.5'
|
||||
|
||||
compile project(':wpiutil')
|
||||
compile project(':ntcore')
|
||||
compile project(':cscore')
|
||||
compile project(':cameraserver')
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
multiCameraServerCpp(NativeExecutableSpec) {
|
||||
targetBuildTypes 'release'
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs = ['src/main/native/cpp']
|
||||
includes = ['**/*.cpp']
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs = ['src/main/native/include']
|
||||
includes = ['**/*.h']
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries.all { binary ->
|
||||
lib project: ':cameraserver', library: 'cameraserver', linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
cameraserver/multiCameraServer/src/main/java/Main.java
Normal file
211
cameraserver/multiCameraServer/src/main/java/Main.java
Normal file
@@ -0,0 +1,211 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import edu.wpi.cscore.VideoSource;
|
||||
import edu.wpi.first.cameraserver.CameraServer;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
|
||||
/*
|
||||
JSON format:
|
||||
{
|
||||
"team": <team number>,
|
||||
"ntmode": <"client" or "server", "client" if unspecified>
|
||||
"cameras": [
|
||||
{
|
||||
"name": <camera name>
|
||||
"path": <path, e.g. "/dev/video0">
|
||||
"pixel format": <"MJPEG", "YUYV", etc> // optional
|
||||
"width": <video mode width> // optional
|
||||
"height": <video mode height> // optional
|
||||
"fps": <video mode fps> // optional
|
||||
"brightness": <percentage brightness> // optional
|
||||
"white balance": <"auto", "hold", value> // optional
|
||||
"exposure": <"auto", "hold", value> // optional
|
||||
"properties": [ // optional
|
||||
{
|
||||
"name": <property name>
|
||||
"value": <property value>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
public final class Main {
|
||||
private static String configFile = "/boot/frc.json";
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public static class CameraConfig {
|
||||
public String name;
|
||||
public String path;
|
||||
public JsonObject config;
|
||||
}
|
||||
|
||||
public static int team;
|
||||
public static boolean server;
|
||||
public static List<CameraConfig> cameras = new ArrayList<>();
|
||||
|
||||
private Main() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report parse error.
|
||||
*/
|
||||
public static void parseError(String str) {
|
||||
System.err.println("config error in '" + configFile + "': " + str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read single camera configuration.
|
||||
*/
|
||||
public static boolean readCameraConfig(JsonObject config) {
|
||||
CameraConfig cam = new CameraConfig();
|
||||
|
||||
// name
|
||||
JsonElement nameElement = config.get("name");
|
||||
if (nameElement == null) {
|
||||
parseError("could not read camera name");
|
||||
return false;
|
||||
}
|
||||
cam.name = nameElement.getAsString();
|
||||
|
||||
// path
|
||||
JsonElement pathElement = config.get("path");
|
||||
if (pathElement == null) {
|
||||
parseError("camera '" + cam.name + "': could not read path");
|
||||
return false;
|
||||
}
|
||||
cam.path = pathElement.getAsString();
|
||||
|
||||
cam.config = config;
|
||||
|
||||
cameras.add(cam);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read configuration file.
|
||||
*/
|
||||
@SuppressWarnings("PMD.CyclomaticComplexity")
|
||||
public static boolean readConfig() {
|
||||
// parse file
|
||||
JsonElement top;
|
||||
try {
|
||||
top = new JsonParser().parse(Files.newBufferedReader(Paths.get(configFile)));
|
||||
} catch (IOException ex) {
|
||||
System.err.println("could not open '" + configFile + "': " + ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
// top level must be an object
|
||||
if (!top.isJsonObject()) {
|
||||
parseError("must be JSON object");
|
||||
return false;
|
||||
}
|
||||
JsonObject obj = top.getAsJsonObject();
|
||||
|
||||
// team number
|
||||
JsonElement teamElement = obj.get("team");
|
||||
if (teamElement == null) {
|
||||
parseError("could not read team number");
|
||||
return false;
|
||||
}
|
||||
team = teamElement.getAsInt();
|
||||
|
||||
// ntmode (optional)
|
||||
if (obj.has("ntmode")) {
|
||||
String str = obj.get("ntmode").getAsString();
|
||||
if ("client".equalsIgnoreCase(str)) {
|
||||
server = false;
|
||||
} else if ("server".equalsIgnoreCase(str)) {
|
||||
server = true;
|
||||
} else {
|
||||
parseError("could not understand ntmode value '" + str + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// cameras
|
||||
JsonElement camerasElement = obj.get("cameras");
|
||||
if (camerasElement == null) {
|
||||
parseError("could not read cameras");
|
||||
return false;
|
||||
}
|
||||
JsonArray cameras = camerasElement.getAsJsonArray();
|
||||
for (JsonElement camera : cameras) {
|
||||
if (!readCameraConfig(camera.getAsJsonObject())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start running the camera.
|
||||
*/
|
||||
public static void startCamera(CameraConfig config) {
|
||||
System.out.println("Starting camera '" + config.name + "' on " + config.path);
|
||||
VideoSource camera = CameraServer.getInstance().startAutomaticCapture(
|
||||
config.name, config.path);
|
||||
|
||||
Gson gson = new GsonBuilder().create();
|
||||
|
||||
camera.setConfigJson(gson.toJson(config.config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Main.
|
||||
*/
|
||||
public static void main(String... args) {
|
||||
if (args.length > 0) {
|
||||
configFile = args[0];
|
||||
}
|
||||
|
||||
// read configuration
|
||||
if (!readConfig()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start NetworkTables
|
||||
NetworkTableInstance ntinst = NetworkTableInstance.getDefault();
|
||||
if (server) {
|
||||
System.out.println("Setting up NetworkTables server");
|
||||
ntinst.startServer();
|
||||
} else {
|
||||
System.out.println("Setting up NetworkTables client for team " + team);
|
||||
ntinst.startClientTeam(team);
|
||||
}
|
||||
|
||||
// start cameras
|
||||
for (CameraConfig camera : cameras) {
|
||||
startCamera(camera);
|
||||
}
|
||||
|
||||
// loop forever
|
||||
for (;;) {
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException ex) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
190
cameraserver/multiCameraServer/src/main/native/cpp/main.cpp
Normal file
190
cameraserver/multiCameraServer/src/main/native/cpp/main.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "cameraserver/CameraServer.h"
|
||||
|
||||
/*
|
||||
JSON format:
|
||||
{
|
||||
"team": <team number>,
|
||||
"ntmode": <"client" or "server", "client" if unspecified>
|
||||
"cameras": [
|
||||
{
|
||||
"name": <camera name>
|
||||
"path": <path, e.g. "/dev/video0">
|
||||
"pixel format": <"MJPEG", "YUYV", etc> // optional
|
||||
"width": <video mode width> // optional
|
||||
"height": <video mode height> // optional
|
||||
"fps": <video mode fps> // optional
|
||||
"brightness": <percentage brightness> // optional
|
||||
"white balance": <"auto", "hold", value> // optional
|
||||
"exposure": <"auto", "hold", value> // optional
|
||||
"properties": [ // optional
|
||||
{
|
||||
"name": <property name>
|
||||
"value": <property value>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
#ifdef __RASPBIAN__
|
||||
static const char* configFile = "/boot/frc.json";
|
||||
#else
|
||||
static const char* configFile = "frc.json";
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
unsigned int team;
|
||||
bool server = false;
|
||||
|
||||
struct CameraConfig {
|
||||
std::string name;
|
||||
std::string path;
|
||||
wpi::json config;
|
||||
};
|
||||
|
||||
std::vector<CameraConfig> cameras;
|
||||
|
||||
wpi::raw_ostream& ParseError() {
|
||||
return wpi::errs() << "config error in '" << configFile << "': ";
|
||||
}
|
||||
|
||||
bool ReadCameraConfig(const wpi::json& config) {
|
||||
CameraConfig c;
|
||||
|
||||
// name
|
||||
try {
|
||||
c.name = config.at("name").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read camera name: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// path
|
||||
try {
|
||||
c.path = config.at("path").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "camera '" << c.name
|
||||
<< "': could not read path: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
c.config = config;
|
||||
|
||||
cameras.emplace_back(std::move(c));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadConfig() {
|
||||
// open config file
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream is(configFile, ec);
|
||||
if (ec) {
|
||||
wpi::errs() << "could not open '" << configFile << "': " << ec.message()
|
||||
<< '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse file
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(is);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
ParseError() << "byte " << e.byte << ": " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// top level must be an object
|
||||
if (!j.is_object()) {
|
||||
ParseError() << "must be JSON object\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// team number
|
||||
try {
|
||||
team = j.at("team").get<unsigned int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read team number: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// ntmode (optional)
|
||||
if (j.count("ntmode") != 0) {
|
||||
try {
|
||||
auto str = j.at("ntmode").get<std::string>();
|
||||
wpi::StringRef s(str);
|
||||
if (s.equals_lower("client")) {
|
||||
server = false;
|
||||
} else if (s.equals_lower("server")) {
|
||||
server = true;
|
||||
} else {
|
||||
ParseError() << "could not understand ntmode value '" << str << "'\n";
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read ntmode: " << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// cameras
|
||||
try {
|
||||
for (auto&& camera : j.at("cameras")) {
|
||||
if (!ReadCameraConfig(camera)) return false;
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read cameras: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StartCamera(const CameraConfig& config) {
|
||||
wpi::outs() << "Starting camera '" << config.name << "' on " << config.path
|
||||
<< '\n';
|
||||
auto camera = frc::CameraServer::GetInstance()->StartAutomaticCapture(
|
||||
config.name, config.path);
|
||||
|
||||
camera.SetConfigJson(config.config);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc >= 2) configFile = argv[1];
|
||||
|
||||
// read configuration
|
||||
if (!ReadConfig()) return EXIT_FAILURE;
|
||||
|
||||
// start NetworkTables
|
||||
auto ntinst = nt::NetworkTableInstance::GetDefault();
|
||||
if (server) {
|
||||
wpi::outs() << "Setting up NetworkTables server\n";
|
||||
ntinst.StartServer();
|
||||
} else {
|
||||
wpi::outs() << "Setting up NetworkTables client for team " << team << '\n';
|
||||
ntinst.StartClientTeam(team);
|
||||
}
|
||||
|
||||
// start cameras
|
||||
for (auto&& camera : cameras) StartCamera(camera);
|
||||
|
||||
// loop forever
|
||||
for (;;) std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
public final class DevMain {
|
||||
public static void main(String[] args) {
|
||||
|
||||
}
|
||||
|
||||
private DevMain() {
|
||||
}
|
||||
}
|
||||
8
cameraserver/src/dev/native/cpp/main.cpp
Normal file
8
cameraserver/src/dev/native/cpp/main.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
int main() {}
|
||||
@@ -0,0 +1,799 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import edu.wpi.cscore.AxisCamera;
|
||||
import edu.wpi.cscore.CameraServerJNI;
|
||||
import edu.wpi.cscore.CvSink;
|
||||
import edu.wpi.cscore.CvSource;
|
||||
import edu.wpi.cscore.MjpegServer;
|
||||
import edu.wpi.cscore.UsbCamera;
|
||||
import edu.wpi.cscore.VideoEvent;
|
||||
import edu.wpi.cscore.VideoException;
|
||||
import edu.wpi.cscore.VideoListener;
|
||||
import edu.wpi.cscore.VideoMode;
|
||||
import edu.wpi.cscore.VideoMode.PixelFormat;
|
||||
import edu.wpi.cscore.VideoProperty;
|
||||
import edu.wpi.cscore.VideoSink;
|
||||
import edu.wpi.cscore.VideoSource;
|
||||
import edu.wpi.first.networktables.EntryListenerFlags;
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.NetworkTableEntry;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
|
||||
/**
|
||||
* Singleton class for creating and keeping camera servers.
|
||||
* Also publishes camera information to NetworkTables.
|
||||
*/
|
||||
@SuppressWarnings("PMD.TooManyMethods")
|
||||
public final class CameraServer {
|
||||
public static final int kBasePort = 1181;
|
||||
|
||||
@Deprecated
|
||||
public static final int kSize640x480 = 0;
|
||||
@Deprecated
|
||||
public static final int kSize320x240 = 1;
|
||||
@Deprecated
|
||||
public static final int kSize160x120 = 2;
|
||||
|
||||
private static final String kPublishName = "/CameraPublisher";
|
||||
private static CameraServer server;
|
||||
|
||||
/**
|
||||
* Get the CameraServer instance.
|
||||
*/
|
||||
public static synchronized CameraServer getInstance() {
|
||||
if (server == null) {
|
||||
server = new CameraServer();
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
private final AtomicInteger m_defaultUsbDevice;
|
||||
private String m_primarySourceName;
|
||||
private final Map<String, VideoSource> m_sources;
|
||||
private final Map<String, VideoSink> m_sinks;
|
||||
private final Map<Integer, NetworkTable> m_tables; // indexed by source handle
|
||||
// source handle indexed by sink handle
|
||||
private final Map<Integer, Integer> m_fixedSources;
|
||||
private final NetworkTable m_publishTable;
|
||||
private final VideoListener m_videoListener; //NOPMD
|
||||
private final int m_tableListener; //NOPMD
|
||||
private int m_nextPort;
|
||||
private String[] m_addresses;
|
||||
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
private static String makeSourceValue(int source) {
|
||||
switch (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))) {
|
||||
case kUsb:
|
||||
return "usb:" + CameraServerJNI.getUsbCameraPath(source);
|
||||
case kHttp: {
|
||||
String[] urls = CameraServerJNI.getHttpCameraUrls(source);
|
||||
if (urls.length > 0) {
|
||||
return "ip:" + urls[0];
|
||||
} else {
|
||||
return "ip:";
|
||||
}
|
||||
}
|
||||
case kCv:
|
||||
return "cv:";
|
||||
default:
|
||||
return "unknown:";
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
private static String makeStreamValue(String address, int port) {
|
||||
return "mjpg:http://" + address + ":" + port + "/?action=stream";
|
||||
}
|
||||
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"})
|
||||
private synchronized String[] getSinkStreamValues(int sink) {
|
||||
// Ignore all but MjpegServer
|
||||
if (VideoSink.getKindFromInt(CameraServerJNI.getSinkKind(sink)) != VideoSink.Kind.kMjpeg) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
// Get port
|
||||
int port = CameraServerJNI.getMjpegServerPort(sink);
|
||||
|
||||
// Generate values
|
||||
ArrayList<String> values = new ArrayList<>(m_addresses.length + 1);
|
||||
String listenAddress = CameraServerJNI.getMjpegServerListenAddress(sink);
|
||||
if (!listenAddress.isEmpty()) {
|
||||
// If a listen address is specified, only use that
|
||||
values.add(makeStreamValue(listenAddress, port));
|
||||
} else {
|
||||
// Otherwise generate for hostname and all interface addresses
|
||||
values.add(makeStreamValue(CameraServerJNI.getHostname() + ".local", port));
|
||||
for (String addr : m_addresses) {
|
||||
if ("127.0.0.1".equals(addr)) {
|
||||
continue; // ignore localhost
|
||||
}
|
||||
values.add(makeStreamValue(addr, port));
|
||||
}
|
||||
}
|
||||
|
||||
return values.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"})
|
||||
private synchronized String[] getSourceStreamValues(int source) {
|
||||
// Ignore all but HttpCamera
|
||||
if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))
|
||||
!= VideoSource.Kind.kHttp) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
// Generate values
|
||||
String[] values = CameraServerJNI.getHttpCameraUrls(source);
|
||||
for (int j = 0; j < values.length; j++) {
|
||||
values[j] = "mjpg:" + values[j];
|
||||
}
|
||||
|
||||
// Look to see if we have a passthrough server for this source
|
||||
for (VideoSink i : m_sinks.values()) {
|
||||
int sink = i.getHandle();
|
||||
int sinkSource = CameraServerJNI.getSinkSource(sink);
|
||||
if (source == sinkSource
|
||||
&& VideoSink.getKindFromInt(CameraServerJNI.getSinkKind(sink)) == VideoSink.Kind.kMjpeg) {
|
||||
// Add USB-only passthrough
|
||||
String[] finalValues = Arrays.copyOf(values, values.length + 1);
|
||||
int port = CameraServerJNI.getMjpegServerPort(sink);
|
||||
finalValues[values.length] = makeStreamValue("172.22.11.2", port);
|
||||
return finalValues;
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP", "PMD.CyclomaticComplexity"})
|
||||
private synchronized void updateStreamValues() {
|
||||
// Over all the sinks...
|
||||
for (VideoSink i : m_sinks.values()) {
|
||||
int sink = i.getHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
int source;
|
||||
Integer fixedSource = m_fixedSources.get(sink);
|
||||
if (fixedSource != null) {
|
||||
source = fixedSource;
|
||||
} else {
|
||||
source = CameraServerJNI.getSinkSource(sink);
|
||||
}
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
NetworkTable table = m_tables.get(source);
|
||||
if (table != null) {
|
||||
// Don't set stream values if this is a HttpCamera passthrough
|
||||
if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))
|
||||
== VideoSource.Kind.kHttp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set table value
|
||||
String[] values = getSinkStreamValues(sink);
|
||||
if (values.length > 0) {
|
||||
table.getEntry("streams").setStringArray(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Over all the sources...
|
||||
for (VideoSource i : m_sources.values()) {
|
||||
int source = i.getHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
NetworkTable table = m_tables.get(source);
|
||||
if (table != null) {
|
||||
// Set table value
|
||||
String[] values = getSourceStreamValues(source);
|
||||
if (values.length > 0) {
|
||||
table.getEntry("streams").setStringArray(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
private static String pixelFormatToString(PixelFormat pixelFormat) {
|
||||
switch (pixelFormat) {
|
||||
case kMJPEG:
|
||||
return "MJPEG";
|
||||
case kYUYV:
|
||||
return "YUYV";
|
||||
case kRGB565:
|
||||
return "RGB565";
|
||||
case kBGR:
|
||||
return "BGR";
|
||||
case kGray:
|
||||
return "Gray";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide string description of video mode.
|
||||
/// The returned string is "{width}x{height} {format} {fps} fps".
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
private static String videoModeToString(VideoMode mode) {
|
||||
return mode.width + "x" + mode.height + " " + pixelFormatToString(mode.pixelFormat)
|
||||
+ " " + mode.fps + " fps";
|
||||
}
|
||||
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
private static String[] getSourceModeValues(int sourceHandle) {
|
||||
VideoMode[] modes = CameraServerJNI.enumerateSourceVideoModes(sourceHandle);
|
||||
String[] modeStrings = new String[modes.length];
|
||||
for (int i = 0; i < modes.length; i++) {
|
||||
modeStrings[i] = videoModeToString(modes[i]);
|
||||
}
|
||||
return modeStrings;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.CyclomaticComplexity"})
|
||||
private static void putSourcePropertyValue(NetworkTable table, VideoEvent event, boolean isNew) {
|
||||
String name;
|
||||
String infoName;
|
||||
if (event.name.startsWith("raw_")) {
|
||||
name = "RawProperty/" + event.name;
|
||||
infoName = "RawPropertyInfo/" + event.name;
|
||||
} else {
|
||||
name = "Property/" + event.name;
|
||||
infoName = "PropertyInfo/" + event.name;
|
||||
}
|
||||
|
||||
NetworkTableEntry entry = table.getEntry(name);
|
||||
try {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
if (isNew) {
|
||||
entry.setDefaultBoolean(event.value != 0);
|
||||
} else {
|
||||
entry.setBoolean(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
if (isNew) {
|
||||
entry.setDefaultDouble(event.value);
|
||||
table.getEntry(infoName + "/min").setDouble(
|
||||
CameraServerJNI.getPropertyMin(event.propertyHandle));
|
||||
table.getEntry(infoName + "/max").setDouble(
|
||||
CameraServerJNI.getPropertyMax(event.propertyHandle));
|
||||
table.getEntry(infoName + "/step").setDouble(
|
||||
CameraServerJNI.getPropertyStep(event.propertyHandle));
|
||||
table.getEntry(infoName + "/default").setDouble(
|
||||
CameraServerJNI.getPropertyDefault(event.propertyHandle));
|
||||
} else {
|
||||
entry.setDouble(event.value);
|
||||
}
|
||||
break;
|
||||
case kString:
|
||||
if (isNew) {
|
||||
entry.setDefaultString(event.valueStr);
|
||||
} else {
|
||||
entry.setString(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.UnusedLocalVariable", "PMD.ExcessiveMethodLength",
|
||||
"PMD.NPathComplexity"})
|
||||
private CameraServer() {
|
||||
m_defaultUsbDevice = new AtomicInteger();
|
||||
m_sources = new Hashtable<>();
|
||||
m_sinks = new Hashtable<>();
|
||||
m_fixedSources = new Hashtable<>();
|
||||
m_tables = new Hashtable<>();
|
||||
m_publishTable = NetworkTableInstance.getDefault().getTable(kPublishName);
|
||||
m_nextPort = kBasePort;
|
||||
m_addresses = new String[0];
|
||||
|
||||
// We publish sources to NetworkTables using the following structure:
|
||||
// "/CameraPublisher/{Source.Name}/" - root
|
||||
// - "source" (string): Descriptive, prefixed with type (e.g. "usb:0")
|
||||
// - "streams" (string array): URLs that can be used to stream data
|
||||
// - "description" (string): Description of the source
|
||||
// - "connected" (boolean): Whether source is connected
|
||||
// - "mode" (string): Current video mode
|
||||
// - "modes" (string array): Available video modes
|
||||
// - "Property/{Property}" - Property values
|
||||
// - "PropertyInfo/{Property}" - Property supporting information
|
||||
|
||||
// Listener for video events
|
||||
m_videoListener = new VideoListener(event -> {
|
||||
switch (event.kind) {
|
||||
case kSourceCreated: {
|
||||
// Create subtable for the camera
|
||||
NetworkTable table = m_publishTable.getSubTable(event.name);
|
||||
m_tables.put(event.sourceHandle, table);
|
||||
table.getEntry("source").setString(makeSourceValue(event.sourceHandle));
|
||||
table.getEntry("description").setString(
|
||||
CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
table.getEntry("connected").setBoolean(
|
||||
CameraServerJNI.isSourceConnected(event.sourceHandle));
|
||||
table.getEntry("streams").setStringArray(getSourceStreamValues(event.sourceHandle));
|
||||
try {
|
||||
VideoMode mode = CameraServerJNI.getSourceVideoMode(event.sourceHandle);
|
||||
table.getEntry("mode").setDefaultString(videoModeToString(mode));
|
||||
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
|
||||
} catch (VideoException ignored) {
|
||||
// Do nothing. Let the other event handlers update this if there is an error.
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDestroyed: {
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("source").setString("");
|
||||
table.getEntry("streams").setStringArray(new String[0]);
|
||||
table.getEntry("modes").setStringArray(new String[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceConnected: {
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
// update the description too (as it may have changed)
|
||||
table.getEntry("description").setString(
|
||||
CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
table.getEntry("connected").setBoolean(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDisconnected: {
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("connected").setBoolean(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModesUpdated: {
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModeChanged: {
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("mode").setString(videoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyCreated: {
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
putSourcePropertyValue(table, event, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyValueUpdated: {
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
putSourcePropertyValue(table, event, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyChoicesUpdated: {
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
try {
|
||||
String[] choices = CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
|
||||
table.getEntry("PropertyInfo/" + event.name + "/choices").setStringArray(choices);
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSinkSourceChanged:
|
||||
case kSinkCreated:
|
||||
case kSinkDestroyed:
|
||||
case kNetworkInterfacesChanged: {
|
||||
m_addresses = CameraServerJNI.getNetworkInterfaces();
|
||||
updateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, 0x4fff, true);
|
||||
|
||||
// Listener for NetworkTable events
|
||||
// We don't currently support changing settings via NT due to
|
||||
// synchronization issues, so just update to current setting if someone
|
||||
// else tries to change it.
|
||||
m_tableListener = NetworkTableInstance.getDefault().addEntryListener(kPublishName + "/",
|
||||
event -> {
|
||||
String relativeKey = event.name.substring(kPublishName.length() + 1);
|
||||
|
||||
// get source (sourceName/...)
|
||||
int subKeyIndex = relativeKey.indexOf('/');
|
||||
if (subKeyIndex == -1) {
|
||||
return;
|
||||
}
|
||||
String sourceName = relativeKey.substring(0, subKeyIndex);
|
||||
VideoSource source = m_sources.get(sourceName);
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get subkey
|
||||
relativeKey = relativeKey.substring(subKeyIndex + 1);
|
||||
|
||||
// handle standard names
|
||||
String propName;
|
||||
if ("mode".equals(relativeKey)) {
|
||||
// reset to current mode
|
||||
event.getEntry().setString(videoModeToString(source.getVideoMode()));
|
||||
return;
|
||||
} else if (relativeKey.startsWith("Property/")) {
|
||||
propName = relativeKey.substring(9);
|
||||
} else if (relativeKey.startsWith("RawProperty/")) {
|
||||
propName = relativeKey.substring(12);
|
||||
} else {
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
// everything else is a property
|
||||
VideoProperty property = source.getProperty(propName);
|
||||
switch (property.getKind()) {
|
||||
case kNone:
|
||||
return;
|
||||
case kBoolean:
|
||||
// reset to current setting
|
||||
event.getEntry().setBoolean(property.get() != 0);
|
||||
return;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
// reset to current setting
|
||||
event.getEntry().setDouble(property.get());
|
||||
return;
|
||||
case kString:
|
||||
// reset to current setting
|
||||
event.getEntry().setString(property.getString());
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}, EntryListenerFlags.kImmediate | EntryListenerFlags.kUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
* <p>You should call this method to see a camera feed on the dashboard.
|
||||
* If you also want to perform vision processing on the roboRIO, use
|
||||
* getVideo() to get access to the camera images.
|
||||
*
|
||||
* <p>The first time this overload is called, it calls
|
||||
* {@link #startAutomaticCapture(int)} with device 0, creating a camera
|
||||
* named "USB Camera 0". Subsequent calls increment the device number
|
||||
* (e.g. 1, 2, etc).
|
||||
*/
|
||||
public UsbCamera startAutomaticCapture() {
|
||||
UsbCamera camera = startAutomaticCapture(m_defaultUsbDevice.getAndIncrement());
|
||||
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
* <p>This overload calls {@link #startAutomaticCapture(String, int)} with
|
||||
* a name of "USB Camera {dev}".
|
||||
*
|
||||
* @param dev The device number of the camera interface
|
||||
*/
|
||||
public UsbCamera startAutomaticCapture(int dev) {
|
||||
UsbCamera camera = new UsbCamera("USB Camera " + dev, dev);
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param dev The device number of the camera interface
|
||||
*/
|
||||
public UsbCamera startAutomaticCapture(String name, int dev) {
|
||||
UsbCamera camera = new UsbCamera(name, dev);
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param path The device path (e.g. "/dev/video0") of the camera
|
||||
*/
|
||||
public UsbCamera startAutomaticCapture(String name, String path) {
|
||||
UsbCamera camera = new UsbCamera(name, path);
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard from
|
||||
* an existing camera.
|
||||
*
|
||||
* @param camera Camera
|
||||
*/
|
||||
public MjpegServer startAutomaticCapture(VideoSource camera) {
|
||||
addCamera(camera);
|
||||
MjpegServer server = addServer("serve_" + camera.getName());
|
||||
server.setSource(camera);
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* <p>This overload calls {@link #addAxisCamera(String, String)} with
|
||||
* name "Axis Camera".
|
||||
*
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
*/
|
||||
public AxisCamera addAxisCamera(String host) {
|
||||
return addAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* <p>This overload calls {@link #addAxisCamera(String, String[])} with
|
||||
* name "Axis Camera".
|
||||
*
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
public AxisCamera addAxisCamera(String[] hosts) {
|
||||
return addAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
*/
|
||||
public AxisCamera addAxisCamera(String name, String host) {
|
||||
AxisCamera camera = new AxisCamera(name, host);
|
||||
// Create a passthrough MJPEG server for USB access
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportAxisCamera(camera.getHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
public AxisCamera addAxisCamera(String name, String[] hosts) {
|
||||
AxisCamera camera = new AxisCamera(name, hosts);
|
||||
// Create a passthrough MJPEG server for USB access
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.getCameraServerShared().reportAxisCamera(camera.getHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a virtual camera for switching between two streams. Unlike the
|
||||
* other addCamera methods, this returns a VideoSink rather than a
|
||||
* VideoSource. Calling setSource() on the returned object can be used
|
||||
* to switch the actual source of the stream.
|
||||
*/
|
||||
public MjpegServer addSwitchedCamera(String name) {
|
||||
// create a dummy CvSource
|
||||
CvSource source = new CvSource(name, VideoMode.PixelFormat.kMJPEG, 160, 120, 30);
|
||||
MjpegServer server = startAutomaticCapture(source);
|
||||
m_fixedSources.put(server.getHandle(), source.getHandle());
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the primary camera feed. This allows you to
|
||||
* get images from the camera for image processing on the roboRIO.
|
||||
*
|
||||
* <p>This is only valid to call after a camera feed has been added
|
||||
* with startAutomaticCapture() or addServer().
|
||||
*/
|
||||
public CvSink getVideo() {
|
||||
VideoSource source;
|
||||
synchronized (this) {
|
||||
if (m_primarySourceName == null) {
|
||||
throw new VideoException("no camera available");
|
||||
}
|
||||
source = m_sources.get(m_primarySourceName);
|
||||
}
|
||||
if (source == null) {
|
||||
throw new VideoException("no camera available");
|
||||
}
|
||||
return getVideo(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the specified camera. This allows you to get
|
||||
* images from the camera for image processing on the roboRIO.
|
||||
*
|
||||
* @param camera Camera (e.g. as returned by startAutomaticCapture).
|
||||
*/
|
||||
public CvSink getVideo(VideoSource camera) {
|
||||
String name = "opencv_" + camera.getName();
|
||||
|
||||
synchronized (this) {
|
||||
VideoSink sink = m_sinks.get(name);
|
||||
if (sink != null) {
|
||||
VideoSink.Kind kind = sink.getKind();
|
||||
if (kind != VideoSink.Kind.kCv) {
|
||||
throw new VideoException("expected OpenCV sink, but got " + kind);
|
||||
}
|
||||
return (CvSink) sink;
|
||||
}
|
||||
}
|
||||
|
||||
CvSink newsink = new CvSink(name);
|
||||
newsink.setSource(camera);
|
||||
addServer(newsink);
|
||||
return newsink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the specified camera. This allows you to get
|
||||
* images from the camera for image processing on the roboRIO.
|
||||
*
|
||||
* @param name Camera name
|
||||
*/
|
||||
public CvSink getVideo(String name) {
|
||||
VideoSource source;
|
||||
synchronized (this) {
|
||||
source = m_sources.get(name);
|
||||
if (source == null) {
|
||||
throw new VideoException("could not find camera " + name);
|
||||
}
|
||||
}
|
||||
return getVideo(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MJPEG stream with OpenCV input. This can be called to pass custom
|
||||
* annotated images to the dashboard.
|
||||
*
|
||||
* @param name Name to give the stream
|
||||
* @param width Width of the image being sent
|
||||
* @param height Height of the image being sent
|
||||
*/
|
||||
public CvSource putVideo(String name, int width, int height) {
|
||||
CvSource source = new CvSource(name, VideoMode.PixelFormat.kMJPEG, width, height, 30);
|
||||
startAutomaticCapture(source);
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a MJPEG server at the next available port.
|
||||
*
|
||||
* @param name Server name
|
||||
*/
|
||||
public MjpegServer addServer(String name) {
|
||||
int port;
|
||||
synchronized (this) {
|
||||
port = m_nextPort;
|
||||
m_nextPort++;
|
||||
}
|
||||
return addServer(name, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a MJPEG server.
|
||||
*
|
||||
* @param name Server name
|
||||
*/
|
||||
public MjpegServer addServer(String name, int port) {
|
||||
MjpegServer server = new MjpegServer(name, port);
|
||||
addServer(server);
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an already created server.
|
||||
*
|
||||
* @param server Server
|
||||
*/
|
||||
public void addServer(VideoSink server) {
|
||||
synchronized (this) {
|
||||
m_sinks.put(server.getName(), server);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a server by name.
|
||||
*
|
||||
* @param name Server name
|
||||
*/
|
||||
public void removeServer(String name) {
|
||||
synchronized (this) {
|
||||
m_sinks.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server for the primary camera feed.
|
||||
*
|
||||
* <p>This is only valid to call after a camera feed has been added
|
||||
* with startAutomaticCapture() or addServer().
|
||||
*/
|
||||
public VideoSink getServer() {
|
||||
synchronized (this) {
|
||||
if (m_primarySourceName == null) {
|
||||
throw new VideoException("no camera available");
|
||||
}
|
||||
return getServer("serve_" + m_primarySourceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a server by name.
|
||||
*
|
||||
* @param name Server name
|
||||
*/
|
||||
public VideoSink getServer(String name) {
|
||||
synchronized (this) {
|
||||
return m_sinks.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an already created camera.
|
||||
*
|
||||
* @param camera Camera
|
||||
*/
|
||||
public void addCamera(VideoSource camera) {
|
||||
String name = camera.getName();
|
||||
synchronized (this) {
|
||||
if (m_primarySourceName == null) {
|
||||
m_primarySourceName = name;
|
||||
}
|
||||
m_sources.put(name, camera);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a camera by name.
|
||||
*
|
||||
* @param name Camera name
|
||||
*/
|
||||
public void removeCamera(String name) {
|
||||
synchronized (this) {
|
||||
m_sources.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
|
||||
public interface CameraServerShared {
|
||||
/**
|
||||
* get the main thread id func.
|
||||
*
|
||||
* @return the robotMainThreadId
|
||||
*/
|
||||
Long getRobotMainThreadId();
|
||||
|
||||
/**
|
||||
* Report an error to the driver station.
|
||||
*
|
||||
* @param error the error to set
|
||||
*/
|
||||
void reportDriverStationError(String error);
|
||||
|
||||
/**
|
||||
* Report an video server usage.
|
||||
*
|
||||
* @param id the usage id
|
||||
*/
|
||||
void reportVideoServer(int id);
|
||||
|
||||
/**
|
||||
* Report a usb camera usage.
|
||||
*
|
||||
* @param id the usage id
|
||||
*/
|
||||
void reportUsbCamera(int id);
|
||||
|
||||
/**
|
||||
* Report an axis camera usage.
|
||||
*
|
||||
* @param id the usage id
|
||||
*/
|
||||
void reportAxisCamera(int id);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
public final class CameraServerSharedStore {
|
||||
private static CameraServerShared cameraServerShared;
|
||||
|
||||
private CameraServerSharedStore() {
|
||||
}
|
||||
|
||||
/**
|
||||
* get the CameraServerShared object.
|
||||
*/
|
||||
public static synchronized CameraServerShared getCameraServerShared() {
|
||||
if (cameraServerShared == null) {
|
||||
cameraServerShared = new CameraServerShared() {
|
||||
|
||||
@Override
|
||||
public void reportVideoServer(int id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportUsbCamera(int id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDriverStationError(String error) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAxisCamera(int id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getRobotMainThreadId() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
return cameraServerShared;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the CameraServerShared object.
|
||||
*/
|
||||
public static synchronized void setCameraServerShared(CameraServerShared shared) {
|
||||
cameraServerShared = shared;
|
||||
}
|
||||
}
|
||||
@@ -5,24 +5,22 @@
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "HLUsageReporting.h"
|
||||
package edu.wpi.first.vision;
|
||||
|
||||
using namespace frc;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
HLUsageReportingInterface* HLUsageReporting::impl = nullptr;
|
||||
/**
|
||||
* A vision pipeline is responsible for running a group of
|
||||
* OpenCV algorithms to extract data from an image.
|
||||
*
|
||||
* @see VisionRunner
|
||||
* @see VisionThread
|
||||
*/
|
||||
public interface VisionPipeline {
|
||||
/**
|
||||
* Processes the image input and sets the result objects.
|
||||
* Implementations should make these objects accessible.
|
||||
*/
|
||||
void process(Mat image);
|
||||
|
||||
void HLUsageReporting::SetImplementation(HLUsageReportingInterface* i) {
|
||||
impl = i;
|
||||
}
|
||||
|
||||
void HLUsageReporting::ReportScheduler() {
|
||||
if (impl != nullptr) {
|
||||
impl->ReportScheduler();
|
||||
}
|
||||
}
|
||||
|
||||
void HLUsageReporting::ReportSmartDashboard() {
|
||||
if (impl != nullptr) {
|
||||
impl->ReportSmartDashboard();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.vision;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
import edu.wpi.cscore.CvSink;
|
||||
import edu.wpi.cscore.VideoSource;
|
||||
import edu.wpi.first.cameraserver.CameraServerSharedStore;
|
||||
|
||||
/**
|
||||
* A vision runner is a convenient wrapper object to make it easy to run vision pipelines
|
||||
* from robot code. The easiest way to use this is to run it in a {@link VisionThread}
|
||||
* and use the listener to take snapshots of the pipeline's outputs.
|
||||
*
|
||||
* @see VisionPipeline
|
||||
* @see VisionThread
|
||||
* @see <a href="package-summary.html">vision</a>
|
||||
*/
|
||||
public class VisionRunner<P extends VisionPipeline> {
|
||||
private final CvSink m_cvSink = new CvSink("VisionRunner CvSink");
|
||||
private final P m_pipeline;
|
||||
private final Mat m_image = new Mat();
|
||||
private final Listener<? super P> m_listener;
|
||||
private volatile boolean m_enabled = true;
|
||||
|
||||
/**
|
||||
* Listener interface for a callback that should run after a pipeline has processed its input.
|
||||
*
|
||||
* @param <P> the type of the pipeline this listener is for
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Listener<P extends VisionPipeline> {
|
||||
/**
|
||||
* Called when the pipeline has run. This shouldn't take much time to run because it will delay
|
||||
* later calls to the pipeline's {@link VisionPipeline#process process} method. Copying the
|
||||
* outputs and code that uses the copies should be <i>synchronized</i> on the same mutex to
|
||||
* prevent multiple threads from reading and writing to the same memory at the same time.
|
||||
*
|
||||
* @param pipeline the vision pipeline that ran
|
||||
*/
|
||||
void copyPipelineOutputs(P pipeline);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new vision runner. It will take images from the {@code videoSource}, send them to
|
||||
* the {@code pipeline}, and call the {@code listener} when the pipeline has finished to alert
|
||||
* user code when it is safe to access the pipeline's outputs.
|
||||
*
|
||||
* @param videoSource the video source to use to supply images for the pipeline
|
||||
* @param pipeline the vision pipeline to run
|
||||
* @param listener a function to call after the pipeline has finished running
|
||||
*/
|
||||
public VisionRunner(VideoSource videoSource, P pipeline, Listener<? super P> listener) {
|
||||
this.m_pipeline = pipeline;
|
||||
this.m_listener = listener;
|
||||
m_cvSink.setSource(videoSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the pipeline one time, giving it the next image from the video source specified
|
||||
* in the constructor. This will block until the source either has an image or throws an error.
|
||||
* If the source successfully supplied a frame, the pipeline's image input will be set,
|
||||
* the pipeline will run, and the listener specified in the constructor will be called to notify
|
||||
* it that the pipeline ran.
|
||||
*
|
||||
* <p>This method is exposed to allow teams to add additional functionality or have their own
|
||||
* ways to run the pipeline. Most teams, however, should just use {@link #runForever} in its own
|
||||
* thread using a {@link VisionThread}.</p>
|
||||
*/
|
||||
public void runOnce() {
|
||||
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
|
||||
|
||||
if (id != null && Thread.currentThread().getId() == id.longValue()) {
|
||||
throw new IllegalStateException(
|
||||
"VisionRunner.runOnce() cannot be called from the main robot thread");
|
||||
}
|
||||
runOnceInternal();
|
||||
}
|
||||
|
||||
private void runOnceInternal() {
|
||||
long frameTime = m_cvSink.grabFrame(m_image);
|
||||
if (frameTime == 0) {
|
||||
// There was an error, report it
|
||||
String error = m_cvSink.getError();
|
||||
CameraServerSharedStore.getCameraServerShared().reportDriverStationError(error);
|
||||
} else {
|
||||
// No errors, process the image
|
||||
m_pipeline.process(m_image);
|
||||
m_listener.copyPipelineOutputs(m_pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method that calls {@link #runOnce()} in an infinite loop. This must
|
||||
* be run in a dedicated thread, and cannot be used in the main robot thread because
|
||||
* it will freeze the robot program.
|
||||
*
|
||||
* <p><strong>Do not call this method directly from the main thread.</strong></p>
|
||||
*
|
||||
* @throws IllegalStateException if this is called from the main robot thread
|
||||
* @see VisionThread
|
||||
*/
|
||||
public void runForever() {
|
||||
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
|
||||
|
||||
if (id != null && Thread.currentThread().getId() == id.longValue()) {
|
||||
throw new IllegalStateException(
|
||||
"VisionRunner.runForever() cannot be called from the main robot thread");
|
||||
}
|
||||
while (m_enabled && !Thread.interrupted()) {
|
||||
runOnceInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a RunForever() loop.
|
||||
*/
|
||||
public void stop() {
|
||||
m_enabled = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.vision;
|
||||
|
||||
import edu.wpi.cscore.VideoSource;
|
||||
|
||||
/**
|
||||
* A vision thread is a special thread that runs a vision pipeline. It is a <i>daemon</i> thread;
|
||||
* it does not prevent the program from exiting when all other non-daemon threads
|
||||
* have finished running.
|
||||
*
|
||||
* @see VisionPipeline
|
||||
* @see VisionRunner
|
||||
* @see Thread#setDaemon(boolean)
|
||||
*/
|
||||
public class VisionThread extends Thread {
|
||||
/**
|
||||
* Creates a vision thread that continuously runs a {@link VisionPipeline}.
|
||||
*
|
||||
* @param visionRunner the runner for a vision pipeline
|
||||
*/
|
||||
public VisionThread(VisionRunner<?> visionRunner) {
|
||||
super(visionRunner::runForever, "WPILib Vision Thread");
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new vision thread that continuously runs the given vision pipeline. This is
|
||||
* equivalent to {@code new VisionThread(new VisionRunner<>(videoSource, pipeline, listener))}.
|
||||
*
|
||||
* @param videoSource the source for images the pipeline should process
|
||||
* @param pipeline the pipeline to run
|
||||
* @param listener the listener to copy outputs from the pipeline after it runs
|
||||
* @param <P> the type of the pipeline
|
||||
*/
|
||||
public <P extends VisionPipeline> VisionThread(VideoSource videoSource,
|
||||
P pipeline,
|
||||
VisionRunner.Listener<? super P> listener) {
|
||||
this(new VisionRunner<>(videoSource, pipeline, listener));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Classes in the {@code edu.wpi.first.vision} package are designed to
|
||||
* simplify using OpenCV vision processing code from a robot program.
|
||||
*
|
||||
* <p>An example use case for grabbing a yellow tote from 2015 in autonomous:
|
||||
* <br>
|
||||
* <pre><code>
|
||||
* public class Robot extends IterativeRobot
|
||||
* implements VisionRunner.Listener<MyFindTotePipeline> {
|
||||
*
|
||||
* // A USB camera connected to the roboRIO.
|
||||
* private {@link edu.wpi.cscore.VideoSource VideoSource} usbCamera;
|
||||
*
|
||||
* // A vision pipeline. This could be handwritten or generated by GRIP.
|
||||
* // This has to implement {@link edu.wpi.first.vision.VisionPipeline}.
|
||||
* // For this example, assume that it's perfect and will always see the tote.
|
||||
* private MyFindTotePipeline findTotePipeline;
|
||||
* private {@link edu.wpi.first.vision.VisionThread} findToteThread;
|
||||
*
|
||||
* // The object to synchronize on to make sure the vision thread doesn't
|
||||
* // write to variables the main thread is using.
|
||||
* private final Object visionLock = new Object();
|
||||
*
|
||||
* // The pipeline outputs we want
|
||||
* private boolean pipelineRan = false; // lets us know when the pipeline has actually run
|
||||
* private double angleToTote = 0;
|
||||
* private double distanceToTote = 0;
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void {@link edu.wpi.first.vision.VisionRunner.Listener#copyPipelineOutputs
|
||||
* copyPipelineOutputs(MyFindTotePipeline pipeline)} {
|
||||
* synchronized (visionLock) {
|
||||
* // Take a snapshot of the pipeline's output because
|
||||
* // it may have changed the next time this method is called!
|
||||
* this.pipelineRan = true;
|
||||
* this.angleToTote = pipeline.getAngleToTote();
|
||||
* this.distanceToTote = pipeline.getDistanceToTote();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void robotInit() {
|
||||
* usbCamera = CameraServer.getInstance().startAutomaticCapture(0);
|
||||
* findTotePipeline = new MyFindTotePipeline();
|
||||
* findToteThread = new VisionThread(usbCamera, findTotePipeline, this);
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void autonomousInit() {
|
||||
* findToteThread.start();
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void autonomousPeriodic() {
|
||||
* double angle;
|
||||
* double distance;
|
||||
* synchronized (visionLock) {
|
||||
* if (!pipelineRan) {
|
||||
* // Wait until the pipeline has run
|
||||
* return;
|
||||
* }
|
||||
* // Copy the outputs to make sure they're all from the same run
|
||||
* angle = this.angleToTote;
|
||||
* distance = this.distanceToTote;
|
||||
* }
|
||||
* if (!aimedAtTote()) {
|
||||
* turnToAngle(angle);
|
||||
* } else if (!droveToTote()) {
|
||||
* driveDistance(distance);
|
||||
* } else if (!grabbedTote()) {
|
||||
* grabTote();
|
||||
* } else {
|
||||
* // Tote was grabbed and we're done!
|
||||
* return;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </code></pre>
|
||||
*/
|
||||
package edu.wpi.first.vision;
|
||||
@@ -5,71 +5,93 @@
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "CameraServer.h"
|
||||
#include "cameraserver/CameraServer.h"
|
||||
|
||||
#include <HAL/HAL.h>
|
||||
#include <llvm/SmallString.h>
|
||||
#include <llvm/raw_ostream.h>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTable.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "Utility.h"
|
||||
#include "WPIErrors.h"
|
||||
#include "cameraserver/CameraServerShared.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
static constexpr char const* kPublishName = "/CameraPublisher";
|
||||
|
||||
struct CameraServer::Impl {
|
||||
Impl();
|
||||
std::shared_ptr<nt::NetworkTable> GetSourceTable(CS_Source source);
|
||||
std::vector<std::string> GetSinkStreamValues(CS_Sink sink);
|
||||
std::vector<std::string> GetSourceStreamValues(CS_Source source);
|
||||
void UpdateStreamValues();
|
||||
|
||||
wpi::mutex m_mutex;
|
||||
std::atomic<int> m_defaultUsbDevice{0};
|
||||
std::string m_primarySourceName;
|
||||
wpi::StringMap<cs::VideoSource> m_sources;
|
||||
wpi::StringMap<cs::VideoSink> m_sinks;
|
||||
wpi::DenseMap<CS_Sink, CS_Source> m_fixedSources;
|
||||
wpi::DenseMap<CS_Source, std::shared_ptr<nt::NetworkTable>> m_tables;
|
||||
std::shared_ptr<nt::NetworkTable> m_publishTable;
|
||||
cs::VideoListener m_videoListener;
|
||||
int m_tableListener;
|
||||
int m_nextPort;
|
||||
std::vector<std::string> m_addresses;
|
||||
};
|
||||
|
||||
CameraServer* CameraServer::GetInstance() {
|
||||
static CameraServer instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
static llvm::StringRef MakeSourceValue(CS_Source source,
|
||||
llvm::SmallVectorImpl<char>& buf) {
|
||||
static wpi::StringRef MakeSourceValue(CS_Source source,
|
||||
wpi::SmallVectorImpl<char>& buf) {
|
||||
CS_Status status = 0;
|
||||
buf.clear();
|
||||
switch (cs::GetSourceKind(source, &status)) {
|
||||
#ifdef __linux__
|
||||
case cs::VideoSource::kUsb: {
|
||||
llvm::StringRef prefix{"usb:"};
|
||||
wpi::StringRef prefix{"usb:"};
|
||||
buf.append(prefix.begin(), prefix.end());
|
||||
auto path = cs::GetUsbCameraPath(source, &status);
|
||||
buf.append(path.begin(), path.end());
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case cs::VideoSource::kHttp: {
|
||||
llvm::StringRef prefix{"ip:"};
|
||||
wpi::StringRef prefix{"ip:"};
|
||||
buf.append(prefix.begin(), prefix.end());
|
||||
auto urls = cs::GetHttpCameraUrls(source, &status);
|
||||
if (!urls.empty()) buf.append(urls[0].begin(), urls[0].end());
|
||||
break;
|
||||
}
|
||||
case cs::VideoSource::kCv:
|
||||
// FIXME: Should be "cv:", but LabVIEW dashboard requires "usb:".
|
||||
// https://github.com/wpilibsuite/allwpilib/issues/407
|
||||
return "usb:";
|
||||
return "cv:";
|
||||
default:
|
||||
return "unknown:";
|
||||
}
|
||||
|
||||
return llvm::StringRef{buf.begin(), buf.size()};
|
||||
return wpi::StringRef{buf.begin(), buf.size()};
|
||||
}
|
||||
|
||||
static std::string MakeStreamValue(llvm::StringRef address, int port) {
|
||||
std::string rv;
|
||||
llvm::raw_string_ostream stream(rv);
|
||||
stream << "mjpg:http://" << address << ':' << port << "/?action=stream";
|
||||
stream.flush();
|
||||
return rv;
|
||||
static std::string MakeStreamValue(const wpi::Twine& address, int port) {
|
||||
return ("mjpg:http://" + address + wpi::Twine(':') + wpi::Twine(port) +
|
||||
"/?action=stream")
|
||||
.str();
|
||||
}
|
||||
|
||||
std::shared_ptr<nt::NetworkTable> CameraServer::GetSourceTable(
|
||||
std::shared_ptr<nt::NetworkTable> CameraServer::Impl::GetSourceTable(
|
||||
CS_Source source) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
return m_tables.lookup(source);
|
||||
}
|
||||
|
||||
std::vector<std::string> CameraServer::GetSinkStreamValues(CS_Sink sink) {
|
||||
std::vector<std::string> CameraServer::Impl::GetSinkStreamValues(CS_Sink sink) {
|
||||
CS_Status status = 0;
|
||||
|
||||
// Ignore all but MjpegServer
|
||||
@@ -98,7 +120,8 @@ std::vector<std::string> CameraServer::GetSinkStreamValues(CS_Sink sink) {
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<std::string> CameraServer::GetSourceStreamValues(CS_Source source) {
|
||||
std::vector<std::string> CameraServer::Impl::GetSourceStreamValues(
|
||||
CS_Source source) {
|
||||
CS_Status status = 0;
|
||||
|
||||
// Ignore all but HttpCamera
|
||||
@@ -126,7 +149,7 @@ std::vector<std::string> CameraServer::GetSourceStreamValues(CS_Source source) {
|
||||
return values;
|
||||
}
|
||||
|
||||
void CameraServer::UpdateStreamValues() {
|
||||
void CameraServer::Impl::UpdateStreamValues() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
// Over all the sinks...
|
||||
for (const auto& i : m_sinks) {
|
||||
@@ -134,7 +157,8 @@ void CameraServer::UpdateStreamValues() {
|
||||
CS_Sink sink = i.second.GetHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
CS_Source source = cs::GetSinkSource(sink, &status);
|
||||
CS_Source source = m_fixedSources.lookup(sink);
|
||||
if (source == 0) source = cs::GetSinkSource(sink, &status);
|
||||
if (source == 0) continue;
|
||||
auto table = m_tables.lookup(source);
|
||||
if (table) {
|
||||
@@ -180,7 +204,7 @@ static std::string PixelFormatToString(int pixelFormat) {
|
||||
|
||||
static std::string VideoModeToString(const cs::VideoMode& mode) {
|
||||
std::string rv;
|
||||
llvm::raw_string_ostream oss{rv};
|
||||
wpi::raw_string_ostream oss{rv};
|
||||
oss << mode.width << "x" << mode.height;
|
||||
oss << " " << PixelFormatToString(mode.pixelFormat) << " ";
|
||||
oss << mode.fps << " fps";
|
||||
@@ -195,20 +219,11 @@ static std::vector<std::string> GetSourceModeValues(int source) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
static inline llvm::StringRef Concatenate(llvm::StringRef lhs,
|
||||
llvm::StringRef rhs,
|
||||
llvm::SmallVectorImpl<char>& buf) {
|
||||
buf.clear();
|
||||
llvm::raw_svector_ostream oss{buf};
|
||||
oss << lhs << rhs;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
const cs::VideoEvent& event, bool isNew) {
|
||||
llvm::SmallString<64> name;
|
||||
llvm::SmallString<64> infoName;
|
||||
if (llvm::StringRef{event.name}.startswith("raw_")) {
|
||||
wpi::SmallString<64> name;
|
||||
wpi::SmallString<64> infoName;
|
||||
if (wpi::StringRef{event.name}.startswith("raw_")) {
|
||||
name = "RawProperty/";
|
||||
name += event.name;
|
||||
infoName = "RawPropertyInfo/";
|
||||
@@ -220,7 +235,7 @@ static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
infoName += event.name;
|
||||
}
|
||||
|
||||
llvm::SmallString<64> buf;
|
||||
wpi::SmallString<64> buf;
|
||||
CS_Status status = 0;
|
||||
nt::NetworkTableEntry entry = table->GetEntry(name);
|
||||
switch (event.propertyKind) {
|
||||
@@ -234,13 +249,13 @@ static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
case cs::VideoProperty::kEnum:
|
||||
if (isNew) {
|
||||
entry.SetDefaultDouble(event.value);
|
||||
table->GetEntry(Concatenate(infoName, "/min", buf))
|
||||
table->GetEntry(infoName + "/min")
|
||||
.SetDouble(cs::GetPropertyMin(event.propertyHandle, &status));
|
||||
table->GetEntry(Concatenate(infoName, "/max", buf))
|
||||
table->GetEntry(infoName + "/max")
|
||||
.SetDouble(cs::GetPropertyMax(event.propertyHandle, &status));
|
||||
table->GetEntry(Concatenate(infoName, "/step", buf))
|
||||
table->GetEntry(infoName + "/step")
|
||||
.SetDouble(cs::GetPropertyStep(event.propertyHandle, &status));
|
||||
table->GetEntry(Concatenate(infoName, "/default", buf))
|
||||
table->GetEntry(infoName + "/default")
|
||||
.SetDouble(cs::GetPropertyDefault(event.propertyHandle, &status));
|
||||
} else {
|
||||
entry.SetDouble(event.value);
|
||||
@@ -257,7 +272,7 @@ static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
}
|
||||
}
|
||||
|
||||
CameraServer::CameraServer()
|
||||
CameraServer::Impl::Impl()
|
||||
: m_publishTable{nt::NetworkTableInstance::GetDefault().GetTable(
|
||||
kPublishName)},
|
||||
m_nextPort(kBasePort) {
|
||||
@@ -284,10 +299,10 @@ CameraServer::CameraServer()
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_tables.insert(std::make_pair(event.sourceHandle, table));
|
||||
}
|
||||
llvm::SmallString<64> buf;
|
||||
wpi::SmallString<64> buf;
|
||||
table->GetEntry("source").SetString(
|
||||
MakeSourceValue(event.sourceHandle, buf));
|
||||
llvm::SmallString<64> descBuf;
|
||||
wpi::SmallString<64> descBuf;
|
||||
table->GetEntry("description")
|
||||
.SetString(cs::GetSourceDescription(event.sourceHandle, descBuf,
|
||||
&status));
|
||||
@@ -316,7 +331,7 @@ CameraServer::CameraServer()
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
// update the description too (as it may have changed)
|
||||
llvm::SmallString<64> descBuf;
|
||||
wpi::SmallString<64> descBuf;
|
||||
table->GetEntry("description")
|
||||
.SetString(cs::GetSourceDescription(event.sourceHandle,
|
||||
descBuf, &status));
|
||||
@@ -355,7 +370,7 @@ CameraServer::CameraServer()
|
||||
case cs::VideoEvent::kSourcePropertyChoicesUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
llvm::SmallString<64> name{"PropertyInfo/"};
|
||||
wpi::SmallString<64> name{"PropertyInfo/"};
|
||||
name += event.name;
|
||||
name += "/choices";
|
||||
auto choices =
|
||||
@@ -382,17 +397,17 @@ CameraServer::CameraServer()
|
||||
// We don't currently support changing settings via NT due to
|
||||
// synchronization issues, so just update to current setting if someone
|
||||
// else tries to change it.
|
||||
llvm::SmallString<64> buf;
|
||||
wpi::SmallString<64> buf;
|
||||
m_tableListener = nt::NetworkTableInstance::GetDefault().AddEntryListener(
|
||||
Concatenate(kPublishName, "/", buf),
|
||||
kPublishName + wpi::Twine('/'),
|
||||
[=](const nt::EntryNotification& event) {
|
||||
llvm::StringRef relativeKey =
|
||||
event.name.substr(llvm::StringRef(kPublishName).size() + 1);
|
||||
wpi::StringRef relativeKey =
|
||||
event.name.substr(wpi::StringRef(kPublishName).size() + 1);
|
||||
|
||||
// get source (sourceName/...)
|
||||
auto subKeyIndex = relativeKey.find('/');
|
||||
if (subKeyIndex == llvm::StringRef::npos) return;
|
||||
llvm::StringRef sourceName = relativeKey.slice(0, subKeyIndex);
|
||||
if (subKeyIndex == wpi::StringRef::npos) return;
|
||||
wpi::StringRef sourceName = relativeKey.slice(0, subKeyIndex);
|
||||
auto sourceIt = m_sources.find(sourceName);
|
||||
if (sourceIt == m_sources.end()) return;
|
||||
|
||||
@@ -400,7 +415,7 @@ CameraServer::CameraServer()
|
||||
relativeKey = relativeKey.substr(subKeyIndex + 1);
|
||||
|
||||
// handle standard names
|
||||
llvm::StringRef propName;
|
||||
wpi::StringRef propName;
|
||||
nt::NetworkTableEntry entry{event.entry};
|
||||
if (relativeKey == "mode") {
|
||||
// reset to current mode
|
||||
@@ -435,46 +450,45 @@ CameraServer::CameraServer()
|
||||
},
|
||||
NT_NOTIFY_IMMEDIATE | NT_NOTIFY_UPDATE);
|
||||
}
|
||||
#ifdef __linux__
|
||||
|
||||
CameraServer::CameraServer() : m_impl(new Impl) {}
|
||||
|
||||
CameraServer::~CameraServer() {}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture() {
|
||||
cs::UsbCamera camera = StartAutomaticCapture(m_defaultUsbDevice++);
|
||||
HAL_Report(HALUsageReporting::kResourceType_PCVideoServer,
|
||||
camera.GetHandle());
|
||||
cs::UsbCamera camera = StartAutomaticCapture(m_impl->m_defaultUsbDevice++);
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportUsbCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture(int dev) {
|
||||
llvm::SmallString<64> buf;
|
||||
llvm::raw_svector_ostream name{buf};
|
||||
name << "USB Camera " << dev;
|
||||
|
||||
cs::UsbCamera camera{name.str(), dev};
|
||||
cs::UsbCamera camera{"USB Camera " + wpi::Twine(dev), dev};
|
||||
StartAutomaticCapture(camera);
|
||||
HAL_Report(HALUsageReporting::kResourceType_PCVideoServer,
|
||||
camera.GetHandle());
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportUsbCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture(llvm::StringRef name,
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture(const wpi::Twine& name,
|
||||
int dev) {
|
||||
cs::UsbCamera camera{name, dev};
|
||||
StartAutomaticCapture(camera);
|
||||
HAL_Report(HALUsageReporting::kResourceType_PCVideoServer,
|
||||
camera.GetHandle());
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportUsbCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture(llvm::StringRef name,
|
||||
llvm::StringRef path) {
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture(const wpi::Twine& name,
|
||||
const wpi::Twine& path) {
|
||||
cs::UsbCamera camera{name, path};
|
||||
StartAutomaticCapture(camera);
|
||||
HAL_Report(HALUsageReporting::kResourceType_PCVideoServer,
|
||||
camera.GetHandle());
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportUsbCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
#endif
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef host) {
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& host) {
|
||||
return AddAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
@@ -486,62 +500,75 @@ cs::AxisCamera CameraServer::AddAxisCamera(const std::string& host) {
|
||||
return AddAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(llvm::ArrayRef<std::string> hosts) {
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(wpi::ArrayRef<std::string> hosts) {
|
||||
return AddAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef name,
|
||||
llvm::StringRef host) {
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& name,
|
||||
const wpi::Twine& host) {
|
||||
cs::AxisCamera camera{name, host};
|
||||
StartAutomaticCapture(camera);
|
||||
HAL_Report(HALUsageReporting::kResourceType_AxisCamera, camera.GetHandle());
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportAxisCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef name,
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& name,
|
||||
const char* host) {
|
||||
cs::AxisCamera camera{name, host};
|
||||
StartAutomaticCapture(camera);
|
||||
HAL_Report(HALUsageReporting::kResourceType_AxisCamera, camera.GetHandle());
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportAxisCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef name,
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& name,
|
||||
const std::string& host) {
|
||||
cs::AxisCamera camera{name, host};
|
||||
StartAutomaticCapture(camera);
|
||||
HAL_Report(HALUsageReporting::kResourceType_AxisCamera, camera.GetHandle());
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportAxisCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef name,
|
||||
llvm::ArrayRef<std::string> hosts) {
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& name,
|
||||
wpi::ArrayRef<std::string> hosts) {
|
||||
cs::AxisCamera camera{name, hosts};
|
||||
StartAutomaticCapture(camera);
|
||||
HAL_Report(HALUsageReporting::kResourceType_AxisCamera, camera.GetHandle());
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->ReportAxisCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
|
||||
void CameraServer::StartAutomaticCapture(const cs::VideoSource& camera) {
|
||||
llvm::SmallString<64> name{"serve_"};
|
||||
name += camera.GetName();
|
||||
cs::MjpegServer CameraServer::AddSwitchedCamera(const wpi::Twine& name) {
|
||||
// create a dummy CvSource
|
||||
cs::CvSource source{name, cs::VideoMode::PixelFormat::kMJPEG, 160, 120, 30};
|
||||
cs::MjpegServer server = StartAutomaticCapture(source);
|
||||
m_impl->m_fixedSources[server.GetHandle()] = source.GetHandle();
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
cs::MjpegServer CameraServer::StartAutomaticCapture(
|
||||
const cs::VideoSource& camera) {
|
||||
AddCamera(camera);
|
||||
auto server = AddServer(name);
|
||||
auto server = AddServer(wpi::Twine("serve_") + camera.GetName());
|
||||
server.SetSource(camera);
|
||||
return server;
|
||||
}
|
||||
|
||||
cs::CvSink CameraServer::GetVideo() {
|
||||
cs::VideoSource source;
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_primarySourceName.empty()) {
|
||||
wpi_setWPIErrorWithContext(CameraServerError, "no camera available");
|
||||
auto csShared = GetCameraServerShared();
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
if (m_impl->m_primarySourceName.empty()) {
|
||||
csShared->SetCameraServerError("no camera available");
|
||||
return cs::CvSink{};
|
||||
}
|
||||
auto it = m_sources.find(m_primarySourceName);
|
||||
if (it == m_sources.end()) {
|
||||
wpi_setWPIErrorWithContext(CameraServerError, "no camera available");
|
||||
auto it = m_impl->m_sources.find(m_impl->m_primarySourceName);
|
||||
if (it == m_impl->m_sources.end()) {
|
||||
csShared->SetCameraServerError("no camera available");
|
||||
return cs::CvSink{};
|
||||
}
|
||||
source = it->second;
|
||||
@@ -550,19 +577,18 @@ cs::CvSink CameraServer::GetVideo() {
|
||||
}
|
||||
|
||||
cs::CvSink CameraServer::GetVideo(const cs::VideoSource& camera) {
|
||||
llvm::SmallString<64> name{"opencv_"};
|
||||
wpi::SmallString<64> name{"opencv_"};
|
||||
name += camera.GetName();
|
||||
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto it = m_sinks.find(name);
|
||||
if (it != m_sinks.end()) {
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
auto it = m_impl->m_sinks.find(name);
|
||||
if (it != m_impl->m_sinks.end()) {
|
||||
auto kind = it->second.GetKind();
|
||||
if (kind != cs::VideoSink::kCv) {
|
||||
llvm::SmallString<64> buf;
|
||||
llvm::raw_svector_ostream err{buf};
|
||||
err << "expected OpenCV sink, but got " << kind;
|
||||
wpi_setWPIErrorWithContext(CameraServerError, err.str());
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->SetCameraServerError("expected OpenCV sink, but got " +
|
||||
wpi::Twine(kind));
|
||||
return cs::CvSink{};
|
||||
}
|
||||
return *static_cast<cs::CvSink*>(&it->second);
|
||||
@@ -575,16 +601,16 @@ cs::CvSink CameraServer::GetVideo(const cs::VideoSource& camera) {
|
||||
return newsink;
|
||||
}
|
||||
|
||||
cs::CvSink CameraServer::GetVideo(llvm::StringRef name) {
|
||||
cs::CvSink CameraServer::GetVideo(const wpi::Twine& name) {
|
||||
wpi::SmallString<64> nameBuf;
|
||||
wpi::StringRef nameStr = name.toStringRef(nameBuf);
|
||||
cs::VideoSource source;
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto it = m_sources.find(name);
|
||||
if (it == m_sources.end()) {
|
||||
llvm::SmallString<64> buf;
|
||||
llvm::raw_svector_ostream err{buf};
|
||||
err << "could not find camera " << name;
|
||||
wpi_setWPIErrorWithContext(CameraServerError, err.str());
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
auto it = m_impl->m_sources.find(nameStr);
|
||||
if (it == m_impl->m_sources.end()) {
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->SetCameraServerError("could not find camera " + nameStr);
|
||||
return cs::CvSink{};
|
||||
}
|
||||
source = it->second;
|
||||
@@ -592,60 +618,62 @@ cs::CvSink CameraServer::GetVideo(llvm::StringRef name) {
|
||||
return GetVideo(source);
|
||||
}
|
||||
|
||||
cs::CvSource CameraServer::PutVideo(llvm::StringRef name, int width,
|
||||
cs::CvSource CameraServer::PutVideo(const wpi::Twine& name, int width,
|
||||
int height) {
|
||||
cs::CvSource source{name, cs::VideoMode::kMJPEG, width, height, 30};
|
||||
StartAutomaticCapture(source);
|
||||
return source;
|
||||
}
|
||||
|
||||
cs::MjpegServer CameraServer::AddServer(llvm::StringRef name) {
|
||||
cs::MjpegServer CameraServer::AddServer(const wpi::Twine& name) {
|
||||
int port;
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
port = m_nextPort++;
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
port = m_impl->m_nextPort++;
|
||||
}
|
||||
return AddServer(name, port);
|
||||
}
|
||||
|
||||
cs::MjpegServer CameraServer::AddServer(llvm::StringRef name, int port) {
|
||||
cs::MjpegServer CameraServer::AddServer(const wpi::Twine& name, int port) {
|
||||
cs::MjpegServer server{name, port};
|
||||
AddServer(server);
|
||||
return server;
|
||||
}
|
||||
|
||||
void CameraServer::AddServer(const cs::VideoSink& server) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_sinks.emplace_second(server.GetName(), server);
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
m_impl->m_sinks.try_emplace(server.GetName(), server);
|
||||
}
|
||||
|
||||
void CameraServer::RemoveServer(llvm::StringRef name) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_sinks.erase(name);
|
||||
void CameraServer::RemoveServer(const wpi::Twine& name) {
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
wpi::SmallString<64> nameBuf;
|
||||
m_impl->m_sinks.erase(name.toStringRef(nameBuf));
|
||||
}
|
||||
|
||||
cs::VideoSink CameraServer::GetServer() {
|
||||
llvm::SmallString<64> name;
|
||||
wpi::SmallString<64> name;
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_primarySourceName.empty()) {
|
||||
wpi_setWPIErrorWithContext(CameraServerError, "no camera available");
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
if (m_impl->m_primarySourceName.empty()) {
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->SetCameraServerError("no camera available");
|
||||
return cs::VideoSink{};
|
||||
}
|
||||
name = "serve_";
|
||||
name += m_primarySourceName;
|
||||
name += m_impl->m_primarySourceName;
|
||||
}
|
||||
return GetServer(name);
|
||||
}
|
||||
|
||||
cs::VideoSink CameraServer::GetServer(llvm::StringRef name) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto it = m_sinks.find(name);
|
||||
if (it == m_sinks.end()) {
|
||||
llvm::SmallString<64> buf;
|
||||
llvm::raw_svector_ostream err{buf};
|
||||
err << "could not find server " << name;
|
||||
wpi_setWPIErrorWithContext(CameraServerError, err.str());
|
||||
cs::VideoSink CameraServer::GetServer(const wpi::Twine& name) {
|
||||
wpi::SmallString<64> nameBuf;
|
||||
wpi::StringRef nameStr = name.toStringRef(nameBuf);
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
auto it = m_impl->m_sinks.find(nameStr);
|
||||
if (it == m_impl->m_sinks.end()) {
|
||||
auto csShared = GetCameraServerShared();
|
||||
csShared->SetCameraServerError("could not find server " + nameStr);
|
||||
return cs::VideoSink{};
|
||||
}
|
||||
return it->second;
|
||||
@@ -653,21 +681,22 @@ cs::VideoSink CameraServer::GetServer(llvm::StringRef name) {
|
||||
|
||||
void CameraServer::AddCamera(const cs::VideoSource& camera) {
|
||||
std::string name = camera.GetName();
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_primarySourceName.empty()) m_primarySourceName = name;
|
||||
m_sources.emplace_second(name, camera);
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
if (m_impl->m_primarySourceName.empty()) m_impl->m_primarySourceName = name;
|
||||
m_impl->m_sources.try_emplace(name, camera);
|
||||
}
|
||||
|
||||
void CameraServer::RemoveCamera(llvm::StringRef name) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_sources.erase(name);
|
||||
void CameraServer::RemoveCamera(const wpi::Twine& name) {
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
wpi::SmallString<64> nameBuf;
|
||||
m_impl->m_sources.erase(name.toStringRef(nameBuf));
|
||||
}
|
||||
|
||||
void CameraServer::SetSize(int size) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_primarySourceName.empty()) return;
|
||||
auto it = m_sources.find(m_primarySourceName);
|
||||
if (it == m_sources.end()) return;
|
||||
std::lock_guard<wpi::mutex> lock(m_impl->m_mutex);
|
||||
if (m_impl->m_primarySourceName.empty()) return;
|
||||
auto it = m_impl->m_sources.find(m_impl->m_primarySourceName);
|
||||
if (it == m_impl->m_sources.end()) return;
|
||||
if (size == kSize160x120)
|
||||
it->second.SetResolution(160, 120);
|
||||
else if (size == kSize320x240)
|
||||
@@ -0,0 +1,43 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "cameraserver/CameraServerShared.h"
|
||||
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
namespace {
|
||||
class DefaultCameraServerShared : public frc::CameraServerShared {
|
||||
public:
|
||||
void ReportUsbCamera(int id) override {}
|
||||
void ReportAxisCamera(int id) override {}
|
||||
void ReportVideoServer(int id) override {}
|
||||
void SetCameraServerError(const wpi::Twine& error) override {}
|
||||
void SetVisionRunnerError(const wpi::Twine& error) override {}
|
||||
void ReportDriverStationError(const wpi::Twine& error) override {}
|
||||
std::pair<std::thread::id, bool> GetRobotMainThreadId() const override {
|
||||
return std::make_pair(std::thread::id(), false);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace frc {
|
||||
|
||||
static std::unique_ptr<CameraServerShared> cameraServerShared = nullptr;
|
||||
static wpi::mutex setLock;
|
||||
|
||||
void SetCameraServerShared(std::unique_ptr<CameraServerShared> shared) {
|
||||
std::unique_lock<wpi::mutex> lock(setLock);
|
||||
cameraServerShared = std::move(shared);
|
||||
}
|
||||
CameraServerShared* GetCameraServerShared() {
|
||||
std::unique_lock<wpi::mutex> lock(setLock);
|
||||
if (!cameraServerShared) {
|
||||
cameraServerShared = std::make_unique<DefaultCameraServerShared>();
|
||||
}
|
||||
return cameraServerShared.get();
|
||||
}
|
||||
} // namespace frc
|
||||
59
cameraserver/src/main/native/cpp/vision/VisionRunner.cpp
Normal file
59
cameraserver/src/main/native/cpp/vision/VisionRunner.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "vision/VisionRunner.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <opencv2/core/mat.hpp>
|
||||
|
||||
#include "cameraserver/CameraServerShared.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
VisionRunnerBase::VisionRunnerBase(cs::VideoSource videoSource)
|
||||
: m_image(std::make_unique<cv::Mat>()),
|
||||
m_cvSink("VisionRunner CvSink"),
|
||||
m_enabled(true) {
|
||||
m_cvSink.SetSource(videoSource);
|
||||
}
|
||||
|
||||
// Located here and not in header due to cv::Mat forward declaration.
|
||||
VisionRunnerBase::~VisionRunnerBase() {}
|
||||
|
||||
void VisionRunnerBase::RunOnce() {
|
||||
auto csShared = frc::GetCameraServerShared();
|
||||
auto res = csShared->GetRobotMainThreadId();
|
||||
if (res.second && (std::this_thread::get_id() == res.first)) {
|
||||
csShared->SetVisionRunnerError(
|
||||
"VisionRunner::RunOnce() cannot be called from the main robot thread");
|
||||
return;
|
||||
}
|
||||
auto frameTime = m_cvSink.GrabFrame(*m_image);
|
||||
if (frameTime == 0) {
|
||||
auto error = m_cvSink.GetError();
|
||||
csShared->ReportDriverStationError(error);
|
||||
} else {
|
||||
DoProcess(*m_image);
|
||||
}
|
||||
}
|
||||
|
||||
void VisionRunnerBase::RunForever() {
|
||||
auto csShared = frc::GetCameraServerShared();
|
||||
auto res = csShared->GetRobotMainThreadId();
|
||||
if (res.second && (std::this_thread::get_id() == res.first)) {
|
||||
csShared->SetVisionRunnerError(
|
||||
"VisionRunner::RunForever() cannot be called from the main robot "
|
||||
"thread");
|
||||
return;
|
||||
}
|
||||
while (m_enabled) {
|
||||
RunOnce();
|
||||
}
|
||||
}
|
||||
|
||||
void VisionRunnerBase::Stop() { m_enabled = false; }
|
||||
19
cameraserver/src/main/native/include/CameraServer.h
Normal file
19
cameraserver/src/main/native/include/CameraServer.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// clang-format off
|
||||
#ifdef _MSC_VER
|
||||
#pragma message "warning: CameraServer.h is deprecated; include cameraserver/CameraServer.h instead"
|
||||
#else
|
||||
#warning "CameraServer.h is deprecated; include cameraserver/CameraServer.h instead"
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
|
||||
#include "cameraserver/CameraServer.h"
|
||||
@@ -9,18 +9,12 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/DenseMap.h>
|
||||
#include <llvm/StringMap.h>
|
||||
#include <llvm/StringRef.h>
|
||||
#include <networktables/NetworkTable.h>
|
||||
#include <support/mutex.h>
|
||||
#include <wpi/ArrayRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
#include "ErrorBase.h"
|
||||
#include "cscore.h"
|
||||
|
||||
namespace frc {
|
||||
@@ -30,7 +24,7 @@ namespace frc {
|
||||
*
|
||||
* Also publishes camera information to NetworkTables.
|
||||
*/
|
||||
class CameraServer : public ErrorBase {
|
||||
class CameraServer {
|
||||
public:
|
||||
static constexpr uint16_t kBasePort = 1181;
|
||||
static constexpr int kSize640x480 = 0;
|
||||
@@ -42,8 +36,6 @@ class CameraServer : public ErrorBase {
|
||||
*/
|
||||
static CameraServer* GetInstance();
|
||||
|
||||
#ifdef __linux__
|
||||
// USBCamera does not work on anything except Linux.
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
@@ -73,7 +65,7 @@ class CameraServer : public ErrorBase {
|
||||
* @param name The name to give the camera
|
||||
* @param dev The device number of the camera interface
|
||||
*/
|
||||
cs::UsbCamera StartAutomaticCapture(llvm::StringRef name, int dev);
|
||||
cs::UsbCamera StartAutomaticCapture(const wpi::Twine& name, int dev);
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
@@ -81,9 +73,8 @@ class CameraServer : public ErrorBase {
|
||||
* @param name The name to give the camera
|
||||
* @param path The device path (e.g. "/dev/video0") of the camera
|
||||
*/
|
||||
cs::UsbCamera StartAutomaticCapture(llvm::StringRef name,
|
||||
llvm::StringRef path);
|
||||
#endif
|
||||
cs::UsbCamera StartAutomaticCapture(const wpi::Twine& name,
|
||||
const wpi::Twine& path);
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard from
|
||||
@@ -91,7 +82,7 @@ class CameraServer : public ErrorBase {
|
||||
*
|
||||
* @param camera Camera
|
||||
*/
|
||||
void StartAutomaticCapture(const cs::VideoSource& camera);
|
||||
cs::MjpegServer StartAutomaticCapture(const cs::VideoSource& camera);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -100,7 +91,7 @@ class CameraServer : public ErrorBase {
|
||||
*
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
*/
|
||||
cs::AxisCamera AddAxisCamera(llvm::StringRef host);
|
||||
cs::AxisCamera AddAxisCamera(const wpi::Twine& host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -127,7 +118,7 @@ class CameraServer : public ErrorBase {
|
||||
*
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
cs::AxisCamera AddAxisCamera(llvm::ArrayRef<std::string> hosts);
|
||||
cs::AxisCamera AddAxisCamera(wpi::ArrayRef<std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -145,7 +136,7 @@ class CameraServer : public ErrorBase {
|
||||
* @param name The name to give the camera
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
*/
|
||||
cs::AxisCamera AddAxisCamera(llvm::StringRef name, llvm::StringRef host);
|
||||
cs::AxisCamera AddAxisCamera(const wpi::Twine& name, const wpi::Twine& host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -153,7 +144,7 @@ class CameraServer : public ErrorBase {
|
||||
* @param name The name to give the camera
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
*/
|
||||
cs::AxisCamera AddAxisCamera(llvm::StringRef name, const char* host);
|
||||
cs::AxisCamera AddAxisCamera(const wpi::Twine& name, const char* host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -161,7 +152,7 @@ class CameraServer : public ErrorBase {
|
||||
* @param name The name to give the camera
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
*/
|
||||
cs::AxisCamera AddAxisCamera(llvm::StringRef name, const std::string& host);
|
||||
cs::AxisCamera AddAxisCamera(const wpi::Twine& name, const std::string& host);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -169,8 +160,8 @@ class CameraServer : public ErrorBase {
|
||||
* @param name The name to give the camera
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
cs::AxisCamera AddAxisCamera(llvm::StringRef name,
|
||||
llvm::ArrayRef<std::string> hosts);
|
||||
cs::AxisCamera AddAxisCamera(const wpi::Twine& name,
|
||||
wpi::ArrayRef<std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -179,9 +170,17 @@ class CameraServer : public ErrorBase {
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
template <typename T>
|
||||
cs::AxisCamera AddAxisCamera(llvm::StringRef name,
|
||||
cs::AxisCamera AddAxisCamera(const wpi::Twine& name,
|
||||
std::initializer_list<T> hosts);
|
||||
|
||||
/**
|
||||
* Adds a virtual camera for switching between two streams. Unlike the
|
||||
* other addCamera methods, this returns a VideoSink rather than a
|
||||
* VideoSource. Calling SetSource() on the returned object can be used
|
||||
* to switch the actual source of the stream.
|
||||
*/
|
||||
cs::MjpegServer AddSwitchedCamera(const wpi::Twine& name);
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the primary camera feed. This allows you to
|
||||
* get images from the camera for image processing on the roboRIO.
|
||||
@@ -205,7 +204,7 @@ class CameraServer : public ErrorBase {
|
||||
*
|
||||
* @param name Camera name
|
||||
*/
|
||||
cs::CvSink GetVideo(llvm::StringRef name);
|
||||
cs::CvSink GetVideo(const wpi::Twine& name);
|
||||
|
||||
/**
|
||||
* Create a MJPEG stream with OpenCV input. This can be called to pass custom
|
||||
@@ -215,21 +214,21 @@ class CameraServer : public ErrorBase {
|
||||
* @param width Width of the image being sent
|
||||
* @param height Height of the image being sent
|
||||
*/
|
||||
cs::CvSource PutVideo(llvm::StringRef name, int width, int height);
|
||||
cs::CvSource PutVideo(const wpi::Twine& name, int width, int height);
|
||||
|
||||
/**
|
||||
* Adds a MJPEG server at the next available port.
|
||||
*
|
||||
* @param name Server name
|
||||
*/
|
||||
cs::MjpegServer AddServer(llvm::StringRef name);
|
||||
cs::MjpegServer AddServer(const wpi::Twine& name);
|
||||
|
||||
/**
|
||||
* Adds a MJPEG server.
|
||||
*
|
||||
* @param name Server name
|
||||
*/
|
||||
cs::MjpegServer AddServer(llvm::StringRef name, int port);
|
||||
cs::MjpegServer AddServer(const wpi::Twine& name, int port);
|
||||
|
||||
/**
|
||||
* Adds an already created server.
|
||||
@@ -243,7 +242,7 @@ class CameraServer : public ErrorBase {
|
||||
*
|
||||
* @param name Server name
|
||||
*/
|
||||
void RemoveServer(llvm::StringRef name);
|
||||
void RemoveServer(const wpi::Twine& name);
|
||||
|
||||
/**
|
||||
* Get server for the primary camera feed.
|
||||
@@ -258,7 +257,7 @@ class CameraServer : public ErrorBase {
|
||||
*
|
||||
* @param name Server name
|
||||
*/
|
||||
cs::VideoSink GetServer(llvm::StringRef name);
|
||||
cs::VideoSink GetServer(const wpi::Twine& name);
|
||||
|
||||
/**
|
||||
* Adds an already created camera.
|
||||
@@ -272,7 +271,7 @@ class CameraServer : public ErrorBase {
|
||||
*
|
||||
* @param name Camera name
|
||||
*/
|
||||
void RemoveCamera(llvm::StringRef name);
|
||||
void RemoveCamera(const wpi::Twine& name);
|
||||
|
||||
/**
|
||||
* Sets the size of the image to use. Use the public kSize constants to set
|
||||
@@ -287,27 +286,12 @@ class CameraServer : public ErrorBase {
|
||||
|
||||
private:
|
||||
CameraServer();
|
||||
~CameraServer();
|
||||
|
||||
std::shared_ptr<nt::NetworkTable> GetSourceTable(CS_Source source);
|
||||
std::vector<std::string> GetSinkStreamValues(CS_Sink sink);
|
||||
std::vector<std::string> GetSourceStreamValues(CS_Source source);
|
||||
void UpdateStreamValues();
|
||||
|
||||
static constexpr char const* kPublishName = "/CameraPublisher";
|
||||
|
||||
wpi::mutex m_mutex;
|
||||
std::atomic<int> m_defaultUsbDevice;
|
||||
std::string m_primarySourceName;
|
||||
llvm::StringMap<cs::VideoSource> m_sources;
|
||||
llvm::StringMap<cs::VideoSink> m_sinks;
|
||||
llvm::DenseMap<CS_Source, std::shared_ptr<nt::NetworkTable>> m_tables;
|
||||
std::shared_ptr<nt::NetworkTable> m_publishTable;
|
||||
cs::VideoListener m_videoListener;
|
||||
int m_tableListener;
|
||||
int m_nextPort;
|
||||
std::vector<std::string> m_addresses;
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace frc
|
||||
|
||||
#include "CameraServer.inc"
|
||||
#include "cameraserver/CameraServer.inc"
|
||||
@@ -20,7 +20,7 @@ inline cs::AxisCamera CameraServer::AddAxisCamera(
|
||||
|
||||
template <typename T>
|
||||
inline cs::AxisCamera CameraServer::AddAxisCamera(
|
||||
llvm::StringRef name, std::initializer_list<T> hosts) {
|
||||
const wpi::Twine& name, std::initializer_list<T> hosts) {
|
||||
std::vector<std::string> vec;
|
||||
vec.reserve(hosts.size());
|
||||
for (const auto& host : hosts) vec.emplace_back(host);
|
||||
@@ -0,0 +1,31 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
namespace frc {
|
||||
class CameraServerShared {
|
||||
public:
|
||||
virtual ~CameraServerShared() = default;
|
||||
virtual void ReportUsbCamera(int id) = 0;
|
||||
virtual void ReportAxisCamera(int id) = 0;
|
||||
virtual void ReportVideoServer(int id) = 0;
|
||||
virtual void SetCameraServerError(const wpi::Twine& error) = 0;
|
||||
virtual void SetVisionRunnerError(const wpi::Twine& error) = 0;
|
||||
virtual void ReportDriverStationError(const wpi::Twine& error) = 0;
|
||||
virtual std::pair<std::thread::id, bool> GetRobotMainThreadId() const = 0;
|
||||
};
|
||||
|
||||
void SetCameraServerShared(std::unique_ptr<CameraServerShared> shared);
|
||||
CameraServerShared* GetCameraServerShared();
|
||||
} // namespace frc
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "ErrorBase.h"
|
||||
#include "cscore.h"
|
||||
#include "vision/VisionPipeline.h"
|
||||
|
||||
@@ -20,18 +19,49 @@ namespace frc {
|
||||
/**
|
||||
* Non-template base class for VisionRunner.
|
||||
*/
|
||||
class VisionRunnerBase : public ErrorBase {
|
||||
class VisionRunnerBase {
|
||||
public:
|
||||
/**
|
||||
* Creates a new vision runner. It will take images from the {@code
|
||||
* videoSource}, and call the virtual DoProcess() method.
|
||||
*
|
||||
* @param videoSource the video source to use to supply images for the
|
||||
* pipeline
|
||||
*/
|
||||
explicit VisionRunnerBase(cs::VideoSource videoSource);
|
||||
~VisionRunnerBase() override;
|
||||
|
||||
~VisionRunnerBase();
|
||||
|
||||
VisionRunnerBase(const VisionRunnerBase&) = delete;
|
||||
VisionRunnerBase& operator=(const VisionRunnerBase&) = delete;
|
||||
|
||||
/**
|
||||
* Runs the pipeline one time, giving it the next image from the video source
|
||||
* specified in the constructor. This will block until the source either has
|
||||
* an image or throws an error. If the source successfully supplied a frame,
|
||||
* the pipeline's image input will be set, the pipeline will run, and the
|
||||
* listener specified in the constructor will be called to notify it that the
|
||||
* pipeline ran. This must be run in a dedicated thread, and cannot be used in
|
||||
* the main robot thread because it will freeze the robot program.
|
||||
*
|
||||
* <p>This method is exposed to allow teams to add additional functionality or
|
||||
* have their own ways to run the pipeline. Most teams, however, should just
|
||||
* use {@link #runForever} in its own thread using a std::thread.</p>
|
||||
*/
|
||||
void RunOnce();
|
||||
|
||||
/**
|
||||
* A convenience method that calls {@link #runOnce()} in an infinite loop.
|
||||
* This must be run in a dedicated thread, and cannot be used in the main
|
||||
* robot thread because it will freeze the robot program.
|
||||
*
|
||||
* <strong>Do not call this method directly from the main thread.</strong>
|
||||
*/
|
||||
void RunForever();
|
||||
|
||||
/**
|
||||
* Stop a RunForever() loop.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
protected:
|
||||
8
cameraserver/src/test/native/cpp/main.cpp
Normal file
8
cameraserver/src/test/native/cpp/main.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
int main() { return 0; }
|
||||
25
cmake/modules/GenResources.cmake
Normal file
25
cmake/modules/GenResources.cmake
Normal file
@@ -0,0 +1,25 @@
|
||||
MACRO(GENERATE_RESOURCES inputDir outputDir prefix namespace outputFiles)
|
||||
FILE(GLOB inputFiles ${inputDir}/*)
|
||||
SET(${outputFiles})
|
||||
FOREACH(input ${inputFiles})
|
||||
GET_FILENAME_COMPONENT(inputBase ${input} NAME)
|
||||
IF("${inputBase}" MATCHES "^\\.")
|
||||
CONTINUE()
|
||||
ENDIF()
|
||||
SET(output "${outputDir}/${inputBase}.cpp")
|
||||
LIST(APPEND ${outputFiles} "${output}")
|
||||
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ${output}
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-Dinput=${input}"
|
||||
"-Doutput=${output}"
|
||||
"-Dprefix=${prefix}"
|
||||
"-Dnamespace=${namespace}"
|
||||
-P "${CMAKE_SOURCE_DIR}/cmake/scripts/GenResource.cmake"
|
||||
MAIN_DEPENDENCY ${input}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/cmake/scripts/GenResource.cmake
|
||||
VERBATIM
|
||||
)
|
||||
ENDFOREACH()
|
||||
ENDMACRO()
|
||||
17
cmake/modules/SubDirList.cmake
Normal file
17
cmake/modules/SubDirList.cmake
Normal file
@@ -0,0 +1,17 @@
|
||||
MACRO(SUBDIR_LIST result curdir)
|
||||
FILE(GLOB children RELATIVE ${curdir} ${curdir}/*)
|
||||
SET(dirlist "")
|
||||
FOREACH(child ${children})
|
||||
IF(IS_DIRECTORY ${curdir}/${child})
|
||||
LIST(APPEND dirlist ${child})
|
||||
ENDIF()
|
||||
ENDFOREACH()
|
||||
SET(${result} ${dirlist})
|
||||
ENDMACRO()
|
||||
|
||||
MACRO(ADD_ALL_SUBDIRECTORIES curdir)
|
||||
SUBDIR_LIST (_SUBPROJECTS ${curdir})
|
||||
FOREACH (dir ${_SUBPROJECTS})
|
||||
ADD_SUBDIRECTORY (${dir})
|
||||
ENDFOREACH ()
|
||||
ENDMACRO()
|
||||
23
cmake/scripts/GenResource.cmake
Normal file
23
cmake/scripts/GenResource.cmake
Normal file
@@ -0,0 +1,23 @@
|
||||
# Parameters: input output prefix namespace
|
||||
FILE(READ ${input} fileHex HEX)
|
||||
STRING(LENGTH "${fileHex}" fileHexSize)
|
||||
MATH(EXPR fileSize "${fileHexSize} / 2")
|
||||
|
||||
GET_FILENAME_COMPONENT(inputBase ${input} NAME)
|
||||
STRING(REGEX REPLACE "[^a-zA-Z0-9]" "_" funcName "${inputBase}")
|
||||
SET(funcName "GetResource_${funcName}")
|
||||
|
||||
FILE(WRITE "${output}" "#include <stddef.h>\n#include <wpi/StringRef.h>\nextern \"C\" {\nstatic const unsigned char contents[] = {")
|
||||
|
||||
STRING(REGEX MATCHALL ".." outputData "${fileHex}")
|
||||
STRING(REGEX REPLACE ";" ", 0x" outputData "${outputData}")
|
||||
FILE(APPEND "${output}" " 0x${outputData} };\n")
|
||||
FILE(APPEND "${output}" "const unsigned char* ${prefix}${funcName}(size_t* len) {\n *len = ${fileSize};\n return contents;\n}\n}\n")
|
||||
|
||||
IF(NOT namespace STREQUAL "")
|
||||
FILE(APPEND "${output}" "namespace ${namespace} {\n")
|
||||
ENDIF()
|
||||
FILE(APPEND "${output}" "wpi::StringRef ${funcName}() {\n return wpi::StringRef(reinterpret_cast<const char*>(contents), ${fileSize});\n}\n")
|
||||
IF(NOT namespace STREQUAL "")
|
||||
FILE(APPEND "${output}" "}\n")
|
||||
ENDIF()
|
||||
235
config.gradle
235
config.gradle
@@ -1,235 +0,0 @@
|
||||
import edu.wpi.first.nativeutils.*
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
def windowsCompilerArgs = ['/EHsc', '/DNOMINMAX', '/Zi', '/FS', '/Zc:inline', '/MT']
|
||||
def windowsReleaseCompilerArgs = ['/O2']
|
||||
def windowsLinkerArgs = [ '/DEBUG:FULL' ]
|
||||
def windowsReleaseLinkerArgs = [ '/OPT:REF', '/OPT:ICF' ]
|
||||
|
||||
def linuxCompilerArgs = ['-std=c++11', '-Wformat=2', '-Wall', '-Wextra', '-Werror', '-pedantic', '-Wno-psabi', '-g',
|
||||
'-Wno-unused-parameter', '-fPIC', '-rdynamic', '-Wno-error=deprecated-declarations', '-pthread']
|
||||
def linuxLinkerArgs = ['-rdynamic', '-pthread']
|
||||
def linuxReleaseCompilerArgs = ['-Og']
|
||||
def linuxDebugCompilerArgs = ['-O0']
|
||||
def linux32BitArg = '-m32'
|
||||
|
||||
def macCompilerArgs = ['-std=c++11', '-Wall', '-Wextra', '-Werror', '-pedantic-errors', '-fPIC', '-g',
|
||||
'-Wno-unused-parameter', '-Wno-missing-field-initializers', '-Wno-unused-private-field',
|
||||
'-Wno-unused-const-variable', '-pthread']
|
||||
def macReleaseCompilerArgs = ['-O2']
|
||||
def macDebugCompilerArgs = ['-O0']
|
||||
def mac32BitArg = '-m32'
|
||||
|
||||
def buildAll = project.hasProperty('buildAll')
|
||||
|
||||
def windows64PlatformDetect = {
|
||||
def arch = System.getProperty("os.arch")
|
||||
def isWin = OperatingSystem.current().isWindows()
|
||||
if (buildAll) {
|
||||
return isWin
|
||||
} else {
|
||||
return isWin && arch == 'amd64'
|
||||
}
|
||||
}
|
||||
|
||||
def windows32PlatformDetect = {
|
||||
def arch = System.getProperty("os.arch")
|
||||
def isWin = OperatingSystem.current().isWindows()
|
||||
if (buildAll) {
|
||||
return isWin
|
||||
} else {
|
||||
return isWin && arch == 'x86'
|
||||
}
|
||||
}
|
||||
|
||||
def linux32IntelPlatformDetect = {
|
||||
def arch = System.getProperty("os.arch")
|
||||
def isLinux = OperatingSystem.current().isLinux()
|
||||
def isIntel = (arch == 'amd64' || arch == 'i386')
|
||||
if (buildAll) {
|
||||
return isLinux && isIntel
|
||||
} else {
|
||||
return isLinux && arch == 'i386'
|
||||
}
|
||||
}
|
||||
|
||||
def linux64IntelPlatformDetect = {
|
||||
def arch = System.getProperty("os.arch")
|
||||
def isLinux = OperatingSystem.current().isLinux()
|
||||
def isIntel = (arch == 'amd64' || arch == 'i386')
|
||||
if (buildAll) {
|
||||
return isLinux && isIntel
|
||||
} else {
|
||||
return isLinux && arch == 'amd64'
|
||||
}
|
||||
}
|
||||
|
||||
def linuxArmPlatformDetect = {
|
||||
def arch = System.getProperty("os.arch")
|
||||
def isIntel = (arch == 'amd64' || arch == 'i386')
|
||||
return OperatingSystem.current().isLinux() && !isIntel
|
||||
}
|
||||
|
||||
def mac64PlatformDetect = {
|
||||
def arch = System.getProperty("os.arch")
|
||||
def isMac = OperatingSystem.current().isMacOsX()
|
||||
if (buildAll) {
|
||||
return isMac
|
||||
} else {
|
||||
return isMac && arch == 'x86_64'
|
||||
}
|
||||
}
|
||||
|
||||
def mac32PlatformDetect = {
|
||||
def arch = System.getProperty("os.arch")
|
||||
def isMac = OperatingSystem.current().isMacOsX()
|
||||
if (buildAll) {
|
||||
return isMac
|
||||
} else {
|
||||
return isMac && arch == 'x86'
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.hasProperty('skipAthena')) {
|
||||
model {
|
||||
buildConfigs {
|
||||
roboRio(CrossBuildConfig) {
|
||||
architecture = 'athena'
|
||||
operatingSystem = 'linux'
|
||||
toolChainPrefix = 'arm-frc-linux-gnueabi-'
|
||||
compilerArgs = linuxCompilerArgs
|
||||
linkerArgs = linuxLinkerArgs
|
||||
debugCompilerArgs = linuxDebugCompilerArgs
|
||||
releaseCompilerArgs = linuxReleaseCompilerArgs
|
||||
releaseStripBinaries = true
|
||||
compilerFamily = 'Gcc'
|
||||
exclude << 'halSim'
|
||||
exclude << 'halSimStaticDeps'
|
||||
exclude << 'halSimTestingBase'
|
||||
exclude << 'wpilibcTestingBase'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.hasProperty('onlyAthena')) {
|
||||
model {
|
||||
buildConfigs {
|
||||
winX86(BuildConfig) {
|
||||
architecture = 'x86'
|
||||
operatingSystem = 'windows'
|
||||
compilerArgs = windowsCompilerArgs
|
||||
linkerArgs = windowsLinkerArgs
|
||||
releaseCompilerArgs = windowsReleaseCompilerArgs
|
||||
releaseLinkerArgs = windowsReleaseLinkerArgs
|
||||
compilerFamily = 'VisualCpp'
|
||||
detectPlatform = windows32PlatformDetect
|
||||
exclude << 'halAthena'
|
||||
}
|
||||
winX64(BuildConfig) {
|
||||
architecture = 'x86-64'
|
||||
operatingSystem = 'windows'
|
||||
compilerArgs = windowsCompilerArgs
|
||||
linkerArgs = windowsLinkerArgs
|
||||
releaseCompilerArgs = windowsReleaseCompilerArgs
|
||||
releaseLinkerArgs = windowsReleaseLinkerArgs
|
||||
compilerFamily = 'VisualCpp'
|
||||
detectPlatform = windows64PlatformDetect
|
||||
exclude << 'halAthena'
|
||||
}
|
||||
/* Disable 32 bit linux until we can figure out jenkins
|
||||
linuxX86(BuildConfig) {
|
||||
architecture = 'x86'
|
||||
operatingSystem = 'linux'
|
||||
compilerArgs = linuxCompilerArgs
|
||||
compilerArgs << linux32BitArg
|
||||
linkerArgs = linuxLinkerArgs
|
||||
linkerArgs << linux32BitArg
|
||||
debugCompilerArgs = linuxDebugCompilerArgs
|
||||
releaseCompilerArgs = linuxReleaseCompilerArgs
|
||||
releaseStripBinaries = true
|
||||
compilerFamily = 'Gcc'
|
||||
detectPlatform = linux32IntelPlatformDetect
|
||||
exclude << 'halAthena'
|
||||
}
|
||||
*/
|
||||
linuxX64(BuildConfig) {
|
||||
architecture = 'x86-64'
|
||||
operatingSystem = 'linux'
|
||||
compilerArgs = linuxCompilerArgs
|
||||
linkerArgs = linuxLinkerArgs
|
||||
debugCompilerArgs = linuxDebugCompilerArgs
|
||||
releaseCompilerArgs = linuxReleaseCompilerArgs
|
||||
releaseStripBinaries = true
|
||||
compilerFamily = 'Gcc'
|
||||
detectPlatform = linux64IntelPlatformDetect
|
||||
exclude << 'halAthena'
|
||||
}
|
||||
/* 32 bit Mac OS X not supported by OpenCV.
|
||||
* If support is ever added, will add this back in
|
||||
macX86(BuildConfig) {
|
||||
architecture = 'x86'
|
||||
operatingSystem = 'osx'
|
||||
compilerArgs = macCompilerArgs
|
||||
compilerArgs << mac32BitArg
|
||||
linkerArgs << mac32BitArg
|
||||
debugCompilerArgs = macDebugCompilerArgs
|
||||
releaseCompilerArgs = macReleaseCompilerArgs
|
||||
releaseStripBinaries = true
|
||||
compilerFamily = 'Clang'
|
||||
detectPlatform = mac32PlatformDetect
|
||||
exclude << 'halAthena'
|
||||
}
|
||||
*/
|
||||
macX64(BuildConfig) {
|
||||
architecture = 'x86-64'
|
||||
operatingSystem = 'osx'
|
||||
compilerArgs = macCompilerArgs
|
||||
debugCompilerArgs = macDebugCompilerArgs
|
||||
releaseCompilerArgs = macReleaseCompilerArgs
|
||||
releaseStripBinaries = true
|
||||
compilerFamily = 'Clang'
|
||||
detectPlatform = mac64PlatformDetect
|
||||
exclude << 'halAthena'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.hasProperty('linuxCross')) {
|
||||
model {
|
||||
buildConfigs {
|
||||
linuxArm(CrossBuildConfig) {
|
||||
architecture = 'nativearm'
|
||||
operatingSystem = 'linux'
|
||||
toolChainPrefix = 'PLEASE_PROVIDE_A_COMPILER_NAME'
|
||||
compilerArgs = linuxCompilerArgs
|
||||
linkerArgs = linuxLinkerArgs
|
||||
debugCompilerArgs = linuxDebugCompilerArgs
|
||||
releaseCompilerArgs = linuxReleaseCompilerArgs
|
||||
releaseStripBinaries = true
|
||||
skipByDefault = true
|
||||
compilerFamily = 'Gcc'
|
||||
exclude << 'gmock'
|
||||
exclude << 'halAthena'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
model {
|
||||
buildConfigs {
|
||||
linuxArm(BuildConfig) {
|
||||
architecture = 'nativearm'
|
||||
operatingSystem = 'linux'
|
||||
compilerArgs = linuxCompilerArgs
|
||||
linkerArgs = linuxLinkerArgs
|
||||
debugCompilerArgs = linuxDebugCompilerArgs
|
||||
releaseCompilerArgs = linuxReleaseCompilerArgs
|
||||
releaseStripBinaries = true
|
||||
compilerFamily = 'Gcc'
|
||||
detectPlatform = linuxArmPlatformDetect
|
||||
exclude << 'halAthena'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
cscore/.styleguide
Normal file
37
cscore/.styleguide
Normal file
@@ -0,0 +1,37 @@
|
||||
cHeaderFileInclude {
|
||||
_c\.h$
|
||||
}
|
||||
|
||||
cppHeaderFileInclude {
|
||||
(?<!_c)\.h$
|
||||
\.hpp$
|
||||
\.inc$
|
||||
}
|
||||
|
||||
cppSrcFileInclude {
|
||||
\.cpp$
|
||||
}
|
||||
|
||||
licenseUpdateExclude {
|
||||
src/main/native/cpp/default_init_allocator\.h$
|
||||
}
|
||||
|
||||
includeGuardRoots {
|
||||
cscore/src/main/native/cpp/
|
||||
cscore/src/main/native/include/
|
||||
cscore/src/main/native/linux/
|
||||
cscore/src/main/native/osx/
|
||||
cscore/src/main/native/windows/
|
||||
cscore/src/main/test/native/cpp/
|
||||
}
|
||||
|
||||
repoRootNameOverride {
|
||||
cscore
|
||||
}
|
||||
|
||||
includeOtherLibs {
|
||||
^opencv2/
|
||||
^support/
|
||||
^tcpsockets/
|
||||
^wpi/
|
||||
}
|
||||
122
cscore/CMakeLists.txt
Normal file
122
cscore/CMakeLists.txt
Normal file
@@ -0,0 +1,122 @@
|
||||
project(cscore)
|
||||
|
||||
include(SubDirList)
|
||||
|
||||
find_package( OpenCV REQUIRED )
|
||||
|
||||
file(GLOB
|
||||
cscore_native_src src/main/native/cpp/*.cpp)
|
||||
file(GLOB cscore_linux_src src/main/native/linux/*.cpp)
|
||||
file(GLOB cscore_osx_src src/main/native/osx/*.cpp)
|
||||
file(GLOB cscore_windows_src src/main/native/windows/*.cpp)
|
||||
|
||||
add_library(cscore ${cscore_native_src})
|
||||
set_target_properties(cscore PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
if(NOT MSVC)
|
||||
if (APPLE)
|
||||
target_sources(cscore PRIVATE ${cscore_osx_src})
|
||||
else()
|
||||
target_sources(cscore PRIVATE ${cscore_linux_src})
|
||||
endif()
|
||||
else()
|
||||
target_sources(cscore PRIVATE ${cscore_windows_src})
|
||||
target_compile_options(cscore PUBLIC -DNOMINMAX)
|
||||
target_compile_options(cscore PRIVATE -D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
target_include_directories(cscore PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/cscore>)
|
||||
target_include_directories(cscore PRIVATE src/main/native/cpp)
|
||||
target_link_libraries(cscore PUBLIC wpiutil ${OpenCV_LIBS})
|
||||
|
||||
set_property(TARGET cscore PROPERTY FOLDER "libraries")
|
||||
|
||||
install(TARGETS cscore EXPORT cscore DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cscore")
|
||||
|
||||
if (MSVC)
|
||||
set (cscore_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (cscore_config_dir share/cscore)
|
||||
endif()
|
||||
|
||||
install(FILES cscore-config.cmake DESTINATION ${cscore_config_dir})
|
||||
install(EXPORT cscore DESTINATION ${cscore_config_dir})
|
||||
|
||||
SUBDIR_LIST(cscore_examples "${CMAKE_CURRENT_SOURCE_DIR}/examples")
|
||||
foreach(example ${cscore_examples})
|
||||
file(GLOB cscore_example_src examples/${example}/*.cpp)
|
||||
if(cscore_example_src)
|
||||
add_executable(cscore_${example} ${cscore_example_src})
|
||||
target_link_libraries(cscore_${example} cscore)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Java bindings
|
||||
if (NOT WITHOUT_JAVA)
|
||||
find_package(Java REQUIRED)
|
||||
find_package(JNI REQUIRED)
|
||||
include(UseJava)
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-Xlint:unchecked")
|
||||
|
||||
#find java files, copy them locally
|
||||
|
||||
set(OPENCV_JAVA_INSTALL_DIR ${OpenCV_INSTALL_PATH}/share/OpenCV/java/)
|
||||
|
||||
find_file(OPENCV_JAR_FILE NAMES opencv-${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.jar PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin NO_DEFAULT_PATH)
|
||||
find_file(OPENCV_JNI_FILE NAMES libopencv_java${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.so
|
||||
libopencv_java${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.dylib
|
||||
opencv_java${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.dll
|
||||
PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin ${OpenCV_INSTALL_PATH}/bin/Release ${OpenCV_INSTALL_PATH}/bin/Debug ${OpenCV_INSTALL_PATH}/lib NO_DEFAULT_PATH)
|
||||
|
||||
file(GLOB
|
||||
cscore_jni_src src/main/native/cpp/jni/CameraServerJNI.cpp)
|
||||
|
||||
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
|
||||
set(CMAKE_JNI_TARGET true)
|
||||
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-h" "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
|
||||
add_jar(cscore_jar ${JAVA_SOURCES} INCLUDE_JARS wpiutil_jar ${OPENCV_JAR_FILE} OUTPUT_NAME cscore)
|
||||
else()
|
||||
add_jar(cscore_jar ${JAVA_SOURCES} INCLUDE_JARS wpiutil_jar ${OPENCV_JAR_FILE} OUTPUT_NAME cscore GENERATE_NATIVE_HEADERS cscore_jni_headers)
|
||||
endif()
|
||||
|
||||
get_property(CSCORE_JAR_FILE TARGET cscore_jar PROPERTY JAR_FILE)
|
||||
install(FILES ${CSCORE_JAR_FILE} DESTINATION "${java_lib_dest}")
|
||||
install(FILES ${OPENCV_JAR_FILE} DESTINATION "${java_lib_dest}")
|
||||
|
||||
if (MSVC)
|
||||
install(FILES ${OPENCV_JNI_FILE} DESTINATION "${jni_lib_dest}")
|
||||
|
||||
foreach(cvFile ${OpenCV_LIBS})
|
||||
find_file(${cvFile}Loc NAMES ${cvFile}${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.dll
|
||||
PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin ${OpenCV_INSTALL_PATH}/bin/Release ${OpenCV_INSTALL_PATH}/bin/Debug ${OpenCV_INSTALL_PATH}/lib NO_DEFAULT_PATH)
|
||||
install(FILES ${${cvFile}Loc} DESTINATION "${jni_lib_dest}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
set_property(TARGET cscore_jar PROPERTY FOLDER "java")
|
||||
|
||||
add_library(cscorejni ${cscore_jni_src})
|
||||
target_link_libraries(cscorejni PUBLIC cscore wpiutil ${OpenCV_LIBS})
|
||||
|
||||
set_property(TARGET cscorejni PROPERTY FOLDER "libraries")
|
||||
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
|
||||
target_include_directories(cscorejni PRIVATE ${JNI_INCLUDE_DIRS})
|
||||
target_include_directories(cscorejni PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
|
||||
else()
|
||||
target_link_libraries(cscorejni PRIVATE cscore_jni_headers)
|
||||
endif()
|
||||
add_dependencies(cscorejni cscore_jar)
|
||||
|
||||
if (MSVC)
|
||||
install(TARGETS cscorejni RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
install(TARGETS cscorejni EXPORT cscorejni DESTINATION "${main_lib_dest}")
|
||||
|
||||
endif()
|
||||
147
cscore/build.gradle
Normal file
147
cscore/build.gradle
Normal file
@@ -0,0 +1,147 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
ext {
|
||||
nativeName = 'cscore'
|
||||
devMain = 'edu.wpi.cscore.DevMain'
|
||||
}
|
||||
|
||||
if (OperatingSystem.current().isMacOsX()) {
|
||||
apply plugin: 'objective-cpp'
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/jni/setupBuild.gradle"
|
||||
|
||||
ext {
|
||||
sharedCvConfigs = [cscore : [],
|
||||
cscoreBase: [],
|
||||
cscoreDev : [],
|
||||
cscoreTest: []]
|
||||
staticCvConfigs = [cscoreJNI: []]
|
||||
useJava = true
|
||||
useCpp = true
|
||||
splitSetup = {
|
||||
if (it.targetPlatform.operatingSystem.name == 'osx') {
|
||||
it.sources {
|
||||
macObjCpp(ObjectiveCppSourceSet) {
|
||||
source {
|
||||
srcDirs = ['src/main/native/objcpp']
|
||||
include '**/*.mm'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
cscoreMacCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/osx'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (it.targetPlatform.operatingSystem.name == 'linux') {
|
||||
it.sources {
|
||||
cscoreLinuxCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/linux'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (it.targetPlatform.operatingSystem.name == 'windows') {
|
||||
it.sources {
|
||||
cscoreWindowsCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/windows'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def examplesMap = [:];
|
||||
|
||||
File examplesTree = file("$projectDir/examples")
|
||||
examplesTree.list(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File current, String name) {
|
||||
return new File(current, name).isDirectory();
|
||||
}
|
||||
}).each {
|
||||
sharedCvConfigs.put(it, [])
|
||||
examplesMap.put(it, [])
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
model {
|
||||
// Exports config is a utility to enable exporting all symbols in a C++ library on windows to a DLL.
|
||||
// This removes the need for DllExport on a library. However, the gradle C++ builder has a bug
|
||||
// where some extra symbols are added that cannot be resolved at link time. This configuration
|
||||
// lets you specify specific symbols to exlude from exporting.
|
||||
exportsConfigs {
|
||||
cscore(ExportsConfig) {
|
||||
x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVbad_cast',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure', '==']
|
||||
x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVbad_cast',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure', '==']
|
||||
}
|
||||
cscoreJNI(ExportsConfig) {
|
||||
x86SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('CS_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
x64SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('CS_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
}
|
||||
}
|
||||
components {
|
||||
examplesMap.each { key, value ->
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
}
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs 'examples/' + "${key}"
|
||||
include '**/*.cpp'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
cscore/cscore-config.cmake
Normal file
6
cscore/cscore-config.cmake
Normal file
@@ -0,0 +1,6 @@
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(wpiutil)
|
||||
find_dependency(OpenCV)
|
||||
|
||||
get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
include(${SELF_DIR}/cscore.cmake)
|
||||
84
cscore/examples/enum_usb/enum_usb.cpp
Normal file
84
cscore/examples/enum_usb/enum_usb.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "cscore.h"
|
||||
|
||||
int main() {
|
||||
CS_Status status = 0;
|
||||
wpi::SmallString<64> buf;
|
||||
|
||||
for (const auto& caminfo : cs::EnumerateUsbCameras(&status)) {
|
||||
wpi::outs() << caminfo.dev << ": " << caminfo.path << " (" << caminfo.name
|
||||
<< ")\n";
|
||||
if (!caminfo.otherPaths.empty()) {
|
||||
wpi::outs() << "Other device paths:\n";
|
||||
for (auto&& path : caminfo.otherPaths)
|
||||
wpi::outs() << " " << path << '\n';
|
||||
}
|
||||
|
||||
cs::UsbCamera camera{"usbcam", caminfo.dev};
|
||||
|
||||
wpi::outs() << "Properties:\n";
|
||||
for (const auto& prop : camera.EnumerateProperties()) {
|
||||
wpi::outs() << " " << prop.GetName();
|
||||
switch (prop.GetKind()) {
|
||||
case cs::VideoProperty::kBoolean:
|
||||
wpi::outs() << " (bool): "
|
||||
<< "value=" << prop.Get()
|
||||
<< " default=" << prop.GetDefault();
|
||||
break;
|
||||
case cs::VideoProperty::kInteger:
|
||||
wpi::outs() << " (int): "
|
||||
<< "value=" << prop.Get() << " min=" << prop.GetMin()
|
||||
<< " max=" << prop.GetMax() << " step=" << prop.GetStep()
|
||||
<< " default=" << prop.GetDefault();
|
||||
break;
|
||||
case cs::VideoProperty::kString:
|
||||
wpi::outs() << " (string): " << prop.GetString(buf);
|
||||
break;
|
||||
case cs::VideoProperty::kEnum: {
|
||||
wpi::outs() << " (enum): "
|
||||
<< "value=" << prop.Get();
|
||||
auto choices = prop.GetChoices();
|
||||
for (size_t i = 0; i < choices.size(); ++i) {
|
||||
if (choices[i].empty()) continue;
|
||||
wpi::outs() << "\n " << i << ": " << choices[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
wpi::outs() << '\n';
|
||||
}
|
||||
|
||||
wpi::outs() << "Video Modes:\n";
|
||||
for (const auto& mode : camera.EnumerateVideoModes()) {
|
||||
wpi::outs() << " PixelFormat:";
|
||||
switch (mode.pixelFormat) {
|
||||
case cs::VideoMode::kMJPEG:
|
||||
wpi::outs() << "MJPEG";
|
||||
break;
|
||||
case cs::VideoMode::kYUYV:
|
||||
wpi::outs() << "YUYV";
|
||||
break;
|
||||
case cs::VideoMode::kRGB565:
|
||||
wpi::outs() << "RGB565";
|
||||
break;
|
||||
default:
|
||||
wpi::outs() << "Unknown";
|
||||
break;
|
||||
}
|
||||
wpi::outs() << " Width:" << mode.width;
|
||||
wpi::outs() << " Height:" << mode.height;
|
||||
wpi::outs() << " FPS:" << mode.fps << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
37
cscore/examples/httpcvstream/httpcvstream.cpp
Normal file
37
cscore/examples/httpcvstream/httpcvstream.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
|
||||
#include "cscore.h"
|
||||
|
||||
int main() {
|
||||
cs::HttpCamera camera{"httpcam", "http://localhost:8081/?action=stream"};
|
||||
camera.SetVideoMode(cs::VideoMode::kMJPEG, 320, 240, 30);
|
||||
cs::CvSink cvsink{"cvsink"};
|
||||
cvsink.SetSource(camera);
|
||||
cs::CvSource cvsource{"cvsource", cs::VideoMode::kMJPEG, 320, 240, 30};
|
||||
cs::MjpegServer cvMjpegServer{"cvhttpserver", 8083};
|
||||
cvMjpegServer.SetSource(cvsource);
|
||||
|
||||
cv::Mat test;
|
||||
cv::Mat flip;
|
||||
for (;;) {
|
||||
uint64_t time = cvsink.GrabFrame(test);
|
||||
if (time == 0) {
|
||||
std::cout << "error: " << cvsink.GetError() << std::endl;
|
||||
continue;
|
||||
}
|
||||
std::cout << "got frame at time " << time << " size " << test.size()
|
||||
<< std::endl;
|
||||
cv::flip(test, flip, 0);
|
||||
cvsource.PutFrame(flip);
|
||||
}
|
||||
}
|
||||
104
cscore/examples/settings/settings.cpp
Normal file
104
cscore/examples/settings/settings.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "cscore.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
wpi::errs() << "Usage: settings camera [prop val] ... -- [prop val]...\n";
|
||||
wpi::errs() << " Example: settings 1 brightness 30 raw_contrast 10\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
int id;
|
||||
if (wpi::StringRef{argv[1]}.getAsInteger(10, id)) {
|
||||
wpi::errs() << "Expected number for camera\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
cs::UsbCamera camera{"usbcam", id};
|
||||
|
||||
// Set prior to connect
|
||||
int arg = 2;
|
||||
wpi::StringRef propName;
|
||||
for (; arg < argc && wpi::StringRef{argv[arg]} != "--"; ++arg) {
|
||||
if (propName.empty()) {
|
||||
propName = argv[arg];
|
||||
} else {
|
||||
wpi::StringRef propVal{argv[arg]};
|
||||
int intVal;
|
||||
if (propVal.getAsInteger(10, intVal))
|
||||
camera.GetProperty(propName).SetString(propVal);
|
||||
else
|
||||
camera.GetProperty(propName).Set(intVal);
|
||||
propName = wpi::StringRef{};
|
||||
}
|
||||
}
|
||||
if (arg < argc && wpi::StringRef{argv[arg]} == "--") ++arg;
|
||||
|
||||
// Wait to connect
|
||||
while (!camera.IsConnected())
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
// Set rest
|
||||
propName = wpi::StringRef{};
|
||||
for (; arg < argc; ++arg) {
|
||||
if (propName.empty()) {
|
||||
propName = argv[arg];
|
||||
} else {
|
||||
wpi::StringRef propVal{argv[arg]};
|
||||
int intVal;
|
||||
if (propVal.getAsInteger(10, intVal))
|
||||
camera.GetProperty(propName).SetString(propVal);
|
||||
else
|
||||
camera.GetProperty(propName).Set(intVal);
|
||||
propName = wpi::StringRef{};
|
||||
}
|
||||
}
|
||||
|
||||
// Print settings
|
||||
wpi::SmallString<64> buf;
|
||||
wpi::outs() << "Properties:\n";
|
||||
for (const auto& prop : camera.EnumerateProperties()) {
|
||||
wpi::outs() << " " << prop.GetName();
|
||||
switch (prop.GetKind()) {
|
||||
case cs::VideoProperty::kBoolean:
|
||||
wpi::outs() << " (bool): "
|
||||
<< "value=" << prop.Get()
|
||||
<< " default=" << prop.GetDefault();
|
||||
break;
|
||||
case cs::VideoProperty::kInteger:
|
||||
wpi::outs() << " (int): "
|
||||
<< "value=" << prop.Get() << " min=" << prop.GetMin()
|
||||
<< " max=" << prop.GetMax() << " step=" << prop.GetStep()
|
||||
<< " default=" << prop.GetDefault();
|
||||
break;
|
||||
case cs::VideoProperty::kString:
|
||||
wpi::outs() << " (string): " << prop.GetString(buf);
|
||||
break;
|
||||
case cs::VideoProperty::kEnum: {
|
||||
wpi::outs() << " (enum): "
|
||||
<< "value=" << prop.Get();
|
||||
auto choices = prop.GetChoices();
|
||||
for (size_t i = 0; i < choices.size(); ++i) {
|
||||
if (choices[i].empty()) continue;
|
||||
wpi::outs() << "\n " << i << ": " << choices[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
wpi::outs() << '\n';
|
||||
}
|
||||
}
|
||||
39
cscore/examples/usbcvstream/usbcvstream.cpp
Normal file
39
cscore/examples/usbcvstream/usbcvstream.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
|
||||
#include "cscore.h"
|
||||
|
||||
int main() {
|
||||
cs::UsbCamera camera{"usbcam", 0};
|
||||
camera.SetVideoMode(cs::VideoMode::kMJPEG, 320, 240, 30);
|
||||
cs::MjpegServer mjpegServer{"httpserver", 8081};
|
||||
mjpegServer.SetSource(camera);
|
||||
cs::CvSink cvsink{"cvsink"};
|
||||
cvsink.SetSource(camera);
|
||||
cs::CvSource cvsource{"cvsource", cs::VideoMode::kMJPEG, 320, 240, 30};
|
||||
cs::MjpegServer cvMjpegServer{"cvhttpserver", 8082};
|
||||
cvMjpegServer.SetSource(cvsource);
|
||||
|
||||
cv::Mat test;
|
||||
cv::Mat flip;
|
||||
for (;;) {
|
||||
uint64_t time = cvsink.GrabFrame(test);
|
||||
if (time == 0) {
|
||||
std::cout << "error: " << cvsink.GetError() << std::endl;
|
||||
continue;
|
||||
}
|
||||
std::cout << "got frame at time " << time << " size " << test.size()
|
||||
<< std::endl;
|
||||
cv::flip(test, flip, 0);
|
||||
cvsource.PutFrame(flip);
|
||||
}
|
||||
}
|
||||
35
cscore/examples/usbstream/usbstream.cpp
Normal file
35
cscore/examples/usbstream/usbstream.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "cscore.h"
|
||||
|
||||
int main() {
|
||||
wpi::outs() << "hostname: " << cs::GetHostname() << '\n';
|
||||
wpi::outs() << "IPv4 network addresses:\n";
|
||||
for (const auto& addr : cs::GetNetworkInterfaces())
|
||||
wpi::outs() << " " << addr << '\n';
|
||||
cs::UsbCamera camera{"usbcam", 0};
|
||||
camera.SetVideoMode(cs::VideoMode::kMJPEG, 320, 240, 30);
|
||||
cs::MjpegServer mjpegServer{"httpserver", 8081};
|
||||
mjpegServer.SetSource(camera);
|
||||
|
||||
CS_Status status = 0;
|
||||
cs::AddListener(
|
||||
[&](const cs::RawEvent& event) {
|
||||
wpi::outs() << "FPS=" << camera.GetActualFPS()
|
||||
<< " MBPS=" << (camera.GetActualDataRate() / 1000000.0)
|
||||
<< '\n';
|
||||
},
|
||||
cs::RawEvent::kTelemetryUpdated, false, &status);
|
||||
cs::SetTelemetryPeriod(1.0);
|
||||
|
||||
std::getchar();
|
||||
}
|
||||
24
cscore/src/dev/java/edu/wpi/cscore/DevMain.java
Normal file
24
cscore/src/dev/java/edu/wpi/cscore/DevMain.java
Normal file
@@ -0,0 +1,24 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
import edu.wpi.first.wpiutil.RuntimeDetector;
|
||||
|
||||
public final class DevMain {
|
||||
/**
|
||||
* Main method.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello World!");
|
||||
System.out.println(RuntimeDetector.getPlatformPath());
|
||||
System.out.println(CameraServerJNI.getHostname());
|
||||
}
|
||||
|
||||
private DevMain() {
|
||||
}
|
||||
}
|
||||
12
cscore/src/dev/native/cpp/main.cpp
Normal file
12
cscore/src/dev/native/cpp/main.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "cscore.h"
|
||||
|
||||
int main() { std::cout << cs::GetHostname() << std::endl; }
|
||||
45
cscore/src/main/java/edu/wpi/cscore/AxisCamera.java
Normal file
45
cscore/src/main/java/edu/wpi/cscore/AxisCamera.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* A source that represents an Axis IP camera.
|
||||
*/
|
||||
public class AxisCamera extends HttpCamera {
|
||||
private static String hostToUrl(String host) {
|
||||
return "http://" + host + "/mjpg/video.mjpg";
|
||||
}
|
||||
|
||||
private static String[] hostToUrl(String[] hosts) {
|
||||
String[] urls = new String[hosts.length];
|
||||
for (int i = 0; i < hosts.length; i++) {
|
||||
urls[i] = hostToUrl(hosts[i]);
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source for an Axis IP camera.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
*/
|
||||
public AxisCamera(String name, String host) {
|
||||
super(name, hostToUrl(host), HttpCameraKind.kAxis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source for an Axis IP camera.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
public AxisCamera(String name, String[] hosts) {
|
||||
super(name, hostToUrl(hosts), HttpCameraKind.kAxis);
|
||||
}
|
||||
}
|
||||
231
cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java
Normal file
231
cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java
Normal file
@@ -0,0 +1,231 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
|
||||
import edu.wpi.first.wpiutil.RuntimeLoader;
|
||||
|
||||
public class CameraServerJNI {
|
||||
static boolean libraryLoaded = false;
|
||||
static boolean cvLibraryLoaded = false;
|
||||
|
||||
static RuntimeLoader<CameraServerJNI> loader = null;
|
||||
static RuntimeLoader<Core> cvLoader = null;
|
||||
|
||||
static {
|
||||
if (!libraryLoaded) {
|
||||
try {
|
||||
loader = new RuntimeLoader<>("cscorejni", RuntimeLoader.getDefaultExtractionRoot(), CameraServerJNI.class);
|
||||
loader.loadLibrary();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
libraryLoaded = true;
|
||||
}
|
||||
|
||||
String opencvName = Core.NATIVE_LIBRARY_NAME;
|
||||
if (!cvLibraryLoaded) {
|
||||
try {
|
||||
cvLoader = new RuntimeLoader<>(opencvName, RuntimeLoader.getDefaultExtractionRoot(), Core.class);
|
||||
cvLoader.loadLibraryHashed();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
cvLibraryLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void forceLoad() {}
|
||||
|
||||
//
|
||||
// Property Functions
|
||||
//
|
||||
public static native int getPropertyKind(int property);
|
||||
public static native String getPropertyName(int property);
|
||||
public static native int getProperty(int property);
|
||||
public static native void setProperty(int property, int value);
|
||||
public static native int getPropertyMin(int property);
|
||||
public static native int getPropertyMax(int property);
|
||||
public static native int getPropertyStep(int property);
|
||||
public static native int getPropertyDefault(int property);
|
||||
public static native String getStringProperty(int property);
|
||||
public static native void setStringProperty(int property, String value);
|
||||
public static native String[] getEnumPropertyChoices(int property);
|
||||
|
||||
//
|
||||
// Source Creation Functions
|
||||
//
|
||||
public static native int createUsbCameraDev(String name, int dev);
|
||||
public static native int createUsbCameraPath(String name, String path);
|
||||
public static native int createHttpCamera(String name, String url, int kind);
|
||||
public static native int createHttpCameraMulti(String name, String[] urls, int kind);
|
||||
public static native int createCvSource(String name, int pixelFormat, int width, int height, int fps);
|
||||
|
||||
//
|
||||
// Source Functions
|
||||
//
|
||||
public static native int getSourceKind(int source);
|
||||
public static native String getSourceName(int source);
|
||||
public static native String getSourceDescription(int source);
|
||||
public static native long getSourceLastFrameTime(int source);
|
||||
public static native void setSourceConnectionStrategy(int source, int strategy);
|
||||
public static native boolean isSourceConnected(int source);
|
||||
public static native boolean isSourceEnabled(int source);
|
||||
public static native int getSourceProperty(int source, String name);
|
||||
public static native int[] enumerateSourceProperties(int source);
|
||||
public static native VideoMode getSourceVideoMode(int source);
|
||||
public static native boolean setSourceVideoMode(int source, int pixelFormat, int width, int height, int fps);
|
||||
public static native boolean setSourcePixelFormat(int source, int pixelFormat);
|
||||
public static native boolean setSourceResolution(int source, int width, int height);
|
||||
public static native boolean setSourceFPS(int source, int fps);
|
||||
public static native boolean setSourceConfigJson(int source, String config);
|
||||
public static native String getSourceConfigJson(int source);
|
||||
public static native VideoMode[] enumerateSourceVideoModes(int source);
|
||||
public static native int[] enumerateSourceSinks(int source);
|
||||
public static native int copySource(int source);
|
||||
public static native void releaseSource(int source);
|
||||
|
||||
//
|
||||
// Camera Source Common Property Fuctions
|
||||
//
|
||||
public static native void setCameraBrightness(int source, int brightness);
|
||||
public static native int getCameraBrightness(int source);
|
||||
public static native void setCameraWhiteBalanceAuto(int source);
|
||||
public static native void setCameraWhiteBalanceHoldCurrent(int source);
|
||||
public static native void setCameraWhiteBalanceManual(int source, int value);
|
||||
public static native void setCameraExposureAuto(int source);
|
||||
public static native void setCameraExposureHoldCurrent(int source);
|
||||
public static native void setCameraExposureManual(int source, int value);
|
||||
|
||||
//
|
||||
// UsbCamera Source Functions
|
||||
//
|
||||
public static native String getUsbCameraPath(int source);
|
||||
public static native UsbCameraInfo getUsbCameraInfo(int source);
|
||||
|
||||
//
|
||||
// HttpCamera Source Functions
|
||||
//
|
||||
public static native int getHttpCameraKind(int source);
|
||||
public static native void setHttpCameraUrls(int source, String[] urls);
|
||||
public static native String[] getHttpCameraUrls(int source);
|
||||
|
||||
//
|
||||
// OpenCV Source Functions
|
||||
//
|
||||
public static native void putSourceFrame(int source, long imageNativeObj);
|
||||
public static native void notifySourceError(int source, String msg);
|
||||
public static native void setSourceConnected(int source, boolean connected);
|
||||
public static native void setSourceDescription(int source, String description);
|
||||
public static native int createSourceProperty(int source, String name, int kind, int minimum, int maximum, int step, int defaultValue, int value);
|
||||
public static native void setSourceEnumPropertyChoices(int source, int property, String[] choices);
|
||||
|
||||
//
|
||||
// Sink Creation Functions
|
||||
//
|
||||
public static native int createMjpegServer(String name, String listenAddress, int port);
|
||||
public static native int createCvSink(String name);
|
||||
//public static native int createCvSinkCallback(String name,
|
||||
// void (*processFrame)(long time));
|
||||
|
||||
//
|
||||
// Sink Functions
|
||||
//
|
||||
public static native int getSinkKind(int sink);
|
||||
public static native String getSinkName(int sink);
|
||||
public static native String getSinkDescription(int sink);
|
||||
public static native int getSinkProperty(int sink, String name);
|
||||
public static native int[] enumerateSinkProperties(int sink);
|
||||
public static native boolean setSinkConfigJson(int sink, String config);
|
||||
public static native String getSinkConfigJson(int sink);
|
||||
public static native void setSinkSource(int sink, int source);
|
||||
public static native int getSinkSourceProperty(int sink, String name);
|
||||
public static native int getSinkSource(int sink);
|
||||
public static native int copySink(int sink);
|
||||
public static native void releaseSink(int sink);
|
||||
|
||||
//
|
||||
// MjpegServer Sink Functions
|
||||
//
|
||||
public static native String getMjpegServerListenAddress(int sink);
|
||||
public static native int getMjpegServerPort(int sink);
|
||||
|
||||
//
|
||||
// OpenCV Sink Functions
|
||||
//
|
||||
public static native void setSinkDescription(int sink, String description);
|
||||
public static native long grabSinkFrame(int sink, long imageNativeObj);
|
||||
public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout);
|
||||
public static native String getSinkError(int sink);
|
||||
public static native void setSinkEnabled(int sink, boolean enabled);
|
||||
|
||||
//
|
||||
// Listener Functions
|
||||
//
|
||||
public static native int addListener(Consumer<VideoEvent> listener,
|
||||
int eventMask, boolean immediateNotify);
|
||||
|
||||
public static native void removeListener(int handle);
|
||||
|
||||
//
|
||||
// Telemetry Functions
|
||||
//
|
||||
public enum TelemetryKind {
|
||||
kSourceBytesReceived(1),
|
||||
kSourceFramesReceived(2);
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private final int value;
|
||||
|
||||
TelemetryKind(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
public static native void setTelemetryPeriod(double seconds);
|
||||
public static native double getTelemetryElapsedTime();
|
||||
public static native long getTelemetryValue(int handle, int kind);
|
||||
public static long getTelemetryValue(int handle, TelemetryKind kind) {
|
||||
return getTelemetryValue(handle, kind.getValue());
|
||||
}
|
||||
public static native double getTelemetryAverageValue(int handle, int kind);
|
||||
public static double getTelemetryAverageValue(int handle, TelemetryKind kind) {
|
||||
return getTelemetryAverageValue(handle, kind.getValue());
|
||||
}
|
||||
|
||||
//
|
||||
// Logging Functions
|
||||
//
|
||||
@FunctionalInterface
|
||||
public interface LoggerFunction {
|
||||
void apply(int level, String file, int line, String msg);
|
||||
}
|
||||
public static native void setLogger(LoggerFunction func, int minLevel);
|
||||
|
||||
//
|
||||
// Utility Functions
|
||||
//
|
||||
public static native UsbCameraInfo[] enumerateUsbCameras();
|
||||
|
||||
public static native int[] enumerateSources();
|
||||
|
||||
public static native int[] enumerateSinks();
|
||||
|
||||
public static native String getHostname();
|
||||
|
||||
public static native String[] getNetworkInterfaces();
|
||||
}
|
||||
101
cscore/src/main/java/edu/wpi/cscore/CvSink.java
Normal file
101
cscore/src/main/java/edu/wpi/cscore/CvSink.java
Normal file
@@ -0,0 +1,101 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
/**
|
||||
* A sink for user code to accept video frames as OpenCV images.
|
||||
*/
|
||||
public class CvSink extends VideoSink {
|
||||
/**
|
||||
* Create a sink for accepting OpenCV images.
|
||||
* WaitForFrame() must be called on the created sink to get each new
|
||||
* image.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
*/
|
||||
public CvSink(String name) {
|
||||
super(CameraServerJNI.createCvSink(name));
|
||||
}
|
||||
|
||||
/// Create a sink for accepting OpenCV images in a separate thread.
|
||||
/// A thread will be created that calls WaitForFrame() and calls the
|
||||
/// processFrame() callback each time a new frame arrives.
|
||||
/// @param name Source name (arbitrary unique identifier)
|
||||
/// @param processFrame Frame processing function; will be called with a
|
||||
/// time=0 if an error occurred. processFrame should call GetImage()
|
||||
/// or GetError() as needed, but should not call (except in very
|
||||
/// unusual circumstances) WaitForImage().
|
||||
//public CvSink(wpi::StringRef name,
|
||||
// std::function<void(uint64_t time)> processFrame) {
|
||||
// super(CameraServerJNI.createCvSinkCallback(name, processFrame));
|
||||
//}
|
||||
|
||||
/**
|
||||
* Set sink description.
|
||||
*
|
||||
* @param description Description
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
CameraServerJNI.setSinkDescription(m_handle, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the next frame and get the image.
|
||||
* Times out (returning 0) after 0.225 seconds.
|
||||
* The provided image will have three 3-bit channels stored in BGR order.
|
||||
*
|
||||
* @return Frame time, or 0 on error (call GetError() to obtain the error
|
||||
* message)
|
||||
*/
|
||||
public long grabFrame(Mat image) {
|
||||
return grabFrame(image, 0.225);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the next frame and get the image.
|
||||
* Times out (returning 0) after timeout seconds.
|
||||
* The provided image will have three 3-bit channels stored in BGR order.
|
||||
*
|
||||
* @return Frame time, or 0 on error (call GetError() to obtain the error
|
||||
* message); the frame time is in 1 us increments.
|
||||
*/
|
||||
public long grabFrame(Mat image, double timeout) {
|
||||
return CameraServerJNI.grabSinkFrameTimeout(m_handle, image.nativeObj, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the next frame and get the image. May block forever.
|
||||
* The provided image will have three 3-bit channels stored in BGR order.
|
||||
*
|
||||
* @return Frame time, or 0 on error (call GetError() to obtain the error
|
||||
* message); the frame time is in 1 us increments.
|
||||
*/
|
||||
public long grabFrameNoTimeout(Mat image) {
|
||||
return CameraServerJNI.grabSinkFrame(m_handle, image.nativeObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error string. Call this if WaitForFrame() returns 0 to determine
|
||||
* what the error is.
|
||||
*/
|
||||
public String getError() {
|
||||
return CameraServerJNI.getSinkError(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable getting new frames.
|
||||
* Disabling will cause processFrame (for callback-based CvSinks) to not
|
||||
* be called and WaitForFrame() to not return. This can be used to save
|
||||
* processor resources when frames are not needed.
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
CameraServerJNI.setSinkEnabled(m_handle, enabled);
|
||||
}
|
||||
}
|
||||
203
cscore/src/main/java/edu/wpi/cscore/CvSource.java
Normal file
203
cscore/src/main/java/edu/wpi/cscore/CvSource.java
Normal file
@@ -0,0 +1,203 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
/**
|
||||
* A source that represents a video camera.
|
||||
*/
|
||||
public class CvSource extends VideoSource {
|
||||
/**
|
||||
* Create an OpenCV source.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param mode Video mode being generated
|
||||
*/
|
||||
public CvSource(String name, VideoMode mode) {
|
||||
super(CameraServerJNI.createCvSource(name,
|
||||
mode.pixelFormat.getValue(),
|
||||
mode.width,
|
||||
mode.height,
|
||||
mode.fps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an OpenCV source.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param pixelFormat Pixel format
|
||||
* @param width width
|
||||
* @param height height
|
||||
* @param fps fps
|
||||
*/
|
||||
public CvSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
super(CameraServerJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an OpenCV image and notify sinks.
|
||||
*
|
||||
* <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images
|
||||
* are supported. If the format, depth or channel order is different, use
|
||||
* Mat.convertTo() and/or cvtColor() to convert it first.
|
||||
*
|
||||
* @param image OpenCV image
|
||||
*/
|
||||
public void putFrame(Mat image) {
|
||||
CameraServerJNI.putSourceFrame(m_handle, image.nativeObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal sinks that an error has occurred. This should be called instead
|
||||
* of NotifyFrame when an error occurs.
|
||||
*/
|
||||
public void notifyError(String msg) {
|
||||
CameraServerJNI.notifySourceError(m_handle, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set source connection status. Defaults to true.
|
||||
*
|
||||
* @param connected True for connected, false for disconnected
|
||||
*/
|
||||
public void setConnected(boolean connected) {
|
||||
CameraServerJNI.setSourceConnected(m_handle, connected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set source description.
|
||||
*
|
||||
* @param description Description
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
CameraServerJNI.setSourceDescription(m_handle, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a property.
|
||||
*
|
||||
* @param name Property name
|
||||
* @param kind Property kind
|
||||
* @param minimum Minimum value
|
||||
* @param maximum Maximum value
|
||||
* @param step Step value
|
||||
* @param defaultValue Default value
|
||||
* @param value Current value
|
||||
* @return Property
|
||||
*/
|
||||
public VideoProperty createProperty(String name,
|
||||
VideoProperty.Kind kind,
|
||||
int minimum,
|
||||
int maximum,
|
||||
int step,
|
||||
int defaultValue,
|
||||
int value) {
|
||||
return new VideoProperty(
|
||||
CameraServerJNI.createSourceProperty(m_handle,
|
||||
name,
|
||||
kind.getValue(),
|
||||
minimum,
|
||||
maximum,
|
||||
step,
|
||||
defaultValue,
|
||||
value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an integer property.
|
||||
*
|
||||
* @param name Property name
|
||||
* @param minimum Minimum value
|
||||
* @param maximum Maximum value
|
||||
* @param step Step value
|
||||
* @param defaultValue Default value
|
||||
* @param value Current value
|
||||
* @return Property
|
||||
*/
|
||||
public VideoProperty createIntegerProperty(String name,
|
||||
int minimum,
|
||||
int maximum,
|
||||
int step,
|
||||
int defaultValue,
|
||||
int value) {
|
||||
return new VideoProperty(
|
||||
CameraServerJNI.createSourceProperty(m_handle,
|
||||
name,
|
||||
VideoProperty.Kind.kInteger.getValue(),
|
||||
minimum,
|
||||
maximum,
|
||||
step,
|
||||
defaultValue,
|
||||
value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a boolean property.
|
||||
*
|
||||
* @param name Property name
|
||||
* @param defaultValue Default value
|
||||
* @param value Current value
|
||||
* @return Property
|
||||
*/
|
||||
public VideoProperty createBooleanProperty(String name, boolean defaultValue, boolean value) {
|
||||
return new VideoProperty(
|
||||
CameraServerJNI.createSourceProperty(m_handle,
|
||||
name,
|
||||
VideoProperty.Kind.kBoolean.getValue(),
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
defaultValue ? 1 : 0,
|
||||
value ? 1 : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a string property.
|
||||
*
|
||||
* @param name Property name
|
||||
* @param value Current value
|
||||
* @return Property
|
||||
*/
|
||||
public VideoProperty createStringProperty(String name, String value) {
|
||||
VideoProperty prop = new VideoProperty(
|
||||
CameraServerJNI.createSourceProperty(m_handle,
|
||||
name,
|
||||
VideoProperty.Kind.kString.getValue(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0));
|
||||
prop.setString(value);
|
||||
return prop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure enum property choices.
|
||||
*
|
||||
* @param property Property
|
||||
* @param choices Choices
|
||||
*/
|
||||
public void setEnumPropertyChoices(VideoProperty property, String[] choices) {
|
||||
CameraServerJNI.setSourceEnumPropertyChoices(m_handle, property.m_handle, choices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure enum property choices.
|
||||
*
|
||||
* @param property Property
|
||||
* @param choices Choices
|
||||
* @deprecated Use {@code setEnumPropertyChoices} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("MethodName")
|
||||
public void SetEnumPropertyChoices(VideoProperty property, String[] choices) {
|
||||
setEnumPropertyChoices(property, choices);
|
||||
}
|
||||
}
|
||||
109
cscore/src/main/java/edu/wpi/cscore/HttpCamera.java
Normal file
109
cscore/src/main/java/edu/wpi/cscore/HttpCamera.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* A source that represents a MJPEG-over-HTTP (IP) camera.
|
||||
*/
|
||||
public class HttpCamera extends VideoCamera {
|
||||
public enum HttpCameraKind {
|
||||
kUnknown(0), kMJPGStreamer(1), kCSCore(2), kAxis(3);
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private final int value;
|
||||
|
||||
HttpCameraKind(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from the numerical representation of kind to an enum type.
|
||||
*
|
||||
* @param kind The numerical representation of kind
|
||||
* @return The kind
|
||||
*/
|
||||
public static HttpCameraKind getHttpCameraKindFromInt(int kind) {
|
||||
switch (kind) {
|
||||
case 1: return HttpCameraKind.kMJPGStreamer;
|
||||
case 2: return HttpCameraKind.kCSCore;
|
||||
case 3: return HttpCameraKind.kAxis;
|
||||
default: return HttpCameraKind.kUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source for a MJPEG-over-HTTP (IP) camera.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
|
||||
*/
|
||||
public HttpCamera(String name, String url) {
|
||||
super(CameraServerJNI.createHttpCamera(name, url, HttpCameraKind.kUnknown.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source for a MJPEG-over-HTTP (IP) camera.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
public HttpCamera(String name, String url, HttpCameraKind kind) {
|
||||
super(CameraServerJNI.createHttpCamera(name, url, kind.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source for a MJPEG-over-HTTP (IP) camera.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param urls Array of Camera URLs
|
||||
*/
|
||||
public HttpCamera(String name, String[] urls) {
|
||||
super(CameraServerJNI.createHttpCameraMulti(name, urls, HttpCameraKind.kUnknown.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source for a MJPEG-over-HTTP (IP) camera.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param urls Array of Camera URLs
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
public HttpCamera(String name, String[] urls, HttpCameraKind kind) {
|
||||
super(CameraServerJNI.createHttpCameraMulti(name, urls, kind.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the kind of HTTP camera.
|
||||
*
|
||||
* <p>Autodetection can result in returning a different value than the camera
|
||||
* was created with.
|
||||
*/
|
||||
public HttpCameraKind getHttpCameraKind() {
|
||||
return getHttpCameraKindFromInt(CameraServerJNI.getHttpCameraKind(m_handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the URLs used to connect to the camera.
|
||||
*/
|
||||
public void setUrls(String[] urls) {
|
||||
CameraServerJNI.setHttpCameraUrls(m_handle, urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URLs used to connect to the camera.
|
||||
*/
|
||||
public String[] getUrls() {
|
||||
return CameraServerJNI.getHttpCameraUrls(m_handle);
|
||||
}
|
||||
}
|
||||
104
cscore/src/main/java/edu/wpi/cscore/MjpegServer.java
Normal file
104
cscore/src/main/java/edu/wpi/cscore/MjpegServer.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* A sink that acts as a MJPEG-over-HTTP network server.
|
||||
*/
|
||||
public class MjpegServer extends VideoSink {
|
||||
/**
|
||||
* Create a MJPEG-over-HTTP server sink.
|
||||
*
|
||||
* @param name Sink name (arbitrary unique identifier)
|
||||
* @param listenAddress TCP listen address (empty string for all addresses)
|
||||
* @param port TCP port number
|
||||
*/
|
||||
public MjpegServer(String name, String listenAddress, int port) {
|
||||
super(CameraServerJNI.createMjpegServer(name, listenAddress, port));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MJPEG-over-HTTP server sink.
|
||||
*
|
||||
* @param name Sink name (arbitrary unique identifier)
|
||||
* @param port TCP port number
|
||||
*/
|
||||
public MjpegServer(String name, int port) {
|
||||
this(name, "", port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the listen address of the server.
|
||||
*/
|
||||
public String getListenAddress() {
|
||||
return CameraServerJNI.getMjpegServerListenAddress(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port number of the server.
|
||||
*/
|
||||
public int getPort() {
|
||||
return CameraServerJNI.getMjpegServerPort(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stream resolution for clients that don't specify it.
|
||||
*
|
||||
* <p>It is not necessary to set this if it is the same as the source
|
||||
* resolution.
|
||||
*
|
||||
* <p>Setting this different than the source resolution will result in
|
||||
* increased CPU usage, particularly for MJPEG source cameras, as it will
|
||||
* decompress, resize, and recompress the image, instead of using the
|
||||
* camera's MJPEG image directly.
|
||||
*
|
||||
* @param width width, 0 for unspecified
|
||||
* @param height height, 0 for unspecified
|
||||
*/
|
||||
public void setResolution(int width, int height) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "width"), width);
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "height"), height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stream frames per second (FPS) for clients that don't specify it.
|
||||
*
|
||||
* <p>It is not necessary to set this if it is the same as the source FPS.
|
||||
*
|
||||
* @param fps FPS, 0 for unspecified
|
||||
*/
|
||||
public void setFPS(int fps) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "fps"), fps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the compression for clients that don't specify it.
|
||||
*
|
||||
* <p>Setting this will result in increased CPU usage for MJPEG source cameras
|
||||
* as it will decompress and recompress the image instead of using the
|
||||
* camera's MJPEG image directly.
|
||||
*
|
||||
* @param quality JPEG compression quality (0-100), -1 for unspecified
|
||||
*/
|
||||
public void setCompression(int quality) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "compression"),
|
||||
quality);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default compression used for non-MJPEG sources. If not set,
|
||||
* 80 is used. This function has no effect on MJPEG source cameras; use
|
||||
* SetCompression() instead to force recompression of MJPEG source images.
|
||||
*
|
||||
* @param quality JPEG compression quality (0-100)
|
||||
*/
|
||||
public void setDefaultCompression(int quality) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "default_compression"),
|
||||
quality);
|
||||
}
|
||||
}
|
||||
66
cscore/src/main/java/edu/wpi/cscore/UsbCamera.java
Normal file
66
cscore/src/main/java/edu/wpi/cscore/UsbCamera.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* A source that represents a USB camera.
|
||||
*/
|
||||
public class UsbCamera extends VideoCamera {
|
||||
/**
|
||||
* Create a source for a USB camera based on device number.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param dev Device number (e.g. 0 for /dev/video0)
|
||||
*/
|
||||
public UsbCamera(String name, int dev) {
|
||||
super(CameraServerJNI.createUsbCameraDev(name, dev));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source for a USB camera based on device path.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param path Path to device (e.g. "/dev/video0" on Linux)
|
||||
*/
|
||||
public UsbCamera(String name, String path) {
|
||||
super(CameraServerJNI.createUsbCameraPath(name, path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate USB cameras on the local system.
|
||||
*
|
||||
* @return Vector of USB camera information (one for each camera)
|
||||
*/
|
||||
public static UsbCameraInfo[] enumerateUsbCameras() {
|
||||
return CameraServerJNI.enumerateUsbCameras();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the device.
|
||||
*/
|
||||
public String getPath() {
|
||||
return CameraServerJNI.getUsbCameraPath(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full camera information for the device.
|
||||
*/
|
||||
public UsbCameraInfo getInfo() {
|
||||
return CameraServerJNI.getUsbCameraInfo(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set how verbose the camera connection messages are.
|
||||
*
|
||||
* @param level 0=don't display Connecting message, 1=do display message
|
||||
*/
|
||||
public void setConnectVerbose(int level) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSourceProperty(m_handle, "connect_verbose"),
|
||||
level);
|
||||
}
|
||||
}
|
||||
53
cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java
Normal file
53
cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* USB camera information.
|
||||
*/
|
||||
public class UsbCameraInfo {
|
||||
/**
|
||||
* Create a new set of UsbCameraInfo.
|
||||
*
|
||||
* @param dev Device number (e.g. N in '/dev/videoN' on Linux)
|
||||
* @param path Path to device if available (e.g. '/dev/video0' on Linux)
|
||||
* @param name Vendor/model name of the camera as provided by the USB driver
|
||||
* @param otherPaths Other path aliases to device
|
||||
*/
|
||||
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
|
||||
public UsbCameraInfo(int dev, String path, String name, String[] otherPaths) {
|
||||
this.dev = dev;
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
this.otherPaths = otherPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Device number (e.g. N in '/dev/videoN' on Linux).
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public int dev;
|
||||
|
||||
/**
|
||||
* Path to device if available (e.g. '/dev/video0' on Linux).
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public String path;
|
||||
|
||||
/**
|
||||
* Vendor/model name of the camera as provided by the USB driver.
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public String name;
|
||||
|
||||
/**
|
||||
* Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux).
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public String[] otherPaths;
|
||||
}
|
||||
81
cscore/src/main/java/edu/wpi/cscore/VideoCamera.java
Normal file
81
cscore/src/main/java/edu/wpi/cscore/VideoCamera.java
Normal file
@@ -0,0 +1,81 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* A source that represents a video camera.
|
||||
*/
|
||||
public class VideoCamera extends VideoSource {
|
||||
public static class WhiteBalance {
|
||||
public static final int kFixedIndoor = 3000;
|
||||
public static final int kFixedOutdoor1 = 4000;
|
||||
public static final int kFixedOutdoor2 = 5000;
|
||||
public static final int kFixedFluorescent1 = 5100;
|
||||
public static final int kFixedFlourescent2 = 5200;
|
||||
}
|
||||
|
||||
protected VideoCamera(int handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the brightness, as a percentage (0-100).
|
||||
*/
|
||||
public synchronized void setBrightness(int brightness) {
|
||||
CameraServerJNI.setCameraBrightness(m_handle, brightness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the brightness, as a percentage (0-100).
|
||||
*/
|
||||
public synchronized int getBrightness() {
|
||||
return CameraServerJNI.getCameraBrightness(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the white balance to auto.
|
||||
*/
|
||||
public synchronized void setWhiteBalanceAuto() {
|
||||
CameraServerJNI.setCameraWhiteBalanceAuto(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the white balance to hold current.
|
||||
*/
|
||||
public synchronized void setWhiteBalanceHoldCurrent() {
|
||||
CameraServerJNI.setCameraWhiteBalanceHoldCurrent(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the white balance to manual, with specified color temperature.
|
||||
*/
|
||||
public synchronized void setWhiteBalanceManual(int value) {
|
||||
CameraServerJNI.setCameraWhiteBalanceManual(m_handle, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the exposure to auto aperture.
|
||||
*/
|
||||
public synchronized void setExposureAuto() {
|
||||
CameraServerJNI.setCameraExposureAuto(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the exposure to hold current.
|
||||
*/
|
||||
public synchronized void setExposureHoldCurrent() {
|
||||
CameraServerJNI.setCameraExposureHoldCurrent(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the exposure to manual, as a percentage (0-100).
|
||||
*/
|
||||
public synchronized void setExposureManual(int value) {
|
||||
CameraServerJNI.setCameraExposureManual(m_handle, value);
|
||||
}
|
||||
}
|
||||
133
cscore/src/main/java/edu/wpi/cscore/VideoEvent.java
Normal file
133
cscore/src/main/java/edu/wpi/cscore/VideoEvent.java
Normal file
@@ -0,0 +1,133 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* Video event.
|
||||
*/
|
||||
public class VideoEvent {
|
||||
public enum Kind {
|
||||
kUnknown(0x0000),
|
||||
kSourceCreated(0x0001),
|
||||
kSourceDestroyed(0x0002),
|
||||
kSourceConnected(0x0004),
|
||||
kSourceDisconnected(0x0008),
|
||||
kSourceVideoModesUpdated(0x0010),
|
||||
kSourceVideoModeChanged(0x0020),
|
||||
kSourcePropertyCreated(0x0040),
|
||||
kSourcePropertyValueUpdated(0x0080),
|
||||
kSourcePropertyChoicesUpdated(0x0100),
|
||||
kSinkSourceChanged(0x0200),
|
||||
kSinkCreated(0x0400),
|
||||
kSinkDestroyed(0x0800),
|
||||
kSinkEnabled(0x1000),
|
||||
kSinkDisabled(0x2000),
|
||||
kNetworkInterfacesChanged(0x4000),
|
||||
kTelemetryUpdated(0x8000),
|
||||
kSinkPropertyCreated(0x10000),
|
||||
kSinkPropertyValueUpdated(0x20000),
|
||||
kSinkPropertyChoicesUpdated(0x40000);
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private final int value;
|
||||
|
||||
Kind(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from the numerical representation of kind to an enum type.
|
||||
*
|
||||
* @param kind The numerical representation of kind
|
||||
* @return The kind
|
||||
*/
|
||||
@SuppressWarnings("PMD.CyclomaticComplexity")
|
||||
public static Kind getKindFromInt(int kind) {
|
||||
switch (kind) {
|
||||
case 0x0001: return Kind.kSourceCreated;
|
||||
case 0x0002: return Kind.kSourceDestroyed;
|
||||
case 0x0004: return Kind.kSourceConnected;
|
||||
case 0x0008: return Kind.kSourceDisconnected;
|
||||
case 0x0010: return Kind.kSourceVideoModesUpdated;
|
||||
case 0x0020: return Kind.kSourceVideoModeChanged;
|
||||
case 0x0040: return Kind.kSourcePropertyCreated;
|
||||
case 0x0080: return Kind.kSourcePropertyValueUpdated;
|
||||
case 0x0100: return Kind.kSourcePropertyChoicesUpdated;
|
||||
case 0x0200: return Kind.kSinkSourceChanged;
|
||||
case 0x0400: return Kind.kSinkCreated;
|
||||
case 0x0800: return Kind.kSinkDestroyed;
|
||||
case 0x1000: return Kind.kSinkEnabled;
|
||||
case 0x2000: return Kind.kSinkDisabled;
|
||||
case 0x4000: return Kind.kNetworkInterfacesChanged;
|
||||
case 0x10000: return Kind.kSinkPropertyCreated;
|
||||
case 0x20000: return Kind.kSinkPropertyValueUpdated;
|
||||
case 0x40000: return Kind.kSinkPropertyChoicesUpdated;
|
||||
default: return Kind.kUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.ExcessiveParameterList")
|
||||
VideoEvent(int kind, int source, int sink, String name, int pixelFormat,
|
||||
int width, int height, int fps, int property, int propertyKind,
|
||||
int value, String valueStr) {
|
||||
this.kind = getKindFromInt(kind);
|
||||
this.sourceHandle = source;
|
||||
this.sinkHandle = sink;
|
||||
this.name = name;
|
||||
this.mode = new VideoMode(pixelFormat, width, height, fps);
|
||||
this.propertyHandle = property;
|
||||
this.propertyKind = VideoProperty.getKindFromInt(propertyKind);
|
||||
this.value = value;
|
||||
this.valueStr = valueStr;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public Kind kind;
|
||||
|
||||
// Valid for kSource* and kSink* respectively
|
||||
@SuppressWarnings("MemberName")
|
||||
public int sourceHandle;
|
||||
@SuppressWarnings("MemberName")
|
||||
public int sinkHandle;
|
||||
|
||||
// Source/sink/property name
|
||||
@SuppressWarnings("MemberName")
|
||||
public String name;
|
||||
|
||||
// Fields for kSourceVideoModeChanged event
|
||||
@SuppressWarnings("MemberName")
|
||||
public VideoMode mode;
|
||||
|
||||
// Fields for kSourceProperty* events
|
||||
@SuppressWarnings("MemberName")
|
||||
public int propertyHandle;
|
||||
@SuppressWarnings("MemberName")
|
||||
public VideoProperty.Kind propertyKind;
|
||||
@SuppressWarnings("MemberName")
|
||||
public int value;
|
||||
@SuppressWarnings("MemberName")
|
||||
public String valueStr;
|
||||
|
||||
public VideoSource getSource() {
|
||||
return new VideoSource(CameraServerJNI.copySource(sourceHandle));
|
||||
}
|
||||
|
||||
public VideoSink getSink() {
|
||||
return new VideoSink(CameraServerJNI.copySink(sinkHandle));
|
||||
}
|
||||
|
||||
public VideoProperty getProperty() {
|
||||
return new VideoProperty(propertyHandle, propertyKind);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,17 +5,20 @@
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Internal/HardwareHLReporting.h"
|
||||
package edu.wpi.cscore;
|
||||
|
||||
#include <HAL/HAL.h>
|
||||
/**
|
||||
* An exception raised by the camera server.
|
||||
*/
|
||||
public class VideoException extends RuntimeException {
|
||||
private static final long serialVersionUID = -9155939328084105145L;
|
||||
|
||||
using namespace frc;
|
||||
public VideoException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
void HardwareHLReporting::ReportScheduler() {
|
||||
HAL_Report(HALUsageReporting::kResourceType_Command,
|
||||
HALUsageReporting::kCommand_Scheduler);
|
||||
}
|
||||
|
||||
void HardwareHLReporting::ReportSmartDashboard() {
|
||||
HAL_Report(HALUsageReporting::kResourceType_SmartDashboard, 0);
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VideoException [" + super.toString() + "]";
|
||||
}
|
||||
}
|
||||
48
cscore/src/main/java/edu/wpi/cscore/VideoListener.java
Normal file
48
cscore/src/main/java/edu/wpi/cscore/VideoListener.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An event listener. This calls back to a desigated callback function when
|
||||
* an event matching the specified mask is generated by the library.
|
||||
*/
|
||||
public class VideoListener implements AutoCloseable {
|
||||
/**
|
||||
* Create an event listener.
|
||||
*
|
||||
* @param listener Listener function
|
||||
* @param eventMask Bitmask of VideoEvent.Type values
|
||||
* @param immediateNotify Whether callback should be immediately called with
|
||||
* a representative set of events for the current library state.
|
||||
*/
|
||||
public VideoListener(Consumer<VideoEvent> listener, int eventMask,
|
||||
boolean immediateNotify) {
|
||||
m_handle = CameraServerJNI.addListener(listener, eventMask, immediateNotify);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void free() {
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
CameraServerJNI.removeListener(m_handle);
|
||||
}
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
private int m_handle;
|
||||
}
|
||||
78
cscore/src/main/java/edu/wpi/cscore/VideoMode.java
Normal file
78
cscore/src/main/java/edu/wpi/cscore/VideoMode.java
Normal file
@@ -0,0 +1,78 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* Video mode.
|
||||
*/
|
||||
public class VideoMode {
|
||||
public enum PixelFormat {
|
||||
kUnknown(0), kMJPEG(1), kYUYV(2), kRGB565(3), kBGR(4), kGray(5);
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private final int value;
|
||||
|
||||
PixelFormat(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static final PixelFormat[] m_pixelFormatValues = PixelFormat.values();
|
||||
|
||||
public static PixelFormat getPixelFormatFromInt(int pixelFormat) {
|
||||
return m_pixelFormatValues[pixelFormat];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new video mode.
|
||||
*/
|
||||
public VideoMode(int pixelFormat, int width, int height, int fps) {
|
||||
this.pixelFormat = getPixelFormatFromInt(pixelFormat);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.fps = fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new video mode.
|
||||
*/
|
||||
public VideoMode(PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
this.pixelFormat = pixelFormat;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.fps = fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pixel format.
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public PixelFormat pixelFormat;
|
||||
|
||||
/**
|
||||
* Width in pixels.
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public int width;
|
||||
|
||||
/**
|
||||
* Height in pixels.
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public int height;
|
||||
|
||||
/**
|
||||
* Frames per second.
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public int fps;
|
||||
}
|
||||
124
cscore/src/main/java/edu/wpi/cscore/VideoProperty.java
Normal file
124
cscore/src/main/java/edu/wpi/cscore/VideoProperty.java
Normal file
@@ -0,0 +1,124 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* A source or sink property.
|
||||
*/
|
||||
public class VideoProperty {
|
||||
public enum Kind {
|
||||
kNone(0), kBoolean(1), kInteger(2), kString(4), kEnum(8);
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private final int value;
|
||||
|
||||
Kind(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from the numerical representation of kind to an enum type.
|
||||
*
|
||||
* @param kind The numerical representation of kind
|
||||
* @return The kind
|
||||
*/
|
||||
public static Kind getKindFromInt(int kind) {
|
||||
switch (kind) {
|
||||
case 1: return Kind.kBoolean;
|
||||
case 2: return Kind.kInteger;
|
||||
case 4: return Kind.kString;
|
||||
case 8: return Kind.kEnum;
|
||||
default: return Kind.kNone;
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return CameraServerJNI.getPropertyName(m_handle);
|
||||
}
|
||||
|
||||
public Kind getKind() {
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return m_kind != Kind.kNone;
|
||||
}
|
||||
|
||||
// Kind checkers
|
||||
public boolean isBoolean() {
|
||||
return m_kind == Kind.kBoolean;
|
||||
}
|
||||
|
||||
public boolean isInteger() {
|
||||
return m_kind == Kind.kInteger;
|
||||
}
|
||||
|
||||
public boolean isString() {
|
||||
return m_kind == Kind.kString;
|
||||
}
|
||||
|
||||
public boolean isEnum() {
|
||||
return m_kind == Kind.kEnum;
|
||||
}
|
||||
|
||||
public int get() {
|
||||
return CameraServerJNI.getProperty(m_handle);
|
||||
}
|
||||
|
||||
public void set(int value) {
|
||||
CameraServerJNI.setProperty(m_handle, value);
|
||||
}
|
||||
|
||||
public int getMin() {
|
||||
return CameraServerJNI.getPropertyMin(m_handle);
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return CameraServerJNI.getPropertyMax(m_handle);
|
||||
}
|
||||
|
||||
public int getStep() {
|
||||
return CameraServerJNI.getPropertyStep(m_handle);
|
||||
}
|
||||
|
||||
public int getDefault() {
|
||||
return CameraServerJNI.getPropertyDefault(m_handle);
|
||||
}
|
||||
|
||||
// String-specific functions
|
||||
public String getString() {
|
||||
return CameraServerJNI.getStringProperty(m_handle);
|
||||
}
|
||||
|
||||
public void setString(String value) {
|
||||
CameraServerJNI.setStringProperty(m_handle, value);
|
||||
}
|
||||
|
||||
// Enum-specific functions
|
||||
public String[] getChoices() {
|
||||
return CameraServerJNI.getEnumPropertyChoices(m_handle);
|
||||
}
|
||||
|
||||
VideoProperty(int handle) {
|
||||
m_handle = handle;
|
||||
m_kind = getKindFromInt(CameraServerJNI.getPropertyKind(handle));
|
||||
}
|
||||
|
||||
VideoProperty(int handle, Kind kind) {
|
||||
m_handle = handle;
|
||||
m_kind = kind;
|
||||
}
|
||||
|
||||
int m_handle;
|
||||
private Kind m_kind;
|
||||
}
|
||||
222
cscore/src/main/java/edu/wpi/cscore/VideoSink.java
Normal file
222
cscore/src/main/java/edu/wpi/cscore/VideoSink.java
Normal file
@@ -0,0 +1,222 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* A source for video that provides a sequence of frames. Each frame may
|
||||
* consist of multiple images (e.g. from a stereo or depth camera); these
|
||||
* are called channels.
|
||||
*/
|
||||
public class VideoSink implements AutoCloseable {
|
||||
public enum Kind {
|
||||
kUnknown(0), kMjpeg(2), kCv(4);
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private final int value;
|
||||
|
||||
Kind(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from the numerical representation of kind to an enum type.
|
||||
*
|
||||
* @param kind The numerical representation of kind
|
||||
* @return The kind
|
||||
*/
|
||||
public static Kind getKindFromInt(int kind) {
|
||||
switch (kind) {
|
||||
case 2: return Kind.kMjpeg;
|
||||
case 4: return Kind.kCv;
|
||||
default: return Kind.kUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
protected VideoSink(int handle) {
|
||||
m_handle = handle;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void free() {
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
CameraServerJNI.releaseSink(m_handle);
|
||||
}
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
VideoSink sink = (VideoSink) other;
|
||||
return m_handle == sink.m_handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the kind of the sink.
|
||||
*/
|
||||
public Kind getKind() {
|
||||
return getKindFromInt(CameraServerJNI.getSinkKind(m_handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the sink. The name is an arbitrary identifier
|
||||
* provided when the sink is created, and should be unique.
|
||||
*/
|
||||
public String getName() {
|
||||
return CameraServerJNI.getSinkName(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sink description. This is sink-kind specific.
|
||||
*/
|
||||
public String getDescription() {
|
||||
return CameraServerJNI.getSinkDescription(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a property of the sink.
|
||||
*
|
||||
* @param name Property name
|
||||
* @return Property (kind Property::kNone if no property with
|
||||
* the given name exists)
|
||||
*/
|
||||
public VideoProperty getProperty(String name) {
|
||||
return new VideoProperty(CameraServerJNI.getSinkProperty(m_handle, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all properties of this sink.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
||||
public VideoProperty[] enumerateProperties() {
|
||||
int[] handles = CameraServerJNI.enumerateSinkProperties(m_handle);
|
||||
VideoProperty[] rv = new VideoProperty[handles.length];
|
||||
for (int i = 0; i < handles.length; i++) {
|
||||
rv[i] = new VideoProperty(handles[i]);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set properties from a JSON configuration string.
|
||||
*
|
||||
* <p>The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setConfigJson(String config) {
|
||||
return CameraServerJNI.setSinkConfigJson(m_handle, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
public String getConfigJson() {
|
||||
return CameraServerJNI.getSinkConfigJson(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure which source should provide frames to this sink. Each sink
|
||||
* can accept frames from only a single source, but a single source can
|
||||
* provide frames to multiple clients.
|
||||
*
|
||||
* @param source Source
|
||||
*/
|
||||
public void setSource(VideoSource source) {
|
||||
if (source == null) {
|
||||
CameraServerJNI.setSinkSource(m_handle, 0);
|
||||
} else {
|
||||
CameraServerJNI.setSinkSource(m_handle, source.m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connected source.
|
||||
*
|
||||
* @return Connected source; nullptr if no source connected.
|
||||
*/
|
||||
public VideoSource getSource() {
|
||||
// While VideoSource.free() will call releaseSource(), getSinkSource()
|
||||
// increments the internal reference count so this is okay to do.
|
||||
return new VideoSource(CameraServerJNI.getSinkSource(m_handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a property of the associated source.
|
||||
*
|
||||
* @param name Property name
|
||||
* @return Property (kind Property::kNone if no property with
|
||||
* the given name exists or no source connected)
|
||||
*/
|
||||
public VideoProperty getSourceProperty(String name) {
|
||||
return new VideoProperty(
|
||||
CameraServerJNI.getSinkSourceProperty(m_handle, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all existing sinks.
|
||||
*
|
||||
* @return Vector of sinks.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
||||
public static VideoSink[] enumerateSinks() {
|
||||
int[] handles = CameraServerJNI.enumerateSinks();
|
||||
VideoSink[] rv = new VideoSink[handles.length];
|
||||
for (int i = 0; i < handles.length; i++) {
|
||||
rv[i] = new VideoSink(handles[i]);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
protected int m_handle;
|
||||
}
|
||||
376
cscore/src/main/java/edu/wpi/cscore/VideoSource.java
Normal file
376
cscore/src/main/java/edu/wpi/cscore/VideoSource.java
Normal file
@@ -0,0 +1,376 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
/**
|
||||
* A source for video that provides a sequence of frames. Each frame may
|
||||
* consist of multiple images (e.g. from a stereo or depth camera); these
|
||||
* are called channels.
|
||||
*/
|
||||
public class VideoSource implements AutoCloseable {
|
||||
public enum Kind {
|
||||
kUnknown(0), kUsb(1), kHttp(2), kCv(4);
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private final int value;
|
||||
|
||||
Kind(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection strategy.
|
||||
*/
|
||||
public enum ConnectionStrategy {
|
||||
/**
|
||||
* Automatically connect or disconnect based on whether any sinks are
|
||||
* connected to this source. This is the default behavior.
|
||||
*/
|
||||
kAutoManage(0),
|
||||
|
||||
/**
|
||||
* Try to keep the connection open regardless of whether any sinks are
|
||||
* connected.
|
||||
*/
|
||||
kKeepOpen(1),
|
||||
|
||||
/**
|
||||
* Never open the connection. If this is set when the connection is open,
|
||||
* close the connection.
|
||||
*/
|
||||
kForceClose(2);
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private final int value;
|
||||
|
||||
ConnectionStrategy(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from the numerical representation of kind to an enum type.
|
||||
*
|
||||
* @param kind The numerical representation of kind
|
||||
* @return The kind
|
||||
*/
|
||||
public static Kind getKindFromInt(int kind) {
|
||||
switch (kind) {
|
||||
case 1: return Kind.kUsb;
|
||||
case 2: return Kind.kHttp;
|
||||
case 4: return Kind.kCv;
|
||||
default: return Kind.kUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
protected VideoSource(int handle) {
|
||||
m_handle = handle;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void free() {
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
CameraServerJNI.releaseSource(m_handle);
|
||||
}
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
VideoSource source = (VideoSource) other;
|
||||
return m_handle == source.m_handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the kind of the source.
|
||||
*/
|
||||
public Kind getKind() {
|
||||
return getKindFromInt(CameraServerJNI.getSourceKind(m_handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the source. The name is an arbitrary identifier
|
||||
* provided when the source is created, and should be unique.
|
||||
*/
|
||||
public String getName() {
|
||||
return CameraServerJNI.getSourceName(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source description. This is source-kind specific.
|
||||
*/
|
||||
public String getDescription() {
|
||||
return CameraServerJNI.getSourceDescription(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last time a frame was captured.
|
||||
* @return Time in 1 us increments.
|
||||
*/
|
||||
public long getLastFrameTime() {
|
||||
return CameraServerJNI.getSourceLastFrameTime(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection strategy. By default, the source will automatically
|
||||
* connect or disconnect based on whether any sinks are connected.
|
||||
*
|
||||
* <p>This function is non-blocking; look for either a connection open or
|
||||
* close event or call {@link #isConnected()} to determine the connection
|
||||
* state.
|
||||
*
|
||||
* @param strategy connection strategy (auto, keep open, or force close)
|
||||
*/
|
||||
public void setConnectionStrategy(ConnectionStrategy strategy) {
|
||||
CameraServerJNI.setSourceConnectionStrategy(m_handle, strategy.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the source currently connected to whatever is providing the images.
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return CameraServerJNI.isSourceConnected(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets source enable status. This is determined with a combination of
|
||||
* connection strategy and the number of sinks connected.
|
||||
*
|
||||
* @return True if enabled, false otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return CameraServerJNI.isSourceEnabled(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a property.
|
||||
*
|
||||
* @param name Property name
|
||||
* @return Property contents (of kind Property::kNone if no property with
|
||||
* the given name exists)
|
||||
*/
|
||||
public VideoProperty getProperty(String name) {
|
||||
return new VideoProperty(CameraServerJNI.getSourceProperty(m_handle, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all properties of this source.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
||||
public VideoProperty[] enumerateProperties() {
|
||||
int[] handles = CameraServerJNI.enumerateSourceProperties(m_handle);
|
||||
VideoProperty[] rv = new VideoProperty[handles.length];
|
||||
for (int i = 0; i < handles.length; i++) {
|
||||
rv[i] = new VideoProperty(handles[i]);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current video mode.
|
||||
*/
|
||||
public VideoMode getVideoMode() {
|
||||
return CameraServerJNI.getSourceVideoMode(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the video mode.
|
||||
* @param mode Video mode
|
||||
*/
|
||||
public boolean setVideoMode(VideoMode mode) {
|
||||
return CameraServerJNI.setSourceVideoMode(m_handle,
|
||||
mode.pixelFormat.getValue(),
|
||||
mode.width,
|
||||
mode.height,
|
||||
mode.fps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the video mode.
|
||||
*
|
||||
* @param pixelFormat desired pixel format
|
||||
* @param width desired width
|
||||
* @param height desired height
|
||||
* @param fps desired FPS
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setVideoMode(VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
|
||||
return CameraServerJNI.setSourceVideoMode(m_handle, pixelFormat.getValue(), width, height, fps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pixel format.
|
||||
*
|
||||
* @param pixelFormat desired pixel format
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setPixelFormat(VideoMode.PixelFormat pixelFormat) {
|
||||
return CameraServerJNI.setSourcePixelFormat(m_handle, pixelFormat.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resolution.
|
||||
*
|
||||
* @param width desired width
|
||||
* @param height desired height
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setResolution(int width, int height) {
|
||||
return CameraServerJNI.setSourceResolution(m_handle, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the frames per second (FPS).
|
||||
*
|
||||
* @param fps desired FPS
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setFPS(int fps) {
|
||||
return CameraServerJNI.setSourceFPS(m_handle, fps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set video mode and properties from a JSON configuration string.
|
||||
*
|
||||
* <p>The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "pixel format": "MJPEG", "YUYV", etc
|
||||
* "width": video mode width
|
||||
* "height": video mode height
|
||||
* "fps": video mode fps
|
||||
* "brightness": percentage brightness
|
||||
* "white balance": "auto", "hold", or value
|
||||
* "exposure": "auto", "hold", or value
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setConfigJson(String config) {
|
||||
return CameraServerJNI.setSourceConfigJson(m_handle, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
public String getConfigJson() {
|
||||
return CameraServerJNI.getSourceConfigJson(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual FPS.
|
||||
*
|
||||
* <p>CameraServerJNI#setTelemetryPeriod() must be called for this to be valid
|
||||
* (throws VisionException if telemetry is not enabled).
|
||||
*
|
||||
* @return Actual FPS averaged over the telemetry period.
|
||||
*/
|
||||
public double getActualFPS() {
|
||||
return CameraServerJNI.getTelemetryAverageValue(m_handle,
|
||||
CameraServerJNI.TelemetryKind.kSourceFramesReceived);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data rate (in bytes per second).
|
||||
*
|
||||
* <p>CameraServerJNI#setTelemetryPeriod() must be called for this to be valid
|
||||
* (throws VisionException if telemetry is not enabled).
|
||||
*
|
||||
* @return Data rate averaged over the telemetry period.
|
||||
*/
|
||||
public double getActualDataRate() {
|
||||
return CameraServerJNI.getTelemetryAverageValue(m_handle,
|
||||
CameraServerJNI.TelemetryKind.kSourceBytesReceived);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all known video modes for this source.
|
||||
*/
|
||||
public VideoMode[] enumerateVideoModes() {
|
||||
return CameraServerJNI.enumerateSourceVideoModes(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all sinks connected to this source.
|
||||
*
|
||||
* @return Vector of sinks.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
||||
public VideoSink[] enumerateSinks() {
|
||||
int[] handles = CameraServerJNI.enumerateSourceSinks(m_handle);
|
||||
VideoSink[] rv = new VideoSink[handles.length];
|
||||
for (int i = 0; i < handles.length; i++) {
|
||||
rv[i] = new VideoSink(handles[i]);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all existing sources.
|
||||
*
|
||||
* @return Vector of sources.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
||||
public static VideoSource[] enumerateSources() {
|
||||
int[] handles = CameraServerJNI.enumerateSources();
|
||||
VideoSource[] rv = new VideoSource[handles.length];
|
||||
for (int i = 0; i < handles.length; i++) {
|
||||
rv[i] = new VideoSource(handles[i]);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
protected int m_handle;
|
||||
}
|
||||
250
cscore/src/main/native/cpp/CvSinkImpl.cpp
Normal file
250
cscore/src/main/native/cpp/CvSinkImpl.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "CvSinkImpl.h"
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
#include <wpi/SmallString.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "c_util.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
CvSinkImpl::CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry)
|
||||
: SinkImpl{name, logger, notifier, telemetry} {
|
||||
m_active = true;
|
||||
// m_thread = std::thread(&CvSinkImpl::ThreadMain, this);
|
||||
}
|
||||
|
||||
CvSinkImpl::CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
std::function<void(uint64_t time)> processFrame)
|
||||
: SinkImpl{name, logger, notifier, telemetry} {}
|
||||
|
||||
CvSinkImpl::~CvSinkImpl() { Stop(); }
|
||||
|
||||
void CvSinkImpl::Stop() {
|
||||
m_active = false;
|
||||
|
||||
// wake up any waiters by forcing an empty frame to be sent
|
||||
if (auto source = GetSource()) source->Wakeup();
|
||||
|
||||
// join thread
|
||||
if (m_thread.joinable()) m_thread.join();
|
||||
}
|
||||
|
||||
uint64_t CvSinkImpl::GrabFrame(cv::Mat& image) {
|
||||
SetEnabled(true);
|
||||
|
||||
auto source = GetSource();
|
||||
if (!source) {
|
||||
// Source disconnected; sleep for one second
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto frame = source->GetNextFrame(); // blocks
|
||||
if (!frame) {
|
||||
// Bad frame; sleep for 20 ms so we don't consume all processor time.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
return 0; // signal error
|
||||
}
|
||||
|
||||
if (!frame.GetCv(image)) {
|
||||
// Shouldn't happen, but just in case...
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return frame.GetTime();
|
||||
}
|
||||
|
||||
uint64_t CvSinkImpl::GrabFrame(cv::Mat& image, double timeout) {
|
||||
SetEnabled(true);
|
||||
|
||||
auto source = GetSource();
|
||||
if (!source) {
|
||||
// Source disconnected; sleep for one second
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto frame = source->GetNextFrame(timeout); // blocks
|
||||
if (!frame) {
|
||||
// Bad frame; sleep for 20 ms so we don't consume all processor time.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
return 0; // signal error
|
||||
}
|
||||
|
||||
if (!frame.GetCv(image)) {
|
||||
// Shouldn't happen, but just in case...
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return frame.GetTime();
|
||||
}
|
||||
|
||||
// Send HTTP response and a stream of JPG-frames
|
||||
void CvSinkImpl::ThreadMain() {
|
||||
Enable();
|
||||
while (m_active) {
|
||||
auto source = GetSource();
|
||||
if (!source) {
|
||||
// Source disconnected; sleep for one second
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(); // blocks
|
||||
if (!m_active) break;
|
||||
if (!frame) {
|
||||
// Bad frame; sleep for 10 ms so we don't consume all processor time.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
continue;
|
||||
}
|
||||
// TODO m_processFrame();
|
||||
}
|
||||
Disable();
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
CS_Sink CreateCvSink(const wpi::Twine& name, CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
return inst.CreateSink(
|
||||
CS_SINK_CV, std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
|
||||
inst.telemetry));
|
||||
}
|
||||
|
||||
CS_Sink CreateCvSinkCallback(const wpi::Twine& name,
|
||||
std::function<void(uint64_t time)> processFrame,
|
||||
CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
return inst.CreateSink(
|
||||
CS_SINK_CV, std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
|
||||
inst.telemetry, processFrame));
|
||||
}
|
||||
|
||||
void SetSinkDescription(CS_Sink sink, const wpi::Twine& description,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSinkImpl&>(*data->sink).SetDescription(description);
|
||||
}
|
||||
|
||||
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image);
|
||||
}
|
||||
|
||||
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image, timeout);
|
||||
}
|
||||
|
||||
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
return static_cast<CvSinkImpl&>(*data->sink).GetError();
|
||||
}
|
||||
|
||||
wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::StringRef{};
|
||||
}
|
||||
return static_cast<CvSinkImpl&>(*data->sink).GetError(buf);
|
||||
}
|
||||
|
||||
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSinkImpl&>(*data->sink).SetEnabled(enabled);
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_Sink CS_CreateCvSink(const char* name, CS_Status* status) {
|
||||
return cs::CreateCvSink(name, status);
|
||||
}
|
||||
|
||||
CS_Sink CS_CreateCvSinkCallback(const char* name, void* data,
|
||||
void (*processFrame)(void* data, uint64_t time),
|
||||
CS_Status* status) {
|
||||
return cs::CreateCvSinkCallback(
|
||||
name, [=](uint64_t time) { processFrame(data, time); }, status);
|
||||
}
|
||||
|
||||
void CS_SetSinkDescription(CS_Sink sink, const char* description,
|
||||
CS_Status* status) {
|
||||
return cs::SetSinkDescription(sink, description, status);
|
||||
}
|
||||
|
||||
uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image,
|
||||
CS_Status* status) {
|
||||
auto mat = cv::cvarrToMat(image);
|
||||
return cs::GrabSinkFrame(sink, mat, status);
|
||||
}
|
||||
|
||||
uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image,
|
||||
double timeout, CS_Status* status) {
|
||||
auto mat = cv::cvarrToMat(image);
|
||||
return cs::GrabSinkFrameTimeout(sink, mat, timeout, status);
|
||||
}
|
||||
|
||||
uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status) {
|
||||
return cs::GrabSinkFrame(sink, *image, status);
|
||||
}
|
||||
|
||||
uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
|
||||
double timeout, CS_Status* status) {
|
||||
return cs::GrabSinkFrameTimeout(sink, *image, timeout, status);
|
||||
}
|
||||
|
||||
char* CS_GetSinkError(CS_Sink sink, CS_Status* status) {
|
||||
wpi::SmallString<128> buf;
|
||||
auto str = cs::GetSinkError(sink, buf, status);
|
||||
if (*status != 0) return nullptr;
|
||||
return cs::ConvertToC(str);
|
||||
}
|
||||
|
||||
void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status) {
|
||||
return cs::SetSinkEnabled(sink, enabled, status);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
52
cscore/src/main/native/cpp/CvSinkImpl.h
Normal file
52
cscore/src/main/native/cpp/CvSinkImpl.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_CVSINKIMPL_H_
|
||||
#define CSCORE_CVSINKIMPL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
|
||||
#include "Frame.h"
|
||||
#include "SinkImpl.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SourceImpl;
|
||||
|
||||
class CvSinkImpl : public SinkImpl {
|
||||
public:
|
||||
CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry);
|
||||
CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry,
|
||||
std::function<void(uint64_t time)> processFrame);
|
||||
~CvSinkImpl() override;
|
||||
|
||||
void Stop();
|
||||
|
||||
uint64_t GrabFrame(cv::Mat& image);
|
||||
uint64_t GrabFrame(cv::Mat& image, double timeout);
|
||||
|
||||
private:
|
||||
void ThreadMain();
|
||||
|
||||
std::atomic_bool m_active; // set to false to terminate threads
|
||||
std::thread m_thread;
|
||||
std::function<void(uint64_t time)> m_processFrame;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_CVSINKIMPL_H_
|
||||
312
cscore/src/main/native/cpp/CvSourceImpl.cpp
Normal file
312
cscore/src/main/native/cpp/CvSourceImpl.cpp
Normal file
@@ -0,0 +1,312 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "CvSourceImpl.h"
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "c_util.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
CvSourceImpl::CvSourceImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
const VideoMode& mode)
|
||||
: SourceImpl{name, logger, notifier, telemetry} {
|
||||
m_mode = mode;
|
||||
m_videoModes.push_back(m_mode);
|
||||
}
|
||||
|
||||
CvSourceImpl::~CvSourceImpl() {}
|
||||
|
||||
void CvSourceImpl::Start() {
|
||||
m_notifier.NotifySource(*this, CS_SOURCE_CONNECTED);
|
||||
m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
|
||||
m_notifier.NotifySourceVideoMode(*this, m_mode);
|
||||
}
|
||||
|
||||
bool CvSourceImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_mode = mode;
|
||||
m_videoModes[0] = mode;
|
||||
}
|
||||
m_notifier.NotifySourceVideoMode(*this, mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CvSourceImpl::NumSinksChanged() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
void CvSourceImpl::NumSinksEnabledChanged() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
void CvSourceImpl::PutFrame(cv::Mat& image) {
|
||||
// We only support 8-bit images; convert if necessary.
|
||||
cv::Mat finalImage;
|
||||
if (image.depth() == CV_8U)
|
||||
finalImage = image;
|
||||
else
|
||||
image.convertTo(finalImage, CV_8U);
|
||||
|
||||
std::unique_ptr<Image> dest;
|
||||
switch (image.channels()) {
|
||||
case 1:
|
||||
dest =
|
||||
AllocImage(VideoMode::kGray, image.cols, image.rows, image.total());
|
||||
finalImage.copyTo(dest->AsMat());
|
||||
break;
|
||||
case 3:
|
||||
dest = AllocImage(VideoMode::kBGR, image.cols, image.rows,
|
||||
image.total() * 3);
|
||||
finalImage.copyTo(dest->AsMat());
|
||||
break;
|
||||
case 4:
|
||||
dest = AllocImage(VideoMode::kBGR, image.cols, image.rows,
|
||||
image.total() * 3);
|
||||
cv::cvtColor(finalImage, dest->AsMat(), cv::COLOR_BGRA2BGR);
|
||||
break;
|
||||
default:
|
||||
SERROR("PutFrame: " << image.channels()
|
||||
<< "-channel images not supported");
|
||||
return;
|
||||
}
|
||||
SourceImpl::PutFrame(std::move(dest), wpi::Now());
|
||||
}
|
||||
|
||||
void CvSourceImpl::NotifyError(const wpi::Twine& msg) {
|
||||
PutError(msg, wpi::Now());
|
||||
}
|
||||
|
||||
int CvSourceImpl::CreateProperty(const wpi::Twine& name, CS_PropertyKind kind,
|
||||
int minimum, int maximum, int step,
|
||||
int defaultValue, int value) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
int ndx = CreateOrUpdateProperty(name,
|
||||
[=] {
|
||||
return wpi::make_unique<PropertyImpl>(
|
||||
name, kind, minimum, maximum, step,
|
||||
defaultValue, value);
|
||||
},
|
||||
[&](PropertyImpl& prop) {
|
||||
// update all but value
|
||||
prop.propKind = kind;
|
||||
prop.minimum = minimum;
|
||||
prop.maximum = maximum;
|
||||
prop.step = step;
|
||||
prop.defaultValue = defaultValue;
|
||||
value = prop.value;
|
||||
});
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name, ndx,
|
||||
kind, value, wpi::Twine{});
|
||||
return ndx;
|
||||
}
|
||||
|
||||
int CvSourceImpl::CreateProperty(
|
||||
const wpi::Twine& name, CS_PropertyKind kind, int minimum, int maximum,
|
||||
int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange) {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CvSourceImpl::SetEnumPropertyChoices(int property,
|
||||
wpi::ArrayRef<std::string> choices,
|
||||
CS_Status* status) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
if (prop->propKind != CS_PROP_ENUM) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return;
|
||||
}
|
||||
prop->enumChoices = choices;
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
|
||||
prop->name, property, CS_PROP_ENUM,
|
||||
prop->value, wpi::Twine{});
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
CS_Source CreateCvSource(const wpi::Twine& name, const VideoMode& mode,
|
||||
CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
return inst.CreateSource(CS_SOURCE_CV, std::make_shared<CvSourceImpl>(
|
||||
name, inst.logger, inst.notifier,
|
||||
inst.telemetry, mode));
|
||||
}
|
||||
|
||||
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSourceImpl&>(*data->source).PutFrame(image);
|
||||
}
|
||||
|
||||
void NotifySourceError(CS_Source source, const wpi::Twine& msg,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSourceImpl&>(*data->source).NotifyError(msg);
|
||||
}
|
||||
|
||||
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSourceImpl&>(*data->source).SetConnected(connected);
|
||||
}
|
||||
|
||||
void SetSourceDescription(CS_Source source, const wpi::Twine& description,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<CvSourceImpl&>(*data->source).SetDescription(description);
|
||||
}
|
||||
|
||||
CS_Property CreateSourceProperty(CS_Source source, const wpi::Twine& name,
|
||||
CS_PropertyKind kind, int minimum, int maximum,
|
||||
int step, int defaultValue, int value,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return -1;
|
||||
}
|
||||
int property = static_cast<CvSourceImpl&>(*data->source)
|
||||
.CreateProperty(name, kind, minimum, maximum, step,
|
||||
defaultValue, value);
|
||||
return Handle{source, property, Handle::kProperty};
|
||||
}
|
||||
|
||||
CS_Property CreateSourcePropertyCallback(
|
||||
CS_Source source, const wpi::Twine& name, CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return -1;
|
||||
}
|
||||
int property = static_cast<CvSourceImpl&>(*data->source)
|
||||
.CreateProperty(name, kind, minimum, maximum, step,
|
||||
defaultValue, value, onChange);
|
||||
return Handle{source, property, Handle::kProperty};
|
||||
}
|
||||
|
||||
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
wpi::ArrayRef<std::string> choices,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get property index; also validate the source owns this property
|
||||
Handle handle{property};
|
||||
int i = handle.GetParentIndex();
|
||||
if (i < 0) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
auto data2 = Instance::GetInstance().GetSource(Handle{i, Handle::kSource});
|
||||
if (!data2 || data->source.get() != data2->source.get()) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
int propertyIndex = handle.GetIndex();
|
||||
static_cast<CvSourceImpl&>(*data->source)
|
||||
.SetEnumPropertyChoices(propertyIndex, choices, status);
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode,
|
||||
CS_Status* status) {
|
||||
return cs::CreateCvSource(name, static_cast<const cs::VideoMode&>(*mode),
|
||||
status);
|
||||
}
|
||||
|
||||
void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
|
||||
CS_Status* status) {
|
||||
auto mat = cv::cvarrToMat(image);
|
||||
return cs::PutSourceFrame(source, mat, status);
|
||||
}
|
||||
|
||||
void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status) {
|
||||
return cs::PutSourceFrame(source, *image, status);
|
||||
}
|
||||
|
||||
void CS_NotifySourceError(CS_Source source, const char* msg,
|
||||
CS_Status* status) {
|
||||
return cs::NotifySourceError(source, msg, status);
|
||||
}
|
||||
|
||||
void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
|
||||
CS_Status* status) {
|
||||
return cs::SetSourceConnected(source, connected, status);
|
||||
}
|
||||
|
||||
void CS_SetSourceDescription(CS_Source source, const char* description,
|
||||
CS_Status* status) {
|
||||
return cs::SetSourceDescription(source, description, status);
|
||||
}
|
||||
|
||||
CS_Property CS_CreateSourceProperty(CS_Source source, const char* name,
|
||||
enum CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue,
|
||||
int value, CS_Status* status) {
|
||||
return cs::CreateSourceProperty(source, name, kind, minimum, maximum, step,
|
||||
defaultValue, value, status);
|
||||
}
|
||||
|
||||
CS_Property CS_CreateSourcePropertyCallback(
|
||||
CS_Source source, const char* name, enum CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value, void* data,
|
||||
void (*onChange)(void* data, CS_Property property), CS_Status* status) {
|
||||
return cs::CreateSourcePropertyCallback(
|
||||
source, name, kind, minimum, maximum, step, defaultValue, value,
|
||||
[=](CS_Property property) { onChange(data, property); }, status);
|
||||
}
|
||||
|
||||
void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
const char** choices, int count,
|
||||
CS_Status* status) {
|
||||
wpi::SmallVector<std::string, 8> vec;
|
||||
vec.reserve(count);
|
||||
for (int i = 0; i < count; ++i) vec.push_back(choices[i]);
|
||||
return cs::SetSourceEnumPropertyChoices(source, property, vec, status);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
55
cscore/src/main/native/cpp/CvSourceImpl.h
Normal file
55
cscore/src/main/native/cpp/CvSourceImpl.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_CVSOURCEIMPL_H_
|
||||
#define CSCORE_CVSOURCEIMPL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <wpi/ArrayRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class CvSourceImpl : public SourceImpl {
|
||||
public:
|
||||
CvSourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry, const VideoMode& mode);
|
||||
~CvSourceImpl() override;
|
||||
|
||||
void Start() override;
|
||||
|
||||
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
|
||||
|
||||
void NumSinksChanged() override;
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
// OpenCV-specific functions
|
||||
void PutFrame(cv::Mat& image);
|
||||
void NotifyError(const wpi::Twine& msg);
|
||||
int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value);
|
||||
int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange);
|
||||
void SetEnumPropertyChoices(int property, wpi::ArrayRef<std::string> choices,
|
||||
CS_Status* status);
|
||||
|
||||
private:
|
||||
std::atomic_bool m_connected{true};
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_CVSOURCEIMPL_H_
|
||||
499
cscore/src/main/native/cpp/Frame.cpp
Normal file
499
cscore/src/main/native/cpp/Frame.cpp
Normal file
@@ -0,0 +1,499 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Frame.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
#include "SourceImpl.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
Frame::Frame(SourceImpl& source, const wpi::Twine& error, Time time)
|
||||
: m_impl{source.AllocFrameImpl().release()} {
|
||||
m_impl->refcount = 1;
|
||||
m_impl->error = error.str();
|
||||
m_impl->time = time;
|
||||
}
|
||||
|
||||
Frame::Frame(SourceImpl& source, std::unique_ptr<Image> image, Time time)
|
||||
: m_impl{source.AllocFrameImpl().release()} {
|
||||
m_impl->refcount = 1;
|
||||
m_impl->error.resize(0);
|
||||
m_impl->time = time;
|
||||
m_impl->images.push_back(image.release());
|
||||
}
|
||||
|
||||
Image* Frame::GetNearestImage(int width, int height) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
Image* found = nullptr;
|
||||
|
||||
// Ideally we want the smallest image at least width/height in size
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->IsLarger(width, height) && (!found || (i->IsSmaller(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// Find the largest image (will be less than width/height)
|
||||
for (auto i : m_impl->images) {
|
||||
if (!found || (i->IsLarger(*found))) found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// Shouldn't reach this, but just in case...
|
||||
return m_impl->images.empty() ? nullptr : m_impl->images[0];
|
||||
}
|
||||
|
||||
Image* Frame::GetNearestImage(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat,
|
||||
int jpegQuality) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
Image* found = nullptr;
|
||||
|
||||
// We want the smallest image at least width/height (or the next largest),
|
||||
// but the primary search order is in order of conversion cost.
|
||||
// If we don't find exactly what we want, we prefer non-JPEG source images
|
||||
// (because JPEG source images require a decompression step).
|
||||
// While the searching takes a little time, it pales in comparison to the
|
||||
// image processing to come, so it's worth spending a little extra time
|
||||
// looking for the most efficient conversion.
|
||||
|
||||
// 1) Same width, height, pixelFormat, and (possibly) JPEG quality
|
||||
// (e.g. exactly what we want)
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height, pixelFormat, jpegQuality)) return i;
|
||||
}
|
||||
|
||||
// 2) Same width, height, different (but non-JPEG) pixelFormat (color conv)
|
||||
// 2a) If we want JPEG output, prefer BGR over other pixel formats
|
||||
if (pixelFormat == VideoMode::kMJPEG) {
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height, VideoMode::kBGR)) return i;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height) && i->pixelFormat != VideoMode::kMJPEG) return i;
|
||||
}
|
||||
|
||||
// 3) Different width, height, same pixelFormat (only if non-JPEG) (resample)
|
||||
if (pixelFormat != VideoMode::kMJPEG) {
|
||||
// 3a) Smallest image at least width/height in size
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->IsLarger(width, height) && i->pixelFormat == pixelFormat &&
|
||||
(!found || (i->IsSmaller(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// 3b) Largest image (less than width/height)
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->pixelFormat == pixelFormat && (!found || (i->IsLarger(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
}
|
||||
|
||||
// 4) Different width, height, different (but non-JPEG) pixelFormat
|
||||
// (color conversion + resample)
|
||||
// 4a) Smallest image at least width/height in size
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->IsLarger(width, height) && i->pixelFormat != VideoMode::kMJPEG &&
|
||||
(!found || (i->IsSmaller(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// 4b) Largest image (less than width/height)
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->pixelFormat != VideoMode::kMJPEG &&
|
||||
(!found || (i->IsLarger(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// 5) Same width, height, JPEG pixelFormat (decompression). As there may be
|
||||
// multiple JPEG images, find the highest quality one.
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height, VideoMode::kMJPEG) &&
|
||||
(!found || i->jpegQuality > found->jpegQuality)) {
|
||||
found = i;
|
||||
// consider one without a quality setting to be the highest quality
|
||||
// (e.g. directly from the camera)
|
||||
if (i->jpegQuality == -1) break;
|
||||
}
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// 6) Different width, height, JPEG pixelFormat (decompression)
|
||||
// 6a) Smallest image at least width/height in size
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->IsLarger(width, height) && i->pixelFormat == VideoMode::kMJPEG &&
|
||||
(!found || (i->IsSmaller(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// 6b) Largest image (less than width/height)
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->pixelFormat != VideoMode::kMJPEG &&
|
||||
(!found || (i->IsLarger(*found))))
|
||||
found = i;
|
||||
}
|
||||
if (found) return found;
|
||||
|
||||
// Shouldn't reach this, but just in case...
|
||||
return m_impl->images.empty() ? nullptr : m_impl->images[0];
|
||||
}
|
||||
|
||||
Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
|
||||
int requiredJpegQuality, int defaultJpegQuality) {
|
||||
if (!image ||
|
||||
image->Is(image->width, image->height, pixelFormat, requiredJpegQuality))
|
||||
return image;
|
||||
Image* cur = image;
|
||||
|
||||
// If the source image is a JPEG, we need to decode it before we can do
|
||||
// anything else with it. Note that if the destination format is JPEG, we
|
||||
// still need to do this (unless it was already a JPEG, in which case we
|
||||
// would have returned above).
|
||||
if (cur->pixelFormat == VideoMode::kMJPEG) {
|
||||
cur = ConvertMJPEGToBGR(cur);
|
||||
if (pixelFormat == VideoMode::kBGR) return cur;
|
||||
}
|
||||
|
||||
// Color convert
|
||||
switch (pixelFormat) {
|
||||
case VideoMode::kRGB565:
|
||||
// If source is YUYV or Gray, need to convert to BGR first
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
||||
cur = newImage;
|
||||
else
|
||||
cur = ConvertYUYVToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kGray) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
||||
cur = newImage;
|
||||
else
|
||||
cur = ConvertGrayToBGR(cur);
|
||||
}
|
||||
return ConvertBGRToRGB565(cur);
|
||||
case VideoMode::kGray:
|
||||
// If source is YUYV or RGB565, need to convert to BGR first
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
||||
cur = newImage;
|
||||
else
|
||||
cur = ConvertYUYVToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kRGB565) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
||||
cur = newImage;
|
||||
else
|
||||
cur = ConvertRGB565ToBGR(cur);
|
||||
}
|
||||
return ConvertBGRToGray(cur);
|
||||
case VideoMode::kBGR:
|
||||
case VideoMode::kMJPEG:
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
cur = ConvertYUYVToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kRGB565) {
|
||||
cur = ConvertRGB565ToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kGray) {
|
||||
if (pixelFormat == VideoMode::kBGR)
|
||||
return ConvertGrayToBGR(cur);
|
||||
else
|
||||
return ConvertGrayToMJPEG(cur, defaultJpegQuality);
|
||||
}
|
||||
break;
|
||||
case VideoMode::kYUYV:
|
||||
default:
|
||||
return nullptr; // Unsupported
|
||||
}
|
||||
|
||||
// Compress if destination is JPEG
|
||||
if (pixelFormat == VideoMode::kMJPEG)
|
||||
cur = ConvertBGRToMJPEG(cur, defaultJpegQuality);
|
||||
|
||||
return cur;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertMJPEGToBGR(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kMJPEG) return nullptr;
|
||||
|
||||
// Allocate an BGR image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
||||
image->width * image->height * 3);
|
||||
|
||||
// Decode
|
||||
cv::Mat newMat = newImage->AsMat();
|
||||
cv::imdecode(image->AsInputArray(), cv::IMREAD_COLOR, &newMat);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertMJPEGToGray(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kMJPEG) return nullptr;
|
||||
|
||||
// Allocate an grayscale image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
|
||||
image->width * image->height);
|
||||
|
||||
// Decode
|
||||
cv::Mat newMat = newImage->AsMat();
|
||||
cv::imdecode(image->AsInputArray(), cv::IMREAD_GRAYSCALE, &newMat);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertYUYVToBGR(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kYUYV) return nullptr;
|
||||
|
||||
// Allocate a BGR image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
||||
image->width * image->height * 3);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2BGR_YUYV);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertBGRToRGB565(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
||||
|
||||
// Allocate a RGB565 image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kRGB565, image->width, image->height,
|
||||
image->width * image->height * 2);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_RGB2BGR565);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertRGB565ToBGR(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kRGB565) return nullptr;
|
||||
|
||||
// Allocate a BGR image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
||||
image->width * image->height * 3);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR5652RGB);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertBGRToGray(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
||||
|
||||
// Allocate a Grayscale image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
|
||||
image->width * image->height);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR2GRAY);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertGrayToBGR(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
||||
|
||||
// Allocate a BGR image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
||||
image->width * image->height * 3);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_GRAY2BGR);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertBGRToMJPEG(Image* image, int quality) {
|
||||
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
|
||||
// Allocate a JPEG image. We don't actually know what the resulting size
|
||||
// will be; while the destination will automatically grow, doing so will
|
||||
// cause an extra malloc, so we don't want to be too conservative here.
|
||||
// Per Wikipedia, Q=100 on a sample image results in 8.25 bits per pixel,
|
||||
// this is a little bit more conservative in assuming 50% space savings over
|
||||
// the equivalent BGR image.
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kMJPEG, image->width, image->height,
|
||||
image->width * image->height * 1.5);
|
||||
|
||||
// Compress
|
||||
if (m_impl->compressionParams.empty()) {
|
||||
m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
|
||||
m_impl->compressionParams.push_back(quality);
|
||||
} else {
|
||||
m_impl->compressionParams[1] = quality;
|
||||
}
|
||||
cv::imencode(".jpg", image->AsMat(), newImage->vec(),
|
||||
m_impl->compressionParams);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
m_impl->images.push_back(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
|
||||
if (!image || image->pixelFormat != VideoMode::kGray) return nullptr;
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
|
||||
// Allocate a JPEG image. We don't actually know what the resulting size
|
||||
// will be; while the destination will automatically grow, doing so will
|
||||
// cause an extra malloc, so we don't want to be too conservative here.
|
||||
// Per Wikipedia, Q=100 on a sample image results in 8.25 bits per pixel,
|
||||
// this is a little bit more conservative in assuming 25% space savings over
|
||||
// the equivalent grayscale image.
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kMJPEG, image->width, image->height,
|
||||
image->width * image->height * 0.75);
|
||||
|
||||
// Compress
|
||||
if (m_impl->compressionParams.empty()) {
|
||||
m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
|
||||
m_impl->compressionParams.push_back(quality);
|
||||
} else {
|
||||
m_impl->compressionParams[1] = quality;
|
||||
}
|
||||
cv::imencode(".jpg", image->AsMat(), newImage->vec(),
|
||||
m_impl->compressionParams);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
m_impl->images.push_back(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::GetImageImpl(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat,
|
||||
int requiredJpegQuality, int defaultJpegQuality) {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
Image* cur = GetNearestImage(width, height, pixelFormat, requiredJpegQuality);
|
||||
if (!cur || cur->Is(width, height, pixelFormat, requiredJpegQuality))
|
||||
return cur;
|
||||
|
||||
WPI_DEBUG4(Instance::GetInstance().logger,
|
||||
"converting image from " << cur->width << "x" << cur->height
|
||||
<< " type " << cur->pixelFormat << " to "
|
||||
<< width << "x" << height << " type "
|
||||
<< pixelFormat);
|
||||
|
||||
// If the source image is a JPEG, we need to decode it before we can do
|
||||
// anything else with it. Note that if the destination format is JPEG, we
|
||||
// still need to do this (unless the width/height/compression were the same,
|
||||
// in which case we already returned the existing JPEG above).
|
||||
if (cur->pixelFormat == VideoMode::kMJPEG) cur = ConvertMJPEGToBGR(cur);
|
||||
|
||||
// Resize
|
||||
if (!cur->Is(width, height)) {
|
||||
// Allocate an image.
|
||||
auto newImage = m_impl->source.AllocImage(
|
||||
cur->pixelFormat, width, height,
|
||||
width * height * (cur->size() / (cur->width * cur->height)));
|
||||
|
||||
// Resize
|
||||
cv::Mat newMat = newImage->AsMat();
|
||||
cv::resize(cur->AsMat(), newMat, newMat.size(), 0, 0);
|
||||
|
||||
// Save the result
|
||||
cur = newImage.release();
|
||||
m_impl->images.push_back(cur);
|
||||
}
|
||||
|
||||
// Convert to output format
|
||||
return ConvertImpl(cur, pixelFormat, requiredJpegQuality, defaultJpegQuality);
|
||||
}
|
||||
|
||||
bool Frame::GetCv(cv::Mat& image, int width, int height) {
|
||||
Image* rawImage = GetImage(width, height, VideoMode::kBGR);
|
||||
if (!rawImage) return false;
|
||||
rawImage->AsMat().copyTo(image);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Frame::ReleaseFrame() {
|
||||
for (auto image : m_impl->images)
|
||||
m_impl->source.ReleaseImage(std::unique_ptr<Image>(image));
|
||||
m_impl->images.clear();
|
||||
m_impl->source.ReleaseFrameImpl(std::unique_ptr<Impl>(m_impl));
|
||||
m_impl = nullptr;
|
||||
}
|
||||
200
cscore/src/main/native/cpp/Frame.h
Normal file
200
cscore/src/main/native/cpp/Frame.h
Normal file
@@ -0,0 +1,200 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_FRAME_H_
|
||||
#define CSCORE_FRAME_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "Image.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SourceImpl;
|
||||
|
||||
class Frame {
|
||||
friend class SourceImpl;
|
||||
|
||||
public:
|
||||
using Time = uint64_t;
|
||||
|
||||
private:
|
||||
struct Impl {
|
||||
explicit Impl(SourceImpl& source_) : source(source_) {}
|
||||
|
||||
wpi::recursive_mutex mutex;
|
||||
std::atomic_int refcount{0};
|
||||
Time time{0};
|
||||
SourceImpl& source;
|
||||
std::string error;
|
||||
wpi::SmallVector<Image*, 4> images;
|
||||
std::vector<int> compressionParams;
|
||||
};
|
||||
|
||||
public:
|
||||
Frame() noexcept : m_impl{nullptr} {}
|
||||
|
||||
Frame(SourceImpl& source, const wpi::Twine& error, Time time);
|
||||
|
||||
Frame(SourceImpl& source, std::unique_ptr<Image> image, Time time);
|
||||
|
||||
Frame(const Frame& frame) noexcept : m_impl{frame.m_impl} {
|
||||
if (m_impl) ++m_impl->refcount;
|
||||
}
|
||||
|
||||
Frame(Frame&& other) noexcept : Frame() { swap(*this, other); }
|
||||
|
||||
~Frame() { DecRef(); }
|
||||
|
||||
Frame& operator=(Frame other) noexcept {
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return m_impl && m_impl->error.empty(); }
|
||||
|
||||
friend void swap(Frame& first, Frame& second) noexcept {
|
||||
using std::swap;
|
||||
swap(first.m_impl, second.m_impl);
|
||||
}
|
||||
|
||||
Time GetTime() const { return m_impl ? m_impl->time : 0; }
|
||||
|
||||
wpi::StringRef GetError() const {
|
||||
if (!m_impl) return wpi::StringRef{};
|
||||
return m_impl->error;
|
||||
}
|
||||
|
||||
int GetOriginalWidth() const {
|
||||
if (!m_impl) return 0;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
if (m_impl->images.empty()) return 0;
|
||||
return m_impl->images[0]->width;
|
||||
}
|
||||
|
||||
int GetOriginalHeight() const {
|
||||
if (!m_impl) return 0;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
if (m_impl->images.empty()) return 0;
|
||||
return m_impl->images[0]->height;
|
||||
}
|
||||
|
||||
int GetOriginalPixelFormat() const {
|
||||
if (!m_impl) return 0;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
if (m_impl->images.empty()) return 0;
|
||||
return m_impl->images[0]->pixelFormat;
|
||||
}
|
||||
|
||||
int GetOriginalJpegQuality() const {
|
||||
if (!m_impl) return 0;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
if (m_impl->images.empty()) return 0;
|
||||
return m_impl->images[0]->jpegQuality;
|
||||
}
|
||||
|
||||
Image* GetExistingImage(size_t i = 0) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
if (i >= m_impl->images.size()) return nullptr;
|
||||
return m_impl->images[i];
|
||||
}
|
||||
|
||||
Image* GetExistingImage(int width, int height) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height)) return i;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Image* GetExistingImage(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height, pixelFormat)) return i;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Image* GetExistingImage(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat,
|
||||
int jpegQuality) const {
|
||||
if (!m_impl) return nullptr;
|
||||
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
|
||||
for (auto i : m_impl->images) {
|
||||
if (i->Is(width, height, pixelFormat, jpegQuality)) return i;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Image* GetNearestImage(int width, int height) const;
|
||||
Image* GetNearestImage(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat,
|
||||
int jpegQuality = -1) const;
|
||||
|
||||
Image* Convert(Image* image, VideoMode::PixelFormat pixelFormat) {
|
||||
if (pixelFormat == VideoMode::kMJPEG) return nullptr;
|
||||
return ConvertImpl(image, pixelFormat, -1, 80);
|
||||
}
|
||||
Image* ConvertToMJPEG(Image* image, int requiredQuality,
|
||||
int defaultQuality = 80) {
|
||||
return ConvertImpl(image, VideoMode::kMJPEG, requiredQuality,
|
||||
defaultQuality);
|
||||
}
|
||||
Image* ConvertMJPEGToBGR(Image* image);
|
||||
Image* ConvertMJPEGToGray(Image* image);
|
||||
Image* ConvertYUYVToBGR(Image* image);
|
||||
Image* ConvertBGRToRGB565(Image* image);
|
||||
Image* ConvertRGB565ToBGR(Image* image);
|
||||
Image* ConvertBGRToGray(Image* image);
|
||||
Image* ConvertGrayToBGR(Image* image);
|
||||
Image* ConvertBGRToMJPEG(Image* image, int quality);
|
||||
Image* ConvertGrayToMJPEG(Image* image, int quality);
|
||||
|
||||
Image* GetImage(int width, int height, VideoMode::PixelFormat pixelFormat) {
|
||||
if (pixelFormat == VideoMode::kMJPEG) return nullptr;
|
||||
return GetImageImpl(width, height, pixelFormat, -1, 80);
|
||||
}
|
||||
Image* GetImageMJPEG(int width, int height, int requiredQuality,
|
||||
int defaultQuality = 80) {
|
||||
return GetImageImpl(width, height, VideoMode::kMJPEG, requiredQuality,
|
||||
defaultQuality);
|
||||
}
|
||||
|
||||
bool GetCv(cv::Mat& image) {
|
||||
return GetCv(image, GetOriginalWidth(), GetOriginalHeight());
|
||||
}
|
||||
bool GetCv(cv::Mat& image, int width, int height);
|
||||
|
||||
private:
|
||||
Image* ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
|
||||
int requiredJpegQuality, int defaultJpegQuality);
|
||||
Image* GetImageImpl(int width, int height, VideoMode::PixelFormat pixelFormat,
|
||||
int requiredJpegQuality, int defaultJpegQuality);
|
||||
void DecRef() {
|
||||
if (m_impl && --(m_impl->refcount) == 0) ReleaseFrame();
|
||||
}
|
||||
void ReleaseFrame();
|
||||
|
||||
Impl* m_impl;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_FRAME_H_
|
||||
69
cscore/src/main/native/cpp/Handle.h
Normal file
69
cscore/src/main/native/cpp/Handle.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_HANDLE_H_
|
||||
#define CSCORE_HANDLE_H_
|
||||
|
||||
#include "cscore_c.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
// Handle data layout:
|
||||
// Bits 0-15: Handle index
|
||||
// Bits 16-23: Parent index (property only)
|
||||
// Bits 24-30: Type
|
||||
|
||||
class Handle {
|
||||
public:
|
||||
enum Type {
|
||||
kUndefined = 0,
|
||||
kProperty = 0x40,
|
||||
kSource,
|
||||
kSink,
|
||||
kListener,
|
||||
kSinkProperty
|
||||
};
|
||||
enum { kIndexMax = 0xffff };
|
||||
|
||||
Handle(CS_Handle handle) : m_handle(handle) {} // NOLINT
|
||||
operator CS_Handle() const { return m_handle; }
|
||||
|
||||
Handle(int index, Type type) {
|
||||
if (index < 0) {
|
||||
m_handle = 0;
|
||||
return;
|
||||
}
|
||||
m_handle = ((static_cast<int>(type) & 0x7f) << 24) | (index & 0xffff);
|
||||
}
|
||||
Handle(int index, int property, Type type) {
|
||||
if (index < 0 || property < 0) {
|
||||
m_handle = 0;
|
||||
return;
|
||||
}
|
||||
m_handle = ((static_cast<int>(type) & 0x7f) << 24) |
|
||||
((index & 0xff) << 16) | (property & 0xffff);
|
||||
}
|
||||
|
||||
int GetIndex() const { return static_cast<int>(m_handle) & 0xffff; }
|
||||
Type GetType() const {
|
||||
return static_cast<Type>((static_cast<int>(m_handle) >> 24) & 0xff);
|
||||
}
|
||||
bool IsType(Type type) const { return type == GetType(); }
|
||||
int GetTypedIndex(Type type) const { return IsType(type) ? GetIndex() : -1; }
|
||||
int GetParentIndex() const {
|
||||
return (IsType(Handle::kProperty) || IsType(Handle::kSinkProperty))
|
||||
? (static_cast<int>(m_handle) >> 16) & 0xff
|
||||
: -1;
|
||||
}
|
||||
|
||||
private:
|
||||
CS_Handle m_handle;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_HANDLE_H_
|
||||
618
cscore/src/main/native/cpp/HttpCameraImpl.cpp
Normal file
618
cscore/src/main/native/cpp/HttpCameraImpl.cpp
Normal file
@@ -0,0 +1,618 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "HttpCameraImpl.h"
|
||||
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/TCPConnector.h>
|
||||
#include <wpi/memory.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "JpegUtil.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "Telemetry.h"
|
||||
#include "c_util.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
HttpCameraImpl::HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind,
|
||||
wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry)
|
||||
: SourceImpl{name, logger, notifier, telemetry}, m_kind{kind} {}
|
||||
|
||||
HttpCameraImpl::~HttpCameraImpl() {
|
||||
m_active = false;
|
||||
|
||||
// force wakeup of monitor thread
|
||||
m_monitorCond.notify_one();
|
||||
|
||||
// join monitor thread
|
||||
if (m_monitorThread.joinable()) m_monitorThread.join();
|
||||
|
||||
// Close file if it's open
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_streamConn) m_streamConn->stream->close();
|
||||
if (m_settingsConn) m_settingsConn->stream->close();
|
||||
}
|
||||
|
||||
// force wakeup of camera thread in case it's waiting on cv
|
||||
m_sinkEnabledCond.notify_one();
|
||||
|
||||
// join camera thread
|
||||
if (m_streamThread.joinable()) m_streamThread.join();
|
||||
|
||||
// force wakeup of settings thread
|
||||
m_settingsCond.notify_one();
|
||||
|
||||
// join settings thread
|
||||
if (m_settingsThread.joinable()) m_settingsThread.join();
|
||||
}
|
||||
|
||||
void HttpCameraImpl::Start() {
|
||||
// Kick off the stream and settings threads
|
||||
m_streamThread = std::thread(&HttpCameraImpl::StreamThreadMain, this);
|
||||
m_settingsThread = std::thread(&HttpCameraImpl::SettingsThreadMain, this);
|
||||
m_monitorThread = std::thread(&HttpCameraImpl::MonitorThreadMain, this);
|
||||
}
|
||||
|
||||
void HttpCameraImpl::MonitorThreadMain() {
|
||||
while (m_active) {
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
// sleep for 1 second between checks
|
||||
m_monitorCond.wait_for(lock, std::chrono::seconds(1),
|
||||
[=] { return !m_active; });
|
||||
|
||||
if (!m_active) break;
|
||||
|
||||
// check to see if we got any frames, and close the stream if not
|
||||
// (this will result in an error at the read point, and ultimately
|
||||
// a reconnect attempt)
|
||||
if (m_streamConn && m_frameCount == 0) {
|
||||
SWARNING("Monitor detected stream hung, disconnecting");
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
|
||||
// reset the frame counter
|
||||
m_frameCount = 0;
|
||||
}
|
||||
|
||||
SDEBUG("Monitor Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::StreamThreadMain() {
|
||||
while (m_active) {
|
||||
SetConnected(false);
|
||||
|
||||
// sleep between retries
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
|
||||
// disconnect if not enabled
|
||||
if (!IsEnabled()) {
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
if (m_streamConn) m_streamConn->stream->close();
|
||||
// Wait for enable
|
||||
m_sinkEnabledCond.wait(lock, [=] { return !m_active || IsEnabled(); });
|
||||
if (!m_active) return;
|
||||
}
|
||||
|
||||
// connect
|
||||
wpi::SmallString<64> boundary;
|
||||
wpi::HttpConnection* conn = DeviceStreamConnect(boundary);
|
||||
|
||||
if (!m_active) break;
|
||||
|
||||
// keep retrying
|
||||
if (!conn) continue;
|
||||
|
||||
// update connected since we're actually connected
|
||||
SetConnected(true);
|
||||
|
||||
// stream
|
||||
DeviceStream(conn->is, boundary);
|
||||
{
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
SDEBUG("Camera Thread exiting");
|
||||
SetConnected(false);
|
||||
}
|
||||
|
||||
wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
wpi::SmallVectorImpl<char>& boundary) {
|
||||
// Build the request
|
||||
wpi::HttpRequest req;
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_locations.empty()) {
|
||||
SERROR("locations array is empty!?");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return nullptr;
|
||||
}
|
||||
if (m_nextLocation >= m_locations.size()) m_nextLocation = 0;
|
||||
req = wpi::HttpRequest{m_locations[m_nextLocation++], m_streamSettings};
|
||||
m_streamSettingsUpdated = false;
|
||||
}
|
||||
|
||||
// Try to connect
|
||||
auto stream =
|
||||
wpi::TCPConnector::connect(req.host.c_str(), req.port, m_logger, 1);
|
||||
|
||||
if (!m_active || !stream) return nullptr;
|
||||
|
||||
auto connPtr = wpi::make_unique<wpi::HttpConnection>(std::move(stream), 1);
|
||||
wpi::HttpConnection* conn = connPtr.get();
|
||||
|
||||
// update m_streamConn
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_frameCount = 1; // avoid a race with monitor thread
|
||||
m_streamConn = std::move(connPtr);
|
||||
}
|
||||
|
||||
std::string warn;
|
||||
if (!conn->Handshake(req, &warn)) {
|
||||
SWARNING(GetName() << ": " << warn);
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Parse Content-Type header to get the boundary
|
||||
wpi::StringRef mediaType, contentType;
|
||||
std::tie(mediaType, contentType) = conn->contentType.str().split(';');
|
||||
mediaType = mediaType.trim();
|
||||
if (mediaType != "multipart/x-mixed-replace") {
|
||||
SWARNING("\"" << req.host << "\": unrecognized Content-Type \"" << mediaType
|
||||
<< "\"");
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// media parameters
|
||||
boundary.clear();
|
||||
while (!contentType.empty()) {
|
||||
wpi::StringRef keyvalue;
|
||||
std::tie(keyvalue, contentType) = contentType.split(';');
|
||||
contentType = contentType.ltrim();
|
||||
wpi::StringRef key, value;
|
||||
std::tie(key, value) = keyvalue.split('=');
|
||||
if (key.trim() == "boundary") {
|
||||
value = value.trim().trim('"'); // value may be quoted
|
||||
boundary.append(value.begin(), value.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (boundary.empty()) {
|
||||
SWARNING("\"" << req.host
|
||||
<< "\": empty multi-part boundary or no Content-Type");
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::DeviceStream(wpi::raw_istream& is,
|
||||
wpi::StringRef boundary) {
|
||||
// Stored here so we reuse it from frame to frame
|
||||
std::string imageBuf;
|
||||
|
||||
// keep track of number of bad images received; if we receive 3 bad images
|
||||
// in a row, we reconnect
|
||||
int numErrors = 0;
|
||||
|
||||
// streaming loop
|
||||
while (m_active && !is.has_error() && IsEnabled() && numErrors < 3 &&
|
||||
!m_streamSettingsUpdated) {
|
||||
if (!FindMultipartBoundary(is, boundary, nullptr)) break;
|
||||
|
||||
// Read the next two characters after the boundary (normally \r\n)
|
||||
char eol[2];
|
||||
is.read(eol, 2);
|
||||
if (!m_active || is.has_error()) break;
|
||||
// End-of-stream is indicated with trailing --
|
||||
if (eol[0] == '-' && eol[1] == '-') break;
|
||||
|
||||
if (!DeviceStreamFrame(is, imageBuf))
|
||||
++numErrors;
|
||||
else
|
||||
numErrors = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
std::string& imageBuf) {
|
||||
// Read the headers
|
||||
wpi::SmallString<64> contentTypeBuf;
|
||||
wpi::SmallString<64> contentLengthBuf;
|
||||
if (!ParseHttpHeaders(is, &contentTypeBuf, &contentLengthBuf)) {
|
||||
SWARNING("disconnected during headers");
|
||||
PutError("disconnected during headers", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the content type (if present)
|
||||
if (!contentTypeBuf.str().empty() &&
|
||||
!contentTypeBuf.str().startswith("image/jpeg")) {
|
||||
wpi::SmallString<64> errBuf;
|
||||
wpi::raw_svector_ostream errMsg{errBuf};
|
||||
errMsg << "received unknown Content-Type \"" << contentTypeBuf << "\"";
|
||||
SWARNING(errMsg.str());
|
||||
PutError(errMsg.str(), wpi::Now());
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int contentLength = 0;
|
||||
if (contentLengthBuf.str().getAsInteger(10, contentLength)) {
|
||||
// Ugh, no Content-Length? Read the blocks of the JPEG file.
|
||||
int width, height;
|
||||
if (!ReadJpeg(is, imageBuf, &width, &height)) {
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
|
||||
wpi::Now());
|
||||
++m_frameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We know how big it is! Just get a frame of the right size and read
|
||||
// the data directly into it.
|
||||
auto image = AllocImage(VideoMode::PixelFormat::kMJPEG, 0, 0, contentLength);
|
||||
is.read(image->data(), contentLength);
|
||||
if (!m_active || is.has_error()) return false;
|
||||
int width, height;
|
||||
if (!GetJpegSize(image->str(), &width, &height)) {
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
PutFrame(std::move(image), wpi::Now());
|
||||
++m_frameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SettingsThreadMain() {
|
||||
for (;;) {
|
||||
wpi::HttpRequest req;
|
||||
{
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
m_settingsCond.wait(lock, [=] {
|
||||
return !m_active || (m_prefLocation != -1 && !m_settings.empty());
|
||||
});
|
||||
if (!m_active) break;
|
||||
|
||||
// Build the request
|
||||
req = wpi::HttpRequest{m_locations[m_prefLocation], m_settings};
|
||||
}
|
||||
|
||||
DeviceSendSettings(req);
|
||||
}
|
||||
|
||||
SDEBUG("Settings Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
|
||||
// Try to connect
|
||||
auto stream =
|
||||
wpi::TCPConnector::connect(req.host.c_str(), req.port, m_logger, 1);
|
||||
|
||||
if (!m_active || !stream) return;
|
||||
|
||||
auto connPtr = wpi::make_unique<wpi::HttpConnection>(std::move(stream), 1);
|
||||
wpi::HttpConnection* conn = connPtr.get();
|
||||
|
||||
// update m_settingsConn
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_settingsConn = std::move(connPtr);
|
||||
}
|
||||
|
||||
// Just need a handshake as settings are sent via GET parameters
|
||||
std::string warn;
|
||||
if (!conn->Handshake(req, &warn)) SWARNING(GetName() << ": " << warn);
|
||||
|
||||
conn->stream->close();
|
||||
}
|
||||
|
||||
CS_HttpCameraKind HttpCameraImpl::GetKind() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::SetUrls(wpi::ArrayRef<std::string> urls,
|
||||
CS_Status* status) {
|
||||
std::vector<wpi::HttpLocation> locations;
|
||||
for (const auto& url : urls) {
|
||||
bool error = false;
|
||||
std::string errorMsg;
|
||||
locations.emplace_back(url, &error, &errorMsg);
|
||||
if (error) {
|
||||
SERROR(GetName() << ": " << errorMsg);
|
||||
*status = CS_BAD_URL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_locations.swap(locations);
|
||||
m_nextLocation = 0;
|
||||
m_streamSettingsUpdated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> HttpCameraImpl::GetUrls() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
std::vector<std::string> urls;
|
||||
for (const auto& loc : m_locations) urls.push_back(loc.url);
|
||||
return urls;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::CreateProperty(const wpi::Twine& name,
|
||||
const wpi::Twine& httpParam,
|
||||
bool viaSettings, CS_PropertyKind kind,
|
||||
int minimum, int maximum, int step,
|
||||
int defaultValue, int value) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_propertyData.emplace_back(wpi::make_unique<PropertyData>(
|
||||
name, httpParam, viaSettings, kind, minimum, maximum, step, defaultValue,
|
||||
value));
|
||||
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name,
|
||||
m_propertyData.size() + 1, kind, value,
|
||||
wpi::Twine{});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void HttpCameraImpl::CreateEnumProperty(
|
||||
const wpi::Twine& name, const wpi::Twine& httpParam, bool viaSettings,
|
||||
int defaultValue, int value, std::initializer_list<T> choices) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_propertyData.emplace_back(wpi::make_unique<PropertyData>(
|
||||
name, httpParam, viaSettings, CS_PROP_ENUM, 0, choices.size() - 1, 1,
|
||||
defaultValue, value));
|
||||
|
||||
auto& enumChoices = m_propertyData.back()->enumChoices;
|
||||
enumChoices.clear();
|
||||
for (const auto& choice : choices) enumChoices.emplace_back(choice);
|
||||
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name,
|
||||
m_propertyData.size() + 1, CS_PROP_ENUM,
|
||||
value, wpi::Twine{});
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
|
||||
name, m_propertyData.size() + 1, CS_PROP_ENUM,
|
||||
value, wpi::Twine{});
|
||||
}
|
||||
|
||||
std::unique_ptr<PropertyImpl> HttpCameraImpl::CreateEmptyProperty(
|
||||
const wpi::Twine& name) const {
|
||||
return wpi::make_unique<PropertyData>(name);
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::CacheProperties(CS_Status* status) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
|
||||
// Pretty typical set of video modes
|
||||
m_videoModes.clear();
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 640, 480, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 320, 240, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 160, 120, 30);
|
||||
|
||||
m_properties_cached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetProperty(int property, int value, CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetStringProperty(int property, const wpi::Twine& value,
|
||||
CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetBrightness(int brightness, CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
int HttpCameraImpl::GetBrightness(CS_Status* status) const {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetWhiteBalanceAuto(CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetWhiteBalanceHoldCurrent(CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetWhiteBalanceManual(int value, CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetExposureAuto(CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetExposureHoldCurrent(CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HttpCameraImpl::SetExposureManual(int value, CS_Status* status) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
|
||||
if (mode.pixelFormat != VideoMode::kMJPEG) return false;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_mode = mode;
|
||||
m_streamSettingsUpdated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpCameraImpl::NumSinksChanged() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
void HttpCameraImpl::NumSinksEnabledChanged() {
|
||||
m_sinkEnabledCond.notify_one();
|
||||
}
|
||||
|
||||
bool AxisCameraImpl::CacheProperties(CS_Status* status) const {
|
||||
CreateProperty("brightness", "ImageSource.I0.Sensor.Brightness", true,
|
||||
CS_PROP_INTEGER, 0, 100, 1, 50, 50);
|
||||
CreateEnumProperty("white_balance", "ImageSource.I0.Sensor.WhiteBalance",
|
||||
true, 0, 0,
|
||||
{"auto", "hold", "fixed_outdoor1", "fixed_outdoor2",
|
||||
"fixed_indoor", "fixed_fluor1", "fixed_fluor2"});
|
||||
CreateProperty("color_level", "ImageSource.I0.Sensor.ColorLevel", true,
|
||||
CS_PROP_INTEGER, 0, 100, 1, 50, 50);
|
||||
CreateEnumProperty("exposure", "ImageSource.I0.Sensor.Exposure", true, 0, 0,
|
||||
{"auto", "hold", "flickerfree50", "flickerfree60"});
|
||||
CreateProperty("exposure_priority", "ImageSource.I0.Sensor.ExposurePriority",
|
||||
true, CS_PROP_INTEGER, 0, 100, 1, 50, 50);
|
||||
|
||||
// TODO: get video modes from device
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_videoModes.clear();
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 640, 480, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 480, 360, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 320, 240, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 240, 180, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 176, 144, 30);
|
||||
m_videoModes.emplace_back(VideoMode::kMJPEG, 160, 120, 30);
|
||||
|
||||
m_properties_cached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
CS_Source CreateHttpCamera(const wpi::Twine& name, const wpi::Twine& url,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
std::shared_ptr<HttpCameraImpl> source;
|
||||
switch (kind) {
|
||||
case CS_HTTP_AXIS:
|
||||
source = std::make_shared<AxisCameraImpl>(name, inst.logger,
|
||||
inst.notifier, inst.telemetry);
|
||||
break;
|
||||
default:
|
||||
source = std::make_shared<HttpCameraImpl>(name, kind, inst.logger,
|
||||
inst.notifier, inst.telemetry);
|
||||
break;
|
||||
}
|
||||
if (!source->SetUrls(url.str(), status)) return 0;
|
||||
return inst.CreateSource(CS_SOURCE_HTTP, source);
|
||||
}
|
||||
|
||||
CS_Source CreateHttpCamera(const wpi::Twine& name,
|
||||
wpi::ArrayRef<std::string> urls,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
if (urls.empty()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
return 0;
|
||||
}
|
||||
auto source = std::make_shared<HttpCameraImpl>(name, kind, inst.logger,
|
||||
inst.notifier, inst.telemetry);
|
||||
if (!source->SetUrls(urls, status)) return 0;
|
||||
return inst.CreateSource(CS_SOURCE_HTTP, source);
|
||||
}
|
||||
|
||||
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_HTTP) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return CS_HTTP_UNKNOWN;
|
||||
}
|
||||
return static_cast<HttpCameraImpl&>(*data->source).GetKind();
|
||||
}
|
||||
|
||||
void SetHttpCameraUrls(CS_Source source, wpi::ArrayRef<std::string> urls,
|
||||
CS_Status* status) {
|
||||
if (urls.empty()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
return;
|
||||
}
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_HTTP) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<HttpCameraImpl&>(*data->source).SetUrls(urls, status);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetHttpCameraUrls(CS_Source source,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_HTTP) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::vector<std::string>{};
|
||||
}
|
||||
return static_cast<HttpCameraImpl&>(*data->source).GetUrls();
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_Source CS_CreateHttpCamera(const char* name, const char* url,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
return cs::CreateHttpCamera(name, url, kind, status);
|
||||
}
|
||||
|
||||
CS_Source CS_CreateHttpCameraMulti(const char* name, const char** urls,
|
||||
int count, CS_HttpCameraKind kind,
|
||||
CS_Status* status) {
|
||||
wpi::SmallVector<std::string, 4> vec;
|
||||
vec.reserve(count);
|
||||
for (int i = 0; i < count; ++i) vec.push_back(urls[i]);
|
||||
return cs::CreateHttpCamera(name, vec, kind, status);
|
||||
}
|
||||
|
||||
CS_HttpCameraKind CS_GetHttpCameraKind(CS_Source source, CS_Status* status) {
|
||||
return cs::GetHttpCameraKind(source, status);
|
||||
}
|
||||
|
||||
void CS_SetHttpCameraUrls(CS_Source source, const char** urls, int count,
|
||||
CS_Status* status) {
|
||||
wpi::SmallVector<std::string, 4> vec;
|
||||
vec.reserve(count);
|
||||
for (int i = 0; i < count; ++i) vec.push_back(urls[i]);
|
||||
cs::SetHttpCameraUrls(source, vec, status);
|
||||
}
|
||||
|
||||
char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status) {
|
||||
auto urls = cs::GetHttpCameraUrls(source, status);
|
||||
char** out =
|
||||
static_cast<char**>(wpi::CheckedMalloc(urls.size() * sizeof(char*)));
|
||||
*count = urls.size();
|
||||
for (size_t i = 0; i < urls.size(); ++i) out[i] = cs::ConvertToC(urls[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
void CS_FreeHttpCameraUrls(char** urls, int count) {
|
||||
if (!urls) return;
|
||||
for (int i = 0; i < count; ++i) std::free(urls[i]);
|
||||
std::free(urls);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
166
cscore/src/main/native/cpp/HttpCameraImpl.h
Normal file
166
cscore/src/main/native/cpp/HttpCameraImpl.h
Normal file
@@ -0,0 +1,166 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_HTTPCAMERAIMPL_H_
|
||||
#define CSCORE_HTTPCAMERAIMPL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/HttpUtil.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class HttpCameraImpl : public SourceImpl {
|
||||
public:
|
||||
HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind,
|
||||
wpi::Logger& logger, Notifier& notifier, Telemetry& telemetry);
|
||||
~HttpCameraImpl() override;
|
||||
|
||||
void Start() override;
|
||||
|
||||
// Property functions
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
void SetStringProperty(int property, const wpi::Twine& value,
|
||||
CS_Status* status) override;
|
||||
|
||||
// Standard common camera properties
|
||||
void SetBrightness(int brightness, CS_Status* status) override;
|
||||
int GetBrightness(CS_Status* status) const override;
|
||||
void SetWhiteBalanceAuto(CS_Status* status) override;
|
||||
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
|
||||
void SetWhiteBalanceManual(int value, CS_Status* status) override;
|
||||
void SetExposureAuto(CS_Status* status) override;
|
||||
void SetExposureHoldCurrent(CS_Status* status) override;
|
||||
void SetExposureManual(int value, CS_Status* status) override;
|
||||
|
||||
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
|
||||
|
||||
void NumSinksChanged() override;
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
CS_HttpCameraKind GetKind() const;
|
||||
bool SetUrls(wpi::ArrayRef<std::string> urls, CS_Status* status);
|
||||
std::vector<std::string> GetUrls() const;
|
||||
|
||||
// Property data
|
||||
class PropertyData : public PropertyImpl {
|
||||
public:
|
||||
PropertyData() = default;
|
||||
explicit PropertyData(const wpi::Twine& name_) : PropertyImpl{name_} {}
|
||||
PropertyData(const wpi::Twine& name_, const wpi::Twine& httpParam_,
|
||||
bool viaSettings_, CS_PropertyKind kind_, int minimum_,
|
||||
int maximum_, int step_, int defaultValue_, int value_)
|
||||
: PropertyImpl(name_, kind_, step_, defaultValue_, value_),
|
||||
viaSettings(viaSettings_),
|
||||
httpParam(httpParam_.str()) {
|
||||
hasMinimum = true;
|
||||
minimum = minimum_;
|
||||
hasMaximum = true;
|
||||
maximum = maximum_;
|
||||
}
|
||||
~PropertyData() override = default;
|
||||
|
||||
bool viaSettings{false};
|
||||
std::string httpParam;
|
||||
};
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
|
||||
const wpi::Twine& name) const override;
|
||||
|
||||
bool CacheProperties(CS_Status* status) const override;
|
||||
|
||||
void CreateProperty(const wpi::Twine& name, const wpi::Twine& httpParam,
|
||||
bool viaSettings, CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value) const;
|
||||
|
||||
template <typename T>
|
||||
void CreateEnumProperty(const wpi::Twine& name, const wpi::Twine& httpParam,
|
||||
bool viaSettings, int defaultValue, int value,
|
||||
std::initializer_list<T> choices) const;
|
||||
|
||||
private:
|
||||
// The camera streaming thread
|
||||
void StreamThreadMain();
|
||||
|
||||
// Functions used by StreamThreadMain()
|
||||
wpi::HttpConnection* DeviceStreamConnect(
|
||||
wpi::SmallVectorImpl<char>& boundary);
|
||||
void DeviceStream(wpi::raw_istream& is, wpi::StringRef boundary);
|
||||
bool DeviceStreamFrame(wpi::raw_istream& is, std::string& imageBuf);
|
||||
|
||||
// The camera settings thread
|
||||
void SettingsThreadMain();
|
||||
void DeviceSendSettings(wpi::HttpRequest& req);
|
||||
|
||||
// The monitor thread
|
||||
void MonitorThreadMain();
|
||||
|
||||
std::atomic_bool m_connected{false};
|
||||
std::atomic_bool m_active{true}; // set to false to terminate thread
|
||||
std::thread m_streamThread;
|
||||
std::thread m_settingsThread;
|
||||
std::thread m_monitorThread;
|
||||
|
||||
//
|
||||
// Variables protected by m_mutex
|
||||
//
|
||||
|
||||
// The camera connections
|
||||
std::unique_ptr<wpi::HttpConnection> m_streamConn;
|
||||
std::unique_ptr<wpi::HttpConnection> m_settingsConn;
|
||||
|
||||
CS_HttpCameraKind m_kind;
|
||||
|
||||
std::vector<wpi::HttpLocation> m_locations;
|
||||
size_t m_nextLocation{0};
|
||||
int m_prefLocation{-1}; // preferred location
|
||||
|
||||
std::atomic_int m_frameCount{0};
|
||||
|
||||
wpi::condition_variable m_sinkEnabledCond;
|
||||
|
||||
wpi::StringMap<wpi::SmallString<16>> m_settings;
|
||||
wpi::condition_variable m_settingsCond;
|
||||
|
||||
wpi::StringMap<wpi::SmallString<16>> m_streamSettings;
|
||||
std::atomic_bool m_streamSettingsUpdated{false};
|
||||
|
||||
wpi::condition_variable m_monitorCond;
|
||||
};
|
||||
|
||||
class AxisCameraImpl : public HttpCameraImpl {
|
||||
public:
|
||||
AxisCameraImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry)
|
||||
: HttpCameraImpl{name, CS_HTTP_AXIS, logger, notifier, telemetry} {}
|
||||
#if 0
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
void SetStringProperty(int property, const wpi::Twine& value,
|
||||
CS_Status* status) override;
|
||||
#endif
|
||||
protected:
|
||||
bool CacheProperties(CS_Status* status) const override;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_HTTPCAMERAIMPL_H_
|
||||
111
cscore/src/main/native/cpp/Image.h
Normal file
111
cscore/src/main/native/cpp/Image.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_IMAGE_H_
|
||||
#define CSCORE_IMAGE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <wpi/StringRef.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
#include "default_init_allocator.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Frame;
|
||||
|
||||
class Image {
|
||||
friend class Frame;
|
||||
|
||||
public:
|
||||
#ifndef __linux__
|
||||
explicit Image(size_t capacity) { m_data.reserve(capacity); }
|
||||
#else
|
||||
explicit Image(size_t capacity)
|
||||
: m_data{capacity, default_init_allocator<uchar>{}} {
|
||||
m_data.resize(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
Image(const Image&) = delete;
|
||||
Image& operator=(const Image&) = delete;
|
||||
|
||||
// Getters
|
||||
operator wpi::StringRef() const { return str(); }
|
||||
wpi::StringRef str() const { return wpi::StringRef(data(), size()); }
|
||||
size_t capacity() const { return m_data.capacity(); }
|
||||
const char* data() const {
|
||||
return reinterpret_cast<const char*>(m_data.data());
|
||||
}
|
||||
char* data() { return reinterpret_cast<char*>(m_data.data()); }
|
||||
size_t size() const { return m_data.size(); }
|
||||
|
||||
const std::vector<uchar>& vec() const { return m_data; }
|
||||
std::vector<uchar>& vec() { return m_data; }
|
||||
|
||||
void resize(size_t size) { m_data.resize(size); }
|
||||
void SetSize(size_t size) { m_data.resize(size); }
|
||||
|
||||
cv::Mat AsMat() {
|
||||
int type;
|
||||
switch (pixelFormat) {
|
||||
case VideoMode::kYUYV:
|
||||
case VideoMode::kRGB565:
|
||||
type = CV_8UC2;
|
||||
break;
|
||||
case VideoMode::kBGR:
|
||||
type = CV_8UC3;
|
||||
break;
|
||||
case VideoMode::kGray:
|
||||
case VideoMode::kMJPEG:
|
||||
default:
|
||||
type = CV_8UC1;
|
||||
break;
|
||||
}
|
||||
return cv::Mat{height, width, type, m_data.data()};
|
||||
}
|
||||
|
||||
cv::_InputArray AsInputArray() { return cv::_InputArray{m_data}; }
|
||||
|
||||
bool Is(int width_, int height_) {
|
||||
return width == width_ && height == height_;
|
||||
}
|
||||
bool Is(int width_, int height_, VideoMode::PixelFormat pixelFormat_) {
|
||||
return width == width_ && height == height_ && pixelFormat == pixelFormat_;
|
||||
}
|
||||
bool Is(int width_, int height_, VideoMode::PixelFormat pixelFormat_,
|
||||
int jpegQuality_) {
|
||||
// Consider +/-5 on JPEG quality to be "close enough"
|
||||
return width == width_ && height == height_ &&
|
||||
pixelFormat == pixelFormat_ &&
|
||||
(pixelFormat != VideoMode::kMJPEG || jpegQuality_ == -1 ||
|
||||
(jpegQuality != -1 && std::abs(jpegQuality - jpegQuality_) <= 5));
|
||||
}
|
||||
bool IsLarger(int width_, int height_) {
|
||||
return width >= width_ && height >= height_;
|
||||
}
|
||||
bool IsLarger(const Image& oth) {
|
||||
return width >= oth.width && height >= oth.height;
|
||||
}
|
||||
bool IsSmaller(int width_, int height_) { return !IsLarger(width_, height_); }
|
||||
bool IsSmaller(const Image& oth) { return !IsLarger(oth); }
|
||||
|
||||
private:
|
||||
std::vector<uchar> m_data;
|
||||
|
||||
public:
|
||||
VideoMode::PixelFormat pixelFormat{VideoMode::kUnknown};
|
||||
int width{0};
|
||||
int height{0};
|
||||
int jpegQuality{-1};
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_IMAGE_H_
|
||||
97
cscore/src/main/native/cpp/Instance.cpp
Normal file
97
cscore/src/main/native/cpp/Instance.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Instance.h"
|
||||
|
||||
#include <wpi/Path.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
using namespace cs;
|
||||
|
||||
static void def_log_func(unsigned int level, const char* file,
|
||||
unsigned int line, const char* msg) {
|
||||
wpi::SmallString<128> buf;
|
||||
wpi::raw_svector_ostream oss(buf);
|
||||
if (level == 20) {
|
||||
oss << "CS: " << msg << '\n';
|
||||
wpi::errs() << oss.str();
|
||||
return;
|
||||
}
|
||||
|
||||
wpi::StringRef levelmsg;
|
||||
if (level >= 50)
|
||||
levelmsg = "CRITICAL: ";
|
||||
else if (level >= 40)
|
||||
levelmsg = "ERROR: ";
|
||||
else if (level >= 30)
|
||||
levelmsg = "WARNING: ";
|
||||
else
|
||||
return;
|
||||
oss << "CS: " << levelmsg << msg << " (" << wpi::sys::path::filename(file)
|
||||
<< ':' << line << ")\n";
|
||||
wpi::errs() << oss.str();
|
||||
}
|
||||
|
||||
Instance::Instance() : telemetry(notifier), networkListener(logger, notifier) {
|
||||
SetDefaultLogger();
|
||||
}
|
||||
|
||||
Instance::~Instance() {}
|
||||
|
||||
Instance& Instance::GetInstance() {
|
||||
static Instance* inst = new Instance;
|
||||
return *inst;
|
||||
}
|
||||
|
||||
void Instance::Shutdown() {
|
||||
eventLoop.Stop();
|
||||
m_sinks.FreeAll();
|
||||
m_sources.FreeAll();
|
||||
networkListener.Stop();
|
||||
telemetry.Stop();
|
||||
notifier.Stop();
|
||||
}
|
||||
|
||||
void Instance::SetDefaultLogger() { logger.SetLogger(def_log_func); }
|
||||
|
||||
std::pair<CS_Source, std::shared_ptr<SourceData>> Instance::FindSource(
|
||||
const SourceImpl& source) {
|
||||
return m_sources.FindIf(
|
||||
[&](const SourceData& data) { return data.source.get() == &source; });
|
||||
}
|
||||
|
||||
std::pair<CS_Sink, std::shared_ptr<SinkData>> Instance::FindSink(
|
||||
const SinkImpl& sink) {
|
||||
return m_sinks.FindIf(
|
||||
[&](const SinkData& data) { return data.sink.get() == &sink; });
|
||||
}
|
||||
|
||||
CS_Source Instance::CreateSource(CS_SourceKind kind,
|
||||
std::shared_ptr<SourceImpl> source) {
|
||||
auto handle = m_sources.Allocate(kind, source);
|
||||
notifier.NotifySource(source->GetName(), handle, CS_SOURCE_CREATED);
|
||||
source->Start();
|
||||
return handle;
|
||||
}
|
||||
|
||||
CS_Sink Instance::CreateSink(CS_SinkKind kind, std::shared_ptr<SinkImpl> sink) {
|
||||
auto handle = m_sinks.Allocate(kind, sink);
|
||||
notifier.NotifySink(sink->GetName(), handle, CS_SINK_CREATED);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void Instance::DestroySource(CS_Source handle) {
|
||||
if (auto data = m_sources.Free(handle))
|
||||
notifier.NotifySource(data->source->GetName(), handle, CS_SOURCE_DESTROYED);
|
||||
}
|
||||
|
||||
void Instance::DestroySink(CS_Sink handle) {
|
||||
if (auto data = m_sinks.Free(handle))
|
||||
notifier.NotifySink(data->sink->GetName(), handle, CS_SINK_DESTROYED);
|
||||
}
|
||||
115
cscore/src/main/native/cpp/Instance.h
Normal file
115
cscore/src/main/native/cpp/Instance.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_INSTANCE_H_
|
||||
#define CSCORE_INSTANCE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/EventLoopRunner.h>
|
||||
#include <wpi/Logger.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "NetworkListener.h"
|
||||
#include "Notifier.h"
|
||||
#include "SinkImpl.h"
|
||||
#include "SourceImpl.h"
|
||||
#include "Telemetry.h"
|
||||
#include "UnlimitedHandleResource.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
struct SourceData {
|
||||
SourceData(CS_SourceKind kind_, std::shared_ptr<SourceImpl> source_)
|
||||
: kind{kind_}, refCount{0}, source{source_} {}
|
||||
|
||||
CS_SourceKind kind;
|
||||
std::atomic_int refCount;
|
||||
std::shared_ptr<SourceImpl> source;
|
||||
};
|
||||
|
||||
struct SinkData {
|
||||
explicit SinkData(CS_SinkKind kind_, std::shared_ptr<SinkImpl> sink_)
|
||||
: kind{kind_}, refCount{0}, sourceHandle{0}, sink{sink_} {}
|
||||
|
||||
CS_SinkKind kind;
|
||||
std::atomic_int refCount;
|
||||
std::atomic<CS_Source> sourceHandle;
|
||||
std::shared_ptr<SinkImpl> sink;
|
||||
};
|
||||
|
||||
class Instance {
|
||||
public:
|
||||
Instance(const Instance&) = delete;
|
||||
Instance& operator=(const Instance&) = delete;
|
||||
~Instance();
|
||||
|
||||
static Instance& GetInstance();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
wpi::Logger logger;
|
||||
Notifier notifier;
|
||||
Telemetry telemetry;
|
||||
NetworkListener networkListener;
|
||||
|
||||
private:
|
||||
UnlimitedHandleResource<Handle, SourceData, Handle::kSource> m_sources;
|
||||
UnlimitedHandleResource<Handle, SinkData, Handle::kSink> m_sinks;
|
||||
|
||||
public:
|
||||
wpi::EventLoopRunner eventLoop;
|
||||
|
||||
std::pair<CS_Sink, std::shared_ptr<SinkData>> FindSink(const SinkImpl& sink);
|
||||
std::pair<CS_Source, std::shared_ptr<SourceData>> FindSource(
|
||||
const SourceImpl& source);
|
||||
|
||||
void SetDefaultLogger();
|
||||
|
||||
std::shared_ptr<SourceData> GetSource(CS_Source handle) {
|
||||
return m_sources.Get(handle);
|
||||
}
|
||||
|
||||
std::shared_ptr<SinkData> GetSink(CS_Sink handle) {
|
||||
return m_sinks.Get(handle);
|
||||
}
|
||||
|
||||
CS_Source CreateSource(CS_SourceKind kind,
|
||||
std::shared_ptr<SourceImpl> source);
|
||||
|
||||
CS_Sink CreateSink(CS_SinkKind kind, std::shared_ptr<SinkImpl> sink);
|
||||
|
||||
void DestroySource(CS_Source handle);
|
||||
void DestroySink(CS_Sink handle);
|
||||
|
||||
wpi::ArrayRef<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec) {
|
||||
return m_sources.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::ArrayRef<CS_Sink> EnumerateSinkHandles(
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
return m_sinks.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::ArrayRef<CS_Sink> EnumerateSourceSinks(
|
||||
CS_Source source, wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
vec.clear();
|
||||
m_sinks.ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
|
||||
if (source == data.sourceHandle.load()) vec.push_back(sinkHandle);
|
||||
});
|
||||
return vec;
|
||||
}
|
||||
|
||||
private:
|
||||
Instance();
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_INSTANCE_H_
|
||||
197
cscore/src/main/native/cpp/JpegUtil.cpp
Normal file
197
cscore/src/main/native/cpp/JpegUtil.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "JpegUtil.h"
|
||||
|
||||
#include <wpi/raw_istream.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
// DHT data for MJPEG images that don't have it.
|
||||
static const unsigned char dhtData[] = {
|
||||
0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
|
||||
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x03,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
|
||||
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04,
|
||||
0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
|
||||
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15,
|
||||
0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
|
||||
0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95,
|
||||
0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
|
||||
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
|
||||
0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
|
||||
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
|
||||
0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
|
||||
0xfa, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
|
||||
0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
|
||||
0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
|
||||
0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33,
|
||||
0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25,
|
||||
0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
|
||||
0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
|
||||
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
|
||||
0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
|
||||
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
|
||||
0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
|
||||
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
|
||||
0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa};
|
||||
|
||||
bool IsJpeg(wpi::StringRef data) {
|
||||
if (data.size() < 11) return false;
|
||||
|
||||
// Check for valid SOI
|
||||
auto bytes = data.bytes_begin();
|
||||
if (bytes[0] != 0xff || bytes[1] != 0xd8) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetJpegSize(wpi::StringRef data, int* width, int* height) {
|
||||
if (!IsJpeg(data)) return false;
|
||||
|
||||
data = data.substr(2); // Get to the first block
|
||||
auto bytes = data.bytes_begin();
|
||||
for (;;) {
|
||||
if (data.size() < 4) return false; // EOF
|
||||
bytes = data.bytes_begin();
|
||||
if (bytes[0] != 0xff) return false; // not a tag
|
||||
if (bytes[1] == 0xd9) return false; // EOI without finding SOF?
|
||||
if (bytes[1] == 0xda) return false; // SOS without finding SOF?
|
||||
if (bytes[1] == 0xc0) {
|
||||
// SOF contains the file size
|
||||
if (data.size() < 9) return false;
|
||||
*height = bytes[5] * 256 + bytes[6];
|
||||
*width = bytes[7] * 256 + bytes[8];
|
||||
return true;
|
||||
}
|
||||
// Go to the next block
|
||||
data = data.substr(bytes[2] * 256 + bytes[3] + 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF) {
|
||||
wpi::StringRef sdata(data, *size);
|
||||
if (!IsJpeg(sdata)) return false;
|
||||
|
||||
*locSOF = *size;
|
||||
|
||||
// Search until SOS for DHT tag
|
||||
sdata = sdata.substr(2); // Get to the first block
|
||||
auto bytes = sdata.bytes_begin();
|
||||
for (;;) {
|
||||
if (sdata.size() < 4) return false; // EOF
|
||||
bytes = sdata.bytes_begin();
|
||||
if (bytes[0] != 0xff) return false; // not a tag
|
||||
if (bytes[1] == 0xda) break; // SOS
|
||||
if (bytes[1] == 0xc4) return false; // DHT
|
||||
if (bytes[1] == 0xc0) *locSOF = sdata.data() - data; // SOF
|
||||
// Go to the next block
|
||||
sdata = sdata.substr(bytes[2] * 256 + bytes[3] + 2);
|
||||
}
|
||||
|
||||
// Only add DHT if we also found SOF (insertion point)
|
||||
if (*locSOF != *size) {
|
||||
*size += sizeof(dhtData);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
wpi::StringRef JpegGetDHT() {
|
||||
return wpi::StringRef(reinterpret_cast<const char*>(dhtData),
|
||||
sizeof(dhtData));
|
||||
}
|
||||
|
||||
static inline void ReadInto(wpi::raw_istream& is, std::string& buf,
|
||||
size_t len) {
|
||||
size_t oldSize = buf.size();
|
||||
buf.resize(oldSize + len);
|
||||
is.read(&(*buf.begin()) + oldSize, len);
|
||||
}
|
||||
|
||||
bool ReadJpeg(wpi::raw_istream& is, std::string& buf, int* width, int* height) {
|
||||
// in case we don't get a SOF
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
|
||||
// read SOI and first marker
|
||||
buf.resize(4);
|
||||
is.read(&(*buf.begin()), 4);
|
||||
if (is.has_error()) return false;
|
||||
|
||||
// Check for valid SOI
|
||||
auto bytes = reinterpret_cast<const unsigned char*>(buf.data());
|
||||
if (bytes[0] != 0xff || bytes[1] != 0xd8) return false;
|
||||
size_t pos = 2; // point to first marker
|
||||
for (;;) {
|
||||
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
|
||||
if (bytes[0] != 0xff) return false; // not a marker
|
||||
unsigned char marker = bytes[1];
|
||||
|
||||
if (marker == 0xd9) return true; // EOI, we're done
|
||||
|
||||
if (marker == 0xda) {
|
||||
// SOS: need to keep reading until we reach a normal marker.
|
||||
// Byte stuffing ensures we don't get false markers.
|
||||
// Have to read a byte at a time as we don't want to overread.
|
||||
pos += 2; // point after SOS marker
|
||||
bool maybeMarker = false;
|
||||
for (;;) {
|
||||
ReadInto(is, buf, 1);
|
||||
if (is.has_error()) return false;
|
||||
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
|
||||
if (maybeMarker) {
|
||||
if (bytes[0] != 0x00 && bytes[0] != 0xff &&
|
||||
(bytes[0] < 0xd0 || bytes[0] > 0xd7))
|
||||
break;
|
||||
maybeMarker = false;
|
||||
} else if (bytes[0] == 0xff) {
|
||||
maybeMarker = true;
|
||||
}
|
||||
++pos; // point after byte we finished reading
|
||||
}
|
||||
--pos; // point back to start of marker
|
||||
continue;
|
||||
}
|
||||
|
||||
// A normal block. Read the length
|
||||
ReadInto(is, buf, 2); // read length
|
||||
if (is.has_error()) return false;
|
||||
|
||||
// Point to length
|
||||
pos += 2;
|
||||
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
|
||||
|
||||
// Read the block and the next marker
|
||||
size_t blockLength = bytes[0] * 256 + bytes[1];
|
||||
ReadInto(is, buf, blockLength);
|
||||
if (is.has_error()) return false;
|
||||
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
|
||||
|
||||
// Special block processing
|
||||
if (marker == 0xc0) {
|
||||
// SOF: contains the file size; make sure we actually read enough bytes
|
||||
if (blockLength >= 7) {
|
||||
*height = bytes[3] * 256 + bytes[4];
|
||||
*width = bytes[5] * 256 + bytes[6];
|
||||
}
|
||||
}
|
||||
|
||||
// Point to next marker
|
||||
pos += blockLength;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
33
cscore/src/main/native/cpp/JpegUtil.h
Normal file
33
cscore/src/main/native/cpp/JpegUtil.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_JPEGUTIL_H_
|
||||
#define CSCORE_JPEGUTIL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <wpi/StringRef.h>
|
||||
|
||||
namespace wpi {
|
||||
class raw_istream;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
bool IsJpeg(wpi::StringRef data);
|
||||
|
||||
bool GetJpegSize(wpi::StringRef data, int* width, int* height);
|
||||
|
||||
bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF);
|
||||
|
||||
wpi::StringRef JpegGetDHT();
|
||||
|
||||
bool ReadJpeg(wpi::raw_istream& is, std::string& buf, int* width, int* height);
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_JPEGUTIL_H_
|
||||
36
cscore/src/main/native/cpp/Log.h
Normal file
36
cscore/src/main/native/cpp/Log.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_LOG_H_
|
||||
#define CSCORE_LOG_H_
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
|
||||
#define LOG(level, x) WPI_LOG(m_logger, level, x)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(x) WPI_ERROR(m_logger, x)
|
||||
#define WARNING(x) WPI_WARNING(m_logger, x)
|
||||
#define INFO(x) WPI_INFO(m_logger, x)
|
||||
|
||||
#define DEBUG(x) WPI_DEBUG(m_logger, x)
|
||||
#define DEBUG1(x) WPI_DEBUG1(m_logger, x)
|
||||
#define DEBUG2(x) WPI_DEBUG2(m_logger, x)
|
||||
#define DEBUG3(x) WPI_DEBUG3(m_logger, x)
|
||||
#define DEBUG4(x) WPI_DEBUG4(m_logger, x)
|
||||
|
||||
#define SERROR(x) ERROR(GetName() << ": " << x)
|
||||
#define SWARNING(x) WARNING(GetName() << ": " << x)
|
||||
#define SINFO(x) INFO(GetName() << ": " << x)
|
||||
|
||||
#define SDEBUG(x) DEBUG(GetName() << ": " << x)
|
||||
#define SDEBUG1(x) DEBUG1(GetName() << ": " << x)
|
||||
#define SDEBUG2(x) DEBUG2(GetName() << ": " << x)
|
||||
#define SDEBUG3(x) DEBUG3(GetName() << ": " << x)
|
||||
#define SDEBUG4(x) DEBUG4(GetName() << ": " << x)
|
||||
|
||||
#endif // CSCORE_LOG_H_
|
||||
1005
cscore/src/main/native/cpp/MjpegServerImpl.cpp
Normal file
1005
cscore/src/main/native/cpp/MjpegServerImpl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
71
cscore/src/main/native/cpp/MjpegServerImpl.h
Normal file
71
cscore/src/main/native/cpp/MjpegServerImpl.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_MJPEGSERVERIMPL_H_
|
||||
#define CSCORE_MJPEGSERVERIMPL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/NetworkAcceptor.h>
|
||||
#include <wpi/NetworkStream.h>
|
||||
#include <wpi/SafeThread.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/raw_socket_ostream.h>
|
||||
|
||||
#include "SinkImpl.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SourceImpl;
|
||||
|
||||
class MjpegServerImpl : public SinkImpl {
|
||||
public:
|
||||
MjpegServerImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
const wpi::Twine& listenAddress, int port,
|
||||
std::unique_ptr<wpi::NetworkAcceptor> acceptor);
|
||||
~MjpegServerImpl() override;
|
||||
|
||||
void Stop();
|
||||
std::string GetListenAddress() { return m_listenAddress; }
|
||||
int GetPort() { return m_port; }
|
||||
|
||||
private:
|
||||
void SetSourceImpl(std::shared_ptr<SourceImpl> source) override;
|
||||
|
||||
void ServerThreadMain();
|
||||
|
||||
class ConnThread;
|
||||
|
||||
// Never changed, so not protected by mutex
|
||||
std::string m_listenAddress;
|
||||
int m_port;
|
||||
|
||||
std::unique_ptr<wpi::NetworkAcceptor> m_acceptor;
|
||||
std::atomic_bool m_active; // set to false to terminate threads
|
||||
std::thread m_serverThread;
|
||||
|
||||
std::vector<wpi::SafeThreadOwner<ConnThread>> m_connThreads;
|
||||
|
||||
// property indices
|
||||
int m_widthProp;
|
||||
int m_heightProp;
|
||||
int m_compressionProp;
|
||||
int m_defaultCompressionProp;
|
||||
int m_fpsProp;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_MJPEGSERVERIMPL_H_
|
||||
34
cscore/src/main/native/cpp/NetworkListener.h
Normal file
34
cscore/src/main/native/cpp/NetworkListener.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_NETWORKLISTENER_H_
|
||||
#define CSCORE_NETWORKLISTENER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Notifier;
|
||||
|
||||
class NetworkListener {
|
||||
public:
|
||||
NetworkListener(wpi::Logger& logger, Notifier& notifier);
|
||||
~NetworkListener();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_NETWORKLISTENER_H_
|
||||
248
cscore/src/main/native/cpp/Notifier.cpp
Normal file
248
cscore/src/main/native/cpp/Notifier.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Notifier.h"
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "SinkImpl.h"
|
||||
#include "SourceImpl.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
bool Notifier::s_destroyed = false;
|
||||
|
||||
namespace {
|
||||
// Vector which provides an integrated freelist for removal and reuse of
|
||||
// individual elements.
|
||||
template <typename T>
|
||||
class UidVector {
|
||||
public:
|
||||
using size_type = typename std::vector<T>::size_type;
|
||||
|
||||
size_type size() const { return m_vector.size(); }
|
||||
T& operator[](size_type i) { return m_vector[i]; }
|
||||
const T& operator[](size_type i) const { return m_vector[i]; }
|
||||
|
||||
// Add a new T to the vector. If there are elements on the freelist,
|
||||
// reuses the last one; otherwise adds to the end of the vector.
|
||||
// Returns the resulting element index (+1).
|
||||
template <class... Args>
|
||||
unsigned int emplace_back(Args&&... args) {
|
||||
unsigned int uid;
|
||||
if (m_free.empty()) {
|
||||
uid = m_vector.size();
|
||||
m_vector.emplace_back(std::forward<Args>(args)...);
|
||||
} else {
|
||||
uid = m_free.back();
|
||||
m_free.pop_back();
|
||||
m_vector[uid] = T(std::forward<Args>(args)...);
|
||||
}
|
||||
return uid + 1;
|
||||
}
|
||||
|
||||
// Removes the identified element by replacing it with a default-constructed
|
||||
// one. The element is added to the freelist for later reuse.
|
||||
void erase(unsigned int uid) {
|
||||
--uid;
|
||||
if (uid >= m_vector.size() || !m_vector[uid]) return;
|
||||
m_free.push_back(uid);
|
||||
m_vector[uid] = T();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> m_vector;
|
||||
std::vector<unsigned int> m_free;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class Notifier::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
Thread(std::function<void()> on_start, std::function<void()> on_exit)
|
||||
: m_on_start(on_start), m_on_exit(on_exit) {}
|
||||
|
||||
void Main();
|
||||
|
||||
struct Listener {
|
||||
Listener() = default;
|
||||
Listener(std::function<void(const RawEvent& event)> callback_,
|
||||
int eventMask_)
|
||||
: callback(callback_), eventMask(eventMask_) {}
|
||||
|
||||
explicit operator bool() const { return static_cast<bool>(callback); }
|
||||
|
||||
std::string prefix;
|
||||
std::function<void(const RawEvent& event)> callback;
|
||||
int eventMask;
|
||||
};
|
||||
UidVector<Listener> m_listeners;
|
||||
|
||||
std::queue<RawEvent> m_notifications;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
};
|
||||
|
||||
Notifier::Notifier() { s_destroyed = false; }
|
||||
|
||||
Notifier::~Notifier() { s_destroyed = true; }
|
||||
|
||||
void Notifier::Start() { m_owner.Start(m_on_start, m_on_exit); }
|
||||
|
||||
void Notifier::Stop() { m_owner.Stop(); }
|
||||
|
||||
void Notifier::Thread::Main() {
|
||||
if (m_on_start) m_on_start();
|
||||
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
while (m_active) {
|
||||
while (m_notifications.empty()) {
|
||||
m_cond.wait(lock);
|
||||
if (!m_active) goto done;
|
||||
}
|
||||
|
||||
while (!m_notifications.empty()) {
|
||||
if (!m_active) goto done;
|
||||
auto item = std::move(m_notifications.front());
|
||||
m_notifications.pop();
|
||||
|
||||
// Use index because iterator might get invalidated.
|
||||
for (size_t i = 0; i < m_listeners.size(); ++i) {
|
||||
if (!m_listeners[i]) continue; // removed
|
||||
|
||||
// Event type must be within requested set for this listener.
|
||||
if ((item.kind & m_listeners[i].eventMask) == 0) continue;
|
||||
|
||||
// make a copy of the callback so we can safely release the mutex
|
||||
auto callback = m_listeners[i].callback;
|
||||
|
||||
// Don't hold mutex during callback execution!
|
||||
lock.unlock();
|
||||
callback(item);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (m_on_exit) m_on_exit();
|
||||
}
|
||||
|
||||
int Notifier::AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask) {
|
||||
Start();
|
||||
auto thr = m_owner.GetThread();
|
||||
return thr->m_listeners.emplace_back(callback, eventMask);
|
||||
}
|
||||
|
||||
void Notifier::RemoveListener(int uid) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
thr->m_listeners.erase(uid);
|
||||
}
|
||||
|
||||
void Notifier::NotifySource(const wpi::Twine& name, CS_Source source,
|
||||
CS_EventKind kind) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
thr->m_notifications.emplace(name, source, static_cast<RawEvent::Kind>(kind));
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifySource(const SourceImpl& source, CS_EventKind kind) {
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
NotifySource(source.GetName(), handleData.first, kind);
|
||||
}
|
||||
|
||||
void Notifier::NotifySourceVideoMode(const SourceImpl& source,
|
||||
const VideoMode& mode) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
|
||||
thr->m_notifications.emplace(source.GetName(), handleData.first, mode);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifySourceProperty(const SourceImpl& source, CS_EventKind kind,
|
||||
const wpi::Twine& propertyName,
|
||||
int property, CS_PropertyKind propertyKind,
|
||||
int value, const wpi::Twine& valueStr) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
|
||||
thr->m_notifications.emplace(
|
||||
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),
|
||||
Handle{handleData.first, property, Handle::kProperty}, propertyKind,
|
||||
value, valueStr);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifySink(const wpi::Twine& name, CS_Sink sink,
|
||||
CS_EventKind kind) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
thr->m_notifications.emplace(name, sink, static_cast<RawEvent::Kind>(kind));
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifySink(const SinkImpl& sink, CS_EventKind kind) {
|
||||
auto handleData = Instance::GetInstance().FindSink(sink);
|
||||
NotifySink(sink.GetName(), handleData.first, kind);
|
||||
}
|
||||
|
||||
void Notifier::NotifySinkSourceChanged(const wpi::Twine& name, CS_Sink sink,
|
||||
CS_Source source) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
RawEvent event{name, sink, RawEvent::kSinkSourceChanged};
|
||||
event.sourceHandle = source;
|
||||
|
||||
thr->m_notifications.emplace(std::move(event));
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifySinkProperty(const SinkImpl& sink, CS_EventKind kind,
|
||||
const wpi::Twine& propertyName, int property,
|
||||
CS_PropertyKind propertyKind, int value,
|
||||
const wpi::Twine& valueStr) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
auto handleData = Instance::GetInstance().FindSink(sink);
|
||||
|
||||
thr->m_notifications.emplace(
|
||||
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),
|
||||
Handle{handleData.first, property, Handle::kSinkProperty}, propertyKind,
|
||||
value, valueStr);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifyNetworkInterfacesChanged() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
thr->m_notifications.emplace(RawEvent::kNetworkInterfacesChanged);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
void Notifier::NotifyTelemetryUpdated() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
thr->m_notifications.emplace(RawEvent::kTelemetryUpdated);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
72
cscore/src/main/native/cpp/Notifier.h
Normal file
72
cscore/src/main/native/cpp/Notifier.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_NOTIFIER_H_
|
||||
#define CSCORE_NOTIFIER_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <wpi/SafeThread.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SinkImpl;
|
||||
class SourceImpl;
|
||||
|
||||
class Notifier {
|
||||
friend class NotifierTest;
|
||||
|
||||
public:
|
||||
Notifier();
|
||||
~Notifier();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
static bool destroyed() { return s_destroyed; }
|
||||
|
||||
void SetOnStart(std::function<void()> on_start) { m_on_start = on_start; }
|
||||
void SetOnExit(std::function<void()> on_exit) { m_on_exit = on_exit; }
|
||||
|
||||
int AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask);
|
||||
void RemoveListener(int uid);
|
||||
|
||||
// Notification events
|
||||
void NotifySource(const wpi::Twine& name, CS_Source source,
|
||||
CS_EventKind kind);
|
||||
void NotifySource(const SourceImpl& source, CS_EventKind kind);
|
||||
void NotifySourceVideoMode(const SourceImpl& source, const VideoMode& mode);
|
||||
void NotifySourceProperty(const SourceImpl& source, CS_EventKind kind,
|
||||
const wpi::Twine& propertyName, int property,
|
||||
CS_PropertyKind propertyKind, int value,
|
||||
const wpi::Twine& valueStr);
|
||||
void NotifySink(const wpi::Twine& name, CS_Sink sink, CS_EventKind kind);
|
||||
void NotifySink(const SinkImpl& sink, CS_EventKind kind);
|
||||
void NotifySinkSourceChanged(const wpi::Twine& name, CS_Sink sink,
|
||||
CS_Source source);
|
||||
void NotifySinkProperty(const SinkImpl& sink, CS_EventKind kind,
|
||||
const wpi::Twine& propertyName, int property,
|
||||
CS_PropertyKind propertyKind, int value,
|
||||
const wpi::Twine& valueStr);
|
||||
void NotifyNetworkInterfacesChanged();
|
||||
void NotifyTelemetryUpdated();
|
||||
|
||||
private:
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
static bool s_destroyed;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_NOTIFIER_H_
|
||||
282
cscore/src/main/native/cpp/PropertyContainer.cpp
Normal file
282
cscore/src/main/native/cpp/PropertyContainer.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "PropertyContainer.h"
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/json.h>
|
||||
|
||||
using namespace cs;
|
||||
|
||||
int PropertyContainer::GetPropertyIndex(const wpi::Twine& name) const {
|
||||
// We can't fail, so instead we create a new index if caching fails.
|
||||
CS_Status status = 0;
|
||||
if (!m_properties_cached) CacheProperties(&status);
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
wpi::SmallVector<char, 64> nameBuf;
|
||||
int& ndx = m_properties[name.toStringRef(nameBuf)];
|
||||
if (ndx == 0) {
|
||||
// create a new index
|
||||
ndx = m_propertyData.size() + 1;
|
||||
m_propertyData.emplace_back(CreateEmptyProperty(name));
|
||||
}
|
||||
return ndx;
|
||||
}
|
||||
|
||||
wpi::ArrayRef<int> PropertyContainer::EnumerateProperties(
|
||||
wpi::SmallVectorImpl<int>& vec, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status))
|
||||
return wpi::ArrayRef<int>{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
for (int i = 0; i < static_cast<int>(m_propertyData.size()); ++i) {
|
||||
if (m_propertyData[i]) vec.push_back(i + 1);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
CS_PropertyKind PropertyContainer::GetPropertyKind(int property) const {
|
||||
CS_Status status = 0;
|
||||
if (!m_properties_cached && !CacheProperties(&status)) return CS_PROP_NONE;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) return CS_PROP_NONE;
|
||||
return prop->propKind;
|
||||
}
|
||||
|
||||
wpi::StringRef PropertyContainer::GetPropertyName(
|
||||
int property, wpi::SmallVectorImpl<char>& buf, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return wpi::StringRef{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return wpi::StringRef{};
|
||||
}
|
||||
// safe to not copy because we never modify it after caching
|
||||
return prop->name;
|
||||
}
|
||||
|
||||
int PropertyContainer::GetProperty(int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
if ((prop->propKind & (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) ==
|
||||
0) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return 0;
|
||||
}
|
||||
return prop->value;
|
||||
}
|
||||
|
||||
void PropertyContainer::SetProperty(int property, int value,
|
||||
CS_Status* status) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
|
||||
// Guess it's integer if we've set before get
|
||||
if (prop->propKind == CS_PROP_NONE) prop->propKind = CS_PROP_INTEGER;
|
||||
|
||||
if ((prop->propKind & (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) ==
|
||||
0) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePropertyValue(property, false, value, wpi::Twine{});
|
||||
}
|
||||
|
||||
int PropertyContainer::GetPropertyMin(int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
return prop->minimum;
|
||||
}
|
||||
|
||||
int PropertyContainer::GetPropertyMax(int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
return prop->maximum;
|
||||
}
|
||||
|
||||
int PropertyContainer::GetPropertyStep(int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
return prop->step;
|
||||
}
|
||||
|
||||
int PropertyContainer::GetPropertyDefault(int property,
|
||||
CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return 0;
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return 0;
|
||||
}
|
||||
return prop->defaultValue;
|
||||
}
|
||||
|
||||
wpi::StringRef PropertyContainer::GetStringProperty(
|
||||
int property, wpi::SmallVectorImpl<char>& buf, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) return wpi::StringRef{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return wpi::StringRef{};
|
||||
}
|
||||
if (prop->propKind != CS_PROP_STRING) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return wpi::StringRef{};
|
||||
}
|
||||
buf.clear();
|
||||
buf.append(prop->valueStr.begin(), prop->valueStr.end());
|
||||
return wpi::StringRef(buf.data(), buf.size());
|
||||
}
|
||||
|
||||
void PropertyContainer::SetStringProperty(int property, const wpi::Twine& value,
|
||||
CS_Status* status) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return;
|
||||
}
|
||||
|
||||
// Guess it's string if we've set before get
|
||||
if (prop->propKind == CS_PROP_NONE) prop->propKind = CS_PROP_STRING;
|
||||
|
||||
if (prop->propKind != CS_PROP_STRING) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePropertyValue(property, true, 0, value);
|
||||
}
|
||||
|
||||
std::vector<std::string> PropertyContainer::GetEnumPropertyChoices(
|
||||
int property, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status))
|
||||
return std::vector<std::string>{};
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
*status = CS_INVALID_PROPERTY;
|
||||
return std::vector<std::string>{};
|
||||
}
|
||||
if (prop->propKind != CS_PROP_ENUM) {
|
||||
*status = CS_WRONG_PROPERTY_TYPE;
|
||||
return std::vector<std::string>{};
|
||||
}
|
||||
return prop->enumChoices;
|
||||
}
|
||||
|
||||
std::unique_ptr<PropertyImpl> PropertyContainer::CreateEmptyProperty(
|
||||
const wpi::Twine& name) const {
|
||||
return wpi::make_unique<PropertyImpl>(name);
|
||||
}
|
||||
|
||||
bool PropertyContainer::CacheProperties(CS_Status* status) const {
|
||||
// Doesn't need to do anything.
|
||||
m_properties_cached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PropertyContainer::SetPropertiesJson(const wpi::json& config,
|
||||
wpi::Logger& logger,
|
||||
wpi::StringRef logName,
|
||||
CS_Status* status) {
|
||||
for (auto&& prop : config) {
|
||||
std::string name;
|
||||
try {
|
||||
name = prop.at("name").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
WPI_WARNING(logger,
|
||||
logName << ": SetConfigJson: could not read property name: "
|
||||
<< e.what());
|
||||
continue;
|
||||
}
|
||||
int n = GetPropertyIndex(name);
|
||||
try {
|
||||
auto& v = prop.at("value");
|
||||
if (v.is_string()) {
|
||||
std::string val = v.get<std::string>();
|
||||
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
|
||||
<< name << "' to '" << val << '\'');
|
||||
SetStringProperty(n, val, status);
|
||||
} else if (v.is_boolean()) {
|
||||
bool val = v.get<bool>();
|
||||
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
|
||||
<< name << "' to " << val);
|
||||
SetProperty(n, val, status);
|
||||
} else {
|
||||
int val = v.get<int>();
|
||||
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
|
||||
<< name << "' to " << val);
|
||||
SetProperty(n, val, status);
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
WPI_WARNING(logger,
|
||||
logName << ": SetConfigJson: could not read property value: "
|
||||
<< e.what());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wpi::json PropertyContainer::GetPropertiesJsonObject(CS_Status* status) {
|
||||
wpi::json j;
|
||||
wpi::SmallVector<int, 32> propVec;
|
||||
for (int p : EnumerateProperties(propVec, status)) {
|
||||
wpi::json prop;
|
||||
wpi::SmallString<128> strBuf;
|
||||
prop.emplace("name", GetPropertyName(p, strBuf, status));
|
||||
switch (GetPropertyKind(p)) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
prop.emplace("value", static_cast<bool>(GetProperty(p, status)));
|
||||
break;
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM:
|
||||
prop.emplace("value", GetProperty(p, status));
|
||||
break;
|
||||
case CS_PROP_STRING:
|
||||
prop.emplace("value", GetStringProperty(p, strBuf, status));
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
j.emplace_back(prop);
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
128
cscore/src/main/native/cpp/PropertyContainer.h
Normal file
128
cscore/src/main/native/cpp/PropertyContainer.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_PROPERTYCONTAINER_H_
|
||||
#define CSCORE_PROPERTYCONTAINER_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/ArrayRef.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "PropertyImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace wpi {
|
||||
class Logger;
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
class PropertyContainer {
|
||||
public:
|
||||
virtual ~PropertyContainer() = default;
|
||||
|
||||
int GetPropertyIndex(const wpi::Twine& name) const;
|
||||
wpi::ArrayRef<int> EnumerateProperties(wpi::SmallVectorImpl<int>& vec,
|
||||
CS_Status* status) const;
|
||||
CS_PropertyKind GetPropertyKind(int property) const;
|
||||
wpi::StringRef GetPropertyName(int property, wpi::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) const;
|
||||
int GetProperty(int property, CS_Status* status) const;
|
||||
virtual void SetProperty(int property, int value, CS_Status* status);
|
||||
int GetPropertyMin(int property, CS_Status* status) const;
|
||||
int GetPropertyMax(int property, CS_Status* status) const;
|
||||
int GetPropertyStep(int property, CS_Status* status) const;
|
||||
int GetPropertyDefault(int property, CS_Status* status) const;
|
||||
wpi::StringRef GetStringProperty(int property,
|
||||
wpi::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) const;
|
||||
virtual void SetStringProperty(int property, const wpi::Twine& value,
|
||||
CS_Status* status);
|
||||
std::vector<std::string> GetEnumPropertyChoices(int property,
|
||||
CS_Status* status) const;
|
||||
|
||||
bool SetPropertiesJson(const wpi::json& config, wpi::Logger& logger,
|
||||
wpi::StringRef logName, CS_Status* status);
|
||||
wpi::json GetPropertiesJsonObject(CS_Status* status);
|
||||
|
||||
protected:
|
||||
// Get a property; must be called with m_mutex held.
|
||||
PropertyImpl* GetProperty(int property) {
|
||||
if (property <= 0 || static_cast<size_t>(property) > m_propertyData.size())
|
||||
return nullptr;
|
||||
return m_propertyData[property - 1].get();
|
||||
}
|
||||
const PropertyImpl* GetProperty(int property) const {
|
||||
if (property <= 0 || static_cast<size_t>(property) > m_propertyData.size())
|
||||
return nullptr;
|
||||
return m_propertyData[property - 1].get();
|
||||
}
|
||||
// Create or update a property; must be called with m_mutex held.
|
||||
// @tparam NewFunc functor that returns a std::unique_ptr<PropertyImpl>
|
||||
// @tparam UpdateFunc functor that takes a PropertyImpl&.
|
||||
template <typename NewFunc, typename UpdateFunc>
|
||||
int CreateOrUpdateProperty(const wpi::Twine& name, NewFunc newFunc,
|
||||
UpdateFunc updateFunc) {
|
||||
wpi::SmallVector<char, 64> nameBuf;
|
||||
int& ndx = m_properties[name.toStringRef(nameBuf)];
|
||||
if (ndx == 0) {
|
||||
// create a new index
|
||||
ndx = m_propertyData.size() + 1;
|
||||
m_propertyData.emplace_back(newFunc());
|
||||
} else {
|
||||
// update existing
|
||||
updateFunc(*GetProperty(ndx));
|
||||
}
|
||||
return ndx;
|
||||
}
|
||||
template <typename NewFunc>
|
||||
int CreateProperty(const wpi::Twine& name, NewFunc newFunc) {
|
||||
return CreateOrUpdateProperty(name, newFunc, [](PropertyImpl&) {});
|
||||
}
|
||||
|
||||
// Create an "empty" property. This is called by GetPropertyIndex to create
|
||||
// properties that don't exist (as GetPropertyIndex can't fail).
|
||||
// Note: called with m_mutex held.
|
||||
// The default implementation simply creates a PropertyImpl object.
|
||||
virtual std::unique_ptr<PropertyImpl> CreateEmptyProperty(
|
||||
const wpi::Twine& name) const;
|
||||
|
||||
// Cache properties. Implementations must return false and set status to
|
||||
// CS_SOURCE_IS_DISCONNECTED if not possible to cache.
|
||||
// The default implementation simply sets m_property_cached to true.
|
||||
virtual bool CacheProperties(CS_Status* status) const;
|
||||
|
||||
virtual void NotifyPropertyCreated(int propIndex, PropertyImpl& prop) = 0;
|
||||
|
||||
// Update property value; must be called with m_mutex held.
|
||||
virtual void UpdatePropertyValue(int property, bool setString, int value,
|
||||
const wpi::Twine& valueStr) = 0;
|
||||
|
||||
// Whether CacheProperties() has been successful at least once (and thus
|
||||
// should not be called again)
|
||||
mutable std::atomic_bool m_properties_cached{false};
|
||||
|
||||
mutable wpi::mutex m_mutex;
|
||||
|
||||
// Cached properties (protected with m_mutex)
|
||||
mutable std::vector<std::unique_ptr<PropertyImpl>> m_propertyData;
|
||||
mutable wpi::StringMap<int> m_properties;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_PROPERTYCONTAINER_H_
|
||||
65
cscore/src/main/native/cpp/PropertyImpl.cpp
Normal file
65
cscore/src/main/native/cpp/PropertyImpl.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "PropertyImpl.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
PropertyImpl::PropertyImpl(const wpi::Twine& name_) : name{name_.str()} {}
|
||||
PropertyImpl::PropertyImpl(const wpi::Twine& name_, CS_PropertyKind kind_,
|
||||
int step_, int defaultValue_, int value_)
|
||||
: name{name_.str()},
|
||||
propKind{kind_},
|
||||
step{step_},
|
||||
defaultValue{defaultValue_},
|
||||
value{value_} {}
|
||||
PropertyImpl::PropertyImpl(const wpi::Twine& name_, CS_PropertyKind kind_,
|
||||
int minimum_, int maximum_, int step_,
|
||||
int defaultValue_, int value_)
|
||||
: name{name_.str()},
|
||||
propKind{kind_},
|
||||
hasMinimum{true},
|
||||
hasMaximum{true},
|
||||
minimum{minimum_},
|
||||
maximum{maximum_},
|
||||
step{step_},
|
||||
defaultValue{defaultValue_},
|
||||
value{value_} {}
|
||||
|
||||
void PropertyImpl::SetValue(int v) {
|
||||
int oldValue = value;
|
||||
if (hasMinimum && v < minimum)
|
||||
value = minimum;
|
||||
else if (hasMaximum && v > maximum)
|
||||
value = maximum;
|
||||
else
|
||||
value = v;
|
||||
bool wasValueSet = valueSet;
|
||||
valueSet = true;
|
||||
if (!wasValueSet || value != oldValue) changed();
|
||||
}
|
||||
|
||||
void PropertyImpl::SetValue(const wpi::Twine& v) {
|
||||
bool valueChanged = false;
|
||||
std::string vStr = v.str();
|
||||
if (valueStr != vStr) {
|
||||
valueStr = vStr;
|
||||
valueChanged = true;
|
||||
}
|
||||
bool wasValueSet = valueSet;
|
||||
valueSet = true;
|
||||
if (!wasValueSet || valueChanged) changed();
|
||||
}
|
||||
|
||||
void PropertyImpl::SetDefaultValue(int v) {
|
||||
if (hasMinimum && v < minimum)
|
||||
defaultValue = minimum;
|
||||
else if (hasMaximum && v > maximum)
|
||||
defaultValue = maximum;
|
||||
else
|
||||
defaultValue = v;
|
||||
}
|
||||
58
cscore/src/main/native/cpp/PropertyImpl.h
Normal file
58
cscore/src/main/native/cpp/PropertyImpl.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_PROPERTYIMPL_H_
|
||||
#define CSCORE_PROPERTYIMPL_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
#include "cscore_c.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
// Property data
|
||||
class PropertyImpl {
|
||||
public:
|
||||
PropertyImpl() = default;
|
||||
explicit PropertyImpl(const wpi::Twine& name_);
|
||||
PropertyImpl(const wpi::Twine& name_, CS_PropertyKind kind_, int step_,
|
||||
int defaultValue_, int value_);
|
||||
PropertyImpl(const wpi::Twine& name_, CS_PropertyKind kind_, int minimum_,
|
||||
int maximum_, int step_, int defaultValue_, int value_);
|
||||
virtual ~PropertyImpl() = default;
|
||||
PropertyImpl(const PropertyImpl& oth) = delete;
|
||||
PropertyImpl& operator=(const PropertyImpl& oth) = delete;
|
||||
|
||||
void SetValue(int v);
|
||||
void SetValue(const wpi::Twine& v);
|
||||
void SetDefaultValue(int v);
|
||||
|
||||
std::string name;
|
||||
CS_PropertyKind propKind{CS_PROP_NONE};
|
||||
bool hasMinimum{false};
|
||||
bool hasMaximum{false};
|
||||
int minimum{0};
|
||||
int maximum{100};
|
||||
int step{1};
|
||||
int defaultValue{0};
|
||||
int value{0};
|
||||
std::string valueStr;
|
||||
std::vector<std::string> enumChoices;
|
||||
bool valueSet{false};
|
||||
|
||||
// emitted when value changes
|
||||
wpi::sig::Signal<> changed;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_PROPERTYIMPL_H_
|
||||
173
cscore/src/main/native/cpp/SinkImpl.cpp
Normal file
173
cscore/src/main/native/cpp/SinkImpl.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "SinkImpl.h"
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
#include "Instance.h"
|
||||
#include "Notifier.h"
|
||||
#include "SourceImpl.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
SinkImpl::SinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry)
|
||||
: m_logger(logger),
|
||||
m_notifier(notifier),
|
||||
m_telemetry(telemetry),
|
||||
m_name{name.str()} {}
|
||||
|
||||
SinkImpl::~SinkImpl() {
|
||||
if (m_source) {
|
||||
if (m_enabledCount > 0) m_source->DisableSink();
|
||||
m_source->RemoveSink();
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::SetDescription(const wpi::Twine& description) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_description = description.str();
|
||||
}
|
||||
|
||||
wpi::StringRef SinkImpl::GetDescription(wpi::SmallVectorImpl<char>& buf) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
buf.append(m_description.begin(), m_description.end());
|
||||
return wpi::StringRef{buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
void SinkImpl::Enable() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
++m_enabledCount;
|
||||
if (m_enabledCount == 1) {
|
||||
if (m_source) m_source->EnableSink();
|
||||
m_notifier.NotifySink(*this, CS_SINK_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::Disable() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
--m_enabledCount;
|
||||
if (m_enabledCount == 0) {
|
||||
if (m_source) m_source->DisableSink();
|
||||
m_notifier.NotifySink(*this, CS_SINK_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::SetEnabled(bool enabled) {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (enabled && m_enabledCount == 0) {
|
||||
if (m_source) m_source->EnableSink();
|
||||
m_enabledCount = 1;
|
||||
m_notifier.NotifySink(*this, CS_SINK_ENABLED);
|
||||
} else if (!enabled && m_enabledCount > 0) {
|
||||
if (m_source) m_source->DisableSink();
|
||||
m_enabledCount = 0;
|
||||
m_notifier.NotifySink(*this, CS_SINK_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::SetSource(std::shared_ptr<SourceImpl> source) {
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (m_source == source) return;
|
||||
if (m_source) {
|
||||
if (m_enabledCount > 0) m_source->DisableSink();
|
||||
m_source->RemoveSink();
|
||||
}
|
||||
m_source = source;
|
||||
if (m_source) {
|
||||
m_source->AddSink();
|
||||
if (m_enabledCount > 0) m_source->EnableSink();
|
||||
}
|
||||
}
|
||||
SetSourceImpl(source);
|
||||
}
|
||||
|
||||
std::string SinkImpl::GetError() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (!m_source) return "no source connected";
|
||||
return m_source->GetCurFrame().GetError();
|
||||
}
|
||||
|
||||
wpi::StringRef SinkImpl::GetError(wpi::SmallVectorImpl<char>& buf) const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
if (!m_source) return "no source connected";
|
||||
// Make a copy as it's shared data
|
||||
wpi::StringRef error = m_source->GetCurFrame().GetError();
|
||||
buf.clear();
|
||||
buf.append(error.data(), error.data() + error.size());
|
||||
return wpi::StringRef{buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
bool SinkImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) {
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(config);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
SWARNING("SetConfigJson: parse error at byte " << e.byte << ": "
|
||||
<< e.what());
|
||||
*status = CS_PROPERTY_WRITE_FAILED;
|
||||
return false;
|
||||
}
|
||||
return SetConfigJson(j, status);
|
||||
}
|
||||
|
||||
bool SinkImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
|
||||
if (config.count("properties") != 0)
|
||||
SetPropertiesJson(config.at("properties"), m_logger, GetName(), status);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string SinkImpl::GetConfigJson(CS_Status* status) {
|
||||
std::string rv;
|
||||
wpi::raw_string_ostream os(rv);
|
||||
GetConfigJsonObject(status).dump(os, 4);
|
||||
os.flush();
|
||||
return rv;
|
||||
}
|
||||
|
||||
wpi::json SinkImpl::GetConfigJsonObject(CS_Status* status) {
|
||||
wpi::json j;
|
||||
|
||||
wpi::json props = GetPropertiesJsonObject(status);
|
||||
if (props.is_array()) j.emplace("properties", props);
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
void SinkImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) {
|
||||
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CREATED, prop.name,
|
||||
propIndex, prop.propKind, prop.value,
|
||||
prop.valueStr);
|
||||
// also notify choices updated event for enum types
|
||||
if (prop.propKind == CS_PROP_ENUM)
|
||||
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CHOICES_UPDATED,
|
||||
prop.name, propIndex, prop.propKind,
|
||||
prop.value, wpi::Twine{});
|
||||
}
|
||||
|
||||
void SinkImpl::UpdatePropertyValue(int property, bool setString, int value,
|
||||
const wpi::Twine& valueStr) {
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) return;
|
||||
|
||||
if (setString)
|
||||
prop->SetValue(valueStr);
|
||||
else
|
||||
prop->SetValue(value);
|
||||
|
||||
// Only notify updates after we've notified created
|
||||
if (m_properties_cached) {
|
||||
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_VALUE_UPDATED,
|
||||
prop->name, property, prop->propKind,
|
||||
prop->value, prop->valueStr);
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {}
|
||||
85
cscore/src/main/native/cpp/SinkImpl.h
Normal file
85
cscore/src/main/native/cpp/SinkImpl.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_SINKIMPL_H_
|
||||
#define CSCORE_SINKIMPL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Frame;
|
||||
class Notifier;
|
||||
class Telemetry;
|
||||
|
||||
class SinkImpl : public PropertyContainer {
|
||||
public:
|
||||
explicit SinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry);
|
||||
virtual ~SinkImpl();
|
||||
SinkImpl(const SinkImpl& queue) = delete;
|
||||
SinkImpl& operator=(const SinkImpl& queue) = delete;
|
||||
|
||||
wpi::StringRef GetName() const { return m_name; }
|
||||
|
||||
void SetDescription(const wpi::Twine& description);
|
||||
wpi::StringRef GetDescription(wpi::SmallVectorImpl<char>& buf) const;
|
||||
|
||||
void Enable();
|
||||
void Disable();
|
||||
void SetEnabled(bool enabled);
|
||||
|
||||
void SetSource(std::shared_ptr<SourceImpl> source);
|
||||
|
||||
std::shared_ptr<SourceImpl> GetSource() const {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
return m_source;
|
||||
}
|
||||
|
||||
std::string GetError() const;
|
||||
wpi::StringRef GetError(wpi::SmallVectorImpl<char>& buf) const;
|
||||
|
||||
bool SetConfigJson(wpi::StringRef config, CS_Status* status);
|
||||
virtual bool SetConfigJson(const wpi::json& config, CS_Status* status);
|
||||
std::string GetConfigJson(CS_Status* status);
|
||||
virtual wpi::json GetConfigJsonObject(CS_Status* status);
|
||||
|
||||
protected:
|
||||
// PropertyContainer implementation
|
||||
void NotifyPropertyCreated(int propIndex, PropertyImpl& prop) override;
|
||||
void UpdatePropertyValue(int property, bool setString, int value,
|
||||
const wpi::Twine& valueStr) override;
|
||||
|
||||
virtual void SetSourceImpl(std::shared_ptr<SourceImpl> source);
|
||||
|
||||
protected:
|
||||
wpi::Logger& m_logger;
|
||||
Notifier& m_notifier;
|
||||
Telemetry& m_telemetry;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::string m_description;
|
||||
std::shared_ptr<SourceImpl> m_source;
|
||||
int m_enabledCount{0};
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_SINKIMPL_H_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user