mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
989 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f83ff439c4 | ||
|
|
983796e952 | ||
|
|
40be0c20e2 | ||
|
|
f1e9d4568c | ||
|
|
f4f57cce38 | ||
|
|
c87ad5703c | ||
|
|
95df13b7cb | ||
|
|
bfa1516491 | ||
|
|
36c9ff22b3 | ||
|
|
17f54a7977 | ||
|
|
574986d0fd | ||
|
|
d5ce2e835f | ||
|
|
6201554ba1 | ||
|
|
75dee4e91d | ||
|
|
e1b4e0db66 | ||
|
|
0d37b09bb4 | ||
|
|
18767b68ff | ||
|
|
7975615c6c | ||
|
|
90f09b197e | ||
|
|
687e97d523 | ||
|
|
c1675001fa | ||
|
|
69cc45a2a0 | ||
|
|
ece96b5834 | ||
|
|
383ed7530b | ||
|
|
7f620c758f | ||
|
|
fb50eea526 | ||
|
|
63f757a9da | ||
|
|
4ec02d46b9 | ||
|
|
527afb66df | ||
|
|
5502758a7e | ||
|
|
faa1776407 | ||
|
|
3d7fb8c8dd | ||
|
|
b5f814d71e | ||
|
|
6eafd4156a | ||
|
|
df1e4eaa06 | ||
|
|
d2bef81b03 | ||
|
|
7b3dffd072 | ||
|
|
1a6e9998fb | ||
|
|
9ab9561803 | ||
|
|
11f0b80228 | ||
|
|
85e2dbd69e | ||
|
|
b693c24411 | ||
|
|
54af47fc43 | ||
|
|
1c1e3b0875 | ||
|
|
2c9334d53f | ||
|
|
5c747a5b5d | ||
|
|
cbee3adb2e | ||
|
|
e3293d6887 | ||
|
|
9d1a42631e | ||
|
|
13f522e81d | ||
|
|
28f13f7514 | ||
|
|
2f86f818cc | ||
|
|
1ffcd83993 | ||
|
|
9b46c2e597 | ||
|
|
ad1b63890a | ||
|
|
ad79bd0957 | ||
|
|
021823bcba | ||
|
|
7b0e171e74 | ||
|
|
b24f106ce7 | ||
|
|
20f056668f | ||
|
|
f9439c785f | ||
|
|
5e364fb88b | ||
|
|
69dc9b8b49 | ||
|
|
42237d9728 | ||
|
|
17bd25d497 | ||
|
|
a2ac1f8067 | ||
|
|
fdb1b2d562 | ||
|
|
fd2bf5ea25 | ||
|
|
c874929e9d | ||
|
|
db5b9d3cd7 | ||
|
|
12ab66be43 | ||
|
|
27d25a59d8 | ||
|
|
71991254a5 | ||
|
|
50e8469a59 | ||
|
|
3f45debca0 | ||
|
|
627b614ab5 | ||
|
|
f11d390b22 | ||
|
|
f1e50b6f9b | ||
|
|
fdf3340a7d | ||
|
|
02513be220 | ||
|
|
d29ce42f08 | ||
|
|
aa6fc7fe04 | ||
|
|
c91341a375 | ||
|
|
3de7e26a36 | ||
|
|
63cc66e8a7 | ||
|
|
92a090dec3 | ||
|
|
8b0ef8a271 | ||
|
|
a360f1901e | ||
|
|
ed2d5f3af4 | ||
|
|
b935dd1fe8 | ||
|
|
6e1f60a36d | ||
|
|
210dfcfa4f | ||
|
|
ec10a9e3fe | ||
|
|
217d146378 | ||
|
|
3e2a43e2af | ||
|
|
2e0a19c1cb | ||
|
|
054748234e | ||
|
|
53873e9ddb | ||
|
|
c21d0aef7e | ||
|
|
e505a56910 | ||
|
|
f4278f32c4 | ||
|
|
e9a6db02f6 | ||
|
|
63fe32ddd9 | ||
|
|
710652073a | ||
|
|
dded49cd83 | ||
|
|
0f753dc654 | ||
|
|
c505a423af | ||
|
|
1cff2545b1 | ||
|
|
3d994d6e67 | ||
|
|
7175f8d569 | ||
|
|
d4a9862a18 | ||
|
|
3d4e9fbb1a | ||
|
|
4a59b038ec | ||
|
|
6795744aea | ||
|
|
798fe04905 | ||
|
|
1460de95c1 | ||
|
|
358950d0c2 | ||
|
|
caef82d96f | ||
|
|
dff7dd7087 | ||
|
|
79bb881a34 | ||
|
|
f0763e3865 | ||
|
|
a8dfa96db0 | ||
|
|
7ceb8a3da5 | ||
|
|
9344059e0e | ||
|
|
ee2b1afe5b | ||
|
|
31f47f2eac | ||
|
|
69a9bafcd5 | ||
|
|
dbecdf5d9b | ||
|
|
967574a78a | ||
|
|
bb4f6f3993 | ||
|
|
fdc955a713 | ||
|
|
322c67f7a5 | ||
|
|
7aa7f43c7e | ||
|
|
8ef5505a48 | ||
|
|
481f2c9479 | ||
|
|
924ebf7c77 | ||
|
|
d67af7a9dc | ||
|
|
e6be119ab5 | ||
|
|
ead06e741a | ||
|
|
fbabba29ed | ||
|
|
ff9b0f5396 | ||
|
|
52a31f619b | ||
|
|
743e8ac828 | ||
|
|
cecec39281 | ||
|
|
280254e231 | ||
|
|
97a1aa04cb | ||
|
|
e14644572b | ||
|
|
55e0a096e7 | ||
|
|
e68e6b6fae | ||
|
|
aa03e1610f | ||
|
|
2b969dd20d | ||
|
|
c51984c9d9 | ||
|
|
459060532a | ||
|
|
016ee1c554 | ||
|
|
26e0c9d54c | ||
|
|
e6584da7c8 | ||
|
|
630ad66cc8 | ||
|
|
222b9a0309 | ||
|
|
235a1765d2 | ||
|
|
bff8a5d854 | ||
|
|
fd65ed006a | ||
|
|
52478df3cf | ||
|
|
7732841cdf | ||
|
|
10aa33d899 | ||
|
|
bd6fa7b0dc | ||
|
|
536f45eff5 | ||
|
|
bb1dcc005c | ||
|
|
ea313d3517 | ||
|
|
78a12f7ed2 | ||
|
|
281f4eb3a0 | ||
|
|
dabaab4987 | ||
|
|
61ab2a81a5 | ||
|
|
d9b511c69f | ||
|
|
4d9e72e313 | ||
|
|
616a3eb6a6 | ||
|
|
3a8f035014 | ||
|
|
be2e0f11da | ||
|
|
fec76dc9eb | ||
|
|
48826208fd | ||
|
|
dd4fda6f5d | ||
|
|
1dafb54621 | ||
|
|
19f9567098 | ||
|
|
b3d1f06e5d | ||
|
|
f17069153d | ||
|
|
8a7d7b9c54 | ||
|
|
df7649d56f | ||
|
|
58cc227cba | ||
|
|
76be4a1d10 | ||
|
|
496c9300fc | ||
|
|
5b7780cf7c | ||
|
|
421f4a1f2e | ||
|
|
55a5d4bbdd | ||
|
|
666ae0c4f1 | ||
|
|
ca3afc074c | ||
|
|
030077fb47 | ||
|
|
61ddff8b6e | ||
|
|
27fbfe149e | ||
|
|
349f655cd9 | ||
|
|
f5c0a15967 | ||
|
|
d822ae0890 | ||
|
|
2d82adb2ba | ||
|
|
56b21274d1 | ||
|
|
76329ba5a6 | ||
|
|
4d5c5fffb4 | ||
|
|
29234c330b | ||
|
|
690d291818 | ||
|
|
8cfb3fc301 | ||
|
|
69ab3fa831 | ||
|
|
79d6c76084 | ||
|
|
f15d2fc194 | ||
|
|
99aa4828c5 | ||
|
|
93969f487c | ||
|
|
61407c6596 | ||
|
|
6c95a56f3a | ||
|
|
283f53a961 | ||
|
|
ef8fb8216e | ||
|
|
6293d54296 | ||
|
|
c20754e62c | ||
|
|
c621e36929 | ||
|
|
484ff0f32b | ||
|
|
adb60124fb | ||
|
|
767d8ac240 | ||
|
|
4648ecba17 | ||
|
|
0a08ca657d | ||
|
|
48f35ccd32 | ||
|
|
3558089a22 | ||
|
|
47b735c51f | ||
|
|
e60991974e | ||
|
|
3a16d17f8f | ||
|
|
08ddb351c7 | ||
|
|
3f251a2459 | ||
|
|
a97f08eac8 | ||
|
|
92ff097d7e | ||
|
|
0eb0370bd3 | ||
|
|
b329dfed1f | ||
|
|
62dc2e5ad5 | ||
|
|
7ea81fa627 | ||
|
|
8ddf77a3d3 | ||
|
|
fde89465ac | ||
|
|
aa1d78a3cd | ||
|
|
6dfe077775 | ||
|
|
dd6dc866a6 | ||
|
|
3bf68884f5 | ||
|
|
e1b2eceea5 | ||
|
|
7ce11fecb6 | ||
|
|
cbc6145977 | ||
|
|
24f7fb797b | ||
|
|
71f1d397bf | ||
|
|
00e83c155e | ||
|
|
1f186a0afe | ||
|
|
3c7075d0d2 | ||
|
|
c1d7bd1595 | ||
|
|
04d1d3d5a6 | ||
|
|
01bc0529e7 | ||
|
|
181231f3ff | ||
|
|
58569f0315 | ||
|
|
5903fcf718 | ||
|
|
1a820b23a5 | ||
|
|
67b767b152 | ||
|
|
74bf710bb6 | ||
|
|
1d3b428a75 | ||
|
|
749bc5caf7 | ||
|
|
3b7cbc62c4 | ||
|
|
dff49d8e7b | ||
|
|
b23883e65f | ||
|
|
c2e30c7fc4 | ||
|
|
37216250b8 | ||
|
|
dc82894038 | ||
|
|
f3a350148e | ||
|
|
5e49f50e22 | ||
|
|
91ff97f721 | ||
|
|
daaa488dd6 | ||
|
|
bad05a6827 | ||
|
|
89d3ed98c7 | ||
|
|
1187ace2a2 | ||
|
|
9704fb054c | ||
|
|
b10148a438 | ||
|
|
1b4bcd09fe | ||
|
|
245cd94a4c | ||
|
|
8a24a0b129 | ||
|
|
b362c1fa50 | ||
|
|
0414fa5a51 | ||
|
|
191dd16d7b | ||
|
|
31a03928c9 | ||
|
|
185a8b6773 | ||
|
|
b7501cfbda | ||
|
|
1dc90dad7a | ||
|
|
45a4d148f5 | ||
|
|
93ab12b54b | ||
|
|
fb07444b70 | ||
|
|
a8ba2f7364 | ||
|
|
171bcb315a | ||
|
|
911df1af15 | ||
|
|
a165ff4523 | ||
|
|
5cfb3b1e60 | ||
|
|
82b3b78238 | ||
|
|
8a82ff6691 | ||
|
|
2807678551 | ||
|
|
e96e0aa73c | ||
|
|
d06c2619b2 | ||
|
|
6b18455d11 | ||
|
|
217977d9cb | ||
|
|
b9f186e47c | ||
|
|
d7d56f3536 | ||
|
|
2e3c764369 | ||
|
|
7236e53813 | ||
|
|
e7f7350405 | ||
|
|
81f0266a87 | ||
|
|
eb1a2e3695 | ||
|
|
6cfd3739d3 | ||
|
|
77b8386de7 | ||
|
|
a002bd3427 | ||
|
|
202b907430 | ||
|
|
d9f4aba953 | ||
|
|
1b08857534 | ||
|
|
eec19892fa | ||
|
|
40abe73391 | ||
|
|
e4f1ef654f | ||
|
|
fa6afb96ce | ||
|
|
c3f98b34f2 | ||
|
|
b7b3e8e87d | ||
|
|
94383a2d54 | ||
|
|
184a4879eb | ||
|
|
bf48908c59 | ||
|
|
66afbccf21 | ||
|
|
97dbe59aea | ||
|
|
d874fdeaec | ||
|
|
97e3938d56 | ||
|
|
87c7e8063f | ||
|
|
fe5beb0114 | ||
|
|
eec8e41b2b | ||
|
|
87bff56a78 | ||
|
|
34e0e4dab4 | ||
|
|
e08ac1467f | ||
|
|
bc25e787cc | ||
|
|
5f11caf6fc | ||
|
|
9be264e176 | ||
|
|
3d28dcbaff | ||
|
|
61c3b44c8a | ||
|
|
e26973a9f1 | ||
|
|
598e2372e5 | ||
|
|
4fb8c7745c | ||
|
|
14131f0b54 | ||
|
|
de41c9653e | ||
|
|
66e0bb0a2c | ||
|
|
5af18e8b70 | ||
|
|
b746bc307c | ||
|
|
3fd3aab909 | ||
|
|
2f1afb6044 | ||
|
|
a016c1040e | ||
|
|
d4e9948220 | ||
|
|
5d1183f5c6 | ||
|
|
c75863d4bd | ||
|
|
5576cbb3b8 | ||
|
|
4a156a692a | ||
|
|
1a8dfb1f1b | ||
|
|
d0a5246580 | ||
|
|
0497e178ca | ||
|
|
3338dced5a | ||
|
|
b6d4a42fa7 | ||
|
|
e7b9ce500b | ||
|
|
d807f9fa87 | ||
|
|
d95a6ad0b0 | ||
|
|
1e8a06b924 | ||
|
|
e72947ab8d | ||
|
|
4bedf7d286 | ||
|
|
5aa9a4b7a0 | ||
|
|
832915ce86 | ||
|
|
0e0c3ec023 | ||
|
|
c75bd39e6a | ||
|
|
d90cb1cff7 | ||
|
|
94dab648bc | ||
|
|
f37c1cf50c | ||
|
|
6b9b19c077 | ||
|
|
c7d558dd6a | ||
|
|
083ec30c66 | ||
|
|
649cda2f47 | ||
|
|
302f1d297c | ||
|
|
cc06f2abad | ||
|
|
1e6c3b9708 | ||
|
|
310e30fdff | ||
|
|
8324b55396 | ||
|
|
9c679f6d5d | ||
|
|
f14f49dc92 | ||
|
|
0a6c0335d0 | ||
|
|
fedb1d4baf | ||
|
|
cfad2a8343 | ||
|
|
6969de4263 | ||
|
|
47994d5960 | ||
|
|
d77a7c74fb | ||
|
|
a2509158c6 | ||
|
|
901146e5b4 | ||
|
|
35627fb64e | ||
|
|
dbca5e29c0 | ||
|
|
ab9e37a1a9 | ||
|
|
da057b2423 | ||
|
|
17c4f5a815 | ||
|
|
0e3143c1d5 | ||
|
|
70084993d8 | ||
|
|
ebe1d20e69 | ||
|
|
6377830a35 | ||
|
|
2ab0f34add | ||
|
|
e176b1d738 | ||
|
|
3199ef3b1d | ||
|
|
b16b447927 | ||
|
|
b924a0fecb | ||
|
|
7883625165 | ||
|
|
14ec7741f9 | ||
|
|
d05169d6d4 | ||
|
|
63c7d35b25 | ||
|
|
db00971622 | ||
|
|
d5275cacf7 | ||
|
|
0fbb41752e | ||
|
|
5c904cf766 | ||
|
|
ac55b260ed | ||
|
|
8207de6bd4 | ||
|
|
fb19858026 | ||
|
|
c81fa7b5a2 | ||
|
|
fcee60346c | ||
|
|
a43d09ac73 | ||
|
|
2630147a96 | ||
|
|
17bb7c77f3 | ||
|
|
7d587052ad | ||
|
|
85e63f49a0 | ||
|
|
dd90d378a4 | ||
|
|
19b93fb237 | ||
|
|
23292e9f42 | ||
|
|
511894e6ae | ||
|
|
e479a8f08c | ||
|
|
944bd89b4e | ||
|
|
6eb5e62aae | ||
|
|
5f5afb6f69 | ||
|
|
05b86c14a7 | ||
|
|
3fdd69b444 | ||
|
|
0ccf540417 | ||
|
|
619389970a | ||
|
|
f7504211e5 | ||
|
|
1b1c546a55 | ||
|
|
6fadbb76d1 | ||
|
|
fa846d01d7 | ||
|
|
28deafaeef | ||
|
|
3fc9795ade | ||
|
|
22fd555454 | ||
|
|
4fc022f4d7 | ||
|
|
5936830b28 | ||
|
|
7f089201d2 | ||
|
|
e21c39e172 | ||
|
|
1054b8c10f | ||
|
|
9002d8e445 | ||
|
|
61ef6ecd95 | ||
|
|
f1fe57109e | ||
|
|
daaefdd391 | ||
|
|
6687548ba9 | ||
|
|
8222c17aa7 | ||
|
|
0eed7f1b89 | ||
|
|
5375781086 | ||
|
|
3090de6ff6 | ||
|
|
2ebd1e3d4a | ||
|
|
ee4b9c6338 | ||
|
|
775bf32a6f | ||
|
|
e36d2bded3 | ||
|
|
0949c28658 | ||
|
|
1e8789f5e5 | ||
|
|
01d0ed97de | ||
|
|
22d108f7ad | ||
|
|
3b223f5c49 | ||
|
|
c352ed7f67 | ||
|
|
ccf713dc1c | ||
|
|
0d97fffb3e | ||
|
|
cf5f284b95 | ||
|
|
1837f502a7 | ||
|
|
89467a0ef9 | ||
|
|
0f92e73f56 | ||
|
|
b9e4975b77 | ||
|
|
7225857fcc | ||
|
|
f943f5927c | ||
|
|
f966907808 | ||
|
|
983cb899ec | ||
|
|
9e1bf2fdea | ||
|
|
f19ab11f76 | ||
|
|
9039aa8ac5 | ||
|
|
8fc11ac056 | ||
|
|
eebd8307c5 | ||
|
|
0d006cffa9 | ||
|
|
d94bb948eb | ||
|
|
5ded791ef0 | ||
|
|
0ccb54b4f0 | ||
|
|
b3ad29c0c7 | ||
|
|
dd86e8cb42 | ||
|
|
f7ddb635a5 | ||
|
|
2fd3bc34b5 | ||
|
|
283eba0666 | ||
|
|
5c48faa832 | ||
|
|
7bb0aae71e | ||
|
|
07b712a46b | ||
|
|
5e18ce3806 | ||
|
|
e7ad86ded9 | ||
|
|
338389c219 | ||
|
|
847726c0d7 | ||
|
|
a9d50a2a74 | ||
|
|
720baf09b5 | ||
|
|
b502714281 | ||
|
|
92c8215d3d | ||
|
|
348849da96 | ||
|
|
e845d53940 | ||
|
|
c999f59ddd | ||
|
|
ee144473c1 | ||
|
|
af325ed54e | ||
|
|
0d363791fe | ||
|
|
814a2eb641 | ||
|
|
73c22fa960 | ||
|
|
f6f9a12789 | ||
|
|
b14c53cd10 | ||
|
|
23164f2c16 | ||
|
|
8e7d21c1b5 | ||
|
|
973d1cc10e | ||
|
|
3bc4afca9d | ||
|
|
f43afababa | ||
|
|
1b2de09438 | ||
|
|
b0c54b18a5 | ||
|
|
f8e26d785f | ||
|
|
28563abdbc | ||
|
|
f1a869a215 | ||
|
|
9778a805ca | ||
|
|
a008dcf99d | ||
|
|
71c64e668d | ||
|
|
d9b91a1d5f | ||
|
|
d682a1c173 | ||
|
|
ba03333623 | ||
|
|
c7e6e5e006 | ||
|
|
45b1e2f285 | ||
|
|
d9bbd8a74d | ||
|
|
37179184ae | ||
|
|
fc8aba0a12 | ||
|
|
0d749eada3 | ||
|
|
da6984d531 | ||
|
|
df14031042 | ||
|
|
03975c1a85 | ||
|
|
214a924da3 | ||
|
|
9e6a9a2fd4 | ||
|
|
b498ae7e38 | ||
|
|
278645ce51 | ||
|
|
f1ee5514e3 | ||
|
|
3900728f9f | ||
|
|
3dc083d2ef | ||
|
|
653ebd6e88 | ||
|
|
a770e7675d | ||
|
|
6725083be6 | ||
|
|
0b39cadaad | ||
|
|
871b0cf132 | ||
|
|
afa888432a | ||
|
|
a42bd147ff | ||
|
|
2ad8871a54 | ||
|
|
266e210b04 | ||
|
|
0ac9f77619 | ||
|
|
c1bc1d9506 | ||
|
|
deb37986b6 | ||
|
|
ee6c555ce0 | ||
|
|
4395b8487f | ||
|
|
f622d03d1b | ||
|
|
36e6fa7b09 | ||
|
|
8cf6c66f21 | ||
|
|
ac9761beb2 | ||
|
|
90b7a5600f | ||
|
|
4c70baecb1 | ||
|
|
15c14bfebf | ||
|
|
eab8043496 | ||
|
|
53feba1248 | ||
|
|
119821d5af | ||
|
|
4faabf27ec | ||
|
|
191f6e3c09 | ||
|
|
4e51439118 | ||
|
|
e184e187a2 | ||
|
|
592568c9aa | ||
|
|
46c5a547a9 | ||
|
|
3d097a4ffb | ||
|
|
00e32c915c | ||
|
|
d44c340dce | ||
|
|
8c18f1dffe | ||
|
|
c3c386ea5b | ||
|
|
fa09992c46 | ||
|
|
cefcd0c963 | ||
|
|
96c806071d | ||
|
|
0775b35ef8 | ||
|
|
138d9a74d8 | ||
|
|
2a668643dc | ||
|
|
56312cffb5 | ||
|
|
f553b97dba | ||
|
|
b619b1e096 | ||
|
|
06a32fd3ab | ||
|
|
8637ff5c09 | ||
|
|
bd5cf7d3de | ||
|
|
e488eec90c | ||
|
|
6615a23361 | ||
|
|
a91eba8d90 | ||
|
|
d5aebc1231 | ||
|
|
5f4f46bbe6 | ||
|
|
61a2fe6546 | ||
|
|
98499b6604 | ||
|
|
f52d090f9b | ||
|
|
ebb3df46b9 | ||
|
|
8fd6659cf1 | ||
|
|
13ce0bbc63 | ||
|
|
0add4cc25f | ||
|
|
96d84b33bd | ||
|
|
f2dfe7641e | ||
|
|
97403cbb75 | ||
|
|
0663bb1035 | ||
|
|
06f4017a5b | ||
|
|
6493a62c6c | ||
|
|
66c627c682 | ||
|
|
8d6f5f7f8f | ||
|
|
f1cdfd4223 | ||
|
|
972c288df3 | ||
|
|
fd1d2dec71 | ||
|
|
331bd181bf | ||
|
|
231ff37570 | ||
|
|
cec0b203b3 | ||
|
|
e5c9d699a3 | ||
|
|
198fbc1756 | ||
|
|
55aa443d68 | ||
|
|
44d6288416 | ||
|
|
aeae342853 | ||
|
|
d22034da96 | ||
|
|
43788f812d | ||
|
|
95318e14d8 | ||
|
|
67d6f15776 | ||
|
|
6c2353ce2c | ||
|
|
fcdfb2930a | ||
|
|
dcc90341c9 | ||
|
|
7e102c88cd | ||
|
|
e131a3ba49 | ||
|
|
e393be4c63 | ||
|
|
cf4f5f5b2a | ||
|
|
1190059359 | ||
|
|
910b27feb4 | ||
|
|
60ca92d367 | ||
|
|
4f44c5efa1 | ||
|
|
3504095871 | ||
|
|
6eeb49ef75 | ||
|
|
9353b3474a | ||
|
|
dfc98a67f2 | ||
|
|
904c76fa93 | ||
|
|
f0a7ca5c94 | ||
|
|
bb4e9db7e7 | ||
|
|
61f2bfa00e | ||
|
|
49eb7f6e51 | ||
|
|
b5375b835a | ||
|
|
e3e2c5cead | ||
|
|
ef4150877b | ||
|
|
cc00d0fea3 | ||
|
|
91a1e48a7c | ||
|
|
f381113d50 | ||
|
|
a6304959f4 | ||
|
|
57bc6e160d | ||
|
|
03dd5dfebb | ||
|
|
00ef8928b3 | ||
|
|
2b338cea90 | ||
|
|
e64a1a1688 | ||
|
|
2ad196b95e | ||
|
|
70c7bcc209 | ||
|
|
890b248563 | ||
|
|
be2de06b09 | ||
|
|
6175e6974c | ||
|
|
6bc26b734e | ||
|
|
da6b5fd786 | ||
|
|
e97b512f79 | ||
|
|
a1c83fc765 | ||
|
|
57132c46ac | ||
|
|
6a7a26360f | ||
|
|
7a699833c4 | ||
|
|
fcdb8f5d42 | ||
|
|
e827ad8a7a | ||
|
|
caf5b9191b | ||
|
|
163aa8b5af | ||
|
|
bc873e31d0 | ||
|
|
947924e900 | ||
|
|
0827cb2b65 | ||
|
|
d24d48212f | ||
|
|
a23475be57 | ||
|
|
b7d8c5bfa6 | ||
|
|
433e884fad | ||
|
|
1d1c7c705d | ||
|
|
b02b0f910c | ||
|
|
bfbb5dd29d | ||
|
|
f3339f0502 | ||
|
|
5d270b0029 | ||
|
|
dd5a5a079a | ||
|
|
a3fcb01b7b | ||
|
|
afe5b91f63 | ||
|
|
330641ee9f | ||
|
|
d8ec5169e7 | ||
|
|
93d5be6ffc | ||
|
|
2dbaf08eb0 | ||
|
|
9a216153dc | ||
|
|
090ed174af | ||
|
|
4292c8a2d1 | ||
|
|
856dab30bc | ||
|
|
16d5c81c22 | ||
|
|
58e3a77a79 | ||
|
|
16105db7a0 | ||
|
|
6e307b1ef4 | ||
|
|
554491ff19 | ||
|
|
fcd70c3166 | ||
|
|
aaed14e9de | ||
|
|
76ba25607b | ||
|
|
1d8dedea85 | ||
|
|
dfe8245181 | ||
|
|
50569a53a0 | ||
|
|
6ace44e4de | ||
|
|
c4a5eea75b | ||
|
|
87de066369 | ||
|
|
c3c15b16bf | ||
|
|
e6dfe3d2b7 | ||
|
|
fe8699b7f3 | ||
|
|
c751e4ff08 | ||
|
|
99a00ca57c | ||
|
|
c009a7efe4 | ||
|
|
291d7431b0 | ||
|
|
a1cd490fdf | ||
|
|
7a85774085 | ||
|
|
2ed3c4815b | ||
|
|
724c6e118f | ||
|
|
da3a3adc65 | ||
|
|
32013a6360 | ||
|
|
16a2495766 | ||
|
|
05246706f0 | ||
|
|
9355055a7f | ||
|
|
9f16de13fe | ||
|
|
36a987fb9d | ||
|
|
652397f569 | ||
|
|
3333fc56a3 | ||
|
|
47378a17db | ||
|
|
5f320786f5 | ||
|
|
498c6e1f5d | ||
|
|
6c09adc689 | ||
|
|
8bf7ac3005 | ||
|
|
6aebd7167e | ||
|
|
3f03575222 | ||
|
|
9ca43f17a3 | ||
|
|
66ef566c9a | ||
|
|
07ecc5b0a0 | ||
|
|
8d19711f2c | ||
|
|
c94117ae1e | ||
|
|
06a80df708 | ||
|
|
13dff256c8 | ||
|
|
07e9dbc0f7 | ||
|
|
c83dddbf0b | ||
|
|
5672d1aa75 | ||
|
|
078097efec | ||
|
|
3cd8338886 | ||
|
|
d2b57cc7d5 | ||
|
|
2c4f59c87a | ||
|
|
36eb7eeb76 | ||
|
|
3b6544db8a | ||
|
|
8033ab23ed | ||
|
|
ddb3db8b20 | ||
|
|
b3c8071edb | ||
|
|
bc70faae09 | ||
|
|
ae7c4c91e0 | ||
|
|
613baa4e1e | ||
|
|
33a101b4f7 | ||
|
|
9e9039c4e6 | ||
|
|
b3ceec51de | ||
|
|
e3ac4ba6f5 | ||
|
|
fa6b8b44c1 | ||
|
|
8cb7574af2 | ||
|
|
c34d644c2a | ||
|
|
7857fa8f63 | ||
|
|
3253de83dc | ||
|
|
030464c3b8 | ||
|
|
0020aa69ec | ||
|
|
93bfa56ccf | ||
|
|
3392ac5fbc | ||
|
|
2077d94edc | ||
|
|
defe5eb6fe | ||
|
|
dee5e18134 | ||
|
|
6ef5a7e440 | ||
|
|
462735147d | ||
|
|
51ca0e4474 | ||
|
|
4ee3b18533 | ||
|
|
cdc9ed54c9 | ||
|
|
d9e7c07851 | ||
|
|
f2debc5d16 | ||
|
|
b3dbaf40cf | ||
|
|
abfc7b917b | ||
|
|
a0e488b0a5 | ||
|
|
6b99df2792 | ||
|
|
6f8434a5c2 | ||
|
|
b15888dbd4 | ||
|
|
2e96d74ac0 | ||
|
|
fc4cbb1fe1 | ||
|
|
67f9bcf4c8 | ||
|
|
da227ec234 | ||
|
|
933be02c86 | ||
|
|
28e979a2be | ||
|
|
8cde363338 | ||
|
|
4741fe1952 | ||
|
|
7d4ae57fbd | ||
|
|
c50388ab9f | ||
|
|
17099e86de | ||
|
|
6528352e04 | ||
|
|
4f7b426068 | ||
|
|
acc8628f3d | ||
|
|
46e99be201 | ||
|
|
7fbeca41fa | ||
|
|
73ceba77a8 | ||
|
|
a3e5d17628 | ||
|
|
c30dea20a5 | ||
|
|
b31450ba41 | ||
|
|
c05457ce2f | ||
|
|
9e63076ec5 | ||
|
|
92844fc3db | ||
|
|
7bb9434850 | ||
|
|
3104a00913 | ||
|
|
28c51f3f2f | ||
|
|
5c035f21c8 | ||
|
|
52ecaf7fd3 | ||
|
|
aa007c676f | ||
|
|
1f0b49e5fe | ||
|
|
d979209096 | ||
|
|
154d8e4c2b | ||
|
|
27d42d2545 | ||
|
|
142670c374 | ||
|
|
2a9c4d7e7a | ||
|
|
dc43f01a7d | ||
|
|
bac7a2595e | ||
|
|
50158397a0 | ||
|
|
484f89cb82 | ||
|
|
fd7b5e9c59 | ||
|
|
966f226dde | ||
|
|
8a2a0474b2 | ||
|
|
22be6443ef | ||
|
|
da1348d25f | ||
|
|
02604d4ef3 | ||
|
|
351da0dce0 | ||
|
|
01e21e419d | ||
|
|
a5bca4cca3 | ||
|
|
f439f37526 | ||
|
|
502aa3a0cb | ||
|
|
84a7bcc2a4 | ||
|
|
388198a504 | ||
|
|
69d6fda56b | ||
|
|
9a38078c72 | ||
|
|
af2dd0d9a3 | ||
|
|
09fc14d63d | ||
|
|
e683d1d370 | ||
|
|
24fed54cae | ||
|
|
b76b34ad65 | ||
|
|
ef65812ec7 | ||
|
|
4c8351c1bc | ||
|
|
4492cc1efe | ||
|
|
b2ca0ea998 | ||
|
|
924665c1a3 | ||
|
|
5d49018bb2 | ||
|
|
3ae8818b3d | ||
|
|
2bb1f71c9c | ||
|
|
537e55afc6 | ||
|
|
6cdaceb561 | ||
|
|
08aacdc9af | ||
|
|
3db57cfa42 | ||
|
|
d8a774e358 | ||
|
|
869d12759c | ||
|
|
bce3ae0f21 | ||
|
|
1541921070 | ||
|
|
cc25abcc00 | ||
|
|
e80ee2f574 | ||
|
|
56a95c7f17 | ||
|
|
667e3610b2 | ||
|
|
142c8c84ac | ||
|
|
383075d323 | ||
|
|
e2922aa820 | ||
|
|
dc9667cf0c | ||
|
|
bbbfda0b5c | ||
|
|
aa3f079ee9 | ||
|
|
59bd4e8dd2 | ||
|
|
ce6184b8cd | ||
|
|
9ca511d29d | ||
|
|
74de28c64a | ||
|
|
24060e8068 | ||
|
|
3a03d48855 | ||
|
|
935a9125d6 | ||
|
|
d4ea97ef2c | ||
|
|
867aa4e52a | ||
|
|
bc2bb444dc | ||
|
|
19796f3b64 | ||
|
|
348a6e4a8f | ||
|
|
ba3300ddde | ||
|
|
09526884f4 | ||
|
|
160a0e8d29 | ||
|
|
9dddd0075e | ||
|
|
0e0e3939b2 | ||
|
|
9c8d87412e | ||
|
|
3666d92819 | ||
|
|
9388d4dd22 | ||
|
|
6cf4924e05 | ||
|
|
713e3751b4 | ||
|
|
8e61d7c55e | ||
|
|
4e42c42bae | ||
|
|
b53e3edef1 | ||
|
|
0afbf02451 | ||
|
|
dd79efd6f5 | ||
|
|
97ac19a2fe | ||
|
|
755e0c2a2a | ||
|
|
ccab33a290 | ||
|
|
76a8e65e80 | ||
|
|
3b86e64222 | ||
|
|
020482a05a | ||
|
|
1896e22dff | ||
|
|
56df20fe84 | ||
|
|
ed7dabbfcb | ||
|
|
b1d40d1b3a | ||
|
|
43939c7475 | ||
|
|
8fa6db0be1 | ||
|
|
d57e9864a4 | ||
|
|
077f236a43 | ||
|
|
d57277877e | ||
|
|
1b0db859b2 | ||
|
|
d1d8c645a8 | ||
|
|
a54541ff10 | ||
|
|
e5a57ac2e0 | ||
|
|
da0e00252d | ||
|
|
526ae8c3e6 | ||
|
|
d5081b2c18 | ||
|
|
758b5558f9 | ||
|
|
97b2183038 | ||
|
|
2732482d36 | ||
|
|
ca52f12378 | ||
|
|
ab437d402b | ||
|
|
79d9214084 | ||
|
|
b5db16e1ba | ||
|
|
a68e27f09e | ||
|
|
4af8c6a121 | ||
|
|
693c89ae6b | ||
|
|
8be5d6d370 | ||
|
|
5a7f3d30a7 | ||
|
|
fb6331b64a | ||
|
|
77b5e6eabc | ||
|
|
ca07a9155b | ||
|
|
6cc202133e | ||
|
|
797e9427e5 | ||
|
|
b624ea2005 | ||
|
|
189aba1488 | ||
|
|
5f43fb7d0a | ||
|
|
86fada184c | ||
|
|
f0eece484f | ||
|
|
67f8e6665b | ||
|
|
6569e27312 | ||
|
|
4284ad3734 | ||
|
|
1c2f85cd59 | ||
|
|
2f83742f1d | ||
|
|
26d884c2da | ||
|
|
2d054fa9a8 | ||
|
|
f2863d1108 | ||
|
|
281e1b36ce | ||
|
|
160ad5c10f | ||
|
|
667137638b | ||
|
|
b5dffa927a | ||
|
|
651a82d1ca | ||
|
|
d963c95af8 | ||
|
|
7c524a1196 | ||
|
|
0c85aad5a2 | ||
|
|
5515654497 | ||
|
|
5955310bf3 | ||
|
|
a9df6da912 | ||
|
|
f3c56d5774 | ||
|
|
9a86793923 | ||
|
|
93c6248fdb | ||
|
|
ec738b18dc | ||
|
|
6029408564 | ||
|
|
8734834341 | ||
|
|
809f86955d | ||
|
|
2f557617d8 | ||
|
|
35c8196103 | ||
|
|
c71df1bb25 | ||
|
|
bc107d2870 | ||
|
|
294ed36b8f | ||
|
|
e01c7640b7 | ||
|
|
90125dcce4 | ||
|
|
a0c87c1c04 | ||
|
|
8924cdcac4 | ||
|
|
0d396e3f0a | ||
|
|
28daefc5ff | ||
|
|
895db6a8c9 | ||
|
|
73b894419a | ||
|
|
23b25634e8 | ||
|
|
b42a6c4124 | ||
|
|
f18a3ef992 | ||
|
|
5146314725 |
@@ -1,16 +1,39 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.13
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
current_version = 5.26
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
[bumpversion:file:src/config.h]
|
||||
search = VERSION "{current_version}"
|
||||
replace = VERSION "{new_version}"
|
||||
[bumpversion:file:src/libs/const.h]
|
||||
parse = (?P<major>\d+)
|
||||
serialize = {major}
|
||||
search = VERSION_MAJOR {current_version}
|
||||
replace = VERSION_MAJOR {new_version}
|
||||
|
||||
[bumpversion:file:PKGBUILD]
|
||||
[bumpversion:file:./src/libs/const.h]
|
||||
parse = <major>\d+\.(?P<minor>\d+)
|
||||
serialize = {minor}
|
||||
search = VERSION_MINOR {current_version}
|
||||
replace = VERSION_MINOR {new_version}
|
||||
|
||||
[bumpversion:file:python/setup.py]
|
||||
search = version="{current_version}"
|
||||
replace = version="{new_version}"
|
||||
|
||||
[bumpversion:file:pkg/arch/PKGBUILD]
|
||||
search = pkgver={current_version}
|
||||
replace = pkgver={new_version}
|
||||
|
||||
[bumpversion:file:pkg/openwrt/Makefile]
|
||||
search = PKG_VERSION:={current_version}
|
||||
replace = PKG_VERSION:={new_version}
|
||||
|
||||
[bumpversion:file:man/ustreamer.1]
|
||||
search = "version {current_version}"
|
||||
replace = "version {new_version}"
|
||||
|
||||
[bumpversion:file:man/ustreamer-dump.1]
|
||||
search = "version {current_version}"
|
||||
replace = "version {new_version}"
|
||||
|
||||
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
||||
# Ignore everything
|
||||
*
|
||||
|
||||
# Allow source code
|
||||
!Makefile
|
||||
!src/**
|
||||
!python/**
|
||||
!janus/**
|
||||
!man/**
|
||||
!pkg/docker/entry.sh
|
||||
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_file = lf
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.{py,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/.github/ export-ignore
|
||||
/.bumpversion.cfg export-ignore
|
||||
/.dockerignore export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: pikvm
|
||||
custom: https://paypal.me/pikvm
|
||||
81
.github/workflows/docker-alpine-image.yaml
vendored
Normal file
81
.github/workflows/docker-alpine-image.yaml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: Build Alpine
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, build_*]
|
||||
tags: [v*]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Login to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Login to DockerHub Container Registry
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/ustreamer,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
ghcr.io/${{ github.repository }},enable=${{ github.event_name != 'pull_request' }}
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=sha,format=long
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
driver-opts: image=moby/buildkit:master
|
||||
-
|
||||
name: Build and export to Docker
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
load: true
|
||||
tags: ustreamer
|
||||
file: pkg/docker/Dockerfile.alpine
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
echo version: $(docker run --rm -t ustreamer --version)
|
||||
echo -e "features:\n$(docker run --rm -t ustreamer --features)"
|
||||
-
|
||||
name: Build multi arch
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64,linux/amd64,linux/arm/v7
|
||||
file: pkg/docker/Dockerfile.alpine
|
||||
-
|
||||
name: Push
|
||||
if: steps.meta.outputs.tags != ''
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64,linux/amd64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
file: pkg/docker/Dockerfile.alpine
|
||||
20
.github/workflows/dockerimage.yml
vendored
Normal file
20
.github/workflows/dockerimage.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Building linters image ...
|
||||
run: make linters
|
||||
|
||||
- name: Running linters ...
|
||||
run: make tox
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
@@ -1,8 +1,17 @@
|
||||
/pkg/
|
||||
/src/ustreamer-*/
|
||||
/src/v*.tar.gz
|
||||
/v*.tar.gz
|
||||
/ustreamer-*.pkg.tar.xz
|
||||
/vgcore.*
|
||||
/linters/.tox/
|
||||
/linters/.mypy_cache/
|
||||
/pkg/arch/pkg/
|
||||
/pkg/arch/src/
|
||||
/src/build/
|
||||
/python/build/
|
||||
/janus/build/
|
||||
/ustreamer
|
||||
*.o
|
||||
/ustreamer-dump
|
||||
/config.mk
|
||||
vgcore.*
|
||||
*.sock
|
||||
*.so
|
||||
*.bin
|
||||
*.pkg.tar.xz
|
||||
*.pkg.tar.zst
|
||||
*.tar.gz
|
||||
|
||||
127
Makefile
127
Makefile
@@ -1,55 +1,108 @@
|
||||
-include config.mk
|
||||
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
MANPREFIX ?= $(PREFIX)/share/man
|
||||
|
||||
CC ?= gcc
|
||||
PY ?= python3
|
||||
CFLAGS ?= -O3
|
||||
LDFLAGS ?=
|
||||
|
||||
RPI_VC_HEADERS ?= /opt/vc/include
|
||||
RPI_VC_LIBS ?= /opt/vc/lib
|
||||
|
||||
export
|
||||
|
||||
_LINTERS_IMAGE ?= ustreamer-linters
|
||||
|
||||
|
||||
# =====
|
||||
CC = gcc
|
||||
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads
|
||||
override CFLAGS += -c -std=c99 -Wall -Wextra -D_GNU_SOURCE
|
||||
SOURCES = $(shell ls src/*.c src/jpeg/*.c)
|
||||
OBJECTS = $(SOURCES:.c=.o)
|
||||
PROG = ustreamer
|
||||
define optbool
|
||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||
endef
|
||||
|
||||
|
||||
ifeq ($(shell ls -d /opt/vc/include 2>/dev/null), /opt/vc/include)
|
||||
SOURCES += $(shell ls src/omx/*.c)
|
||||
LIBS += -lbcm_host -lvcos -lopenmaxil -L/opt/vc/lib
|
||||
override CFLAGS += -DOMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
|
||||
# =====
|
||||
all:
|
||||
+ $(MAKE) apps
|
||||
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
||||
+ $(MAKE) python
|
||||
endif
|
||||
ifneq ($(call optbool,$(WITH_JANUS)),)
|
||||
+ $(MAKE) janus
|
||||
endif
|
||||
|
||||
|
||||
# =====
|
||||
all: $(SOURCES) $(PROG)
|
||||
apps:
|
||||
$(MAKE) -C src
|
||||
@ ln -sf src/ustreamer.bin ustreamer
|
||||
@ ln -sf src/ustreamer-dump.bin ustreamer-dump
|
||||
|
||||
|
||||
install: $(PROG)
|
||||
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
|
||||
python:
|
||||
$(MAKE) -C python
|
||||
@ ln -sf python/build/lib.*/*.so .
|
||||
|
||||
|
||||
janus:
|
||||
$(MAKE) -C janus
|
||||
@ ln -sf janus/*.so .
|
||||
|
||||
|
||||
install: all
|
||||
$(MAKE) -C src install
|
||||
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
||||
$(MAKE) -C python install
|
||||
endif
|
||||
ifneq ($(call optbool,$(WITH_JANUS)),)
|
||||
$(MAKE) -C janus install
|
||||
endif
|
||||
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
|
||||
for man in $(shell ls man); do \
|
||||
install -m644 man/$$man $(DESTDIR)$(MANPREFIX)/man1/$$man; \
|
||||
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$$man; \
|
||||
done
|
||||
|
||||
|
||||
install-strip: install
|
||||
$(MAKE) -C src install-strip
|
||||
|
||||
|
||||
regen:
|
||||
tools/make-jpg-h.py src/data/blank.jpg src/data/blank.h BLANK 640 480
|
||||
|
||||
|
||||
$(PROG): $(OBJECTS)
|
||||
$(CC) $(LIBS) $(LDFLAGS) $(OBJECTS) -o $@
|
||||
|
||||
|
||||
.c.o:
|
||||
$(CC) $(LIBS) $(CFLAGS) $< -o $@
|
||||
tools/$(MAKE)-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK
|
||||
tools/$(MAKE)-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
|
||||
|
||||
|
||||
release:
|
||||
make clean
|
||||
make push
|
||||
make bump
|
||||
make push
|
||||
make clean
|
||||
$(MAKE) clean
|
||||
$(MAKE) tox
|
||||
$(MAKE) push
|
||||
$(MAKE) bump V=$(V)
|
||||
$(MAKE) push
|
||||
$(MAKE) clean
|
||||
|
||||
|
||||
tox: linters
|
||||
time docker run --rm \
|
||||
--volume `pwd`:/src:ro \
|
||||
--volume `pwd`/linters:/src/linters:rw \
|
||||
-t $(_LINTERS_IMAGE) bash -c " \
|
||||
cd /src \
|
||||
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
|
||||
"
|
||||
|
||||
|
||||
linters:
|
||||
docker build \
|
||||
$(if $(call optbool,$(NC)),--no-cache,) \
|
||||
--rm \
|
||||
--tag $(_LINTERS_IMAGE) \
|
||||
-f linters/Dockerfile linters
|
||||
|
||||
|
||||
bump:
|
||||
bumpversion minor
|
||||
bumpversion $(if $(V),$(V),minor)
|
||||
|
||||
|
||||
push:
|
||||
@@ -57,6 +110,18 @@ push:
|
||||
git push --tags
|
||||
|
||||
|
||||
clean-all: linters clean
|
||||
- docker run --rm \
|
||||
--volume `pwd`:/src \
|
||||
-it $(_LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
|
||||
|
||||
|
||||
clean:
|
||||
rm -f src/*.o src/{jpeg,omx}/*.o vgcore.* $(PROG)
|
||||
rm -rf pkg src/$(PROG)-* src/v*.tar.gz v*.tar.gz $(PROG)-*.pkg.tar.xz
|
||||
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
|
||||
rm -f ustreamer ustreamer-dump *.so
|
||||
$(MAKE) -C src clean
|
||||
$(MAKE) -C python clean
|
||||
$(MAKE) -C janus clean
|
||||
|
||||
|
||||
.PHONY: python janus linters
|
||||
|
||||
30
PKGBUILD
30
PKGBUILD
@@ -1,30 +0,0 @@
|
||||
# Contributor: Maxim Devaev <mdevaev@gmail.com>
|
||||
# Author: Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=0.13
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pi-kvm/ustreamer"
|
||||
license=(GPL)
|
||||
arch=(i686 x86_64 armv6h armv7h)
|
||||
depends=(libjpeg libevent)
|
||||
# optional: raspberrypi-firmware for OMX JPEG compressor
|
||||
makedepends=(gcc make)
|
||||
source=("$url/archive/v$pkgver.tar.gz")
|
||||
md5sums=(SKIP)
|
||||
|
||||
|
||||
build() {
|
||||
cd $srcdir
|
||||
rm -rf $pkgname-build
|
||||
cp -r ustreamer-$pkgver $pkgname-build
|
||||
cd $pkgname-build
|
||||
make CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
|
||||
}
|
||||
|
||||
package() {
|
||||
cd $srcdir/$pkgname-build
|
||||
make DESTDIR="$pkgdir" PREFIX=/usr install
|
||||
}
|
||||
187
README.md
187
README.md
@@ -1,69 +1,188 @@
|
||||
# µStreamer
|
||||
[](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
|
||||
[](https://discord.gg/bpmXfz5)
|
||||
|
||||
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pi-kvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
|
||||
[[Русская версия]](README.ru.md)
|
||||
|
||||
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
|
||||
µStreamer is a lightweight and very quick server to stream [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
|
||||
µStreamer is a part of the [PiKVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
|
||||
|
||||
| **Фича** | **µStreamer** | **mjpg-streamer** |
|
||||
µStreamer is very similar to [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) with ```input_uvc.so``` and ```output_http.so``` plugins, however, there are some major differences. The key ones are:
|
||||
|
||||
| **Feature** | **µStreamer** | **mjpg-streamer** |
|
||||
|----------|---------------|-------------------|
|
||||
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi |  Есть |  Нет |
|
||||
| Поведение при физическом отключении устройства<br>от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Необратимо зависает <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) |  Есть |  Нет <sup>1</sup> |
|
||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||
| Поддерживаемые входные форматы устройств |  YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> |  YUYV, UYVY,<br>RGB565, MJPG |
|
||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации |  Нет <sup>4</sup> |  Есть |
|
||||
| Multithreaded JPEG encoding | ✔ | ✘ |
|
||||
| Hardware image encoding<br>on Raspberry Pi | ✔ | ✘ |
|
||||
| Behavior when the device<br>is disconnected while streaming | ✔ Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ✘ Stops the streaming <sup>1</sup> |
|
||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ✔ | ☹ Partially yes <sup>1</sup> |
|
||||
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ✔ <sup>2</sup> | ✘ |
|
||||
| Streaming via UNIX domain socket | ✔ | ✘ |
|
||||
| Systemd socket activation | ✔ | ✘ |
|
||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ✔ | ✘ |
|
||||
| Option to serve files<br>with a built-in HTTP server | ✔ | ☹ Regular files only |
|
||||
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
|
||||
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ✘ | ✔ |
|
||||
| Compatibility with mjpg-streamer's API | ✔ | :) |
|
||||
|
||||
Сносочки:
|
||||
* ```1``` Для mjpg-streamer существует [мой патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), предотвращающий зависание при отключении устройства и добавляющий поддержку DV-таймингов, однако трансляция при этом все равно прерывается. В данный момент этот патч не принят в апстрим, и я даже не гарантирую его стопроцентную работоспособность. Код mjpg-streamer очень плохо структурирован и чрезвычайно запутан, и я мог что-то упустить. Собственно, это одна из причин, почему µStreamer написан с нуля.
|
||||
|
||||
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако увеличивает загрузку процессора и добавляет небольшую задержку. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
|
||||
Footnotes:
|
||||
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
|
||||
|
||||
* ```3``` Поскольку µStreamer писался в первую очередь для устройств видеозахвата, в нем реализованы только те форматы, которые для них были нужны. MJPG в контексте входных данных означает, что устройство умеет самостоятельно сжимать картинку в JPEG и отдавать ее программе, что позволяет значительно снизить загрузку процессора и избавить его от необходимости кодировать картинку софтом. Этот формат поддерживается большинством веб-камер, но не поддерживается ни одним из встреченных мной устройств видеозахвата; его не умеет ни [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), ни [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC). Нет никаких технических сложностей добавить поддержку аппаратного MJPG источника, но у меня просто пока не дошли до этого руки.
|
||||
|
||||
* ```4``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
|
||||
* ```2``` This feature allows to cut down outgoing traffic several-fold when streaming HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to stream the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
|
||||
If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win.
|
||||
|
||||
-----
|
||||
# Сборка
|
||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads``` и ```libjpeg8```/```libjpeg-turbo```.
|
||||
# Building
|
||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
|
||||
|
||||
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX, если обнаружит нужные хедеры в ```/opt/vc/include```.
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1`.
|
||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
|
||||
|
||||
To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
|
||||
|
||||
```
|
||||
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
||||
$ git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||
$ cd ustreamer
|
||||
$ make
|
||||
$ ./ustreamer --help
|
||||
```
|
||||
|
||||
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer
|
||||
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
|
||||
|
||||
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
||||
|
||||
-----
|
||||
# Использование
|
||||
Будучи запущенным без аргументов, ```ustremaer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://localhost:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80 порту:
|
||||
# Usage
|
||||
**For M2M hardware encoding on Raspberry Pi, you need at least 5.15.32 kernel. OpenMAX and MMAL support on older kernels is deprecated and removed.**
|
||||
|
||||
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start streaming on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to stream to the world, run:
|
||||
```
|
||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||
```
|
||||
|
||||
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
|
||||
:exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`.
|
||||
|
||||
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
|
||||
```bash
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Настройка входного формата устройства
|
||||
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
|
||||
--dv-timings # Включение DV-таймингов
|
||||
--format=uyvy \ # Device input format
|
||||
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
|
||||
--workers=3 \ # Workers number
|
||||
--persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected)
|
||||
--dv-timings \ # Use DV-timings
|
||||
--drop-same-frames=30 # Save the traffic
|
||||
```
|
||||
|
||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
||||
:exclamation: Please note that to use `--drop-same-frames` for different browsers you need to use some specific URL `/stream` parameters (see URL `/` for details).
|
||||
|
||||
You can always view the full list of options with ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Лицензия
|
||||
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com
|
||||
# Docker (Raspberry Pi 4 HDMI)
|
||||
|
||||
## Preparations
|
||||
Add following lines to /boot/firmware/usercfg.txt:
|
||||
|
||||
```
|
||||
gpu_mem=128
|
||||
dtoverlay=tc358743
|
||||
```
|
||||
|
||||
Check size of CMA:
|
||||
|
||||
```bash
|
||||
$ dmesg | grep cma-reserved
|
||||
[ 0.000000] Memory: 7700524K/8244224K available (11772K kernel code, 1278K rwdata, 4320K rodata, 4096K init, 1077K bss, 281556K reserved, 262144K cma-reserved)
|
||||
```
|
||||
|
||||
If it is smaller than 128M add following to /boot/firmware/cmdline.txt:
|
||||
|
||||
```
|
||||
cma=128M
|
||||
```
|
||||
|
||||
Save changes and reboot.
|
||||
|
||||
## Launch
|
||||
Start container:
|
||||
|
||||
```bash
|
||||
$ docker run --device /dev/video0:/dev/video0 -e EDID=1 -p 8080:8080 pikvm/ustreamer:latest
|
||||
```
|
||||
|
||||
Then access the web interface at port 8080 (e.g. http://raspberrypi.local:8080).
|
||||
|
||||
## Custom config
|
||||
```bash
|
||||
$ docker run --rm pikvm/ustreamer:latest \
|
||||
--format=uyvy \
|
||||
--workers=3 \
|
||||
--persistent \
|
||||
--dv-timings \
|
||||
--drop-same-frames=30
|
||||
```
|
||||
|
||||
## EDID
|
||||
Add `-e EDID=1` to set HDMI EDID before starging ustreamer. Use together with `-e EDID_HEX=xx` to specify custom EDID data.
|
||||
|
||||
-----
|
||||
# Raspberry Pi Camera Example
|
||||
|
||||
Example usage for the Raspberry Pi v1 camera:
|
||||
```bash
|
||||
$ sudo modprobe bcm2835-v4l2
|
||||
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
||||
```
|
||||
|
||||
:exclamation: Please note that newer camera models have a different maximum resolution. You can see the supported resolutions at the [PiCamera documentation](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes).
|
||||
|
||||
:exclamation: If you get a poor framerate, it could be that the camera is switched to photo mode, which produces a low framerate (but a higher quality picture). This is because `bcm2835-v4l2` switches to photo mode at resolutions higher than `1280x720`. To work around this, pass the `max_video_width` and `max_video_height` module parameters like so:
|
||||
|
||||
```bash
|
||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||
```
|
||||
|
||||
-----
|
||||
# Integrations
|
||||
|
||||
## Janus
|
||||
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
|
||||
|
||||
## Nginx
|
||||
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
||||
|
||||
```nginx
|
||||
location /stream {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
proxy_pass http://ustreamer;
|
||||
}
|
||||
```
|
||||
|
||||
-----
|
||||
# Tips & tricks for v4l2
|
||||
v4l2 utilities provide the tools to manage USB webcam setting and information. Scripts can be use to make adjustments and run manually or with cron. Running in cron for example to change the exposure settings at certain times of day. The package is available in all Linux distributions and is usually called `v4l-utils`.
|
||||
|
||||
* List of available video devices: `v4l2-ctl --list-devices`.
|
||||
* List available control settings: `v4l2-ctl -d /dev/video0 --list-ctrls`.
|
||||
* List available video formats: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
|
||||
* Read the current setting: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
|
||||
* Change the setting value: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
|
||||
|
||||
[Here](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/) you can find more examples. Documentation is available in [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
|
||||
|
||||
-----
|
||||
# See also
|
||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
||||
|
||||
-----
|
||||
# License
|
||||
Copyright (C) 2018-2022 by Maxim Devaev mdevaev@gmail.com
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
145
README.ru.md
Normal file
145
README.ru.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# µStreamer
|
||||
[](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
|
||||
[](https://discord.gg/bpmXfz5)
|
||||
|
||||
[[English version]](README.md)
|
||||
|
||||
|
||||
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [PiKVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
|
||||
|
||||
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
|
||||
|
||||
| **Фича** | **µStreamer** | **mjpg-streamer** |
|
||||
|----------|---------------|-------------------|
|
||||
| Многопоточное кодирование JPEG | ✔ | ✘ |
|
||||
| Аппаратное кодирование на Raspberry Pi | ✔ | ✘ |
|
||||
| Поведение при физическом отключении<br>устройства от сервера во время работы | ✔ Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ✘ Прерывает трансляцию <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ✔ | ☹ Условно есть <sup>1</sup> |
|
||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ✔ <sup>2</sup> | ✘ |
|
||||
| Стрим через UNIX domain socket | ✔ | ✘ |
|
||||
| Systemd socket activation | ✔ | ✘ |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ✔ | ✘ |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером | ✔ | ☹ Нет каталогов |
|
||||
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
|
||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ✘ | ✔ |
|
||||
| Совместимость с API mjpg-streamer'а | ✔ | :) |
|
||||
|
||||
Сносочки:
|
||||
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
||||
|
||||
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
|
||||
|
||||
-----
|
||||
# Сборка
|
||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libbsd``` (только для Linux).
|
||||
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libgpiod` для `WITH_GPIO=1` и `libsystemd-dev` для `WITH_SYSTEMD=1`.
|
||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||
|
||||
Для включения сборки с поддержкой GPIO установите [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
|
||||
|
||||
```
|
||||
$ git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||
$ cd ustreamer
|
||||
$ make
|
||||
$ ./ustreamer --help
|
||||
```
|
||||
|
||||
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer.
|
||||
|
||||
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
|
||||
|
||||
-----
|
||||
# Использование
|
||||
**Для аппаратного кодирования M2M на Raspberry Pi, вам нужно ядро минимальной версии 5.15.32. Поддержка OpenMAX и MMAL для более старых ядер объявлена устаревшей и была удалена.**
|
||||
|
||||
Будучи запущенным без аргументов, ```ustreamer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://127.0.0.1:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80-м порту:
|
||||
```
|
||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||
```
|
||||
|
||||
:exclamation: Обратите внимание, что начиная с версии µStreamer v2.0 кросс-доменные запросы были выключены по умолчанию [по соображениям безопасности](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). Чтобы включить старое поведение, используйте опцию `--allow-origin=\*`.
|
||||
|
||||
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
|
||||
```bash
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Настройка входного формата устройства
|
||||
--encoder=m2m-image \ # Аппаратное кодирование с помощью драйвера V4L2 M2M
|
||||
--workers=3 \ # Максимум воркеров
|
||||
--persistent \ # Не переинициализировать устройство при таймауте (например, когда был отключен HDMI-кабель)
|
||||
--dv-timings \ # Включение DV-таймингов
|
||||
--drop-same-frames=30 # Экономим трафик
|
||||
```
|
||||
|
||||
:exclamation: Обратите внимание, что для использования `--drop-same-frames` для разных браузеров нужно использовать ряд специальных параметров в `/stream` (за деталями обратитесь к урлу `/`).
|
||||
|
||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Камера Raspberry Pi
|
||||
Пример использования камеры Raspberry Pi v1:
|
||||
```bash
|
||||
$ sudo modprobe bcm2835-v4l2
|
||||
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
||||
```
|
||||
|
||||
:exclamation: Обратите внимание что боле новые модели камеры имеют другое максимальное разрешение. Список поддерживаемых разрешений можно найти в [документации PiCamera](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes).
|
||||
|
||||
:exclamation: Если камера выдает низкий фреймрейт, возможно что она работает в фото-режиме, где производит более низкий фпс, но более качественную кратинку. Это происходит потому что `bcm2835-v4l2` переключает камеру в фото-режим на разрешениях выше `1280x720`. Чтобы обойти это, передайте параметры `max_video_width` и `max_video_height` при загрузке модуля, например:
|
||||
|
||||
```bash
|
||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||
```
|
||||
|
||||
-----
|
||||
# Интеграция
|
||||
|
||||
## Nginx
|
||||
Если uStreamer находится на Nginx, то последний будет буферизировать поток и создавать дополнительную задержку в стриме. Чтобы задержки не было, буферизацию можно отключить:
|
||||
|
||||
```nginx
|
||||
location /stream {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
proxy_pass http://ustreamer;
|
||||
}
|
||||
```
|
||||
|
||||
-----
|
||||
# Утилиты V4L2
|
||||
V4L2 предоставляет ряд официальных утилит для управления USB-вебкамерами и получения информации об устройствах. С их помощью можно писать всякие настроечные скрипты и запускать их по крону, если, например, вам требуется изменять настройки экспозиции в зависимости от времени суток. Пакет с этими утилитами доступен на всех дистрибутивах Linux и обычно называется `v4l-utils`.
|
||||
|
||||
* Вывести список видеоустройств: `v4l2-ctl --list-devices`.
|
||||
* Вывести список доступных контролов устройства: `v4l2-ctl -d /dev/video0 --list-ctrls`.
|
||||
* Вывести список доступных форматов видео: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
|
||||
* Показать текущее значение контрола: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
|
||||
* Изменить значение контрола: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
|
||||
|
||||
Больше примеров вы можете найти [здесь](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/), а документацию в [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
|
||||
|
||||
-----
|
||||
# Смотрите также
|
||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
||||
|
||||
-----
|
||||
# Лицензия
|
||||
Copyright (C) 2018-2022 by Maxim Devaev mdevaev@gmail.com
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
244
docs/h264.md
Normal file
244
docs/h264.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# Streaming H.264 Video over WebRTC with Janus
|
||||
|
||||
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server.
|
||||
|
||||
This guide explains how to configure µStreamer to provide an H.264 video stream and consume it within a web application using WebRTC.
|
||||
|
||||
## Components
|
||||
|
||||
In addition to µStreamer, H.264 streaming involves the following components:
|
||||
|
||||
- [**Janus**](https://janus.conf.meetecho.com/): A general-purpose WebRTC server.
|
||||
- [**µStreamer Janus Plugin**](https://github.com/pikvm/ustreamer/tree/master/janus): A Janus plugin that allows Janus to consume a video stream from µStreamer via shared memory.
|
||||
- [**Janus JavaScript Client**](https://janus.conf.meetecho.com/docs/JS.html): A frontend library that communicates with the Janus server and the µStreamer Janus plugin.
|
||||
|
||||
## Control Flow
|
||||
|
||||
To connect to a µStreamer video stream over WebRTC, an application must establish the following control flow:
|
||||
|
||||
1. The backend server starts the µStreamer service.
|
||||
1. The backend server starts the Janus WebRTC server with the µStreamer Janus plugin.
|
||||
1. The client-side JavaScript application establishes a connection to the Janus server.
|
||||
1. The client-side JavaScript application instructs the Janus server to attach the µStreamer Janus plugin and start the video stream.
|
||||
1. The client-side JavaScript application renders the video stream in the web browser.
|
||||
|
||||
## Server Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To integrate with Janus, µStreamer has the following prerequisites:
|
||||
|
||||
- The system packages that µStreamer depends on (see the [main build instructions](../README.md#building)).
|
||||
- The Janus WebRTC server with WebSockets transport enabled (see the [Janus documentation](https://github.com/meetecho/janus-gateway)).
|
||||
|
||||
### Fixing Janus C Headers
|
||||
|
||||
To compile µStreamer with the Janus option, (see [“Installation”](#installation)), you need to make the Janus header files available within µStreamer's build context.
|
||||
|
||||
First, create a symbolic link from `/usr/include` to the Janus install directory. By default, Janus installs to `/opt/janus`.
|
||||
|
||||
```sh
|
||||
ln -s /opt/janus/include/janus /usr/include/janus
|
||||
```
|
||||
|
||||
Janus uses `#include` paths that make it difficult for third-party libraries to build against its headers. To fix this, modify the `#include` directive in `janus/plugins/plugin.h` to prepend a `../` to the included file name (`#include "refcount.h"` → `#include "../refcount.h"`).
|
||||
|
||||
```sh
|
||||
sed \
|
||||
--in-place \
|
||||
--expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' \
|
||||
/usr/include/janus/plugins/plugin.h
|
||||
```
|
||||
|
||||
### Installation
|
||||
|
||||
First, compile µStreamer with the Janus plugin option (`WITH_JANUS`).
|
||||
|
||||
```sh
|
||||
git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||
cd ustreamer
|
||||
make WITH_JANUS=1
|
||||
```
|
||||
|
||||
The `WITH_JANUS` option compiles the µStreamer Janus plugin and outputs it to `janus/libjanus_ustreamer.so`. Move this file to the plugin directory of your Janus installation.
|
||||
|
||||
```sh
|
||||
mv \
|
||||
janus/libjanus_ustreamer.so \
|
||||
/opt/janus/lib/janus/plugins/libjanus_ustreamer.so
|
||||
```
|
||||
|
||||
Finally, specify a qualifier for the shared memory object so that the µStreamer Janus plugin can read µStreamer's video data.
|
||||
|
||||
```sh
|
||||
cat > /opt/janus/lib/janus/configs/janus.plugin.ustreamer.jcfg <<EOF
|
||||
memsink: {
|
||||
object = "demo::ustreamer::h264"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
If you're using a TC358743-based video capture device that supports audio capture, run the following command to enable audio streaming:
|
||||
|
||||
```sh
|
||||
cat << EOF >> /opt/janus/lib/janus/configs/janus.plugin.ustreamer.jcfg
|
||||
audio: {
|
||||
device = "hw:1"
|
||||
tc358743 = "/dev/video0"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Start µStreamer and the Janus WebRTC Server
|
||||
|
||||
For µStreamer to share the video stream with the µStreamer Janus plugin, µStreamer must run with the following command-line flags:
|
||||
|
||||
- `--h264-sink` with the qualifier of the shared memory object you specified above (`demo::ustreamer::h264`)
|
||||
- `--h264-sink-mode` with the permissions bitmask for the shared memory object (e.g., `660`)
|
||||
- `--h264-sink-rm` to clean up the shared memory object when the µStreamer process exits
|
||||
|
||||
To load the µStreamer Janus plugin and configuration, the Janus WebRTC server must run with the following command-line flags:
|
||||
|
||||
- `--configs-folder` with the path to the Janus configuration directory (e.g., `/opt/janus/lib/janus/configs/`)
|
||||
- `--plugins-folder` with the path to the Janus plugin directory (e.g., `/opt/janus/lib/janus/plugins/`)
|
||||
|
||||
## Client Setup
|
||||
|
||||
Once an application's backend server is running µStreamer and Janus, a browser-based JavaScript application can consume the server's video stream.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
The client needs to load the following JavaScript libraries:
|
||||
|
||||
- [WebRTC Adapter library](https://webrtc.github.io/adapter/adapter-8.1.0.js) (`webrtc-adapter.js`, version `8.1.0`)
|
||||
- [Janus Gateway JavaScript library](https://raw.githubusercontent.com/meetecho/janus-gateway/v1.0.0/html/janus.js) (`janus.js`, version `1.0.0`)
|
||||
|
||||
### Control Flow
|
||||
|
||||
The client-side JavaScript application uses the following control flow:
|
||||
|
||||
1. The client loads and initializes the Janus client library.
|
||||
1. The client establishes a WebSockets connection to the Janus server.
|
||||
1. The client instructs the Janus server to attach the µStreamer Janus plugin.
|
||||
1. On success, the client obtains a plugin handle through which it can send requests directly to the µStreamer Janus plugin. The client processes responses via the `attach` callbacks:
|
||||
- `onmessage` for general messages
|
||||
- `onremotetrack` for the H.264 video stream and (optionally) an Opus audio stream
|
||||
1. The client issues a `watch` request to the µStreamer Janus plugin, which initiates the H.264 stream in the plugin itself.
|
||||
- It takes a few seconds for uStreamer's video stream to become available to Janus. The first `watch` request may fail, so the client must retry the `watch` request.
|
||||
1. The client and server negotiate the underlying parameters of the WebRTC session. This procedure is called JavaScript Session Establishment Protocol (JSEP). The server makes a `jsepOffer` to the client, and the client responds with a `jsepAnswer`.
|
||||
1. The client issues a `start` request to the µStreamer Janus plugin to indicate that the client wants to begin consuming the video stream.
|
||||
1. The µStreamer Janus plugin delivers the H.264 video stream and (optionally) an Opus audio stream to the client via WebRTC.
|
||||
1. The Janus client library invokes the `onremotetrack` callback with the video stream. The client attaches the video stream to the `<video>` element, rendering the video stream in the browser window.
|
||||
1. (if an audio track is available) The Janus client library invokes the `onremotetrack` callback with the Opus audio stream. The client adds the audio stream to the `<video>` element, rendering the audio in the browser window.
|
||||
|
||||
### Sample Code
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>µStreamer H.264 demo</title>
|
||||
<script src="https://webrtc.github.io/adapter/adapter-8.1.0.js"></script>
|
||||
<!-- janus.js is the JavaScript client library of Janus, as specified above in
|
||||
the prerequisites section of the client setup. You might need to change
|
||||
the `src` path, depending on where you serve this file from. -->
|
||||
<script src="janus.js"></script>
|
||||
<style>
|
||||
video {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<video id="webrtc-output" autoplay playsinline muted></video>
|
||||
<script type="text/javascript">
|
||||
// Initialize Janus library.
|
||||
Janus.init({
|
||||
// Turn on debug logs in the browser console.
|
||||
debug: true,
|
||||
|
||||
// Configure Janus to use standard browser APIs internally.
|
||||
dependencies: Janus.useDefaultDependencies(),
|
||||
});
|
||||
|
||||
// Establish a WebSockets connection to the server.
|
||||
const janus = new Janus({
|
||||
// Specify the URL of the Janus server’s WebSockets endpoint.
|
||||
server: `ws://${window.location.hostname}:8188/`,
|
||||
|
||||
// Callback function if the client connects successfully.
|
||||
success: attachUStreamerPlugin,
|
||||
|
||||
// Callback function if the client fails to connect.
|
||||
error: console.error,
|
||||
});
|
||||
|
||||
let uStreamerPluginHandle = null;
|
||||
|
||||
function attachUStreamerPlugin() {
|
||||
// Instruct the server to attach the µStreamer Janus plugin.
|
||||
janus.attach({
|
||||
// Qualifier of the plugin.
|
||||
plugin: "janus.plugin.ustreamer",
|
||||
|
||||
// Callback function, for when the server attached the plugin
|
||||
// successfully.
|
||||
success: function (pluginHandle) {
|
||||
uStreamerPluginHandle = pluginHandle;
|
||||
// Instruct the µStreamer Janus plugin to initiate streaming.
|
||||
uStreamerPluginHandle.send({ message: { request: "watch" } });
|
||||
},
|
||||
|
||||
// Callback function if the server fails to attach the plugin.
|
||||
error: console.error,
|
||||
|
||||
// Callback function for processing messages from the Janus server.
|
||||
onmessage: function (msg, jsepOffer) {
|
||||
// 503 indicates that the plugin is not ready to stream yet. Retry the
|
||||
// watch request until the video stream is available.
|
||||
if (msg.error_code === 503) {
|
||||
uStreamerPluginHandle.send({ message: { request: "watch" } });
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is a JSEP offer, respond to it. This starts the WebRTC
|
||||
// connection.
|
||||
if (jsepOffer) {
|
||||
uStreamerPluginHandle.createAnswer({
|
||||
jsep: jsepOffer,
|
||||
// Prevent the client from sending audio and video, as this would
|
||||
// trigger a permission dialog in the browser.
|
||||
media: { audioSend: false, videoSend: false },
|
||||
success: function (jsepAnswer) {
|
||||
uStreamerPluginHandle.send({
|
||||
message: { request: "start" },
|
||||
jsep: jsepAnswer,
|
||||
});
|
||||
},
|
||||
error: console.error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Callback function, for when a media stream arrives.
|
||||
onremotetrack: function (mediaStreamTrack, mediaId, isAdded) {
|
||||
if (isAdded) {
|
||||
// Attach the received media track to the video element. Cloning the
|
||||
// mediaStreamTrack creates a new object with a distinct, globally
|
||||
// unique stream identifier.
|
||||
const videoElement = document.getElementById("webrtc-output");
|
||||
if (videoElement.srcObject === null) {
|
||||
videoElement.srcObject = new MediaStream();
|
||||
}
|
||||
videoElement.srcObject.addTrack(mediaStreamTrack.clone());
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
54
janus/Makefile
Normal file
54
janus/Makefile
Normal file
@@ -0,0 +1,54 @@
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -O3
|
||||
LDFLAGS ?=
|
||||
|
||||
|
||||
# =====
|
||||
_PLUGIN = libjanus_ustreamer.so
|
||||
|
||||
_CFLAGS = -fPIC -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS)
|
||||
_LDFLAGS = -shared -lm -pthread -lrt -ljansson -lopus -lasound -lspeexdsp $(shell pkg-config --libs glib-2.0) $(LDFLAGS)
|
||||
|
||||
_SRCS = $(shell ls src/uslibs/*.c src/*.c)
|
||||
|
||||
_BUILD = build
|
||||
|
||||
|
||||
define optbool
|
||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||
endef
|
||||
|
||||
|
||||
WITH_PTHREAD_NP ?= 1
|
||||
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
|
||||
override _CFLAGS += -DWITH_PTHREAD_NP
|
||||
endif
|
||||
|
||||
|
||||
# =====
|
||||
$(_PLUGIN): $(_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == SO $@)
|
||||
@ $(CC) $^ -o $@ $(_LDFLAGS)
|
||||
|
||||
|
||||
$(_BUILD)/%.o: %.c
|
||||
$(info -- CC $<)
|
||||
@ mkdir -p $(dir $@) || true
|
||||
@ $(CC) $< -o $@ $(_CFLAGS)
|
||||
|
||||
|
||||
|
||||
install: $(_PLUGIN)
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/lib/ustreamer/janus
|
||||
install -m755 $(_PLUGIN) $(DESTDIR)$(PREFIX)/lib/ustreamer/janus/$(PLUGIN)
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf $(_PLUGIN) $(_BUILD)
|
||||
|
||||
|
||||
_OBJS = $(_SRCS:%.c=$(_BUILD)/%.o)
|
||||
-include $(_OBJS:%.o=%.d)
|
||||
242
janus/src/audio.c
Normal file
242
janus/src/audio.c
Normal file
@@ -0,0 +1,242 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
|
||||
#define _JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
|
||||
#define _JLOG_PERROR_RES(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
|
||||
#define _JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))
|
||||
|
||||
// A number of frames per 1 channel:
|
||||
// - https://github.com/xiph/opus/blob/7b05f44/src/opus_demo.c#L368
|
||||
#define _HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
|
||||
#define _HZ_TO_BUF16(_hz) (_HZ_TO_FRAMES(_hz) * 2) // One stereo frame = (16bit L) + (16bit R)
|
||||
#define _HZ_TO_BUF8(_hz) (_HZ_TO_BUF16(_hz) * sizeof(int16_t))
|
||||
|
||||
#define _MIN_PCM_HZ 8000
|
||||
#define _MAX_PCM_HZ 192000
|
||||
#define _MAX_BUF16 _HZ_TO_BUF16(_MAX_PCM_HZ)
|
||||
#define _MAX_BUF8 _HZ_TO_BUF8(_MAX_PCM_HZ)
|
||||
#define _ENCODER_INPUT_HZ 48000
|
||||
|
||||
|
||||
typedef struct {
|
||||
int16_t data[_MAX_BUF16];
|
||||
} _pcm_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[_MAX_BUF8]; // Worst case
|
||||
size_t used;
|
||||
uint64_t pts;
|
||||
} _enc_buffer_s;
|
||||
|
||||
|
||||
static void *_pcm_thread(void *v_audio);
|
||||
static void *_encoder_thread(void *v_audio);
|
||||
|
||||
|
||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
|
||||
us_audio_s *audio;
|
||||
US_CALLOC(audio, 1);
|
||||
audio->pcm_hz = pcm_hz;
|
||||
audio->pcm_queue = us_queue_init(8);
|
||||
audio->enc_queue = us_queue_init(8);
|
||||
atomic_init(&audio->stop, false);
|
||||
|
||||
int err;
|
||||
|
||||
{
|
||||
if ((err = snd_pcm_open(&audio->pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
||||
audio->pcm = NULL;
|
||||
_JLOG_PERROR_ALSA(err, "audio", "Can't open PCM capture");
|
||||
goto error;
|
||||
}
|
||||
assert(!snd_pcm_hw_params_malloc(&audio->pcm_params));
|
||||
|
||||
# define SET_PARAM(_msg, _func, ...) { \
|
||||
if ((err = _func(audio->pcm, audio->pcm_params, ##__VA_ARGS__)) < 0) { \
|
||||
_JLOG_PERROR_ALSA(err, "audio", _msg); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
SET_PARAM("Can't initialize PCM params", snd_pcm_hw_params_any);
|
||||
SET_PARAM("Can't set PCM access type", snd_pcm_hw_params_set_access, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
SET_PARAM("Can't set PCM channels numbre", snd_pcm_hw_params_set_channels, 2);
|
||||
SET_PARAM("Can't set PCM sampling format", snd_pcm_hw_params_set_format, SND_PCM_FORMAT_S16_LE);
|
||||
SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &audio->pcm_hz, 0);
|
||||
if (audio->pcm_hz < _MIN_PCM_HZ || audio->pcm_hz > _MAX_PCM_HZ) {
|
||||
US_JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
|
||||
audio->pcm_hz, _MIN_PCM_HZ, _MAX_PCM_HZ);
|
||||
goto error;
|
||||
}
|
||||
audio->pcm_frames = _HZ_TO_FRAMES(audio->pcm_hz);
|
||||
audio->pcm_size = _HZ_TO_BUF8(audio->pcm_hz);
|
||||
SET_PARAM("Can't apply PCM params", snd_pcm_hw_params);
|
||||
|
||||
# undef SET_PARAM
|
||||
}
|
||||
|
||||
if (audio->pcm_hz != _ENCODER_INPUT_HZ) {
|
||||
audio->res = speex_resampler_init(2, audio->pcm_hz, _ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
|
||||
if (err < 0) {
|
||||
audio->res = NULL;
|
||||
_JLOG_PERROR_RES(err, "audio", "Can't create resampler");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
|
||||
audio->enc = opus_encoder_create(_ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err);
|
||||
assert(err == 0);
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_BITRATE(48000)));
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)));
|
||||
// OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c
|
||||
}
|
||||
|
||||
US_JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz);
|
||||
audio->tids_created = true;
|
||||
US_THREAD_CREATE(audio->enc_tid, _encoder_thread, audio);
|
||||
US_THREAD_CREATE(audio->pcm_tid, _pcm_thread, audio);
|
||||
|
||||
return audio;
|
||||
|
||||
error:
|
||||
us_audio_destroy(audio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void us_audio_destroy(us_audio_s *audio) {
|
||||
if (audio->tids_created) {
|
||||
atomic_store(&audio->stop, true);
|
||||
US_THREAD_JOIN(audio->pcm_tid);
|
||||
US_THREAD_JOIN(audio->enc_tid);
|
||||
}
|
||||
US_DELETE(audio->enc, opus_encoder_destroy);
|
||||
US_DELETE(audio->res, speex_resampler_destroy);
|
||||
US_DELETE(audio->pcm, snd_pcm_close);
|
||||
US_DELETE(audio->pcm_params, snd_pcm_hw_params_free);
|
||||
US_QUEUE_DELETE_WITH_ITEMS(audio->enc_queue, free);
|
||||
US_QUEUE_DELETE_WITH_ITEMS(audio->pcm_queue, free);
|
||||
if (audio->tids_created) {
|
||||
US_JLOG_INFO("audio", "Pipeline closed");
|
||||
}
|
||||
free(audio);
|
||||
}
|
||||
|
||||
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
|
||||
if (atomic_load(&audio->stop)) {
|
||||
return -1;
|
||||
}
|
||||
_enc_buffer_s *buf;
|
||||
if (!us_queue_get(audio->enc_queue, (void **)&buf, 0.1)) {
|
||||
if (*size < buf->used) {
|
||||
free(buf);
|
||||
return -3;
|
||||
}
|
||||
memcpy(data, buf->data, buf->used);
|
||||
*size = buf->used;
|
||||
*pts = buf->pts;
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
static void *_pcm_thread(void *v_audio) {
|
||||
US_THREAD_RENAME("us_a_pcm");
|
||||
|
||||
us_audio_s *const audio = (us_audio_s *)v_audio;
|
||||
uint8_t in[_MAX_BUF8];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
const int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
|
||||
if (frames < 0) {
|
||||
_JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames");
|
||||
break;
|
||||
} else if (frames < (int)audio->pcm_frames) {
|
||||
US_JLOG_ERROR("audio", "Fatal: Too few PCM frames captured");
|
||||
break;
|
||||
}
|
||||
|
||||
if (us_queue_get_free(audio->pcm_queue)) {
|
||||
_pcm_buffer_s *out;
|
||||
US_CALLOC(out, 1);
|
||||
memcpy(out->data, in, audio->pcm_size);
|
||||
assert(!us_queue_put(audio->pcm_queue, out, 0));
|
||||
} else {
|
||||
US_JLOG_ERROR("audio", "PCM queue is full");
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&audio->stop, true);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_encoder_thread(void *v_audio) {
|
||||
US_THREAD_RENAME("us_a_enc");
|
||||
|
||||
us_audio_s *const audio = (us_audio_s *)v_audio;
|
||||
int16_t in_res[_MAX_BUF16];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
_pcm_buffer_s *in;
|
||||
if (!us_queue_get(audio->pcm_queue, (void **)&in, 0.1)) {
|
||||
int16_t *in_ptr;
|
||||
if (audio->res != NULL) {
|
||||
assert(audio->pcm_hz != _ENCODER_INPUT_HZ);
|
||||
uint32_t in_count = audio->pcm_frames;
|
||||
uint32_t out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
|
||||
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count);
|
||||
in_ptr = in_res;
|
||||
} else {
|
||||
assert(audio->pcm_hz == _ENCODER_INPUT_HZ);
|
||||
in_ptr = in->data;
|
||||
}
|
||||
|
||||
_enc_buffer_s *out;
|
||||
US_CALLOC(out, 1);
|
||||
const int size = opus_encode(audio->enc, in_ptr, _HZ_TO_FRAMES(_ENCODER_INPUT_HZ), out->data, US_ARRAY_LEN(out->data));
|
||||
free(in);
|
||||
if (size < 0) {
|
||||
_JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
|
||||
free(out);
|
||||
break;
|
||||
}
|
||||
out->used = size;
|
||||
out->pts = audio->pts;
|
||||
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
|
||||
audio->pts += _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
|
||||
|
||||
if (us_queue_put(audio->enc_queue, out, 0) != 0) {
|
||||
US_JLOG_ERROR("audio", "OPUS encoder queue is full");
|
||||
free(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&audio->stop, true);
|
||||
return NULL;
|
||||
}
|
||||
69
janus/src/audio.h
Normal file
69
janus/src/audio.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <speex/speex_resampler.h>
|
||||
#include <opus/opus.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/array.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
#include "logging.h"
|
||||
#include "queue.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
snd_pcm_t *pcm;
|
||||
unsigned pcm_hz;
|
||||
unsigned pcm_frames;
|
||||
size_t pcm_size;
|
||||
snd_pcm_hw_params_t *pcm_params;
|
||||
SpeexResamplerState *res;
|
||||
OpusEncoder *enc;
|
||||
|
||||
us_queue_s *pcm_queue;
|
||||
us_queue_s *enc_queue;
|
||||
uint32_t pts;
|
||||
|
||||
pthread_t pcm_tid;
|
||||
pthread_t enc_tid;
|
||||
bool tids_created;
|
||||
atomic_bool stop;
|
||||
} us_audio_s;
|
||||
|
||||
|
||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
|
||||
void us_audio_destroy(us_audio_s *audio);
|
||||
|
||||
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
|
||||
119
janus/src/client.c
Normal file
119
janus/src/client.c
Normal file
@@ -0,0 +1,119 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "client.h"
|
||||
|
||||
|
||||
static void *_video_thread(void *v_client);
|
||||
static void *_audio_thread(void *v_client);
|
||||
static void *_common_thread(void *v_client, bool video);
|
||||
|
||||
|
||||
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio) {
|
||||
us_janus_client_s *client;
|
||||
US_CALLOC(client, 1);
|
||||
client->gw = gw;
|
||||
client->session = session;
|
||||
atomic_init(&client->transmit, true);
|
||||
|
||||
atomic_init(&client->stop, false);
|
||||
|
||||
client->video_queue = us_queue_init(1024);
|
||||
US_THREAD_CREATE(client->video_tid, _video_thread, client);
|
||||
|
||||
if (has_audio) {
|
||||
client->audio_queue = us_queue_init(64);
|
||||
US_THREAD_CREATE(client->audio_tid, _audio_thread, client);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
void us_janus_client_destroy(us_janus_client_s *client) {
|
||||
atomic_store(&client->stop, true);
|
||||
us_queue_put(client->video_queue, NULL, 0);
|
||||
if (client->audio_queue != NULL) {
|
||||
us_queue_put(client->audio_queue, NULL, 0);
|
||||
}
|
||||
|
||||
US_THREAD_JOIN(client->video_tid);
|
||||
US_QUEUE_DELETE_WITH_ITEMS(client->video_queue, us_rtp_destroy);
|
||||
if (client->audio_queue != NULL) {
|
||||
US_THREAD_JOIN(client->audio_tid);
|
||||
US_QUEUE_DELETE_WITH_ITEMS(client->audio_queue, us_rtp_destroy);
|
||||
}
|
||||
free(client);
|
||||
}
|
||||
|
||||
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
|
||||
if (
|
||||
!atomic_load(&client->transmit)
|
||||
|| (!rtp->video && client->audio_queue == NULL)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
us_rtp_s *const new = us_rtp_dup(rtp);
|
||||
if (us_queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
|
||||
US_JLOG_ERROR("client", "Session %p %s queue is full",
|
||||
client->session, (new->video ? "video" : "audio"));
|
||||
us_rtp_destroy(new);
|
||||
}
|
||||
}
|
||||
|
||||
static void *_video_thread(void *v_client) {
|
||||
return _common_thread(v_client, true);
|
||||
}
|
||||
|
||||
static void *_audio_thread(void *v_client) {
|
||||
return _common_thread(v_client, false);
|
||||
}
|
||||
|
||||
static void *_common_thread(void *v_client, bool video) {
|
||||
us_janus_client_s *const client = (us_janus_client_s *)v_client;
|
||||
us_queue_s *const queue = (video ? client->video_queue : client->audio_queue);
|
||||
assert(queue != NULL); // Audio may be NULL
|
||||
|
||||
while (!atomic_load(&client->stop)) {
|
||||
us_rtp_s *rtp;
|
||||
if (!us_queue_get(queue, (void **)&rtp, 0.1)) {
|
||||
if (rtp == NULL) {
|
||||
break;
|
||||
}
|
||||
if (atomic_load(&client->transmit)) {
|
||||
janus_plugin_rtp packet = {0};
|
||||
packet.video = rtp->video;
|
||||
packet.buffer = (char *)rtp->datagram;
|
||||
packet.length = rtp->used;
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
// FIXME: Это очень эффективный способ уменьшить задержку, но WebRTC стек в хроме и фоксе
|
||||
// слишком корявый, чтобы обработать это, из-за чего на кейфреймах начинаются заикания.
|
||||
// - https://github.com/Glimesh/janus-ftl-plugin/issues/101
|
||||
if (rtp->zero_playout_delay) {
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 0;
|
||||
}
|
||||
client->gw->relay_rtp(client->session, &packet);
|
||||
}
|
||||
us_rtp_destroy(rtp);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
@@ -21,27 +22,40 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Component.h>
|
||||
#include <pthread.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/list.h"
|
||||
|
||||
#include "logging.h"
|
||||
#include "queue.h"
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
#define OMX_INIT_STRUCTURE(_var) { \
|
||||
memset(&(_var), 0, sizeof(_var)); \
|
||||
(_var).nSize = sizeof(_var); \
|
||||
(_var).nVersion.nVersion = OMX_VERSION; \
|
||||
(_var).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \
|
||||
(_var).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \
|
||||
(_var).nVersion.s.nRevision = OMX_VERSION_REVISION; \
|
||||
(_var).nVersion.s.nStep = OMX_VERSION_STEP; \
|
||||
}
|
||||
typedef struct us_janus_client_sx {
|
||||
janus_callbacks *gw;
|
||||
janus_plugin_session *session;
|
||||
atomic_bool transmit;
|
||||
|
||||
pthread_t video_tid;
|
||||
pthread_t audio_tid;
|
||||
atomic_bool stop;
|
||||
|
||||
us_queue_s *video_queue;
|
||||
us_queue_s *audio_queue;
|
||||
|
||||
US_LIST_STRUCT(struct us_janus_client_sx);
|
||||
} us_janus_client_s;
|
||||
|
||||
|
||||
int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
|
||||
int component_disable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
|
||||
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio);
|
||||
void us_janus_client_destroy(us_janus_client_s *client);
|
||||
|
||||
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, const OMX_U32 port);
|
||||
int component_set_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef);
|
||||
|
||||
int component_set_state(OMX_HANDLETYPE *component, const OMX_STATETYPE state);
|
||||
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp);
|
||||
99
janus/src/config.c
Normal file
99
janus/src/config.c
Normal file
@@ -0,0 +1,99 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
||||
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
|
||||
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
||||
|
||||
|
||||
us_config_s *us_config_init(const char *config_dir_path) {
|
||||
us_config_s *config;
|
||||
US_CALLOC(config, 1);
|
||||
|
||||
char *config_file_path;
|
||||
janus_config *jcfg = NULL;
|
||||
|
||||
US_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, US_PLUGIN_PACKAGE);
|
||||
US_JLOG_INFO("config", "Reading config file '%s' ...", config_file_path);
|
||||
|
||||
jcfg = janus_config_parse(config_file_path);
|
||||
if (jcfg == NULL) {
|
||||
US_JLOG_ERROR("config", "Can't read config");
|
||||
goto error;
|
||||
}
|
||||
janus_config_print(jcfg);
|
||||
|
||||
if (
|
||||
(config->video_sink_name = _get_value(jcfg, "memsink", "object")) == NULL
|
||||
&& (config->video_sink_name = _get_value(jcfg, "video", "sink")) == NULL
|
||||
) {
|
||||
US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
|
||||
goto error;
|
||||
}
|
||||
if ((config->video_zero_playout_delay = _get_bool(jcfg, "video", "zero_playout_delay", false)) == true) {
|
||||
US_JLOG_INFO("config", "Enabled the experimental Playout-Delay=0 RTP extension for VIDEO");
|
||||
}
|
||||
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
|
||||
US_JLOG_INFO("config", "Enabled the experimental AUDIO feature");
|
||||
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
|
||||
US_JLOG_INFO("config", "Missing config value: audio.tc358743");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
goto ok;
|
||||
error:
|
||||
us_config_destroy(config);
|
||||
config = NULL;
|
||||
ok:
|
||||
US_DELETE(jcfg, janus_config_destroy);
|
||||
free(config_file_path);
|
||||
return config;
|
||||
}
|
||||
|
||||
void us_config_destroy(us_config_s *config) {
|
||||
US_DELETE(config->video_sink_name, free);
|
||||
US_DELETE(config->audio_dev_name, free);
|
||||
US_DELETE(config->tc358743_dev_path, free);
|
||||
free(config);
|
||||
}
|
||||
|
||||
static char *_get_value(janus_config *jcfg, const char *section, const char *option) {
|
||||
janus_config_category *section_obj = janus_config_get_create(jcfg, NULL, janus_config_type_category, section);
|
||||
janus_config_item *option_obj = janus_config_get(jcfg, section_obj, janus_config_type_item, option);
|
||||
if (option_obj == NULL || option_obj->value == NULL || option_obj->value[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
return us_strdup(option_obj->value);
|
||||
}
|
||||
|
||||
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def) {
|
||||
char *const tmp = _get_value(jcfg, section, option);
|
||||
bool value = def;
|
||||
if (tmp != NULL) {
|
||||
value = (!strcasecmp(tmp, "1") || !strcasecmp(tmp, "true") || !strcasecmp(tmp, "yes"));
|
||||
free(tmp);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
47
janus/src/config.h
Normal file
47
janus/src/config.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <janus/config.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
#include "const.h"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *video_sink_name;
|
||||
bool video_zero_playout_delay;
|
||||
|
||||
char *audio_dev_name;
|
||||
char *tc358743_dev_path;
|
||||
} us_config_s;
|
||||
|
||||
|
||||
us_config_s *us_config_init(const char *config_dir_path);
|
||||
void us_config_destroy(us_config_s *config);
|
||||
@@ -1,7 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
@@ -21,7 +22,5 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../device.h"
|
||||
|
||||
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, const unsigned quality);
|
||||
#define US_PLUGIN_NAME "ustreamer"
|
||||
#define US_PLUGIN_PACKAGE "janus.plugin.ustreamer"
|
||||
38
janus/src/logging.h
Normal file
38
janus/src/logging.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
#include "const.h"
|
||||
|
||||
|
||||
#define US_JLOG_INFO(x_prefix, x_msg, ...) JANUS_LOG(LOG_INFO, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
|
||||
#define US_JLOG_WARN(x_prefix, x_msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
|
||||
#define US_JLOG_ERROR(x_prefix, x_msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
|
||||
|
||||
#define US_JLOG_PERROR(x_prefix, x_msg, ...) { \
|
||||
char *const m_perror_str = us_errno_to_string(errno); \
|
||||
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \
|
||||
free(m_perror_str); \
|
||||
}
|
||||
73
janus/src/memsinkfd.c
Normal file
73
janus/src/memsinkfd.c
Normal file
@@ -0,0 +1,73 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "memsinkfd.h"
|
||||
|
||||
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id) {
|
||||
const long double deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
|
||||
long double now;
|
||||
do {
|
||||
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
|
||||
now = us_get_now_monotonic();
|
||||
if (result < 0 && errno != EWOULDBLOCK) {
|
||||
US_JLOG_PERROR("video", "Can't lock memsink");
|
||||
return -1;
|
||||
} else if (result == 0) {
|
||||
if (mem->magic == US_MEMSINK_MAGIC && mem->version == US_MEMSINK_VERSION && mem->id != last_id) {
|
||||
return 0;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
US_JLOG_PERROR("video", "Can't unlock memsink");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
usleep(1000); // lock_polling
|
||||
} while (now < deadline_ts);
|
||||
return -2;
|
||||
}
|
||||
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required) {
|
||||
us_frame_s *frame = us_frame_init();
|
||||
us_frame_set_data(frame, mem->data, mem->used);
|
||||
US_FRAME_COPY_META(mem, frame);
|
||||
*frame_id = mem->id;
|
||||
mem->last_client_ts = us_get_now_monotonic();
|
||||
if (key_required) {
|
||||
mem->key_requested = true;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
if (frame->format != V4L2_PIX_FMT_H264) {
|
||||
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
|
||||
ok = false;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
US_JLOG_PERROR("video", "Can't unlock memsink");
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) {
|
||||
us_frame_destroy(frame);
|
||||
frame = NULL;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
39
janus/src/memsinkfd.h
Normal file
39
janus/src/memsinkfd.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id);
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required);
|
||||
509
janus/src/plugin.c
Normal file
509
janus/src/plugin.c
Normal file
@@ -0,0 +1,509 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <jansson.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/const.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/list.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
|
||||
#include "const.h"
|
||||
#include "logging.h"
|
||||
#include "queue.h"
|
||||
#include "client.h"
|
||||
#include "audio.h"
|
||||
#include "tc358743.h"
|
||||
#include "rtp.h"
|
||||
#include "rtpv.h"
|
||||
#include "rtpa.h"
|
||||
#include "memsinkfd.h"
|
||||
#include "config.h"
|
||||
|
||||
|
||||
static us_config_s *_g_config = NULL;
|
||||
const useconds_t _g_watchers_polling = 100000;
|
||||
|
||||
static us_janus_client_s *_g_clients = NULL;
|
||||
static janus_callbacks *_g_gw = NULL;
|
||||
static us_queue_s *_g_video_queue = NULL;
|
||||
static us_rtpv_s *_g_rtpv = NULL;
|
||||
static us_rtpa_s *_g_rtpa = NULL;
|
||||
|
||||
static pthread_t _g_video_rtp_tid;
|
||||
static atomic_bool _g_video_rtp_tid_created = false;
|
||||
static pthread_t _g_video_sink_tid;
|
||||
static atomic_bool _g_video_sink_tid_created = false;
|
||||
static pthread_t _g_audio_tid;
|
||||
static atomic_bool _g_audio_tid_created = false;
|
||||
|
||||
static pthread_mutex_t _g_video_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t _g_audio_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static atomic_bool _g_ready = false;
|
||||
static atomic_bool _g_stop = false;
|
||||
static atomic_bool _g_has_watchers = false;
|
||||
static atomic_bool _g_key_required = false;
|
||||
|
||||
|
||||
#define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock)
|
||||
#define _UNLOCK_VIDEO US_MUTEX_UNLOCK(_g_video_lock)
|
||||
|
||||
#define _LOCK_AUDIO US_MUTEX_LOCK(_g_audio_lock)
|
||||
#define _UNLOCK_AUDIO US_MUTEX_UNLOCK(_g_audio_lock)
|
||||
|
||||
#define _LOCK_ALL { _LOCK_VIDEO; _LOCK_AUDIO; }
|
||||
#define _UNLOCK_ALL { _UNLOCK_AUDIO; _UNLOCK_VIDEO; }
|
||||
|
||||
#define _READY atomic_load(&_g_ready)
|
||||
#define _STOP atomic_load(&_g_stop)
|
||||
#define _HAS_WATCHERS atomic_load(&_g_has_watchers)
|
||||
|
||||
|
||||
janus_plugin *create(void);
|
||||
|
||||
|
||||
#define _IF_NOT_REPORTED(...) { \
|
||||
const unsigned _error_code = __LINE__; \
|
||||
if (error_reported != _error_code) { __VA_ARGS__; error_reported = _error_code; } \
|
||||
}
|
||||
|
||||
static void *_video_rtp_thread(UNUSED void *arg) {
|
||||
US_THREAD_RENAME("us_video_rtp");
|
||||
atomic_store(&_g_video_rtp_tid_created, true);
|
||||
|
||||
while (!_STOP) {
|
||||
us_frame_s *frame;
|
||||
if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
|
||||
_LOCK_VIDEO;
|
||||
us_rtpv_wrap(_g_rtpv, frame);
|
||||
_UNLOCK_VIDEO;
|
||||
us_frame_destroy(frame);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_video_sink_thread(UNUSED void *arg) {
|
||||
US_THREAD_RENAME("us_video_sink");
|
||||
atomic_store(&_g_video_sink_tid_created, true);
|
||||
|
||||
uint64_t frame_id = 0;
|
||||
unsigned error_reported = 0;
|
||||
|
||||
while (!_STOP) {
|
||||
if (!_HAS_WATCHERS) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
||||
usleep(_g_watchers_polling);
|
||||
continue;
|
||||
}
|
||||
|
||||
int fd = -1;
|
||||
us_memsink_shared_s *mem = NULL;
|
||||
|
||||
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't open memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
if ((mem = us_memsink_shared_map(fd)) == NULL) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't map memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
error_reported = 0;
|
||||
|
||||
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
|
||||
while (!_STOP && _HAS_WATCHERS) {
|
||||
const int result = us_memsink_fd_wait_frame(fd, mem, frame_id);
|
||||
if (result == 0) {
|
||||
us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id, atomic_load(&_g_key_required));
|
||||
if (frame == NULL) {
|
||||
goto close_memsink;
|
||||
}
|
||||
// if (frame->key) {
|
||||
// atomic_store(&_g_key_required, false);
|
||||
// }
|
||||
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); });
|
||||
us_frame_destroy(frame);
|
||||
}
|
||||
} else if (result == -1) {
|
||||
goto close_memsink;
|
||||
}
|
||||
}
|
||||
|
||||
close_memsink:
|
||||
if (mem != NULL) {
|
||||
US_JLOG_INFO("video", "Memsink closed");
|
||||
us_memsink_shared_unmap(mem);
|
||||
}
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_audio_thread(UNUSED void *arg) {
|
||||
US_THREAD_RENAME("us_audio");
|
||||
atomic_store(&_g_audio_tid_created, true);
|
||||
assert(_g_config->audio_dev_name != NULL);
|
||||
assert(_g_config->tc358743_dev_path != NULL);
|
||||
|
||||
unsigned error_reported = 0;
|
||||
|
||||
while (!_STOP) {
|
||||
if (!_HAS_WATCHERS) {
|
||||
usleep(_g_watchers_polling);
|
||||
continue;
|
||||
}
|
||||
|
||||
us_tc358743_info_s info = {0};
|
||||
us_audio_s *audio = NULL;
|
||||
|
||||
if (us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0) {
|
||||
goto close_audio;
|
||||
}
|
||||
if (!info.has_audio) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "No audio presented from the host"); });
|
||||
goto close_audio;
|
||||
}
|
||||
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "Detected host audio"); });
|
||||
if ((audio = us_audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
error_reported = 0;
|
||||
|
||||
while (!_STOP && _HAS_WATCHERS) {
|
||||
if (
|
||||
us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|
||||
|| !info.has_audio
|
||||
|| audio->pcm_hz != info.audio_hz
|
||||
) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
size_t size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
|
||||
uint8_t data[size];
|
||||
uint64_t pts;
|
||||
const int result = us_audio_get_encoded(audio, data, &size, &pts);
|
||||
if (result == 0) {
|
||||
_LOCK_AUDIO;
|
||||
us_rtpa_wrap(_g_rtpa, data, size, pts);
|
||||
_UNLOCK_AUDIO;
|
||||
} else if (result == -1) {
|
||||
goto close_audio;
|
||||
}
|
||||
}
|
||||
|
||||
close_audio:
|
||||
US_DELETE(audio, us_audio_destroy);
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#undef _IF_NOT_REPORTED
|
||||
|
||||
static void _relay_rtp_clients(const us_rtp_s *rtp) {
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
us_janus_client_send(client, rtp);
|
||||
});
|
||||
}
|
||||
|
||||
static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
|
||||
// https://groups.google.com/g/meetecho-janus/c/xoWIQfaoJm8
|
||||
// sysctl -w net.core.rmem_default=500000
|
||||
// sysctl -w net.core.wmem_default=500000
|
||||
// sysctl -w net.core.rmem_max=1000000
|
||||
// sysctl -w net.core.wmem_max=1000000
|
||||
|
||||
US_JLOG_INFO("main", "Initializing PiKVM uStreamer plugin %s ...", US_VERSION);
|
||||
if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
_g_gw = gw;
|
||||
|
||||
_g_video_queue = us_queue_init(1024);
|
||||
_g_rtpv = us_rtpv_init(_relay_rtp_clients, _g_config->video_zero_playout_delay);
|
||||
if (_g_config->audio_dev_name != NULL) {
|
||||
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
|
||||
US_THREAD_CREATE(_g_audio_tid, _audio_thread, NULL);
|
||||
}
|
||||
US_THREAD_CREATE(_g_video_rtp_tid, _video_rtp_thread, NULL);
|
||||
US_THREAD_CREATE(_g_video_sink_tid, _video_sink_thread, NULL);
|
||||
|
||||
atomic_store(&_g_ready, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _plugin_destroy(void) {
|
||||
US_JLOG_INFO("main", "Destroying plugin ...");
|
||||
|
||||
atomic_store(&_g_stop, true);
|
||||
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } }
|
||||
JOIN(_g_video_sink_tid);
|
||||
JOIN(_g_video_rtp_tid);
|
||||
JOIN(_g_audio_tid);
|
||||
# undef JOIN
|
||||
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
US_LIST_REMOVE(_g_clients, client);
|
||||
us_janus_client_destroy(client);
|
||||
});
|
||||
|
||||
US_QUEUE_DELETE_WITH_ITEMS(_g_video_queue, us_frame_destroy);
|
||||
|
||||
US_DELETE(_g_rtpa, us_rtpa_destroy);
|
||||
US_DELETE(_g_rtpv, us_rtpv_destroy);
|
||||
US_DELETE(_g_config, us_config_destroy);
|
||||
}
|
||||
|
||||
#define _IF_DISABLED(...) { if (!_READY || _STOP) { __VA_ARGS__ } }
|
||||
|
||||
static void _plugin_create_session(janus_plugin_session *session, int *err) {
|
||||
_IF_DISABLED({ *err = -1; return; });
|
||||
_LOCK_ALL;
|
||||
US_JLOG_INFO("main", "Creating session %p ...", session);
|
||||
us_janus_client_s *const client = us_janus_client_init(_g_gw, session, (_g_config->audio_dev_name != NULL));
|
||||
US_LIST_APPEND(_g_clients, client);
|
||||
atomic_store(&_g_has_watchers, true);
|
||||
_UNLOCK_ALL;
|
||||
}
|
||||
|
||||
static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
||||
_IF_DISABLED({ *err = -1; return; });
|
||||
_LOCK_ALL;
|
||||
bool found = false;
|
||||
bool has_watchers = false;
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
US_JLOG_INFO("main", "Removing session %p ...", session);
|
||||
US_LIST_REMOVE(_g_clients, client);
|
||||
us_janus_client_destroy(client);
|
||||
found = true;
|
||||
} else {
|
||||
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
||||
}
|
||||
});
|
||||
if (!found) {
|
||||
US_JLOG_WARN("main", "No session %p", session);
|
||||
*err = -2;
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
_UNLOCK_ALL;
|
||||
}
|
||||
|
||||
static json_t *_plugin_query_session(janus_plugin_session *session) {
|
||||
_IF_DISABLED({ return NULL; });
|
||||
json_t *info = NULL;
|
||||
_LOCK_ALL;
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
info = json_string("session_found");
|
||||
break;
|
||||
}
|
||||
});
|
||||
_UNLOCK_ALL;
|
||||
return info;
|
||||
}
|
||||
|
||||
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
|
||||
_IF_DISABLED({ return; });
|
||||
_LOCK_ALL;
|
||||
bool found = false;
|
||||
bool has_watchers = false;
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
atomic_store(&client->transmit, transmit);
|
||||
// US_JLOG_INFO("main", "%s session %p", msg, session);
|
||||
found = true;
|
||||
}
|
||||
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
||||
});
|
||||
if (!found) {
|
||||
US_JLOG_WARN("main", "No session %p", session);
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
_UNLOCK_ALL;
|
||||
}
|
||||
|
||||
#undef _IF_DISABLED
|
||||
|
||||
static void _plugin_setup_media(janus_plugin_session *session) { _set_transmit(session, "Unmuted", true); }
|
||||
static void _plugin_hangup_media(janus_plugin_session *session) { _set_transmit(session, "Muted", false); }
|
||||
|
||||
static struct janus_plugin_result *_plugin_handle_message(
|
||||
janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep) {
|
||||
|
||||
assert(transaction != NULL);
|
||||
|
||||
# define FREE_MSG_JSEP { \
|
||||
US_DELETE(msg, json_decref); \
|
||||
US_DELETE(jsep, json_decref); \
|
||||
}
|
||||
|
||||
if (session == NULL || msg == NULL) {
|
||||
free(transaction);
|
||||
FREE_MSG_JSEP;
|
||||
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, (msg ? "No session" : "No message"), NULL);
|
||||
}
|
||||
|
||||
# define PUSH_ERROR(x_error, x_reason) { \
|
||||
/*US_JLOG_ERROR("main", "Message error in session %p: %s", session, x_reason);*/ \
|
||||
json_t *m_event = json_object(); \
|
||||
json_object_set_new(m_event, "ustreamer", json_string("event")); \
|
||||
json_object_set_new(m_event, "error_code", json_integer(x_error)); \
|
||||
json_object_set_new(m_event, "error", json_string(x_reason)); \
|
||||
_g_gw->push_event(session, create(), transaction, m_event, NULL); \
|
||||
json_decref(m_event); \
|
||||
}
|
||||
|
||||
json_t *const request_obj = json_object_get(msg, "request");
|
||||
if (request_obj == NULL) {
|
||||
PUSH_ERROR(400, "Request missing");
|
||||
goto ok_wait;
|
||||
}
|
||||
|
||||
const char *const request_str = json_string_value(request_obj);
|
||||
if (request_str == NULL) {
|
||||
PUSH_ERROR(400, "Request not a string");
|
||||
goto ok_wait;
|
||||
}
|
||||
// US_JLOG_INFO("main", "Message: %s", request_str);
|
||||
|
||||
# define PUSH_STATUS(x_status, x_jsep) { \
|
||||
json_t *const m_event = json_object(); \
|
||||
json_object_set_new(m_event, "ustreamer", json_string("event")); \
|
||||
json_t *const m_result = json_object(); \
|
||||
json_object_set_new(m_result, "status", json_string(x_status)); \
|
||||
json_object_set_new(m_event, "result", m_result); \
|
||||
_g_gw->push_event(session, create(), transaction, m_event, x_jsep); \
|
||||
json_decref(m_event); \
|
||||
}
|
||||
|
||||
if (!strcmp(request_str, "start")) {
|
||||
PUSH_STATUS("started", NULL);
|
||||
|
||||
} else if (!strcmp(request_str, "stop")) {
|
||||
PUSH_STATUS("stopped", NULL);
|
||||
|
||||
} else if (!strcmp(request_str, "watch")) {
|
||||
char *sdp;
|
||||
{
|
||||
// atomic_store(&_g_key_required, true);
|
||||
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
|
||||
if (video_sdp == NULL) {
|
||||
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
|
||||
goto ok_wait;
|
||||
}
|
||||
char *const audio_sdp = (_g_rtpa ? us_rtpa_make_sdp(_g_rtpa) : us_strdup(""));
|
||||
US_ASPRINTF(sdp,
|
||||
"v=0" RN
|
||||
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
|
||||
"s=PiKVM uStreamer" RN
|
||||
"t=0 0" RN
|
||||
"%s%s",
|
||||
us_get_now_id() >> 1, audio_sdp, video_sdp
|
||||
);
|
||||
free(audio_sdp);
|
||||
free(video_sdp);
|
||||
}
|
||||
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
|
||||
free(sdp);
|
||||
PUSH_STATUS("started", offer_jsep);
|
||||
json_decref(offer_jsep);
|
||||
|
||||
} else {
|
||||
PUSH_ERROR(405, "Not implemented");
|
||||
}
|
||||
|
||||
ok_wait:
|
||||
FREE_MSG_JSEP;
|
||||
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
|
||||
|
||||
# undef PUSH_STATUS
|
||||
# undef PUSH_ERROR
|
||||
# undef FREE_MSG_JSEP
|
||||
}
|
||||
|
||||
|
||||
// ***** Plugin *****
|
||||
|
||||
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
|
||||
static int _plugin_get_version(void) { return US_VERSION_U; }
|
||||
static const char *_plugin_get_version_string(void) { return US_VERSION; }
|
||||
static const char *_plugin_get_description(void) { return "PiKVM uStreamer Janus plugin for H.264 video"; }
|
||||
static const char *_plugin_get_name(void) { return US_PLUGIN_NAME; }
|
||||
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
|
||||
static const char *_plugin_get_package(void) { return US_PLUGIN_PACKAGE; }
|
||||
|
||||
static void _plugin_incoming_rtp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtp *packet) {
|
||||
// Just a stub to avoid logging spam about the plugin's purpose
|
||||
}
|
||||
|
||||
janus_plugin *create(void) {
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Woverride-init"
|
||||
static janus_plugin plugin = JANUS_PLUGIN_INIT(
|
||||
.init = _plugin_init,
|
||||
.destroy = _plugin_destroy,
|
||||
|
||||
.create_session = _plugin_create_session,
|
||||
.destroy_session = _plugin_destroy_session,
|
||||
.query_session = _plugin_query_session,
|
||||
|
||||
.setup_media = _plugin_setup_media,
|
||||
.hangup_media = _plugin_hangup_media,
|
||||
|
||||
.handle_message = _plugin_handle_message,
|
||||
|
||||
.get_api_compatibility = _plugin_get_api_compatibility,
|
||||
.get_version = _plugin_get_version,
|
||||
.get_version_string = _plugin_get_version_string,
|
||||
.get_description = _plugin_get_description,
|
||||
.get_name = _plugin_get_name,
|
||||
.get_author = _plugin_get_author,
|
||||
.get_package = _plugin_get_package,
|
||||
|
||||
.incoming_rtp = _plugin_incoming_rtp,
|
||||
);
|
||||
# pragma GCC diagnostic pop
|
||||
return &plugin;
|
||||
}
|
||||
102
janus/src/queue.c
Normal file
102
janus/src/queue.c
Normal file
@@ -0,0 +1,102 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
|
||||
us_queue_s *us_queue_init(unsigned capacity) {
|
||||
us_queue_s *queue;
|
||||
US_CALLOC(queue, 1);
|
||||
US_CALLOC(queue->items, capacity);
|
||||
queue->capacity = capacity;
|
||||
US_MUTEX_INIT(queue->mutex);
|
||||
|
||||
pthread_condattr_t attrs;
|
||||
assert(!pthread_condattr_init(&attrs));
|
||||
assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
|
||||
assert(!pthread_cond_init(&queue->full_cond, &attrs));
|
||||
assert(!pthread_cond_init(&queue->empty_cond, &attrs));
|
||||
assert(!pthread_condattr_destroy(&attrs));
|
||||
return queue;
|
||||
}
|
||||
|
||||
void us_queue_destroy(us_queue_s *queue) {
|
||||
US_COND_DESTROY(queue->empty_cond);
|
||||
US_COND_DESTROY(queue->full_cond);
|
||||
US_MUTEX_DESTROY(queue->mutex);
|
||||
free(queue->items);
|
||||
free(queue);
|
||||
}
|
||||
|
||||
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \
|
||||
struct timespec m_ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
|
||||
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
|
||||
while (x_var) { \
|
||||
const int err = pthread_cond_timedwait(&(x_cond), &queue->mutex, &m_ts); \
|
||||
if (err == ETIMEDOUT) { \
|
||||
US_MUTEX_UNLOCK(queue->mutex); \
|
||||
return -1; \
|
||||
} \
|
||||
assert(!err); \
|
||||
} \
|
||||
}
|
||||
|
||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
if (timeout == 0) {
|
||||
if (queue->size == queue->capacity) {
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
_WAIT_OR_UNLOCK(queue->size == queue->capacity, queue->full_cond);
|
||||
}
|
||||
queue->items[queue->in] = item;
|
||||
++queue->size;
|
||||
++queue->in;
|
||||
queue->in %= queue->capacity;
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
US_COND_BROADCAST(queue->empty_cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond);
|
||||
*item = queue->items[queue->out];
|
||||
--queue->size;
|
||||
++queue->out;
|
||||
queue->out %= queue->capacity;
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
US_COND_BROADCAST(queue->full_cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef _WAIT_OR_UNLOCK
|
||||
|
||||
int us_queue_get_free(us_queue_s *queue) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
const unsigned size = queue->size;
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
return queue->capacity - size;
|
||||
}
|
||||
68
janus/src/queue.h
Normal file
68
janus/src/queue.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
|
||||
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
|
||||
|
||||
typedef struct {
|
||||
void **items;
|
||||
unsigned size;
|
||||
unsigned capacity;
|
||||
unsigned in;
|
||||
unsigned out;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t full_cond;
|
||||
pthread_cond_t empty_cond;
|
||||
} us_queue_s;
|
||||
|
||||
|
||||
#define US_QUEUE_DELETE_WITH_ITEMS(x_queue, x_free_item) { \
|
||||
if (x_queue) { \
|
||||
while (!us_queue_get_free(x_queue)) { \
|
||||
void *m_ptr; \
|
||||
if (!us_queue_get(x_queue, &m_ptr, 0)) { \
|
||||
US_DELETE(m_ptr, x_free_item); \
|
||||
} \
|
||||
} \
|
||||
us_queue_destroy(x_queue); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
us_queue_s *us_queue_init(unsigned capacity);
|
||||
void us_queue_destroy(us_queue_s *queue);
|
||||
|
||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout);
|
||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout);
|
||||
int us_queue_get_free(us_queue_s *queue);
|
||||
64
janus/src/rtp.c
Normal file
64
janus/src/rtp.c
Normal file
@@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# This source file is partially based on this code: #
|
||||
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay) {
|
||||
us_rtp_s *rtp;
|
||||
US_CALLOC(rtp, 1);
|
||||
rtp->payload = payload;
|
||||
rtp->video = video;
|
||||
rtp->zero_playout_delay = zero_playout_delay; // See client.c
|
||||
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
|
||||
return rtp;
|
||||
}
|
||||
|
||||
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp) {
|
||||
us_rtp_s *new;
|
||||
US_CALLOC(new, 1);
|
||||
memcpy(new, rtp, sizeof(us_rtp_s));
|
||||
return new;
|
||||
}
|
||||
|
||||
void us_rtp_destroy(us_rtp_s *rtp) {
|
||||
free(rtp);
|
||||
}
|
||||
|
||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
uint32_t word0 = 0x80000000;
|
||||
if (marked) {
|
||||
word0 |= 1 << 23;
|
||||
}
|
||||
word0 |= (rtp->payload & 0x7F) << 16;
|
||||
word0 |= rtp->seq;
|
||||
++rtp->seq;
|
||||
|
||||
# define WRITE_BE_U32(_offset, _value) *((uint32_t *)(rtp->datagram + _offset)) = __builtin_bswap32(_value)
|
||||
WRITE_BE_U32(0, word0);
|
||||
WRITE_BE_U32(4, pts);
|
||||
WRITE_BE_U32(8, rtp->ssrc);
|
||||
# undef WRITE_BE_U32
|
||||
}
|
||||
57
janus/src/rtp.h
Normal file
57
janus/src/rtp.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
||||
#define US_RTP_DATAGRAM_SIZE 1200
|
||||
#define US_RTP_HEADER_SIZE 12
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned payload;
|
||||
bool video;
|
||||
bool zero_playout_delay;
|
||||
uint32_t ssrc;
|
||||
|
||||
uint16_t seq;
|
||||
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
|
||||
size_t used;
|
||||
} us_rtp_s;
|
||||
|
||||
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
|
||||
|
||||
|
||||
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay);
|
||||
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
|
||||
void us_rtp_destroy(us_rtp_s *rtp);
|
||||
|
||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked);
|
||||
66
janus/src/rtpa.c
Normal file
66
janus/src/rtpa.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "rtpa.h"
|
||||
|
||||
|
||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
|
||||
us_rtpa_s *rtpa;
|
||||
US_CALLOC(rtpa, 1);
|
||||
rtpa->rtp = us_rtp_init(111, false, false);
|
||||
rtpa->callback = callback;
|
||||
return rtpa;
|
||||
}
|
||||
|
||||
void us_rtpa_destroy(us_rtpa_s *rtpa) {
|
||||
us_rtp_destroy(rtpa->rtp);
|
||||
free(rtpa);
|
||||
}
|
||||
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
|
||||
# define PAYLOAD rtpa->rtp->payload
|
||||
char *sdp;
|
||||
US_ASPRINTF(sdp,
|
||||
"m=audio 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u OPUS/48000/2" RN
|
||||
// "a=fmtp:%u useinbandfec=1" RN
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, // PAYLOAD,
|
||||
rtpa->rtp->ssrc
|
||||
);
|
||||
# undef PAYLOAD
|
||||
return sdp;
|
||||
}
|
||||
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
us_rtp_write_header(rtpa->rtp, pts, false);
|
||||
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
|
||||
rtpa->rtp->used = size + US_RTP_HEADER_SIZE;
|
||||
rtpa->callback(rtpa->rtp);
|
||||
}
|
||||
}
|
||||
48
janus/src/rtpa.h
Normal file
48
janus/src/rtpa.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
us_rtp_s *rtp;
|
||||
us_rtp_callback_f callback;
|
||||
} us_rtpa_s;
|
||||
|
||||
|
||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
|
||||
void us_rtpa_destroy(us_rtpa_s *rtpa);
|
||||
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa);
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
|
||||
213
janus/src/rtpv.c
Normal file
213
janus/src/rtpv.c
Normal file
@@ -0,0 +1,213 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# This source file is partially based on this code: #
|
||||
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "rtpv.h"
|
||||
|
||||
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||
|
||||
|
||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay) {
|
||||
us_rtpv_s *rtpv;
|
||||
US_CALLOC(rtpv, 1);
|
||||
rtpv->rtp = us_rtp_init(96, true, zero_playout_delay);
|
||||
rtpv->callback = callback;
|
||||
rtpv->sps = us_frame_init();
|
||||
rtpv->pps = us_frame_init();
|
||||
US_MUTEX_INIT(rtpv->mutex);
|
||||
return rtpv;
|
||||
}
|
||||
|
||||
void us_rtpv_destroy(us_rtpv_s *rtpv) {
|
||||
US_MUTEX_DESTROY(rtpv->mutex);
|
||||
us_frame_destroy(rtpv->pps);
|
||||
us_frame_destroy(rtpv->sps);
|
||||
us_rtp_destroy(rtpv->rtp);
|
||||
free(rtpv);
|
||||
}
|
||||
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
US_MUTEX_LOCK(rtpv->mutex);
|
||||
|
||||
if (rtpv->sps->used == 0 || rtpv->pps->used == 0) {
|
||||
US_MUTEX_UNLOCK(rtpv->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *sps = NULL;
|
||||
char *pps = NULL;
|
||||
us_base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
|
||||
us_base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
|
||||
|
||||
US_MUTEX_UNLOCK(rtpv->mutex);
|
||||
|
||||
# define PAYLOAD rtpv->rtp->payload
|
||||
// https://tools.ietf.org/html/rfc6184
|
||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||
char *sdp;
|
||||
US_ASPRINTF(sdp,
|
||||
"m=video 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u H264/90000" RN
|
||||
"a=fmtp:%u profile-level-id=42E01F" RN
|
||||
"a=fmtp:%u packetization-mode=1" RN
|
||||
"a=fmtp:%u sprop-sps=%s" RN
|
||||
"a=fmtp:%u sprop-pps=%s" RN
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"%s" // playout-delay
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
PAYLOAD, sps,
|
||||
PAYLOAD, pps,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
rtpv->rtp->ssrc,
|
||||
(rtpv->rtp->zero_playout_delay ? "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN : "")
|
||||
);
|
||||
# undef PAYLOAD
|
||||
|
||||
free(sps);
|
||||
free(pps);
|
||||
return sdp;
|
||||
}
|
||||
|
||||
#define _PRE 3 // Annex B prefix length
|
||||
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
|
||||
// There is a complicated logic here but everything works as it should:
|
||||
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
||||
|
||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||
|
||||
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
ssize_t last_offset = -_PRE;
|
||||
|
||||
while (true) { // Find and iterate by nalus
|
||||
const size_t next_start = last_offset + _PRE;
|
||||
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
|
||||
if (offset < 0) {
|
||||
break;
|
||||
}
|
||||
offset += next_start;
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *const data = frame->data + last_offset + _PRE;
|
||||
size_t size = offset - last_offset - _PRE;
|
||||
if (data[size - 1] == 0) { // Check for extra 00
|
||||
--size;
|
||||
}
|
||||
_rtpv_process_nalu(rtpv, data, size, pts, false);
|
||||
}
|
||||
|
||||
last_offset = offset;
|
||||
}
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *const data = frame->data + last_offset + _PRE;
|
||||
size_t size = frame->used - last_offset - _PRE;
|
||||
_rtpv_process_nalu(rtpv, data, size, pts, true);
|
||||
}
|
||||
}
|
||||
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
|
||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
||||
const unsigned type = data[0] & 0x1F;
|
||||
|
||||
us_frame_s *ps = NULL;
|
||||
switch (type) {
|
||||
case 7: ps = rtpv->sps; break;
|
||||
case 8: ps = rtpv->pps; break;
|
||||
}
|
||||
if (ps != NULL) {
|
||||
US_MUTEX_LOCK(rtpv->mutex);
|
||||
us_frame_set_data(ps, data, size);
|
||||
US_MUTEX_UNLOCK(rtpv->mutex);
|
||||
}
|
||||
|
||||
# define DG rtpv->rtp->datagram
|
||||
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
us_rtp_write_header(rtpv->rtp, pts, marked);
|
||||
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
|
||||
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||
|
||||
const uint8_t *src = data + 1;
|
||||
ssize_t remaining = size - 1;
|
||||
|
||||
bool first = true;
|
||||
while (remaining > 0) {
|
||||
ssize_t frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
const bool last = (remaining <= frag_size);
|
||||
if (last) {
|
||||
frag_size = remaining;
|
||||
}
|
||||
|
||||
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
|
||||
|
||||
DG[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||
|
||||
uint8_t fu = type;
|
||||
if (first) {
|
||||
fu |= 0x80;
|
||||
}
|
||||
if (last) {
|
||||
fu |= 0x40;
|
||||
}
|
||||
DG[US_RTP_HEADER_SIZE + 1] = fu;
|
||||
|
||||
memcpy(DG + fu_overhead, src, frag_size);
|
||||
rtpv->rtp->used = fu_overhead + frag_size;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
|
||||
src += frag_size;
|
||||
remaining -= frag_size;
|
||||
first = false;
|
||||
}
|
||||
|
||||
# undef DG
|
||||
}
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
|
||||
// Parses buffer for 00 00 01 start codes
|
||||
if (size >= _PRE) {
|
||||
for (size_t index = 0; index <= size - _PRE; ++index) {
|
||||
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef _PRE
|
||||
@@ -1,7 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
@@ -21,33 +22,36 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <IL/OMX_Component.h>
|
||||
#include <interface/vcos/vcos_semaphore.h>
|
||||
#include <sys/types.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../device.h"
|
||||
#include <pthread.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/base64.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
struct omx_encoder_t {
|
||||
OMX_HANDLETYPE encoder;
|
||||
OMX_BUFFERHEADERTYPE *input_buffer;
|
||||
OMX_BUFFERHEADERTYPE *output_buffer;
|
||||
bool input_required;
|
||||
bool output_available;
|
||||
bool failed;
|
||||
VCOS_SEMAPHORE_T handler_lock;
|
||||
|
||||
bool i_omx;
|
||||
bool i_handler_lock;
|
||||
bool i_encoder;
|
||||
bool i_input_port_enabled;
|
||||
bool i_output_port_enabled;
|
||||
};
|
||||
typedef struct {
|
||||
us_rtp_s *rtp;
|
||||
us_rtp_callback_f callback;
|
||||
us_frame_s *sps; // Actually not a frame, just a bytes storage
|
||||
us_frame_s *pps;
|
||||
pthread_mutex_t mutex;
|
||||
} us_rtpv_s;
|
||||
|
||||
|
||||
struct omx_encoder_t *omx_encoder_init();
|
||||
void omx_encoder_destroy(struct omx_encoder_t *omx);
|
||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay);
|
||||
void us_rtpv_destroy(us_rtpv_s *rtpv);
|
||||
|
||||
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg);
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, const unsigned index);
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame);
|
||||
64
janus/src/tc358743.c
Normal file
64
janus/src/tc358743.c
Normal file
@@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "tc358743.h"
|
||||
|
||||
|
||||
#ifndef V4L2_CID_USER_TC358743_BASE
|
||||
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
|
||||
#endif
|
||||
#ifndef TC358743_CID_AUDIO_PRESENT
|
||||
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
|
||||
#endif
|
||||
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
|
||||
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
|
||||
#endif
|
||||
|
||||
|
||||
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
|
||||
US_MEMSET_ZERO(*info);
|
||||
|
||||
int fd = -1;
|
||||
if ((fd = open(path, O_RDWR)) < 0) {
|
||||
US_JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define READ_CID(x_cid, x_field) { \
|
||||
struct v4l2_control m_ctl = {0}; \
|
||||
m_ctl.id = x_cid; \
|
||||
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
|
||||
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
|
||||
close(fd); \
|
||||
return -1; \
|
||||
} \
|
||||
info->x_field = m_ctl.value; \
|
||||
}
|
||||
|
||||
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
|
||||
READ_CID(TC358743_CID_AUDIO_SAMPLING_RATE, audio_hz);
|
||||
|
||||
# undef READ_CID
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
46
janus/src/tc358743.h
Normal file
46
janus/src/tc358743.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/xioctl.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool has_audio;
|
||||
unsigned audio_hz;
|
||||
} us_tc358743_info_s;
|
||||
|
||||
|
||||
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info);
|
||||
1
janus/src/uslibs/array.h
Symbolic link
1
janus/src/uslibs/array.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/array.h
|
||||
1
janus/src/uslibs/base64.c
Symbolic link
1
janus/src/uslibs/base64.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/base64.c
|
||||
1
janus/src/uslibs/base64.h
Symbolic link
1
janus/src/uslibs/base64.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/base64.h
|
||||
1
janus/src/uslibs/const.h
Symbolic link
1
janus/src/uslibs/const.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/const.h
|
||||
1
janus/src/uslibs/frame.c
Symbolic link
1
janus/src/uslibs/frame.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.c
|
||||
1
janus/src/uslibs/frame.h
Symbolic link
1
janus/src/uslibs/frame.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.h
|
||||
1
janus/src/uslibs/list.h
Symbolic link
1
janus/src/uslibs/list.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/list.h
|
||||
1
janus/src/uslibs/memsinksh.h
Symbolic link
1
janus/src/uslibs/memsinksh.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsinksh.h
|
||||
1
janus/src/uslibs/threading.h
Symbolic link
1
janus/src/uslibs/threading.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/threading.h
|
||||
1
janus/src/uslibs/tools.h
Symbolic link
1
janus/src/uslibs/tools.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/tools.h
|
||||
1
janus/src/uslibs/xioctl.h
Symbolic link
1
janus/src/uslibs/xioctl.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/xioctl.h
|
||||
2
linters/.dockerignore
Normal file
2
linters/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
/.tox/
|
||||
/.mypy_cache/
|
||||
26
linters/Dockerfile
Normal file
26
linters/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM archlinux/archlinux:base-devel
|
||||
|
||||
RUN mkdir -p /etc/pacman.d/hooks \
|
||||
&& ln -s /dev/null /etc/pacman.d/hooks/30-systemd-tmpfiles.hook
|
||||
|
||||
RUN echo 'Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch' > /etc/pacman.d/mirrorlist
|
||||
|
||||
RUN pacman -Syu --noconfirm \
|
||||
&& pacman -S --needed --noconfirm \
|
||||
vim \
|
||||
git \
|
||||
libjpeg \
|
||||
libevent \
|
||||
libutil-linux \
|
||||
libbsd \
|
||||
python \
|
||||
python-pip \
|
||||
python-tox \
|
||||
cppcheck \
|
||||
npm \
|
||||
&& (pacman -Sc --noconfirm || true) \
|
||||
&& rm -rf /var/cache/pacman/pkg/*
|
||||
|
||||
RUN npm install htmlhint -g
|
||||
|
||||
CMD /bin/bash
|
||||
6
linters/cppcheck.h
Normal file
6
linters/cppcheck.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#define CHAR_BIT 8
|
||||
#define WITH_GPIO
|
||||
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
|
||||
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
|
||||
#define CLOCK_MONOTONIC_RAW 1
|
||||
#define CLOCK_MONOTONIC_FAST 1
|
||||
9
linters/flake8.ini
Normal file
9
linters/flake8.ini
Normal file
@@ -0,0 +1,9 @@
|
||||
[flake8]
|
||||
inline-quotes = double
|
||||
max-line-length = 160
|
||||
ignore = W503, E227, E241, E252, Q003
|
||||
# W503 line break before binary operator
|
||||
# E227 missing whitespace around bitwise or shift operator
|
||||
# E241 multiple spaces after
|
||||
# E252 missing whitespace around parameter equals
|
||||
# Q003 Change outer quotes to avoid escaping inner quotes
|
||||
5
linters/mypy.ini
Normal file
5
linters/mypy.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[mypy]
|
||||
python_version = 3.9
|
||||
ignore_missing_imports = true
|
||||
disallow_untyped_defs = true
|
||||
strict_optional = true
|
||||
54
linters/pylint.ini
Normal file
54
linters/pylint.ini
Normal file
@@ -0,0 +1,54 @@
|
||||
[MASTER]
|
||||
ignore = .git
|
||||
|
||||
[DESIGN]
|
||||
min-public-methods = 0
|
||||
max-args = 10
|
||||
|
||||
[TYPECHECK]
|
||||
ignored-classes=
|
||||
AioQueue,
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable =
|
||||
file-ignored,
|
||||
locally-disabled,
|
||||
fixme,
|
||||
missing-docstring,
|
||||
superfluous-parens,
|
||||
duplicate-code,
|
||||
broad-except,
|
||||
redundant-keyword-arg,
|
||||
wrong-import-order,
|
||||
too-many-ancestors,
|
||||
no-else-return,
|
||||
len-as-condition,
|
||||
unspecified-encoding,
|
||||
|
||||
[REPORTS]
|
||||
msg-template = {symbol} -- {path}:{line}({obj}): {msg}
|
||||
|
||||
[FORMAT]
|
||||
max-line-length = 160
|
||||
|
||||
[BASIC]
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names = _, __, x, y, ws, make-html-h, make-jpeg-h
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx = [a-z_][a-z0-9_]{2,50}$
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx = [a-z_][a-z0-9_]{2,50}$
|
||||
|
||||
# Regular expression which should only match correct module level names
|
||||
const-rgx = ([a-zA-Z_][a-zA-Z0-9_]*)$
|
||||
|
||||
# Regular expression which should only match correct argument names
|
||||
argument-rgx = [a-z_][a-z0-9_]{1,30}$
|
||||
|
||||
# Regular expression which should only match correct variable names
|
||||
variable-rgx = [a-z_][a-z0-9_]{1,30}$
|
||||
|
||||
# Regular expression which should only match correct instance attribute names
|
||||
attr-rgx = [a-z_][a-z0-9_]{1,30}$
|
||||
52
linters/tox.ini
Normal file
52
linters/tox.ini
Normal file
@@ -0,0 +1,52 @@
|
||||
[tox]
|
||||
envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
|
||||
skipsdist = true
|
||||
|
||||
[testenv]
|
||||
basepython = python3.10
|
||||
changedir = /src
|
||||
|
||||
[testenv:cppcheck]
|
||||
whitelist_externals = cppcheck
|
||||
commands = cppcheck \
|
||||
-j4 \
|
||||
--force \
|
||||
--std=c17 \
|
||||
--error-exitcode=1 \
|
||||
--quiet \
|
||||
--enable=warning,unusedFunction,portability,performance,style \
|
||||
--suppress=assignmentInAssert \
|
||||
--suppress=variableScope \
|
||||
--inline-suppr \
|
||||
--library=python \
|
||||
--include=linters/cppcheck.h \
|
||||
src python/*.? janus/*.?
|
||||
|
||||
[testenv:flake8]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
|
||||
deps =
|
||||
flake8
|
||||
flake8-quotes
|
||||
|
||||
[testenv:pylint]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py python/*.py'
|
||||
deps =
|
||||
pylint
|
||||
|
||||
[testenv:mypy]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py python/*.py'
|
||||
deps =
|
||||
mypy
|
||||
|
||||
[testenv:vulture]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'vulture tools/*.py python/*.py'
|
||||
deps =
|
||||
vulture
|
||||
|
||||
[testenv:htmlhint]
|
||||
whitelist_externals = htmlhint
|
||||
commands = htmlhint src/ustreamer/http/data/*.html
|
||||
94
man/ustreamer-dump.1
Normal file
94
man/ustreamer-dump.1
Normal file
@@ -0,0 +1,94 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 5.26" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B ustreamer-dump
|
||||
.RI [OPTIONS]
|
||||
|
||||
.SH DESCRIPTION
|
||||
µStreamer-dump (\fBustreamer-dump\fP) writes a local stream from ustreamer to a file or redirect it to other utilities (such as \fBffmpeg\fR).
|
||||
|
||||
.SH USAGE
|
||||
\fBustreamer\fR requires at least the \fB\-\-sink\fR option to operate.
|
||||
|
||||
To output ustreamers sink "test" to ffmpeg, and into a file called test.mp4:
|
||||
|
||||
\fBustreamer-dump \e\fR
|
||||
.RS
|
||||
\fB\-\-sink=test \e\fR # Use ustreamer sink "test"
|
||||
.nf
|
||||
\fB\-\-output\ \- \e\fR # Output to stdout
|
||||
\fB|\ ffmpeg\ \-use_wallclock_as_timestamps\ 1\ \-i\ pipe:\ \-c:v\ libx264\ test\.mp4\fR
|
||||
|
||||
.SH OPTIONS
|
||||
.SS "Sink options"
|
||||
.TP
|
||||
.BR \-s ", " \-\-sink\ \fIname
|
||||
Memory sink ID. No default.
|
||||
.TP
|
||||
.BR \-t ", " \-\-sink\-timeout\ \fIsec
|
||||
Timeout for the upcoming frame. Default: 1.
|
||||
.TP
|
||||
.BR \-o ", " \-\-output\ \fIfilename
|
||||
Filename to dump output to. Use '-' for stdout. Default: just consume the sink.
|
||||
.TP
|
||||
.BR \-j ", " \-\-output-json
|
||||
Format output as JSON. Required option --output. Default: disabled.
|
||||
.TP
|
||||
.BR \-c ", " \-\-count\ \fIN
|
||||
Limit the number of frames. Default: 0 (infinite).
|
||||
.TP
|
||||
.BR \-i ", "\-\-interval\ \fIsec
|
||||
Delay between reading frames (float). Default: 0.
|
||||
.TP
|
||||
.BR \-k ", " \-\-key
|
||||
Request keyframe from the sink. Default: disabled.
|
||||
|
||||
.SS "Logging options"
|
||||
.TP
|
||||
.BR \-\-log\-level\ \fIN
|
||||
Verbosity level of messages from 0 (info) to 3 (debug). Enabling debugging messages can slow down the program.
|
||||
Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).
|
||||
Default: 0.
|
||||
.TP
|
||||
.BR \-\-perf
|
||||
Enable performance messages (same as \-\-log\-level=1). Default: disabled.
|
||||
.TP
|
||||
.BR \-\-verbose
|
||||
Enable verbose messages and lower (same as \-\-log\-level=2). Default: disabled.
|
||||
.TP
|
||||
.BR \-\-debug
|
||||
Enable debug messages and lower (same as \-\-log\-level=3). Default: disabled.
|
||||
.TP
|
||||
.BR \-\-force\-log\-colors
|
||||
Force color logging. Default: colored if stderr is a TTY.
|
||||
.TP
|
||||
.BR \-\-no\-log\-colors
|
||||
Disable color logging. Default: ditto.
|
||||
|
||||
.SS "Help options"
|
||||
.TP
|
||||
.BR \-h ", " \-\-help
|
||||
Print this text and exit.
|
||||
.TP
|
||||
.BR \-v ", " \-\-version
|
||||
Print version and exit.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR ustreamer (1)
|
||||
|
||||
.SH BUGS
|
||||
Please file any bugs and issues at \fIhttps://github.com/pikvm/ustreamer/issues\fR
|
||||
|
||||
.SH AUTHOR
|
||||
Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
.SH HOMEPAGE
|
||||
\fIhttps://pikvm.org/\fR
|
||||
|
||||
.SH COPYRIGHT
|
||||
GNU General Public License v3.0
|
||||
336
man/ustreamer.1
Normal file
336
man/ustreamer.1
Normal file
@@ -0,0 +1,336 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 5.26" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B ustreamer
|
||||
.RI [OPTIONS]
|
||||
|
||||
.SH DESCRIPTION
|
||||
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPEG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the PiKVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
|
||||
|
||||
.SH USAGE
|
||||
Without arguments, \fBustreamer\fR will try to open \fB/dev/video0\fR with 640x480 resolution and start streaming on \fBhttp://127\.0\.0\.1:8080\fR\. You can override this behavior using parameters \fB\-\-device\fR, \fB\-\-host\fR and \fB\-\-port\fR\. For example, to stream to the world, run: \fBustreamer --device=/dev/video1 --host=0.0.0.0 --port=80\fR
|
||||
|
||||
Please note that since µStreamer v2\.0 cross\-domain requests were disabled by default for security reasons\. To enable the old behavior, use the option \fB\-\-allow\-origin=\e*\fR\.
|
||||
|
||||
For example, the recommended way of running µStreamer with Auvidea B101 on a Raspberry Pi is:
|
||||
|
||||
\fBustreamer \e\fR
|
||||
.RS
|
||||
\fB\-\-format=uyvy \e\fR # Device input format
|
||||
.nf
|
||||
\fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M intraface
|
||||
.nf
|
||||
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
|
||||
.nf
|
||||
\fB\-\-persistent \e\fR # Don\'t re\-initialize device on timeout (for example when HDMI cable was disconnected)
|
||||
.nf
|
||||
\fB\-\-dv\-timings \e\fR # Use DV\-timings
|
||||
.nf
|
||||
\fB\-\-drop\-same\-frames=30\fR # Save the traffic\fR
|
||||
.RE
|
||||
.P
|
||||
Please note that to use \fB\-\-drop\-same\-frames\fR for different browsers you need to use some specific URL \fB/stream\fR parameters (see URL \fB/\fR for details)\.
|
||||
.P
|
||||
You can always view the full list of options with \fBustreamer \-\-help\fR\. Some features may not be available on your platform. To find out which features are enabled, use \fBustreamer \-\-features\fR.
|
||||
|
||||
.SH OPTIONS
|
||||
.SS "Capturing options"
|
||||
.TP
|
||||
.BR \-d\ \fI/dev/path ", " \-\-device\ \fI/dev/path
|
||||
Path to V4L2 device. Default: /dev/video0.
|
||||
.TP
|
||||
.BR \-i\ \fIN ", " \-\-input\ \fIN
|
||||
Input channel. Default: 0.
|
||||
.TP
|
||||
.BR \-r\ \fIWxH ", " \-\-resolution\ \fIWxH
|
||||
Initial image resolution. Default: 640x480.
|
||||
.TP
|
||||
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
|
||||
Image format.
|
||||
Available: YUYV, UYVY, RGB565, RGB24, JPEG; default: YUYV.
|
||||
.TP
|
||||
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
|
||||
Force TV standard.
|
||||
Available: PAL, NTSC, SECAM; default: disabled.
|
||||
.TP
|
||||
.BR \-I\ \fImethod ", " \-\-io\-method\ \fImethod
|
||||
Set V4L2 IO method (see kernel documentation). Changing of this parameter may increase the performance. Or not.
|
||||
Available: MMAP, USERPTR; default: MMAP.
|
||||
.TP
|
||||
.BR \-f\ \fIN ", " \-\-desired\-fps\ \fIN
|
||||
Desired FPS. Default: maximum possible.
|
||||
.TP
|
||||
.BR \-z\ \fIN ", " \-\-min\-frame\-size\ \fIN
|
||||
Drop frames smaller then this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
|
||||
.TP
|
||||
.BR \-n ", " \-\-persistent
|
||||
Don't re\-initialize device on timeout. Default: disabled.
|
||||
.TP
|
||||
.BR \-t ", " \-\-dv\-timings
|
||||
Enable DV-timings querying and events processing to automatic resolution change. Default: disabled.
|
||||
.TP
|
||||
.BR \-b\ \fIN ", " \-\-buffers\ \fIN
|
||||
The number of buffers to receive data from the device. Each buffer may processed using an independent thread.
|
||||
Default: 2 (the number of CPU cores (but not more than 4) + 1).
|
||||
.TP
|
||||
.BR \-w\ \fIN ", " \-\-workers\ \fIN
|
||||
The number of worker threads but not more than buffers.
|
||||
Default: 1 (the number of CPU cores (but not more than 4)).
|
||||
.TP
|
||||
.BR \-q\ \fIN ", " \-\-quality\ \fIN
|
||||
Set quality of JPEG encoding from 1 to 100 (best). Default: 80.
|
||||
Note: If HW encoding is used (JPEG source format selected), this parameter attempts to configure the camera or capture device hardware's internal encoder. It does not re\-encode MJPEG to MJPEG to change the quality level for sources that already output MJPEG.
|
||||
.TP
|
||||
.BR \-c\ \fItype ", " \-\-encoder\ \fItype
|
||||
Use specified encoder. It may affect the number of workers.
|
||||
|
||||
CPU ─ Software MJPEG encoding (default).
|
||||
|
||||
HW ─ Use pre-encoded MJPEG frames directly from camera hardware.
|
||||
|
||||
M2M-VIDEO ─ GPU-accelerated MJPEG encoding.
|
||||
|
||||
M2M-IMAGE ─ GPU-accelerated JPEG encoding.
|
||||
|
||||
NOOP ─ Don't compress MJPEG stream (do nothing).
|
||||
.TP
|
||||
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
|
||||
It doesn't do anything. Still here for compatibility.
|
||||
.TP
|
||||
.BR \-k\ \fIpath ", " \-\-blank\ \fIpath
|
||||
Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.
|
||||
.TP
|
||||
.BR \-K\ \fIsec ", " \-\-last\-as\-blank\ \fIsec
|
||||
Show the last frame received from the camera after it was disconnected, but no more than specified time (or endlessly if 0 is specified). If the device has not yet been online, display 'NO SIGNAL' or the image specified by option \-\-blank. Note: currently this option has no effect on memory sinks. Default: disabled.
|
||||
.TP
|
||||
.BR \-l ", " \-\-slowdown
|
||||
Slowdown capturing to 1 FPS or less when no stream or sink clients are connected. Useful to reduce CPU consumption. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-device\-timeout\ \fIsec
|
||||
Timeout for device querying. Default: 1.
|
||||
.TP
|
||||
.BR \-\-device\-error\-delay\ \fIsec
|
||||
Delay before trying to connect to the device again after an error (timeout for example). Default: 1.
|
||||
.TP
|
||||
.BR \-\-m2m\-device\ \fI/dev/path
|
||||
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
|
||||
|
||||
.SS "Image control options"
|
||||
.TP
|
||||
.BR \-\-image\-default
|
||||
Reset all image settings below to default. Default: no change.
|
||||
.TP
|
||||
.BR \-\-brightness\ \fIN ", " \fIauto ", " \fIdefault
|
||||
Set brightness. Default: no change.
|
||||
.TP
|
||||
.BR \-\-contrast\ \fIN ", " \fIdefault
|
||||
Set contrast. Default: no change.
|
||||
.TP
|
||||
.BR \-\-saturation\ \fIN ", " \fIdefault
|
||||
Set saturation. Default: no change.
|
||||
.TP
|
||||
.BR \-\-hue\ \fIN ", " \fIauto ", " \fIdefault
|
||||
Set hue. Default: no change.
|
||||
.TP
|
||||
.BR \-\-gamma\ \fIN ", " \fIdefault
|
||||
Set gamma. Default: no change.
|
||||
.TP
|
||||
.BR \-\-sharpness\ \fIN ", " \fIdefault
|
||||
Set sharpness. Default: no change.
|
||||
.TP
|
||||
.BR \-\-backlight\-compensation\ \fIN ", " \fIdefault
|
||||
Set backlight compensation. Default: no change.
|
||||
.TP
|
||||
.BR \-\-white\-balance\ \fIN ", " \fIauto ", " \fIdefault
|
||||
Set white balance. Default: no change.
|
||||
.TP
|
||||
.BR \-\-gain\ \fIN ", " \fIauto ", " \fIdefault
|
||||
Set gain. Default: no change.
|
||||
.TP
|
||||
.BR \-\-color\-effect\ \fIN ", " \fIdefault
|
||||
Set color effect. Default: no change.
|
||||
.TP
|
||||
.BR \-\-flip\-vertical\ \fI1 ", " \fI0 ", " \fIdefault
|
||||
Set vertical flip. Default: no change.
|
||||
.TP
|
||||
.BR \-\-flip\-horizontal\ \fI1 ", " \fI0 ", " \fIdefault
|
||||
Set horizontal flip. Default: no change.
|
||||
|
||||
.SS "HTTP server options"
|
||||
.TP
|
||||
.BR \-s\ \fIaddress ", " \-\-host\ \fIaddress
|
||||
Listen on Hostname or IP. Default: 127.0.0.1.
|
||||
.TP
|
||||
.BR \-p\ \fIN ", " \-\-port\ \fIN
|
||||
Bind to this TCP port. Default: 8080.
|
||||
.TP
|
||||
.BR \-U\ \fIpath ", " \-\-unix\ \fIpath
|
||||
Bind to UNIX domain socket. Default: disabled.
|
||||
.TP
|
||||
.BR \-D ", " \-\-unix\-rm
|
||||
Try to remove old unix socket file before binding. default: disabled.
|
||||
.TP
|
||||
.BR \-M\ \fImode ", " \-\-unix\-mode\ \fImode
|
||||
Set UNIX socket file permissions (like 777). Default: disabled.
|
||||
.TP
|
||||
.BR \-S ", " \-\-systemd
|
||||
Bind to systemd socket for socket activation. Required \fBWITH_SYSTEMD\fR feature. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-user\ \fIname
|
||||
HTTP basic auth user. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-passwd\ \fIstr
|
||||
HTTP basic auth passwd. Default: empty.
|
||||
.TP
|
||||
.BR \-\-static\ \fIpath
|
||||
Path to dir with static files instead of embedded root index page. Symlinks are not supported for security reasons. Default: disabled.
|
||||
.TP
|
||||
.BR \-e\ \fIN ", " \-\-drop\-same\-frames\ \fIN
|
||||
Don't send identical frames to clients, but no more than specified number. It can significantly reduce the outgoing traffic, but will increase the CPU loading. Don't use this option with analog signal sources or webcams, it's useless. Default: disabled.
|
||||
.TP
|
||||
.BR \-R\ \fIWxH ", " \-\-fake\-resolution\ \fIWxH
|
||||
Override image resolution for the /state. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-tcp\-nodelay
|
||||
Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.
|
||||
Default: disabled.
|
||||
.TP
|
||||
.BR \-\-allow\-origin\ \fIstr
|
||||
Set Access\-Control\-Allow\-Origin header. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-instance\-id\ \fIstr
|
||||
A short string identifier to be displayed in the /state handle. It must satisfy regexp ^[a-zA-Z0-9\./+_-]*$. Default: an empty string.
|
||||
.TP
|
||||
.BR \-\-server\-timeout\ \fIsec
|
||||
Timeout for client connections. Default: 10.
|
||||
|
||||
.SS "JPEG sink options"
|
||||
With shared memory sink you can write a stream to a file. See \fBustreamer-dump\fR(1) for more info.
|
||||
.TP
|
||||
.BR \-\-sink\ \fIname
|
||||
Use the specified shared memory object to sink JPEG frames. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-sink\-mode\ \fImode
|
||||
Set JPEG sink permissions (like 777). Default: 660.
|
||||
.TP
|
||||
.BR \-\-sink\-rm
|
||||
Remove shared memory on stop. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-sink\-client\-ttl\ \fIsec
|
||||
Client TTL. Default: 10.
|
||||
.TP
|
||||
.BR \-\-sink\-timeout\ \fIsec
|
||||
Timeout for lock. Default: 1.
|
||||
|
||||
.SS "H264 sink options"
|
||||
.TP
|
||||
.BR \-\-h264\-sink\ \fIname
|
||||
Use the specified shared memory object to sink H264 frames. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\-mode\ \fImode
|
||||
Set H264 sink permissions (like 777). Default: 660.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\-rm
|
||||
Remove shared memory on stop. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\-client\-ttl\ \fIsec
|
||||
Client TTL. Default: 10.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\-timeout\ \fIsec
|
||||
Timeout for lock. Default: 1.
|
||||
.TP
|
||||
.BR \-\-h264\-bitrate\ \fIkbps
|
||||
H264 bitrate in Kbps. Default: 5000.
|
||||
.TP
|
||||
.BR \-\-h264\-gop\ \fIN
|
||||
Intarval between keyframes. Default: 30.
|
||||
.TP
|
||||
.BR \-\-h264\-m2m\-device\ \fI/dev/path
|
||||
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
|
||||
|
||||
|
||||
.SS "Process options"
|
||||
.TP
|
||||
.BR \-\-exit\-on\-parent\-death
|
||||
Exit the program if the parent process is dead. Required \fBHAS_PDEATHSIG\fR feature. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-exit\-on\-no\-clients \fIsec
|
||||
Exit the program if there have been no stream or sink clients or any HTTP requests in the last N seconds. Default: 0 (disabled).
|
||||
.TP
|
||||
.BR \-\-process\-name\-prefix\ \fIstr
|
||||
Set process name prefix which will be displayed in the process list like '\fIstr: ustreamer \-\-blah\-blah\-blah'\fR. Required \fBWITH_SETPROCTITLE\fR feature. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-notify\-parent
|
||||
Send SIGUSR2 to the parent process when the stream parameters are changed. Checking changes is performed for the online flag and image resolution. Required \fBWITH_SETPROCTITLE\fR feature.
|
||||
|
||||
.SS "GPIO options"
|
||||
Available only if \fBWITH_GPIO\fR feature enabled.
|
||||
.TP
|
||||
.BR \-\-gpio\-device\ \fI/dev/path
|
||||
Path to GPIO character device. Default: /dev/gpiochip0.
|
||||
.TP
|
||||
.BR \-\-gpio\-consumer\-prefix\ \fIstr
|
||||
Consumer prefix for GPIO outputs. Default: ustreamer.
|
||||
.TP
|
||||
.BR \-\-gpio\-prog\-running\ \fIpin
|
||||
Set 1 on GPIO pin while µStreamer is running. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-gpio\-stream\-online\ \fIpin
|
||||
Set 1 while streaming. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-gpio\-has\-http\-clients\ \fIpin
|
||||
Set 1 while stream has at least one client. Default: disabled.
|
||||
|
||||
.SS "Logging options"
|
||||
.TP
|
||||
.BR \-\-log\-level\ \fIN
|
||||
Verbosity level of messages from 0 (info) to 3 (debug). Enabling debugging messages can slow down the program.
|
||||
Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).
|
||||
Default: 0.
|
||||
.TP
|
||||
.BR \-\-perf
|
||||
Enable performance messages (same as \-\-log\-level=1). Default: disabled.
|
||||
.TP
|
||||
.BR \-\-verbose
|
||||
Enable verbose messages and lower (same as \-\-log\-level=2). Default: disabled.
|
||||
.TP
|
||||
.BR \-\-debug
|
||||
Enable debug messages and lower (same as \-\-log\-level=3). Default: disabled.
|
||||
.TP
|
||||
.BR \-\-force\-log\-colors
|
||||
Force color logging. Default: colored if stderr is a TTY.
|
||||
.TP
|
||||
.BR \-\-no\-log\-colors
|
||||
Disable color logging. Default: ditto.
|
||||
|
||||
.SS "Help options"
|
||||
.TP
|
||||
.BR \-h ", " \-\-help
|
||||
Print this text and exit.
|
||||
.TP
|
||||
.BR \-v ", " \-\-version
|
||||
Print version and exit.
|
||||
.TP
|
||||
.BR \-\-features
|
||||
Print list of supported features.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR ustreamer-dump (1)
|
||||
|
||||
.SH BUGS
|
||||
Please file any bugs and issues at \fIhttps://github.com/pikvm/ustreamer/issues\fR
|
||||
|
||||
.SH AUTHOR
|
||||
Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
.SH HOMEPAGE
|
||||
\fIhttps://pikvm.org/\fR
|
||||
|
||||
.SH COPYRIGHT
|
||||
GNU General Public License v3.0
|
||||
|
||||
48
pkg/arch/PKGBUILD
Normal file
48
pkg/arch/PKGBUILD
Normal file
@@ -0,0 +1,48 @@
|
||||
# Contributor: Maxim Devaev <mdevaev@gmail.com>
|
||||
# Author: Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=5.26
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
license=(GPL)
|
||||
arch=(i686 x86_64 armv6h armv7h aarch64)
|
||||
depends=(libjpeg libevent libbsd libgpiod systemd)
|
||||
makedepends=(gcc make systemd)
|
||||
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
||||
md5sums=(SKIP)
|
||||
|
||||
|
||||
_options="WITH_GPIO=1 WITH_SYSTEMD=1"
|
||||
if [ -e /usr/bin/python3 ]; then
|
||||
_options="$_options WITH_PYTHON=1"
|
||||
depends+=(python)
|
||||
makedepends+=(python-setuptools)
|
||||
fi
|
||||
if [ -e /usr/include/janus/plugins/plugin.h ];then
|
||||
depends+=(janus-gateway alsa-lib opus)
|
||||
makedepends+=(janus-gateway alsa-lib opus)
|
||||
_options="$_options WITH_JANUS=1"
|
||||
fi
|
||||
|
||||
|
||||
# LD does not link mmal with this option
|
||||
# This DOESN'T affect setup.py
|
||||
LDFLAGS="${LDFLAGS//--as-needed/}"
|
||||
export LDFLAGS="${LDFLAGS//,,/,}"
|
||||
|
||||
|
||||
build() {
|
||||
cd "$srcdir"
|
||||
rm -rf $pkgname-build
|
||||
cp -r $pkgname $pkgname-build
|
||||
cd $pkgname-build
|
||||
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$srcdir/$pkgname-build"
|
||||
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" DESTDIR="$pkgdir" PREFIX=/usr install
|
||||
}
|
||||
34
pkg/docker/Dockerfile.alpine
Normal file
34
pkg/docker/Dockerfile.alpine
Normal file
@@ -0,0 +1,34 @@
|
||||
FROM alpine:3.16 as build
|
||||
RUN apk add --no-cache \
|
||||
alpine-sdk \
|
||||
linux-headers \
|
||||
libjpeg-turbo-dev \
|
||||
libevent-dev \
|
||||
libbsd-dev \
|
||||
libgpiod-dev
|
||||
|
||||
WORKDIR /build/ustreamer/
|
||||
COPY . .
|
||||
RUN make -j5 WITH_GPIO=1
|
||||
|
||||
FROM alpine:3.16 as run
|
||||
|
||||
RUN apk add --no-cache \
|
||||
libevent \
|
||||
libjpeg-turbo \
|
||||
libevent \
|
||||
libgpiod \
|
||||
libbsd \
|
||||
v4l-utils
|
||||
|
||||
WORKDIR /ustreamer
|
||||
COPY --from=build /build/ustreamer/src/ustreamer.bin ustreamer
|
||||
|
||||
RUN wget https://raw.githubusercontent.com/pikvm/kvmd/master/configs/kvmd/tc358743-edid.hex -O /edid.hex
|
||||
COPY pkg/docker/entry.sh /
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/entry.sh"]
|
||||
CMD ["--dv-timings", "--format", "UYVY"]
|
||||
|
||||
# vim: syntax=dockerfile
|
||||
39
pkg/docker/Dockerfile.arm.cross
Normal file
39
pkg/docker/Dockerfile.arm.cross
Normal file
@@ -0,0 +1,39 @@
|
||||
FROM balenalib/raspberrypi3-debian:build as build
|
||||
|
||||
RUN ["cross-build-start"]
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
libjpeg8-dev \
|
||||
libbsd-dev \
|
||||
libgpiod-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build/ustreamer/
|
||||
COPY . .
|
||||
RUN make -j5 WITH_GPIO=1
|
||||
RUN ["cross-build-end"]
|
||||
|
||||
FROM balenalib/raspberrypi3-debian:run as RUN
|
||||
|
||||
RUN ["cross-build-start"]
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
libevent-2.1 \
|
||||
libevent-pthreads-2.1-6 \
|
||||
libjpeg8 \
|
||||
libbsd0 \
|
||||
libgpiod2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN ["cross-build-end"]
|
||||
|
||||
WORKDIR /ustreamer
|
||||
COPY --from=build /build/ustreamer/ustreamer .
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["./ustreamer", "--host=::"]
|
||||
|
||||
# vim: syntax=dockerfile
|
||||
32
pkg/docker/Dockerfile.arm.native
Normal file
32
pkg/docker/Dockerfile.arm.native
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM balenalib/raspberrypi3-debian:build as build
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
libjpeg8-dev \
|
||||
libbsd-dev \
|
||||
libgpiod-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build/ustreamer/
|
||||
COPY . .
|
||||
RUN make -j5 WITH_GPIO=1
|
||||
|
||||
FROM balenalib/raspberrypi3-debian:run as RUN
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
libevent-2.1 \
|
||||
libevent-pthreads-2.1-6 \
|
||||
libjpeg8 \
|
||||
libbsd0 \
|
||||
libgpiod2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /ustreamer
|
||||
COPY --from=build /build/ustreamer/ustreamer .
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["./ustreamer", "--host=::"]
|
||||
|
||||
# vim: syntax=dockerfile
|
||||
38
pkg/docker/Dockerfile.x64.native
Normal file
38
pkg/docker/Dockerfile.x64.native
Normal file
@@ -0,0 +1,38 @@
|
||||
FROM debian:buster-slim as build
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
ca-certificates \
|
||||
make \
|
||||
gcc \
|
||||
git \
|
||||
libevent-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libbsd-dev \
|
||||
libgpiod-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build/ustreamer/
|
||||
COPY . .
|
||||
RUN make -j5 WITH_GPIO=1
|
||||
|
||||
FROM debian:buster-slim as run
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
ca-certificates \
|
||||
libevent-2.1 \
|
||||
libevent-pthreads-2.1-6 \
|
||||
libjpeg62-turbo \
|
||||
libbsd0 \
|
||||
libgpiod2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /ustreamer
|
||||
COPY --from=build /build/ustreamer/ustreamer .
|
||||
|
||||
#ENV LD_LIBRARY_PATH=/opt/vc/lib
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["./ustreamer", "--host=0.0.0.0"]
|
||||
|
||||
# vim: syntax=dockerfile
|
||||
14
pkg/docker/entry.sh
Executable file
14
pkg/docker/entry.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
[ -n "$EDID" ] && {
|
||||
[ -n "$EDID_HEX" ] && echo "$EDID_HEX" > /edid.hex
|
||||
while true; do
|
||||
v4l2-ctl --device=/dev/video0 --set-edid=file=/edid.hex --fix-edid-checksums --info-edid && break
|
||||
echo 'Failed to set EDID. Reetrying...'
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
./ustreamer --host=0.0.0.0 $@
|
||||
30
pkg/gentoo/ustreamer-9999.ebuild
Normal file
30
pkg/gentoo/ustreamer-9999.ebuild
Normal file
@@ -0,0 +1,30 @@
|
||||
# Copyright 2019 Gentoo Authors
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
|
||||
EAPI=7
|
||||
|
||||
inherit git-r3
|
||||
|
||||
DESCRIPTION="uStreamer - Lightweight and fast MJPEG-HTTP streamer"
|
||||
HOMEPAGE="https://github.com/pikvm/ustreamer"
|
||||
EGIT_REPO_URI="https://github.com/pikvm/ustreamer.git"
|
||||
|
||||
LICENSE="GPL-3"
|
||||
SLOT="0"
|
||||
KEYWORDS="~amd64"
|
||||
IUSE=""
|
||||
|
||||
DEPEND="
|
||||
>=dev-libs/libevent-2.1.8
|
||||
>=media-libs/libjpeg-turbo-1.5.3
|
||||
>=dev-libs/libbsd-0.9.1
|
||||
"
|
||||
RDEPEND="${DEPEND}"
|
||||
BDEPEND=""
|
||||
|
||||
src_install() {
|
||||
dobin ustreamer
|
||||
dobin ustreamer-dump
|
||||
doman man/ustreamer.1
|
||||
doman man/ustreamer-dump.1
|
||||
}
|
||||
46
pkg/openwrt/Makefile
Normal file
46
pkg/openwrt/Makefile
Normal file
@@ -0,0 +1,46 @@
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=5.26
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/pikvm/ustreamer.git
|
||||
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/ustreamer
|
||||
SECTION:=multimedia
|
||||
CATEGORY:=Multimedia
|
||||
TITLE:=uStreamer
|
||||
DEPENDS:=+libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
|
||||
URL:=https://github.com/pikvm/ustreamer
|
||||
endef
|
||||
|
||||
define Package/ustreamer/description
|
||||
µStreamer - Lightweight and fast MJPEG-HTTP streamer
|
||||
endef
|
||||
|
||||
define Package/ustreamer/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer $(1)/usr/bin/
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer-dump $(1)/usr/bin/
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(CP) ./files/ustreamer.config $(1)/etc/config/ustreamer
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/ustreamer.init $(1)/etc/init.d/ustreamer
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,ustreamer))
|
||||
19
pkg/openwrt/files/ustreamer.config
Normal file
19
pkg/openwrt/files/ustreamer.config
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
config ustreamer
|
||||
option enabled '0'
|
||||
|
||||
option device '/dev/video0'
|
||||
option device_timeout '5'
|
||||
option input '0'
|
||||
|
||||
option resolution '640x480'
|
||||
option format 'YUYV'
|
||||
option quality '80'
|
||||
option desired_fps '0'
|
||||
option encoder 'CPU'
|
||||
|
||||
option host '::'
|
||||
option port '8080'
|
||||
option static ''
|
||||
option user ''
|
||||
option password ''
|
||||
54
pkg/openwrt/files/ustreamer.init
Normal file
54
pkg/openwrt/files/ustreamer.init
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2009-2019 OpenWrt.org
|
||||
|
||||
START=90
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/bin/ustreamer
|
||||
|
||||
getcfg() {
|
||||
config_get value ustreamer $1 $2
|
||||
return "$value"
|
||||
}
|
||||
|
||||
start_instance() {
|
||||
config_get_bool enabled ustreamer enabled 0
|
||||
[ "$enabled" -eq 0 ] && return
|
||||
|
||||
local options=""
|
||||
|
||||
options="$options --device='`getcfg device /dev/video0`'"
|
||||
options="$options --device-timeout='`getcfg device_timeout 5`'"
|
||||
options="$options --input='`getcfg input 0`'"
|
||||
|
||||
options="$options --resolution='`getcfg resolution 640x480`'"
|
||||
options="$options --format='`getcfg format YUYV`'"
|
||||
options="$options --quality='`getcfg quality 80`'"
|
||||
options="$options --desired-fps='`getcfg desired_fps 0`'"
|
||||
options="$options --encoder='`getcfg encoder CPU`'"
|
||||
|
||||
options="$options --host='`getcfg host '::'`'"
|
||||
local port=`getcfg port 8080`
|
||||
options="$options --port='$port'"
|
||||
options="$options --static='`getcfg static ''`'"
|
||||
options="$options --user='`getcfg user ''`'"
|
||||
options="$options --passwd='`getcfg password ''`'"
|
||||
|
||||
config-get-bool opt_slowdown ustreamer slowdown 1
|
||||
[ "$slowdown" -eq 1 ] && options="$options --slowdown"
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param command "$PROG" $options
|
||||
procd_add_mdns http tcp "$port" daemon=ustreamer
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load ustreamer
|
||||
config_foreach start_instance ustreamer
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger ustreamer
|
||||
}
|
||||
20
python/Makefile
Normal file
20
python/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
-include ../config.mk
|
||||
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
PY ?= python3
|
||||
|
||||
|
||||
# =====
|
||||
all:
|
||||
$(info == PY_BUILD ustreamer-*.so)
|
||||
@ $(PY) setup.py build
|
||||
|
||||
|
||||
install:
|
||||
$(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
35
python/setup.py
Normal file
35
python/setup.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import os
|
||||
|
||||
from setuptools import Extension
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
# =====
|
||||
def _find_sources(suffix: str) -> list[str]:
|
||||
sources: list[str] = []
|
||||
for (root_path, _, names) in os.walk("src"):
|
||||
for name in names:
|
||||
if name.endswith(suffix):
|
||||
sources.append(os.path.join(root_path, name))
|
||||
return sources
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="5.26",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
url="https://github.com/pikvm/ustreamer",
|
||||
ext_modules=[
|
||||
Extension(
|
||||
"ustreamer",
|
||||
libraries=["rt", "m", "pthread"],
|
||||
extra_compile_args=["-std=c17", "-D_GNU_SOURCE"],
|
||||
undef_macros=["NDEBUG"],
|
||||
sources=_find_sources(".c"),
|
||||
depends=_find_sources(".h"),
|
||||
),
|
||||
],
|
||||
)
|
||||
1
python/src/uslibs/frame.c
Symbolic link
1
python/src/uslibs/frame.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.c
|
||||
1
python/src/uslibs/frame.h
Symbolic link
1
python/src/uslibs/frame.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.h
|
||||
1
python/src/uslibs/memsinksh.h
Symbolic link
1
python/src/uslibs/memsinksh.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsinksh.h
|
||||
1
python/src/uslibs/tools.h
Symbolic link
1
python/src/uslibs/tools.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/tools.h
|
||||
324
python/src/ustreamer.c
Normal file
324
python/src/ustreamer.c
Normal file
@@ -0,0 +1,324 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
char *obj;
|
||||
double lock_timeout;
|
||||
double wait_timeout;
|
||||
double drop_same_frames;
|
||||
|
||||
int fd;
|
||||
us_memsink_shared_s *mem;
|
||||
|
||||
uint64_t frame_id;
|
||||
long double frame_ts;
|
||||
us_frame_s *frame;
|
||||
} _MemsinkObject;
|
||||
|
||||
|
||||
#define _MEM(x_next) self->mem->x_next
|
||||
#define _FRAME(x_next) self->frame->x_next
|
||||
|
||||
|
||||
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
|
||||
if (self->mem != NULL) {
|
||||
us_memsink_shared_unmap(self->mem);
|
||||
self->mem = NULL;
|
||||
}
|
||||
if (self->fd >= 0) {
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
}
|
||||
if (self->frame != NULL) {
|
||||
us_frame_destroy(self->frame);
|
||||
self->frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
self->lock_timeout = 1;
|
||||
self->wait_timeout = 1;
|
||||
|
||||
static char *kws[] = {"obj", "lock_timeout", "wait_timeout", "drop_same_frames", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, kwargs, "s|ddd", kws,
|
||||
&self->obj, &self->lock_timeout, &self->wait_timeout, &self->drop_same_frames)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define SET_DOUBLE(_field, _cond) { \
|
||||
if (!(self->_field _cond)) { \
|
||||
PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \
|
||||
return -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
SET_DOUBLE(lock_timeout, > 0);
|
||||
SET_DOUBLE(wait_timeout, > 0);
|
||||
SET_DOUBLE(drop_same_frames, >= 0);
|
||||
|
||||
# undef SET_DOUBLE
|
||||
|
||||
self->frame = us_frame_init();
|
||||
|
||||
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
|
||||
char repr[1024];
|
||||
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
|
||||
return Py_BuildValue("s", repr);
|
||||
}
|
||||
|
||||
static void _MemsinkObject_dealloc(_MemsinkObject *self) {
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
PyObject_Del(self);
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_close(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_enter(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
Py_INCREF(self);
|
||||
return (PyObject *)self;
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
return PyObject_CallMethod((PyObject *)self, "close", "");
|
||||
}
|
||||
|
||||
static int _wait_frame(_MemsinkObject *self) {
|
||||
const long double deadline_ts = us_get_now_monotonic() + self->wait_timeout;
|
||||
|
||||
# define RETURN_OS_ERROR { \
|
||||
Py_BLOCK_THREADS \
|
||||
PyErr_SetFromErrno(PyExc_OSError); \
|
||||
return -1; \
|
||||
}
|
||||
|
||||
long double now;
|
||||
do {
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
|
||||
const int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
||||
now = us_get_now_monotonic();
|
||||
|
||||
if (retval < 0 && errno != EWOULDBLOCK) {
|
||||
RETURN_OS_ERROR;
|
||||
|
||||
} else if (retval == 0) {
|
||||
if (_MEM(magic) == US_MEMSINK_MAGIC && _MEM(version) == US_MEMSINK_VERSION && _MEM(id) != self->frame_id) {
|
||||
if (self->drop_same_frames > 0) {
|
||||
if (
|
||||
US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
||||
&& (self->frame_ts + self->drop_same_frames > now)
|
||||
&& !memcmp(_FRAME(data), _MEM(data), _MEM(used))
|
||||
) {
|
||||
self->frame_id = _MEM(id);
|
||||
goto drop;
|
||||
}
|
||||
}
|
||||
|
||||
Py_BLOCK_THREADS
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
RETURN_OS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
drop:
|
||||
|
||||
if (usleep(1000) < 0) {
|
||||
RETURN_OS_ERROR;
|
||||
}
|
||||
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (PyErr_CheckSignals() < 0) {
|
||||
return -1;
|
||||
}
|
||||
} while (now < deadline_ts);
|
||||
|
||||
# undef RETURN_OS_ERROR
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
if (self->mem == NULL || self->fd <= 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Closed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool key_required = false;
|
||||
static char *kws[] = {"key_required", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|p", kws, &key_required)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (_wait_frame(self)) {
|
||||
case 0: break;
|
||||
case -2: Py_RETURN_NONE;
|
||||
default: return NULL;
|
||||
}
|
||||
|
||||
us_frame_set_data(self->frame, _MEM(data), _MEM(used));
|
||||
US_FRAME_COPY_META(self->mem, self->frame);
|
||||
self->frame_id = _MEM(id);
|
||||
self->frame_ts = us_get_now_monotonic();
|
||||
_MEM(last_client_ts) = self->frame_ts;
|
||||
if (key_required) {
|
||||
_MEM(key_requested) = true;
|
||||
}
|
||||
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
|
||||
PyObject *dict_frame = PyDict_New();
|
||||
if (dict_frame == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
# define SET_VALUE(_key, _maker) { \
|
||||
PyObject *_tmp = _maker; \
|
||||
if (_tmp == NULL) { \
|
||||
return NULL; \
|
||||
} \
|
||||
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
|
||||
Py_DECREF(_tmp); \
|
||||
return NULL; \
|
||||
} \
|
||||
Py_DECREF(_tmp); \
|
||||
}
|
||||
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(_FRAME(_key)))
|
||||
|
||||
SET_NUMBER(width, Long, Long);
|
||||
SET_NUMBER(height, Long, Long);
|
||||
SET_NUMBER(format, Long, Long);
|
||||
SET_NUMBER(stride, Long, Long);
|
||||
SET_NUMBER(online, Long, Bool);
|
||||
SET_NUMBER(key, Long, Bool);
|
||||
SET_NUMBER(grab_ts, Double, Float);
|
||||
SET_NUMBER(encode_begin_ts, Double, Float);
|
||||
SET_NUMBER(encode_end_ts, Double, Float);
|
||||
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)_FRAME(data), _FRAME(used)));
|
||||
|
||||
# undef SET_NUMBER
|
||||
# undef SET_VALUE
|
||||
|
||||
return dict_frame;
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_is_opened(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
|
||||
}
|
||||
|
||||
#define FIELD_GETTER(_field, _from, _to) \
|
||||
static PyObject *_MemsinkObject_getter_##_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
||||
return Py##_to##_From##_from(self->_field); \
|
||||
}
|
||||
|
||||
FIELD_GETTER(obj, String, Unicode)
|
||||
FIELD_GETTER(lock_timeout, Double, Float)
|
||||
FIELD_GETTER(wait_timeout, Double, Float)
|
||||
FIELD_GETTER(drop_same_frames, Double, Float)
|
||||
|
||||
#undef FIELD_GETTER
|
||||
|
||||
static PyMethodDef _MemsinkObject_methods[] = {
|
||||
# define ADD_METHOD(_name, _method, _flags) \
|
||||
{.ml_name = _name, .ml_meth = (PyCFunction)_MemsinkObject_##_method, .ml_flags = (_flags)}
|
||||
ADD_METHOD("close", close, METH_NOARGS),
|
||||
ADD_METHOD("__enter__", enter, METH_NOARGS),
|
||||
ADD_METHOD("__exit__", exit, METH_VARARGS),
|
||||
ADD_METHOD("wait_frame", wait_frame, METH_VARARGS | METH_KEYWORDS),
|
||||
ADD_METHOD("is_opened", is_opened, METH_NOARGS),
|
||||
{},
|
||||
# undef ADD_METHOD
|
||||
};
|
||||
|
||||
static PyGetSetDef _MemsinkObject_getsets[] = {
|
||||
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)_MemsinkObject_getter_##_field}
|
||||
ADD_GETTER(obj),
|
||||
ADD_GETTER(lock_timeout),
|
||||
ADD_GETTER(wait_timeout),
|
||||
ADD_GETTER(drop_same_frames),
|
||||
{},
|
||||
# undef ADD_GETTER
|
||||
};
|
||||
|
||||
static PyTypeObject _MemsinkType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "ustreamer.Memsink",
|
||||
.tp_basicsize = sizeof(_MemsinkObject),
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_new = PyType_GenericNew,
|
||||
.tp_init = (initproc)_MemsinkObject_init,
|
||||
.tp_dealloc = (destructor)_MemsinkObject_dealloc,
|
||||
.tp_repr = (reprfunc)_MemsinkObject_repr,
|
||||
.tp_methods = _MemsinkObject_methods,
|
||||
.tp_getset = _MemsinkObject_getsets,
|
||||
};
|
||||
|
||||
static PyModuleDef _Module = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "ustreamer",
|
||||
.m_size = -1,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
|
||||
PyObject *module = PyModule_Create(&_Module);
|
||||
if (module == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyType_Ready(&_MemsinkType) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_INCREF(&_MemsinkType);
|
||||
|
||||
if (PyModule_AddObject(module, "Memsink", (PyObject *)&_MemsinkType) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
108
src/Makefile
Normal file
108
src/Makefile
Normal file
@@ -0,0 +1,108 @@
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -O3
|
||||
LDFLAGS ?=
|
||||
|
||||
|
||||
# =====
|
||||
_USTR = ustreamer.bin
|
||||
_DUMP = ustreamer-dump.bin
|
||||
|
||||
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
||||
_LDFLAGS = $(LDFLAGS)
|
||||
|
||||
_COMMON_LIBS = -lm -ljpeg -pthread -lrt
|
||||
|
||||
_USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads
|
||||
_USTR_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
ustreamer/*.c \
|
||||
ustreamer/http/*.c \
|
||||
ustreamer/data/*.c \
|
||||
ustreamer/encoders/cpu/*.c \
|
||||
ustreamer/encoders/hw/*.c \
|
||||
ustreamer/h264/*.c \
|
||||
)
|
||||
|
||||
_DUMP_LIBS = $(_COMMON_LIBS)
|
||||
_DUMP_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
dump/*.c \
|
||||
)
|
||||
|
||||
_BUILD = build
|
||||
|
||||
|
||||
define optbool
|
||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||
endef
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||
_USTR_LIBS += -lgpiod
|
||||
override _CFLAGS += -DWITH_GPIO
|
||||
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_SYSTEMD)),)
|
||||
_USTR_LIBS += -lsystemd
|
||||
override _CFLAGS += -DWITH_SYSTEMD
|
||||
_USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
|
||||
endif
|
||||
|
||||
|
||||
WITH_PTHREAD_NP ?= 1
|
||||
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
|
||||
override _CFLAGS += -DWITH_PTHREAD_NP
|
||||
endif
|
||||
|
||||
|
||||
WITH_SETPROCTITLE ?= 1
|
||||
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
|
||||
ifeq ($(shell uname -s | tr A-Z a-z),linux)
|
||||
_USTR_LIBS += -lbsd
|
||||
endif
|
||||
override _CFLAGS += -DWITH_SETPROCTITLE
|
||||
endif
|
||||
|
||||
|
||||
# =====
|
||||
all: $(_USTR) $(_DUMP)
|
||||
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||
install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
||||
install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
||||
|
||||
|
||||
install-strip: install
|
||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
||||
|
||||
|
||||
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
|
||||
|
||||
|
||||
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
||||
|
||||
|
||||
$(_BUILD)/%.o: %.c
|
||||
$(info -- CC $<)
|
||||
@ mkdir -p $(dir $@) || true
|
||||
@ $(CC) $< -o $@ $(_CFLAGS)
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf $(_USTR) $(_DUMP) $(_BUILD)
|
||||
|
||||
|
||||
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
-include $(_OBJS:%.o=%.d)
|
||||
688
src/data/blank.h
688
src/data/blank.h
@@ -1,688 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
const unsigned BLANK_JPG_WIDTH = 640;
|
||||
const unsigned BLANK_JPG_HEIGHT = 480;
|
||||
|
||||
const unsigned long BLANK_JPG_SIZE = 13845;
|
||||
|
||||
const unsigned char BLANK_JPG_DATA[] = {
|
||||
0xff, 0xd8, 0xff, 0xe1, 0x9, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b,
|
||||
0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35,
|
||||
0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39,
|
||||
0x64, 0x22, 0x3f, 0x3e, 0x20, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73,
|
||||
0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78,
|
||||
0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72,
|
||||
0x65, 0x20, 0x35, 0x2e, 0x36, 0x2d, 0x63, 0x31, 0x33, 0x38, 0x20, 0x37, 0x39, 0x2e, 0x31, 0x35, 0x39, 0x38, 0x32, 0x34, 0x2c,
|
||||
0x20, 0x32, 0x30, 0x31, 0x36, 0x2f, 0x30, 0x39, 0x2f, 0x31, 0x34, 0x2d, 0x30, 0x31, 0x3a, 0x30, 0x39, 0x3a, 0x30, 0x31, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d,
|
||||
0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77,
|
||||
0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d,
|
||||
0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22,
|
||||
0x2f, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x20, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70,
|
||||
0x6d, 0x65, 0x74, 0x61, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x3f, 0x78, 0x70,
|
||||
0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x77, 0x22, 0x3f, 0x3e, 0xff, 0xed, 0x0, 0x2c, 0x50, 0x68,
|
||||
0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x33, 0x2e, 0x30, 0x0, 0x38, 0x42, 0x49, 0x4d, 0x4, 0x25, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x10, 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x0, 0xb2, 0x4, 0xe9, 0x80, 0x9, 0x98, 0xec, 0xf8, 0x42, 0x7e, 0xff, 0xdb,
|
||||
0x0, 0x84, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
|
||||
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
|
||||
0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
|
||||
0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x3,
|
||||
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
|
||||
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
|
||||
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0xff, 0xdd, 0x0, 0x4, 0x0, 0x50, 0xff, 0xee, 0x0, 0xe, 0x41, 0x64, 0x6f, 0x62, 0x65,
|
||||
0x0, 0x64, 0xc0, 0x0, 0x0, 0x0, 0x1, 0xff, 0xc0, 0x0, 0x11, 0x8, 0x1, 0xe0, 0x2, 0x80, 0x3, 0x0, 0x11, 0x0, 0x1,
|
||||
0x11, 0x1, 0x2, 0x11, 0x1, 0xff, 0xc4, 0x0, 0x7d, 0x0, 0x1, 0x0, 0x2, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xb, 0x7, 0x8, 0x9, 0x5, 0x6, 0x2, 0x3, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1, 0x0, 0x1, 0x4, 0x2, 0x1, 0x3,
|
||||
0x2, 0x3, 0x4, 0x8, 0x4, 0x6, 0x3, 0x0, 0x0, 0x0, 0x3, 0x2, 0x4, 0x5, 0x6, 0x1, 0x7, 0x8, 0x9, 0x11, 0x12,
|
||||
0xa, 0x13, 0x14, 0x21, 0x22, 0x15, 0x37, 0x77, 0xb6, 0x16, 0x23, 0x31, 0x35, 0x39, 0x41, 0x75, 0xb4, 0x17, 0x38, 0xb5, 0xb7,
|
||||
0x18, 0x1a, 0x24, 0x32, 0x51, 0x78, 0x56, 0x97, 0xd4, 0x11, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xda, 0x0, 0xc, 0x3, 0x0, 0x0, 0x1, 0x11, 0x2, 0x11, 0x0, 0x3f, 0x0, 0xaf,
|
||||
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0,
|
||||
0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff,
|
||||
0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
|
||||
0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x7, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x7, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd4, 0xaf,
|
||||
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd5,
|
||||
0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff,
|
||||
0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
|
||||
0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x6, 0x4c, 0xe9, 0x5b, 0xb, 0x1c, 0xaf, 0x71, 0xf5, 0x2e, 0x2f, 0x29, 0x65, 0x69, 0x92, 0xc6, 0x64,
|
||||
0xbb, 0x33, 0x43, 0xb0, 0xc8, 0xe3, 0xaf, 0xed, 0xa1, 0xbc, 0xb1, 0xbf, 0xb1, 0xbc, 0xda, 0x71, 0x56, 0xf7, 0x76, 0x57, 0xb6,
|
||||
0x97, 0x14, 0x49, 0x6f, 0x75, 0x69, 0x75, 0x6f, 0x25, 0x54, 0x49, 0x1d, 0x74, 0xd5, 0x45, 0x74, 0x55, 0xcd, 0x35, 0x71, 0xcf,
|
||||
0x1c, 0xf3, 0xc0, 0x24, 0x45, 0xeb, 0x5f, 0xe9, 0x19, 0x61, 0xd4, 0x3f, 0xb6, 0x7c, 0xbb, 0xf1, 0x67, 0x4e, 0xb6, 0xc6, 0x75,
|
||||
0x4c, 0x95, 0x47, 0x71, 0xdc, 0x5d, 0x53, 0xaa, 0xe3, 0x20, 0xb3, 0xc6, 0x75, 0x94, 0xf5, 0x71, 0x15, 0xbf, 0xf4, 0xe7, 0x4d,
|
||||
0xc2, 0x63, 0xa0, 0x8a, 0x1b, 0x1d, 0x2, 0xf6, 0x5f, 0x6e, 0x72, 0x56, 0x50, 0x51, 0xf6, 0xf0, 0xd3, 0xd7, 0xf7, 0xe2, 0xa6,
|
||||
0x9b, 0xa, 0xeb, 0xa2, 0xc4, 0x23, 0x22, 0xd, 0xd8, 0xf4, 0xe3, 0xeb, 0xed, 0x2f, 0xb5, 0xbc, 0xe3, 0xf1, 0xa7, 0xae, 0xbb,
|
||||
0x17, 0x5c, 0xc7, 0x6d, 0xba, 0x46, 0xdd, 0xd9, 0x36, 0x38, 0x7d, 0x97, 0x5b, 0xcb, 0x47, 0x5c, 0xb8, 0xdc, 0xc6, 0x36, 0x6b,
|
||||
0xc, 0x85, 0x72, 0x59, 0xdd, 0xd1, 0x1c, 0x91, 0x49, 0xcc, 0x55, 0x57, 0x1d, 0x3c, 0xfe, 0x9a, 0xa9, 0xe7, 0x8e, 0x78, 0xe3,
|
||||
0x9e, 0x39, 0x6, 0xf5, 0xfa, 0xf4, 0x78, 0xe9, 0xd2, 0x3e, 0x34, 0xf9, 0x4d, 0xd5, 0x7a, 0x6f, 0x44, 0x75, 0xbe, 0xbb, 0xd6,
|
||||
0x3a, 0xbe, 0x6b, 0xa0, 0x30, 0xbb, 0x3e, 0x57, 0xb, 0xad, 0x45, 0x73, 0xd, 0x95, 0xee, 0x7e, 0xe3, 0xb1, 0x7b, 0x23, 0x15,
|
||||
0x36, 0x52, 0x6a, 0x6e, 0xae, 0x6e, 0x6b, 0xe6, 0xea, 0x4c, 0x76, 0x26, 0xda, 0x2e, 0x79, 0xe2, 0xae, 0x38, 0xf8, 0x43, 0x4f,
|
||||
0xe5, 0xff, 0x0, 0xc8, 0x6b, 0xe7, 0xa6, 0xff, 0x0, 0xa5, 0xd7, 0x6f, 0xfa, 0x83, 0xed, 0x57, 0xd7, 0xf8, 0xeb, 0xfe, 0x3a,
|
||||
0xe7, 0xa3, 0xb5, 0x1c, 0x8c, 0x56, 0x3b, 0xcf, 0x6b, 0x64, 0x71, 0xf5, 0xe4, 0x3e, 0x59, 0xa, 0xa0, 0xa6, 0xeb, 0x8d, 0x57,
|
||||
0x49, 0xc4, 0x73, 0x35, 0xa5, 0x1b, 0x1e, 0xd3, 0x25, 0xbc, 0x91, 0xd7, 0x3f, 0xca, 0x68, 0x6d, 0x31, 0xd6, 0xf2, 0xd3, 0x2c,
|
||||
0xf5, 0xf3, 0x5d, 0x76, 0xf6, 0xf7, 0x1, 0xda, 0xee, 0xc7, 0x83, 0xd0, 0x8f, 0xd3, 0x7, 0x27, 0xff, 0x0, 0xe, 0x76, 0xe,
|
||||
0xa9, 0xaf, 0xca, 0xce, 0xf4, 0xd7, 0x61, 0xae, 0xd3, 0x69, 0xc6, 0x65, 0x31, 0x98, 0xce, 0xee, 0xcb, 0x58, 0xe5, 0xe1, 0xb3,
|
||||
0xbe, 0xb0, 0xa6, 0x1d, 0xea, 0xdf, 0x71, 0xc9, 0xe0, 0xba, 0x47, 0x57, 0xbf, 0xaa, 0x6b, 0xa9, 0x7f, 0x11, 0x8f, 0xb1, 0xb2,
|
||||
0xfd, 0xa3, 0x67, 0x55, 0x31, 0xcd, 0x2d, 0x9f, 0xdc, 0x8e, 0xd6, 0xae, 0x43, 0x15, 0xea, 0xfe, 0xa3, 0x9e, 0x87, 0x9d, 0xd5,
|
||||
0x2d, 0x3a, 0x2f, 0x73, 0xfa, 0x79, 0xeb, 0x1d, 0x25, 0x84, 0xca, 0xdf, 0x5b, 0xc1, 0x46, 0xe7, 0xab, 0x74, 0xdf, 0x5c, 0x5b,
|
||||
0x59, 0x63, 0x20, 0xb8, 0xb7, 0xbb, 0xb4, 0xba, 0xbe, 0xcd, 0xec, 0x5d, 0x39, 0xce, 0xad, 0xd9, 0xd8, 0xcb, 0x7b, 0x5a, 0x2e,
|
||||
0x7e, 0x54, 0xd1, 0x8c, 0xb4, 0xc8, 0xd7, 0x55, 0x5e, 0xd2, 0xf1, 0x4d, 0x32, 0x45, 0x1f, 0x20, 0xf1, 0xfc, 0xca, 0xf4, 0x2e,
|
||||
0xeb, 0xad, 0xbf, 0xab, 0x6b, 0xf2, 0x77, 0xd3, 0x43, 0x7a, 0xa7, 0xb2, 0xb4, 0x6b, 0xdc, 0x3d, 0xce, 0xd3, 0x1f, 0x52, 0xf1,
|
||||
0xb2, 0x45, 0xba, 0xc1, 0x9c, 0xc2, 0x5a, 0xd1, 0x27, 0x37, 0x55, 0x75, 0x6, 0xeb, 0x45, 0x73, 0x64, 0x32, 0xb9, 0x1b, 0xf,
|
||||
0xc3, 0x57, 0x4d, 0x78, 0x3c, 0xbc, 0xb7, 0x59, 0x9, 0xae, 0x28, 0x96, 0x28, 0xee, 0xf9, 0xb9, 0xa6, 0x3b, 0x2a, 0xc2, 0x30,
|
||||
0x12, 0x47, 0x24, 0x52, 0x57, 0x14, 0xb4, 0x57, 0x14, 0xb1, 0x57, 0x54, 0x72, 0x47, 0x25, 0x3c, 0xd1, 0x24, 0x72, 0x51, 0xcf,
|
||||
0x34, 0xd7, 0x45, 0x74, 0x55, 0xc7, 0x15, 0x51, 0x5d, 0x15, 0x71, 0xcf, 0x1c, 0xf1, 0xcf, 0x1e, 0xfc, 0x72, 0xf, 0xc0, 0x3a,
|
||||
0x19, 0xe9, 0xa3, 0xe0, 0x76, 0x6b, 0xd4, 0x3, 0xc8, 0x9b, 0x6e, 0xae, 0xe7, 0x33, 0x79, 0xaa, 0x75, 0xde, 0xab, 0x85, 0x97,
|
||||
0x75, 0xed, 0x5d, 0xbb, 0x1f, 0x14, 0x32, 0x64, 0xf1, 0x7a, 0xad, 0xb5, 0xf5, 0x9e, 0x3a, 0x1c, 0x56, 0xbd, 0xc5, 0xe4, 0x17,
|
||||
0x18, 0xfa, 0xb6, 0x8d, 0x8f, 0x25, 0x7d, 0x1d, 0xbd, 0xa7, 0xdf, 0xa2, 0xb8, 0xe0, 0x8b, 0x89, 0xee, 0xaa, 0x8e, 0x6a, 0x6d,
|
||||
0xea, 0x86, 0xb0, 0x90, 0x37, 0x92, 0x7d, 0xb9, 0xe8, 0xb9, 0xe9, 0x95, 0x9b, 0x8f, 0xc7, 0x5b, 0xf, 0xc, 0x74, 0x8f, 0x21,
|
||||
0x3b, 0x3f, 0x9, 0x69, 0x8c, 0x9f, 0x70, 0xb4, 0xcb, 0xe9, 0x1a, 0x5f, 0x67, 0xdf, 0xeb, 0x52, 0x5e, 0x5b, 0x71, 0x91, 0x86,
|
||||
0x9d, 0xc3, 0xb1, 0x7b, 0x82, 0xbc, 0xfd, 0xf5, 0xae, 0xc9, 0x95, 0xb5, 0xc8, 0x53, 0x77, 0xc6, 0x3b, 0x19, 0xc, 0x90, 0x43,
|
||||
0xc, 0x94, 0x53, 0x5d, 0x36, 0x91, 0xd3, 0x6f, 0x10, 0x3f, 0x5b, 0xf, 0x40, 0xfa, 0x35, 0xfa, 0x99, 0x78, 0xcf, 0xda, 0x3d,
|
||||
0xd7, 0xd2, 0xb0, 0x6a, 0x9e, 0x21, 0x6d, 0x5d, 0x51, 0x87, 0x97, 0x2b, 0xb9, 0x6c, 0x18, 0xdd, 0x73, 0xf, 0xd6, 0x37, 0xbd,
|
||||
0x51, 0x27, 0x18, 0xda, 0xb8, 0xc4, 0xcb, 0xd8, 0xfd, 0x51, 0xaf, 0xe4, 0xb9, 0xeb, 0xfd, 0x9b, 0x4f, 0xce, 0xf3, 0x8e, 0xa7,
|
||||
0xed, 0x5c, 0x62, 0x6a, 0xaa, 0x6b, 0xdb, 0x88, 0xe5, 0x86, 0xd6, 0xfa, 0x3b, 0xde, 0x6e, 0xa1, 0xe4, 0x38, 0x81, 0xe8, 0xdf,
|
||||
0xd6, 0xdd, 0x77, 0xd8, 0xfe, 0xa4, 0x7d, 0x35, 0xd7, 0xdd, 0x89, 0xaa, 0xe8, 0xfd, 0xb3, 0xa1, 0xde, 0xdb, 0x77, 0xc, 0x77,
|
||||
0xd8, 0x3d, 0xc3, 0x56, 0xb1, 0xda, 0x74, 0xdd, 0x8e, 0x8c, 0x3f, 0x53, 0xef, 0x97, 0xd8, 0x9c, 0x8c, 0xda, 0xde, 0xe1, 0x88,
|
||||
0xae, 0x1b, 0x88, 0x63, 0xbe, 0xb2, 0x86, 0xf2, 0xd7, 0x8b, 0xbb, 0x3a, 0x27, 0x86, 0x5a, 0x28, 0xaf, 0x9a, 0x23, 0x96, 0x9f,
|
||||
0x6a, 0x43, 0xeb, 0xfd, 0x72, 0xfa, 0xcb, 0xad, 0xfa, 0x93, 0xcf, 0x7d, 0x8f, 0x4e, 0xea, 0x9e, 0xbe, 0xd2, 0x3a, 0xcb, 0x51,
|
||||
0x83, 0xac, 0xfa, 0xda, 0xfe, 0xd, 0x5b, 0xaf, 0x75, 0x3c, 0xe, 0x97, 0xae, 0x43, 0x7d, 0x7d, 0x8b, 0xba, 0x92, 0xfa, 0xf6,
|
||||
0x2c, 0x1e, 0xb9, 0x61, 0x8d, 0xc6, 0x47, 0x77, 0x79, 0x25, 0x3c, 0x55, 0x2c, 0x9c, 0x45, 0xc5, 0x72, 0x73, 0xc7, 0xbd, 0x5c,
|
||||
0xf3, 0xc8, 0x35, 0xa7, 0xc0, 0x5f, 0x4f, 0xee, 0xe3, 0xf5, 0x1, 0xed, 0x59, 0xf4, 0x3e, 0xba, 0xae, 0xdb, 0x58, 0xd3, 0xf5,
|
||||
0x98, 0x6d, 0x32, 0x5d, 0x99, 0xda, 0x59, 0xab, 0x39, 0xef, 0x35, 0xed, 0x17, 0x11, 0x7b, 0x24, 0xd4, 0x59, 0x51, 0xc5, 0x94,
|
||||
0x13, 0x5a, 0xcd, 0x9f, 0xd9, 0xf3, 0x55, 0x5b, 0x4b, 0x46, 0x3b, 0x17, 0x14, 0xd0, 0xd7, 0x73, 0x54, 0x52, 0x57, 0x24, 0xb0,
|
||||
0x5b, 0x45, 0x3d, 0xc4, 0x41, 0x22, 0xce, 0xc0, 0xea, 0xf, 0x43, 0xcf, 0x4a, 0x3b, 0x4c, 0x56, 0xa9, 0xdc, 0xda, 0x87, 0xfe,
|
||||
0x24, 0x7b, 0xd2, 0x28, 0xac, 0xef, 0xf2, 0x9a, 0xee, 0xc5, 0x8c, 0xb0, 0xee, 0x6e, 0xc0, 0xb8, 0x8e, 0x4b, 0x39, 0x66, 0x8a,
|
||||
0xef, 0x33, 0xa2, 0xe5, 0x32, 0x1a, 0xff, 0x0, 0x4c, 0xe8, 0xf8, 0xa9, 0x69, 0xbc, 0xf9, 0xda, 0xda, 0xe4, 0x28, 0xb3, 0xbc,
|
||||
0xbb, 0x86, 0x68, 0xa4, 0xf7, 0xbc, 0xa6, 0x1f, 0xbf, 0x18, 0x60, 0xad, 0x2f, 0xd4, 0xd7, 0xd1, 0x2f, 0xb3, 0xaf, 0xe6, 0xd3,
|
||||
0x3b, 0x67, 0xd3, 0x9f, 0x48, 0xe9, 0xdd, 0x77, 0x31, 0x73, 0x61, 0x4, 0x7b, 0xb6, 0x1b, 0xa1, 0x7a, 0x7f, 0x23, 0x6f, 0x8c,
|
||||
0x8a, 0x2b, 0x9a, 0xae, 0xe6, 0xbc, 0xcc, 0xe4, 0x3a, 0xda, 0xc7, 0xb, 0xd8, 0x98, 0x4b, 0x68, 0x6b, 0xb5, 0x86, 0x9e, 0x78,
|
||||
0xc3, 0xdb, 0xe4, 0xa6, 0xb9, 0xa2, 0x49, 0x22, 0x92, 0x8e, 0x22, 0xf9, 0x71, 0x20, 0x63, 0x8f, 0x52, 0xdf, 0x4b, 0xcf, 0x7,
|
||||
0x35, 0x5f, 0x19, 0x24, 0xf3, 0x77, 0xc3, 0x5e, 0xee, 0xd6, 0x35, 0x8d, 0x6, 0xe3, 0x9c, 0x75, 0x38, 0x9d, 0x16, 0xf7, 0x74,
|
||||
0x9f, 0x75, 0xd1, 0xbb, 0xa, 0xea, 0xfe, 0xea, 0x58, 0xeb, 0xc2, 0x75, 0xb6, 0xc7, 0x7d, 0x79, 0x95, 0xdb, 0x71, 0xdb, 0xd5,
|
||||
0xad, 0x31, 0xcf, 0x54, 0xd8, 0x8b, 0xd9, 0x6f, 0x78, 0xa7, 0x9b, 0x49, 0x63, 0x93, 0xf0, 0x1c, 0xc1, 0x2f, 0x20, 0xc1, 0x3e,
|
||||
0x85, 0x7e, 0x2d, 0x74, 0x8f, 0x97, 0x1b, 0x57, 0x96, 0x9d, 0x59, 0xdd, 0xfa, 0x46, 0x1b, 0x6a, 0xc3, 0xe4, 0x3a, 0x53, 0xb,
|
||||
0x6f, 0x84, 0xcd, 0x4f, 0x8e, 0xc7, 0x49, 0xb5, 0xe8, 0x99, 0x6c, 0x86, 0xcf, 0x55, 0xbd, 0x1b, 0x4e, 0x85, 0x9f, 0xbb, 0xb3,
|
||||
0xba, 0xbb, 0xd6, 0x76, 0x4b, 0x2a, 0xa9, 0xa2, 0xaa, 0x67, 0x83, 0xf4, 0x4d, 0x4d, 0x1f, 0x66, 0xe2, 0x89, 0xad, 0xeb, 0x92,
|
||||
0x1a, 0xc3, 0x9d, 0x3e, 0x77, 0x78, 0x45, 0xda, 0x3e, 0x7, 0xf7, 0x96, 0x5b, 0xa9, 0xbb, 0x2, 0x19, 0x72, 0x9a, 0xfd, 0xf7,
|
||||
0x17, 0x19, 0xae, 0xb2, 0xec, 0x2b, 0x7b, 0x4a, 0xe0, 0xc2, 0x76, 0xe, 0x9d, 0xcd, 0xcd, 0x51, 0x5b, 0x64, 0xed, 0x3f, 0x54,
|
||||
0xb1, 0xd9, 0x66, 0xf1, 0xd5, 0x73, 0x4c, 0x19, 0x4c, 0x7d, 0x55, 0xd5, 0x2d, 0x95, 0xd7, 0xf9, 0xd7, 0x4, 0xb6, 0xf3, 0xcc,
|
||||
0x1a, 0x5a, 0x9, 0x44, 0xfa, 0x3, 0xf8, 0x73, 0xe3, 0xf, 0x93, 0x7d, 0x45, 0xe4, 0x6, 0x6f, 0xbe, 0xfa, 0x5f, 0x4d, 0xed,
|
||||
0xc, 0xb6, 0xaf, 0xd8, 0xfa, 0xc6, 0x2b, 0x5f, 0xbf, 0xd9, 0xad, 0xaf, 0x26, 0xb8, 0xc5, 0xe3, 0xaf, 0x75, 0x89, 0x6e, 0xee,
|
||||
0xad, 0x2d, 0xab, 0xb5, 0xbc, 0xb5, 0xf6, 0x86, 0x6b, 0x9a, 0x38, 0xaf, 0x9e, 0x2a, 0xe2, 0xaf, 0xd5, 0xfd, 0x9e, 0xde, 0xfc,
|
||||
0xfb, 0x84, 0x62, 0x33, 0x10, 0xc7, 0x6f, 0x97, 0xca, 0xc1, 0xd, 0x1c, 0x47, 0xc, 0x19, 0x2b, 0xe8, 0x62, 0x8e, 0x9f, 0x7f,
|
||||
0x6a, 0x23, 0x8e, 0xe6, 0x5a, 0x23, 0xa3, 0x8f, 0x7f, 0x7e, 0x7d, 0xa9, 0xa6, 0x9e, 0x38, 0x7, 0x9a, 0x9, 0x48, 0xfa, 0x14,
|
||||
0x7a, 0x66, 0xf5, 0x9f, 0x79, 0xf4, 0xf7, 0x72, 0x79, 0x5, 0xe4, 0x87, 0x5e, 0x61, 0x77, 0x3d, 0x47, 0xb2, 0xb1, 0x99, 0xde,
|
||||
0x9b, 0xea, 0x5c, 0x5e, 0xc9, 0x88, 0xb0, 0xc8, 0xfe, 0x3, 0x1b, 0x17, 0xce, 0xdb, 0x7e, 0xec, 0xdd, 0x66, 0x4c, 0x84, 0x17,
|
||||
0xb1, 0xe2, 0x76, 0x5b, 0x4c, 0xd5, 0x11, 0x63, 0x30, 0xd9, 0x6b, 0x7a, 0x62, 0xbf, 0xc6, 0x5d, 0x63, 0xef, 0xf9, 0x8a, 0xba,
|
||||
0x7e, 0xe7, 0x1c, 0x82, 0x3d, 0xde, 0x53, 0x78, 0xf1, 0xb9, 0x78, 0xa7, 0xdf, 0xfd, 0x9f, 0xd0, 0x7b, 0xd4, 0x33, 0x7e, 0xd8,
|
||||
0xeb, 0xdd, 0x9a, 0xf7, 0x17, 0x67, 0x93, 0x92, 0xd6, 0x4b, 0x4b, 0x7d, 0x9b, 0x5b, 0x96, 0xaf, 0xc5, 0xea, 0xdb, 0x6e, 0x3a,
|
||||
0x29, 0x3d, 0xf9, 0xe3, 0x1d, 0xb3, 0xe0, 0x27, 0xb7, 0xbd, 0x8b, 0x8f, 0x7e, 0x79, 0x8f, 0x89, 0xbe, 0xdd, 0x5e, 0xd5, 0xd1,
|
||||
0x57, 0x1c, 0x6, 0xbf, 0x82, 0x57, 0x7f, 0x4e, 0xff, 0x0, 0x8e, 0x9e, 0x3e, 0x77, 0x67, 0x4e, 0x79, 0x17, 0x94, 0xee, 0x5e,
|
||||
0x8a, 0xe9, 0xbe, 0xdb, 0xc9, 0xe1, 0x3b, 0x33, 0x54, 0xb0, 0xc2, 0xe4, 0x7b, 0x3b, 0xac, 0x74, 0x9d, 0xf6, 0xfb, 0x11, 0x63,
|
||||
0x71, 0xab, 0x4f, 0x71, 0x71, 0x65, 0x8b, 0xbb, 0xda, 0xb0, 0x79, 0x6b, 0x8c, 0x7d, 0xa4, 0xf7, 0x1c, 0x71, 0x5d, 0x71, 0xc5,
|
||||
0x55, 0x14, 0x55, 0x5f, 0x1f, 0x2e, 0x78, 0xe7, 0x9f, 0xcc, 0x11, 0x57, 0xcd, 0xc7, 0x1c, 0x59, 0x9c, 0xbc, 0x51, 0x51, 0x44,
|
||||
0x51, 0x45, 0x93, 0xbf, 0x8e, 0x38, 0xe3, 0xa7, 0x8a, 0x23, 0x8e, 0x3a, 0x2e, 0xa5, 0xa6, 0x8a, 0x28, 0xa2, 0x9e, 0x38, 0xa6,
|
||||
0x8a, 0x28, 0xa7, 0x8e, 0x38, 0xe3, 0x8e, 0x38, 0xf6, 0xe3, 0x80, 0x79, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x57, 0xe8, 0x6f, 0xdf, 0x97, 0x4c, 0xff, 0x0, 0x15, 0xfa,
|
||||
0xef, 0xf9, 0xbf, 0xe, 0xb, 0xf, 0x7c, 0xc4, 0xf3, 0x8b, 0xaa, 0x7c, 0x4d, 0xed, 0xf, 0x1b, 0x7a, 0xdb, 0xbc, 0xec, 0x31,
|
||||
0x90, 0x75, 0x47, 0x93, 0x95, 0xf6, 0x96, 0x95, 0x9e, 0xdd, 0x33, 0x1f, 0x66, 0x6c, 0x26, 0x9f, 0x96, 0xc1, 0x45, 0xa0, 0xc1,
|
||||
0x84, 0xe7, 0x6d, 0xb2, 0xbb, 0xa2, 0x4b, 0x9, 0x34, 0x5d, 0x82, 0x3d, 0xbe, 0xea, 0xd3, 0x29, 0x3c, 0xdc, 0x73, 0x1d, 0x9f,
|
||||
0x15, 0x45, 0x34, 0xbe, 0xd6, 0xb4, 0xdc, 0x55, 0x48, 0x45, 0xf, 0xd6, 0x3b, 0xd2, 0xb2, 0xef, 0xc3, 0xfd, 0xba, 0x6e, 0xfe,
|
||||
0xe8, 0xec, 0x54, 0xf9, 0x2f, 0x17, 0x7b, 0xb, 0x33, 0xf2, 0x92, 0xca, 0xc2, 0x9a, 0xee, 0xff, 0x0, 0xe0, 0xbe, 0xd1, 0x99,
|
||||
0x9a, 0xb9, 0xad, 0xb5, 0x8b, 0xe9, 0x69, 0xaa, 0x5a, 0xeb, 0xd1, 0x33, 0x32, 0x57, 0xed, 0x83, 0xc8, 0x57, 0xcf, 0x34, 0xc3,
|
||||
0x5f, 0x3c, 0x58, 0x5c, 0x55, 0xc4, 0xdf, 0x84, 0x96, 0xf4, 0x34, 0xc3, 0xd2, 0x8b, 0xfc, 0x46, 0x3c, 0x46, 0xfe, 0x2d, 0xe3,
|
||||
0x3f, 0xe9, 0xd9, 0x30, 0x74, 0xff, 0x0, 0xea, 0x34, 0xd7, 0x72, 0x9b, 0x7f, 0x9e, 0x1e, 0x3b, 0x6a, 0x78, 0x48, 0xa8, 0xb8,
|
||||
0xcd, 0x6d, 0x1e, 0x3f, 0xe9, 0x3a, 0xee, 0x22, 0x9, 0x24, 0xa6, 0x18, 0xe6, 0xca, 0x66, 0xbb, 0x93, 0xb4, 0x71, 0xb8, 0xf8,
|
||||
0xab, 0x96, 0xaf, 0xd3, 0x15, 0x12, 0x5d, 0xdc, 0xd1, 0xc7, 0x35, 0x73, 0xf9, 0x53, 0xc7, 0x3e, 0xe0, 0xea, 0xf7, 0xa8, 0x47,
|
||||
0x67, 0xd9, 0xfa, 0x4a, 0xfa, 0x63, 0xf5, 0xff, 0x0, 0x49, 0x78, 0xf7, 0x3c, 0x78, 0x1d, 0xeb, 0x3d, 0x6, 0x2f, 0xa5, 0xb4,
|
||||
0xcd, 0xa2, 0xce, 0xde, 0x2b, 0x7c, 0x95, 0xae, 0x46, 0xfb, 0x15, 0x7f, 0x9e, 0xed, 0x4e, 0xd8, 0xae, 0x88, 0xfe, 0xd7, 0x1c,
|
||||
0x6c, 0xb9, 0x4a, 0xa1, 0xbb, 0x96, 0x39, 0xe9, 0xe7, 0xe5, 0x6b, 0x95, 0xcb, 0x43, 0x35, 0x34, 0xf3, 0x44, 0x3f, 0x0, 0x41,
|
||||
0x76, 0x69, 0xa6, 0xb9, 0x9a, 0x5b, 0x8b, 0x89, 0x64, 0x9e, 0xe2, 0x79, 0x2b, 0x9a, 0x79, 0xe6, 0xae, 0xb9, 0x66, 0x9a, 0x69,
|
||||
0x6b, 0xe6, 0xb9, 0x65, 0x96, 0x5a, 0xf9, 0xaa, 0xb9, 0x24, 0x92, 0xba, 0xb9, 0xe6, 0xaa, 0xb9, 0xe7, 0x9e, 0x79, 0xe7, 0x9f,
|
||||
0x7e, 0x41, 0xfc, 0xc1, 0xdf, 0xbf, 0x40, 0x9f, 0x36, 0x76, 0xbe, 0x97, 0xf2, 0x73, 0x13, 0xe3, 0xe, 0xc5, 0x99, 0xbc, 0xbc,
|
||||
0xe9, 0xdf, 0x21, 0x6e, 0xae, 0xb1, 0xd8, 0xec, 0x3d, 0xd4, 0xf2, 0x4d, 0x65, 0xa9, 0x76, 0xbd, 0xae, 0x36, 0x7b, 0xbd, 0x73,
|
||||
0x3f, 0x88, 0x8a, 0x49, 0x79, 0xa2, 0xc3, 0x8d, 0xae, 0x3b, 0x1e, 0x71, 0x17, 0xf1, 0xc3, 0x47, 0x1f, 0x8b, 0x96, 0x6b, 0x29,
|
||||
0x24, 0xe7, 0xda, 0xd6, 0x90, 0x7c, 0x6f, 0xaf, 0xbf, 0x8b, 0x9a, 0xf7, 0x40, 0x79, 0x9f, 0x47, 0x60, 0x69, 0x58, 0xeb, 0x6c,
|
||||
0x4e, 0xa9, 0xe4, 0x7e, 0xb3, 0x37, 0x65, 0xdd, 0xe3, 0x2d, 0x22, 0xae, 0x1b, 0x5b, 0x2e, 0xc5, 0xb5, 0xcb, 0x5c, 0xe2, 0xfb,
|
||||
0xe, 0xab, 0x68, 0xfe, 0x1c, 0xc5, 0xf6, 0xf3, 0x97, 0x75, 0x5a, 0x66, 0x26, 0xe7, 0x8a, 0xf9, 0xe7, 0x9b, 0xdc, 0x9c, 0xfc,
|
||||
0x7c, 0x28, 0xa3, 0x88, 0xf8, 0xa8, 0x38, 0x6c, 0x9, 0x2c, 0x7d, 0x34, 0x7d, 0x9b, 0xa8, 0x6b, 0x9e, 0x41, 0xf7, 0xf7, 0x58,
|
||||
0x66, 0xb2, 0x56, 0x76, 0x1b, 0x5f, 0x66, 0xf5, 0xbe, 0xb5, 0x95, 0xd2, 0xed, 0xee, 0xe6, 0xa2, 0x9, 0x33, 0x55, 0x75, 0xf6,
|
||||
0x5f, 0x31, 0x75, 0xb0, 0xe2, 0x71, 0xbc, 0x49, 0x4f, 0x1f, 0x8b, 0xc9, 0x51, 0x8b, 0xd8, 0xf8, 0xbe, 0xfb, 0x14, 0x55, 0xf7,
|
||||
0x39, 0xb4, 0xb2, 0x9e, 0x5e, 0x29, 0xaa, 0x88, 0x64, 0xaa, 0x80, 0xd3, 0x5f, 0x58, 0x8f, 0x7, 0x7c, 0x87, 0xe8, 0xcf, 0x2b,
|
||||
0xbb, 0xcb, 0xbb, 0x36, 0x2d, 0x4f, 0x66, 0xda, 0x7a, 0x67, 0xb8, 0xbb, 0x2f, 0x69, 0xec, 0x6d, 0x5f, 0xb5, 0xf1, 0x76, 0x17,
|
||||
0xb9, 0x8d, 0x67, 0x19, 0x1e, 0xe9, 0x97, 0xb9, 0xce, 0x53, 0xa5, 0x6c, 0xf9, 0x3b, 0x68, 0xa6, 0x8b, 0x54, 0xcb, 0xeb, 0x72,
|
||||
0xde, 0x57, 0x61, 0x69, 0x6f, 0x7d, 0xcc, 0x1c, 0x5e, 0x5a, 0x5a, 0x53, 0x25, 0xaf, 0x32, 0x51, 0x4d, 0x7f, 0x6c, 0x38, 0xe2,
|
||||
0xe, 0xbd, 0x7a, 0x12, 0xff, 0x0, 0x89, 0xef, 0x8f, 0xbf, 0xe9, 0x3d, 0xcb, 0xff, 0x0, 0x64, 0xfb, 0x4, 0x19, 0x7, 0xea,
|
||||
0xd, 0xff, 0x0, 0x11, 0xad, 0xa3, 0xf8, 0x51, 0xd5, 0x9f, 0xf4, 0x8b, 0xc0, 0x77, 0x77, 0x44, 0xbf, 0xc6, 0x7a, 0x47, 0xfa,
|
||||
0x2e, 0xe3, 0xbb, 0x13, 0x5d, 0xc2, 0xd9, 0xc3, 0xdc, 0x7b, 0x2e, 0x83, 0xae, 0x6e, 0x57, 0x12, 0x5d, 0x58, 0x5b, 0xcd, 0x71,
|
||||
0x91, 0xef, 0x7e, 0xf1, 0x87, 0x17, 0x4e, 0x1e, 0x4d, 0x82, 0x2e, 0x7d, 0xad, 0xf2, 0x36, 0x9d, 0x69, 0x8f, 0xc9, 0x5b, 0xc3,
|
||||
0x24, 0x55, 0x57, 0xc5, 0x17, 0x16, 0x18, 0x1e, 0x63, 0xa7, 0x9e, 0x6b, 0x97, 0xde, 0xa0, 0x84, 0x2e, 0xd1, 0xb4, 0x6c, 0x9b,
|
||||
0xbe, 0xc9, 0x9d, 0xdc, 0x37, 0xc, 0xee, 0x57, 0x67, 0xda, 0xb6, 0x7c, 0xad, 0xf6, 0x73, 0x61, 0xd8, 0x73, 0x97, 0xd7, 0x19,
|
||||
0x2c, 0xc6, 0x6b, 0x31, 0x92, 0xb8, 0x92, 0xea, 0xff, 0x0, 0x25, 0x92, 0xbf, 0xba, 0x92, 0x5b, 0x8b, 0xbb, 0xcb, 0xbb, 0x89,
|
||||
0x6a, 0xae, 0xba, 0xeb, 0xab, 0x9a, 0xaa, 0xab, 0x90, 0x78, 0x20, 0xf4, 0x79, 0xcc, 0x65, 0xaa, 0xc4, 0xc7, 0x80, 0xab, 0x29,
|
||||
0x91, 0xab, 0x5, 0x16, 0x46, 0x6c, 0xc4, 0x58, 0x5e, 0x6f, 0x6e, 0x79, 0xc4, 0xc7, 0x96, 0xb8, 0xb6, 0x82, 0xca, 0x7c, 0xa4,
|
||||
0x78, 0xee, 0x65, 0xfc, 0x1d, 0x19, 0x19, 0xec, 0xed, 0xa3, 0x8a, 0xb9, 0xf8, 0xa3, 0x89, 0x6a, 0x8a, 0x3a, 0x69, 0xe6, 0xae,
|
||||
0x69, 0xa7, 0x8e, 0x38, 0x9, 0x2e, 0x7d, 0x32, 0x7f, 0xbf, 0x7f, 0x26, 0xff, 0x0, 0x84, 0x9a, 0x9f, 0xf3, 0x8d, 0x40, 0xeb,
|
||||
0x6e, 0xed, 0xb1, 0xf8, 0xbf, 0xea, 0xf5, 0x8f, 0xf2, 0xb7, 0xc1, 0xae, 0xcd, 0x82, 0xcb, 0x47, 0xef, 0xbf, 0x1b, 0xbb, 0x6b,
|
||||
0xb2, 0xf0, 0x1a, 0x96, 0x46, 0x3a, 0x60, 0xbc, 0xd8, 0xf0, 0xf6, 0x7a, 0x96, 0xdf, 0x93, 0xd6, 0x75, 0x5e, 0xe0, 0xd1, 0x3f,
|
||||
0x15, 0x25, 0xbc, 0xd9, 0x7c, 0x3d, 0xd5, 0xbd, 0xbd, 0xbd, 0x86, 0xd1, 0x8b, 0xe2, 0x4a, 0x69, 0xe2, 0x49, 0x7e, 0x15, 0xf3,
|
||||
0xd, 0x37, 0x16, 0x17, 0x14, 0x84, 0x28, 0x7c, 0x99, 0xf1, 0xaf, 0xb5, 0x7c, 0x4a, 0xee, 0x5d, 0xbb, 0xa3, 0xbb, 0x8b, 0x7,
|
||||
0x56, 0x1b, 0x6d, 0xd5, 0x6e, 0xb8, 0xe6, 0xb, 0xbb, 0x7f, 0xbb, 0x36, 0xb, 0x69, 0xc0, 0x5d, 0x55, 0x25, 0x58, 0x5d, 0xbf,
|
||||
0x55, 0xc8, 0xc9, 0x14, 0x3c, 0x65, 0x35, 0xcc, 0xed, 0xb4, 0x7c, 0xd7, 0x4, 0xbf, 0x1a, 0x24, 0x8a, 0x4a, 0x64, 0xb7, 0x9e,
|
||||
0x88, 0x6e, 0x61, 0x9a, 0x18, 0xc2, 0x54, 0x7f, 0x4c, 0x97, 0xee, 0x37, 0xc9, 0xef, 0xe2, 0xbe, 0x9d, 0xfc, 0xa1, 0x70, 0x8,
|
||||
0x7b, 0x67, 0xff, 0x0, 0xbf, 0x73, 0x5f, 0xea, 0xd9, 0x1f, 0xf7, 0x93, 0x3, 0x28, 0xf8, 0xed, 0xd1, 0x9b, 0x97, 0x92, 0xdd,
|
||||
0xdf, 0xd6, 0x5d, 0x13, 0xa0, 0xc1, 0xcc, 0xbb, 0x47, 0x65, 0xed, 0x78, 0xdd, 0x72, 0xd2, 0xe3, 0x98, 0x6a, 0xb8, 0xb7, 0xc3,
|
||||
0xd8, 0xcf, 0x5f, 0x33, 0xe7, 0x36, 0x4c, 0x84, 0x54, 0x57, 0x1d, 0x75, 0x62, 0xb5, 0x8c, 0x1d, 0xbd, 0xce, 0x42, 0xef, 0xe3,
|
||||
0x57, 0x15, 0x7e, 0x1a, 0xda, 0xbf, 0x8f, 0xbd, 0x5e, 0xdc, 0x72, 0x12, 0xe5, 0xf5, 0x32, 0xf3, 0xa6, 0xc7, 0xd2, 0xdb, 0x59,
|
||||
0xf0, 0xcb, 0xc4, 0xdf, 0x18, 0x78, 0x86, 0xde, 0xeb, 0xaf, 0x26, 0xd1, 0xb7, 0x2d, 0xdf, 0x9, 0xc4, 0xf1, 0xf1, 0x3d, 0xff,
|
||||
0x0, 0x4c, 0x68, 0xd7, 0x3f, 0xb2, 0xe0, 0xd3, 0xf3, 0xf7, 0x54, 0x53, 0x54, 0x94, 0xdf, 0xf7, 0x16, 0x56, 0xda, 0xfe, 0x6c,
|
||||
0x85, 0xdf, 0x14, 0xf1, 0x71, 0xff, 0x0, 0xa4, 0x92, 0x5e, 0x7f, 0x3b, 0x9e, 0x2a, 0xe4, 0x30, 0xe7, 0xaf, 0x17, 0x8f, 0x9a,
|
||||
0x8f, 0x92, 0xfe, 0x35, 0xf4, 0xaf, 0xa8, 0xff, 0x0, 0x47, 0x51, 0x46, 0x77, 0x1f, 0x8c, 0xd5, 0x75, 0x8b, 0x4d, 0xc7, 0x23,
|
||||
0x65, 0x6f, 0x4f, 0x17, 0x39, 0x7e, 0xa0, 0xdf, 0x2b, 0x8b, 0x23, 0xa5, 0x67, 0xf2, 0x50, 0xc3, 0xf7, 0x6b, 0x82, 0xfb, 0x49,
|
||||
0xda, 0x33, 0x35, 0x59, 0x5d, 0xc5, 0x57, 0x3c, 0xcb, 0x7, 0x19, 0x6a, 0xe9, 0x97, 0x9a, 0x78, 0xb4, 0xe7, 0x8a, 0x42, 0x22,
|
||||
0xc0, 0x98, 0xff, 0x0, 0xd3, 0x25, 0xfb, 0x8d, 0xf2, 0x7b, 0xf8, 0xaf, 0xa7, 0x7f, 0x28, 0x5c, 0x2, 0x1e, 0xd9, 0xff, 0x0,
|
||||
0xef, 0xdc, 0xd7, 0xfa, 0xb6, 0x47, 0xfd, 0xe4, 0xc0, 0xf2, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x57, 0xe8, 0x6f, 0xdf, 0x97, 0x4c, 0xff, 0x0, 0x15, 0xfa, 0xef, 0xf9,
|
||||
0xbf, 0xe, 0x9, 0x43, 0xfd, 0x4f, 0xff, 0x0, 0xdc, 0x5e, 0x17, 0x7f, 0xab, 0x77, 0xf7, 0xfb, 0x3e, 0x9d, 0x6, 0x35, 0xf4,
|
||||
0x75, 0xf5, 0x2c, 0xd3, 0xfb, 0x1b, 0x50, 0x87, 0xd3, 0x8f, 0xcd, 0x6a, 0xf1, 0x7b, 0x76, 0x89, 0xba, 0x62, 0x64, 0xd0, 0xfa,
|
||||
0x83, 0x66, 0xde, 0x64, 0xa6, 0xf3, 0x17, 0x90, 0xc5, 0x5f, 0xdb, 0xd1, 0x63, 0x6b, 0xd2, 0x7b, 0x75, 0xcd, 0xe5, 0x5c, 0x7c,
|
||||
0x6d, 0xa4, 0xa3, 0x8e, 0x28, 0xd6, 0x6f, 0x2b, 0x92, 0x99, 0x20, 0x9b, 0x8a, 0x2c, 0x28, 0xae, 0x9e, 0x78, 0xb0, 0xa6, 0x80,
|
||||
0xf8, 0x7c, 0x7f, 0xa6, 0xa6, 0xe3, 0xe0, 0x17, 0xab, 0x8f, 0x89, 0x17, 0x78, 0x58, 0xf2, 0x5b, 0x27, 0x8e, 0x9d, 0x85, 0xde,
|
||||
0x11, 0x4b, 0xd5, 0x1b, 0xc4, 0xd4, 0x57, 0x71, 0x3e, 0x2a, 0x4e, 0x71, 0x99, 0x7b, 0xd9, 0x3a, 0xe7, 0x72, 0xb9, 0xa2, 0x2a,
|
||||
0x61, 0x83, 0x6d, 0xc1, 0xda, 0xd3, 0x5f, 0xd8, 0x9b, 0xf4, 0xc7, 0x98, 0xb1, 0x8b, 0xf1, 0x51, 0x53, 0x4c, 0x94, 0x5d, 0x5b,
|
||||
0xda, 0x87, 0xd9, 0x7a, 0xe9, 0x6c, 0xb8, 0x9d, 0x2f, 0xd5, 0x33, 0xc2, 0x3d, 0xc7, 0x3d, 0x2c, 0x30, 0x60, 0xf5, 0x3d, 0x17,
|
||||
0xa4, 0xb6, 0x5c, 0xcc, 0xf7, 0x32, 0xc7, 0x5, 0xbc, 0x38, 0x9c, 0x17, 0x91, 0x5b, 0xee, 0x53, 0x23, 0x2c, 0xf3, 0x4d, 0xfd,
|
||||
0x4c, 0x30, 0xc7, 0x67, 0x6b, 0x5f, 0x35, 0x55, 0x5f, 0xe9, 0xa6, 0x9e, 0x39, 0xe7, 0x9f, 0xc8, 0x1b, 0xf, 0xf5, 0x36, 0x68,
|
||||
0xf9, 0xcc, 0xb7, 0x4d, 0xf8, 0xb7, 0xd9, 0x56, 0x34, 0x49, 0x36, 0xb5, 0xa8, 0x76, 0x26, 0xff, 0x0, 0xa9, 0xe6, 0x64, 0x86,
|
||||
0x3a, 0xe5, 0x86, 0x2c, 0x87, 0x60, 0x6b, 0x9a, 0xfe, 0x57, 0x1, 0x71, 0x34, 0xb4, 0x55, 0xcc, 0x71, 0x45, 0x54, 0x3d, 0x7f,
|
||||
0x7b, 0x45, 0x35, 0x55, 0xc7, 0xb5, 0x55, 0x49, 0xc7, 0x1c, 0x55, 0xc7, 0x3c, 0xfb, 0x54, 0x10, 0xea, 0x0, 0x1b, 0xdd, 0xe9,
|
||||
0x87, 0xa2, 0xe7, 0xfb, 0xf, 0xd4, 0x1b, 0xc4, 0x1c, 0x1e, 0xb9, 0x1c, 0xf2, 0x5e, 0xe2, 0xfb, 0xdb, 0x41, 0xde, 0xaf, 0x79,
|
||||
0x82, 0x89, 0xab, 0xe6, 0x2c, 0x7, 0x5a, 0x66, 0xad, 0xfb, 0xb, 0x66, 0x92, 0x5e, 0x60, 0xa6, 0xaa, 0xa8, 0x83, 0xfa, 0x3f,
|
||||
0xac, 0x5c, 0xf1, 0x5d, 0x55, 0x7b, 0x51, 0xed, 0x57, 0xea, 0xe7, 0x8e, 0x39, 0xe4, 0x1d, 0x98, 0xfa, 0x9c, 0xf6, 0x9c, 0x35,
|
||||
0xdf, 0x6b, 0x78, 0xa1, 0xa4, 0xc1, 0x24, 0x1c, 0xec, 0x1a, 0xff, 0x0, 0x5e, 0xf6, 0x56, 0xd3, 0x93, 0x86, 0x9f, 0x87, 0xe2,
|
||||
0x68, 0xc3, 0x6e, 0x3b, 0x26, 0xb5, 0x89, 0xc1, 0x49, 0x2f, 0xb7, 0x3f, 0x73, 0xec, 0x4b, 0x7b, 0xa3, 0x64, 0x78, 0x8f, 0xdf,
|
||||
0x8e, 0x29, 0xf9, 0x51, 0x5f, 0xb7, 0xbf, 0x3e, 0xfe, 0xc1, 0x17, 0x90, 0x7d, 0x36, 0x9b, 0xb9, 0xed, 0xbd, 0x77, 0xb5, 0x60,
|
||||
0x37, 0x9d, 0x13, 0x64, 0xcc, 0xea, 0x1b, 0x8e, 0xad, 0x93, 0xb6, 0xcc, 0xeb, 0x9b, 0x36, 0xbd, 0x90, 0xb9, 0xc5, 0x66, 0xb0,
|
||||
0xb9, 0x4b, 0x3a, 0xfe, 0xe5, 0xbd, 0xf6, 0x3f, 0x21, 0x67, 0x24, 0x57, 0x16, 0xd3, 0xc7, 0x57, 0xe5, 0xef, 0x4d, 0x5f, 0xaa,
|
||||
0x9e, 0x79, 0xa7, 0x9f, 0x7e, 0x39, 0xe7, 0x8e, 0x42, 0x45, 0x7e, 0x34, 0x7d, 0x47, 0xdd, 0xdf, 0xa7, 0xd9, 0xe3, 0xf5, 0x4f,
|
||||
0x2a, 0x3a, 0xa7, 0x59, 0xef, 0xc, 0x4, 0x76, 0x3f, 0xb3, 0x6f, 0x77, 0x5d, 0x42, 0xbb, 0x7d, 0xf, 0xb0, 0x2e, 0x69, 0xaa,
|
||||
0x9a, 0x29, 0x92, 0xff, 0x0, 0x3b, 0x87, 0xaa, 0xda, 0xfb, 0x43, 0xd9, 0xa4, 0x96, 0x1e, 0x2b, 0x8a, 0xbb, 0x7b, 0x6b, 0x5c,
|
||||
0xd, 0x15, 0xfc, 0xf8, 0xaf, 0x99, 0x39, 0xe6, 0x9e, 0x69, 0x90, 0x3a, 0x21, 0xb8, 0xf8, 0x41, 0xe9, 0xb5, 0xea, 0xfd, 0xd0,
|
||||
0x9b, 0x17, 0x75, 0x78, 0x85, 0x8e, 0xd7, 0xba, 0x77, 0xb8, 0x21, 0xae, 0xfa, 0x9a, 0xb2, 0xfa, 0xce, 0xbb, 0x69, 0xa3, 0x5d,
|
||||
0xe1, 0xb7, 0xd9, 0xed, 0xe8, 0xc9, 0x73, 0xad, 0xf7, 0x77, 0x59, 0xe1, 0xb9, 0xaf, 0x9, 0x77, 0x1e, 0x6e, 0x4e, 0x79, 0xaa,
|
||||
0x4c, 0xb5, 0x8d, 0x32, 0x4f, 0x2d, 0x72, 0x57, 0x73, 0x6f, 0x7b, 0x77, 0x4d, 0x12, 0xc3, 0x28, 0x70, 0xfb, 0xd1, 0x67, 0x50,
|
||||
0xd8, 0x7a, 0xf7, 0xd5, 0xbb, 0xaa, 0x74, 0x1d, 0xbb, 0x1d, 0x26, 0x23, 0x6b, 0xd1, 0xef, 0xfc, 0x82, 0xd4, 0x36, 0x7c, 0x4c,
|
||||
0xd5, 0x51, 0x5c, 0xd8, 0xbd, 0x87, 0x5a, 0xea, 0x8e, 0xcb, 0xc2, 0xe6, 0xb1, 0xd2, 0xd7, 0x1d, 0x55, 0xc7, 0x54, 0x96, 0x59,
|
||||
0x2b, 0x29, 0x62, 0xab, 0x9a, 0x79, 0xe6, 0x9e, 0x79, 0xa7, 0xf2, 0xe7, 0x9e, 0x1, 0xed, 0xfd, 0x41, 0xbf, 0xe2, 0x35, 0xb4,
|
||||
0x7f, 0xa, 0x3a, 0xb3, 0xfe, 0x91, 0x78, 0xe, 0xdb, 0xfa, 0xd6, 0x63, 0x2e, 0x3b, 0x67, 0xd2, 0x2b, 0xaa, 0xbb, 0x13, 0x47,
|
||||
0x8f, 0x8b, 0x9d, 0x5b, 0x5, 0x9a, 0xf1, 0xdf, 0xb5, 0xaf, 0x2b, 0xb1, 0xf6, 0x9e, 0xda, 0x8d, 0x2f, 0x62, 0xd2, 0x72, 0x5a,
|
||||
0xbe, 0x2e, 0xe6, 0x3a, 0xed, 0x7d, 0xa1, 0xaa, 0xce, 0x9c, 0x96, 0xff, 0x0, 0x8e, 0xe7, 0x8a, 0xf8, 0xe3, 0xed, 0xfc, 0x79,
|
||||
0xe3, 0x9e, 0x3d, 0xb8, 0xf6, 0xe7, 0x80, 0x84, 0x68, 0x24, 0x81, 0xa0, 0xfd, 0x47, 0xfd, 0xdf, 0xa0, 0xe8, 0x9a, 0x56, 0x89,
|
||||
0x67, 0xe3, 0x7f, 0x55, 0x64, 0x2d, 0x34, 0xad, 0x4b, 0x5c, 0xd4, 0xad, 0x6f, 0xee, 0x76, 0x9d, 0xba, 0x2b, 0x9b, 0xdb, 0x6d,
|
||||
0x73, 0xf, 0x67, 0x87, 0x82, 0xee, 0xe2, 0x38, 0xa9, 0xfb, 0x51, 0xcd, 0x73, 0x15, 0x9f, 0x15, 0xd7, 0x4d, 0x3f, 0xa7, 0x8a,
|
||||
0xaa, 0xe7, 0x8e, 0x3f, 0x20, 0x77, 0xbb, 0xc6, 0x5f, 0x30, 0xf6, 0x5f, 0x39, 0xbd, 0x36, 0xbb, 0x9b, 0xbe, 0xb6, 0xcd, 0x3b,
|
||||
0x7, 0xa2, 0xe6, 0x2f, 0xf4, 0xbe, 0xff, 0x0, 0xd5, 0xeb, 0xc0, 0xeb, 0xb7, 0xd7, 0xf9, 0x1c, 0x6d, 0x10, 0x6b, 0x7a, 0x8e,
|
||||
0x4e, 0x8, 0x2e, 0xa8, 0xb9, 0xc9, 0x71, 0xc5, 0xd7, 0x32, 0xdc, 0xd3, 0x3f, 0x3c, 0xd7, 0x4f, 0x3f, 0xa7, 0x8e, 0x78, 0xfc,
|
||||
0x81, 0xc3, 0xef, 0xa6, 0x4f, 0xf7, 0xef, 0xe4, 0xdf, 0xf0, 0x93, 0x53, 0xfe, 0x71, 0xa8, 0x1c, 0xa7, 0xf2, 0xb3, 0xb8, 0xbb,
|
||||
0x1b, 0xa0, 0x3d, 0x50, 0x7c, 0x9c, 0xed, 0xfe, 0xa6, 0xd9, 0xaf, 0xb5, 0xd, 0xff, 0x0, 0x47, 0xf2, 0xc7, 0xba, 0x72, 0xd8,
|
||||
0x1c, 0xd5, 0x85, 0x7f, 0x9d, 0x12, 0x71, 0xd8, 0x3b, 0x1c, 0x17, 0x76, 0x17, 0xd6, 0xf5, 0x7b, 0xc1, 0x91, 0xc3, 0xe5, 0xac,
|
||||
0x66, 0x96, 0xd6, 0xf6, 0xd2, 0x6a, 0x6b, 0x82, 0xee, 0xd6, 0x69, 0x22, 0x92, 0x9a, 0xa8, 0xaf, 0x9e, 0x39, 0x9, 0x24, 0x66,
|
||||
0xf1, 0xbe, 0x3f, 0xfd, 0x40, 0x3e, 0x16, 0xf1, 0x9c, 0xc1, 0x51, 0xaf, 0x75, 0xaf, 0x98, 0x7d, 0x3d, 0x67, 0xf6, 0x68, 0x86,
|
||||
0x79, 0x2a, 0x92, 0xe7, 0x47, 0xdc, 0x2e, 0x20, 0xaa, 0x7a, 0xb0, 0x79, 0x39, 0x78, 0xa2, 0x5c, 0xc6, 0x5b, 0xa5, 0x3b, 0x32,
|
||||
0xab, 0x4a, 0xea, 0xb4, 0xb9, 0xe2, 0x99, 0xa4, 0xc7, 0xdc, 0xd1, 0xcd, 0x7c, 0x71, 0x2d, 0xcd, 0x95, 0xcd, 0xbc, 0xe1, 0xfe,
|
||||
0x7d, 0x3c, 0x5d, 0x61, 0xbe, 0x74, 0xbe, 0x9f, 0xe6, 0x67, 0x55, 0xf6, 0x76, 0xb5, 0x91, 0xd4, 0x37, 0xdd, 0x1b, 0xbc, 0x35,
|
||||
0x7c, 0xe, 0xcd, 0xaf, 0x65, 0x22, 0xfb, 0x77, 0x56, 0x17, 0xf6, 0xda, 0x84, 0xb5, 0xd3, 0x55, 0x15, 0xd3, 0xcd, 0x50, 0xdd,
|
||||
0xd8, 0xde, 0xdb, 0x49, 0x1d, 0xc5, 0xad, 0xcc, 0x35, 0x57, 0x6f, 0x77, 0x6b, 0x2c, 0x73, 0x43, 0x5d, 0x71, 0x49, 0x45, 0x75,
|
||||
0x4, 0x2e, 0x73, 0xff, 0x0, 0xdf, 0xb9, 0xaf, 0xf5, 0x6c, 0x8f, 0xfb, 0xc9, 0x81, 0x2a, 0xdf, 0x40, 0x5f, 0x1c, 0x75, 0x4e,
|
||||
0x94, 0xe9, 0xee, 0xeb, 0xf5, 0x22, 0xee, 0xee, 0x6d, 0xf0, 0x1a, 0xe6, 0x3f, 0x5a, 0xda, 0xb5, 0xde, 0xbf, 0xcc, 0x65, 0x21,
|
||||
0xe7, 0xed, 0xe2, 0x3a, 0xfb, 0x4e, 0xa2, 0xbc, 0x9f, 0x6a, 0x6f, 0x36, 0x74, 0x55, 0x4d, 0x7f, 0x7e, 0xbc, 0xa6, 0x53, 0x13,
|
||||
0x46, 0x1e, 0xce, 0xa8, 0x7e, 0x37, 0x3c, 0x55, 0x8e, 0xbe, 0x83, 0x8e, 0x2a, 0xa6, 0xe7, 0x8e, 0x2a, 0xf, 0xe9, 0xde, 0x1e,
|
||||
0x5e, 0x7d, 0x3e, 0xbe, 0x48, 0xf6, 0x66, 0xc3, 0xdc, 0x3d, 0xd9, 0xd6, 0x5d, 0xcf, 0xbd, 0xf6, 0x36, 0xd3, 0xc6, 0x3a, 0x9c,
|
||||
0xde, 0xc5, 0x77, 0x77, 0xe4, 0x16, 0x27, 0xf1, 0x11, 0x62, 0x31, 0x96, 0x98, 0x7c, 0x6d, 0xb5, 0xae, 0x23, 0x5e, 0xed, 0xbc,
|
||||
0x46, 0xb, 0x15, 0x67, 0x67, 0x8e, 0xb1, 0x8a, 0x3a, 0x21, 0xb4, 0xb5, 0x82, 0x2e, 0x3e, 0x3c, 0xd5, 0xf1, 0xf9, 0xd5, 0x55,
|
||||
0x55, 0x7, 0x4a, 0x3c, 0x1c, 0xf2, 0x83, 0xd3, 0x33, 0xc9, 0xde, 0xb9, 0xd9, 0xbc, 0x1, 0xf1, 0xa6, 0xcf, 0x6d, 0xb4, 0xeb,
|
||||
0xd8, 0xba, 0xc7, 0x76, 0xa6, 0x4e, 0xb1, 0xec, 0x28, 0xf7, 0x69, 0xa9, 0xbb, 0xd1, 0x76, 0x5c, 0x8c, 0x96, 0x9b, 0x7d, 0x96,
|
||||
0xbb, 0xb0, 0x76, 0x6, 0xc7, 0xb5, 0x66, 0x2e, 0xe4, 0xb5, 0xbd, 0xda, 0xf9, 0x9e, 0x8b, 0x5e, 0x2e, 0xf9, 0xae, 0xd2, 0x89,
|
||||
0x39, 0x96, 0xde, 0x8a, 0x63, 0x86, 0xae, 0x63, 0x8, 0x4f, 0xf9, 0x71, 0xe3, 0x7e, 0xd9, 0xe2, 0x57, 0x91, 0x3d, 0xa5, 0xd0,
|
||||
0x5b, 0x85, 0x17, 0x12, 0x5e, 0x68, 0x5b, 0x2d, 0xd5, 0xa6, 0x1b, 0x2f, 0x34, 0x15, 0x41, 0x1e, 0xcf, 0xa8, 0xde, 0xfb, 0x64,
|
||||
0x35, 0x1d, 0xaa, 0xd7, 0x8e, 0x69, 0xa6, 0x3e, 0x60, 0xcf, 0xeb, 0xd7, 0x36, 0xf7, 0x15, 0x53, 0x47, 0x35, 0x71, 0xc, 0xd5,
|
||||
0x57, 0x17, 0x3c, 0xfc, 0xe3, 0xab, 0x8e, 0x2, 0x52, 0x1f, 0x4c, 0x97, 0xee, 0x37, 0xc9, 0xef, 0xe2, 0xbe, 0x9d, 0xfc, 0xa1,
|
||||
0x70, 0x8, 0x7b, 0x67, 0xff, 0x0, 0xbf, 0x73, 0x5f, 0xea, 0xd9, 0x1f, 0xf7, 0x93, 0x3, 0xc9, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x57, 0xe8, 0x6f, 0xdf, 0x97, 0x4c,
|
||||
0xff, 0x0, 0x15, 0xfa, 0xef, 0xf9, 0xbf, 0xe, 0x9, 0x43, 0xfd, 0x4f, 0xff, 0x0, 0xdc, 0x5e, 0x17, 0x7f, 0xab, 0x77, 0xf7,
|
||||
0xfb, 0x3e, 0x9d, 0x4, 0x49, 0x23, 0x92, 0x48, 0xa4, 0xa2, 0x58, 0xab, 0xae, 0x29, 0x62, 0xae, 0x99, 0x23, 0x92, 0x3a, 0xb9,
|
||||
0xa2, 0x48, 0xe4, 0xa3, 0x9e, 0x2a, 0xa2, 0xba, 0x2b, 0xa7, 0x9e, 0x2a, 0xa2, 0xba, 0x2a, 0xe3, 0x8e, 0x78, 0xe7, 0x8e, 0x7d,
|
||||
0xf8, 0xe4, 0x13, 0x4d, 0xf4, 0x6b, 0xf5, 0x44, 0xc0, 0x79, 0x59, 0xaf, 0xea, 0xfe, 0x28, 0x79, 0x49, 0x79, 0x8d, 0xcc, 0x77,
|
||||
0xe6, 0x85, 0x5d, 0x9e, 0x67, 0xaa, 0x37, 0x3d, 0xa2, 0x3b, 0x4b, 0x89, 0x7b, 0x52, 0xcb, 0x51, 0xa7, 0x9c, 0x8e, 0x36, 0x5e,
|
||||
0x6e, 0x6e, 0xb8, 0xe7, 0x9e, 0x3b, 0x6b, 0x4a, 0xb3, 0xb7, 0xaa, 0x4e, 0x67, 0xa3, 0xe3, 0x73, 0x93, 0xc7, 0xc3, 0x55, 0xcf,
|
||||
0x3c, 0xd7, 0x3c, 0x77, 0x92, 0x48, 0x1c, 0xdb, 0xfa, 0x96, 0xbf, 0xe7, 0x33, 0xa5, 0xff, 0x0, 0xfa, 0xc7, 0xaf, 0xff, 0x0,
|
||||
0xdd, 0x5e, 0xd8, 0x7, 0x49, 0xfd, 0x3d, 0xbc, 0x99, 0xe8, 0xbf, 0x55, 0x8f, 0x8, 0x72, 0x5e, 0x8, 0x79, 0x29, 0x99, 0x8f,
|
||||
0x8e, 0xea, 0xd6, 0xf4, 0x5b, 0x6d, 0x52, 0xfe, 0x3b, 0xc9, 0xac, 0x6d, 0x76, 0x4d, 0xab, 0x9, 0xab, 0x53, 0x4f, 0xf4, 0x1b,
|
||||
0xb7, 0x7a, 0xfe, 0xe6, 0xf3, 0xef, 0x71, 0x91, 0xdb, 0xb4, 0xf8, 0xac, 0x2d, 0x2a, 0xca, 0xd3, 0xf0, 0x92, 0x4e, 0x2e, 0xe0,
|
||||
0xaa, 0x6b, 0x88, 0xeb, 0xb4, 0xbc, 0xaa, 0x9e, 0x42, 0x39, 0xde, 0x64, 0xfa, 0x56, 0xf9, 0x75, 0xe1, 0xb6, 0xd1, 0x99, 0x83,
|
||||
0x64, 0xeb, 0xbc, 0xf7, 0x62, 0xf5, 0x85, 0xbd, 0xc5, 0xd4, 0xb8, 0xe, 0xe6, 0xeb, 0xac, 0x16, 0x53, 0x3f, 0xa7, 0x64, 0xb0,
|
||||
0xd1, 0xd7, 0x5d, 0x56, 0xb7, 0x5b, 0x1c, 0x58, 0xf8, 0xaf, 0xaf, 0x74, 0x1c, 0xbf, 0x36, 0xdc, 0x71, 0xf8, 0x8b, 0x2c, 0xa7,
|
||||
0xdb, 0xa6, 0x99, 0xa9, 0x93, 0x8b, 0x69, 0xee, 0xe0, 0xa6, 0x9b, 0x8a, 0xc3, 0x4d, 0xba, 0x9b, 0xa1, 0xfb, 0xab, 0xbe, 0x33,
|
||||
0xd6, 0xfa, 0xcf, 0x4c, 0x75, 0x56, 0xfd, 0xd9, 0xd9, 0xab, 0x8b, 0xb8, 0x2c, 0xbf, 0xb, 0xa5, 0xea, 0xf9, 0x7c, 0xf4, 0x76,
|
||||
0x93, 0x4f, 0xf9, 0xd3, 0x5e, 0x56, 0xfa, 0xc6, 0xd6, 0x5b, 0xc, 0x35, 0xa4, 0x71, 0xfb, 0xc9, 0x2d, 0xc5, 0xdc, 0xb0, 0x41,
|
||||
0xc, 0x54, 0xd5, 0x24, 0x95, 0xd3, 0x45, 0x3c, 0xd5, 0xc0, 0x4c, 0x2b, 0xd3, 0x9f, 0xc0, 0xde, 0xb7, 0xf4, 0x90, 0xea, 0xd,
|
||||
0xff, 0x0, 0xcc, 0x8f, 0x34, 0xf7, 0x4d, 0x5b, 0x1, 0xda, 0x97, 0x9a, 0xa4, 0xf8, 0xab, 0xda, 0x22, 0xc8, 0xdb, 0xe4, 0xf0,
|
||||
0xfd, 0x65, 0xab, 0x5c, 0xcb, 0x16, 0x47, 0x9d, 0x1f, 0x57, 0x9a, 0xf, 0x7e, 0x77, 0x4e, 0xcf, 0xdc, 0xef, 0xb1, 0xd0, 0x45,
|
||||
0x37, 0xec, 0xfa, 0x67, 0xf9, 0xd7, 0x15, 0x16, 0x56, 0x1c, 0xcb, 0x1d, 0x57, 0x13, 0xdd, 0x84, 0x55, 0xfc, 0xe9, 0xf2, 0xcb,
|
||||
0x63, 0xf3, 0x5b, 0xc9, 0xbe, 0xc6, 0xef, 0xdc, 0xed, 0xad, 0xc6, 0x27, 0x1d, 0xb0, 0x5e, 0xc1, 0x89, 0xd1, 0xf5, 0x9b, 0x89,
|
||||
0xa8, 0x9e, 0xbd, 0x4f, 0xaf, 0xf0, 0x31, 0xf3, 0x63, 0xab, 0x60, 0xab, 0x92, 0x2f, 0x78, 0x2b, 0xbe, 0xa6, 0xd3, 0x8a, 0xae,
|
||||
0xaf, 0xab, 0x8f, 0xfa, 0xb9, 0x72, 0x37, 0x57, 0x12, 0x51, 0xc7, 0x14, 0xd7, 0xc7, 0x1c, 0x6, 0xa2, 0x3, 0xb0, 0x9e, 0x93,
|
||||
0x1e, 0x9a, 0x3a, 0x7, 0xa8, 0x66, 0xc5, 0xda, 0xb0, 0xf6, 0x7, 0x74, 0x73, 0xa0, 0xe3, 0x7a, 0xeb, 0x1, 0x1d, 0x76, 0xba,
|
||||
0x7e, 0xa1, 0x5e, 0x3a, 0x7e, 0xcb, 0xcc, 0x65, 0x33, 0x50, 0xcd, 0x6f, 0x88, 0xda, 0x79, 0xb3, 0xce, 0x63, 0xee, 0xf1, 0x74,
|
||||
0x68, 0x38, 0x2c, 0x97, 0x11, 0xd3, 0x7d, 0x54, 0x7c, 0x49, 0x73, 0x75, 0x35, 0x74, 0xdb, 0x71, 0x5d, 0x9f, 0x32, 0xd1, 0x72,
|
||||
0xd, 0x27, 0xf2, 0x9f, 0xc2, 0xdf, 0x21, 0xbc, 0x3e, 0xec, 0x9c, 0x97, 0x5c, 0xf7, 0x1f, 0x5f, 0x67, 0x71, 0x9c, 0xc7, 0x97,
|
||||
0xab, 0x19, 0xab, 0xee, 0x36, 0x38, 0xcb, 0xeb, 0xdd, 0x23, 0x7f, 0xb7, 0x9e, 0xb9, 0xf9, 0xc5, 0x64, 0x74, 0xdd, 0x8e, 0x2b,
|
||||
0x7e, 0x6c, 0x32, 0xbc, 0x65, 0x2d, 0xa0, 0xe6, 0x4e, 0x2d, 0x3e, 0x54, 0xdf, 0xdb, 0x55, 0xc5, 0x51, 0x5c, 0x43, 0x14, 0xd1,
|
||||
0xd7, 0x1d, 0x21, 0x28, 0x7f, 0xa7, 0x97, 0xc4, 0x9e, 0xe8, 0xe8, 0x1d, 0x1f, 0xbd, 0x3b, 0xf7, 0xba, 0x30, 0x39, 0x8e, 0xb1,
|
||||
0xd6, 0x3b, 0x6b, 0x1b, 0xa5, 0x63, 0xb4, 0xad, 0x67, 0x6f, 0x82, 0xe7, 0x5f, 0xcb, 0xe4, 0xf0, 0x1a, 0x7f, 0x3b, 0x1e, 0x63,
|
||||
0x21, 0xd8, 0x39, 0xac, 0x3e, 0x4e, 0x9b, 0x59, 0x70, 0xf8, 0x19, 0x28, 0xce, 0xd1, 0x16, 0x2a, 0x6b, 0xaa, 0x68, 0x92, 0xe6,
|
||||
0xf, 0xc4, 0xdc, 0x53, 0x4d, 0x36, 0xb5, 0xdb, 0xcd, 0x70, 0x1c, 0xdf, 0xf4, 0xf6, 0xec, 0x8d, 0x67, 0xb8, 0x3d, 0x7d, 0xb2,
|
||||
0xbd, 0xa3, 0xa5, 0xd7, 0x44, 0xda, 0x86, 0xf9, 0xdb, 0x5e, 0x58, 0xec, 0xda, 0xc5, 0xdd, 0x11, 0xd3, 0x17, 0x19, 0x1c, 0xe,
|
||||
0x53, 0x40, 0xed, 0x5b, 0x8c, 0x4e, 0x5a, 0xa8, 0xe9, 0xfc, 0xa8, 0x97, 0x2f, 0x63, 0x55, 0x17, 0x55, 0xf1, 0xef, 0xcf, 0x3c,
|
||||
0x57, 0x2f, 0x3e, 0xfc, 0xf3, 0xcf, 0xe7, 0xc8, 0x62, 0x7f, 0xa8, 0x37, 0xfc, 0x46, 0xb6, 0x8f, 0xe1, 0x47, 0x56, 0x7f, 0xd2,
|
||||
0x2f, 0x1, 0xd5, 0x6f, 0x45, 0xff, 0x0, 0x33, 0xfa, 0x77, 0xca, 0x9f, 0x18, 0x72, 0x3e, 0x9b, 0x5e, 0x4b, 0xcb, 0x8a, 0xbd,
|
||||
0xd8, 0xf1, 0xba, 0x96, 0x7f, 0x47, 0xd3, 0x31, 0x5b, 0x15, 0xcc, 0x36, 0x90, 0x76, 0xb7, 0x51, 0x64, 0x6d, 0xee, 0xe6, 0xa3,
|
||||
0x5d, 0xc2, 0x5d, 0xfd, 0xc8, 0x25, 0xa3, 0x77, 0xeb, 0xab, 0x69, 0x64, 0xa2, 0xde, 0x3b, 0x7e, 0x63, 0xbc, 0x8f, 0x1b, 0x6d,
|
||||
0x6d, 0x77, 0x6d, 0x55, 0x52, 0xda, 0x5c, 0xcb, 0x10, 0x71, 0x87, 0xce, 0xff, 0x0, 0x47, 0x6f, 0x28, 0xfc, 0x41, 0xdc, 0x73,
|
||||
0x39, 0xd, 0x3f, 0x4a, 0xda, 0xfb, 0xbb, 0xa2, 0x2e, 0xb2, 0x37, 0x32, 0x6a, 0x7d, 0x91, 0xa2, 0xe0, 0xef, 0x36, 0x5c, 0xa6,
|
||||
0x2b, 0x15, 0x24, 0xde, 0xf6, 0x78, 0xfe, 0xcb, 0xd7, 0xb0, 0x36, 0x93, 0xe4, 0x35, 0x5c, 0xc5, 0xa5, 0x12, 0x47, 0xc, 0x97,
|
||||
0xb5, 0x41, 0x4e, 0x22, 0xf2, 0x5a, 0xa9, 0xfc, 0x3c, 0xfc, 0x49, 0x5d, 0x56, 0xd1, 0x7, 0x34, 0x3a, 0xef, 0xa8, 0xbb, 0x53,
|
||||
0xb7, 0x36, 0x28, 0xf5, 0x1e, 0xad, 0xeb, 0x8d, 0xe3, 0xb0, 0xf6, 0x79, 0x2e, 0xa0, 0xb2, 0xfd, 0x85, 0xa6, 0x6a, 0xf9, 0x9d,
|
||||
0x8f, 0x25, 0xd, 0xc5, 0xcd, 0x72, 0xc7, 0xd, 0x17, 0x76, 0xb8, 0xab, 0x3b, 0x99, 0x2c, 0xa9, 0xe6, 0xab, 0x79, 0x39, 0xe6,
|
||||
0xb9, 0xb8, 0xa2, 0x8a, 0x69, 0x8e, 0xba, 0xaa, 0xe7, 0x8e, 0x28, 0xab, 0x9e, 0x2, 0x72, 0x5e, 0x13, 0xf4, 0x7, 0x60, 0x78,
|
||||
0x5b, 0xe9, 0x1b, 0xd9, 0xfa, 0x3f, 0x91, 0x7c, 0x6b, 0xba, 0x6, 0xcd, 0xfd, 0x2, 0xef, 0xad, 0xcf, 0x33, 0x63, 0x79, 0xb1,
|
||||
0xe3, 0x24, 0xb6, 0xd5, 0xac, 0xf7, 0xd, 0x72, 0xfe, 0x3c, 0x3e, 0x2b, 0x61, 0xcc, 0xf1, 0x2d, 0x18, 0x3b, 0x7c, 0xdd, 0x55,
|
||||
0xf1, 0x45, 0x32, 0x45, 0x5, 0xcd, 0xc4, 0x34, 0xc9, 0x35, 0x11, 0xf1, 0x2d, 0x52, 0x73, 0x55, 0x14, 0x87, 0x25, 0xbe, 0x99,
|
||||
0x3f, 0xdf, 0xbf, 0x93, 0x7f, 0xc2, 0x4d, 0x4f, 0xf9, 0xc6, 0xa0, 0x71, 0x6b, 0xd4, 0x47, 0xfe, 0x7d, 0x7c, 0xcb, 0xff, 0x0,
|
||||
0xec, 0xe7, 0x76, 0xff, 0x0, 0xdc, 0x4d, 0x80, 0x1f, 0x19, 0xe2, 0x67, 0x95, 0x9d, 0xb1, 0xe1, 0xaf, 0x75, 0xeb, 0x1d, 0xdd,
|
||||
0xd4, 0x39, 0x6f, 0xc2, 0x66, 0x70, 0xb3, 0x53, 0x69, 0x9e, 0xc0, 0x5d, 0xd7, 0x2f, 0x3a, 0xfe, 0xf3, 0xaa, 0x5c, 0x5c, 0x5b,
|
||||
0xcb, 0x9a, 0xd3, 0x76, 0x7b, 0x48, 0xf9, 0xe3, 0x9b, 0x9c, 0x3e, 0x5e, 0x2b, 0x7a, 0x78, 0xf9, 0xd3, 0xed, 0x3d, 0xac, 0xf4,
|
||||
0x47, 0x71, 0x5, 0x71, 0xcf, 0x14, 0x72, 0x52, 0x16, 0x1f, 0xf8, 0x61, 0xe4, 0xe7, 0x45, 0xf9, 0x95, 0xd5, 0x16, 0x9e, 0x45,
|
||||
0xf4, 0xcc, 0x18, 0xfb, 0x4b, 0xed, 0xba, 0x2c, 0x6e, 0xf, 0xb1, 0xf1, 0x72, 0x45, 0x63, 0x1e, 0xe3, 0xac, 0x6d, 0x9a, 0xd5,
|
||||
0xa5, 0x5e, 0xfa, 0x7e, 0xeb, 0x25, 0xad, 0x34, 0xcb, 0x75, 0x79, 0x81, 0x87, 0x2b, 0xcd, 0x56, 0x53, 0xd7, 0xfd, 0x5d, 0xcd,
|
||||
0x85, 0xcc, 0x53, 0xc3, 0xfd, 0x54, 0xb4, 0x71, 0xc0, 0x57, 0x61, 0xd1, 0x7d, 0xf, 0xb8, 0x79, 0x3b, 0xe4, 0x9e, 0x91, 0xd0,
|
||||
0xba, 0x24, 0x5c, 0xd5, 0xb1, 0xf6, 0x67, 0x60, 0xd5, 0xaf, 0xc3, 0x79, 0xcc, 0x35, 0xdc, 0x41, 0x84, 0xc5, 0xf3, 0x79, 0x73,
|
||||
0x79, 0xb1, 0x6c, 0xd7, 0xd1, 0x47, 0xcd, 0x32, 0x57, 0x8c, 0xd5, 0xf5, 0xeb, 0x4b, 0xac, 0x8d, 0xcf, 0x14, 0xf3, 0xf2, 0xe6,
|
||||
0xde, 0xda, 0xbf, 0x8f, 0xbd, 0x5e, 0xdc, 0x72, 0x12, 0x62, 0xf5, 0xd8, 0xee, 0xfd, 0x4f, 0xc5, 0x6f, 0x15, 0xfa, 0x1b, 0xd3,
|
||||
0x87, 0xa3, 0xe7, 0x8f, 0xb, 0x8e, 0xcc, 0xea, 0xf8, 0x1b, 0xad, 0xc3, 0x1f, 0x6b, 0x73, 0x4f, 0x19, 0x3b, 0xe, 0xa3, 0xeb,
|
||||
0xf9, 0xad, 0xec, 0x75, 0x4b, 0x1c, 0xbd, 0x50, 0xf1, 0xd, 0x53, 0xdd, 0x76, 0x1e, 0xe9, 0x8d, 0x9a, 0xfa, 0xea, 0xe3, 0x9e,
|
||||
0x3e, 0x77, 0x32, 0x61, 0xae, 0x3e, 0xef, 0x1c, 0xf1, 0x71, 0x57, 0x35, 0x4, 0x46, 0x1, 0xb0, 0xbe, 0x28, 0xf9, 0x9, 0xb2,
|
||||
0xf8, 0xab, 0xe4, 0x57, 0x52, 0xf7, 0xfe, 0xab, 0xf7, 0xe5, 0xc8, 0x75, 0xbe, 0xdd, 0x61, 0x97, 0xbf, 0xc6, 0xdb, 0xcf, 0xcd,
|
||||
0xbf, 0x39, 0xfd, 0x62, 0xe3, 0x89, 0x31, 0x9b, 0x7e, 0xb1, 0x24, 0xbe, 0xfc, 0x71, 0x44, 0x3b, 0x2e, 0xaf, 0x7d, 0x77, 0x63,
|
||||
0x55, 0x5c, 0xfb, 0xf1, 0x47, 0x13, 0xfc, 0xbf, 0xb6, 0x9e, 0x1, 0x28, 0x5f, 0x5e, 0x8f, 0x1c, 0x75, 0x5f, 0x26, 0x3c, 0x62,
|
||||
0xea, 0xf, 0x50, 0xbe, 0x93, 0xe2, 0x1d, 0x86, 0x8d, 0x4f, 0x59, 0xd7, 0x78, 0xd9, 0xb2, 0xd8, 0xc8, 0x38, 0xae, 0x6d, 0x8f,
|
||||
0xa3, 0x7b, 0x2, 0xb8, 0x32, 0x7a, 0xb6, 0x7a, 0xe2, 0x98, 0x63, 0x9a, 0xe2, 0xa9, 0x74, 0x7d, 0x9b, 0x35, 0x4f, 0xce, 0x2f,
|
||||
0xd3, 0xf8, 0x7b, 0x6c, 0xc5, 0xdc, 0x93, 0x73, 0xc7, 0x16, 0xde, 0xd4, 0x87, 0xab, 0xf4, 0xc9, 0x7e, 0xe3, 0x7c, 0x9e, 0xfe,
|
||||
0x2b, 0xe9, 0xdf, 0xca, 0x17, 0x0, 0x8d, 0x2e, 0x6f, 0xc0, 0xcf, 0x39, 0x65, 0xcc, 0xe5, 0xe5, 0x8b, 0xc3, 0x1f, 0x2b, 0xe4,
|
||||
0x8a, 0x4c, 0x9d, 0xfc, 0x91, 0xc9, 0x1f, 0x8e, 0xbd, 0xbf, 0x5c, 0x72, 0x47, 0x5d, 0xd4, 0xb5, 0x51, 0x5d, 0x15, 0xd3, 0xa7,
|
||||
0xf3, 0x4d, 0x74, 0x57, 0x4f, 0x3c, 0x73, 0xc7, 0x3c, 0x73, 0xed, 0xcf, 0x0, 0xc0, 0x7d, 0x99, 0xd3, 0x5d, 0xbf, 0xd2, 0xb9,
|
||||
0x5c, 0x7e, 0xb, 0xb9, 0x3a, 0xa7, 0xb2, 0x7a, 0x97, 0x39, 0x96, 0xc7, 0xfe, 0xd6, 0xc5, 0xe1, 0xbb, 0x33, 0x46, 0xd9, 0xf4,
|
||||
0x3c, 0xae, 0x4b, 0x15, 0xf8, 0x99, 0xac, 0xff, 0x0, 0x69, 0xe3, 0xf1, 0xdb, 0x4e, 0x2f, 0x15, 0x77, 0x79, 0x8f, 0xfc, 0x5d,
|
||||
0xb4, 0x91, 0x7d, 0xe8, 0xe8, 0xaa, 0x3f, 0xb9, 0x1d, 0x54, 0xfc, 0xbe, 0x54, 0xf3, 0xc7, 0x1, 0x8d, 0x80, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xb9, 0xac, 0x6c, 0x39, 0x1d,
|
||||
0x4b, 0x64, 0xd7, 0xb6, 0xbc, 0x47, 0x30, 0xd3, 0x96, 0xd6, 0x73, 0x98, 0x9d, 0x87, 0x17, 0x55, 0xc4, 0x5f, 0x7e, 0xde, 0x9c,
|
||||
0x8e, 0x16, 0xfe, 0xdf, 0x25, 0x65, 0xcc, 0xf0, 0xfc, 0xa9, 0xfb, 0xd0, 0xf1, 0x73, 0x6d, 0x4f, 0xca, 0x9f, 0x7e, 0x3e, 0x54,
|
||||
0xfb, 0xf1, 0xee, 0xd, 0xc8, 0xf3, 0x1f, 0xd4, 0x3b, 0xc9, 0x1f, 0x3b, 0x2d, 0xba, 0xfa, 0xd7, 0xbf, 0xb2, 0xba, 0x96, 0x4a,
|
||||
0x1e, 0xb2, 0x9f, 0x67, 0xb8, 0xd5, 0xb8, 0xd6, 0x35, 0x4b, 0x3d, 0x6b, 0x98, 0x64, 0xdb, 0xa3, 0xc0, 0x47, 0x99, 0xe6, 0xf6,
|
||||
0xab, 0x59, 0xa5, 0xe6, 0xf3, 0x8a, 0xe9, 0xd6, 0xed, 0x7e, 0xdf, 0x15, 0x7b, 0x7d, 0xbf, 0x6a, 0xbd, 0xbf, 0xf7, 0x7e, 0x41,
|
||||
0xa3, 0x60, 0xf5, 0xf5, 0xfd, 0x83, 0x39, 0xa9, 0xe7, 0xb0, 0xbb, 0x46, 0xb1, 0x97, 0xc9, 0x6b, 0xfb, 0x26, 0xb9, 0x95, 0xc7,
|
||||
0xe7, 0x70, 0x19, 0xec, 0x35, 0xe5, 0xc6, 0x3b, 0x2d, 0x85, 0xcc, 0xe2, 0x6e, 0xe2, 0xbe, 0xc6, 0x65, 0x71, 0x97, 0xf6, 0xb2,
|
||||
0x45, 0x73, 0x65, 0x90, 0xc7, 0xde, 0xc1, 0x44, 0xb0, 0xcb, 0x1d, 0x54, 0xd7, 0x1c, 0x94, 0x71, 0x55, 0x3c, 0xf1, 0xcf, 0x1c,
|
||||
0x72, 0xd, 0xb6, 0xf3, 0x53, 0xcd, 0xce, 0xcc, 0xf3, 0x9f, 0x6d, 0xea, 0xfd, 0xf7, 0xb6, 0xb1, 0x58, 0x1b, 0x3d, 0xcb, 0xae,
|
||||
0xfa, 0x87, 0x3, 0xd5, 0x37, 0xf9, 0x9c, 0x15, 0x13, 0x5b, 0xd3, 0xb9, 0x57, 0x84, 0xce, 0x6c, 0x79, 0xd9, 0x76, 0xdc, 0x9d,
|
||||
0x85, 0x7c, 0xfe, 0x13, 0x1b, 0x97, 0xcb, 0x5d, 0x6c, 0x72, 0x73, 0x3c, 0x16, 0xbc, 0x51, 0x6b, 0x4d, 0x54, 0x7b, 0xc7, 0x45,
|
||||
0x14, 0xf3, 0xc5, 0x14, 0x86, 0xa6, 0x6b, 0xbb, 0x26, 0xc3, 0xa8, 0x67, 0x71, 0x5b, 0x46, 0xa5, 0x9e, 0xcd, 0x6a, 0xfb, 0x36,
|
||||
0xa, 0xf6, 0xc, 0x9e, 0xf, 0x62, 0xd7, 0x72, 0x97, 0xd8, 0x4c, 0xee, 0x1b, 0x25, 0x6b, 0x5f, 0xce, 0xdb, 0x21, 0x8a, 0xcb,
|
||||
0xe3, 0x67, 0xb6, 0xc8, 0x63, 0xaf, 0x6d, 0xeb, 0xe3, 0xde, 0x89, 0x61, 0x92, 0x89, 0x28, 0xe7, 0xf3, 0xe3, 0x9e, 0x1, 0xdc,
|
||||
0xce, 0x8e, 0xfa, 0x87, 0x7c, 0xe4, 0xea, 0xfc, 0x3e, 0x33, 0x5d, 0xec, 0x3c, 0x6f, 0x57, 0x77, 0xd6, 0x3b, 0x1d, 0x6f, 0xcd,
|
||||
0xaf, 0xed, 0xcd, 0xd7, 0x3, 0x92, 0xd7, 0xf7, 0xdb, 0x88, 0x61, 0x86, 0x58, 0xec, 0x68, 0x9f, 0x63, 0xd3, 0x32, 0xb8, 0x6c,
|
||||
0x25, 0xe5, 0x70, 0x7b, 0xc7, 0xc4, 0xb3, 0xdd, 0x62, 0x2e, 0x6f, 0x2e, 0xa9, 0x8f, 0xde, 0x59, 0xb9, 0x9a, 0xba, 0xe6, 0xa8,
|
||||
0x33, 0x2e, 0xd3, 0xf5, 0x30, 0x79, 0x3f, 0x7f, 0x87, 0x9e, 0xd7, 0x50, 0xe8, 0x2e, 0x8d, 0xd6, 0xf3, 0x52, 0xf3, 0xf0, 0x8b,
|
||||
0x2f, 0x99, 0xba, 0xde, 0x76, 0x9b, 0x6b, 0x58, 0xaa, 0xa2, 0x4a, 0x64, 0xae, 0x1c, 0x4d, 0xbe, 0x7b, 0x5a, 0xf9, 0xde, 0x53,
|
||||
0x55, 0x54, 0xd5, 0x15, 0x72, 0x4f, 0x5c, 0x54, 0xd5, 0x4f, 0xeb, 0x8a, 0x4e, 0x39, 0xf6, 0xe0, 0x38, 0xad, 0xe4, 0xff, 0x0,
|
||||
0x99, 0xde, 0x4a, 0xf9, 0x8b, 0xb4, 0xd1, 0xb4, 0xf9, 0x3, 0xda, 0x59, 0xdd, 0xd7, 0xf0, 0x53, 0x4d, 0x2e, 0xbf, 0xac, 0x51,
|
||||
0xcc, 0x38, 0x6d, 0x17, 0x53, 0xa2, 0x6f, 0x78, 0xfe, 0xde, 0xb1, 0xa5, 0xe1, 0xe2, 0xb3, 0xd7, 0xf1, 0x73, 0x7e, 0x1b, 0xe3,
|
||||
0xc, 0xb7, 0x9c, 0x41, 0x5e, 0x42, 0xf2, 0x88, 0xe8, 0xe6, 0xea, 0xe2, 0x7a, 0xf8, 0xf9, 0xf2, 0x1a, 0xba, 0x0, 0x32, 0x7f,
|
||||
0x4e, 0x77, 0x4f, 0x6a, 0x78, 0xfb, 0xd8, 0x38, 0x3e, 0xd4, 0xe9, 0x8d, 0xe3, 0x39, 0xd7, 0xbb, 0xfe, 0xb9, 0x24, 0x95, 0xe2,
|
||||
0xb6, 0x2c, 0xc, 0xf1, 0xd1, 0x3d, 0x31, 0x4f, 0x4f, 0xdb, 0xba, 0xb0, 0xbe, 0xb3, 0xba, 0x8a, 0xe7, 0x1b, 0x98, 0xc4, 0x5f,
|
||||
0xc5, 0xfa, 0x2e, 0x6c, 0xaf, 0x21, 0x9e, 0xd2, 0xe6, 0x3f, 0xd3, 0x2c, 0x75, 0xd3, 0xf9, 0x3, 0xbe, 0x5a, 0x57, 0xd4, 0xb5,
|
||||
0xe5, 0x3e, 0x23, 0x3, 0x6d, 0x8f, 0xde, 0x3a, 0x3b, 0xa4, 0x77, 0x4c, 0xe5, 0xb7, 0xc2, 0x2a, 0xb6, 0x1c, 0x6c, 0x9b, 0x96,
|
||||
0xa1, 0xcd, 0xfc, 0x11, 0xdb, 0x5b, 0xc5, 0xc4, 0xd9, 0x1c, 0x4d, 0x19, 0xbc, 0xed, 0x9f, 0x39, 0x49, 0xee, 0x28, 0x96, 0x59,
|
||||
0x64, 0xb5, 0xe6, 0xd6, 0xdb, 0x9f, 0xb9, 0xc5, 0x31, 0xdb, 0xc5, 0xc5, 0x3f, 0xa8, 0x34, 0xf3, 0xcc, 0x7f, 0x5a, 0xff, 0x0,
|
||||
0x31, 0x7c, 0xbe, 0xd5, 0x33, 0x3d, 0x69, 0x35, 0xe6, 0xb1, 0xd3, 0x1d, 0x53, 0x9f, 0xb5, 0xe7, 0x1f, 0x9f, 0xd4, 0x3a, 0xb6,
|
||||
0xdf, 0x29, 0x67, 0x94, 0xda, 0xb1, 0x72, 0x53, 0xed, 0x73, 0x8c, 0xdb, 0x37, 0x2c, 0xbe, 0x4b, 0x21, 0x9c, 0xbe, 0xc6, 0x5e,
|
||||
0x7c, 0xab, 0x8e, 0xe2, 0xd2, 0xc7, 0xf6, 0x6d, 0x95, 0xdd, 0xb5, 0x5f, 0x66, 0xe6, 0x9, 0xe9, 0xf9, 0xf3, 0x58, 0x73, 0xdb,
|
||||
0xc6, 0xcf, 0x22, 0xfb, 0x2f, 0xc5, 0x1e, 0xe3, 0xd5, 0xbb, 0xd7, 0xa8, 0x6e, 0xf1, 0x16, 0x5b, 0xfe, 0x9f, 0x6, 0x7e, 0xdf,
|
||||
0xb, 0x73, 0x9d, 0xc4, 0xc3, 0x9c, 0xc5, 0xd1, 0x1e, 0xcb, 0xae, 0xe5, 0x35, 0x7c, 0xa7, 0x17, 0x18, 0xcb, 0x8a, 0xe3, 0x8a,
|
||||
0x7e, 0x6b, 0xc5, 0x66, 0x26, 0xe2, 0x8e, 0x79, 0xe7, 0x8f, 0x85, 0x7c, 0xf1, 0x57, 0xf9, 0x3, 0xd6, 0xf2, 0x93, 0xca, 0x4e,
|
||||
0xd7, 0xf3, 0xb, 0xb5, 0xee, 0xfb, 0xa3, 0xba, 0x2e, 0xf0, 0x77, 0xdb, 0xc5, 0xf6, 0xf, 0xb, 0xaf, 0x5c, 0x5c, 0x6b, 0xd8,
|
||||
0x58, 0x70, 0x38, 0xea, 0xf1, 0xd8, 0x18, 0x65, 0x83, 0x1d, 0xc7, 0x18, 0xe8, 0x25, 0x96, 0x2a, 0x26, 0xa2, 0x29, 0x79, 0xe2,
|
||||
0xaa, 0xb8, 0xe7, 0x8f, 0x97, 0xb7, 0x1f, 0x97, 0xbf, 0xf6, 0x86, 0x0, 0xc7, 0x64, 0x72, 0x18, 0x7c, 0x85, 0x86, 0x5f, 0x11,
|
||||
0x7f, 0x79, 0x8b, 0xca, 0xe2, 0xef, 0x2d, 0x72, 0x38, 0xcc, 0x9e, 0x3a, 0xea, 0x7b, 0x1c, 0x86, 0x3b, 0x21, 0x63, 0x3c, 0x77,
|
||||
0x36, 0x57, 0xf6, 0x17, 0xb6, 0xd2, 0x45, 0x73, 0x67, 0x79, 0x67, 0x73, 0x15, 0x32, 0x45, 0x2c, 0x75, 0x53, 0x5c, 0x75, 0xd3,
|
||||
0xc5, 0x54, 0xf3, 0xc7, 0x3c, 0x71, 0xcf, 0x1, 0xdb, 0xbf, 0x1e, 0xbe, 0xa0, 0x2f, 0x3a, 0x7a, 0x5f, 0x13, 0x8e, 0xd6, 0x77,
|
||||
0x8b, 0x8d, 0x17, 0xc8, 0x4d, 0x7e, 0xc2, 0x3a, 0x2d, 0xe3, 0xbd, 0xed, 0xc, 0x56, 0x52, 0x1d, 0xf6, 0x3b, 0x48, 0x21, 0xb9,
|
||||
0xa6, 0x18, 0xa3, 0xde, 0x35, 0x8c, 0xb6, 0x1a, 0x5c, 0x94, 0xf5, 0x5c, 0x4b, 0x1d, 0x53, 0x5c, 0xe5, 0xec, 0xf2, 0xd7, 0x52,
|
||||
0xd1, 0x17, 0xc7, 0xee, 0x53, 0x55, 0x5c, 0xd7, 0xc0, 0x6c, 0x9e, 0x67, 0xea, 0x67, 0xf2, 0x42, 0x7c, 0x65, 0xdc, 0x5a, 0xff,
|
||||
0x0, 0x8e, 0x7d, 0x23, 0x8c, 0xcc, 0x57, 0x45, 0x1c, 0x58, 0xdf, 0xe6, 0x72, 0xdb, 0xe6, 0x77, 0x19, 0x6f, 0x27, 0x12, 0xd1,
|
||||
0xcc, 0x95, 0x5d, 0xe2, 0x6c, 0xb3, 0x1a, 0xed, 0xd5, 0xe5, 0x15, 0x43, 0xc5, 0x54, 0xf1, 0x4d, 0x17, 0xb0, 0x73, 0x4d, 0x5c,
|
||||
0xf1, 0x57, 0xbf, 0x3c, 0x71, 0xcd, 0x35, 0x7, 0x1f, 0x7c, 0xb3, 0xf5, 0xc, 0xf2, 0xcb, 0xcd, 0x5b, 0xc8, 0x69, 0xef, 0x4e,
|
||||
0xcf, 0xbe, 0xc8, 0xea, 0x96, 0x17, 0x3c, 0x5e, 0x61, 0xfa, 0xdb, 0x59, 0xb6, 0x8b, 0x55, 0xeb, 0x9c, 0x4d, 0xcd, 0x14, 0xf1,
|
||||
0xc4, 0x77, 0x54, 0x6b, 0x58, 0xce, 0x68, 0xa3, 0x33, 0x91, 0x83, 0x9e, 0x6b, 0xe6, 0x2b, 0xdc, 0xa4, 0xb7, 0xf7, 0xd0, 0xd3,
|
||||
0x2d, 0x74, 0x47, 0x35, 0x31, 0xd5, 0xf0, 0xe0, 0x3c, 0x9f, 0xf, 0xbc, 0xe5, 0xef, 0xbf, 0x6, 0xb6, 0x4d, 0xc7, 0x6b, 0xe8,
|
||||
0x4c, 0x86, 0xb1, 0x8e, 0xcc, 0x6f, 0x58, 0x3b, 0xd, 0x7b, 0x3d, 0x2e, 0xcd, 0xad, 0xdb, 0x6c, 0x90, 0xd7, 0x8d, 0xc7, 0x5f,
|
||||
0xd5, 0x92, 0xb7, 0x8e, 0xd6, 0xb, 0x99, 0xa2, 0xa2, 0xda, 0x5f, 0xc5, 0x55, 0xef, 0x55, 0x7c, 0x7b, 0xf3, 0xcf, 0x1c, 0x7b,
|
||||
0x7e, 0x5f, 0x9f, 0xb8, 0x6b, 0xdf, 0x6a, 0xf6, 0x56, 0xd1, 0xdc, 0x9d, 0x99, 0xbf, 0xf6, 0xd6, 0xed, 0x35, 0x9d, 0xc6, 0xe1,
|
||||
0xd9, 0x7b, 0x8e, 0xc5, 0xbd, 0x6d, 0x13, 0xe3, 0xec, 0xe8, 0xc7, 0xd8, 0x4d, 0x9f, 0xda, 0x32, 0xb7, 0x59, 0x9c, 0xac, 0x96,
|
||||
0x76, 0x31, 0x73, 0x54, 0x76, 0x76, 0xb5, 0xde, 0xde, 0x57, 0xcd, 0x11, 0x53, 0xcf, 0x3c, 0x51, 0x4f, 0xb7, 0x1f, 0xe4, 0xf,
|
||||
0x81, 0x6, 0xed, 0xf8, 0x2b, 0xe7, 0x97, 0x73, 0x78, 0xd, 0xda, 0xb7, 0x3d, 0x8b, 0xd5, 0x92, 0xda, 0xe6, 0xb0, 0xbb, 0x6,
|
||||
0x3b, 0x9c, 0x46, 0xf9, 0xd7, 0x19, 0xeb, 0x9b, 0xd8, 0xf5, 0x2d, 0xe3, 0x1f, 0xc, 0x77, 0x5c, 0xe2, 0x6b, 0xc9, 0xc5, 0x67,
|
||||
0x25, 0x13, 0x5a, 0xe5, 0xf5, 0xfb, 0xdb, 0xaa, 0xa7, 0xb0, 0xbd, 0x8b, 0xda, 0x7b, 0x7e, 0x6b, 0x96, 0x2f, 0x7a, 0xa0, 0x9e,
|
||||
0x78, 0xe4, 0xf, 0x94, 0xf1, 0x5f, 0xcc, 0xbe, 0xe1, 0xf0, 0xdf, 0xb3, 0x76, 0x1e, 0xdd, 0xe9, 0x58, 0xb4, 0xbb, 0x5d, 0xdf,
|
||||
0x62, 0xc1, 0x64, 0x75, 0xb9, 0x32, 0x7b, 0x5e, 0xab, 0x67, 0xb5, 0x7e, 0xcb, 0xc4, 0xe5, 0xb2, 0x76, 0x79, 0x5c, 0x84, 0x58,
|
||||
0x5a, 0x2f, 0xa4, 0xa3, 0xf6, 0x6d, 0xcd, 0xdc, 0xd6, 0x11, 0x47, 0x24, 0xd4, 0x73, 0xf7, 0x2a, 0x87, 0x8e, 0x63, 0xf7, 0xf8,
|
||||
0xd7, 0x5f, 0x15, 0x7, 0xc0, 0x79, 0x19, 0xe4, 0x4f, 0x6a, 0x79, 0x55, 0xdb, 0xbb, 0x3f, 0x77, 0x77, 0x2e, 0x72, 0x2c, 0xf6,
|
||||
0xf7, 0xb5, 0xd3, 0x8b, 0x86, 0xfa, 0x7b, 0x3b, 0x4a, 0x31, 0xb8, 0xab, 0x2b, 0x2c, 0x2e, 0x2e, 0xd3, 0xf, 0x8c, 0xc6, 0xe1,
|
||||
0xb1, 0x30, 0xd5, 0x55, 0xb6, 0x2f, 0x1d, 0x6b, 0x67, 0x65, 0x4f, 0xb4, 0x51, 0xfb, 0x71, 0x54, 0xb5, 0x57, 0x2d, 0x5f, 0x29,
|
||||
0x24, 0xae, 0xaa, 0x83, 0x7, 0x0, 0xe, 0x8f, 0xf5, 0x2f, 0xaa, 0xa7, 0x96, 0xfd, 0x3b, 0xe3, 0x8c, 0xfe, 0x29, 0x60, 0x73,
|
||||
0x3a, 0x2e, 0xc5, 0xd2, 0x97, 0x18, 0x3d, 0xbb, 0x57, 0x93, 0x5a, 0xde, 0xf4, 0x7c, 0x76, 0xd7, 0x2f, 0xf4, 0x67, 0x78, 0xaf,
|
||||
0x23, 0x26, 0xc1, 0xaf, 0x7e, 0x3a, 0xf6, 0x6a, 0x65, 0xab, 0x11, 0x34, 0x99, 0x7b, 0x9e, 0x62, 0x8a, 0xae, 0x2a, 0xe2, 0x1e,
|
||||
0x26, 0xe6, 0x9a, 0x3d, 0xa9, 0xe2, 0x9a, 0x69, 0xf, 0x99, 0xf0, 0xfb, 0xd4, 0x9f, 0xc9, 0xef, 0x6, 0xb5, 0xbd, 0xc7, 0x54,
|
||||
0xe8, 0x4c, 0xae, 0x9b, 0x8d, 0xc4, 0xef, 0x59, 0xcb, 0x1d, 0x87, 0x60, 0xa7, 0x66, 0xd4, 0x6c, 0xf6, 0x4b, 0x89, 0x72, 0x38,
|
||||
0xeb, 0xe, 0x71, 0xb6, 0xbc, 0xdb, 0x4d, 0x75, 0x3c, 0x5f, 0x86, 0x86, 0x8b, 0x6a, 0xb9, 0xf7, 0xa6, 0x9e, 0x3f, 0x55, 0x5c,
|
||||
0xfb, 0xf3, 0xcf, 0xf6, 0x7b, 0x6, 0xe1, 0x7f, 0xe6, 0xd, 0xf5, 0x1a, 0xff, 0x0, 0xf2, 0x8e, 0xa8, 0xff, 0x0, 0xf5, 0x66,
|
||||
0x23, 0xff, 0x0, 0xeb, 0x7, 0x3e, 0x7c, 0xbe, 0xf3, 0x5f, 0xbd, 0x3c, 0xe1, 0xdd, 0x75, 0x8d, 0xfb, 0xbe, 0x72, 0x3a, 0xe6,
|
||||
0x47, 0x61, 0xd4, 0x75, 0x7f, 0xe8, 0x7e, 0x1a, 0x4d, 0x6b, 0x5d, 0xb6, 0xd6, 0xed, 0x23, 0xc3, 0x7e, 0xd6, 0xc8, 0x66, 0xbe,
|
||||
0x13, 0xda, 0xda, 0xcb, 0x2d, 0x33, 0xdc, 0x7e, 0x3b, 0x27, 0x2f, 0x3f, 0x73, 0x9e, 0x7d, 0xfe, 0x3e, 0xdc, 0x7f, 0x90, 0x35,
|
||||
0x28, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0,
|
||||
0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff,
|
||||
0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
|
||||
0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x7, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x7, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd4, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd5, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd3, 0xaf, 0xfc,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd4, 0xaf,
|
||||
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xd5,
|
||||
0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff,
|
||||
0xd6, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
|
||||
0xff, 0xd7, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x7, 0xff, 0xd0, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x7, 0xff, 0xd1, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x7, 0xff, 0xd2, 0xaf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x7, 0xff, 0xd9
|
||||
};
|
||||
452
src/device.c
452
src/device.c
@@ -1,452 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "xioctl.h"
|
||||
#include "device.h"
|
||||
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
const v4l2_std_id standard;
|
||||
} _STANDARDS[] = {
|
||||
{"UNKNOWN", V4L2_STD_UNKNOWN},
|
||||
{"PAL", V4L2_STD_PAL},
|
||||
{"NTSC", V4L2_STD_NTSC},
|
||||
{"SECAM", V4L2_STD_SECAM},
|
||||
};
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
const unsigned format;
|
||||
} _FORMATS[] = {
|
||||
{"YUYV", V4L2_PIX_FMT_YUYV},
|
||||
{"UYVY", V4L2_PIX_FMT_UYVY},
|
||||
{"RGB565", V4L2_PIX_FMT_RGB565},
|
||||
};
|
||||
|
||||
|
||||
static int _device_open_check_cap(struct device_t *dev);
|
||||
static int _device_open_dv_timings(struct device_t *dev);
|
||||
static int _device_apply_dv_timings(struct device_t *dev);
|
||||
static int _device_open_format(struct device_t *dev);
|
||||
static void _device_open_alloc_picbufs(struct device_t *dev);
|
||||
static int _device_open_mmap(struct device_t *dev);
|
||||
static int _device_open_queue_buffers(struct device_t *dev);
|
||||
|
||||
static const char *_format_to_string_auto(char *buf, const size_t size, const unsigned format);
|
||||
static const char *_format_to_string_null(const unsigned format);
|
||||
static const char *_standard_to_string(const v4l2_std_id standard);
|
||||
|
||||
|
||||
struct device_t *device_init() {
|
||||
struct device_runtime_t *run;
|
||||
struct device_t *dev;
|
||||
|
||||
A_CALLOC(run, 1);
|
||||
run->fd = -1;
|
||||
|
||||
A_CALLOC(dev, 1);
|
||||
dev->path = "/dev/video0";
|
||||
dev->width = 640;
|
||||
dev->height = 480;
|
||||
dev->format = V4L2_PIX_FMT_YUYV;
|
||||
dev->standard = V4L2_STD_UNKNOWN;
|
||||
dev->n_buffers = max_u(sysconf(_SC_NPROCESSORS_ONLN), 1) + 1;
|
||||
dev->n_workers = dev->n_buffers;
|
||||
dev->timeout = 1;
|
||||
dev->error_timeout = 1;
|
||||
dev->run = run;
|
||||
return dev;
|
||||
}
|
||||
|
||||
void device_destroy(struct device_t *dev) {
|
||||
free(dev->run);
|
||||
free(dev);
|
||||
}
|
||||
|
||||
int device_parse_format(const char *const str) {
|
||||
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
|
||||
if (!strcasecmp(str, _FORMATS[index].name)) {
|
||||
return _FORMATS[index].format;
|
||||
}
|
||||
}
|
||||
return FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
v4l2_std_id device_parse_standard(const char *const str) {
|
||||
for (unsigned index = 1; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
|
||||
if (!strcasecmp(str, _STANDARDS[index].name)) {
|
||||
return _STANDARDS[index].standard;
|
||||
}
|
||||
}
|
||||
return STANDARD_UNKNOWN;
|
||||
}
|
||||
|
||||
int device_open(struct device_t *dev) {
|
||||
if ((dev->run->fd = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
|
||||
LOG_PERROR("Can't open device");
|
||||
goto error;
|
||||
}
|
||||
LOG_INFO("Device fd=%d opened", dev->run->fd);
|
||||
|
||||
if (_device_open_check_cap(dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_device_open_dv_timings(dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_device_open_format(dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_device_open_mmap(dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_device_open_queue_buffers(dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
_device_open_alloc_picbufs(dev);
|
||||
|
||||
dev->run->n_workers = dev->n_workers;
|
||||
|
||||
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
device_close(dev);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void device_close(struct device_t *dev) {
|
||||
if (dev->run->pictures) {
|
||||
LOG_DEBUG("Releasing picture buffers ...");
|
||||
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
|
||||
free(dev->run->pictures[index].data);
|
||||
dev->run->pictures[index].data = NULL;
|
||||
}
|
||||
free(dev->run->pictures);
|
||||
dev->run->pictures = NULL;
|
||||
}
|
||||
|
||||
if (dev->run->hw_buffers) {
|
||||
LOG_DEBUG("Unmapping HW buffers ...");
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
if (dev->run->hw_buffers[index].start != MAP_FAILED) {
|
||||
if (munmap(dev->run->hw_buffers[index].start, dev->run->hw_buffers[index].length) < 0) {
|
||||
LOG_PERROR("Can't unmap device buffer %d", index);
|
||||
}
|
||||
}
|
||||
}
|
||||
dev->run->n_buffers = 0;
|
||||
free(dev->run->hw_buffers);
|
||||
dev->run->hw_buffers = NULL;
|
||||
}
|
||||
|
||||
if (dev->run->fd >= 0) {
|
||||
LOG_DEBUG("Closing device ...");
|
||||
if (close(dev->run->fd) < 0) {
|
||||
LOG_PERROR("Can't close device fd=%d", dev->run->fd);
|
||||
} else {
|
||||
LOG_INFO("Device fd=%d closed", dev->run->fd);
|
||||
}
|
||||
dev->run->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int _device_open_check_cap(struct device_t *dev) {
|
||||
struct v4l2_capability cap;
|
||||
|
||||
MEMSET_ZERO(cap);
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYCAP) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_QUERYCAP, &cap) < 0) {
|
||||
LOG_PERROR("Can't query device (VIDIOC_QUERYCAP)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
||||
LOG_ERROR("Video capture not supported by our device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
|
||||
LOG_ERROR("Device does not support streaming IO");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dev->standard != V4L2_STD_UNKNOWN) {
|
||||
LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard));
|
||||
if (xioctl(dev->run->fd, VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
LOG_PERROR("Can't set video standard");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
LOG_INFO("Using TV standard: DEFAULT");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _device_open_dv_timings(struct device_t *dev) {
|
||||
if (dev->dv_timings) {
|
||||
LOG_DEBUG("Using DV-timings");
|
||||
|
||||
if (_device_apply_dv_timings(dev) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct v4l2_event_subscription sub;
|
||||
|
||||
MEMSET_ZERO(sub);
|
||||
sub.type = V4L2_EVENT_SOURCE_CHANGE;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_SUBSCRIBE_EVENT) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
|
||||
LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE");
|
||||
return -1;
|
||||
}
|
||||
|
||||
} else {
|
||||
dev->run->width = dev->width;
|
||||
dev->run->height = dev->height;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _device_apply_dv_timings(struct device_t *dev) {
|
||||
struct v4l2_dv_timings dv_timings;
|
||||
|
||||
MEMSET_ZERO(dv_timings);
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) == 0) {
|
||||
LOG_INFO(
|
||||
"Got new DV timings: resolution=%dx%d; pixclk=%llu\n",
|
||||
dv_timings.bt.width,
|
||||
dv_timings.bt.height,
|
||||
dv_timings.bt.pixelclock
|
||||
);
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv_timings) < 0) {
|
||||
LOG_PERROR("Failed to set DV timings");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dev->run->width = dv_timings.bt.width;
|
||||
dev->run->height = dv_timings.bt.height;
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYSTD) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_QUERYSTD, &dev->standard) == 0) {
|
||||
LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard));
|
||||
if (xioctl(dev->run->fd, VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
LOG_PERROR("Can't set video standard");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _device_open_format(struct device_t *dev) {
|
||||
struct v4l2_format fmt;
|
||||
|
||||
MEMSET_ZERO(fmt);
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = dev->run->width;
|
||||
fmt.fmt.pix.height = dev->run->height;
|
||||
fmt.fmt.pix.pixelformat = dev->format;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_ANY;
|
||||
|
||||
// Set format
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
char format_str[8];
|
||||
|
||||
LOG_PERROR(
|
||||
"Unable to set format=%s; resolution=%dx%d",
|
||||
_format_to_string_auto(format_str, 8, dev->format),
|
||||
dev->run->width,
|
||||
dev->run->height
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check resolution
|
||||
if (fmt.fmt.pix.width != dev->run->width || fmt.fmt.pix.height != dev->run->height) {
|
||||
LOG_ERROR("Requested resolution=%dx%d is unavailable", dev->run->width, dev->run->height);
|
||||
}
|
||||
dev->run->width = fmt.fmt.pix.width;
|
||||
dev->run->height = fmt.fmt.pix.height;
|
||||
LOG_INFO("Using resolution: %dx%d", dev->run->width, dev->run->height);
|
||||
|
||||
// Check format
|
||||
if (fmt.fmt.pix.pixelformat != dev->format) {
|
||||
char format_requested_str[8];
|
||||
char format_obtained_str[8];
|
||||
char *format_str_nullable;
|
||||
|
||||
LOG_ERROR(
|
||||
"Could not obtain the requested pixelformat=%s; driver gave us %s",
|
||||
_format_to_string_auto(format_requested_str, 8, dev->format),
|
||||
_format_to_string_auto(format_obtained_str, 8, fmt.fmt.pix.pixelformat)
|
||||
);
|
||||
|
||||
if ((format_str_nullable = (char *)_format_to_string_null(fmt.fmt.pix.pixelformat)) != NULL) {
|
||||
LOG_INFO(
|
||||
"Falling back to %s mode (consider using '--format=%s' option)",
|
||||
format_str_nullable,
|
||||
format_str_nullable
|
||||
);
|
||||
} else {
|
||||
LOG_ERROR("Unsupported pixel format");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
dev->run->format = fmt.fmt.pix.pixelformat;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _device_open_mmap(struct device_t *dev) {
|
||||
struct v4l2_requestbuffers req;
|
||||
|
||||
MEMSET_ZERO(req);
|
||||
req.count = dev->n_buffers;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req)) {
|
||||
LOG_PERROR("Device '%s' doesn't support memory mapping", dev->path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (req.count < 1) {
|
||||
LOG_ERROR("Insufficient buffer memory: %d", req.count);
|
||||
return -1;
|
||||
} else {
|
||||
LOG_INFO("Requested %d HW buffers, got %d", dev->n_buffers, req.count);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Allocating HW buffers ...");
|
||||
|
||||
A_CALLOC(dev->run->hw_buffers, req.count);
|
||||
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
|
||||
struct v4l2_buffer buf_info;
|
||||
|
||||
MEMSET_ZERO(buf_info);
|
||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||
buf_info.index = dev->run->n_buffers;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer %d ...", dev->run->n_buffers);
|
||||
if (xioctl(dev->run->fd, VIDIOC_QUERYBUF, &buf_info) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QUERYBUF");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Mapping device buffer %d ...", dev->run->n_buffers);
|
||||
dev->run->hw_buffers[dev->run->n_buffers].length = buf_info.length;
|
||||
dev->run->hw_buffers[dev->run->n_buffers].start = mmap(NULL, buf_info.length, PROT_READ|PROT_WRITE, MAP_SHARED, dev->run->fd, buf_info.m.offset);
|
||||
if (dev->run->hw_buffers[dev->run->n_buffers].start == MAP_FAILED) {
|
||||
LOG_PERROR("Can't map device buffer %d", dev->run->n_buffers);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _device_open_queue_buffers(struct device_t *dev) {
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
struct v4l2_buffer buf_info;
|
||||
|
||||
MEMSET_ZERO(buf_info);
|
||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||
buf_info.index = index;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %d ...", index);
|
||||
if (xioctl(dev->run->fd, VIDIOC_QBUF, &buf_info) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QBUF");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _device_open_alloc_picbufs(struct device_t *dev) {
|
||||
LOG_DEBUG("Allocating picture buffers ...");
|
||||
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
|
||||
|
||||
dev->run->max_picture_size = ((dev->run->width * dev->run->height) << 1) * 2;
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
LOG_DEBUG("Allocating picture buffer %d sized %lu bytes... ", index, dev->run->max_picture_size);
|
||||
A_CALLOC(dev->run->pictures[index].data, dev->run->max_picture_size);
|
||||
dev->run->pictures[index].allocated = dev->run->max_picture_size;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *_format_to_string_auto(char *buf, const size_t size, const unsigned format) {
|
||||
assert(size >= 8);
|
||||
buf[0] = format & 0x7f;
|
||||
buf[1] = (format >> 8) & 0x7f;
|
||||
buf[2] = (format >> 16) & 0x7f;
|
||||
buf[3] = (format >> 24) & 0x7f;
|
||||
if (format & (1 << 31)) {
|
||||
buf[4] = '-';
|
||||
buf[5] = 'B';
|
||||
buf[6] = 'E';
|
||||
buf[7] = '\0';
|
||||
} else {
|
||||
buf[4] = '\0';
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *_format_to_string_null(const unsigned format) {
|
||||
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
|
||||
if (format == _FORMATS[index].format) {
|
||||
return _FORMATS[index].name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *_standard_to_string(v4l2_std_id standard) {
|
||||
for (unsigned index = 0; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
|
||||
if (standard == _STANDARDS[index].standard) {
|
||||
return _STANDARDS[index].name;
|
||||
}
|
||||
}
|
||||
return _STANDARDS[0].name;
|
||||
}
|
||||
77
src/dump/file.c
Normal file
77
src/dump/file.c
Normal file
@@ -0,0 +1,77 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "file.h"
|
||||
|
||||
|
||||
us_output_file_s *us_output_file_init(const char *path, bool json) {
|
||||
us_output_file_s *output;
|
||||
US_CALLOC(output, 1);
|
||||
|
||||
if (!strcmp(path, "-")) {
|
||||
US_LOG_INFO("Using output: <stdout>");
|
||||
output->fp = stdout;
|
||||
} else {
|
||||
US_LOG_INFO("Using output: %s", path);
|
||||
if ((output->fp = fopen(path, "wb")) == NULL) {
|
||||
US_LOG_PERROR("Can't open output file");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
output->json = json;
|
||||
return output;
|
||||
|
||||
error:
|
||||
us_output_file_destroy(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void us_output_file_write(void *v_output, const us_frame_s *frame) {
|
||||
us_output_file_s *output = (us_output_file_s *)v_output;
|
||||
if (output->json) {
|
||||
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
|
||||
fprintf(output->fp,
|
||||
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
|
||||
" \"format\": %u, \"stride\": %u, \"online\": %u,"
|
||||
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
|
||||
" \"data\": \"%s\"}\n",
|
||||
frame->used, frame->width, frame->height,
|
||||
frame->format, frame->stride, frame->online,
|
||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
|
||||
output->base64_data);
|
||||
} else {
|
||||
fwrite(frame->data, 1, frame->used, output->fp);
|
||||
}
|
||||
fflush(output->fp);
|
||||
}
|
||||
|
||||
void us_output_file_destroy(void *v_output) {
|
||||
us_output_file_s *output = (us_output_file_s *)v_output;
|
||||
US_DELETE(output->base64_data, free);
|
||||
if (output->fp && output->fp != stdout) {
|
||||
if (fclose(output->fp) < 0) {
|
||||
US_LOG_PERROR("Can't close output file");
|
||||
}
|
||||
}
|
||||
free(output);
|
||||
}
|
||||
49
src/dump/file.h
Normal file
49
src/dump/file.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/base64.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
bool json;
|
||||
|
||||
FILE *fp;
|
||||
char *base64_data;
|
||||
size_t base64_allocated;
|
||||
} us_output_file_s;
|
||||
|
||||
|
||||
us_output_file_s *us_output_file_init(const char *path, bool json);
|
||||
void us_output_file_write(void *v_output, const us_frame_s *frame);
|
||||
void us_output_file_destroy(void *v_output);
|
||||
350
src/dump/main.c
Normal file
350
src/dump/main.c
Normal file
@@ -0,0 +1,350 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <limits.h>
|
||||
#include <float.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/options.h"
|
||||
|
||||
#include "file.h"
|
||||
|
||||
|
||||
enum _OPT_VALUES {
|
||||
_O_SINK = 's',
|
||||
_O_SINK_TIMEOUT = 't',
|
||||
_O_OUTPUT = 'o',
|
||||
_O_OUTPUT_JSON = 'j',
|
||||
_O_COUNT = 'c',
|
||||
_O_INTERVAL = 'i',
|
||||
_O_KEY = 'k',
|
||||
|
||||
_O_HELP = 'h',
|
||||
_O_VERSION = 'v',
|
||||
|
||||
_O_LOG_LEVEL = 10000,
|
||||
_O_PERF,
|
||||
_O_VERBOSE,
|
||||
_O_DEBUG,
|
||||
_O_FORCE_LOG_COLORS,
|
||||
_O_NO_LOG_COLORS,
|
||||
};
|
||||
|
||||
static const struct option _LONG_OPTS[] = {
|
||||
{"sink", required_argument, NULL, _O_SINK},
|
||||
{"sink-timeout", required_argument, NULL, _O_SINK_TIMEOUT},
|
||||
{"output", required_argument, NULL, _O_OUTPUT},
|
||||
{"output-json", no_argument, NULL, _O_OUTPUT_JSON},
|
||||
{"count", required_argument, NULL, _O_COUNT},
|
||||
{"interval", required_argument, NULL, _O_INTERVAL},
|
||||
{"key", no_argument, NULL, _O_KEY},
|
||||
|
||||
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
|
||||
{"perf", no_argument, NULL, _O_PERF},
|
||||
{"verbose", no_argument, NULL, _O_VERBOSE},
|
||||
{"debug", no_argument, NULL, _O_DEBUG},
|
||||
{"force-log-colors", no_argument, NULL, _O_FORCE_LOG_COLORS},
|
||||
{"no-log-colors", no_argument, NULL, _O_NO_LOG_COLORS},
|
||||
|
||||
{"help", no_argument, NULL, _O_HELP},
|
||||
{"version", no_argument, NULL, _O_VERSION},
|
||||
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
|
||||
|
||||
volatile bool _g_stop = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
void *v_output;
|
||||
void (*write)(void *v_output, const us_frame_s *frame);
|
||||
void (*destroy)(void *v_output);
|
||||
} _output_context_s;
|
||||
|
||||
|
||||
static void _signal_handler(int signum);
|
||||
static void _install_signal_handlers(void);
|
||||
|
||||
static int _dump_sink(
|
||||
const char *sink_name, unsigned sink_timeout,
|
||||
long long count, long double interval,
|
||||
bool key_required,
|
||||
_output_context_s *ctx);
|
||||
|
||||
static void _help(FILE *fp);
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
US_LOGGING_INIT;
|
||||
US_THREAD_RENAME("main");
|
||||
|
||||
char *sink_name = NULL;
|
||||
unsigned sink_timeout = 1;
|
||||
char *output_path = NULL;
|
||||
bool output_json = false;
|
||||
long long count = 0;
|
||||
long double interval = 0;
|
||||
bool key_required = false;
|
||||
|
||||
# define OPT_SET(_dest, _value) { \
|
||||
_dest = _value; \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
|
||||
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
|
||||
if (errno || *_end || _tmp < _min || _tmp > _max) { \
|
||||
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
|
||||
return 1; \
|
||||
} \
|
||||
_dest = _tmp; \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_LDOUBLE(_name, _dest, _min, _max) { \
|
||||
errno = 0; char *_end = NULL; long double _tmp = strtold(optarg, &_end); \
|
||||
if (errno || *_end || _tmp < _min || _tmp > _max) { \
|
||||
printf("Invalid value for '%s=%s': min=%Lf, max=%Lf\n", _name, optarg, (long double)_min, (long double)_max); \
|
||||
return 1; \
|
||||
} \
|
||||
_dest = _tmp; \
|
||||
break; \
|
||||
}
|
||||
|
||||
char short_opts[128];
|
||||
us_build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
|
||||
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
case _O_SINK: OPT_SET(sink_name, optarg);
|
||||
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
|
||||
case _O_OUTPUT: OPT_SET(output_path, optarg);
|
||||
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
|
||||
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
|
||||
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
|
||||
case _O_KEY: OPT_SET(key_required, true);
|
||||
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(us_g_log_level, US_LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(us_g_log_level, US_LOG_LEVEL_DEBUG);
|
||||
case _O_FORCE_LOG_COLORS: OPT_SET(us_g_log_colored, true);
|
||||
case _O_NO_LOG_COLORS: OPT_SET(us_g_log_colored, false);
|
||||
|
||||
case _O_HELP: _help(stdout); return 0;
|
||||
case _O_VERSION: puts(US_VERSION); return 0;
|
||||
|
||||
case 0: break;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
# undef OPT_LDOUBLE
|
||||
# undef OPT_NUMBER
|
||||
# undef OPT_SET
|
||||
|
||||
if (sink_name == NULL || sink_name[0] == '\0') {
|
||||
puts("Missing option --sink. See --help for details.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
_output_context_s ctx = {0};
|
||||
|
||||
if (output_path && output_path[0] != '\0') {
|
||||
if ((ctx.v_output = (void *)us_output_file_init(output_path, output_json)) == NULL) {
|
||||
return 1;
|
||||
}
|
||||
ctx.write = us_output_file_write;
|
||||
ctx.destroy = us_output_file_destroy;
|
||||
}
|
||||
|
||||
_install_signal_handlers();
|
||||
const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, key_required, &ctx));
|
||||
if (ctx.v_output && ctx.destroy) {
|
||||
ctx.destroy(ctx.v_output);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static void _signal_handler(int signum) {
|
||||
char *const name = us_signum_to_string(signum);
|
||||
US_LOG_INFO_NOLOCK("===== Stopping by %s =====", name);
|
||||
free(name);
|
||||
_g_stop = true;
|
||||
}
|
||||
|
||||
static void _install_signal_handlers(void) {
|
||||
struct sigaction sig_act = {0};
|
||||
|
||||
assert(!sigemptyset(&sig_act.sa_mask));
|
||||
sig_act.sa_handler = _signal_handler;
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
|
||||
|
||||
US_LOG_DEBUG("Installing SIGINT handler ...");
|
||||
assert(!sigaction(SIGINT, &sig_act, NULL));
|
||||
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
}
|
||||
|
||||
static int _dump_sink(
|
||||
const char *sink_name, unsigned sink_timeout,
|
||||
long long count, long double interval,
|
||||
bool key_required,
|
||||
_output_context_s *ctx) {
|
||||
|
||||
if (count == 0) {
|
||||
count = -1;
|
||||
}
|
||||
|
||||
const useconds_t interval_us = interval * 1000000;
|
||||
|
||||
us_frame_s *frame = us_frame_init();
|
||||
us_memsink_s *sink = NULL;
|
||||
|
||||
if ((sink = us_memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
unsigned fps = 0;
|
||||
unsigned fps_accum = 0;
|
||||
long long fps_second = 0;
|
||||
|
||||
long double last_ts = 0;
|
||||
|
||||
while (!_g_stop) {
|
||||
const int error = us_memsink_client_get(sink, frame, key_required);
|
||||
if (error == 0) {
|
||||
key_required = false;
|
||||
|
||||
const long double now = us_get_now_monotonic();
|
||||
const long long now_second = us_floor_ms(now);
|
||||
|
||||
char fourcc_str[8];
|
||||
US_LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
|
||||
frame->used, frame->width, frame->height,
|
||||
us_fourcc_to_string(frame->format, fourcc_str, 8),
|
||||
frame->stride, frame->online, frame->key,
|
||||
now - frame->grab_ts, (last_ts ? now - last_ts : 0));
|
||||
last_ts = now;
|
||||
|
||||
US_LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
|
||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
|
||||
|
||||
if (now_second != fps_second) {
|
||||
fps = fps_accum;
|
||||
fps_accum = 0;
|
||||
fps_second = now_second;
|
||||
US_LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
|
||||
}
|
||||
fps_accum += 1;
|
||||
|
||||
if (ctx->v_output != NULL) {
|
||||
ctx->write(ctx->v_output, frame);
|
||||
}
|
||||
|
||||
if (count >= 0) {
|
||||
--count;
|
||||
if (count <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (interval_us > 0) {
|
||||
usleep(interval_us);
|
||||
}
|
||||
} else if (error == -2) {
|
||||
usleep(1000);
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
int retval = 0;
|
||||
goto ok;
|
||||
|
||||
error:
|
||||
retval = -1;
|
||||
|
||||
ok:
|
||||
US_DELETE(sink, us_memsink_destroy);
|
||||
us_frame_destroy(frame);
|
||||
|
||||
US_LOG_INFO("Bye-bye");
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _help(FILE *fp) {
|
||||
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
|
||||
SAY("═════════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", US_VERSION);
|
||||
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Example:");
|
||||
SAY("════════");
|
||||
SAY(" ustreamer-dump --sink test --output - \\");
|
||||
SAY(" | ffmpeg -use_wallclock_as_timestamps 1 -i pipe: -c:v libx264 test.mp4\n");
|
||||
SAY("Sink options:");
|
||||
SAY("═════════════");
|
||||
SAY(" -s|--sink <name> ──────── Memory sink ID. No default.\n");
|
||||
SAY(" -t|--sink-timeout <sec> ─ Timeout for the upcoming frame. Default: 1.\n");
|
||||
SAY(" -o|--output <filename> ─── Filename to dump output to. Use '-' for stdout. Default: just consume the sink.\n");
|
||||
SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n");
|
||||
SAY(" -c|--count <N> ───────── Limit the number of frames. Default: 0 (infinite).\n");
|
||||
SAY(" -i|--interval <sec> ───── Delay between reading frames (float). Default: 0.\n");
|
||||
SAY(" -k|--key ──────────────── Request keyframe from the sink. Default: disabled.\n");
|
||||
SAY("Logging options:");
|
||||
SAY("════════════════");
|
||||
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||
SAY(" Enabling debugging messages can slow down the program.");
|
||||
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
|
||||
SAY(" Default: %d.\n", us_g_log_level);
|
||||
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
|
||||
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
|
||||
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");
|
||||
SAY(" --force-log-colors ─ Force color logging. Default: colored if stderr is a TTY.\n");
|
||||
SAY(" --no-log-colors ──── Disable color logging. Default: ditto.\n");
|
||||
SAY("Help options:");
|
||||
SAY("═════════════");
|
||||
SAY(" -h|--help ─────── Print this text and exit.\n");
|
||||
SAY(" -v|--version ──── Print version and exit.\n");
|
||||
# undef SAY
|
||||
}
|
||||
155
src/encoder.c
155
src/encoder.c
@@ -1,155 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
|
||||
#include "jpeg/encoder.h"
|
||||
|
||||
#ifdef OMX_ENCODER
|
||||
# include "omx/encoder.h"
|
||||
#endif
|
||||
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
const enum encoder_type_t type;
|
||||
} _ENCODER_TYPES[] = {
|
||||
{"CPU", ENCODER_TYPE_CPU},
|
||||
# ifdef OMX_ENCODER
|
||||
{"OMX", ENCODER_TYPE_OMX},
|
||||
# endif
|
||||
};
|
||||
|
||||
|
||||
struct encoder_t *encoder_init() {
|
||||
struct encoder_t *encoder;
|
||||
|
||||
A_CALLOC(encoder, 1);
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
encoder->quality = 80;
|
||||
return encoder;
|
||||
}
|
||||
|
||||
void encoder_prepare(struct encoder_t *encoder) {
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
if (encoder->type != ENCODER_TYPE_CPU) {
|
||||
LOG_DEBUG("Initializing encoder ...");
|
||||
}
|
||||
|
||||
LOG_INFO("Using JPEG quality: %d%%", encoder->quality);
|
||||
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if ((encoder->omx = omx_encoder_init()) == NULL) {
|
||||
goto use_fallback;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
return;
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
use_fallback:
|
||||
LOG_ERROR("Can't initialize selected encoder, using CPU instead it");
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
void encoder_destroy(struct encoder_t *encoder) {
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->omx) {
|
||||
omx_encoder_destroy(encoder->omx);
|
||||
}
|
||||
# endif
|
||||
free(encoder);
|
||||
}
|
||||
|
||||
enum encoder_type_t encoder_parse_type(const char *const str) {
|
||||
for (unsigned index = 0; index < sizeof(_ENCODER_TYPES) / sizeof(_ENCODER_TYPES[0]); ++index) {
|
||||
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
|
||||
return _ENCODER_TYPES[index].type;
|
||||
}
|
||||
}
|
||||
return ENCODER_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic push
|
||||
void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev) {
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if (omx_encoder_prepare_for_device(encoder->omx, dev, encoder->quality, encoder->omx_use_ijg) < 0) {
|
||||
goto use_fallback;
|
||||
}
|
||||
if (dev->run->n_workers > 1) {
|
||||
LOG_INFO("OMX encoder can only work with one worker thread; forcing n_workers to 1");
|
||||
dev->run->n_workers = 1;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
return;
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
use_fallback:
|
||||
LOG_ERROR("Can't prepare selected encoder, falling back to CPU");
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
dev->run->n_workers = dev->n_workers;
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, const unsigned index) {
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
if (encoder->type == ENCODER_TYPE_CPU) {
|
||||
jpeg_encoder_compress_buffer(dev, index, encoder->quality);
|
||||
}
|
||||
# ifdef OMX_ENCODER
|
||||
else if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if (omx_encoder_compress_buffer(encoder->omx, dev, index) < 0) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
return 0;
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
error:
|
||||
LOG_INFO("HW compressing error, falling back to CPU");
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
dev->run->n_workers = dev->n_workers;
|
||||
return -1;
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
460
src/http.c
460
src/http.c
@@ -1,460 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/thread.h>
|
||||
#include <event2/http.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#ifndef EVTHREAD_USE_PTHREADS_IMPLEMENTED
|
||||
# error Required libevent-pthreads support
|
||||
#endif
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "stream.h"
|
||||
#include "http.h"
|
||||
|
||||
#include "data/blank.h"
|
||||
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, void *arg);
|
||||
static void _http_callback_ping(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
|
||||
|
||||
static void _http_callback_stream(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx);
|
||||
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
|
||||
|
||||
static void _http_exposed_refresh(int fd, short event, void *v_server);
|
||||
static void _http_queue_send_stream(struct http_server_t *server, const bool updated);
|
||||
|
||||
static bool _expose_new_picture(struct http_server_t *server);
|
||||
static bool _expose_blank_picture(struct http_server_t *server);
|
||||
|
||||
|
||||
struct http_server_t *http_server_init(struct stream_t *stream) {
|
||||
struct http_server_runtime_t *run;
|
||||
struct http_server_t *server;
|
||||
struct exposed_t *exposed;
|
||||
struct timeval refresh_interval;
|
||||
|
||||
A_CALLOC(exposed, 1);
|
||||
|
||||
A_CALLOC(run, 1);
|
||||
run->stream = stream;
|
||||
run->exposed = exposed;
|
||||
run->drop_same_frames_blank = 10;
|
||||
|
||||
A_CALLOC(server, 1);
|
||||
server->host = "localhost";
|
||||
server->port = 8080;
|
||||
server->timeout = 10;
|
||||
server->run = run;
|
||||
|
||||
_expose_blank_picture(server);
|
||||
|
||||
assert(!evthread_use_pthreads());
|
||||
assert((run->base = event_base_new()));
|
||||
assert((run->http = evhttp_new(run->base)));
|
||||
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
|
||||
|
||||
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, NULL));
|
||||
assert(!evhttp_set_cb(run->http, "/ping", _http_callback_ping, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)exposed));
|
||||
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
|
||||
|
||||
refresh_interval.tv_sec = 0;
|
||||
refresh_interval.tv_usec = 30000; // ~30 refreshes per second
|
||||
assert((run->refresh = event_new(run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
|
||||
assert(!event_add(run->refresh, &refresh_interval));
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void http_server_destroy(struct http_server_t *server) {
|
||||
event_del(server->run->refresh);
|
||||
event_free(server->run->refresh);
|
||||
evhttp_free(server->run->http);
|
||||
event_base_free(server->run->base);
|
||||
libevent_global_shutdown();
|
||||
|
||||
free(server->run->exposed->picture.data);
|
||||
free(server->run->exposed);
|
||||
free(server->run);
|
||||
free(server);
|
||||
}
|
||||
|
||||
int http_server_listen(struct http_server_t *server) {
|
||||
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
|
||||
|
||||
LOG_DEBUG("Binding HTTP to [%s]:%d ...", server->host, server->port);
|
||||
evhttp_set_timeout(server->run->http, server->timeout);
|
||||
|
||||
if (evhttp_bind_socket(server->run->http, server->host, server->port) != 0) {
|
||||
LOG_PERROR("Can't listen HTTP on [%s]:%d", server->host, server->port)
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_INFO("Listening HTTP on [%s]:%d", server->host, server->port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_server_loop(struct http_server_t *server) {
|
||||
LOG_INFO("Starting HTTP eventloop ...");
|
||||
event_base_dispatch(server->run->base);
|
||||
LOG_INFO("HTTP eventloop stopped");
|
||||
}
|
||||
|
||||
void http_server_loop_break(struct http_server_t *server) {
|
||||
event_base_loopbreak(server->run->base);
|
||||
}
|
||||
|
||||
|
||||
#define ADD_HEADER(_key, _value) \
|
||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
||||
|
||||
#define PROCESS_HEAD_REQUEST { \
|
||||
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", NULL); \
|
||||
return; \
|
||||
} \
|
||||
}
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg) {
|
||||
struct evbuffer *buf;
|
||||
|
||||
PROCESS_HEAD_REQUEST;
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"<!DOCTYPE html><html><head><meta charset=\"utf-8\">"
|
||||
"<title>uStreamer</title></head><body><ul>"
|
||||
"<li><a href=\"/ping\">/ping</a></li>"
|
||||
"<li><a href=\"/snapshot\">/snapshot</a></li>"
|
||||
"<li><a href=\"/stream\">/stream</a></li>"
|
||||
"</body></html>"
|
||||
));
|
||||
ADD_HEADER("Content-Type", "text/html");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_ping(struct evhttp_request *request, void *v_server) {
|
||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||
struct evbuffer *buf;
|
||||
|
||||
PROCESS_HEAD_REQUEST;
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"{\"stream\": {\"resolution\":"
|
||||
" {\"width\": %u, \"height\": %u},"
|
||||
" \"fps\": %u, \"online\": %s}}",
|
||||
(server->fake_width ? server->fake_width : server->run->exposed->width),
|
||||
(server->fake_height ? server->fake_height : server->run->exposed->height),
|
||||
server->run->exposed->fps, (server->run->exposed->online ? "true" : "false")
|
||||
));
|
||||
ADD_HEADER("Content-Type", "application/json");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_exposed) {
|
||||
struct exposed_t *exposed = (struct exposed_t *)v_exposed;
|
||||
struct evbuffer *buf;
|
||||
char x_timestamp_buf[64];
|
||||
|
||||
PROCESS_HEAD_REQUEST;
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(!evbuffer_add(buf, (const void *)exposed->picture.data, exposed->picture.size));
|
||||
|
||||
ADD_HEADER("Access-Control-Allow-Origin:", "*");
|
||||
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0");
|
||||
ADD_HEADER("Pragma", "no-cache");
|
||||
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
||||
sprintf(x_timestamp_buf, "%.06Lf", now_real_ms());
|
||||
ADD_HEADER("X-Timestamp", x_timestamp_buf);
|
||||
ADD_HEADER("Content-Type", "image/jpeg");
|
||||
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
#undef ADD_HEADER
|
||||
|
||||
static void _http_callback_stream(struct evhttp_request *request, void *v_server) {
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2814
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2789
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L362
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L791
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L1458
|
||||
|
||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||
struct evhttp_connection *conn;
|
||||
struct bufferevent *buf_event;
|
||||
struct stream_client_t *client;
|
||||
char *client_addr;
|
||||
unsigned short client_port;
|
||||
|
||||
PROCESS_HEAD_REQUEST;
|
||||
|
||||
conn = evhttp_request_get_connection(request);
|
||||
if (conn != NULL) {
|
||||
A_CALLOC(client, 1);
|
||||
client->server = server;
|
||||
client->request = request;
|
||||
client->need_initial = true;
|
||||
client->need_first_frame = true;
|
||||
|
||||
if (server->run->stream_clients == NULL) {
|
||||
server->run->stream_clients = client;
|
||||
} else {
|
||||
struct stream_client_t *last = server->run->stream_clients;
|
||||
|
||||
for (; last->next != NULL; last = last->next);
|
||||
client->prev = last;
|
||||
last->next = client;
|
||||
}
|
||||
server->run->stream_clients_count += 1;
|
||||
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
LOG_INFO(
|
||||
"HTTP: Registered the new stream client: [%s]:%u; clients now: %u",
|
||||
client_addr, client_port, server->run->stream_clients_count
|
||||
);
|
||||
|
||||
buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||
bufferevent_enable(buf_event, EV_READ);
|
||||
} else {
|
||||
evhttp_request_free(request);
|
||||
}
|
||||
}
|
||||
|
||||
#undef PROCESS_HEAD_REQUEST
|
||||
|
||||
#define BOUNDARY "boundarydonotcross"
|
||||
#define RN "\r\n"
|
||||
|
||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
||||
struct stream_client_t *client = (struct stream_client_t *)v_client;
|
||||
struct evbuffer *buf;
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
|
||||
if (client->need_initial) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"HTTP/1.0 200 OK" RN
|
||||
"Access-Control-Allow-Origin: *" RN
|
||||
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||
"Pragma: no-cache" RN
|
||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
||||
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
||||
RN
|
||||
"--" BOUNDARY RN
|
||||
));
|
||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||
client->need_initial = false;
|
||||
}
|
||||
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"Content-Type: image/jpeg" RN
|
||||
"Content-Length: %lu" RN
|
||||
"X-Timestamp: %.06Lf" RN
|
||||
RN,
|
||||
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data),
|
||||
now_real_ms()
|
||||
));
|
||||
assert(!evbuffer_add(buf,
|
||||
(void *)client->server->run->exposed->picture.data,
|
||||
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data)
|
||||
));
|
||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||
|
||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||
evbuffer_free(buf);
|
||||
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||
bufferevent_enable(buf_event, EV_READ);
|
||||
}
|
||||
|
||||
#undef BOUNDARY
|
||||
#undef RN
|
||||
|
||||
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
|
||||
struct stream_client_t *client = (struct stream_client_t *)v_client;
|
||||
struct evhttp_connection *conn;
|
||||
char *client_addr = "???";
|
||||
unsigned short client_port = 0;
|
||||
|
||||
client->server->run->stream_clients_count -= 1;
|
||||
|
||||
conn = evhttp_request_get_connection(client->request);
|
||||
if (conn != NULL) {
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
}
|
||||
LOG_INFO(
|
||||
"HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
|
||||
client_addr, client_port, client->server->run->stream_clients_count
|
||||
);
|
||||
if (conn != NULL) {
|
||||
evhttp_connection_free(conn);
|
||||
}
|
||||
|
||||
if (client->prev == NULL) {
|
||||
client->server->run->stream_clients = client->next;
|
||||
} else {
|
||||
client->prev->next = client->next;
|
||||
}
|
||||
if (client->next != NULL) {
|
||||
client->next->prev = client->prev;
|
||||
}
|
||||
free(client);
|
||||
}
|
||||
|
||||
static void _http_queue_send_stream(struct http_server_t *server, const bool updated) {
|
||||
struct stream_client_t *client;
|
||||
struct evhttp_connection *conn;
|
||||
struct bufferevent *buf_event;
|
||||
|
||||
for (client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||
conn = evhttp_request_get_connection(client->request);
|
||||
if (conn != NULL && (updated || client->need_first_frame)) {
|
||||
buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
bufferevent_setcb(buf_event, NULL, _http_callback_stream_write, _http_callback_stream_error, (void *)client);
|
||||
bufferevent_enable(buf_event, EV_READ|EV_WRITE);
|
||||
client->need_first_frame = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||
bool updated = false;
|
||||
bool queue_send = false;
|
||||
|
||||
#define LOCK_STREAM \
|
||||
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
|
||||
|
||||
#define UNLOCK_STREAM \
|
||||
{ server->run->stream->updated = false; A_PTHREAD_M_UNLOCK(&server->run->stream->mutex); }
|
||||
|
||||
if (server->run->stream->updated) {
|
||||
LOG_DEBUG("Refreshing HTTP exposed ...");
|
||||
LOCK_STREAM;
|
||||
if (server->run->stream->picture.size > 0) { // If online
|
||||
updated = _expose_new_picture(server);
|
||||
UNLOCK_STREAM;
|
||||
} else {
|
||||
UNLOCK_STREAM;
|
||||
updated = _expose_blank_picture(server);
|
||||
}
|
||||
queue_send = true;
|
||||
} else if (!server->run->exposed->online) {
|
||||
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
|
||||
updated = _expose_blank_picture(server);
|
||||
queue_send = true;
|
||||
}
|
||||
|
||||
if (queue_send) {
|
||||
_http_queue_send_stream(server, updated);
|
||||
}
|
||||
|
||||
# undef LOCK_STREAM
|
||||
# undef UNLOCK_STREAM
|
||||
}
|
||||
|
||||
static bool _expose_new_picture(struct http_server_t *server) {
|
||||
assert(server->run->stream->picture.size > 0);
|
||||
server->run->exposed->fps = server->run->stream->fps;
|
||||
|
||||
# define MEM_STREAM_TO_EXPOSED \
|
||||
server->run->exposed->picture.data, server->run->stream->picture.data, \
|
||||
server->run->stream->picture.size * sizeof(*server->run->exposed->picture.data)
|
||||
|
||||
if (server->drop_same_frames) {
|
||||
if (
|
||||
server->run->exposed->online
|
||||
&& server->run->exposed->dropped < server->drop_same_frames
|
||||
&& server->run->exposed->picture.size == server->run->stream->picture.size
|
||||
&& !memcmp(MEM_STREAM_TO_EXPOSED)
|
||||
) {
|
||||
LOG_PERF("HTTP: dropped same frame number %u", server->run->exposed->dropped);
|
||||
server->run->exposed->dropped += 1;
|
||||
return false; // Not updated
|
||||
}
|
||||
}
|
||||
|
||||
if (server->run->exposed->picture.allocated < server->run->stream->picture.allocated) {
|
||||
A_REALLOC(server->run->exposed->picture.data, server->run->stream->picture.allocated);
|
||||
server->run->exposed->picture.allocated = server->run->stream->picture.allocated;
|
||||
}
|
||||
|
||||
memcpy(MEM_STREAM_TO_EXPOSED);
|
||||
|
||||
# undef MEM_STREAM_TO_EXPOSED
|
||||
|
||||
server->run->exposed->picture.size = server->run->stream->picture.size;
|
||||
server->run->exposed->width = server->run->stream->width;
|
||||
server->run->exposed->height = server->run->stream->height;
|
||||
server->run->exposed->online = true;
|
||||
server->run->exposed->dropped = 0;
|
||||
return true; // Updated
|
||||
}
|
||||
|
||||
static bool _expose_blank_picture(struct http_server_t *server) {
|
||||
if (server->run->exposed->online || server->run->exposed->picture.size == 0) {
|
||||
if (server->run->exposed->picture.allocated < BLANK_JPG_SIZE) {
|
||||
A_REALLOC(server->run->exposed->picture.data, BLANK_JPG_SIZE);
|
||||
server->run->exposed->picture.allocated = BLANK_JPG_SIZE;
|
||||
}
|
||||
|
||||
memcpy(
|
||||
server->run->exposed->picture.data, BLANK_JPG_DATA,
|
||||
BLANK_JPG_SIZE * sizeof(*server->run->exposed->picture.data)
|
||||
);
|
||||
|
||||
server->run->exposed->picture.size = BLANK_JPG_SIZE;
|
||||
server->run->exposed->width = BLANK_JPG_WIDTH;
|
||||
server->run->exposed->height = BLANK_JPG_HEIGHT;
|
||||
server->run->exposed->fps = 0;
|
||||
server->run->exposed->online = false;
|
||||
goto updated;
|
||||
}
|
||||
|
||||
if (server->run->exposed->dropped < server->run->drop_same_frames_blank) {
|
||||
LOG_PERF("HTTP: dropped same frame (BLANK) number %u", server->run->exposed->dropped);
|
||||
server->run->exposed->dropped += 1;
|
||||
return false; // Not updated
|
||||
}
|
||||
|
||||
updated:
|
||||
server->run->exposed->dropped = 0;
|
||||
return true; // Updated
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# This source file based on code of MJPG-Streamer. #
|
||||
# #
|
||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||
# Copyright (C) 2007 Tom Stöveken #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../device.h"
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
|
||||
struct _mjpg_destination_mgr {
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
JOCTET *buffer; // Start of buffer
|
||||
unsigned char *outbuffer_cursor;
|
||||
unsigned long *written;
|
||||
};
|
||||
|
||||
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written);
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height);
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg);
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
|
||||
static void _jpeg_term_destination(j_compress_ptr jpeg);
|
||||
|
||||
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, const unsigned quality) {
|
||||
// This function based on compress_image_to_jpeg() from mjpg-streamer
|
||||
|
||||
struct jpeg_compress_struct jpeg;
|
||||
struct jpeg_error_mgr jpeg_error;
|
||||
unsigned char *line_buffer;
|
||||
|
||||
A_CALLOC(line_buffer, dev->run->width * 3);
|
||||
|
||||
jpeg.err = jpeg_std_error(&jpeg_error);
|
||||
jpeg_create_compress(&jpeg);
|
||||
|
||||
dev->run->pictures[index].size = 0;
|
||||
_jpeg_set_dest_picture(&jpeg, dev->run->pictures[index].data, &dev->run->pictures[index].size);
|
||||
|
||||
jpeg.image_width = dev->run->width;
|
||||
jpeg.image_height = dev->run->height;
|
||||
jpeg.input_components = 3;
|
||||
jpeg.in_color_space = JCS_RGB;
|
||||
|
||||
jpeg_set_defaults(&jpeg);
|
||||
jpeg_set_quality(&jpeg, quality, TRUE);
|
||||
|
||||
jpeg_start_compress(&jpeg, TRUE);
|
||||
|
||||
# define WRITE_SCANLINES(_func) \
|
||||
_func(&jpeg, line_buffer, dev->run->hw_buffers[index].start, dev->run->width, dev->run->height)
|
||||
switch (dev->run->format) {
|
||||
// https://www.fourcc.org/yuv.php
|
||||
case V4L2_PIX_FMT_YUYV: WRITE_SCANLINES(_jpeg_write_scanlines_yuyv); break;
|
||||
case V4L2_PIX_FMT_UYVY: WRITE_SCANLINES(_jpeg_write_scanlines_uyvy); break;
|
||||
case V4L2_PIX_FMT_RGB565: WRITE_SCANLINES(_jpeg_write_scanlines_rgb565); break;
|
||||
default: assert(0 && "Unsupported input format for JPEG compressor");
|
||||
}
|
||||
# undef WRITE_SCANLINES
|
||||
|
||||
// TODO: process jpeg errors:
|
||||
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
|
||||
jpeg_finish_compress(&jpeg);
|
||||
jpeg_destroy_compress(&jpeg);
|
||||
free(line_buffer);
|
||||
assert(dev->run->pictures[index].size > 0);
|
||||
assert(dev->run->pictures[index].size <= dev->run->max_picture_size);
|
||||
}
|
||||
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written) {
|
||||
struct _mjpg_destination_mgr *dest;
|
||||
|
||||
if (jpeg->dest == NULL) {
|
||||
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _mjpg_destination_mgr)
|
||||
)));
|
||||
}
|
||||
|
||||
dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
dest->mgr.init_destination = _jpeg_init_destination;
|
||||
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
||||
dest->mgr.term_destination = _jpeg_term_destination;
|
||||
dest->outbuffer_cursor = picture;
|
||||
dest->written = written;
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
unsigned z = 0;
|
||||
|
||||
while (jpeg->next_scanline < height) {
|
||||
unsigned char *ptr = line_buffer;
|
||||
|
||||
for (unsigned x = 0; x < width; ++x) {
|
||||
int y = (!z ? data[0] << 8 : data[2] << 8);
|
||||
int u = data[1] - 128;
|
||||
int v = data[3] - 128;
|
||||
|
||||
int r = (y + (359 * v)) >> 8;
|
||||
int g = (y - (88 * u) - (183 * v)) >> 8;
|
||||
int b = (y + (454 * u)) >> 8;
|
||||
|
||||
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
|
||||
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
|
||||
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
|
||||
|
||||
if (z++) {
|
||||
z = 0;
|
||||
data += 4;
|
||||
}
|
||||
}
|
||||
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
unsigned z = 0;
|
||||
|
||||
while(jpeg->next_scanline < height) {
|
||||
unsigned char *ptr = line_buffer;
|
||||
|
||||
for(unsigned x = 0; x < width; ++x) {
|
||||
int y = (!z ? data[1] << 8 : data[3] << 8);
|
||||
int u = data[0] - 128;
|
||||
int v = data[2] - 128;
|
||||
|
||||
int r = (y + (359 * v)) >> 8;
|
||||
int g = (y - (88 * u) - (183 * v)) >> 8;
|
||||
int b = (y + (454 * u)) >> 8;
|
||||
|
||||
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
|
||||
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
|
||||
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
|
||||
|
||||
if (z++) {
|
||||
z = 0;
|
||||
data += 4;
|
||||
}
|
||||
}
|
||||
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
|
||||
while(jpeg->next_scanline < height) {
|
||||
unsigned char *ptr = line_buffer;
|
||||
|
||||
for(unsigned x = 0; x < width; ++x) {
|
||||
unsigned int two_byte = (data[1] << 8) + data[0];
|
||||
|
||||
*(ptr++) = data[1] & 248;
|
||||
*(ptr++) = (unsigned char)((two_byte & 2016) >> 3);
|
||||
*(ptr++) = (data[0] & 31) * 8;
|
||||
|
||||
data += 2;
|
||||
}
|
||||
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#define JPEG_OUTPUT_BUFFER_SIZE 4096
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
|
||||
// Allocate the output buffer - it will be released when done with image
|
||||
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
|
||||
)));
|
||||
|
||||
dest->mgr.next_output_byte = dest->buffer;
|
||||
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
||||
// Called whenever local jpeg buffer fills up
|
||||
|
||||
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
|
||||
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
|
||||
*dest->written += JPEG_OUTPUT_BUFFER_SIZE;
|
||||
|
||||
dest->mgr.next_output_byte = dest->buffer;
|
||||
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
||||
// Called by jpeg_finish_compress after all data has been written.
|
||||
// Usually needs to flush buffer
|
||||
|
||||
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
|
||||
|
||||
// Write any data remaining in the buffer
|
||||
memcpy(dest->outbuffer_cursor, dest->buffer, data_count);
|
||||
dest->outbuffer_cursor += data_count;
|
||||
*dest->written += data_count;
|
||||
}
|
||||
|
||||
#undef JPEG_OUTPUT_BUFFER_SIZE
|
||||
37
src/libs/array.h
Normal file
37
src/libs/array.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define US_ARRAY_LEN(x_array) (sizeof(x_array) / sizeof((x_array)[0]))
|
||||
|
||||
#define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \
|
||||
const int m_len = US_ARRAY_LEN(x_array); \
|
||||
assert(x_start <= m_len); \
|
||||
for (int m_index = x_start; m_index < m_len; ++m_index) { \
|
||||
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_index]; \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
}
|
||||
72
src/libs/base64.c
Normal file
72
src/libs/base64.c
Normal file
@@ -0,0 +1,72 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
|
||||
static const char _ENCODING_TABLE[] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '+', '/',
|
||||
};
|
||||
|
||||
static const unsigned _MOD_TABLE[] = {0, 2, 1};
|
||||
|
||||
|
||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
|
||||
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
|
||||
|
||||
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
|
||||
US_REALLOC(*encoded, encoded_size);
|
||||
if (allocated) {
|
||||
*allocated = encoded_size;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned data_index = 0, encoded_index = 0; data_index < size;) {
|
||||
# define OCTET(_name) unsigned _name = (data_index < size ? (uint8_t)data[data_index++] : 0)
|
||||
OCTET(octet_a);
|
||||
OCTET(octet_b);
|
||||
OCTET(octet_c);
|
||||
# undef OCTET
|
||||
|
||||
const unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||
|
||||
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
|
||||
ENCODE(3);
|
||||
ENCODE(2);
|
||||
ENCODE(1);
|
||||
ENCODE(0);
|
||||
# undef ENCODE
|
||||
}
|
||||
|
||||
for (unsigned index = 0; index < _MOD_TABLE[size % 3]; index++) {
|
||||
(*encoded)[encoded_size - 2 - index] = '=';
|
||||
}
|
||||
|
||||
(*encoded)[encoded_size - 1] = '\0';
|
||||
}
|
||||
34
src/libs/base64.h
Normal file
34
src/libs/base64.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
|
||||
32
src/libs/const.h
Normal file
32
src/libs/const.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#define US_VERSION_MAJOR 5
|
||||
#define US_VERSION_MINOR 26
|
||||
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
#define US_VERSION US_MAKE_VERSION1(US_VERSION_MAJOR, US_VERSION_MINOR)
|
||||
|
||||
#define US_VERSION_U ((unsigned)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))
|
||||
105
src/libs/frame.c
Normal file
105
src/libs/frame.c
Normal file
@@ -0,0 +1,105 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
us_frame_s *us_frame_init(void) {
|
||||
us_frame_s *frame;
|
||||
US_CALLOC(frame, 1);
|
||||
us_frame_realloc_data(frame, 512 * 1024);
|
||||
frame->dma_fd = -1;
|
||||
return frame;
|
||||
}
|
||||
|
||||
void us_frame_destroy(us_frame_s *frame) {
|
||||
US_DELETE(frame->data, free);
|
||||
free(frame);
|
||||
}
|
||||
|
||||
void us_frame_realloc_data(us_frame_s *frame, size_t size) {
|
||||
if (frame->allocated < size) {
|
||||
US_REALLOC(frame->data, size);
|
||||
frame->allocated = size;
|
||||
}
|
||||
}
|
||||
|
||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
||||
us_frame_realloc_data(frame, size);
|
||||
memcpy(frame->data, data, size);
|
||||
frame->used = size;
|
||||
}
|
||||
|
||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
||||
const size_t new_used = frame->used + size;
|
||||
us_frame_realloc_data(frame, new_used);
|
||||
memcpy(frame->data + frame->used, data, size);
|
||||
frame->used = new_used;
|
||||
}
|
||||
|
||||
void us_frame_copy(const us_frame_s *src, us_frame_s *dest) {
|
||||
us_frame_set_data(dest, src->data, src->used);
|
||||
US_FRAME_COPY_META(src, dest);
|
||||
}
|
||||
|
||||
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
|
||||
return (
|
||||
a->allocated && b->allocated
|
||||
&& US_FRAME_COMPARE_META_USED_NOTS(a, b)
|
||||
&& !memcmp(a->data, b->data, b->used)
|
||||
);
|
||||
}
|
||||
|
||||
unsigned us_frame_get_padding(const us_frame_s *frame) {
|
||||
unsigned bytes_per_pixel = 0;
|
||||
switch (frame->format) {
|
||||
case V4L2_PIX_FMT_YUYV:
|
||||
case V4L2_PIX_FMT_UYVY:
|
||||
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break;
|
||||
case V4L2_PIX_FMT_RGB24: bytes_per_pixel = 3; break;
|
||||
// case V4L2_PIX_FMT_H264:
|
||||
case V4L2_PIX_FMT_MJPEG:
|
||||
case V4L2_PIX_FMT_JPEG: bytes_per_pixel = 0; break;
|
||||
default: assert(0 && "Unknown format");
|
||||
}
|
||||
if (bytes_per_pixel > 0 && frame->stride > frame->width) {
|
||||
return (frame->stride - frame->width * bytes_per_pixel);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size) {
|
||||
assert(size >= 8);
|
||||
buf[0] = format & 0x7F;
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
buf[2] = (format >> 16) & 0x7F;
|
||||
buf[3] = (format >> 24) & 0x7F;
|
||||
if (format & ((unsigned)1 << 31)) {
|
||||
buf[4] = '-';
|
||||
buf[5] = 'B';
|
||||
buf[6] = 'E';
|
||||
buf[7] = '\0';
|
||||
} else {
|
||||
buf[4] = '\0';
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
118
src/libs/frame.h
Normal file
118
src/libs/frame.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
int dma_fd;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
// Stride is a bytesperline in V4L2
|
||||
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
|
||||
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
|
||||
|
||||
bool online;
|
||||
bool key;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
} us_frame_s;
|
||||
|
||||
|
||||
#define US_FRAME_COPY_META(x_src, x_dest) { \
|
||||
x_dest->width = x_src->width; \
|
||||
x_dest->height = x_src->height; \
|
||||
x_dest->format = x_src->format; \
|
||||
x_dest->stride = x_src->stride; \
|
||||
x_dest->online = x_src->online; \
|
||||
x_dest->key = x_src->key; \
|
||||
x_dest->grab_ts = x_src->grab_ts; \
|
||||
x_dest->encode_begin_ts = x_src->encode_begin_ts; \
|
||||
x_dest->encode_end_ts = x_src->encode_end_ts; \
|
||||
}
|
||||
|
||||
static inline void us_frame_copy_meta(const us_frame_s *src, us_frame_s *dest) {
|
||||
US_FRAME_COPY_META(src, dest);
|
||||
}
|
||||
|
||||
#define US_FRAME_COMPARE_META_USED_NOTS(x_a, x_b) ( \
|
||||
x_a->used == x_b->used \
|
||||
&& x_a->width == x_b->width \
|
||||
&& x_a->height == x_b->height \
|
||||
&& x_a->format == x_b->format \
|
||||
&& x_a->stride == x_b->stride \
|
||||
&& x_a->online == x_b->online \
|
||||
&& x_a->key == x_b->key \
|
||||
)
|
||||
|
||||
|
||||
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, unsigned format) {
|
||||
assert(src->used > 0);
|
||||
us_frame_copy_meta(src, dest);
|
||||
dest->encode_begin_ts = us_get_now_monotonic();
|
||||
dest->format = format;
|
||||
dest->stride = 0;
|
||||
dest->used = 0;
|
||||
}
|
||||
|
||||
static inline void us_frame_encoding_end(us_frame_s *dest) {
|
||||
assert(dest->used > 0);
|
||||
dest->encode_end_ts = us_get_now_monotonic();
|
||||
}
|
||||
|
||||
|
||||
us_frame_s *us_frame_init(void);
|
||||
void us_frame_destroy(us_frame_s *frame);
|
||||
|
||||
void us_frame_realloc_data(us_frame_s *frame, size_t size);
|
||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
||||
|
||||
void us_frame_copy(const us_frame_s *src, us_frame_s *dest);
|
||||
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b);
|
||||
|
||||
unsigned us_frame_get_padding(const us_frame_s *frame);
|
||||
|
||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size);
|
||||
|
||||
static inline bool us_is_jpeg(unsigned format) {
|
||||
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
||||
}
|
||||
71
src/libs/list.h
Normal file
71
src/libs/list.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define US_LIST_STRUCT(...) \
|
||||
__VA_ARGS__ *prev; \
|
||||
__VA_ARGS__ *next;
|
||||
|
||||
#define US_LIST_ITERATE(x_first, x_item, ...) { \
|
||||
for (__typeof__(x_first) x_item = x_first; x_item;) { \
|
||||
__typeof__(x_first) m_next = x_item->next; \
|
||||
__VA_ARGS__ \
|
||||
x_item = m_next; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define US_LIST_APPEND(x_first, x_item) { \
|
||||
if (x_first == NULL) { \
|
||||
x_first = x_item; \
|
||||
} else { \
|
||||
__typeof__(x_first) m_last = x_first; \
|
||||
for (; m_last->next; m_last = m_last->next); \
|
||||
x_item->prev = m_last; \
|
||||
m_last->next = x_item; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define US_LIST_APPEND_C(x_first, x_item, x_count) { \
|
||||
US_LIST_APPEND(x_first, x_item); \
|
||||
++(x_count); \
|
||||
}
|
||||
|
||||
#define US_LIST_REMOVE(x_first, x_item) { \
|
||||
if (x_item->prev == NULL) { \
|
||||
x_first = x_item->next; \
|
||||
} else { \
|
||||
x_item->prev->next = x_item->next; \
|
||||
} \
|
||||
if (x_item->next != NULL) { \
|
||||
x_item->next->prev = x_item->prev; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define US_LIST_REMOVE_C(x_first, x_item, x_count) { \
|
||||
US_LIST_REMOVE(x_first, x_item); \
|
||||
assert((x_count) >= 1); \
|
||||
--(x_count); \
|
||||
}
|
||||
30
src/libs/logging.c
Normal file
30
src/libs/logging.c
Normal file
@@ -0,0 +1,30 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
enum us_log_level_t us_g_log_level;
|
||||
|
||||
bool us_g_log_colored;
|
||||
|
||||
pthread_mutex_t us_g_log_mutex;
|
||||
162
src/libs/logging.h
Normal file
162
src/libs/logging.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
|
||||
|
||||
enum us_log_level_t {
|
||||
US_LOG_LEVEL_INFO,
|
||||
US_LOG_LEVEL_PERF,
|
||||
US_LOG_LEVEL_VERBOSE,
|
||||
US_LOG_LEVEL_DEBUG,
|
||||
};
|
||||
|
||||
|
||||
extern enum us_log_level_t us_g_log_level;
|
||||
|
||||
extern bool us_g_log_colored;
|
||||
|
||||
extern pthread_mutex_t us_g_log_mutex;
|
||||
|
||||
|
||||
#define US_LOGGING_INIT { \
|
||||
us_g_log_level = US_LOG_LEVEL_INFO; \
|
||||
us_g_log_colored = isatty(2); \
|
||||
US_MUTEX_INIT(us_g_log_mutex); \
|
||||
}
|
||||
|
||||
#define US_LOGGING_DESTROY US_MUTEX_DESTROY(us_g_log_mutex)
|
||||
|
||||
#define US_LOGGING_LOCK US_MUTEX_LOCK(us_g_log_mutex)
|
||||
#define US_LOGGING_UNLOCK US_MUTEX_UNLOCK(us_g_log_mutex)
|
||||
|
||||
|
||||
#define US_COLOR_GRAY "\x1b[30;1m"
|
||||
#define US_COLOR_RED "\x1b[31;1m"
|
||||
#define US_COLOR_GREEN "\x1b[32;1m"
|
||||
#define US_COLOR_YELLOW "\x1b[33;1m"
|
||||
#define US_COLOR_BLUE "\x1b[34;1m"
|
||||
#define US_COLOR_CYAN "\x1b[36;1m"
|
||||
#define US_COLOR_RESET "\x1b[0m"
|
||||
|
||||
|
||||
#define US_SEP_INFO(x_ch) { \
|
||||
US_LOGGING_LOCK; \
|
||||
for (int m_count = 0; m_count < 80; ++m_count) { \
|
||||
fputc((x_ch), stderr); \
|
||||
} \
|
||||
fputc('\n', stderr); \
|
||||
fflush(stderr); \
|
||||
US_LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define US_SEP_DEBUG(x_ch) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_DEBUG) { \
|
||||
US_SEP_INFO(x_ch); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
||||
char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
||||
us_thread_get_name(m_tname_buf); \
|
||||
if (us_g_log_colored) { \
|
||||
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
|
||||
" [%.03Lf %9s]" " -- " US_COLOR_RESET x_msg_color x_msg US_COLOR_RESET, \
|
||||
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
|
||||
} else { \
|
||||
fprintf(stderr, "-- " x_label " [%.03Lf %9s] -- " x_msg, \
|
||||
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
|
||||
} \
|
||||
fputc('\n', stderr); \
|
||||
fflush(stderr); \
|
||||
}
|
||||
|
||||
#define US_LOG_PRINTF(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
||||
US_LOGGING_LOCK; \
|
||||
US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ##__VA_ARGS__); \
|
||||
US_LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define US_LOG_ERROR(x_msg, ...) { \
|
||||
US_LOG_PRINTF(US_COLOR_RED, "ERROR", US_COLOR_RED, x_msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define US_LOG_PERROR(x_msg, ...) { \
|
||||
char *const m_perror_str = us_errno_to_string(errno); \
|
||||
US_LOG_ERROR(x_msg ": %s", ##__VA_ARGS__, m_perror_str); \
|
||||
free(m_perror_str); \
|
||||
}
|
||||
|
||||
#define US_LOG_INFO(x_msg, ...) { \
|
||||
US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define US_LOG_INFO_NOLOCK(x_msg, ...) { \
|
||||
US_LOG_PRINTF_NOLOCK(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define US_LOG_PERF(x_msg, ...) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_PERF) { \
|
||||
US_LOG_PRINTF(US_COLOR_CYAN, "PERF ", US_COLOR_CYAN, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define US_LOG_PERF_FPS(x_msg, ...) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_PERF) { \
|
||||
US_LOG_PRINTF(US_COLOR_YELLOW, "PERF ", US_COLOR_YELLOW, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define US_LOG_VERBOSE(x_msg, ...) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_VERBOSE) { \
|
||||
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define US_LOG_VERBOSE_PERROR(x_msg, ...) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_VERBOSE) { \
|
||||
char *m_perror_str = us_errno_to_string(errno); \
|
||||
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg ": %s", ##__VA_ARGS__, m_perror_str); \
|
||||
free(m_perror_str); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define US_LOG_DEBUG(x_msg, ...) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_DEBUG) { \
|
||||
US_LOG_PRINTF(US_COLOR_GRAY, "DEBUG", US_COLOR_GRAY, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
206
src/libs/memsink.c
Normal file
206
src/libs/memsink.c
Normal file
@@ -0,0 +1,206 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "memsink.h"
|
||||
|
||||
|
||||
us_memsink_s *us_memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
|
||||
|
||||
us_memsink_s *sink;
|
||||
US_CALLOC(sink, 1);
|
||||
sink->name = name;
|
||||
sink->obj = obj;
|
||||
sink->server = server;
|
||||
sink->rm = rm;
|
||||
sink->client_ttl = client_ttl;
|
||||
sink->timeout = timeout;
|
||||
sink->fd = -1;
|
||||
atomic_init(&sink->has_clients, false);
|
||||
|
||||
US_LOG_INFO("Using %s-sink: %s", name, obj);
|
||||
|
||||
const mode_t mask = umask(0);
|
||||
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
|
||||
umask(mask);
|
||||
if (sink->fd == -1) {
|
||||
umask(mask);
|
||||
US_LOG_PERROR("%s-sink: Can't open shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s)) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't truncate shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((sink->mem = us_memsink_shared_map(sink->fd)) == NULL) {
|
||||
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return sink;
|
||||
|
||||
error:
|
||||
us_memsink_destroy(sink);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void us_memsink_destroy(us_memsink_s *sink) {
|
||||
if (sink->mem != NULL) {
|
||||
if (us_memsink_shared_unmap(sink->mem) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
|
||||
}
|
||||
}
|
||||
if (sink->fd >= 0) {
|
||||
if (close(sink->fd) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
|
||||
}
|
||||
if (sink->rm && shm_unlink(sink->obj) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
US_LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(sink);
|
||||
}
|
||||
|
||||
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
// Return true (the need to write to memsink) on any of these conditions:
|
||||
// - EWOULDBLOCK - we have an active client;
|
||||
// - Incorrect magic or version - need to first write;
|
||||
// - We have some active clients by last_client_ts;
|
||||
// - Frame meta differs (like size, format, but not timestamp).
|
||||
|
||||
assert(sink->server);
|
||||
|
||||
if (flock(sink->fd, LOCK_EX | LOCK_NB) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
atomic_store(&sink->has_clients, true);
|
||||
return true;
|
||||
}
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic());
|
||||
atomic_store(&sink->has_clients, has_clients);
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return false;
|
||||
}
|
||||
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
||||
}
|
||||
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested) {
|
||||
assert(sink->server);
|
||||
|
||||
const long double now = us_get_now_monotonic();
|
||||
|
||||
if (frame->used > US_MEMSINK_MAX_DATA) {
|
||||
US_LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
|
||||
sink->name, frame->used, US_MEMSINK_MAX_DATA);
|
||||
return 0; // -2
|
||||
}
|
||||
|
||||
if (us_flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||
US_LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
|
||||
|
||||
sink->last_id = us_get_now_id();
|
||||
sink->mem->id = sink->last_id;
|
||||
if (sink->mem->key_requested && frame->key) {
|
||||
sink->mem->key_requested = false;
|
||||
}
|
||||
if (key_requested != NULL) {
|
||||
*key_requested = sink->mem->key_requested;
|
||||
}
|
||||
|
||||
memcpy(sink->mem->data, frame->data, frame->used);
|
||||
sink->mem->used = frame->used;
|
||||
US_FRAME_COPY_META(frame, sink->mem);
|
||||
|
||||
sink->mem->magic = US_MEMSINK_MAGIC;
|
||||
sink->mem->version = US_MEMSINK_VERSION;
|
||||
|
||||
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic()));
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
US_LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
|
||||
sink->name, us_get_now_monotonic() - now);
|
||||
|
||||
} else if (errno == EWOULDBLOCK) {
|
||||
US_LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
|
||||
|
||||
} else {
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool key_required) { // cppcheck-suppress unusedFunction
|
||||
assert(!sink->server); // Client only
|
||||
|
||||
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
return -2;
|
||||
}
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int retval = -2; // Not updated
|
||||
if (sink->mem->magic == US_MEMSINK_MAGIC) {
|
||||
if (sink->mem->version != US_MEMSINK_VERSION) {
|
||||
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
|
||||
sink->name, sink->mem->version, US_MEMSINK_VERSION);
|
||||
retval = -1;
|
||||
goto done;
|
||||
}
|
||||
if (sink->mem->id != sink->last_id) { // When updated
|
||||
sink->last_id = sink->mem->id;
|
||||
us_frame_set_data(frame, sink->mem->data, sink->mem->used);
|
||||
US_FRAME_COPY_META(sink->mem, frame);
|
||||
retval = 0;
|
||||
}
|
||||
sink->mem->last_client_ts = us_get_now_monotonic();
|
||||
if (key_required) {
|
||||
sink->mem->key_requested = true;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
68
src/libs/memsink.h
Normal file
68
src/libs/memsink.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "frame.h"
|
||||
#include "memsinksh.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *obj;
|
||||
bool server;
|
||||
bool rm;
|
||||
unsigned client_ttl; // Only for server
|
||||
unsigned timeout;
|
||||
|
||||
int fd;
|
||||
us_memsink_shared_s *mem;
|
||||
uint64_t last_id;
|
||||
atomic_bool has_clients; // Only for server
|
||||
} us_memsink_s;
|
||||
|
||||
|
||||
us_memsink_s *us_memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
|
||||
|
||||
void us_memsink_destroy(us_memsink_s *sink);
|
||||
|
||||
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested);
|
||||
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool key_required);
|
||||
@@ -1,7 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
@@ -19,60 +20,66 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/http.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "stream.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
|
||||
struct stream_client_t {
|
||||
struct http_server_t *server;
|
||||
struct evhttp_request *request;
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||
#define US_MEMSINK_VERSION ((uint32_t)3)
|
||||
|
||||
struct stream_client_t *prev;
|
||||
struct stream_client_t *next;
|
||||
};
|
||||
|
||||
struct exposed_t {
|
||||
struct picture_t picture;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned fps;
|
||||
bool online;
|
||||
unsigned dropped;
|
||||
};
|
||||
|
||||
struct http_server_runtime_t {
|
||||
struct event_base *base;
|
||||
struct evhttp *http;
|
||||
struct event *refresh;
|
||||
struct stream_t *stream;
|
||||
struct exposed_t *exposed;
|
||||
struct stream_client_t *stream_clients;
|
||||
unsigned stream_clients_count;
|
||||
unsigned drop_same_frames_blank;
|
||||
};
|
||||
|
||||
struct http_server_t {
|
||||
char *host;
|
||||
unsigned port;
|
||||
unsigned drop_same_frames;
|
||||
unsigned fake_width;
|
||||
unsigned fake_height;
|
||||
unsigned timeout;
|
||||
|
||||
struct http_server_runtime_t *run;
|
||||
};
|
||||
#ifndef US_CFG_MEMSINK_MAX_DATA
|
||||
# define US_CFG_MEMSINK_MAX_DATA 33554432
|
||||
#endif
|
||||
#define US_MEMSINK_MAX_DATA ((size_t)(US_CFG_MEMSINK_MAX_DATA))
|
||||
|
||||
|
||||
struct http_server_t *http_server_init(struct stream_t *stream);
|
||||
void http_server_destroy(struct http_server_t *server);
|
||||
typedef struct {
|
||||
uint64_t magic;
|
||||
uint32_t version;
|
||||
|
||||
int http_server_listen(struct http_server_t *server);
|
||||
void http_server_loop(struct http_server_t *server);
|
||||
void http_server_loop_break(struct http_server_t *server);
|
||||
uint64_t id;
|
||||
|
||||
size_t used;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
bool online;
|
||||
bool key;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
|
||||
long double last_client_ts;
|
||||
bool key_requested;
|
||||
|
||||
uint8_t data[US_MEMSINK_MAX_DATA];
|
||||
} us_memsink_shared_s;
|
||||
|
||||
|
||||
INLINE us_memsink_shared_s *us_memsink_shared_map(int fd) {
|
||||
us_memsink_shared_s *mem = mmap(
|
||||
NULL,
|
||||
sizeof(us_memsink_shared_s),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
0
|
||||
);
|
||||
if (mem == MAP_FAILED) {
|
||||
return NULL;
|
||||
}
|
||||
assert(mem != NULL);
|
||||
return mem;
|
||||
}
|
||||
|
||||
INLINE int us_memsink_shared_unmap(us_memsink_shared_s *mem) {
|
||||
assert(mem != NULL);
|
||||
return munmap(mem, sizeof(us_memsink_shared_s));
|
||||
}
|
||||
39
src/libs/options.c
Normal file
39
src/libs/options.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "options.h"
|
||||
|
||||
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size) {
|
||||
memset(short_opts, 0, size);
|
||||
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
|
||||
assert(short_index < size - 3);
|
||||
if (isalpha(opts[opt_index].val)) {
|
||||
short_opts[short_index] = opts[opt_index].val;
|
||||
++short_index;
|
||||
if (opts[opt_index].has_arg == required_argument) {
|
||||
short_opts[short_index] = ':';
|
||||
++short_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/libs/options.h
Normal file
33
src/libs/options.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <getopt.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size);
|
||||
144
src/libs/process.h
Normal file
144
src/libs/process.h
Normal file
@@ -0,0 +1,144 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
#if defined(__linux__)
|
||||
# define HAS_PDEATHSIG
|
||||
#elif defined(__FreeBSD__)
|
||||
# include <sys/param.h>
|
||||
# if __FreeBSD_version >= 1102000
|
||||
# define HAS_PDEATHSIG
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
# if defined(__linux__)
|
||||
# include <bsd/unistd.h>
|
||||
# elif (defined(__FreeBSD__) || defined(__DragonFly__))
|
||||
//# include <unistd.h>
|
||||
//# include <sys/types.h>
|
||||
# elif (defined(__NetBSD__) || defined(__OpenBSD__)) // setproctitle() placed in stdlib.h
|
||||
# else
|
||||
# error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0
|
||||
# endif
|
||||
#endif
|
||||
#ifdef HAS_PDEATHSIG
|
||||
# if defined(__linux__)
|
||||
# include <sys/prctl.h>
|
||||
# elif defined(__FreeBSD__)
|
||||
# include <sys/procctl.h>
|
||||
# endif
|
||||
#endif
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
# include "tools.h"
|
||||
#endif
|
||||
#ifdef HAS_PDEATHSIG
|
||||
# include "logging.h"
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
extern char **environ;
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef HAS_PDEATHSIG
|
||||
INLINE int us_process_track_parent_death(void) {
|
||||
const pid_t parent = getppid();
|
||||
int signum = SIGTERM;
|
||||
# if defined(__linux__)
|
||||
const int retval = prctl(PR_SET_PDEATHSIG, signum);
|
||||
# elif defined(__FreeBSD__)
|
||||
const int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
|
||||
# else
|
||||
# error WTF?
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
US_LOG_PERROR("Can't set to receive SIGTERM on parent process death");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (kill(parent, 0) < 0) {
|
||||
US_LOG_PERROR("The parent process %d is already dead", parent);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
# pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
# pragma GCC diagnostic push
|
||||
INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) {
|
||||
# pragma GCC diagnostic pop
|
||||
|
||||
char *cmdline = NULL;
|
||||
size_t allocated = 2048;
|
||||
size_t used = 0;
|
||||
|
||||
US_REALLOC(cmdline, allocated);
|
||||
cmdline[0] = '\0';
|
||||
|
||||
for (int index = 0; index < argc; ++index) {
|
||||
size_t arg_len = strlen(argv[index]);
|
||||
if (used + arg_len + 16 >= allocated) {
|
||||
allocated += arg_len + 2048;
|
||||
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
||||
}
|
||||
|
||||
strcat(cmdline, " ");
|
||||
strcat(cmdline, argv[index]);
|
||||
used = strlen(cmdline); // Не считаем вручную, так надежнее
|
||||
}
|
||||
|
||||
# ifdef __linux__
|
||||
setproctitle_init(argc, argv, environ);
|
||||
# endif
|
||||
setproctitle("-%s:%s", prefix, cmdline);
|
||||
|
||||
free(cmdline);
|
||||
}
|
||||
#endif
|
||||
|
||||
INLINE void us_process_notify_parent(void) {
|
||||
const pid_t parent = getppid();
|
||||
if (kill(parent, SIGUSR2) < 0) {
|
||||
US_LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void us_process_suicide(void) {
|
||||
const pid_t pid = getpid();
|
||||
if (kill(pid, SIGTERM) < 0) {
|
||||
US_LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
|
||||
}
|
||||
}
|
||||
126
src/libs/threading.h
Normal file
126
src/libs/threading.h
Normal file
@@ -0,0 +1,126 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
# include <pthread_np.h>
|
||||
# include <sys/param.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#ifdef PTHREAD_MAX_NAMELEN_NP
|
||||
# define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
||||
#else
|
||||
# define US_MAX_THREAD_NAME ((size_t)16)
|
||||
#endif
|
||||
|
||||
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
||||
#define US_THREAD_JOIN(x_tid) assert(!pthread_join((x_tid), NULL))
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# define US_THREAD_RENAME(x_fmt, ...) { \
|
||||
char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
||||
assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
|
||||
us_thread_set_name(m_new_tname_buf); \
|
||||
}
|
||||
#else
|
||||
# define US_THREAD_RENAME(_fmt, ...)
|
||||
#endif
|
||||
|
||||
#define US_MUTEX_INIT(x_mutex) assert(!pthread_mutex_init(&(x_mutex), NULL))
|
||||
#define US_MUTEX_DESTROY(x_mutex) assert(!pthread_mutex_destroy(&(x_mutex)))
|
||||
#define US_MUTEX_LOCK(x_mutex) assert(!pthread_mutex_lock(&(x_mutex)))
|
||||
#define US_MUTEX_UNLOCK(x_mutex) assert(!pthread_mutex_unlock(&(x_mutex)))
|
||||
|
||||
#define US_COND_INIT(x_cond) assert(!pthread_cond_init(&(x_cond), NULL))
|
||||
#define US_COND_DESTROY(x_cond) assert(!pthread_cond_destroy(&(x_cond)))
|
||||
#define US_COND_SIGNAL(x_cond) assert(!pthread_cond_signal(&(x_cond)))
|
||||
#define US_COND_BROADCAST(x_cond) assert(!pthread_cond_broadcast(&(x_cond)))
|
||||
#define US_COND_WAIT_FOR(x_var, x_cond, x_mutex) { while(!(x_var)) assert(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
|
||||
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
INLINE void us_thread_set_name(const char *name) {
|
||||
# if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
pthread_set_name_np(pthread_self(), name);
|
||||
# elif defined(__NetBSD__)
|
||||
pthread_setname_np(pthread_self(), "%s", (void *)name);
|
||||
# else
|
||||
# error us_thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
INLINE void us_thread_get_name(char *name) { // Always required for logging
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
int retval = -1;
|
||||
# if defined(__linux__) || defined (__NetBSD__)
|
||||
retval = pthread_getname_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
||||
# elif \
|
||||
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|
||||
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|
||||
|| defined(__DragonFly__)
|
||||
pthread_get_name_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
||||
if (name[0] != '\0') {
|
||||
retval = 0;
|
||||
}
|
||||
# else
|
||||
# error us_thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
const pid_t tid = syscall(SYS_gettid);
|
||||
#elif defined(__FreeBSD__)
|
||||
const pid_t tid = syscall(SYS_thr_self);
|
||||
#elif defined(__OpenBSD__)
|
||||
const pid_t tid = syscall(SYS_getthrid);
|
||||
#elif defined(__NetBSD__)
|
||||
const pid_t tid = syscall(SYS__lwp_self);
|
||||
#elif defined(__DragonFly__)
|
||||
const pid_t tid = syscall(SYS_lwp_gettid);
|
||||
#else
|
||||
const pid_t tid = 0; // Makes cppcheck happy
|
||||
# warning gettid() not implemented
|
||||
#endif
|
||||
assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
}
|
||||
#endif
|
||||
}
|
||||
220
src/libs/tools.h
Normal file
220
src/libs/tools.h
Normal file
@@ -0,0 +1,220 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <locale.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
|
||||
# define HAS_SIGABBREV_NP
|
||||
#else
|
||||
# include <signal.h>
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef NDEBUG
|
||||
# error WTF dude? Asserts are good things!
|
||||
#endif
|
||||
|
||||
#if CHAR_BIT != 8
|
||||
# error There are not 8 bits in a char!
|
||||
#endif
|
||||
|
||||
|
||||
#define RN "\r\n"
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
||||
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
||||
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
|
||||
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
||||
|
||||
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
|
||||
|
||||
|
||||
INLINE char *us_strdup(const char *str) {
|
||||
char *const new = strdup(str);
|
||||
assert(new != NULL);
|
||||
return new;
|
||||
}
|
||||
|
||||
INLINE const char *us_bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
INLINE size_t us_align_size(size_t size, size_t to) {
|
||||
return ((size + (to - 1)) & ~(to - 1));
|
||||
}
|
||||
|
||||
INLINE unsigned us_min_u(unsigned a, unsigned b) {
|
||||
return (a < b ? a : b);
|
||||
}
|
||||
|
||||
INLINE unsigned us_max_u(unsigned a, unsigned b) {
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
INLINE long long us_floor_ms(long double now) {
|
||||
return (long long)now - (now < (long long)now); // floor()
|
||||
}
|
||||
|
||||
INLINE uint32_t us_triple_u32(uint32_t x) {
|
||||
// https://nullprogram.com/blog/2018/07/31/
|
||||
x ^= x >> 17;
|
||||
x *= UINT32_C(0xED5AD4BB);
|
||||
x ^= x >> 11;
|
||||
x *= UINT32_C(0xAC4C1B51);
|
||||
x ^= x >> 15;
|
||||
x *= UINT32_C(0x31848BAB);
|
||||
x ^= x >> 14;
|
||||
return x;
|
||||
}
|
||||
|
||||
INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(clk_id, &ts));
|
||||
*sec = ts.tv_sec;
|
||||
*msec = round(ts.tv_nsec / 1.0e6);
|
||||
|
||||
if (*msec > 999) {
|
||||
*sec += 1;
|
||||
*msec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CLOCK_MONOTONIC_RAW)
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
|
||||
#elif defined(CLOCK_MONOTONIC_FAST)
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
|
||||
#else
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC
|
||||
#endif
|
||||
|
||||
INLINE long double us_get_now_monotonic(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
us_get_now(_X_CLOCK_MONOTONIC, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE uint64_t us_get_now_monotonic_u64(void) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(_X_CLOCK_MONOTONIC, &ts));
|
||||
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
|
||||
}
|
||||
|
||||
#undef _X_CLOCK_MONOTONIC
|
||||
|
||||
INLINE uint64_t us_get_now_id(void) {
|
||||
const uint64_t now = us_get_now_monotonic_u64();
|
||||
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
|
||||
}
|
||||
|
||||
INLINE long double us_get_now_real(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
us_get_now(CLOCK_REALTIME, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE unsigned us_get_cores_available(void) {
|
||||
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return us_max_u(us_min_u(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
ts->tv_sec = (long)ld;
|
||||
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
|
||||
if (ts->tv_nsec > 999999999L) {
|
||||
ts->tv_sec += 1;
|
||||
ts->tv_nsec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
INLINE long double us_timespec_to_ld(const struct timespec *ts) {
|
||||
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
|
||||
}
|
||||
|
||||
INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
const long double deadline_ts = us_get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
while (true) {
|
||||
retval = flock(fd, LOCK_EX | LOCK_NB);
|
||||
if (retval == 0 || errno != EWOULDBLOCK || us_get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
}
|
||||
if (usleep(1000) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
INLINE char *us_errno_to_string(int error) {
|
||||
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
|
||||
char *buf;
|
||||
if (locale) {
|
||||
buf = us_strdup(strerror_l(error, locale));
|
||||
freelocale(locale);
|
||||
} else {
|
||||
buf = us_strdup("!!! newlocale() error !!!");
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
INLINE char *us_signum_to_string(int signum) {
|
||||
# ifdef HAS_SIGABBREV_NP
|
||||
const char *const name = sigabbrev_np(signum);
|
||||
# else
|
||||
const char *const name = (
|
||||
signum == SIGTERM ? "TERM" :
|
||||
signum == SIGINT ? "INT" :
|
||||
signum == SIGPIPE ? "PIPE" :
|
||||
NULL
|
||||
);
|
||||
# endif
|
||||
char *buf;
|
||||
if (name != NULL) {
|
||||
US_ASPRINTF(buf, "SIG%s", name);
|
||||
} else {
|
||||
US_ASPRINTF(buf, "SIG[%d]", signum);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
92
src/libs/unjpeg.c
Normal file
92
src/libs/unjpeg.c
Normal file
@@ -0,0 +1,92 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "unjpeg.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_error_mgr mgr; // Default manager
|
||||
jmp_buf jmp;
|
||||
const us_frame_s *frame;
|
||||
} _jpeg_error_manager_s;
|
||||
|
||||
|
||||
static void _jpeg_error_handler(j_common_ptr jpeg);
|
||||
|
||||
|
||||
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
|
||||
assert(us_is_jpeg(src->format));
|
||||
|
||||
volatile int retval = 0;
|
||||
|
||||
struct jpeg_decompress_struct jpeg;
|
||||
jpeg_create_decompress(&jpeg);
|
||||
|
||||
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
|
||||
_jpeg_error_manager_s jpeg_error;
|
||||
jpeg.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error);
|
||||
jpeg_error.mgr.error_exit = _jpeg_error_handler;
|
||||
jpeg_error.frame = src;
|
||||
if (setjmp(jpeg_error.jmp) < 0) {
|
||||
retval = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
jpeg_mem_src(&jpeg, src->data, src->used);
|
||||
jpeg_read_header(&jpeg, TRUE);
|
||||
jpeg.out_color_space = JCS_RGB;
|
||||
|
||||
jpeg_start_decompress(&jpeg);
|
||||
|
||||
us_frame_copy_meta(src, dest);
|
||||
dest->format = V4L2_PIX_FMT_RGB24;
|
||||
dest->width = jpeg.output_width;
|
||||
dest->height = jpeg.output_height;
|
||||
dest->stride = jpeg.output_width * jpeg.output_components; // Row stride
|
||||
dest->used = 0;
|
||||
|
||||
if (decode) {
|
||||
JSAMPARRAY scanlines;
|
||||
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1);
|
||||
|
||||
us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
|
||||
while (jpeg.output_scanline < jpeg.output_height) {
|
||||
jpeg_read_scanlines(&jpeg, scanlines, 1);
|
||||
us_frame_append_data(dest, scanlines[0], dest->stride);
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&jpeg);
|
||||
}
|
||||
|
||||
done:
|
||||
jpeg_destroy_decompress(&jpeg);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _jpeg_error_handler(j_common_ptr jpeg) {
|
||||
_jpeg_error_manager_s *jpeg_error = (_jpeg_error_manager_s *)jpeg->err;
|
||||
char msg[JMSG_LENGTH_MAX];
|
||||
|
||||
(*jpeg_error->mgr.format_message)(jpeg, msg);
|
||||
US_LOG_ERROR("Can't decompress JPEG: %s", msg);
|
||||
longjmp(jpeg_error->jmp, -1);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
@@ -22,24 +23,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <setjmp.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <IL/OMX_IVCommon.h>
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Image.h>
|
||||
#include <jpeglib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../logging.h"
|
||||
#include "../tools.h"
|
||||
#include "logging.h"
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
#define LOG_OMX_ERROR(_error, _msg, ...) { \
|
||||
LOGGING_LOCK; \
|
||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", now_monotonic_ms(), \
|
||||
syscall(SYS_gettid), ##__VA_ARGS__, omx_error_to_string(_error)); \
|
||||
LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
|
||||
const char *omx_error_to_string(const OMX_ERRORTYPE error);
|
||||
const char *omx_state_to_string(const OMX_STATETYPE state);
|
||||
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user