Compare commits

...

1355 Commits

Author SHA1 Message Date
Maxim Devaev
88460b72e1 Bump version: 6.54 → 6.55 2026-02-13 23:26:35 +02:00
Sergey Radionov
8c69c77481
janus: fixed compatibility with Tailscale MTU (#325) 2026-02-13 17:34:52 +02:00
Maxim Devaev
5331ae14aa Bump version: 6.53 → 6.54 2026-02-12 20:38:03 +02:00
Maxim Devaev
0127dcf018 janus: hotfix: reverted sps/pps logic for first/last packages 2026-02-12 20:36:00 +02:00
Maxim Devaev
aa58b1b002 Bump version: 6.52 → 6.53 2026-02-12 19:47:33 +02:00
Maxim Devaev
a05eab71a8 janus: reserve 50 bytes for RTP extensions 2026-02-12 19:34:39 +02:00
Maxim Devaev
e013356cf0 janus: renamed US_RTP_DATAGRAM_SIZE -> US_RTP_TOTAL_SIZE 2026-02-12 19:27:00 +02:00
Maxim Devaev
c730981827 janus: set first/last_of_frame only for non-sps/pps packets 2026-02-12 18:48:22 +02:00
Maxim Devaev
3bb1ed3ef3 openwrt package 2026-02-09 17:09:09 +02:00
Paul Donald
1cda22bfd2
spell fixes (#323) 2026-02-09 16:59:05 +02:00
Maxim Devaev
29ee20e864 Bump version: 6.51 → 6.52 2026-02-07 17:06:14 +02:00
Maxim Devaev
5e13f4cd58 using CC -dumpmachine instead of uname 2026-02-07 17:02:43 +02:00
Maxim Devaev
39c1916f61 Bump version: 6.50 → 6.51 2026-02-03 19:52:09 +02:00
Ivan Shapovalov
614e83771b
ustreamer: options: NULL-terminate the copy of argv (#322)
According to N2176 of ISO/IEC 9899:2017 §5.1.2.2.1 ¶2:

> - argv[argc] shall be a null pointer.

Possibly fixes openwrt/packages#28472.
2026-02-03 19:41:40 +02:00
Maxim Devaev
15a9e28ac6 spelling fix 2026-02-03 10:59:26 +02:00
Maxim Devaev
a1ae02de5d janus: refactored sdp logic 2026-01-31 16:45:26 +02:00
Maxim Devaev
214708549d Bump version: 6.49 → 6.50 2026-01-30 16:21:30 +02:00
Maxim Devaev
1be4521664 Issue #321: Fixed compilation error on FreeBSD 2026-01-30 12:51:25 +02:00
Maxim Devaev
94752dde75 Bump version: 6.48 → 6.49 2026-01-28 15:14:30 +02:00
Maxim Devaev
b933b7b407 Bump version: 6.47 → 6.48 2026-01-28 14:17:18 +02:00
Maxim Devaev
61f44b5f97 janus: don't send rtp extensions with each packet 2026-01-28 12:06:37 +02:00
Maxim Devaev
8fef0408b6 janus: time functions refactored 2026-01-28 11:05:50 +02:00
Maxim Devaev
62028be064 janus: rtpv: mark all extensions as sendonly 2026-01-28 10:57:43 +02:00
Sergey Radionov
3b7592bb31
Janus: "Absolute Capture Time" RTP extension added (#320)
for video.
2026-01-28 09:59:01 +02:00
Maxim Devaev
8adca998e9 janus: fixed sdp for firefox 2026-01-28 08:50:53 +02:00
Maxim Devaev
6ac5a5f065 refactoring 2026-01-28 08:49:41 +02:00
Maxim Devaev
12cf4492bd Bump version: 6.46 → 6.47 2026-01-23 23:15:09 +02:00
Maxim Devaev
a6f111f7cf refactoring 2026-01-23 03:06:16 +02:00
Maxim Devaev
efbb2aa7ba Bump version: 6.45 → 6.46 2026-01-21 08:26:04 +02:00
Maxim Devaev
5692d81e46 lint fix 2026-01-21 08:21:44 +02:00
Maxim Devaev
4cec824b13 fixed fps limit for h264 2026-01-21 07:57:44 +02:00
Maxim Devaev
ac1989451c added help for --h264-boost 2026-01-21 07:07:41 +02:00
Maxim Devaev
e39d27309a Merge branch 'h264-boost' 2026-01-21 07:04:06 +02:00
Maxim Devaev
b983b6c355 new fps limiter 2026-01-21 07:03:58 +02:00
Maxim Devaev
5204f00812 h264 boost mode 2026-01-21 03:16:20 +02:00
Maxim Devaev
9eb39bbfc3 grab_begin_ts and grab_end_ts 2026-01-21 00:07:40 +02:00
Maxim Devaev
6adbb93e57 fpsi: optional meta arg in us_fpsi_get() 2026-01-20 11:52:19 +02:00
Maxim Devaev
4bd1465a10 janus: apply zero_playout_delay 2026-01-20 11:49:46 +02:00
Maxim Devaev
cf7f8947ef always capture maximum possible fps 2026-01-20 05:16:02 +02:00
Maxim Devaev
ec2e6c313b removed old fps regulation for jpeg encoders 2026-01-20 02:48:39 +02:00
Maxim Devaev
de2cfa36e1 Bump version: 6.44 → 6.45 2026-01-16 23:31:31 +02:00
Maxim Devaev
6c1a8f75a1 bumped python 2026-01-16 23:29:54 +02:00
Maxim Devaev
26ee5143ee Bump version: 6.43 → 6.44 2026-01-04 16:43:12 +02:00
Maxim Devaev
e2890e5851 janus: removed sync between video and audio 2026-01-04 16:03:42 +02:00
Maxim Devaev
e2b01e4d79 Bump version: 6.42 → 6.43 2026-01-03 19:43:43 +02:00
Maxim Devaev
903bc45bee lint fixes 2026-01-03 19:21:10 +02:00
Maxim Devaev
b2b1989c5b reduced preallocated us_frame_s size 2026-01-03 18:54:56 +02:00
Maxim Devaev
36b539c275 Bump version: 6.41 → 6.42 2025-11-11 00:00:35 +02:00
Maxim Devaev
38c6917644 janus: pkg-config 2025-11-10 23:58:42 +02:00
Maxim Devaev
05a5d3fed4 Bump version: 6.40 → 6.41 2025-10-23 16:28:07 +03:00
Maxim Devaev
0e4bf31325 janus: non-tc358743 devices for acap suppurted
An alternative implementation of pikvm/ustreamer#304.
Thanks for the idea.
2025-10-23 00:50:19 +03:00
Maxim Devaev
9a5cce3b92 janus: deprecated aplay/check option 2025-10-22 21:35:56 +03:00
Maxim Devaev
c4ac67acba janus: plug audio devices dynamically 2025-10-22 19:35:35 +03:00
Maxim Devaev
472673ea90 Bump version: 6.39 → 6.40 2025-07-28 21:32:04 +03:00
Maxim Devaev
f7ebe31c71 refactoring 2025-07-28 21:29:27 +03:00
Maxim Devaev
3a831817f4 pikvm/pikvm#1558: Discard JPEGs with invalid headers 2025-07-28 21:26:08 +03:00
Maxim Devaev
913cdac7a6 Bump version: 6.38 → 6.39 2025-07-03 04:19:51 +03:00
Maxim Devaev
777697dc1e improved logging on --exit-on-device-error 2025-07-03 04:17:48 +03:00
Maxim Devaev
5f437b9a35 Bump version: 6.37 → 6.38 2025-07-03 03:51:05 +03:00
Maxim Devaev
b089f896da pikvm/pikvm#312: --exit-on-device-error 2025-07-03 03:49:02 +03:00
Maxim Devaev
0e521ad0c6 Bump version: 6.36 → 6.37 2025-05-27 19:42:34 +03:00
Maxim Devaev
620a0ec847 Fixed #290: improved blank diagnostics 2025-05-27 19:30:07 +03:00
Maxim Devaev
7a1d4816ed frametext: more improvements 2025-05-26 22:34:19 +03:00
Maxim Devaev
aec8431024 verbose on-screen error messages 2025-05-26 20:06:06 +03:00
Maxim Devaev
5b18e29555 frametext: improved proportions 2025-05-26 20:04:35 +03:00
Maxim Devaev
2717248581 Bump version: 6.35 → 6.36 2025-03-27 04:38:28 +02:00
Maxim Devaev
afd305e87d v4p: fix for some DOS device 2025-03-27 04:36:35 +02:00
Maxim Devaev
e3d8132237 fixed --format-swap-rgb 2025-03-27 04:33:17 +02:00
Maxim Devaev
1f32e875c3 openwrt: +libatomic 2025-03-09 06:45:37 +02:00
Maxim Devaev
2e88fb9294 Bump version: 6.34 → 6.35 2025-03-08 20:16:11 +02:00
Maxim Devaev
d68f8e6d86 added missing formats 2025-03-08 20:14:17 +02:00
gudvinr
b380beba6d
Add GREY pixelformat (#171)
Fixes #170

Monochrome cameras send only Y component of YUV image
2025-03-08 20:01:49 +02:00
Maxim Devaev
3a06a484ce Bump version: 6.33 → 6.34 2025-03-05 17:34:18 +02:00
Maxim Devaev
0307d3bdb6 Issue #287: Don't add -latomic on FreeBSD 2025-03-05 17:32:01 +02:00
Maxim Devaev
f2dd9c3c5a pikvm/ustreamer#306: Added ifdef for linux 2025-02-28 22:24:31 +02:00
Maxim Devaev
4e3f873f0d Added pkg-config to README 2025-02-27 22:21:23 +02:00
Maxim Devaev
029440cf82 Bump version: 6.32 → 6.33 2025-02-24 18:47:04 +02:00
Maxim Devaev
df74f5cf18 janus: added default ICE url 2025-02-24 18:41:40 +02:00
Maxim Devaev
97494c3531 janus: replaces STUN variables to ICE_URL 2025-02-24 18:21:46 +02:00
Maxim Devaev
71544880d1 janus: changed env prefix 2025-02-24 17:16:24 +02:00
Maxim Devaev
83127e58ff Bump version: 6.31 → 6.32 2025-02-24 05:19:22 +02:00
Maxim Devaev
604a8f7cb4 janus: STUN env 2025-02-24 05:17:32 +02:00
Maxim Devaev
602c1747d5 Bump version: 6.30 → 6.31 2025-02-08 15:46:31 +02:00
Maxim Devaev
a2b8b35070 improved build system 2025-02-08 15:44:40 +02:00
Maxim Devaev
dd7701be38 Bump version: 6.29 → 6.30 2025-02-08 13:03:01 +02:00
Maxim Devaev
1c9bd91b31 lint fix 2025-02-08 13:01:32 +02:00
Maxim Devaev
e19a3ca7ff report about all WITH_* flags in --features 2025-02-08 02:21:26 +02:00
Maxim Devaev
b2d1a5612d manual WITH_PDEATHSIG 2025-02-08 01:56:59 +02:00
Maxim Devaev
f3e0613de3 python: expose FEATURES variable 2025-02-08 00:25:17 +02:00
Maxim Devaev
5baf921660 common WITH_* flags 2025-02-07 23:31:36 +02:00
Maxim Devaev
6cabcd39f1 python: fixed uninitialized fd 2025-02-07 23:24:05 +02:00
Maxim Devaev
3df3658e4f python: version constants 2025-02-07 23:20:45 +02:00
Maxim Devaev
f21fc5f6d3 added missing WITH_V4P flag to --features 2025-02-07 18:02:04 +02:00
Maxim Devaev
b70ed98af9 Bump version: 6.28 → 6.29 2025-02-03 08:55:13 +02:00
Maxim Devaev
52cdabe150 janus: counterclockwise video rotation 2025-02-03 08:52:42 +02:00
Maxim Devaev
fe86997d08 Bump version: 6.27 → 6.28 2025-01-28 15:59:57 +02:00
Maxim Devaev
df39b824c6 refactoring 2025-01-27 06:32:26 +02:00
Sam Listopad
db297db52e
Add Support for YUV420 and YVU variants. (#276)
* Add Support fo YUV420 and 410 and YVU variants.

* Add new formats to the help messaging

* Remove YUV410 supprt since M2M encoder on Pi cannot convert it

* Cleanups requested by @mdevaev

* Change to use u8 per @mdevaev
2025-01-27 06:14:18 +02:00
Jack Wilsdon
b304364af9
Allow overriding pkg-config (#301) 2025-01-27 02:53:39 +02:00
Maxim Devaev
ddec4e8478 Bump version: 6.26 → 6.27 2025-01-21 05:44:36 +02:00
Maxim Devaev
28ca658621 moved to python-3.13 2025-01-21 05:43:04 +02:00
Maxim Devaev
270d3ae3a9 Bump version: 6.25 → 6.26 2025-01-20 16:41:44 +02:00
Maxim Devaev
c1f080f29f check file flag for aplay 2025-01-20 16:39:50 +02:00
Maxim Devaev
b1e7c82131 Bump version: 6.24 → 6.25 2025-01-20 00:23:11 +02:00
Maxim Devaev
3d7685ac48 bunch of mic fixes 2025-01-20 00:21:36 +02:00
Maxim Devaev
37e79995fe Bump version: 6.23 → 6.24 2025-01-19 18:19:30 +02:00
Maxim Devaev
1ee096b17c mic support 2025-01-19 18:15:08 +02:00
Maxim Devaev
918688e91d refactoring 2025-01-18 18:32:41 +02:00
Maxim Devaev
a94ff667b0 refactoring, increased bitrate, reduced buffers 2025-01-18 17:16:55 +02:00
Maxim Devaev
10595a13e9 refactoring 2025-01-18 05:09:32 +02:00
Maxim Devaev
80ffc8b2bd Bump version: 6.22 → 6.23 2025-01-17 20:53:21 +02:00
Maxim Devaev
ba246d90c0 refactoring 2025-01-17 20:40:18 +02:00
Maxim Devaev
29c98e3908 Bump version: 6.21 → 6.22 2025-01-13 17:17:27 +02:00
Maxim Devaev
acc8cecbe4 lint fix 2025-01-13 17:15:55 +02:00
Maxim Devaev
8c31af2f03 janus: sendonly/sendrecv audio flag 2025-01-13 17:10:42 +02:00
Maxim Devaev
a727c9b7c5 Bump version: 6.20 → 6.21 2024-12-27 05:22:35 +02:00
Maxim Devaev
eabc8d8343 fixed bug with reversed logic of parent notification 2024-12-27 05:20:22 +02:00
Maxim Devaev
4e4ae21a83 Bump version: 6.19 → 6.20 2024-12-26 04:31:23 +02:00
Maxim Devaev
412a1775a6 hotfixed online flag 2024-12-26 04:29:15 +02:00
Maxim Devaev
c404c49c6d Bump version: 6.18 → 6.19 2024-12-26 04:09:49 +02:00
Maxim Devaev
481e359153 janus: reduces opus frame length to 20ms 2024-12-26 04:05:53 +02:00
Maxim Devaev
04114bba86 refactoring 2024-12-15 11:34:41 +02:00
Maxim Devaev
c848756d53 Bump version: 6.17 → 6.18 2024-11-29 22:26:02 +02:00
Maxim Devaev
2a8aaabe48 janus: Fixed return value of message handler + memory leak with transaction 2024-11-29 22:03:49 +02:00
Maxim Devaev
239db92a85 Issue #295: Fixed double json_decref() 2024-11-27 16:08:29 +02:00
Maxim Devaev
740e09c70d Bump version: 6.16 → 6.17 2024-11-07 12:38:32 +02:00
Maxim Devaev
e030479aae lint fixes 2024-11-07 12:36:16 +02:00
Maxim Devaev
4db730abd9 fixed missing argument 2024-11-07 12:24:05 +02:00
Frank Müller
79020143c7 scale the blank image for NO SIGNAL to the resolution in the options 2024-11-07 12:06:35 +02:00
Maxim Devaev
1f96925181 Bump version: 6.15 → 6.16 2024-09-11 01:09:17 +03:00
Maxim Devaev
74dc1dc146 Janus: Added sprop-stereo=1 2024-09-11 01:06:19 +03:00
Maxim Devaev
6f8e8205b3 Bump version: 6.14 → 6.15 2024-09-06 22:21:34 +03:00
Maxim Devaev
5f932d862b Small refactoring of #289 + manpage 2024-09-06 20:40:23 +03:00
zefir-o
590a73f9ec
Add option to which allows to handle truncated frames. (#289)
Extension of c96559e4ac.
Some cheap Chinise cameras produces frames which are detected as 'broken'. However they
are later handled well.
Introduce an option which allows disable the check on demand.
2024-09-06 19:32:48 +03:00
Maxim Devaev
79bbafdc98 Bump version: 6.13 → 6.14 2024-09-04 18:56:32 +03:00
Maxim Devaev
fcecc12229 Revert "refactoring"
This reverts commit 3e228c1fb8c1f5999bbb062ec76b2d4dc77fc039.
2024-09-04 18:34:41 +03:00
Maxim Devaev
f79a663839 added pkgconf to deps 2024-09-04 18:31:48 +03:00
Maxim Devaev
3e228c1fb8 refactoring 2024-09-04 15:49:55 +03:00
Maxim Devaev
53ec87b416 Issue #264: Properly checking of pkg-config 2024-08-17 05:40:03 +03:00
Maxim Devaev
de8cb85605 Bump version: 6.12 → 6.13 2024-08-16 07:07:54 +03:00
Maxim Devaev
000be92a0b lint fix 2024-08-16 07:04:21 +03:00
Maxim Devaev
f2779f7b44 check for pkg-config 2024-08-16 06:38:52 +03:00
yuri@FreeBSD
dcddfddf56
Fix crash on FreeBSD due to incorrect thr_self system call invocation (#285)
The correct signature is:
int thr_self(long *id);

It was called as thr_self() which caused memory corruption.
2024-08-16 06:38:07 +03:00
Randolf Richardson 張文道
793f24c48e
Update README.md (#275)
Minor spelling correction
2024-05-29 12:59:48 +03:00
Maxim Devaev
25d87d5fa8 Bump version: 6.11 → 6.12 2024-05-16 00:13:24 +03:00
Maxim Devaev
e8a7fb32ac lint fixes 2024-05-16 00:10:53 +03:00
Maxim Devaev
9d5eb8bacb fixed edid path 2024-05-16 00:01:03 +03:00
Maxim Devaev
353e58d7ca fix 2024-05-16 00:00:10 +03:00
Fabrice Fontaine
6c24c9ea61
src/libs/types.h: include sys/types.h (#273)
Include sys/types.h to avoid the following uclibc build failure since
version 5.52 and
2d6716aa47:

In file included from libs/base64.h:25,
                 from libs/base64.c:23:
libs/types.h:30:9: error: unknown type name 'ssize_t'
   30 | typedef ssize_t sz;
      |         ^~~~~~~

Fixes:
 - http://autobuild.buildroot.org/results/24498049d7beb4afaaf9f9a0c2fc0bcd26a3ee04

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2024-05-15 20:56:49 +03:00
Maxim Devaev
dfeefe5a1c Bump version: 6.10 → 6.11 2024-04-05 19:31:57 +03:00
Maxim Devaev
aae090ab4e list: clean next pointer on append 2024-04-05 19:29:13 +03:00
Maxim Devaev
18038799f0 reworked pool logic 2024-04-05 19:21:42 +03:00
Maxim Devaev
fab4c47f17 list: clean prev/next pointers on remove 2024-04-05 17:48:26 +03:00
Maxim Devaev
c40b3ee225 refactoring 2024-04-04 23:25:06 +03:00
Maxim Devaev
fca69db680 us_workers_pool_wait() without side effect 2024-04-04 23:21:34 +03:00
Maxim Devaev
0d974a5faf refactoring 2024-04-04 19:37:03 +03:00
Maxim Devaev
1ed39790ba use JCS_EXT_BGR on libjpeg-turbo 2024-04-04 15:20:16 +03:00
Maxim Devaev
75a193f997 syntax fix 2024-04-04 03:58:45 +03:00
Maxim Devaev
65c652e624 encoder: removed cpu_forced logic 2024-04-04 03:44:20 +03:00
Maxim Devaev
ae2f270f50 refactoring 2024-04-04 02:36:28 +03:00
Maxim Devaev
0a639eabca deprecated noop jpeg encoder 2024-04-03 20:23:35 +03:00
Maxim Devaev
9ec59143dd Bump version: 6.9 → 6.10 2024-04-01 22:03:40 +03:00
Maxim Devaev
e059a21ef9 refactoring 2024-04-01 21:40:51 +03:00
Maxim Devaev
074ce86f67 using fps_meta instead of flags 2024-04-01 00:12:42 +03:00
Maxim Devaev
b8b67de5cf mutexless fpsi at all 2024-03-31 17:33:51 +03:00
Maxim Devaev
5f3198e72f sort of fps reset 2024-03-30 21:37:13 +02:00
Maxim Devaev
3a3889d02c fpsi: mutexless mode 2024-03-30 19:34:43 +02:00
Maxim Devaev
88203f9c53 fix 2024-03-30 19:05:59 +02:00
Maxim Devaev
24aca349a3 we don't need us_fpsi_reset() anymore 2024-03-30 19:05:15 +02:00
Maxim Devaev
a9e0cb49e9 h264 and drm statistics in http 2024-03-30 17:48:15 +02:00
Maxim Devaev
4ec3f11935 refactoring 2024-03-30 16:10:46 +02:00
Maxim Devaev
14e9d9f7af fps -> fpsi, store frame meta 2024-03-30 15:35:14 +02:00
Maxim Devaev
580ca68291 US_FRAME_META_DECLARE 2024-03-30 13:13:17 +02:00
Maxim Devaev
37f3f093dc simplified list declaration 2024-03-30 13:07:20 +02:00
Maxim Devaev
70fa6548fe common fps counter 2024-03-30 12:15:59 +02:00
Maxim Devaev
f8a703f166 refactoring 2024-03-29 22:58:07 +02:00
Maxim Devaev
3f69dd785f fix 2024-03-29 15:41:54 +02:00
Maxim Devaev
8e6c374acf refactoring 2024-03-29 15:36:43 +02:00
Maxim Devaev
caf9ed7bfe refactoring 2024-03-29 03:34:35 +02:00
Maxim Devaev
94b1224456 fix 2024-03-29 02:24:36 +02:00
Maxim Devaev
c8201df720 don't rebuild python module without necessary 2024-03-29 01:15:02 +02:00
Maxim Devaev
e0f09f65a1 new macro US_ONCE_FOR() 2024-03-29 01:02:40 +02:00
Maxim Devaev
4e1f62bfac refactoring 2024-03-29 00:13:08 +02:00
Maxim Devaev
b0b881f199 fix 2024-03-28 18:38:01 +02:00
Maxim Devaev
a21f527bce common error constants 2024-03-28 17:17:22 +02:00
Maxim Devaev
d64077c2d5 Bump version: 6.8 → 6.9 2024-03-27 21:39:03 +02:00
Maxim Devaev
83f12baa61 refactoring 2024-03-27 19:27:28 +02:00
Maxim Devaev
b6fac2608d ustreamer-v4p: bring back busy message 2024-03-27 19:22:21 +02:00
Maxim Devaev
e6ebc12505 replaced comment 2024-03-27 02:14:36 +02:00
Maxim Devaev
8c92ab6f47 ustreamer: blank drm output by timeout 2024-03-26 22:20:08 +02:00
Maxim Devaev
7dc492d875 refactoring 2024-03-26 21:51:47 +02:00
Maxim Devaev
d43014346d Bump version: 6.7 → 6.8 2024-03-26 20:23:16 +02:00
Maxim Devaev
bcd447963c build fix 2024-03-26 20:22:10 +02:00
Maxim Devaev
eec6cfd0d4 lint fix 2024-03-26 20:10:06 +02:00
Maxim Devaev
f177300e69 ustreamer/drm: fixed assertion 2024-03-26 18:59:33 +02:00
Maxim Devaev
7015a26a63 Userspace workaround for the wrong TC358743 RGB bytes ordering
- https://github.com/raspberrypi/linux/issues/6068
2024-03-26 18:35:13 +02:00
Maxim Devaev
290282b6b6 drm: fixed big endian case for rgb/bgr 2024-03-26 18:05:51 +02:00
Maxim Devaev
a339ff5d06 v4p mode in ustreamer 2024-03-26 17:45:53 +02:00
Maxim Devaev
8d4e9a6ca0 renamed us_hw_buffer_s to us_capture_hwbuf_s 2024-03-26 01:54:01 +02:00
Maxim Devaev
f0f5fcd67f renamed us_device* to us_capture* 2024-03-26 01:25:04 +02:00
Maxim Devaev
02fabc7b45 Bump version: 6.6 → 6.7 2024-03-25 20:16:52 +02:00
Maxim Devaev
bdf55396e5 fixed memsink data offset 2024-03-25 20:15:38 +02:00
Maxim Devaev
976f466038 PKGBUILD: fixed missing python-wheel 2024-03-23 19:40:29 +02:00
Maxim Devaev
b79b4fd55e Bump version: 6.5 → 6.6 2024-03-22 15:58:21 +02:00
Maxim Devaev
6d77f5334f updated copyright date 2024-03-22 15:57:16 +02:00
Maxim Devaev
25957de017 direct call of setup.py is deprecated 2024-03-22 15:55:06 +02:00
Maxim Devaev
847f34e10c fixed symlink 2024-03-22 14:12:43 +02:00
Maxim Devaev
0ab8e0d05e DESTDIR always transformed to absolute R_DESTDIR 2024-03-22 12:59:37 +02:00
Kiyofumi Kondoh
ac88996a8c
adapt pacman 6.1 and lower version (#266)
* adapt pacman 6.1 and lower version

* remove legacy operation
2024-03-21 22:47:23 +02:00
Maxim Devaev
408157c82b Bump version: 6.4 → 6.5 2024-03-21 13:33:29 +02:00
Maxim Devaev
7356dea737 renamed options --sink* to --jpeg-sink* 2024-03-21 13:21:51 +02:00
Maxim Devaev
87a75a816a memsink: suffix-based memory limites 2024-03-20 22:55:37 +02:00
Maxim Devaev
b6a2332207 refactoring 2024-03-20 17:51:56 +02:00
Maxim Devaev
34c0dcb1ce refactoring 2024-03-19 19:33:55 +02:00
Maxim Devaev
283f31a5a6 Bump version: 6.3 → 6.4 2024-03-17 10:44:37 +02:00
Maxim Devaev
2f1264c916 janus: rtp orientation support 2024-03-17 10:42:52 +02:00
Maxim Devaev
69e7cbf746 refactoring 2024-03-16 23:20:38 +02:00
Maxim Devaev
05804e309f Bump version: 6.2 → 6.3 2024-03-14 16:36:20 +02:00
Maxim Devaev
1d8c93d3ad Issue #253: Added m2m encoder timeout 2024-03-14 16:06:32 +02:00
Maxim Devaev
f48695a04e refactoring 2024-03-13 13:09:27 +02:00
Maxim Devaev
8ac2fa201b Bump version: 6.1 → 6.2 2024-03-10 18:11:26 +02:00
Maxim Devaev
2d9e51a1ca v4p: turn off the display after timeout 2024-03-10 16:05:50 +00:00
Maxim Devaev
c4cf4f015b v4p: wait for dma_fd on close 2024-03-10 16:49:49 +02:00
Maxim Devaev
646afbffff refactoring 2024-03-10 12:30:22 +02:00
Maxim Devaev
6475eeef4c refactoring 2024-03-10 12:25:08 +02:00
Maxim Devaev
2e67a46eb8 refactoring 2024-03-10 12:10:09 +02:00
Maxim Devaev
c333e75dff v4p: dpms 2024-03-09 19:55:22 +00:00
Maxim Devaev
72285023cb refactoring 2024-03-09 12:13:17 +02:00
Maxim Devaev
b00a6ffd8d Bump version: 6.0 → 6.1 2024-03-09 04:33:44 +02:00
Maxim Devaev
ce935c431e v4p: changed logging levels 2024-03-09 04:32:12 +02:00
Maxim Devaev
ac0944ae1a v4p: added some checks and asserts 2024-03-09 04:29:50 +02:00
Maxim Devaev
66572806a2 fix 2024-03-09 03:28:13 +02:00
Maxim Devaev
a75d6487e3 font info 2024-03-09 03:28:13 +02:00
Maxim Devaev
897ad4951b v4p: dma support 2024-03-09 01:27:50 +00:00
Maxim Devaev
e1ef86146f Bump version: 5.59 → 6.0 2024-03-06 21:50:47 +02:00
Maxim Devaev
8f3a475a32 Bump version: 5.58 → 5.59 2024-03-06 20:56:38 +02:00
Maxim Devaev
be5f63d64d noted TC358743 errors 2024-03-06 01:02:52 +02:00
Maxim Devaev
40e17b05b3 memsink client: bump last_as_blank_ts on every flock() 2024-03-06 00:43:15 +02:00
Maxim Devaev
0b8940d93d limit fps by m2m hardware to reduce latency 2024-03-05 21:49:39 +02:00
Maxim Devaev
e92002c3d8 repeat blank every second on offline 2024-03-05 14:32:45 +02:00
Maxim Devaev
e558b0f1a1 Issue #264: Bring back BSD compatibility in strerror hacks 2024-03-05 14:19:55 +02:00
Maxim Devaev
b5784149b2 fix 2024-03-05 13:49:18 +02:00
Maxim Devaev
55b6a3e933 improved logging 2024-03-05 13:47:29 +02:00
Maxim Devaev
f7c2948477 improved memsink checks performance 2024-03-05 13:44:54 +02:00
Maxim Devaev
c55b6c4d7d lint fix 2024-03-04 17:39:39 +02:00
Maxim Devaev
442790486c fixed empty label for goto 2024-03-04 17:19:30 +02:00
Maxim Devaev
bbc7ceb110 fix 2024-03-04 08:32:09 +02:00
Maxim Devaev
2ffa561eb1 reduced snapshot timeout to error_delay*3 2024-03-04 07:47:14 +02:00
Maxim Devaev
490d833983 refactoring 2024-03-04 07:22:48 +02:00
Maxim Devaev
0b3a1eb963 deprecated --last-as-blank 2024-03-04 07:18:45 +02:00
Maxim Devaev
7fd5eb229f fixed offline state 2024-03-04 03:54:16 +02:00
Maxim Devaev
98b5e52a68 block signals in threads 2024-03-04 03:38:45 +02:00
Maxim Devaev
c8dc5119fe signal lib to reduce duplicating code 2024-03-04 03:12:14 +02:00
Maxim Devaev
b556dfb897 improved persistent logic 2024-03-04 01:50:34 +02:00
Maxim Devaev
06eda04180 always generate blanks for offline snapshots 2024-03-03 22:15:25 +02:00
Maxim Devaev
05bba86c63 refactoring 2024-03-03 21:28:25 +02:00
Maxim Devaev
6827a72097 failed if dv timings are not available 2024-03-03 20:04:47 +02:00
Maxim Devaev
299b3886af improved messages 2024-03-03 19:02:59 +02:00
Maxim Devaev
f9bc5666b8 refactoring 2024-03-03 18:44:13 +02:00
Maxim Devaev
c9cb0a416e fixed persistent timeout 2024-03-03 08:23:18 +02:00
Maxim Devaev
ffa68a86a6 refactoring 2024-03-03 08:10:33 +02:00
Maxim Devaev
8fe411aa8b compress only lastest frame 2024-03-03 07:05:00 +02:00
Maxim Devaev
36dd5d1533 pass encoders if there is no clients 2024-03-03 06:27:19 +02:00
Maxim Devaev
33b9bff0b9 atomic capture state for http 2024-03-03 06:04:48 +02:00
Maxim Devaev
c24d6338e2 Issue #228: Request fresh snapshot from jpeg encoder 2024-03-03 04:59:12 +02:00
Maxim Devaev
8cb6fc4e78 refactoring 2024-03-03 03:24:40 +02:00
Maxim Devaev
a9dfff84e6 fix 2024-03-03 02:55:13 +02:00
Maxim Devaev
988a91634a fixed force_key logic for slowdown 2024-03-03 02:51:53 +02:00
Maxim Devaev
8f6df3b455 Issue #263: -latomic is required now 2024-03-03 01:45:16 +02:00
Maxim Devaev
ef47fa4c74 jpeg in a separate thread 2024-03-03 01:04:06 +02:00
Maxim Devaev
f2f560a345 h264 encoder in separate thread 2024-03-02 23:06:06 +02:00
Maxim Devaev
6a0ee68692 renamed captured_fps to http_captured_fps 2024-03-02 21:33:55 +02:00
Maxim Devaev
72741b90f4 releaser threads 2024-03-02 21:07:46 +02:00
Maxim Devaev
0296ab60c3 added device timeout error message 2024-03-02 20:16:34 +02:00
Maxim Devaev
77a53347c3 refactoring 2024-03-02 19:37:50 +02:00
Maxim Devaev
c32ea286f2 Bump version: 5.57 → 5.58 2024-03-02 19:11:06 +02:00
Maxim Devaev
b2fb857f5b refactoring 2024-03-02 19:09:53 +02:00
Maxim Devaev
20cdabc8a4 lint fix 2024-03-02 10:42:30 +02:00
Maxim Devaev
e2f4c193e3 refactoring 2024-03-02 10:08:06 +02:00
Maxim Devaev
b4aa9593dc refactoring 2024-03-02 09:59:41 +02:00
Maxim Devaev
20c729893b refactoring 2024-03-02 09:56:44 +02:00
Maxim Devaev
a00f49331c wait (select) device in grab function 2024-03-02 09:33:45 +02:00
Maxim Devaev
85308e48fd stream: null hw buffer pointer after encoding 2024-03-02 07:54:38 +02:00
Maxim Devaev
ff08a0fb25 refactoring 2024-03-02 07:23:18 +02:00
Maxim Devaev
6145b69c97 refactoring 2024-03-02 02:09:54 +02:00
Maxim Devaev
cfc5ae1b94 v4p: using /dev/dri/by-path/platform-gpu-card instead of card0 2024-03-02 01:14:15 +02:00
Maxim Devaev
54b221aabd moved exit_on_no_clients logic from http to stream loop 2024-03-01 09:40:51 +02:00
Maxim Devaev
dabee9d47a gitignores ustreamer-v4p 2024-03-01 08:22:14 +02:00
Maxim Devaev
e30520d9f3 refactoring 2024-03-01 08:11:37 +02:00
Maxim Devaev
8f0acb2176 Bump version: 5.56 → 5.57 2024-03-01 04:33:13 +02:00
Maxim Devaev
8edeff8160 fixed makefile 2024-03-01 04:21:45 +02:00
Maxim Devaev
cacec0d25c refactoring 2024-03-01 04:09:16 +02:00
Maxim Devaev
c54d7da19f Bump version: 5.55 → 5.56 2024-03-01 03:48:15 +02:00
Maxim Devaev
df610e1045 improved makefiles 2024-03-01 03:47:03 +02:00
Maxim Devaev
8d4e6a13b0 server: fixed zero frame behaviour 2024-03-01 03:20:43 +02:00
Maxim Devaev
c852e5f827 lint fixes 2024-03-01 00:43:57 +02:00
Maxim Devaev
2a37f1650b using text generator for blank images 2024-03-01 00:39:19 +02:00
Maxim Devaev
15f160c874 refactoring 2024-02-29 22:40:51 +02:00
Maxim Devaev
df63ad4678 refactoring 2024-02-29 22:40:51 +02:00
Markus Küffner
72cd61cccf
docker: build images for armv6 (#262) 2024-02-29 22:13:38 +02:00
Maxim Devaev
3e5a444023 refactoring 2024-02-29 19:17:41 +02:00
Maxim Devaev
a3f4294cd8 refactoring 2024-02-29 18:21:26 +02:00
Maxim Devaev
d9401b70f5 commented unused us_queue_get_free() 2024-02-29 08:07:47 +02:00
Maxim Devaev
4296d5dada janus: using ring for audio pipeline 2024-02-29 08:06:48 +02:00
Maxim Devaev
12937b93d5 janus: using ring buffers for rtp pipelines 2024-02-29 06:57:16 +02:00
Maxim Devaev
28cd5e5b87 janus: reducing memory allocating using ring buffer 2024-02-29 06:27:11 +02:00
Maxim Devaev
7bacef7622 minor fix 2024-02-29 03:29:23 +02:00
Maxim Devaev
ca3638313b moved ftext to libs as frametext 2024-02-29 03:10:48 +02:00
Maxim Devaev
bb3e4ec2c7 moved queue from janus to common libs 2024-02-29 02:48:52 +02:00
Maxim Devaev
d52ac784f6 device: don't expose dma for jpeg 2024-02-29 02:42:44 +02:00
Maxim Devaev
f5a9214dd4 simplified 2024-02-29 01:23:41 +02:00
Maxim Devaev
5b54a8dbe2 v4p: fixed buf error handling 2024-02-29 00:52:03 +02:00
Maxim Devaev
32165e97ed simplified 2024-02-29 00:42:34 +02:00
Maxim Devaev
4b6f65881d simplified 2024-02-28 23:25:06 +02:00
Maxim Devaev
0e7d8da407 Bump version: 5.54 → 5.55 2024-02-28 00:14:42 +02:00
Maxim Devaev
1c862b21e9 v4p: better messages 2024-02-28 00:13:37 +02:00
Maxim Devaev
b202438a4d v4p: ignore ENOENT (unplugged) on restoring saved_crtc 2024-02-27 23:59:02 +02:00
Maxim Devaev
50ee2ba964 v4p: simplified 2024-02-27 23:50:27 +02:00
Maxim Devaev
9f40713ee1 v4p: check display status via sysfs 2024-02-27 07:55:33 +02:00
Maxim Devaev
5f393ae972 Bump version: 5.53 → 5.54 2024-02-27 00:14:06 +02:00
Maxim Devaev
aca3ee9e23 fix installer 2024-02-27 00:12:57 +02:00
Maxim Devaev
284b17d1db Bump version: 5.52 → 5.53 2024-02-26 22:42:48 +02:00
Maxim Devaev
506a4496d1 fix 2024-02-26 22:41:48 +02:00
Maxim Devaev
b5c201d1c1 Bump version: 5.51 → 5.52 2024-02-26 21:42:53 +02:00
Maxim Devaev
032faa9882 refactoring 2024-02-26 21:29:26 +02:00
Maxim Devaev
414baadc40 refactoring 2024-02-26 21:14:36 +02:00
Maxim Devaev
2d6716aa47 refactoring 2024-02-26 20:28:09 +02:00
Maxim Devaev
260619923a new macro US_MIN() and US_MAX() 2024-02-26 19:09:16 +02:00
Maxim Devaev
3715c89ec8 v4p 2024-02-26 17:36:43 +02:00
Maxim Devaev
5026108079 device: Logging prefix
(cherry picked from commit f3bfdb2fd74c0bb5249df3f21ab6daf0ec12318a)
Signed-off-by: Maxim Devaev <mdevaev@gmail.com>
2024-02-23 04:03:43 +00:00
Maxim Devaev
5f7c556697 device: Keep DV-timing Hz
(cherry picked from commit 17a448223c0dd1cf420ba9a79232f433ff11c4b5)
Signed-off-by: Maxim Devaev <mdevaev@gmail.com>
2024-02-23 04:03:37 +00:00
Maxim Devaev
3c7564da19 big refactoring 2024-02-22 19:35:49 +02:00
Maxim Devaev
f0e070be5b Bump version: 5.50 → 5.51 2024-02-19 00:40:14 +02:00
Maxim Devaev
aa05c470b3 string fixes 2024-02-19 00:22:45 +02:00
Maxim Devaev
13af11a3a6 Using strerror_r() instead of strerror_l() for better compatibility 2024-02-18 18:41:33 +02:00
Maxim Devaev
41330940c6 Bump version: 5.49 → 5.50 2024-02-16 17:15:26 +02:00
Maxim Devaev
46e630d2f6 using (void) instead of UNUSED 2024-02-16 01:51:36 +02:00
Maxim Devaev
63d87f0526 add instance_id to stream_client cookie name 2024-02-15 15:52:03 +02:00
Maxim Devaev
b578e9897e notes about libcamerify 2024-02-03 20:14:19 +02:00
Maxim Devaev
b2ebcf99c8 Bump version: 5.48 → 5.49 2024-02-02 22:53:48 +02:00
Maxim Devaev
6a6b910869 refactoring 2024-02-02 22:41:45 +02:00
Maxim Devaev
4e8acf371f Issue #252: YVYU support 2024-02-02 22:41:45 +02:00
Thomer Gil
c4cb8288c7
Fix it's vs its typo (#255)
Co-authored-by: Thomer Gil <thomer@thomer.com>
2024-01-30 18:28:32 +02:00
Maxim Devaev
2dddb879bc Bump version: 5.47 → 5.48 2024-01-09 00:12:23 +02:00
Maxim Devaev
4d92dc662c removed some checks in gpio 2024-01-09 00:11:11 +02:00
Maxim Devaev
3cb649d97c Bump version: 5.46 → 5.47 2024-01-07 04:48:07 +02:00
Maxim Devaev
d9b5f2b03d Issue #255: libgpiod 2.0 API supported
Thanks for @jpalus for #249 with compatibility code
2024-01-07 04:36:53 +02:00
Jan Palus
2997906d98
add option to make verbose builds (#251)
if V=1 is passed to make echo invoked command verbosely
2024-01-02 02:27:12 +02:00
Jan Palus
bcd3deaa13
remove remainings of rpi vc paths (#250)
accidently left after Makefile split and subsequently switching to v4l2
based encoding
2024-01-02 02:24:37 +02:00
Maxim Devaev
f8ed7d7b3b Bump version: 5.45 → 5.46 2023-12-14 13:29:50 +02:00
Maxim Devaev
622f5cf1eb janus plugin: increased video queue 2023-12-14 12:41:29 +02:00
Maxim Devaev
81756811f3 update for bookworm 2023-11-15 14:59:34 +02:00
Maxim Devaev
bd403593a0 using libjpeg62-turbo on raspbian 2023-11-15 14:52:11 +02:00
Jason Huggins
fc3e0232e1
'-e' should be '--encoder' (#241) 2023-11-03 17:39:18 +02:00
Maxim Devaev
89fd83bfc1 added info for v2 camera 2023-10-28 11:05:55 +03:00
Maxim Devaev
83a77ea898 doc about youtube streaming 2023-10-24 20:18:07 +03:00
Maxim Devaev
33fdf9bf43 Bump version: 5.44 → 5.45 2023-10-24 14:14:00 +03:00
Ed Maste
6bd4ef59c0
avoid duplicate increment of slc (#239)
Clang reported "warning: variable 'slc' is incremented both in the loop
header and in the loop body".
2023-10-24 05:11:36 +03:00
Maxim Devaev
79987da1bf Bump version: 5.43 → 5.44 2023-10-14 01:47:24 +03:00
Maxim Devaev
05e5db09e4 fix 2023-10-12 04:19:23 +03:00
Maxim Devaev
55e432a529 Merge branch 'mp' 2023-10-12 04:13:10 +03:00
chr
4732c85ec4
Optimize JPEG scanline copy of yuv format (#235)
* opt jpeg scanline copy with yuv format

* remove unused macro
2023-10-12 04:11:34 +03:00
Michael Lynch
0ce7f28754
Correct typo on 'interval' (#236)
This fixes a minor typo on the word 'interval'.
2023-10-10 20:19:24 +03:00
Maxim Devaev
a2641dfcb6 some multiplane fixes 2023-10-10 20:13:57 +03:00
Artem
ec33425c05
Multi Planar device support (#233)
* added multi planar device support (RK3588 HDMI IN)

* sync with upstream version

* fix use local variable after free

Signed-off-by: Artem Mamonov <artyom.mamonov@gmail.com>

* request buffer length = VIDEO_MAX_PLANES for multi-planar devices

---------

Signed-off-by: Artem Mamonov <artyom.mamonov@gmail.com>
Co-authored-by: hongruichen <chraac@gmail.com>
2023-10-08 19:27:17 +03:00
Maxim Devaev
a4b4dd3932 Bump version: 5.42 → 5.43 2023-10-04 02:46:33 +03:00
Maxim Devaev
e952f787a0 moved ssl docs 2023-10-04 02:43:28 +03:00
Maxim Devaev
b3e4ea9c0f issue #230: processing any freshest valid buffer 2023-10-04 02:41:55 +03:00
Maxim Devaev
22a816b9b5 issue #230: fixed possible memory error 2023-10-04 02:41:55 +03:00
Stargirl Flowers
c96559e4ac Discard truncated JPEG frames (#230)
Hello! This patch works around an issue encountered with [ELP-USB100W03M]
cameras where they send a vast amount of invalid JPEGs when capturing
their MJPEG streams. These bad frames account for about 87% of captured
frames and cause issues for browsers and downstream applications.

Replaces #229

[ELP-USB100W03M]: https://www.webcamerausb.com/elp-10mp-free-driver-usb20-ov9712-cmos-sensor-hd-mjpeg-web-camera-board-720p-36mm-lens-p-116.html
2023-10-04 02:41:55 +03:00
Maxim Devaev
a52df47b29 skip broken frames and save only good 2023-10-04 02:41:55 +03:00
tallman5
68e7e97e74
SSL Proxy Scripts (#226)
* adding basic ssl steps

* added down the road section
2023-10-04 02:41:39 +03:00
Maxim Devaev
35ed415f4c Bump version: 5.41 → 5.42 2023-08-23 07:54:17 +03:00
Maxim Devaev
121edf5a10 lint fix 2023-08-23 07:21:18 +03:00
Maxim Devaev
aa90ed1fbb lint fix 2023-08-23 07:11:57 +03:00
Maxim Devaev
a102a4a3db refactoring 2023-08-23 07:08:02 +03:00
Maxim Devaev
516c0be6ea decreased grab latency 2023-08-23 05:26:35 +03:00
Maxim Devaev
0745f0a75a lint fixes 2023-08-23 02:41:41 +03:00
Maxim Devaev
90e51c0619 always using CLOCK_MONOTONIC 2023-08-23 01:19:30 +03:00
Maxim Devaev
cb9c1658af ignoring ustreamer.egg-info 2023-08-23 00:42:49 +03:00
Maxim Devaev
548c261d92 Bump version: 5.40 → 5.41 2023-06-19 21:31:49 +03:00
Maxim Devaev
d4560fcba9 pikvm/ustreamer#221: Increased resolution limit 2023-06-19 21:31:09 +03:00
Maxim Devaev
370434601c example for camera module 3 2023-06-08 20:41:33 +03:00
Maxim Devaev
09359cb957 Bump version: 5.39 → 5.40 2023-06-08 20:19:34 +03:00
Maxim Devaev
71b93a2a38 Fixed #212: Supported Raspberry Camera 3 via libcamerify 2023-06-08 20:17:19 +03:00
Maxim Devaev
aeb5930483 Bump version: 5.38 → 5.39 2023-05-27 12:48:12 +03:00
Maxim Devaev
b17b87018b lint fix 2023-05-27 12:47:50 +03:00
Maxim Devaev
602ca16178 copyright update 2023-05-27 12:39:18 +03:00
Maxim Devaev
28c8599167 Bump version: 5.37 → 5.38 2023-02-25 15:55:58 +02:00
amiablepointers
aa668cec9d
Update encoder.c (#207)
It gave me Segmentation Fault after few scanlines. Changing the way the next pointer to the scanline is calculated worked for me
2023-02-25 16:10:25 +03:00
Michael Lynch
f6ec0ade38
Fix typo: starging -> starting (#204) 2023-02-15 04:44:48 +03:00
Binil Jacob
a10df2f01f
Update README.md (#201) 2023-02-02 16:21:55 +03:00
Maxim Devaev
dde2190ac9 Bump version: 5.36 → 5.37 2023-01-16 20:01:22 +02:00
Maxim Devaev
c24c1ec5ff added debian and ubuntu links 2022-12-29 04:40:25 +03:00
Maxim Devaev
cfd0cdad59 updated doc 2022-12-29 03:56:52 +03:00
Maxim Devaev
31219358c5 Bump version: 5.35 → 5.36 2022-12-27 19:11:09 +03:00
Maxim Devaev
4adfadfaea DOCKERHUB_REPO 2022-12-27 18:45:44 +03:00
Maxim Devaev
6174edcf0d Bump version: 5.34 → 5.35 2022-12-21 22:03:12 +03:00
Maxim Devaev
05d9322404 using archlinux/archlinux:base for testenv 2022-12-21 22:00:18 +03:00
Maxim Devaev
bf78d8f562 Bump version: 5.33 → 5.34 2022-11-29 16:07:18 +03:00
Maxim Devaev
77a5dbfeae janus: probe alsa capture device 2022-11-29 16:06:38 +03:00
Maxim Devaev
3eebeaeedd fixed edid 2022-11-29 08:04:34 +03:00
Maxim Devaev
aba8396d60 pinned flake8==5.0.4 due zheller/flake8-quotes#110 2022-11-29 05:16:15 +03:00
Maxim Devaev
5b33246b6b Bump version: 5.32 → 5.33 2022-11-28 03:33:42 +03:00
Maxim Devaev
7f3f480d92 janus: fixed features response 2022-11-28 03:16:10 +03:00
Maxim Devaev
86fef47018 Bump version: 5.31 → 5.32 2022-11-27 12:05:21 +03:00
Maxim Devaev
f09bb1ade9 janus: features request 2022-11-27 12:03:43 +03:00
Maxim Devaev
fa030147e8 janus: improved audio switching 2022-11-27 09:34:14 +03:00
Maxim Devaev
f88333b6bf refactoring 2022-11-27 08:00:58 +03:00
Maxim Devaev
9b4f3229f2 janus: optional audio for each client 2022-11-27 07:01:23 +03:00
Maxim Devaev
900d7e1112 Merge branch 'auto-playout-delay' 2022-11-27 03:34:58 +03:00
Maxim Devaev
c0588c6736 janus: commented zero_playout_delay opts 2022-11-27 03:34:46 +03:00
Maxim Devaev
5bf8c97a1c int once 2022-11-19 04:21:37 +03:00
Maxim Devaev
7335a5d2df US_ONCE macro 2022-11-19 00:25:21 +03:00
Maxim Devaev
839804b476 indentation fix 2022-11-18 22:34:52 +03:00
Marcel Stör
5bb223506a
Extend the installation instructions (#190) 2022-11-14 23:33:37 +03:00
Maxim Devaev
414f536ace auto playout delay 2022-11-14 19:58:12 +03:00
Maxim Devaev
c1363d55e0 pass gop to memsink 2022-11-13 05:26:30 +03:00
Maxim Devaev
c871229740 Bump version: 5.30 → 5.31 2022-11-09 02:57:02 +03:00
Maxim Devaev
01de2caa97 refactoring 2022-11-09 02:56:28 +03:00
Maxim Devaev
d154a3d1c1 favicon.ico 2022-11-09 02:41:12 +03:00
Marcel Stör
b111487757
Add (inline) favicon (#186)
Adding a data URI favicon avoids having the browser send an extra HTTP request for `/favicon.ico` - which is met with HTTP 404 anyway.
2022-11-09 01:48:56 +03:00
Maxim Devaev
47d1c09377 shorten messages 2022-11-08 16:56:45 +03:00
Maxim Devaev
3d2f254f40 Bump version: 5.29 → 5.30 2022-11-05 23:50:17 +03:00
Maxim Devaev
13e31d0cd5 janus: handle PLI video 2022-11-05 22:57:22 +03:00
Maxim Devaev
4e7676f307 refactoring 2022-11-05 22:45:18 +03:00
Maxim Devaev
95fcc3c58e Bump version: 5.28 → 5.29 2022-11-04 19:48:26 +03:00
Maxim Devaev
81500af1b3 renamed --key to --key-required 2022-11-04 19:47:50 +03:00
Maxim Devaev
86a2141361 janus: key_required handler 2022-11-04 13:51:49 +03:00
Maxim Devaev
22e5c8627b removed sps/pps from sdp 2022-11-03 22:54:06 +03:00
Maxim Devaev
c8600e62c2 Bump version: 5.27 → 5.28 2022-11-03 19:12:35 +03:00
Maxim Devaev
335f19f0e3 refactoring 2022-11-03 19:11:58 +03:00
Michael Lynch
a24e0eeb86
Document additional required audio packages (#184) 2022-11-03 19:11:26 +03:00
Michael Lynch
3fcb8d3ee5
Document additional required audio packages (#184) 2022-11-03 19:10:17 +03:00
Michael Lynch
ca30656bf8
Assign stream index on outgoing RTP packets (#182)
* Assign stream index on outgoing RTP packets (#5)

* Correctly assign mindex on outgoing rtp packets

Previously mindex was not set and defaulted to zero. This lead to most packets
getting dropped because of sequence number reuse when streaming both audio and
video. This reorders the SDP entries for video and audio so that video is first
in both a video-only and a audio+video configuration. This means that the mindex
for video packets should always be zero, and for audio (if present) should
always be one. This assumes there will never be an audio-only configuration.

* Adjust comments

Co-authored-by: Michael Lynch <git@mtlynch.io>

* Add preprocessor conditional to guard packet.mindex setting

The mindex field wasn't added to the janus_plugin_rtp_packet until Janus 1.0, so this change adds a precompiler check to ensure JANUS_PLUGIN_API_VERSION is >= 100 before assigning a value to the mindex field.

* Preserve audio-then-video ordering for Janus 0.x

Co-authored-by: Louis Goessling <louis@goessling.com>
2022-11-03 19:04:15 +03:00
Michael Lynch
4f0abf7eec
Assign stream index on outgoing RTP packets (#182)
* Assign stream index on outgoing RTP packets (#5)

* Correctly assign mindex on outgoing rtp packets

Previously mindex was not set and defaulted to zero. This lead to most packets
getting dropped because of sequence number reuse when streaming both audio and
video. This reorders the SDP entries for video and audio so that video is first
in both a video-only and a audio+video configuration. This means that the mindex
for video packets should always be zero, and for audio (if present) should
always be one. This assumes there will never be an audio-only configuration.

* Adjust comments

Co-authored-by: Michael Lynch <git@mtlynch.io>

* Add preprocessor conditional to guard packet.mindex setting

The mindex field wasn't added to the janus_plugin_rtp_packet until Janus 1.0, so this change adds a precompiler check to ensure JANUS_PLUGIN_API_VERSION is >= 100 before assigning a value to the mindex field.

* Preserve audio-then-video ordering for Janus 0.x

Co-authored-by: Louis Goessling <louis@goessling.com>
2022-11-03 19:03:16 +03:00
Maxim Devaev
8b233a4c71 Bump version: 5.26 → 5.27 2022-11-01 22:30:04 +03:00
Maxim Devaev
8a81158276 improved dump 2022-11-01 20:59:16 +03:00
Maxim Devaev
f83ff439c4 Bump version: 5.25 → 5.26 2022-11-01 18:10:19 +03:00
Maxim Devaev
983796e952 request keyframe via sink 2022-11-01 16:36:10 +03:00
Maxim Devaev
40be0c20e2 Bump version: 5.24 → 5.25 2022-10-31 16:34:13 +03:00
Maxim Devaev
f1e9d4568c Fixed #169: Userspace fix for insufficient size of H.264 buffer 2022-10-31 16:33:34 +03:00
Maxim Devaev
f4f57cce38 prettify 2022-10-22 07:01:21 +03:00
Michael Lynch
c87ad5703c
Update H264 streaming guide to include audio streaming (#181)
* Support multiple (audio+video) streams in demo janus client (#4)

* Support multiple (audio+video) streams in demo janus client

* Adjust wording in H264 guide

* Use consistent braces style

Co-authored-by: Louis Goessling <louis@goessling.com>
2022-10-22 04:43:38 +03:00
tomaszduda23
95df13b7cb
Update docker documentation (#180)
* Update README.md

* Update README.md

* replace NO_EDID=1 with EDID=1

* doc update
2022-10-22 04:41:54 +03:00
Maxim Devaev
bfa1516491 Bump version: 5.23 → 5.24 2022-10-19 08:13:54 +03:00
tomaszduda23
36c9ff22b3
set HDMI EDID before starting ustreamer (#179)
* build docker image

* Push each image to an user github registry.
It can be used during development for testing.

* set HDMI EDID before starting ustreamer

* Update README.md
2022-10-18 20:11:29 +03:00
Maxim Devaev
17f54a7977
Merge pull request #178 from tomaszduda23/github_registry
GitHub registry
2022-10-18 12:08:58 +03:00
Maxim Devaev
574986d0fd added --instance-id to the manpage 2022-10-16 02:54:28 +03:00
Maxim Devaev
d5ce2e835f report version 2022-10-13 02:10:16 +03:00
Maxim Devaev
6201554ba1 instance id 2022-10-12 20:00:37 +03:00
Maxim Devaev
75dee4e91d
Merge pull request #177 from tomaszduda23/master
build docker image
2022-10-09 21:09:32 +03:00
Tomasz Duda
e1b4e0db66 Push each image to an user github registry.
It can be used during development for testing.
2022-10-09 15:17:35 +02:00
Tomasz Duda
0d37b09bb4 build docker image 2022-10-09 13:28:46 +02:00
Maxim Devaev
18767b68ff
Merge pull request #176 from tomaszduda23/master
check compilation
2022-10-03 16:47:05 +03:00
Tomasz Duda
7975615c6c check compilation 2022-10-03 02:31:31 +02:00
Maxim Devaev
90f09b197e Bump version: 5.22 → 5.23 2022-09-25 17:17:28 +03:00
Maxim Devaev
687e97d523 Issue #169: Reverted MIN_QP to 16 2022-09-25 17:16:31 +03:00
Maxim Devaev
c1675001fa enabled paypal 2022-09-23 19:04:48 +03:00
Maxim Devaev
69cc45a2a0 Bump version: 5.21 → 5.22 2022-09-09 16:25:02 +03:00
Maxim Devaev
ece96b5834 changed arch mirror 2022-09-09 16:22:54 +03:00
Maxim Devaev
383ed7530b Issue #169: changed min QP to avoid stream corruption 2022-09-09 15:49:00 +03:00
Maxim Devaev
7f620c758f new style typing 2022-09-04 17:12:56 +03:00
Maxim Devaev
fb50eea526 Bump version: 5.20 → 5.21 2022-08-27 07:27:40 +03:00
Maxim Devaev
63f757a9da readme update 2022-08-27 07:25:44 +03:00
Maxim Devaev
4ec02d46b9 Bump version: 5.19 → 5.20 2022-08-17 17:59:16 +03:00
Maxim Devaev
527afb66df fixed arch deps 2022-08-17 17:55:59 +03:00
Maxim Devaev
5502758a7e Bump version: 5.18 → 5.19 2022-07-30 13:10:16 +03:00
Maxim Devaev
faa1776407 simplified us_errno_to_string() 2022-07-30 13:05:00 +03:00
Maxim Devaev
3d7fb8c8dd fixed #162: optional sigabbrev_np() 2022-07-30 02:59:21 +03:00
Maxim Devaev
b5f814d71e Bump version: 5.17 → 5.18 2022-07-29 15:16:49 +03:00
Maxim Devaev
6eafd4156a us_signum_to_string() 2022-07-29 14:34:41 +03:00
Maxim Devaev
df1e4eaa06 refactoring 2022-07-29 13:44:48 +03:00
Maxim Devaev
d2bef81b03 refactoring 2022-07-29 13:05:47 +03:00
Maxim Devaev
7b3dffd072 refactoring 2022-07-29 12:41:47 +03:00
Maxim Devaev
1a6e9998fb missing destroy 2022-07-29 12:21:13 +03:00
Maxim Devaev
9ab9561803 global variables prefix 2022-07-21 16:29:20 +03:00
Maxim Devaev
11f0b80228 Bump version: 5.16 → 5.17 2022-07-20 16:46:58 +03:00
Maxim Devaev
85e2dbd69e lint fix 2022-07-20 16:42:49 +03:00
Maxim Devaev
b693c24411 refactoring 2022-07-20 14:55:47 +03:00
Maxim Devaev
54af47fc43 fix 2022-07-20 13:04:18 +03:00
Maxim Devaev
1c1e3b0875 US_ARRAY_ITERATE() 2022-07-20 12:54:13 +03:00
Maxim Devaev
2c9334d53f refactoring, const 2022-07-20 11:20:48 +03:00
Maxim Devaev
5c747a5b5d refactoring 2022-07-20 06:05:05 +03:00
Maxim Devaev
cbee3adb2e using us_ prefixes 2022-07-19 11:02:36 +03:00
Maxim Devaev
e3293d6887 Bump version: 5.15 → 5.16 2022-07-16 23:01:02 +03:00
Maxim Devaev
9d1a42631e option for zero playout-delay 2022-07-16 12:15:51 +03:00
Maxim Devaev
13f522e81d check queue before free in macro 2022-07-16 12:02:55 +03:00
Maxim Devaev
28f13f7514 config structure 2022-07-16 11:51:43 +03:00
Maxim Devaev
2f86f818cc Bump version: 5.14 → 5.15 2022-07-16 06:31:36 +03:00
Maxim Devaev
1ffcd83993 commented playout-delay 2022-07-16 06:17:02 +03:00
Maxim Devaev
9b46c2e597 Bump version: 5.13 → 5.14 2022-07-16 02:16:02 +03:00
Maxim Devaev
ad1b63890a playout delay 2022-07-15 22:07:27 +03:00
Maxim Devaev
ad79bd0957 Revert "latency test"
This reverts commit 021823bcba216ad03c62fcaa7e38a9917fdb459c.
2022-07-13 15:28:52 +03:00
Maxim Devaev
021823bcba latency test 2022-07-13 15:08:25 +03:00
Maxim Devaev
7b0e171e74 refactoring 2022-07-13 14:41:54 +03:00
Maxim Devaev
b24f106ce7 moved ready flag to _plugin_init() 2022-07-13 08:42:24 +03:00
Maxim Devaev
20f056668f separate memsink and rtp threads 2022-07-13 06:41:04 +03:00
Maxim Devaev
f9439c785f refactoring 2022-07-12 10:02:26 +03:00
Maxim Devaev
5e364fb88b refactoring 2022-07-12 08:58:55 +03:00
Maxim Devaev
69dc9b8b49 separate locks for audio and video 2022-07-11 07:48:23 +03:00
Maxim Devaev
42237d9728 reverted client lock 2022-07-11 00:22:16 +03:00
Maxim Devaev
17bd25d497 increased video queue size 2022-07-11 00:18:31 +03:00
Maxim Devaev
a2ac1f8067 fix 2022-07-10 23:35:48 +03:00
Maxim Devaev
fdb1b2d562 using 0 timeout in audio.c 2022-07-10 23:32:00 +03:00
Maxim Devaev
fd2bf5ea25 separate thread for each client 2022-07-10 23:31:35 +03:00
Maxim Devaev
c874929e9d long double queue timeout 2022-07-10 03:24:38 +03:00
Maxim Devaev
db5b9d3cd7 renamed jlogging.h to logging.h 2022-07-08 22:51:50 +03:00
Maxim Devaev
12ab66be43 refactoring 2022-07-08 22:48:03 +03:00
Maxim Devaev
27d25a59d8 refactoring 2022-07-08 20:50:38 +03:00
Maxim Devaev
71991254a5 renamed config.h to const.h 2022-07-08 20:33:30 +03:00
Maxim Devaev
50e8469a59 refactoring 2022-07-08 20:29:57 +03:00
Maxim Devaev
3f45debca0 refactoring 2022-07-08 07:43:32 +03:00
Maxim Devaev
627b614ab5 refactoring 2022-07-08 04:29:13 +03:00
Maxim Devaev
f11d390b22 Bump version: 5.12 → 5.13 2022-07-05 08:08:20 +03:00
Maxim Devaev
f1e50b6f9b refactoring, using h264 5.1 profile for resolutions > 1920x1080 2022-07-05 07:53:41 +03:00
Maxim Devaev
fdf3340a7d Bump version: 5.11 → 5.12 2022-07-05 00:50:48 +03:00
Maxim Devaev
02513be220 don't assert if m2m encoder is not successfully prepared 2022-07-05 00:48:22 +03:00
Maxim Devaev
d29ce42f08 Bump version: 5.10 → 5.11 2022-06-28 23:12:59 +03:00
Maxim Devaev
aa6fc7fe04 Bump version: 5.9 → 5.10 2022-06-28 23:03:46 +03:00
Maxim Devaev
c91341a375 fixed missing frame_encoding_begin() for noop encoder 2022-06-28 18:51:30 +03:00
Maxim Devaev
3de7e26a36 Bump version: 5.8 → 5.9 2022-06-09 03:37:10 +03:00
Maxim Devaev
63cc66e8a7 improved logging 2022-06-09 03:30:35 +03:00
Maxim Devaev
92a090dec3 Bump version: 5.7 → 5.8 2022-06-07 07:53:00 +03:00
Maxim Devaev
8b0ef8a271 renambed memsink.object to video.sink 2022-06-07 07:48:48 +03:00
Maxim Devaev
a360f1901e Bump version: 5.6 → 5.7 2022-06-07 05:32:38 +03:00
Maxim Devaev
ed2d5f3af4 not based 2022-06-07 05:25:05 +03:00
Maxim Devaev
b935dd1fe8 refactoring 2022-06-07 05:00:35 +03:00
Maxim Devaev
6e1f60a36d get rid of ATOMIC_VAR_INIT 2022-06-07 04:58:09 +03:00
Maxim Devaev
210dfcfa4f lint fix 2022-06-07 04:51:46 +03:00
Maxim Devaev
ec10a9e3fe using c17 2022-06-07 04:48:28 +03:00
Maxim Devaev
217d146378 log fix 2022-06-07 03:01:27 +03:00
Maxim Devaev
3e2a43e2af speed up cppcheck 2022-06-07 02:54:39 +03:00
Maxim Devaev
2e0a19c1cb tc358743 hacks 2022-06-06 20:26:09 +03:00
Maxim Devaev
054748234e refactoring 2022-06-06 19:15:55 +03:00
Maxim Devaev
53873e9ddb refactoring 2022-06-06 17:39:03 +03:00
Maxim Devaev
c21d0aef7e moved xioctl() to libs 2022-06-06 17:06:00 +03:00
Maxim Devaev
e505a56910 refactoring 2022-06-06 16:59:03 +03:00
Maxim Devaev
f4278f32c4 refactoring 2022-06-06 16:36:55 +03:00
Maxim Devaev
e9a6db02f6 refactoring 2022-06-06 16:17:34 +03:00
Maxim Devaev
63fe32ddd9 refactoring 2022-06-06 15:15:37 +03:00
Maxim Devaev
710652073a refactoring 2022-06-06 14:35:31 +03:00
Maxim Devaev
dded49cd83 resampler 2022-06-06 14:19:11 +03:00
Maxim Devaev
0f753dc654 queue fix 2022-06-06 02:04:16 +03:00
Maxim Devaev
c505a423af refactoring 2022-06-03 07:25:41 +03:00
Maxim Devaev
1cff2545b1 Bump version: 5.5 → 5.6 2022-06-02 02:18:13 +03:00
Maxim Devaev
3d994d6e67 fixed deps 2022-06-02 02:15:50 +03:00
Maxim Devaev
7175f8d569 Bump version: 5.4 → 5.5 2022-06-02 01:47:54 +03:00
Maxim Devaev
d4a9862a18 webrtc audio 2022-06-01 07:57:34 +03:00
Maxim Devaev
3d4e9fbb1a Bump version: 5.3 → 5.4 2022-04-29 16:45:30 +03:00
Maxim Devaev
4a59b038ec refactoring 2022-04-29 16:44:13 +03:00
Maxim Devaev
6795744aea
Merge pull request #153 from FallingSnow/cors
Better CORS support
2022-04-29 13:59:25 +03:00
Maxim Devaev
798fe04905
Merge pull request #152 from FallingSnow/patch-1
Add alpine linux instructions to readme
2022-04-29 13:56:27 +03:00
Ayrton Sparling
1460de95c1
Add "better cors" to /stream http endpoint
Signed-off-by: Ayrton Sparling <ayrton@sparling.us>
2022-04-28 08:40:15 -07:00
Ayrton Sparling
358950d0c2
Better CORS support
Current CORS support only adds an Access-Control-Allow-Origin header to some requests. It also does not support the OPTIONS preflight sent by modern browsers. This commit adds support for OPTIONS preflight as well as more CORS headers.

The OPTIONS preflight is sent without any credentials attached to it so it mist take place before Authorization header processesing. Firefox 99 requires other access control headers like Access-Control-Allow-Headers be returned for a successful CORS interaction.

Signed-off-by: Ayrton Sparling <ayrton@sparling.us>
2022-04-26 16:28:57 -07:00
Ayrton Sparling
caef82d96f
Change WITH_PTHREAD_NP=false to WITH_PTHREAD_NP=0 2022-04-21 13:34:05 -07:00
Ayrton Sparling
dff7dd7087
Add alpine linux instructions to readme
Alpine's uses musl rather than libc and does not have support for `pthread_getname_np`. Therefore it must be build with `WITH_PTHREAD_NP=false`.
2022-04-21 10:34:25 -07:00
Maxim Devaev
79bb881a34
Merge pull request #150 from tiny-pilot/h264-guide
Add documentation for integrating with Janus to stream H.264 video
2022-04-16 19:34:19 +03:00
Michael Lynch
f0763e3865
Tweak language in H264 guide (#3)
* Adjust wording in uStreamer Janus guide

* Small tweaks

* Small changes

* Updating based on review notes
2022-04-13 09:50:15 -04:00
jotaen4tinypilot
a8dfa96db0
Document guide for H.264 (#2)
* Setup guide for H.264

* Phrasing, structure

* Remove MJPEG primer and rewrite intro

* Link to library files directly

* Adjust page title

* Remove todo

* Resolve todo (Janus setup)

* Comment all JavaScript

* Rephrase to use active voice, clarify details

* Phrasing and clarifications

* Write backend instructions

* Grammar

* Use generic URL with port number

* Use consistent terminology

* Use term “V4L2 device” to refer to the video device

* Link “building” section of uStreamer README

* Use active voice

* Change page title to “demo”

* Add comment about the `janus.js` library file

* Use window location to construct server URL

* Move `videoElement` variable closer to it’s usage

* Elaborate why we clone the media stream

* Drop obsolete `WITH_OMX` option

* Correct path of shared object file

* Fix shadowing variable of same name

* Send `start` request to avoid (harmless) `400` response

* Add `refcount.h` fix

Co-authored-by: Jan Heuermann <jan@jotaen.net>
2022-04-13 06:31:15 -04:00
Maxim Devaev
7ceb8a3da5 Bump version: 5.2 → 5.3 2022-04-01 18:35:34 +03:00
Maxim Devaev
9344059e0e systemd is required for arch now 2022-04-01 18:33:05 +03:00
Maxim Devaev
ee2b1afe5b note about kernel support 2022-04-01 18:30:21 +03:00
Maxim Devaev
31f47f2eac Bump version: 5.1 → 5.2 2022-03-21 15:01:23 +03:00
Maxim Devaev
69a9bafcd5 moved platform checks to tools.h 2022-03-21 15:00:06 +03:00
Maxim Devaev
dbecdf5d9b disabled paypal 2022-03-20 03:05:23 +03:00
Maxim Devaev
967574a78a Bump version: 5.0 → 5.1 2022-03-15 16:23:24 +03:00
Maxim Devaev
bb4f6f3993 h264 max kbps is 20000 2022-03-15 16:22:07 +03:00
Maxim Devaev
fdc955a713 Bump version: 4.13 → 5.0 2022-03-02 14:42:31 +03:00
Maxim Devaev
322c67f7a5 fix 2022-02-21 05:11:25 +03:00
Maxim Devaev
7aa7f43c7e fix 2022-02-21 05:11:25 +03:00
Maxim Devaev
8ef5505a48 only required m2m options 2022-02-21 05:11:25 +03:00
Maxim Devaev
481f2c9479 readme fix 2022-02-21 05:11:25 +03:00
Maxim Devaev
924ebf7c77 disabled dma for m2m jpeg, fixed device path 2022-02-21 05:11:25 +03:00
Maxim Devaev
d67af7a9dc copyright bump 2022-02-21 05:11:25 +03:00
Maxim Devaev
e6be119ab5 changed some log messages 2022-02-21 05:11:25 +03:00
Maxim Devaev
ead06e741a removed grabbed_mutex 2022-02-21 05:11:25 +03:00
Maxim Devaev
fbabba29ed refactoring 2022-02-21 05:11:25 +03:00
Maxim Devaev
ff9b0f5396 fixed readme 2022-02-21 05:11:25 +03:00
Maxim Devaev
52a31f619b refactoring 2022-02-21 05:11:25 +03:00
Maxim Devaev
743e8ac828 using setuptools 2022-02-21 05:11:25 +03:00
Maxim Devaev
cecec39281 refactoring 2022-02-21 05:11:25 +03:00
Maxim Devaev
280254e231 fixed m2m buffers messing 2022-02-21 05:11:25 +03:00
Maxim Devaev
97a1aa04cb allow_dma flag 2022-02-21 05:11:25 +03:00
Maxim Devaev
e14644572b refactoring 2022-02-21 05:11:25 +03:00
Maxim Devaev
55e0a096e7 renamed m2m encoders 2022-02-21 05:11:25 +03:00
Maxim Devaev
e68e6b6fae renamed MJPG to MJPEG 2022-02-21 05:11:25 +03:00
Maxim Devaev
aa03e1610f supported /dev/video21 2022-02-21 05:11:25 +03:00
Maxim Devaev
2b969dd20d encode_end_ts on noop 2022-02-21 05:11:25 +03:00
Maxim Devaev
c51984c9d9 common --m2m-device option 2022-02-21 05:11:25 +03:00
Maxim Devaev
459060532a m2m jpeg quality 2022-02-21 05:11:25 +03:00
Maxim Devaev
016ee1c554 using actual bitrate range 2022-02-21 05:11:25 +03:00
Maxim Devaev
26e0c9d54c refactoring 2022-02-21 05:11:25 +03:00
Maxim Devaev
e6584da7c8 renamed v4l2 encoder to m2m 2022-02-21 05:11:25 +03:00
Maxim Devaev
630ad66cc8 bunch of fixes for m2m 2022-02-21 05:11:23 +03:00
Maxim Devaev
222b9a0309 mjpeg v4l2 encoding 2022-02-21 05:08:48 +03:00
Maxim Devaev
235a1765d2 fixed m2m encoder fd 2022-02-21 05:08:48 +03:00
Maxim Devaev
bff8a5d854 unused dest_role 2022-02-21 05:08:48 +03:00
Maxim Devaev
fd65ed006a simplified m2m usage 2022-02-21 05:08:48 +03:00
Maxim Devaev
52478df3cf dma_fd in frame 2022-02-21 05:08:48 +03:00
Maxim Devaev
7732841cdf refactoring 2022-02-21 05:08:48 +03:00
Maxim Devaev
10aa33d899 common m2m code, fixed non-dma dq 2022-02-21 05:08:48 +03:00
Maxim Devaev
bd6fa7b0dc renamed zero_copy to dma 2022-02-21 05:08:48 +03:00
Maxim Devaev
536f45eff5 fixed man 2022-02-21 05:08:48 +03:00
Maxim Devaev
bb1dcc005c configure quant_min and quant_max 2022-02-21 05:08:48 +03:00
Maxim Devaev
ea313d3517 changed some log messages 2022-02-21 05:08:48 +03:00
Maxim Devaev
78a12f7ed2 Issue #83: Moved H.264 to h264_v4l2m2m 2022-02-21 05:08:48 +03:00
Maxim Devaev
281f4eb3a0 replaced MEMSET_ZERO to initializers 2022-02-21 05:08:48 +03:00
Maxim Devaev
dabaab4987 renamed buf_info to buf 2022-02-21 05:08:48 +03:00
Maxim Devaev
61ab2a81a5 Bump version: 4.12 → 4.13 2022-02-21 05:06:06 +03:00
Maxim Devaev
d9b511c69f doc update 2022-02-21 05:04:42 +03:00
Maxim Devaev
4d9e72e313 man fix 2022-02-15 01:33:04 +03:00
Maxim Devaev
616a3eb6a6 new option --exit-on-no-clients 2022-02-14 02:44:58 +03:00
Maxim Devaev
3a8f035014 refactoring 2022-02-14 01:47:13 +03:00
Maxim Devaev
be2e0f11da systemd socket activation 2022-02-13 18:59:33 +03:00
Maxim Devaev
fec76dc9eb Bump version: 4.11 → 4.12 2022-02-11 20:53:52 +03:00
Maxim Devaev
48826208fd Fixed #143: Show HDMI info 2022-02-11 20:00:47 +03:00
Maxim Devaev
dd4fda6f5d fixed indent 2022-02-11 19:57:15 +03:00
Maxim Devaev
1dafb54621
Merge pull request #140 from fphammerle/readme-libopenmaxil-on-bullseye
readme: suggest adding `/opt/vc/lib/` to library search path on bullseye
2022-02-05 14:44:26 +03:00
Maxim Devaev
19f9567098 added comment about evbuffer_add_file() 2022-02-05 14:35:07 +03:00
Maxim Devaev
b3d1f06e5d
Merge pull request #141 from russdill/evbuffer_add_file_fix
Don't close evbuffer_add_file's file descriptor
2022-02-05 14:30:24 +03:00
Maxim Devaev
f17069153d fixed quotes 2022-02-05 14:12:54 +03:00
Maxim Devaev
8a7d7b9c54
Merge pull request #142 from russdill/client-key-in-state
Expose supplied client key in state
2022-02-05 14:09:34 +03:00
Russ Dill
df7649d56f Expose supplied client key in state
The client key and id is currently supplied in a cookie. This
provides a way for a client to determine it's id in order to match
it within the state response. However if cookies are disabled or
the source domain differs from the ustreamer domain the cookie will
not be set.

This provides an alternate way for a client to find the state
response associated with it's connection by including the client
provided key in the state response. If the client does not supply
a key, the value 0 is supplied.

Signed-off-by: Russ Dill <russ.dill@gmail.com>
2022-02-04 23:07:41 -08:00
Russ Dill
58cc227cba Don't close evbuffer_add_file's file descriptor
evbuffer_add_file takes ownership for the file descriptor we pass
and manages closing it. Closing it ourselves will lead to the function
only being able to make very small transfers.

Signed-off-by: Russ Dill <russ.dill@gmail.com>
2022-02-04 23:06:22 -08:00
Fabian Peter Hammerle
76be4a1d10
readme: suggest adding /opt/vc/lib/ to library search path on bullseye
On Raspberry Pi OS Bullseye:
```sh
$ make WITH_OMX=1
[...]
== LD ustreamer.bin
/usr/bin/ld: warning: libbrcmGLESv2.so, needed by /opt/vc/lib/libopenmaxil.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: warning: libbrcmEGL.so, needed by /opt/vc/lib/libopenmaxil.so, not found (try using -rpath or -rpath-link)
[...]
$ ./ustreamer --features
./ustreamer: error while loading shared libraries: libopenmaxil.so: cannot open shared object file: No such file or directory
$ LD_LIBRARY_PATH=/opt/vc/lib/ ./ustreamer --features
+ WITH_OMX
- WITH_GPIO
+ WITH_PTHREAD_NP
+ WITH_SETPROCTITLE
+ HAS_PDEATHSIG
```
2022-01-30 19:57:43 +01:00
Maxim Devaev
496c9300fc Bump version: 4.10 → 4.11 2021-12-31 01:28:51 +03:00
Maxim Devaev
5b7780cf7c using python-3.10 2021-12-31 01:26:47 +03:00
Maxim Devaev
421f4a1f2e Bump version: 4.9 → 4.10 2021-11-28 08:43:41 +03:00
Maxim Devaev
55a5d4bbdd real bitrate range 2021-11-28 08:40:56 +03:00
Maxim Devaev
666ae0c4f1
Merge pull request #133 from jtrmal/patch-1
correct/updated package name is libgpiod-dev
2021-11-26 03:33:04 +03:00
Jan "yenda" Trmal
ca3afc074c
correct/updated package name is libgpiod-dev 2021-11-21 12:19:03 -05:00
Maxim Devaev
030077fb47 Bump version: 4.8 → 4.9 2021-10-30 11:40:31 +03:00
Maxim Devaev
61ddff8b6e
Merge pull request #129 from b-rad15/master
Change html links to relative not absolute
2021-10-30 11:27:20 +03:00
Bradley O'Connell
27fbfe149e changed html links to relative not absolute 2021-10-30 03:20:57 -04:00
Maxim Devaev
349f655cd9 Bump version: 4.7 → 4.8 2021-10-19 15:56:19 +03:00
Maxim Devaev
f5c0a15967 removed ansible link 2021-10-19 15:55:34 +03:00
Maxim Devaev
d822ae0890 Fixed #121: static inline 2021-10-19 15:47:42 +03:00
Maxim Devaev
2d82adb2ba Bump version: 4.6 → 4.7 2021-10-17 00:14:25 +03:00
Maxim Devaev
56b21274d1 ru doc, etc 2021-10-17 00:13:01 +03:00
Maxim Devaev
76329ba5a6 Fixed #128: added mjpg_streamer compatibility for --static 2021-10-17 00:08:13 +03:00
Maxim Devaev
4d5c5fffb4
Merge pull request #127 from tiny-pilot/nginx-doc
Document how to integrate uStreamer with nginx
2021-10-17 00:07:55 +03:00
Michael Lynch
29234c330b Document how to integrate uStreamer with nginx
This change documents a gotcha that can occur when clients deploy uStreamer behind an nginx proxy. By default, nginx buffers responses, which introduces latency into the video stream. Disabling the buffer eliminates this latency.
2021-10-14 10:02:42 -04:00
Maxim Devaev
690d291818 Bump version: 4.5 → 4.6 2021-09-27 23:14:24 +03:00
Maxim Devaev
8cfb3fc301 lint fix 2021-09-27 22:55:01 +03:00
Maxim Devaev
69ab3fa831 ustreamer-dump: count and interval 2021-09-27 22:54:08 +03:00
Maxim Devaev
79d6c76084 Bump version: 4.4 → 4.5 2021-08-13 11:02:34 +03:00
Maxim Devaev
f15d2fc194 fixed .dockerignore 2021-08-13 10:49:23 +03:00
Maxim Devaev
99aa4828c5 fixed .gitignore 2021-08-13 10:48:41 +03:00
Maxim Devaev
93969f487c Issue #115: Comment about rtp_wrap_h264() 2021-08-13 10:39:24 +03:00
Maxim Devaev
61407c6596 Issue #115: Avoid usage of uninitialized last_client_ts 2021-08-13 10:36:53 +03:00
Maxim Devaev
6c95a56f3a
Merge pull request #119 from PCPartPicker/vcpr_h264_rtp_mark_fix
Fix h264 rtp packetization - the marked bit should only be set for th…
2021-08-05 01:46:22 +03:00
aggieNick02
283f53a961 Fix h264 rtp packetization - the marked bit should only be set for the very last packet of each access unit (frame) - previously it was being set for the last packet of each type 1-5 NALU, which is equivalent and correct if the frame contains only one type 1-5 NALU, but not if it contains more 2021-08-03 23:10:21 -05:00
Maxim Devaev
ef8fb8216e .editorconfig 2021-07-25 04:23:24 +03:00
Devaev Maxim
6293d54296 Bump version: 4.3 → 4.4 2021-06-10 19:32:24 +03:00
Devaev Maxim
c20754e62c Bump version: 4.2 → 4.3 2021-06-10 19:10:51 +03:00
Devaev Maxim
c621e36929 fixed deps 2021-06-10 19:10:13 +03:00
Devaev Maxim
484ff0f32b Bump version: 4.1 → 4.2 2021-06-10 19:06:50 +03:00
Devaev Maxim
adb60124fb compile janus if provided 2021-06-10 19:06:12 +03:00
Devaev Maxim
767d8ac240 Bump version: 4.0 → 4.1 2021-06-10 18:56:00 +03:00
Devaev Maxim
4648ecba17 fixed install 2021-06-10 18:55:19 +03:00
Devaev Maxim
0a08ca657d Bump version: 3.27 → 4.0 2021-06-10 18:48:00 +03:00
Devaev Maxim
48f35ccd32 janus plugin 2021-06-10 18:42:16 +03:00
Devaev Maxim
3558089a22 get_now_monotonic_u64() 2021-06-08 18:55:52 +03:00
Devaev Maxim
47b735c51f Bump version: 3.26 → 3.27 2021-04-26 20:37:27 +03:00
Maxim Devaev
e60991974e
Merge pull request #109 from netbr/master
Option to rotate image for Raspberry Pi Camera Module
2021-04-24 01:31:47 +03:00
netbr
3a16d17f8f
Update options.c 2021-04-21 07:57:16 +02:00
netbr
08ddb351c7
Update device.h 2021-04-21 07:53:14 +02:00
netbr
3f251a2459
Update device.c 2021-04-21 07:52:06 +02:00
Devaev Maxim
a97f08eac8 Bump version: 3.25 → 3.26 2021-04-20 19:35:49 +03:00
Devaev Maxim
92ff097d7e show memsink has_client in the /state 2021-04-08 08:42:53 +03:00
Devaev Maxim
0eb0370bd3 moved python sources 2021-04-08 05:19:19 +03:00
Devaev Maxim
b329dfed1f Bump version: 3.24 → 3.25 2021-04-07 00:46:39 +03:00
Devaev Maxim
62dc2e5ad5 Fixed #105: using $(MAKE) 2021-04-06 23:43:32 +03:00
Devaev Maxim
7ea81fa627 fix 2021-04-03 06:42:33 +03:00
Devaev Maxim
8ddf77a3d3 Bump version: 3.23 → 3.24 2021-04-03 02:12:22 +03:00
Devaev Maxim
fde89465ac global variables prefix 2021-04-02 13:31:34 +03:00
Devaev Maxim
aa1d78a3cd fix 2021-04-02 12:39:09 +03:00
Devaev Maxim
6dfe077775 refactoring 2021-04-02 12:38:38 +03:00
Devaev Maxim
dd6dc866a6 Fixed #103: symlinks without .bin suffix 2021-04-02 09:01:29 +03:00
Devaev Maxim
3bf68884f5 improved linting 2021-04-02 02:18:27 +03:00
Devaev Maxim
e1b2eceea5 split makefiles 2021-04-02 01:00:22 +03:00
Devaev Maxim
7ce11fecb6 RN 2021-04-01 13:14:58 +03:00
Devaev Maxim
cbc6145977 using strerror_l() 2021-04-01 10:21:12 +03:00
Devaev Maxim
24f7fb797b refactoring 2021-04-01 09:32:29 +03:00
Devaev Maxim
71f1d397bf moved errno_to_string() to the tools 2021-04-01 08:49:18 +03:00
Devaev Maxim
00e83c155e removed managed flag 2021-03-30 10:16:55 +03:00
Devaev Maxim
1f186a0afe refactoring 2021-03-30 10:11:14 +03:00
Devaev Maxim
3c7075d0d2 removed frame name 2021-03-30 10:02:03 +03:00
Devaev Maxim
c1d7bd1595 link fix 2021-03-30 03:21:04 +03:00
Devaev Maxim
04d1d3d5a6 some common macroses 2021-03-30 02:18:40 +03:00
Devaev Maxim
01bc0529e7 refactoring 2021-03-28 22:48:17 +03:00
Devaev Maxim
181231f3ff common list operations 2021-03-28 21:51:34 +03:00
Devaev Maxim
58569f0315 separate major and minor numbers 2021-03-28 08:04:12 +03:00
Maxim Devaev
5903fcf718
Update README.ru.md 2021-03-27 22:45:24 +03:00
Maxim Devaev
1a820b23a5
Merge pull request #101 from Lennie/patch-1
Update README.md
2021-03-27 22:44:51 +03:00
Lennie
67b767b152
Update README.md
On my system I needed to use -d my guess is it was just a typo from you ?
2021-03-27 20:34:33 +01:00
Devaev Maxim
74bf710bb6 Bump version: 3.22 → 3.23 2021-03-26 22:03:40 +03:00
Devaev Maxim
1d3b428a75 using asprintf 2021-03-25 14:23:43 +03:00
Devaev Maxim
749bc5caf7 const 2021-03-25 13:35:56 +03:00
Devaev Maxim
3b7cbc62c4 Bump version: 3.21 → 3.22 2021-03-22 07:07:56 +03:00
Devaev Maxim
dff49d8e7b h264 mmal brokes on 0 fps and 640x480 2021-03-22 07:07:21 +03:00
Maxim Devaev
b23883e65f
Update README.md 2021-03-21 17:57:37 +03:00
Maxim Devaev
c2e30c7fc4
Update README.ru.md 2021-03-21 17:57:01 +03:00
Devaev Maxim
37216250b8 Bump version: 3.20 → 3.21 2021-03-21 03:58:35 +03:00
Devaev Maxim
dc82894038 Issue #100: handle X-Forwarded-For 2021-03-21 03:35:47 +03:00
Devaev Maxim
f3a350148e Bump version: 3.19 → 3.20 2021-03-14 18:43:53 +03:00
Devaev Maxim
5e49f50e22 don't cache dict_frame 2021-03-14 18:16:41 +03:00
Devaev Maxim
91ff97f721 Bump version: 3.18 → 3.19 2021-03-14 03:03:54 +03:00
Devaev Maxim
daaa488dd6 segfault fix 2021-03-14 03:03:18 +03:00
Devaev Maxim
bad05a6827 Bump version: 3.17 → 3.18 2021-03-07 16:35:22 +03:00
Devaev Maxim
89d3ed98c7 readme fix 2021-03-07 16:23:22 +03:00
Devaev Maxim
1187ace2a2 removed libuuid dep and using get_now_id() 2021-03-07 14:54:13 +03:00
Devaev Maxim
9704fb054c fixed pikvm/pikvm#222 2021-03-06 13:49:08 +03:00
Maxim Devaev
b10148a438
Update README.md 2021-03-01 07:32:00 +03:00
Devaev Maxim
1b4bcd09fe Bump version: 3.16 → 3.17 2021-02-18 06:34:26 +03:00
Devaev Maxim
245cd94a4c ru readme fix 2021-02-18 04:07:56 +03:00
Devaev Maxim
8a24a0b129 prevent hanging on vcos semaphore 2021-02-18 03:38:48 +03:00
Devaev Maxim
b362c1fa50 russian readme 2021-02-17 23:25:17 +03:00
Maxim Devaev
0414fa5a51
Merge pull request #94 from Drachenkaetzchen/patch-1
Give V4L2_PIX_FMT_MJPEG an unique name
2021-02-17 23:17:06 +03:00
Maxim Devaev
191dd16d7b
Merge pull request #91 from mtlynch/trailing-whitespace
Delete trailing whitespace from README
2021-02-17 23:15:11 +03:00
Maxim Devaev
31a03928c9
Merge pull request #90 from mtlynch/libjpeg-dev-ubuntu
Add libjpeg-dev to Ubuntu 20.04 dependencies
2021-02-17 23:14:50 +03:00
Felicia Hummel
185a8b6773
Update README.md 2021-02-17 11:48:16 +01:00
Felicia Hummel
b7501cfbda
Add MJPEG to FORMATS_STR
Add missing MJPEG option to FORMATS_STR
2021-02-12 00:17:06 +01:00
Felicia Hummel
1dc90dad7a
Give V4L2_PIX_FMT_MJPEG an unique name
This patch gives `V4L2_PIX_FMT_MJPEG` an unique name. Previously, `V4L2_PIX_FMT_MJPEG` would be chosen instead of `V4L2_PIX_FMT_JPEG` if `JPEG` was specified on the command line.

Relates to issue #40
2021-02-12 00:06:03 +01:00
Michael Lynch
45a4d148f5 Delete trailing whitespace from README 2021-02-05 15:19:10 -05:00
Michael Lynch
93ab12b54b
Add libjpeg-dev to Ubuntu 20.04 dependencies
Building on Ubuntu 20.04 fails without libjpeg-dev, so this change adds it to the README.
2021-02-05 15:18:26 -05:00
Devaev Maxim
fb07444b70 Bump version: 3.15 → 3.16 2021-02-04 01:55:16 +03:00
Devaev Maxim
a8ba2f7364 H264: disable MMAL_PARAMETER_VIDEO_ENCODE_FRAME_LIMIT_BITS 2021-02-04 01:54:42 +03:00
Devaev Maxim
171bcb315a Bump version: 3.14 → 3.15 2021-02-03 05:49:40 +03:00
Devaev Maxim
911df1af15 rename 2021-02-03 05:48:15 +03:00
Devaev Maxim
a165ff4523 expose keyframe flag 2021-02-03 05:39:18 +03:00
Devaev Maxim
5cfb3b1e60 Bump version: 3.13 → 3.14 2021-01-28 22:16:44 +03:00
Devaev Maxim
82b3b78238 ignore drop-same-frames for sinks 2021-01-27 11:30:35 +03:00
Devaev Maxim
8a82ff6691 using archlinux/archlinux:base-devel 2021-01-25 11:58:52 +03:00
Devaev Maxim
2807678551 Bump version: 3.12 → 3.13 2021-01-25 03:26:26 +03:00
Devaev Maxim
e96e0aa73c workaround for unreasonable rebuilding in package() 2021-01-25 03:21:31 +03:00
Devaev Maxim
d06c2619b2 added missing options to man 2021-01-24 12:26:43 +03:00
Devaev Maxim
6b18455d11 systemd-tmpfiles hangs 2021-01-24 12:21:08 +03:00
Maxim Devaev
217977d9cb
Merge pull request #87 from reedy/patch-1
Fix casing of macros
2021-01-22 05:22:26 +03:00
Maxim Devaev
b9f186e47c
Merge pull request #88 from reedy/patch-2
Fix below typo
2021-01-22 05:21:44 +03:00
Sam Reed
d7d56f3536
Fix below typo 2021-01-22 01:45:40 +00:00
Sam Reed
2e3c764369
Fix casing of macros 2021-01-22 01:43:50 +00:00
Devaev Maxim
7236e53813 Bump version: 3.11 → 3.12 2021-01-22 02:00:35 +03:00
Devaev Maxim
e7f7350405 fix 2021-01-21 23:49:37 +03:00
Devaev Maxim
81f0266a87 drop_same_frames in python 2021-01-21 23:37:06 +03:00
Devaev Maxim
eb1a2e3695 Bump version: 3.10 → 3.11 2021-01-21 18:55:17 +03:00
Devaev Maxim
6cfd3739d3 fixed python context manager 2021-01-21 18:54:39 +03:00
Devaev Maxim
77b8386de7 Bump version: 3.9 → 3.10 2021-01-21 16:59:47 +03:00
Devaev Maxim
a002bd3427 Makefile deps fix 2021-01-21 16:59:10 +03:00
Devaev Maxim
202b907430 Bump version: 3.8 → 3.9 2021-01-21 12:47:40 +03:00
Devaev Maxim
d9f4aba953 fixed build 2021-01-21 12:47:05 +03:00
Devaev Maxim
1b08857534 Bump version: 3.7 → 3.8 2021-01-21 12:03:35 +03:00
Devaev Maxim
eec19892fa fixed PKGBUILD 2021-01-21 12:03:01 +03:00
Devaev Maxim
40abe73391 Bump version: 3.6 → 3.7 2021-01-21 11:51:34 +03:00
Devaev Maxim
e4f1ef654f lint fix 2021-01-21 11:34:24 +03:00
Devaev Maxim
fa6afb96ce check usleep() retval 2021-01-21 11:22:04 +03:00
Devaev Maxim
c3f98b34f2 python builddeps 2021-01-21 09:27:29 +03:00
Devaev Maxim
b7b3e8e87d python: handle signals 2021-01-21 09:18:09 +03:00
Devaev Maxim
94383a2d54 moved python module 2021-01-21 07:19:10 +03:00
Devaev Maxim
184a4879eb gzip force 2021-01-21 05:23:22 +03:00
Devaev Maxim
bf48908c59 fixed another segfault 2021-01-21 05:22:35 +03:00
Devaev Maxim
66afbccf21 fixed segfault 2021-01-21 03:01:50 +03:00
Devaev Maxim
97dbe59aea handle usleep error 2021-01-21 02:56:43 +03:00
Devaev Maxim
d874fdeaec gitignore 2021-01-20 16:06:42 +03:00
Devaev Maxim
97e3938d56 trying to eliminate memory leaks 2021-01-20 16:06:32 +03:00
Devaev Maxim
87c7e8063f fixed shm umask 2021-01-20 15:17:13 +03:00
Devaev Maxim
fe5beb0114 fixed python destdir 2021-01-20 14:24:12 +03:00
Devaev Maxim
eec8e41b2b Bump version: 3.5 → 3.6 2021-01-20 14:03:48 +03:00
Devaev Maxim
87bff56a78 python module 2021-01-20 14:03:08 +03:00
Devaev Maxim
34e0e4dab4 moved flock_timedwait_monotonic() to tools.h 2021-01-20 12:36:37 +03:00
Devaev Maxim
e08ac1467f moved memsink_shared_s to separate file 2021-01-20 12:28:46 +03:00
Devaev Maxim
bc25e787cc buf fix 2021-01-20 12:27:28 +03:00
Devaev Maxim
5f11caf6fc added protocol version to memsink 2021-01-20 02:21:03 +03:00
Devaev Maxim
9be264e176 memsink magic 2021-01-18 11:27:57 +03:00
Devaev Maxim
3d28dcbaff improved memsink logic 2021-01-18 11:05:04 +03:00
Devaev Maxim
61c3b44c8a raw sink 2021-01-17 14:33:18 +03:00
Devaev Maxim
e26973a9f1 don't show help on option error 2021-01-17 09:37:44 +03:00
Devaev Maxim
598e2372e5 refactoring 2021-01-17 09:35:55 +03:00
Devaev Maxim
4fb8c7745c client ttl; some refactoring 2021-01-17 08:38:13 +03:00
Devaev Maxim
14131f0b54 check memsink clients 2021-01-17 00:06:44 +03:00
Devaev Maxim
de41c9653e pluggable outputs for the future 2021-01-15 12:38:32 +03:00
Devaev Maxim
66e0bb0a2c Bump version: 3.4 → 3.5 2021-01-15 01:30:17 +03:00
Devaev Maxim
5af18e8b70 fixed segfault on uninitialized mmal 2021-01-15 01:26:13 +03:00
Devaev Maxim
b746bc307c Bump version: 3.3 → 3.4 2021-01-13 17:46:08 +03:00
Devaev Maxim
3fd3aab909 banned option --as-needed from LDFLAGS 2021-01-13 17:45:33 +03:00
Devaev Maxim
2f1afb6044 Bump version: 3.2 → 3.3 2021-01-13 05:35:34 +03:00
Devaev Maxim
a016c1040e compatibility with freebsd install 2021-01-13 05:27:21 +03:00
Devaev Maxim
d4e9948220 Bump version: 3.1 → 3.2 2021-01-13 03:46:30 +03:00
Devaev Maxim
5d1183f5c6 http: zero_data option and X-UStreamer-Latency header 2021-01-13 03:45:45 +03:00
Devaev Maxim
c75863d4bd do not initialize omx on every startup 2021-01-13 03:30:31 +03:00
Devaev Maxim
5576cbb3b8 minor snprintf fixes 2021-01-13 01:26:11 +03:00
Devaev Maxim
4a156a692a Bump version: 3.0 → 3.1 2021-01-12 18:58:16 +03:00
Devaev Maxim
1a8dfb1f1b export h264 state 2021-01-12 15:28:42 +03:00
Devaev Maxim
d0a5246580 Bump version: 2.2 → 3.0 2021-01-12 01:42:42 +03:00
Devaev Maxim
0497e178ca fixed bumpversion 2021-01-12 01:42:08 +03:00
Devaev Maxim
3338dced5a mute 5 OMX_ErrorInsufficientResources 2021-01-12 01:32:35 +03:00
Devaev Maxim
b6d4a42fa7 refactoring 2021-01-12 00:35:28 +03:00
Devaev Maxim
e7b9ce500b fix 2021-01-11 15:14:10 +03:00
Devaev Maxim
d807f9fa87 force h264 keyframe on slowdown 2021-01-11 14:36:41 +03:00
Devaev Maxim
d95a6ad0b0 improved slowdown logic 2021-01-11 13:41:05 +03:00
Devaev Maxim
1e8a06b924 fix 2021-01-11 04:13:13 +03:00
Devaev Maxim
e72947ab8d fix 2021-01-11 04:12:14 +03:00
Devaev Maxim
4bedf7d286 enabled slowdown logic for sinks 2021-01-11 04:11:05 +03:00
Devaev Maxim
5aa9a4b7a0 last_id 2021-01-11 02:07:56 +03:00
Devaev Maxim
832915ce86 another log fix 2021-01-10 22:46:00 +03:00
Devaev Maxim
0e0c3ec023 log fix 2021-01-10 21:20:59 +03:00
Devaev Maxim
c75bd39e6a using hashes to enumerate frames in memsink 2021-01-10 19:21:29 +03:00
Devaev Maxim
d90cb1cff7 fixed alloc_size 2021-01-10 17:25:05 +03:00
Devaev Maxim
94dab648bc fix 2021-01-10 14:36:10 +03:00
Devaev Maxim
f37c1cf50c msg about h264 disabling 2021-01-10 14:31:21 +03:00
Devaev Maxim
6b9b19c077 lint fix 2021-01-10 14:27:17 +03:00
Devaev Maxim
c7d558dd6a security fix 2021-01-10 13:14:01 +03:00
Devaev Maxim
083ec30c66 renamed sink 2021-01-10 13:00:50 +03:00
Devaev Maxim
649cda2f47 h264 zero-copy 2021-01-10 00:40:39 +03:00
Devaev Maxim
302f1d297c fix 2021-01-09 16:39:04 +03:00
Devaev Maxim
cc06f2abad refactoring 2021-01-09 16:39:04 +03:00
Devaev Maxim
1e6c3b9708 refactoring 2021-01-09 15:08:34 +03:00
Devaev Maxim
310e30fdff deps tree 2021-01-09 01:03:27 +03:00
Devaev Maxim
8324b55396 fixed formatters 2021-01-08 03:39:29 +03:00
Devaev Maxim
9c679f6d5d refactoring 2021-01-08 03:33:14 +03:00
Devaev Maxim
f14f49dc92 fixed packages 2021-01-08 00:50:50 +03:00
Devaev Maxim
0a6c0335d0 help fix 2021-01-08 00:48:14 +03:00
Devaev Maxim
fedb1d4baf refactoring 2021-01-07 23:03:52 +03:00
Devaev Maxim
cfad2a8343 encoder only 2021-01-07 23:03:52 +03:00
Maxim Devaev
6969de4263
Merge pull request #81 from reedy/manpage-dump
Add upstreamer-dump manpage
2021-01-07 23:03:12 +03:00
Devaev Maxim
47994d5960 moved unjpeg from h264 to stream 2021-01-07 20:40:29 +03:00
Sam Reed
d77a7c74fb Install ustreamer-dump.1 during make install 2021-01-07 16:16:27 +00:00
Sam Reed
a2509158c6 Add upstreamer-dump manpage 2021-01-07 16:09:36 +00:00
Maxim Devaev
901146e5b4
Merge pull request #80 from reedy/patch-4
ustreamer-dump -o takes a filename argument
2021-01-07 19:01:24 +03:00
Sam Reed
35627fb64e
ustreamer-dump -o takes a filename argument 2021-01-07 15:48:26 +00:00
Maxim Devaev
dbca5e29c0
Merge pull request #79 from reedy/patch-3
Update ustreamer.1
2021-01-07 18:47:20 +03:00
Sam Reed
ab9e37a1a9
Update ustreamer.1 2021-01-07 15:43:33 +00:00
Maxim Devaev
da057b2423
Merge pull request #78 from reedy/patch-2
Update copyright year in ustreamer help
2021-01-07 18:33:20 +03:00
Maxim Devaev
17c4f5a815
Merge pull request #77 from reedy/patch-1
Update copyright in ustreamer-dump help
2021-01-07 18:32:47 +03:00
Sam Reed
0e3143c1d5
Update copyright year in ustreamer help 2021-01-07 14:48:06 +00:00
Sam Reed
70084993d8
Update copyright in ustreamer-dump help 2021-01-07 14:46:44 +00:00
Devaev Maxim
ebe1d20e69 renamed workers 2021-01-07 17:45:29 +03:00
Devaev Maxim
6377830a35 simultaneous access to memsink 2021-01-07 17:36:35 +03:00
Devaev Maxim
2ab0f34add refactoring 2021-01-07 14:51:43 +03:00
Devaev Maxim
e176b1d738 improved prepare logic 2021-01-07 14:13:38 +03:00
Devaev Maxim
3199ef3b1d info about bitrate 2021-01-07 12:54:46 +03:00
Devaev Maxim
b16b447927 refactoring 2021-01-07 12:51:31 +03:00
Devaev Maxim
b924a0fecb configurable framerate and gop for h264 2021-01-07 12:19:59 +03:00
Devaev Maxim
7883625165 verbose latency 2021-01-07 11:09:58 +03:00
Devaev Maxim
14ec7741f9 update copy 2021-01-07 10:18:25 +03:00
Devaev Maxim
d05169d6d4 refactoring 2021-01-07 10:12:07 +03:00
Devaev Maxim
63c7d35b25 dump fourcc 2021-01-07 09:58:33 +03:00
Devaev Maxim
db00971622 fix 2021-01-07 09:47:29 +03:00
Devaev Maxim
d5275cacf7 dump as json 2021-01-07 09:44:39 +03:00
Devaev Maxim
0fbb41752e man directory 2021-01-06 22:49:26 +03:00
Devaev Maxim
5c904cf766 mute 2021-01-06 22:30:18 +03:00
Devaev Maxim
ac55b260ed updated manpage 2021-01-06 22:24:44 +03:00
Devaev Maxim
8207de6bd4 fix 2021-01-06 22:18:47 +03:00
Devaev Maxim
fb19858026 ustreamer-dump 2021-01-06 22:10:26 +03:00
Devaev Maxim
c81fa7b5a2 moved files 2021-01-06 18:52:54 +03:00
Devaev Maxim
fcee60346c jpeg sink 2021-01-06 18:47:14 +03:00
Devaev Maxim
a43d09ac73 refactoring 2021-01-06 15:50:19 +03:00
Devaev Maxim
2630147a96 aligned input 2021-01-05 21:26:07 +03:00
Devaev Maxim
17bb7c77f3 removed option --glitched-resolutions 2021-01-05 14:31:27 +03:00
Devaev Maxim
7d587052ad send logs to stderr 2021-01-05 09:56:01 +03:00
Devaev Maxim
85e63f49a0 refactoring 2021-01-04 20:38:14 +03:00
Devaev Maxim
dd90d378a4 mute 2021-01-04 17:32:34 +03:00
Devaev Maxim
19b93fb237 refactoring 2021-01-04 17:27:49 +03:00
Devaev Maxim
23292e9f42 fixed link 2021-01-04 14:06:09 +03:00
Devaev Maxim
511894e6ae added sps/pps frames 2021-01-04 13:58:35 +03:00
Devaev Maxim
e479a8f08c verb 2021-01-04 13:30:27 +03:00
Devaev Maxim
944bd89b4e h264 2021-01-04 12:55:25 +03:00
Devaev Maxim
6eb5e62aae don't recreate mmal wrapper 2021-01-03 15:45:15 +03:00
Devaev Maxim
5f5afb6f69 refactoring 2021-01-03 14:53:50 +03:00
Devaev Maxim
05b86c14a7 fixes 2021-01-03 13:00:22 +03:00
Devaev Maxim
3fdd69b444 refactoring 2021-01-03 12:27:26 +03:00
Devaev Maxim
0ccf540417 refactoring 2021-01-03 10:24:49 +03:00
Devaev Maxim
619389970a initializing global object in main() 2021-01-03 10:22:29 +03:00
Devaev Maxim
f7504211e5 512 kb 2021-01-03 08:01:01 +03:00
Devaev Maxim
1b1c546a55 mute 2021-01-03 07:58:42 +03:00
Devaev Maxim
6fadbb76d1 refactoring 2021-01-03 07:23:37 +03:00
Devaev Maxim
fa846d01d7 refactoring 2021-01-03 06:59:09 +03:00
Devaev Maxim
28deafaeef don't install recorder 2021-01-03 06:17:43 +03:00
Devaev Maxim
3fc9795ade fix 2021-01-02 19:08:11 +03:00
Devaev Maxim
22fd555454 reused unjpeg 2021-01-02 18:42:16 +03:00
Devaev Maxim
4fc022f4d7 fix 2021-01-02 18:01:16 +03:00
Devaev Maxim
5936830b28 refactoring 2021-01-02 17:58:23 +03:00
Devaev Maxim
7f089201d2 refactoring 2021-01-02 17:42:49 +03:00
Devaev Maxim
e21c39e172 refactoring 2021-01-02 17:18:35 +03:00
Devaev Maxim
1054b8c10f fix 2021-01-02 15:50:22 +03:00
Devaev Maxim
9002d8e445 separated workers pool 2021-01-02 15:43:52 +03:00
Devaev Maxim
61ef6ecd95 refactoring 2021-01-02 10:41:12 +03:00
Devaev Maxim
f1fe57109e set hw quality in device_open() 2021-01-02 10:35:36 +03:00
Devaev Maxim
daaefdd391 refactoring 2021-01-02 06:49:18 +03:00
Devaev Maxim
6687548ba9 refactoring 2021-01-02 06:12:44 +03:00
Devaev Maxim
8222c17aa7 refactoring 2021-01-01 15:30:19 +03:00
Devaev Maxim
0eed7f1b89 refactoring and test recorder 2021-01-01 15:04:12 +03:00
Devaev Maxim
5375781086 h264 encoder 2021-01-01 14:42:46 +03:00
Devaev Maxim
3090de6ff6 comparsion fix 2021-01-01 09:05:38 +03:00
Devaev Maxim
2ebd1e3d4a refactoring 2021-01-01 08:43:14 +03:00
Devaev Maxim
ee4b9c6338 frame_copy_meta() 2020-12-31 10:16:06 +03:00
Devaev Maxim
775bf32a6f refactoring 2020-12-28 07:09:52 +03:00
Devaev Maxim
e36d2bded3 refactoring 2020-12-28 06:24:47 +03:00
Devaev Maxim
0949c28658 unjpeg 2020-12-27 10:46:54 +03:00
Devaev Maxim
1e8789f5e5 keep pixel format 2020-12-27 04:46:36 +03:00
Devaev Maxim
01d0ed97de using frame_s for rawsink 2020-12-27 04:18:53 +03:00
Devaev Maxim
22d108f7ad using frame_s as common data storage 2020-12-27 03:10:52 +03:00
Devaev Maxim
3b223f5c49 refactoring 2020-12-27 02:41:47 +03:00
Devaev Maxim
c352ed7f67 lint fix 2020-12-18 19:38:16 +03:00
Devaev Maxim
ccf713dc1c lint fix 2020-12-17 19:03:35 +03:00
Devaev Maxim
0d97fffb3e pass online flag to rawsink 2020-12-14 13:19:55 +03:00
Devaev Maxim
cf5f284b95 expose blank to rawsink 2020-12-14 13:15:18 +03:00
Devaev Maxim
1837f502a7 refactoring 2020-12-13 18:31:17 +03:00
Devaev Maxim
89467a0ef9 using flock for rawsink 2020-12-11 22:19:41 +03:00
Devaev Maxim
0f92e73f56 lint fix 2020-12-10 14:33:24 +03:00
Devaev Maxim
b9e4975b77 refactoring 2020-12-10 14:03:53 +03:00
Devaev Maxim
7225857fcc refactoring 2020-12-10 12:36:35 +03:00
Devaev Maxim
f943f5927c meh 2020-12-09 20:47:41 +03:00
Devaev Maxim
f966907808 fix 2020-12-09 20:34:09 +03:00
Devaev Maxim
983cb899ec man fix 2020-12-09 19:10:24 +03:00
Devaev Maxim
9e1bf2fdea using uint8_t 2020-12-09 19:06:14 +03:00
Devaev Maxim
f19ab11f76 refactoring 2020-12-09 18:44:12 +03:00
Devaev Maxim
9039aa8ac5 refactoring 2020-12-08 17:17:10 +03:00
Devaev Maxim
8fc11ac056 refactoring 2020-12-08 15:51:32 +03:00
Devaev Maxim
eebd8307c5 refactoring 2020-12-08 15:39:53 +03:00
Devaev Maxim
0d006cffa9 noop delay 2020-12-08 14:17:46 +03:00
Devaev Maxim
d94bb948eb fix 2020-12-08 12:45:47 +03:00
Devaev Maxim
5ded791ef0 quality=0 for noop encoder 2020-12-08 12:42:23 +03:00
Devaev Maxim
0ccb54b4f0 refactoring 2020-12-08 11:43:35 +03:00
Devaev Maxim
b3ad29c0c7 one encoder for noop 2020-12-08 11:17:38 +03:00
Devaev Maxim
dd86e8cb42 refactoring 2020-12-08 10:38:42 +03:00
Devaev Maxim
f7ddb635a5 refactoring 2020-12-08 10:32:10 +03:00
Devaev Maxim
2fd3bc34b5 refactoring 2020-12-08 08:13:56 +03:00
Devaev Maxim
283eba0666 refactoring 2020-12-08 07:58:53 +03:00
Devaev Maxim
5c48faa832 refactoring 2020-12-08 07:28:35 +03:00
Devaev Maxim
7bb0aae71e noop encoder 2020-12-07 22:00:41 +03:00
Devaev Maxim
07b712a46b refactoring 2020-12-07 21:06:44 +03:00
Devaev Maxim
5e18ce3806 rawsink slave 2020-12-07 20:31:04 +03:00
Devaev Maxim
e7ad86ded9 refactoring 2020-12-07 19:35:37 +03:00
Devaev Maxim
338389c219 raw sink 2020-12-07 18:06:20 +03:00
Devaev Maxim
847726c0d7 refactoring 2020-12-07 13:26:27 +03:00
Devaev Maxim
a9d50a2a74 refactoring 2020-12-07 11:58:02 +03:00
Devaev Maxim
720baf09b5 codebase refactoring 2020-12-06 18:45:10 +03:00
Devaev Maxim
b502714281 style fix 2020-12-05 03:32:38 +03:00
Devaev Maxim
92c8215d3d install manpage on gentoo 2020-12-05 03:32:02 +03:00
Devaev Maxim
348849da96 improved manpage 2020-12-05 03:29:15 +03:00
Devaev Maxim
e845d53940 install manpage 2020-12-03 16:33:29 +03:00
Devaev Maxim
c999f59ddd python bump 2020-12-03 07:06:43 +03:00
Maxim Devaev
ee144473c1
Merge pull request #64 from reedy/manpage
Create initial ustreamer manpage
2020-12-03 06:57:28 +03:00
Sam Reed
af325ed54e Add Usage section to manpage
Based on https://github.com/pikvm/ustreamer/blob/f8e26d7/README.md#usage
2020-12-01 22:53:52 +00:00
Sam Reed
0d363791fe Support ustreamer.1 manpage in bumpversion 2020-12-01 22:17:32 +00:00
Maxim Devaev
814a2eb641
Merge pull request #66 from reedy/patch-1
s/bellow/below/
2020-12-01 23:54:28 +03:00
Sam Reed
73c22fa960
s/bellow/below/ 2020-12-01 20:42:13 +00:00
Maxim Devaev
f6f9a12789
Merge pull request #63 from reedy/patch-1
Add trailing . for consistency
2020-12-01 21:53:45 +03:00
Maxim Devaev
b14c53cd10
Merge pull request #65 from reedy/patch-2
Create .gitattributes
2020-12-01 21:52:21 +03:00
Sam Reed
23164f2c16 Create .gitattributes
Removes a few files unnecessary from the source tarball
2020-12-01 16:54:19 +00:00
Sam Reed
8e7d21c1b5 Create initial ustreamer manpage 2020-12-01 16:42:46 +00:00
Sam Reed
973d1cc10e
Add trailing . for consistency 2020-12-01 15:55:57 +00:00
Devaev Maxim
3bc4afca9d Bump version: 2.1 → 2.2 2020-11-27 03:00:23 +03:00
Devaev Maxim
f43afababa Merge branch 'semwait' 2020-11-27 01:52:58 +03:00
Devaev Maxim
1b2de09438 CFG_OMX_SEMWAIT_TIMEOUT 2020-11-27 01:46:33 +03:00
Maxim Devaev
b0c54b18a5
Update README.ru.md 2020-11-26 22:42:39 +03:00
Maxim Devaev
f8e26d785f
Update README.md 2020-11-26 22:42:10 +03:00
Devaev Maxim
28563abdbc Issue #56: busyloop-based _vcos_semwait() 2020-11-13 17:02:00 +03:00
Devaev Maxim
f1a869a215 lint fix 2020-11-13 11:48:49 +03:00
Maxim Devaev
9778a805ca
Merge pull request #57 from mtlynch/fix-typos
Fix two typos in log messages
2020-11-13 11:47:26 +03:00
Michael Lynch
a008dcf99d Fix typo: quierying -> querying
This fixes a small typo in a log message from 'quierying' to 'querying'.
2020-11-12 17:26:00 -05:00
Michael Lynch
71c64e668d Fix typo in log message: EINVAL
Fixes a minor typo in a logging message from EINTVAL to EINVAL.
2020-11-12 15:38:49 -05:00
Devaev Maxim
d9b91a1d5f Bump version: 2.0 → 2.1 2020-09-29 01:42:40 +03:00
Devaev Maxim
d682a1c173 added info about mjpg-streamer compat and prettify 2020-09-28 13:54:38 +03:00
Devaev Maxim
ba03333623 better access checking 2020-09-28 10:28:42 +03:00
Devaev Maxim
c7e6e5e006 check for device access before open 2020-09-28 04:29:02 +03:00
Devaev Maxim
45b1e2f285 refactoring 2020-09-28 04:23:41 +03:00
Devaev Maxim
d9bbd8a74d refactoring 2020-09-27 22:58:13 +03:00
Devaev Maxim
37179184ae Bump version: 1.26 → 2.0 2020-09-25 00:03:14 +03:00
Devaev Maxim
fc8aba0a12 Bump version: 1.25 → 1.26 2020-09-25 00:01:13 +03:00
Devaev Maxim
0d749eada3 V for make release 2020-09-23 18:11:25 +03:00
Maxim Devaev
da6984d531
Merge pull request #49 from pikvm/allow-origin
Disabled cross-domain requests by default
2020-09-23 00:04:45 +03:00
Devaev Maxim
df14031042 readme fix 2020-09-22 21:43:01 +03:00
Maxim Devaev
03975c1a85
Update README.md 2020-09-22 18:02:27 +03:00
Maxim Devaev
214a924da3
Update README.ru.md 2020-09-22 18:01:48 +03:00
Maxim Devaev
9e6a9a2fd4
Update README.md 2020-09-22 17:59:09 +03:00
Devaev Maxim
b498ae7e38 Issue #48: Disabled cross-domain requests by default 2020-09-22 17:58:10 +03:00
Maxim Devaev
278645ce51
Merge pull request #47 from pikvm/queue-assert
Double dequeue error
2020-09-20 00:53:58 +03:00
Devaev Maxim
f1ee5514e3 style fix 2020-09-20 00:44:21 +03:00
Maxim Devaev
3900728f9f
Merge pull request #46 from pikvm/libgpiod
moved from wiringpi to libgpiod
2020-09-19 17:01:28 +03:00
Devaev Maxim
3dc083d2ef Rewrited #44: fixed memory error and leak 2020-09-19 16:55:39 +03:00
Maxim Devaev
653ebd6e88
Merge pull request #44 from schneemaier/master
Added HTTP GET parameter handling to server.c
2020-09-19 13:29:07 +03:00
Devaev Maxim
a770e7675d Issue #43: assert for double VIDIOC_DQBUF 2020-09-19 05:54:47 +03:00
Devaev Maxim
6725083be6 moved from wiringpi to libgpiod 2020-09-19 04:06:14 +03:00
Akos Schneemaier
0b39cadaad Added HTTP GET parameter handling to server.c to make URLcompatibility with mjpg streamer 2020-09-18 00:00:11 -04:00
Devaev Maxim
871b0cf132 improved logs 2020-09-18 04:19:58 +03:00
Maxim Devaev
afa888432a
Update README.ru.md 2020-09-15 23:07:22 +03:00
Maxim Devaev
a42bd147ff
Update README.md 2020-09-15 23:06:47 +03:00
Devaev Maxim
2ad8871a54 Bump version: 1.24 → 1.25 2020-09-01 08:55:28 +03:00
Devaev Maxim
266e210b04 fixed ld error 2020-09-01 08:53:33 +03:00
Devaev Maxim
0ac9f77619 Bump version: 1.23 → 1.24 2020-09-01 07:57:09 +03:00
Devaev Maxim
c1bc1d9506 fixed linter error 2020-09-01 07:52:29 +03:00
Devaev Maxim
deb37986b6 Issue #39: fixed missing gettid() syscall on *BSD 2020-09-01 06:39:12 +03:00
Devaev Maxim
ee6c555ce0 Issue #32: refactoring 2020-08-24 10:21:47 +03:00
Maxim Devaev
4395b8487f
Merge pull request #37 from PascalHonegger/master
Issue #32: Create Dockerfiles
2020-08-24 00:22:52 +03:00
Pascal Honegger
f622d03d1b Issue #32: Create Dockerfiles 2020-08-23 15:36:48 +02:00
Devaev Maxim
36e6fa7b09 added aarch64 to PKGBUILD 2020-08-23 15:56:59 +03:00
Devaev Maxim
8cf6c66f21 Fixed #35: spell fix 2020-08-22 23:32:06 +03:00
Devaev Maxim
ac9761beb2 Bump version: 1.22 → 1.23 2020-08-22 16:53:29 +03:00
Devaev Maxim
90b7a5600f Issue #24: disable software framedrop if hw_fps == desired_fps 2020-08-22 16:17:34 +03:00
Maxim Devaev
4c70baecb1
Update README.ru.md 2020-08-21 23:01:15 +03:00
Maxim Devaev
15c14bfebf
Update README.ru.md 2020-08-21 23:00:14 +03:00
Maxim Devaev
eab8043496
Update README.md 2020-08-21 22:59:11 +03:00
Devaev Maxim
53feba1248 Bump version: 1.21 → 1.22 2020-08-20 05:15:24 +03:00
Devaev Maxim
119821d5af queued_fps = 0 for no clients 2020-08-19 14:27:52 +03:00
Maxim Devaev
4faabf27ec
Merge pull request #33 from pikvm/sem-timeout
Sem timeout
2020-08-19 13:44:23 +03:00
Devaev Maxim
191f6e3c09 non-zero min-frame-size; default = 128 2020-08-19 13:20:22 +03:00
Devaev Maxim
4e51439118 bsd compat 2020-08-18 15:19:09 +03:00
Devaev Maxim
e184e187a2 option --color-effect 2020-08-18 12:15:44 +03:00
Maxim Devaev
592568c9aa
Merge pull request #31 from pikvm/flip
Options to flip image
2020-08-17 18:17:01 +03:00
Devaev Maxim
46c5a547a9 options to flip image 2020-08-17 03:49:29 +03:00
Devaev Maxim
3d097a4ffb more logs 2020-08-15 04:51:00 +03:00
Devaev Maxim
00e32c915c fixed uninitialized value 2020-08-15 04:40:55 +03:00
Devaev Maxim
d44c340dce vcos sem timeout 2020-08-15 00:10:07 +03:00
Devaev Maxim
8c18f1dffe Issue #25: fixed freebsd build 2020-08-14 03:38:05 +03:00
Devaev Maxim
c3c386ea5b Bump version: 1.20 → 1.21 2020-08-13 08:59:36 +03:00
Devaev Maxim
fa09992c46 better logging 2020-08-11 06:08:13 +03:00
Devaev Maxim
cefcd0c963 fixed ptr printing 2020-08-11 05:49:03 +03:00
Devaev Maxim
96c806071d Bump version: 1.19 → 1.20 2020-08-11 02:13:16 +03:00
Devaev Maxim
0775b35ef8 option --tcp-nodelay 2020-08-10 08:12:14 +03:00
Maxim Devaev
138d9a74d8
Update README.ru.md 2020-08-01 14:34:15 +03:00
Maxim Devaev
2a668643dc
Update README.md 2020-08-01 14:33:52 +03:00
Maxim Devaev
56312cffb5
Update README.md 2020-07-24 13:08:53 +03:00
Maxim Devaev
f553b97dba
Update README.md 2020-07-23 22:19:12 +03:00
Maxim Devaev
b619b1e096
Update README.ru.md 2020-07-23 22:18:52 +03:00
Maxim Devaev
06a32fd3ab
Update README.md 2020-07-23 22:18:20 +03:00
Devaev Maxim
8637ff5c09 Bump version: 1.18 → 1.19 2020-07-11 04:43:55 +03:00
Devaev Maxim
bd5cf7d3de fixed io method parser 2020-07-08 00:38:49 +03:00
Devaev Maxim
e488eec90c userptr 2020-07-07 09:13:07 +03:00
Devaev Maxim
6615a23361 safer picture_compare(), removed one assert 2020-07-06 14:36:05 +03:00
Devaev Maxim
a91eba8d90 fixed picture_copy(): don't copy garbage 2020-07-06 13:10:49 +03:00
Devaev Maxim
d5aebc1231 Bump version: 1.17 → 1.18 2020-07-03 22:18:48 +03:00
Devaev Maxim
5f4f46bbe6 more omx error codes 2020-07-02 23:02:40 +03:00
Devaev Maxim
61a2fe6546 refactoring 2020-07-02 18:05:41 +03:00
Devaev Maxim
98499b6604 verbose message about disconnecting client 2020-06-28 13:15:24 +03:00
Devaev Maxim
f52d090f9b updated readme 2020-06-23 07:37:20 +03:00
Maxim Devaev
ebb3df46b9
Merge pull request #21 from mtlynch/ansible-note
Add note about uStreamer Ansible role
2020-06-21 03:12:38 +03:00
Michael Lynch
8fd6659cf1 Add note about uStreamer Ansible role 2020-06-20 19:53:22 -04:00
Devaev Maxim
13ce0bbc63 fixed raspbian installation manual 2020-05-30 20:19:03 +03:00
Maxim Devaev
0add4cc25f
Update FUNDING.yml 2020-05-27 03:55:15 +03:00
Devaev Maxim
96d84b33bd new pyflakes 2020-05-22 15:43:33 +03:00
Maxim Devaev
f2dfe7641e
Create FUNDING.yml 2020-05-19 18:59:08 +03:00
Devaev Maxim
97403cbb75 Bump version: 1.16 → 1.17 2020-05-19 01:08:37 +03:00
Devaev Maxim
0663bb1035 fix for gcc 10 multiple definition errors 2020-05-19 01:07:55 +03:00
Devaev Maxim
06f4017a5b Bump version: 1.15 → 1.16 2020-05-12 09:42:48 +03:00
Devaev Maxim
6493a62c6c badges 2020-05-12 09:39:02 +03:00
Devaev Maxim
66c627c682 lint fixes 2020-05-12 09:37:02 +03:00
Devaev Maxim
8d6f5f7f8f .dockerignore 2020-05-12 09:36:48 +03:00
Devaev Maxim
f1cdfd4223 docker-based linters 2020-05-12 09:26:24 +03:00
Devaev Maxim
972c288df3 action fix 2020-05-12 09:08:23 +03:00
Devaev Maxim
fd1d2dec71 action fix 2020-05-12 09:05:26 +03:00
Devaev Maxim
331bd181bf action fix 2020-05-12 09:01:44 +03:00
Devaev Maxim
231ff37570 action fix 2020-05-12 08:54:01 +03:00
Devaev Maxim
cec0b203b3 action fix 2020-05-12 08:51:32 +03:00
Devaev Maxim
e5c9d699a3 action fix 2020-05-12 08:50:08 +03:00
Maxim Devaev
198fbc1756
Create ccpp.yml 2020-05-12 08:46:39 +03:00
Devaev Maxim
55aa443d68 linters 2020-05-12 08:43:12 +03:00
Devaev Maxim
44d6288416 lint fixes 2020-05-12 08:42:18 +03:00
Devaev Maxim
aeae342853 lint fixes 2020-05-12 08:19:26 +03:00
Devaev Maxim
d22034da96 lint fixes 2020-05-12 07:45:12 +03:00
Devaev Maxim
43788f812d type fixes 2020-05-12 07:27:42 +03:00
Devaev Maxim
95318e14d8 lint fixes 2020-05-12 06:44:59 +03:00
Devaev Maxim
67d6f15776 lint fix 2020-05-11 20:02:30 +03:00
Devaev Maxim
6c2353ce2c Bump version: 1.14 → 1.15 2020-05-03 06:38:11 +03:00
Devaev Maxim
fcdfb2930a refactoring 2020-05-03 06:38:02 +03:00
Devaev Maxim
dcc90341c9 Bump version: 1.13 → 1.14 2020-05-03 06:32:59 +03:00
Devaev Maxim
7e102c88cd log static errors as verbose 2020-05-03 06:17:43 +03:00
Devaev Maxim
e131a3ba49 typo 2020-04-30 17:27:55 +03:00
Devaev Maxim
e393be4c63 Bump version: 1.12 → 1.13 2020-03-04 03:04:44 +03:00
Devaev Maxim
cf4f5f5b2a notify parent using SIGUSR2 2020-03-04 03:04:28 +03:00
Maxim Devaev
1190059359
Update README.ru.md 2020-02-25 16:06:25 +03:00
Maxim Devaev
910b27feb4
Update README.md 2020-02-25 16:05:58 +03:00
Devaev Maxim
60ca92d367 Bump version: 1.11 → 1.12 2020-02-19 16:46:38 +03:00
Devaev Maxim
4f44c5efa1 hint 2020-02-19 16:46:13 +03:00
Devaev Maxim
3504095871 refactoring 2020-02-19 08:56:50 +03:00
Devaev Maxim
6eeb49ef75 merged ctl options 2020-02-19 08:56:50 +03:00
Devaev Maxim
9353b3474a options to reset image settings to default 2020-02-19 08:56:50 +03:00
Maxim Devaev
dfc98a67f2 Update README.ru.md 2020-02-10 06:40:25 +03:00
Maxim Devaev
904c76fa93 Update README.md 2020-02-10 06:39:37 +03:00
Devaev Maxim
f0a7ca5c94 Issue #14: added message about fileserver 2020-01-30 03:53:06 +03:00
Devaev Maxim
bb4e9db7e7 fixed serving of empty static files 2020-01-29 20:20:53 +03:00
Devaev Maxim
61f2bfa00e fixed mime guessing 2020-01-29 20:13:31 +03:00
Devaev Maxim
49eb7f6e51 fixed clang 'linker' input unused 2020-01-19 17:23:41 +03:00
Devaev Maxim
b5375b835a Fixed #11: clang warning 2020-01-19 01:07:57 +03:00
Devaev Maxim
e3e2c5cead Bump version: 1.10 → 1.11 2020-01-14 16:49:50 +03:00
Devaev Maxim
ef4150877b check for 8 bits 2020-01-14 16:46:31 +03:00
Devaev Maxim
cc00d0fea3 Bump version: 1.9 → 1.10 2019-10-21 08:20:45 +03:00
Devaev Maxim
91a1e48a7c refactoring; renamed X-UStreamer-*-Time to X-UStreamer-*-Timestamp 2019-10-21 08:16:19 +03:00
Devaev Maxim
f381113d50 new arch pkg extension 2019-10-17 02:58:56 +03:00
Devaev Maxim
a6304959f4 Bump version: 1.8 → 1.9 2019-10-11 23:37:25 +03:00
Devaev Maxim
57bc6e160d keep original argv 2019-10-11 23:37:05 +03:00
Devaev Maxim
03dd5dfebb Bump version: 1.7 → 1.8 2019-10-11 08:23:40 +03:00
Devaev Maxim
00ef8928b3 temporary disabled setproctitle to avoid memory corruption 2019-10-11 08:23:04 +03:00
Devaev Maxim
2b338cea90 Bump version: 1.6 → 1.7 2019-10-11 06:42:20 +03:00
Devaev Maxim
e64a1a1688 improved readme 2019-10-11 06:42:06 +03:00
Devaev Maxim
2ad196b95e --features 2019-10-11 06:39:15 +03:00
Devaev Maxim
70c7bcc209 --process-name-prefix 2019-10-11 06:23:32 +03:00
Devaev Maxim
890b248563 ifdef 2019-10-10 23:11:15 +03:00
Devaev Maxim
be2de06b09 manual check if the parent process is alive after prctl() 2019-10-10 23:09:24 +03:00
Devaev Maxim
6175e6974c refactoring 2019-10-10 10:44:12 +03:00
Devaev Maxim
6bc26b734e Bump version: 1.5 → 1.6 2019-10-10 02:58:59 +03:00
Devaev Maxim
da6b5fd786 fixed spell 2019-10-10 02:54:49 +03:00
Devaev Maxim
e97b512f79 refactoring 2019-10-10 02:18:37 +03:00
Devaev Maxim
a1c83fc765 --exit-on-parent-death 2019-10-08 23:32:23 +03:00
Devaev Maxim
57132c46ac Bump version: 1.4 → 1.5 2019-09-18 04:55:27 +03:00
Devaev Maxim
6a7a26360f messages fix 2019-09-18 04:51:39 +03:00
Devaev Maxim
7a699833c4 objects in separate directory 2019-09-18 03:43:37 +03:00
Devaev Maxim
fcdb8f5d42 headers sanity 2019-09-17 19:45:56 +03:00
Devaev Maxim
e827ad8a7a refactoring 2019-09-17 17:16:59 +03:00
Devaev Maxim
caf5b9191b refactoring 2019-09-17 10:53:36 +03:00
Devaev Maxim
163aa8b5af refactoring 2019-09-17 10:28:46 +03:00
Devaev Maxim
bc873e31d0 refactoring 2019-09-17 10:21:33 +03:00
Devaev Maxim
947924e900 refactoring 2019-09-17 09:57:54 +03:00
Devaev Maxim
0827cb2b65 Issue #9: --last-as-blank accept timeout or 0 2019-09-17 09:50:14 +03:00
Devaev Maxim
d24d48212f refactoring 2019-09-17 03:19:44 +03:00
Devaev Maxim
a23475be57 thread names 2019-09-14 04:28:11 +03:00
Devaev Maxim
b7d8c5bfa6 Issue #9: Option to show last frame as blank 2019-09-13 03:22:17 +03:00
Devaev Maxim
433e884fad unified picture_t with api 2019-09-13 02:18:24 +03:00
Devaev Maxim
1d1c7c705d refactoring 2019-09-12 18:23:45 +03:00
Devaev Maxim
b02b0f910c Bump version: 1.3 → 1.4 2019-09-12 03:04:29 +03:00
Devaev Maxim
bfbb5dd29d show option choices on fail 2019-09-12 03:04:06 +03:00
Devaev Maxim
f3339f0502 fixed short options 2019-09-12 03:00:13 +03:00
Devaev Maxim
5d270b0029 improved help and messages 2019-09-12 02:45:02 +03:00
Devaev Maxim
dd5a5a079a Bump version: 1.2 → 1.3 2019-09-10 18:38:58 +03:00
Devaev Maxim
a3fcb01b7b renamed log color options 2019-09-10 18:38:49 +03:00
Devaev Maxim
afe5b91f63 Bump version: 1.1 → 1.2 2019-09-10 18:23:00 +03:00
Devaev Maxim
330641ee9f removed legacy --with/--height (with fake-) 2019-09-06 23:28:31 +03:00
Devaev Maxim
d8ec5169e7 refactoring 2019-09-06 22:44:26 +03:00
Devaev Maxim
93d5be6ffc color logging 2019-09-06 22:26:15 +03:00
Devaev Maxim
2dbaf08eb0 refactoring 2019-09-06 02:00:09 +03:00
Devaev Maxim
9a216153dc enum log_level 2019-09-06 01:48:53 +03:00
Devaev Maxim
090ed174af enum for short opts 2019-09-05 22:07:01 +03:00
Devaev Maxim
4292c8a2d1 moved options parser to separate file 2019-09-05 22:06:37 +03:00
Devaev Maxim
856dab30bc refactoring 2019-09-05 16:25:37 +03:00
Devaev Maxim
16d5c81c22 Bump version: 1.0 → 1.1 2019-09-01 22:43:13 +03:00
Devaev Maxim
58e3a77a79 changed upstream url 2019-09-01 22:42:58 +03:00
Devaev Maxim
16105db7a0 refactoring 2019-07-27 18:22:08 +03:00
Devaev Maxim
6e307b1ef4 Bump version: 0.81 → 1.0 2019-07-13 07:15:28 +03:00
Devaev Maxim
554491ff19 messages refactoring 2019-07-13 07:14:33 +03:00
Devaev Maxim
fcd70c3166 Bump version: 0.80 → 0.81 2019-07-13 01:06:35 +03:00
Devaev Maxim
aaed14e9de wider fps and resolution range 2019-07-13 01:06:21 +03:00
Devaev Maxim
76ba25607b Bump version: 0.79 → 0.80 2019-07-13 00:30:13 +03:00
Devaev Maxim
1d8dedea85 --glitched-resolutions 2019-07-12 23:03:42 +03:00
Devaev Maxim
dfe8245181 refactoring 2019-07-12 19:09:54 +03:00
Devaev Maxim
50569a53a0 show gpio feature in version 2019-07-12 05:07:35 +03:00
Devaev Maxim
6ace44e4de refactoring 2019-07-11 19:40:42 +03:00
Devaev Maxim
c4a5eea75b --(fake-)resolution instead of --(fake-)width/height 2019-07-11 16:48:33 +03:00
Devaev Maxim
87de066369 refactoring 2019-07-10 07:08:29 +03:00
Devaev Maxim
c3c15b16bf WITH_OMX_ENCODER -> WITH_OMX 2019-07-10 02:20:46 +03:00
Devaev Maxim
e6dfe3d2b7 gpio 2019-07-09 20:11:41 +03:00
Devaev Maxim
fe8699b7f3 log_level = LOG_LEVEL_INFO 2019-07-03 00:26:55 +03:00
Devaev Maxim
c751e4ff08 Bump version: 0.78 → 0.79 2019-07-01 02:47:22 +03:00
Devaev Maxim
99a00ca57c approx_comp_time 2019-07-01 02:20:13 +03:00
Devaev Maxim
c009a7efe4 Revert "fluency synchronization"
This reverts commit 291d7431b031a0ffea55fa9cd9bdc93be1d869fa.
2019-06-30 06:37:24 +03:00
Devaev Maxim
291d7431b0 fluency synchronization 2019-06-29 16:30:43 +03:00
Devaev Maxim
a1cd490fdf using desired_frames_interval instead of soft_delay 2019-06-29 07:02:21 +03:00
Devaev Maxim
7a85774085 fixed mutex routine 2019-06-29 06:59:16 +03:00
Devaev Maxim
2ed3c4815b f-strings 2019-06-28 06:27:46 +03:00
Devaev Maxim
724c6e118f () -> (void) 2019-06-28 05:15:24 +03:00
Devaev Maxim
da3a3adc65 fixed includes order 2019-06-28 05:00:49 +03:00
Devaev Maxim
32013a6360 refactoring 2019-06-27 05:10:19 +03:00
Devaev Maxim
16a2495766 messages refactoring 2019-06-13 14:37:03 +03:00
Devaev Maxim
05246706f0 Bump version: 0.77 → 0.78 2019-06-02 21:07:18 +03:00
Devaev Maxim
9355055a7f Revert "fluency delay based on involved workers counter"
This reverts commit 9f16de13fec21d2832edf789bc26695426f53cdb.
2019-06-02 21:06:30 +03:00
Devaev Maxim
9f16de13fe fluency delay based on involved workers counter 2019-06-02 20:23:39 +03:00
Devaev Maxim
36a987fb9d locked captured_fps 2019-06-02 19:35:53 +03:00
Devaev Maxim
652397f569 refactoring 2019-06-02 16:31:50 +03:00
Devaev Maxim
3333fc56a3 refactoring 2019-06-02 16:20:30 +03:00
Devaev Maxim
47378a17db refactoring 2019-06-02 15:22:34 +03:00
Devaev Maxim
5f320786f5 fixed workers cleanup on restart 2019-06-01 05:31:44 +03:00
Devaev Maxim
498c6e1f5d refactoring 2019-06-01 05:00:09 +03:00
Devaev Maxim
6c09adc689 free_worker -> ready_worker 2019-06-01 04:58:01 +03:00
Devaev Maxim
8bf7ac3005 refactoring 2019-06-01 04:47:51 +03:00
Devaev Maxim
6aebd7167e config.mk 2019-05-31 01:02:03 +03:00
Devaev Maxim
3f03575222 refactoring 2019-05-30 06:53:01 +03:00
Devaev Maxim
9ca43f17a3 debug workers using gpio on rpi 2019-05-26 22:32:51 +03:00
Devaev Maxim
66ef566c9a Makefile: on/off WITH_* options 2019-05-26 18:47:53 +03:00
Devaev Maxim
07ecc5b0a0 Bump version: 0.76 → 0.77 2019-05-26 06:03:59 +03:00
Devaev Maxim
8d19711f2c fixed pkgbuild 2019-05-26 06:03:48 +03:00
Devaev Maxim
c94117ae1e Bump version: 0.75 → 0.76 2019-05-26 05:55:48 +03:00
Devaev Maxim
06a80df708 More flexible build 2019-05-26 05:55:35 +03:00
Devaev Maxim
13dff256c8 workers by number of cores 2019-05-25 00:52:49 +03:00
Devaev Maxim
07e9dbc0f7 fixed gcc options ordering 2019-05-24 20:13:09 +03:00
Devaev Maxim
c83dddbf0b fixed indent 2019-05-18 23:51:14 +03:00
Devaev Maxim
5672d1aa75 Bump version: 0.74 → 0.75 2019-05-18 18:24:32 +03:00
Devaev Maxim
078097efec Added links to FreeBSD port 2019-05-18 18:22:37 +03:00
Devaev Maxim
3cd8338886 Issue #6: make install-strip and make uninstall 2019-05-18 17:58:23 +03:00
Maxim Devaev
d2b57cc7d5
Update README.md 2019-05-18 17:25:42 +03:00
Maxim Devaev
2c4f59c87a
Update README.ru.md 2019-05-18 17:24:53 +03:00
Devaev Maxim
36eb7eeb76 minor help fix 2019-05-18 16:35:10 +03:00
Devaev Maxim
3b6544db8a cpu/encoder: removed TODO about jpeg error handling 2019-05-18 02:12:18 +03:00
Devaev Maxim
8033ab23ed Bump version: 0.73 → 0.74 2019-05-17 17:15:17 +03:00
Devaev Maxim
ddb3db8b20 Issue #5: Added libevent version check 2019-05-17 16:57:53 +03:00
Devaev Maxim
b3c8071edb Bump version: 0.72 → 0.73 2019-05-09 18:51:27 +03:00
Devaev Maxim
bc70faae09 fix 2019-05-05 04:26:25 +03:00
Devaev Maxim
ae7c4c91e0 Bump version: 0.71 → 0.72 2019-05-03 01:49:44 +03:00
Devaev Maxim
613baa4e1e better unix socket handling 2019-05-03 00:10:48 +03:00
Devaev Maxim
33a101b4f7 Bump version: 0.70 → 0.71 2019-05-01 21:00:22 +03:00
Devaev Maxim
9e9039c4e6 y != yes 2019-05-01 20:59:30 +03:00
Maxim Devaev
b3ceec51de
Update README.md 2019-04-26 02:24:16 +03:00
Maxim Devaev
e3ac4ba6f5
Update README.ru.md 2019-04-26 02:23:54 +03:00
Devaev Maxim
fa6b8b44c1 Bump version: 0.69 → 0.70 2019-04-18 05:17:00 +03:00
Devaev Maxim
8cb7574af2 encoder refactoring 2019-04-18 05:09:32 +03:00
Devaev Maxim
c34d644c2a Bump version: 0.68 → 0.69 2019-04-17 07:38:37 +03:00
Devaev Maxim
7857fa8f63 blank: handling libjpeg errors 2019-04-17 07:14:19 +03:00
Devaev Maxim
3253de83dc refactoring 2019-04-14 06:09:21 +03:00
Devaev Maxim
030464c3b8 refactoring 2019-04-14 03:41:35 +03:00
Devaev Maxim
0020aa69ec configurable blank page 2019-04-14 03:35:25 +03:00
Devaev Maxim
93bfa56ccf unicode fix 2019-04-13 23:02:26 +03:00
Devaev Maxim
3392ac5fbc refactoring 2019-04-13 23:02:12 +03:00
Devaev Maxim
2077d94edc Bump version: 0.67 → 0.68 2019-04-10 16:47:18 +03:00
Devaev Maxim
defe5eb6fe openwrt package 2019-04-10 16:46:01 +03:00
Devaev Maxim
dee5e18134 better makefile 2019-04-10 02:38:08 +03:00
Devaev Maxim
6ef5a7e440 Bump version: 0.66 → 0.67 2019-03-28 05:52:44 +03:00
Devaev Maxim
462735147d another place for SEP_INFO 2019-03-28 05:52:21 +03:00
Devaev Maxim
51ca0e4474 Bump version: 0.65 → 0.66 2019-03-26 21:45:14 +03:00
Devaev Maxim
4ee3b18533 better tools 2019-03-25 02:36:12 +03:00
Maxim Devaev
cdc9ed54c9
Update README.ru.md 2019-03-23 20:54:52 +03:00
Maxim Devaev
d9e7c07851
Update README.ru.md 2019-03-23 20:52:43 +03:00
Maxim Devaev
f2debc5d16
Update README.ru.md 2019-03-23 20:19:49 +03:00
Maxim Devaev
b3dbaf40cf
Update README.md 2019-03-23 20:19:22 +03:00
Devaev Maxim
abfc7b917b Bump version: 0.64 → 0.65 2019-03-23 06:44:36 +03:00
Devaev Maxim
a0e488b0a5 help fix 2019-03-23 06:44:11 +03:00
Devaev Maxim
6b99df2792 minor fix for b101 2019-03-23 06:36:17 +03:00
Devaev Maxim
6f8434a5c2 Bump version: 0.63 → 0.64 2019-03-23 04:02:25 +03:00
Devaev Maxim
b15888dbd4 refactoring 2019-03-23 04:02:11 +03:00
Devaev Maxim
2e96d74ac0 supported hw fps 2019-03-23 03:49:59 +03:00
Devaev Maxim
fc4cbb1fe1 Bump version: 0.62 → 0.63 2019-03-22 05:59:06 +03:00
Devaev Maxim
67f9bcf4c8 fixed help 2019-03-22 05:58:54 +03:00
Maxim Devaev
da227ec234
Update README.ru.md 2019-03-22 05:50:23 +03:00
Maxim Devaev
933be02c86
Update README.md 2019-03-22 05:48:37 +03:00
Devaev Maxim
28e979a2be dots 2019-03-22 05:23:26 +03:00
Devaev Maxim
8cde363338 pkg dir 2019-03-22 05:14:19 +03:00
Devaev Maxim
4741fe1952 http static server 2019-03-22 04:06:41 +03:00
Devaev Maxim
7d4ae57fbd refactoring 2019-03-21 17:54:43 +03:00
Devaev Maxim
c50388ab9f refactoring 2019-03-21 01:28:02 +03:00
Devaev Maxim
17099e86de --static stub 2019-03-20 23:17:41 +03:00
Devaev Maxim
6528352e04 http basic auth 2019-03-20 16:15:26 +03:00
Devaev Maxim
4f7b426068 fixed -l option 2019-03-20 03:29:05 +03:00
Devaev Maxim
acc8628f3d Bump version: 0.61 → 0.62 2019-03-19 22:33:06 +03:00
Devaev Maxim
46e99be201 Supported XSI strerror_r() 2019-03-19 22:26:30 +03:00
Devaev Maxim
7fbeca41fa refactoring 2019-03-18 21:43:23 +03:00
Devaev Maxim
73ceba77a8 Bump version: 0.60 → 0.61 2019-03-18 00:30:13 +03:00
Devaev Maxim
a3e5d17628 redefineable XIOCTL_RETRIES 2019-03-17 22:54:26 +03:00
Devaev Maxim
c30dea20a5 Bump version: 0.59 → 0.60 2019-03-17 20:10:11 +03:00
Devaev Maxim
b31450ba41 size -> used 2019-03-17 19:44:49 +03:00
Devaev Maxim
c05457ce2f separate hw_buffer_t size and allocated 2019-03-17 19:33:56 +03:00
Devaev Maxim
9e63076ec5 fixed omx slice_size 2019-03-17 18:50:12 +03:00
Devaev Maxim
92844fc3db Bump version: 0.58 → 0.59 2019-03-17 15:22:30 +03:00
Devaev Maxim
7bb9434850 shorten log about stream clients 2019-03-17 15:06:08 +03:00
Devaev Maxim
3104a00913 Bump version: 0.57 → 0.58 2019-03-16 19:20:39 +03:00
Devaev Maxim
28c51f3f2f better help 2019-03-16 16:27:45 +03:00
Devaev Maxim
5c035f21c8 some short options 2019-03-16 15:28:11 +03:00
Devaev Maxim
52ecaf7fd3 refactoring 2019-03-16 13:22:06 +03:00
Devaev Maxim
aa007c676f limited default workers and buffers number 2019-03-16 13:21:44 +03:00
Devaev Maxim
1f0b49e5fe refactoring 2019-03-16 13:09:30 +03:00
Devaev Maxim
d979209096 Bump version: 0.56 → 0.57 2019-03-16 01:36:14 +03:00
Devaev Maxim
154d8e4c2b updated readme 2019-03-16 01:35:47 +03:00
Devaev Maxim
27d42d2545 --device-persistent -> -n|--persistent 2019-03-16 01:30:55 +03:00
Devaev Maxim
142670c374 refactoring 2019-03-16 01:21:03 +03:00
Devaev Maxim
2a9c4d7e7a C11 atomic types 2019-03-16 01:02:27 +03:00
Devaev Maxim
dc43f01a7d Bump version: 0.55 → 0.56 2019-03-15 23:59:25 +03:00
Devaev Maxim
bac7a2595e refactoring 2019-03-15 14:21:50 +03:00
Devaev Maxim
50158397a0 refactoring 2019-03-15 13:34:06 +03:00
Devaev Maxim
484f89cb82 slowdown 2019-03-15 13:26:53 +03:00
Devaev Maxim
fd7b5e9c59 Bump version: 0.54 → 0.55 2019-03-14 10:34:06 +03:00
Devaev Maxim
966f226dde refactoring 2019-03-13 22:10:36 +03:00
Devaev Maxim
8a2a0474b2 big controls refactoring 2019-03-12 23:46:19 +03:00
Devaev Maxim
22be6443ef Makefile: optional CC 2019-03-12 13:23:25 +03:00
Devaev Maxim
da1348d25f fixed potential placeholder error with evbuffer_add_printf() 2019-03-12 13:23:05 +03:00
Devaev Maxim
02604d4ef3 refactoring 2019-03-06 06:57:59 +03:00
Devaev Maxim
351da0dce0 refactoring 2019-03-06 05:18:26 +03:00
Devaev Maxim
01e21e419d refactoring 2019-03-06 03:07:02 +03:00
Devaev Maxim
a5bca4cca3 huffman 2019-03-06 01:33:20 +03:00
Devaev Maxim
f439f37526 refactoring 2019-03-05 14:23:11 +03:00
Devaev Maxim
502aa3a0cb refactoring 2019-03-05 13:14:17 +03:00
Devaev Maxim
84a7bcc2a4 help fix 2019-03-05 12:48:11 +03:00
Devaev Maxim
388198a504 show query result of image settings 2019-03-05 12:43:13 +03:00
Devaev Maxim
69d6fda56b refactoring 2019-03-05 12:42:03 +03:00
Devaev Maxim
9a38078c72 Added backlight_compensation, white_balance and gain 2019-03-05 10:47:43 +03:00
Devaev Maxim
af2dd0d9a3 refactoring 2019-03-05 10:09:07 +03:00
Devaev Maxim
09fc14d63d First implementation of image settings 2019-03-05 09:54:15 +03:00
Devaev Maxim
e683d1d370 Makefile: OBJECTS after SOURCES 2019-03-05 02:08:32 +03:00
Devaev Maxim
24fed54cae OMX_ENCODER -> WITH_OMX_ENCODER 2019-03-05 01:15:57 +03:00
Devaev Maxim
b76b34ad65 removed fallback field from /state 2019-03-05 00:30:54 +03:00
Devaev Maxim
ef65812ec7 removed UNKNOWN from STANDARDS_STR 2019-03-05 00:28:43 +03:00
Devaev Maxim
4c8351c1bc Bump version: 0.53 → 0.54 2019-03-04 15:47:02 +03:00
Devaev Maxim
4492cc1efe refactoring 2019-03-04 15:42:47 +03:00
Devaev Maxim
b2ca0ea998 encoders subdir 2019-03-04 15:29:11 +03:00
Devaev Maxim
924665c1a3 refactoring 2019-03-04 15:16:36 +03:00
Devaev Maxim
5d49018bb2 refactoring 2019-03-04 15:07:16 +03:00
Devaev Maxim
3ae8818b3d consistent using fourcc 2019-03-04 15:06:29 +03:00
Devaev Maxim
2bb1f71c9c minor omx fixes 2019-03-04 14:25:57 +03:00
Devaev Maxim
537e55afc6 RGB24 2019-03-04 11:34:25 +03:00
Devaev Maxim
6cdaceb561 Bump version: 0.52 → 0.53 2019-03-03 19:06:27 +03:00
Devaev Maxim
08aacdc9af show encoder type in /stat 2019-03-03 19:02:20 +03:00
Devaev Maxim
3db57cfa42 logging; removed some pragmas 2019-03-03 17:09:34 +03:00
Devaev Maxim
d8a774e358 Added ebuild by @chron0 2019-03-03 17:05:29 +03:00
Devaev Maxim
869d12759c fixed remarks 2019-03-03 15:49:06 +03:00
Devaev Maxim
bce3ae0f21 Bump version: 0.51 → 0.52 2019-03-03 15:39:41 +03:00
Devaev Maxim
1541921070 supported hw mjpeg format 2019-03-03 15:39:32 +03:00
Devaev Maxim
cc25abcc00 Bump version: 0.50 → 0.51 2019-03-03 05:23:51 +03:00
Devaev Maxim
e80ee2f574 refactoring 2019-03-03 04:25:32 +03:00
Devaev Maxim
56a95c7f17 ARRAY_LEN() 2019-03-02 11:30:10 +03:00
Devaev Maxim
667e3610b2 encoder runtime 2019-03-02 11:29:57 +03:00
Devaev Maxim
142c8c84ac Bump version: 0.49 → 0.50 2019-03-01 11:36:15 +03:00
Devaev Maxim
383075d323 refactoring 2019-03-01 10:44:50 +03:00
Devaev Maxim
e2922aa820 fixed short options 2019-03-01 07:48:03 +03:00
Devaev Maxim
dc9667cf0c report about using pixelformat 2019-03-01 07:07:16 +03:00
Devaev Maxim
bbbfda0b5c Bump version: 0.48 → 0.49 2019-02-22 02:55:13 +03:00
Devaev Maxim
aa3f079ee9 fixed memory leak in http_server_destroy() 2019-02-22 02:54:03 +03:00
Devaev Maxim
59bd4e8dd2 Bump version: 0.47 → 0.48 2019-02-21 08:02:31 +03:00
Devaev Maxim
ce6184b8cd refactoring 2019-02-21 08:02:21 +03:00
Devaev Maxim
9ca511d29d Bump version: 0.46 → 0.47 2019-02-19 04:38:14 +03:00
Devaev Maxim
74de28c64a refactoring 2019-02-19 04:33:35 +03:00
Maxim Devaev
24060e8068
Update README.md 2019-01-28 17:33:57 +03:00
Maxim Devaev
3a03d48855
Update README.ru.md 2019-01-22 03:32:52 +03:00
Maxim Devaev
935a9125d6
Update README.md 2019-01-22 03:32:17 +03:00
Devaev Maxim
d4ea97ef2c Bump version: 0.45 → 0.46 2018-12-18 03:17:44 +03:00
Devaev Maxim
867aa4e52a proxy-revalidate 2018-12-18 03:17:34 +03:00
Devaev Maxim
bc2bb444dc Bump version: 0.44 → 0.45 2018-12-18 01:37:50 +03:00
Devaev Maxim
19796f3b64 X-UStreamer-Dropped 2018-12-18 01:37:39 +03:00
Devaev Maxim
348a6e4a8f Bump version: 0.43 → 0.44 2018-12-17 01:34:00 +03:00
Devaev Maxim
ba3300ddde refactoring 2018-12-17 01:31:44 +03:00
Devaev Maxim
09526884f4 forbid incorrect resolutions 2018-12-17 01:02:04 +03:00
Devaev Maxim
160a0e8d29 Bump version: 0.42 → 0.43 2018-12-06 21:56:11 +03:00
Devaev Maxim
9dddd0075e unix socket perms; strtol() error handling 2018-12-06 21:55:28 +03:00
Devaev Maxim
0e0e3939b2 Bump version: 0.41 → 0.42 2018-12-06 13:24:11 +03:00
Devaev Maxim
9c8d87412e streaming via unix socket 2018-12-06 13:23:58 +03:00
Maxim Devaev
3666d92819
Update README.md 2018-12-06 13:23:37 +03:00
Maxim Devaev
9388d4dd22
Update README.ru.md 2018-12-06 13:23:20 +03:00
Maxim Devaev
6cf4924e05
Update README.ru.md 2018-12-06 13:23:01 +03:00
Maxim Devaev
713e3751b4
Update README.md 2018-12-06 13:22:29 +03:00
Devaev Maxim
8e61d7c55e Bump version: 0.40 → 0.41 2018-11-12 11:37:09 +03:00
Devaev Maxim
4e42c42bae always use ijg 2018-11-12 11:36:56 +03:00
Devaev Maxim
b53e3edef1 updated readme 2018-11-12 11:02:53 +03:00
Devaev Maxim
0afbf02451 Bump version: 0.39 → 0.40 2018-11-09 22:42:24 +03:00
Devaev Maxim
dd79efd6f5 removed legacy option --every-frame 2018-11-09 22:42:07 +03:00
Devaev Maxim
97ac19a2fe Bump version: 0.38 → 0.39 2018-11-08 04:25:27 +03:00
Devaev Maxim
755e0c2a2a fixed long option for desired fps 2018-11-08 04:25:16 +03:00
Devaev Maxim
ccab33a290 Bump version: 0.37 → 0.38 2018-11-08 03:24:42 +03:00
Devaev Maxim
76a8e65e80 soft_fps -> desired_fps 2018-11-08 03:23:58 +03:00
Devaev Maxim
3b86e64222 more flexible --soft-fps 2018-11-07 21:04:21 +03:00
Devaev Maxim
020482a05a refactoring 2018-11-07 12:11:59 +03:00
Devaev Maxim
1896e22dff unified /stat json with kvmd 2018-11-07 11:27:34 +03:00
Devaev Maxim
56df20fe84 Bump version: 0.36 → 0.37 2018-11-07 05:59:57 +03:00
Devaev Maxim
ed7dabbfcb /ping -> /state; some docs 2018-11-07 05:59:48 +03:00
Devaev Maxim
b1d40d1b3a Bump version: 0.35 → 0.36 2018-11-07 04:22:08 +03:00
Devaev Maxim
43939c7475 combined stream_client cookie 2018-11-07 04:20:57 +03:00
Devaev Maxim
8fa6db0be1 Bump version: 0.34 → 0.35 2018-11-07 03:08:10 +03:00
Devaev Maxim
d57e9864a4 stream key param 2018-11-07 03:07:58 +03:00
Devaev Maxim
077f236a43 Bump version: 0.33 → 0.34 2018-11-06 06:58:14 +03:00
Devaev Maxim
d57277877e refactoring 2018-11-06 06:57:17 +03:00
193 changed files with 18182 additions and 4894 deletions

View File

@ -1,16 +1,39 @@
[bumpversion]
commit = True
tag = True
current_version = 0.33
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
current_version = 6.55
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
View File

@ -0,0 +1,10 @@
# Ignore everything
*
# Allow source code
!Makefile
!src/**
!python/**
!janus/**
!man/**
!pkg/docker/entry.sh

10
.editorconfig Normal file
View 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
View 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
View File

@ -0,0 +1,4 @@
# These are supported funding model platforms
patreon: pikvm
custom: https://paypal.me/pikvm

View 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_REPO }}/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/v6,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
View 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

25
.gitignore vendored
View File

@ -1,8 +1,19 @@
/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/dist/
/python/ustreamer.egg-info/
/python/root/
/janus/build/
/ustreamer
*.o
/ustreamer-*
/config.mk
vgcore.*
*.sock
*.so
*.bin
*.pkg.tar.xz
*.pkg.tar.zst
*.tar.gz

166
Makefile
View File

@ -1,63 +1,157 @@
DESTDIR ?=
PREFIX ?= /usr/local
CFLAGS ?= -O3
LDFLAGS ?=
-include config.mk
# =====
CC = gcc
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
override CFLAGS += -c -std=c99 -Wall -Wextra -D_GNU_SOURCE
SOURCES = $(shell ls src/*.c src/jpeg/*.c)
OBJECTS = $(SOURCES:.c=.o)
PROG = ustreamer
DESTDIR ?=
PREFIX ?= /usr/local
MANPREFIX ?= $(PREFIX)/share/man
CC ?= gcc
PY ?= python3
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O3
LDFLAGS ?=
R_DESTDIR = $(if $(DESTDIR),$(shell realpath "$(DESTDIR)"),)
WITH_PYTHON ?= 0
WITH_JANUS ?= 0
WITH_V4P ?= 0
WITH_GPIO ?= 0
WITH_SYSTEMD ?= 0
WITH_PTHREAD_NP ?= 1
WITH_SETPROCTITLE ?= 1
WITH_PDEATHSIG ?= 1
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
MK_WITH_PYTHON = $(call optbool,$(WITH_PYTHON))
MK_WITH_JANUS = $(call optbool,$(WITH_JANUS))
MK_WITH_V4P = $(call optbool,$(WITH_V4P))
MK_WITH_GPIO = $(call optbool,$(WITH_GPIO))
MK_WITH_SYSTEMD = $(call optbool,$(WITH_SYSTEMD))
MK_WITH_PTHREAD_NP = $(call optbool,$(WITH_PTHREAD_NP))
MK_WITH_SETPROCTITLE = $(call optbool,$(WITH_SETPROCTITLE))
MK_WITH_PDEATHSIG = $(call optbool,$(WITH_PDEATHSIG))
export
_LINTERS_IMAGE ?= ustreamer-linters
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
# =====
ifeq (__not_found__,$(shell which $(PKG_CONFIG) 2>/dev/null || echo "__not_found__"))
$(error "No $(PKG_CONFIG) found in $(PATH)")
endif
# =====
all: $(SOURCES) $(PROG)
ifeq ($(V),)
ECHO = @
endif
install: $(PROG)
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
# =====
all:
+ $(MAKE) apps
ifneq ($(MK_WITH_PYTHON),)
+ $(MAKE) python
endif
ifneq ($(MK_WITH_JANUS),)
+ $(MAKE) janus
endif
apps:
$(MAKE) -C src
for i in src/*.bin; do \
test ! -x $$i || ln -sf $$i `basename $$i .bin`; \
done
python:
$(MAKE) -C python
$(ECHO) ln -sf python/root/usr/lib/python*/site-packages/*.so .
janus:
$(MAKE) -C janus
$(ECHO) ln -sf janus/*.so .
install: all
$(MAKE) -C src install
ifneq ($(MK_WITH_PYTHON),)
$(MAKE) -C python install
endif
ifneq ($(MK_WITH_JANUS),)
$(MAKE) -C janus install
endif
mkdir -p $(R_DESTDIR)$(MANPREFIX)/man1
for man in $(shell ls man); do \
install -m644 man/$$man $(R_DESTDIR)$(MANPREFIX)/man1/$$man; \
gzip -f $(R_DESTDIR)$(MANPREFIX)/man1/$$man; \
done
install-strip: install
$(MAKE) -C src install-strip
regen:
tools/make-jpeg-h.py src/data/blank.jpeg src/data/blank_jpeg.h BLANK 640 480
tools/make-html-h.py src/data/index.html src/data/index_html.h HTML_INDEX_PAGE
$(PROG): $(OBJECTS)
$(CC) $(LIBS) $(LDFLAGS) $(OBJECTS) -o $@
.c.o:
$(CC) $(LIBS) $(CFLAGS) $< -o $@
tools/make-ico-h.py src/ustreamer/data/favicon.ico src/ustreamer/data/favicon_ico.c FAVICON
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:
git push
git push --tags
clean-all: clean
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-* *.so
$(MAKE) -C src clean
$(MAKE) -C python clean
$(MAKE) -C janus clean
.PHONY: python janus linters

View File

@ -1,30 +0,0 @@
# Contributor: Maxim Devaev <mdevaev@gmail.com>
# Author: Maxim Devaev <mdevaev@gmail.com>
pkgname=ustreamer
pkgver=0.33
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 libutil-linux)
# 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
}

205
README.md
View File

@ -1,73 +1,216 @@
# µStreamer
[[Русская версия]](README.ru.md)
[![CI](https://github.com/pikvm/ustreamer/workflows/CI/badge.svg)](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
[![Discord](https://img.shields.io/discord/580094191938437144?logo=discord)](https://discord.gg/bpmXfz5)
µStreamer is a lightweight and very quick server to broadcast [MJPG](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 [Pi-KVM](https://github.com/pi-kvm) 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 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 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** |
|----------|---------------|-------------------|
| Multithreaded JPEG encoding | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Behavior when the device<br>is disconnected while streaming | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Stops the broadcast<sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save traffic | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Supported input devices | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) YUYV, UYVY,<br>RGB565, MJPG |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
| Option to serve files<br>with a built-in HTTP server, auth settings | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No <sup>4</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
| 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 LIVE VIDEO``` 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 | ✔ | :) |
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.
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting 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 broadcast 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()```.
* ```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 on 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``` As µStreamer was made mainly to be used with screencast hardware it supports video formats they usually need. MJPG input means that the screencast device can compress images to JPEG and feed it to the software, which allows to cut down CPU usage and avoid software image encoding. This video format is supported by most webcams, but I've never seen it supported by screencast hardware: neither [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), nor [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC) offer such support. It's not hard to add hardware MJPG sources support, it's just not done yet.
* ```4``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install 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
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.
-----
# Building
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```.
# Installation
It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
## Building
You need to download the µStreamer onto your system and build it from the sources, or use a package:
* Arch Linux: https://aur.archlinux.org/packages/ustreamer
* Fedora: https://src.fedoraproject.org/rpms/ustreamer
* Ubuntu: https://packages.ubuntu.com/jammy/ustreamer
* Debian: https://packages.debian.org/sid/ustreamer
* OpenWRT: https://github.com/openwrt/packages/tree/master/multimedia/ustreamer
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer
### Preconditions
You'll need ```make```, ```gcc```, ```pkg-config```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspberry OS Bullseye: `sudo apt install libevent-dev libjpeg62-turbo libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
* Raspberry OS Bookworm: same as previous but replace `libjpeg62-turbo` to `libjpeg62-turbo-dev`.
* 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```.
### Make
The most convenient process is to clone the µStreamer Git repository onto your system. If you don't have Git installed and don't want to install it either, you can download and unzip the sources from GitHub using `wget https://github.com/pikvm/ustreamer/archive/refs/heads/master.zip`.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
$ git clone --depth=1 https://github.com/pikvm/ustreamer
$ cd ustreamer
$ make
$ ./ustreamer --help
```
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer
## Update
Assuming you have a µStreamer clone as discussed above you can update µStreamer as follows.
```
$ cd ustreamer
$ git pull
$ make clean
$ make
```
-----
# Usage
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start broadcasting on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to broadcast to the world, run:
**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
```
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
: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 [TC358743-based capture device](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
```
$ ./ustreamer \
--format=uyvy \ # Device input format
--encoder=omx \ # Hardware encoding with OpenMAX
--dv-timings \ # use DV-timings
--quality=20 \ # OpenMAX has a non-linear quality scale
--drop-same-frames=30 # Save that traffic
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
--workers=3 \ # Workers number
--persistent \ # Suppress repetitive signal source errors (for example when HDMI cable was disconnected)
--dv-timings \ # Use DV-timings
--drop-same-frames=30 # Save the traffic
```
: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```.
-----
# Docker (Raspberry Pi 4 HDMI)
## Preparations
Add following lines to /boot/firmware/usercfg.txt:
```
gpu_mem=128
dtoverlay=tc358743
```
Check size of CMA:
```
$ 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:
```
$ 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
```
$ 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 starting ustreamer. Use together with `-e EDID_HEX=xx` to specify custom EDID data.
-----
# Raspberry Pi Camera Example
Example usage for the Raspberry Pi v3 camera (required `libcamerify` which is located in `libcamera-tools` and `libcamera-v4l2` (install both) on Raspbian):
```
$ sudo modprobe bcm2835-v4l2
$ libcamerify ./ustreamer --host :: --encoder=m2m-image
```
For v2 camera you can use the same trick with `libcamerify` but enable legacy camera mode in `raspi-config`.
Example usage for the Raspberry Pi v1 camera:
```
$ 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:
```
$ 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, its 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 by Maxim Devaev mdevaev@gmail.com
Copyright (C) 2018-2024 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

View File

@ -1,83 +0,0 @@
# µStreamer
[[English version]](README.md)
µ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, которые только позволяет железо.
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
| **Фича** | **µStreamer** | **mjpg-streamer** |
|----------|---------------|-------------------|
| Многопоточное кодирование JPEG | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении устройства<br>от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддерживаемые входные форматы устройств | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) YUYV, UYVY,<br>RGB565, MJPG |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>4</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки:
* ```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()```.
* ```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.
-----
# TL;DR
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
-----
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
$ cd ustreamer
$ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer
-----
# Использование
Будучи запущенным без аргументов, ```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
```
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
```bash
$ ./ustreamer \
--format=uyvy \ # Настройка входного формата устройства
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
--dv-timings \ # Включение DV-таймингов
--quality=20 \ # У OpenMAX нелинейная шкала качества
--drop-same-frames=30 # Экономим трафик
```
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
-----
# Лицензия
Copyright (C) 2018 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/.

237
docs/h264.md Normal file
View File

@ -0,0 +1,237 @@
# 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 servers 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", params: {audio: true} } });
},
// 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) {
// 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>
```

50
docs/ssl/README.md Normal file
View File

@ -0,0 +1,50 @@
# Adding SSL
These days, browsers are not happy if you have HTTP content on an HTTPS page.
The browser will not show an HTTP stream on a page if the parent page is from a site which is using HTTPS.
The files in this folder configure an Nginx proxy in front of the µStreamer stream.
Using certbot, an SSL cert is created from Let's Encrypt and installed.
These scripts can be modified to add SSL to just about any HTTP server.
The scripts are not fire and forget.
They will require some pre-configuration and are interactive (you'll be asked questions while they're running).
They have been tested using the following setup.
1. A Raspberry Pi 4
1. µStreamer set up and running as a service
1. Internally on port 8080
1. Public port will be 5101
1. Verizon home Wi-Fi router
1. Domain registration from GoDaddy
## The Script
Below is an overview of the steps performed by `ssl-config.sh` (for Raspberry OS):
1. Install snapd - certbot uses this for installation
1. Install certbot
1. Get a free cert from Let's Encrypt using certbot
1. Install nginx
1. Configures nginx to proxy for µStreamer
## Steps
1. Create a public DNS entry.
1. Pointing to the Pi itself or the public IP of the router behind which the Pi sits.
1. This would be managed in the domain registrar, such as GoDaddy.
1. Use a subdomain, such as `webcam.domain.com`
1. Port Forwarding
1. If using a Wi-Fi router, create a port forwarding rule which passes traffic from port 80 to the Pi. This is needed for certbot to ensure your DNS entry reaches the Pi, even if your final port will be something else.
1. Create a second rule for your final setup. For example, forward traffic from the router on port 5101 to the Pi's IP port 8080.
1. Update the ustreamer-proxy file in this folder
1. Replace `your.domain.com` with a fully qualified domain, it's three places in the proxy file.
1. Modify the line `listen 5101 ssl` port if needed. This is the public port, not the port on which the µStreamer service is running
1. Modify `proxy_pass http://127.0.0.1:8080;` with the working address of the internal µStreamer service.
1. Run the script
1. Stand buy, certbot asks some basic questions, such as email, domain, agree to terms, etc.
1. `bash ssl-config.sh`
1. Test your URL!
## Down the Road
Two important points to keep in mind for the future:
1. Dynamic IP - Most routers do not have a static IP address on the WAN side. So, if you reboot your router or if your internet provider gives you a new IP, you'll have to update the DNS entry.
1. Many routers have some sort of dynamic DNS feature. This would automatically update the DNS entry for you. That functionality is outside the scope of this document.
1. SSL Renewals - certbot automatically creates a task to renew the SSL cert before it expires. Assuming the Pi is running all the time, this shouldn't be an issue.
## Enjoy!

20
docs/ssl/ssl-config.sh Normal file
View File

@ -0,0 +1,20 @@
#!/bin/sh
echo -e "\e[32mInstalling snapd...\e[0m"
sudo apt install snapd -y
sudo snap install core
echo -e "\e[32mInstalling certbot, don't leave, it's going to ask questions...\e[0m"
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot certonly --standalone
sudo certbot renew --dry-run
echo -e "\e[32mInstalling nginx...\e[0m"
sudo apt-get install nginx -y
sudo cp ustreamer-proxy /etc/nginx/sites-available/ustreamer-proxy
sudo ln -s /etc/nginx/sites-available/ustreamer-proxy /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

13
docs/ssl/ustreamer-proxy Normal file
View File

@ -0,0 +1,13 @@
server {
listen 5101 ssl;
server_name your.domain.com;
ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080; # Change this to the uStreamer server address
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

97
docs/youtube.md Normal file
View File

@ -0,0 +1,97 @@
## Streaming to third party services
This method provides the ability of streaming a USB Webcam and include audio to large audiences.
It uses to two machines. One is a Raspberry Pi and the other a more capable machine to performance
the encoding of the video and audio that is streamed to the third party service such as YouTube.
Another benefit of using a browser (http stream) is the video can have overlays add in the custom ustreamer webpage.
For example a cron process that retrieves weather information and updates a file to include on the page, announcements,
or other creative ideas. The audio stream can also be something other than the webcam mic (music, voice files, etc.)
and easily changed on the second machine setup. In the following example filtering is applied in ffmpeg to
improve the sound of the webcam mic making vocals clearer and more intelligible.
* Machine 1:
* USB webcam on the machine (Pi for example) running ustreamer (video) and VLC (audio). Remember to make any needed firewall changes if machine 2 is on a separate network so it can reach the ports for the video and audio.
* To stream audio from the Pi.
```
/usr/bin/vlc -I dummy -vvv alsa://hw:2,0 --sout #transcode{acodec=mp3,ab=128}:standard{access=http,mux=ts,dst=:[PickAPort}
```
* Machine 2:
* On a more capable box run the video stream in a browser using ffmpeg to combine the video (browser) and audio and stream to YouTube or other services. In this example a VM with two virtual monitors running the browser full screen one of the monitors is used.
Script to stream the combination to YouTube:
```bash
#!/bin/bash
KEY=$1
echo
echo Cleanup -------------------------------------------------
source live-yt.key
killall -9 ffmpeg
killall -9 chromium
sleep 3
echo Setup General--------------------------------------------
cd /home/[USER]
rm -f nohup.out
export DISPLAY=:0.0
export $(dbus-launch)
echo Setup Chromium-------------------------------------------
CHROMIUM_TEMP=/home/{USER]/tmp/chromium
rm -rf $CHROMIUM_TEMP.bak
mv $CHROMIUM_TEMP $CHROMIUM_TEMP.bak
mkdir -p $CHROMIUM_TEMP
echo Start Chromium ------------------------------------------
nohup /usr/lib/chromium/chromium \
--new-window "http://[ustreamerURL]" \
--start-fullscreen \
--disable \
--disable-translate \
--disable-infobars \
--disable-suggestions-service \
--disable-save-password-bubble \
--disable-new-tab-first-run \
--disable-session-crashed-bubble \
--disable-bundled-ppapi-flash \
--disable-gpu \
--enable-javascript \
--enable-user-scripts \
--disk-cache-dir=$CHROMIUM_TEMP/cache/ustreamer/ \
--user-data-dir=$CHROMIUM_TEMP/user_data/ustreamer/ \
--window-position=1440,12 \
>/dev/null 2>&1 &
sleep 5
echo Start FFMpeg---------------------------------------------
nohup /usr/bin/ffmpeg \
-loglevel level+warning \
-thread_queue_size 512 \
-framerate 30 \
-f x11grab \
-s 1920x1080 \
-probesize 42M \
-i :0.0+1024,0 \
-i http://[VLCaudioURL] \
-filter:a "volume=10, highpass=f=300, lowpass=f=2800" \
-c:v libx264 \
-pix_fmt yuv420p \
-g 60 \
-b:v 2500k \
-c:a libmp3lame \
-ar 44100 \
-b:a 32k \
-preset ultrafast \
-maxrate 5000k \
-bufsize 2500k \
-preset ultrafast \
-flvflags no_duration_filesize \
-f flv "rtmp://a.rtmp.youtube.com/live2/$KEY" \
>/home/{USER]/ff-audio.log 2>&1 &
echo Done ----------------------------------------------------
echo
```
*PS: Recipe by David Klippel*

54
janus/Makefile Normal file
View File

@ -0,0 +1,54 @@
R_DESTDIR ?=
PREFIX ?= /usr/local
CC ?= gcc
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O3
LDFLAGS ?=
# =====
_PLUGIN = libjanus_ustreamer.so
_CFLAGS = -fPIC -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(shell $(PKG_CONFIG) --cflags janus-gateway) $(CFLAGS)
_LDFLAGS = -shared -lm -pthread -lrt -ljansson -lopus -lasound -lspeexdsp $(shell $(PKG_CONFIG) --libs janus-gateway) $(LDFLAGS)
_SRCS = $(shell ls src/uslibs/*.c src/*.c)
_BUILD = build
# =====
ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
override _LDFLAGS += -latomic
endif
ifneq ($(MK_WITH_PTHREAD_NP),)
override _CFLAGS += -DWITH_PTHREAD_NP
endif
# =====
$(_PLUGIN): $(_SRCS:%.c=$(_BUILD)/%.o)
$(info == SO $@)
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS)
$(_BUILD)/%.o: %.c
$(info -- CC $<)
$(ECHO) mkdir -p $(dir $@) || true
$(ECHO) $(CC) $< -o $@ $(_CFLAGS)
install: $(_PLUGIN)
mkdir -p $(R_DESTDIR)$(PREFIX)/lib/ustreamer/janus
install -m755 $(_PLUGIN) $(R_DESTDIR)$(PREFIX)/lib/ustreamer/janus/$(PLUGIN)
clean:
rm -rf $(_PLUGIN) $(_BUILD)
_OBJS = $(_SRCS:%.c=$(_BUILD)/%.o)
-include $(_OBJS:%.o=%.d)

243
janus/src/acap.c Normal file
View File

@ -0,0 +1,243 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "acap.h"
#include <stdlib.h>
#include <stdatomic.h>
#include <assert.h>
#include <pthread.h>
#include <alsa/asoundlib.h>
#include <speex/speex_resampler.h>
#include <opus/opus.h>
#include "uslibs/types.h"
#include "uslibs/errors.h"
#include "uslibs/tools.h"
#include "uslibs/array.h"
#include "uslibs/ring.h"
#include "uslibs/threading.h"
#include "rtp.h"
#include "au.h"
#include "logging.h"
static void *_pcm_thread(void *v_acap);
static void *_encoder_thread(void *v_acap);
us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
us_acap_s *acap;
US_CALLOC(acap, 1);
acap->pcm_hz = pcm_hz;
US_RING_INIT_WITH_ITEMS(acap->pcm_ring, 8, us_au_pcm_init);
US_RING_INIT_WITH_ITEMS(acap->enc_ring, 8, us_au_encoded_init);
atomic_init(&acap->stop, false);
int err;
{
if ((err = snd_pcm_open(&acap->dev, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
acap->dev = NULL;
US_JLOG_PERROR_ALSA(err, "acap", "Can't open PCM capture");
goto error;
}
assert(!snd_pcm_hw_params_malloc(&acap->dev_params));
# define SET_PARAM(_msg, _func, ...) { \
if ((err = _func(acap->dev, acap->dev_params, ##__VA_ARGS__)) < 0) { \
US_JLOG_PERROR_ALSA(err, "acap", _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 number", snd_pcm_hw_params_set_channels, US_RTP_OPUS_CH);
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, &acap->pcm_hz, 0);
if (acap->pcm_hz < US_AU_MIN_PCM_HZ || acap->pcm_hz > US_AU_MAX_PCM_HZ) {
US_JLOG_ERROR("acap", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
acap->pcm_hz, US_AU_MIN_PCM_HZ, US_AU_MAX_PCM_HZ);
goto error;
}
acap->pcm_frames = US_AU_HZ_TO_FRAMES(acap->pcm_hz);
acap->pcm_size = US_AU_HZ_TO_BUF8(acap->pcm_hz);
SET_PARAM("Can't apply PCM params", snd_pcm_hw_params);
# undef SET_PARAM
}
if (acap->pcm_hz != US_RTP_OPUS_HZ) {
acap->res = speex_resampler_init(US_RTP_OPUS_CH, acap->pcm_hz, US_RTP_OPUS_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
if (err < 0) {
acap->res = NULL;
US_JLOG_PERROR_RES(err, "acap", "Can't create resampler");
goto error;
}
}
{
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
acap->enc = opus_encoder_create(US_RTP_OPUS_HZ, US_RTP_OPUS_CH, OPUS_APPLICATION_AUDIO, &err);
assert(err == 0);
// https://github.com/meetecho/janus-gateway/blob/3cdd6ff/src/plugins/janus_audiobridge.c#L2272
// https://datatracker.ietf.org/doc/html/rfc7587#section-3.1.1
assert(!opus_encoder_ctl(acap->enc, OPUS_SET_BITRATE(128000)));
assert(!opus_encoder_ctl(acap->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
assert(!opus_encoder_ctl(acap->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)));
// OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c
}
US_JLOG_INFO("acap", "Capture configured on %uHz; capturing ...", acap->pcm_hz);
acap->tids_created = true;
US_THREAD_CREATE(acap->enc_tid, _encoder_thread, acap);
US_THREAD_CREATE(acap->pcm_tid, _pcm_thread, acap);
return acap;
error:
us_acap_destroy(acap);
return NULL;
}
void us_acap_destroy(us_acap_s *acap) {
if (acap->tids_created) {
atomic_store(&acap->stop, true);
US_THREAD_JOIN(acap->pcm_tid);
US_THREAD_JOIN(acap->enc_tid);
}
US_DELETE(acap->enc, opus_encoder_destroy);
US_DELETE(acap->res, speex_resampler_destroy);
US_DELETE(acap->dev, snd_pcm_close);
US_DELETE(acap->dev_params, snd_pcm_hw_params_free);
US_RING_DELETE_WITH_ITEMS(acap->enc_ring, us_au_encoded_destroy);
US_RING_DELETE_WITH_ITEMS(acap->pcm_ring, us_au_pcm_destroy);
if (acap->tids_created) {
US_JLOG_INFO("acap", "Capture closed");
}
free(acap);
}
int us_acap_get_encoded(us_acap_s *acap, u8 *data, uz *size, u64 *pts) {
if (atomic_load(&acap->stop)) {
return -1;
}
const int ri = us_ring_consumer_acquire(acap->enc_ring, 0.1);
if (ri < 0) {
return US_ERROR_NO_DATA;
}
const us_au_encoded_s *const buf = acap->enc_ring->items[ri];
if (buf->used == 0 || *size < buf->used) {
us_ring_consumer_release(acap->enc_ring, ri);
return US_ERROR_NO_DATA;
}
memcpy(data, buf->data, buf->used);
*size = buf->used;
*pts = buf->pts;
us_ring_consumer_release(acap->enc_ring, ri);
return 0;
}
static void *_pcm_thread(void *v_acap) {
US_THREAD_SETTLE("us_ac_pcm");
us_acap_s *const acap = v_acap;
u8 in[US_AU_MAX_BUF8];
while (!atomic_load(&acap->stop)) {
const int frames = snd_pcm_readi(acap->dev, in, acap->pcm_frames);
if (frames < 0) {
US_JLOG_PERROR_ALSA(frames, "acap", "Fatal: Can't capture PCM frames");
break;
} else if (frames < (int)acap->pcm_frames) {
US_JLOG_ERROR("acap", "Fatal: Too few PCM frames captured");
break;
}
const int ri = us_ring_producer_acquire(acap->pcm_ring, 0);
if (ri >= 0) {
us_au_pcm_s *const out = acap->pcm_ring->items[ri];
memcpy(out->data, in, acap->pcm_size);
us_ring_producer_release(acap->pcm_ring, ri);
} else {
US_JLOG_ERROR("acap", "PCM ring is full");
}
}
atomic_store(&acap->stop, true);
return NULL;
}
static void *_encoder_thread(void *v_acap) {
US_THREAD_SETTLE("us_ac_enc");
us_acap_s *const acap = v_acap;
s16 in_res[US_AU_MAX_BUF16];
while (!atomic_load(&acap->stop)) {
const int in_ri = us_ring_consumer_acquire(acap->pcm_ring, 0.1);
if (in_ri < 0) {
continue;
}
us_au_pcm_s *const in = acap->pcm_ring->items[in_ri];
s16 *in_ptr;
if (acap->res != NULL) {
assert(acap->pcm_hz != US_RTP_OPUS_HZ);
u32 in_count = acap->pcm_frames;
u32 out_count = US_AU_HZ_TO_FRAMES(US_RTP_OPUS_HZ);
speex_resampler_process_interleaved_int(acap->res, in->data, &in_count, in_res, &out_count);
in_ptr = in_res;
} else {
assert(acap->pcm_hz == US_RTP_OPUS_HZ);
in_ptr = in->data;
}
const int out_ri = us_ring_producer_acquire(acap->enc_ring, 0);
if (out_ri < 0) {
US_JLOG_ERROR("acap", "OPUS encoder queue is full");
us_ring_consumer_release(acap->pcm_ring, in_ri);
continue;
}
us_au_encoded_s *const out = acap->enc_ring->items[out_ri];
const int size = opus_encode(acap->enc, in_ptr, US_AU_HZ_TO_FRAMES(US_RTP_OPUS_HZ), out->data, US_ARRAY_LEN(out->data));
us_ring_consumer_release(acap->pcm_ring, in_ri);
if (size > 0) {
out->used = size;
out->pts = acap->pts;
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
acap->pts += US_AU_HZ_TO_FRAMES(US_RTP_OPUS_HZ);
} else {
out->used = 0;
US_JLOG_PERROR_OPUS(size, "acap", "Fatal: Can't encode PCM frame to OPUS");
}
us_ring_producer_release(acap->enc_ring, out_ri);
}
atomic_store(&acap->stop, true);
return NULL;
}

59
janus/src/acap.h Normal file
View File

@ -0,0 +1,59 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 <stdatomic.h>
#include <pthread.h>
#include <alsa/asoundlib.h>
#include <speex/speex_resampler.h>
#include <opus/opus.h>
#include "uslibs/types.h"
#include "uslibs/ring.h"
typedef struct {
snd_pcm_t *dev;
uint pcm_hz;
uint pcm_frames;
uz pcm_size;
snd_pcm_hw_params_t *dev_params;
SpeexResamplerState *res;
OpusEncoder *enc;
us_ring_s *pcm_ring;
us_ring_s *enc_ring;
u32 pts;
pthread_t pcm_tid;
pthread_t enc_tid;
bool tids_created;
atomic_bool stop;
} us_acap_s;
us_acap_s *us_acap_init(const char *name, uint pcm_hz);
void us_acap_destroy(us_acap_s *acap);
int us_acap_get_encoded(us_acap_s *acap, u8 *data, uz *size, u64 *pts);

151
janus/src/au.c Normal file
View File

@ -0,0 +1,151 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "au.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include "uslibs/tools.h"
bool us_au_probe(const char *name) {
// This function is very limited. It takes something like:
// hw:0,0 or hw:tc358743,0 or plughw:UAC2Gadget,0
// parses card name (0, tc358743, UAC2Gadget) and checks
// the existence of it in /proc/asound/.
// It's enough for our case.
if (name == NULL) {
return false;
}
if (strchr(name, '/') || strchr(name, '.')) {
return false;
}
const char *begin = strchr(name, ':');
if (begin == NULL) {
return false;
}
begin += 1;
if (*begin == '\0') {
return false;
}
const char *end = strchr(begin, ',');
if (end == NULL) {
return false;
}
if (end - begin < 1) {
return false;
}
char *card = us_strdup(begin);
card[end - begin] = '\0';
bool numeric = true;
for (uz index = 0; card[index] != '\0'; ++index) {
if (!isdigit(card[index])) {
numeric = false;
break;
}
}
char *path;
if (numeric) {
US_ASPRINTF(path, "/proc/asound/card%s", card);
} else {
US_ASPRINTF(path, "/proc/asound/%s", card);
}
bool ok = false;
struct stat st;
if (lstat(path, &st) == 0) {
if (numeric && S_ISDIR(st.st_mode)) {
ok = true;
} else if (!numeric && S_ISLNK(st.st_mode)) {
ok = true;
}
}
free(path);
free(card);
return ok;
}
us_au_pcm_s *us_au_pcm_init(void) {
us_au_pcm_s *pcm;
US_CALLOC(pcm, 1);
return pcm;
}
void us_au_pcm_destroy(us_au_pcm_s *pcm) {
free(pcm);
}
void us_au_pcm_mix(us_au_pcm_s *dest, us_au_pcm_s *src) {
const uz size = src->frames * US_RTP_OPUS_CH * 2; // 2 for 16 bit
if (src->frames == 0) {
return;
} else if (dest->frames == 0) {
memcpy(dest->data, src->data, size);
dest->frames = src->frames;
} else if (dest->frames == src->frames) {
// https://stackoverflow.com/questions/12089662
for (uz index = 0; index < size; ++index) {
int a = dest->data[index];
int b = src->data[index];
int m;
a += 32768;
b += 32768;
if ((a < 32768) && (b < 32768)) {
m = a * b / 32768;
} else {
m = 2 * (a + b) - (a * b) / 32768 - 65536;
}
if (m == 65536) {
m = 65535;
}
m -= 32768;
dest->data[index] = m;
}
}
}
us_au_encoded_s *us_au_encoded_init(void) {
us_au_encoded_s *enc;
US_CALLOC(enc, 1);
return enc;
}
void us_au_encoded_destroy(us_au_encoded_s *enc) {
free(enc);
}

61
janus/src/au.h Normal file
View File

@ -0,0 +1,61 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "uslibs/types.h"
#include "rtp.h"
// A number of frames per 1 channel:
// - https://github.com/xiph/opus/blob/7b05f44/src/opus_demo.c#L368
#define US_AU_FRAME_MS 20
// #define _HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
#define US_AU_HZ_TO_FRAMES(_hz) ((_hz) / 50) // 20ms
#define US_AU_HZ_TO_BUF16(_hz) (US_AU_HZ_TO_FRAMES(_hz) * US_RTP_OPUS_CH) // ... * 2: One stereo frame = (16bit L) + (16bit R)
#define US_AU_HZ_TO_BUF8(_hz) (US_AU_HZ_TO_BUF16(_hz) * sizeof(s16))
#define US_AU_MIN_PCM_HZ 8000
#define US_AU_MAX_PCM_HZ 192000
#define US_AU_MAX_BUF16 US_AU_HZ_TO_BUF16(US_AU_MAX_PCM_HZ)
#define US_AU_MAX_BUF8 US_AU_HZ_TO_BUF8(US_AU_MAX_PCM_HZ)
typedef struct {
s16 data[US_AU_MAX_BUF16];
uz frames;
} us_au_pcm_s;
typedef struct {
u8 data[US_RTP_PAYLOAD_SIZE];
uz used;
u64 pts;
} us_au_encoded_s;
bool us_au_probe(const char *name);
us_au_pcm_s *us_au_pcm_init(void);
void us_au_pcm_destroy(us_au_pcm_s *pcm);
void us_au_pcm_mix(us_au_pcm_s *a, us_au_pcm_s *b);
us_au_encoded_s *us_au_encoded_init(void);
void us_au_encoded_destroy(us_au_encoded_s *enc);

276
janus/src/client.c Normal file
View File

@ -0,0 +1,276 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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"
#include <stdlib.h>
#include <stdatomic.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
#include <janus/plugins/plugin.h>
#include <janus/rtp.h>
#include <opus/opus.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/array.h"
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "logging.h"
#include "au.h"
#include "rtp.h"
static void *_video_thread(void *v_client);
static void *_acap_thread(void *v_client);
static void *_video_or_acap_thread(void *v_client, bool video);
static void *_aplay_thread(void *v_client);
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session) {
us_janus_client_s *client;
US_CALLOC(client, 1);
client->gw = gw;
client->session = session;
atomic_init(&client->transmit, false);
atomic_init(&client->transmit_acap, false);
atomic_init(&client->transmit_aplay, false);
atomic_init(&client->video_orient, 0);
atomic_init(&client->stop, false);
US_RING_INIT_WITH_ITEMS(client->video_ring, 2048, us_rtp_init);
US_THREAD_CREATE(client->video_tid, _video_thread, client);
US_RING_INIT_WITH_ITEMS(client->acap_ring, 64, us_rtp_init);
US_THREAD_CREATE(client->acap_tid, _acap_thread, client);
US_RING_INIT_WITH_ITEMS(client->aplay_enc_ring, 64, us_au_encoded_init);
US_RING_INIT_WITH_ITEMS(client->aplay_pcm_ring, 64, us_au_pcm_init);
US_THREAD_CREATE(client->aplay_tid, _aplay_thread, client);
return client;
}
void us_janus_client_destroy(us_janus_client_s *client) {
atomic_store(&client->stop, true);
US_THREAD_JOIN(client->video_tid);
US_RING_DELETE_WITH_ITEMS(client->video_ring, us_rtp_destroy);
US_THREAD_JOIN(client->acap_tid);
US_RING_DELETE_WITH_ITEMS(client->acap_ring, us_rtp_destroy);
US_THREAD_JOIN(client->aplay_tid);
US_RING_DELETE_WITH_ITEMS(client->aplay_enc_ring, us_au_encoded_destroy);
US_RING_DELETE_WITH_ITEMS(client->aplay_pcm_ring, us_au_pcm_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 || atomic_load(&client->transmit_acap))
) {
us_ring_s *const ring = (rtp->video ? client->video_ring : client->acap_ring);
const int ri = us_ring_producer_acquire(ring, 0);
if (ri < 0) {
US_JLOG_ERROR("client", "Session %p %s ring is full",
client->session, (rtp->video ? "video" : "acap"));
return;
}
memcpy(ring->items[ri], rtp, sizeof(us_rtp_s));
us_ring_producer_release(ring, ri);
}
}
void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet) {
if (
packet->video
|| packet->length < sizeof(janus_rtp_header)
|| !atomic_load(&client->transmit)
|| !atomic_load(&client->transmit_aplay)
) {
return;
}
const janus_rtp_header *const header = (janus_rtp_header*)packet->buffer;
if (header->type != US_RTP_OPUS_PAYLOAD) {
return;
}
const u16 seq = ntohs(header->seq_number);
if (
seq >= client->aplay_seq_next // In order or missing
|| (client->aplay_seq_next - seq) > 50 // In late sequence or sequence wrapped
) {
client->aplay_seq_next = seq + 1;
int size = 0;
const char *const data = janus_rtp_payload(packet->buffer, packet->length, &size);
if (data == NULL || size <= 0) {
return;
}
us_ring_s *const ring = client->aplay_enc_ring;
const int ri = us_ring_producer_acquire(ring, 0);
if (ri < 0) {
// US_JLOG_ERROR("client", "Session %p aplay ring is full", client->session);
return;
}
us_au_encoded_s *enc = ring->items[ri];
if ((uz)size < US_ARRAY_LEN(enc->data)) {
memcpy(enc->data, data, size);
enc->used = size;
} else {
enc->used = 0;
}
us_ring_producer_release(ring, ri);
}
}
static void *_video_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_vid");
return _video_or_acap_thread(v_client, true);
}
static void *_acap_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_ac");
return _video_or_acap_thread(v_client, false);
}
static void *_video_or_acap_thread(void *v_client, bool video) {
us_janus_client_s *const client = v_client;
us_ring_s *const ring = (video ? client->video_ring : client->acap_ring);
assert(ring != NULL);
while (!atomic_load(&client->stop)) {
const int ri = us_ring_consumer_acquire(ring, 0.1);
if (ri < 0) {
continue;
}
us_rtp_s rtp;
memcpy(&rtp, ring->items[ri], sizeof(us_rtp_s));
us_ring_consumer_release(ring, ri);
if (
atomic_load(&client->transmit)
&& (video || atomic_load(&client->transmit_acap))
) {
janus_plugin_rtp packet = {
.video = rtp.video,
.buffer = (char*)rtp.datagram,
.length = rtp.used,
# if JANUS_PLUGIN_API_VERSION >= 100
// The uStreamer Janus plugin places video in stream index 0 and audio
// (if available) in stream index 1.
.mindex = (rtp.video ? 0 : 1),
# endif
};
janus_plugin_rtp_extensions_reset(&packet.extensions);
if (rtp.first_of_frame) {
if (rtp.zero_playout_delay) {
// https://github.com/pikvm/pikvm/issues/784
packet.extensions.min_delay = 0;
packet.extensions.max_delay = 0;
} else {
// Эти дефолты используются в Chrome/Safari/Firefox.
// Работает всё одинаково, потому что у них общая кодовая база WebRTC.
packet.extensions.min_delay = 0;
packet.extensions.max_delay = 1000; // == 10s, i.e. 10ms granularity
}
}
if (rtp.video && rtp.first_of_frame) {
packet.extensions.abs_capture_ts = rtp.grab_ntp_ts;
}
if (rtp.video && rtp.last_of_frame) {
uint video_orient = atomic_load(&client->video_orient);
if (video_orient != 0) {
// The extension rotates the video clockwise, but want it counterclockwise.
// It's more intuitive for people who have seen a protractor at least once in their life.
if (video_orient == 90) {
video_orient = 270;
} else if (video_orient == 270) {
video_orient = 90;
}
packet.extensions.video_rotation = video_orient;
}
}
client->gw->relay_rtp(client->session, &packet);
}
}
return NULL;
}
static void *_aplay_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_ap");
us_janus_client_s *const client = v_client;
int err;
OpusDecoder *dec = opus_decoder_create(US_RTP_OPUS_HZ, US_RTP_OPUS_CH, &err);
assert(err == 0);
while (!atomic_load(&client->stop)) {
const int in_ri = us_ring_consumer_acquire(client->aplay_enc_ring, 0.1);
if (in_ri < 0) {
continue;
}
us_au_encoded_s *in = client->aplay_enc_ring->items[in_ri];
if (in->used == 0) {
us_ring_consumer_release(client->aplay_enc_ring, in_ri);
continue;
}
const int out_ri = us_ring_producer_acquire(client->aplay_pcm_ring, 0);
if (out_ri < 0) {
US_JLOG_ERROR("aplay", "OPUS decoder queue is full");
us_ring_consumer_release(client->aplay_enc_ring, in_ri);
continue;
}
us_au_pcm_s *out = client->aplay_pcm_ring->items[out_ri];
const int frames = opus_decode(dec, in->data, in->used, out->data, US_AU_HZ_TO_FRAMES(US_RTP_OPUS_HZ), 0);
us_ring_consumer_release(client->aplay_enc_ring, in_ri);
if (frames > 0) {
out->frames = frames;
} else {
out->frames = 0;
US_JLOG_PERROR_OPUS(frames, "aplay", "Fatal: Can't decode OPUS to PCM frame");
}
us_ring_producer_release(client->aplay_pcm_ring, out_ri);
}
opus_decoder_destroy(dec);
return NULL;
}

65
janus/src/client.h Normal file
View File

@ -0,0 +1,65 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 <stdatomic.h>
#include <pthread.h>
#include <janus/plugins/plugin.h>
#include "uslibs/types.h"
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "rtp.h"
typedef struct {
janus_callbacks *gw;
janus_plugin_session *session;
atomic_bool transmit;
atomic_bool transmit_acap;
atomic_bool transmit_aplay;
atomic_uint video_orient;
pthread_t video_tid;
pthread_t acap_tid;
pthread_t aplay_tid;
atomic_bool stop;
us_ring_s *video_ring;
us_ring_s *acap_ring;
us_ring_s *aplay_enc_ring;
u16 aplay_seq_next;
us_ring_s *aplay_pcm_ring;
US_LIST_DECLARE;
} us_janus_client_s;
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session);
void us_janus_client_destroy(us_janus_client_s *client);
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp);
void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet);

124
janus/src/config.c Normal file
View File

@ -0,0 +1,124 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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"
#include <stdlib.h>
#include <string.h>
#include <janus/config.h>
#include <janus/plugins/plugin.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "const.h"
#include "logging.h"
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
static uint _get_uint(janus_config *jcfg, const char *section, const char *option, uint def);
// 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, "video", "sink")) == NULL) {
US_JLOG_ERROR("config", "Missing config value: video.sink");
goto error;
}
if ((config->acap_dev_name = _get_value(jcfg, "acap", "device")) != NULL) {
config->acap_hz = _get_uint(jcfg, "acap", "sampling_rate", 0);
config->tc358743_dev_path = _get_value(jcfg, "acap", "tc358743");
if (config->acap_hz == 0 && config->tc358743_dev_path == NULL) {
US_JLOG_ERROR("config", "Either acap.sampling_rate or acap.tc358743 required");
goto error;
}
config->aplay_dev_name = _get_value(jcfg, "aplay", "device");
}
goto ok;
error:
US_DELETE(config, us_config_destroy);
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->acap_dev_name, free);
US_DELETE(config->tc358743_dev_path, free);
US_DELETE(config->aplay_dev_name, 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 uint _get_uint(janus_config *jcfg, const char *section, const char *option, uint def) {
char *const tmp = _get_value(jcfg, section, option);
uint value = def;
if (tmp != NULL) {
errno = 0;
value = (uint)strtoul(tmp, NULL, 10);
if (errno != 0) {
value = def;
}
free(tmp);
}
return 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;
}*/

41
janus/src/config.h Normal file
View File

@ -0,0 +1,41 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "uslibs/types.h"
typedef struct {
char *video_sink_name;
char *acap_dev_name;
uint acap_hz;
char *tc358743_dev_path;
char *aplay_dev_name;
} us_config_s;
us_config_s *us_config_init(const char *config_dir_path);
void us_config_destroy(us_config_s *config);

26
janus/src/const.h Normal file
View File

@ -0,0 +1,26 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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_PLUGIN_NAME "ustreamer"
#define US_PLUGIN_PACKAGE "janus.plugin.ustreamer"

43
janus/src/logging.h Normal file
View File

@ -0,0 +1,43 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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); \
}
// We don't include alsa, speex and opus headers here
#define US_JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
#define US_JLOG_PERROR_RES(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
#define US_JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))

80
janus/src/memsinkfd.c Normal file
View File

@ -0,0 +1,80 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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"
#include <unistd.h>
#include <linux/videodev2.h>
#include "uslibs/types.h"
#include "uslibs/errors.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, u64 last_id) {
const ldf deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
ldf now_ts;
do {
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
now_ts = 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_ts < deadline_ts);
return US_ERROR_NO_DATA;
}
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required) {
us_frame_set_data(frame, us_memsink_get_data(mem), 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 retval = 0;
if (frame->format != V4L2_PIX_FMT_H264) {
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
retval = -1;
}
if (flock(fd, LOCK_UN) < 0) {
US_JLOG_PERROR("video", "Can't unlock memsink");
retval = -1;
}
return retval;
}

31
janus/src/memsinkfd.h Normal file
View File

@ -0,0 +1,31 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "uslibs/types.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s *mem, u64 last_id);
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required);

771
janus/src/plugin.c Normal file
View File

@ -0,0 +1,771 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 <stdatomic.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <pthread.h>
#include <jansson.h>
#include <janus/plugins/plugin.h>
#include <janus/rtp.h>
#include <janus/rtcp.h>
#include <alsa/asoundlib.h>
#include "uslibs/types.h"
#include "uslibs/const.h"
#include "uslibs/errors.h"
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "uslibs/memsinksh.h"
#include "uslibs/tc358743.h"
#include "const.h"
#include "logging.h"
#include "client.h"
#include "au.h"
#include "acap.h"
#include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
#include "sdp.h"
#include "memsinkfd.h"
#include "config.h"
static const char *const default_ice_url = "stun:stun.l.google.com:19302";
static us_config_s *_g_config = NULL;
static const useconds_t _g_watchers_polling = 100000;
static us_janus_client_s *_g_clients = NULL;
static janus_callbacks *_g_gw = NULL;
static us_ring_s *_g_video_ring = 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_acap_tid;
static atomic_bool _g_acap_tid_created = false;
static pthread_t _g_aplay_tid;
static atomic_bool _g_aplay_tid_created = false;
static pthread_mutex_t _g_video_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t _g_acap_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t _g_aplay_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_has_listeners = false;
static atomic_bool _g_has_speakers = 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_ACAP US_MUTEX_LOCK(_g_acap_lock)
#define _UNLOCK_ACAP US_MUTEX_UNLOCK(_g_acap_lock)
#define _LOCK_APLAY US_MUTEX_LOCK(_g_aplay_lock)
#define _UNLOCK_APLAY US_MUTEX_UNLOCK(_g_aplay_lock)
#define _LOCK_ALL { _LOCK_VIDEO; _LOCK_ACAP; _LOCK_APLAY; }
#define _UNLOCK_ALL { _UNLOCK_APLAY; _UNLOCK_ACAP; _UNLOCK_VIDEO; }
#define _READY atomic_load(&_g_ready)
#define _STOP atomic_load(&_g_stop)
#define _HAS_WATCHERS atomic_load(&_g_has_watchers)
#define _HAS_LISTENERS atomic_load(&_g_has_listeners)
#define _HAS_SPEAKERS atomic_load(&_g_has_speakers)
#define _IF_DISABLED(...) { if (!_READY || _STOP) { __VA_ARGS__ } }
janus_plugin *create(void);
static void *_video_rtp_thread(void *arg) {
(void)arg;
US_THREAD_SETTLE("us_p_rtpv");
atomic_store(&_g_video_rtp_tid_created, true);
while (!_STOP) {
const int ri = us_ring_consumer_acquire(_g_video_ring, 0.1);
if (ri >= 0) {
const us_frame_s *const frame = _g_video_ring->items[ri];
_LOCK_VIDEO;
const bool zero_playout_delay = (frame->gop == 0);
us_rtpv_wrap(_g_rtpv, frame, zero_playout_delay);
_UNLOCK_VIDEO;
us_ring_consumer_release(_g_video_ring, ri);
}
}
return NULL;
}
static void *_video_sink_thread(void *arg) {
(void)arg;
US_THREAD_SETTLE("us_p_vsink");
atomic_store(&_g_video_sink_tid_created, true);
us_frame_s *drop = us_frame_init();
u64 frame_id = 0;
int once = 0;
while (!_STOP) {
if (!_HAS_WATCHERS) {
US_ONCE({ US_JLOG_INFO("video", "No active watchers, memsink disconnected"); });
usleep(_g_watchers_polling);
continue;
}
int fd = -1;
us_memsink_shared_s *mem = NULL;
const uz data_size = us_memsink_calculate_size(_g_config->video_sink_name);
if (data_size == 0) {
US_ONCE({ US_JLOG_ERROR("video", "Invalid memsink object suffix"); });
goto close_memsink;
}
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
US_ONCE({ US_JLOG_PERROR("video", "Can't open memsink"); });
goto close_memsink;
}
if ((mem = us_memsink_shared_map(fd, data_size)) == NULL) {
US_ONCE({ US_JLOG_PERROR("video", "Can't map memsink"); });
goto close_memsink;
}
once = 0;
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
while (!_STOP && _HAS_WATCHERS) {
const int waited = us_memsink_fd_wait_frame(fd, mem, frame_id);
if (waited == 0) {
const int ri = us_ring_producer_acquire(_g_video_ring, 0);
us_frame_s *frame;
if (ri >= 0) {
frame = _g_video_ring->items[ri];
} else {
US_ONCE({ US_JLOG_PERROR("video", "Video ring is full"); });
frame = drop;
}
const int got = us_memsink_fd_get_frame(fd, mem, frame, &frame_id, atomic_load(&_g_key_required));
if (ri >= 0) {
us_ring_producer_release(_g_video_ring, ri);
}
if (got < 0) {
goto close_memsink;
}
if (ri >= 0 && frame->key) {
atomic_store(&_g_key_required, false);
}
} else if (waited != US_ERROR_NO_DATA) {
goto close_memsink;
}
}
close_memsink:
if (mem != NULL) {
us_memsink_shared_unmap(mem, data_size);
mem = NULL;
}
US_CLOSE_FD(fd);
US_JLOG_INFO("video", "Memsink closed");
sleep(1); // error_delay
}
us_frame_destroy(drop);
return NULL;
}
static int _get_acap_hz(uint *hz) {
if (_g_config->acap_hz != 0) {
*hz = _g_config->acap_hz;
return 0;
}
if (_g_config->tc358743_dev_path == NULL) {
US_JLOG_ERROR("acap", "No configured sampling rate");
return -1;
}
int fd;
if ((fd = open(_g_config->tc358743_dev_path, O_RDWR)) < 0) {
US_JLOG_PERROR("acap", "Can't open TC358743 V4L2 device");
return -1;
}
const int checked = us_tc358743_xioctl_get_audio_hz(fd, hz);
if (checked < 0) {
US_JLOG_PERROR("acap", "Can't check TC358743 audio state (%d)", checked);
close(fd);
return -1;
}
close(fd);
return 0;
}
static void *_acap_thread(void *arg) {
(void)arg;
US_THREAD_SETTLE("us_p_ac");
atomic_store(&_g_acap_tid_created, true);
assert(_g_config->acap_dev_name != NULL);
assert(_g_rtpa != NULL);
int once = 0;
while (!_STOP) {
if (!_HAS_WATCHERS || !_HAS_LISTENERS) {
usleep(_g_watchers_polling);
continue;
}
uint hz = 0;
us_acap_s *acap = NULL;
if (!us_au_probe(_g_config->acap_dev_name)) {
US_ONCE({ US_JLOG_ERROR("acap", "No PCM capture device"); });
goto close_acap;
}
if (_get_acap_hz(&hz) < 0) {
goto close_acap;
}
if (hz == 0) {
US_ONCE({ US_JLOG_INFO("acap", "No audio presented from the host"); });
goto close_acap;
}
US_ONCE({ US_JLOG_INFO("acap", "Detected host audio"); });
if ((acap = us_acap_init(_g_config->acap_dev_name, hz)) == NULL) {
goto close_acap;
}
once = 0;
while (!_STOP && _HAS_WATCHERS && _HAS_LISTENERS) {
if (_get_acap_hz(&hz) < 0 || acap->pcm_hz != hz) {
goto close_acap;
}
uz size = US_RTP_TOTAL_SIZE - US_RTP_HEADER_SIZE;
u8 data[size];
u64 pts;
const int result = us_acap_get_encoded(acap, data, &size, &pts);
if (result == 0) {
_LOCK_ACAP;
us_rtpa_wrap(_g_rtpa, data, size, pts);
_UNLOCK_ACAP;
} else if (result == -1) {
goto close_acap;
}
}
close_acap:
US_DELETE(acap, us_acap_destroy);
sleep(1); // error_delay
}
return NULL;
}
static void *_aplay_thread(void *arg) {
(void)arg;
US_THREAD_SETTLE("us_p_ap");
atomic_store(&_g_aplay_tid_created, true);
assert(_g_config->aplay_dev_name != NULL);
int once = 0;
while (!_STOP) {
snd_pcm_t *dev = NULL;
bool skip = true;
while (!_STOP) {
usleep((US_AU_FRAME_MS / 4) * 1000);
us_au_pcm_s mixed = {0};
_LOCK_APLAY;
US_LIST_ITERATE(_g_clients, client, {
us_au_pcm_s last = {0};
do {
const int ri = us_ring_consumer_acquire(client->aplay_pcm_ring, 0);
if (ri >= 0) {
const us_au_pcm_s *pcm = client->aplay_pcm_ring->items[ri];
memcpy(&last, pcm, sizeof(us_au_pcm_s));
us_ring_consumer_release(client->aplay_pcm_ring, ri);
} else {
break;
}
} while (skip && !_STOP);
us_au_pcm_mix(&mixed, &last);
// US_JLOG_INFO("++++++", "mixed %p", client);
});
_UNLOCK_APLAY;
// US_JLOG_INFO("++++++", "--------------");
if (skip) {
static uint skipped = 0;
if (skipped < (1000 / (US_AU_FRAME_MS / 4))) {
++skipped;
continue;
} else {
skipped = 0;
}
}
if (!_HAS_WATCHERS || !_HAS_LISTENERS || !_HAS_SPEAKERS) {
goto close_aplay;
}
if (dev == NULL) {
if (!us_au_probe(_g_config->aplay_dev_name)) {
US_ONCE({ US_JLOG_ERROR("aplay", "No PCM playback device"); });
goto close_aplay;
}
int err = snd_pcm_open(&dev, _g_config->aplay_dev_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
US_ONCE({ US_JLOG_PERROR_ALSA(err, "aplay", "Can't open PCM playback"); });
goto close_aplay;
}
err = snd_pcm_set_params(dev, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED,
US_RTP_OPUS_CH, US_RTP_OPUS_HZ, 1 /* soft resample */, 50000 /* 50000 = 0.05sec */
);
if (err < 0) {
US_ONCE({ US_JLOG_PERROR_ALSA(err, "aplay", "Can't configure PCM playback"); });
goto close_aplay;
}
US_JLOG_INFO("aplay", "Playback opened, playing ...");
once = 0;
}
if (dev != NULL && mixed.frames > 0) {
snd_pcm_sframes_t frames = snd_pcm_writei(dev, mixed.data, mixed.frames);
if (frames < 0) {
frames = snd_pcm_recover(dev, frames, 1);
} else {
if (once != 0) {
US_JLOG_INFO("aplay", "Playing resumed (snd_pcm_writei) ...");
}
once = 0;
skip = false;
}
if (frames < 0) {
US_ONCE({ US_JLOG_PERROR_ALSA(frames, "aplay", "Can't play to PCM playback"); });
if (frames == -ENODEV) {
goto close_aplay;
}
skip = true;
} else {
if (once != 0) {
US_JLOG_INFO("aplay", "Playing resumed (snd_pcm_recover) ...");
}
once = 0;
skip = false;
}
}
}
close_aplay:
if (dev != NULL) {
US_DELETE(dev, snd_pcm_close);
US_JLOG_INFO("aplay", "Playback closed");
}
}
return NULL;
}
static void _relay_rtp_clients(const us_rtp_s *rtp) {
US_LIST_ITERATE(_g_clients, client, {
us_janus_client_send(client, rtp);
});
}
static void _alsa_quiet(const char *file, int line, const char *func, int err, const char *fmt, ...) {
(void)file;
(void)line;
(void)func;
(void)err;
(void)fmt;
}
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;
snd_lib_error_set_handler(_alsa_quiet);
US_RING_INIT_WITH_ITEMS(_g_video_ring, 64, us_frame_init);
_g_rtpv = us_rtpv_init(_relay_rtp_clients);
if (_g_config->acap_dev_name != NULL) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_acap_tid, _acap_thread, NULL);
if (_g_config->aplay_dev_name != NULL) {
US_THREAD_CREATE(_g_aplay_tid, _aplay_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_acap_tid);
JOIN(_g_aplay_tid);
# undef JOIN
US_LIST_ITERATE(_g_clients, client, {
US_LIST_REMOVE(_g_clients, client);
us_janus_client_destroy(client);
});
US_RING_DELETE_WITH_ITEMS(_g_video_ring, us_frame_destroy);
US_DELETE(_g_rtpa, us_rtpa_destroy);
US_DELETE(_g_rtpv, us_rtpv_destroy);
US_DELETE(_g_config, us_config_destroy);
}
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);
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;
bool has_listeners = false;
bool has_speakers = 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));
has_listeners = (has_listeners || atomic_load(&client->transmit_acap));
has_speakers = (has_speakers || atomic_load(&client->transmit_aplay));
}
});
if (!found) {
US_JLOG_WARN("main", "No session %p", session);
*err = -2;
}
atomic_store(&_g_has_watchers, has_watchers);
atomic_store(&_g_has_listeners, has_listeners);
atomic_store(&_g_has_speakers, has_speakers);
_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, const char *msg, bool transmit) {
(void)msg;
_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;
}
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) {
janus_plugin_result_type result_type = JANUS_PLUGIN_OK;
char *result_msg = NULL;
if (session == NULL || msg == NULL) {
result_type = JANUS_PLUGIN_ERROR;
result_msg = (msg ? "No session" : "No message");
goto done;
}
# 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(), NULL, m_event, NULL); \
json_decref(m_event); \
}
json_t *const request = json_object_get(msg, "request");
if (request == NULL) {
PUSH_ERROR(400, "Request missing");
goto done;
}
const char *const request_str = json_string_value(request);
if (request_str == NULL) {
PUSH_ERROR(400, "Request not a string");
goto done;
}
// US_JLOG_INFO("main", "Message: %s", request_str);
# define PUSH_STATUS(x_status, x_payload, 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)); \
if (x_payload != NULL) { \
json_object_set(m_result, x_status, x_payload); \
} \
json_object_set_new(m_event, "result", m_result); \
_g_gw->push_event(session, create(), NULL, m_event, x_jsep); \
json_decref(m_event); \
}
if (!strcmp(request_str, "start")) {
PUSH_STATUS("started", NULL, NULL);
} else if (!strcmp(request_str, "stop")) {
PUSH_STATUS("stopped", NULL, NULL);
} else if (!strcmp(request_str, "watch")) {
uint video_orient = 0;
bool with_acap = false;
bool with_aplay = false;
{
json_t *const params = json_object_get(msg, "params");
if (params != NULL) {
{
json_t *const obj = json_object_get(params, "audio");
if (obj != NULL && json_is_boolean(obj)) {
with_acap = (us_au_probe(_g_config->acap_dev_name) && json_boolean_value(obj));
}
}
{
json_t *const obj = json_object_get(params, "mic");
if (obj != NULL && json_is_boolean(obj)) {
with_aplay = (us_au_probe(_g_config->aplay_dev_name) && json_boolean_value(obj));
}
}
{
json_t *const obj = json_object_get(params, "orientation");
if (obj != NULL && json_is_integer(obj)) {
video_orient = json_integer_value(obj);
switch (video_orient) {
case 90: case 180: case 270: break;
default: video_orient = 0; break;
}
}
}
}
}
{
char *const sdp = us_sdp_create(
_g_rtpv,
(with_acap ? _g_rtpa : NULL),
(with_acap && with_aplay));
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
PUSH_STATUS("started", NULL, offer_jsep);
json_decref(offer_jsep);
free(sdp);
}
{
_LOCK_ALL;
bool has_listeners = false;
bool has_speakers = false;
US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
atomic_store(&client->transmit_acap, with_acap);
atomic_store(&client->transmit_aplay, with_aplay);
atomic_store(&client->video_orient, video_orient);
}
has_listeners = (has_listeners || atomic_load(&client->transmit_acap));
has_speakers = (has_speakers || atomic_load(&client->transmit_aplay));
});
atomic_store(&_g_has_listeners, has_listeners);
atomic_store(&_g_has_speakers, has_speakers);
_UNLOCK_ALL;
}
} else if (!strcmp(request_str, "features")) {
const char *const ice_url = getenv("JANUS_USTREAMER_WEB_ICE_URL");
const bool acap_avail = us_au_probe(_g_config->acap_dev_name);
json_t *const features = json_pack(
"{s:b, s:b, s:{s:s?}}",
"audio", acap_avail,
"mic", (acap_avail && us_au_probe(_g_config->aplay_dev_name)),
"ice", "url", (ice_url != NULL ? ice_url : default_ice_url)
);
PUSH_STATUS("features", features, NULL);
json_decref(features);
} else if (!strcmp(request_str, "key_required")) {
// US_JLOG_INFO("main", "Got key_required message");
atomic_store(&_g_key_required, true);
} else {
PUSH_ERROR(405, "Not implemented");
}
done:
US_DELETE(transaction, free);
US_DELETE(msg, json_decref);
US_DELETE(jsep, json_decref);
return janus_plugin_result_new(
result_type, result_msg,
(result_type == JANUS_PLUGIN_OK ? json_pack("{sb}", "ok", 1) : NULL));
# undef PUSH_STATUS
# undef PUSH_ERROR
}
static void _plugin_incoming_rtp(janus_plugin_session *session, janus_plugin_rtp *packet) {
_IF_DISABLED({ return; });
if (session == NULL || packet == NULL || packet->video) {
return; // Accept only valid audio
}
_LOCK_APLAY;
US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
us_janus_client_recv(client, packet);
break;
}
});
_UNLOCK_APLAY;
}
static void _plugin_incoming_rtcp(janus_plugin_session *session, janus_plugin_rtcp *packet) {
_IF_DISABLED({ return; });
if (session == NULL || packet == NULL || !packet->video) {
return; // Accept only valid video
}
if (janus_rtcp_has_pli(packet->buffer, packet->length)) {
// US_JLOG_INFO("main", "Got video PLI");
atomic_store(&_g_key_required, true);
}
}
// ***** 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; }
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,
.incoming_rtcp = _plugin_incoming_rtcp,
);
# pragma GCC diagnostic pop
return &plugin;
}

65
janus/src/rtp.c Normal file
View File

@ -0,0 +1,65 @@
/*****************************************************************************
# #
# 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-2024 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"
#include <stdlib.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
us_rtp_s *us_rtp_init(void) {
us_rtp_s *rtp;
US_CALLOC(rtp, 1);
return rtp;
}
void us_rtp_destroy(us_rtp_s *rtp) {
free(rtp);
}
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video) {
rtp->payload = payload;
rtp->video = video;
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
}
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked) {
u32 word0 = 0x80000000;
if (marked) {
word0 |= 1 << 23;
}
word0 |= (rtp->payload & 0x7F) << 16;
word0 |= rtp->seq;
++rtp->seq;
# define WRITE_BE_U32(x_offset, x_value) \
*((u32*)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
WRITE_BE_U32(0, word0);
WRITE_BE_U32(4, pts);
WRITE_BE_U32(8, rtp->ssrc);
# undef WRITE_BE_U32
}

69
janus/src/rtp.h Normal file
View File

@ -0,0 +1,69 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "uslibs/types.h"
// Max RTP size for WebRTC is 1200 bytes:
// - https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
// But(!) Tailscale has 1200 MTU. So to fit it required to substract:
// 1. possible RTP extensions (see sdp.c)
// 2. additional SRTP fields (>= 10 bytes)
// 3. UDP header (8 bytes)
// 4. IPv6 header (40 bytes)
// Finally it looks like 100 bytes for all above should be enough
#define US_RTP_TOTAL_SIZE (1200 - 100)
#define US_RTP_HEADER_SIZE 12
#define US_RTP_PAYLOAD_SIZE (US_RTP_TOTAL_SIZE - US_RTP_HEADER_SIZE)
#define US_RTP_H264_PAYLOAD 96
#define US_RTP_OPUS_PAYLOAD 111
#define US_RTP_OPUS_HZ 48000
#define US_RTP_OPUS_CH 2
typedef struct {
uint payload;
bool video;
u32 ssrc;
u16 seq;
u8 datagram[US_RTP_TOTAL_SIZE];
uz used;
bool first_of_frame;
bool last_of_frame;
bool zero_playout_delay;
u64 grab_ntp_ts;
} us_rtp_s;
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
us_rtp_s *us_rtp_init(void);
void us_rtp_destroy(us_rtp_s *rtp);
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video);
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked);

54
janus/src/rtpa.c Normal file
View File

@ -0,0 +1,54 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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"
#include <stdlib.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "rtp.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();
us_rtp_assign(rtpa->rtp, US_RTP_OPUS_PAYLOAD, false);
rtpa->callback = callback;
return rtpa;
}
void us_rtpa_destroy(us_rtpa_s *rtpa) {
us_rtp_destroy(rtpa->rtp);
free(rtpa);
}
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts) {
if (size + US_RTP_HEADER_SIZE <= US_RTP_TOTAL_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);
}
}

39
janus/src/rtpa.h Normal file
View File

@ -0,0 +1,39 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "uslibs/types.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);
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts);

191
janus/src/rtpv.c Normal file
View File

@ -0,0 +1,191 @@
/*****************************************************************************
# #
# 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-2024 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"
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/frame.h"
#include "rtp.h"
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked);
static sz _find_annexb(const u8 *data, uz size);
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback) {
us_rtpv_s *rtpv;
US_CALLOC(rtpv, 1);
rtpv->rtp = us_rtp_init();
us_rtp_assign(rtpv->rtp, US_RTP_H264_PAYLOAD, true);
rtpv->callback = callback;
return rtpv;
}
void us_rtpv_destroy(us_rtpv_s *rtpv) {
us_rtp_destroy(rtpv->rtp);
free(rtpv);
}
#define _PRE 3 // Annex B prefix length
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay) {
// 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);
rtpv->rtp->first_of_frame = true;
rtpv->rtp->last_of_frame = false;
rtpv->rtp->zero_playout_delay = zero_playout_delay;
rtpv->rtp->grab_ntp_ts = us_get_now_ntp() - us_ld_to_ntp(us_get_now_monotonic() - frame->grab_begin_ts);
const u32 pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
sz last_offset = -_PRE;
while (true) { // Find and iterate by nalus
const uz next_start = last_offset + _PRE;
sz offset = _find_annexb(frame->data + next_start, frame->used - next_start);
if (offset < 0) {
break;
}
offset += next_start;
if (last_offset >= 0) {
const u8 *const data = frame->data + last_offset + _PRE;
uz 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 u8 *const data = frame->data + last_offset + _PRE;
uz size = frame->used - last_offset - _PRE;
_rtpv_process_nalu(rtpv, data, size, pts, true);
}
}
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked) {
const uint ref_idc = (data[0] >> 5) & 3;
const uint type = data[0] & 0x1F;
u8 *dg = rtpv->rtp->datagram;
// Set *_of_frame flags only for non-SPS/PPS packages
/*
# define CALL_FOR_SERVICE { \
const bool m_fof = rtpv->rtp->first_of_frame; \
const bool m_lof = rtpv->rtp->last_of_frame; \
rtpv->rtp->first_of_frame = false; \
rtpv->rtp->last_of_frame = false; \
rtpv->callback(rtpv->rtp); \
rtpv->rtp->first_of_frame = m_fof; \
rtpv->rtp->last_of_frame = m_lof; \
}
*/
if (size + US_RTP_HEADER_SIZE <= US_RTP_TOTAL_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;
// if (type == 7 || type == 8) {
// CALL_FOR_SERVICE;
// } else {*/
rtpv->rtp->last_of_frame = true;
rtpv->callback(rtpv->rtp);
rtpv->rtp->first_of_frame = false;
// }
return;
}
const uz fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
const u8 *src = data + 1;
sz remaining = size - 1;
bool first = true;
while (remaining > 0) {
sz frag_size = US_RTP_TOTAL_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);
u8 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;
// if (type == 7 || type == 8) {
// CALL_FOR_SERVICE;
// } else {
rtpv->rtp->last_of_frame = last;
rtpv->callback(rtpv->rtp);
rtpv->rtp->first_of_frame = false;
// }
src += frag_size;
remaining -= frag_size;
first = false;
}
# undef CALL_FOR_SERVICE
}
static sz _find_annexb(const u8 *data, uz size) {
// Parses buffer for 00 00 01 start codes
if (size >= _PRE) {
for (uz index = 0; index <= size - _PRE; ++index) {
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return index;
}
}
}
return -1;
}
#undef _PRE

40
janus/src/rtpv.h Normal file
View File

@ -0,0 +1,40 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "uslibs/types.h"
#include "uslibs/frame.h"
#include "rtp.h"
typedef struct {
us_rtp_s *rtp;
us_rtp_callback_f callback;
} us_rtpv_s;
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback);
void us_rtpv_destroy(us_rtpv_s *rtpv);
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay);

110
janus/src/sdp.c Normal file
View File

@ -0,0 +1,110 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "sdp.h"
#include <inttypes.h>
#include <janus/plugins/plugin.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic) {
char *video_sdp;
{
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
const uint pl = rtpv->rtp->payload;
US_ASPRINTF(
video_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;packetization-mode=1" RN
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=mid:v" RN
"a=msid:video v" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=extmap:1/sendonly urn:3gpp:video-orientation" RN
"a=extmap:2/sendonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
"a=extmap:3/sendonly http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" RN
"a=sendonly" RN,
pl, pl, pl, pl, pl, pl,
rtpv->rtp->ssrc);
}
char *audio_sdp;
if (rtpa == NULL) {
audio_sdp = us_strdup("");
} else {
const uint pl = rtpa->rtp->payload;
US_ASPRINTF(
audio_sdp,
"m=audio 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u OPUS/%u/%u" RN
"a=fmtp:%u sprop-stereo=1" RN // useinbandfec=1
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=mid:a" RN
"a=msid:audio a" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=%s" RN,
pl, pl,
US_RTP_OPUS_HZ, US_RTP_OPUS_CH,
pl, pl, pl, pl,
rtpa->rtp->ssrc,
(mic ? "sendrecv" : "sendonly"));
}
char *sdp;
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,
# if JANUS_PLUGIN_API_VERSION >= 100
// Place video SDP before audio SDP so that the video and audio streams
// have predictable indices, even if audio is not available.
// See also client.c.
video_sdp, audio_sdp
# else
// For versions of Janus prior to 1.x, place the audio SDP first.
audio_sdp, video_sdp
# endif
);
free(audio_sdp);
free(video_sdp);
return sdp;
}

31
janus/src/sdp.h Normal file
View File

@ -0,0 +1,31 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "uslibs/types.h"
#include "rtpv.h"
#include "rtpa.h"
char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic);

1
janus/src/uslibs/array.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/array.h

1
janus/src/uslibs/const.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/const.h

1
janus/src/uslibs/errors.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/errors.h

1
janus/src/uslibs/frame.c Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/frame.c

1
janus/src/uslibs/frame.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/frame.h

1
janus/src/uslibs/list.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/list.h

View File

@ -0,0 +1 @@
../../../src/libs/memsinksh.c

View File

@ -0,0 +1 @@
../../../src/libs/memsinksh.h

1
janus/src/uslibs/queue.c Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/queue.c

1
janus/src/uslibs/queue.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/queue.h

1
janus/src/uslibs/ring.c Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/ring.c

1
janus/src/uslibs/ring.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/ring.h

1
janus/src/uslibs/tc358743.c Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/tc358743.c

1
janus/src/uslibs/tc358743.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/tc358743.h

View File

@ -0,0 +1 @@
../../../src/libs/threading.h

1
janus/src/uslibs/tools.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/tools.h

1
janus/src/uslibs/types.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/types.h

1
janus/src/uslibs/xioctl.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/xioctl.h

2
linters/.dockerignore Normal file
View File

@ -0,0 +1,2 @@
/.tox/
/.mypy_cache/

26
linters/Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM archlinux/archlinux:base
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

4
linters/cppcheck.h Normal file
View File

@ -0,0 +1,4 @@
#define CHAR_BIT 8
#define WITH_GPIO
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1

9
linters/flake8.ini Normal file
View 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
View File

@ -0,0 +1,5 @@
[mypy]
python_version = 3.14
ignore_missing_imports = true
disallow_untyped_defs = true
strict_optional = true

54
linters/pylint.ini Normal file
View 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 = _, __, i, x, y, ws, make-html-h, make-ico-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
View File

@ -0,0 +1,52 @@
[tox]
envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true
[testenv]
basepython = python3.14
changedir = /src
[testenv:cppcheck]
allowlist_externals = cppcheck
commands = cppcheck \
--force \
--std=c17 \
--error-exitcode=1 \
--quiet \
--check-level=exhaustive \
--enable=warning,portability,performance,style \
--suppress=variableScope \
--inline-suppr \
--library=python \
--include=linters/cppcheck.h \
src python/src/*.? janus/src/*.?
[testenv:flake8]
allowlist_externals = bash
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
deps =
flake8
flake8-quotes
[testenv:pylint]
allowlist_externals = bash
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py python/*.py'
deps =
pylint
setuptools
[testenv:mypy]
allowlist_externals = bash
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py python/*.py'
deps =
mypy
[testenv:vulture]
allowlist_externals = bash
commands = bash -c 'vulture tools/*.py python/*.py'
deps =
vulture
[testenv:htmlhint]
allowlist_externals = htmlhint
commands = htmlhint src/ustreamer/http/data/*.html

94
man/ustreamer-dump.1 Normal file
View 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 6.55" "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\-required
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

359
man/ustreamer.1 Normal file
View File

@ -0,0 +1,359 @@
.\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 6.55" "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 TC358743-based capture device 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 interface
.nf
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
.nf
\fB\-\-persistent \e\fR # Suppress repetitive signal source errors (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, YVYU, UYVY, YUV420, YVU420, RGB565, RGB24, GREY, MJPEG, 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 than this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
.TP
.BR \-T ", " \-\-allow\-truncated\-frames
Allows to handle truncated frames. Useful if the device produces incorrect but still acceptable frames. Default: disabled.
.TP
.BR \-n ", " \-\-persistent
Suppress repetitive signal source errors. 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 be 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.
.TP
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
It doesn't do anything. Still here for compatibility.
.TP
.BR \-k\ \fIpath ", " \-\-blank\ \fIpath
It doesn't do anything. Still here for compatibility.
.TP
.BR \-K\ \fIsec ", " \-\-last\-as\-blank\ \fIsec
It doesn't do anything. Still here for compatibility.
.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 \-\-jpeg\-sink\ \fIname
Use the specified shared memory object to sink JPEG frames. The name should end with a suffix ".jpeg" or ":jpeg". Default: disabled.
.TP
.BR \-\-jpeg\-sink\-mode\ \fImode
Set JPEG sink permissions (like 777). Default: 660.
.TP
.BR \-\-jpeg\-sink\-rm
Remove shared memory on stop. Default: disabled.
.TP
.BR \-\-jpeg\-sink\-client\-ttl\ \fIsec
Client TTL. Default: 10.
.TP
.BR \-\-jpeg\-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. The name should end with a suffix ".h264" or ":h264". 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
Interval between keyframes. Default: 30.
.TP
.BR \-\-h264\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
.TP
.BR \-\-h264\-boost\-device
Increase encoder performance on PiKVM V4. Default: disabled.
.SS "RAW sink options"
.TP
.BR \-\-raw\-sink\ \fIname
Use the specified shared memory object to sink RAW frames. The name should end with a suffix ".raw" or ":raw". Default: disabled.
.TP
.BR \-\-raw\-sink\-mode\ \fImode
Set RAW sink permissions (like 777). Default: 660.
.TP
.BR \-\-raw\-sink\-rm
Remove shared memory on stop. Default: disabled.
.TP
.BR \-\-raw\-sink\-client\-ttl\ \fIsec
Client TTL. Default: 10.
.TP
.BR \-\-raw\-sink\-timeout\ \fIsec
Timeout for lock. Default: 1.
.SS "Process options"
.TP
.BR \-\-exit\-on\-parent\-death
Exit the program if the parent process is dead. Required \fBWITH_PDEATHSIG\fR feature. Default: disabled.
.TP
.BR \-\-exit\-on\-device\-error
Exit on any device error instead of polling until success. 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

42
pkg/arch/PKGBUILD Normal file
View File

@ -0,0 +1,42 @@
# Contributor: Maxim Devaev <mdevaev@gmail.com>
# Author: Maxim Devaev <mdevaev@gmail.com>
pkgname=ustreamer
pkgver=6.55
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 pkgconf 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>=3.14" "python<3.15")
makedepends+=(python-setuptools python-pip python-build python-wheel)
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
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
}

View 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/edid/v2.hex -O /edid.hex
COPY pkg/docker/entry.sh /
EXPOSE 8080
ENTRYPOINT ["/entry.sh"]
CMD ["--dv-timings", "--format", "UYVY"]
# vim: syntax=dockerfile

View 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

View 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

View 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
View 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 $@

View 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
}

49
pkg/openwrt/Makefile Normal file
View File

@ -0,0 +1,49 @@
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
# This package is just an example. For OpenWRT it is recommended to use upstream package:
# - https://github.com/openwrt/packages/tree/master/multimedia/ustreamer
#
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=6.55
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:=+libatomic +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))

View 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 ''

View 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
}

2
python/MANIFEST.in Normal file
View File

@ -0,0 +1,2 @@
include setup.py
recursive-include src *.c *h

21
python/Makefile Normal file
View File

@ -0,0 +1,21 @@
R_DESTDIR ?=
PREFIX ?= /usr/local
PY ?= python3
# =====
all: root
root: $(shell find src -type f,l) setup.py
$(info == PY_BUILD ustreamer-*.so)
rm -rf root
$(ECHO) $(PY) -m build --skip-dependency-check --no-isolation
$(ECHO) $(PY) -m pip install dist/*.whl --ignore-installed --root=./root
install:
$(PY) -m pip install dist/*.whl --ignore-installed --prefix=$(PREFIX) --root=$(if $(R_DESTDIR),$(R_DESTDIR),/)
clean:
rm -rf root dist ustreamer.egg-info

58
python/setup.py Normal file
View File

@ -0,0 +1,58 @@
import os
from setuptools import Extension
from setuptools import setup
# =====
def _find_sources() -> list[str]:
sources: list[str] = []
for (root_path, _, names) in os.walk("src"):
for name in names:
if name.endswith(".c"):
sources.append(os.path.join(root_path, name))
return sources
def _find_flags() -> dict[str, bool]:
return {
key[3:]: (value.strip().lower() in ["true", "on", "1"])
for (key, value) in sorted(os.environ.items())
if key.startswith("MK_WITH_")
}
def _make_d_features(flags: dict[str, bool]) -> str:
features = " ".join([
f"{key}={int(value)}"
for (key, value) in flags.items()
])
return f"-DUS_FEATURES=\"{features}\""
def main() -> None:
flags = _find_flags()
setup(
name="ustreamer",
version="6.55",
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",
_make_d_features(flags),
],
undef_macros=["NDEBUG"],
sources=_find_sources(),
),
],
)
if __name__ == "__main__":
main()

1
python/src/uslibs/const.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/const.h

1
python/src/uslibs/errors.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/errors.h

1
python/src/uslibs/frame.c Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/frame.c

1
python/src/uslibs/frame.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/frame.h

View File

@ -0,0 +1 @@
../../../src/libs/memsinksh.c

View File

@ -0,0 +1 @@
../../../src/libs/memsinksh.h

1
python/src/uslibs/tools.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/tools.h

1
python/src/uslibs/types.h Symbolic link
View File

@ -0,0 +1 @@
../../../src/libs/types.h

361
python/src/ustreamer.c Normal file
View File

@ -0,0 +1,361 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 <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/const.h"
#include "uslibs/types.h"
#include "uslibs/errors.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;
uz data_size;
int fd;
us_memsink_shared_s *mem;
u64 frame_id;
ldf frame_ts;
us_frame_s *frame;
} _MemsinkObject;
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
if (self->mem != NULL) {
us_memsink_shared_unmap(self->mem, self->data_size);
self->mem = NULL;
}
US_CLOSE_FD(self->fd);
US_DELETE(self->frame, us_frame_destroy);
}
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
self->fd = -1;
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(x_field, x_cond) { \
if (!(self->x_field x_cond)) { \
PyErr_SetString(PyExc_ValueError, #x_field " must be " #x_cond); \
return -1; \
} \
}
SET_DOUBLE(lock_timeout, > 0);
SET_DOUBLE(wait_timeout, > 0);
SET_DOUBLE(drop_same_frames, >= 0);
# undef SET_DOUBLE
if ((self->data_size = us_memsink_calculate_size(self->obj)) == 0) {
PyErr_SetString(PyExc_ValueError, "Invalid memsink object suffix");
return -1;
}
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, self->data_size)) == 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];
US_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 ldf deadline_ts = us_get_now_monotonic() + self->wait_timeout;
int locked = -1;
ldf now_ts;
do {
Py_BEGIN_ALLOW_THREADS
locked = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
now_ts = us_get_now_monotonic();
if (locked < 0) {
if (errno == EWOULDBLOCK) {
goto retry;
}
goto os_error;
}
us_memsink_shared_s *mem = self->mem;
if (mem->magic != US_MEMSINK_MAGIC || mem->version != US_MEMSINK_VERSION) {
goto retry;
}
// Let the sink know that the client is alive
mem->last_client_ts = now_ts;
if (mem->id == self->frame_id) {
goto retry;
}
if (self->drop_same_frames > 0) {
if (
US_FRAME_COMPARE_GEOMETRY(self->mem, self->frame)
&& (self->frame_ts + self->drop_same_frames > now_ts)
&& !memcmp(self->frame->data, us_memsink_get_data(mem), mem->used)
) {
self->frame_id = mem->id;
goto retry;
}
}
// New frame found
Py_BLOCK_THREADS
return 0;
os_error:
Py_BLOCK_THREADS
PyErr_SetFromErrno(PyExc_OSError);
return -1;
retry:
if (locked >= 0 && flock(self->fd, LOCK_UN) < 0) {
goto os_error;
}
if (usleep(1000) < 0) {
goto os_error;
}
Py_END_ALLOW_THREADS
if (PyErr_CheckSignals() < 0) {
return -1;
}
} while (now_ts < deadline_ts);
return US_ERROR_NO_DATA;
}
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 US_ERROR_NO_DATA: Py_RETURN_NONE;
default: return NULL;
}
us_memsink_shared_s *mem = self->mem;
us_frame_set_data(self->frame, us_memsink_get_data(mem), mem->used);
US_FRAME_COPY_META(self->mem, self->frame);
self->frame_id = mem->id;
self->frame_ts = us_get_now_monotonic();
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(x_key, x_maker) { \
PyObject *m_tmp = x_maker; \
if (m_tmp == NULL) { \
return NULL; \
} \
if (PyDict_SetItemString(dict_frame, x_key, m_tmp) < 0) { \
Py_DECREF(m_tmp); \
return NULL; \
} \
Py_DECREF(m_tmp); \
}
# define SET_NUMBER(x_key, x_from, x_to) \
SET_VALUE(#x_key, Py##x_to##_From##x_from(self->frame->x_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(gop, Long, Long);
SET_NUMBER(grab_begin_ts, Double, Float);
SET_NUMBER(grab_end_ts, Double, Float);
SET_NUMBER(encode_begin_ts, Double, Float);
SET_NUMBER(encode_end_ts, Double, Float);
SET_VALUE("data", PyBytes_FromStringAndSize((const char*)self->frame->data, self->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(x_field, x_from, x_to) \
static PyObject *_MemsinkObject_getter_##x_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
return Py##x_to##_From##x_from(self->x_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(x_name, x_method, x_flags) \
{.ml_name = x_name, .ml_meth = (PyCFunction)_MemsinkObject_##x_method, .ml_flags = (x_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(x_field) \
{.name = #x_field, .get = (getter)_MemsinkObject_getter_##x_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) {
PyObject *module = NULL;
if (PyType_Ready(&_MemsinkType) < 0) {
goto error;
}
if ((module = PyModule_Create(&_Module)) == NULL) {
goto error;
}
# define ADD(x_what, x_key, x_value) \
{ if (PyModule_Add##x_what(module, x_key, x_value) < 0) { goto error; } }
ADD(StringConstant, "__version__", US_VERSION);
ADD(StringConstant, "VERSION", US_VERSION);
ADD(IntConstant, "VERSION_MAJOR", US_VERSION_MAJOR);
ADD(IntConstant, "VERSION_MINOR", US_VERSION_MINOR);
ADD(StringConstant, "FEATURES", US_FEATURES); // Defined in setup.py
ADD(ObjectRef, "Memsink", (PyObject*)&_MemsinkType);
# undef ADD
return module;
error:
if (module != NULL) {
Py_DECREF(module);
}
return NULL;
}

142
src/Makefile Normal file
View File

@ -0,0 +1,142 @@
R_DESTDIR ?=
PREFIX ?= /usr/local
CC ?= gcc
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O3
LDFLAGS ?=
# =====
_USTR = ustreamer.bin
_DUMP = ustreamer-dump.bin
_V4P = ustreamer-v4p.bin
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
_USTR_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -levent -levent_pthreads
_DUMP_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt
_V4P_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt
_USTR_SRCS = $(shell ls \
libs/*.c \
ustreamer/*.c \
ustreamer/http/*.c \
ustreamer/data/*.c \
ustreamer/encoders/cpu/*.c \
ustreamer/encoders/hw/*.c \
ustreamer/*.c \
)
_DUMP_SRCS = $(shell ls \
libs/*.c \
dump/*.c \
)
_V4P_SRCS = $(shell ls \
libs/*.c \
libs/drm/*.c \
v4p/*.c \
)
_BUILD = build
_TARGETS = $(_USTR) $(_DUMP)
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
# =====
ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
override _USTR_LDFLAGS += -latomic
override _DUMP_LDFLAGS += -latomic
override _V4P_LDFLAGS += -latomic
endif
ifneq ($(MK_WITH_PYTHON),)
override _CFLAGS += -DMK_WITH_PYTHON
endif
ifneq ($(MK_WITH_JANUS),)
override _CFLAGS += -DMK_WITH_JANUS
endif
ifneq ($(MK_WITH_GPIO),)
override _CFLAGS += -DMK_WITH_GPIO -DWITH_GPIO $(shell $(PKG_CONFIG) --atleast-version=2 libgpiod 2> /dev/null && echo -DHAVE_GPIOD2)
override _USTR_LDFLAGS += -lgpiod
override _USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
endif
ifneq ($(MK_WITH_SYSTEMD),)
override _CFLAGS += -DMK_WITH_SYSTEMD -DWITH_SYSTEMD
override _USTR_LDFLAGS += -lsystemd
override _USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
endif
ifneq ($(MK_WITH_PTHREAD_NP),)
override _CFLAGS += -DMK_WITH_PTHREAD_NP -DWITH_PTHREAD_NP
endif
ifneq ($(MK_WITH_SETPROCTITLE),)
override _CFLAGS += -DMK_WITH_SETPROCTITLE -DWITH_SETPROCTITLE
ifneq ($(findstring linux,$(shell $(CC) -dumpmachine)),)
override _USTR_LDFLAGS += -lbsd
endif
endif
ifneq ($(MK_WITH_PDEATHSIG),)
override _CFLAGS += -DMK_WITH_PDEATHSIG -DWITH_PDEATHSIG
endif
ifneq ($(MK_WITH_V4P),)
override _TARGETS += $(_V4P)
override _OBJS += $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
override _CFLAGS += -DMK_WITH_V4P -DWITH_V4P $(shell $(PKG_CONFIG) --cflags libdrm)
override _V4P_LDFLAGS += $(shell $(PKG_CONFIG) --libs libdrm)
override _USTR_SRCS += $(shell ls libs/drm/*.c)
override _USTR_LDFLAGS += $(shell $(PKG_CONFIG) --libs libdrm)
endif
# =====
all: $(_TARGETS)
install: all
mkdir -p $(R_DESTDIR)$(PREFIX)/bin
for i in $(subst .bin,,$(_TARGETS)); do \
install -m755 $$i.bin $(R_DESTDIR)$(PREFIX)/bin/$$i; \
done
install-strip: install
for i in $(subst .bin,,$(_TARGETS)); do \
strip $(R_DESTDIR)$(PREFIX)/bin/$$i; \
done
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
$(ECHO) $(CC) $^ -o $@ $(_USTR_LDFLAGS)
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
$(ECHO) $(CC) $^ -o $@ $(_DUMP_LDFLAGS)
$(_V4P): $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
$(ECHO) $(CC) $^ -o $@ $(_V4P_LDFLAGS)
$(_BUILD)/%.o: %.c
$(info -- CC $<)
$(ECHO) mkdir -p $(dir $@) || true
$(ECHO) $(CC) $< -o $@ $(_CFLAGS)
clean:
rm -rf $(_USTR) $(_DUMP) $(_V4P) $(_BUILD)
-include $(_OBJS:%.o=%.d)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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
};

View File

@ -1,53 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>uStreamer</title>
</head>
<body>
<h3>&micro;Streamer v%VERSION%</h3>
<hr>
<ul>
<li>
<a href="/ping"><b><samp>/ping</samp></b></a><br>
Get JSON structure with state of the server.
</li>
<br>
<li>
<a href="/snapshot"><b><samp>/snapshot</samp></b></a><br>
Get a current actual image from the server.
</li>
<br>
<li>
<a href="/stream"><b><samp>/stream</samp></b></a><br>
Get a live stream. Query params:<br>
<br>
<ul>
<li>
<b><samp>extra_headers=1</samp></b><br>
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href="/snapshot"><samp>/snapshot</samp></a>).
</li>
<br>
<li>
<b><samp>advance_headers=1</samp></b><br>
Enable workaround for Chromium/Blink
<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=527446">Bug #527446</a>.
</li>
<br>
<li>
<b><samp>dual_final_frames=1</samp></b><br>
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<br>
Without this option, when the frame series is completed, WebKit-based browsers<br>
renders the last one with a delay.
</li>
</ul>
</li>
<br>
</ul>
<br>
<hr>
<a href="https://github.com/pi-kvm/ustreamer">Sources &amp; docs</a>
</body>
</html>

View File

@ -1,458 +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->soft_fps = 30;
dev->timeout = 1;
dev->error_delay = 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);
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;
int input = dev->input; // Needs pointer to int for ioctl()
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;
}
LOG_INFO("Using input channel: %d", input);
if (xioctl(dev->run->fd, VIDIOC_S_INPUT, &input) < 0) {
LOG_ERROR("Can't set input channel");
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_ERROR("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",
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;
}

View File

@ -1,94 +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/>. #
# #
*****************************************************************************/
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <signal.h>
#include <linux/videodev2.h>
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "UNKNOWN, PAL, NTSC, SECAM"
#define FORMAT_UNKNOWN -1
#define FORMATS_STR "YUYV, UYVY, RGB565"
struct hw_buffer_t {
void *start;
size_t length;
};
struct picture_t {
unsigned char *data;
unsigned long size;
unsigned long allocated;
long double grab_time;
long double encode_begin_time;
long double encode_end_time;
};
struct device_runtime_t {
int fd;
unsigned width;
unsigned height;
unsigned format;
unsigned n_buffers;
unsigned n_workers;
struct hw_buffer_t *hw_buffers;
struct picture_t *pictures;
unsigned long max_picture_size;
bool capturing;
};
struct device_t {
char *path;
unsigned input;
unsigned width;
unsigned height;
unsigned format;
v4l2_std_id standard;
bool dv_timings;
unsigned n_buffers;
unsigned n_workers;
unsigned soft_fps;
unsigned every_frame;
unsigned min_frame_size;
bool persistent;
unsigned timeout;
unsigned error_delay;
struct device_runtime_t *run;
sig_atomic_t volatile stop;
};
struct device_t *device_init();
void device_destroy(struct device_t *dev);
int device_parse_format(const char *const str);
v4l2_std_id device_parse_standard(const char *const str);
int device_open(struct device_t *dev);
void device_close(struct device_t *dev);

96
src/dump/file.c Normal file
View File

@ -0,0 +1,96 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 *out;
US_CALLOC(out, 1);
if (!strcmp(path, "-")) {
US_LOG_INFO("Using output: <stdout>");
out->fp = stdout;
} else {
US_LOG_INFO("Using output: %s", path);
if ((out->fp = fopen(path, "wb")) == NULL) {
US_LOG_PERROR("Can't open output file");
goto error;
}
}
out->json = json;
return out;
error:
us_output_file_destroy(out);
return NULL;
}
void us_output_file_write(void *v_out, const us_frame_s *frame) {
us_output_file_s *out = v_out;
if (out->json) {
us_base64_encode(frame->data, frame->used, &out->base64_data, &out->base64_allocated);
fprintf(
out->fp,
"{\"size\": %zu,"
" \"width\": %u,"
" \"height\": %u,"
" \"format\": %u,"
" \"stride\": %u,"
" \"online\": %u,"
" \"key\": %u,"
" \"gop\": %u,"
" \"grab_begin_ts\": %.3Lf,"
" \"grab_end_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->key,
frame->gop,
frame->grab_begin_ts,
frame->grab_end_ts,
frame->encode_begin_ts,
frame->encode_end_ts,
out->base64_data);
} else {
fwrite(frame->data, 1, frame->used, out->fp);
}
fflush(out->fp);
}
void us_output_file_destroy(void *v_out) {
us_output_file_s *out = v_out;
US_DELETE(out->base64_data, free);
if (out->fp && out->fp != stdout) {
if (fclose(out->fp) < 0) {
US_LOG_PERROR("Can't close output file");
}
}
free(out);
}

49
src/dump/file.h Normal file
View File

@ -0,0 +1,49 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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_out, const us_frame_s *frame);
void us_output_file_destroy(void *v_out);

332
src/dump/main.c Normal file
View File

@ -0,0 +1,332 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 <limits.h>
#include <float.h>
#include <getopt.h>
#include <errno.h>
#include "../libs/const.h"
#include "../libs/errors.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/fpsi.h"
#include "../libs/signal.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_REQUIRED = '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-required", no_argument, NULL, _O_KEY_REQUIRED},
{"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_out;
void (*write)(void *v_out, const us_frame_s *frame);
void (*destroy)(void *v_out);
} _output_context_s;
static void _signal_handler(int signum);
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");
const char *sink_name = NULL;
unsigned sink_timeout = 1;
const char *out_path = NULL;
bool out_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(out_path, optarg);
case _O_OUTPUT_JSON: OPT_SET(out_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_REQUIRED: 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 (out_path && out_path[0] != '\0') {
if ((ctx.v_out = (void*)us_output_file_init(out_path, out_json)) == NULL) {
return 1;
}
ctx.write = us_output_file_write;
ctx.destroy = us_output_file_destroy;
}
us_install_signals_handler(_signal_handler, false);
const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, key_required, &ctx));
if (ctx.v_out && ctx.destroy) {
ctx.destroy(ctx.v_out);
}
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 int _dump_sink(
const char *sink_name,
unsigned sink_timeout,
long long count,
long double interval,
bool key_required,
_output_context_s *ctx
) {
int retval = -1;
if (count == 0) {
count = -1;
}
const useconds_t interval_us = interval * 1000000;
us_frame_s *frame = us_frame_init();
us_fpsi_s *fpsi = us_fpsi_init("SINK", false);
us_memsink_s *sink = NULL;
if ((sink = us_memsink_init_opened("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
goto error;
}
long double last_ts = 0;
while (!_g_stop) {
bool key_requested;
const int got = us_memsink_client_get(sink, frame, &key_requested, key_required);
if (got == 0) {
key_required = false;
const long double now = us_get_now_monotonic();
char fourcc_str[8];
US_LOG_VERBOSE("%s %.3Lf - %s %ux%u - gop=%u, key=%u, kr=%u - GRAB=%.3Lf ~~%.3Lf~~ ENC=%.3Lf ~~> LAT=%.3Lf - size=%zu",
(frame->online ? " ON" : "OFF"),
(last_ts ? now - last_ts : 0),
us_fourcc_to_string(frame->format, fourcc_str, 8),
frame->width,
frame->height,
frame->gop,
frame->key,
key_requested,
frame->grab_end_ts - frame->grab_begin_ts,
frame->encode_begin_ts - frame->grab_end_ts,
frame->encode_end_ts - frame->encode_begin_ts,
now - frame->grab_begin_ts,
frame->used);
last_ts = now;
us_fpsi_update(fpsi, true, NULL);
if (ctx->v_out != NULL) {
ctx->write(ctx->v_out, frame);
}
if (count >= 0) {
--count;
if (count <= 0) {
break;
}
}
if (interval_us > 0) {
usleep(interval_us);
}
} else if (got == US_ERROR_NO_DATA) {
usleep(1000);
} else {
goto error;
}
}
retval = 0;
error:
US_DELETE(sink, us_memsink_destroy);
us_fpsi_destroy(fpsi);
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-2024 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-required ─────── 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
}

View File

@ -1,181 +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;
}
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic push
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
#pragma GCC diagnostic pop
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 (dev->n_workers > OMX_MAX_ENCODERS) {
LOG_INFO(
"OMX-based encoder can only work with %u worker threads; forced --workers=%u",
OMX_MAX_ENCODERS, OMX_MAX_ENCODERS
);
dev->n_workers = OMX_MAX_ENCODERS;
}
encoder->n_omxs = dev->n_workers;
A_CALLOC(encoder->omxs, encoder->n_omxs);
for (unsigned index = 0; index < encoder->n_omxs; ++index) {
if ((encoder->omxs[index] = 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->omxs) {
for (unsigned index = 0; index < encoder->n_omxs; ++index) {
if (encoder->omxs[index]) {
omx_encoder_destroy(encoder->omxs[index]);
}
}
free(encoder->omxs);
}
# 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_live(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) {
for (unsigned index = 0; index < encoder->n_omxs; ++index) {
if (omx_encoder_prepare_live(encoder->omxs[index], dev, encoder->quality, encoder->omx_use_ijg) < 0) {
goto use_fallback;
}
}
}
# 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;
# pragma GCC diagnostic pop
}
#pragma GCC diagnostic ignored "-Wunused-label"
#pragma GCC diagnostic push
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev,
const unsigned worker_number, const unsigned buf_index) {
#pragma GCC diagnostic pop
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
dev->run->pictures[buf_index].encode_begin_time = get_now_monotonic();
if (encoder->type == ENCODER_TYPE_CPU) {
jpeg_encoder_compress_buffer(dev, buf_index, encoder->quality);
}
# ifdef OMX_ENCODER
else if (encoder->type == ENCODER_TYPE_OMX) {
if (omx_encoder_compress_buffer(encoder->omxs[worker_number], dev, buf_index) < 0) {
goto error;
}
}
# endif
dev->run->pictures[buf_index].encode_end_time = get_now_monotonic();
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;
return -1;
# pragma GCC diagnostic pop
}

View File

@ -1,682 +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 <strings.h>
#include <assert.h>
#include <event2/event.h>
#include <event2/thread.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/keyvalq_struct.h>
#include <uuid/uuid.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/index_html.h"
#include "data/blank_jpeg.h"
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
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 stream_updated, const bool picture_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;
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 = "127.0.0.1";
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 *)server));
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
return server;
}
void http_server_destroy(struct http_server_t *server) {
if (server->run->refresh) {
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) {
struct timeval refresh_interval;
refresh_interval.tv_sec = 0;
refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->soft_fps * 2);
assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
assert(!event_add(server->run->refresh, &refresh_interval));
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);
}
static bool _http_get_param_true(struct evkeyvalq *params, const char *key) {
const char *value_str;
if ((value_str = evhttp_find_header(params, key)) != NULL) {
if (!strcasecmp(value_str, "true") || !strcasecmp(value_str, "yes") || value_str[0] == '1') {
return true;
}
}
return false;
}
#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, HTML_INDEX_PAGE));
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,
"{\"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
" \"online\": %s, \"quality\": %u, \"soft_fps\": %u, \"captured_fps\": %u},"
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
(server->fake_width ? server->fake_width : server->run->exposed->width),
(server->fake_height ? server->fake_height : server->run->exposed->height),
bool_to_string(server->run->exposed->online),
server->run->stream->encoder->quality,
server->run->stream->dev->soft_fps,
server->run->exposed->captured_fps,
server->run->exposed->queued_fps,
server->run->stream_clients_count
));
for (struct stream_client_t * client = server->run->stream_clients; client != NULL; client = client->next) {
assert(evbuffer_add_printf(buf,
"\"%s\": {\"fps\": %u, \"advance_headers\": %s, \"dual_final_frames\": %s}%s",
client->id,
client->fps,
bool_to_string(client->advance_headers),
bool_to_string(client->dual_final_frames),
(client->next ? ", " : "")
));
}
assert(evbuffer_add_printf(buf, "}}}"));
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_server) {
struct http_server_t *server = (struct http_server_t *)v_server;
struct evbuffer *buf;
char header_buf[64];
PROCESS_HEAD_REQUEST;
# define EXPOSED(_next) server->run->exposed->_next
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");
# define ADD_TIME_HEADER(_key, _value) \
{ sprintf(header_buf, "%.06Lf", _value); ADD_HEADER(_key, header_buf); }
# define ADD_UNSIGNED_HEADER(_key, _value) \
{ sprintf(header_buf, "%u", _value); ADD_HEADER(_key, header_buf); }
ADD_TIME_HEADER("X-Timestamp", get_now_real());
ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online)));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(width));
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(height));
ADD_TIME_HEADER("X-UStreamer-Grab-Time", EXPOSED(picture.grab_time));
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Time", EXPOSED(picture.encode_begin_time));
ADD_TIME_HEADER("X-UStreamer-Encode-End-Time", EXPOSED(picture.encode_end_time));
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Time", EXPOSED(expose_begin_time));
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Time", EXPOSED(expose_cmp_time));
ADD_TIME_HEADER("X-UStreamer-Expose-End-Time", EXPOSED(expose_end_time));
ADD_TIME_HEADER("X-UStreamer-Send-Time", get_now_monotonic());
# undef ADD_TIME_HEADER
# undef ADD_UNSUGNED_HEADER
ADD_HEADER("Content-Type", "image/jpeg");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
# undef EXPOSED
}
#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 evkeyvalq params;
struct bufferevent *buf_event;
struct stream_client_t *client;
char *client_addr;
unsigned short client_port;
uuid_t uuid;
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;
evhttp_parse_query(evhttp_request_get_uri(request), &params);
client->extra_headers = _http_get_param_true(&params, "extra_headers");
client->advance_headers = _http_get_param_true(&params, "advance_headers");
client->dual_final_frames = _http_get_param_true(&params, "dual_final_frames");
evhttp_clear_headers(&params);
uuid_generate(uuid);
uuid_unparse_lower(uuid, client->id);
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; id=%s;"
" advance_headers=%s; dual_final_frames=%s; clients now: %u",
client_addr,
client_port,
client->id,
bool_to_string(client->advance_headers),
bool_to_string(client->dual_final_frames),
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
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
# define BOUNDARY "boundarydonotcross"
# define RN "\r\n"
struct stream_client_t *client = (struct stream_client_t *)v_client;
struct evbuffer *buf;
long double now = get_now_monotonic();
long long now_second = floor_ms(now);
if (now_second != client->fps_accum_second) {
client->fps = client->fps_accum;
client->fps_accum = 0;
client->fps_accum_second = now_second;
}
client->fps_accum += 1;
assert((buf = evbuffer_new()));
// В хроме и его производных есть фундаментальный баг: он отрисовывает
// фрейм с задержкой на один, как только ему придут заголовки следующего.
// В сочетании с drop_same_frames это дает значительный лаг стрима
// при большом количестве дропов (на статичном изображении, где внезапно
// что-то изменилось.
//
// https://bugs.chromium.org/p/chromium/issues/detail?id=527446
//
// Включение advance_headers заставляет стример отсылать заголовки
// будущего фрейма сразу после данных текущего, чтобы триггернуть отрисовку.
// Естественным следствием этого является невозможность установки заголовка
// Content-Length, так как предсказывать будущее мы еще не научились.
// Его наличие не требуется RFC, однако никаких стандартов на MJPG over HTTP
// в природе не существует, и никто не может гарантировать, что отсутствие
// Content-Length не сломает вещание для каких-нибудь маргинальных браузеров.
//
// Кроме того, advance_headers форсит отключение заголовков X-UStreamer-*
// по тем же причинам, по которым у нас нет Content-Length.
# define ADD_ADVANCE_HEADERS \
{ assert(evbuffer_add_printf(buf, \
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real())); }
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
"Set-Cookie: stream_client_id=%s; path=/; max-age=30" RN
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
RN
"--" BOUNDARY RN,
client->id
));
if (client->advance_headers) {
ADD_ADVANCE_HEADERS;
}
assert(!bufferevent_write_buffer(buf_event, buf));
client->need_initial = false;
}
# define EXPOSED(_next) client->server->run->exposed->_next
if (!client->advance_headers) {
assert(evbuffer_add_printf(buf,
"Content-Type: image/jpeg" RN
"Content-Length: %lu" RN
"X-Timestamp: %.06Lf" RN
"%s",
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data)),
get_now_real(), (client->extra_headers ? "" : RN)
));
if (client->extra_headers) {
assert(evbuffer_add_printf(buf,
"X-UStreamer-Online: %s" RN
"X-UStreamer-Width: %u" RN
"X-UStreamer-Height: %u" RN
"X-UStreamer-Client-FPS: %u" RN
"X-UStreamer-Grab-Time: %.06Lf" RN
"X-UStreamer-Encode-Begin-Time: %.06Lf" RN
"X-UStreamer-Encode-End-Time: %.06Lf" RN
"X-UStreamer-Expose-Begin-Time: %.06Lf" RN
"X-UStreamer-Expose-Cmp-Time: %.06Lf" RN
"X-UStreamer-Expose-End-Time: %.06Lf" RN
"X-UStreamer-Send-Time: %.06Lf" RN
RN,
bool_to_string(EXPOSED(online)),
EXPOSED(width),
EXPOSED(height),
client->fps,
EXPOSED(picture.grab_time),
EXPOSED(picture.encode_begin_time),
EXPOSED(picture.encode_end_time),
EXPOSED(expose_begin_time),
EXPOSED(expose_cmp_time),
EXPOSED(expose_end_time),
now
));
}
}
assert(!evbuffer_add(buf,
(void *)EXPOSED(picture.data),
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data))
));
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
if (client->advance_headers) {
ADD_ADVANCE_HEADERS;
}
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
# undef ADD_ADVANCE_HEADERS
# undef EXPOSED
}
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 stream_updated, const bool picture_updated) {
struct evhttp_connection *conn;
struct bufferevent *buf_event;
long long now;
bool queued = false;
static unsigned queued_fps_accum = 0;
static long long queued_fps_second = 0;
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
conn = evhttp_request_get_connection(client->request);
if (conn != NULL) {
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
// и нужно послать два фрейма, чтобы серия была вовремя завершена.
// Это похоже на баг Blink (см. _http_callback_stream_write() и advance_headers),
// но фикс для него не лечит проблему вебкита. Такие дела.
bool dual_update = (
server->drop_same_frames
&& client->dual_final_frames
&& stream_updated
&& client->updated_prev
&& !picture_updated
);
if (dual_update || picture_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;
client->updated_prev = (picture_updated || client->need_first_frame); // Игнорировать dual
queued = true;
} else if (stream_updated) { // Для dual
client->updated_prev = false;
}
}
}
if (queued) {
if ((now = floor_ms(get_now_monotonic())) != queued_fps_second) {
server->run->exposed->queued_fps = queued_fps_accum;
queued_fps_accum = 0;
queued_fps_second = now;
}
queued_fps_accum += 1;
}
}
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 stream_updated = false;
bool picture_updated = false;
# 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 ...");
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
if (server->run->stream->picture.size > 0) { // If online
picture_updated = _expose_new_picture(server);
UNLOCK_STREAM;
} else {
UNLOCK_STREAM;
picture_updated = _expose_blank_picture(server);
}
stream_updated = true;
} else if (!server->run->exposed->online) {
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
picture_updated = _expose_blank_picture(server);
stream_updated = true;
}
# undef UNLOCK_STREAM
_http_queue_send_stream(server, stream_updated, picture_updated);
}
static bool _expose_new_picture(struct http_server_t *server) {
# define STREAM(_next) server->run->stream->_next
# define EXPOSED(_next) server->run->exposed->_next
assert(STREAM(picture.size) > 0);
EXPOSED(captured_fps) = STREAM(captured_fps);
EXPOSED(expose_begin_time) = get_now_monotonic();
# define MEM_STREAM_TO_EXPOSED \
EXPOSED(picture.data), STREAM(picture.data), \
STREAM(picture.size) * sizeof(*STREAM(picture.data))
if (server->drop_same_frames) {
if (
EXPOSED(online)
&& EXPOSED(dropped) < server->drop_same_frames
&& EXPOSED(picture.size) == STREAM(picture.size)
&& !memcmp(MEM_STREAM_TO_EXPOSED)
) {
EXPOSED(expose_cmp_time) = get_now_monotonic();
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
LOG_VERBOSE(
"HTTP: dropped same frame number %u; comparsion time = %.06Lf",
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
);
EXPOSED(dropped) += 1;
return false; // Not updated
} else {
EXPOSED(expose_cmp_time) = get_now_monotonic();
LOG_VERBOSE(
"HTTP: passed same frame check (frames are differ); comparsion time = %.06Lf",
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
);
}
}
if (EXPOSED(picture.allocated) < STREAM(picture.allocated)) {
A_REALLOC(EXPOSED(picture.data), STREAM(picture.allocated));
EXPOSED(picture.allocated) = STREAM(picture.allocated);
}
memcpy(MEM_STREAM_TO_EXPOSED);
# undef MEM_STREAM_TO_EXPOSED
EXPOSED(picture.size) = STREAM(picture.size);
EXPOSED(picture.grab_time) = STREAM(picture.grab_time);
EXPOSED(picture.encode_begin_time) = STREAM(picture.encode_begin_time);
EXPOSED(picture.encode_end_time) = STREAM(picture.encode_end_time);
EXPOSED(width) = STREAM(width);
EXPOSED(height) = STREAM(height);
EXPOSED(online) = true;
EXPOSED(dropped) = 0;
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
EXPOSED(expose_end_time) = get_now_monotonic();
LOG_VERBOSE(
"HTTP: exposed new frame; full exposition time = %.06Lf",
EXPOSED(expose_end_time) - EXPOSED(expose_begin_time)
);
# undef STREAM
# undef EXPOSED
return true; // Updated
}
static bool _expose_blank_picture(struct http_server_t *server) {
# define EXPOSED(_next) server->run->exposed->_next
EXPOSED(expose_begin_time) = get_now_monotonic();
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
if (EXPOSED(online) || EXPOSED(picture.size) == 0) {
if (EXPOSED(picture.allocated) < BLANK_JPG_SIZE) {
A_REALLOC(EXPOSED(picture.data), BLANK_JPG_SIZE);
EXPOSED(picture.allocated) = BLANK_JPG_SIZE;
}
memcpy(
EXPOSED(picture.data), BLANK_JPG_DATA,
BLANK_JPG_SIZE * sizeof(*EXPOSED(picture.data))
);
EXPOSED(picture.size) = BLANK_JPG_SIZE;
EXPOSED(picture.grab_time) = 0;
EXPOSED(picture.encode_begin_time) = 0;
EXPOSED(picture.encode_end_time) = 0;
EXPOSED(width) = BLANK_JPG_WIDTH;
EXPOSED(height) = BLANK_JPG_HEIGHT;
EXPOSED(captured_fps) = 0;
EXPOSED(online) = false;
goto updated;
}
if (EXPOSED(dropped) < server->run->drop_same_frames_blank) {
LOG_PERF("HTTP: dropped same frame (BLANK) number %u", EXPOSED(dropped));
EXPOSED(dropped) += 1;
EXPOSED(expose_end_time) = get_now_monotonic();
return false; // Not updated
}
updated:
EXPOSED(dropped) = 0;
EXPOSED(expose_end_time) = get_now_monotonic();
return true; // Updated
# undef EXPOSED
}

View File

@ -1,92 +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 <stdbool.h>
#include <event2/event.h>
#include <event2/http.h>
#include "tools.h"
#include "stream.h"
struct stream_client_t {
struct http_server_t *server;
struct evhttp_request *request;
bool extra_headers;
bool advance_headers;
bool dual_final_frames;
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
bool need_initial;
bool need_first_frame;
bool updated_prev;
unsigned fps;
unsigned fps_accum;
long long fps_accum_second;
struct stream_client_t *prev;
struct stream_client_t *next;
};
struct exposed_t {
struct picture_t picture;
unsigned width;
unsigned height;
unsigned captured_fps;
unsigned queued_fps;
bool online;
unsigned dropped;
long double expose_begin_time;
long double expose_cmp_time;
long double expose_end_time;
};
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;
};
struct http_server_t *http_server_init(struct stream_t *stream);
void http_server_destroy(struct http_server_t *server);
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);

Some files were not shown because too many files have changed in this diff Show More