Compare commits

...

309 Commits
v5.50 ... v6.47

Author SHA1 Message Date
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 3e228c1fb8.
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
143 changed files with 8299 additions and 5520 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 5.50
current_version = 6.47
parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize =
{major}.{minor}

View File

@@ -66,7 +66,7 @@ jobs:
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/arm64,linux/amd64,linux/arm/v7
platforms: linux/arm64,linux/amd64,linux/arm/v6,linux/arm/v7
file: pkg/docker/Dockerfile.alpine
-
name: Push

5
.gitignore vendored
View File

@@ -3,11 +3,12 @@
/pkg/arch/pkg/
/pkg/arch/src/
/src/build/
/python/build/
/python/dist/
/python/ustreamer.egg-info/
/python/root/
/janus/build/
/ustreamer
/ustreamer-dump
/ustreamer-*
/config.mk
vgcore.*
*.sock

View File

@@ -1,48 +1,78 @@
-include config.mk
# =====
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
# =====
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifeq (__not_found__,$(shell which $(PKG_CONFIG) 2>/dev/null || echo "__not_found__"))
$(error "No $(PKG_CONFIG) found in $(PATH)")
endif
# =====
ifeq ($(V),)
ECHO = @
endif
# =====
all:
+ $(MAKE) apps
ifneq ($(call optbool,$(WITH_PYTHON)),)
ifneq ($(MK_WITH_PYTHON),)
+ $(MAKE) python
endif
ifneq ($(call optbool,$(WITH_JANUS)),)
ifneq ($(MK_WITH_JANUS),)
+ $(MAKE) janus
endif
apps:
$(MAKE) -C src
$(ECHO) ln -sf src/ustreamer.bin ustreamer
$(ECHO) ln -sf src/ustreamer-dump.bin ustreamer-dump
for i in src/*.bin; do \
test ! -x $$i || ln -sf $$i `basename $$i .bin`; \
done
python:
$(MAKE) -C python
$(ECHO) ln -sf python/build/lib.*/*.so .
$(ECHO) ln -sf python/root/usr/lib/python*/site-packages/*.so .
janus:
@@ -52,16 +82,16 @@ janus:
install: all
$(MAKE) -C src install
ifneq ($(call optbool,$(WITH_PYTHON)),)
ifneq ($(MK_WITH_PYTHON),)
$(MAKE) -C python install
endif
ifneq ($(call optbool,$(WITH_JANUS)),)
ifneq ($(MK_WITH_JANUS),)
$(MAKE) -C janus install
endif
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
mkdir -p $(R_DESTDIR)$(MANPREFIX)/man1
for man in $(shell ls man); do \
install -m644 man/$$man $(DESTDIR)$(MANPREFIX)/man1/$$man; \
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$$man; \
install -m644 man/$$man $(R_DESTDIR)$(MANPREFIX)/man1/$$man; \
gzip -f $(R_DESTDIR)$(MANPREFIX)/man1/$$man; \
done
@@ -70,9 +100,8 @@ install-strip: install
regen:
tools/$(MAKE)-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK
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
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:
@@ -119,7 +148,7 @@ clean-all: linters clean
clean:
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
rm -f ustreamer ustreamer-dump *.so
rm -f ustreamer ustreamer-* *.so
$(MAKE) -C src clean
$(MAKE) -C python clean
$(MAKE) -C janus clean

View File

@@ -11,7 +11,7 @@
|----------|---------------|-------------------|
| Multithreaded JPEG encoding | ✔ | ✘ |
| Hardware image encoding<br>on Raspberry Pi | ✔ | ✘ |
| Behavior when the device<br>is disconnected while streaming | ✔ Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ✘ Stops the streaming <sup>1</sup> |
| 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 | ✔ | ✘ |
@@ -23,7 +23,7 @@
| 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.
* ```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.
* ```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()```.
@@ -44,7 +44,7 @@ You need to download the µStreamer onto your system and build it from the sourc
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
### Preconditions
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
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`.
@@ -85,13 +85,13 @@ Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x4
:exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`.
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
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=m2m-image \ # Hardware encoding on V4L2 M2M driver
--workers=3 \ # Workers number
--persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected)
--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
```
@@ -209,7 +209,7 @@ v4l2 utilities provide the tools to manage USB webcam setting and information. S
-----
# License
Copyright (C) 2018-2023 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,7 +1,8 @@
DESTDIR ?=
R_DESTDIR ?=
PREFIX ?= /usr/local
CC ?= gcc
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O3
LDFLAGS ?=
@@ -9,21 +10,20 @@ LDFLAGS ?=
# =====
_PLUGIN = libjanus_ustreamer.so
_CFLAGS = -fPIC -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS)
_LDFLAGS = -shared -lm -pthread -lrt -ljansson -lopus -lasound -lspeexdsp $(shell pkg-config --libs glib-2.0) $(LDFLAGS)
_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
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
# =====
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD)
override _LDFLAGS += -latomic
endif
WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
ifneq ($(MK_WITH_PTHREAD_NP),)
override _CFLAGS += -DWITH_PTHREAD_NP
endif
@@ -42,8 +42,8 @@ $(_BUILD)/%.o: %.c
install: $(_PLUGIN)
mkdir -p $(DESTDIR)$(PREFIX)/lib/ustreamer/janus
install -m755 $(_PLUGIN) $(DESTDIR)$(PREFIX)/lib/ustreamer/janus/$(PLUGIN)
mkdir -p $(R_DESTDIR)$(PREFIX)/lib/ustreamer/janus
install -m755 $(_PLUGIN) $(R_DESTDIR)$(PREFIX)/lib/ustreamer/janus/$(PLUGIN)
clean:

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

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,28 +22,38 @@
#pragma once
#include <stdbool.h>
#include <stdatomic.h>
#include <assert.h>
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/unjpeg.h"
#include "m2m.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 {
us_memsink_s *sink;
bool key_requested;
us_frame_s *tmp_src;
us_frame_s *dest;
us_m2m_encoder_s *enc;
atomic_bool online;
} us_h264_stream_s;
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_h264_stream_s *us_h264_stream_init(us_memsink_s *sink, const char *path, unsigned bitrate, unsigned gop);
void us_h264_stream_destroy(us_h264_stream_s *h264);
void us_h264_stream_process(us_h264_stream_s *h264, const us_frame_s *frame, bool force_key);
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);

View File

@@ -1,255 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "audio.h"
#define _JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
#define _JLOG_PERROR_RES(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
#define _JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))
// A number of frames per 1 channel:
// - https://github.com/xiph/opus/blob/7b05f44/src/opus_demo.c#L368
#define _HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
#define _HZ_TO_BUF16(_hz) (_HZ_TO_FRAMES(_hz) * 2) // One stereo frame = (16bit L) + (16bit R)
#define _HZ_TO_BUF8(_hz) (_HZ_TO_BUF16(_hz) * sizeof(int16_t))
#define _MIN_PCM_HZ 8000
#define _MAX_PCM_HZ 192000
#define _MAX_BUF16 _HZ_TO_BUF16(_MAX_PCM_HZ)
#define _MAX_BUF8 _HZ_TO_BUF8(_MAX_PCM_HZ)
#define _ENCODER_INPUT_HZ 48000
typedef struct {
int16_t data[_MAX_BUF16];
} _pcm_buffer_s;
typedef struct {
uint8_t data[_MAX_BUF8]; // Worst case
size_t used;
uint64_t pts;
} _enc_buffer_s;
static void *_pcm_thread(void *v_audio);
static void *_encoder_thread(void *v_audio);
bool us_audio_probe(const char *name) {
snd_pcm_t *pcm;
int err;
US_JLOG_INFO("audio", "Probing PCM capture ...");
if ((err = snd_pcm_open(&pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
_JLOG_PERROR_ALSA(err, "audio", "Can't probe PCM capture");
return false;
}
snd_pcm_close(pcm);
US_JLOG_INFO("audio", "PCM capture is available");
return true;
}
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
us_audio_s *audio;
US_CALLOC(audio, 1);
audio->pcm_hz = pcm_hz;
audio->pcm_queue = us_queue_init(8);
audio->enc_queue = us_queue_init(8);
atomic_init(&audio->stop, false);
int err;
{
if ((err = snd_pcm_open(&audio->pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
audio->pcm = NULL;
_JLOG_PERROR_ALSA(err, "audio", "Can't open PCM capture");
goto error;
}
assert(!snd_pcm_hw_params_malloc(&audio->pcm_params));
# define SET_PARAM(_msg, _func, ...) { \
if ((err = _func(audio->pcm, audio->pcm_params, ##__VA_ARGS__)) < 0) { \
_JLOG_PERROR_ALSA(err, "audio", _msg); \
goto error; \
} \
}
SET_PARAM("Can't initialize PCM params", snd_pcm_hw_params_any);
SET_PARAM("Can't set PCM access type", snd_pcm_hw_params_set_access, SND_PCM_ACCESS_RW_INTERLEAVED);
SET_PARAM("Can't set PCM channels numbre", snd_pcm_hw_params_set_channels, 2);
SET_PARAM("Can't set PCM sampling format", snd_pcm_hw_params_set_format, SND_PCM_FORMAT_S16_LE);
SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &audio->pcm_hz, 0);
if (audio->pcm_hz < _MIN_PCM_HZ || audio->pcm_hz > _MAX_PCM_HZ) {
US_JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
audio->pcm_hz, _MIN_PCM_HZ, _MAX_PCM_HZ);
goto error;
}
audio->pcm_frames = _HZ_TO_FRAMES(audio->pcm_hz);
audio->pcm_size = _HZ_TO_BUF8(audio->pcm_hz);
SET_PARAM("Can't apply PCM params", snd_pcm_hw_params);
# undef SET_PARAM
}
if (audio->pcm_hz != _ENCODER_INPUT_HZ) {
audio->res = speex_resampler_init(2, audio->pcm_hz, _ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
if (err < 0) {
audio->res = NULL;
_JLOG_PERROR_RES(err, "audio", "Can't create resampler");
goto error;
}
}
{
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
audio->enc = opus_encoder_create(_ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err);
assert(err == 0);
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_BITRATE(48000)));
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)));
// OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c
}
US_JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz);
audio->tids_created = true;
US_THREAD_CREATE(audio->enc_tid, _encoder_thread, audio);
US_THREAD_CREATE(audio->pcm_tid, _pcm_thread, audio);
return audio;
error:
us_audio_destroy(audio);
return NULL;
}
void us_audio_destroy(us_audio_s *audio) {
if (audio->tids_created) {
atomic_store(&audio->stop, true);
US_THREAD_JOIN(audio->pcm_tid);
US_THREAD_JOIN(audio->enc_tid);
}
US_DELETE(audio->enc, opus_encoder_destroy);
US_DELETE(audio->res, speex_resampler_destroy);
US_DELETE(audio->pcm, snd_pcm_close);
US_DELETE(audio->pcm_params, snd_pcm_hw_params_free);
US_QUEUE_DELETE_WITH_ITEMS(audio->enc_queue, free);
US_QUEUE_DELETE_WITH_ITEMS(audio->pcm_queue, free);
if (audio->tids_created) {
US_JLOG_INFO("audio", "Pipeline closed");
}
free(audio);
}
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
if (atomic_load(&audio->stop)) {
return -1;
}
_enc_buffer_s *buf;
if (!us_queue_get(audio->enc_queue, (void **)&buf, 0.1)) {
if (*size < buf->used) {
free(buf);
return -3;
}
memcpy(data, buf->data, buf->used);
*size = buf->used;
*pts = buf->pts;
free(buf);
return 0;
}
return -2;
}
static void *_pcm_thread(void *v_audio) {
US_THREAD_RENAME("us_a_pcm");
us_audio_s *const audio = (us_audio_s *)v_audio;
uint8_t in[_MAX_BUF8];
while (!atomic_load(&audio->stop)) {
const int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
if (frames < 0) {
_JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames");
break;
} else if (frames < (int)audio->pcm_frames) {
US_JLOG_ERROR("audio", "Fatal: Too few PCM frames captured");
break;
}
if (us_queue_get_free(audio->pcm_queue)) {
_pcm_buffer_s *out;
US_CALLOC(out, 1);
memcpy(out->data, in, audio->pcm_size);
assert(!us_queue_put(audio->pcm_queue, out, 0));
} else {
US_JLOG_ERROR("audio", "PCM queue is full");
}
}
atomic_store(&audio->stop, true);
return NULL;
}
static void *_encoder_thread(void *v_audio) {
US_THREAD_RENAME("us_a_enc");
us_audio_s *const audio = (us_audio_s *)v_audio;
int16_t in_res[_MAX_BUF16];
while (!atomic_load(&audio->stop)) {
_pcm_buffer_s *in;
if (!us_queue_get(audio->pcm_queue, (void **)&in, 0.1)) {
int16_t *in_ptr;
if (audio->res != NULL) {
assert(audio->pcm_hz != _ENCODER_INPUT_HZ);
uint32_t in_count = audio->pcm_frames;
uint32_t out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count);
in_ptr = in_res;
} else {
assert(audio->pcm_hz == _ENCODER_INPUT_HZ);
in_ptr = in->data;
}
_enc_buffer_s *out;
US_CALLOC(out, 1);
const int size = opus_encode(audio->enc, in_ptr, _HZ_TO_FRAMES(_ENCODER_INPUT_HZ), out->data, US_ARRAY_LEN(out->data));
free(in);
if (size < 0) {
_JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
free(out);
break;
}
out->used = size;
out->pts = audio->pts;
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
audio->pts += _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
if (us_queue_put(audio->enc_queue, out, 0) != 0) {
US_JLOG_ERROR("audio", "OPUS encoder queue is full");
free(out);
}
}
}
atomic_store(&audio->stop, true);
return NULL;
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,10 +22,32 @@
#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 *_audio_thread(void *v_client);
static void *_common_thread(void *v_client, bool video);
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) {
@@ -34,29 +56,37 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
client->gw = gw;
client->session = session;
atomic_init(&client->transmit, false);
atomic_init(&client->transmit_audio, false);
atomic_init(&client->transmit_acap, false);
atomic_init(&client->transmit_aplay, false);
atomic_init(&client->video_orient, 0);
atomic_init(&client->stop, false);
client->video_queue = us_queue_init(2048);
US_RING_INIT_WITH_ITEMS(client->video_ring, 2048, us_rtp_init);
US_THREAD_CREATE(client->video_tid, _video_thread, client);
client->audio_queue = us_queue_init(64);
US_THREAD_CREATE(client->audio_tid, _audio_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_queue_put(client->video_queue, NULL, 0);
us_queue_put(client->audio_queue, NULL, 0);
US_THREAD_JOIN(client->video_tid);
US_QUEUE_DELETE_WITH_ITEMS(client->video_queue, us_rtp_destroy);
US_RING_DELETE_WITH_ITEMS(client->video_ring, us_rtp_destroy);
US_THREAD_JOIN(client->audio_tid);
US_QUEUE_DELETE_WITH_ITEMS(client->audio_queue, 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);
}
@@ -64,67 +94,177 @@ 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) {
if (
atomic_load(&client->transmit)
&& (rtp->video || atomic_load(&client->transmit_audio))
&& (rtp->video || atomic_load(&client->transmit_acap))
) {
us_rtp_s *const new = us_rtp_dup(rtp);
if (us_queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
US_JLOG_ERROR("client", "Session %p %s queue is full",
client->session, (new->video ? "video" : "audio"));
us_rtp_destroy(new);
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) {
return _common_thread(v_client, true);
US_THREAD_SETTLE("us_cx_vid");
return _video_or_acap_thread(v_client, true);
}
static void *_audio_thread(void *v_client) {
return _common_thread(v_client, false);
static void *_acap_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_ac");
return _video_or_acap_thread(v_client, false);
}
static void *_common_thread(void *v_client, bool video) {
us_janus_client_s *const client = (us_janus_client_s *)v_client;
us_queue_s *const queue = (video ? client->video_queue : client->audio_queue);
assert(queue != NULL); // Audio may be NULL
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)) {
us_rtp_s *rtp;
if (!us_queue_get(queue, (void **)&rtp, 0.1)) {
if (rtp == NULL) {
break;
}
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_audio))
) {
janus_plugin_rtp packet = {0};
packet.video = rtp->video;
packet.buffer = (char *)rtp->datagram;
packet.length = rtp->used;
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.
packet.mindex = (rtp->video ? 0 : 1);
.mindex = (rtp.video ? 0 : 1),
# endif
};
janus_plugin_rtp_extensions_reset(&packet.extensions);
janus_plugin_rtp_extensions_reset(&packet.extensions);
/*if (rtp->zero_playout_delay) {
// https://github.com/pikvm/pikvm/issues/784
packet.extensions.min_delay = 0;
packet.extensions.max_delay = 0;
} else {
packet.extensions.min_delay = 0;
// 10s - Chromium/WebRTC default
// 3s - Firefox default
packet.extensions.max_delay = 300; // == 3s, i.e. 10ms granularity
}*/
client->gw->relay_rtp(client->session, &packet);
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
}
us_rtp_destroy(rtp);
if (rtp.video) {
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;
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,37 +22,39 @@
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <pthread.h>
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/types.h"
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "logging.h"
#include "queue.h"
#include "rtp.h"
typedef struct us_janus_client_sx {
typedef struct {
janus_callbacks *gw;
janus_plugin_session *session;
atomic_bool transmit;
atomic_bool transmit_audio;
atomic_bool transmit_acap;
atomic_bool transmit_aplay;
atomic_uint video_orient;
pthread_t video_tid;
pthread_t audio_tid;
pthread_t acap_tid;
pthread_t aplay_tid;
atomic_bool stop;
us_queue_s *video_queue;
us_queue_s *audio_queue;
us_ring_s *video_ring;
us_ring_s *acap_ring;
US_LIST_STRUCT(struct us_janus_client_sx);
us_ring_s *aplay_enc_ring;
u16 aplay_seq_next;
us_ring_s *aplay_pcm_ring;
US_LIST_DECLARE;
} us_janus_client_s;
@@ -60,3 +62,4 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
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);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,8 +22,21 @@
#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);
@@ -44,34 +57,36 @@ us_config_s *us_config_init(const char *config_dir_path) {
}
janus_config_print(jcfg);
if (
(config->video_sink_name = _get_value(jcfg, "memsink", "object")) == NULL
&& (config->video_sink_name = _get_value(jcfg, "video", "sink")) == NULL
) {
US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
if ((config->video_sink_name = _get_value(jcfg, "video", "sink")) == NULL) {
US_JLOG_ERROR("config", "Missing config value: video.sink");
goto error;
}
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
US_JLOG_INFO("config", "Missing config value: audio.tc358743");
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_config_destroy(config);
config = NULL;
ok:
US_DELETE(jcfg, janus_config_destroy);
free(config_file_path);
return config;
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->audio_dev_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);
}
@@ -84,6 +99,20 @@ static char *_get_value(janus_config *jcfg, const char *section, const char *opt
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;

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,23 +22,18 @@
#pragma once
#include <stdlib.h>
#include <string.h>
#include <janus/config.h>
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "const.h"
#include "logging.h"
#include "uslibs/types.h"
typedef struct {
char *video_sink_name;
char *audio_dev_name;
char *acap_dev_name;
uint acap_hz;
char *tc358743_dev_path;
char *aplay_dev_name;
} us_config_s;

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -37,10 +37,7 @@
free(m_perror_str); \
}
#define US_ONCE(...) { \
const int m_reported = __LINE__; \
if (m_reported != once) { \
__VA_ARGS__; \
once = m_reported; \
} \
}
// 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))

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,13 +22,25 @@
#include "memsinkfd.h"
#include <unistd.h>
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id) {
const long double deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
long double now;
#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 = us_get_now_monotonic();
now_ts = us_get_now_monotonic();
if (result < 0 && errno != EWOULDBLOCK) {
US_JLOG_PERROR("video", "Can't lock memsink");
return -1;
@@ -42,13 +54,12 @@ int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id)
}
}
usleep(1000); // lock_polling
} while (now < deadline_ts);
return -2;
} while (now_ts < deadline_ts);
return US_ERROR_NO_DATA;
}
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required) {
us_frame_s *frame = us_frame_init();
us_frame_set_data(frame, mem->data, mem->used);
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();
@@ -56,18 +67,14 @@ us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *
mem->key_requested = true;
}
bool ok = true;
bool retval = 0;
if (frame->format != V4L2_PIX_FMT_H264) {
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
ok = false;
retval = -1;
}
if (flock(fd, LOCK_UN) < 0) {
US_JLOG_PERROR("video", "Can't unlock memsink");
ok = false;
retval = -1;
}
if (!ok) {
us_frame_destroy(frame);
frame = NULL;
}
return frame;
return retval;
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,18 +22,10 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include "uslibs/tools.h"
#include "uslibs/types.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
#include "logging.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id);
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required);
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);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -20,13 +20,12 @@
*****************************************************************************/
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/mman.h>
@@ -35,20 +34,25 @@
#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 "queue.h"
#include "client.h"
#include "audio.h"
#include "tc358743.h"
#include "au.h"
#include "acap.h"
#include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
@@ -56,12 +60,14 @@
#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_queue_s *_g_video_queue = NULL;
static us_ring_s *_g_video_ring = NULL;
static us_rtpv_s *_g_rtpv = NULL;
static us_rtpa_s *_g_rtpa = NULL;
@@ -69,31 +75,41 @@ static pthread_t _g_video_rtp_tid;
static atomic_bool _g_video_rtp_tid_created = false;
static pthread_t _g_video_sink_tid;
static atomic_bool _g_video_sink_tid_created = false;
static pthread_t _g_audio_tid;
static atomic_bool _g_audio_tid_created = false;
static pthread_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_audio_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_AUDIO US_MUTEX_LOCK(_g_audio_lock)
#define _UNLOCK_AUDIO US_MUTEX_UNLOCK(_g_audio_lock)
#define _LOCK_ACAP US_MUTEX_LOCK(_g_acap_lock)
#define _UNLOCK_ACAP US_MUTEX_UNLOCK(_g_acap_lock)
#define _LOCK_ALL { _LOCK_VIDEO; _LOCK_AUDIO; }
#define _UNLOCK_ALL { _UNLOCK_AUDIO; _UNLOCK_VIDEO; }
#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);
@@ -101,18 +117,18 @@ janus_plugin *create(void);
static void *_video_rtp_thread(void *arg) {
(void)arg;
US_THREAD_RENAME("us_video_rtp");
US_THREAD_SETTLE("us_p_rtpv");
atomic_store(&_g_video_rtp_tid_created, true);
while (!_STOP) {
us_frame_s *frame;
if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
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_frame_destroy(frame);
us_ring_consumer_release(_g_video_ring, ri);
}
}
return NULL;
@@ -120,11 +136,11 @@ static void *_video_rtp_thread(void *arg) {
static void *_video_sink_thread(void *arg) {
(void)arg;
US_THREAD_RENAME("us_video_sink");
US_THREAD_SETTLE("us_p_vsink");
atomic_store(&_g_video_sink_tid_created, true);
uint64_t frame_id = 0;
us_frame_s *drop = us_frame_init();
u64 frame_id = 0;
int once = 0;
while (!_STOP) {
@@ -137,12 +153,18 @@ static void *_video_sink_thread(void *arg) {
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)) == NULL) {
if ((mem = us_memsink_shared_map(fd, data_size)) == NULL) {
US_ONCE({ US_JLOG_PERROR("video", "Can't map memsink"); });
goto close_memsink;
}
@@ -151,44 +173,78 @@ static void *_video_sink_thread(void *arg) {
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
while (!_STOP && _HAS_WATCHERS) {
const int result = us_memsink_fd_wait_frame(fd, mem, frame_id);
if (result == 0) {
us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id, atomic_load(&_g_key_required));
if (frame == NULL) {
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 (frame->key) {
if (ri >= 0 && frame->key) {
atomic_store(&_g_key_required, false);
}
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
US_ONCE({ US_JLOG_PERROR("video", "Video queue is full"); });
us_frame_destroy(frame);
}
} else if (result == -1) {
} else if (waited != US_ERROR_NO_DATA) {
goto close_memsink;
}
}
close_memsink:
if (mem != NULL) {
US_JLOG_INFO("video", "Memsink closed");
us_memsink_shared_unmap(mem);
}
if (fd >= 0) {
close(fd);
}
sleep(1); // error_delay
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 void *_audio_thread(void *arg) {
(void)arg;
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;
}
US_THREAD_RENAME("us_audio");
atomic_store(&_g_audio_tid_created, true);
assert(_g_config->audio_dev_name != NULL);
assert(_g_config->tc358743_dev_path != NULL);
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;
@@ -198,48 +254,157 @@ static void *_audio_thread(void *arg) {
continue;
}
us_tc358743_info_s info = {0};
us_audio_s *audio = NULL;
uint hz = 0;
us_acap_s *acap = NULL;
if (us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0) {
goto close_audio;
if (!us_au_probe(_g_config->acap_dev_name)) {
US_ONCE({ US_JLOG_ERROR("acap", "No PCM capture device"); });
goto close_acap;
}
if (!info.has_audio) {
US_ONCE({ US_JLOG_INFO("audio", "No audio presented from the host"); });
goto close_audio;
if (_get_acap_hz(&hz) < 0) {
goto close_acap;
}
US_ONCE({ US_JLOG_INFO("audio", "Detected host audio"); });
if ((audio = us_audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
goto close_audio;
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 (
us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|| !info.has_audio
|| audio->pcm_hz != info.audio_hz
) {
goto close_audio;
if (_get_acap_hz(&hz) < 0 || acap->pcm_hz != hz) {
goto close_acap;
}
size_t size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
uint8_t data[size];
uint64_t pts;
const int result = us_audio_get_encoded(audio, data, &size, &pts);
uz size = US_RTP_DATAGRAM_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_AUDIO;
_LOCK_ACAP;
us_rtpa_wrap(_g_rtpa, data, size, pts);
_UNLOCK_AUDIO;
_UNLOCK_ACAP;
} else if (result == -1) {
goto close_audio;
goto close_acap;
}
}
close_audio:
US_DELETE(audio, us_audio_destroy);
sleep(1); // error_delay
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;
}
@@ -250,6 +415,14 @@ static void _relay_rtp_clients(const us_rtp_s *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
@@ -263,11 +436,16 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
}
_g_gw = gw;
_g_video_queue = us_queue_init(1024);
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->audio_dev_name != NULL && us_audio_probe(_g_config->audio_dev_name)) {
if (_g_config->acap_dev_name != NULL) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_audio_tid, _audio_thread, NULL);
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);
@@ -283,7 +461,8 @@ static void _plugin_destroy(void) {
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } }
JOIN(_g_video_sink_tid);
JOIN(_g_video_rtp_tid);
JOIN(_g_audio_tid);
JOIN(_g_acap_tid);
JOIN(_g_aplay_tid);
# undef JOIN
US_LIST_ITERATE(_g_clients, client, {
@@ -291,15 +470,13 @@ static void _plugin_destroy(void) {
us_janus_client_destroy(client);
});
US_QUEUE_DELETE_WITH_ITEMS(_g_video_queue, us_frame_destroy);
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);
}
#define _IF_DISABLED(...) { if (!_READY || _STOP) { __VA_ARGS__ } }
static void _plugin_create_session(janus_plugin_session *session, int *err) {
_IF_DISABLED({ *err = -1; return; });
_LOCK_ALL;
@@ -316,6 +493,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
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);
@@ -324,7 +502,8 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
found = true;
} else {
has_watchers = (has_watchers || atomic_load(&client->transmit));
has_listeners = (has_listeners || atomic_load(&client->transmit_audio));
has_listeners = (has_listeners || atomic_load(&client->transmit_acap));
has_speakers = (has_speakers || atomic_load(&client->transmit_aplay));
}
});
if (!found) {
@@ -333,6 +512,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
}
atomic_store(&_g_has_watchers, has_watchers);
atomic_store(&_g_has_listeners, has_listeners);
atomic_store(&_g_has_speakers, has_speakers);
_UNLOCK_ALL;
}
@@ -371,25 +551,19 @@ static void _set_transmit(janus_plugin_session *session, const char *msg, bool t
_UNLOCK_ALL;
}
#undef _IF_DISABLED
static void _plugin_setup_media(janus_plugin_session *session) { _set_transmit(session, "Unmuted", true); }
static void _plugin_hangup_media(janus_plugin_session *session) { _set_transmit(session, "Muted", false); }
static struct janus_plugin_result *_plugin_handle_message(
janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep) {
assert(transaction != NULL);
# define FREE_MSG_JSEP { \
US_DELETE(msg, json_decref); \
US_DELETE(jsep, json_decref); \
}
janus_plugin_result_type result_type = JANUS_PLUGIN_OK;
char *result_msg = NULL;
if (session == NULL || msg == NULL) {
free(transaction);
FREE_MSG_JSEP;
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, (msg ? "No session" : "No message"), NULL);
result_type = JANUS_PLUGIN_ERROR;
result_msg = (msg ? "No session" : "No message");
goto done;
}
# define PUSH_ERROR(x_error, x_reason) { \
@@ -398,20 +572,20 @@ static struct janus_plugin_result *_plugin_handle_message(
json_object_set_new(m_event, "ustreamer", json_string("event")); \
json_object_set_new(m_event, "error_code", json_integer(x_error)); \
json_object_set_new(m_event, "error", json_string(x_reason)); \
_g_gw->push_event(session, create(), transaction, m_event, NULL); \
_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 ok_wait;
goto done;
}
const char *const request_str = json_string_value(request);
if (request_str == NULL) {
PUSH_ERROR(400, "Request not a string");
goto ok_wait;
goto done;
}
// US_JLOG_INFO("main", "Message: %s", request_str);
@@ -421,10 +595,10 @@ static struct janus_plugin_result *_plugin_handle_message(
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_new(m_result, x_status, x_payload); \
json_object_set(m_result, x_status, x_payload); \
} \
json_object_set_new(m_event, "result", m_result); \
_g_gw->push_event(session, create(), transaction, m_event, x_jsep); \
_g_gw->push_event(session, create(), NULL, m_event, x_jsep); \
json_decref(m_event); \
}
@@ -435,13 +609,33 @@ static struct janus_plugin_result *_plugin_handle_message(
PUSH_STATUS("stopped", NULL, NULL);
} else if (!strcmp(request_str, "watch")) {
bool with_audio = false;
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 audio = json_object_get(params, "audio");
if (audio != NULL && json_is_boolean(audio)) {
with_audio = (_g_rtpa != NULL && json_boolean_value(audio));
{
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;
}
}
}
}
}
@@ -449,7 +643,7 @@ static struct janus_plugin_result *_plugin_handle_message(
{
char *sdp;
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
char *const audio_sdp = (with_audio ? us_rtpa_make_sdp(_g_rtpa) : us_strdup(""));
char *const audio_sdp = (with_acap ? us_rtpa_make_sdp(_g_rtpa, with_aplay) : us_strdup(""));
US_ASPRINTF(sdp,
"v=0" RN
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
@@ -478,18 +672,30 @@ static struct janus_plugin_result *_plugin_handle_message(
{
_LOCK_ALL;
bool has_listeners = false;
bool has_speakers = false;
US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
atomic_store(&client->transmit_audio, with_audio);
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_audio));
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")) {
json_t *const features = json_pack("{sb}", "audio", (_g_rtpa != NULL));
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);
@@ -501,19 +707,40 @@ static struct janus_plugin_result *_plugin_handle_message(
PUSH_ERROR(405, "Not implemented");
}
ok_wait:
FREE_MSG_JSEP;
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
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
# undef FREE_MSG_JSEP
}
static void _plugin_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet) {
(void)handle;
(void)packet;
if (packet->video && janus_rtcp_has_pli(packet->buffer, packet->length)) {
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);
}
@@ -554,6 +781,7 @@ janus_plugin *create(void) {
.get_author = _plugin_get_author,
.get_package = _plugin_get_package,
.incoming_rtp = _plugin_incoming_rtp,
.incoming_rtcp = _plugin_incoming_rtcp,
);
# pragma GCC diagnostic pop

View File

@@ -5,7 +5,7 @@
# This source file is partially based on this code: #
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -25,29 +25,30 @@
#include "rtp.h"
#include <stdlib.h>
us_rtp_s *us_rtp_init(unsigned payload, bool video) {
#include "uslibs/types.h"
#include "uslibs/tools.h"
us_rtp_s *us_rtp_init(void) {
us_rtp_s *rtp;
US_CALLOC(rtp, 1);
rtp->payload = payload;
rtp->video = video;
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
return rtp;
}
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp) {
us_rtp_s *new;
US_CALLOC(new, 1);
memcpy(new, rtp, sizeof(us_rtp_s));
return new;
}
void us_rtp_destroy(us_rtp_s *rtp) {
free(rtp);
}
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
uint32_t word0 = 0x80000000;
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;
}
@@ -55,7 +56,8 @@ void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
word0 |= rtp->seq;
++rtp->seq;
# define WRITE_BE_U32(_offset, _value) *((uint32_t *)(rtp->datagram + _offset)) = __builtin_bswap32(_value)
# 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);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,36 +22,37 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include "uslibs/tools.h"
#include "uslibs/types.h"
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
#define US_RTP_DATAGRAM_SIZE 1200
#define US_RTP_HEADER_SIZE 12
#define US_RTP_PAYLOAD_SIZE (US_RTP_DATAGRAM_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 {
unsigned payload;
bool video;
uint32_t ssrc;
uint payload;
bool video;
u32 ssrc;
uint16_t seq;
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
size_t used;
bool zero_playout_delay;
u16 seq;
u8 datagram[US_RTP_DATAGRAM_SIZE];
uz used;
bool zero_playout_delay;
} us_rtp_s;
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
us_rtp_s *us_rtp_init(unsigned payload, bool video);
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
us_rtp_s *us_rtp_init(void);
void us_rtp_destroy(us_rtp_s *rtp);
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked);
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);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,11 +22,18 @@
#include "rtpa.h"
#include <stdlib.h>
#include <inttypes.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
us_rtpa_s *rtpa;
US_CALLOC(rtpa, 1);
rtpa->rtp = us_rtp_init(111, false);
rtpa->rtp = us_rtp_init();
us_rtp_assign(rtpa->rtp, US_RTP_OPUS_PAYLOAD, false);
rtpa->callback = callback;
return rtpa;
}
@@ -36,27 +43,31 @@ void us_rtpa_destroy(us_rtpa_s *rtpa) {
free(rtpa);
}
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
# define PAYLOAD rtpa->rtp->payload
char *us_rtpa_make_sdp(us_rtpa_s *rtpa, bool mic) {
const uint pl = rtpa->rtp->payload;
char *sdp;
US_ASPRINTF(sdp,
"m=audio 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u OPUS/48000/2" RN
// "a=fmtp:%u useinbandfec=1" RN
"a=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=sendonly" RN,
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, // PAYLOAD,
rtpa->rtp->ssrc
"a=%s" RN,
pl, pl,
US_RTP_OPUS_HZ, US_RTP_OPUS_CH,
pl, pl, pl, pl,
rtpa->rtp->ssrc,
(mic ? "sendrecv" : "sendonly")
);
# undef PAYLOAD
return sdp;
}
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts) {
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpa->rtp, pts, false);
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,15 +22,7 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <sys/types.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/types.h"
#include "rtp.h"
@@ -44,5 +36,5 @@ typedef struct {
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
void us_rtpa_destroy(us_rtpa_s *rtpa);
char *us_rtpa_make_sdp(us_rtpa_s *rtpa);
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
char *us_rtpa_make_sdp(us_rtpa_s *rtpa, bool mic);
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts);

View File

@@ -5,7 +5,7 @@
# This source file is partially based on this code: #
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -25,16 +25,27 @@
#include "rtpv.h"
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
#include <linux/videodev2.h>
static ssize_t _find_annexb(const uint8_t *data, size_t size);
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/frame.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(96, true);
rtpv->rtp = us_rtp_init();
us_rtp_assign(rtpv->rtp, US_RTP_H264_PAYLOAD, true);
rtpv->callback = callback;
return rtpv;
}
@@ -45,9 +56,9 @@ void us_rtpv_destroy(us_rtpv_s *rtpv) {
}
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
# define PAYLOAD rtpv->rtp->payload
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
const uint pl = rtpv->rtp->payload;
char *sdp;
US_ASPRINTF(sdp,
"m=video 1 RTP/SAVPF %u" RN
@@ -58,15 +69,17 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
"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 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
"a=extmap:2 urn:3gpp:video-orientation" RN
"a=sendonly" RN,
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
PAYLOAD, PAYLOAD, PAYLOAD,
pl, pl, pl, pl,
pl, pl, pl,
rtpv->rtp->ssrc
);
return sdp;
# undef PAYLOAD
}
#define _PRE 3 // Annex B prefix length
@@ -79,20 +92,20 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_de
rtpv->rtp->zero_playout_delay = zero_playout_delay;
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
ssize_t last_offset = -_PRE;
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 size_t next_start = last_offset + _PRE;
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
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 uint8_t *const data = frame->data + last_offset + _PRE;
size_t size = offset - last_offset - _PRE;
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;
}
@@ -103,34 +116,33 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_de
}
if (last_offset >= 0) {
const uint8_t *const data = frame->data + last_offset + _PRE;
size_t size = frame->used - last_offset - _PRE;
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 uint8_t *data, size_t size, uint32_t pts, bool marked) {
# define DG rtpv->rtp->datagram
const unsigned ref_idc = (data[0] >> 5) & 3;
const unsigned type = data[0] & 0x1F;
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;
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpv->rtp, pts, marked);
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
memcpy(dg + US_RTP_HEADER_SIZE, data, size);
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
rtpv->callback(rtpv->rtp);
return;
}
const size_t fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
const uz fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
const uint8_t *src = data + 1;
ssize_t remaining = size - 1;
const u8 *src = data + 1;
sz remaining = size - 1;
bool first = true;
while (remaining > 0) {
ssize_t frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
sz frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
const bool last = (remaining <= frag_size);
if (last) {
frag_size = remaining;
@@ -138,18 +150,18 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint3
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
DG[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
dg[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
uint8_t fu = type;
u8 fu = type;
if (first) {
fu |= 0x80;
}
if (last) {
fu |= 0x40;
}
DG[US_RTP_HEADER_SIZE + 1] = fu;
dg[US_RTP_HEADER_SIZE + 1] = fu;
memcpy(DG + fu_overhead, src, frag_size);
memcpy(dg + fu_overhead, src, frag_size);
rtpv->rtp->used = fu_overhead + frag_size;
rtpv->callback(rtpv->rtp);
@@ -157,14 +169,12 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint3
remaining -= frag_size;
first = false;
}
# undef DG
}
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
static sz _find_annexb(const u8 *data, uz size) {
// Parses buffer for 00 00 01 start codes
if (size >= _PRE) {
for (size_t index = 0; index <= size - _PRE; ++index) {
for (uz index = 0; index <= size - _PRE; ++index) {
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return index;
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,16 +22,7 @@
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <assert.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include "uslibs/tools.h"
#include "uslibs/types.h"
#include "uslibs/frame.h"
#include "rtp.h"

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

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

View File

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

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

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

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

View File

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

View File

@@ -33,7 +33,7 @@ max-line-length = 160
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names = _, __, x, y, ws, make-html-h, make-jpeg-h, make-ico-h
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}$

View File

@@ -3,7 +3,7 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true
[testenv]
basepython = python3.11
basepython = python3.14
changedir = /src
[testenv:cppcheck]
@@ -13,19 +13,21 @@ commands = cppcheck \
--std=c17 \
--error-exitcode=1 \
--quiet \
--enable=warning,unusedFunction,portability,performance,style \
--check-level=exhaustive \
--enable=warning,portability,performance,style \
--suppress=assignmentInAssert \
--suppress=assertWithSideEffect \
--suppress=variableScope \
--inline-suppr \
--library=python \
--include=linters/cppcheck.h \
src python/*.? janus/*.?
src python/src/*.? janus/src/*.?
[testenv:flake8]
allowlist_externals = bash
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
deps =
flake8==5.0.4
flake8
flake8-quotes
[testenv:pylint]
@@ -33,6 +35,7 @@ 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

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer-dump.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER-DUMP 1 "version 5.50" "January 2021"
.TH USTREAMER-DUMP 1 "version 6.47" "January 2021"
.SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 5.50" "November 2020"
.TH USTREAMER 1 "version 6.47" "November 2020"
.SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network
@@ -17,7 +17,7 @@ Without arguments, \fBustreamer\fR will try to open \fB/dev/video0\fR with 640x4
Please note that since µStreamer v2\.0 cross\-domain requests were disabled by default for security reasons\. To enable the old behavior, use the option \fB\-\-allow\-origin=\e*\fR\.
For example, the recommended way of running µStreamer with Auvidea B101 on a Raspberry Pi is:
For example, the recommended way of running µStreamer with TC358743-based capture device on a Raspberry Pi is:
\fBustreamer \e\fR
.RS
@@ -27,7 +27,7 @@ For example, the recommended way of running µStreamer with Auvidea B101 on a Ra
.nf
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
.nf
\fB\-\-persistent \e\fR # Don\'t re\-initialize device on timeout (for example when HDMI cable was disconnected)
\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
@@ -52,7 +52,7 @@ Initial image resolution. Default: 640x480.
.TP
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
Image format.
Available: YUYV, YVYU, UYVY, RGB565, RGB24, JPEG; default: YUYV.
Available: YUYV, YVYU, UYVY, YUV420, YVU420, RGB565, RGB24, GREY, MJPEG, JPEG; default: YUYV.
.TP
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
Force TV standard.
@@ -68,8 +68,11 @@ Desired FPS. Default: maximum possible.
.BR \-z\ \fIN ", " \-\-min\-frame\-size\ \fIN
Drop frames smaller then this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
.TP
.BR \-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
Don't re\-initialize device on timeout. Default: disabled.
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.
@@ -96,17 +99,15 @@ HW ─ Use pre-encoded MJPEG frames directly from camera hardware.
M2M-VIDEO ─ GPU-accelerated MJPEG encoding.
M2M-IMAGE ─ GPU-accelerated JPEG encoding.
NOOP ─ Don't compress MJPEG stream (do nothing).
.TP
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
It doesn't do anything. Still here for compatibility.
.TP
.BR \-k\ \fIpath ", " \-\-blank\ \fIpath
Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.
It doesn't do anything. Still here for compatibility.
.TP
.BR \-K\ \fIsec ", " \-\-last\-as\-blank\ \fIsec
Show the last frame received from the camera after it was disconnected, but no more than specified time (or endlessly if 0 is specified). If the device has not yet been online, display 'NO SIGNAL' or the image specified by option \-\-blank. Note: currently this option has no effect on memory sinks. Default: disabled.
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.
@@ -212,25 +213,25 @@ Timeout for client connections. Default: 10.
.SS "JPEG sink options"
With shared memory sink you can write a stream to a file. See \fBustreamer-dump\fR(1) for more info.
.TP
.BR \-\-sink\ \fIname
Use the specified shared memory object to sink JPEG frames. Default: disabled.
.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 \-\-sink\-mode\ \fImode
.BR \-\-jpeg\-sink\-mode\ \fImode
Set JPEG sink permissions (like 777). Default: 660.
.TP
.BR \-\-sink\-rm
.BR \-\-jpeg\-sink\-rm
Remove shared memory on stop. Default: disabled.
.TP
.BR \-\-sink\-client\-ttl\ \fIsec
.BR \-\-jpeg\-sink\-client\-ttl\ \fIsec
Client TTL. Default: 10.
.TP
.BR \-\-sink\-timeout\ \fIsec
.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. Default: disabled.
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.
@@ -252,12 +253,34 @@ 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 \fBHAS_PDEATHSIG\fR feature. Default: disabled.
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).

View File

@@ -3,14 +3,14 @@
pkgname=ustreamer
pkgver=5.50
pkgver=6.47
pkgrel=1
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"
license=(GPL)
arch=(i686 x86_64 armv6h armv7h aarch64)
depends=(libjpeg libevent libbsd libgpiod systemd)
makedepends=(gcc make systemd)
makedepends=(gcc make pkgconf systemd)
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP)
@@ -18,8 +18,8 @@ md5sums=(SKIP)
_options="WITH_GPIO=1 WITH_SYSTEMD=1"
if [ -e /usr/bin/python3 ]; then
_options="$_options WITH_PYTHON=1"
depends+=(python)
makedepends+=(python-setuptools)
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)
@@ -28,12 +28,6 @@ if [ -e /usr/include/janus/plugins/plugin.h ];then
fi
# LD does not link mmal with this option
# This DOESN'T affect setup.py
LDFLAGS="${LDFLAGS//--as-needed/}"
export LDFLAGS="${LDFLAGS//,,/,}"
build() {
cd "$srcdir"
rm -rf $pkgname-build

View File

@@ -24,7 +24,7 @@ RUN apk add --no-cache \
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/src/ustreamer.bin ustreamer
RUN wget https://raw.githubusercontent.com/pikvm/kvmd/master/configs/kvmd/edid/v3-hdmi.hex -O /edid.hex
RUN wget https://raw.githubusercontent.com/pikvm/kvmd/master/configs/kvmd/edid/v2.hex -O /edid.hex
COPY pkg/docker/entry.sh /
EXPOSE 8080

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=5.50
PKG_VERSION:=6.47
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
@@ -25,7 +25,7 @@ define Package/ustreamer
SECTION:=multimedia
CATEGORY:=Multimedia
TITLE:=uStreamer
DEPENDS:=+libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
DEPENDS:=+libatomic +libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
URL:=https://github.com/pikvm/ustreamer
endef

2
python/MANIFEST.in Normal file
View File

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

View File

@@ -1,20 +1,21 @@
-include ../config.mk
DESTDIR ?=
R_DESTDIR ?=
PREFIX ?= /usr/local
PY ?= python3
# =====
all:
all: root
root: $(shell find src -type f,l) setup.py
$(info == PY_BUILD ustreamer-*.so)
$(ECHO) $(PY) setup.py build
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) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
$(PY) -m pip install dist/*.whl --ignore-installed --prefix=$(PREFIX) --root=$(if $(R_DESTDIR),$(R_DESTDIR),/)
clean:
rm -rf build ustreamer.egg-info
rm -rf root dist ustreamer.egg-info

View File

@@ -5,19 +5,36 @@ from setuptools import setup
# =====
def _find_sources(suffix: str) -> list[str]:
def _find_sources() -> list[str]:
sources: list[str] = []
for (root_path, _, names) in os.walk("src"):
for name in names:
if name.endswith(suffix):
if name.endswith(".c"):
sources.append(os.path.join(root_path, name))
return sources
if __name__ == "__main__":
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="5.50",
version="6.47",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",
@@ -26,10 +43,16 @@ if __name__ == "__main__":
Extension(
"ustreamer",
libraries=["rt", "m", "pthread"],
extra_compile_args=["-std=c17", "-D_GNU_SOURCE"],
extra_compile_args=[
"-std=c17", "-D_GNU_SOURCE",
_make_d_features(flags),
],
undef_macros=["NDEBUG"],
sources=_find_sources(".c"),
depends=_find_sources(".h"),
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

View File

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

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

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

View File

@@ -1,3 +1,25 @@
/*****************************************************************************
# #
# 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>
@@ -13,6 +35,9 @@
#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"
@@ -25,36 +50,29 @@ typedef struct {
double lock_timeout;
double wait_timeout;
double drop_same_frames;
uz data_size;
int fd;
us_memsink_shared_s *mem;
uint64_t frame_id;
long double frame_ts;
u64 frame_id;
ldf frame_ts;
us_frame_s *frame;
} _MemsinkObject;
#define _MEM(x_next) self->mem->x_next
#define _FRAME(x_next) self->frame->x_next
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
if (self->mem != NULL) {
us_memsink_shared_unmap(self->mem);
us_memsink_shared_unmap(self->mem, self->data_size);
self->mem = NULL;
}
if (self->fd >= 0) {
close(self->fd);
self->fd = -1;
}
if (self->frame != NULL) {
us_frame_destroy(self->frame);
self->frame = NULL;
}
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;
@@ -65,41 +83,42 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
return -1;
}
# define SET_DOUBLE(_field, _cond) { \
if (!(self->_field _cond)) { \
PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \
# 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)) == NULL) {
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;
error:
_MemsinkObject_destroy_internals(self);
return -1;
}
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
char repr[1024];
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
US_SNPRINTF(repr, 1023, "<Memsink(%s)>", self->obj);
return Py_BuildValue("s", repr);
}
@@ -115,70 +134,76 @@ static PyObject *_MemsinkObject_close(_MemsinkObject *self, PyObject *Py_UNUSED(
static PyObject *_MemsinkObject_enter(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
Py_INCREF(self);
return (PyObject *)self;
return (PyObject*)self;
}
static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
return PyObject_CallMethod((PyObject *)self, "close", "");
return PyObject_CallMethod((PyObject*)self, "close", "");
}
static int _wait_frame(_MemsinkObject *self) {
const long double deadline_ts = us_get_now_monotonic() + self->wait_timeout;
const ldf deadline_ts = us_get_now_monotonic() + self->wait_timeout;
# define RETURN_OS_ERROR { \
Py_BLOCK_THREADS \
PyErr_SetFromErrno(PyExc_OSError); \
return -1; \
}
long double now;
int locked = -1;
ldf now_ts;
do {
Py_BEGIN_ALLOW_THREADS
const int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
now = us_get_now_monotonic();
if (retval < 0 && errno != EWOULDBLOCK) {
RETURN_OS_ERROR;
} else if (retval == 0) {
if (_MEM(magic) == US_MEMSINK_MAGIC && _MEM(version) == US_MEMSINK_VERSION && _MEM(id) != self->frame_id) {
if (self->drop_same_frames > 0) {
if (
US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
&& (self->frame_ts + self->drop_same_frames > now)
&& !memcmp(_FRAME(data), _MEM(data), _MEM(used))
) {
self->frame_id = _MEM(id);
goto drop;
}
}
Py_BLOCK_THREADS
return 0;
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;
}
if (flock(self->fd, LOCK_UN) < 0) {
RETURN_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;
}
}
drop:
// 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) {
RETURN_OS_ERROR;
goto os_error;
}
Py_END_ALLOW_THREADS
if (PyErr_CheckSignals() < 0) {
return -1;
}
} while (now < deadline_ts);
} while (now_ts < deadline_ts);
# undef RETURN_OS_ERROR
return -2;
return US_ERROR_NO_DATA;
}
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
@@ -195,17 +220,17 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
switch (_wait_frame(self)) {
case 0: break;
case -2: Py_RETURN_NONE;
case US_ERROR_NO_DATA: Py_RETURN_NONE;
default: return NULL;
}
us_frame_set_data(self->frame, _MEM(data), _MEM(used));
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_id = mem->id;
self->frame_ts = us_get_now_monotonic();
_MEM(last_client_ts) = self->frame_ts;
if (key_required) {
_MEM(key_requested) = true;
mem->key_requested = true;
}
if (flock(self->fd, LOCK_UN) < 0) {
@@ -217,18 +242,19 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
return NULL;
}
# define SET_VALUE(_key, _maker) { \
PyObject *_tmp = _maker; \
if (_tmp == NULL) { \
# define SET_VALUE(x_key, x_maker) { \
PyObject *m_tmp = x_maker; \
if (m_tmp == NULL) { \
return NULL; \
} \
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
Py_DECREF(_tmp); \
if (PyDict_SetItemString(dict_frame, x_key, m_tmp) < 0) { \
Py_DECREF(m_tmp); \
return NULL; \
} \
Py_DECREF(_tmp); \
Py_DECREF(m_tmp); \
}
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(_FRAME(_key)))
# 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);
@@ -237,10 +263,11 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
SET_NUMBER(online, Long, Bool);
SET_NUMBER(key, Long, Bool);
SET_NUMBER(gop, Long, Long);
SET_NUMBER(grab_ts, Double, Float);
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 *)_FRAME(data), _FRAME(used)));
SET_VALUE("data", PyBytes_FromStringAndSize((const char*)self->frame->data, self->frame->used));
# undef SET_NUMBER
# undef SET_VALUE
@@ -252,21 +279,19 @@ static PyObject *_MemsinkObject_is_opened(_MemsinkObject *self, PyObject *Py_UNU
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
}
#define FIELD_GETTER(_field, _from, _to) \
static PyObject *_MemsinkObject_getter_##_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
return Py##_to##_From##_from(self->_field); \
#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(_name, _method, _flags) \
{.ml_name = _name, .ml_meth = (PyCFunction)_MemsinkObject_##_method, .ml_flags = (_flags)}
# 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),
@@ -277,7 +302,8 @@ static PyMethodDef _MemsinkObject_methods[] = {
};
static PyGetSetDef _MemsinkObject_getsets[] = {
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)_MemsinkObject_getter_##_field}
# 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),
@@ -305,21 +331,31 @@ static PyModuleDef _Module = {
.m_size = -1,
};
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
PyObject *module = PyModule_Create(&_Module);
if (module == NULL) {
return NULL;
}
PyMODINIT_FUNC PyInit_ustreamer(void) {
PyObject *module = NULL;
if (PyType_Ready(&_MemsinkType) < 0) {
return NULL;
goto error;
}
Py_INCREF(&_MemsinkType);
if (PyModule_AddObject(module, "Memsink", (PyObject *)&_MemsinkType) < 0) {
return NULL;
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;
}

View File

@@ -1,7 +1,8 @@
DESTDIR ?=
R_DESTDIR ?=
PREFIX ?= /usr/local
CC ?= gcc
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O3
LDFLAGS ?=
@@ -9,13 +10,14 @@ LDFLAGS ?=
# =====
_USTR = ustreamer.bin
_DUMP = ustreamer-dump.bin
_V4P = ustreamer-v4p.bin
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
_LDFLAGS = $(LDFLAGS)
_COMMON_LIBS = -lm -ljpeg -pthread -lrt
_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_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads
_USTR_SRCS = $(shell ls \
libs/*.c \
ustreamer/*.c \
@@ -26,72 +28,105 @@ _USTR_SRCS = $(shell ls \
ustreamer/*.c \
)
_DUMP_LIBS = $(_COMMON_LIBS)
_DUMP_SRCS = $(shell ls \
libs/*.c \
dump/*.c \
)
_V4P_SRCS = $(shell ls \
libs/*.c \
libs/drm/*.c \
v4p/*.c \
)
_BUILD = build
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
_TARGETS = $(_USTR) $(_DUMP)
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
ifneq ($(call optbool,$(WITH_GPIO)),)
_USTR_LIBS += -lgpiod
override _CFLAGS += -DWITH_GPIO $(shell pkg-config --atleast-version=2 libgpiod 2> /dev/null && echo -DHAVE_GPIOD2)
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
# =====
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD)
override _USTR_LDFLAGS += -latomic
override _DUMP_LDFLAGS += -latomic
override _V4P_LDFLAGS += -latomic
endif
ifneq ($(call optbool,$(WITH_SYSTEMD)),)
_USTR_LIBS += -lsystemd
override _CFLAGS += -DWITH_SYSTEMD
_USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
ifneq ($(MK_WITH_PYTHON),)
override _CFLAGS += -DMK_WITH_PYTHON
endif
WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override _CFLAGS += -DWITH_PTHREAD_NP
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
WITH_SETPROCTITLE ?= 1
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
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
ifeq ($(shell uname -s | tr A-Z a-z),linux)
_USTR_LIBS += -lbsd
override _USTR_LDFLAGS += -lbsd
endif
override _CFLAGS += -DWITH_SETPROCTITLE
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: $(_USTR) $(_DUMP)
all: $(_TARGETS)
install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin
install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
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
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
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 $@ $(_LDFLAGS) $(_USTR_LIBS)
$(ECHO) $(CC) $^ -o $@ $(_USTR_LDFLAGS)
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
$(ECHO) $(CC) $^ -o $@ $(_DUMP_LDFLAGS)
$(_V4P): $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
$(ECHO) $(CC) $^ -o $@ $(_V4P_LDFLAGS)
$(_BUILD)/%.o: %.c
@@ -101,8 +136,7 @@ $(_BUILD)/%.o: %.c
clean:
rm -rf $(_USTR) $(_DUMP) $(_BUILD)
rm -rf $(_USTR) $(_DUMP) $(_V4P) $(_BUILD)
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
-include $(_OBJS:%.o=%.d)

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -24,54 +24,73 @@
us_output_file_s *us_output_file_init(const char *path, bool json) {
us_output_file_s *output;
US_CALLOC(output, 1);
us_output_file_s *out;
US_CALLOC(out, 1);
if (!strcmp(path, "-")) {
US_LOG_INFO("Using output: <stdout>");
output->fp = stdout;
out->fp = stdout;
} else {
US_LOG_INFO("Using output: %s", path);
if ((output->fp = fopen(path, "wb")) == NULL) {
if ((out->fp = fopen(path, "wb")) == NULL) {
US_LOG_PERROR("Can't open output file");
goto error;
}
}
output->json = json;
return output;
out->json = json;
return out;
error:
us_output_file_destroy(output);
us_output_file_destroy(out);
return NULL;
}
void us_output_file_write(void *v_output, const us_frame_s *frame) {
us_output_file_s *output = (us_output_file_s *)v_output;
if (output->json) {
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
fprintf(output->fp,
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
" \"format\": %u, \"stride\": %u, \"online\": %u, \"key\": %u, \"gop\": %u,"
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
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_ts, frame->encode_begin_ts, frame->encode_end_ts,
output->base64_data);
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, output->fp);
fwrite(frame->data, 1, frame->used, out->fp);
}
fflush(output->fp);
fflush(out->fp);
}
void us_output_file_destroy(void *v_output) {
us_output_file_s *output = (us_output_file_s *)v_output;
US_DELETE(output->base64_data, free);
if (output->fp && output->fp != stdout) {
if (fclose(output->fp) < 0) {
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(output);
free(out);
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -45,5 +45,5 @@ typedef struct {
us_output_file_s *us_output_file_init(const char *path, bool json);
void us_output_file_write(void *v_output, const us_frame_s *frame);
void us_output_file_destroy(void *v_output);
void us_output_file_write(void *v_out, const us_frame_s *frame);
void us_output_file_destroy(void *v_out);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -24,7 +24,6 @@
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <signal.h>
#include <limits.h>
#include <float.h>
#include <getopt.h>
@@ -32,10 +31,13 @@
#include <assert.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"
@@ -88,18 +90,19 @@ volatile bool _g_stop = false;
typedef struct {
void *v_output;
void (*write)(void *v_output, const us_frame_s *frame);
void (*destroy)(void *v_output);
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 void _install_signal_handlers(void);
static int _dump_sink(
const char *sink_name, unsigned sink_timeout,
long long count, long double interval,
const char *sink_name,
unsigned sink_timeout,
long long count,
long double interval,
bool key_required,
_output_context_s *ctx);
@@ -110,10 +113,10 @@ int main(int argc, char *argv[]) {
US_LOGGING_INIT;
US_THREAD_RENAME("main");
char *sink_name = NULL;
const char *sink_name = NULL;
unsigned sink_timeout = 1;
char *output_path = NULL;
bool output_json = false;
const char *out_path = NULL;
bool out_json = false;
long long count = 0;
long double interval = 0;
bool key_required = false;
@@ -150,8 +153,8 @@ int main(int argc, char *argv[]) {
switch (ch) {
case _O_SINK: OPT_SET(sink_name, optarg);
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
case _O_OUTPUT: OPT_SET(output_path, optarg);
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
case _O_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);
@@ -182,18 +185,18 @@ int main(int argc, char *argv[]) {
_output_context_s ctx = {0};
if (output_path && output_path[0] != '\0') {
if ((ctx.v_output = (void *)us_output_file_init(output_path, output_json)) == NULL) {
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;
}
_install_signal_handlers();
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_output && ctx.destroy) {
ctx.destroy(ctx.v_output);
if (ctx.v_out && ctx.destroy) {
ctx.destroy(ctx.v_out);
}
return retval;
}
@@ -206,30 +209,16 @@ static void _signal_handler(int signum) {
_g_stop = true;
}
static void _install_signal_handlers(void) {
struct sigaction sig_act = {0};
assert(!sigemptyset(&sig_act.sa_mask));
sig_act.sa_handler = _signal_handler;
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
US_LOG_DEBUG("Installing SIGINT handler ...");
assert(!sigaction(SIGINT, &sig_act, NULL));
US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGTERM, &sig_act, NULL));
US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGPIPE, &sig_act, NULL));
}
static int _dump_sink(
const char *sink_name, unsigned sink_timeout,
long long count, long double interval,
const char *sink_name,
unsigned sink_timeout,
long long count,
long double interval,
bool key_required,
_output_context_s *ctx) {
_output_context_s *ctx
) {
int retval = -1;
if (count == 0) {
count = -1;
@@ -238,49 +227,45 @@ static int _dump_sink(
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("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
if ((sink = us_memsink_init_opened("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
goto error;
}
unsigned fps = 0;
unsigned fps_accum = 0;
long long fps_second = 0;
long double last_ts = 0;
while (!_g_stop) {
bool key_requested;
const int error = us_memsink_client_get(sink, frame, &key_requested, key_required);
if (error == 0) {
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();
const long long now_second = us_floor_ms(now);
char fourcc_str[8];
US_LOG_VERBOSE("Frame: %s - %ux%u -- online=%d, key=%d, kr=%d, gop=%u, latency=%.3Lf, backlog=%.3Lf, size=%zu",
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->online, frame->key, key_requested, frame->gop,
now - frame->grab_ts, (last_ts ? now - last_ts : 0),
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_LOG_DEBUG(" stride=%u, grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
frame->stride, frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
us_fpsi_update(fpsi, true, NULL);
if (now_second != fps_second) {
fps = fps_accum;
fps_accum = 0;
fps_second = now_second;
US_LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
}
fps_accum += 1;
if (ctx->v_output != NULL) {
ctx->write(ctx->v_output, frame);
if (ctx->v_out != NULL) {
ctx->write(ctx->v_out, frame);
}
if (count >= 0) {
@@ -293,25 +278,21 @@ static int _dump_sink(
if (interval_us > 0) {
usleep(interval_us);
}
} else if (error == -2) {
} else if (got == US_ERROR_NO_DATA) {
usleep(1000);
} else {
goto error;
}
}
int retval = 0;
goto ok;
retval = 0;
error:
retval = -1;
ok:
US_DELETE(sink, us_memsink_destroy);
us_frame_destroy(frame);
US_LOG_INFO("Bye-bye");
return retval;
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) {
@@ -319,7 +300,7 @@ static void _help(FILE *fp) {
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
SAY("═════════════════════════════════════════════════════");
SAY("Version: %s; license: GPLv3", US_VERSION);
SAY("Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com>\n");
SAY("Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com>\n");
SAY("Example:");
SAY("════════");
SAY(" ustreamer-dump --sink test --output - \\");

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -30,8 +30,8 @@
#define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \
const int m_len = US_ARRAY_LEN(x_array); \
assert(x_start <= m_len); \
for (int m_index = x_start; m_index < m_len; ++m_index) { \
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_index]; \
for (int m_i = x_start; m_i < m_len; ++m_i) { \
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_i]; \
__VA_ARGS__ \
} \
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,6 +22,13 @@
#include "base64.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "types.h"
#include "tools.h"
static const char _ENCODING_TABLE[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
@@ -34,11 +41,11 @@ static const char _ENCODING_TABLE[] = {
'4', '5', '6', '7', '8', '9', '+', '/',
};
static const unsigned _MOD_TABLE[] = {0, 2, 1};
static const uint _MOD_TABLE[] = {0, 2, 1};
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated) {
const uz encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
US_REALLOC(*encoded, encoded_size);
@@ -47,16 +54,16 @@ void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *
}
}
for (unsigned data_index = 0, encoded_index = 0; data_index < size;) {
# define OCTET(_name) unsigned _name = (data_index < size ? (uint8_t)data[data_index++] : 0)
for (uint data_i = 0, encoded_i = 0; data_i < size;) {
# define OCTET(_name) uint _name = (data_i < size ? (u8)data[data_i++] : 0)
OCTET(octet_a);
OCTET(octet_b);
OCTET(octet_c);
# undef OCTET
const unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
const uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
# define ENCODE(_offset) (*encoded)[encoded_i++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
ENCODE(3);
ENCODE(2);
ENCODE(1);
@@ -64,7 +71,7 @@ void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *
# undef ENCODE
}
for (unsigned index = 0; index < _MOD_TABLE[size % 3]; index++) {
for (uint index = 0; index < _MOD_TABLE[size % 3]; index++) {
(*encoded)[encoded_size - 2 - index] = '=';
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,13 +22,7 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include "tools.h"
#include "types.h"
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated);

1233
src/libs/capture.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,48 +22,24 @@
#pragma once
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <stdatomic.h>
#include <sys/select.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "../libs/tools.h"
#include "../libs/array.h"
#include "../libs/logging.h"
#include "../libs/threading.h"
#include "../libs/frame.h"
#include "../libs/xioctl.h"
#include "types.h"
#include "frame.h"
#define US_VIDEO_MIN_WIDTH ((unsigned)160)
#define US_VIDEO_MAX_WIDTH ((unsigned)15360)
#define US_VIDEO_MIN_WIDTH ((uint)160)
#define US_VIDEO_MAX_WIDTH ((uint)15360) // Remember about stream->run->http_capture_state;
#define US_VIDEO_MIN_HEIGHT ((unsigned)120)
#define US_VIDEO_MAX_HEIGHT ((unsigned)8640)
#define US_VIDEO_MIN_HEIGHT ((uint)120)
#define US_VIDEO_MAX_HEIGHT ((uint)8640)
#define US_VIDEO_MAX_FPS ((unsigned)120)
#define US_VIDEO_MAX_FPS ((uint)120)
#define US_STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
#define US_FORMAT_UNKNOWN -1
#define US_FORMATS_STR "YUYV, YVYU, UYVY, RGB565, RGB24, BGR24, MJPEG, JPEG"
#define US_IO_METHOD_UNKNOWN -1
#define US_FORMATS_STR "YUYV, YVYU, UYVY, YUV420, YVU420, RGB565, RGB24, BGR24, GREY, MJPEG, JPEG"
#define US_IO_METHODS_STR "MMAP, USERPTR"
@@ -72,23 +48,26 @@ typedef struct {
struct v4l2_buffer buf;
int dma_fd;
bool grabbed;
} us_hw_buffer_s;
atomic_int refs;
} us_capture_hwbuf_s;
typedef struct {
int fd;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
unsigned hw_fps;
unsigned jpeg_quality;
size_t raw_size;
unsigned n_bufs;
us_hw_buffer_s *hw_bufs;
uint width;
uint height;
uint format;
uint stride;
float hz;
uint jpeg_quality;
uz raw_size;
uint n_bufs;
us_capture_hwbuf_s *bufs;
bool dma;
enum v4l2_buf_type capture_type;
bool capturing;
bool persistent_timeout_reported;
} us_device_runtime_s;
bool capture_mplane;
bool streamon;
int open_error_once;
} us_capture_runtime_s;
typedef enum {
CTL_MODE_NONE = 0,
@@ -120,37 +99,40 @@ typedef struct {
typedef struct {
char *path;
unsigned input;
unsigned width;
unsigned height;
unsigned format;
unsigned jpeg_quality;
uint input;
uint width;
uint height;
uint format;
bool format_swap_rgb;
uint jpeg_quality;
v4l2_std_id standard;
enum v4l2_memory io_method;
bool dv_timings;
unsigned n_bufs;
unsigned desired_fps;
size_t min_frame_size;
uint n_bufs;
bool dma_export;
bool dma_required;
uz min_frame_size;
bool allow_truncated_frames;
bool persistent;
unsigned timeout;
uint timeout;
us_controls_s ctl;
us_device_runtime_s *run;
} us_device_s;
us_capture_runtime_s *run;
} us_capture_s;
us_device_s *us_device_init(void);
void us_device_destroy(us_device_s *dev);
us_capture_s *us_capture_init(void);
void us_capture_destroy(us_capture_s *cap);
int us_device_parse_format(const char *str);
v4l2_std_id us_device_parse_standard(const char *str);
int us_device_parse_io_method(const char *str);
int us_capture_parse_format(const char *str);
int us_capture_parse_standard(const char *str);
int us_capture_parse_io_method(const char *str);
int us_device_open(us_device_s *dev);
void us_device_close(us_device_s *dev);
int us_capture_open(us_capture_s *cap);
void us_capture_close(us_capture_s *cap);
int us_device_export_to_dma(us_device_s *dev);
int us_device_switch_capturing(us_device_s *dev, bool enable);
int us_device_select(us_device_s *dev, bool *has_read, bool *has_write, bool *has_error);
int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw);
int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw);
int us_device_consume_event(us_device_s *dev);
int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw);
int us_capture_hwbuf_release(const us_capture_s *cap, us_capture_hwbuf_s *hw);
void us_capture_hwbuf_incref(us_capture_hwbuf_s *hw);
void us_capture_hwbuf_decref(us_capture_hwbuf_s *hw);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,11 +22,14 @@
#pragma once
#define US_VERSION_MAJOR 5
#define US_VERSION_MINOR 50
#include "types.h"
#define US_VERSION_MAJOR 6
#define US_VERSION_MINOR 47
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
#define US_VERSION US_MAKE_VERSION1(US_VERSION_MAJOR, US_VERSION_MINOR)
#define US_VERSION_U ((unsigned)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))
#define US_VERSION_U ((uint)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))

794
src/libs/drm/drm.c Normal file
View File

@@ -0,0 +1,794 @@
/*****************************************************************************
# #
# 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 "drm.h"
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#ifdef __linux__
# include <sys/sysmacros.h>
#endif
#include <linux/videodev2.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#include <libdrm/drm.h>
#include "../types.h"
#include "../errors.h"
#include "../tools.h"
#include "../logging.h"
#include "../frame.h"
#include "../frametext.h"
#include "../capture.h"
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_buf);
static int _drm_check_status(us_drm_s *drm);
static void _drm_ensure_dpms_power(us_drm_s *drm, bool on);
static int _drm_init_buffers(us_drm_s *drm, const us_capture_s *cap);
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz);
static u32 _find_dpms(int fd, drmModeConnector *conn);
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs);
static const char *_connector_type_to_string(u32 type);
static float _get_refresh_rate(const drmModeModeInfo *mode);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
us_drm_s *us_drm_init(void) {
us_drm_runtime_s *run;
US_CALLOC(run, 1);
run->fd = -1;
run->status_fd = -1;
run->dpms_state = -1;
run->opened = -1;
run->has_vsync = true;
run->exposing_dma_fd = -1;
run->ft = us_frametext_init();
us_drm_s *drm;
US_CALLOC(drm, 1);
// drm->path = "/dev/dri/card0";
drm->path = "/dev/dri/by-path/platform-gpu-card";
drm->port = "HDMI-A-2"; // OUT2 on PiKVM V4 Plus
drm->timeout = 5;
drm->blank_after = 5;
drm->run = run;
return drm;
}
void us_drm_destroy(us_drm_s *drm) {
us_frametext_destroy(drm->run->ft);
US_DELETE(drm->run, free);
US_DELETE(drm, free); // cppcheck-suppress uselessAssignmentPtrArg
}
int us_drm_open(us_drm_s *drm, const us_capture_s *cap) {
us_drm_runtime_s *const run = drm->run;
assert(run->fd < 0);
switch (_drm_check_status(drm)) {
case 0: break;
case US_ERROR_NO_DEVICE: goto unplugged;
default: goto error;
}
_LOG_INFO("Using passthrough: %s[%s]", drm->path, drm->port);
_LOG_INFO("Configuring DRM device for %s ...", (cap == NULL ? "STUB" : "DMA"));
if ((run->fd = open(drm->path, O_RDWR | O_CLOEXEC | O_NONBLOCK)) < 0) {
_LOG_PERROR("Can't open DRM device");
goto error;
}
_LOG_DEBUG("DRM device fd=%d opened", run->fd);
int stub = 0; // Open the real device with DMA
if (cap == NULL) {
stub = US_DRM_STUB_USER;
} else if (cap->run->format != V4L2_PIX_FMT_RGB24 && cap->run->format != V4L2_PIX_FMT_BGR24) {
stub = US_DRM_STUB_BAD_FORMAT;
char fourcc_str[8];
us_fourcc_to_string(cap->run->format, fourcc_str, 8);
_LOG_ERROR("Input format %s is not supported, forcing to STUB ...", fourcc_str);
}
# define CHECK_CAP(x_cap) { \
_LOG_DEBUG("Checking %s ...", #x_cap); \
u64 m_check; \
if (drmGetCap(run->fd, x_cap, &m_check) < 0) { \
_LOG_PERROR("Can't check " #x_cap); \
goto error; \
} \
if (!m_check) { \
_LOG_ERROR(#x_cap " is not supported"); \
goto error; \
} \
}
CHECK_CAP(DRM_CAP_DUMB_BUFFER);
if (stub == 0) {
CHECK_CAP(DRM_CAP_PRIME);
}
# undef CHECK_CAP
const uint width = (stub > 0 ? 0 : cap->run->width);
const uint height = (stub > 0 ? 0 : cap->run->height);
const uint hz = (stub > 0 ? 0 : cap->run->hz);
switch (_drm_find_sink(drm, width, height, hz)) {
case 0: break;
case US_ERROR_NO_DEVICE: goto unplugged;
default: goto error;
}
if ((stub == 0) && (width != run->mode.hdisplay || height < run->mode.vdisplay)) {
// We'll try to show something instead of nothing if height != vdisplay
stub = US_DRM_STUB_BAD_RESOLUTION;
_LOG_ERROR("There is no appropriate modes for the capture, forcing to STUB ...");
}
if (_drm_init_buffers(drm, (stub > 0 ? NULL : cap)) < 0) {
goto error;
}
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
_LOG_DEBUG("Setting up CRTC ...");
if (drmModeSetCrtc(
run->fd,
run->crtc_id, run->bufs[0].id,
0, 0, // X, Y
&run->conn_id, 1, &run->mode
) < 0) {
_LOG_PERROR("Can't set CRTC");
goto error;
}
_LOG_INFO("Opened for %s ...", (stub > 0 ? "STUB" : "DMA"));
run->exposing_dma_fd = -1;
run->blank_at_ts = 0;
run->opened = stub;
run->once = 0;
return run->opened;
error:
us_drm_close(drm);
return run->opened; // -1 after us_drm_close()
unplugged:
US_ONCE_FOR(run->once, __LINE__, {
_LOG_ERROR("Display is not plugged");
});
us_drm_close(drm);
run->opened = US_ERROR_NO_DEVICE;
return run->opened;
}
void us_drm_close(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
if (run->exposing_dma_fd >= 0) {
// Нужно подождать, пока dma_fd не освободится, прежде чем прерывать процесс.
// Просто на всякий случай.
assert(run->fd >= 0);
us_drm_wait_for_vsync(drm);
run->exposing_dma_fd = -1;
}
if (run->saved_crtc != NULL) {
_LOG_DEBUG("Restoring CRTC ...");
if (drmModeSetCrtc(
run->fd,
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
run->saved_crtc->x, run->saved_crtc->y,
&run->conn_id, 1, &run->saved_crtc->mode
) < 0 && errno != ENOENT) {
_LOG_PERROR("Can't restore CRTC");
}
drmModeFreeCrtc(run->saved_crtc);
run->saved_crtc = NULL;
}
if (run->bufs != NULL) {
_LOG_DEBUG("Releasing buffers ...");
for (uint n_buf = 0; n_buf < run->n_bufs; ++n_buf) {
us_drm_buffer_s *const buf = &run->bufs[n_buf];
if (buf->fb_added && drmModeRmFB(run->fd, buf->id) < 0) {
_LOG_PERROR("Can't remove buffer=%u", n_buf);
}
if (buf->dumb_created) {
struct drm_mode_destroy_dumb destroy = {.handle = buf->handle};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy) < 0) {
_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
}
}
if (buf->data != NULL && munmap(buf->data, buf->allocated)) {
_LOG_PERROR("Can't unmap buffer=%u", n_buf);
}
}
US_DELETE(run->bufs, free);
run->n_bufs = 0;
}
const bool say = (run->fd >= 0);
US_CLOSE_FD(run->status_fd);
US_CLOSE_FD(run->fd);
run->crtc_id = 0;
run->dpms_state = -1;
run->opened = -1;
run->has_vsync = true;
run->stub_n_buf = 0;
if (say) {
_LOG_INFO("Closed");
}
}
int us_drm_ensure_no_signal(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
assert(run->fd >= 0);
assert(run->opened > 0);
const ldf now_ts = us_get_now_monotonic();
if (run->blank_at_ts == 0) {
run->blank_at_ts = now_ts + drm->blank_after;
}
const ldf saved_ts = run->blank_at_ts; // us_drm*() rewrites it to 0
int retval;
if (now_ts <= run->blank_at_ts) {
retval = us_drm_wait_for_vsync(drm);
if (retval == 0) {
retval = us_drm_expose_stub(drm, US_DRM_STUB_NO_SIGNAL, NULL);
}
} else {
US_ONCE_FOR(run->once, __LINE__, {
_LOG_INFO("Turning off the display by timeout ...");
});
retval = us_drm_dpms_power_off(drm);
}
run->blank_at_ts = saved_ts;
return retval;
}
int us_drm_dpms_power_off(us_drm_s *drm) {
assert(drm->run->fd >= 0);
switch (_drm_check_status(drm)) {
case 0: break;
case US_ERROR_NO_DEVICE: return 0; // Unplugged, nice
// Во время переключения DPMS монитор моргает один раз состоянием disconnected,
// а потом почему-то снова оказывается connected. Так что просто считаем,
// что отсоединенный монитор на этом этапе - это нормально.
default: return -1;
}
_drm_ensure_dpms_power(drm, false);
return 0;
}
int us_drm_wait_for_vsync(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run;
assert(run->fd >= 0);
run->blank_at_ts = 0;
switch (_drm_check_status(drm)) {
case 0: break;
case US_ERROR_NO_DEVICE: return US_ERROR_NO_DEVICE;
default: return -1;
}
_drm_ensure_dpms_power(drm, true);
if (run->has_vsync) {
return 0;
}
struct timeval timeout = {.tv_sec = drm->timeout};
fd_set fds;
FD_ZERO(&fds);
FD_SET(run->fd, &fds);
_LOG_DEBUG("Calling select() for VSync ...");
const int result = select(run->fd + 1, &fds, NULL, NULL, &timeout);
if (result < 0) {
_LOG_PERROR("Can't select(%d) device for VSync", run->fd);
return -1;
} else if (result == 0) {
_LOG_ERROR("Device timeout while waiting VSync");
return -1;
}
drmEventContext ctx = {
.version = DRM_EVENT_CONTEXT_VERSION,
.page_flip_handler = _drm_vsync_callback,
};
_LOG_DEBUG("Handling DRM event (maybe VSync) ...");
if (drmHandleEvent(run->fd, &ctx) < 0) {
_LOG_PERROR("Can't handle DRM event");
return -1;
}
return 0;
}
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_buf) {
(void)fd;
(void)n_frame;
(void)sec;
(void)usec;
us_drm_buffer_s *const buf = v_buf;
*buf->ctx.has_vsync = true;
*buf->ctx.exposing_dma_fd = -1;
_LOG_DEBUG("Got VSync signal");
}
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap) {
us_drm_runtime_s *const run = drm->run;
assert(run->fd >= 0);
assert(run->opened > 0);
run->blank_at_ts = 0;
switch (_drm_check_status(drm)) {
case 0: break;
case US_ERROR_NO_DEVICE: return US_ERROR_NO_DEVICE;
default: return -1;
}
_drm_ensure_dpms_power(drm, true);
# define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay)
switch (stub) {
case US_DRM_STUB_BAD_RESOLUTION: {
assert(cap != NULL);
char msg[1024];
US_SNPRINTF(msg, 1023,
"=== PiKVM ==="
"\n \n< UNSUPPORTED RESOLUTION >"
"\n \n< %ux%up%.02f >"
"\n \nby this display",
cap->run->width, cap->run->height, cap->run->hz);
DRAW_MSG(msg);
break;
};
case US_DRM_STUB_BAD_FORMAT:
DRAW_MSG("=== PiKVM ===\n \n< UNSUPPORTED CAPTURE FORMAT >");
break;
case US_DRM_STUB_NO_SIGNAL:
DRAW_MSG("=== PiKVM ===\n \n< NO LIVE VIDEO >");
break;
case US_DRM_STUB_BUSY:
DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >");
break;
default:
DRAW_MSG("=== PiKVM ===\n \n< ??? >");
break;
}
# undef DRAW_MSG
us_drm_buffer_s *const buf = &run->bufs[run->stub_n_buf];
run->has_vsync = false;
_LOG_DEBUG("Copying STUB frame ...")
memcpy(buf->data, run->ft->frame->data, US_MIN(run->ft->frame->used, buf->allocated));
_LOG_DEBUG("Exposing STUB framebuffer n_buf=%u ...", run->stub_n_buf);
const int retval = drmModePageFlip(
run->fd, run->crtc_id, buf->id,
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
buf);
if (retval < 0) {
_LOG_PERROR("Can't expose STUB framebuffer n_buf=%u ...", run->stub_n_buf);
}
_LOG_DEBUG("Exposed STUB framebuffer n_buf=%u", run->stub_n_buf);
run->stub_n_buf = (run->stub_n_buf + 1) % run->n_bufs;
return retval;
}
int us_drm_expose_dma(us_drm_s *drm, const us_capture_hwbuf_s *hw) {
us_drm_runtime_s *const run = drm->run;
us_drm_buffer_s *const buf = &run->bufs[hw->buf.index];
assert(run->fd >= 0);
assert(run->opened == 0);
run->blank_at_ts = 0;
switch (_drm_check_status(drm)) {
case 0: break;
case US_ERROR_NO_DEVICE: return US_ERROR_NO_DEVICE;
default: return -1;
}
_drm_ensure_dpms_power(drm, true);
run->has_vsync = false;
_LOG_DEBUG("Exposing DMA framebuffer n_buf=%u ...", hw->buf.index);
const int retval = drmModePageFlip(
run->fd, run->crtc_id, buf->id,
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
buf);
if (retval < 0) {
_LOG_PERROR("Can't expose DMA framebuffer n_buf=%u ...", run->stub_n_buf);
}
_LOG_DEBUG("Exposed DMA framebuffer n_buf=%u", run->stub_n_buf);
run->exposing_dma_fd = hw->dma_fd;
return retval;
}
static int _drm_check_status(us_drm_s *drm) {
us_drm_runtime_s *run = drm->run;
if (run->status_fd < 0) {
_LOG_DEBUG("Trying to find status file ...");
struct stat st;
if (stat(drm->path, &st) < 0) {
_LOG_PERROR("Can't stat() DRM device");
goto error;
}
const uint mi = minor(st.st_rdev);
_LOG_DEBUG("DRM device minor(st_rdev)=%u", mi);
char path[128];
US_SNPRINTF(path, 127, "/sys/class/drm/card%u-%s/status", mi, drm->port);
_LOG_DEBUG("Opening status file %s ...", path);
if ((run->status_fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) {
_LOG_PERROR("Can't open status file: %s", path);
goto error;
}
_LOG_DEBUG("Status file fd=%d opened", run->status_fd);
}
char status_ch;
if (read(run->status_fd, &status_ch, 1) != 1) {
_LOG_PERROR("Can't read status file");
goto error;
}
if (lseek(run->status_fd, 0, SEEK_SET) != 0) {
_LOG_PERROR("Can't rewind status file");
goto error;
}
_LOG_DEBUG("Current display status: %c", status_ch);
return (status_ch == 'd' ? US_ERROR_NO_DEVICE : 0);
error:
US_CLOSE_FD(run->status_fd);
return -1;
}
static void _drm_ensure_dpms_power(us_drm_s *drm, bool on) {
us_drm_runtime_s *const run = drm->run;
if (run->dpms_id > 0 && run->dpms_state != (int)on) {
_LOG_INFO("Changing DPMS power mode: %d -> %u ...", run->dpms_state, on);
if (drmModeConnectorSetProperty(
run->fd, run->conn_id, run->dpms_id,
(on ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)
) < 0) {
_LOG_PERROR("Can't set DPMS power=%u (ignored)", on);
}
}
run->dpms_state = (int)on;
}
static int _drm_init_buffers(us_drm_s *drm, const us_capture_s *cap) {
us_drm_runtime_s *const run = drm->run;
const uint n_bufs = (cap == NULL ? 4 : cap->run->n_bufs);
const char *name = (cap == NULL ? "STUB" : "DMA");
_LOG_DEBUG("Initializing %u %s buffers ...", n_bufs, name);
uint format = DRM_FORMAT_RGB888;
US_CALLOC(run->bufs, n_bufs);
for (run->n_bufs = 0; run->n_bufs < n_bufs; ++run->n_bufs) {
const uint n_buf = run->n_bufs;
us_drm_buffer_s *const buf = &run->bufs[n_buf];
buf->ctx.has_vsync = &run->has_vsync;
buf->ctx.exposing_dma_fd = &run->exposing_dma_fd;
u32 handles[4] = {0};
u32 strides[4] = {0};
u32 offsets[4] = {0};
if (cap == NULL) {
struct drm_mode_create_dumb create = {
.width = run->mode.hdisplay,
.height = run->mode.vdisplay,
.bpp = 24,
};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) {
_LOG_PERROR("Can't create %s buffer=%u", name, n_buf);
return -1;
}
buf->handle = create.handle;
buf->dumb_created = true;
struct drm_mode_map_dumb map = {.handle = create.handle};
if (drmIoctl(run->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) {
_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
return -1;
}
if ((buf->data = mmap(
NULL, create.size,
PROT_READ | PROT_WRITE, MAP_SHARED,
run->fd, map.offset
)) == MAP_FAILED) {
_LOG_PERROR("Can't map buffer=%u", n_buf);
return -1;
}
memset(buf->data, 0, create.size);
buf->allocated = create.size;
handles[0] = create.handle;
strides[0] = create.pitch;
} else {
if (drmPrimeFDToHandle(run->fd, cap->run->bufs[n_buf].dma_fd, &buf->handle) < 0) {
_LOG_PERROR("Can't import DMA buffer=%u from capture device", n_buf);
return -1;
}
handles[0] = buf->handle;
strides[0] = cap->run->stride;
switch (cap->run->format) {
case V4L2_PIX_FMT_RGB24: format = (DRM_FORMAT_BIG_ENDIAN ? DRM_FORMAT_BGR888 : DRM_FORMAT_RGB888); break;
case V4L2_PIX_FMT_BGR24: format = (DRM_FORMAT_BIG_ENDIAN ? DRM_FORMAT_RGB888 : DRM_FORMAT_BGR888); break;
}
}
if (drmModeAddFB2(
run->fd,
run->mode.hdisplay, run->mode.vdisplay, format,
handles, strides, offsets, &buf->id, 0
)) {
_LOG_PERROR("Can't setup buffer=%u", n_buf);
return -1;
}
buf->fb_added = true;
}
return 0;
}
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
us_drm_runtime_s *const run = drm->run;
run->crtc_id = 0;
_LOG_DEBUG("Trying to find the appropriate sink ...");
drmModeRes *res = drmModeGetResources(run->fd);
if (res == NULL) {
_LOG_PERROR("Can't get resources info");
goto done;
}
if (res->count_connectors <= 0) {
_LOG_ERROR("Can't find any connectors");
goto done;
}
for (int ci = 0; ci < res->count_connectors; ++ci) {
drmModeConnector *conn = drmModeGetConnector(run->fd, res->connectors[ci]);
if (conn == NULL) {
_LOG_PERROR("Can't get connector index=%d", ci);
goto done;
}
char port[32];
US_SNPRINTF(port, 31, "%s-%u",
_connector_type_to_string(conn->connector_type),
conn->connector_type_id);
if (strcmp(port, drm->port) != 0) {
drmModeFreeConnector(conn);
continue;
}
_LOG_INFO("Using connector %s: conn_type=%d, conn_type_id=%d",
drm->port, conn->connector_type, conn->connector_type_id);
if (conn->connection != DRM_MODE_CONNECTED) {
_LOG_ERROR("Connector for port %s has !DRM_MODE_CONNECTED", drm->port);
drmModeFreeConnector(conn);
goto done;
}
const drmModeModeInfo *best;
if ((best = _find_best_mode(conn, width, height, hz)) == NULL) {
_LOG_ERROR("Can't find any appropriate display modes");
drmModeFreeConnector(conn);
goto unplugged;
}
_LOG_INFO("Using best mode: %ux%up%.02f",
best->hdisplay, best->vdisplay, _get_refresh_rate(best));
if ((run->dpms_id = _find_dpms(run->fd, conn)) > 0) {
_LOG_INFO("Using DPMS: id=%u", run->dpms_id);
} else {
_LOG_INFO("Using DPMS: None");
}
u32 taken_crtcs = 0; // Unused here
if ((run->crtc_id = _find_crtc(run->fd, res, conn, &taken_crtcs)) == 0) {
_LOG_ERROR("Can't find CRTC");
drmModeFreeConnector(conn);
goto done;
}
_LOG_INFO("Using CRTC: id=%u", run->crtc_id);
run->conn_id = conn->connector_id;
memcpy(&run->mode, best, sizeof(drmModeModeInfo));
drmModeFreeConnector(conn);
break;
}
done:
drmModeFreeResources(res);
return (run->crtc_id > 0 ? 0 : -1);
unplugged:
drmModeFreeResources(res);
return US_ERROR_NO_DEVICE;
}
static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz) {
drmModeModeInfo *best = NULL;
drmModeModeInfo *closest = NULL;
drmModeModeInfo *pref = NULL;
for (int mi = 0; mi < conn->count_modes; ++mi) {
drmModeModeInfo *const mode = &conn->modes[mi];
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
continue; // Discard interlaced
}
const float mode_hz = _get_refresh_rate(mode);
if (width == 640 && height == 416 && mode->hdisplay == 640 && mode->vdisplay == 480) {
// A special case for some ancient DOS device with VGA converter.
// @CapnKirk in Discord
if (hz > 0 && mode_hz < hz) {
best = mode;
best->vdisplay = 416;
break;
}
}
if (mode->hdisplay == width && mode->vdisplay == height) {
best = mode; // Any mode with exact resolution
if (hz > 0 && mode_hz == hz) {
break; // Exact mode with same freq
}
}
if (mode->hdisplay == width && mode->vdisplay < height) {
if (closest == NULL || _get_refresh_rate(closest) != hz) {
closest = mode; // Something like 1920x1080p60 for 1920x1200p60 source
}
}
if (pref == NULL && (mode->type & DRM_MODE_TYPE_PREFERRED)) {
pref = mode; // Preferred mode if nothing is found
}
}
if (best == NULL) {
best = closest;
}
if (best == NULL) {
best = pref;
}
if (best == NULL) {
best = (conn->count_modes > 0 ? &conn->modes[0] : NULL);
}
assert(best == NULL || best->hdisplay > 0);
assert(best == NULL || best->vdisplay > 0);
return best;
}
static u32 _find_dpms(int fd, drmModeConnector *conn) {
for (int pi = 0; pi < conn->count_props; pi++) {
drmModePropertyPtr prop = drmModeGetProperty(fd, conn->props[pi]);
if (prop != NULL) {
if (!strcmp(prop->name, "DPMS")) {
const u32 id = prop->prop_id;
drmModeFreeProperty(prop);
return id;
}
drmModeFreeProperty(prop);
}
}
return 0;
}
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs) {
for (int ei = 0; ei < conn->count_encoders; ++ei) {
drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[ei]);
if (enc == NULL) {
continue;
}
for (int ci = 0; ci < res->count_crtcs; ++ci) {
u32 bit = (1 << ci);
if (!(enc->possible_crtcs & bit)) {
continue; // Not compatible
}
if (*taken_crtcs & bit) {
continue; // Already taken
}
drmModeFreeEncoder(enc);
*taken_crtcs |= bit;
return res->crtcs[ci];
}
drmModeFreeEncoder(enc);
}
return 0;
}
static const char *_connector_type_to_string(u32 type) {
switch (type) {
# define CASE_NAME(x_suffix, x_name) \
case DRM_MODE_CONNECTOR_##x_suffix: return x_name;
CASE_NAME(VGA, "VGA");
CASE_NAME(DVII, "DVI-I");
CASE_NAME(DVID, "DVI-D");
CASE_NAME(DVIA, "DVI-A");
CASE_NAME(Composite, "Composite");
CASE_NAME(SVIDEO, "SVIDEO");
CASE_NAME(LVDS, "LVDS");
CASE_NAME(Component, "Component");
CASE_NAME(9PinDIN, "DIN");
CASE_NAME(DisplayPort, "DP");
CASE_NAME(HDMIA, "HDMI-A");
CASE_NAME(HDMIB, "HDMI-B");
CASE_NAME(TV, "TV");
CASE_NAME(eDP, "eDP");
CASE_NAME(VIRTUAL, "Virtual");
CASE_NAME(DSI, "DSI");
CASE_NAME(DPI, "DPI");
CASE_NAME(WRITEBACK, "Writeback");
CASE_NAME(SPI, "SPI");
CASE_NAME(USB, "USB");
case DRM_MODE_CONNECTOR_Unknown: break;
# undef CASE_NAME
}
return "Unknown";
}
static float _get_refresh_rate(const drmModeModeInfo *mode) {
int mhz = (mode->clock * 1000000LL / mode->htotal + mode->vtotal / 2) / mode->vtotal;
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
mhz *= 2;
}
if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
mhz /= 2;
}
if (mode->vscan > 1) {
mhz /= mode->vscan;
}
return (float)mhz / 1000;
}

97
src/libs/drm/drm.h Normal file
View File

@@ -0,0 +1,97 @@
/*****************************************************************************
# #
# 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 <xf86drmMode.h>
#include "../types.h"
#include "../frame.h"
#include "../frametext.h"
#include "../capture.h"
typedef enum {
US_DRM_STUB_USER = 1,
US_DRM_STUB_BAD_RESOLUTION,
US_DRM_STUB_BAD_FORMAT,
US_DRM_STUB_NO_SIGNAL,
US_DRM_STUB_BUSY,
} us_drm_stub_e;
typedef struct {
u32 id;
u32 handle;
u8 *data;
uz allocated;
bool dumb_created;
bool fb_added;
struct {
bool *has_vsync;
int *exposing_dma_fd;
} ctx;
} us_drm_buffer_s;
typedef struct {
int status_fd;
int fd;
u32 crtc_id;
u32 conn_id;
u32 dpms_id;
drmModeModeInfo mode;
us_drm_buffer_s *bufs;
uint n_bufs;
drmModeCrtc *saved_crtc;
int dpms_state;
int opened;
bool has_vsync;
int exposing_dma_fd;
uint stub_n_buf;
ldf blank_at_ts;
int once;
us_frametext_s *ft;
} us_drm_runtime_s;
typedef struct {
char *path;
char *port;
uint timeout;
uint blank_after;
us_drm_runtime_s *run;
} us_drm_s;
us_drm_s *us_drm_init(void);
void us_drm_destroy(us_drm_s *drm);
int us_drm_open(us_drm_s *drm, const us_capture_s *cap);
void us_drm_close(us_drm_s *drm);
int us_drm_dpms_power_off(us_drm_s *drm);
int us_drm_wait_for_vsync(us_drm_s *drm);
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap);
int us_drm_expose_dma(us_drm_s *drm, const us_capture_hwbuf_s *hw);
int us_drm_ensure_no_signal(us_drm_s *drm);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,12 +22,10 @@
#pragma once
#include <stdbool.h>
#include <event2/util.h>
#include <event2/http.h>
#include <event2/keyvalq_struct.h>
bool us_uri_get_true(struct evkeyvalq *params, const char *key);
char *us_uri_get_string(struct evkeyvalq *params, const char *key);
#define US_ERROR_COMMON -1
#define US_ERROR_NO_DEVICE -2
#define US_ERROR_NO_CABLE -3
#define US_ERROR_NO_SIGNAL -4
#define US_ERROR_NO_SYNC -5
#define US_ERROR_NO_LANES -6
#define US_ERROR_NO_DATA -7

109
src/libs/fpsi.c Normal file
View File

@@ -0,0 +1,109 @@
/*****************************************************************************
# #
# 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 "fpsi.h"
#include <stdatomic.h>
#include <pthread.h>
#include "types.h"
#include "tools.h"
#include "threading.h"
#include "logging.h"
#include "frame.h"
us_fpsi_s *us_fpsi_init(const char *name, bool with_meta) {
us_fpsi_s *fpsi;
US_CALLOC(fpsi, 1);
fpsi->name = us_strdup(name);
fpsi->with_meta = with_meta;
atomic_init(&fpsi->state_ts, 0);
atomic_init(&fpsi->state, 0);
return fpsi;
}
void us_fpsi_destroy(us_fpsi_s *fpsi) {
free(fpsi->name);
free(fpsi);
}
void us_fpsi_frame_to_meta(const us_frame_s *frame, us_fpsi_meta_s *meta) {
meta->width = frame->width;
meta->height = frame->height;
meta->online = frame->online;
}
void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
if (meta != NULL) {
assert(fpsi->with_meta);
} else {
assert(!fpsi->with_meta);
}
const sll now_ts = us_floor_ms(us_get_now_monotonic());
if (atomic_load(&fpsi->state_ts) != now_ts) {
US_LOG_PERF_FPS("FPS: %s: %u", fpsi->name, fpsi->accum);
// Fast mutex-less store method
ull state = (ull)fpsi->accum & 0xFFFF;
if (fpsi->with_meta) {
assert(meta != NULL);
state |= (ull)(meta->width & 0xFFFF) << 16;
state |= (ull)(meta->height & 0xFFFF) << 32;
state |= (ull)(meta->online ? 1 : 0) << 48;
}
atomic_store(&fpsi->state, state); // Сначала инфа
atomic_store(&fpsi->state_ts, now_ts); // Потом время, это важно
fpsi->accum = 0;
}
if (bump) {
++fpsi->accum;
}
}
uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
if (meta != NULL) {
assert(fpsi->with_meta);
}
// Между чтением инфы и времени может быть гонка,
// но это неважно. Если время свежее, до данные тоже
// будут свежмими, обратный случай не так важен.
const sll now_ts = us_floor_ms(us_get_now_monotonic());
const sll state_ts = atomic_load(&fpsi->state_ts); // Сначала время
const ull state = atomic_load(&fpsi->state); // Потом инфа
uint current = state & 0xFFFF;
if (fpsi->with_meta && meta != NULL) {
meta->width = (state >> 16) & 0xFFFF;
meta->height = (state >> 32) & 0xFFFF;
meta->online = (state >> 48) & 1;
}
if (state_ts != now_ts && (state_ts + 1) != now_ts) {
// Только текущая или прошлая секунда
current = 0;
}
return current;
}

51
src/libs/fpsi.h Normal file
View File

@@ -0,0 +1,51 @@
/*****************************************************************************
# #
# 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 "types.h"
#include "frame.h"
typedef struct {
uint width;
uint height;
bool online;
} us_fpsi_meta_s;
typedef struct {
char *name;
bool with_meta;
uint accum;
atomic_llong state_ts;
atomic_ullong state;
} us_fpsi_s;
us_fpsi_s *us_fpsi_init(const char *name, bool with_meta);
void us_fpsi_destroy(us_fpsi_s *fpsi);
void us_fpsi_frame_to_meta(const us_frame_s *frame, us_fpsi_meta_s *meta);
void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta);
uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,11 +22,21 @@
#include "frame.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "types.h"
#include "tools.h"
us_frame_s *us_frame_init(void) {
us_frame_s *frame;
US_CALLOC(frame, 1);
us_frame_realloc_data(frame, 512 * 1024);
us_frame_realloc_data(frame, 32 * 1024);
frame->dma_fd = -1;
return frame;
}
@@ -36,21 +46,21 @@ void us_frame_destroy(us_frame_s *frame) {
free(frame);
}
void us_frame_realloc_data(us_frame_s *frame, size_t size) {
void us_frame_realloc_data(us_frame_s *frame, uz size) {
if (frame->allocated < size) {
US_REALLOC(frame->data, size);
frame->allocated = size;
}
}
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size) {
void us_frame_set_data(us_frame_s *frame, const u8 *data, uz size) {
us_frame_realloc_data(frame, size);
memcpy(frame->data, data, size);
frame->used = size;
}
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size) {
const size_t new_used = frame->used + size;
void us_frame_append_data(us_frame_s *frame, const u8 *data, uz size) {
const uz new_used = frame->used + size;
us_frame_realloc_data(frame, new_used);
memcpy(frame->data + frame->used, data, size);
frame->used = new_used;
@@ -64,24 +74,40 @@ void us_frame_copy(const us_frame_s *src, us_frame_s *dest) {
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
return (
a->allocated && b->allocated
&& US_FRAME_COMPARE_META_USED_NOTS(a, b)
&& US_FRAME_COMPARE_GEOMETRY(a, b)
&& !memcmp(a->data, b->data, b->used)
);
}
unsigned us_frame_get_padding(const us_frame_s *frame) {
unsigned bytes_per_pixel = 0;
uint us_frame_get_padding(const us_frame_s *frame) {
uint bytes_per_pixel = 0;
switch (frame->format) {
case V4L2_PIX_FMT_YUV420:
case V4L2_PIX_FMT_YVU420:
case V4L2_PIX_FMT_GREY:
bytes_per_pixel = 1;
break;
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YVYU:
case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break;
case V4L2_PIX_FMT_RGB565:
bytes_per_pixel = 2;
break;
case V4L2_PIX_FMT_BGR24:
case V4L2_PIX_FMT_RGB24: bytes_per_pixel = 3; break;
case V4L2_PIX_FMT_RGB24:
bytes_per_pixel = 3;
break;
// case V4L2_PIX_FMT_H264:
case V4L2_PIX_FMT_MJPEG:
case V4L2_PIX_FMT_JPEG: bytes_per_pixel = 0; break;
default: assert(0 && "Unknown format");
case V4L2_PIX_FMT_JPEG:
bytes_per_pixel = 0;
break;
default:
assert(0 && "Unknown format");
}
if (bytes_per_pixel > 0 && frame->stride > frame->width) {
return (frame->stride - frame->width * bytes_per_pixel);
@@ -89,13 +115,17 @@ unsigned us_frame_get_padding(const us_frame_s *frame) {
return 0;
}
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size) {
bool us_is_jpeg(uint format) {
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
}
const char *us_fourcc_to_string(uint format, char *buf, uz size) {
assert(size >= 8);
buf[0] = format & 0x7F;
buf[1] = (format >> 8) & 0x7F;
buf[2] = (format >> 16) & 0x7F;
buf[3] = (format >> 24) & 0x7F;
if (format & ((unsigned)1 << 31)) {
if (format & ((uint)1 << 31)) {
buf[4] = '-';
buf[5] = 'B';
buf[6] = 'E';

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,74 +22,70 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "types.h"
#include "tools.h"
#define US_FRAME_META_DECLARE \
uint width; \
uint height; \
uint format; \
uint stride; \
/* Stride is a bytesperline in V4L2 */ \
/* https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html */ \
/* https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd */ \
bool online; \
bool key; \
uint gop; \
\
ldf grab_begin_ts; \
ldf grab_end_ts; \
ldf encode_begin_ts; \
ldf encode_end_ts;
typedef struct {
uint8_t *data;
size_t used;
size_t allocated;
int dma_fd;
u8 *data;
uz used;
uz allocated;
int dma_fd;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
// Stride is a bytesperline in V4L2
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
bool online;
bool key;
unsigned gop;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
US_FRAME_META_DECLARE;
} us_frame_s;
#define US_FRAME_COPY_META(x_src, x_dest) { \
x_dest->width = x_src->width; \
x_dest->height = x_src->height; \
x_dest->format = x_src->format; \
x_dest->stride = x_src->stride; \
x_dest->online = x_src->online; \
x_dest->key = x_src->key; \
x_dest->gop = x_src->gop; \
x_dest->grab_ts = x_src->grab_ts; \
x_dest->encode_begin_ts = x_src->encode_begin_ts; \
x_dest->encode_end_ts = x_src->encode_end_ts; \
(x_dest)->width = (x_src)->width; \
(x_dest)->height = (x_src)->height; \
(x_dest)->format = (x_src)->format; \
(x_dest)->stride = (x_src)->stride; \
(x_dest)->online = (x_src)->online; \
(x_dest)->key = (x_src)->key; \
(x_dest)->gop = (x_src)->gop; \
\
(x_dest)->grab_begin_ts = (x_src)->grab_begin_ts; \
(x_dest)->grab_end_ts = (x_src)->grab_end_ts; \
(x_dest)->encode_begin_ts = (x_src)->encode_begin_ts; \
(x_dest)->encode_end_ts = (x_src)->encode_end_ts; \
}
static inline void us_frame_copy_meta(const us_frame_s *src, us_frame_s *dest) {
US_FRAME_COPY_META(src, dest);
}
#define US_FRAME_COMPARE_META_USED_NOTS(x_a, x_b) ( \
x_a->used == x_b->used \
&& x_a->width == x_b->width \
&& x_a->height == x_b->height \
&& x_a->format == x_b->format \
&& x_a->stride == x_b->stride \
&& x_a->online == x_b->online \
&& x_a->key == x_b->key \
&& x_a->gop == x_b->gop \
#define US_FRAME_COMPARE_GEOMETRY(x_a, x_b) ( \
/* Compare the used size and significant meta (no timings) */ \
(x_a)->used == (x_b)->used \
\
&& (x_a)->width == (x_b)->width \
&& (x_a)->height == (x_b)->height \
&& (x_a)->format == (x_b)->format \
&& (x_a)->stride == (x_b)->stride \
&& (x_a)->online == (x_b)->online \
&& (x_a)->key == (x_b)->key \
&& (x_a)->gop == (x_b)->gop \
)
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, unsigned format) {
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, uint format) {
assert(src->used > 0);
us_frame_copy_meta(src, dest);
US_FRAME_COPY_META(src, dest);
dest->encode_begin_ts = us_get_now_monotonic();
dest->format = format;
dest->stride = 0;
@@ -105,17 +101,14 @@ static inline void us_frame_encoding_end(us_frame_s *dest) {
us_frame_s *us_frame_init(void);
void us_frame_destroy(us_frame_s *frame);
void us_frame_realloc_data(us_frame_s *frame, size_t size);
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size);
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size);
void us_frame_realloc_data(us_frame_s *frame, uz size);
void us_frame_set_data(us_frame_s *frame, const u8 *data, uz size);
void us_frame_append_data(us_frame_s *frame, const u8 *data, uz size);
void us_frame_copy(const us_frame_s *src, us_frame_s *dest);
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b);
unsigned us_frame_get_padding(const us_frame_s *frame);
uint us_frame_get_padding(const us_frame_s *frame);
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size);
static inline bool us_is_jpeg(unsigned format) {
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
}
bool us_is_jpeg(uint format);
const char *us_fourcc_to_string(uint format, char *buf, uz size);

199
src/libs/frametext.c Normal file
View File

@@ -0,0 +1,199 @@
/*****************************************************************************
# #
# 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 "frametext.h"
#include <string.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include "tools.h"
#include "frame.h"
#include "frametext_font.h"
static void _frametext_draw_line(
us_frametext_s *ft,
const char *line,
uint scale_x,
uint scale_y,
uint start_x,
uint start_y);
us_frametext_s *us_frametext_init(void) {
us_frametext_s *ft;
US_CALLOC(ft, 1);
ft->frame = us_frame_init();
return ft;
}
void us_frametext_destroy(us_frametext_s *ft) {
us_frame_destroy(ft->frame);
US_DELETE(ft->text, free);
free(ft);
}
/*
Every character in the font is encoded row-wise in 8 bytes.
The least significant bit of each byte corresponds to the first pixel in a row.
The character 'A' (0x41 / 65) is encoded as { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}
0x0C => 0000 1100 => ..XX....
0X1E => 0001 1110 => .XXXX...
0x33 => 0011 0011 => XX..XX..
0x33 => 0011 0011 => XX..XX..
0x3F => 0011 1111 => xxxxxx..
0x33 => 0011 0011 => XX..XX..
0x33 => 0011 0011 => XX..XX..
0x00 => 0000 0000 => ........
To access the nth pixel in a row, right-shift by n.
. . X X . . . .
| | | | | | | |
(0x0C >> 0) & 1 == 0-+ | | | | | | |
(0x0C >> 1) & 1 == 0---+ | | | | | |
(0x0C >> 2) & 1 == 1-----+ | | | | |
(0x0C >> 3) & 1 == 1-------+ | | | |
(0x0C >> 4) & 1 == 0---------+ | | |
(0x0C >> 5) & 1 == 0-----------+ | |
(0x0C >> 6) & 1 == 0-------------+ |
(0x0C >> 7) & 1 == 0---------------+
*/
void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint height) {
assert(width > 0);
assert(height > 0);
us_frame_s *const frame = ft->frame;
if (
frame->width == width && frame->height == height
&& ft->text != NULL && !strcmp(ft->text, text)
) {
return;
}
US_DELETE(ft->text, free);
ft->text = us_strdup(text);
strcpy(ft->text, text);
frame->width = width;
frame->height = height;
frame->format = V4L2_PIX_FMT_RGB24;
frame->stride = width * 3;
frame->used = width * height * 3;
us_frame_realloc_data(frame, frame->used);
memset(frame->data, 0, frame->used);
if (frame->width == 0 || frame->height == 0) {
return;
}
char *str = us_strdup(text);
char *line;
char *rest;
uint block_width = 0;
uint block_height = 0;
while ((line = strtok_r((block_height == 0 ? str : NULL), "\n", &rest)) != NULL) {
block_width = US_MAX(strlen(line) * 8, block_width);
block_height += 8;
}
if (block_width == 0 || block_height == 0) {
goto empty;
}
// Ширина текста должна быть от 75%, до половины экрана, в зависимости от длины
const float div_x = US_MAX(US_MIN((100 / block_width * 2), 2.0), 1.5);
// Высоту тоже отрегулировать как-нибудь
const float div_y = US_MAX(US_MIN((70 / block_height * 2), 2.0), 1.5);
uint scale_x = frame->width / block_width / div_x;
uint scale_y = frame->height / block_height / div_y;
if (scale_x < scale_y / 1.5) { // Keep proportions
scale_y = scale_x * 1.5;
} else if (scale_y < scale_x * 1.5) {
scale_x = scale_y / 1.5;
}
strcpy(str, text);
const uint start_y = (frame->height >= (block_height * scale_y)
? ((frame->height - (block_height * scale_y)) / 2)
: 0);
uint n_line = 0;
while ((line = strtok_r((n_line == 0 ? str : NULL), "\n", &rest)) != NULL) {
const uint line_width = strlen(line) * 8 * scale_x;
const uint start_x = (frame->width >= line_width
? ((frame->width - line_width) / 2)
: 0);
_frametext_draw_line(ft, line, scale_x, scale_y, start_x, start_y + n_line * 8 * scale_y);
++n_line;
}
empty:
free(str);
}
void _frametext_draw_line(
us_frametext_s *ft,
const char *line,
uint scale_x,
uint scale_y,
uint start_x,
uint start_y
) {
us_frame_s *const frame = ft->frame;
const size_t len = strlen(line);
for (uint ch_y = 0; ch_y < 8 * scale_y; ++ch_y) {
const uint canvas_y = start_y + ch_y;
for (uint ch_x = 0; ch_x < 8 * len * scale_x; ++ch_x) {
if ((start_x + ch_x) >= frame->width) {
break;
}
const uint canvas_x = (start_x + ch_x) * 3;
const uint offset = canvas_y * frame->stride + canvas_x;
if (offset >= frame->used) {
break;
}
const u8 ch = US_MIN((u8)line[ch_x / 8 / scale_x], sizeof(US_FRAMETEXT_FONT) / 8 - 1);
const uint ch_byte = (ch_y / scale_y) % 8;
const uint ch_bit = (ch_x / scale_x) % 8;
const bool pix_on = !!(US_FRAMETEXT_FONT[ch][ch_byte] & (1 << ch_bit));
u8 *const r = &frame->data[offset];
u8 *const g = r + 1;
u8 *const b = r + 2;
*r = pix_on * 0x65; // RGB/BGR-friendly
*g = pix_on * 0x65;
*b = pix_on * 0x65;
}
}
}

39
src/libs/frametext.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 "types.h"
#include "frame.h"
typedef struct {
char *text;
us_frame_s *frame;
} us_frametext_s;
us_frametext_s *us_frametext_init(void);
void us_frametext_destroy(us_frametext_s *ft);
void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint height);

160
src/libs/frametext_font.c Normal file
View File

@@ -0,0 +1,160 @@
/*****************************************************************************
# #
# 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 "frametext_font.h"
#include "types.h"
const u8 US_FRAMETEXT_FONT[128][8] = {
// https://github.com/dhepper/font8x8/blob/master/font8x8_basic.h
// Author: Daniel Hepper <daniel@hepper.net>
// License: Public Domain
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space)
{0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!)
{0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (")
{0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#)
{0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($)
{0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%)
{0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&)
{0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (')
{0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (()
{0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ())
{0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*)
{0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,)
{0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.)
{0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/)
{0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0)
{0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1)
{0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2)
{0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3)
{0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4)
{0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5)
{0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6)
{0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7)
{0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8)
{0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9)
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:)
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;)
{0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<)
{0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=)
{0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>)
{0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?)
{0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@)
{0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A)
{0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B)
{0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C)
{0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D)
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E)
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F)
{0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G)
{0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H)
{0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I)
{0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J)
{0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K)
{0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L)
{0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M)
{0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N)
{0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O)
{0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P)
{0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q)
{0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R)
{0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S)
{0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T)
{0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U)
{0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V)
{0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W)
{0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X)
{0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y)
{0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z)
{0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([)
{0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\)
{0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (])
{0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_)
{0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`)
{0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a)
{0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b)
{0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c)
{0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d)
{0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e)
{0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f)
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g)
{0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h)
{0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i)
{0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j)
{0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k)
{0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l)
{0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m)
{0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n)
{0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o)
{0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p)
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q)
{0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r)
{0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s)
{0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t)
{0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u)
{0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v)
{0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w)
{0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x)
{0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y)
{0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z)
{0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({)
{0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|)
{0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (})
{0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007F
};

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,13 +22,7 @@
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include "types.h"
extern const unsigned US_BLANK_JPEG_WIDTH;
extern const unsigned US_BLANK_JPEG_HEIGHT;
extern const size_t US_BLANK_JPEG_DATA_SIZE;
extern const uint8_t US_BLANK_JPEG_DATA[];
extern const u8 US_FRAMETEXT_FONT[128][8];

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -25,9 +25,9 @@
#include <assert.h>
#define US_LIST_STRUCT(...) \
__VA_ARGS__ *prev; \
__VA_ARGS__ *next;
#define US_LIST_DECLARE \
void *prev; \
void *next;
#define US_LIST_ITERATE(x_first, x_item, ...) { \
for (__typeof__(x_first) x_item = x_first; x_item;) { \
@@ -42,10 +42,11 @@
x_first = x_item; \
} else { \
__typeof__(x_first) m_last = x_first; \
for (; m_last->next; m_last = m_last->next); \
for (; m_last->next != NULL; m_last = m_last->next); \
x_item->prev = m_last; \
m_last->next = x_item; \
} \
x_item->next = NULL; \
}
#define US_LIST_APPEND_C(x_first, x_item, x_count) { \
@@ -57,11 +58,15 @@
if (x_item->prev == NULL) { \
x_first = x_item->next; \
} else { \
x_item->prev->next = x_item->next; \
__typeof__(x_first) m_prev = x_item->prev; \
m_prev->next = x_item->next; \
} \
if (x_item->next != NULL) { \
x_item->next->prev = x_item->prev; \
__typeof__(x_first) m_next = x_item->next; \
m_next->prev = x_item->prev; \
} \
x_item->prev = NULL; \
x_item->next = NULL; \
}
#define US_LIST_REMOVE_C(x_first, x_item, x_count) { \

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,6 +22,10 @@
#include "logging.h"
#include <stdbool.h>
#include <pthread.h>
enum us_log_level_t us_g_log_level;

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -23,7 +23,6 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -33,6 +32,7 @@
#include <pthread.h>
#include "types.h"
#include "tools.h"
#include "threading.h"
@@ -75,7 +75,7 @@ extern pthread_mutex_t us_g_log_mutex;
#define US_SEP_INFO(x_ch) { \
US_LOGGING_LOCK; \
for (int m_count = 0; m_count < 80; ++m_count) { \
for (int m_i = 0; m_i < 80; ++m_i) { \
fputc((x_ch), stderr); \
} \
fputc('\n', stderr); \
@@ -91,7 +91,7 @@ extern pthread_mutex_t us_g_log_mutex;
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
us_thread_get_name(m_tname_buf); \
if (us_g_log_colored) { \
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,10 +22,27 @@
#include "memsink.h"
#include <stdatomic.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
us_memsink_s *us_memsink_init(
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "types.h"
#include "errors.h"
#include "tools.h"
#include "logging.h"
#include "frame.h"
#include "memsinksh.h"
us_memsink_s *us_memsink_init_opened(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
mode_t mode, bool rm, uint client_ttl, uint timeout) {
us_memsink_s *sink;
US_CALLOC(sink, 1);
@@ -40,6 +57,11 @@ us_memsink_s *us_memsink_init(
US_LOG_INFO("Using %s-sink: %s", name, obj);
if ((sink->data_size = us_memsink_calculate_size(obj)) == 0) {
US_LOG_ERROR("%s-sink: Invalid object suffix", name);
goto error;
}
const mode_t mask = umask(0);
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
umask(mask);
@@ -49,26 +71,25 @@ us_memsink_s *us_memsink_init(
goto error;
}
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s)) < 0) {
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s) + sink->data_size) < 0) {
US_LOG_PERROR("%s-sink: Can't truncate shared memory", name);
goto error;
}
if ((sink->mem = us_memsink_shared_map(sink->fd)) == NULL) {
if ((sink->mem = us_memsink_shared_map(sink->fd, sink->data_size)) == NULL) {
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
goto error;
}
return sink;
error:
us_memsink_destroy(sink);
return NULL;
error:
us_memsink_destroy(sink);
return NULL;
}
void us_memsink_destroy(us_memsink_s *sink) {
if (sink->mem != NULL) {
if (us_memsink_shared_unmap(sink->mem) < 0) {
if (us_memsink_shared_unmap(sink->mem, sink->data_size) < 0) {
US_LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
}
}
@@ -86,16 +107,35 @@ void us_memsink_destroy(us_memsink_s *sink) {
}
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
// Return true (the need to write to memsink) on any of these conditions:
// - EWOULDBLOCK - we have an active client;
// - Incorrect magic or version - need to first write;
// - We have some active clients by last_client_ts;
// - Frame meta differs (like size, format, but not timestamp).
// Если frame == NULL, то только проверяем наличие клиентов
// или необходимость инициализировать память.
assert(sink->server);
if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
// Если регион памяти не был инициализирован, то нужно что-то туда положить.
// Блокировка не нужна, потому что только сервер пишет в эти переменные.
return true;
}
const ldf unsafe_ts = sink->mem->last_client_ts;
if (unsafe_ts != sink->unsafe_last_client_ts) {
// Клиент пишет в синке свою отметку last_client_ts при любом действии.
// Мы не берем блокировку здесь, а просто проверяем, является ли это число тем же самым,
// что было прочитано нами в предыдущих итерациях. Значению не нужно быть консистентным,
// и даже если мы прочитали мусор из-за гонки в памяти между чтением здеси и записью
// из клиента, мы все равно можем сделать вывод, есть ли у нас клиенты вообще.
// Если число число поменялось то у нас точно есть клиенты и дальнейшие проверки
// проводить не требуется. Если же число неизменно, то стоит поставить блокировку
// и проверить, нужно ли записать что-нибудь в память для инициализации фрейма.
sink->unsafe_last_client_ts = unsafe_ts;
atomic_store(&sink->has_clients, true);
return true;
}
if (flock(sink->fd, LOCK_EX | LOCK_NB) < 0) {
if (errno == EWOULDBLOCK) {
// Есть живой клиент, который прямо сейчас взял блокировку и читает фрейм из синка
atomic_store(&sink->has_clients, true);
return true;
}
@@ -103,10 +143,7 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
return false;
}
if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
return true;
}
// Проверяем, есть ли у нас живой клиент по таймауту
const bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic());
atomic_store(&sink->has_clients, has_clients);
@@ -114,31 +151,39 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return false;
}
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
if (has_clients) {
return true;
}
if (frame != NULL && !US_FRAME_COMPARE_GEOMETRY(sink->mem, frame)) {
// Если есть изменения в геометрии/формате фрейма, то их тоже нобходимо сразу записать в синк
return true;
}
return false;
}
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested) {
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested) {
assert(sink->server);
const long double now = us_get_now_monotonic();
const ldf now = us_get_now_monotonic();
if (frame->used > US_MEMSINK_MAX_DATA) {
if (frame->used > sink->data_size) {
US_LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
sink->name, frame->used, US_MEMSINK_MAX_DATA);
return 0; // -2
sink->name, frame->used, sink->data_size);
return 0;
}
if (us_flock_timedwait_monotonic(sink->fd, 1) == 0) {
US_LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
sink->last_id = us_get_now_id();
sink->mem->id = sink->last_id;
sink->mem->id = us_get_now_id();
if (sink->mem->key_requested && frame->key) {
sink->mem->key_requested = false;
}
*key_requested = sink->mem->key_requested;
if (key_requested != NULL) { // We don't need it for non-H264 sinks
*key_requested = sink->mem->key_requested;
}
memcpy(sink->mem->data, frame->data, frame->used);
memcpy(us_memsink_get_data(sink->mem), frame->data, frame->used);
sink->mem->used = frame->used;
US_FRAME_COPY_META(frame, sink->mem);
@@ -164,42 +209,52 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *con
return 0;
}
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key_requested, bool key_required) { // cppcheck-suppress unusedFunction
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required) {
assert(!sink->server); // Client only
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
if (errno == EWOULDBLOCK) {
return -2;
return US_ERROR_NO_DATA;
}
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return -1;
}
int retval = -2; // Not updated
if (sink->mem->magic == US_MEMSINK_MAGIC) {
if (sink->mem->version != US_MEMSINK_VERSION) {
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
sink->name, sink->mem->version, US_MEMSINK_VERSION);
retval = -1;
goto done;
}
if (sink->mem->id != sink->last_id) { // When updated
sink->last_id = sink->mem->id;
us_frame_set_data(frame, sink->mem->data, sink->mem->used);
US_FRAME_COPY_META(sink->mem, frame);
*key_requested = sink->mem->key_requested;
retval = 0;
}
sink->mem->last_client_ts = us_get_now_monotonic();
if (key_required) {
sink->mem->key_requested = true;
}
int retval = 0;
if (sink->mem->magic != US_MEMSINK_MAGIC) {
retval = US_ERROR_NO_DATA; // Not updated
goto done;
}
if (sink->mem->version != US_MEMSINK_VERSION) {
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
sink->name, sink->mem->version, US_MEMSINK_VERSION);
retval = -1;
goto done;
}
done:
if (flock(sink->fd, LOCK_UN) < 0) {
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1;
}
return retval;
// Let the sink know that the client is alive
sink->mem->last_client_ts = us_get_now_monotonic();
if (sink->mem->id == sink->last_readed_id) {
retval = US_ERROR_NO_DATA; // Not updated
goto done;
}
sink->last_readed_id = sink->mem->id;
us_frame_set_data(frame, us_memsink_get_data(sink->mem), sink->mem->used);
US_FRAME_COPY_META(sink->mem, frame);
if (key_requested != NULL) { // We don't need it for non-H264 sinks
*key_requested = sink->mem->key_requested;
}
if (key_required) {
sink->mem->key_requested = true;
}
done:
if (flock(sink->fd, LOCK_UN) < 0) {
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
retval = -1;
}
return retval;
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,47 +22,41 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "tools.h"
#include "logging.h"
#include "types.h"
#include "frame.h"
#include "memsinksh.h"
typedef struct {
const char *name;
const char *obj;
bool server;
bool rm;
unsigned client_ttl; // Only for server
unsigned timeout;
const char *name;
const char *obj;
uz data_size;
bool server;
bool rm;
uint client_ttl; // Only for server
uint timeout;
int fd;
us_memsink_shared_s *mem;
uint64_t last_id;
atomic_bool has_clients; // Only for server
u64 last_readed_id; // Only for client
atomic_bool has_clients; // Only for server results
ldf unsafe_last_client_ts; // Only for server
} us_memsink_s;
us_memsink_s *us_memsink_init(
us_memsink_s *us_memsink_init_opened(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
mode_t mode, bool rm, uint client_ttl, uint timeout);
void us_memsink_destroy(us_memsink_s *sink);
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested);
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested);
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key_requested, bool key_required);
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required);

72
src/libs/memsinksh.c Normal file
View File

@@ -0,0 +1,72 @@
/*****************************************************************************
# #
# 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 "memsinksh.h"
#include <string.h>
#include <strings.h>
#include <assert.h>
#include <sys/mman.h>
#include "types.h"
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) {
us_memsink_shared_s *mem = mmap(
NULL,
sizeof(us_memsink_shared_s) + data_size,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd, 0);
if (mem == MAP_FAILED) {
return NULL;
}
assert(mem != NULL);
return mem;
}
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size) {
assert(mem != NULL);
return munmap(mem, sizeof(us_memsink_shared_s) + data_size);
}
uz us_memsink_calculate_size(const char *obj) {
const char *ptr = strrchr(obj, ':');
if (ptr == NULL) {
ptr = strrchr(obj, '.');
}
if (ptr != NULL) {
ptr += 1;
if (!strcasecmp(ptr, "jpeg")) {
return 4 * 1024 * 1024;
} else if (!strcasecmp(ptr, "h264")) {
return 2 * 1024 * 1024;
} else if (!strcasecmp(ptr, "raw")) {
return 1920 * 1200 * 3; // RGB
}
}
return 0;
}
u8 *us_memsink_get_data(us_memsink_shared_s *mem) {
return (u8*)(mem) + sizeof(us_memsink_shared_s);
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,65 +22,29 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/mman.h>
#include "types.h"
#include "frame.h"
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((uint32_t)4)
#ifndef US_CFG_MEMSINK_MAX_DATA
# define US_CFG_MEMSINK_MAX_DATA 33554432
#endif
#define US_MEMSINK_MAX_DATA ((size_t)(US_CFG_MEMSINK_MAX_DATA))
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((u32)7)
typedef struct {
uint64_t magic;
uint32_t version;
u64 magic;
u32 version;
u64 id;
uz used;
uint64_t id;
ldf last_client_ts;
bool key_requested;
size_t used;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
bool online;
bool key;
unsigned gop;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
long double last_client_ts;
bool key_requested;
uint8_t data[US_MEMSINK_MAX_DATA];
US_FRAME_META_DECLARE;
} us_memsink_shared_s;
INLINE us_memsink_shared_s *us_memsink_shared_map(int fd) {
us_memsink_shared_s *mem = mmap(
NULL,
sizeof(us_memsink_shared_s),
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0
);
if (mem == MAP_FAILED) {
return NULL;
}
assert(mem != NULL);
return mem;
}
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size);
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size);
INLINE int us_memsink_shared_unmap(us_memsink_shared_s *mem) {
assert(mem != NULL);
return munmap(mem, sizeof(us_memsink_shared_s));
}
uz us_memsink_calculate_size(const char *obj);
u8 *us_memsink_get_data(us_memsink_shared_s *mem);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,17 +22,24 @@
#include "options.h"
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>
void us_build_short_options(const struct option opts[], char *short_opts, size_t size) {
#include "types.h"
void us_build_short_options(const struct option opts[], char *short_opts, uz size) {
memset(short_opts, 0, size);
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
assert(short_index < size - 3);
if (isalpha(opts[opt_index].val)) {
short_opts[short_index] = opts[opt_index].val;
++short_index;
if (opts[opt_index].has_arg == required_argument) {
short_opts[short_index] = ':';
++short_index;
for (uint short_i = 0, opt_i = 0; opts[opt_i].name != NULL; ++opt_i) {
assert(short_i < size - 3);
if (isalpha(opts[opt_i].val)) {
short_opts[short_i] = opts[opt_i].val;
++short_i;
if (opts[opt_i].has_arg == required_argument) {
short_opts[short_i] = ':';
++short_i;
}
}
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,12 +22,9 @@
#pragma once
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>
#include <sys/types.h>
#include "types.h"
void us_build_short_options(const struct option opts[], char *short_opts, size_t size);
void us_build_short_options(const struct option opts[], char *short_opts, uz size);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -25,16 +25,8 @@
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#if defined(__linux__)
# define HAS_PDEATHSIG
#elif defined(__FreeBSD__)
#if defined(__FreeBSD__)
# include <sys/param.h>
# if __FreeBSD_version >= 1102000
# define HAS_PDEATHSIG
# endif
#endif
@@ -51,19 +43,22 @@
# error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0
# endif
#endif
#ifdef HAS_PDEATHSIG
#ifdef WITH_PDEATHSIG
# if defined(__linux__)
# include <sys/prctl.h>
# elif defined(__FreeBSD__)
# elif defined(__FreeBSD__) && (__FreeBSD_version >= 1102000)
# include <sys/procctl.h>
# else
# error WITH_PDEATHSIG is not supported on your system
# endif
#endif
#include "types.h"
#ifdef WITH_SETPROCTITLE
# include "tools.h"
#endif
#ifdef HAS_PDEATHSIG
# include "logging.h"
#endif
#include "logging.h"
#ifdef WITH_SETPROCTITLE
@@ -71,18 +66,18 @@ extern char **environ;
#endif
#ifdef HAS_PDEATHSIG
#ifdef WITH_PDEATHSIG
INLINE int us_process_track_parent_death(void) {
const pid_t parent = getppid();
int signum = SIGTERM;
# if defined(__linux__)
const int retval = prctl(PR_SET_PDEATHSIG, signum);
const int result = prctl(PR_SET_PDEATHSIG, signum);
# elif defined(__FreeBSD__)
const int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
const int result = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
# else
# error WTF?
# endif
if (retval < 0) {
if (result < 0) {
US_LOG_PERROR("Can't set to receive SIGTERM on parent process death");
return -1;
}
@@ -102,21 +97,21 @@ INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefi
# pragma GCC diagnostic pop
char *cmdline = NULL;
size_t allocated = 2048;
size_t used = 0;
uz allocated = 2048;
uz used = 0;
US_REALLOC(cmdline, allocated);
cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) {
size_t arg_len = strlen(argv[index]);
for (int i = 0; i < argc; ++i) {
uz arg_len = strlen(argv[i]);
if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048;
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
}
strcat(cmdline, " ");
strcat(cmdline, argv[index]);
strcat(cmdline, argv[i]);
used = strlen(cmdline); // Не считаем вручную, так надежнее
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,29 +22,39 @@
#include "queue.h"
#include <errno.h>
#include <time.h>
#include <assert.h>
us_queue_s *us_queue_init(unsigned capacity) {
us_queue_s *queue;
US_CALLOC(queue, 1);
US_CALLOC(queue->items, capacity);
queue->capacity = capacity;
US_MUTEX_INIT(queue->mutex);
#include <pthread.h>
#include "types.h"
#include "tools.h"
#include "threading.h"
us_queue_s *us_queue_init(uint capacity) {
us_queue_s *q;
US_CALLOC(q, 1);
US_CALLOC(q->items, capacity);
q->capacity = capacity;
US_MUTEX_INIT(q->mutex);
pthread_condattr_t attrs;
assert(!pthread_condattr_init(&attrs));
assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
assert(!pthread_cond_init(&queue->full_cond, &attrs));
assert(!pthread_cond_init(&queue->empty_cond, &attrs));
assert(!pthread_cond_init(&q->full_cond, &attrs));
assert(!pthread_cond_init(&q->empty_cond, &attrs));
assert(!pthread_condattr_destroy(&attrs));
return queue;
return q;
}
void us_queue_destroy(us_queue_s *queue) {
US_COND_DESTROY(queue->empty_cond);
US_COND_DESTROY(queue->full_cond);
US_MUTEX_DESTROY(queue->mutex);
free(queue->items);
free(queue);
void us_queue_destroy(us_queue_s *q) {
US_COND_DESTROY(q->empty_cond);
US_COND_DESTROY(q->full_cond);
US_MUTEX_DESTROY(q->mutex);
free(q->items);
free(q);
}
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \
@@ -52,51 +62,51 @@ void us_queue_destroy(us_queue_s *queue) {
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
while (x_var) { \
const int err = pthread_cond_timedwait(&(x_cond), &queue->mutex, &m_ts); \
const int err = pthread_cond_timedwait(&(x_cond), &q->mutex, &m_ts); \
if (err == ETIMEDOUT) { \
US_MUTEX_UNLOCK(queue->mutex); \
US_MUTEX_UNLOCK(q->mutex); \
return -1; \
} \
assert(!err); \
} \
}
int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
US_MUTEX_LOCK(queue->mutex);
int us_queue_put(us_queue_s *q, void *item, ldf timeout) {
US_MUTEX_LOCK(q->mutex);
if (timeout == 0) {
if (queue->size == queue->capacity) {
US_MUTEX_UNLOCK(queue->mutex);
if (q->size == q->capacity) {
US_MUTEX_UNLOCK(q->mutex);
return -1;
}
} else {
_WAIT_OR_UNLOCK(queue->size == queue->capacity, queue->full_cond);
_WAIT_OR_UNLOCK(q->size == q->capacity, q->full_cond);
}
queue->items[queue->in] = item;
++queue->size;
++queue->in;
queue->in %= queue->capacity;
US_MUTEX_UNLOCK(queue->mutex);
US_COND_BROADCAST(queue->empty_cond);
q->items[q->in] = item;
++q->size;
++q->in;
q->in %= q->capacity;
US_MUTEX_UNLOCK(q->mutex);
US_COND_BROADCAST(q->empty_cond);
return 0;
}
int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
US_MUTEX_LOCK(queue->mutex);
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond);
*item = queue->items[queue->out];
--queue->size;
++queue->out;
queue->out %= queue->capacity;
US_MUTEX_UNLOCK(queue->mutex);
US_COND_BROADCAST(queue->full_cond);
int us_queue_get(us_queue_s *q, void **item, ldf timeout) {
US_MUTEX_LOCK(q->mutex);
_WAIT_OR_UNLOCK(q->size == 0, q->empty_cond);
*item = q->items[q->out];
--q->size;
++q->out;
q->out %= q->capacity;
US_MUTEX_UNLOCK(q->mutex);
US_COND_BROADCAST(q->full_cond);
return 0;
}
#undef _WAIT_OR_UNLOCK
int us_queue_get_free(us_queue_s *queue) {
US_MUTEX_LOCK(queue->mutex);
const unsigned size = queue->size;
US_MUTEX_UNLOCK(queue->mutex);
return queue->capacity - size;
bool us_queue_is_empty(us_queue_s *q) {
US_MUTEX_LOCK(q->mutex);
const uint size = q->size;
US_MUTEX_UNLOCK(q->mutex);
return (bool)(q->capacity - size);
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,24 +22,20 @@
#pragma once
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "types.h"
#include "tools.h"
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
typedef struct {
void **items;
unsigned size;
unsigned capacity;
unsigned in;
unsigned out;
uint size;
uint capacity;
uint in;
uint out;
pthread_mutex_t mutex;
pthread_cond_t full_cond;
@@ -47,22 +43,22 @@ typedef struct {
} us_queue_s;
#define US_QUEUE_DELETE_WITH_ITEMS(x_queue, x_free_item) { \
if (x_queue) { \
while (!us_queue_get_free(x_queue)) { \
#define US_QUEUE_DELETE_WITH_ITEMS(x_q, x_free_item) { \
if (x_q) { \
while (!us_queue_is_empty(x_q)) { \
void *m_ptr; \
if (!us_queue_get(x_queue, &m_ptr, 0)) { \
if (!us_queue_get(x_q, &m_ptr, 0)) { \
US_DELETE(m_ptr, x_free_item); \
} \
} \
us_queue_destroy(x_queue); \
us_queue_destroy(x_q); \
} \
}
us_queue_s *us_queue_init(unsigned capacity);
void us_queue_destroy(us_queue_s *queue);
us_queue_s *us_queue_init(uint capacity);
void us_queue_destroy(us_queue_s *q);
int us_queue_put(us_queue_s *queue, void *item, long double timeout);
int us_queue_get(us_queue_s *queue, void **item, long double timeout);
int us_queue_get_free(us_queue_s *queue);
int us_queue_put(us_queue_s *q, void *item, ldf timeout);
int us_queue_get(us_queue_s *q, void **item, ldf timeout);
bool us_queue_is_empty(us_queue_s *q);

86
src/libs/ring.c Normal file
View File

@@ -0,0 +1,86 @@
/*****************************************************************************
# #
# 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 <assert.h>
#include "ring.h"
#include "types.h"
#include "tools.h"
#include "queue.h"
int _acquire(us_ring_s *ring, us_queue_s *q, ldf timeout);
void _release(us_ring_s *ring, us_queue_s *q, uint ri);
us_ring_s *us_ring_init(uint capacity) {
us_ring_s *ring;
US_CALLOC(ring, 1);
US_CALLOC(ring->items, capacity);
US_CALLOC(ring->places, capacity);
ring->capacity = capacity;
ring->producer = us_queue_init(capacity);
ring->consumer = us_queue_init(capacity);
for (uint ri = 0; ri < capacity; ++ri) {
ring->places[ri] = ri; // XXX: Just to avoid casting between pointer and uint
assert(!us_queue_put(ring->producer, (void*)(ring->places + ri), 0));
}
return ring;
}
void us_ring_destroy(us_ring_s *ring) {
us_queue_destroy(ring->consumer);
us_queue_destroy(ring->producer);
free(ring->places);
free(ring->items);
free(ring);
}
int us_ring_producer_acquire(us_ring_s *ring, ldf timeout) {
return _acquire(ring, ring->producer, timeout);
}
void us_ring_producer_release(us_ring_s *ring, uint ri) {
_release(ring, ring->consumer, ri);
}
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout) {
return _acquire(ring, ring->consumer, timeout);
}
void us_ring_consumer_release(us_ring_s *ring, uint ri) {
_release(ring, ring->producer, ri);
}
int _acquire(us_ring_s *ring, us_queue_s *q, ldf timeout) {
(void)ring;
uint *place;
if (us_queue_get(q, (void**)&place, timeout) < 0) {
return -1;
}
return *place;
}
void _release(us_ring_s *ring, us_queue_s *q, uint ri) {
assert(!us_queue_put(q, (void*)(ring->places + ri), 0));
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,50 +22,42 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <assert.h>
#include <sys/types.h>
#include <pthread.h>
#include <alsa/asoundlib.h>
#include <speex/speex_resampler.h>
#include <opus/opus.h>
#include "uslibs/tools.h"
#include "uslibs/array.h"
#include "uslibs/threading.h"
#include "logging.h"
#include "types.h"
#include "queue.h"
typedef struct {
snd_pcm_t *pcm;
unsigned pcm_hz;
unsigned pcm_frames;
size_t pcm_size;
snd_pcm_hw_params_t *pcm_params;
SpeexResamplerState *res;
OpusEncoder *enc;
us_queue_s *pcm_queue;
us_queue_s *enc_queue;
uint32_t pts;
pthread_t pcm_tid;
pthread_t enc_tid;
bool tids_created;
atomic_bool stop;
} us_audio_s;
uz capacity;
void **items;
uint *places;
us_queue_s *producer;
us_queue_s *consumer;
} us_ring_s;
bool us_audio_probe(const char *name);
#define US_RING_INIT_WITH_ITEMS(x_ring, x_capacity, x_init_item) { \
(x_ring) = us_ring_init(x_capacity); \
for (uz m_ri = 0; m_ri < (x_ring)->capacity; ++m_ri) { \
(x_ring)->items[m_ri] = x_init_item(); \
} \
}
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
void us_audio_destroy(us_audio_s *audio);
#define US_RING_DELETE_WITH_ITEMS(x_ring, x_destroy_item) { \
if (x_ring) { \
for (uz m_ri = 0; m_ri < (x_ring)->capacity; ++m_ri) { \
x_destroy_item((x_ring)->items[m_ri]); \
} \
us_ring_destroy(x_ring); \
} \
}
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
us_ring_s *us_ring_init(uint capacity);
void us_ring_destroy(us_ring_s *ring);
int us_ring_producer_acquire(us_ring_s *ring, ldf timeout);
void us_ring_producer_release(us_ring_s *ring, uint ri);
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout);
void us_ring_consumer_release(us_ring_s *ring, uint ri);

82
src/libs/signal.c Normal file
View File

@@ -0,0 +1,82 @@
/*****************************************************************************
# #
# 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 "signal.h"
#include <string.h>
#include <signal.h>
#include <assert.h>
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
# define HAS_SIGABBREV_NP
#endif
#include "types.h"
#include "tools.h"
#include "logging.h"
char *us_signum_to_string(int signum) {
# ifdef HAS_SIGABBREV_NP
const char *const name = sigabbrev_np(signum);
# else
const char *const name = (
signum == SIGTERM ? "TERM" :
signum == SIGINT ? "INT" :
signum == SIGPIPE ? "PIPE" :
NULL
);
# endif
char *buf;
if (name != NULL) {
US_ASPRINTF(buf, "SIG%s", name);
} else {
US_ASPRINTF(buf, "SIG[%d]", signum);
}
return buf;
}
void us_install_signals_handler(us_signal_handler_f handler, bool ignore_sigpipe) {
struct sigaction sig_act = {0};
assert(!sigemptyset(&sig_act.sa_mask));
sig_act.sa_handler = handler;
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
if (!ignore_sigpipe) {
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
}
US_LOG_DEBUG("Installing SIGINT handler ...");
assert(!sigaction(SIGINT, &sig_act, NULL));
US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGTERM, &sig_act, NULL));
if (!ignore_sigpipe) {
US_LOG_DEBUG("Installing SIGPIPE handler ...");
assert(!sigaction(SIGPIPE, &sig_act, NULL));
} else {
US_LOG_DEBUG("Ignoring SIGPIPE ...");
assert(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
}
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,14 +22,11 @@
#pragma once
#include <string.h>
#include <errno.h>
#include <event2/util.h>
#include <event2/bufferevent.h>
#include "../../libs/tools.h"
#include "../../libs/logging.h"
#include "types.h"
char *us_bufferevent_format_reason(short what);
typedef void (*us_signal_handler_f)(int);
char *us_signum_to_string(int signum);
void us_install_signals_handler(us_signal_handler_f handler, bool ignore_sigpipe);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -20,27 +20,35 @@
*****************************************************************************/
#include "uri.h"
#include "tc358743.h"
#include <unistd.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "types.h"
#include "tools.h"
#include "xioctl.h"
bool us_uri_get_true(struct evkeyvalq *params, const char *key) {
const char *value_str = evhttp_find_header(params, key);
if (value_str != NULL) {
if (
value_str[0] == '1'
|| !evutil_ascii_strcasecmp(value_str, "true")
|| !evutil_ascii_strcasecmp(value_str, "yes")
) {
return true;
}
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz) {
*audio_hz = 0;
struct v4l2_control ctl = {.id = TC358743_CID_AUDIO_PRESENT};
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
return -1;
}
return false;
}
char *us_uri_get_string(struct evkeyvalq *params, const char *key) {
const char *const value_str = evhttp_find_header(params, key);
if (value_str != NULL) {
return evhttp_encode_uri(value_str);
if (!ctl.value) {
return 0; // No audio
}
return NULL;
US_MEMSET_ZERO(ctl);
ctl.id = TC358743_CID_AUDIO_SAMPLING_RATE;
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
return -1;
}
*audio_hz = ctl.value;
return 0;
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -20,45 +20,28 @@
*****************************************************************************/
#include "tc358743.h"
#pragma once
#include <linux/v4l2-controls.h>
#include "types.h"
#ifndef V4L2_CID_USER_TC358743_BASE
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
#endif
#ifndef TC358743_CID_AUDIO_PRESENT
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
#endif
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
#endif
#ifndef TC358743_CID_AUDIO_PRESENT
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
#endif
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
US_MEMSET_ZERO(*info);
#ifndef TC358743_CID_LANES_ENOUGH
# define TC358743_CID_LANES_ENOUGH (V4L2_CID_USER_TC358743_BASE + 2)
#endif
int fd = -1;
if ((fd = open(path, O_RDWR)) < 0) {
US_JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
return -1;
}
# define READ_CID(x_cid, x_field) { \
struct v4l2_control m_ctl = {0}; \
m_ctl.id = x_cid; \
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
close(fd); \
return -1; \
} \
info->x_field = m_ctl.value; \
}
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
READ_CID(TC358743_CID_AUDIO_SAMPLING_RATE, audio_hz);
# undef READ_CID
close(fd);
return 0;
}
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -24,9 +24,9 @@
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>
@@ -37,13 +37,14 @@
# endif
#endif
#include "types.h"
#include "tools.h"
#ifdef PTHREAD_MAX_NAMELEN_NP
# define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
# define US_THREAD_NAME_SIZE ((uz)(PTHREAD_MAX_NAMELEN_NP))
#else
# define US_MAX_THREAD_NAME ((size_t)16)
# define US_THREAD_NAME_SIZE ((uz)16)
#endif
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
@@ -51,14 +52,19 @@
#ifdef WITH_PTHREAD_NP
# define US_THREAD_RENAME(x_fmt, ...) { \
char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
char m_new_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
US_SNPRINTF(m_new_tname_buf, (US_THREAD_NAME_SIZE - 1), (x_fmt), ##__VA_ARGS__); \
us_thread_set_name(m_new_tname_buf); \
}
#else
# define US_THREAD_RENAME(_fmt, ...)
# define US_THREAD_RENAME(x_fmt, ...)
#endif
#define US_THREAD_SETTLE(x_fmt, ...) { \
US_THREAD_RENAME((x_fmt), ##__VA_ARGS__); \
us_thread_block_signals(); \
}
#define US_MUTEX_INIT(x_mutex) assert(!pthread_mutex_init(&(x_mutex), NULL))
#define US_MUTEX_DESTROY(x_mutex) assert(!pthread_mutex_destroy(&(x_mutex)))
#define US_MUTEX_LOCK(x_mutex) assert(!pthread_mutex_lock(&(x_mutex)))
@@ -78,7 +84,7 @@ INLINE void us_thread_set_name(const char *name) {
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
pthread_set_name_np(pthread_self(), name);
# elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void *)name);
pthread_setname_np(pthread_self(), "%s", (void*)name);
# else
# error us_thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif
@@ -89,12 +95,12 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
#ifdef WITH_PTHREAD_NP
int retval = -1;
# if defined(__linux__) || defined (__NetBSD__)
retval = pthread_getname_np(pthread_self(), name, US_MAX_THREAD_NAME);
retval = pthread_getname_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
# elif \
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|| defined(__DragonFly__)
pthread_get_name_np(pthread_self(), name, US_MAX_THREAD_NAME);
pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
if (name[0] != '\0') {
retval = 0;
}
@@ -107,7 +113,9 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
#if defined(__linux__)
const pid_t tid = syscall(SYS_gettid);
#elif defined(__FreeBSD__)
const pid_t tid = syscall(SYS_thr_self);
long id;
assert(!syscall(SYS_thr_self, &id));
const pid_t tid = id;
#elif defined(__OpenBSD__)
const pid_t tid = syscall(SYS_getthrid);
#elif defined(__NetBSD__)
@@ -118,9 +126,17 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
const pid_t tid = 0; // Makes cppcheck happy
# warning gettid() not implemented
#endif
assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
US_SNPRINTF(name, (US_THREAD_NAME_SIZE - 1), "tid=%d", tid);
#ifdef WITH_PTHREAD_NP
}
#endif
}
INLINE void us_thread_block_signals(void) {
sigset_t mask;
assert(!sigemptyset(&mask));
assert(!sigaddset(&mask, SIGINT));
assert(!sigaddset(&mask, SIGTERM));
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -23,26 +23,19 @@
#pragma once
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <locale.h>
#include <locale.h> // Make C locale for strerror_l()
#include <errno.h>
#include <math.h>
#include <time.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/file.h>
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
# define HAS_SIGABBREV_NP
#else
# include <signal.h>
#endif
#include "types.h"
#ifdef NDEBUG
@@ -60,10 +53,34 @@
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); x_dest = NULL; } }
#define US_CLOSE_FD(x_dest) { if (x_dest >= 0) { close(x_dest); x_dest = -1; } }
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) assert(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
#define US_MIN(x_a, x_b) ({ \
__typeof__(x_a) m_a = (x_a); \
__typeof__(x_b) m_b = (x_b); \
(m_a < m_b ? m_a : m_b); \
})
#define US_MAX(x_a, x_b) ({ \
__typeof__(x_a) m_a = (x_a); \
__typeof__(x_b) m_b = (x_b); \
(m_a > m_b ? m_a : m_b); \
})
#define US_ONCE_FOR(x_once, x_value, ...) { \
const int m_reported = (x_value); \
if (m_reported != (x_once)) { \
__VA_ARGS__; \
(x_once) = m_reported; \
} \
}
#define US_ONCE(...) US_ONCE_FOR(once, __LINE__, ##__VA_ARGS__)
INLINE char *us_strdup(const char *str) {
@@ -76,23 +93,15 @@ INLINE const char *us_bool_to_string(bool flag) {
return (flag ? "true" : "false");
}
INLINE size_t us_align_size(size_t size, size_t to) {
INLINE uz us_align_size(uz size, uz to) {
return ((size + (to - 1)) & ~(to - 1));
}
INLINE unsigned us_min_u(unsigned a, unsigned b) {
return (a < b ? a : b);
INLINE sll us_floor_ms(ldf now) {
return (sll)now - (now < (sll)now); // floor()
}
INLINE unsigned us_max_u(unsigned a, unsigned b) {
return (a > b ? a : b);
}
INLINE long long us_floor_ms(long double now) {
return (long long)now - (now < (long long)now); // floor()
}
INLINE uint32_t us_triple_u32(uint32_t x) {
INLINE u32 us_triple_u32(u32 x) {
// https://nullprogram.com/blog/2018/07/31/
x ^= x >> 17;
x *= UINT32_C(0xED5AD4BB);
@@ -116,38 +125,38 @@ INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
}
}
INLINE long double us_get_now_monotonic(void) {
INLINE ldf us_get_now_monotonic(void) {
time_t sec;
long msec;
us_get_now(CLOCK_MONOTONIC, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
return (ldf)sec + ((ldf)msec) / 1000;
}
INLINE uint64_t us_get_now_monotonic_u64(void) {
INLINE u64 us_get_now_monotonic_u64(void) {
struct timespec ts;
assert(!clock_gettime(CLOCK_MONOTONIC, &ts));
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
return (u64)(ts.tv_nsec / 1000) + (u64)ts.tv_sec * 1000000;
}
INLINE uint64_t us_get_now_id(void) {
const uint64_t now = us_get_now_monotonic_u64();
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
INLINE u64 us_get_now_id(void) {
const u64 now = us_get_now_monotonic_u64();
return (u64)us_triple_u32(now) | ((u64)us_triple_u32(now + 12345) << 32);
}
INLINE long double us_get_now_real(void) {
INLINE ldf us_get_now_real(void) {
time_t sec;
long msec;
us_get_now(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
return (ldf)sec + ((ldf)msec) / 1000;
}
INLINE unsigned us_get_cores_available(void) {
INLINE uint us_get_cores_available(void) {
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
return us_max_u(us_min_u(cores_sysconf, 4), 1);
return US_MAX(US_MIN(cores_sysconf, 4), 1);
}
INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
INLINE void us_ld_to_timespec(ldf ld, struct timespec *ts) {
ts->tv_sec = (long)ld;
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
if (ts->tv_nsec > 999999999L) {
@@ -156,12 +165,12 @@ INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
}
}
INLINE long double us_timespec_to_ld(const struct timespec *ts) {
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
INLINE ldf us_timespec_to_ld(const struct timespec *ts) {
return ts->tv_sec + ((ldf)ts->tv_nsec) / 1000000000;
}
INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
const long double deadline_ts = us_get_now_monotonic() + timeout;
INLINE int us_flock_timedwait_monotonic(int fd, ldf timeout) {
const ldf deadline_ts = us_get_now_monotonic() + timeout;
int retval = -1;
while (true) {
@@ -177,33 +186,26 @@ INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
}
INLINE char *us_errno_to_string(int error) {
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
char *buf;
if (locale) {
buf = us_strdup(strerror_l(error, locale));
freelocale(locale);
} else {
buf = us_strdup("!!! newlocale() error !!!");
# if (_POSIX_C_SOURCE >= 200112L) && !defined(_GNU_SOURCE) // XSI
char buf[2048];
const uz max_len = sizeof(buf) - 1;
if (strerror_r(error, buf, max_len) != 0) {
US_SNPRINTF(buf, max_len, "Errno = %d", error);
}
return buf;
}
return us_strdup(buf);
INLINE char *us_signum_to_string(int signum) {
# ifdef HAS_SIGABBREV_NP
const char *const name = sigabbrev_np(signum);
# else
const char *const name = (
signum == SIGTERM ? "TERM" :
signum == SIGINT ? "INT" :
signum == SIGPIPE ? "PIPE" :
NULL
);
# endif
char *buf;
if (name != NULL) {
US_ASPRINTF(buf, "SIG%s", name);
} else {
US_ASPRINTF(buf, "SIG[%d]", signum);
# elif defined(__GLIBC__) && defined(_GNU_SOURCE) // GNU
char buf[2048];
const uz max_len = sizeof(buf) - 1;
return us_strdup(strerror_r(error, buf, max_len));
# else // BSD
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
if (locale) {
char *ptr = us_strdup(strerror_l(error, locale));
freelocale(locale);
return ptr;
}
return buf;
return us_strdup("!!! newlocale() error !!!");
# endif
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -19,28 +19,28 @@
# #
*****************************************************************************/
#pragma once
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "uslibs/tools.h"
#include "uslibs/xioctl.h"
typedef long long sll;
typedef ssize_t sz;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
#include "logging.h"
typedef unsigned uint;
typedef unsigned long long ull;
typedef size_t uz;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef struct {
bool has_audio;
unsigned audio_hz;
} us_tc358743_info_s;
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info);
typedef long double ldf;

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,6 +22,17 @@
#include "unjpeg.h"
#include <stdio.h>
#include <setjmp.h>
#include <assert.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "types.h"
#include "logging.h"
#include "frame.h"
typedef struct {
struct jpeg_error_mgr mgr; // Default manager
@@ -43,7 +54,7 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
_jpeg_error_manager_s jpeg_error;
jpeg.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error);
jpeg.err = jpeg_std_error((struct jpeg_error_mgr*)&jpeg_error);
jpeg_error.mgr.error_exit = _jpeg_error_handler;
jpeg_error.frame = src;
if (setjmp(jpeg_error.jmp) < 0) {
@@ -57,16 +68,16 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
jpeg_start_decompress(&jpeg);
us_frame_copy_meta(src, dest);
dest->format = V4L2_PIX_FMT_RGB24;
dest->width = jpeg.output_width;
dest->height = jpeg.output_height;
dest->stride = jpeg.output_width * jpeg.output_components; // Row stride
dest->used = 0;
US_FRAME_COPY_META(src, dest); // cppcheck-suppress redundantAssignment
dest->format = V4L2_PIX_FMT_RGB24; // cppcheck-suppress redundantAssignment
dest->width = jpeg.output_width; // cppcheck-suppress redundantAssignment
dest->height = jpeg.output_height; // cppcheck-suppress redundantAssignment
dest->stride = jpeg.output_width * jpeg.output_components; // cppcheck-suppress redundantAssignment
dest->used = 0; // cppcheck-suppress redundantAssignment
if (decode) {
JSAMPARRAY scanlines;
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1);
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr)&jpeg, JPOOL_IMAGE, dest->stride, 1);
us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
while (jpeg.output_scanline < jpeg.output_height) {
@@ -77,13 +88,13 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
jpeg_finish_decompress(&jpeg);
}
done:
jpeg_destroy_decompress(&jpeg);
return retval;
done:
jpeg_destroy_decompress(&jpeg);
return retval;
}
static void _jpeg_error_handler(j_common_ptr jpeg) {
_jpeg_error_manager_s *jpeg_error = (_jpeg_error_manager_s *)jpeg->err;
_jpeg_error_manager_s *jpeg_error = (_jpeg_error_manager_s*)jpeg->err;
char msg[JMSG_LENGTH_MAX];
(*jpeg_error->mgr.format_message)(jpeg, msg);

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,18 +22,7 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <setjmp.h>
#include <assert.h>
#include <sys/types.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "logging.h"
#include "types.h"
#include "frame.h"

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -26,11 +26,13 @@
#include <sys/ioctl.h>
#include "types.h"
#ifndef US_CFG_XIOCTL_RETRIES
# define US_CFG_XIOCTL_RETRIES 4
#endif
#define _XIOCTL_RETRIES ((unsigned)(US_CFG_XIOCTL_RETRIES))
#define _XIOCTL_RETRIES ((uint)(US_CFG_XIOCTL_RETRIES))
INLINE int us_xioctl(int fd, int request, void *arg) {

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,84 +22,31 @@
#include "blank.h"
#include "../libs/types.h"
#include "../libs/tools.h"
#include "../libs/frame.h"
#include "../libs/frametext.h"
static us_frame_s *_init_internal(void);
static us_frame_s *_init_external(const char *path);
#include "encoders/cpu/encoder.h"
us_frame_s *us_blank_frame_init(const char *path) {
us_frame_s *blank = NULL;
if (path && path[0] != '\0') {
blank = _init_external(path);
}
if (blank != NULL) {
US_LOG_INFO("Using external blank placeholder: %s", path);
} else {
blank = _init_internal();
US_LOG_INFO("Using internal blank placeholder");
}
us_blank_s *us_blank_init(void) {
us_blank_s *blank;
US_CALLOC(blank, 1);
blank->ft = us_frametext_init();
blank->raw = blank->ft->frame;
blank->jpeg = us_frame_init();
us_blank_draw(blank, "< NO LIVE VIDEO >", 640, 480);
return blank;
}
static us_frame_s *_init_internal(void) {
us_frame_s *const blank = us_frame_init();
us_frame_set_data(blank, US_BLANK_JPEG_DATA, US_BLANK_JPEG_DATA_SIZE);
blank->width = US_BLANK_JPEG_WIDTH;
blank->height = US_BLANK_JPEG_HEIGHT;
blank->format = V4L2_PIX_FMT_JPEG;
return blank;
void us_blank_draw(us_blank_s *blank, const char *text, uint width, uint height) {
us_frametext_draw(blank->ft, text, width, height);
us_cpu_encoder_compress(blank->raw, blank->jpeg, 95);
}
static us_frame_s *_init_external(const char *path) {
FILE *fp = NULL;
us_frame_s *blank = us_frame_init();
blank->format = V4L2_PIX_FMT_JPEG;
if ((fp = fopen(path, "rb")) == NULL) {
US_LOG_PERROR("Can't open blank placeholder '%s'", path);
goto error;
}
# define CHUNK_SIZE ((size_t)(100 * 1024))
while (true) {
if (blank->used + CHUNK_SIZE >= blank->allocated) {
us_frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
}
const size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
blank->used += readed;
if (readed < CHUNK_SIZE) {
if (feof(fp)) {
break;
} else {
US_LOG_PERROR("Can't read blank placeholder");
goto error;
}
}
}
# undef CHUNK_SIZE
us_frame_s *const decoded = us_frame_init();
if (us_unjpeg(blank, decoded, false) < 0) {
us_frame_destroy(decoded);
goto error;
}
blank->width = decoded->width;
blank->height = decoded->height;
us_frame_destroy(decoded);
goto ok;
error:
us_frame_destroy(blank);
blank = NULL;
ok:
US_DELETE(fp, fclose);
return blank;
void us_blank_destroy(us_blank_s *blank) {
us_frame_destroy(blank->jpeg);
us_frametext_destroy(blank->ft);
free(blank);
}

View File

@@ -2,7 +2,7 @@
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# 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 #
@@ -22,17 +22,19 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <linux/videodev2.h>
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/types.h"
#include "../libs/frame.h"
#include "../libs/unjpeg.h"
#include "data/blank_jpeg.h"
#include "../libs/frametext.h"
us_frame_s *us_blank_frame_init(const char *path);
typedef struct {
us_frametext_s *ft;
us_frame_s *raw;
us_frame_s *jpeg;
} us_blank_s;
us_blank_s *us_blank_init(void);
void us_blank_destroy(us_blank_s *blank);
void us_blank_draw(us_blank_s *blank, const char *text, uint width, uint height);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,723 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 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 "blank_jpeg.h"
const unsigned US_BLANK_JPEG_WIDTH = 640;
const unsigned US_BLANK_JPEG_HEIGHT = 480;
const size_t US_BLANK_JPEG_DATA_SIZE = 13845;
const uint8_t US_BLANK_JPEG_DATA[] = {
0xFF, 0xD8, 0xFF, 0xE1, 0x09, 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, 0x00, 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, 0x00, 0x2C, 0x50, 0x68, 0x6F, 0x74, 0x6F, 0x73, 0x68, 0x6F,
0x70, 0x20, 0x33, 0x2E, 0x30, 0x00, 0x38, 0x42, 0x49, 0x4D, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xD4, 0x1D,
0x8C, 0xD9, 0x8F, 0x00, 0xB2, 0x04, 0xE9, 0x80, 0x09, 0x98, 0xEC, 0xF8, 0x42, 0x7E, 0xFF, 0xDB, 0x00, 0x84, 0x00, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFF, 0xDD, 0x00, 0x04, 0x00, 0x50, 0xFF, 0xEE, 0x00, 0x0E, 0x41, 0x64,
0x6F, 0x62, 0x65, 0x00, 0x64, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x01, 0xE0, 0x02, 0x80, 0x03,
0x00, 0x11, 0x00, 0x01, 0x11, 0x01, 0x02, 0x11, 0x01, 0xFF, 0xC4, 0x00, 0x7D, 0x00, 0x01, 0x00, 0x02, 0x03, 0x01, 0x01,
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x07, 0x08, 0x09, 0x05, 0x06, 0x02, 0x03,
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
0x01, 0x00, 0x01, 0x04, 0x02, 0x01, 0x03, 0x02, 0x03, 0x04, 0x08, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x03, 0x02, 0x04,
0x05, 0x06, 0x01, 0x07, 0x08, 0x09, 0x11, 0x12, 0x0A, 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, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xDA, 0x00, 0x0C,
0x03, 0x00, 0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3F, 0x00, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x06, 0x4C, 0xE9, 0x5B, 0x0B, 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, 0x02, 0xF6, 0x5F, 0x6E, 0x72, 0x56, 0x50, 0x51, 0xF6, 0xF0, 0xD3, 0xD7,
0xF7, 0xE2, 0xA6, 0x9B, 0x0A, 0xEB, 0xA2, 0xC4, 0x23, 0x22, 0x0D, 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, 0x0C, 0x85, 0x72, 0x59, 0xDD, 0xD1, 0x1C, 0x91, 0x49, 0xCC, 0x55, 0x57, 0x1D, 0x3C, 0xFE,
0x9A, 0xA9, 0xE7, 0x8E, 0x78, 0xE3, 0x9E, 0x39, 0x06, 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, 0x0B, 0xAD, 0x45, 0x73, 0x0D,
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, 0x00, 0xC8, 0x6B, 0xE7, 0xA6, 0xFF, 0x00, 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, 0x0A, 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, 0x01, 0xDA,
0xEE, 0xC7, 0x83, 0xD0, 0x8F, 0xD3, 0x07, 0x27, 0xFF, 0x00, 0x0E, 0x76, 0x0E, 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, 0x06, 0xEB, 0x45, 0x73, 0x64, 0x32, 0xB9, 0x1B,
0x0F, 0xC3, 0x57, 0x4D, 0x78, 0x3C, 0xBC, 0xB7, 0x59, 0x09, 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, 0x0F, 0xC0, 0x3A, 0x19, 0xE9, 0xA3, 0xE0, 0x76, 0x6B, 0xD4, 0x03, 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, 0x0F, 0x0C, 0x74, 0x8F, 0x21, 0x3B, 0x3F, 0x09, 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, 0x0C, 0x90, 0x43, 0x0C, 0x94, 0x53, 0x5D, 0x36, 0x91, 0xD3, 0x6F, 0x10, 0x3F,
0x5B, 0x0F, 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, 0x0F, 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, 0x0C, 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, 0x0D, 0x5B, 0xAF, 0x75, 0x3C, 0x0E, 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, 0x01, 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, 0x0F, 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, 0x00, 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, 0x04, 0x7B,
0xB6, 0x1B, 0xA1, 0x7A, 0x7F, 0x23, 0x6F, 0x8C, 0x8A, 0x2B, 0x9A, 0xAE, 0xE6, 0xBC, 0xCC, 0xE4, 0x3A, 0xDA, 0xC7, 0x0B,
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, 0x07, 0x35, 0x5F, 0x19, 0x24, 0xF3, 0x77, 0xC3, 0x5E, 0xEE, 0xD6,
0x35, 0x8D, 0x06, 0xE3, 0x9C, 0x75, 0x38, 0x9D, 0x16, 0xF7, 0x74, 0x9F, 0x75, 0xD1, 0xBB, 0x0A, 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, 0x0B, 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, 0x07, 0xF7, 0x96, 0x5B, 0xA9, 0xBB, 0x02, 0x19, 0x72, 0x9A, 0xFD, 0xF7, 0x17, 0x19, 0xAE,
0xB2, 0xEC, 0x2B, 0x7B, 0x4A, 0xE0, 0xC2, 0x76, 0x0E, 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, 0x04, 0xB6, 0xF3, 0xCC, 0x1A,
0x5A, 0x09, 0x44, 0xFA, 0x03, 0xF8, 0x73, 0xE3, 0x0F, 0x93, 0x7D, 0x45, 0xE4, 0x06, 0x6F, 0xBE, 0xFA, 0x5F, 0x4D, 0xED,
0x0C, 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, 0x0D, 0x1C, 0x47, 0x0C, 0x19, 0x2B, 0xE8, 0x62,
0x8E, 0x9F, 0x7F, 0x6A, 0x23, 0x8E, 0xE6, 0x5A, 0x23, 0xA3, 0x8F, 0x7F, 0x7E, 0x7D, 0xA9, 0xA6, 0x9E, 0x38, 0x07, 0x9A,
0x09, 0x48, 0xFA, 0x14, 0x7A, 0x66, 0xF5, 0x9F, 0x79, 0xF4, 0xF7, 0x72, 0x79, 0x05, 0xE4, 0x87, 0x5E, 0x61, 0x77, 0x3D,
0x47, 0xB2, 0xB1, 0x99, 0xDE, 0x9B, 0xEA, 0x5C, 0x5E, 0xC9, 0x88, 0xB0, 0xC8, 0xFE, 0x03, 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, 0x06, 0xBF, 0x82, 0x57, 0x7F, 0x4E, 0xFF, 0x00,
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57, 0xE8, 0x6F, 0xDF, 0x97, 0x4C, 0xFF, 0x00, 0x15, 0xFA, 0xEF, 0xF9,
0xBF, 0x0E, 0x0B, 0x0F, 0x7C, 0xC4, 0xF3, 0x8B, 0xAA, 0x7C, 0x4D, 0xED, 0x0F, 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, 0x09, 0x34, 0x5D, 0x82, 0x3D, 0xBE, 0xEA, 0xD3, 0x29, 0x3C, 0xDC, 0x73, 0x1D,
0x9F, 0x15, 0x45, 0x34, 0xBE, 0xD6, 0xB4, 0xDC, 0x55, 0x48, 0x45, 0x0F, 0xD6, 0x3B, 0xD2, 0xB2, 0xEF, 0xC3, 0xFD, 0xBA,
0x6E, 0xFE, 0xE8, 0xEC, 0x54, 0xF9, 0x2F, 0x17, 0x7B, 0x0B, 0x33, 0xF2, 0x92, 0xCA, 0xC2, 0x9A, 0xEE, 0xFF, 0x00, 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, 0x00, 0xEA, 0x34, 0xD7, 0x72, 0x9B, 0x7F, 0x9E, 0x1E,
0x3B, 0x6A, 0x78, 0x48, 0xA8, 0xB8, 0xCD, 0x6D, 0x1E, 0x3F, 0xE9, 0x3A, 0xEE, 0x22, 0x09, 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, 0x00, 0x49, 0x78, 0xF7,
0x3C, 0x78, 0x1D, 0xEB, 0x3D, 0x06, 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, 0x00, 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, 0x0E, 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, 0x0E, 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, 0x09, 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, 0x09, 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, 0x07, 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, 0x0E, 0xBD, 0x7A, 0x12, 0xFF, 0x00, 0x89, 0xEF, 0x8F, 0xBF, 0xE9, 0x3D, 0xCB, 0xFF,
0x00, 0x64, 0xFB, 0x04, 0x19, 0x07, 0xEA, 0x0D, 0xFF, 0x00, 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, 0x0C, 0xEE, 0x57, 0x67,
0xDA, 0xB6, 0x7C, 0xAD, 0xF6, 0x73, 0x61, 0xD8, 0x73, 0x97, 0xD7, 0x19, 0x2C, 0xC6, 0x6B, 0x31, 0x92, 0xB8, 0x92, 0xEA,
0xFF, 0x00, 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, 0x05, 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,
0x09, 0x2E, 0x7D, 0x32, 0x7F, 0xBF, 0x7F, 0x26, 0xFF, 0x00, 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,
0x0D, 0x37, 0x16, 0x17, 0x14, 0x84, 0x28, 0x7C, 0x99, 0xF1, 0xAF, 0xB5, 0x7C, 0x4A, 0xEE, 0x5D, 0xBB, 0xA3, 0xBB, 0x8B,
0x07, 0x56, 0x1B, 0x6D, 0xD5, 0x6E, 0xB8, 0xE6, 0x0B, 0xBB, 0x7F, 0xBB, 0x36, 0x0B, 0x69, 0xC0, 0x5D, 0x55, 0x25, 0x58,
0x5D, 0xBF, 0x55, 0xC8, 0xC9, 0x14, 0x3C, 0x65, 0x35, 0xCC, 0xED, 0xB4, 0x7C, 0xD7, 0x04, 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, 0x08, 0x7B, 0x67, 0xFF, 0x00, 0xBF, 0x73, 0x5F, 0xEA, 0xD9, 0x1F, 0xF7, 0x93, 0x03, 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, 0x09, 0xC4, 0xF1, 0xF1, 0x3D, 0xFF, 0x00, 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, 0x00, 0xA4, 0x92, 0x5E,
0x7F, 0x3B, 0x9E, 0x2A, 0xE4, 0x30, 0xE7, 0xAF, 0x17, 0x8F, 0x9A, 0x8F, 0x92, 0xFE, 0x35, 0xF4, 0xAF, 0xA8, 0xFF, 0x00,
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, 0x07, 0x19, 0x6A, 0xE9, 0x97, 0x9A, 0x78, 0xB4, 0xE7, 0x8A, 0x42, 0x22, 0xC0, 0x98, 0xFF, 0x00, 0xD3, 0x25,
0xFB, 0x8D, 0xF2, 0x7B, 0xF8, 0xAF, 0xA7, 0x7F, 0x28, 0x5C, 0x02, 0x1E, 0xD9, 0xFF, 0x00, 0xEF, 0xDC, 0xD7, 0xFA, 0xB6,
0x47, 0xFD, 0xE4, 0xC0, 0xF2, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57, 0xE8, 0x6F, 0xDF, 0x97, 0x4C, 0xFF, 0x00, 0x15, 0xFA, 0xEF, 0xF9, 0xBF, 0x0E,
0x09, 0x43, 0xFD, 0x4F, 0xFF, 0x00, 0xDC, 0x5E, 0x17, 0x7F, 0xAB, 0x77, 0xF7, 0xFB, 0x3E, 0x9D, 0x06, 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, 0x05, 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, 0x0F, 0xF5, 0x36, 0x68, 0xF9, 0xCC, 0xB7, 0x4D, 0xF8, 0xB7, 0xD9, 0x56, 0x34, 0x49, 0x36, 0xB5,
0xA8, 0x76, 0x26, 0xFF, 0x00, 0xA9, 0xE6, 0x64, 0x86, 0x3A, 0xE5, 0x86, 0x2C, 0x87, 0x60, 0x6B, 0x9A, 0xFE, 0x57, 0x01,
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, 0x00, 0x1B, 0xDD, 0xE9, 0x87, 0xA2, 0xE7, 0xFB, 0x0F, 0xD4, 0x1B, 0xC4, 0x1C,
0x1E, 0xB9, 0x1C, 0xF2, 0x5E, 0xE2, 0xFB, 0xDB, 0x41, 0xDE, 0xAF, 0x79, 0x82, 0x89, 0xAB, 0xE6, 0x2C, 0x07, 0x5A, 0x66,
0xAD, 0xFB, 0x0B, 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, 0x00, 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, 0x0C, 0x04, 0x76, 0x3F, 0xB3, 0x6F, 0x77, 0x5D, 0x42, 0xBB, 0x7D, 0x0F, 0xB0, 0x2E, 0x69,
0xAA, 0x9A, 0x29, 0x92, 0xFF, 0x00, 0x3B, 0x87, 0xAA, 0xDA, 0xFB, 0x43, 0xD9, 0xA4, 0x96, 0x1E, 0x2B, 0x8A, 0xBB, 0x7B,
0x6B, 0x5C, 0x0D, 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, 0x09, 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, 0x01, 0xED, 0xFD, 0x41, 0xBF, 0xE2, 0x35, 0xB4, 0x7F, 0x0A, 0x3A, 0xB3, 0xFE, 0x91, 0x78, 0x0E, 0xDB, 0xFA, 0xD6,
0x63, 0x2E, 0x3B, 0x67, 0xD2, 0x2B, 0xAA, 0xBB, 0x13, 0x47, 0x8F, 0x8B, 0x9D, 0x5B, 0x05, 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, 0x00, 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, 0x0F, 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, 0x07, 0xA2, 0xE6, 0x2F,
0xF4, 0xBE, 0xFF, 0x00, 0xD5, 0xEB, 0xC0, 0xEB, 0xB7, 0xD7, 0xF9, 0x1C, 0x6D, 0x10, 0x6B, 0x7A, 0x8E, 0x4E, 0x08, 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, 0x0D, 0xFF, 0x00, 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, 0x09,
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, 0x0E, 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, 0x04, 0x2E, 0x73, 0xFF, 0x00, 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, 0x0F, 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, 0x0B, 0x15, 0x67, 0x67, 0x8E,
0xB1, 0x8A, 0x3A, 0x21, 0xB4, 0xB5, 0x82, 0x2E, 0x3E, 0x3C, 0xD5, 0xF1, 0xF9, 0xD5, 0x55, 0x55, 0x07, 0x4A, 0x3C, 0x1C,
0xF2, 0x83, 0xD3, 0x33, 0xC9, 0xDE, 0xB9, 0xD9, 0xBC, 0x01, 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,
0x06, 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, 0x08, 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, 0x0C,
0xD5, 0x57, 0x17, 0x3C, 0xFC, 0xE3, 0xAB, 0x8E, 0x02, 0x52, 0x1F, 0x4C, 0x97, 0xEE, 0x37, 0xC9, 0xEF, 0xE2, 0xBE, 0x9D,
0xFC, 0xA1, 0x70, 0x08, 0x7B, 0x67, 0xFF, 0x00, 0xBF, 0x73, 0x5F, 0xEA, 0xD9, 0x1F, 0xF7, 0x93, 0x03, 0xC9, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF,
0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57,
0xE8, 0x6F, 0xDF, 0x97, 0x4C, 0xFF, 0x00, 0x15, 0xFA, 0xEF, 0xF9, 0xBF, 0x0E, 0x09, 0x43, 0xFD, 0x4F, 0xFF, 0x00, 0xDC,
0x5E, 0x17, 0x7F, 0xAB, 0x77, 0xF7, 0xFB, 0x3E, 0x9D, 0x04, 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, 0x00, 0xFA, 0xC7, 0xAF, 0xFF, 0x00, 0xDD, 0x5E, 0xD8, 0x07, 0x49, 0xFD, 0x3D, 0xBC,
0x99, 0xE8, 0xBF, 0x55, 0x8F, 0x08, 0x72, 0x5E, 0x08, 0x79, 0x29, 0x99, 0x8F, 0x8E, 0xEA, 0xD6, 0xF4, 0x5B, 0x6D, 0x52,
0xFE, 0x3B, 0xC9, 0xAC, 0x6D, 0x76, 0x4D, 0xAB, 0x09, 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, 0x0E, 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, 0x0B, 0xA5, 0xEA, 0xF9, 0x7C, 0xF4, 0x76,
0x93, 0x4F, 0xF9, 0xD3, 0x5E, 0x56, 0xFA, 0xC6, 0xD6, 0x5B, 0x0C, 0x35, 0xA4, 0x71, 0xFB, 0xC9, 0x2D, 0xC5, 0xDC, 0xB0,
0x41, 0x0C, 0x54, 0xD5, 0x24, 0x95, 0xD3, 0x45, 0x3C, 0xD5, 0xC0, 0x4C, 0x2B, 0xD3, 0x9F, 0xC0, 0xDE, 0xB7, 0xF4, 0x90,
0xEA, 0x0D, 0xFF, 0x00, 0xCC, 0x8F, 0x34, 0xF7, 0x4D, 0x5B, 0x01, 0xDA, 0x97, 0x9A, 0xA4, 0xF8, 0xAB, 0xDA, 0x22, 0xC8,
0xDB, 0xE4, 0xF0, 0xFD, 0x65, 0xAB, 0x5C, 0xCB, 0x16, 0x47, 0x9D, 0x1F, 0x57, 0x9A, 0x0F, 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, 0x06, 0xA2, 0x03, 0xB0, 0x9E, 0x93, 0x1E, 0x9A, 0x3A, 0x07, 0xA8, 0x66, 0xC5, 0xDA, 0xB0, 0xF6, 0x07, 0x74,
0x73, 0xA0, 0xE3, 0x7A, 0xEB, 0x01, 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, 0x0D, 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, 0x0F, 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, 0x0E, 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, 0x01, 0xD5, 0x6F, 0x45, 0xFF, 0x00, 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, 0x00, 0x47, 0x6F,
0x28, 0xFC, 0x41, 0xDC, 0x73, 0x39, 0x0D, 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, 0x0C, 0x97, 0xB5, 0x41, 0x4E, 0x22, 0xF2, 0x5A, 0xA9, 0xFC, 0x3C, 0xFC, 0x49, 0x5D, 0x56,
0xD1, 0x07, 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, 0x0D, 0xC5, 0xCD, 0x72, 0xC7, 0x0D, 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, 0x02, 0x72, 0x5E, 0x13, 0xF4, 0x07, 0x60, 0x78, 0x5B, 0xE9, 0x1B, 0xD9, 0xFA, 0x3F, 0x91, 0x7C, 0x6B,
0xBA, 0x06, 0xCD, 0xFD, 0x02, 0xEF, 0xAD, 0xCF, 0x33, 0x63, 0x79, 0xB1, 0xE3, 0x24, 0xB6, 0xD5, 0xAC, 0xF7, 0x0D, 0x72,
0xFE, 0x3C, 0x3E, 0x2B, 0x61, 0xCC, 0xF1, 0x2D, 0x18, 0x3B, 0x7C, 0xDD, 0x55, 0xF1, 0x45, 0x32, 0x45, 0x05, 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, 0x00, 0xEC, 0xE7, 0x76, 0xFF, 0x00,
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,
0x05, 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, 0x0F, 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, 0x0F, 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, 0x0B, 0x8E, 0xCC, 0xEA, 0xF8, 0x1B, 0xAD, 0xC3, 0x1F, 0x6B, 0x73,
0x4F, 0x19, 0x3B, 0x0E, 0xA3, 0xEB, 0xF9, 0xAD, 0xEC, 0x75, 0x4B, 0x1C, 0xBD, 0x50, 0xF1, 0x0D, 0x53, 0xDD, 0x76, 0x1E,
0xE9, 0x8D, 0x9A, 0xFA, 0xEA, 0xE3, 0x9E, 0x3E, 0x77, 0x32, 0x61, 0xAE, 0x3E, 0xEF, 0x1C, 0xF1, 0x71, 0x57, 0x35, 0x04,
0x46, 0x01, 0xB0, 0xBE, 0x28, 0xF9, 0x09, 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,
0x01, 0x28, 0x5F, 0x5E, 0x8F, 0x1C, 0x75, 0x5F, 0x26, 0x3C, 0x62, 0xEA, 0x0F, 0x50, 0xBE, 0x93, 0xE2, 0x1D, 0x86, 0x8D,
0x4F, 0x59, 0xD7, 0x78, 0xD9, 0xB2, 0xD8, 0xC8, 0x38, 0xAE, 0x6D, 0x8F, 0xA3, 0x7B, 0x02, 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, 0x00,
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, 0x00, 0xC0, 0x7D, 0x99, 0xD3, 0x5D, 0xBF, 0xD2, 0xB9, 0x5C, 0x7E, 0x0B,
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, 0x00, 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, 0x01, 0x8D, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xD7, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 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, 0x0D, 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, 0x0D, 0xB6, 0xF3, 0x53, 0xCD, 0xCE, 0xCC, 0xF3, 0x9F,
0x6D, 0xEA, 0xFD, 0xF7, 0xB6, 0xB1, 0x58, 0x1B, 0x3D, 0xCB, 0xAE, 0xFA, 0x87, 0x03, 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, 0x0A, 0xF6, 0x0C, 0x9E, 0x0F, 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, 0x01, 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, 0x03, 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, 0x00, 0x99, 0xDE, 0x4A, 0xF9, 0x8B, 0xB4, 0xD1, 0xB4, 0xF9, 0x03, 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, 0x0C, 0xB7, 0x9C, 0x41, 0x5E, 0x42, 0xF2, 0x88, 0xE8, 0xE6, 0xEA, 0xE2, 0x7A, 0xF8, 0xF9, 0xF2,
0x1A, 0xBA, 0x00, 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, 0x0C, 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, 0x03, 0xBE, 0x5A, 0x57, 0xD4, 0xB5, 0xE5, 0x3E, 0x23, 0x03, 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, 0x00, 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, 0x09, 0xE9, 0xF9, 0xF3, 0x58, 0x73, 0xDB, 0xC6, 0xCF, 0x22, 0xFB, 0x2F, 0xC5,
0x1E, 0xE3, 0xD5, 0xBB, 0xD7, 0xA8, 0x6E, 0xF1, 0x16, 0x5B, 0xFE, 0x9F, 0x06, 0x7E, 0xDF, 0x0B, 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, 0x03, 0xD6, 0xF2, 0x93, 0xCA, 0x4E, 0xD7, 0xF3, 0x0B,
0xB5, 0xEE, 0xFB, 0xA3, 0xBA, 0x2E, 0xF0, 0x77, 0xDB, 0xC5, 0xF6, 0x0F, 0x0B, 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, 0x00, 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, 0x01, 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, 0x0C, 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, 0x00, 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, 0x07, 0x1F, 0x7C, 0xB3, 0xF5,
0x0C, 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, 0x0F, 0xBC, 0xE5, 0xEF, 0xBF, 0x06, 0xB6, 0x4D, 0xC7, 0x6B, 0xE8, 0x4C, 0x86, 0xB1, 0x8E, 0xCC, 0x6F, 0x58, 0x3B,
0x0D, 0x7B, 0x3D, 0x2E, 0xCD, 0xAD, 0xDB, 0x6C, 0x90, 0xD7, 0x8D, 0xC7, 0x5F, 0xD5, 0x92, 0xB7, 0x8E, 0xD6, 0x0B, 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, 0x0F, 0x81, 0x06, 0xED,
0xF8, 0x2B, 0xE7, 0x97, 0x73, 0x78, 0x0D, 0xDA, 0xB7, 0x3D, 0x8B, 0xD5, 0x92, 0xDA, 0xE6, 0xB0, 0xBB, 0x06, 0x3B, 0x9C,
0x46, 0xF9, 0xD7, 0x19, 0xEB, 0x9B, 0xD8, 0xF5, 0x2D, 0xE3, 0x1F, 0x0C, 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, 0x0F, 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, 0x07, 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, 0x0F, 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, 0x07, 0x00, 0x0E, 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, 0x0F, 0x99, 0xF0,
0xFB, 0xD4, 0x9F, 0xC9, 0xEF, 0x06, 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, 0x0E, 0x71, 0xB6, 0xBC, 0xDB, 0x4D, 0x75, 0x3C,
0x5F, 0x86, 0x86, 0x8B, 0x6A, 0xB9, 0xF7, 0xA6, 0x9E, 0x3F, 0x55, 0x5C, 0xFB, 0xF3, 0xCF, 0xF6, 0x7B, 0x06, 0xE1, 0x7F,
0xE6, 0x0D, 0xF5, 0x1A, 0xFF, 0x00, 0xF2, 0x8E, 0xA8, 0xFF, 0x00, 0xF5, 0x66, 0x23, 0xFF, 0x00, 0xEB, 0x07, 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, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xD9,
};

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